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 与时间
|
### ID 与时间
|
||||||
|
|
||||||
- 后台主键使用 Snowflake 数字 ID,接口按字符串返回,避免 JavaScript 长整型精度丢失。
|
- 后台主键使用 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`,防止新增接口误用前端主键。
|
- `POST */save` 默认会删除请求体里的 `id`,防止新增接口误用前端主键。
|
||||||
|
|
||||||
## Runtime Health
|
## Runtime Health
|
||||||
|
|||||||
@ -181,7 +181,7 @@ API 暴露 `GET /health/runtime` 作为本地 smoke、Jenkins/K8s 和 ktWorkflow
|
|||||||
## 核心规则
|
## 核心规则
|
||||||
|
|
||||||
- 后台主键使用 Snowflake 数字 ID,数据库字段为 `BIGINT`,接口按字符串返回。
|
- 后台主键使用 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` 分组展示;可运营映射优先走字典或静态配置,不硬编码到业务函数。
|
- 字典维护在 `admin_dict`,Admin 字典管理按 `dictCode` 分组展示;可运营映射优先走字典或静态配置,不硬编码到业务函数。
|
||||||
- 全局 `SaveBodyInterceptor` 会删除 `POST */save` 请求体里的 `id`;需要保留时使用 `@SkipSaveBodyNormalize()`。
|
- 全局 `SaveBodyInterceptor` 会删除 `POST */save` 请求体里的 `id`;需要保留时使用 `@SkipSaveBodyNormalize()`。
|
||||||
- Admin、Component、Dict、MinIO、Blog 管理、WordPress 管理和 QQBot 管理接口默认走 `JwtAuthGuard`;公开接口用 `@Public()`。
|
- Admin、Component、Dict、MinIO、Blog 管理、WordPress 管理和 QQBot 管理接口默认走 `JwtAuthGuard`;公开接口用 `@Public()`。
|
||||||
|
|||||||
@ -201,8 +201,9 @@ export function KtCreateDateColumn(
|
|||||||
options?: ColumnOptions,
|
options?: ColumnOptions,
|
||||||
): PropertyDecorator {
|
): PropertyDecorator {
|
||||||
const normalized = normalizeDateTimeColumnOptions(formatOrOptions, options);
|
const normalized = normalizeDateTimeColumnOptions(formatOrOptions, options);
|
||||||
|
const columnOptions = applyCurrentTimestampPrecision(normalized.options);
|
||||||
return CreateDateColumn({
|
return CreateDateColumn({
|
||||||
...normalized.options,
|
...columnOptions,
|
||||||
transformer: mergeDateTimeTransformer(
|
transformer: mergeDateTimeTransformer(
|
||||||
normalized.options.transformer,
|
normalized.options.transformer,
|
||||||
normalized.format,
|
normalized.format,
|
||||||
@ -248,8 +249,12 @@ export function KtUpdateDateColumn(
|
|||||||
options?: ColumnOptions,
|
options?: ColumnOptions,
|
||||||
): PropertyDecorator {
|
): PropertyDecorator {
|
||||||
const normalized = normalizeDateTimeColumnOptions(formatOrOptions, options);
|
const normalized = normalizeDateTimeColumnOptions(formatOrOptions, options);
|
||||||
|
const columnOptions = applyCurrentTimestampPrecision(
|
||||||
|
normalized.options,
|
||||||
|
true,
|
||||||
|
);
|
||||||
return UpdateDateColumn({
|
return UpdateDateColumn({
|
||||||
...normalized.options,
|
...columnOptions,
|
||||||
transformer: mergeDateTimeTransformer(
|
transformer: mergeDateTimeTransformer(
|
||||||
normalized.options.transformer,
|
normalized.options.transformer,
|
||||||
normalized.format,
|
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。
|
* 合并Date Time Transformer。
|
||||||
* @param existing - existing 输入;驱动 `Array.isArray()` 的 公共基础设施步骤。
|
* @param existing - existing 输入;驱动 `Array.isArray()` 的 公共基础设施步骤。
|
||||||
|
|||||||
@ -27,6 +27,28 @@ class DateTimeColumnFixture {
|
|||||||
|
|
||||||
@KtUpdateDateColumn('YYYY-MM-DD')
|
@KtUpdateDateColumn('YYYY-MM-DD')
|
||||||
updateTime!: KtDateTime;
|
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', () => {
|
describe('KtDateTime decorators', () => {
|
||||||
@ -77,6 +99,37 @@ describe('KtDateTime decorators', () => {
|
|||||||
expect(String(updateTime)).toBe('2026-05-13');
|
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', () => {
|
it('keeps DTO transformer datetime fields usable as Date values', () => {
|
||||||
const data = new DateTimeFormatFixture();
|
const data = new DateTimeFormatFixture();
|
||||||
data.createTime = new Date(2026, 4, 13, 10, 30, 0);
|
data.createTime = new Date(2026, 4, 13, 10, 30, 0);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user