## Context 当前成员关系表已保证一个平台账号至多一条未删除归属。`SetMembers` 实际只迁移请求中的账号,不会替换目标组完整集合;`ClearMembers` 则按账号全局清空归属,不校验这些账号属于哪个组。缺少成员和候选查询使前端无法展示真实归属,全局清空入口还允许错误页面移除其他组成员。 现有写用例已具备启用平台账号校验、账号行按 ID 升序加锁、唯一约束裁决和逐账号审计。成员查询需要保留禁用及软删除账号关系,而候选查询只面向启用未删除平台用户。 ## Goals / Non-Goals **Goals:** - 为每个用户组提供可分页的成员和候选查询,明确账号当前归属与下一步动作。 - 把增加和移除都约束到目标组,避免整体覆盖和跨组误清空。 - 保持现有唯一归属、事务回滚、并发锁和审计不变量。 - 让成员变化继续实时驱动店铺所属组推导。 **Non-Goals:** - 不引入完整成员集合保存、乐观版本号或组层级。 - 不改变角色、登录、权限、数据范围或店铺负责人本身。 - 不新增数据库迁移、导入导出或成员审批。 - 不保留旧成员写入口兼容别名。 ## Decisions ### 查询拆分为当前成员与可选候选 新增组内成员 Query,成员关系为主表并以 `Unscoped` 联查账号,使禁用和软删除账号仍可展示。关键字匹配用户名或手机号;账号状态筛选区分启用、禁用和已删除。返回关系创建时间及目标组快照。 新增候选 Query,以启用未删除的平台账号为主表,LEFT JOIN 当前未删除成员关系和用户组。支持全部、未分组、当前组和其他组筛选,并按当前关系计算 `add`、`already_member`、`move`。目标组停用时仍可查询,但统一返回不可添加标记。 选择两个端点而非在一个端点用模式参数混合:成员查询需要保留删除账号,候选查询必须排除它们,主表与授权语义不同。 ### 增加成员沿用唯一归属替换语义并细分结果 把现有 `PUT /:id/members` 改为 `POST /:id/members`。写用例继续要求目标组启用、全部账号为可用平台用户,并按账号 ID 升序锁账号行。锁后读取原归属:无关系新增、其他组更新、当前组不写。 结果按锁后事实统计 `added_count`、`moved_count` 和 `unchanged_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. 回滚需同步恢复旧前端调用和旧二进制;成员关系本身仍兼容旧模型,不需要数据回滚。