Files
tps-dashboard/logs/phase2_log.md
T

21 KiB
Raw Blame History

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 — 后端 CRUD2026-05-15

  1. modelsepisode.py(软引用+快照)、yearly_target.pyyear UNIQUE
  2. schemasepisode.pyyearly_target.pyuser_admin.pyauth.py 顺手改 ConfigDict
  3. APIusers.py(管理员 CRUD)、episodes.py(增删改查)、yearly_targets.py(增删改查,新增 DELETE
  4. 路由注册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 200year 不变) 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 报 500NameError: name 'YearlyTarget' is not defined

原因api/yearly_targets.pyapi/episodes.py 用了 Episode / YearlyTarget 类但顶部漏了 import 语句

排查:同步检查 api/users.pyapi/episodes.pyapi/yearly_targets.py 三文件,发现 episodes.py 也漏了

修复

  • episodes.pyfrom app.models.episode import Episode
  • yearly_targets.pyfrom app.models.yearly_target import YearlyTarget
  • users.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(数据已存在)

修复:改为幂等版脚本,先清理同名数据再创建

坑 4pip 默认路由到系统 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)" 确认实际调用的解释器,再执行安装。


坑 5002b 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"。


坑 6Gitea OAuth refresh token 过期导致 git push 反复 401

现象git push 多次返回 401 Unauthorized,Git 凭证管理器反复弹框,输入正确账密后仍然失败。

原因Gitea OAuth access token 有效期到期,refresh token 也已失效,导致 Git 凭证无法续期。多次 401 后需重新完成 OAuth 认证流程刷新 token。

修复

  • 删掉旧 tokenGit 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 待解决",不写入"完工"状态

纪律 2SQL / 代码 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 相关权限:当前 UserUpdate Schema 允许改 is_activeroledisplay_nameprofile_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-productionPhase 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 进行中坑与纪律警告


