ai-engineering-init 1.6.0 → 1.8.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.
Files changed (187) hide show
  1. package/.claude/agents/code-reviewer.md +3 -130
  2. package/.claude/hooks/skill-forced-eval.js +46 -60
  3. package/.claude/hooks/stop.js +24 -1
  4. package/.claude/settings.json +10 -1
  5. package/.claude/skills/api-development/SKILL.md +179 -130
  6. package/.claude/skills/architecture-design/SKILL.md +102 -212
  7. package/.claude/skills/backend-annotations/SKILL.md +166 -220
  8. package/.claude/skills/bug-detective/SKILL.md +225 -186
  9. package/.claude/skills/code-patterns/SKILL.md +127 -244
  10. package/.claude/skills/codex-code-review/SKILL.md +327 -0
  11. package/.claude/skills/collaborating-with-codex/SKILL.md +96 -113
  12. package/.claude/skills/crud-development/SKILL.md +226 -307
  13. package/.claude/skills/data-permission/SKILL.md +131 -202
  14. package/.claude/skills/database-ops/SKILL.md +158 -355
  15. package/.claude/skills/error-handler/SKILL.md +224 -285
  16. package/.claude/skills/file-oss-management/SKILL.md +174 -169
  17. package/.claude/skills/git-workflow/SKILL.md +123 -341
  18. package/.claude/skills/json-serialization/SKILL.md +121 -137
  19. package/.claude/skills/leniu-report-customization/SKILL.md +82 -2
  20. package/.claude/skills/leniu-report-standard-customization/SKILL.md +65 -2
  21. package/.claude/skills/loki-log-query/SKILL.md +400 -0
  22. package/.claude/skills/mysql-debug/SKILL.md +58 -22
  23. package/.claude/skills/performance-doctor/SKILL.md +83 -89
  24. package/.claude/skills/redis-cache/SKILL.md +134 -185
  25. package/.claude/skills/scheduled-jobs/SKILL.md +187 -224
  26. package/.claude/skills/security-guard/SKILL.md +168 -276
  27. package/.claude/skills/sms-mail/SKILL.md +266 -228
  28. package/.claude/skills/social-login/SKILL.md +257 -195
  29. package/.claude/skills/sync-back-merge/SKILL.md +66 -0
  30. package/.claude/skills/tenant-management/SKILL.md +172 -188
  31. package/.claude/skills/utils-toolkit/SKILL.md +214 -222
  32. package/.claude/skills/websocket-sse/SKILL.md +251 -172
  33. package/.claude/skills/workflow-engine/SKILL.md +178 -250
  34. package/.claude/skills/yunxiao-task-management/SKILL.md +489 -0
  35. package/.codex/skills/api-development/SKILL.md +179 -130
  36. package/.codex/skills/architecture-design/SKILL.md +102 -212
  37. package/.codex/skills/backend-annotations/SKILL.md +166 -220
  38. package/.codex/skills/bug-detective/SKILL.md +225 -186
  39. package/.codex/skills/code-patterns/SKILL.md +127 -244
  40. package/.codex/skills/collaborating-with-codex/SKILL.md +96 -113
  41. package/.codex/skills/crud-development/SKILL.md +226 -307
  42. package/.codex/skills/data-permission/SKILL.md +131 -202
  43. package/.codex/skills/database-ops/SKILL.md +158 -355
  44. package/.codex/skills/error-handler/SKILL.md +224 -285
  45. package/.codex/skills/file-oss-management/SKILL.md +174 -169
  46. package/.codex/skills/git-workflow/SKILL.md +123 -341
  47. package/.codex/skills/json-serialization/SKILL.md +121 -137
  48. package/.codex/skills/leniu-report-customization/SKILL.md +82 -2
  49. package/.codex/skills/leniu-report-standard-customization/SKILL.md +65 -2
  50. package/.codex/skills/loki-log-query/SKILL.md +400 -0
  51. package/.codex/skills/loki-log-query/environments.json +45 -0
  52. package/.codex/skills/mysql-debug/SKILL.md +58 -22
  53. package/.codex/skills/performance-doctor/SKILL.md +83 -89
  54. package/.codex/skills/redis-cache/SKILL.md +134 -185
  55. package/.codex/skills/scheduled-jobs/SKILL.md +187 -224
  56. package/.codex/skills/security-guard/SKILL.md +168 -276
  57. package/.codex/skills/skill-creator/LICENSE.txt +202 -0
  58. package/.codex/skills/skill-creator/SKILL.md +479 -0
  59. package/.codex/skills/skill-creator/agents/analyzer.md +274 -0
  60. package/.codex/skills/skill-creator/agents/comparator.md +202 -0
  61. package/.codex/skills/skill-creator/agents/grader.md +223 -0
  62. package/.codex/skills/skill-creator/assets/eval_review.html +146 -0
  63. package/.codex/skills/skill-creator/eval-viewer/generate_review.py +471 -0
  64. package/.codex/skills/skill-creator/eval-viewer/viewer.html +1325 -0
  65. package/.codex/skills/skill-creator/references/schemas.md +430 -0
  66. package/.codex/skills/skill-creator/scripts/__init__.py +0 -0
  67. package/.codex/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  68. package/.codex/skills/skill-creator/scripts/generate_report.py +326 -0
  69. package/.codex/skills/skill-creator/scripts/improve_description.py +248 -0
  70. package/.codex/skills/skill-creator/scripts/package_skill.py +136 -0
  71. package/.codex/skills/skill-creator/scripts/quick_validate.py +103 -0
  72. package/.codex/skills/skill-creator/scripts/run_eval.py +310 -0
  73. package/.codex/skills/skill-creator/scripts/run_loop.py +332 -0
  74. package/.codex/skills/skill-creator/scripts/utils.py +47 -0
  75. package/.codex/skills/sms-mail/SKILL.md +266 -228
  76. package/.codex/skills/social-login/SKILL.md +257 -195
  77. package/.codex/skills/sync-back-merge/SKILL.md +66 -0
  78. package/.codex/skills/tenant-management/SKILL.md +172 -188
  79. package/.codex/skills/utils-toolkit/SKILL.md +214 -222
  80. package/.codex/skills/websocket-sse/SKILL.md +251 -172
  81. package/.codex/skills/workflow-engine/SKILL.md +178 -250
  82. package/.codex/skills/yunxiao-task-management/SKILL.md +489 -0
  83. package/.cursor/hooks/cursor-skill-eval.js +66 -6
  84. package/.cursor/hooks/stop.js +23 -1
  85. package/.cursor/skills/api-development/SKILL.md +179 -130
  86. package/.cursor/skills/architecture-design/SKILL.md +102 -212
  87. package/.cursor/skills/backend-annotations/SKILL.md +166 -220
  88. package/.cursor/skills/bug-detective/SKILL.md +225 -186
  89. package/.cursor/skills/code-patterns/SKILL.md +127 -244
  90. package/.cursor/skills/collaborating-with-codex/SKILL.md +96 -113
  91. package/.cursor/skills/crud-development/SKILL.md +226 -307
  92. package/.cursor/skills/data-permission/SKILL.md +131 -202
  93. package/.cursor/skills/database-ops/SKILL.md +158 -355
  94. package/.cursor/skills/error-handler/SKILL.md +224 -285
  95. package/.cursor/skills/file-oss-management/SKILL.md +174 -169
  96. package/.cursor/skills/git-workflow/SKILL.md +123 -341
  97. package/.cursor/skills/json-serialization/SKILL.md +121 -137
  98. package/.cursor/skills/leniu-report-customization/SKILL.md +82 -2
  99. package/.cursor/skills/leniu-report-standard-customization/SKILL.md +65 -2
  100. package/.cursor/skills/loki-log-query/SKILL.md +400 -0
  101. package/.cursor/skills/loki-log-query/environments.json +45 -0
  102. package/.cursor/skills/mysql-debug/SKILL.md +58 -22
  103. package/.cursor/skills/performance-doctor/SKILL.md +83 -89
  104. package/.cursor/skills/redis-cache/SKILL.md +134 -185
  105. package/.cursor/skills/scheduled-jobs/SKILL.md +187 -224
  106. package/.cursor/skills/security-guard/SKILL.md +168 -276
  107. package/.cursor/skills/skill-creator/LICENSE.txt +202 -0
  108. package/.cursor/skills/skill-creator/SKILL.md +479 -0
  109. package/.cursor/skills/skill-creator/agents/analyzer.md +274 -0
  110. package/.cursor/skills/skill-creator/agents/comparator.md +202 -0
  111. package/.cursor/skills/skill-creator/agents/grader.md +223 -0
  112. package/.cursor/skills/skill-creator/assets/eval_review.html +146 -0
  113. package/.cursor/skills/skill-creator/eval-viewer/generate_review.py +471 -0
  114. package/.cursor/skills/skill-creator/eval-viewer/viewer.html +1325 -0
  115. package/.cursor/skills/skill-creator/references/schemas.md +430 -0
  116. package/.cursor/skills/skill-creator/scripts/__init__.py +0 -0
  117. package/.cursor/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  118. package/.cursor/skills/skill-creator/scripts/generate_report.py +326 -0
  119. package/.cursor/skills/skill-creator/scripts/improve_description.py +248 -0
  120. package/.cursor/skills/skill-creator/scripts/package_skill.py +136 -0
  121. package/.cursor/skills/skill-creator/scripts/quick_validate.py +103 -0
  122. package/.cursor/skills/skill-creator/scripts/run_eval.py +310 -0
  123. package/.cursor/skills/skill-creator/scripts/run_loop.py +332 -0
  124. package/.cursor/skills/skill-creator/scripts/utils.py +47 -0
  125. package/.cursor/skills/sms-mail/SKILL.md +266 -228
  126. package/.cursor/skills/social-login/SKILL.md +257 -195
  127. package/.cursor/skills/sync-back-merge/SKILL.md +66 -0
  128. package/.cursor/skills/tenant-management/SKILL.md +172 -188
  129. package/.cursor/skills/utils-toolkit/SKILL.md +214 -222
  130. package/.cursor/skills/websocket-sse/SKILL.md +251 -172
  131. package/.cursor/skills/workflow-engine/SKILL.md +178 -250
  132. package/.cursor/skills/yunxiao-task-management/SKILL.md +489 -0
  133. package/AGENTS.md +49 -540
  134. package/CLAUDE.md +73 -119
  135. package/README.md +37 -6
  136. package/bin/index.js +611 -25
  137. package/package.json +1 -1
  138. package/src/platform-map.json +4 -0
  139. package/src/skills/api-development/SKILL.md +179 -130
  140. package/src/skills/architecture-design/SKILL.md +102 -212
  141. package/src/skills/backend-annotations/SKILL.md +166 -220
  142. package/src/skills/bug-detective/SKILL.md +225 -186
  143. package/src/skills/code-patterns/SKILL.md +127 -244
  144. package/src/skills/codex-code-review/SKILL.md +261 -69
  145. package/src/skills/collaborating-with-codex/SKILL.md +96 -113
  146. package/src/skills/crud-development/SKILL.md +226 -307
  147. package/src/skills/data-permission/SKILL.md +131 -202
  148. package/src/skills/database-ops/SKILL.md +158 -355
  149. package/src/skills/error-handler/SKILL.md +224 -285
  150. package/src/skills/file-oss-management/SKILL.md +174 -169
  151. package/src/skills/git-workflow/SKILL.md +123 -341
  152. package/src/skills/json-serialization/SKILL.md +121 -137
  153. package/src/skills/leniu-report-customization/SKILL.md +82 -2
  154. package/src/skills/leniu-report-standard-customization/SKILL.md +65 -2
  155. package/src/skills/loki-log-query/SKILL.md +400 -0
  156. package/src/skills/loki-log-query/environments.json +45 -0
  157. package/src/skills/mysql-debug/SKILL.md +58 -22
  158. package/src/skills/performance-doctor/SKILL.md +83 -89
  159. package/src/skills/redis-cache/SKILL.md +134 -185
  160. package/src/skills/scheduled-jobs/SKILL.md +187 -224
  161. package/src/skills/security-guard/SKILL.md +168 -276
  162. package/src/skills/skill-creator/LICENSE.txt +202 -0
  163. package/src/skills/skill-creator/SKILL.md +479 -0
  164. package/src/skills/skill-creator/agents/analyzer.md +274 -0
  165. package/src/skills/skill-creator/agents/comparator.md +202 -0
  166. package/src/skills/skill-creator/agents/grader.md +223 -0
  167. package/src/skills/skill-creator/assets/eval_review.html +146 -0
  168. package/src/skills/skill-creator/eval-viewer/generate_review.py +471 -0
  169. package/src/skills/skill-creator/eval-viewer/viewer.html +1325 -0
  170. package/src/skills/skill-creator/references/schemas.md +430 -0
  171. package/src/skills/skill-creator/scripts/__init__.py +0 -0
  172. package/src/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  173. package/src/skills/skill-creator/scripts/generate_report.py +326 -0
  174. package/src/skills/skill-creator/scripts/improve_description.py +248 -0
  175. package/src/skills/skill-creator/scripts/package_skill.py +136 -0
  176. package/src/skills/skill-creator/scripts/quick_validate.py +103 -0
  177. package/src/skills/skill-creator/scripts/run_eval.py +310 -0
  178. package/src/skills/skill-creator/scripts/run_loop.py +332 -0
  179. package/src/skills/skill-creator/scripts/utils.py +47 -0
  180. package/src/skills/sms-mail/SKILL.md +266 -228
  181. package/src/skills/social-login/SKILL.md +257 -195
  182. package/src/skills/sync-back-merge/SKILL.md +66 -0
  183. package/src/skills/tenant-management/SKILL.md +172 -188
  184. package/src/skills/utils-toolkit/SKILL.md +214 -222
  185. package/src/skills/websocket-sse/SKILL.md +251 -172
  186. package/src/skills/workflow-engine/SKILL.md +178 -250
  187. package/src/skills/yunxiao-task-management/SKILL.md +489 -0
