From e690b52007c4152769bcb85944115700dee1c5f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=84=A6=E9=BE=99=E8=A8=80?= Date: Tue, 22 Sep 2026 12:05:52 +0800 Subject: [PATCH] =?UTF-8?q?=E8=AE=BE=E8=AE=A1=EF=BC=9A=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E6=95=B0=E6=8D=AE=E6=89=B9=E6=AC=A1=E6=B8=85?= =?UTF-8?q?=E7=90=86=E6=96=B9=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...26-09-22-test-data-batch-cleanup-design.md | 298 ++++++++++++++++++ 1 file changed, 298 insertions(+) create mode 100644 docs/superpowers/specs/2026-09-22-test-data-batch-cleanup-design.md diff --git a/docs/superpowers/specs/2026-09-22-test-data-batch-cleanup-design.md b/docs/superpowers/specs/2026-09-22-test-data-batch-cleanup-design.md new file mode 100644 index 0000000..d88a731 --- /dev/null +++ b/docs/superpowers/specs/2026-09-22-test-data-batch-cleanup-design.md @@ -0,0 +1,298 @@ +# 测试数据批次隔离与级联清理设计 + +## 1. 背景 + +当前菌种、客户、客户现场、产线、仓库和托盘等主数据在被业务单据引用后不允许删除。该约束能保护正式业务的外键完整性、库存状态、报表口径和托盘追溯链,但也导致开发、联调和验收期间产生的测试数据无法安全清理。 + +本设计引入“测试数据批次”,将正式数据与测试数据从创建、查询、业务操作到报表统计全程隔离。超级管理员可在预览完整影响范围后整批清理测试数据;正式数据在任何情况下都不能通过该能力物理删除。 + +## 2. 目标与非目标 + +### 2.1 目标 + +- 以测试批次为边界隔离正式数据和测试数据。 +- 支持授权的普通用户在指定测试批次内完成入库、装填、发货、使用和退回流程。 +- 支持超级管理员将升级前已有的旧测试数据完整纳入测试批次。 +- 支持关闭批次后预览影响范围,并在单个数据库事务中整批清理。 +- 保留批次元数据和操作审计,但不保留已清理的测试业务明细。 +- 防止前端绕过、权限误配、并发请求或部分失败导致正式数据被删或测试链路残缺。 + +### 2.2 非目标 + +- 不允许按单条主数据或单据强制级联删除。 +- 不允许将正在进行的批次直接清理。 +- 不用测试清理代替正式业务的停用、作废、冲销或审计流程。 +- 不根据名称、编码、创建人或时间自动猜测旧数据是否为测试数据。 +- 不在本次实施中直接运行生产数据库迁移或清理。 + +## 3. 角色与权限边界 + +| 能力 | 超级管理员 | 已授权测试用户 | 其他用户 | +| --- | --- | --- | --- | +| 查看测试批次管理 | 允许 | 不允许 | 不允许 | +| 创建、关闭、重新开启批次 | 允许 | 不允许 | 不允许 | +| 授权批次测试用户 | 允许 | 不允许 | 不允许 | +| 创建批次内测试主数据 | 允许 | 不允许 | 不允许 | +| 进入已授权批次执行业务 | 允许 | 允许 | 不允许 | +| 将旧数据纳入批次 | 允许 | 不允许 | 不允许 | +| 预览并整批清理 | 允许 | 不允许 | 不允许 | +| 物理删除正式数据 | 不允许 | 不允许 | 不允许 | + +超级管理员以后端 `current_user.user.admin` 为最终判断,不仅依赖前端角色或菜单权限。测试批次接口可同时使用独立权限字符控制菜单展示,但权限字符被误分配给普通角色时,后端仍必须返回 403。 + +## 4. 数据模型 + +### 4.1 测试批次 + +新增 `biz_test_batch`,至少包含: + +- `batch_id`:主键。 +- `batch_code`:系统生成的唯一编码。 +- `batch_name`:用于界面识别与二次确认。 +- `status`:`ACTIVE`、`CLOSED`、`PURGED`。 +- `create_by`、`create_time`、`update_by`、`update_time`。 +- `closed_by`、`closed_at`、`purged_by`、`purged_at`。 +- `purge_summary`:保留按数据类型汇总的清理数量,不保留敏感业务内容。 +- `remark`:批次用途说明。 + +新增 `biz_test_batch_user`,保存批次与参与测试用户的关联。该关联只是批次的外层访问权;现有用户-仓库、用户-现场数据范围仍然生效。 + +### 4.2 业务数据标记 + +以下表新增可空 `test_batch_id` 和索引,并外键引用 `biz_test_batch.batch_id`: + +- 主数据:`biz_strain`、`biz_customer`、`biz_customer_site`、`biz_production_line`、`biz_warehouse`、`biz_pallet`。 +- 入库:`biz_inbound_order`、`biz_inbound_item`。 +- 装填:`biz_filling_record`。 +- 发货:`biz_shipment_order`、`biz_shipment_item`。 +- 使用:`biz_usage_record`。 +- 退回:`biz_return_order`、`biz_return_item`。 +- 库存与追溯:`biz_pallet_inventory`、`biz_pallet_event`。 + +`test_batch_id IS NULL` 表示正式数据;非空表示所属测试批次。该字段不允许普通业务请求直接写入,由后端从已验证的当前模式或关联记录中派生。 + +`biz_user_warehouse` 和 `biz_user_site` 不重复保存 `test_batch_id`,其数据归属由所关联的仓库或现场派生。批次清理时,必须先删除指向该批次仓库和现场的用户关联。 + +### 4.3 一致性约束 + +- 正式数据只能引用正式数据。 +- 测试数据只能引用同一批次的测试数据。 +- 不同测试批次之间不能互相引用。 +- 批次标记一旦创建不能通过通用编辑接口修改。 +- 只有“纳入测试批次”专用流程可将旧的无标记数据一次性改为测试数据。 +- 关联对象的批次一致性由服务层在写入前校验,数据库外键作为存在性和删除顺序的最后保护。 +- 现有编码、单号和二维码唯一约束继续全局生效;测试数据使用独立编码,清理后可重新使用。 + +## 5. 数据模式与查询隔离 + +### 5.1 模式选择 + +Web 管理端和 PDA 操作端都需支持: + +- 正式模式:不传测试批次上下文,后端只允许 `test_batch_id IS NULL` 的数据。 +- 测试模式:携带当前 `batch_id`。已授权用户只能进入 `ACTIVE` 批次;超级管理员可在批次管理页以只读方式检查 `CLOSED` 批次。任何写入都要求批次为 `ACTIVE`。 + +前端可通过统一请求头传递当前批次,但该值只是请求上下文,不是信任根。后端必须每次验证用户授权、批次状态和数据归属。 + +### 5.2 隔离范围 + +批次条件必须应用于: + +- 六类基础资料的列表、详情和选项。编码、单号和二维码的唯一性校验仍按全局数据执行,与现有数据库唯一约束保持一致。 +- 入库、装填、发货、使用和退回的列表、详情、扫码、创建、修改和确认。 +- 仓库、现场、产线、菌种和托盘等业务选项接口。 +- 托盘库存、装填、发货、消耗、退回和追溯六类报表。 +- 首页仓储运营指标、待处理数量、趋势图和状态分布。 + +任何缺失批次条件的查询都可能造成测试数据污染正式报表,实施时必须对所有业务 DAO 做完整盘点。 + +## 6. 批次生命周期 + +```text +ACTIVE --关闭--> CLOSED --重新开启--> ACTIVE + | + +--预览并确认清理--> PURGED +``` + +- `ACTIVE`:允许超级管理员创建测试主数据,允许已授权用户执行业务。 +- `CLOSED`:所有测试数据只读;可查看、预览清理范围、重新开启或清理。 +- `PURGED`:批次业务数据已删除;仅保留批次元数据和审计汇总,不能重新开启。 + +关闭、重新开启和清理都需锁定批次记录。每个测试写操作在业务数据加锁前必须重新校验批次仍为 `ACTIVE`,保证“关闭成功后无新写入”。 + +## 7. 新测试数据创建与业务继承 + +1. 超级管理员创建批次,分配参与测试的用户。 +2. 超级管理员切换到该批次,创建菌种、客户、现场、产线、仓库和托盘。 +3. 后端根据当前批次自动写入 `test_batch_id`,忽略或拒绝请求体中伪造的批次字段。 +4. 已授权用户进入该批次,使用批次内的仓库、现场、产线、菌种和托盘执行业务。 +5. 入库单、装填记录、发货单、使用记录、退回单、库存和事件由后端自动继承同一 `test_batch_id`。 +6. 任一关联数据的批次不一致时,整个业务操作失败并回滚。 + +## 8. 旧数据纳入测试批次 + +### 8.1 入口 + +超级管理员在正式模式的基础资料和业务管理页选择旧数据后,可执行“纳入测试批次”。报表页不提供该操作,因为报表行不是独立业务实体;需从其来源主数据或单据发起。 + +### 8.2 关联闭包 + +后端必须沿实际外键与业务关联双向计算连通闭包,直到没有新关联记录。闭包至少覆盖: + +- 菌种与装填记录。 +- 客户、现场与产线层级。 +- 仓库、入库、装填、发货和退回。 +- 托盘、单据明细、库存、使用记录和追溯事件。 +- 与装填周期相关的发货、使用和退回记录。 + +由于旧数据没有可靠的测试标记,系统无法自动识别闭包中哪些数据被用户视为“正式”。预览必须按表展示数量和关键编号;如果闭包包含需保留的正式数据,管理员必须取消,系统不允许只标记半条关联链。 + +### 8.3 确认与写入 + +- 目标批次必须为 `ACTIVE`。 +- 闭包中所有数据必须尚未归属任何测试批次。 +- 管理员需输入目标批次名称二次确认。 +- 服务端使用不透明预览校验标识;确认时重新计算闭包,范围变化则拒绝旧确认。 +- 事务内锁定批次和闭包中的根记录,然后一次性写入所有 `test_batch_id`。 +- 任何一步失败都回滚,不得出现部分数据已纳入、部分数据仍为正式的状态。 + +## 9. 整批清理 + +### 9.1 前置条件 + +- 操作人必须是超级管理员。 +- 批次必须为 `CLOSED`。 +- 清理预览需展示每类主数据、单据、明细、库存和事件的数量。 +- 管理员必须输入完整批次名称并通过最终确认。 +- 确认请求必须携带服务端生成的一次性预览标识。 + +### 9.2 事务与删除顺序 + +清理服务在单个数据库事务内锁定批次,重新计算影响范围并校验预览标识。删除顺序必须由实际外键图生成并以回归测试固化,原则为: + +1. 先删除 `biz_pallet_inventory`,解除其对托盘事件和活动装填记录的外键引用。 +2. 删除 `biz_usage_record`、`biz_return_item`、`biz_shipment_item` 和 `biz_inbound_item`。 +3. 删除 `biz_return_order`、`biz_shipment_order` 和 `biz_inbound_order`。 +4. 删除 `biz_pallet_event`,再删除 `biz_filling_record`。 +5. 删除指向测试仓库、现场的 `biz_user_warehouse` 和 `biz_user_site` 关联。 +6. 先删除产线,再删除现场和客户;随后删除仓库、菌种和托盘等其他主数据。 +7. 删除批次用户授权,更新批次为 `PURGED`,写入清理汇总。 + +实施时以 SQLAlchemy 模型和 MySQL/PostgreSQL 外键的实际编译结果为准,不依赖上述文字顺序代替自动化测试。 + +不对业务外键配置 `ON DELETE CASCADE`。如果任何外键、并发状态或数据库错误导致删除失败,整个事务回滚,批次保持 `CLOSED`。 + +### 9.3 幂等与审计 + +- 同一批次的并发清理只有一个可以执行,其他请求等待后读取最新状态。 +- 对 `PURGED` 批次重复请求返回稳定的“已清理”结果,不重复执行删除。 +- 批次记录保留清理人、清理时间和分类数量。 +- 系统操作日志保留,作为审计证据;日志不记录密码、Token、Cookie 或大量业务明细。 + +## 10. 界面与交互 + +### 10.1 测试数据批次管理 + +在“系统管理”下新增“测试数据批次”页面,仅超级管理员可见。页面支持: + +- 查看批次编码、名称、状态、创建人、参与用户和时间。 +- 创建批次、编辑备注、授权或移除测试用户。 +- 关闭或重新开启未清理批次。 +- 查看数据汇总、清理预览和最终清理确认。 +- 查看已清理批次的审计汇总。 + +### 10.2 模式提示 + +- Web 顶部和 PDA 工作台提供“正式数据 / 已授权测试批次”切换。 +- 测试模式始终展示高可见的批次名称和“测试数据”标识,不得只在隐蔽的下拉框中表示。 +- 切换模式后刷新当前列表、选项、首页指标和报表,并清空未提交的跨模式表单状态。 +- 批次被关闭、授权被移除或会话失效时,普通测试用户退回正式模式并得到明确提示;超级管理员仍可从批次管理页查看关闭批次的只读汇总和清理预览。 + +### 10.3 现有删除入口 + +- 基础资料、业务管理和报表页不再提供物理删除入口。 +- 正式主数据通过停用保留历史引用;正式业务单据按现有业务状态处理,不使用测试批次能力物理删除。 +- 旧的删除 API 保留路由兼容,但统一拒绝物理删除并提示“请通过测试数据批次清理”。 +- “纳入测试批次”只在正式模式下向超级管理员展示。 + +## 11. 后端组件与接口边界 + +建议新增独立的批次控制器、服务、DAO 和 VO,不将级联清理逻辑分散到六个主数据服务中。职责划分如下: + +- 批次管理:创建、列表、详情、用户授权、关闭和重新开启。 +- 批次上下文:解析请求模式,校验用户与批次,为查询和写入提供受信任的 `batch_id | None`。 +- 批次一致性校验:根据业务输入校验所有关联数据处于同一模式和批次。 +- 旧数据纳入:计算关联闭包、生成预览、校验确认并原子写入批次标记。 +- 整批清理:统计影响数据、生成预览、校验确认并原子删除。 + +预览与执行必须分为不同接口。执行接口不接受前端传入的“要删哪些表或 ID”,只接受批次 ID、批次名称确认和一次性预览标识;实际范围始终由后端重算。 + +## 12. 错误处理 + +需向用户明确区分以下失败: + +- 非超级管理员访问管理或清理接口。 +- 用户未被授权访问所选测试批次。 +- 批次已关闭、已清理或在操作期间状态发生变化。 +- 请求将正式数据与测试数据或不同批次数据混用。 +- 旧数据关联闭包包含已属于其他批次的数据。 +- 预览后数据范围发生变化,一次性校验标识失效。 +- 批次名称确认不匹配。 +- 外键、并发或数据库错误触发整体回滚。 + +前端不将未知错误表示为成功,也不在失败后乐观移除列表记录。所有成功状态均以服务端提交后重新查询的结果为准。 + +## 13. 数据库迁移与发布 + +- 同步更新 MySQL 和 PostgreSQL 全量建库脚本。 +- 为已有环境提供 MySQL 和 PostgreSQL 增量补丁。 +- 先创建批次与授权表,再以可空字段方式增加 `test_batch_id`、索引和外键。 +- 既有记录的 `test_batch_id` 保持 `NULL`,迁移不自动更新业务数据。 +- 发布前必须完成数据库备份和恢复演练;应用迁移和首次旧数据纳入属于独立高风险操作,需单独确认后执行。 +- 先发布后端与前端代码,确认正式模式查询不回归,再创建新测试批次或纳入旧数据。 + +## 14. 测试与验证 + +### 14.1 后端 + +- 批次生命周期和不合法状态转换。 +- 超级管理员硬校验,包括普通用户持有误分配权限字符的情况。 +- 批次用户授权与现有仓库/现场数据范围的叠加效果。 +- 正式、同批次和跨批次关联校验。 +- 创建主数据和五类业务时的批次自动继承。 +- 主数据、业务列表、选项、扫码、首页和六类报表的模式隔离。 +- 旧数据闭包的完整性、循环去重、跨批次拒绝和事务回滚。 +- 关闭批次后所有写入被拒绝,重新开启后恢复。 +- 清理预览计数、预览过期、确认文本、外键删除顺序、失败回滚和重复清理幂等性。 +- MySQL 和 PostgreSQL 的 SQL 编译与迁移契约。 + +### 14.2 Web 与 PDA + +- 超级管理员、已授权测试用户和未授权用户的菜单与按钮可见性。 +- 正式/测试模式切换、批次标识、请求上下文和授权失效回退。 +- 旧数据纳入预览、批次名称二次确认和预览过期提示。 +- 批次关闭后的只读界面和清理确认。 +- 现有物理删除按钮移除,直接调用旧删除 API 时的稳定拒绝提示。 +- Web 生产构建、PDA H5 构建、最相关契约测试和后端业务单元测试。 + +## 15. 验收标准 + +1. 正式模式的页面、选项、首页和报表不展示任何测试数据。 +2. 测试模式只展示当前批次数据,不显示正式数据或其他批次数据。 +3. 授权的普通用户可完成测试业务链,但无法管理、纳入、关闭或清理批次。 +4. 正式与测试数据、不同测试批次之间不能混用。 +5. 超级管理员可预览并完整纳入旧测试数据,范围变化时旧确认失效。 +6. `ACTIVE` 批次不能清理;`CLOSED` 批次不能新增或修改业务数据。 +7. 整批清理成功后,所有相关主数据、单据、明细、库存和追溯记录消失,报表不再返回该批次数据。 +8. 清理任一步失败时不删除任何数据,批次仍为 `CLOSED`。 +9. 超级管理员也无法通过批次清理或旧删除 API 物理删除正式数据。 +10. 已清理批次保留操作人、时间和分类数量,且日志不包含凭据或敏感信息。 + +## 16. 已知风险与控制 + +- **旧数据闭包可能过大**:旧测试数据如果复用了正式仓库、现场或托盘,关联闭包可能扩展到大量正式数据。系统必须展示完整预览并允许取消,不提供忽略关联的强制选项。 +- **大批量事务时间较长**:在目标数据库上通过压力测试确定在线清理上限,并将上限写入部署配置和运维文档。在线接口必须阻止超过上限的批次,且不得通过分段提交规避原子性;超大批次由 DBA 在备份后使用受审计脚本处理。数值上限依赖目标环境,本设计不在缺少实测数据时猜测固定值。 +- **请求上下文被伪造**:后端每次验证授权、批次状态和数据归属,不相信前端显示或请求头本身。 +- **漏加查询条件导致统计污染**:使用统一批次上下文和 DAO 查询辅助方法,并用契约测试盘点全部业务查询。 +- **仅前端隐藏不能提供安全性**:批次管理、纳入和清理均在后端强制超级管理员检查。 +- **迁移与实际数据存在差异**:应用前在目标数据库执行只读外键和表结构盘点,与代码模型和迁移脚本三方对照,不假设生产结构与仓库 SQL 完全一致。