# 权限与内容去重设计文档

## 1. 权限需求

### 1.1 组织层级

| 层级 | 前缀 | 规模 | 说明 |
|------|------|------|------|
| 地区 | `A_` | ~10 | 顶层，包含多个部门 |
| 部门 | `D_` | ~3,000 | 中层，包含多个科室 |
| 科室 | `O_` | ~30,000 | 底层 |
| 角色 | `R_` | ~10 | 跨组织的功能角色 |
| 用户 | `U_` | - | 个人 |

层级关系：地区 > 部门 > 科室（ID 无规律，不可从父级推导）

### 1.2 文档权限

每个文档记录一组 `acl_ids`，使用前缀标识类型：

```
示例: ["O_02", "O_03", "D_01", "D_11", "A_00", "R_12", "U_22"]
```

- OA 系统保证：科室级文件同时记录上级部门和地区 ID
- 例如发给 O_18（属于 D_05，属于 A_01）的文件，ACL 中同时包含 O_18、D_05、A_01

### 1.3 用户可见范围

| 角色 | 可见文件 | 预估数量 |
|------|---------|---------|
| 普通科室人员 | ACL 包含所属科室 O_xx 或用户 U_xx 的文件 | 数千份 |
| 部门领导 | ACL 包含所属部门 D_xx 或用户 U_xx 的文件 | 数万份 |
| 地区领导 | ACL 包含所属地区 A_xx 或用户 U_xx 的文件 | 数十万份 |
| 角色用户 | ACL 包含所持角色 R_xx 的文件 | - |

部门领导能看到标记了 D_xx 的文件（因为科室文件已同时记录了部门 ID），无需额外展开。

### 1.4 用户身份

JWT 令牌包含完整层级信息：

```json
{
  "sub": "zhang_san",
  "office_id": "O_17",
  "dept_id": "D_05",
  "area_id": "A_01",
  "role_ids": ["R_03"]
}
```

查询时构建用户令牌：`["U_zhang_san", "O_17", "D_05", "A_01", "R_03"]`

## 2. 两层架构

```
Document 层 (gov_doc_meta)              Content 层 (gov_doc_chunks)
┌───────────────────────┐               ┌──────────────────────────┐
│ doc_id: GOV_001       │               │ content_hash: abc123     │
│ title: 关于XX的通知    │──┐            │ chunks: [0, 1, 2, ...]   │
│ issuing_org: 发改委    │  │            │ content_vector: [...]    │
│ acl_ids: [O_02, D_01] │  │  N:1       │ doc_ids: [GOV_001, 088] │
└───────────────────────┘  ├──────────► │ acl_ids: [O_02, D_01,   │ ← 并集
┌───────────────────────┐  │            │           D_05, A_01]   │
│ doc_id: GOV_088       │  │            │ title: 关于XX的通知       │ ← 代表性元数据
│ title: 转发XX通知      │──┘            │ issuing_org: 发改委       │
│ acl_ids: [D_05, A_01] │               └──────────────────────────┘
└───────────────────────┘

PDF: /data/pdfs/abc123.pdf  ← 只存一份

```


### 2.1 Document 层（业务数据）

ES 索引 `gov_doc_meta`，每个 doc_id 一条记录：
- 业务元数据：标题、发文机关、文件类型、发文日期等
- 自身 ACL：`acl_ids`（该文档的权限列表）
- 内容关联：`content_hash`（关联到 Content 层）

### 2.2 Content 层（内容数据）

ES 索引 `gov_doc_chunks`，每个 content_hash 一组 chunks：
- 文本内容 + 向量：用于 BM25 + kNN 混合搜索
- `doc_ids`：引用此内容的所有文档 ID
- `acl_ids`：所有关联文档 ACL 的并集（用于搜索过滤）
- 冗余元数据：首次入库文档的标题、机关等（用于搜索过滤和标题加权）

### 2.3 PDF 存储

按 content_hash 存储：`/data/pdfs/{content_hash}.pdf`，相同内容只存一份。

## 3. 内容去重

### 3.1 60% 文件内容重复

系统中大量文件内容相同（MD5 一致）但业务属性不同（标题、发文机关、ACL 等）。

### 3.2 去重策略

入库时计算 MD5：
- **新内容**：完整处理（解析 → 分块 → 向量化 → 索引）
- **重复内容**：跳过处理，仅新建 meta 记录并重算 chunks 的 ACL 并集

### 3.3 ACL 重算策略

所有 ACL 变更（新增/删除/权限更新）统一从 `gov_doc_meta`（权威数据源）重新计算并集，
整体覆盖 `gov_doc_chunks` 的 `acl_ids`，确保不因 ACL 重叠导致误删。

### 3.4 搜索去重

- 按 `content_hash` 分组，每组只返回一条结果
- 优先展示用户有最直接权限（U_ > O_ > D_ > A_ > R_）的版本
- 附带 `version_count` 提示其他版本数量
- 提供 `GET /document/versions/{content_hash}` 接口查看所有版本