九、Task 2 完工记录(2026-05-19 commit 442d52f

9.1 冒烟结果(8 步全绿)

Step 描述 预期 实际 结果
1 GET /api/imports/template 200 xlsx 200 xlsx (5595 bytes)
2 POST /api/imports/episodes 合法行 200 success=1 200 success=1 failed=0
3 POST 部分失败(1ok+2bad 200 errors=2 200 errors=2 row_number=[3,4]
4 GET /api/imports/errors/{batch_id} 200 xlsx 200 xlsx (5181 bytes)
5 POST 2024 无年度目标 400 400 含"年度目标"+"2024"
6 POST 重复期次(库内重复) 数据库约束触发 failed_count=1 UniqueViolation
7 POST 未知编导"李不存在"软落地 200 GET id 确认 null 200 id=22 editor_id=null
8 GET /api/episodes/{真实id} 验收 200 editor_id=null 200

smoke 脚本:backend/scripts/import_smoke.py(幂等版,可重复跑)


9.2 坑 7pandas 2.x 不接受 raw bytesBytesIO 包装解决)

发现时间2026-05-19 第一轮冒烟

现象pd.read_excel(file_content) 传入 bytespandas 2.x 报错或读不出数据。

原因pandas 2.x 要求 file-like object,不再直接接受 bytes。

修复excel_service.py 中用 io.BytesIO(file_content) 包装后传给 pd.read_excel

commit442d52f


坑 8openpyxl add_named_style 误用(4ca24dc 原代码遗留)

发现时间2026-05-19 冒烟 Step 4 下载失败行 Excel

现象generate_error_excel() 调用 wb.add_named_style("error_header") 触发 TypeError: Only NamedStyle instances can be added,导致 GET /api/imports/errors/{batch_id} 500。

来源:经 git show 4ca24dc 确认,该 bug 在 4ca24dc 原代码中已存在,非修坑引入。4ca24dc 自检只发现了 pandas bytes 问题,未发现此第二个 bug。

修复:删除 wb.add_named_style("error_header") 这一行(该样式未实际使用)。

教训commit 前自测要覆盖所有接口路径,不能只测主要流程。Step 1-3 通过不等于 Step 4 也通。


9.3 业务红线澄清:Step 6 重复期次拦截的两套机制

两套拦截路径,各管各的事,不能混为一谈:

  1. 文件内重复 → check_duplicates() 整体 409

    • 检测 Excel 内部是否有重复的 (year, episode_number)
    • ExcelService.check_duplicates() 在入库前调用
    • 有重复则整体拒绝,整文件不入库,返回 HTTP 409
  2. 库内重复 → 数据库 UNIQUE 约束 单行失败,其他行入库

    • 检测 Excel 中某行与数据库已有记录重复
    • 由 PostgreSQL idx_episodes_year_number 唯一约束触发
    • 重复行报错进入 errors[],其他行继续入库,返回 HTTP 200

Step 6 实测的是第二套(库内重复):Excel 写入 episode_number=99(与 Step 2 已入库的重复),数据库约束抛出 UniqueViolation,整行失败,success=0 failed=1。这两套机制分管文件内重复和库内重复,职责清晰。


9.4 纪律 3:启动期三件套不能证明接口业务路径通

背景:Task 2 开发期间,启动期(imports.py + excel_service.py + schemas/imports.py)写完后只跑了 GET 接口自测,POST/PATCH/DELETE 接口靠理论推导认为没问题就直接 commit。实际冒烟时发现 Step 2/3/4/5/6/7 全是 POST,全部出问题。

教训:启动期三件套(models/schemas/API 文件 + 路由注册 + 依赖注入)只能证明文件层面没有导入错误、路由能匹配、类型能解析。不能证明接口的业务路径(读写数据库、事务提交、错误处理)正常。任何 POST/PUT/PATCH/DELETE 接口 commit 前必须发真请求验证业务路径。

落地规矩

  • 所有变更(SQL 文件、后端代码、前端代码)在 commit 前必须经过本机验证
  • POST/PUT/PATCH/DELETE 接口必须用真请求实测,不能靠代码逻辑推导
  • 发现错误当场修,不把问题带进 commit

9.5 遗留 bugsmoke 脚本 Step 7 写错(已改正)

发现时间2026-05-19 冒烟 Step 7 第一版

现象GET /api/episodes/300 返回 404"在库中不存在"。

原因smoke 脚本把 episode_number 当 id 传给 GET /api/episodes/{id}。后端 GET /api/episodes/{id} 接收的是数据库自增主键 id,不是业务期号 episode_number。episode_number=300 的记录入库后真实 id 是 14(或列表查询返回的其他值),不是 300。

改正:先从 GET /api/episodes?limit=100 列表中筛选 episode_number=301 的记录,取其真实 id,再用该真实 id 调用 GET /api/episodes/{real_id}。确保 GET /api/episodes/{id} 路径被实际验收。

教训:smoke 测试中查到新记录后,应先从查询结果中提取真实 id,再进行后续验证,不能假设 episode_number 等于数据库 id。


9.6 Task 2 收尾清单(还未做的四件小尾巴)

  • 补录 2024 真值年度目标Step 5 冒烟用到 2024 年,当前库中无 2024 目标,补录后 Step 5 才能真正通过
  • Windows python 默认 PATH 改指向 3.12:坑 4 未完全解决,py 命令仍可能路由到系统 Python 3.14,需在系统 PATH 中显式优先 3.12
  • backups/ 进 .gitignore:虽然 .gitignore 有 *.log 等模式,但 backups/ 目录显式加入更稳,避免样本数据误传
  • MiniMax 自检报告里 schema 类名对照 Plan 自查ImportError/ImportResult vs UploadTemplateResponse/ImportResultResponse 等命名差异对着 Plan 确认是否一致

十、Git commit 记录(2026-05-19 追加)

commit 内容
442d52f feat: 完善批量导入逻辑,8步冒烟全绿

本日志由 MiniMax M2.7(Cline Act 模式)于 2026-05-15 持续追加,2026-05-19 追加 Task 2 完工坑与红线澄清,2026-05-20 追加 Task 2 收尾记录。


十一、Task 2 收尾完工记录(2026-05-20)

11.1 Git 安全卫生:token 从 URL 剥离

背景:.git/config 里 origin 远程 URL 含明文 token(cline-tps-dashboard), 存在被 Cline 误贴日志、截图泄露、备份带出等风险。

改动:

  • git remote set-url origin 改成干净 URL(去掉 user:token@)
  • token 改由 Windows Git Credential Manager (manager) 加密存储
  • 两次 fetch 静默通过,push 走 GCM 透明工作
  • 旧 token cline-tps-dashboard 继续使用,不吊销

纪律备注:以后任何凭证(密码、API key、token)都不许写进 URL、不许写进 .py 文件、 不许写进任何会被 commit 的文件。

11.2 小尾巴 1:补录 2024 真值

操作:制片人手动 psql 直接 INSERT(绕过 API,因 API 权限收紧待办未做)。

当前 yearly_targets 真值:

  • 2024: base=0.6448 / stretch=0.8290 (issued_date=2024-01-01, 补录说明在 notes)
  • 2025: base=0.6448 / stretch=0.8989
  • 2026: base=0.6448 / stretch=0.8989

11.3 小尾巴 3:backups/ 进 .gitignore

commit:20c0a1d(chore: backups/ 进 .gitignore) push:已成功

11.4 小尾巴 4:schema 类名自查通过

类名与 Task 2 Plan 一致,无漂移。ImportError / ImportResult 命名正确。

发现 2 个隐患,暂不处理,记为待办:

  • 隐患 A:ImportError 与 Python 内置同名,当前未撞,未来若在导入逻辑里 raise 才会冲突
  • 隐患 B:ExcelService.import_episodes() 返回 dict 而非 Pydantic 对象, API 层手动构造 ImportResult,缺一次 Pydantic 校验层。8 步冒烟已验证业务正确, 仅代码洁癖问题

11.5 小尾巴 2:Python PATH 根治坑 4

改动:从系统 PATH 删除 C:\Python314\Scripts\C:\Python314\ 两行。 用户 PATH 中 Python 3.12 自动顶上来。

验证:

  • where.exe python 第一行变成 3.12 路径
  • python --version = Python 3.12.10
  • sys.executable 指向 3.12

遗留:E:\tps-dashboard\backend\ 下没有 venv,Python 依赖装在系统 3.12 全局 site-packages 里。记入待办,Phase 4 部署前或 Task 3 启动前评估是否补 venv。

11.6 待办清单(Task 3 启动前或后续 Phase 处理)

待办 触发时机 优先级
yearly_targets 写权限收紧到 zhipianren + zebian,biandao 只读 Task 3 启动前 高(业务红线)
backend/ 补 venv,把全局依赖搬进去 Task 3 启动前评估
隐患 A:ImportError 同名冲突预防 真要 raise 时再说
隐患 B:ExcelService 返回 Pydantic 化 代码洁癖,有空时做

11.7 Task 2 完整 commit 链(2026-05-19 → 2026-05-20)

commit 内容
442d52f feat: 完善批量导入逻辑,8步冒烟全绿(2026-05-19)
6de8111 docs: Task 2 完工记录,坑7/8/纪律3/Q1-Q4 写入(2026-05-19)
20c0a1d chore: backups/ 进 .gitignore(Task 2 收尾)(2026-05-20)

Task 2 完整收尾,Phase 2 Task 2 状态: 完工。


第十二章 yearly_targets 写权限收紧(2026-05-20

状态: 完工。冒烟 12/12 全绿。

12.1 任务来源

task brief 明确要求收紧 yearly_targets 表的写权限,是业务红线。

12.2 改动文件

文件 改动内容
backend/app/api/yearly_targets.py 5处 require_role 参数修正
backend/app/schemas/yearly_target.py YearlyTargetUpdate 改为部分字段可选(支持 PATCH 部分更新)
backend/scripts/smoke_yearly_targets_auth.py 新建,12步幂等冒烟脚本
README.md 新增"角色权限表"节
.clinerules 5.5 新增 yearly_targets 写权限收紧说明
logs/phase2_log.md 本章记录

12.3 权限矩阵最终状态

接口 zhipianren zebian biandao
GET 列表/详情 200 200 200
POST 新增 201 201 403
PATCH 修改 200 200 403
DELETE 删除 204 403 403

12.4 GET 权限修改说明

GET 权限修改原本不在 task brief 范围,是 Plan 阶段自查发现并扩大的范围。

Opus 审 Plan 时同意纳入,理由:权限矩阵整体校正一次做完更合理。

此条在 Opus 审方案 v2 中明确记录,phase2_log 本章也写入此说明,不得糊涂带过。

12.5 冒烟实测记录

backend/scripts/smoke_yearly_targets_auth.py12步结果:

步骤 场景 预期 结果
1 zhipianren POST 201
2 zebian POST 201
3 biandao POST 403
4 未登录 POST 401
5 zhipianren PATCH 200
6 zebian PATCH 200
7 biandao PATCH 403
8 zhipianren DELETE 204
9 zebian DELETE 403
10 biandao DELETE 403
11 biandao GET 列表 200
12 zhipianren GET 列表 200

12/12 全部通过。

12.6 测试账号保留

冒烟脚本末尾显式打印保留账号(供后续手动清理):

  • test_zebian_smoke (zebian)
  • test_biandao_smoke (biandao)