@mindbase/express-common 1.0.7 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/commands/prepare.ts +62 -0
- package/core/app.ts +186 -0
- package/core/module/CreateModule.ts +38 -0
- package/core/module/FindPackageRoot.ts +58 -0
- package/core/module/GetModulePath.ts +58 -0
- package/core/state.ts +67 -0
- package/feature/cron/CronManager.ts +63 -0
- package/feature/scanner/FileScanner.ts +289 -0
- package/index.ts +32 -0
- package/middleware/Cors.ts +17 -0
- package/middleware/IpParser.ts +81 -0
- package/middleware/UaParser.ts +50 -0
- package/package.json +14 -10
- package/routes/Doc.route.ts +123 -0
- package/tsconfig.json +8 -0
- package/types/DocTypes.ts +111 -0
- package/types/express.d.ts +12 -0
- package/types/index.ts +19 -0
- package/utils/AppError.ts +21 -0
- package/utils/ComponentRegistry.ts +34 -0
- package/utils/DatabaseMigration.ts +121 -0
- package/utils/Dayjs.ts +16 -0
- package/utils/DocManager.ts +279 -0
- package/utils/HttpServer.ts +41 -0
- package/utils/InitDatabase.ts +133 -0
- package/utils/InitErrorHandler.ts +82 -0
- package/utils/InitExpress.ts +38 -0
- package/utils/Logger.ts +206 -0
- package/utils/MiddlewareRegistry.ts +14 -0
- package/utils/RouteParser.ts +655 -0
- package/utils/RouteRegistry.ts +92 -0
- package/utils/TSTypeParser.ts +456 -0
- package/utils/Validate.ts +25 -0
- package/utils/ZodSchemaParser.ts +420 -0
- package/zod/Doc.schema.ts +9 -0
- package/dist/index.d.mts +0 -360
- package/dist/index.mjs +0 -2449
- package/dist/index.mjs.map +0 -1
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import { drizzle } from "drizzle-orm/better-sqlite3";
|
|
4
|
+
import { migrate } from "drizzle-orm/better-sqlite3/migrator";
|
|
5
|
+
import Database from "better-sqlite3";
|
|
6
|
+
import { AppState } from "../core/state";
|
|
7
|
+
import { ScanResult } from "../types/Index";
|
|
8
|
+
import logger from "./Logger";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 封装细节:从扫描结果中提取 Schema 并初始化数据库
|
|
12
|
+
* @param state 应用状态
|
|
13
|
+
* @param scannedResults 扫描结果
|
|
14
|
+
* @returns 数据库实例
|
|
15
|
+
* @throws 如果数据库设置失败
|
|
16
|
+
*/
|
|
17
|
+
export function setupDatabase(state: AppState, scannedResults: ScanResult[]) {
|
|
18
|
+
try {
|
|
19
|
+
if (!state) {
|
|
20
|
+
throw new Error("应用状态对象不能为空");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!Array.isArray(scannedResults)) {
|
|
24
|
+
throw new Error("扫描结果必须是一个数组");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const schemas: Record<string, any> = {};
|
|
28
|
+
for (const item of scannedResults) {
|
|
29
|
+
if (item.type === "schema" && item.allExports) {
|
|
30
|
+
try {
|
|
31
|
+
Object.assign(schemas, item.allExports);
|
|
32
|
+
} catch (e) {
|
|
33
|
+
logger.warn(`处理 Schema 文件失败: ${item.filePath}`, e);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
state.schemas = schemas;
|
|
38
|
+
|
|
39
|
+
logger.startup("扫描", `提取到 ${Object.keys(schemas).length} 个 Schema 定义`);
|
|
40
|
+
|
|
41
|
+
return initDatabase({
|
|
42
|
+
schemas,
|
|
43
|
+
state,
|
|
44
|
+
});
|
|
45
|
+
} catch (error) {
|
|
46
|
+
logger.error("数据库设置失败:", error);
|
|
47
|
+
throw new Error(`数据库设置失败:${error instanceof Error ? error.message : String(error)}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 初始化 SQLite 数据库
|
|
53
|
+
* @param options 配置选项
|
|
54
|
+
* @returns 数据库实例
|
|
55
|
+
* @throws 如果数据库初始化失败
|
|
56
|
+
*/
|
|
57
|
+
export function initDatabase(options: { schemas: Record<string, any>; dbPath?: string; state?: AppState }): any {
|
|
58
|
+
if (!options) {
|
|
59
|
+
throw new Error("初始化选项不能为空");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const currentDir = process.cwd();
|
|
64
|
+
const dbPath = options.dbPath || options.state?.options?.database?.path || "./data/app.db";
|
|
65
|
+
const absoluteDbPath = path.resolve(currentDir, dbPath);
|
|
66
|
+
const dbDir = path.dirname(absoluteDbPath);
|
|
67
|
+
|
|
68
|
+
logger.startup("数据库", `初始化:路径=${absoluteDbPath}`);
|
|
69
|
+
|
|
70
|
+
// 确保目录存在
|
|
71
|
+
try {
|
|
72
|
+
if (!fs.existsSync(dbDir)) {
|
|
73
|
+
logger.startup("数据库", `创建目录:${dbDir}`);
|
|
74
|
+
fs.mkdirSync(dbDir, { recursive: true });
|
|
75
|
+
}
|
|
76
|
+
} catch (e) {
|
|
77
|
+
throw new Error(`创建数据库目录失败:${e instanceof Error ? e.message : String(e)}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 初始化 SQLite 数据库
|
|
81
|
+
let sqlite: Database.Database;
|
|
82
|
+
try {
|
|
83
|
+
sqlite = new Database(absoluteDbPath);
|
|
84
|
+
} catch (e) {
|
|
85
|
+
throw new Error(`连接数据库失败:${e instanceof Error ? e.message : String(e)}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// SQLite 性能优化设置
|
|
89
|
+
try {
|
|
90
|
+
// 写前日志模式(提升并发性能)
|
|
91
|
+
sqlite.pragma("journal_mode = WAL");
|
|
92
|
+
// 同步模式(安全性与性能平衡)
|
|
93
|
+
sqlite.pragma("synchronous = NORMAL");
|
|
94
|
+
// 页面缓存:1GB(负值表示 KB)
|
|
95
|
+
sqlite.pragma("cache_size = -1048576");
|
|
96
|
+
// 临时表使用内存
|
|
97
|
+
sqlite.pragma("temp_store = MEMORY");
|
|
98
|
+
// 页面大小:4KB
|
|
99
|
+
sqlite.pragma("page_size = 4096");
|
|
100
|
+
// WAL 自动检查点阈值(减少刷盘频率)
|
|
101
|
+
sqlite.pragma("wal_autocheckpoint = 10000");
|
|
102
|
+
// 忙等待超时:5秒
|
|
103
|
+
sqlite.pragma("busy_timeout = 5000");
|
|
104
|
+
} catch (e) {
|
|
105
|
+
logger.warn("设置 SQLite 性能参数失败:", e);
|
|
106
|
+
// 继续执行,不中断初始化
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// 初始化 Drizzle
|
|
110
|
+
let db: any;
|
|
111
|
+
try {
|
|
112
|
+
db = drizzle(sqlite, { schema: options.schemas || {} });
|
|
113
|
+
} catch (e) {
|
|
114
|
+
// 关闭 SQLite 连接
|
|
115
|
+
try {
|
|
116
|
+
sqlite.close();
|
|
117
|
+
} catch (closeError) {
|
|
118
|
+
logger.warn("关闭数据库连接失败:", closeError);
|
|
119
|
+
}
|
|
120
|
+
throw new Error(`初始化 Drizzle ORM 失败:${e instanceof Error ? e.message : String(e)}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// 如果提供了状态对象,更新它
|
|
124
|
+
if (options.state) {
|
|
125
|
+
options.state.db = db;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return db;
|
|
129
|
+
} catch (error) {
|
|
130
|
+
logger.error("数据库初始化失败:", error);
|
|
131
|
+
throw new Error(`数据库初始化失败:${error instanceof Error ? error.message : String(error)}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Request, Response, NextFunction, Express } from "express";
|
|
2
|
+
import { ZodError } from "zod";
|
|
3
|
+
import logger from "./Logger";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 检测是否为 SQL 相关错误(表/字段不存在)
|
|
7
|
+
*/
|
|
8
|
+
function isSQLError(err: any): boolean {
|
|
9
|
+
if (!err) return false;
|
|
10
|
+
const msg = err.message || err.toString() || "";
|
|
11
|
+
return /no such table|table.*does not exist|column.*does not exist|database schema has changed/i.test(msg);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 封装细节:初始化错误处理和 404 中间件
|
|
16
|
+
*/
|
|
17
|
+
export function setupErrorHandlers(app: Express, logging?: boolean) {
|
|
18
|
+
// 404 处理
|
|
19
|
+
app.use((req: Request, res: Response) => {
|
|
20
|
+
res.status(404).json({
|
|
21
|
+
code: 404,
|
|
22
|
+
data: null,
|
|
23
|
+
msg: "资源不存在",
|
|
24
|
+
error: "Not Found",
|
|
25
|
+
fields: {},
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// 错误处理
|
|
30
|
+
app.use((err: any, req: Request, res: Response, next: NextFunction) => {
|
|
31
|
+
// 处理业务错误(带有 isAppError 标记或 4xx 状态码)
|
|
32
|
+
if (err.isAppError || (err.statusCode && err.statusCode >= 400 && err.statusCode < 500)) {
|
|
33
|
+
return res.status(err.statusCode).json({
|
|
34
|
+
code: err.statusCode,
|
|
35
|
+
data: null,
|
|
36
|
+
msg: err.message,
|
|
37
|
+
error: "BusinessError",
|
|
38
|
+
fields: {},
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 处理 Zod 校验错误
|
|
43
|
+
if (err instanceof ZodError) {
|
|
44
|
+
const fields: Record<string, string> = {};
|
|
45
|
+
err.errors.forEach((e) => {
|
|
46
|
+
const path = e.path.join(".");
|
|
47
|
+
fields[path] = e.message;
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
return res.status(400).json({
|
|
51
|
+
code: 400,
|
|
52
|
+
data: null,
|
|
53
|
+
msg: "参数校验失败",
|
|
54
|
+
error: "ZodValidationError",
|
|
55
|
+
fields,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 处理 SQL 错误(给出友好提示)
|
|
60
|
+
if (isSQLError(err)) {
|
|
61
|
+
logger.error("");
|
|
62
|
+
logger.error("❌ 数据库结构可能已变化,请运行以下命令同步:");
|
|
63
|
+
logger.error("");
|
|
64
|
+
logger.error(" npm run db:migrate");
|
|
65
|
+
logger.error("");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 处理普通错误
|
|
69
|
+
const statusCode = err.status || err.statusCode || 500;
|
|
70
|
+
if (!isSQLError(err)) {
|
|
71
|
+
logger.error("系统错误:", err);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
res.status(statusCode).json({
|
|
75
|
+
code: statusCode,
|
|
76
|
+
data: null,
|
|
77
|
+
msg: err.message || "服务器内部错误",
|
|
78
|
+
error: logging ? err.stack : "InternalServerError",
|
|
79
|
+
fields: {},
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import express, { Express } from "express";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import cookieParser from "cookie-parser";
|
|
4
|
+
import cors from "../middleware/Cors";
|
|
5
|
+
import ipParser from "../middleware/IpParser";
|
|
6
|
+
import uaParser from "../middleware/UaParser";
|
|
7
|
+
import { AppState } from "../types/Index";
|
|
8
|
+
import logger from "./Logger";
|
|
9
|
+
|
|
10
|
+
export default function initExpress(mindBaseState: AppState): void {
|
|
11
|
+
const app = mindBaseState.express;
|
|
12
|
+
const options = mindBaseState.options;
|
|
13
|
+
app.getDB = () => mindBaseState.db;
|
|
14
|
+
app.configs = mindBaseState.options;
|
|
15
|
+
app.disable("x-powered-by");
|
|
16
|
+
app.use(express.urlencoded({ extended: false, limit: "10mb" }));
|
|
17
|
+
app.use(express.json({ limit: "10mb" }));
|
|
18
|
+
app.use(cookieParser());
|
|
19
|
+
if (options.staticPath) {
|
|
20
|
+
if (!fs.existsSync(options.staticPath)) {
|
|
21
|
+
fs.mkdirSync(options.staticPath, { recursive: true });
|
|
22
|
+
}
|
|
23
|
+
app.use(express.static(options.staticPath));
|
|
24
|
+
logger.startup("设置", `静态目录: ${options.staticPath}`);
|
|
25
|
+
}
|
|
26
|
+
if (options.cors) {
|
|
27
|
+
app.use(cors);
|
|
28
|
+
logger.startup("设置", "跨域请求头");
|
|
29
|
+
}
|
|
30
|
+
if (options.ip) {
|
|
31
|
+
logger.startup("设置", "IP地址解析");
|
|
32
|
+
app.use(ipParser);
|
|
33
|
+
}
|
|
34
|
+
if (options.userAgent) {
|
|
35
|
+
logger.startup("设置", "用户代理解析");
|
|
36
|
+
app.use(uaParser);
|
|
37
|
+
}
|
|
38
|
+
}
|
package/utils/Logger.ts
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import dayjs from "./Dayjs";
|
|
2
|
+
|
|
3
|
+
export type LogLevel = "debug" | "info" | "warn" | "error" | "silent";
|
|
4
|
+
|
|
5
|
+
const LOG_LEVELS = {
|
|
6
|
+
debug: 0,
|
|
7
|
+
info: 1,
|
|
8
|
+
warn: 2,
|
|
9
|
+
error: 3,
|
|
10
|
+
silent: 4,
|
|
11
|
+
} as const;
|
|
12
|
+
|
|
13
|
+
let currentLogLevel: LogLevel = "info";
|
|
14
|
+
|
|
15
|
+
const COLORS = {
|
|
16
|
+
debug: "\x1b[36m",
|
|
17
|
+
info: "\x1b[32m",
|
|
18
|
+
warn: "\x1b[33m",
|
|
19
|
+
error: "\x1b[31m",
|
|
20
|
+
reset: "\x1b[0m",
|
|
21
|
+
} as const;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 计算字符串在终端中的视觉宽度
|
|
25
|
+
* 修复:区分单宽符号 (如 ✓) 和双宽符号 (如 中文、Emoji)
|
|
26
|
+
*/
|
|
27
|
+
function getVisualWidth(str: string): number {
|
|
28
|
+
let width = 0;
|
|
29
|
+
const segmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
30
|
+
for (const { segment } of segmenter.segment(str)) {
|
|
31
|
+
const charCode = segment.charCodeAt(0);
|
|
32
|
+
|
|
33
|
+
// 1. 基础 ASCII
|
|
34
|
+
if (charCode <= 255) {
|
|
35
|
+
width += 1;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 2. CJK 字符集范围 (常用中文、标点、全角符号)
|
|
40
|
+
const isCJK =
|
|
41
|
+
(charCode >= 0x4e00 && charCode <= 0x9fff) || // CJK Unified Ideographs
|
|
42
|
+
(charCode >= 0x3000 && charCode <= 0x303f) || // CJK Symbols and Punctuation
|
|
43
|
+
(charCode >= 0xff00 && charCode <= 0xffef); // Fullwidth Forms
|
|
44
|
+
|
|
45
|
+
// 3. Emoji 或代理对 (通常 segment.length > 1)
|
|
46
|
+
const isMultiByte = segment.length > 1;
|
|
47
|
+
|
|
48
|
+
if (isCJK || isMultiByte) {
|
|
49
|
+
width += 2;
|
|
50
|
+
} else {
|
|
51
|
+
// 4. 其他特殊符号 (如 ✓, ★, ☎) 在大多数终端占 1 格
|
|
52
|
+
width += 1;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return width;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 截断字符串以适应视觉宽度,从头部截断并保留末尾
|
|
60
|
+
*/
|
|
61
|
+
function truncateToWidth(str: string, maxWidth: number): string {
|
|
62
|
+
const segmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
63
|
+
const segments = Array.from(segmenter.segment(str)).map((s) => s.segment);
|
|
64
|
+
|
|
65
|
+
if (getVisualWidth(str) <= maxWidth) return str;
|
|
66
|
+
|
|
67
|
+
let currentWidth = 3; // 为 "..." 预留
|
|
68
|
+
let result = "";
|
|
69
|
+
|
|
70
|
+
// 从后往前遍历 segments
|
|
71
|
+
for (let i = segments.length - 1; i >= 0; i--) {
|
|
72
|
+
const segment = segments[i];
|
|
73
|
+
const segmentWidth = getVisualWidth(segment);
|
|
74
|
+
if (currentWidth + segmentWidth > maxWidth) break;
|
|
75
|
+
result = segment + result;
|
|
76
|
+
currentWidth += segmentWidth;
|
|
77
|
+
}
|
|
78
|
+
return "..." + result;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function formatMessage(level: LogLevel, message: string): string {
|
|
82
|
+
const timestamp = dayjs().format("YYYY-MM-DD HH:mm:ss");
|
|
83
|
+
const terminalWidth = process.stdout.columns || 80;
|
|
84
|
+
const timeStr = ` ${timestamp}`;
|
|
85
|
+
const timeWidth = getVisualWidth(timeStr);
|
|
86
|
+
const maxMessageWidth = terminalWidth - timeWidth - 2;
|
|
87
|
+
|
|
88
|
+
let displayMessage = message;
|
|
89
|
+
const messageWidth = getVisualWidth(displayMessage);
|
|
90
|
+
|
|
91
|
+
if (messageWidth > maxMessageWidth) {
|
|
92
|
+
displayMessage = truncateToWidth(displayMessage, maxMessageWidth);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const currentMsgWidth = getVisualWidth(displayMessage);
|
|
96
|
+
const paddingCount = Math.max(0, terminalWidth - currentMsgWidth - timeWidth);
|
|
97
|
+
const padding = " ".repeat(paddingCount);
|
|
98
|
+
|
|
99
|
+
return `${COLORS[level]}${displayMessage}${padding}${timeStr}${COLORS.reset}`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const startupTime = Date.now();
|
|
103
|
+
|
|
104
|
+
// 标签填充函数(目标显示宽度 8,空格分散到字符间)
|
|
105
|
+
function padTag(tag: string, targetWidth = 8): string {
|
|
106
|
+
const currentWidth = getVisualWidth(tag);
|
|
107
|
+
const padding = Math.max(0, targetWidth - currentWidth);
|
|
108
|
+
|
|
109
|
+
if (padding === 0) return tag;
|
|
110
|
+
|
|
111
|
+
// 将空格均匀分散到字符之间
|
|
112
|
+
const chars = [...tag]; // 按码点分割
|
|
113
|
+
const gapCount = chars.length + 1; // 间隙数 = 字符数 + 1(前后和中间)
|
|
114
|
+
|
|
115
|
+
// 计算每个间隙的空格数
|
|
116
|
+
const baseSpaces = Math.floor(padding / gapCount);
|
|
117
|
+
const extraSpaces = padding % gapCount;
|
|
118
|
+
|
|
119
|
+
let result = "";
|
|
120
|
+
for (let i = 0; i < chars.length; i++) {
|
|
121
|
+
// 每个间隙的空格 = 基础空格 + 额外空格(前 extraSpaces 个间隙多1个)
|
|
122
|
+
const spaces = baseSpaces + (i < extraSpaces ? 1 : 0);
|
|
123
|
+
result += " ".repeat(spaces) + chars[i];
|
|
124
|
+
}
|
|
125
|
+
// 最后一个间隙
|
|
126
|
+
result += " ".repeat(baseSpaces + (chars.length < extraSpaces ? 1 : 0));
|
|
127
|
+
|
|
128
|
+
return result;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function startupMessage(message: string, tag?: string): string {
|
|
132
|
+
const diff = Date.now() - startupTime;
|
|
133
|
+
const timeStr = diff.toString().padStart(6, "0");
|
|
134
|
+
const tagStr = tag ? `【${padTag(tag)}】` : "";
|
|
135
|
+
return `${COLORS["warn"]}[${timeStr}] ${tagStr}${message}${COLORS["reset"]}`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function shouldLog(level: LogLevel): boolean {
|
|
139
|
+
return LOG_LEVELS[level] >= LOG_LEVELS[currentLogLevel];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function formatArgs(args: any[]): string {
|
|
143
|
+
return args
|
|
144
|
+
.map((arg) => {
|
|
145
|
+
if (arg instanceof Error) {
|
|
146
|
+
return arg.message;
|
|
147
|
+
}
|
|
148
|
+
if (typeof arg === "object") {
|
|
149
|
+
return JSON.stringify(arg, null, 2);
|
|
150
|
+
}
|
|
151
|
+
return String(arg);
|
|
152
|
+
})
|
|
153
|
+
.join(" ");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export const logger = {
|
|
157
|
+
debug(...args: any[]): void {
|
|
158
|
+
if (shouldLog("debug")) {
|
|
159
|
+
console.log(formatMessage("debug", formatArgs(args)));
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
info(...args: any[]): void {
|
|
164
|
+
if (shouldLog("info")) {
|
|
165
|
+
console.log(formatMessage("info", formatArgs(args)));
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
|
|
169
|
+
warn(...args: any[]): void {
|
|
170
|
+
if (shouldLog("warn")) {
|
|
171
|
+
console.warn(formatMessage("warn", formatArgs(args)));
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
error(...args: any[]): void {
|
|
176
|
+
if (shouldLog("error")) {
|
|
177
|
+
// 先打印带颜色的格式化消息
|
|
178
|
+
console.error(formatMessage("error", formatArgs(args)));
|
|
179
|
+
// 如果有 Error 对象,用 console.error 单独打印以显示堆栈
|
|
180
|
+
const errorArg = args.find((arg) => arg instanceof Error);
|
|
181
|
+
if (errorArg) {
|
|
182
|
+
console.error(errorArg);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
|
|
187
|
+
startup(tagOrMessage: string, ...args: any[]): void {
|
|
188
|
+
// 如果有额外参数,第一个是标签,其余是消息
|
|
189
|
+
// 否则第一个参数是消息,无标签
|
|
190
|
+
if (args.length > 0) {
|
|
191
|
+
console.log(startupMessage(formatArgs(args), tagOrMessage));
|
|
192
|
+
} else {
|
|
193
|
+
console.log(startupMessage(tagOrMessage));
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
setLevel(level: LogLevel): void {
|
|
198
|
+
currentLogLevel = level;
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
getLevel(): LogLevel {
|
|
202
|
+
return currentLogLevel;
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
export default logger;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Express } from "express";
|
|
2
|
+
import { ScanResult } from "../types/Index";
|
|
3
|
+
import logger from "./Logger";
|
|
4
|
+
|
|
5
|
+
export async function registerMiddleware(app: Express, config: ScanResult): Promise<void> {
|
|
6
|
+
const { fileName = "anonymous", defaultExport: handler } = config;
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
app.use(handler);
|
|
10
|
+
logger.startup("中间件", `注册: ${fileName}`);
|
|
11
|
+
} catch (error) {
|
|
12
|
+
logger.error(`注册中间件失败 ${fileName}:`, error);
|
|
13
|
+
}
|
|
14
|
+
}
|