Selaa lähdekoodia

定义短信回调的结果

YunaiV 4 vuotta sitten
vanhempi
commit
c91833a504
17 muutettua tiedostoa jossa 222 lisäystä ja 184 poistoa
  1. 10 4
      src/main/java/cn/iocoder/dashboard/framework/security/config/SecurityConfiguration.java
  2. 7 8
      src/main/java/cn/iocoder/dashboard/framework/sms/core/client/SmsClient.java
  3. 8 0
      src/main/java/cn/iocoder/dashboard/framework/sms/core/client/SmsClientFactory.java
  4. 4 2
      src/main/java/cn/iocoder/dashboard/framework/sms/core/client/dto/SmsReceiveRespDTO.java
  5. 18 14
      src/main/java/cn/iocoder/dashboard/framework/sms/core/client/impl/AbstractSmsClient.java
  6. 31 5
      src/main/java/cn/iocoder/dashboard/framework/sms/core/client/impl/SmsClientFactoryImpl.java
  7. 12 8
      src/main/java/cn/iocoder/dashboard/framework/sms/core/client/impl/aliyun/AliyunSmsClient.java
  8. 65 59
      src/main/java/cn/iocoder/dashboard/framework/sms/core/client/impl/yunpian/YunpianSmsClient.java
  9. 3 2
      src/main/java/cn/iocoder/dashboard/framework/sms/core/enums/SmsChannelEnum.java
  10. 0 16
      src/main/java/cn/iocoder/dashboard/framework/sms/core/enums/SmsConstants.java
  11. 34 0
      src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/SmsCallbackController.java
  12. 0 48
      src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/SmsDefaultCallbackController.java
  13. 5 5
      src/main/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsService.java
  14. 8 7
      src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SysSmsServiceImpl.java
  15. 15 4
      src/main/java/cn/iocoder/dashboard/util/json/JsonUtils.java
  16. 1 1
      src/test-integration/java/cn/iocoder/dashboard/framework/sms/core/client/impl/aliyun/AliyunSmsClientTest.java
  17. 1 1
      src/test-integration/java/cn/iocoder/dashboard/framework/sms/core/client/impl/yunpian/YunpianSmsClientIntegrationTest.java

+ 10 - 4
src/main/java/cn/iocoder/dashboard/framework/security/config/SecurityConfiguration.java

@@ -128,13 +128,13 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
                 // 设置每个请求的权限
                 .authorizeRequests()
                     // 登陆的接口,可匿名访问
-                    .antMatchers(webProperties.getApiPrefix() + "/login").anonymous()
+                    .antMatchers(api("/login")).anonymous()
                     // 通用的接口,可匿名访问
-                    .antMatchers( webProperties.getApiPrefix() + "/system/captcha/**").anonymous()
+                    .antMatchers(api("/system/captcha/**")).anonymous()
                     // 静态资源,可匿名访问
                     .antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll()
                     // 文件的获取接口,可匿名访问
-                    .antMatchers(webProperties.getApiPrefix() + "/infra/file/get/**").anonymous()
+                    .antMatchers(api("/infra/file/get/**")).anonymous()
                     // Swagger 接口文档
                     .antMatchers("/swagger-ui.html").anonymous()
                     .antMatchers("/swagger-resources/**").anonymous()
@@ -148,13 +148,19 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
                     .antMatchers("/actuator/**").anonymous()
                     // Druid 监控
                     .antMatchers("/druid/**").anonymous()
+                    // 短信回调 API
+                    .antMatchers(api("/system/sms/callback/**")).anonymous()
                     // 除上面外的所有请求全部需要鉴权认证
                     .anyRequest().authenticated()
                 .and()
                 .headers().frameOptions().disable();
-        httpSecurity.logout().logoutUrl(webProperties.getApiPrefix() + "/logout").logoutSuccessHandler(logoutSuccessHandler);
+        httpSecurity.logout().logoutUrl(api("/logout")).logoutSuccessHandler(logoutSuccessHandler);
         // 添加 JWT Filter
         httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
     }
 
+    private String api(String url) {
+        return webProperties.getApiPrefix() + url;
+    }
+
 }

+ 7 - 8
src/main/java/cn/iocoder/dashboard/framework/sms/core/client/SmsClient.java

