Merge branch 'master-jdk17' of https://gitee.com/zhijiantianya/yudao-cloud
# 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.javapull/132/head
commit
cef4c14286
|
@ -5,6 +5,8 @@ import cn.hutool.core.map.TableMap;
|
||||||
import cn.hutool.core.net.url.UrlBuilder;
|
import cn.hutool.core.net.url.UrlBuilder;
|
||||||
import cn.hutool.core.util.ReflectUtil;
|
import cn.hutool.core.util.ReflectUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.hutool.http.HttpRequest;
|
||||||
|
import cn.hutool.http.HttpResponse;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.util.UriComponents;
|
import org.springframework.web.util.UriComponents;
|
||||||
import org.springframework.web.util.UriComponentsBuilder;
|
import org.springframework.web.util.UriComponentsBuilder;
|
||||||
|
@ -122,5 +124,23 @@ public class HttpUtils {
|
||||||
return null;
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,11 +3,15 @@ package cn.iocoder.yudao.framework.common.util.spring;
|
||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import cn.hutool.core.map.MapUtil;
|
import cn.hutool.core.map.MapUtil;
|
||||||
import cn.hutool.core.util.ArrayUtil;
|
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.JoinPoint;
|
||||||
import org.aspectj.lang.reflect.MethodSignature;
|
import org.aspectj.lang.reflect.MethodSignature;
|
||||||
|
import org.springframework.context.expression.BeanFactoryResolver;
|
||||||
import org.springframework.core.DefaultParameterNameDiscoverer;
|
import org.springframework.core.DefaultParameterNameDiscoverer;
|
||||||
import org.springframework.core.ParameterNameDiscoverer;
|
import org.springframework.core.ParameterNameDiscoverer;
|
||||||
import org.springframework.expression.EvaluationContext;
|
import org.springframework.expression.EvaluationContext;
|
||||||
|
import org.springframework.expression.Expression;
|
||||||
import org.springframework.expression.ExpressionParser;
|
import org.springframework.expression.ExpressionParser;
|
||||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||||
|
@ -86,4 +90,20 @@ public class SpringExpressionUtils {
|
||||||
return result;
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -185,10 +185,6 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> {
|
||||||
return Db.updateBatchById(entities, size);
|
return Db.updateBatchById(entities, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
default boolean insertOrUpdate(T entity) {
|
|
||||||
return Db.saveOrUpdate(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
default Boolean insertOrUpdateBatch(Collection<T> collection) {
|
default Boolean insertOrUpdateBatch(Collection<T> collection) {
|
||||||
return Db.saveOrUpdateBatch(collection);
|
return Db.saveOrUpdateBatch(collection);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,11 +5,7 @@ import cn.iocoder.yudao.framework.desensitize.core.base.serializer.StringDesensi
|
||||||
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
|
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
|
||||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||||
|
|
||||||
import java.lang.annotation.Documented;
|
import java.lang.annotation.*;
|
||||||
import java.lang.annotation.ElementType;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 顶级脱敏注解,自定义注解需要使用此注解
|
* 顶级脱敏注解,自定义注解需要使用此注解
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package cn.iocoder.yudao.framework.desensitize.core.base.handler;
|
package cn.iocoder.yudao.framework.desensitize.core.base.handler;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ReflectUtil;
|
||||||
|
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -18,4 +20,21 @@ public interface DesensitizationHandler<T extends Annotation> {
|
||||||
*/
|
*/
|
||||||
String desensitize(String origin, T 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 "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,7 @@ import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy
|
||||||
import cn.iocoder.yudao.framework.desensitize.core.regex.handler.EmailDesensitizationHandler;
|
import cn.iocoder.yudao.framework.desensitize.core.regex.handler.EmailDesensitizationHandler;
|
||||||
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
|
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
|
||||||
|
|
||||||
import java.lang.annotation.Documented;
|
import java.lang.annotation.*;
|
||||||
import java.lang.annotation.ElementType;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 邮箱脱敏注解
|
* 邮箱脱敏注解
|
||||||
|
@ -33,4 +29,12 @@ public @interface EmailDesensitize {
|
||||||
* 比如:example@gmail.com 脱敏之后为 e****@gmail.com
|
* 比如:example@gmail.com 脱敏之后为 e****@gmail.com
|
||||||
*/
|
*/
|
||||||
String replacer() default "$1****$2";
|
String replacer() default "$1****$2";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否禁用脱敏
|
||||||
|
*
|
||||||
|
* 支持 Spring EL 表达式,如果返回 true 则跳过脱敏
|
||||||
|
*/
|
||||||
|
String disable() default "";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,7 @@ import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy
|
||||||
import cn.iocoder.yudao.framework.desensitize.core.regex.handler.DefaultRegexDesensitizationHandler;
|
import cn.iocoder.yudao.framework.desensitize.core.regex.handler.DefaultRegexDesensitizationHandler;
|
||||||
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
|
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
|
||||||
|
|
||||||
import java.lang.annotation.Documented;
|
import java.lang.annotation.*;
|
||||||
import java.lang.annotation.ElementType;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 正则脱敏注解
|
* 正则脱敏注解
|
||||||
|
@ -35,4 +31,12 @@ public @interface RegexDesensitize {
|
||||||
* 脱敏后字符串 ******456789
|
* 脱敏后字符串 ******456789
|
||||||
*/
|
*/
|
||||||
String replacer() default "******";
|
String replacer() default "******";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否禁用脱敏
|
||||||
|
*
|
||||||
|
* 支持 Spring EL 表达式,如果返回 true 则跳过脱敏
|
||||||
|
*/
|
||||||
|
String disable() default "";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package cn.iocoder.yudao.framework.desensitize.core.regex.handler;
|
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 cn.iocoder.yudao.framework.desensitize.core.base.handler.DesensitizationHandler;
|
||||||
|
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
|
@ -14,6 +15,13 @@ public abstract class AbstractRegexDesensitizationHandler<T extends Annotation>
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String desensitize(String origin, T annotation) {
|
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 regex = getRegex(annotation);
|
||||||
String replacer = getReplacer(annotation);
|
String replacer = getReplacer(annotation);
|
||||||
return origin.replaceAll(regex, replacer);
|
return origin.replaceAll(regex, replacer);
|
||||||
|
|
|
@ -18,4 +18,10 @@ public class DefaultRegexDesensitizationHandler extends AbstractRegexDesensitiza
|
||||||
String getReplacer(RegexDesensitize annotation) {
|
String getReplacer(RegexDesensitize annotation) {
|
||||||
return annotation.replacer();
|
return annotation.replacer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisable(RegexDesensitize annotation) {
|
||||||
|
return annotation.disable();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,7 @@ import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy
|
||||||
import cn.iocoder.yudao.framework.desensitize.core.slider.handler.BankCardDesensitization;
|
import cn.iocoder.yudao.framework.desensitize.core.slider.handler.BankCardDesensitization;
|
||||||
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
|
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
|
||||||
|
|
||||||
import java.lang.annotation.Documented;
|
import java.lang.annotation.*;
|
||||||
import java.lang.annotation.ElementType;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 银行卡号
|
* 银行卡号
|
||||||
|
@ -37,4 +33,11 @@ public @interface BankCardDesensitize {
|
||||||
*/
|
*/
|
||||||
String replacer() default "*";
|
String replacer() default "*";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否禁用脱敏
|
||||||
|
*
|
||||||
|
* 支持 Spring EL 表达式,如果返回 true 则跳过脱敏
|
||||||
|
*/
|
||||||
|
String disable() default "";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,7 @@ import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy
|
||||||
import cn.iocoder.yudao.framework.desensitize.core.slider.handler.CarLicenseDesensitization;
|
import cn.iocoder.yudao.framework.desensitize.core.slider.handler.CarLicenseDesensitization;
|
||||||
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
|
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
|
||||||
|
|
||||||
import java.lang.annotation.Documented;
|
import java.lang.annotation.*;
|
||||||
import java.lang.annotation.ElementType;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 车牌号
|
* 车牌号
|
||||||
|
@ -37,4 +33,11 @@ public @interface CarLicenseDesensitize {
|
||||||
*/
|
*/
|
||||||
String replacer() default "*";
|
String replacer() default "*";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否禁用脱敏
|
||||||
|
*
|
||||||
|
* 支持 Spring EL 表达式,如果返回 true 则跳过脱敏
|
||||||
|
*/
|
||||||
|
String disable() default "";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,7 @@ import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy
|
||||||
import cn.iocoder.yudao.framework.desensitize.core.slider.handler.ChineseNameDesensitization;
|
import cn.iocoder.yudao.framework.desensitize.core.slider.handler.ChineseNameDesensitization;
|
||||||
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
|
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
|
||||||
|
|
||||||
import java.lang.annotation.Documented;
|
import java.lang.annotation.*;
|
||||||
import java.lang.annotation.ElementType;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 中文名
|
* 中文名
|
||||||
|
@ -37,4 +33,11 @@ public @interface ChineseNameDesensitize {
|
||||||
*/
|
*/
|
||||||
String replacer() default "*";
|
String replacer() default "*";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否禁用脱敏
|
||||||
|
*
|
||||||
|
* 支持 Spring EL 表达式,如果返回 true 则跳过脱敏
|
||||||
|
*/
|
||||||
|
String disable() default "";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,7 @@ import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy
|
||||||
import cn.iocoder.yudao.framework.desensitize.core.slider.handler.FixedPhoneDesensitization;
|
import cn.iocoder.yudao.framework.desensitize.core.slider.handler.FixedPhoneDesensitization;
|
||||||
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
|
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
|
||||||
|
|
||||||
import java.lang.annotation.Documented;
|
import java.lang.annotation.*;
|
||||||
import java.lang.annotation.ElementType;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 固定电话
|
* 固定电话
|
||||||
|
@ -37,4 +33,11 @@ public @interface FixedPhoneDesensitize {
|
||||||
*/
|
*/
|
||||||
String replacer() default "*";
|
String replacer() default "*";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否禁用脱敏
|
||||||
|
*
|
||||||
|
* 支持 Spring EL 表达式,如果返回 true 则跳过脱敏
|
||||||
|
*/
|
||||||
|
String disable() default "";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,7 @@ import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy
|
||||||
import cn.iocoder.yudao.framework.desensitize.core.slider.handler.IdCardDesensitization;
|
import cn.iocoder.yudao.framework.desensitize.core.slider.handler.IdCardDesensitization;
|
||||||
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
|
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
|
||||||
|
|
||||||
import java.lang.annotation.Documented;
|
import java.lang.annotation.*;
|
||||||
import java.lang.annotation.ElementType;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 身份证
|
* 身份证
|
||||||
|
@ -37,4 +33,11 @@ public @interface IdCardDesensitize {
|
||||||
*/
|
*/
|
||||||
String replacer() default "*";
|
String replacer() default "*";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否禁用脱敏
|
||||||
|
*
|
||||||
|
* 支持 Spring EL 表达式,如果返回 true 则跳过脱敏
|
||||||
|
*/
|
||||||
|
String disable() default "";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,7 @@ import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy
|
||||||
import cn.iocoder.yudao.framework.desensitize.core.slider.handler.MobileDesensitization;
|
import cn.iocoder.yudao.framework.desensitize.core.slider.handler.MobileDesensitization;
|
||||||
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
|
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
|
||||||
|
|
||||||
import java.lang.annotation.Documented;
|
import java.lang.annotation.*;
|
||||||
import java.lang.annotation.ElementType;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 手机号
|
* 手机号
|
||||||
|
@ -37,4 +33,11 @@ public @interface MobileDesensitize {
|
||||||
*/
|
*/
|
||||||
String replacer() default "*";
|
String replacer() default "*";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否禁用脱敏
|
||||||
|
*
|
||||||
|
* 支持 Spring EL 表达式,如果返回 true 则跳过脱敏
|
||||||
|
*/
|
||||||
|
String disable() default "";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,7 @@ import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy
|
||||||
import cn.iocoder.yudao.framework.desensitize.core.slider.handler.PasswordDesensitization;
|
import cn.iocoder.yudao.framework.desensitize.core.slider.handler.PasswordDesensitization;
|
||||||
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
|
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
|
||||||
|
|
||||||
import java.lang.annotation.Documented;
|
import java.lang.annotation.*;
|
||||||
import java.lang.annotation.ElementType;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 密码
|
* 密码
|
||||||
|
@ -39,4 +35,11 @@ public @interface PasswordDesensitize {
|
||||||
*/
|
*/
|
||||||
String replacer() default "*";
|
String replacer() default "*";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否禁用脱敏
|
||||||
|
*
|
||||||
|
* 支持 Spring EL 表达式,如果返回 true 则跳过脱敏
|
||||||
|
*/
|
||||||
|
String disable() default "";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,7 @@ import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy
|
||||||
import cn.iocoder.yudao.framework.desensitize.core.slider.handler.DefaultDesensitizationHandler;
|
import cn.iocoder.yudao.framework.desensitize.core.slider.handler.DefaultDesensitizationHandler;
|
||||||
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
|
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
|
||||||
|
|
||||||
import java.lang.annotation.Documented;
|
import java.lang.annotation.*;
|
||||||
import java.lang.annotation.ElementType;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 滑动脱敏注解
|
* 滑动脱敏注解
|
||||||
|
@ -40,4 +36,12 @@ public @interface SliderDesensitize {
|
||||||
* 前缀保留长度
|
* 前缀保留长度
|
||||||
*/
|
*/
|
||||||
int prefixKeep() default 0;
|
int prefixKeep() default 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否禁用脱敏
|
||||||
|
*
|
||||||
|
* 支持 Spring EL 表达式,如果返回 true 则跳过脱敏
|
||||||
|
*/
|
||||||
|
String disable() default "";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package cn.iocoder.yudao.framework.desensitize.core.slider.handler;
|
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 cn.iocoder.yudao.framework.desensitize.core.base.handler.DesensitizationHandler;
|
||||||
|
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
|
@ -14,6 +15,13 @@ public abstract class AbstractSliderDesensitizationHandler<T extends Annotation>
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String desensitize(String origin, T annotation) {
|
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 prefixKeep = getPrefixKeep(annotation);
|
||||||
int suffixKeep = getSuffixKeep(annotation);
|
int suffixKeep = getSuffixKeep(annotation);
|
||||||
String replacer = getReplacer(annotation);
|
String replacer = getReplacer(annotation);
|
||||||
|
|
|
@ -24,4 +24,9 @@ public class BankCardDesensitization extends AbstractSliderDesensitizationHandle
|
||||||
return annotation.replacer();
|
return annotation.replacer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisable(BankCardDesensitize annotation) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.CarLicenseD
|
||||||
* @author gaibu
|
* @author gaibu
|
||||||
*/
|
*/
|
||||||
public class CarLicenseDesensitization extends AbstractSliderDesensitizationHandler<CarLicenseDesensitize> {
|
public class CarLicenseDesensitization extends AbstractSliderDesensitizationHandler<CarLicenseDesensitize> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
Integer getPrefixKeep(CarLicenseDesensitize annotation) {
|
Integer getPrefixKeep(CarLicenseDesensitize annotation) {
|
||||||
return annotation.prefixKeep();
|
return annotation.prefixKeep();
|
||||||
|
@ -22,4 +23,10 @@ public class CarLicenseDesensitization extends AbstractSliderDesensitizationHand
|
||||||
String getReplacer(CarLicenseDesensitize annotation) {
|
String getReplacer(CarLicenseDesensitize annotation) {
|
||||||
return annotation.replacer();
|
return annotation.replacer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisable(CarLicenseDesensitize annotation) {
|
||||||
|
return annotation.disable();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.SliderDesen
|
||||||
* @author gaibu
|
* @author gaibu
|
||||||
*/
|
*/
|
||||||
public class DefaultDesensitizationHandler extends AbstractSliderDesensitizationHandler<SliderDesensitize> {
|
public class DefaultDesensitizationHandler extends AbstractSliderDesensitizationHandler<SliderDesensitize> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
Integer getPrefixKeep(SliderDesensitize annotation) {
|
Integer getPrefixKeep(SliderDesensitize annotation) {
|
||||||
return annotation.prefixKeep();
|
return annotation.prefixKeep();
|
||||||
|
@ -22,4 +23,5 @@ public class DefaultDesensitizationHandler extends AbstractSliderDesensitization
|
||||||
String getReplacer(SliderDesensitize annotation) {
|
String getReplacer(SliderDesensitize annotation) {
|
||||||
return annotation.replacer();
|
return annotation.replacer();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.FixedPhoneD
|
||||||
* @author gaibu
|
* @author gaibu
|
||||||
*/
|
*/
|
||||||
public class FixedPhoneDesensitization extends AbstractSliderDesensitizationHandler<FixedPhoneDesensitize> {
|
public class FixedPhoneDesensitization extends AbstractSliderDesensitizationHandler<FixedPhoneDesensitize> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
Integer getPrefixKeep(FixedPhoneDesensitize annotation) {
|
Integer getPrefixKeep(FixedPhoneDesensitize annotation) {
|
||||||
return annotation.prefixKeep();
|
return annotation.prefixKeep();
|
||||||
|
@ -22,4 +23,5 @@ public class FixedPhoneDesensitization extends AbstractSliderDesensitizationHand
|
||||||
String getReplacer(FixedPhoneDesensitize annotation) {
|
String getReplacer(FixedPhoneDesensitize annotation) {
|
||||||
return annotation.replacer();
|
return annotation.replacer();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,4 +22,5 @@ public class IdCardDesensitization extends AbstractSliderDesensitizationHandler<
|
||||||
String getReplacer(IdCardDesensitize annotation) {
|
String getReplacer(IdCardDesensitize annotation) {
|
||||||
return annotation.replacer();
|
return annotation.replacer();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,4 +23,5 @@ public class MobileDesensitization extends AbstractSliderDesensitizationHandler<
|
||||||
String getReplacer(MobileDesensitize annotation) {
|
String getReplacer(MobileDesensitize annotation) {
|
||||||
return annotation.replacer();
|
return annotation.replacer();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,4 +22,5 @@ public class PasswordDesensitization extends AbstractSliderDesensitizationHandle
|
||||||
String getReplacer(PasswordDesensitize annotation) {
|
String getReplacer(PasswordDesensitize annotation) {
|
||||||
return annotation.replacer();
|
return annotation.replacer();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum;
|
||||||
import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
|
import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
|
||||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||||
import com.baomidou.mybatisplus.annotation.TableField;
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
@ -30,6 +31,7 @@ public class CouponDO extends BaseDO {
|
||||||
/**
|
/**
|
||||||
* 优惠劵编号
|
* 优惠劵编号
|
||||||
*/
|
*/
|
||||||
|
@TableId
|
||||||
private Long id;
|
private Long id;
|
||||||
/**
|
/**
|
||||||
* 优惠劵模板编号
|
* 优惠劵模板编号
|
||||||
|
|
|
@ -24,7 +24,8 @@ import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
@ -132,7 +133,7 @@ public class CouponServiceImpl implements CouponService {
|
||||||
@Transactional
|
@Transactional
|
||||||
public void deleteCoupon(Long id) {
|
public void deleteCoupon(Long id) {
|
||||||
// 校验存在
|
// 校验存在
|
||||||
validateCouponExists(id);
|
CouponDO coupon = validateCouponExists(id);
|
||||||
|
|
||||||
// 更新优惠劵
|
// 更新优惠劵
|
||||||
int deleteCount = couponMapper.delete(id,
|
int deleteCount = couponMapper.delete(id,
|
||||||
|
@ -140,8 +141,9 @@ public class CouponServiceImpl implements CouponService {
|
||||||
if (deleteCount == 0) {
|
if (deleteCount == 0) {
|
||||||
throw exception(COUPON_DELETE_FAIL_USED);
|
throw exception(COUPON_DELETE_FAIL_USED);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 减少优惠劵模板的领取数量 -1
|
// 减少优惠劵模板的领取数量 -1
|
||||||
couponTemplateService.updateCouponTemplateTakeCount(id, -1);
|
couponTemplateService.updateCouponTemplateTakeCount(coupon.getTemplateId(), -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -149,10 +151,12 @@ public class CouponServiceImpl implements CouponService {
|
||||||
return couponMapper.selectListByUserIdAndStatus(userId, status);
|
return couponMapper.selectListByUserIdAndStatus(userId, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void validateCouponExists(Long id) {
|
private CouponDO validateCouponExists(Long id) {
|
||||||
if (couponMapper.selectById(id) == null) {
|
CouponDO coupon = couponMapper.selectById(id);
|
||||||
|
if (coupon == null) {
|
||||||
throw exception(COUPON_NOT_EXISTS);
|
throw exception(COUPON_NOT_EXISTS);
|
||||||
}
|
}
|
||||||
|
return coupon;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -22,4 +22,7 @@ public class AppProductSpuBaseRespVO {
|
||||||
@Schema(description = "商品主图地址", example = "https://www.iocoder.cn/xx.png")
|
@Schema(description = "商品主图地址", example = "https://www.iocoder.cn/xx.png")
|
||||||
private String picUrl;
|
private String picUrl;
|
||||||
|
|
||||||
|
@Schema(description = "商品分类编号", example = "1")
|
||||||
|
private Long categoryId;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 cn.iocoder.yudao.module.trade.controller.app.base.property.AppProductPropertyValueDetailRespVO;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
import javax.validation.constraints.NotNull;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Schema(description = "用户 App - 交易订单结算信息 Response VO")
|
@Schema(description = "用户 App - 交易订单结算信息 Response VO")
|
||||||
|
@ -26,7 +26,7 @@ public class AppTradeOrderSettlementRespVO {
|
||||||
private Address address;
|
private Address address;
|
||||||
|
|
||||||
@Schema(description = "已使用的积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
|
@Schema(description = "已使用的积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
|
||||||
private Integer usedPoint;
|
private Integer usePoint;
|
||||||
|
|
||||||
@Schema(description = "总积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
|
@Schema(description = "总积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
|
||||||
private Integer totalPoint;
|
private Integer totalPoint;
|
||||||
|
|
|
@ -48,13 +48,17 @@ public class TradePriceCalculateRespBO {
|
||||||
*/
|
*/
|
||||||
private Long couponId;
|
private Long couponId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会员剩余积分
|
||||||
|
*/
|
||||||
|
private Integer totalPoint;
|
||||||
/**
|
/**
|
||||||
* 使用的积分
|
* 使用的积分
|
||||||
*/
|
*/
|
||||||
private Integer usePoint;
|
private Integer usePoint;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 使用的积分
|
* 赠送的积分
|
||||||
*/
|
*/
|
||||||
private Integer givePoint;
|
private Integer givePoint;
|
||||||
|
|
||||||
|
|
|
@ -55,6 +55,7 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
|
||||||
if (param.getDeliveryType() == null) {
|
if (param.getDeliveryType() == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// TODO @puhui999:需要校验,是不是存在商品不能门店自提,或者不能快递发货的情况。就是说,配送方式不匹配哈
|
||||||
if (DeliveryTypeEnum.PICK_UP.getType().equals(param.getDeliveryType())) {
|
if (DeliveryTypeEnum.PICK_UP.getType().equals(param.getDeliveryType())) {
|
||||||
calculateByPickUp(param);
|
calculateByPickUp(param);
|
||||||
} else if (DeliveryTypeEnum.EXPRESS.getType().equals(param.getDeliveryType())) {
|
} else if (DeliveryTypeEnum.EXPRESS.getType().equals(param.getDeliveryType())) {
|
||||||
|
|
|
@ -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.promotion.enums.common.PromotionTypeEnum;
|
||||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
|
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
|
||||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
|
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.core.annotation.Order;
|
import org.springframework.core.annotation.Order;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||||
|
@ -37,11 +37,12 @@ public class TradePointUsePriceCalculator implements TradePriceCalculator {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
|
public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
|
||||||
// 默认使用积分为 0
|
// 0. 初始化积分
|
||||||
result.setUsePoint(0);
|
MemberUserRespDTO user = memberUserApi.getUser(param.getUserId()).getCheckedData();
|
||||||
|
result.setTotalPoint(user.getPoint()).setUsePoint(0);
|
||||||
|
|
||||||
// 1.1 校验是否使用积分
|
// 1.1 校验是否使用积分
|
||||||
if (!BooleanUtil.isTrue(param.getPointStatus())) {
|
if (!BooleanUtil.isTrue(param.getPointStatus())) {
|
||||||
result.setUsePoint(0);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 1.2 校验积分抵扣是否开启
|
// 1.2 校验积分抵扣是否开启
|
||||||
|
@ -50,7 +51,6 @@ public class TradePointUsePriceCalculator implements TradePriceCalculator {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 1.3 校验用户积分余额
|
// 1.3 校验用户积分余额
|
||||||
MemberUserRespDTO user = memberUserApi.getUser(param.getUserId()).getCheckedData();
|
|
||||||
if (user.getPoint() == null || user.getPoint() <= 0) {
|
if (user.getPoint() == null || user.getPoint() <= 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -142,19 +142,6 @@
|
||||||
<artifactId>wx-java-miniapp-spring-boot-starter</artifactId> <!-- 微信登录(小程序) -->
|
<artifactId>wx-java-miniapp-spring-boot-starter</artifactId> <!-- 微信登录(小程序) -->
|
||||||
</dependency>
|
</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>
|
<dependency>
|
||||||
<groupId>com.xingyuv</groupId>
|
<groupId>com.xingyuv</groupId>
|
||||||
<artifactId>spring-boot-starter-captcha-plus</artifactId> <!-- 验证码,一般用于登录使用 -->
|
<artifactId>spring-boot-starter-captcha-plus</artifactId> <!-- 验证码,一般用于登录使用 -->
|
||||||
|
|
|
@ -1,36 +1,33 @@
|
||||||
package cn.iocoder.yudao.module.system.framework.sms.core.client.impl;
|
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.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.core.KeyValue;
|
||||||
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
|
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.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.SmsReceiveRespDTO;
|
||||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
|
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.client.dto.SmsTemplateRespDTO;
|
||||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.impl.AbstractSmsClient;
|
|
||||||
import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
|
import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
|
||||||
import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties;
|
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 com.google.common.annotations.VisibleForTesting;
|
||||||
import lombok.Data;
|
import lombok.SneakyThrows;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.net.URLEncoder;
|
||||||
import java.util.List;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Objects;
|
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.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;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 阿里短信客户端的实现类
|
* 阿里短信客户端的实现类
|
||||||
|
@ -41,20 +38,11 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DE
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class AliyunSmsClient extends AbstractSmsClient {
|
public class AliyunSmsClient extends AbstractSmsClient {
|
||||||
|
|
||||||
/**
|
private static final String URL = "https://dysmsapi.aliyuncs.com";
|
||||||
* 调用成功 code
|
private static final String HOST = "dysmsapi.aliyuncs.com";
|
||||||
*/
|
private static final String VERSION = "2017-05-25";
|
||||||
public static final String API_CODE_SUCCESS = "OK";
|
|
||||||
|
|
||||||
/**
|
private static final String RESPONSE_CODE_SUCCESS = "OK";
|
||||||
* REGION, 使用杭州
|
|
||||||
*/
|
|
||||||
private static final String ENDPOINT = "cn-hangzhou";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 阿里云客户端
|
|
||||||
*/
|
|
||||||
private volatile IAcsClient client;
|
|
||||||
|
|
||||||
public AliyunSmsClient(SmsChannelProperties properties) {
|
public AliyunSmsClient(SmsChannelProperties properties) {
|
||||||
super(properties);
|
super(properties);
|
||||||
|
@ -64,47 +52,70 @@ public class AliyunSmsClient extends AbstractSmsClient {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doInit() {
|
protected void doInit() {
|
||||||
IClientProfile profile = DefaultProfile.getProfile(ENDPOINT, properties.getApiKey(), properties.getApiSecret());
|
|
||||||
client = new DefaultAcsClient(profile);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId,
|
public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId,
|
||||||
List<KeyValue<String, Object>> templateParams) throws Throwable {
|
List<KeyValue<String, Object>> templateParams) throws Throwable {
|
||||||
// 构建请求
|
Assert.notBlank(properties.getSignature(), "短信签名不能为空");
|
||||||
SendSmsRequest request = new SendSmsRequest();
|
// 1. 执行请求
|
||||||
request.setPhoneNumbers(mobile);
|
// 参考链接 https://api.aliyun.com/document/Dysmsapi/2017-05-25/SendSms
|
||||||
request.setSignName(properties.getSignature());
|
TreeMap<String, Object> queryParam = new TreeMap<>();
|
||||||
request.setTemplateCode(apiTemplateId);
|
queryParam.put("PhoneNumbers", mobile);
|
||||||
request.setTemplateParam(JsonUtils.toJsonString(MapUtils.convertMap(templateParams)));
|
queryParam.put("SignName", properties.getSignature());
|
||||||
request.setOutId(String.valueOf(sendLogId));
|
queryParam.put("TemplateCode", apiTemplateId);
|
||||||
// 执行请求
|
queryParam.put("TemplateParam", JsonUtils.toJsonString(MapUtils.convertMap(templateParams)));
|
||||||
SendSmsResponse response = client.getAcsResponse(request);
|
queryParam.put("OutId", sendLogId);
|
||||||
return new SmsSendRespDTO().setSuccess(Objects.equals(response.getCode(), API_CODE_SUCCESS)).setSerialNo(response.getBizId())
|
JSONObject response = request("SendSms", queryParam);
|
||||||
.setApiRequestId(response.getRequestId()).setApiCode(response.getCode()).setApiMsg(response.getMessage());
|
|
||||||
|
// 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
|
@Override
|
||||||
public List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) {
|
public List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) {
|
||||||
List<SmsReceiveStatus> statuses = JsonUtils.parseArray(text, SmsReceiveStatus.class);
|
JSONArray statuses = JSONUtil.parseArray(text);
|
||||||
return convertList(statuses, status -> new SmsReceiveRespDTO().setSuccess(status.getSuccess())
|
// 字段参考
|
||||||
.setErrorCode(status.getErrCode()).setErrorMsg(status.getErrMsg())
|
return convertList(statuses, status -> {
|
||||||
.setMobile(status.getPhoneNumber()).setReceiveTime(status.getReportTime())
|
JSONObject statusObj = (JSONObject) status;
|
||||||
.setSerialNo(status.getBizId()).setLogId(Long.valueOf(status.getOutId())));
|
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
|
@Override
|
||||||
public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable {
|
public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable {
|
||||||
// 构建请求
|
// 1. 执行请求
|
||||||
QuerySmsTemplateRequest request = new QuerySmsTemplateRequest();
|
// 参考链接 https://api.aliyun.com/document/Dysmsapi/2017-05-25/QuerySmsTemplate
|
||||||
request.setTemplateCode(apiTemplateId);
|
TreeMap<String, Object> queryParam = new TreeMap<>();
|
||||||
// 执行请求
|
queryParam.put("TemplateCode", apiTemplateId);
|
||||||
QuerySmsTemplateResponse response = client.getAcsResponse(request);
|
JSONObject response = request("QuerySmsTemplate", queryParam);
|
||||||
if (response.getTemplateStatus() == null) {
|
|
||||||
|
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 null;
|
||||||
}
|
}
|
||||||
return new SmsTemplateRespDTO().setId(response.getTemplateCode()).setContent(response.getTemplateContent())
|
// 2.2 请求成功
|
||||||
.setAuditStatus(convertSmsTemplateAuditStatus(response.getTemplateStatus())).setAuditReason(response.getReason());
|
return new SmsTemplateRespDTO()
|
||||||
|
.setId(response.getStr("TemplateCode"))
|
||||||
|
.setContent(response.getStr("TemplateContent"))
|
||||||
|
.setAuditStatus(convertSmsTemplateAuditStatus(response.getInt("TemplateStatus")))
|
||||||
|
.setAuditReason(response.getStr("Reason"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
|
@ -118,66 +129,71 @@ public class AliyunSmsClient extends AbstractSmsClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 短信接收状态
|
* 请求阿里云短信
|
||||||
*
|
*
|
||||||
* 参见 <a href="https://help.aliyun.com/document_detail/101867.html">文档</a>
|
* @see <a href="https://help.aliyun.com/zh/sdk/product-overview/v3-request-structure-and-signature">V3 版本请求体&签名机制</>
|
||||||
*
|
* @param apiName 请求的 API 名称
|
||||||
* @author 芋道源码
|
* @param queryParams 请求参数
|
||||||
|
* @return 请求结果
|
||||||
*/
|
*/
|
||||||
@Data
|
private JSONObject request(String apiName, TreeMap<String, Object> queryParams) {
|
||||||
public static class SmsReceiveStatus {
|
// 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编码规范
|
||||||
*/
|
|
||||||
@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 的日志编号
|
* @param str 需要进行 URL 编码的字符串
|
||||||
|
* @return 编码后的字符串
|
||||||
*/
|
*/
|
||||||
@JsonProperty("out_id")
|
@SneakyThrows
|
||||||
private String outId;
|
private static String percentCode(String str) {
|
||||||
/**
|
Assert.notNull(str, "str 不能为空");
|
||||||
* 短信长度,例如说 1、2、3
|
return URLEncoder.encode(str, StandardCharsets.UTF_8.name())
|
||||||
*
|
.replace("+", "%20") // 加号 "+" 被替换为 "%20"
|
||||||
* 140 字节算一条短信,短信长度超过 140 字节时会拆分成多条短信发送
|
.replace("*", "%2A") // 星号 "*" 被替换为 "%2A"
|
||||||
*/
|
.replace("%7E", "~"); // 波浪号 "%7E" 被替换为 "~"
|
||||||
@JsonProperty("sms_size")
|
|
||||||
private Integer smsSize;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -13,7 +13,6 @@ 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.SmsReceiveRespDTO;
|
||||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
|
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.client.dto.SmsTemplateRespDTO;
|
||||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.impl.AbstractSmsClient;
|
|
||||||
import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
|
import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
|
||||||
import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties;
|
import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties;
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
package cn.iocoder.yudao.module.system.framework.sms.core.client.impl;
|
package cn.iocoder.yudao.module.system.framework.sms.core.client.impl;
|
||||||
|
|
||||||
|
|
||||||
import cn.hutool.core.lang.Assert;
|
import cn.hutool.core.lang.Assert;
|
||||||
import cn.hutool.core.util.HexUtil;
|
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.hutool.crypto.SecureUtil;
|
import cn.hutool.crypto.SecureUtil;
|
||||||
import cn.hutool.crypto.digest.DigestUtil;
|
import cn.hutool.http.HttpRequest;
|
||||||
import cn.hutool.json.JSONArray;
|
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.core.KeyValue;
|
||||||
|
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
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.SmsReceiveRespDTO;
|
||||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
|
import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
|
||||||
|
@ -17,24 +20,19 @@ import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
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.io.UnsupportedEncodingException;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.*;
|
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.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.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT;
|
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 华为短信客户端的实现类
|
* 华为短信客户端的实现类
|
||||||
*
|
*
|
||||||
|
@ -47,7 +45,14 @@ public class HuaweiSmsClient extends AbstractSmsClient {
|
||||||
/**
|
/**
|
||||||
* 调用成功 code
|
* 调用成功 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) {
|
public HuaweiSmsClient(SmsChannelProperties properties) {
|
||||||
super(properties);
|
super(properties);
|
||||||
|
@ -55,96 +60,79 @@ public class HuaweiSmsClient extends AbstractSmsClient {
|
||||||
Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空");
|
Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doInit() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId,
|
public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId,
|
||||||
List<KeyValue<String, Object>> templateParams) throws Throwable {
|
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 通道号"。空格为分隔符。
|
// 所以将 通道号 拼接到 apiTemplateId 字段中,格式为 "apiTemplateId 通道号"。空格为分隔符。
|
||||||
// TODO @scholar:暂时只考虑中国大陆,所以不需要 sender 哈
|
|
||||||
String sender = StrUtil.subAfter(apiTemplateId, " ", true); //中国大陆短信签名通道号或全球短信通道号
|
String sender = StrUtil.subAfter(apiTemplateId, " ", true); //中国大陆短信签名通道号或全球短信通道号
|
||||||
String templateId = StrUtil.subBefore(apiTemplateId, " ", true); //模板ID
|
String templateId = StrUtil.subBefore(apiTemplateId, " ", true); //模板ID
|
||||||
|
|
||||||
// 选填,短信状态报告接收地址,推荐使用域名,为空或者不填表示不接收状态报告
|
//选填,短信状态报告接收地址,推荐使用域名,为空或者不填表示不接收状态报告
|
||||||
String statusCallBack = properties.getCallbackUrl();
|
String statusCallBack = properties.getCallbackUrl();
|
||||||
|
|
||||||
// TODO @scholar:1)是不是用 LocalDateTimeUtil.format();这样 3 行变成一行
|
List<String> templateParas = CollectionUtils.convertList(templateParams, kv -> String.valueOf(kv.getValue()));
|
||||||
// TODO @scholar:singerDate 叫 sdkDate 会更合适哈,这样理解起来简单。另外,singer 应该是 signed 么?
|
|
||||||
|
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);
|
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'", Locale.ENGLISH);
|
||||||
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
|
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||||
String singerDate = sdf.format(new Date());
|
String sdkDate = sdf.format(new Date());
|
||||||
|
|
||||||
// TODO @scholar:整个处理加密的过程,是不是应该抽成一个 private 方法哈。这样整个调用的主干更清晰。
|
|
||||||
// ************* 步骤 1:拼接规范请求串 *************
|
// ************* 步骤 1:拼接规范请求串 *************
|
||||||
String httpRequestMethod = "POST";
|
String httpRequestMethod = "POST";
|
||||||
String canonicalUri = "/sms/batchSendSms/v1/";
|
String canonicalUri = "/sms/batchSendSms/v1/";
|
||||||
String canonicalQueryString = ""; // 查询参数为空
|
String canonicalQueryString = "";//查询参数为空
|
||||||
String canonicalHeaders = "content-type:application/x-www-form-urlencoded\n"
|
String canonicalHeaders = "content-type:application/x-www-form-urlencoded\n"
|
||||||
+ "host:smsapi.cn-north-4.myhuaweicloud.com:443\n"
|
+ "host:"+ HOST +"\n"
|
||||||
+ "x-sdk-date:" + singerDate + "\n";
|
+ "x-sdk-date:" + sdkDate + "\n";
|
||||||
// TODO @scholar:静态枚举了
|
//请求Body,不携带签名名称时,signature请填null
|
||||||
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
|
|
||||||
String body = buildRequestBody(sender, mobile, templateId, templateParas, statusCallBack, null);
|
String body = buildRequestBody(sender, mobile, templateId, templateParas, statusCallBack, null);
|
||||||
// TODO @scholar:Assert 断言,抛出异常
|
|
||||||
if (null == body || body.isEmpty()) {
|
if (null == body || body.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
String hashedRequestBody = HexUtil.encodeHexStr(DigestUtil.sha256(body));
|
String hashedRequestBody = sha256Hex(body);
|
||||||
String canonicalRequest = httpRequestMethod + "\n" + canonicalUri + "\n" + canonicalQueryString + "\n"
|
String canonicalRequest = httpRequestMethod + "\n" + canonicalUri + "\n" + canonicalQueryString + "\n"
|
||||||
+ canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestBody;
|
+ canonicalHeaders + "\n" + SIGNEDHEADERS + "\n" + hashedRequestBody;
|
||||||
|
|
||||||
// ************* 步骤 2:拼接待签名字符串 *************
|
// ************* 步骤 2:拼接待签名字符串 *************
|
||||||
// TODO @scholar:sha256Hex 是不是更简洁哈
|
String hashedCanonicalRequest = sha256Hex(canonicalRequest);
|
||||||
String hashedCanonicalRequest = HexUtil.encodeHexStr(DigestUtil.sha256(canonicalRequest));
|
String stringToSign = "SDK-HMAC-SHA256" + "\n" + sdkDate + "\n" + hashedCanonicalRequest;
|
||||||
String stringToSign = "SDK-HMAC-SHA256" + "\n" + singerDate + "\n" + hashedCanonicalRequest;
|
|
||||||
|
|
||||||
// ************* 步骤 3:计算签名 *************
|
// ************* 步骤 3:计算签名 *************
|
||||||
String signature = SecureUtil.hmacSha256(properties.getApiSecret()).digestHex(stringToSign);
|
String signature = SecureUtil.hmacSha256(properties.getApiSecret()).digestHex(stringToSign);
|
||||||
|
|
||||||
// ************* 步骤 4:拼接 Authorization *************
|
// ************* 步骤 4:拼接 Authorization *************
|
||||||
String authorization = "SDK-HMAC-SHA256" + " " + "Access=" + properties.getApiKey() + ", "
|
String authorization = "SDK-HMAC-SHA256" + " " + "Access=" + properties.getApiKey() + ", "
|
||||||
+ "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature;
|
+ "SignedHeaders=" + SIGNEDHEADERS + ", " + "Signature=" + signature;
|
||||||
|
|
||||||
// ************* 步骤 5:构造HttpRequest 并执行request请求,获得response *************
|
// ************* 步骤 5:构造HttpRequest 并执行request请求,获得response *************
|
||||||
// TODO @scholar:考虑了下,还是换 hutool 的 httpUtils。因为未来 httpclient 我们可能会移除掉
|
HttpResponse response = HttpRequest.post(URL)
|
||||||
HttpUriRequest postMethod = RequestBuilder.post()
|
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||||
.setUri(url)
|
.header("X-Sdk-Date", sdkDate)
|
||||||
.setEntity(new StringEntity(body, StandardCharsets.UTF_8))
|
.header("host",HOST)
|
||||||
.setHeader("Content-Type","application/x-www-form-urlencoded")
|
.header("Authorization", authorization)
|
||||||
.setHeader("X-Sdk-Date", singerDate)
|
.body(body)
|
||||||
.setHeader("Authorization", authorization)
|
.execute();
|
||||||
.build();
|
|
||||||
// TODO @scholar:这种不太适合一直 new 的哈
|
return JSONUtil.parseObj(response.body());
|
||||||
CloseableHttpClient client = HttpClientBuilder.create().build();
|
}
|
||||||
HttpResponse response = client.execute(postMethod);
|
|
||||||
// TODO @scholar:失败的情况下的处理
|
private SmsResponse getSmsSendResponse(JSONObject resJson) {
|
||||||
// TODO @scholar:setSerialNo(Integer.toString(response.getStatusLine().getStatusCode())) 这部分,空一行。一行代码太多了,阅读性不太好哈
|
SmsResponse smsResponse = new SmsResponse();
|
||||||
return new SmsSendRespDTO().setSuccess(Objects.equals(response.getStatusLine().getReasonPhrase(), API_CODE_SUCCESS)).setSerialNo(Integer.toString(response.getStatusLine().getStatusCode()))
|
smsResponse.setSuccess("000000".equals(resJson.getStr("code")));
|
||||||
.setApiRequestId(null).setApiCode(null).setApiMsg(null);
|
smsResponse.setData(resJson);
|
||||||
|
return smsResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
static String buildRequestBody(String sender, String receiver, String templateId, List<String> templateParas,
|
static String buildRequestBody(String sender, String receiver, String templateId, List<String> templateParas,
|
||||||
String statusCallBack, @SuppressWarnings("SameParameterValue") String signature) throws UnsupportedEncodingException {
|
String statusCallBack, String signature) throws UnsupportedEncodingException {
|
||||||
// TODO @scholar:参数不满足,是不是抛出异常更好哈;通过 hutool 的 Assert 去断言
|
|
||||||
if (null == sender || null == receiver || null == templateId || sender.isEmpty() || receiver.isEmpty()
|
if (null == sender || null == receiver || null == templateId || sender.isEmpty() || receiver.isEmpty()
|
||||||
|| templateId.isEmpty()) {
|
|| templateId.isEmpty()) {
|
||||||
System.out.println("buildRequestBody(): sender, receiver or templateId is null.");
|
System.out.println("buildRequestBody(): sender, receiver or templateId is null.");
|
||||||
|
@ -155,20 +143,17 @@ public class HuaweiSmsClient extends AbstractSmsClient {
|
||||||
appendToBody(body, "from=", sender);
|
appendToBody(body, "from=", sender);
|
||||||
appendToBody(body, "&to=", receiver);
|
appendToBody(body, "&to=", receiver);
|
||||||
appendToBody(body, "&templateId=", templateId);
|
appendToBody(body, "&templateId=", templateId);
|
||||||
// TODO @scholar:new JSONArray(templateParas).toString(),是不是 JsonUtils.toString 呀?
|
appendToBody(body, "&templateParas=", JsonUtils.toJsonString(templateParas));
|
||||||
appendToBody(body, "&templateParas=", new JSONArray(templateParas).toString());
|
|
||||||
appendToBody(body, "&statusCallback=", statusCallBack);
|
appendToBody(body, "&statusCallback=", statusCallBack);
|
||||||
appendToBody(body, "&signature=", signature);
|
appendToBody(body, "&signature=", signature);
|
||||||
return body.toString();
|
return body.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void appendToBody(StringBuilder body, String key, String val) throws UnsupportedEncodingException {
|
private static void appendToBody(StringBuilder body, String key, String val) throws UnsupportedEncodingException {
|
||||||
// TODO @scholar:StrUtils.isNotEmpty(val),是不是更简洁哈
|
|
||||||
if (null != val && !val.isEmpty()) {
|
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
|
@Override
|
||||||
public List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) {
|
public List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) {
|
||||||
List<SmsReceiveStatus> statuses = JsonUtils.parseArray(text, SmsReceiveStatus.class);
|
List<SmsReceiveStatus> statuses = JsonUtils.parseArray(text, SmsReceiveStatus.class);
|
||||||
|
@ -180,12 +165,28 @@ public class HuaweiSmsClient extends AbstractSmsClient {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable {
|
public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable {
|
||||||
// 华为短信模板查询和发送短信,是不同的两套 key 和 secret,与阿里、腾讯的区别较大,这里模板查询校验暂不实现
|
//华为短信模板查询和发送短信,是不同的两套key和secret,与阿里、腾讯的区别较大,这里模板查询校验暂不实现。
|
||||||
// 对应文档 https://support.huaweicloud.com/api-msgsms/sms_05_0040.html
|
return new SmsTemplateRespDTO().setId(null).setContent(null)
|
||||||
return new SmsTemplateRespDTO().setId(apiTemplateId).setContent(null)
|
|
||||||
.setAuditStatus(SmsTemplateAuditStatusEnum.SUCCESS.getStatus()).setAuditReason(null);
|
.setAuditStatus(SmsTemplateAuditStatusEnum.SUCCESS.getStatus()).setAuditReason(null);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class SmsResponse {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否成功
|
||||||
|
*/
|
||||||
|
private boolean success;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 厂商原返回体
|
||||||
|
*/
|
||||||
|
private Object data;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 短信接收状态
|
* 短信接收状态
|
||||||
*
|
*
|
||||||
|
|
|
@ -2,31 +2,38 @@ package cn.iocoder.yudao.module.system.framework.sms.core.client.impl;
|
||||||
|
|
||||||
import cn.hutool.core.lang.Assert;
|
import cn.hutool.core.lang.Assert;
|
||||||
import cn.hutool.core.util.StrUtil;
|
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.core.KeyValue;
|
||||||
import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
|
import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
|
||||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
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.SmsReceiveRespDTO;
|
||||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
|
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.client.dto.SmsTemplateRespDTO;
|
||||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.impl.AbstractSmsClient;
|
|
||||||
import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
|
import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
|
||||||
import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties;
|
import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties;
|
||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.tencentcloudapi.common.Credential;
|
import jakarta.xml.bind.DatatypeConverter;
|
||||||
import com.tencentcloudapi.sms.v20210111.SmsClient;
|
|
||||||
import com.tencentcloudapi.sms.v20210111.models.*;
|
|
||||||
import lombok.Data;
|
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.time.LocalDateTime;
|
||||||
import java.util.List;
|
import java.util.*;
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
|
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.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.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT;
|
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT;
|
||||||
|
|
||||||
|
// TODO @scholar 建议参考 AliyunSmsClient 优化下
|
||||||
/**
|
/**
|
||||||
* 腾讯云短信功能实现
|
* 腾讯云短信功能实现
|
||||||
*
|
*
|
||||||
|
@ -41,11 +48,6 @@ public class TencentSmsClient extends AbstractSmsClient {
|
||||||
*/
|
*/
|
||||||
public static final String API_CODE_SUCCESS = "Ok";
|
public static final String API_CODE_SUCCESS = "Ok";
|
||||||
|
|
||||||
/**
|
|
||||||
* REGION,使用南京
|
|
||||||
*/
|
|
||||||
private static final String ENDPOINT = "ap-nanjing";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否国际/港澳台短信:
|
* 是否国际/港澳台短信:
|
||||||
*
|
*
|
||||||
|
@ -54,7 +56,6 @@ public class TencentSmsClient extends AbstractSmsClient {
|
||||||
*/
|
*/
|
||||||
private static final long INTERNATIONAL_CHINA = 0L;
|
private static final long INTERNATIONAL_CHINA = 0L;
|
||||||
|
|
||||||
private SmsClient client;
|
|
||||||
|
|
||||||
public TencentSmsClient(SmsChannelProperties properties) {
|
public TencentSmsClient(SmsChannelProperties properties) {
|
||||||
super(properties);
|
super(properties);
|
||||||
|
@ -64,9 +65,7 @@ public class TencentSmsClient extends AbstractSmsClient {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doInit() {
|
protected void doInit() {
|
||||||
// 实例化一个认证对象,入参需要传入腾讯云账户密钥对 secretId,secretKey
|
|
||||||
Credential credential = new Credential(getApiKey(), properties.getApiSecret());
|
|
||||||
client = new SmsClient(credential, ENDPOINT);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -97,18 +96,87 @@ public class TencentSmsClient extends AbstractSmsClient {
|
||||||
public SmsSendRespDTO sendSms(Long sendLogId, String mobile,
|
public SmsSendRespDTO sendSms(Long sendLogId, String mobile,
|
||||||
String apiTemplateId, List<KeyValue<String, Object>> templateParams) throws Throwable {
|
String apiTemplateId, List<KeyValue<String, Object>> templateParams) throws Throwable {
|
||||||
// 构建请求
|
// 构建请求
|
||||||
SendSmsRequest request = new SendSmsRequest();
|
TreeMap<String, Object> body = new TreeMap<>();
|
||||||
request.setSmsSdkAppId(getSdkAppId());
|
String[] phones = {mobile};
|
||||||
request.setPhoneNumberSet(new String[]{mobile});
|
body.put("PhoneNumberSet",phones);
|
||||||
request.setSignName(properties.getSignature());
|
body.put("SmsSdkAppId",getSdkAppId());
|
||||||
request.setTemplateId(apiTemplateId);
|
body.put("SignName",properties.getSignature());
|
||||||
request.setTemplateParamSet(ArrayUtils.toArray(templateParams, e -> String.valueOf(e.getValue())));
|
body.put("TemplateId",apiTemplateId);
|
||||||
request.setSessionContext(JsonUtils.toJsonString(new SessionContext().setLogId(sendLogId)));
|
body.put("TemplateParamSet",ArrayUtils.toArray(templateParams, e -> String.valueOf(e.getValue())));
|
||||||
// 执行请求
|
|
||||||
SendSmsResponse response = client.SendSms(request);
|
JSONObject JsonResponse = sendSmsRequest(body,"SendSms","2021-01-11","ap-guangzhou");
|
||||||
SendStatus status = response.getSendStatusSet()[0];
|
SmsResponse smsResponse = getSmsSendResponse(JsonResponse);
|
||||||
return new SmsSendRespDTO().setSuccess(Objects.equals(status.getCode(), API_CODE_SUCCESS)).setSerialNo(status.getSerialNo())
|
|
||||||
.setApiRequestId(response.getRequestId()).setApiCode(status.getCode()).setApiMsg(status.getMessage());
|
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
|
@Override
|
||||||
|
@ -123,18 +191,49 @@ public class TencentSmsClient extends AbstractSmsClient {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable {
|
public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable {
|
||||||
|
|
||||||
// 构建请求
|
// 构建请求
|
||||||
DescribeSmsTemplateListRequest request = new DescribeSmsTemplateListRequest();
|
TreeMap<String, Object> body = new TreeMap<>();
|
||||||
request.setTemplateIdSet(new Long[]{Long.parseLong(apiTemplateId)});
|
body.put("International",0);
|
||||||
request.setInternational(INTERNATIONAL_CHINA);
|
Integer[] templateIds = {Integer.valueOf(apiTemplateId)};
|
||||||
// 执行请求
|
body.put("TemplateIdSet",templateIds);
|
||||||
DescribeSmsTemplateListResponse response = client.DescribeSmsTemplateList(request);
|
|
||||||
DescribeTemplateListStatus status = response.getDescribeTemplateStatusSet()[0];
|
JSONObject JsonResponse = sendSmsRequest(body,"DescribeSmsTemplateList","2021-01-11","ap-guangzhou");
|
||||||
if (status == null || status.getStatusCode() == null) {
|
QuerySmsTemplateResponse smsTemplateResponse = getSmsTemplateResponse(JsonResponse);
|
||||||
return null;
|
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);
|
||||||
}
|
}
|
||||||
return new SmsTemplateRespDTO().setId(status.getTemplateId().toString()).setContent(status.getTemplateContent())
|
|
||||||
.setAuditStatus(convertSmsTemplateAuditStatus(status.getStatusCode().intValue())).setAuditReason(status.getReviewReply());
|
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
|
@VisibleForTesting
|
||||||
|
@ -147,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
|
@Data
|
||||||
private static class SmsReceiveStatus {
|
private static class SmsReceiveStatus {
|
||||||
|
|
||||||
|
|
|
@ -1,34 +1,27 @@
|
||||||
package cn.iocoder.yudao.module.system.framework.sms.core.client.impl;
|
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.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.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.SmsReceiveRespDTO;
|
||||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
|
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.client.dto.SmsTemplateRespDTO;
|
||||||
import cn.iocoder.yudao.module.system.framework.sms.core.client.impl.AliyunSmsClient;
|
|
||||||
import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
|
import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
|
||||||
import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties;
|
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 com.google.common.collect.Lists;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.mockito.ArgumentMatcher;
|
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.MockedStatic;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.List;
|
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.randomLongId;
|
||||||
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.junit.jupiter.api.Assertions.*;
|
||||||
import static org.mockito.ArgumentMatchers.argThat;
|
import static org.mockito.ArgumentMatchers.anyMap;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.Mockito.mockStatic;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link AliyunSmsClient} 的单元测试
|
* {@link AliyunSmsClient} 的单元测试
|
||||||
|
@ -45,9 +38,6 @@ public class AliyunSmsClientTest extends BaseMockitoUnitTest {
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
private final AliyunSmsClient smsClient = new AliyunSmsClient(properties);
|
private final AliyunSmsClient smsClient = new AliyunSmsClient(properties);
|
||||||
|
|
||||||
@Mock
|
|
||||||
private IAcsClient client;
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDoInit() {
|
public void testDoInit() {
|
||||||
// 准备参数
|
// 准备参数
|
||||||
|
@ -55,12 +45,11 @@ public class AliyunSmsClientTest extends BaseMockitoUnitTest {
|
||||||
|
|
||||||
// 调用
|
// 调用
|
||||||
smsClient.doInit();
|
smsClient.doInit();
|
||||||
// 断言
|
|
||||||
assertNotSame(client, ReflectUtil.getFieldValue(smsClient, "acsClient"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void tesSendSms_success() throws Throwable {
|
public void tesSendSms_success() throws Throwable {
|
||||||
|
try (MockedStatic<HttpUtils> httpUtilsMockedStatic = mockStatic(HttpUtils.class)) {
|
||||||
// 准备参数
|
// 准备参数
|
||||||
Long sendLogId = randomLongId();
|
Long sendLogId = randomLongId();
|
||||||
String mobile = randomString();
|
String mobile = randomString();
|
||||||
|
@ -68,29 +57,24 @@ public class AliyunSmsClientTest extends BaseMockitoUnitTest {
|
||||||
List<KeyValue<String, Object>> templateParams = Lists.newArrayList(
|
List<KeyValue<String, Object>> templateParams = Lists.newArrayList(
|
||||||
new KeyValue<>("code", 1234), new KeyValue<>("op", "login"));
|
new KeyValue<>("code", 1234), new KeyValue<>("op", "login"));
|
||||||
// mock 方法
|
// mock 方法
|
||||||
SendSmsResponse response = randomPojo(SendSmsResponse.class, o -> o.setCode("OK"));
|
httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString()))
|
||||||
when(client.getAcsResponse(argThat((ArgumentMatcher<SendSmsRequest>) acsRequest -> {
|
.thenReturn("{\"Message\":\"OK\",\"RequestId\":\"30067CE9-3710-5984-8881-909B21D8DB28\",\"Code\":\"OK\",\"BizId\":\"800025323183427988\"}");
|
||||||
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,
|
SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile,
|
||||||
apiTemplateId, templateParams);
|
apiTemplateId, templateParams);
|
||||||
// 断言
|
// 断言
|
||||||
assertTrue(result.getSuccess());
|
assertTrue(result.getSuccess());
|
||||||
assertEquals(response.getRequestId(), result.getApiRequestId());
|
assertEquals("30067CE9-3710-5984-8881-909B21D8DB28", result.getApiRequestId());
|
||||||
assertEquals(response.getCode(), result.getApiCode());
|
assertEquals("OK", result.getApiCode());
|
||||||
assertEquals(response.getMessage(), result.getApiMsg());
|
assertEquals("OK", result.getApiMsg());
|
||||||
assertEquals(response.getBizId(), result.getSerialNo());
|
assertEquals("800025323183427988", result.getSerialNo());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void tesSendSms_fail() throws Throwable {
|
public void tesSendSms_fail() throws Throwable {
|
||||||
|
try (MockedStatic<HttpUtils> httpUtilsMockedStatic = mockStatic(HttpUtils.class)) {
|
||||||
// 准备参数
|
// 准备参数
|
||||||
Long sendLogId = randomLongId();
|
Long sendLogId = randomLongId();
|
||||||
String mobile = randomString();
|
String mobile = randomString();
|
||||||
|
@ -98,24 +82,18 @@ public class AliyunSmsClientTest extends BaseMockitoUnitTest {
|
||||||
List<KeyValue<String, Object>> templateParams = Lists.newArrayList(
|
List<KeyValue<String, Object>> templateParams = Lists.newArrayList(
|
||||||
new KeyValue<>("code", 1234), new KeyValue<>("op", "login"));
|
new KeyValue<>("code", 1234), new KeyValue<>("op", "login"));
|
||||||
// mock 方法
|
// mock 方法
|
||||||
SendSmsResponse response = randomPojo(SendSmsResponse.class, o -> o.setCode("ERROR"));
|
httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString()))
|
||||||
when(client.getAcsResponse(argThat((ArgumentMatcher<SendSmsRequest>) acsRequest -> {
|
.thenReturn("{\"Message\":\"手机号码格式错误\",\"RequestId\":\"B7700B8E-227E-5886-9564-26036172F01F\",\"Code\":\"isv.MOBILE_NUMBER_ILLEGAL\"}");
|
||||||
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);
|
SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, apiTemplateId, templateParams);
|
||||||
// 断言
|
// 断言
|
||||||
assertFalse(result.getSuccess());
|
assertFalse(result.getSuccess());
|
||||||
assertEquals(response.getRequestId(), result.getApiRequestId());
|
assertEquals("B7700B8E-227E-5886-9564-26036172F01F", result.getApiRequestId());
|
||||||
assertEquals(response.getCode(), result.getApiCode());
|
assertEquals("isv.MOBILE_NUMBER_ILLEGAL", result.getApiCode());
|
||||||
assertEquals(response.getMessage(), result.getApiMsg());
|
assertEquals("手机号码格式错误", result.getApiMsg());
|
||||||
assertEquals(response.getBizId(), result.getSerialNo());
|
assertNull(result.getSerialNo());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -152,25 +130,21 @@ public class AliyunSmsClientTest extends BaseMockitoUnitTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetSmsTemplate() throws Throwable {
|
public void testGetSmsTemplate() throws Throwable {
|
||||||
|
try (MockedStatic<HttpUtils> httpUtilsMockedStatic = mockStatic(HttpUtils.class)) {
|
||||||
// 准备参数
|
// 准备参数
|
||||||
String apiTemplateId = randomString();
|
String apiTemplateId = randomString();
|
||||||
// mock 方法
|
// mock 方法
|
||||||
QuerySmsTemplateResponse response = randomPojo(QuerySmsTemplateResponse.class, o -> {
|
httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString()))
|
||||||
o.setCode("OK");
|
.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}");
|
||||||
o.setTemplateStatus(1); // 设置模板通过
|
|
||||||
});
|
|
||||||
when(client.getAcsResponse(argThat((ArgumentMatcher<QuerySmsTemplateRequest>) acsRequest -> {
|
|
||||||
assertEquals(apiTemplateId, acsRequest.getTemplateCode());
|
|
||||||
return true;
|
|
||||||
}))).thenReturn(response);
|
|
||||||
|
|
||||||
// 调用
|
// 调用
|
||||||
SmsTemplateRespDTO result = smsClient.getSmsTemplate(apiTemplateId);
|
SmsTemplateRespDTO result = smsClient.getSmsTemplate(apiTemplateId);
|
||||||
// 断言
|
// 断言
|
||||||
assertEquals(response.getTemplateCode(), result.getId());
|
assertEquals("SMS_207945135", result.getId());
|
||||||
assertEquals(response.getTemplateContent(), result.getContent());
|
assertEquals("您的验证码${code},该验证码5分钟内有效,请勿泄漏于他人!", result.getContent());
|
||||||
assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getAuditStatus());
|
assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getAuditStatus());
|
||||||
assertEquals(response.getReason(), result.getAuditReason());
|
assertEquals("无审批备注", result.getAuditReason());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -2,7 +2,9 @@ package cn.iocoder.yudao.module.system.framework.sms.core.client.impl;
|
||||||
|
|
||||||
import cn.hutool.core.collection.ListUtil;
|
import cn.hutool.core.collection.ListUtil;
|
||||||
import cn.iocoder.yudao.framework.common.core.KeyValue;
|
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.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 cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties;
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
@ -18,7 +20,7 @@ public class SmsClientTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Disabled
|
@Disabled
|
||||||
public void testHuaweiSmsClient() throws Throwable {
|
public void testHuaweiSmsClient_sendSms() throws Throwable {
|
||||||
SmsChannelProperties properties = new SmsChannelProperties()
|
SmsChannelProperties properties = new SmsChannelProperties()
|
||||||
.setApiKey("123")
|
.setApiKey("123")
|
||||||
.setApiSecret("456");
|
.setApiSecret("456");
|
||||||
|
@ -34,4 +36,68 @@ public class SmsClientTests {
|
||||||
System.out.println(smsSendRespDTO);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,36 +1,22 @@
|
||||||
package cn.iocoder.yudao.module.system.framework.sms.core.client.impl;
|
package cn.iocoder.yudao.module.system.framework.sms.core.client.impl;
|
||||||
|
|
||||||
import cn.hutool.core.util.ReflectUtil;
|
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.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.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.enums.SmsTemplateAuditStatusEnum;
|
||||||
import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties;
|
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.junit.jupiter.api.Test;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
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.randomString;
|
||||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
import static org.mockito.ArgumentMatchers.argThat;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
|
// TODO @芋艿:补全单测
|
||||||
/**
|
/**
|
||||||
* {@link TencentSmsClient} 的单元测试
|
* {@link TencentSmsClient} 的单元测试
|
||||||
*
|
*
|
||||||
|
@ -73,87 +59,87 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest {
|
||||||
assertNotSame(client, ReflectUtil.getFieldValue(smsClient, "client"));
|
assertNotSame(client, ReflectUtil.getFieldValue(smsClient, "client"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
// @Test
|
||||||
public void testDoSendSms_success() throws Throwable {
|
// public void testDoSendSms_success() throws Throwable {
|
||||||
// 准备参数
|
// // 准备参数
|
||||||
Long sendLogId = randomLongId();
|
// Long sendLogId = randomLongId();
|
||||||
String mobile = randomString();
|
// String mobile = randomString();
|
||||||
String apiTemplateId = randomString();
|
// String apiTemplateId = randomString();
|
||||||
List<KeyValue<String, Object>> templateParams = Lists.newArrayList(
|
// List<KeyValue<String, Object>> templateParams = Lists.newArrayList(
|
||||||
new KeyValue<>("1", 1234), new KeyValue<>("2", "login"));
|
// new KeyValue<>("1", 1234), new KeyValue<>("2", "login"));
|
||||||
String requestId = randomString();
|
// String requestId = randomString();
|
||||||
String serialNo = randomString();
|
// String serialNo = randomString();
|
||||||
// mock 方法
|
// // mock 方法
|
||||||
SendSmsResponse response = randomPojo(SendSmsResponse.class, o -> {
|
// SendSmsResponse response = randomPojo(SendSmsResponse.class, o -> {
|
||||||
o.setRequestId(requestId);
|
// o.setRequestId(requestId);
|
||||||
SendStatus[] sendStatuses = new SendStatus[1];
|
// SendStatus[] sendStatuses = new SendStatus[1];
|
||||||
o.setSendStatusSet(sendStatuses);
|
// o.setSendStatusSet(sendStatuses);
|
||||||
SendStatus sendStatus = new SendStatus();
|
// SendStatus sendStatus = new SendStatus();
|
||||||
sendStatuses[0] = sendStatus;
|
// sendStatuses[0] = sendStatus;
|
||||||
sendStatus.setCode(TencentSmsClient.API_CODE_SUCCESS);
|
// sendStatus.setCode(TencentSmsClient.API_CODE_SUCCESS);
|
||||||
sendStatus.setMessage("send success");
|
// sendStatus.setMessage("send success");
|
||||||
sendStatus.setSerialNo(serialNo);
|
// sendStatus.setSerialNo(serialNo);
|
||||||
});
|
// });
|
||||||
when(client.SendSms(argThat(request -> {
|
// when(client.SendSms(argThat(request -> {
|
||||||
assertEquals(mobile, request.getPhoneNumberSet()[0]);
|
// assertEquals(mobile, request.getPhoneNumberSet()[0]);
|
||||||
assertEquals(properties.getSignature(), request.getSignName());
|
// assertEquals(properties.getSignature(), request.getSignName());
|
||||||
assertEquals(apiTemplateId, request.getTemplateId());
|
// assertEquals(apiTemplateId, request.getTemplateId());
|
||||||
assertEquals(toJsonString(ArrayUtils.toArray(new ArrayList<>(MapUtils.convertMap(templateParams).values()), String::valueOf)),
|
// assertEquals(toJsonString(ArrayUtils.toArray(new ArrayList<>(MapUtils.convertMap(templateParams).values()), String::valueOf)),
|
||||||
toJsonString(request.getTemplateParamSet()));
|
// toJsonString(request.getTemplateParamSet()));
|
||||||
assertEquals(sendLogId, ReflectUtil.getFieldValue(JsonUtils.parseObject(request.getSessionContext(), TencentSmsClient.SessionContext.class), "logId"));
|
// assertEquals(sendLogId, ReflectUtil.getFieldValue(JsonUtils.parseObject(request.getSessionContext(), TencentSmsClient.SessionContext.class), "logId"));
|
||||||
return true;
|
// return true;
|
||||||
}))).thenReturn(response);
|
// }))).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
|
||||||
SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, apiTemplateId, templateParams);
|
// public void testDoSendSms_fail() throws Throwable {
|
||||||
// 断言
|
// // 准备参数
|
||||||
assertTrue(result.getSuccess());
|
// Long sendLogId = randomLongId();
|
||||||
assertEquals(response.getRequestId(), result.getApiRequestId());
|
// String mobile = randomString();
|
||||||
assertEquals(response.getSendStatusSet()[0].getCode(), result.getApiCode());
|
// String apiTemplateId = randomString();
|
||||||
assertEquals(response.getSendStatusSet()[0].getMessage(), result.getApiMsg());
|
// List<KeyValue<String, Object>> templateParams = Lists.newArrayList(
|
||||||
assertEquals(response.getSendStatusSet()[0].getSerialNo(), result.getSerialNo());
|
// new KeyValue<>("1", 1234), new KeyValue<>("2", "login"));
|
||||||
}
|
// String requestId = randomString();
|
||||||
|
// String serialNo = randomString();
|
||||||
@Test
|
// // mock 方法
|
||||||
public void testDoSendSms_fail() throws Throwable {
|
// SendSmsResponse response = randomPojo(SendSmsResponse.class, o -> {
|
||||||
// 准备参数
|
// o.setRequestId(requestId);
|
||||||
Long sendLogId = randomLongId();
|
// SendStatus[] sendStatuses = new SendStatus[1];
|
||||||
String mobile = randomString();
|
// o.setSendStatusSet(sendStatuses);
|
||||||
String apiTemplateId = randomString();
|
// SendStatus sendStatus = new SendStatus();
|
||||||
List<KeyValue<String, Object>> templateParams = Lists.newArrayList(
|
// sendStatuses[0] = sendStatus;
|
||||||
new KeyValue<>("1", 1234), new KeyValue<>("2", "login"));
|
// sendStatus.setCode("ERROR");
|
||||||
String requestId = randomString();
|
// sendStatus.setMessage("send success");
|
||||||
String serialNo = randomString();
|
// sendStatus.setSerialNo(serialNo);
|
||||||
// mock 方法
|
// });
|
||||||
SendSmsResponse response = randomPojo(SendSmsResponse.class, o -> {
|
// when(client.SendSms(argThat(request -> {
|
||||||
o.setRequestId(requestId);
|
// assertEquals(mobile, request.getPhoneNumberSet()[0]);
|
||||||
SendStatus[] sendStatuses = new SendStatus[1];
|
// assertEquals(properties.getSignature(), request.getSignName());
|
||||||
o.setSendStatusSet(sendStatuses);
|
// assertEquals(apiTemplateId, request.getTemplateId());
|
||||||
SendStatus sendStatus = new SendStatus();
|
// assertEquals(toJsonString(ArrayUtils.toArray(new ArrayList<>(MapUtils.convertMap(templateParams).values()), String::valueOf)),
|
||||||
sendStatuses[0] = sendStatus;
|
// toJsonString(request.getTemplateParamSet()));
|
||||||
sendStatus.setCode("ERROR");
|
// assertEquals(sendLogId, ReflectUtil.getFieldValue(JsonUtils.parseObject(request.getSessionContext(), TencentSmsClient.SessionContext.class), "logId"));
|
||||||
sendStatus.setMessage("send success");
|
// return true;
|
||||||
sendStatus.setSerialNo(serialNo);
|
// }))).thenReturn(response);
|
||||||
});
|
//
|
||||||
when(client.SendSms(argThat(request -> {
|
// // 调用
|
||||||
assertEquals(mobile, request.getPhoneNumberSet()[0]);
|
// SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, apiTemplateId, templateParams);
|
||||||
assertEquals(properties.getSignature(), request.getSignName());
|
// // 断言
|
||||||
assertEquals(apiTemplateId, request.getTemplateId());
|
// assertFalse(result.getSuccess());
|
||||||
assertEquals(toJsonString(ArrayUtils.toArray(new ArrayList<>(MapUtils.convertMap(templateParams).values()), String::valueOf)),
|
// assertEquals(response.getRequestId(), result.getApiRequestId());
|
||||||
toJsonString(request.getTemplateParamSet()));
|
// assertEquals(response.getSendStatusSet()[0].getCode(), result.getApiCode());
|
||||||
assertEquals(sendLogId, ReflectUtil.getFieldValue(JsonUtils.parseObject(request.getSessionContext(), TencentSmsClient.SessionContext.class), "logId"));
|
// assertEquals(response.getSendStatusSet()[0].getMessage(), result.getApiMsg());
|
||||||
return true;
|
// assertEquals(response.getSendStatusSet()[0].getSerialNo(), result.getSerialNo());
|
||||||
}))).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
|
@Test
|
||||||
public void testParseSmsReceiveStatus() {
|
public void testParseSmsReceiveStatus() {
|
||||||
|
@ -185,35 +171,35 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest {
|
||||||
assertEquals(67890L, statuses.get(0).getLogId());
|
assertEquals(67890L, statuses.get(0).getLogId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
// @Test
|
||||||
public void testGetSmsTemplate() throws Throwable {
|
// public void testGetSmsTemplate() throws Throwable {
|
||||||
// 准备参数
|
// // 准备参数
|
||||||
Long apiTemplateId = randomLongId();
|
// Long apiTemplateId = randomLongId();
|
||||||
String requestId = randomString();
|
// String requestId = randomString();
|
||||||
|
//
|
||||||
// mock 方法
|
// // mock 方法
|
||||||
DescribeSmsTemplateListResponse response = randomPojo(DescribeSmsTemplateListResponse.class, o -> {
|
// DescribeSmsTemplateListResponse response = randomPojo(DescribeSmsTemplateListResponse.class, o -> {
|
||||||
DescribeTemplateListStatus[] describeTemplateListStatuses = new DescribeTemplateListStatus[1];
|
// DescribeTemplateListStatus[] describeTemplateListStatuses = new DescribeTemplateListStatus[1];
|
||||||
DescribeTemplateListStatus templateStatus = new DescribeTemplateListStatus();
|
// DescribeTemplateListStatus templateStatus = new DescribeTemplateListStatus();
|
||||||
templateStatus.setTemplateId(apiTemplateId);
|
// templateStatus.setTemplateId(apiTemplateId);
|
||||||
templateStatus.setStatusCode(0L);// 设置模板通过
|
// templateStatus.setStatusCode(0L);// 设置模板通过
|
||||||
describeTemplateListStatuses[0] = templateStatus;
|
// describeTemplateListStatuses[0] = templateStatus;
|
||||||
o.setDescribeTemplateStatusSet(describeTemplateListStatuses);
|
// o.setDescribeTemplateStatusSet(describeTemplateListStatuses);
|
||||||
o.setRequestId(requestId);
|
// o.setRequestId(requestId);
|
||||||
});
|
// });
|
||||||
when(client.DescribeSmsTemplateList(argThat(request -> {
|
// when(client.DescribeSmsTemplateList(argThat(request -> {
|
||||||
assertEquals(apiTemplateId, request.getTemplateIdSet()[0]);
|
// assertEquals(apiTemplateId, request.getTemplateIdSet()[0]);
|
||||||
return true;
|
// return true;
|
||||||
}))).thenReturn(response);
|
// }))).thenReturn(response);
|
||||||
|
//
|
||||||
// 调用
|
// // 调用
|
||||||
SmsTemplateRespDTO result = smsClient.getSmsTemplate(apiTemplateId.toString());
|
// SmsTemplateRespDTO result = smsClient.getSmsTemplate(apiTemplateId.toString());
|
||||||
// 断言
|
// // 断言
|
||||||
assertEquals(response.getDescribeTemplateStatusSet()[0].getTemplateId().toString(), result.getId());
|
// assertEquals(response.getDescribeTemplateStatusSet()[0].getTemplateId().toString(), result.getId());
|
||||||
assertEquals(response.getDescribeTemplateStatusSet()[0].getTemplateContent(), result.getContent());
|
// assertEquals(response.getDescribeTemplateStatusSet()[0].getTemplateContent(), result.getContent());
|
||||||
assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getAuditStatus());
|
// assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getAuditStatus());
|
||||||
assertEquals(response.getDescribeTemplateStatusSet()[0].getReviewReply(), result.getAuditReason());
|
// assertEquals(response.getDescribeTemplateStatusSet()[0].getReviewReply(), result.getAuditReason());
|
||||||
}
|
// }
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testConvertSmsTemplateAuditStatus() {
|
public void testConvertSmsTemplateAuditStatus() {
|
||||||
|
|
Loading…
Reference in New Issue