Prechádzať zdrojové kódy

Merge branch 'master-jdk17' of https://gitee.com/zhijiantianya/ruoyi-vue-pro

# Conflicts:
#	yudao-dependencies/pom.xml
#	yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderSettlementRespVO.java
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointUsePriceCalculator.java
#	yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClient.java
YunaiV 10 mesiacov pred
rodič
commit
ae7323cbe0
41 zmenil súbory, kde vykonal 839 pridanie a 504 odobranie
  1. 1 32
      yudao-dependencies/pom.xml
  2. 20 0
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/http/HttpUtils.java
  3. 20 0
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/spring/SpringExpressionUtils.java
  4. 0 4
      yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java
  5. 5 0
      yudao-framework/yudao-spring-boot-starter-web/pom.xml
  6. 19 0
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/base/handler/DesensitizationHandler.java
  7. 8 0
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/annotation/EmailDesensitize.java
  8. 8 0
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/annotation/RegexDesensitize.java
  9. 8 0
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/handler/AbstractRegexDesensitizationHandler.java
  10. 6 0
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/handler/DefaultRegexDesensitizationHandler.java
  11. 7 0
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/BankCardDesensitize.java
  12. 7 0
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/CarLicenseDesensitize.java
  13. 7 0
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/ChineseNameDesensitize.java
  14. 7 0
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/FixedPhoneDesensitize.java
  15. 7 0
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/IdCardDesensitize.java
  16. 7 0
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/MobileDesensitize.java
  17. 7 0
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/PasswordDesensitize.java
  18. 8 0
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/SliderDesensitize.java
  19. 8 0
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/AbstractSliderDesensitizationHandler.java
  20. 5 0
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/BankCardDesensitization.java
  21. 7 0
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/CarLicenseDesensitization.java
  22. 2 0
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/DefaultDesensitizationHandler.java
  23. 2 0
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/FixedPhoneDesensitization.java
  24. 1 0
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/IdCardDesensitization.java
  25. 1 0
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/MobileDesensitization.java
  26. 1 0
      yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/PasswordDesensitization.java
  27. 2 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/coupon/CouponDO.java
  28. 9 5
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java
  29. 3 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/base/spu/AppProductSpuBaseRespVO.java
  30. 2 2
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderSettlementRespVO.java
  31. 5 1
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java
  32. 1 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java
  33. 5 5
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointUsePriceCalculator.java
  34. 0 13
      yudao-module-system/yudao-module-system-biz/pom.xml
  35. 2 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/SmsClient.java
  36. 130 113
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java
  37. 81 75
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClient.java
  38. 176 37
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClient.java
  39. 64 89
      yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClientTest.java
  40. 67 1
      yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java
  41. 113 127
      yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClientTest.java

+ 1 - 32
yudao-dependencies/pom.xml

@@ -69,10 +69,7 @@
         <okhttp3.version>4.11.0</okhttp3.version>
         <commons-io.version>2.15.1</commons-io.version>
         <minio.version>8.5.7</minio.version>
-        <aliyun-java-sdk-core.version>4.6.4</aliyun-java-sdk-core.version>
-        <aliyun-java-sdk-dysmsapi.version>2.2.1</aliyun-java-sdk-dysmsapi.version>
-        <tencentcloud-sdk-java.version>3.1.880</tencentcloud-sdk-java.version>
-        <justauth.version>1.0.8</justauth.version>
+        <justauth.version>2.0.5</justauth.version>
         <jimureport.version>1.7.8</jimureport.version>
         <xercesImpl.version>2.12.2</xercesImpl.version>
         <weixin-java.version>4.6.0</weixin-java.version>
@@ -558,34 +555,6 @@
                 <version>${minio.version}</version>
             </dependency>
 
-            <!-- SMS SDK begin -->
-            <dependency>
-                <groupId>com.aliyun</groupId>
-                <artifactId>aliyun-java-sdk-core</artifactId>
-                <version>${aliyun-java-sdk-core.version}</version>
-                <exclusions>
-                    <exclusion>
-                        <artifactId>opentracing-api</artifactId>
-                        <groupId>io.opentracing</groupId>
-                    </exclusion>
-                    <exclusion>
-                        <artifactId>opentracing-util</artifactId>
-                        <groupId>io.opentracing</groupId>
-                    </exclusion>
-                </exclusions>
-            </dependency>
-            <dependency>
-                <groupId>com.aliyun</groupId>
-                <artifactId>aliyun-java-sdk-dysmsapi</artifactId>
-                <version>${aliyun-java-sdk-dysmsapi.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>com.tencentcloudapi</groupId>
-                <artifactId>tencentcloud-sdk-java-sms</artifactId>
-                <version>${tencentcloud-sdk-java.version}</version>
-            </dependency>
-            <!-- SMS SDK end -->
-
             <dependency>
                 <groupId>com.xingyuv</groupId>
                 <artifactId>spring-boot-starter-justauth</artifactId> <!-- 社交登陆(例如说,个人微信、企业微信等等) -->

+ 20 - 0
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/http/HttpUtils.java

@@ -5,6 +5,8 @@ import cn.hutool.core.map.TableMap;
 import cn.hutool.core.net.url.UrlBuilder;
 import cn.hutool.core.util.ReflectUtil;
 import cn.hutool.core.util.StrUtil;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
 import org.springframework.util.StringUtils;
 import org.springframework.web.util.UriComponents;
 import org.springframework.web.util.UriComponentsBuilder;
@@ -122,5 +124,23 @@ public class HttpUtils {
         return null;
     }
 
+    /**
+     * HTTP post 请求,基于 {@link cn.hutool.http.HttpUtil} 实现
+     *
+     * 为什么要封装该方法,因为 HttpUtil 默认封装的方法,没有允许传递 headers 参数
+     *
+     * @param url URL
+     * @param headers 请求头
+     * @param requestBody 请求体
+     * @return 请求结果
+     */
+    public static String post(String url, Map<String, String> headers, String requestBody) {
+        try (HttpResponse response = HttpRequest.post(url)
+                .addHeaders(headers)
+                .body(requestBody)
+                .execute()) {
+            return response.body();
+        }
+    }
 
 }

+ 20 - 0
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/spring/SpringExpressionUtils.java

@@ -3,11 +3,15 @@ package cn.iocoder.yudao.framework.common.util.spring;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.extra.spring.SpringUtil;
 import org.aspectj.lang.JoinPoint;
 import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.context.expression.BeanFactoryResolver;
 import org.springframework.core.DefaultParameterNameDiscoverer;
 import org.springframework.core.ParameterNameDiscoverer;
 import org.springframework.expression.EvaluationContext;
+import org.springframework.expression.Expression;
 import org.springframework.expression.ExpressionParser;
 import org.springframework.expression.spel.standard.SpelExpressionParser;
 import org.springframework.expression.spel.support.StandardEvaluationContext;
@@ -86,4 +90,20 @@ public class SpringExpressionUtils {
         return result;
     }
 
+    /**
+     * 从 Bean 工厂,解析 EL 表达式的结果
+     *
+     * @param expressionString EL 表达式
+     * @return 执行界面
+     */
+    public static Object parseExpression(String expressionString) {
+        if (StrUtil.isBlank(expressionString)) {
+            return null;
+        }
+        Expression expression = EXPRESSION_PARSER.parseExpression(expressionString);
+        StandardEvaluationContext context = new StandardEvaluationContext();
+        context.setBeanResolver(new BeanFactoryResolver(SpringUtil.getApplicationContext()));
+        return expression.getValue(context);
+    }
+
 }

+ 0 - 4
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java

@@ -185,10 +185,6 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> {
         return Db.updateBatchById(entities, size);
     }
 
-    default boolean insertOrUpdate(T entity) {
-        return Db.saveOrUpdate(entity);
-    }
-
     default Boolean insertOrUpdateBatch(Collection<T> collection) {
         return Db.saveOrUpdateBatch(collection);
     }

+ 5 - 0
yudao-framework/yudao-spring-boot-starter-web/pom.xml

@@ -32,6 +32,11 @@
             <artifactId>spring-boot-configuration-processor</artifactId>
             <optional>true</optional>
         </dependency>
+        <dependency>
+            <groupId>org.aspectj</groupId>
+            <artifactId>aspectjweaver</artifactId>
+            <scope>provided</scope> <!-- 解决工具类 SpringExpressionUtils 加载的时候访问不到 org.aspectj.lang.JoinPoint 问题 -->
+        </dependency>
 
         <dependency>
             <groupId>com.github.xiaoymin</groupId>

+ 19 - 0
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/base/handler/DesensitizationHandler.java

@@ -1,5 +1,7 @@
 package cn.iocoder.yudao.framework.desensitize.core.base.handler;
 
+import cn.hutool.core.util.ReflectUtil;
+
 import java.lang.annotation.Annotation;
 
 /**
@@ -18,4 +20,21 @@ public interface DesensitizationHandler<T extends Annotation> {
      */
     String desensitize(String origin, T annotation);
 
+    /**
+     * 是否禁用脱敏的 Spring EL 表达式
+     *
+     * 如果返回 true 则跳过脱敏
+     *
+     * @param annotation 注解信息
+     * @return 是否禁用脱敏的 Spring EL 表达式
+     */
+    default String getDisable(T annotation) {
+        // 约定:默认就是 enable() 属性。如果不符合,子类重写
+        try {
+            return (String) ReflectUtil.invoke(annotation, "disable");
+        } catch (Exception ex) {
+            return "";
+        }
+    }
+
 }

+ 8 - 0
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/annotation/EmailDesensitize.java

@@ -33,4 +33,12 @@ public @interface EmailDesensitize {
      * 比如:example@gmail.com 脱敏之后为 e****@gmail.com
      */
     String replacer() default "$1****$2";
+
+    /**
+     * 是否禁用脱敏
+     *
+     * 支持 Spring EL 表达式,如果返回 true 则跳过脱敏
+     */
+    String disable() default "";
+
 }

+ 8 - 0
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/annotation/RegexDesensitize.java

@@ -35,4 +35,12 @@ public @interface RegexDesensitize {
      * 脱敏后字符串 ******456789
      */
     String replacer() default "******";
+
+    /**
+     * 是否禁用脱敏
+     *
+     * 支持 Spring EL 表达式,如果返回 true 则跳过脱敏
+     */
+    String disable() default "";
+
 }

+ 8 - 0
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/handler/AbstractRegexDesensitizationHandler.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.framework.desensitize.core.regex.handler;
 
+import cn.iocoder.yudao.framework.common.util.spring.SpringExpressionUtils;
 import cn.iocoder.yudao.framework.desensitize.core.base.handler.DesensitizationHandler;
 
 import java.lang.annotation.Annotation;