@@ -1,10 +1,9 @@
 package cn.iocoder.dashboard.framework.sms.core.client;
 
 import cn.iocoder.dashboard.common.core.KeyValue;
-import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsResultDetail;
+import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsReceiveRespDTO;
 import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsSendRespDTO;
 
-import javax.servlet.ServletRequest;
 import java.util.List;
 
 /**
@@ -31,15 +30,15 @@ public interface SmsClient {
      * @param templateParams 短信模板参数
      * @return 短信发送结果
      */
-    SmsCommonResult<SmsSendRespDTO> send(Long logId, String mobile, String apiTemplateId, List<KeyValue<String, Object>> templateParams);
+    SmsCommonResult<SmsSendRespDTO> sendSms(Long logId, String mobile, String apiTemplateId, List<KeyValue<String, Object>> templateParams);
 
-    // TODO FROM 芋艿 to ZZF:是不是可以改成意图更明确的解析返回结果,例如说 parseXXXX
     /**
-     * 短信发送回调请求处理
+     * 解析接收短信的接收结果
      *
-     * @param request 请求
-     * @return 短信发送结果
+     * @param text 结果
+     * @return 结果内容
+     * @throws Throwable 当解析 text 发生异常时,则会抛出异常
      */
-    SmsResultDetail smsSendCallbackHandle(ServletRequest request) throws Exception;
+    SmsCommonResult<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) throws Throwable;
 
 }

+ 8 - 0
src/main/java/cn/iocoder/dashboard/framework/sms/core/client/SmsClientFactory.java

