- 新增平台账号列表查询接口(自动筛选超级管理员和平台用户) - 新增密码修改和状态切换专用接口 - 增强角色分配功能,支持空数组清空所有角色 - 新增超级管理员保护机制,禁止分配角色 - 新增完整的集成测试和OpenSpec规范文档
270 lines
12 KiB
Markdown
270 lines
12 KiB
Markdown
# 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_id,enterprise_id 为 NULL
|
||
|
||
#### Scenario: 创建企业账号
|
||
- **WHEN** 创建账号时 user_type = 4
|
||
- **THEN** 系统必须指定 enterprise_id,shop_id 为 NULL
|
||
|
||
#### Scenario: 代理账号必须关联店铺
|
||
- **WHEN** 创建代理账号(user_type = 3)但未指定 shop_id
|
||
- **THEN** 系统拒绝创建并返回错误"代理账号必须关联店铺"
|
||
|
||
#### Scenario: 企业账号必须关联企业
|
||
- **WHEN** 创建企业账号(user_type = 4)但未指定 enterprise_id
|
||
- **THEN** 系统拒绝创建并返回错误"企业账号必须关联企业"
|
||
|
||
---
|
||
|
||
### Requirement: 店铺层级递归查询
|
||
|
||
系统 SHALL 支持递归查询指定店铺的所有下级店铺ID列表(包含直接和间接下级),并将结果缓存到Redis(30分钟过期)。当店铺的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 = 3,shop_id = X)查询业务数据
|
||
- **THEN** 系统自动添加 WHERE 条件:shop_id IN (X, 及X的所有下级店铺ID)
|
||
|
||
#### Scenario: 企业账号查询数据
|
||
- **WHEN** 企业账号(user_type = 4,enterprise_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 方法,删除角色关联
|
||
|