封装自动生成 traefik 代理配置的 nodejs 插件代码

This commit is contained in:
编码猿
2025-09-23 00:59:56 +08:00
parent bfe5f57378
commit 135839d6e2
27 changed files with 33463 additions and 12085 deletions

1
docker/traefik/README.md Normal file
View File

@@ -0,0 +1 @@
docker-compose down && docker-compose up -d

View File

@@ -20,5 +20,5 @@ services:
working_dir: /app
volumes:
- ./:/app # 挂载当前目录到容器
command: sh -c "npm install && node nacos-watcher.js"
command: sh -c "npm install && node src/index.js"
restart: always

View File

@@ -1,8 +1,8 @@
http:
routers:
main-app-router:
rule: PathPrefix(`/main-app`)
service: main-app-service
app-product-router:
rule: PathPrefix(`/app-product`)
service: app-product-service
entryPoints:
- web
middlewares:
@@ -10,9 +10,9 @@ http:
- ipWhiteList
- rateLimit
- retries
app-product-router:
rule: PathPrefix(`/app-product`)
service: app-product-service
main-app-router:
rule: PathPrefix(`/main-app`)
service: main-app-service
entryPoints:
- web
middlewares:
@@ -31,14 +31,14 @@ http:
- rateLimit
- retries
services:
main-app-service:
loadBalancer:
servers:
- url: http://192.168.31.101:1300
app-product-service:
loadBalancer:
servers:
- url: http://192.168.31.101:1301
main-app-service:
loadBalancer:
servers:
- url: http://192.168.31.101:1300
app-shared-service:
loadBalancer:
servers:

View File

@@ -0,0 +1,61 @@
const path = require("path");
module.exports = {
filePath: path.join(__dirname, '../../dynamic.yml'),
nacosApi: {
baseUrl: 'http://192.168.31.101:8848/nacos/v1/ns',
apiList: {
instanceInfo: '/instance/list',
serviceList: '/service/list',
}
},
nacosConfig: {
nameSpace: 'public',
},
// 每隔五秒检查一下最新配置
check_time: 10000,
// traefik 通用中间件配置
traefikConfig: {
middlewaresStr: ['compress', 'ipWhiteList', 'rateLimit', 'retries'],
middlewaresConfig: {
// 限流中间件(独立配置,关键修复)
rateLimit: {
rateLimit: {
average: 100,
burst: 200,
period: '1s',
sourceCriterion: {
ipStrategy: {depth: 2}
}
}
},
// 压缩中间件
compress: {
compress: {
excludedContentTypes: ['text/event-stream'],
minResponseBodyBytes: 1024
}
},
// IP白名单中间件示例允许本地和192.168.31网段)
ipWhiteList: {
ipWhiteList: {
sourceRange: ['127.0.0.1/32', '192.168.31.0/24']
}
},
// // 重定向中间件HTTP→HTTPS
// redirectToHttps: {
// redirectScheme: {
// scheme: 'https',
// permanent: true
// }
// },
// 重试中间件(关键修复:将重试配置移到这里)
retries: {
retry: {
attempts: 3,
initialInterval: '200ms'
}
}
}
}
}

View File

@@ -0,0 +1,38 @@
const axios = require('axios');
const config = require('../config');
const {AxiosError} = require("axios");
class Http {
instance = null;
constructor() {
this.instance = axios.create({
baseURL: config.nacosApi.baseUrl,
timeout: 10000,
headers: {
"Content-Type": "application/json"
}
})
this.ResponseInterceptor();
}
async get(params) {
return await this.instance.get(params.url, {
params: params.data,
});
}
async ResponseInterceptor() {
this.instance.interceptors.response.use(
(response) => {
return response.data;
},
(error) => {
return Promise.reject(error)
})
}
}
module.exports = new Http

View File

@@ -0,0 +1,9 @@
const utils = require('./utils')
const config = require('./config')
const uti = new utils();
setInterval(
uti.getServiceName.bind(uti),
config.check_time
)

View File

@@ -0,0 +1,109 @@
const config = require("../config")
const http = require("../http")
const yaml = require("js-yaml");
const path = require('path');
const fs = require('fs');
class Utils {
// 存储 Nacos上 服务列表 的服务名称
ServiceNameList = []
constructor() {
this.getServiceName()
}
/**
* 获取Nacos上的服务名称数组
* @returns {Promise<void>}
*/
async getServiceName() {
console.log("1: [getServiceName] 从Nacos 获取服务名..")
const {doms} = await http.get({
url: config.nacosApi.apiList.serviceList,
data: {pageNo: 1, pageSize: 20},
});
if (doms.length === 0) {
throw new Error("No service found for service list");
}
this.serviceName = doms;
console.log("2: [getServiceName] 服务名为:", this.serviceName)
await this.generateTraefikConfig()
}
/**
* 从 Nacos 获取服务实例
* @param serviceName
* @returns {Promise<*|*[]>}
*/
async getServiceInstances(serviceName) {
const {hosts} = await http.get({
url: config.nacosApi.apiList.instanceInfo,
data: {serviceName, namespaceId: config.nacosConfig.nameSpace},
});
return hosts || []
}
/**
* 生成 traefik 动态配置
* @returns {Promise<void>}
*/
async generateTraefikConfig() {
const traefikConf = {
http: {
routers: {},
services: {},
middlewares: config.traefikConfig.middlewaresConfig,
}
};
for (const serviceName of this.serviceName) {
const instances = await this.getServiceInstances(serviceName);
if (instances.length === 0) {
console.warn(`服务 ${serviceName} 无可用实例`);
continue;
}
// 1. 配置服务(服务名 + 实例列表)
traefikConf.http.services[`${serviceName}-service`] = {
loadBalancer: {
servers: instances.map(inst => ({
url: `http://${inst.ip}:${inst.port}`,
})),
}
};
// 2. 配置路由(路径前缀 + 绑定服务)
traefikConf.http.routers[`${serviceName}-router`] = {
rule: `PathPrefix(\`/${serviceName}\`)`, // 如 /main、/product
service: `${serviceName}-service`,
entryPoints: ['web'],
// 应用中间件压缩、IP白名单、限流、重试等
middlewares: config.traefikConfig.middlewaresStr,
};
}
this.stringToYaml(traefikConf)
}
/**
* 生成 yaml 配置文件
* @param conf
*/
stringToYaml(conf) {
console.log("3: [stringToYaml] 生成配置:", JSON.stringify(conf, null, 2));
console.log()
console.log()
const yamlString = yaml.dump(conf, {
indent: 2, // 缩进空格数
skipInvalid: true, // 跳过无效类型
noRefs: true // 禁止引用(防止循环引用问题)
});
fs.writeFileSync(config.filePath, yamlString, "utf8");
}
}
module.exports = Utils