chanjs 2.3.0 → 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/base/Database.js CHANGED
@@ -2,7 +2,7 @@ import knex from "knex";
2
2
 
3
3
  /**
4
4
  * 数据库管理器类
5
- * 用于管理多个数据库连接和创建数据模型
5
+ * 用于管理多个数据库连接
6
6
  */
7
7
  class DatabaseManager {
8
8
  /**
@@ -78,237 +78,6 @@ class DatabaseManager {
78
78
  }
79
79
  }
80
80
 
81
- /**
82
- * 关闭所有数据库连接
83
- * @returns {Promise<void>}
84
- */
85
- async closeAll() {
86
- const closePromises = [];
87
- for (const [name, connection] of this._connections) {
88
- closePromises.push(
89
- connection.destroy().catch((err) => {
90
- console.error(`Error closing database "${name}":`, err);
91
- })
92
- );
93
- }
94
- await Promise.all(closePromises);
95
- this._connections.clear();
96
- }
97
-
98
- /**
99
- * 获取所有连接名称
100
- * @returns {Array<string>} 连接名称数组
101
- */
102
- getConnections() {
103
- return Array.from(this._connections.keys());
104
- }
105
-
106
- /**
107
- * 创建数据模型
108
- * @param {string} name - 模型名称
109
- * @param {string} tableName - 表名
110
- * @param {string} connectionName - 连接名称
111
- * @returns {Object} 模型对象
112
- */
113
- createModel(name, tableName, connectionName) {
114
- const db = this.get(connectionName);
115
- return {
116
- db,
117
- tableName,
118
-
119
- /**
120
- * 查询字段
121
- * @param {string} fields - 字段列表,默认"*"
122
- * @returns {Object} Knex查询构建器
123
- */
124
- select(fields = "*") {
125
- return db(this.tableName).select(fields);
126
- },
127
-
128
- /**
129
- * 条件查询
130
- * @param {Object} conditions - 查询条件
131
- * @returns {Object} Knex查询构建器
132
- */
133
- where(conditions) {
134
- return db(this.tableName).where(conditions);
135
- },
136
-
137
- /**
138
- * 查找记录
139
- * @param {Object} query - 查询参数
140
- * @param {Object} query.sort - 排序条件
141
- * @param {Object} query.where - 查询条件
142
- * @returns {Promise<Array>} 查询结果数组
143
- */
144
- find(query = {}) {
145
- let q = db(this.tableName);
146
- const { sort, ...where } = query;
147
-
148
- if (Object.keys(where).length > 0) {
149
- q = q.where(where);
150
- }
151
-
152
- if (sort && typeof sort === "object") {
153
- for (const [field, dir] of Object.entries(sort)) {
154
- q = q.orderBy(field, dir);
155
- }
156
- }
157
-
158
- return q.select();
159
- },
160
-
161
- /**
162
- * 查找单条记录
163
- * @param {Object} query - 查询条件
164
- * @returns {Promise<Object|null>} 查询结果或null
165
- */
166
- findOne(query = {}) {
167
- let q = db(this.tableName);
168
- if (Object.keys(query).length > 0) {
169
- q = q.where(query);
170
- }
171
- return q.first();
172
- },
173
-
174
- /**
175
- * 根据ID查找记录
176
- * @param {number|string} id - 记录ID
177
- * @returns {Promise<Object|null>} 查询结果或null
178
- */
179
- findById(id) {
180
- return db(this.tableName).where({ id }).first();
181
- },
182
-
183
- /**
184
- * 创建记录
185
- * @param {Object} data - 要插入的数据
186
- * @returns {Promise<Array>} 插入结果
187
- */
188
- create(data) {
189
- return db(this.tableName).insert(data);
190
- },
191
-
192
- /**
193
- * 批量创建记录
194
- * @param {Array} records - 要插入的记录数组
195
- * @returns {Promise<Array>} 插入结果
196
- */
197
- createMany(records) {
198
- return db(this.tableName).insert(records);
199
- },
200
-
201
- /**
202
- * 更新记录
203
- * @param {Object} conditions - 更新条件
204
- * @param {Object} data - 更新数据
205
- * @returns {Promise<number>} 影响的行数
206
- */
207
- update(conditions, data) {
208
- return db(this.tableName).where(conditions).update(data);
209
- },
210
-
211
- /**
212
- * 删除记录
213
- * @param {Object} conditions - 删除条件
214
- * @returns {Promise<number>} 影响的行数
215
- */
216
- delete(conditions) {
217
- return db(this.tableName).where(conditions).del();
218
- },
219
-
220
- /**
221
- * 统计记录数
222
- * @param {Object} query - 查询条件
223
- * @returns {Promise<number>} 记录总数
224
- */
225
- count(query = {}) {
226
- let q = db(this.tableName).count("* as total");
227
- if (Object.keys(query).length > 0) {
228
- q = q.where(query);
229
- }
230
- return q.first().then((result) => result?.total ?? 0);
231
- },
232
-
233
- /**
234
- * 分页查询
235
- * @param {Object} options - 查询选项
236
- * @param {number} options.current - 当前页码,默认1
237
- * @param {number} options.pageSize - 每页大小,默认10
238
- * @param {Object} options.where - 查询条件
239
- * @param {Array} options.select - 查询字段
240
- * @returns {Promise<Object>} 分页结果
241
- */
242
- query(options = {}) {
243
- const { current = 1, pageSize = 10, where = {}, select } = options;
244
- const offset = (current - 1) * pageSize;
245
-
246
- const countQuery = db(this.tableName).count("* as total");
247
- const dataQuery = db(this.tableName);
248
-
249
- if (Object.keys(where).length > 0) {
250
- countQuery.where(where);
251
- dataQuery.where(where);
252
- }
253
-
254
- if (select) {
255
- dataQuery.select(select);
256
- }
257
-
258
- return Promise.all([
259
- countQuery.first(),
260
- dataQuery.offset(offset).limit(pageSize),
261
- ]).then(([totalResult, list]) => ({
262
- list,
263
- total: totalResult?.total ?? 0,
264
- current,
265
- pageSize,
266
- }));
267
- },
268
-
269
- /**
270
- * 关联查询
271
- * @param {Object} options - 查询选项
272
- * @param {string} options.joinTable - 关联表名
273
- * @param {string} options.localField - 本地表字段
274
- * @param {string} options.foreignField - 关联表字段
275
- * @param {string} options.select - 查询字段,默认"*"
276
- * @param {Object} options.where - 查询条件
277
- * @returns {Object} Knex查询构建器
278
- */
279
- join(options) {
280
- const { joinTable, localField, foreignField, select = "*", where = {} } = options;
281
- let q = db(this.tableName)
282
- .join(joinTable, `${this.tableName}.${localField}`, "=", `${joinTable}.${foreignField}`)
283
- .select(select);
284
-
285
- if (Object.keys(where).length > 0) {
286
- q = q.where(where);
287
- }
288
-
289
- return q;
290
- },
291
-
292
- /**
293
- * 执行事务
294
- * @param {Function} callback - 事务回调函数
295
- * @returns {Promise} 事务执行结果
296
- */
297
- transaction(callback) {
298
- return db.transaction(callback);
299
- },
300
-
301
- /**
302
- * 执行原生SQL
303
- * @param {string} sql - SQL语句
304
- * @param {Array} bindings - 绑定参数
305
- * @returns {Promise} 查询结果
306
- */
307
- raw(sql, bindings) {
308
- return db.raw(sql, bindings);
309
- },
310
- };
311
- }
312
81
  }
