Initial commit: Telegram Management System
Some checks failed
Deploy / deploy (push) Has been cancelled

Full-stack web application for Telegram management
- Frontend: Vue 3 + Vben Admin
- Backend: NestJS
- Features: User management, group broadcast, statistics

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
你的用户名
2025-11-04 15:37:50 +08:00
commit 237c7802e5
3674 changed files with 525172 additions and 0 deletions

View File

@@ -0,0 +1,180 @@
import { applyDecorators, Type } from '@nestjs/common';
import { ApiResponse, ApiResponseOptions } from '@nestjs/swagger';
/**
* 标准API响应装饰器
*/
export const ApiStandardResponse = <TModel extends Type<any>>(
model?: TModel,
options?: Omit<ApiResponseOptions, 'schema'>
) => {
const baseSchema = {
type: 'object',
properties: {
success: {
type: 'boolean',
description: '请求是否成功',
example: true,
},
code: {
type: 'number',
description: 'HTTP状态码',
example: 200,
},
msg: {
type: 'string',
description: '响应消息',
example: '操作成功',
},
timestamp: {
type: 'string',
description: '响应时间戳',
example: '2023-12-01T12:00:00.000Z',
},
path: {
type: 'string',
description: '请求路径',
example: '/api/users',
},
requestId: {
type: 'string',
description: '请求ID',
example: 'uuid-string',
},
},
required: ['success', 'code', 'msg'],
};
if (model) {
baseSchema.properties['data'] = {
$ref: `#/components/schemas/${model.name}`,
};
} else {
baseSchema.properties['data'] = {
type: 'object',
description: '响应数据',
nullable: true,
};
}
return applyDecorators(
ApiResponse({
...options,
schema: baseSchema,
})
);
};
/**
* 成功响应装饰器
*/
export const ApiSuccessResponse = <TModel extends Type<any>>(
model?: TModel,
description: string = '操作成功'
) => {
return ApiStandardResponse(model, {
status: 200,
description,
});
};
/**
* 创建成功响应装饰器
*/
export const ApiCreatedResponse = <TModel extends Type<any>>(
model?: TModel,
description: string = '创建成功'
) => {
return ApiStandardResponse(model, {
status: 201,
description,
});
};
/**
* 错误响应装饰器
*/
export const ApiErrorResponse = (
status: number,
description: string,
errorCode?: string
) => {
const schema = {
type: 'object',
properties: {
success: {
type: 'boolean',
description: '请求是否成功',
example: false,
},
code: {
type: 'number',
description: 'HTTP状态码',
example: status,
},
msg: {
type: 'string',
description: '错误消息',
example: description,
},
data: {
type: 'object',
nullable: true,
example: null,
},
errorCode: {
type: 'string',
description: '错误代码',
example: errorCode || 'ERROR',
},
timestamp: {
type: 'string',
description: '响应时间戳',
example: '2023-12-01T12:00:00.000Z',
},
path: {
type: 'string',
description: '请求路径',
example: '/api/users',
},
requestId: {
type: 'string',
description: '请求ID',
example: 'uuid-string',
},
},
required: ['success', 'code', 'msg', 'errorCode'],
};
return applyDecorators(
ApiResponse({
status,
description,
schema,
})
);
};
/**
* 常用错误响应装饰器
*/
export const ApiBadRequestResponse = (description: string = '请求参数错误') =>
ApiErrorResponse(400, description, 'BAD_REQUEST');
export const ApiUnauthorizedResponse = (description: string = '未授权访问') =>
ApiErrorResponse(401, description, 'UNAUTHORIZED');
export const ApiForbiddenResponse = (description: string = '禁止访问') =>
ApiErrorResponse(403, description, 'FORBIDDEN');
export const ApiNotFoundResponse = (description: string = '资源不存在') =>
ApiErrorResponse(404, description, 'NOT_FOUND');
export const ApiConflictResponse = (description: string = '资源冲突') =>
ApiErrorResponse(409, description, 'CONFLICT');
export const ApiValidationResponse = (description: string = '请求参数验证失败') =>
ApiErrorResponse(422, description, 'VALIDATION_FAILED');
export const ApiInternalServerErrorResponse = (description: string = '服务器内部错误') =>
ApiErrorResponse(500, description, 'INTERNAL_SERVER_ERROR');

View File

@@ -0,0 +1,34 @@
import { SetMetadata } from '@nestjs/common';
import { CACHE_KEY_METADATA, CACHE_TTL_METADATA } from '../interceptors/cache.interceptor';
/**
* 缓存装饰器
* @param key 缓存键
* @param ttl 过期时间默认300秒
*/
export const Cache = (key: string, ttl: number = 300) => {
return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
SetMetadata(CACHE_KEY_METADATA, key)(target, propertyKey, descriptor);
SetMetadata(CACHE_TTL_METADATA, ttl)(target, propertyKey, descriptor);
};
};
/**
* 短时间缓存1分钟
*/
export const CacheShort = (key: string) => Cache(key, 60);
/**
* 中等时间缓存5分钟
*/
export const CacheMedium = (key: string) => Cache(key, 300);
/**
* 长时间缓存30分钟
*/
export const CacheLong = (key: string) => Cache(key, 1800);
/**
* 超长时间缓存2小时
*/
export const CacheVeryLong = (key: string) => Cache(key, 7200);

View File

@@ -0,0 +1,4 @@
import { SetMetadata } from '@nestjs/common';
export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);

View File

@@ -0,0 +1,45 @@
import { SetMetadata } from '@nestjs/common';
export const RATE_LIMIT_KEY = 'rate_limit';
export interface RateLimitOptions {
windowMs?: number; // 时间窗口(毫秒)
maxRequests?: number; // 最大请求数
skipSuccessfulRequests?: boolean; // 是否跳过成功请求
skipFailedRequests?: boolean; // 是否跳过失败请求
keyGenerator?: (req: any) => string; // 自定义key生成器
message?: string; // 自定义错误消息
}
/**
* 速率限制装饰器
*/
export const RateLimit = (options: RateLimitOptions = {}) =>
SetMetadata(RATE_LIMIT_KEY, {
windowMs: 15 * 60 * 1000, // 默认15分钟
maxRequests: 100, // 默认100次请求
skipSuccessfulRequests: false,
skipFailedRequests: false,
message: '请求频率过快,请稍后再试',
...options,
});
/**
* 严格速率限制(用于敏感操作)
*/
export const StrictRateLimit = (maxRequests: number = 10, windowMs: number = 60 * 1000) =>
RateLimit({
maxRequests,
windowMs,
message: '操作频率过快,请稍后再试',
});
/**
* 宽松速率限制(用于一般查询)
*/
export const LooseRateLimit = (maxRequests: number = 1000, windowMs: number = 60 * 1000) =>
RateLimit({
maxRequests,
windowMs,
message: '请求次数过多,请稍后再试',
});

View File

@@ -0,0 +1,9 @@
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { Admin } from '@database/entities/admin.entity';
export const CurrentUser = createParamDecorator(
(data: unknown, ctx: ExecutionContext): Admin => {
const request = ctx.switchToHttp().getRequest();
return request.user;
},
);