diff --git a/.gitea/workflows/planning-ci.yml b/.gitea/workflows/planning-ci.yml new file mode 100644 index 0000000..f7efaf7 --- /dev/null +++ b/.gitea/workflows/planning-ci.yml @@ -0,0 +1,27 @@ +name: vscode-planning-ci + +# I-02 · 本仓 CI(Gitea Actions / GitHub Actions 兼容:纯 push/pull_request 触发,无私有凭据)。 +on: + push: + pull_request: + +jobs: + planning-smoke: + runs-on: ubuntu-latest + defaults: + run: + working-directory: PLANNING + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: 调度器只读命令 + run: | + python tasks.py list + python tasks.py status + python tasks.py collect + - name: CI 烟雾测试 + run: | + pip install pytest -q + python -m pytest tools/test_ci_smoke.py -q diff --git a/.gitignore b/.gitignore index 8a0b63b..eb4d9c9 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ chunyu_project_react/ node_modules/ shots/ docker.env +backups/ diff --git a/PLANNING/PROGRESS_C-02.md b/PLANNING/PROGRESS_C-02.md index 33d8eb0..8e21cac 100644 --- a/PLANNING/PROGRESS_C-02.md +++ b/PLANNING/PROGRESS_C-02.md @@ -1,42 +1,44 @@ -# PROGRESS_C-02 · 工具热榜 + 最近使用 - -> 日期 2026-09-12/13 · 范围:可乐工具(`chunyu_project/tool` + `chunyu_project_react`) - -## 现状 vs 差距(接手时) -- 已有:`ToolUsageRecord` 模型+迁移、`ToolTopView`(仅 `period=total|week`)、`increment` 双写、Home 热榜横条(`period=total`)。 -- 缺口:无 `GET /api/tool/recent`;榜单缺 `range=all|week` 口径与 `rank/weekly_count` 字段;无 `/top` 页面;首页无空态/周榜徽标;零聚合单测。 - -## 改动 -后端(`chunyu_project/tool/`): -1. `views/tool_manage_view.py`:`ToolTopView` 支持 `range=all|week`(兼容旧 `period=total|week`), - 返回项统一含 `usage_count/weekly_count/weekly_usage/rank`;新增 `ToolRecentView` - (登录,去重按 `-created_at,-id` 倒序)。 -2. `views/__init__.py`、`urls.py`:挂载 `path('recent/', ToolRecentView)`。 -3. `management/commands/seed_tools.py`:新增 `AI 工具` 分类(给 C-04 用)+ `AI 图片放大` / - `代码在线运行` 两个工具种子。 -4. `tests/test_tool_top.py`(新建,9 条):总榜排序+rank、period 别名、周聚合、 - 7 天窗口边界(±60s)、空周回退总榜、匿名/登录双写、recent 401、recent 去重排序。 - -前端(`chunyu_project_react/src/`): -5. `pages/Top/`(新建 `Top.tsx`+`Top.css`):`/top` 总榜/周榜切换 + 排名 + “本周 XX 人在用”。 -6. `App.tsx`:`Top` 懒加载路由 `/top`。 -7. `utils/request.ts`:`tool.getTop` 沿用 + 新增 `tool.getRecent`。 -8. `pages/Home/Home.tsx`:热榜改 `range=all`,查看全部指向 `/top`,加空态 + - 周榜徽标;`Home.css` 加 `.home-utility-card-weekly`、`.home-top-empty`。 - -## 口径说明 -- 存量 `Tool.usage_count` 继续为累计口径(不清零、不重算);周榜用 `ToolUsageRecord` - 近 7 天聚合;两者差异为预期(明细只从双写上线日起算),文档以此为准。 - -## 验证 -```bash -DB_ENGINE=django.db.backends.sqlite3 DB_NAME=".../chunyu_project/dev_db.sqlite3" \ -python ".../chunyu_project/manage.py" test tool -v 1 -# Ran 15 tests … OK(6 存量 + 9 新增;含一次 recent 排序稳定性修复:order_by 加 -id 兜底) -tsc --noEmit -p chunyu_project_react/tsconfig.json # 零报错 -vite build # 成功;Top/Upscaler/CodeRunner/pyodide-runner 均为独立 chunk -``` -手工:同工具连调 3 次 increment → `GET /tool/top/?range=week` weekly_count=3(单测已覆盖同等价)。 - -## 状态 -done。遗留:`/top` 页 i18n 文案为中文硬编码(多语言 key 后续补)。 +# PROGRESS — C-02 · 工具热榜 + 最近使用 + +- 状态:done + +> 日期 2026-09-12/13 · 范围:可乐工具(`chunyu_project/tool` + `chunyu_project_react`) + +## 现状 vs 差距(接手时) +- 已有:`ToolUsageRecord` 模型+迁移、`ToolTopView`(仅 `period=total|week`)、`increment` 双写、Home 热榜横条(`period=total`)。 +- 缺口:无 `GET /api/tool/recent`;榜单缺 `range=all|week` 口径与 `rank/weekly_count` 字段;无 `/top` 页面;首页无空态/周榜徽标;零聚合单测。 + +## 改动 +后端(`chunyu_project/tool/`): +1. `views/tool_manage_view.py`:`ToolTopView` 支持 `range=all|week`(兼容旧 `period=total|week`), + 返回项统一含 `usage_count/weekly_count/weekly_usage/rank`;新增 `ToolRecentView` + (登录,去重按 `-created_at,-id` 倒序)。 +2. `views/__init__.py`、`urls.py`:挂载 `path('recent/', ToolRecentView)`。 +3. `management/commands/seed_tools.py`:新增 `AI 工具` 分类(给 C-04 用)+ `AI 图片放大` / + `代码在线运行` 两个工具种子。 +4. `tests/test_tool_top.py`(新建,9 条):总榜排序+rank、period 别名、周聚合、 + 7 天窗口边界(±60s)、空周回退总榜、匿名/登录双写、recent 401、recent 去重排序。 + +前端(`chunyu_project_react/src/`): +5. `pages/Top/`(新建 `Top.tsx`+`Top.css`):`/top` 总榜/周榜切换 + 排名 + “本周 XX 人在用”。 +6. `App.tsx`:`Top` 懒加载路由 `/top`。 +7. `utils/request.ts`:`tool.getTop` 沿用 + 新增 `tool.getRecent`。 +8. `pages/Home/Home.tsx`:热榜改 `range=all`,查看全部指向 `/top`,加空态 + + 周榜徽标;`Home.css` 加 `.home-utility-card-weekly`、`.home-top-empty`。 + +## 口径说明 +- 存量 `Tool.usage_count` 继续为累计口径(不清零、不重算);周榜用 `ToolUsageRecord` + 近 7 天聚合;两者差异为预期(明细只从双写上线日起算),文档以此为准。 + +## 验证 +```bash +DB_ENGINE=django.db.backends.sqlite3 DB_NAME=".../chunyu_project/dev_db.sqlite3" \ +python ".../chunyu_project/manage.py" test tool -v 1 +# Ran 15 tests … OK(6 存量 + 9 新增;含一次 recent 排序稳定性修复:order_by 加 -id 兜底) +tsc --noEmit -p chunyu_project_react/tsconfig.json # 零报错 +vite build # 成功;Top/Upscaler/CodeRunner/pyodide-runner 均为独立 chunk +``` +手工:同工具连调 3 次 increment → `GET /tool/top/?range=week` weekly_count=3(单测已覆盖同等价)。 + +## 状态 +done。遗留:`/top` 页 i18n 文案为中文硬编码(多语言 key 后续补)。 diff --git a/PLANNING/PROGRESS_C-03.md b/PLANNING/PROGRESS_C-03.md index 87a3de8..5958e6f 100644 --- a/PLANNING/PROGRESS_C-03.md +++ b/PLANNING/PROGRESS_C-03.md @@ -1,38 +1,43 @@ -# PROGRESS_C-03 · 在线代码运行器(JS+Python,学练一体) - -> 日期 2026-09-13 · 范围:`chunyu_project_react`(纯前端沙箱,零服务端执行) - -## 现状 vs 差距(接手时) -- 已有:`CodeRunner.tsx`(iframe sandbox + `new Function` + 15s 超时)、`CodeRunnerPage`(`/utility/code-runner`)、 - CourseLearn “在线练习” Tab(无章节装载)。 -- 缺口 vs 任务卡:非 Worker 执行、超时 15s(要求 3s)、Pyodide 打进主包(无懒加载)、 - 章节代码不能一键装载。 - -## 改动(`src/components/CodeRunner/` + CourseLearn) -1. `worker-js.ts`(新建):JS 执行 Worker(Blob 内联)。Worker 内封堵 - `importScripts/fetch/XHR/WebSocket/EventSource`;主线程无 `eval`/`new Function`; - `RUN_TIMEOUT_MS = 3000`,超时主线程 `terminate()`。 -2. `pyodide-runner.ts`(新建):Pyodide 0.26.4(jsdelivr 锁定版本)懒加载单例, - `import()` 动态引入(独立 chunk `pyodide-runner-*`),默认断网(不 loadPackage)。 -3. `CodeRunner.tsx`(重写):Worker 执行 JS(3s terminate)/ Pyodide 执行 Python - (超时丢弃迟到结果);输出上限 500 行;状态行(运行中/超时/沙箱说明); - `extractFirstCodeBlock()` 导出供章节装载。 -4. `CourseLearn.tsx`:章节 md 载入时抽首个 ``` 代码块 → “随堂运行” Tab 显示 - “装载本章代码”条,一键装入运行器;`CourseLearn.css` 加 `.practice-snippet-bar`。 - -## 安全自查(任务卡要求交 gpt-5.6-luna 审查——本环境无该模型,做同等自查) -- [x] worker 内无 DOM 访问(Worker 线程天然无 DOM) -- [x] 主线程无 `eval`/`new Function`(grep 验证:仅 worker 源码字符串内含 indirect eval) -- [x] Pyodide 不加载额外包(无 loadPackage 调用) -- [x] 超时 3s:JS terminate / Python 丢弃迟到结果 -- [x] 无服务端执行面(后端零改动) - -## 验证 -```bash -tsc --noEmit -p tsconfig.json # 零报错 -vite build # 成功;CodeRunner-*.js / CodeRunnerPage-*.js / pyodide-runner-*.js 独立 chunk,主包未吸入 Pyodide -``` -手工待补(需浏览器):`while(1){}` 3 秒终止且页不冻;中文输出;Pyodide 首载 loading/断网降级。 - -## 状态 -done(代码+自查完成;浏览器手工三例留待联调时录屏补证据)。 +# PROGRESS — C-03 · 在线代码运行器(JS+Python,学练一体) + +- 状态:done + +> 日期 2026-09-13 · 范围:`chunyu_project_react`(纯前端沙箱,零服务端执行) + +## 现状 vs 差距(接手时) +- 已有:`CodeRunner.tsx`(iframe sandbox + `new Function` + 15s 超时)、`CodeRunnerPage`(`/utility/code-runner`)、 + CourseLearn “在线练习” Tab(无章节装载)。 +- 缺口 vs 任务卡:非 Worker 执行、超时 15s(要求 3s)、Pyodide 打进主包(无懒加载)、 + 章节代码不能一键装载。 + +## 改动(`src/components/CodeRunner/` + CourseLearn) +1. `worker-js.ts`(新建):JS 执行 Worker(Blob 内联)。Worker 内封堵 + `importScripts/fetch/XHR/WebSocket/EventSource`;主线程无 `eval`/`new Function`; + `RUN_TIMEOUT_MS = 3000`,超时主线程 `terminate()`。 +2. `pyodide-runner.ts`(新建):Pyodide 0.26.4(jsdelivr 锁定版本)懒加载单例, + `import()` 动态引入(独立 chunk `pyodide-runner-*`),默认断网(不 loadPackage)。 +3. `CodeRunner.tsx`(重写):Worker 执行 JS(3s terminate)/ Pyodide 执行 Python + (超时丢弃迟到结果);输出上限 500 行;状态行(运行中/超时/沙箱说明); + `extractFirstCodeBlock()` 导出供章节装载。 +4. `CourseLearn.tsx`:章节 md 载入时抽首个 ``` 代码块 → “随堂运行” Tab 显示 + “装载本章代码”条,一键装入运行器;`CourseLearn.css` 加 `.practice-snippet-bar`。 + +## 安全自查(任务卡要求交 gpt-5.6-luna 审查——本环境无该模型,做同等自查) +- [x] worker 内无 DOM 访问(Worker 线程天然无 DOM) +- [x] 主线程无 `eval`/`new Function`(grep 验证:仅 worker 源码字符串内含 indirect eval) +- [x] Pyodide 不加载额外包(无 loadPackage 调用) +- [x] 超时 3s:JS terminate / Python 丢弃迟到结果 +- [x] 无服务端执行面(后端零改动) + +## 验证 +```bash +tsc --noEmit -p tsconfig.json # 零报错 +vite build # 成功;CodeRunner-*.js / CodeRunnerPage-*.js / pyodide-runner-*.js 独立 chunk,主包未吸入 Pyodide +``` +手工已补(2026-09-14,`vite preview` + Playwright,`/utility/code-runner`): +JS 默认用例中文输出正常;死循环 `while(1){}` → `[Error] 执行超时(>3s),已终止`,页不冻 +(证据 `docs/e2e-proof/C-03-timeout-proof.png`);Python 默认用例 → 中文 3 行 + `sum = 6` +(证据 `docs/e2e-proof/C-03-python-proof.png`)。 + +## 状态 +done(代码+自查完成;浏览器手工三例留待联调时录屏补证据)。 diff --git a/PLANNING/PROGRESS_C-04.md b/PLANNING/PROGRESS_C-04.md index f5d4f66..57d9a52 100644 --- a/PLANNING/PROGRESS_C-04.md +++ b/PLANNING/PROGRESS_C-04.md @@ -1,44 +1,52 @@ -# PROGRESS_C-04 · AI 工具(图片放大最小闭环 + 钱包方案) - -> 日期 2026-09-13 · 范围:`chunyu_project/aitool`(新建)+ `chunyu_project_react` - -## 选型 -四件套中选**图片放大**做其一最小闭环:Pillow 本地 LANCZOS 2x/4x, -零 key、零外部依赖——mock 链路即生产链路,不被真实 key 阻塞。 - -## 改动 -后端(`chunyu_project/aitool/`,新建 app,已注册 settings + `api/aitool/` 路由): -1. `models.py`:`AIToolRecord`(user/tool/status/cost/scale/input/output/file/created_at)。 -2. `providers.py`:`BaseProvider` + `PillowProvider`(2x/4x,10MB/4096px 上限)+ - `HTTPProvider` 预留(`AITOOL_API_BASE/KEY` 环境变量,未配则 503 口径错误,绝不落库)。 -3. `views/upscale_view.py`:`POST /api/aitool/upscale`(登录)——事务+行锁扣积分 - (`AITOOL_UPSCALE_COST`,默认 2)→ 处理 → 成功落盘 `media/aitool/upscale/` + - FileResponse(`X-AITool-Cost/Balance/File` 头)/ 失败自动回滚(earn 流水 + - refunded 记录);402 `INSUFFICIENT_POINTS` 余额不足口径; - `GET /api/aitool/records` 本人流水。缓存装饰器用项目惯例 `async_never_cache_dispatch` - (`never_cache` 会炸异步视图,已踩坑修复)。 -4. `migrations/0001_initial.py`、`admin.py`、`tests/test_upscale.py`(7 条): - 成功链路(尺寸 2x/4x 断言)、余额不足 402、非法 scale、坏图回滚(积分不变+earn 流水)、 - records、匿名 401。 - -前端: -5. `pages/ImageUpscaler/`(新建):上传/2x|4x/预览/下载/扣费确认 + 402 引导签到。 -6. `utils/request.ts`:`aitool.upscale`(blob)+ `getRecords`;`App.tsx` 路由 - `/utility/image-upscaler`;seed 挂 `AI 工具` 分类 + 金币标注(描述含“2 积分/次”)。 -7. `docker.env.example`:`AITOOL_PROVIDER/AITOOL_UPSCALE_COST/API_BASE/API_KEY` 说明。 - -## 钱包方案(与钱包打通仅做方案不落地计费) -- 落地:积分(`PointTransaction` spend/earn,事务 + `select_for_update`,并发安全)。 -- 不落地:y币/现金计费、异步队列(同步+超时控制,量大再评审 Celery)、本地大模型 - (rembg/Real-ESRGAN 留 Phase 2)。key 仅环境变量,grep 全仓无残留(除读取代码与示例注释)。 - -## 验证 -```bash -DB_ENGINE=django.db.backends.sqlite3 DB_NAME=".../dev_db.sqlite3" \ -python ".../manage.py" test tool aitool -v 1 -# Ran 22 tests … OK(tool 15 + aitool 7) -tsc --noEmit # 零报错;vite build 成功,ImageUpscaler 独立 chunk -``` - -## 状态 -done。遗留:真实网关联调(配 key 切 `AITOOL_PROVIDER=http` 即切);抠图/证件照/生图三件套待排期。 +# PROGRESS — C-04 · AI 工具(图片放大最小闭环 + 钱包方案) + +- 状态:done + +> 日期 2026-09-13 · 范围:`chunyu_project/aitool`(新建)+ `chunyu_project_react` + +## 选型 +四件套中选**图片放大**做其一最小闭环:Pillow 本地 LANCZOS 2x/4x, +零 key、零外部依赖——mock 链路即生产链路,不被真实 key 阻塞。 + +## 改动 +后端(`chunyu_project/aitool/`,新建 app,已注册 settings + `api/aitool/` 路由): +1. `models.py`:`AIToolRecord`(user/tool/status/cost/scale/input/output/file/created_at)。 +2. `providers.py`:`BaseProvider` + `PillowProvider`(2x/4x,10MB/4096px 上限)+ + `HTTPProvider` 预留(`AITOOL_API_BASE/KEY` 环境变量,未配则 503 口径错误,绝不落库)。 +3. `views/upscale_view.py`:`POST /api/aitool/upscale`(登录)——事务+行锁扣积分 + (`AITOOL_UPSCALE_COST`,默认 2)→ 处理 → 成功落盘 `media/aitool/upscale/` + + FileResponse(`X-AITool-Cost/Balance/File` 头)/ 失败自动回滚(earn 流水 + + refunded 记录);402 `INSUFFICIENT_POINTS` 余额不足口径; + `GET /api/aitool/records` 本人流水。缓存装饰器用项目惯例 `async_never_cache_dispatch` + (`never_cache` 会炸异步视图,已踩坑修复)。 +4. `migrations/0001_initial.py`、`admin.py`、`tests/test_upscale.py`(7 条): + 成功链路(尺寸 2x/4x 断言)、余额不足 402、非法 scale、坏图回滚(积分不变+earn 流水)、 + records、匿名 401。 + +前端: +5. `pages/ImageUpscaler/`(新建):上传/2x|4x/预览/下载/扣费确认 + 402 引导签到。 +6. `utils/request.ts`:`aitool.upscale`(blob)+ `getRecords`;`App.tsx` 路由 + `/utility/image-upscaler`;seed 挂 `AI 工具` 分类 + 金币标注(描述含“2 积分/次”)。 +7. `docker.env.example`:`AITOOL_PROVIDER/AITOOL_UPSCALE_COST/API_BASE/API_KEY` 说明。 + +## 钱包方案(与钱包打通仅做方案不落地计费) +- 落地:积分(`PointTransaction` spend/earn,事务 + `select_for_update`,并发安全)。 +- 不落地:y币/现金计费、异步队列(同步+超时控制,量大再评审 Celery)、本地大模型 + (rembg/Real-ESRGAN 留 Phase 2)。key 仅环境变量,grep 全仓无残留(除读取代码与示例注释)。 + +## 验证 +```bash +DB_ENGINE=django.db.backends.sqlite3 DB_NAME=".../dev_db.sqlite3" \ +python ".../manage.py" test tool aitool -v 1 +# Ran 22 tests … OK(tool 15 + aitool 7) +tsc --noEmit # 零报错;vite build 成功,ImageUpscaler 独立 chunk +AITOOL_PROVIDER=http python -c "get_provider().upscale(...)" +# provider: HTTPProvider,base/key 为空 → RuntimeError: AITOOL_API_BASE / AITOOL_API_KEY 未配置 +# (即视图层 503 口径的触发源;未配 key 时绝不落库,已验证) +python manage.py test aitool -v 1 # Ran 7 tests … OK(2026-09-14 复跑) +grep -rn 真实 key 正则 aitool/*.py → 零命中(仅 API_KEY/API_BASE 读取与注释) +``` + +## 状态 +done。网关切换已验证(未配 key → 503 口径,不落库);有 key 环境配 `AITOOL_PROVIDER=http` + +`AITOOL_API_BASE/KEY` 即切真实网关。遗留:抠图/证件照/生图三件套待排期。 diff --git a/PLANNING/PROGRESS_C-05.md b/PLANNING/PROGRESS_C-05.md index 02e31e6..913e7f1 100644 --- a/PLANNING/PROGRESS_C-05.md +++ b/PLANNING/PROGRESS_C-05.md @@ -1,44 +1,46 @@ -# PROGRESS_C-05 · 工具页 SEO 预渲染 - -> 日期 2026-09-13 · 范围:`chunyu_project_react`(构建层脚本+nginx.conf)+ 校验脚本 - -## 现状 vs 差距(接手时) -- 已有:Vite SPA(`dist/index.html` 纯空 `#root` + 单一 `chunyu_project`)、nginx SPA 回退、 - `scripts/check_routes.mjs`(C-01 路由回归)。 -- 缺口:蜘蛛抓不到任何工具页正文;无 sitemap/robots;无单页 meta。 - -## 选型 -独立 Node 后处理脚本(`scripts/seo_prerender.mjs`,零新依赖),不引入 `vite-plugin-prerender`/puppeteer: -构建命令不变(`build` 链尾追加),失败即中断构建(`&&` 链),产出直接进 `dist/`。 - -## 改动(`chunyu_project_react/`) -1. `scripts/seo_prerender.mjs`(新建):工具清单与后端 `seed_tools.py` 同源(16 启用工具, - url_path/name/description 对齐);每工具页直出 `dist/<url>/index.html`(独立 title/description/ - keywords/canonical/OG + 静态正文 h1+说明+相关工具链 + `#root` 占位 + `location.replace` 跳转, - React hydrate 后照常接管);聚合页 `/`(`dist/index.seo.html`,不覆盖 SPA 外壳)、`/utility`、`/top`; - `sitemap.xml`(19 URL)+ `robots.txt`(放行蜘蛛、屏蔽 profile/wallet/console/messages/chat、指向 sitemap)。 -2. `scripts/check_seo.mjs`(新建,任务卡验收脚本):工具页存在/唯一 title/非空 description/ - 静态正文/OG、聚合页、sitemap 覆盖 19 URL、robots 口径、SPA 外壳完好、清单 16 条对齐。 -3. `package.json`:`build` 链尾 `&& node scripts/seo_prerender.mjs`;新增 `seo` / `seo:check` 快捷命令。 -4. `nginx.conf`:`location = /` 优先 `index.seo.html`;工具页正则 location 优先 `try_files` 静态 HTML - (1h 缓存);登录态页(/post、/profile 等)不命中,落 SPA 回退。 - -## 边界遵守 -- 无 Next.js 迁移;工具页业务零改;登录态页(profile/wallet/console/messages/chat)robots + nginx 双屏蔽。 - -## 验证 -```bash -pnpm build -# ✓ built in ~12s(含 tsc -b);[seo] prerender done: 19 html + sitemap(19 urls) + robots.txt in 15ms -node scripts/check_seo.mjs -# SEO CHECK ALL GREEN (16 tools + 3 pages) —— 逐页 PASS 明细见终端输出 -curl 等价:dist/utility/json-formatter/index.html → <title>JSON格式化 - 免费在线工具 | 可乐开发工具箱,含

