# Jenkins 层级权限 (jenkins_acls)

---
DDS-Section: 8. 权限模型 - 8.2 Jenkins 层级权限
DDS-Lines: L558-L603
---

## 层级结构

```
Organization (组织)
    └── Repository (仓库)
            └── Branch (分支)
```

## 设计原则

| 原则 | 说明 |
|:---|:---|
| 层级继承 | 上级权限覆盖下级 |
| 存储最小化 | 一条记录可覆盖子层级 |
| 权限类型 | can_view（查看）、can_build（构建） |

## 权限表结构 (jenkins_acls)

| 字段 | 类型 | 说明 |
|:---|:---|:---|
| `id` | int64 | 主键 |
| `user_id` | int64 | 用户 ID |
| `organization_folder` | string | 组织文件夹（必填） |
| `repository_name` | string | 仓库名称（可空 = Org 级权限） |
| `branch_name` | string | 分支名称（可空 = Repo 级权限） |
| `permission_level` | string | 权限层级：org/repo/branch |
| `can_view` | bool | 是否可查看 |
| `can_build` | bool | 是否可构建 |
| `granted_by` | int64 | 授权人 ID |
| `granted_at` | time.Time | 授权时间 |

## 层级覆盖示例

| 记录 | organization | repository | branch | level | 覆盖范围 |
|:---|:---|:---|:---|:---|:---|
| 1 | cmit-dev | (null) | (null) | org | cmit-dev 下所有仓库和分支 |
| 2 | cmit-dev | rmdc-core | (null) | repo | rmdc-core 下所有分支 |
| 3 | cmit-dev | rmdc-core | main | branch | 仅 main 分支 |

## 权限缓存机制

| 层级 | 存储位置 | 说明 |
|:---|:---|:---|
| L1 | 内存 `permissionCache` | 进程内高速缓存 |
| L2 | DB `user_permission_caches` | 用户权限缓存树 JSON |

### 缓存失效

权限变更时必须同时清除 L1 和 L2 缓存：

```go
func InvalidatePermissionCache(userID int64) error {
    // 清除 L1 内存缓存
    permissionCache.Delete(userID)
    
    // 清除 L2 DB 缓存
    return userPermissionCacheDao.DeleteByUserID(userID)
}
```

## 权限检查逻辑

```go
// CheckHierarchicalPermission 检查层级权限
// 1. 先检查 Branch 级权限
// 2. 若无则检查 Repo 级权限
// 3. 若无则检查 Org 级权限
// 4. build 需要 view + build 两个权限

func CheckHierarchicalPermission(
    userID int64,
    org, repo, branch string,
    needBuild bool,
) (bool, error) {
    // 尝试从 L1 缓存获取
    if cached, ok := permissionCache.Get(userID, org, repo, branch); ok {
        return checkCachedPermission(cached, needBuild), nil
    }
    
    // L1 miss，查询 DB
    // 1. 先查 Branch 级
    if perm, err := jenkinsAclDao.FindByBranch(userID, org, repo, branch); err == nil && perm != nil {
        return checkPermission(perm, needBuild), nil
    }
    
    // 2. 再查 Repo 级
    if perm, err := jenkinsAclDao.FindByRepo(userID, org, repo); err == nil && perm != nil {
        return checkPermission(perm, needBuild), nil
    }
    
    // 3. 最后查 Org 级
    if perm, err := jenkinsAclDao.FindByOrg(userID, org); err == nil && perm != nil {
        return checkPermission(perm, needBuild), nil
    }
    
    return false, nil
}
```

## 懒加载接口

按级别逐层加载，减少首次加载数据量：

| 接口 | 返回 |
|:---|:---|
| `/api/permissions/jenkins/my-tree/organizations` | 用户有权限的组织列表 |
| `/api/permissions/jenkins/my-tree/repositories` | 指定组织下的仓库列表 |
| `/api/permissions/jenkins/my-tree/branches` | 指定仓库下的分支列表 |
