# 文件模块审计报告

> 审查日期：2026-06-25
> 审查范围：`src/modules/files/**`、`src/app/api/upload/**`、`src/app/api/files/**`、`src/app/(dashboard)/admin/files/**`、`src/modules/settings/actions-avatar.ts`、`src/modules/settings/components/avatar-upload.tsx`
> 架构图参考：`docs/architecture/004_architecture_impact_map.md` §2.17、`docs/architecture/005_architecture_data.json`

---

## 一、现有实现概要

### 1.1 文件分布

| 层 | 路径 | 文件数 | 说明 |
|----|------|--------|------|
| 路由层 - 上传 | `src/app/api/upload/route.ts` | 1 | FormData 接收 + 直接写磁盘 + DB 落库 |
| 路由层 - 单文件 | `src/app/api/files/[id]/route.ts` | 1 | GET 查询单文件、DELETE 删除单文件 |
| 路由层 - 批量删除 | `src/app/api/files/batch-delete/route.ts` | 1 | POST 批量删除 |
| 路由层 - 管理页 | `src/app/(dashboard)/admin/files/page.tsx` | 1 | Server Component，调用 data-access 渲染 AdminFilesView |
| 模块层 - data-access | `src/modules/files/data-access.ts` | 1（305 行） | 11 个函数 + mapRow |
| 模块层 - types | `src/modules/files/types.ts` | 1（63 行） | 7 个接口/类型 |
| 模块层 - 组件 | `src/modules/files/components/` | 6 文件 | FileUpload / FileList / FilePreview / FilePreviewDialog / FileIcon / AdminFilesView |
| i18n | `messages/{en,zh-CN}/files.json` | 2 | 仅 2 个键（title、description） |
| Schema | `src/shared/db/schema.ts` §12 | - | `file_attachments` 表 + 3 个索引 |
| 权限点 | `src/shared/types/permissions.ts` | - | `FILE_UPLOAD` / `FILE_READ` / `FILE_DELETE` |

### 1.2 数据流

```
浏览器 (AdminFilesView/FileUpload/AvatarUpload)
   │  fetch /api/upload (POST FormData)
   │  fetch /api/files/[id] (DELETE)
   │  fetch /api/files/batch-delete (POST JSON)
   ▼
API 路由层（requireAuth / requirePermission）
   │  直接调用 files/data-access 函数
   ▼
files/data-access.ts → shared/db → file_attachments 表
   │  存储抽象：/api/upload 与 /api/files/[id] 直接用 fs/promises
   │            /api/files/batch-delete 用 storageProvider
```

### 1.3 架构图记录情况

`004_architecture_impact_map.md` §2.17 与 `005_architecture_data.json` 的 `modules.files` 节点对 data-access 的导出函数签名、依赖关系记录基本完整。但发现以下遗漏（详见 §五）：

- **`getFileByUrl` 函数未在 005 JSON 的 `dataAccess` 列表中记录**（实际代码存在，被 settings/actions-avatar.ts 使用）。
- **被依赖方遗漏 settings 模块**：004 §2.17 仅记录 `app/api/upload / app/api/files/[id] / app/api/files/batch-delete / homework`，遗漏 `settings/actions-avatar.ts` 通过 `getFileByUrl` + `deleteFileAttachment` 调用清理头像文件。
- **`FilePreviewDialog` 组件未在 005 JSON 的 `components` 列表中记录**。

---

## 二、现存问题与原因分析

### 2.1 三层架构违反：缺少 actions.ts 编排层（P0）

| 位置 | 问题 | 违反规则 |
|------|------|----------|
| `src/modules/files/` 目录 | 无 `actions.ts` 文件 | "每个模块标准结构：`actions.ts`（编排层）" |
| `src/app/api/upload/route.ts` L13 | `import { createFileAttachment } from "@/modules/files/data-access"` | "app/ 只能调用 modules/ 的 Server Actions 和 data-access" — API 路由虽可直调 data-access，但项目规范要求所有写操作通过 actions 编排 |
| `src/app/api/files/batch-delete/route.ts` L6-9 | 直接 import 两个 data-access 函数 | 同上 |
| `src/app/(dashboard)/admin/files/page.tsx` L7-10 | page.tsx 直接调用 data-access 函数 | 同上（应通过 actions 编排权限与数据获取） |

