docs: 基本完成初版数据库设计
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
import {
|
||||
ArgumentsHost,
|
||||
Catch,
|
||||
HttpException,
|
||||
type ExceptionFilter,
|
||||
} from '@nestjs/common';
|
||||
import type { Response } from 'express';
|
||||
import type { HttpRequest } from './http-request';
|
||||
import { AppLogger } from '../logging/app-logger';
|
||||
|
||||
// 对外只提供稳定的安全提示;异常原始 message 可能包含请求值、路径或密钥。
|
||||
const messages: Record<number, string> = {
|
||||
400: '请求参数或格式不正确',
|
||||
401: '请重新登录',
|
||||
403: '没有访问权限',
|
||||
404: '接口不存在',
|
||||
409: '请求与当前数据冲突',
|
||||
413: '请求体超过大小限制',
|
||||
415: '不支持的请求格式',
|
||||
429: '请求过于频繁',
|
||||
503: '服务暂时不可用',
|
||||
};
|
||||
|
||||
@Catch()
|
||||
/** 将 Nest 和解析器异常转为统一 HTTP 错误,使用 requestId 关联服务端日志。 */
|
||||
export class ApiErrorFilter implements ExceptionFilter {
|
||||
constructor(private readonly logger: AppLogger) {}
|
||||
|
||||
catch(exception: unknown, host: ArgumentsHost): void {
|
||||
const request = host.switchToHttp().getRequest<HttpRequest>();
|
||||
const response = host.switchToHttp().getResponse<Response>();
|
||||
// 未识别异常默认视为服务端错误;已知 HTTP 异常保留原状态码。
|
||||
let status = 500;
|
||||
if (exception instanceof HttpException) status = exception.getStatus();
|
||||
else if (
|
||||
exception &&
|
||||
typeof exception === 'object' &&
|
||||
'type' in exception
|
||||
) {
|
||||
// body-parser 在进入控制器前抛出的错误不一定是 Nest HttpException。
|
||||
if (exception.type === 'entity.too.large') status = 413;
|
||||
else if (exception.type === 'entity.parse.failed') status = 400;
|
||||
else if (
|
||||
exception.type === 'encoding.unsupported' ||
|
||||
exception.type === 'charset.unsupported'
|
||||
)
|
||||
status = 415;
|
||||
}
|
||||
// 请求完成中间件记录全部状态;服务端错误另记诊断事件,不记录原请求及堆栈。
|
||||
if (status >= 500) {
|
||||
this.logger.error('http_request_failed', {
|
||||
request_id: request.requestId,
|
||||
method: request.method,
|
||||
path: request.path,
|
||||
status_code: status,
|
||||
error_type:
|
||||
exception instanceof Error
|
||||
? exception.name
|
||||
: 'UnknownError',
|
||||
});
|
||||
}
|
||||
// HTTP 状态与响应 code 保持一致,客户端可用 requestId 反馈问题。
|
||||
response.status(status).json({
|
||||
code: status,
|
||||
message: messages[status] ?? '服务器暂时无法处理请求',
|
||||
requestId: request.requestId,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import type { Request } from 'express';
|
||||
|
||||
/** 守卫核对账户后写入的身份,后续业务不能用客户端提交的 userId 替代它。 */
|
||||
export interface AuthenticatedUser {
|
||||
userId: string;
|
||||
authVersion: number;
|
||||
role: 'user' | 'admin';
|
||||
}
|
||||
|
||||
/** 生命周期内共享的请求上下文;未经过中间件或鉴权时字段可能尚未赋值。 */
|
||||
export type HttpRequest = Request & {
|
||||
requestId?: string;
|
||||
auth?: AuthenticatedUser;
|
||||
};
|
||||
@@ -3,6 +3,7 @@ export interface RuntimeOptions {
|
||||
port: number;
|
||||
}
|
||||
|
||||
/** 解析监听地址与端口;默认仅本机可访问,部署时显式配置外部监听地址。 */
|
||||
export function getRuntimeOptions(
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
): RuntimeOptions {
|
||||
@@ -10,6 +11,7 @@ export function getRuntimeOptions(
|
||||
const port = Number(rawPort);
|
||||
const host = (env.HOST ?? '127.0.0.1').trim();
|
||||
|
||||
// 先检查原始字符串,拒绝 Number 会接受的空串、科学计数法等输入。
|
||||
if (
|
||||
!/^\d+$/.test(rawPort) ||
|
||||
!Number.isInteger(port) ||
|
||||
|
||||
Reference in New Issue
Block a user