@@ -14,6 +15,13 @@ public abstract class AbstractRegexDesensitizationHandler<T extends Annotation>
 
     @Override
     public String desensitize(String origin, T annotation) {
+        // 1. 判断是否禁用脱敏
+        Object disable = SpringExpressionUtils.parseExpression(getDisable(annotation));
+        if (Boolean.TRUE.equals(disable)) {
+            return origin;
+        }
+
+        // 2. 执行脱敏
         String regex = getRegex(annotation);
         String replacer = getReplacer(annotation);
         return origin.replaceAll(regex, replacer);

+ 6 - 0
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/handler/DefaultRegexDesensitizationHandler.java

@@ -18,4 +18,10 @@ public class DefaultRegexDesensitizationHandler extends AbstractRegexDesensitiza
     String getReplacer(RegexDesensitize annotation) {
         return annotation.replacer();
     }
+
+    @Override
+    public String getDisable(RegexDesensitize annotation) {
+        return annotation.disable();
+    }
+
 }

+ 7 - 0
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/BankCardDesensitize.java

@@ -37,4 +37,11 @@ public @interface BankCardDesensitize {
      */
     String replacer() default "*";
 
+    /**
+     * 是否禁用脱敏
+     *
+     * 支持 Spring EL 表达式,如果返回 true 则跳过脱敏
+     */
+    String disable() default "";
+
 }

+ 7 - 0
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/CarLicenseDesensitize.java

@@ -37,4 +37,11 @@ public @interface CarLicenseDesensitize {
      */
     String replacer() default "*";
 
+    /**
+     * 是否禁用脱敏
+     *
+     * 支持 Spring EL 表达式,如果返回 true 则跳过脱敏
+     */
+    String disable() default "";
+
 }

+ 7 - 0
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/ChineseNameDesensitize.java

@@ -37,4 +37,11 @@ public @interface ChineseNameDesensitize {
      */
     String replacer() default "*";
 
+    /**
+     * 是否禁用脱敏
+     *
+     * 支持 Spring EL 表达式,如果返回 true 则跳过脱敏
+     */
+    String disable() default "";
+
 }

+ 7 - 0
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/FixedPhoneDesensitize.java

@@ -37,4 +37,11 @@ public @interface FixedPhoneDesensitize {
      */
     String replacer() default "*";
 
+    /**
+     * 是否禁用脱敏
+     *
+     * 支持 Spring EL 表达式,如果返回 true 则跳过脱敏
+     */
+    String disable() default "";
+
 }

+ 7 - 0
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/IdCardDesensitize.java

@@ -37,4 +37,11 @@ public @interface IdCardDesensitize {
      */
     String replacer() default "*";
 
+    /**
+     * 是否禁用脱敏
+     *
+     * 支持 Spring EL 表达式,如果返回 true 则跳过脱敏
+     */
+    String disable() default "";
+
 }

+ 7 - 0
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/MobileDesensitize.java

@@ -37,4 +37,11 @@ public @interface MobileDesensitize {
      */
     String replacer() default "*";
 
+    /**
+     * 是否禁用脱敏
+     *
+     * 支持 Spring EL 表达式,如果返回 true 则跳过脱敏
+     */
+    String disable() default "";
+
 }

+ 7 - 0
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/PasswordDesensitize.java

@@ -39,4 +39,11 @@ public @interface PasswordDesensitize {
      */
     String replacer() default "*";
 
+    /**
+     * 是否禁用脱敏
+     *
+     * 支持 Spring EL 表达式,如果返回 true 则跳过脱敏
+     */
+    String disable() default "";
+
 }

+ 8 - 0
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/SliderDesensitize.java

@@ -40,4 +40,12 @@ public @interface SliderDesensitize {
      * 前缀保留长度
      */
     int prefixKeep() default 0;
+
+    /**
+     * 是否禁用脱敏
+     *
+     * 支持 Spring EL 表达式,如果返回 true 则跳过脱敏
+     */
+    String disable() default "";
+
 }

+ 8 - 0
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/AbstractSliderDesensitizationHandler.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.framework.desensitize.core.slider.handler;
 
+import cn.iocoder.yudao.framework.common.util.spring.SpringExpressionUtils;
 import cn.iocoder.yudao.framework.desensitize.core.base.handler.DesensitizationHandler;
 
 import java.lang.annotation.Annotation;
@@ -14,6 +15,13 @@ public abstract class AbstractSliderDesensitizationHandler<T extends Annotation>
 
     @Override
     public String desensitize(String origin, T annotation) {
+        // 1. 判断是否禁用脱敏
+        Object disable = SpringExpressionUtils.parseExpression(getDisable(annotation));
+        if (Boolean.FALSE.equals(disable)) {
+            return origin;
+        }
+
+        // 2. 执行脱敏
         int prefixKeep = getPrefixKeep(annotation);
         int suffixKeep = getSuffixKeep(annotation);
         String replacer = getReplacer(annotation);

+ 5 - 0
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/BankCardDesensitization.java

@@ -24,4 +24,9 @@ public class BankCardDesensitization extends AbstractSliderDesensitizationHandle
         return annotation.replacer();
     }
 
+    @Override
+    public String getDisable(BankCardDesensitize annotation) {
+        return "";
+    }
+
 }

+ 7 - 0
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/CarLicenseDesensitization.java

@@ -8,6 +8,7 @@ import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.CarLicenseD
  * @author gaibu
  */
 public class CarLicenseDesensitization extends AbstractSliderDesensitizationHandler<CarLicenseDesensitize> {
+
     @Override
     Integer getPrefixKeep(CarLicenseDesensitize annotation) {
         return annotation.prefixKeep();
@@ -22,4 +23,10 @@ public class CarLicenseDesensitization extends AbstractSliderDesensitizationHand
     String getReplacer(CarLicenseDesensitize annotation) {
         return annotation.replacer();
     }
+
+    @Override
+    public String getDisable(CarLicenseDesensitize annotation) {
+        return annotation.disable();
+    }
+
 }

+ 2 - 0
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/DefaultDesensitizationHandler.java

@@ -8,6 +8,7 @@ import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.SliderDesen
  * @author gaibu
  */
 public class DefaultDesensitizationHandler extends AbstractSliderDesensitizationHandler<SliderDesensitize> {
+
     @Override
     Integer getPrefixKeep(SliderDesensitize annotation) {
         return annotation.prefixKeep();
@@ -22,4 +23,5 @@ public class DefaultDesensitizationHandler extends AbstractSliderDesensitization
     String getReplacer(SliderDesensitize annotation) {
         return annotation.replacer();
     }
+
 }

+ 2 - 0
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/FixedPhoneDesensitization.java

@@ -8,6 +8,7 @@ import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.FixedPhoneD
  * @author gaibu
  */
 public class FixedPhoneDesensitization extends AbstractSliderDesensitizationHandler<FixedPhoneDesensitize> {
+
     @Override
     Integer getPrefixKeep(FixedPhoneDesensitize annotation) {
         return annotation.prefixKeep();
@@ -22,4 +23,5 @@ public class FixedPhoneDesensitization extends AbstractSliderDesensitizationHand
     String getReplacer(FixedPhoneDesensitize annotation) {
         return annotation.replacer();
     }
+
 }

+ 1 - 0
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/IdCardDesensitization.java

@@ -22,4 +22,5 @@ public class IdCardDesensitization extends AbstractSliderDesensitizationHandler<
     String getReplacer(IdCardDesensitize annotation) {
         return annotation.replacer();
     }
+
 }

+ 1 - 0
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/MobileDesensitization.java

@@ -23,4 +23,5 @@ public class MobileDesensitization extends AbstractSliderDesensitizationHandler<
     String getReplacer(MobileDesensitize annotation) {
         return annotation.replacer();
     }
+
 }

+ 1 - 0
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/PasswordDesensitization.java

@@ -22,4 +22,5 @@ public class PasswordDesensitization extends AbstractSliderDesensitizationHandle
     String getReplacer(PasswordDesensitize annotation) {
         return annotation.replacer();
     }
+
 }

+ 2 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/coupon/CouponDO.java

@@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum;
 import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
 import com.baomidou.mybatisplus.annotation.KeySequence;
 import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
