# Feature Specification: RBAC表结构与GORM数据权限过滤

**Feature Branch**: `004-rbac-data-permission`  
**Created**: 2025-11-17  
**Status**: Draft  
**Input**: 用户描述: "添加RBAC表结构、实现GORM租户系统(数据权限过滤)、主函数重构及路由优化"

## Clarifications

### Session 2025-11-17

- Q: 您提到creator不代表归属,未来会有"分配/分销"功能。请问数据归属和权限过滤应该如何设计? → A: 在业务表添加owner_id字段,数据权限过滤改为仅基于owner_id(忽略creator)
- Q: 如果用户的上下级关系形成循环(如A→B→C→A),递归查询下级ID时会陷入死循环。请问如何处理? → A: 不会出现循环,系统设计为:只有本级能建下级账号,parent_id在账号创建时设置且不可更改
- Q: 如果某些查询(如公开API)没有登录用户,context中没有用户ID,数据权限过滤应该如何处理? → A: 系统有两种用户:B端账号用户(accounts表,基于owner_id过滤)和C端业务用户(通过C端认证中间件识别,特定分组路由,只能查看特定业务数据,需跳过owner_id过滤使用业务字段过滤)
- Q: 如果用户层级关系有10层或更多,每次查询都递归查询所有下级ID可能影响性能。请问是否需要缓存这个下级ID列表? → A: 需要缓存到Redis,设置30分钟过期时间,账号关系变更时主动清除缓存
- Q: 如果账号A被软删除(deleted_at不为NULL),归属于账号A的数据(owner_id=A)是否仍然对A的上级可见? → A: 软删除账号后,该账号的数据对上级仍然可见(递归查询下级ID包含已删除账号)
- Q: 跨店铺数据访问控制策略 - 规格中账号表包含`shop_id`字段(店铺ID)和`owner_id`字段(数据归属者)。当用户查询业务数据时,数据权限过滤应该如何处理`shop_id`? → A: 同时使用owner_id和shop_id双重过滤(账号只能访问同店铺且归属于自己或下级的数据)
- Q: 账号密码字段的安全处理 - 账号表的`password`字段存储MD5哈希值。在查询账号信息(如列表查询、详情查询)时,返回给客户端的数据是否应该包含密码字段? → A: 查询时排除密码字段(使用GORM标签`json:"-"`或DTO过滤,任何情况不返回)
- Q: 关联表的软删除策略 - `account_roles`(账号-角色关联)和`role_permissions`(角色-权限关联)是否需要`deleted_at`字段支持软删除? → A: 需要软删除(account_roles和role_permissions都包含deleted_at字段,支持软删除和审计追踪)
- Q: 数据分配时owner_id更新和历史记录 - 当数据从用户A分配给用户B时,系统应该如何处理`owner_id`字段的更新和历史追踪? → A: 直接更新owner_id,在独立的数据变更日志表(data_transfer_log)记录分配历史(包含原owner_id、新owner_id、操作人、操作时间、原因等)
- Q: 高并发场景下的context隔离机制 - 在高并发场景下,每个请求的`context`中包含不同用户的`user_id`和`shop_id`,系统如何确保这些context不会混淆? → A: 依赖Fiber框架的请求隔离(每个请求独立的goroutine和context,通过参数显式传递,无需额外机制)

## User Scenarios & Testing *(mandatory)*

### User Story 1 - 数据库表结构和GORM模型定义 (Priority: P1)

系统需要创建5个RBAC相关的数据库表(账号、角色、权限、账号-角色、角色-权限),并定义对应的GORM模型结构体,支持层级关系和软删除。

**Why this priority**: 这是整个权限系统的数据基础,没有表结构和模型,后续的租户系统和权限功能都无法实现。

**Independent Test**: 可以通过运行数据库迁移脚本、检查表结构、创建测试数据来独立验证表和模型是否正确定义。

**Acceptance Scenarios**:

