【重要】重构 并实现新的架构
This commit is contained in:
32
front-end/vite-plugin-nacos/README.md
Normal file
32
front-end/vite-plugin-nacos/README.md
Normal file
@@ -0,0 +1,32 @@
|
||||
功能分析:
|
||||
|
||||
1 注册服务
|
||||
|
||||
子模块:直接通过nacos 注册,给 内网 的 使用
|
||||
|
||||
主模块:main-app -> Traefik -> ConfigServer(nacos-federation) -> Nacos
|
||||
|
||||
main-app 发送 http 请求
|
||||
/nacos/reg
|
||||
参数:
|
||||
ip,port
|
||||
|
||||
|
||||
|
||||
|
||||
2 获取配置
|
||||
|
||||
子模块:直接通过 nacos 获取配置
|
||||
|
||||
主模块: main-app -> Traefik -> ConfigServer(nacos-federation) -> Nacos
|
||||
|
||||
|
||||
注意:
|
||||
|
||||
|
||||
增加功能:
|
||||
|
||||
认证授权机制
|
||||
请求限流保护
|
||||
操作日志记录
|
||||
服务治理功能(如权重调整、元数据管理等)
|
||||
30
front-end/vite-plugin-nacos/package.json
Normal file
30
front-end/vite-plugin-nacos/package.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "vite-plugin-nacos",
|
||||
"version": "1.1.0",
|
||||
"description": "Vite 插件,实现Nacos服务注册与发现插件,用于模块联邦",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc -w",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"chalk": "^5.6.2",
|
||||
"nacos": "^2.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.5.2",
|
||||
"typescript": "~5.8.3",
|
||||
"vite": "^7.1.6"
|
||||
},
|
||||
"keywords": [
|
||||
"nacos",
|
||||
"module-federation",
|
||||
"service-discovery"
|
||||
],
|
||||
"author": "",
|
||||
"license": "MIT"
|
||||
}
|
||||
56
front-end/vite-plugin-nacos/src/index.ts
Normal file
56
front-end/vite-plugin-nacos/src/index.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
// 从插件实现文件导入
|
||||
export * from './vite-plugin-nacos';
|
||||
export * from './utils';
|
||||
import { log } from 'console';
|
||||
import { nacosServiceManage } from './nacosServiceManage';
|
||||
import { mergeConfig } from 'vite';
|
||||
|
||||
export const init = async (env: Record<string, string>) => {
|
||||
const nacos = await nacosServiceManage.create({
|
||||
serverList: env.VITE_NACOS_ADDRESS,
|
||||
serviceName: env.VITE_SERVER_NAME,
|
||||
port: Number(env.VITE_SERVER_PORT)
|
||||
});
|
||||
|
||||
if (env.VITE_SERVER_NAME === "main-app") {
|
||||
log("不需要服务发现,进行配置发现...")
|
||||
|
||||
const baseUrl = await nacos.getConfig("baseUrl", "DEFAULT_GROUP")
|
||||
const baseUrlConfig = baseUrl ? JSON.parse(baseUrl) : {};
|
||||
baseUrlConfig.httpTimeOut = await nacos.getConfig("httpTimeOut", "DEFAULT_GROUP")
|
||||
|
||||
// 将新配置处理一下,需要 JSON.stringify
|
||||
const customizeEnv: Record<string, Record<string, string>> = { define: {} };
|
||||
Object.keys(baseUrlConfig).forEach(key => {
|
||||
customizeEnv.define[`import.meta.env.VITE_${key.toUpperCase()}`] = JSON.stringify(baseUrlConfig[key]);
|
||||
});
|
||||
|
||||
// 将老配置处理一下,需要 JSON.stringify
|
||||
const existingDefine: Record<string, Record<string, string>> = { define: {} }
|
||||
Object.keys(env).forEach(key => {
|
||||
existingDefine.define[`import.meta.env.${key}`] = JSON.stringify(env[key])
|
||||
})
|
||||
|
||||
return mergeConfig(existingDefine, customizeEnv)
|
||||
|
||||
} else {
|
||||
log("进行服务发现,获取依赖模块的地址...")
|
||||
let modelName = []
|
||||
for (const key in env) {
|
||||
if (key.includes("VITE_MODEL_")) modelName.push(env[key])
|
||||
}
|
||||
|
||||
const modelUrl: Record<string, string> = {}
|
||||
|
||||
if (modelName.length !== 0) {
|
||||
for (const val of modelName) modelUrl[`VITE_MODEL_${val.replace("app-", "").toUpperCase()}_URL`] = await nacos.find(val)
|
||||
}
|
||||
|
||||
log("modelUrl: ", modelUrl)
|
||||
return modelUrl
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
151
front-end/vite-plugin-nacos/src/nacosServiceManage.ts
Normal file
151
front-end/vite-plugin-nacos/src/nacosServiceManage.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
import { NacosConfigClient, NacosNamingClient } from 'nacos';
|
||||
import { formatServiceUrl, getLocalIP } from "./utils";
|
||||
import chalk from 'chalk';
|
||||
|
||||
const logger = console;
|
||||
|
||||
interface RegisterOptions {
|
||||
serverList: string;
|
||||
/** 服务名称(如 'main-app') */
|
||||
serviceName: string;
|
||||
/** 服务端口号 */
|
||||
port: number;
|
||||
/** 命名空间(用于环境隔离,默认 'public') */
|
||||
namespace?: string;
|
||||
}
|
||||
|
||||
export class nacosServiceManage {
|
||||
|
||||
// 静态变量:缓存单例实例(key为配置唯一标识,value为实例)
|
||||
private static instanceMap: Map<string, nacosServiceManage> = new Map();
|
||||
|
||||
public nacosClient!: NacosNamingClient;
|
||||
public configClient!: NacosConfigClient;
|
||||
public initOptions!: RegisterOptions;
|
||||
|
||||
|
||||
private constructor(option: RegisterOptions) {
|
||||
this.initOptions = option;
|
||||
}
|
||||
|
||||
/**
|
||||
* 静态 create 方法:单例模式入口,同一配置只返回一个实例
|
||||
* @param option 初始化配置
|
||||
* @returns nacosServiceManage 实例(单例)
|
||||
*/
|
||||
public static async create(option: RegisterOptions): Promise<nacosServiceManage> {
|
||||
const configKey = this.getConfigKey(option);
|
||||
|
||||
if (this.instanceMap.has(configKey)) {
|
||||
logger.log(`[nacos-federation] 已存在 ${ option.serviceName } 的Nacos实例,直接复用`);
|
||||
return this.instanceMap.get(configKey)!;
|
||||
}
|
||||
|
||||
const instance = new nacosServiceManage(option);
|
||||
await instance.initClient();
|
||||
await instance.initConfig();
|
||||
|
||||
this.instanceMap.set(configKey, instance);
|
||||
return instance;
|
||||
}
|
||||
|
||||
// 服务注册
|
||||
public async initClient() {
|
||||
this.nacosClient = new NacosNamingClient({
|
||||
logger,
|
||||
serverList: this.initOptions.serverList,
|
||||
namespace: this.initOptions.namespace || 'public',
|
||||
});
|
||||
await this.nacosClient.ready();
|
||||
this.reg()
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成配置唯一标识:基于 serverList + namespace + serviceName(确保不同配置对应不同实例)
|
||||
*/
|
||||
private static getConfigKey(option: RegisterOptions): string {
|
||||
const namespace = option.namespace || 'public';
|
||||
return `${ option.serverList }-${ namespace }-${ option.serviceName }`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 订阅配置变化
|
||||
* @param dataId
|
||||
* @param group
|
||||
*/
|
||||
async subscribe(dataId: string, group: string = "DEFAULT_GROUP") {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
this.configClient.subscribe({ dataId, group }, (content: any) => {
|
||||
resolve(content)
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取配置
|
||||
* @param dataId
|
||||
* @param group
|
||||
*/
|
||||
async getConfig(dataId: string, group: string = "DEFAULT_GROUP") {
|
||||
const res = await this.configClient.getConfig(dataId, group)
|
||||
console.log()
|
||||
console.log(`${ chalk.blue('[nacos-cluster-federation] 配置发现:') }\n ${ chalk.green('➜') } ${ chalk.green(dataId) } 的配置为: ${ chalk.green(res) }`)
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从Nacos发现服务地址
|
||||
* @param serviceName 发现选项
|
||||
* @returns 服务地址(如 'http://192.168.1.101:1301')
|
||||
*/
|
||||
public async find(serviceName: string) {
|
||||
const instances = await this.nacosClient.getAllInstances(serviceName);
|
||||
// console.log("instances: ", instances);
|
||||
if (instances.length === 0) {
|
||||
throw new Error(`[nacos-federation] 未发现服务 ${ serviceName } 的实例`);
|
||||
}
|
||||
return formatServiceUrl(instances[0].ip, instances[0].port);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化配置服务
|
||||
* @private
|
||||
*/
|
||||
private async initConfig() {
|
||||
this.configClient = new NacosConfigClient({
|
||||
serverAddr: this.initOptions.serverList,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册服务到Nacos并返回客户端实例
|
||||
*/
|
||||
private async reg() {
|
||||
const { serviceName, port } = this.initOptions;
|
||||
const ip = getLocalIP();
|
||||
// @ts-ignore
|
||||
await this.nacosClient.registerInstance(serviceName, { port, ip })
|
||||
logger.log()
|
||||
logger.log(chalk.blue("[nacos-cluster-federation] 服务注册:"))
|
||||
logger.log(` ${ chalk.green('➜') } ${ chalk.green(serviceName) } (${ formatServiceUrl(ip, port) }) 已注册到配置中心!`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动销毁实例(如服务下线时)
|
||||
*/
|
||||
public async destroy() {
|
||||
const configKey = nacosServiceManage.getConfigKey(this.initOptions);
|
||||
// 注销Nacos服务实例
|
||||
const { serviceName, port } = this.initOptions;
|
||||
const ip = getLocalIP();
|
||||
// @ts-ignore
|
||||
await this.nacosClient.deregisterInstance(serviceName, { ip, port });
|
||||
logger.log(`[nacos-federation] 服务 ${ serviceName } 已从Nacos注销`);
|
||||
|
||||
// 从缓存中移除实例
|
||||
nacosServiceManage.instanceMap.delete(configKey);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
52
front-end/vite-plugin-nacos/src/utils.ts
Normal file
52
front-end/vite-plugin-nacos/src/utils.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { networkInterfaces } from "os";
|
||||
|
||||
|
||||
class Utils {
|
||||
/**
|
||||
* 获取本机局域网IPv4地址
|
||||
* @returns 局域网IP地址(如 '192.168.1.100')
|
||||
*/
|
||||
static 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')
|
||||
*/
|
||||
static formatServiceUrl(ip: string, port: number): string {
|
||||
return `http://${ ip }:${ port }`;
|
||||
}
|
||||
|
||||
/**
|
||||
* VITE_DEPEND_MODEL_NAME_LIST 配置 格式化成数组
|
||||
* app-shared,app-test
|
||||
* @param arg ["app-shared", "app-test"]
|
||||
*/
|
||||
static dependModel(arg: string): string[] {
|
||||
return arg.split(',').map(name => name.trim()).filter(Boolean) || [];
|
||||
}
|
||||
}
|
||||
|
||||
export const {
|
||||
getLocalIP, formatServiceUrl,
|
||||
dependModel
|
||||
} = Utils
|
||||
144
front-end/vite-plugin-nacos/src/vite-plugin-nacos.ts
Normal file
144
front-end/vite-plugin-nacos/src/vite-plugin-nacos.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
import { loadEnv, Plugin, UserConfig } from 'vite';
|
||||
import { NacosConfigClient, NacosNamingClient } from 'nacos';
|
||||
import { getLocalIP, dependModel, formatServiceUrl } from './utils';
|
||||
import chalk from 'chalk';
|
||||
|
||||
const logger = console;
|
||||
|
||||
export interface RegisterOptions {
|
||||
serverList: string;
|
||||
/** 服务名称(如 'main-app') */
|
||||
serviceName: string;
|
||||
/** 服务端口号 */
|
||||
port: number;
|
||||
/** 命名空间(用于环境隔离,默认 'public') */
|
||||
namespace?: string;
|
||||
}
|
||||
|
||||
export function reg(options?: Partial<RegisterOptions>): Plugin {
|
||||
return {
|
||||
name: 'vite-plugin-nacos',
|
||||
async config(config: UserConfig, { mode }) {
|
||||
// 修改base,增加资源来源地址,修复资源错误加载的问题
|
||||
const env = loadEnv(mode, process.cwd());
|
||||
let baseUrl = ""
|
||||
|
||||
// 主模块 main-app 不加 base 前缀,其他模块都加,
|
||||
// 也是为了命中 traefik 路由代理
|
||||
if (env.VITE_SERVER_NAME != "main-app") {
|
||||
baseUrl = `${env.VITE_SERVER_NAME}/`
|
||||
}
|
||||
|
||||
return {
|
||||
server: {
|
||||
allowedHosts: [
|
||||
'.ddd.com',
|
||||
],
|
||||
port: Number(env.VITE_SERVER_PORT),
|
||||
host: '::',
|
||||
strictPort: true,
|
||||
cors: true
|
||||
},
|
||||
// TODO dist 上线到服务器后,http://192.168.31.101:1301/ 会出问题
|
||||
// http(https)://前端运行的服务器所在ip/前端模块名
|
||||
base: `${env.VITE_PREFIX}${getLocalIP()}:${env.VITE_SERVER_PORT}/${baseUrl}`
|
||||
};
|
||||
},
|
||||
async configResolved(config) {
|
||||
const env = config.env;
|
||||
|
||||
// const registerOptions: RegisterOptions = {
|
||||
// serverList: env.VITE_NACOS_ADDRESS || options?.serverList!,
|
||||
// serviceName: env.VITE_SERVER_NAME || options?.serviceName!,
|
||||
// port: Number(env.VITE_SERVER_PORT || options?.port),
|
||||
// namespace: options?.namespace || 'public'
|
||||
// };
|
||||
//
|
||||
// // 验证必要配置
|
||||
// if (!registerOptions.serverList) {
|
||||
// throw new Error('[nacos-cluster-federation] 缺少Nacos服务列表配置,请设置VITE_NACOS_ADDRESS环境变量或插件选项');
|
||||
// }
|
||||
//
|
||||
// if (!registerOptions.serviceName) {
|
||||
// throw new Error('[nacos-cluster-federation] 缺少服务名称配置,请设置VITE_SERVER_NAME环境变量或插件选项');
|
||||
// }
|
||||
// if (!registerOptions.port) {
|
||||
// throw new Error('[nacos-cluster-federation] 缺少服务端口配置,请设置VITE_SERVER_PORT环境变量或插件选项');
|
||||
// }
|
||||
|
||||
// // 初始化Nacos实例
|
||||
// nacosInstance = await nacosServiceManage.create(registerOptions);
|
||||
//
|
||||
// const baseUrl = await nacosInstance.getConfig("baseUrl", "DEFAULT_GROUP")
|
||||
// const baseUrlConfig = baseUrl ? JSON.parse(baseUrl) : {};
|
||||
//
|
||||
// baseUrlConfig.httpTimeOut = await nacosInstance.getConfig("httpTimeOut", "DEFAULT_GROUP")
|
||||
//
|
||||
//
|
||||
// const customizeEnv: Record<string, string> = {};
|
||||
// Object.keys(baseUrlConfig).forEach(key => {
|
||||
// const envKey = `import.meta.env.VITE_${ key.toUpperCase() }`;
|
||||
// customizeEnv[envKey] = JSON.stringify(baseUrlConfig[key]);
|
||||
// });
|
||||
//
|
||||
// // @ts-ignore
|
||||
// Object.assign(config.define, customizeEnv);
|
||||
// Logs(env)
|
||||
},
|
||||
async closeBundle() {
|
||||
// // 服务关闭时注销Nacos实例
|
||||
// if (nacosInstance) {
|
||||
// await nacosInstance.destroy();
|
||||
// }
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function Logs(env: Record<string, any>) {
|
||||
const moduleName = []
|
||||
for (const key in env) {
|
||||
if (key.includes("VITE_MODEL_")) {
|
||||
moduleName.push(env[key]);
|
||||
}
|
||||
}
|
||||
moduleName.length == 0 ? moduleName.push('无') : null
|
||||
const webUI = env.VITE_NACOS_ADDRESS.split(":")[0]
|
||||
console.log()
|
||||
|
||||
console.log(dedentTemplateString(`
|
||||
${chalk.blue.bold('微服务工具:')}
|
||||
${chalk.green('➜')} Naco 配置中心: ${env.VITE_PREFIX}${env.VITE_NACOS_ADDRESS}
|
||||
${chalk.green('➜')} WebUi: ${env.VITE_PREFIX}${webUI}:8085
|
||||
${chalk.green('➜')} Prometheus 监控: ${env.VITE_PREFIX}${webUI}:9090
|
||||
${chalk.green('➜')} Grafana 面板: ${env.VITE_PREFIX}${webUI}:3000
|
||||
${chalk.green('➜')} Traefik 网关: ${env.VITE_PREFIX}${env.VITE_TRAEFIK_ADDRESS}
|
||||
${chalk.green('➜')} WebUi: ${env.VITE_PREFIX}${env.VITE_TRAEFIK_ADDRESS}:8880
|
||||
|
||||
${chalk.blue.bold('微前端信息:')}
|
||||
${chalk.green('➜')} 主模块:${chalk.green(env.VITE_SERVER_NAME)}
|
||||
|
||||
${chalk.blue.bold('调用模块:')}
|
||||
${chalk.green('➜')} [ ${chalk.yellow(moduleName)} ]
|
||||
`))
|
||||
console.log()
|
||||
}
|
||||
|
||||
function dedentTemplateString(str: string) {
|
||||
// 分割成行
|
||||
const lines = str.split('\n');
|
||||
|
||||
// 移除首尾空行
|
||||
if (lines[0].trim() === '') lines.shift();
|
||||
if (lines[lines.length - 1].trim() === '') lines.pop();
|
||||
|
||||
// 找到最小缩进量
|
||||
const minIndent = lines.reduce((min, line) => {
|
||||
if (line.trim() === '') return min; // 跳过空行
|
||||
// @ts-ignore
|
||||
const indent = line.match(/^\s*/)[0].length;
|
||||
return indent < min ? indent : min;
|
||||
}, Infinity);
|
||||
|
||||
// 移除统一缩进
|
||||
return lines.map(line => line.slice(minIndent)).join('\n');
|
||||
}
|
||||
5
front-end/vite-plugin-nacos/test.ts
Normal file
5
front-end/vite-plugin-nacos/test.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { nacosServiceManage } from "./src";
|
||||
|
||||
const main = async () => {
|
||||
const nacos = new nacosServiceManage.create({})
|
||||
}
|
||||
23
front-end/vite-plugin-nacos/tsconfig.json
Normal file
23
front-end/vite-plugin-nacos/tsconfig.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"module": "CommonJS",
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"declaration": true,
|
||||
// 生成类型声明文件
|
||||
"declarationDir": "./dist"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"src/types/*"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user