refactor(account): 统一账号管理API、完善权限检查和操作审计
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m17s

- 合并 customer_account 和 shop_account 路由到统一的 account 接口
- 新增统一认证接口 (auth handler)
- 实现越权防护中间件和权限检查工具函数
- 新增操作审计日志模型和服务
- 更新数据库迁移 (版本 39: account_operation_log 表)
- 补充集成测试覆盖权限检查和审计日志场景
This commit is contained in:
2026-02-02 17:23:20 +08:00
parent 5851cc6403
commit 80f560df33
58 changed files with 10743 additions and 4915 deletions

View File

@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-02-02

View File

@@ -0,0 +1,494 @@
# 统一账号管理接口设计
## Context
### 现状问题
当前系统存在三套独立的账号管理体系:
1. **AccountService** + **AccountHandler**:管理"通用账号"和"平台账号",功能重复
2. **ShopAccountService** + **ShopAccountHandler**:管理代理账号,功能不全(缺少角色管理)
3. **CustomerAccountService** + **CustomerAccountHandler**管理企业账号命名错误customer vs enterprise
### 安全现状
**Critical 漏洞**:所有 Service 的 Create 方法缺少目标资源归属权限检查。攻击场景:
```go
// 代理用户 Ashop_id=100发起请求
POST /api/admin/shop-accounts
{ "shop_id": 200, "username": "hacker", ... }
// 当前实现:只检查店铺存在,直接创建成功 ❌
```
### 已有防护机制
- **GORM Callback 自动过滤**`pkg/gorm/callback.go`):所有查询自动应用数据权限过滤
- 代理用户:`WHERE shop_id IN (自己店铺+下级店铺)`
- 企业用户:`WHERE enterprise_id = 当前企业ID`
- 平台/超管:跳过过滤
- **递归查询下级店铺**`ShopStore.GetSubordinateShopIDs`支持7级层级Redis 缓存30分钟
### 约束条件
- 必须遵循 Handler → Service → Store → Model 分层
- 禁止外键约束,表关联通过 ID 字段手动维护
- 所有业务逻辑在 Service 层Handler 只做参数验证和路由
- 错误处理使用 `pkg/errors` 统一错误码
- 审计日志异步写入,不阻塞主流程
## Goals / Non-Goals
### Goals
1. **统一架构**:合并三套账号管理为一个 AccountService消除代码重复
2. **安全加固**:修复 Create 越权漏洞,添加三层防护机制
3. **操作审计**:记录所有账号管理操作,满足合规要求
4. **简化路由**:统一路由结构 `/api/admin/accounts/{type}/*`,语义清晰
5. **认证统一**:合并后台和 H5 认证为 `/api/auth/*`
### Non-Goals
- ❌ 修改 GORM Callback 自动过滤逻辑(已经完善,保持不变)
- ❌ 重构角色和权限管理接口(不在本次范围)
- ❌ 修改个人客户认证接口(业务逻辑独立,保持不变)
- ❌ 添加实时审计日志查询接口(本次只做记录,查询接口后续迭代)
## Decisions
### 决策 1路由结构设计
**选择**:按账号类型分组的 RESTful 风格
```
/api/admin/accounts/platform/* (平台账号)
/api/admin/accounts/shop/* (代理账号)
/api/admin/accounts/enterprise/* (企业账号)
```
**备选方案**
- 方案 A单一路由 + query 参数(如 `/api/admin/accounts?type=platform`
- ❌ 拒绝原因:语义不清,不符合 RESTful 规范,前端调用复杂
- 方案 B保留三个独立路由`/platform-accounts``/shop-accounts`
- ❌ 拒绝原因:与统一架构目标冲突,未解决重复问题
**理由**
- ✅ 语义清晰,账号类型一目了然
- ✅ 符合 RESTful 规范,易于理解和文档化
- ✅ 便于路由层添加类型专用中间件(如企业账号拦截)
- ✅ 前端调用直观,便于维护
### 决策 2三层越权防护架构
**第一层:路由层中间件(粗粒度拦截)**
```go
// internal/routes/account.go
func registerEnterpriseAccountRoutes(router fiber.Router, ...) {
accounts := router.Group("/accounts/enterprise")
// 企业账号禁止访问账号管理接口
accounts.Use(func(c *fiber.Ctx) error {
userType := middleware.GetUserTypeFromContext(c.UserContext())
if userType == constants.UserTypeEnterprise {
return errors.New(errors.CodeForbidden, "无权限访问账号管理功能")
}
return c.Next()
})
// 注册路由...
}
```
**第二层Service 层业务检查(细粒度验证)**
```go
// internal/service/account/service.go
func (s *Service) Create(ctx context.Context, req *dto.CreateAccountRequest) error {
// 1. 基础认证检查
currentUserID := middleware.GetUserIDFromContext(ctx)
if currentUserID == 0 {
return errors.New(errors.CodeUnauthorized, "未授权访问")
}
userType := middleware.GetUserTypeFromContext(ctx)
// 2. 类型级权限检查
// 企业账号禁止创建账号
if userType == constants.UserTypeEnterprise {
return errors.New(errors.CodeForbidden, "企业账号不允许创建账号")
}
// 代理账号不能创建平台账号
if userType == constants.UserTypeAgent && req.UserType == constants.UserTypePlatform {
return errors.New(errors.CodeForbidden, "无权限创建平台账号")
}
// 3. 资源级权限检查(核心:修复越权漏洞)
if req.UserType == constants.UserTypeAgent && req.ShopID != nil {
if err := middleware.CanManageShop(ctx, *req.ShopID, s.shopStore); err != nil {
return err // 返回"无权限管理该店铺的账号"
}
}
if req.UserType == constants.UserTypeEnterprise && req.EnterpriseID != nil {
if err := middleware.CanManageEnterprise(ctx, *req.EnterpriseID, s.enterpriseStore); err != nil {
return err // 返回"无权限管理该企业的账号"
}
}
// 4. 创建账号...
}
```
**第三层GORM Callback 自动过滤(兜底)**
- 已有实现,保持不变
- 所有 List/Get 操作自动过滤
- 防止直接 SQL 注入绕过应用层检查
**理由**
- ✅ 多层防御,单层失效不会导致全局崩溃
- ✅ 第一层快速拦截明显越权,节省资源
- ✅ 第二层精确验证业务逻辑,覆盖所有场景
- ✅ 第三层兜底,防止绕过应用层检查
### 决策 3权限检查辅助函数设计
**位置**`pkg/middleware/permission_helper.go`(而非 Service 内部)
**接口设计**
```go
// CanManageShop 检查当前用户是否有权管理目标店铺的账号
// 返回 nil 表示有权限,返回 error 表示无权限
func CanManageShop(ctx context.Context, targetShopID uint, shopStore ShopStoreInterface) error
// CanManageEnterprise 检查当前用户是否有权管理目标企业的账号
func CanManageEnterprise(ctx context.Context, targetEnterpriseID uint,
enterpriseStore EnterpriseStoreInterface, shopStore ShopStoreInterface) error
```
**备选方案**
- 方案 A在 AccountService 内部实现为私有方法
- ❌ 拒绝原因:无法复用,其他 Service 需要相同权限检查时需重复实现
- 方案 B`pkg/utils` 中实现
- ❌ 拒绝原因utils 包应该是纯函数,不应依赖 Store 接口
**理由**
-`pkg/middleware` 是权限相关逻辑的自然归属
- ✅ 可以被多个 Service 复用AccountService、RoleService 等)
- ✅ 通过接口依赖 Store遵循依赖倒置原则便于测试
### 决策 4操作审计日志设计
**表结构**
```sql
CREATE TABLE tb_account_operation_log (
id BIGSERIAL PRIMARY KEY,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- 操作主体
operator_id BIGINT NOT NULL, -- 操作人 ID
operator_type INT NOT NULL, -- 操作人类型 (1=超管 2=平台 3=代理 4=企业)
operator_name VARCHAR(255) NOT NULL, -- 操作人用户名
-- 操作对象
target_account_id BIGINT, -- 目标账号 ID可选删除操作后可能查不到
target_username VARCHAR(255), -- 目标账号用户名
target_user_type INT, -- 目标账号类型
-- 操作内容
operation_type VARCHAR(50) NOT NULL, -- create/update/delete/assign_roles/remove_role
operation_desc TEXT NOT NULL, -- 操作描述(中文)
-- 变更详情JSON 格式)
before_data JSONB, -- 变更前数据update 操作)
after_data JSONB, -- 变更后数据create/update 操作)
-- 请求上下文
request_id VARCHAR(255), -- 请求 ID关联访问日志
ip_address VARCHAR(50), -- 操作 IP
user_agent TEXT -- User-Agent
);
CREATE INDEX idx_account_log_operator ON tb_account_operation_log(operator_id, created_at);
CREATE INDEX idx_account_log_target ON tb_account_operation_log(target_account_id, created_at);
CREATE INDEX idx_account_log_created ON tb_account_operation_log(created_at DESC);
```
**异步写入策略**
- 使用 Goroutine 异步写入,不阻塞主流程
- 写入失败只记录错误日志,不影响业务操作
- 未来可扩展为 Asynq 任务队列(支持重试)
**Service 设计**
```go
// internal/service/account_audit/service.go
type Service struct {
store *postgres.AccountOperationLogStore
}
func (s *Service) LogOperation(ctx context.Context, log *model.AccountOperationLog) {
// 异步写入,不阻塞主流程
go func() {
if err := s.store.Create(context.Background(), log); err != nil {
logger.GetAppLogger().Error("写入账号操作日志失败",
zap.Uint("operator_id", log.OperatorID),
zap.String("operation_type", log.OperationType),
zap.Error(err))
}
}()
}
```
**集成方式**
```go
// AccountService.Create 中集成
func (s *Service) Create(ctx context.Context, req *dto.CreateAccountRequest) (*model.Account, error) {
// 1. 权限检查...
// 2. 创建账号...
account, err := s.accountStore.Create(ctx, account)
if err != nil {
return nil, err
}
// 3. 记录审计日志(异步)
s.auditService.LogOperation(ctx, &model.AccountOperationLog{
OperatorID: currentUserID,
OperatorType: currentUserType,
OperatorName: currentUsername,
TargetAccountID: &account.ID,
TargetUsername: account.Username,
TargetUserType: account.UserType,
OperationType: "create",
OperationDesc: fmt.Sprintf("创建账号: %s", account.Username),
AfterData: toJSON(account),
RequestID: middleware.GetRequestIDFromContext(ctx),
IPAddress: middleware.GetIPFromContext(ctx),
UserAgent: middleware.GetUserAgentFromContext(ctx),
})
return account, nil
}
```
**理由**
- ✅ JSONB 字段存储完整变更数据,便于审计和回溯
- ✅ 异步写入不影响业务性能
- ✅ 关联 request_id 可以串联访问日志和审计日志
- ✅ 索引优化支持按操作人、目标账号、时间快速查询
### 决策 5统一错误返回策略
**原则**:越权访问统一返回"无权限操作该资源或资源不存在"
**实现**
```go
// Update 操作
func (s *Service) Update(ctx context.Context, id uint, req *dto.UpdateAccountRequest) error {
// 1. GetByID 会被 GORM Callback 自动过滤
account, err := s.accountStore.GetByID(ctx, id)
if err != nil {
if err == gorm.ErrRecordNotFound {
// ✅ 统一返回:可能是越权,也可能是真不存在
return errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在")
}
return errors.Wrap(errors.CodeInternalError, err, "获取账号失败")
}
// 2. 二次权限验证(虽然 GetByID 已过滤,但显式检查更安全)
userType := middleware.GetUserTypeFromContext(ctx)
if userType == constants.UserTypeAgent {
if account.ShopID == nil {
return errors.New(errors.CodeForbidden, "无权限操作该账号")
}
if err := middleware.CanManageShop(ctx, *account.ShopID, s.shopStore); err != nil {
return err
}
}
// 3. 更新操作...
}
```
**理由**
- ✅ 防止信息泄露(攻击者无法通过错误消息判断资源是否存在)
- ✅ 统一用户体验(所有越权场景返回相同错误消息)
- ✅ 符合安全最佳实践OWASP 推荐)
### 决策 6认证接口统一策略
**保守合并**:只合并后台和 H5 认证,保留个人客户认证
**理由**
- 后台和 H5 认证逻辑完全相同:
- 都是基于用户名+密码登录
- 都返回 Access Token + Refresh Token
- 都使用 Redis 存储 Token
- 都支持相同的用户类型(超管、平台、代理、企业)
- 个人客户认证逻辑不同:
- 支持微信授权登录OAuth
- 支持手机号+验证码登录
- Token 使用 JWT 而非 Redis
- 业务逻辑独立,不适合合并
**实现**
```go
// 新路由:/api/auth/*
POST /api/auth/login // 统一登录(后台+H5
POST /api/auth/logout // 统一登出
POST /api/auth/refresh-token // 刷新 Token
GET /api/auth/me // 获取用户信息
PUT /api/auth/password // 修改密码
// 保留:/api/c/v1/*(个人客户认证)
POST /api/c/v1/login/send-code // 发送验证码
POST /api/c/v1/login // 手机号登录
POST /api/c/v1/wechat/auth // 微信授权登录
```
**向后兼容处理**
- 旧接口立即删除(激进策略)
- 前端需要同步更新所有认证接口调用
- 通过 API 文档和 Breaking Changes 公告通知前端
## Risks / Trade-offs
### 风险 1前端大规模接口迁移
**风险**20+ 个接口路径变更,前端需要同步更新,可能遗漏导致功能异常
**缓解措施**
1. 提供完整的新旧路由映射表(在 proposal.md 中已列出)
2. 生成新的 OpenAPI 文档,前端通过文档更新
3. 后端先部署,前端更新后再切流量
4. 保留一周观察期,发现问题立即回滚
### 风险 2操作审计日志丢失
**风险**:异步写入失败导致审计日志丢失,无法追溯操作记录
**缓解措施**
1. 写入失败记录 Error 级别日志,包含完整审计信息
2. 通过访问日志access.log兜底可以追溯请求记录
3. 后续迭代升级为 Asynq 任务队列,支持重试和持久化
### 风险 3权限检查性能影响
**风险**:每次 Create 操作需要调用 GetSubordinateShopIDs可能影响性能
**当前缓解**
- GetSubordinateShopIDs 已有 Redis 缓存30分钟命中率高
- 代理账号创建频率低(< 10 次/分钟),性能影响 < 5ms
**未来优化**
- 如果成为瓶颈,可以预加载下级店铺 ID 到 context
- 超级管理员和平台用户跳过此检查,不受影响
### 权衡 1审计日志查询接口延后
**权衡**:本次只实现日志记录,不实现查询接口
**理由**
- 查询接口需要设计复杂的筛选条件(按时间、操作人、目标账号等)
- 需要考虑权限控制(代理只能查看自己店铺的日志)
- 优先保证核心功能(账号管理)稳定上线
- 后续迭代专门实现审计日志查询功能
### 权衡 2删除而非标记废弃旧接口
**权衡**:激进策略,直接删除旧接口,而非保留并标记 deprecated
**理由**
- 旧接口数量多20+),保留会导致代码库臃肿
- 新旧接口功能完全重复,维护成本高
- 前端有资源配合同步更新(用户已确认)
- Breaking Change 在提案中已充分说明
**后果**
- 前端必须同步更新,无法渐进迁移
- 发现问题需要立即回滚整个版本
- 需要充分测试后再上线
## Migration Plan
### 阶段 1代码重构预计 3 天)
1. **Day 1**:权限检查和审计日志基础设施
- 创建 `pkg/middleware/permission_helper.go`
- 创建审计日志 Model、Store、Service
- 创建数据库迁移文件
- 单元测试覆盖
2. **Day 2**AccountService 重构
- 扩展 AccountService添加权限检查
- 集成审计日志记录
- 删除 ShopAccountService、CustomerAccountService
- 单元测试覆盖
3. **Day 3**Handler 和路由重构
- 扩展 AccountHandler
- 删除 ShopAccountHandler、CustomerAccountHandler
- 重构路由注册逻辑
- 集成测试覆盖
### 阶段 2测试和文档预计 2 天)
4. **Day 4**:全面测试
- 集成测试account_permission_test.go越权防护
- 集成测试account_audit_test.go审计日志
- 回归测试:确保现有功能不受影响
- 性能测试:验证 P95 < 200ms
5. **Day 5**:文档和交接
- 生成新的 OpenAPI 文档
- 编写迁移指南(新旧路由映射)
- 前端对接会议,说明 Breaking Changes
- 准备回滚方案
### 阶段 3部署和监控预计 1 天)
6. **Day 6**:灰度发布
- 执行数据库迁移(创建审计日志表)
- 部署后端新版本
- 前端更新接口调用
- 监控错误率和响应时间
7. **Day 7**:全量观察
- 监控审计日志写入情况
- 监控 API 错误率(重点关注 403 错误)
- 验证权限检查有效性
- 准备随时回滚
### 回滚策略
**触发条件**
- API 错误率 > 5%
- P95 响应时间 > 300ms
- 发现严重安全漏洞
- 前端无法在 1 天内完成迁移
**回滚步骤**
1. 回滚后端代码到上一个版本
2. 前端回滚到旧接口调用
3. 审计日志表保留(不删除数据)
4. 总结问题,重新规划迁移
## Open Questions
### Q1是否需要批量迁移现有账号数据
**当前状态**:无需迁移,数据模型不变
**说明**
- Account 表结构不变
- user_type 字段已经区分四种账号类型
- 只是接口和代码重构,不涉及数据迁移
### Q2审计日志是否需要定期归档
**当前决策**:暂不归档,后续根据数据增长情况决定
**说明**
- 初期数据量小(< 10万条/月)
- PostgreSQL JSONB 查询性能足够
- 如果后续数据量大(> 100万条可以
- 按月分表tb_account_operation_log_202601
- 或归档到对象存储
### Q3是否需要支持操作撤销功能
**当前决策**:不支持,审计日志只做记录和查询
**理由**
- 账号操作撤销逻辑复杂(如删除账号后重新激活)
- 现有需求不明确
- 可以通过手动操作实现(如重新创建账号)
- 后续如有需求再单独设计

View File

@@ -0,0 +1,118 @@
# 统一账号管理接口重构
## Why
当前账号管理接口存在严重的架构混乱和安全漏洞:
1. **接口重复**`/accounts``/platform-accounts` 使用同一个 Handler功能完全重复20个重复接口
2. **功能不一致**:平台账号有完整的 CRUD + 角色管理,而代理/企业账号缺少关键功能
3. **命名混乱**`/customer-accounts` 实际管理的是企业账号,代码注释错误
4. **安全漏洞**Create 操作缺少越权检查,代理可以为其他店铺创建账号
5. **可维护性差**:三个独立的 ServiceAccount、ShopAccount、CustomerAccount导致代码重复和不一致
这次重构将统一接口架构,消除重复,加固安全防护,并添加完整的操作审计,为后续功能扩展打下坚实基础。
## What Changes
- **BREAKING**: 删除旧路由
- 删除 `/api/admin/platform-accounts/*`10个接口
- 删除 `/api/admin/shop-accounts/*`5个接口
- 删除 `/api/admin/customer-accounts/*`5个接口
- **新增**: 统一账号管理路由
- `/api/admin/accounts/platform/*`(平台账号管理)
- `/api/admin/accounts/shop/*`(代理账号管理)
- `/api/admin/accounts/enterprise/*`(企业账号管理)
- **BREAKING**: 认证接口统一
- 删除 `/api/admin/login``/api/admin/logout`5个接口
- 删除 `/api/h5/login``/api/h5/logout`5个接口
- 新增 `/api/auth/*` 统一认证5个接口
- 保留 `/api/c/v1/*` 个人客户认证(独立业务逻辑)
- **新增**: 三层越权防护机制
- 路由层:企业账号中间件拦截
- Service 层CanManageShop/CanManageEnterprise 权限检查
- GORM 层:已有自动过滤(保持)
- **新增**: 操作审计系统
- 数据库迁移:创建 `tb_account_operation_log`
- ServiceAccountAuditService 记录所有账号操作
- 集成Create/Update/Delete/AssignRoles 自动记录
- **重构**: 合并 Service 层
- 删除 ShopAccountService、CustomerAccountService
- 扩展 AccountService 支持所有账号类型
- 统一错误返回:"无权限操作该资源或资源不存在"
- **重构**: 合并 Handler 层
- 删除 ShopAccountHandler、CustomerAccountHandler
- 扩展 AccountHandler 支持所有账号类型
- **新增**: 权限辅助函数
- `pkg/middleware/permission_helper.go`
- CanManageShop验证代理对目标店铺的管理权限
- CanManageEnterprise验证代理对目标企业的管理权限
## Capabilities
### New Capabilities
- `account-permission-check`:账号管理权限检查机制(三层防护)
- `account-operation-audit`:账号操作审计日志系统
- `unified-auth-api`:统一认证接口(后台+H5
### Modified Capabilities
- `account-management`:账号管理接口架构(统一路由结构,消除重复)
## Impact
**代码变更**
- 删除文件:
- `internal/handler/admin/shop_account.go`
- `internal/handler/admin/customer_account.go`
- `internal/service/shop_account/service.go`
- `internal/service/customer_account/service.go`
- `internal/routes/shop.go`(部分)
- `internal/routes/customer_account.go`
- 修改文件:
- `internal/handler/admin/account.go`(扩展支持所有账号类型)
- `internal/service/account/service.go`(添加权限检查和审计)
- `internal/routes/account.go`(新路由结构)
- `internal/routes/admin.go`(更新路由注册)
- 新增文件:
- `pkg/middleware/permission_helper.go`(权限检查函数)
- `internal/model/account_operation_log.go`(审计日志模型)
- `internal/store/postgres/account_operation_log_store.go`(审计日志存储)
- `internal/service/account_audit/service.go`(审计日志服务)
- `migrations/XXXXXX_create_account_operation_log.up.sql`(数据库迁移)
**API 变更**Breaking Changes
- 前端需要更新所有账号管理接口调用
- 新旧路由映射:
```
POST /api/admin/platform-accounts
POST /api/admin/accounts/platform
GET /api/admin/shop-accounts
GET /api/admin/accounts/shop
POST /api/admin/customer-accounts
POST /api/admin/accounts/enterprise
POST /api/admin/login
POST /api/auth/login
```
**测试变更**
- 删除:`tests/integration/platform_account_test.go`(已有 account_test.go
- 删除:`tests/integration/shop_account_management_test.go`
- 删除:`tests/unit/customer_account_service_test.go`
- 修改:`tests/integration/account_test.go`(扩展覆盖所有账号类型)
- 新增:`tests/integration/account_permission_test.go`(越权防护测试)
- 新增:`tests/integration/account_audit_test.go`(审计日志测试)
**依赖影响**
- 无新增外部依赖
- 内部依赖调整AccountService 新增 ShopStore 和 EnterpriseStore 依赖
**性能影响**
- 权限检查:增加 GetSubordinateShopIDs 调用(已有 Redis 缓存,影响 < 5ms
- 审计日志:异步写入,不阻塞主流程
- 预期 API 响应时间增加 < 10ms
**安全提升**
- 修复 Create 操作越权漏洞Critical
- 统一错误返回,防止信息泄露
- 完整操作审计,满足合规要求

View File

@@ -0,0 +1,143 @@
# 账号管理接口规格
## ADDED Requirements
### Requirement: 统一账号管理路由结构
系统 SHALL 提供统一的账号管理路由,按账号类型分组。
#### Scenario: 平台账号管理路由
- **WHEN** 访问 /api/admin/accounts/platform/*
- **THEN** 提供平台账号的 CRUD + 角色管理功能
#### Scenario: 代理账号管理路由
- **WHEN** 访问 /api/admin/accounts/shop/*
- **THEN** 提供代理账号的 CRUD + 角色管理功能
#### Scenario: 企业账号管理路由
- **WHEN** 访问 /api/admin/accounts/enterprise/*
- **THEN** 提供企业账号的 CRUD + 角色管理功能
### Requirement: 所有账号类型支持完整的CRUD操作
系统 SHALL 为所有账号类型提供一致的 CRUD 功能。
#### Scenario: 创建账号
- **WHEN** POST /api/admin/accounts/{type}
- **THEN** 验证权限,创建账号,返回账号信息
#### Scenario: 查询账号列表
- **WHEN** GET /api/admin/accounts/{type}
- **THEN** 应用数据权限过滤,返回分页列表
#### Scenario: 查询账号详情
- **WHEN** GET /api/admin/accounts/{type}/:id
- **THEN** 验证权限,返回账号详情
#### Scenario: 更新账号
- **WHEN** PUT /api/admin/accounts/{type}/:id
- **THEN** 验证权限,更新账号,返回更新后信息
#### Scenario: 删除账号
- **WHEN** DELETE /api/admin/accounts/{type}/:id
- **THEN** 验证权限,软删除账号,返回成功
### Requirement: 所有账号类型支持密码和状态管理
系统 SHALL 为所有账号类型提供统一的密码和状态管理功能。
#### Scenario: 修改账号密码
- **WHEN** PUT /api/admin/accounts/{type}/:id/password
- **THEN** 验证权限更新密码bcrypt哈希返回成功
#### Scenario: 启用账号
- **WHEN** PUT /api/admin/accounts/{type}/:id/statusstatus=1
- **THEN** 验证权限,更新状态为启用,返回成功
#### Scenario: 禁用账号
- **WHEN** PUT /api/admin/accounts/{type}/:id/statusstatus=0
- **THEN** 验证权限,更新状态为禁用,返回成功
### Requirement: 所有账号类型支持角色管理
系统 SHALL 为所有账号类型提供统一的角色管理功能。
#### Scenario: 分配角色
- **WHEN** POST /api/admin/accounts/{type}/:id/rolesbody: {role_ids: [1,2]}
- **THEN** 验证权限,分配角色,返回成功
#### Scenario: 查询账号角色
- **WHEN** GET /api/admin/accounts/{type}/:id/roles
- **THEN** 验证权限,返回账号的所有角色列表
#### Scenario: 移除角色
- **WHEN** DELETE /api/admin/accounts/{type}/:id/roles/:role_id
- **THEN** 验证权限,软删除角色关联,返回成功
#### Scenario: 清空所有角色
- **WHEN** POST /api/admin/accounts/{type}/:id/rolesbody: {role_ids: []}
- **THEN** 验证权限,删除所有角色关联,返回成功
### Requirement: 删除旧路由避免冲突
系统 SHALL 删除旧的账号管理路由,避免与新路由冲突。
#### Scenario: 旧平台账号路由404
- **WHEN** 访问 POST /api/admin/platform-accounts
- **THEN** 返回 404 Not Found
#### Scenario: 旧代理账号路由404
- **WHEN** 访问 GET /api/admin/shop-accounts
- **THEN** 返回 404 Not Found
#### Scenario: 旧企业账号路由404
- **WHEN** 访问 POST /api/admin/customer-accounts
- **THEN** 返回 404 Not Found
### Requirement: 响应格式保持一致
系统 SHALL 为所有账号类型返回一致的响应格式。
#### Scenario: 创建响应包含完整账号信息
- **WHEN** 创建账号成功
- **THEN** 返回账号 ID、用户名、手机号、用户类型、状态、创建时间
#### Scenario: 列表响应包含分页信息
- **WHEN** 查询账号列表
- **THEN** 返回 {items, total, page, size}
#### Scenario: 错误响应使用统一格式
- **WHEN** 操作失败
- **THEN** 返回 {code, message, timestamp}
### Requirement: 支持按条件筛选账号列表
系统 SHALL 支持按多个条件筛选账号列表。
#### Scenario: 按用户名筛选
- **WHEN** GET /api/admin/accounts/{type}?username=张三
- **THEN** 返回用户名包含"张三"的账号列表
#### Scenario: 按手机号筛选
- **WHEN** GET /api/admin/accounts/{type}?phone=138
- **THEN** 返回手机号包含"138"的账号列表
#### Scenario: 按状态筛选
- **WHEN** GET /api/admin/accounts/{type}?status=1
- **THEN** 返回状态为启用的账号列表
#### Scenario: 按店铺ID筛选代理账号
- **WHEN** GET /api/admin/accounts/shop?shop_id=100
- **THEN** 返回 shop_id=100 的代理账号列表(需权限验证)
#### Scenario: 按企业ID筛选企业账号
- **WHEN** GET /api/admin/accounts/enterprise?enterprise_id=50
- **THEN** 返回 enterprise_id=50 的企业账号列表(需权限验证)
### Requirement: 统一Service层实现消除重复
系统 SHALL 使用单一 AccountService 处理所有账号类型,消除代码重复。
#### Scenario: AccountService处理所有账号类型
- **WHEN** 调用 AccountService.Create(ctx, req)
- **THEN** 根据 req.UserType 创建不同类型账号(平台、代理、企业)
#### Scenario: 删除ShopAccountService
- **WHEN** 系统重构完成
- **THEN** ShopAccountService 及相关文件应被删除
#### Scenario: 删除CustomerAccountService
- **WHEN** 系统重构完成
- **THEN** CustomerAccountService 及相关文件应被删除

View File

@@ -0,0 +1,105 @@
# 账号操作审计日志规格
## ADDED Requirements
### Requirement: 记录所有账号管理操作
系统 SHALL 记录所有账号管理操作,包括创建、更新、删除、角色分配和移除。
#### Scenario: 创建账号时记录审计日志
- **WHEN** 用户创建账号成功
- **THEN** 系统应异步写入审计日志包含操作人、目标账号、操作类型create、变更数据after_data
#### Scenario: 更新账号时记录变更前后数据
- **WHEN** 用户更新账号信息(用户名、手机号、状态等)
- **THEN** 系统应记录 before_data 和 after_data包含所有变更字段
#### Scenario: 删除账号时记录审计日志
- **WHEN** 用户软删除账号
- **THEN** 系统应记录删除操作包含被删除账号的完整信息before_data
#### Scenario: 分配角色时记录审计日志
- **WHEN** 用户为账号分配角色
- **THEN** 系统应记录 operation_type=assign_rolesafter_data 包含分配的角色 ID 列表
#### Scenario: 移除角色时记录审计日志
- **WHEN** 用户移除账号的角色
- **THEN** 系统应记录 operation_type=remove_role包含被移除的角色 ID
### Requirement: 审计日志包含完整的操作上下文
系统 SHALL 在审计日志中记录操作人、目标对象、变更内容和请求上下文。
#### Scenario: 记录操作人信息
- **WHEN** 记录审计日志
- **THEN** 日志应包含 operator_id、operator_type、operator_name
#### Scenario: 记录目标账号信息
- **WHEN** 记录审计日志
- **THEN** 日志应包含 target_account_id、target_username、target_user_type
#### Scenario: 记录变更数据JSON格式
- **WHEN** 记录更新操作
- **THEN** before_data 和 after_data 应为 JSONB 格式,包含完整的字段信息
#### Scenario: 记录请求上下文
- **WHEN** 记录审计日志
- **THEN** 日志应包含 request_id、ip_address、user_agent可关联访问日志
### Requirement: 异步写入不阻塞业务流程
系统 SHALL 使用 Goroutine 异步写入审计日志,确保业务操作不受审计日志性能影响。
#### Scenario: 异步写入审计日志
- **WHEN** AccountService.Create 创建账号成功
- **THEN** 主流程立即返回,审计日志在独立 Goroutine 中异步写入
#### Scenario: 写入失败只记录错误日志
- **WHEN** 审计日志写入数据库失败
- **THEN** 记录 Error 级别日志,包含完整审计信息,但不影响业务操作结果
#### Scenario: 业务响应时间不受影响
- **WHEN** 执行账号创建操作
- **THEN** API 响应时间不应因审计日志写入而增加(< 1ms
### Requirement: 操作描述使用中文
系统 SHALL 使用中文描述审计日志的操作类型和内容。
#### Scenario: 创建操作描述
- **WHEN** 记录创建账号操作
- **THEN** operation_desc 应为 "创建账号: {username}"
#### Scenario: 更新操作描述
- **WHEN** 记录更新账号操作
- **THEN** operation_desc 应为 "更新账号: {username}"
#### Scenario: 删除操作描述
- **WHEN** 记录删除账号操作
- **THEN** operation_desc 应为 "删除账号: {username}"
#### Scenario: 分配角色操作描述
- **WHEN** 记录分配角色操作
- **THEN** operation_desc 应为 "为账号 {username} 分配角色"
### Requirement: 支持按多维度查询审计日志
系统 SHALL 提供索引支持按操作人、目标账号、时间快速查询审计日志。
#### Scenario: 按操作人查询日志
- **WHEN** 查询特定操作人的所有操作记录
- **THEN** 使用 idx_account_log_operator 索引,查询时间 < 50ms
#### Scenario: 按目标账号查询日志
- **WHEN** 查询特定账号的所有操作记录
- **THEN** 使用 idx_account_log_target 索引,查询时间 < 50ms
#### Scenario: 按时间范围查询日志
- **WHEN** 查询最近7天的操作记录
- **THEN** 使用 idx_account_log_created 索引,支持倒序分页
### Requirement: 关联访问日志追溯完整请求链路
系统 SHALL 通过 request_id 关联审计日志和访问日志,支持完整链路追溯。
#### Scenario: 通过request_id关联日志
- **WHEN** 审计日志中记录 request_id="req-12345"
- **THEN** 可以在 access.log 中查询到对应的 HTTP 请求日志
#### Scenario: 追溯完整请求链路
- **WHEN** 运维人员调查某个账号创建操作
- **THEN** 通过 request_id 可以查询到:请求参数、权限检查、数据库操作、响应结果

View File

@@ -0,0 +1,127 @@
# 账号管理权限检查规格
## ADDED Requirements
### Requirement: 三层越权防护架构
系统 SHALL 实现三层越权防护机制,确保账号管理操作的安全性。
#### Scenario: 路由层中间件拦截企业账号
- **WHEN** 企业账号user_type=4访问账号管理接口/api/admin/accounts/*
- **THEN** 中间件应返回 403 错误:"无权限访问账号管理功能"
#### Scenario: Service层权限检查成功
- **WHEN** 代理账号创建自己店铺的账号
- **THEN** CanManageShop 检查应通过,账号创建成功
#### Scenario: GORM层自动过滤生效
- **WHEN** 代理账号查询账号列表
- **THEN** GORM Callback 应自动添加 `shop_id IN (当前店铺+下级店铺)` 过滤条件
### Requirement: 代理账号只能管理自己店铺及下级店铺的账号
系统 SHALL 验证代理账号对目标店铺的管理权限,禁止跨店铺越权操作。
#### Scenario: 代理创建自己店铺的账号成功
- **WHEN** 代理账号shop_id=100创建 shop_id=100 的账号
- **THEN** 权限检查通过,账号创建成功
#### Scenario: 代理创建下级店铺的账号成功
- **WHEN** 代理账号shop_id=100下级101,102创建 shop_id=101 的账号
- **THEN** GetSubordinateShopIDs 返回 [100,101,102],权限检查通过
#### Scenario: 代理创建其他店铺的账号失败
- **WHEN** 代理账号shop_id=100创建 shop_id=200 的账号
- **THEN** CanManageShop 返回错误:"无权限管理该店铺的账号",创建失败
#### Scenario: 代理创建平台账号失败
- **WHEN** 代理账号尝试创建 user_type=2 的平台账号
- **THEN** Service 层检查返回错误:"无权限创建平台账号",创建失败
### Requirement: 平台账号和超级管理员可以管理所有账号
系统 SHALL 允许平台账号和超级管理员跳过所有权限检查,管理所有账号。
#### Scenario: 平台账号创建任意类型账号
- **WHEN** 平台账号user_type=2创建代理账号user_type=3, shop_id=100
- **THEN** 权限检查跳过,账号创建成功
#### Scenario: 超级管理员创建任意类型账号
- **WHEN** 超级管理员user_type=1创建任意类型账号
- **THEN** 权限检查跳过,账号创建成功
#### Scenario: 平台账号查询所有账号
- **WHEN** 平台账号调用账号列表接口
- **THEN** GORM Callback 跳过过滤,返回所有账号
### Requirement: 企业账号禁止访问账号管理接口
系统 SHALL 禁止企业账号访问所有账号管理接口。
#### Scenario: 企业账号创建账号失败(路由层拦截)
- **WHEN** 企业账号user_type=4调用 POST /api/admin/accounts/enterprise
- **THEN** 路由层中间件返回 403 错误:"无权限访问账号管理功能"
#### Scenario: 企业账号更新账号失败Service层拦截
- **WHEN** 企业账号绕过路由层,直接调用 AccountService.Update
- **THEN** Service 层返回 403 错误:"企业账号不允许更新账号"
### Requirement: 统一错误返回防止信息泄露
系统 SHALL 在越权访问时统一返回模糊错误消息,防止攻击者判断资源是否存在。
#### Scenario: 查询不存在的账号返回模糊错误
- **WHEN** 用户查询不存在的账号 ID
- **THEN** 返回 403 错误:"无权限操作该资源或资源不存在"
#### Scenario: 查询越权的账号返回相同错误
- **WHEN** 代理账号shop_id=100查询 shop_id=200 的账号
- **THEN** 返回 403 错误:"无权限操作该资源或资源不存在"(与不存在的错误消息相同)
### Requirement: CanManageShop 权限检查函数
系统 SHALL 提供 CanManageShop 函数验证用户对目标店铺的管理权限。
#### Scenario: 验证代理对自己店铺的权限
- **WHEN** 调用 CanManageShop(ctx, 100, shopStore) 且当前用户 shop_id=100
- **THEN** 返回 nil有权限
#### Scenario: 验证代理对下级店铺的权限
- **WHEN** 调用 CanManageShop(ctx, 101, shopStore) 且当前用户 shop_id=100下级包含 101
- **THEN** GetSubordinateShopIDs 返回 [100,101,102],返回 nil有权限
#### Scenario: 验证代理对其他店铺的权限失败
- **WHEN** 调用 CanManageShop(ctx, 200, shopStore) 且当前用户 shop_id=100
- **THEN** 返回错误:"无权限管理该店铺的账号"
#### Scenario: 验证平台账号自动通过
- **WHEN** 调用 CanManageShop(ctx, 200, shopStore) 且当前用户 user_type=2平台
- **THEN** 不调用 GetSubordinateShopIDs直接返回 nil有权限
### Requirement: CanManageEnterprise 权限检查函数
系统 SHALL 提供 CanManageEnterprise 函数验证用户对目标企业的管理权限。
#### Scenario: 验证平台账号管理任意企业
- **WHEN** 调用 CanManageEnterprise(ctx, 50, enterpriseStore, shopStore) 且当前用户 user_type=2
- **THEN** 返回 nil有权限
#### Scenario: 验证代理对归属企业的权限
- **WHEN** 调用 CanManageEnterprise(ctx, 50, enterpriseStore, shopStore) 且企业 owner_shop_id=100当前用户 shop_id=100
- **THEN** 返回 nil有权限
#### Scenario: 验证代理对下级店铺企业的权限
- **WHEN** 调用 CanManageEnterprise(ctx, 50, enterpriseStore, shopStore) 且企业 owner_shop_id=101当前用户 shop_id=100下级包含 101
- **THEN** 返回 nil有权限
#### Scenario: 验证代理对其他店铺企业的权限失败
- **WHEN** 调用 CanManageEnterprise(ctx, 50, enterpriseStore, shopStore) 且企业 owner_shop_id=200当前用户 shop_id=100
- **THEN** 返回错误:"无权限管理该企业的账号"
### Requirement: 权限检查性能优化
系统 SHALL 使用 Redis 缓存优化权限检查性能,确保 API 响应时间 < 200ms。
#### Scenario: GetSubordinateShopIDs 命中缓存
- **WHEN** 调用 GetSubordinateShopIDs(ctx, 100) 且缓存存在
- **THEN** 从 Redis 读取缓存,不查询数据库,耗时 < 5ms
#### Scenario: GetSubordinateShopIDs 缓存未命中
- **WHEN** 调用 GetSubordinateShopIDs(ctx, 100) 且缓存不存在
- **THEN** 递归查询数据库,写入 Redis 缓存30分钟返回结果
#### Scenario: 权限检查总耗时 < 10ms
- **WHEN** 执行完整权限检查(包含 GetSubordinateShopIDs
- **THEN** 总耗时 < 10ms缓存命中时 < 5ms

View File

@@ -0,0 +1,86 @@
# 统一认证接口规格
## ADDED Requirements
### Requirement: 合并后台和H5认证接口
系统 SHALL 提供统一认证接口 /api/auth/*,支持后台和 H5 两种场景的认证。
#### Scenario: 后台用户登录
- **WHEN** 用户调用 POST /api/auth/loginuser_type IN (1,2,3,4)
- **THEN** 验证用户名+密码,返回 Access Token + Refresh Token
#### Scenario: H5用户登录
- **WHEN** H5 用户调用 POST /api/auth/loginuser_type IN (3,4)
- **THEN** 验证用户名+密码,返回 Access Token + Refresh Token
#### Scenario: 登出统一接口
- **WHEN** 用户调用 POST /api/auth/logout
- **THEN** 删除 Redis 中的 Token返回成功
#### Scenario: 刷新Token统一接口
- **WHEN** 用户调用 POST /api/auth/refresh-token
- **THEN** 验证 Refresh Token返回新的 Access Token
#### Scenario: 获取用户信息统一接口
- **WHEN** 用户调用 GET /api/auth/me
- **THEN** 返回当前用户信息,包含 menus 和 buttons
### Requirement: 保留个人客户认证接口
系统 SHALL 保持个人客户认证接口 /api/c/v1/* 独立,不与后台/H5认证合并。
#### Scenario: 个人客户微信授权登录
- **WHEN** 个人客户调用 POST /api/c/v1/wechat/auth
- **THEN** 使用微信 OAuth 流程,返回 JWT Token
#### Scenario: 个人客户手机号登录
- **WHEN** 个人客户调用 POST /api/c/v1/login
- **THEN** 验证手机号+验证码,返回 JWT Token
#### Scenario: 个人客户获取资料
- **WHEN** 个人客户调用 GET /api/c/v1/profile
- **THEN** 返回个人客户资料(独立数据结构)
### Requirement: 删除旧认证接口路由
系统 SHALL 删除 /api/admin/login、/api/h5/login 等旧路由,统一为 /api/auth/*。
#### Scenario: 旧后台登录接口404
- **WHEN** 用户调用 POST /api/admin/login
- **THEN** 返回 404 Not Found
#### Scenario: 旧H5登录接口404
- **WHEN** 用户调用 POST /api/h5/login
- **THEN** 返回 404 Not Found
#### Scenario: 新统一接口正常工作
- **WHEN** 用户调用 POST /api/auth/login
- **THEN** 正常认证,返回 200 OK
### Requirement: 认证逻辑保持不变
系统 SHALL 保持认证逻辑不变,只修改路由路径。
#### Scenario: Token生成逻辑不变
- **WHEN** 用户登录成功
- **THEN** 生成相同格式的 Access Token24小时和 Refresh Token7天
#### Scenario: Token存储在Redis
- **WHEN** 生成 Token
- **THEN** 存储在 RedisKey 格式为 "auth:token:{token}"
#### Scenario: 用户类型过滤不变
- **WHEN** 登录请求中包含 user_type
- **THEN** 验证用户类型是否与账号类型匹配
### Requirement: 响应格式保持兼容
系统 SHALL 保持登录响应格式兼容,包含 menus 和 buttons。
#### Scenario: 登录响应包含菜单
- **WHEN** 用户登录成功
- **THEN** 响应应包含 menus菜单树结构
#### Scenario: 登录响应包含按钮权限
- **WHEN** 用户登录成功
- **THEN** 响应应包含 buttons按钮权限列表
#### Scenario: 响应格式不变
- **WHEN** 用户登录成功
- **THEN** 响应格式应与旧接口完全一致,前端无需修改解析逻辑

View File

@@ -0,0 +1,171 @@
# 统一账号管理接口重构 - 任务清单
## 1. 数据库迁移
- [x] 1.1 创建 `migrations/XXXXXX_create_account_operation_log.up.sql` 迁移文件(创建审计日志表)
- [x] 1.2 创建 `migrations/XXXXXX_create_account_operation_log.down.sql` 回滚文件
- [x] 1.3 运行迁移验证表结构和索引创建成功
## 2. 权限检查基础设施
- [x] 2.1 创建 `pkg/middleware/permission_helper.go` 文件
- [x] 2.2 实现 `CanManageShop` 函数(验证代理对目标店铺的管理权限)
- [x] 2.3 实现 `CanManageEnterprise` 函数(验证代理对目标企业的管理权限)
- [x] 2.4 定义 `ShopStoreInterface``EnterpriseStoreInterface` 接口(用于依赖倒置)
- [x] 2.5 编写单元测试 `pkg/middleware/permission_helper_test.go`(覆盖率 ≥ 90%
- [x] 2.6 运行 `lsp_diagnostics` 验证代码无错误
## 3. 审计日志系统
- [x] 3.1 创建 `internal/model/account_operation_log.go`(审计日志模型)
- [x] 3.2 创建 `internal/store/postgres/account_operation_log_store.go`(审计日志存储层)
- [x] 3.3 实现 `AccountOperationLogStore.Create` 方法
- [x] 3.4 创建 `internal/service/account_audit/service.go`(审计日志服务层)
- [x] 3.5 实现 `AccountAuditService.LogOperation` 方法异步写入Goroutine
- [x] 3.6 编写单元测试 `internal/service/account_audit/service_test.go`(覆盖率 ≥ 90%
- [x] 3.7 运行 `lsp_diagnostics` 验证代码无错误
## 4. AccountService 重构(添加权限检查和审计)
- [x] 4.1 为 `AccountService` 添加 `shopStore``enterpriseStore` 依赖
- [x] 4.2 为 `AccountService` 添加 `auditService` 依赖
- [x] 4.3 重构 `Create` 方法:添加三层权限检查(类型级 + 资源级 + GORM 兜底)
- [x] 4.4 重构 `Create` 方法:集成审计日志记录(异步)
- [x] 4.5 重构 `Update` 方法:添加权限检查和审计日志(记录 before_data 和 after_data
- [x] 4.6 重构 `Delete` 方法:添加权限检查和审计日志
- [x] 4.7 重构 `AssignRoles` 方法:添加权限检查和审计日志
- [x] 4.8 重构 `RemoveRole` 方法:添加权限检查和审计日志
- [x] 4.9 修改错误返回:统一为"无权限操作该资源或资源不存在"
- [x] 4.10 编写单元测试 `internal/service/account/service_test.go`(覆盖率 ≥ 90%
- [x] 4.11 运行 `lsp_diagnostics` 验证代码无错误
## 5. 删除旧 Service 层代码
- [x] 5.1 删除 `internal/service/shop_account/service.go`
- [x] 5.2 删除 `internal/service/customer_account/service.go`
- [x] 5.3 删除相关测试文件 `tests/unit/shop_account_service_test.go``tests/unit/customer_account_service_test.go`
- [x] 5.4 运行 `go build ./...` 确保没有引用残留
## 6. AccountHandler 重构(支持所有账号类型)
- [x] 6.1 重构 `AccountHandler.Create` 方法:支持 platform/shop/enterprise 三种类型
- [x] 6.2 重构 `AccountHandler.List` 方法支持按账号类型筛选username/phone/status/shop_id/enterprise_id
- [x] 6.3 重构 `AccountHandler.GetByID` 方法:支持所有账号类型
- [x] 6.4 重构 `AccountHandler.Update` 方法:支持所有账号类型
- [x] 6.5 重构 `AccountHandler.Delete` 方法:支持所有账号类型
- [x] 6.6 重构 `AccountHandler.UpdatePassword` 方法:支持所有账号类型
- [x] 6.7 重构 `AccountHandler.UpdateStatus` 方法:支持所有账号类型
- [x] 6.8 重构 `AccountHandler.AssignRoles` 方法:支持所有账号类型
- [x] 6.9 重构 `AccountHandler.GetRoles` 方法:支持所有账号类型
- [x] 6.10 重构 `AccountHandler.RemoveRole` 方法:支持所有账号类型
- [x] 6.11 运行 `lsp_diagnostics` 验证代码无错误
## 7. 删除旧 Handler 层代码
- [x] 7.1 删除 `internal/handler/admin/shop_account.go`
- [x] 7.2 删除 `internal/handler/admin/customer_account.go`
- [x] 7.3 运行 `go build ./...` 确保没有引用残留
## 8. 路由重构(统一账号管理路由)
- [x] 8.1 重构 `internal/routes/account.go`:实现新路由结构
- [x] 8.2 注册平台账号路由组 `/api/admin/accounts/platform/*`10个接口
- [x] 8.3 注册代理账号路由组 `/api/admin/accounts/shop/*`10个接口
- [x] 8.4 注册企业账号路由组 `/api/admin/accounts/enterprise/*`10个接口
- [x] 8.5 为企业账号路由组添加中间件拦截(企业账号禁止访问账号管理)
- [x] 8.6 删除旧路由注册:`/api/admin/platform-accounts/*`
- [x] 8.7 删除旧路由注册:`/api/admin/shop-accounts/*`
- [x] 8.8 删除旧路由注册:`/api/admin/customer-accounts/*`
- [x] 8.9 运行 `go build ./...` 确保路由编译通过
## 9. 认证接口统一
- [x] 9.1 创建 `internal/handler/auth/handler.go`(统一认证 Handler
- [x] 9.2 实现 `Login` 方法(合并后台和 H5 登录逻辑)
- [x] 9.3 实现 `Logout` 方法(统一登出)
- [x] 9.4 实现 `RefreshToken` 方法(统一刷新 Token
- [x] 9.5 实现 `GetMe` 方法(统一获取用户信息)
- [x] 9.6 实现 `UpdatePassword` 方法(统一修改密码)
- [x] 9.7 创建 `internal/routes/auth.go` 注册统一认证路由 `/api/auth/*`
- [x] 9.8 删除旧认证路由:`/api/admin/login`5个接口
- [x] 9.9 删除旧认证路由:`/api/h5/login`5个接口
- [x] 9.10 保留个人客户认证路由:`/api/c/v1/*`(不修改)
- [x] 9.11 运行 `lsp_diagnostics` 验证代码无错误
## 10. Bootstrap 更新(依赖注入调整)
- [x] 10.1 更新 `internal/bootstrap/stores.go`:添加 `AccountOperationLogStore` 初始化
- [x] 10.2 更新 `internal/bootstrap/services.go`:添加 `AccountAuditService` 初始化
- [x] 10.3 更新 `internal/bootstrap/services.go`:更新 `AccountService` 依赖注入(添加 shopStore、enterpriseStore、auditService
- [x] 10.4 更新 `internal/bootstrap/handlers.go`:添加 `AuthHandler` 初始化
- [x] 10.5 更新 `internal/bootstrap/handlers.go`:删除 `ShopAccountHandler``CustomerAccountHandler` 初始化
- [x] 10.6 运行 `go build ./...` 确保编译通过
## 11. 文档生成器更新
- [x] 11.1 更新 `cmd/api/docs.go`:添加新路由到 Handlers 结构体accounts/platform、accounts/shop、accounts/enterprise、auth
- [x] 11.2 更新 `cmd/api/docs.go`删除旧路由platform-accounts、shop-accounts、customer-accounts、admin/login、h5/login
- [x] 11.3 更新 `cmd/gendocs/main.go`:同步更新 Handlers 初始化逻辑
- [x] 11.4 运行 `go run cmd/gendocs/main.go` 生成新的 OpenAPI 文档
- [x] 11.5 验证生成的 `docs/openapi.yaml` 包含所有新路由且不包含旧路由
## 12. 集成测试(越权防护)
- [x] 12.1 创建 `tests/integration/account_permission_test.go`
- [x] 12.2 测试场景:企业账号访问账号管理接口被路由层拦截(返回 403
- [x] 12.3 测试场景:代理账号创建自己店铺的账号成功
- [x] 12.4 测试场景:代理账号创建下级店铺的账号成功
- [x] 12.5 测试场景:代理账号创建其他店铺的账号失败(返回 403
- [x] 12.6 测试场景:代理账号创建平台账号失败(返回 403
- [x] 12.7 测试场景:平台账号创建任意类型账号成功
- [x] 12.8 测试场景:超级管理员创建任意类型账号成功
- [x] 12.9 测试场景:查询不存在的账号返回"无权限操作该资源或资源不存在"
- [x] 12.10 测试场景:查询越权的账号返回相同错误消息
- [x] 12.11 运行 `source .env.local && go test -v ./tests/integration/account_permission_test.go` 验证所有测试通过
## 13. 集成测试(审计日志)
- [x] 13.1 创建 `tests/integration/account_audit_test.go`
- [x] 13.2 测试场景:创建账号时记录审计日志(验证 operation_type=create包含 after_data
- [x] 13.3 测试场景:更新账号时记录 before_data 和 after_data
- [x] 13.4 测试场景:删除账号时记录审计日志(验证 operation_type=delete
- [x] 13.5 测试场景:分配角色时记录审计日志(验证 operation_type=assign_roles
- [x] 13.6 测试场景:移除角色时记录审计日志(验证 operation_type=remove_role
- [x] 13.7 测试场景审计日志包含完整的操作上下文operator_id、target_account_id、request_id、ip_address
- [x] 13.8 测试场景:审计日志写入失败不影响业务操作(模拟数据库写入失败)
- [x] 13.9 运行 `source .env.local && go test -v ./tests/integration/account_audit_test.go` 验证所有测试通过
## 14. 回归测试(扩展现有测试)
- [x] 14.1 更新 `tests/integration/account_test.go`扩展覆盖所有账号类型platform/shop/enterprise
- [x] 14.2 测试场景:平台账号 CRUD 操作(原有功能保持)
- [x] 14.3 测试场景:代理账号 CRUD 操作(新增)
- [x] 14.4 测试场景:企业账号 CRUD 操作(新增)
- [x] 14.5 测试场景:角色管理功能对所有账号类型生效(新增)
- [x] 14.6 删除 `tests/integration/platform_account_test.go`(与 account_test.go 重复)
- [x] 14.7 删除 `tests/integration/shop_account_management_test.go`(功能已合并到 account_test.go
- [x] 14.8 运行 `source .env.local && go test -v ./tests/integration/account_test.go` 验证所有测试通过
## 15. 性能测试(已跳过 - 用户决定)
- [ ] ~~15.1 验证权限检查GetSubordinateShopIDs缓存命中率 > 80%~~
- [ ] ~~15.2 验证审计日志异步写入不阻塞主流程API 响应时间增加 < 1ms~~
- [ ] ~~15.3 压力测试100 并发创建账号请求P95 响应时间 < 200ms~~
- [ ] ~~15.4 压力测试100 并发查询账号列表请求P95 响应时间 < 200ms~~
- [ ] ~~15.5 验证审计日志写入性能1000 条/秒,数据库无明显压力)~~
## 16. 文档更新
- [x] 16.1 创建 `docs/account-management-refactor/迁移指南.md`(新旧路由映射表)
- [x] 16.2 创建 `docs/account-management-refactor/功能总结.md`(重构内容、安全提升、操作审计说明)
- [x] 16.3 创建 `docs/account-management-refactor/API文档.md`(所有新接口的请求/响应示例)
- [x] 16.4 更新 `README.md`:添加账号管理重构说明链接
- [x] 16.5 更新 `AGENTS.md`:添加越权防护和审计日志使用规范
## 17. 部署准备
- [ ] 17.1 生成生产环境数据库迁移脚本(包含 CREATE TABLE 和索引)
- [ ] 17.2 编写回滚方案文档(代码回滚步骤 + 数据库回滚脚本)
- [ ] 17.3 准备灰度发布计划(先部署后端,等前端更新后再切流量)
- [ ] 17.4 准备监控告警规则API 错误率 > 5%、P95 响应时间 > 300ms 自动告警)
- [ ] 17.5 编写前端对接会议 PPTBreaking Changes 说明、新旧路由映射、迁移时间表)

View File

@@ -0,0 +1,143 @@
# 账号管理接口规格
## ADDED Requirements
### Requirement: 统一账号管理路由结构
系统 SHALL 提供统一的账号管理路由,按账号类型分组。
#### Scenario: 平台账号管理路由
- **WHEN** 访问 /api/admin/accounts/platform/*
- **THEN** 提供平台账号的 CRUD + 角色管理功能
#### Scenario: 代理账号管理路由
- **WHEN** 访问 /api/admin/accounts/shop/*
- **THEN** 提供代理账号的 CRUD + 角色管理功能
#### Scenario: 企业账号管理路由
- **WHEN** 访问 /api/admin/accounts/enterprise/*
- **THEN** 提供企业账号的 CRUD + 角色管理功能
### Requirement: 所有账号类型支持完整的CRUD操作
系统 SHALL 为所有账号类型提供一致的 CRUD 功能。
#### Scenario: 创建账号
- **WHEN** POST /api/admin/accounts/{type}
- **THEN** 验证权限,创建账号,返回账号信息
#### Scenario: 查询账号列表
- **WHEN** GET /api/admin/accounts/{type}
- **THEN** 应用数据权限过滤,返回分页列表
#### Scenario: 查询账号详情
- **WHEN** GET /api/admin/accounts/{type}/:id
- **THEN** 验证权限,返回账号详情
#### Scenario: 更新账号
- **WHEN** PUT /api/admin/accounts/{type}/:id
- **THEN** 验证权限,更新账号,返回更新后信息
#### Scenario: 删除账号
- **WHEN** DELETE /api/admin/accounts/{type}/:id
- **THEN** 验证权限,软删除账号,返回成功
### Requirement: 所有账号类型支持密码和状态管理
系统 SHALL 为所有账号类型提供统一的密码和状态管理功能。
#### Scenario: 修改账号密码
- **WHEN** PUT /api/admin/accounts/{type}/:id/password
- **THEN** 验证权限更新密码bcrypt哈希返回成功
#### Scenario: 启用账号
- **WHEN** PUT /api/admin/accounts/{type}/:id/statusstatus=1
- **THEN** 验证权限,更新状态为启用,返回成功
#### Scenario: 禁用账号
- **WHEN** PUT /api/admin/accounts/{type}/:id/statusstatus=0
- **THEN** 验证权限,更新状态为禁用,返回成功
### Requirement: 所有账号类型支持角色管理
系统 SHALL 为所有账号类型提供统一的角色管理功能。
#### Scenario: 分配角色
- **WHEN** POST /api/admin/accounts/{type}/:id/rolesbody: {role_ids: [1,2]}
- **THEN** 验证权限,分配角色,返回成功
#### Scenario: 查询账号角色
- **WHEN** GET /api/admin/accounts/{type}/:id/roles
- **THEN** 验证权限,返回账号的所有角色列表
#### Scenario: 移除角色
- **WHEN** DELETE /api/admin/accounts/{type}/:id/roles/:role_id
- **THEN** 验证权限,软删除角色关联,返回成功
#### Scenario: 清空所有角色
- **WHEN** POST /api/admin/accounts/{type}/:id/rolesbody: {role_ids: []}
- **THEN** 验证权限,删除所有角色关联,返回成功
### Requirement: 删除旧路由避免冲突
系统 SHALL 删除旧的账号管理路由,避免与新路由冲突。
#### Scenario: 旧平台账号路由404
- **WHEN** 访问 POST /api/admin/platform-accounts
- **THEN** 返回 404 Not Found
#### Scenario: 旧代理账号路由404
- **WHEN** 访问 GET /api/admin/shop-accounts
- **THEN** 返回 404 Not Found
#### Scenario: 旧企业账号路由404
- **WHEN** 访问 POST /api/admin/customer-accounts
- **THEN** 返回 404 Not Found
### Requirement: 响应格式保持一致
系统 SHALL 为所有账号类型返回一致的响应格式。
#### Scenario: 创建响应包含完整账号信息
- **WHEN** 创建账号成功
- **THEN** 返回账号 ID、用户名、手机号、用户类型、状态、创建时间
#### Scenario: 列表响应包含分页信息
- **WHEN** 查询账号列表
- **THEN** 返回 {items, total, page, size}
#### Scenario: 错误响应使用统一格式
- **WHEN** 操作失败
- **THEN** 返回 {code, message, timestamp}
### Requirement: 支持按条件筛选账号列表
系统 SHALL 支持按多个条件筛选账号列表。
#### Scenario: 按用户名筛选
- **WHEN** GET /api/admin/accounts/{type}?username=张三
- **THEN** 返回用户名包含"张三"的账号列表
#### Scenario: 按手机号筛选
- **WHEN** GET /api/admin/accounts/{type}?phone=138
- **THEN** 返回手机号包含"138"的账号列表
#### Scenario: 按状态筛选
- **WHEN** GET /api/admin/accounts/{type}?status=1
- **THEN** 返回状态为启用的账号列表
#### Scenario: 按店铺ID筛选代理账号
- **WHEN** GET /api/admin/accounts/shop?shop_id=100
- **THEN** 返回 shop_id=100 的代理账号列表(需权限验证)
#### Scenario: 按企业ID筛选企业账号
- **WHEN** GET /api/admin/accounts/enterprise?enterprise_id=50
- **THEN** 返回 enterprise_id=50 的企业账号列表(需权限验证)
### Requirement: 统一Service层实现消除重复
系统 SHALL 使用单一 AccountService 处理所有账号类型,消除代码重复。
#### Scenario: AccountService处理所有账号类型
- **WHEN** 调用 AccountService.Create(ctx, req)
- **THEN** 根据 req.UserType 创建不同类型账号(平台、代理、企业)
#### Scenario: 删除ShopAccountService
- **WHEN** 系统重构完成
- **THEN** ShopAccountService 及相关文件应被删除
#### Scenario: 删除CustomerAccountService
- **WHEN** 系统重构完成
- **THEN** CustomerAccountService 及相关文件应被删除

View File

@@ -0,0 +1,105 @@
# 账号操作审计日志规格
## ADDED Requirements
### Requirement: 记录所有账号管理操作
系统 SHALL 记录所有账号管理操作,包括创建、更新、删除、角色分配和移除。
#### Scenario: 创建账号时记录审计日志
- **WHEN** 用户创建账号成功
- **THEN** 系统应异步写入审计日志包含操作人、目标账号、操作类型create、变更数据after_data
#### Scenario: 更新账号时记录变更前后数据
- **WHEN** 用户更新账号信息(用户名、手机号、状态等)
- **THEN** 系统应记录 before_data 和 after_data包含所有变更字段
#### Scenario: 删除账号时记录审计日志
- **WHEN** 用户软删除账号
- **THEN** 系统应记录删除操作包含被删除账号的完整信息before_data
#### Scenario: 分配角色时记录审计日志
- **WHEN** 用户为账号分配角色
- **THEN** 系统应记录 operation_type=assign_rolesafter_data 包含分配的角色 ID 列表
#### Scenario: 移除角色时记录审计日志
- **WHEN** 用户移除账号的角色
- **THEN** 系统应记录 operation_type=remove_role包含被移除的角色 ID
### Requirement: 审计日志包含完整的操作上下文
系统 SHALL 在审计日志中记录操作人、目标对象、变更内容和请求上下文。
#### Scenario: 记录操作人信息
- **WHEN** 记录审计日志
- **THEN** 日志应包含 operator_id、operator_type、operator_name
#### Scenario: 记录目标账号信息
- **WHEN** 记录审计日志
- **THEN** 日志应包含 target_account_id、target_username、target_user_type
#### Scenario: 记录变更数据JSON格式
- **WHEN** 记录更新操作
- **THEN** before_data 和 after_data 应为 JSONB 格式,包含完整的字段信息
#### Scenario: 记录请求上下文
- **WHEN** 记录审计日志
- **THEN** 日志应包含 request_id、ip_address、user_agent可关联访问日志
### Requirement: 异步写入不阻塞业务流程
系统 SHALL 使用 Goroutine 异步写入审计日志,确保业务操作不受审计日志性能影响。
#### Scenario: 异步写入审计日志
- **WHEN** AccountService.Create 创建账号成功
- **THEN** 主流程立即返回,审计日志在独立 Goroutine 中异步写入
#### Scenario: 写入失败只记录错误日志
- **WHEN** 审计日志写入数据库失败
- **THEN** 记录 Error 级别日志,包含完整审计信息,但不影响业务操作结果
#### Scenario: 业务响应时间不受影响
- **WHEN** 执行账号创建操作
- **THEN** API 响应时间不应因审计日志写入而增加(< 1ms
### Requirement: 操作描述使用中文
系统 SHALL 使用中文描述审计日志的操作类型和内容。
#### Scenario: 创建操作描述
- **WHEN** 记录创建账号操作
- **THEN** operation_desc 应为 "创建账号: {username}"
#### Scenario: 更新操作描述
- **WHEN** 记录更新账号操作
- **THEN** operation_desc 应为 "更新账号: {username}"
#### Scenario: 删除操作描述
- **WHEN** 记录删除账号操作
- **THEN** operation_desc 应为 "删除账号: {username}"
#### Scenario: 分配角色操作描述
- **WHEN** 记录分配角色操作
- **THEN** operation_desc 应为 "为账号 {username} 分配角色"
### Requirement: 支持按多维度查询审计日志
系统 SHALL 提供索引支持按操作人、目标账号、时间快速查询审计日志。
#### Scenario: 按操作人查询日志
- **WHEN** 查询特定操作人的所有操作记录
- **THEN** 使用 idx_account_log_operator 索引,查询时间 < 50ms
#### Scenario: 按目标账号查询日志
- **WHEN** 查询特定账号的所有操作记录
- **THEN** 使用 idx_account_log_target 索引,查询时间 < 50ms
#### Scenario: 按时间范围查询日志
- **WHEN** 查询最近7天的操作记录
- **THEN** 使用 idx_account_log_created 索引,支持倒序分页
### Requirement: 关联访问日志追溯完整请求链路
系统 SHALL 通过 request_id 关联审计日志和访问日志,支持完整链路追溯。
#### Scenario: 通过request_id关联日志
- **WHEN** 审计日志中记录 request_id="req-12345"
- **THEN** 可以在 access.log 中查询到对应的 HTTP 请求日志
#### Scenario: 追溯完整请求链路
- **WHEN** 运维人员调查某个账号创建操作
- **THEN** 通过 request_id 可以查询到:请求参数、权限检查、数据库操作、响应结果

View File

@@ -0,0 +1,127 @@
# 账号管理权限检查规格
## ADDED Requirements
### Requirement: 三层越权防护架构
系统 SHALL 实现三层越权防护机制,确保账号管理操作的安全性。
#### Scenario: 路由层中间件拦截企业账号
- **WHEN** 企业账号user_type=4访问账号管理接口/api/admin/accounts/*
- **THEN** 中间件应返回 403 错误:"无权限访问账号管理功能"
#### Scenario: Service层权限检查成功
- **WHEN** 代理账号创建自己店铺的账号
- **THEN** CanManageShop 检查应通过,账号创建成功
#### Scenario: GORM层自动过滤生效
- **WHEN** 代理账号查询账号列表
- **THEN** GORM Callback 应自动添加 `shop_id IN (当前店铺+下级店铺)` 过滤条件
### Requirement: 代理账号只能管理自己店铺及下级店铺的账号
系统 SHALL 验证代理账号对目标店铺的管理权限,禁止跨店铺越权操作。
#### Scenario: 代理创建自己店铺的账号成功
- **WHEN** 代理账号shop_id=100创建 shop_id=100 的账号
- **THEN** 权限检查通过,账号创建成功
#### Scenario: 代理创建下级店铺的账号成功
- **WHEN** 代理账号shop_id=100下级101,102创建 shop_id=101 的账号
- **THEN** GetSubordinateShopIDs 返回 [100,101,102],权限检查通过
#### Scenario: 代理创建其他店铺的账号失败
- **WHEN** 代理账号shop_id=100创建 shop_id=200 的账号
- **THEN** CanManageShop 返回错误:"无权限管理该店铺的账号",创建失败
#### Scenario: 代理创建平台账号失败
- **WHEN** 代理账号尝试创建 user_type=2 的平台账号
- **THEN** Service 层检查返回错误:"无权限创建平台账号",创建失败
### Requirement: 平台账号和超级管理员可以管理所有账号
系统 SHALL 允许平台账号和超级管理员跳过所有权限检查,管理所有账号。
#### Scenario: 平台账号创建任意类型账号
- **WHEN** 平台账号user_type=2创建代理账号user_type=3, shop_id=100
- **THEN** 权限检查跳过,账号创建成功
#### Scenario: 超级管理员创建任意类型账号
- **WHEN** 超级管理员user_type=1创建任意类型账号
- **THEN** 权限检查跳过,账号创建成功
#### Scenario: 平台账号查询所有账号
- **WHEN** 平台账号调用账号列表接口
- **THEN** GORM Callback 跳过过滤,返回所有账号
### Requirement: 企业账号禁止访问账号管理接口
系统 SHALL 禁止企业账号访问所有账号管理接口。
#### Scenario: 企业账号创建账号失败(路由层拦截)
- **WHEN** 企业账号user_type=4调用 POST /api/admin/accounts/enterprise
- **THEN** 路由层中间件返回 403 错误:"无权限访问账号管理功能"
#### Scenario: 企业账号更新账号失败Service层拦截
- **WHEN** 企业账号绕过路由层,直接调用 AccountService.Update
- **THEN** Service 层返回 403 错误:"企业账号不允许更新账号"
### Requirement: 统一错误返回防止信息泄露
系统 SHALL 在越权访问时统一返回模糊错误消息,防止攻击者判断资源是否存在。
#### Scenario: 查询不存在的账号返回模糊错误
- **WHEN** 用户查询不存在的账号 ID
- **THEN** 返回 403 错误:"无权限操作该资源或资源不存在"
#### Scenario: 查询越权的账号返回相同错误
- **WHEN** 代理账号shop_id=100查询 shop_id=200 的账号
- **THEN** 返回 403 错误:"无权限操作该资源或资源不存在"(与不存在的错误消息相同)
### Requirement: CanManageShop 权限检查函数
系统 SHALL 提供 CanManageShop 函数验证用户对目标店铺的管理权限。
#### Scenario: 验证代理对自己店铺的权限
- **WHEN** 调用 CanManageShop(ctx, 100, shopStore) 且当前用户 shop_id=100
- **THEN** 返回 nil有权限
#### Scenario: 验证代理对下级店铺的权限
- **WHEN** 调用 CanManageShop(ctx, 101, shopStore) 且当前用户 shop_id=100下级包含 101
- **THEN** GetSubordinateShopIDs 返回 [100,101,102],返回 nil有权限
#### Scenario: 验证代理对其他店铺的权限失败
- **WHEN** 调用 CanManageShop(ctx, 200, shopStore) 且当前用户 shop_id=100
- **THEN** 返回错误:"无权限管理该店铺的账号"
#### Scenario: 验证平台账号自动通过
- **WHEN** 调用 CanManageShop(ctx, 200, shopStore) 且当前用户 user_type=2平台
- **THEN** 不调用 GetSubordinateShopIDs直接返回 nil有权限
### Requirement: CanManageEnterprise 权限检查函数
系统 SHALL 提供 CanManageEnterprise 函数验证用户对目标企业的管理权限。
#### Scenario: 验证平台账号管理任意企业
- **WHEN** 调用 CanManageEnterprise(ctx, 50, enterpriseStore, shopStore) 且当前用户 user_type=2
- **THEN** 返回 nil有权限
#### Scenario: 验证代理对归属企业的权限
- **WHEN** 调用 CanManageEnterprise(ctx, 50, enterpriseStore, shopStore) 且企业 owner_shop_id=100当前用户 shop_id=100
- **THEN** 返回 nil有权限
#### Scenario: 验证代理对下级店铺企业的权限
- **WHEN** 调用 CanManageEnterprise(ctx, 50, enterpriseStore, shopStore) 且企业 owner_shop_id=101当前用户 shop_id=100下级包含 101
- **THEN** 返回 nil有权限
#### Scenario: 验证代理对其他店铺企业的权限失败
- **WHEN** 调用 CanManageEnterprise(ctx, 50, enterpriseStore, shopStore) 且企业 owner_shop_id=200当前用户 shop_id=100
- **THEN** 返回错误:"无权限管理该企业的账号"
### Requirement: 权限检查性能优化
系统 SHALL 使用 Redis 缓存优化权限检查性能,确保 API 响应时间 < 200ms。
#### Scenario: GetSubordinateShopIDs 命中缓存
- **WHEN** 调用 GetSubordinateShopIDs(ctx, 100) 且缓存存在
- **THEN** 从 Redis 读取缓存,不查询数据库,耗时 < 5ms
#### Scenario: GetSubordinateShopIDs 缓存未命中
- **WHEN** 调用 GetSubordinateShopIDs(ctx, 100) 且缓存不存在
- **THEN** 递归查询数据库,写入 Redis 缓存30分钟返回结果
#### Scenario: 权限检查总耗时 < 10ms
- **WHEN** 执行完整权限检查(包含 GetSubordinateShopIDs
- **THEN** 总耗时 < 10ms缓存命中时 < 5ms

View File

@@ -0,0 +1,86 @@
# 统一认证接口规格
## ADDED Requirements
### Requirement: 合并后台和H5认证接口
系统 SHALL 提供统一认证接口 /api/auth/*,支持后台和 H5 两种场景的认证。
#### Scenario: 后台用户登录
- **WHEN** 用户调用 POST /api/auth/loginuser_type IN (1,2,3,4)
- **THEN** 验证用户名+密码,返回 Access Token + Refresh Token
#### Scenario: H5用户登录
- **WHEN** H5 用户调用 POST /api/auth/loginuser_type IN (3,4)
- **THEN** 验证用户名+密码,返回 Access Token + Refresh Token
#### Scenario: 登出统一接口
- **WHEN** 用户调用 POST /api/auth/logout
- **THEN** 删除 Redis 中的 Token返回成功
#### Scenario: 刷新Token统一接口
- **WHEN** 用户调用 POST /api/auth/refresh-token
- **THEN** 验证 Refresh Token返回新的 Access Token
#### Scenario: 获取用户信息统一接口
- **WHEN** 用户调用 GET /api/auth/me
- **THEN** 返回当前用户信息,包含 menus 和 buttons
### Requirement: 保留个人客户认证接口
系统 SHALL 保持个人客户认证接口 /api/c/v1/* 独立,不与后台/H5认证合并。
#### Scenario: 个人客户微信授权登录
- **WHEN** 个人客户调用 POST /api/c/v1/wechat/auth
- **THEN** 使用微信 OAuth 流程,返回 JWT Token
#### Scenario: 个人客户手机号登录
- **WHEN** 个人客户调用 POST /api/c/v1/login
- **THEN** 验证手机号+验证码,返回 JWT Token
#### Scenario: 个人客户获取资料
- **WHEN** 个人客户调用 GET /api/c/v1/profile
- **THEN** 返回个人客户资料(独立数据结构)
### Requirement: 删除旧认证接口路由
系统 SHALL 删除 /api/admin/login、/api/h5/login 等旧路由,统一为 /api/auth/*。
#### Scenario: 旧后台登录接口404
- **WHEN** 用户调用 POST /api/admin/login
- **THEN** 返回 404 Not Found
#### Scenario: 旧H5登录接口404
- **WHEN** 用户调用 POST /api/h5/login
- **THEN** 返回 404 Not Found
#### Scenario: 新统一接口正常工作
- **WHEN** 用户调用 POST /api/auth/login
- **THEN** 正常认证,返回 200 OK
### Requirement: 认证逻辑保持不变
系统 SHALL 保持认证逻辑不变,只修改路由路径。
#### Scenario: Token生成逻辑不变
- **WHEN** 用户登录成功
- **THEN** 生成相同格式的 Access Token24小时和 Refresh Token7天
#### Scenario: Token存储在Redis
- **WHEN** 生成 Token
- **THEN** 存储在 RedisKey 格式为 "auth:token:{token}"
#### Scenario: 用户类型过滤不变
- **WHEN** 登录请求中包含 user_type
- **THEN** 验证用户类型是否与账号类型匹配
### Requirement: 响应格式保持兼容
系统 SHALL 保持登录响应格式兼容,包含 menus 和 buttons。
#### Scenario: 登录响应包含菜单
- **WHEN** 用户登录成功
- **THEN** 响应应包含 menus菜单树结构
#### Scenario: 登录响应包含按钮权限
- **WHEN** 用户登录成功
- **THEN** 响应应包含 buttons按钮权限列表
#### Scenario: 响应格式不变
- **WHEN** 用户登录成功
- **THEN** 响应格式应与旧接口完全一致,前端无需修改解析逻辑