统一前后端代码格式化配置

This commit is contained in:
yml2213
2026-08-16 17:27:12 +08:00
parent 8705f6a6a1
commit 5c7c3e14e3
278 changed files with 6386 additions and 5500 deletions
+24 -31
View File
@@ -1,51 +1,44 @@
import type { Request, Response, NextFunction } from "express";
import { createRequestId, logInfo } from "../utils/logger.js";
import type { Request, Response, NextFunction } from 'express'
import { createRequestId, logInfo } from '../utils/logger.js'
export function accessLogMiddleware(
req: Request,
res: Response,
next: NextFunction
): void {
const startedAt = Date.now();
const requestId = resolveRequestId(req);
export function accessLogMiddleware(req: Request, res: Response, next: NextFunction): void {
const startedAt = Date.now()
const requestId = resolveRequestId(req)
req.requestId = requestId;
res.setHeader("X-Request-Id", requestId);
req.requestId = requestId
res.setHeader('X-Request-Id', requestId)
res.on("finish", () => {
res.on('finish', () => {
if (shouldSkipAccessLog(req.originalUrl, res.statusCode)) {
return;
return
}
logInfo("[http/access]", "request completed", {
logInfo('[http/access]', 'request completed', {
requestId,
method: req.method,
originalUrl: req.originalUrl,
statusCode: res.statusCode,
durationMs: Date.now() - startedAt,
ip: req.ip,
forwardedFor: String(req.headers["x-forwarded-for"] || ""),
userAgent: String(req.headers["user-agent"] || ""),
referer: String(req.headers.referer || ""),
contentLength: Number(res.getHeader("content-length") || 0),
actorUserId: req.adminSession?.userId || "",
actorUsername: req.adminSession?.username || "",
actorRole: req.adminSession?.role || "",
});
});
forwardedFor: String(req.headers['x-forwarded-for'] || ''),
userAgent: String(req.headers['user-agent'] || ''),
referer: String(req.headers.referer || ''),
contentLength: Number(res.getHeader('content-length') || 0),
actorUserId: req.adminSession?.userId || '',
actorUsername: req.adminSession?.username || '',
actorRole: req.adminSession?.role || '',
})
})
next();
next()
}
function resolveRequestId(req: Request): string {
const fromHeader = String(req.headers["x-request-id"] || "").trim();
return fromHeader || createRequestId("req");
const fromHeader = String(req.headers['x-request-id'] || '').trim()
return fromHeader || createRequestId('req')
}
function shouldSkipAccessLog(originalUrl: string, statusCode: number): boolean {
const pathname = String(originalUrl || "").split("?")[0] || "";
return (
["/health", "/health/live", "/health/ready"].includes(pathname) &&
Number(statusCode) < 400
);
const pathname = String(originalUrl || '').split('?')[0] || ''
return ['/health', '/health/live', '/health/ready'].includes(pathname) && Number(statusCode) < 400
}
+18 -29
View File
@@ -1,43 +1,32 @@
import type { Request, Response, NextFunction } from "express";
import type { RuntimeConfig } from "../types/runtime-config.js";
import type { Request, Response, NextFunction } from 'express'
import type { RuntimeConfig } from '../types/runtime-config.js'
export function createCorsMiddleware(
config: RuntimeConfig
config: RuntimeConfig,
): (req: Request, res: Response, next: NextFunction) => void {
const allowedOrigins = config.cors.allowedOrigins;
const isWildcard =
allowedOrigins.length === 1 && allowedOrigins[0] === "*";
const allowedOrigins = config.cors.allowedOrigins
const isWildcard = allowedOrigins.length === 1 && allowedOrigins[0] === '*'
return function corsMiddleware(
req: Request,
res: Response,
next: NextFunction
): void {
return function corsMiddleware(req: Request, res: Response, next: NextFunction): void {
if (isWildcard) {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader('Access-Control-Allow-Origin', '*')
} else {
const origin = req.headers.origin;
const origin = req.headers.origin
if (origin && allowedOrigins.includes(origin)) {
res.setHeader("Access-Control-Allow-Origin", origin);
res.setHeader("Access-Control-Allow-Credentials", "true");
res.setHeader('Access-Control-Allow-Origin', origin)
res.setHeader('Access-Control-Allow-Credentials', 'true')
}
}
res.setHeader(
"Access-Control-Allow-Headers",
"Content-Type, Authorization"
);
res.setHeader(
"Access-Control-Allow-Methods",
"GET, POST, PUT, PATCH, DELETE, OPTIONS"
);
res.setHeader("Access-Control-Max-Age", "86400");
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization')
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS')
res.setHeader('Access-Control-Max-Age', '86400')
if (req.method === "OPTIONS") {
res.sendStatus(204);
return;
if (req.method === 'OPTIONS') {
res.sendStatus(204)
return
}
next();
};
next()
}
}
+64 -67
View File
@@ -1,116 +1,113 @@
import test from "node:test";
import assert from "node:assert/strict";
import type { NextFunction, Request, Response } from "express";
import test from 'node:test'
import assert from 'node:assert/strict'
import type { NextFunction, Request, Response } from 'express'
import {
createRateLimitMiddleware,
resetRateLimitBucketsForTest,
} from "./rate-limit.js";
import { createRateLimitMiddleware, resetRateLimitBucketsForTest } from './rate-limit.js'
test("createRateLimitMiddleware allows requests within the window", () => {
resetRateLimitBucketsForTest();
test('createRateLimitMiddleware allows requests within the window', () => {
resetRateLimitBucketsForTest()
const limiter = createRateLimitMiddleware({
scope: "test:allow",
scope: 'test:allow',
windowMs: 60_000,
max: 2,
});
const req = createMockRequest();
const res = createMockResponse();
let nextCount = 0;
})
const req = createMockRequest()
const res = createMockResponse()
let nextCount = 0
const next: NextFunction = () => {
nextCount += 1;
};
nextCount += 1
}
limiter(req, res, next);
limiter(req, res, next);
limiter(req, res, next)
limiter(req, res, next)
assert.equal(nextCount, 2);
assert.equal(res.statusCode, 200);
});
assert.equal(nextCount, 2)
assert.equal(res.statusCode, 200)
})
test("createRateLimitMiddleware blocks requests over the limit", () => {
resetRateLimitBucketsForTest();
test('createRateLimitMiddleware blocks requests over the limit', () => {
resetRateLimitBucketsForTest()
const limiter = createRateLimitMiddleware({
scope: "test:block",
scope: 'test:block',
windowMs: 60_000,
max: 1,
});
const req = createMockRequest();
const res = createMockResponse();
let nextCount = 0;
})
const req = createMockRequest()
const res = createMockResponse()
let nextCount = 0
const next: NextFunction = () => {
nextCount += 1;
};
nextCount += 1
}
limiter(req, res, next);
limiter(req, res, next);
limiter(req, res, next)
limiter(req, res, next)
assert.equal(nextCount, 1);
assert.equal(res.statusCode, 429);
assert.equal(res.headers["Retry-After"], "60");
assert.equal(res.body.errorCode, "rate_limited");
});
assert.equal(nextCount, 1)
assert.equal(res.statusCode, 429)
assert.equal(res.headers['Retry-After'], '60')
assert.equal(res.body.errorCode, 'rate_limited')
})
test("createRateLimitMiddleware supports custom limit responses", () => {
resetRateLimitBucketsForTest();
test('createRateLimitMiddleware supports custom limit responses', () => {
resetRateLimitBucketsForTest()
const limiter = createRateLimitMiddleware({
scope: "test:custom",
scope: 'test:custom',
windowMs: 60_000,
max: 1,
onLimit: (_req, res) => {
res.status(200).json({ code: 429, message: "limited" });
res.status(200).json({ code: 429, message: 'limited' })
},
});
const req = createMockRequest();
const res = createMockResponse();
})
const req = createMockRequest()
const res = createMockResponse()
limiter(req, res, () => {});
limiter(req, res, () => {});
limiter(req, res, () => {})
limiter(req, res, () => {})
assert.equal(res.statusCode, 200);
assert.deepEqual(res.body, { code: 429, message: "limited" });
});
assert.equal(res.statusCode, 200)
assert.deepEqual(res.body, { code: 429, message: 'limited' })
})
function createMockRequest(): Request {
return {
ip: "127.0.0.1",
originalUrl: "/test",
ip: '127.0.0.1',
originalUrl: '/test',
headers: {},
socket: {
remoteAddress: "127.0.0.1",
remoteAddress: '127.0.0.1',
},
} as Request;
} as Request
}
function createMockResponse(): Response & {
statusCode: number;
headers: Record<string, string>;
body: any;
statusCode: number
headers: Record<string, string>
body: any
} {
const res = {
statusCode: 200,
headers: {} as Record<string, string>,
body: null as any,
setHeader(name: string, value: string) {
this.headers[name] = value;
return this;
this.headers[name] = value
return this
},
status(statusCode: number) {
this.statusCode = statusCode;
return this;
this.statusCode = statusCode
return this
},
json(body: any) {
this.body = body;
return this;
this.body = body
return this
},
};
}
return res as Response & {
statusCode: number;
headers: Record<string, string>;
body: any;
};
statusCode: number
headers: Record<string, string>
body: any
}
}
+56 -55
View File
@@ -1,30 +1,30 @@
import type { NextFunction, Request, Response } from "express";
import type { NextFunction, Request, Response } from 'express'
import { buildErrorPayload, createHttpError } from "../utils/http.js";
import { logWarn } from "../utils/logger.js";
import { buildErrorPayload, createHttpError } from '../utils/http.js'
import { logWarn } from '../utils/logger.js'
type RateLimitKeyResolver = (req: Request) => string;
type RateLimitKeyResolver = (req: Request) => string
type RateLimitExceededContext = {
scope: string;
retryAfterSeconds: number;
};
scope: string
retryAfterSeconds: number
}
type RateLimitOptions = {
scope: string;
windowMs: number;
max: number;
key?: RateLimitKeyResolver;
onLimit?: (req: Request, res: Response, context: RateLimitExceededContext) => void;
};
scope: string
windowMs: number
max: number
key?: RateLimitKeyResolver
onLimit?: (req: Request, res: Response, context: RateLimitExceededContext) => void
}
type RateLimitBucket = {
count: number;
resetAt: number;
};
count: number
resetAt: number
}
const buckets = new Map<string, RateLimitBucket>();
let lastCleanupAt = 0;
const buckets = new Map<string, RateLimitBucket>()
let lastCleanupAt = 0
export function createRateLimitMiddleware({
scope,
@@ -33,90 +33,91 @@ export function createRateLimitMiddleware({
key = defaultRateLimitKey,
onLimit,
}: RateLimitOptions) {
const normalizedScope = String(scope || "default").trim() || "default";
const normalizedWindowMs = Math.max(1000, Number(windowMs || 0));
const normalizedMax = Math.max(1, Number(max || 0));
const normalizedScope = String(scope || 'default').trim() || 'default'
const normalizedWindowMs = Math.max(1000, Number(windowMs || 0))
const normalizedMax = Math.max(1, Number(max || 0))
return (req: Request, res: Response, next: NextFunction): void => {
const now = Date.now();
cleanupExpiredBuckets(now);
const now = Date.now()
cleanupExpiredBuckets(now)
const bucketKey = `${normalizedScope}:${key(req)}`;
const current = buckets.get(bucketKey);
const bucket = current && current.resetAt > now
? current
: { count: 0, resetAt: now + normalizedWindowMs };
const bucketKey = `${normalizedScope}:${key(req)}`
const current = buckets.get(bucketKey)
const bucket =
current && current.resetAt > now ? current : { count: 0, resetAt: now + normalizedWindowMs }
bucket.count += 1;
buckets.set(bucketKey, bucket);
bucket.count += 1
buckets.set(bucketKey, bucket)
if (bucket.count <= normalizedMax) {
next();
return;
next()
return
}
const retryAfterSeconds = Math.max(1, Math.ceil((bucket.resetAt - now) / 1000));
res.setHeader("Retry-After", String(retryAfterSeconds));
const retryAfterSeconds = Math.max(1, Math.ceil((bucket.resetAt - now) / 1000))
res.setHeader('Retry-After', String(retryAfterSeconds))
logWarn("[rate-limit]", "请求触发限流", {
logWarn('[rate-limit]', '请求触发限流', {
scope: normalizedScope,
ip: req.ip,
originalUrl: req.originalUrl,
retryAfterSeconds,
});
})
if (onLimit) {
onLimit(req, res, {
scope: normalizedScope,
retryAfterSeconds,
});
return;
})
return
}
const error = createHttpError("请求过于频繁,请稍后再试", {
const error = createHttpError('请求过于频繁,请稍后再试', {
statusCode: 429,
errorCode: "rate_limited",
});
res.status(429).json(buildErrorPayload(error, "请求过于频繁,请稍后再试"));
};
errorCode: 'rate_limited',
})
res.status(429).json(buildErrorPayload(error, '请求过于频繁,请稍后再试'))
}
}
export function resetRateLimitBucketsForTest(): void {
buckets.clear();
lastCleanupAt = 0;
buckets.clear()
lastCleanupAt = 0
}
export function getBodyFieldRateLimitKey(fieldName: string): RateLimitKeyResolver {
return (req) => [defaultRateLimitKey(req), normalizeKeyPart(req.body?.[fieldName])].join(":");
return (req) => [defaultRateLimitKey(req), normalizeKeyPart(req.body?.[fieldName])].join(':')
}
export function getParamRateLimitKey(paramName: string): RateLimitKeyResolver {
return (req) => [defaultRateLimitKey(req), normalizeKeyPart(req.params?.[paramName])].join(":");
return (req) => [defaultRateLimitKey(req), normalizeKeyPart(req.params?.[paramName])].join(':')
}
function defaultRateLimitKey(req: Request): string {
return normalizeKeyPart(
req.ip ||
String(req.headers["x-forwarded-for"] || "").split(",")[0] ||
String(req.headers['x-forwarded-for'] || '').split(',')[0] ||
req.socket.remoteAddress ||
"unknown",
);
'unknown',
)
}
function normalizeKeyPart(value: unknown): string {
const normalized = String(value || "").trim().toLowerCase();
return normalized || "unknown";
const normalized = String(value || '')
.trim()
.toLowerCase()
return normalized || 'unknown'
}
function cleanupExpiredBuckets(now: number): void {
if (now - lastCleanupAt < 60_000) {
return;
return
}
lastCleanupAt = now;
lastCleanupAt = now
for (const [key, bucket] of buckets.entries()) {
if (bucket.resetAt <= now) {
buckets.delete(key);
buckets.delete(key)
}
}
}