chanjs 2.1.1 → 2.3.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.
Files changed (61) hide show
  1. package/App.js +387 -0
  2. package/base/Context.js +78 -0
  3. package/base/Controller.js +137 -0
  4. package/base/Database.js +314 -0
  5. package/base/Service.js +539 -0
  6. package/common/api.js +25 -0
  7. package/common/category.js +22 -0
  8. package/common/code.js +42 -0
  9. package/common/email.js +110 -0
  10. package/common/index.js +7 -0
  11. package/common/pages.js +86 -0
  12. package/common/sms.js +104 -0
  13. package/common/utils.js +73 -0
  14. package/config/code.js +110 -52
  15. package/config/index.js +10 -0
  16. package/config/paths.js +60 -0
  17. package/extend/art-template.js +46 -28
  18. package/extend/index.js +6 -0
  19. package/global/env.js +11 -5
  20. package/global/global.js +63 -39
  21. package/global/import.js +43 -39
  22. package/global/index.js +8 -3
  23. package/helper/cache.js +182 -0
  24. package/helper/data-parse.js +121 -37
  25. package/helper/db.js +71 -83
  26. package/helper/file.js +158 -208
  27. package/helper/filter.js +34 -0
  28. package/helper/html.js +30 -47
  29. package/helper/index.js +29 -5
  30. package/helper/ip.js +48 -31
  31. package/helper/jwt.js +78 -11
  32. package/helper/loader.js +93 -50
  33. package/helper/request.js +41 -144
  34. package/helper/sign.js +96 -33
  35. package/helper/time.js +89 -74
  36. package/helper/tree.js +77 -0
  37. package/index.js +15 -181
  38. package/middleware/cookie.js +20 -4
  39. package/middleware/cors.js +20 -0
  40. package/middleware/favicon.js +21 -5
  41. package/middleware/header.js +26 -9
  42. package/middleware/index.js +14 -23
  43. package/middleware/preventRetry.js +30 -0
  44. package/middleware/setBody.js +24 -10
  45. package/middleware/static.js +31 -10
  46. package/middleware/template.js +34 -14
  47. package/middleware/validator.js +43 -23
  48. package/middleware/waf.js +147 -287
  49. package/package.json +1 -1
  50. package/utils/checker.js +68 -0
  51. package/utils/error-handler.js +115 -0
  52. package/utils/error.js +81 -0
  53. package/utils/index.js +6 -0
  54. package/utils/keywords.js +126 -0
  55. package/utils/rate-limit.js +116 -0
  56. package/utils/response.js +103 -64
  57. package/utils/xss-filter.js +42 -0
  58. package/core/controller.js +0 -33
  59. package/core/index.js +0 -3
  60. package/core/service.js +0 -307
  61. package/middleware/log.js +0 -21
@@ -1,18 +1,55 @@
1
1
  /**
2
- * 将数组转换为键值对对象
3
- * @param {Array} arr - 包含键值对的数组
4
- * @param {string} keyField - 作为键的字段名
5
- * @param {string} valueField - 作为值的字段名
2
+ * 解析 JSON 字符串或返回原始值
3
+ * @param {string} str - 要解析的字符串
4
+ * @returns {Object|string} 解析后的对象或原始字符串
5
+ * @description
6
+ * 尝试将字符串解析为 JSON 对象
7
+ * 如果解析失败或结果不是对象,则返回原始字符串
8
+ * @example
9
+ * const obj = dataParse('{"name":"张三","age":25}');
10
+ * console.log(obj); // { name: '张三', age: 25 }
11
+ *
12
+ * const str = dataParse('hello');
13
+ * console.log(str); // 'hello'
14
+ */
15
+ export function dataParse(str) {
16
+ try {
17
+ const data = JSON.parse(str);
18
+ if (typeof data === 'object' && data !== null) {
19
+ return data;
20
+ }
21
+ } catch (e) {
22
+ return str;
23
+ }
24
+ return str;
25
+ }
26
+
27
+ /**
28
+ * 将数组转换为对象
29
+ * @param {Array<Object>} arr - 要转换的数组
30
+ * @param {string} [keyField="config_key"] - 作为对象键的字段名
31
+ * @param {string} [valueField="config_value"] - 作为对象值的字段名
6
32
  * @returns {Object} 转换后的对象
33
+ * @description
34
+ * 将数组中的每个元素转换为一个键值对
35
+ * 键由 keyField 指定,值由 valueField 指定
36
+ * @example
37
+ * const arr = [
38
+ * { config_key: 'app.name', config_value: 'MyApp' },
39
+ * { config_key: 'app.port', config_value: '3000' }
40
+ * ];
41
+ * const obj = arrToObj(arr);
42
+ * console.log(obj);
43
+ * // { 'app.name': 'MyApp', 'app.port': '3000' }
7
44
  */