**原因**：模块创建时未遵循标准结构，所有数据访问被路由层与页面层直接调用，跳过 actions 编排层。

**后果**：
1. 权限校验分散在路由层，无法集中管理；
2. 无法在 actions 层统一埋点、审计日志、缓存策略；
3. 与项目其他模块（如 announcements、settings、grades 等）的 actions.ts 模式不一致，破坏一致性；
4. 无法被其他模块以 actions 形式复用（settings 模块只能 import data-access）。

### 2.2 i18n 严重缺失：UI 文本全部硬编码英文（P0）

| 位置 | 问题 | 违反规则 |
|------|------|----------|
| `messages/en/files.json`、`messages/zh-CN/files.json` | 仅 `title`、`description` 2 个键 | "所有用户可见文本必须适配 i18n" |
| `components/file-upload.tsx` L48-51、L188-191、L218、L230 | `"File is empty"` / `"Click to upload or drag and drop"` / `"Uploaded"` 等硬编码 | 同上 |
| `components/file-list.tsx` L27-28、L37、L40、L84、L104、L117 | `"No files"` / `"File deleted"` / `"Download"` / `"Delete"` 等硬编码 | 同上 |
| `components/file-preview.tsx` L71、L113-119、L207、L215 | `"Download"` / `"Office file preview not available"` / `"Load preview"` 等硬编码 | 同上 |
| `components/file-preview-dialog.tsx` L29、L47 | `"Preview"` / `"File preview ·"` 硬编码 | 同上 |
| `components/admin-files-view.tsx` L33-45、L121、L135-139、L147、L154、L177、L199、L217、L261、L271 | `TYPE_OPTIONS` 标签 + "Files"/"Total Files"/"Total Size"/"Filter by type"/"Search by file name..."/"Delete Selected"/"Deleting..." 等几十处硬编码 | 同上 |

**原因**：组件开发时直接用英文字面量，未提取翻译键；i18n 文件仅 placeholder。

**后果**：
1. 中文用户看到全英文界面（与系统其他模块本地化不一致）；
2. 切换语言无效；
3. 翻译协作无法进行；
4. 违反项目硬约束。

### 2.3 权限校验缺失：上传路由仅 requireAuth 未走 requirePermission（P0）

| 位置 | 问题 | 违反规则 |
|------|------|----------|
| `src/app/api/upload/route.ts` L31 | `const ctx = await requireAuth()` | "所有 Server Action 必须调用 `requirePermission()` 进行权限校验" |
| 同文件 L18-24 | 已定义 `FILE_UPLOAD` 权限点（permissions.ts L123），但路由层未使用 | 同上 |

**原因**：上传路由创建时仅考虑登录态校验，未将 `FILE_UPLOAD` 权限点接入；项目已定义该权限点但未消费。

**后果**：
1. 任何登录用户（含无上传权限的角色）均可调用上传接口；
2. 与 RBAC 设计意图相悖；
3. FILE_UPLOAD 权限点形同虚设。

### 2.4 横向越权：GET /api/files/[id] 仅 requireAuth（P0）

| 位置 | 问题 | 违反规则 |
|------|------|----------|
| `src/app/api/files/[id]/route.ts` L24 | `await requireAuth()` 后直接返回任意 id 的文件元数据 | "Server Action 二次校验" + 数据级别权限过滤要求 |
| 同文件 L52-58 | DELETE 仅校验 `FILE_DELETE`，未校验调用者是否拥有该文件 | 同上 |

**原因**：GET 路由未做权限分级，DELETE 未做所有权或所属 target 关联校验。

**后果**：
1. 学生可通过枚举 ID 拉取他人上传的考试附件元数据（含 mimeType、url、uploaderId 等）；
2. 通过返回的 url 即可访问文件本体（文件存储在 `/public/uploads/...`，无签名机制）；
3. 任何 `FILE_DELETE` 权限者可删除他人上传的文件；
4. **安全风险：违反 FERPA/GDPR 学生数据保护原则**。

### 2.5 缺少 loading.tsx 和 error.tsx（P1）

| 位置 | 问题 | 违反规则 |
|------|------|----------|
| `src/app/(dashboard)/admin/files/` | 仅 `page.tsx`，缺 `loading.tsx` / `error.tsx` | "All student routes must include loading.tsx and error.tsx for error boundaries"（admin 同样遵循，参考 admin/users、admin/roles） |

