ai-engineering-init 1.4.3 → 1.6.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/.cursor/skills/bug-detective/SKILL.md +19 -19
- package/.cursor/skills/project-navigator/SKILL.md +164 -258
- package/README.md +20 -236
- package/bin/index.js +437 -7
- package/package.json +7 -1
- package/scripts/build-skills.js +180 -0
- package/src/platform-map.json +56 -0
- package/src/skills/add-skill/SKILL.md +488 -0
- package/src/skills/add-todo/SKILL.md +269 -0
- package/src/skills/api-development/SKILL.md +266 -0
- package/src/skills/architecture-design/SKILL.md +262 -0
- package/src/skills/backend-annotations/SKILL.md +302 -0
- package/src/skills/banana-image/CHANGELOG.md +37 -0
- package/src/skills/banana-image/README.md +146 -0
- package/src/skills/banana-image/SKILL.md +171 -0
- package/src/skills/banana-image/assets/logo.png +0 -0
- package/src/skills/banana-image/references/advanced-usage.md +189 -0
- package/src/skills/banana-image/scripts/apply_template.py +125 -0
- package/src/skills/banana-image/scripts/banana_image_exec.ts +412 -0
- package/src/skills/banana-image/scripts/batch_prep.py +82 -0
- package/src/skills/banana-image/scripts/package-lock.json +1437 -0
- package/src/skills/banana-image/scripts/package.json +18 -0
- package/src/skills/banana-image/scripts/requirements.txt +10 -0
- package/src/skills/banana-image/templates/poster.json +22 -0
- package/src/skills/banana-image/templates/product.json +17 -0
- package/src/skills/banana-image/templates/social.json +22 -0
- package/src/skills/banana-image/templates/thumbnail.json +17 -0
- package/src/skills/brainstorm/SKILL.md +216 -0
- package/src/skills/bug-detective/SKILL.md +256 -0
- package/src/skills/bug-detective/references/error-patterns.md +242 -0
- package/src/skills/check/SKILL.md +367 -0
- package/src/skills/code-patterns/SKILL.md +280 -0
- package/src/skills/code-patterns/references/leniu-code-patterns.md +87 -0
- package/src/skills/codex-code-review/SKILL.md +135 -0
- package/src/skills/collaborating-with-codex/SKILL.md +174 -0
- package/src/skills/collaborating-with-codex/scripts/codex_bridge.py +275 -0
- package/src/skills/collaborating-with-gemini/SKILL.md +194 -0
- package/src/skills/collaborating-with-gemini/scripts/gemini_bridge.py +275 -0
- package/src/skills/crud/SKILL.md +265 -0
- package/src/skills/crud-development/SKILL.md +409 -0
- package/src/skills/data-permission/SKILL.md +292 -0
- package/src/skills/data-permission/references/custom-data-scope.md +90 -0
- package/src/skills/database-ops/SKILL.md +407 -0
- package/src/skills/dev/SKILL.md +187 -0
- package/src/skills/error-handler/SKILL.md +371 -0
- package/src/skills/file-oss-management/SKILL.md +255 -0
- package/src/skills/file-oss-management/references/entities.md +105 -0
- package/src/skills/file-oss-management/references/service-impl.md +104 -0
- package/src/skills/git-workflow/SKILL.md +397 -0
- package/src/skills/init-docs/SKILL.md +194 -0
- package/src/skills/json-serialization/SKILL.md +357 -0
- package/src/skills/leniu-api-development/SKILL.md +319 -0
- package/src/skills/leniu-api-development/references/real-examples.md +273 -0
- package/src/skills/leniu-architecture-design/SKILL.md +383 -0
- package/src/skills/leniu-backend-annotations/SKILL.md +277 -0
- package/src/skills/leniu-brainstorm/SKILL.md +242 -0
- package/src/skills/leniu-brainstorm/references/business-scenarios.md +162 -0
- package/src/skills/leniu-code-patterns/SKILL.md +411 -0
- package/src/skills/leniu-crud-development/SKILL.md +404 -0
- package/src/skills/leniu-crud-development/references/templates.md +597 -0
- package/src/skills/leniu-customization-location/SKILL.md +410 -0
- package/src/skills/leniu-data-permission/SKILL.md +341 -0
- package/src/skills/leniu-database-ops/SKILL.md +426 -0
- package/src/skills/leniu-error-handler/SKILL.md +462 -0
- package/src/skills/leniu-java-amount-handling/SKILL.md +461 -0
- package/src/skills/leniu-java-code-style/SKILL.md +510 -0
- package/src/skills/leniu-java-concurrent/SKILL.md +400 -0
- package/src/skills/leniu-java-entity/SKILL.md +237 -0
- package/src/skills/leniu-java-entity/references/templates.md +237 -0
- package/src/skills/leniu-java-export/SKILL.md +570 -0
- package/src/skills/leniu-java-logging/SKILL.md +229 -0
- package/src/skills/leniu-java-logging/references/data-mask.md +46 -0
- package/src/skills/leniu-java-logging/references/logging-scenarios.md +113 -0
- package/src/skills/leniu-java-mq/SKILL.md +338 -0
- package/src/skills/leniu-java-mybatis/SKILL.md +267 -0
- package/src/skills/leniu-java-mybatis/references/report-mapper.md +88 -0
- package/src/skills/leniu-java-report-query-param/SKILL.md +291 -0
- package/src/skills/leniu-java-task/SKILL.md +367 -0
- package/src/skills/leniu-java-total-line/SKILL.md +196 -0
- package/src/skills/leniu-marketing-price-rule-customizer/SKILL.md +301 -0
- package/src/skills/leniu-marketing-recharge-rule-customizer/SKILL.md +285 -0
- package/src/skills/leniu-mealtime/SKILL.md +215 -0
- package/src/skills/leniu-redis-cache/SKILL.md +331 -0
- package/src/skills/leniu-report-customization/SKILL.md +335 -0
- package/src/skills/leniu-report-customization/references/table-fields.md +93 -0
- package/src/skills/leniu-report-standard-customization/SKILL.md +328 -0
- package/src/skills/leniu-report-standard-customization/references/analysis-module.md +64 -0
- package/src/skills/leniu-report-standard-customization/references/table-fields.md +113 -0
- package/src/skills/leniu-security-guard/SKILL.md +306 -0
- package/src/skills/leniu-utils-toolkit/SKILL.md +380 -0
- package/src/skills/mysql-debug/SKILL.md +364 -0
- package/src/skills/next/SKILL.md +137 -0
- package/src/skills/openspec-apply-change/SKILL.md +165 -0
- package/src/skills/openspec-archive-change/SKILL.md +122 -0
- package/src/skills/openspec-bulk-archive-change/SKILL.md +254 -0
- package/src/skills/openspec-continue-change/SKILL.md +126 -0
- package/src/skills/openspec-explore/SKILL.md +299 -0
- package/src/skills/openspec-ff-change/SKILL.md +109 -0
- package/src/skills/openspec-new-change/SKILL.md +82 -0
- package/src/skills/openspec-onboard/SKILL.md +414 -0
- package/src/skills/openspec-sync-specs/SKILL.md +146 -0
- package/src/skills/openspec-verify-change/SKILL.md +176 -0
- package/src/skills/performance-doctor/SKILL.md +303 -0
- package/src/skills/progress/SKILL.md +193 -0
- package/src/skills/project-navigator/SKILL.md +211 -0
- package/src/skills/redis-cache/SKILL.md +333 -0
- package/src/skills/redis-cache/references/listeners.md +23 -0
- package/src/skills/scheduled-jobs/SKILL.md +314 -0
- package/src/skills/security-guard/SKILL.md +353 -0
- package/src/skills/security-guard/references/encrypt-config.md +103 -0
- package/src/skills/security-guard/references/sensitive-strategies.md +42 -0
- package/src/skills/sms-mail/SKILL.md +308 -0
- package/src/skills/sms-mail/references/mail-config.md +88 -0
- package/src/skills/sms-mail/references/sms-config.md +74 -0
- package/src/skills/social-login/SKILL.md +266 -0
- package/src/skills/social-login/references/provider-configs.md +118 -0
- package/src/skills/start/SKILL.md +154 -0
- package/src/skills/store-pc/SKILL.md +366 -0
- package/src/skills/sync/SKILL.md +149 -0
- package/src/skills/task-tracker/SKILL.md +307 -0
- package/src/skills/tech-decision/SKILL.md +393 -0
- package/src/skills/tenant-management/SKILL.md +288 -0
- package/src/skills/tenant-management/references/tenant-scenarios.md +91 -0
- package/src/skills/test-development/SKILL.md +301 -0
- package/src/skills/test-development/references/parameterized-examples.md +119 -0
- package/src/skills/ui-pc/SKILL.md +438 -0
- package/src/skills/update-status/SKILL.md +159 -0
- package/src/skills/utils-toolkit/SKILL.md +362 -0
- package/src/skills/utils-toolkit/references/redis-utils-api.md +56 -0
- package/src/skills/websocket-sse/SKILL.md +271 -0
- package/src/skills/workflow-engine/SKILL.md +321 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# 数据加密详细配置
|
|
2
|
+
|
|
3
|
+
## 加密配置(application.yml)
|
|
4
|
+
|
|
5
|
+
```yaml
|
|
6
|
+
mybatis-encryptor:
|
|
7
|
+
enable: true
|
|
8
|
+
algorithm: AES
|
|
9
|
+
password: your-aes-key-16bit # AES 密钥(16/24/32位)
|
|
10
|
+
encode: BASE64
|
|
11
|
+
publicKey: xxx # RSA 公钥
|
|
12
|
+
privateKey: xxx # RSA 私钥
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## EncryptUtils 工具类
|
|
16
|
+
|
|
17
|
+
```java
|
|
18
|
+
import org.dromara.common.encrypt.utils.EncryptUtils;
|
|
19
|
+
|
|
20
|
+
// AES
|
|
21
|
+
String encrypted = EncryptUtils.encryptByAes(data, aesKey);
|
|
22
|
+
String decrypted = EncryptUtils.decryptByAes(encrypted, aesKey);
|
|
23
|
+
|
|
24
|
+
// RSA(公钥加密,私钥解密)
|
|
25
|
+
String encrypted = EncryptUtils.encryptByRsa(data, publicKey);
|
|
26
|
+
String decrypted = EncryptUtils.decryptByRsa(encrypted, privateKey);
|
|
27
|
+
|
|
28
|
+
// SM2 国密
|
|
29
|
+
String encrypted = EncryptUtils.encryptBySm2(data, publicKey);
|
|
30
|
+
String decrypted = EncryptUtils.decryptBySm2(encrypted, privateKey);
|
|
31
|
+
|
|
32
|
+
// SM4 国密
|
|
33
|
+
String encrypted = EncryptUtils.encryptBySm4(data, sm4Key);
|
|
34
|
+
String decrypted = EncryptUtils.decryptBySm4(encrypted, sm4Key);
|
|
35
|
+
|
|
36
|
+
// BASE64
|
|
37
|
+
String encoded = EncryptUtils.encryptByBase64(data);
|
|
38
|
+
String decoded = EncryptUtils.decryptByBase64(encoded);
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## 字段加密完整示例
|
|
42
|
+
|
|
43
|
+
```java
|
|
44
|
+
import org.dromara.common.encrypt.annotation.EncryptField;
|
|
45
|
+
import org.dromara.common.encrypt.enumd.AlgorithmType;
|
|
46
|
+
import org.dromara.common.encrypt.enumd.EncodeType;
|
|
47
|
+
|
|
48
|
+
public class User {
|
|
49
|
+
@EncryptField // 使用全局配置
|
|
50
|
+
private String password;
|
|
51
|
+
|
|
52
|
+
@EncryptField(algorithm = AlgorithmType.AES) // AES
|
|
53
|
+
private String idCard;
|
|
54
|
+
|
|
55
|
+
@EncryptField(algorithm = AlgorithmType.SM4) // SM4
|
|
56
|
+
private String phone;
|
|
57
|
+
|
|
58
|
+
@EncryptField(algorithm = AlgorithmType.AES, encode = EncodeType.HEX) // AES + HEX 编码
|
|
59
|
+
private String bankCard;
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## @RateLimiter 注解参数
|
|
64
|
+
|
|
65
|
+
| 参数 | 类型 | 默认值 | 说明 |
|
|
66
|
+
|------|------|--------|------|
|
|
67
|
+
| `key` | String | `""` | 限流 key,支持 SpEL |
|
|
68
|
+
| `time` | int | `60` | 时间窗口(秒) |
|
|
69
|
+
| `count` | int | `100` | 最大请求次数 |
|
|
70
|
+
| `limitType` | LimitType | `DEFAULT` | 限流类型(DEFAULT/IP/CLUSTER) |
|
|
71
|
+
| `message` | String | 国际化 key | 错误提示 |
|
|
72
|
+
| `timeout` | int | `86400` | Redis 超时时间(秒) |
|
|
73
|
+
|
|
74
|
+
## @RepeatSubmit 注解参数
|
|
75
|
+
|
|
76
|
+
| 参数 | 类型 | 默认值 | 说明 |
|
|
77
|
+
|------|------|--------|------|
|
|
78
|
+
| `interval` | int | `5000` | 间隔时间 |
|
|
79
|
+
| `timeUnit` | TimeUnit | `MILLISECONDS` | 时间单位 |
|
|
80
|
+
| `message` | String | 国际化 key | 错误提示 |
|
|
81
|
+
|
|
82
|
+
## 文件上传安全模板
|
|
83
|
+
|
|
84
|
+
```java
|
|
85
|
+
private static final Set<String> ALLOWED_TYPES = Set.of(
|
|
86
|
+
"image/jpeg", "image/png", "image/gif", "image/webp"
|
|
87
|
+
);
|
|
88
|
+
private static final long MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
|
|
89
|
+
|
|
90
|
+
public void uploadFile(MultipartFile file) {
|
|
91
|
+
if (!ALLOWED_TYPES.contains(file.getContentType())) {
|
|
92
|
+
throw new ServiceException("不支持的文件类型");
|
|
93
|
+
}
|
|
94
|
+
if (file.getSize() > MAX_FILE_SIZE) {
|
|
95
|
+
throw new ServiceException("文件大小不能超过10MB");
|
|
96
|
+
}
|
|
97
|
+
String extension = getExtension(file.getOriginalFilename());
|
|
98
|
+
if (!Set.of(".jpg", ".jpeg", ".png", ".gif", ".webp").contains(extension.toLowerCase())) {
|
|
99
|
+
throw new ServiceException("不支持的文件扩展名");
|
|
100
|
+
}
|
|
101
|
+
String newName = UUID.randomUUID() + extension; // 重命名防路径穿越
|
|
102
|
+
}
|
|
103
|
+
```
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# 数据脱敏策略完整列表(17种)
|
|
2
|
+
|
|
3
|
+
> 来源:`ruoyi-common-sensitive/.../core/SensitiveStrategy`
|
|
4
|
+
|
|
5
|
+
| 策略 | 枚举值 | 脱敏效果 | 说明 |
|
|
6
|
+
|------|--------|---------|------|
|
|
7
|
+
| 身份证 | `ID_CARD` | `110***********1234` | 保留前3位和后4位 |
|
|
8
|
+
| 手机号 | `PHONE` | `138****8888` | 保留前3位和后4位 |
|
|
9
|
+
| 邮箱 | `EMAIL` | `t**@example.com` | 保留用户名首尾和完整域名 |
|
|
10
|
+
| 银行卡 | `BANK_CARD` | `6222***********1234` | 保留前4位和后4位 |
|
|
11
|
+
| 中文姓名 | `CHINESE_NAME` | `张*` | 保留姓氏 |
|
|
12
|
+
| 地址 | `ADDRESS` | `北京市朝阳区****` | 保留前8个字符 |
|
|
13
|
+
| 固定电话 | `FIXED_PHONE` | `010****1234` | 保留区号和后4位 |
|
|
14
|
+
| 密码 | `PASSWORD` | `******` | 全部掩码 |
|
|
15
|
+
| IPv4 地址 | `IPV4` | `192.168.***.***` | 保留网络段 |
|
|
16
|
+
| IPv6 地址 | `IPV6` | 部分隐藏 | 保留前缀 |
|
|
17
|
+
| 车牌号 | `CAR_LICENSE` | `京A***12` | 支持普通和新能源 |
|
|
18
|
+
| 用户ID | `USER_ID` | 随机数字 | 生成随机数字替代 |
|
|
19
|
+
| 首字符保留 | `FIRST_MASK` | `张***` | 只显示第一个字符 |
|
|
20
|
+
| 通用掩码 | `STRING_MASK` | `1234**7890` | 前4+4个*+后4 |
|
|
21
|
+
| 高安全级别 | `MASK_HIGH_SECURITY` | `to******en` | 前2后2可见 |
|
|
22
|
+
| 清空 | `CLEAR` | 空字符串 | 返回空字符串 |
|
|
23
|
+
| 置空 | `CLEAR_TO_NULL` | `null` | 返回 null |
|
|
24
|
+
|
|
25
|
+
## 用法示例
|
|
26
|
+
|
|
27
|
+
```java
|
|
28
|
+
@Sensitive(strategy = SensitiveStrategy.PHONE)
|
|
29
|
+
private String phone;
|
|
30
|
+
|
|
31
|
+
@Sensitive(strategy = SensitiveStrategy.ID_CARD)
|
|
32
|
+
private String idCard;
|
|
33
|
+
|
|
34
|
+
@Sensitive(strategy = SensitiveStrategy.ADDRESS)
|
|
35
|
+
private String address;
|
|
36
|
+
|
|
37
|
+
@Sensitive(strategy = SensitiveStrategy.IPV4)
|
|
38
|
+
private String loginIp;
|
|
39
|
+
|
|
40
|
+
@Sensitive(strategy = SensitiveStrategy.CAR_LICENSE)
|
|
41
|
+
private String carNumber;
|
|
42
|
+
```
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sms-mail
|
|
3
|
+
description: |
|
|
4
|
+
当需要发送短信、邮件通知时自动使用此 Skill。
|
|
5
|
+
|
|
6
|
+
触发场景:
|
|
7
|
+
- 需要发送短信验证码
|
|
8
|
+
- 需要发送邮件通知(文本/HTML)
|
|
9
|
+
- 需要配置短信服务商(阿里云/腾讯云/华为云等)
|
|
10
|
+
- 需要配置邮件服务器(SMTP)
|
|
11
|
+
- 需要实现多渠道消息通知
|
|
12
|
+
|
|
13
|
+
触发词:短信、邮件、SMS、验证码、通知、SMS4j、MailUtils、邮件发送、短信发送、SmsBlend、SmsFactory、SMTP、模板消息
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# 短信与邮件开发指南
|
|
17
|
+
|
|
18
|
+
> 短信模块:`ruoyi-common/ruoyi-common-sms`(SMS4j)
|
|
19
|
+
> 邮件模块:`ruoyi-common/ruoyi-common-mail`(Hutool JakartaMail)
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 一、短信开发(SMS4j)
|
|
24
|
+
|
|
25
|
+
### 1.1 配置
|
|
26
|
+
|
|
27
|
+
```yaml
|
|
28
|
+
sms:
|
|
29
|
+
config-type: yaml # yaml / 接口 / 数据库
|
|
30
|
+
blends:
|
|
31
|
+
config1:
|
|
32
|
+
supplier: alibaba
|
|
33
|
+
access-key-id: ${ALIYUN_SMS_ACCESS_KEY_ID}
|
|
34
|
+
access-key-secret: ${ALIYUN_SMS_ACCESS_KEY_SECRET}
|
|
35
|
+
signature: 若依框架
|
|
36
|
+
config2:
|
|
37
|
+
supplier: tencent
|
|
38
|
+
access-key-id: ${TENCENT_SMS_SECRET_ID}
|
|
39
|
+
access-key-secret: ${TENCENT_SMS_SECRET_KEY}
|
|
40
|
+
signature: 若依框架
|
|
41
|
+
sdk-app-id: "1400000000" # 腾讯云需要
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
> 支持的服务商:alibaba / tencent / huawei / yunpian / unisms / jdcloud / cloopen / emay / ctyun
|
|
45
|
+
> 限流配置详见 [references/sms-config.md](references/sms-config.md)
|
|
46
|
+
|
|
47
|
+
### 1.2 核心 API
|
|
48
|
+
|
|
49
|
+
```java
|
|
50
|
+
import org.dromara.sms4j.api.SmsBlend;
|
|
51
|
+
import org.dromara.sms4j.api.entity.SmsResponse;
|
|
52
|
+
import org.dromara.sms4j.core.factory.SmsFactory;
|
|
53
|
+
|
|
54
|
+
// 获取短信实例
|
|
55
|
+
SmsBlend smsBlend = SmsFactory.getSmsBlend(); // 默认配置
|
|
56
|
+
SmsBlend smsBlend = SmsFactory.getSmsBlend("config1"); // 指定配置
|
|
57
|
+
|
|
58
|
+
// 发送模板短信
|
|
59
|
+
Map<String, String> params = new LinkedHashMap<>();
|
|
60
|
+
params.put("code", "123456");
|
|
61
|
+
params.put("time", "5");
|
|
62
|
+
SmsResponse response = smsBlend.sendMessage("13800138000", "SMS_123456789", params);
|
|
63
|
+
|
|
64
|
+
// 批量发送
|
|
65
|
+
List<String> phones = Arrays.asList("13800138001", "13800138002");
|
|
66
|
+
SmsResponse response = smsBlend.sendMessage(phones, "SMS_123456789", params);
|
|
67
|
+
|
|
68
|
+
// 检查结果
|
|
69
|
+
if (response.isSuccess()) {
|
|
70
|
+
log.info("发送成功,msgId: {}", response.getBizId());
|
|
71
|
+
} else {
|
|
72
|
+
log.error("发送失败:{}", response.getMessage());
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 1.3 验证码短信示例
|
|
77
|
+
|
|
78
|
+
```java
|
|
79
|
+
@RestController
|
|
80
|
+
@RequestMapping("/captcha")
|
|
81
|
+
@RequiredArgsConstructor
|
|
82
|
+
public class CaptchaController {
|
|
83
|
+
|
|
84
|
+
@GetMapping("/sms")
|
|
85
|
+
public R<Void> sendSmsCode(@RequestParam String phonenumber) {
|
|
86
|
+
String code = RandomUtil.randomNumbers(6);
|
|
87
|
+
|
|
88
|
+
// 存入Redis(5分钟有效)
|
|
89
|
+
RedisUtils.setCacheObject("sms:code:" + phonenumber, code, Duration.ofMinutes(5));
|
|
90
|
+
|
|
91
|
+
// 发送短信
|
|
92
|
+
Map<String, String> map = new LinkedHashMap<>();
|
|
93
|
+
map.put("code", code);
|
|
94
|
+
SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
|
|
95
|
+
SmsResponse smsResponse = smsBlend.sendMessage(phonenumber, "SMS_123456789", map);
|
|
96
|
+
|
|
97
|
+
if (!smsResponse.isSuccess()) {
|
|
98
|
+
log.error("短信发送失败:{}", smsResponse.getMessage());
|
|
99
|
+
return R.fail("短信发送失败");
|
|
100
|
+
}
|
|
101
|
+
return R.ok("验证码已发送");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
@PostMapping("/verify")
|
|
105
|
+
public R<Boolean> verifySmsCode(@RequestParam String phonenumber,
|
|
106
|
+
@RequestParam String code) {
|
|
107
|
+
String cacheKey = "sms:code:" + phonenumber;
|
|
108
|
+
String cachedCode = RedisUtils.getCacheObject(cacheKey);
|
|
109
|
+
|
|
110
|
+
if (cachedCode == null) return R.fail("验证码已过期");
|
|
111
|
+
if (!cachedCode.equals(code)) return R.fail("验证码错误");
|
|
112
|
+
|
|
113
|
+
RedisUtils.deleteObject(cacheKey);
|
|
114
|
+
return R.ok(true);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## 二、邮件开发(Hutool Mail)
|
|
122
|
+
|
|
123
|
+
### 2.1 配置
|
|
124
|
+
|
|
125
|
+
```yaml
|
|
126
|
+
mail:
|
|
127
|
+
enabled: true
|
|
128
|
+
host: smtp.163.com
|
|
129
|
+
port: 465
|
|
130
|
+
auth: true
|
|
131
|
+
from: system@example.com
|
|
132
|
+
user: system@example.com
|
|
133
|
+
pass: ${MAIL_PASSWORD} # 授权码,非登录密码
|
|
134
|
+
sslEnable: true
|
|
135
|
+
starttlsEnable: false
|
|
136
|
+
timeout: 10000
|
|
137
|
+
connectionTimeout: 10000
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
> 常用 SMTP 服务器配置详见 [references/mail-config.md](references/mail-config.md)
|
|
141
|
+
|
|
142
|
+
### 2.2 核心 API
|
|
143
|
+
|
|
144
|
+
```java
|
|
145
|
+
import org.dromara.common.mail.utils.MailUtils;
|
|
146
|
+
|
|
147
|
+
// 发送文本邮件
|
|
148
|
+
String msgId = MailUtils.sendText("to@example.com", "标题", "正文内容");
|
|
149
|
+
|
|
150
|
+
// 发送HTML邮件
|
|
151
|
+
String msgId = MailUtils.sendHtml("to@example.com", "标题", "<h1>内容</h1>");
|
|
152
|
+
|
|
153
|
+
// 带附件
|
|
154
|
+
String msgId = MailUtils.sendText("to@example.com", "标题", "正文", attachment);
|
|
155
|
+
String msgId = MailUtils.sendHtml("to@example.com", "标题", htmlContent, attachment);
|
|
156
|
+
|
|
157
|
+
// 带内嵌图片
|
|
158
|
+
Map<String, InputStream> imageMap = Map.of("logo", new FileInputStream("logo.png"));
|
|
159
|
+
String msgId = MailUtils.sendHtml("to@example.com", "标题",
|
|
160
|
+
"<img src='cid:logo'/>", imageMap);
|
|
161
|
+
|
|
162
|
+
// 群发
|
|
163
|
+
String msgId = MailUtils.sendHtml(List.of("a@x.com", "b@x.com"), "标题", htmlContent);
|
|
164
|
+
|
|
165
|
+
// 抄送/密送
|
|
166
|
+
String msgId = MailUtils.send("to@x.com", "cc@x.com", "bcc@x.com", "标题", "内容", true);
|
|
167
|
+
|
|
168
|
+
// 获取邮件账户配置
|
|
169
|
+
MailAccount account = MailUtils.getMailAccount();
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### 2.3 邮件验证码示例
|
|
173
|
+
|
|
174
|
+
```java
|
|
175
|
+
@Service
|
|
176
|
+
@RequiredArgsConstructor
|
|
177
|
+
public class EmailService {
|
|
178
|
+
|
|
179
|
+
public void sendEmailCode(String email) {
|
|
180
|
+
String code = RandomUtil.randomNumbers(6);
|
|
181
|
+
RedisUtils.setCacheObject("email:code:" + email, code, Duration.ofMinutes(10));
|
|
182
|
+
|
|
183
|
+
String htmlContent = String.format("""
|
|
184
|
+
<div style="padding: 20px; background: #f5f5f5;">
|
|
185
|
+
<h2>验证码</h2>
|
|
186
|
+
<p>您的验证码是:<strong style="color: #1890ff; font-size: 24px;">%s</strong></p>
|
|
187
|
+
<p>有效期10分钟,请勿泄露。</p>
|
|
188
|
+
</div>
|
|
189
|
+
""", code);
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
MailUtils.sendHtml(email, "【若依系统】验证码", htmlContent);
|
|
193
|
+
} catch (Exception e) {
|
|
194
|
+
log.error("邮件发送失败:{}", e.getMessage());
|
|
195
|
+
throw new ServiceException("邮件发送失败");
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
public boolean verifyEmailCode(String email, String code) {
|
|
200
|
+
String cacheKey = "email:code:" + email;
|
|
201
|
+
String cachedCode = RedisUtils.getCacheObject(cacheKey);
|
|
202
|
+
if (cachedCode != null && cachedCode.equals(code)) {
|
|
203
|
+
RedisUtils.deleteObject(cacheKey);
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## 三、多渠道消息通知示例
|
|
214
|
+
|
|
215
|
+
```java
|
|
216
|
+
@Service
|
|
217
|
+
@RequiredArgsConstructor
|
|
218
|
+
public class MessageService {
|
|
219
|
+
|
|
220
|
+
public void sendMessage(List<String> messageType, String subject,
|
|
221
|
+
String message, List<UserDTO> userList) {
|
|
222
|
+
for (UserDTO user : userList) {
|
|
223
|
+
try {
|
|
224
|
+
if (messageType.contains("sms") && StringUtils.isNotBlank(user.getPhone())) {
|
|
225
|
+
Map<String, String> params = new LinkedHashMap<>();
|
|
226
|
+
params.put("content", message);
|
|
227
|
+
SmsFactory.getSmsBlend("config1")
|
|
228
|
+
.sendMessage(user.getPhone(), "SMS_NOTIFY", params);
|
|
229
|
+
}
|
|
230
|
+
if (messageType.contains("email") && StringUtils.isNotBlank(user.getEmail())) {
|
|
231
|
+
MailUtils.sendHtml(user.getEmail(), subject,
|
|
232
|
+
"<div style='padding:20px;'><h3>" + subject + "</h3><p>" + message + "</p></div>");
|
|
233
|
+
}
|
|
234
|
+
} catch (Exception e) {
|
|
235
|
+
log.error("消息发送失败,类型:{},用户:{},错误:{}",
|
|
236
|
+
messageType, user.getUserId(), e.getMessage());
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## 四、常见错误
|
|
246
|
+
|
|
247
|
+
```java
|
|
248
|
+
// ❌ 未配置就使用,getSmsBlend 返回 null → NPE
|
|
249
|
+
SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
|
|
250
|
+
smsBlend.sendMessage(...);
|
|
251
|
+
|
|
252
|
+
// ❌ 模板参数名与模板定义不匹配
|
|
253
|
+
params.put("verifyCode", "123456"); // 模板中是 ${code}
|
|
254
|
+
// ✅ params.put("code", "123456");
|
|
255
|
+
|
|
256
|
+
// ❌ 不检查发送结果
|
|
257
|
+
smsBlend.sendMessage(phone, templateId, params);
|
|
258
|
+
// ✅ SmsResponse response = smsBlend.sendMessage(...);
|
|
259
|
+
// if (!response.isSuccess()) throw new ServiceException("短信发送失败");
|
|
260
|
+
|
|
261
|
+
// ❌ 验证码无过期时间
|
|
262
|
+
RedisUtils.setCacheObject("sms:code:" + phone, code);
|
|
263
|
+
// ✅ RedisUtils.setCacheObject("sms:code:" + phone, code, Duration.ofMinutes(5));
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## 五、API 速查表
|
|
269
|
+
|
|
270
|
+
### 短信 API(SMS4j)
|
|
271
|
+
|
|
272
|
+
| 方法 | 说明 |
|
|
273
|
+
|------|------|
|
|
274
|
+
| `SmsFactory.getSmsBlend()` | 获取默认短信实例 |
|
|
275
|
+
| `SmsFactory.getSmsBlend("config1")` | 获取指定配置短信实例 |
|
|
276
|
+
| `smsBlend.sendMessage(phone, templateId, params)` | 发送模板短信 |
|
|
277
|
+
| `smsBlend.sendMessage(phones, templateId, params)` | 批量发送 |
|
|
278
|
+
| `response.isSuccess()` | 是否成功 |
|
|
279
|
+
| `response.getMessage()` | 错误信息 |
|
|
280
|
+
| `response.getBizId()` | 消息ID |
|
|
281
|
+
|
|
282
|
+
### 邮件 API(MailUtils)
|
|
283
|
+
|
|
284
|
+
| 方法 | 说明 |
|
|
285
|
+
|------|------|
|
|
286
|
+
| `MailUtils.sendText(to, subject, content)` | 文本邮件 |
|
|
287
|
+
| `MailUtils.sendHtml(to, subject, content)` | HTML邮件 |
|
|
288
|
+
| `MailUtils.sendText(to, subject, content, files...)` | 带附件文本邮件 |
|
|
289
|
+
| `MailUtils.sendHtml(to, subject, content, files...)` | 带附件HTML邮件 |
|
|
290
|
+
| `MailUtils.sendHtml(to, subject, content, imageMap)` | 带内嵌图片邮件 |
|
|
291
|
+
| `MailUtils.send(to, cc, bcc, subject, content, isHtml)` | 抄送/密送 |
|
|
292
|
+
| `MailUtils.sendHtml(tos, subject, content)` | 群发HTML邮件 |
|
|
293
|
+
| `MailUtils.getMailAccount()` | 获取邮件配置 |
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## 六、核心文件位置
|
|
298
|
+
|
|
299
|
+
| 类型 | 位置 |
|
|
300
|
+
|------|------|
|
|
301
|
+
| 短信配置 | `ruoyi-common/ruoyi-common-sms/.../config/SmsAutoConfiguration.java` |
|
|
302
|
+
| 短信缓存 | `ruoyi-common/ruoyi-common-sms/.../core/dao/PlusSmsDao.java` |
|
|
303
|
+
| 邮件工具类 | `ruoyi-common/ruoyi-common-mail/.../utils/MailUtils.java` |
|
|
304
|
+
| 邮件配置 | `ruoyi-common/ruoyi-common-mail/.../config/MailConfig.java` |
|
|
305
|
+
| 邮件属性 | `ruoyi-common/ruoyi-common-mail/.../config/properties/MailProperties.java` |
|
|
306
|
+
| 验证码示例 | `ruoyi-admin/.../web/controller/CaptchaController.java` |
|
|
307
|
+
| 短信演示 | `ruoyi-modules/ruoyi-demo/.../controller/SmsController.java` |
|
|
308
|
+
| 工作流消息 | `ruoyi-modules/ruoyi-workflow/.../service/impl/FlwCommonServiceImpl.java` |
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# 邮件完整配置参考
|
|
2
|
+
|
|
3
|
+
## 常用 SMTP 服务器
|
|
4
|
+
|
|
5
|
+
| 邮箱服务商 | SMTP 服务器 | SSL 端口 | 非 SSL 端口 |
|
|
6
|
+
|-----------|-------------|---------|------------|
|
|
7
|
+
| 163 邮箱 | smtp.163.com | 465 | 25 |
|
|
8
|
+
| QQ 邮箱 | smtp.qq.com | 465 | 587 |
|
|
9
|
+
| 阿里企业邮箱 | smtp.mxhichina.com | 465 | 25 |
|
|
10
|
+
| 腾讯企业邮箱 | smtp.exmail.qq.com | 465 | 587 |
|
|
11
|
+
| Gmail | smtp.gmail.com | 465 | 587 |
|
|
12
|
+
| Outlook | smtp.office365.com | 587 | 587 |
|
|
13
|
+
|
|
14
|
+
## 完整配置示例
|
|
15
|
+
|
|
16
|
+
```yaml
|
|
17
|
+
mail:
|
|
18
|
+
enabled: true
|
|
19
|
+
host: smtp.163.com
|
|
20
|
+
port: 465
|
|
21
|
+
auth: true
|
|
22
|
+
from: system@example.com
|
|
23
|
+
user: system@example.com
|
|
24
|
+
pass: ${MAIL_PASSWORD} # 授权码,非登录密码
|
|
25
|
+
sslEnable: true
|
|
26
|
+
starttlsEnable: false
|
|
27
|
+
timeout: 10000
|
|
28
|
+
connectionTimeout: 10000
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## 业务通知邮件示例
|
|
32
|
+
|
|
33
|
+
### 订单确认邮件
|
|
34
|
+
|
|
35
|
+
```java
|
|
36
|
+
public void sendOrderConfirmation(Order order, User user) {
|
|
37
|
+
String htmlContent = String.format("""
|
|
38
|
+
<div style="max-width: 600px; margin: 0 auto; font-family: Arial;">
|
|
39
|
+
<h2>订单确认</h2>
|
|
40
|
+
<p>尊敬的 %s,您好!</p>
|
|
41
|
+
<table style="width: 100%%; border-collapse: collapse;">
|
|
42
|
+
<tr><td>订单号:</td><td>%s</td></tr>
|
|
43
|
+
<tr><td>下单时间:</td><td>%s</td></tr>
|
|
44
|
+
<tr><td>订单金额:</td><td>¥%s</td></tr>
|
|
45
|
+
</table>
|
|
46
|
+
</div>
|
|
47
|
+
""",
|
|
48
|
+
user.getNickName(), order.getOrderNo(),
|
|
49
|
+
DateUtils.parseDateToStr("yyyy-MM-dd HH:mm:ss", order.getCreateTime()),
|
|
50
|
+
order.getTotalAmount()
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
MailUtils.sendHtml(user.getEmail(), "【若依商城】订单确认", htmlContent);
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 密码重置邮件
|
|
58
|
+
|
|
59
|
+
```java
|
|
60
|
+
public void sendPasswordResetEmail(String email, String resetToken) {
|
|
61
|
+
String resetUrl = "https://example.com/reset-password?token=" + resetToken;
|
|
62
|
+
String htmlContent = String.format("""
|
|
63
|
+
<div style="padding: 20px;">
|
|
64
|
+
<h2>密码重置</h2>
|
|
65
|
+
<p><a href="%s" style="color: #1890ff;">点击重置密码</a></p>
|
|
66
|
+
<p>链接有效期30分钟。</p>
|
|
67
|
+
</div>
|
|
68
|
+
""", resetUrl);
|
|
69
|
+
|
|
70
|
+
MailUtils.sendHtml(email, "【若依系统】密码重置", htmlContent);
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 报表邮件(带附件)
|
|
75
|
+
|
|
76
|
+
```java
|
|
77
|
+
public void sendReportEmail(String email, String reportName, File reportFile) {
|
|
78
|
+
String htmlContent = String.format("""
|
|
79
|
+
<div style="padding: 20px;">
|
|
80
|
+
<h2>%s</h2>
|
|
81
|
+
<p>请查看附件中的报表文件。</p>
|
|
82
|
+
<p>生成时间:%s</p>
|
|
83
|
+
</div>
|
|
84
|
+
""", reportName, DateUtils.getTime());
|
|
85
|
+
|
|
86
|
+
MailUtils.sendHtml(email, reportName, htmlContent, reportFile);
|
|
87
|
+
}
|
|
88
|
+
```
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# 短信完整配置参考
|
|
2
|
+
|
|
3
|
+
## 支持的服务商
|
|
4
|
+
|
|
5
|
+
| 服务商 | 配置标识 | 说明 |
|
|
6
|
+
|--------|---------|------|
|
|
7
|
+
| 阿里云 | `alibaba` | 阿里云短信服务 |
|
|
8
|
+
| 腾讯云 | `tencent` | 腾讯云短信(需 sdk-app-id) |
|
|
9
|
+
| 华为云 | `huawei` | 华为云短信 |
|
|
10
|
+
| 云片 | `yunpian` | 云片短信 |
|
|
11
|
+
| 合一 | `unisms` | 合一短信 |
|
|
12
|
+
| 京东云 | `jdcloud` | 京东云短信 |
|
|
13
|
+
| 容联云 | `cloopen` | 容联云通讯 |
|
|
14
|
+
| 亿美软通 | `emay` | 亿美短信 |
|
|
15
|
+
| 天翼云 | `ctyun` | 天翼云短信 |
|
|
16
|
+
|
|
17
|
+
## 完整配置示例
|
|
18
|
+
|
|
19
|
+
```yaml
|
|
20
|
+
sms:
|
|
21
|
+
config-type: yaml
|
|
22
|
+
# 短信拦截配置(可选)
|
|
23
|
+
restricted:
|
|
24
|
+
account-max: 0 # 是否开启账号维度每日上限
|
|
25
|
+
minute-max: 1 # 是否开启手机号维度每分钟上限
|
|
26
|
+
account-max-count: 10 # 单账号每日上限
|
|
27
|
+
minute-max-count: 1 # 单手机号每分钟上限
|
|
28
|
+
|
|
29
|
+
blends:
|
|
30
|
+
config1:
|
|
31
|
+
supplier: alibaba
|
|
32
|
+
access-key-id: ${ALIYUN_SMS_ACCESS_KEY_ID}
|
|
33
|
+
access-key-secret: ${ALIYUN_SMS_ACCESS_KEY_SECRET}
|
|
34
|
+
signature: 若依框架
|
|
35
|
+
|
|
36
|
+
config2:
|
|
37
|
+
supplier: tencent
|
|
38
|
+
access-key-id: ${TENCENT_SMS_SECRET_ID}
|
|
39
|
+
access-key-secret: ${TENCENT_SMS_SECRET_KEY}
|
|
40
|
+
signature: 若依框架
|
|
41
|
+
sdk-app-id: "1400000000"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## 业务通知短信示例
|
|
45
|
+
|
|
46
|
+
```java
|
|
47
|
+
@Service
|
|
48
|
+
@RequiredArgsConstructor
|
|
49
|
+
public class OrderNotifyService {
|
|
50
|
+
|
|
51
|
+
public void notifyShipment(Order order) {
|
|
52
|
+
Map<String, String> params = new LinkedHashMap<>();
|
|
53
|
+
params.put("orderNo", order.getOrderNo());
|
|
54
|
+
params.put("expressNo", order.getExpressNo());
|
|
55
|
+
params.put("expressCompany", order.getExpressCompany());
|
|
56
|
+
|
|
57
|
+
SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
|
|
58
|
+
SmsResponse response = smsBlend.sendMessage(
|
|
59
|
+
order.getPhone(), "SMS_SHIPMENT_NOTIFY", params);
|
|
60
|
+
|
|
61
|
+
if (!response.isSuccess()) {
|
|
62
|
+
log.error("发货通知短信发送失败,订单号:{},错误:{}",
|
|
63
|
+
order.getOrderNo(), response.getMessage());
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
public void sendMarketingSms(List<String> phones, String content) {
|
|
68
|
+
Map<String, String> params = new LinkedHashMap<>();
|
|
69
|
+
params.put("content", content);
|
|
70
|
+
|
|
71
|
+
SmsFactory.getSmsBlend("config1").sendMessage(phones, "SMS_MARKETING", params);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|