@@ -18,6 +18,14 @@ public interface SmsClientFactory {
      */
     SmsClient getSmsClient(Long channelId);
 
+    /**
+     * 获得短信 Client
+     *
+     * @param channelCode 渠道编码
+     * @return 短信 Client
+     */
+    SmsClient getSmsClient(String channelCode);
+
     /**
      * 创建短信 Client
      *

+ 4 - 2
src/main/java/cn/iocoder/dashboard/framework/sms/core/client/dto/SmsResultDetail.java → src/main/java/cn/iocoder/dashboard/framework/sms/core/client/dto/SmsReceiveRespDTO.java

@@ -7,10 +7,12 @@ import java.io.Serializable;
 import java.util.Date;
 
 /**
- * 消息内容实体类
+ * 消息接收 Response DTO
+ *
+ * @author 芋道源码
  */
 @Data
-public class SmsResultDetail implements Serializable {
+public class SmsReceiveRespDTO implements Serializable {
 
     /**
      * 唯一标识

+ 18 - 14
src/main/java/cn/iocoder/dashboard/framework/sms/core/client/impl/AbstractSmsClient.java

@@ -4,6 +4,7 @@ import cn.iocoder.dashboard.common.core.KeyValue;
 import cn.iocoder.dashboard.framework.sms.core.client.SmsClient;
 import cn.iocoder.dashboard.framework.sms.core.client.SmsCodeMapping;
 import cn.iocoder.dashboard.framework.sms.core.client.SmsCommonResult;
+import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsReceiveRespDTO;
 import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsSendRespDTO;
 import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties;
 import lombok.extern.slf4j.Slf4j;
@@ -68,12 +69,12 @@ public abstract class AbstractSmsClient implements SmsClient {
     }
 
     @Override
-    public final SmsCommonResult<SmsSendRespDTO> send(Long logId, String mobile,
-                                                      String apiTemplateId, List<KeyValue<String, Object>> templateParams) {
+    public final SmsCommonResult<SmsSendRespDTO> sendSms(Long logId, String mobile,
+                                                         String apiTemplateId, List<KeyValue<String, Object>> templateParams) {
         // 执行短信发送
         SmsCommonResult<SmsSendRespDTO> result;
         try {
-            result = doSend(logId, mobile, apiTemplateId, templateParams);
+            result = doSendSms(logId, mobile, apiTemplateId, templateParams);
         } catch (Throwable ex) {
             // 打印异常日志
             log.error("[send][发送短信异常,sendLogId({}) mobile({}) apiTemplateId({}) templateParams({})]",
@@ -84,17 +85,20 @@ public abstract class AbstractSmsClient implements SmsClient {
         return result;
     }
 
-    /**
-     * 发送消息
-     *
-     * @param sendLogId 发送日志编号
-     * @param mobile 手机号
-     * @param apiTemplateId 短信 API 的模板编号
-     * @param templateParams 短信模板参数
-     * @return 短信发送结果
-     */
-    protected abstract SmsCommonResult<SmsSendRespDTO> doSend(Long sendLogId, String mobile,
-                                                              String apiTemplateId, List<KeyValue<String, Object>> templateParams)
+    protected abstract SmsCommonResult<SmsSendRespDTO> doSendSms(Long sendLogId, String mobile,
+                                                                 String apiTemplateId, List<KeyValue<String, Object>> templateParams)
             throws Throwable;
 
+    @Override
+    public SmsCommonResult<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) throws Throwable {
+        try {
+            return doParseSmsReceiveStatus(text);
+        } catch (Throwable ex) {
+            log.error("[parseSmsReceiveStatus][text({}) 解析发生异常]", text, ex);
+            throw ex;
+        }
+    }
+
+    protected abstract SmsCommonResult<SmsReceiveRespDTO> doParseSmsReceiveStatus(String text) throws Throwable;
+
 }

+ 31 - 5
src/main/java/cn/iocoder/dashboard/framework/sms/core/client/impl/SmsClientFactoryImpl.java

@@ -10,8 +10,9 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.util.Assert;
 import org.springframework.validation.annotation.Validated;
 
-import java.util.Map;
+import java.util.Arrays;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
 
 /**
  * 短信客户端工厂接口
@@ -26,20 +27,45 @@ public class SmsClientFactoryImpl implements SmsClientFactory {
      * 短信客户端 Map
      * key:渠道编号,使用 {@link SmsChannelProperties#getId()}
      */
-    private final Map<Long, AbstractSmsClient> clients = new ConcurrentHashMap<>();
+    private final ConcurrentMap<Long, AbstractSmsClient> channelIdClients = new ConcurrentHashMap<>();
+
+    /**
+     * 短信客户端 Map
+     * key:渠道编码,使用 {@link SmsChannelProperties#getCode()} ()}
+     *
+     * 注意,一些场景下,需要获得某个渠道类型的客户端,所以需要使用它。
+     * 例如说,解析短信接收结果,是相对通用的,不需要使用某个渠道编号的 {@link #channelIdClients}
+     */
+    private final ConcurrentMap<String, AbstractSmsClient> channelCodeClients = new ConcurrentHashMap<>();
+
+    public SmsClientFactoryImpl() {
+        // 初始化 channelCodeClients 集合
+        Arrays.stream(SmsChannelEnum.values()).forEach(channel -> {
+            // 创建一个空的 SmsChannelProperties 对象
+            SmsChannelProperties properties = new SmsChannelProperties().setCode(channel.getCode());
+            // 创建 Sms 客户端
+            AbstractSmsClient smsClient = createSmsClient(properties);
+            channelCodeClients.put(channel.getCode(), smsClient);
+        });
+    }
 
     @Override
     public SmsClient getSmsClient(Long channelId) {
-        return clients.get(channelId);
+        return channelIdClients.get(channelId);
+    }
+
+    @Override
+    public SmsClient getSmsClient(String channelCode) {
+        return channelCodeClients.get(channelCode);
     }
 
     @Override
     public void createOrUpdateSmsClient(SmsChannelProperties properties) {
-        AbstractSmsClient client = clients.get(properties.getId());
+        AbstractSmsClient client = channelIdClients.get(properties.getId());
         if (client == null) {
             client = this.createSmsClient(properties);
             client.init();
-            clients.put(client.getId(), client);
+            channelIdClients.put(client.getId(), client);
         } else {
             client.refresh(properties);
         }

+ 12 - 8
src/main/java/cn/iocoder/dashboard/framework/sms/core/client/impl/aliyun/AliyunSmsClient.java

@@ -5,7 +5,7 @@ import cn.hutool.core.date.DateUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.dashboard.common.core.KeyValue;
 import cn.iocoder.dashboard.framework.sms.core.client.SmsCommonResult;
-import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsResultDetail;
+import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsReceiveRespDTO;
 import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsSendRespDTO;
 import cn.iocoder.dashboard.framework.sms.core.client.impl.AbstractSmsClient;
 import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties;
@@ -61,8 +61,8 @@ public class AliyunSmsClient extends AbstractSmsClient {
     }
 
     @Override
-    protected SmsCommonResult<SmsSendRespDTO> doSend(Long sendLogId, String mobile,
-                                                     String apiTemplateId, List<KeyValue<String, Object>> templateParams) {
+    protected SmsCommonResult<SmsSendRespDTO> doSendSms(Long sendLogId, String mobile,
+                                                        String apiTemplateId, List<KeyValue<String, Object>> templateParams) {
         // 构建参数
         SendSmsRequest request = new SendSmsRequest();
         request.setSysMethod(MethodType.POST);
@@ -110,11 +110,10 @@ public class AliyunSmsClient extends AbstractSmsClient {
      * @return
      * @throws Exception
      */
-    @Override
-    public SmsResultDetail smsSendCallbackHandle(ServletRequest request) throws Exception {
+    public SmsReceiveRespDTO smsSendCallbackHandle(ServletRequest request) throws Exception {
         BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream()));
         String paramStr = reader.readLine();
-        List<Map<String, Object>> params = JsonUtils.parseByType(paramStr, new TypeReference<List<Map<String, Object>>>() {
+        List<Map<String, Object>> params = JsonUtils.parseObject(paramStr, new TypeReference<List<Map<String, Object>>>() {
         });
         if (CollectionUtil.isNotEmpty(params)) {
             Map<String, Object> sendResultParamMap = params.get(0);
@@ -123,6 +122,11 @@ public class AliyunSmsClient extends AbstractSmsClient {
         return null;
     }
 
+    @Override
+    protected SmsCommonResult<SmsReceiveRespDTO> doParseSmsReceiveStatus(String text) throws Throwable {
+        return null;
+    }
+
     /**
      * 短信发送回调辅助类
      */
@@ -168,8 +172,8 @@ public class AliyunSmsClient extends AbstractSmsClient {
             return sendResultParamMap.get(CallbackField.OUT_ID).toString();
         }
 
-        public SmsResultDetail toResultDetail() {
-            SmsResultDetail resultDetail = new SmsResultDetail();
+        public SmsReceiveRespDTO toResultDetail() {
+            SmsReceiveRespDTO resultDetail = new SmsReceiveRespDTO();
             resultDetail.setSendStatus(getSendStatus());
             resultDetail.setApiId(getBizId());
             resultDetail.setSendTime(getSendTime());

+ 65 - 59
src/main/java/cn/iocoder/dashboard/framework/sms/core/client/impl/yunpian/YunpianSmsClient.java

@@ -2,33 +2,31 @@ package cn.iocoder.dashboard.framework.sms.core.client.impl.yunpian;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.collection.CollectionUtil;
-import cn.hutool.core.date.DateUtil;
 import cn.hutool.core.util.CharsetUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.core.util.URLUtil;
 import cn.iocoder.dashboard.common.core.KeyValue;
 import cn.iocoder.dashboard.framework.sms.core.client.SmsCommonResult;
-import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsResultDetail;
+import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsReceiveRespDTO;
 import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsSendRespDTO;
 import cn.iocoder.dashboard.framework.sms.core.client.impl.AbstractSmsClient;
-import cn.iocoder.dashboard.framework.sms.core.enums.SmsConstants;
 import cn.iocoder.dashboard.framework.sms.core.property.SmsChannelProperties;
-import cn.iocoder.dashboard.modules.system.enums.sms.SysSmsSendStatusEnum;
+import cn.iocoder.dashboard.util.date.DateUtils;
 import cn.iocoder.dashboard.util.json.JsonUtils;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.yunpian.sdk.YunpianClient;
 import com.yunpian.sdk.constant.YunpianConstant;
 import com.yunpian.sdk.model.Result;
 import com.yunpian.sdk.model.SmsSingleSend;
+import lombok.Data;
 import lombok.extern.slf4j.Slf4j;
 
 import javax.servlet.ServletRequest;
 import java.io.UnsupportedEncodingException;
 import java.net.URLEncoder;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.StringJoiner;
+import java.util.*;
 
 /**
  * 云片短信客户端的实现类
@@ -65,8 +63,8 @@ public class YunpianSmsClient extends AbstractSmsClient {
     }
 
     @Override
-    protected SmsCommonResult<SmsSendRespDTO> doSend(Long sendLogId, String mobile,
-                                                     String apiTemplateId, List<KeyValue<String, Object>> templateParams) throws Throwable {
+    protected SmsCommonResult<SmsSendRespDTO> doSendSms(Long sendLogId, String mobile,
+                                                        String apiTemplateId, List<KeyValue<String, Object>> templateParams) throws Throwable {
         // 构建参数
         Map<String, String> request = new HashMap<>();
         request.put(YunpianConstant.APIKEY, properties.getApiKey());
@@ -74,7 +72,7 @@ public class YunpianSmsClient extends AbstractSmsClient {
         request.put(YunpianConstant.TPL_ID, apiTemplateId);
         request.put(YunpianConstant.TPL_VALUE, formatTplValue(templateParams));
         request.put(YunpianConstant.UID, String.valueOf(sendLogId));
-        request.put(Helper.CALLBACK, properties.getCallbackUrl());
+        request.put(YunpianConstant.CALLBACK_URL, properties.getCallbackUrl());
 
         // 执行发送
         Result<SmsSingleSend> sendResult = client.sms().tpl_single_send(request);
@@ -107,15 +105,6 @@ public class YunpianSmsClient extends AbstractSmsClient {
         return sendResult.getMsg() + " => " + sendResult.getDetail();
     }
 
-    /**
-     * 云片的比较复杂,又是加密又是套娃的
-     */
-    @Override
-    public SmsResultDetail smsSendCallbackHandle(ServletRequest request) throws UnsupportedEncodingException {
-        Map<String, String> map = getRequestParams(request);
-        return Helper.getSmsResultDetailByParam(map);
-    }
-
     /**
      * 从 request 中获取请求中传入的短信发送结果信息
      *
@@ -127,7 +116,7 @@ public class YunpianSmsClient extends AbstractSmsClient {
         Map<String, String[]> parameterMap = request.getParameterMap();
         String[] smsStatuses = parameterMap.get(YunpianConstant.SMS_STATUS);
         String encode = URLEncoder.encode(smsStatuses[0], CharsetUtil.UTF_8);
-        List<Map<String, String>> paramList = JsonUtils.parseByType(encode, callbackType);
+        List<Map<String, String>> paramList = JsonUtils.parseObject(encode, callbackType);
         if (CollectionUtil.isNotEmpty(paramList)) {
             return paramList.get(0);
         }
@@ -135,46 +124,63 @@ public class YunpianSmsClient extends AbstractSmsClient {
                 + JsonUtils.toJsonString(request.getParameterMap()));
     }
 
+    @Override
+    protected SmsCommonResult<SmsReceiveRespDTO> doParseSmsReceiveStatus(String text) throws Throwable {
+        return null;
+    }
+
     /**
-     * 云片的回调函数的一些辅助方法
+     * 短信接收状态
+     *
+     * 参见 https://www.yunpian.com/official/document/sms/zh_cn/domestic_push_report 文档
+     *
+     * @author 芋道源码
      */
-    private static class Helper {
-
-        //短信唯一标识
-        private final static String API_ID = "sid";
+    @Data
+    public static class SmsReceiveStatus {
+
+        /**
+         * 运营商反馈代码的中文解释
+         *
+         * 默认不推送此字段,如需推送,请联系客服
+         */
+        @JsonProperty("error_detail")
+        private String errorDetail;
+        /**
+         * 短信编号
+         */
+        private Long sid;
+        /**
+         * 用户自定义 id
+         *
+         * 这里我们传递的是 SysSmsLogDO 的日志编号
+         */
+        private Long uid;
+        /**
+         * 用户接收时间
+         */
+        @JsonProperty("user_receive_time")
+        @JsonFormat(pattern = DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+        private Date userReceiveTime;
+        /**
+         * 运营商返回的代码,如:"DB:0103"
+         *
+         * 由于不同运营商信息不同,此字段仅供参考;
+         */
+        @JsonProperty("error_msg")
+        private String errorMsg;
+        /**
+         * 接收手机号
+         */
+        private String mobile;
+        /**
+         * 接收状态
+         *
+         * 目前仅有 SUCCESS / FAIL,所以使用 Boolean 接收
+         */
+        @JsonProperty("report_status")
+        private String reportStatus;
 
-        //回调地址·
-        private final static String CALLBACK = "callback";
-
-        //手机号
-        private final static String MOBILE = "mobile";
-
-        //错误信息
-        private final static String ERROR_MSG = "error_msg";
-
-        //用户接收时间 字符串 标准格式
-        private final static String USER_RECEIVE_TIME = "user_receive_time";
-
-        //发送状态
-        private final static String REPORT_STATUS = "report_status";
-
-        private static int getSendStatus(Map<String, String> map) {
-            String reportStatus = map.get(REPORT_STATUS);
-            return SmsConstants.SUCCESS.equals(reportStatus)
-                    ? SysSmsSendStatusEnum.SUCCESS.getStatus()
-                    : SysSmsSendStatusEnum.FAILURE.getStatus();
-        }
-
-        public static SmsResultDetail getSmsResultDetailByParam(Map<String, String> map) {
-            SmsResultDetail detail = new SmsResultDetail();
-            detail.setPhone(map.get(MOBILE));
-            detail.setMessage(map.get(ERROR_MSG));
-            detail.setSendTime(DateUtil.parseTime(map.get(USER_RECEIVE_TIME)));
-            detail.setSendStatus(getSendStatus(map));
-            detail.setApiId(API_ID);
-
-            detail.setCallbackResponseBody(SmsConstants.SUCCESS);
-            return detail;
-        }
     }
+
 }

+ 3 - 2
src/main/java/cn/iocoder/dashboard/framework/sms/core/enums/SmsChannelEnum.java

@@ -16,8 +16,9 @@ public enum SmsChannelEnum {
 
     YUN_PIAN("YUN_PIAN", "云片"),
     ALIYUN("ALIYUN", "阿里云"),
-    TENCENT("TENCENT", "腾讯云"),
-    HUA_WEI("HUA_WEI", "华为云"),;
+//    TENCENT("TENCENT", "腾讯云"),
+//    HUA_WEI("HUA_WEI", "华为云"),
+    ;
 
     /**
      * 编码

+ 0 - 16
src/main/java/cn/iocoder/dashboard/framework/sms/core/enums/SmsConstants.java

@@ -1,16 +0,0 @@
-package cn.iocoder.dashboard.framework.sms.core.enums;
-
-/**
- * 短信相关常量类
- *
- * @author zzf
- * @date 2021/3/5 10:42
- */
-public interface SmsConstants {
-
-
-    String COMMA = ",";
-
-    String SUCCESS = "SUCCESS";
-
-}

+ 34 - 0
src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/SmsCallbackController.java

@@ -0,0 +1,34 @@
+package cn.iocoder.dashboard.modules.system.controller.sms;
+
+import cn.hutool.core.util.URLUtil;
+import cn.iocoder.dashboard.common.pojo.CommonResult;
+import cn.iocoder.dashboard.framework.sms.core.enums.SmsChannelEnum;
+import cn.iocoder.dashboard.modules.system.service.sms.SysSmsService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+
+@Api(tags = "短信回调")
+@RestController
+@RequestMapping("/system/sms/callback")
+public class SmsCallbackController {
+
+    @Resource
+    private SysSmsService smsService;
+
+    @PostMapping("/sms/yunpian")
+    @ApiOperation(value = "云片短信的回调", notes = "参见 https://www.yunpian.com/official/document/sms/zh_cn/domestic_push_report 文档")
+    @ApiImplicitParam(name = "sms_status", value = "发送状态", required = true, example = "[{具体内容}]", dataTypeClass = Long.class)
+    public CommonResult<Boolean> receiveYunpianSmsStatus(@RequestParam("sms_status") String smsStatus) throws Throwable {
+        String text = URLUtil.decode(smsStatus); // decode 解码参数,因为它被 encode
+        smsService.receiveSmsStatus(SmsChannelEnum.YUN_PIAN.getCode(), text);
+        return CommonResult.success(true);
+    }
+
+}

+ 0 - 48
src/main/java/cn/iocoder/dashboard/modules/system/controller/sms/SmsDefaultCallbackController.java

@@ -1,48 +0,0 @@
-package cn.iocoder.dashboard.modules.system.controller.sms;
-
-import cn.iocoder.dashboard.modules.system.service.sms.SysSmsService;
-import io.swagger.annotations.Api;
-import io.swagger.annotations.ApiOperation;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-import javax.annotation.Resource;
-import javax.servlet.ServletRequest;
-
-/**
- * 短信默认回调接口
- *
- * @author zzf
- * @date 2021/3/5 8:59
- */
-@Api(tags = "短信回调api")
-@RestController
-@RequestMapping("/sms/callback")
-public class SmsDefaultCallbackController {
-
-    @Resource
-    private SysSmsService smsService;
-
-
-    @ApiOperation(value = "短信发送回调接口")
-    @PostMapping("/sms-send")
-    public Object sendSmsCallback(ServletRequest request) {
-        return smsService.smsSendCallbackHandle(request);
-    }
-
-/*
-    @Resource
-    private SmsSendStreamProducer smsSendStreamProducer;
-
-    @ApiOperation("redis stream测试")
-    @GetMapping("/test/redis/stream")
-    public void test() {
-        SmsBody smsBody = new SmsBody();
-        smsBody.setSmsLogId(1L);
-        smsBody.setTemplateCode("sdf");
-        smsBody.setTemplateContent("sdf");
-        smsSendStreamProducer.sendSmsSendMessage(smsBody, "18216466755");
-    }*/
-
-}

+ 5 - 5
src/main/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsService.java

@@ -2,7 +2,6 @@ package cn.iocoder.dashboard.modules.system.service.sms;
 
 import cn.iocoder.dashboard.modules.system.mq.message.sms.SysSmsSendMessage;
 
-import javax.servlet.ServletRequest;
 import java.util.List;
 import java.util.Map;
 
@@ -24,11 +23,12 @@ public interface SysSmsService {
     void doSendSms(SysSmsSendMessage message);
 
     /**
-     * 处理短信发送回调函数
+     * 接收短信的接收结果
      *
-     * @param request        请求
-     * @return 响应数据
+     * @param channelCode 渠道编码
+     * @param text 结果内容
+     * @throws Throwable 处理失败时,抛出异常
      */
-    Object smsSendCallbackHandle(ServletRequest request);
+    void receiveSmsStatus(String channelCode, String text) throws Throwable;
 
 }

+ 8 - 7
src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SysSmsServiceImpl.java

@@ -7,6 +7,7 @@ import cn.iocoder.dashboard.common.enums.UserTypeEnum;
 import cn.iocoder.dashboard.framework.sms.core.client.SmsClient;
 import cn.iocoder.dashboard.framework.sms.core.client.SmsClientFactory;
 import cn.iocoder.dashboard.framework.sms.core.client.SmsCommonResult;
+import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsReceiveRespDTO;
 import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsSendRespDTO;
 import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsTemplateDO;
 import cn.iocoder.dashboard.modules.system.dal.dataobject.user.SysUserDO;
@@ -21,7 +22,6 @@ import org.springframework.stereotype.Service;
 import org.springframework.util.Assert;
 
 import javax.annotation.Resource;
-import javax.servlet.ServletRequest;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -139,7 +139,7 @@ public class SysSmsServiceImpl implements SysSmsService {
         SmsClient smsClient = smsClientFactory.getSmsClient(message.getChannelId());
         Assert.notNull(smsClient, String.format("短信客户端(%d) 不存在", message.getChannelId()));
         // 发送短信
-        SmsCommonResult<SmsSendRespDTO> sendResult = smsClient.send(message.getLogId(), message.getMobile(),
+        SmsCommonResult<SmsSendRespDTO> sendResult = smsClient.sendSms(message.getLogId(), message.getMobile(),
                 message.getApiTemplateId(), message.getTemplateParams());
         smsLogService.updateSmsSendResult(message.getLogId(), sendResult.getCode(), sendResult.getMsg(),
                 sendResult.getApiCode(), sendResult.getApiMsg(), sendResult.getApiRequestId(),
@@ -147,11 +147,12 @@ public class SysSmsServiceImpl implements SysSmsService {
     }
 
     @Override
-    public Object smsSendCallbackHandle(ServletRequest request) {
-//        SmsResultDetail smsResultDetail = smsClientFactory.getSmsResultDetailFromCallbackQuery(request);
-//        logService.updateSendLogByResultDetail(smsResultDetail);
-//        return smsResultDetail.getCallbackResponseBody();
-        return null;
+    public void receiveSmsStatus(String channelCode, String text) throws Throwable {
+        // 获得渠道对应的 SmsClient 客户端
+        SmsClient smsClient = smsClientFactory.getSmsClient(channelCode);
+        Assert.notNull(smsClient, String.format("短信客户端(%s) 不存在", channelCode));
+        // 解析内容
+        SmsCommonResult<SmsReceiveRespDTO> receiveResult = smsClient.parseSmsReceiveStatus(text);
     }
 
 }

+ 15 - 4
src/main/java/cn/iocoder/dashboard/util/json/JsonUtils.java

@@ -2,12 +2,14 @@ package cn.iocoder.dashboard.util.json;
 
 import cn.hutool.core.util.ArrayUtil;
 import cn.hutool.core.util.StrUtil;
+import cn.iocoder.dashboard.framework.sms.core.client.impl.yunpian.YunpianSmsClient;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.ObjectMapper;
 
 import java.io.IOException;
-import java.util.Set;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * JSON 工具类
@@ -59,7 +61,7 @@ public class JsonUtils {
         }
     }
 
-    public static Object parseObject(String text, TypeReference<Set<Long>> typeReference) {
+    public static <T> T parseObject(String text, TypeReference<T> typeReference) {
         try {
             return objectMapper.readValue(text, typeReference);
         } catch (IOException e) {
@@ -67,12 +69,21 @@ public class JsonUtils {
         }
     }
 
-    public static <T> T parseByType(String text, TypeReference<T> typeReference) {
+    public static <T> List<T> parseArray(String text, Class<T> clazz) {
+        if (StrUtil.isEmpty(text)) {
+            return new ArrayList<>();
+        }
         try {
-            return objectMapper.readValue(text, typeReference);
+            return objectMapper.readValue(text, objectMapper.getTypeFactory().constructCollectionType(List.class, clazz));
         } catch (IOException e) {
             throw new RuntimeException(e);
         }
     }
 
+    public static void main(String[] args) {
+        String text = "[{\"sid\":9527,\"uid\":null,\"user_receive_time\":\"2014-03-17 22:55:21\",\"error_msg\":\"\",\"mobile\":\"15205201314\",\"report_status\":\"SUCCESS\"},{\"sid\":9528,\"uid\":null,\"user_receive_time\":\"2014-03-17 22:55:23\",\"error_msg\":\"\",\"mobile\":\"15212341234\",\"report_status\":\"SUCCESS\"}]";
+        List<YunpianSmsClient.SmsReceiveStatus> result = parseArray(text, YunpianSmsClient.SmsReceiveStatus.class);
+        System.out.println(result);
+    }
+
 }

+ 1 - 1
src/test-integration/java/cn/iocoder/dashboard/framework/sms/core/client/impl/aliyun/AliyunSmsClientTest.java

@@ -32,7 +32,7 @@ public class AliyunSmsClientTest {
         templateParams.add(new KeyValue<>("code", "1024"));
 //        templateParams.put("operation", "嘿嘿");
 //        SmsResult result = smsClient.send(1L, "15601691399", "4372216", templateParams);
-        SmsCommonResult<SmsSendRespDTO> result = smsClient.send(1L, "15601691399", "SMS_207945135", templateParams);
+        SmsCommonResult<SmsSendRespDTO> result = smsClient.sendSms(1L, "15601691399", "SMS_207945135", templateParams);
         System.out.println(result);
     }
 

+ 1 - 1
src/test-integration/java/cn/iocoder/dashboard/framework/sms/core/client/impl/yunpian/YunpianSmsClientIntegrationTest.java

@@ -31,7 +31,7 @@ public class YunpianSmsClientIntegrationTest {
         templateParams.add(new KeyValue<>("code", "1024"));
         templateParams.add(new KeyValue<>("operation", "嘿嘿"));
 //        SmsResult result = smsClient.send(1L, "15601691399", "4372216", templateParams);
-        SmsCommonResult<SmsSendRespDTO> result = smsClient.send(1L, "15601691399", "4383920", templateParams);
+        SmsCommonResult<SmsSendRespDTO> result = smsClient.sendSms(1L, "15601691399", "4383920", templateParams);
         System.out.println(result);
     }