1. **Given** 数据库迁移脚本已准备, **When** 执行数据库迁移, **Then** 系统成功创建5个表(accounts、roles、permissions、account_roles、role_permissions),每个表包含所有必需字段
2. **Given** 表已创建, **When** 检查表结构, **Then** 所有表包含标准字段(id、created_at、updated_at、deleted_at、creator、updater、status)
3. **Given** 账号表已创建, **When** 检查表结构, **Then** 包含用户名、手机号、密码、用户类型、店铺ID、上级ID等字段
4. **Given** 权限表已创建, **When** 检查表结构, **Then** 支持层级关系(parent_id字段)和排序(sort字段)
5. **Given** GORM模型已定义, **When** 使用GORM创建测试数据, **Then** 数据成功插入,created_at和updated_at自动填充
6. **Given** GORM模型已定义, **When** 执行软删除操作, **Then** 记录的deleted_at字段被设置,查询时自动排除已删除记录
7. **Given** 关联表(account_roles、role_permissions)已创建, **When** 删除账号-角色或角色-权限关联, **Then** 系统执行软删除(设置deleted_at),保留审计历史

---

### User Story 2 - GORM自动数据权限过滤(租户系统) (Priority: P1)

系统在GORM查询时自动应用数据权限过滤:根据当前登录用户的ID、层级关系和店铺归属,自动添加WHERE条件,使用户只能查询归属于自己和下级且在同一店铺的数据(基于owner_id和shop_id双重过滤,而非creator字段)。root账号不受限制。

**Why this priority**: 数据权限过滤是核心安全功能,确保数据隔离,防止越权访问,必须在P1阶段完成。

**Independent Test**: 可以通过创建层级用户数据、使用不同用户身份执行查询、验证返回结果是否正确过滤来独立测试。

**Acceptance Scenarios**:

1. **Given** 用户A(ID=1,parent_id=null,user_type=root)登录, **When** 查询任意业务数据, **Then** 系统返回所有数据,不应用过滤条件
2. **Given** 用户B(ID=2,parent_id=1,shop_id=10)登录, **When** 查询数据, **Then** 系统自动添加WHERE条件:owner_id IN (2, 及所有B的下级ID) AND shop_id = 10
3. **Given** 用户C(ID=3,parent_id=2,shop_id=10)和用户D(ID=4,parent_id=2,shop_id=10), **When** 用户B(ID=2,shop_id=10)查询数据, **Then** 系统返回owner_id为2、3、4且shop_id为10的数据
4. **Given** 用户E(ID=5,parent_id=2,shop_id=20), **When** 用户B(ID=2,shop_id=10)查询数据, **Then** 系统不返回用户E创建的数据(尽管E是B的下级,但shop_id不同)
5. **Given** Store层方法接收context参数, **When** context中包含当前用户ID和shop_id, **Then** GORM自动从context提取用户ID和shop_id并应用数据权限过滤
6. **Given** 某些特殊查询需要跳过过滤, **When** 调用Store方法时传入WithoutDataFilter选项, **Then** 系统不应用数据权限过滤
7. **Given** 用户层级关系为A→B→C→D(4层), **When** 用户A查询数据, **Then** 系统正确递归查询所有下级ID(B、C、D)并结合shop_id应用过滤

---

### User Story 3 - 主函数重构和路由模块化 (Priority: P2)

将main函数中的初始化逻辑拆分为独立的辅助函数,将路由注册按业务模块拆分到internal/routes/目录下的独立文件中。

**Why this priority**: 代码组织优化提升可维护性,但不影响功能交付,可以在核心功能完成后进行。

**Independent Test**: 可以通过运行应用、验证所有现有端点正常工作、检查代码结构来独立测试。

**Acceptance Scenarios**:

1. **Given** main函数过长(200+行), **When** 重构为多个初始化函数, **Then** main函数代码行数减少至100行以内,只负责编排
2. **Given** 初始化逻辑已拆分, **When** 查看代码结构, **Then** 存在独立函数:initConfig、initLogger、initDatabase、initRedis、initQueue、initServices、initMiddleware、initRoutes
3. **Given** 路由直接写在main函数中, **When** 按模块拆分路由, **Then** 创建文件:internal/routes/routes.go(总入口)、internal/routes/user.go、internal/routes/order.go、internal/routes/health.go、internal/routes/task.go
4. **Given** 路由已模块化, **When** main函数调用routes.RegisterRoutes(app, handlers), **Then** 该函数内部调用各模块的路由注册函数
5. **Given** 代码重构完成, **When** 运行应用并测试所有现有API端点, **Then** 所有端点功能正常,无回归问题

---

### Edge Cases

