feat(M4): S-02部署基线复核+I-02本仓CI与备份演练+储备评审;M3遗留C-03/S-01/C-04证据补齐;12项PROGRESS归属+collect解析修复
vscode-planning-ci / planning-smoke (push) Failing after 1m31s

This commit is contained in:
2026-09-14 03:46:37 +08:00
parent ec55b5a845
commit 5b3850c191
23 changed files with 827 additions and 499 deletions
+42 -40
View File
@@ -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/\<front\>,大小写归一,重词 order_by id 确定性),
`SeoQuestionView`(/question/\<id\>,public_question 白名单,**不含答案/解析**),
`SeoBookView`(/book/\<id\>,词数/章节概览),`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/<front>`,
公开白名单:释义/音标/例句/同课相关词,无用户数据)。
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 → <title>abandon 是什么意思…</title> + 释义正文(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/\<front\>,大小写归一,重词 order_by id 确定性),
`SeoQuestionView`(/question/\<id\>,public_question 白名单,**不含答案/解析**),
`SeoBookView`(/book/\<id\>,词数/章节概览),`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/<front>`,
公开白名单:释义/音标/例句/同课相关词,无用户数据)。
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 → <title>abandon 是什么意思…</title> + 释义正文(E2E 同等价覆盖)
```
踩坑:harness `hit()` 默认按 JSON 解析——直出 HTML/XML 用例改原生 resp 断状态码 + 正文关键字。
## 状态
done。遗留:sitemap loc 为站内相对路径(部署 nginx 按真实域名拼 canonical,见 deploy/nginx.conf 接线时处理);
词面 440 卡去重 421(19 个跨词书重词,重词页 order_by id 取首个,诚实记录)。