create-pixle-koa-template 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/create-app.js +76 -0
- package/package.json +17 -0
- package/template/.env +20 -0
- package/template/app.js +49 -0
- package/template/config/db.js +29 -0
- package/template/config/email.js +30 -0
- package/template/config/index.js +36 -0
- package/template/config/redis.js +19 -0
- package/template/controllers/AuthController.js +71 -0
- package/template/controllers/DownloadController.js +18 -0
- package/template/controllers/UploadController.js +60 -0
- package/template/controllers/UserController.js +90 -0
- package/template/middleware/auth.js +91 -0
- package/template/middleware/errorHandler.js +17 -0
- package/template/middleware/logger.js +41 -0
- package/template/middleware/notFound.js +84 -0
- package/template/middleware/upload.js +165 -0
- package/template/models/Auth.js +11 -0
- package/template/models/BaseDAO.js +449 -0
- package/template/models/User.js +10 -0
- package/template/package-lock.json +3427 -0
- package/template/package.json +34 -0
- package/template/public/404.html +160 -0
- package/template/routes/auth.js +21 -0
- package/template/routes/download.js +9 -0
- package/template/routes/index.js +105 -0
- package/template/routes/upload.js +28 -0
- package/template/routes/user.js +22 -0
- package/template/services/AuthService.js +190 -0
- package/template/services/CodeRedisService.js +94 -0
- package/template/services/DownloadService.js +54 -0
- package/template/services/EmailService.js +245 -0
- package/template/services/JwtTokenService.js +50 -0
- package/template/services/PasswordService.js +133 -0
- package/template/services/TokenRedisService.js +29 -0
- package/template/services/UserService.js +128 -0
- package/template/utils/crypto.js +9 -0
- package/template/utils/passwordValidator.js +81 -0
- package/template/utils/prototype/day.js +237 -0
- package/template/utils/prototype/deepClone.js +32 -0
- package/template/utils/prototype/index.js +61 -0
- package/template/utils/response.js +26 -0
- package/template//344/275/277/347/224/250/346/225/231/347/250/213.md +881 -0
|
@@ -0,0 +1,881 @@
|
|
|
1
|
+
# Koa.js 项目基础框架使用教程
|
|
2
|
+
|
|
3
|
+
## 1. 项目概述
|
|
4
|
+
|
|
5
|
+
本项目是一个基于 Koa.js 框架构建的后端服务基础框架,提供了完整的 MVC 架构设计、用户认证、文件上传、数据访问层等核心功能。框架采用了模块化设计,各组件职责清晰,便于扩展和维护。
|
|
6
|
+
|
|
7
|
+
### 主要特性:
|
|
8
|
+
- 完整的用户认证系统(登录、注册、密码重置)
|
|
9
|
+
- 基于 JWT 的安全认证机制
|
|
10
|
+
- RESTful API 路由设计
|
|
11
|
+
- 统一的错误处理和响应格式
|
|
12
|
+
- 强大的数据访问层(支持复杂查询、分页等)
|
|
13
|
+
- Redis 集成(用于验证码、Token 存储)
|
|
14
|
+
- 文件上传下载功能
|
|
15
|
+
- 详细的请求日志记录
|
|
16
|
+
|
|
17
|
+
## 2. 环境要求
|
|
18
|
+
|
|
19
|
+
- Node.js 18.x 或更高版本
|
|
20
|
+
- MySQL 数据库
|
|
21
|
+
- Redis 服务
|
|
22
|
+
- npm 或 yarn 包管理器
|
|
23
|
+
|
|
24
|
+
## 3. 安装与配置
|
|
25
|
+
|
|
26
|
+
### 3.1 安装依赖
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### 3.2 环境配置
|
|
33
|
+
|
|
34
|
+
在项目根目录修改 `.env` 文件,配置以下环境变量:
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
# 服务器配置
|
|
38
|
+
PORT=3001 # 端口
|
|
39
|
+
NODE_ENV=development
|
|
40
|
+
|
|
41
|
+
# JWT配置
|
|
42
|
+
JWT_SECRET=your_jwt_secret_key_here # 密钥
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# 邮箱配置
|
|
46
|
+
EMAIL_HOST=smtp.example.com # 邮箱SMTP服务器地址
|
|
47
|
+
EMAIL_PORT=465 # SSL加密端口(推荐)
|
|
48
|
+
EMAIL_USER=your_email@example.com # 发件人邮箱地址
|
|
49
|
+
EMAIL_PASS=your_email_authorization_code # 邮箱授权码(非登录密码)
|
|
50
|
+
EMAIL_FROM=your_email@example.com # 显示的发件人地址
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# 验证码配置
|
|
54
|
+
VERIFICATION_CODE_EXPIRE=300 # 验证码过期时间 5分钟
|
|
55
|
+
VERIFICATION_SEND_LIMIT_EXPIRE=60 # 重复发送限制时间60秒
|
|
56
|
+
MAX_ATTEMPTS_PER_DAY=10 # 每日最多发送10次
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 3.3 数据库配置
|
|
60
|
+
|
|
61
|
+
修改 `config/db.js` 文件中的数据库连接信息:
|
|
62
|
+
|
|
63
|
+
```javascript
|
|
64
|
+
const pool = mysql.createPool({
|
|
65
|
+
host: "localhost", // 数据库服务器地址
|
|
66
|
+
port: 3306, // 数据库端口
|
|
67
|
+
user: "root", // 数据库用户名
|
|
68
|
+
password: "abc123abc", // 数据库密码
|
|
69
|
+
database: "demo", // 数据库名称
|
|
70
|
+
// 连接池配置
|
|
71
|
+
waitForConnections: true,
|
|
72
|
+
connectionLimit: 10,
|
|
73
|
+
queueLimit: 0,
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 3.4 Redis 配置
|
|
78
|
+
|
|
79
|
+
修改 `config/redis.js` 文件中的 Redis 连接信息:
|
|
80
|
+
|
|
81
|
+
```javascript
|
|
82
|
+
const redis = new Redis({
|
|
83
|
+
host: "localhost", // Redis服务器地址
|
|
84
|
+
port: 6379, // Redis端口
|
|
85
|
+
password: '', // Redis密码
|
|
86
|
+
db: 0, // 数据库索引
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 3.5 启动项目
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
npm start
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
服务器默认在 `http://localhost:3001` 启动。
|
|
97
|
+
|
|
98
|
+
## 4. 项目结构
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
├── app.js # 应用入口文件
|
|
102
|
+
├── config/ # 配置文件目录
|
|
103
|
+
│ ├── db.js # 数据库配置
|
|
104
|
+
│ ├── email.js # 邮箱配置
|
|
105
|
+
│ ├── index.js # 主配置文件
|
|
106
|
+
│ └── redis.js # Redis配置
|
|
107
|
+
├── controllers/ # 控制器目录
|
|
108
|
+
│ ├── AuthController.js # 认证控制器
|
|
109
|
+
│ ├── DownloadController.js # 下载控制器
|
|
110
|
+
│ ├── UploadController.js # 上传控制器
|
|
111
|
+
│ └── UserController.js # 用户控制器
|
|
112
|
+
├── middleware/ # 中间件目录
|
|
113
|
+
│ ├── auth.js # 认证中间件
|
|
114
|
+
│ ├── errorHandler.js # 错误处理中间件
|
|
115
|
+
│ ├── logger.js # 日志中间件
|
|
116
|
+
│ ├── notFound.js # 404处理中间件
|
|
117
|
+
│ └── upload.js # 上传中间件
|
|
118
|
+
├── models/ # 数据模型目录
|
|
119
|
+
│ ├── Auth.js # 认证模型
|
|
120
|
+
│ ├── BaseDAO.js # 基础数据访问对象
|
|
121
|
+
│ └── User.js # 用户模型
|
|
122
|
+
├── routes/ # 路由目录
|
|
123
|
+
│ ├── auth.js # 认证路由
|
|
124
|
+
│ ├── download.js # 下载路由
|
|
125
|
+
│ ├── index.js # 路由管理器
|
|
126
|
+
│ ├── upload.js # 上传路由
|
|
127
|
+
│ └── user.js # 用户路由
|
|
128
|
+
├── services/ # 服务层目录
|
|
129
|
+
│ ├── AuthService.js # 认证服务
|
|
130
|
+
│ ├── CodeRedisService.js # 验证码Redis服务
|
|
131
|
+
│ ├── DownloadService.js # 下载服务
|
|
132
|
+
│ ├── EmailService.js # 邮件服务
|
|
133
|
+
│ ├── JwtTokenService.js # JWT Token服务
|
|
134
|
+
│ ├── PasswordService.js # 密码服务
|
|
135
|
+
│ ├── TokenRedisService.js # Token Redis服务
|
|
136
|
+
│ └── UserService.js # 用户服务
|
|
137
|
+
├── storage/ # 存储目录
|
|
138
|
+
│ └── uploads/ # 上传文件目录
|
|
139
|
+
├── utils/ # 工具类目录
|
|
140
|
+
│ ├── crypto.js # 加密工具
|
|
141
|
+
│ ├── passwordValidator.js # 密码验证工具
|
|
142
|
+
│ ├── prototype/ # 原型扩展目录
|
|
143
|
+
│ └── response.js # 响应工具
|
|
144
|
+
└── public/ # 静态资源目录
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## 5. 核心功能模块
|
|
148
|
+
|
|
149
|
+
### 5.1 路由系统
|
|
150
|
+
|
|
151
|
+
#### 路由注册机制
|
|
152
|
+
|
|
153
|
+
项目使用自动路由注册机制,只需在 `routes` 目录下创建路由文件,系统会自动加载并注册这些路由。
|
|
154
|
+
|
|
155
|
+
#### 创建新路由
|
|
156
|
+
|
|
157
|
+
在 `routes` 目录下创建新的路由文件,例如 `product.js`:
|
|
158
|
+
|
|
159
|
+
```javascript
|
|
160
|
+
const productController = require('../controllers/ProductController');
|
|
161
|
+
const Router = require('@koa/router');
|
|
162
|
+
const router = new Router({
|
|
163
|
+
prefix: '/product'
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// 获取产品列表
|
|
167
|
+
router.get('/', productController.list);
|
|
168
|
+
// 获取单个产品
|
|
169
|
+
router.get('/:id', productController.detail);
|
|
170
|
+
// 创建产品
|
|
171
|
+
router.post('/', productController.create);
|
|
172
|
+
// 更新产品
|
|
173
|
+
router.put('/:id', productController.update);
|
|
174
|
+
// 删除产品
|
|
175
|
+
router.delete('/:id', productController.delete);
|
|
176
|
+
|
|
177
|
+
module.exports = router;
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### 5.2 中间件系统
|
|
181
|
+
|
|
182
|
+
#### 核心中间件
|
|
183
|
+
|
|
184
|
+
1. **错误处理中间件**:统一捕获并处理应用中的错误
|
|
185
|
+
2. **日志中间件**:记录请求信息、响应时间、状态码等
|
|
186
|
+
3. **认证中间件**:基于 JWT 的认证,支持路由白名单
|
|
187
|
+
4. **404 中间件**:处理不存在的路由请求
|
|
188
|
+
|
|
189
|
+
#### 自定义中间件
|
|
190
|
+
|
|
191
|
+
在 `middleware` 目录下创建自定义中间件文件,例如 `validation.js`:
|
|
192
|
+
|
|
193
|
+
```javascript
|
|
194
|
+
/**
|
|
195
|
+
* 请求参数验证中间件
|
|
196
|
+
*/
|
|
197
|
+
const validateParams = (schema) => {
|
|
198
|
+
return async (ctx, next) => {
|
|
199
|
+
try {
|
|
200
|
+
// 这里可以使用如 Joi、Yup 等验证库
|
|
201
|
+
// 验证通过则继续
|
|
202
|
+
await next();
|
|
203
|
+
} catch (error) {
|
|
204
|
+
ctx.status = 400;
|
|
205
|
+
ctx.body = {
|
|
206
|
+
code: 400,
|
|
207
|
+
message: error.message || '参数验证失败'
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
module.exports = validateParams;
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### 5.3 验证码系统
|
|
217
|
+
|
|
218
|
+
验证码系统用于用户注册、密码重置等场景,确保操作的安全性和防止恶意攻击。本系统通过邮箱发送验证码,并使用Redis进行验证码存储和频率限制管理。
|
|
219
|
+
|
|
220
|
+
#### 验证码使用场景
|
|
221
|
+
|
|
222
|
+
- **用户注册**:新用户注册时需要验证邮箱的真实性
|
|
223
|
+
- **密码重置**:用户忘记密码时通过邮箱验证码进行身份验证
|
|
224
|
+
|
|
225
|
+
#### 验证码类型
|
|
226
|
+
|
|
227
|
+
- `register`:用于用户注册场景
|
|
228
|
+
- `resetPassword`:用于密码重置场景
|
|
229
|
+
|
|
230
|
+
#### 如何使用验证码系统
|
|
231
|
+
|
|
232
|
+
##### 1. 发送验证码
|
|
233
|
+
|
|
234
|
+
**步骤说明**:
|
|
235
|
+
|
|
236
|
+
1. 调用发送验证码接口 `/api/auth/verify-code`
|
|
237
|
+
2. 系统会自动执行以下操作:
|
|
238
|
+
- 验证邮箱格式是否正确
|
|
239
|
+
- 检查邮箱是否已注册(注册场景)
|
|
240
|
+
- 验证发送频率限制(60秒内只能发送一次)
|
|
241
|
+
- 验证每日发送次数限制(每邮箱每日最多10次)
|
|
242
|
+
- 生成4位数字验证码
|
|
243
|
+
- 发送验证码邮件
|
|
244
|
+
- 存储验证码到Redis(有效期5分钟)
|
|
245
|
+
|
|
246
|
+
**使用示例**:
|
|
247
|
+
|
|
248
|
+
```javascript
|
|
249
|
+
// 前端调用示例
|
|
250
|
+
const response = await fetch('/api/auth/verify-code', {
|
|
251
|
+
method: 'POST',
|
|
252
|
+
headers: {
|
|
253
|
+
'Content-Type': 'application/json'
|
|
254
|
+
},
|
|
255
|
+
body: JSON.stringify({
|
|
256
|
+
email: 'user@example.com',
|
|
257
|
+
type: 'register' // 或 'resetPassword'
|
|
258
|
+
})
|
|
259
|
+
});
|
|
260
|
+
const result = await response.json();
|
|
261
|
+
// {"code": 200, "message": "验证码发送成功", "data": true}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
##### 2. 验证验证码
|
|
265
|
+
|
|
266
|
+
**步骤说明**:
|
|
267
|
+
|
|
268
|
+
1. 在用户注册或重置密码接口中传递验证码
|
|
269
|
+
2. 系统会自动执行以下操作:
|
|
270
|
+
- 从Redis获取存储的验证码
|
|
271
|
+
- 验证验证码是否有效且未过期
|
|
272
|
+
- 验证用户输入的验证码是否匹配
|
|
273
|
+
- 验证成功后自动清除验证码(防止重复使用)
|
|
274
|
+
|
|
275
|
+
**使用示例**:
|
|
276
|
+
|
|
277
|
+
```javascript
|
|
278
|
+
// 注册时验证验证码
|
|
279
|
+
const registerResponse = await fetch('/api/auth/register', {
|
|
280
|
+
method: 'POST',
|
|
281
|
+
headers: {
|
|
282
|
+
'Content-Type': 'application/json'
|
|
283
|
+
},
|
|
284
|
+
body: JSON.stringify({
|
|
285
|
+
account: 'newuser',
|
|
286
|
+
password: 'StrongPass123',
|
|
287
|
+
email: 'user@example.com',
|
|
288
|
+
code: '1234' // 用户收到的验证码
|
|
289
|
+
})
|
|
290
|
+
});
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
#### 验证码系统的安全特性
|
|
294
|
+
|
|
295
|
+
1. **有效期限制**:验证码默认有效期为5分钟,通过环境变量 `VERIFICATION_CODE_EXPIRE` 配置
|
|
296
|
+
2. **发送频率限制**:60秒内每个邮箱只能发送一次验证码,通过环境变量 `VERIFICATION_SEND_LIMIT_EXPIRE` 配置
|
|
297
|
+
3. **每日次数限制**:每个邮箱每日最多发送10次验证码,通过环境变量 `MAX_ATTEMPTS_PER_DAY` 配置
|
|
298
|
+
4. **邮箱格式验证**:系统自动验证邮箱格式的合法性
|
|
299
|
+
5. **验证码一次性使用**:验证成功后自动删除验证码,防止重复使用
|
|
300
|
+
6. **安全的Redis存储**:验证码以特定格式存储在Redis中,使用合理的键名前缀
|
|
301
|
+
|
|
302
|
+
#### 常见错误处理
|
|
303
|
+
|
|
304
|
+
使用验证码系统时,可能遇到的常见错误:
|
|
305
|
+
|
|
306
|
+
- 邮箱格式不正确
|
|
307
|
+
- 邮箱已被注册(注册场景)
|
|
308
|
+
- 60秒内已发送验证码
|
|
309
|
+
- 今日发送验证码已达上限
|
|
310
|
+
- 验证码无效或已过期
|
|
311
|
+
- 验证码错误
|
|
312
|
+
|
|
313
|
+
系统会返回相应的错误信息,前端可以根据返回的错误信息给用户友好的提示。
|
|
314
|
+
### 5.4 数据访问层
|
|
315
|
+
|
|
316
|
+
#### BaseDAO 类
|
|
317
|
+
|
|
318
|
+
项目提供了强大的 `BaseDAO` 类,封装了常见的数据库操作:
|
|
319
|
+
|
|
320
|
+
- 增删改查
|
|
321
|
+
- 分页查询
|
|
322
|
+
- 条件查询
|
|
323
|
+
- 高级搜索
|
|
324
|
+
|
|
325
|
+
#### 创建新的 DAO
|
|
326
|
+
|
|
327
|
+
```javascript
|
|
328
|
+
const BaseDAO = require('./BaseDAO');
|
|
329
|
+
|
|
330
|
+
class ProductDAO extends BaseDAO {
|
|
331
|
+
constructor() {
|
|
332
|
+
super('products'); // 表名
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// 可以添加自定义方法
|
|
336
|
+
async findByCategory(categoryId) {
|
|
337
|
+
return await this.findAll({ category_id: categoryId });
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
module.exports = new ProductDAO();
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
#### 使用 DAO 示例
|
|
345
|
+
|
|
346
|
+
```javascript
|
|
347
|
+
const productDAO = require('../models/ProductDAO');
|
|
348
|
+
|
|
349
|
+
// 查询所有产品
|
|
350
|
+
const products = await productDAO.findAll();
|
|
351
|
+
|
|
352
|
+
// 条件查询
|
|
353
|
+
const activeProducts = await productDAO.findAll({ status: 'active' });
|
|
354
|
+
|
|
355
|
+
// 分页查询
|
|
356
|
+
const pagination = await productDAO.paginate(page, pageSize, conditions);
|
|
357
|
+
|
|
358
|
+
// 插入数据
|
|
359
|
+
const newProduct = await productDAO.create(productData);
|
|
360
|
+
|
|
361
|
+
// 更新数据
|
|
362
|
+
await productDAO.update(productId, updateData);
|
|
363
|
+
|
|
364
|
+
// 删除数据
|
|
365
|
+
await productDAO.delete(productId);
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
#### BaseDAO 条件查询详细用法
|
|
369
|
+
|
|
370
|
+
BaseDAO 提供了丰富的条件查询功能,支持以下查询方式:
|
|
371
|
+
|
|
372
|
+
##### 1. 普通等值查询
|
|
373
|
+
|
|
374
|
+
```javascript
|
|
375
|
+
// 查询名称等于 '张三' 的用户
|
|
376
|
+
const user = await userDAO.findOne({ name: '张三' });
|
|
377
|
+
|
|
378
|
+
// 查询状态为 'active' 的产品
|
|
379
|
+
const activeProducts = await productDAO.findAll({ status: 'active' });
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
##### 2. 模糊查询($like_)
|
|
383
|
+
|
|
384
|
+
```javascript
|
|
385
|
+
// 查询用户名包含 '张' 的用户
|
|
386
|
+
const users = await userDAO.findAll({ $like_name: '张' });
|
|
387
|
+
|
|
388
|
+
// 查询产品描述包含 '新品' 的产品
|
|
389
|
+
const newProducts = await productDAO.findAll({ $like_description: '新品' });
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
##### 3. 大于等于查询($gte_)
|
|
393
|
+
|
|
394
|
+
```javascript
|
|
395
|
+
// 查询价格大于等于 100 的产品
|
|
396
|
+
const expensiveProducts = await productDAO.findAll({ $gte_price: 100 });
|
|
397
|
+
|
|
398
|
+
// 查询创建时间大于等于 2024-01-01 的订单
|
|
399
|
+
const orders = await orderDAO.findAll({ $gte_created_at: '2024-01-01' });
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
##### 4. 小于等于查询($lte_)
|
|
403
|
+
|
|
404
|
+
```javascript
|
|
405
|
+
// 查询价格小于等于 500 的产品
|
|
406
|
+
const affordableProducts = await productDAO.findAll({ $lte_price: 500 });
|
|
407
|
+
|
|
408
|
+
// 查询创建时间小于等于 2024-12-31 的订单
|
|
409
|
+
const orders = await orderDAO.findAll({ $lte_created_at: '2024-12-31' });
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
##### 5. 范围查询($between_)
|
|
413
|
+
|
|
414
|
+
```javascript
|
|
415
|
+
// 查询价格在 100-500 之间的产品
|
|
416
|
+
const products = await productDAO.findAll({ $between_price: [100, 500] });
|
|
417
|
+
|
|
418
|
+
// 查询创建时间在 2024-01-01 到 2024-12-31 之间的订单
|
|
419
|
+
const orders = await orderDAO.findAll({
|
|
420
|
+
$between_created_at: ['2024-01-01', '2024-12-31']
|
|
421
|
+
});
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
##### 6. IN 查询($in_)
|
|
425
|
+
|
|
426
|
+
```javascript
|
|
427
|
+
// 查询状态为 'active' 或 'pending' 的产品
|
|
428
|
+
const products = await productDAO.findAll({
|
|
429
|
+
$in_status: ['active', 'pending']
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
// 查询指定 ID 的用户列表
|
|
433
|
+
const users = await userDAO.findAll({
|
|
434
|
+
$in_id: [1, 2, 3, 4, 5]
|
|
435
|
+
});
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
##### 7. 组合条件查询
|
|
439
|
+
|
|
440
|
+
```javascript
|
|
441
|
+
// 组合多种条件查询
|
|
442
|
+
const products = await productDAO.findAll({
|
|
443
|
+
category_id: 1,
|
|
444
|
+
$gte_price: 100,
|
|
445
|
+
$lte_price: 500,
|
|
446
|
+
$in_status: ['active', 'pending']
|
|
447
|
+
});
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
##### 8. 条件查询配合排序和分页
|
|
451
|
+
|
|
452
|
+
```javascript
|
|
453
|
+
// 条件查询并排序
|
|
454
|
+
const products = await productDAO.findAll(
|
|
455
|
+
{ category_id: 1 },
|
|
456
|
+
{ orderBy: 'price', orderDirection: 'desc' }
|
|
457
|
+
);
|
|
458
|
+
|
|
459
|
+
// 条件查询并分页
|
|
460
|
+
const products = await productDAO.findAll(
|
|
461
|
+
{ status: 'active' },
|
|
462
|
+
{ limit: 10, offset: 20 }
|
|
463
|
+
);
|
|
464
|
+
|
|
465
|
+
// 完整的分页查询(推荐使用 paginate 方法)
|
|
466
|
+
const result = await productDAO.paginate(
|
|
467
|
+
1, // 页码
|
|
468
|
+
10, // 每页数量
|
|
469
|
+
{ category_id: 1 }, // 查询条件
|
|
470
|
+
{ orderBy: 'created_at', orderDirection: 'desc' } // 排序选项
|
|
471
|
+
);
|
|
472
|
+
// result 结构: { list: [...], pagination: { page, pageSize, total, totalPages, hasNext, hasPrev } }
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
##### 9. 高级搜索方法
|
|
476
|
+
|
|
477
|
+
BaseDAO 还提供了 `advancedSearch` 方法,支持更复杂的搜索场景:
|
|
478
|
+
|
|
479
|
+
```javascript
|
|
480
|
+
const searchConfig = {
|
|
481
|
+
keyword: '张三', // 搜索关键词
|
|
482
|
+
keywordFields: ['name', 'email', 'phone'], // 搜索的字段
|
|
483
|
+
conditions: { status: 'active' }, // 基础条件
|
|
484
|
+
dateRangeField: 'created_at', // 时间范围字段
|
|
485
|
+
startDate: '2024-01-01', // 开始时间
|
|
486
|
+
endDate: '2024-12-31', // 结束时间
|
|
487
|
+
options: { orderBy: 'created_at', orderDirection: 'desc' } // 排序选项
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
const users = await userDAO.advancedSearch(searchConfig);
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
### 5.5 控制器层
|
|
494
|
+
|
|
495
|
+
控制器负责处理 HTTP 请求,调用服务层进行业务逻辑处理,并返回响应。
|
|
496
|
+
|
|
497
|
+
#### 创建控制器
|
|
498
|
+
|
|
499
|
+
```javascript
|
|
500
|
+
const ProductService = require('../services/ProductService');
|
|
501
|
+
const { success, error } = require('../utils/response');
|
|
502
|
+
|
|
503
|
+
class ProductController {
|
|
504
|
+
// 获取产品列表
|
|
505
|
+
async list(ctx) {
|
|
506
|
+
const { page = 1, pageSize = 10 } = ctx.query;
|
|
507
|
+
const result = await ProductService.getProducts(page, pageSize);
|
|
508
|
+
success(ctx, result, '获取产品列表成功');
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// 获取单个产品
|
|
512
|
+
async detail(ctx) {
|
|
513
|
+
const { id } = ctx.params;
|
|
514
|
+
const product = await ProductService.getProductById(id);
|
|
515
|
+
if (product) {
|
|
516
|
+
success(ctx, product, '获取产品详情成功');
|
|
517
|
+
} else {
|
|
518
|
+
error(ctx, '产品不存在', 404);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// 创建产品
|
|
523
|
+
async create(ctx) {
|
|
524
|
+
const result = await ProductService.createProduct(ctx.request.body);
|
|
525
|
+
success(ctx, result, '创建产品成功');
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
module.exports = new ProductController();
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
### 5.6 服务层
|
|
533
|
+
|
|
534
|
+
服务层封装业务逻辑,协调多个 DAO 的操作。
|
|
535
|
+
|
|
536
|
+
#### 创建服务
|
|
537
|
+
|
|
538
|
+
```javascript
|
|
539
|
+
const productDAO = require('../models/ProductDAO');
|
|
540
|
+
const { error } = require('../utils/response');
|
|
541
|
+
|
|
542
|
+
class ProductService {
|
|
543
|
+
// 获取产品列表(带分页)
|
|
544
|
+
async getProducts(page, pageSize) {
|
|
545
|
+
return await productDAO.paginate(page, pageSize);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// 根据ID获取产品
|
|
549
|
+
async getProductById(id) {
|
|
550
|
+
return await productDAO.findById(id);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// 创建产品
|
|
554
|
+
async createProduct(productData) {
|
|
555
|
+
// 数据验证
|
|
556
|
+
if (!productData.name) {
|
|
557
|
+
throw new Error('产品名称不能为空');
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// 业务逻辑处理
|
|
561
|
+
// ...
|
|
562
|
+
|
|
563
|
+
// 调用DAO进行数据操作
|
|
564
|
+
return await productDAO.create(productData);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
module.exports = new ProductService();
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
### 5.7 认证系统
|
|
572
|
+
|
|
573
|
+
#### JWT 认证流程
|
|
574
|
+
|
|
575
|
+
1. 用户登录获取 JWT Token
|
|
576
|
+
2. 客户端在请求头中携带 Token
|
|
577
|
+
3. 服务端验证 Token 的有效性
|
|
578
|
+
4. 验证通过则允许访问受保护的资源
|
|
579
|
+
|
|
580
|
+
#### 配置白名单
|
|
581
|
+
|
|
582
|
+
在 `config/index.js` 中配置不需要认证的路由:
|
|
583
|
+
|
|
584
|
+
```javascript
|
|
585
|
+
jwt: {
|
|
586
|
+
secret: process.env.JWT_SECRET,
|
|
587
|
+
expiresIn: 24 * 60 * 60 * 7, // 7天
|
|
588
|
+
pathWhiteList: [
|
|
589
|
+
/^\/static\/.*/, // 静态资源
|
|
590
|
+
'/api/auth/login', // 登录接口
|
|
591
|
+
'/api/auth/register', // 注册接口
|
|
592
|
+
'/api/auth/verify-code', // 发送验证码接口
|
|
593
|
+
// 其他不需要认证的路由
|
|
594
|
+
],
|
|
595
|
+
},
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
## 6. 使用示例
|
|
599
|
+
|
|
600
|
+
### 6.1 发送验证码
|
|
601
|
+
|
|
602
|
+
**请求**:
|
|
603
|
+
```
|
|
604
|
+
POST /api/auth/verify-code
|
|
605
|
+
Content-Type: application/json
|
|
606
|
+
|
|
607
|
+
{
|
|
608
|
+
"email": "test@example.com",
|
|
609
|
+
"type": "register" // 可选值: register(注册), resetPassword(重置密码)
|
|
610
|
+
}
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
**响应**:
|
|
614
|
+
```json
|
|
615
|
+
{
|
|
616
|
+
"code": 200,
|
|
617
|
+
"message": "验证码发送成功",
|
|
618
|
+
"data": true
|
|
619
|
+
}
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
**验证码限制说明**:
|
|
623
|
+
- 验证码有效期为5分钟
|
|
624
|
+
- 每个邮箱60秒内只能发送一次验证码
|
|
625
|
+
- 每个邮箱每日最多发送10次验证码
|
|
626
|
+
|
|
627
|
+
### 6.2 用户注册
|
|
628
|
+
|
|
629
|
+
**请求**:
|
|
630
|
+
```
|
|
631
|
+
POST /api/auth/register
|
|
632
|
+
Content-Type: application/json
|
|
633
|
+
|
|
634
|
+
{
|
|
635
|
+
"account": "testuser",
|
|
636
|
+
"password": "Test123456",
|
|
637
|
+
"email": "test@example.com",
|
|
638
|
+
"code": "123456"
|
|
639
|
+
}
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
**响应**:
|
|
643
|
+
```json
|
|
644
|
+
{
|
|
645
|
+
"code": 200,
|
|
646
|
+
"message": "注册成功",
|
|
647
|
+
"data": { "id": 1 }
|
|
648
|
+
}
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
### 6.3 用户登录
|
|
652
|
+
|
|
653
|
+
**请求**:
|
|
654
|
+
```
|
|
655
|
+
POST /api/auth/login
|
|
656
|
+
Content-Type: application/json
|
|
657
|
+
|
|
658
|
+
{
|
|
659
|
+
"account": "testuser",
|
|
660
|
+
"password": "Test123456"
|
|
661
|
+
}
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
**响应**:
|
|
665
|
+
```json
|
|
666
|
+
{
|
|
667
|
+
"code": 200,
|
|
668
|
+
"message": "登录成功",
|
|
669
|
+
"data": {
|
|
670
|
+
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
|
671
|
+
"userId": 1
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
### 6.4 用户退出登录
|
|
677
|
+
|
|
678
|
+
**请求**:
|
|
679
|
+
```
|
|
680
|
+
POST /api/auth/logout
|
|
681
|
+
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
**响应**:
|
|
685
|
+
```json
|
|
686
|
+
{
|
|
687
|
+
"code": 200,
|
|
688
|
+
"message": "退出登录成功"
|
|
689
|
+
}
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
### 6.5 用户详情
|
|
693
|
+
|
|
694
|
+
**请求**:
|
|
695
|
+
```
|
|
696
|
+
GET /api/user/current
|
|
697
|
+
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
**响应**:
|
|
701
|
+
```json
|
|
702
|
+
{
|
|
703
|
+
"code": 200,
|
|
704
|
+
"message": "success",
|
|
705
|
+
"data": {
|
|
706
|
+
"id": 1,
|
|
707
|
+
"account": "testuser",
|
|
708
|
+
"email": "test@example.com",
|
|
709
|
+
"created_at": "2023-07-01 00:00:00",
|
|
710
|
+
"updated_at": "2023-07-01 00:00:00"
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
### 6.6 文件上传
|
|
716
|
+
|
|
717
|
+
#### 单文件上传
|
|
718
|
+
|
|
719
|
+
**请求**:
|
|
720
|
+
```
|
|
721
|
+
POST /api/upload/single
|
|
722
|
+
Content-Type: multipart/form-data
|
|
723
|
+
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
|
724
|
+
|
|
725
|
+
[form-data with file]
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
**响应**:
|
|
729
|
+
```json
|
|
730
|
+
{
|
|
731
|
+
"code": 200,
|
|
732
|
+
"message": "上传成功",
|
|
733
|
+
"data": {
|
|
734
|
+
"url": "/storage/uploads/image/2025/12/16/file123.jpg",
|
|
735
|
+
"filename": "file123.jpg",
|
|
736
|
+
"size": 2546
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
#### 多文件上传
|
|
742
|
+
|
|
743
|
+
**请求**:
|
|
744
|
+
```
|
|
745
|
+
POST /api/upload/multiple
|
|
746
|
+
Content-Type: multipart/form-data
|
|
747
|
+
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
|
748
|
+
|
|
749
|
+
[form-data with multiple files]
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
**响应**:
|
|
753
|
+
```json
|
|
754
|
+
{
|
|
755
|
+
"code": 200,
|
|
756
|
+
"message": "上传成功",
|
|
757
|
+
"data": [
|
|
758
|
+
{
|
|
759
|
+
"url": "/storage/uploads/image/2025/12/16/file1.jpg",
|
|
760
|
+
"filename": "file1.jpg",
|
|
761
|
+
"size": 2061
|
|
762
|
+
},
|
|
763
|
+
{
|
|
764
|
+
"url": "/storage/uploads/image//2025/12/16/file2.jpg",
|
|
765
|
+
"filename": "file2.jpg",
|
|
766
|
+
"size": 7256
|
|
767
|
+
}
|
|
768
|
+
]
|
|
769
|
+
}
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
### 6.7 接口列表
|
|
773
|
+
|
|
774
|
+
#### 认证相关接口
|
|
775
|
+
|
|
776
|
+
| 接口路径 | 方法 | 描述 | 请求参数 | 成功响应 (200 OK) |
|
|
777
|
+
|---------|------|------|---------|-------------------|
|
|
778
|
+
| `/api/auth/login` | `POST` | 用户登录 | `{"account": "用户名", "password": "密码"}` | `{"code": 200, "message": "登录成功", "data": {"token": "JWT令牌", "user": {"id": 1, "account": "用户名", "email": "邮箱", "created_at": "2023-07-01 00:00:00", "updated_at": "2023-07-01 00:00:00"}}}` |
|
|
779
|
+
| `/api/auth/register` | `POST` | 用户注册 | `{"account": "用户名", "password": "密码", "email": "邮箱", "code": "验证码"}` | `{"code": 200, "message": "注册成功", "data": {"id": 1, "account": "用户名", "email": "邮箱", "created_at": "2023-07-01 00:00:00", "updated_at": "2023-07-01 00:00:00"}}` |
|
|
780
|
+
| `/api/auth/verify-code` | `POST` | 发送验证码 | `{"email": "邮箱", "type": "验证码类型(register/resetPassword)"}` | `{"code": 200, "message": "验证码发送成功", "data": true}` |
|
|
781
|
+
| `/api/auth/logout` | `POST` | 用户退出登录 | 无(需要JWT认证) | `{"code": 200, "message": "退出登录成功"}` |
|
|
782
|
+
| `/api/auth/change-password` | `POST` | 修改密码 | `{"oldPassword": "旧密码", "newPassword": "新密码"}` | `{"code": 200, "message": "密码修改成功"}` |
|
|
783
|
+
| `/api/auth/reset-password` | `POST` | 重置密码 | `{"email": "邮箱", "code": "验证码", "newPassword": "新密码"}` | `{"code": 200, "message": "密码重置成功"}` |
|
|
784
|
+
|
|
785
|
+
#### 用户相关接口
|
|
786
|
+
|
|
787
|
+
| 接口路径 | 方法 | 描述 | 请求参数 | 成功响应 (200 OK) |
|
|
788
|
+
|---------|------|------|---------|-------------------|
|
|
789
|
+
| `/api/user/current` | `GET` | 获取当前用户信息 | 无(需要JWT认证) | `{"code": 200, "message": "获取成功", "data": {"id": 1, "account": "用户名", "email": "邮箱", "created_at": "2023-07-01 00:00:00", "updated_at": "2023-07-01 00:00:00"}}` |
|
|
790
|
+
| `/api/user/list` | `GET` | 获取用户列表 | 查询参数:`email`(邮箱), `startTime`(开始时间), `endTime`(结束时间), `page`(页码), `pageSize`(每页数量), `all`(是否获取全部列表,默认为false) | 当all=true时:`{"code": 200, "message": "success", "data": [{"id": 15, "account": "admin", "updated_at": "2025-12-15 16:28:58", "email": "xxx@qq.com", "created_at": "2025-12-15 15:38:12"}]}`<br>当all=false时:`{"code": 200, "message": "success", "data": {"list": [{"id": 15, "account": "admin", "updated_at": "2025-12-15 16:28:58", "email": "xxx@qq.com", "created_at": "2025-12-15 15:38:12"}], "pagination": {"page": 2, "pageSize": 1, "total": 4, "totalPages": 4, "hasNext": true, "hasPrev": true}}}` |
|
|
791
|
+
| `/api/user` | `POST` | 创建新用户 | `{"account": "用户名", "password": "密码", "email": "邮箱"}` | `{"code": 200, "message": "创建成功", "data": {"id": 1, "account": "用户名", "email": "邮箱", "created_at": "2023-07-01 00:00:00", "updated_at": "2023-07-01 00:00:00"}}` |
|
|
792
|
+
| `/api/user/:id` | `PUT` | 更新用户信息 | `{"account": "用户名", "email": "邮箱"}` | `{"code": 200, "message": "success", "data": true}` |
|
|
793
|
+
| `/api/user/:id` | `DELETE` | 删除用户 | 无(路径参数id) | `{"code": 200, "message": "删除成功"}` |
|
|
794
|
+
| `/api/user/:id` | `GET` | 获取指定用户信息 | 无(路径参数id) | `{"code": 200, "message": "获取成功", "data": {"id": 1, "account": "用户名", "email": "邮箱", "created_at": "2023-07-01 00:00:00", "updated_at": "2023-07-01 00:00:00"}}` |
|
|
795
|
+
|
|
796
|
+
#### 文件上传相关接口
|
|
797
|
+
|
|
798
|
+
| 接口路径 | 方法 | 描述 | 请求参数 | 成功响应 (200 OK) |
|
|
799
|
+
|---------|------|------|---------|-------------------|
|
|
800
|
+
| `/api/upload/single` | `POST` | 单文件上传 | FormData: `file`(文件字段) | `{"code": 200, "message": "上传成功", "data": {"url": "/storage/uploads/image/2025/12/16/file.jpg", "filename": "file.jpg", "size":2650}}` |
|
|
801
|
+
| `/api/upload/multiple` | `POST` | 多文件上传 | FormData: `files`(多文件字段) | `{"code": 200, "message": "上传成功", "data": [{"url": "...", "filename": "...", "size": "..."}]}` |
|
|
802
|
+
|
|
803
|
+
#### 文件下载相关接口
|
|
804
|
+
|
|
805
|
+
| 接口路径 | 方法 | 描述 | 请求参数 | 成功响应 (200 OK) |
|
|
806
|
+
|---------|------|------|---------|-------------------|
|
|
807
|
+
| `/api/download` | `GET` | 文件下载 | 查询参数:`url`(文件相对路径) | 文件流响应(Content-Type: 对应的文件MIME类型) |
|
|
808
|
+
|
|
809
|
+
## 7. 最佳实践
|
|
810
|
+
|
|
811
|
+
### 7.1 代码组织
|
|
812
|
+
|
|
813
|
+
- 遵循 MVC 架构,将代码分层组织
|
|
814
|
+
- 控制器只负责请求处理和响应返回
|
|
815
|
+
- 业务逻辑放在服务层
|
|
816
|
+
- 数据访问操作通过 DAO 进行
|
|
817
|
+
|
|
818
|
+
### 7.2 错误处理
|
|
819
|
+
|
|
820
|
+
- 使用统一的错误处理中间件
|
|
821
|
+
- 业务逻辑错误通过抛出异常处理
|
|
822
|
+
- 返回统一格式的错误响应
|
|
823
|
+
- 记录关键错误日志
|
|
824
|
+
|
|
825
|
+
### 7.3 安全性
|
|
826
|
+
|
|
827
|
+
- 敏感数据加密存储(如密码使用 argon2 加密)
|
|
828
|
+
- 接口访问使用 JWT 认证
|
|
829
|
+
- 输入参数严格验证
|
|
830
|
+
- 防止 SQL 注入(使用参数化查询)
|
|
831
|
+
|
|
832
|
+
### 7.4 性能优化
|
|
833
|
+
|
|
834
|
+
- 使用数据库连接池
|
|
835
|
+
- 合理使用 Redis 缓存
|
|
836
|
+
- 分页查询避免一次性加载大量数据
|
|
837
|
+
- 使用异步操作提高并发性能
|
|
838
|
+
|
|
839
|
+
## 8. 常见问题
|
|
840
|
+
|
|
841
|
+
### 8.1 如何添加新的 API 接口?
|
|
842
|
+
|
|
843
|
+
1. 在 `controllers` 目录下创建对应的控制器
|
|
844
|
+
2. 在 `services` 目录下创建对应的服务
|
|
845
|
+
3. 在 `models` 目录下创建对应的 DAO
|
|
846
|
+
4. 在 `routes` 目录下创建对应的路由文件
|
|
847
|
+
|
|
848
|
+
### 8.2 如何扩展中间件?
|
|
849
|
+
|
|
850
|
+
在 `middleware` 目录下创建新的中间件文件,并在 `app.js` 中注册使用。
|
|
851
|
+
|
|
852
|
+
### 8.3 如何处理文件上传?
|
|
853
|
+
|
|
854
|
+
使用已有的 `UploadController` 和 `upload.js` 中间件,或根据需要扩展。
|
|
855
|
+
|
|
856
|
+
### 8.4 如何进行数据库事务?
|
|
857
|
+
|
|
858
|
+
在 DAO 层可以通过获取连接并手动控制事务:
|
|
859
|
+
|
|
860
|
+
```javascript
|
|
861
|
+
async transactionExample() {
|
|
862
|
+
const connection = await this.pool.getConnection();
|
|
863
|
+
try {
|
|
864
|
+
await connection.beginTransaction();
|
|
865
|
+
// 执行多个操作
|
|
866
|
+
await connection.execute(sql1, params1);
|
|
867
|
+
await connection.execute(sql2, params2);
|
|
868
|
+
await connection.commit();
|
|
869
|
+
return true;
|
|
870
|
+
} catch (error) {
|
|
871
|
+
await connection.rollback();
|
|
872
|
+
throw error;
|
|
873
|
+
} finally {
|
|
874
|
+
connection.release();
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
```
|
|
878
|
+
|
|
879
|
+
## 9. 总结
|
|
880
|
+
|
|
881
|
+
本框架提供了一个完整的 Koa.js 后端服务开发基础,包含了用户认证、数据访问、文件上传等常用功能。通过模块化设计和清晰的分层架构,使得项目易于维护和扩展。开发者可以基于此框架快速构建自己的后端服务,专注于业务逻辑的实现。
|