chanjs 2.7.3 → 2.7.5
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/USAGE.md +533 -0
- package/config/index.js +37 -6
- package/core/App.js +166 -0
- package/core/Container.js +77 -0
- package/core/Controller.js +29 -0
- package/core/Database.js +93 -0
- package/core/Repository.js +327 -0
- package/core/Service.js +11 -0
- package/core/bootstrap/error-handler.js +104 -0
- package/core/bootstrap/hook-runner.js +64 -0
- package/core/bootstrap/middleware.js +35 -0
- package/core/bootstrap/router-loader.js +53 -0
- package/core/errors.js +224 -0
- package/core/loader.js +89 -0
- package/core/registry.js +17 -0
- package/doc/Cache.md +279 -106
- package/doc/Common.md +590 -134
- package/doc/Controller.md +166 -95
- package/doc/Help.md +299 -698
- package/doc/QuickStart.md +116 -0
- package/doc/Repository.md +560 -0
- package/doc/Service.md +201 -527
- package/index.js +75 -37
- package/middleware/body.js +17 -0
- package/middleware/cookie.js +7 -15
- package/middleware/cors.js +9 -27
- package/middleware/favicon.js +7 -17
- package/middleware/header.js +15 -16
- package/middleware/index.js +11 -11
- package/middleware/log.js +26 -56
- package/middleware/static.js +15 -28
- package/middleware/template.js +75 -115
- package/middleware/validate.js +79 -0
- package/middleware/waf.js +174 -197
- package/package.json +11 -3
- package/response/code.js +73 -0
- package/response/index.js +9 -6
- package/response/response.js +82 -236
- package/security/checker.js +26 -74
- package/security/index.js +4 -9
- package/security/jwt.js +69 -142
- package/security/keywords.js +32 -136
- package/security/rate-limit.js +38 -80
- package/security/sign.js +83 -176
- package/security/xss-filter.js +21 -53
- package/storage/cache.js +57 -196
- package/storage/index.js +3 -6
- package/storage/redis.js +123 -181
- package/storage/store.js +163 -188
- package/utils/data-parse.js +42 -186
- package/utils/file.js +73 -244
- package/utils/filter.js +22 -25
- package/utils/html.js +49 -33
- package/utils/index.js +21 -7
- package/utils/ip.js +31 -71
- package/utils/logger.js +117 -0
- package/utils/pages.js +55 -0
- package/utils/paths.js +18 -0
- package/utils/request.js +94 -136
- package/utils/signal.js +87 -0
- package/utils/time.js +33 -75
- package/utils/tree.js +112 -104
- package/App.js +0 -533
- package/base/Aop.js +0 -195
- package/base/Container.js +0 -161
- package/base/Controller.js +0 -65
- package/base/Database.js +0 -133
- package/base/Event.js +0 -61
- package/base/Repository.js +0 -644
- package/common/api.js +0 -35
- package/common/code.js +0 -52
- package/common/email.js +0 -191
- package/common/index.js +0 -5
- package/common/pages.js +0 -120
- package/common/utils.js +0 -73
- package/config/code.js +0 -166
- package/config/paths.js +0 -60
- package/doc/Aop.md +0 -269
- package/doc/Email.md +0 -114
- package/doc/Event.md +0 -232
- package/global/env.js +0 -11
- package/global/import.js +0 -39
- package/global/index.js +0 -8
- package/helper/index.js +0 -79
- package/loader/index.js +0 -6
- package/loader/loader.js +0 -138
- package/middleware/compress.js +0 -185
- package/middleware/setBody.js +0 -32
- package/realtime/index.js +0 -7
- package/realtime/sse.js +0 -424
- package/realtime/websocket.js +0 -540
- package/schedule/index.js +0 -6
- package/schedule/schedule.js +0 -491
package/response/response.js
CHANGED
|
@@ -1,258 +1,104 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
CODE_OK,
|
|
5
|
-
CODE_BUSINESS_FAIL,
|
|
6
|
-
CODE_SYSTEM_ERROR,
|
|
7
|
-
CODE_NOT_FOUND,
|
|
8
|
-
getCodeMsg,
|
|
9
|
-
} from "../config/code.js";
|
|
1
|
+
import { CODE_OK, CODE_BUSINESS_FAIL, CODE_NOT_FOUND, CODE_SYSTEM_ERROR, getCodeMsg } from "./code.js";
|
|
2
|
+
import { isAppError } from "../core/errors.js";
|
|
3
|
+
import { htmlEncode } from "../utils/html.js";
|
|
10
4
|
|
|
11
5
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* ============================================================
|
|
16
|
-
* 统一响应格式(三层 code 体系)
|
|
17
|
-
* ============================================================
|
|
18
|
-
* 所有响应都遵循以下结构:
|
|
19
|
-
* {
|
|
20
|
-
* success: boolean, // 业务是否成功(true/false)
|
|
21
|
-
* code: number, // 业务 code(0 成功 / 1xxx 业务 / 5xxx 系统 / 6xxx 数据库)
|
|
22
|
-
* msg: string, // 用户可见消息
|
|
23
|
-
* data: any // 业务数据
|
|
24
|
-
* }
|
|
25
|
-
*
|
|
26
|
-
* HTTP Status 与业务 code 解耦:
|
|
27
|
-
* - HTTP Status 由网关/Express 控制(200/400/401/403/404/500)
|
|
28
|
-
* - 业务 code 由业务层控制(0 / 1xxx / 5xxx / 6xxx)
|
|
29
|
-
* - 前端先看 HTTP Status,再看业务 code
|
|
30
|
-
*
|
|
31
|
-
* ============================================================
|
|
32
|
-
* 使用示例
|
|
33
|
-
* ============================================================
|
|
34
|
-
* // 成功
|
|
35
|
-
* success({ data: { list: [] } })
|
|
36
|
-
* // → { success: true, code: 0, msg: '操作成功', data: { list: [] } }
|
|
37
|
-
*
|
|
38
|
-
* // 业务失败(通用)
|
|
39
|
-
* fail({ msg: '栏目下存在文章' })
|
|
40
|
-
* // → { success: false, code: 1008, msg: '栏目下存在文章', data: {} }
|
|
41
|
-
*
|
|
42
|
-
* // 业务失败(指定 code)
|
|
43
|
-
* fail({ msg: '参数缺失', code: 1007 })
|
|
44
|
-
* // → { success: false, code: 1007, msg: '参数缺失', data: {} }
|
|
45
|
-
*
|
|
46
|
-
* // 错误(系统/数据库异常,err 必传)
|
|
47
|
-
* error({ err: dbError })
|
|
48
|
-
* // → { success: false, code: 6005, msg: '数据重复', data: {} }
|
|
6
|
+
* 统一响应序列化工具(纯函数:无 IO、无日志、无环境判断、无 DB 感知)
|
|
7
|
+
* 仅做数据格式化,可复用于定时任务 / RPC / 接口返回,与全局错误处理器解耦
|
|
49
8
|
*/
|
|
9
|
+
export const success = ({ data = {}, msg = getCodeMsg(CODE_OK) } = {}) => ({
|
|
10
|
+
success: true, code: CODE_OK, msg, data,
|
|
11
|
+
});
|
|
50
12
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
* @private
|
|
55
|
-
*/
|
|
56
|
-
const ERROR_MESSAGES = CODE;
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* 是否暴露错误详情(sql/stack)
|
|
60
|
-
* 显式环境变量控制,避免 development 模式自动暴露敏感信息
|
|
61
|
-
* - 默认 false:生产/开发都不暴露
|
|
62
|
-
* - 设 EXPOSE_ERR_DETAIL=true 后才返回 sql/stack/message 等详细信息
|
|
63
|
-
* @private
|
|
64
|
-
*/
|
|
65
|
-
const EXPOSE_ERR_DETAIL = process.env.EXPOSE_ERR_DETAIL === 'true';
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* 根据错误对象推断业务 code
|
|
69
|
-
* @private
|
|
70
|
-
* @param {Error} error - 错误对象
|
|
71
|
-
* @returns {number} 业务 code
|
|
72
|
-
*/
|
|
73
|
-
const inferErrorCode = (error) => {
|
|
74
|
-
if (!error?.message) return CODE_SYSTEM_ERROR;
|
|
75
|
-
|
|
76
|
-
const msg = error.message.toLowerCase();
|
|
77
|
-
if (msg.includes("syntax") || msg.includes("sql")) {
|
|
78
|
-
return 6008; // 数据库语法错误
|
|
79
|
-
}
|
|
80
|
-
if (msg.includes("connection closed") || msg.includes("connection lost")) {
|
|
81
|
-
return 6009; // 数据库连接关闭
|
|
82
|
-
}
|
|
83
|
-
if (msg.includes("permission") || msg.includes("access denied")) {
|
|
84
|
-
return 1003; // 权限不足
|
|
85
|
-
}
|
|
86
|
-
return CODE_SYSTEM_ERROR;
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* 解析数据库错误
|
|
91
|
-
* @param {Error} error - 数据库错误对象
|
|
92
|
-
* @returns {{code: number, msg: string, statusCode: number}} 包含 code、msg 和 HTTP statusCode
|
|
93
|
-
* @description
|
|
94
|
-
* 将数据库原生错误码映射为业务 code,并给出对应的 HTTP statusCode
|
|
95
|
-
* - 6xxx 数据库错误 → HTTP 500
|
|
96
|
-
* - 1xxx 业务错误 → HTTP 400
|
|
97
|
-
* - 其他 → HTTP 500
|
|
98
|
-
*/
|
|
99
|
-
export function parseDatabaseError(error) {
|
|
100
|
-
let errorCode;
|
|
101
|
-
if (error?.code && DB_ERROR[error.code]) {
|
|
102
|
-
errorCode = DB_ERROR[error.code];
|
|
103
|
-
} else {
|
|
104
|
-
errorCode = inferErrorCode(error);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// 根据 code 段位决定 HTTP statusCode
|
|
108
|
-
let statusCode;
|
|
109
|
-
if (errorCode >= 6000) {
|
|
110
|
-
statusCode = 500; // 数据库错误 → 500
|
|
111
|
-
} else if (errorCode >= 5000) {
|
|
112
|
-
statusCode = 500; // 系统错误 → 500
|
|
113
|
-
} else if (errorCode >= 1000) {
|
|
114
|
-
statusCode = 400; // 业务错误 → 400
|
|
115
|
-
} else {
|
|
116
|
-
statusCode = 500; // 兜底
|
|
117
|
-
}
|
|
13
|
+
export const fail = ({ msg = getCodeMsg(CODE_BUSINESS_FAIL), data = {}, code = CODE_BUSINESS_FAIL } = {}) => ({
|
|
14
|
+
success: false, code, msg, data,
|
|
15
|
+
});
|
|
118
16
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
17
|
+
/** 404 路由不存在专用返回体 */
|
|
18
|
+
export const routeNotFound = req => ({
|
|
19
|
+
success: false,
|
|
20
|
+
code: CODE_NOT_FOUND,
|
|
21
|
+
msg: "请求的资源不存在",
|
|
22
|
+
data: { path: req.originalUrl || req.url, method: req.method },
|
|
23
|
+
});
|
|
125
24
|
|
|
126
25
|
/**
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
* @param {Object} [options.data={}] - 响应数据
|
|
131
|
-
* @param {number} [options.code=5001] - 错误 code(err 为空时使用)
|
|
132
|
-
* @returns {Object} 错误响应对象
|
|
133
|
-
* @description
|
|
134
|
-
* - 传 err:自动解析数据库错误码并映射为业务 code
|
|
135
|
-
* - 不传 err:使用传入的 code 或默认 5001
|
|
136
|
-
* 开发环境下(NODE_ENV=development)附带数据库错误详情
|
|
26
|
+
* 统一错误序列化格式化(纯函数)
|
|
27
|
+
* - 业务错误(AppError 体系):提取 code / msg + 自定义扩展字段
|
|
28
|
+
* - 系统 / 未知错误:生产环境强制脱敏,仅 exposeDetail=true 时附带堆栈
|
|
137
29
|
*/
|
|
138
|
-
export
|
|
139
|
-
if (err) {
|
|
140
|
-
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
sql: err?.sql,
|
|
152
|
-
sqlMessage: err?.sqlMessage,
|
|
153
|
-
message: err?.message,
|
|
154
|
-
} : {},
|
|
155
|
-
};
|
|
30
|
+
export function serializeError(err, exposeDetail = false) {
|
|
31
|
+
if (isAppError(err)) {
|
|
32
|
+
const result = { success: false, code: err.code, msg: err.message };
|
|
33
|
+
const extra = {};
|
|
34
|
+
for (const key of Object.getOwnPropertyNames(err)) {
|
|
35
|
+
const val = err[key];
|
|
36
|
+
if (val === undefined || val === null) continue;
|
|
37
|
+
if (typeof val === "function" || typeof val === "symbol") continue;
|
|
38
|
+
try { extra[key] = val && typeof val === "object" && !Array.isArray(val) ? JSON.stringify(val) : val; }
|
|
39
|
+
catch { extra[key] = "[unserializable]"; }
|
|
40
|
+
}
|
|
41
|
+
if (Object.keys(extra).length > 0) result.data = extra;
|
|
42
|
+
return result;
|
|
156
43
|
}
|
|
157
44
|
|
|
158
|
-
|
|
45
|
+
// 原生未知系统错误:生产环境强制脱敏,不泄露 SQL / 堆栈
|
|
159
46
|
return {
|
|
160
47
|
success: false,
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
data,
|
|
48
|
+
code: CODE_SYSTEM_ERROR,
|
|
49
|
+
msg: err?.message || "系统内部错误",
|
|
50
|
+
...(exposeDetail && err?.stack ? { data: { stack: err.stack } } : {}),
|
|
164
51
|
};
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* 生成失败响应(业务失败用)
|
|
169
|
-
* @param {Object} options - 响应选项
|
|
170
|
-
* @param {string} [options.msg="操作失败"] - 错误消息
|
|
171
|
-
* @param {Object} [options.data={}] - 响应数据
|
|
172
|
-
* @param {number} [options.code=1008] - 业务 code(默认 1008 业务处理失败)
|
|
173
|
-
* @returns {Object} 失败响应对象
|
|
174
|
-
* @description
|
|
175
|
-
* 用于业务逻辑失败(如:栏目下存在文章、参数缺失等)
|
|
176
|
-
* 与 error() 区别:fail 用于业务可恢复错误,error 用于系统/数据库异常
|
|
177
|
-
*
|
|
178
|
-
* @example
|
|
179
|
-
* fail({ msg: '栏目下存在文章' }) // → code: 1008
|
|
180
|
-
* fail({ msg: '参数缺失', code: 1007 }) // → code: 1007
|
|
181
|
-
* fail({ msg: '权限不足', code: 1003 }) // → code: 1003
|
|
182
|
-
*/
|
|
183
|
-
export const fail = ({ msg = "操作失败", data = {}, code = CODE_BUSINESS_FAIL } = {}) => {
|
|
184
|
-
return {
|
|
185
|
-
success: false,
|
|
186
|
-
msg,
|
|
187
|
-
code,
|
|
188
|
-
data,
|
|
189
|
-
};
|
|
190
|
-
};
|
|
52
|
+
}
|
|
191
53
|
|
|
192
|
-
|
|
193
|
-
* 生成成功响应
|
|
194
|
-
* @param {Object} options - 响应选项
|
|
195
|
-
* @param {Object} [options.data={}] - 响应数据
|
|
196
|
-
* @param {string} [options.msg="操作成功"] - 成功消息
|
|
197
|
-
* @returns {Object} 成功响应对象
|
|
198
|
-
* @description
|
|
199
|
-
* code 固定为 0(不再用 200,避免与 HTTP 200 混淆)
|
|
200
|
-
*/
|
|
201
|
-
export const success = ({ data = {}, msg = "操作成功" } = {}) => ({
|
|
202
|
-
success: true,
|
|
203
|
-
msg,
|
|
204
|
-
code: CODE_OK,
|
|
205
|
-
data,
|
|
206
|
-
});
|
|
54
|
+
// =================== HTML 错误页(纯函数,无模板引擎依赖) ===================
|
|
207
55
|
|
|
208
56
|
/**
|
|
209
|
-
*
|
|
210
|
-
* @param {Object} req - Express 请求对象
|
|
211
|
-
* @returns {Object} 404 响应对象
|
|
212
|
-
* @description
|
|
213
|
-
* HTTP Status 404,业务 code 1004
|
|
57
|
+
* 框架内置兜底错误HTML页面
|
|
214
58
|
*/
|
|
215
|
-
export function
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
59
|
+
export function buildErrorHtml(status, errorMsg, code, req) {
|
|
60
|
+
const url = htmlEncode(req.originalUrl || req.url);
|
|
61
|
+
const method = htmlEncode(req.method);
|
|
62
|
+
const msg = htmlEncode(errorMsg || "服务器内部错误");
|
|
63
|
+
const codeText = htmlEncode(code ? String(code) : `HTTP_${status}`);
|
|
64
|
+
|
|
65
|
+
return `<!DOCTYPE html>
|
|
66
|
+
<html lang="zh-CN">
|
|
67
|
+
<head>
|
|
68
|
+
<meta charset="utf-8">
|
|
69
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
70
|
+
<title>${status} 服务异常</title>
|
|
71
|
+
<style>
|
|
72
|
+
*{margin:0;padding:0;box-sizing:border-box}
|
|
73
|
+
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Hiragino Sans GB","Microsoft YaHei",sans-serif;background:#f5f7fa;color:#2c3e50;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px}
|
|
74
|
+
.box{max-width:620px;width:100%;background:#fff;border-radius:12px;box-shadow:0 2px 16px rgba(0,0,0,.06);padding:40px 32px;}
|
|
75
|
+
.code{font-size:64px;font-weight:700;line-height:1;color:#1677ff;margin-bottom:8px;text-align:center}
|
|
76
|
+
.title{font-size:18px;font-weight:600;margin-bottom:12px;color:rgba(0,0,0,0.88);text-align:center}
|
|
77
|
+
.msg{font-size:14px;color:#262626;line-height:1.6;margin-bottom:20px;word-break:break-all;background: #fafafa;padding: 20px;border-radius: 8px;}
|
|
78
|
+
.url{font-size:14px;color:#434343;margin-bottom:24px;word-break:break-all}
|
|
79
|
+
</style>
|
|
80
|
+
</head>
|
|
81
|
+
<body>
|
|
82
|
+
<div class="box">
|
|
83
|
+
<div class="code">${status}</div>
|
|
84
|
+
<div class="title">服务异常</div>
|
|
85
|
+
<div class="msg">${msg}</div>
|
|
86
|
+
<div class="url">请求地址:${method} ${url}</div>
|
|
87
|
+
<div class="url">错误码:${codeText}</div>
|
|
88
|
+
</div>
|
|
89
|
+
</body>
|
|
90
|
+
</html>`;
|
|
222
91
|
}
|
|
223
92
|
|
|
224
93
|
/**
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
* @returns {Object} 错误响应对象
|
|
229
|
-
* @description
|
|
230
|
-
* 用于 Express 全局错误处理中间件
|
|
231
|
-
* 自动解析数据库错误并映射为业务 code
|
|
94
|
+
* 统一错误响应出口(HTML / JSON 自动分流)
|
|
95
|
+
* - API 请求(XHR、Accept 优先 json)→ 一律返回 JSON
|
|
96
|
+
* - 浏览器页面请求(Accept 优先 html)→ 渲染内置 HTML 错误页
|
|
232
97
|
*/
|
|
233
|
-
export function
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
method: req?.method,
|
|
240
|
-
sql: err?.sql,
|
|
241
|
-
sqlMessage: err?.sqlMessage,
|
|
242
|
-
stack: err?.stack,
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
return {
|
|
246
|
-
success: false,
|
|
247
|
-
msg: errorInfo.msg,
|
|
248
|
-
code: errorInfo.code,
|
|
249
|
-
data: EXPOSE_ERR_DETAIL
|
|
250
|
-
? {
|
|
251
|
-
message: err?.message,
|
|
252
|
-
sql: err?.sql,
|
|
253
|
-
sqlMessage: err?.sqlMessage,
|
|
254
|
-
stack: err?.stack,
|
|
255
|
-
}
|
|
256
|
-
: {},
|
|
257
|
-
};
|
|
98
|
+
export function respondError(res, req, { httpStatus = 500, code = 500, msg = "服务器内部错误", data = null } = {}) {
|
|
99
|
+
const wantsJson = req && (req.xhr || req.accepts?.(["json", "html"]) === "json");
|
|
100
|
+
if (!wantsJson && req?.accepts?.("html")) {
|
|
101
|
+
return res.status(httpStatus).type("html").send(buildErrorHtml(httpStatus, msg, code, req));
|
|
102
|
+
}
|
|
103
|
+
return res.status(httpStatus).json({ success: false, code, msg, data });
|
|
258
104
|
}
|
package/security/checker.js
CHANGED
|
@@ -1,83 +1,35 @@
|
|
|
1
|
-
import { keywordRegexCache } from "./keywords.js";
|
|
1
|
+
import { KEYWORD_RULES, keywordRegexCache, SORTED_CATEGORIES } from "./keywords.js";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* 恶意关键词检测:单次遍历预排序的分类列表,利用捕获组直接定位命中关键词
|
|
5
|
+
* @param {string} fullText 待检测文本
|
|
6
|
+
* @returns {{category: string; keyword: string}|null}
|
|
6
7
|
*/
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
* 4. 恶意请求:大正则预检 + 该类关键词遍历,开销略增但可接受
|
|
18
|
-
*
|
|
19
|
-
* 按优先级检查:sqlInjection / xss / commandInjection 优先
|
|
20
|
-
* @example
|
|
21
|
-
* const result = checkKeywords('SELECT * FROM users WHERE 1=1');
|
|
22
|
-
* console.log(result); // { category: 'sqlInjection', keyword: 'union select' }
|
|
23
|
-
*/
|
|
24
|
-
export function checkKeywords(fullText) {
|
|
25
|
-
if (!fullText?.trim()) return null;
|
|
26
|
-
|
|
27
|
-
const priorityCategories = ['sqlInjection', 'xss', 'commandInjection'];
|
|
28
|
-
const otherCategories = Object.keys(keywordRegexCache).filter(c => !priorityCategories.includes(c));
|
|
29
|
-
|
|
30
|
-
// 优先类别
|
|
31
|
-
for (const category of priorityCategories) {
|
|
32
|
-
const cache = keywordRegexCache[category];
|
|
33
|
-
if (!cache?.mergedRegex) continue;
|
|
34
|
-
|
|
35
|
-
// 预检:大正则一次 test
|
|
36
|
-
if (!cache.mergedRegex.test(fullText)) continue;
|
|
37
|
-
|
|
38
|
-
// 命中:遍历定位具体关键词
|
|
39
|
-
for (const { keyword, regex } of cache.patterns) {
|
|
40
|
-
if (regex.test(fullText)) {
|
|
41
|
-
return { category, keyword };
|
|
42
|
-
}
|
|
43
|
-
}
|
|
8
|
+
export const checkKeywords = fullText => {
|
|
9
|
+
const text = fullText?.trim();
|
|
10
|
+
if (!text) return null;
|
|
11
|
+
|
|
12
|
+
for (const cat of SORTED_CATEGORIES) {
|
|
13
|
+
const match = keywordRegexCache[cat].merged.exec(text);
|
|
14
|
+
if (!match) continue;
|
|
15
|
+
// capture[0]是整体匹配,capture[1..]是每个关键词的捕获组
|
|
16
|
+
const idx = match.findIndex((g, i) => i > 0 && g);
|
|
17
|
+
return { category: cat, keyword: KEYWORD_RULES[cat][idx - 1] };
|
|
44
18
|
}
|
|
45
|
-
|
|
46
|
-
// 其他类别
|
|
47
|
-
for (const category of otherCategories) {
|
|
48
|
-
const cache = keywordRegexCache[category];
|
|
49
|
-
if (!cache?.mergedRegex) continue;
|
|
50
|
-
|
|
51
|
-
if (!cache.mergedRegex.test(fullText)) continue;
|
|
52
|
-
|
|
53
|
-
for (const { keyword, regex } of cache.patterns) {
|
|
54
|
-
if (regex.test(fullText)) {
|
|
55
|
-
return { category, keyword };
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
19
|
return null;
|
|
61
|
-
}
|
|
20
|
+
};
|
|
62
21
|
|
|
63
22
|
/**
|
|
64
|
-
*
|
|
65
|
-
* @param {string} path
|
|
66
|
-
* @param {
|
|
67
|
-
* @returns {boolean}
|
|
68
|
-
* @description
|
|
69
|
-
* 检查给定路径是否匹配忽略列表中的任意路径
|
|
70
|
-
* 支持精确匹配和前缀匹配
|
|
71
|
-
* @example
|
|
72
|
-
* isIgnored('/api/health', ['/api/health', '/metrics']); // true
|
|
73
|
-
* isIgnored('/api/users', ['/api/health']); // false
|
|
23
|
+
* 路径忽略校验:大小写不敏感、自动去除末尾斜杠,支持精确/前缀匹配
|
|
24
|
+
* @param {string} path 当前请求路径
|
|
25
|
+
* @param {string[]} ignorePaths 忽略路径列表
|
|
26
|
+
* @returns {boolean}
|
|
74
27
|
*/
|
|
75
|
-
export
|
|
76
|
-
if (!path || !ignorePaths
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
return normalizedPath === normalizedP || normalizedPath.startsWith(`${normalizedP}/`);
|
|
28
|
+
export const isIgnored = (path, ignorePaths) => {
|
|
29
|
+
if (!path || !Array.isArray(ignorePaths) || !ignorePaths.length) return false;
|
|
30
|
+
const target = path.trim().toLowerCase().replace(/\/$/, "");
|
|
31
|
+
return ignorePaths.some(raw => {
|
|
32
|
+
const rule = raw.trim().toLowerCase().replace(/\/$/, "");
|
|
33
|
+
return target === rule || target.startsWith(`${rule}/`);
|
|
82
34
|
});
|
|
83
|
-
}
|
|
35
|
+
};
|
package/security/index.js
CHANGED
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* - checker + keywords: 敏感词检测(大正则预检优化)
|
|
4
|
-
* - xss-filter: XSS 过滤(WeakSet 防循环引用)
|
|
5
|
-
* - sign: AES-256-GCM 签名加密(timingSafeEqual 防时序攻击)
|
|
6
|
-
* - jwt: JWT 令牌生成/验证
|
|
7
|
-
* - rate-limit: IP 维度限流(服务端存储)
|
|
2
|
+
* security 模块聚合导出
|
|
8
3
|
*/
|
|
9
4
|
export { checkKeywords, isIgnored } from "./checker.js";
|
|
10
|
-
export { keywordRegexCache
|
|
5
|
+
export { keywordRegexCache } from "./keywords.js";
|
|
11
6
|
export { filterXSS } from "./xss-filter.js";
|
|
12
|
-
export {
|
|
13
|
-
export {
|
|
7
|
+
export { setToken, getToken } from "./jwt.js";
|
|
8
|
+
export { aesEncrypt, aesDecrypt } from "./sign.js";
|
|
14
9
|
export { createRateLimitMiddleware } from "./rate-limit.js";
|