diff --git a/README.md b/README.md index 3828191..464171a 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ - [GraphQL](https://graphql.org/) - [docker](https://www.docker.com/) - [drone](https://drone.cool/) +- [nacos](https://nacos.io/?spm=5238cd80.47ee59c.0.0.189fcd36fF0ca9) - [nginx](https://nginx.org/) - [Vue3](https://cn.vuejs.org/) - [Jsx](https://legacy.reactjs.org/docs/introducing-jsx.html) @@ -206,6 +207,8 @@ export default class Header extends TSX()(Vue) implements Props { - [x] 基于DDD理念改造 - [x] 基于 vite-plugin-federation 拆分领域模块为联邦模块,实现微前端 - [x] 基于Vue3+OOP+JSX改造项目代码 +- [x] 基于Nacos开发Nodejs模块,实现各模块的服务注册&发现,统一管理远程地址 +- [ ] pnpm 统一管理集中项目依赖,运行&构建等... - [ ] 领域模块实现 docker 自动化构建部署上线 - [ ] Deno集成GraphQL - [ ] .... diff --git a/front-end/app-product/package.json b/front-end/app-product/package.json index 07cd316..389e506 100644 --- a/front-end/app-product/package.json +++ b/front-end/app-product/package.json @@ -14,21 +14,22 @@ "type-check": "vue-tsc --build" }, "dependencies": { + "nacos-federation": "^1.0.2", "pinia": "^3.0.3", - "vue": "^3.5.18", - "vue-router": "^4.0.6" + "vue": "^3.5.21", + "vue-router": "^4.5.1" }, "devDependencies": { "@originjs/vite-plugin-federation": "^1.4.1", "@tsconfig/node22": "^22.0.2", - "@types/node": "^22.16.5", + "@types/node": "^22.18.6", "@vitejs/plugin-vue": "^6.0.1", - "@vitejs/plugin-vue-jsx": "^5.0.1", + "@vitejs/plugin-vue-jsx": "^5.1.1", "@vue/tsconfig": "^0.7.0", "npm-run-all2": "^8.0.4", - "typescript": "~5.8.0", - "vite": "^7.0.6", - "vite-plugin-vue-devtools": "^8.0.0", - "vue-tsc": "^3.0.4" + "typescript": "~5.8.3", + "vite": "^7.1.6", + "vite-plugin-vue-devtools": "^8.0.2", + "vue-tsc": "^3.0.7" } } \ No newline at end of file diff --git a/front-end/app-product/vite.config.ts b/front-end/app-product/vite.config.ts index d3f062a..5e9d7a1 100644 --- a/front-end/app-product/vite.config.ts +++ b/front-end/app-product/vite.config.ts @@ -1,51 +1,63 @@ -import {defineConfig} from 'vite'; +import { defineConfig } from 'vite'; import vue from '@vitejs/plugin-vue'; import vueJsx from '@vitejs/plugin-vue-jsx' import vueDevTools from 'vite-plugin-vue-devtools' import federation from '@originjs/vite-plugin-federation'; -import {fileURLToPath, URL} from "node:url"; +import { fileURLToPath, URL } from "node:url"; +import { nacosServiceManage } from "nacos-federation"; -export default defineConfig({ - server: { - port: 1301, - host: '0.0.0.0', - strictPort: true, - }, - plugins: [ - federation({ - name: 'product', - remotes: { - shared: 'http://localhost:1333/assets/remoteEntry.js' - }, - exposes: { // 暴露给主应用的资源(按功能分组) - './ProductDomain': './src/domains/product/index.ts', // 领域层(实体、服务、接口等) - './ProductRouter': './src/features/product/router/index.ts' // 商品路由 - }, - shared: { - vue: {import: false, generate: false, requiredVersion: '^3.5.18'}, - 'vue-router': {import: false, generate: false, requiredVersion: '^4.0.6'}, - pinia: {import: false, generate: false, requiredVersion: '^3.0.3'}, - } - }), - vue(), - vueJsx(), - vueDevTools() - ], - build: { - target: 'esnext', - cssCodeSplit: true, - rollupOptions: { - output: { - minifyInternalExports: false - } - } - }, - resolve: { - alias: { - '@': fileURLToPath(new URL('./src/', import.meta.url)), - '@shared': fileURLToPath(new URL('./src/shared/', import.meta.url)), - '@features': fileURLToPath(new URL('./src/features/', import.meta.url)), - '@domains': fileURLToPath(new URL('./src/domains/', import.meta.url)) + +export default defineConfig(async () => { + const client = new nacosServiceManage({ + serverList: '192.168.31.100:8848', + serviceName: 'app-product', + port: 1301 + }) + const sharedUrl = await client.find("app-shared") + + return { + server: { + port: 1301, + host: '0.0.0.0', + strictPort: true, + cors: true }, - }, + plugins: [ + federation({ + name: 'product', + remotes: { + shared: `${ sharedUrl }/assets/remoteEntry.js` + }, + exposes: { // 暴露给主应用的资源(按功能分组) + './ProductDomain': './src/domains/product/index.ts', // 领域层(实体、服务、接口等) + './ProductRouter': './src/features/product/router/index.ts' // 商品路由 + }, + shared: { + vue: { import: false, generate: false, requiredVersion: '^3.5.18' }, + 'vue-router': { import: false, generate: false, requiredVersion: '^4.0.6' }, + pinia: { import: false, generate: false, requiredVersion: '^3.0.3' }, + } + }), + vue(), + vueJsx(), + vueDevTools() + ], + build: { + target: 'esnext', + cssCodeSplit: true, + rollupOptions: { + output: { + minifyInternalExports: false + } + } + }, + resolve: { + alias: { + '@': fileURLToPath(new URL('./src/', import.meta.url)), + '@shared': fileURLToPath(new URL('./src/shared/', import.meta.url)), + '@features': fileURLToPath(new URL('./src/features/', import.meta.url)), + '@domains': fileURLToPath(new URL('./src/domains/', import.meta.url)) + }, + }, + } }); \ No newline at end of file diff --git a/front-end/app-shared/package.json b/front-end/app-shared/package.json index 3b3d874..d345f07 100644 --- a/front-end/app-shared/package.json +++ b/front-end/app-shared/package.json @@ -13,11 +13,12 @@ "type-check": "vue-tsc --build" }, "dependencies": { - "axios": "^1.12.2" + "axios": "^1.12.2", + "nacos-federation": "^1.0.2" }, "devDependencies": { "@originjs/vite-plugin-federation": "^1.4.1", - "typescript": "~5.8.0", - "vite": "^7.0.6" + "typescript": "~5.8.3", + "vite": "^7.1.6" } } diff --git a/front-end/app-shared/vite.config.ts b/front-end/app-shared/vite.config.ts index 47f603b..e5c262f 100644 --- a/front-end/app-shared/vite.config.ts +++ b/front-end/app-shared/vite.config.ts @@ -1,35 +1,44 @@ -import {defineConfig} from 'vite' -import federation from '@originjs/vite-plugin-federation'; - +import { defineConfig, UserConfig } from 'vite' +import federation from "@originjs/vite-plugin-federation"; +import { nacosServiceManage } from "nacos-federation"; // https://vite.dev/config/ -export default defineConfig({ - server: { - port: 1333, - host: '0.0.0.0', - strictPort: true, - }, - plugins: [ - federation({ - name: 'shared', - exposes: { - "./config": "./src/config/index.ts", - "./infra": "./src/infra/index.ts", - "./utils": "./src/utils/index.ts", - }, - shared: { - axios: {requiredVersion: '^1.12.2'} - } - }) - ], - build: { - target: 'esnext', - minify: false, - cssCodeSplit: true, - rollupOptions: { - input: './src/index.ts', - output: { - format: 'esm', - minifyInternalExports: false +export default defineConfig(async (): Promise => { + new nacosServiceManage({ + serverList: '192.168.31.100:8848', + serviceName: 'app-shared', + port: 1333 + }) + + return { + server: { + port: 1333, + host: '0.0.0.0', + strictPort: true, + cors: true + }, + plugins: [ + federation({ + name: 'shared', + exposes: { + "./config": "./src/config/index.ts", + "./infra": "./src/infra/index.ts", + "./utils": "./src/utils/index.ts", + }, + shared: { + axios: { requiredVersion: '^1.12.2' } + } + }) + ], + build: { + target: 'esnext', + minify: false, + cssCodeSplit: true, + rollupOptions: { + input: './src/index.ts', + output: { + format: 'esm', + minifyInternalExports: false + } } } } diff --git a/front-end/main-app/package.json b/front-end/main-app/package.json index 5a9304e..65673e6 100644 --- a/front-end/main-app/package.json +++ b/front-end/main-app/package.json @@ -11,18 +11,19 @@ "type-check": "vue-tsc --build" }, "dependencies": { + "nacos-federation": "^1.0.2", "pinia": "^3.0.3", - "vue": "^3.5.18", + "vue": "^3.5.21", "vue-facing-decorator": "^4.0.1", - "vue-router": "^4.0.6" + "vue-router": "^4.5.1" }, "devDependencies": { "@originjs/vite-plugin-federation": "^1.4.1", "@types/node": "^24.5.2", "@vue3-oop/plugin-vue-jsx": "^1.5.1", "tslib": "^2.8.1", - "typescript": "~5.8.0", - "vite": "^7.0.6", - "vite-plugin-vue-devtools": "^8.0.0" + "typescript": "~5.8.3", + "vite": "^7.1.6", + "vite-plugin-vue-devtools": "^8.0.2" } } diff --git a/front-end/main-app/vite.config.ts b/front-end/main-app/vite.config.ts index 29c783c..99a5694 100644 --- a/front-end/main-app/vite.config.ts +++ b/front-end/main-app/vite.config.ts @@ -3,44 +3,56 @@ import { fileURLToPath, URL } from 'node:url' import { defineConfig } from 'vite' import vueJsx from '@vue3-oop/plugin-vue-jsx' import federation from '@originjs/vite-plugin-federation'; +import { nacosServiceManage } from "nacos-federation"; -export default defineConfig({ - server: { - port: 1300, - host: '0.0.0.0', - strictPort: true, - }, - plugins: [ - vueJsx(), - federation({ - name: 'mainApp', // 主应用模块名(联邦模块需通过此名引用) - remotes: { // 配置远程联邦模块地址(开发/生产需对应) - product: 'http://localhost:1301/assets/remoteEntry.js', - shared: 'http://localhost:1333/assets/remoteEntry.js' - }, - shared: { - vue: { requiredVersion: '^3.5.18' }, - 'vue-router': { requiredVersion: '^4.0.6' }, - pinia: { requiredVersion: '^3.0.3' } - } - }) - ], - build: { - target: 'esnext', - minify: false, - cssCodeSplit: true, - rollupOptions: { - output: { - minifyInternalExports: false - } - } - }, - resolve: { - alias: { - '@': fileURLToPath(new URL('./src/app/', import.meta.url)), - '@shared': fileURLToPath(new URL('./src/shared/', import.meta.url)), - '@features': fileURLToPath(new URL('./src/features/', import.meta.url)), - '@domains': fileURLToPath(new URL('./src/domains/', import.meta.url)) + +export default defineConfig(async () => { + const client = new nacosServiceManage({ + serverList: '192.168.31.100:8848', + serviceName: 'main-app', + port: 1300 + }) + const productUrl = await client.find("app-product") + const sharedUrl = await client.find("app-shared") + + return { + server: { + port: 1300, + host: '0.0.0.0', + strictPort: true, }, - }, + plugins: [ + vueJsx(), + federation({ + name: 'mainApp', // 主应用模块名(联邦模块需通过此名引用) + remotes: { // 配置远程联邦模块地址(开发/生产需对应) + product: `${ productUrl }/assets/remoteEntry.js`, + shared: `${ sharedUrl }/assets/remoteEntry.js` + }, + shared: { + vue: { requiredVersion: '^3.5.18' }, + 'vue-router': { requiredVersion: '^4.0.6' }, + pinia: { requiredVersion: '^3.0.3' } + } + }) + ], + build: { + target: 'esnext', + minify: false, + cssCodeSplit: true, + rollupOptions: { + output: { + minifyInternalExports: false + } + } + }, + resolve: { + alias: { + '@': fileURLToPath(new URL('./src/app/', import.meta.url)), + '@shared': fileURLToPath(new URL('./src/shared/', import.meta.url)), + '@features': fileURLToPath(new URL('./src/features/', import.meta.url)), + '@domains': fileURLToPath(new URL('./src/domains/', import.meta.url)) + }, + }, + } }) diff --git a/front-end/nacos-federation/README.md b/front-end/nacos-federation/README.md new file mode 100644 index 0000000..e69de29 diff --git a/front-end/nacos-federation/package.json b/front-end/nacos-federation/package.json new file mode 100644 index 0000000..6abf36f --- /dev/null +++ b/front-end/nacos-federation/package.json @@ -0,0 +1,28 @@ +{ + "name": "nacos-federation", + "version": "1.0.2", + "description": "Nacos服务注册与发现插件,用于模块联邦", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "tsc", + "prepublishOnly": "npm run build" + }, + "dependencies": { + "nacos": "^2.6.0" + }, + "devDependencies": { + "@types/node": "^20.19.17", + "typescript": "~5.8.3" + }, + "keywords": [ + "nacos", + "module-federation", + "service-discovery" + ], + "author": "", + "license": "MIT" +} diff --git a/front-end/nacos-federation/src/index.ts b/front-end/nacos-federation/src/index.ts new file mode 100644 index 0000000..9c28ad6 --- /dev/null +++ b/front-end/nacos-federation/src/index.ts @@ -0,0 +1,2 @@ +export * from './nacosServiceManage'; +export * from './utils'; diff --git a/front-end/nacos-federation/src/nacosServiceManage.ts b/front-end/nacos-federation/src/nacosServiceManage.ts new file mode 100644 index 0000000..78d9d9a --- /dev/null +++ b/front-end/nacos-federation/src/nacosServiceManage.ts @@ -0,0 +1,67 @@ +import { NacosNamingClient } from 'nacos'; +// @ts-ignore +import { formatServiceUrl, getLocalIP } from "./utils"; + +const logger = console; + + +export interface RegisterOptions { + serverList: string; + /** 服务名称(如 'main-app') */ + serviceName: string; + /** 服务端口号 */ + port: number; + /** 命名空间(用于环境隔离,默认 'public') */ + namespace?: string; +} + + +export class nacosServiceManage { + + public nacosClient!: NacosNamingClient; + public initOptions!: RegisterOptions; + + + constructor(option: RegisterOptions) { + this.initOptions = option; + this.init() + } + + + async init() { + this.nacosClient = new NacosNamingClient({ + logger, + serverList: this.initOptions.serverList, + namespace: this.initOptions.namespace || 'public', + }); + await this.nacosClient.ready(); + this.reg() + } + + /** + * 注册服务到Nacos并返回客户端实例 + */ + async reg() { + const { serviceName, port } = this.initOptions; + const ip = getLocalIP(); + // @ts-ignore + await this.nacosClient.registerInstance(serviceName, { + port, ip + }) + console.log(`[nacos] 服务 ${ serviceName } 已注册:${ formatServiceUrl(ip, port) }`); + } + + /** + * 从Nacos发现服务地址 + * @param serviceName 发现选项 + * @returns 服务地址(如 'http://192.168.1.101:1301') + */ + async find(serviceName: string) { + const instances = await this.nacosClient.getAllInstances(serviceName); + if (instances.length === 0) { + throw new Error(`[nacos] 未发现服务 ${ serviceName } 的实例`); + } + return formatServiceUrl(instances[0].ip, instances[0].port); + } + +} \ No newline at end of file diff --git a/front-end/nacos-federation/src/utils.ts b/front-end/nacos-federation/src/utils.ts new file mode 100644 index 0000000..560b976 --- /dev/null +++ b/front-end/nacos-federation/src/utils.ts @@ -0,0 +1,36 @@ +import { networkInterfaces } from "os"; + +/** + * 获取本机局域网IPv4地址 + * @returns 局域网IP地址(如 '192.168.1.100') + */ +export function getLocalIP(): string { + + const interfaces = networkInterfaces(); + for (const devName in interfaces) { + const iface = interfaces[devName]; + if (!iface) continue; + + for (const alias of iface) { + if ( + alias.family === 'IPv4' && + alias.address !== '127.0.0.1' && + !alias.internal + ) { + return alias.address; + } + } + } + + return '127.0.0.1'; +} + +/** + * 格式化服务地址 + * @param ip IP地址 + * @param port 端口号 + * @returns 完整服务地址(如 'http://192.168.1.100:1300') + */ +export function formatServiceUrl(ip: string, port: number): string { + return `http://${ ip }:${ port }`; +} diff --git a/front-end/nacos-federation/test.ts b/front-end/nacos-federation/test.ts new file mode 100644 index 0000000..3680d4f --- /dev/null +++ b/front-end/nacos-federation/test.ts @@ -0,0 +1,33 @@ +// import {NacosNamingClient} from 'nacos'; +// +// const logger = console; +// +// const client = new NacosNamingClient({ +// logger, +// serverList: '192.168.31.100:8848', // replace to real nacos serverList +// namespace: 'public', +// }); +// +// await client.ready(); +// +// // registry instance +// await client.registerInstance('main-app', { +// ip: '192.168.31.101', +// port: 1300, +// }); + +// @ts-ignore +import { nacosServiceManage } from "./src/nacosServiceManage.ts"; + +const client = new nacosServiceManage({ + serverList: '192.168.31.100:8848', + serviceName: 'main-app', + port: 1300 +}) + + +setTimeout(() => { + client.find("main-app").then((client) => { + console.log("client ---- : ", client); + }) +}, 3000) diff --git a/front-end/nacos-federation/tsconfig.json b/front-end/nacos-federation/tsconfig.json new file mode 100644 index 0000000..129934d --- /dev/null +++ b/front-end/nacos-federation/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "CommonJS", + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + // 生成类型声明文件 + "declarationDir": "./dist" + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules", + "dist" + ] +}