Files
PartsInquiry/doc/货品删除功能开发文档.md
2025-09-29 21:38:32 +08:00

9.5 KiB
Raw Blame History

货品删除功能开发文档(软删方案)

1. 背景与目标

  • 将“与货品相关”的删除行为统一为软删除,避免历史引用断裂,支持后续恢复与审计。
  • 用户仅保留“拉黑/恢复”,订单维持“作废 void”不做删除。

2. 范围

  • 货品主表:products
  • 关联信息:product_imagesproduct_pricesinventoriesproduct_aliases
  • 相关查询接口:商品搜索、详情、导出(如有)

2.1 父子级联关系(必须遵守)

  • 分类(product_categories → 模板(part_templates → 商品(products
  • 规则:
    • 删除分类 ⇒ 级联软删该分类下所有模板;再级联软删由这些模板创建的所有商品;并同时软删所有 category_id=该分类 的商品(包括未通过模板创建的商品)。
    • 删除模板 ⇒ 仅软删该模板下的商品,不影响同分类其它模板的商品。
    • 订单不可删除仅允许作废void因此采用“软删”是必要前提避免历史订单断裂。
    • 恢复:当前不提供任何恢复入口;如未来开放,恢复不做级联,需逐层独立恢复以避免误恢复。

3. 设计要点

  • 软删标记:使用 products.deleted_at DATETIME NULL(已存在)。被软删即视为“不对外可见”。

  • 恢复:当前不提供恢复入口。若未来开放,语义为将 deleted_at=NULL

  • 查询默认过滤:所有列表/搜索默认附加 deleted_at IS NULL(当前搜索已实现)。

  • 详情访问:若记录被软删,返回 404或通过 includeDeleted=true 显式读取)。

  • 关联表处理:软删商品时不物理删除图片/价格/库存/别名(均按商品引用读取,详情被 404 屏蔽即可)。

  • 模板软删标记统一:为 part_templates 引入 deleted_at DATETIME NULL 以统一软删标记;status 字段保留为启停用,不代表软删。所有查询需同时过滤 deleted_at IS NULL AND status=1(按需)。

  • 字典与作用域:分类与单位属于 shop_id=0 的全局字典。删除分类会影响所有店铺下此分类的模板与商品;此操作需平台管理员权限并要求二次确认。

  • 报表与搜索:默认排除软删记录;不提供“含回收站”开关。

  • 数据保留与清理:支持配置项 SOFT_DELETE_RETENTION_DAYS(默认永久保留,仅清理无引用对象)。

  • 单位删除校验:移除对已废弃 products.unit_id 的校验逻辑。

4. 数据库与索引

现状:products 存在唯一约束 UNIQUE(shop_id, barcode)。软删后可能需要“同店铺、同条码”重新建商品。

  • 目标:唯一约束仅作用于“活动记录”(未软删)。
  • 做法:增加生成列 is_active 并重建唯一索引MySQL 8

DDL上线脚本草案

-- 仅对生产环境执行一次;如已存在请跳过对应步骤
ALTER TABLE products
  ADD COLUMN is_active TINYINT AS (CASE WHEN deleted_at IS NULL THEN 1 ELSE 0 END) STORED,
  ADD INDEX idx_products_deleted_at (deleted_at);

-- 重建唯一索引,使其仅约束未软删记录
DROP INDEX ux_products_shop_barcode ON products; -- 若不存在请忽略
CREATE UNIQUE INDEX ux_products_shop_barcode_live ON products(shop_id, barcode, is_active);

风险与说明

  • “条码为空”不会受唯一约束影响MySQL 对 NULL 不唯一);符合预期。
  • 老数据不受影响;后续删除改为软删即可。
  • 若未来需要“永久删除”,可新增仅限平台运维的强删脚本,先清理关联,再物理删除目标商品。
  • 如未来开放“恢复”,当恢复商品与现存“活动记录”在 (shop_id, barcode) 上冲突时,恢复应返回 409 Conflict 并附带冲突商品信息。

模板表 DDL新增软删标记

ALTER TABLE part_templates
  ADD COLUMN deleted_at DATETIME NULL,
  ADD INDEX idx_part_templates_deleted_at (deleted_at);

5. 接口设计OpenAPI 约定)

说明:按规范,等后端开始开发即补充到 /doc/openapi.yaml 并标注实现状态;本方案不新增任何“恢复”接口。

  1. 软删商品(行为不变,明确语义)
  • Method/Path: DELETE /api/products/{id}
  • 语义:软删,将 deleted_at=NOW()
  • 返回:200 {}
  • 鉴权:需要 X-Shop-Id/X-User-Id 或 Token且仅允许同店铺数据。
  1. 商品详情(行为调整)
  • Method/Path: GET /api/products/{id}
  • 默认:若 deleted_at IS NOT NULL 返回 404
  • 可选:includeDeleted=true 时允许读取已软删详情(仅管理端使用)。
  1. 恢复接口
  • 不同意新增以下恢复接口:PUT /api/admin/dicts/categories/{id}/restorePUT /api/admin/part-templates/{id}/restorePUT /api/products/{id}/restore

