# 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. **是否需要支持权限否定（黑名单）？**
   - 当前设计：仅支持白名单（有权限才能访问）
   - 可选：支持明确拒绝某些权限
   - **决策**: 暂不实现，通过角色分配控制即可