@@ -1,239 +1,300 @@
1
1
  ---
2
2
  name: sms-mail
3
3
  description: |
4
- 当需要发送短信、邮件通知时自动使用此 Skill。
5
-
4
+ 通用短信邮件开发指南。涵盖抽象接口模式、多渠道切换策略、验证码流程、模板消息。
6
5
  触发场景:
7
- - 需要发送短信验证码
8
- - 需要发送邮件通知(文本/HTML
9
- - 需要配置短信服务商(阿里云/腾讯云/华为云等)
10
- - 需要配置邮件服务器(SMTP)
11
- - 需要实现多渠道消息通知
12
-
13
- 触发词:短信、邮件、SMS、验证码、通知、SMS4j、MailUtils、邮件发送、短信发送、SmsBlend、SmsFactory、SMTP、模板消息
6
+ - 发送短信验证码
7
+ - 发送邮件通知(文本/HTML/附件)
8
+ - 配置短信服务商(阿里云/腾讯云等)
9
+ - 配置邮件服务器(SMTP)
10
+ - 实现多渠道消息通知
11
+ 触发词:短信、邮件、SMS、验证码、通知、邮件发送、短信发送、SMTP、模板消息、多渠道
12
+ 注意:如果项目有专属技能,优先使用专属版本。
14
13
  ---
15
14
 
16
15
  # 短信与邮件开发指南
17
16
 
18
- > 短信模块:`ruoyi-common/ruoyi-common-sms`(SMS4j)
19
- > 邮件模块:`ruoyi-common/ruoyi-common-mail`(Hutool JakartaMail)
17
+ > 通用模板。如果项目有专属技能,优先使用。
20
18
 
21
- ---
19
+ ## 设计原则
22
20
 
23
- ## 一、短信开发(SMS4j)
21
+ 1. **接口抽象**:定义统一的发送接口,具体渠道通过策略模式实现,方便切换供应商。
22
+ 2. **异步发送**:发送操作应异步执行,不阻塞主业务流程。
23
+ 3. **结果校验**:每次发送必须检查返回结果,失败需记录日志并决策是否重试。
24
+ 4. **安全防护**:验证码需限频限量、设置过期时间,防止短信轰炸和暴力破解。
24
25
 
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
- ```
26
+ ---
43
27
 
44
- > 支持的服务商:alibaba / tencent / huawei / yunpian / unisms / jdcloud / cloopen / emay / ctyun
45
- > 限流配置详见 [references/sms-config.md](references/sms-config.md)
28
+ ## 实现模式
46
29
 
47
- ### 1.2 核心 API
30
+ ### 一、抽象接口设计
48
31
 
49
32
  ```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());
