@svton/cli 1.2.2 → 1.2.3
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/dist/index.js +6 -13
- package/dist/index.mjs +6 -13
- package/package.json +1 -3
- package/templates/apps/admin/next-env.d.ts +0 -2
- package/templates/apps/admin/next.config.js +0 -15
- package/templates/apps/admin/package.json.tpl +0 -54
- package/templates/apps/admin/postcss.config.js +0 -6
- package/templates/apps/admin/src/app/globals.css +0 -37
- package/templates/apps/admin/src/app/layout.tsx +0 -19
- package/templates/apps/admin/src/app/login/page.tsx +0 -96
- package/templates/apps/admin/src/app/page.tsx +0 -8
- package/templates/apps/admin/src/app/users/page.tsx +0 -165
- package/templates/apps/admin/src/components/ui/switch.tsx +0 -29
- package/templates/apps/admin/src/hooks/useAPI.ts +0 -130
- package/templates/apps/admin/src/lib/api-client.ts +0 -112
- package/templates/apps/admin/src/lib/api-server.ts +0 -95
- package/templates/apps/admin/tailwind.config.js +0 -54
- package/templates/apps/admin/tsconfig.json +0 -22
- package/templates/apps/backend/.env.example +0 -29
- package/templates/apps/backend/nest-cli.json +0 -8
- package/templates/apps/backend/package.json.tpl +0 -57
- package/templates/apps/backend/prisma/schema.prisma +0 -72
- package/templates/apps/backend/prisma/seed.ts +0 -32
- package/templates/apps/backend/src/app.controller.ts +0 -15
- package/templates/apps/backend/src/app.module.ts +0 -85
- package/templates/apps/backend/src/app.service.ts +0 -12
- package/templates/apps/backend/src/auth/auth.controller.ts +0 -31
- package/templates/apps/backend/src/auth/auth.module.ts +0 -27
- package/templates/apps/backend/src/auth/auth.service.ts +0 -89
- package/templates/apps/backend/src/auth/jwt-auth.guard.ts +0 -5
- package/templates/apps/backend/src/auth/jwt.strategy.ts +0 -27
- package/templates/apps/backend/src/config/env.schema.ts +0 -35
- package/templates/apps/backend/src/main.ts +0 -51
- package/templates/apps/backend/src/object-storage/object-storage.controller.ts +0 -114
- package/templates/apps/backend/src/object-storage/object-storage.module.ts +0 -7
- package/templates/apps/backend/src/prisma/prisma.module.ts +0 -9
- package/templates/apps/backend/src/prisma/prisma.service.ts +0 -13
- package/templates/apps/backend/src/user/user.controller.ts +0 -50
- package/templates/apps/backend/src/user/user.module.ts +0 -12
- package/templates/apps/backend/src/user/user.service.ts +0 -117
- package/templates/apps/backend/tsconfig.json +0 -23
- package/templates/apps/mobile/babel.config.js +0 -8
- package/templates/apps/mobile/config/index.ts +0 -65
- package/templates/apps/mobile/package.json.tpl +0 -48
- package/templates/apps/mobile/project.config.json.tpl +0 -17
- package/templates/apps/mobile/src/app.config.ts +0 -9
- package/templates/apps/mobile/src/app.scss +0 -4
- package/templates/apps/mobile/src/app.ts +0 -8
- package/templates/apps/mobile/src/hooks/useAPI.ts +0 -285
- package/templates/apps/mobile/src/pages/index/index.scss +0 -7
- package/templates/apps/mobile/src/pages/index/index.tsx +0 -49
- package/templates/apps/mobile/src/services/api.ts +0 -155
- package/templates/apps/mobile/src/services/upload.service.ts +0 -41
- package/templates/apps/mobile/tsconfig.json +0 -21
- package/templates/configs/authz.config.ts +0 -10
- package/templates/configs/cache.config.ts +0 -14
- package/templates/configs/oauth.config.ts +0 -20
- package/templates/configs/payment.config.ts +0 -44
- package/templates/configs/queue.config.ts +0 -21
- package/templates/configs/rate-limit.config.ts +0 -16
- package/templates/configs/sms.config.ts +0 -11
- package/templates/configs/storage.config.ts +0 -14
- package/templates/examples/README.md +0 -258
- package/templates/examples/authz/README.md +0 -273
- package/templates/examples/authz/roles.guard.ts +0 -37
- package/templates/examples/authz/user.controller.ts +0 -116
- package/templates/examples/cache/README.md +0 -82
- package/templates/examples/cache/user.controller.ts +0 -42
- package/templates/examples/cache/user.service.ts +0 -78
- package/templates/examples/oauth/README.md +0 -192
- package/templates/examples/oauth/auth.controller.ts +0 -99
- package/templates/examples/oauth/auth.service.ts +0 -97
- package/templates/examples/payment/README.md +0 -151
- package/templates/examples/payment/order.controller.ts +0 -56
- package/templates/examples/payment/order.service.ts +0 -132
- package/templates/examples/payment/webhook.controller.ts +0 -73
- package/templates/examples/queue/README.md +0 -134
- package/templates/examples/queue/email.controller.ts +0 -34
- package/templates/examples/queue/email.processor.ts +0 -68
- package/templates/examples/queue/email.service.ts +0 -64
- package/templates/examples/rate-limit/README.md +0 -249
- package/templates/examples/rate-limit/api.controller.ts +0 -113
- package/templates/examples/sms/README.md +0 -121
- package/templates/examples/sms/sms.service.ts +0 -69
- package/templates/examples/sms/verification.controller.ts +0 -100
- package/templates/examples/storage/README.md +0 -224
- package/templates/examples/storage/upload.controller.ts +0 -117
- package/templates/examples/storage/upload.service.ts +0 -123
- package/templates/packages/types/package.json.tpl +0 -16
- package/templates/packages/types/src/api.ts +0 -88
- package/templates/packages/types/src/common.ts +0 -89
- package/templates/packages/types/src/index.ts +0 -3
- package/templates/packages/types/tsconfig.json +0 -16
- package/templates/skills/authz.skill.md +0 -42
- package/templates/skills/base.skill.md +0 -57
- package/templates/skills/cache.skill.md +0 -88
- package/templates/skills/oauth.skill.md +0 -41
- package/templates/skills/payment.skill.md +0 -129
- package/templates/skills/queue.skill.md +0 -140
- package/templates/skills/rate-limit.skill.md +0 -38
- package/templates/skills/sms.skill.md +0 -39
- package/templates/skills/storage.skill.md +0 -42
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { Injectable } from '@nestjs/common';
|
|
2
|
-
import { QueueService } from '@svton/nestjs-queue';
|
|
3
|
-
import { EmailData } from './email.processor';
|
|
4
|
-
|
|
5
|
-
@Injectable()
|
|
6
|
-
export class EmailService {
|
|
7
|
-
constructor(private readonly queueService: QueueService) {}
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* 发送单个邮件
|
|
11
|
-
*/
|
|
12
|
-
async sendEmail(data: EmailData): Promise<void> {
|
|
13
|
-
await this.queueService.addJob('email', 'send', data);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* 延迟发送邮件
|
|
18
|
-
*/
|
|
19
|
-
async sendEmailDelayed(data: EmailData, delayMs: number): Promise<void> {
|
|
20
|
-
await this.queueService.addJob('email', 'send', data, {
|
|
21
|
-
delay: delayMs,
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* 发送邮件(带重试)
|
|
27
|
-
*/
|
|
28
|
-
async sendEmailWithRetry(data: EmailData): Promise<void> {
|
|
29
|
-
await this.queueService.addJob('email', 'send', data, {
|
|
30
|
-
attempts: 3,
|
|
31
|
-
backoff: {
|
|
32
|
-
type: 'exponential',
|
|
33
|
-
delay: 1000,
|
|
34
|
-
},
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* 批量发送邮件
|
|
40
|
-
*/
|
|
41
|
-
async sendBatchEmails(emails: EmailData[]): Promise<void> {
|
|
42
|
-
await this.queueService.addJob('email', 'sendBatch', { emails });
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* 定时发送邮件
|
|
47
|
-
*/
|
|
48
|
-
async scheduleEmail(data: EmailData, cron: string): Promise<void> {
|
|
49
|
-
await this.queueService.addJob('email', 'send', data, {
|
|
50
|
-
repeat: {
|
|
51
|
-
cron, // 例如: '0 9 * * *' 每天 9 点
|
|
52
|
-
},
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* 高优先级邮件
|
|
58
|
-
*/
|
|
59
|
-
async sendUrgentEmail(data: EmailData): Promise<void> {
|
|
60
|
-
await this.queueService.addJob('email', 'send', data, {
|
|
61
|
-
priority: 1, // 数字越小优先级越高
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
}
|
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
# 限流功能示例
|
|
2
|
-
|
|
3
|
-
本示例展示如何使用 `@svton/nestjs-rate-limit` 模块实现接口限流。
|
|
4
|
-
|
|
5
|
-
## 文件说明
|
|
6
|
-
|
|
7
|
-
- `api.controller.ts` - API 控制器,展示不同的限流策略
|
|
8
|
-
|
|
9
|
-
## 核心装饰器
|
|
10
|
-
|
|
11
|
-
### @RateLimit - 接口限流
|
|
12
|
-
|
|
13
|
-
```typescript
|
|
14
|
-
@RateLimit({ ttl: 60, limit: 10 })
|
|
15
|
-
@Get('api')
|
|
16
|
-
async api() {
|
|
17
|
-
return { message: 'Success' };
|
|
18
|
-
}
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
参数说明:
|
|
22
|
-
- `ttl`:时间窗口(秒)
|
|
23
|
-
- `limit`:时间窗口内最大请求数
|
|
24
|
-
|
|
25
|
-
### @UseGuards(RateLimitGuard) - 启用限流
|
|
26
|
-
|
|
27
|
-
```typescript
|
|
28
|
-
@Controller('api')
|
|
29
|
-
@UseGuards(RateLimitGuard)
|
|
30
|
-
export class ApiController {
|
|
31
|
-
// 所有接口都会应用限流
|
|
32
|
-
}
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
## 限流策略
|
|
36
|
-
|
|
37
|
-
### 1. 普通接口限流
|
|
38
|
-
|
|
39
|
-
适用于一般 API 接口:
|
|
40
|
-
|
|
41
|
-
```typescript
|
|
42
|
-
@RateLimit({ ttl: 60, limit: 10 }) // 每分钟 10 次
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
### 2. 严格限流
|
|
46
|
-
|
|
47
|
-
适用于敏感操作:
|
|
48
|
-
|
|
49
|
-
```typescript
|
|
50
|
-
@RateLimit({ ttl: 60, limit: 3 }) // 每分钟 3 次
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
### 3. 防暴力破解
|
|
54
|
-
|
|
55
|
-
适用于登录、注册等接口:
|
|
56
|
-
|
|
57
|
-
```typescript
|
|
58
|
-
@RateLimit({ ttl: 60, limit: 5 }) // 每分钟 5 次
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
### 4. 验证码限流
|
|
62
|
-
|
|
63
|
-
防止验证码被刷:
|
|
64
|
-
|
|
65
|
-
```typescript
|
|
66
|
-
@RateLimit({ ttl: 60, limit: 1 }) // 每分钟 1 次
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
### 5. 搜索接口限流
|
|
70
|
-
|
|
71
|
-
高频接口:
|
|
72
|
-
|
|
73
|
-
```typescript
|
|
74
|
-
@RateLimit({ ttl: 1, limit: 10 }) // 每秒 10 次
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
## 测试接口
|
|
78
|
-
|
|
79
|
-
### 普通限流(每分钟 10 次)
|
|
80
|
-
|
|
81
|
-
```bash
|
|
82
|
-
# 快速请求 15 次,第 11 次开始会被限流
|
|
83
|
-
for i in {1..15}; do
|
|
84
|
-
curl http://localhost:3000/examples/api/normal
|
|
85
|
-
echo ""
|
|
86
|
-
done
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
### 严格限流(每分钟 3 次)
|
|
90
|
-
|
|
91
|
-
```bash
|
|
92
|
-
# 快速请求 5 次,第 4 次开始会被限流
|
|
93
|
-
for i in {1..5}; do
|
|
94
|
-
curl http://localhost:3000/examples/api/strict
|
|
95
|
-
echo ""
|
|
96
|
-
done
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
### 验证码限流(每分钟 1 次)
|
|
100
|
-
|
|
101
|
-
```bash
|
|
102
|
-
# 请求 2 次,第 2 次会被限流
|
|
103
|
-
curl -X POST http://localhost:3000/examples/api/send-code
|
|
104
|
-
curl -X POST http://localhost:3000/examples/api/send-code
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
## 限流响应
|
|
108
|
-
|
|
109
|
-
当请求被限流时,会返回 429 状态码:
|
|
110
|
-
|
|
111
|
-
```json
|
|
112
|
-
{
|
|
113
|
-
"statusCode": 429,
|
|
114
|
-
"message": "Too Many Requests",
|
|
115
|
-
"error": "Rate limit exceeded"
|
|
116
|
-
}
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
响应头会包含限流信息:
|
|
120
|
-
|
|
121
|
-
```
|
|
122
|
-
X-RateLimit-Limit: 10
|
|
123
|
-
X-RateLimit-Remaining: 0
|
|
124
|
-
X-RateLimit-Reset: 1234567890
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
## 环境变量配置
|
|
128
|
-
|
|
129
|
-
在 `.env` 文件中配置:
|
|
130
|
-
|
|
131
|
-
```env
|
|
132
|
-
REDIS_HOST=localhost
|
|
133
|
-
REDIS_PORT=6379
|
|
134
|
-
REDIS_PASSWORD=
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
## 全局限流配置
|
|
138
|
-
|
|
139
|
-
在 `app.module.ts` 中配置全局限流:
|
|
140
|
-
|
|
141
|
-
```typescript
|
|
142
|
-
RateLimitModule.forRoot({
|
|
143
|
-
redis: {
|
|
144
|
-
host: process.env.REDIS_HOST,
|
|
145
|
-
port: parseInt(process.env.REDIS_PORT),
|
|
146
|
-
},
|
|
147
|
-
global: {
|
|
148
|
-
ttl: 60,
|
|
149
|
-
limit: 100,
|
|
150
|
-
},
|
|
151
|
-
});
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
## 自定义限流 Key
|
|
155
|
-
|
|
156
|
-
默认使用 IP 地址作为限流 key,可以自定义:
|
|
157
|
-
|
|
158
|
-
```typescript
|
|
159
|
-
@RateLimit({
|
|
160
|
-
ttl: 60,
|
|
161
|
-
limit: 10,
|
|
162
|
-
keyGenerator: (req) => {
|
|
163
|
-
// 使用用户 ID 作为 key
|
|
164
|
-
return req.user?.id || req.ip;
|
|
165
|
-
},
|
|
166
|
-
})
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
## 最佳实践
|
|
170
|
-
|
|
171
|
-
1. **合理设置限流**:根据接口特点设置不同的限流策略
|
|
172
|
-
2. **分级限流**:普通用户和 VIP 用户使用不同的限流规则
|
|
173
|
-
3. **白名单机制**:内部服务或管理员不受限流限制
|
|
174
|
-
4. **友好提示**:返回清晰的错误信息和重试时间
|
|
175
|
-
5. **监控告警**:监控限流触发情况,及时发现异常
|
|
176
|
-
|
|
177
|
-
## 常见场景
|
|
178
|
-
|
|
179
|
-
### 登录接口
|
|
180
|
-
|
|
181
|
-
```typescript
|
|
182
|
-
@Post('login')
|
|
183
|
-
@RateLimit({ ttl: 60, limit: 5 }) // 防暴力破解
|
|
184
|
-
async login() { }
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
### 注册接口
|
|
188
|
-
|
|
189
|
-
```typescript
|
|
190
|
-
@Post('register')
|
|
191
|
-
@RateLimit({ ttl: 3600, limit: 3 }) // 每小时 3 次
|
|
192
|
-
async register() { }
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
### 发送验证码
|
|
196
|
-
|
|
197
|
-
```typescript
|
|
198
|
-
@Post('send-code')
|
|
199
|
-
@RateLimit({ ttl: 60, limit: 1 }) // 每分钟 1 次
|
|
200
|
-
async sendCode() { }
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
### 搜索接口
|
|
204
|
-
|
|
205
|
-
```typescript
|
|
206
|
-
@Get('search')
|
|
207
|
-
@RateLimit({ ttl: 1, limit: 10 }) // 每秒 10 次
|
|
208
|
-
async search() { }
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
### 文件上传
|
|
212
|
-
|
|
213
|
-
```typescript
|
|
214
|
-
@Post('upload')
|
|
215
|
-
@RateLimit({ ttl: 60, limit: 5 }) // 每分钟 5 次
|
|
216
|
-
async upload() { }
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
## 高级用法
|
|
220
|
-
|
|
221
|
-
### 动态限流
|
|
222
|
-
|
|
223
|
-
根据用户等级动态调整限流:
|
|
224
|
-
|
|
225
|
-
```typescript
|
|
226
|
-
@RateLimit({
|
|
227
|
-
ttl: 60,
|
|
228
|
-
limit: (req) => {
|
|
229
|
-
const user = req.user;
|
|
230
|
-
if (user?.vip) return 100; // VIP 用户
|
|
231
|
-
return 10; // 普通用户
|
|
232
|
-
},
|
|
233
|
-
})
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
### 组合限流
|
|
237
|
-
|
|
238
|
-
同时应用多个限流规则:
|
|
239
|
-
|
|
240
|
-
```typescript
|
|
241
|
-
@RateLimit({ ttl: 1, limit: 10 }) // 每秒 10 次
|
|
242
|
-
@RateLimit({ ttl: 60, limit: 100 }) // 每分钟 100 次
|
|
243
|
-
@Get('api')
|
|
244
|
-
async api() { }
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
## 更多信息
|
|
248
|
-
|
|
249
|
-
查看官方文档:https://751848178.github.io/svton/packages/nestjs-rate-limit
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import { Controller, Get, Post, UseGuards } from '@nestjs/common';
|
|
2
|
-
import { RateLimit, RateLimitGuard } from '@svton/nestjs-rate-limit';
|
|
3
|
-
|
|
4
|
-
@Controller('examples/api')
|
|
5
|
-
@UseGuards(RateLimitGuard)
|
|
6
|
-
export class ApiController {
|
|
7
|
-
/**
|
|
8
|
-
* 普通接口 - 每分钟 10 次
|
|
9
|
-
*/
|
|
10
|
-
@Get('normal')
|
|
11
|
-
@RateLimit({ ttl: 60, limit: 10 })
|
|
12
|
-
normal() {
|
|
13
|
-
return {
|
|
14
|
-
message: 'Normal API',
|
|
15
|
-
timestamp: Date.now(),
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* 严格限流 - 每分钟 3 次
|
|
21
|
-
*/
|
|
22
|
-
@Get('strict')
|
|
23
|
-
@RateLimit({ ttl: 60, limit: 3 })
|
|
24
|
-
strict() {
|
|
25
|
-
return {
|
|
26
|
-
message: 'Strict rate limit API',
|
|
27
|
-
timestamp: Date.now(),
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* 宽松限流 - 每分钟 100 次
|
|
33
|
-
*/
|
|
34
|
-
@Get('loose')
|
|
35
|
-
@RateLimit({ ttl: 60, limit: 100 })
|
|
36
|
-
loose() {
|
|
37
|
-
return {
|
|
38
|
-
message: 'Loose rate limit API',
|
|
39
|
-
timestamp: Date.now(),
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* 短时限流 - 每 10 秒 5 次
|
|
45
|
-
*/
|
|
46
|
-
@Get('short-window')
|
|
47
|
-
@RateLimit({ ttl: 10, limit: 5 })
|
|
48
|
-
shortWindow() {
|
|
49
|
-
return {
|
|
50
|
-
message: 'Short window rate limit API',
|
|
51
|
-
timestamp: Date.now(),
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* 长时限流 - 每小时 1000 次
|
|
57
|
-
*/
|
|
58
|
-
@Get('long-window')
|
|
59
|
-
@RateLimit({ ttl: 3600, limit: 1000 })
|
|
60
|
-
longWindow() {
|
|
61
|
-
return {
|
|
62
|
-
message: 'Long window rate limit API',
|
|
63
|
-
timestamp: Date.now(),
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* 登录接口 - 每分钟 5 次(防暴力破解)
|
|
69
|
-
*/
|
|
70
|
-
@Post('login')
|
|
71
|
-
@RateLimit({ ttl: 60, limit: 5 })
|
|
72
|
-
login() {
|
|
73
|
-
return {
|
|
74
|
-
message: 'Login API',
|
|
75
|
-
token: 'mock_token',
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* 发送验证码 - 每分钟 1 次
|
|
81
|
-
*/
|
|
82
|
-
@Post('send-code')
|
|
83
|
-
@RateLimit({ ttl: 60, limit: 1 })
|
|
84
|
-
sendCode() {
|
|
85
|
-
return {
|
|
86
|
-
message: 'Verification code sent',
|
|
87
|
-
code: '123456', // 开发环境返回
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* 搜索接口 - 每秒 10 次
|
|
93
|
-
*/
|
|
94
|
-
@Get('search')
|
|
95
|
-
@RateLimit({ ttl: 1, limit: 10 })
|
|
96
|
-
search() {
|
|
97
|
-
return {
|
|
98
|
-
message: 'Search API',
|
|
99
|
-
results: [],
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* 无限流接口(用于测试)
|
|
105
|
-
*/
|
|
106
|
-
@Get('unlimited')
|
|
107
|
-
unlimited() {
|
|
108
|
-
return {
|
|
109
|
-
message: 'Unlimited API',
|
|
110
|
-
timestamp: Date.now(),
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
}
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
# 短信功能示例
|
|
2
|
-
|
|
3
|
-
本示例展示如何使用 `@svton/nestjs-sms` 模块发送短信。
|
|
4
|
-
|
|
5
|
-
## 文件说明
|
|
6
|
-
|
|
7
|
-
- `sms.service.ts` - 短信服务,封装发送逻辑
|
|
8
|
-
- `verification.controller.ts` - 验证码控制器,提供发送和验证接口
|
|
9
|
-
|
|
10
|
-
## 核心功能
|
|
11
|
-
|
|
12
|
-
### 发送验证码
|
|
13
|
-
|
|
14
|
-
```typescript
|
|
15
|
-
await this.smsService.sendVerificationCode('13800138000', '123456');
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
### 发送通知短信
|
|
19
|
-
|
|
20
|
-
```typescript
|
|
21
|
-
await this.smsService.sendNotification('13800138000', {
|
|
22
|
-
message: '您的订单已发货',
|
|
23
|
-
});
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
### 批量发送
|
|
27
|
-
|
|
28
|
-
```typescript
|
|
29
|
-
await this.smsService.sendBatch(
|
|
30
|
-
['13800138000', '13800138001'],
|
|
31
|
-
'SMS_123456',
|
|
32
|
-
{ code: '123456' },
|
|
33
|
-
);
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
## 测试接口
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
# 发送验证码
|
|
40
|
-
curl -X POST http://localhost:3000/examples/verification/send-code \
|
|
41
|
-
-H "Content-Type: application/json" \
|
|
42
|
-
-d '{"phoneNumber":"13800138000"}'
|
|
43
|
-
|
|
44
|
-
# 验证验证码
|
|
45
|
-
curl -X POST http://localhost:3000/examples/verification/verify-code \
|
|
46
|
-
-H "Content-Type: application/json" \
|
|
47
|
-
-d '{
|
|
48
|
-
"phoneNumber":"13800138000",
|
|
49
|
-
"code":"123456"
|
|
50
|
-
}'
|
|
51
|
-
|
|
52
|
-
# 发送通知短信
|
|
53
|
-
curl -X POST http://localhost:3000/examples/verification/send-notification \
|
|
54
|
-
-H "Content-Type: application/json" \
|
|
55
|
-
-d '{
|
|
56
|
-
"phoneNumber":"13800138000",
|
|
57
|
-
"message":"您的订单已发货"
|
|
58
|
-
}'
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
## 环境变量配置
|
|
62
|
-
|
|
63
|
-
在 `.env` 文件中配置:
|
|
64
|
-
|
|
65
|
-
```env
|
|
66
|
-
SMS_PROVIDER=aliyun
|
|
67
|
-
SMS_ACCESS_KEY_ID=your_access_key_id
|
|
68
|
-
SMS_ACCESS_KEY_SECRET=your_access_key_secret
|
|
69
|
-
SMS_SIGN_NAME=your_sign_name
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
## 短信模板配置
|
|
73
|
-
|
|
74
|
-
需要在阿里云/腾讯云后台配置短信模板:
|
|
75
|
-
|
|
76
|
-
1. 登录短信服务控制台
|
|
77
|
-
2. 创建短信模板
|
|
78
|
-
3. 等待审核通过
|
|
79
|
-
4. 将模板 ID 替换到代码中的 `SMS_123456`
|
|
80
|
-
|
|
81
|
-
## 最佳实践
|
|
82
|
-
|
|
83
|
-
1. **频率限制**:同一手机号 1 分钟内只能发送一次
|
|
84
|
-
2. **验证码有效期**:建议 5 分钟
|
|
85
|
-
3. **使用 Redis**:生产环境使用 Redis 存储验证码
|
|
86
|
-
4. **防刷机制**:添加图形验证码或滑块验证
|
|
87
|
-
5. **日志记录**:记录发送日志,便于排查问题
|
|
88
|
-
|
|
89
|
-
## 常见场景
|
|
90
|
-
|
|
91
|
-
### 注册验证
|
|
92
|
-
|
|
93
|
-
```typescript
|
|
94
|
-
// 1. 发送验证码
|
|
95
|
-
await this.smsService.sendVerificationCode(phoneNumber, code);
|
|
96
|
-
|
|
97
|
-
// 2. 用户输入验证码
|
|
98
|
-
// 3. 验证通过后创建账号
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
### 登录验证
|
|
102
|
-
|
|
103
|
-
```typescript
|
|
104
|
-
// 1. 发送验证码
|
|
105
|
-
await this.smsService.sendVerificationCode(phoneNumber, code);
|
|
106
|
-
|
|
107
|
-
// 2. 验证码验证通过后生成 token
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
### 订单通知
|
|
111
|
-
|
|
112
|
-
```typescript
|
|
113
|
-
await this.smsService.sendNotification(phoneNumber, {
|
|
114
|
-
orderNo: 'ORDER_001',
|
|
115
|
-
status: '已发货',
|
|
116
|
-
});
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
## 更多信息
|
|
120
|
-
|
|
121
|
-
查看官方文档:https://751848178.github.io/svton/packages/nestjs-sms
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { Injectable } from '@nestjs/common';
|
|
2
|
-
import { SmsService as SvtonSmsService } from '@svton/nestjs-sms';
|
|
3
|
-
|
|
4
|
-
@Injectable()
|
|
5
|
-
export class SmsService {
|
|
6
|
-
constructor(private readonly smsService: SvtonSmsService) {}
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* 发送验证码短信
|
|
10
|
-
*/
|
|
11
|
-
async sendVerificationCode(phoneNumber: string, code: string): Promise<void> {
|
|
12
|
-
await this.smsService.send({
|
|
13
|
-
phoneNumber,
|
|
14
|
-
templateCode: 'SMS_123456', // TODO: 替换为实际的模板 ID
|
|
15
|
-
templateParams: {
|
|
16
|
-
code,
|
|
17
|
-
},
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* 发送通知短信
|
|
23
|
-
*/
|
|
24
|
-
async sendNotification(
|
|
25
|
-
phoneNumber: string,
|
|
26
|
-
params: Record<string, string>,
|
|
27
|
-
): Promise<void> {
|
|
28
|
-
await this.smsService.send({
|
|
29
|
-
phoneNumber,
|
|
30
|
-
templateCode: 'SMS_234567', // TODO: 替换为实际的模板 ID
|
|
31
|
-
templateParams: params,
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* 批量发送短信
|
|
37
|
-
*/
|
|
38
|
-
async sendBatch(
|
|
39
|
-
phoneNumbers: string[],
|
|
40
|
-
templateCode: string,
|
|
41
|
-
templateParams: Record<string, string>,
|
|
42
|
-
): Promise<void> {
|
|
43
|
-
await Promise.all(
|
|
44
|
-
phoneNumbers.map((phoneNumber) =>
|
|
45
|
-
this.smsService.send({
|
|
46
|
-
phoneNumber,
|
|
47
|
-
templateCode,
|
|
48
|
-
templateParams,
|
|
49
|
-
}),
|
|
50
|
-
),
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* 发送营销短信
|
|
56
|
-
*/
|
|
57
|
-
async sendMarketing(
|
|
58
|
-
phoneNumber: string,
|
|
59
|
-
content: string,
|
|
60
|
-
): Promise<void> {
|
|
61
|
-
await this.smsService.send({
|
|
62
|
-
phoneNumber,
|
|
63
|
-
templateCode: 'SMS_345678', // TODO: 替换为实际的模板 ID
|
|
64
|
-
templateParams: {
|
|
65
|
-
content,
|
|
66
|
-
},
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
}
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { Controller, Post, Body, HttpCode } from '@nestjs/common';
|
|
2
|
-
import { SmsService } from './sms.service';
|
|
3
|
-
|
|
4
|
-
interface SendCodeDto {
|
|
5
|
-
phoneNumber: string;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
interface VerifyCodeDto {
|
|
9
|
-
phoneNumber: string;
|
|
10
|
-
code: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
@Controller('examples/verification')
|
|
14
|
-
export class VerificationController {
|
|
15
|
-
// 简单的内存存储,实际项目应使用 Redis
|
|
16
|
-
private verificationCodes = new Map<string, { code: string; expiresAt: number }>();
|
|
17
|
-
|
|
18
|
-
constructor(private readonly smsService: SmsService) {}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* 发送验证码
|
|
22
|
-
*/
|
|
23
|
-
@Post('send-code')
|
|
24
|
-
@HttpCode(200)
|
|
25
|
-
async sendCode(@Body() dto: SendCodeDto) {
|
|
26
|
-
// 生成 6 位验证码
|
|
27
|
-
const code = Math.floor(100000 + Math.random() * 900000).toString();
|
|
28
|
-
|
|
29
|
-
// 存储验证码(5 分钟有效期)
|
|
30
|
-
this.verificationCodes.set(dto.phoneNumber, {
|
|
31
|
-
code,
|
|
32
|
-
expiresAt: Date.now() + 5 * 60 * 1000,
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
// 发送短信
|
|
36
|
-
await this.smsService.sendVerificationCode(dto.phoneNumber, code);
|
|
37
|
-
|
|
38
|
-
return {
|
|
39
|
-
message: 'Verification code sent successfully',
|
|
40
|
-
// 开发环境返回验证码,生产环境不应返回
|
|
41
|
-
...(process.env.NODE_ENV === 'development' && { code }),
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* 验证验证码
|
|
47
|
-
*/
|
|
48
|
-
@Post('verify-code')
|
|
49
|
-
@HttpCode(200)
|
|
50
|
-
async verifyCode(@Body() dto: VerifyCodeDto) {
|
|
51
|
-
const stored = this.verificationCodes.get(dto.phoneNumber);
|
|
52
|
-
|
|
53
|
-
if (!stored) {
|
|
54
|
-
return {
|
|
55
|
-
success: false,
|
|
56
|
-
message: 'Verification code not found',
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (Date.now() > stored.expiresAt) {
|
|
61
|
-
this.verificationCodes.delete(dto.phoneNumber);
|
|
62
|
-
return {
|
|
63
|
-
success: false,
|
|
64
|
-
message: 'Verification code expired',
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (stored.code !== dto.code) {
|
|
69
|
-
return {
|
|
70
|
-
success: false,
|
|
71
|
-
message: 'Invalid verification code',
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// 验证成功,删除验证码
|
|
76
|
-
this.verificationCodes.delete(dto.phoneNumber);
|
|
77
|
-
|
|
78
|
-
return {
|
|
79
|
-
success: true,
|
|
80
|
-
message: 'Verification successful',
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* 发送通知短信
|
|
86
|
-
*/
|
|
87
|
-
@Post('send-notification')
|
|
88
|
-
@HttpCode(200)
|
|
89
|
-
async sendNotification(
|
|
90
|
-
@Body() dto: { phoneNumber: string; message: string },
|
|
91
|
-
) {
|
|
92
|
-
await this.smsService.sendNotification(dto.phoneNumber, {
|
|
93
|
-
message: dto.message,
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
return {
|
|
97
|
-
message: 'Notification sent successfully',
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
}
|