Commit 225664550f3cd23b4c26a9aded828cf39c2ec0eb

Authored by zengzefeng
1 parent 767cd902

实现短信通过redis stream异步发送,实现短信callback处理

Showing 53 changed files with 819 additions and 434 deletions
sql/sms.sql
... ... @@ -12,7 +12,6 @@ CREATE TABLE `sms_channel`
12 12 `code` varchar(50) NOT NULL COMMENT '编码(来自枚举类 阿里、华为、七牛等)',
13 13 `api_key` varchar(100) NOT NULL COMMENT '账号id',
14 14 `api_secret` varchar(100) NOT NULL COMMENT '账号秘钥',
15   - `had_callback` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否拥有回调函数',
16 15 `callback_url` varchar(100) NOT NULL default '' COMMENT '回调请求路径',
17 16 `api_signature_id` varchar(100) NOT NULL COMMENT '实际渠道签名唯一标识',
18 17 `name` varchar(50) NOT NULL COMMENT '名称',
... ... @@ -61,7 +60,7 @@ CREATE TABLE `sms_template`
61 60 ) ENGINE = InnoDB
62 61 AUTO_INCREMENT = 1
63 62 DEFAULT CHARSET = utf8mb4 COMMENT ='短信模板';
64   -
  63 +/*
65 64 -- ----------------------------
66 65 -- Table structure for sms_query_log
67 66 -- ----------------------------
... ... @@ -73,7 +72,7 @@ CREATE TABLE `sms_query_log`
73 72 `channel_code` varchar(50) NOT NULL COMMENT '短信渠道编码(来自枚举类)',
74 73 `channel_id` bigint(20) NOT NULL COMMENT '短信渠道id',
75 74 `template_code` varchar(50) NOT NULL COMMENT '渠道编码',
76   - `phones` varchar(2000) NOT NULL COMMENT '手机号(数组json字符串)',
  75 + `phone` char(11) NOT NULL COMMENT '手机号',
77 76 `content` varchar(1000) NOT NULL DEFAULT '' COMMENT '内容',
78 77 `send_result_param` varchar(200) NOT NULL DEFAULT '' COMMENT '查询短信发送结果的参数',
79 78 `send_status` tinyint(1) NOT NULL DEFAULT 2 COMMENT '发送状态(0本地异步中 1发送请求失败 2发送请求成功)',
... ... @@ -84,24 +83,26 @@ CREATE TABLE `sms_query_log`
84 83 PRIMARY KEY (`id`) USING BTREE
85 84 ) ENGINE = InnoDB
86 85 AUTO_INCREMENT = 1
87   - DEFAULT CHARSET = utf8mb4 COMMENT ='短信请求日志';
  86 + DEFAULT CHARSET = utf8mb4 COMMENT ='短信请求日志';*/
88 87  
89 88 -- ----------------------------
90 89 -- Table structure for sms_log
91 90 -- ----------------------------
92   -DROP TABLE IF EXISTS `sms_send_log`;
93   -CREATE TABLE `sms_send_log`
  91 +DROP TABLE IF EXISTS `sms_query_log`;
  92 +CREATE TABLE `sms_query_log`
94 93 (
95 94 `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增编号',
  95 + `api_id` varchar(100) NOT NULL COMMENT '第三方唯一标识',
96 96 `channel_code` varchar(50) NOT NULL COMMENT '短信渠道编码(来自枚举类)',
97 97 `channel_id` bigint(20) NOT NULL COMMENT '短信渠道id',
98 98 `template_code` varchar(50) NOT NULL COMMENT '渠道编码',
99   - `query_log_id` bigint(20) NOT NULL COMMENT '请求日志id',
100 99 `phone` char(11) NOT NULL COMMENT '手机号',
101 100 `content` varchar(1000) NOT NULL DEFAULT '' COMMENT '内容',
  101 + `send_status` tinyint(1) NOT NULL DEFAULT 0 COMMENT '发送状态 详情见:SmsSendStatusEnum',
102 102 `remark` varchar(200) DEFAULT NULL COMMENT '备注',
103   - `success` tinyint(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
104   - `send_time` datetime DEFAULT NULL COMMENT '创建时间',
  103 + `create_by` varchar(64) NOT NULL DEFAULT '' COMMENT '创建者',
  104 + `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  105 + `send_time` datetime DEFAULT NULL COMMENT '发送时间',
105 106 PRIMARY KEY (`id`) USING BTREE
106 107 ) ENGINE = InnoDB
107 108 AUTO_INCREMENT = 1
... ...
src/main/java/cn/iocoder/dashboard/DashboardApplication.java
1   -//package cn.iocoder.dashboard;
2   -//
3   -//import de.codecentric.boot.admin.server.config.EnableAdminServer;
4   -//import org.springframework.boot.SpringApplication;
5   -//import org.springframework.boot.autoconfigure.SpringBootApplication;
6   -//
7   -//@SpringBootApplication
8   -//@EnableAdminServer
9   -//public class DashboardApplication {
10   -//
11   -// public static void main(String[] args) {
12   -// SpringApplication.run(DashboardApplication.class, args);
13   -// }
14   -//
15   -//}
  1 +package cn.iocoder.dashboard;
  2 +
  3 +import de.codecentric.boot.admin.server.config.EnableAdminServer;
  4 +import org.springframework.boot.SpringApplication;
  5 +import org.springframework.boot.autoconfigure.SpringBootApplication;
  6 +
  7 +@SpringBootApplication
  8 +@EnableAdminServer
  9 +public class DashboardApplication {
  10 +
  11 + public static void main(String[] args) {
  12 + SpringApplication.run(DashboardApplication.class, args);
  13 + }
  14 +
  15 +}
... ...
src/main/java/cn/iocoder/dashboard/framework/redis/core/util/RedisStreamUtils.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.redis.core.util;
  2 +
  3 +import cn.iocoder.dashboard.modules.system.redis.stream.sms.SmsSendMessage;
  4 +import org.springframework.data.redis.connection.stream.StreamRecords;
  5 +import org.springframework.data.redis.core.RedisTemplate;
  6 +
  7 +/**
  8 + * Redis 消息工具类
  9 + *
  10 + * @author 芋道源码
  11 + */
  12 +public class RedisStreamUtils {
  13 +
  14 + public static final String KEY_SMS_SEND = "stream_sms_send";
  15 +
  16 + public static final String GROUP_SMS_SEND = "group_sms_send";
  17 +
  18 + /**
  19 + * 发送 Redis 消息,基于 Redis pub/sub 实现
  20 + *
  21 + * @param redisTemplate Redis 操作模板
  22 + * @param message 消息
  23 + */
  24 + public static void sendChannelMessage(RedisTemplate<String, ?> redisTemplate, SmsSendMessage message) {
  25 +
  26 + redisTemplate.opsForStream().add(StreamRecords.newRecord().ofObject(message).withStreamKey(KEY_SMS_SEND));
  27 + }
  28 +
  29 +}
... ...
src/main/java/cn/iocoder/dashboard/framework/security/config/SecurityConfiguration.java
... ... @@ -134,6 +134,7 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
134 134 .antMatchers(webProperties.getApiPrefix() + "/system/file/get/**").anonymous()
135 135 // TODO
136 136 .antMatchers("/swagger-ui.html").anonymous()
  137 + .antMatchers("/**").anonymous()
137 138 .antMatchers("/swagger-resources/**").anonymous()
138 139 .antMatchers("/webjars/**").anonymous()
139 140 .antMatchers("/*/api-docs").anonymous()
... ...
src/main/java/cn/iocoder/dashboard/framework/sms/client/AbstractSmsClient.java
... ... @@ -5,8 +5,6 @@ import cn.iocoder.dashboard.framework.sms.core.SmsResult;
5 5 import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperty;
6 6 import lombok.extern.slf4j.Slf4j;
7 7  
8   -import java.util.Collection;
9   -
10 8 /**
11 9 * 抽象短息客户端
12 10 *
... ... @@ -35,12 +33,12 @@ public abstract class AbstractSmsClient implements SmsClient {
35 33 }
36 34  
37 35 @Override
38   - public final SmsResult send(String templateApiId, SmsBody smsBody, Collection<String> targets) {
  36 + public final SmsResult send(String templateApiId, SmsBody smsBody, String target) {
39 37 SmsResult result;
40 38 try {
41   - beforeSend(templateApiId, smsBody, targets);
42   - result = doSend(templateApiId, smsBody, targets);
43   - afterSend(templateApiId, smsBody, targets, result);
  39 + beforeSend(templateApiId, smsBody, target);
  40 + result = doSend(templateApiId, smsBody, target);
  41 + afterSend(templateApiId, smsBody, target, result);
44 42 } catch (Exception e) {
45 43 // exception handle
46 44 log.debug(e.getMessage(), e);
... ... @@ -54,16 +52,16 @@ public abstract class AbstractSmsClient implements SmsClient {
54 52 *
55 53 * @param templateApiId 短信模板唯一标识
56 54 * @param smsBody 消息内容
57   - * @param targets 发送对象列表
  55 + * @param targetPhone 发送对象手机号
58 56 * @return 短信发送结果
59 57 * @throws Exception 调用发送失败,抛出异常
60 58 */
61   - protected abstract SmsResult doSend(String templateApiId, SmsBody smsBody, Collection<String> targets) throws Exception;
  59 + protected abstract SmsResult doSend(String templateApiId, SmsBody smsBody, String targetPhone) throws Exception;