**原因**：路由未补齐标准文件。

**后果**：
1. 无骨架屏 → 用户首次进入白屏时间长；
2. data-access 抛错 → 整个 dashboard 布局错误（无 error boundary 隔离）。

### 2.6 类型安全：API 路由存在 `as` 断言（P1）

| 位置 | 问题 | 违反规则 |
|------|------|----------|
| `src/app/api/upload/route.ts` L100-102 | `targetType: isTargetType(...) ? (targetType as string) : null` | "禁止 `as` 断言（除类型收窄外）" |
| `src/app/api/files/batch-delete/route.ts` L23 | `const body = (await req.json().catch(() => null)) as { ids?: unknown } \| null` | 同上 |
| `src/app/api/files/batch-delete/route.ts` L23 | `body!.ids` 非空断言 | "可选链后禁止跟非空断言 `!`"（同类违规） |

**原因**：缺少 Zod schema 校验，只能用 `as` 转换未验证输入。

**后果**：类型不安全的输入直接进入业务逻辑，运行时仍可能爆炸；tsc 严格模式被绕过。

### 2.7 缺少 Zod schema 验证（P1）

| 位置 | 问题 | 违反规则 |
|------|------|----------|
| `src/modules/files/` | 无 `schema.ts` | "模块标准结构：`schema.ts`（Zod 验证）" |
| `src/app/api/upload/route.ts` L44-47 | `formData.get("file")` / `formData.get("targetType")` 未做 Zod 校验 | "输入使用 Zod 验证，验证失败返回结构化错误" |
| `src/app/api/files/batch-delete/route.ts` L22 | `body.ids` 仅做 `Array.isArray` + `typeof === "string"` 简单过滤，无长度上限/格式约束 | 同上 |

**原因**：路由层手写最小校验，未引入 Zod schema。

**后果**：
1. 大量恶意 ids 可导致 inArray 查询超长 SQL；
2. targetType 可注入任意字符串进入 DB；
3. 与项目其他模块（如 announcements、classes）的 schema.ts 模式不一致。

### 2.8 存储抽象使用不一致（P1）

| 位置 | 问题 |
|------|------|
| `src/app/api/upload/route.ts` L3-4、L80-85 | 直接 `import { mkdir, writeFile } from "fs/promises"` + `path.join(process.cwd(), "public", ...)` 写文件 |
| `src/app/api/files/[id]/route.ts` L2-3、L61-66 | 直接 `import { unlink } from "fs/promises"` 删文件 |
| `src/app/api/files/batch-delete/route.ts` L5、L37 | 使用 `storageProvider.delete()` 抽象 |
| `src/modules/settings/actions-avatar.ts` L2-3、L27-34 | 又一次直接 `import { unlink } from "fs/promises"` |

**原因**：`storageProvider` 抽象存在但只在批量删除处使用，单文件上传/删除路径未迁移。

**后果**：
1. 切换到 OSS/S3 时需要修改 3 处代码；
2. 4 个调用点对路径解析逻辑重复实现（DRY 违反）；
3. `actions-avatar.ts` 中的 `path.join(process.cwd(), "public", fileRecord.storagePath)` 与 storageProvider 的 `delete()` 行为重复，且未处理 `storagePath` 以 `/` 开头的情况。

### 2.9 FileList 组件死代码（P2）

| 位置 | 问题 |
|------|------|
| `src/modules/files/components/file-list.tsx`（125 行） | 全代码库无任何文件 import 该组件（grep `from "@/modules/files/components/file-list"` 为 0 结果）；admin-files-view.tsx 自行实现列表，未使用 FileList |

**原因**：早期创建后未接入；AdminFilesView 复刻了类似 UI。

**后果**：
1. 死代码维护负担；
2. 误导后续维护者以为有消费者；
3. 125 行无测试覆盖。

### 2.10 缺少自定义 hooks（P1）

