create-dp-koa 1.1.2 → 1.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/template/.cursor/rules/00-backend-core.skill.md +1 -1
- package/template/.cursor/rules/01-backend-skill-router.skill.md +8 -2
- package/template/.cursor/rules/10-backend-api.skill.md +8 -0
- package/template/.cursor/rules/11-backend-controller-recipes.skill.md +12 -9
- package/template/.cursor/rules/21-backend-service.skill.md +14 -0
- package/template/.cursor/rules/30-backend-validation.skill.md +1 -1
- package/template/.cursor/rules/40-backend-error-logging.skill.md +9 -5
- package/template/.cursor/rules/50-backend-bootstrap-lifecycle.skill.md +4 -4
- package/template/.cursor/rules/60-backend-router-registration.skill.md +16 -6
- package/template/.cursor/rules/70-backend-middleware.skill.md +2 -2
- package/template/.cursor/rules/80-backend-utils-and-libs.skill.md +71 -14
- package/template/.cursor/rules/85-backend-plugins.rule.md +4 -4
- package/template/.cursor/rules/90-backend-testing.skill.md +26 -0
- package/template/.cursor/rules/README.md +2 -2
- package/template/.trae/skills/11-backend-controller-recipes.skill.md +12 -9
- package/template/.trae/skills/21-backend-service.skill.md +15 -1
- package/template/.trae/skills/40-backend-error-logging.skill.md +9 -5
- package/template/.trae/skills/80-backend-utils-and-libs.skill.md +77 -8
- package/template/.trae/skills/90-backend-testing.skill.md +26 -0
- package/template/scripts/sync-template.mjs +20 -0
- package/template/src/app.ts +1 -2
- package/template/src/{framework/plugins → plugins}/registry.ts +2 -2
- package/template/src/plugins/weboffice/http/routes.ts +1 -1
- package/template/src/plugins/weboffice/index.ts +3 -3
- package/template/src/plugins/weboffice/services/webofficeCallback.service.ts +3 -4
- package/template/src/types/ctxState.ts +9 -0
- package/template/src/utils/testDataInitializer.ts +1 -1
- package/template/tsconfig.json +6 -0
- package/template/src/annotations/decorators/ConfigManagement.ts +0 -9
- package/template/src/annotations/decorators/DistributedTracing.ts +0 -9
- package/template/src/annotations/decorators/EnterprisePerformance.ts +0 -9
- package/template/src/annotations/decorators/PerformanceMonitor.ts +0 -32
- package/template/src/annotations/decorators/SecurityAudit.ts +0 -9
- package/template/src/annotations/index.ts +0 -50
- package/template/src/annotations/processors/ConfigManagementProcessor.ts +0 -369
- package/template/src/annotations/processors/DistributedTracingProcessor.ts +0 -288
- package/template/src/annotations/processors/EnterprisePerformanceProcessor.ts +0 -189
- package/template/src/annotations/processors/PerformanceMonitorProcessor.ts +0 -101
- package/template/src/annotations/processors/SecurityAuditProcessor.ts +0 -345
- package/template/src/annotations/processors/SwaggerProcessor.ts +0 -612
- package/template/src/annotations/processors/index.ts +0 -10
- package/template/src/examples/InterceptorExampleRunner.ts +0 -284
- package/template/src/examples/ServiceInterceptorExample.ts +0 -214
- package/template/src/examples/cacheExamples.ts +0 -155
- package/template/src/framework/decorator/controller.ts +0 -311
- package/template/src/framework/decorator/processor/AnnotationDecorators.ts +0 -100
- package/template/src/framework/decorator/processor/AnnotationProcessor.ts +0 -160
- package/template/src/framework/decorator/processor/AnnotationProcessorConfig.ts +0 -45
- package/template/src/framework/decorator/processor/AnnotationRegistry.ts +0 -117
- package/template/src/framework/decorator/processor/AnnotationSystemInitializer.ts +0 -95
- package/template/src/framework/decorator/processor/ProcessorManager.ts +0 -76
- package/template/src/framework/decorator/processor/processors/CustomProcessors.ts +0 -126
- package/template/src/framework/decorator/processor/processors/DefaultProcessors.ts +0 -207
- package/template/src/framework/decorator/refactored/DecoratorFactory.ts +0 -99
- package/template/src/framework/decorator/refactored/DecoratorMetadataManager.ts +0 -125
- package/template/src/framework/decorator/refactored/DecoratorValidator.ts +0 -128
- package/template/src/framework/decorator/refactored/TypeSafeDecorators.ts +0 -139
- package/template/src/framework/decorator/refactored/index.ts +0 -98
- package/template/src/framework/decorator/swagger.ts +0 -150
- package/template/src/framework/interceptors/AdvancedServiceCallInterceptor.ts +0 -375
- package/template/src/framework/interceptors/ServiceCallInterceptor.ts +0 -348
- package/template/src/framework/interceptors/index.ts +0 -19
- package/template/src/framework/plugins/types.ts +0 -15
- package/template/src/framework/types/ServiceResult.ts +0 -151
- package/template/src/framework/types/index.ts +0 -16
- package/template/src/framework/utils/CacheManager.ts +0 -430
- package/template/src/framework/utils/CacheService.ts +0 -248
- package/template/src/framework/utils/DtoValidator.ts +0 -164
- package/template/src/framework/utils/MigrationHelper.ts +0 -179
- package/template/src/framework/utils/MigrationManager.ts +0 -256
- package/template/src/framework/utils/NewRouter.ts +0 -207
- package/template/src/framework/utils/TransactionManager.ts +0 -172
- package/template/src/framework/utils/bootstrap.ts +0 -445
- package/template/src/framework/utils/cache.ts +0 -269
- package/template/src/framework/utils/databaseConfig.ts +0 -148
- package/template/src/framework/utils/db.ts +0 -39
- package/template/src/framework/utils/dbMonitor.ts +0 -106
- package/template/src/framework/utils/function.ts +0 -61
- package/template/src/framework/utils/gracefulShutdown.ts +0 -131
- package/template/src/framework/utils/logger.ts +0 -388
- package/template/src/framework/utils/metrics.ts +0 -182
- package/template/src/framework/utils/router.ts +0 -417
- package/template/src/framework/utils/swagger.ts +0 -184
- package/template/src/framework/utils/testDb.ts +0 -19
- package/template/src/framework/utils/token.ts +0 -23
- package/template/src/framework/utils/transform.ts +0 -17
- package/template/src/libs/aokEmailSender.ts +0 -42
- package/template/src/libs/captcha.ts +0 -37
- package/template/src/libs/cos.ts +0 -45
- package/template/src/libs/mCache.ts +0 -7
- package/template/src/libs/serviceValidate.ts +0 -3
- package/template/src/libs/tecentSms.ts +0 -51
- package/template/src/middlewares/a.middleware.ts +0 -6
- package/template/src/middlewares/error.middleware.ts +0 -14
- package/template/src/middlewares/logging.middleware.ts +0 -187
- package/template/src/middlewares/static.middleware.ts +0 -79
- package/template/src/middlewares/swagger.middleware.ts +0 -70
- package/template/src/middlewares/token.middleware.ts +0 -32
- package/template/src/migrations/1700000000000-InitialDatabaseStructure.ts +0 -172
- package/template/src/migrations/index.ts +0 -6
- package/template/src/repository/base/BaseRepository.ts +0 -124
- package/template/src/repository/interfaces/IBaseRepository.ts +0 -67
- package/template/src/service/base.service.ts +0 -116
|
@@ -1,369 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 企业级配置管理注解处理器
|
|
3
|
-
* 提供动态配置、环境变量管理和配置验证
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { Context } from 'koa';
|
|
7
|
-
import { AnnotationProcessor } from '@src/framework/decorator/processor/AnnotationProcessor';
|
|
8
|
-
import { logger } from '@src/framework/utils/logger';
|
|
9
|
-
import { getRuntimeEnvironmentLabel } from '@src/framework/utils/function';
|
|
10
|
-
|
|
11
|
-
export interface ConfigManagementConfig {
|
|
12
|
-
configKey?: string;
|
|
13
|
-
defaultValue?: any;
|
|
14
|
-
required?: boolean;
|
|
15
|
-
validationRules?: ValidationRule[];
|
|
16
|
-
refreshInterval?: number;
|
|
17
|
-
environment?: 'development' | 'staging' | 'production';
|
|
18
|
-
fallbackConfig?: Record<string, any>;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface ValidationRule {
|
|
22
|
-
type: 'string' | 'number' | 'boolean' | 'array' | 'object' | 'email' | 'url';
|
|
23
|
-
min?: number;
|
|
24
|
-
max?: number;
|
|
25
|
-
pattern?: RegExp;
|
|
26
|
-
custom?: (value: any) => boolean;
|
|
27
|
-
message?: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface ConfigValue {
|
|
31
|
-
key: string;
|
|
32
|
-
value: any;
|
|
33
|
-
source: 'environment' | 'database' | 'file' | 'default';
|
|
34
|
-
lastUpdated: Date;
|
|
35
|
-
environment: string;
|
|
36
|
-
validated: boolean;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export class ConfigManagementProcessor implements AnnotationProcessor {
|
|
40
|
-
readonly name = 'ConfigManagement';
|
|
41
|
-
readonly priority = 4;
|
|
42
|
-
|
|
43
|
-
private configCache: Map<string, ConfigValue> = new Map();
|
|
44
|
-
private validationRules: Map<string, ValidationRule[]> = new Map();
|
|
45
|
-
private refreshTimers: Map<string, NodeJS.Timeout> = new Map();
|
|
46
|
-
private configRefreshInterval: NodeJS.Timeout | null = null; // 存储定时器引用
|
|
47
|
-
|
|
48
|
-
constructor() {
|
|
49
|
-
this.loadEnvironmentConfigs();
|
|
50
|
-
this.startConfigRefresh();
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
async process(
|
|
54
|
-
ctx: Context,
|
|
55
|
-
controller: any,
|
|
56
|
-
methodName: string,
|
|
57
|
-
annotationData: ConfigManagementConfig,
|
|
58
|
-
callParams: any[]
|
|
59
|
-
): Promise<boolean> {
|
|
60
|
-
const config = annotationData || {};
|
|
61
|
-
|
|
62
|
-
if (!config.configKey) return true;
|
|
63
|
-
|
|
64
|
-
try {
|
|
65
|
-
// 获取配置值
|
|
66
|
-
const configValue = await this.getConfigValue(config.configKey, config);
|
|
67
|
-
|
|
68
|
-
// 验证配置
|
|
69
|
-
if (config.validationRules) {
|
|
70
|
-
const isValid = this.validateConfig(config.configKey, configValue.value, config.validationRules);
|
|
71
|
-
if (!isValid) {
|
|
72
|
-
logger.error(`[${this.name}] 配置验证失败: ${config.configKey}`);
|
|
73
|
-
ctx.status = 500;
|
|
74
|
-
ctx.body = { error: 'Configuration validation failed' };
|
|
75
|
-
return false;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// 将配置注入到上下文
|
|
80
|
-
ctx.state.config = ctx.state.config || {};
|
|
81
|
-
ctx.state.config[config.configKey] = configValue.value;
|
|
82
|
-
|
|
83
|
-
logger.debug(`[${this.name}] 配置加载成功: ${config.configKey}`, {
|
|
84
|
-
value: this.sanitizeValue(configValue.value),
|
|
85
|
-
source: configValue.source
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
} catch (error) {
|
|
89
|
-
logger.error(`[${this.name}] 配置加载失败: ${config.configKey}`, error as Error);
|
|
90
|
-
|
|
91
|
-
if (config.required) {
|
|
92
|
-
ctx.status = 500;
|
|
93
|
-
ctx.body = { error: 'Required configuration missing' };
|
|
94
|
-
return false;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return true;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
private async getConfigValue(key: string, config: ConfigManagementConfig): Promise<ConfigValue> {
|
|
102
|
-
// 检查缓存
|
|
103
|
-
const cached = this.configCache.get(key);
|
|
104
|
-
if (cached && this.isCacheValid(cached)) {
|
|
105
|
-
return cached;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
let value: any;
|
|
109
|
-
let source: ConfigValue['source'] = 'default';
|
|
110
|
-
|
|
111
|
-
// 1. 尝试从环境变量获取
|
|
112
|
-
const envValue = process.env[key.toUpperCase()];
|
|
113
|
-
if (envValue !== undefined) {
|
|
114
|
-
value = this.parseValue(envValue);
|
|
115
|
-
source = 'environment';
|
|
116
|
-
}
|
|
117
|
-
// 2. 尝试从数据库获取
|
|
118
|
-
else if (await this.hasDatabaseConfig(key)) {
|
|
119
|
-
value = await this.getDatabaseConfig(key);
|
|
120
|
-
source = 'database';
|
|
121
|
-
}
|
|
122
|
-
// 3. 尝试从配置文件获取
|
|
123
|
-
else if (await this.hasFileConfig(key)) {
|
|
124
|
-
value = await this.getFileConfig(key);
|
|
125
|
-
source = 'file';
|
|
126
|
-
}
|
|
127
|
-
// 4. 使用默认值
|
|
128
|
-
else {
|
|
129
|
-
value = config.defaultValue;
|
|
130
|
-
source = 'default';
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const configValue: ConfigValue = {
|
|
134
|
-
key,
|
|
135
|
-
value,
|
|
136
|
-
source,
|
|
137
|
-
lastUpdated: new Date(),
|
|
138
|
-
environment: config.environment || getRuntimeEnvironmentLabel(),
|
|
139
|
-
validated: false
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
// 缓存配置
|
|
143
|
-
this.configCache.set(key, configValue);
|
|
144
|
-
|
|
145
|
-
return configValue;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
private parseValue(value: string): any {
|
|
149
|
-
// 尝试解析 JSON
|
|
150
|
-
try {
|
|
151
|
-
return JSON.parse(value);
|
|
152
|
-
} catch {
|
|
153
|
-
// 尝试解析布尔值
|
|
154
|
-
if (value.toLowerCase() === 'true') return true;
|
|
155
|
-
if (value.toLowerCase() === 'false') return false;
|
|
156
|
-
|
|
157
|
-
// 尝试解析数字
|
|
158
|
-
const num = Number(value);
|
|
159
|
-
if (!isNaN(num)) return num;
|
|
160
|
-
|
|
161
|
-
// 返回字符串
|
|
162
|
-
return value;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
private validateConfig(key: string, value: any, rules: ValidationRule[]): boolean {
|
|
167
|
-
for (const rule of rules) {
|
|
168
|
-
if (!this.validateRule(value, rule)) {
|
|
169
|
-
logger.warn(`[${this.name}] 配置验证失败: ${key}`, {
|
|
170
|
-
value: this.sanitizeValue(value),
|
|
171
|
-
rule: rule.type,
|
|
172
|
-
message: rule.message
|
|
173
|
-
});
|
|
174
|
-
return false;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
return true;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
private validateRule(value: any, rule: ValidationRule): boolean {
|
|
181
|
-
switch (rule.type) {
|
|
182
|
-
case 'string':
|
|
183
|
-
if (typeof value !== 'string') return false;
|
|
184
|
-
if (rule.min && value.length < rule.min) return false;
|
|
185
|
-
if (rule.max && value.length > rule.max) return false;
|
|
186
|
-
if (rule.pattern && !rule.pattern.test(value)) return false;
|
|
187
|
-
break;
|
|
188
|
-
|
|
189
|
-
case 'number':
|
|
190
|
-
if (typeof value !== 'number') return false;
|
|
191
|
-
if (rule.min && value < rule.min) return false;
|
|
192
|
-
if (rule.max && value > rule.max) return false;
|
|
193
|
-
break;
|
|
194
|
-
|
|
195
|
-
case 'boolean':
|
|
196
|
-
if (typeof value !== 'boolean') return false;
|
|
197
|
-
break;
|
|
198
|
-
|
|
199
|
-
case 'array':
|
|
200
|
-
if (!Array.isArray(value)) return false;
|
|
201
|
-
if (rule.min && value.length < rule.min) return false;
|
|
202
|
-
if (rule.max && value.length > rule.max) return false;
|
|
203
|
-
break;
|
|
204
|
-
|
|
205
|
-
case 'object':
|
|
206
|
-
if (typeof value !== 'object' || value === null) return false;
|
|
207
|
-
break;
|
|
208
|
-
|
|
209
|
-
case 'email':
|
|
210
|
-
if (typeof value !== 'string' || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) return false;
|
|
211
|
-
break;
|
|
212
|
-
|
|
213
|
-
case 'url':
|
|
214
|
-
if (typeof value !== 'string' || !/^https?:\/\/.+/.test(value)) return false;
|
|
215
|
-
break;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// 自定义验证
|
|
219
|
-
if (rule.custom && !rule.custom(value)) return false;
|
|
220
|
-
|
|
221
|
-
return true;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
private async hasDatabaseConfig(key: string): Promise<boolean> {
|
|
225
|
-
// 这里应该实现数据库配置检查
|
|
226
|
-
return false;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
private async getDatabaseConfig(key: string): Promise<any> {
|
|
230
|
-
// 这里应该实现数据库配置获取
|
|
231
|
-
return null;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
private async hasFileConfig(key: string): Promise<boolean> {
|
|
235
|
-
// 这里应该实现文件配置检查
|
|
236
|
-
return false;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
private async getFileConfig(key: string): Promise<any> {
|
|
240
|
-
// 这里应该实现文件配置获取
|
|
241
|
-
return null;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
private isCacheValid(configValue: ConfigValue): boolean {
|
|
245
|
-
const maxAge = 5 * 60 * 1000; // 5分钟
|
|
246
|
-
return Date.now() - configValue.lastUpdated.getTime() < maxAge;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
private sanitizeValue(value: any): any {
|
|
250
|
-
if (typeof value === 'string' && value.length > 100) {
|
|
251
|
-
return value.substring(0, 100) + '...';
|
|
252
|
-
}
|
|
253
|
-
return value;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
private loadEnvironmentConfigs(): void {
|
|
257
|
-
// 加载环境变量配置
|
|
258
|
-
Object.keys(process.env).forEach(key => {
|
|
259
|
-
if (key.startsWith('CONFIG_')) {
|
|
260
|
-
const configKey = key.substring(7).toLowerCase();
|
|
261
|
-
const value = this.parseValue(process.env[key]!);
|
|
262
|
-
|
|
263
|
-
this.configCache.set(configKey, {
|
|
264
|
-
key: configKey,
|
|
265
|
-
value,
|
|
266
|
-
source: 'environment',
|
|
267
|
-
lastUpdated: new Date(),
|
|
268
|
-
environment: getRuntimeEnvironmentLabel(),
|
|
269
|
-
validated: false
|
|
270
|
-
});
|
|
271
|
-
}
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
private startConfigRefresh(): void {
|
|
276
|
-
// 如果已有定时器,先清除
|
|
277
|
-
if (this.configRefreshInterval) {
|
|
278
|
-
clearInterval(this.configRefreshInterval);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// 定期刷新配置
|
|
282
|
-
this.configRefreshInterval = setInterval(() => {
|
|
283
|
-
this.refreshConfigs();
|
|
284
|
-
}, 60000); // 每分钟刷新一次
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* 停止配置刷新定时器
|
|
289
|
-
* 防止内存泄露
|
|
290
|
-
*/
|
|
291
|
-
stopConfigRefresh(): void {
|
|
292
|
-
if (this.configRefreshInterval) {
|
|
293
|
-
clearInterval(this.configRefreshInterval);
|
|
294
|
-
this.configRefreshInterval = null;
|
|
295
|
-
logger.info(`[${this.name}] 配置刷新定时器已停止`);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* 清理所有定时器
|
|
301
|
-
*/
|
|
302
|
-
cleanup(): void {
|
|
303
|
-
// 清理主刷新定时器
|
|
304
|
-
this.stopConfigRefresh();
|
|
305
|
-
|
|
306
|
-
// 清理所有独立的刷新定时器
|
|
307
|
-
this.refreshTimers.forEach((timer) => {
|
|
308
|
-
clearInterval(timer);
|
|
309
|
-
});
|
|
310
|
-
this.refreshTimers.clear();
|
|
311
|
-
|
|
312
|
-
// 清理缓存
|
|
313
|
-
this.configCache.clear();
|
|
314
|
-
this.validationRules.clear();
|
|
315
|
-
|
|
316
|
-
logger.info(`[${this.name}] 所有资源已清理`);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
private async refreshConfigs(): Promise<void> {
|
|
320
|
-
const keys = Array.from(this.configCache.keys());
|
|
321
|
-
|
|
322
|
-
for (const key of keys) {
|
|
323
|
-
try {
|
|
324
|
-
const cached = this.configCache.get(key)!;
|
|
325
|
-
if (cached.source === 'environment') {
|
|
326
|
-
// 环境变量配置不需要刷新
|
|
327
|
-
continue;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// 重新加载配置
|
|
331
|
-
const newValue = await this.getConfigValue(key, {});
|
|
332
|
-
if (JSON.stringify(newValue.value) !== JSON.stringify(cached.value)) {
|
|
333
|
-
logger.info(`[${this.name}] 配置已更新: ${key}`);
|
|
334
|
-
}
|
|
335
|
-
} catch (error) {
|
|
336
|
-
logger.error(`[${this.name}] 配置刷新失败: ${key}`, error as Error);
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
/**
|
|
342
|
-
* 获取所有配置
|
|
343
|
-
*/
|
|
344
|
-
getAllConfigs(): ConfigValue[] {
|
|
345
|
-
return Array.from(this.configCache.values());
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
/**
|
|
349
|
-
* 获取指定配置
|
|
350
|
-
*/
|
|
351
|
-
getConfig(key: string): ConfigValue | undefined {
|
|
352
|
-
return this.configCache.get(key);
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
/**
|
|
356
|
-
* 设置配置验证规则
|
|
357
|
-
*/
|
|
358
|
-
setValidationRules(key: string, rules: ValidationRule[]): void {
|
|
359
|
-
this.validationRules.set(key, rules);
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
/**
|
|
363
|
-
* 清理配置缓存
|
|
364
|
-
*/
|
|
365
|
-
clearCache(): void {
|
|
366
|
-
this.configCache.clear();
|
|
367
|
-
logger.info(`[${this.name}] 配置缓存已清理`);
|
|
368
|
-
}
|
|
369
|
-
}
|
|
@@ -1,288 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 企业级分布式追踪注解处理器
|
|
3
|
-
* 提供跨服务的请求追踪和性能分析
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { Context } from 'koa';
|
|
7
|
-
import { AnnotationProcessor } from '@src/framework/decorator/processor/AnnotationProcessor';
|
|
8
|
-
import { logger } from '@src/framework/utils/logger';
|
|
9
|
-
|
|
10
|
-
export interface TracingConfig {
|
|
11
|
-
enableTracing?: boolean;
|
|
12
|
-
traceIdHeader?: string;
|
|
13
|
-
spanIdHeader?: string;
|
|
14
|
-
parentSpanIdHeader?: string;
|
|
15
|
-
samplingRate?: number;
|
|
16
|
-
exportToJaeger?: boolean;
|
|
17
|
-
exportToZipkin?: boolean;
|
|
18
|
-
customTags?: Record<string, string>;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface TraceSpan {
|
|
22
|
-
traceId: string;
|
|
23
|
-
spanId: string;
|
|
24
|
-
parentSpanId?: string;
|
|
25
|
-
operationName: string;
|
|
26
|
-
startTime: number;
|
|
27
|
-
endTime?: number;
|
|
28
|
-
duration?: number;
|
|
29
|
-
tags: Record<string, any>;
|
|
30
|
-
logs: TraceLog[];
|
|
31
|
-
status: 'started' | 'completed' | 'error';
|
|
32
|
-
serviceName: string;
|
|
33
|
-
componentName: string;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export interface TraceLog {
|
|
37
|
-
timestamp: number;
|
|
38
|
-
level: 'info' | 'warn' | 'error';
|
|
39
|
-
message: string;
|
|
40
|
-
fields?: Record<string, any>;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export class DistributedTracingProcessor implements AnnotationProcessor {
|
|
44
|
-
readonly name = 'DistributedTracing';
|
|
45
|
-
readonly priority = 3;
|
|
46
|
-
|
|
47
|
-
private activeSpans: Map<string, TraceSpan> = new Map();
|
|
48
|
-
private completedSpans: TraceSpan[] = [];
|
|
49
|
-
private samplingRate: number = 1.0;
|
|
50
|
-
|
|
51
|
-
constructor() {
|
|
52
|
-
// 从环境变量读取采样率
|
|
53
|
-
this.samplingRate = parseFloat(process.env.TRACING_SAMPLING_RATE || '1.0');
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
async process(
|
|
57
|
-
ctx: Context,
|
|
58
|
-
controller: any,
|
|
59
|
-
methodName: string,
|
|
60
|
-
annotationData: TracingConfig,
|
|
61
|
-
callParams: any[]
|
|
62
|
-
): Promise<boolean> {
|
|
63
|
-
const config = annotationData || {};
|
|
64
|
-
|
|
65
|
-
if (!config.enableTracing) return true;
|
|
66
|
-
|
|
67
|
-
// 决定是否采样
|
|
68
|
-
if (!this.shouldSample()) {
|
|
69
|
-
return true;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// 获取或创建追踪上下文
|
|
73
|
-
const traceContext = this.getOrCreateTraceContext(ctx, config);
|
|
74
|
-
|
|
75
|
-
// 创建新的 Span
|
|
76
|
-
const span = this.createSpan(traceContext, controller, methodName, config);
|
|
77
|
-
|
|
78
|
-
// 存储到上下文
|
|
79
|
-
ctx.state.traceSpan = span;
|
|
80
|
-
ctx.state.traceContext = traceContext;
|
|
81
|
-
|
|
82
|
-
// 添加追踪头到响应
|
|
83
|
-
ctx.set('X-Trace-Id', span.traceId);
|
|
84
|
-
ctx.set('X-Span-Id', span.spanId);
|
|
85
|
-
|
|
86
|
-
// 记录开始日志
|
|
87
|
-
this.addSpanLog(span, 'info', 'Span started', {
|
|
88
|
-
controller: controller.constructor.name,
|
|
89
|
-
method: methodName,
|
|
90
|
-
traceId: span.traceId,
|
|
91
|
-
spanId: span.spanId
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
logger.info(`[${this.name}] 开始追踪: ${span.operationName}`, {
|
|
95
|
-
traceId: span.traceId,
|
|
96
|
-
spanId: span.spanId,
|
|
97
|
-
parentSpanId: span.parentSpanId
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
return true;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
async postProcess(
|
|
104
|
-
ctx: Context,
|
|
105
|
-
controller: any,
|
|
106
|
-
methodName: string,
|
|
107
|
-
response: any
|
|
108
|
-
): Promise<void> {
|
|
109
|
-
const span = ctx.state.traceSpan;
|
|
110
|
-
if (!span) return;
|
|
111
|
-
|
|
112
|
-
// 完成 Span
|
|
113
|
-
span.endTime = Date.now();
|
|
114
|
-
span.duration = span.endTime - span.startTime;
|
|
115
|
-
span.status = 'completed';
|
|
116
|
-
|
|
117
|
-
// 添加响应信息到标签
|
|
118
|
-
span.tags['http.status_code'] = ctx.status;
|
|
119
|
-
span.tags['response.size'] = JSON.stringify(response).length;
|
|
120
|
-
span.tags['response.type'] = typeof response;
|
|
121
|
-
|
|
122
|
-
// 记录完成日志
|
|
123
|
-
this.addSpanLog(span, 'info', 'Span completed', {
|
|
124
|
-
duration: span.duration,
|
|
125
|
-
statusCode: ctx.status,
|
|
126
|
-
responseSize: span.tags['response.size']
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
// 移动到已完成列表
|
|
130
|
-
this.activeSpans.delete(span.spanId);
|
|
131
|
-
this.completedSpans.push(span);
|
|
132
|
-
|
|
133
|
-
// 导出到外部系统
|
|
134
|
-
await this.exportSpan(span);
|
|
135
|
-
|
|
136
|
-
logger.info(`[${this.name}] 完成追踪: ${span.operationName}`, {
|
|
137
|
-
traceId: span.traceId,
|
|
138
|
-
spanId: span.spanId,
|
|
139
|
-
duration: `${span.duration}ms`
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
private shouldSample(): boolean {
|
|
144
|
-
return Math.random() < this.samplingRate;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
private getOrCreateTraceContext(ctx: Context, config: TracingConfig): any {
|
|
148
|
-
const traceIdHeader = config.traceIdHeader || 'X-Trace-Id';
|
|
149
|
-
const spanIdHeader = config.spanIdHeader || 'X-Span-Id';
|
|
150
|
-
const parentSpanIdHeader = config.parentSpanIdHeader || 'X-Parent-Span-Id';
|
|
151
|
-
|
|
152
|
-
// 尝试从请求头获取现有的追踪上下文
|
|
153
|
-
const traceId = ctx.get(traceIdHeader) || this.generateTraceId();
|
|
154
|
-
const parentSpanId = ctx.get(parentSpanIdHeader);
|
|
155
|
-
|
|
156
|
-
return {
|
|
157
|
-
traceId,
|
|
158
|
-
parentSpanId,
|
|
159
|
-
headers: {
|
|
160
|
-
traceIdHeader,
|
|
161
|
-
spanIdHeader,
|
|
162
|
-
parentSpanIdHeader
|
|
163
|
-
}
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
private createSpan(traceContext: any, controller: any, methodName: string, config: TracingConfig): TraceSpan {
|
|
168
|
-
const spanId = this.generateSpanId();
|
|
169
|
-
const operationName = `${controller.constructor.name}.${methodName}`;
|
|
170
|
-
|
|
171
|
-
const span: TraceSpan = {
|
|
172
|
-
traceId: traceContext.traceId,
|
|
173
|
-
spanId,
|
|
174
|
-
parentSpanId: traceContext.parentSpanId,
|
|
175
|
-
operationName,
|
|
176
|
-
startTime: Date.now(),
|
|
177
|
-
tags: {
|
|
178
|
-
'component': 'controller',
|
|
179
|
-
'controller.name': controller.constructor.name,
|
|
180
|
-
'method.name': methodName,
|
|
181
|
-
'service.name': process.env.SERVICE_NAME || 'koa-framework',
|
|
182
|
-
'http.method': 'GET', // 这里应该从 ctx 获取
|
|
183
|
-
'http.url': '', // 这里应该从 ctx 获取
|
|
184
|
-
...config.customTags
|
|
185
|
-
},
|
|
186
|
-
logs: [],
|
|
187
|
-
status: 'started',
|
|
188
|
-
serviceName: process.env.SERVICE_NAME || 'koa-framework',
|
|
189
|
-
componentName: 'controller'
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
this.activeSpans.set(spanId, span);
|
|
193
|
-
return span;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
private addSpanLog(span: TraceSpan, level: TraceLog['level'], message: string, fields?: Record<string, any>): void {
|
|
197
|
-
span.logs.push({
|
|
198
|
-
timestamp: Date.now(),
|
|
199
|
-
level,
|
|
200
|
-
message,
|
|
201
|
-
fields
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
private async exportSpan(span: TraceSpan): Promise<void> {
|
|
206
|
-
try {
|
|
207
|
-
// 导出到 Jaeger
|
|
208
|
-
if (process.env.JAEGER_ENDPOINT) {
|
|
209
|
-
await this.exportToJaeger(span);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// 导出到 Zipkin
|
|
213
|
-
if (process.env.ZIPKIN_ENDPOINT) {
|
|
214
|
-
await this.exportToZipkin(span);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// 导出到自定义系统
|
|
218
|
-
if (process.env.CUSTOM_TRACING_ENDPOINT) {
|
|
219
|
-
await this.exportToCustom(span);
|
|
220
|
-
}
|
|
221
|
-
} catch (error) {
|
|
222
|
-
logger.error(`[${this.name}] 导出追踪数据失败:`, error as Error);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
private async exportToJaeger(span: TraceSpan): Promise<void> {
|
|
227
|
-
// 这里应该实现 Jaeger 导出逻辑
|
|
228
|
-
logger.debug(`[${this.name}] 导出到 Jaeger: ${span.traceId}`);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
private async exportToZipkin(span: TraceSpan): Promise<void> {
|
|
232
|
-
// 这里应该实现 Zipkin 导出逻辑
|
|
233
|
-
logger.debug(`[${this.name}] 导出到 Zipkin: ${span.traceId}`);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
private async exportToCustom(span: TraceSpan): Promise<void> {
|
|
237
|
-
// 这里应该实现自定义系统导出逻辑
|
|
238
|
-
logger.debug(`[${this.name}] 导出到自定义系统: ${span.traceId}`);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
private generateTraceId(): string {
|
|
242
|
-
return Math.random().toString(36).substring(2, 15) +
|
|
243
|
-
Math.random().toString(36).substring(2, 15);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
private generateSpanId(): string {
|
|
247
|
-
return Math.random().toString(36).substring(2, 15);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* 获取追踪统计信息
|
|
252
|
-
*/
|
|
253
|
-
getTracingStats(): any {
|
|
254
|
-
const activeCount = this.activeSpans.size;
|
|
255
|
-
const completedCount = this.completedSpans.length;
|
|
256
|
-
|
|
257
|
-
const avgDuration = completedCount > 0
|
|
258
|
-
? this.completedSpans.reduce((sum, span) => sum + (span.duration || 0), 0) / completedCount
|
|
259
|
-
: 0;
|
|
260
|
-
|
|
261
|
-
return {
|
|
262
|
-
activeSpans: activeCount,
|
|
263
|
-
completedSpans: completedCount,
|
|
264
|
-
averageDuration: avgDuration,
|
|
265
|
-
samplingRate: this.samplingRate
|
|
266
|
-
};
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* 获取指定追踪的所有 Span
|
|
271
|
-
*/
|
|
272
|
-
getTraceSpans(traceId: string): TraceSpan[] {
|
|
273
|
-
return this.completedSpans.filter(span => span.traceId === traceId);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
/**
|
|
277
|
-
* 清理过期的 Span
|
|
278
|
-
*/
|
|
279
|
-
cleanupExpiredSpans(maxAge: number = 3600000): void { // 1小时
|
|
280
|
-
const cutoffTime = Date.now() - maxAge;
|
|
281
|
-
|
|
282
|
-
this.completedSpans = this.completedSpans.filter(span =>
|
|
283
|
-
span.startTime > cutoffTime
|
|
284
|
-
);
|
|
285
|
-
|
|
286
|
-
logger.info(`[${this.name}] 清理过期 Span 完成,保留 ${this.completedSpans.length} 个 Span`);
|
|
287
|
-
}
|
|
288
|
-
}
|