62 60  
63   - protected void beforeSend(String templateApiId, SmsBody smsBody, Collection<String> targets) throws Exception {
  61 + protected void beforeSend(String templateApiId, SmsBody smsBody, String targetPhone) throws Exception {
64 62 }
65 63  
66   - protected void afterSend(String templateApiId, SmsBody smsBody, Collection<String> targets, SmsResult result) throws Exception {
  64 + protected void afterSend(String templateApiId, SmsBody smsBody, String targetPhone, SmsResult result) throws Exception {
67 65 }
68 66  
69 67 }
... ...
src/main/java/cn/iocoder/dashboard/framework/sms/client/AliyunSmsClient.java deleted
1   -package cn.iocoder.dashboard.framework.sms.client;
2   -
3   -import cn.hutool.core.date.DateUtil;
4   -import cn.hutool.core.util.ArrayUtil;
5   -import cn.iocoder.dashboard.framework.sms.core.SmsBody;
6   -import cn.iocoder.dashboard.framework.sms.core.SmsResult;
7   -import cn.iocoder.dashboard.framework.sms.core.SmsResultDetail;
8   -import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperty;
9   -import cn.iocoder.dashboard.modules.system.enums.sms.SmsSendStatusEnum;
10   -import com.aliyuncs.DefaultAcsClient;
11   -import com.aliyuncs.IAcsClient;
12   -import com.aliyuncs.dysmsapi.model.v20170525.QuerySendDetailsRequest;
13   -import com.aliyuncs.dysmsapi.model.v20170525.QuerySendDetailsResponse;
14   -import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
15   -import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
16   -import com.aliyuncs.exceptions.ClientException;
17   -import com.aliyuncs.http.MethodType;
18   -import com.aliyuncs.profile.DefaultProfile;
19   -import com.aliyuncs.profile.IClientProfile;
20   -import lombok.extern.slf4j.Slf4j;
21   -
22   -import java.util.ArrayList;
23   -import java.util.Collection;
24   -import java.util.List;
25   -
26   -/**
27   - * 阿里短信实现类
28   - *
29   - * @author zzf
30   - * @date 2021/1/25 14:17
31   - */
32   -@Slf4j
33   -public class AliyunSmsClient extends AbstractSmsClient implements NeedQuerySendResultSmsClient {
34   -
35   - private static final String OK = "OK";
36   -
37   - private static final String PRODUCT = "Dysmsapi";
38   -
39   - private static final String DOMAIN = "dysmsapi.aliyuncs.com";
40   -
41   - private static final String ENDPOINT = "cn-hangzhou";
42   -
43   - private final IAcsClient acsClient;
44   -
45   - /**
46   - * 构造阿里云短信发送处理
47   - *
48   - * @param channelVO 阿里云短信配置
49   - */
50   - public AliyunSmsClient(SmsChannelProperty channelVO) {
51   - super(channelVO);
52   -
53   - String accessKeyId = channelVO.getApiKey();
54   - String accessKeySecret = channelVO.getApiSecret();
55   -
56   - IClientProfile profile = DefaultProfile.getProfile(ENDPOINT, accessKeyId, accessKeySecret);
57   - DefaultProfile.addEndpoint(ENDPOINT, PRODUCT, DOMAIN);
58   -
59   - acsClient = new DefaultAcsClient(profile);
60   - }
61   -
62   - @Override
63   - public SmsResult doSend(String templateApiId, SmsBody smsBody, Collection<String> targets) throws Exception {
64   - SendSmsRequest request = new SendSmsRequest();
65   - request.setSysMethod(MethodType.POST);
66   - request.setPhoneNumbers(ArrayUtil.join(targets, ","));
67   - request.setSignName(channelVO.getApiSignatureId());
68   - request.setTemplateCode(templateApiId);
69   - request.setTemplateParam(smsBody.getParamsStr());
70   - SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request);
71   -
72   - boolean success = OK.equals(sendSmsResponse.getCode());
73   - if (!success) {
74   - log.debug("send fail[code={}, message={}]", sendSmsResponse.getCode(), sendSmsResponse.getMessage());
75   - }
76   - return new SmsResult()
77   - .setSuccess(success)
78   - .setMessage(sendSmsResponse.getMessage())
79   - .setCode(sendSmsResponse.getCode())
80   - .setApiId(sendSmsResponse.getBizId())
81   - .setSendResultParam(sendSmsResponse.getBizId());
82   - }
83   -
84   -
85   - @Override
86   - public List<SmsResultDetail> getSmsSendResult(String param) throws ClientException {
87   - QuerySendDetailsRequest querySendDetailsRequest = new QuerySendDetailsRequest();
88   - querySendDetailsRequest.setBizId(param);
89   - // TODO FROM 芋艿 to zzf:发送完之后,基于短信平台回调,去更新回执状态。短信发送是否成功,和最终用户收到,是两个维度。这块有困惑,可以微信,我给个截图哈。 DONE
90   - QuerySendDetailsResponse acsResponse = acsClient.getAcsResponse(querySendDetailsRequest);
91   - List<SmsResultDetail> resultDetailList = new ArrayList<>(Integer.parseInt(acsResponse.getTotalCount()));
92   - acsResponse.getSmsSendDetailDTOs().forEach(s -> {
93   - SmsResultDetail resultDetail = new SmsResultDetail();
94   - resultDetail.setSendTime(DateUtil.parseDateTime(s.getSendDate()));
95   - resultDetail.setMessage(s.getContent());
96   - resultDetail.setPhone(s.getPhoneNum());
97   - resultDetail.setSendStatus(statusConvert(s.getSendStatus()));
98   - resultDetailList.add(resultDetail);
99   - });
100   - return resultDetailList;
101   - }
102   -
103   - private int statusConvert(Long aliSendStatus) {
104   - if (aliSendStatus == 1L) {
105   - return SmsSendStatusEnum.SEND_SUCCESS.getStatus();
106   - }
107   - if (aliSendStatus == 2L) {
108   - return SmsSendStatusEnum.SEND_FAIL.getStatus();
109   - }
110   - return SmsSendStatusEnum.WAITING.getStatus();
111   - }
112   -
113   -}
src/main/java/cn/iocoder/dashboard/framework/sms/client/HadCallbackSmsClient.java deleted
1   -package cn.iocoder.dashboard.framework.sms.client;
2   -
3   -import cn.iocoder.dashboard.framework.sms.core.SmsResultDetail;
4   -
5   -import javax.servlet.ServletRequest;
6   -import java.io.UnsupportedEncodingException;
7   -import java.util.List;
8   -
9   -/**
10   - * 需要发送请求获取短信发送结果的短信客户端
11   - *
12   - * @author zzf
13   - * @date 2021/3/4 17:20
14   - */
15   -public interface HadCallbackSmsClient {
16   -
17   - /**
18   - * 获取短信发送结果
19   - *
20   - * @param request 请求
21   - * @return 短信发送结果
22   - */
23   - List<SmsResultDetail> getSmsSendResult(ServletRequest request) throws Exception;
24   -
25   -}
src/main/java/cn/iocoder/dashboard/framework/sms/client/NeedQuerySendResultSmsClient.java deleted
1   -package cn.iocoder.dashboard.framework.sms.client;
2   -
3   -import cn.iocoder.dashboard.framework.sms.core.SmsResultDetail;
4   -import com.aliyuncs.exceptions.ClientException;
5   -
6   -import java.util.List;
7   -
8   -/**
9   - * 需要发送请求获取短信发送结果的短信客户端
10   - *
11   - * @author zzf
12   - * @date 2021/3/4 17:20
13   - */
14   -public interface NeedQuerySendResultSmsClient {
15   -
16   - /**
17   - * 获取短信发送结果
18   - *
19   - * @param param 参数
20   - * @return 短信发送结果
21   - */
22   - List<SmsResultDetail> getSmsSendResult(String param) throws Exception;
23   -
24   -}
src/main/java/cn/iocoder/dashboard/framework/sms/client/SmsClient.java
... ... @@ -4,8 +4,7 @@ import cn.iocoder.dashboard.framework.sms.core.SmsBody;
4 4 import cn.iocoder.dashboard.framework.sms.core.SmsResult;
5 5 import cn.iocoder.dashboard.framework.sms.core.SmsResultDetail;
6 6  
7   -import java.util.Collection;
8   -import java.util.List;
  7 +import javax.servlet.ServletRequest;
9 8  
10 9 /**
11 10 * 短信父接口
... ... @@ -23,9 +22,15 @@ public interface SmsClient {
23 22 * @param targets 发送对象列表
24 23 * @return 短信发送结果
25 24 */
26   - SmsResult send(String templateApiId, SmsBody smsBody, Collection<String> targets);
  25 + SmsResult send(String templateApiId, SmsBody smsBody, String targets);
27 26  
28 27  
29   - //List<SmsResultDetail> getSmsSendResult(String jsonObjectParam);
  28 + /**
  29 + * 短信发送回调请求处理
  30 + *
  31 + * @param request 请求
  32 + * @return 短信发送结果
  33 + */
  34 + SmsResultDetail smsSendCallbackHandle(ServletRequest request) throws Exception;
30 35  
31 36 }
32 37 \ No newline at end of file
... ...
src/main/java/cn/iocoder/dashboard/framework/sms/client/impl/ali/AliyunSmsClient.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.sms.client.impl.ali;
  2 +
  3 +import cn.hutool.core.collection.CollectionUtil;
  4 +import cn.hutool.core.date.DateUtil;
  5 +import cn.iocoder.dashboard.framework.sms.client.AbstractSmsClient;
  6 +import cn.iocoder.dashboard.framework.sms.core.SmsBody;
  7 +import cn.iocoder.dashboard.framework.sms.core.SmsResult;
  8 +import cn.iocoder.dashboard.framework.sms.core.SmsResultDetail;
  9 +import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperty;
  10 +import cn.iocoder.dashboard.modules.system.enums.sms.SmsSendStatusEnum;
  11 +import cn.iocoder.dashboard.util.json.JsonUtils;
  12 +import com.aliyuncs.DefaultAcsClient;
  13 +import com.aliyuncs.IAcsClient;
  14 +import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
  15 +import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
  16 +import com.aliyuncs.http.MethodType;
  17 +import com.aliyuncs.profile.DefaultProfile;
  18 +import com.aliyuncs.profile.IClientProfile;
  19 +import com.fasterxml.jackson.core.type.TypeReference;
  20 +import lombok.extern.slf4j.Slf4j;
  21 +
  22 +import javax.servlet.ServletRequest;
  23 +import java.io.BufferedReader;
  24 +import java.io.InputStreamReader;
  25 +import java.util.Date;
  26 +import java.util.HashMap;
  27 +import java.util.List;
  28 +import java.util.Map;
  29 +
  30 +/**
  31 + * 阿里短信实现类
  32 + *
  33 + * @author zzf
  34 + * @date 2021/1/25 14:17
  35 + */
  36 +@Slf4j
  37 +public class AliyunSmsClient extends AbstractSmsClient {
  38 +
  39 + private static final String OK = "OK";
  40 +
  41 + private static final String PRODUCT = "Dystopi";
  42 +
  43 + private static final String DOMAIN = "dysmsapi.aliyuncs.com";
  44 +
  45 + private static final String ENDPOINT = "cn-hangzhou";
  46 +
  47 + private final IAcsClient acsClient;
  48 +
  49 + /**
  50 + * 构造阿里云短信发送处理
  51 + *
  52 + * @param channelVO 阿里云短信配置
  53 + */
  54 + public AliyunSmsClient(SmsChannelProperty channelVO) {
  55 + super(channelVO);
  56 +
  57 + String accessKeyId = channelVO.getApiKey();
  58 + String accessKeySecret = channelVO.getApiSecret();
  59 +
  60 + IClientProfile profile = DefaultProfile.getProfile(ENDPOINT, accessKeyId, accessKeySecret);
  61 + DefaultProfile.addEndpoint(ENDPOINT, PRODUCT, DOMAIN);
  62 +
  63 + acsClient = new DefaultAcsClient(profile);
  64 + }
  65 +
  66 + @Override
  67 + public SmsResult doSend(String templateApiId, SmsBody smsBody, String targetPhone) throws Exception {
  68 + SendSmsRequest request = new SendSmsRequest();
  69 + request.setSysMethod(MethodType.POST);
  70 + request.setPhoneNumbers(targetPhone);
  71 + request.setSignName(channelVO.getApiSignatureId());
  72 + request.setTemplateCode(templateApiId);
  73 + request.setTemplateParam(smsBody.getParamsStr());
  74 + SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request);
  75 +
  76 + boolean success = OK.equals(sendSmsResponse.getCode());
  77 + if (!success) {
  78 + log.debug("send fail[code={}, message={}]", sendSmsResponse.getCode(), sendSmsResponse.getMessage());
  79 + }
  80 + return new SmsResult()
  81 + .setSuccess(success)
  82 + .setMessage(sendSmsResponse.getMessage())
  83 + .setCode(sendSmsResponse.getCode())
  84 + .setApiId(sendSmsResponse.getBizId());
  85 + }
  86 +
  87 + /**
  88 + * [{
  89 + * "send_time" : "2017-08-30 00:00:00",
  90 + * "report_time" : "2017-08-30 00:00:00",
  91 + * "success" : true,
  92 + * "err_msg" : "用户接收成功",
  93 + * "err_code" : "DELIVERED",
  94 + * "phone_number" : "18612345678",
  95 + * "sms_size" : "1",
  96 + * "biz_id" : "932702304080415357^0",
  97 + * "out_id" : "1184585343"
  98 + * }]
  99 + *
  100 + * @param request 请求
  101 + * @return
  102 + * @throws Exception
  103 + */
  104 + @Override
  105 + public SmsResultDetail smsSendCallbackHandle(ServletRequest request) throws Exception {
  106 + BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream()));
  107 + String paramStr = reader.readLine();
  108 + List<Map<String, Object>> params = JsonUtils.parseByType(paramStr, new TypeReference<List<Map<String, Object>>>() {
  109 + });
  110 + if (CollectionUtil.isNotEmpty(params)) {
  111 + Map<String, Object> sendResultParamMap = params.get(0);
  112 + return CallbackHelper.of(sendResultParamMap).toResultDetail();
  113 + }
  114 + return null;
  115 + }
  116 +
  117 + /**
  118 + * 短信发送回调辅助类
  119 + */
  120 + private static class CallbackHelper {
  121 +
  122 + private final Map<String, Object> sendResultParamMap;
  123 +
  124 + private CallbackHelper(Map<String, Object> sendResultParamMap) {
  125 + this.sendResultParamMap = sendResultParamMap;
  126 + }
  127 +
  128 + public static CallbackHelper of(Map<String, Object> sendResultParamMap) {
  129 + return new CallbackHelper(sendResultParamMap);
  130 + }
  131 +
  132 + public Integer getSendStatus() {
  133 + return ((Boolean) sendResultParamMap.get(CallbackField.SUCCESS))
  134 + ? SmsSendStatusEnum.SEND_SUCCESS.getStatus()
  135 + : SmsSendStatusEnum.SEND_FAIL.getStatus();
  136 + }
  137 +
  138 + public String getBizId() {
  139 + return sendResultParamMap.get(CallbackField.BIZ_ID).toString();
  140 + }
  141 +
  142 + public String getErrMsg() {
  143 + return sendResultParamMap.get(CallbackField.ERR_MSG).toString();
  144 + }
  145 +
  146 + public String getErrCode() {
  147 + return sendResultParamMap.get(CallbackField.ERR_CODE).toString();
  148 + }
  149 +
  150 + public Date getSendTime() {
  151 + return DateUtil.parseTime(sendResultParamMap.get(CallbackField.SEND_TIME).toString());
  152 + }
  153 +
  154 + public String getPhoneNumber() {
  155 + return sendResultParamMap.get(CallbackField.PHONE_NUMBER).toString();
  156 + }
  157 +
  158 + public String getOutId() {
  159 + return sendResultParamMap.get(CallbackField.OUT_ID).toString();
  160 + }
  161 +
  162 + public SmsResultDetail toResultDetail() {
  163 + SmsResultDetail resultDetail = new SmsResultDetail();
  164 + resultDetail.setSendStatus(getSendStatus());
  165 + resultDetail.setApiId(getBizId());
  166 + resultDetail.setSendTime(getSendTime());
  167 + resultDetail.setPhone(getPhoneNumber());
  168 + resultDetail.setMessage(getErrMsg());
  169 +
  170 + resultDetail.setCallbackResponseBody(generateSuccessResponseBody());
  171 + return resultDetail;
  172 + }
  173 +
  174 + /**
  175 + * 生成回调成功的返回对象
  176 + */
  177 + private Map<String, Object> generateSuccessResponseBody() {
  178 + Map<String, Object> result = new HashMap<>();
  179 + result.put("code", 0);
  180 + result.put("msg", "成功");
  181 + return result;
  182 + }
  183 +
  184 + }
  185 +
  186 + /**
  187 + * 回调接口字段定义
  188 + */
  189 + private interface CallbackField {
  190 + //是否成功 boolean
  191 + String SUCCESS = "success";
  192 +
  193 + //发送时间
  194 + String SEND_TIME = "send_time";
  195 +
  196 + //错误信息
  197 + String ERR_MSG = "err_msg";
  198 +
  199 + //错误编码
  200 + String ERR_CODE = "err_code";
  201 +
  202 + //手机号
  203 + String PHONE_NUMBER = "phone_number";
  204 +
  205 + //用户序列号 out_id
  206 + String OUT_ID = "out_id";
  207 +
  208 + //biz_id 即 apiId 唯一标识
  209 + String BIZ_ID = "biz_id";
  210 + }
  211 +
  212 +}
