chanjs 2.0.7 → 2.0.9

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/config/code.js ADDED
@@ -0,0 +1,48 @@
1
+ // 简化的状态码定义(数字作为键)
2
+ export const CODE = {
3
+ 200: "操作成功", // 成功
4
+ 201: "操作失败", // 通用失败
5
+
6
+ // 业务逻辑错误
7
+ 1001: "业务处理失败", // 业务处理失败
8
+
9
+ // 参数错误
10
+ 2001: "参数无效", // 参数无效
11
+ 2002: "参数缺失", // 参数缺失
12
+
13
+ // 认证授权错误
14
+ 3001: "认证失败", // 认证失败
15
+ 3002: "令牌已过期", // 令牌过期
16
+ 3003: "权限不足", // 权限不足
17
+
18
+ // 资源相关错误
19
+ 4001: "资源不存在", // 资源不存在
20
+ 4002: "资源已锁定", // 资源锁定
21
+ 4003: "资源已存在", // 资源重复
22
+
23
+ // 系统错误
24
+ 5001: "系统内部错误", // 系统错误
25
+ 5002: "服务繁忙,请稍后再试", // 服务繁忙
26
+
27
+ // 数据库错误
28
+ 6001: "数据库连接失败", // 连接错误
29
+ 6002: "数据库访问被拒绝", // 访问被拒
30
+ 6003: "存在关联数据,操作失败", // 引用错误
31
+ 6004: "数据库字段错误", // 字段错误
32
+ 6005: "数据重复,违反唯一性约束", // 数据重复
33
+ 6006: "目标表不存在", // 表不存在
34
+ 6007: "数据库操作超时", // 操作超时
35
+ 6008: "数据库语法错误,请检查查询语句", // 语法错误
36
+ 6009: "数据库连接已关闭,请重试", // 连接已关闭
37
+ };
38
+
39
+ // 数据库错误码映射(仅映射状态码)
40
+ export const DB_ERROR = {
41
+ ECONNREFUSED: 6001,
42
+ ER_ACCESS_DENIED_ERROR: 6002,
43
+ ER_ROW_IS_REFERENCED_2: 6003,
44
+ ER_BAD_FIELD_ERROR: 6004,
45
+ ER_DUP_ENTRY: 6005,
46
+ ER_NO_SUCH_TABLE: 6006,
47
+ ETIMEOUT: 6007,
48
+ };
package/core/service.js CHANGED
@@ -1,57 +1,6 @@
1
- // ====================== 模块级别工具函数 (只初始化一次) ======================
2
- const errCode = {
3
- "ECONNREFUSED": "数据库连接被拒绝,请检查数据库服务是否正常运行。",
4
- "ER_ACCESS_DENIED_ERROR": "无权限访问,账号或密码错误。",
5
- "ER_ROW_IS_REFERENCED_2": "无法删除或更新记录,存在关联数据。",
6
- "ER_BAD_FIELD_ERROR": "SQL语句中包含无效字段,请检查查询条件或列名。",
7
- "ER_DUP_ENTRY": "插入失败:数据重复,违反唯一性约束。",
8
- "ER_NO_SUCH_TABLE": "操作失败:目标表不存在。",
9
- "ETIMEOUT": "数据库操作超时,请稍后再试。"
10
- };
11
-
12
- const getDefaultErrorMessage = (error) => {
13
- if (error.message.includes('syntax') || error.message.includes('SQL')) {
14
- return '数据库语法错误,请检查您的查询语句。';
15
- } else if (error.message.includes('Connection closed')) {
16
- return '数据库连接已关闭,请重试。';
17
- } else if (error.message.includes('permission')) {
18
- return '数据库权限不足,请检查配置。';
19
- }
20
- return '数据库发生未知错误,请稍后重试。';
21
- };
22
-
23
- const errorResponse = (err) => {
24
- console.error('DB Error:', err);
25
- const message = errCode[err.code] || getDefaultErrorMessage(err);
26
- return {
27
- success: false,
28
- msg: message,
29
- code: 500,
30
- data: {
31
- sql: err.sql,
32
- sqlMessage: err.sqlMessage
33
- }
34
- };
35
- };
36
-
37
- const failResponse = (msg = "操作失败", data = {}) => {
38
- console.warn('Operation failed:', msg);
39
- return {
40
- success: false,
41
- msg,
42
- code: 201,
43
- data
44
- };
45
- };
46
-
47
- const successResponse = (data = {}, msg = "操作成功") => ({
48
- success: true,
49
- msg,
50
- code: 200,
51
- data
52
- });
53
-
54
- // ====================== 共享数据库方法 (只创建一次) ======================
1
+ import { success, fail, error } from "../utils/response.js";
2
+ import { CODE } from "../config/code.js";
3
+ // ====================== 共享数据库方法 ======================
55
4
  const databaseMethods = {
56
5
  /**
57
6
  * 查询表所有记录(慎用)
@@ -65,9 +14,9 @@ const databaseMethods = {
65
14
  dbQuery = dbQuery.where(query);
66
15
  }
67
16
  const res = await dbQuery.select();
68
- return successResponse(res);
17
+ return success(res);
69
18
  } catch (err) {
70
- return errorResponse(err);
19
+ return error(err);
71
20
  }
72
21
  },
73
22
 
@@ -75,9 +24,7 @@ const databaseMethods = {
75
24
  * 获取单个记录
76
25
  * @param {Object} query - 查询条件
77
26
  * @returns {Promise} 查询结果
78
- *
79
- * */
80
-
27
+ */
81
28
  async one(query = {}) {
82
29
  try {
83
30
  let dbQuery = this.knex(this.model);
@@ -85,9 +32,9 @@ const databaseMethods = {
85
32
  dbQuery = dbQuery.where(query);
86
33
  }
87
34
  const res = await dbQuery.first();
88
- return successResponse(res);
35
+ return success(res);
89
36
  } catch (err) {
90
- return errorResponse(err);
37
+ return error(err);
91
38
  }
92
39
  },
93
40
 
@@ -106,9 +53,9 @@ const databaseMethods = {
106
53
  else if (len > 1) dataQuery = dataQuery.limit(len);
107
54
 
108
55
  const res = await dataQuery;
109
- return successResponse(res || (len === 1 ? {} : []));
56
+ return success(res || (len === 1 ? {} : []));
110
57
  } catch (err) {
111
- return errorResponse(err);
58
+ return error(err);
112
59
  }
113
60
  },
