模块路由加载优化,系统自动重启&路由方法

This commit is contained in:
Jerry 2025-05-03 23:40:10 +08:00
parent 832bf3704c
commit 683df58d12
12 changed files with 237 additions and 38 deletions

View File

@ -1,7 +1,7 @@
## 构建: ## 构建:
- pnpm i - pnpm i
- pnpm build - pnpm build
- pnpm start - bash start.sh
## 使用: ## 使用:
- public/files/image/图片 支持多级目录 - public/files/image/图片 支持多级目录

View File

@ -1,20 +1,19 @@
import express from 'express'; import express from 'express';
import compression from 'compression';
import fs from 'fs';
import path from 'path';
import logger from './utils/core/logger'; import logger from './utils/core/logger';
import paths from './utils/core/path'; 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 config from './utils/core/config';
import './services/ws/wsServer'; import './services/ws/wsServer';
import compression from 'compression'; import System from './utils/core/system';
import testController from './modules/test/test.controller';
import BotController from './modules/bot/bot.controller';
const apps = { const apps = {
async createApp() { async createApp() {
const app = express(); const app = express();
paths.init(); paths.init();
logger.info('晶灵核心初始化..'); logger.info('晶灵核心初始化..');
app.use(express.json()); app.use(express.json());
app.use(compression()); app.use(compression());
logger.debug('成功加载 express.json() 中间件'); logger.debug('成功加载 express.json() 中间件');
@ -23,28 +22,61 @@ const apps = {
app.use('/public', express.static(publicPath)); app.use('/public', express.static(publicPath));
logger.debug(`静态资源路由挂载: /public => ${publicPath}`); logger.debug(`静态资源路由挂载: /public => ${publicPath}`);
const modules = [ const modulesDir = path.resolve(__dirname, './modules');
{ path: '/api/sample', name: '测试模块', controller: sampleController }, const controllerPattern = /\.controller\.[jt]s$/;
{ path: '/public', name: '文件模块', controller: imageController },
{ path: '/api/test', name: '测试', controller: testController },
{ path: '/api/bot', name: '寄气人模块', controller: BotController },
];
modules.forEach((module) => { if (!fs.existsSync(modulesDir)) {
app.use(module.path, module.controller.getRouter()); logger.warn(`未找到模块目录: ${modulesDir}`);
logger.debug(`模块路由挂载: ${module.path.padEnd(12)} => ${module.name}`); } else {
const moduleFolders = fs.readdirSync(modulesDir).filter((folder) => {
const fullPath = path.join(modulesDir, folder);
return fs.statSync(fullPath).isDirectory();
});
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)) { if (config.get('DEBUG', false)) {
module.controller.getRouter().stack.forEach((layer) => { controller.getRouter().stack.forEach((layer: any) => {
if (layer.route) { if (layer.route) {
const methods = Object.keys(layer.route) const methods = Object.keys(layer.route.methods || {})
.map((m) => m.toUpperCase()) .map((m) => m.toUpperCase())
.join(','); .join(',');
logger.debug(`${methods.padEnd(6)} ${module.path}${layer.route.path}`); 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('晶灵核心初始化完毕!'); logger.info('晶灵核心初始化完毕!');
return app; return app;
}, },

View File

@ -1,7 +1,7 @@
import express from 'express'; import express from 'express';
import response from '../../utils/core/response'; import response from '../../utils/core/response';
import BotService from './bot.service'; import BotService from './bot.service';
import Config from '../../utils/core/config'; import tools from '../../utils/modules/tools';
class BotController { class BotController {
private readonly router: express.Router; private readonly router: express.Router;
@ -19,19 +19,19 @@ class BotController {
this.router.post(`/getBotId`, this.postBotsId); this.router.post(`/getBotId`, this.postBotsId);
} }
/**
* botId数组
* @param req
* @param res
*/
private postBotsId = async (req: express.Request, res: express.Response): Promise<void> => { private postBotsId = async (req: express.Request, res: express.Response): Promise<void> => {
try { try {
const token = req.body.token; const token = req.body.token;
if (token.toString() === Config.get('TOKEN').toString()) { if (tools.checkToken(token.toString())) {
const result = await BotService.getBotId(); const result = await BotService.getBotId();
await response.success(res, result); await response.success(res, result);
} else { } else {
await response.error( await tools.tokenCheckFailed(res, token);
res,
'token验证失败..',
404,
`有个小可爱使用了错误的token:${JSON.stringify(token)}`
);
} }
} catch (err) { } catch (err) {
await response.error(res, `请求失败..`, 500, err); await response.error(res, `请求失败..`, 500, err);

View File

@ -5,6 +5,9 @@ import path from 'path';
import redisService from '../../services/redis/redis'; import redisService from '../../services/redis/redis';
class BotService { class BotService {
/**
* botId数组
*/
public async getBotId() { public async getBotId() {
logger.debug('GetBotId..'); logger.debug('GetBotId..');
const userPath = paths.get('userData'); const userPath = paths.get('userData');

View File

@ -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<void> => {
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<void> => {
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();

View File

@ -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();

View File

@ -48,7 +48,9 @@ async function testGetAPI() {
async function testPostAPI() { async function testPostAPI() {
try { 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); console.log('[HTTP][POST] Response:', response.data);
} catch (err) { } catch (err) {
console.error('[HTTP][POST] Error:', err); console.error('[HTTP][POST] Error:', err);

View File

@ -36,6 +36,7 @@ class PathManager {
files: path.join(this.baseDir, 'public/files'), files: path.join(this.baseDir, 'public/files'),
media: path.join(this.baseDir, 'public/files/media'), media: path.join(this.baseDir, 'public/files/media'),
package: path.join(this.baseDir, 'package.json'), package: path.join(this.baseDir, 'package.json'),
modules: path.join(this.baseDir, 'src/modules'),
}; };
return type ? mappings[type] : this.baseDir; return type ? mappings[type] : this.baseDir;
@ -63,6 +64,7 @@ class PathManager {
this.get('config'), this.get('config'),
this.get('userData'), this.get('userData'),
this.get('media'), this.get('media'),
this.get('temp'),
]; ];
pathsToInit.forEach((dirPath) => { pathsToInit.forEach((dirPath) => {
@ -83,7 +85,8 @@ type PathType =
| 'userData' | 'userData'
| 'files' | 'files'
| 'package' | 'package'
| 'media'; | 'media'
| 'modules';
const paths = PathManager.getInstance(); const paths = PathManager.getInstance();
export default paths; export default paths;

View File

@ -23,7 +23,12 @@ class response {
* @param statusCode HTTP状态码500 * @param statusCode HTTP状态码500
* @param error * @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<string, any> = { const response: Record<string, any> = {
success: false, success: false,
message, message,

40
src/utils/core/system.ts Normal file
View File

@ -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;

View File

@ -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<void> {
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;

6
start.sh Normal file
View File

@ -0,0 +1,6 @@
while true; do
echo "启动服务..."
pnpm start
echo "服务退出5秒后重启..."
sleep 5
done