fix:动图卡顿问题

This commit is contained in:
Jerry 2025-09-23 17:28:39 +08:00
parent a1fb3d0cec
commit 20b533efe2
4 changed files with 52 additions and 5 deletions

View File

@ -25,6 +25,7 @@
"@nestjs/swagger": "^11.2.0", "@nestjs/swagger": "^11.2.0",
"@nestjs/websockets": "^11.1.6", "@nestjs/websockets": "^11.1.6",
"axios": "1.11.0", "axios": "1.11.0",
"image-type": "^6.0.0",
"ioredis": "^5.6.1", "ioredis": "^5.6.1",
"moment": "^2.30.1", "moment": "^2.30.1",
"reflect-metadata": "^0.2.2", "reflect-metadata": "^0.2.2",

26
pnpm-lock.yaml generated
View File

@ -38,6 +38,9 @@ importers:
axios: axios:
specifier: 1.11.0 specifier: 1.11.0
version: 1.11.0 version: 1.11.0
image-type:
specifier: ^6.0.0
version: 6.0.0
ioredis: ioredis:
specifier: ^5.6.1 specifier: ^5.6.1
version: 5.6.1 version: 5.6.1
@ -2062,6 +2065,10 @@ packages:
resolution: {integrity: sha512-VZR5I7k5wkD0HgFnMsq5hOsSc710MJMu5Nc5QYsbe38NN5iPV/XTObYLc/cpttRTf6lX538+5uO1ZQRhYibiZQ==} resolution: {integrity: sha512-VZR5I7k5wkD0HgFnMsq5hOsSc710MJMu5Nc5QYsbe38NN5iPV/XTObYLc/cpttRTf6lX538+5uO1ZQRhYibiZQ==}
engines: {node: '>=18'} engines: {node: '>=18'}
file-type@20.5.0:
resolution: {integrity: sha512-BfHZtG/l9iMm4Ecianu7P8HRD2tBHLtjXinm4X62XBOYzi7CYA7jyqfJzOvXHqzVrVPYqBo2/GvbARMaaJkKVg==}
engines: {node: '>=18'}
file-type@21.0.0: file-type@21.0.0:
resolution: {integrity: sha512-ek5xNX2YBYlXhiUXui3D/BXa3LdqPmoLJ7rqEx2bKJ7EAUEfmXgW0Das7Dc6Nr9MvqaOnIqiPV0mZk/r/UpNAg==} resolution: {integrity: sha512-ek5xNX2YBYlXhiUXui3D/BXa3LdqPmoLJ7rqEx2bKJ7EAUEfmXgW0Das7Dc6Nr9MvqaOnIqiPV0mZk/r/UpNAg==}
engines: {node: '>=20'} engines: {node: '>=20'}
@ -2301,6 +2308,10 @@ packages:
resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==}
engines: {node: '>= 4'} engines: {node: '>= 4'}
image-type@6.0.0:
resolution: {integrity: sha512-efpcYd/E9A7a+oanft11ceIbO9Aw0iszfJ7Qfh4QLWl2Ulsth9nnllV/L1TmzKwlQ2O5FuT08vy5zxLnGxZe8w==}
engines: {node: '>=20'}
import-fresh@3.3.1: import-fresh@3.3.1:
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
engines: {node: '>=6'} engines: {node: '>=6'}
@ -5886,6 +5897,15 @@ snapshots:
token-types: 6.0.3 token-types: 6.0.3
uint8array-extras: 1.4.0 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: file-type@21.0.0:
dependencies: dependencies:
'@tokenizer/inflate': 0.2.7 '@tokenizer/inflate': 0.2.7
@ -6137,6 +6157,12 @@ snapshots:
ignore@7.0.5: {} ignore@7.0.5: {}
image-type@6.0.0:
dependencies:
file-type: 20.5.0
transitivePeerDependencies:
- supports-color
import-fresh@3.3.1: import-fresh@3.3.1:
dependencies: dependencies:
parent-module: 1.0.1 parent-module: 1.0.1

View File

@ -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', { app.setGlobalPrefix('api', {
exclude: [ exclude: [
'cdn', 'cdn',

View File

@ -18,6 +18,7 @@ import * as fs from 'fs';
import { Throttle } from 'stream-throttle'; import { Throttle } from 'stream-throttle';
import { ToolsService } from '../../core/tools/tools.service'; import { ToolsService } from '../../core/tools/tools.service';
import { RedisService } from '../../core/redis/redis.service'; import { RedisService } from '../../core/redis/redis.service';
import imageType from 'image-type';
class MemeRequestDto { class MemeRequestDto {
character?: string; 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) { if (hasValidToken) {
this.logger.log(`[${method}] 有token的入不限速 => ${memePath}`); this.logger.log(`[${method}] 有token的入不限速 => ${memePath}`);
stream.pipe(res); stream.pipe(res);
@ -124,15 +139,18 @@ export class MemeController {
bytes, bytes,
1, 1,
); );
if (total > 100 * 1024) { if (total > maxRate && !isAnimatedImage) {
this.logger.warn(`[${method}]${ip} 超过速率限制,断开连接..`); this.logger.warn(`[${method}] ${ip} 超过速率限制,断开连接..`);
stream.destroy(); stream.destroy();
res.end(); res.end();
} }
}); });
const throttle = new Throttle({ rate: 100 * 1024 }); const throttle = new Throttle({ rate: singleRate });
this.logger.log(`[${method}] 白嫖入限速! (${ip}) => ${memePath}`); this.logger.log(
`[${method}] 白嫖入限速! (${ip}) => ${memePath}
`,
);
stream.pipe(throttle).pipe(res); stream.pipe(throttle).pipe(res);
} }
} catch (e) { } catch (e) {