313
82
 
314
83
  export default DatabaseManager;
package/base/Service.js CHANGED
@@ -14,11 +14,6 @@ class Service {
14
14
  if (knex && tableName) {
15
15
  this.db = knex;
16
16
  this.tableName = tableName;
17
- this._config = Chan.config || {};
18
- } else if (global.appContext) {
19
- this.context = global.appContext;
20
- } else {
21
- this.context = { get: () => null, set: () => null };
22
17
  }
23
18
 
24
19
  this._dateFields = ['created_at', 'updated_at', 'deleted_at', 'publish_time', 'start_time', 'end_time', 'login_time', 'created_date', 'updated_date', 'createdAt', 'updatedAt', 'deletedAt', 'publishTime', 'startTime', 'endTime', 'loginTime', 'createdDate', 'updatedDate'];
@@ -29,7 +24,7 @@ class Service {
29
24
  * @returns {number} 每页记录数,默认20
30
25
  */
31
26
  get pageSize() {
32
- return this._config.PAGE_SIZE || 20;
27
+ return Chan.config.PAGE_SIZE || 20;
33
28
  }
34
29
 
35
30
  /**
@@ -37,35 +32,7 @@ class Service {
37
32
  * @returns {number} 最大记录数,默认300
38
33
  */
39
34
  get limit() {
40
- return this._config.LIMIT_MAX || 300;
41
- }
42
-
43
- /**
44
- * 获取模型实例
45
- * @param {string} name - 模型名称
46
- * @returns {Object} 模型实例
47
- */
48
- getModel(name) {
49
- return this.context.getModel(name);
50
- }
51
-
52
- /**
53
- * 获取数据库连接
54
- * @param {string} name - 数据库名称
55
- * @returns {Object} 数据库实例
56
- */
57
- getDB(name) {
58
- return this.context.get(`db:${name}`);
59
- }
60
-
61
- /**
62
- * 执行事务
63
- * @param {Function} callback - 事务回调函数
64
- * @returns {Promise} 事务执行结果
65
- */
66
- async withTransaction(callback) {
67
- const db = this.getDB("default") || Chan.db;
68
- return db.transaction(callback);
35
+ return Chan.config.LIMIT_MAX || 300;
69
36
  }
70
37
 
71
38
  /**
package/global/import.js CHANGED
@@ -36,8 +36,4 @@ const importFile = async (filepath) => {
36
36
  */
37
37
  export const importjs = createRequire(import.meta.url);
38
38
 
39
- /**
40
- * 将导入函数挂载到全局
41
- */
42
- global.requirejs = importjs;
43
- global.importFile = importFile;
39
+ export { importFile };
package/helper/index.js CHANGED
@@ -1,5 +1,3 @@
1
- export { db } from "./db.js";
2
- export { prefixDbConfig } from "./db.js";
3
1
  export { loaderSort, loadConfig, clearConfigCache, bindInstance, loadController } from "./loader.js";
4
2
  export { formatTime } from "./time.js";
5
3
  export { formatDateFields } from "./time.js";
package/index.js CHANGED
@@ -1,7 +1,6 @@
1
1
  export { default as Chan } from "./App.js";
2
2
  export { default as Controller } from "./base/Controller.js";
3
3
  export { default as Service } from "./base/Service.js";
4
- export { default as AppContext } from "./base/Context.js";
5
4
  export { default as DatabaseManager } from "./base/Database.js";
6
5
 
7
6
  export * as helper from "./helper/index.js";
@@ -10,5 +10,4 @@ export { setStatic } from "./static.js";
10
10
  export { setHeader } from "./header.js";
11
11
  export { setTemplate } from "./template.js";
12
12
  export { Cors } from "./cors.js";
13
- export { validator } from "./validator.js";
14
13
  export { waf } from "./waf.js";
@@ -1,4 +1,5 @@
1
1
  import "../extend/index.js";
2
+ import { importjs } from "../global/import.js";
2
3
 
3
4
  /**
4
5
  * 模板引擎中间件配置
@@ -30,5 +31,5 @@ export let setTemplate = (app, config) => {
30
31
  });
31
32
  app.set("view engine", "html");
32
33
  app.set("views", all);
33
- app.engine(".html", requirejs("express-art-template"));
34
+ app.engine(".html", importjs("express-art-template"));
34
35
  };
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
- return res.status(403).json({
96
- code: 403,
97
- success: false,
98
- msg: '非法请求:检测到恶意内容'
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "chanjs",
4
- "version": "2.3.0",
4
+ "version": "2.5.0",
5
5
  "description": "chanjs基于express5 纯js研发的轻量级mvc框架。",
6
6
  "main": "index.js",
7
7
  "module": "index.js",
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;