From 683df58d12e3343d8e39b8989bd1a53323945c26 Mon Sep 17 00:00:00 2001 From: Jerry Date: Sat, 3 May 2025 23:40:10 +0800 Subject: [PATCH] =?UTF-8?q?=E6=A8=A1=E5=9D=97=E8=B7=AF=E7=94=B1=E5=8A=A0?= =?UTF-8?q?=E8=BD=BD=E4=BC=98=E5=8C=96=EF=BC=8C=E7=B3=BB=E7=BB=9F=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E9=87=8D=E5=90=AF&=E8=B7=AF=E7=94=B1=E6=96=B9?= =?UTF-8?q?=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- src/app.ts | 84 +++++++++++++++++-------- src/modules/bot/bot.controller.ts | 16 ++--- src/modules/bot/bot.service.ts | 3 + src/modules/system/system.controller.ts | 62 ++++++++++++++++++ src/modules/system/system.service.ts | 17 +++++ src/test/wsTestClient.ts | 4 +- src/utils/core/path.ts | 5 +- src/utils/core/response.ts | 7 ++- src/utils/core/system.ts | 40 ++++++++++++ src/utils/modules/tools.ts | 29 +++++++++ start.sh | 6 ++ 12 files changed, 237 insertions(+), 38 deletions(-) create mode 100644 src/modules/system/system.controller.ts create mode 100644 src/modules/system/system.service.ts create mode 100644 src/utils/core/system.ts create mode 100644 src/utils/modules/tools.ts create mode 100644 start.sh diff --git a/README.md b/README.md index 41a844d..3c64df9 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ## 构建: - pnpm i - pnpm build -- pnpm start +- bash start.sh ## 使用: - public/files/image/图片 支持多级目录 diff --git a/src/app.ts b/src/app.ts index 9e6c0dc..1ac25ea 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,50 +1,82 @@ import express from 'express'; +import compression from 'compression'; +import fs from 'fs'; +import path from 'path'; import logger from './utils/core/logger'; import paths from './utils/core/path'; -import sampleController from './modules/sample/sample.controller'; -import imageController from './modules/image/file.controller'; import config from './utils/core/config'; import './services/ws/wsServer'; -import compression from 'compression'; -import testController from './modules/test/test.controller'; -import BotController from './modules/bot/bot.controller'; +import System from './utils/core/system'; const apps = { async createApp() { const app = express(); - paths.init(); logger.info('晶灵核心初始化..'); + app.use(express.json()); app.use(compression()); - logger.debug('成功加载express.json()中间件'); + logger.debug('成功加载 express.json() 中间件'); const publicPath = paths.get('public'); app.use('/public', express.static(publicPath)); - logger.debug(`静态资源路由挂载:/public => ${publicPath}`); + logger.debug(`静态资源路由挂载: /public => ${publicPath}`); - const modules = [ - { path: '/api/sample', name: '测试模块', controller: sampleController }, - { path: '/public', name: '文件模块', controller: imageController }, - { path: '/api/test', name: '测试', controller: testController }, - { path: '/api/bot', name: '寄气人模块', controller: BotController }, - ]; + const modulesDir = path.resolve(__dirname, './modules'); + const controllerPattern = /\.controller\.[jt]s$/; - modules.forEach((module) => { - app.use(module.path, module.controller.getRouter()); - logger.debug(`模块路由挂载: ${module.path.padEnd(12)} => ${module.name}`); + if (!fs.existsSync(modulesDir)) { + logger.warn(`未找到模块目录: ${modulesDir}`); + } else { + const moduleFolders = fs.readdirSync(modulesDir).filter((folder) => { + const fullPath = path.join(modulesDir, folder); + return fs.statSync(fullPath).isDirectory(); + }); - if (config.get('DEBUG', false)) { - module.controller.getRouter().stack.forEach((layer) => { - if (layer.route) { - const methods = Object.keys(layer.route) - .map((m) => m.toUpperCase()) - .join(','); - logger.debug(` ↳ ${methods.padEnd(6)} ${module.path}${layer.route.path}`); + for (const folder of moduleFolders) { + const folderPath = path.join(modulesDir, folder); + const files = fs.readdirSync(folderPath).filter((f) => controllerPattern.test(f)); + + for (const file of files) { + const filePath = path.join(folderPath, file); + + try { + //logger.debug(`尝试加载模块: ${moduleUrl}`); + const controllerModule = require(filePath); + const controller = controllerModule.default; + + if (controller?.getRouter) { + const routePath = `/api/${folder}`; + app.use(routePath, controller.getRouter()); + logger.debug(`模块路由挂载: ${routePath.padEnd(12)} => ${file}`); + + if (config.get('DEBUG', false)) { + controller.getRouter().stack.forEach((layer: any) => { + if (layer.route) { + const methods = Object.keys(layer.route.methods || {}) + .map((m) => m.toUpperCase()) + .join(','); + logger.debug(` ↳ ${methods.padEnd(6)} ${routePath}${layer.route.path}`); + } + }); + } + } else { + logger.warn(`模块 ${file} 没有导出 getRouter 方法,跳过..`); + } + } catch (err) { + logger.error(`模块 ${file} 加载失败:`, err); } - }); + } } - }); + } + + const duration = System.checkRestartTime(); + //logger.info(duration); + if (duration) { + logger.warn(`重启完成!耗时 ${duration} 秒..`); + fs.writeFileSync('/temp/restart_time', duration.toString()); + } + logger.info('晶灵核心初始化完毕!'); return app; }, diff --git a/src/modules/bot/bot.controller.ts b/src/modules/bot/bot.controller.ts index f01b4d1..9fb56f8 100644 --- a/src/modules/bot/bot.controller.ts +++ b/src/modules/bot/bot.controller.ts @@ -1,7 +1,7 @@ import express from 'express'; import response from '../../utils/core/response'; import BotService from './bot.service'; -import Config from '../../utils/core/config'; +import tools from '../../utils/modules/tools'; class BotController { private readonly router: express.Router; @@ -19,19 +19,19 @@ class BotController { this.router.post(`/getBotId`, this.postBotsId); } + /** + * 获取当前连接到核心的全部botId数组 + * @param req + * @param res + */ private postBotsId = async (req: express.Request, res: express.Response): Promise => { try { const token = req.body.token; - if (token.toString() === Config.get('TOKEN').toString()) { + if (tools.checkToken(token.toString())) { const result = await BotService.getBotId(); await response.success(res, result); } else { - await response.error( - res, - 'token验证失败..', - 404, - `有个小可爱使用了错误的token:${JSON.stringify(token)}` - ); + await tools.tokenCheckFailed(res, token); } } catch (err) { await response.error(res, `请求失败..`, 500, err); diff --git a/src/modules/bot/bot.service.ts b/src/modules/bot/bot.service.ts index cb0eff5..03cd96b 100644 --- a/src/modules/bot/bot.service.ts +++ b/src/modules/bot/bot.service.ts @@ -5,6 +5,9 @@ import path from 'path'; import redisService from '../../services/redis/redis'; class BotService { + /** + * 获取botId数组 + */ public async getBotId() { logger.debug('GetBotId..'); const userPath = paths.get('userData'); diff --git a/src/modules/system/system.controller.ts b/src/modules/system/system.controller.ts new file mode 100644 index 0000000..8e71728 --- /dev/null +++ b/src/modules/system/system.controller.ts @@ -0,0 +1,62 @@ +import express from 'express'; +import tools from '../../utils/modules/tools'; +import response from '../../utils/core/response'; +import SystemService from './system.service'; + +class SystemController { + private readonly router: express.Router; + + constructor() { + this.router = express.Router(); + this.init(); + } + + public getRouter(): express.Router { + return this.router; + } + + private init(): void { + this.router.post('/restart', this.systemRestart); + this.router.post('/getRestartTime', this.getRestartTime); + } + + /** + * 系统重启路由 + * @param req + * @param res + */ + private systemRestart = async (req: express.Request, res: express.Response): Promise => { + try { + const token = req.body.token; + if (tools.checkToken(token.toString())) { + await SystemService.systemRestart(); + await response.success(res, '核心正在重启..'); + } else { + await tools.tokenCheckFailed(res, token); + } + } catch (e) { + await response.error(res); + } + }; + + /** + * 获取重启所需时间 + * @param req + * @param res + */ + private getRestartTime = async (req: express.Request, res: express.Response): Promise => { + try { + const token = req.body.token; + if (tools.checkToken(token.toString())) { + const time = await SystemService.getRestartTime(); + await response.success(res, time); + } else { + await tools.tokenCheckFailed(res, token); + } + } catch (e) { + await response.error(res); + } + }; +} + +export default new SystemController(); diff --git a/src/modules/system/system.service.ts b/src/modules/system/system.service.ts new file mode 100644 index 0000000..ad3b893 --- /dev/null +++ b/src/modules/system/system.service.ts @@ -0,0 +1,17 @@ +import System from '../../utils/core/system'; +import fs from 'fs/promises'; +import logger from '../../utils/core/logger'; + +class SystemService { + public async systemRestart() { + logger.debug(`有个小可爱正在请求重启核心..`); + await System.restart(); + } + + public async getRestartTime() { + logger.debug(`有个小可爱想知道核心重启花了多久..`); + return await fs.readFile('/temp/restart_time', 'utf8'); + } +} + +export default new SystemService(); diff --git a/src/test/wsTestClient.ts b/src/test/wsTestClient.ts index 4b8450f..8bdcbe6 100644 --- a/src/test/wsTestClient.ts +++ b/src/test/wsTestClient.ts @@ -48,7 +48,9 @@ async function testGetAPI() { async function testPostAPI() { try { - const response = await axios.post('http://localhost:4000/api/bot/getBotId', { token: 54188 }); + const response = await axios.post('http://localhost:4000/api/system/getRestartTime', { + token: 54188, + }); console.log('[HTTP][POST] Response:', response.data); } catch (err) { console.error('[HTTP][POST] Error:', err); diff --git a/src/utils/core/path.ts b/src/utils/core/path.ts index 86e65fb..2d4d903 100644 --- a/src/utils/core/path.ts +++ b/src/utils/core/path.ts @@ -36,6 +36,7 @@ class PathManager { files: path.join(this.baseDir, 'public/files'), media: path.join(this.baseDir, 'public/files/media'), package: path.join(this.baseDir, 'package.json'), + modules: path.join(this.baseDir, 'src/modules'), }; return type ? mappings[type] : this.baseDir; @@ -63,6 +64,7 @@ class PathManager { this.get('config'), this.get('userData'), this.get('media'), + this.get('temp'), ]; pathsToInit.forEach((dirPath) => { @@ -83,7 +85,8 @@ type PathType = | 'userData' | 'files' | 'package' - | 'media'; + | 'media' + | 'modules'; const paths = PathManager.getInstance(); export default paths; diff --git a/src/utils/core/response.ts b/src/utils/core/response.ts index ceb871c..50c7ac8 100644 --- a/src/utils/core/response.ts +++ b/src/utils/core/response.ts @@ -23,7 +23,12 @@ class response { * @param statusCode HTTP状态码,默认500 * @param error 原始错误对象(开发环境显示) */ - public static async error(res: Response, message: string, statusCode = 500, error?: any) { + public static async error( + res: Response, + message: string = '请求失败..', + statusCode = 500, + error?: any + ) { const response: Record = { success: false, message, diff --git a/src/utils/core/system.ts b/src/utils/core/system.ts new file mode 100644 index 0000000..80a20ff --- /dev/null +++ b/src/utils/core/system.ts @@ -0,0 +1,40 @@ +import path from 'path'; +import paths from './path'; +import fs from 'fs'; +import logger from './logger'; + +const restartFile = path.join(paths.get('temp'), 'restart.timestamp'); +class System { + /** + * 重启前保存时间戳 + */ + private static markRestartTime() { + const now = Date.now(); + fs.writeFileSync(restartFile, now.toString(), 'utf-8'); + } + + /** + * 检查重启时间戳 + */ + public static checkRestartTime() { + if (fs.existsSync(restartFile)) { + const prev = Number(fs.readFileSync(restartFile, 'utf-8')); + const duration = ((Date.now() - prev) / 1000 - 5).toFixed(2); + fs.unlinkSync(restartFile); + return Number(duration); + } + return null; + } + + /** + * 重启服务 + */ + public static async restart() { + this.markRestartTime(); + logger.warn('服务即将重启..'); + await new Promise((r) => setTimeout(r, 300)); + process.exit(0); + } +} + +export default System; diff --git a/src/utils/modules/tools.ts b/src/utils/modules/tools.ts new file mode 100644 index 0000000..7975e06 --- /dev/null +++ b/src/utils/modules/tools.ts @@ -0,0 +1,29 @@ +import express from 'express'; +import response from '../core/response'; +import Config from '../core/config'; + +let tools = { + /** + * token验证错误处理逻辑 + * @param res + * @param token + */ + async tokenCheckFailed(res: express.Response, token: string): Promise { + await response.error( + res, + 'token验证失败..', + 404, + `有个小可爱使用了错误的token:${JSON.stringify(token)}` + ); + }, + + /** + * 检查token是否正确 + * @param token + */ + checkToken(token: string): boolean { + return token.toString() === Config.get('TOKEN').toString(); + }, +}; + +export default tools; diff --git a/start.sh b/start.sh new file mode 100644 index 0000000..7cf70a4 --- /dev/null +++ b/start.sh @@ -0,0 +1,6 @@ +while true; do + echo "启动服务..." + pnpm start + echo "服务退出,5秒后重启..." + sleep 5 +done