# 统一资源权限控制使用说明

## 📋 概述

采用**统一权限表** `tb_resource_permission` 来管理所有资源（新闻、课程、任务等）的访问权限，支持基于**部门**和**角色**的细粒度权限控制。

## 🎯 设计优势

1. ✅ **统一管理**：所有资源类型的权限在一张表中管理
2. ✅ **灵活控制**：支持部门、角色、部门+角色三种授权方式
3. ✅ **细粒度**：支持读、写、执行三种权限级别
4. ✅ **易扩展**：新增资源类型只需添加类型编号
5. ✅ **高性能**：合理的索引设计，查询效率高

## 📊 表结构

```sql
tb_resource_permission (
  id                VARCHAR(50)     主键
  resource_type     INT(4)          资源类型
  resource_id       VARCHAR(50)     资源ID
  dept_id           VARCHAR(50)     部门ID（可为NULL）
  role_id           VARCHAR(50)     角色ID（可为NULL）
  can_read          TINYINT(1)      读权限
  can_write         TINYINT(1)      写权限
  can_execute       TINYINT(1)      执行权限
  creator           VARCHAR(50)     创建者
  create_time       TIMESTAMP       创建时间
  ...
)
```

## 🏷️ 资源类型编码

| 类型值 | 资源类型 | 说明 |
|--------|----------|------|
| 1 | 资源/新闻 | tb_resource |
| 2 | 课程 | tb_course |
| 3 | 课程章节 | tb_course_chapter |
| 4 | 学习任务 | tb_learning_task |

*可根据业务需要扩展更多类型*

## 🔐 权限组合说明

### 部门和角色组合

| dept_id | role_id | 含义 | 应用场景 |
|---------|---------|------|----------|
| NULL | NULL | 公开 | 所有人可访问 |
| 有值 | NULL | 部门限制 | 该部门所有人可访问 |
| NULL | 有值 | 角色限制 | 该角色所有人可访问（跨部门） |
| 有值 | 有值 | 部门+角色 | 特定部门的特定角色可访问 |

### 权限级别说明

| 权限字段 | 含义 | 典型操作 |
|---------|------|----------|
| can_read | 读权限 | 查看、浏览、阅读、下载 |
| can_write | 写权限 | 编辑、修改、删除 |
| can_execute | 执行权限 | 发布、审核、归档、推荐 |

## 💡 使用示例

### 示例1：公开资源

```sql
-- 所有人都可以阅读的公开资源
INSERT INTO tb_resource_permission (
  id, resource_type, resource_id, 
  dept_id, role_id,
  can_read, can_write, can_execute,
  creator, create_time
) VALUES (
  '1', 1, 'res_001',
  NULL, NULL,  -- 部门和角色都为NULL表示公开
  1, 0, 0,     -- 只有读权限
  'admin', NOW()
);
```

### 示例2：部门限制

```sql
-- 只有"党委办公室"部门可以访问的资源
INSERT INTO tb_resource_permission (
  id, resource_type, resource_id,
  dept_id, role_id,
  can_read, can_write, can_execute,
  creator, create_time
) VALUES (
  '2', 1, 'res_002',
  'dept_party_office', NULL,  -- 指定部门
  1, 0, 0,
  'admin', NOW()
);
```

### 示例3：角色限制（跨部门）

```sql
-- 所有"管理员"角色可以编辑的课程（不限部门）
INSERT INTO tb_resource_permission (
  id, resource_type, resource_id,
  dept_id, role_id,
  can_read, can_write, can_execute,
  creator, create_time
) VALUES (
  '3', 2, 'course_001',
  NULL, 'admin',  -- 只指定角色
  1, 1, 0,        -- 读写权限
  'admin', NOW()
);
```

### 示例4：部门+角色组合

```sql
-- "教务处"部门的"教师"角色可以管理的课程
INSERT INTO tb_resource_permission (
  id, resource_type, resource_id,
  dept_id, role_id,
  can_read, can_write, can_execute,
  creator, create_time
) VALUES (
  '4', 2, 'course_002',
  'dept_academic', 'teacher',  -- 部门+角色
  1, 1, 1,                     -- 全部权限
  'admin', NOW()
);
```

### 示例5：多个授权组合

同一个资源可以有多条权限记录：

