Files
ViGent2/Docs/DevLogs/Day32.md
Kevin Wong 71b45852bf 更新
2026-03-04 17:35:59 +08:00

159 lines
9.3 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.
## 视频下载同源修复 + 安全漏洞第一批修复 (Day 32)
### 概述
今天的工作聚焦四件事:
1. 修复首页与发布成功弹窗点击下载时被浏览器当作在线播放(新开标签页)的问题。
2. 将下载修复开始后的开发内容从 `Day31` 拆分到 `Day32`,保持日志按天清晰归档。
3. 根据安全审计报告(`Temp/安全审计报告.md`),实施第一批 6 项无功能风险的安全修复。
4. 统一弹窗关闭交互:默认支持点空白关闭,发布成功清理弹窗保持强制留存。
---
## ✅ 1) 视频下载链路修复(避免新开标签页播放)
### 问题现象
- 首页“下载视频”与发布成功弹窗“下载视频备份”在部分浏览器会打开新标签页播放视频,而不是直接触发下载。
- 根因是跨域签名 URL 场景下,浏览器可能忽略 `<a download>`
### 修复方案
- 后端新增同源下载接口:`GET /api/videos/generated/{video_id}/download`
- 使用 `FileResponse` 返回本地视频文件
- 显式返回 `Content-Disposition: attachment`
- 浏览器直接进入保存文件流程
- 发布成功弹窗下载改为传 `videoId`,不再依赖签名 URL。
- 首页作品预览下载同步改为同源下载接口,下载行为与发布弹窗统一。
- 兼容旧清理状态:`CleanupContext` 对旧 `videoDownloadUrl` 持久化字段做 `videoId` 解析回填。
---
## ✅ 2) 配套调整与文档拆分
### 前端联动
- `CleanupContext` 继续沿用“清理失败不关弹窗、不清本地”的逻辑,下载链路仅替换为同源接口。
- 首页 `PreviewPanel` 支持传入 `generatedVideoId`,下载按钮优先走 `/api/videos/generated/{id}/download`
### 日志归档
- 将“下载修复开始后的内容”从 `Day31` 移出并归档到 `Day32`
- `Day31` 保留 Day31 当日核心内容(到 cleanup 链路加固为止)。
---
## ✅ 3) 安全漏洞第一批修复6 项,无功能风险)
根据安全审计报告,实施第一批 6 项可直接修复的安全加固项。
### 3.1 JWT 默认密钥启动拦截
- **文件**`backend/app/main.py`
- 新增 `check_jwt_secret` startup 事件(在 `init_admin` 之前)
-`JWT_SECRET_KEY` 仍为默认值 `"your-secret-key-change-in-production"` 时:
- **生产环境**`DEBUG=False``raise RuntimeError` 直接阻止服务启动
- **开发环境**`DEBUG=True`):输出 `CRITICAL` 级别日志告警,不阻止启动
### 3.2 AI / Tools 接口加认证
- **文件**`backend/app/modules/ai/router.py``backend/app/modules/tools/router.py`
- AI 路由 3 个端点(`/translate``/generate-meta``/rewrite`)均增加 `Depends(get_current_user)`
- Tools 路由 1 个端点(`/extract-script`)增加 `Depends(get_current_user)`
- 前端 axios 已有 `withCredentials: true`401 自动跳登录页,无需前端改动
### 3.3 素材路径穿越修复
- **文件**`backend/app/modules/materials/router.py``backend/app/modules/materials/service.py`
- `stream``delete_material``rename_material` 三处在 `startswith(user_id)` 校验之前新增 `..` 拒绝
-`..``material_id` 直接返回 400
- `delete_material` 路由补充 `except ValueError` → 400原先仅 catch `PermissionError``ValueError` 会被 `Exception` 兜底返回 500
### 3.4 video_id 白名单校验
- **文件**`backend/app/modules/videos/router.py`
- `download_generated``delete_generated` 两个端点在函数开头增加正则校验
- 仅允许 `^[A-Za-z0-9_-]+$`,不符合直接返回 400
### 3.5 上传/下载大小限制
- **materials/service.py**(流式上传):在 chunk 累加后检查 `MAX_UPLOAD_SIZE_MB`(默认 500MB超限抛 `ValueError`
- **ref_audios/service.py**(参考音频):`await file.read()` 后检查 5MB 上限
- **tools/service.py**(文案提取文件上传):将 `shutil.copyfileobj` 替换为分块拷贝 + 500MB 限制
- **tools/service.py**URL 下载分支):`_download_video` 返回后检查文件体积,超 500MB 删除临时文件并拒绝
### 3.6 错误信息通用化
- **ai/router.py**3 处 `detail=str(e)` 分别改为"翻译服务暂时不可用"、"生成标题标签失败"、"改写服务暂时不可用"
- **tools/router.py**:保留 "Fresh cookies" 特定分支提示fallback 改为"文案提取失败,请稍后重试"
- **generated_audios/service.py**:任务失败 `error` 字段从 `traceback.format_exc()` 改为 `str(e)`traceback 仅写入服务端日志
---
## ✅ 4) 弹窗关闭交互统一UX
### 目标
- 保持统一交互预期:业务弹窗默认可通过 `X` 与点击遮罩关闭。
- 保留关键流程保护:发布成功清理弹窗继续禁止遮罩关闭,避免误触导致流程中断。
### 调整内容
- 文案提取弹窗(`ScriptExtractionModal`)支持点击遮罩关闭。
- AI 改写弹窗(`RewriteModal`)支持点击遮罩关闭。
- 发布页扫码登录弹窗支持点击遮罩关闭。
- 修改密码弹窗支持点击遮罩关闭。
- 录音弹窗采用动态策略:`closeOnOverlay={!isRecording}`
- 未录音:允许遮罩关闭
- 录音中:禁止遮罩关闭(防误触);`X` 关闭仍可用,且会先停止录音再关闭
- 发布成功清理弹窗维持 `closeOnOverlay=false`,并且不提供 `onClose`(无右上角关闭按钮)。
---
## 📁 今日主要修改文件
| 文件 | 改动 |
|------|------|
| `backend/app/modules/videos/router.py` | 新增 `GET /api/videos/generated/{video_id}/download`,返回 `attachment` 下载响应;新增 `video_id` 白名单正则校验(`^[A-Za-z0-9_-]+$` |
| `frontend/src/features/publish/model/usePublishController.ts` | 发布成功后 `triggerCleanup()``video.id`(替换签名 URL |
| `frontend/src/shared/contexts/CleanupContext.tsx` | 下载字段改为 `videoId`;兼容旧 `videoDownloadUrl` 回填;下载按钮改同源路径 |
| `frontend/src/features/home/ui/PreviewPanel.tsx` | 首页下载改为同源下载接口 |
| `frontend/src/features/home/ui/HomePage.tsx` | 透传 `generatedVideoId``PreviewPanel` |
| `frontend/src/features/home/ui/ScriptExtractionModal.tsx` | 弹窗支持点击遮罩关闭(`closeOnOverlay` |
| `frontend/src/features/home/ui/RewriteModal.tsx` | 弹窗支持点击遮罩关闭(`closeOnOverlay` |
| `frontend/src/features/publish/ui/PublishPage.tsx` | 扫码登录弹窗支持点击遮罩关闭 |
| `frontend/src/components/AccountSettingsDropdown.tsx` | 修改密码弹窗支持点击遮罩关闭 |
| `frontend/src/features/home/ui/RefAudioPanel.tsx` | 录音弹窗改为 `closeOnOverlay={!isRecording}`(录音中禁遮罩关闭) |
| `Docs/DevLogs/Day31.md` | 移除下载修复章节与对应验证/覆盖项(迁入 Day32 |
| `Docs/TASK_COMPLETE.md` | 新增 Day32 Current 区块Day31 取消 Current |
| `Docs/BACKEND_README.md` | 补充 `/api/videos/generated/{video_id}/download` 接口说明 |
| `Docs/BACKEND_DEV.md` | 补充下载接口 `attachment` 约定 |
| `Docs/FRONTEND_README.md` | 补充首页/发布弹窗下载统一同源接口说明 |
| `Docs/FRONTEND_DEV.md` | 补充 CleanupContext 下载策略规范 |
| `Docs/PUBLISH_DEPLOY.md` | 补充发布成功后同源下载联动说明 |
| `README.md` | 补充”一键下载直达(同源 attachment”能力描述 |
| `backend/app/main.py` | `check_jwt_secret` startup 事件:生产环境(`DEBUG=False`)强拦截启动,开发环境 `CRITICAL` 告警 |
| `backend/app/modules/ai/router.py` | 3 个端点加 `Depends(get_current_user)` 认证;错误返回改为通用消息 |
| `backend/app/modules/tools/router.py` | `extract-script` 端点加 `Depends(get_current_user)` 认证;错误返回改为通用消息 |
| `backend/app/modules/materials/router.py` | `stream` 端点新增 `..` 路径穿越拒绝;`delete` 端点补充 `except ValueError` → 400 |
| `backend/app/modules/materials/service.py` | `delete_material` / `rename_material` 新增 `..` 路径穿越拒绝;流式上传增加 `MAX_UPLOAD_SIZE_MB` 大小限制 |
| `backend/app/modules/ref_audios/service.py` | 参考音频上传增加 5MB 大小限制 |
| `backend/app/modules/tools/service.py` | 文案提取文件上传替换为限大小分块拷贝500MBURL 下载分支增加下载后体积检查500MB |
| `backend/app/modules/generated_audios/service.py` | 任务失败错误字段从 `traceback.format_exc()` 改为 `str(e)`,避免泄露内部路径 |
---
## 🔍 验证记录
- `python -m py_compile backend/app/modules/videos/router.py`
- `npm run build`frontend
- `npm run build`frontend弹窗关闭策略调整后复验
- `pm2 restart vigent2-frontend`
- `pm2 restart vigent2-backend`
- `curl http://127.0.0.1:8006/health` 返回 `{"status":"ok"}`
- 安全修复第一批语法验证:`python -m py_compile backend/app/main.py backend/app/modules/materials/router.py backend/app/modules/tools/service.py backend/app/modules/ai/router.py backend/app/modules/tools/router.py backend/app/modules/materials/service.py backend/app/modules/ref_audios/service.py backend/app/modules/videos/router.py backend/app/modules/generated_audios/service.py`
- 未登录调用 `/api/ai/translate` → 返回 401 ✅
- 未登录调用 `/api/tools/extract-script` → 返回 401 ✅
- 收尾三刀语法验证:`python -m py_compile backend/app/main.py backend/app/modules/materials/router.py backend/app/modules/tools/service.py`