# 项目权限 (project_acls)

---
DDS-Section: 8. 权限模型 - 8.4 项目权限
DDS-Lines: L666-L691
---

## 设计原则

| 原则 | 说明 |
|:---|:---|
| 权限粒度 | 模块级 |
| SuperAdmin | 默认拥有所有权限 |
| 项目填写人 | 自动获得非授权模块的 view 权限 |

## 模块代码与 JSONB 映射

| 模块代码 | JSONB 字段 | 说明 |
|:---|:---|:---|
| `basic_info` | `projects.basic_info` | 省份、城市、联系人等 |
| `business_info` | `projects.deploy_business` | 部署人、版本、入口等 |
| `environment_info` | `projects.deploy_env` | 主机、网络、管理方式等 |
| `middleware_info` | `projects.deploy_middleware` | MySQL/Redis/EMQX 等 |
| `authorization_info` | `project_auth_configs.*` | TOTP 授权（仅 SuperAdmin） |

## 权限表结构 (project_acls)

| 字段 | 类型 | 说明 |
|:---|:---|:---|
| `id` | int64 | 主键 |
| `user_id` | int64 | 用户 ID |
| `project_id` | string | 项目 ID |
| `module_code` | string | 模块代码 |
| `can_view` | bool | 是否可查看 |
| `can_export` | bool | 是否可导出 |
| `granted_by` | int64 | 授权人 ID |
| `granted_at` | time.Time | 授权时间 |

## 索引设计

| 索引名 | 字段 | 用途 |
|:---|:---|:---|
| `idx_project_acl_user` | user_id | 用户维度查询 |
| `idx_project_acl_project` | project_id, module_code | 项目+模块维度查询 |

## 权限检查规则

```go
// CheckProjectModulePermission 检查项目模块权限
func CheckProjectModulePermission(
    ctx context.Context,
    userID int64,
    userRole string,
    projectID string,
    moduleCode string,
    permissionType string, // "view" or "export"
) (bool, error) {
    // Rule 1: SuperAdmin 拥有所有权限
    if userRole == "superadmin" {
        return true, nil
    }
    
    // Rule 2: authorization_info 模块仅 SuperAdmin 可访问
    if moduleCode == "authorization_info" {
        return false, nil
    }
    
    // Rule 3: 项目填写人自动拥有非授权模块的 view 权限
    if permissionType == "view" {
        fillerID, err := businessInfoRegistry.GetProjectFillerID(ctx, projectID)
        if err == nil && fillerID == userID {
            return true, nil
        }
    }
    
    // Rule 4: 查询 project_acls 表
    acl, err := projectAclDao.Find(userID, projectID, moduleCode)
    if err != nil {
        return false, err
    }
    if acl == nil {
        return false, nil
    }
    
    switch permissionType {
    case "view":
        return acl.CanView, nil
    case "export":
        return acl.CanExport, nil
    default:
        return false, nil
    }
}
```
