Commit Graph

5 Commits

Author SHA1 Message Date
5392345078 feat(score): R173/R174 大题成绩 Excel 导入(三小问四列制)+ 三节点备份调度器与四项修复
成绩导入(R173 落地 → R174 改为三小问四列制)
- app/lib/bigScoreImport.js(新增 · 判据唯一真相源 · 零依赖:不 require Egg / 不连库 / 不读文件)
  · R174 删除 splitScoreAcrossSubs(均分),改为 scoreSubs 逐项取值
  · 留空 SUB_MISSING(整行拒绝,⛔ 不按 0 分)· 非数值 SUB_INVALID · 超单问上限 SUB_OUT_OF_RANGE
  · 文件级 LEGACY_TEMPLATE(旧两列模板)与 SUB_COLUMNS_MISSING(缺一小问整列)⇒ 整份拒绝
  · 定位键必须带 group:(group, team_uid),因 team_uid 跨组重号
- app/service/bigQuestion.js
  · buildScoreTemplate 改四列 + 说明书重写(删均分段,补"三列必须全填"与"旧模板不再支持")
  · previewScoreImport 回带 subRaws/subs/total;importScores 逐项写 setSubScores
  · ⛔ 不新增第二条写成绩通路:仍只走 setSubScores → admin.updateScores
- app/lib/docx.js:run 支持 color(6 位 hex);w:color 必须排在 w:sz 之前(CT_RPr sequence)
- app/controller/admin.js + app/router.js:import-template / import-preview(零写入)/ import-scores
  · 上传内存解析不落盘(app/public 对静态服务开放 ⇒ 成绩表可被猜名下载)

节点备份调度器(R168/R170)
- app/lib/nodeBackupScheduler.js(新增):考试开始前 30min / 结束后 10min / 成绩发布瞬间
  · 幂等键 group:node:fireAtISO(时间点即身份);overdue 判定用 clamp:false 的数学时刻
  · 非生产 enabled 强制 false 且 fail-safe(与 DEPLOY_ENV_LOCAL_SKIP_PREFIXES 同源)
- config/config.default.js:config.nodeBackup 四项路径全走环境变量,默认值只覆盖本地
  (生产目录布局与仓库不一致,⛔ 不许用 __dirname 推导)
- app.js:启动构造单例 + hydrate 未到期计划

缺陷修复
- app/controller/admin.js:R161 临时文件路径移到 try 外声明,否则导入抛错即跳过清理
- app/service/admin.js:R166「已答 N / N 题」恒真显示(原 totalQuestions = answeredCount)
- app/service/upload.js + app/controller/upload.js:R157/R162 删除结果计数 + @no-temp-cleanup 标注

