89 lines
3.6 KiB
JavaScript
89 lines
3.6 KiB
JavaScript
|
|
'use strict';
|
|||
|
|
|
|||
|
|
const fs = require('fs');
|
|||
|
|
const path = require('path');
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Egg.js 应用启动钩子 - AppBootHook
|
|||
|
|
* 表结构通过 migration/seeder 管理,不在启动时自动同步
|
|||
|
|
*/
|
|||
|
|
class AppBootHook {
|
|||
|
|
constructor(app) {
|
|||
|
|
this.app = app;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async willReady() {
|
|||
|
|
// 数据库表结构由 migration 管理,不在启动时自动 sync
|
|||
|
|
this.app.logger.info('[AppBoot] 服务启动完成');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 进程就绪后,确保 app/public 下所有业务目录存在。
|
|||
|
|
*
|
|||
|
|
* 关键背景(生产踩坑):
|
|||
|
|
* app/public/{works,answers,materials,tmp,...} 这类上传目录通常被 .gitignore 忽略,
|
|||
|
|
* 每次「整目录重新部署」(rsync --delete / 重新 clone) 都会被清空。
|
|||
|
|
* 若运行中的代码某条写入路径恰好没有即时 mkdir,就会 ENOENT → 上传失败 / 下载 404,
|
|||
|
|
* 且旧版 auth 中间件会把这类磁盘异常伪装成「401 登录过期」误导排查。
|
|||
|
|
* 此处统一在启动时把全部已知写入目录递归建好,作为永久兜底,
|
|||
|
|
* 与各处写入点的 fs.mkdirSync(recursive:true) 形成双保险。
|
|||
|
|
*/
|
|||
|
|
async didReady() {
|
|||
|
|
const publicDir = path.join(this.app.baseDir, 'app/public');
|
|||
|
|
const groups = ['zhongzhi', 'gaozhi', 'benke', 'jiaoshi'];
|
|||
|
|
|
|||
|
|
// 需要按「目录/组别」创建的(组别子目录)
|
|||
|
|
const grouped = ['works', 'answers', 'materials', 'templates'];
|
|||
|
|
// 需要直接创建(无组别)的
|
|||
|
|
const flat = ['tmp', 'tmp/excel', 'tmp/zip', 'carousel', 'news'];
|
|||
|
|
|
|||
|
|
const targets = [];
|
|||
|
|
for (const d of grouped) {
|
|||
|
|
for (const g of groups) targets.push(path.join(publicDir, d, g));
|
|||
|
|
}
|
|||
|
|
for (const d of flat) targets.push(path.join(publicDir, d));
|
|||
|
|
|
|||
|
|
let created = 0;
|
|||
|
|
let existed = 0;
|
|||
|
|
for (const t of targets) {
|
|||
|
|
try {
|
|||
|
|
if (!fs.existsSync(t)) {
|
|||
|
|
fs.mkdirSync(t, { recursive: true });
|
|||
|
|
created++;
|
|||
|
|
} else {
|
|||
|
|
existed++;
|
|||
|
|
}
|
|||
|
|
} catch (e) {
|
|||
|
|
this.app.logger.error('[AppBoot] 创建上传目录失败', { target: t, message: e.message });
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
this.app.logger.info(
|
|||
|
|
`[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。
|
|||
|
|
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 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`
|
|||
|
|
);
|
|||
|
|
} else {
|
|||
|
|
this.app.logger.info(`[AppBoot][pool] 连接池容量 OK:${estimated} (${workers} × ${poolMax}) ≤ MySQL max_connections=${dbMax}`);
|
|||
|
|
}
|
|||
|
|
} catch (e) {
|
|||
|
|
this.app.logger.warn('[AppBoot][pool] 连接数自举断言跳过(无法查询 MySQL):' + (e && e.message));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
module.exports = AppBootHook;
|