33
+ // 统一短信发送接口
34
+ public interface SmsSender {
35
+ SmsResult send(String phone, String templateId, Map<String, String> params);
36
+ SmsResult batchSend(List<String> phones, String templateId, Map<String, String> params);
73
37
  }
74
- ```
75
38
 
76
- ### 1.3 验证码短信示例
39
+ // 统一邮件发送接口
40
+ public interface MailSender {
41
+ String sendText(String to, String subject, String content);
42
+ String sendHtml(String to, String subject, String htmlContent);
43
+ String sendWithAttachment(String to, String subject, String content, File... files);
44
+ }
77
45
 
78
- ```java
79
- @RestController
80
- @RequestMapping("/captcha")
81
- @RequiredArgsConstructor
82
- public class CaptchaController {
46
+ // 发送结果
47
+ @Data
48
+ public class SmsResult {
49
+ private boolean success;
50
+ private String messageId;
51
+ private String errorMessage;
52
+ }
53
+ ```
83
54
 
84
- @GetMapping("/sms")
85
- public R<Void> sendSmsCode(@RequestParam String phonenumber) {
86
- String code = RandomUtil.randomNumbers(6);
55
+ ### 二、短信服务
87
56
 
88
- // 存入Redis(5分钟有效)
89
- RedisUtils.setCacheObject("sms:code:" + phonenumber, code, Duration.ofMinutes(5));
57
+ #### 多渠道策略模式
90
58
 
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);
59
+ ```java
60
+ // 阿里云实现
61
+ @Service("aliyunSmsSender")
62
+ public class AliyunSmsSender implements SmsSender {
63
+ @Override
64
+ public SmsResult send(String phone, String templateId, Map<String, String> params) {
65
+ // 调用阿里云 SMS SDK
66
+ }
67
+ }
96
68
 
97
- if (!smsResponse.isSuccess()) {
98
- log.error("短信发送失败:{}", smsResponse.getMessage());
99
- return R.fail("短信发送失败");
100
- }
101
- return R.ok("验证码已发送");
69
+ // 腾讯云实现
70
+ @Service("tencentSmsSender")
71
+ public class TencentSmsSender implements SmsSender {
72
+ @Override
73
+ public SmsResult send(String phone, String templateId, Map<String, String> params) {
74
+ // 调用腾讯云 SMS SDK
102
75
  }
76
+ }
103
77
 
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);
78
+ // 工厂/路由
79
+ @Service
80
+ public class SmsService {
81
+
82
+ @Autowired
83
+ private Map<String, SmsSender> senderMap;
109
84
 
110
- if (cachedCode == null) return R.fail("验证码已过期");
111
- if (!cachedCode.equals(code)) return R.fail("验证码错误");
85
+ @Value("${sms.default-channel:aliyunSmsSender}")
86
+ private String defaultChannel;
112
87
 
113
- RedisUtils.deleteObject(cacheKey);
114
- return R.ok(true);
88
+ public SmsResult send(String phone, String templateId, Map<String, String> params) {
89
+ SmsSender sender = senderMap.get(defaultChannel);
90
+ if (sender == null) {
91
+ throw new [你的异常类]("短信渠道未配置: " + defaultChannel);
92
+ }
93
+ SmsResult result = sender.send(phone, templateId, params);
94
+ if (!result.isSuccess()) {
95
+ log.error("短信发送失败: phone={}, error={}", phone, result.getErrorMessage());
96
+ }
97
+ return result;
115
98
  }
116
99
  }
