diff --git a/package.json b/package.json index 58bca3a..155e57d 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "@nestjs/platform-ws": "^11.1.6", "@nestjs/swagger": "^11.2.0", "@nestjs/websockets": "^11.1.6", - "axios": "^1.10.0", + "axios": "1.11.0", "ioredis": "^5.6.1", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 20c6be0..d4c5da7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,7 +10,7 @@ importers: dependencies: '@nestjs/axios': specifier: ^4.0.1 - version: 4.0.1(@nestjs/common@11.1.5(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.10.0)(rxjs@7.8.2) + version: 4.0.1(@nestjs/common@11.1.5(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.11.0)(rxjs@7.8.2) '@nestjs/common': specifier: ^11.0.1 version: 11.1.5(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -36,8 +36,8 @@ importers: specifier: ^11.1.6 version: 11.1.6(@nestjs/common@11.1.5(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.5)(@nestjs/platform-socket.io@11.1.6)(reflect-metadata@0.2.2)(rxjs@7.8.2) axios: - specifier: ^1.10.0 - version: 1.10.0 + specifier: 1.11.0 + version: 1.11.0 ioredis: specifier: ^5.6.1 version: 5.6.1 @@ -1437,8 +1437,8 @@ packages: asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - axios@1.10.0: - resolution: {integrity: sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==} + axios@1.11.0: + resolution: {integrity: sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==} b4a@1.6.7: resolution: {integrity: sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==} @@ -4432,10 +4432,10 @@ snapshots: '@napi-rs/nice-win32-x64-msvc': 1.0.4 optional: true - '@nestjs/axios@4.0.1(@nestjs/common@11.1.5(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.10.0)(rxjs@7.8.2)': + '@nestjs/axios@4.0.1(@nestjs/common@11.1.5(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.11.0)(rxjs@7.8.2)': dependencies: '@nestjs/common': 11.1.5(reflect-metadata@0.2.2)(rxjs@7.8.2) - axios: 1.10.0 + axios: 1.11.0 rxjs: 7.8.2 '@nestjs/cli@11.0.7(@swc/cli@0.6.0(@swc/core@1.13.1)(chokidar@4.0.3))(@swc/core@1.13.1)(@types/node@22.16.5)': @@ -5191,7 +5191,7 @@ snapshots: asynckit@0.4.0: {} - axios@1.10.0: + axios@1.11.0: dependencies: follow-redirects: 1.15.9 form-data: 4.0.4 diff --git a/src/common/interceptors/response.interceptor.ts b/src/common/interceptors/response.interceptor.ts index 9975776..0d9f948 100644 --- a/src/common/interceptors/response.interceptor.ts +++ b/src/common/interceptors/response.interceptor.ts @@ -22,7 +22,7 @@ export class ResponseInterceptor map((data) => ({ success: true, data, - message: '操作成功', + message: '欢迎使用晶灵核心 | Welcome to use crystelf-core', })), ); } diff --git a/src/config/config.service.ts b/src/config/config.service.ts index 74ff027..5a08cdf 100644 --- a/src/config/config.service.ts +++ b/src/config/config.service.ts @@ -19,7 +19,7 @@ export class AppConfigService implements OnModuleInit { * @param key 键值 * @param defaultValue 默认 */ - get(key: string, defaultValue?: T): T | undefined { + public get(key: string, defaultValue?: T): T | undefined { const value = this.nestConfigService.get(key); if (value === undefined || value === null) { if (defaultValue !== undefined) { diff --git a/src/core/system/system.service.ts b/src/core/system/system.service.ts index 8405238..0e3b848 100644 --- a/src/core/system/system.service.ts +++ b/src/core/system/system.service.ts @@ -2,7 +2,6 @@ import { Inject, Injectable, Logger } from '@nestjs/common'; import * as path from 'path'; import * as fs from 'fs'; import { PathService } from '../path/path.service'; -import { AutoUpdateModule } from '../auto-update/auto-update.module'; import { AutoUpdateService } from '../auto-update/auto-update.service'; import * as process from 'node:process'; diff --git a/src/core/tools/tools.module.ts b/src/core/tools/tools.module.ts index c2bd867..67b070c 100644 --- a/src/core/tools/tools.module.ts +++ b/src/core/tools/tools.module.ts @@ -1,7 +1,9 @@ import { Module } from '@nestjs/common'; import { ToolsService } from './tools.service'; +import { AppConfigModule } from '../../config/config.module'; @Module({ + imports: [AppConfigModule], providers: [ToolsService], exports: [ToolsService], }) diff --git a/src/core/tools/tools.service.ts b/src/core/tools/tools.service.ts index 1da6a76..268a444 100644 --- a/src/core/tools/tools.service.ts +++ b/src/core/tools/tools.service.ts @@ -1,10 +1,19 @@ -import { Injectable, Logger } from '@nestjs/common'; +import { + Inject, + Injectable, + Logger, + UnauthorizedException, +} from '@nestjs/common'; import { RetryOptions } from './retry-options.interface'; +import { AppConfigService } from '../../config/config.service'; @Injectable() export class ToolsService { private readonly logger = new Logger(ToolsService.name); - + constructor( + @Inject(AppConfigService) + private readonly config: AppConfigService, + ) {} /** * 异步重试 * @param operation @@ -48,4 +57,26 @@ export class ToolsService { getRandomDelay(min: number, max: number): number { return Math.floor(Math.random() * (max - min + 1)) + min; } + + /** + * 检查 token 是否有效 + * @param token 待验证的 token + */ + checkToken(token: string): boolean { + const expected = this.config.get('TOKEN'); + if (!expected) { + this.logger.error('环境变量 TOKEN 未配置,无法进行验证!'); + throw new UnauthorizedException('系统配置错误,缺少 TOKEN'); + } + return token === expected; + } + + /** + * token 验证失败时的逻辑 + * @param token 无效的 token + */ + tokenCheckFailed(token: string): never { + this.logger.warn(`有个小可爱使用了错误的 token: ${JSON.stringify(token)}`); + throw new UnauthorizedException('token 验证失败..'); + } } diff --git a/src/core/ws/handlers/pong.handler.ts b/src/core/ws/handlers/pong.handler.ts index fc3bca1..dd4064d 100644 --- a/src/core/ws/handlers/pong.handler.ts +++ b/src/core/ws/handlers/pong.handler.ts @@ -1,11 +1,10 @@ -import { Injectable, Logger } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { AuthenticatedSocket } from '../../../types/ws/ws.interface'; import { IMessageHandler } from '../../../types/ws/ws.handlers.interface'; @Injectable() export class PongHandler implements IMessageHandler { type = 'pong'; - private readonly logger = new Logger(PongHandler.name); async handle(socket: AuthenticatedSocket, msg: any) { //this.logger.debug(`收到 pong 消息: ${JSON.stringify(msg)}`); diff --git a/src/core/ws/ws-message.handler.ts b/src/core/ws/ws-message.handler.ts index bd92f27..43dde4f 100644 --- a/src/core/ws/ws-message.handler.ts +++ b/src/core/ws/ws-message.handler.ts @@ -3,7 +3,6 @@ import { WsTools } from './ws.tools'; import { WsClientManager } from './ws-client.manager'; import { IMessageHandler } from '../../types/ws/ws.handlers.interface'; import { AuthenticatedSocket } from '../../types/ws/ws.interface'; -import { TestHandler } from './handlers/test.handler'; @Injectable() export class WsMessageHandler { diff --git a/src/modules/system/system.controller.ts b/src/modules/system/system.controller.ts new file mode 100644 index 0000000..ddaa587 --- /dev/null +++ b/src/modules/system/system.controller.ts @@ -0,0 +1,58 @@ +import { + Controller, + Post, + Body, + UnauthorizedException, + Inject, +} from '@nestjs/common'; +import { ApiTags, ApiOperation, ApiBody } from '@nestjs/swagger'; +import { SystemWebService } from './system.service'; +import { ToolsService } from '../../core/tools/tools.service'; + +class TokenDto { + token: string; +} + +@ApiTags('System') +@Controller('system') +export class SystemController { + constructor( + @Inject(SystemWebService) + private readonly systemService: SystemWebService, + @Inject(ToolsService) + private readonly toolService: ToolsService, + ) {} + + /** + * 重启系统 + */ + @Post('restart') + @ApiOperation({ + summary: '系统重启', + description: '传入正确的 token 后,核心将执行重启。', + }) + @ApiBody({ type: TokenDto }) + async systemRestart(@Body() body: TokenDto): Promise { + if (!this.toolService.checkToken(body.token)) { + throw new UnauthorizedException('Token 无效'); + } + await this.systemService.systemRestart(); + return '核心正在重启..'; + } + + /** + * 获取系统重启耗时 + */ + @Post('getRestartTime') + @ApiOperation({ + summary: '获取重启所需时间', + description: '传入正确的 token,返回上次核心重启的耗时', + }) + @ApiBody({ type: TokenDto }) + async getRestartTime(@Body() body: TokenDto): Promise { + if (!this.toolService.checkToken(body.token)) { + throw new UnauthorizedException('Token 无效'); + } + return await this.systemService.getRestartTime(); + } +} diff --git a/src/modules/system/system.module.ts b/src/modules/system/system.module.ts new file mode 100644 index 0000000..847abea --- /dev/null +++ b/src/modules/system/system.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { SystemController } from './system.controller'; +import { SystemWebService } from './system.service'; +import { ToolsModule } from '../../core/tools/tools.module'; +import { PathModule } from '../../core/path/path.module'; + +@Module({ + imports: [ToolsModule, SystemModule, PathModule], + controllers: [SystemController], + providers: [SystemWebService], +}) +export class SystemModule {} diff --git a/src/modules/system/system.service.ts b/src/modules/system/system.service.ts new file mode 100644 index 0000000..99cd0fb --- /dev/null +++ b/src/modules/system/system.service.ts @@ -0,0 +1,34 @@ +import { Inject, Injectable, Logger } from '@nestjs/common'; +import fs from 'fs/promises'; +import path from 'path'; +import { PathService } from '../../core/path/path.service'; +import { SystemService } from 'src/core/system/system.service'; + +@Injectable() +export class SystemWebService { + private readonly logger = new Logger(SystemWebService.name); + @Inject(SystemService) + private readonly system: SystemService; + @Inject(PathService) + private readonly pathService: PathService; + + /** + * 重启系统 + */ + async systemRestart(): Promise { + this.logger.debug(`有个小可爱正在请求重启核心..`); + await this.system.restart(); + } + + /** + * 获取上次重启所耗时间 + */ + async getRestartTime(): Promise { + this.logger.debug(`有个小可爱想知道核心重启花了多久..`); + const restartTimePath = path.join( + this.pathService.get('temp'), + 'restart_time', + ); + return await fs.readFile(restartTimePath, 'utf8'); + } +}