10 KiB
Phase 2 开发日志 — 仪表盘真数据 + 用户管理 + Excel 批量导入
《军事科技》栏目内部工作台 · Phase 2 撰写日期:2026-05-15 状态:Task 1 后端 CRUD 完成,冒烟实测 6/6 通过;Task 2 进行中
一、本 Phase 完成时间
- 开始日期:2026-05-15
- Task 1 后端 CRUD:2026-05-15 完成,冒烟 6/6 全绿
- Task 2 Excel 批量导入:进行中
- Task 3 前端:待开始
- 预计 Phase 2 整体收官:待定
二、本 Phase 做了什么
Task 1 — 后端 CRUD(2026-05-15)
- models:
episode.py(软引用+快照)、yearly_target.py(year UNIQUE) - schemas:
episode.py、yearly_target.py、user_admin.py、auth.py顺手改 ConfigDict - API:
users.py(管理员 CRUD)、episodes.py(增删改查)、yearly_targets.py(增删改查,新增 DELETE) - 路由注册:
main.py注册新路由
三、Task 1 冒烟实测记录(2026-05-15 Cline 自跑)
使用 backend/scripts/smoke_test.py(幂等版可重复跑),结果:
| 步骤 | 接口 | 预期 | 实际 | 结果 |
|---|---|---|---|---|
| 1 | POST /api/users 新建 zebian | 201 | 201 | ✅ ID=4 |
| 2 | POST /api/yearly_targets 新建 2026 | 201 | 201 | ✅ ID=2 |
| 3 | POST /api/yearly_targets 重复 2026 | 409 | 409 | ✅ 正确拦截 |
| 4 | POST /api/episodes editor_id=null | 201 | 201 | ✅ ID=2 |
| 5 | GET /api/episodes 列表 | 200 | 200 | ✅ |
| 6 | PATCH /api/yearly_targets 尝试改 year | 200(year 不变) | 200 | ✅ year 仍为 2026 |
6/6 全部通过。
四、业务红线验证记录
| 红线 | 代码位置 | 验证结果 |
|---|---|---|
| 收视颜色只对 audience_share | episode.py 注释 |
✅ 未实现颜色逻辑,仅存字段 |
| 年度目标只增不改 year UNIQUE | yearly_targets.py create_target + yearly_target.py UNIQUE |
✅ 冲突报 409 |
| year 字段禁止 UPDATE 修改 | yearly_targets.py update_target pop("year") |
✅ 冒烟 Step 6 验证 |
| editor_id 可空+editor_name_snapshot 必填 | episode.py Field 设计 |
✅ 冒烟 Step 4 editor_id=null 成功 |
| 角色枚举全拼音 | UserRole Enum + CHECK 约束 |
✅ 三角色全是拼音 |
| ON CONFLICT DO NOTHING(非 UPDATE) | yearly_targets.py 注释 |
✅ 只增不改 |
| password_hash 明文传输开发配置 | security.py + main.py https_only=False |
⚠️ Phase 5 部署前必改 |
五、踩过的坑(Phase 2 内部教训)
坑 1:API 文件漏了模型类导入(一类 bug)
现象:Swagger UI 实测 POST /api/yearly_targets 报 500,NameError: name 'YearlyTarget' is not defined
原因:api/yearly_targets.py 和 api/episodes.py 用了 Episode / YearlyTarget 类但顶部漏了 import 语句
排查:同步检查 api/users.py、api/episodes.py、api/yearly_targets.py 三文件,发现 episodes.py 也漏了
修复:
episodes.py加from app.models.episode import Episodeyearly_targets.py加from app.models.yearly_target import YearlyTargetusers.py删掉未使用的verify_password导入
教训:新建 API 文件后,第一件事检查所有模型类是否已 import,与 Schema import 同等优先级。Task 1 完工报告前未做最小冒烟测试,导致这类问题漏到实测阶段才发现。以后每个 Task Act 完成后必须自跑冒烟,不依赖制片人发现。
坑 2:冒烟脚本 emoji + 中文导致 Windows GBK 崩溃
现象:UnicodeEncodeError: 'gbk' codec can't encode character '\u274c'
修复:将 smoke_test.py 全部改英文 + ASCII 字符(✅→[OK],❌→[FAIL])
坑 3:数据不幂等导致重跑失败
现象:第二次跑冒烟,Step 1/2/4 全部 409(数据已存在)
修复:改为幂等版脚本,先清理同名数据再创建
坑 4:pip 默认路由到系统 Python 3.14,绕过项目锁定的 3.12
现象:pip install pandas openpyxl 后报错,或运行时报 ModuleNotFoundError: No module named 'pandas'(已确认装过)
原因:系统 PATH 中 python 命令指向 Microsoft Store 的 Python 3.14,而项目要求 Python 3.12,导致装到了错误的解释器。pip show pandas 发现路径在 AppData/Local/Microsoft/WindowsApps 而非项目 venv。
修复:
- 确认项目使用
python -m pip install ...(明确调项目锁定的 3.12) - 或激活 venv 后
& .\venv\Scripts\Activate.ps1再 pip install - 装完
pip show pandas核对路径包含venv或项目目录
教训:任何 pip install 必须验证装到了哪个 Python 解释器。先用 where python 确认 PATH 优先级,用 python -c "import sys; print(sys.executable)" 确认实际调用的解释器,再执行安装。
坑 5:002b SQL 表达式索引使用 EXTRACT(...)::int 语法不被 PostgreSQL 接受
现象:CREATE INDEX idx_episodes_broadcast_date ON episodes (EXTRACT(year FROM broadcast_date)::int); 在 pgAdmin / psql 中执行报错:ERROR: syntax error at or near "::"
原因:PostgreSQL 的 EXTRACT() 返回 double precision 类型,::int 是标准 cast 语法,但某些 PostgreSQL 版本或客户端对表达式索引内 cast 有限制。SQL 在 commit 前未本机干跑直接进库,翻车后才发现。
修复:改为 date_part('year', broadcast_date) 语法,等效且兼容性更好:
CREATE INDEX idx_episodes_broadcast_date ON episodes (date_part('year', broadcast_date));
教训:SQL 文件 commit 前必须在本机干跑。即使是看似简单的 DDL,本机跑一遍能提前发现语法兼容性问题,避免入库后才发现翻车。Task 1 NameError(坑 1)与本坑同源,都是"没自测就 commit"。
坑 6:Gitea OAuth refresh token 过期导致 git push 反复 401
现象:git push 多次返回 401 Unauthorized,Git 凭证管理器反复弹框,输入正确账密后仍然失败。
原因:Gitea OAuth access token 有效期到期,refresh token 也已失效,导致 Git 凭证无法续期。多次 401 后需重新完成 OAuth 认证流程刷新 token。
修复:
- 删掉旧 token:Git Bash 执行
git credential reject+ 重置 credential helper - 重新触发 GitHub/Gitea OAuth 流程获取新 access token
- 验证:
git push成功后确认 remote URL 使用新的 token
教训:push 不成功不算完工。本次 Task 2 期间 push 已全部恢复正常,之前的 git push 条目从"认证失败,需制片人手动"已转为✅完成。
六、纪律警告(Phase 2 Task 2 期间立)
纪律 1:"push 待制片人手动处理"不算完工
背景:Task 1 期间 MiniMax 多次以"push 待手动处理"作为完工结尾。本 chat 期间明确规矩。
教训:push 是 Git 工作流最后一环,未 push 就不是真正的完工。Git commit 只代表本地变更保存,push 成功才代表远程仓库同步完成。在任务报告中以"push 待手动处理"结尾会误导协作者认为工作已完成。
落地规矩:任何任务的完工报告必须包含"git push 成功"状态才算闭环。若 push 遇到认证问题:
- 优先排查 token 状态(坑 6 经验)
- 不得以"待手动处理"为由跳过 push 环节
- push 失败时用红字标注"⚠️ push 待解决",不写入"完工"状态
纪律 2:SQL / 代码 commit 前必须本机干跑
背景:Task 1 NameError(坑 1)+ Task 2 002b SQL 语法错(坑 5)同源,都是 commit 前没自测直接进库。
教训:任何变更(SQL 文件、后端代码、前端代码)在 commit 前必须经过本机验证。对于 SQL 文件,本机用 psql -U postgres -d milsci_dev -f backend/sql/00X_xxx.sql 干跑;对于 Python 代码,用 python -m pytest 或冒烟脚本验证。发现错误当场修,不把问题带进 commit。
落地规矩:
- SQL 文件:commit 前用 psql 在本机 dev 数据库干跑,确认无语法错误
- Python 代码:至少跑一遍冒烟测试或单元测试,确认基本功能正常
- 禁止用"这只是小改/语法简单/理论上没问题"为由跳过自测
- 养成习惯:commit 前自测,发现问题当场修,比入库后发现 cost 低一个数量级
七、遗留事项
7.1 Phase 2 内部待完成
- Task 1 后端 CRUD:✅ 冒烟 6/6 全绿
- git push:✅ 已恢复正常(坑 6 已解决)
- Task 2:Excel 批量导入(收视数据 + 排期模板)进行中
- Task 3:前端仪表盘 + 录入界面 + 用户管理界面
7.2 跨 Phase 遗留
Phase 4 或后续 Task 处理
- PATCH /api/users 接口 password 相关权限:当前
UserUpdateSchema 允许改is_active、role、display_name、profile_text,但:password_hash不允许通过 API 修改(当前设计)- 能否改自己密码?不能(当前设计)
- 改别人密码是否强制对方下次登录改密?未实现
- 建议 Phase 4+ 新增独立
/api/users/change-password接口,设计时拉 Op
Phase 5 部署时处理(与 SECRET_KEY 并列)
-
传输安全:当前
password在请求体明文传输,仅 HTTPS 环境下可接受。Phase 5 部署到腾讯云后:- 改
same_site="strict"+https_only=True - 确认 HTTPS 开启,强制全站 TLS
- 考虑 API 请求体密码加密或单独
/api/auth/change-password接口
- 改
-
SECRET_KEY 占位符:
.env当前是change-me-to-a-random-string-in-production,Phase 5 部署前必须改为真实随机值。生成命令:python -c "import secrets; print(secrets.token_urlsafe(32))"
八、Git commit 记录
| commit | 内容 |
|---|---|
| 143601c | docs: 合入第十三章 Opus task brief 节制原则,版本升至 v5 |
| 33bee0c | feat: 后端 CRUD 完成(episodes + yearly_targets + users 管理 API) |
| 63c031a | docs: Task 1 完工记录,Swagger UI 验收清单,跨 Phase 遗留事项 |
| 4f7c92d | fix: 修复 episodes.py + yearly_targets.py 模型导入缺失,删 users.py 残留 verify_password,增 DELETE 接口(幂等冒烟后确认 6/6 全绿) |
| f82a71b | docs: phase2_log 追加 Task 2 进行中坑与纪律警告 |
本日志由 MiniMax M2.7(Cline Act 模式)于 2026-05-15 持续追加,2026-05-18 追加 Task 2 进行中坑与纪律警告。