feat(api): A4 数据字典与系统配置后端

背景:A4 需要字典三层合并、配置单一来源与阈值同源。\n改动:新增 catalog 模块(类型/条目/三层合并/编码不可改/快照工具);platform 新增 SystemConfigService/Controller、NamingService 与配置版本历史;roles/users/customers/customer-fields 改为读 system_configs 上限。\n影响面:新增 13 个 REST 端点与 4 个模块注入;system_configs 写入走事务/乐观锁/版本历史;OpenAPI 快照更新。\n回滚:回滚本提交并恢复 shared 契约;新增表可保留。\n验证:api typecheck/build;单测 137;集成 58;OpenAPI diff 无破坏性变更。
This commit is contained in:
Stev_Wang committed 2026-11-04 11:49:05 +08:00
1 parent cc6e84cdbc
commit e4330a43e4
25 files changed
+4189 -30

No files matched your search

@@ -0,0 +1,78 @@
/**
* 系统配置 Prisma 行 -> 共享 DTO 映射(A4-BE-02)。
*
* 元数据(标签 / 说明 / 影响面 / 类型 / 上下限 / 是否公开)唯一来源是
* `SYSTEM_CONFIG_DEFINITIONS`;`system_configs` 只提供当前值与版本。
* DB 缺行时用定义默认值 + version 1 兜底,不写库。
*/
import {
type SystemConfig,
type SystemConfigChange,
type SystemConfigDefinition,
type SystemConfigValue,
systemConfigValueType,
} from '@lingxi/shared';
import type { Prisma } from '@prisma/client';
import type {
SystemConfig as PrismaSystemConfig,
SystemConfigVersion as PrismaSystemConfigVersion,
} from '@prisma/client';
/** 从未落库的配置:用 epoch 表示「未更新过」,避免伪造读取时间 */
const NEVER_UPDATED_AT = new Date(0).toISOString();
/** Prisma JsonB 只应存原始值;异常结构回退配置默认值,避免脏数据打穿前端 schema */
export function normalizeConfigValue(
raw: Prisma.JsonValue | undefined,
fallback: SystemConfigValue,
): SystemConfigValue {
if (typeof raw === 'string' || typeof raw === 'number' || typeof raw === 'boolean') {
return raw;
}
return fallback;
}
export function toSystemConfig(
definition: SystemConfigDefinition,
row: PrismaSystemConfig | undefined,
updatedByName?: string | null,
): SystemConfig {
return {
key: definition.key,
group: definition.group,
label: definition.label,
description: definition.description,
impact: definition.impact,
type: definition.type,
value: normalizeConfigValue(row?.configValue, definition.defaultValue),
valueType: systemConfigValueType(definition),
unit: definition.unit ?? null,
version: row?.version ?? 1,
updatedAt: row === undefined ? NEVER_UPDATED_AT : row.updatedAt.toISOString(),
updatedBy: updatedByName ?? row?.updatedBy ?? null,
editable: definition.editable,
isPublic: definition.public,
min: definition.min ?? null,
max: definition.max ?? null,
step: definition.step ?? null,
maxLength: definition.maxLength ?? null,
};
}
export function toSystemConfigChange(
row: PrismaSystemConfigVersion,
label: string,
operator: string | null,
): SystemConfigChange {
return {
key: row.configKey,
label,
version: row.version,
oldValue: row.oldValue,
newValue: row.newValue,
reason: row.reason,
operator,
changedAt: row.changedAt.toISOString(),
result: '已生效',
};
}