chanjs 2.6.3 → 2.6.5
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 +1 -1
- package/base/Container.js +6 -3
- package/base/Controller.js +2 -1
- package/base/Service.js +20 -1
- package/common/index.js +0 -2
- package/doc/Aop.md +269 -0
- package/doc/Cache.md +160 -0
- package/doc/Common.md +182 -0
- package/doc/Controller.md +152 -0
- package/doc/Email.md +114 -0
- package/doc/Event.md +232 -0
- package/doc/Help.md +789 -0
- package/doc/Service.md +566 -0
- package/helper/cache.js +1 -0
- package/helper/index.js +71 -22
- package/index.js +0 -1
- package/middleware/waf.js +5 -6
- package/package.json +1 -1
- package//345/275/222/346/241/243.zip +0 -0
- package/utils/index.js +0 -4
- /package/{utils → helper}/checker.js +0 -0
- /package/{utils → helper}/keywords.js +0 -0
- /package/{utils → helper}/rate-limit.js +0 -0
- /package/{utils → helper}/response.js +0 -0
- /package/{utils → helper}/xss-filter.js +0 -0
package/doc/Common.md
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# chanjs/common/index.js 模块文档
|
|
2
|
+
## 概述
|
|
3
|
+
`chanjs/common/index.js` 是 `chanjs` 框架的通用工具模块出口文件,聚合了 API 响应处理、分类操作、状态码、邮件、页面处理、短信、通用工具等多个子模块的核心方法/常量,提供统一的导出入口,简化模块引用流程。
|
|
4
|
+
|
|
5
|
+
## 导出内容总览
|
|
6
|
+
该文件通过 ES6 导出语法,批量导出以下子模块的核心成员:
|
|
7
|
+
|
|
8
|
+
| 导出来源文件 | 导出成员 | 功能分类 |
|
|
9
|
+
|--------------|----------|----------|
|
|
10
|
+
| `./api.js` | `success`, `fail`, `error` | API 响应格式化 |
|
|
11
|
+
| `./category.js` | `getChildrenId` | 分类数据处理 |
|
|
12
|
+
| `./code.js` | `CODE` | 业务状态码常量 |
|
|
13
|
+
| `./email.js` | `sendMail`, `genRegEmailHtml`, `genResetPasswordEmail` | 邮件发送与模板生成 |
|
|
14
|
+
| `./pages.js` | `pages`, `getHtmlFilesSync` | 页面/HTML 文件处理 |
|
|
15
|
+
| `./sms.js` | `createSmsClient` | 短信客户端创建 |
|
|
16
|
+
| `./utils.js` | `filterBody`, `pc`, `filterImgFromStr` | 通用工具函数 |
|
|
17
|
+
|
|
18
|
+
## 详细导出成员说明
|
|
19
|
+
### 1. 来自 `./api.js`:API 响应格式化
|
|
20
|
+
| 成员名 | 类型 | 说明 |
|
|
21
|
+
|--------|------|------|
|
|
22
|
+
| `success` | 函数 | 生成「成功」类型的 API 响应数据(如包含 `code: 200`、`data`、`msg` 等字段) |
|
|
23
|
+
| `fail` | 函数 | 生成「业务失败」类型的 API 响应数据(如包含 `code: 非200`、`msg` 等字段,适用于参数错误、业务逻辑失败等场景) |
|
|
24
|
+
| `error` | 函数 | 生成「系统错误」类型的 API 响应数据(如包含 `code: 500`、`msg` 等字段,适用于服务器内部异常场景) |
|
|
25
|
+
|
|
26
|
+
**使用示例**:
|
|
27
|
+
```javascript
|
|
28
|
+
import { success, fail } from 'chanjs/common';
|
|
29
|
+
|
|
30
|
+
// 接口返回成功响应
|
|
31
|
+
export const getUser = (req, res) => {
|
|
32
|
+
res.json(success({ name: '张三' }, '获取用户信息成功'));
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// 接口返回业务失败响应
|
|
36
|
+
export const login = (req, res) => {
|
|
37
|
+
res.json(fail('用户名或密码错误', 401));
|
|
38
|
+
};
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 2. 来自 `./category.js`:分类数据处理
|
|
42
|
+
| 成员名 | 类型 | 说明 |
|
|
43
|
+
|--------|------|------|
|
|
44
|
+
| `getChildrenId` | 函数 | 根据分类 ID,递归获取该分类下所有子分类的 ID 集合(适用于树形结构分类场景,如商品分类、栏目分类) |
|
|
45
|
+
|
|
46
|
+
**使用示例**:
|
|
47
|
+
```javascript
|
|
48
|
+
import { getChildrenId } from 'chanjs/common';
|
|
49
|
+
|
|
50
|
+
// 假设分类数据为树形结构
|
|
51
|
+
const categoryList = [
|
|
52
|
+
{ id: 1, name: '数码', children: [{ id: 11, name: '手机' }, { id: 12, name: '电脑' }] },
|
|
53
|
+
{ id: 2, name: '服饰' }
|
|
54
|
+
];
|
|
55
|
+
// 获取分类1下所有子分类ID
|
|
56
|
+
const childrenIds = getChildrenId(categoryList, 1);
|
|
57
|
+
console.log(childrenIds); // [11, 12]
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 3. 来自 `./code.js`:业务状态码常量
|
|
61
|
+
| 成员名 | 类型 | 说明 |
|
|
62
|
+
|--------|------|------|
|
|
63
|
+
| `CODE` | 对象 | 聚合了项目中所有业务状态码的常量对象(如 `CODE.SUCCESS = 200`、`CODE.PARAM_ERROR = 400`、`CODE.LOGIN_EXPIRE = 401` 等),统一管理状态码,避免硬编码 |
|
|
64
|
+
|
|
65
|
+
**使用示例**:
|
|
66
|
+
```javascript
|
|
67
|
+
import { CODE, fail } from 'chanjs/common';
|
|
68
|
+
|
|
69
|
+
export const submitForm = (req, res) => {
|
|
70
|
+
if (!req.body.name) {
|
|
71
|
+
// 使用统一状态码返回失败响应
|
|
72
|
+
return res.json(fail('姓名不能为空', CODE.PARAM_ERROR));
|
|
73
|
+
}
|
|
74
|
+
// 业务逻辑处理...
|
|
75
|
+
};
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 4. 来自 `./email.js`:邮件发送与模板生成
|
|
79
|
+
| 成员名 | 类型 | 说明 |
|
|
80
|
+
|--------|------|------|
|
|
81
|
+
| `sendMail` | 函数 | 封装邮件发送逻辑,接收收件人、邮件标题、邮件内容等参数,调用底层邮件服务发送邮件 |
|
|
82
|
+
| `genRegEmailHtml` | 函数 | 生成「用户注册验证」的邮件 HTML 模板(包含验证链接/验证码等动态内容) |
|
|
83
|
+
| `genResetPasswordEmail` | 函数 | 生成「密码重置」的邮件 HTML 模板(包含重置链接/验证码等动态内容) |
|
|
84
|
+
|
|
85
|
+
**使用示例**:
|
|
86
|
+
```javascript
|
|
87
|
+
import { sendMail, genRegEmailHtml } from 'chanjs/common';
|
|
88
|
+
|
|
89
|
+
// 发送注册验证邮件
|
|
90
|
+
const sendRegEmail = async (toEmail, verifyCode) => {
|
|
91
|
+
const html = genRegEmailHtml(verifyCode);
|
|
92
|
+
await sendMail({
|
|
93
|
+
to: toEmail,
|
|
94
|
+
subject: '【XX平台】注册验证',
|
|
95
|
+
html
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 5. 来自 `./pages.js`:页面/HTML 文件处理
|
|
101
|
+
| 成员名 | 类型 | 说明 |
|
|
102
|
+
|--------|------|------|
|
|
103
|
+
| `pages` | 对象/函数 | 页面相关配置/工具(如页面路由映射、页面模板配置等,具体需结合子模块实现) |
|
|
104
|
+
| `getHtmlFilesSync` | 函数 | 同步读取指定目录下所有 HTML 文件的路径/内容,返回文件列表(适用于静态页面生成、模板扫描等场景) |
|
|
105
|
+
|
|
106
|
+
**使用示例**:
|
|
107
|
+
```javascript
|
|
108
|
+
import { getHtmlFilesSync } from 'chanjs/common';
|
|
109
|
+
|
|
110
|
+
// 获取指定目录下所有HTML文件
|
|
111
|
+
const htmlFiles = getHtmlFilesSync('./src/pages');
|
|
112
|
+
console.log(htmlFiles); // ['./src/pages/index.html', './src/pages/about.html']
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### 6. 来自 `./sms.js`:短信客户端创建
|
|
116
|
+
| 成员名 | 类型 | 说明 |
|
|
117
|
+
|--------|------|------|
|
|
118
|
+
| `createSmsClient` | 函数 | 创建并返回短信服务客户端实例(封装了短信服务商 SDK,如阿里云短信、腾讯云短信等),支持配置密钥、签名等参数 |
|
|
119
|
+
|
|
120
|
+
**使用示例**:
|
|
121
|
+
```javascript
|
|
122
|
+
import { createSmsClient } from 'chanjs/common';
|
|
123
|
+
|
|
124
|
+
// 创建短信客户端
|
|
125
|
+
const smsClient = createSmsClient({
|
|
126
|
+
accessKeyId: 'your-access-key',
|
|
127
|
+
accessKeySecret: 'your-access-secret',
|
|
128
|
+
signName: 'XX平台'
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// 发送短信验证码
|
|
132
|
+
smsClient.sendSms({
|
|
133
|
+
phoneNumbers: '13800138000',
|
|
134
|
+
templateCode: 'SMS_123456789',
|
|
135
|
+
templateParam: { code: '668899' }
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### 7. 来自 `./utils.js`:通用工具函数
|
|
140
|
+
| 成员名 | 类型 | 说明 |
|
|
141
|
+
|--------|------|------|
|
|
142
|
+
| `filterBody` | 函数 | 过滤请求体(req.body)中的敏感字段/空值字段,返回清洗后的对象(适用于接口入参校验,避免无效/敏感数据传入业务层) |
|
|
143
|
+
| `pc` | 函数/对象 | 设备判断工具,通常用于检测请求是否来自 PC 端(如返回 `true/false`,或包含 `isPc`、`isMobile` 等方法) |
|
|
144
|
+
| `filterImgFromStr` | 函数 | 从字符串(如 HTML 文本、富文本内容)中提取所有图片 URL,返回图片链接数组 |
|
|
145
|
+
|
|
146
|
+
**使用示例**:
|
|
147
|
+
```javascript
|
|
148
|
+
import { filterBody, pc, filterImgFromStr } from 'chanjs/common';
|
|
149
|
+
|
|
150
|
+
// 过滤请求体空值
|
|
151
|
+
const cleanBody = filterBody(req.body, ['name', 'age']); // 仅保留name、age字段,且过滤空值
|
|
152
|
+
|
|
153
|
+
// 判断是否为PC端请求
|
|
154
|
+
if (pc(req.headers['user-agent'])) {
|
|
155
|
+
console.log('PC端访问');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// 提取富文本中的图片
|
|
159
|
+
const content = '<p>测试<img src="https://example.com/1.jpg">内容<img src="https://example.com/2.jpg"></p>';
|
|
160
|
+
const imgUrls = filterImgFromStr(content);
|
|
161
|
+
console.log(imgUrls); // ['https://example.com/1.jpg', 'https://example.com/2.jpg']
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## 引用方式
|
|
165
|
+
### 方式1:按需导入(推荐)
|
|
166
|
+
```javascript
|
|
167
|
+
import { success, CODE, filterBody } from 'chanjs/common';
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### 方式2:批量导入
|
|
171
|
+
```javascript
|
|
172
|
+
import * as common from 'chanjs/common';
|
|
173
|
+
|
|
174
|
+
// 使用时通过 common.xxx 访问
|
|
175
|
+
common.success(data, '操作成功');
|
|
176
|
+
common.filterBody(req.body);
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## 注意事项
|
|
180
|
+
1. 该文件仅为「导出入口」,无实际业务逻辑,所有功能的具体实现均在对应的子模块(`api.js`/`category.js` 等)中;
|
|
181
|
+
2. 若需修改/扩展功能,需修改对应的子模块文件,而非直接修改 `index.js`;
|
|
182
|
+
3. 导入时确保子模块文件路径正确,避免因路径变更导致导入失败。
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# Controller 控制器基类
|
|
2
|
+
## 概述
|
|
3
|
+
`Controller` 是所有业务控制器的基类,继承自 `Container` 容器类,核心作用是提供统一的响应格式封装(成功/失败响应),规范控制器层的返回数据结构,减少重复代码,提升开发效率。
|
|
4
|
+
|
|
5
|
+
## 依赖说明
|
|
6
|
+
| 依赖模块 | 路径 | 说明 |
|
|
7
|
+
|----------|------|------|
|
|
8
|
+
| `success`/`fail` | `../helper/response.js` | 响应格式封装工具函数,提供标准化的成功/失败响应结构 |
|
|
9
|
+
| `Container` | `./Container.js` | 基础容器类,为控制器提供组件类型标识等能力 |
|
|
10
|
+
|
|
11
|
+
## 类继承关系
|
|
12
|
+
```
|
|
13
|
+
Container <|-- Controller
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## 构造函数
|
|
17
|
+
### 语法
|
|
18
|
+
```javascript
|
|
19
|
+
constructor()
|
|
20
|
+
```
|
|
21
|
+
### 说明
|
|
22
|
+
调用父类 `Container` 的构造函数,并指定组件类型为 `'controller'`,用于容器类对控制器组件的统一管理。
|
|
23
|
+
|
|
24
|
+
### 示例
|
|
25
|
+
```javascript
|
|
26
|
+
import Controller from './chanjs/base/Controller.js';
|
|
27
|
+
|
|
28
|
+
class UserController extends Controller {
|
|
29
|
+
constructor() {
|
|
30
|
+
super(); // 继承 Controller 构造逻辑
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## 核心方法
|
|
36
|
+
### 1. 成功响应 - `success(options)`
|
|
37
|
+
封装标准化的成功响应格式,返回统一结构的成功数据。
|
|
38
|
+
|
|
39
|
+
#### 参数说明
|
|
40
|
+
| 参数名 | 类型 | 必传 | 默认值 | 说明 |
|
|
41
|
+
|--------|------|------|--------|------|
|
|
42
|
+
| `options` | `Object` | 否 | `{}` | 响应配置项 |
|
|
43
|
+
| `options.data` | `any` | 否 | - | 响应体数据,可传任意类型(对象、数组、基本类型等) |
|
|
44
|
+
| `options.msg` | `string` | 否 | `"操作成功"` | 响应提示消息 |
|
|
45
|
+
|
|
46
|
+
#### 返回值
|
|
47
|
+
`Object`:标准化的成功响应对象(结构由 `../helper/response.js` 的 `success` 函数定义)。
|
|
48
|
+
|
|
49
|
+
#### 示例
|
|
50
|
+
```javascript
|
|
51
|
+
class UserController extends Controller {
|
|
52
|
+
async getUserInfo() {
|
|
53
|
+
const data = { id: 1, name: "张三" };
|
|
54
|
+
// 返回成功响应,使用默认提示语
|
|
55
|
+
return this.success({ data });
|
|
56
|
+
|
|
57
|
+
// 自定义提示语
|
|
58
|
+
// return this.success({ data, msg: "获取用户信息成功" });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 2. 失败响应 - `fail(options)`
|
|
64
|
+
封装标准化的失败响应格式,返回统一结构的失败数据。
|
|
65
|
+
|
|
66
|
+
#### 参数说明
|
|
67
|
+
| 参数名 | 类型 | 必传 | 默认值 | 说明 |
|
|
68
|
+
|--------|------|------|--------|------|
|
|
69
|
+
| `options` | `Object` | 否 | `{}` | 响应配置项 |
|
|
70
|
+
| `options.msg` | `string` | 否 | `"操作失败"` | 失败提示消息 |
|
|
71
|
+
| `options.data` | `any` | 否 | `{}` | 失败时附带的补充数据 |
|
|
72
|
+
| `options.code` | `number` | 否 | `201` | 自定义错误码,用于前端区分不同失败场景 |
|
|
73
|
+
|
|
74
|
+
#### 返回值
|
|
75
|
+
`Object`:标准化的失败响应对象(结构由 `../helper/response.js` 的 `fail` 函数定义)。
|
|
76
|
+
|
|
77
|
+
#### 示例
|
|
78
|
+
```javascript
|
|
79
|
+
class UserController extends Controller {
|
|
80
|
+
async updateUserInfo() {
|
|
81
|
+
try {
|
|
82
|
+
// 业务逻辑:更新用户信息失败
|
|
83
|
+
throw new Error("用户ID不存在");
|
|
84
|
+
} catch (err) {
|
|
85
|
+
// 返回失败响应,自定义提示语和错误码
|
|
86
|
+
return this.fail({
|
|
87
|
+
msg: err.message,
|
|
88
|
+
code: 400,
|
|
89
|
+
data: { userId: 1 } // 附带失败关联的用户ID
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// 使用默认配置
|
|
93
|
+
// return this.fail();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## 完整使用示例
|
|
100
|
+
```javascript
|
|
101
|
+
import Controller from './chanjs/base/Controller.js';
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 用户业务控制器
|
|
105
|
+
* 继承 Controller 基类,使用统一响应格式
|
|
106
|
+
*/
|
|
107
|
+
class UserController extends Controller {
|
|
108
|
+
constructor() {
|
|
109
|
+
super(); // 必须调用父类构造函数
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* 获取用户列表
|
|
114
|
+
* @returns {Object} 成功响应
|
|
115
|
+
*/
|
|
116
|
+
async getList() {
|
|
117
|
+
const list = [
|
|
118
|
+
{ id: 1, name: "张三" },
|
|
119
|
+
{ id: 2, name: "李四" }
|
|
120
|
+
];
|
|
121
|
+
return this.success({
|
|
122
|
+
data: list,
|
|
123
|
+
msg: "获取用户列表成功"
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* 删除用户
|
|
129
|
+
* @param {number} id - 用户ID
|
|
130
|
+
* @returns {Object} 成功/失败响应
|
|
131
|
+
*/
|
|
132
|
+
async delete(id) {
|
|
133
|
+
if (!id) {
|
|
134
|
+
return this.fail({
|
|
135
|
+
msg: "用户ID不能为空",
|
|
136
|
+
code: 401
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// 模拟删除成功
|
|
141
|
+
return this.success({ msg: `删除ID为${id}的用户成功` });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export default UserController;
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## 注意事项
|
|
149
|
+
1. 所有自定义控制器必须继承 `Controller` 基类,以保证响应格式的统一性;
|
|
150
|
+
2. `success`/`fail` 方法的参数为可选配置对象,未传参时会使用默认值;
|
|
151
|
+
3. 响应的最终数据结构由 `../helper/response.js` 中的 `success`/`fail` 函数决定,若需调整全局响应格式,建议修改该工具文件;
|
|
152
|
+
4. 错误码 `code` 可根据业务场景自定义扩展(如 400 代表参数错误、404 代表资源不存在等),建议与前端约定统一的错误码规范。
|
package/doc/Email.md
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
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
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
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. 适用于业务模块解耦场景,通过事件发布/订阅模式减少模块间直接依赖,提升代码可维护性。
|