| 位置 | 问题 | 违反规则 |
|------|------|----------|
| `src/modules/files/` | 无 `hooks/` 目录 | "模块标准结构：`hooks/`（可选）" + "逻辑复用一律抽取为自定义 hooks" |
| `components/file-upload.tsx` L43-159 | 上传逻辑（XHR + 进度 + 状态机）全部内联在组件中 | "逻辑复用一律抽取为自定义 hooks" |
| `components/file-preview.tsx` L180-233 | `TextPreview` 内部 `load` 逻辑（fetch + 错误处理）内联 | 同上 |
| `components/admin-files-view.tsx` L49-128 | 客户端筛选、批量选择、批量删除逻辑全部内联 | 同上 |

**原因**：未抽取 `useFileUpload` / `useFilePreview` / `useFileBatchOperations` 等 hooks。

**后果**：
1. 组件无法独立测试；
2. 逻辑无法在多个组件间复用（如 `FileUpload` 的进度逻辑无法被 `AvatarUpload` 复用）；
3. 单组件行数膨胀风险。

### 2.11 缺少 React Error Boundary 和 Suspense 骨架屏（P1）

| 位置 | 问题 | 违反规则 |
|------|------|----------|
| `src/app/(dashboard)/admin/files/page.tsx` | 整页同步 await，无 Suspense 包裹 | "异步数据使用 React Suspense + 骨架屏" |
| `components/admin-files-view.tsx` | 上传/批量删除错误仅 toast，无 Error Boundary 隔离失败区块 | "每个独立的数据区块必须用 React Error Boundary 包裹" |
| `components/file-preview.tsx` L188-198 | TextPreview 的 fetch 错误仅展示文本，无重试按钮 | "明确处理空数据、无权限、网络异常等边界状态" |

**原因**：未引入 React 19 的 Error Boundary 与 Suspense 流式渲染模式。

**后果**：
1. 上传失败仅 toast，用户无法定位失败任务重试；
2. 整页 await 阻塞流式渲染；
3. 网络异常时整页空白。

### 2.12 数据级别权限过滤缺失（P1）

| 位置 | 问题 | 违反规则 |
|------|------|----------|
| `src/modules/files/data-access.ts` L128-143 | `getAllFileAttachments` 不带 scope/uploaderId 过滤，返回全局数据 | "所有敏感数据查询必须在 data-access 层结合当前用户权限过滤" |
| `src/modules/files/data-access.ts` L83-103 | `getFileAttachmentsByTarget` 不校验调用者是否有权访问该 target 资源 | 同上 |
| `src/modules/files/data-access.ts` L196-234 | `getFileAttachmentsWithFilters` 不带任何权限维度 | 同上 |
| `src/app/(dashboard)/admin/files/page.tsx` L25-28 | 仅校验 `FILE_READ` 权限，未结合 scope（如教师只能看自己班级的文件） | 同上 |

**原因**：模块未引入 `DataScope` 概念（grades/classes 模块已实现）。

**后果**：FILE_READ 持有者可读全部文件元数据，与 K12 多角色数据隔离要求不符。

### 2.13 架构图遗漏：getFileByUrl 未记录（P2）

| 位置 | 问题 | 违反规则 |
|------|------|----------|
| `docs/architecture/005_architecture_data.json` `modules.files.exports.dataAccess` | 列出 10 个函数，但代码中实际有 11 个（缺 `getFileByUrl`） | "如果架构图未覆盖该模块的任何部分，必须优先补全架构图" |

**原因**：`getFileByUrl` 在 P2-13 修复时新增，未同步到 JSON。

**后果**：AI 友好数据不准确，自动化审计会遗漏该函数的依赖分析。

### 2.14 架构图遗漏：settings 模块依赖未记录（P2）

| 位置 | 问题 |
|------|------|
| `docs/architecture/004_architecture_impact_map.md` §2.17 被依赖 | 仅列 `app/api/upload / app/api/files/[id] / app/api/files/batch-delete / homework`，遗漏 `settings/actions-avatar.ts` 调用 `getFileByUrl` + `deleteFileAttachment` |

**原因**：avatar 清理功能上线时未同步架构图。

### 2.15 avatar-upload.tsx 使用 "user_avatar" 非枚举 targetType（P1）

| 位置 | 问题 | 违反规则 |
|------|------|----------|
| `src/modules/settings/components/avatar-upload.tsx` L78 | `formData.append("targetType", "user_avatar")` | "TypeScript 严格模式：禁止 `any`"（类型不一致） |
| `src/modules/files/types.ts` L2 | `FileTargetType = "exam" \| "textbook" \| "question" \| "announcement" \| "homework"` 不含 `"user_avatar"` | 类型契约违反 |
| `src/app/api/upload/route.ts` L100-102 | `isTargetType(...)` 返回 false 后 fallback 到 null，导致头像文件 `targetType=null` | 数据完整性受损 |