```sql
-- 示例：一个课程有多种访问权限
-- 1. 所有人可以查看
INSERT INTO tb_resource_permission VALUES 
  ('5', 2, 'course_003', NULL, NULL, 1, 0, 0, 'admin', NOW());

-- 2. "教师"角色可以编辑
INSERT INTO tb_resource_permission VALUES 
  ('6', 2, 'course_003', NULL, 'teacher', 1, 1, 0, 'admin', NOW());

-- 3. "教务处"的"管理员"可以发布
INSERT INTO tb_resource_permission VALUES 
  ('7', 2, 'course_003', 'dept_academic', 'admin', 1, 1, 1, 'admin', NOW());
```

## 🔍 权限查询

### 查询1：检查用户对资源的访问权限

```sql
-- 检查用户是否可以访问某个资源
-- 假设：用户部门='dept_001'，角色='teacher'，资源类型=2，资源ID='course_001'

SELECT 
  MAX(can_read) as has_read,
  MAX(can_write) as has_write,
  MAX(can_execute) as has_execute
FROM tb_resource_permission
WHERE resource_type = 2
  AND resource_id = 'course_001'
  AND deleted = 0
  AND (
    -- 公开资源
    (dept_id IS NULL AND role_id IS NULL)
    OR
    -- 部门匹配
    (dept_id = 'dept_001' AND role_id IS NULL)
    OR
    -- 角色匹配
    (dept_id IS NULL AND role_id = 'teacher')
    OR
    -- 部门+角色匹配
    (dept_id = 'dept_001' AND role_id = 'teacher')
  );
```

### 查询2：获取用户可访问的资源列表

```sql
-- 获取用户可以访问的所有课程
-- 假设：用户部门='dept_001'，角色列表=['teacher', 'user']

SELECT DISTINCT rp.resource_id, c.*
FROM tb_resource_permission rp
INNER JOIN tb_course c ON c.course_id = rp.resource_id
WHERE rp.resource_type = 2
  AND rp.can_read = 1
  AND rp.deleted = 0
  AND (
    (rp.dept_id IS NULL AND rp.role_id IS NULL)
    OR (rp.dept_id = 'dept_001' AND rp.role_id IS NULL)
    OR (rp.dept_id IS NULL AND rp.role_id IN ('teacher', 'user'))
    OR (rp.dept_id = 'dept_001' AND rp.role_id IN ('teacher', 'user'))
  )
ORDER BY c.create_time DESC;
```

### 查询3：获取资源的所有权限配置

```sql
-- 查看某个资源配置了哪些权限
SELECT 
  rp.*,
  d.name as dept_name,
  r.name as role_name
FROM tb_resource_permission rp
LEFT JOIN tb_sys_dept d ON d.dept_id = rp.dept_id
LEFT JOIN tb_sys_role r ON r.role_id = rp.role_id
WHERE rp.resource_type = 1
  AND rp.resource_id = 'res_001'
  AND rp.deleted = 0;
```

## 🎨 业务场景实现

### 场景1：新闻资源的分级查看

```sql
-- 1. 公开新闻：所有人可查看
INSERT INTO tb_resource_permission VALUES 
  (UUID(), 1, 'news_public', NULL, NULL, 1, 0, 0, 'admin', NOW());

-- 2. 内部新闻：全体员工可查看
INSERT INTO tb_resource_permission VALUES 
  (UUID(), 1, 'news_internal', NULL, 'employee', 1, 0, 0, 'admin', NOW());

-- 3. 机密新闻：只有党委办公室可查看
INSERT INTO tb_resource_permission VALUES 
  (UUID(), 1, 'news_confidential', 'dept_party', NULL, 1, 0, 0, 'admin', NOW());
```

### 场景2：课程权限分配

```sql
-- 1. 所有学生可以学习
INSERT INTO tb_resource_permission VALUES 
  (UUID(), 2, 'course_101', NULL, 'student', 1, 0, 0, 'admin', NOW());

-- 2. 授课教师可以编辑
INSERT INTO tb_resource_permission VALUES 
  (UUID(), 2, 'course_101', NULL, 'teacher', 1, 1, 0, 'admin', NOW());

-- 3. 教务处管理员可以发布
INSERT INTO tb_resource_permission VALUES 
  (UUID(), 2, 'course_101', 'dept_academic', 'admin', 1, 1, 1, 'admin', NOW());
```

### 场景3：学习任务的部门分配