- **用户上下级关系规则**: 只有本级能建下级账号(A创建B,B创建C),parent_id在账号创建时设置且不可更改,因此不会出现循环关系
- **软删除用户的数据权限**: 账号被软删除后,该账号的数据(owner_id=该账号ID)对上级仍然可见,递归查询下级ID时包含已删除账号
- **深层级性能优化**: 用户的所有下级ID列表必须缓存到Redis(30分钟过期),账号关系变更时主动清除缓存,避免每次查询都递归查询
- **C端业务用户的数据权限**: C端用户通过C端认证中间件识别(通常基于特定路由分组,如 /api/c/...),他们的数据权限过滤不使用owner_id,而是基于业务字段(如WHERE iccid = ?或WHERE device_id = ?),C端认证中间件在context中设置特定标记,触发Store层跳过owner_id过滤
- **creator字段用途**: creator字段仅用于审计追踪(记录原始创建人),不参与数据权限过滤,对吗?

## Requirements *(mandatory)*

### Functional Requirements

- **FR-001**: 系统必须创建账号表(accounts),包含字段:id、username、phone、password(MD5)、user_type(1=root,2=平台,3=代理,4=企业)、shop_id、parent_id、status(0=禁用,1=启用)、created_at、updated_at、creator、updater、deleted_at
- **FR-002**: 系统必须创建角色表(roles),包含字段:id、role_name、role_desc、role_type(1=超级,2=代理,3=企业)、status、created_at、updated_at、creator、updater、deleted_at
- **FR-003**: 系统必须创建权限表(permissions),包含字段:id、perm_name、perm_type(1=菜单,2=按钮)、url、parent_id、perm_code、sort、status、created_at、updated_at、creator、updater、deleted_at
- **FR-004**: 系统必须创建账号-角色关联表(account_roles),包含字段:id、account_id、role_id、status、created_at、updated_at、creator、updater、deleted_at
- **FR-005**: 系统必须创建角色-权限关联表(role_permissions),包含字段:id、role_id、perm_id、status、created_at、updated_at、creator、updater、deleted_at
- **FR-006**: 系统必须为每个表定义对应的GORM模型结构体(Account、Role、Permission、AccountRole、RolePermission),放置在internal/model/目录
- **FR-006.1**: 系统必须在Account模型的password字段上使用GORM标签`json:"-"`,确保查询账号信息时不返回密码哈希值给客户端
- **FR-007**: 系统必须在所有5个GORM模型(包括关联表AccountRole和RolePermission)中配置软删除支持(gorm.DeletedAt类型),支持审计追踪和撤销操作
- **FR-008**: 系统必须禁止在GORM模型中使用关联关系标签(foreignKey、references、hasMany、belongsTo等),表关联通过ID字段手动维护
- **FR-009**: 系统必须为所有业务表添加owner_id字段(INT类型,允许NULL)和shop_id字段(INT类型,允许NULL),分别表示数据归属者和店铺归属,用于数据权限过滤
- **FR-009.1**: 系统必须实现数据权限过滤机制:在Store层查询时,自动根据context中的用户ID和shop_id添加WHERE条件:(owner_id IN (...) AND shop_id = ?)
- **FR-009.2**: creator字段仅用于审计追踪(记录原始创建人),不参与数据权限过滤逻辑
- **FR-010**: 系统必须支持递归查询用户的所有下级ID:给定用户ID,查询所有直接和间接下级的ID列表
- **FR-011**: 系统必须对root账号(user_type=1)跳过数据权限过滤,允许查看所有数据
- **FR-012**: 系统必须提供WithoutDataFilter选项,允许特定查询跳过基于owner_id和shop_id的数据权限过滤(用于C端业务用户场景,改为使用业务字段如iccid/device_id进行过滤)
- **FR-013**: 系统必须通过context.Context在Handler→Service→Store之间传递当前用户ID和shop_id
- **FR-014**: 系统必须将main函数拆分为多个初始化函数,每个函数负责一项初始化任务(配置、日志、数据库等)
- **FR-015**: 系统必须将路由注册按业务模块拆分:创建internal/routes/包,包含routes.go(总入口)和各业务模块路由文件
- **FR-016**: 账号的parent_id字段在创建时设置,创建后不可更改,确保上下级关系的不变性
- **FR-017**: 只有本级账号能创建下级账号(例如A创建B,B创建C),禁止跨级创建(A不能直接创建C)
- **FR-018**: 系统必须支持两种用户体系:B端账号用户(accounts表,使用owner_id和shop_id双重数据权限过滤)和C端业务用户(通过C端认证中间件识别,在context中设置SkipOwnerFilter标记,跳过owner_id和shop_id过滤,使用业务字段过滤)
- **FR-019**: 系统必须将用户的所有下级ID列表缓存到Redis,key格式为`account:subordinates:{账号ID}`,value为下级ID列表(JSON数组),过期时间30分钟
- **FR-020**: 系统必须在账号的parent_id字段变更(虽然正常情况不可更改,但数据修复场景可能需要)或账号软删除时,主动清除相关的下级ID缓存
- **FR-021**: 递归查询用户的所有下级ID时,必须包含已软删除的账号(deleted_at不为NULL的账号仍被视为下级),确保软删除账号的数据对上级仍然可见
- **FR-022**: 系统在应用数据权限过滤时,必须同时验证owner_id和shop_id:只返回owner_id在用户的下级ID列表中且shop_id与当前用户一致的数据
- **FR-023** (🔮 未来功能): 系统将支持数据分配功能:当数据从用户A分配给用户B时,直接更新业务数据的owner_id为用户B的ID
- **FR-024** (🔮 未来功能): 系统将在数据分配时,在独立的数据变更日志表(data_transfer_log)记录分配历史,包含字段:id、table_name(业务表名)、record_id(业务数据ID)、old_owner_id(原归属者)、new_owner_id(新归属者)、operator_id(操作人)、transfer_reason(分配原因)、created_at
- **FR-025** (🔮 未来功能): 数据变更日志表(data_transfer_log)将支持查询:给定业务表和记录ID,可以查询完整的归属变更历史链
- **FR-026**: 系统必须确保每个HTTP请求的context独立隔离:通过Fiber框架的请求级goroutine和显式参数传递,禁止使用全局变量存储用户信息,确保并发请求的用户身份不会混淆