测试
- .gitignore + test/lib.test.js:R134 候选 ㊴ —— test/* + !test/*.test.js,
  使第 26 步裸 mocha 在干净检出上可复现(改前 rc=1 ↔ 改后 23 passing)
2026-10-19 13:03:48 +08:00
a019df510d chore(security): 处置已入库的本地 DB 口令(P1-P3;P4 改历史不做)
背景(R14 登记的 §16.3):
  git grep -l "74191221" HEAD 曾命中 7 个文件,全为本地 MariaDB root 口令
  (已核实无生产凭据)。本次按登记项执行 P1-P3;P4(git filter-repo 改写
  历史)明确不做。

P1  database/config.json(dev/test 两段明文口令)
  - 加入 .gitignore
  - 新增 database/config.json.example(口令为 CHANGE_ME 占位符)
  - git rm --cached(保留磁盘文件,本地 sequelize-cli 不受影响)
  - 注意:这是对共享仓库的文件移除。push 后其他克隆需
    cp database/config.json.example database/config.json 并填入口令。

P2  五个脚本口令环境变量化(去明文兜底)
  - add_word_cols.js / add_wx_link.js / show_tables.js / seed-carousel.js
    内联 '74191221' 改为 process.env.DB_PASSWORD(另支持 DB_HOST/DB_USER/DB_NAME)
  - scripts/diagnose_upload_download.cjs:process.env.DB_PASS || '74191221'
    改为 process.env.DB_PASS;用法注释中的示例口令改为占位符
  - 有意的行为变更:未注入 DB_PASSWORD 时明确失败,不再静默使用公开口令

P3  config/config.default.js 消除「生产静默回退公开口令」
  - sequelize.password 去掉 || '74191221'
  - redis.client.password 去掉 || '74191221'
  - 生产安全守卫新增 DB_PASSWORD 缺失阻断(与 JWT_SECRET/APP_KEYS 同源同形)
  - deploy/.env.production 本就含 DB_PASSWORD ⇒ 现网零影响

验证(全部实测):
  - git grep 74191221 工作树零命中(HEAD 仍有,属 P4 不做范围)
  - 裸启动(无任何 DB 变量)成功:本地口令由 config.local.js 提供
  - 端到端:health db:ok + admin-login code=0
  - MySQL 侧独立取证:processlist = root @ competition_db
  - 变异测试(改坏 config.local.js 口令)⇒ 报 1045 且用户名仍为 root
    ⇒ 同证 P3 兜底确已移除 + R18 的生产账号过滤仍生效
  - P2 双向验证:无 DB_PASSWORD 失败 / 注入后正常列 27 张表
  - 门禁四连:verify_scoring_e2e 17/0、verify_exam_submit_integrity 46/0、
    verify_big_question --cleanup(清理+窗口还原)、check_no_proxy_guard 5 变异全检出
2026-09-23 16:34:08 +08:00
4f1e28d643 fix(config): 本地启动不再被生产 DB 凭据污染(loadDeployEnv 收口 + config.local 兜底)
问题(2026-09-23 用户报障):
  本地 `npm run dev` 启动即崩:
  SequelizeAccessDeniedError: Access denied for user '<生产库账号>'@'localhost' (using password: YES)
  → [agent_worker] start error, exiting with code:1 → master 退出 → 7001 无监听。

根因:
  config/config.default.js 顶部的 loadDeployEnv() 在**模块加载期**无条件把
  deploy/.env.production 的全部 27 个键灌进 process.env,其中 DB_USER 指向生产库账号,
  而本机 MySQL 并无该账号。且 deploy/ 不在任何 git 仓库内 ⇒ 该文件变更无 diff、无 review。

改动 A —— 新增 config/config.local.js(已由 .gitignore 排除,不入库):
  仅在 serverEnv=local 时被 Egg 加载并「覆盖」config.default.js。
  合并语义已取证:egg-core/lib/loader/mixin/config.js:61-71 `_preloadAppConfig()`
  对 ['config.default', `config.${serverEnv}`] 逐项执行 extend(true, ...) ⇒ 深合并、后者覆盖前者。
  本文件只覆盖 sequelize.username / password 两个字段,
  dialect/database/host/port/timezone/define/dialectOptions/pool/logging 全部沿用 default。
  生产 serverEnv=prod ⇒ Egg 只找 config.prod.js ⇒ 本文件不加载 ⇒ 零生产影响。

改动 B —— config/config.default.js:
  ① 执行时机:模块级 IIFE → module.exports(appInfo) 内部调用(此时 appInfo.env 已确定);
  ② 作用范围:非生产环境跳过 DB_ / REDIS_ / ALIYUN_ / SMS_ 前缀键
     (连接类 / 凭据类 / 网关开关类),并在日志打印被跳过的键名(绝不打印值)。
  生产判定 = appInfo.env==='prod' || NODE_ENV==='production'。生产必然成立的三重依据:
     a) egg-scripts/lib/cmd/start.js:109 无条件 `env.NODE_ENV='production'`;
     b) deploy/.env.production:17 NODE_ENV=production,且 start-backend.sh 以
        `set -a; source "$ENV_FILE"` 注入 shell 后再 exec node;
     c) start.js:126 注释:argv → EGG_SERVER_ENV → 兜底 egg 取 prod。
  ⇒ 生产**不跳过任何键**,行为逐键不变。外部显式注入的键仍优先(判定在 `!== undefined` 之后)。

改动 C —— .gitignore:排除 config/config.local.js(含本地口令)。

验证(全部实测):
  · 双向对照探针:本地跳过 15 键、生产跳过 0 键且 DB_USER 正常注入;
    合并后 pool.max=20 / define.charset=utf8mb4 / dialectOptions.charset 均保留 ⇒ 深合并成立。
  · 纯裸启动(不带任何 DB 变量):egg started on :7001;health db:ok;admin-login code=0;
    MySQL 侧 information_schema.processlist 独立取证 user=root / db=competition_db。
  · 变异测试(config.local 口令改为错值):报 1045 且报错用户名仍是 root
    ⇒ 一次实验同时证明 A 被加载且优先生效、B 已挡住生产账号。
  · 门禁回归:verify_scoring_e2e 17/0、verify_big_question --cleanup 52/0、
    verify_exam_submit_integrity 46/0、check_no_proxy_guard 5 变异全检出。

不变量(本次未改,勿顺手改):
  · deploy/.env.production 一字未动 —— README L57 明确它是**拷往服务器的权威源**,改它生产连不上库;
  · sequelize 的 dialect/database/host/port/timezone/define/pool/logging 全部沿用 config.default.js;
  · 环境变量优先级不变:外部显式注入 > .env.production 兜底 > 代码默认值。
2026-09-23 15:59:41 +08:00
WorkBuddy
f281d206fb chore: 后端 .gitignore 补正(忽略 test/ 非必须目录)
按"上传目的=运行必须源码"准则,test/ 为代码运行非必须目录,须排除;修正此前误写的 test_dir/ 归类,新增明确 Tests 段覆盖 test/ 与 test_dir/。
2026-08-17 12:34:27 +08:00
WorkBuddy
a303cf2d53 chore: 后端首次基线提交
运行必须源码:app/(controller/model/service/middleware/lib/utils/validate/router)、config/、database/(migrations/seeders/sql)、启动脚本、app/public/templates 模板。
已忽略运行非必须:node_modules/、logs/、run/、app/public 运行时上传数据、.env*、构建/缓存产物。
2026-08-17 12:25:42 +08:00