```sql
-- 为不同部门分配不同的学习任务
-- 党委办公室的任务
INSERT INTO tb_resource_permission VALUES 
  (UUID(), 4, 'task_party', 'dept_party', NULL, 1, 0, 1, 'admin', NOW());

-- 教务处的任务
INSERT INTO tb_resource_permission VALUES 
  (UUID(), 4, 'task_academic', 'dept_academic', NULL, 1, 0, 1, 'admin', NOW());
```

## 📝 Java代码示例

### 权限检查工具类

```java
public class ResourcePermissionChecker {
    
    /**
     * 检查用户是否有资源访问权限
     */
    public boolean hasPermission(String userId, int resourceType, 
                                String resourceId, PermissionType type) {
        // 1. 获取用户的部门和角色
        UserInfo user = getUserInfo(userId);
        List<String> roleIds = user.getRoleIds();
        String deptId = user.getDeptId();
        
        // 2. 查询权限
        List<ResourcePermission> permissions = permissionMapper.selectList(
            new QueryWrapper<ResourcePermission>()
                .eq("resource_type", resourceType)
                .eq("resource_id", resourceId)
                .eq("deleted", 0)
                .and(wrapper -> wrapper
                    // 公开
                    .or(w -> w.isNull("dept_id").isNull("role_id"))
                    // 部门匹配
                    .or(w -> w.eq("dept_id", deptId).isNull("role_id"))
                    // 角色匹配
                    .or(w -> w.isNull("dept_id").in("role_id", roleIds))
                    // 部门+角色匹配
                    .or(w -> w.eq("dept_id", deptId).in("role_id", roleIds))
                )
        );
        
        // 3. 判断权限
        for (ResourcePermission perm : permissions) {
            switch (type) {
                case READ: if (perm.getCanRead()) return true; break;
                case WRITE: if (perm.getCanWrite()) return true; break;
                case EXECUTE: if (perm.getCanExecute()) return true; break;
            }
        }
        
        return false;
    }
    
    /**
     * 获取用户可访问的资源ID列表
     */
    public List<String> getAccessibleResources(String userId, int resourceType) {
        UserInfo user = getUserInfo(userId);
        // ... 类似逻辑
        return resourceIds;
    }
}
```

### 权限注解

```java
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequirePermission {
    ResourceType resourceType();
    PermissionType permission() default PermissionType.READ;
}

// 使用示例
@RequirePermission(resourceType = ResourceType.COURSE, permission = PermissionType.WRITE)
public void updateCourse(String courseId, CourseDTO dto) {
    // ...
}
```

## ⚠️ 注意事项

1. **NULL值处理**：dept_id 和 role_id 为 NULL 时有特殊含义，不能用空字符串代替
2. **权限优先级**：查询时使用 MAX() 聚合，任一条记录授权即有权限
3. **索引优化**：已创建组合索引 `idx_resource`，确保查询性能
4. **软删除**：使用 deleted 字段，避免物理删除导致的问题
5. **唯一约束**：`uk_resource_dept_role` 防止重复配置，包含 deleted 字段支持重复添加

## 🔄 权限变更场景

### 场景1：资源从公开改为部门限制

```sql
-- 1. 删除原公开权限
UPDATE tb_resource_permission 
SET deleted = 1, delete_time = NOW()
WHERE resource_type = 1 
  AND resource_id = 'res_001'
  AND dept_id IS NULL AND role_id IS NULL;

-- 2. 添加部门限制
INSERT INTO tb_resource_permission VALUES 
  (UUID(), 1, 'res_001', 'dept_target', NULL, 1, 0, 0, 'admin', NOW());
```

### 场景2：提升用户权限（从只读到读写）

```sql
-- 更新权限级别
UPDATE tb_resource_permission 
SET can_write = 1, 
    updater = 'admin',
    update_time = NOW()
WHERE resource_type = 2
  AND resource_id = 'course_001'
  AND role_id = 'teacher';
```

## 📈 性能优化建议

1. **合理使用索引**：已创建的索引包括 `idx_resource`、`idx_dept`、`idx_role`
2. **缓存策略**：对频繁访问的资源权限进行Redis缓存
3. **批量查询**：使用 IN 子句批量查询多个资源的权限
4. **避免全表扫描**：查询时务必带上 resource_type 和 resource_id

## 🔗 相关表说明

- `tb_sys_dept`: 部门表
- `tb_sys_role`: 角色表
- `tb_sys_user_dept_role`: 用户-部门-角色关联表
- `tb_resource`: 资源表
- `tb_course`: 课程表
- `tb_learning_task`: 学习任务表