... ...
src/main/java/cn/iocoder/dashboard/framework/sms/client/YunpianSmsClient.java renamed to src/main/java/cn/iocoder/dashboard/framework/sms/client/impl/yunpian/YunpianSmsClient.java
1   -package cn.iocoder.dashboard.framework.sms.client;
  1 +package cn.iocoder.dashboard.framework.sms.client.impl.yunpian;
2 2  
  3 +import cn.hutool.core.collection.CollectionUtil;
3 4 import cn.hutool.core.date.DateUtil;
4 5 import cn.hutool.core.util.CharsetUtil;
  6 +import cn.iocoder.dashboard.framework.sms.client.AbstractSmsClient;
5 7 import cn.iocoder.dashboard.framework.sms.core.SmsBody;
6 8 import cn.iocoder.dashboard.framework.sms.core.SmsConstants;
7 9 import cn.iocoder.dashboard.framework.sms.core.SmsResult;
... ... @@ -14,13 +16,15 @@ import com.yunpian.sdk.YunpianClient;
14 16 import com.yunpian.sdk.constant.Code;
15 17 import com.yunpian.sdk.constant.YunpianConstant;
16 18 import com.yunpian.sdk.model.Result;
17   -import com.yunpian.sdk.model.SmsBatchSend;
  19 +import com.yunpian.sdk.model.SmsSingleSend;
18 20 import lombok.extern.slf4j.Slf4j;
19 21  
20 22 import javax.servlet.ServletRequest;
21 23 import java.io.UnsupportedEncodingException;
22 24 import java.net.URLEncoder;
23   -import java.util.*;
  25 +import java.util.HashMap;
  26 +import java.util.List;
  27 +import java.util.Map;
