【SRM数据看板】基础数据管理 — 详细设计文档
版本:V3.3
日期:2025-07-11
状态:修订稿
涉及系统:SRM 租户管理系统、SRM 品牌方管理系统
前置依赖:商品主表 goods_base 已有
1. 文档概述
1.1 目的
本文档基于《【SRM数据看板】基础数据管理需求规格说明书 V2.0》,对商品类目管理、属性库管理、商品档案管理等功能模块进行详细设计。
渠道表和部门表已存在,不在本次设计范围内。
1.2 设计范围
| 模块 | 功能点 | 备注 |
|---|---|---|
| 商品类目管理 | 类目列表、新建/编辑类目、类目属性模板 | 新建表 |
| 属性库管理 | 属性列表、新增/编辑/删除属性 | 新建表 |
| 商品档案管理 | 新建商品(优化) | 基于已有 goods_base |
| 历史商品数据割接 | 预制默认类目 + 类目变更机制 | 通过 catalog_id 判断 |
1.3 不在本迭代范围
- 渠道/部门表变更(已存在)
- 变更日志功能(含变更日志表,本迭代不建表;仅类目和属性需要,后续迭代补充)
- 商品批量导入(已移除)
- catalog_id 自动填充(由割接方案处理)
2. 核心设计决策
2.1 商品数据存储方案(方案 B)
goods_base 固定字段不动
↓
通用属性 → 类目模板控制显示顺序/可见性 → 存储到 goods_base 固定列
类目属性 → 类目模板定义可用属性 → 存储到动态属性表
2.2 编码唯一性规则
| 业务对象 | 唯一性范围 |
|---|---|
| 渠道编码 | 租户内唯一 |
| 部门编码 | 租户内唯一 |
| 类目编码 | 全局唯一(数据按租户隔离) |
| 商品编码 | 全局唯一 |
2.3 价格处理规则
| 场景 | 处理方式 |
|---|---|
| 页面输入 | 用户输入小数,如 99.00 |
| 页面显示 | 保留 2 位小数 |
| 数据库存储 | 金额 × 10000 存为整数 |
| 读取展示 | 数据库值 ÷ 10000 还原 |
2.4 权限控制
- 仅菜单按钮级权限
2.5 重量字段
- 数字类型,单位 g(克)
2.6 历史商品割接策略
- 建立预制默认类目
CAT_DEFAULT,仅含 14 项通用属性,无类目属性 - 历史商品 catalog_id 统一指向
CAT_DEFAULT - 通过
catalog_id = 'cat_default'判断是否允许类目变更,不需要额外字段 - 类目属性允许先保存后补填
3. 数据模型设计
3.1 公共字段规范
所有新建表必须包含以下标准字段:
| 字段名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| id | VARCHAR(64) | — | 主键 |
| create_by | VARCHAR(64) | NULL | 创建者 |
| create_date | DATETIME | CURRENT_TIMESTAMP | 创建时间 |
| update_by | VARCHAR(64) | NULL | 更新者 |
| update_date | DATETIME | CURRENT_TIMESTAMP | 更新时间 |
| remarks | VARCHAR(256) | NULL | 备注 |
| del_flag | VARCHAR(2) | '0' | 删除标记 1=已删除, 0=未删除 |
3.2 商品类目表 (t_goods_catalog)
| 字段名 | 数据类型 | 必填 | 说明 |
|---|---|---|---|
| id | VARCHAR(64) | PK | 主键 |
| catalog_code | VARCHAR(64) | ✅ | 类目编码,全局唯一 |
| catalog_name | VARCHAR(64) | ✅ | 类目名称 |
| parent_id | VARCHAR(64) | ❌ | 父类目 ID,NULL 为一级 |
| parent_ids | VARCHAR(256) | ✅ | 物化路径:所有上级+自己ID,逗号分隔,如 "1,5,12" |
| level | TINYINT | ✅ | 层级:1/2/3,最多三级 |
| status | VARCHAR(16) | ✅ | 状态:1-启用,0-停用 |
| description | VARCHAR(512) | ❌ | 类目说明 |
| has_product | TINYINT | ✅ | 是否已关联商品:0-否,1-是(默认0,商品创建时置1) |
| category_type | VARCHAR(20) | ✅ | 类目类型:DEFAULT=预制默认类目,NORMAL=正常业务类目(默认 NORMAL) |
| + 公共字段 | — | — | 见 3.1 |
业务规则: - category_type=DEFAULT 的预制类目:不可删除、不可停用、不可修改模板 - 预制类目 has_product 始终为 1 - 新建商品时类目选择器过滤掉 DEFAULT 类目
索引:
| 索引名 | 字段 | 类型 |
|---|---|---|
| uk_catalog_code | catalog_code | UNIQUE(全局) |
| idx_parent_ids | parent_ids | NORMAL(支持 LIKE 查询子孙) |
| idx_parent_id | parent_id | NORMAL |
| idx_category_type | category_type | NORMAL |
3.3 属性库表 (goods_attribute)
| 字段名 | 数据类型 | 必填 | 说明 |
|---|---|---|---|
| id | VARCHAR(64) | PK | 主键 |
| attr_type | TINYINT | ✅ | 属性类型:1-通用属性(平台),2-类目属性 |
| attr_name | VARCHAR(128) | ✅ | 属性名称 |
| data_type | TINYINT | ✅ | 数据类型:1-文本,2-单选,3-多选,4-数字,5-数值区间,6-图片上传 |
| options | TEXT | ❌ | 选项值 JSON(单选/多选时使用) |
| regex_pattern | VARCHAR(256) | ❌ | 正则表达式 |
| description | VARCHAR(512) | ❌ | 属性说明(≤200字) |
| is_builtin | TINYINT | ✅ | 是否内置:1-是(不可删除),0-否 |
| + 公共字段 | — | — | 见 3.1 |
业务规则: - 属性库由总后台统一维护(无 tenant_id) - 属性没有启用/停用状态 - 内置属性(is_builtin=1)不可删除 - 数值区间类型的单位通过 goods_attribute_option(option_type=2)管理
索引:
| 索引名 | 字段 | 类型 |
|---|---|---|
| idx_attr_type | attr_type | NORMAL |
3.4 属性选项值表 (goods_attribute_option)
| 字段名 | 数据类型 | 必填 | 说明 |
|---|---|---|---|
| id | VARCHAR(64) | PK | 主键 |
| attribute_id | VARCHAR(64) | ✅ | 属性 ID(FK → goods_attribute.id) |
| option_value | VARCHAR(128) | ✅ | 选项值文本 |
| option_type | TINYINT | ✅ | 选项类型:1-属性选项值,2-单位选项值(数值区间用) |
| sort_order | INT | ✅ | 排序号 |
| + 公共字段 | — | — | 见 3.1 |
3.5 类目属性模板组表 (goods_catalog_template_group)
| 字段名 | 数据类型 | 必填 | 说明 |
|---|---|---|---|
| id | VARCHAR(64) | PK | 主键 |
| catalog_id | VARCHAR(64) | ✅ | 类目 ID(FK → t_goods_catalog.id) |
| group_name | VARCHAR(32) | ✅ | 属性组名称(≤12字符) |
| group_desc | VARCHAR(512) | ❌ | 属性组说明 |
| group_sort | INT | ✅ | 属性组排序 |
| + 公共字段 | — | — | 见 3.1 |
3.6 类目属性模板明细表 (goods_catalog_template_item)
| 字段名 | 数据类型 | 必填 | 说明 |
|---|---|---|---|
| id | VARCHAR(64) | PK | 主键 |
| group_id | VARCHAR(64) | ✅ | 模板属性组 ID(FK → goods_catalog_template_group.id) |
| attribute_id | VARCHAR(64) | ✅ | 属性 ID(FK → goods_attribute.id) |
| is_required | TINYINT | ✅ | 是否必填:1-是,0-否 |
| is_visible | TINYINT | ✅ | 是否显示:1-显示,0-隐藏(通用属性用) |
| sort_order | INT | ✅ | 排序号 |
| + 公共字段 | — | — | 见 3.1 |
3.7 商品动态属性值表 (goods_product_attr_value)
| 字段名 | 数据类型 | 必填 | 说明 |
|---|---|---|---|
| id | VARCHAR(64) | PK | 主键 |
| product_id | VARCHAR(64) | ✅ | 商品 ID(FK → goods_base.id) |
| attribute_id | VARCHAR(64) | ✅ | 属性 ID(FK → goods_attribute.id) |
| attr_value | TEXT | ❌ | 属性值(文本/单选/数值) |
| attr_values | TEXT | ❌ | 多选值 JSON(如 ["红色","蓝色"]) |
| attr_value_min | DECIMAL(18,4) | ❌ | 数值区间最小值 |
| attr_value_max | DECIMAL(18,4) | ❌ | 数值区间最大值 |
| + 公共字段 | — | — | 见 3.1 |
索引:
| 索引名 | 字段 | 类型 |
|---|---|---|
| uk_product_attr | product_id + attribute_id | UNIQUE |
| idx_attribute_id | attribute_id | NORMAL |
3.8 变更日志表 (goods_change_log)
⚠️ 本迭代不建表。仅类目和属性需要变更日志,后续迭代补充。
预留表结构(点击展开)
| 字段名 | 数据类型 | 必填 | 说明 | |--------|----------|------|------| | id | VARCHAR(64) | PK | 主键 | | biz_type | VARCHAR(32) | ✅ | 业务类型:CATALOG / ATTRIBUTE | | biz_id | VARCHAR(64) | ✅ | 业务对象 ID | | action | VARCHAR(16) | ✅ | 操作类型:CREATE / UPDATE / DELETE | | before_data | TEXT | ❌ | 变更前数据 JSON | | after_data | TEXT | ❌ | 变更后数据 JSON | | tenant_id | VARCHAR(64) | ✅ | 租户 ID | | + 公共字段 | — | — | 见 3.1(create_by 为操作人) | **索引**: | 索引名 | 字段 | 类型 | |--------|------|------| | idx_biz | biz_type, biz_id | NORMAL | | idx_create_date | create_date | NORMAL |~~3.9 导入任务表 (goods_import_task)~~
❌ 已删除:本迭代不需要导入任务表。
3.10 goods_base 已有表
goods_base 为已有表,不新增任何字段。类目变更判断仅通过
catalog_id判断: -catalog_id = 'cat_default'→ 允许切换类目 -catalog_id ≠ 'cat_default'→ 不允许切换类目
不需要 is_catalog_confirmed 字段。
3.11 ER 关系图
goods_attribute ──1:N──→ goods_attribute_option
│
│ (模板引用)
↓
goods_catalog_template_group ──1:N──→ goods_catalog_template_item
│ │
│ (所属类目) (引用属性)
↓ │
t_goods_catalog ←───────────────────────────┘
├── category_type = NORMAL (业务类目)
└── category_type = DEFAULT (预制默认类目)
│
│ (商品选择类目 → goods_base.catalog_id)
↓
goods_base (已有,不新增字段)
└── catalog_id = 'cat_default' → 允许变更类目
└── catalog_id ≠ 'cat_default' → 不允许变更类目
│
└──1:N──→ goods_product_attr_value
│
└──→ goods_attribute
4. 接口设计
4.1 商品类目管理接口
4.1.1 查询类目列表
GET /api/v1/goods-catalog/list
请求参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| keyword | String | ❌ | 类目编码/名称模糊搜索 |
| status | String | ❌ | 状态 |
| startDate | String | ❌ | 创建日期起 |
| endDate | String | ❌ | 创建日期止 |
4.1.2 获取类目树
GET /api/v1/goods-catalog/tree
业务规则: - 新建商品调用时,过滤掉 category_type='DEFAULT',仅返回 NORMAL 类目 - 类目管理页面调用时,返回全部类目(含 DEFAULT)
响应体:
{
"code": 200,
"data": [
{
"id": "1",
"catalogCode": "CAT001",
"catalogName": "婴童服饰鞋配",
"categoryType": "NORMAL",
"level": 1,
"children": [
{
"id": "2",
"catalogCode": "CAT001-01",
"catalogName": "婴童内着家居服",
"categoryType": "NORMAL",
"level": 2,
"children": []
}
]
}
]
}
4.1.3 新建类目
POST /api/v1/goods-catalog
业务逻辑:
- 校验类目编码全局唯一
- 校验层级不超过 3 级
- 若有 parentId,从父类目继承模板(深拷贝)
- category_type 强制为 NORMAL(不允许通过 API 创建 DEFAULT 类目)
请求体:
{
"catalogCode": "CAT001",
"catalogName": "婴童服饰鞋配",
"parentId": null,
"level": 1,
"description": "",
"status": "1",
"templateGroups": [
{
"groupName": "基础信息",
"groupDesc": "商品基础属性",
"groupSort": 1,
"items": [
{
"attributeId": "attr_001",
"isRequired": 1,
"isVisible": 1,
"sortOrder": 1
}
]
}
]
}
4.1.4 编辑类目
PUT /api/v1/goods-catalog/{id}
业务逻辑:
- category_type=DEFAULT 的类目禁止编辑
- 若类目已关联商品:只允许新增属性到模板,不允许删除或修改已有属性
- 若类目未关联商品:允许完整 CRUD
- 修改模板后级联影响子类目:
- 子级属性 = 子级自有属性 + 上级属性(去重)
- 上级新增属性同步到子级和已有商品
4.1.5 删除类目
DELETE /api/v1/goods-catalog/{id}
业务校验: - category_type=DEFAULT 的类目禁止删除 - 类目下存在商品或有子类目时禁止删除
4.1.6 预览类目商品模板
GET /api/v1/goods-catalog/{id}/template-preview
返回该类目完整属性模板(含继承的所有层级属性,去重后),用于新建/编辑商品时展示
响应体:
{
"code": 200,
"data": {
"catalogId": "1",
"catalogName": "婴童服饰鞋配",
"groups": [
{
"groupId": "g1",
"groupName": "基础信息",
"groupSort": 1,
"items": [
{
"attributeId": "attr_001",
"attrName": "商品编码",
"attrType": 1,
"dataType": 1,
"isRequired": 1,
"isVisible": 1,
"sortOrder": 1,
"isBuiltin": 1,
"mappedField": "code"
}
]
}
]
}
}
4.2 属性库管理接口
4.2.1 查询属性列表
GET /api/v1/attribute/list
请求参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| attrType | Integer | ❌ | 属性类型筛选 |
| keyword | String | ❌ | 属性名称搜索 |
4.2.2 新增属性
POST /api/v1/attribute
请求体:
{
"attrType": 2,
"attrName": "适合年龄",
"dataType": 5,
"options": null,
"regexPattern": null,
"description": "适合年龄区间",
"unit": "月"
}
4.2.3 编辑属性
PUT /api/v1/attribute/{id}
校验逻辑:
检查属性是否已被商品引用
├── 未引用 → 允许完整编辑
└── 已引用 → 仅允许 attr_name/description,选项值已用不可删
4.2.4 删除属性
DELETE /api/v1/attribute/{id}
- is_builtin=1 → 拒绝:"内置属性不可删除"
- 已被商品引用 → 拒绝:"该属性已存在商品档案信息,无法删除"
- 其他 → 逻辑删除(del_flag='1')
4.3 商品档案管理接口
4.3.1 新建商品
POST /api/v1/goods
请求体:
{
"catalogId": "2",
"commonAttrs": {
"code": "SPU001",
"name": "婴童纯棉连体衣",
"barCode": "6901234567890",
"exField2": "ART001",
"brandId": "brand_001",
"kindId": "kind_001",
"typeId": "9",
"producer": "浙江杭州",
"weight": "200",
"salesPrice": 990000,
"status": "1",
"remarks": "",
"imageUrl": "/images/product001.jpg"
},
"customAttrs": [
{
"attributeId": "attr_020",
"attrValues": ["红色", "蓝色"]
},
{
"attributeId": "attr_025",
"attrValueMin": 1,
"attrValueMax": 5
}
]
}
业务逻辑:
- 校验 catalogId 对应的类目 category_type='NORMAL'(拒绝 DEFAULT 类目)
- 根据 catalogId 加载模板,校验必填属性
- 通用属性写入 goods_base 固定列
- 类目属性写入 goods_product_attr_value
- 商品编码全局唯一校验
- 不需要 is_catalog_confirmed 字段,新建商品 catalog_id 直接指向真实类目
4.3.2 编辑商品
PUT /api/v1/goods/{id}
业务逻辑:
- 加载商品所属类目的最新模板
- 模板中新增的属性(已有商品没有的):展示该字段,值为空
- 通用属性更新 goods_base
- 类目属性更新 goods_product_attr_value
4.3.3 变更商品类目(历史商品专用)
POST /api/v1/goods/{id}/change-catalog
请求体:
{
"newCatalogId": "cat_001"
}
业务逻辑:
- 校验
catalog_id = 'cat_default',否则拒绝:"该商品类目已确认,不可变更" - 校验 newCatalogId 对应的类目 category_type='NORMAL'
- 事务内执行:
- UPDATE goods_base SET catalog_id = 新值 WHERE id = 商品ID
- DELETE FROM goods_product_attr_value WHERE product_id = 商品ID(清空旧类目属性值)
- 通用属性保留不变,类目属性为空待用户填写
后端事务伪代码:
BEGIN TRANSACTION;
-- 校验前置条件(FOR UPDATE 防并发)
SELECT catalog_id INTO @cat_id
FROM goods_base WHERE id = @goods_id FOR UPDATE;
IF @cat_id != 'cat_default' THEN
ROLLBACK; RETURN 'NOT_ALLOWED';
END IF;
-- 变更类目
UPDATE goods_base
SET catalog_id = @new_catalog_id
WHERE id = @goods_id;
-- 清空旧类目属性值(通用属性不动)
DELETE FROM goods_product_attr_value
WHERE product_id = @goods_id;
COMMIT;
失败处理:
| 失败场景 | 处理 | 用户提示 |
|---|---|---|
| catalog_id ≠ 'cat_default' | 拒绝 | "该商品类目已确认,不可变更" |
| newCatalogId 无效或为 DEFAULT | 前端拦截 | "请选择有效类目" |
| 并发冲突(两人同时变更) | FOR UPDATE 拒绝后者 | "该商品类目已被其他人员更新" |
| 数据库异常 | 事务回滚,保持原状 | "操作失败,请重试" |
~~4.4 导入任务接口~~
❌ 已删除:本迭代不需要导入任务表和接口。
5. 详细功能逻辑
5.1 类目属性模板继承机制
继承规则
子级属性 = 子级自有属性 ∪ 上级属性(去重)
示例:
一级类目属性:A, B, C
↓ 继承
二级类目属性:A, B, C + D, E, F
↓ 继承
三级类目属性:A, B, C, D, E, F + H, J
关键规则:
- 同一级内属性不能重复,跨级可以重复
- 子级不影响上级
- 上级会影响子级:上级新增属性自动同步到子级
- 上级新增属性同步回填到已有商品(编辑时展示,值为空)
模板变更级联流程
上级类目新增属性 X
│
↓
查找所有子级类目(通过 parent_ids 物化路径)
│
↓
去重检查 → 没有则添加到子级模板
│
↓
查找该类目下所有已有商品
│
↓
在 goods_product_attr_value 中添加空记录
(编辑商品时展示该字段,值为空)
类目 CRUD 权限矩阵
| 类目状态 | 新增属性 | 删除属性 | 修改属性 | 调整排序 |
|---|---|---|---|---|
| DEFAULT 类目 | ❌ | ❌ | ❌ | ❌ |
| 未关联商品 | ✅ | ✅ | ✅ | ✅ |
| 已关联商品 | ✅ | ❌ | ❌ | ❌ |
5.2 属性库 CRUD 权限矩阵
| 属性状态 | 新增 | 编辑 | 删除 | 新增选项值 |
|---|---|---|---|---|
| 未关联商品 | ✅ | ✅ | ✅ | ✅ |
| 已关联商品 | — | 仅 name/desc | ❌ | ✅(已用的不能删) |
5.3 新建商品流程
用户点击"新建商品"
│
↓
【第一步】选择商品类目
│
├── 加载类目树 GET /goods-catalog/tree(过滤 category_type=DEFAULT)
├── 用户选择叶子类目
└── 点击"下一步"(未选时按钮置灰)
│
↓
【第二步】填写商品信息
│
├── 加载模板 GET /goods-catalog/{id}/template-preview
├── 通用属性组 → 固定字段表单(按模板排序/可见性)
├── 类目属性组 → 动态字段表单
├── 必填字段显示 *
└── 提交保存
├── 通用属性 → goods_base
└── 类目属性 → goods_product_attr_value
5.4 编辑商品流程
加载商品数据 + 最新类目模板
│
↓
判断 catalog_id
├── = 'cat_default'(历史商品)→ 展示"变更类目"按钮 + 提示
└── ≠ 'cat_default'(正常商品)→ 正常编辑
│
↓
合并展示
├── goods_base 固定字段值 + 模板控制显示
├── goods_product_attr_value 已有值
└── 模板中新增的属性(值为空) → 展示空字段
│
↓
用户修改 → 提交保存
5.5 页面展示方案
通用属性不存 goods_product_attr_value 表,前端通过类目模板控制展示顺序和可见性。
前端渲染逻辑
1. 获取类目模板 GET /goods-catalog/{id}/template-preview
→ 返回:属性组列表 + 每组下的属性列表(含排序、可见性)
2. 遍历模板属性:
├── 通用属性(attrType=1)→ 从 goods_base 取值渲染
└── 类目属性(attrType=2)→ 从 goods_product_attr_value 取值渲染
3. 前端按 group_sort 分块,按 sort_order 排列
历史商品编辑页展示(catalog_id = 'cat_default')
┌─────────────────────────────────────────────────────────┐
│ 编辑商品 — 婴童纯棉连体衣 │
│ 当前类目:未分类商品(预制默认类目) │
│ ⚠️ 此商品尚未归属具体类目 │
│ [ 🔄 变更类目 ] │
├─────────────────────────────────────────────────────────┤
│ │
│ ▎ 商品基础信息 │
│ ┌──────────────────────────────────────────────┐ │
│ │ 商品编码 * [ SP001 ] 只读 │ │
│ │ 商品名称 * [ 婴童纯棉连体衣 ] │ │
│ │ 商品条码 [ 6901234567890 ] │ │
│ │ ... (通用属性正常展示) │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ ℹ️ 变更类目后可补充类目属性(颜色、尺码等) │
│ │
│ [ 取消 ] [ 保存 ] │
└─────────────────────────────────────────────────────────┘
正常商品编辑页(catalog_id ≠ 'cat_default')
┌─────────────────────────────────────────────────────────┐
│ 编辑商品 — 婴童纯棉连体衣 │
│ 类目:婴童内着家居服 │
├─────────────────────────────────────────────────────────┤
│ │
│ ▎ 基础信息(属性组1) │
│ ┌──────────────────────────────────────────────┐ │
│ │ 商品编码 * [ SP001 ] │ │
│ │ 商品名称 * [ 婴童纯棉连体衣 ] │ │
│ │ ... (通用属性正常展示) │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ ▎ 服饰属性(属性组2) │
│ ┌──────────────────────────────────────────────┐ │
│ │ 颜色 [ + 添加标签 ] │ │
│ │ 尺码 [ + 添加标签 ] │ │
│ │ ... (类目属性为空,可补填) │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ [ 取消 ] [ 保存 ] │
└─────────────────────────────────────────────────────────┘
变更类目后展示
┌─────────────────────────────────────────────────────────┐
│ 编辑商品 — 婴童纯棉连体衣 │
│ 类目:婴童内着家居服 ✓ 已确认 │
├─────────────────────────────────────────────────────────┤
│ │
│ ▎ 基础信息 │
│ ┌──────────────────────────────────────────────┐ │
│ │ (通用属性保留原值) │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ ▎ 服饰属性 │
│ ┌──────────────────────────────────────────────┐ │
│ │ 适用性别 [ ] │ │
│ │ 颜色 [ ] │ │
│ │ 尺码 [ ] │ │
│ │ ... (类目属性为空,待用户补填) │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ ℹ️ 请补充类目属性后保存 │
│ │
│ [ 取消 ] [ 保存 ] │
└─────────────────────────────────────────────────────────┘
数据来源对照
| 问题 | 方案 |
|---|---|
| 通用属性按类目顺序展示 | 前端从模板 API 获取排序,按排序渲染 goods_base 字段 |
| 通用属性是否显示 | 模板中 is_visible 控制 |
| 编辑商品时回填 | 通用属性从 goods_base 回填,类目属性从 goods_product_attr_value 回填 |
| 数据来源 | 前端一次请求拿到模板定义 + 商品数据,本地合并渲染 |
6. 内置通用属性与 goods_base 映射表
| # | 属性名称 | 数据类型 | 必填 | goods_base 字段 | 字段类型 | 备注 |
|---|---|---|---|---|---|---|
| 1 | 商品编码 | 文本 | ✅ | code | VARCHAR(64) | — |
| 2 | 商品名称 | 文本 | ✅ | name | VARCHAR(80) | — |
| 3 | 商品条码 | 文本 | ✅ | bar_code | VARCHAR(64) | 文本类型 |
| 4 | 货号 | 文本 | ❌ | ex_field2 | VARCHAR(64) | 扩展字段 |
| 5 | 品牌 | 单选 | ✅ | brand_id | VARCHAR(64) | 关联 goods_brand |
| 6 | 品类 | 单选 | ✅ | kind_id | VARCHAR(64) | 关联 goods_kind |
| 7 | 商品类型 | 单选 | ✅ | type_id | VARCHAR(64) | 新品/老品/引流品/主推品 |
| 8 | 产地 | 文本 | ❌ | producer | VARCHAR(64) | — |
| 9 | 重量(g) | 数字 | ❌ | weight | VARCHAR(100) | 单位 g |
| 10 | 效期 | 文本 | ❌ | validity_period | INT | 不用管 |
| 11 | 建议零售价 | 数字 | ✅ | sales_price | DECIMAL(18) | ×10000 存储 |
| 12 | 状态 | 开关 | ✅ | status | VARCHAR(16) | 两边状态值不同 |
| 13 | 备注 | 文本 | ❌ | remarks | VARCHAR(256) | — |
| 14 | 图片 | 图片 | ❌ | image_url | VARCHAR(500) | 存 URL/路径 |
注意:goods_base.status 与属性模板中的"状态"是不同的状态体系,各自独立维护。
7. 预制类目初始化数据
7.1 预制默认类目(割接用)
| 字段 | 值 |
|---|---|
| catalog_code | DEFAULT |
| catalog_name | 未分类商品 |
| category_type | DEFAULT |
| level | 1 |
| status | 1(启用) |
| has_product | 1(始终为 true) |
模板配置: - 仅含一个「商品基础信息」属性组 - 14 项通用属性映射到 goods_base 固定列 - 无类目属性
保护规则:不可删除、不可停用、不可修改模板
7.2 一级类目 + 类目属性
一级类目:婴童服饰鞋配
通用属性:14 项(同第 6 节映射表)
类目属性(9 项):
| # | 属性名称 | 数据类型 | 必填 | 说明 |
|---|---|---|---|---|
| 1 | 适用性别 | 单选(下拉) | ❌ | — |
| 2 | 颜色 | 多选(标签) | ❌ | — |
| 3 | 尺码 | 多选(标签) | ❌ | — |
| 4 | 季节 | 单选(下拉) | ❌ | — |
| 5 | 年份 | 单选(下拉) | ❌ | — |
| 6 | 系列 | 文本 | ❌ | — |
| 7 | 面料 | 多选 | ❌ | — |
| 8 | 风格 | 文本 | ❌ | — |
| 9 | 适合年龄 | 数值区间 | ❌ | 单位:岁/月/日 |
7.3 二级类目
| 类目名称 | 继承属性 |
|---|---|
| 婴童内着家居服 | 继承一级全部属性 |
| 婴童外出服饰 | 继承一级全部属性 |
| 婴童鞋靴 | 继承一级全部属性 |
| 婴童配饰 | 继承一级全部属性 |
7.4 类目属性与 goods_base 映射(类目属性部分)
| 类目属性 | goods_base 字段 | 映射方式 |
|---|---|---|
| 颜色 | color_id | 关联字段 |
| 尺码 | size | 直接映射 |
| 面料 | ex_field3 | 扩展字段 |
| 其他 | goods_product_attr_value | 动态存储 |
~~8. 批量导入通用设计~~
❌ 已删除:导入任务表已移除,批量导入使用已有框架能力。
9. 历史商品数据割接
9.1 割接背景
新体系引入类目属性模板后,历史商品数据需要关联类目(catalog_id),以便前端按模板正确展示。由于无法自动推断历史商品应归属哪个类目,采用「预制默认类目 + 人工一次性变更」方案。
9.2 割接范围
| 项目 | 说明 |
|---|---|
| 割接对象 | goods_base 中 catalog_id 为 NULL 的历史商品 |
| 割接动作 | 填充 catalog_id = 预制默认类目 |
| 不涉及 | 通用属性(已在 goods_base 固定列,无需迁移) |
| 不涉及 | 类目属性(历史商品此类为空,不做割接) |
| goods_product_attr_value | 不写入任何数据 |
9.3 DDL 变更
-- 1. t_goods_catalog 新增 category_type 字段
ALTER TABLE t_goods_catalog
ADD COLUMN category_type VARCHAR(20) NOT NULL DEFAULT 'NORMAL'
COMMENT '类目类型: DEFAULT=预制 NORMAL=正常';
-- ⚠️ 不需要新增 is_catalog_confirmed 字段
-- 判断逻辑仅通过 catalog_id: 'cat_default'=允许切换, 其他=不允许
9.4 创建预制默认类目
-- 创建类目
INSERT INTO t_goods_catalog
(catalog_code, catalog_name, parent_id, level, status, category_type, has_product)
VALUES
('DEFAULT', '未分类商品', NULL, 1, '1', 'DEFAULT', 1);
-- 获取预制类目 ID
SET @CAT_DEFAULT = (SELECT id FROM t_goods_catalog
WHERE category_type = 'DEFAULT' AND del_flag = '0' LIMIT 1);
-- 创建模板组(仅商品基础信息)
INSERT INTO goods_catalog_template_group
(catalog_id, group_name, group_desc, group_sort)
VALUES
(@CAT_DEFAULT, '商品基础信息', '通用属性模板', 1);
-- 创建模板明细(14 项通用属性映射)
-- 按第 6 节映射表,逐一插入 goods_catalog_template_item
9.5 割接迁移 SQL
-- 统计待迁移数量
SELECT COUNT(*) FROM goods_base WHERE catalog_id IS NULL AND del_flag = '0';
-- 执行迁移:填充 catalog_id
UPDATE goods_base
SET catalog_id = @CAT_DEFAULT
WHERE catalog_id IS NULL
AND del_flag = '0';
分批执行(大数据量):
-- batch size 5000,每批间隔 2~3 秒
UPDATE goods_base
SET catalog_id = @CAT_DEFAULT
WHERE catalog_id IS NULL AND del_flag = '0'
LIMIT 5000;
-- 重复直到影响行数 = 0
9.6 割接后验证
-- 1. 确认无残留
SELECT COUNT(*) FROM goods_base
WHERE catalog_id IS NULL AND del_flag = '0';
-- 期望: 0
-- 2. 确认历史商品状态
SELECT catalog_id, COUNT(*)
FROM goods_base
WHERE catalog_id = @CAT_DEFAULT AND del_flag = '0'
GROUP BY catalog_id;
-- 期望: catalog_id = 'cat_default' 的行数 = 历史商品总数
-- 3. 确认预制类目已创建
SELECT id, catalog_code, catalog_name, category_type
FROM t_goods_catalog
WHERE category_type = 'DEFAULT';
9.7 一次性变更类目流程
历史商品编辑页
│
↓
检测 catalog_id = 'cat_default'
│
↓
展示"变更类目"按钮 + 提示"建议切换到具体类目"
│
↓
用户点击 → 打开类目选择器(过滤 category_type='NORMAL')
│
↓
用户选择新类目 → 确认
│
↓
后端事务内执行:
├── UPDATE goods_base SET catalog_id=新值
└── DELETE goods_product_attr_value(清空旧类目属性值)
│
↓
前端跳转编辑页
├── 通用属性保留原值
├── 类目属性为空(可先保存后补填)
└── "变更类目"按钮消失(不可逆)
9.8 回滚手册
严格按逆序执行:
-- Step 1:清空历史商品的 catalog_id
UPDATE goods_base
SET catalog_id = NULL
WHERE catalog_id = @CAT_DEFAULT AND del_flag = '0';
-- Step 2:删除预制类目模板明细
DELETE FROM goods_catalog_template_item
WHERE group_id IN (SELECT id FROM goods_catalog_template_group WHERE catalog_id = @CAT_DEFAULT);
-- Step 3:删除预制类目模板组
DELETE FROM goods_catalog_template_group WHERE catalog_id = @CAT_DEFAULT;
-- Step 4:删除预制类目
DELETE FROM t_goods_catalog WHERE category_type = 'DEFAULT';
-- Step 5(可选):回滚 DDL
ALTER TABLE t_goods_catalog DROP COLUMN category_type;
9.9 执行计划
| Step | 内容 | 负责人 | 依赖 |
|---|---|---|---|
| 1 | 执行 DDL(t_goods_catalog 新增 category_type) | 后端/DBA | — |
| 2 | 类目预置数据 + 属性库初始化 | 后端 | — |
| 3 | 创建预制默认类目 + 模板 + 14 项映射 | 后端 | Step 1, 2 |
| 4 | 执行历史商品迁移 SQL | DBA | Step 3 |
| 5 | 前端:变更类目按钮 + catalog_id 判断逻辑 | 前端 | Step 1 |
| 6 | 前端:新建商品过滤 DEFAULT 类目 | 前端 | Step 3 |
| 7 | 测试环境全流程验证 | QA | Step 4~6 |
| 8 | 生产执行 | DBA + 后端 | Step 7 |
附录:变更记录
| 版本 | 日期 | 变更内容 |
|---|---|---|
| V2.0 | 2025-07-11 | 初始详细设计 |
| V2.1 | 2025-07-11 | 同步完整交付文档:t_goods_catalog 增加 parent_ids/has_product、移除 tenant_id;goods_attribute 移除 tenant_id/unit;goods_attribute_option 增加 option_type;变更日志表改为本迭代不建表;补充编辑商品流程和页面展示方案 |
| V3.0 | 2025-07-11 | 新增历史商品数据割接方案:t_goods_catalog 增加 category_type 字段;预制默认类目机制;新增变更类目接口(4.3.3);补充割接流程、前端交互、回滚手册和执行计划(第 9 章) |
| V3.1 | 2026-05-12 | 移除导入任务表(goods_import_task);移除 is_catalog_confirmed 字段,改用 catalog_id 判断类目变更;移除导入任务接口 |
| V3.2 | 2026-05-14 | 确认割接决策:去掉 is_catalog_confirmed,一次性不可逆,不允许真实→真实切换,使用 category_type,无报表模块 |
| V3.3 | 2026-05-14 | 所有新建表统一以 goods_ 开头 |