### Technical Requirements (Constitution-Driven)

**Tech Stack Compliance**:
- [x] 所有HTTP操作使用Fiber框架(禁止`net/http`快捷方式)
- [x] 所有数据库操作使用GORM(禁止`database/sql`直接调用)
- [x] 所有JSON操作使用sonic(禁止`encoding/json`)
- [x] 所有异步任务使用Asynq
- [x] 所有日志使用Zap + Lumberjack.v2
- [x] 所有配置使用Viper
- [x] 使用Go官方工具链:`go fmt`、`go vet`、`golangci-lint`

**Architecture Requirements**:
- [x] 实现遵循Handler → Service → Store → Model分层架构
- [x] 依赖通过结构体字段注入(不使用构造函数模式)
- [x] 统一错误码定义在`pkg/errors/`
- [x] 统一API响应通过`pkg/response/`
- [x] 所有常量定义在`pkg/constants/`(禁止magic numbers/strings)
- [x] **禁止硬编码值:3个以上相同字面量必须提取为常量**
- [x] **已定义的常量必须使用(禁止重复硬编码)**
- [x] **代码注释使用中文(实现注释用中文)**
- [x] **日志消息使用中文(logger.Info/Warn/Error/Debug用中文)**
- [x] **错误消息支持中文(用户可见错误有中文文本)**
- [x] 所有Redis key通过`pkg/constants/`的key生成函数管理
- [x] 包结构扁平化,按功能组织(不按层次)

**Go Idiomatic Design Requirements**:
- [x] 禁止Java风格模式:禁止getter/setter方法、禁止I-前缀接口、禁止Impl-后缀
- [x] 接口小而专注(1-3个方法),在使用方定义
- [x] 错误处理显式(返回错误,不用panic)
- [x] 使用组合(结构体嵌入)不用继承
- [x] 并发使用goroutines和channels
- [x] 命名遵循Go规范:`UserID`不是`userId`,`HTTPServer`不是`HttpServer`
- [x] 禁止匈牙利命名法或类型前缀
- [x] 代码简单直接

**API Design Requirements**:
- [x] 所有API遵循RESTful原则
- [x] 所有响应使用统一JSON格式(code/message/data/timestamp)
- [x] 所有错误消息包含错误码和双语描述
- [x] 所有分页使用标准参数(page、page_size、total)
- [x] 所有时间字段使用ISO 8601格式(RFC3339)
- [x] 所有货币金额使用整数(分)

