【重要】重构 并实现新的架构

This commit is contained in:
编码猿
2025-09-30 10:00:10 +08:00
parent 04754a1f77
commit a3ba6c2944
31 changed files with 459 additions and 302 deletions

View File

@@ -0,0 +1,32 @@
功能分析:
1 注册服务
子模块直接通过nacos 注册,给 内网 的 使用
主模块main-app -> Traefik -> ConfigServernacos-federation -> Nacos
main-app 发送 http 请求
/nacos/reg
参数:
ipport
2 获取配置
子模块:直接通过 nacos 获取配置
主模块: main-app -> Traefik -> ConfigServernacos-federation -> Nacos
注意:
增加功能:
认证授权机制
请求限流保护
操作日志记录
服务治理功能(如权重调整、元数据管理等)

View 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"
}

View 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
}
}

View 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);
}
}

View 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

View 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');
}

View File

@@ -0,0 +1,5 @@
import { nacosServiceManage } from "./src";
const main = async () => {
const nacos = new nacosServiceManage.create({})
}

View 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"
]
}