**原因**：头像上传复用 `/api/upload`，但用了未注册的 targetType 字符串。

**后果**：
1. 头像文件在 DB 中 targetType 字段为 null，无法按类型筛选头像；
2. 清理孤立头像文件时无法用 `getFileAttachmentsByTarget("user_avatar", ...)`；
3. 类型系统未保护跨模块字符串。

### 2.16 客户端二次筛选与硬编码 limit 200（P2）

| 位置 | 问题 |
|------|------|
| `src/app/(dashboard)/admin/files/page.tsx` L26 | `getFileAttachmentsWithFilters({ limit: 200 })` 硬编码 200，无分页 |
| `components/admin-files-view.tsx` L54-75 | 客户端 useMemo 重复执行筛选逻辑（与 data-access 的 getFileAttachmentsWithFilters 重复） |

**原因**：早期为简化实现，未做服务端分页 + URL 查询参数。

**后果**：
1. 文件数超过 200 时静默丢失；
2. 服务端筛选与客户端筛选逻辑双写、易不一致；
3. 无法被搜索引擎或分享链接复用筛选状态。

### 2.17 监控埋点缺失（P2）

| 位置 | 问题 | 违反规则 |
|------|------|----------|
| `src/app/api/upload/route.ts`、`src/app/api/files/[id]/route.ts`、`src/app/api/files/batch-delete/route.ts` | 无 `trackEvent` 调用 | "方案中预留关键操作埋点接口" |
| `src/modules/files/data-access.ts` 各写函数 | 无审计日志（其他模块如 audit、announcements 已接入 `audit-logger`） | 同上 |

**原因**：模块创建时未规划可观测性。

**后果**：无法追踪上传/删除异常、容量增长、恶意批量删除等运营事件。

### 2.18 可访问性不足（P2）

| 位置 | 问题 |
|------|------|
| `components/file-list.tsx` L70-78 | `<a>` 链接无 `aria-label`，仅靠 `title` 提供上下文 |
| `components/file-preview.tsx` L96-101 | PDF iframe 缺 `aria-label`，仅 `title` |
| `components/admin-files-view.tsx` L226-229 | 全选 Checkbox indeterminate 状态无 ARIA 描述 |
| `components/file-upload.tsx` L161-204 | 拖拽区 `role="button"` 但缺 `aria-describedby` 关联限制说明 |

---

## 三、行业差距对比

### 3.1 文件预览能力薄弱

- **行业实践**（如 PowerSchool、Canvas、阿里云教育）：Office 文档通过 Office Online Viewer / WPS 在线预览服务打开；视频支持原生 `<video>` 流式播放；音频支持波形 + 播放器。
- **当前实现**：仅支持 image / pdf / text 三类，Office 与视频、音频统一回退到下载。
- **影响**：教师上传 PPT 课件时无法快速浏览；学生作业扫描图（PNG）预览体验勉强可用，但 Office 资料无法即时查阅。

### 3.2 缺少文件分类与文件夹概念

- **行业实践**：按课程/班级/学科组织文件树；支持文件夹嵌套；提供"我的文件"/"共享文件"/"班级文件"分区。
- **当前实现**：仅靠 `targetType` + `targetId` 多态关联，无独立文件夹实体；admin/files 是单一平铺列表。
- **影响**：教师上传大量课件后无法分类组织；学生查找特定学科资料困难。

### 3.3 缺少文件版本管理

- **行业实践**：同名文件覆盖时保留历史版本；支持版本对比、回滚；显示修改人/修改时间。
- **当前实现**：每次上传生成新 cuid 文件名，无版本关联；同名文件多次上传产生多个独立记录。
- **影响**：教师更新课件后旧链接失效；无法回滚误删/误改。

### 3.4 缺少配额与存储管理

- **行业实践**：按用户/角色/班级分配存储配额；接近上限时提醒；超限拒绝上传；管理员可查看配额使用排行。
- **当前实现**：仅全局 `getFileStats` 统计总量，无 per-user 配额、无超限拦截。
- **影响**：单个用户可耗尽服务器磁盘；K12 学校存储成本不可控。