114
61
 
@@ -119,13 +66,16 @@ const databaseMethods = {
119
66
  */
120
67
  async insert(data = {}) {
121
68
  try {
69
+ console.log('data--->',data)
122
70
  if (Object.keys(data).length === 0) {
123
- return failResponse('插入数据不能为空');
71
+ return fail(CODE[2002], { code: 2002 });
124
72
  }
73
+
74
+ console.log('this.model--->',this.model)
125
75
  const result = await this.knex(this.model).insert(data);
126
- return successResponse(result?.length > 0 || !!result);
76
+ return success(result?.length > 0 || !!result);
127
77
  } catch (err) {
128
- return errorResponse(err);
78
+ return error(err);
129
79
  }
130
80
  },
131
81
 
@@ -137,12 +87,12 @@ const databaseMethods = {
137
87
  async insertMany(records = []) {
138
88
  try {
139
89
  if (records.length === 0) {
140
- return failResponse('插入数据不能为空');
90
+ return fail(CODE[2002], { code: 2002 });
141
91
  }
142
92
  const result = await this.knex(this.model).insert(records);
143
- return successResponse(result);
93
+ return success(result);
144
94
  } catch (err) {
145
- return errorResponse(err);
95
+ return error(err);
146
96
  }
147
97
  },
148
98
 
@@ -154,12 +104,12 @@ const databaseMethods = {
154
104
  async delete(query = {}) {
155
105
  try {
156
106
  if (Object.keys(query).length === 0) {
157
- return failResponse("请指定删除条件");
107
+ return fail(CODE[2002], { code: 2002 });
158
108
  }
159
109
  const affectedRows = await this.knex(this.model).where(query).del();
160
- return successResponse(affectedRows > 0);
110
+ return success(affectedRows > 0);
161
111
  } catch (err) {
162
- return errorResponse(err);
112
+ return error(err);
163
113
  }
164
114
  },
165
115
 
@@ -173,12 +123,17 @@ const databaseMethods = {
173
123
  async update({ query, params } = {}) {
174
124
  try {
175
125
  if (!query || !params || Object.keys(query).length === 0) {
176
- return failResponse("参数错误");
126
+ return fail(CODE[2001], { code: 2001 });
177
127
  }
128
+ console.log('query--->',query)
129
+ console.log('this.model--->',this.model)
130
+
131
+ console.log('params---->',params)
178
132
  const result = await this.knex(this.model).where(query).update(params);
179
- return successResponse(!!result);
133
+ console.log('result--->',result)
134
+ return success(!!result);
180
135
  } catch (err) {
181
- return errorResponse(err);
136
+ return error(err);
182
137
  }
183
138
  },
184
139
 
@@ -189,7 +144,7 @@ const databaseMethods = {
189
144
  */
190
145
  async updateMany(updates = []) {
191
146
  if (!Array.isArray(updates) || updates.length === 0) {
192
- return failResponse('更新数据不能为空');
147
+ return fail(CODE[2002], { code: 2002 });
193
148
  }
194
149
 
195
150
  const trx = await this.knex.transaction();
@@ -198,14 +153,14 @@ const databaseMethods = {
198
153
  const result = await trx(this.model).where(query).update(params);
199
154
  if (result === 0) {
200
155
  await trx.rollback();
201
- return failResponse(`更新失败: ${JSON.stringify(query)}`);
156
+ return fail(`更新失败: ${JSON.stringify(query)}`);
202
157
  }
203
158
  }
204
159
  await trx.commit();
205
- return successResponse(true);
160
+ return success(true);
206
161
  } catch (err) {
207
162
  await trx.rollback();
208
- return errorResponse(err);
163
+ return error(err);
209
164
  }
210
165
  },
211
166
 
@@ -239,9 +194,9 @@ const databaseMethods = {
239
194
  ]);
240
195
 
241
196
  const total = totalResult?.total || 0;
242
- return successResponse({ list, total, current, pageSize });
197
+ return success({ list, total, current, pageSize });
243
198
  } catch (err) {
244
- return errorResponse(err);
199
+ return error(err);
245
200
  }
246
201
  },
247
202
 
@@ -257,14 +212,14 @@ const databaseMethods = {
257
212
  query.forEach(condition => dataQuery = dataQuery.where(condition));
258
213
  }
259
214
  const result = await dataQuery.count("* as total").first();
260
- return successResponse(Number(result?.total) || 0);
215
+ return success(Number(result?.total) || 0);
261
216
  } catch (err) {
262
- return errorResponse(err);
217
+ return error(err);
263
218
  }
