This commit is contained in:
2026-04-10 15:37:02 +08:00
parent 161cd32277
commit f19e482c8f
233 changed files with 43987 additions and 5127 deletions

View File

@@ -22,10 +22,13 @@ export function requireJwtAuth(config: AppConfig, userRepository: UserRepository
}
const claims = await verifyAccessToken(token, config);
const user = userRepository.findById(claims.userId);
const user = await userRepository.findById(claims.userId);
if (!user) {
throw unauthorized('用户不存在');
}
if (user.accountStatus === 'disabled') {
throw unauthorized('账号已被禁用');
}
if (user.tokenVersion !== claims.tokenVersion) {
throw unauthorized('登录状态已失效,请重新登录');
}

View File

@@ -1,27 +1,54 @@
import type { ErrorRequestHandler } from 'express';
import { HttpError } from '../errors.js';
import { toHttpError } from '../errors.js';
import {
applyApiResponseHeaders,
buildApiLogContext,
wantsApiEnvelope,
} from '../http.js';
export const errorHandler: ErrorRequestHandler = (error, request, response, _next) => {
const statusCode =
error instanceof HttpError ? error.statusCode : 500;
const message =
error instanceof HttpError
? error.message
: '服务器内部错误';
export const errorHandler: ErrorRequestHandler = (
error,
request,
response,
_next,
) => {
const normalizedError = toHttpError(error);
const meta = applyApiResponseHeaders(request, response);
request.log?.error(
{
err: error,
request_id: request.requestId,
...buildApiLogContext(request, response),
user_id: request.userId ?? null,
status: normalizedError.statusCode,
error_code: normalizedError.code,
},
'request failed',
);
response.status(statusCode).json({
error: {
message,
},
response.status(normalizedError.statusCode);
const errorPayload = {
code: normalizedError.code,
message: normalizedError.message,
...(normalizedError.expose && normalizedError.details !== undefined
? { details: normalizedError.details }
: {}),
};
if (wantsApiEnvelope(request)) {
response.json({
ok: false,
data: null,
error: errorPayload,
meta,
});
return;
}
response.json({
error: errorPayload,
meta,
});
};

View File

@@ -2,7 +2,15 @@ import crypto from 'node:crypto';
import type { RequestHandler } from 'express';
export const requestIdMiddleware: RequestHandler = (request, _response, next) => {
request.requestId = request.header('x-request-id')?.trim() || crypto.randomUUID();
export const requestIdMiddleware: RequestHandler = (
request,
response,
next,
) => {
const requestId =
request.header('x-request-id')?.trim() || crypto.randomUUID();
request.requestId = requestId;
request.requestStartedAt = Date.now();
response.setHeader('x-request-id', requestId);
next();
};

View File

@@ -0,0 +1,49 @@
import type { RequestHandler, Response } from 'express';
import {
applyApiResponseHeaders,
isStandardApiErrorResponse,
isStandardApiSuccessEnvelope,
toApiErrorBody,
toApiSuccessBody,
} from '../http.js';
function isLegacyApiErrorBody(body: unknown) {
if (!body || typeof body !== 'object' || Array.isArray(body)) {
return false;
}
return (
('error' in body || 'message' in body || 'code' in body) &&
!('meta' in body && 'ok' in body)
);
}
function patchJsonResponse(response: Response) {
const originalJson = response.json.bind(response);
response.json = ((body: unknown) => {
if (
isStandardApiSuccessEnvelope(body) ||
isStandardApiErrorResponse(body)
) {
applyApiResponseHeaders(response.req, response);
return originalJson(body);
}
if (response.statusCode >= 400 || isLegacyApiErrorBody(body)) {
return originalJson(toApiErrorBody(response.req, response, body));
}
return originalJson(toApiSuccessBody(response.req, response, body));
}) as Response['json'];
}
export const responseEnvelopeMiddleware: RequestHandler = (
_request,
response,
next,
) => {
patchJsonResponse(response);
next();
};

View File

@@ -0,0 +1,10 @@
import type { RequestHandler } from 'express';
import { setRouteMeta, type ApiRouteMeta } from '../http.js';
export function routeMeta(meta: ApiRouteMeta): RequestHandler {
return (_request, response, next) => {
setRouteMeta(response, meta);
next();
};
}