### 3.5 缺少文件分享与权限管理

- **行业实践**：生成分享链接（含过期时间、密码、查看/下载权限）；可按用户/班级精细授权；分享操作有审计日志。
- **当前实现**：所有文件 url 直接 `/uploads/...` 公开访问，无签名、无过期、无权限校验。
- **影响**：**严重安全风险**：任何知道 URL 的人（含未登录用户）均可下载；学生隐私材料（成绩单扫描件、家长联系信息）可能泄露。

### 3.6 缺少病毒扫描与内容安全

- **行业实践**：上传时通过 ClamAV / 云安全服务扫描；图片通过 NSFW 检测；扫描结果记录到 DB。
- **当前实现**：仅校验 MIME 与大小，无内容安全检测。
- **影响**：恶意文件可上传到服务器并被其他用户下载；K12 场景下未成年人保护要求更高。

### 3.7 缺少图片缩略图与 CDN 加速

- **行业实践**：上传图片后异步生成多档缩略图（48/128/512）；通过 CDN 分发；列表用小图、预览用大图。
- **当前实现**：原图直出。
- **影响**：列表页加载慢；带宽浪费；移动端体验差。

### 3.8 缺少文件使用追踪

- **行业实践**：DB 记录文件被哪些资源（exam/textbook/homework）引用；删除文件前校验引用计数；提供"孤立文件清理"任务。
- **当前实现**：仅 `targetType` + `targetId` 字段记录关联，无引用计数；删除 exam 时其附件不会自动清理。
- **影响**：磁盘孤儿文件累积；删除资源后附件残留。

### 3.9 多角色体验差距

- **教师**：无"我上传的"快捷筛选；无按班级/学科筛选；批量上传后无"批量设置 target"操作。
- **学生**：完全无入口查看自己上传的作业扫描图（仅 homework 模块内部使用）；无法预览已上传的作业附件。
- **家长**：无入口查看学校下发的通知附件；仅靠 announcement 模块内嵌预览。
- **管理员**：无"按上传者筛选"快捷入口；无"近期 7 天"快捷时间筛选；无配额管理面板。

### 3.10 缺少批量操作与企业级功能

- **行业实践**：批量移动到 target、批量设置权限、批量下载（ZIP 打包）、批量重命名、Excel 导出文件清单。
- **当前实现**：仅批量删除。
- **影响**：管理员整理文件效率低。

---

## 四、改进优先级建议

### P0（必须立即修复 — 安全与架构合规）

| # | 项 | 改进方向 |
|---|----|---------|
| P0-1 | 创建 `src/modules/files/actions.ts` 编排层 | 将 upload / delete / batchDelete / query 包装为 Server Actions（带 `requirePermission` + Zod + 审计日志），路由层改为转发到 actions |
| P0-2 | 补全 i18n 翻译键（en + zh-CN `files.json`） | 提取约 40 个翻译键，组件全部接入 `useTranslations("files")` |
| P0-3 | 修复 `/api/upload` 权限校验 | 将 `requireAuth()` 替换为 `requirePermission(Permissions.FILE_UPLOAD)` |
| P0-4 | 修复横向越权 | `GET /api/files/[id]` 加 `FILE_READ`；`DELETE /api/files/[id]` 校验调用者 ownership 或拥有 `FILE_DELETE`；data-access 新增 `assertFileOwnedBy` 辅助 |
| P0-5 | 文件 URL 签名保护（中长期） | 改为非 public 路径 + 通过 API 路由签名分发，含过期时间与权限校验 |

### P1（本轮实施 — 质量与一致性）

