mirror of
https://github.com/crystelf/crystelf-core.git
synced 2025-10-14 05:19:19 +00:00
Compare commits
2 Commits
296bd924cd
...
c1c3f9a5fd
Author | SHA1 | Date | |
---|---|---|---|
c1c3f9a5fd | |||
dc37a45990 |
@ -30,6 +30,7 @@
|
||||
"rxjs": "^7.8.1",
|
||||
"simple-git": "^3.28.0",
|
||||
"ssh2": "^1.16.0",
|
||||
"stream-throttle": "^0.1.3",
|
||||
"uuid": "^11.1.0",
|
||||
"ws": "^8.18.3"
|
||||
},
|
||||
@ -44,6 +45,7 @@
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/node": "^22.16.4",
|
||||
"@types/stream-throttle": "^0.1.4",
|
||||
"@types/supertest": "^6.0.2",
|
||||
"@types/ws": "^8.18.1",
|
||||
"eslint": "^9.18.0",
|
||||
|
39
pnpm-lock.yaml
generated
39
pnpm-lock.yaml
generated
@ -53,6 +53,9 @@ importers:
|
||||
ssh2:
|
||||
specifier: ^1.16.0
|
||||
version: 1.16.0
|
||||
stream-throttle:
|
||||
specifier: ^0.1.3
|
||||
version: 0.1.3
|
||||
uuid:
|
||||
specifier: ^11.1.0
|
||||
version: 11.1.0
|
||||
@ -90,6 +93,9 @@ importers:
|
||||
'@types/node':
|
||||
specifier: ^22.16.4
|
||||
version: 22.16.5
|
||||
'@types/stream-throttle':
|
||||
specifier: ^0.1.4
|
||||
version: 0.1.4
|
||||
'@types/supertest':
|
||||
specifier: ^6.0.2
|
||||
version: 6.0.3
|
||||
@ -699,49 +705,42 @@ packages:
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@napi-rs/nice-linux-arm64-musl@1.0.4':
|
||||
resolution: {integrity: sha512-4b1KYG+sriufhFrpUS9uNOEYYJqSfcbnwGx6uGX7JjrH8tELG90cOpCawz5THNIwlS3DhLgnCOcn0+4p6z26QA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@napi-rs/nice-linux-ppc64-gnu@1.0.4':
|
||||
resolution: {integrity: sha512-iaf3vMRgr23oe1PUaKpxaH3DS0IMN0+N9iEiWVwYPm/U15vZFYdqVegGfN2PzrZLUl5lc8ZxbmEKDfuqslhAMA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@napi-rs/nice-linux-riscv64-gnu@1.0.4':
|
||||
resolution: {integrity: sha512-UXoREY6Yw6rHrGuTwQgBxpfjK34t6mTjibE9/cXbefL9AuUCJ9gEgwNKZiONuR5QGswChqo9cnthjdKkYyAdDg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@napi-rs/nice-linux-s390x-gnu@1.0.4':
|
||||
resolution: {integrity: sha512-eFbgYCRPmsqbYPAlLYU5hYTNbogmIDUvknilehHsFhCH1+0/kN87lP+XaLT0Yeq4V/rpwChSd9vlz4muzFArtw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@napi-rs/nice-linux-x64-gnu@1.0.4':
|
||||
resolution: {integrity: sha512-4T3E6uTCwWT6IPnwuPcWVz3oHxvEp/qbrCxZhsgzwTUBEwu78EGNXGdHfKJQt3soth89MLqZJw+Zzvnhrsg1mQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@napi-rs/nice-linux-x64-musl@1.0.4':
|
||||
resolution: {integrity: sha512-NtbBkAeyBPLvCBkWtwkKXkNSn677eaT0cX3tygq+2qVv71TmHgX4gkX6o9BXjlPzdgPGwrUudavCYPT9tzkEqQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@napi-rs/nice-win32-arm64-msvc@1.0.4':
|
||||
resolution: {integrity: sha512-vubOe3i+YtSJGEk/++73y+TIxbuVHi+W8ZzrRm2eETCjCRwNlgbfToQZ85dSA+4iBB/NJRGNp+O4hfdbbttZWA==}
|
||||
@ -986,28 +985,24 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@swc/core-linux-arm64-musl@1.13.1':
|
||||
resolution: {integrity: sha512-JaqFdBCarIBKiMu5bbAp+kWPMNGg97ej+7KzbKOzWP5pRptqKi86kCDZT3WmjPe8hNG6dvBwbm7Y8JNry5LebQ==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@swc/core-linux-x64-gnu@1.13.1':
|
||||
resolution: {integrity: sha512-t4cLkku10YECDaakWUH0452WJHIZtrLPRwezt6BdoMntVMwNjvXRX7C8bGuYcKC3YxRW7enZKFpozLhQIQ37oA==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@swc/core-linux-x64-musl@1.13.1':
|
||||
resolution: {integrity: sha512-fSMwZOaG+3ukUucbEbzz9GhzGhUhXoCPqHe9qW0/Vc2IZRp538xalygKyZynYweH5d9EHux1aj3+IO8/xBaoiA==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@swc/core-win32-arm64-msvc@1.13.1':
|
||||
resolution: {integrity: sha512-tweCXK/79vAwj1NhAsYgICy8T1z2QEairmN2BFEBYFBFNMEB1iI1YlXwBkBtuihRvgZrTh1ORusKa4jLYzLCZA==}
|
||||
@ -1152,6 +1147,9 @@ packages:
|
||||
'@types/stack-utils@2.0.3':
|
||||
resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==}
|
||||
|
||||
'@types/stream-throttle@0.1.4':
|
||||
resolution: {integrity: sha512-VxXIHGjVuK8tYsVm60rIQMmF/0xguCeen5OmK5S4Y6K64A+z+y4/GI6anRnVzaUZaJB9Ah9IfbDcO0o1gZCc/w==}
|
||||
|
||||
'@types/superagent@8.1.9':
|
||||
resolution: {integrity: sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==}
|
||||
|
||||
@ -2619,6 +2617,9 @@ packages:
|
||||
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
||||
limiter@1.1.5:
|
||||
resolution: {integrity: sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==}
|
||||
|
||||
lines-and-columns@1.2.4:
|
||||
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
|
||||
|
||||
@ -3262,6 +3263,11 @@ packages:
|
||||
resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
stream-throttle@0.1.3:
|
||||
resolution: {integrity: sha512-889+B9vN9dq7/vLbGyuHeZ6/ctf5sNuGWsDy89uNxkFTAgzy0eK7+w5fL3KLNRTkLle7EgZGvHUphZW0Q26MnQ==}
|
||||
engines: {node: '>= 0.10.0'}
|
||||
hasBin: true
|
||||
|
||||
streamsearch@1.1.0:
|
||||
resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
@ -4828,6 +4834,10 @@ snapshots:
|
||||
|
||||
'@types/stack-utils@2.0.3': {}
|
||||
|
||||
'@types/stream-throttle@0.1.4':
|
||||
dependencies:
|
||||
'@types/node': 22.16.5
|
||||
|
||||
'@types/superagent@8.1.9':
|
||||
dependencies:
|
||||
'@types/cookiejar': 2.1.5
|
||||
@ -6611,6 +6621,8 @@ snapshots:
|
||||
prelude-ls: 1.2.1
|
||||
type-check: 0.4.0
|
||||
|
||||
limiter@1.1.5: {}
|
||||
|
||||
lines-and-columns@1.2.4: {}
|
||||
|
||||
load-esm@1.0.2: {}
|
||||
@ -7209,6 +7221,11 @@ snapshots:
|
||||
|
||||
statuses@2.0.2: {}
|
||||
|
||||
stream-throttle@0.1.3:
|
||||
dependencies:
|
||||
commander: 2.20.3
|
||||
limiter: 1.1.5
|
||||
|
||||
streamsearch@1.1.0: {}
|
||||
|
||||
streamx@2.22.1:
|
||||
|
@ -25,7 +25,7 @@ export class AppConfigService implements OnModuleInit {
|
||||
if (defaultValue !== undefined) {
|
||||
return defaultValue;
|
||||
}
|
||||
this.logger.error(`环境变量 ${key} 未定义!`);
|
||||
this.logger.error(`环境变量 ${key} 未定义!`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
@ -33,17 +33,17 @@ export class AutoUpdateService {
|
||||
label = '子仓库',
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
this.logger.log(`[${label}] 检查仓库更新中...`);
|
||||
this.logger.log(`[${label}] 检查仓库更新中..`);
|
||||
|
||||
const repoGit = simpleGit(folderPath);
|
||||
const status = await repoGit.status();
|
||||
|
||||
if (status.ahead > 0) {
|
||||
this.logger.warn(`[${label}] 检测到本地仓库有未提交的更改,跳过更新`);
|
||||
this.logger.warn(`[${label}] 检测到本地仓库有未提交的更改,跳过更新..`);
|
||||
return false;
|
||||
}
|
||||
|
||||
this.logger.log(`[${label}] 正在获取远程仓库信息...`);
|
||||
this.logger.log(`[${label}] 正在获取远程仓库信息..`);
|
||||
await repoGit.fetch();
|
||||
|
||||
const localBranch = status.current;
|
||||
@ -52,22 +52,22 @@ export class AutoUpdateService {
|
||||
]);
|
||||
|
||||
if (diffSummary.files.length > 0) {
|
||||
this.logger.log(`[${label}] 检测到远程仓库有更新!`);
|
||||
this.logger.log(`[${label}] 检测到远程仓库有更新!`);
|
||||
if (localBranch) {
|
||||
this.logger.log(`[${label}] 正在拉取远程代码...`);
|
||||
this.logger.log(`[${label}] 正在拉取远程代码..`);
|
||||
await repoGit.pull('origin', localBranch);
|
||||
} else {
|
||||
this.logger.error(`[${label}] 当前分支名称未知,无法执行拉取操作。`);
|
||||
this.logger.error(`[${label}] 当前分支名称未知,无法执行拉取操作..`);
|
||||
return false;
|
||||
}
|
||||
|
||||
this.logger.log(`[${label}] 代码更新成功,开始更新依赖...`);
|
||||
this.logger.log(`[${label}] 代码更新成功,开始更新依赖..`);
|
||||
await this.updateDependencies(folderPath, label);
|
||||
|
||||
this.logger.log(`[${label}] 自动更新流程完成`);
|
||||
this.logger.log(`[${label}] 自动更新流程完成!`);
|
||||
return true;
|
||||
} else {
|
||||
this.logger.log(`[${label}] 远程仓库没有新变化`);
|
||||
this.logger.log(`[${label}] 远程仓库没有新变化..`);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
@ -94,16 +94,16 @@ export class AutoUpdateService {
|
||||
try {
|
||||
pkgJson = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
||||
} catch {
|
||||
this.logger.warn(`[${label}] 未找到 package.json,跳过依赖构建`);
|
||||
this.logger.warn(`[${label}] 未找到 package.json,跳过依赖构建`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pkgJson.scripts?.build) {
|
||||
this.logger.log(`[${label}] 检测到 build 脚本,执行 pnpm build...`);
|
||||
this.logger.log(`[${label}] 检测到 build 脚本,执行 pnpm build..`);
|
||||
await execAsync('pnpm build', { cwd: folderPath });
|
||||
this.logger.log(`[${label}] 构建完成`);
|
||||
this.logger.log(`[${label}] 构建完成!`);
|
||||
} else {
|
||||
this.logger.log(`[${label}] 未检测到 build 脚本,跳过构建`);
|
||||
this.logger.log(`[${label}] 未检测到 build 脚本,跳过构建..`);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(`[${label}] 更新依赖或构建失败:`, error);
|
||||
|
@ -178,8 +178,29 @@ export class RedisService implements OnModuleInit {
|
||||
await this.Persistence.writeDataLocal(key, data, fileName);
|
||||
}
|
||||
|
||||
public async test(): Promise<void> {
|
||||
const user = await this.fetch<IUser>('Jerry', 'IUser');
|
||||
this.logger.debug('User:', user);
|
||||
/**
|
||||
* 增加某个 IP 的流量计数
|
||||
* @param ip IP 地址
|
||||
* @param bytes 本次传输的字节数
|
||||
* @param window 窗口秒数
|
||||
*/
|
||||
public async incrementIpTraffic(
|
||||
ip: string,
|
||||
bytes: number,
|
||||
window = 1,
|
||||
): Promise<number> {
|
||||
const key = `traffic:${ip}`;
|
||||
const total = await this.client.incrby(key, bytes);
|
||||
await this.client.expire(key, window);
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某个 IP 当前窗口的流量
|
||||
*/
|
||||
public async getIpTraffic(ip: string): Promise<number> {
|
||||
const key = `traffic:${ip}`;
|
||||
const value = await this.client.get(key);
|
||||
return value ? parseInt(value, 10) : 0;
|
||||
}
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ export class SystemService {
|
||||
const prev = Number(fs.readFileSync(this.restartFile, 'utf-8'));
|
||||
const duration = ((Date.now() - prev) / 1000 - 5).toFixed(2);
|
||||
fs.unlinkSync(this.restartFile);
|
||||
this.logger.debug(`检测到重启,耗时: ${duration}秒`);
|
||||
this.logger.debug(`检测到重启,耗时: ${duration}秒`);
|
||||
return Number(duration);
|
||||
}
|
||||
return null;
|
||||
@ -65,7 +65,7 @@ export class SystemService {
|
||||
this.logger.debug('检查系统代码更新..');
|
||||
const updated = await this.autoUpdateService.checkForUpdates();
|
||||
if (updated) {
|
||||
this.logger.warn('系统代码已更新,正在重启..');
|
||||
this.logger.warn('系统代码已更新,正在重启..');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
@ -65,8 +65,8 @@ export class ToolsService {
|
||||
public checkToken(token: string): boolean {
|
||||
const expected = this.config.get<string>('TOKEN');
|
||||
if (!expected) {
|
||||
this.logger.error('环境变量 TOKEN 未配置,无法进行验证!');
|
||||
throw new UnauthorizedException('系统配置错误,缺少 TOKEN');
|
||||
this.logger.error('环境变量 TOKEN 未配置,无法进行验证!');
|
||||
throw new UnauthorizedException('系统配置错误,缺少 TOKEN');
|
||||
}
|
||||
return token === expected;
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ export class WsTools {
|
||||
|
||||
static async send(socket: WebSocket, data: unknown): Promise<boolean> {
|
||||
if (socket.readyState !== 1) {
|
||||
this.logger.warn('尝试向非 OPEN 状态的 socket 发送消息,已丢弃');
|
||||
this.logger.warn('尝试向非 OPEN 状态的 socket 发送消息,已丢弃');
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -16,10 +16,10 @@ async function bootstrap() {
|
||||
if (!fs.existsSync(envPath)) {
|
||||
if (fs.existsSync(envExamplePath)) {
|
||||
fs.copyFileSync(envExamplePath, envPath);
|
||||
Logger.warn(`.env 文件已自动生成,请修改配置后重启核心..`, '', 'ENV');
|
||||
Logger.warn(`.env 文件已自动生成,请修改配置后重启核心..`, '', 'ENV');
|
||||
process.exit(1);
|
||||
} else {
|
||||
Logger.error('配置模块初始化出错,请重新拉取应用!', '', 'ENV');
|
||||
Logger.error('配置模块初始化出错,请重新拉取应用!', '', 'ENV');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
@ -39,7 +39,7 @@ async function bootstrap() {
|
||||
const systemService = app.get(SystemService);
|
||||
const restartDuration = systemService.checkRestartTime();
|
||||
if (restartDuration) {
|
||||
Logger.warn(`重启完成!耗时 ${restartDuration} 秒`, '', 'System');
|
||||
Logger.warn(`重启完成!耗时 ${restartDuration} 秒`, '', 'System');
|
||||
}
|
||||
const config = new DocumentBuilder()
|
||||
.setTitle('晶灵核心')
|
||||
|
@ -65,6 +65,6 @@ export class BotController {
|
||||
@ApiBody({ type: BroadcastDto })
|
||||
public async smartBroadcast(@Body() dto: BroadcastDto) {
|
||||
await this.botService.broadcastToAllGroups(dto.message);
|
||||
return { message: '广播任务已开始,正在后台执行..' };
|
||||
return { message: '广播任务已开始,正在后台执行..' };
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ export class SendMessageDto extends GroupInfoDto {
|
||||
export class BroadcastDto extends TokenDto {
|
||||
@ApiProperty({
|
||||
description: '要广播的消息',
|
||||
example: '全体目光向我看齐!我宣布个事儿..',
|
||||
example: '全体目光向我看齐!我宣布个事儿..',
|
||||
})
|
||||
message: string;
|
||||
}
|
||||
|
@ -179,7 +179,7 @@ export class BotService {
|
||||
},
|
||||
};
|
||||
this.logger.log(
|
||||
`[广播] 向群 ${groupId} 使用Bot ${selectedBotId}(客户端 ${selectedClientId})发送消息${message},延迟 ${
|
||||
`[广播] 向群 ${groupId} 使用Bot ${selectedBotId}(客户端 ${selectedClientId})发送消息${message},延迟 ${
|
||||
delay / 1000
|
||||
} 秒`,
|
||||
);
|
||||
|
@ -26,7 +26,7 @@ export class CdnController {
|
||||
const filePath = await this.fileService.getFile(relativePath);
|
||||
if (!filePath) {
|
||||
this.logger.warn(`${relativePath}:文件不存在..`);
|
||||
throw new HttpException('文件不存在啦!', HttpStatus.NOT_FOUND);
|
||||
throw new HttpException('文件不存在啦!', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
res.sendFile(filePath, (err) => {
|
||||
|
@ -7,11 +7,15 @@ import {
|
||||
HttpStatus,
|
||||
Logger,
|
||||
Inject,
|
||||
Ip,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiBody, ApiProperty } from '@nestjs/swagger';
|
||||
import { MemeService } from './meme.service';
|
||||
import { Response } from 'express';
|
||||
import * as fs from 'fs';
|
||||
import { Throttle } from 'stream-throttle';
|
||||
import { ToolsService } from '../../core/tools/tools.service';
|
||||
import { RedisService } from '../../core/redis/redis.service';
|
||||
|
||||
class MemeRequestDto {
|
||||
@ApiProperty({ description: '角色名称', example: 'zhenxun', required: false })
|
||||
@ -19,6 +23,13 @@ class MemeRequestDto {
|
||||
|
||||
@ApiProperty({ description: '状态', example: 'happy', required: false })
|
||||
status?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: '可选访问令牌',
|
||||
example: 'token',
|
||||
required: false,
|
||||
})
|
||||
token?: string;
|
||||
}
|
||||
|
||||
@Controller('meme')
|
||||
@ -26,7 +37,14 @@ class MemeRequestDto {
|
||||
export class MemeController {
|
||||
private readonly logger = new Logger(MemeController.name);
|
||||
|
||||
constructor(@Inject(MemeService) private readonly memeService: MemeService) {}
|
||||
constructor(
|
||||
@Inject(MemeService)
|
||||
private readonly memeService: MemeService,
|
||||
@Inject(ToolsService)
|
||||
private readonly toolsService: ToolsService,
|
||||
@Inject(RedisService)
|
||||
private readonly redisService: RedisService,
|
||||
) {}
|
||||
|
||||
@Post('getRandom')
|
||||
@ApiOperation({ summary: '获取随机表情包' })
|
||||
@ -34,8 +52,13 @@ export class MemeController {
|
||||
public async getRandomMeme(
|
||||
@Body() dto: MemeRequestDto,
|
||||
@Res() res: Response,
|
||||
@Ip() ip: string,
|
||||
) {
|
||||
try {
|
||||
const realToken = dto.token;
|
||||
const hasValidToken =
|
||||
realToken && this.toolsService.checkToken(realToken);
|
||||
|
||||
const memePath = await this.memeService.getRandomMemePath(
|
||||
dto.character,
|
||||
dto.status,
|
||||
@ -48,14 +71,6 @@ export class MemeController {
|
||||
);
|
||||
}
|
||||
|
||||
const stream = fs.createReadStream(memePath);
|
||||
stream.on('error', () => {
|
||||
throw new HttpException(
|
||||
'读取表情包失败',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
});
|
||||
|
||||
const ext = memePath.split('.').pop()?.toLowerCase();
|
||||
let contentType = 'image/jpeg';
|
||||
if (ext === 'png') contentType = 'image/png';
|
||||
@ -63,9 +78,39 @@ export class MemeController {
|
||||
if (ext === 'webp') contentType = 'image/webp';
|
||||
|
||||
res.setHeader('Content-Type', contentType);
|
||||
const stream = fs.createReadStream(memePath);
|
||||
|
||||
stream.on('error', () => {
|
||||
throw new HttpException(
|
||||
'读取表情包失败',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
});
|
||||
|
||||
if (hasValidToken) {
|
||||
this.logger.log(`有token入不限速: ${memePath}`);
|
||||
stream.pipe(res);
|
||||
} else {
|
||||
stream.on('data', async (chunk) => {
|
||||
const bytes = chunk.length;
|
||||
const total = await this.redisService.incrementIpTraffic(
|
||||
ip,
|
||||
bytes,
|
||||
1,
|
||||
);
|
||||
if (total > 100 * 1024) {
|
||||
this.logger.warn(`IP ${ip} 超过速率限制,断开连接..`);
|
||||
stream.destroy();
|
||||
res.end();
|
||||
}
|
||||
});
|
||||
|
||||
const throttle = new Throttle({ rate: 100 * 1024 });
|
||||
this.logger.log(`白嫖的入限速!(${ip}) => ${memePath}`);
|
||||
stream.pipe(throttle).pipe(res);
|
||||
}
|
||||
} catch (e) {
|
||||
this.logger.error(`获取表情包失败: ${e.message}`);
|
||||
this.logger.error(`获取表情包失败:${e.message}`);
|
||||
throw new HttpException('服务器错误', HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
@ -3,9 +3,11 @@ import { MemeService } from './meme.service';
|
||||
import { MemeController } from './meme.controller';
|
||||
import { PathModule } from '../../core/path/path.module';
|
||||
import { AutoUpdateModule } from '../../core/auto-update/auto-update.module';
|
||||
import { ToolsModule } from '../../core/tools/tools.module';
|
||||
import { RedisModule } from '../../core/redis/redis.module';
|
||||
|
||||
@Module({
|
||||
imports: [PathModule, AutoUpdateModule],
|
||||
imports: [PathModule, AutoUpdateModule, ToolsModule, RedisModule],
|
||||
providers: [MemeService],
|
||||
controllers: [MemeController],
|
||||
})
|
||||
|
@ -21,6 +21,7 @@ export class MemeService {
|
||||
private startAutoUpdate() {
|
||||
setInterval(async () => {
|
||||
const memePath = this.pathService.get('meme');
|
||||
//const memePath = path.join(this.pathService.get('meme'),'..'); TODO 需确认检查src更新是否影响正常运行
|
||||
this.logger.log('定时检查表情仓库更新..');
|
||||
const updated = await this.autoUpdateService.checkRepoForUpdates(
|
||||
memePath,
|
||||
|
@ -31,6 +31,7 @@ export class WordsService {
|
||||
|
||||
private startAutoUpdate() {
|
||||
setInterval(async () => {
|
||||
//const wordsPath = path.join(this.paths.get('words'),'..'); TODO 需确认检查src更新是否影响正常运行
|
||||
const wordsPath = this.paths.get('words');
|
||||
this.logger.log('定时检查文案仓库更新..');
|
||||
const updated = await this.autoUpdateService.checkRepoForUpdates(
|
||||
@ -38,7 +39,7 @@ export class WordsService {
|
||||
'words 仓库',
|
||||
);
|
||||
if (updated) {
|
||||
this.logger.log('文案仓库已更新,清理缓存..');
|
||||
this.logger.log('文案仓库已更新,清理缓存..');
|
||||
this.wordCache = {};
|
||||
}
|
||||
}, this.updateMs);
|
||||
|
Loading…
x
Reference in New Issue
Block a user