ai-engineering-init 1.3.3 → 1.4.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/.claude/hooks/skill-forced-eval.js +4 -1
- package/.claude/settings.json +3 -3
- package/.claude/skills/add-skill/SKILL.md +252 -116
- package/.claude/skills/api-development/SKILL.md +83 -377
- package/.claude/skills/architecture-design/SKILL.md +138 -632
- package/.claude/skills/backend-annotations/SKILL.md +134 -506
- package/.claude/skills/banana-image/SKILL.md +10 -3
- package/.claude/skills/brainstorm/SKILL.md +103 -535
- package/.claude/skills/bug-detective/SKILL.md +147 -1097
- package/.claude/skills/bug-detective/references/error-patterns.md +242 -0
- package/.claude/skills/code-patterns/SKILL.md +116 -426
- package/.claude/skills/code-patterns/references/leniu-code-patterns.md +87 -0
- package/.claude/skills/crud-development/SKILL.md +64 -304
- package/.claude/skills/data-permission/SKILL.md +105 -412
- package/.claude/skills/data-permission/references/custom-data-scope.md +90 -0
- package/.claude/skills/file-oss-management/SKILL.md +106 -714
- package/.claude/skills/file-oss-management/references/entities.md +105 -0
- package/.claude/skills/file-oss-management/references/service-impl.md +104 -0
- package/.claude/skills/leniu-api-development/SKILL.md +142 -626
- package/.claude/skills/leniu-api-development/references/real-examples.md +273 -0
- package/.claude/skills/leniu-architecture-design/SKILL.md +176 -391
- package/.claude/skills/leniu-backend-annotations/SKILL.md +132 -519
- package/.claude/skills/leniu-brainstorm/SKILL.md +132 -541
- package/.claude/skills/leniu-brainstorm/references/business-scenarios.md +162 -0
- package/.claude/skills/leniu-crud-development/SKILL.md +232 -938
- package/.claude/skills/leniu-crud-development/references/templates.md +597 -0
- package/.claude/skills/leniu-customization-location/SKILL.md +410 -0
- package/.claude/skills/leniu-data-permission/SKILL.md +70 -0
- package/.claude/skills/leniu-java-entity/SKILL.md +76 -590
- package/.claude/skills/leniu-java-entity/references/templates.md +237 -0
- package/.claude/skills/leniu-java-export/SKILL.md +94 -379
- package/.claude/skills/leniu-java-logging/SKILL.md +106 -709
- package/.claude/skills/leniu-java-logging/references/data-mask.md +46 -0
- package/.claude/skills/leniu-java-logging/references/logging-scenarios.md +113 -0
- package/.claude/skills/leniu-java-mybatis/SKILL.md +73 -446
- package/.claude/skills/leniu-java-mybatis/references/report-mapper.md +88 -0
- package/.claude/skills/leniu-report-customization/SKILL.md +111 -325
- package/.claude/skills/leniu-report-customization/references/table-fields.md +93 -0
- package/.claude/skills/leniu-report-standard-customization/SKILL.md +328 -0
- package/.claude/skills/leniu-report-standard-customization/references/analysis-module.md +64 -0
- package/.claude/skills/leniu-report-standard-customization/references/table-fields.md +113 -0
- package/.claude/skills/leniu-security-guard/SKILL.md +133 -347
- package/.claude/skills/mysql-debug/SKILL.md +364 -0
- package/.claude/skills/openspec-apply-change/SKILL.md +10 -1
- package/.claude/skills/openspec-archive-change/SKILL.md +9 -1
- package/.claude/skills/openspec-bulk-archive-change/SKILL.md +9 -1
- package/.claude/skills/openspec-continue-change/SKILL.md +9 -1
- package/.claude/skills/openspec-explore/SKILL.md +10 -1
- package/.claude/skills/openspec-ff-change/SKILL.md +9 -1
- package/.claude/skills/openspec-new-change/SKILL.md +9 -1
- package/.claude/skills/openspec-onboard/SKILL.md +15 -130
- package/.claude/skills/openspec-sync-specs/SKILL.md +9 -1
- package/.claude/skills/openspec-verify-change/SKILL.md +9 -1
- package/.claude/skills/performance-doctor/SKILL.md +110 -434
- package/.claude/skills/redis-cache/SKILL.md +89 -595
- package/.claude/skills/redis-cache/references/listeners.md +23 -0
- package/.claude/skills/scheduled-jobs/SKILL.md +88 -407
- package/.claude/skills/security-guard/SKILL.md +137 -532
- package/.claude/skills/security-guard/references/encrypt-config.md +103 -0
- package/.claude/skills/security-guard/references/sensitive-strategies.md +42 -0
- package/.claude/skills/sms-mail/SKILL.md +116 -574
- package/.claude/skills/sms-mail/references/mail-config.md +88 -0
- package/.claude/skills/sms-mail/references/sms-config.md +74 -0
- package/.claude/skills/social-login/SKILL.md +112 -514
- package/.claude/skills/social-login/references/provider-configs.md +118 -0
- package/.claude/skills/tenant-management/SKILL.md +129 -444
- package/.claude/skills/tenant-management/references/tenant-scenarios.md +91 -0
- package/.claude/skills/test-development/SKILL.md +86 -540
- package/.claude/skills/test-development/references/parameterized-examples.md +119 -0
- package/.claude/skills/utils-toolkit/SKILL.md +52 -305
- package/.claude/skills/utils-toolkit/references/redis-utils-api.md +56 -0
- package/.claude/skills/websocket-sse/SKILL.md +105 -550
- package/.claude/skills/workflow-engine/SKILL.md +147 -502
- package/.codex/skills/add-skill/SKILL.md +252 -116
- package/.codex/skills/api-development/SKILL.md +172 -599
- package/.codex/skills/architecture-design/SKILL.md +138 -504
- package/.codex/skills/backend-annotations/SKILL.md +134 -496
- package/.codex/skills/banana-image/SKILL.md +10 -3
- package/.codex/skills/brainstorm/SKILL.md +103 -535
- package/.codex/skills/bug-detective/SKILL.md +147 -1097
- package/.codex/skills/bug-detective/references/error-patterns.md +242 -0
- package/.codex/skills/code-patterns/SKILL.md +120 -282
- package/.codex/skills/code-patterns/references/leniu-code-patterns.md +87 -0
- package/.codex/skills/crud-development/SKILL.md +64 -292
- package/.codex/skills/data-permission/SKILL.md +108 -407
- package/.codex/skills/data-permission/references/custom-data-scope.md +90 -0
- package/.codex/skills/database-ops/SKILL.md +8 -154
- package/.codex/skills/error-handler/SKILL.md +10 -0
- package/.codex/skills/file-oss-management/SKILL.md +106 -714
- package/.codex/skills/file-oss-management/references/entities.md +105 -0
- package/.codex/skills/file-oss-management/references/service-impl.md +104 -0
- package/.codex/skills/git-workflow/SKILL.md +27 -5
- package/.codex/skills/leniu-api-development/SKILL.md +142 -626
- package/.codex/skills/leniu-api-development/references/real-examples.md +273 -0
- package/.codex/skills/leniu-architecture-design/SKILL.md +176 -391
- package/.codex/skills/leniu-backend-annotations/SKILL.md +132 -519
- package/.codex/skills/leniu-brainstorm/SKILL.md +132 -541
- package/.codex/skills/leniu-brainstorm/references/business-scenarios.md +162 -0
- package/.codex/skills/leniu-crud-development/SKILL.md +232 -938
- package/.codex/skills/leniu-crud-development/references/templates.md +597 -0
- package/.codex/skills/leniu-customization-location/SKILL.md +410 -0
- package/.codex/skills/leniu-data-permission/SKILL.md +70 -0
- package/.codex/skills/leniu-java-code-style/SKILL.md +510 -0
- package/.codex/skills/leniu-java-entity/SKILL.md +76 -590
- package/.codex/skills/leniu-java-entity/references/templates.md +237 -0
- package/.codex/skills/leniu-java-export/SKILL.md +94 -379
- package/.codex/skills/leniu-java-logging/SKILL.md +106 -709
- package/.codex/skills/leniu-java-logging/references/data-mask.md +46 -0
- package/.codex/skills/leniu-java-logging/references/logging-scenarios.md +113 -0
- package/.codex/skills/leniu-java-mybatis/SKILL.md +73 -446
- package/.codex/skills/leniu-java-mybatis/references/report-mapper.md +88 -0
- package/.codex/skills/leniu-report-customization/SKILL.md +111 -325
- package/.codex/skills/leniu-report-customization/references/table-fields.md +93 -0
- package/.codex/skills/leniu-report-standard-customization/SKILL.md +328 -0
- package/.codex/skills/leniu-report-standard-customization/references/analysis-module.md +64 -0
- package/.codex/skills/leniu-report-standard-customization/references/table-fields.md +113 -0
- package/.codex/skills/leniu-security-guard/SKILL.md +133 -347
- package/.codex/skills/mysql-debug/SKILL.md +364 -0
- package/.codex/skills/openspec-apply-change/SKILL.md +10 -1
- package/.codex/skills/openspec-archive-change/SKILL.md +9 -1
- package/.codex/skills/openspec-bulk-archive-change/SKILL.md +9 -1
- package/.codex/skills/openspec-continue-change/SKILL.md +9 -1
- package/.codex/skills/openspec-explore/SKILL.md +10 -1
- package/.codex/skills/openspec-ff-change/SKILL.md +9 -1
- package/.codex/skills/openspec-new-change/SKILL.md +9 -1
- package/.codex/skills/openspec-onboard/SKILL.md +15 -130
- package/.codex/skills/openspec-sync-specs/SKILL.md +9 -1
- package/.codex/skills/openspec-verify-change/SKILL.md +9 -1
- package/.codex/skills/performance-doctor/SKILL.md +110 -434
- package/.codex/skills/project-navigator/SKILL.md +20 -1
- package/.codex/skills/redis-cache/SKILL.md +93 -589
- package/.codex/skills/redis-cache/references/listeners.md +23 -0
- package/.codex/skills/scheduled-jobs/SKILL.md +88 -407
- package/.codex/skills/security-guard/SKILL.md +141 -527
- package/.codex/skills/security-guard/references/encrypt-config.md +103 -0
- package/.codex/skills/security-guard/references/sensitive-strategies.md +42 -0
- package/.codex/skills/sms-mail/SKILL.md +116 -574
- package/.codex/skills/sms-mail/references/mail-config.md +88 -0
- package/.codex/skills/sms-mail/references/sms-config.md +74 -0
- package/.codex/skills/social-login/SKILL.md +112 -514
- package/.codex/skills/social-login/references/provider-configs.md +118 -0
- package/.codex/skills/store-pc/SKILL.md +258 -383
- package/.codex/skills/tenant-management/SKILL.md +129 -444
- package/.codex/skills/tenant-management/references/tenant-scenarios.md +91 -0
- package/.codex/skills/test-development/SKILL.md +86 -540
- package/.codex/skills/test-development/references/parameterized-examples.md +119 -0
- package/.codex/skills/ui-pc/SKILL.md +350 -387
- package/.codex/skills/utils-toolkit/SKILL.md +52 -283
- package/.codex/skills/utils-toolkit/references/redis-utils-api.md +56 -0
- package/.codex/skills/websocket-sse/SKILL.md +105 -550
- package/.codex/skills/workflow-engine/SKILL.md +147 -502
- package/.cursor/hooks.json +3 -3
- package/.cursor/rules/skill-activation.mdc +2 -0
- package/.cursor/skills/add-skill/SKILL.md +252 -116
- package/.cursor/skills/api-development/SKILL.md +83 -377
- package/.cursor/skills/architecture-design/SKILL.md +138 -632
- package/.cursor/skills/backend-annotations/SKILL.md +134 -506
- package/.cursor/skills/banana-image/SKILL.md +10 -3
- package/.cursor/skills/brainstorm/SKILL.md +103 -535
- package/.cursor/skills/bug-detective/SKILL.md +147 -1097
- package/.cursor/skills/bug-detective/references/error-patterns.md +242 -0
- package/.cursor/skills/code-patterns/SKILL.md +116 -426
- package/.cursor/skills/code-patterns/references/leniu-code-patterns.md +87 -0
- package/.cursor/skills/crud-development/SKILL.md +64 -304
- package/.cursor/skills/data-permission/SKILL.md +105 -412
- package/.cursor/skills/data-permission/references/custom-data-scope.md +90 -0
- package/.cursor/skills/file-oss-management/SKILL.md +106 -714
- package/.cursor/skills/file-oss-management/references/entities.md +105 -0
- package/.cursor/skills/file-oss-management/references/service-impl.md +104 -0
- package/.cursor/skills/git-workflow/SKILL.md +27 -5
- package/.cursor/skills/leniu-api-development/SKILL.md +142 -626
- package/.cursor/skills/leniu-api-development/references/real-examples.md +273 -0
- package/.cursor/skills/leniu-architecture-design/SKILL.md +176 -391
- package/.cursor/skills/leniu-backend-annotations/SKILL.md +132 -519
- package/.cursor/skills/leniu-brainstorm/SKILL.md +132 -541
- package/.cursor/skills/leniu-brainstorm/references/business-scenarios.md +162 -0
- package/.cursor/skills/leniu-crud-development/SKILL.md +232 -938
- package/.cursor/skills/leniu-crud-development/references/templates.md +597 -0
- package/.cursor/skills/leniu-customization-location/SKILL.md +410 -0
- package/.cursor/skills/leniu-data-permission/SKILL.md +70 -0
- package/.cursor/skills/leniu-java-code-style/SKILL.md +510 -0
- package/.cursor/skills/leniu-java-entity/SKILL.md +76 -590
- package/.cursor/skills/leniu-java-entity/references/templates.md +237 -0
- package/.cursor/skills/leniu-java-export/SKILL.md +94 -379
- package/.cursor/skills/leniu-java-logging/SKILL.md +106 -709
- package/.cursor/skills/leniu-java-logging/references/data-mask.md +46 -0
- package/.cursor/skills/leniu-java-logging/references/logging-scenarios.md +113 -0
- package/.cursor/skills/leniu-java-mybatis/SKILL.md +73 -446
- package/.cursor/skills/leniu-java-mybatis/references/report-mapper.md +88 -0
- package/.cursor/skills/leniu-report-customization/SKILL.md +111 -325
- package/.cursor/skills/leniu-report-customization/references/table-fields.md +93 -0
- package/.cursor/skills/leniu-report-standard-customization/SKILL.md +328 -0
- package/.cursor/skills/leniu-report-standard-customization/references/analysis-module.md +64 -0
- package/.cursor/skills/leniu-report-standard-customization/references/table-fields.md +113 -0
- package/.cursor/skills/leniu-security-guard/SKILL.md +133 -347
- package/.cursor/skills/mysql-debug/SKILL.md +364 -0
- package/.cursor/skills/openspec-apply-change/SKILL.md +10 -1
- package/.cursor/skills/openspec-archive-change/SKILL.md +9 -1
- package/.cursor/skills/openspec-bulk-archive-change/SKILL.md +9 -1
- package/.cursor/skills/openspec-continue-change/SKILL.md +9 -1
- package/.cursor/skills/openspec-explore/SKILL.md +10 -1
- package/.cursor/skills/openspec-ff-change/SKILL.md +9 -1
- package/.cursor/skills/openspec-new-change/SKILL.md +9 -1
- package/.cursor/skills/openspec-onboard/SKILL.md +15 -130
- package/.cursor/skills/openspec-sync-specs/SKILL.md +9 -1
- package/.cursor/skills/openspec-verify-change/SKILL.md +9 -1
- package/.cursor/skills/performance-doctor/SKILL.md +110 -434
- package/.cursor/skills/redis-cache/SKILL.md +89 -595
- package/.cursor/skills/redis-cache/references/listeners.md +23 -0
- package/.cursor/skills/scheduled-jobs/SKILL.md +88 -407
- package/.cursor/skills/security-guard/SKILL.md +137 -532
- package/.cursor/skills/security-guard/references/encrypt-config.md +103 -0
- package/.cursor/skills/security-guard/references/sensitive-strategies.md +42 -0
- package/.cursor/skills/sms-mail/SKILL.md +116 -574
- package/.cursor/skills/sms-mail/references/mail-config.md +88 -0
- package/.cursor/skills/sms-mail/references/sms-config.md +74 -0
- package/.cursor/skills/social-login/SKILL.md +112 -514
- package/.cursor/skills/social-login/references/provider-configs.md +118 -0
- package/.cursor/skills/tenant-management/SKILL.md +129 -444
- package/.cursor/skills/tenant-management/references/tenant-scenarios.md +91 -0
- package/.cursor/skills/test-development/SKILL.md +86 -540
- package/.cursor/skills/test-development/references/parameterized-examples.md +119 -0
- package/.cursor/skills/utils-toolkit/SKILL.md +52 -305
- package/.cursor/skills/utils-toolkit/references/redis-utils-api.md +56 -0
- package/.cursor/skills/websocket-sse/SKILL.md +105 -550
- package/.cursor/skills/workflow-engine/SKILL.md +147 -502
- package/AGENTS.md +1 -0
- package/package.json +1 -1
|
@@ -15,118 +15,65 @@ description: |
|
|
|
15
15
|
|
|
16
16
|
# 短信与邮件开发指南
|
|
17
17
|
|
|
18
|
-
>
|
|
19
|
-
|
|
20
|
-
## 概述
|
|
21
|
-
|
|
22
|
-
本框架提供两种消息通知方案:
|
|
23
|
-
|
|
24
|
-
| 方案 | 模块 | 技术栈 | 适用场景 |
|
|
25
|
-
|------|------|--------|---------|
|
|
26
|
-
| **短信** | `ruoyi-common-sms` | SMS4j | 验证码、营销短信、通知短信 |
|
|
27
|
-
| **邮件** | `ruoyi-common-mail` | Hutool JakartaMail | 系统通知、报表发送、验证邮件 |
|
|
28
|
-
|
|
29
|
-
**共同特性**:
|
|
30
|
-
- ✅ 多供应商支持
|
|
31
|
-
- ✅ 模板消息
|
|
32
|
-
- ✅ 配置热更新
|
|
18
|
+
> 短信模块:`ruoyi-common/ruoyi-common-sms`(SMS4j)
|
|
19
|
+
> 邮件模块:`ruoyi-common/ruoyi-common-mail`(Hutool JakartaMail)
|
|
33
20
|
|
|
34
21
|
---
|
|
35
22
|
|
|
36
|
-
##
|
|
37
|
-
|
|
38
|
-
### 1.1 支持的短信服务商
|
|
39
|
-
|
|
40
|
-
SMS4j 支持多种短信服务商,开箱即用:
|
|
23
|
+
## 一、短信开发(SMS4j)
|
|
41
24
|
|
|
42
|
-
|
|
43
|
-
|--------|---------|------|
|
|
44
|
-
| 阿里云 | `alibaba` | 阿里云短信服务 |
|
|
45
|
-
| 腾讯云 | `tencent` | 腾讯云短信 |
|
|
46
|
-
| 华为云 | `huawei` | 华为云短信 |
|
|
47
|
-
| 云片 | `yunpian` | 云片短信 |
|
|
48
|
-
| 合一 | `unisms` | 合一短信 |
|
|
49
|
-
| 京东云 | `jdcloud` | 京东云短信 |
|
|
50
|
-
| 容联云 | `cloopen` | 容联云通讯 |
|
|
51
|
-
| 亿美软通 | `emay` | 亿美短信 |
|
|
52
|
-
| 天翼云 | `ctyun` | 天翼云短信 |
|
|
53
|
-
|
|
54
|
-
### 1.2 配置短信服务
|
|
25
|
+
### 1.1 配置
|
|
55
26
|
|
|
56
27
|
```yaml
|
|
57
|
-
# application.yml
|
|
58
28
|
sms:
|
|
59
|
-
#
|
|
60
|
-
config-type: yaml
|
|
61
|
-
|
|
62
|
-
# 短信配置列表
|
|
29
|
+
config-type: yaml # yaml / 接口 / 数据库
|
|
63
30
|
blends:
|
|
64
|
-
# 配置标识(可自定义,用于 SmsFactory.getSmsBlend("config1"))
|
|
65
31
|
config1:
|
|
66
|
-
# 供应商类型
|
|
67
32
|
supplier: alibaba
|
|
68
|
-
|
|
69
|
-
access-key-
|
|
70
|
-
# AccessKey Secret
|
|
71
|
-
access-key-secret: your-access-key-secret
|
|
72
|
-
# 短信签名
|
|
33
|
+
access-key-id: ${ALIYUN_SMS_ACCESS_KEY_ID}
|
|
34
|
+
access-key-secret: ${ALIYUN_SMS_ACCESS_KEY_SECRET}
|
|
73
35
|
signature: 若依框架
|
|
74
|
-
# SDK AppId(腾讯云需要)
|
|
75
|
-
sdk-app-id: ""
|
|
76
|
-
|
|
77
|
-
# 可配置多个供应商
|
|
78
36
|
config2:
|
|
79
37
|
supplier: tencent
|
|
80
|
-
access-key-id:
|
|
81
|
-
access-key-secret:
|
|
38
|
+
access-key-id: ${TENCENT_SMS_SECRET_ID}
|
|
39
|
+
access-key-secret: ${TENCENT_SMS_SECRET_KEY}
|
|
82
40
|
signature: 若依框架
|
|
83
|
-
sdk-app-id: "1400000000"
|
|
41
|
+
sdk-app-id: "1400000000" # 腾讯云需要
|
|
84
42
|
```
|
|
85
43
|
|
|
86
|
-
|
|
44
|
+
> 支持的服务商:alibaba / tencent / huawei / yunpian / unisms / jdcloud / cloopen / emay / ctyun
|
|
45
|
+
> 限流配置详见 [references/sms-config.md](references/sms-config.md)
|
|
87
46
|
|
|
88
|
-
|
|
47
|
+
### 1.2 核心 API
|
|
89
48
|
|
|
90
49
|
```java
|
|
91
50
|
import org.dromara.sms4j.api.SmsBlend;
|
|
92
51
|
import org.dromara.sms4j.api.entity.SmsResponse;
|
|
93
52
|
import org.dromara.sms4j.core.factory.SmsFactory;
|
|
94
53
|
|
|
95
|
-
//
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
SmsBlend smsBlend = SmsFactory.getSmsBlend();
|
|
99
|
-
|
|
100
|
-
// 2. 获取指定配置的短信实例
|
|
101
|
-
SmsBlend smsBlend = SmsFactory.getSmsBlend("config1"); // 阿里云
|
|
102
|
-
SmsBlend smsBlend = SmsFactory.getSmsBlend("config2"); // 腾讯云
|
|
54
|
+
// 获取短信实例
|
|
55
|
+
SmsBlend smsBlend = SmsFactory.getSmsBlend(); // 默认配置
|
|
56
|
+
SmsBlend smsBlend = SmsFactory.getSmsBlend("config1"); // 指定配置
|
|
103
57
|
|
|
104
|
-
//
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
templateParams.put("time", "5");
|
|
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);
|
|
110
63
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
templateParams // 模板参数
|
|
115
|
-
);
|
|
64
|
+
// 批量发送
|
|
65
|
+
List<String> phones = Arrays.asList("13800138001", "13800138002");
|
|
66
|
+
SmsResponse response = smsBlend.sendMessage(phones, "SMS_123456789", params);
|
|
116
67
|
|
|
117
|
-
//
|
|
68
|
+
// 检查结果
|
|
118
69
|
if (response.isSuccess()) {
|
|
119
|
-
log.info("
|
|
70
|
+
log.info("发送成功,msgId: {}", response.getBizId());
|
|
120
71
|
} else {
|
|
121
|
-
log.error("
|
|
72
|
+
log.error("发送失败:{}", response.getMessage());
|
|
122
73
|
}
|
|
123
|
-
|
|
124
|
-
// 5. 批量发送短信
|
|
125
|
-
List<String> phones = Arrays.asList("13800138001", "13800138002");
|
|
126
|
-
SmsResponse response = smsBlend.sendMessage(phones, "SMS_123456789", templateParams);
|
|
127
74
|
```
|
|
128
75
|
|
|
129
|
-
### 1.
|
|
76
|
+
### 1.3 验证码短信示例
|
|
130
77
|
|
|
131
78
|
```java
|
|
132
79
|
@RestController
|
|
@@ -134,279 +81,124 @@ SmsResponse response = smsBlend.sendMessage(phones, "SMS_123456789", templatePar
|
|
|
134
81
|
@RequiredArgsConstructor
|
|
135
82
|
public class CaptchaController {
|
|
136
83
|
|
|
137
|
-
private final RedisUtils redisUtils;
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* 发送短信验证码
|
|
141
|
-
*/
|
|
142
84
|
@GetMapping("/sms")
|
|
143
85
|
public R<Void> sendSmsCode(@RequestParam String phonenumber) {
|
|
144
|
-
// 1. 生成验证码
|
|
145
86
|
String code = RandomUtil.randomNumbers(6);
|
|
146
87
|
|
|
147
|
-
//
|
|
148
|
-
|
|
149
|
-
RedisUtils.setCacheObject(cacheKey, code, Duration.ofMinutes(5));
|
|
88
|
+
// 存入Redis(5分钟有效)
|
|
89
|
+
RedisUtils.setCacheObject("sms:code:" + phonenumber, code, Duration.ofMinutes(5));
|
|
150
90
|
|
|
151
|
-
//
|
|
91
|
+
// 发送短信
|
|
152
92
|
Map<String, String> map = new LinkedHashMap<>();
|
|
153
93
|
map.put("code", code);
|
|
154
|
-
|
|
155
|
-
// 4. 发送短信
|
|
156
|
-
String templateId = "SMS_123456789"; // 短信模板ID
|
|
157
94
|
SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
|
|
158
|
-
SmsResponse smsResponse = smsBlend.sendMessage(phonenumber,
|
|
95
|
+
SmsResponse smsResponse = smsBlend.sendMessage(phonenumber, "SMS_123456789", map);
|
|
159
96
|
|
|
160
|
-
// 5. 处理结果
|
|
161
97
|
if (!smsResponse.isSuccess()) {
|
|
162
98
|
log.error("短信发送失败:{}", smsResponse.getMessage());
|
|
163
99
|
return R.fail("短信发送失败");
|
|
164
100
|
}
|
|
165
|
-
|
|
166
101
|
return R.ok("验证码已发送");
|
|
167
102
|
}
|
|
168
103
|
|
|
169
|
-
/**
|
|
170
|
-
* 验证短信验证码
|
|
171
|
-
*/
|
|
172
104
|
@PostMapping("/verify")
|
|
173
105
|
public R<Boolean> verifySmsCode(@RequestParam String phonenumber,
|
|
174
106
|
@RequestParam String code) {
|
|
175
107
|
String cacheKey = "sms:code:" + phonenumber;
|
|
176
108
|
String cachedCode = RedisUtils.getCacheObject(cacheKey);
|
|
177
109
|
|
|
178
|
-
if (cachedCode == null)
|
|
179
|
-
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
if (!cachedCode.equals(code)) {
|
|
183
|
-
return R.fail("验证码错误");
|
|
184
|
-
}
|
|
110
|
+
if (cachedCode == null) return R.fail("验证码已过期");
|
|
111
|
+
if (!cachedCode.equals(code)) return R.fail("验证码错误");
|
|
185
112
|
|
|
186
|
-
// 验证成功,删除缓存
|
|
187
113
|
RedisUtils.deleteObject(cacheKey);
|
|
188
114
|
return R.ok(true);
|
|
189
115
|
}
|
|
190
116
|
}
|
|
191
117
|
```
|
|
192
118
|
|
|
193
|
-
### 1.5 业务通知短信示例
|
|
194
|
-
|
|
195
|
-
```java
|
|
196
|
-
@Service
|
|
197
|
-
@RequiredArgsConstructor
|
|
198
|
-
public class OrderNotifyService {
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* 订单发货通知
|
|
202
|
-
*/
|
|
203
|
-
public void notifyShipment(Order order) {
|
|
204
|
-
Map<String, String> params = new LinkedHashMap<>();
|
|
205
|
-
params.put("orderNo", order.getOrderNo());
|
|
206
|
-
params.put("expressNo", order.getExpressNo());
|
|
207
|
-
params.put("expressCompany", order.getExpressCompany());
|
|
208
|
-
|
|
209
|
-
SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
|
|
210
|
-
SmsResponse response = smsBlend.sendMessage(
|
|
211
|
-
order.getPhone(),
|
|
212
|
-
"SMS_SHIPMENT_NOTIFY", // 发货通知模板
|
|
213
|
-
params
|
|
214
|
-
);
|
|
215
|
-
|
|
216
|
-
if (!response.isSuccess()) {
|
|
217
|
-
log.error("发货通知短信发送失败,订单号:{},错误:{}",
|
|
218
|
-
order.getOrderNo(), response.getMessage());
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* 批量发送营销短信
|
|
224
|
-
*/
|
|
225
|
-
public void sendMarketingSms(List<String> phones, String content) {
|
|
226
|
-
Map<String, String> params = new LinkedHashMap<>();
|
|
227
|
-
params.put("content", content);
|
|
228
|
-
|
|
229
|
-
SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
|
|
230
|
-
SmsResponse response = smsBlend.sendMessage(
|
|
231
|
-
phones,
|
|
232
|
-
"SMS_MARKETING",
|
|
233
|
-
params
|
|
234
|
-
);
|
|
235
|
-
|
|
236
|
-
log.info("营销短信发送结果:成功={}, 消息={}",
|
|
237
|
-
response.isSuccess(), response.getMessage());
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
```
|
|
241
|
-
|
|
242
119
|
---
|
|
243
120
|
|
|
244
|
-
##
|
|
121
|
+
## 二、邮件开发(Hutool Mail)
|
|
245
122
|
|
|
246
|
-
### 2.1
|
|
123
|
+
### 2.1 配置
|
|
247
124
|
|
|
248
125
|
```yaml
|
|
249
|
-
# application.yml
|
|
250
126
|
mail:
|
|
251
127
|
enabled: true
|
|
252
|
-
# SMTP 服务器
|
|
253
128
|
host: smtp.163.com
|
|
254
|
-
# SMTP 端口
|
|
255
129
|
port: 465
|
|
256
|
-
# 是否需要认证
|
|
257
130
|
auth: true
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
#
|
|
261
|
-
user: xxx@163.com
|
|
262
|
-
# 密码(授权码,非登录密码)
|
|
263
|
-
pass: your-smtp-password
|
|
264
|
-
# 使用 SSL
|
|
131
|
+
from: system@example.com
|
|
132
|
+
user: system@example.com
|
|
133
|
+
pass: ${MAIL_PASSWORD} # 授权码,非登录密码
|
|
265
134
|
sslEnable: true
|
|
266
|
-
# 使用 STARTTLS
|
|
267
135
|
starttlsEnable: false
|
|
268
|
-
# 超时时间(毫秒)
|
|
269
136
|
timeout: 10000
|
|
270
137
|
connectionTimeout: 10000
|
|
271
138
|
```
|
|
272
139
|
|
|
273
|
-
|
|
140
|
+
> 常用 SMTP 服务器配置详见 [references/mail-config.md](references/mail-config.md)
|
|
274
141
|
|
|
275
|
-
|
|
276
|
-
|-----------|-------------|---------|------------|
|
|
277
|
-
| 163 邮箱 | smtp.163.com | 465 | 25 |
|
|
278
|
-
| QQ 邮箱 | smtp.qq.com | 465 | 587 |
|
|
279
|
-
| 阿里企业邮箱 | smtp.mxhichina.com | 465 | 25 |
|
|
280
|
-
| 腾讯企业邮箱 | smtp.exmail.qq.com | 465 | 587 |
|
|
281
|
-
| Gmail | smtp.gmail.com | 465 | 587 |
|
|
282
|
-
| Outlook | smtp.office365.com | 587 | 587 |
|
|
283
|
-
|
|
284
|
-
### 2.3 核心工具类:MailUtils
|
|
285
|
-
|
|
286
|
-
**位置**:`org.dromara.common.mail.utils.MailUtils`
|
|
142
|
+
### 2.2 核心 API
|
|
287
143
|
|
|
288
144
|
```java
|
|
289
145
|
import org.dromara.common.mail.utils.MailUtils;
|
|
290
146
|
|
|
291
|
-
//
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
);
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
String
|
|
303
|
-
|
|
304
|
-
"
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
);
|
|
308
|
-
|
|
309
|
-
//
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
<p>您的验证码是:<strong>123456</strong></p>
|
|
315
|
-
<p>有效期5分钟</p>
|
|
316
|
-
""";
|
|
317
|
-
|
|
318
|
-
String messageId = MailUtils.sendHtml(
|
|
319
|
-
"recipient@example.com",
|
|
320
|
-
"验证码邮件",
|
|
321
|
-
htmlContent
|
|
322
|
-
);
|
|
323
|
-
|
|
324
|
-
// 4. 发送 HTML 邮件(带内嵌图片)
|
|
325
|
-
Map<String, InputStream> imageMap = new HashMap<>();
|
|
326
|
-
imageMap.put("logo", new FileInputStream("/path/to/logo.png"));
|
|
327
|
-
|
|
328
|
-
String htmlWithImage = """
|
|
329
|
-
<h1>欢迎使用若依系统</h1>
|
|
330
|
-
<img src="cid:logo" alt="Logo"/>
|
|
331
|
-
""";
|
|
332
|
-
|
|
333
|
-
String messageId = MailUtils.sendHtml(
|
|
334
|
-
"recipient@example.com",
|
|
335
|
-
"带图片的邮件",
|
|
336
|
-
htmlWithImage,
|
|
337
|
-
imageMap
|
|
338
|
-
);
|
|
339
|
-
|
|
340
|
-
// ========== 发送给多人 ==========
|
|
341
|
-
|
|
342
|
-
// 5. 发送给多个收件人
|
|
343
|
-
List<String> recipients = Arrays.asList(
|
|
344
|
-
"user1@example.com",
|
|
345
|
-
"user2@example.com"
|
|
346
|
-
);
|
|
347
|
-
|
|
348
|
-
String messageId = MailUtils.sendHtml(
|
|
349
|
-
recipients,
|
|
350
|
-
"群发邮件",
|
|
351
|
-
"<p>这是群发的邮件内容</p>"
|
|
352
|
-
);
|
|
353
|
-
|
|
354
|
-
// 6. 发送邮件(带抄送、密送)
|
|
355
|
-
String messageId = MailUtils.send(
|
|
356
|
-
"recipient@example.com", // 收件人
|
|
357
|
-
"cc@example.com", // 抄送人
|
|
358
|
-
"bcc@example.com", // 密送人
|
|
359
|
-
"重要通知", // 标题
|
|
360
|
-
"<p>这是重要通知内容</p>", // 正文
|
|
361
|
-
true // 是否 HTML
|
|
362
|
-
);
|
|
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();
|
|
363
170
|
```
|
|
364
171
|
|
|
365
|
-
### 2.
|
|
172
|
+
### 2.3 邮件验证码示例
|
|
366
173
|
|
|
367
174
|
```java
|
|
368
175
|
@Service
|
|
369
176
|
@RequiredArgsConstructor
|
|
370
177
|
public class EmailService {
|
|
371
178
|
|
|
372
|
-
/**
|
|
373
|
-
* 发送邮箱验证码
|
|
374
|
-
*/
|
|
375
179
|
public void sendEmailCode(String email) {
|
|
376
|
-
// 1. 生成验证码
|
|
377
180
|
String code = RandomUtil.randomNumbers(6);
|
|
181
|
+
RedisUtils.setCacheObject("email:code:" + email, code, Duration.ofMinutes(10));
|
|
378
182
|
|
|
379
|
-
// 2. 存入 Redis
|
|
380
|
-
String cacheKey = "email:code:" + email;
|
|
381
|
-
RedisUtils.setCacheObject(cacheKey, code, Duration.ofMinutes(10));
|
|
382
|
-
|
|
383
|
-
// 3. 构建邮件内容
|
|
384
183
|
String htmlContent = String.format("""
|
|
385
184
|
<div style="padding: 20px; background: #f5f5f5;">
|
|
386
185
|
<h2>验证码</h2>
|
|
387
186
|
<p>您的验证码是:<strong style="color: #1890ff; font-size: 24px;">%s</strong></p>
|
|
388
|
-
<p>有效期10
|
|
389
|
-
<p style="color: #999;">如非本人操作,请忽略此邮件。</p>
|
|
187
|
+
<p>有效期10分钟,请勿泄露。</p>
|
|
390
188
|
</div>
|
|
391
189
|
""", code);
|
|
392
190
|
|
|
393
|
-
// 4. 发送邮件
|
|
394
191
|
try {
|
|
395
192
|
MailUtils.sendHtml(email, "【若依系统】验证码", htmlContent);
|
|
396
|
-
log.info("验证码邮件发送成功:{}", email);
|
|
397
193
|
} catch (Exception e) {
|
|
398
|
-
log.error("
|
|
194
|
+
log.error("邮件发送失败:{}", e.getMessage());
|
|
399
195
|
throw new ServiceException("邮件发送失败");
|
|
400
196
|
}
|
|
401
197
|
}
|
|
402
198
|
|
|
403
|
-
/**
|
|
404
|
-
* 验证邮箱验证码
|
|
405
|
-
*/
|
|
406
199
|
public boolean verifyEmailCode(String email, String code) {
|
|
407
200
|
String cacheKey = "email:code:" + email;
|
|
408
201
|
String cachedCode = RedisUtils.getCacheObject(cacheKey);
|
|
409
|
-
|
|
410
202
|
if (cachedCode != null && cachedCode.equals(code)) {
|
|
411
203
|
RedisUtils.deleteObject(cacheKey);
|
|
412
204
|
return true;
|
|
@@ -416,179 +208,32 @@ public class EmailService {
|
|
|
416
208
|
}
|
|
417
209
|
```
|
|
418
210
|
|
|
419
|
-
### 2.5 业务通知邮件示例
|
|
420
|
-
|
|
421
|
-
```java
|
|
422
|
-
@Service
|
|
423
|
-
@RequiredArgsConstructor
|
|
424
|
-
public class NotificationService {
|
|
425
|
-
|
|
426
|
-
/**
|
|
427
|
-
* 发送订单确认邮件
|
|
428
|
-
*/
|
|
429
|
-
public void sendOrderConfirmation(Order order, User user) {
|
|
430
|
-
String htmlContent = String.format("""
|
|
431
|
-
<div style="max-width: 600px; margin: 0 auto; font-family: Arial;">
|
|
432
|
-
<h2>订单确认</h2>
|
|
433
|
-
<p>尊敬的 %s,您好!</p>
|
|
434
|
-
<p>您的订单已确认,订单信息如下:</p>
|
|
435
|
-
<table style="width: 100%%; border-collapse: collapse;">
|
|
436
|
-
<tr><td>订单号:</td><td>%s</td></tr>
|
|
437
|
-
<tr><td>下单时间:</td><td>%s</td></tr>
|
|
438
|
-
<tr><td>订单金额:</td><td>¥%s</td></tr>
|
|
439
|
-
</table>
|
|
440
|
-
<p>感谢您的购买!</p>
|
|
441
|
-
</div>
|
|
442
|
-
""",
|
|
443
|
-
user.getNickName(),
|
|
444
|
-
order.getOrderNo(),
|
|
445
|
-
DateUtils.parseDateToStr("yyyy-MM-dd HH:mm:ss", order.getCreateTime()),
|
|
446
|
-
order.getTotalAmount()
|
|
447
|
-
);
|
|
448
|
-
|
|
449
|
-
MailUtils.sendHtml(user.getEmail(), "【若依商城】订单确认", htmlContent);
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
/**
|
|
453
|
-
* 发送密码重置邮件
|
|
454
|
-
*/
|
|
455
|
-
public void sendPasswordResetEmail(String email, String resetToken) {
|
|
456
|
-
String resetUrl = "https://example.com/reset-password?token=" + resetToken;
|
|
457
|
-
|
|
458
|
-
String htmlContent = String.format("""
|
|
459
|
-
<div style="padding: 20px;">
|
|
460
|
-
<h2>密码重置</h2>
|
|
461
|
-
<p>您正在重置密码,请点击下方链接完成操作:</p>
|
|
462
|
-
<p><a href="%s" style="color: #1890ff;">点击重置密码</a></p>
|
|
463
|
-
<p>链接有效期30分钟。</p>
|
|
464
|
-
<p style="color: #999;">如非本人操作,请忽略此邮件。</p>
|
|
465
|
-
</div>
|
|
466
|
-
""", resetUrl);
|
|
467
|
-
|
|
468
|
-
MailUtils.sendHtml(email, "【若依系统】密码重置", htmlContent);
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
/**
|
|
472
|
-
* 发送报表邮件(带附件)
|
|
473
|
-
*/
|
|
474
|
-
public void sendReportEmail(String email, String reportName, File reportFile) {
|
|
475
|
-
String htmlContent = String.format("""
|
|
476
|
-
<div style="padding: 20px;">
|
|
477
|
-
<h2>%s</h2>
|
|
478
|
-
<p>请查看附件中的报表文件。</p>
|
|
479
|
-
<p>报表生成时间:%s</p>
|
|
480
|
-
</div>
|
|
481
|
-
""", reportName, DateUtils.getTime());
|
|
482
|
-
|
|
483
|
-
MailUtils.sendHtml(email, reportName, htmlContent, reportFile);
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
```
|
|
487
|
-
|
|
488
211
|
---
|
|
489
212
|
|
|
490
|
-
##
|
|
491
|
-
|
|
492
|
-
### 3.1 统一消息服务
|
|
213
|
+
## 三、多渠道消息通知示例
|
|
493
214
|
|
|
494
215
|
```java
|
|
495
216
|
@Service
|
|
496
217
|
@RequiredArgsConstructor
|
|
497
218
|
public class MessageService {
|
|
498
219
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
public enum MessageType {
|
|
503
|
-
SMS, // 短信
|
|
504
|
-
EMAIL, // 邮件
|
|
505
|
-
WECHAT, // 微信(需额外实现)
|
|
506
|
-
SITE // 站内信(需额外实现)
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
/**
|
|
510
|
-
* 发送多渠道消息
|
|
511
|
-
*/
|
|
512
|
-
public void sendMessage(List<MessageType> types, String userId,
|
|
513
|
-
String subject, String content) {
|
|
514
|
-
// 获取用户信息
|
|
515
|
-
SysUser user = userService.selectUserById(Long.valueOf(userId));
|
|
516
|
-
|
|
517
|
-
for (MessageType type : types) {
|
|
220
|
+
public void sendMessage(List<String> messageType, String subject,
|
|
221
|
+
String message, List<UserDTO> userList) {
|
|
222
|
+
for (UserDTO user : userList) {
|
|
518
223
|
try {
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
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>");
|
|
524
233
|
}
|
|
525
234
|
} catch (Exception e) {
|
|
526
235
|
log.error("消息发送失败,类型:{},用户:{},错误:{}",
|
|
527
|
-
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
private void sendSms(String phone, String content) {
|
|
533
|
-
if (StringUtils.isBlank(phone)) return;
|
|
534
|
-
|
|
535
|
-
Map<String, String> params = new LinkedHashMap<>();
|
|
536
|
-
params.put("content", content);
|
|
537
|
-
|
|
538
|
-
SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
|
|
539
|
-
smsBlend.sendMessage(phone, "SMS_NOTIFY", params);
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
private void sendEmail(String email, String subject, String content) {
|
|
543
|
-
if (StringUtils.isBlank(email)) return;
|
|
544
|
-
|
|
545
|
-
MailUtils.sendHtml(email, subject,
|
|
546
|
-
"<div style='padding:20px;'>" + content + "</div>");
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
```
|
|
550
|
-
|
|
551
|
-
### 3.2 工作流消息通知示例
|
|
552
|
-
|
|
553
|
-
```java
|
|
554
|
-
/**
|
|
555
|
-
* 工作流消息服务(参考框架实现)
|
|
556
|
-
*/
|
|
557
|
-
@Service
|
|
558
|
-
@RequiredArgsConstructor
|
|
559
|
-
public class FlwCommonServiceImpl implements IFlwCommonService {
|
|
560
|
-
|
|
561
|
-
/**
|
|
562
|
-
* 发送审批通知
|
|
563
|
-
*/
|
|
564
|
-
@Override
|
|
565
|
-
public void sendMessage(List<String> messageType, String message,
|
|
566
|
-
String subject, List<UserDTO> userList) {
|
|
567
|
-
if (CollUtil.isEmpty(messageType) || CollUtil.isEmpty(userList)) {
|
|
568
|
-
return;
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
for (UserDTO user : userList) {
|
|
572
|
-
// 短信通知
|
|
573
|
-
if (messageType.contains("sms") && StringUtils.isNotBlank(user.getPhone())) {
|
|
574
|
-
Map<String, String> params = new LinkedHashMap<>();
|
|
575
|
-
params.put("content", message);
|
|
576
|
-
|
|
577
|
-
SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
|
|
578
|
-
smsBlend.sendMessage(user.getPhone(), "SMS_APPROVAL", params);
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
// 邮件通知
|
|
582
|
-
if (messageType.contains("email") && StringUtils.isNotBlank(user.getEmail())) {
|
|
583
|
-
String htmlContent = String.format("""
|
|
584
|
-
<div style="padding: 20px;">
|
|
585
|
-
<h3>%s</h3>
|
|
586
|
-
<p>%s</p>
|
|
587
|
-
<p>请登录系统处理。</p>
|
|
588
|
-
</div>
|
|
589
|
-
""", subject, message);
|
|
590
|
-
|
|
591
|
-
MailUtils.sendHtml(user.getEmail(), subject, htmlContent);
|
|
236
|
+
messageType, user.getUserId(), e.getMessage());
|
|
592
237
|
}
|
|
593
238
|
}
|
|
594
239
|
}
|
|
@@ -597,75 +242,25 @@ public class FlwCommonServiceImpl implements IFlwCommonService {
|
|
|
597
242
|
|
|
598
243
|
---
|
|
599
244
|
|
|
600
|
-
##
|
|
601
|
-
|
|
602
|
-
### ❌ 错误1:未配置短信/邮件服务就使用
|
|
245
|
+
## 四、常见错误
|
|
603
246
|
|
|
604
247
|
```java
|
|
605
|
-
// ❌
|
|
606
|
-
SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
|
|
607
|
-
smsBlend.sendMessage(...);
|
|
608
|
-
```
|
|
609
|
-
|
|
610
|
-
```yaml
|
|
611
|
-
# ✅ 正确:先配置
|
|
612
|
-
sms:
|
|
613
|
-
blends:
|
|
614
|
-
config1:
|
|
615
|
-
supplier: alibaba
|
|
616
|
-
access-key-id: xxx
|
|
617
|
-
access-key-secret: xxx
|
|
618
|
-
signature: 若依框架
|
|
619
|
-
```
|
|
620
|
-
|
|
621
|
-
### ❌ 错误2:邮箱使用登录密码而非授权码
|
|
622
|
-
|
|
623
|
-
```yaml
|
|
624
|
-
# ❌ 错误:使用登录密码
|
|
625
|
-
mail:
|
|
626
|
-
pass: my-login-password # 错误!
|
|
627
|
-
|
|
628
|
-
# ✅ 正确:使用授权码(在邮箱设置中生成)
|
|
629
|
-
mail:
|
|
630
|
-
pass: ABCDEFGHIJKLMNOP # 授权码
|
|
631
|
-
```
|
|
632
|
-
|
|
633
|
-
### ❌ 错误3:短信模板参数与模板不匹配
|
|
248
|
+
// ❌ 未配置就使用,getSmsBlend 返回 null → NPE
|
|
249
|
+
SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
|
|
250
|
+
smsBlend.sendMessage(...);
|
|
634
251
|
|
|
635
|
-
|
|
636
|
-
// ❌ 错误:参数名与模板中定义的不一致
|
|
637
|
-
Map<String, String> params = new LinkedHashMap<>();
|
|
252
|
+
// ❌ 模板参数名与模板定义不匹配
|
|
638
253
|
params.put("verifyCode", "123456"); // 模板中是 ${code}
|
|
254
|
+
// ✅ params.put("code", "123456");
|
|
639
255
|
|
|
640
|
-
//
|
|
641
|
-
Map<String, String> params = new LinkedHashMap<>();
|
|
642
|
-
params.put("code", "123456"); // 匹配模板 ${code}
|
|
643
|
-
```
|
|
644
|
-
|
|
645
|
-
### ❌ 错误4:未处理发送失败的情况
|
|
646
|
-
|
|
647
|
-
```java
|
|
648
|
-
// ❌ 错误:直接发送,不处理结果
|
|
649
|
-
SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
|
|
256
|
+
// ❌ 不检查发送结果
|
|
650
257
|
smsBlend.sendMessage(phone, templateId, params);
|
|
651
|
-
//
|
|
258
|
+
// ✅ SmsResponse response = smsBlend.sendMessage(...);
|
|
259
|
+
// if (!response.isSuccess()) throw new ServiceException("短信发送失败");
|
|
652
260
|
|
|
653
|
-
//
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
log.error("短信发送失败:{}", response.getMessage());
|
|
657
|
-
throw new ServiceException("短信发送失败,请稍后重试");
|
|
658
|
-
}
|
|
659
|
-
```
|
|
660
|
-
|
|
661
|
-
### ❌ 错误5:验证码明文存储或无过期时间
|
|
662
|
-
|
|
663
|
-
```java
|
|
664
|
-
// ❌ 错误:无过期时间
|
|
665
|
-
RedisUtils.setCacheObject("sms:code:" + phone, code); // 永不过期
|
|
666
|
-
|
|
667
|
-
// ✅ 正确:设置合理的过期时间
|
|
668
|
-
RedisUtils.setCacheObject("sms:code:" + phone, code, Duration.ofMinutes(5));
|
|
261
|
+
// ❌ 验证码无过期时间
|
|
262
|
+
RedisUtils.setCacheObject("sms:code:" + phone, code);
|
|
263
|
+
// ✅ RedisUtils.setCacheObject("sms:code:" + phone, code, Duration.ofMinutes(5));
|
|
669
264
|
```
|
|
670
265
|
|
|
671
266
|
---
|
|
@@ -677,90 +272,37 @@ RedisUtils.setCacheObject("sms:code:" + phone, code, Duration.ofMinutes(5));
|
|
|
677
272
|
| 方法 | 说明 |
|
|
678
273
|
|------|------|
|
|
679
274
|
| `SmsFactory.getSmsBlend()` | 获取默认短信实例 |
|
|
680
|
-
| `SmsFactory.getSmsBlend("config1")` |
|
|
275
|
+
| `SmsFactory.getSmsBlend("config1")` | 获取指定配置短信实例 |
|
|
681
276
|
| `smsBlend.sendMessage(phone, templateId, params)` | 发送模板短信 |
|
|
682
|
-
| `smsBlend.sendMessage(phones, templateId, params)` |
|
|
683
|
-
| `response.isSuccess()` |
|
|
684
|
-
| `response.getMessage()` |
|
|
685
|
-
| `response.getBizId()` |
|
|
277
|
+
| `smsBlend.sendMessage(phones, templateId, params)` | 批量发送 |
|
|
278
|
+
| `response.isSuccess()` | 是否成功 |
|
|
279
|
+
| `response.getMessage()` | 错误信息 |
|
|
280
|
+
| `response.getBizId()` | 消息ID |
|
|
686
281
|
|
|
687
282
|
### 邮件 API(MailUtils)
|
|
688
283
|
|
|
689
284
|
| 方法 | 说明 |
|
|
690
285
|
|------|------|
|
|
691
|
-
| `MailUtils.sendText(to, subject, content)` |
|
|
692
|
-
| `MailUtils.sendHtml(to, subject, content)` |
|
|
693
|
-
| `MailUtils.sendText(to, subject, content, files...)` |
|
|
694
|
-
| `MailUtils.sendHtml(to, subject, content, files...)` |
|
|
695
|
-
| `MailUtils.sendHtml(to, subject, content, imageMap)` |
|
|
696
|
-
| `MailUtils.send(to, cc, bcc, subject, content, isHtml)` |
|
|
697
|
-
| `MailUtils.sendHtml(tos, subject, content)` | 群发
|
|
698
|
-
| `MailUtils.getMailAccount()` |
|
|
699
|
-
|
|
700
|
-
---
|
|
701
|
-
|
|
702
|
-
## 六、配置参考
|
|
703
|
-
|
|
704
|
-
### 短信完整配置
|
|
705
|
-
|
|
706
|
-
```yaml
|
|
707
|
-
sms:
|
|
708
|
-
# 配置源类型:yaml、接口、数据库
|
|
709
|
-
config-type: yaml
|
|
710
|
-
# 短信拦截配置(可选)
|
|
711
|
-
restricted:
|
|
712
|
-
# 是否开启账号维度的每日发送上限
|
|
713
|
-
account-max: 0
|
|
714
|
-
# 是否开启手机号维度的每分钟发送上限
|
|
715
|
-
minute-max: 1
|
|
716
|
-
# 单账号每日发送上限
|
|
717
|
-
account-max-count: 10
|
|
718
|
-
# 单手机号每分钟发送上限
|
|
719
|
-
minute-max-count: 1
|
|
720
|
-
|
|
721
|
-
blends:
|
|
722
|
-
config1:
|
|
723
|
-
supplier: alibaba
|
|
724
|
-
access-key-id: ${ALIYUN_SMS_ACCESS_KEY_ID}
|
|
725
|
-
access-key-secret: ${ALIYUN_SMS_ACCESS_KEY_SECRET}
|
|
726
|
-
signature: 若依框架
|
|
727
|
-
|
|
728
|
-
config2:
|
|
729
|
-
supplier: tencent
|
|
730
|
-
access-key-id: ${TENCENT_SMS_SECRET_ID}
|
|
731
|
-
access-key-secret: ${TENCENT_SMS_SECRET_KEY}
|
|
732
|
-
signature: 若依框架
|
|
733
|
-
sdk-app-id: "1400000000"
|
|
734
|
-
```
|
|
735
|
-
|
|
736
|
-
### 邮件完整配置
|
|
737
|
-
|
|
738
|
-
```yaml
|
|
739
|
-
mail:
|
|
740
|
-
enabled: true
|
|
741
|
-
host: smtp.163.com
|
|
742
|
-
port: 465
|
|
743
|
-
auth: true
|
|
744
|
-
from: system@example.com
|
|
745
|
-
user: system@example.com
|
|
746
|
-
pass: ${MAIL_PASSWORD}
|
|
747
|
-
sslEnable: true
|
|
748
|
-
starttlsEnable: false
|
|
749
|
-
timeout: 10000
|
|
750
|
-
connectionTimeout: 10000
|
|
751
|
-
```
|
|
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()` | 获取邮件配置 |
|
|
752
294
|
|
|
753
295
|
---
|
|
754
296
|
|
|
755
|
-
##
|
|
297
|
+
## 六、核心文件位置
|
|
756
298
|
|
|
757
299
|
| 类型 | 位置 |
|
|
758
300
|
|------|------|
|
|
759
|
-
| 短信配置 | `ruoyi-common/ruoyi-common-sms
|
|
760
|
-
| 短信缓存 | `ruoyi-common/ruoyi-common-sms
|
|
761
|
-
| 邮件工具类 | `ruoyi-common/ruoyi-common-mail
|
|
762
|
-
| 邮件配置 | `ruoyi-common/ruoyi-common-mail
|
|
763
|
-
| 邮件属性 | `ruoyi-common/ruoyi-common-mail
|
|
764
|
-
| 验证码示例 | `ruoyi-admin
|
|
765
|
-
| 短信演示 | `ruoyi-modules/ruoyi-demo
|
|
766
|
-
| 工作流消息 | `ruoyi-modules/ruoyi-workflow
|
|
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` |
|