| # | 项 | 改进方向 |
|---|----|---------|
| P1-1 | 新增 `loading.tsx` + `error.tsx` | 骨架屏 + Error Boundary 隔离 |
| P1-2 | 新增 `src/modules/files/schema.ts` | Zod 校验 upload/batchDelete/filter 输入，移除 `as` 断言 |
| P1-3 | 抽取 hooks | `useFileUpload` / `useFileBatchOperations` / `useFilePreview`，组件瘦身 |
| P1-4 | 统一 storageProvider 调用 | upload / single delete / avatar cleanup 全部改用 `storageProvider` |
| P1-5 | 修复 user_avatar targetType | 将 `"user_avatar"` 加入 `FileTargetType` 枚举；upload route `VALID_TARGET_TYPES` 同步；avatar-upload.tsx 类型对齐 |
| P1-6 | 引入 DataScope 过滤 | data-access 的查询函数新增 `scope: DataScope` 与 `currentUserId?: string` 参数，参照 grades 模块实现 |
| P1-7 | 分区 Error Boundary + Suspense | AdminFilesView 拆为 StatsSection / UploadSection / ListSection，每区独立 Suspense + Boundary |
| P1-8 | 接入 audit-logger | upload / delete / batchDelete 记录审计日志（参照 announcements 模块） |
| P1-9 | 接入 trackEvent 埋点 | 关键操作埋点：upload_success / upload_failed / delete / batch_delete |

### P2（中长期 — 体验与企业级）

| # | 项 | 改进方向 |
|---|----|---------|
| P2-1 | 删除 FileList 死代码 | 确认无消费者后删除 125 行 |
| P2-2 | 同步架构图 | 补全 `getFileByUrl` 节点、`FilePreviewDialog` 组件、settings 依赖关系 |
| P2-3 | 服务端分页 + URL 查询参数 | 替换硬编码 limit=200，支持 `?page=&size=&type=&search=&uploader=` |
| P2-4 | a11y 增强 | 给所有链接/iframe/Checkbox 加 ARIA 标签；拖拽区 `aria-describedby` |
| P2-5 | 文件预览扩展（中长期） | 接入 Office Online Viewer / 视频原生 `<video>` / 音频播放器 |
| P2-6 | 文件版本管理（中长期） | 新增 `file_versions` 表，同名覆盖时保留历史 |
| P2-7 | 文件配额（中长期） | 新增 `user_storage_quota` 配置；data-access 校验超限；admin 配额面板 |
| P2-8 | 文件分享与签名 URL（中长期） | 文件改为非 public 存储 + 签名分发；分享链接生成/吊销 |
| P2-9 | 病毒扫描（中长期） | 接入 ClamAV 或云扫描服务；扫描结果记录到 DB |
| P2-10 | 图片缩略图 + CDN（中长期） | 异步生成多档缩略图；CDN 分发 |
| P2-11 | 引用计数与孤立文件清理（中长期） | 删除资源时级联清理附件；定期清理孤儿文件任务 |
| P2-12 | 文件夹/分类实体（中长期） | 新增 `folders` 表；支持嵌套与按班级/学科组织 |
| P2-13 | 批量操作扩展（中长期） | 批量下载 ZIP、批量移动 target、Excel 导出清单 |

### 重构设计原则遵循说明

| 原则 | 落地方式 |
|------|---------|
| 完全解耦 | data-access 仅暴露纯函数；actions 注入权限/审计；组件通过 props 接收数据（无直接 data-access import） |
| 组合优先 | AdminFilesView 拆为 `<StatsSection />` + `<UploadSection />` + `<ListSection />` 组合；逻辑全入 hooks |
| 国际化就绪 | 所有可见文本走 `useTranslations("files.*")`；提供翻译文件结构示例 |
| 最大化复用 | `useFileUpload` hook 同时被 FileUpload 与 AvatarUpload 复用；`FileIcon` 已是复用单元 |
| 错误与边界处理 | 每区独立 Error Boundary；Suspense + 骨架屏；上传任务列表保留失败项供重试 |
| 可测试性 | hooks 独立可测；data-access 纯函数；schema 可独立测试 |
| 可扩展性 | 通过 `FILES_ROLE_CONFIG` 决定各角色可见 Widget；新增角色仅改配置 |
| 企业级补充 | a11y ARIA 标签、流式 RSC、storageProvider 二次校验、audit-logger + trackEvent |

---

## 五、架构图同步说明

### 5.1 004_architecture_impact_map.md §2.17 同步项

1. **导出函数**章节：
   - 补充 `getFileByUrl` 函数（用于头像 URL 反查文件记录）
   - 修正 `getFileAttachmentsByOwner` → 实际函数名为 `getFileAttachmentsByUploader`
   - 补充 `FilePreviewDialog` 组件到组件清单
2. **依赖关系**章节：
   - 被依赖方新增 `settings`（`actions-avatar.ts` 调用 `getFileByUrl` + `deleteFileAttachment`）