117
100
  ```
118
101
 
119
- ---
120
-
121
- ## 二、邮件开发(Hutool Mail)
122
-
123
- ### 2.1 配置
102
+ #### 配置示例
124
103
 
125
104
  ```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
105
+ sms:
106
+ default-channel: aliyunSmsSender
107
+ aliyun:
108
+ access-key-id: ${ALIYUN_SMS_KEY:}
109
+ access-key-secret: ${ALIYUN_SMS_SECRET:}
110
+ sign-name: [你的签名]
111
+ tencent:
112
+ secret-id: ${TENCENT_SMS_ID:}
113
+ secret-key: ${TENCENT_SMS_KEY:}
114
+ sdk-app-id: "1400000000"
115
+ sign-name: [你的签名]
138
116
  ```
139
117
 
140
- > 常用 SMTP 服务器配置详见 [references/mail-config.md](references/mail-config.md)
141
-
142
- ### 2.2 核心 API
118
+ #### 验证码短信完整示例
143
119
 
144
120
  ```java
145
- import org.dromara.common.mail.utils.MailUtils;
121
+ @RestController
122
+ @RequestMapping("/captcha")
123
+ public class CaptchaController {
146
124
 
147
- // 发送文本邮件
148
- String msgId = MailUtils.sendText("to@example.com", "标题", "正文内容");
125
+ @Autowired
126
+ private SmsService smsService;
149
127
 
150
- // 发送HTML邮件
151
- String msgId = MailUtils.sendHtml("to@example.com", "标题", "<h1>内容</h1>");
128
+ @Autowired
129
+ private StringRedisTemplate redisTemplate;
152
130
 
153
- // 带附件
154
- String msgId = MailUtils.sendText("to@example.com", "标题", "正文", attachment);
155
- String msgId = MailUtils.sendHtml("to@example.com", "标题", htmlContent, attachment);
131
+ @GetMapping("/sms")
132
+ public Result<?> sendSmsCode(@RequestParam String phone) {
133
+ // 1. 限频检查(60秒内只能发一次)
134
+ String limitKey = "sms:limit:" + phone;
135
+ if (Boolean.TRUE.equals(redisTemplate.hasKey(limitKey))) {
136
+ return Result.fail("请60秒后重试");
137
+ }
156
138
 
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);
139
+ // 2. 生成验证码
140
+ String code = String.valueOf(ThreadLocalRandom.current().nextInt(100000, 999999));
161
141
 
162
- // 群发
163
- String msgId = MailUtils.sendHtml(List.of("a@x.com", "b@x.com"), "标题", htmlContent);
142
+ // 3. 存入 Redis(5分钟有效)
143
+ String codeKey = "sms:code:" + phone;
144
+ redisTemplate.opsForValue().set(codeKey, code, 5, TimeUnit.MINUTES);
145
+ redisTemplate.opsForValue().set(limitKey, "1", 60, TimeUnit.SECONDS);
164
146
 
165
- // 抄送/密送
166
- String msgId = MailUtils.send("to@x.com", "cc@x.com", "bcc@x.com", "标题", "内容", true);
147
+ // 4. 发送短信
148
+ Map<String, String> params = new LinkedHashMap<>();
149
+ params.put("code", code);
150
+ SmsResult result = smsService.send(phone, "SMS_VERIFY_CODE", params);
167
151
 
168
- // 获取邮件账户配置
169
- MailAccount account = MailUtils.getMailAccount();
152
+ if (!result.isSuccess()) {
153
+ redisTemplate.delete(codeKey);
154
+ return Result.fail("短信发送失败");
155
+ }
156
+ return Result.ok("验证码已发送");
157
+ }
158
+
159
+ @PostMapping("/verify")
160
+ public Result<Boolean> verify(@RequestParam String phone, @RequestParam String code) {
161
+ String codeKey = "sms:code:" + phone;
162
+ String cached = redisTemplate.opsForValue().get(codeKey);
163
+
164
+ if (cached == null) return Result.fail("验证码已过期");
165
+ if (!cached.equals(code)) return Result.fail("验证码错误");
166
+
167
+ redisTemplate.delete(codeKey);
168
+ return Result.ok(true);
169
+ }
170
+ }
170
171
  ```
171
172
 
172
- ### 2.3 邮件验证码示例
173
+ ---
174
+
175
+ ### 三、邮件服务
176
+
177
+ #### Spring Boot 原生邮件
173
178
 
174
179
  ```java
