# Technical Design: 权限检查服务实现 ## Context 当前权限系统已实现: - ✅ RBAC 数据模型(Account - Role - Permission 多对多关联) - ✅ Store 层(AccountRoleStore, RolePermissionStore, PermissionStore) - ✅ 权限中间件框架(`pkg/middleware/permission.go`) - ❌ **权限检查核心逻辑缺失**(`CheckPermission` 方法仅为占位) **目标**:补全权限检查服务,使权限中间件能够正常工作。 **约束**: - 严格遵循项目四层架构(Handler → Service → Store → Model) - 不引入外部依赖或框架 - 保持 Go 惯用模式,避免过度抽象 ## Goals / Non-Goals ### Goals 1. 实现完整的权限检查逻辑(账号 → 角色 → 权限链式查询) 2. 支持 platform 参数过滤(all/web/h5) 3. 超级管理员自动通过所有权限检查 4. 提供单元测试和集成测试覆盖 ### Non-Goals 1. ❌ 不实现基于资源的权限(RBAC 仅支持功能权限) 2. ❌ 不实现权限继承或权限组(保持简单) 3. ❌ 不实现动态权限(权限在数据库中静态配置) ## Decisions ### Decision 1: 依赖注入方式 **选择**: 在 `PermissionService` 结构体中注入 `AccountRoleStore` 和 `RolePermissionStore` **理由**: - ✅ 符合项目依赖注入模式(通过结构体字段注入) - ✅ 避免循环依赖(Service 依赖 Store,Store 不依赖 Service) - ✅ 便于单元测试(可 mock Store 层) **替代方案**: - ❌ 方案 A: 在 PermissionStore 中添加聚合查询方法 - 缺点:违反 Store 层单一职责原则 - ❌ 方案 B: 使用全局变量或服务定位器 - 缺点:违反项目依赖注入原则 ### Decision 2: 超级管理员权限处理 **选择**: 在 `CheckPermission` 方法开头检查用户类型,超级管理员直接返回 true **理由**: - ✅ 性能优化:避免无意义的数据库查询 - ✅ 业务语义:超级管理员拥有所有权限 - ✅ 与现有数据权限逻辑一致(数据权限也跳过超级管理员) **实现**: ```go func (s *Service) CheckPermission(ctx, userID, permCode, platform) (bool, error) { // 1. 检查用户类型(需要从 context 或通过 AccountStore 查询) userType := middleware.GetUserTypeFromContext(ctx) if userType == constants.UserTypeSuperAdmin { return true, nil } // 2. 常规权限检查流程 // ... } ``` ### Decision 3: Platform 匹配逻辑 **选择**: 权限的 `platform` 字段支持三种值:`all`(任意端),`web`(仅后台),`h5`(仅 H5) **匹配规则**: ```go // 权限匹配条件: // 1. permCode 完全匹配 // 2. platform 匹配: // - permission.platform == "all" → 任意 platform 参数都匹配 // - permission.platform == platform → 精确匹配 ``` **示例**: ``` 查询权限: CheckPermission(userID, "user:create", "web") 权限数据库: - {permCode: "user:create", platform: "all"} → ✅ 匹配 - {permCode: "user:create", platform: "web"} → ✅ 匹配 - {permCode: "user:create", platform: "h5"} → ❌ 不匹配 ``` ### Decision 4: 缓存策略(可选实现) **选择**: 第一阶段**不实现**缓存,保持简单 **理由**: - ✅ 权限查询频率不高(仅在请求进入时检查一次) - ✅ 权限数据量小(通常 < 100 条权限,< 10 个角色/用户) - ✅ PostgreSQL 查询性能足够(3 次简单查询 < 10ms) - ✅ 避免缓存失效复杂性(角色变更时需清除所有关联用户缓存) **未来优化方向**: - 如果性能测试发现瓶颈,可添加 Redis 缓存 - 缓存粒度:`permission:user:{userID}:perms` → `[]string` (权限编码列表) - 过期时间:5 分钟 - 失效策略:角色分配/权限分配变更时,清除相关用户缓存 ## Implementation Details ### 核心算法流程 ```go func (s *Service) CheckPermission(ctx context.Context, userID uint, permCode string, platform string) (bool, error) { // 步骤 1: 检查超级管理员 userType := middleware.GetUserTypeFromContext(ctx) if userType == constants.UserTypeSuperAdmin { return true, nil } // 步骤 2: 查询用户的角色 ID 列表 roleIDs, err := s.accountRoleStore.GetRoleIDsByAccountID(ctx, userID) if err != nil { return false, fmt.Errorf("failed to get user roles: %w", err) } if len(roleIDs) == 0 { return false, nil // 用户无角色,无权限 } // 步骤 3: 查询角色的权限 ID 列表(去重) permIDs, err := s.rolePermStore.GetPermIDsByRoleIDs(ctx, roleIDs) if err != nil { return false, fmt.Errorf("failed to get role permissions: %w", err) } if len(permIDs) == 0 { return false, nil // 角色无权限 } // 步骤 4: 查询权限详情 permissions, err := s.permissionStore.GetByIDs(ctx, permIDs) if err != nil { return false, fmt.Errorf("failed to get permissions: %w", err) } // 步骤 5: 遍历匹配 permCode 和 platform for _, perm := range permissions { if perm.PermCode == permCode { // platform 匹配规则 if perm.Platform == constants.PlatformAll || perm.Platform == platform { return true, nil } } } return false, nil // 未找到匹配的权限 } ``` ### 数据库查询分析 **查询次数**: 3 次 1. `AccountRoleStore.GetRoleIDsByAccountID()` - 1 次查询 2. `RolePermissionStore.GetPermIDsByRoleIDs()` - 1 次查询(带去重) 3. `PermissionStore.GetByIDs()` - 1 次查询(批量) **预估性能**: - 单次权限检查 < 10ms(本地数据库) - 单次权限检查 < 20ms(远程数据库) **优化空间**: - 如果未来需要,可添加缓存层减少查询次数 - 数据库索引已存在(`account_id`, `role_id`, `perm_id`) ## Risks / Trade-offs ### Risk 1: 多次数据库查询影响性能 **影响**: 每次权限检查需要 3 次数据库查询 **缓解**: - ✅ 查询简单,使用索引,性能可接受 - ✅ 权限检查仅在请求入口执行一次(不在业务逻辑中频繁调用) - ✅ 如果未来需要,可添加缓存层 **监控**: - 在日志中记录权限检查耗时 - 生产环境监控 API 响应时间 ### Risk 2: 角色或权限变更后权限立即生效 **现状**: 不使用缓存,权限变更立即生效 **影响**: - ✅ 无缓存一致性问题 - ❌ 性能稍低(可接受) **未来优化**: - 如果添加缓存,需实现缓存失效机制 ## Migration Plan ### 部署步骤 1. **代码部署**: - 合并代码到主分支 - 部署 API 服务 2. **验证**: - 运行集成测试 - 手动测试权限中间件 3. **激活权限中间件**(可选): - 在需要权限控制的路由上添加 `RequirePermission` 中间件 - 逐步启用,监控错误率 ### Rollback Plan - ✅ 无破坏性变更,可直接回滚代码 - ✅ 权限中间件默认未启用,不影响现有功能 ## Open Questions 1. **是否需要添加权限检查日志审计?** - 当前设计:仅在错误时记录日志 - 可选:记录所有权限检查结果(包括成功)用于安全审计 - **决策**: 暂不实现,避免日志量过大 2. **是否需要支持权限继承?** - 当前设计:权限扁平化,不支持继承 - 可选:支持父级权限自动包含子级权限 - **决策**: 暂不实现,保持简单 3. **是否需要支持权限否定(黑名单)?** - 当前设计:仅支持白名单(有权限才能访问) - 可选:支持明确拒绝某些权限 - **决策**: 暂不实现,通过角色分配控制即可