6. 后端实现说明

  • Controller 改动(示意)

    • ProductController.delete(id, shopId):保持现有调用,内部执行软删。
    • GET /api/products/{id}:调用 productService.findDetail(id) 前,先判断 deleted_at,若非空且未显式 includeDeleted404
  • Service 改动(核心)

    • 移除/不提供任何恢复相关方法。
    • findDetail(id):若被软删且无 includeDeleted 参数 → 返回空 Optional。
    • 模板表采用 deleted_at 表示软删,status 表示启停用;查询需同时过滤 deleted_at IS NULL 与必要的 status 条件。

6.1 级联软删伪代码

// 分类软删
void deleteCategorySoft(Long categoryId) {
  // 1) 标记分类 deleted_at
  UPDATE product_categories SET deleted_at=NOW() WHERE id=? AND deleted_at IS NULL;
  // 2) 级联模板软删(统一使用 deleted_at
  UPDATE part_templates SET deleted_at=NOW() WHERE category_id=? AND deleted_at IS NULL;
  // 3) 级联商品软删:模板创建的商品 + 直接挂在分类下的商品
  UPDATE products SET deleted_at=NOW() WHERE (
    template_id IN (SELECT id FROM part_templates WHERE category_id=?)
    OR category_id=?
  ) AND deleted_at IS NULL;
}

// 模板软删(不波及其它模板)
void deleteTemplateSoft(Long templateId) {
  // 1) 模板标记为软删
  UPDATE part_templates SET deleted_at=NOW() WHERE id=? AND deleted_at IS NULL;
  // 2) 级联商品软删(仅该模板下)
  UPDATE products SET deleted_at=NOW() WHERE template_id=? AND deleted_at IS NULL;
}

7. 前端改动

  • 列表页:保持不显示软删项(现已过滤)。
  • 详情页:若接口返回 404提示“已被删除或无权限”。
  • 管理端:不提供“回收站/恢复”入口;删除按钮提示:该操作为软删除,对前台不可见,当前无恢复入口。

8. 权限与审计

  • 鉴权:沿用现有用户/店铺头部识别;仅同店铺商品可操作。
  • 权限边界:
    • 普通用户:仅可删除本用户的货品;无权删除模板与分类;无恢复权限。
    • 店铺管理员:仅有审核功能;无删除模板/分类与恢复权限。
    • 平台管理员:可删除货品、模板、分类;删除全局分类需二次确认;无恢复权限。
  • 审计:不记录操作日志(操作者、时间、来源 IP、对象 ID 与名称),以简化开发。

9. 测试用例

  • 删除后搜索不可见;GET /api/products/{id} 返回 404。
  • 条码唯一:软删后允许同店铺同条码新建。
  • (如未来开放恢复)恢复时如与现有活动记录冲突,返回 409 并附带冲突商品信息。

10. 发布与回滚

  • 发布顺序:
    1. 执行数据库 DDL生成列与索引
    2. 上线后端(调整 detail 行为,移除/不提供恢复逻辑)。
    3. 上线前端(不提供回收站/恢复入口)。
  • 回滚:
    • 后端回滚到旧版本DDL 不需要回退(生成列与新索引向前兼容)。

11. FAQ / 风险

  • 问:软删后图片与价格是否清理?
    • 答:不清理,保持数据可恢复;若永久删除再统一清理关联。
  • 问:库存与统计是否包含软删商品?
    • 答:常规统计应排除软删;如需包含,增加显式参数。
  • 问:条码冲突如何处理?
    • 答:按“活动记录”唯一;如未来开放恢复,发现冲突则返回 409并指明冲突商品。
  • 问:字典(分类/单位)是否为全局维度?删除是否影响所有店铺?
    • 答:是,shop_id=0 全局字典;删除全局分类会影响所有店铺下该分类的模板与商品,需平台管理员二次确认。
  • 问:是否保留“强删”入口?
    • 答:保留仅限平台运维的强删入口(默认关闭)。分类/模板强删前需校验无订单关联商品后再执行。
  • 问:为何不做物理删除?
    • 答:订单/流水等历史记录必须可追溯;物理删除会破坏外键与统计。软删能满足“前台不可见、后台可恢复”的业务诉求。

12. 任务拆解(实施)

  • 后端:
    • GET /api/products/{id} 软删返回 404 / 支持 includeDeleted
    • 分类删除级联扩展:同时软删 category_id=该分类 的商品(含未走模板创建)
    • 模板表引入 deleted_at;查询同时过滤 deleted_at IS NULL 与必要的 status
    • 移除“单位删除校验检查 products.unit_id”的逻辑
  • 数据库:
    • products 增加 is_active 与唯一索引(见 DDL
    • part_templates 增加 deleted_at 与索引
  • 前端管理端:
    • 删除按钮文案更新(软删除,对前台不可见,当前无恢复入口)
    • 不提供“回收站/恢复”入口

(本文件为技术方案与实施指引,变更上线后请同步 /doc/openapi.yaml/doc/database_documentation.md