Files
NextEdu/docs/architecture/audit/archive/files-audit-report.md

558 lines
30 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 文件模块审计报告
> 审查日期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 中长期项按优先级逐步推进。