|
|
070195a22c
|
docs(backend): 同步 worker 数注释(生产 4 -> 2,与 2 核匹配)
仅注释变更,**无行为变更**。EGG_WORKERS 实际由 deploy/.env.production 注入 +
deploy/start-backend.sh 兜底决定,本次把注释里写死的旧口径同步为 2,避免
「注释与配置漂移」再次误导后人。
- app.js:
· 「生产显式设为 4」-> 2
· 「4×20=80(安全)」->「2×20=40(安全)」
· 「本地 dev 也会加载 .env.production,其中写着 4」-> 2
· 「dev 下真 worker=1 却报 4×20=80」-> 2×20=40
- config/config.default.js:
· 「生产值写在 deploy/.env.production(EGG_WORKERS=4)」-> 2(并注明 2026-09-29
R40 由 4 校正为 2)
· 「4 × 20 = 80」->「2 × 20 = 40」
· pool 段「worker 数由 EGG_WORKERS 显式控制(生产 = 4)」-> 2
· 「本机/多数服务器为 8 核 → 8 个 worker」->「核数越多起得越多(开发机 8 核 → 8 个)」
(原文把"8 核"当成多数服务器的事实,与实测的 2 核服务器不符)
背景(R40 · BACKLOG C2):worker 数 ≈ CPU 核数。worker 数 > 核数对 CPU 型路径
(bcrypt 登录)**零收益** —— 加 worker 不加核(native bcrypt 走 libuv threadpool),
只增加 context switch 与内存(单 worker 空闲 RSS ≈108MB)。登录容量由物理核数
决定(实测 ≈12.6 次/核·秒)⇒ 2 与 4 的登录容量相同。连接池 2×20=40 ≤ 151-20 ✅
验证:node --check app.js / config/config.default.js 通过;
verify-c1c2.py 含「五处文档/代码注释已无旧口径「4」残留」断言(24/24 通过)。
|
2026-09-29 12:34:38 +08:00 |
|
|
|
079c1b31c9
|
R20:客观题分值口径 + 大题一位小数评分 + 页面刷新修复 + 大题导出 Word
A1/A2 客观题:
- 清理题库占位题(答案恒 A/true、分值恒 5),客观题分值硬约束为 1 分
- 新增 scripts/check_question_bank_health.py(带变异注入自检,已验证判别力)
- ci-gates.sh 由 9 步扩为 10 步,纳入题库健康门禁
B 大题评分一位小数:
- 新增 app/lib/decimal_score.js:DECIMAL(6,1) + getter 兜底,单点收口
「DB 列 / model 声明 / dialectOptions.decimalNumbers」三处必须同时成立
- 10 个 model 的 score 字段改走该工厂
- database/migrations/025:17 列 INT → DECIMAL(6,1)(6 列题目分值保持 int)
- service/bigQuestion.js:setSubScores 用 Math.round(n*10)/10 收敛,不再抹成整数
C 页面状态刷新:
- 修复 onQuestionTypeChange 的 `bigQuestions.length === 0` 守卫(切组后旧数组仍非空
⇒ 永不重新拉取)
- 修复评分后只刷一半(saveBigScores / saveExamScore 各只刷一侧列表)
- 收敛为唯一刷新入口 refreshQuestionData();ensureBigData 改按「数据属于哪个组」
判定;fetchBigQuestions/fetchBigAnswers 增加组别竞态守卫
D 大题导出 Word(单题 / 批量 / 全部):
- 新增 app/lib/docx.js:零新依赖手写 OOXML(复用已有 archiver@8),支持 p/strong/
span/a/br/table/ul-ol 子集,具名捕获组词法解析,pPr 严格排在 runs 之前
- service.bigQuestion.exportQuestions + controller.exportBigQuestions +
GET /api/admin/big-questions/:group/export
- 前端导出选中/全部两按钮 + 表格多选(reserve-selection)
计划外发现并修复(已在报告中披露):
- 管理端跨组写分越权:setSubScores 定位作答行时缺 group 约束,可用任意组的路径
改写他组考生成绩(读写不对称:读取侧有 where:{group},写入侧没有)
⇒ 已补 group 约束,并区分「不属于本组」与「尚未抽题」两种文案
- 导出路由注册顺序:export 原注册在 :group/:id 之后(当时因后者仅 PUT/DELETE 而
未暴露问题),已前移以消除「将来新增 GET /:group/:id 会吞掉 export」的隐患
验证(全部实测,非仅看退出码):
- ci-gates.sh 10/10 全绿;四连门禁 52/0 · 22/0 · 20/0 · 46/0
- 端到端探针 probe_d_03(导出+小数)ALL PASS;probe_e_01(切组刷新)22/22 ALL PASS
- 前端构建 VITE_EXIT=0
未 push:须先上线验证通过再单独授权。
|
2026-09-23 18:29:50 +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 |
|
|
|
23604f2718
|
feat(exam/auth): 登录闸门 + 交卷稳定性加固(R4-R8)
R4 登录/交卷背压闸门
- app/middleware/concurrency_guard.js: 新增。超限即 429 + Retry-After(立即拒绝式,
不排队;实测 429 P50=34ms)。RETRY_AFTER 收敛为单一有依据常量 = '2'
(≈1.8 个满载窗口;实测 bcryptjs 1129ms / 原生 265ms 每窗口)。
配额:LOGIN_MAX=16 / SUBMIT_MAX=24 / HEAVY_REQ_MAX=20,各 *REQ_MAX 覆盖。
- config/config.default.js: 闸门配额与连接池容量配置;DB dialect 锁 mysql。
R7 顺序红线 + R8 Retry-After 同源 + 原生 bcrypt
- 3 处硬编码 '2' 收敛为单一常量;补"勿因换实现而删闸门"红线注释。
- bcryptjs -> bcrypt@6(N-API,Windows/Linux 均免编译;drop-in,存量哈希 $2b$10$ 兼容)。
- app/model/account.js / app/service/auth.js: 同步哈希实现注释(D4 全族 5 处陈旧
SHA256 注释修正)。
答题链路与综合大题
- app/controller/exam.js: 中职/高职综合大题接口(GET big-question / POST big-answer,
逐问 2000 字上限校验);uploadWord 对中职/高职封堵(400)。
- app/service/exam.js: 抽题快照 question_snapshot 落库、答案归集键修正。
- app/controller/auth.js / app/service/auth.js: toClientUser() 唯一出口,
未发布态按组剔除 score,exam_score/word_score/ppt_score 任何情况下不下发。
- app/router.js: 新增大题与管理端路由。
- app/extend/helper.js: 下载文件名 RFC5987 编码(禁双重编码)。
- app.js: 池容量断言按 env 区分单/多 worker(防 dev 假日志)。
验证:ci-gates.sh 9/9 全绿;verify_login_guard.py 28/28(含 D 组变异 9/9);
真实登录/抽题/交卷端到端 code=0;前端构建 BUILD_RC=0。
|
2026-09-22 09:16:55 +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 |
|