175
180
  @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
- }
181
+ public class MailService {
182
+
183
+ @Autowired
184
+ private JavaMailSender mailSender;
185
+
186
+ @Value("${spring.mail.username}")
187
+ private String from;
188
+
189
+ // 文本邮件
190
+ public void sendText(String to, String subject, String content) {
191
+ SimpleMailMessage message = new SimpleMailMessage();
192
+ message.setFrom(from);
193
+ message.setTo(to);
194
+ message.setSubject(subject);
195
+ message.setText(content);
196
+ mailSender.send(message);
197
197
  }
198
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;
199
+ // HTML 邮件
200
+ public void sendHtml(String to, String subject, String htmlContent) throws MessagingException {
201
+ MimeMessage message = mailSender.createMimeMessage();
202
+ MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
203
+ helper.setFrom(from);
204
+ helper.setTo(to);
205
+ helper.setSubject(subject);
206
+ helper.setText(htmlContent, true);
207
+ mailSender.send(message);
208
+ }
209
+
210
+ // 带附件邮件
211
+ public void sendWithAttachment(String to, String subject, String content,
212
+ File... attachments) throws MessagingException {
213
+ MimeMessage message = mailSender.createMimeMessage();
214
+ MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
215
+ helper.setFrom(from);
216
+ helper.setTo(to);
217
+ helper.setSubject(subject);
218
+ helper.setText(content, true);
219
+ for (File file : attachments) {
220
+ helper.addAttachment(file.getName(), file);
205
221
  }
206
- return false;
222
+ mailSender.send(message);
223
+ }
224
+
225
+ // 群发
226
+ public void sendHtml(List<String> toList, String subject, String htmlContent)
227
+ throws MessagingException {
228
+ MimeMessage message = mailSender.createMimeMessage();
229
+ MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
230
+ helper.setFrom(from);
231
+ helper.setTo(toList.toArray(new String[0]));
232
+ helper.setSubject(subject);
233
+ helper.setText(htmlContent, true);
234
+ mailSender.send(message);
207
235
  }
208
236
  }
