diff --git a/package.json b/package.json index c8e5f2a..92aab2c 100755 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "@nestjs/swagger": "^11.2.0", "@nestjs/websockets": "^11.1.6", "axios": "1.11.0", + "image-type": "^6.0.0", "ioredis": "^5.6.1", "moment": "^2.30.1", "reflect-metadata": "^0.2.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 83ebc11..049883f 100755 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -38,6 +38,9 @@ importers: axios: specifier: 1.11.0 version: 1.11.0 + image-type: + specifier: ^6.0.0 + version: 6.0.0 ioredis: specifier: ^5.6.1 version: 5.6.1 @@ -2062,6 +2065,10 @@ packages: resolution: {integrity: sha512-VZR5I7k5wkD0HgFnMsq5hOsSc710MJMu5Nc5QYsbe38NN5iPV/XTObYLc/cpttRTf6lX538+5uO1ZQRhYibiZQ==} engines: {node: '>=18'} + file-type@20.5.0: + resolution: {integrity: sha512-BfHZtG/l9iMm4Ecianu7P8HRD2tBHLtjXinm4X62XBOYzi7CYA7jyqfJzOvXHqzVrVPYqBo2/GvbARMaaJkKVg==} + engines: {node: '>=18'} + file-type@21.0.0: resolution: {integrity: sha512-ek5xNX2YBYlXhiUXui3D/BXa3LdqPmoLJ7rqEx2bKJ7EAUEfmXgW0Das7Dc6Nr9MvqaOnIqiPV0mZk/r/UpNAg==} engines: {node: '>=20'} @@ -2301,6 +2308,10 @@ packages: resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} engines: {node: '>= 4'} + image-type@6.0.0: + resolution: {integrity: sha512-efpcYd/E9A7a+oanft11ceIbO9Aw0iszfJ7Qfh4QLWl2Ulsth9nnllV/L1TmzKwlQ2O5FuT08vy5zxLnGxZe8w==} + engines: {node: '>=20'} + import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} @@ -5886,6 +5897,15 @@ snapshots: token-types: 6.0.3 uint8array-extras: 1.4.0 + file-type@20.5.0: + dependencies: + '@tokenizer/inflate': 0.2.7 + strtok3: 10.3.2 + token-types: 6.0.3 + uint8array-extras: 1.4.0 + transitivePeerDependencies: + - supports-color + file-type@21.0.0: dependencies: '@tokenizer/inflate': 0.2.7 @@ -6137,6 +6157,12 @@ snapshots: ignore@7.0.5: {} + image-type@6.0.0: + dependencies: + file-type: 20.5.0 + transitivePeerDependencies: + - supports-color + import-fresh@3.3.1: dependencies: parent-module: 1.0.1 diff --git a/src/main.ts b/src/main.ts index 5b1cc46..4951931 100644 --- a/src/main.ts +++ b/src/main.ts @@ -24,7 +24,9 @@ async function bootstrap() { } } - const app = await NestFactory.create(AppModule); + const app = await NestFactory.create(AppModule, { cors: true }); + const expressApp = app.getHttpAdapter().getInstance(); + expressApp.set('trust proxy', true); app.setGlobalPrefix('api', { exclude: [ 'cdn', diff --git a/src/modules/meme/meme.controller.ts b/src/modules/meme/meme.controller.ts index ebcda0e..38a58ef 100644 --- a/src/modules/meme/meme.controller.ts +++ b/src/modules/meme/meme.controller.ts @@ -18,6 +18,7 @@ import * as fs from 'fs'; import { Throttle } from 'stream-throttle'; import { ToolsService } from '../../core/tools/tools.service'; import { RedisService } from '../../core/redis/redis.service'; +import imageType from 'image-type'; class MemeRequestDto { character?: string; @@ -113,6 +114,20 @@ export class MemeController { ); }); + const fd = await fs.promises.open(memePath, 'r'); + const { buffer } = await fd.read(Buffer.alloc(4100), 0, 4100, 0); + await fd.close(); + const type = await imageType(buffer); + const isAnimatedImage = + type?.mime === 'image/gif' || + type?.mime === 'image/webp' || + type?.mime === 'image/apng'; + + this.logger.debug(type?.mime); + const singleRate = 100 * 1024; // 100 KB/s * 3 + const maxThreads = 3; + const maxRate = singleRate * maxThreads; + if (hasValidToken) { this.logger.log(`[${method}] 有token的入不限速 => ${memePath}`); stream.pipe(res); @@ -124,15 +139,18 @@ export class MemeController { bytes, 1, ); - if (total > 100 * 1024) { - this.logger.warn(`[${method}]${ip} 超过速率限制,断开连接..`); + if (total > maxRate && !isAnimatedImage) { + this.logger.warn(`[${method}] ${ip} 超过速率限制,断开连接..`); stream.destroy(); res.end(); } }); - const throttle = new Throttle({ rate: 100 * 1024 }); - this.logger.log(`[${method}] 白嫖入限速! (${ip}) => ${memePath}`); + const throttle = new Throttle({ rate: singleRate }); + this.logger.log( + `[${method}] 白嫖入限速! (${ip}) => ${memePath} + `, + ); stream.pipe(throttle).pipe(res); } } catch (e) {