18 KiB
数据库主数据治理与外部只读契约
创建时间:2026-05-29
适用范围:furniture-3d-system 主业务数据库、产品资料链路、外部系统只读接入。
配套文档:
docs/架构设计/外部只读视图字段字典-v1.mddocs/架构设计/外部只读契约巡检说明.mddocs/架构设计/数据库质量巡检基线-20260529.mddocs/架构设计/内部主数据地基治理方案.mddocs/架构设计/内部主数据巡检基线-20260529.mdscripts/sql/grant-external-readonly.sqlscripts/sql/preflight-intake-non-integrated-sku-items.sqlscripts/sql/check-external-readonly-contract.sqlscripts/sql/check-internal-master-data-foundation.sqlscripts/verify-master-data-foundation.js
1. 结论
多个外部系统会依赖本系统数据库时,不能直接开放业务原表。本系统必须先把内部主数据真相源梳理清楚,再通过稳定的外部契约层对外开放。
固定原则:
- PostgreSQL 是本系统主业务数据库。
- 数据库保存业务数据、文件 URL、对象 key、元数据;图片、模型、附件等素材本体不直接存入数据库。
- 外部系统只能读专用只读契约层,不能直接读
ProductSKU、IntakeNonIntegratedProduct.payload等业务原表。 - 外部系统主同步、去重和幂等更新必须优先使用
barcode_business_key;内部id / product_id / sku_id / intake_product_id / intake_sku_item_id只能用于问题追踪。 - 内部业务表允许随系统演进调整;外部契约必须版本化、稳定、可追溯。
- 对外开放字段必须白名单化,默认不开放内部流程、权限、草稿、审批、临时 UI 状态。
2. 当前数据库与素材现状
2.1 主业务库
当前 Prisma datasource 使用 postgresql。项目运行时通过 @prisma/adapter-pg 和 pg 连接池访问 PostgreSQL。
mysql2 只用于 JST/聚水潭等外部库读取同步,例如 SKU 库存、供应商和仓库目录读取。它不是本项目主业务库。外部 MySQL 同步写入本系统前必须先确认来源库身份和目标 PostgreSQL 身份,不能把 MySQL 当作第二套内部主数据真相。
2.2 素材存放
数据和素材是分开存放的。
当前主要形态:
- 业务表中保存
modelUrl、thumbnailUrl、images、fileUrl、storageKey等引用。 - 非集成产品附件默认存放在
.data/non-integrated-assets,通过/api/intake/non-integrated/assets/...受权限控制读取。 - 旧附件可能仍在
public/non-integrated-test-assets。 - 资产缩略图、AI 生图等服务可通过 S3/MinIO 工具链上传,并在数据库保存公开 URL。
外部系统不应拿服务器文件系统权限。正确方式是读取数据库契约层里的 URL,再通过受控 HTTP 地址访问素材。
3. 主链数据真相源
3.1 资产中心
资产中心负责资产源数据。
| 数据对象 | 当前主表 | 真相源定位 | 对外开放建议 |
|---|---|---|---|
| 系列 | AssetSeries |
资产系列主数据 | 可开放编码、名称、分类、启用状态 |
| 资产族 | AssetFamily |
同系列下的资产族主数据 | 可开放编码、名称、类型、系列归属、状态 |
| 规格款 | AssetVariant |
具体可调用资产规格 | 可开放编码、名称、条码、尺寸、颜色、材质、图片、模型、重量、成本 |
| 花色/材质 | Material |
板材花色和材质配置 | 可开放编码、名称、贴图/缩略图、PBR 参数中的必要字段 |
| 五金件 | Hardware |
标准五金主数据 | 可开放编码、名称、分类、规格、模型、缩略图、尺寸 |
注意:
AssetVariant是资产中心里最接近后续 SKU 资料来源的结构化表。AssetVariant.businessData、assemblyBasis、compatibilityRules等 JSON 字段不能整体对外开放,需要按字段白名单投影。- 已删除、草稿、审核中资产不能默认对外开放,必须按状态过滤。
3.2 辅助装配与草稿组
辅助装配结果进入草稿组。草稿组是正式入库前的承接层,不是最终正式产品真相。
| 数据对象 | 当前主表 | 真相源定位 | 对外开放建议 |
|---|---|---|---|
| 草稿组 | DraftGroup |
一次辅助装配结果集合 | 默认不作为外部主数据,可按内部管理需要开放只读摘要 |
| 准 SKU | PreSku |
入库前的准 SKU 快照 | 默认不直接对外开放;正式入库后以 ProductSKU 为准 |
| 准 SKU 包装 | PreSkuPackagingPlan |
入库前包装草案 | 默认不直接对外开放;正式化后再进入正式产品/轻仓库契约 |
注意:
PreSku.familySelection、variantSelection、displaySummary、bomPreview、packagingSnapshot都是 JSON 快照,适合内部承接,不适合原样对外。- 外部系统如果需要看“待入库草稿”,应单独设计
external.draft_pre_skus_v1,并明确它不是正式商品资料。
3.3 正式产品资料
正式产品资料以 Product 和 ProductSKU 为核心。
| 数据对象 | 当前主表 | 真相源定位 | 对外开放建议 |
|---|---|---|---|
| 正式 SPU/型号 | Product |
正式产品型号主数据 | 可开放型号、名称、分类、缩略图、模型、状态 |
| 正式 SKU | ProductSKU |
正式产品 SKU 主数据 | 必须作为外部产品资料的核心出口;skuCode / name 为结构化主字段 |
| 正式 SKU 资产资料副本 | ProductSkuAssetProfile |
正式 SKU 对资产中心资料的结构化同步副本 | 开放型号、规格码、条码、尺寸、颜色、材质、图片、来源状态 |
| SKU 板件 | Panel |
SKU/产品结构件 | 需要时开放结构件编号、名称、尺寸、材质摘要 |
| SKU 孔位 | Hole |
SKU/产品孔位结构 | 仅向需要 3D/制造数据的系统开放,不作为普通商品资料 |
| SKU 五金 | ProductHardware |
SKU/产品五金用量和位置 | 可按 BOM 需求开放编码、数量、安装关系摘要 |
| 包装方案 | PackagingPlan / PackagedItem |
正式包装与库存承接 | 需单独按轻仓库口径整理 |
ProductSkuAssetProfile 保存正式 SKU 的资产资料同步副本,包括资产来源 ID、型号、规格码、条码、尺寸、颜色、材质、图片、来源状态等。ProductSKU.attributes.assetSkuSnapshot 仅作为兼容快照保留,不能再作为正式商品资料的主读真相。
代码层边界由 npm run db:verify-master-data:code 和 node scripts/verify-master-data-code-boundaries.js --mode compatibility-snapshot-read 共同兜底:正式 SKU 的 attributes、准 SKU 的 displaySummary / familySelection、非集成产品的 payload 只能留在已分类的兼容、迁移或过程入口里;高优先级或未分类读点会进入阻断风险,不能作为对外契约或内部主读的依据。
普通正式 SKU 的创建、CAD 导入、产品上传和编辑链路必须写入 ProductSKU.name。attributes.skuName 只允许作为兼容输入或历史兜底;对外视图、DTO 和轻仓库概览优先读取 ProductSKU.name。
外部正式产品契约统一携带稳定业务键:
external.product_skus_v1.product_business_key来自Product.modelNumber的规范化大写值。external.product_skus_v1.sku_business_key来自ProductSKU.skuCode的规范化大写值。external.product_skus_v1.barcode_business_key来自产品条码的规范化大写值,是外部系统最小商品身份。external.product_assets_v1和external.product_bom_v1也必须携带同一套业务键,避免外部系统为了关联素材或 BOM 被迫使用内部技术 ID。external.product_catalog_wide_v1在同一业务键口径上聚合正式 SKU,以及已进入 ready 状态但尚未正式落库的 non-integrated SKU;素材数量、BOM 摘要、仓库、质检和限价摘要优先按barcode_business_key关联。
3.4 非集成开品资料
非集成开品数据集中在 IntakeNonIntegratedProduct。
| 字段 | 定位 |
|---|---|
id |
内部产品记录 ID |
spu / spuBase |
产品编码和基础编码 |
brand |
品牌 |
designer / operator / purchaser |
人员字段 |
visibility / departmentId / teamId |
权限范围 |
payload |
大型业务资料快照 |
payload 包含 SKU、附件、BOM、空间布局、审批状态、文档工作区、权限确认、临时 UI 状态等大量内容。它是当前最大风险点,不能直接开放给外部系统。
当前治理口径:
IntakeNonIntegratedSkuItem是非集成 SKU 明细的结构化承接表。payload.skuItems作为历史过程快照保留,用于同步差异巡检,不作为对外主数据来源。IntakeNonIntegratedProductProfile承接产品名称、主图、品类、销售分类、年份、状态、开发类型、货品类型、材质类型、生产类型、主要负责人等产品头主数据。IntakeNonIntegratedProductLimitRow承接产品限价行,来源是productLimitRows的结构化同步副本,不允许外部系统直接读取payload推断限价。external.intake_products_v1读取IntakeNonIntegratedProductProfile,不再直接从payload/openingReference读取产品头主数据。external.intake_product_limits_v1读取IntakeNonIntegratedProductLimitRow;存量商品明细默认限价不适用,产品开发明细和模块化产品明细到产品限价节点确认后才视为正式限价。external.intake_products_v1 / external.intake_sku_items_v1显式输出稳定业务键:product_business_key = 品牌:SPU,sku_business_key = 品牌:SPU:SKU编码;这些键用于产品归组和兼容关联。- 非空
skuCode / barcode由数据库唯一索引兜底;外部正式商品最小身份以条码规范化后的barcode_business_key为准。 - 软删除非集成产品时,后端和数据库触发器都会清理结构化 SKU 明细,避免已删除记录占用编码池。
非集成对外只能做白名单投影,例如:
- 产品基础:
spu、品牌、产品名称、品类、年份、负责人、状态。 - SKU 明细:图片、型号、SKU 编码、规格名称、条码、尺寸、颜色、材质、版本号、供应商、成本等。
- BOM 摘要:只开放已确认的物料编码、名称、规格、数量、单位、成本字段。
- 素材:只开放可访问 URL、文件名、类型、大小,不开放内部路径和权限状态。
4. 当前主要风险
风险 1:结构化表和 JSON payload 并存
正式产品链较结构化,非集成开品链大量依赖 payload Json。这不是马上不可用,但对外开放时风险很高。
治理要求:
- 外部系统不得直接读取整段
payload。 - 必须先沉淀白名单字段。
- 高频外部依赖字段应逐步从
payload治理为结构化列或稳定视图字段。
风险 2:同一业务概念存在多个位置
图片、型号、规格码、条码、尺寸、颜色、材质可能同时出现在资产中心、草稿准 SKU、正式 SKU 快照、非集成 payload 中。
治理要求:
- 资产源数据以
AssetVariant为准。 - 草稿准 SKU 基础资料以
PreSkuBasicInfo为准,displaySummary只是展示兼容快照。 - 正式产品对外以
ProductSKU的结构化字段和ProductSkuAssetProfile副本为准。 - 非集成开品对外以
IntakeNonIntegratedProductProfile和IntakeNonIntegratedSkuItem的白名单投影结果为准。 - 页面展示字段不能反过来成为外部系统真相源。
- 内部兼容快照读点必须持续通过
compatibility-snapshot-read专项盘点,确保新增 DTO、Repository 或页面 helper 不会绕开结构化字段重新读取 JSON 快照。
风险 3:内部流程字段泄露
payload 内含审批、确认、上传、文档工作区、权限相关状态。直接开放会把内部流程暴露给外部系统。
治理要求:
- 外部契约层默认排除权限、审批、草稿、删除、锁定原因、内部人员 ID 等字段。
- 需要开放状态时,只开放业务状态摘要,例如
source_status、product_status、sku_status。
风险 4:素材 URL 混合来源
当前素材来源可能是 S3/MinIO URL、内部 API URL、旧 public 路径或历史 data URL。
治理要求:
- 外部契约层输出统一的
asset_url字段。 - 内部路径、
storageKey默认不对外开放;如外部系统确需同步文件,可单独开放asset_key,并配合签名 URL 或下载 API。 - 历史 data URL 需要逐步迁移成文件 URL。
风险 5:外部系统直接绑定内部表结构
如果直接给外部系统 SELECT 原始表,一旦内部迁移字段、改 JSON 结构或拆表,外部系统会一起故障。
治理要求:
- 新建独立
externalschema。 - 所有外部读取只走
external.*_v1视图或后端只读 API。 - 重大变更新增
v2,保留v1兼容窗口。
5. 外部只读契约层建议
推荐 PostgreSQL 中新增独立 schema:
CREATE SCHEMA IF NOT EXISTS external;
第一批建议视图:
| 视图 | 用途 |
|---|---|
external.product_skus_v1 |
正式产品 SKU 主数据 |
external.product_catalog_wide_v1 |
一条码一行的商品目录聚合宽表,覆盖正式条码与可消费的 non-integrated ready 条码,以及产品资料、开发阶段负责人、限价、库存库位摘要、包装库存摘要和最新质检摘要 |
external.intake_product_limits_v1 |
非集成产品限价白名单 |
external.warehouse_locations_v1 |
仓库库位资料 |
external.sku_warehouse_stock_v1 |
正式 SKU 库存和库位 |
external.packaged_items_v1 |
已关联正式 SKU 的包装库存项 |
external.sku_quality_inspections_v1 |
SKU 仓库/首批质检记录 |
external.product_assets_v1 |
SKU 图片、模型、资产来源、素材引用 |
external.product_bom_v1 |
正式 SKU 的板材、组件、五金摘要 |
external.intake_products_v1 |
非集成开品主信息白名单 |
external.intake_sku_items_v1 |
非集成 SKU 明细白名单 |
external.materials_v1 |
花色/材质基础资料 |
external.asset_variants_v1 |
可被正式产品引用的资产规格款 |
契约层允许保留内部技术 ID 作为追踪字段,但外部主同步必须优先使用 barcode_business_key;product_business_key 用于 SPU/型号归组,sku_business_key 用于 SKU 编码兼容关联。仓库库位优先使用 warehouse_location_code,质检记录自身去重使用 record_key。独立 SQL 巡检和 npm run db:verify-master-data:all 都会检查核心外部视图的业务键缺失或重复风险。
只读账号授权原则:
GRANT USAGE ON SCHEMA external TO external_core_reader;
GRANT SELECT ON external.product_catalog_wide_v1 TO external_core_reader;
GRANT SELECT ON external.intake_product_limits_v1 TO external_core_reader;
GRANT SELECT ON external.warehouse_locations_v1 TO external_core_reader;
GRANT SELECT ON external.sku_warehouse_stock_v1 TO external_core_reader;
GRANT SELECT ON external.packaged_items_v1 TO external_core_reader;
GRANT SELECT ON external.sku_quality_inspections_v1 TO external_core_reader;
不要给外部账号使用 SELECT ON ALL TABLES IN SCHEMA external 这类全量授权;新增外部视图必须单独评审后追加白名单。
禁止:
- 给外部账号授权
publicschema 全表读取。 - 给外部账号授权业务原表。
- 给外部账号写权限。
- 把数据库超级用户、应用用户连接串给外部系统。
6. 治理优先级
P0:先定契约,不先大改表
先完成数据字典和视图草案。不要在真相源没定前重构所有表。
产出:
- 核心表职责清单。
- 对外字段白名单。
- 第一版
external视图 SQL 草案。 - 只读账号权限方案。
P1:正式产品链结构化稳定
把外部系统最需要的数据优先从 Product / ProductSKU / AssetVariant / Material / Hardware 组合成稳定视图。
重点字段:
- SPU/型号、SKU 编码、规格名称、规格码、条码。
- 图片、3D 模型。
- 尺寸、颜色、材质。
- 重量、成本、供应商。
- 来源资产 ID、来源状态、同步时间。
P2:非集成 payload 白名单投影
只对外投影确认后的产品资料和 SKU 明细。不要把 payload 的流程数据整体暴露。
重点字段:
openingReference中的产品基础资料。skuItems中的 SKU 明细。- 已确认 BOM 摘要。
- 附件 URL 摘要。
P3:逐步治理高频字段
如果某些字段长期被多个系统依赖,就不应该永远藏在 JSON 里。后续应逐步结构化。
候选字段:
- 产品正式编码。
- SKU 条码。
- 规格码和版本号。
- 主图 URL。
- 材质、颜色、尺寸。
- 供应商编码。
- 成本、重量。
7. 当前不建议做的事
- 不建议马上把所有 JSON 字段拆表,成本高且容易影响现有业务。
- 不建议直接让外部系统读
IntakeNonIntegratedProduct.payload。 - 不建议把素材二进制塞进 PostgreSQL。
- 不建议通过共享应用数据库账号解决只读需求。
- 不建议先开放再补契约文档。
8. 下一步执行清单
- 输出第一版外部字段字典。
- 写并迁移
external.product_skus_v1 / product_assets_v1 / product_bom_v1 / intake_products_v1 / intake_sku_items_v1等 SQL 视图。 - 在测试库验证视图字段、稳定业务键和样例数据。
- 建只读账号权限脚本。
- 明确素材访问方式:公开 URL、登录态 API、签名 URL 三选一。
- 和外部系统确认字段命名、增量同步方式、删除/失效状态口径。
- 再决定是否把高频 JSON 字段结构化。
- 部署后运行
scripts/sql/check-external-readonly-contract.sql,确认视图、字段、授权和关键数据质量。
9. 规范审查
结论:passed
这是自审,不是独立复审。
- 架构:文档明确了主数据真相源、外部契约层和只读账号边界,没有破坏现有分层。
- 视觉:本次不涉及前端视觉。
- 根因:没有用“直接开放表权限”做表面处理,而是先治理内部数据库地基和对外契约。