@nest-omni/core 4.1.3-28 → 4.1.3-29

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.
Files changed (33) hide show
  1. package/audit/interceptors/audit-action.interceptor.d.ts +1 -0
  2. package/audit/interceptors/audit-action.interceptor.js +5 -3
  3. package/audit/services/audit-action.service.d.ts +1 -0
  4. package/audit/services/audit-action.service.js +5 -3
  5. package/audit/services/operation-description.service.d.ts +1 -0
  6. package/audit/services/operation-description.service.js +5 -3
  7. package/audit/services/transaction-audit.service.d.ts +1 -0
  8. package/audit/services/transaction-audit.service.js +6 -4
  9. package/common/helpers/validation-metadata-helper.d.ts +57 -0
  10. package/common/helpers/validation-metadata-helper.js +109 -5
  11. package/decorators/examples/field-i18n.example.d.ts +294 -0
  12. package/decorators/examples/field-i18n.example.js +478 -0
  13. package/decorators/field.decorators.d.ts +23 -0
  14. package/decorators/field.decorators.js +7 -2
  15. package/decorators/translate.decorator.d.ts +26 -0
  16. package/decorators/translate.decorator.js +26 -1
  17. package/filters/bad-request.filter.js +15 -9
  18. package/http-client/decorators/http-client.decorators.d.ts +1 -0
  19. package/http-client/decorators/http-client.decorators.js +47 -30
  20. package/http-client/http-client.module.d.ts +8 -0
  21. package/http-client/http-client.module.js +24 -24
  22. package/http-client/services/http-client.service.js +56 -11
  23. package/http-client/utils/context-extractor.util.d.ts +2 -0
  24. package/http-client/utils/context-extractor.util.js +15 -3
  25. package/interceptors/index.d.ts +0 -1
  26. package/interceptors/index.js +0 -1
  27. package/interceptors/translation-interceptor.service.d.ts +7 -0
  28. package/interceptors/translation-interceptor.service.js +40 -8
  29. package/package.json +1 -1
  30. package/setup/bootstrap.setup.js +1 -1
  31. package/shared/services/api-config.service.js +18 -3
  32. package/interceptors/http-logging-interceptor.service.d.ts +0 -34
  33. package/interceptors/http-logging-interceptor.service.js +0 -138
