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。
This commit is contained in:
44
app.js
44
app.js
@@ -61,23 +61,53 @@ class AppBootHook {
|
||||
`[AppBoot] 上传目录就绪:已创建 ${created} 个,已存在 ${existed} 个(根: ${publicDir})`
|
||||
);
|
||||
|
||||
// 🔴 连接池容量自举断言(2026-08-13 推进 #16):egg-cluster 按 os.cpus() 起多 worker,
|
||||
// 总连接数 ≈ worker 数 × pool.max。若超过 MySQL max_connections(默认 151),
|
||||
// 高峰期会偶发 "connection acquire timeout" / "too many connections"。
|
||||
// 启动期只做 WARN(不阻断启动),提醒运维调大 DB 或调小 DB_POOL_MAX。
|
||||
// 🔴 连接池容量自举断言(2026-08-13 推进 #16):总连接数 = worker 数 × pool.max。
|
||||
// 若超过 MySQL max_connections(默认 151),高峰期会偶发 "connection acquire
|
||||
// timeout" / "too many connections"。启动期只做 WARN(不阻断启动)。
|
||||
//
|
||||
// 🔴 2026-09-21 修正(并发压测结论):原实现用 `os.cpus().length` 估 worker 数,
|
||||
// 这是**误估** —— 真实 worker 数由 egg-scripts 的 --workers / EGG_WORKERS 决定
|
||||
// (生产显式设为 4,见 config.default.js「集群 worker 数」段),与 CPU 核数无必然关系。
|
||||
// 误估会在 8 核机上把「4×20=80(安全)」误报成「8×20=160(越限)」,
|
||||
// 既制造假告警、也会掩盖真实的越限,使这条断言失去作用。
|
||||
// 改为优先读 EGG_WORKERS(worker 进程继承 master 的环境变量),回退才用 CPU 数,
|
||||
// 并在日志中显式标注估算依据,便于排障时判断该行可信度。
|
||||
try {
|
||||
const os = require('os');
|
||||
const poolMax = parseInt(process.env.DB_POOL_MAX, 10) || 20;
|
||||
const rows = await this.app.model.query('SELECT @@max_connections AS m', { type: this.app.model.QueryTypes.SELECT });
|
||||
const dbMax = parseInt(rows && rows[0] && rows[0].m, 10) || 151;
|
||||
const workers = os.cpus().length;
|
||||
const explicitWorkers = parseInt(process.env.EGG_WORKERS, 10);
|
||||
const usingExplicit = Number.isFinite(explicitWorkers) && explicitWorkers > 0;
|
||||
// 🔴 2026-09-21 二次修正:egg-bin dev / test 会**显式传** `workers: 1`
|
||||
// (node_modules/egg-bin/scripts/start-cluster.js),此时即便环境里存在
|
||||
// EGG_WORKERS(本地 dev 也会加载 deploy/.env.production,其中写着 4),
|
||||
// 也不是真实 worker 数 —— 真实值是 1。故必须先按 app.config.env 判定
|
||||
// 单 worker 的运行形态,否则本地仍会打出一条假数字日志(实测:dev 下
|
||||
// 真 worker=1 却报 4×20=80)。
|
||||
const envName = (this.app.config && this.app.config.env) || '';
|
||||
const singleWorkerEnv = envName === 'local' || envName === 'unittest';
|
||||
let workers;
|
||||
let workersSource;
|
||||
if (singleWorkerEnv) {
|
||||
workers = 1;
|
||||
workersSource = `egg env=${envName}(egg-bin 强制单 app worker)`;
|
||||
} else if (usingExplicit) {
|
||||
workers = explicitWorkers;
|
||||
workersSource = `EGG_WORKERS=${explicitWorkers}(显式)`;
|
||||
} else {
|
||||
workers = os.cpus().length;
|
||||
workersSource = `os.cpus()=${workers}(EGG_WORKERS 未设置,为回退估算)`;
|
||||
}
|
||||
const estimated = workers * poolMax;
|
||||
if (estimated > dbMax) {
|
||||
this.app.logger.warn(
|
||||
`[AppBoot][pool] 预估总连接数 ${estimated} (${workers} workers × ${poolMax}) 超过 MySQL max_connections=${dbMax},高峰期可能连接获取超时;建议调大 DB max_connections 或调小 DB_POOL_MAX`
|
||||
`[AppBoot][pool] 预估总连接数 ${estimated} (${workersSource} × DB_POOL_MAX=${poolMax}) 超过 MySQL max_connections=${dbMax},高峰期可能连接获取超时;建议调大 DB max_connections 或调小 DB_POOL_MAX / EGG_WORKERS`
|
||||
);
|
||||
} else {
|
||||
this.app.logger.info(`[AppBoot][pool] 连接池容量 OK:${estimated} (${workers} × ${poolMax}) ≤ MySQL max_connections=${dbMax}`);
|
||||
this.app.logger.info(
|
||||
`[AppBoot][pool] 连接池容量 OK:${estimated} (${workersSource} × DB_POOL_MAX=${poolMax}) ≤ MySQL max_connections=${dbMax}`
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
this.app.logger.warn('[AppBoot][pool] 连接数自举断言跳过(无法查询 MySQL):' + (e && e.message));
|
||||
|
||||
Reference in New Issue
Block a user