8
45
  export const arrToObj = (
9
46
  arr,
10
47
  keyField = "config_key",
11
48
  valueField = "config_value"
12
49
  ) => {
13
- // 输入验证
14
50
  if (!Array.isArray(arr)) {
15
- throw new Error("arrToObj 期望接收数组作为第一个参数");
51
+ console.error("[arrToObj] 输入必须是数组");
52
+ return {};
16
53
  }
17
54
 
18
55
  return arr.reduce((result, item) => {
@@ -28,16 +65,27 @@ export const arrToObj = (
28
65
  };
29
66
 
30
67
  /**
31
- * @description 将字符串中的 JSON 字符串转换为对象
32
- * @param {*} obj - 包含 JSON 字符串的对象
33
- * @returns
68
+ * 解析对象中的 JSON 字符串字段
69
+ * @param {Object} obj - 要处理的对象
70
+ * @returns {Object} 处理后的对象
71
+ * @description
72
+ * 遍历对象的所有属性,将 JSON 字符串格式的值解析为对象
73
+ * 只解析以 '{' 或 '[' 开头的字符串值
74
+ * @example
75
+ * const obj = {
76
+ * name: '张三',
77
+ * settings: '{"theme":"dark","lang":"zh"}',
78
+ * tags: '["a","b","c"]'
79
+ * };
80
+ * const result = parseJsonFields(obj);
81
+ * console.log(result);
82
+ * // { name: '张三', settings: { theme: 'dark', lang: 'zh' }, tags: ['a', 'b', 'c'] }
34
83
  */
35
84
  export function parseJsonFields(obj) {
36
85
  const result = {};
37
86
  for (const key in obj) {
38
87
  if (!obj.hasOwnProperty(key)) continue;
39
88
  const value = obj[key];
40
- // 如果是字符串,并且看起来像 JSON(以 { 或 [ 开头)
41
89
  if (
42
90
  typeof value === "string" &&
43
91
  (value.startsWith("{") || value.startsWith("["))
@@ -45,8 +93,8 @@ export function parseJsonFields(obj) {
45
93
  try {
46
94
  result[key] = JSON.parse(value);
47
95
  } catch (e) {
48
- console.warn(`JSON parse failed for field: ${key}`, e);
49
- result[key] = value; // 保留原始值
96
+ console.error(`JSON parse failed for field: ${key}`, e);
97
+ result[key] = value;
50
98
  }
51
99
  } else {
52
100
  result[key] = value;
@@ -57,12 +105,35 @@ export function parseJsonFields(obj) {
57
105
  }
58
106
 
59
107
  /**
60
- * @param {Array} arr - 原始数据数组
61
- * @param {number|string} [pid=0] - 根节点父ID
62
- * @param {string} [idKey='id'] - ID字段名
63
- * @param {string} [pidKey='pid'] - ID字段名
64
- * @param {string} [childrenKey='children'] - 子节点字段名
65
- * @returns {Array} 树形结构数组
108
+ * 构建树形结构
109
+ * @param {Array<Object>} arr - 扁平化的数据数组
110
+ * @param {number|string} [pid=0] - 根节点的父 ID
111
+ * @param {string} [idKey="id"] - ID 字段名
112
+ * @param {string} [pidKey="pid"] - 父 ID 字段名
113
+ * @param {string} [childrenKey="children"] - 子节点字段名
114
+ * @returns {Array<Object>} 树形结构数组
115
+ * @description
116
+ * 将扁平化的数组转换为树形结构
117
+ * 支持自定义 ID 和父 ID 字段名
118
+ * 自动检测并跳过循环引用
119
+ * 自动清理空的 children 数组
120
+ * @example
121
+ * const arr = [
122
+ * { id: 1, pid: 0, name: '根节点' },
123
+ * { id: 2, pid: 1, name: '子节点1' },
124
+ * { id: 3, pid: 1, name: '子节点2' },
125
+ * { id: 4, pid: 2, name: '孙节点' }
126
+ * ];
127
+ * const tree = buildTree(arr);
128
+ * console.log(tree);
129
+ * // [
130
+ * // { id: 1, pid: 0, name: '根节点', children: [
131
+ * // { id: 2, pid: 1, name: '子节点1', children: [
132
+ * // { id: 4, pid: 2, name: '孙节点' }
133
+ * // ]},
134
+ * // { id: 3, pid: 1, name: '子节点2' }
135
+ * // ]}
136
+ * // ]
66
137
  */
67
138
  export function buildTree(
68
139
  arr,
@@ -71,32 +142,45 @@ export function buildTree(
71
142
  pidKey = "pid",
72
143
  childrenKey = "children"
73
144
  ) {
74
- // 基础参数校验
75
- if (!Array.isArray(arr)) return [];
145
+ if (!Array.isArray(arr) || arr.length === 0) return [];
76
146
 
147
+ const nodeMap = new Map();
77
148
  const tree = [];
149
+ const visited = new Set();
78
150
 
79
- for (let i = 0; i < arr.length; i++) {
80
- const item = arr[i];
81
- // 找到当前层级的节点
82
- if (item[pidKey] === pid) {
83
- // 递归查找子节点(通过slice创建子数组,避免重复遍历已处理项)
84
- const children = buildTree(
85
- arr.slice(i + 1),
86
- item[idKey],
87
- idKey,
88
- pidKey,
89
- childrenKey
90
- );
151
+ for (const item of arr) {
152
+ if (item && typeof item === 'object') {
153
+ nodeMap.set(item[idKey], { ...item, [childrenKey]: [] });
154
+ }
155
+ }
91
156
 
92
- // 有子节点则添加,避免空数组
93
- if (children.length) {
94
- item[childrenKey] = children;
157
+ for (const node of nodeMap.values()) {
158
+ const parentId = node[pidKey];
159
+ if (parentId === pid || parentId == null) {
160
+ tree.push(node);
161
+ } else {
162
+ const parent = nodeMap.get(parentId);
163
+ if (parent) {
164
+ if (visited.has(node[idKey])) {
165
+ console.error(`[buildTree] 检测到循环引用: ${node[idKey]}`);
166
+ continue;
167
+ }
168
+ visited.add(node[idKey]);
169
+ parent[childrenKey].push(node);
95
170
  }
96
-
97
- tree.push(item);
98
171
  }
99
172
  }
100
173
 
174
+ const cleanEmptyChildren = (nodes) => {
175
+ for (const node of nodes) {
176
+ if (node[childrenKey].length === 0) {
177
+ delete node[childrenKey];
178
+ } else {
179
+ cleanEmptyChildren(node[childrenKey]);
180
+ }
181
+ }
182
+ };
183
+ cleanEmptyChildren(tree);
184
+
101
185
  return tree;
102
186
  }
package/helper/db.js CHANGED
@@ -1,91 +1,79 @@
1
1
  import knex from "knex";
2
2
 
3
- const errCode = {
4
- ECONNREFUSED: "数据库连接被拒绝,请检查数据库服务是否正常运行。",
5
- ER_ACCESS_DENIED_ERROR: "无权限访问,账号或密码错误。",
6
- ER_ROW_IS_REFERENCED_2: "无法删除或更新记录,存在关联数据。",
7
- ER_BAD_FIELD_ERROR: "SQL语句中包含无效字段,请检查查询条件或列名。",
8
- ER_DUP_ENTRY: "插入失败:数据重复,违反唯一性约束。",
9
- ER_NO_SUCH_TABLE: "操作失败:目标表不存在。",
10
- ETIMEOUT: "数据库操作超时,请稍后再试。",
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 "数据库权限不足,请检查配置。";
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
+ }
19
58
  }
20
- return "数据库发生未知错误,请稍后重试。";
21
- };
22
- export const db = ({
23
- client = "mysql2",
24
- host = "127.0.0.1",
25
- user = "root",
26
- password = "123456",
27
- database = "test",
28
- port = 3306,
29
- debug = true,
30
- charset = "utf8mb4",
31
- min = 0,
32
- max = 2,
33
- filename = "",
34
- }) => {
35
- let config = {
59
+
60
+ const client = process.env.DB_CLIENT || "mysql2";
61
+ if (client !== "mysql2") {
62
+ return null;
63
+ }
64
+
65
+ return {
36
66
  client,
37
67
  connection: {
38
- host,
39
- port,
40
- user,
41
- password,
42
- database,
43
- charset,
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"
44
73
  },
45
- debug,
46
74
  pool: {
47
- //默认为{min: 2, max: 10},连接池配置
48
- min,
49
- max,
50
- // 添加连接池错误处理
51
- afterCreate: (conn, done) => {
52
- conn.on("error", (error) => {
53
- console.error(
54
- `[连接池错误] 连接出错: ${getDefaultErrorMessage(error)}`,
55
- error
56
- );
57
- });
58
- done(null, conn);
59
- },
60
- },
61
- log: {
62
- warn(message) {
63
- console.error("[knex warn]", message);
64
- },
65
- error(message) {
66
- console.error("[knex error]", message);
67
- },
68
- debug(message) {
69
- console.log("[knex debug]", message);
70
- },
71
- deprecate(message) {
72
- console.warn("[knex deprecate]", message);
73
- },
74
- trace(message) {
75
- console.log("[knex trace]", message);
76
- },
77
- log(message) {
78
- console.log("[knex log]", message);
79
- },
80
- info(message) {
81
- console.log("[knex info]", message);
82
- },
83
- },
75
+ min: parseInt(process.env.DB_POOL_MIN || "2"),
76
+ max: parseInt(process.env.DB_POOL_MAX || "10")
77
+ }
84
78
  };
85
-
86
- if (client === "sqlite3" || client === "better-sqlite3") {
87
- config.connection.filename = filename;
88
- }
89
-
90
- return knex(config);
91
- };
79
+ }