@@ -0,0 +1,478 @@
1
+ "use strict";
2
+ /**
3
+ * 字段装饰器 i18n 翻译使用示例
4
+ *
5
+ * 本示例演示如何使用 fieldI18n 选项通过 i18n key 进行多语言转换
6
+ *
7
+ * 注意:此示例文件仅供参考,展示如何使用 fieldI18n 选项。
8
+ * 如果使用 TypeScript 5+ 的新装饰器语法,需要确保 tsconfig.json 中
9
+ * "experimentalDecorators" 设置为 true。
10
+ */
11
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
12
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
13
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
14
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
15
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
16
+ };
17
+ var __metadata = (this && this.__metadata) || function (k, v) {
18
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
19
+ };
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.RegisterUserDto = exports.MixedDto = exports.PureLabelDto = exports.PureI18nDto = exports.UpdateUserDto = exports.CreateProductDto = exports.CreateUserDto = void 0;
22
+ const field_decorators_1 = require("../field.decorators");
23
+ // ============================================
24
+ // 步骤 1: 配置 i18n 翻译函数
25
+ // ============================================
26
+ /**
27
+ * 在应用启动时(如 app.module.ts 或 main.ts)配置 i18n 翻译函数
28
+ *
29
+ * @example
30
+ * // app.module.ts
31
+ * import { setI18nTranslate } from '@your-lib/core';
32
+ *
33
+ *@Module({
34
+ * imports: [
35
+ * ConfigModule.forRoot(),
36
+ * I18nModule.forRoot({
37
+ * fallbackLanguage: 'en',
38
+ * loader: TranslationsLoader({
39
+ * config: {
40
+ * defaultLanguage: 'zh',
41
+ * languages: ['zh', 'en'],
42
+ * types: ['validation', 'field'],
43
+ * },
44
+ * }),
45
+ * }),
46
+ * ],
47
+ * providers: [AppService],
48
+ * })
49
+ * export class AppModule implements OnModuleInit {
50
+ * constructor(private readonly i18n: I18nService) {}
51
+ *
52
+ * async onModuleInit() {
53
+ * // 设置全局 i18n 翻译函数
54
+ * setI18nTranslate((key, options) => this.i18n.translate(key, options));
55
+ * }
56
+ * }
57
+ */
58
+ // ============================================
59
+ // 步骤 2: 在翻译文件中定义字段标签
60
+ // ============================================
61
+ /**
62
+ * 翻译文件结构示例 (src/i18n/zh.json):
63
+ *
64
+ * {
65
+ * "field": {
66
+ * "user": {
67
+ * "username": "用户名",
68
+ * "email": "邮箱",
69
+ * "phone": "手机号",
70
+ * "role": "角色",
71
+ * "status": "状态",
72
+ * "description": "个人简介"
73
+ * },
74
+ * "product": {
75
+ * "name": "产品名称",
76
+ * "price": "价格",
77
+ * "stock": "库存",
78
+ * "status": "产品状态"
79
+ * }
80
+ * },
81
+ * "field.value": {
82
+ * "user.role": {
83
+ * "user": "普通用户",
84
+ * "admin": "管理员",
85
+ * "vip": "VIP用户"
86
+ * },
87
+ * "user.status": {
88
+ * "active": "正常",
89
+ * "inactive": "未激活",
90
+ * "suspended": "已暂停"
91
+ * }
92
+ * }
93
+ * }
94
+ *
95
+ * 翻译文件结构示例 (src/i18n/en.json):
96
+ *
97
+ * {
98
+ * "field": {
99
+ * "user": {
100
+ * "username": "Username",
101
+ * "email": "Email",
102
+ * "phone": "Phone Number",
103
+ * "role": "Role",
104
+ * "status": "Status",
105
+ * "description": "Bio"
106
+ * },
107
+ * "product": {
108
+ * "name": "Product Name",
109
+ * "price": "Price",
110
+ * "stock": "Stock",
111
+ * "status": "Product Status"
112
+ * }
113
+ * },
114
+ * "field.value": {
115
+ * "user.role": {
116
+ * "user": "User",
117
+ * "admin": "Administrator",
118
+ * "vip": "VIP User"
119
+ * },
120
+ * "user.status": {
121
+ * "active": "Active",
122
+ * "inactive": "Inactive",
123
+ * "suspended": "Suspended"
124
+ * }
125
+ * }
126
+ * }
127
+ */
128
+ // ============================================
129
+ // 步骤 3: 在 DTO 中使用 fieldI18n
130
+ // ============================================
131
+ /**
132
+ * 用户 DTO - 使用 i18n key
133
+ */
134
+ class CreateUserDto {
135
+ }
136
+ exports.CreateUserDto = CreateUserDto;
137
+ __decorate([
138
+ (0, field_decorators_1.StringField)({
139
+ minLength: 3,
140
+ maxLength: 20,
141
+ fieldI18n: 'field.user.username',
142
+ // 注意: 如果同时提供 fieldI18n 和 fieldLabel,fieldLabel 优先级更高
143
+ // fieldLabel: { zh: '用户名', en: 'Username' },
144
+ }),
145
+ __metadata("design:type", String)
146
+ ], CreateUserDto.prototype, "username", void 0);
147
+ __decorate([
148
+ (0, field_decorators_1.StringField)({
149
+ fieldI18n: 'field.user.email',
150
+ }),
151
+ __metadata("design:type", String)
152
+ ], CreateUserDto.prototype, "email", void 0);
153
+ __decorate([
154
+ (0, field_decorators_1.StringFieldOptional)({
155
+ maxLength: 11,
156
+ fieldI18n: 'field.user.phone',
157
+ }),
158
+ __metadata("design:type", String)
159
+ ], CreateUserDto.prototype, "phone", void 0);
160
+ __decorate([
161
+ (0, field_decorators_1.StringFieldOptional)({
162
+ maxLength: 500,
163
+ fieldI18n: 'field.user.description',
164
+ }),
165
+ __metadata("design:type", String)
166
+ ], CreateUserDto.prototype, "description", void 0);
167
+ __decorate([
168
+ (0, field_decorators_1.BooleanFieldOptional)({
169
+ fieldI18n: 'field.user.emailVerified',
170
+ valueLabels: {
171
+ // valueLabels 也支持 i18n key
172
+ true: 'field.user.emailVerified.yes',
173
+ false: 'field.user.emailVerified.no',
174
+ // 或者直接使用多语言对象
175
+ // true: { zh: '已验证', en: 'Verified' },
176
+ // false: { zh: '未验证', en: 'Not Verified' },
177
+ },
178
+ }),
179
+ __metadata("design:type", Boolean)
180
+ ], CreateUserDto.prototype, "emailVerified", void 0);
181
+ /**
182
+ * 产品 DTO - 使用 i18n key
183
+ */
184
+ class CreateProductDto {
185
+ }
186
+ exports.CreateProductDto = CreateProductDto;
187
+ __decorate([
188
+ (0, field_decorators_1.StringField)({
189
+ minLength: 1,
190
+ maxLength: 200,
191
+ fieldI18n: 'field.product.name',
192
+ }),
193
+ __metadata("design:type", String)
194
+ ], CreateProductDto.prototype, "name", void 0);
195
+ __decorate([
196
+ (0, field_decorators_1.NumberField)({
197
+ min: 0,
198
+ fieldI18n: 'field.product.price',
199
+ }),
200
+ __metadata("design:type", Number)
201
+ ], CreateProductDto.prototype, "price", void 0);
202
+ __decorate([
203
+ (0, field_decorators_1.NumberField)({
204
+ min: 0,
205
+ fieldI18n: 'field.product.stock',
206
+ }),
207
+ __metadata("design:type", Number)
208
+ ], CreateProductDto.prototype, "stock", void 0);
209
+ // ============================================
210
+ // 步骤 4: 枚举字段的 i18n 使用
211
+ // ============================================
212
+ var UserRole;
213
+ (function (UserRole) {
214
+ UserRole["USER"] = "user";
215
+ UserRole["ADMIN"] = "admin";
216
+ UserRole["VIP"] = "vip";
217
+ })(UserRole || (UserRole = {}));
218
+ var UserStatus;
219
+ (function (UserStatus) {
220
+ UserStatus["ACTIVE"] = "active";
221
+ UserStatus["INACTIVE"] = "inactive";
222
+ UserStatus["SUSPENDED"] = "suspended";
223
+ })(UserStatus || (UserStatus = {}));
224
+ /**
225
+ * 用户更新 DTO - 枚举字段使用 i18n
226
+ */
227
+ class UpdateUserDto {
228
+ }
229
+ exports.UpdateUserDto = UpdateUserDto;
230
+ __decorate([
231
+ (0, field_decorators_1.EnumField)(() => UserRole, {
232
+ fieldI18n: 'field.user.role',
233
+ valueLabels: {
234
+ // 使用 i18n key 指向枚举值的翻译
235
+ [UserRole.USER]: 'field.value.user.role.user',
236
+ [UserRole.ADMIN]: 'field.value.user.role.admin',
237
+ [UserRole.VIP]: 'field.value.user.role.vip',
238
+ },
239
+ }),
240
+ __metadata("design:type", String)
241
+ ], UpdateUserDto.prototype, "role", void 0);
242
+ __decorate([
243
+ (0, field_decorators_1.EnumField)(() => UserStatus, {
244
+ fieldI18n: 'field.user.status',
245
+ valueLabels: {
246
+ [UserStatus.ACTIVE]: 'field.value.user.status.active',
247
+ [UserStatus.INACTIVE]: 'field.value.user.status.inactive',
248
+ [UserStatus.SUSPENDED]: 'field.value.user.status.suspended',
249
+ },
250
+ }),
251
+ __metadata("design:type", String)
252
+ ], UpdateUserDto.prototype, "status", void 0);
253
+ // ============================================
254
+ // 步骤 5: 在验证错误过滤器中使用 i18n
255
+ // ============================================
256
+ /**
257
+ * 验证错误过滤器示例
258
+ *
259
+ * @example
260
+ * import { getValidationMetadata, getFieldLabelForValidation } from '@your-lib/core';
261
+ *
262
+ * @Catch(ValidationError)
263
+ * export class ValidationErrorFilter implements ExceptionFilter {
264
+ * catch(exception: ValidationError, host: ArgumentsHost) {
265
+ * const ctx = host.switchToHttp();
266
+ * const response = ctx.getResponse<Response>();
267
+ * const request = ctx.getRequest<Request>();
268
+ * const language = request.headers['accept-language'] || 'zh';
269
+ *
270
+ * const errors = exception.errors.map(error => {
271
+ * const metadata = getValidationMetadata(error.target.constructor, error.property);
272
+ * const fieldLabel = getFieldLabelForValidation(metadata, language);
273
+ *
274
+ * // 如果 fieldLabel 是 i18n key,需要翻译
275
+ * const displayLabel = fieldLabel.includes('.')
276
+ * ? this.i18n.translate(fieldLabel, { lang: language })
277
+ * : fieldLabel;
278
+ *
279
+ * return {
280
+ * field: error.property,
281
+ * fieldLabel: displayLabel,
282
+ * constraints: error.constraints,
283
+ * };
284
+ * });
285
+ *
286
+ * response.status(400).json({
287
+ * statusCode: 400,
288
+ * message: 'Validation failed',
289
+ * errors,
290
+ * });
291
+ * }
292
+ * }
293
+ */
294
+ // ============================================
295
+ // 步骤 6: 在审计日志中使用 i18n
296
+ // ============================================
297
+ /**
298
+ * 审计日志服务示例
299
+ *
300
+ * @example
301
+ * import { getAuditMetadata } from '@your-lib/core';
302
+ *
303
+ * @Injectable()
304
+ * export class AuditService {
305
+ * constructor(private readonly i18n: I18nService) {}
306
+ *
307
+ * async logFieldChange(
308
+ * entity: any,
309
+ * field: string,
310
+ * oldValue: any,
311
+ * newValue: any,
312
+ * language: string = 'zh',
313
+ * ) {
314
+ * const metadata = getAuditMetadata(entity.constructor, field);
315
+ *
316
+ * // 翻译字段标签
317
+ * let fieldLabel = metadata?.label || field;
318
+ * if (typeof fieldLabel === 'string' && fieldLabel.includes('.')) {
319
+ * fieldLabel = await this.i18n.translate(fieldLabel, { lang: language });
320
+ * } else if (typeof fieldLabel === 'object') {
321
+ * fieldLabel = fieldLabel[language] || fieldLabel.zh || fieldLabel.en;
322
+ * }
323
+ *
324
+ * // 翻译枚举值
325
+ * const valueLabels = metadata?.valueLabels;
326
+ * const displayOldValue = valueLabels?.[oldValue]
327
+ * ? await this.translateValueLabel(valueLabels[oldValue], language)
328
+ * : oldValue;
329
+ * const displayNewValue = valueLabels?.[newValue]
330
+ * ? await this.translateValueLabel(valueLabels[newValue], language)
331
+ * : newValue;
332
+ *
333
+ * return {
334
+ * fieldLabel,
335
+ * oldValue,
336
+ * newValue,
337
+ * displayOldValue,
338
+ * displayNewValue,
339
+ * };
340
+ * }
341
+ *
342
+ * private async translateValueLabel(
343
+ * label: string | { [lang: string]: string },
344
+ * language: string,
345
+ * ): Promise<string> {
346
+ * if (typeof label === 'string' && label.includes('.')) {
347
+ * return await this.i18n.translate(label, { lang: language });
348
+ * }
349
+ * if (typeof label === 'object') {
350
+ * return label[language] || label.zh || label.en || '';
351
+ * }
352
+ * return label;
353
+ * }
354
+ * }
355
+ */
356
+ // ============================================
357
+ // 使用场景对比
358
+ // ============================================
359
+ /**
360
+ * 场景 1: 纯 i18n key 方式(推荐)
361
+ *
362
+ * 优点:
363
+ * - 翻译集中管理,易于维护
364
+ * - 支持动态切换语言
365
+ * - 翻译可以复用
366
+ * - 适合大型项目和国际化需求
367
+ *
368
+ * 缺点:
369
+ * - 需要额外的翻译文件配置
370
+ * - 增加了一定的复杂度
371
+ */
372
+ class PureI18nDto {
373
+ }
374
+ exports.PureI18nDto = PureI18nDto;
375
+ __decorate([
376
+ (0, field_decorators_1.StringField)({
377
+ minLength: 3,
378
+ maxLength: 20,
379
+ fieldI18n: 'field.user.username',
380
+ }),
381
+ __metadata("design:type", String)
382
+ ], PureI18nDto.prototype, "username", void 0);
383
+ /**
384
+ * 场景 2: 纯多语言对象方式
385
+ *
386
+ * 优点:
387
+ * - 简单直接,无需额外配置
388
+ * - 适合小型项目或固定语言场景
389
+ * - 翻译就在代码中,查看方便
390
+ *
391
+ * 缺点:
392
+ * - 翻译分散在各个 DTO 中,难以统一管理
393
+ * - 不易于动态切换语言
394
+ * - 翻译无法复用
395
+ */
396
+ class PureLabelDto {
397
+ }
398
+ exports.PureLabelDto = PureLabelDto;
399
+ __decorate([
400
+ (0, field_decorators_1.StringField)({
401
+ minLength: 3,
402
+ maxLength: 20,
403
+ fieldLabel: { zh: '用户名', en: 'Username' },
404
+ }),
405
+ __metadata("design:type", String)
406
+ ], PureLabelDto.prototype, "username", void 0);
407
+ /**
408
+ * 场景 3: 混合方式(fieldLabel 优先)
409
+ *
410
+ * 优点:
411
+ * - 灵活性最高
412
+ * - 可以为特定字段覆盖 i18n 翻译
413
+ *
414
+ * 缺点:
415
+ * - 可能造成混淆
416
+ * - 建议统一使用一种方式
417
+ */
418
+ class MixedDto {
419
+ }
420
+ exports.MixedDto = MixedDto;
421
+ __decorate([
422
+ (0, field_decorators_1.StringField)({
423
+ minLength: 3,
424
+ maxLength: 20,
425
+ fieldI18n: 'field.user.username', // 会被 fieldLabel 覆盖
426
+ fieldLabel: { zh: '用户名(自定义)', en: 'Username (Custom)' },
427
+ }),
428
+ __metadata("design:type", String)
429
+ ], MixedDto.prototype, "username", void 0);
430
+ // ============================================
431
+ // 完整示例:用户注册 DTO
432
+ // ============================================
433
+ class RegisterUserDto {
434
+ }
435
+ exports.RegisterUserDto = RegisterUserDto;
436
+ __decorate([
437
+ (0, field_decorators_1.StringField)({
438
+ minLength: 3,
439
+ maxLength: 20,
440
+ fieldI18n: 'field.user.username',
441
+ fieldDescription: {
442
+ zh: '用于登录的唯一用户名',
443
+ en: 'Unique username for login',
444
+ },
445
+ }),
446
+ __metadata("design:type", String)
447
+ ], RegisterUserDto.prototype, "username", void 0);
448
+ __decorate([
449
+ (0, field_decorators_1.StringField)({
450
+ fieldI18n: 'field.user.email',
451
+ }),
452
+ __metadata("design:type", String)
453
+ ], RegisterUserDto.prototype, "email", void 0);
454
+ __decorate([
455
+ (0, field_decorators_1.StringField)({
456
+ minLength: 6,
457
+ fieldI18n: 'field.user.password',
458
+ }),
459
+ __metadata("design:type", String)
460
+ ], RegisterUserDto.prototype, "password", void 0);
461
+ __decorate([
462
+ (0, field_decorators_1.StringFieldOptional)({
463
+ minLength: 11,
464
+ maxLength: 11,
465
+ fieldI18n: 'field.user.phone',
466
+ }),
467
+ __metadata("design:type", String)
468
+ ], RegisterUserDto.prototype, "phone", void 0);
469
+ __decorate([
470
+ (0, field_decorators_1.BooleanFieldOptional)({
471
+ fieldI18n: 'field.user.agreeTerms',
472
+ valueLabels: {
473
+ true: { zh: '同意', en: 'Agreed' },
474
+ false: { zh: '未同意', en: 'Not Agreed' },
475
+ },
476
+ }),
477
+ __metadata("design:type", Boolean)
478
+ ], RegisterUserDto.prototype, "agreeTerms", void 0);
@@ -8,9 +8,29 @@ interface IFieldOptions {
8
8
  group?: string[];
9
9
  message?: string;
10
10
  skipValidation?: boolean;
11
+ /**
12
+ * i18n 翻译键
13
+ * 用于通过 i18n key 进行多语言转换
14
+ *
15
+ * @example
16
+ * // 使用 i18n key
17
+ * @StringField({
18
+ * fieldI18n: 'user.username',
19
+ * })
20
+ * username: string;
21
+ *
22
+ * // 翻译文件中定义:
23
+ * // {
24
+ * // "user": {
25
+ * // "username": "用户名" // 或 "Username" for English
26
+ * // }
27
+ * // }
28
+ */
29
+ fieldI18n?: string;
11
30
  /**
12
31
  * 字段标签(多语言)
13
32
  * 用于审计日志、验证错误消息、UI 显示等
33
+ * 注意:如果同时提供了 fieldI18n 和 fieldLabel,fieldLabel 优先级更高
14
34
  */
15
35
  fieldLabel?: string | {
16
36
  zh?: string;
@@ -20,6 +40,9 @@ interface IFieldOptions {
20
40
  /**
21
41
  * 字段描述(多语言)
22
42
  * 用于 API 文档、审计日志等
43
+ * 支持两种形式:
44
+ * 1. i18n key: "user.username.description"
45
+ * 2. 直接多语言对象: { zh: '...', en: '...' }
23
46
  */
24
47
  fieldDescription?: string | {
25
48
  zh?: string;
@@ -62,6 +62,7 @@ function applyAuditMetadata(options) {
62
62
  return (target, propertyKey) => {
63
63
  // 只有当有相关元数据时才存储
64
64
  if (options.fieldLabel ||
65
+ options.fieldI18n ||
65
66
  options.fieldDescription ||
66
67
  options.valueLabels ||
67
68
  options.displayFormatter ||
@@ -70,9 +71,12 @@ function applyAuditMetadata(options) {
70
71
  const AUDIT_FIELD_OPTIONS_KEY = 'FIELD_AUDIT_OPTIONS';
71
72
  // 获取现有的审计元数据
72
73
  const existingOptions = Reflect.getMetadata(AUDIT_FIELD_OPTIONS_KEY, constructor) || {};
74
+ // 合并元数据(优先使用 fieldLabel,如果没有则使用 fieldI18n)
75
+ const label = options.fieldLabel || options.fieldI18n;
73
76
  // 合并元数据
74
77
  existingOptions[propertyKey] = {
75
- label: options.fieldLabel,
78
+ label,
79
+ i18nKey: options.fieldI18n,
76
80
  description: options.fieldDescription,
77
81
  example: options.fieldExample,
78
82
  valueLabels: options.valueLabels,
@@ -93,9 +97,10 @@ function applyAuditMetadata(options) {
93
97
  function applyValidationMetadata(options) {
94
98
  return (target, propertyKey) => {
95
99
  // 只有当有相关元数据时才存储
96
- if (options.fieldLabel) {
100
+ if (options.fieldLabel || options.fieldI18n) {
97
101
  (0, validation_metadata_helper_1.setValidationMetadata)(target.constructor, String(propertyKey), {
98
102
  fieldName: String(propertyKey),
103
+ i18nKey: options.fieldI18n,
99
104
  label: options.fieldLabel,
100
105
  description: options.fieldDescription,
101
106
  });
@@ -1,5 +1,31 @@
1
1
  import type { ITranslationDecoratorInterface } from '../interfaces';
2
2
  export declare const STATIC_TRANSLATION_DECORATOR_KEY = "custom:static-translate";
3
3
  export declare const DYNAMIC_TRANSLATION_DECORATOR_KEY = "custom:dynamic-translate";
4
+ /**
5
+ * 静态翻译装饰器
6
+ * 用于标记 DTO 字段需要进行翻译
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * class UserDto {
11
+ * @StaticTranslate({ translationKey: 'user.status' })
12
+ * status: string;
13
+ * }
14
+ * ```
15
+ *
16
+ * @param data 翻译配置选项
17
+ */
4
18
  export declare function StaticTranslate(data?: ITranslationDecoratorInterface): PropertyDecorator;
19
+ /**
20
+ * 动态翻译装饰器
21
+ * 用于标记 DTO 字段需要动态翻译(从数据库获取翻译值)
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * class ProductDto {
26
+ * @DynamicTranslate()
27
+ * name: string;
28
+ * }
29
+ * ```
30
+ */
5
31
  export declare function DynamicTranslate(): PropertyDecorator;
@@ -5,12 +5,37 @@ exports.StaticTranslate = StaticTranslate;
5
5
  exports.DynamicTranslate = DynamicTranslate;
6
6
  exports.STATIC_TRANSLATION_DECORATOR_KEY = 'custom:static-translate';
7
7
  exports.DYNAMIC_TRANSLATION_DECORATOR_KEY = 'custom:dynamic-translate';
8
- // FIXME: This is a temporary solution to get the translation decorator working.
8
+ /**
9
+ * 静态翻译装饰器
10
+ * 用于标记 DTO 字段需要进行翻译
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * class UserDto {
15
+ * @StaticTranslate({ translationKey: 'user.status' })
16
+ * status: string;
17
+ * }
18
+ * ```
19
+ *
20
+ * @param data 翻译配置选项
21
+ */
9
22
  function StaticTranslate(data = {}) {
10
23
  return (target, key) => {
11
24
  Reflect.defineMetadata(exports.STATIC_TRANSLATION_DECORATOR_KEY, data, target, key);
12
25
  };
13
26
  }
27
+ /**
28
+ * 动态翻译装饰器
29
+ * 用于标记 DTO 字段需要动态翻译(从数据库获取翻译值)
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * class ProductDto {
34
+ * @DynamicTranslate()
35
+ * name: string;
36
+ * }
37
+ * ```
38
+ */
14
39
  function DynamicTranslate() {
15
40
  return (target, key) => {
16
41
  Reflect.defineMetadata(exports.DYNAMIC_TRANSLATION_DECORATOR_KEY, {}, target, key);
@@ -13,7 +13,7 @@ const nestjs_i18n_1 = require("nestjs-i18n");
13
13
  const validation_metadata_helper_1 = require("../common/helpers/validation-metadata-helper");
14
14
  let HttpExceptionFilter = class HttpExceptionFilter {
15
15
  catch(exception, host) {
16
- var _a;
16
+ var _a, _b, _c, _d, _e;
17
17
  const i18n = nestjs_i18n_1.I18nContext.current(host);
18
18
  const ctx = host.switchToHttp();
19
19
  const response = ctx.getResponse();
@@ -26,13 +26,19 @@ let HttpExceptionFilter = class HttpExceptionFilter {
26
26
  statusCode = 404;
27
27
  error = 'Entity not found';
28
28
  }
29
- // 将异常信息存储到 request 对象上,供 HttpLoggingInterceptor 使用
30
- request._exception = {
31
- message: exception.message,
32
- stack: exception.stack,
33
- code: exception.code,
34
- statusCode,
35
- };
29
+ if (statusCode === 500) {
30
+ console.error({
31
+ message: exception.message,
32
+ stack: exception.stack,
33
+ connection: {
34
+ localAddress: (_a = request.socket) === null || _a === void 0 ? void 0 : _a.localAddress,
35
+ remoteAddress: (_b = request.socket) === null || _b === void 0 ? void 0 : _b.remoteAddress,
36
+ bytesRead: (_c = request.socket) === null || _c === void 0 ? void 0 : _c.bytesRead,
37
+ bytesWritten: (_d = request.socket) === null || _d === void 0 ? void 0 : _d.bytesWritten,
38
+ },
39
+ headers: request.headers,
40
+ });
41
+ }
36
42
  const getFirstError = (error, property = '') => {
37
43
  var _a;
38
44
  if (((_a = error === null || error === void 0 ? void 0 : error.children) === null || _a === void 0 ? void 0 : _a.length) > 0) {
@@ -51,7 +57,7 @@ let HttpExceptionFilter = class HttpExceptionFilter {
51
57
  const lang = i18n.lang || 'zh';
52
58
  // 在 stringify 之前获取 target
53
59
  // firstError.target 可能是实例而不是构造函数,所以需要获取 constructor
54
- let target = ((_a = firstError.target) === null || _a === void 0 ? void 0 : _a.constructor) || firstError.target;
60
+ let target = ((_e = firstError.target) === null || _e === void 0 ? void 0 : _e.constructor) || firstError.target;
55
61
  if (!target && (firstError === null || firstError === void 0 ? void 0 : firstError.object)) {
56
62
  target = firstError.object.constructor;
57
63
  }
@@ -17,6 +17,7 @@ export declare const HttpClient: (options?: HttpClientConfig) => (target: any) =
17
17
  * 基于axios-retry库
18
18
  */
19
19
  export declare const HttpRetry: (options?: {
20
+ enabled?: boolean;
20
21
  retries?: number;
21
22
  retryDelay?: (retryCount: number) => number;
22
23
  retryCondition?: (error: any) => boolean;