chanjs 2.7.4 → 2.7.6
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/BaseComponent.js +27 -0
- package/core/Container.js +68 -0
- package/core/Controller.js +29 -0
- package/core/Database.js +93 -0
- package/core/Repository.js +323 -0
- package/core/Service.js +11 -0
- package/core/bootstrap/error-handler.js +101 -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 +251 -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 +61 -37
- package/middleware/body.js +17 -0
- package/middleware/cookie.js +7 -15
- package/middleware/cors.js +9 -27
- package/middleware/favicon.js +15 -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 +176 -197
- package/package.json +9 -2
- 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 +84 -139
- package/security/keywords.js +33 -137
- package/security/rate-limit.js +38 -80
- package/security/sign.js +83 -176
- package/security/xss-filter.js +21 -53
- package/storage/cache.js +58 -198
- package/storage/index.js +3 -6
- package/storage/redis.js +124 -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 +20 -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 +95 -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/doc/Aop.md
DELETED
|
@@ -1,269 +0,0 @@
|
|
|
1
|
-
# Aop 轻量级切面类使用文档
|
|
2
|
-
|
|
3
|
-
## 概述
|
|
4
|
-
`Aop` 是一个轻量级的 JavaScript 切面编程(AOP)工具类,支持为对象方法注册并执行 `before`(前置)、`after`(后置)、`error`(异常)三种类型的切面函数,适用于日志记录、参数校验、异常处理等横切关注点场景。
|
|
5
|
-
|
|
6
|
-
## 特性
|
|
7
|
-
- 支持链式调用注册切面函数
|
|
8
|
-
- 内置切面类型校验,仅支持 `before`/`after`/`error` 三种常用类型
|
|
9
|
-
- 支持切面规则启用/禁用,灵活控制切面执行
|
|
10
|
-
- 保留原方法上下文,保证方法执行正确性
|
|
11
|
-
- 完善的错误处理,切面执行异常不阻断主流程
|
|
12
|
-
|
|
13
|
-
## 安装与引入
|
|
14
|
-
### 方式1:ESModule 引入
|
|
15
|
-
```javascript
|
|
16
|
-
import { Aop, aop } from './aop.js';
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
## 核心 API
|
|
20
|
-
|
|
21
|
-
### 1. 切面注册(set)
|
|
22
|
-
注册一个切面函数,返回当前实例支持链式调用。
|
|
23
|
-
|
|
24
|
-
#### 语法
|
|
25
|
-
```javascript
|
|
26
|
-
aop.set(name, fn)
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
#### 参数
|
|
30
|
-
| 参数 | 类型 | 说明 |
|
|
31
|
-
|------|------|------|
|
|
32
|
-
| name | string | 切面名称(非空字符串) |
|
|
33
|
-
| fn | Function | 切面执行函数,入参为切面上下文对象 |
|
|
34
|
-
|
|
35
|
-
#### 示例
|
|
36
|
-
```javascript
|
|
37
|
-
// 注册日志切面
|
|
38
|
-
aop.set('logger', async (params) => {
|
|
39
|
-
console.log(`[${params.methodName}] 执行时间:${new Date().toISOString()}`);
|
|
40
|
-
console.log(`参数:`, params.args);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
// 注册参数校验切面(链式调用)
|
|
44
|
-
aop.set('paramCheck', async (params) => {
|
|
45
|
-
const [id] = params.args;
|
|
46
|
-
if (!id || typeof id !== 'number') {
|
|
47
|
-
throw new Error(`[${params.methodName}] 参数id必须是数字`);
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
### 2. 获取切面(get)
|
|
53
|
-
获取已注册的切面函数。
|
|
54
|
-
|
|
55
|
-
#### 语法
|
|
56
|
-
```javascript
|
|
57
|
-
aop.get(name)
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
#### 参数
|
|
61
|
-
| 参数 | 类型 | 说明 |
|
|
62
|
-
|------|------|------|
|
|
63
|
-
| name | string | 切面名称 |
|
|
64
|
-
|
|
65
|
-
#### 返回值
|
|
66
|
-
| 类型 | 说明 |
|
|
67
|
-
|------|------|
|
|
68
|
-
| Function \| null | 存在则返回切面函数,否则返回 null |
|
|
69
|
-
|
|
70
|
-
#### 示例
|
|
71
|
-
```javascript
|
|
72
|
-
const logger = aop.get('logger');
|
|
73
|
-
if (logger) {
|
|
74
|
-
console.log('日志切面已注册');
|
|
75
|
-
}
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
### 3. 移除切面(remove)
|
|
79
|
-
移除指定名称的切面函数。
|
|
80
|
-
|
|
81
|
-
#### 语法
|
|
82
|
-
```javascript
|
|
83
|
-
aop.remove(name)
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
#### 参数
|
|
87
|
-
| 参数 | 类型 | 说明 |
|
|
88
|
-
|------|------|------|
|
|
89
|
-
| name | string | 切面名称 |
|
|
90
|
-
|
|
91
|
-
#### 示例
|
|
92
|
-
```javascript
|
|
93
|
-
// 移除日志切面
|
|
94
|
-
aop.remove('logger');
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
### 4. 清空所有切面(clear)
|
|
98
|
-
清空已注册的所有切面函数。
|
|
99
|
-
|
|
100
|
-
#### 语法
|
|
101
|
-
```javascript
|
|
102
|
-
aop.clear()
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
#### 示例
|
|
106
|
-
```javascript
|
|
107
|
-
// 清空所有切面
|
|
108
|
-
aop.clear();
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
### 5. 绑定切面到方法(wrap)
|
|
112
|
-
核心方法,为目标对象的指定方法绑定切面规则。
|
|
113
|
-
|
|
114
|
-
#### 语法
|
|
115
|
-
```javascript
|
|
116
|
-
aop.wrap(instance, config)
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
#### 参数
|
|
120
|
-
| 参数 | 类型 | 说明 |
|
|
121
|
-
|------|------|------|
|
|
122
|
-
| instance | object | 目标实例(非空对象) |
|
|
123
|
-
| config | object | 切面配置,格式:`{ 方法名: [{ type: 切面类型, enabled: 是否启用, 切面名称: 配置 }] }` |
|
|
124
|
-
|
|
125
|
-
#### 配置规则说明
|
|
126
|
-
| 字段 | 类型 | 说明 | 必填 | 默认值 |
|
|
127
|
-
|------|------|------|------|--------|
|
|
128
|
-
| type | string | 切面类型(before/after/error) | 是 | - |
|
|
129
|
-
| enabled | boolean | 是否启用该规则 | 否 | true |
|
|
130
|
-
| [切面名称] | any | 自定义配置,会透传给切面函数 | 否 | {} |
|
|
131
|
-
|
|
132
|
-
#### 返回值
|
|
133
|
-
| 类型 | 说明 |
|
|
134
|
-
|------|------|
|
|
135
|
-
| object | 绑定后的实例对象 |
|
|
136
|
-
|
|
137
|
-
## 完整使用示例
|
|
138
|
-
|
|
139
|
-
### 步骤1:定义目标类
|
|
140
|
-
```javascript
|
|
141
|
-
// 示例控制器类
|
|
142
|
-
class UserController {
|
|
143
|
-
async getUser(id) {
|
|
144
|
-
console.log(`获取用户信息,ID:${id}`);
|
|
145
|
-
if (id === 0) {
|
|
146
|
-
throw new Error('用户ID不能为0');
|
|
147
|
-
}
|
|
148
|
-
return { id, name: '张三', age: 20 };
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
### 步骤2:注册切面函数
|
|
154
|
-
```javascript
|
|
155
|
-
import { aop } from './aop.js';
|
|
156
|
-
|
|
157
|
-
// 1. 注册参数校验切面
|
|
158
|
-
aop.set('paramCheck', async (params) => {
|
|
159
|
-
const [id] = params.args;
|
|
160
|
-
console.log(`[paramCheck] 校验参数:id = ${id}`);
|
|
161
|
-
if (typeof id !== 'number') {
|
|
162
|
-
throw new Error(`参数id必须是数字,当前值:${id}`);
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
// 2. 注册日志切面
|
|
167
|
-
aop.set('logger', async (params) => {
|
|
168
|
-
const { methodName, args, result, error } = params;
|
|
169
|
-
if (params.type === 'before') {
|
|
170
|
-
console.log(`[logger] 方法${methodName}开始执行,参数:`, args);
|
|
171
|
-
} else if (params.type === 'after') {
|
|
172
|
-
console.log(`[logger] 方法${methodName}执行完成,结果:`, result);
|
|
173
|
-
} else if (params.type === 'error') {
|
|
174
|
-
console.log(`[logger] 方法${methodName}执行异常:`, error.message);
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
// 3. 注册异常处理切面
|
|
179
|
-
aop.set('errorHandler', async (params) => {
|
|
180
|
-
console.log(`[errorHandler] 捕获异常:${params.error.message},执行兜底逻辑`);
|
|
181
|
-
// 可在这里添加异常上报、数据清理等逻辑
|
|
182
|
-
});
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
### 步骤3:绑定切面到方法并使用
|
|
186
|
-
```javascript
|
|
187
|
-
// 创建实例
|
|
188
|
-
const userController = new UserController();
|
|
189
|
-
|
|
190
|
-
// 绑定切面规则
|
|
191
|
-
aop.wrap(userController, {
|
|
192
|
-
getUser: [
|
|
193
|
-
// 前置切面:参数校验 + 日志
|
|
194
|
-
{ type: 'before', enabled: true, paramCheck: true, logger: true },
|
|
195
|
-
// 后置切面:日志
|
|
196
|
-
{ type: 'after', enabled: true, logger: true },
|
|
197
|
-
// 异常切面:日志 + 异常处理
|
|
198
|
-
{ type: 'error', enabled: true, logger: true, errorHandler: true }
|
|
199
|
-
]
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
// 测试正常执行
|
|
203
|
-
async function testNormal() {
|
|
204
|
-
try {
|
|
205
|
-
const result = await userController.getUser(1);
|
|
206
|
-
console.log('最终结果:', result);
|
|
207
|
-
} catch (e) {
|
|
208
|
-
console.log('测试异常:', e.message);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// 测试异常执行
|
|
213
|
-
async function testError() {
|
|
214
|
-
try {
|
|
215
|
-
const result = await userController.getUser(0);
|
|
216
|
-
console.log('最终结果:', result);
|
|
217
|
-
} catch (e) {
|
|
218
|
-
console.log('测试异常:', e.message);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// 执行测试
|
|
223
|
-
testNormal();
|
|
224
|
-
// testError();
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
### 执行结果(testNormal)
|
|
228
|
-
```
|
|
229
|
-
[paramCheck] 校验参数:id = 1
|
|
230
|
-
[logger] 方法getUser开始执行,参数: [1]
|
|
231
|
-
获取用户信息,ID:1
|
|
232
|
-
[logger] 方法getUser执行完成,结果: { id: 1, name: '张三', age: 20 }
|
|
233
|
-
最终结果: { id: 1, name: '张三', age: 20 }
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
### 执行结果(testError)
|
|
237
|
-
```
|
|
238
|
-
[paramCheck] 校验参数:id = 0
|
|
239
|
-
[logger] 方法getUser开始执行,参数: [0]
|
|
240
|
-
获取用户信息,ID:0
|
|
241
|
-
[logger] 方法getUser执行异常: 用户ID不能为0
|
|
242
|
-
[errorHandler] 捕获异常:用户ID不能为0,执行兜底逻辑
|
|
243
|
-
测试异常: 用户ID不能为0
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
## 切面函数上下文参数说明
|
|
247
|
-
切面函数的入参是一个上下文对象,包含以下字段:
|
|
248
|
-
|
|
249
|
-
| 字段 | 类型 | 说明 | 可用切面类型 |
|
|
250
|
-
|------|------|------|--------------|
|
|
251
|
-
| ctx | object | 原方法的执行上下文(实例对象) | all |
|
|
252
|
-
| methodName | string | 被包装的方法名称 | all |
|
|
253
|
-
| args | Array | 原方法的入参数组(浅拷贝) | all |
|
|
254
|
-
| originalMethod | Function | 原始未包装的方法 | all |
|
|
255
|
-
| result | any | 原方法执行结果 | after |
|
|
256
|
-
| error | Error | 原方法抛出的异常 | error |
|
|
257
|
-
| params | object | 切面规则中配置的自定义参数 | all |
|
|
258
|
-
|
|
259
|
-
## 注意事项
|
|
260
|
-
1. **异步支持**:切面函数和被包装的方法均支持异步(async/await),内部会自动处理异步执行顺序
|
|
261
|
-
2. **上下文保留**:原方法的 `this` 指向会被正确保留,无需额外处理
|
|
262
|
-
3. **异常处理**:`error` 类型切面执行完成后,异常会被重新抛出,不会阻断原有异常流程
|
|
263
|
-
4. **规则过滤**:`enabled: false` 的规则会被自动过滤,不执行对应的切面
|
|
264
|
-
5. **切面名称冲突**:不要使用 `type`/`enabled` 作为切面名称(内置关键字)
|
|
265
|
-
|
|
266
|
-
### 总结
|
|
267
|
-
1. `Aop` 类核心提供切面注册(`set`)和方法包装(`wrap`)能力,仅支持 `before`/`after`/`error` 三种切面类型。
|
|
268
|
-
2. 切面函数接收包含上下文、参数、结果/异常的完整入参,可灵活实现各类横切逻辑。
|
|
269
|
-
3. 通过配置规则的 `enabled` 字段可动态控制切面是否生效,异常切面执行后会重新抛出异常,保证原有错误流程不受影响。
|
package/doc/Email.md
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
# 邮件发送工具模块文档
|
|
2
|
-
## 1. 模块概述
|
|
3
|
-
该模块基于 `nodemailer` 实现邮件发送功能,并提供注册验证码、重置密码验证码两类邮件的HTML模板生成能力,适用于系统中的邮件通知场景(如用户注册、密码重置)。
|
|
4
|
-
|
|
5
|
-
## 2. 依赖
|
|
6
|
-
- 第三方库:`nodemailer`(需提前安装)
|
|
7
|
-
- 全局配置:`Chan.config` 需包含 `EMAIL`(邮件服务配置)和 `APP_NAME`(应用名称)字段
|
|
8
|
-
|
|
9
|
-
## 3. 配置说明
|
|
10
|
-
`Chan.config.EMAIL` 需包含以下配置项:
|
|
11
|
-
|
|
12
|
-
| 配置项 | 类型 | 说明 |
|
|
13
|
-
|--------|------|------|
|
|
14
|
-
| HOST | string | 邮件服务器主机地址(如 smtp.qq.com) |
|
|
15
|
-
| PORT | string/number | 邮件服务器端口(如 465、587) |
|
|
16
|
-
| SECURE | string | 是否启用SSL加密("true" 或 "false") |
|
|
17
|
-
| USER | string | 发件人邮箱账号 |
|
|
18
|
-
| PASS | string | 发件人邮箱授权码/密码 |
|
|
19
|
-
| FROM | string | 发件人显示格式(如 "应用名称 <xxx@xxx.com>") |
|
|
20
|
-
|
|
21
|
-
## 4. API 详情
|
|
22
|
-
### 4.1 sendMail - 发送邮件
|
|
23
|
-
#### 功能描述
|
|
24
|
-
创建邮件传输器,验证邮件服务配置后发送邮件,支持纯文本和HTML格式内容。
|
|
25
|
-
|
|
26
|
-
#### 函数签名
|
|
27
|
-
```javascript
|
|
28
|
-
async function sendMail(to, subject, text, html = null) => Promise<Object>
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
#### 参数说明
|
|
32
|
-
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
|
33
|
-
|--------|------|------|--------|------|
|
|
34
|
-
| to | string | 是 | - | 收件人邮箱地址 |
|
|
35
|
-
| subject | string | 是 | - | 邮件主题 |
|
|
36
|
-
| text | string | 是 | - | 邮件纯文本内容 |
|
|
37
|
-
| html | string \| null | 否 | null | 邮件HTML内容,未传则使用text内容替代 |
|
|
38
|
-
|
|
39
|
-
#### 返回值
|
|
40
|
-
`Promise<Object>`:nodemailer 发送邮件后的响应对象(包含 messageId 等信息)。
|
|
41
|
-
|
|
42
|
-
#### 异常抛出
|
|
43
|
-
- 邮件服务配置验证失败:抛出 `Error("邮件服务未配置")`
|
|
44
|
-
- 邮件发送失败:打印错误日志并抛出原错误对象
|
|
45
|
-
|
|
46
|
-
#### 使用示例
|
|
47
|
-
```javascript
|
|
48
|
-
import { sendMail, genRegEmailHtml } from './chanjs/common/email.js';
|
|
49
|
-
|
|
50
|
-
// 发送注册验证码邮件
|
|
51
|
-
async function sendRegCodeEmail(email, code) {
|
|
52
|
-
const subject = `${Chan.config.APP_NAME} 注册验证码`;
|
|
53
|
-
const text = `您的注册验证码是:${code},有效期10分钟。`;
|
|
54
|
-
const html = genRegEmailHtml(code);
|
|
55
|
-
|
|
56
|
-
try {
|
|
57
|
-
const result = await sendMail(email, subject, text, html);
|
|
58
|
-
console.log("邮件发送成功:", result.messageId);
|
|
59
|
-
} catch (error) {
|
|
60
|
-
console.error("发送失败:", error);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
### 4.2 genRegEmailHtml - 生成注册验证码邮件HTML模板
|
|
66
|
-
#### 功能描述
|
|
67
|
-
生成美观的注册验证码邮件HTML字符串,包含应用名称、验证码、有效期等信息。
|
|
68
|
-
|
|
69
|
-
#### 函数签名
|
|
70
|
-
```javascript
|
|
71
|
-
function genRegEmailHtml(code, minutes = 10) => string
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
#### 参数说明
|
|
75
|
-
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
|
76
|
-
|--------|------|------|--------|------|
|
|
77
|
-
| code | string | 是 | - | 注册验证码 |
|
|
78
|
-
| minutes | number | 否 | 10 | 验证码有效期(分钟) |
|
|
79
|
-
|
|
80
|
-
#### 返回值
|
|
81
|
-
`string`:完整的邮件HTML字符串。
|
|
82
|
-
|
|
83
|
-
### 4.3 genResetPasswordEmail - 生成重置密码邮件HTML模板
|
|
84
|
-
#### 功能描述
|
|
85
|
-
生成重置密码验证码的邮件HTML字符串,样式与注册邮件区分,突出重置密码场景。
|
|
86
|
-
|
|
87
|
-
#### 函数签名
|
|
88
|
-
```javascript
|
|
89
|
-
function genResetPasswordEmail(code, minutes = 10) => string
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
#### 参数说明
|
|
93
|
-
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
|
94
|
-
|--------|------|------|--------|------|
|
|
95
|
-
| code | string | 是 | - | 重置密码验证码 |
|
|
96
|
-
| minutes | number | 否 | 10 | 验证码有效期(分钟) |
|
|
97
|
-
|
|
98
|
-
#### 返回值
|
|
99
|
-
`string`:完整的邮件HTML字符串。
|
|
100
|
-
|
|
101
|
-
## 5. 模板样式说明
|
|
102
|
-
- 注册邮件模板:头部背景色为蓝色(#007bff),最大宽度750px,整体风格简洁清晰。
|
|
103
|
-
- 重置密码邮件模板:头部背景色为绿色(#28a745),最大宽度600px,布局与注册邮件一致,文案适配重置密码场景。
|
|
104
|
-
- 两类模板均包含:应用名称、验证码醒目展示、有效期提示、版权信息,适配主流邮箱的HTML渲染规则。
|
|
105
|
-
|
|
106
|
-
## 6. 异常处理建议
|
|
107
|
-
1. 调用 `sendMail` 时务必使用 `try/catch` 捕获异常,避免程序崩溃;
|
|
108
|
-
2. 邮件服务配置错误(如HOST/PORT错误)会触发“邮件服务未配置”异常,需检查 `Chan.config.EMAIL` 配置;
|
|
109
|
-
3. 发送失败(如收件人邮箱格式错误、服务器拒绝)需记录详细错误日志,便于排查问题。
|
|
110
|
-
|
|
111
|
-
## 7. 扩展建议
|
|
112
|
-
1. 可新增更多邮件模板(如通知类、营销类),参考现有模板结构封装;
|
|
113
|
-
2. 可添加邮件发送重试机制,提升稳定性;
|
|
114
|
-
3. 可将模板样式抽离为配置项,支持自定义主题色、模板宽度等。
|
package/doc/Event.md
DELETED
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
# chanjs Event 类使用文档
|
|
2
|
-
## 概述
|
|
3
|
-
`chanjs` 中的 `Event` 类基于 Node.js 内置 `EventEmitter` 封装,提供轻量级的事件订阅/发布能力,支持事件注册、触发、注销等核心操作,接口简洁且保持与原生 `EventEmitter` 兼容,适用于业务中解耦组件间的通信场景。
|
|
4
|
-
|
|
5
|
-
## 前置准备
|
|
6
|
-
### 1. 引入方式
|
|
7
|
-
```javascript
|
|
8
|
-
import { Event } from "chanjs";
|
|
9
|
-
|
|
10
|
-
// 方式1:创建实例使用
|
|
11
|
-
const event = new Event();
|
|
12
|
-
|
|
13
|
-
// 方式2:直接使用内置单例(推荐全局通信)
|
|
14
|
-
import { event } from "chanjs";
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
### 2. 通用约定
|
|
18
|
-
- 事件名(`eventName`)建议使用**语义化字符串**(如 `user:login`、`order:created`),避免命名冲突
|
|
19
|
-
- 监听器函数(`listener`)接收的参数为触发事件时传入的 `data`,支持任意类型数据
|
|
20
|
-
- 所有方法均返回当前 `Event` 实例,支持**链式调用**
|
|
21
|
-
|
|
22
|
-
## 核心方法使用指南
|
|
23
|
-
### 1. 注册事件监听器 - on
|
|
24
|
-
#### 作用
|
|
25
|
-
为指定事件注册一个持久化的监听器(事件触发时会执行该函数),支持为同一事件注册多个监听器。
|
|
26
|
-
#### 传参格式
|
|
27
|
-
| 参数 | 类型 | 必填 | 说明 |
|
|
28
|
-
|------|------|------|------|
|
|
29
|
-
| eventName | String | 是 | 事件名称(如 `user:login`) |
|
|
30
|
-
| listener | Function | 是 | 事件触发时执行的回调函数,参数为触发事件传入的 `data` |
|
|
31
|
-
#### 使用示例
|
|
32
|
-
```javascript
|
|
33
|
-
// 注册单个事件监听器
|
|
34
|
-
event.on("user:login", (data) => {
|
|
35
|
-
console.log("用户登录事件触发:", data);
|
|
36
|
-
// 业务逻辑:记录登录日志、更新最后登录时间等
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
// 为同一事件注册多个监听器
|
|
40
|
-
event.on("order:created", (orderData) => {
|
|
41
|
-
console.log("订单创建-日志记录:", orderData.id);
|
|
42
|
-
});
|
|
43
|
-
event.on("order:created", (orderData) => {
|
|
44
|
-
console.log("订单创建-消息推送:", orderData.userId);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
// 链式调用注册多个事件
|
|
48
|
-
event
|
|
49
|
-
.on("article:add", (article) => console.log("新增文章:", article.title))
|
|
50
|
-
.on("article:delete", (id) => console.log("删除文章ID:", id));
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
### 2. 触发事件 - emit
|
|
54
|
-
#### 作用
|
|
55
|
-
触发指定名称的事件,执行该事件下所有已注册的监听器,并传递数据给监听器函数。
|
|
56
|
-
#### 传参格式
|
|
57
|
-
| 参数 | 类型 | 必填 | 说明 |
|
|
58
|
-
|------|------|------|------|
|
|
59
|
-
| eventName | String | 是 | 要触发的事件名称 |
|
|
60
|
-
| data | Any | 否 | 传递给监听器的事件数据(任意类型:对象、数组、基本类型等) |
|
|
61
|
-
#### 使用示例
|
|
62
|
-
```javascript
|
|
63
|
-
// 触发用户登录事件,传递用户数据
|
|
64
|
-
event.emit("user:login", {
|
|
65
|
-
userId: 1001,
|
|
66
|
-
username: "test_user",
|
|
67
|
-
loginTime: new Date(),
|
|
68
|
-
ip: "127.0.0.1"
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
// 触发订单创建事件,传递订单数据
|
|
72
|
-
const orderData = {
|
|
73
|
-
id: "ORD20260323001",
|
|
74
|
-
userId: 1001,
|
|
75
|
-
amount: 99.9,
|
|
76
|
-
createTime: new Date()
|
|
77
|
-
};
|
|
78
|
-
event.emit("order:created", orderData);
|
|
79
|
-
|
|
80
|
-
// 触发无数据的事件
|
|
81
|
-
event.emit("system:refresh");
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
### 3. 注销事件监听器 - off
|
|
85
|
-
#### 作用
|
|
86
|
-
注销指定事件下的某个具体监听器,仅移除该监听器,不影响同一事件的其他监听器。
|
|
87
|
-
#### 传参格式
|
|
88
|
-
| 参数 | 类型 | 必填 | 说明 |
|
|
89
|
-
|------|------|------|------|
|
|
90
|
-
| eventName | String | 是 | 事件名称 |
|
|
91
|
-
| listener | Function | 是 | 要注销的监听器函数(需与注册时的函数引用一致) |
|
|
92
|
-
#### 使用示例
|
|
93
|
-
```javascript
|
|
94
|
-
// 定义可复用的监听器函数
|
|
95
|
-
const loginListener = (data) => {
|
|
96
|
-
console.log("用户登录监听器:", data);
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
// 注册监听器
|
|
100
|
-
event.on("user:login", loginListener);
|
|
101
|
-
|
|
102
|
-
// 触发事件(监听器会执行)
|
|
103
|
-
event.emit("user:login", { userId: 1001 });
|
|
104
|
-
|
|
105
|
-
// 注销指定监听器
|
|
106
|
-
event.off("user:login", loginListener);
|
|
107
|
-
|
|
108
|
-
// 再次触发事件(该监听器不再执行)
|
|
109
|
-
event.emit("user:login", { userId: 1001 });
|
|
110
|
-
|
|
111
|
-
// 链式注销多个监听器
|
|
112
|
-
const articleAddListener = (data) => console.log("新增文章:", data);
|
|
113
|
-
const articleDelListener = (id) => console.log("删除文章:", id);
|
|
114
|
-
|
|
115
|
-
event
|
|
116
|
-
.on("article:add", articleAddListener)
|
|
117
|
-
.on("article:delete", articleDelListener)
|
|
118
|
-
.off("article:add", articleAddListener)
|
|
119
|
-
.off("article:delete", articleDelListener);
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
### 4. 注销所有事件监听器 - removeAllListeners
|
|
123
|
-
#### 作用
|
|
124
|
-
注销指定事件下的**所有**监听器,或注销所有事件的所有监听器(不传参数时)。
|
|
125
|
-
#### 传参格式
|
|
126
|
-
| 参数 | 类型 | 必填 | 说明 |
|
|
127
|
-
|------|------|------|------|
|
|
128
|
-
| eventName | String | 否 | 事件名称(不传则注销所有事件的监听器) |
|
|
129
|
-
#### 使用示例
|
|
130
|
-
```javascript
|
|
131
|
-
// 注册多个监听器
|
|
132
|
-
event
|
|
133
|
-
.on("order:created", (data) => console.log("监听器1:", data))
|
|
134
|
-
.on("order:created", (data) => console.log("监听器2:", data))
|
|
135
|
-
.on("user:login", (data) => console.log("登录监听器:", data));
|
|
136
|
-
|
|
137
|
-
// 注销order:created事件的所有监听器
|
|
138
|
-
event.removeAllListeners("order:created");
|
|
139
|
-
// 触发该事件(无监听器执行)
|
|
140
|
-
event.emit("order:created", { id: "ORD001" });
|
|
141
|
-
|
|
142
|
-
// 注销所有事件的所有监听器(全局清空)
|
|
143
|
-
event.removeAllListeners();
|
|
144
|
-
// 触发任何事件都无监听器执行
|
|
145
|
-
event.emit("user:login", { userId: 1001 });
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
## 高级使用场景
|
|
149
|
-
### 场景1:一次性事件监听(扩展)
|
|
150
|
-
原生 `EventEmitter` 支持 `once` 方法(注册仅执行一次的监听器),`Event` 类继承该能力,可直接使用:
|
|
151
|
-
```javascript
|
|
152
|
-
// 注册仅执行一次的监听器
|
|
153
|
-
event.once("config:update", (config) => {
|
|
154
|
-
console.log("配置更新(仅执行一次):", config);
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
// 第一次触发(监听器执行)
|
|
158
|
-
event.emit("config:update", { theme: "dark" });
|
|
159
|
-
// 第二次触发(监听器已自动注销,不执行)
|
|
160
|
-
event.emit("config:update", { theme: "light" });
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
### 场景2:业务模块解耦示例
|
|
164
|
-
```javascript
|
|
165
|
-
// 模块A:用户模块
|
|
166
|
-
import { event } from "chanjs";
|
|
167
|
-
|
|
168
|
-
class UserModule {
|
|
169
|
-
async login(username, password) {
|
|
170
|
-
// 登录逻辑
|
|
171
|
-
const user = { id: 1001, username };
|
|
172
|
-
// 触发登录事件,无需关心其他模块逻辑
|
|
173
|
-
event.emit("user:login", user);
|
|
174
|
-
return user;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// 模块B:日志模块
|
|
179
|
-
import { event } from "chanjs";
|
|
180
|
-
|
|
181
|
-
class LogModule {
|
|
182
|
-
constructor() {
|
|
183
|
-
// 监听登录事件,记录日志
|
|
184
|
-
event.on("user:login", (user) => {
|
|
185
|
-
console.log(`[LOG] 用户${user.username}(${user.id})于${new Date()}登录`);
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// 模块C:消息模块
|
|
191
|
-
import { event } from "chanjs";
|
|
192
|
-
|
|
193
|
-
class MessageModule {
|
|
194
|
-
constructor() {
|
|
195
|
-
// 监听登录事件,推送消息
|
|
196
|
-
event.on("user:login", (user) => {
|
|
197
|
-
console.log(`[MSG] 向用户${user.id}推送登录成功消息`);
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// 业务入口
|
|
203
|
-
const userModule = new UserModule();
|
|
204
|
-
const logModule = new LogModule();
|
|
205
|
-
const messageModule = new MessageModule();
|
|
206
|
-
|
|
207
|
-
// 执行登录,自动触发日志和消息模块的逻辑
|
|
208
|
-
await userModule.login("test_user", "123456");
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
### 场景3:错误事件处理
|
|
212
|
-
```javascript
|
|
213
|
-
// 注册全局错误事件监听器
|
|
214
|
-
event.on("error", (err) => {
|
|
215
|
-
console.error("全局事件错误:", err);
|
|
216
|
-
// 业务逻辑:错误上报、告警等
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
// 触发错误事件(建议使用Error对象传递错误信息)
|
|
220
|
-
event.emit("error", new Error("订单创建失败:库存不足"));
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
## 注意事项
|
|
224
|
-
1. 注销监听器时,`off` 方法传入的 `listener` 必须与 `on` 注册时的函数**引用一致**,匿名函数无法被注销;
|
|
225
|
-
2. 事件名建议使用 `模块:动作` 的命名规范(如 `user:login`、`order:created`),避免全局命名冲突;
|
|
226
|
-
3. 若监听器函数执行过程中抛出异常,不会阻断其他监听器执行,但需自行捕获异常避免程序崩溃;
|
|
227
|
-
4. 单例 `event` 适用于全局通信,若需隔离事件作用域,可创建多个 `Event` 实例(`new Event()`)。
|
|
228
|
-
|
|
229
|
-
## 总结
|
|
230
|
-
1. `Event` 类核心提供 `on`(注册)、`emit`(触发)、`off`(注销)、`removeAllListeners`(清空)四个方法,覆盖事件通信全流程;
|
|
231
|
-
2. 基于 Node.js `EventEmitter` 实现,兼容原生方法(如 `once`),接口简洁易上手;
|
|
232
|
-
3. 适用于业务模块解耦场景,通过事件发布/订阅模式减少模块间直接依赖,提升代码可维护性。
|
package/global/env.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import dotenv from "dotenv";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import { Paths } from "../config/paths.js";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* 环境变量配置加载
|
|
7
|
-
* 从.env文件加载环境变量到process.env
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
const envFile = process.env.ENV_FILE || ".env.prd";
|
|
11
|
-
dotenv.config({ path: path.join(Paths.rootPath, envFile) });
|
package/global/import.js
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import fs from "fs/promises";
|
|
2
|
-
import { pathToFileURL } from "url";
|
|
3
|
-
import { createRequire } from "module";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* 动态导入文件
|
|
7
|
-
* @param {string} filepath - 文件路径
|
|
8
|
-
* @returns {Promise<Module|null>} 模块对象,失败返回null
|
|
9
|
-
*/
|
|
10
|
-
const importFile = async (filepath) => {
|
|
11
|
-
if (!filepath || typeof filepath !== "string") {
|
|
12
|
-
console.error("错误: 文件路径必须是有效的字符串");
|
|
13
|
-
return null;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
try {
|
|
17
|
-
await fs.access(filepath);
|
|
18
|
-
const fileUrl = pathToFileURL(filepath).href;
|
|
19
|
-
const module = await import(fileUrl);
|
|
20
|
-
return module.default || module;
|
|
21
|
-
} catch (error) {
|
|
22
|
-
if (error.code === "ENOENT") {
|
|
23
|
-
console.error(`文件不存在: ${filepath}`);
|
|
24
|
-
} else if (error.code === "EACCES") {
|
|
25
|
-
console.error(`没有权限访问文件: ${filepath}`);
|
|
26
|
-
} else {
|
|
27
|
-
console.error(`导入文件时出错 [${filepath}]:`, error.message);
|
|
28
|
-
}
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* CommonJS风格的require函数
|
|
35
|
-
* 用于在ES模块中加载CommonJS模块
|
|
36
|
-
*/
|
|
37
|
-
export const importjs = createRequire(import.meta.url);
|
|
38
|
-
|
|
39
|
-
export { importFile };
|