chanjs 2.7.2 → 2.7.4
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 +232 -16
- package/base/Aop.js +20 -3
- package/base/Container.js +80 -3
- package/base/Controller.js +38 -9
- package/base/Database.js +50 -0
- package/base/Event.js +12 -0
- package/base/{Service.js → Repository.js} +644 -539
- package/common/api.js +18 -8
- package/common/code.js +25 -15
- package/common/email.js +98 -17
- package/common/index.js +1 -1
- package/config/code.js +138 -82
- package/global/index.js +1 -1
- package/helper/index.js +43 -41
- package/index.js +19 -6
- package/loader/index.js +6 -0
- package/{helper → loader}/loader.js +41 -27
- package/middleware/compress.js +185 -0
- package/middleware/cors.js +36 -24
- package/middleware/header.js +5 -10
- package/middleware/index.js +1 -0
- package/middleware/log.js +27 -3
- package/middleware/setBody.js +9 -1
- package/middleware/static.js +2 -1
- package/middleware/template.js +139 -4
- package/middleware/waf.js +136 -76
- package/package.json +4 -4
- package/realtime/index.js +7 -0
- package/realtime/sse.js +424 -0
- package/realtime/websocket.js +540 -0
- package/response/index.js +12 -0
- package/response/response.js +258 -0
- package/schedule/index.js +6 -0
- package/schedule/schedule.js +491 -0
- package/{helper → security}/checker.js +23 -8
- package/security/index.js +14 -0
- package/{helper → security}/jwt.js +175 -107
- package/security/keywords.js +179 -0
- package/security/rate-limit.js +105 -0
- package/security/sign.js +210 -0
- package/security/xss-filter.js +63 -0
- package/storage/cache.js +258 -0
- package/storage/index.js +9 -0
- package/storage/redis.js +258 -0
- package/storage/store.js +266 -0
- package/{helper → utils}/file.js +106 -15
- package/{helper → utils}/filter.js +2 -1
- package/{helper → utils}/html.js +19 -1
- package/utils/index.js +34 -0
- package/{helper → utils}/ip.js +25 -16
- package/utils/request.js +172 -0
- package/{helper → utils}/time.js +1 -1
- package/utils/tree.js +121 -0
- package/common/category.js +0 -22
- package/common/sms.js +0 -104
- package/extend/art-template.js +0 -129
- package/extend/index.js +0 -6
- package/global/global.js +0 -63
- package/helper/cache.js +0 -187
- package/helper/keywords.js +0 -132
- package/helper/rate-limit.js +0 -116
- package/helper/request.js +0 -47
- package/helper/response.js +0 -180
- package/helper/sign.js +0 -96
- package/helper/tree.js +0 -77
- package/helper/xss-filter.js +0 -42
- /package/{helper → utils}/data-parse.js +0 -0
package/common/api.js
CHANGED
|
@@ -1,21 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* API
|
|
3
|
-
*
|
|
2
|
+
* API 响应常量定义(旧版对象形式,保留向后兼容)
|
|
3
|
+
*
|
|
4
|
+
* ============================================================
|
|
5
|
+
* 重要说明
|
|
6
|
+
* ============================================================
|
|
7
|
+
* 业务侧推荐使用 helper/response.js 的函数式响应(success/fail/error),
|
|
8
|
+
* 通过 Controller 的 this.success() / this.fail() 调用。
|
|
9
|
+
*
|
|
10
|
+
* 本文件的 success/fail/error 为旧版对象常量,仅用于兼容历史代码。
|
|
11
|
+
* code 值已对齐新体系(0/1008/5001),不再使用 200/201/500。
|
|
12
|
+
*
|
|
13
|
+
* @deprecated 推荐使用 helper/response.js 的函数式响应
|
|
4
14
|
*/
|
|
5
15
|
|
|
6
16
|
export const success = {
|
|
7
|
-
code:
|
|
8
|
-
msg: "
|
|
17
|
+
code: 0,
|
|
18
|
+
msg: "操作成功",
|
|
9
19
|
};
|
|
10
20
|
|
|
11
21
|
export const fail = {
|
|
12
|
-
code:
|
|
13
|
-
msg: "
|
|
22
|
+
code: 1008,
|
|
23
|
+
msg: "操作失败",
|
|
14
24
|
};
|
|
15
25
|
|
|
16
26
|
export const error = {
|
|
17
|
-
code:
|
|
18
|
-
msg: "
|
|
27
|
+
code: 5001,
|
|
28
|
+
msg: "系统错误",
|
|
19
29
|
};
|
|
20
30
|
|
|
21
31
|
export default {
|
package/common/code.js
CHANGED
|
@@ -1,42 +1,52 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* 响应状态码常量定义(旧版对象形式,保留向后兼容)
|
|
3
|
+
*
|
|
4
|
+
* ============================================================
|
|
5
|
+
* 重要说明
|
|
6
|
+
* ============================================================
|
|
7
|
+
* 业务侧推荐使用 config/code.js 的三层 code 体系(0/1xxx/5xxx/6xxx),
|
|
8
|
+
* 该文件提供完整的 CODE 字典、CODE_RANGE 段位常量、语义化别名、DB_ERROR 映射。
|
|
9
|
+
*
|
|
10
|
+
* 本文件的 CODE 对象为旧版形式,仅用于兼容历史代码。
|
|
11
|
+
* code 值已对齐新体系,不再使用 200/201/500。
|
|
12
|
+
*
|
|
13
|
+
* @deprecated 推荐使用 config/code.js 的新体系
|
|
4
14
|
*/
|
|
5
15
|
|
|
6
16
|
export const CODE = {
|
|
7
17
|
/**
|
|
8
|
-
*
|
|
18
|
+
* 操作成功(code: 0,不再用 200)
|
|
9
19
|
*/
|
|
10
20
|
SUCCESS: {
|
|
11
|
-
code:
|
|
21
|
+
code: 0,
|
|
12
22
|
message: "操作成功",
|
|
13
23
|
},
|
|
14
24
|
/**
|
|
15
|
-
*
|
|
25
|
+
* 操作失败(code: 1008 业务处理失败)
|
|
16
26
|
*/
|
|
17
27
|
FAIL: {
|
|
18
|
-
code:
|
|
28
|
+
code: 1008,
|
|
19
29
|
message: "操作失败",
|
|
20
30
|
},
|
|
21
31
|
/**
|
|
22
|
-
*
|
|
32
|
+
* 系统错误(code: 5001 系统内部错误)
|
|
23
33
|
*/
|
|
24
34
|
ERROR: {
|
|
25
|
-
code:
|
|
26
|
-
message: "
|
|
35
|
+
code: 5001,
|
|
36
|
+
message: "系统错误",
|
|
27
37
|
},
|
|
28
38
|
/**
|
|
29
|
-
*
|
|
39
|
+
* 未授权(code: 1001 认证失败)
|
|
30
40
|
*/
|
|
31
41
|
UNAUTHORIZED: {
|
|
32
|
-
code:
|
|
33
|
-
message: "
|
|
42
|
+
code: 1001,
|
|
43
|
+
message: "认证失败",
|
|
34
44
|
},
|
|
35
45
|
/**
|
|
36
|
-
*
|
|
46
|
+
* 禁止访问(code: 1003 权限不足)
|
|
37
47
|
*/
|
|
38
48
|
FORBIDDEN: {
|
|
39
|
-
code:
|
|
40
|
-
message: "
|
|
49
|
+
code: 1003,
|
|
50
|
+
message: "权限不足",
|
|
41
51
|
},
|
|
42
52
|
};
|
package/common/email.js
CHANGED
|
@@ -1,10 +1,81 @@
|
|
|
1
1
|
import nodemailer from "nodemailer";
|
|
2
|
+
import Chan from "../App.js";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* 邮件发送工具
|
|
5
6
|
* 提供邮件发送和HTML模板生成功能
|
|
7
|
+
* 通过 import Chan 获取应用配置(不依赖 global.Chan)
|
|
6
8
|
*/
|
|
7
9
|
|
|
10
|
+
/**
|
|
11
|
+
* 邮箱地址正则(基础校验,防注入和拼写错误)
|
|
12
|
+
*/
|
|
13
|
+
const EMAIL_REGEX = /^[^\s@<>'"`]+@[^\s@<>'"`]+\.[^\s@<>'"`]+$/;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 模块级 transporter 缓存(避免每次发邮件都创建连接池)
|
|
17
|
+
* 配置不变时复用同一个 transporter,提升性能
|
|
18
|
+
*/
|
|
19
|
+
let _transporter = null;
|
|
20
|
+
let _transporterKey = '';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* HTML 特殊字符转义,防止邮件模板注入
|
|
24
|
+
* @param {string} str - 待转义的字符串
|
|
25
|
+
* @returns {string} 转义后的字符串
|
|
26
|
+
*/
|
|
27
|
+
function escapeHtml(str) {
|
|
28
|
+
if (str == null) return '';
|
|
29
|
+
return String(str)
|
|
30
|
+
.replace(/&/g, '&')
|
|
31
|
+
.replace(/</g, '<')
|
|
32
|
+
.replace(/>/g, '>')
|
|
33
|
+
.replace(/"/g, '"')
|
|
34
|
+
.replace(/'/g, ''');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 校验收件人邮箱格式(基础规则,拒绝含特殊字符的非法地址)
|
|
39
|
+
* @param {string} email - 邮箱地址
|
|
40
|
+
* @returns {boolean} 是否合法
|
|
41
|
+
*/
|
|
42
|
+
function isValidEmail(email) {
|
|
43
|
+
return typeof email === 'string' && EMAIL_REGEX.test(email) && email.length <= 254;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 获取或创建 transporter(带缓存)
|
|
48
|
+
* @param {Object} EMAIL - 邮件配置
|
|
49
|
+
* @returns {Object} nodemailer transporter
|
|
50
|
+
*/
|
|
51
|
+
function getTransporter(EMAIL) {
|
|
52
|
+
// 用 host+port+user 作为缓存 key,配置变化时自动重建
|
|
53
|
+
const key = `${EMAIL.HOST}:${EMAIL.PORT}:${EMAIL.USER}`;
|
|
54
|
+
if (_transporter && _transporterKey === key) {
|
|
55
|
+
return _transporter;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
_transporter = nodemailer.createTransport({
|
|
59
|
+
host: EMAIL.HOST,
|
|
60
|
+
port: parseInt(EMAIL.PORT),
|
|
61
|
+
secure: EMAIL.SECURE === "true",
|
|
62
|
+
auth: {
|
|
63
|
+
user: EMAIL.USER,
|
|
64
|
+
pass: EMAIL.PASS,
|
|
65
|
+
},
|
|
66
|
+
// 连接池配置:避免每次发邮件都握手,降低 SMTP 服务器压力
|
|
67
|
+
pool: true,
|
|
68
|
+
maxConnections: 5,
|
|
69
|
+
maxMessages: 100,
|
|
70
|
+
// 超时控制,避免 SMTP 卡死拖垮业务
|
|
71
|
+
connectionTimeout: 10 * 1000,
|
|
72
|
+
greetingTimeout: 10 * 1000,
|
|
73
|
+
socketTimeout: 30 * 1000,
|
|
74
|
+
});
|
|
75
|
+
_transporterKey = key;
|
|
76
|
+
return _transporter;
|
|
77
|
+
}
|
|
78
|
+
|
|
8
79
|
/**
|
|
9
80
|
* 发送邮件
|
|
10
81
|
* @param {string} to - 收件人邮箱
|
|
@@ -13,19 +84,19 @@ import nodemailer from "nodemailer";
|
|
|
13
84
|
* @param {string|null} html - 邮件HTML内容,默认为null
|
|
14
85
|
* @returns {Promise<Object>} 发送结果
|
|
15
86
|
* @throws {Error} 邮件服务未配置或发送失败时抛出异常
|
|
87
|
+
* @description
|
|
88
|
+
* - 收件人邮箱格式校验,拒绝非法地址
|
|
89
|
+
* - transporter 模块级缓存,避免每次创建连接池
|
|
16
90
|
*/
|
|
17
91
|
export const sendMail = async (to, subject, text, html = null) => {
|
|
92
|
+
// 收件人邮箱格式校验
|
|
93
|
+
if (!isValidEmail(to)) {
|
|
94
|
+
throw new Error(`[email] 收件人邮箱格式非法: ${to}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
18
97
|
const { EMAIL, APP_NAME } = Chan.config;
|
|
19
98
|
|
|
20
|
-
const transporter =
|
|
21
|
-
host: EMAIL.HOST,
|
|
22
|
-
port: parseInt(EMAIL.PORT),
|
|
23
|
-
secure: EMAIL.SECURE === "true",
|
|
24
|
-
auth: {
|
|
25
|
-
user: EMAIL.USER,
|
|
26
|
-
pass: EMAIL.PASS,
|
|
27
|
-
},
|
|
28
|
-
});
|
|
99
|
+
const transporter = getTransporter(EMAIL);
|
|
29
100
|
|
|
30
101
|
let res = await transporter.verify();
|
|
31
102
|
if (!res) {
|
|
@@ -54,26 +125,31 @@ export const sendMail = async (to, subject, text, html = null) => {
|
|
|
54
125
|
* @param {string} code - 验证码
|
|
55
126
|
* @param {number} minutes - 有效期(分钟),默认10分钟
|
|
56
127
|
* @returns {string} HTML字符串
|
|
128
|
+
* @description
|
|
129
|
+
* 安全改进:所有动态内容(code、APP_NAME)都经 escapeHtml 转义,防止 HTML 注入
|
|
57
130
|
*/
|
|
58
131
|
export const genRegEmailHtml = (code, minutes = 10) => {
|
|
59
132
|
const { APP_NAME } = Chan.config;
|
|
133
|
+
// 转义防止 HTML 注入
|
|
134
|
+
const safeCode = escapeHtml(code);
|
|
135
|
+
const safeAppName = escapeHtml(APP_NAME);
|
|
60
136
|
return `
|
|
61
137
|
<div style="font-family: Arial, sans-serif; max-width: 750px; margin: auto; border: 1px solid #ddd; border-radius: 10px; overflow: hidden;">
|
|
62
138
|
<div style="background-color: #007bff; color: white; padding: 10px; text-align: center;">
|
|
63
|
-
<h2>${
|
|
139
|
+
<h2>${safeAppName} 欢迎注册!</h2>
|
|
64
140
|
</div>
|
|
65
141
|
<div style="padding: 20px; line-height: 1.6;">
|
|
66
142
|
<p>您好,感谢您注册我们的服务!</p>
|
|
67
143
|
<p>您的注册验证码是:</p>
|
|
68
144
|
<div style="font-size: 24px; font-weight: bold; color: #007bff; text-align: center; margin: 20px 0;">
|
|
69
|
-
${
|
|
145
|
+
${safeCode}
|
|
70
146
|
</div>
|
|
71
|
-
<p>请在 <strong>${minutes} 分钟内</strong> 输入该验证码,完成账户验证。</p>
|
|
147
|
+
<p>请在 <strong>${escapeHtml(minutes)} 分钟内</strong> 输入该验证码,完成账户验证。</p>
|
|
72
148
|
<p>如非本人操作,请忽略此邮件。</p>
|
|
73
149
|
<p>祝您使用愉快!</p>
|
|
74
150
|
</div>
|
|
75
151
|
<div style="background-color: #f8f9fa; padding: 10px; text-align: center; font-size: 12px; color: #666;">
|
|
76
|
-
© ${new Date().getFullYear()} ${
|
|
152
|
+
© ${new Date().getFullYear()} ${safeAppName}. 保留所有权利。
|
|
77
153
|
</div>
|
|
78
154
|
</div>
|
|
79
155
|
`;
|
|
@@ -84,26 +160,31 @@ export const genRegEmailHtml = (code, minutes = 10) => {
|
|
|
84
160
|
* @param {string} code - 验证码
|
|
85
161
|
* @param {number} minutes - 有效期(分钟),默认10分钟
|
|
86
162
|
* @returns {string} HTML字符串
|
|
163
|
+
* @description
|
|
164
|
+
* 安全改进:所有动态内容(code、APP_NAME)都经 escapeHtml 转义,防止 HTML 注入
|
|
87
165
|
*/
|
|
88
166
|
export const genResetPasswordEmail = (code, minutes = 10) => {
|
|
89
167
|
const { APP_NAME } = Chan.config;
|
|
168
|
+
// 转义防止 HTML 注入
|
|
169
|
+
const safeCode = escapeHtml(code);
|
|
170
|
+
const safeAppName = escapeHtml(APP_NAME);
|
|
90
171
|
return `
|
|
91
172
|
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: auto; border: 1px solid #ddd; border-radius: 10px; overflow: hidden;">
|
|
92
173
|
<div style="background-color: #28a745; color: white; padding: 20px; text-align: center;">
|
|
93
|
-
<h2>重置密码_${
|
|
174
|
+
<h2>重置密码_${safeAppName}</h2>
|
|
94
175
|
</div>
|
|
95
176
|
<div style="padding: 20px; line-height: 1.6;">
|
|
96
177
|
<p>您好,我们收到了您重置密码的请求。</p>
|
|
97
178
|
<p>您的重置密码验证码是:</p>
|
|
98
179
|
<div style="font-size: 24px; font-weight: bold; color: #007bff; text-align: center; margin: 20px 0;">
|
|
99
|
-
${
|
|
180
|
+
${safeCode}
|
|
100
181
|
</div>
|
|
101
|
-
<p>请在 <strong>${minutes} 分钟内</strong> 输入该验证码,完成账户验证。</p>
|
|
182
|
+
<p>请在 <strong>${escapeHtml(minutes)} 分钟内</strong> 输入该验证码,完成账户验证。</p>
|
|
102
183
|
<p>如非本人操作,请忽略此邮件。</p>
|
|
103
184
|
<p>祝您使用愉快!</p>
|
|
104
185
|
</div>
|
|
105
186
|
<div style="background-color: #f8f9fa; padding: 10px; text-align: center; font-size: 12px; color: #666;">
|
|
106
|
-
© ${new Date().getFullYear()} ${
|
|
187
|
+
© ${new Date().getFullYear()} ${safeAppName}. 保留所有权利。
|
|
107
188
|
</div>
|
|
108
189
|
</div>
|
|
109
190
|
`;
|
package/common/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { success, fail, error } from "./api.js";
|
|
2
|
-
export { getChildrenId } from "./
|
|
2
|
+
export { getChildrenId } from "./utils.js";
|
|
3
3
|
export { CODE } from "./code.js";
|
|
4
4
|
export { sendMail, genRegEmailHtml, genResetPasswordEmail } from "./email.js";
|
|
5
5
|
export { pages, getHtmlFilesSync } from "./pages.js";
|
package/config/code.js
CHANGED
|
@@ -1,102 +1,127 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* 业务状态码定义(统一三层 code 体系)
|
|
3
|
+
*
|
|
4
|
+
* ============================================================
|
|
5
|
+
* 设计原则
|
|
6
|
+
* ============================================================
|
|
7
|
+
* 1. 业务 code 与 HTTP Status 彻底解耦
|
|
8
|
+
* - HTTP Status 由网关/Express 控制(200/400/401/403/404/500)
|
|
9
|
+
* - 业务 code 由业务层控制(0 / 1xxx / 5xxx / 6xxx)
|
|
10
|
+
* - 前端 axios 先看 HTTP Status,再看业务 code
|
|
11
|
+
*
|
|
12
|
+
* 2. 0 表示成功(不再用 200,避免与 HTTP 200 混淆)
|
|
13
|
+
*
|
|
14
|
+
* 3. 错误按业务域分段
|
|
15
|
+
* - 1xxx 通用业务错误(认证/权限/参数/资源)
|
|
16
|
+
* - 2xxx CMS 业务专用错误(保留扩展段)
|
|
17
|
+
* - 5xxx 系统错误
|
|
18
|
+
* - 6xxx 数据库错误
|
|
19
|
+
*
|
|
20
|
+
* 4. 每个 code 都有默认 msg,业务可覆盖
|
|
21
|
+
*
|
|
22
|
+
* ============================================================
|
|
23
|
+
* Code 段位定义
|
|
24
|
+
* ============================================================
|
|
25
|
+
* 0 → 成功
|
|
26
|
+
* 1001 → 认证失败(token 无效/未登录)
|
|
27
|
+
* 1002 → token 已过期
|
|
28
|
+
* 1003 → 权限不足
|
|
29
|
+
* 1004 → 资源不存在
|
|
30
|
+
* 1005 → 资源已存在
|
|
31
|
+
* 1006 → 参数无效
|
|
32
|
+
* 1007 → 参数缺失
|
|
33
|
+
* 1008 → 业务处理失败(通用兜底)
|
|
34
|
+
* 1009 → 请求过于频繁(限流)
|
|
35
|
+
* 1010 → 登录设备异常
|
|
36
|
+
* 2xxx → CMS 业务专用错误(保留扩展段)
|
|
37
|
+
* 5001 → 系统内部错误
|
|
38
|
+
* 5002 → 服务繁忙,请稍后再试
|
|
39
|
+
* 6001 → 数据库连接失败
|
|
40
|
+
* 6002 → 数据库访问被拒绝
|
|
41
|
+
* 6003 → 存在关联数据,操作失败
|
|
42
|
+
* 6004 → 数据库字段错误
|
|
43
|
+
* 6005 → 数据重复,违反唯一性约束
|
|
44
|
+
* 6006 → 目标表不存在
|
|
45
|
+
* 6007 → 数据库操作超时
|
|
46
|
+
* 6008 → 数据库语法错误
|
|
47
|
+
* 6009 → 数据库连接已关闭
|
|
4
48
|
*/
|
|
5
49
|
|
|
50
|
+
/**
|
|
51
|
+
* 业务状态码 → 默认消息
|
|
52
|
+
* @type {Object<number, string>}
|
|
53
|
+
*/
|
|
6
54
|
export const CODE = {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
2002: "参数缺失",
|
|
27
|
-
/**
|
|
28
|
-
* 认证失败
|
|
29
|
-
*/
|
|
30
|
-
3001: "认证失败",
|
|
31
|
-
/**
|
|
32
|
-
* 令牌已过期
|
|
33
|
-
*/
|
|
34
|
-
3002: "令牌已过期",
|
|
35
|
-
/**
|
|
36
|
-
* 权限不足
|
|
37
|
-
*/
|
|
38
|
-
3003: "权限不足",
|
|
39
|
-
/**
|
|
40
|
-
* 资源不存在
|
|
41
|
-
*/
|
|
42
|
-
4001: "资源不存在",
|
|
43
|
-
/**
|
|
44
|
-
* 资源已锁定
|
|
45
|
-
*/
|
|
46
|
-
4002: "资源已锁定",
|
|
47
|
-
/**
|
|
48
|
-
* 资源已存在
|
|
49
|
-
*/
|
|
50
|
-
4003: "资源已存在",
|
|
51
|
-
/**
|
|
52
|
-
* 系统内部错误
|
|
53
|
-
*/
|
|
55
|
+
// 成功
|
|
56
|
+
0: "操作成功",
|
|
57
|
+
|
|
58
|
+
// 1xxx 通用业务错误
|
|
59
|
+
1001: "认证失败",
|
|
60
|
+
1002: "令牌已过期",
|
|
61
|
+
1003: "权限不足",
|
|
62
|
+
1004: "资源不存在",
|
|
63
|
+
1005: "资源已存在",
|
|
64
|
+
1006: "参数无效",
|
|
65
|
+
1007: "参数缺失",
|
|
66
|
+
1008: "业务处理失败",
|
|
67
|
+
1009: "请求过于频繁",
|
|
68
|
+
1010: "登录设备异常",
|
|
69
|
+
|
|
70
|
+
// 2xxx CMS 业务专用错误(保留扩展段,业务方可自定义)
|
|
71
|
+
// 例如:2001 文章不存在 / 2002 栏目不存在 等
|
|
72
|
+
|
|
73
|
+
// 5xxx 系统错误
|
|
54
74
|
5001: "系统内部错误",
|
|
55
|
-
/**
|
|
56
|
-
* 服务繁忙,请稍后再试
|
|
57
|
-
*/
|
|
58
75
|
5002: "服务繁忙,请稍后再试",
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
*/
|
|
76
|
+
|
|
77
|
+
// 6xxx 数据库错误
|
|
62
78
|
6001: "数据库连接失败",
|
|
63
|
-
/**
|
|
64
|
-
* 数据库访问被拒绝
|
|
65
|
-
*/
|
|
66
79
|
6002: "数据库访问被拒绝",
|
|
67
|
-
/**
|
|
68
|
-
* 存在关联数据,操作失败
|
|
69
|
-
*/
|
|
70
80
|
6003: "存在关联数据,操作失败",
|
|
71
|
-
/**
|
|
72
|
-
* 数据库字段错误
|
|
73
|
-
*/
|
|
74
81
|
6004: "数据库字段错误",
|
|
75
|
-
/**
|
|
76
|
-
* 数据重复,违反唯一性约束
|
|
77
|
-
*/
|
|
78
82
|
6005: "数据重复,违反唯一性约束",
|
|
79
|
-
/**
|
|
80
|
-
* 目标表不存在
|
|
81
|
-
*/
|
|
82
83
|
6006: "目标表不存在",
|
|
83
|
-
/**
|
|
84
|
-
* 数据库操作超时
|
|
85
|
-
*/
|
|
86
84
|
6007: "数据库操作超时",
|
|
87
|
-
/**
|
|
88
|
-
* 数据库语法错误,请检查查询语句
|
|
89
|
-
*/
|
|
90
85
|
6008: "数据库语法错误,请检查查询语句",
|
|
91
|
-
/**
|
|
92
|
-
* 数据库连接已关闭,请重试
|
|
93
|
-
*/
|
|
94
86
|
6009: "数据库连接已关闭,请重试",
|
|
95
87
|
};
|
|
96
88
|
|
|
97
89
|
/**
|
|
98
|
-
*
|
|
99
|
-
*
|
|
90
|
+
* 业务 code 段位常量
|
|
91
|
+
* 便于业务方判断 code 归属段位
|
|
92
|
+
* @example
|
|
93
|
+
* if (code >= CODE_RANGE.SYSTEM[0] && code <= CODE_RANGE.SYSTEM[1]) { ... }
|
|
94
|
+
*/
|
|
95
|
+
export const CODE_RANGE = {
|
|
96
|
+
SUCCESS: [0, 0], // 成功
|
|
97
|
+
BUSINESS: [1000, 1999], // 通用业务错误
|
|
98
|
+
CMS: [2000, 2999], // CMS 业务错误(保留扩展段)
|
|
99
|
+
SYSTEM: [5000, 5999], // 系统错误
|
|
100
|
+
DATABASE: [6000, 6999], // 数据库错误
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 常用 code 语义化别名
|
|
105
|
+
* 业务方可直接用 CODE_OK / CODE_AUTH_FAILED 等语义化常量,避免硬编码数字
|
|
106
|
+
*/
|
|
107
|
+
export const CODE_OK = 0; // 成功
|
|
108
|
+
export const CODE_AUTH_FAILED = 1001; // 认证失败
|
|
109
|
+
export const CODE_TOKEN_EXPIRED = 1002; // token 过期
|
|
110
|
+
export const CODE_FORBIDDEN = 1003; // 权限不足
|
|
111
|
+
export const CODE_NOT_FOUND = 1004; // 资源不存在
|
|
112
|
+
export const CODE_CONFLICT = 1005; // 资源已存在
|
|
113
|
+
export const CODE_PARAM_INVALID = 1006; // 参数无效
|
|
114
|
+
export const CODE_PARAM_MISSING = 1007; // 参数缺失
|
|
115
|
+
export const CODE_BUSINESS_FAIL = 1008; // 业务处理失败(通用)
|
|
116
|
+
export const CODE_RATE_LIMIT = 1009; // 限流
|
|
117
|
+
export const CODE_DEVICE_ERROR = 1010; // 登录设备异常
|
|
118
|
+
export const CODE_SYSTEM_ERROR = 5001; // 系统内部错误
|
|
119
|
+
export const CODE_SERVICE_BUSY = 5002; // 服务繁忙
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* 数据库原生错误码 → 业务 code 映射
|
|
123
|
+
* 将 MySQL/Knex 等数据库原生错误码转换为业务 code
|
|
124
|
+
* @type {Object<string, number>}
|
|
100
125
|
*/
|
|
101
126
|
export const DB_ERROR = {
|
|
102
127
|
ECONNREFUSED: 6001,
|
|
@@ -106,5 +131,36 @@ export const DB_ERROR = {
|
|
|
106
131
|
ER_DUP_ENTRY: 6005,
|
|
107
132
|
ER_NO_SUCH_TABLE: 6006,
|
|
108
133
|
ETIMEOUT: 6007,
|
|
109
|
-
ER_TABLE_EXISTS_ERROR:
|
|
134
|
+
ER_TABLE_EXISTS_ERROR: 1005, // 表已存在 → 资源已存在
|
|
110
135
|
};
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* 判断 code 是否为成功
|
|
139
|
+
* @param {number} code - 业务 code
|
|
140
|
+
* @returns {boolean}
|
|
141
|
+
*/
|
|
142
|
+
export function isSuccessCode(code) {
|
|
143
|
+
return code === CODE_OK;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* 判断 code 所属段位
|
|
148
|
+
* @param {number} code - 业务 code
|
|
149
|
+
* @returns {string} 段位名:SUCCESS / BUSINESS / CMS / SYSTEM / DATABASE / UNKNOWN
|
|
150
|
+
*/
|
|
151
|
+
export function getCodeRange(code) {
|
|
152
|
+
for (const [name, [min, max]] of Object.entries(CODE_RANGE)) {
|
|
153
|
+
if (code >= min && code <= max) return name;
|
|
154
|
+
}
|
|
155
|
+
return 'UNKNOWN';
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* 获取 code 的默认消息
|
|
160
|
+
* @param {number} code - 业务 code
|
|
161
|
+
* @param {string} [fallback='操作失败'] - 兜底消息
|
|
162
|
+
* @returns {string}
|
|
163
|
+
*/
|
|
164
|
+
export function getCodeMsg(code, fallback = '操作失败') {
|
|
165
|
+
return CODE[code] || fallback;
|
|
166
|
+
}
|
package/global/index.js
CHANGED
package/helper/index.js
CHANGED
|
@@ -1,11 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* 聚合导出层 - 从各子模块统一 re-export
|
|
3
|
+
*
|
|
4
|
+
* 框架内部已按职责拆分到 storage/security/realtime/schedule/loader/response/utils
|
|
5
|
+
* 本文件提供扁平化聚合访问,业务侧可用:
|
|
6
|
+
* import { helper } from "chanjs";
|
|
7
|
+
* const { getIp, formatDateFields, filterXSS } = helper;
|
|
8
|
+
*
|
|
9
|
+
* 也可直接从子模块导入(更精确):
|
|
10
|
+
* import { utils, security, storage } from "chanjs";
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// 加载器
|
|
14
|
+
export { loaderSort, loadConfig, clearConfigCache, loadController } from "../loader/index.js";
|
|
3
15
|
|
|
4
16
|
// 时间处理
|
|
5
|
-
export { formatTime, formatDateFields } from "
|
|
17
|
+
export { formatTime, formatDateFields } from "../utils/time.js";
|
|
6
18
|
|
|
7
|
-
//
|
|
8
|
-
export { cache } from "
|
|
19
|
+
// 存储相关
|
|
20
|
+
export { cache, DEFAULT_INCR_TTL } from "../storage/index.js";
|
|
21
|
+
export { store } from "../storage/index.js";
|
|
22
|
+
export { default as RedisBackend } from "../storage/redis.js";
|
|
9
23
|
|
|
10
24
|
// 文件操作
|
|
11
25
|
export {
|
|
@@ -15,47 +29,32 @@ export {
|
|
|
15
29
|
readFileContent,
|
|
16
30
|
saveFileContent,
|
|
17
31
|
isPathSafe,
|
|
18
|
-
getFolders
|
|
19
|
-
} from "
|
|
32
|
+
getFolders,
|
|
33
|
+
} from "../utils/file.js";
|
|
20
34
|
|
|
21
|
-
// HTML处理
|
|
22
|
-
export { htmlDecode, htmlEncode,escapeScript } from "
|
|
35
|
+
// HTML 处理
|
|
36
|
+
export { htmlDecode, htmlEncode, escapeScript } from "../utils/html.js";
|
|
23
37
|
|
|
24
|
-
// IP
|
|
25
|
-
export { getIp } from "
|
|
38
|
+
// IP 获取
|
|
39
|
+
export { getIp } from "../utils/ip.js";
|
|
26
40
|
|
|
27
|
-
// JWT令牌
|
|
28
|
-
export {
|
|
29
|
-
verifyToken,
|
|
30
|
-
generateToken,
|
|
31
|
-
setToken,
|
|
32
|
-
getToken
|
|
33
|
-
} from "./jwt.js";
|
|
41
|
+
// JWT 令牌
|
|
42
|
+
export { verifyToken, generateToken, setToken, getToken } from "../security/jwt.js";
|
|
34
43
|
|
|
35
44
|
// 签名/加密
|
|
36
|
-
export {
|
|
37
|
-
signData,
|
|
38
|
-
verifySign,
|
|
39
|
-
aesEncrypt,
|
|
40
|
-
aesDecrypt
|
|
41
|
-
} from "./sign.js";
|
|
45
|
+
export { signData, verifySign, aesEncrypt, aesDecrypt } from "../security/sign.js";
|
|
42
46
|
|
|
43
47
|
// 网络请求
|
|
44
|
-
export { request } from "
|
|
48
|
+
export { request } from "../utils/request.js";
|
|
45
49
|
|
|
46
50
|
// 数据解析
|
|
47
|
-
export {
|
|
48
|
-
dataParse,
|
|
49
|
-
arrToObj,
|
|
50
|
-
parseJsonFields,
|
|
51
|
-
buildTree
|
|
52
|
-
} from "./data-parse.js";
|
|
51
|
+
export { dataParse, arrToObj, parseJsonFields, buildTree } from "../utils/data-parse.js";
|
|
53
52
|
|
|
54
53
|
// 树形结构
|
|
55
|
-
export { tree, treeById } from "
|
|
54
|
+
export { tree, treeById } from "../utils/tree.js";
|
|
56
55
|
|
|
57
56
|
// 字段过滤
|
|
58
|
-
export { filterFields } from "
|
|
57
|
+
export { filterFields } from "../utils/filter.js";
|
|
59
58
|
|
|
60
59
|
// 响应格式化
|
|
61
60
|
export {
|
|
@@ -64,14 +63,17 @@ export {
|
|
|
64
63
|
error,
|
|
65
64
|
parseDatabaseError,
|
|
66
65
|
notFoundResponse,
|
|
67
|
-
errorResponse
|
|
68
|
-
} from "
|
|
66
|
+
errorResponse,
|
|
67
|
+
} from "../response/index.js";
|
|
69
68
|
|
|
70
|
-
//
|
|
71
|
-
export { checkKeywords, isIgnored } from "
|
|
69
|
+
// 安全检查
|
|
70
|
+
export { checkKeywords, isIgnored } from "../security/checker.js";
|
|
71
|
+
export { filterXSS } from "../security/xss-filter.js";
|
|
72
|
+
export { createRateLimitMiddleware } from "../security/rate-limit.js";
|
|
72
73
|
|
|
73
|
-
//
|
|
74
|
-
export {
|
|
74
|
+
// 定时任务
|
|
75
|
+
export { schedule } from "../schedule/index.js";
|
|
75
76
|
|
|
76
|
-
//
|
|
77
|
-
export {
|
|
77
|
+
// 实时通信
|
|
78
|
+
export { sse, SSEManager } from "../realtime/index.js";
|
|
79
|
+
export { websocket, WebSocketManager } from "../realtime/index.js";
|