feat: 前端组件准备

This commit is contained in:
2026-05-22 15:02:31 +08:00
parent 397ccdca13
commit 5e05bf4f63
62 changed files with 6903 additions and 878 deletions
+80
View File
@@ -0,0 +1,80 @@
// server/utils/logging.ts - 安全日志工具,避免输出密钥、cookie 和完整敏感内容
import { randomUUID } from "node:crypto";
import type { H3Event } from "h3";
/**
* 常见敏感片段匹配
*
* 这里不是完整 DLP,只处理最容易误打出来的 key、Bearer token 和 token=xxx
* 真实请求体、响应体本来就不应该进入日志
*/
const SENSITIVE_VALUE_RE =
/(sk-[A-Za-z0-9_-]+|Bearer\s+[A-Za-z0-9._-]+|token[=:]\s*[^,\s}]+)/giu;
/** 对错误摘要做最小脱敏和截断,避免日志过长或泄露密钥 */
const sanitizeLogText = (value: string) => {
return value.replace(SENSITIVE_VALUE_RE, "[redacted]").slice(0, 500);
};
/**
* 将 unknown 异常变成安全可打印对象
*
* API 对前端统一返回“服务器内部错误”,日志里只保留类型和脱敏摘要,
* 既能排查方向,又不把上游响应体或 key 打出去
*/
export const toSafeLogError = (error: unknown) => {
if (error instanceof Error) {
return {
name: error.name,
message: sanitizeLogText(error.message)
};
}
return {
name: "UnknownError",
message: sanitizeLogText(String(error))
};
};
/**
* 创建带 requestId 的 API 日志器
*
* 每个 handler 只需要记录“阶段”和少量结构化字段;这里统一补 method、path、
* 耗时等上下文,方便后续按一次请求串起来查
*/
export const createApiLogger = (event: H3Event, scope: string) => {
const requestId =
event.node.req.headers["x-request-id"]?.toString() || randomUUID();
const startedAt = Date.now();
/** 内部统一出口,避免不同 level 打出的字段不一致 */
const write = (
level: "info" | "warn" | "error",
stage: string,
extra: Record<string, unknown> = {}
) => {
const payload = {
requestId,
scope,
stage,
method: event.node.req.method,
path: event.path,
elapsedMs: Date.now() - startedAt,
...extra
};
console[level](`[${scope}] ${stage}`, JSON.stringify(payload));
};
/** 返回轻量 APIhandler 不直接接触 console,便于以后替换日志实现 */
return {
requestId,
info: (stage: string, extra?: Record<string, unknown>) =>
write("info", stage, extra),
warn: (stage: string, extra?: Record<string, unknown>) =>
write("warn", stage, extra),
error: (stage: string, extra?: Record<string, unknown>) =>
write("error", stage, extra)
};
};