Files
junhong_cmp_fiber/openspec/specs/user-organization/spec.md
huang b1195c16df feat(account): 实现平台账号管理功能
- 新增平台账号列表查询接口(自动筛选超级管理员和平台用户)
- 新增密码修改和状态切换专用接口
- 增强角色分配功能,支持空数组清空所有角色
- 新增超级管理员保护机制,禁止分配角色
- 新增完整的集成测试和OpenSpec规范文档
2026-01-14 17:00:30 +08:00

270 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# user-organization Specification
## Purpose
TBD - created by archiving change add-user-organization-model. Update Purpose after archive.
## Requirements
### Requirement: 店铺模型定义
系统 SHALL 创建店铺表tb_shop用于存储代理商的组织信息包含店铺名称、店铺编号、上级店铺ID、层级、联系人信息、地址信息和状态字段。
#### Scenario: 创建一级代理店铺
- **WHEN** 创建店铺时 parent_id 为 NULL
- **THEN** 系统创建该店铺并设置 level = 1
#### Scenario: 创建下级代理店铺
- **WHEN** 创建店铺时指定 parent_id 为已存在店铺的 ID
- **THEN** 系统创建该店铺并设置 level = 上级店铺的 level + 1
#### Scenario: 店铺层级限制
- **WHEN** 创建店铺时计算出的 level 超过 7
- **THEN** 系统拒绝创建并返回错误"店铺层级不能超过7级"
#### Scenario: 店铺编号唯一性
- **WHEN** 创建店铺时指定的 shop_code 已存在
- **THEN** 系统拒绝创建并返回错误"店铺编号已存在"
---
### Requirement: 企业模型定义
系统 SHALL 创建企业表tb_enterprise用于存储企业客户的组织信息包含企业名称、企业编号、归属店铺ID、法人代表、联系人信息、营业执照号、地址信息和状态字段。
#### Scenario: 创建平台直属企业
- **WHEN** 创建企业时 owner_shop_id 为 NULL
- **THEN** 系统创建该企业,归属于平台
#### Scenario: 创建代理商下属企业
- **WHEN** 创建企业时指定 owner_shop_id 为已存在店铺的 ID
- **THEN** 系统创建该企业,归属于指定店铺
#### Scenario: 企业编号唯一性
- **WHEN** 创建企业时指定的 enterprise_code 已存在
- **THEN** 系统拒绝创建并返回错误"企业编号已存在"
---
### Requirement: 个人客户模型定义
系统 SHALL 创建个人客户表tb_personal_customer用于存储个人客户信息包含手机号、昵称、头像URL、微信OpenID、微信UnionID和状态字段。个人客户不参与RBAC权限体系。
#### Scenario: 创建个人客户
- **WHEN** 用户通过手机号注册
- **THEN** 系统创建个人客户记录phone 字段存储手机号
#### Scenario: 手机号唯一性
- **WHEN** 创建个人客户时手机号已存在
- **THEN** 系统拒绝创建并返回错误"手机号已被注册"
#### Scenario: 绑定微信信息
- **WHEN** 个人客户授权微信登录
- **THEN** 系统更新 wx_open_id 和 wx_union_id 字段
---
### Requirement: 账号模型重构
系统 SHALL 修改账号表tb_account结构支持四种用户类型超级管理员(1)、平台用户(2)、代理账号(3)、企业账号(4)。代理账号必须关联店铺ID企业账号必须关联企业ID。
#### Scenario: 创建超级管理员账号
- **WHEN** 创建账号时 user_type = 1
- **THEN** 系统创建超级管理员账号shop_id 和 enterprise_id 均为 NULL
#### Scenario: 创建平台用户账号
- **WHEN** 创建账号时 user_type = 2
- **THEN** 系统创建平台用户账号shop_id 和 enterprise_id 均为 NULL
#### Scenario: 创建代理账号
- **WHEN** 创建账号时 user_type = 3
- **THEN** 系统必须指定 shop_identerprise_id 为 NULL
#### Scenario: 创建企业账号
- **WHEN** 创建账号时 user_type = 4
- **THEN** 系统必须指定 enterprise_idshop_id 为 NULL
#### Scenario: 代理账号必须关联店铺
- **WHEN** 创建代理账号user_type = 3但未指定 shop_id
- **THEN** 系统拒绝创建并返回错误"代理账号必须关联店铺"
#### Scenario: 企业账号必须关联企业
- **WHEN** 创建企业账号user_type = 4但未指定 enterprise_id
- **THEN** 系统拒绝创建并返回错误"企业账号必须关联企业"
---
### Requirement: 店铺层级递归查询
系统 SHALL 支持递归查询指定店铺的所有下级店铺ID列表包含直接和间接下级并将结果缓存到Redis30分钟过期。当店铺的parent_id变更或店铺被删除时系统必须清除相关缓存。
#### Scenario: 查询下级店铺ID列表
- **WHEN** 调用 GetSubordinateShopIDs(shopID) 方法
- **THEN** 系统返回该店铺的所有下级店铺ID列表递归包含所有层级
#### Scenario: 下级店铺缓存命中
- **WHEN** Redis 中存在店铺的下级ID缓存
- **THEN** 系统直接返回缓存数据,不查询数据库
#### Scenario: 下级店铺缓存未命中
- **WHEN** Redis 中不存在店铺的下级ID缓存
- **THEN** 系统查询数据库将结果缓存到Redis过期时间30分钟然后返回结果
#### Scenario: 店铺删除时清除缓存
- **WHEN** 店铺被软删除
- **THEN** 系统清除该店铺及其所有上级店铺的下级ID缓存
---
### Requirement: 用户类型常量定义
系统 SHALL 在 pkg/constants/ 中定义用户类型常量,禁止在代码中硬编码用户类型数值。
#### Scenario: 使用用户类型常量
- **WHEN** 代码中需要判断用户类型
- **THEN** 必须使用 constants.UserTypeSuperAdmin、constants.UserTypePlatform、constants.UserTypeAgent、constants.UserTypeEnterprise 常量
#### Scenario: 禁止硬编码用户类型
- **WHEN** 代码中直接使用数字 1、2、3、4 表示用户类型
- **THEN** 代码审查不通过,必须改为使用常量
---
### Requirement: 店铺账号数据权限
系统 SHALL 基于店铺层级实现数据权限过滤同一店铺的所有账号能看到店铺的所有数据上级店铺能看到下级店铺的数据。平台用户user_type = 1 或 2跳过数据权限过滤。
#### Scenario: 平台用户查询数据
- **WHEN** 平台用户user_type = 1 或 2查询业务数据
- **THEN** 系统返回所有数据,不应用店铺过滤条件
#### Scenario: 代理账号查询数据
- **WHEN** 代理账号user_type = 3shop_id = X查询业务数据
- **THEN** 系统自动添加 WHERE 条件shop_id IN (X, 及X的所有下级店铺ID)
#### Scenario: 企业账号查询数据
- **WHEN** 企业账号user_type = 4enterprise_id = Y查询业务数据
- **THEN** 系统自动添加 WHERE 条件enterprise_id = Y
---
### Requirement: 平台账号列表查询
系统 SHALL 提供专门的平台账号列表查询接口自动筛选平台用户user_type=2和超级管理员user_type=1支持按用户名、手机号、状态筛选并返回分页结果。
#### Scenario: 查询平台账号列表
- **WHEN** 调用平台账号列表接口 `GET /api/admin/platform-accounts`
- **THEN** 系统自动筛选 `user_type IN (1, 2)` 的账号并返回列表
#### Scenario: 按用户名筛选
- **WHEN** 调用平台账号列表接口并传递 `username=admin`
- **THEN** 系统返回用户名包含 "admin" 的平台账号(模糊查询)
#### Scenario: 按手机号筛选
- **WHEN** 调用平台账号列表接口并传递 `phone=138`
- **THEN** 系统返回手机号包含 "138" 的平台账号(模糊查询)
#### Scenario: 按状态筛选
- **WHEN** 调用平台账号列表接口并传递 `status=1`
- **THEN** 系统返回状态为启用status=1的平台账号
#### Scenario: 分页查询
- **WHEN** 调用平台账号列表接口并传递 `page=2&page_size=10`
- **THEN** 系统返回第2页数据每页10条记录同时返回总记录数
#### Scenario: 超级管理员包含在列表中
- **WHEN** 调用平台账号列表接口
- **THEN** 返回结果包含所有超级管理员账号user_type=1
#### Scenario: 列表返回字段
- **WHEN** 平台账号列表接口返回数据
- **THEN** 每条记录包含id, username, phone, user_type, status, created_at, updated_at
---
### Requirement: 平台账号密码修改
系统 SHALL 提供专门的密码修改接口,允许管理员重置平台账号密码,无需验证旧密码。新密码必须经过 bcrypt 哈希后存储,并自动设置 updater 字段。
#### Scenario: 修改平台账号密码
- **WHEN** 调用密码修改接口 `PUT /api/admin/platform-accounts/:id/password` 并传递 `new_password`
- **THEN** 系统验证账号存在,哈希新密码,更新数据库,设置 updater 字段
#### Scenario: 密码格式验证
- **WHEN** 调用密码修改接口传递的密码长度小于 8 位或大于 32 位
- **THEN** 系统拒绝修改并返回错误 CodeInvalidParam "密码长度必须在 8-32 位之间"
#### Scenario: 账号不存在
- **WHEN** 调用密码修改接口传递的账号ID不存在
- **THEN** 系统返回错误 CodeAccountNotFound "账号不存在"
#### Scenario: 密码哈希
- **WHEN** 密码修改成功
- **THEN** 系统使用 bcrypt.GenerateFromPassword 哈希密码,并将哈希值存储到 password 字段
#### Scenario: 修改超级管理员密码
- **WHEN** 调用密码修改接口修改超级管理员user_type=1的密码
- **THEN** 系统允许修改(超级管理员密码可以被重置)
---
### Requirement: 平台账号状态切换
系统 SHALL 提供专门的状态切换接口,允许启用或禁用平台账号。状态值必须为 0禁用或 1启用操作自动设置 updater 字段。
#### Scenario: 启用平台账号
- **WHEN** 调用状态切换接口 `PUT /api/admin/platform-accounts/:id/status` 并传递 `status=1`
- **THEN** 系统将账号状态设置为启用status=1设置 updater 字段
#### Scenario: 禁用平台账号
- **WHEN** 调用状态切换接口并传递 `status=0`
- **THEN** 系统将账号状态设置为禁用status=0设置 updater 字段
#### Scenario: 无效状态值
- **WHEN** 调用状态切换接口传递的 status 不是 0 或 1
- **THEN** 系统拒绝修改并返回错误 CodeInvalidParam "状态值必须为 0 或 1"
#### Scenario: 账号不存在
- **WHEN** 调用状态切换接口传递的账号ID不存在
- **THEN** 系统返回错误 CodeAccountNotFound "账号不存在"
#### Scenario: 禁用超级管理员
- **WHEN** 调用状态切换接口禁用超级管理员user_type=1
- **THEN** 系统允许禁用(超级管理员可以被禁用)
#### Scenario: 已禁用账号无法登录
- **WHEN** 账号状态为禁用status=0时尝试登录
- **THEN** 认证系统拒绝登录并返回错误 CodeAccountDisabled "账号已被禁用"
---
### Requirement: 平台账号 CRUD 复用
系统 SHALL 为平台账号管理接口复用现有的账号 CRUD 功能,包括新增、查询详情、编辑、删除和角色管理,确保代码复用和功能一致性。
#### Scenario: 新增平台账号
- **WHEN** 调用 `POST /api/admin/platform-accounts` 创建账号
- **THEN** 系统复用现有 AccountHandler.Create 方法,限制 user_type 必须为 1 或 2
#### Scenario: 查询平台账号详情
- **WHEN** 调用 `GET /api/admin/platform-accounts/:id` 查询账号
- **THEN** 系统复用现有 AccountHandler.Get 方法,返回账号完整信息
#### Scenario: 编辑平台账号
- **WHEN** 调用 `PUT /api/admin/platform-accounts/:id` 更新账号
- **THEN** 系统复用现有 AccountHandler.Update 方法,支持部分字段更新
#### Scenario: 删除平台账号
- **WHEN** 调用 `DELETE /api/admin/platform-accounts/:id` 删除账号
- **THEN** 系统复用现有 AccountHandler.Delete 方法,执行软删除
#### Scenario: 分配角色
- **WHEN** 调用 `POST /api/admin/platform-accounts/:id/roles` 分配角色
- **THEN** 系统复用现有 AccountHandler.AssignRoles 方法,支持空数组和超级管理员保护
#### Scenario: 查询账号角色
- **WHEN** 调用 `GET /api/admin/platform-accounts/:id/roles` 查询角色
- **THEN** 系统复用现有 AccountHandler.GetRoles 方法,返回角色列表
#### Scenario: 移除单个角色
- **WHEN** 调用 `DELETE /api/admin/platform-accounts/:id/roles/:role_id` 移除角色
- **THEN** 系统复用现有 AccountHandler.RemoveRole 方法,删除角色关联