فهرست منبع

短信回调的解析重构

YunaiV 4 سال پیش
والد
کامیت
8ab29d2a25

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

@@ -39,6 +39,6 @@ public interface SmsClient {
      * @return 结果内容
      * @throws Throwable 当解析 text 发生异常时,则会抛出异常
      */
-    SmsCommonResult<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) throws Throwable;
+    List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) throws Throwable;
 
 }

+ 20 - 18
src/main/java/cn/iocoder/dashboard/framework/sms/core/client/dto/SmsReceiveRespDTO.java

@@ -1,9 +1,7 @@
 package cn.iocoder.dashboard.framework.sms.core.client.dto;
 
-import cn.iocoder.dashboard.modules.system.enums.sms.SysSmsSendStatusEnum;
 import lombok.Data;
 
-import java.io.Serializable;
 import java.util.Date;
 
 /**
@@ -12,35 +10,39 @@ import java.util.Date;
  * @author 芋道源码
  */
 @Data
-public class SmsReceiveRespDTO implements Serializable {
+public class SmsReceiveRespDTO {
 
     /**
-     * 唯一标识
+     * 是否接收成功
      */
-    private String apiId;
-
+    private Boolean success;
     /**
-     * 短信发送状态 {@link SysSmsSendStatusEnum}
+     * API 接收结果编码
      */
-    private Integer sendStatus;
-
+    private String errorCode;
     /**
-     * 接收手机号
+     * API 接收结果说明
      */
-    private String phone;
+    private String errorMsg;
 
     /**
-     * 提示
+     * 手机号
      */
-    private String message;
-
+    private String mobile;
     /**
-     * 时间
+     * 用户接收时间
      */
-    private Date sendTime;
+    private Date receiveTime;
 
     /**
-     * 接口返回值
+     * 短信 API 发送返回的序号
      */
-    private Object callbackResponseBody;
+    private String serialNo;
+    /**
+     * 短信日志编号
+     *
+     * 对应 SysSmsLogDO 的编号
+     */
+    private Long logId;
+
 }

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

@@ -90,7 +90,7 @@ public abstract class AbstractSmsClient implements SmsClient {
             throws Throwable;
 
     @Override
-    public SmsCommonResult<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) throws Throwable {
+    public List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) throws Throwable {
         try {
             return doParseSmsReceiveStatus(text);
         } catch (Throwable ex) {
@@ -99,6 +99,6 @@ public abstract class AbstractSmsClient implements SmsClient {
         }
     }
 
-    protected abstract SmsCommonResult<SmsReceiveRespDTO> doParseSmsReceiveStatus(String text) throws Throwable;
+    protected abstract List<SmsReceiveRespDTO> doParseSmsReceiveStatus(String text) throws Throwable;
 
 }

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

@@ -1,7 +1,5 @@
 package cn.iocoder.dashboard.framework.sms.core.client.impl.aliyun;
 
-import cn.hutool.core.collection.CollectionUtil;
-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;
@@ -9,7 +7,6 @@ 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;
-import cn.iocoder.dashboard.modules.system.enums.sms.SysSmsSendStatusEnum;
 import cn.iocoder.dashboard.util.collection.MapUtils;
 import cn.iocoder.dashboard.util.json.JsonUtils;
 import com.aliyuncs.DefaultAcsClient;
@@ -20,16 +17,17 @@ import com.aliyuncs.exceptions.ClientException;
 import com.aliyuncs.http.MethodType;
 import com.aliyuncs.profile.DefaultProfile;
 import com.aliyuncs.profile.IClientProfile;
-import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
 import lombok.extern.slf4j.Slf4j;
 
-import javax.servlet.ServletRequest;
-import java.io.BufferedReader;
-import java.io.InputStreamReader;
 import java.util.Date;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
+import java.util.stream.Collectors;
+
+import static cn.iocoder.dashboard.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+import static cn.iocoder.dashboard.util.date.DateUtils.TIME_ZONE_DEFAULT;
 
 /**
  * 阿里短信客户端的实现类
@@ -40,8 +38,9 @@ import java.util.Map;
 @Slf4j
 public class AliyunSmsClient extends AbstractSmsClient {
 
-    private static final String PRODUCT = "Dystopi";
-    private static final String DOMAIN = "dysmsapi.aliyuncs.com";
+    /**
+     * REGION, 使用杭州
+     */
     private static final String ENDPOINT = "cn-hangzhou";
 
     /**
@@ -56,7 +55,6 @@ public class AliyunSmsClient extends AbstractSmsClient {
     @Override
     protected void doInit() {
         IClientProfile profile = DefaultProfile.getProfile(ENDPOINT, properties.getApiKey(), properties.getApiSecret());
-        DefaultProfile.addEndpoint(ENDPOINT, PRODUCT, DOMAIN);
         acsClient = new DefaultAcsClient(profile);
     }
 
@@ -93,133 +91,80 @@ public class AliyunSmsClient extends AbstractSmsClient {
         return ex.getErrMsg() + " => " + ex.getErrorDescription();
     }
 
-    /**
-     * [{
-     * "send_time" : "2017-08-30 00:00:00",
-     * "report_time" : "2017-08-30 00:00:00",
-     * "success" : true,
-     * "err_msg" : "用户接收成功",
-     * "err_code" : "DELIVERED",
-     * "phone_number" : "18612345678",
-     * "sms_size" : "1",
-     * "biz_id" : "932702304080415357^0",
-     * "out_id" : "1184585343"
-     * }]
-     *
-     * @param request 请求
-     * @return
-     * @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.parseObject(paramStr, new TypeReference<List<Map<String, Object>>>() {
-        });
-        if (CollectionUtil.isNotEmpty(params)) {
-            Map<String, Object> sendResultParamMap = params.get(0);
-            return CallbackHelper.of(sendResultParamMap).toResultDetail();
-        }
-        return null;
-    }
-
     @Override
-    protected SmsCommonResult<SmsReceiveRespDTO> doParseSmsReceiveStatus(String text) throws Throwable {
-        return null;
+    protected List<SmsReceiveRespDTO> doParseSmsReceiveStatus(String text) throws Throwable {
+        List<SmsReceiveStatus> statuses = JsonUtils.parseArray(text, SmsReceiveStatus.class);
+        return statuses.stream().map(status -> {
+            SmsReceiveRespDTO resp = new SmsReceiveRespDTO();
+            resp.setSuccess(status.getSuccess());
+            resp.setErrorCode(status.getErrCode()).setErrorMsg(status.getErrMsg());
+            resp.setMobile(status.getPhoneNumber()).setReceiveTime(status.getReportTime());
+            resp.setSerialNo(status.getBizId()).setLogId(Long.valueOf(status.getOutId()));
+            return resp;
+        }).collect(Collectors.toList());
     }
 
     /**
-     * 短信发送回调辅助类
+     * 短信接收状态
+     *
+     * 参见 https://help.aliyun.com/document_detail/101867.html 文档
+     *
+     * @author 芋道源码
      */
-    private static class CallbackHelper {
-
-        private final Map<String, Object> sendResultParamMap;
-
-        private CallbackHelper(Map<String, Object> sendResultParamMap) {
-            this.sendResultParamMap = sendResultParamMap;
-        }
-
-        public static CallbackHelper of(Map<String, Object> sendResultParamMap) {
-            return new CallbackHelper(sendResultParamMap);
-        }
-
-        public Integer getSendStatus() {
-            return ((Boolean) sendResultParamMap.get(CallbackField.SUCCESS))
-                    ? SysSmsSendStatusEnum.SUCCESS.getStatus()
-                    : SysSmsSendStatusEnum.FAILURE.getStatus();
-        }
-
-        public String getBizId() {
-            return sendResultParamMap.get(CallbackField.BIZ_ID).toString();
-        }
-
-        public String getErrMsg() {
-            return sendResultParamMap.get(CallbackField.ERR_MSG).toString();
-        }
-
-        public String getErrCode() {
-            return sendResultParamMap.get(CallbackField.ERR_CODE).toString();
-        }
-
-        public Date getSendTime() {
-            return DateUtil.parseTime(sendResultParamMap.get(CallbackField.SEND_TIME).toString());
-        }
-
-        public String getPhoneNumber() {
-            return sendResultParamMap.get(CallbackField.PHONE_NUMBER).toString();
-        }
-
-        public String getOutId() {
-            return sendResultParamMap.get(CallbackField.OUT_ID).toString();
-        }
-
-        public SmsReceiveRespDTO toResultDetail() {
-            SmsReceiveRespDTO resultDetail = new SmsReceiveRespDTO();
-            resultDetail.setSendStatus(getSendStatus());
-            resultDetail.setApiId(getBizId());
-            resultDetail.setSendTime(getSendTime());
-            resultDetail.setPhone(getPhoneNumber());
-            resultDetail.setMessage(getErrMsg());
-
-            resultDetail.setCallbackResponseBody(generateSuccessResponseBody());
-            return resultDetail;
-        }
+    @Data
+    public static class SmsReceiveStatus {
 
         /**
-         * 生成回调成功的返回对象
+         * 手机号
          */
-        private Map<String, Object> generateSuccessResponseBody() {
-            Map<String, Object> result = new HashMap<>();
-            result.put("code", 0);
-            result.put("msg", "成功");
-            return result;
-        }
-
-    }
-
-    /**
-     * 回调接口字段定义
-     */
-    private interface CallbackField {
-        //是否成功 boolean
-        String SUCCESS = "success";
-
-        //发送时间
-        String SEND_TIME = "send_time";
-
-        //错误信息
-        String ERR_MSG = "err_msg";
-
-        //错误编码
-        String ERR_CODE = "err_code";
-
-        //手机号
-        String PHONE_NUMBER = "phone_number";
-
-        //用户序列号 out_id
-        String OUT_ID = "out_id";
+        @JsonProperty("phone_number")
+        private String phoneNumber;
+        /**
+         * 发送时间
+         */
+        @JsonProperty("send_time")
+        @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
+        private Date sendTime;
+        /**
+         * 状态报告时间
+         */
+        @JsonProperty("report_time")
+        @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
+        private Date reportTime;
+        /**
+         * 是否接收成功
+         */
+        private Boolean success;
+        /**
+         * 状态报告说明
+         */
+        @JsonProperty("err_msg")
+        private String errMsg;
+        /**
+         * 状态报告编码
+         */
+        @JsonProperty("err_code")
+        private String errCode;
+        /**
+         * 发送序列号
+         */
+        @JsonProperty("biz_id")
+        private String bizId;
+        /**
+         * 用户序列号
+         *
+         * 这里我们传递的是 SysSmsLogDO 的日志编号
+         */
+        @JsonProperty("out_id")
+        private String outId;
+        /**
+         * 短信长度,例如说 1、2、3
+         *
+         * 140 字节算一条短信,短信长度超过 140 字节时会拆分成多条短信发送
+         */
+        @JsonProperty("sms_size")
+        private Integer smsSize;
 
-        //biz_id 即 apiId 唯一标识
-        String BIZ_ID = "biz_id";
     }
 
 }

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

@@ -1,8 +1,6 @@
 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.util.CharsetUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.core.util.URLUtil;
 import cn.iocoder.dashboard.common.core.KeyValue;
@@ -11,11 +9,9 @@ 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;
-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;
@@ -23,10 +19,11 @@ 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.*;
+import java.util.stream.Collectors;
+
+import static cn.iocoder.dashboard.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+import static cn.iocoder.dashboard.util.date.DateUtils.TIME_ZONE_DEFAULT;
 
 /**
  * 云片短信客户端的实现类
@@ -42,9 +39,6 @@ public class YunpianSmsClient extends AbstractSmsClient {
      */
     private volatile YunpianClient client;
 
-    private final TypeReference<List<Map<String, String>>> callbackType = new TypeReference<List<Map<String, String>>>() {
-    };
-
     public YunpianSmsClient(SmsChannelProperties properties) {
         super(properties, new YunpianSmsCodeMapping());
     }
@@ -105,28 +99,17 @@ public class YunpianSmsClient extends AbstractSmsClient {
         return sendResult.getMsg() + " => " + sendResult.getDetail();
     }
 
-    /**
-     * 从 request 中获取请求中传入的短信发送结果信息
-     *
-     * @param request 回调请求
-     * @return 短信发送结果信息
-     * @throws UnsupportedEncodingException 解码异常
-     */
-    private Map<String, String> getRequestParams(ServletRequest request) throws UnsupportedEncodingException {
-        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.parseObject(encode, callbackType);
-        if (CollectionUtil.isNotEmpty(paramList)) {
-            return paramList.get(0);
-        }
-        throw new IllegalArgumentException("YunpianSmsClient getRequestParams fail! can't format RequestParam: "
-                + JsonUtils.toJsonString(request.getParameterMap()));
-    }
-
     @Override
-    protected SmsCommonResult<SmsReceiveRespDTO> doParseSmsReceiveStatus(String text) throws Throwable {
-        return null;
+    protected List<SmsReceiveRespDTO> doParseSmsReceiveStatus(String text) throws Throwable {
+        List<SmsReceiveStatus> statuses = JsonUtils.parseArray(text, SmsReceiveStatus.class);
+        return statuses.stream().map(status -> {
+            SmsReceiveRespDTO resp = new SmsReceiveRespDTO();
+            resp.setSuccess(Objects.equals(status.getReportStatus(), "SUCCESS"));
+            resp.setErrorCode(status.getErrorMsg()).setErrorMsg(status.getErrorDetail());
+            resp.setMobile(status.getMobile()).setReceiveTime(status.getUserReceiveTime());
+            resp.setSerialNo(String.valueOf(status.getSid())).setLogId(status.getUid());
+            return resp;
+        }).collect(Collectors.toList());
     }
 
     /**
@@ -139,6 +122,24 @@ public class YunpianSmsClient extends AbstractSmsClient {
     @Data
     public static class SmsReceiveStatus {
 
+        /**
+         * 接收状态
+         *
+         * 目前仅有 SUCCESS / FAIL,所以使用 Boolean 接收
+         */
+        @JsonProperty("report_status")
+        private String reportStatus;
+        /**
+         * 接收手机号
+         */
+        private String mobile;
+        /**
+         * 运营商返回的代码,如:"DB:0103"
+         *
+         * 由于不同运营商信息不同,此字段仅供参考;
+         */
+        @JsonProperty("error_msg")
+        private String errorMsg;
         /**
          * 运营商反馈代码的中文解释
          *
@@ -160,26 +161,8 @@ public class YunpianSmsClient extends AbstractSmsClient {
          * 用户接收时间
          */
         @JsonProperty("user_receive_time")
-        @JsonFormat(pattern = DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+        @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
         private Date userReceiveTime;
-        /**
-         * 运营商返回的代码,如:"DB:0103"
-         *
-         * 由于不同运营商信息不同,此字段仅供参考;
-         */
-        @JsonProperty("error_msg")
-        private String errorMsg;
-        /**
-         * 接收手机号
-         */
-        private String mobile;
-        /**
-         * 接收状态
-         *
-         * 目前仅有 SUCCESS / FAIL,所以使用 Boolean 接收
-         */
-        @JsonProperty("report_status")
-        private String reportStatus;
 
     }
 

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

@@ -152,7 +152,7 @@ public class SysSmsServiceImpl implements SysSmsService {
         SmsClient smsClient = smsClientFactory.getSmsClient(channelCode);
         Assert.notNull(smsClient, String.format("短信客户端(%s) 不存在", channelCode));
         // 解析内容
-        SmsCommonResult<SmsReceiveRespDTO> receiveResult = smsClient.parseSmsReceiveStatus(text);
+        List<SmsReceiveRespDTO> receiveResults = smsClient.parseSmsReceiveStatus(text);
     }
 
 }

+ 5 - 0
src/main/java/cn/iocoder/dashboard/util/date/DateUtils.java

@@ -9,6 +9,11 @@ import java.util.Date;
  */
 public class DateUtils {
 
+    /**
+     * 时区 - 默认
+     */
+    public static final String TIME_ZONE_DEFAULT = "GMT+8";
+
     public static final String FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND = "yyyy-MM-dd HH:mm:ss";
 
     public static Date addTime(Duration duration) {

+ 0 - 7
src/main/java/cn/iocoder/dashboard/util/json/JsonUtils.java

@@ -2,7 +2,6 @@ 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;
@@ -80,10 +79,4 @@ public class JsonUtils {
         }
     }
 
-    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 → src/test-integration/java/cn/iocoder/dashboard/framework/sms/core/client/impl/aliyun/AliyunSmsClientIntegrationTest.java

@@ -13,7 +13,7 @@ import java.util.List;
 /**
  * {@link AliyunSmsClient} 的集成测试
  */
-public class AliyunSmsClientTest {
+public class AliyunSmsClientIntegrationTest {
 
     @Test
     public void testSend() {

+ 2 - 2
src/test-integration/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsServiceIntegrationTest.java

@@ -34,7 +34,7 @@ public class SysSmsServiceIntegrationTest extends BaseDbAndRedisIntegrationTest
     private SysUserService userService;
 
     @Test
-    public void testSendSingleSms_云片发送成功() {
+    public void testSendSingleSms_yunpianSuccess() {
         // 参数准备
         String mobile = "15601691399";
         Long userId = 1L;
@@ -50,7 +50,7 @@ public class SysSmsServiceIntegrationTest extends BaseDbAndRedisIntegrationTest
     }
 
     @Test
-    public void testSendSingleSms_阿里云发送成功() {
+    public void testSendSingleSms_aliyunSuccess() {
         // 参数准备
         String mobile = "15601691399";
         Long userId = 1L;

+ 1 - 0
src/test/java/cn/iocoder/dashboard/framework/package-info.java

@@ -0,0 +1 @@
+package cn.iocoder.dashboard.framework;

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

@@ -0,0 +1,60 @@
+package cn.iocoder.dashboard.framework.sms.core.client.impl.aliyun;
+
+import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsReceiveRespDTO;
+import cn.iocoder.dashboard.util.date.DateUtils;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * {@link AliyunSmsClient} 的单元测试
+ */
+public class AliyunSmsClientTest {
+
+    @InjectMocks
+    private final AliyunSmsClient smsClient = new AliyunSmsClient(null);
+
+    @Test
+    void doInit() {
+    }
+
+    @Test
+    void doSendSms() {
+    }
+
+    @Test
+    public void testDoTParseSmsReceiveStatus() throws Throwable {
+        // 准备参数
+        String text = "[\n" +
+                "  {\n" +
+                "    \"phone_number\" : \"13900000001\",\n" +
+                "    \"send_time\" : \"2017-01-01 11:12:13\",\n" +
+                "    \"report_time\" : \"2017-02-02 22:23:24\",\n" +
+                "    \"success\" : true,\n" +
+                "    \"err_code\" : \"DELIVERED\",\n" +
+                "    \"err_msg\" : \"用户接收成功\",\n" +
+                "    \"sms_size\" : \"1\",\n" +
+                "    \"biz_id\" : \"12345\",\n" +
+                "    \"out_id\" : \"67890\"\n" +
+                "  }\n" +
+                "]";
+        // mock 方法
+
+        // 调用
+        List<SmsReceiveRespDTO> statuses = smsClient.doParseSmsReceiveStatus(text);
+        // 断言
+        assertEquals(1, statuses.size());
+        assertTrue(statuses.get(0).getSuccess());
+        assertEquals("DELIVERED", statuses.get(0).getErrorCode());
+        assertEquals("用户接收成功", statuses.get(0).getErrorMsg());
+        assertEquals("13900000001", statuses.get(0).getMobile());
+        assertEquals(DateUtils.buildTime(2017, 2, 2, 22, 23, 24), statuses.get(0).getReceiveTime());
+        assertEquals("12345", statuses.get(0).getSerialNo());
+        assertEquals(67890L, statuses.get(0).getLogId());
+    }
+
+}

+ 50 - 0
src/test/java/cn/iocoder/dashboard/framework/sms/core/client/impl/yunpian/YunpianSmsClientTest.java

@@ -0,0 +1,50 @@
+package cn.iocoder.dashboard.framework.sms.core.client.impl.yunpian;
+
+import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsReceiveRespDTO;
+import cn.iocoder.dashboard.util.date.DateUtils;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * 对 {@link YunpianSmsClient} 的单元测试
+ */
+public class YunpianSmsClientTest {
+
+    @InjectMocks
+    private final YunpianSmsClient smsClient = new YunpianSmsClient(null);
+
+    @Test
+    void doInit() {
+    }
+
+    @Test
+    void doSendSms() {
+    }
+
+    @Test
+    void testDoParseSmsReceiveStatus() throws Throwable {
+        // 准备参数
+        String text = "[{\"sid\":9527,\"uid\":1024,\"user_receive_time\":\"2014-03-17 22:55:21\",\"error_msg\":\"\",\"mobile\":\"15205201314\",\"report_status\":\"SUCCESS\"}]";
+        // mock 方法
+
+        // 调用
+
+        // 断言
+        // 调用
+        List<SmsReceiveRespDTO> statuses = smsClient.doParseSmsReceiveStatus(text);
+        // 断言
+        assertEquals(1, statuses.size());
+        assertTrue(statuses.get(0).getSuccess());
+        assertEquals("", statuses.get(0).getErrorCode());
+        assertNull(statuses.get(0).getErrorMsg());
+        assertEquals("15205201314", statuses.get(0).getMobile());
+        assertEquals(DateUtils.buildTime(2014, 3, 17, 22, 55, 21), statuses.get(0).getReceiveTime());
+        assertEquals("9527", statuses.get(0).getSerialNo());
+        assertEquals(1024L, statuses.get(0).getLogId());
+    }
+
+}