264
219
  }
265
220
  };
266
221
 
267
- // ====================== 工厂函数 (轻量创建实例) ======================
222
+ // ====================== 工厂函数 ======================
268
223
  /**
269
224
  * 创建数据库服务实例
270
225
  * @param {Object} knex - Knex实例
@@ -273,7 +228,7 @@ const databaseMethods = {
273
228
  */
274
229
  export default function Service(knex, model) {
275
230
  if (!knex || !model) {
276
- throw new Error('createDBService: knex instance and model name are required');
231
+ throw new Error('Service: knex instance and model name are required');
277
232
  }
278
233
 
279
234
  // 创建继承数据库方法的轻量对象
@@ -294,4 +249,4 @@ export default function Service(knex, model) {
294
249
  });
295
250
 
296
251
  return service;
297
- }
252
+ }
package/helper/db.js CHANGED
@@ -46,6 +46,13 @@ export const db =({
46
46
  //默认为{min: 2, max: 10},连接池配置
47
47
  min,
48
48
  max,
49
+ // 添加连接池错误处理
50
+ afterCreate: (conn, done) => {
51
+ conn.on('error', (error) => {
52
+ console.error(`[连接池错误] 连接出错: ${getDefaultErrorMessage(error)}`, error);
53
+ });
54
+ done(null, conn);
55
+ }
49
56
  },
50
57
  log: {
51
58
  warn(message) {
@@ -71,5 +78,6 @@ export const db =({
71
78
  },
72
79
  },
73
80
  };
81
+
74
82
  return knex(config);
75
83
  };
package/helper/sign.js ADDED
@@ -0,0 +1,33 @@
1
+ import CryptoJS from "crypto-js";
2
+
3
+ /**
4
+ * AES加密
5
+ * @param message 要加密的字符串
6
+ * @param key 加密密钥
7
+ * @returns 加密后的字符串,加密失败返回空字符串
8
+ */
9
+ export const aesEncrypt = (obj, key = Chan.config.AES_SALT) => {
10
+ try {
11
+ const encrypted = CryptoJS.AES.encrypt(JSON.stringify(obj), key);
12
+ return encrypted.toString();
13
+ } catch (error) {
14
+ console.error("AES加密失败:", error);
15
+ return "";
16
+ }
17
+ };
18
+
19
+ /**
20
+ * AES解密
21
+ * @param encryptedMessage 要解密的字符串
22
+ * @param key 解密密钥(必须与加密时使用的密钥相同)
23
+ * @returns 解密后的原始字符串,解密失败返回空字符串
24
+ */
25
+ export const aesDecrypt = (str, key = Chan.config.AES_SALT) => {
26
+ try {
27
+ const decrypted = CryptoJS.AES.decrypt(str, key);
28
+ return decrypted.toString(CryptoJS.enc.Utf8);
29
+ } catch (error) {
30
+ console.error("AES解密失败:", error);
31
+ return "";
32
+ }
33
+ };
package/index.js CHANGED
@@ -2,6 +2,7 @@ import "./global/index.js";
2
2
  import path from "path";
3
3
  import fs from "fs";
4
4
  import express from "express";
5
+
5
6
  import { Controller, Service } from "./core/index.js";
6
7
  import {
7
8
  log,
@@ -64,7 +65,8 @@ class Chan {
64
65
  logger,
65
66
  cors,
66
67
  } = Chan.config;
67
-
68
+
69
+ this.app.set('trust proxy', true);
68
70
  log(this.app, logger);
69
71
  setFavicon(this.app);
70
72
  setCookie(this.app, cookieKey);
@@ -74,7 +76,6 @@ class Chan {
74
76
  setStatic(this.app, statics);
75
77
  Cors(this.app, cors);
76
78
  setHeader(this.app, { APP_NAME, APP_VERSION });
77
-
78
79
  }
79
80
 
80
81
  //数据库操作
@@ -165,7 +166,7 @@ class Chan {
165
166
  }
166
167
 
167
168
  run(cb) {
168
- const port = Chan.config.PORT || "81";
169
+ const port = parseInt(process.env.PORT) || 3000;
169
170
  this.app.listen(port, () => {
170
171
  cb ? cb(port) : console.log(`Server is running on port ${port}`);
171
172
  });
package/middleware/log.js CHANGED
@@ -1,4 +1,21 @@
1
1
  import morgan from "morgan";
2
- export const log = (app,logger) => {
3
- app.use(morgan(logger.level));
2
+ import { getIp } from "../helper/ip.js";
3
+ morgan.token("ip", (req, res) => {
4
+ return getIp(req);
5
+ });
6
+ // 自定义 morgan 格式:IP Method URL Status Length - Response-Time ms
7
+ morgan.format("chancms", (tokens, req, res) => {
8
+ return [
9
+ tokens.ip(req, res), // 客户端 IP
10
+ tokens.method(req, res), // GET/POST
11
+ tokens.url(req, res), // 完整 URL(含 query)
12
+ tokens.status(req, res), // 状态码(如 403)
13
+ tokens.res(req, res, "content-length") || "-", // 响应体大小
14
+ "-", // 占位符(原日志中的 '-')
15
+ tokens["response-time"](req, res),
16
+ "ms", // 响应时间
17
+ ].join(" ");
18
+ });
19
+ export const log = (app, logger) => {
20
+ app.use(morgan(logger.level));
4
21
  };
package/middleware/waf.js CHANGED
@@ -1,195 +1,179 @@
1
1
  import url from "url";
2
- import {getIp} from "../helper/ip.js";
3
-
4
- // 原始关键词列表(保持不变)
5
- const keywords = [
6
- ".aspx",
7
- ".php",
8
- ".pl",
9
- ".jsa",
10
- ".jsp",
11
- ".asp",
12
- ".go",
13
- ".jhtml",
14
- ".shtml",
15
- ".cfm",
16
- ".cgi",
17
- ".svn",
18
- ".env",
19
- ".keys",
20
- ".cache",
21
- ".hidden",
22
- ".bod",
23
- ".ll",
24
- ".backup",
25
- ".json",
26
- ".xml",
27
- ".bak",
28
- ".aws",
29
- ".database",
30
- // ".cookie",
31
- ".location",
32
- ".dump",
33
- ".ftp",
34
- ".idea",
35
- ".s3",
36
- ".sh",
37
- ".old",
38
- ".tf",
39
- ".sql",
40
- ".vscode",
41
- ".docker",
42
- ".map",
43
- "1+1",
44
- ".save",
45
- ".gz",
46
- ".yml",
47
- ".tar",
48
- ".rar",
49
- ".7z",
50
- ".zip",
51
- ".git",
52
- ".log",
53
- ".local",
54
- "../",
55
- "db_",
56
- "smtp",
57
- "meta",
58
- "debug",
59
- "secret",
60
- "/xampp/",
61
- "/metadata/",
62
- "/internal/",
63
- "/aws/",
64
- "/debug/",
65
- "/configs/",
66
- "/cgi-bin/",
67
- "/tmp/",
68
- "/staging/",
69
- "/mail/",
70
- "/docker/",
71
- "/.secure/",
72
- "/php-cgi/",
73
- "/wp-",
74
- "/backup/",
75
- "redirect",
76
- "/phpMyAdmin/",
77
- "/setup/",
78
- "concat(",
79
- "version(",
80
- "sleep(",
81
- "benchmark(",
82
- "0x7e",
83
- "extractvalue(",
84
- "(select",
85
- "a%",
86
- "union",
87
- "drop",
88
- "alter",
89
- "truncate",
90
- "exec",
91
- ];
92
-
93
- // 预处理:合并字符串关键词和正则关键词为两个单一正则(核心优化)
94
- const { combinedStrRegex, combinedRegRegex } = (() => {
2
+ import { getIp } from "../helper/ip.js";
3
+
4
+ // 改进的关键词列表,区分需要全词匹配的关键词
5
+ const keywords = {
6
+ // 需要全词匹配的关键词(避免短词误报)
7
+ wholeWord: [
8
+ // 系统命令/工具(容易产生短词误报)
9
+ "opt", "cmd", "rm", "mdc", "netcat", "nc", "mdb", "bin", "mk", "sys","sh","chomd",
10
+ "php-cgi"
11
+ ],
12
+
13
+ // 普通关键词(包含特殊字符或较长关键词)
14
+ normal: [
15
+ // 文件扩展名 & 敏感文件
16
+ ".php", ".asp", ".aspx", ".jsp", ".jspx", ".do", ".action", ".cgi",
17
+ ".py", ".pl", ".md", ".log", ".conf", ".config", ".env", ".jsa",
18
+ ".go", ".jhtml", ".shtml", ".cfm", ".svn", ".keys", ".hidden",
19
+ ".bod", ".ll", ".backup", ".json", ".xml", ".bak", ".aws",
20
+ ".database", ".cookie", ".rsp", ".old", ".tf", ".sql", ".vscode",
21
+ ".docker", ".map", ".save", ".gz", ".yml", ".tar", ".sh", ".idea", ".s3",
22
+
23
+ // 敏感目录
24
+ "/administrator", "/wp-admin",
25
+
26
+ // 高危路径/应用
27
+ "phpMyAdmin", "setup", "wp-", "cgi-bin", "xampp", "staging", "internal",
28
+ "debug", "metadata", "secret", "smtp", "redirect", "configs",
29
+
30
+ // SQL 注入
31
+ "sleep(", "benchmark(", "concat(", "extractvalue(", "updatexml(",
32
+ "version(", "union select", "union all", "select @@", "drop",
33
+ "alter", "truncate", "exec", "(select", "information_schema",
34
+ "load_file(", "into outfile", "into dumpfile",
35
+
36
+ // 命令注入
37
+ "cmd=", "system(", "exec(", "shell_exec(", "passthru(", "base64_decode",
38
+ "eval(", "assert(", "preg_replace", "bash -i", "rm -rf", "wget ", "curl ",
39
+ "chmod ", "phpinfo()",
40
+
41
+ // 路径遍历
42
+ "../", "..\\", "/etc/passwd", "/etc/shadow",
43
+
44
+ // XSS
45
+ "<script", "javascript:", "onerror=", "onload=", "alert(", "document.cookie",
46
+
47
+ // 特殊编码
48
+ "0x7e", "UNION%20SELECT", "%27OR%27", "{{", "}}", "1+1"
49
+ ]
50
+ };
51
+
52
+ // === 预处理:构建正则缓存 ===
53
+ const { wholeWordRegexCache, normalRegexCache } = (() => {
95
54
  const regexSpecialChars = /[.*+?^${}()|[\]\\]/g;
96
- const strKwParts = []; // 存储无特殊字符的关键词(用于合并正则)
97
- const regKwParts = []; // 存储转义后的正则关键词(用于合并正则)
98
-
99
- keywords.forEach((keyword) => {
100
- if (regexSpecialChars.test(keyword)) {
101
- // 含正则特殊字符:转义后加入正则关键词部分
102
- const escaped = keyword.replace(regexSpecialChars, "\\$&");
103
- regKwParts.push(escaped);
104
- } else {
105
- // 无特殊字符:直接加入字符串关键词部分
106
- strKwParts.push(keyword);
107
- }
55
+ const wholeWordRegexCache = {};
56
+ const normalRegexCache = {};
57
+
58
+ // 处理需要全词匹配的关键词
59
+ keywords.wholeWord.forEach((keyword) => {
60
+ // 转义特殊字符
61
+ const escaped = keyword.replace(regexSpecialChars, "\\$&");
62
+ // 使用单词边界确保全词匹配,忽略大小写
63
+ wholeWordRegexCache[keyword] = new RegExp(`\\b${escaped}\\b`, "i");
64
+ });
65
+
66
+ // 处理普通关键词
67
+ keywords.normal.forEach((keyword) => {
68
+ // 转义特殊字符
69
+ const escaped = keyword.replace(regexSpecialChars, "\\$&");
70
+ // 普通匹配,忽略大小写
71
+ normalRegexCache[keyword] = new RegExp(escaped, "i");
108
72
  });
109
73
 
110
- // 构建合并后的正则(空数组时返回匹配失败的正则,避免报错)
111
- const buildCombinedRegex = (parts) => {
112
- return parts.length
113
- ? new RegExp(`(?:${parts.join("|")})`, "i") // 非捕获组+不区分大小写
114
- : new RegExp("^$"); // 匹配空字符串(永远不命中)
115
- };
116
-
117
- return {
118
- combinedStrRegex: buildCombinedRegex(strKwParts),
119
- combinedRegRegex: buildCombinedRegex(regKwParts),
120
- };
74
+ return { wholeWordRegexCache, normalRegexCache };
121
75
  })();
122
76
 
77
+ /**
78
+ * WAF 安全中间件(优化版,减少误报)
79
+ */
123
80
  const safe = (req, res, next) => {
124
- try {
125
- // 1. 设置安全头(保持不变)
126
- // res.setHeader("X-Frame-Options", "SAMEORIGIN");
127
- // res.setHeader("X-Content-Type-Options", "nosniff");
128
- // res.setHeader("Referrer-Policy", "no-referrer-when-downgrade");
129
- // res.removeHeader("Server");
130
-
131
- // 2. 构建检查文本:req.path(仅路径)+ query(非空才加)(优化冗余)
132
- let checkText = req.path || "";
133
- if (req.query && Object.keys(req.query).length > 0) {
134
- const queryStr = Object.entries(req.query)
135
- .map(([k, v]) => `${k}=${v}`)
136
- .join(' ');
137
- checkText += ` ${queryStr}`;
138
- }
81
+ try {
82
+ const { WAF_LEVEL = 1 } = Chan.config || {};
83
+
84
+ // 设置基础安全头
85
+ res.setHeader("X-Content-Type-Options", "nosniff");
86
+ res.setHeader("X-XSS-Protection", "1; mode=block");
87
+ res.setHeader("X-Frame-Options", "DENY");
88
+
89
+ // 构建检测文本:path + query + body
90
+ let checkText = req.path || "";
91
+
92
+ // 添加 query
93
+ if (req.query && Object.keys(req.query).length > 0) {
94
+ const queryStr = Object.entries(req.query)
95
+ .map(([k, v]) => `${k}=${v}`)
96
+ .join(" ");
97
+ checkText += ` ${queryStr}`;
98
+ }
139
99
 
140
- // 3. 处理请求体(优化序列化逻辑)
141
- let bodyText = "";
142
- const contentType = req.headers["content-type"] || "";
143
- const isMultipart = contentType.includes("multipart/form-data");
144
-
145
- if (!isMultipart && req.body) {
146
- try {
147
- // 若已是字符串直接用,否则序列化(避免重复序列化)
148
- const bodyStr =
149
- typeof req.body === "string" ? req.body : JSON.stringify(req.body);
150
-
151
- // 限制大小(保持原逻辑,避免大文本开销)
152
- if (bodyStr.length < 10000) {
153
- bodyText = ` ${bodyStr}`;
154
- }
155
- } catch (e) {
156
- // 忽略序列化错误
100
+ // 添加 body(非 multipart)
101
+ let bodyText = "";
102
+ const contentType = req.headers["content-type"] || "";
103
+ const isMultipart = contentType.includes("multipart/form-data");
104
+
105
+ if (!isMultipart && req.body) {
106
+ try {
107
+ const bodyStr =
108
+ typeof req.body === "string" ? req.body : JSON.stringify(req.body);
109
+ // 限制检测的body长度,避免性能问题
110
+ if (bodyStr.length < 10000) {
111
+ bodyText = ` ${bodyStr}`;
157
112
  }
113
+ } catch (e) {
114
+ // 忽略序列化错误
158
115
  }
116
+ }
159
117
 
160
- // 合并完整文本(无需 toLowerCase)
161
- const fullText = checkText + bodyText;
118
+ const fullText = checkText + bodyText;
119
+
120
+ // 空文本直接跳过检测
121
+ if (!fullText.trim()) return next();
162
122
 
163
-
123
+ let matchedKeyword = null;
164
124
 
165
- // 4. 高效匹配:两次正则 test 替代 N 次循环(核心优化)
166
- let foundMatch = false;
167
- // 先检查字符串关键词合并正则(更快,因为无复杂正则逻辑)
168
- if (combinedStrRegex.test(fullText)) {
169
- foundMatch = true;
125
+ // 1. 检测需要全词匹配的关键词
126
+ for (const [kw, regex] of Object.entries(wholeWordRegexCache)) {
127
+ if (regex.test(fullText)) {
128
+ matchedKeyword = kw;
129
+ break;
170
130
  }
171
- // 再检查正则关键词合并正则(仅当字符串匹配未命中时)
172
- else if (combinedRegRegex.test(fullText)) {
173
- foundMatch = true;
131
+ }
132
+
133
+ // 2. 检测普通关键词
134
+ if (!matchedKeyword) {
135
+ for (const [kw, regex] of Object.entries(normalRegexCache)) {
136
+ if (regex.test(fullText)) {
137
+ matchedKeyword = kw;
138
+ break;
139
+ }
174
140
  }
141
+ }
175
142
 
176
- if (foundMatch) {
177
- console.error("[安全拦截] 疑似恶意请求:", {
178
- url: req.url,
179
- ip: getIp(req),
180
- userAgent: req.get("User-Agent") || "",
181
- });
182
-
143
+ // === 拦截处理 ===
144
+ if (matchedKeyword) {
145
+ const clientIp = getIp(req);
146
+ const userAgent = req.get("User-Agent") || "";
147
+
148
+ console.error("[WAF 拦截] 疑似恶意请求:", {
149
+ url: req.url,
150
+ ip: clientIp,
151
+ userAgent: userAgent.substring(0, 100),
152
+ method: req.method,
153
+ matchedKeyword,
154
+ sample: fullText.substring(0, 200) + (fullText.length > 200 ? "..." : ""),
155
+ });
156
+
157
+ // 根据WAF级别决定拦截方式
158
+ if (WAF_LEVEL >= 2) {
183
159
  return res.status(403).send("非法风险请求,已拦截");
160
+ } else {
161
+ // 低级别仅记录不拦截,便于观察误报
162
+ console.warn("[WAF 警告] 低级别模式下未拦截请求");
163
+ return next();
184
164
  }
185
-
186
- next();
187
- } catch (error) {
188
- console.error("[安全中间件异常]", error);
189
- res.status(500).send("服务器内部错误");
190
165
  }
191
- };
192
166
 
167
+ next();
168
+ } catch (error) {
169
+ console.error("[WAF 异常]", error);
170
+ res.status(500).send("服务器内部错误");
171
+ }
172
+ };
173
+
174
+ /**
175
+ * 导出 WAF
176
+ */
193
177
  export const waf = (app) => {
194
178
  app.use(safe);
195
179
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "chanjs",
4
- "version": "2.0.7",
4
+ "version": "2.0.9",
5
5
  "description": "chanjs基于express5 纯js研发的轻量级mvc框架。",
6
6
  "main": "index.js",
7
7
  "module": "index.js",
@@ -30,7 +30,6 @@
30
30
  "knex": "^3.1.0",
31
31
  "morgan": "^1.10.0",
32
32
  "mysql2": "^3.14.1",
33
- "schedule": "^0.5.0",
34
- "zod": "^4.0.5"
33
+ "schedule": "^0.5.0"
35
34
  }
36
35
  }
@@ -0,0 +1,46 @@
1
+ import { CODE, DB_ERROR } from "../config/code.js";
2
+
3
+ // ====================== 模块级别工具函数 ======================
4
+ export const getDefaultErrorCode = (error) => {
5
+ if (error.message.includes("syntax") || error.message.includes("SQL")) {
6
+ return 6008;
7
+ } else if (error.message.includes("Connection closed")) {
8
+ return 6009;
9
+ } else if (error.message.includes("permission")) {
10
+ return 3003;
11
+ }
12
+ return 5001;
13
+ };
14
+
15
+ export const error = (err) => {
16
+ console.error("DB Error:", err);
17
+ // 从映射表获取状态码或使用默认错误码
18
+ const errorCode = DB_ERROR[err.code] || getDefaultErrorCode(err);
19
+
20
+ return {
21
+ success: false,
22
+ msg: CODE[errorCode], // 从CODE对象获取错误消息
23
+ code: errorCode,
24
+ data: {
25
+ sql: err.sql,
26
+ sqlMessage: err.sqlMessage,
27
+ },
28
+ };
29
+ };
30
+
31
+ export const fail = (msg = CODE[201], data = {}) => {
32
+ console.warn("Operation failed:", msg);
33
+ return {
34
+ success: false,
35
+ msg,
36
+ code: 201, // 使用通用失败码
37
+ data,
38
+ };
39
+ };
40
+
41
+ export const success = (data = {}, msg = CODE[200]) => ({
42
+ success: true,
43
+ msg,
44
+ code: 200, // 使用标准成功码
45
+ data,
46
+ });
@@ -1,2 +0,0 @@
1
- import { z } from "zod";
2
- export { z };