chanjs 2.7.3 → 2.7.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/USAGE.md +533 -0
- package/config/index.js +37 -6
- package/core/App.js +166 -0
- package/core/Container.js +77 -0
- package/core/Controller.js +29 -0
- package/core/Database.js +93 -0
- package/core/Repository.js +327 -0
- package/core/Service.js +11 -0
- package/core/bootstrap/error-handler.js +104 -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 +224 -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 +75 -37
- package/middleware/body.js +17 -0
- package/middleware/cookie.js +7 -15
- package/middleware/cors.js +9 -27
- package/middleware/favicon.js +7 -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 +174 -197
- package/package.json +11 -3
- 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 +69 -142
- package/security/keywords.js +32 -136
- package/security/rate-limit.js +38 -80
- package/security/sign.js +83 -176
- package/security/xss-filter.js +21 -53
- package/storage/cache.js +57 -196
- package/storage/index.js +3 -6
- package/storage/redis.js +123 -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 +21 -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 +94 -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/USAGE.md
ADDED
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
# ChanJS 使用文档
|
|
2
|
+
|
|
3
|
+
> 基于 Express 5 的轻量级 Node.js MVC 框架
|
|
4
|
+
|
|
5
|
+
## 目录
|
|
6
|
+
|
|
7
|
+
- [快速开始](#快速开始)
|
|
8
|
+
- [项目结构](#项目结构)
|
|
9
|
+
- [配置说明](#配置说明)
|
|
10
|
+
- [Controller 控制器](#controller-控制器)
|
|
11
|
+
- [Service 服务层](#service-服务层)
|
|
12
|
+
- [Repository 数据仓库](#repository-数据仓库)
|
|
13
|
+
- [路由定义](#路由定义)
|
|
14
|
+
- [中间件](#中间件)
|
|
15
|
+
- [参数校验](#参数校验)
|
|
16
|
+
- [响应封装](#响应封装)
|
|
17
|
+
- [错误处理](#错误处理)
|
|
18
|
+
- [安全机制](#安全机制)
|
|
19
|
+
- [存储系统](#存储系统)
|
|
20
|
+
- [日志系统](#日志系统)
|
|
21
|
+
- [工具函数](#工具函数)
|
|
22
|
+
- [启动钩子](#启动钩子)
|
|
23
|
+
- [优雅停机](#优雅停机)
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 快速开始
|
|
28
|
+
|
|
29
|
+
### 安装
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install chanjs
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 入口文件
|
|
36
|
+
|
|
37
|
+
```javascript
|
|
38
|
+
// app.js
|
|
39
|
+
import Chan from "chanjs";
|
|
40
|
+
|
|
41
|
+
const chan = new Chan();
|
|
42
|
+
await chan.start();
|
|
43
|
+
chan.run(port => {
|
|
44
|
+
console.log(`服务启动在端口 ${port}`);
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 启动
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# 开发环境
|
|
52
|
+
NODE_ENV=dev node app.js
|
|
53
|
+
|
|
54
|
+
# 生产环境
|
|
55
|
+
NODE_ENV=prd node app.js
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 项目结构
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
project/
|
|
64
|
+
├── app.js # 入口文件
|
|
65
|
+
├── router.js # 公共路由(可选)
|
|
66
|
+
├── config/
|
|
67
|
+
│ └── index.js # 全局配置
|
|
68
|
+
├── public/
|
|
69
|
+
│ └── favicon.ico # 网站图标
|
|
70
|
+
├── app/
|
|
71
|
+
│ ├── modules/ # 业务模块
|
|
72
|
+
│ │ ├── web/ # 前端页面模块
|
|
73
|
+
│ │ │ ├── controller/ # 控制器
|
|
74
|
+
│ │ │ ├── service/ # 服务层
|
|
75
|
+
│ │ │ └── router.js # 模块路由
|
|
76
|
+
│ │ └── api/ # API 模块
|
|
77
|
+
│ │ ├── controller/
|
|
78
|
+
│ │ ├── service/
|
|
79
|
+
│ │ └── router.js
|
|
80
|
+
│ ├── common/ # 公共资源
|
|
81
|
+
│ ├── helper/ # 辅助工具
|
|
82
|
+
│ └── extend/ # 扩展
|
|
83
|
+
├── .env.dev # 开发环境变量
|
|
84
|
+
└── .env.prd # 生产环境变量
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## 配置说明
|
|
90
|
+
|
|
91
|
+
### config/index.js
|
|
92
|
+
|
|
93
|
+
```javascript
|
|
94
|
+
export default {
|
|
95
|
+
// 应用信息
|
|
96
|
+
APP_NAME: "MyApp",
|
|
97
|
+
APP_VERSION: "1.0.0",
|
|
98
|
+
PORT: 3000,
|
|
99
|
+
NODE_ENV: process.env.NODE_ENV || "dev",
|
|
100
|
+
|
|
101
|
+
// 业务模块列表(路由按此顺序加载,web 模块自动后置)
|
|
102
|
+
modules: ["api", "web"],
|
|
103
|
+
|
|
104
|
+
// 数据库配置(支持多库)
|
|
105
|
+
db: [
|
|
106
|
+
{
|
|
107
|
+
key: "default",
|
|
108
|
+
client: "mysql2",
|
|
109
|
+
connection: {
|
|
110
|
+
host: process.env.DB_HOST || "127.0.0.1",
|
|
111
|
+
port: process.env.DB_PORT || 3306,
|
|
112
|
+
user: process.env.DB_USER || "root",
|
|
113
|
+
password: process.env.DB_PASSWORD || "",
|
|
114
|
+
database: process.env.DB_NAME || "mydb",
|
|
115
|
+
},
|
|
116
|
+
pool: { min: 2, max: 10 },
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
|
|
120
|
+
// Redis 配置(可选)
|
|
121
|
+
REDIS_ENABLED: process.env.REDIS_ENABLED === "true",
|
|
122
|
+
REDIS: {
|
|
123
|
+
host: process.env.REDIS_HOST || "127.0.0.1",
|
|
124
|
+
port: process.env.REDIS_PORT || 6379,
|
|
125
|
+
password: process.env.REDIS_PASSWORD || "",
|
|
126
|
+
db: 0,
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
// JWT 密钥
|
|
130
|
+
JWT_SECRET: process.env.JWT_SECRET || "your-secret-key",
|
|
131
|
+
|
|
132
|
+
// Cookie 签名密钥
|
|
133
|
+
cookieKey: process.env.COOKIE_KEY || "cookie-secret",
|
|
134
|
+
|
|
135
|
+
// 分页配置
|
|
136
|
+
PAGE_SIZE: 20,
|
|
137
|
+
LIMIT_MAX: 300,
|
|
138
|
+
|
|
139
|
+
// 请求体大小限制
|
|
140
|
+
BODY_LIMIT: "10mb",
|
|
141
|
+
|
|
142
|
+
// 静态资源配置
|
|
143
|
+
statics: [
|
|
144
|
+
{ prefix: "/static", dir: "public/static", maxAge: 86400000 },
|
|
145
|
+
],
|
|
146
|
+
|
|
147
|
+
// 模板视图目录
|
|
148
|
+
views: ["app/modules/web/view"],
|
|
149
|
+
|
|
150
|
+
// CORS 跨域配置
|
|
151
|
+
cors: {
|
|
152
|
+
origin: "*",
|
|
153
|
+
methods: ["GET", "POST", "PUT", "DELETE"],
|
|
154
|
+
allowedHeaders: ["Content-Type", "Authorization"],
|
|
155
|
+
credentials: false,
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
// WAF 防火墙配置
|
|
159
|
+
waf: {
|
|
160
|
+
enabled: true,
|
|
161
|
+
rateLimit: {
|
|
162
|
+
windowMs: "1m", // 时间窗口
|
|
163
|
+
max: 60, // 窗口内最大请求数
|
|
164
|
+
ignorePaths: [], // 白名单路径
|
|
165
|
+
},
|
|
166
|
+
block: {
|
|
167
|
+
BLOCK_DURATION: 30 * 60 * 1000, // 封禁时长 30 分钟
|
|
168
|
+
STRIKE_THRESHOLD: 3, // 命中次数阈值
|
|
169
|
+
STRIKE_WINDOW: 60 * 60 * 1000, // 计数窗口
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
// 日志配置
|
|
174
|
+
logger: {
|
|
175
|
+
level: "chancms", // chancms | combined | common | dev | short | tiny
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### 环境变量
|
|
181
|
+
|
|
182
|
+
| 变量 | 说明 | 默认值 |
|
|
183
|
+
|------|------|--------|
|
|
184
|
+
| `NODE_ENV` | 运行环境 | `dev` |
|
|
185
|
+
| `ENV_FILE` | 自定义环境文件 | `.env.dev` / `.env.prd` |
|
|
186
|
+
| `DB_HOST` | 数据库地址 | `127.0.0.1` |
|
|
187
|
+
| `DB_PORT` | 数据库端口 | `3306` |
|
|
188
|
+
| `DB_USER` | 数据库用户 | `root` |
|
|
189
|
+
| `DB_PASSWORD` | 数据库密码 | - |
|
|
190
|
+
| `DB_NAME` | 数据库名 | - |
|
|
191
|
+
| `REDIS_ENABLED` | 是否启用 Redis | `false` |
|
|
192
|
+
| `REDIS_HOST` | Redis 地址 | `127.0.0.1` |
|
|
193
|
+
| `REDIS_PORT` | Redis 端口 | `6379` |
|
|
194
|
+
| `REDIS_PASSWORD` | Redis 密码 | - |
|
|
195
|
+
| `JWT_SECRET` | JWT 签名密钥 | - |
|
|
196
|
+
| `COOKIE_KEY` | Cookie 签名密钥 | - |
|
|
197
|
+
| `TRUSTED_PROXIES` | 信任代理 IP(逗号分隔) | `loopback` |
|
|
198
|
+
| `EXPOSE_ERR_DETAIL` | 是否暴露错误详情 | `false` |
|
|
199
|
+
| `DEBUG_TOKEN` | 调试令牌(生产环境查看错误详情) | - |
|
|
200
|
+
| `LOG_LEVEL` | 日志级别 | `debug` (dev) / `info` (prd) |
|
|
201
|
+
| `LOG_FILE` | 日志文件路径(仅 dev) | - |
|
|
202
|
+
| `AES_SALT` | AES 加密盐值 | - |
|
|
203
|
+
| `CF_ENABLED` | 是否启用 Cloudflare IP 解析 | `false` |
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Controller 控制器
|
|
208
|
+
|
|
209
|
+
控制器继承自 `Controller` 基类,内置响应封装。
|
|
210
|
+
|
|
211
|
+
```javascript
|
|
212
|
+
// app/modules/api/controller/UserController.js
|
|
213
|
+
import { Controller } from "chanjs";
|
|
214
|
+
|
|
215
|
+
class UserController extends Controller {
|
|
216
|
+
// 获取用户列表
|
|
217
|
+
async list(req, res) {
|
|
218
|
+
const { page = 1, pageSize = 10 } = req.query;
|
|
219
|
+
const result = await this.getUserList(page, pageSize);
|
|
220
|
+
return res.json(this.success({ data: result }));
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// 创建用户
|
|
224
|
+
async create(req, res) {
|
|
225
|
+
const { name, email } = req.body;
|
|
226
|
+
if (!name) {
|
|
227
|
+
return res.json(this.fail("用户名不能为空"));
|
|
228
|
+
}
|
|
229
|
+
const user = await this.createUser({ name, email });
|
|
230
|
+
return res.json(this.success({ data: user, msg: "创建成功" }));
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// 访问配置和数据库
|
|
234
|
+
async info(req, res) {
|
|
235
|
+
const config = this.config; // 全局配置
|
|
236
|
+
const db = this.db; // 默认数据库连接
|
|
237
|
+
const paths = this.paths; // 路径配置
|
|
238
|
+
const app = this.app; // 全局应用实例
|
|
239
|
+
return res.json(this.success({ data: { config, paths } }));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// 加载其他模块的 service
|
|
243
|
+
async loadData(req, res) {
|
|
244
|
+
const articleService = await this.get("api", "ArticleService");
|
|
245
|
+
const data = await articleService.getArticles();
|
|
246
|
+
return res.json(this.success({ data }));
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export default new UserController();
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Controller 方法
|
|
254
|
+
|
|
255
|
+
| 方法 | 说明 |
|
|
256
|
+
|------|------|
|
|
257
|
+
| `this.success({ data, msg })` | 成功响应 `{ success: true, code: 0, msg, data }` |
|
|
258
|
+
| `this.fail(msg)` | 失败响应(字符串直接作为提示文案) |
|
|
259
|
+
| `this.fail({ msg, code, data })` | 失败响应(对象形式) |
|
|
260
|
+
| `this.config` | 全局配置 |
|
|
261
|
+
| `this.db` | 默认数据库连接 |
|
|
262
|
+
| `this.paths` | 路径配置 |
|
|
263
|
+
| `this.app` | 全局应用实例 |
|
|
264
|
+
| `this.get(moduleName, fileName)` | 加载其他组件 |
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## Service 服务层
|
|
269
|
+
|
|
270
|
+
Service 用于封装业务逻辑,可注入多个 Repository 进行跨表事务编排。
|
|
271
|
+
|
|
272
|
+
```javascript
|
|
273
|
+
// app/modules/api/service/UserService.js
|
|
274
|
+
import { Service } from "chanjs";
|
|
275
|
+
|
|
276
|
+
class UserService extends Service {
|
|
277
|
+
async getUserWithArticles(userId) {
|
|
278
|
+
const userRepo = await this.get("api", "UserRepository");
|
|
279
|
+
const articleRepo = await this.get("api", "ArticleRepository");
|
|
280
|
+
|
|
281
|
+
const user = await userRepo.findById(userId);
|
|
282
|
+
const articles = await articleRepo.all({
|
|
283
|
+
query: { user_id: userId },
|
|
284
|
+
sort: { created_at: "desc" },
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
return { user: user.data, articles: articles.data };
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export default new UserService();
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
---
|
|
295
|
+
|
|
296
|
+
## Repository 数据仓库
|
|
297
|
+
|
|
298
|
+
Repository 封装通用 CRUD 操作,继承自 `Container`。
|
|
299
|
+
|
|
300
|
+
```javascript
|
|
301
|
+
// app/modules/api/service/UserRepository.js
|
|
302
|
+
import { Repository } from "chanjs";
|
|
303
|
+
|
|
304
|
+
class UserRepository extends Repository {
|
|
305
|
+
constructor() {
|
|
306
|
+
super("users"); // 绑定表名
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// 自定义查询方法
|
|
310
|
+
async findByEmail(email) {
|
|
311
|
+
return this.findOne({ query: { email } });
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// 复杂查询
|
|
315
|
+
async searchUsers(keyword, page = 1) {
|
|
316
|
+
return this.query({
|
|
317
|
+
current: page,
|
|
318
|
+
pageSize: 10,
|
|
319
|
+
query: {
|
|
320
|
+
name: { $like: `%${keyword}%` },
|
|
321
|
+
status: 1,
|
|
322
|
+
},
|
|
323
|
+
sort: { created_at: "desc" },
|
|
324
|
+
field: ["id", "name", "email", "created_at"],
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export default new UserRepository();
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### 构造函数
|
|
333
|
+
|
|
334
|
+
```javascript
|
|
335
|
+
super(tableName, dbName?, opts?)
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
| 参数 | 说明 |
|
|
339
|
+
|------|------|
|
|
340
|
+
| `tableName` | 表名 |
|
|
341
|
+
| `dbName` | 数据库连接名(多库时使用) |
|
|
342
|
+
| `opts.dateFields` | 自定义日期字段列表 |
|
|
343
|
+
|
|
344
|
+
### 内置方法
|
|
345
|
+
|
|
346
|
+
| 方法 | 说明 | 返回值 |
|
|
347
|
+
|------|------|--------|
|
|
348
|
+
| `all({ query, sort, fields, limit })` | 查询全部 | `{ success, code, msg, data }` |
|
|
349
|
+
| `find({ query, sort, fields, limit, offset })` | 分页偏移查询 | 同上 |
|
|
350
|
+
| `findOne({ query, fields })` | 查询单条 | 同上 |
|
|
351
|
+
| `findById(id, { fields })` | 根据 ID 查询 | 同上 |
|
|
352
|
+
| `insert(data)` | 单条插入 | `{ success, code, msg, data: { insertId, affectedRows } }` |
|
|
353
|
+
| `insertMany(records)` | 批量插入 | 同上 |
|
|
354
|
+
| `del(query)` / `delete(query)` | 条件删除 | `{ success, code, msg, data: { affectedRows } }` |
|
|
355
|
+
| `deleteById(id)` | 根据 ID 删除 | 同上 |
|
|
356
|
+
| `deleteMany(ids)` | 批量删除 | 同上 |
|
|
357
|
+
| `updateByQuery({ query, data })` | 条件更新 | 同上 |
|
|
358
|
+
| `updateById(id, data)` | 根据 ID 更新(返回更新后数据) | `{ success, code, msg, data }` |
|
|
359
|
+
| `updateMany(updates)` | 事务批量更新 | `{ success, code, msg, data: { affectedRows } }` |
|
|
360
|
+
| `query({ current, pageSize, query, sort, field })` | 标准分页 | `{ success, code, msg, data: { list, total, current, pageSize, totalPages } }` |
|
|
361
|
+
| `count(query)` | 统计行数 | `{ success, code, msg, data: { count } }` |
|
|
362
|
+
| `exists(query)` | 判断存在 | `{ success, code, msg, data: { exists } }` |
|
|
363
|
+
| `join({ joinTable, localField, foreignField, fields, query, sort })` | 联表查询 | `{ success, code, msg, data }` |
|
|
364
|
+
| `stats()` | 统计总数+今日新增 | `{ success, code, msg, data: { total, today } }` |
|
|
365
|
+
|
|
366
|
+
### 查询操作符
|
|
367
|
+
|
|
368
|
+
| 操作符 | 说明 | 示例 |
|
|
369
|
+
|--------|------|------|
|
|
370
|
+
| `$like` | 模糊匹配 | `{ name: { $like: "%张%" } }` |
|
|
371
|
+
| `$gt` | 大于 | `{ age: { $gt: 18 } }` |
|
|
372
|
+
| `$gte` | 大于等于 | `{ age: { $gte: 18 } }` |
|
|
373
|
+
| `$lt` | 小于 | `{ age: { $lt: 60 } }` |
|
|
374
|
+
| `$lte` | 小于等于 | `{ age: { $lte: 60 } }` |
|
|
375
|
+
| `$ne` | 不等于 | `{ status: { $ne: 0 } }` |
|
|
376
|
+
| `$in` | 包含 | `{ id: { $in: [1, 2, 3] } }` |
|
|
377
|
+
| `$null` | 为空 | `{ deleted_at: { $null: true } }` |
|
|
378
|
+
| `$notNull` | 不为空 | `{ email: { $notNull: true } }` |
|
|
379
|
+
|
|
380
|
+
### 受保护的方法
|
|
381
|
+
|
|
382
|
+
| 方法 | 说明 |
|
|
383
|
+
|------|------|
|
|
384
|
+
| `this._checkDB()` | 前置数据库校验 |
|
|
385
|
+
| `this._buildBaseQuery({ query, sort, fields })` | 统一构建查询实例 |
|
|
386
|
+
|
|
387
|
+
---
|
|
388
|
+
|
|
389
|
+
## 路由定义
|
|
390
|
+
|
|
391
|
+
### 模块路由
|
|
392
|
+
|
|
393
|
+
```javascript
|
|
394
|
+
// app/modules/api/router.js
|
|
395
|
+
import { Router } from "express";
|
|
396
|
+
import { getApp } from "chanjs";
|
|
397
|
+
import UserController from "./controller/UserController.js";
|
|
398
|
+
|
|
399
|
+
export default async (app, router, config) => {
|
|
400
|
+
// 基础路由
|
|
401
|
+
router.get("/api/users", UserController.list.bind(UserController));
|
|
402
|
+
router.post("/api/users", UserController.create.bind(UserController));
|
|
403
|
+
|
|
404
|
+
// 带参数的路由
|
|
405
|
+
router.get("/api/users/:id", async (req, res) => {
|
|
406
|
+
const userRepo = await getApp().dbManager.get("default");
|
|
407
|
+
// ...
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
// 使用中间件
|
|
411
|
+
router.post("/api/users/update", authMiddleware, UserController.update.bind(UserController));
|
|
412
|
+
};
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### 公共路由
|
|
416
|
+
|
|
417
|
+
```javascript
|
|
418
|
+
// router.js(项目根目录)
|
|
419
|
+
export default async (app, router, config) => {
|
|
420
|
+
router.get("/", (req, res) => {
|
|
421
|
+
res.render("index", { title: "首页" });
|
|
422
|
+
});
|
|
423
|
+
};
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
|
|
428
|
+
## 中间件
|
|
429
|
+
|
|
430
|
+
### 内置中间件
|
|
431
|
+
|
|
432
|
+
框架自动注册以下中间件(顺序固定):
|
|
433
|
+
|
|
434
|
+
1. **WAF 前置** — IP/关键词/限流/封禁检测
|
|
435
|
+
2. **Favicon** — 网站图标
|
|
436
|
+
3. **静态资源** — 配置目录的静态文件服务
|
|
437
|
+
4. **Cookie** — Cookie 解析与签名
|
|
438
|
+
5. **Body 解析** — JSON/URL-encoded/XML
|
|
439
|
+
6. **WAF Body** — 请求体关键词/XSS 检测
|
|
440
|
+
7. **CORS** — 跨域处理
|
|
441
|
+
8. **日志** — 请求日志(morgan)
|
|
442
|
+
9. **模板引擎** — art-template
|
|
443
|
+
10. **响应头** — 安全头设置
|
|
444
|
+
|
|
445
|
+
### 自定义中间件
|
|
446
|
+
|
|
447
|
+
```javascript
|
|
448
|
+
// 在路由中使用
|
|
449
|
+
import { validate, helper } from "chanjs";
|
|
450
|
+
|
|
451
|
+
router.post("/api/users",
|
|
452
|
+
validate(userSchema), // zod 校验
|
|
453
|
+
UserController.create.bind(UserController)
|
|
454
|
+
);
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
---
|
|
458
|
+
|
|
459
|
+
## 参数校验
|
|
460
|
+
|
|
461
|
+
基于 **zod** 的声明式校验。
|
|
462
|
+
|
|
463
|
+
```javascript
|
|
464
|
+
import { z } from "zod";
|
|
465
|
+
import { validate, validateAll } from "chanjs";
|
|
466
|
+
|
|
467
|
+
// 单来源校验
|
|
468
|
+
const createUserSchema = z.object({
|
|
469
|
+
name: z.string().min(1, "用户名不能为空"),
|
|
470
|
+
email: z.string().email("邮箱格式不正确"),
|
|
471
|
+
age: z.number().int().min(0).max(150).optional(),
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
router.post("/api/users",
|
|
475
|
+
validate(createUserSchema, "body"),
|
|
476
|
+
UserController.create.bind(UserController)
|
|
477
|
+
);
|
|
478
|
+
|
|
479
|
+
// 多来源校验
|
|
480
|
+
router.put("/api/users/:id",
|
|
481
|
+
validateAll({
|
|
482
|
+
params: z.object({ id: z.number().int().positive() }),
|
|
483
|
+
body: createUserSchema.partial(),
|
|
484
|
+
}),
|
|
485
|
+
UserController.update.bind(UserController)
|
|
486
|
+
);
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
校验失败自动抛出 `ValidationError`,由全局错误处理器统一返回 422 响应。
|
|
490
|
+
|
|
491
|
+
---
|
|
492
|
+
|
|
493
|
+
## 响应封装
|
|
494
|
+
|
|
495
|
+
### 统一响应格式
|
|
496
|
+
|
|
497
|
+
```javascript
|
|
498
|
+
{
|
|
499
|
+
success: true/false,
|
|
500
|
+
code: 0, // 业务码:0成功,1xxx业务错误,5xxx系统错误,6xxx数据库错误
|
|
501
|
+
msg: "操作成功",
|
|
502
|
+
data: {}
|
|
503
|
+
}
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
### 业务码规范
|
|
507
|
+
|
|
508
|
+
| 分段 | 类型 | 示例 |
|
|
509
|
+
|------|------|------|
|
|
510
|
+
| 0 | 成功 | 操作成功 |
|
|
511
|
+
| 1xxx | 通用业务错误 | 认证失败(1001)、令牌过期(1002)、权限不足(1003)、资源不存在(1004)、参数无效(1006) |
|
|
512
|
+
| 5xxx | 系统错误 | 系统内部错误(5001)、服务繁忙(5002) |
|
|
513
|
+
| 6xxx | 数据库错误 | 连接失败(6001)、访问拒绝(6002)、操作超时(6007) |
|
|
514
|
+
|
|
515
|
+
### 在 Controller 中使用
|
|
516
|
+
|
|
517
|
+
```javascript
|
|
518
|
+
// 成功
|
|
519
|
+
return res.json(this.success({ data: userList, msg: "查询成功" }));
|
|
520
|
+
|
|
521
|
+
// 失败(字符串简写)
|
|
522
|
+
return res.json(this.fail("用户名已存在"));
|
|
523
|
+
|
|
524
|
+
// 失败(对象形式)
|
|
525
|
+
return res.json(this.fail({ msg: "参数错误", code: 1006, data: { field: "email" } }));
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
### 独立使用(非 Controller 场景)
|
|
529
|
+
|
|
530
|
+
```javascript
|
|
531
|
+
import { success, fail } from "chanjs";
|
|
532
|
+
|
|
533
|
+
const ok = success({ data: { id: 1 }, msg: "创建成功" });
|
package/config/index.js
CHANGED
|
@@ -1,10 +1,41 @@
|
|
|
1
|
+
import dotenv from "dotenv";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { Paths } from "../utils/paths.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 框架运行默认常量
|
|
8
|
+
* 1. 优雅停机强制退出超时(ms)
|
|
9
|
+
* 2. 请求体最大限制
|
|
10
|
+
* 3. 单条beforeStart启动钩子超时阈值(ms)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export const SHUTDOWN_TIMEOUT = 5000;
|
|
14
|
+
|
|
15
|
+
export const BODY_LIMIT = "10mb";
|
|
16
|
+
|
|
17
|
+
export const HOOK_TIMEOUT = 10000;
|
|
18
|
+
|
|
19
|
+
|
|
1
20
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
21
|
+
* @description {string}
|
|
22
|
+
* @description 环境文件加载工具 获取当前生效的环境文件名
|
|
23
|
+
* 优先级:进程自定义ENV_FILE > NODE_ENV区分dev/prd
|
|
4
24
|
*/
|
|
25
|
+
export function getEnvFilePath() {
|
|
26
|
+
return process.env.ENV_FILE || (process.env.NODE_ENV === "dev" ? ".env.dev" : ".env.prd");
|
|
27
|
+
}
|
|
5
28
|
|
|
6
|
-
|
|
7
|
-
|
|
29
|
+
/**
|
|
30
|
+
* @description {void}
|
|
31
|
+
* @description 环境文件加载工具 加载对应环境env文件,文件不存在静默跳过,不阻断启动
|
|
32
|
+
*/
|
|
33
|
+
export function loadDotEnv() {
|
|
34
|
+
const envName = getEnvFilePath();
|
|
35
|
+
const envPath = path.resolve(Paths.rootPath, envName);
|
|
8
36
|
|
|
9
|
-
|
|
10
|
-
|
|
37
|
+
// 文件存在才加载,不存在直接跳过
|
|
38
|
+
if (fs.existsSync(envPath)) {
|
|
39
|
+
dotenv.config({ path: envPath });
|
|
40
|
+
}
|
|
41
|
+
}
|