chanjs 2.3.1 → 2.5.0
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/App.js +41 -42
- package/base/Controller.js +5 -110
- package/base/Database.js +1 -232
- package/base/Service.js +2 -35
- package/global/import.js +1 -5
- package/helper/index.js +0 -2
- package/index.js +0 -1
- package/middleware/index.js +0 -1
- package/middleware/template.js +2 -1
- package/middleware/waf.js +34 -6
- package/package.json +1 -1
- package/utils/index.js +1 -3
- package/utils/keywords.js +8 -2
- package/utils/response.js +87 -10
- package/base/Context.js +0 -78
- package/helper/db.js +0 -79
- package/middleware/preventRetry.js +0 -30
- package/middleware/validator.js +0 -43
- package/utils/error-handler.js +0 -115
- package/utils/error.js +0 -81
package/middleware/waf.js
CHANGED
|
@@ -2,13 +2,15 @@ import { getIp } from "../helper/ip.js";
|
|
|
2
2
|
import { checkKeywords } from "../utils/checker.js";
|
|
3
3
|
import { filterXSS } from "../utils/xss-filter.js";
|
|
4
4
|
import { createRateLimitMiddleware } from "../utils/rate-limit.js";
|
|
5
|
-
import { configError } from "../utils/error.js";
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
7
|
* Web应用防火墙(WAF)中间件
|
|
9
8
|
* 提供访问限流、关键词过滤、XSS防护等功能
|
|
10
9
|
*/
|
|
11
10
|
|
|
11
|
+
const WAF_BLOCKED_KEY = `${process.env.APP_NAME || 'app'}_waf_blocked`;
|
|
12
|
+
const WAF_BLOCKED_HEADER = 'x-waf-blocked';
|
|
13
|
+
|
|
12
14
|
/**
|
|
13
15
|
* 路径白名单 - 这些路径不会被 WAF 检查
|
|
14
16
|
* @type {Array<string>}
|
|
@@ -56,6 +58,18 @@ function createWafMiddleware(wafConfig) {
|
|
|
56
58
|
res.setHeader("X-XSS-Protection", "1; mode=block");
|
|
57
59
|
res.setHeader("X-Frame-Options", "DENY");
|
|
58
60
|
|
|
61
|
+
if (!isWhitelistedPath(requestPath)) {
|
|
62
|
+
const isBlocked = req.headers[WAF_BLOCKED_HEADER];
|
|
63
|
+
if (isBlocked === 'true') {
|
|
64
|
+
console.error(`[WAF 永久封禁] IP:${clientIp} 路径:${requestPath} 原因:localStorage标记`);
|
|
65
|
+
return res.status(403).json({
|
|
66
|
+
code: 403,
|
|
67
|
+
success: false,
|
|
68
|
+
msg: '您的访问已被限制,如需恢复请联系管理员'
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
59
73
|
await new Promise((resolve) => {
|
|
60
74
|
rateLimitMiddleware(req, res, resolve);
|
|
61
75
|
});
|
|
@@ -92,11 +106,25 @@ function createWafMiddleware(wafConfig) {
|
|
|
92
106
|
const { category, keyword } = matched;
|
|
93
107
|
console.error(`[WAF 拦截] IP:${clientIp} 路径:${requestPath} 关键词:${keyword} 类别:${category}`);
|
|
94
108
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
109
|
+
const htmlResponse = `
|
|
110
|
+
<!DOCTYPE html>
|
|
111
|
+
<html>
|
|
112
|
+
<head>
|
|
113
|
+
<meta charset="UTF-8">
|
|
114
|
+
<title>访问受限</title>
|
|
115
|
+
</head>
|
|
116
|
+
<body>
|
|
117
|
+
<h1>非法请求</h1>
|
|
118
|
+
<p>检测到恶意内容,您的访问已被限制。</p>
|
|
119
|
+
<p>如需恢复请联系管理员。</p>
|
|
120
|
+
<script>
|
|
121
|
+
localStorage.setItem('${WAF_BLOCKED_KEY}', 'true');
|
|
122
|
+
</script>
|
|
123
|
+
</body>
|
|
124
|
+
</html>
|
|
125
|
+
`;
|
|
126
|
+
|
|
127
|
+
return res.status(403).setHeader('Content-Type', 'text/html').send(htmlResponse);
|
|
100
128
|
}
|
|
101
129
|
}
|
|
102
130
|
|
package/package.json
CHANGED
package/utils/index.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
export { success, fail, error } from "./response.js";
|
|
2
|
-
export { parseDatabaseError, notFoundResponse, errorResponse } from "./error-handler.js";
|
|
3
|
-
export { configError, authError } from "./error.js";
|
|
1
|
+
export { success, fail, error, parseDatabaseError, notFoundResponse, errorResponse } from "./response.js";
|
|
4
2
|
export { checkKeywords, isIgnored } from "./checker.js";
|
|
5
3
|
export { filterXSS } from "./xss-filter.js";
|
|
6
4
|
export { createRateLimitMiddleware } from "./rate-limit.js";
|
package/utils/keywords.js
CHANGED
|
@@ -16,7 +16,7 @@ export const KEYWORD_RULES = {
|
|
|
16
16
|
*/
|
|
17
17
|
extensions: [
|
|
18
18
|
".php", ".asp", ".aspx", ".jsp", ".jspx", ".do", ".action", ".cgi",
|
|
19
|
-
".py", ".pl", ".cfm", ".jhtml", ".shtml"
|
|
19
|
+
".py", ".pl", ".cfm", ".jhtml", ".shtml",".sql"
|
|
20
20
|
],
|
|
21
21
|
/**
|
|
22
22
|
* 敏感目录名称
|
|
@@ -39,7 +39,13 @@ export const KEYWORD_RULES = {
|
|
|
39
39
|
commandInjection: [
|
|
40
40
|
"cmd=", "system(", "exec(", "shell_exec(", "passthru(",
|
|
41
41
|
"eval(", "assert(", "preg_replace", "bash -i", "rm -rf",
|
|
42
|
-
"wget ", "curl ", "chmod ", "base64_decode", "phpinfo()"
|
|
42
|
+
"wget ", "curl ", "chmod ", "base64_decode", "phpinfo()",
|
|
43
|
+
"kill ", "killall", "shutdown", "reboot", "halt", "fdisk",
|
|
44
|
+
"mkfs", "dd ", "ssh ", "scp ", "rsync", "nc ",
|
|
45
|
+
"netcat", "nmap", "iptables", "systemctl", "service",
|
|
46
|
+
"init", "crontab", "at ", "su ", "sudo", "useradd",
|
|
47
|
+
"userdel", "usermod", "groupadd", "groupdel", "passwd",
|
|
48
|
+
"chpasswd", "mount ", "umount", "ln -s"
|
|
43
49
|
],
|
|
44
50
|
/**
|
|
45
51
|
* 路径遍历关键词
|
package/utils/response.js
CHANGED
|
@@ -5,6 +5,20 @@ import { CODE, DB_ERROR } from "../config/code.js";
|
|
|
5
5
|
* 提供统一的响应格式和错误处理
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
const ERROR_MESSAGES = {
|
|
9
|
+
6001: "数据库连接失败",
|
|
10
|
+
6002: "数据库访问被拒绝",
|
|
11
|
+
6003: "存在关联数据,操作失败",
|
|
12
|
+
6004: "数据库字段错误",
|
|
13
|
+
6005: "数据重复,违反唯一性约束",
|
|
14
|
+
6006: "目标表不存在",
|
|
15
|
+
6007: "数据库操作超时",
|
|
16
|
+
6008: "数据库语法错误,请检查查询语句",
|
|
17
|
+
6009: "数据库连接已关闭,请重试",
|
|
18
|
+
4003: "资源已存在",
|
|
19
|
+
5001: "系统内部错误",
|
|
20
|
+
};
|
|
21
|
+
|
|
8
22
|
/**
|
|
9
23
|
* 获取默认错误代码
|
|
10
24
|
* @private
|
|
@@ -23,6 +37,32 @@ const getDefaultErrorCode = (error) => {
|
|
|
23
37
|
return 5001;
|
|
24
38
|
};
|
|
25
39
|
|
|
40
|
+
/**
|
|
41
|
+
* 解析数据库错误
|
|
42
|
+
* @param {Error} error - 数据库错误对象
|
|
43
|
+
* @returns {Object} 包含 code、msg 和 statusCode 的对象
|
|
44
|
+
* @description
|
|
45
|
+
* 根据数据库错误代码映射为业务状态码
|
|
46
|
+
* 返回对应的错误消息和 HTTP 状态码
|
|
47
|
+
*/
|
|
48
|
+
export function parseDatabaseError(error) {
|
|
49
|
+
const errorCode = error?.code && DB_ERROR[error.code]
|
|
50
|
+
? DB_ERROR[error.code]
|
|
51
|
+
: error?.message?.includes("syntax") || error?.message?.includes("SQL")
|
|
52
|
+
? 6008
|
|
53
|
+
: error?.message?.includes("Connection closed")
|
|
54
|
+
? 6009
|
|
55
|
+
: error?.message?.includes("permission")
|
|
56
|
+
? 3003
|
|
57
|
+
: 5001;
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
code: errorCode,
|
|
61
|
+
msg: ERROR_MESSAGES[errorCode] || error?.message || "服务器内部错误",
|
|
62
|
+
statusCode: errorCode >= 6000 ? 500 : errorCode >= 4000 ? 400 : 500,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
26
66
|
/**
|
|
27
67
|
* 生成错误响应
|
|
28
68
|
* @param {Object} options - 响应选项
|
|
@@ -33,8 +73,6 @@ const getDefaultErrorCode = (error) => {
|
|
|
33
73
|
* @description
|
|
34
74
|
* 根据错误类型生成标准错误响应
|
|
35
75
|
* 开发环境下包含数据库错误详情
|
|
36
|
-
* @example
|
|
37
|
-
* const response = error({ err: databaseError });
|
|
38
76
|
*/
|
|
39
77
|
export const error = ({ err, data = {}, code = 500 } = {}) => {
|
|
40
78
|
if (err) {
|
|
@@ -70,10 +108,6 @@ export const error = ({ err, data = {}, code = 500 } = {}) => {
|
|
|
70
108
|
* @param {Object} [options.data={}] - 响应数据
|
|
71
109
|
* @param {number} [options.code=201] - 错误代码
|
|
72
110
|
* @returns {Object} 失败响应对象
|
|
73
|
-
* @description
|
|
74
|
-
* 生成标准失败响应
|
|
75
|
-
* @example
|
|
76
|
-
* const response = fail({ msg: '用户不存在', code: 404 });
|
|
77
111
|
*/
|
|
78
112
|
export const fail = ({ msg = "操作失败", data = {}, code = 201 } = {}) => {
|
|
79
113
|
return {
|
|
@@ -90,10 +124,6 @@ export const fail = ({ msg = "操作失败", data = {}, code = 201 } = {}) => {
|
|
|
90
124
|
* @param {Object} [options.data={}] - 响应数据
|
|
91
125
|
* @param {string} [options.msg="操作成功"] - 成功消息
|
|
92
126
|
* @returns {Object} 成功响应对象
|
|
93
|
-
* @description
|
|
94
|
-
* 生成标准成功响应
|
|
95
|
-
* @example
|
|
96
|
-
* const response = success({ data: { id: 1, name: '张三' } });
|
|
97
127
|
*/
|
|
98
128
|
export const success = ({ data = {}, msg = "操作成功" } = {}) => ({
|
|
99
129
|
success: true,
|
|
@@ -101,3 +131,50 @@ export const success = ({ data = {}, msg = "操作成功" } = {}) => ({
|
|
|
101
131
|
code: 200,
|
|
102
132
|
data,
|
|
103
133
|
});
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* 生成 404 响应
|
|
137
|
+
* @param {Object} req - Express 请求对象
|
|
138
|
+
* @returns {Object} 404 响应对象
|
|
139
|
+
*/
|
|
140
|
+
export function notFoundResponse(req) {
|
|
141
|
+
return {
|
|
142
|
+
success: false,
|
|
143
|
+
msg: "接口不存在",
|
|
144
|
+
code: 404,
|
|
145
|
+
data: { path: req.path, method: req.method },
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* 生成错误响应(Express 错误处理中间件用)
|
|
151
|
+
* @param {Error} err - 错误对象
|
|
152
|
+
* @param {Object} req - Express 请求对象
|
|
153
|
+
* @returns {Object} 错误响应对象
|
|
154
|
+
*/
|
|
155
|
+
export function errorResponse(err, req) {
|
|
156
|
+
const errorInfo = parseDatabaseError(err);
|
|
157
|
+
|
|
158
|
+
console.error(`[Error Handler] ${errorInfo.msg} - ${err?.message}`, {
|
|
159
|
+
code: errorInfo.code,
|
|
160
|
+
path: req?.path,
|
|
161
|
+
method: req?.method,
|
|
162
|
+
sql: err?.sql,
|
|
163
|
+
sqlMessage: err?.sqlMessage,
|
|
164
|
+
stack: err?.stack,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
success: false,
|
|
169
|
+
msg: errorInfo.msg,
|
|
170
|
+
code: errorInfo.code,
|
|
171
|
+
data: process.env.NODE_ENV === "development"
|
|
172
|
+
? {
|
|
173
|
+
message: err?.message,
|
|
174
|
+
sql: err?.sql,
|
|
175
|
+
sqlMessage: err?.sqlMessage,
|
|
176
|
+
stack: err?.stack,
|
|
177
|
+
}
|
|
178
|
+
: {},
|
|
179
|
+
};
|
|
180
|
+
}
|
package/base/Context.js
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 应用上下文类
|
|
3
|
-
* 用于存储和管理应用的配置和模型实例
|
|
4
|
-
*/
|
|
5
|
-
class AppContext {
|
|
6
|
-
/**
|
|
7
|
-
* 构造函数
|
|
8
|
-
* 初始化配置和模型的Map存储
|
|
9
|
-
*/
|
|
10
|
-
constructor() {
|
|
11
|
-
this._config = new Map();
|
|
12
|
-
this._models = new Map();
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* 设置配置项
|
|
17
|
-
* @param {string} key - 配置键名
|
|
18
|
-
* @param {*} value - 配置值
|
|
19
|
-
*/
|
|
20
|
-
set(key, value) {
|
|
21
|
-
this._config.set(key, value);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* 获取配置项
|
|
26
|
-
* @param {string} key - 配置键名
|
|
27
|
-
* @param {*} defaultValue - 默认值
|
|
28
|
-
* @returns {*} 配置值或默认值
|
|
29
|
-
*/
|
|
30
|
-
get(key, defaultValue) {
|
|
31
|
-
return this._config.get(key) ?? defaultValue;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* 检查配置项是否存在
|
|
36
|
-
* @param {string} key - 配置键名
|
|
37
|
-
* @returns {boolean} 是否存在
|
|
38
|
-
*/
|
|
39
|
-
has(key) {
|
|
40
|
-
return this._config.has(key);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* 设置模型实例
|
|
45
|
-
* @param {string} name - 模型名称
|
|
46
|
-
* @param {Object} instance - 模型实例
|
|
47
|
-
*/
|
|
48
|
-
setModel(name, instance) {
|
|
49
|
-
this._models.set(name, instance);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* 获取模型实例
|
|
54
|
-
* @param {string} name - 模型名称
|
|
55
|
-
* @returns {Object|undefined} 模型实例
|
|
56
|
-
*/
|
|
57
|
-
getModel(name) {
|
|
58
|
-
return this._models.get(name);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* 获取所有模型实例
|
|
63
|
-
* @returns {Object} 包含所有模型的对象
|
|
64
|
-
*/
|
|
65
|
-
getAllModels() {
|
|
66
|
-
return Object.fromEntries(this._models.entries());
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* 清空所有配置和模型
|
|
71
|
-
*/
|
|
72
|
-
clear() {
|
|
73
|
-
this._config.clear();
|
|
74
|
-
this._models.clear();
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export default AppContext;
|
package/helper/db.js
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import knex from "knex";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* 创建 Knex 数据库连接实例
|
|
5
|
-
* @param {Object} config - 数据库配置对象
|
|
6
|
-
* @param {string} config.client - 数据库客户端类型(如 mysql2, pg, sqlite3)
|
|
7
|
-
* @param {Object} config.connection - 数据库连接配置
|
|
8
|
-
* @param {Object} [config.pool] - 连接池配置
|
|
9
|
-
* @returns {Knex} Knex 实例
|
|
10
|
-
* @example
|
|
11
|
-
* const db = db({
|
|
12
|
-
* client: 'mysql2',
|
|
13
|
-
* connection: {
|
|
14
|
-
* host: 'localhost',
|
|
15
|
-
* user: 'root',
|
|
16
|
-
* password: 'password',
|
|
17
|
-
* database: 'mydb'
|
|
18
|
-
* }
|
|
19
|
-
* });
|
|
20
|
-
*/
|
|
21
|
-
export function db(config) {
|
|
22
|
-
return knex(config);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* 根据前缀从环境变量中获取数据库配置
|
|
27
|
-
* @param {string} prefix - 配置前缀(如 primary, secondary, redis)
|
|
28
|
-
* @returns {Object|null} 解析后的配置对象,如果配置不存在或解析失败则返回 null
|
|
29
|
-
* @description
|
|
30
|
-
* 支持的前缀映射:
|
|
31
|
-
* - primary -> DB_PRIMARY
|
|
32
|
-
* - secondary -> DB_SECONDARY
|
|
33
|
-
* - redis -> REDIS
|
|
34
|
-
* - 其他 -> DB_{PREFIX.toUpperCase()}
|
|
35
|
-
*
|
|
36
|
-
* 配置格式为 JSON 字符串,存储在环境变量中
|
|
37
|
-
* @example
|
|
38
|
-
* // 环境变量: DB_PRIMARY='{"client":"mysql2","connection":{"host":"localhost"}}'
|
|
39
|
-
* const config = prefixDbConfig('primary');
|
|
40
|
-
* // 返回: { client: 'mysql2', connection: { host: 'localhost' } }
|
|
41
|
-
*/
|
|
42
|
-
export function prefixDbConfig(prefix) {
|
|
43
|
-
const prefixMap = {
|
|
44
|
-
primary: "DB_PRIMARY",
|
|
45
|
-
secondary: "DB_SECONDARY",
|
|
46
|
-
redis: "REDIS",
|
|
47
|
-
};
|
|
48
|
-
const envKey = prefixMap[prefix] || `DB_${prefix.toUpperCase()}`;
|
|
49
|
-
const configStr = process.env[envKey];
|
|
50
|
-
|
|
51
|
-
if (configStr) {
|
|
52
|
-
try {
|
|
53
|
-
return JSON.parse(configStr);
|
|
54
|
-
} catch (e) {
|
|
55
|
-
console.error(`[DB] 配置解析失败: ${envKey}`);
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const client = process.env.DB_CLIENT || "mysql2";
|
|
61
|
-
if (client !== "mysql2") {
|
|
62
|
-
return null;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return {
|
|
66
|
-
client,
|
|
67
|
-
connection: {
|
|
68
|
-
host: process.env.DB_HOST || "localhost",
|
|
69
|
-
port: parseInt(process.env.DB_PORT || "3306"),
|
|
70
|
-
user: process.env.DB_USER || "root",
|
|
71
|
-
password: process.env.DB_PASS || "123456",
|
|
72
|
-
database: process.env.DB_DATABASE || "chancms"
|
|
73
|
-
},
|
|
74
|
-
pool: {
|
|
75
|
-
min: parseInt(process.env.DB_POOL_MIN || "2"),
|
|
76
|
-
max: parseInt(process.env.DB_POOL_MAX || "10")
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 防止快速重复请求中间件
|
|
3
|
-
* 使用 Cache 实现请求频率限制
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import Cache from "../helper/cache.js";
|
|
7
|
-
|
|
8
|
-
const sendResponse = (res, code, message, data = null) => {
|
|
9
|
-
res.json({ code, msg: message, data });
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
const requestCache = new Cache({ maxSize: 1000, defaultTTL: 500 });
|
|
13
|
-
|
|
14
|
-
export default () => {
|
|
15
|
-
return (req, res, next) => {
|
|
16
|
-
if (req.method !== 'GET') {
|
|
17
|
-
return next();
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const key = `${req.method}:${req.originalUrl}`;
|
|
21
|
-
|
|
22
|
-
if (requestCache.has(key)) {
|
|
23
|
-
console.log(`[preventRetry] 阻止快速重复请求: ${key}`);
|
|
24
|
-
return sendResponse(res, 429, '请求过于频繁,请稍后再试');
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
requestCache.set(key, Date.now());
|
|
28
|
-
next();
|
|
29
|
-
};
|
|
30
|
-
};
|
package/middleware/validator.js
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 请求验证中间件
|
|
3
|
-
* 提供请求参数验证功能
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* 创建验证中间件
|
|
8
|
-
* @param {Object} schemas - 验证规则对象
|
|
9
|
-
* @param {Object} [schemas.headers] - 请求头验证规则
|
|
10
|
-
* @param {Object} [schemas.params] - 路径参数验证规则
|
|
11
|
-
* @param {Object} [schemas.query] - 查询参数验证规则
|
|
12
|
-
* @param {Object} [schemas.body] - 请求体验证规则
|
|
13
|
-
* @returns {Function} Express 中间件函数
|
|
14
|
-
* @description
|
|
15
|
-
* 验证请求的 headers、params、query 和 body
|
|
16
|
-
* 使用 Zod 风格的验证规则
|
|
17
|
-
* 验证失败返回 400 状态码和错误信息
|
|
18
|
-
* @example
|
|
19
|
-
* const schema = {
|
|
20
|
-
* body: z.object({ name: z.string() })
|
|
21
|
-
* };
|
|
22
|
-
* app.use(validator(schema));
|
|
23
|
-
*/
|
|
24
|
-
export const validator = (schemas) => (req, res, next) => {
|
|
25
|
-
const sections = {
|
|
26
|
-
headers: schemas.headers,
|
|
27
|
-
params: schemas.params,
|
|
28
|
-
query: schemas.query,
|
|
29
|
-
body: schemas.body,
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
for (const [key, schema] of Object.entries(sections)) {
|
|
33
|
-
if (!schema) continue;
|
|
34
|
-
|
|
35
|
-
const result = schema.safeParse(req[key]);
|
|
36
|
-
if (!result.success) {
|
|
37
|
-
const firstError = result.error.errors[0];
|
|
38
|
-
return res.status(400).json({ error: firstError.message });
|
|
39
|
-
}
|
|
40
|
-
req[key] = result.data;
|
|
41
|
-
}
|
|
42
|
-
next();
|
|
43
|
-
};
|
package/utils/error-handler.js
DELETED
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 数据库错误处理工具
|
|
3
|
-
* 提供数据库错误解析和错误响应生成功能
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export const DB_ERROR = {
|
|
7
|
-
ECONNREFUSED: 6001,
|
|
8
|
-
ER_ACCESS_DENIED_ERROR: 6002,
|
|
9
|
-
ER_ROW_IS_REFERENCED_2: 6003,
|
|
10
|
-
ER_BAD_FIELD_ERROR: 6004,
|
|
11
|
-
ER_DUP_ENTRY: 6005,
|
|
12
|
-
ER_NO_SUCH_TABLE: 6006,
|
|
13
|
-
ETIMEOUT: 6007,
|
|
14
|
-
ER_TABLE_EXISTS_ERROR: 4003,
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export const ERROR_MESSAGES = {
|
|
18
|
-
6001: "数据库连接失败",
|
|
19
|
-
6002: "数据库访问被拒绝",
|
|
20
|
-
6003: "存在关联数据,操作失败",
|
|
21
|
-
6004: "数据库字段错误",
|
|
22
|
-
6005: "数据重复,违反唯一性约束",
|
|
23
|
-
6006: "目标表不存在",
|
|
24
|
-
6007: "数据库操作超时",
|
|
25
|
-
6008: "数据库语法错误,请检查查询语句",
|
|
26
|
-
6009: "数据库连接已关闭,请重试",
|
|
27
|
-
4003: "资源已存在",
|
|
28
|
-
5001: "系统内部错误",
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* 解析数据库错误
|
|
33
|
-
* @param {Error} error - 数据库错误对象
|
|
34
|
-
* @returns {Object} 包含 code、msg 和 statusCode 的对象
|
|
35
|
-
* @description
|
|
36
|
-
* 根据数据库错误代码映射为业务状态码
|
|
37
|
-
* 返回对应的错误消息和 HTTP 状态码
|
|
38
|
-
* @example
|
|
39
|
-
* const errorInfo = parseDatabaseError({ code: 'ER_DUP_ENTRY' });
|
|
40
|
-
* console.log(errorInfo); // { code: 6005, msg: '数据重复...', statusCode: 500 }
|
|
41
|
-
*/
|
|
42
|
-
export function parseDatabaseError(error) {
|
|
43
|
-
const errorCode = error?.code && DB_ERROR[error.code]
|
|
44
|
-
? DB_ERROR[error.code]
|
|
45
|
-
: error?.message?.includes("syntax") || error?.message?.includes("SQL")
|
|
46
|
-
? 6008
|
|
47
|
-
: error?.message?.includes("Connection closed")
|
|
48
|
-
? 6009
|
|
49
|
-
: error?.message?.includes("permission")
|
|
50
|
-
? 3003
|
|
51
|
-
: 5001;
|
|
52
|
-
|
|
53
|
-
return {
|
|
54
|
-
code: errorCode,
|
|
55
|
-
msg: ERROR_MESSAGES[errorCode] || error?.message || "服务器内部错误",
|
|
56
|
-
statusCode: errorCode >= 6000 ? 500 : errorCode >= 4000 ? 400 : 500,
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* 生成 404 响应
|
|
62
|
-
* @param {Object} req - Express 请求对象
|
|
63
|
-
* @returns {Object} 404 响应对象
|
|
64
|
-
* @description
|
|
65
|
-
* 返回标准的接口不存在响应
|
|
66
|
-
* 包含请求的路径和方法
|
|
67
|
-
* @example
|
|
68
|
-
* const response = notFoundResponse(req);
|
|
69
|
-
*/
|
|
70
|
-
export function notFoundResponse(req) {
|
|
71
|
-
return {
|
|
72
|
-
success: false,
|
|
73
|
-
msg: "接口不存在",
|
|
74
|
-
code: 404,
|
|
75
|
-
data: { path: req.path, method: req.method },
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* 生成错误响应
|
|
81
|
-
* @param {Error} err - 错误对象
|
|
82
|
-
* @param {Object} req - Express 请求对象
|
|
83
|
-
* @returns {Object} 错误响应对象
|
|
84
|
-
* @description
|
|
85
|
-
* 根据错误类型生成相应的错误响应
|
|
86
|
-
* 开发环境下包含详细的错误信息
|
|
87
|
-
* @example
|
|
88
|
-
* const response = errorResponse(error, req);
|
|
89
|
-
*/
|
|
90
|
-
export function errorResponse(err, req) {
|
|
91
|
-
const errorInfo = parseDatabaseError(err);
|
|
92
|
-
|
|
93
|
-
console.error(`[Error Handler] ${errorInfo.msg} - ${err?.message}`, {
|
|
94
|
-
code: errorInfo.code,
|
|
95
|
-
path: req?.path,
|
|
96
|
-
method: req?.method,
|
|
97
|
-
sql: err?.sql,
|
|
98
|
-
sqlMessage: err?.sqlMessage,
|
|
99
|
-
stack: err?.stack,
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
return {
|
|
103
|
-
success: false,
|
|
104
|
-
msg: errorInfo.msg,
|
|
105
|
-
code: errorInfo.code,
|
|
106
|
-
data: process.env.NODE_ENV === "development"
|
|
107
|
-
? {
|
|
108
|
-
message: err?.message,
|
|
109
|
-
sql: err?.sql,
|
|
110
|
-
sqlMessage: err?.sqlMessage,
|
|
111
|
-
stack: err?.stack,
|
|
112
|
-
}
|
|
113
|
-
: {},
|
|
114
|
-
};
|
|
115
|
-
}
|
package/utils/error.js
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 错误处理和日志工具
|
|
3
|
-
* 提供错误码定义和错误日志记录功能
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export const CODE = {
|
|
7
|
-
SUCCESS: { code: 200, message: "操作成功" },
|
|
8
|
-
FAIL: { code: 201, message: "操作失败" },
|
|
9
|
-
ERROR: { code: 500, message: "服务器错误" },
|
|
10
|
-
UNAUTHORIZED: { code: 401, message: "未授权" },
|
|
11
|
-
FORBIDDEN: { code: 403, message: "禁止访问" },
|
|
12
|
-
NOT_FOUND: { code: 404, message: "资源不存在" },
|
|
13
|
-
VALIDATION: { code: 400, message: "参数校验失败" },
|
|
14
|
-
CONFIG: { code: 500, message: "配置错误" },
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* 记录错误日志
|
|
19
|
-
* @private
|
|
20
|
-
* @param {string} type - 错误类型
|
|
21
|
-
* @param {string} message - 错误消息
|
|
22
|
-
* @param {Object} [extra={}] - 额外信息
|
|
23
|
-
* @description
|
|
24
|
-
* 将错误信息格式化为 JSON 并输出到控制台
|
|
25
|
-
* 包含时间戳、错误类型和消息
|
|
26
|
-
*/
|
|
27
|
-
function logError(type, message, extra = {}) {
|
|
28
|
-
const timestamp = new Date().toISOString();
|
|
29
|
-
console.error(JSON.stringify({
|
|
30
|
-
timestamp,
|
|
31
|
-
type,
|
|
32
|
-
message,
|
|
33
|
-
...extra,
|
|
34
|
-
}));
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* 创建错误对象
|
|
39
|
-
* @private
|
|
40
|
-
* @param {string} message - 错误消息
|
|
41
|
-
* @param {number} [statusCode=500] - HTTP 状态码
|
|
42
|
-
* @param {string} [code="ERROR"] - 错误代码
|
|
43
|
-
* @returns {Object} 错误对象
|
|
44
|
-
*/
|
|
45
|
-
function createError(message, statusCode = 500, code = "ERROR") {
|
|
46
|
-
logError(code, message, { statusCode });
|
|
47
|
-
return {
|
|
48
|
-
isError: true,
|
|
49
|
-
message,
|
|
50
|
-
statusCode,
|
|
51
|
-
code,
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* 生成配置错误
|
|
57
|
-
* @param {string} message - 错误消息
|
|
58
|
-
* @returns {Object} 配置错误对象
|
|
59
|
-
* @description
|
|
60
|
-
* 生成配置类型错误并记录日志
|
|
61
|
-
* @example
|
|
62
|
-
* const error = configError('数据库配置缺失');
|
|
63
|
-
*/
|
|
64
|
-
export function configError(message) {
|
|
65
|
-
logError("CONFIG_ERROR", message);
|
|
66
|
-
return createError(message, CODE.CONFIG.code, "CONFIG_ERROR");
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* 生成认证错误
|
|
71
|
-
* @param {string} [message=CODE.UNAUTHORIZED.message] - 错误消息
|
|
72
|
-
* @returns {Object} 认证错误对象
|
|
73
|
-
* @description
|
|
74
|
-
* 生成认证类型错误并记录日志
|
|
75
|
-
* @example
|
|
76
|
-
* const error = authError('Token已过期');
|
|
77
|
-
*/
|
|
78
|
-
export function authError(message = CODE.UNAUTHORIZED.message) {
|
|
79
|
-
logError("AUTH_ERROR", message);
|
|
80
|
-
return createError(message, CODE.UNAUTHORIZED.code, "AUTH_ERROR");
|
|
81
|
-
}
|