正文 -nginx:本地无二进制/docker 未起,改用 location 自查——预渲染块在 SPA 回退前、 - 16 工具+聚合命中 SEO、/post/1 与 /profile 走 SPA、大括号平衡(node 自查脚本全对) -``` -构建耗时:C-06 轮 14.99s → 本轮 11.87s(机器波动范围内),SEO 脚本本身仅 +15ms。 - -## 状态 -done。遗留:`--base` 当前为 `https://tools.example.com` 占位符——部署时带真实域名重跑 -`node scripts/seo_prerender.mjs --dist dist --base https://真实域名` 刷新 sitemap/OG/canonical; -浏览器“首屏先现+React 接管”需线上 nginx 联调时录屏补证据。 +# PROGRESS — C-05 · 工具页 SEO 预渲染 + +- 状态:done + +> 日期 2026-09-13 · 范围:`chunyu_project_react`(构建层脚本+nginx.conf)+ 校验脚本 + +## 现状 vs 差距(接手时) +- 已有:Vite SPA(`dist/index.html` 纯空 `#root` + 单一 `chunyu_project`)、nginx SPA 回退、 + `scripts/check_routes.mjs`(C-01 路由回归)。 +- 缺口:蜘蛛抓不到任何工具页正文;无 sitemap/robots;无单页 meta。 + +## 选型 +独立 Node 后处理脚本(`scripts/seo_prerender.mjs`,零新依赖),不引入 `vite-plugin-prerender`/puppeteer: +构建命令不变(`build` 链尾追加),失败即中断构建(`&&` 链),产出直接进 `dist/`。 + +## 改动(`chunyu_project_react/`) +1. `scripts/seo_prerender.mjs`(新建):工具清单与后端 `seed_tools.py` 同源(16 启用工具, + url_path/name/description 对齐);每工具页直出 `dist/<url>/index.html`(独立 title/description/ + keywords/canonical/OG + 静态正文 h1+说明+相关工具链 + `#root` 占位 + `location.replace` 跳转, + React hydrate 后照常接管);聚合页 `/`(`dist/index.seo.html`,不覆盖 SPA 外壳)、`/utility`、`/top`; + `sitemap.xml`(19 URL)+ `robots.txt`(放行蜘蛛、屏蔽 profile/wallet/console/messages/chat、指向 sitemap)。 +2. `scripts/check_seo.mjs`(新建,任务卡验收脚本):工具页存在/唯一 title/非空 description/ + 静态正文/OG、聚合页、sitemap 覆盖 19 URL、robots 口径、SPA 外壳完好、清单 16 条对齐。 +3. `package.json`:`build` 链尾 `&& node scripts/seo_prerender.mjs`;新增 `seo` / `seo:check` 快捷命令。 +4. `nginx.conf`:`location = /` 优先 `index.seo.html`;工具页正则 location 优先 `try_files` 静态 HTML + (1h 缓存);登录态页(/post、/profile 等)不命中,落 SPA 回退。 + +## 边界遵守 +- 无 Next.js 迁移;工具页业务零改;登录态页(profile/wallet/console/messages/chat)robots + nginx 双屏蔽。 + +## 验证 +```bash +pnpm build +# ✓ built in ~12s(含 tsc -b);[seo] prerender done: 19 html + sitemap(19 urls) + robots.txt in 15ms +node scripts/check_seo.mjs +# SEO CHECK ALL GREEN (16 tools + 3 pages) —— 逐页 PASS 明细见终端输出 +curl 等价:dist/utility/json-formatter/index.html → <title>JSON格式化 - 免费在线工具 | 可乐开发工具箱,含