@@ -30,6 +31,7 @@ public class CouponDO extends BaseDO {
     /**
      * 优惠劵编号
      */
+    @TableId
     private Long id;
     /**
      * 优惠劵模板编号

+ 9 - 5
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java

@@ -24,7 +24,8 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
-import javax.annotation.Resource;
+import jakarta.annotation.Resource;
+
 import java.time.LocalDateTime;
 import java.util.*;
 import java.util.stream.Collectors;
@@ -132,7 +133,7 @@ public class CouponServiceImpl implements CouponService {
     @Transactional
     public void deleteCoupon(Long id) {
         // 校验存在
-        validateCouponExists(id);
+        CouponDO coupon = validateCouponExists(id);
 
         // 更新优惠劵
         int deleteCount = couponMapper.delete(id,
@@ -140,8 +141,9 @@ public class CouponServiceImpl implements CouponService {
         if (deleteCount == 0) {
             throw exception(COUPON_DELETE_FAIL_USED);
         }
+
         // 减少优惠劵模板的领取数量 -1
-        couponTemplateService.updateCouponTemplateTakeCount(id, -1);
+        couponTemplateService.updateCouponTemplateTakeCount(coupon.getTemplateId(), -1);
     }
 
     @Override
@@ -149,10 +151,12 @@ public class CouponServiceImpl implements CouponService {
         return couponMapper.selectListByUserIdAndStatus(userId, status);
     }
 
-    private void validateCouponExists(Long id) {
-        if (couponMapper.selectById(id) == null) {
+    private CouponDO validateCouponExists(Long id) {
+        CouponDO coupon = couponMapper.selectById(id);
+        if (coupon == null) {
             throw exception(COUPON_NOT_EXISTS);
         }
+        return coupon;
     }
 
     @Override

+ 3 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/base/spu/AppProductSpuBaseRespVO.java

@@ -22,4 +22,7 @@ public class AppProductSpuBaseRespVO {
     @Schema(description = "商品主图地址", example = "https://www.iocoder.cn/xx.png")
     private String picUrl;
 
+    @Schema(description = "商品分类编号", example = "1")
+    private Long categoryId;
+
 }

+ 2 - 2
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderSettlementRespVO.java

@@ -2,11 +2,11 @@ package cn.iocoder.yudao.module.trade.controller.app.order.vo;
 
 import cn.iocoder.yudao.module.trade.controller.app.base.property.AppProductPropertyValueDetailRespVO;
 import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
 import lombok.AllArgsConstructor;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 
-import javax.validation.constraints.NotNull;
 import java.util.List;
 
 @Schema(description = "用户 App - 交易订单结算信息 Response VO")
@@ -26,7 +26,7 @@ public class AppTradeOrderSettlementRespVO {
     private Address address;
 
     @Schema(description = "已使用的积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
-    private Integer usedPoint;
+    private Integer usePoint;
 
     @Schema(description = "总积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
     private Integer totalPoint;

+ 5 - 1
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java

@@ -48,13 +48,17 @@ public class TradePriceCalculateRespBO {
      */
     private Long couponId;
 
+    /**
+     * 会员剩余积分
+     */
+    private Integer totalPoint;
     /**
      * 使用的积分
      */
     private Integer usePoint;
 
     /**
-     * 使用的积分
+     * 赠送的积分
      */
     private Integer givePoint;
 

+ 1 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java

@@ -55,6 +55,7 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
         if (param.getDeliveryType() == null) {
             return;
         }
+        // TODO @puhui999:需要校验,是不是存在商品不能门店自提,或者不能快递发货的情况。就是说,配送方式不匹配哈
         if (DeliveryTypeEnum.PICK_UP.getType().equals(param.getDeliveryType())) {
             calculateByPickUp(param);
         } else if (DeliveryTypeEnum.EXPRESS.getType().equals(param.getDeliveryType())) {

+ 5 - 5
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointUsePriceCalculator.java

@@ -9,11 +9,11 @@ import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
 import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
+import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.core.annotation.Order;
 import org.springframework.stereotype.Component;
 
-import javax.annotation.Resource;
 import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -37,11 +37,12 @@ public class TradePointUsePriceCalculator implements TradePriceCalculator {
 
     @Override
     public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
-        // 默认使用积分为 0
-        result.setUsePoint(0);
+        // 0. 初始化积分
+        MemberUserRespDTO user = memberUserApi.getUser(param.getUserId());
+        result.setTotalPoint(user.getPoint()).setUsePoint(0);
+
         // 1.1 校验是否使用积分
         if (!BooleanUtil.isTrue(param.getPointStatus())) {
-            result.setUsePoint(0);
             return;
         }
         // 1.2 校验积分抵扣是否开启
@@ -50,7 +51,6 @@ public class TradePointUsePriceCalculator implements TradePriceCalculator {
             return;
         }
         // 1.3 校验用户积分余额
-        MemberUserRespDTO user = memberUserApi.getUser(param.getUserId());
         if (user.getPoint() == null || user.getPoint() <= 0) {
             return;
         }

+ 0 - 13
yudao-module-system/yudao-module-system-biz/pom.xml

@@ -110,19 +110,6 @@
             <artifactId>wx-java-miniapp-spring-boot-starter</artifactId>  <!-- 微信登录(小程序) -->
         </dependency>
 
-        <dependency>
-            <groupId>com.aliyun</groupId>
-            <artifactId>aliyun-java-sdk-core</artifactId> <!-- 短信(阿里云) -->
-        </dependency>
-        <dependency>
-            <groupId>com.aliyun</groupId>
-            <artifactId>aliyun-java-sdk-dysmsapi</artifactId> <!-- 短信(阿里云) -->
-        </dependency>
-        <dependency>
-            <groupId>com.tencentcloudapi</groupId>
-            <artifactId>tencentcloud-sdk-java-sms</artifactId> <!-- 短信(腾讯云) -->
-        </dependency>
-
         <dependency>
             <groupId>com.xingyuv</groupId>
             <artifactId>spring-boot-starter-captcha-plus</artifactId> <!-- 验证码,一般用于登录使用 -->

+ 2 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/SmsClient.java

@@ -46,6 +46,8 @@ public interface SmsClient {
     /**
      * 查询指定的短信模板
      *
+     * 如果查询失败,则返回 null 空
+     *
      * @param apiTemplateId 短信 API 的模板编号
      * @return 短信模板
      */

+ 130 - 113
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java

@@ -1,35 +1,33 @@
 package cn.iocoder.yudao.module.system.framework.sms.core.client.impl;
 
+import cn.hutool.core.date.format.FastDateFormat;
 import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.crypto.SecureUtil;
+import cn.hutool.crypto.digest.DigestUtil;
+import cn.hutool.json.JSONArray;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
 import cn.iocoder.yudao.framework.common.core.KeyValue;
 import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
+import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
 import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;
 import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
 import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO;
 import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
 import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties;
-import com.aliyuncs.DefaultAcsClient;
-import com.aliyuncs.IAcsClient;
-import com.aliyuncs.dysmsapi.model.v20170525.QuerySmsTemplateRequest;
-import com.aliyuncs.dysmsapi.model.v20170525.QuerySmsTemplateResponse;
-import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
-import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
-import com.aliyuncs.profile.DefaultProfile;
-import com.aliyuncs.profile.IClientProfile;
-import com.fasterxml.jackson.annotation.JsonFormat;
-import com.fasterxml.jackson.annotation.JsonProperty;
 import com.google.common.annotations.VisibleForTesting;
-import lombok.Data;
+import lombok.SneakyThrows;
 import lombok.extern.slf4j.Slf4j;
 
-import java.time.LocalDateTime;
-import java.util.List;
-import java.util.Objects;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+import java.util.stream.Collectors;
 
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
-import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
-import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT;
 
 /**
  * 阿里短信客户端的实现类
@@ -40,20 +38,11 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DE
 @Slf4j
 public class AliyunSmsClient extends AbstractSmsClient {
 
-    /**
-     * 调用成功 code
-     */
-    public static final String API_CODE_SUCCESS = "OK";
-
-    /**
-     * REGION, 使用杭州
-     */
-    private static final String ENDPOINT = "cn-hangzhou";
+    private static final String URL = "https://dysmsapi.aliyuncs.com";
+    private static final String HOST = "dysmsapi.aliyuncs.com";
+    private static final String VERSION = "2017-05-25";
 
-    /**
-     * 阿里云客户端
-     */
-    private volatile IAcsClient client;
+    private static final String RESPONSE_CODE_SUCCESS = "OK";
 
     public AliyunSmsClient(SmsChannelProperties properties) {
         super(properties);
@@ -63,47 +52,70 @@ public class AliyunSmsClient extends AbstractSmsClient {
 
     @Override
     protected void doInit() {
-        IClientProfile profile = DefaultProfile.getProfile(ENDPOINT, properties.getApiKey(), properties.getApiSecret());
-        client = new DefaultAcsClient(profile);
     }
 
     @Override
     public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId,
                                   List<KeyValue<String, Object>> templateParams) throws Throwable {
-        // 构建请求
-        SendSmsRequest request = new SendSmsRequest();
-        request.setPhoneNumbers(mobile);
-        request.setSignName(properties.getSignature());
-        request.setTemplateCode(apiTemplateId);
-        request.setTemplateParam(JsonUtils.toJsonString(MapUtils.convertMap(templateParams)));
-        request.setOutId(String.valueOf(sendLogId));
-        // 执行请求
-        SendSmsResponse response = client.getAcsResponse(request);
-        return new SmsSendRespDTO().setSuccess(Objects.equals(response.getCode(), API_CODE_SUCCESS)).setSerialNo(response.getBizId())
-                .setApiRequestId(response.getRequestId()).setApiCode(response.getCode()).setApiMsg(response.getMessage());
+        Assert.notBlank(properties.getSignature(), "短信签名不能为空");
+        // 1. 执行请求
+        // 参考链接 https://api.aliyun.com/document/Dysmsapi/2017-05-25/SendSms
+        TreeMap<String, Object> queryParam = new TreeMap<>();
+        queryParam.put("PhoneNumbers", mobile);
+        queryParam.put("SignName", properties.getSignature());
+        queryParam.put("TemplateCode", apiTemplateId);
+        queryParam.put("TemplateParam", JsonUtils.toJsonString(MapUtils.convertMap(templateParams)));
+        queryParam.put("OutId", sendLogId);
+        JSONObject response = request("SendSms", queryParam);
+
+        // 2. 解析请求
+        return new SmsSendRespDTO()
+                .setSuccess(Objects.equals(response.getStr("Code"), RESPONSE_CODE_SUCCESS))
+                .setSerialNo(response.getStr("BizId"))
+                .setApiRequestId(response.getStr("RequestId"))
+                .setApiCode(response.getStr("Code"))
+                .setApiMsg(response.getStr("Message"));
     }
 
     @Override
     public List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) {
-        List<SmsReceiveStatus> statuses = JsonUtils.parseArray(text, SmsReceiveStatus.class);
-        return convertList(statuses, status -> new SmsReceiveRespDTO().setSuccess(status.getSuccess())
-                .setErrorCode(status.getErrCode()).setErrorMsg(status.getErrMsg())
-                .setMobile(status.getPhoneNumber()).setReceiveTime(status.getReportTime())
-                .setSerialNo(status.getBizId()).setLogId(Long.valueOf(status.getOutId())));
+        JSONArray statuses = JSONUtil.parseArray(text);
+        // 字段参考
+        return convertList(statuses, status -> {
+            JSONObject statusObj = (JSONObject) status;
+            return new SmsReceiveRespDTO()
+                    .setSuccess(statusObj.getBool("success")) // 是否接收成功
+                    .setErrorCode(statusObj.getStr("err_code")) // 状态报告编码
+                    .setErrorMsg(statusObj.getStr("err_msg")) // 状态报告说明
+                    .setMobile(statusObj.getStr("phone_number")) // 手机号
+                    .setReceiveTime(statusObj.getLocalDateTime("report_time", null)) // 状态报告时间
+                    .setSerialNo(statusObj.getStr("biz_id")) // 发送序列号
+                    .setLogId(statusObj.getLong("out_id")); // 用户序列号
+        });
     }
 
     @Override
     public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable {
-        // 构建请求
-        QuerySmsTemplateRequest request = new QuerySmsTemplateRequest();
-        request.setTemplateCode(apiTemplateId);
-        // 执行请求
-        QuerySmsTemplateResponse response = client.getAcsResponse(request);
-        if (response.getTemplateStatus() == null) {
+        // 1. 执行请求
+        // 参考链接 https://api.aliyun.com/document/Dysmsapi/2017-05-25/QuerySmsTemplate
+        TreeMap<String, Object> queryParam = new TreeMap<>();
+        queryParam.put("TemplateCode", apiTemplateId);
+        JSONObject response = request("QuerySmsTemplate", queryParam);
+
+        System.out.println("getSmsTemplate response is =====" + response.toString());
+
+        // 2.1 请求失败
+        String code = response.getStr("Code");
+        if (ObjectUtil.notEqual(code, RESPONSE_CODE_SUCCESS)) {
+            log.error("[getSmsTemplate][模版编号({}) 响应不正确({})]", apiTemplateId, response);
             return null;
         }
-        return new SmsTemplateRespDTO().setId(response.getTemplateCode()).setContent(response.getTemplateContent())
-                .setAuditStatus(convertSmsTemplateAuditStatus(response.getTemplateStatus())).setAuditReason(response.getReason());
+        // 2.2 请求成功
+        return new SmsTemplateRespDTO()
+                .setId(response.getStr("TemplateCode"))
+                .setContent(response.getStr("TemplateContent"))
+                .setAuditStatus(convertSmsTemplateAuditStatus(response.getInt("TemplateStatus")))
+                .setAuditReason(response.getStr("Reason"));
     }
 
     @VisibleForTesting
@@ -117,66 +129,71 @@ public class AliyunSmsClient extends AbstractSmsClient {
     }
 
     /**
-     * 短信接收状态
-     *
-     * 参见 <a href="https://help.aliyun.com/document_detail/101867.html">文档</a>
+     * 请求阿里云短信
      *
-     * @author 芋道源码
+     * @see <a href="https://help.aliyun.com/zh/sdk/product-overview/v3-request-structure-and-signature">V3 版本请求体&签名机制</>
+     * @param apiName 请求的 API 名称
+     * @param queryParams 请求参数
+     * @return 请求结果
      */
-    @Data
-    public static class SmsReceiveStatus {
-
-        /**
-         * 手机号
-         */
-        @JsonProperty("phone_number")
-        private String phoneNumber;
-        /**
-         * 发送时间
-         */
-        @JsonProperty("send_time")
-        @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
-        private LocalDateTime sendTime;
-        /**
-         * 状态报告时间
-         */
-        @JsonProperty("report_time")
-        @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
-        private LocalDateTime 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;
+    private JSONObject request(String apiName, TreeMap<String, Object> queryParams) {
+        // 1. 请求参数
+        String queryString = queryParams.entrySet().stream()
+                .map(entry -> percentCode(entry.getKey()) + "=" + percentCode(String.valueOf(entry.getValue())))
+                .collect(Collectors.joining("&"));
+
+        // 2.1 请求 Header
+        TreeMap<String, String> headers = new TreeMap<>();
+        headers.put("host", HOST);
+        headers.put("x-acs-version", VERSION);
+        headers.put("x-acs-action", apiName);
+        headers.put("x-acs-date", FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss'Z'", TimeZone.getTimeZone("GMT")).format(new Date()));
+        headers.put("x-acs-signature-nonce", IdUtil.randomUUID());
+
+        // 2.2 构建签名 Header
+        StringBuilder canonicalHeaders = new StringBuilder(); // 构造请求头,多个规范化消息头,按照消息头名称(小写)的字符代码顺序以升序排列后拼接在一起
+        StringBuilder signedHeadersBuilder = new StringBuilder(); // 已签名消息头列表,多个请求头名称(小写)按首字母升序排列并以英文分号(;)分隔
+        headers.entrySet().stream().filter(entry -> entry.getKey().toLowerCase().startsWith("x-acs-")
+                        || entry.getKey().equalsIgnoreCase("host")
+                        || entry.getKey().equalsIgnoreCase("content-type"))
+                .sorted(Map.Entry.comparingByKey()).forEach(entry -> {
+                    String lowerKey = entry.getKey().toLowerCase();
+                    canonicalHeaders.append(lowerKey).append(":").append(String.valueOf(entry.getValue()).trim()).append("\n");
+                    signedHeadersBuilder.append(lowerKey).append(";");
+                });
+        String signedHeaders = signedHeadersBuilder.substring(0, signedHeadersBuilder.length() - 1);
+
+        // 3. 请求 Body
+        String requestBody = ""; // 短信 API 为 RPC 接口,query parameters 在 uri 中拼接,因此 request body 如果没有特殊要求,设置为空。
+        String hashedRequestBody = DigestUtil.sha256Hex(requestBody);
+
+        // 4. 构建 Authorization 签名
+        String canonicalRequest = "POST" + "\n" + "/" + "\n" + queryString + "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestBody;
+        String hashedCanonicalRequest = DigestUtil.sha256Hex(canonicalRequest);
+
+        String stringToSign = "ACS3-HMAC-SHA256" + "\n" + hashedCanonicalRequest;
+        String signature = SecureUtil.hmacSha256(properties.getApiSecret()).digestHex(stringToSign); // 计算签名
+        headers.put("Authorization", "ACS3-HMAC-SHA256" + " " + "Credential=" + properties.getApiKey()
+                + ", " + "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature);
+
+        // 5. 发起请求
+        String responseBody = HttpUtils.post(URL + "?" + queryString, headers, requestBody);
+        return JSONUtil.parseObj(responseBody);
+    }
 
+    /**
+     * 对指定的字符串进行 URL 编码,并对特定的字符进行替换,以符合URL编码规范
+     *
+     * @param str 需要进行 URL 编码的字符串
+     * @return 编码后的字符串
+     */
+    @SneakyThrows
+    private static String percentCode(String str) {
+        Assert.notNull(str, "str 不能为空");
+        return URLEncoder.encode(str, StandardCharsets.UTF_8.name())
+                .replace("+", "%20") // 加号 "+" 被替换为 "%20"
+                .replace("*", "%2A") // 星号 "*" 被替换为 "%2A"
+                .replace("%7E", "~"); // 波浪号 "%7E" 被替换为 "~"
     }
 
-}
+}

+ 81 - 75
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClient.java

@@ -1,40 +1,43 @@
 package cn.iocoder.yudao.module.system.framework.sms.core.client.impl;
 
+
 import cn.hutool.core.lang.Assert;
-import cn.hutool.core.util.HexUtil;
 import cn.hutool.core.util.StrUtil;
+
 import cn.hutool.crypto.SecureUtil;
-import cn.hutool.crypto.digest.DigestUtil;
-import cn.hutool.json.JSONArray;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
 import cn.iocoder.yudao.framework.common.core.KeyValue;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
 import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;
 import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
 import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO;
 import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
 import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties;
+
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import lombok.Data;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.http.HttpResponse;
-import org.apache.http.client.methods.HttpUriRequest;
-import org.apache.http.client.methods.RequestBuilder;
-import org.apache.http.entity.StringEntity;
-import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.impl.client.HttpClientBuilder;
 
 import java.io.UnsupportedEncodingException;
 import java.net.URLEncoder;
-import java.nio.charset.StandardCharsets;
 import java.text.SimpleDateFormat;
-import java.time.LocalDateTime;
 import java.util.*;
 
+
+import java.time.LocalDateTime;
+
+
+import static cn.hutool.crypto.digest.DigestUtil.sha256Hex;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT;
 
+
 /**
  * 华为短信客户端的实现类
  *
@@ -47,7 +50,14 @@ public class HuaweiSmsClient extends AbstractSmsClient {
     /**
      * 调用成功 code
      */
-    public static final String API_CODE_SUCCESS = "OK";
+    public static final String URL = "https://smsapi.cn-north-4.myhuaweicloud.com:443/sms/batchSendSms/v1";//APP接入地址+接口访问URI
+    public static final String HOST = "smsapi.cn-north-4.myhuaweicloud.com:443";
+    public static final String SIGNEDHEADERS = "content-type;host;x-sdk-date";
+
+    @Override
+    protected void doInit() {
+
+    }
 
     public HuaweiSmsClient(SmsChannelProperties properties) {
         super(properties);
@@ -55,96 +65,79 @@ public class HuaweiSmsClient extends AbstractSmsClient {
         Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空");
     }
 
-    @Override
-    protected void doInit() {
-    }
-
     @Override
     public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId,
                                   List<KeyValue<String, Object>> templateParams) throws Throwable {
-        // TODO @scholar:https://smsapi.cn-north-4.myhuaweicloud.com:443 是不是枚举成静态变量
-        String url = "https://smsapi.cn-north-4.myhuaweicloud.com:443/sms/batchSendSms/v1"; //APP接入地址+接口访问URI
         // 相比较阿里短信,华为短信发送的时候需要额外的参数“通道号”,考虑到不破坏原有的的结构
         // 所以将 通道号 拼接到 apiTemplateId 字段中,格式为 "apiTemplateId 通道号"。空格为分隔符。
-        // TODO @scholar:暂时只考虑中国大陆,所以不需要 sender 哈
         String sender = StrUtil.subAfter(apiTemplateId, " ", true); //中国大陆短信签名通道号或全球短信通道号
         String templateId = StrUtil.subBefore(apiTemplateId, " ", true); //模板ID
 
-        // 选填,短信状态报告接收地址,推荐使用域名,为空或者不填表示不接收状态报告
+        //选填,短信状态报告接收地址,推荐使用域名,为空或者不填表示不接收状态报告
         String statusCallBack = properties.getCallbackUrl();
 
-        // TODO @scholar:1)是不是用  LocalDateTimeUtil.format();这样 3 行变成一行
-        // TODO @scholar:singerDate 叫 sdkDate 会更合适哈,这样理解起来简单。另外,singer 应该是 signed 么?
+        List<String> templateParas = CollectionUtils.convertList(templateParams, kv -> String.valueOf(kv.getValue()));
+
+        JSONObject JsonResponse = sendSmsRequest(sender,mobile,templateId,templateParas,statusCallBack);
+        SmsResponse smsResponse = getSmsSendResponse(JsonResponse);
+
+        return new SmsSendRespDTO().setSuccess(smsResponse.success).setApiMsg(smsResponse.data.toString());
+    }
+
+    JSONObject sendSmsRequest(String sender,String mobile,String templateId,List<String> templateParas,String statusCallBack) throws UnsupportedEncodingException {
+
         SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'", Locale.ENGLISH);
         sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
-        String singerDate = sdf.format(new Date());
+        String sdkDate = sdf.format(new Date());
 
-        // TODO @scholar:整个处理加密的过程,是不是应该抽成一个 private 方法哈。这样整个调用的主干更清晰。
         // ************* 步骤 1:拼接规范请求串 *************
         String httpRequestMethod = "POST";
         String canonicalUri = "/sms/batchSendSms/v1/";
-        String canonicalQueryString = ""; // 查询参数为空
+        String canonicalQueryString = "";//查询参数为空
         String canonicalHeaders = "content-type:application/x-www-form-urlencoded\n"
-                + "host:smsapi.cn-north-4.myhuaweicloud.com:443\n"
-                + "x-sdk-date:" + singerDate + "\n";
-        // TODO @scholar:静态枚举了
-        String signedHeaders = "content-type;host;x-sdk-date";
-        // TODO @scholar:下面的注释,可以考虑去掉
-        /*
-         * 选填,使用无变量模板时请赋空值 String templateParas = "";
-         * 单变量模板示例:模板内容为"您的验证码是${NUM_6}"时,templateParas可填写为"[\"111111\"]"
-         * 双变量模板示例:模板内容为"您有${NUM_2}件快递请到${TXT_20}领取"时,templateParas可填写为"[\"3\",\"人民公园正门\"]"
-         */
-        // TODO @scholar:CollectionUtils.convertList 可以把 4 行变成 1 行。
-        // TODO @scholar:templateParams 拼写错误哈
-        List<String> templateParas = new ArrayList<>();
-        for (KeyValue<String, Object> kv : templateParams) {
-            templateParas.add(String.valueOf(kv.getValue()));
-        }
-
-        // 请求Body,不携带签名名称时,signature请填null
+                + "host:"+ HOST +"\n"
+                + "x-sdk-date:" + sdkDate + "\n";
+        //请求Body,不携带签名名称时,signature请填null
         String body = buildRequestBody(sender, mobile, templateId, templateParas, statusCallBack, null);
-        // TODO @scholar:Assert 断言,抛出异常
         if (null == body || body.isEmpty()) {
             return null;
         }
-        String hashedRequestBody = HexUtil.encodeHexStr(DigestUtil.sha256(body));
+        String hashedRequestBody = sha256Hex(body);
         String canonicalRequest = httpRequestMethod + "\n" + canonicalUri + "\n" + canonicalQueryString + "\n"
-                + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestBody;
+                + canonicalHeaders + "\n" + SIGNEDHEADERS + "\n" + hashedRequestBody;
 
         // ************* 步骤 2:拼接待签名字符串 *************
-        // TODO @scholar:sha256Hex 是不是更简洁哈
-        String hashedCanonicalRequest = HexUtil.encodeHexStr(DigestUtil.sha256(canonicalRequest));
-        String stringToSign = "SDK-HMAC-SHA256" + "\n" + singerDate + "\n" + hashedCanonicalRequest;
+        String hashedCanonicalRequest = sha256Hex(canonicalRequest);
+        String stringToSign = "SDK-HMAC-SHA256" + "\n" + sdkDate + "\n" + hashedCanonicalRequest;
 
         // ************* 步骤 3:计算签名 *************
         String signature = SecureUtil.hmacSha256(properties.getApiSecret()).digestHex(stringToSign);
 
         // ************* 步骤 4:拼接 Authorization *************
         String authorization = "SDK-HMAC-SHA256" + " " + "Access=" + properties.getApiKey() + ", "
-                + "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature;
+                + "SignedHeaders=" + SIGNEDHEADERS + ", " + "Signature=" + signature;
 
         // ************* 步骤 5:构造HttpRequest 并执行request请求,获得response *************
-        // TODO @scholar:考虑了下,还是换 hutool 的 httpUtils。因为未来 httpclient 我们可能会移除掉
-        HttpUriRequest postMethod = RequestBuilder.post()
-                .setUri(url)
-                .setEntity(new StringEntity(body, StandardCharsets.UTF_8))
-                .setHeader("Content-Type","application/x-www-form-urlencoded")
-                .setHeader("X-Sdk-Date", singerDate)
-                .setHeader("Authorization", authorization)
-                .build();
-        // TODO @scholar:这种不太适合一直 new 的哈
-        CloseableHttpClient client = HttpClientBuilder.create().build();
-        HttpResponse response = client.execute(postMethod);
-        // TODO @scholar:失败的情况下的处理
-        // TODO @scholar:setSerialNo(Integer.toString(response.getStatusLine().getStatusCode())) 这部分,空一行。一行代码太多了,阅读性不太好哈
-        return new SmsSendRespDTO().setSuccess(Objects.equals(response.getStatusLine().getReasonPhrase(), API_CODE_SUCCESS)).setSerialNo(Integer.toString(response.getStatusLine().getStatusCode()))
-                .setApiRequestId(null).setApiCode(null).setApiMsg(null);
+        HttpResponse response = HttpRequest.post(URL)
+                .header("Content-Type", "application/x-www-form-urlencoded")
+                .header("X-Sdk-Date", sdkDate)
+                .header("host",HOST)
+                .header("Authorization", authorization)
+                .body(body)
+                .execute();
+
+        return JSONUtil.parseObj(response.body());
+    }
+
+    private SmsResponse getSmsSendResponse(JSONObject resJson) {
+        SmsResponse smsResponse = new SmsResponse();
+        smsResponse.setSuccess("000000".equals(resJson.getStr("code")));
+        smsResponse.setData(resJson);
+        return smsResponse;
     }
 
     static String buildRequestBody(String sender, String receiver, String templateId, List<String> templateParas,
-                                   String statusCallBack, @SuppressWarnings("SameParameterValue") String signature) throws UnsupportedEncodingException {
-        // TODO @scholar:参数不满足,是不是抛出异常更好哈;通过 hutool 的 Assert 去断言
+                                   String statusCallBack, String signature) throws UnsupportedEncodingException {
         if (null == sender || null == receiver || null == templateId || sender.isEmpty() || receiver.isEmpty()
                 || templateId.isEmpty()) {
             System.out.println("buildRequestBody(): sender, receiver or templateId is null.");
@@ -155,20 +148,17 @@ public class HuaweiSmsClient extends AbstractSmsClient {
         appendToBody(body, "from=", sender);
         appendToBody(body, "&to=", receiver);
         appendToBody(body, "&templateId=", templateId);
-        // TODO @scholar:new JSONArray(templateParas).toString(),是不是 JsonUtils.toString 呀?
-        appendToBody(body, "&templateParas=", new JSONArray(templateParas).toString());
+        appendToBody(body, "&templateParas=", JsonUtils.toJsonString(templateParas));
         appendToBody(body, "&statusCallback=", statusCallBack);
         appendToBody(body, "&signature=", signature);
         return body.toString();
     }
 
     private static void appendToBody(StringBuilder body, String key, String val) throws UnsupportedEncodingException {
-        // TODO @scholar:StrUtils.isNotEmpty(val),是不是更简洁哈
         if (null != val && !val.isEmpty()) {
-            body.append(key).append(URLEncoder.encode(val, StandardCharsets.UTF_8.name()));
+            body.append(key).append(URLEncoder.encode(val, "UTF-8"));
         }
     }
-
     @Override
     public List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) {
         List<SmsReceiveStatus> statuses = JsonUtils.parseArray(text, SmsReceiveStatus.class);
@@ -180,12 +170,28 @@ public class HuaweiSmsClient extends AbstractSmsClient {
 
     @Override
     public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable {
-        // 华为短信模板查询和发送短信,是不同的两套 key 和 secret,与阿里、腾讯的区别较大,这里模板查询校验暂不实现
-        // 对应文档 https://support.huaweicloud.com/api-msgsms/sms_05_0040.html
-        return new SmsTemplateRespDTO().setId(apiTemplateId).setContent(null)
+        //华为短信模板查询和发送短信,是不同的两套key和secret,与阿里、腾讯的区别较大,这里模板查询校验暂不实现。
+        return new SmsTemplateRespDTO().setId(null).setContent(null)
                 .setAuditStatus(SmsTemplateAuditStatusEnum.SUCCESS.getStatus()).setAuditReason(null);
+
+    }
+
+    @Data
+    public static class SmsResponse {
+
+        /**
+         * 是否成功
+         */
+        private boolean success;
+
+        /**
+         * 厂商原返回体
+         */
+        private Object data;
+
     }
 
+
     /**
      * 短信接收状态
      *

+ 176 - 37
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClient.java

@@ -2,6 +2,11 @@ package cn.iocoder.yudao.module.system.framework.sms.core.client.impl;
 
 import cn.hutool.core.lang.Assert;
 import cn.hutool.core.util.StrUtil;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
+import cn.hutool.json.JSONArray;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
 import cn.iocoder.yudao.framework.common.core.KeyValue;
 import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
@@ -13,19 +18,22 @@ import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProp
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.google.common.annotations.VisibleForTesting;
-import com.tencentcloudapi.common.Credential;
-import com.tencentcloudapi.sms.v20210111.SmsClient;
-import com.tencentcloudapi.sms.v20210111.models.*;
+import jakarta.xml.bind.DatatypeConverter;
 import lombok.Data;
 
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.charset.StandardCharsets;
+import java.text.SimpleDateFormat;
 import java.time.LocalDateTime;
-import java.util.List;
-import java.util.Objects;
+import java.util.*;
 
+import static cn.hutool.crypto.digest.DigestUtil.sha256Hex;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT;
 
+// TODO @scholar 建议参考 AliyunSmsClient 优化下
 /**
  * 腾讯云短信功能实现
  *
@@ -40,11 +48,6 @@ public class TencentSmsClient extends AbstractSmsClient {
      */
     public static final String API_CODE_SUCCESS = "Ok";
 
-    /**
-     * REGION,使用南京
-     */
-    private static final String ENDPOINT = "ap-nanjing";
-
     /**
      * 是否国际/港澳台短信:
      *
@@ -53,7 +56,6 @@ public class TencentSmsClient extends AbstractSmsClient {
      */
     private static final long INTERNATIONAL_CHINA = 0L;
 
-    private SmsClient client;
 
     public TencentSmsClient(SmsChannelProperties properties) {
         super(properties);
@@ -63,9 +65,7 @@ public class TencentSmsClient extends AbstractSmsClient {
 
     @Override
     protected void doInit() {
-        // 实例化一个认证对象,入参需要传入腾讯云账户密钥对 secretId,secretKey
-        Credential credential = new Credential(getApiKey(), properties.getApiSecret());
-        client = new SmsClient(credential, ENDPOINT);
+
     }
 
     /**
@@ -96,18 +96,87 @@ public class TencentSmsClient extends AbstractSmsClient {
     public SmsSendRespDTO sendSms(Long sendLogId, String mobile,
                                   String apiTemplateId, List<KeyValue<String, Object>> templateParams) throws Throwable {
         // 构建请求
-        SendSmsRequest request = new SendSmsRequest();
-        request.setSmsSdkAppId(getSdkAppId());
-        request.setPhoneNumberSet(new String[]{mobile});
-        request.setSignName(properties.getSignature());
-        request.setTemplateId(apiTemplateId);
-        request.setTemplateParamSet(ArrayUtils.toArray(templateParams, e -> String.valueOf(e.getValue())));
-        request.setSessionContext(JsonUtils.toJsonString(new SessionContext().setLogId(sendLogId)));
-        // 执行请求
-        SendSmsResponse response = client.SendSms(request);
-        SendStatus status = response.getSendStatusSet()[0];
-        return new SmsSendRespDTO().setSuccess(Objects.equals(status.getCode(), API_CODE_SUCCESS)).setSerialNo(status.getSerialNo())
-                .setApiRequestId(response.getRequestId()).setApiCode(status.getCode()).setApiMsg(status.getMessage());
+        TreeMap<String, Object> body = new TreeMap<>();
+        String[] phones = {mobile};
+        body.put("PhoneNumberSet",phones);
+        body.put("SmsSdkAppId",getSdkAppId());
+        body.put("SignName",properties.getSignature());
+        body.put("TemplateId",apiTemplateId);
+        body.put("TemplateParamSet",ArrayUtils.toArray(templateParams, e -> String.valueOf(e.getValue())));
+
+        JSONObject JsonResponse = sendSmsRequest(body,"SendSms","2021-01-11","ap-guangzhou");
+        SmsResponse smsResponse = getSmsSendResponse(JsonResponse);
+
+        return new SmsSendRespDTO().setSuccess(smsResponse.success).setApiMsg(smsResponse.data.toString());
+
+    }
+
+    JSONObject sendSmsRequest(TreeMap<String, Object> body,String action,String version,String region) throws Exception {
+
+        String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+        // 注意时区,否则容易出错
+        sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
+        String date = sdf.format(new Date(Long.valueOf(timestamp + "000")));
+
+        // ************* 步骤 1:拼接规范请求串 *************
+        String host = "sms.tencentcloudapi.com"; //APP接入地址+接口访问URI
+        String httpMethod = "POST"; // 请求方式
+        String canonicalUri = "/";
+        String canonicalQueryString = "";
+
+        String canonicalHeaders = "content-type:application/json; charset=utf-8\n"
+                + "host:" + host + "\n" + "x-tc-action:" + action.toLowerCase() + "\n";
+        String signedHeaders = "content-type;host;x-tc-action";
+        String hashedRequestBody = sha256Hex(JSONUtil.toJsonStr(body));
+        String canonicalRequest = httpMethod + "\n" + canonicalUri + "\n" + canonicalQueryString + "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestBody;
+
+        // ************* 步骤 2:拼接待签名字符串 *************
+        String credentialScope = date + "/" + "sms" + "/" + "tc3_request";
+        String hashedCanonicalRequest = sha256Hex(canonicalRequest);
+        String stringToSign = "TC3-HMAC-SHA256" + "\n" + timestamp + "\n" + credentialScope + "\n" + hashedCanonicalRequest;
+
+        // ************* 步骤 3:计算签名 *************
+        byte[] secretDate = hmac256(("TC3" + properties.getApiSecret()).getBytes(StandardCharsets.UTF_8), date);
+        byte[] secretService = hmac256(secretDate, "sms");
+        byte[] secretSigning = hmac256(secretService, "tc3_request");
+        String signature = DatatypeConverter.printHexBinary(hmac256(secretSigning, stringToSign)).toLowerCase();
+
+        // ************* 步骤 4:拼接 Authorization *************
+        String authorization = "TC3-HMAC-SHA256" + " " + "Credential=" + getApiKey() + "/" + credentialScope + ", "
+                + "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature;
+
+        // ************* 步骤 5:构造HttpRequest 并执行request请求,获得response *************
+        Map<String, String> headers = new HashMap<>();
+        headers.put("Authorization", authorization);
+        headers.put("Content-Type", "application/json; charset=utf-8");
+        headers.put("Host", host);
+        headers.put("X-TC-Action", action);
+        headers.put("X-TC-Timestamp", timestamp);
+        headers.put("X-TC-Version", version);
+        headers.put("X-TC-Region", region);
+
+        HttpResponse response = HttpRequest.post("https://"+host)
+                .addHeaders(headers)
+                .body(JSONUtil.toJsonStr(body))
+                .execute();
+
+        return JSONUtil.parseObj(response.body());
+    }
+
+    public static byte[] hmac256(byte[] key, String msg) throws Exception {
+        Mac mac = Mac.getInstance("HmacSHA256");
+        SecretKeySpec secretKeySpec = new SecretKeySpec(key, mac.getAlgorithm());
+        mac.init(secretKeySpec);
+        return mac.doFinal(msg.getBytes(StandardCharsets.UTF_8));
+    }
+
+    private SmsResponse getSmsSendResponse(JSONObject resJson) {
+        SmsResponse smsResponse = new SmsResponse();
+        JSONArray statusJson =resJson.getJSONObject("Response").getJSONArray("SendStatusSet");
+        smsResponse.setSuccess("Ok".equals(statusJson.getJSONObject(0).getStr("Code")));
+        smsResponse.setData(resJson);
+        return smsResponse;
     }
 
     @Override
@@ -122,18 +191,49 @@ public class TencentSmsClient extends AbstractSmsClient {
 
     @Override
     public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable {
+
         // 构建请求
-        DescribeSmsTemplateListRequest request = new DescribeSmsTemplateListRequest();
-        request.setTemplateIdSet(new Long[]{Long.parseLong(apiTemplateId)});
-        request.setInternational(INTERNATIONAL_CHINA);
-        // 执行请求
-        DescribeSmsTemplateListResponse response = client.DescribeSmsTemplateList(request);
-        DescribeTemplateListStatus status = response.getDescribeTemplateStatusSet()[0];
-        if (status == null || status.getStatusCode() == null) {
-            return null;
-        }
-        return new SmsTemplateRespDTO().setId(status.getTemplateId().toString()).setContent(status.getTemplateContent())
-                .setAuditStatus(convertSmsTemplateAuditStatus(status.getStatusCode().intValue())).setAuditReason(status.getReviewReply());
+        TreeMap<String, Object> body = new TreeMap<>();
+        body.put("International",0);
+        Integer[] templateIds = {Integer.valueOf(apiTemplateId)};
+        body.put("TemplateIdSet",templateIds);
+
+        JSONObject JsonResponse = sendSmsRequest(body,"DescribeSmsTemplateList","2021-01-11","ap-guangzhou");
+        QuerySmsTemplateResponse smsTemplateResponse = getSmsTemplateResponse(JsonResponse);
+        String templateId = Integer.toString(smsTemplateResponse.getDescribeTemplateStatusSet().get(0).getTemplateId());
+        String content = smsTemplateResponse.getDescribeTemplateStatusSet().get(0).getTemplateContent();
+        Integer templateStatus = smsTemplateResponse.getDescribeTemplateStatusSet().get(0).getStatusCode();
+        String auditReason = smsTemplateResponse.getDescribeTemplateStatusSet().get(0).getReviewReply();
+
+        return new SmsTemplateRespDTO().setId(templateId).setContent(content)
+                .setAuditStatus(convertSmsTemplateAuditStatus(templateStatus)).setAuditReason(auditReason);
+    }
+
+    private QuerySmsTemplateResponse getSmsTemplateResponse(JSONObject resJson) {
+
+        QuerySmsTemplateResponse smsTemplateResponse = new QuerySmsTemplateResponse();
+
+        smsTemplateResponse.setRequestId(resJson.getJSONObject("Response").getStr("RequestId"));
+
+        smsTemplateResponse.setDescribeTemplateStatusSet(new ArrayList<>());
+
+        QuerySmsTemplateResponse.TemplateInfo templateInfo = new QuerySmsTemplateResponse.TemplateInfo();
+
+        Object statusObject = resJson.getJSONObject("Response").getJSONArray("DescribeTemplateStatusSet").get(0);
+
+        JSONObject statusJSON = new JSONObject(statusObject);
+
+        templateInfo.setTemplateContent(statusJSON.get("TemplateContent").toString());
+
+        templateInfo.setStatusCode(Integer.parseInt(statusJSON.get("StatusCode").toString()));
+
+        templateInfo.setReviewReply(statusJSON.get("ReviewReply").toString());
+
+        templateInfo.setTemplateId(Integer.parseInt(statusJSON.get("TemplateId").toString()));
+
+        smsTemplateResponse.getDescribeTemplateStatusSet().add(templateInfo);
+
+        return smsTemplateResponse;
     }
 
     @VisibleForTesting
@@ -146,6 +246,45 @@ public class TencentSmsClient extends AbstractSmsClient {
         }
     }
 
+    @Data
+    public static class SmsResponse {
+
+        /**
+         * 是否成功
+         */
+        private boolean success;
+
+        /**
+         * 厂商原返回体
+         */
+        private Object data;
+
+    }
+
+
+    /**
+     * <p>类名: QuerySmsTemplateResponse
+     * <p>说明:  sms模板查询返回信息
+     *
+     * @author :scholar
+     * 2024/07/17  0:25
+     **/
+    @Data
+    public static class QuerySmsTemplateResponse {
+        private List<TemplateInfo> DescribeTemplateStatusSet;
+        private String RequestId;
+        @Data
+        static class TemplateInfo {
+            private String TemplateName;
+            private Integer TemplateId;
+            private Integer International;
+            private String ReviewReply;
+            private long CreateTime;
+            private String TemplateContent;
+            private Integer StatusCode;
+        }
+    }
+
     @Data
     private static class SmsReceiveStatus {
 

+ 64 - 89
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClientTest.java

@@ -1,36 +1,30 @@
 package cn.iocoder.yudao.module.system.framework.sms.core.client.impl;
 
-import cn.hutool.core.util.ReflectUtil;
 import cn.iocoder.yudao.framework.common.core.KeyValue;
-import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
+import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
 import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;
 import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
 import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO;
 import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
 import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties;
-import com.aliyuncs.IAcsClient;
-import com.aliyuncs.dysmsapi.model.v20170525.QuerySmsTemplateRequest;
-import com.aliyuncs.dysmsapi.model.v20170525.QuerySmsTemplateResponse;
-import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
-import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
 import com.google.common.collect.Lists;
 import org.junit.jupiter.api.Test;
-import org.mockito.ArgumentMatcher;
 import org.mockito.InjectMocks;
-import org.mockito.Mock;
+import org.mockito.MockedStatic;
 
 import java.time.LocalDateTime;
 import java.util.List;
 
-import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
-import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
 import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.Mockito.when;
+import static org.mockito.ArgumentMatchers.anyMap;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mockStatic;
 
 /**
- * {@link AliyunSmsClient} 的单元测试
+ * {@link cn.iocoder.yudao.module.system.framework.sms.core.client.impl.AliyunSmsClient} 的单元测试
  *
  * @author 芋道源码
  */
@@ -44,9 +38,6 @@ public class AliyunSmsClientTest extends BaseMockitoUnitTest {
     @InjectMocks
     private final AliyunSmsClient smsClient = new AliyunSmsClient(properties);
 
-    @Mock
-    private IAcsClient client;
-
     @Test
     public void testDoInit() {
         // 准备参数
@@ -54,67 +45,55 @@ public class AliyunSmsClientTest extends BaseMockitoUnitTest {
 
         // 调用
         smsClient.doInit();
-        // 断言
-        assertNotSame(client, ReflectUtil.getFieldValue(smsClient, "acsClient"));
     }
 
     @Test
     public void tesSendSms_success() throws Throwable {
-        // 准备参数
-        Long sendLogId = randomLongId();
-        String mobile = randomString();
-        String apiTemplateId = randomString();
-        List<KeyValue<String, Object>> templateParams = Lists.newArrayList(
-                new KeyValue<>("code", 1234), new KeyValue<>("op", "login"));
-        // mock 方法
-        SendSmsResponse response = randomPojo(SendSmsResponse.class, o -> o.setCode("OK"));
-        when(client.getAcsResponse(argThat((ArgumentMatcher<SendSmsRequest>) acsRequest -> {
-            assertEquals(mobile, acsRequest.getPhoneNumbers());
-            assertEquals(properties.getSignature(), acsRequest.getSignName());
-            assertEquals(apiTemplateId, acsRequest.getTemplateCode());
-            assertEquals(toJsonString(MapUtils.convertMap(templateParams)), acsRequest.getTemplateParam());
-            assertEquals(sendLogId.toString(), acsRequest.getOutId());
-            return true;
-        }))).thenReturn(response);
-
-        // 调用
-        SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile,
-                apiTemplateId, templateParams);
-        // 断言
-        assertTrue(result.getSuccess());
-        assertEquals(response.getRequestId(), result.getApiRequestId());
-        assertEquals(response.getCode(), result.getApiCode());
-        assertEquals(response.getMessage(), result.getApiMsg());
-        assertEquals(response.getBizId(), result.getSerialNo());
+        try (MockedStatic<HttpUtils> httpUtilsMockedStatic = mockStatic(HttpUtils.class)) {
+            // 准备参数
+            Long sendLogId = randomLongId();
+            String mobile = randomString();
+            String apiTemplateId = randomString();
+            List<KeyValue<String, Object>> templateParams = Lists.newArrayList(
+                    new KeyValue<>("code", 1234), new KeyValue<>("op", "login"));
+            // mock 方法
+            httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString()))
+                    .thenReturn("{\"Message\":\"OK\",\"RequestId\":\"30067CE9-3710-5984-8881-909B21D8DB28\",\"Code\":\"OK\",\"BizId\":\"800025323183427988\"}");
+
+            // 调用
+            SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile,
+                    apiTemplateId, templateParams);
+            // 断言
+            assertTrue(result.getSuccess());
+            assertEquals("30067CE9-3710-5984-8881-909B21D8DB28", result.getApiRequestId());
+            assertEquals("OK", result.getApiCode());
+            assertEquals("OK", result.getApiMsg());
+            assertEquals("800025323183427988", result.getSerialNo());
+        }
     }
 
     @Test
     public void tesSendSms_fail() throws Throwable {
-        // 准备参数
-        Long sendLogId = randomLongId();
-        String mobile = randomString();
-        String apiTemplateId = randomString();
-        List<KeyValue<String, Object>> templateParams = Lists.newArrayList(
-                new KeyValue<>("code", 1234), new KeyValue<>("op", "login"));
-        // mock 方法
-        SendSmsResponse response = randomPojo(SendSmsResponse.class, o -> o.setCode("ERROR"));
-        when(client.getAcsResponse(argThat((ArgumentMatcher<SendSmsRequest>) acsRequest -> {
-            assertEquals(mobile, acsRequest.getPhoneNumbers());
-            assertEquals(properties.getSignature(), acsRequest.getSignName());
-            assertEquals(apiTemplateId, acsRequest.getTemplateCode());
-            assertEquals(toJsonString(MapUtils.convertMap(templateParams)), acsRequest.getTemplateParam());
-            assertEquals(sendLogId.toString(), acsRequest.getOutId());
-            return true;
-        }))).thenReturn(response);
-
-        // 调用
-        SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, apiTemplateId, templateParams);
-        // 断言
-        assertFalse(result.getSuccess());
-        assertEquals(response.getRequestId(), result.getApiRequestId());
-        assertEquals(response.getCode(), result.getApiCode());
-        assertEquals(response.getMessage(), result.getApiMsg());
-        assertEquals(response.getBizId(), result.getSerialNo());
+        try (MockedStatic<HttpUtils> httpUtilsMockedStatic = mockStatic(HttpUtils.class)) {
+            // 准备参数
+            Long sendLogId = randomLongId();
+            String mobile = randomString();
+            String apiTemplateId = randomString();
+            List<KeyValue<String, Object>> templateParams = Lists.newArrayList(
+                    new KeyValue<>("code", 1234), new KeyValue<>("op", "login"));
+            // mock 方法
+            httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString()))
+                    .thenReturn("{\"Message\":\"手机号码格式错误\",\"RequestId\":\"B7700B8E-227E-5886-9564-26036172F01F\",\"Code\":\"isv.MOBILE_NUMBER_ILLEGAL\"}");
+
+            // 调用
+            SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, apiTemplateId, templateParams);
+            // 断言
+            assertFalse(result.getSuccess());
+            assertEquals("B7700B8E-227E-5886-9564-26036172F01F", result.getApiRequestId());
+            assertEquals("isv.MOBILE_NUMBER_ILLEGAL", result.getApiCode());
+            assertEquals("手机号码格式错误", result.getApiMsg());
+            assertNull(result.getSerialNo());
+        }
     }
 
     @Test
@@ -151,25 +130,21 @@ public class AliyunSmsClientTest extends BaseMockitoUnitTest {
 
     @Test
     public void testGetSmsTemplate() throws Throwable {
-        // 准备参数
-        String apiTemplateId = randomString();
-        // mock 方法
-        QuerySmsTemplateResponse response = randomPojo(QuerySmsTemplateResponse.class, o -> {
-            o.setCode("OK");
-            o.setTemplateStatus(1); // 设置模板通过
-        });
-        when(client.getAcsResponse(argThat((ArgumentMatcher<QuerySmsTemplateRequest>) acsRequest -> {
-            assertEquals(apiTemplateId, acsRequest.getTemplateCode());
-            return true;
-        }))).thenReturn(response);
-
-        // 调用
-        SmsTemplateRespDTO result = smsClient.getSmsTemplate(apiTemplateId);
-        // 断言
-        assertEquals(response.getTemplateCode(), result.getId());
-        assertEquals(response.getTemplateContent(), result.getContent());
-        assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getAuditStatus());
-        assertEquals(response.getReason(), result.getAuditReason());
+        try (MockedStatic<HttpUtils> httpUtilsMockedStatic = mockStatic(HttpUtils.class)) {
+            // 准备参数
+            String apiTemplateId = randomString();
+            // mock 方法
+            httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString()))
+                    .thenReturn("{\"TemplateCode\":\"SMS_207945135\",\"RequestId\":\"6F4CC077-29C8-5BA5-AB62-5FF95068A5AC\",\"Message\":\"OK\",\"TemplateContent\":\"您的验证码${code},该验证码5分钟内有效,请勿泄漏于他人!\",\"TemplateName\":\"公告通知\",\"TemplateType\":0,\"Code\":\"OK\",\"CreateDate\":\"2020-12-23 17:34:42\",\"Reason\":\"无审批备注\",\"TemplateStatus\":1}");
+
+            // 调用
+            SmsTemplateRespDTO result = smsClient.getSmsTemplate(apiTemplateId);
+            // 断言
+            assertEquals("SMS_207945135", result.getId());
+            assertEquals("您的验证码${code},该验证码5分钟内有效,请勿泄漏于他人!", result.getContent());
+            assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getAuditStatus());
+            assertEquals("无审批备注", result.getAuditReason());
+        }
     }
 
     @Test

+ 67 - 1
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java

@@ -2,7 +2,9 @@ package cn.iocoder.yudao.module.system.framework.sms.core.client.impl;
 
 import cn.hutool.core.collection.ListUtil;
 import cn.iocoder.yudao.framework.common.core.KeyValue;
+import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;
 import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
+import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO;
 import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties;
 import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
@@ -18,7 +20,7 @@ public class SmsClientTests {
 
     @Test
     @Disabled
-    public void testHuaweiSmsClient() throws Throwable {
+    public void testHuaweiSmsClient_sendSms() throws Throwable {
         SmsChannelProperties properties = new SmsChannelProperties()
                 .setApiKey("123")
                 .setApiSecret("456");
@@ -34,4 +36,68 @@ public class SmsClientTests {
         System.out.println(smsSendRespDTO);
     }
 
+    // ========== 阿里云 ==========
+
+    @Test
+    @Disabled
+    public void testAliyunSmsClient_getSmsTemplate() throws Throwable {
+        SmsChannelProperties properties = new SmsChannelProperties()
+                .setApiKey("LTAI5tAicJAxaSFiZuGGeXHR")
+                .setApiSecret("Fdr9vadxnDvS6GJU0W1tijQ0VmLhYz");
+        AliyunSmsClient client = new AliyunSmsClient(properties);
+        // 准备参数
+        String apiTemplateId = "SMS_207945135";
+        // 调用
+        SmsTemplateRespDTO template = client.getSmsTemplate(apiTemplateId);
+        // 打印结果
+        System.out.println(template);
+    }
+
+    @Test
+    @Disabled
+    public void testAliyunSmsClient_sendSms() throws Throwable {
+        SmsChannelProperties properties = new SmsChannelProperties()
+                .setApiKey("LTAI5tAicJAxaSFiZuGGeXHR")
+                .setApiSecret("Fdr9vadxnDvS6GJU0W1tijQ0VmLhYz")
+                .setSignature("Ballcat");
+        AliyunSmsClient client = new AliyunSmsClient(properties);
+        // 准备参数
+        Long sendLogId = System.currentTimeMillis();
+        String mobile = "173213154791";
+        String apiTemplateId = "SMS_207945135";
+        // 调用
+        SmsSendRespDTO sendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, List.of(new KeyValue<>("code", "1024")));
+        // 打印结果
+        System.out.println(sendRespDTO);
+    }
+
+    @Test
+    @Disabled
+    public void testAliyunSmsClient_parseSmsReceiveStatus() {
+        SmsChannelProperties properties = new SmsChannelProperties()
+                .setApiKey("LTAI5tAicJAxaSFiZuGGeXHR")
+                .setApiSecret("Fdr9vadxnDvS6GJU0W1tijQ0VmLhYz");
+        AliyunSmsClient client = new AliyunSmsClient(properties);
+        // 准备参数
+        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 = client.parseSmsReceiveStatus(text);
+        // 打印结果
+        System.out.println(statuses);
+    }
+
 }

+ 113 - 127
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClientTest.java

@@ -1,36 +1,22 @@
 package cn.iocoder.yudao.module.system.framework.sms.core.client.impl;
 
 import cn.hutool.core.util.ReflectUtil;
-import cn.iocoder.yudao.framework.common.core.KeyValue;
-import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
-import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
-import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
+import cn.iocoder.yudao.module.system.framework.sms.core.client.SmsClient;
 import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;
-import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
-import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO;
 import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
 import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties;
-import com.google.common.collect.Lists;
-import com.tencentcloudapi.sms.v20210111.SmsClient;
-import com.tencentcloudapi.sms.v20210111.models.DescribeSmsTemplateListResponse;
-import com.tencentcloudapi.sms.v20210111.models.DescribeTemplateListStatus;
-import com.tencentcloudapi.sms.v20210111.models.SendSmsResponse;
-import com.tencentcloudapi.sms.v20210111.models.SendStatus;
 import org.junit.jupiter.api.Test;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
 
 import java.time.LocalDateTime;
-import java.util.ArrayList;
 import java.util.List;
 
-import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
-import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
 import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.Mockito.when;
 
+// TODO @芋艿:补全单测
 /**
  * {@link TencentSmsClient} 的单元测试
  *
@@ -73,87 +59,87 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest {
         assertNotSame(client, ReflectUtil.getFieldValue(smsClient, "client"));
     }
 
-    @Test
-    public void testDoSendSms_success() throws Throwable {
-        // 准备参数
-        Long sendLogId = randomLongId();
-        String mobile = randomString();
-        String apiTemplateId = randomString();
-        List<KeyValue<String, Object>> templateParams = Lists.newArrayList(
-                new KeyValue<>("1", 1234), new KeyValue<>("2", "login"));
-        String requestId = randomString();
-        String serialNo = randomString();
-        // mock 方法
-        SendSmsResponse response = randomPojo(SendSmsResponse.class, o -> {
-            o.setRequestId(requestId);
-            SendStatus[] sendStatuses = new SendStatus[1];
-            o.setSendStatusSet(sendStatuses);
-            SendStatus sendStatus = new SendStatus();
-            sendStatuses[0] = sendStatus;
-            sendStatus.setCode(TencentSmsClient.API_CODE_SUCCESS);
-            sendStatus.setMessage("send success");
-            sendStatus.setSerialNo(serialNo);
-        });
-        when(client.SendSms(argThat(request -> {
-            assertEquals(mobile, request.getPhoneNumberSet()[0]);
-            assertEquals(properties.getSignature(), request.getSignName());
-            assertEquals(apiTemplateId, request.getTemplateId());
-            assertEquals(toJsonString(ArrayUtils.toArray(new ArrayList<>(MapUtils.convertMap(templateParams).values()), String::valueOf)),
-                    toJsonString(request.getTemplateParamSet()));
-            assertEquals(sendLogId, ReflectUtil.getFieldValue(JsonUtils.parseObject(request.getSessionContext(), TencentSmsClient.SessionContext.class), "logId"));
-            return true;
-        }))).thenReturn(response);
-
-        // 调用
-        SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, apiTemplateId, templateParams);
-        // 断言
-        assertTrue(result.getSuccess());
-        assertEquals(response.getRequestId(), result.getApiRequestId());
-        assertEquals(response.getSendStatusSet()[0].getCode(), result.getApiCode());
-        assertEquals(response.getSendStatusSet()[0].getMessage(), result.getApiMsg());
-        assertEquals(response.getSendStatusSet()[0].getSerialNo(), result.getSerialNo());
-    }
-
-    @Test
-    public void testDoSendSms_fail() throws Throwable {
-        // 准备参数
-        Long sendLogId = randomLongId();
-        String mobile = randomString();
-        String apiTemplateId = randomString();
-        List<KeyValue<String, Object>> templateParams = Lists.newArrayList(
-                new KeyValue<>("1", 1234), new KeyValue<>("2", "login"));
-        String requestId = randomString();
-        String serialNo = randomString();
-        // mock 方法
-        SendSmsResponse response = randomPojo(SendSmsResponse.class, o -> {
-            o.setRequestId(requestId);
-            SendStatus[] sendStatuses = new SendStatus[1];
-            o.setSendStatusSet(sendStatuses);
-            SendStatus sendStatus = new SendStatus();
-            sendStatuses[0] = sendStatus;
-            sendStatus.setCode("ERROR");
-            sendStatus.setMessage("send success");
-            sendStatus.setSerialNo(serialNo);
-        });
-        when(client.SendSms(argThat(request -> {
-            assertEquals(mobile, request.getPhoneNumberSet()[0]);
-            assertEquals(properties.getSignature(), request.getSignName());
-            assertEquals(apiTemplateId, request.getTemplateId());
-            assertEquals(toJsonString(ArrayUtils.toArray(new ArrayList<>(MapUtils.convertMap(templateParams).values()), String::valueOf)),
-                    toJsonString(request.getTemplateParamSet()));
-            assertEquals(sendLogId, ReflectUtil.getFieldValue(JsonUtils.parseObject(request.getSessionContext(), TencentSmsClient.SessionContext.class), "logId"));
-            return true;
-        }))).thenReturn(response);
-
-        // 调用
-        SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, apiTemplateId, templateParams);
-        // 断言
-        assertFalse(result.getSuccess());
-        assertEquals(response.getRequestId(), result.getApiRequestId());
-        assertEquals(response.getSendStatusSet()[0].getCode(), result.getApiCode());
-        assertEquals(response.getSendStatusSet()[0].getMessage(), result.getApiMsg());
-        assertEquals(response.getSendStatusSet()[0].getSerialNo(), result.getSerialNo());
-    }
+//    @Test
+//    public void testDoSendSms_success() throws Throwable {
+//        // 准备参数
+//        Long sendLogId = randomLongId();
+//        String mobile = randomString();
+//        String apiTemplateId = randomString();
+//        List<KeyValue<String, Object>> templateParams = Lists.newArrayList(
+//                new KeyValue<>("1", 1234), new KeyValue<>("2", "login"));
+//        String requestId = randomString();
+//        String serialNo = randomString();
+//        // mock 方法
+//        SendSmsResponse response = randomPojo(SendSmsResponse.class, o -> {
+//            o.setRequestId(requestId);
+//            SendStatus[] sendStatuses = new SendStatus[1];
+//            o.setSendStatusSet(sendStatuses);
+//            SendStatus sendStatus = new SendStatus();
+//            sendStatuses[0] = sendStatus;
+//            sendStatus.setCode(TencentSmsClient.API_CODE_SUCCESS);
+//            sendStatus.setMessage("send success");
+//            sendStatus.setSerialNo(serialNo);
+//        });
+//        when(client.SendSms(argThat(request -> {
+//            assertEquals(mobile, request.getPhoneNumberSet()[0]);
+//            assertEquals(properties.getSignature(), request.getSignName());
+//            assertEquals(apiTemplateId, request.getTemplateId());
+//            assertEquals(toJsonString(ArrayUtils.toArray(new ArrayList<>(MapUtils.convertMap(templateParams).values()), String::valueOf)),
+//                    toJsonString(request.getTemplateParamSet()));
+//            assertEquals(sendLogId, ReflectUtil.getFieldValue(JsonUtils.parseObject(request.getSessionContext(), TencentSmsClient.SessionContext.class), "logId"));
+//            return true;
+//        }))).thenReturn(response);
+//
+//        // 调用
+//        SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, apiTemplateId, templateParams);
+//        // 断言
+//        assertTrue(result.getSuccess());
+//        assertEquals(response.getRequestId(), result.getApiRequestId());
+//        assertEquals(response.getSendStatusSet()[0].getCode(), result.getApiCode());
+//        assertEquals(response.getSendStatusSet()[0].getMessage(), result.getApiMsg());
+//        assertEquals(response.getSendStatusSet()[0].getSerialNo(), result.getSerialNo());
+//    }
+
+//    @Test
+//    public void testDoSendSms_fail() throws Throwable {
+//        // 准备参数
+//        Long sendLogId = randomLongId();
+//        String mobile = randomString();
+//        String apiTemplateId = randomString();
+//        List<KeyValue<String, Object>> templateParams = Lists.newArrayList(
+//                new KeyValue<>("1", 1234), new KeyValue<>("2", "login"));
+//        String requestId = randomString();
+//        String serialNo = randomString();
+//        // mock 方法
+//        SendSmsResponse response = randomPojo(SendSmsResponse.class, o -> {
+//            o.setRequestId(requestId);
+//            SendStatus[] sendStatuses = new SendStatus[1];
+//            o.setSendStatusSet(sendStatuses);
+//            SendStatus sendStatus = new SendStatus();
+//            sendStatuses[0] = sendStatus;
+//            sendStatus.setCode("ERROR");
+//            sendStatus.setMessage("send success");
+//            sendStatus.setSerialNo(serialNo);
+//        });
+//        when(client.SendSms(argThat(request -> {
+//            assertEquals(mobile, request.getPhoneNumberSet()[0]);
+//            assertEquals(properties.getSignature(), request.getSignName());
+//            assertEquals(apiTemplateId, request.getTemplateId());
+//            assertEquals(toJsonString(ArrayUtils.toArray(new ArrayList<>(MapUtils.convertMap(templateParams).values()), String::valueOf)),
+//                    toJsonString(request.getTemplateParamSet()));
+//            assertEquals(sendLogId, ReflectUtil.getFieldValue(JsonUtils.parseObject(request.getSessionContext(), TencentSmsClient.SessionContext.class), "logId"));
+//            return true;
+//        }))).thenReturn(response);
+//
+//        // 调用
+//        SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, apiTemplateId, templateParams);
+//        // 断言
+//        assertFalse(result.getSuccess());
+//        assertEquals(response.getRequestId(), result.getApiRequestId());
+//        assertEquals(response.getSendStatusSet()[0].getCode(), result.getApiCode());
+//        assertEquals(response.getSendStatusSet()[0].getMessage(), result.getApiMsg());
+//        assertEquals(response.getSendStatusSet()[0].getSerialNo(), result.getSerialNo());
+//    }
 
     @Test
     public void testParseSmsReceiveStatus() {
@@ -185,35 +171,35 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest {
         assertEquals(67890L, statuses.get(0).getLogId());
     }
 
-    @Test
-    public void testGetSmsTemplate() throws Throwable {
-        // 准备参数
-        Long apiTemplateId = randomLongId();
-        String requestId = randomString();
-
-        // mock 方法
-        DescribeSmsTemplateListResponse response = randomPojo(DescribeSmsTemplateListResponse.class, o -> {
-            DescribeTemplateListStatus[] describeTemplateListStatuses = new DescribeTemplateListStatus[1];
-            DescribeTemplateListStatus templateStatus = new DescribeTemplateListStatus();
-            templateStatus.setTemplateId(apiTemplateId);
-            templateStatus.setStatusCode(0L);// 设置模板通过
-            describeTemplateListStatuses[0] = templateStatus;
-            o.setDescribeTemplateStatusSet(describeTemplateListStatuses);
-            o.setRequestId(requestId);
-        });
-        when(client.DescribeSmsTemplateList(argThat(request -> {
-            assertEquals(apiTemplateId, request.getTemplateIdSet()[0]);
-            return true;
-        }))).thenReturn(response);
-
-        // 调用
-        SmsTemplateRespDTO result = smsClient.getSmsTemplate(apiTemplateId.toString());
-        // 断言
-        assertEquals(response.getDescribeTemplateStatusSet()[0].getTemplateId().toString(), result.getId());
-        assertEquals(response.getDescribeTemplateStatusSet()[0].getTemplateContent(), result.getContent());
-        assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getAuditStatus());
-        assertEquals(response.getDescribeTemplateStatusSet()[0].getReviewReply(), result.getAuditReason());
-    }
+//    @Test
+//    public void testGetSmsTemplate() throws Throwable {
+//        // 准备参数
+//        Long apiTemplateId = randomLongId();
+//        String requestId = randomString();
+//
+//        // mock 方法
+//        DescribeSmsTemplateListResponse response = randomPojo(DescribeSmsTemplateListResponse.class, o -> {
+//            DescribeTemplateListStatus[] describeTemplateListStatuses = new DescribeTemplateListStatus[1];
+//            DescribeTemplateListStatus templateStatus = new DescribeTemplateListStatus();
+//            templateStatus.setTemplateId(apiTemplateId);
+//            templateStatus.setStatusCode(0L);// 设置模板通过
+//            describeTemplateListStatuses[0] = templateStatus;
+//            o.setDescribeTemplateStatusSet(describeTemplateListStatuses);
+//            o.setRequestId(requestId);
+//        });
+//        when(client.DescribeSmsTemplateList(argThat(request -> {
+//            assertEquals(apiTemplateId, request.getTemplateIdSet()[0]);
+//            return true;
+//        }))).thenReturn(response);
+//
+//        // 调用
+//        SmsTemplateRespDTO result = smsClient.getSmsTemplate(apiTemplateId.toString());
+//        // 断言
+//        assertEquals(response.getDescribeTemplateStatusSet()[0].getTemplateId().toString(), result.getId());
+//        assertEquals(response.getDescribeTemplateStatusSet()[0].getTemplateContent(), result.getContent());
+//        assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getAuditStatus());
+//        assertEquals(response.getDescribeTemplateStatusSet()[0].getReviewReply(), result.getAuditReason());
+//    }
 
     @Test
     public void testConvertSmsTemplateAuditStatus() {