docs(audit): add audit reports for grades, homework, lesson-preparation, messaging, permissions, question-bank, settings, textbooks

- Add grades-audit-report

- Add homework-audit-report and homework-exams-audit-report

- Add lesson-preparation-audit-report-v3 and v4

- Add messaging-audit-report

- Add permissions-audit-report

- Add question-bank-audit-report

- Add settings-profile-audit-report-v3

- Add textbooks-audit-report-v3
This commit is contained in:
SpecialX
2026-07-03 10:23:34 +08:00
parent 365c36d97b
commit 89b9e181d2
29 changed files with 13009 additions and 230 deletions

View File

@@ -0,0 +1,557 @@
# 文件模块审计报告
> 审查日期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` | 1305 行) | 11 个函数 + mapRow |
| 模块层 - types | `src/modules/files/types.ts` | 163 行) | 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 未走 requirePermissionP0
| 位置 | 问题 | 违反规则 |
|------|------|----------|
| `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] 仅 requireAuthP0
| 位置 | 问题 | 违反规则 |
|------|------|----------|
| `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.tsxP1
| 位置 | 问题 | 违反规则 |
|------|------|----------|
| `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 缺少自定义 hooksP1
| 位置 | 问题 | 违反规则 |
|------|------|----------|
| `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" 非枚举 targetTypeP1
| 位置 | 问题 | 违反规则 |
|------|------|----------|
| `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 200P2
| 位置 | 问题 |
|------|------|
| `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 BoundarySuspense + 骨架屏;上传任务列表保留失败项供重试 |
| 可测试性 | 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 中长期项按优先级逐步推进。