24 28  
25 29 /**
26 30 * 云片短信实现类
... ... @@ -29,7 +33,7 @@ import java.util.*;
29 33 * @date 9:48 2021/3/5
30 34 */
31 35 @Slf4j
32   -public class YunpianSmsClient extends AbstractSmsClient implements HadCallbackSmsClient {
  36 +public class YunpianSmsClient extends AbstractSmsClient {
33 37  
34 38 private final YunpianClient client;
35 39  
... ... @@ -47,14 +51,14 @@ public class YunpianSmsClient extends AbstractSmsClient implements HadCallbackSm
47 51 }
48 52  
49 53 @Override
50   - public SmsResult doSend(String templateApiId, SmsBody smsBody, Collection<String> targets) {
  54 + public SmsResult doSend(String templateApiId, SmsBody smsBody, String targetPhone) {
51 55 Map<String, String> paramMap = new HashMap<>();
52   - paramMap.put("apikey", getProperty().getApiKey());
53   - paramMap.put("mobile", String.join(SmsConstants.COMMA, targets));
54   - paramMap.put("text", formatContent(smsBody));
55   - paramMap.put("callback", getProperty().getCallbackUrl());
  56 + paramMap.put(YunpianConstant.APIKEY, getProperty().getApiKey());
  57 + paramMap.put(YunpianConstant.MOBILE, String.join(SmsConstants.COMMA, targetPhone));
  58 + paramMap.put(YunpianConstant.TEXT, formatContent(smsBody));
  59 + paramMap.put(Helper.CALLBACK, getProperty().getCallbackUrl());
56 60  
57   - Result<SmsBatchSend> sendResult = client.sms().batch_send(paramMap);
  61 + Result<SmsSingleSend> sendResult = client.sms().single_send(paramMap);
58 62 boolean success = sendResult.getCode().equals(Code.OK);
59 63  
60 64 if (!success) {
... ... @@ -64,7 +68,7 @@ public class YunpianSmsClient extends AbstractSmsClient implements HadCallbackSm
64 68 .setSuccess(success)
65 69 .setMessage(sendResult.getDetail())
66 70 .setCode(sendResult.getCode().toString())
67   - .setApiId(sendResult.getData().getData().get(0).getSid().toString());
  71 + .setApiId(sendResult.getData().getSid().toString());
68 72 }
69 73  
70 74  
... ... @@ -96,26 +100,16 @@ public class YunpianSmsClient extends AbstractSmsClient implements HadCallbackSm
96 100 }
97 101  
98 102  
  103 + /**
  104 + * 云片的比较复杂,又是加密又是套娃的
  105 + */
99 106 @Override
100   - public List<SmsResultDetail> getSmsSendResult(ServletRequest request) throws UnsupportedEncodingException {
101   - List<Map<String, String>> stringStringMap = getSendResult(request);
102   - List<SmsResultDetail> resultDetailList = new ArrayList<>(stringStringMap.size());
103   - stringStringMap.forEach(map -> {
104   - SmsResultDetail detail = new SmsResultDetail();
105   -
106   - detail.setPhone(map.get("mobile"));
107   - detail.setMessage(map.get("error_msg"));
108   - detail.setSendTime(DateUtil.parseTime(map.get("user_receive_time")));
109   - String reportStatus = map.get("report_status");
110   - detail.setSendStatus(reportStatus.equals(SmsConstants.SUCCESS)
111   - ? SmsSendStatusEnum.SEND_SUCCESS.getStatus()
112   - : SmsSendStatusEnum.SEND_FAIL.getStatus()
113   - );
114   - resultDetailList.add(detail);
115   - });
116   - return resultDetailList;
  107 + public SmsResultDetail smsSendCallbackHandle(ServletRequest request) throws UnsupportedEncodingException {
  108 + Map<String, String> map = getRequestParams(request);
  109 + return Helper.getSmsResultDetailByParam(map);
117 110 }
118 111  
  112 +
119 113 /**
120 114 * 从 request 中获取请求中传入的短信发送结果信息
121 115 *
... ... @@ -123,10 +117,58 @@ public class YunpianSmsClient extends AbstractSmsClient implements HadCallbackSm
123 117 * @return 短信发送结果信息
124 118 * @throws UnsupportedEncodingException 解码异常
125 119 */
126   - private List<Map<String, String>> getSendResult(ServletRequest request) throws UnsupportedEncodingException {
  120 + private Map<String, String> getRequestParams(ServletRequest request) throws UnsupportedEncodingException {
127 121 Map<String, String[]> parameterMap = request.getParameterMap();
128 122 String[] smsStatuses = parameterMap.get(YunpianConstant.SMS_STATUS);
129 123 String encode = URLEncoder.encode(smsStatuses[0], CharsetUtil.UTF_8);
130   - return JsonUtils.parseByType(encode, callbackType);
  124 + List<Map<String, String>> paramList = JsonUtils.parseByType(encode, callbackType);
  125 + if (CollectionUtil.isNotEmpty(paramList)) {
  126 + return paramList.get(0);
  127 + }
  128 + throw new IllegalArgumentException("YunpianSmsClient getRequestParams fail! can't format RequestParam: "
  129 + + JsonUtils.toJsonString(request.getParameterMap()));
  130 + }
  131 +
  132 + /**
  133 + * 云片的回调函数的一些辅助方法
  134 + */
  135 + private static class Helper {
  136 +
  137 + //短信唯一标识
  138 + private final static String API_ID = "sid";
  139 +
  140 + //回调地址·
  141 + private final static String CALLBACK = "callback";
  142 +
  143 + //手机号
  144 + private final static String MOBILE = "mobile";
  145 +
  146 + //错误信息
  147 + private final static String ERROR_MSG = "error_msg";
  148 +
  149 + //用户接收时间 字符串 标准格式
  150 + private final static String USER_RECEIVE_TIME = "user_receive_time";
  151 +
  152 + //发送状态
  153 + private final static String REPORT_STATUS = "report_status";
  154 +
  155 + private static int getSendStatus(Map<String, String> map) {
  156 + String reportStatus = map.get(REPORT_STATUS);
  157 + return SmsConstants.SUCCESS.equals(reportStatus)
  158 + ? SmsSendStatusEnum.SEND_SUCCESS.getStatus()
  159 + : SmsSendStatusEnum.SEND_FAIL.getStatus();
  160 + }
  161 +
  162 + public static SmsResultDetail getSmsResultDetailByParam(Map<String, String> map) {
  163 + SmsResultDetail detail = new SmsResultDetail();
  164 + detail.setPhone(map.get(MOBILE));
  165 + detail.setMessage(map.get(ERROR_MSG));
  166 + detail.setSendTime(DateUtil.parseTime(map.get(USER_RECEIVE_TIME)));
  167 + detail.setSendStatus(getSendStatus(map));
  168 + detail.setApiId(API_ID);
  169 +
  170 + detail.setCallbackResponseBody(SmsConstants.SUCCESS);
  171 + return detail;
  172 + }
131 173 }
132 174 }
... ...
src/main/java/cn/iocoder/dashboard/framework/sms/core/SmsClientFactory.java
... ... @@ -2,12 +2,15 @@ package cn.iocoder.dashboard.framework.sms.core;
2 2  
3 3 import cn.iocoder.dashboard.common.exception.ServiceException;
4 4 import cn.iocoder.dashboard.framework.sms.client.AbstractSmsClient;
5   -import cn.iocoder.dashboard.framework.sms.client.AliyunSmsClient;
  5 +import cn.iocoder.dashboard.framework.sms.client.impl.ali.AliyunSmsClient;
  6 +import cn.iocoder.dashboard.framework.sms.client.impl.yunpian.YunpianSmsClient;
6 7 import cn.iocoder.dashboard.framework.sms.core.enums.SmsChannelEnum;
7 8 import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperty;
8 9 import cn.iocoder.dashboard.framework.sms.core.property.SmsTemplateProperty;
  10 +import cn.iocoder.dashboard.util.json.JsonUtils;
9 11 import org.springframework.stereotype.Component;
10 12  
  13 +import javax.servlet.ServletRequest;
11 14 import java.util.Collection;
12 15 import java.util.Map;
13 16 import java.util.concurrent.ConcurrentHashMap;
... ... @@ -54,6 +57,8 @@ public class SmsClientFactory {
54 57 switch (channelEnum) {
55 58 case ALI:
56 59 return new AliyunSmsClient(channelVO);
  60 + case YUN_PIAN:
  61 + return new YunpianSmsClient(channelVO);
57 62 // TODO fill more channel
58 63 default:
59 64 break;
... ... @@ -102,4 +107,28 @@ public class SmsClientFactory {
102 107 return smsTemplateProperty.getApiTemplateId();
103 108 }
104 109  
  110 +
  111 + /**
  112 + * 从短信发送回调函数请求中获取用于唯一确定一条send_lod的apiId
  113 + *
  114 + * @param callbackRequest 短信发送回调函数请求
  115 + * @return 第三方平台短信唯一标识
  116 + */
  117 + public SmsResultDetail getSmsResultDetailFromCallbackQuery(ServletRequest callbackRequest) {
  118 +
  119 + for (Long channelId : smsSenderMap.keySet()) {
  120 + AbstractSmsClient smsClient = smsSenderMap.get(channelId);
  121 + try {
  122 + SmsResultDetail smsSendResult = smsClient.smsSendCallbackHandle(callbackRequest);
  123 + if (smsSendResult != null) {
  124 + return smsSendResult;
  125 + }
  126 + } catch (Exception ignored) {
  127 + }
  128 + }
  129 + throw new IllegalArgumentException("getSmsResultDetailFromCallbackQuery fail! don't match SmsClient by RequestParam: "
  130 + + JsonUtils.toJsonString(callbackRequest.getParameterMap()));
  131 + }
  132 +
  133 +
105 134 }
... ...
src/main/java/cn/iocoder/dashboard/framework/sms/core/SmsResult.java
... ... @@ -32,11 +32,6 @@ public class SmsResult implements Serializable {
32 32 */
33 33 private String message;
34 34  
35   - /**
36   - * 用于查询发送结果的参数
37   - */
38   - private String sendResultParam;
39   -
40 35 public static SmsResult failResult(String message) {
41 36 SmsResult resultBody = new SmsResult();
42 37 resultBody.setSuccess(false);
... ...
src/main/java/cn/iocoder/dashboard/framework/sms/core/SmsResultDetail.java
... ... @@ -12,6 +12,11 @@ import java.util.Date;
12 12 public class SmsResultDetail implements Serializable {
13 13  
14 14 /**
  15 + * 唯一标识
  16 + */
  17 + private String apiId;
  18 +
  19 + /**
15 20 * 短信发送状态 {@link cn.iocoder.dashboard.modules.system.enums.sms.SmsSendStatusEnum}
16 21 */
17 22 private Integer sendStatus;
... ... @@ -30,4 +35,9 @@ public class SmsResultDetail implements Serializable {
30 35 * 时间
31 36 */
32 37 private Date sendTime;
  38 +
  39 + /**
  40 + * 接口返回值
  41 + */
  42 + private Object callbackResponseBody;
33 43 }
... ...
src/main/java/cn/iocoder/dashboard/framework/sms/core/enums/SmsChannelEnum.java
... ... @@ -14,8 +14,8 @@ import lombok.Getter;
14 14 public enum SmsChannelEnum {
15 15  
16 16 ALI("ALI", "阿里"),
  17 + YUN_PIAN("YUN_PIAN", "云片"),
17 18 HUA_WEI("HUA_WEI", "华为"),
18   - QI_NIU("QI_NIU", "七牛"),
19 19 TENCENT("TENCENT", "腾讯");
20 20  
21 21 private final String code;
... ...
src/main/java/cn/iocoder/dashboard/modules/infra/controller/redis/RedisController.java
1   -package cn.iocoder.dashboard.modules.infra.controller.redis;
2   -
3   -import cn.hutool.core.util.StrUtil;
4   -import cn.iocoder.dashboard.common.pojo.CommonResult;
5   -import cn.iocoder.dashboard.framework.redis.core.RedisKeyRegistry;
6   -import cn.iocoder.dashboard.modules.infra.controller.redis.vo.InfRedisKeyRespVO;
7   -import cn.iocoder.dashboard.modules.infra.controller.redis.vo.InfRedisMonitorRespVO;
8   -import org.springframework.data.redis.connection.RedisServerCommands;
9   -import org.springframework.data.redis.core.RedisCallback;
10   -import org.springframework.data.redis.core.StringRedisTemplate;
11   -import org.springframework.web.bind.annotation.GetMapping;
12   -import org.springframework.web.bind.annotation.RequestMapping;
13   -import org.springframework.web.bind.annotation.RestController;
14   -
15   -import javax.annotation.Resource;
16   -import java.util.ArrayList;
17   -import java.util.List;
18   -import java.util.Properties;
19   -import java.util.stream.Collectors;
20   -
21   -import static cn.iocoder.dashboard.common.pojo.CommonResult.success;
22   -
23   -@RestController
24   -@RequestMapping("/infra/redis")
25   -public class RedisController {
26   -
27   - @Resource
28   - private StringRedisTemplate stringRedisTemplate;
29   -
30   -// @PreAuthorize("@ss.hasPermission('infra:redis:get-monitor-info')")
31   - @GetMapping("/get-monitor-info")
32   - public CommonResult<InfRedisMonitorRespVO> getRedisMonitorInfo() {
33   - // 获得 Redis 统计信息
34   - Properties info = stringRedisTemplate.execute((RedisCallback<Properties>) RedisServerCommands::info);
35   - Long dbSize = stringRedisTemplate.execute(RedisServerCommands::dbSize);
36   - Properties commandStats = stringRedisTemplate.execute((
37   - RedisCallback<Properties>) connection -> connection.info("commandstats"));
38   - assert commandStats != null; // 断言,避免警告
39   -
40   - // 拼接结果返回
41   - InfRedisMonitorRespVO respVO = InfRedisMonitorRespVO.builder().info(info).dbSize(dbSize)
42   - .commandStats(new ArrayList<>(commandStats.size())).build();
43   - commandStats.forEach((key, value) -> {
44   - respVO.getCommandStats().add(InfRedisMonitorRespVO.CommandStat.builder()
45   - .command(StrUtil.subAfter((String) key, "cmdstat_", false))
46   - .calls(Integer.valueOf(StrUtil.subBetween((String) value, "calls=", ",")))
47   - .usec(Integer.valueOf(StrUtil.subBetween((String) value, "usec=", ",")))
48   - .build());
49   - });
50   - return success(respVO);
51   - }
52   -
53   -// @PreAuthorize("@ss.hasPermission('infra:redis:get-key-list')")
54   - @GetMapping("/get-key-list")
55   - public CommonResult<List<InfRedisKeyRespVO>> getKeyList() {
56   - List<InfRedisKeyRespVO> respVOList = RedisKeyRegistry.list().stream()
57   - .map(define -> InfRedisKeyRespVO.builder()
58   - .keyTemplate(define.getKeyTemplate())
59   - .keyType(define.getKeyType().name())
60   - .valueType(define.getValueType().getName())
61   - .timeoutType(define.getTimeoutType().getType())
62   - .timeout((int) define.getTimeout().getSeconds())
63   - .build())
64   - .collect(Collectors.toList());
65   - return success(respVOList);
66   - }
67   -
68   -}
  1 +//package cn.iocoder.dashboard.modules.infra.controller.redis;
  2 +//
  3 +//import cn.hutool.core.util.StrUtil;
  4 +//import cn.iocoder.dashboard.common.pojo.CommonResult;
  5 +//import cn.iocoder.dashboard.framework.redis.core.RedisKeyRegistry;
  6 +//import cn.iocoder.dashboard.modules.infra.controller.redis.vo.InfRedisKeyRespVO;
  7 +//import cn.iocoder.dashboard.modules.infra.controller.redis.vo.InfRedisMonitorRespVO;
  8 +//import org.springframework.data.redis.connection.RedisServerCommands;
  9 +//import org.springframework.data.redis.core.RedisCallback;
  10 +//import org.springframework.data.redis.core.StringRedisTemplate;
  11 +//import org.springframework.web.bind.annotation.GetMapping;
  12 +//import org.springframework.web.bind.annotation.RequestMapping;
  13 +//import org.springframework.web.bind.annotation.RestController;
  14 +//
  15 +//import javax.annotation.Resource;
  16 +//import java.util.ArrayList;
  17 +//import java.util.List;
  18 +//import java.util.Properties;
  19 +//import java.util.stream.Collectors;
  20 +//
  21 +//import static cn.iocoder.dashboard.common.pojo.CommonResult.success;
  22 +//
  23 +//@RestController
  24 +//@RequestMapping("/infra/redis")
  25 +//public class RedisController {
  26 +//
  27 +// @Resource
  28 +// private StringRedisTemplate stringRedisTemplate;
  29 +//
  30 +//// @PreAuthorize("@ss.hasPermission('infra:redis:get-monitor-info')")
  31 +// @GetMapping("/get-monitor-info")
  32 +// public CommonResult<InfRedisMonitorRespVO> getRedisMonitorInfo() {
  33 +// // 获得 Redis 统计信息
  34 +// Properties info = stringRedisTemplate.execute((RedisCallback<Properties>) RedisServerCommands::info);
  35 +// Long dbSize = stringRedisTemplate.execute(RedisServerCommands::dbSize);
  36 +// Properties commandStats = stringRedisTemplate.execute((
  37 +// RedisCallback<Properties>) connection -> connection.info("commandstats"));
  38 +// assert commandStats != null; // 断言,避免警告
  39 +//
  40 +// // 拼接结果返回
  41 +// InfRedisMonitorRespVO respVO = InfRedisMonitorRespVO.builder().info(info).dbSize(dbSize)
  42 +// .commandStats(new ArrayList<>(commandStats.size())).build();
  43 +// commandStats.forEach((key, value) -> {
  44 +// respVO.getCommandStats().add(InfRedisMonitorRespVO.CommandStat.builder()
  45 +// .command(StrUtil.subAfter((String) key, "cmdstat_", false))
  46 +// .calls(Integer.valueOf(StrUtil.subBetween((String) value, "calls=", ",")))
  47 +// .usec(Integer.valueOf(StrUtil.subBetween((String) value, "usec=", ",")))
  48 +// .build());
  49 +// });
  50 +// return success(respVO);
  51 +// }
  52 +//
  53 +//// @PreAuthorize("@ss.hasPermission('infra:redis:get-key-list')")
  54 +// @GetMapping("/get-key-list")
  55 +// public CommonResult<List<InfRedisKeyRespVO>> getKeyList() {
  56 +// List<InfRedisKeyRespVO> respVOList = RedisKeyRegistry.list().stream()
  57 +// .map(define -> InfRedisKeyRespVO.builder()
  58 +// .keyTemplate(define.getKeyTemplate())
  59 +// .keyType(define.getKeyType().name())
  60 +// .valueType(define.getValueType().getName())
  61 +// .timeoutType(define.getTimeoutType().getType())
  62 +// .timeout((int) define.getTimeout().getSeconds())
  63 +// .build())
  64 +// .collect(Collectors.toList());
  65 +// return success(respVOList);
  66 +// }
  67 +//
  68 +//}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/SmsDefaultCallbackController.java
1 1 package cn.iocoder.dashboard.modules.system.controller.sms;
2 2  
  3 +import cn.iocoder.dashboard.framework.sms.core.SmsBody;
  4 +import cn.iocoder.dashboard.modules.system.redis.stream.sms.SmsSendStreamProducer;
3 5 import cn.iocoder.dashboard.modules.system.service.sms.SysSmsService;
  6 +import io.swagger.annotations.Api;
  7 +import io.swagger.annotations.ApiOperation;
  8 +import org.springframework.web.bind.annotation.GetMapping;
  9 +import org.springframework.web.bind.annotation.PostMapping;
4 10 import org.springframework.web.bind.annotation.RequestMapping;
5 11 import org.springframework.web.bind.annotation.RestController;
6 12  
7 13 import javax.annotation.Resource;
8 14 import javax.servlet.ServletRequest;
  15 +import java.util.Arrays;
  16 +import java.util.Map;
9 17  
10 18 /**
11 19 * 短信默认回调接口
... ... @@ -13,15 +21,33 @@ import javax.servlet.ServletRequest;
13 21 * @author zzf
14 22 * @date 2021/3/5 8:59
15 23 */
16   -@RestController("/sms/callback")
  24 +@Api(tags = "短信回调api")
  25 +@RestController
  26 +@RequestMapping("/sms/callback")
17 27 public class SmsDefaultCallbackController {
18 28  
19 29 @Resource
20 30 private SysSmsService smsService;
21 31  
22   - @RequestMapping("/sms-send")
23   - public Object sendSmsCallback(ServletRequest request){
  32 +
  33 + @ApiOperation(value = "短信发送回调接口")
  34 + @PostMapping("/sms-send")
  35 + public Object sendSmsCallback(ServletRequest request) {
24 36 return smsService.smsSendCallbackHandle(request);
25 37 }
26 38  
  39 +/*
  40 + @Resource
  41 + private SmsSendStreamProducer smsSendStreamProducer;
  42 +
  43 + @ApiOperation("redis stream测试")
  44 + @GetMapping("/test/redis/stream")
  45 + public void test() {
  46 + SmsBody smsBody = new SmsBody();
  47 + smsBody.setSmsLogId(1L);
  48 + smsBody.setTemplateCode("sdf");
  49 + smsBody.setTemplateContent("sdf");
  50 + smsSendStreamProducer.sendSmsSendMessage(smsBody, "18216466755");
  51 + }*/
  52 +
27 53 }
... ...
src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dao/sms/SysSmsQueryLogMapper.java
... ... @@ -14,14 +14,21 @@ public interface SysSmsQueryLogMapper extends BaseMapper&lt;SysSmsQueryLogDO&gt; {
14 14  
15 15 /**
16 16 * 查询还没有获取发送结果的短信请求信息
17   - *
18   - * @return
19 17 */
20 18 default List<SysSmsQueryLogDO> selectNoResultQueryLogList() {
21 19 return this.selectList(new LambdaQueryWrapper<SysSmsQueryLogDO>()
22 20 .eq(SysSmsQueryLogDO::getSendStatus, SmsSendStatusEnum.QUERY_SUCCESS)
23 21 .eq(SysSmsQueryLogDO::getGotResult, DefaultBitFieldEnum.NO)
24   - .eq(SysSmsQueryLogDO::getHadCallback, DefaultBitFieldEnum.NO)
25 22 );
26 23 }
  24 +
  25 +
  26 + /**
  27 + * 根据APIId修改对象
  28 + */
  29 + default boolean updateByApiId(SysSmsQueryLogDO queryLogDO, String apiId) {
  30 + return update(queryLogDO, new LambdaQueryWrapper<SysSmsQueryLogDO>()
  31 + .eq(SysSmsQueryLogDO::getApiId, apiId)
  32 + ) > 0;
  33 + }
27 34 }
... ...
src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dataobject/sms/SysSmsChannelDO.java
... ... @@ -28,11 +28,6 @@ public class SysSmsChannelDO extends BaseDO {
28 28 private String code;
29 29  
30 30 /**
31   - * 是否拥有回答(0否 1是)
32   - */
33   - private Integer had_callback;
34   -
35   - /**
36 31 * 短信发送回调url
37 32 */
38 33 private String callback_url;
... ...
src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dataobject/sms/SysSmsQueryLogDO.java
1 1 package cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.sms;
2 2  
3   -import com.baomidou.mybatisplus.annotation.TableField;
4 3 import com.baomidou.mybatisplus.annotation.TableName;
5   -import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
6 4 import lombok.Data;
7 5 import lombok.EqualsAndHashCode;
8 6 import lombok.experimental.Accessors;
9 7  
10 8 import java.io.Serializable;
11 9 import java.util.Date;
12   -import java.util.List;
13 10  
14 11 /**
15 12 * 短信日志
... ... @@ -29,6 +26,11 @@ public class SysSmsQueryLogDO implements Serializable {
29 26 private Long id;
30 27  
31 28 /**
  29 + * 第三方唯一标识
  30 + */
  31 + private String apiId;
  32 +
  33 + /**
32 34 * 短信渠道编码(来自枚举类)
33 35 */
34 36 private String channelCode;
... ... @@ -46,8 +48,7 @@ public class SysSmsQueryLogDO implements Serializable {
46 48 /**
47 49 * 手机号
48 50 */
49   - @TableField(typeHandler = JacksonTypeHandler.class)
50   - private List<String> phones;
  51 + private String phone;
51 52  
52 53 /**
53 54 * 内容
... ... @@ -67,16 +68,6 @@ public class SysSmsQueryLogDO implements Serializable {
67 68 private Integer gotResult;
68 69  
69 70 /**
70   - * 是否拥有回调函数(0否 1是)
71   - */
72   - private Integer hadCallback;
73   -
74   - /**
75   - * 结果(对象json字符串)
76   - */
77   - private String sendResultParam;
78   -
79   - /**
80 71 * 备注
81 72 */
82 73 private String remark;
... ... @@ -91,4 +82,9 @@ public class SysSmsQueryLogDO implements Serializable {
91 82 */
92 83 private Date createTime;
93 84  
  85 + /**
  86 + * 发送时间
  87 + */
  88 + private Date sendTime;
  89 +
94 90 }
... ...
src/main/java/cn/iocoder/dashboard/modules/system/dal/redis/RedisKeyConstants.java
... ... @@ -28,6 +28,6 @@ public interface RedisKeyConstants {
28 28 * key 的 format 的参数是 uuid
29 29 */
30 30 RedisKeyDefine CAPTCHA_CODE = new RedisKeyDefine("captcha_code:%s", STRING, String.class,
31   - RedisKeyDefine.TimeoutTypeEnum.DYNAMIC);
  31 + Duration.ofMinutes(30));
32 32  
33 33 }
... ...
src/main/java/cn/iocoder/dashboard/modules/system/mq/consumer/dept/SysDeptRefreshConsumer.java renamed to src/main/java/cn/iocoder/dashboard/modules/system/redis/mq/consumer/dept/SysDeptRefreshConsumer.java
1   -package cn.iocoder.dashboard.modules.system.mq.consumer.dept;
  1 +package cn.iocoder.dashboard.modules.system.redis.mq.consumer.dept;
2 2  
3 3 import cn.iocoder.dashboard.framework.redis.core.pubsub.AbstractChannelMessageListener;
4   -import cn.iocoder.dashboard.modules.system.mq.message.dept.SysDeptRefreshMessage;
  4 +import cn.iocoder.dashboard.modules.system.redis.mq.message.dept.SysDeptRefreshMessage;
5 5 import cn.iocoder.dashboard.modules.system.service.dept.SysDeptService;
6 6 import lombok.extern.slf4j.Slf4j;
7 7 import org.springframework.stereotype.Component;
... ...
src/main/java/cn/iocoder/dashboard/modules/system/mq/consumer/dict/SysDictDataRefreshConsumer.java renamed to src/main/java/cn/iocoder/dashboard/modules/system/redis/mq/consumer/dict/SysDictDataRefreshConsumer.java
1   -package cn.iocoder.dashboard.modules.system.mq.consumer.dict;
  1 +package cn.iocoder.dashboard.modules.system.redis.mq.consumer.dict;
2 2  
3 3 import cn.iocoder.dashboard.framework.redis.core.pubsub.AbstractChannelMessageListener;
4   -import cn.iocoder.dashboard.modules.system.mq.message.dict.SysDictDataRefreshMessage;
  4 +import cn.iocoder.dashboard.modules.system.redis.mq.message.dict.SysDictDataRefreshMessage;
5 5 import cn.iocoder.dashboard.modules.system.service.dict.SysDictDataService;
6 6 import lombok.extern.slf4j.Slf4j;
7 7 import org.springframework.stereotype.Component;
... ...
src/main/java/cn/iocoder/dashboard/modules/system/mq/consumer/permission/SysMenuRefreshConsumer.java renamed to src/main/java/cn/iocoder/dashboard/modules/system/redis/mq/consumer/permission/SysMenuRefreshConsumer.java
1   -package cn.iocoder.dashboard.modules.system.mq.consumer.permission;
  1 +package cn.iocoder.dashboard.modules.system.redis.mq.consumer.permission;
2 2  
3 3 import cn.iocoder.dashboard.framework.redis.core.pubsub.AbstractChannelMessageListener;
4   -import cn.iocoder.dashboard.modules.system.mq.message.permission.SysMenuRefreshMessage;
  4 +import cn.iocoder.dashboard.modules.system.redis.mq.message.permission.SysMenuRefreshMessage;
5 5 import cn.iocoder.dashboard.modules.system.service.permission.SysMenuService;
6 6 import lombok.extern.slf4j.Slf4j;
7 7 import org.springframework.stereotype.Component;
... ...
src/main/java/cn/iocoder/dashboard/modules/system/mq/consumer/permission/SysRoleMenuRefreshConsumer.java renamed to src/main/java/cn/iocoder/dashboard/modules/system/redis/mq/consumer/permission/SysRoleMenuRefreshConsumer.java
1   -package cn.iocoder.dashboard.modules.system.mq.consumer.permission;
  1 +package cn.iocoder.dashboard.modules.system.redis.mq.consumer.permission;
2 2  
3 3 import cn.iocoder.dashboard.framework.redis.core.pubsub.AbstractChannelMessageListener;
4   -import cn.iocoder.dashboard.modules.system.mq.message.permission.SysRoleMenuRefreshMessage;
  4 +import cn.iocoder.dashboard.modules.system.redis.mq.message.permission.SysRoleMenuRefreshMessage;
5 5 import cn.iocoder.dashboard.modules.system.service.permission.SysPermissionService;
6 6 import lombok.extern.slf4j.Slf4j;
7 7 import org.springframework.stereotype.Component;
... ...
src/main/java/cn/iocoder/dashboard/modules/system/mq/consumer/permission/SysRoleRefreshConsumer.java renamed to src/main/java/cn/iocoder/dashboard/modules/system/redis/mq/consumer/permission/SysRoleRefreshConsumer.java
1   -package cn.iocoder.dashboard.modules.system.mq.consumer.permission;
  1 +package cn.iocoder.dashboard.modules.system.redis.mq.consumer.permission;
2 2  
3 3 import cn.iocoder.dashboard.framework.redis.core.pubsub.AbstractChannelMessageListener;
4   -import cn.iocoder.dashboard.modules.system.mq.message.permission.SysRoleRefreshMessage;
  4 +import cn.iocoder.dashboard.modules.system.redis.mq.message.permission.SysRoleRefreshMessage;
5 5 import cn.iocoder.dashboard.modules.system.service.permission.SysRoleService;
6 6 import lombok.extern.slf4j.Slf4j;
7 7 import org.springframework.stereotype.Component;
... ...
src/main/java/cn/iocoder/dashboard/modules/system/redis/mq/consumer/sms/SmsSendConsumer.java 0 → 100644
  1 +//package cn.iocoder.dashboard.modules.system.redis.mq.consumer.sms;
  2 +//
  3 +//import cn.iocoder.dashboard.framework.redis.core.pubsub.AbstractChannelMessageListener;
  4 +//import cn.iocoder.dashboard.framework.sms.client.AbstractSmsClient;
  5 +//import cn.iocoder.dashboard.framework.sms.core.SmsResult;
  6 +//import cn.iocoder.dashboard.modules.system.redis.mq.message.dept.SysDeptRefreshMessage;
  7 +//import cn.iocoder.dashboard.modules.system.redis.stream.sms.SmsSendMessage;
  8 +//import cn.iocoder.dashboard.modules.system.service.sms.SysSmsChannelService;
  9 +//import cn.iocoder.dashboard.modules.system.service.sms.SysSmsQueryLogService;
  10 +//import lombok.extern.slf4j.Slf4j;
  11 +//import org.springframework.beans.factory.annotation.Autowired;
  12 +//import org.springframework.data.redis.connection.stream.Consumer;
  13 +//import org.springframework.data.redis.connection.stream.ObjectRecord;
  14 +//import org.springframework.data.redis.connection.stream.ReadOffset;
  15 +//import org.springframework.data.redis.connection.stream.StreamOffset;
  16 +//import org.springframework.data.redis.core.StringRedisTemplate;
  17 +//import org.springframework.stereotype.Component;
  18 +//
  19 +//import javax.annotation.Resource;
  20 +//
  21 +///**
  22 +// * 针对 {@link SysDeptRefreshMessage} 的消费者
  23 +// *
  24 +// * @author 芋道源码
  25 +// */
  26 +//@Component
  27 +//@Slf4j
  28 +//public class SmsSendConsumer extends AbstractChannelMessageListener<SmsSendMessage> {
  29 +//
  30 +// @Resource
  31 +// private SysSmsChannelService smsChannelService;
  32 +//
  33 +// @Resource
  34 +// private SysSmsQueryLogService smsQueryLogService;
  35 +//
  36 +// @Autowired
  37 +// StringRedisTemplate redisTemplate;
  38 +//
  39 +// @Override
  40 +// public void onMessage(SmsSendMessage message) {
  41 +//
  42 +// redisTemplate.opsForStream().add(ObjectRecord.create("String", message));
  43 +//
  44 +// redisTemplate.opsForStream().read(Consumer.from("",""), StreamOffset.create("", ReadOffset.lastConsumed()));
  45 +//
  46 +//
  47 +//
  48 +// log.info("[onMessage][收到 发送短信 消息], content: " + message.toString());
  49 +// AbstractSmsClient smsClient = smsChannelService.getSmsClient(message.getSmsBody().getTemplateCode());
  50 +// String templateApiId = smsChannelService.getSmsTemplateApiIdByCode(message.getSmsBody().getTemplateCode());
  51 +//
  52 +// SmsResult result = smsClient.send(templateApiId, message.getSmsBody(), message.getTargetPhone());
  53 +// smsQueryLogService.afterSendLog(message.getSmsBody().getSmsLogId(), result);
  54 +// }
  55 +//
  56 +//}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/mq/message/dept/SysDeptRefreshMessage.java renamed to src/main/java/cn/iocoder/dashboard/modules/system/redis/mq/message/dept/SysDeptRefreshMessage.java
1   -package cn.iocoder.dashboard.modules.system.mq.message.dept;
  1 +package cn.iocoder.dashboard.modules.system.redis.mq.message.dept;
2 2  
3 3 import cn.iocoder.dashboard.framework.redis.core.pubsub.ChannelMessage;
4 4 import lombok.Data;
... ...
src/main/java/cn/iocoder/dashboard/modules/system/mq/message/dict/SysDictDataRefreshMessage.java renamed to src/main/java/cn/iocoder/dashboard/modules/system/redis/mq/message/dict/SysDictDataRefreshMessage.java
1   -package cn.iocoder.dashboard.modules.system.mq.message.dict;
  1 +package cn.iocoder.dashboard.modules.system.redis.mq.message.dict;
2 2  
3 3 import cn.iocoder.dashboard.framework.redis.core.pubsub.ChannelMessage;
4 4 import lombok.Data;
... ...
src/main/java/cn/iocoder/dashboard/modules/system/mq/message/permission/SysMenuRefreshMessage.java renamed to src/main/java/cn/iocoder/dashboard/modules/system/redis/mq/message/permission/SysMenuRefreshMessage.java
1   -package cn.iocoder.dashboard.modules.system.mq.message.permission;
  1 +package cn.iocoder.dashboard.modules.system.redis.mq.message.permission;
2 2  
3 3 import cn.iocoder.dashboard.framework.redis.core.pubsub.ChannelMessage;
4 4 import lombok.Data;
... ...
src/main/java/cn/iocoder/dashboard/modules/system/mq/message/permission/SysRoleMenuRefreshMessage.java renamed to src/main/java/cn/iocoder/dashboard/modules/system/redis/mq/message/permission/SysRoleMenuRefreshMessage.java
1   -package cn.iocoder.dashboard.modules.system.mq.message.permission;
  1 +package cn.iocoder.dashboard.modules.system.redis.mq.message.permission;
2 2  
3 3 import cn.iocoder.dashboard.framework.redis.core.pubsub.ChannelMessage;
4 4 import lombok.Data;
... ...
src/main/java/cn/iocoder/dashboard/modules/system/mq/message/permission/SysRoleRefreshMessage.java renamed to src/main/java/cn/iocoder/dashboard/modules/system/redis/mq/message/permission/SysRoleRefreshMessage.java
1   -package cn.iocoder.dashboard.modules.system.mq.message.permission;
  1 +package cn.iocoder.dashboard.modules.system.redis.mq.message.permission;
2 2  
3 3 import cn.iocoder.dashboard.framework.redis.core.pubsub.ChannelMessage;
4 4 import lombok.Data;
... ...
src/main/java/cn/iocoder/dashboard/modules/system/mq/producer/dept/SysDeptProducer.java renamed to src/main/java/cn/iocoder/dashboard/modules/system/redis/mq/producer/dept/SysDeptProducer.java
1   -package cn.iocoder.dashboard.modules.system.mq.producer.dept;
  1 +package cn.iocoder.dashboard.modules.system.redis.mq.producer.dept;
2 2  
3 3 import cn.iocoder.dashboard.framework.redis.core.util.RedisMessageUtils;
4   -import cn.iocoder.dashboard.modules.system.mq.message.dept.SysDeptRefreshMessage;
  4 +import cn.iocoder.dashboard.modules.system.redis.mq.message.dept.SysDeptRefreshMessage;
5 5 import org.springframework.data.redis.core.StringRedisTemplate;
6 6 import org.springframework.stereotype.Component;
7 7  
... ...
src/main/java/cn/iocoder/dashboard/modules/system/mq/producer/dict/SysDictDataProducer.java renamed to src/main/java/cn/iocoder/dashboard/modules/system/redis/mq/producer/dict/SysDictDataProducer.java
1   -package cn.iocoder.dashboard.modules.system.mq.producer.dict;
  1 +package cn.iocoder.dashboard.modules.system.redis.mq.producer.dict;
2 2  
3 3 import cn.iocoder.dashboard.framework.redis.core.util.RedisMessageUtils;
4   -import cn.iocoder.dashboard.modules.system.mq.message.dict.SysDictDataRefreshMessage;
  4 +import cn.iocoder.dashboard.modules.system.redis.mq.message.dict.SysDictDataRefreshMessage;
5 5 import org.springframework.data.redis.core.StringRedisTemplate;
6 6 import org.springframework.stereotype.Component;
7 7  
... ...
src/main/java/cn/iocoder/dashboard/modules/system/mq/producer/permission/SysMenuProducer.java renamed to src/main/java/cn/iocoder/dashboard/modules/system/redis/mq/producer/permission/SysMenuProducer.java
1   -package cn.iocoder.dashboard.modules.system.mq.producer.permission;
  1 +package cn.iocoder.dashboard.modules.system.redis.mq.producer.permission;
2 2  
3 3 import cn.iocoder.dashboard.framework.redis.core.util.RedisMessageUtils;
4   -import cn.iocoder.dashboard.modules.system.mq.message.permission.SysMenuRefreshMessage;
  4 +import cn.iocoder.dashboard.modules.system.redis.mq.message.permission.SysMenuRefreshMessage;
5 5 import org.springframework.data.redis.core.StringRedisTemplate;
6 6 import org.springframework.stereotype.Component;
7 7  
... ...
src/main/java/cn/iocoder/dashboard/modules/system/mq/producer/permission/SysPermissionProducer.java renamed to src/main/java/cn/iocoder/dashboard/modules/system/redis/mq/producer/permission/SysPermissionProducer.java
1   -package cn.iocoder.dashboard.modules.system.mq.producer.permission;
  1 +package cn.iocoder.dashboard.modules.system.redis.mq.producer.permission;
2 2  
3 3 import cn.iocoder.dashboard.framework.redis.core.util.RedisMessageUtils;
4   -import cn.iocoder.dashboard.modules.system.mq.message.permission.SysRoleMenuRefreshMessage;
  4 +import cn.iocoder.dashboard.modules.system.redis.mq.message.permission.SysRoleMenuRefreshMessage;
5 5 import org.springframework.data.redis.core.StringRedisTemplate;
6 6 import org.springframework.stereotype.Component;
7 7  
... ...
src/main/java/cn/iocoder/dashboard/modules/system/mq/producer/permission/SysRoleProducer.java renamed to src/main/java/cn/iocoder/dashboard/modules/system/redis/mq/producer/permission/SysRoleProducer.java
1   -package cn.iocoder.dashboard.modules.system.mq.producer.permission;
  1 +package cn.iocoder.dashboard.modules.system.redis.mq.producer.permission;
2 2  
3 3 import cn.iocoder.dashboard.framework.redis.core.util.RedisMessageUtils;
4   -import cn.iocoder.dashboard.modules.system.mq.message.permission.SysRoleRefreshMessage;
  4 +import cn.iocoder.dashboard.modules.system.redis.mq.message.permission.SysRoleRefreshMessage;
5 5 import org.springframework.data.redis.core.StringRedisTemplate;
6 6 import org.springframework.stereotype.Component;
7 7  
... ...
src/main/java/cn/iocoder/dashboard/modules/system/redis/mq/producer/sms/SmsProducer.java 0 → 100644
  1 +//package cn.iocoder.dashboard.modules.system.redis.mq.producer.sms;
  2 +//
  3 +//import cn.iocoder.dashboard.framework.redis.core.util.RedisMessageUtils;
  4 +//import cn.iocoder.dashboard.framework.sms.core.SmsBody;
  5 +//import cn.iocoder.dashboard.modules.system.redis.stream.sms.SmsSendMessage;
  6 +//import org.springframework.data.redis.core.StringRedisTemplate;
  7 +//import org.springframework.stereotype.Component;
  8 +//
  9 +//import javax.annotation.Resource;
  10 +//
  11 +///**
  12 +// * 短信的 Producer
  13 +// */
  14 +//@Component
  15 +//public class SmsProducer {
  16 +//
  17 +// @Resource
  18 +// private StringRedisTemplate stringRedisTemplate;
  19 +//
  20 +// /**
  21 +// * 发送 {@link SmsSendMessage} 消息
  22 +// */
  23 +// public void sendSmsSendMessage(SmsBody smsBody, String targetPhone) {
  24 +// SmsSendMessage message = new SmsSendMessage();
  25 +// message.setSmsBody(smsBody);
  26 +// message.setTargetPhone(targetPhone);
  27 +// // TODO FROM 芋艿 TO ZZF:这块等未来改哈。这个方法目前是广播消费,会导致每个节点都发送一次。等后续封装出 redis stream 消息
  28 +// RedisMessageUtils.sendChannelMessage(stringRedisTemplate, message);
  29 +// }
  30 +//
  31 +//}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/redis/stream/StreamConsumerRunner.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.redis.stream;
  2 +
  3 +import cn.iocoder.dashboard.framework.redis.core.util.RedisStreamUtils;
  4 +import cn.iocoder.dashboard.modules.system.redis.stream.sms.SmsSendMessage;
  5 +import cn.iocoder.dashboard.modules.system.redis.stream.sms.SmsSendStreamConsumer;
  6 +import lombok.extern.slf4j.Slf4j;
  7 +import org.springframework.beans.factory.DisposableBean;
  8 +import org.springframework.boot.ApplicationArguments;
  9 +import org.springframework.boot.ApplicationRunner;
  10 +import org.springframework.data.redis.connection.RedisConnectionFactory;
  11 +import org.springframework.data.redis.connection.stream.*;
  12 +import org.springframework.data.redis.core.StringRedisTemplate;
  13 +import org.springframework.data.redis.serializer.StringRedisSerializer;
  14 +import org.springframework.data.redis.stream.StreamMessageListenerContainer;
  15 +import org.springframework.data.redis.stream.StreamMessageListenerContainer.StreamMessageListenerContainerOptions;
  16 +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
  17 +import org.springframework.stereotype.Component;
  18 +import org.springframework.util.ErrorHandler;
  19 +
  20 +import javax.annotation.Resource;
  21 +import java.net.InetAddress;
  22 +import java.net.UnknownHostException;
  23 +import java.time.Duration;
  24 +
  25 +
  26 +@Slf4j
  27 +@Component
  28 +public class StreamConsumerRunner implements ApplicationRunner, DisposableBean {
  29 +
  30 + @Resource
  31 + RedisConnectionFactory redisConnectionFactory;
  32 +
  33 + @Resource
  34 + ThreadPoolTaskExecutor threadPoolTaskExecutor;
  35 +
  36 + @Resource
  37 + SmsSendStreamConsumer streamMessageListener;
  38 +
  39 + @Resource
  40 + StringRedisTemplate stringRedisTemplate;
  41 +
  42 + private StreamMessageListenerContainer<String, ObjectRecord<String, SmsSendMessage>> streamMessageListenerContainer;
  43 +
  44 + @Override
  45 + public void run(ApplicationArguments args) throws UnknownHostException {
  46 +
  47 + StreamInfo.XInfoGroups groups = stringRedisTemplate.opsForStream().groups(RedisStreamUtils.KEY_SMS_SEND);
  48 + if (groups.isEmpty()) {
  49 + stringRedisTemplate.opsForStream().createGroup(RedisStreamUtils.KEY_SMS_SEND, RedisStreamUtils.GROUP_SMS_SEND);
  50 + }
  51 +
  52 +
  53 + // 创建配置对象
  54 + StreamMessageListenerContainerOptions<String, ObjectRecord<String, SmsSendMessage>> streamMessageListenerContainerOptions = StreamMessageListenerContainerOptions
  55 + .builder()
  56 + // 一次性最多拉取多少条消息
  57 + .batchSize(10)
  58 + // 执行消息轮询的执行器
  59 + .executor(this.threadPoolTaskExecutor)
  60 + // 消息消费异常的handler
  61 + .errorHandler(new ErrorHandler() {
  62 + @Override
  63 + public void handleError(Throwable t) {
  64 + // throw new RuntimeException(t);
  65 + t.printStackTrace();
  66 + }
  67 + })
  68 + // 超时时间,设置为0,表示不超时(超时后会抛出异常)
  69 + .pollTimeout(Duration.ZERO)
  70 + // 序列化器
  71 + .serializer(new StringRedisSerializer())
  72 + .targetType(SmsSendMessage.class)
  73 + .build();
  74 +
  75 + // 根据配置对象创建监听容器对象
  76 + StreamMessageListenerContainer<String, ObjectRecord<String, SmsSendMessage>> streamMessageListenerContainer = StreamMessageListenerContainer
  77 + .create(this.redisConnectionFactory, streamMessageListenerContainerOptions);
  78 +
  79 + // 使用监听容器对象开始监听消费(使用的是手动确认方式)
  80 + streamMessageListenerContainer.receive(Consumer.from(RedisStreamUtils.GROUP_SMS_SEND, InetAddress.getLocalHost().getHostName()),
  81 + StreamOffset.create(RedisStreamUtils.KEY_SMS_SEND, ReadOffset.lastConsumed()), this.streamMessageListener);
  82 +
  83 + this.streamMessageListenerContainer = streamMessageListenerContainer;
  84 + // 启动监听
  85 + this.streamMessageListenerContainer.start();
  86 +
  87 + }
  88 +
  89 + @Override
  90 + public void destroy() throws Exception {
  91 + this.streamMessageListenerContainer.stop();
  92 + }
  93 +}
0 94 \ No newline at end of file
... ...
src/main/java/cn/iocoder/dashboard/modules/system/mq/message/sms/SmsSendMessage.java renamed to src/main/java/cn/iocoder/dashboard/modules/system/redis/stream/sms/SmsSendMessage.java
1   -package cn.iocoder.dashboard.modules.system.mq.message.sms;
  1 +package cn.iocoder.dashboard.modules.system.redis.stream.sms;
2 2  
3   -import cn.iocoder.dashboard.framework.redis.core.pubsub.ChannelMessage;
4 3 import cn.iocoder.dashboard.framework.sms.core.SmsBody;
5 4 import lombok.Data;
6 5  
7   -import java.util.Collection;
8   -import java.util.List;
9   -
10 6 /**
11 7 * 部门数据刷新 Message
12 8 */
13 9 @Data
14   -public class SmsSendMessage implements ChannelMessage {
  10 +public class SmsSendMessage {
15 11  
16 12 private SmsBody smsBody;
17 13  
18   - private List<String> targetPhones;
19   -
20   - @Override
21   - public String getChannel() {
22   - return "sms.send";
23   - }
  14 + private String targetPhone;
24 15  
25 16 }
... ...
src/main/java/cn/iocoder/dashboard/modules/system/mq/consumer/sms/SmsSendConsumer.java renamed to src/main/java/cn/iocoder/dashboard/modules/system/redis/stream/sms/SmsSendStreamConsumer.java
1   -package cn.iocoder.dashboard.modules.system.mq.consumer.sms;
  1 +package cn.iocoder.dashboard.modules.system.redis.stream.sms;
2 2  
3   -import cn.iocoder.dashboard.framework.redis.core.pubsub.AbstractChannelMessageListener;
4 3 import cn.iocoder.dashboard.framework.sms.client.AbstractSmsClient;
  4 +import cn.iocoder.dashboard.framework.sms.core.SmsBody;
5 5 import cn.iocoder.dashboard.framework.sms.core.SmsResult;
6   -import cn.iocoder.dashboard.modules.system.mq.message.dept.SysDeptRefreshMessage;
7   -import cn.iocoder.dashboard.modules.system.mq.message.sms.SmsSendMessage;
8 6 import cn.iocoder.dashboard.modules.system.service.sms.SysSmsChannelService;
9 7 import cn.iocoder.dashboard.modules.system.service.sms.SysSmsQueryLogService;
  8 +import cn.iocoder.dashboard.util.json.JsonUtils;
10 9 import lombok.extern.slf4j.Slf4j;
  10 +import org.springframework.data.redis.connection.stream.ObjectRecord;
  11 +import org.springframework.data.redis.stream.StreamListener;
11 12 import org.springframework.stereotype.Component;
12 13  
13 14 import javax.annotation.Resource;
14 15  
15 16 /**
16   - * 针对 {@link SysDeptRefreshMessage} 的消费者
  17 + * 短信发送流消息监听器
17 18 *
18   - * @author 芋道源码
  19 + * @author zzf
  20 + * @date 2021/3/9 16:35
19 21 */
20   -@Component
21 22 @Slf4j
22   -public class SmsSendConsumer extends AbstractChannelMessageListener<SmsSendMessage> {
  23 +@Component
  24 +public class SmsSendStreamConsumer implements StreamListener<String, ObjectRecord<String, SmsSendMessage>> {
23 25  
24 26 @Resource
25 27 private SysSmsChannelService smsChannelService;
... ... @@ -28,14 +30,14 @@ public class SmsSendConsumer extends AbstractChannelMessageListener&lt;SmsSendMessa
28 30 private SysSmsQueryLogService smsQueryLogService;
29 31  
30 32 @Override
31   - public void onMessage(SmsSendMessage message) {
32   - log.info("[onMessage][收到 发送短信 消息], content: " + message.toString());
33   - AbstractSmsClient smsClient = smsChannelService.getSmsClient(message.getSmsBody().getTemplateCode());
34   - String templateApiId = smsChannelService.getSmsTemplateApiIdByCode(message.getSmsBody().getTemplateCode());
35   -
36   - SmsResult result = smsClient.send(templateApiId, message.getSmsBody(), message.getTargetPhones());
37   -
38   - smsQueryLogService.afterSendLog(message.getSmsBody().getSmsLogId(), result);
  33 + public void onMessage(ObjectRecord<String, SmsSendMessage> record) {
  34 + SmsSendMessage message = record.getValue();
  35 + SmsBody body = message.getSmsBody();
  36 + log.info("[onMessage][收到 发送短信 消息], content: " + JsonUtils.toJsonString(body));
  37 + AbstractSmsClient smsClient = smsChannelService.getSmsClient(body.getTemplateCode());
  38 + String templateApiId = smsChannelService.getSmsTemplateApiIdByCode(body.getTemplateCode());
  39 +
  40 + SmsResult result = smsClient.send(templateApiId, body, message.getTargetPhone());
  41 + smsQueryLogService.afterSendLog(body.getSmsLogId(), result);
39 42 }
40   -
41 43 }
... ...
src/main/java/cn/iocoder/dashboard/modules/system/mq/producer/sms/SmsProducer.java renamed to src/main/java/cn/iocoder/dashboard/modules/system/redis/stream/sms/SmsSendStreamProducer.java
1   -package cn.iocoder.dashboard.modules.system.mq.producer.sms;
  1 +package cn.iocoder.dashboard.modules.system.redis.stream.sms;
2 2  
3   -import cn.iocoder.dashboard.framework.redis.core.util.RedisMessageUtils;
  3 +import cn.iocoder.dashboard.framework.redis.core.util.RedisStreamUtils;
4 4 import cn.iocoder.dashboard.framework.sms.core.SmsBody;
5   -import cn.iocoder.dashboard.modules.system.mq.message.sms.SmsSendMessage;
  5 +import lombok.extern.slf4j.Slf4j;
6 6 import org.springframework.data.redis.core.StringRedisTemplate;
7 7 import org.springframework.stereotype.Component;
8 8  
9 9 import javax.annotation.Resource;
10   -import java.util.List;
11 10  
12 11 /**
13   - * 短信的 Producer
  12 + * 短信发送流消息监听器
  13 + *
  14 + * @author zzf
  15 + * @date 2021/3/9 16:35
14 16 */
  17 +@Slf4j
15 18 @Component
16   -public class SmsProducer {
  19 +public class SmsSendStreamProducer {
17 20  
18 21 @Resource
19 22 private StringRedisTemplate stringRedisTemplate;
... ... @@ -21,12 +24,12 @@ public class SmsProducer {
21 24 /**
22 25 * 发送 {@link SmsSendMessage} 消息
23 26 */
24   - public void sendSmsSendMessage(SmsBody smsBody, List<String> targetPhoneList) {
  27 + public void sendSmsSendMessage(SmsBody smsBody, String targetPhone) {
25 28 SmsSendMessage message = new SmsSendMessage();
26 29 message.setSmsBody(smsBody);
27   - message.setTargetPhones(targetPhoneList);
28   - // TODO FROM 芋艿 TO ZZF:这块等未来改哈。这个方法目前是广播消费,会导致每个节点都发送一次。等后续封装出 redis stream 消息
29   - RedisMessageUtils.sendChannelMessage(stringRedisTemplate, message);
  30 + message.setTargetPhone(targetPhone);
  31 +
  32 + RedisStreamUtils.sendChannelMessage(stringRedisTemplate, message);
30 33 }
31 34  
32 35 -}
  36 +}
33 37 \ No newline at end of file
... ...
src/main/java/cn/iocoder/dashboard/modules/system/service/dept/impl/SysDeptServiceImpl.java
... ... @@ -11,7 +11,7 @@ import cn.iocoder.dashboard.modules.system.convert.dept.SysDeptConvert;
11 11 import cn.iocoder.dashboard.modules.system.dal.mysql.dao.dept.SysDeptMapper;
12 12 import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.dept.SysDeptDO;
13 13 import cn.iocoder.dashboard.modules.system.enums.dept.DeptIdEnum;
14   -import cn.iocoder.dashboard.modules.system.mq.producer.dept.SysDeptProducer;
  14 +import cn.iocoder.dashboard.modules.system.redis.mq.producer.dept.SysDeptProducer;
15 15 import cn.iocoder.dashboard.modules.system.service.dept.SysDeptService;
16 16 import com.google.common.collect.ImmutableMap;
17 17 import com.google.common.collect.ImmutableMultimap;
... ...
src/main/java/cn/iocoder/dashboard/modules/system/service/dict/impl/SysDictDataServiceImpl.java
... ... @@ -13,7 +13,7 @@ import cn.iocoder.dashboard.modules.system.convert.dict.SysDictDataConvert;
13 13 import cn.iocoder.dashboard.modules.system.dal.mysql.dao.dict.SysDictDataMapper;
14 14 import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.dict.SysDictDataDO;
15 15 import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.dict.SysDictTypeDO;
16   -import cn.iocoder.dashboard.modules.system.mq.producer.dict.SysDictDataProducer;
  16 +import cn.iocoder.dashboard.modules.system.redis.mq.producer.dict.SysDictDataProducer;
17 17 import cn.iocoder.dashboard.modules.system.service.dict.SysDictDataService;
18 18 import cn.iocoder.dashboard.modules.system.service.dict.SysDictTypeService;
19 19 import com.google.common.collect.ImmutableTable;
... ...
src/main/java/cn/iocoder/dashboard/modules/system/service/permission/impl/SysMenuServiceImpl.java
... ... @@ -11,7 +11,7 @@ import cn.iocoder.dashboard.modules.system.dal.mysql.dao.permission.SysMenuMappe
11 11 import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.permission.SysMenuDO;
12 12 import cn.iocoder.dashboard.modules.system.enums.permission.MenuIdEnum;
13 13 import cn.iocoder.dashboard.modules.system.enums.permission.MenuTypeEnum;
14   -import cn.iocoder.dashboard.modules.system.mq.producer.permission.SysMenuProducer;
  14 +import cn.iocoder.dashboard.modules.system.redis.mq.producer.permission.SysMenuProducer;
15 15 import cn.iocoder.dashboard.modules.system.service.permission.SysMenuService;
16 16 import cn.iocoder.dashboard.modules.system.service.permission.SysPermissionService;
17 17 import cn.iocoder.dashboard.util.collection.CollectionUtils;
... ...
src/main/java/cn/iocoder/dashboard/modules/system/service/permission/impl/SysPermissionServiceImpl.java
... ... @@ -11,7 +11,7 @@ import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.permission.SysMe
11 11 import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.permission.SysRoleDO;
12 12 import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.permission.SysRoleMenuDO;
13 13 import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.permission.SysUserRoleDO;
14   -import cn.iocoder.dashboard.modules.system.mq.producer.permission.SysPermissionProducer;
  14 +import cn.iocoder.dashboard.modules.system.redis.mq.producer.permission.SysPermissionProducer;
15 15 import cn.iocoder.dashboard.modules.system.service.permission.SysMenuService;
16 16 import cn.iocoder.dashboard.modules.system.service.permission.SysPermissionService;
17 17 import cn.iocoder.dashboard.modules.system.service.permission.SysRoleService;
... ...
src/main/java/cn/iocoder/dashboard/modules/system/service/permission/impl/SysRoleServiceImpl.java
... ... @@ -15,7 +15,7 @@ import cn.iocoder.dashboard.modules.system.dal.mysql.dao.permission.SysRoleMappe
15 15 import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.permission.SysRoleDO;
16 16 import cn.iocoder.dashboard.modules.system.enums.permission.RoleCodeEnum;
17 17 import cn.iocoder.dashboard.modules.system.enums.permission.SysRoleTypeEnum;
18   -import cn.iocoder.dashboard.modules.system.mq.producer.permission.SysRoleProducer;
  18 +import cn.iocoder.dashboard.modules.system.redis.mq.producer.permission.SysRoleProducer;
19 19 import cn.iocoder.dashboard.modules.system.service.permission.SysPermissionService;
20 20 import cn.iocoder.dashboard.modules.system.service.permission.SysRoleService;
21 21 import com.google.common.collect.ImmutableMap;
... ...
src/main/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsChannelService.java
... ... @@ -2,12 +2,14 @@ package cn.iocoder.dashboard.modules.system.service.sms;
2 2  
3 3 import cn.iocoder.dashboard.common.pojo.PageResult;
4 4 import cn.iocoder.dashboard.framework.sms.client.AbstractSmsClient;
  5 +import cn.iocoder.dashboard.framework.sms.core.SmsResultDetail;
5 6 import cn.iocoder.dashboard.modules.system.controller.sms.vo.SmsChannelAllVO;
6 7 import cn.iocoder.dashboard.modules.system.controller.sms.vo.req.SmsChannelCreateReqVO;
7 8 import cn.iocoder.dashboard.modules.system.controller.sms.vo.req.SmsChannelPageReqVO;
8 9 import cn.iocoder.dashboard.modules.system.controller.sms.vo.resp.SmsChannelEnumRespVO;
9 10 import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.sms.SysSmsChannelDO;
10 11  
  12 +import javax.servlet.ServletRequest;
11 13 import java.util.List;
12 14  
13 15 /**
... ...
src/main/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsQueryLogService.java
... ... @@ -3,6 +3,7 @@ package cn.iocoder.dashboard.modules.system.service.sms;
3 3 import cn.iocoder.dashboard.framework.sms.client.AbstractSmsClient;
4 4 import cn.iocoder.dashboard.framework.sms.core.SmsBody;
5 5 import cn.iocoder.dashboard.framework.sms.core.SmsResult;
  6 +import cn.iocoder.dashboard.framework.sms.core.SmsResultDetail;
6 7  
7 8 import java.util.List;
8 9  
... ... @@ -16,16 +17,16 @@ public interface SysSmsQueryLogService {
16 17 /**
17 18 * 发送短信前的日志处理
18 19 *
19   - * @param smsBody 短信内容
20   - * @param targetPhones 发送对象手机号集合
21   - * @param client 短信客户端
  20 + * @param smsBody 短信内容
  21 + * @param targetPhone 发送对象手机号
  22 + * @param client 短信客户端
22 23 * @return 生成的日志id
23 24 */
24 25 // TODO FROM 芋艿 to ZZF: async 是针对发送的方式,对于日志不一定需要关心。这样,短信日志,实际就发送前插入,发送后更新结果.
25 26 // 这里只用于记录状态,毕竟异步可能推送失败,此时日志可记录该状态。
26 27  
27 28 // TODO FROM 芋艿 to ZZF:短信日志,群发的情况,应该是每个手机一条哈。虽然是群发,但是可能部分成功,部分失败;对应到短信平台,实际也是多条。
28   - void beforeSendLog(SmsBody smsBody, List<String> targetPhones, AbstractSmsClient client);
  29 + void beforeSendLog(SmsBody smsBody, String targetPhone, AbstractSmsClient client);
29 30  
30 31 /**
31 32 * 发送消息后的日志处理
... ... @@ -35,4 +36,5 @@ public interface SysSmsQueryLogService {
35 36 */
36 37 void afterSendLog(Long logId, SmsResult result);
37 38  
  39 + void updateSendLogByResultDetail(SmsResultDetail smsResultDetail);
38 40 }
... ...
src/main/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsService.java
1 1 package cn.iocoder.dashboard.modules.system.service.sms;
2 2  
  3 +import cn.hutool.core.collection.CollectionUtil;
  4 +import cn.hutool.core.util.ArrayUtil;
3 5 import cn.iocoder.dashboard.framework.sms.core.SmsBody;
  6 +import cn.iocoder.dashboard.framework.sms.core.enums.SmsChannelEnum;
4 7  
5 8 import javax.servlet.ServletRequest;
6 9 import java.util.Arrays;
7   -import java.util.Collections;
8 10 import java.util.List;
9 11  
10 12 /**
... ... @@ -22,7 +24,12 @@ public interface SysSmsService {
22 24 * @param smsBody 消息内容
23 25 * @param targetPhones 发送对象手机号列表
24 26 */
25   - void send(SmsBody smsBody, List<String> targetPhones);
  27 + default void send(SmsBody smsBody, List<String> targetPhones) {
  28 + if (CollectionUtil.isEmpty(targetPhones)) {
  29 + return;
  30 + }
  31 + targetPhones.forEach(s -> this.send(smsBody, s));
  32 + }
26 33  
27 34 /**
28 35 * 发送消息
... ... @@ -30,9 +37,7 @@ public interface SysSmsService {
30 37 * @param smsBody 消息内容
31 38 * @param targetPhone 发送对象手机号
32 39 */
33   - default void send(SmsBody smsBody, String targetPhone) {
34   - send(smsBody, Collections.singletonList(targetPhone));
35   - }
  40 + void send(SmsBody smsBody, String targetPhone);
36 41  
37 42 /**
38 43 * 发送消息
... ... @@ -41,14 +46,18 @@ public interface SysSmsService {
41 46 * @param targetPhones 发送对象手机号数组
42 47 */
43 48 default void send(SmsBody smsBody, String... targetPhones) {
  49 + if (ArrayUtil.isEmpty(targetPhones)) {
  50 + return;
  51 + }
44 52 send(smsBody, Arrays.asList(targetPhones));
45 53 }
46 54  
47 55 /**
48 56 * 处理短信发送回调函数
49 57 *
50   - * @param request 请求
  58 + * @param request 请求
51 59 * @return 响应数据
52 60 */
53 61 Object smsSendCallbackHandle(ServletRequest request);
  62 +
54 63 }
... ...
src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SysSmsQueryLogServiceImpl.java
... ... @@ -3,16 +3,15 @@ package cn.iocoder.dashboard.modules.system.service.sms.impl;
3 3 import cn.iocoder.dashboard.framework.sms.client.AbstractSmsClient;
4 4 import cn.iocoder.dashboard.framework.sms.core.SmsBody;
5 5 import cn.iocoder.dashboard.framework.sms.core.SmsResult;
  6 +import cn.iocoder.dashboard.framework.sms.core.SmsResultDetail;
6 7 import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperty;
7 8 import cn.iocoder.dashboard.modules.system.dal.mysql.dao.sms.SysSmsQueryLogMapper;
8 9 import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.sms.SysSmsQueryLogDO;
9 10 import cn.iocoder.dashboard.modules.system.enums.sms.SmsSendStatusEnum;
10 11 import cn.iocoder.dashboard.modules.system.service.sms.SysSmsQueryLogService;
11   -import cn.iocoder.dashboard.util.json.JsonUtils;
12 12 import org.springframework.stereotype.Service;
13 13  
14 14 import javax.annotation.Resource;
15   -import java.util.List;
16 15  
17 16 /**
18 17 * 短信请求日志服务实现类
... ... @@ -27,14 +26,14 @@ public class SysSmsQueryLogServiceImpl implements SysSmsQueryLogService {
27 26 private SysSmsQueryLogMapper logMapper;
28 27  
29 28 @Override
30   - public void beforeSendLog(SmsBody smsBody, List<String> targetPhones, AbstractSmsClient client) {
  29 + public void beforeSendLog(SmsBody smsBody, String targetPhone, AbstractSmsClient client) {
31 30 SysSmsQueryLogDO smsLog = new SysSmsQueryLogDO();
32 31 SmsChannelProperty property = client.getProperty();
33 32  
34 33 smsLog.setChannelCode(property.getCode())
35 34 .setChannelId(property.getId())
36 35 .setTemplateCode(smsBody.getTemplateCode())
37   - .setPhones(targetPhones)
  36 + .setPhone(targetPhone)
38 37 .setContent(smsBody.getParams().toString());
39 38  
40 39 smsLog.setSendStatus(SmsSendStatusEnum.ASYNC.getStatus());
... ... @@ -46,14 +45,19 @@ public class SysSmsQueryLogServiceImpl implements SysSmsQueryLogService {
46 45 public void afterSendLog(Long logId, SmsResult result) {
47 46 SysSmsQueryLogDO smsLog = new SysSmsQueryLogDO();
48 47 smsLog.setId(logId);
49   - if (result.getSuccess()) {
50   - smsLog.setSendStatus(SmsSendStatusEnum.QUERY_SUCCESS.getStatus());
51   - smsLog.setSendResultParam(result.getSendResultParam());
52   - } else {
53   - smsLog.setSendStatus(SmsSendStatusEnum.QUERY_FAIL.getStatus());
54   - smsLog.setRemark(result.getMessage());
55   - }
  48 + smsLog.setApiId(result.getApiId());
  49 + smsLog.setSendStatus(SmsSendStatusEnum.QUERY_FAIL.getStatus());
  50 + smsLog.setRemark(result.getCode() + ": " + result.getMessage());
56 51 logMapper.updateById(smsLog);
57 52 }
58 53  
  54 + @Override
  55 + public void updateSendLogByResultDetail(SmsResultDetail smsResultDetail) {
  56 + SysSmsQueryLogDO queryLogDO = new SysSmsQueryLogDO();
  57 + queryLogDO.setSendStatus(smsResultDetail.getSendStatus());
  58 + queryLogDO.setSendTime(smsResultDetail.getSendTime());
  59 + queryLogDO.setRemark(smsResultDetail.getMessage());
  60 + logMapper.updateByApiId(queryLogDO, smsResultDetail.getApiId());
  61 + }
  62 +
59 63 }
... ...
src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SysSmsSendLogServiceImpl.java
... ... @@ -2,7 +2,6 @@ package cn.iocoder.dashboard.modules.system.service.sms.impl;
2 2  
3 3 import cn.hutool.core.collection.CollectionUtil;
4 4 import cn.iocoder.dashboard.framework.sms.client.AbstractSmsClient;
5   -import cn.iocoder.dashboard.framework.sms.client.NeedQuerySendResultSmsClient;
6 5 import cn.iocoder.dashboard.framework.sms.core.SmsResultDetail;
7 6 import cn.iocoder.dashboard.modules.system.dal.mysql.dao.sms.SysSmsQueryLogMapper;
8 7 import cn.iocoder.dashboard.modules.system.dal.mysql.dao.sms.SysSmsSendLogMapper;
... ... @@ -62,7 +61,7 @@ public class SysSmsSendLogServiceImpl implements SysSmsSendLogService {
62 61 updateQueryLog.setId(queryLog.getId());
63 62  
64 63 // 只处理实现了获取发送结果方法的短信客户端,理论上这里都是满足条件的,以防万一加个判断。
65   - if (smsClient instanceof NeedQuerySendResultSmsClient) {
  64 + /*if (smsClient instanceof NeedQuerySendResultSmsClient) {
66 65 //初始化点字段值
67 66 queryLog2SendLong(insertSendLog, queryLog);
68 67  
... ... @@ -89,7 +88,7 @@ public class SysSmsSendLogServiceImpl implements SysSmsSendLogService {
89 88 //理论上这里都是满足条件的,以防万一加个判断。
90 89 updateQueryLog.setSendStatus(SmsSendStatusEnum.QUERY_SEND_FAIL.getStatus());
91 90 smsQueryLogMapper.updateById(updateQueryLog);
92   - }
  91 + }*/
93 92 updateQueryLog.setSendStatus(SmsSendStatusEnum.SEND_SUCCESS.getStatus());
94 93 updateQueryLog.setRemark(String.format("日志(id = %s)对应的客户端没有继承NeedQuerySendResultSmsClient, 不能获取短信结果。", queryLog.getId()));
95 94 smsQueryLogMapper.updateById(updateQueryLog);
... ...
src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SysSmsServiceImpl.java
... ... @@ -2,15 +2,16 @@ package cn.iocoder.dashboard.modules.system.service.sms.impl;
2 2  
3 3 import cn.iocoder.dashboard.framework.sms.client.AbstractSmsClient;
4 4 import cn.iocoder.dashboard.framework.sms.core.SmsBody;
5   -import cn.iocoder.dashboard.modules.system.mq.producer.sms.SmsProducer;
  5 +import cn.iocoder.dashboard.framework.sms.core.SmsClientFactory;
  6 +import cn.iocoder.dashboard.framework.sms.core.SmsResultDetail;
  7 +import cn.iocoder.dashboard.modules.system.redis.stream.sms.SmsSendStreamProducer;
6 8 import cn.iocoder.dashboard.modules.system.service.sms.SysSmsChannelService;
7 9 import cn.iocoder.dashboard.modules.system.service.sms.SysSmsQueryLogService;
8 10 import cn.iocoder.dashboard.modules.system.service.sms.SysSmsService;
9   -import org.springframework.scheduling.annotation.Scheduled;
10 11 import org.springframework.stereotype.Service;
11 12  
12 13 import javax.annotation.Resource;
13   -import java.util.List;
  14 +import javax.servlet.ServletRequest;
14 15  
15 16 /**
16 17 * 短信日志Service实现类
... ... @@ -28,15 +29,23 @@ public class SysSmsServiceImpl implements SysSmsService {
28 29 private SysSmsQueryLogService logService;
29 30  
30 31 @Resource
31   - private SmsProducer smsProducer;
  32 + private SmsSendStreamProducer smsProducer;
  33 +
  34 + @Resource
  35 + private SmsClientFactory smsClientFactory;
32 36  
33 37 @Override
34   - public void send(SmsBody smsBody, List<String> targetPhones) {
  38 + public void send(SmsBody smsBody, String targetPhone) {
35 39 AbstractSmsClient client = channelService.getSmsClient(smsBody.getTemplateCode());
36   - logService.beforeSendLog(smsBody, targetPhones, client);
37   - smsProducer.sendSmsSendMessage(smsBody, targetPhones);
  40 + logService.beforeSendLog(smsBody, targetPhone, client);
  41 + smsProducer.sendSmsSendMessage(smsBody, targetPhone);
38 42 }
39 43  
40   - // TODO FROM 芋艿 to ZZF:可能要讨论下,对于短信发送来说,貌似只提供异步发送即可。对于业务来说,一定不能依赖短信的发送结果.
  44 + @Override
  45 + public Object smsSendCallbackHandle(ServletRequest request) {
  46 + SmsResultDetail smsResultDetail = smsClientFactory.getSmsResultDetailFromCallbackQuery(request);
  47 + logService.updateSendLogByResultDetail(smsResultDetail);
  48 + return smsResultDetail.getCallbackResponseBody();
  49 + }
41 50  
42 51 }
... ...