fix: 对齐日期时间列默认值精度
This commit is contained in:
parent
1c35fc9dbd
commit
33e8f28354
2
API.md
2
API.md
@ -47,7 +47,7 @@ Admin、Component、Dict、MinIO、Blog 管理、WordPress 管理和 QQBot 管
|
||||
### ID 与时间
|
||||
|
||||
- 后台主键使用 Snowflake 数字 ID,接口按字符串返回,避免 JavaScript 长整型精度丢失。
|
||||
- 后端格式化时间字段统一使用 `KtDateTime extends Date`:Entity 通过 `@KtDateTimeColumn(format)`、`@KtCreateDateColumn(format)`、`@KtUpdateDateColumn(format)` 在 TypeORM hydrate 边界转换;DTO/外部数据源通过 `@KtDateTimeField(format)` + `transformKtDateTimeFields()` 转换。默认输出 `YYYY-MM-DD HH:mm:ss`,可在装饰器中传入格式字符串;响应包装不做递归遍历。
|
||||
- 后端格式化时间字段统一使用 `KtDateTime extends Date`:Entity 通过 `@KtDateTimeColumn(format)`、`@KtCreateDateColumn(format)`、`@KtUpdateDateColumn(format)` 在 TypeORM hydrate 边界转换;DTO/外部数据源通过 `@KtDateTimeField(format)` + `transformKtDateTimeFields()` 转换。默认输出 `YYYY-MM-DD HH:mm:ss`,可在装饰器中传入格式字符串;Create/Update 列显式配置 `precision` 时自动生成同精度 `CURRENT_TIMESTAMP(n)`,调用方显式 `default/onUpdate` 保持优先;响应包装不做递归遍历。
|
||||
- `POST */save` 默认会删除请求体里的 `id`,防止新增接口误用前端主键。
|
||||
|
||||
## Runtime Health
|
||||
|
||||
@ -181,7 +181,7 @@ API 暴露 `GET /health/runtime` 作为本地 smoke、Jenkins/K8s 和 ktWorkflow
|
||||
## 核心规则
|
||||
|
||||
- 后台主键使用 Snowflake 数字 ID,数据库字段为 `BIGINT`,接口按字符串返回。
|
||||
- 后端响应时间统一用 `KtDateTime extends Date` 承接序列化语义;Entity 使用 `@KtDateTimeColumn(format)`、`@KtCreateDateColumn(format)`、`@KtUpdateDateColumn(format)` 在 TypeORM hydrate 边界转换,DTO/外部数据源使用 `@KtDateTimeField(format)` + `transformKtDateTimeFields()` 转换,默认格式为 `YYYY-MM-DD HH:mm:ss`。`vbenSuccess` / `ToolsService.res` 不做全量递归格式化。
|
||||
- 后端响应时间统一用 `KtDateTime extends Date` 承接序列化语义;Entity 使用 `@KtDateTimeColumn(format)`、`@KtCreateDateColumn(format)`、`@KtUpdateDateColumn(format)` 在 TypeORM hydrate 边界转换,DTO/外部数据源使用 `@KtDateTimeField(format)` + `transformKtDateTimeFields()` 转换,默认格式为 `YYYY-MM-DD HH:mm:ss`。Create/Update 列显式配置 `precision` 时,公共装饰器会在调用方未覆盖的前提下生成同精度 `CURRENT_TIMESTAMP(n)` 默认值与更新表达式,避免 MySQL 列精度不匹配;`vbenSuccess` / `ToolsService.res` 不做全量递归格式化。
|
||||
- 字典维护在 `admin_dict`,Admin 字典管理按 `dictCode` 分组展示;可运营映射优先走字典或静态配置,不硬编码到业务函数。
|
||||
- 全局 `SaveBodyInterceptor` 会删除 `POST */save` 请求体里的 `id`;需要保留时使用 `@SkipSaveBodyNormalize()`。
|
||||
- Admin、Component、Dict、MinIO、Blog 管理、WordPress 管理和 QQBot 管理接口默认走 `JwtAuthGuard`;公开接口用 `@Public()`。
|
||||
|
||||
@ -201,8 +201,9 @@ export function KtCreateDateColumn(
|
||||
options?: ColumnOptions,
|
||||
): PropertyDecorator {
|
||||
const normalized = normalizeDateTimeColumnOptions(formatOrOptions, options);
|
||||
const columnOptions = applyCurrentTimestampPrecision(normalized.options);
|
||||
return CreateDateColumn({
|
||||
...normalized.options,
|
||||
...columnOptions,
|
||||
transformer: mergeDateTimeTransformer(
|
||||
normalized.options.transformer,
|
||||
normalized.format,
|
||||
@ -248,8 +249,12 @@ export function KtUpdateDateColumn(
|
||||
options?: ColumnOptions,
|
||||
): PropertyDecorator {
|
||||
const normalized = normalizeDateTimeColumnOptions(formatOrOptions, options);
|
||||
const columnOptions = applyCurrentTimestampPrecision(
|
||||
normalized.options,
|
||||
true,
|
||||
);
|
||||
return UpdateDateColumn({
|
||||
...normalized.options,
|
||||
...columnOptions,
|
||||
transformer: mergeDateTimeTransformer(
|
||||
normalized.options.transformer,
|
||||
normalized.format,
|
||||
@ -311,6 +316,23 @@ function normalizeDateTimeColumnOptions(
|
||||
};
|
||||
}
|
||||
|
||||
function applyCurrentTimestampPrecision(
|
||||
options: ColumnOptions,
|
||||
includeOnUpdate = false,
|
||||
): ColumnOptions {
|
||||
if (options.precision === undefined) return options;
|
||||
|
||||
const currentTimestamp = `CURRENT_TIMESTAMP(${options.precision})`;
|
||||
return {
|
||||
...options,
|
||||
default:
|
||||
options.default === undefined ? () => currentTimestamp : options.default,
|
||||
...(includeOnUpdate && options.onUpdate === undefined
|
||||
? { onUpdate: currentTimestamp }
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并Date Time Transformer。
|
||||
* @param existing - existing 输入;驱动 `Array.isArray()` 的 公共基础设施步骤。
|
||||
|
||||
@ -27,6 +27,28 @@ class DateTimeColumnFixture {
|
||||
|
||||
@KtUpdateDateColumn('YYYY-MM-DD')
|
||||
updateTime!: KtDateTime;
|
||||
|
||||
@KtCreateDateColumn({ precision: 3, type: 'datetime' })
|
||||
preciseCreateTime!: KtDateTime;
|
||||
|
||||
@KtUpdateDateColumn({ precision: 3, type: 'datetime' })
|
||||
preciseUpdateTime!: KtDateTime;
|
||||
|
||||
@KtCreateDateColumn({
|
||||
default: null,
|
||||
nullable: true,
|
||||
precision: 3,
|
||||
type: 'datetime',
|
||||
})
|
||||
customCreateTime!: KtDateTime | null;
|
||||
|
||||
@KtUpdateDateColumn({
|
||||
default: () => 'CUSTOM_DEFAULT',
|
||||
onUpdate: 'CUSTOM_ON_UPDATE',
|
||||
precision: 3,
|
||||
type: 'datetime',
|
||||
})
|
||||
customUpdateTime!: KtDateTime;
|
||||
}
|
||||
|
||||
describe('KtDateTime decorators', () => {
|
||||
@ -77,6 +99,37 @@ describe('KtDateTime decorators', () => {
|
||||
expect(String(updateTime)).toBe('2026-05-13');
|
||||
});
|
||||
|
||||
it('matches generated current timestamp defaults to explicit column precision', () => {
|
||||
const metadata = getMetadataArgsStorage().columns.filter(
|
||||
(column) => column.target === DateTimeColumnFixture,
|
||||
);
|
||||
const createOptions = metadata.find(
|
||||
(column) => column.propertyName === 'preciseCreateTime',
|
||||
)?.options;
|
||||
const updateOptions = metadata.find(
|
||||
(column) => column.propertyName === 'preciseUpdateTime',
|
||||
)?.options;
|
||||
const customCreateOptions = metadata.find(
|
||||
(column) => column.propertyName === 'customCreateTime',
|
||||
)?.options;
|
||||
const customUpdateOptions = metadata.find(
|
||||
(column) => column.propertyName === 'customUpdateTime',
|
||||
)?.options;
|
||||
|
||||
expect((createOptions?.default as () => string)()).toBe(
|
||||
'CURRENT_TIMESTAMP(3)',
|
||||
);
|
||||
expect((updateOptions?.default as () => string)()).toBe(
|
||||
'CURRENT_TIMESTAMP(3)',
|
||||
);
|
||||
expect(updateOptions?.onUpdate).toBe('CURRENT_TIMESTAMP(3)');
|
||||
expect(customCreateOptions?.default).toBeNull();
|
||||
expect((customUpdateOptions?.default as () => string)()).toBe(
|
||||
'CUSTOM_DEFAULT',
|
||||
);
|
||||
expect(customUpdateOptions?.onUpdate).toBe('CUSTOM_ON_UPDATE');
|
||||
});
|
||||
|
||||
it('keeps DTO transformer datetime fields usable as Date values', () => {
|
||||
const data = new DateTimeFormatFixture();
|
||||
data.createTime = new Date(2026, 4, 13, 10, 30, 0);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user