209
237
  ```
210
238
 
239
+ #### 配置示例
240
+
241
+ ```yaml
242
+ spring:
243
+ mail:
244
+ host: smtp.163.com
245
+ port: 465
246
+ username: system@example.com
247
+ password: ${MAIL_PASSWORD} # 授权码,非登录密码
248
+ properties:
249
+ mail:
250
+ smtp:
251
+ auth: true
252
+ ssl:
253
+ enable: true
254
+ timeout: 10000
255
+ connectiontimeout: 10000
256
+ ```
257
+
258
+ 常用 SMTP 服务器:
259
+
260
+ | 提供商 | Host | 端口(SSL) |
261
+ |--------|------|----------|
262
+ | 163 | smtp.163.com | 465 |
263
+ | QQ | smtp.qq.com | 465 |
264
+ | Gmail | smtp.gmail.com | 465 |
265
+ | 阿里企业邮箱 | smtp.qiye.aliyun.com | 465 |
266
+
211
267
  ---
212
268
 
213
- ## 三、多渠道消息通知示例
269
+ ### 四、多渠道消息通知
214
270
 
215
271
  ```java
216
272
  @Service
217
- @RequiredArgsConstructor
218
273
  public class MessageService {
219
274
 
220
- public void sendMessage(List<String> messageType, String subject,
221
- String message, List<UserDTO> userList) {
222
- for (UserDTO user : userList) {
275
+ @Autowired
276
+ private SmsService smsService;
277
+
278
+ @Autowired
279
+ private MailService mailService;
280
+
281
+ // 可扩展:站内信、推送、企业微信等
282
+ public void sendNotification(List<String> channels, String subject,
283
+ String content, List<UserDTO> users) {
284
+ for (UserDTO user : users) {
223
285
  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);
286
+ if (channels.contains("sms") && StringUtils.hasText(user.getPhone())) {
287
+ Map<String, String> params = Map.of("content", content);
288
+ smsService.send(user.getPhone(), "SMS_NOTIFY", params);
229
289
  }
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>");
290
+ if (channels.contains("email") && StringUtils.hasText(user.getEmail())) {
291
+ mailService.sendHtml(user.getEmail(), subject,
292
+ "<div style='padding:20px;'><h3>" + subject + "</h3><p>" + content + "</p></div>");
233
293
  }
234
294
  } catch (Exception e) {
235
- log.error("消息发送失败,类型:{},用户:{},错误:{}",
236
- messageType, user.getUserId(), e.getMessage());
295
+ log.error("消息发送失败, channel={}, userId={}, error={}",
296
+ channels, user.getUserId(), e.getMessage());
297
+ // 不抛出,继续发送其他用户
237
298
  }
238
299
  }
239
300
  }
@@ -242,67 +303,44 @@ public class MessageService {
242
303
 
243
304
  ---
244
305
 
245
- ## 四、常见错误
306
+ ## 选型建议
246
307
 
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
- ```
308
+ | 维度 | 自研抽象层 | SMS4j | 云 SDK 直接调用 |
309
+ |------|----------|-------|----------------|
310
+ | 灵活性 | 最高 | 高 | 中 |
311
+ | 开发成本 | 中 | 低 | 低 |
312
+ | 多渠道切换 | 需自行实现 | 内置支持 | 需改代码 |
313
+ | 维护成本 | 中 | 低(社区维护) | 低 |
314
+ | 适用场景 | 定制需求高 | 通用 | 单一渠道 |
265
315
 
266
316
  ---
267
317
 
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()` | 获取邮件配置 |
318
+ ## 常见错误
294
319
 
295
- ---
320
+ ```java
321
+ // 1. 未配置就使用,NPE
322
+ SmsSender sender = senderMap.get("xxx");
323
+ sender.send(phone, template, params); // sender 为 null -> NPE
324
+ // 应先判空
325
+
326
+ // 2. 模板参数名与模板定义不匹配
327
+ params.put("verifyCode", "123456"); // 模板中变量名是 code
328
+ // 应确认模板变量名
329
+
330
+ // 3. 不检查发送结果
331
+ smsService.send(phone, templateId, params); // 发送可能失败
332
+ // 应检查 SmsResult.isSuccess()
296
333
 
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` |
334
+ // 4. 验证码无过期时间
335
+ redisTemplate.opsForValue().set("sms:code:" + phone, code); // 永不过期
336
+ // 应设置 5-10 分钟过期
337
+
338
+ // 5. 无限频控制(短信轰炸)
339
+ // 应限制:同号码 60秒 1 次、每天最多 10 次
340
+
341
+ // 6. 邮件密码用登录密码而非授权码
342
+ // 大多数邮件服务商需要使用"授权码"而非"登录密码"
343
+
344
+ // 7. 同步发送阻塞主线程
345
+ // 短信/邮件发送应使用 @Async 异步执行
346
+ ```