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>
127 lines
3.4 KiB
TypeScript
127 lines
3.4 KiB
TypeScript
import {
|
|
Injectable,
|
|
NestInterceptor,
|
|
ExecutionContext,
|
|
CallHandler,
|
|
} from '@nestjs/common';
|
|
import { Observable } from 'rxjs';
|
|
import { map } from 'rxjs/operators';
|
|
import { Reflector } from '@nestjs/core';
|
|
|
|
// 响应格式装饰器
|
|
export const RESPONSE_MESSAGE_KEY = 'response_message';
|
|
export const ResponseMessage = (message: string) =>
|
|
Reflector.createDecorator<string>()[RESPONSE_MESSAGE_KEY](message);
|
|
|
|
// 跳过响应包装装饰器
|
|
export const SKIP_RESPONSE_WRAP_KEY = 'skip_response_wrap';
|
|
export const SkipResponseWrap = () =>
|
|
Reflector.createDecorator<boolean>()[SKIP_RESPONSE_WRAP_KEY](true);
|
|
|
|
// 标准响应格式接口
|
|
export interface ApiResponse<T = any> {
|
|
success: boolean;
|
|
code: number;
|
|
data: T;
|
|
msg: string;
|
|
timestamp?: string;
|
|
path?: string;
|
|
requestId?: string;
|
|
}
|
|
|
|
@Injectable()
|
|
export class ResponseInterceptor<T> implements NestInterceptor<T, ApiResponse<T>> {
|
|
constructor(private readonly reflector: Reflector) {}
|
|
|
|
intercept(context: ExecutionContext, next: CallHandler): Observable<ApiResponse<T>> {
|
|
const request = context.switchToHttp().getRequest();
|
|
const response = context.switchToHttp().getResponse();
|
|
|
|
// 检查是否跳过响应包装
|
|
const skipWrap = this.reflector.getAllAndOverride<boolean>(
|
|
SKIP_RESPONSE_WRAP_KEY,
|
|
[context.getHandler(), context.getClass()],
|
|
);
|
|
|
|
if (skipWrap) {
|
|
return next.handle();
|
|
}
|
|
|
|
// 获取自定义响应消息
|
|
const message = this.reflector.getAllAndOverride<string>(
|
|
RESPONSE_MESSAGE_KEY,
|
|
[context.getHandler(), context.getClass()],
|
|
);
|
|
|
|
return next.handle().pipe(
|
|
map((data) => {
|
|
// 如果数据已经是标准格式,直接返回
|
|
if (this.isApiResponse(data)) {
|
|
return {
|
|
...data,
|
|
timestamp: new Date().toISOString(),
|
|
path: request.url,
|
|
requestId: request.headers['x-request-id'] || request.headers['request-id'],
|
|
};
|
|
}
|
|
|
|
// 包装成标准响应格式
|
|
const result: ApiResponse<T> = {
|
|
success: true,
|
|
code: response.statusCode || 200,
|
|
data: data,
|
|
msg: message || this.getDefaultMessage(response.statusCode),
|
|
timestamp: new Date().toISOString(),
|
|
path: request.url,
|
|
requestId: request.headers['x-request-id'] || request.headers['request-id'],
|
|
};
|
|
|
|
return result;
|
|
}),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 检查数据是否已经是API响应格式
|
|
*/
|
|
private isApiResponse(data: any): data is ApiResponse {
|
|
return (
|
|
data &&
|
|
typeof data === 'object' &&
|
|
'success' in data &&
|
|
'code' in data &&
|
|
'data' in data &&
|
|
'msg' in data
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 根据状态码获取默认消息
|
|
*/
|
|
private getDefaultMessage(statusCode: number): string {
|
|
switch (statusCode) {
|
|
case 200:
|
|
return '操作成功';
|
|
case 201:
|
|
return '创建成功';
|
|
case 204:
|
|
return '操作成功';
|
|
case 400:
|
|
return '请求参数错误';
|
|
case 401:
|
|
return '未授权访问';
|
|
case 403:
|
|
return '禁止访问';
|
|
case 404:
|
|
return '资源不存在';
|
|
case 409:
|
|
return '资源冲突';
|
|
case 422:
|
|
return '请求参数验证失败';
|
|
case 500:
|
|
return '服务器内部错误';
|
|
default:
|
|
return '操作完成';
|
|
}
|
|
}
|
|
} |