3. **已知问题**章节：
   - 移除 "⚠️ P2：无 actions.ts" 标记（本轮 P0-1 实施后失效）
   - 新增 "i18n 缺失"、"权限校验缺失"、"横向越权"标记（实施后转为 ✅）

### 5.2 005_architecture_data.json 同步项

1. `modules.files.exports.dataAccess` 数组补充：
   ```json
   {
     "name": "getFileByUrl",
     "signature": "(url: string) => Promise<FileAttachment | null>",
     "file": "data-access.ts",
     "purpose": "按 URL 反查文件附件记录（用于头像等场景的旧文件清理）",
     "deps": ["shared.db", "shared.db.schema.fileAttachments"],
     "usedBy": ["settings/actions-avatar.ts"]
   }
   ```
2. `modules.files.exports.actions` 节点新增（P0-1 实施后）：
   - `uploadFileAction` / `deleteFileAction` / `batchDeleteFilesAction` / `getFileListAction` / `getFileStatsAction`
3. `modules.files.exports.components` 数组补充 `FilePreviewDialog`
4. `modules.files.dependencies.usedBy` 数组新增 `"settings"`
5. `modules.files.types` 中 `FileTargetType` 定义更新为 `"exam" | "textbook" | "question" | "announcement" | "homework" | "user_avatar"`（P1-5 实施后）

---

## 六、翻译文件结构示例（i18n 就绪）

`messages/en/files.json` 与 `messages/zh-CN/files.json` 建议结构：

```json
{
  "title": "File Management",
  "description": "View and manage all uploaded files in the system.",
  "nav": { "files": "Files" },
  "upload": {
    "title": "Click to upload or drag and drop",
    "hint": "Images, PDF, Word, Excel, PPT, Text, ZIP / RAR · up to {size}",
    "success": "{name} uploaded",
    "error": "{name}: {message}",
    "empty": "File is empty",
    "tooLarge": "File size exceeds {limit} limit",
    "invalidType": "File type {type} is not allowed",
    "uploaded": "Uploaded",
    "remove": "Remove",
    "networkError": "Network error",
    "invalidResponse": "Invalid response"
  },
  "list": {
    "empty": "No files",
    "emptyDescription": "There are no files yet.",
    "download": "Download",
    "delete": "Delete",
    "deleted": "File deleted",
    "deleteFailed": "Failed to delete file"
  },
  "preview": {
    "trigger": "Preview",
    "title": "File preview",
    "download": "Download",
    "zoomIn": "Zoom in",
    "zoomOut": "Zoom out",
    "office": {
      "title": "Office file preview not available",
      "hint": "Download the file to view its contents in your Office application."
    },
    "other": {
      "title": "Preview not available",
      "hint": "Download the file to view its contents."
    },
    "text": {
      "title": "Text file",
      "hint": "Click below to load the content.",
      "load": "Load preview",
      "loading": "Loading...",
      "error": "Failed to load text: {message}"
    }
  },
  "admin": {
    "title": "Files",
    "subtitle": "Upload and manage all files in the system.",
    "stats": {
      "totalFiles": "Total Files",
      "totalSize": "Total Size"
    },
    "filter": {
      "byType": "Filter by type",
      "search": "Search by file name...",
      "allTypes": "All Types",
      "images": "Images",
      "pdf": "PDF",
      "word": "Word",
      "wordDocx": "Word (docx)",
      "excel": "Excel",
      "excelXlsx": "Excel (xlsx)",
      "powerpoint": "PowerPoint",
      "powerpointPptx": "PowerPoint (pptx)",
      "text": "Text",
      "zip": "ZIP"
    },
    "selection": {
      "selected": "{count} selected",
      "deleteSelected": "Delete Selected",
      "deleting": "Deleting...",
      "deleted": "Deleted {count} file(s)",
      "deleteFailed": "Failed to delete files"
    },
    "empty": {
      "title": "No files found",
      "description": "Try adjusting your filters or upload a new file."
    },
    "columns": {
      "file": "File",
      "size": "Size",
      "type": "Type",
      "uploaded": "Uploaded",
      "actions": "Actions"
    }
  }
}
```

---

> 本报告由资深全栈架构师基于 2026-06-25 代码状态生成，所有 P0/P1 项将在本会话内完成实施，P2 中长期项按优先级逐步推进。
