Files
break 22c3e7cc1a
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m42s
实施业务用户组成员管理
2026-09-21 21:20:38 +08:00

5.3 KiB
Raw Blame History

Context

当前成员关系表已保证一个平台账号至多一条未删除归属。SetMembers 实际只迁移请求中的账号,不会替换目标组完整集合;ClearMembers 则按账号全局清空归属,不校验这些账号属于哪个组。缺少成员和候选查询使前端无法展示真实归属,全局清空入口还允许错误页面移除其他组成员。

现有写用例已具备启用平台账号校验、账号行按 ID 升序加锁、唯一约束裁决和逐账号审计。成员查询需要保留禁用及软删除账号关系,而候选查询只面向启用未删除平台用户。

Goals / Non-Goals

Goals:

  • 为每个用户组提供可分页的成员和候选查询,明确账号当前归属与下一步动作。
  • 把增加和移除都约束到目标组,避免整体覆盖和跨组误清空。
  • 保持现有唯一归属、事务回滚、并发锁和审计不变量。
  • 让成员变化继续实时驱动店铺所属组推导。

Non-Goals:

  • 不引入完整成员集合保存、乐观版本号或组层级。
  • 不改变角色、登录、权限、数据范围或店铺负责人本身。
  • 不新增数据库迁移、导入导出或成员审批。
  • 不保留旧成员写入口兼容别名。

Decisions

查询拆分为当前成员与可选候选

新增组内成员 Query成员关系为主表并以 Unscoped 联查账号,使禁用和软删除账号仍可展示。关键字匹配用户名或手机号;账号状态筛选区分启用、禁用和已删除。返回关系创建时间及目标组快照。

新增候选 Query以启用未删除的平台账号为主表LEFT JOIN 当前未删除成员关系和用户组。支持全部、未分组、当前组和其他组筛选,并按当前关系计算 addalready_membermove。目标组停用时仍可查询,但统一返回不可添加标记。

选择两个端点而非在一个端点用模式参数混合:成员查询需要保留删除账号,候选查询必须排除它们,主表与授权语义不同。

增加成员沿用唯一归属替换语义并细分结果

把现有 PUT /:id/members 改为 POST /:id/members。写用例继续要求目标组启用、全部账号为可用平台用户,并按账号 ID 升序锁账号行。锁后读取原归属:无关系新增、其他组更新、当前组不写。

结果按锁后事实统计 added_countmoved_countunchanged_count。只为新增或迁移账号写审计;当前组幂等项不写审计。请求外成员不参与任何删除或更新。

移除成员必须验证目标组归属

新增 DELETE /:id/members。目标组只要求存在未删除,停用组允许移除。事务内按账号 ID 升序锁账号行,再读取成员关系并要求每个账号当前都属于目标组;任何缺失或其他组关系导致整批状态冲突。

校验通过后执行 WHERE business_user_group_id = ? AND account_id IN ? AND deleted_at IS NULL 软删除,并要求影响行数等于请求账号数。校验账号本身时使用保留关系和 Unscoped 账号读取,因此禁用或已删除账号仍可清理。移除审计以账号为主资源、原组为引用资源。

备选继续调用全局 ClearMembers;放弃,因为缺少目标组谓词,无法阻止跨组误清空。

干净移除旧写入口

删除全局 DELETE /business-user-groups/members 路由、Handler 方法及专用 DTO现有 PUT /:id/members 改为 POST不保留 PUT 别名。前端同步迁移到组作用域端点。

这是有意的破坏性收敛:保留旧入口会留下两套不同安全边界,并使调用方继续把操作理解为整体保存或全局清空。

成员数在组查询中批量投影

用户组列表按当页组 ID 一次聚合成员关系数量,详情按目标组计数。计数口径为未删除成员关系,不因账号禁用或软删除排除,和“关系保留”契约一致。成员写入后无需更新组表或缓存。

店铺所属组仍通过当前负责人和成员关系实时推导;本变更不写店铺组 ID 或业务线。

Risks / Trade-offs

  • [破坏性路由切换影响前端] → 同一发布批次同步前端方法与路径OpenAPI 只保留新端点,旧路由明确不可达。
  • [软删除账号缺少可读字段] → 使用账号表 Unscoped 读取历史字段并标记已删除,不恢复账号或关系。
  • [并发迁移与移除同一账号] → 两条用例都按账号 ID 升序锁账号行,锁后重读归属,唯一索引作为最终裁决。
  • [大组成员查询] → 强制分页和关键字长度限制;成员表已有组索引,不预建新索引。
  • [候选手机号属于敏感信息] → 该能力仅超级管理员和平台用户可用,沿用现有平台业务员候选的手机号展示策略;实现时固定响应口径并避免审计复制完整手机号。

Migration Plan

  1. 不执行数据库迁移;发布成员/候选 Query、增量写用例和新路由。
  2. 同步前端PUT 增加改为 POST全局 DELETE 改为携带组 ID 的 DELETE接入成员和候选分页。
  3. 在维护者指定测试环境验证新增、迁移、幂等、停用组移除、删除账号清理、跨组拒绝和并发裁决。
  4. 回滚需同步恢复旧前端调用和旧二进制;成员关系本身仍兼容旧模型,不需要数据回滚。