From e775bcdf774cf0b3d47e0d5145fece3070122efe Mon Sep 17 00:00:00 2001 From: Jerryplusy Date: Mon, 25 Aug 2025 22:42:14 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=87=E6=A1=88=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app.module.ts | 2 + src/main.ts | 9 ++- src/modules/cdn/cdn.controller.ts | 6 +- src/modules/system/systemWeb.controller.ts | 10 +-- src/modules/words/words.controller.ts | 83 ++++++++++++++++++++++ src/modules/words/words.module.ts | 13 ++++ src/modules/words/words.service.ts | 66 +++++++++++++++++ 7 files changed, 176 insertions(+), 13 deletions(-) create mode 100644 src/modules/words/words.controller.ts create mode 100644 src/modules/words/words.module.ts create mode 100644 src/modules/words/words.service.ts diff --git a/src/app.module.ts b/src/app.module.ts index 68822e0..bdfe000 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -11,6 +11,7 @@ import { WsModule } from './core/ws/ws.module'; import { SystemWebModule } from './modules/system/systemWeb.module'; import { BotModule } from './modules/bot/bot.module'; import { CdnModule } from './modules/cdn/cdn.module'; +import { WordsModule } from './modules/words/words.module'; @Module({ imports: [ @@ -26,6 +27,7 @@ import { CdnModule } from './modules/cdn/cdn.module'; SystemWebModule, BotModule, CdnModule, + WordsModule, ], }) export class AppModule {} diff --git a/src/main.ts b/src/main.ts index 9a1d2a1..8092b4d 100644 --- a/src/main.ts +++ b/src/main.ts @@ -10,7 +10,14 @@ import { WsAdapter } from '@nestjs/platform-ws'; async function bootstrap() { Logger.log('晶灵核心初始化..'); const app = await NestFactory.create(AppModule); - app.setGlobalPrefix('api'); + app.setGlobalPrefix('api', { + exclude: [ + 'cdn', + { path: 'cdn/(.*)', method: RequestMethod.ALL }, + 'public', + { path: 'public/(.*)', method: RequestMethod.ALL }, + ], + }); app.useGlobalInterceptors(new ResponseInterceptor()); app.useGlobalFilters(new AllExceptionsFilter()); const systemService = app.get(SystemService); diff --git a/src/modules/cdn/cdn.controller.ts b/src/modules/cdn/cdn.controller.ts index bb5adb9..b91b38b 100644 --- a/src/modules/cdn/cdn.controller.ts +++ b/src/modules/cdn/cdn.controller.ts @@ -55,13 +55,13 @@ export class CdnController { description: '由晶灵资源分发服务器(CDN)提供支持', }) async getFile(@Res() res: Response, @Req() req: Request) { - const relativePath = req.url.replace('/api/cdn/', ''); //params.path; + const relativePath = req.url.replace('/cdn/', ''); //params.path; return this.deliverFile(relativePath, res); } @Get('public/files/*') async fromPublicFiles(@Res() res: Response, @Req() req: Request) { - const relativePath = req.url.replace('/api/public/files/', ''); + const relativePath = req.url.replace('/public/files/', ''); this.logger.debug( `请求 /public/files/${relativePath} → 代理到 /cdn/${relativePath}`, ); @@ -70,7 +70,7 @@ export class CdnController { @Get('public/cdn/*') async fromPublicCdn(@Req() req: Request, @Res() res: Response) { - const relativePath = req.url.replace('/api/public/cdn/', ''); + const relativePath = req.url.replace('/public/cdn/', ''); this.logger.debug( `请求 /public/cdn/${relativePath} → 代理到 /cdn/${relativePath}`, ); diff --git a/src/modules/system/systemWeb.controller.ts b/src/modules/system/systemWeb.controller.ts index da81718..ec32801 100644 --- a/src/modules/system/systemWeb.controller.ts +++ b/src/modules/system/systemWeb.controller.ts @@ -1,12 +1,4 @@ -import { - Controller, - Post, - Body, - UnauthorizedException, - Inject, - UseGuards, - Param, -} from '@nestjs/common'; +import { Controller, Post, Inject, UseGuards, Param } from '@nestjs/common'; import { ApiTags, ApiOperation, ApiBody, ApiProperty } from '@nestjs/swagger'; import { SystemWebService } from './systemWeb.service'; import { ToolsService } from '../../core/tools/tools.service'; diff --git a/src/modules/words/words.controller.ts b/src/modules/words/words.controller.ts new file mode 100644 index 0000000..e389eb9 --- /dev/null +++ b/src/modules/words/words.controller.ts @@ -0,0 +1,83 @@ +import { + Controller, + Get, + Param, + Post, + HttpException, + HttpStatus, + Logger, + Inject, + UseGuards, +} from '@nestjs/common'; +import { WordsService } from './words.service'; +import { TokenAuthGuard } from '../../core/tools/token-auth.guard'; +import { ApiBody, ApiOperation, ApiProperty } from '@nestjs/swagger'; + +class WordsDto { + @ApiProperty({ + description: '文案id', + example: 'poke', + }) + id: string; + @ApiProperty({ + description: '密钥', + example: '1111', + }) + token: string; +} + +@Controller('words') +export class WordsController { + private readonly logger = new Logger(WordsController.name); + + constructor( + @Inject(WordsService) private readonly wordsService: WordsService, + ) {} + + /** + * 获取随机文案 + */ + @Get('getText/:id') + @ApiOperation({ + summary: '获取随机文案', + }) + async getText(@Param('id') id: string) { + try { + const texts = await this.wordsService.loadWordById(id); + if (!texts || texts.length === 0) { + throw new HttpException( + `文案 ${id} 不存在或为空..`, + HttpStatus.NOT_FOUND, + ); + } + const randomIndex = Math.floor(Math.random() * texts.length); + return { success: true, data: texts[randomIndex] }; + } catch (e) { + this.logger.error(`getText 失败: ${e?.message}`); + throw new HttpException('服务器错误', HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + /** + * 重载文案 + */ + @Post('reloadText/:id') + @ApiOperation({ + summary: '重载某条文案', + }) + @UseGuards(TokenAuthGuard) + @ApiBody({ type: WordsDto }) + async reloadWord(@Param('id') id: string, @Param('token') token: string) { + try { + const success = await this.wordsService.reloadWord(id); + if (success) { + return '成功重载..'; + } else { + throw new HttpException('重载失败..', HttpStatus.BAD_REQUEST); + } + } catch (e) { + this.logger.error(`reloadWord 失败: ${e?.message}`); + throw new HttpException('服务器错误', HttpStatus.INTERNAL_SERVER_ERROR); + } + } +} diff --git a/src/modules/words/words.module.ts b/src/modules/words/words.module.ts new file mode 100644 index 0000000..78c95fd --- /dev/null +++ b/src/modules/words/words.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { WordsController } from './words.controller'; +import { WordsService } from './words.service'; +import { PathModule } from '../../core/path/path.module'; +import { ToolsModule } from '../../core/tools/tools.module'; + +@Module({ + imports: [PathModule, ToolsModule], + controllers: [WordsController], + providers: [WordsService], + exports: [WordsService], +}) +export class WordsModule {} diff --git a/src/modules/words/words.service.ts b/src/modules/words/words.service.ts new file mode 100644 index 0000000..a85f137 --- /dev/null +++ b/src/modules/words/words.service.ts @@ -0,0 +1,66 @@ +import { Inject, Injectable, Logger } from '@nestjs/common'; +import * as path from 'path'; +import * as fs from 'fs/promises'; +import { PathService } from '../../core/path/path.service'; + +@Injectable() +export class WordsService { + private readonly logger = new Logger(WordsService.name); + private wordCache: Record = {}; + private readonly clearIntervalMs = 30 * 60 * 1000; // 30min + @Inject(PathService) + private readonly paths: PathService; + + constructor() { + this.startAutoClear(); + } + + private startAutoClear() { + setInterval(() => { + this.logger.log('清理文案缓存..'); + this.wordCache = {}; + }, this.clearIntervalMs); + } + + /** + * 从本地加载 json 到内存&返回 + */ + async loadWordById(id: string): Promise { + this.logger.log(`Loading words ${id}..`); + if (this.wordCache[id]) return this.wordCache[id]; + const filePath = path.join(this.paths.get('words'), `${id}.json`); + try { + const content = await fs.readFile(filePath, 'utf-8'); + const parsed = JSON.parse(content); + if (Array.isArray(parsed)) { + const texts = parsed.filter((item) => typeof item === 'string'); + this.wordCache[id] = texts; + return texts; + } + return null; + } catch (e) { + this.logger.error(`加载文案失败: ${id}..`, e); + return null; + } + } + + /** + * 重载 json 到内存 + */ + async reloadWord(id: string): Promise { + this.logger.log(`重载文案: ${id}..`); + const filePath = path.join(this.paths.get('words'), `${id}.json`); + try { + const content = await fs.readFile(filePath, 'utf-8'); + const parsed = JSON.parse(content); + if (Array.isArray(parsed)) { + this.wordCache[id] = parsed.filter((item) => typeof item === 'string'); + return true; + } + return false; + } catch (e) { + this.logger.error(`重载文案失败: ${id}`, e); + return false; + } + } +}