**Performance Requirements**:
- [x] API响应时间: P95 < 200ms, P99 < 500ms
- [x] 数据库查询: P95 < 50ms, P99 < 100ms
- [x] 递归查询下级ID: P95 < 50ms, P99 < 100ms (含Redis缓存)
- [x] 批量操作使用批量查询
- [x] 列表查询实现分页(默认20,最大100)
- [x] 非实时操作委托给异步任务
- [x] 使用`context.Context`进行超时和取消控制

**Error Handling Requirements**:
- [x] 所有API错误使用统一JSON格式(通过`pkg/errors/`全局ErrorHandler)
- [x] Handler层返回错误(禁止手动`c.Status().JSON()`处理错误)
- [x] 业务错误使用`pkg/errors.New()`或`pkg/errors.Wrap()`并指定错误码
- [x] 所有错误码定义在`pkg/errors/codes.go`
- [x] 所有panic被Recover中间件捕获,转换为500响应
- [x] 错误日志包含完整请求上下文(Request ID、路径、方法、参数)
- [x] 5xx服务端错误自动脱敏(通用消息给客户端,完整错误在日志)
- [x] 4xx客户端错误可返回具体业务消息
- [x] 业务代码禁止panic(除非不可恢复的编程错误)
- [x] 错误码分类:0=成功,1xxx=客户端(4xx),2xxx=服务端(5xx)

**Testing Requirements**:
- [x] Service层业务逻辑有单元测试
- [x] 所有API端点有集成测试
- [x] 测试使用Go标准testing框架,`*_test.go`文件
- [x] 多测试用例使用table-driven tests
- [x] 测试独立运行,使用mocks/testcontainers
- [x] 目标覆盖率:70%+整体,90%+核心业务逻辑

**Database Design Requirements** (Constitution Principle):
- [x] **禁止表之间建立外键约束(Foreign Key Constraints)**
- [x] **GORM模型禁止使用ORM关联关系标签(`foreignKey`、`references`、`hasMany`、`belongsTo`等)**
- [x] **表关联通过存储关联ID字段手动维护**
- [x] **关联数据查询在代码层显式执行,不依赖ORM自动加载或预加载**
- [x] **模型结构体只包含简单字段,不包含其他模型的嵌套引用**
- [x] **数据库迁移脚本禁止外键约束定义**
- [x] **数据库迁移脚本禁止触发器维护关联数据**
- [x] **时间字段(`created_at`、`updated_at`)由GORM自动处理,不使用数据库触发器**

### Key Entities

- **Account(账号)**: 代表系统用户账号,包含身份信息(用户名、手机号、MD5密码)、类型(1=root,2=平台,3=代理,4=企业)、层级关系(上级ID)、绑定关系(店铺ID)、状态、创建人、更新人、时间戳
- **Role(角色)**: 代表权限角色,包含角色名称、角色描述、角色类型(1=超级,2=代理,3=企业)、状态、创建人、更新人、时间戳
- **Permission(权限)**: 代表系统功能权限,包含权限名称、权限类型(1=菜单,2=按钮)、URL路径、层级关系(上级ID)、权限编码、排序、状态、创建人、更新人、时间戳
- **AccountRole(账号-角色关联)**: 代表用户与角色的多对多关系,包含账号ID、角色ID、状态、创建人、更新人、时间戳
- **RolePermission(角色-权限关联)**: 代表角色与权限的多对多关系,包含角色ID、权限ID、状态、创建人、更新人、时间戳

## Success Criteria *(mandatory)*

### Measurable Outcomes

- **SC-001**: 数据库迁移脚本执行成功,5个表全部创建,包含所有必需字段,无外键约束
- **SC-002**: GORM模型定义完整,可以成功创建、查询、更新、软删除数据,created_at和updated_at自动填充
- **SC-003**: 数据权限过滤在3层用户层级下,查询响应时间增加不超过10ms(P95)
- **SC-004**: root账号(user_type=1)可以查询100%的数据,普通用户只能查询自己和下级创建且在同一店铺的数据,数据隔离准确率100%
- **SC-005**: main函数代码行数减少至100行以内,初始化逻辑拆分为至少6个独立函数
- **SC-006**: 路由按模块拆分后,每个路由文件代码行数不超过100行,职责单一
- **SC-007**: 代码重构后,运行所有现有集成测试,通过率100%,无回归问题
- **SC-008**: 数据权限过滤支持至少5层用户层级(A→B→C→D→E),递归查询下级ID性能: P95 < 50ms, P99 < 100ms