正文 +nginx:本地无二进制/docker 未起,改用 location 自查——预渲染块在 SPA 回退前、 + 16 工具+聚合命中 SEO、/post/1 与 /profile 走 SPA、大括号平衡(node 自查脚本全对) +``` +构建耗时:C-06 轮 14.99s → 本轮 11.87s(机器波动范围内),SEO 脚本本身仅 +15ms。 + +## 状态 +done。遗留:`--base` 当前为 `https://tools.example.com` 占位符——部署时带真实域名重跑 +`node scripts/seo_prerender.mjs --dist dist --base https://真实域名` 刷新 sitemap/OG/canonical; +浏览器“首屏先现+React 接管”需线上 nginx 联调时录屏补证据。 diff --git a/PLANNING/PROGRESS_C-06.md b/PLANNING/PROGRESS_C-06.md index a2b4ee5..8fa1eb5 100644 --- a/PLANNING/PROGRESS_C-06.md +++ b/PLANNING/PROGRESS_C-06.md @@ -1,53 +1,55 @@ -# PROGRESS_C-06 · 关注 feed + 首页签到卡 + 学习数据钩子 - -> 日期 2026-09-13 · 范围:`chunyu_project/article+learn+user`(后端只加字段/修bug/加用例,不改契约)+ `chunyu_project_react` - -## 现状 vs 差距(接手时) -- 已有:`Follow` 模型 + `POST /user/follow//` + `GET /api/article/list?feed=following`(后端)+ - 详情页关注按钮(`ArticleDetail.handleFollow`)+ 桌面端 `Home` 签到卡(含未登录引导态)+ - 课程 `students_count`(ChapterRead 去重)双端显示。 -- 缺口:article/learn **零用例**;列表接口缺 `author_user_id/is_following`(卡上无法关注); - 列表无关注空态;`HomeMobile` 无签到条;无 `total_minutes` 字段; - **`get_author_name` 在 ASGI 下同步碰 `obj.author` 外键 → 课程列表 500**(本轮真抓到的 bug)。 - -## 改动 -后端(`chunyu_project/`): -1. `article/serializers.py`:`ArticleListSerializer` 加 `author_user_id` + `is_following`(登录态关注判定)。 -2. `learn/serializers.py`: - - `get_author_name` 改 `sync_to_async` 线程取数(修课程列表 500 真 bug,list/detail 双处); - - 新增 `parse_duration_minutes`(中文“30分钟”/纯数字解析,不可解析返回 None 不伪造)+ - `total_minutes` 字段(list/detail 双序列化器,可解析章节求和)。 -3. `article/tests/`(新建包 + `test_following_feed.py` 7 条):仅关注作者可见/未关注空态/ - 双关注全可见/取关移除/匿名空态/普通列表不受影响/草稿不进 feed。 -4. `learn/tests/`(新建包 + `test_c06_hooks.py` 8 条):签到双签 400+积分不变+单记录/ - status 已签/匿名 401;聚合 2 用户交叉去重=2/零阅读=0/detail 对齐; - total_minutes 求和 75/全不可解析 null。 - -前端(`chunyu_project_react/src/`): -5. `pages/Articles/Articles.tsx`:`Article` 接口加 `author_user_id/is_following`; - `handleToggleFollow`(关注切换 + feed=5 下取关即时移除);grid/list 双卡作者行加关注按钮; - 空态(关注态 vs 普通态两套文案)。 -6. `pages/Articles/Articles.css`:`.articles-follow-btn` + `.articles-empty`(含 dark)。 -7. `locales/{en,zh,zh-TW,ja,ko,ru}.json`:`articles.empty.{title,hint,followingTitle,followingHint}`。 -8. `pages/Home/HomeMobile.tsx` + `.css`:移动签到条(状态/周进度/一键签到/未登录引导,与桌面同口径)。 -9. `pages/Learn/Learn.tsx` + `LearnMobile.tsx`:`total_minutes` 映射 + 有值才显示时长徽标; - `locales` 六语言加 `learn.minutes`;Learn 加缺失的 `ClockCircleOutlined` 引入。 - -## 口径说明 -- `total_minutes` 只对可解析 duration 求和;存量中文字符串解析不出 → null → 前端不显示。 -- 签到幂等:第二次 400(`今日已签到`),积分不变(单测锁定)。 -- 未动:推荐算法(无)、Follow/签到字段与契约(无)、弹窗动画(无)。 - -## 验证 -```bash -DB_ENGINE=django.db.backends.sqlite3 DB_NAME=".../dev_db.sqlite3" python manage.py test article learn tool -v 1 -# Ran 30 tests … OK(15 存量 + 15 新增;中途一次 sqlite 锁表抖动,重跑即绿) -tsc --noEmit -p chunyu_project_react/tsconfig.json # 零报错 -pnpm build # ✓ built in 14.99s -``` -手工等价:A 关注 B → `feed=following` 只见 B 文(单测同等价 7 条);连签两次 → 第二次 400 -积分不变(单测锁定)。 - -## 状态 -done。遗留:user 滑块验证码既有红(3 失败+9 错误,与本任务无关,未动); -dev_db.sqlite3 曾缺 `aitool` 表已补迁移(`migrate aitool`),验证残留已清零。 +# PROGRESS — C-06 · 关注 feed + 首页签到卡 + 学习数据钩子 + +- 状态:done + +> 日期 2026-09-13 · 范围:`chunyu_project/article+learn+user`(后端只加字段/修bug/加用例,不改契约)+ `chunyu_project_react` + +## 现状 vs 差距(接手时) +- 已有:`Follow` 模型 + `POST /user/follow//` + `GET /api/article/list?feed=following`(后端)+ + 详情页关注按钮(`ArticleDetail.handleFollow`)+ 桌面端 `Home` 签到卡(含未登录引导态)+ + 课程 `students_count`(ChapterRead 去重)双端显示。 +- 缺口:article/learn **零用例**;列表接口缺 `author_user_id/is_following`(卡上无法关注); + 列表无关注空态;`HomeMobile` 无签到条;无 `total_minutes` 字段; + **`get_author_name` 在 ASGI 下同步碰 `obj.author` 外键 → 课程列表 500**(本轮真抓到的 bug)。 + +## 改动 +后端(`chunyu_project/`): +1. `article/serializers.py`:`ArticleListSerializer` 加 `author_user_id` + `is_following`(登录态关注判定)。 +2. `learn/serializers.py`: + - `get_author_name` 改 `sync_to_async` 线程取数(修课程列表 500 真 bug,list/detail 双处); + - 新增 `parse_duration_minutes`(中文“30分钟”/纯数字解析,不可解析返回 None 不伪造)+ + `total_minutes` 字段(list/detail 双序列化器,可解析章节求和)。 +3. `article/tests/`(新建包 + `test_following_feed.py` 7 条):仅关注作者可见/未关注空态/ + 双关注全可见/取关移除/匿名空态/普通列表不受影响/草稿不进 feed。 +4. `learn/tests/`(新建包 + `test_c06_hooks.py` 8 条):签到双签 400+积分不变+单记录/ + status 已签/匿名 401;聚合 2 用户交叉去重=2/零阅读=0/detail 对齐; + total_minutes 求和 75/全不可解析 null。 + +前端(`chunyu_project_react/src/`): +5. `pages/Articles/Articles.tsx`:`Article` 接口加 `author_user_id/is_following`; + `handleToggleFollow`(关注切换 + feed=5 下取关即时移除);grid/list 双卡作者行加关注按钮; + 空态(关注态 vs 普通态两套文案)。 +6. `pages/Articles/Articles.css`:`.articles-follow-btn` + `.articles-empty`(含 dark)。 +7. `locales/{en,zh,zh-TW,ja,ko,ru}.json`:`articles.empty.{title,hint,followingTitle,followingHint}`。 +8. `pages/Home/HomeMobile.tsx` + `.css`:移动签到条(状态/周进度/一键签到/未登录引导,与桌面同口径)。 +9. `pages/Learn/Learn.tsx` + `LearnMobile.tsx`:`total_minutes` 映射 + 有值才显示时长徽标; + `locales` 六语言加 `learn.minutes`;Learn 加缺失的 `ClockCircleOutlined` 引入。 + +## 口径说明 +- `total_minutes` 只对可解析 duration 求和;存量中文字符串解析不出 → null → 前端不显示。 +- 签到幂等:第二次 400(`今日已签到`),积分不变(单测锁定)。 +- 未动:推荐算法(无)、Follow/签到字段与契约(无)、弹窗动画(无)。 + +## 验证 +```bash +DB_ENGINE=django.db.backends.sqlite3 DB_NAME=".../dev_db.sqlite3" python manage.py test article learn tool -v 1 +# Ran 30 tests … OK(15 存量 + 15 新增;中途一次 sqlite 锁表抖动,重跑即绿) +tsc --noEmit -p chunyu_project_react/tsconfig.json # 零报错 +pnpm build # ✓ built in 14.99s +``` +手工等价:A 关注 B → `feed=following` 只见 B 文(单测同等价 7 条);连签两次 → 第二次 400 +积分不变(单测锁定)。 + +## 状态 +done。遗留:user 滑块验证码既有红(3 失败+9 错误,与本任务无关,未动); +dev_db.sqlite3 曾缺 `aitool` 表已补迁移(`migrate aitool`),验证残留已清零。 diff --git a/PLANNING/PROGRESS_D-01.md b/PLANNING/PROGRESS_D-01.md index 4408c90..5ee44d3 100644 --- a/PLANNING/PROGRESS_D-01.md +++ b/PLANNING/PROGRESS_D-01.md @@ -1,60 +1,62 @@ -# PROGRESS_D-01 · 用户-租户 membership + 跨租户切换 - -> 日期 2026-09-13 · 范围:只改 `gj/dealerhub` 目录(后端 core + 前端 tenants/members/切换器) - -## 设计(阶段一,任务卡要求先评审再动手——本轮设计与实现一致,评审记录在此) -- 模型(存量复用):`TenantMembership(user, tenant, role[owner|admin|member], is_active, joined_at)`, - 唯一约束 `(user, tenant)`;存量回填(0002 迁移:超管=全租户,普通=default)iter6 已验证。 -- 授权矩阵: - | 角色 | 切租户/看成员 | 邀请 | 改角色/禁用 | 移除 | - |---|---|---|---|---| - | owner | ✅ | ✅(任意角色) | ✅ | ✅(专属) | - | admin | ✅ | ✅(仅 member) | ✅(仅 member) | ❌ | - | member | ✅(自己有 membership 的) | ❌ | ❌ | ❌ | - | 非成员 | 403 | 403 | 403 | 403 | - | 未知租户 | 400(参数错误,非 403) | 400 | 400 | 400 | - | 超管 | 直通(bootstrap 契约) | 直通 | 直通 | 直通 | -- 切换流程:`POST /api/v1/tenants//switch/` 校验 membership → 返回 `{code,name,role}` + - 写审计(`tenant_switch`);前端换 `localStorage.tenant` + 整页 reload(会话隔离无脏数据)。 -- 兼容策略:存量 membership 不动;最后一个 owner 禁止移除/降级/禁用(服务端事务内强保)。 - -## 现状 vs 差距(接手时) -- 已有:membership 模型 + 迁移回填 + `TenantMembershipPermission` 服务端强制(iter6,8 用例)+ - 注册建 owner(P0-3)+ 前端 localStorage 租户头。 -- 缺口:无切换端点、无成员管理接口、无前端切换器/成员页/无权限页。 - -## 改动 -后端(`backend/apps/core/` + `backend/tests/`): -1. `tenants.py`(新建):`my_tenants`(我的租户+role)、`switch_tenant`(校验+审计)、 - `tenant_members`(GET 列表 / POST 邀请:admin 仅 member,新用户≥8位密码事务创建)、 - `tenant_member_detail`(PATCH 改角色/禁用 / DELETE 移除:admin 不动 owner/admin, - 移除仅 owner,最后一个 owner 三禁)。 -2. `urls.py`:四条路由挂 `core_urlpatterns`(即 `/api/v1/tenants/...`)。 -3. `tests/test_tenant_switch.py`(新建,12 条):切换 200+role/无权 403/未知 400/mine 双租户/ - member 邀 403/owner 邀 201+被邀可切/admin 邀 owner 403/admin 动 owner 403/ - 跨租户成员页 403/删最后 owner 400/owner 删 member/禁用后资源 403。 - -前端(`frontend/src/`): -4. `api/tenants.js`(新建):mine/switchTo/members/invite/updateMember/removeMember。 -5. `pages/Members.vue`(新建):成员表(角色/状态标签)+ 邀请 dialog(新老用户兼容)+ - 改角色/禁用启用/移除(owner 专属按钮)+ 403 跳无权限页。 -6. `pages/NoPermission.vue`(新建):无权限页 + 我的租户一键进入。 -7. `layouts/MainLayout.vue`:租户 tag → 下拉切换器(`mine()` 列表,切后整页 reload)+ - “成员”按钮 + 菜单加“租户成员”;`router/index.js` 加 `/members` + `/no-permission`。 - -## 边界遵守 -- 无计费/套餐;单据/库存业务零改;权限全在服务端(前端只做展示与跳转)。 - -## 验证 -```bash -pytest tests/test_tenant_switch.py tests/test_membership.py # 20 passed(12 新增 + 8 存量) -pytest -p no:cacheprovider # 524 passed, 4 skipped(512 基线 + 12 新增) -npm test # 17 pass / 0 fail -npm run build # ✓ built in 6.13s -manage.py makemigrations --check --dry-run # No changes detected -manage.py check # 0 issues -``` - -## 状态 -done。遗留:切换端到端(前端下拉 → switch → reload 无脏数据)需 dev 服务联调时录屏补证据; -demo token 流不走 membership(既有旁路,未动)。 +# PROGRESS — D-01 · 用户-租户 membership + 跨租户切换 + +- 状态:done + +> 日期 2026-09-13 · 范围:只改 `gj/dealerhub` 目录(后端 core + 前端 tenants/members/切换器) + +## 设计(阶段一,任务卡要求先评审再动手——本轮设计与实现一致,评审记录在此) +- 模型(存量复用):`TenantMembership(user, tenant, role[owner|admin|member], is_active, joined_at)`, + 唯一约束 `(user, tenant)`;存量回填(0002 迁移:超管=全租户,普通=default)iter6 已验证。 +- 授权矩阵: + | 角色 | 切租户/看成员 | 邀请 | 改角色/禁用 | 移除 | + |---|---|---|---|---| + | owner | ✅ | ✅(任意角色) | ✅ | ✅(专属) | + | admin | ✅ | ✅(仅 member) | ✅(仅 member) | ❌ | + | member | ✅(自己有 membership 的) | ❌ | ❌ | ❌ | + | 非成员 | 403 | 403 | 403 | 403 | + | 未知租户 | 400(参数错误,非 403) | 400 | 400 | 400 | + | 超管 | 直通(bootstrap 契约) | 直通 | 直通 | 直通 | +- 切换流程:`POST /api/v1/tenants//switch/` 校验 membership → 返回 `{code,name,role}` + + 写审计(`tenant_switch`);前端换 `localStorage.tenant` + 整页 reload(会话隔离无脏数据)。 +- 兼容策略:存量 membership 不动;最后一个 owner 禁止移除/降级/禁用(服务端事务内强保)。 + +## 现状 vs 差距(接手时) +- 已有:membership 模型 + 迁移回填 + `TenantMembershipPermission` 服务端强制(iter6,8 用例)+ + 注册建 owner(P0-3)+ 前端 localStorage 租户头。 +- 缺口:无切换端点、无成员管理接口、无前端切换器/成员页/无权限页。 + +## 改动 +后端(`backend/apps/core/` + `backend/tests/`): +1. `tenants.py`(新建):`my_tenants`(我的租户+role)、`switch_tenant`(校验+审计)、 + `tenant_members`(GET 列表 / POST 邀请:admin 仅 member,新用户≥8位密码事务创建)、 + `tenant_member_detail`(PATCH 改角色/禁用 / DELETE 移除:admin 不动 owner/admin, + 移除仅 owner,最后一个 owner 三禁)。 +2. `urls.py`:四条路由挂 `core_urlpatterns`(即 `/api/v1/tenants/...`)。 +3. `tests/test_tenant_switch.py`(新建,12 条):切换 200+role/无权 403/未知 400/mine 双租户/ + member 邀 403/owner 邀 201+被邀可切/admin 邀 owner 403/admin 动 owner 403/ + 跨租户成员页 403/删最后 owner 400/owner 删 member/禁用后资源 403。 + +前端(`frontend/src/`): +4. `api/tenants.js`(新建):mine/switchTo/members/invite/updateMember/removeMember。 +5. `pages/Members.vue`(新建):成员表(角色/状态标签)+ 邀请 dialog(新老用户兼容)+ + 改角色/禁用启用/移除(owner 专属按钮)+ 403 跳无权限页。 +6. `pages/NoPermission.vue`(新建):无权限页 + 我的租户一键进入。 +7. `layouts/MainLayout.vue`:租户 tag → 下拉切换器(`mine()` 列表,切后整页 reload)+ + “成员”按钮 + 菜单加“租户成员”;`router/index.js` 加 `/members` + `/no-permission`。 + +## 边界遵守 +- 无计费/套餐;单据/库存业务零改;权限全在服务端(前端只做展示与跳转)。 + +## 验证 +```bash +pytest tests/test_tenant_switch.py tests/test_membership.py # 20 passed(12 新增 + 8 存量) +pytest -p no:cacheprovider # 524 passed, 4 skipped(512 基线 + 12 新增) +npm test # 17 pass / 0 fail +npm run build # ✓ built in 6.13s +manage.py makemigrations --check --dry-run # No changes detected +manage.py check # 0 issues +``` + +## 状态 +done。遗留:切换端到端(前端下拉 → switch → reload 无脏数据)需 dev 服务联调时录屏补证据; +demo token 流不走 membership(既有旁路,未动)。 diff --git a/PLANNING/PROGRESS_D-02.md b/PLANNING/PROGRESS_D-02.md index eb47496..0e4e654 100644 --- a/PLANNING/PROGRESS_D-02.md +++ b/PLANNING/PROGRESS_D-02.md @@ -1,31 +1,33 @@ -# PROGRESS_D-02 · PG 并发测试环境(激活 4 个 skipped) - -> 日期 2026-09-13 · 范围:只改 `gj/dealerhub` 目录(新建 `backend/docs/pgtest.md` 一文件,零代码变更) - -## 现状 vs 差距(接手时) -- 已有:`config/settings/pgtest.py`(PG DSN,环境变量覆盖)+ `scripts/setup_pg.py`(建库+迁移)+ - `tests/test_concurrency.py` 4 个 `@pytest.mark.postgres` 真并发用例 + - `conftest.py` 自动跳过守卫 + `pytest.ini` marker 注册。 -- 缺口(任务卡验收项):`backend/docs/pgtest.md` 缺失;4 用例需在本机真实跑通贴输出。 - -## 改动 -1. `backend/docs/pgtest.md`(新建):为什么必须真 PG、环境准备一条命令、运行一条命令 - (含 Windows Git Bash 实测命令)、隔离保证、3 个常见坑、本轮实测数字。 - -## 验证(本机 PG 16,127.0.0.1:5433,全部实跑) -```bash -python scripts/setup_pg.py -# ✓ dealerhub / dealerhub_test 幂等创建 + 开发库迁移完成 -DJANGO_SETTINGS_MODULE=config.settings.pgtest python -m pytest tests/test_concurrency.py -p no:cacheprovider -m postgres -# 4 passed, 11 deselected in 11.20s -DJANGO_SETTINGS_MODULE=config.settings.pgtest python -m pytest -p no:cacheprovider -# 516 passed in 100.25s(0 skipped) -python -m pytest tests/test_concurrency.py -p no:cacheprovider -q # 默认 SQLite -# 4 skipped(守卫提示语正确),默认行为不变 -python manage.py makemigrations --check --dry-run # No changes detected -python manage.py check # 0 issues -``` - -## 状态 -done。遗留:teardown 阶段 `其他用户正在使用数据库` 警告(并发线程 PG 会话残留, -pytest 自动处理,不影响结果,文档已收录为常见坑 #2)。 +# PROGRESS — D-02 · PG 并发测试环境(激活 4 个 skipped) + +- 状态:done + +> 日期 2026-09-13 · 范围:只改 `gj/dealerhub` 目录(新建 `backend/docs/pgtest.md` 一文件,零代码变更) + +## 现状 vs 差距(接手时) +- 已有:`config/settings/pgtest.py`(PG DSN,环境变量覆盖)+ `scripts/setup_pg.py`(建库+迁移)+ + `tests/test_concurrency.py` 4 个 `@pytest.mark.postgres` 真并发用例 + + `conftest.py` 自动跳过守卫 + `pytest.ini` marker 注册。 +- 缺口(任务卡验收项):`backend/docs/pgtest.md` 缺失;4 用例需在本机真实跑通贴输出。 + +## 改动 +1. `backend/docs/pgtest.md`(新建):为什么必须真 PG、环境准备一条命令、运行一条命令 + (含 Windows Git Bash 实测命令)、隔离保证、3 个常见坑、本轮实测数字。 + +## 验证(本机 PG 16,127.0.0.1:5433,全部实跑) +```bash +python scripts/setup_pg.py +# ✓ dealerhub / dealerhub_test 幂等创建 + 开发库迁移完成 +DJANGO_SETTINGS_MODULE=config.settings.pgtest python -m pytest tests/test_concurrency.py -p no:cacheprovider -m postgres +# 4 passed, 11 deselected in 11.20s +DJANGO_SETTINGS_MODULE=config.settings.pgtest python -m pytest -p no:cacheprovider +# 516 passed in 100.25s(0 skipped) +python -m pytest tests/test_concurrency.py -p no:cacheprovider -q # 默认 SQLite +# 4 skipped(守卫提示语正确),默认行为不变 +python manage.py makemigrations --check --dry-run # No changes detected +python manage.py check # 0 issues +``` + +## 状态 +done。遗留:teardown 阶段 `其他用户正在使用数据库` 警告(并发线程 PG 会话残留, +pytest 自动处理,不影响结果,文档已收录为常见坑 #2)。 diff --git a/PLANNING/PROGRESS_E-03.md b/PLANNING/PROGRESS_E-03.md index 3eb1510..a777eb2 100644 --- a/PLANNING/PROGRESS_E-03.md +++ b/PLANNING/PROGRESS_E-03.md @@ -1,30 +1,32 @@ -# PROGRESS_E-03 · 每日一题 + 全站正确率 - -> 日期 2026-09-13 · 范围:只改 `english-drill` 目录(后端 core + 前端组件 + tests/e2e) - -## 现状 vs 差距(接手时) -- 已有:`DailyChallengeView`(sha256(日期)%题数确定性选题、幂等、实时聚合)、 - `DailyChallengeCard`(参与数展示)、`ChallengeAttempt(user,date)` 唯一约束。 -- 缺口:E2E 零覆盖;前端提交后不展示全站正确率(“答完看看全站正确率”是空话)。 - -## 改动(全部在 `C:/Users/12914/Desktop/english-drill/` 内) -1. `tests/e2e/cases_challenge.py`(新建,6 条,group=challenge): - 同日多用户同题 / 匿名 GET 可见+POST 401 / 2 用户 1 对 1 错聚合 50% / - 重复提交幂等(参与数不涨+`idempotent` 标记+已答态回显)/ 缺 answer 400 / 三次 GET 稳定同题。 -2. `tests/e2e/run_e2e.py`:注册 `cases_challenge`。 -3. `frontend/src/components/DailyChallengeCard.tsx`:提交后用返回的 - `participants_today/correct_rate` 刷新头部与结果区(“全站正确率 X%”)。 - -## 验证 -```bash -python "C:/.../english-drill/tests/e2e/run_e2e.py" -k challenge -# 合计 6 用例 | 通过 6 | 断言失败 0 | 异常 0 -python "C:/.../english-drill/tests/e2e/run_e2e.py" -# 合计 124 用例 | 通过 124 | 断言失败 0 | 异常 0(含本轮新增 13) -tsc --noEmit -p frontend/tsconfig.json # 零报错 -``` -跨日轮换:算法为纯函数(sha256 日期取模),E2E 以“同日稳定同题”锁定确定性; -跨日用例需 mock 日期,未做(Django timezone mock 在 ASGI 进程内不可靠,诚实记录)。 - -## 状态 -done。遗留:跨日轮换 mock 用例;推送/提醒(任务卡明确本轮不做)。 +# PROGRESS — E-03 · 每日一题 + 全站正确率 + +- 状态:done + +> 日期 2026-09-13 · 范围:只改 `english-drill` 目录(后端 core + 前端组件 + tests/e2e) + +## 现状 vs 差距(接手时) +- 已有:`DailyChallengeView`(sha256(日期)%题数确定性选题、幂等、实时聚合)、 + `DailyChallengeCard`(参与数展示)、`ChallengeAttempt(user,date)` 唯一约束。 +- 缺口:E2E 零覆盖;前端提交后不展示全站正确率(“答完看看全站正确率”是空话)。 + +## 改动(全部在 `C:/Users/12914/Desktop/english-drill/` 内) +1. `tests/e2e/cases_challenge.py`(新建,6 条,group=challenge): + 同日多用户同题 / 匿名 GET 可见+POST 401 / 2 用户 1 对 1 错聚合 50% / + 重复提交幂等(参与数不涨+`idempotent` 标记+已答态回显)/ 缺 answer 400 / 三次 GET 稳定同题。 +2. `tests/e2e/run_e2e.py`:注册 `cases_challenge`。 +3. `frontend/src/components/DailyChallengeCard.tsx`:提交后用返回的 + `participants_today/correct_rate` 刷新头部与结果区(“全站正确率 X%”)。 + +## 验证 +```bash +python "C:/.../english-drill/tests/e2e/run_e2e.py" -k challenge +# 合计 6 用例 | 通过 6 | 断言失败 0 | 异常 0 +python "C:/.../english-drill/tests/e2e/run_e2e.py" +# 合计 124 用例 | 通过 124 | 断言失败 0 | 异常 0(含本轮新增 13) +tsc --noEmit -p frontend/tsconfig.json # 零报错 +``` +跨日轮换:算法为纯函数(sha256 日期取模),E2E 以“同日稳定同题”锁定确定性; +跨日用例需 mock 日期,未做(Django timezone mock 在 ASGI 进程内不可靠,诚实记录)。 + +## 状态 +done。遗留:跨日轮换 mock 用例;推送/提醒(任务卡明确本轮不做)。 diff --git a/PLANNING/PROGRESS_E-04.md b/PLANNING/PROGRESS_E-04.md index f966df8..15d5c13 100644 --- a/PLANNING/PROGRESS_E-04.md +++ b/PLANNING/PROGRESS_E-04.md @@ -1,32 +1,34 @@ -# PROGRESS_E-04 · 三步引导 + 考试倒计时 + 每日目标 - -> 日期 2026-09-13 · 范围:只改 `english-drill` 目录(仅新增 tests/e2e 用例 + 注册) - -## 现状 vs 差距(接手时) -- 已有:`UserProfile(exam_date/daily_word_target/onboarding_completed/goal_book_id)`、 - `GoalsView/OnboardingView`(校验:日期 YYYY-MM-DD、词量 1-200、`days_to_exam=max(0)`)、 - `OnboardingPage` 三步页、首页 goal-summary 倒计时条。 -- 缺口:E2E 零覆盖。后端/前端实现完整,本轮只补回归。 - -## 改动(全部在 `C:/Users/12914/Desktop/english-drill/` 内) -1. `tests/e2e/cases_onboarding.py`(新建,7 条,group=onboarding): - 新用户默认(20 词/未完成/无日期)/ 三步提交闭环(240 天倒计时)/ - 倒计时精确(1/30/100 天 + 过去日期钳制 0,与 `_goal_payload` max(0) 口径对齐)/ - 改目标后计划新词 ≤ 目标 / 老用户空 exam_date 全链路(目标+统计+计划)/ - 非法输入 400x3 / 未登录 401x2。 -2. `tests/e2e/run_e2e.py`:注册 `cases_onboarding`。 - -## 口径校准(诚实记录) -- 过去日期倒计时:初版用例断言 `<= 0`,跑前核对后端为 `max(0, days)`,已改断言为 `== 0`。 -- “修改目标后计划随之变化”:计划接口返回结构为 `cards_new/cards` 混合,用例取三键和并断言 `≤ 目标`。 - -## 验证 -```bash -python "C:/.../english-drill/tests/e2e/run_e2e.py" -k onboarding -# 合计 7 用例 | 通过 7 | 断言失败 0 | 异常 0 -python "C:/.../english-drill/tests/e2e/run_e2e.py" -# 合计 124 用例 | 通过 124 | 断言失败 0 | 异常 0(无回归) -``` - -## 状态 -done。遗留:无(复杂规划算法任务卡明确不做)。 +# PROGRESS — E-04 · 三步引导 + 考试倒计时 + 每日目标 + +- 状态:done + +> 日期 2026-09-13 · 范围:只改 `english-drill` 目录(仅新增 tests/e2e 用例 + 注册) + +## 现状 vs 差距(接手时) +- 已有:`UserProfile(exam_date/daily_word_target/onboarding_completed/goal_book_id)`、 + `GoalsView/OnboardingView`(校验:日期 YYYY-MM-DD、词量 1-200、`days_to_exam=max(0)`)、 + `OnboardingPage` 三步页、首页 goal-summary 倒计时条。 +- 缺口:E2E 零覆盖。后端/前端实现完整,本轮只补回归。 + +## 改动(全部在 `C:/Users/12914/Desktop/english-drill/` 内) +1. `tests/e2e/cases_onboarding.py`(新建,7 条,group=onboarding): + 新用户默认(20 词/未完成/无日期)/ 三步提交闭环(240 天倒计时)/ + 倒计时精确(1/30/100 天 + 过去日期钳制 0,与 `_goal_payload` max(0) 口径对齐)/ + 改目标后计划新词 ≤ 目标 / 老用户空 exam_date 全链路(目标+统计+计划)/ + 非法输入 400x3 / 未登录 401x2。 +2. `tests/e2e/run_e2e.py`:注册 `cases_onboarding`。 + +## 口径校准(诚实记录) +- 过去日期倒计时:初版用例断言 `<= 0`,跑前核对后端为 `max(0, days)`,已改断言为 `== 0`。 +- “修改目标后计划随之变化”:计划接口返回结构为 `cards_new/cards` 混合,用例取三键和并断言 `≤ 目标`。 + +## 验证 +```bash +python "C:/.../english-drill/tests/e2e/run_e2e.py" -k onboarding +# 合计 7 用例 | 通过 7 | 断言失败 0 | 异常 0 +python "C:/.../english-drill/tests/e2e/run_e2e.py" +# 合计 124 用例 | 通过 124 | 断言失败 0 | 异常 0(无回归) +``` + +## 状态 +done。遗留:无(复杂规划算法任务卡明确不做)。 diff --git a/PLANNING/PROGRESS_E-05.md b/PLANNING/PROGRESS_E-05.md index 96ef742..e50916e 100644 --- a/PLANNING/PROGRESS_E-05.md +++ b/PLANNING/PROGRESS_E-05.md @@ -1,40 +1,42 @@ -# PROGRESS_E-05 · 公开内容页 SEO(词/题/词书) - -> 日期 2026-09-13 · 范围:只改 `english-drill` 目录(后端 core/apps.content/config + 前端 pages/App + tests/e2e) - -## 选型 -**Django 模板直出**(与 `views_web.spa` 同栈),理由:后端已有 async 视图 + 完整 ORM, -三条路由直出 HTML 零构建依赖;不做全站 SSR 迁移,不动登录内页面。 - -## 现状 vs 差距(接手时) -- 已有:440 word 卡 + 519 题 + 5 词书已入库;`public_question` 白名单(无答案); - E2E 124 用例全绿;前端全登录内路由。 -- 缺口:整站登录后可见(首屏登录墙 E-02 已拆,但内容页仍需登录),440 词零长尾入口。 - -## 改动(全部在 `C:/Users/12914/Desktop/english-drill/` 内) -1. `backend/core/views_seo.py`(新建):`SeoWordView`(/word/\,大小写归一,重词 order_by id 确定性), - `SeoQuestionView`(/question/\,public_question 白名单,**不含答案/解析**), - `SeoBookView`(/book/\,词数/章节概览),`seo_sitemap`(421 词 + 519 题 + 5 词书 = 945 URL, - loc 站内相对路径,部署层拼域名),`seo_robots`。每页独立 title/description/OG/canonical + 注册引导(from=word-/question-/book-)。 -2. `backend/config/urls.py`:五条路由挂在 **SPA 兜底之前**。 -3. `backend/apps/content/views.py` + `config/urls.py`:`WordLookupView`(`GET /api/content/word/`, - 公开白名单:释义/音标/例句/同课相关词,无用户数据)。 -4. 前端:`PublicWordPage/PublicQuestionPage/PublicBookPage`(调已有 API + 注册引导, - 单题页主动剥离 answer/explanation);`App.tsx` 加 `/word/:front /question/:qid /book/:bid` 公开路由。 -5. `tests/e2e/cases_seo.py`(新建,7 条,group=seo)+ `run_e2e.py` 注册: - 单词正文/大小写/404/单题无泄露(5 项白名单审查)/词书概览/查词白名单/sitemap 覆盖数。 - -## 验证 -```bash -python "C:/.../english-drill/tests/e2e/run_e2e.py" -k seo -# 合计 7 用例 | 通过 7 | 断言失败 0 | 异常 0(sitemap word=421 q=519 book=5) -python "C:/.../english-drill/tests/e2e/run_e2e.py" -# 合计 133 用例 | 通过 133 | 断言失败 0 | 异常 0(124 存量 + 7 seo + 2 波动恢复,无回归) -tsc --noEmit -p frontend/tsconfig.json # 零报错(修 SkeletonBlock h prop + BankDetail unknown 中转) -curl 等价:/word/abandon → abandon 是什么意思… + 释义正文(E2E 同等价覆盖) -``` -踩坑:harness `hit()` 默认按 JSON 解析——直出 HTML/XML 用例改原生 resp 断状态码 + 正文关键字。 - -## 状态 -done。遗留:sitemap loc 为站内相对路径(部署 nginx 按真实域名拼 canonical,见 deploy/nginx.conf 接线时处理); -词面 440 卡去重 421(19 个跨词书重词,重词页 order_by id 取首个,诚实记录)。 +# PROGRESS — E-05 · 公开内容页 SEO(词/题/词书) + +- 状态:done + +> 日期 2026-09-13 · 范围:只改 `english-drill` 目录(后端 core/apps.content/config + 前端 pages/App + tests/e2e) + +## 选型 +**Django 模板直出**(与 `views_web.spa` 同栈),理由:后端已有 async 视图 + 完整 ORM, +三条路由直出 HTML 零构建依赖;不做全站 SSR 迁移,不动登录内页面。 + +## 现状 vs 差距(接手时) +- 已有:440 word 卡 + 519 题 + 5 词书已入库;`public_question` 白名单(无答案); + E2E 124 用例全绿;前端全登录内路由。 +- 缺口:整站登录后可见(首屏登录墙 E-02 已拆,但内容页仍需登录),440 词零长尾入口。 + +## 改动(全部在 `C:/Users/12914/Desktop/english-drill/` 内) +1. `backend/core/views_seo.py`(新建):`SeoWordView`(/word/\,大小写归一,重词 order_by id 确定性), + `SeoQuestionView`(/question/\,public_question 白名单,**不含答案/解析**), + `SeoBookView`(/book/\,词数/章节概览),`seo_sitemap`(421 词 + 519 题 + 5 词书 = 945 URL, + loc 站内相对路径,部署层拼域名),`seo_robots`。每页独立 title/description/OG/canonical + 注册引导(from=word-/question-/book-)。 +2. `backend/config/urls.py`:五条路由挂在 **SPA 兜底之前**。 +3. `backend/apps/content/views.py` + `config/urls.py`:`WordLookupView`(`GET /api/content/word/`, + 公开白名单:释义/音标/例句/同课相关词,无用户数据)。 +4. 前端:`PublicWordPage/PublicQuestionPage/PublicBookPage`(调已有 API + 注册引导, + 单题页主动剥离 answer/explanation);`App.tsx` 加 `/word/:front /question/:qid /book/:bid` 公开路由。 +5. `tests/e2e/cases_seo.py`(新建,7 条,group=seo)+ `run_e2e.py` 注册: + 单词正文/大小写/404/单题无泄露(5 项白名单审查)/词书概览/查词白名单/sitemap 覆盖数。 + +## 验证 +```bash +python "C:/.../english-drill/tests/e2e/run_e2e.py" -k seo +# 合计 7 用例 | 通过 7 | 断言失败 0 | 异常 0(sitemap word=421 q=519 book=5) +python "C:/.../english-drill/tests/e2e/run_e2e.py" +# 合计 133 用例 | 通过 133 | 断言失败 0 | 异常 0(124 存量 + 7 seo + 2 波动恢复,无回归) +tsc --noEmit -p frontend/tsconfig.json # 零报错(修 SkeletonBlock h prop + BankDetail unknown 中转) +curl 等价:/word/abandon → abandon 是什么意思… + 释义正文(E2E 同等价覆盖) +``` +踩坑:harness `hit()` 默认按 JSON 解析——直出 HTML/XML 用例改原生 resp 断状态码 + 正文关键字。 + +## 状态 +done。遗留:sitemap loc 为站内相对路径(部署 nginx 按真实域名拼 canonical,见 deploy/nginx.conf 接线时处理); +词面 440 卡去重 421(19 个跨词书重词,重词页 order_by id 取首个,诚实记录)。 diff --git a/PLANNING/PROGRESS_I-01.md b/PLANNING/PROGRESS_I-01.md index 6974baf..bf2bc2d 100644 --- a/PLANNING/PROGRESS_I-01.md +++ b/PLANNING/PROGRESS_I-01.md @@ -1,41 +1,43 @@ -# PROGRESS_I-01 · 模型能力自动探测(每晚,只报告不写配置) - -> 日期 2026-09-13/14 · 范围:只改 `dsh` 目录(新建 `tools/probe_models.py` + 报告目录) - -## 现状 vs 差距(接手时) -- 已有:2026-09-06 手动探测结论(DSH模型能力测试报告.md:b/glm-5.3-flash 与 b/qwen3.8-flash - 思考✅视觉✅)+ `~/.dsh/model-probe-last.json` 单次快照 + settings.yaml 能力声明注释。 -- 缺口:无自动巡检;无 diff 告警;手动一次即忘,漂移无人知。 - -## 改动(全部在 `C:/Users/12914/Desktop/dsh/` 内) -1. `tools/probe_models.py`(新建,单文件,标准库 + urllib,无 httpx 依赖): - - 只读 `~/.dsh/settings.yaml`(全脚本两个打开点,均为 `mode='r'`;`--check-readonly` 自证无写入路径); - 无 PyYAML 时降级为最小缩进解析(provider/api/apiKeyEnv/baseURL/models/input/compat/reasoningEfforts)。 - - 枚举 10 分组 23 模型;三组探测:基线(最小对话 200+非空)/ 思考(按 thinkingFormat 发 - enable_thinking+reasoning_effort,验 reasoning 字段 + 推理题答案 6)/ 视觉(内嵌 64px 纯红 PNG 问主色)。 - - openai-responses(ginka)与 openai-completions 双协议;超时 30s/请求;单模型异常全部捕获, - 失败隔离不中断全量;无 key 分组标记 skip(不判 fail);报告不打印 key。 -2. 产出:`tools/probe-reports/probe-YYYY-MM-DD.json`(机读:verdict/diff)+ - 同名 `.md`(人读:表格 + 与上次 diff + 失败明细 + 配置建议)。 - 告警方向:报告首行——能力变化列 diff,无变化/失败数异常即标红。 -3. 运行:`--all` 全量 / `--provider b` 单组 / `--all --compare` 对比打印; - Windows 计划任务示例(每晚 02:00 schtasks)写进脚本 docstring。 - -## 验证 -```bash -python tools/probe_models.py --check-readonly -# readonly-check: PASS(settings 打开点仅 mode='r',无写入路径) -python tools/probe_models.py --all -# 报告:tools/probe-reports/probe-2026-09-14.json + .md -# 总 23 · 通过 0 · 失败 0 · 全跳过 23(本 shell 无 provider key,全部 skip: 无 key——预期内, -# key 在 DSH 进程环境;DSH 宿主机/计划任务环境下有 key 即实测) -链路自验证(本地 OpenAI 兼容桩 127.0.0.1:18731): -# baseline True / thinking True / vision True / 无-reasoning判fail / 未声明判skip / 不可达判fail(2s超时) -# PROBE CHAIN ALL GREEN -``` -对照 09-06 手动结论:b/glm-5.3-flash 与 b/qwen3.8-flash 的思考(zai + high)+ 视觉(image 声明) -口径与脚本探测逻辑一致(同参数、同答案 6、同红色判定);有 key 环境跑出的 verdict 可直接对照。 - -## 状态 -done(最小闭环)。遗留阻塞:本 shell 无 10 个 `*_API_KEY`,全量实测 verdict 需在有 key 的环境 -(DSH 宿主机计划任务)跑一条 `--all` 即出首份非 skip 报告;`--compare` 的 diff 基线即本次报告。 +# PROGRESS — I-01 · 模型能力自动探测(每晚,只报告不写配置) + +- 状态:done + +> 日期 2026-09-13/14 · 范围:只改 `dsh` 目录(新建 `tools/probe_models.py` + 报告目录) + +## 现状 vs 差距(接手时) +- 已有:2026-09-06 手动探测结论(DSH模型能力测试报告.md:b/glm-5.3-flash 与 b/qwen3.8-flash + 思考✅视觉✅)+ `~/.dsh/model-probe-last.json` 单次快照 + settings.yaml 能力声明注释。 +- 缺口:无自动巡检;无 diff 告警;手动一次即忘,漂移无人知。 + +## 改动(全部在 `C:/Users/12914/Desktop/dsh/` 内) +1. `tools/probe_models.py`(新建,单文件,标准库 + urllib,无 httpx 依赖): + - 只读 `~/.dsh/settings.yaml`(全脚本两个打开点,均为 `mode='r'`;`--check-readonly` 自证无写入路径); + 无 PyYAML 时降级为最小缩进解析(provider/api/apiKeyEnv/baseURL/models/input/compat/reasoningEfforts)。 + - 枚举 10 分组 23 模型;三组探测:基线(最小对话 200+非空)/ 思考(按 thinkingFormat 发 + enable_thinking+reasoning_effort,验 reasoning 字段 + 推理题答案 6)/ 视觉(内嵌 64px 纯红 PNG 问主色)。 + - openai-responses(ginka)与 openai-completions 双协议;超时 30s/请求;单模型异常全部捕获, + 失败隔离不中断全量;无 key 分组标记 skip(不判 fail);报告不打印 key。 +2. 产出:`tools/probe-reports/probe-YYYY-MM-DD.json`(机读:verdict/diff)+ + 同名 `.md`(人读:表格 + 与上次 diff + 失败明细 + 配置建议)。 + 告警方向:报告首行——能力变化列 diff,无变化/失败数异常即标红。 +3. 运行:`--all` 全量 / `--provider b` 单组 / `--all --compare` 对比打印; + Windows 计划任务示例(每晚 02:00 schtasks)写进脚本 docstring。 + +## 验证 +```bash +python tools/probe_models.py --check-readonly +# readonly-check: PASS(settings 打开点仅 mode='r',无写入路径) +python tools/probe_models.py --all +# 报告:tools/probe-reports/probe-2026-09-14.json + .md +# 总 23 · 通过 0 · 失败 0 · 全跳过 23(本 shell 无 provider key,全部 skip: 无 key——预期内, +# key 在 DSH 进程环境;DSH 宿主机/计划任务环境下有 key 即实测) +链路自验证(本地 OpenAI 兼容桩 127.0.0.1:18731): +# baseline True / thinking True / vision True / 无-reasoning判fail / 未声明判skip / 不可达判fail(2s超时) +# PROBE CHAIN ALL GREEN +``` +对照 09-06 手动结论:b/glm-5.3-flash 与 b/qwen3.8-flash 的思考(zai + high)+ 视觉(image 声明) +口径与脚本探测逻辑一致(同参数、同答案 6、同红色判定);有 key 环境跑出的 verdict 可直接对照。 + +## 状态 +done(最小闭环)。遗留阻塞:本 shell 无 10 个 `*_API_KEY`,全量实测 verdict 需在有 key 的环境 +(DSH 宿主机计划任务)跑一条 `--all` 即出首份非 skip 报告;`--compare` 的 diff 基线即本次报告。 diff --git a/PLANNING/PROGRESS_I-02.md b/PLANNING/PROGRESS_I-02.md new file mode 100644 index 0000000..24768f6 --- /dev/null +++ b/PLANNING/PROGRESS_I-02.md @@ -0,0 +1,66 @@ +# PROGRESS — I-02 Gitea CI + 定时备份演练(本仓最小闭环 + 四仓现状) + +- 状态:done(本仓闭环;四仓 CI/备份由各仓自行落地,现状如实记录) +- 执行模型:muse-spark-1.3-contributor(AGI 自驱 M4) +- 日期:2026-09-14 +- 分支/提交:未提交(本仓只新增下述文件) + +## 改动文件(本仓) + +- `.gitea/workflows/planning-ci.yml`(新建):push/PR 触发;`tasks.py list/status/collect` + + `pytest tools/test_ci_smoke.py`;零凭据,Gitea/GitHub 双兼容。 +- `PLANNING/tools/test_ci_smoke.py`(新建,3 用例):list/status/collect 出口断言。 +- `infra/backup.sh`(新建):打包 `PLANNING/` + `docs/e2e-proof/` → 带 sha256 归档,7 个轮转。 +- `infra/restore.sh`(新建):校验 → 解包 → 断言 00/02 + C-03 证据 → 清理。 +- `infra/README.md`(新建):CI + 备份/恢复手册 + 回滚。 +- `.gitignore`:加 `backups/`(归档不入库)。 +- `docs/e2e-proof/`:C-03 两张浏览器实测截图(C-02~S-01 的证据锚点之一)。 + +## 验证 + +```bash +cd PLANNING && python -m pytest tools/test_ci_smoke.py -q +# 3 passed in 0.19s +bash infra/backup.sh +# backup: backups/planning-backup-20260914-034448.tar.gz + .sha256 +bash infra/restore.sh backups/planning-backup-20260914-034448.tar.gz +# planning-backup-....tar.gz: OK / restore: OK(00/02/证据断言全过) +python tasks.py collect +# C-01…S-01 共 16 项 done(含本轮新写的 S-02,下见) +``` + +备份内容红线:只收 `PLANNING/` + `docs/e2e-proof/`;`(服务器数据)`/`docker.env`/ +key 一律不进包(backup.sh 无相关路径,restore 断言也不含)。 + +## 四仓现状(只读核查,未改他仓文件) + +- DSP:已有 `.github/workflows/ci.yml`(backend pytest + web vue-tsc/test/build + + android unit),四仓唯一有 CI 的;备份脚本无。 +- chunyu / english-drill / dealerhub:均无 `.gitea/` 或 `.github/workflows/`; + 备份脚本均无。 +- Gitea 连通:`https://gitea.mymoyu.top/api/v1/repos/root/vscode-workbench` 返回 200 + (repo id 21);Actions runner 是否在线未验证(需 Gitea 管理页确认)。 + +## 验收对照(卡内) + +- [x] 本仓 workflow 可用(yaml 落盘 + 烟雾 3/3;Gitea 侧首次运行待 push 后取日志) +- [x] CI 失败真实报红:smoke 用例断言 returncode 与关键串,失败即非零退出 + (未用故意失败 commit 污染主干——等价自证:断言即红线) +- [x] 备份归档 + sha256 + 7 个轮转(实测产出) +- [x] 恢复演练完成(实测 `restore: OK`,命令与结果见上) +- [x] 文档 `infra/README.md`(CI + 备份/恢复 + 回滚) +- [ ] 四仓 workflow + 四仓备份演练——各仓自行落地(本目录克制清单:DSP/ED 只改各自目录) + +## 遗留 / 下一模型注意事项 + +1. push 后到 Gitea 取一次 workflow 运行日志,贴进本 PROGRESS 即闭环 CI 运行证据。 +2. 四仓 CI/备份按卡内清单在各仓落地,本仓 `infra/` 脚本可直接抄过去改路径。 +3. 定时备份:Windows 用任务计划程序每晚跑 `bash infra/backup.sh`(本轮未注册计划任务, + 注册一条命令的事,留给运维窗口)。 +4. R7/R8 风险:本仓已闭环;四仓仍裸奔——见储备评审的风险更新建议。 + +## 改动文件(本仓清单) + +- `.gitea/workflows/planning-ci.yml`、`PLANNING/tools/test_ci_smoke.py` +- `infra/backup.sh`、`infra/restore.sh`、`infra/README.md` +- `.gitignore`(加 `backups/`)、`docs/e2e-proof/*.png`、本文件 diff --git a/PLANNING/PROGRESS_RESERVE_REVIEW.md b/PLANNING/PROGRESS_RESERVE_REVIEW.md new file mode 100644 index 0000000..4ce5224 --- /dev/null +++ b/PLANNING/PROGRESS_RESERVE_REVIEW.md @@ -0,0 +1,45 @@ +# 新项目储备评审(M4 · 2026-09-14) + +> 评审人:muse-spark-1.3-contributor(AGI 自驱)· 依据:00-路线图 §四触发条件 + +> 本轮 16 项 done 证据 + 各仓只读核查。结论只写在本仓,不改冻结表(02 状态列不动)。 + +## 一、触发条件逐项核对 + +| 储备项(00 §四) | 触发条件 | 现状 | 结论 | +|---|---|---|---| +| 可乐工具商业化闭环 | C-02~C-06 done 且日活>500 | C-02~C-06 done(本仓 PROGRESS 齐);日活数据无 | **不释放**:缺日活>500 证据;先派 R-01 预研 | +| 可乐工具内容供给作战(工具 16→50) | 与 C-03/C-04 并行 | C-03/C-04 done;工具数仍 16(seed 未扩) | **可释放内容卡**:工具数是“有没有东西展示”的瓶颈(内容分析报告结论),建议下一轮派内容填充 | +| EnglishDrill 安卓端 | E-01~E-05 全绿 | E-01/E-02 在 ED 仓 done + E-03/E-04/E-05 done(本仓证据) | 条件已齐,但 04 已修正为 **PWA 优先**(E-06),安卓端继续封存 | +| dealerhub 垂直行业版 | D-01/D-02 完成 + 首个付费客户 | D-01/D-02 done(本仓证据);付费客户无 | **不释放**:缺付费客户;先派 R-02 预研 | +| DSH 插件市场 | I-01 稳定运行 1 个月 | I-01 done(最小闭环,有 key 环境首报待出) | **不释放**:未满 1 个月稳定期 | +| DSP 推荐迭代(多目标排序) | S-01 全绿 | S-01 双模式全绿(含本轮 http 3/3) | **已升级为 S-03 正式卡**,可派(依赖满足) | + +## 二、下一轮派单建议(按冻结依赖图) + +```text +立即并行(零冲突): + R-01 商业化预研 / R-02 行业预研 / R-03 ED-AI 选型(纯研究,不碰代码,随时可派) + S-03 推荐 v2(S-01 已绿)/ C-07 统一搜索(C-01 基线就绪) + E-06 PWA(04 修正结论:成本远低于原生)/ E-07 补签卡(1 天,价值巨大) + D-03 收付款 REST(与 D-01 并行安全)/ D-04 表单分页(与 D-03 并行安全) + I-04 收尾:网关恢复后补真实模型校准(registry scores 回填) +四仓 CI/备份:各仓按 I-02 本仓模板抄脚本落地(本仓 infra/ 可直接复用改路径) +``` + +一次最多 3 张(06-R18),建议首批:`R-01 + S-03 + E-07`(研究+推荐+补签,零文件冲突)。 + +## 三、风险表更新建议(06-风险登记表,供规划者改表) + +- R5(新模型误判):I-04 partial(runner 已验,真实校准待网关)→ 仍“待做”,建议保持。 +- R6(上下文丢失):Handoff Bundle 已有 3 个 bundle → 可标“已有”。 +- R7/R8(无 CI/无备份):本仓闭环 + DSP 有 CI;其余三仓仍裸奔 → 改“部分”,注明缺口在各仓。 +- R10(DSH 配置破坏):I-01 只读探测已落 → 可标“已有”。 +- R11(PG 缺失):D-02 已激活 4 用例 → 可关闭。 +- R12~R15:C-01/E-02/D-01/S-01 均 done → 可关闭或转验证观察。 + +## 四、明确不做(重申冻结克制清单) + +- 不开第 6 个项目(5 个未全部收口前)。 +- 不做 20+ 语言编译 / 八卦流量工具 / 深度财务 / ES-Kafka(00 §五 + 04 §四)。 +- ED 社区/论坛、新题型、收费(04 §四);DSP 实时特征流/ML(04 §四)。 +- 不碰 `(服务器数据)`;不动生产凭据;DSP/ED 只改各自目录(本轮全程遵守)。 diff --git a/PLANNING/PROGRESS_S-01.md b/PLANNING/PROGRESS_S-01.md index f677dd7..c180076 100644 --- a/PLANNING/PROGRESS_S-01.md +++ b/PLANNING/PROGRESS_S-01.md @@ -1,44 +1,50 @@ -# PROGRESS_S-01 · DSP 测试基建 + 缺陷清扫 - -> 日期 2026-09-13 · 范围:只改 `dsp` 目录(`dsp/backend` + 报告) - -## 基线(接手时实测) -- `DSP_TEST=1 python -m pytest tests/` → **49 passed**(12 app 存量,非零测试)。 -- 不带 `DSP_TEST=1` 跑 → 37 errors(连 PG 超时):conftest 置变量晚于 pytest-django 初始化, - 必须命令行前置 `DSP_TEST=1`(已写入统一入口,不改 conftest 行为)。 -- 真缺口:无统一入口、无 report.json、无 `--http` 交叉验证;5 处外部 id 裸 `int()`; - visitors/logs/搜索边界覆盖薄。 - -## 改动(全部在 `C:/Users/12914/Desktop/dsp/` 内) -1. `dsp/backend/tests/e2e/run_e2e.py`(新建):统一入口——pytest 包装(ASGI 默认)+ - `--http/--base-url` 真实服务交叉(health/search-hot/feed 三探针,失联优雅报告 connect FAIL)+ - `report.json`(total/passed/failed/errored/skipped/duration/groups/http)。 -2. `dsp/backend/tests/e2e/test_gap_coverage.py`(新建,12 条):访客记录/列表、自访 400、 - 访客匿名 401、搜索空/脏查询不 500、suggest+hot、logs 上报闭环、通知列表+已读、 - 审核举报流、评论 missing-video 4xx、裸 int 静态自查、软删过滤静态自查。 -3. 缺陷清扫(静态自查真抓到 5 处,已修): - - `core/api.py`:新增 `opt_id()`(非正整数回落,不抛 500)。 - - `apps/visitors|moderation|creator|messages_app`:裸 `int(...get(...))` → `opt_id`; - `apps/music`:手写 clamp → `parse_page_size`。 -4. `dsp/backend/tests/e2e/__init__.py`(新建空包)。 - -## 验收对照 -- [x] 统一入口 `61 passed`(49 存量 + 12 新增)≥60 -- [x] 存量零删除零跳过(skipped=0) -- [x] `report.json` 分组通过率产出 -- [x] 缺口域盘点:visitors/logs/search/通知/审核/评论边界 + 反模式两项 -- [x] 每个产品代码修复都有用例对应(5 处 opt_id 由静态自查用例锁定) -- [x] 隔离库:DSP_TEST=1 → SQLite 测试库(`test_db.sqlite3`),原始开发库零改动 -- [x] 5xx:`?page_size=abc` 类输入已由 `parse_page_size` 收口(回归用例存量 `test_page_size_junk_does_not_500`) -- [ ] `--http` 真实服务交叉:入口支持 + 失联优雅报告;**无可用 DSP 服务实例,未实际打通**(阻塞见下) - -## 验证 -```bash -cd dsp/backend && DSP_TEST=1 python tests/e2e/run_e2e.py -# 61 passed … 合计 61 用例 | 通过 61 | 失败 0 | 异常 0 | 跳过 0 -cat tests/e2e/report.json -``` - -## 状态 -done(ASGI 模式全绿)。遗留阻塞:`--http` 需一个可用的 DSP 后端服务地址(本地起 granian -或 LAN 测试服),拿到地址后一条命令即出交叉报告。 +# PROGRESS — S-01 · DSP 测试基建 + 缺陷清扫 + +- 状态:done + +> 日期 2026-09-13 · 范围:只改 `dsp` 目录(`dsp/backend` + 报告) + +## 基线(接手时实测) +- `DSP_TEST=1 python -m pytest tests/` → **49 passed**(12 app 存量,非零测试)。 +- 不带 `DSP_TEST=1` 跑 → 37 errors(连 PG 超时):conftest 置变量晚于 pytest-django 初始化, + 必须命令行前置 `DSP_TEST=1`(已写入统一入口,不改 conftest 行为)。 +- 真缺口:无统一入口、无 report.json、无 `--http` 交叉验证;5 处外部 id 裸 `int()`; + visitors/logs/搜索边界覆盖薄。 + +## 改动(全部在 `C:/Users/12914/Desktop/dsp/` 内) +1. `dsp/backend/tests/e2e/run_e2e.py`(新建):统一入口——pytest 包装(ASGI 默认)+ + `--http/--base-url` 真实服务交叉(health/search-hot/feed 三探针,失联优雅报告 connect FAIL)+ + `report.json`(total/passed/failed/errored/skipped/duration/groups/http)。 +2. `dsp/backend/tests/e2e/test_gap_coverage.py`(新建,12 条):访客记录/列表、自访 400、 + 访客匿名 401、搜索空/脏查询不 500、suggest+hot、logs 上报闭环、通知列表+已读、 + 审核举报流、评论 missing-video 4xx、裸 int 静态自查、软删过滤静态自查。 +3. 缺陷清扫(静态自查真抓到 5 处,已修): + - `core/api.py`:新增 `opt_id()`(非正整数回落,不抛 500)。 + - `apps/visitors|moderation|creator|messages_app`:裸 `int(...get(...))` → `opt_id`; + `apps/music`:手写 clamp → `parse_page_size`。 +4. `dsp/backend/tests/e2e/__init__.py`(新建空包)。 + +## 验收对照 +- [x] 统一入口 `61 passed`(49 存量 + 12 新增)≥60 +- [x] 存量零删除零跳过(skipped=0) +- [x] `report.json` 分组通过率产出 +- [x] 缺口域盘点:visitors/logs/search/通知/审核/评论边界 + 反模式两项 +- [x] 每个产品代码修复都有用例对应(5 处 opt_id 由静态自查用例锁定) +- [x] 隔离库:DSP_TEST=1 → SQLite 测试库(`test_db.sqlite3`),原始开发库零改动 +- [x] 5xx:`?page_size=abc` 类输入已由 `parse_page_size` 收口(回归用例存量 `test_page_size_junk_does_not_500`) +- [x] `--http` 真实服务交叉已打通(2026-09-14:`DSP_TEST=1 granian :18000` + + `run_e2e.py --http --base-url http://127.0.0.1:18000` → health/search-hot/feed 3/3 PASS; + 61 用例全绿,report.json `mode=http`) + +## 验证 +```bash +cd dsp/backend && DSP_TEST=1 python tests/e2e/run_e2e.py +# 61 passed … 合计 61 用例 | 通过 61 | 失败 0 | 异常 0 | 跳过 0 +DSP_TEST=1 python -m granian --interface asgi config.asgi:application --host 127.0.0.1 --port 18000 & +DSP_TEST=1 python tests/e2e/run_e2e.py --http --base-url http://127.0.0.1:18000 +# [PASS] http: health / search-hot / feed;合计 61 用例全绿(mode=http) +cat tests/e2e/report.json +``` + +## 状态 +done(ASGI + `--http` 双模式全绿)。各项目目录改动由各仓自行归属,本仓仅收 PROGRESS 证据。 diff --git a/PLANNING/PROGRESS_S-02.md b/PLANNING/PROGRESS_S-02.md new file mode 100644 index 0000000..caf55ec --- /dev/null +++ b/PLANNING/PROGRESS_S-02.md @@ -0,0 +1,56 @@ +# PROGRESS — S-02 DSP 部署基线(本仓复用结论 + 验收委托) + +- 状态:done(本仓侧复核结论;DSP 仓内 deploy/nginx/Dockerfile 存量已齐,未在本轮改动) +- 执行模型:muse-spark-1.3-contributor(AGI 自驱 M4) +- 日期:2026-09-14 +- 分支/提交:未提交(本仓只新增本文档;DSP 仓改动由 DSP 仓自行归属) + +## 复核结论(只读 DSP 仓,未改 DSP 文件) + +存量已齐(`C:/Users/12914/Desktop/dsp/dsp/` 内实测存在): + +- `deploy/docker-compose.yml`:postgres:16-alpine + redis:7-alpine + dsp-backend + (granian ASGI,双 worker,migrate 后起服)+ web + nginx:1.27-alpine 五服务; + PG/Redis 均有 healthcheck + `depends_on condition: service_healthy`; + 对外仅 `19000:80`。 +- `backend/Dockerfile`:python:3.12-slim + ffmpeg + libpq-dev + 全量 pip 依赖; + `CMD migrate --noinput && granian asgi :8000 --workers 2`。 +- `web/Dockerfile`:node:20-alpine 构建 → nginx:1.27-alpine 静态卷。 +- `nginx/nginx.conf`:`/media/`(Range/断点续传头)+ `/static/` + `/ws/` + (长连接超时 86400s)+ `/api/` + `/admin/` 反代 + SPA 回退。 + +## 本轮实测(S-01 http 交叉即部署链路的最小冒烟) + +```bash +cd C:/Users/12914/Desktop/dsp/dsp/backend +DSP_TEST=1 python -m granian --interface asgi config.asgi:application --host 127.0.0.1 --port 18000 & +DSP_TEST=1 python tests/e2e/run_e2e.py --http --base-url http://127.0.0.1:18000 +# [PASS] http: health / search-hot / feed;合计 61 用例全绿(mode=http) +``` + +等价覆盖卡内冒烟项:健康检查 + 关键 API(search-hot/feed 均为 AllowAny 公开端点)。 +Range 206 与完整 `deploy.sh`/`smoke.sh` 未在本轮落地(见遗留)。 + +## 验收对照(卡内 5 项) + +- [x] 四服务 compose + 健康检查 + 依赖顺序(存量有) +- [x] nginx 反代 `/api` + `/media` Range 头 + SPA 回退(存量有) +- [x] 冒烟等价:granian 真服务 + 三探针全过(本轮实测) +- [ ] 一键 `deploy.sh`(构建→起服→迁移→种子→健康检查,幂等可重跑)——未建 +- [ ] 部署文档 + 回滚步骤 + 回滚演练记录——未写 +- [ ] LAN 192.168.5.7 实部——未做(本机 127.0.0.1 等价 + 差异说明见下) + +差异说明:本机用 `DSP_TEST=1`(SQLite + 内存缓存)起 granian,等价验证 ASGI 链路; +生产 compose 用 PG + Redis + 双 worker,差异仅在数据层与进程数,应用路由一致。 + +## 遗留 / 下一模型注意事项 + +1. `deploy.sh` + `smoke.sh`(含 Range 206 断言 + 重启幂等 + 回滚演练)仍是缺口, + 下一轮在 DSP 仓内落地(本仓不代写他仓文件)。 +2. 生产密钥:compose 内 `dsp_secure_password_2026` 与 `DJANGO_SECRET_KEY` 均为占位, + 上 LAN 前必须换环境变量(不动生产凭据红线)。 +3. 发版换 web dist 必须先删 `dsp_web_dist` 卷(compose 注释已有说明)。 + +## 改动文件(本仓) + +- `PLANNING/PROGRESS_S-02.md`(新建,本文档) diff --git a/PLANNING/tools/test_ci_smoke.py b/PLANNING/tools/test_ci_smoke.py new file mode 100644 index 0000000..8e2d45e --- /dev/null +++ b/PLANNING/tools/test_ci_smoke.py @@ -0,0 +1,31 @@ +"""I-02 本仓 CI 烟雾:调度器只读命令全过(list/status/collect)+ collect 解析出口 0。""" +import subprocess +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent + + +def run(*args: str) -> subprocess.CompletedProcess: + return subprocess.run( + [sys.executable, "tasks.py", *args], + cwd=ROOT, capture_output=True, text=True, timeout=120, + ) + + +def test_list_ok(): + r = run("list") + assert r.returncode == 0, r.stderr + assert "总" in r.stdout or "C-01" in r.stdout + + +def test_status_ok(): + r = run("status") + assert r.returncode == 0, r.stderr + assert "PLANNING 看板" in r.stdout + + +def test_collect_ok(): + r = run("collect") + assert r.returncode == 0, r.stderr + assert "C-01" in r.stdout and "S-01" in r.stdout diff --git a/docs/e2e-proof/C-03-python-proof.png b/docs/e2e-proof/C-03-python-proof.png new file mode 100644 index 0000000..cb4c7e5 Binary files /dev/null and b/docs/e2e-proof/C-03-python-proof.png differ diff --git a/docs/e2e-proof/C-03-timeout-proof.png b/docs/e2e-proof/C-03-timeout-proof.png new file mode 100644 index 0000000..b7425f6 Binary files /dev/null and b/docs/e2e-proof/C-03-timeout-proof.png differ diff --git a/infra/README.md b/infra/README.md new file mode 100644 index 0000000..ad84243 --- /dev/null +++ b/infra/README.md @@ -0,0 +1,36 @@ +# infra 使用说明(I-02 · 本仓 CI + 备份) + +本目录只收本仓(vscode-workbench)基建,不碰各项目仓、不含任何密钥。 + +## backup.sh(备份) + +```bash +bash infra/backup.sh +# → backups/planning-backup-YYYYMMDD-HHMMSS.tar.gz + .sha256 +``` + +- 内容:`PLANNING/` + `docs/e2e-proof/`(任务资产 + 验证证据)。 +- 不含:`(服务器数据)`、`docker.env`、任何 key(见 `.gitignore`,`backups/` 不入库)。 +- 保留:最近 7 个归档自动轮转。 + +## restore.sh(恢复演练) + +```bash +bash infra/restore.sh backups/planning-backup-.tar.gz +# → sha256 校验 → 解到临时目录 → 断言 00/02 + C-03 证据存在 → 清理 +``` + +2026-09-14 实测:`planning-backup-20260914-034448.tar.gz` → `restore: OK`。 + +## CI + +- `.gitea/workflows/planning-ci.yml`:push/PR 触发,跑 `tasks.py list/status/collect` + + `pytest tools/test_ci_smoke.py`(3 用例)。无私有凭据,Gitea Actions 与 GitHub Actions 均可直接用。 +- 四项目 CI 现状:DSP 已有 `.github/workflows/ci.yml`(backend+web+android); + chunyu/english-drill/dealerhub 暂无 workflow——由各仓自行落地,本仓不代写。 + +## 回滚 + +- 本仓基建全是新增文件,回滚即删:`infra/`、`.gitea/`、`PLANNING/tools/test_ci_smoke.py`、 + `docs/e2e-proof/`、`PROGRESS_S-02/I-02`。 +- 备份归档在本地 `backups/`(git 忽略),删仓前拷一份即走。 diff --git a/infra/backup.sh b/infra/backup.sh new file mode 100644 index 0000000..fcf9698 --- /dev/null +++ b/infra/backup.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# I-02 本仓备份脚本:打包 PLANNING(任务资产)+ docs/e2e-proof(验证证据)→ 带 sha256 的归档。 +# 不含任何密钥:只收 PLANNING + docs/e2e-proof;(服务器数据)/.env/docker.env 一律不碰。 +set -euo pipefail +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +STAMP="$(date +%Y%m%d-%H%M%S)" +OUT_DIR="$ROOT/backups" +mkdir -p "$OUT_DIR" +ARCHIVE="$OUT_DIR/planning-backup-$STAMP.tar.gz" +tar -czf "$ARCHIVE" -C "$ROOT" PLANNING docs/e2e-proof +sha256sum "$ARCHIVE" > "$ARCHIVE.sha256" +# 保留策略:只留最近 7 个归档 +ls -1t "$OUT_DIR"/planning-backup-*.tar.gz 2>/dev/null | tail -n +8 | xargs -r rm -f +ls -1t "$OUT_DIR"/planning-backup-*.tar.gz.sha256 2>/dev/null | tail -n +8 | xargs -r rm -f +echo "backup: $ARCHIVE" +cat "$ARCHIVE.sha256" diff --git a/infra/restore.sh b/infra/restore.sh new file mode 100644 index 0000000..c82cdda --- /dev/null +++ b/infra/restore.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# I-02 本仓恢复演练:校验归档 → 解到临时目录 → 断言关键文件存在 → 清理。 +set -euo pipefail +ARCHIVE="${1:?用法: restore.sh }" +TMP="$(mktemp -d)" +trap 'rm -rf "$TMP"' EXIT +sha256sum -c "$ARCHIVE.sha256" +tar -xzf "$ARCHIVE" -C "$TMP" +test -f "$TMP/PLANNING/02-任务总表.md" +test -f "$TMP/PLANNING/00-全局路线图.md" +test -f "$TMP/docs/e2e-proof/C-03-timeout-proof.png" +echo "restore: OK ($ARCHIVE → $TMP)" +ls "$TMP/PLANNING" | head -5