diff --git a/pom.xml b/pom.xml index ee1fafe1..e035be4f 100644 --- a/pom.xml +++ b/pom.xml @@ -17,8 +17,9 @@ sms4j-provider sms4j-core sms4j-spring-boot-starter - sms4j-solon-plugin sms4j-spring-boot-example + sms4j-solon-plugin + sms4j-solon-plugin-example sms4j-javase-plugin sms4j-Email-plugin sms4j-oa-plugin @@ -52,7 +53,7 @@ - 3.2.1 + 3.2.1-SNAPSHOT UTF-8 UTF-8 @@ -60,7 +61,7 @@ 2.6.5 3.17.0 1.3.3 - 5.8.26 + 5.8.28 2.3.0 1.1.1 1.6.2 @@ -242,20 +243,20 @@ - - - org.apache.maven.plugins - maven-gpg-plugin - 1.6 - - - verify - - sign - - - - + + + + + + + + + + + + + + org.apache.maven.plugins diff --git a/sms4j-Email-plugin/sms4j-Email-core/src/main/java/org/dromara/email/core/factory/MailFactory.java b/sms4j-Email-plugin/sms4j-Email-core/src/main/java/org/dromara/email/core/factory/MailFactory.java index f76a8b0b..ac745b33 100644 --- a/sms4j-Email-plugin/sms4j-Email-core/src/main/java/org/dromara/email/core/factory/MailFactory.java +++ b/sms4j-Email-plugin/sms4j-Email-core/src/main/java/org/dromara/email/core/factory/MailFactory.java @@ -17,7 +17,7 @@ import java.util.Map; * 2023/6/8 22:35 **/ public class MailFactory{ - private static final Map configs = new HashMap<>(); + private static final Map CONFIGS = new HashMap<>(); /** * createMailClient @@ -27,7 +27,7 @@ public class MailFactory{ */ public static MailClient createMailClient(Object key){ try { - return MailBuild.build(configs.get(key)); + return MailBuild.build(CONFIGS.get(key)); } catch (MessagingException e) { throw new MailException(e); } @@ -43,7 +43,7 @@ public class MailFactory{ */ public static MailClient createMailClient(Object key, Blacklist blacklist){ try { - return MailBuild.build(configs.get(key),blacklist); + return MailBuild.build(CONFIGS.get(key),blacklist); } catch (MessagingException e) { throw new MailException(e); } @@ -57,7 +57,7 @@ public class MailFactory{ * @author :Wind */ public static void put(Object key, MailSmtpConfig config){ - configs.put(key,config); + CONFIGS.put(key,config); } } diff --git a/sms4j-api/src/main/java/org/dromara/sms4j/api/SmsBlend.java b/sms4j-api/src/main/java/org/dromara/sms4j/api/SmsBlend.java index 3141103d..3fd56907 100644 --- a/sms4j-api/src/main/java/org/dromara/sms4j/api/SmsBlend.java +++ b/sms4j-api/src/main/java/org/dromara/sms4j/api/SmsBlend.java @@ -43,12 +43,13 @@ public interface SmsBlend { SmsResponse sendMessage(String phone, String message); /** - * sendMessage + * sendMessage *

说明:发送固定消息模板多模板参数短信 - * @param phone 接收短信的手机号 + * + * @param phone 接收短信的手机号 * @param messages 模板内容 * @author :Wind - */ + */ SmsResponse sendMessage(String phone, LinkedHashMap messages); /** @@ -190,7 +191,7 @@ public interface SmsBlend { * @param phones 需要加入黑名单的手机号数组 * @author :sh1yu */ - default void batchJoinBlacklist(List phones) { + default void batchJoinBlacklist(List phones) { } /** @@ -200,6 +201,6 @@ public interface SmsBlend { * @param phones 需要移除黑名单的手机号数组 * @author :sh1yu */ - default void batchRemovalFromBlacklist(List phones) { + default void batchRemovalFromBlacklist(List phones) { } } diff --git a/sms4j-api/src/main/java/org/dromara/sms4j/api/utils/SmsRespUtils.java b/sms4j-api/src/main/java/org/dromara/sms4j/api/utils/SmsRespUtils.java new file mode 100644 index 00000000..865c80c2 --- /dev/null +++ b/sms4j-api/src/main/java/org/dromara/sms4j/api/utils/SmsRespUtils.java @@ -0,0 +1,52 @@ +package org.dromara.sms4j.api.utils; + +import org.dromara.sms4j.api.entity.SmsResponse; + +public class SmsRespUtils { + private SmsRespUtils() { + } //私有构造防止实例化 + + public static SmsResponse error(){ + return error("error no response", null); + } + + public static SmsResponse error(String configId){ + return error("error no response", configId); + } + + public static SmsResponse error(String detailMessage, String configId){ + return resp(detailMessage, false, configId); + } + + public static SmsResponse success(){ + return success(null); + } + + public static SmsResponse success(Object data){ + return success(data, null); + } + + public static SmsResponse resp(Object data, boolean success){ + return resp(data, success, null); + } + + public static SmsResponse success(Object data, String configId){ + return resp(data, true, configId); + } + + public static SmsResponse resp(boolean success){ + return success ? success() : error(); + } + + public static SmsResponse resp(boolean success, String configId){ + return resp(null, success, configId); + } + + public static SmsResponse resp(Object data, boolean success, String configId){ + SmsResponse error = new SmsResponse(); + error.setSuccess(success); + error.setData(data); + error.setConfigId(configId); + return error; + } +} diff --git a/sms4j-comm/src/main/java/org/dromara/sms4j/comm/constant/Constant.java b/sms4j-comm/src/main/java/org/dromara/sms4j/comm/constant/Constant.java index 0a1bae69..2526e7db 100644 --- a/sms4j-comm/src/main/java/org/dromara/sms4j/comm/constant/Constant.java +++ b/sms4j-comm/src/main/java/org/dromara/sms4j/comm/constant/Constant.java @@ -17,21 +17,45 @@ public abstract class Constant { * 用于格式化鉴权头域,给"Authorization"参数赋值 */ public static final String HUAWEI_AUTH_HEADER_VALUE = "WSSE realm=\"SDP\",profile=\"UsernameToken\",type=\"Appkey\""; + /** * 用于格式化鉴权头域,给"X-WSSE"参数赋值 */ public static final String HUAWEI_WSSE_HEADER_FORMAT = "UsernameToken Username=\"%s\",PasswordDigest=\"%s\",Nonce=\"%s\",Created=\"%s\""; + /** * 华为云国内短信访问URI */ public static final String HUAWEI_REQUEST_URL = "/sms/batchSendSms/v1"; + /** * Content-Type */ - public static final String FROM_URLENCODED = "application/x-www-form-urlencoded"; + public static final String CONTENT_TYPE = "Content-Type"; - public static final String ACCEPT = "application/json"; + /** + * Authorization + */ + public static final String AUTHORIZATION = "Authorization"; + /** + * Accept + */ + public static final String ACCEPT = "Accept"; + + /** + * x-www-form-urlencoded + */ + public static final String APPLICATION_FROM_URLENCODED = "application/x-www-form-urlencoded"; + + /** + * application/json + */ + public static final String APPLICATION_JSON = "application/json"; + + /** + * application/json; charset=utf-8 + */ public static final String APPLICATION_JSON_UTF8 = "application/json; charset=utf-8"; /** @@ -42,7 +66,7 @@ public abstract class Constant { /** * 云片短信国内短信请求地址 */ - public static final String YUNPIAN_URL = "https://sms.yunpian.com/v2"; + public static final String YUNPIAN_URL = Constant.HTTPS_PREFIX + "sms.yunpian.com/v2"; /** * https请求前缀 diff --git a/sms4j-comm/src/main/java/org/dromara/sms4j/comm/constant/SupplierConstant.java b/sms4j-comm/src/main/java/org/dromara/sms4j/comm/constant/SupplierConstant.java index a09dfa74..59de4921 100644 --- a/sms4j-comm/src/main/java/org/dromara/sms4j/comm/constant/SupplierConstant.java +++ b/sms4j-comm/src/main/java/org/dromara/sms4j/comm/constant/SupplierConstant.java @@ -10,7 +10,7 @@ public abstract class SupplierConstant { */ public static final String ALIBABA = "alibaba"; /** - * 容连云 + * 容联云 */ public static final String CLOOPEN = "cloopen"; /** @@ -49,20 +49,48 @@ public abstract class SupplierConstant { * 助通 */ public static final String ZHUTONG = "zhutong"; - /** * 联麓 */ public static final String LIANLU = "lianlu"; - /** * 鼎众 */ public static final String DINGZHONG = "dingzhong"; - /** - * 七牛 + * 七牛云 */ public static final String QINIU = "qiniu"; - + /** + * 创蓝 + */ + public static final String CHUANGLAN = "chuanglan"; + /** + * 极光 + */ + public static final String JIGUANG = "jiguang"; + /** + * 布丁云V2 + */ + public static final String BUDING_V2 = "buding_v2"; + /** + * 中国移动 云MAS + */ + public static final String MAS = "mas"; + /** + * 百度云 sms + */ + public static final String BAIDU = "baidu"; + /** + * 螺丝帽 sms + */ + public static final String LUO_SI_MAO = "luosimao"; + /** + * SUBMAIL sms + */ + public static final String MY_SUBMAIL = "mysubmail"; + /** + * danmi sms + */ + public static final String DAN_MI = "danmi"; } diff --git a/sms4j-comm/src/main/java/org/dromara/sms4j/comm/enumerate/ConfigType.java b/sms4j-comm/src/main/java/org/dromara/sms4j/comm/enums/ConfigType.java similarity index 88% rename from sms4j-comm/src/main/java/org/dromara/sms4j/comm/enumerate/ConfigType.java rename to sms4j-comm/src/main/java/org/dromara/sms4j/comm/enums/ConfigType.java index 91beb1be..470424fe 100644 --- a/sms4j-comm/src/main/java/org/dromara/sms4j/comm/enumerate/ConfigType.java +++ b/sms4j-comm/src/main/java/org/dromara/sms4j/comm/enums/ConfigType.java @@ -1,4 +1,4 @@ -package org.dromara.sms4j.comm.enumerate; +package org.dromara.sms4j.comm.enums; import lombok.Getter; diff --git a/sms4j-comm/src/main/java/org/dromara/sms4j/comm/utils/SmsDateUtils.java b/sms4j-comm/src/main/java/org/dromara/sms4j/comm/utils/SmsDateUtils.java new file mode 100644 index 00000000..1355f7ca --- /dev/null +++ b/sms4j-comm/src/main/java/org/dromara/sms4j/comm/utils/SmsDateUtils.java @@ -0,0 +1,215 @@ +package org.dromara.sms4j.comm.utils; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +/** + *

类名: SmsDateUtils + *

说明: 时间日期工具类 + * + * @author :bleachtred + * 2024/6/21 23:59 + **/ +public class SmsDateUtils extends DateUtil { + + private SmsDateUtils() { + } + + /** + * 格林威治标准时间(GMT)或世界协调时间(UTC) + */ + private static final String GMT = "GMT"; + + /** + * 东八区 + */ + private static final String GMT_8 = SmsDateUtils.GMT + "+8:00"; + + /** + * 天翼云、七牛云时间格式 + */ + private static final String PURE_DATE_UTC_PATTERN = "yyyyMMdd'T'HHmmss'Z'"; + + /** + * 获取格林威治标准时间(GMT)或世界协调时间(UTC) + * @return TimeZone + */ + public static TimeZone gmt(){ + return getTimeZone(GMT); + } + + /** + * 获取东八区时区 + * @return TimeZone + */ + public static TimeZone gmt8(){ + return getTimeZone(GMT_8); + } + + /** + * 获取时区 + * @param zoneId zoneId + * @return TimeZone + */ + public static TimeZone getTimeZone(String zoneId){ + return TimeZone.getTimeZone(zoneId); + } + + /** + * 获取SimpleDateFormat + * @param pattern 时间格式 + * @return SimpleDateFormat + */ + public static SimpleDateFormat sdfGmt(String pattern){ + return sdf(pattern, gmt()); + } + + /** + * 获取SimpleDateFormat + * @param pattern 时间格式 + * @return SimpleDateFormat + */ + public static SimpleDateFormat sdfGmt8(String pattern){ + return sdf(pattern, gmt8()); + } + + /** + * 获取SimpleDateFormat + * @param pattern 时间格式 + * @param timeZone 时区 + * @return 获取SimpleDateFormat + */ + public static SimpleDateFormat sdf(String pattern, TimeZone timeZone){ + SimpleDateFormat sdf = new SimpleDateFormat(pattern); + sdf.setTimeZone(timeZone); + return sdf; + } + + /** + * 格式化时间 + * @param date 时间 + * @param pattern 时间格式 + * @return String + */ + public static String formatGmtDateToStr(Date date, String pattern){ + SimpleDateFormat sdf = sdfGmt(pattern); + return sdf.format(date); + } + + /** + * 格式化时间 + * @param date 时间 + * @param pattern 时间格式 + * @return String + */ + public static String formatGmt8DateToStr(Date date, String pattern){ + SimpleDateFormat sdf = sdfGmt8(pattern); + return sdf.format(date); + } + + /** + * 格式化时间 + * @param date 时间 + * @param pattern 时间格式 + * @param timeZone 时区 + * @return String + */ + public static String formatDateToStr(Date date, String pattern, TimeZone timeZone){ + SimpleDateFormat sdf = sdf(pattern, timeZone); + return sdf.format(date); + } + + /** + * 日期格式:yyyy-MM-dd'T'HH:mm:ss'Z' + * @param date 时间 + * @return 时间字符串 + */ + public static String utcGmt(Date date){ + return formatGmtDateToStr(date, DatePattern.UTC_PATTERN); + } + + /** + * 日期格式:yyyy-MM-dd'T'HH:mm:ss'Z' + * @param date 时间 + * @return 时间字符串 + */ + public static String utcGmt8(Date date){ + return formatGmt8DateToStr(date, DatePattern.UTC_PATTERN); + } + + /** + * 日期格式:yyyyMMdd + * @param date 时间 + * @return 时间字符串 + */ + public static String pureDateGmt(Date date){ + return formatGmtDateToStr(date, DatePattern.PURE_DATE_PATTERN); + } + + /** + * 日期格式:yyyyMMdd + * @param date 时间 + * @return 时间字符串 + */ + public static String pureDateGmt8(Date date){ + return formatGmt8DateToStr(date, DatePattern.PURE_DATE_PATTERN); + } + + /** + * 天翼云、七牛云时间格式:yyyyMMdd'T'HHmmss'Z' + * @param date 时间 + * @return 时间字符串 + */ + public static String pureDateUtcGmt(Date date){ + return formatGmtDateToStr(date, PURE_DATE_UTC_PATTERN); + } + + /** + * 天翼云、七牛云时间格式:yyyyMMdd'T'HHmmss'Z' + * @param date 时间 + * @return 时间字符串 + */ + public static String pureDateUtcGmt8(Date date){ + return formatGmt8DateToStr(date, PURE_DATE_UTC_PATTERN); + } + + /** + * 日期格式:yyyy-MM-dd + * @param date 时间 + * @return 时间字符串 + */ + public static String normDateGmt(Date date){ + return formatGmtDateToStr(date, DatePattern.NORM_DATE_PATTERN); + } + + /** + * 日期格式:yyyy-MM-dd + * @param date 时间 + * @return 时间字符串 + */ + public static String normDateGmt8(Date date){ + return formatGmt8DateToStr(date, DatePattern.NORM_DATE_PATTERN); + } + + /** + * 日期格式:yyyy-MM-dd HH:mm:ss + * @param date 时间 + * @return 时间字符串 + */ + public static String normDatetimeGmt(Date date){ + return formatGmtDateToStr(date, DatePattern.NORM_DATETIME_PATTERN); + } + + /** + * 日期格式:yyyy-MM-dd HH:mm:ss + * @param date 时间 + * @return 时间字符串 + */ + public static String normDatetimeGmt8(Date date){ + return formatGmt8DateToStr(date, DatePattern.NORM_DATETIME_PATTERN); + } +} diff --git a/sms4j-comm/src/main/java/org/dromara/sms4j/comm/utils/SmsHttpUtils.java b/sms4j-comm/src/main/java/org/dromara/sms4j/comm/utils/SmsHttpUtils.java index 778a3b1d..7048e0e4 100644 --- a/sms4j-comm/src/main/java/org/dromara/sms4j/comm/utils/SmsHttpUtils.java +++ b/sms4j-comm/src/main/java/org/dromara/sms4j/comm/utils/SmsHttpUtils.java @@ -73,6 +73,27 @@ public class SmsHttpUtils { } } + /** + * 发送post form 请求 + * + * @param url 请求地址 + * @param headers 请求头 + * @param body 请求体(map格式请求体) + * @param username 用户名 + * @param password 密码 + * @return 返回体 + */ + public JSONObject postBasicFrom(String url, Map headers, String username, String password, Map body) { + try (HttpResponse response = HttpRequest.post(url) + .addHeaders(headers) + .basicAuth(username, password) + .form(body) + .execute()) { + return JSONUtil.parseObj(response.body()); + } catch (Exception e) { + throw new SmsBlendException(e.getMessage()); + } + } /** * 发送post url 参数拼装url传输 @@ -93,6 +114,37 @@ public class SmsHttpUtils { } } + /** + * 发送get + * + * @param url 请求地址 + * @return 返回体 + */ + public JSONObject getBasic(String url, String username, String password) { + try (HttpResponse response = HttpRequest.get(url) + .basicAuth(username, password) + .execute()) { + return JSONUtil.parseObj(response.body()); + } catch (Exception e) { + throw new SmsBlendException(e.getMessage()); + } + } + + /** + * 发送get + * + * @param url 请求地址 + * @return 返回体 + */ + public JSONObject getUrl(String url) { + try (HttpResponse response = HttpRequest.get(url) + .execute()) { + return JSONUtil.parseObj(response.body()); + } catch (Exception e) { + throw new SmsBlendException(e.getMessage()); + } + } + /** * 线程睡眠 * diff --git a/sms4j-comm/src/main/java/org/dromara/sms4j/comm/utils/SmsUtils.java b/sms4j-comm/src/main/java/org/dromara/sms4j/comm/utils/SmsUtils.java index b4f3de24..6b25a326 100644 --- a/sms4j-comm/src/main/java/org/dromara/sms4j/comm/utils/SmsUtils.java +++ b/sms4j-comm/src/main/java/org/dromara/sms4j/comm/utils/SmsUtils.java @@ -2,15 +2,17 @@ package org.dromara.sms4j.comm.utils; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.RandomUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONUtil; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; +import java.util.*; +import java.util.function.Function; +import java.util.function.Predicate; /** * @author wind @@ -113,18 +115,38 @@ public class SmsUtils { * @param list 要转换的list * @author :Wind */ - public static String listToString(List list) { - return CollUtil.join(list, ","); + public static String joinComma(List list) { + return CollUtil.join(list, StrUtil.COMMA); } /** - * 以 conjunction 为分隔符将集合转换为字符串 + * 切分字符串 * - * @param list 集合 + * @param str 被切分的字符串 + * @return 分割后的数据列表 + */ + public static List splitTrimComma(String str) { + return StrUtil.splitTrim(str, StrUtil.COMMA); + } + + /** + * 将手机号码 添加+86中国的电话国际区号前缀 + * + * @param phones 手机号码集合 * @return 结果字符串 */ - public static String arrayToString(List list) { - return CollUtil.join(list, ",", str -> StrUtil.addPrefixIfNot(str, "+86")); + public static String addCodePrefixIfNot(List phones) { + return CollUtil.join(phones, StrUtil.COMMA, SmsUtils::addCodePrefixIfNot); + } + + /** + * 将手机号码 添加+86电话区号前缀 + * + * @param phone 手机号码 + * @return 结果字符串 + */ + public static String addCodePrefixIfNot(String phone) { + return StrUtil.addPrefixIfNot(phone, "+86"); } /** @@ -133,10 +155,10 @@ public class SmsUtils { * @param list 集合 * @return 结果字符串 */ - public static String[] listToArray(List list) { + public static String[] addCodePrefixIfNotToArray(List list) { List toStr = new ArrayList<>(); for (String s : list) { - toStr.add(StrUtil.addPrefixIfNot(s, "+86")); + toStr.add(addCodePrefixIfNot(s)); } return toStr.toArray(new String[list.size()]); } @@ -144,20 +166,20 @@ public class SmsUtils { /** * 将Map中所有key的分隔符转换为新的分隔符 * @param map map对象 - * @param seperator 旧分隔符 - * @param newSeperator 新分隔符 + * @param separator 旧分隔符 + * @param newSeparator 新分隔符 */ - public static void replaceKeysSeperator(Map map, String seperator, String newSeperator) { + public static void replaceKeysSeparator(Map map, String separator, String newSeparator) { if(CollUtil.isEmpty(map)) { return; } List keySet = new ArrayList<>(map.keySet()); for(String key : keySet) { - if(StrUtil.isEmpty(key) || !key.contains(seperator)) { + if(StrUtil.isEmpty(key) || !key.contains(separator)) { continue; } String value = String.valueOf(map.get(key)); - String newKey = key.replaceAll(seperator, newSeperator); + String newKey = key.replaceAll(separator, newSeparator); map.putIfAbsent(newKey, value); map.remove(key); } @@ -172,4 +194,76 @@ public class SmsUtils { } } + public static LinkedHashMap buildMessageByAmpersand(String message) { + if (isEmpty(message)){ + return new LinkedHashMap<>(); + } + String[] split = message.split("&"); + LinkedHashMap map = new LinkedHashMap<>(split.length); + for (int i = 0; i < split.length; i++) { + map.put(String.valueOf(i), split[i]); + } + return map; + } + + /** + * 将任意类型集合转成想要的数组 + * @param list 需要转换的集合 + * @param predicate 过滤条件 + * @param mapper 对此流的元素执行函数 + * @param array 想要的数组 + * @return 数组 + * @param 集合泛型 + * @param 想要的数组类型 + */ + public static E[] toArray(Collection list, Predicate predicate, Function mapper, E[] array) { + if (isEmpty(list)) { + return array.clone(); + } + return list.stream().filter(predicate).map(mapper).toArray(size -> array.clone()); + } + + /** + * 将map的value转成数组 + * @param map Map + * @return 数组 + */ + public static String[] toArray(Map map){ + if (isEmpty(map)) { + return new String[0]; + } + return toArray(map.values(), SmsUtils::isNotEmpty, s -> s, new String[0]); + } + + /** + * 将所有提交的参数升序排列,并排除部分key字段后,将key与value用"="连接起来 组成"key=value" + "&"(连接符)+ "key=value" 的方式 + * @param params 参数Map + * @param excludes 排除的key + * @return String + */ + public static String sortedParamsAsc(Map params, String... excludes) { + if (MapUtil.isEmpty(params)){ + return StrUtil.EMPTY; + } + List keys = new ArrayList<>(params.keySet()); + if (CollUtil.isEmpty(keys)){ + return StrUtil.EMPTY; + } + if (ArrayUtil.isNotEmpty(excludes)){ + ArrayList excludeKeys = CollUtil.toList(excludes); + keys.removeIf(key -> excludeKeys.stream().anyMatch(exclude -> exclude.equals(key))); + if (CollUtil.isEmpty(keys)){ + return StrUtil.EMPTY; + } + } + Collections.sort(keys); + StringBuilder sb = new StringBuilder(); + for (String key : keys) { + sb.append(key).append("=").append(Convert.toStr(params.get(key))).append("&"); + } + if (sb.length() > 0) { + sb.setLength(sb.length() - 1); // Remove the last '&' + } + return sb.toString(); + } } \ No newline at end of file diff --git a/sms4j-core/src/main/java/org/dromara/sms4j/core/load/SmsLoad.java b/sms4j-core/src/main/java/org/dromara/sms4j/core/load/SmsLoad.java index a84d3a8a..9c69d6af 100644 --- a/sms4j-core/src/main/java/org/dromara/sms4j/core/load/SmsLoad.java +++ b/sms4j-core/src/main/java/org/dromara/sms4j/core/load/SmsLoad.java @@ -20,7 +20,7 @@ public class SmsLoad { // 服务器列表,每个服务器有一个权重和当前权重 private final List LoadServers = new ArrayList<>(); - private static final SmsLoad smsLoad = new SmsLoad(); + private static final SmsLoad SMS_LOAD = new SmsLoad(); private SmsLoad() { } @@ -100,15 +100,15 @@ public class SmsLoad { public static void starConfig(SmsBlend smsBlend, SupplierConfig supplierConfig) { Map supplierConfigMap = BeanUtil.beanToMap(supplierConfig); Object weight = supplierConfigMap.getOrDefault("weight", 1); - smsLoad.addLoadServer(smsBlend, Integer.parseInt(weight.toString())); + SMS_LOAD.addLoadServer(smsBlend, Integer.parseInt(weight.toString())); } public static void starConfig(SmsBlend smsBlend,Integer weight) { - smsLoad.addLoadServer(smsBlend,weight); + SMS_LOAD.addLoadServer(smsBlend,weight); } public static SmsLoad getBeanLoad() { - return smsLoad; + return SMS_LOAD; } } diff --git a/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/SmsProxyFactory.java b/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/SmsProxyFactory.java index 25be0138..6f32c58a 100644 --- a/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/SmsProxyFactory.java +++ b/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/SmsProxyFactory.java @@ -27,10 +27,10 @@ import java.util.stream.Collectors; */ @Slf4j public abstract class SmsProxyFactory { - private static final LinkedList processors = new LinkedList<>(); + private static final LinkedList PROCESSORS = new LinkedList<>(); public static SmsBlend getProxySmsBlend(SmsBlend smsBlend) { - LinkedList ownerProcessors = processors.stream().filter(processor -> !shouldSkipProcess(processor,smsBlend)).collect(Collectors.toCollection(LinkedList::new)); + LinkedList ownerProcessors = PROCESSORS.stream().filter(processor -> !shouldSkipProcess(processor,smsBlend)).collect(Collectors.toCollection(LinkedList::new)); return (SmsBlend) Proxy.newProxyInstance(smsBlend.getClass().getClassLoader(), new Class[]{SmsBlend.class}, new SmsInvocationHandler(smsBlend, ownerProcessors)); } @@ -41,8 +41,8 @@ public abstract class SmsProxyFactory { //校验拦截器是否正确 processorValidate(processor); awareTransfer(processor); - processors.add(processor); - processors.sort(Comparator.comparingInt(Order::getOrder)); + PROCESSORS.add(processor); + PROCESSORS.sort(Comparator.comparingInt(Order::getOrder)); } /* diff --git a/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/processor/CoreMethodParamValidateProcessor.java b/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/processor/CoreMethodParamValidateProcessor.java index 480bf09e..4e05d2e5 100644 --- a/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/processor/CoreMethodParamValidateProcessor.java +++ b/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/processor/CoreMethodParamValidateProcessor.java @@ -93,7 +93,6 @@ public class CoreMethodParamValidateProcessor implements CoreMethodProcessor { } } } - throw new SmsBlendException("cant send message to null!"); } public void validateMessages(String templateId, LinkedHashMap messages) { diff --git a/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/processor/RestrictedProcessor.java b/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/processor/RestrictedProcessor.java index d7eb50e2..21f187ca 100644 --- a/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/processor/RestrictedProcessor.java +++ b/sms4j-core/src/main/java/org/dromara/sms4j/core/proxy/processor/RestrictedProcessor.java @@ -64,39 +64,57 @@ public class RestrictedProcessor implements CoreMethodProcessor, SmsDaoAware { throw new SmsBlendException("The smsDao tool could not be found"); } SmsConfig config = BeanFactory.getSmsConfig(); - // 如果未开始限制则不做处理 - if (!config.getRestricted()){ - return; - } // 每日最大发送量 Integer accountMax = config.getAccountMax(); // 每分钟最大发送量 Integer minuteMax = config.getMinuteMax(); + // 配置了每日最大发送量 + boolean dailyMaxLimitExists = SmsUtils.isNotEmpty(accountMax); + // 配置了每分钟最大发送量 + boolean perMinuteLimitExists = SmsUtils.isNotEmpty(minuteMax); + // 如果未开启限制或未配置任何限制发送量,不做处理 + boolean isNoProcessing = !config.getRestricted() || (!dailyMaxLimitExists && !perMinuteLimitExists); + if (isNoProcessing) { + return; + } for (String phone : phones) { + // 分钟发送量缓存key + String minuteMaxKey = REDIS_KEY + phone; + // 天发送量缓存key + String accountMaxKey = minuteMaxKey.concat("max"); // 是否配置了每日限制 - if (SmsUtils.isNotEmpty(accountMax)) { - Integer i = (Integer) smsDao.get(REDIS_KEY + phone + "max"); - if (SmsUtils.isEmpty(i)) { - smsDao.set(REDIS_KEY + phone + "max", 1, accTimer / 1000); - } else if (i >= accountMax) { + if (dailyMaxLimitExists) { + Integer dailyCount = (Integer) smsDao.get(accountMaxKey); + if (SmsUtils.isEmpty(dailyCount)) { + smsDao.set(accountMaxKey, 1, accTimer / 1000); + } else if (dailyCount >= accountMax) { log.info("The phone: {},number of short messages reached the maximum today", phone); throw new SmsBlendException("The phone: {},number of short messages reached the maximum today", phone); } else { - smsDao.set(REDIS_KEY + phone + "max", i + 1, accTimer / 1000); + smsDao.set(accountMaxKey, dailyCount + 1, accTimer / 1000); } } // 是否配置了每分钟最大限制 - if (SmsUtils.isNotEmpty(minuteMax)) { - Integer o = (Integer) smsDao.get(REDIS_KEY + phone); - if (SmsUtils.isNotEmpty(o)) { - if (o < minuteMax) { - smsDao.set(REDIS_KEY + phone, o + 1, minTimer / 1000); + if (perMinuteLimitExists) { + Integer minuteCount = (Integer) smsDao.get(REDIS_KEY + phone); + if (SmsUtils.isNotEmpty(minuteCount)) { + if (minuteCount < minuteMax) { + smsDao.set(minuteMaxKey, minuteCount + 1, minTimer / 1000); } else { - log.info("The phone: {},number of short messages reached the maximum today", phone); + //如果能走到这里且存在每日限制,说明每日限制已经计数,这里将之前的计数减一次 + if (dailyMaxLimitExists) { + Integer dailyCount = (Integer) smsDao.get(accountMaxKey); + if (dailyCount > 1) { + smsDao.set(accountMaxKey, dailyCount - 1, accTimer / 1000); + } else { + smsDao.remove(accountMaxKey); + } + } + log.info("The phone: {} Text messages are sent too often!", phone); throw new SmsBlendException("The phone: {} Text messages are sent too often!", phone); } } else { - smsDao.set(REDIS_KEY + phone, 1, minTimer / 1000); + smsDao.set(minuteMaxKey, 1, minTimer / 1000); } } } diff --git a/sms4j-email-jakarta/sms4j-email-jakarta-core/src/main/java/org/dromara/email/jakarta/core/factory/MailFactory.java b/sms4j-email-jakarta/sms4j-email-jakarta-core/src/main/java/org/dromara/email/jakarta/core/factory/MailFactory.java index 342738d7..644298d4 100644 --- a/sms4j-email-jakarta/sms4j-email-jakarta-core/src/main/java/org/dromara/email/jakarta/core/factory/MailFactory.java +++ b/sms4j-email-jakarta/sms4j-email-jakarta-core/src/main/java/org/dromara/email/jakarta/core/factory/MailFactory.java @@ -17,7 +17,7 @@ import java.util.Map; * 2023/6/8 22:35 **/ public class MailFactory{ - private static final Map configs = new HashMap<>(); + private static final Map CONFIGS = new HashMap<>(); /** * createMailClient @@ -27,7 +27,7 @@ public class MailFactory{ */ public static MailClient createMailClient(Object key){ try { - return MailBuild.build(configs.get(key)); + return MailBuild.build(CONFIGS.get(key)); } catch (MessagingException e) { throw new MailException(e); } @@ -43,7 +43,7 @@ public class MailFactory{ */ public static MailClient createMailClient(Object key, Blacklist blacklist){ try { - return MailBuild.build(configs.get(key),blacklist); + return MailBuild.build(CONFIGS.get(key),blacklist); } catch (MessagingException e) { throw new MailException(e); } @@ -57,7 +57,7 @@ public class MailFactory{ * @author :Wind */ public static void put(Object key, MailSmtpConfig config){ - configs.put(key,config); + CONFIGS.put(key,config); } } diff --git a/sms4j-javase-plugin/src/main/java/org/dromara/sms4j/javase/config/SEInitializer.java b/sms4j-javase-plugin/src/main/java/org/dromara/sms4j/javase/config/SEInitializer.java index 00f5fb7e..3bd6ce6d 100644 --- a/sms4j-javase-plugin/src/main/java/org/dromara/sms4j/javase/config/SEInitializer.java +++ b/sms4j-javase-plugin/src/main/java/org/dromara/sms4j/javase/config/SEInitializer.java @@ -16,6 +16,8 @@ import org.dromara.sms4j.api.dao.SmsDao; import org.dromara.sms4j.api.dao.SmsDaoDefaultImpl; import org.dromara.sms4j.api.universal.SupplierConfig; import org.dromara.sms4j.api.verify.PhoneVerify; +import org.dromara.sms4j.baidu.config.BaiduFactory; +import org.dromara.sms4j.budingyun.config.BudingV2Factory; import org.dromara.sms4j.cloopen.config.CloopenFactory; import org.dromara.sms4j.comm.constant.Constant; import org.dromara.sms4j.comm.exception.SmsBlendException; @@ -29,17 +31,24 @@ import org.dromara.sms4j.core.proxy.processor.CoreMethodParamValidateProcessor; import org.dromara.sms4j.core.proxy.processor.RestrictedProcessor; import org.dromara.sms4j.core.proxy.processor.SingleBlendRestrictedProcessor; import org.dromara.sms4j.ctyun.config.CtyunFactory; +import org.dromara.sms4j.danmi.config.DanMiFactory; import org.dromara.sms4j.dingzhong.config.DingZhongFactory; import org.dromara.sms4j.emay.config.EmayFactory; import org.dromara.sms4j.huawei.config.HuaweiFactory; import org.dromara.sms4j.javase.util.YamlUtils; import org.dromara.sms4j.jdcloud.config.JdCloudFactory; +import org.dromara.sms4j.chuanglan.config.ChuangLanFactory; +import org.dromara.sms4j.jg.config.JgFactory; import org.dromara.sms4j.lianlu.config.LianLuFactory; +import org.dromara.sms4j.luosimao.config.LuoSiMaoFactory; +import org.dromara.sms4j.mas.config.MasFactory; import org.dromara.sms4j.netease.config.NeteaseFactory; import org.dromara.sms4j.provider.config.SmsConfig; import org.dromara.sms4j.provider.factory.BaseProviderFactory; import org.dromara.sms4j.provider.factory.BeanFactory; import org.dromara.sms4j.provider.factory.ProviderFactoryHolder; +import org.dromara.sms4j.qiniu.config.QiNiuFactory; +import org.dromara.sms4j.submail.config.SubMailFactory; import org.dromara.sms4j.tencent.config.TencentFactory; import org.dromara.sms4j.unisms.config.UniFactory; import org.dromara.sms4j.yunpian.config.YunPianFactory; @@ -228,7 +237,7 @@ public class SEInitializer { continue; } configMap.put("config-id", configId); - SmsUtils.replaceKeysSeperator(configMap, "-", "_"); + SmsUtils.replaceKeysSeparator(configMap, "-", "_"); JSONObject configJson = new JSONObject(configMap); SupplierConfig supplierConfig = JSONUtil.toBean(configJson, providerFactory.getConfigClass()); SmsFactory.createSmsBlend(supplierConfig); @@ -251,6 +260,15 @@ public class SEInitializer { ProviderFactoryHolder.registerFactory(ZhutongFactory.instance()); ProviderFactoryHolder.registerFactory(LianLuFactory.instance()); ProviderFactoryHolder.registerFactory(DingZhongFactory.instance()); + ProviderFactoryHolder.registerFactory(QiNiuFactory.instance()); + ProviderFactoryHolder.registerFactory(ChuangLanFactory.instance()); + ProviderFactoryHolder.registerFactory(JgFactory.instance()); + ProviderFactoryHolder.registerFactory(BudingV2Factory.instance()); + ProviderFactoryHolder.registerFactory(MasFactory.instance()); + ProviderFactoryHolder.registerFactory(BaiduFactory.instance()); + ProviderFactoryHolder.registerFactory(LuoSiMaoFactory.instance()); + ProviderFactoryHolder.registerFactory(SubMailFactory.instance()); + ProviderFactoryHolder.registerFactory(DanMiFactory.instance()); if (SmsUtils.isClassExists("com.jdcloud.sdk.auth.CredentialsProvider")) { ProviderFactoryHolder.registerFactory(JdCloudFactory.instance()); } diff --git a/sms4j-oa-plugin/sms4j-oa-core/src/main/java/org/dromara/oa/core/provider/factory/ProviderFactoryHolder.java b/sms4j-oa-plugin/sms4j-oa-core/src/main/java/org/dromara/oa/core/provider/factory/ProviderFactoryHolder.java index ce5b7a61..5c808ad3 100644 --- a/sms4j-oa-plugin/sms4j-oa-core/src/main/java/org/dromara/oa/core/provider/factory/ProviderFactoryHolder.java +++ b/sms4j-oa-plugin/sms4j-oa-core/src/main/java/org/dromara/oa/core/provider/factory/ProviderFactoryHolder.java @@ -16,13 +16,13 @@ import java.util.concurrent.ConcurrentHashMap; public class ProviderFactoryHolder { - private static final Map> factories = new ConcurrentHashMap<>(); + private static final Map> FACTORIES = new ConcurrentHashMap<>(); public static void registerFactory(OaBaseProviderFactory factory) { if (factory == null) { throw new OaException("注册供应商工厂失败,工厂实例不能为空"); } - factories.put(factory.getSupplier(), factory); + FACTORIES.put(factory.getSupplier(), factory); } public static void registerFactory(List> factoryList) { @@ -38,6 +38,6 @@ public class ProviderFactoryHolder { } public static OaBaseProviderFactory requireForSupplier(String supplier) { - return factories.getOrDefault(supplier, null); + return FACTORIES.getOrDefault(supplier, null); } } diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/aliyun/service/AlibabaSmsImpl.java b/sms4j-provider/src/main/java/org/dromara/sms4j/aliyun/service/AlibabaSmsImpl.java index 0174dd7c..d522c2fe 100644 --- a/sms4j-provider/src/main/java/org/dromara/sms4j/aliyun/service/AlibabaSmsImpl.java +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/aliyun/service/AlibabaSmsImpl.java @@ -7,6 +7,7 @@ import lombok.extern.slf4j.Slf4j; import org.dromara.sms4j.aliyun.config.AlibabaConfig; import org.dromara.sms4j.aliyun.utils.AliyunUtils; import org.dromara.sms4j.api.entity.SmsResponse; +import org.dromara.sms4j.api.utils.SmsRespUtils; import org.dromara.sms4j.comm.constant.Constant; import org.dromara.sms4j.comm.constant.SupplierConstant; import org.dromara.sms4j.comm.delayedTime.DelayedTime; @@ -57,7 +58,7 @@ public class AlibabaSmsImpl extends AbstractSmsBlend { @Override public SmsResponse sendMessage(String phone, String message) { - LinkedHashMap map = new LinkedHashMap<>(); + LinkedHashMap map = new LinkedHashMap<>(1); map.put(getConfig().getTemplateName(), message); return sendMessage(phone, getConfig().getTemplateId(), map); } @@ -92,7 +93,7 @@ public class AlibabaSmsImpl extends AbstractSmsBlend { messages = new LinkedHashMap<>(); } String messageStr = JSONUtil.toJsonStr(messages); - return getSmsResponse(SmsUtils.arrayToString(phones), messageStr, templateId); + return getSmsResponse(SmsUtils.addCodePrefixIfNot(phones), messageStr, templateId); } private SmsResponse getSmsResponse(String phone, String message, String templateId) { @@ -108,14 +109,12 @@ public class AlibabaSmsImpl extends AbstractSmsBlend { log.debug("requestUrl {}", requestUrl); Map headers = MapUtil.newHashMap(1, true); - headers.put("Content-Type", Constant.FROM_URLENCODED); + headers.put(Constant.CONTENT_TYPE, Constant.APPLICATION_FROM_URLENCODED); SmsResponse smsResponse; try { smsResponse = getResponse(http.postJson(requestUrl, headers, paramStr)); } catch (SmsBlendException e) { - smsResponse = new SmsResponse(); - smsResponse.setSuccess(false); - smsResponse.setData(e.getMessage()); + smsResponse = errorResp(e.message); } if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) { retry = 0; @@ -132,11 +131,7 @@ public class AlibabaSmsImpl extends AbstractSmsBlend { } private SmsResponse getResponse(JSONObject resJson) { - SmsResponse smsResponse = new SmsResponse(); - smsResponse.setSuccess("OK".equals(resJson.getStr("Code"))); - smsResponse.setData(resJson); - smsResponse.setConfigId(getConfigId()); - return smsResponse; + return SmsRespUtils.resp(resJson, "OK".equals(resJson.getStr("Code")), getConfigId()); } } \ No newline at end of file diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/aliyun/utils/AliyunUtils.java b/sms4j-provider/src/main/java/org/dromara/sms4j/aliyun/utils/AliyunUtils.java index c7d5939c..fdaf08e5 100644 --- a/sms4j-provider/src/main/java/org/dromara/sms4j/aliyun/utils/AliyunUtils.java +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/aliyun/utils/AliyunUtils.java @@ -4,15 +4,14 @@ import cn.hutool.crypto.digest.HMac; import cn.hutool.crypto.digest.HmacAlgorithm; import org.dromara.sms4j.aliyun.config.AlibabaConfig; import org.dromara.sms4j.comm.constant.Constant; +import org.dromara.sms4j.comm.utils.SmsDateUtils; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; -import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.Map; -import java.util.SimpleTimeZone; import java.util.TreeMap; import java.util.UUID; @@ -27,18 +26,15 @@ public class AliyunUtils { */ private static final String ALGORITHM = "HMAC-SHA1"; - private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); - public static String generateSendSmsRequestUrl(AlibabaConfig alibabaConfig, String message, String phone, String templateId) throws Exception { // 这里一定要设置GMT时区 - SDF.setTimeZone(new SimpleTimeZone(0, "GMT")); Map paras = new HashMap<>(); // 1. 公共请求参数 paras.put("SignatureMethod", ALGORITHM); paras.put("SignatureNonce", UUID.randomUUID().toString()); paras.put("AccessKeyId", alibabaConfig.getAccessKeyId()); paras.put("SignatureVersion", "1.0"); - paras.put("Timestamp", SDF.format(new Date())); + paras.put("Timestamp", SmsDateUtils.utcGmt(new Date())); paras.put("Format", "JSON"); paras.put("Action", alibabaConfig.getAction()); paras.put("Version", alibabaConfig.getVersion()); @@ -110,10 +106,10 @@ public class AliyunUtils { /** * 生成请求参数body字符串 * - * @param alibabaConfig - * @param phone - * @param message - * @param templateId + * @param alibabaConfig 配置数据 + * @param phone 手机号 + * @param message 短信内容 + * @param templateId 模板id */ public static String generateParamBody(AlibabaConfig alibabaConfig, String phone, String message, String templateId) throws Exception { Map paramMap = generateParamMap(alibabaConfig, phone, message, templateId); diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/baidu/config/BaiduConfig.java b/sms4j-provider/src/main/java/org/dromara/sms4j/baidu/config/BaiduConfig.java new file mode 100644 index 00000000..9eebab59 --- /dev/null +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/baidu/config/BaiduConfig.java @@ -0,0 +1,54 @@ +package org.dromara.sms4j.baidu.config; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.sms4j.comm.constant.Constant; +import org.dromara.sms4j.comm.constant.SupplierConstant; +import org.dromara.sms4j.provider.config.BaseConfig; + +/** + *

类名: BaiduConfig + *

说明:百度智能云 sms + * + * @author :bleachtred + * 2024/4/25 13:40 + **/ +@Data +@EqualsAndHashCode(callSuper = true) +public class BaiduConfig extends BaseConfig { + + /** + * 请求地址 + */ + private String host = Constant.HTTPS_PREFIX + "smsv3.bj.baidubce.com"; + + /** + * 接口名称 + */ + private String action = "/api/v3/sendSms"; + + /** + * 模板变量名称 + */ + private String templateName; + + /** + * 用户自定义参数,格式为字符串,状态回调时会回传该值 + */ + private String custom; + + /** + * 通道自定义扩展码 + */ + private String userExtId; + + /** + * 获取供应商 + * + * @since 3.0.0 + */ + @Override + public String getSupplier() { + return SupplierConstant.BAIDU; + } +} diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/baidu/config/BaiduFactory.java b/sms4j-provider/src/main/java/org/dromara/sms4j/baidu/config/BaiduFactory.java new file mode 100644 index 00000000..b59dd6ce --- /dev/null +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/baidu/config/BaiduFactory.java @@ -0,0 +1,49 @@ +package org.dromara.sms4j.baidu.config; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.dromara.sms4j.comm.constant.SupplierConstant; +import org.dromara.sms4j.baidu.service.BaiduSmsImpl; +import org.dromara.sms4j.provider.factory.AbstractProviderFactory; + +/** + *

类名: BaiduFactory + *

说明:百度智能云 sms + * + * @author :bleachtred + * 2024/4/25 13:40 + **/ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class BaiduFactory extends AbstractProviderFactory { + + private static final BaiduFactory INSTANCE = new BaiduFactory(); + + /** + * 获取建造者实例 + * @return 建造者实例 + */ + public static BaiduFactory instance() { + return INSTANCE; + } + + /** + * createSms + *

建造一个短信实现对像 + * + * @author :bleachtred + */ + @Override + public BaiduSmsImpl createSms(BaiduConfig baiduConfig) { + return new BaiduSmsImpl(baiduConfig); + } + + /** + * 获取供应商 + * @return 供应商 + */ + @Override + public String getSupplier() { + return SupplierConstant.BAIDU; + } + +} diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/baidu/service/BaiduSmsImpl.java b/sms4j-provider/src/main/java/org/dromara/sms4j/baidu/service/BaiduSmsImpl.java new file mode 100644 index 00000000..3b88c154 --- /dev/null +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/baidu/service/BaiduSmsImpl.java @@ -0,0 +1,181 @@ +package org.dromara.sms4j.baidu.service; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.dromara.sms4j.api.entity.SmsResponse; +import org.dromara.sms4j.api.utils.SmsRespUtils; +import org.dromara.sms4j.comm.constant.SupplierConstant; +import org.dromara.sms4j.comm.delayedTime.DelayedTime; +import org.dromara.sms4j.comm.exception.SmsBlendException; +import org.dromara.sms4j.comm.utils.SmsUtils; +import org.dromara.sms4j.baidu.config.BaiduConfig; +import org.dromara.sms4j.baidu.utils.BaiduUtils; +import org.dromara.sms4j.provider.service.AbstractSmsBlend; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executor; + +/** + *

类名: BaiduSmsImpl + *

说明:百度智能云 sms + * + * @author :bleachtred + * 2024/4/25 13:40 + **/ +@Slf4j +public class BaiduSmsImpl extends AbstractSmsBlend { + + private int retry = 0; + + public BaiduSmsImpl(BaiduConfig config, Executor pool, DelayedTime delayedTime) { + super(config, pool, delayedTime); + } + + public BaiduSmsImpl(BaiduConfig config) { + super(config); + } + + @Override + public String getSupplier() { + return SupplierConstant.BAIDU; + } + + @Override + public SmsResponse sendMessage(String phone, String message) { + LinkedHashMap map = new LinkedHashMap<>(1); + map.put(getConfig().getTemplateName(), message); + return sendMessage(phone, getConfig().getTemplateId(), map); + } + + @Override + public SmsResponse sendMessage(String phone, LinkedHashMap messages) { + if (CollUtil.isEmpty(messages)){ + messages = new LinkedHashMap<>(); + } + return sendMessage(phone, getConfig().getTemplateId(), messages); + } + + @Override + public SmsResponse sendMessage(String phone, String templateId, LinkedHashMap messages) { + if (CollUtil.isEmpty(messages)){ + messages = new LinkedHashMap<>(); + } + return getSmsResponse(phone, templateId, messages); + } + + @Override + public SmsResponse massTexting(List phones, String message) { + LinkedHashMap map = new LinkedHashMap<>(1); + map.put(getConfig().getTemplateName(), message); + return massTexting(phones, getConfig().getTemplateId(), map); + } + + @Override + public SmsResponse massTexting(List phones, String templateId, LinkedHashMap messages) { + if (CollUtil.isEmpty(messages)){ + messages = new LinkedHashMap<>(); + } + return getSmsResponse(SmsUtils.addCodePrefixIfNot(phones), templateId, messages); + } + + private SmsResponse getSmsResponse(String phone, String templateId, LinkedHashMap messages) { + return getSmsResponseWithClientToken(phone, templateId, messages, null); + } + + private void checkClientToken(String clientToken){ + if (StrUtil.isBlank(clientToken)){ + log.error("clientToken is required."); + throw new SmsBlendException("clientToken is required."); + } + } + + public SmsResponse sendMessageWithClientToken(String phone, String message, String clientToken) { + checkClientToken(clientToken); + LinkedHashMap map = new LinkedHashMap<>(1); + map.put(getConfig().getTemplateName(), message); + return sendMessageWithClientToken(phone, getConfig().getTemplateId(), map, clientToken); + } + + public SmsResponse sendMessageWithClientToken(String phone, LinkedHashMap messages, String clientToken) { + checkClientToken(clientToken); + if (CollUtil.isEmpty(messages)){ + messages = new LinkedHashMap<>(); + } + return sendMessageWithClientToken(phone, getConfig().getTemplateId(), messages, clientToken); + } + + public SmsResponse sendMessageWithClientToken(String phone, String templateId, LinkedHashMap messages, String clientToken) { + checkClientToken(clientToken); + if (CollUtil.isEmpty(messages)){ + messages = new LinkedHashMap<>(); + } + return getSmsResponseWithClientToken(phone, templateId, messages, clientToken); + } + + public SmsResponse massTextingWithClientToken(List phones, String message, String clientToken) { + checkClientToken(clientToken); + LinkedHashMap map = new LinkedHashMap<>(1); + map.put(getConfig().getTemplateName(), message); + return massTextingWithClientToken(phones, getConfig().getTemplateId(), map, clientToken); + } + + public SmsResponse massTextingWithClientToken(List phones, String templateId, LinkedHashMap messages, String clientToken) { + checkClientToken(clientToken); + if (CollUtil.isEmpty(messages)){ + messages = new LinkedHashMap<>(); + } + return getSmsResponseWithClientToken(SmsUtils.addCodePrefixIfNot(phones), templateId, messages, clientToken); + } + + private SmsResponse getSmsResponseWithClientToken(String phone, String templateId, LinkedHashMap messages, String clientToken) { + BaiduConfig config = getConfig(); + if (StrUtil.isBlank(config.getSignature())){ + log.error("signatureId is required."); + throw new SmsBlendException("signatureId is required."); + } + if (StrUtil.isBlank(templateId)){ + log.error("template is required."); + throw new SmsBlendException("template is required."); + } + if (StrUtil.isBlank(phone)){ + log.error("mobile is required."); + throw new SmsBlendException("mobile is required."); + } + Map headers; + Map body; + try { + headers = BaiduUtils.buildHeaders(config, clientToken); + body = BaiduUtils.buildBody(phone, templateId, config.getSignature(), messages, config.getCustom(), config.getUserExtId()); + } catch (Exception e) { + log.error("baidu sms buildHeaders or buildBody error", e); + throw new SmsBlendException(e.getMessage()); + } + SmsResponse smsResponse; + try { + smsResponse = getResponse(http.postJson(config.getHost() + config.getAction(), headers, body)); + } catch (SmsBlendException e) { + smsResponse = errorResp(e.message); + } + if (smsResponse.isSuccess() || retry == config.getMaxRetries()) { + retry = 0; + return smsResponse; + } + return requestRetry(phone, templateId, messages, clientToken); + } + + private SmsResponse requestRetry(String phone, String templateId, LinkedHashMap messages, String clientToken) { + http.safeSleep(getConfig().getRetryInterval()); + retry ++; + log.warn("The SMS has been resent for the {}th time.", retry); + return getSmsResponseWithClientToken(phone, templateId, messages, clientToken); + } + + private SmsResponse getResponse(JSONObject resJson) { + return SmsRespUtils.resp(resJson, "1000".equals(resJson.getStr("code")), getConfigId()); + } + +} \ No newline at end of file diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/baidu/utils/BaiduUtils.java b/sms4j-provider/src/main/java/org/dromara/sms4j/baidu/utils/BaiduUtils.java new file mode 100644 index 00000000..fe609a0b --- /dev/null +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/baidu/utils/BaiduUtils.java @@ -0,0 +1,129 @@ +package org.dromara.sms4j.baidu.utils; + +import cn.hutool.core.net.URLEncodeUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.digest.HMac; +import cn.hutool.crypto.digest.HmacAlgorithm; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.dromara.sms4j.baidu.config.BaiduConfig; +import org.dromara.sms4j.comm.constant.Constant; +import org.dromara.sms4j.comm.utils.SmsDateUtils; + +import java.nio.charset.StandardCharsets; +import java.util.*; + +@Slf4j +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class BaiduUtils { + + /** + * 创建前缀字符串 + * @param accessKeyId 访问密钥ID + * @return bce-auth-v1/{accessKeyId}/{timestamp}/{expirationPeriodInSeconds } + */ + private static String authStringPrefix(String accessKeyId){ + return "bce-auth-v1/" + accessKeyId + "/" + SmsDateUtils.utcGmt(new Date()) + "/1800"; + } + + /** + * 创建规范请求 + * @param host Host域 + * @param action 接口名称 + * @param clientToken 幂等性参数 + * @return HTTP Method + "\n" + CanonicalURI + "\n" + CanonicalQueryString + "\n" + CanonicalHeaders + */ + private static String canonicalRequest(String host, String action, String clientToken){ + return "POST\n" + canonicalURI(action) + "\n" + canonicalQueryString(clientToken) + "\n" + canonicalHeaders(host); + } + + /** + * Formatting the URL with signing protocol. + * @param action URI + * @return UriEncodeExceptSlash + */ + private static String canonicalURI(String action){ + return URLEncodeUtil.encode(action, StandardCharsets.UTF_8); + } + + /** + * Formatting the query string with signing protocol. + * @param clientToken 幂等性参数 + * @return String + */ + private static String canonicalQueryString(String clientToken){ + if (StrUtil.isBlank(clientToken)) { + return StrUtil.EMPTY; + } + return "clientToken=" + URLEncodeUtil.encode(clientToken, StandardCharsets.UTF_8); + } + + /** + * Formatting the headers from the request based on signing protocol. + * @param host only host + * @return String + */ + private static String canonicalHeaders(String host){ + return URLEncodeUtil.encode("host", StandardCharsets.UTF_8) + ":" + URLEncodeUtil.encode(host, StandardCharsets.UTF_8); + } + + /** + * HMAC-SHA256-HEX + * @param key 密钥 + * @param str 要加密的字符串 + * @return 小写形式的十六进制字符串 + */ + private static String sha256Hex(String key, String str) { + HMac hMac = new HMac(HmacAlgorithm.HmacSHA256, key.getBytes(StandardCharsets.UTF_8)); + return hMac.digestHex(str, StandardCharsets.UTF_8); + } + + /** + * 构造 HTTP Headers请求头 + * @param config 百度智能云配置 + * @param clientToken 幂等性参数 + * @return Headers请求头 + */ + public static Map buildHeaders(BaiduConfig config, String clientToken) { + // 创建前缀字符串 + String authStringPrefix = authStringPrefix(config.getAccessKeyId()); + // 生成派生密钥 + String signingKey = sha256Hex(config.getAccessKeySecret(), authStringPrefix(config.getAccessKeyId())); + // 生成签名摘要及认证字符串 + String signature = sha256Hex(signingKey, canonicalRequest(config.getHost(), config.getAction(), clientToken)); + // 认证字符串 + String authorization = authStringPrefix + "/" + "/" + signature; + + Map headers = new HashMap<>(2); + headers.put(Constant.AUTHORIZATION, authorization); + headers.put("host", config.getHost()); + return headers; + } + + /** + * 构造 HTTP Body 请求体 + * @param mobile 手机号码 支持单个或多个手机号,多个手机号之间以英文逗号分隔 + * @param template 短信模板ID,模板申请成功后自动创建,全局内唯一 + * @param signatureId 短信签名ID,签名表申请成功后自动创建,全局内唯一 + * @param contentVar 模板变量内容,用于替换短信模板中定义的变量 + * @param custom 用户自定义参数,格式为字符串,状态回调时会回传该值 + * @param userExtId 通道自定义扩展码,上行回调时会回传该值,其格式为纯数字串。默认为不开通,请求时无需设置该参数。如需开通请联系SMS帮助申请 + * @return Body 请求体 + */ + public static Map buildBody(String mobile, String template, String signatureId, + LinkedHashMap contentVar, String custom, String userExtId) { + Map body = new HashMap<>(4); + body.put("mobile", mobile); + body.put("template", template); + body.put("signatureId", signatureId); + body.put("contentVar", contentVar); + if (StrUtil.isNotBlank(custom)){ + body.put("custom", custom); + } + if (StrUtil.isNotBlank(userExtId)){ + body.put("userExtId", userExtId); + } + return body; + } +} diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/budingyun/config/BudingV2Config.java b/sms4j-provider/src/main/java/org/dromara/sms4j/budingyun/config/BudingV2Config.java new file mode 100644 index 00000000..fc6b6828 --- /dev/null +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/budingyun/config/BudingV2Config.java @@ -0,0 +1,40 @@ +package org.dromara.sms4j.budingyun.config; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import org.dromara.sms4j.comm.constant.SupplierConstant; +import org.dromara.sms4j.provider.config.BaseConfig; + +/** + * BudingV2Config + *

布丁云V2短信配置 + * + * @author NicholaslD + * @date 2024/03/21 12:00 + * */ +@Data +@Accessors(chain = true) +@EqualsAndHashCode(callSuper = true) +public class BudingV2Config extends BaseConfig { + + /** + * 签名密钥 + * 就是发短信的时候的签名,比如:【布丁云】 + */ + private String signKey; + + /** + * 变量列表 + * 用于替换短信模板中的变量 + */ + private String[] args; + + /** + * 获取供应商 + */ + @Override + public String getSupplier() { + return SupplierConstant.BUDING_V2; + } +} diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/budingyun/config/BudingV2Factory.java b/sms4j-provider/src/main/java/org/dromara/sms4j/budingyun/config/BudingV2Factory.java new file mode 100644 index 00000000..6b4693ff --- /dev/null +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/budingyun/config/BudingV2Factory.java @@ -0,0 +1,33 @@ +package org.dromara.sms4j.budingyun.config; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.dromara.sms4j.budingyun.service.BudingV2SmsImpl; +import org.dromara.sms4j.comm.constant.SupplierConstant; +import org.dromara.sms4j.provider.factory.AbstractProviderFactory; + +/** + * BudingV2Factory + *

布丁云V2短信对象建造 + * + * @author NicholaslD + * @date 2024/03/21 12:00 + * */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class BudingV2Factory extends AbstractProviderFactory { + private static final BudingV2Factory INSTANCE = new BudingV2Factory(); + + public static BudingV2Factory instance() { + return INSTANCE; + } + + @Override + public BudingV2SmsImpl createSms(BudingV2Config budingV2Config) { + return new BudingV2SmsImpl(budingV2Config); + } + + @Override + public String getSupplier() { + return SupplierConstant.BUDING_V2; + } +} diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/budingyun/service/BudingV2SmsImpl.java b/sms4j-provider/src/main/java/org/dromara/sms4j/budingyun/service/BudingV2SmsImpl.java new file mode 100644 index 00000000..2605be01 --- /dev/null +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/budingyun/service/BudingV2SmsImpl.java @@ -0,0 +1,162 @@ +package org.dromara.sms4j.budingyun.service; + +import cn.hutool.json.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.dromara.sms4j.api.entity.SmsResponse; +import org.dromara.sms4j.api.utils.SmsRespUtils; +import org.dromara.sms4j.budingyun.config.BudingV2Config; +import org.dromara.sms4j.comm.constant.Constant; +import org.dromara.sms4j.comm.constant.SupplierConstant; +import org.dromara.sms4j.comm.delayedTime.DelayedTime; +import org.dromara.sms4j.comm.exception.SmsBlendException; +import org.dromara.sms4j.provider.service.AbstractSmsBlend; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executor; + +/** + * BudingV2SmsImpl 布丁云V2短信实现 + * @author NicholasLD + * @createTime 2024/3/21 01:28 + */ +@Slf4j +public class BudingV2SmsImpl extends AbstractSmsBlend { + + /** + * 重试次数 + */ + private int retry = 0; + + private static final String URL = Constant.HTTPS_PREFIX + "smsapi.idcbdy.com"; + + protected BudingV2SmsImpl(BudingV2Config config, Executor pool, DelayedTime delayed) { + super(config, pool, delayed); + } + + public BudingV2SmsImpl(BudingV2Config config) { + super(config); + } + + @Override + public String getSupplier() { + return SupplierConstant.BUDING_V2; + } + + @Override + public SmsResponse sendMessage(String phone, String message) { + Map body = new HashMap<>(); + + System.out.println(getConfig().getSignKey()); + System.out.println(getConfig().getSignature()); + + if (getConfig().getSignKey() == null && getConfig().getSignature() == null) { + throw new SmsBlendException("签名秘钥不能为空"); + } + + if (getConfig().getSignKey() == null) { + body.put("sign", getConfig().getSignature()); + } + + body.put("key", getConfig().getAccessKeyId()); + body.put("to", phone); + body.put("content", message); + + Map headers = getHeaders(); + + SmsResponse smsResponse; + try { + smsResponse = getResponse(http.postFrom(URL + "/Api/Sent", headers, body)); + } catch (SmsBlendException e) { + smsResponse = errorResp(e.message); + } + if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) { + retry = 0; + return smsResponse; + } + return requestRetry(phone, message); + } + + private SmsResponse requestRetry(String phone, String message) { + http.safeSleep(getConfig().getRetryInterval()); + retry++; + log.warn("短信第 {" + retry + "} 次重新发送"); + return sendMessage(phone, message); + } + + private SmsResponse getResponse(JSONObject resJson) { + if (resJson == null) { + return SmsRespUtils.error(getConfigId()); + } + return SmsRespUtils.resp(resJson, resJson.getBool("bool"), getConfigId()); + } + + /** + * 发送多条短信 + * @param phone 手机号 + * @param messages 消息内容 + * @return 发送结果 + */ + @Override + public SmsResponse sendMessage(String phone, LinkedHashMap messages) { + int failed = 0; + for (String message : messages.values()) { + SmsResponse smsResponse = sendMessage(phone, message); + if (!smsResponse.isSuccess()) { + failed++; + } + } + return SmsRespUtils.resp(failed == 0, getConfigId()); + } + + /** + * 发送多条短信 (布丁云V2暂不支持模板短信) + * @param phone 手机号 + * @param templateId 模板ID (布丁云V2暂不支持模板短信,此参数无效) + * @param messages 模板参数 + * @return 发送结果 + */ + @Override + public SmsResponse sendMessage(String phone, String templateId, LinkedHashMap messages) { + return sendMessage(phone, messages); + } + + /** + * 群发短信 + * @param phones 手机号列表 + * @param message 消息内容 + * @return 发送结果 + */ + @Override + public SmsResponse massTexting(List phones, String message) { + int failed = 0; + for (String phone : phones) { + SmsResponse smsResponse = sendMessage(phone, message); + if (!smsResponse.isSuccess()) { + failed++; + } + } + return SmsRespUtils.resp(failed == 0, getConfigId()); + } + + /** + * 群发短信 (布丁云V2暂不支持模板短信,此方法无效) + * @param phones 手机号列表 + * @param templateId 模板ID (布丁云V2暂不支持模板短信,此参数无效) + * @param messages 模板参数 + * @return 发送结果 + */ + @Override + public SmsResponse massTexting(List phones, String templateId, LinkedHashMap messages) { + throw new SmsBlendException("布丁云V2暂不支持多条短信发送"); + } + + private Map getHeaders() { + Map headers = new HashMap<>(); + headers.put(Constant.ACCEPT, Constant.APPLICATION_JSON_UTF8); + headers.put(Constant.CONTENT_TYPE, Constant.APPLICATION_FROM_URLENCODED); + return headers; + } +} diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/chuanglan/config/ChuangLanConfig.java b/sms4j-provider/src/main/java/org/dromara/sms4j/chuanglan/config/ChuangLanConfig.java new file mode 100644 index 00000000..d0dbdf89 --- /dev/null +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/chuanglan/config/ChuangLanConfig.java @@ -0,0 +1,34 @@ +package org.dromara.sms4j.chuanglan.config; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.sms4j.comm.constant.Constant; +import org.dromara.sms4j.comm.constant.SupplierConstant; +import org.dromara.sms4j.provider.config.BaseConfig; + +/** + * @author YYM + * @Date: 2024/1/31 17:56 44 + * @描述: ChuangLanConfig + **/ +@EqualsAndHashCode(callSuper = true) +@Data +public class ChuangLanConfig extends BaseConfig { + + /** + * 基础路径 + */ + private String baseUrl = Constant.HTTPS_PREFIX + "smssh1.253.com/msg"; + + /** + * 短信发送路径 + * 普通短信发送 /v1/send/json 此接口支持单发、群发短信 + * 变量短信发送 /variable/json 单号码对应单内容批量下发 + */ + private String msgUrl = "/variable/json"; + + @Override + public String getSupplier() { + return SupplierConstant.CHUANGLAN; + } +} \ No newline at end of file diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/chuanglan/config/ChuangLanFactory.java b/sms4j-provider/src/main/java/org/dromara/sms4j/chuanglan/config/ChuangLanFactory.java new file mode 100644 index 00000000..c49d2f34 --- /dev/null +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/chuanglan/config/ChuangLanFactory.java @@ -0,0 +1,37 @@ +package org.dromara.sms4j.chuanglan.config; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.dromara.sms4j.chuanglan.service.ChuangLanSmsImpl; +import org.dromara.sms4j.comm.constant.SupplierConstant; +import org.dromara.sms4j.provider.factory.AbstractProviderFactory; + +/** + * @author YYM + * @Date: 2024/2/1 9:03 44 + * @描述: ChuangLanFactory + **/ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class ChuangLanFactory extends AbstractProviderFactory { + + private static final ChuangLanFactory INSTANCE = new ChuangLanFactory(); + + /** + * 获取建造者实例 + * + * @return 建造者实例 + */ + public static ChuangLanFactory instance() { + return INSTANCE; + } + + @Override + public ChuangLanSmsImpl createSms(ChuangLanConfig chuangLanConfig) { + return new ChuangLanSmsImpl(chuangLanConfig); + } + + @Override + public String getSupplier() { + return SupplierConstant.CHUANGLAN; + } +} \ No newline at end of file diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/chuanglan/service/ChuangLanSmsImpl.java b/sms4j-provider/src/main/java/org/dromara/sms4j/chuanglan/service/ChuangLanSmsImpl.java new file mode 100644 index 00000000..61a5c57e --- /dev/null +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/chuanglan/service/ChuangLanSmsImpl.java @@ -0,0 +1,131 @@ +package org.dromara.sms4j.chuanglan.service; + +import cn.hutool.json.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.dromara.sms4j.api.entity.SmsResponse; +import org.dromara.sms4j.api.utils.SmsRespUtils; +import org.dromara.sms4j.chuanglan.config.ChuangLanConfig; +import org.dromara.sms4j.comm.constant.Constant; +import org.dromara.sms4j.comm.constant.SupplierConstant; +import org.dromara.sms4j.comm.delayedTime.DelayedTime; +import org.dromara.sms4j.comm.exception.SmsBlendException; +import org.dromara.sms4j.comm.utils.SmsUtils; +import org.dromara.sms4j.provider.service.AbstractSmsBlend; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.concurrent.Executor; + +/** + * @author YYM + * @Date: 2024/2/1 9:04 27 + * @描述: ChuangLanSmsImpl + **/ +@Slf4j +public class ChuangLanSmsImpl extends AbstractSmsBlend { + + private int retry = 0; + + public ChuangLanSmsImpl(ChuangLanConfig config, Executor pool, DelayedTime delayed) { + super(config, pool, delayed); + } + + public ChuangLanSmsImpl(ChuangLanConfig config) { + super(config); + } + + @Override + public String getSupplier() { + return SupplierConstant.CHUANGLAN; + } + + @Override + public SmsResponse sendMessage(String phone, String message) { + return sendMessage(phone, getConfig().getTemplateId(), SmsUtils.buildMessageByAmpersand(message)); + } + + @Override + public SmsResponse sendMessage(String phone, LinkedHashMap messages) { + return sendMessage(phone, getConfig().getTemplateId(), messages); + } + + @Override + public SmsResponse sendMessage(String phone, String templateId, LinkedHashMap messages) { + if (SmsUtils.isEmpty(messages)){ + messages = new LinkedHashMap<>(); + } + String message = String.join(",", messages.values()); + ChuangLanConfig config = getConfig(); + LinkedHashMap body = buildBody(config.getAccessKeyId(), config.getAccessKeySecret(), templateId); + body.put("params", phone + "," + message); + return getSmsResponse(body); + } + + @Override + public SmsResponse massTexting(List phones, String message) { + return massTexting(phones, getConfig().getTemplateId(), SmsUtils.buildMessageByAmpersand(message)); + } + + @Override + public SmsResponse massTexting(List phones, String templateId, LinkedHashMap messages) { + if (SmsUtils.isEmpty(messages)){ + messages = new LinkedHashMap<>(); + } + String message = String.join(",", messages.values()); + StringBuilder param = new StringBuilder(); + phones.forEach(phone -> param.append(phone).append(",").append(message).append(";")); + ChuangLanConfig config = getConfig(); + LinkedHashMap params = buildBody(config.getAccessKeyId(), config.getAccessKeySecret(), templateId); + params.put("params", param.toString()); + return getSmsResponse(params); + } + + private static String buildUrl(String baseUrl, String msgUrl){ + return baseUrl + msgUrl; + } + + private static LinkedHashMap buildHeaders(){ + LinkedHashMap headers = new LinkedHashMap<>(1); + headers.put(Constant.CONTENT_TYPE, Constant.APPLICATION_JSON); + return headers; + } + + private static LinkedHashMap buildBody(String accessKeyId, String accessKeySecret, String templateId){ + LinkedHashMap body = new LinkedHashMap<>(3); + body.put("account", accessKeyId); + body.put("password", accessKeySecret); + body.put("msg", templateId); + return body; + } + + private SmsResponse getSmsResponse(LinkedHashMap body) { + ChuangLanConfig config = getConfig(); + SmsResponse smsResponse; + String reqUrl = buildUrl(config.getBaseUrl(), config.getMsgUrl()); + try { + smsResponse = getResponse(http.postJson(reqUrl, buildHeaders(), body)); + }catch (SmsBlendException e) { + smsResponse = errorResp(e.message); + } + if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) { + retry = 0; + return smsResponse; + } + http.safeSleep(getConfig().getRetryInterval()); + retry++; + log.warn("短信第 {" + retry + "} 次重新发送"); + return requestRetry(body); + } + + private SmsResponse requestRetry(LinkedHashMap body) { + http.safeSleep(getConfig().getRetryInterval()); + retry ++; + log.warn("短信第 {} 次重新发送", retry); + return getSmsResponse(body); + } + + private SmsResponse getResponse(JSONObject resJson) { + return SmsRespUtils.resp(resJson, resJson.containsKey("code") && "0".equals(resJson.getStr("code")), getConfigId()); + } + +} \ No newline at end of file diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/cloopen/config/CloopenConfig.java b/sms4j-provider/src/main/java/org/dromara/sms4j/cloopen/config/CloopenConfig.java index 4904b09b..d37438ed 100644 --- a/sms4j-provider/src/main/java/org/dromara/sms4j/cloopen/config/CloopenConfig.java +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/cloopen/config/CloopenConfig.java @@ -2,6 +2,7 @@ package org.dromara.sms4j.cloopen.config; import lombok.Data; import lombok.EqualsAndHashCode; +import org.dromara.sms4j.comm.constant.Constant; import org.dromara.sms4j.comm.constant.SupplierConstant; import org.dromara.sms4j.provider.config.BaseConfig; @@ -18,7 +19,7 @@ public class CloopenConfig extends BaseConfig { /** * REST API Base URL */ - private String baseUrl = "https://app.cloopen.com:8883/2013-12-26"; + private String baseUrl = Constant.HTTPS_PREFIX + "app.cloopen.com:8883/2013-12-26"; /** * 获取供应商 diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/cloopen/config/CloopenFactory.java b/sms4j-provider/src/main/java/org/dromara/sms4j/cloopen/config/CloopenFactory.java index af092503..0418e4ec 100644 --- a/sms4j-provider/src/main/java/org/dromara/sms4j/cloopen/config/CloopenFactory.java +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/cloopen/config/CloopenFactory.java @@ -27,7 +27,7 @@ public class CloopenFactory extends AbstractProviderFactory headers = MapUtil.newHashMap(3, true); - headers.put("Accept", Constant.ACCEPT); - headers.put("Content-Type", Constant.APPLICATION_JSON_UTF8); - headers.put("Authorization", this.generateAuthorization(config.getAccessKeyId(), timestamp)); - SmsResponse smsResponse = null; + headers.put(Constant.ACCEPT, Constant.APPLICATION_JSON); + headers.put(Constant.CONTENT_TYPE, Constant.APPLICATION_JSON_UTF8); + headers.put(Constant.AUTHORIZATION, this.generateAuthorization(config.getAccessKeyId(), timestamp)); + SmsResponse smsResponse; try { smsResponse = getResponse(http.postJson(url, headers, paramMap)); } catch (SmsBlendException e) { - smsResponse = new SmsResponse(); - smsResponse.setSuccess(false); - smsResponse.setData(e.getMessage()); + smsResponse = SmsRespUtils.error(e.message, config.getConfigId()); } if (smsResponse.isSuccess() || retry == config.getMaxRetries()) { retry = 0; @@ -70,11 +69,7 @@ public class CloopenHelper { } private SmsResponse getResponse(JSONObject resJson) { - SmsResponse smsResponse = new SmsResponse(); - smsResponse.setSuccess("000000".equals(resJson.getStr("statusCode"))); - smsResponse.setData(resJson); - smsResponse.setConfigId(this.config.getConfigId()); - return smsResponse; + return SmsRespUtils.resp(resJson, "000000".equals(resJson.getStr("statusCode")), config.getConfigId()); } /** diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/ctyun/config/CtyunConfig.java b/sms4j-provider/src/main/java/org/dromara/sms4j/ctyun/config/CtyunConfig.java index 7987033c..ca0cc765 100644 --- a/sms4j-provider/src/main/java/org/dromara/sms4j/ctyun/config/CtyunConfig.java +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/ctyun/config/CtyunConfig.java @@ -2,6 +2,7 @@ package org.dromara.sms4j.ctyun.config; import lombok.Data; import lombok.EqualsAndHashCode; +import org.dromara.sms4j.comm.constant.Constant; import org.dromara.sms4j.comm.constant.SupplierConstant; import org.dromara.sms4j.provider.config.BaseConfig; @@ -9,7 +10,7 @@ import org.dromara.sms4j.provider.config.BaseConfig; *

类名: CtyunConfig *

说明: 天翼云短信差异配置 * - * @author :bleachhtred + * @author :bleachtred * 2023/5/12 15:06 **/ @Data @@ -24,7 +25,7 @@ public class CtyunConfig extends BaseConfig { /** * 请求地址 */ - private String requestUrl = "https://sms-global.ctapi.ctyun.cn/sms/api/v1"; + private String requestUrl = Constant.HTTPS_PREFIX + "sms-global.ctapi.ctyun.cn/sms/api/v1"; /** * 接口名称 diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/ctyun/config/CtyunFactory.java b/sms4j-provider/src/main/java/org/dromara/sms4j/ctyun/config/CtyunFactory.java index f6d64429..917417cc 100644 --- a/sms4j-provider/src/main/java/org/dromara/sms4j/ctyun/config/CtyunFactory.java +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/ctyun/config/CtyunFactory.java @@ -10,7 +10,7 @@ import org.dromara.sms4j.provider.factory.AbstractProviderFactory; *

类名: CtyunSmsConfig *

说明: 天翼云 云通信短信配置器 * - * @author :bleachhtred + * @author :bleachtred * 2023/5/12 15:06 **/ @NoArgsConstructor(access = AccessLevel.PRIVATE) @@ -30,7 +30,7 @@ public class CtyunFactory extends AbstractProviderFactory 建造一个短信实现对像 * - * @author :bleachhtred + * @author :bleachtred */ @Override public CtyunSmsImpl createSms(CtyunConfig ctyunConfig) { diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/ctyun/service/CtyunSmsImpl.java b/sms4j-provider/src/main/java/org/dromara/sms4j/ctyun/service/CtyunSmsImpl.java index 038b0498..4b69c69c 100644 --- a/sms4j-provider/src/main/java/org/dromara/sms4j/ctyun/service/CtyunSmsImpl.java +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/ctyun/service/CtyunSmsImpl.java @@ -4,6 +4,7 @@ import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import lombok.extern.slf4j.Slf4j; import org.dromara.sms4j.api.entity.SmsResponse; +import org.dromara.sms4j.api.utils.SmsRespUtils; import org.dromara.sms4j.comm.constant.SupplierConstant; import org.dromara.sms4j.comm.delayedTime.DelayedTime; import org.dromara.sms4j.comm.exception.SmsBlendException; @@ -21,7 +22,7 @@ import java.util.concurrent.Executor; *

类名: CtyunSmsImpl *

说明: 天翼云短信实现 * - * @author :bleachhtred + * @author :bleachtred * 2023/5/12 15:06 **/ @Slf4j @@ -79,15 +80,16 @@ public class CtyunSmsImpl extends AbstractSmsBlend { messages = new LinkedHashMap<>(); } String messageStr = JSONUtil.toJsonStr(messages); - return getSmsResponse(SmsUtils.arrayToString(phones), messageStr, templateId); + return getSmsResponse(SmsUtils.addCodePrefixIfNot(phones), messageStr, templateId); } private SmsResponse getSmsResponse(String phone, String message, String templateId) { + CtyunConfig config = getConfig(); String requestUrl; String paramStr; try { - requestUrl = getConfig().getRequestUrl(); - paramStr = CtyunUtils.generateParamJsonStr(getConfig(), phone, message, templateId); + requestUrl = config.getRequestUrl(); + paramStr = CtyunUtils.generateParamJsonStr(config, phone, message, templateId); } catch (Exception e) { log.error("ctyun send message error", e); throw new SmsBlendException(e.getMessage()); @@ -96,14 +98,12 @@ public class CtyunSmsImpl extends AbstractSmsBlend { SmsResponse smsResponse; try { smsResponse = getResponse(http.postJson(requestUrl, - CtyunUtils.signHeader(paramStr, getConfig().getAccessKeyId(), getConfig().getAccessKeySecret()), + CtyunUtils.signHeader(paramStr, config.getAccessKeyId(), config.getAccessKeySecret()), paramStr)); } catch (SmsBlendException e) { - smsResponse = new SmsResponse(); - smsResponse.setSuccess(false); - smsResponse.setData(e.getMessage()); + smsResponse = errorResp(e.message); } - if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) { + if (smsResponse.isSuccess() || retry == config.getMaxRetries()) { retry = 0; return smsResponse; } @@ -118,11 +118,7 @@ public class CtyunSmsImpl extends AbstractSmsBlend { } private SmsResponse getResponse(JSONObject resJson) { - SmsResponse smsResponse = new SmsResponse(); - smsResponse.setSuccess("OK".equals(resJson.getStr("code"))); - smsResponse.setData(resJson); - smsResponse.setConfigId(getConfigId()); - return smsResponse; + return SmsRespUtils.resp(resJson, "OK".equals(resJson.getStr("code")), getConfigId()); } } \ No newline at end of file diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/ctyun/utils/CtyunUtils.java b/sms4j-provider/src/main/java/org/dromara/sms4j/ctyun/utils/CtyunUtils.java index 5f4af767..1332735a 100644 --- a/sms4j-provider/src/main/java/org/dromara/sms4j/ctyun/utils/CtyunUtils.java +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/ctyun/utils/CtyunUtils.java @@ -1,22 +1,18 @@ package org.dromara.sms4j.ctyun.utils; import cn.hutool.core.codec.Base64; -import cn.hutool.crypto.digest.HMac; -import cn.hutool.crypto.digest.HmacAlgorithm; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.crypto.digest.DigestUtil; import cn.hutool.json.JSONUtil; import lombok.AccessLevel; import lombok.NoArgsConstructor; import org.dromara.sms4j.comm.constant.Constant; -import org.dromara.sms4j.comm.exception.SmsBlendException; +import org.dromara.sms4j.comm.utils.SmsDateUtils; import org.dromara.sms4j.ctyun.config.CtyunConfig; import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; -import java.util.Locale; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; @@ -28,8 +24,7 @@ public class CtyunUtils { * 获取签名时间戳 */ private static String signatureTime(){ - SimpleDateFormat timeFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"); - return timeFormat.format(new Date()); + return SmsDateUtils.pureDateUtcGmt(new Date()); } /** @@ -39,9 +34,7 @@ public class CtyunUtils { Map map = new ConcurrentHashMap<>(4); // 构造时间戳 - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd"); - Date now = new Date(); - String signatureDate = dateFormat.format(now); + String signatureDate = SmsDateUtils.pureDateGmt(new Date()); String signatureTime = signatureTime(); // 构造请求流水号 String uuid = UUID.randomUUID().toString(); @@ -59,7 +52,7 @@ public class CtyunUtils { // 构造签名 String signature = Base64.encode(hmacSHA256(signatureStr.getBytes(StandardCharsets.UTF_8), kDate)); String signHeader = String.format("%s Headers=ctyun-eop-request-id;eop-date Signature=%s", key, signature); - map.put("Content-Type", Constant.APPLICATION_JSON_UTF8); + map.put(Constant.CONTENT_TYPE, Constant.APPLICATION_JSON_UTF8); map.put("ctyun-eop-request-id", uuid); map.put("Eop-date", signatureTime); map.put("Eop-Authorization", signHeader); @@ -84,36 +77,11 @@ public class CtyunUtils { return JSONUtil.toJsonStr(paramMap); } - private static String toHex(byte[] data) { - StringBuilder sb = new StringBuilder(data.length * 2); - for (byte b : data) { - String hex = Integer.toHexString(b); - if (hex.length() == 1) { - sb.append("0"); - } else if (hex.length() == 8) { - hex = hex.substring(6); - } - sb.append(hex); - } - return sb.toString().toLowerCase(Locale.getDefault()); - } - private static String getSHA256(String text) { - try { - MessageDigest md = MessageDigest.getInstance("SHA-256"); - md.update(text.getBytes(StandardCharsets.UTF_8)); - return toHex(md.digest()); - } catch (NoSuchAlgorithmException var3) { - return null; - } + return DigestUtil.sha256Hex(text); } private static byte[] hmacSHA256(byte[] data, byte[] key){ - try { - HMac hMac = new HMac(HmacAlgorithm.HmacSHA256, key); - return hMac.digest(data); - } catch (Exception e) { - throw new SmsBlendException(e.getMessage()); - } + return SecureUtil.hmacSha256(key).digest(data); } } diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/danmi/config/DanMiConfig.java b/sms4j-provider/src/main/java/org/dromara/sms4j/danmi/config/DanMiConfig.java new file mode 100644 index 00000000..79c8b418 --- /dev/null +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/danmi/config/DanMiConfig.java @@ -0,0 +1,44 @@ +package org.dromara.sms4j.danmi.config; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.sms4j.comm.constant.Constant; +import org.dromara.sms4j.comm.constant.SupplierConstant; +import org.dromara.sms4j.provider.config.BaseConfig; + +/** + *

类名: DanMiConfig + *

说明: 旦米短信差异配置 + * + * @author :bleachtred + * 2024/6/23 17:06 + **/ +@Data +@EqualsAndHashCode(callSuper = true) +public class DanMiConfig extends BaseConfig { + + /** + * 请求地址 + */ + private String host = Constant.HTTPS_PREFIX + "openapi.danmi.com/"; + + /** + * 请求方法 + * 短信发送 distributor/sendSMS + * 短信余额查询 distributor/user/query + * 语音验证码发送 voice/voiceCode + * 语音通知文件发送 voice/voiceNotify + * 语音模板通知发送 voice/voiceTemplate + */ + private String action = "distributor/sendSMS"; + + /** + * 获取供应商 + * + * @since 3.0.0 + */ + @Override + public String getSupplier() { + return SupplierConstant.DAN_MI; + } +} diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/danmi/config/DanMiFactory.java b/sms4j-provider/src/main/java/org/dromara/sms4j/danmi/config/DanMiFactory.java new file mode 100644 index 00000000..9c2f0859 --- /dev/null +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/danmi/config/DanMiFactory.java @@ -0,0 +1,48 @@ +package org.dromara.sms4j.danmi.config; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.dromara.sms4j.comm.constant.SupplierConstant; +import org.dromara.sms4j.danmi.service.DanMiSmsImpl; +import org.dromara.sms4j.provider.factory.AbstractProviderFactory; + +/** + *

类名: DanMiFactory + * + * @author :bleachtred + * 2024/6/23 17:06 + **/ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class DanMiFactory extends AbstractProviderFactory { + + private static final DanMiFactory INSTANCE = new DanMiFactory(); + + /** + * 获取建造者实例 + * @return 建造者实例 + */ + public static DanMiFactory instance() { + return INSTANCE; + } + + /** + * createSms + *

建造一个短信实现对像 + * + * @author :bleachtred + */ + @Override + public DanMiSmsImpl createSms(DanMiConfig config) { + return new DanMiSmsImpl(config); + } + + /** + * 获取供应商 + * @return 供应商 + */ + @Override + public String getSupplier() { + return SupplierConstant.DAN_MI; + } + +} diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/danmi/service/DanMiSmsImpl.java b/sms4j-provider/src/main/java/org/dromara/sms4j/danmi/service/DanMiSmsImpl.java new file mode 100644 index 00000000..5287433c --- /dev/null +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/danmi/service/DanMiSmsImpl.java @@ -0,0 +1,153 @@ +package org.dromara.sms4j.danmi.service; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import lombok.extern.slf4j.Slf4j; +import org.dromara.sms4j.api.entity.SmsResponse; +import org.dromara.sms4j.api.utils.SmsRespUtils; +import org.dromara.sms4j.comm.constant.SupplierConstant; +import org.dromara.sms4j.comm.delayedTime.DelayedTime; +import org.dromara.sms4j.comm.exception.SmsBlendException; +import org.dromara.sms4j.comm.utils.SmsUtils; +import org.dromara.sms4j.danmi.config.DanMiConfig; +import org.dromara.sms4j.danmi.utils.DanMiUtils; +import org.dromara.sms4j.provider.service.AbstractSmsBlend; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.Executor; + +/** + *

类名: DanMiSmsImpl + * + * @author :bleachtred + * 2024/6/23 17:06 + **/ +@Slf4j +public class DanMiSmsImpl extends AbstractSmsBlend { + + private int retry = 0; + + public DanMiSmsImpl(DanMiConfig config, Executor pool, DelayedTime delayedTime) { + super(config, pool, delayedTime); + } + + public DanMiSmsImpl(DanMiConfig config) { + super(config); + } + + @Override + public String getSupplier() { + return SupplierConstant.DAN_MI; + } + + @Override + public SmsResponse sendMessage(String phone, String message) { + if (StrUtil.isBlank(phone)){ + log.error("手机号不能为空"); + throw new SmsBlendException("手机号不能为空"); + } + List phones = phone.contains(StrUtil.COMMA) ? SmsUtils.splitTrimComma(phone) : Collections.singletonList(phone); + return massTexting(phones, message); + } + + @Override + public SmsResponse sendMessage(String phone, LinkedHashMap messages) { + throw new SmsBlendException("不支持此方法"); + } + + @Override + public SmsResponse sendMessage(String phone, String templateId, LinkedHashMap messages) { + throw new SmsBlendException("不支持此方法"); + } + + @Override + public SmsResponse massTexting(List phones, String message) { + return getSmsResponse(phones, message, getConfig().getTemplateId()); + } + + @Override + public SmsResponse massTexting(List phones, String templateId, LinkedHashMap messages) { + throw new SmsBlendException("不支持此方法"); + } + + /** + * 短信余额查询 + * 请设置action为 distributor/user/query + * + * @return SmsResponse + */ + public SmsResponse queryBalance() { + return getSmsResponse(null, null, null); + } + + /** + * 语音验证码发送 + * 请设置action为 voice/voiceCode + * + * @param called 被叫号码 + * @param verifyCode 验证码内容(1-8位数字) + * @return SmsResponse + */ + public SmsResponse voiceCode(String called, String verifyCode) { + return getSmsResponse(Collections.singletonList(called), verifyCode, null); + } + + /** + * 语音通知文件发送 + * 请设置action为 voice/voiceNotify + * + * @param called 被叫号码 + * @param notifyFileId 语音文件ID + * @return SmsResponse + */ + public SmsResponse voiceNotify(String called, String notifyFileId) { + return getSmsResponse(Collections.singletonList(called), notifyFileId, null); + } + + /** + * 语音模板通知发送 + * 请设置action为 voice/voiceTemplate + * + * @param called 被叫号码 + * @param templateId 文字模板Id(用户中心创建后产生) + * @param param 模板变量替换的参数(多个变量按英文逗号分开) + * @return SmsResponse + */ + public SmsResponse voiceTemplate(String called, String templateId, String param) { + return getSmsResponse(Collections.singletonList(called), param, templateId); + } + + private SmsResponse getSmsResponse(List phones, String message, String templateId) { + DanMiConfig config = getConfig(); + SmsResponse smsResponse; + try { + String url = config.getHost() + config.getAction(); + smsResponse = getResponse(http.postJson(url, + DanMiUtils.buildHeaders(), + DanMiUtils.buildBody(config, phones, message, templateId))); + } catch (SmsBlendException e) { + smsResponse = errorResp(e.message); + } + if (smsResponse.isSuccess() || retry == config.getMaxRetries()) { + retry = 0; + return smsResponse; + } + return requestRetry(phones, message, templateId); + } + + private SmsResponse requestRetry(List phones, String message, String templateId) { + http.safeSleep(getConfig().getRetryInterval()); + retry ++; + log.warn("短信第 {} 次重新发送", retry); + return getSmsResponse(phones, message, templateId); + } + + private SmsResponse getResponse(JSONObject resJson) { + return SmsRespUtils.resp(resJson, "00000".equals(resJson.getStr("respCode")), getConfigId()); + } + +} \ No newline at end of file diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/danmi/utils/DanMiUtils.java b/sms4j-provider/src/main/java/org/dromara/sms4j/danmi/utils/DanMiUtils.java new file mode 100644 index 00000000..8a48d35f --- /dev/null +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/danmi/utils/DanMiUtils.java @@ -0,0 +1,127 @@ +package org.dromara.sms4j.danmi.utils; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.net.URLEncodeUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.digest.DigestUtil; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.dromara.sms4j.comm.constant.Constant; +import org.dromara.sms4j.comm.exception.SmsBlendException; +import org.dromara.sms4j.comm.utils.SmsUtils; +import org.dromara.sms4j.danmi.config.DanMiConfig; + +import java.util.LinkedHashMap; +import java.util.List; + +/** + *

类名: DanMiUtils + * + * @author :bleachtred + * 2024/6/23 17:06 + **/ +@Slf4j +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class DanMiUtils { + + public static LinkedHashMap buildHeaders(){ + LinkedHashMap headers = new LinkedHashMap<>(1); + headers.put(Constant.CONTENT_TYPE, Constant.APPLICATION_JSON); + return headers; + } + + /** + * 生成请求body参数 + * + * @param config 配置数据 + * @param phones 手机号 + * @param message 短信内容 (or 验证码内容(1-8位数字) or 语音文件ID or 模板参数) + * @param templateId 模板id + */ + public static LinkedHashMap buildBody(DanMiConfig config, List phones, String message, String templateId) { + LinkedHashMap body = new LinkedHashMap<>(); + body.put("respDataType", "JSON"); + body.put("accountSid", config.getAccessKeyId()); + switch (config.getAction()){ + case "distributor/sendSMS": + if (StrUtil.isAllBlank(message, templateId)){ + log.error("message and templateId can not be empty at the same time"); + throw new SmsBlendException("message and templateId can not be empty at the same time"); + } + if (StrUtil.isNotBlank(templateId)){ + body.put("templateid", templateId); + } + if (StrUtil.isNotBlank(message)){ + body.put("smsContent", URLEncodeUtil.encode(message)); + } + if (CollUtil.isEmpty(phones)){ + log.error("phones can not be empty"); + throw new SmsBlendException("phones can not be empty"); + } + body.put("to", SmsUtils.addCodePrefixIfNot(phones)); + break; + case "distributor/user/query": + break; + case "voice/voiceCode": + if (CollUtil.isEmpty(phones) || phones.size() != 1){ + log.error("called can not be empty or phone must be only one"); + throw new SmsBlendException("called can not be empty or phone must be only one"); + } + body.put("called", SmsUtils.addCodePrefixIfNot(phones.get(0))); + if (StrUtil.isBlank(message)){ + log.error("verifyCode can not be empty"); + throw new SmsBlendException("verifyCode can not be empty"); + } + body.put("verifyCode", message); + break; + case "voice/voiceNotify": + if (CollUtil.isEmpty(phones) || phones.size() != 1){ + log.error("called can not be empty or phone must be only one"); + throw new SmsBlendException("called can not be empty or phone must be only one"); + } + body.put("called", SmsUtils.addCodePrefixIfNot(phones.get(0))); + if (StrUtil.isBlank(message)){ + log.error("notifyFileId can not be empty"); + throw new SmsBlendException("notifyFileId can not be empty"); + } + body.put("notifyFileId", message); + break; + case "voice/voiceTemplate": + if (CollUtil.isEmpty(phones) || phones.size() != 1){ + log.error("called can not be empty or phone must be only one"); + throw new SmsBlendException("called can not be empty or phone must be only one"); + } + body.put("called", SmsUtils.addCodePrefixIfNot(phones.get(0))); + if (StrUtil.isEmpty(templateId)){ + log.error("templateId can not be empty"); + throw new SmsBlendException("templateId can not be empty"); + } + body.put("templateId", templateId); + if (StrUtil.isEmpty(message)){ + log.error("param can not be empty"); + throw new SmsBlendException("param can not be empty"); + } + body.put("param", message); + break; + default: + log.error("action not found"); + throw new SmsBlendException("action not found"); + } + long timestamp = System.currentTimeMillis(); + body.put("timestamp", timestamp); + body.put("sig", sign(config.getAccessKeyId(), config.getAccessKeySecret(), timestamp)); + return body; + } + + /** + * 签名:MD5(ACCOUNT SID + AUTH TOKEN + timestamp)。共32位(小写) + * @param accessKeyId ACCOUNT SID + * @param accessKeySecret AUTH TOKEN + * @param timestamp timestamp + * @return 签名:MD5 共32位(小写) + */ + private static String sign(String accessKeyId, String accessKeySecret, long timestamp){ + return DigestUtil.md5Hex(accessKeyId + accessKeySecret + timestamp); + } +} diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/dingzhong/service/DingZhongSmsImpl.java b/sms4j-provider/src/main/java/org/dromara/sms4j/dingzhong/service/DingZhongSmsImpl.java index 604e9605..8b260a37 100644 --- a/sms4j-provider/src/main/java/org/dromara/sms4j/dingzhong/service/DingZhongSmsImpl.java +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/dingzhong/service/DingZhongSmsImpl.java @@ -86,11 +86,11 @@ public class DingZhongSmsImpl extends AbstractSmsBlend { @Override public SmsResponse massTexting(List phones, String message) { - return sendMessage(SmsUtils.arrayToString(phones), message); + return sendMessage(SmsUtils.addCodePrefixIfNot(phones), message); } @Override public SmsResponse massTexting(List phones, String templateId, LinkedHashMap messages) { - return sendMessage(SmsUtils.arrayToString(phones), templateId, messages); + return sendMessage(SmsUtils.addCodePrefixIfNot(phones), templateId, messages); } } \ No newline at end of file diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/dingzhong/util/DingZhongHelper.java b/sms4j-provider/src/main/java/org/dromara/sms4j/dingzhong/util/DingZhongHelper.java index 628a3c40..3776c827 100644 --- a/sms4j-provider/src/main/java/org/dromara/sms4j/dingzhong/util/DingZhongHelper.java +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/dingzhong/util/DingZhongHelper.java @@ -4,6 +4,7 @@ import cn.hutool.core.map.MapUtil; import cn.hutool.json.JSONObject; import lombok.extern.slf4j.Slf4j; import org.dromara.sms4j.api.entity.SmsResponse; +import org.dromara.sms4j.api.utils.SmsRespUtils; import org.dromara.sms4j.comm.constant.Constant; import org.dromara.sms4j.comm.exception.SmsBlendException; import org.dromara.sms4j.comm.utils.SmsHttpUtils; @@ -33,15 +34,13 @@ public class DingZhongHelper { public SmsResponse smsResponse(Map paramMap) { String url = String.format("%s/%s", config.getRequestUrl(), SmsUtils.isEmpty(paramMap.get("templateId"))?config.getBaseAction():config.getTemplateAction()); Map headers = MapUtil.newHashMap(2, true); - headers.put("Accept", Constant.ACCEPT); - headers.put("Content-Type", Constant.FROM_URLENCODED); - SmsResponse smsResponse = null; + headers.put(Constant.ACCEPT, Constant.APPLICATION_JSON); + headers.put(Constant.CONTENT_TYPE, Constant.APPLICATION_FROM_URLENCODED); + SmsResponse smsResponse; try { smsResponse = getResponse(http.postFrom(url, headers, paramMap)); } catch (SmsBlendException e) { - smsResponse = new SmsResponse(); - smsResponse.setSuccess(false); - smsResponse.setData(e.getMessage()); + smsResponse = SmsRespUtils.error(e.message, config.getConfigId()); } if (smsResponse.isSuccess() || retry == config.getMaxRetries()) { retry = 0; @@ -59,10 +58,6 @@ public class DingZhongHelper { } private SmsResponse getResponse(JSONObject resJson) { - SmsResponse smsResponse = new SmsResponse(); - smsResponse.setSuccess("0".equals(resJson.getStr("resCode"))); - smsResponse.setData(resJson); - smsResponse.setConfigId(this.config.getConfigId()); - return smsResponse; + return SmsRespUtils.resp(resJson, "0".equals(resJson.getStr("resCode")), config.getConfigId()); } } diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/emay/service/EmaySmsImpl.java b/sms4j-provider/src/main/java/org/dromara/sms4j/emay/service/EmaySmsImpl.java index 3f5a293d..a9fb930c 100644 --- a/sms4j-provider/src/main/java/org/dromara/sms4j/emay/service/EmaySmsImpl.java +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/emay/service/EmaySmsImpl.java @@ -4,6 +4,7 @@ import cn.hutool.core.map.MapUtil; import cn.hutool.json.JSONObject; import lombok.extern.slf4j.Slf4j; import org.dromara.sms4j.api.entity.SmsResponse; +import org.dromara.sms4j.api.utils.SmsRespUtils; import org.dromara.sms4j.comm.constant.Constant; import org.dromara.sms4j.comm.constant.SupplierConstant; import org.dromara.sms4j.comm.delayedTime.DelayedTime; @@ -44,20 +45,19 @@ public class EmaySmsImpl extends AbstractSmsBlend { @Override public SmsResponse sendMessage(String phone, String message) { - String url = getConfig().getRequestUrl(); - Map params = EmayBuilder.buildRequestBody(getConfig().getAccessKeyId(), getConfig().getAccessKeySecret(), phone, message); + EmayConfig config = getConfig(); + String url = config.getRequestUrl(); + Map params = EmayBuilder.buildRequestBody(config.getAccessKeyId(), config.getAccessKeySecret(), phone, message); Map headers = MapUtil.newHashMap(1, true); - headers.put("Content-Type", Constant.FROM_URLENCODED); + headers.put(Constant.CONTENT_TYPE, Constant.APPLICATION_FROM_URLENCODED); SmsResponse smsResponse; try { smsResponse = getResponse(http.postUrl(url, headers, params)); } catch (SmsBlendException e) { - smsResponse = new SmsResponse(); - smsResponse.setSuccess(false); - smsResponse.setData(e.getMessage()); + smsResponse = errorResp(e.message); } - if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) { + if (smsResponse.isSuccess() || retry == config.getMaxRetries()) { retry = 0; return smsResponse; } @@ -97,7 +97,7 @@ public class EmaySmsImpl extends AbstractSmsBlend { if (phones.size() > 500) { throw new SmsBlendException("单次发送超过最大发送上限,建议每次群发短信人数低于500"); } - return sendMessage(SmsUtils.listToString(phones), message); + return sendMessage(SmsUtils.joinComma(phones), message); } @Override @@ -109,15 +109,11 @@ public class EmaySmsImpl extends AbstractSmsBlend { for (Map.Entry entry : messages.entrySet()) { list.add(entry.getValue()); } - return sendMessage(SmsUtils.listToString(phones), EmayBuilder.listToString(list)); + return sendMessage(SmsUtils.joinComma(phones), EmayBuilder.listToString(list)); } private SmsResponse getResponse(JSONObject resJson) { - SmsResponse smsResponse = new SmsResponse(); - smsResponse.setSuccess("success".equalsIgnoreCase(resJson.getStr("code"))); - smsResponse.setData(resJson); - smsResponse.setConfigId(getConfigId()); - return smsResponse; + return SmsRespUtils.resp(resJson, "success".equalsIgnoreCase(resJson.getStr("code")), getConfigId()); } } diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/huawei/service/HuaweiSmsImpl.java b/sms4j-provider/src/main/java/org/dromara/sms4j/huawei/service/HuaweiSmsImpl.java index 7bb860da..b3415aa6 100644 --- a/sms4j-provider/src/main/java/org/dromara/sms4j/huawei/service/HuaweiSmsImpl.java +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/huawei/service/HuaweiSmsImpl.java @@ -5,6 +5,7 @@ import cn.hutool.core.map.MapUtil; import cn.hutool.json.JSONObject; import lombok.extern.slf4j.Slf4j; import org.dromara.sms4j.api.entity.SmsResponse; +import org.dromara.sms4j.api.utils.SmsRespUtils; import org.dromara.sms4j.comm.constant.Constant; import org.dromara.sms4j.comm.constant.SupplierConstant; import org.dromara.sms4j.comm.delayedTime.DelayedTime; @@ -70,16 +71,14 @@ public class HuaweiSmsImpl extends AbstractSmsBlend { String requestBody = HuaweiBuilder.buildRequestBody(getConfig().getSender(), phone, templateId, mess, getConfig().getStatusCallBack(), getConfig().getSignature()); Map headers = MapUtil.newHashMap(3, true); - headers.put("Authorization", Constant.HUAWEI_AUTH_HEADER_VALUE); + headers.put(Constant.AUTHORIZATION, Constant.HUAWEI_AUTH_HEADER_VALUE); headers.put("X-WSSE", HuaweiBuilder.buildWsseHeader(getConfig().getAccessKeyId(), getConfig().getAccessKeySecret())); - headers.put("Content-Type", Constant.FROM_URLENCODED); + headers.put(Constant.CONTENT_TYPE, Constant.APPLICATION_FROM_URLENCODED); SmsResponse smsResponse; try { smsResponse = getResponse(http.postJson(url, headers, requestBody)); } catch (SmsBlendException e) { - smsResponse = new SmsResponse(); - smsResponse.setSuccess(false); - smsResponse.setData(e.getMessage()); + smsResponse = errorResp(e.message); } if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) { retry = 0; @@ -109,11 +108,7 @@ public class HuaweiSmsImpl extends AbstractSmsBlend { } private SmsResponse getResponse(JSONObject resJson) { - SmsResponse smsResponse = new SmsResponse(); - smsResponse.setSuccess("000000".equals(resJson.getStr("code"))); - smsResponse.setData(resJson); - smsResponse.setConfigId(getConfigId()); - return smsResponse; + return SmsRespUtils.resp(resJson, "000000".equals(resJson.getStr("code")), getConfigId()); } } diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/huawei/utils/HuaweiBuilder.java b/sms4j-provider/src/main/java/org/dromara/sms4j/huawei/utils/HuaweiBuilder.java index df447d7a..b1ba3aac 100644 --- a/sms4j-provider/src/main/java/org/dromara/sms4j/huawei/utils/HuaweiBuilder.java +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/huawei/utils/HuaweiBuilder.java @@ -2,6 +2,11 @@ package org.dromara.sms4j.huawei.utils; import cn.hutool.core.codec.Base64; import cn.hutool.core.date.DateUtil; +import cn.hutool.core.lang.UUID; +import cn.hutool.core.net.URLEncodeUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.digest.DigestUtil; +import lombok.extern.slf4j.Slf4j; import org.dromara.sms4j.comm.constant.Constant; import org.dromara.sms4j.comm.exception.SmsBlendException; @@ -9,17 +14,13 @@ import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import java.security.cert.X509Certificate; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.UUID; +@Slf4j public class HuaweiBuilder { private HuaweiBuilder() { } @@ -32,22 +33,13 @@ public class HuaweiBuilder { */ public static String buildWsseHeader(String appKey, String appSecret) { if (null == appKey || null == appSecret || appKey.isEmpty() || appSecret.isEmpty()) { - System.out.println("buildWsseHeader(): appKey or appSecret is null."); - return null; + log.error("buildWsseHeader(): appKey or appSecret is null."); + throw new SmsBlendException("buildWsseHeader(): appKey or appSecret is null."); } String time = dateFormat(new Date()); // Nonce - String nonce = UUID.randomUUID().toString().replace("-", ""); - MessageDigest md; - byte[] passwordDigest; - - try { - md = MessageDigest.getInstance("SHA-256"); - md.update((nonce + time + appSecret).getBytes()); - passwordDigest = md.digest(); - } catch (NoSuchAlgorithmException e) { - throw new SmsBlendException(e); - } + String nonce = UUID.fastUUID().toString(true); + byte[] passwordDigest = DigestUtil.sha256(nonce + time + appSecret); // PasswordDigest String passwordDigestBase64Str = Base64.encode(passwordDigest); //若passwordDigestBase64Str中包含换行符,请执行如下代码进行修正 @@ -91,12 +83,11 @@ public class HuaweiBuilder { */ public static String buildRequestBody(String sender, String receiver, String templateId, String templateParas, String statusCallBack, String signature) { - if (null == sender || null == receiver || null == templateId || sender.isEmpty() || receiver.isEmpty() - || templateId.isEmpty()) { - System.out.println("buildRequestBody(): sender, receiver or templateId is null."); - return null; + if (StrUtil.hasBlank(sender, receiver, templateId)) { + log.error("buildRequestBody(): sender, receiver or templateId is null."); + throw new SmsBlendException("buildRequestBody(): sender, receiver or templateId is null."); } - Map map = new HashMap<>(); + Map map = new HashMap<>(3); map.put("from", sender); map.put("to", receiver); @@ -112,17 +103,7 @@ public class HuaweiBuilder { } StringBuilder sb = new StringBuilder(); - String temp; - - for (String s : map.keySet()) { - try { - temp = URLEncoder.encode(map.get(s), "UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new SmsBlendException(e); - } - sb.append(s).append("=").append(temp).append("&"); - } - + map.keySet().forEach(s -> sb.append(s).append("=").append(URLEncodeUtil.encode(map.get(s))).append("&")); return sb.deleteCharAt(sb.length() - 1).toString(); } diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/jdcloud/service/JdCloudSmsImpl.java b/sms4j-provider/src/main/java/org/dromara/sms4j/jdcloud/service/JdCloudSmsImpl.java index b37b875e..886d9141 100644 --- a/sms4j-provider/src/main/java/org/dromara/sms4j/jdcloud/service/JdCloudSmsImpl.java +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/jdcloud/service/JdCloudSmsImpl.java @@ -6,6 +6,7 @@ import com.jdcloud.sdk.service.sms.model.BatchSendRequest; import com.jdcloud.sdk.service.sms.model.BatchSendResult; import lombok.extern.slf4j.Slf4j; import org.dromara.sms4j.api.entity.SmsResponse; +import org.dromara.sms4j.api.utils.SmsRespUtils; import org.dromara.sms4j.comm.constant.SupplierConstant; import org.dromara.sms4j.comm.delayedTime.DelayedTime; import org.dromara.sms4j.comm.exception.SmsBlendException; @@ -96,9 +97,7 @@ public class JdCloudSmsImpl extends AbstractSmsBlend { try { smsResponse = getSmsResponse(result); } catch (SmsBlendException e) { - smsResponse = new SmsResponse(); - smsResponse.setSuccess(false); - smsResponse.setData(e.getMessage()); + smsResponse = errorResp(e.message); } if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) { retry = 0; @@ -121,10 +120,6 @@ public class JdCloudSmsImpl extends AbstractSmsBlend { * @return 发送短信返回信息 */ private SmsResponse getSmsResponse(BatchSendResult res) { - SmsResponse smsResponse = new SmsResponse(); - smsResponse.setSuccess(res.getStatus() != null && res.getStatus()); - smsResponse.setData(res); - smsResponse.setConfigId(getConfigId()); - return smsResponse; + return SmsRespUtils.resp(res, res.getStatus() != null && res.getStatus(), getConfigId()); } } diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/jg/config/JgConfig.java b/sms4j-provider/src/main/java/org/dromara/sms4j/jg/config/JgConfig.java new file mode 100644 index 00000000..01f60730 --- /dev/null +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/jg/config/JgConfig.java @@ -0,0 +1,72 @@ +package org.dromara.sms4j.jg.config; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.sms4j.comm.constant.Constant; +import org.dromara.sms4j.comm.constant.SupplierConstant; +import org.dromara.sms4j.provider.config.BaseConfig; + +/** + *

类名: JgConfig + *

说明:极光 sms + * + * @author :SmartFire + * 2024/3/15 + **/ +@Data +@EqualsAndHashCode(callSuper = true) +public class JgConfig extends BaseConfig { + /** + * 签名 ID,该字段为空则使用应用默认签名 + */ + private String signId; + + /** + * 调用地址 + */ + private String requestUrl = Constant.HTTPS_PREFIX + "api.sms.jpush.cn/v1/"; + + /** + * 默认请求方法 messages + * 发送文本验证码短信 codes + * 发送语音验证码短信 voice_codes + * 验证验证码是否有效 valid + * 注意:此处直接写valid即为验证码验证请求 系统会自动补充完整请求地址为codes/{msg_id}/valid (注:msg_id 为调用发送验证码 API 的返回值) + * 发送单条模板短信 messages + * 发送批量模板短信 messages/batch + */ + private String action = "messages"; + + /** + * 模板变量名称 + */ + private String templateName; + + /** + * action设置为voice_codes有效 + * 语音验证码播报语言选择,0:中文播报,1:英文播报,2:中英混合播报 + */ + private String voice; + + /** + * action设置为voice_codes有效 + * 验证码有效期,默认为 60 秒 + */ + private Integer ttl = 60; + + /** + * action设置为messages/batch有效 + * 标签 + */ + private String tag; + + /** + * 获取供应商 + * + * @since 3.0.0 + */ + @Override + public String getSupplier() { + return SupplierConstant.JIGUANG; + } +} diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/jg/config/JgFactory.java b/sms4j-provider/src/main/java/org/dromara/sms4j/jg/config/JgFactory.java new file mode 100644 index 00000000..0ca36aa5 --- /dev/null +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/jg/config/JgFactory.java @@ -0,0 +1,48 @@ +package org.dromara.sms4j.jg.config; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.dromara.sms4j.comm.constant.SupplierConstant; +import org.dromara.sms4j.jg.service.JgSmsImpl; +import org.dromara.sms4j.provider.factory.AbstractProviderFactory; + +/** + *

类名: JgFactory + *

说明:极光 sms + * + * @author :SmartFire + * 2024/3/15 + **/ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class JgFactory extends AbstractProviderFactory { + + private static final JgFactory INSTANCE = new JgFactory(); + + /** + * 获取建造者实例 + * @return 建造者实例 + */ + public static JgFactory instance() { + return INSTANCE; + } + + /** + * 创建短信实现对象 + * @param config 短信配置对象 + * @return 短信实现对象 + */ + @Override + public JgSmsImpl createSms(JgConfig config) { + return new JgSmsImpl(config); + } + + /** + * 获取供应商 + * @return 供应商 + */ + @Override + public String getSupplier() { + return SupplierConstant.JIGUANG; + } + +} diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/jg/service/JgSmsImpl.java b/sms4j-provider/src/main/java/org/dromara/sms4j/jg/service/JgSmsImpl.java new file mode 100644 index 00000000..77fa6110 --- /dev/null +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/jg/service/JgSmsImpl.java @@ -0,0 +1,140 @@ +package org.dromara.sms4j.jg.service; + +import cn.hutool.json.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.dromara.sms4j.api.entity.SmsResponse; +import org.dromara.sms4j.api.utils.SmsRespUtils; +import org.dromara.sms4j.comm.constant.SupplierConstant; +import org.dromara.sms4j.comm.delayedTime.DelayedTime; +import org.dromara.sms4j.comm.exception.SmsBlendException; +import org.dromara.sms4j.comm.utils.SmsUtils; +import org.dromara.sms4j.jg.config.JgConfig; +import org.dromara.sms4j.jg.util.JgUtils; +import org.dromara.sms4j.provider.service.AbstractSmsBlend; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executor; + +/** + *

类名: JgSmsImpl + *

说明:极光 sms + * + * @author :SmartFire + * 2024/3/15 + **/ +@Slf4j +public class JgSmsImpl extends AbstractSmsBlend { + private int retry = 0; + + public JgSmsImpl(JgConfig config, Executor pool, DelayedTime delayedTime) { + super(config, pool, delayedTime); + } + + public JgSmsImpl(JgConfig config) { + super(config); + } + + @Override + public String getSupplier() { + return SupplierConstant.JIGUANG; + } + + @Override + public SmsResponse sendMessage(String phone, String message) { + LinkedHashMap map = new LinkedHashMap<>(1); + if (SmsUtils.isNotEmpty(getConfig().getTemplateName()) && + SmsUtils.isNotEmpty(message)){ + map.put(getConfig().getTemplateName(), message); + } + return sendMessage(phone, getConfig().getTemplateId(), map); + } + + @Override + public SmsResponse sendMessage(String phone, LinkedHashMap messages) { + if (SmsUtils.isEmpty(messages)){ + messages = new LinkedHashMap<>(); + } + return sendMessage(phone, getConfig().getTemplateId(), messages); + } + + @Override + public SmsResponse sendMessage(String phone, String templateId, LinkedHashMap messages) { + if (SmsUtils.isEmpty(messages)){ + messages = new LinkedHashMap<>(); + } + return getSmsResponse(phone, messages, templateId, null, null); + } + + @Override + public SmsResponse massTexting(List phones, String message) { + LinkedHashMap map = new LinkedHashMap<>(1); + if (SmsUtils.isNotEmpty(getConfig().getTemplateName()) && + SmsUtils.isNotEmpty(message)){ + map.put(getConfig().getTemplateName(), message); + } + return massTexting(phones, getConfig().getTemplateId(), map); + } + + @Override + public SmsResponse massTexting(List phones, String templateId, LinkedHashMap messages) { + if (SmsUtils.isEmpty(messages)){ + messages = new LinkedHashMap<>(); + } + return getSmsResponse(SmsUtils.addCodePrefixIfNot(phones), messages, templateId, null, null); + } + + /** + * 自定义方法 + * 发送语音验证码短信 请确保action配置为voice_codes + * @param phone 手机号 + * @param code 语音验证码 可不填 + */ + public SmsResponse sendVoiceCode(String phone, String code){ + return getSmsResponse(phone, null, null, code, null); + } + + /** + * 自定义方法 + * 验证验证码是否有效 请确保action配置为voice_codes + * @param msgId 为调用发送验证码 API 的返回值 + * @param code 验证码 + */ + public SmsResponse verifyCode(String code, String msgId){ + return getSmsResponse(null, null, null, code, msgId); + } + + private SmsResponse getSmsResponse(String phone, LinkedHashMap messages, + String templateId, String code, String msgId) { + SmsResponse smsResponse; + JgConfig config = getConfig(); + String url = JgUtils.buildUrl(config.getRequestUrl(), config.getAction(), msgId); + Map headers = JgUtils.buildHeaders(config.getAccessKeyId(), config.getAccessKeySecret()); + Map body= JgUtils.buildBody(phone, messages, templateId, config, code); + String jsonKey = JgUtils.buildJsonKey(config.getAction()); + try { + smsResponse = getResponse(http.postJson(url, headers, body), jsonKey); + } catch (SmsBlendException e) { + smsResponse = errorResp(e.message); + } + + if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) { + retry = 0; + return smsResponse; + } + return requestRetry(phone, messages, templateId, code, msgId); + } + + private SmsResponse requestRetry(String phone, LinkedHashMap messages, + String templateId, String code, String msgId) { + http.safeSleep(getConfig().getRetryInterval()); + retry ++; + log.warn("短信第 {} 次重新发送", retry); + return getSmsResponse(phone, messages, templateId, code, msgId); + } + + private SmsResponse getResponse(JSONObject resJson, String jsonKey) { + return SmsRespUtils.resp(resJson, resJson.getObj(jsonKey) != null, getConfigId()); + } +} diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/jg/util/JgUtils.java b/sms4j-provider/src/main/java/org/dromara/sms4j/jg/util/JgUtils.java new file mode 100644 index 00000000..a7f6cf98 --- /dev/null +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/jg/util/JgUtils.java @@ -0,0 +1,263 @@ +package org.dromara.sms4j.jg.util; + +import cn.hutool.core.codec.Base64; +import cn.hutool.core.util.StrUtil; +import lombok.extern.slf4j.Slf4j; +import org.dromara.sms4j.comm.constant.Constant; +import org.dromara.sms4j.comm.exception.SmsBlendException; +import org.dromara.sms4j.comm.utils.SmsUtils; +import org.dromara.sms4j.jg.config.JgConfig; + +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.stream.Collectors; + +/** + *

类名: JgHelper + *

说明:极光 sms + * + * @author :SmartFire + * 2024/3/15 + **/ +@Slf4j +public class JgUtils { + + /** + * 构造请求地址 + * @param baseUrl 配置的baseUrl + * @param action 请求方法 + * @param msgId 验证验证码是否有效时使用 msgId 为调用发送验证码 API 的返回值 + * @return url + */ + public static String buildUrl(String baseUrl, String action, String msgId) { + if ("valid".equals(action)){ + check(msgId); + return baseUrl + "codes/" + msgId + "/" + action; + }else { + return baseUrl + action; + } + } + + /** + * 构造请求头 + * @param accessKeyId appKey + * @param accessKeySecret appKey + * @return 请求头 + */ + public static Map buildHeaders(String accessKeyId, String accessKeySecret){ + check(accessKeyId); + check(accessKeySecret); + Map headers = new LinkedHashMap<>(3); + headers.put(Constant.ACCEPT, Constant.APPLICATION_JSON); + headers.put(Constant.CONTENT_TYPE, Constant.APPLICATION_JSON_UTF8); + headers.put(Constant.AUTHORIZATION, "Basic " + Base64.encode(accessKeyId + ":" + accessKeySecret, StandardCharsets.UTF_8)); + return headers; + } + + /** + * 构造请求body + * @param phone 手机号 + * @param messages 消息体 + * @param templateId 模板 ID + * @param config 配置 + * @param code 验证码 + * @return 请求body + */ + public static Map buildBody(String phone, LinkedHashMap messages, + String templateId, JgConfig config, String code) { + checkAction(config.getAction()); + switch (config.getAction()){ + case "codes": + return buildBody(phone, config.getSignId(), templateId); + case "voice_codes": + return buildBody(phone, code, config.getVoice(), config.getTtl()); + case "valid": + return buildBody(code); + case "messages/batch": + return buildBody(phone, config.getSignId(), templateId, config.getTag(), messages); + default: + return buildBody(phone, config.getSignId(), templateId, messages); + } + } + + /** + * 构造返回json验证Key值 + * @param action 请求方法 + * @return 返回json验证Key值 + */ + public static String buildJsonKey(String action){ + checkAction(action); + switch (action){ + case "valid": + return "is_valid"; + case "messages/batch": + return "success_count"; + default: + return "msg_id"; + } + } + + /** + * 构造请求body 发送文本验证码短信 + * @param phone 手机号 + * @param signId 签名 ID,该字段为空则使用应用默认签名 + * @param templateId 模板 ID + * @return 请求body + */ + private static Map buildBody(String phone, String signId, String templateId) { + checkSingle(phone); + Map map = new LinkedHashMap<>(2); + map.put("mobile", phone); + check(templateId); + map.put("temp_id", templateId); + if (SmsUtils.isNotEmpty(signId)){ + map.put("sign_id", signId); + } + return map; + } + + /** + * 构造请求body 发送语音验证码短信 + * @param phone 手机号 + * @param code 语音验证码的值,验证码仅支持 4-8 个数字 可为空 + * @param voice 语音验证码播报语言选择,0:中文播报,1:英文播报,2:中英混合播报 + * @param ttl 验证码有效期,默认为 60 秒 + * @return 请求body + */ + private static Map buildBody(String phone, String code, String voice, Integer ttl) { + checkSingle(phone); + Map map = new LinkedHashMap<>(1); + map.put("mobile", phone); + if (SmsUtils.isNotEmpty(code)) { + map.put("code", code); + } + if (SmsUtils.isNotEmpty(voice)){ + checkVoice(voice); + map.put("voice_lang", voice); + } + if (ttl == null || ttl <= 0){ + map.put("ttl", 60); + }else { + map.put("ttl", ttl); + } + return map; + } + + /** + * 构造请求body 验证验证码是否有效 + * @param code 验证码 + * @return 请求body + */ + private static Map buildBody(String code) { + check(code); + Map map = new LinkedHashMap<>(1); + map.put("code", code); + return map; + } + + /** + * 构造请求body 发送单条模板短信 + * @param phone 手机号码 + * @param signId 签名 ID,该字段为空则使用应用默认签名 + * @param templateId 模板 ID + * @param messages 模板参数,需要替换的参数名和 value 的键值对 可为空 + * @return 请求body + */ + private static Map buildBody(String phone, String signId, String templateId, LinkedHashMap messages) { + checkSingle(phone); + Map map = new LinkedHashMap<>(1); + map.put("mobile", phone); + if (SmsUtils.isNotEmpty(signId)){ + map.put("sign_id", signId); + } + check(templateId); + map.put("temp_id", templateId); + checkMessages(messages); + map.put("temp_para", messages); + return map; + } + + /** + * 构造请求body 发送批量模板短信 + * @param phone 手机号码列表 + * @param signId 签名 ID,该字段为空则使用应用默认签名 + * @param templateId 模板 ID + * @param tag 标签 可为空 + * @param messages 模板参数,需要替换的参数名和 value 的键值对 + * @return 请求body + */ + private static Map buildBody(String phone, String signId, String templateId, + String tag, LinkedHashMap messages) { + Set phones = build(phone); + Map map = new LinkedHashMap<>(1); + if (SmsUtils.isNotEmpty(signId)){ + map.put("sign_id", signId); + } + if (SmsUtils.isNotEmpty(tag)){ + map.put("tag", tag); + } + if (SmsUtils.isEmpty(templateId)){ + log.error("templateId is required"); + throw new SmsBlendException("templateId is required"); + } + map.put("temp_id", templateId); + if (SmsUtils.isEmpty(messages)){ + log.error("temp_para is required"); + throw new SmsBlendException("temp_para is required"); + } + List> recipients = new ArrayList<>(phones.size()); + phones.forEach(mobile -> { + Map params = new LinkedHashMap<>(1); + params.put("mobile", StrUtil.addPrefixIfNot(mobile, "+86")); + params.put("temp_para", messages); + recipients.add(params); + }); + map.put("recipients", recipients); + return map; + } + + private static Set build(String phone){ + check(phone); + return Arrays.stream(phone.split(",")) + .filter(SmsUtils::isNotEmpty) + .map(String::trim) + .collect(Collectors.toSet()); + } + + private static void checkSingle(String phone){ + Set phones = build(phone); + if (phones.size() > 1) { + log.error("Only a single mobile number is supported"); + throw new SmsBlendException("Only a single mobile number is supported"); + } + } + + private static void checkMessages(LinkedHashMap messages){ + if (SmsUtils.isEmpty(messages)){ + log.error("temp_para is required"); + throw new SmsBlendException("temp_para is required"); + } + } + + private static void checkVoice(String voice){ + if (!StrUtil.equalsAny(voice, "0", "1", "2")){ + log.error("voice_lang is error, the value of an is only [1,2,3]"); + throw new SmsBlendException("voice_lang is error, the value of an is only [1,2,3]"); + } + } + + private static void checkAction(String action){ + if (SmsUtils.isEmpty(action) || !StrUtil.equalsAny(action, "codes", "voice_codes", "valid", "messages", "messages/batch")){ + log.error("Unknown action method"); + throw new SmsBlendException("Unknown action method"); + } + } + + private static void check(String str){ + if (SmsUtils.isEmpty(str)){ + String error = str + " is required"; + log.error(error); + throw new SmsBlendException(error); + } + } +} diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/lianlu/config/LianLuConfig.java b/sms4j-provider/src/main/java/org/dromara/sms4j/lianlu/config/LianLuConfig.java index d8c2ab7b..35c52538 100644 --- a/sms4j-provider/src/main/java/org/dromara/sms4j/lianlu/config/LianLuConfig.java +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/lianlu/config/LianLuConfig.java @@ -3,6 +3,7 @@ package org.dromara.sms4j.lianlu.config; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors; +import org.dromara.sms4j.comm.constant.Constant; import org.dromara.sms4j.comm.constant.SupplierConstant; import org.dromara.sms4j.lianlu.req.LianLuRequest; import org.dromara.sms4j.lianlu.utils.LianLuUtils; @@ -10,7 +11,7 @@ import org.dromara.sms4j.provider.config.BaseConfig; /** * 联麓短信: - * 官方文档 + * 官方文档 * * @author lym */ @@ -35,7 +36,7 @@ public class LianLuConfig extends BaseConfig { */ private String signType = LianLuUtils.SIGN_TYPE_MD5; - private String requestUrl = "https://apis.shlianlu.com/sms/trade"; + private String requestUrl = Constant.HTTPS_PREFIX + "apis.shlianlu.com/sms/trade"; @Override public String getSupplier() { diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/lianlu/service/LianLuSmsImpl.java b/sms4j-provider/src/main/java/org/dromara/sms4j/lianlu/service/LianLuSmsImpl.java index 9c0b7c16..0de92155 100644 --- a/sms4j-provider/src/main/java/org/dromara/sms4j/lianlu/service/LianLuSmsImpl.java +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/lianlu/service/LianLuSmsImpl.java @@ -3,6 +3,8 @@ package org.dromara.sms4j.lianlu.service; import cn.hutool.json.JSONObject; import lombok.extern.slf4j.Slf4j; import org.dromara.sms4j.api.entity.SmsResponse; +import org.dromara.sms4j.api.utils.SmsRespUtils; +import org.dromara.sms4j.comm.constant.Constant; import org.dromara.sms4j.comm.constant.SupplierConstant; import org.dromara.sms4j.comm.delayedTime.DelayedTime; import org.dromara.sms4j.comm.exception.SmsBlendException; @@ -177,8 +179,8 @@ public class LianLuSmsImpl extends AbstractSmsBlend { try { Map headers = new HashMap<>(2); - headers.put("Content-Type", "application/json;charset=utf-8"); - headers.put("Accept", "application/json"); + headers.put(Constant.CONTENT_TYPE, Constant.APPLICATION_JSON_UTF8); + headers.put(Constant.ACCEPT, Constant.APPLICATION_JSON); SmsResponse smsResponse = this.getResponse(this.http.postJson(reqUrl, headers, requestBody)); if (!smsResponse.isSuccess() && this.retry != this.getConfig().getMaxRetries()) { return this.requestRetry(req); @@ -199,10 +201,6 @@ public class LianLuSmsImpl extends AbstractSmsBlend { } private SmsResponse getResponse(JSONObject resJson) { - SmsResponse smsResponse = new SmsResponse(); - smsResponse.setSuccess("00".equals(resJson.getStr("status"))); - smsResponse.setData(resJson); - smsResponse.setConfigId(this.getConfigId()); - return smsResponse; + return SmsRespUtils.resp(resJson, "00".equals(resJson.getStr("status")), getConfigId()); } } diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/luosimao/config/LuoSiMaoConfig.java b/sms4j-provider/src/main/java/org/dromara/sms4j/luosimao/config/LuoSiMaoConfig.java new file mode 100644 index 00000000..c4bbd27e --- /dev/null +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/luosimao/config/LuoSiMaoConfig.java @@ -0,0 +1,42 @@ +package org.dromara.sms4j.luosimao.config; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.sms4j.comm.constant.Constant; +import org.dromara.sms4j.comm.constant.SupplierConstant; +import org.dromara.sms4j.provider.config.BaseConfig; + +/** + *

类名: LuoSiMaoConfig + *

说明: 螺丝帽短信差异配置 + * + * @author :bleachtred + * 2024/6/21 23:59 + **/ +@Data +@EqualsAndHashCode(callSuper = true) +public class LuoSiMaoConfig extends BaseConfig { + + /** + * 请求地址 + */ + private String host = Constant.HTTPS_PREFIX + "sms-api.luosimao.com/v1/"; + + /** + * 接口名称 + * 发送短信接口详细 send.json + * 批量发送接口详细 send_batch.json + * 查询账户余额 status.json + */ + private String action = "send.json"; + + /** + * 获取供应商 + * + * @since 3.0.0 + */ + @Override + public String getSupplier() { + return SupplierConstant.LUO_SI_MAO; + } +} diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/luosimao/config/LuoSiMaoFactory.java b/sms4j-provider/src/main/java/org/dromara/sms4j/luosimao/config/LuoSiMaoFactory.java new file mode 100644 index 00000000..542ab79f --- /dev/null +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/luosimao/config/LuoSiMaoFactory.java @@ -0,0 +1,47 @@ +package org.dromara.sms4j.luosimao.config; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.dromara.sms4j.comm.constant.SupplierConstant; +import org.dromara.sms4j.luosimao.service.LuoSiMaoSmsImpl; +import org.dromara.sms4j.provider.factory.AbstractProviderFactory; + +/** + *

类名: LuoSiMaoFactory + * + * @author :bleachtred + * 2024/6/21 23:59 + **/ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class LuoSiMaoFactory extends AbstractProviderFactory { + + private static final LuoSiMaoFactory INSTANCE = new LuoSiMaoFactory(); + + /** + * 获取建造者实例 + * @return 建造者实例 + */ + public static LuoSiMaoFactory instance() { + return INSTANCE; + } + + /** + *

建造一个短信实现对像 + * + * @author :bleachtred + */ + @Override + public LuoSiMaoSmsImpl createSms(LuoSiMaoConfig config) { + return new LuoSiMaoSmsImpl(config); + } + + /** + * 获取供应商 + * @return 供应商 + */ + @Override + public String getSupplier() { + return SupplierConstant.LUO_SI_MAO; + } + +} diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/luosimao/service/LuoSiMaoSmsImpl.java b/sms4j-provider/src/main/java/org/dromara/sms4j/luosimao/service/LuoSiMaoSmsImpl.java new file mode 100644 index 00000000..6447e1d2 --- /dev/null +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/luosimao/service/LuoSiMaoSmsImpl.java @@ -0,0 +1,141 @@ +package org.dromara.sms4j.luosimao.service; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.dromara.sms4j.api.entity.SmsResponse; +import org.dromara.sms4j.api.utils.SmsRespUtils; +import org.dromara.sms4j.comm.constant.SupplierConstant; +import org.dromara.sms4j.comm.delayedTime.DelayedTime; +import org.dromara.sms4j.comm.exception.SmsBlendException; +import org.dromara.sms4j.luosimao.config.LuoSiMaoConfig; +import org.dromara.sms4j.luosimao.utils.LuoSiMaoUtils; +import org.dromara.sms4j.provider.service.AbstractSmsBlend; + +import java.util.*; +import java.util.concurrent.Executor; + +/** + *

类名: LuoSiMaoSmsImpl + *

说明: 螺丝帽短信差异配置 + * + * @author :bleachtred + * 2024/6/21 23:59 + **/ +@Slf4j +public class LuoSiMaoSmsImpl extends AbstractSmsBlend { + + private int retry = 0; + + public LuoSiMaoSmsImpl(LuoSiMaoConfig config, Executor pool, DelayedTime delayedTime) { + super(config, pool, delayedTime); + } + + public LuoSiMaoSmsImpl(LuoSiMaoConfig config) { + super(config); + } + + @Override + public String getSupplier() { + return SupplierConstant.LUO_SI_MAO; + } + + @Override + public SmsResponse sendMessage(String phone, String message) { + return getSmsResponse(Collections.singletonList(phone), message, null, false, false); + } + + @Override + public SmsResponse sendMessage(String phone, LinkedHashMap messages) { + throw new SmsBlendException("不支持此方法"); + } + + @Override + public SmsResponse sendMessage(String phone, String templateId, LinkedHashMap messages) { + throw new SmsBlendException("不支持此方法"); + } + + @Override + public SmsResponse massTexting(List phones, String message) { + return getSmsResponse(phones, message, null, false, false); + } + + @Override + public SmsResponse massTexting(List phones, String templateId, LinkedHashMap messages) { + throw new SmsBlendException("不支持此方法"); + } + + /** + * 定时批量发送 + * @param phones 手机号 + * @param message 信息 + * @param date 时间 + * @return SmsResponse + */ + public SmsResponse massTextingOnTime(List phones, String message, Date date) { + return getSmsResponse(phones, message, date, true, false); + } + + /** + * 查询账户余额 请将接口设置为 status.json + * + * @return SmsResponse + */ + public SmsResponse queryAccountBalance() { + return getSmsResponse(null, null, null, false, true); + } + + private SmsResponse getSmsResponse(List phones, String message, Date date, boolean batch, boolean status) { + LuoSiMaoConfig config = getConfig(); + SmsResponse smsResponse; + try { + String url = config.getHost() + config.getAction(); + LinkedHashMap body; + if (status){ + if ("status.json".equals(config.getAction())){ + log.error("please set the request interface method to status.json"); + throw new SmsBlendException("please set the request interface method to status.json"); + } + smsResponse = getResponse(http.getBasic(url, "api", "key-" + config.getAccessKeyId())); + } else { + if (CollUtil.isEmpty(phones)){ + log.error("mobile number is required"); + throw new SmsBlendException("mobile number is required"); + } + if (StrUtil.isBlank(message)){ + log.error("message number is required"); + throw new SmsBlendException("message number is required"); + } + + if (batch){ + body = LuoSiMaoUtils.buildBody(phones, message, date); + }else { + body = LuoSiMaoUtils.buildBody(phones.get(0), message); + } + smsResponse = getResponse(http.postBasicFrom(url, LuoSiMaoUtils.buildHeaders(), "api", "key-" + config.getAccessKeyId(), body)); + } + log.debug("短信发送结果:{}", smsResponse); + } catch (SmsBlendException e) { + log.error(e.message, e); + smsResponse = errorResp(e.message); + } + if (smsResponse.isSuccess() || retry == config.getMaxRetries()) { + retry = 0; + return smsResponse; + } + return requestRetry(phones, message, date, batch, status); + } + + private SmsResponse requestRetry(List phones, String message, Date date, boolean batch, boolean status) { + http.safeSleep(getConfig().getRetryInterval()); + retry ++; + log.warn("短信第 {} 次重新发送", retry); + return getSmsResponse(phones, message, date, batch, status); + } + + private SmsResponse getResponse(JSONObject resJson) { + return SmsRespUtils.resp(resJson, Objects.equals(0, resJson.getInt("error")), getConfigId()); + } + +} \ No newline at end of file diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/luosimao/utils/LuoSiMaoUtils.java b/sms4j-provider/src/main/java/org/dromara/sms4j/luosimao/utils/LuoSiMaoUtils.java new file mode 100644 index 00000000..3f3a6356 --- /dev/null +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/luosimao/utils/LuoSiMaoUtils.java @@ -0,0 +1,38 @@ +package org.dromara.sms4j.luosimao.utils; + +import cn.hutool.core.util.StrUtil; +import lombok.extern.slf4j.Slf4j; +import org.dromara.sms4j.comm.constant.Constant; +import org.dromara.sms4j.comm.utils.SmsDateUtils; +import org.dromara.sms4j.comm.utils.SmsUtils; + +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.List; + +@Slf4j +public class LuoSiMaoUtils { + + public static LinkedHashMap buildHeaders(){ + LinkedHashMap headers = new LinkedHashMap<>(1); + headers.put(Constant.CONTENT_TYPE, Constant.APPLICATION_FROM_URLENCODED); + return headers; + } + + public static LinkedHashMap buildBody(String phone, String message){ + LinkedHashMap body = new LinkedHashMap<>(2); + body.put("mobile", StrUtil.addPrefixIfNot(phone, "+86")); + body.put("message", message); + return body; + } + + public static LinkedHashMap buildBody(List phones, String message, Date date){ + LinkedHashMap body = new LinkedHashMap<>(2); + body.put("mobile", SmsUtils.addCodePrefixIfNot(phones)); + body.put("message", message); + if (date != null){ + body.put("time", SmsDateUtils.normDatetimeGmt8(date)); + } + return body; + } +} diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/mas/config/MasConfig.java b/sms4j-provider/src/main/java/org/dromara/sms4j/mas/config/MasConfig.java new file mode 100644 index 00000000..9401b4ba --- /dev/null +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/mas/config/MasConfig.java @@ -0,0 +1,48 @@ +package org.dromara.sms4j.mas.config; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.sms4j.comm.constant.SupplierConstant; +import org.dromara.sms4j.provider.config.BaseConfig; + +/** + *

类名: MasConfig + *

说明:中国移动 云MAS + * + * @author :bleachtred + * 2024/4/22 13:40 + **/ +@Data +@EqualsAndHashCode(callSuper = true) +public class MasConfig extends BaseConfig { + + /** + * 企业名称 + */ + private String ecName; + + /** + * 请求地址 + */ + private String requestUrl = "http://112.35.1.155:1992/sms/"; + + /** + * 接口名称 + */ + private String action = "tmpsubmit"; + + /** + * 扩展码 + */ + private String addSerial; + + /** + * 获取供应商 + * + * @since 3.0.0 + */ + @Override + public String getSupplier() { + return SupplierConstant.MAS; + } +} diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/mas/config/MasFactory.java b/sms4j-provider/src/main/java/org/dromara/sms4j/mas/config/MasFactory.java new file mode 100644 index 00000000..ff60e90d --- /dev/null +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/mas/config/MasFactory.java @@ -0,0 +1,49 @@ +package org.dromara.sms4j.mas.config; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.dromara.sms4j.comm.constant.SupplierConstant; +import org.dromara.sms4j.mas.service.MasSmsImpl; +import org.dromara.sms4j.provider.factory.AbstractProviderFactory; + +/** + *

类名: MasFactory + *

说明:中国移动 云MAS短信配置器 + * + * @author :bleachtred + * 2024/4/22 13:40 + **/ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class MasFactory extends AbstractProviderFactory { + + private static final MasFactory INSTANCE = new MasFactory(); + + /** + * 获取建造者实例 + * @return 建造者实例 + */ + public static MasFactory instance() { + return INSTANCE; + } + + /** + * createSms + *

建造一个短信实现对像 + * + * @author :bleachtred + */ + @Override + public MasSmsImpl createSms(MasConfig masConfig) { + return new MasSmsImpl(masConfig); + } + + /** + * 获取供应商 + * @return 供应商 + */ + @Override + public String getSupplier() { + return SupplierConstant.MAS; + } + +} diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/mas/service/MasSmsImpl.java b/sms4j-provider/src/main/java/org/dromara/sms4j/mas/service/MasSmsImpl.java new file mode 100644 index 00000000..b19dfa68 --- /dev/null +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/mas/service/MasSmsImpl.java @@ -0,0 +1,118 @@ +package org.dromara.sms4j.mas.service; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import lombok.extern.slf4j.Slf4j; +import org.dromara.sms4j.api.entity.SmsResponse; +import org.dromara.sms4j.api.utils.SmsRespUtils; +import org.dromara.sms4j.comm.constant.SupplierConstant; +import org.dromara.sms4j.comm.delayedTime.DelayedTime; +import org.dromara.sms4j.comm.exception.SmsBlendException; +import org.dromara.sms4j.comm.utils.SmsUtils; +import org.dromara.sms4j.mas.config.MasConfig; +import org.dromara.sms4j.mas.utils.MasUtils; +import org.dromara.sms4j.provider.service.AbstractSmsBlend; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.concurrent.Executor; + +/** + *

类名: MasSmsImpl + *

说明:中国移动 云MAS短信实现 + * + * @author :bleachtred + * 2024/4/22 13:40 + **/ +@Slf4j +public class MasSmsImpl extends AbstractSmsBlend { + + private int retry = 0; + + public MasSmsImpl(MasConfig config, Executor pool, DelayedTime delayedTime) { + super(config, pool, delayedTime); + } + + public MasSmsImpl(MasConfig config) { + super(config); + } + + @Override + public String getSupplier() { + return SupplierConstant.MAS; + } + + @Override + public SmsResponse sendMessage(String phone, String message) { + return getSmsResponse(phone, message, getConfig().getTemplateId()); + } + + @Override + public SmsResponse sendMessage(String phone, LinkedHashMap messages) { + if (CollUtil.isEmpty(messages)){ + messages = new LinkedHashMap<>(); + } + return getSmsResponse(phone, JSONUtil.toJsonStr(messages), getConfig().getTemplateId()); + } + + @Override + public SmsResponse sendMessage(String phone, String templateId, LinkedHashMap messages) { + if (CollUtil.isEmpty(messages)){ + messages = new LinkedHashMap<>(); + } + String messageStr = JSONUtil.toJsonStr(messages); + return getSmsResponse(phone, messageStr, templateId); + } + + @Override + public SmsResponse massTexting(List phones, String message) { + return getSmsResponse(SmsUtils.addCodePrefixIfNot(phones), message, getConfig().getTemplateId()); + } + + @Override + public SmsResponse massTexting(List phones, String templateId, LinkedHashMap messages) { + if (CollUtil.isEmpty(messages)){ + messages = new LinkedHashMap<>(); + } + String messageStr = JSONUtil.toJsonStr(messages); + return getSmsResponse(SmsUtils.addCodePrefixIfNot(phones), messageStr, templateId); + } + + private SmsResponse getSmsResponse(String phone, String message, String templateId) { + String requestUrl; + String base64Code; + try { + MasConfig config = getConfig(); + requestUrl = config.getRequestUrl() + config.getAction(); + base64Code = MasUtils.base64Code(getConfig(), phone, message, templateId); + } catch (Exception e) { + log.error("mas 10086 send message error", e); + throw new SmsBlendException(e.getMessage()); + } + log.debug("requestUrl {}", requestUrl); + SmsResponse smsResponse; + try { + smsResponse = getResponse(http.postJson(requestUrl, null, base64Code)); + } catch (SmsBlendException e) { + smsResponse = errorResp(e.message); + } + if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) { + retry = 0; + return smsResponse; + } + return requestRetry(phone, message, templateId); + } + + private SmsResponse requestRetry(String phone, String message, String templateId) { + http.safeSleep(getConfig().getRetryInterval()); + retry ++; + log.warn("短信第 {} 次重新发送", retry); + return getSmsResponse(phone, message, templateId); + } + + private SmsResponse getResponse(JSONObject resJson) { + return SmsRespUtils.resp(resJson, "success".equals(resJson.getStr("rspcod")) && resJson.getBool("success"), getConfigId()); + } + +} \ No newline at end of file diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/mas/utils/MasUtils.java b/sms4j-provider/src/main/java/org/dromara/sms4j/mas/utils/MasUtils.java new file mode 100644 index 00000000..7c64e78d --- /dev/null +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/mas/utils/MasUtils.java @@ -0,0 +1,73 @@ +package org.dromara.sms4j.mas.utils; + +import cn.hutool.core.codec.Base64; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.digest.DigestUtil; +import cn.hutool.json.JSONUtil; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.dromara.sms4j.mas.config.MasConfig; + +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class MasUtils { + + public static String base64Code(MasConfig config, String phone, String message, String templateId) { + Map map = new HashMap<>(1); + StringBuilder sb = new StringBuilder(); + if (StrUtil.isNotEmpty(config.getEcName())){ + map.put("ecName", config.getEcName().trim()); + sb.append(config.getEcName().trim()); + } + if (StrUtil.isNotEmpty(config.getSdkAppId())){ + map.put("apId", config.getSdkAppId().trim()); + sb.append(config.getSdkAppId().trim()); + } + if (StrUtil.isNotEmpty(config.getAccessKeySecret())){ + map.put("secretKey", config.getAccessKeySecret().trim()); + sb.append(config.getAccessKeySecret().trim()); + } + if ("norsubmit".equals(config.getAction()) || "submit".equals(config.getAction())){ + if (StrUtil.isNotEmpty(phone)){ + map.put("mobiles", phone.trim()); + sb.append(phone.trim()); + } + if (StrUtil.isNotEmpty(message)){ + map.put("content", message.trim()); + sb.append(message.trim()); + } + }else if ("tmpsubmit".equals(config.getAction())){ + if (StrUtil.isNotEmpty(templateId)){ + sb.append(templateId.trim()); + map.put("templateId", templateId); + } + if (StrUtil.isNotEmpty(phone)){ + map.put("mobiles", phone.trim()); + sb.append(phone.trim()); + } + if (StrUtil.isNotEmpty(message)){ + map.put("params", message.trim()); + sb.append(message.trim()); + }else { + String emptyParams = JSONUtil.toJsonStr(new String[]{""}); + map.put("params", emptyParams); + sb.append(emptyParams); + } + } + + if (StrUtil.isNotEmpty(config.getSignature())){ + map.put("sign", config.getSignature().trim()); + sb.append(config.getSignature().trim()); + } + if (StrUtil.isNotEmpty(config.getAddSerial())){ + map.put("addSerial", config.getAddSerial().trim()); + sb.append(config.getAddSerial().trim()); + } + + map.put("mac", DigestUtil.md5Hex(sb.toString(), StandardCharsets.UTF_8)); + return Base64.encode(JSONUtil.toJsonStr(map), StandardCharsets.UTF_8); + } +} diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/netease/config/NeteaseConfig.java b/sms4j-provider/src/main/java/org/dromara/sms4j/netease/config/NeteaseConfig.java index 09f35e4a..46219184 100644 --- a/sms4j-provider/src/main/java/org/dromara/sms4j/netease/config/NeteaseConfig.java +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/netease/config/NeteaseConfig.java @@ -2,6 +2,7 @@ package org.dromara.sms4j.netease.config; import lombok.Data; import lombok.EqualsAndHashCode; +import org.dromara.sms4j.comm.constant.Constant; import org.dromara.sms4j.comm.constant.SupplierConstant; import org.dromara.sms4j.provider.config.BaseConfig; @@ -21,18 +22,17 @@ public class NeteaseConfig extends BaseConfig { /** * 模板短信请求地址 */ - private String templateUrl = "https://api.netease.im/sms/sendtemplate.action"; - + private String templateUrl = Constant.HTTPS_PREFIX + "api.netease.im/sms/sendtemplate.action"; /** * 验证码短信请求地址 */ - private String codeUrl = "https://api.netease.im/sms/sendcode.action"; + private String codeUrl = Constant.HTTPS_PREFIX + "api.netease.im/sms/sendcode.action"; /** * 验证码验证请求地址 */ - private String verifyUrl = "https://api.netease.im/sms/verifycode.action"; + private String verifyUrl = Constant.HTTPS_PREFIX + "api.netease.im/sms/verifycode.action"; /** * 是否需要支持短信上行。true:需要,false:不需要 diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/netease/service/NeteaseSmsImpl.java b/sms4j-provider/src/main/java/org/dromara/sms4j/netease/service/NeteaseSmsImpl.java index 2909c676..f5ac172a 100644 --- a/sms4j-provider/src/main/java/org/dromara/sms4j/netease/service/NeteaseSmsImpl.java +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/netease/service/NeteaseSmsImpl.java @@ -9,6 +9,7 @@ import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import lombok.extern.slf4j.Slf4j; import org.dromara.sms4j.api.entity.SmsResponse; +import org.dromara.sms4j.api.utils.SmsRespUtils; import org.dromara.sms4j.comm.constant.Constant; import org.dromara.sms4j.comm.constant.SupplierConstant; import org.dromara.sms4j.comm.delayedTime.DelayedTime; @@ -97,7 +98,7 @@ public class NeteaseSmsImpl extends AbstractSmsBlend { throw new SmsBlendException("单次发送超过最大发送上限,建议每次群发短信人数低于100"); } Optional.ofNullable(getConfig().getTemplateId()).orElseThrow(() -> new SmsBlendException("模板ID不能为空")); - return getSmsResponse(getConfig().getTemplateUrl(), phones, getConfig().getTemplateId(), message); + return getSmsResponse(getConfig().getTemplateUrl(), phones,message, getConfig().getTemplateId()); } @Override @@ -133,7 +134,7 @@ public class NeteaseSmsImpl extends AbstractSmsBlend { body.put("needUp", getConfig().getNeedUp()); Map headers = MapUtil.newHashMap(5, true); - headers.put("Content-Type", Constant.FROM_URLENCODED); + headers.put(Constant.CONTENT_TYPE, Constant.APPLICATION_FROM_URLENCODED); headers.put("AppKey", getConfig().getAccessKeyId()); headers.put("Nonce", nonce); headers.put("CurTime", curTime); @@ -142,9 +143,7 @@ public class NeteaseSmsImpl extends AbstractSmsBlend { try { smsResponse = getResponse(http.postFrom(requestUrl, headers, body)); } catch (SmsBlendException e) { - smsResponse = new SmsResponse(); - smsResponse.setSuccess(false); - smsResponse.setData(e.getMessage()); + smsResponse = errorResp(e.message); } if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) { retry = 0; @@ -161,11 +160,7 @@ public class NeteaseSmsImpl extends AbstractSmsBlend { } private SmsResponse getResponse(JSONObject jsonObject) { - SmsResponse smsResponse = new SmsResponse(); - smsResponse.setSuccess(jsonObject.getInt("code") <= 200); - smsResponse.setData(jsonObject); - smsResponse.setConfigId(getConfigId()); - return smsResponse; + return SmsRespUtils.resp(jsonObject, jsonObject.getInt("code") <= 200, getConfigId()); } } diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/provider/config/BaseConfig.java b/sms4j-provider/src/main/java/org/dromara/sms4j/provider/config/BaseConfig.java index 4ca77b64..99b1a002 100644 --- a/sms4j-provider/src/main/java/org/dromara/sms4j/provider/config/BaseConfig.java +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/provider/config/BaseConfig.java @@ -22,6 +22,7 @@ public abstract class BaseConfig implements SupplierConfig { * Access Key */ private String accessKeyId; + /** * Sdk App Id */ diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/provider/config/SmsBanner.java b/sms4j-provider/src/main/java/org/dromara/sms4j/provider/config/SmsBanner.java index 679a4a48..b6137d39 100644 --- a/sms4j-provider/src/main/java/org/dromara/sms4j/provider/config/SmsBanner.java +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/provider/config/SmsBanner.java @@ -1,7 +1,7 @@ package org.dromara.sms4j.provider.config; public class SmsBanner { - private static final String banner = + private static final String BANNER = " ________ _____ ______ ________ ___ ___ ___ \n" + "|\\ ____\\|\\ _ \\ _ \\|\\ ____\\|\\ \\ |\\ \\ |\\ \\ \n" + "\\ \\ \\___|\\ \\ \\\\\\__\\ \\ \\ \\ \\___|\\ \\ \\\\_\\ \\ \\ \\ \\ \n" + @@ -12,6 +12,6 @@ public class SmsBanner { " \\|_________| \\|_________| \n"; /** 初始化配置文件时打印banner*/ public static void PrintBanner(String version) { - System.out.println(banner+version); + System.out.println(BANNER +version); } } diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/provider/config/SmsConfig.java b/sms4j-provider/src/main/java/org/dromara/sms4j/provider/config/SmsConfig.java index 02255872..49b6699a 100644 --- a/sms4j-provider/src/main/java/org/dromara/sms4j/provider/config/SmsConfig.java +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/provider/config/SmsConfig.java @@ -2,7 +2,7 @@ package org.dromara.sms4j.provider.config; import lombok.Data; -import org.dromara.sms4j.comm.enumerate.ConfigType; +import org.dromara.sms4j.comm.enums.ConfigType; import java.util.ArrayList; diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/provider/factory/ProviderFactoryHolder.java b/sms4j-provider/src/main/java/org/dromara/sms4j/provider/factory/ProviderFactoryHolder.java index ded0af6c..cbc8fffe 100644 --- a/sms4j-provider/src/main/java/org/dromara/sms4j/provider/factory/ProviderFactoryHolder.java +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/provider/factory/ProviderFactoryHolder.java @@ -17,13 +17,13 @@ import java.util.concurrent.ConcurrentHashMap; */ public class ProviderFactoryHolder { - private static final Map> factories = new ConcurrentHashMap<>(); + private static final Map> FACTORIES = new ConcurrentHashMap<>(); public static void registerFactory(BaseProviderFactory factory) { if(factory == null) { throw new SmsBlendException("注册供应商工厂失败,工厂实例不能为空"); } - factories.put(factory.getSupplier(), factory); + FACTORIES.put(factory.getSupplier(), factory); } public static void registerFactory(List> factoryList) { @@ -39,7 +39,7 @@ public class ProviderFactoryHolder { } public static BaseProviderFactory requireForSupplier(String supplier) { - return factories.getOrDefault(supplier, null); + return FACTORIES.getOrDefault(supplier, null); } } diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/provider/service/AbstractSmsBlend.java b/sms4j-provider/src/main/java/org/dromara/sms4j/provider/service/AbstractSmsBlend.java index 2c64451c..915a9b82 100644 --- a/sms4j-provider/src/main/java/org/dromara/sms4j/provider/service/AbstractSmsBlend.java +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/provider/service/AbstractSmsBlend.java @@ -6,6 +6,7 @@ import org.dromara.sms4j.api.SmsBlend; import org.dromara.sms4j.api.callback.CallBack; import org.dromara.sms4j.api.entity.SmsResponse; import org.dromara.sms4j.api.universal.SupplierConfig; +import org.dromara.sms4j.api.utils.SmsRespUtils; import org.dromara.sms4j.comm.delayedTime.DelayedTime; import org.dromara.sms4j.comm.utils.SmsHttpUtils; import org.dromara.sms4j.provider.factory.BeanFactory; @@ -62,7 +63,6 @@ public abstract class AbstractSmsBlend implements SmsB * message 消息内容 * @author :Wind */ - @Override public abstract SmsResponse sendMessage(String phone, String message); @@ -84,7 +84,6 @@ public abstract class AbstractSmsBlend implements SmsB * @param messages key为模板变量名称 value为模板变量值 * @author :Wind */ - @Override public abstract SmsResponse sendMessage(String phone, String templateId, LinkedHashMap messages); @@ -94,7 +93,6 @@ public abstract class AbstractSmsBlend implements SmsB * * @author :Wind */ - @Override public abstract SmsResponse massTexting(List phones, String message); @@ -104,7 +102,6 @@ public abstract class AbstractSmsBlend implements SmsB * * @author :Wind */ - @Override public abstract SmsResponse massTexting(List phones, String templateId, LinkedHashMap messages); @@ -145,7 +142,6 @@ public abstract class AbstractSmsBlend implements SmsB * @param callBack 回调 * @author :Wind */ - @Override public final void sendMessageAsync(String phone, String templateId, LinkedHashMap messages, CallBack callBack){ CompletableFuture smsResponseCompletableFuture = CompletableFuture.supplyAsync(() -> sendMessage(phone,templateId, messages), pool); @@ -240,4 +236,13 @@ public abstract class AbstractSmsBlend implements SmsB } }, delayedTime); } + + /** + * 返回异常 + * @param errorMsg 异常信息 + * @return SmsResponse + */ + public SmsResponse errorResp(String errorMsg){ + return SmsRespUtils.error(errorMsg, config.getConfigId()); + } } diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/qiniu/config/QiNiuConfig.java b/sms4j-provider/src/main/java/org/dromara/sms4j/qiniu/config/QiNiuConfig.java index 0c93838e..5bbb31be 100644 --- a/sms4j-provider/src/main/java/org/dromara/sms4j/qiniu/config/QiNiuConfig.java +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/qiniu/config/QiNiuConfig.java @@ -2,11 +2,12 @@ package org.dromara.sms4j.qiniu.config; import lombok.Data; import lombok.EqualsAndHashCode; +import org.dromara.sms4j.comm.constant.Constant; import org.dromara.sms4j.comm.constant.SupplierConstant; import org.dromara.sms4j.provider.config.BaseConfig; /** - * @author Administrator + * @author YYM * @Date: 2024/1/30 15:56 30 * @描述: QiNiuConfig **/ @@ -18,7 +19,7 @@ public class QiNiuConfig extends BaseConfig { /** * 请求地址 */ - private String baseUrl = "https://sms.qiniuapi.com"; + private String baseUrl = Constant.HTTPS_PREFIX + "sms.qiniuapi.com"; /** * 模板变量名称 diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/qiniu/config/QiNiuFactory.java b/sms4j-provider/src/main/java/org/dromara/sms4j/qiniu/config/QiNiuFactory.java index bfd673bb..a1fcd8d6 100644 --- a/sms4j-provider/src/main/java/org/dromara/sms4j/qiniu/config/QiNiuFactory.java +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/qiniu/config/QiNiuFactory.java @@ -7,7 +7,7 @@ import org.dromara.sms4j.provider.factory.AbstractProviderFactory; import org.dromara.sms4j.qiniu.service.QiNiuSmsImpl; /** - * @author Administrator + * @author YYM * @Date: 2024/1/30 16:06 29 * @描述: QiNiuFactory **/ diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/qiniu/service/QiNiuSmsImpl.java b/sms4j-provider/src/main/java/org/dromara/sms4j/qiniu/service/QiNiuSmsImpl.java index 3947e19b..296fded0 100644 --- a/sms4j-provider/src/main/java/org/dromara/sms4j/qiniu/service/QiNiuSmsImpl.java +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/qiniu/service/QiNiuSmsImpl.java @@ -1,11 +1,13 @@ package org.dromara.sms4j.qiniu.service; -import cn.hutool.core.util.ObjectUtil; import cn.hutool.json.JSONObject; import lombok.extern.slf4j.Slf4j; import org.dromara.sms4j.api.entity.SmsResponse; +import org.dromara.sms4j.api.utils.SmsRespUtils; import org.dromara.sms4j.comm.constant.SupplierConstant; import org.dromara.sms4j.comm.delayedTime.DelayedTime; +import org.dromara.sms4j.comm.exception.SmsBlendException; +import org.dromara.sms4j.comm.utils.SmsUtils; import org.dromara.sms4j.provider.service.AbstractSmsBlend; import org.dromara.sms4j.qiniu.config.QiNiuConfig; import org.dromara.sms4j.qiniu.util.QiNiuUtils; @@ -17,7 +19,7 @@ import java.util.Objects; import java.util.concurrent.Executor; /** - * @author Administrator + * @author YYM * @Date: 2024/1/30 16:06 59 * @描述: QiNiuSmsImpl **/ @@ -71,7 +73,6 @@ public class QiNiuSmsImpl extends AbstractSmsBlend { return senMassMsg(phones, templateId, messages); } - /** * @return SmsResponse * @author 初拥。 @@ -79,11 +80,14 @@ public class QiNiuSmsImpl extends AbstractSmsBlend { * @Description: 统一处理返回结果 */ public SmsResponse handleRes(String url, HashMap params) { - JSONObject jsonObject = http.postJson(url, QiNiuUtils.getHeaderAndSign(url, params, getConfig()), params); - SmsResponse smsResponse = new SmsResponse(); - smsResponse.setSuccess(ObjectUtil.isEmpty(jsonObject.getStr("error"))); - smsResponse.setData(jsonObject); - smsResponse.setConfigId(getConfigId()); + JSONObject jsonObject; + SmsResponse smsResponse; + try { + jsonObject = http.postJson(url, QiNiuUtils.getHeaderAndSign(url, params, getConfig()), params); + smsResponse = SmsRespUtils.resp(jsonObject, SmsUtils.isEmpty(jsonObject.getStr("error")), getConfigId()); + }catch (SmsBlendException e){ + smsResponse = errorResp(e.message); + } if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) { retry = 0; return smsResponse; diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/qiniu/util/QiNiuUtils.java b/sms4j-provider/src/main/java/org/dromara/sms4j/qiniu/util/QiNiuUtils.java index e48df1b0..575cf75d 100644 --- a/sms4j-provider/src/main/java/org/dromara/sms4j/qiniu/util/QiNiuUtils.java +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/qiniu/util/QiNiuUtils.java @@ -8,19 +8,18 @@ import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.dromara.sms4j.comm.constant.Constant; import org.dromara.sms4j.comm.exception.SmsBlendException; +import org.dromara.sms4j.comm.utils.SmsDateUtils; import org.dromara.sms4j.qiniu.config.QiNiuConfig; import java.net.URI; import java.nio.charset.StandardCharsets; -import java.text.SimpleDateFormat; import java.util.Base64; import java.util.Date; import java.util.HashMap; import java.util.Map; -import java.util.TimeZone; /** - * @author Administrator + * @author YYM * @Date: 2024/1/30 16:37 50 * @描述: QiNiuUtils **/ @@ -35,7 +34,7 @@ public class QiNiuUtils { StringBuilder dataToSign = new StringBuilder(); dataToSign.append(method.toUpperCase()).append(" ").append(reqUrl.getPath()); dataToSign.append("\nHost: ").append(reqUrl.getHost()); - dataToSign.append("\n").append("Content-Type").append(": ").append(Constant.ACCEPT); + dataToSign.append("\n").append(Constant.CONTENT_TYPE).append(": ").append(Constant.APPLICATION_JSON); dataToSign.append("\n").append("X-Qiniu-Date").append(": ").append(signDate); dataToSign.append("\n\n"); if (ObjectUtil.isNotEmpty(body)) { @@ -50,9 +49,7 @@ public class QiNiuUtils { public static Map getHeaderAndSign(String url, HashMap hashMap, QiNiuConfig qiNiuConfig) { String signature; - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"); - dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); - String signDate = dateFormat.format(new Date()); + String signDate = SmsDateUtils.pureDateUtcGmt(new Date()); try { signature = getSignature("POST", url, qiNiuConfig, JSONUtil.toJsonStr(hashMap), signDate); } catch (Exception e) { @@ -62,9 +59,9 @@ public class QiNiuUtils { //请求头 Map header = new HashMap<>(3); - header.put("Authorization", signature); + header.put(Constant.AUTHORIZATION, signature); header.put("X-Qiniu-Date", signDate); - header.put("Content-Type", "application/json"); + header.put(Constant.CONTENT_TYPE, "application/json"); return header; } } diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/submail/config/SubMailConfig.java b/sms4j-provider/src/main/java/org/dromara/sms4j/submail/config/SubMailConfig.java new file mode 100644 index 00000000..21dfef1d --- /dev/null +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/submail/config/SubMailConfig.java @@ -0,0 +1,56 @@ +package org.dromara.sms4j.submail.config; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.sms4j.comm.constant.Constant; +import org.dromara.sms4j.comm.constant.SupplierConstant; +import org.dromara.sms4j.provider.config.BaseConfig; + +/** + *

类名: SubMailConfig + *

说明: SUBMAIL短信差异配置 + * + * @author :bleachtred + * 2024/6/22 13:59 + **/ +@Data +@EqualsAndHashCode(callSuper = true) +public class SubMailConfig extends BaseConfig { + + /** + * 请求地址 + */ + private String host = Constant.HTTPS_PREFIX + "api-v4.mysubmail.com/sms/"; + + /** + * 接口名称 + * 短信发送 send.json + * 短信模板发送 xsend.json + * 短信一对多发送 multisend.json + * 短信模板一对多发送 multixsend.json + * 短信批量群发 batchsend.json + * 短信批量模板群发 batchxsend.json + */ + private String action = "send.json"; + + /** + * MD5 或 SHA-1 默认MD5 填写任意值,不为即为 密匙明文验证模式 + */ + private String signType = "MD5"; + + /** + * signature加密计算方式 + * (当sign_version传2时,会忽略某些字段) + */ + private String signVersion; + + /** + * 获取供应商 + * + * @since 3.0.0 + */ + @Override + public String getSupplier() { + return SupplierConstant.MY_SUBMAIL; + } +} diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/submail/config/SubMailFactory.java b/sms4j-provider/src/main/java/org/dromara/sms4j/submail/config/SubMailFactory.java new file mode 100644 index 00000000..708cc186 --- /dev/null +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/submail/config/SubMailFactory.java @@ -0,0 +1,47 @@ +package org.dromara.sms4j.submail.config; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.dromara.sms4j.comm.constant.SupplierConstant; +import org.dromara.sms4j.provider.factory.AbstractProviderFactory; +import org.dromara.sms4j.submail.service.SubMailSmsImpl; + +/** + *

类名: SubMailFactory + * + * @author :bleachtred + * 2024/6/22 13:59 + **/ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class SubMailFactory extends AbstractProviderFactory { + + private static final SubMailFactory INSTANCE = new SubMailFactory(); + + /** + * 获取建造者实例 + * @return 建造者实例 + */ + public static SubMailFactory instance() { + return INSTANCE; + } + + /** + *

建造一个短信实现对像 + * + * @author :bleachtred + */ + @Override + public SubMailSmsImpl createSms(SubMailConfig config) { + return new SubMailSmsImpl(config); + } + + /** + * 获取供应商 + * @return 供应商 + */ + @Override + public String getSupplier() { + return SupplierConstant.MY_SUBMAIL; + } + +} diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/submail/service/SubMailSmsImpl.java b/sms4j-provider/src/main/java/org/dromara/sms4j/submail/service/SubMailSmsImpl.java new file mode 100644 index 00000000..9ead7692 --- /dev/null +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/submail/service/SubMailSmsImpl.java @@ -0,0 +1,365 @@ +package org.dromara.sms4j.submail.service; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import lombok.extern.slf4j.Slf4j; +import org.dromara.sms4j.api.entity.SmsResponse; +import org.dromara.sms4j.api.utils.SmsRespUtils; +import org.dromara.sms4j.comm.constant.SupplierConstant; +import org.dromara.sms4j.comm.delayedTime.DelayedTime; +import org.dromara.sms4j.comm.exception.SmsBlendException; +import org.dromara.sms4j.comm.utils.SmsUtils; +import org.dromara.sms4j.provider.service.AbstractSmsBlend; +import org.dromara.sms4j.submail.config.SubMailConfig; +import org.dromara.sms4j.submail.utils.SubMailUtils; + +import java.util.*; +import java.util.concurrent.Executor; + +/** + *

类名: SubMailSmsImpl + * + * @author :bleachtred + * 2023/5/12 15:06 + **/ +@Slf4j +public class SubMailSmsImpl extends AbstractSmsBlend { + + private int retry = 0; + + public SubMailSmsImpl(SubMailConfig config, Executor pool, DelayedTime delayedTime) { + super(config, pool, delayedTime); + } + + public SubMailSmsImpl(SubMailConfig config) { + super(config); + } + + @Override + public String getSupplier() { + return SupplierConstant.MY_SUBMAIL; + } + + @Override + public SmsResponse sendMessage(String phone, String content) { + return getSmsResponse(Collections.singletonList(phone), content, getConfig().getTemplateId(), null); + } + + @Override + public SmsResponse sendMessage(String phone, LinkedHashMap vars) { + if (MapUtil.isEmpty(vars)){ + log.error("vars or content must be not null"); + throw new SmsBlendException("vars or content must be not null"); + } + String content = vars.get("content"); + vars.remove("content"); + return getSmsResponse(Collections.singletonList(phone), content, getConfig().getTemplateId(), vars); + } + + @Override + public SmsResponse sendMessage(String phone, String templateId, LinkedHashMap vars) { + if (MapUtil.isEmpty(vars)){ + log.error("vars or content must be not null"); + throw new SmsBlendException("vars or content must be not null"); + } + String content = vars.get("content"); + vars.remove("content"); + return getSmsResponse(Collections.singletonList(phone), content, templateId, vars); + } + + @SuppressWarnings("unchecked") + @Override + public SmsResponse massTexting(List phones, String content) { + if (StrUtil.isBlank(content)){ + log.error("vars or content must be not null"); + throw new SmsBlendException("vars or content must be not null"); + } + return massTexting(phones, getConfig().getTemplateId(), BeanUtil.copyProperties(content, LinkedHashMap.class)); + } + + @Override + public SmsResponse massTexting(List phones, String templateId, LinkedHashMap vars) { + if (MapUtil.isEmpty(vars)){ + log.error("vars or content must be not null"); + throw new SmsBlendException("vars or content must be not null"); + } + String content = vars.get("content"); + vars.remove("content"); + return getSmsResponse(phones, content, templateId, vars); + } + + private SmsResponse getSmsResponse(List phones, String content, String templateId, LinkedHashMap vars) { + if (CollUtil.isEmpty(phones)){ + log.error("phones must be not null"); + throw new SmsBlendException("phones must be not null"); + } + SubMailConfig config = getConfig(); + SmsResponse smsResponse; + String url = config.getHost() + config.getAction(); + LinkedHashMap body; + switch (config.getAction()){ + case "send.json": + if (StrUtil.isBlank(content)){ + log.error("content must be not null"); + throw new SmsBlendException("content must be not null"); + } + body = buildSend(phones.get(0), content); + break; + case "xsend.json": + body = buildXSend(phones.get(0), templateId, vars); + break; + case "multisend.json": + if (StrUtil.isBlank(content)){ + log.error("content must be not null"); + throw new SmsBlendException("content must be not null"); + } + if (MapUtil.isEmpty(vars)){ + log.error("vars be not null"); + throw new SmsBlendException("vars must be not null"); + } + body = buildMultiSend(phones, content, vars); + break; + case "multixsend.json": + if (MapUtil.isEmpty(vars)){ + log.error("vars be not null"); + throw new SmsBlendException("vars must be not null"); + } + body = buildMultiXSend(phones, templateId, vars); + break; + case "batchsend.json": + if (StrUtil.isBlank(content)){ + log.error("vars or content must be not null"); + throw new SmsBlendException("vars or content must be not null"); + } + body = buildBatchSend(phones, content); + break; + case "batchxsend.json": + if (MapUtil.isEmpty(vars)){ + log.error("vars be not null"); + throw new SmsBlendException("vars must be not null"); + } + body = buildBatchXSend(phones, templateId, vars); + break; + default: + log.error("不支持的短信发送类型"); + throw new SmsBlendException("不支持的短信发送类型"); + } + try { + smsResponse = getResponse(http.postJson(url, SubMailUtils.buildHeaders(), body)); + log.debug("短信发送结果: {}", JSONUtil.toJsonStr(smsResponse)); + } catch (SmsBlendException e) { + log.error(e.message, e); + smsResponse = errorResp(e.message); + } + if (smsResponse.isSuccess() || retry == config.getMaxRetries()) { + retry = 0; + return smsResponse; + } + return requestRetry(phones, content, templateId, vars); + } + + private SmsResponse requestRetry(List phones, String content, String templateId, LinkedHashMap vars) { + http.safeSleep(getConfig().getRetryInterval()); + retry ++; + log.warn("短信第 {} 次重新发送", retry); + return getSmsResponse(phones, content, templateId, vars); + } + + private SmsResponse getResponse(JSONObject resJson) { + return SmsRespUtils.resp(resJson, "success".equals(resJson.getStr("status")), getConfigId()); + } + + /** + * SMS/Send - 短信发送 + * @param phone 单个手机号 + * @param content 短信内容 + * @return 参数组装 + */ + private LinkedHashMap buildSend(String phone, String content){ + SubMailConfig config = getConfig(); + LinkedHashMap body = new LinkedHashMap<>(); + body.put("appid", config.getAccessKeyId()); + body.put("to", StrUtil.addPrefixIfNot(phone, "+86")); + if (StrUtil.isNotBlank(config.getSignature())){ + content = StrUtil.addPrefixIfNot(content, "【 " + config.getSignature() + "】") + StrUtil.sub(content, 0, 1000); + }else { + content = StrUtil.sub(content, 0, 1000); + } + body.put("content", content); + body.put("timestamp", timestamp()); + body.put("sign_type", config.getSignType()); + if (StrUtil.isNotBlank(config.getSignVersion())){ + body.put("sign_version", config.getSignVersion()); + } + body.put("sign_type", config.getSignType()); + String signature = SubMailUtils.signature(body, config.getSignType(), config.getAccessKeyId(), config.getAccessKeySecret(), "content"); + body.put("signature", signature); + return body; + } + + /** + * SMS/XSend - 短信模板发送 + * @param phone 单个手机号 + * @param templateId 短信模板ID + * @param vars 使用文本变量动态控制短信中的文本 + * @return 参数组装 + */ + private LinkedHashMap buildXSend(String phone, String templateId, LinkedHashMap vars){ + SubMailConfig config = getConfig(); + LinkedHashMap body = new LinkedHashMap<>(); + body.put("appid", config.getAccessKeyId()); + body.put("to", StrUtil.addPrefixIfNot(phone, "+86")); + body.put("project", templateId); + if (MapUtil.isNotEmpty(vars)){ + body.put("vars", JSONUtil.toJsonStr(vars)); + } + body.put("timestamp", timestamp()); + body.put("sign_type", config.getSignType()); + if (StrUtil.isNotBlank(config.getSignVersion())){ + body.put("sign_version", config.getSignVersion()); + } + body.put("sign_type", config.getSignType()); + String signature = SubMailUtils.signature(body, config.getSignType(), config.getAccessKeyId(), config.getAccessKeySecret(), "vars"); + body.put("signature", signature); + return body; + } + + /** + * SMS/MultiSend - 短信一对多发送 + * 建议:单线程提交数量控制在50个联系人, 可以开多个线程同时发送 + * @param phones N手机号 + * @param content 短信内容 + * @param vars 使用文本变量动态控制短信中的文本 + * @return 参数组装 + */ + private LinkedHashMap buildMultiSend(List phones, String content, LinkedHashMap vars){ + SubMailConfig config = getConfig(); + LinkedHashMap body = new LinkedHashMap<>(); + body.put("appid", config.getAccessKeyId()); + if (StrUtil.isNotBlank(config.getSignature())){ + content = StrUtil.addPrefixIfNot(content, "【 " + config.getSignature() + "】") + StrUtil.sub(content, 0, 1000); + }else { + content = StrUtil.sub(content, 0, 1000); + } + body.put("content", content); + phones = CollUtil.sub(phones, 0, 50); + List> multi = new ArrayList<>(phones.size()); + phones.forEach(phone -> { + LinkedHashMap map = new LinkedHashMap<>(); + map.put("to", StrUtil.addPrefixIfNot(phone, "+86")); + map.put("vars", vars); + multi.add(map); + }); + body.put("multi", JSONUtil.toJsonStr(multi)); + body.put("timestamp", timestamp()); + body.put("sign_type", config.getSignType()); + if (StrUtil.isNotBlank(config.getSignVersion())){ + body.put("sign_version", config.getSignVersion()); + } + body.put("sign_type", config.getSignType()); + String signature = SubMailUtils.signature(body, config.getSignType(), config.getAccessKeyId(), config.getAccessKeySecret(), "multi", "content"); + body.put("signature", signature); + return body; + } + + /** + * SMS/MultiXSend - 短信模板一对多发送 + * 建议: 单线程提交数量控制在50—200个联系人, 可以开多个线程同时发送 + * @param phones N手机号 + * @param templateId 短信模板ID + * @param vars 使用文本变量动态控制短信中的文本 + * @return 参数组装 + */ + private LinkedHashMap buildMultiXSend(List phones, String templateId, LinkedHashMap vars){ + SubMailConfig config = getConfig(); + LinkedHashMap body = new LinkedHashMap<>(); + body.put("appid", config.getAccessKeyId()); + body.put("project", templateId); + phones = CollUtil.sub(phones, 0, 200); + List> multi = new ArrayList<>(phones.size()); + phones.forEach(phone -> { + LinkedHashMap map = new LinkedHashMap<>(); + map.put("to", StrUtil.addPrefixIfNot(phone, "+86")); + map.put("vars", vars); + multi.add(map); + }); + body.put("multi", JSONUtil.toJsonStr(multi)); + body.put("timestamp", timestamp()); + body.put("sign_type", config.getSignType()); + if (StrUtil.isNotBlank(config.getSignVersion())){ + body.put("sign_version", config.getSignVersion()); + } + body.put("sign_type", config.getSignType()); + String signature = SubMailUtils.signature(body, config.getSignType(), config.getAccessKeyId(), config.getAccessKeySecret(), "multi", "content"); + body.put("signature", signature); + return body; + } + + /** + * SMS/BatchSend - 短信批量群发 + * 单次请求最大支持 10000 个 + * @param phones N手机号 + * @param content 短信内容 + * @return 参数组装 + */ + private LinkedHashMap buildBatchSend(List phones, String content){ + SubMailConfig config = getConfig(); + LinkedHashMap body = new LinkedHashMap<>(); + body.put("appid", config.getAccessKeyId()); + phones = CollUtil.sub(phones, 0, 10000); + body.put("to", SmsUtils.addCodePrefixIfNot(phones)); + if (StrUtil.isNotBlank(config.getSignature())){ + content = StrUtil.addPrefixIfNot(content, "【 " + config.getSignature() + "】") + StrUtil.sub(content, 0, 1000); + }else { + content = StrUtil.sub(content, 0, 1000); + } + body.put("content", content); + body.put("timestamp", timestamp()); + body.put("sign_type", config.getSignType()); + if (StrUtil.isNotBlank(config.getSignVersion())){ + body.put("sign_version", config.getSignVersion()); + } + body.put("sign_type", config.getSignType()); + String signature = SubMailUtils.signature(body, config.getSignType(), config.getAccessKeyId(), config.getAccessKeySecret(), "content"); + body.put("signature", signature); + return body; + } + + /** + * SMS/BatchXSend - 短信批量模板群发 + * 单次请求最大支持 10000 个 + * @param phones N手机号 + * @param templateId 短信模板ID + * @param vars 使用文本变量动态控制短信中的文本 + * @return 参数组装 + */ + private LinkedHashMap buildBatchXSend(List phones, String templateId, LinkedHashMap vars){ + SubMailConfig config = getConfig(); + LinkedHashMap body = new LinkedHashMap<>(); + body.put("appid", config.getAccessKeyId()); + phones = CollUtil.sub(phones, 0, 10000); + body.put("to", SmsUtils.addCodePrefixIfNot(phones)); + body.put("project", templateId); + if (MapUtil.isNotEmpty(vars)){ + body.put("vars", JSONUtil.toJsonStr(vars)); + } + body.put("timestamp", timestamp()); + body.put("sign_type", config.getSignType()); + if (StrUtil.isNotBlank(config.getSignVersion())){ + body.put("sign_version", config.getSignVersion()); + } + body.put("sign_type", config.getSignType()); + String signature = SubMailUtils.signature(body, config.getSignType(), config.getAccessKeyId(), config.getAccessKeySecret(), "vars"); + body.put("signature", signature); + return body; + } + + private String timestamp(){ + JSONObject resp = http.getUrl("https://api-v4.mysubmail.com/service/timestamp"); + return resp.getStr("resp"); + } +} \ No newline at end of file diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/submail/utils/SubMailUtils.java b/sms4j-provider/src/main/java/org/dromara/sms4j/submail/utils/SubMailUtils.java new file mode 100644 index 00000000..27b2ebb4 --- /dev/null +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/submail/utils/SubMailUtils.java @@ -0,0 +1,49 @@ +package org.dromara.sms4j.submail.utils; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.digest.DigestAlgorithm; +import cn.hutool.crypto.digest.DigestUtil; +import lombok.extern.slf4j.Slf4j; +import org.dromara.sms4j.comm.constant.Constant; +import org.dromara.sms4j.comm.utils.SmsUtils; + +import java.util.LinkedHashMap; + +/** + *

类名: SubMailUtils + * + * @author :bleachtred + * 2024/6/22 13:59 + **/ +@Slf4j +public class SubMailUtils { + + public static LinkedHashMap buildHeaders(){ + LinkedHashMap headers = new LinkedHashMap<>(1); + headers.put(Constant.ACCEPT, Constant.APPLICATION_JSON); + headers.put(Constant.CONTENT_TYPE, Constant.APPLICATION_JSON); + return headers; + } + + + public static String signature(LinkedHashMap body, String signType, String accessKeyId, String accessKeySecret, String... excludes){ + if (StrUtil.containsAnyIgnoreCase(signType, DigestAlgorithm.MD5.getValue(), DigestAlgorithm.SHA1.getValue())){ + StringBuilder sb = new StringBuilder(); + sb.append(accessKeyId).append(accessKeySecret); + if ("2".equals(Convert.toStr(body.get("sign_version")))){ + sb.append(SmsUtils.sortedParamsAsc(body, excludes)); + }else { + sb.append(SmsUtils.sortedParamsAsc(body)); + } + sb.append(accessKeyId).append(accessKeySecret); + if (signType.equalsIgnoreCase(DigestAlgorithm.MD5.getValue())){ + return DigestUtil.md5Hex(sb.toString()); + }else { + return DigestUtil.sha1Hex(sb.toString()); + } + }else { + return accessKeySecret; + } + } +} diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/tencent/service/TencentSmsImpl.java b/sms4j-provider/src/main/java/org/dromara/sms4j/tencent/service/TencentSmsImpl.java index 79d2d474..1ed7a034 100644 --- a/sms4j-provider/src/main/java/org/dromara/sms4j/tencent/service/TencentSmsImpl.java +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/tencent/service/TencentSmsImpl.java @@ -5,6 +5,7 @@ import cn.hutool.json.JSONArray; import cn.hutool.json.JSONObject; import lombok.extern.slf4j.Slf4j; import org.dromara.sms4j.api.entity.SmsResponse; +import org.dromara.sms4j.api.utils.SmsRespUtils; import org.dromara.sms4j.comm.constant.Constant; import org.dromara.sms4j.comm.constant.SupplierConstant; import org.dromara.sms4j.comm.delayedTime.DelayedTime; @@ -14,11 +15,9 @@ import org.dromara.sms4j.provider.service.AbstractSmsBlend; import org.dromara.sms4j.tencent.config.TencentConfig; import org.dromara.sms4j.tencent.utils.TencentUtils; -import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.concurrent.Executor; /** @@ -44,12 +43,7 @@ public class TencentSmsImpl extends AbstractSmsBlend { @Override public SmsResponse sendMessage(String phone, String message) { - String[] split = message.split("&"); - LinkedHashMap map = new LinkedHashMap<>(); - for (int i = 0; i < split.length; i++) { - map.put(String.valueOf(i), split[i]); - } - return sendMessage(phone, getConfig().getTemplateId(), map); + return sendMessage(phone, getConfig().getTemplateId(), SmsUtils.buildMessageByAmpersand(message)); } @Override @@ -59,38 +53,17 @@ public class TencentSmsImpl extends AbstractSmsBlend { @Override public SmsResponse sendMessage(String phone, String templateId, LinkedHashMap messages) { - if (Objects.isNull(messages)){ - messages = new LinkedHashMap<>(); - } - List list = new ArrayList<>(); - for (Map.Entry entry : messages.entrySet()) { - list.add(entry.getValue()); - } - String[] s = new String[list.size()]; - return getSmsResponse(new String[]{StrUtil.addPrefixIfNot(phone, "+86")}, list.toArray(s), templateId); + return getSmsResponse(new String[]{StrUtil.addPrefixIfNot(phone, "+86")}, SmsUtils.toArray(messages), templateId); } @Override public SmsResponse massTexting(List phones, String message) { - String[] split = message.split("&"); - LinkedHashMap map = new LinkedHashMap<>(); - for (int i = 0; i < split.length; i++) { - map.put(String.valueOf(i), split[i]); - } - return massTexting(phones, getConfig().getTemplateId(), map); + return massTexting(phones, getConfig().getTemplateId(), SmsUtils.buildMessageByAmpersand(message)); } @Override public SmsResponse massTexting(List phones, String templateId, LinkedHashMap messages) { - if (Objects.isNull(messages)){ - messages = new LinkedHashMap<>(); - } - List list = new ArrayList<>(); - for (Map.Entry entry : messages.entrySet()) { - list.add(entry.getValue()); - } - String[] s = new String[list.size()]; - return getSmsResponse(SmsUtils.listToArray(phones), list.toArray(s), templateId); + return getSmsResponse(SmsUtils.addCodePrefixIfNotToArray(phones), SmsUtils.toArray(messages), templateId); } private SmsResponse getSmsResponse(String[] phones, String[] messages, String templateId) { @@ -112,9 +85,7 @@ public class TencentSmsImpl extends AbstractSmsBlend { try { smsResponse = getResponse(http.postJson(url, headsMap, requestBody)); } catch (SmsBlendException e) { - smsResponse = new SmsResponse(); - smsResponse.setSuccess(false); - smsResponse.setData(e.getMessage()); + smsResponse = errorResp(e.message); } if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) { retry = 0; @@ -131,15 +102,12 @@ public class TencentSmsImpl extends AbstractSmsBlend { } private SmsResponse getResponse(JSONObject resJson) { - SmsResponse smsResponse = new SmsResponse(); JSONObject response = resJson.getJSONObject("Response"); // 根据 Error 判断是否配置错误 - String error = response.getStr("Error"); - smsResponse.setSuccess(StrUtil.isBlank(error)); + boolean success = StrUtil.isBlank(response.getStr("Error")); // 根据 SendStatusSet 判断是否不为Ok JSONArray sendStatusSet = response.getJSONArray("SendStatusSet"); if (sendStatusSet != null) { - boolean success = true; for (Object obj : sendStatusSet) { JSONObject jsonObject = (JSONObject) obj; String code = jsonObject.getStr("Code"); @@ -148,11 +116,8 @@ public class TencentSmsImpl extends AbstractSmsBlend { break; } } - smsResponse.setSuccess(success); } - smsResponse.setData(resJson); - smsResponse.setConfigId(getConfigId()); - return smsResponse; + return SmsRespUtils.resp(resJson, success, getConfigId()); } } \ No newline at end of file diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/tencent/utils/TencentUtils.java b/sms4j-provider/src/main/java/org/dromara/sms4j/tencent/utils/TencentUtils.java index 1fdbd79b..b66e48f6 100644 --- a/sms4j-provider/src/main/java/org/dromara/sms4j/tencent/utils/TencentUtils.java +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/tencent/utils/TencentUtils.java @@ -1,20 +1,19 @@ package org.dromara.sms4j.tencent.utils; +import cn.hutool.crypto.digest.DigestUtil; import cn.hutool.crypto.digest.HMac; import cn.hutool.crypto.digest.HmacAlgorithm; import cn.hutool.json.JSONUtil; import lombok.extern.slf4j.Slf4j; +import org.dromara.sms4j.comm.constant.Constant; +import org.dromara.sms4j.comm.utils.SmsDateUtils; import org.dromara.sms4j.tencent.config.TencentConfig; import javax.xml.bind.DatatypeConverter; import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.Map; -import java.util.TimeZone; - /** * @author Richard @@ -31,18 +30,14 @@ public class TencentUtils { */ private static final String HTTP_REQUEST_METHOD = "POST"; - private static final String CT_JSON = "application/json; charset=utf-8"; - private static byte[] hmac256(byte[] key, String msg) { HMac hMac = new HMac(HmacAlgorithm.HmacSHA256, key); - return hMac.digest(msg.getBytes(StandardCharsets.UTF_8)); + return hMac.digest(msg); } private static String sha256Hex(String s) throws Exception { - MessageDigest md = MessageDigest.getInstance("SHA-256"); - byte[] d = md.digest(s.getBytes(StandardCharsets.UTF_8)); - return DatatypeConverter.printHexBinary(d).toLowerCase(); + return DatatypeConverter.printHexBinary(DigestUtil.sha256(s)).toLowerCase(); } /** @@ -52,13 +47,11 @@ public class TencentUtils { * @param messages 短信内容 * @param phones 手机号 * @param timestamp 时间戳 - * @throws Exception + * @throws Exception Exception */ public static String generateSignature(TencentConfig tencentConfig, String templateId, String[] messages, String[] phones, String timestamp) throws Exception { - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); - sdf.setTimeZone(TimeZone.getTimeZone("UTC")); - String date = sdf.format(new Date(Long.parseLong(timestamp + "000"))); + String date = SmsDateUtils.normDateGmt(new Date(Long.parseLong(timestamp + "000"))); String canonicalUri = "/"; String canonicalQueryString = ""; String canonicalHeaders = "content-type:application/json; charset=utf-8\nhost:" + tencentConfig.getRequestUrl() + "\n"; @@ -95,8 +88,8 @@ public class TencentUtils { public static Map generateHeadsMap(String authorization, String timestamp, String action, String version, String territory, String requestUrl) { Map headers = new HashMap<>(); - headers.put("Authorization", authorization); - headers.put("Content-Type", CT_JSON); + headers.put(Constant.AUTHORIZATION, authorization); + headers.put(Constant.CONTENT_TYPE, Constant.APPLICATION_JSON_UTF8); headers.put("Host", requestUrl); headers.put("X-TC-Action", action); headers.put("X-TC-Timestamp", timestamp); @@ -113,7 +106,7 @@ public class TencentUtils { * @param signatureName 短信签名 * @param templateId 模板id * @param templateParamSet 模板参数 - * @return + * @return Map */ public static Map generateRequestBody(String[] phones, String sdkAppId, String signatureName, String templateId, String[] templateParamSet) { diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/unisms/core/Uni.java b/sms4j-provider/src/main/java/org/dromara/sms4j/unisms/core/Uni.java index 235c8125..c4962784 100644 --- a/sms4j-provider/src/main/java/org/dromara/sms4j/unisms/core/Uni.java +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/unisms/core/Uni.java @@ -1,5 +1,6 @@ package org.dromara.sms4j.unisms.core; +import org.dromara.sms4j.comm.constant.Constant; /** * 初始化统一环境的单例类. @@ -8,8 +9,8 @@ public class Uni { /** 模仿SDK版本*/ public static final String VERSION = "0.0.4"; - public static final String signingAlgorithm = "hmac-sha256"; - public static String endpoint = System.getenv().getOrDefault("UNI_ENDPOINT", "https://uni.apistd.com"); + public static final String SIGNING_ALGORITHM = "hmac-sha256"; + public static String endpoint = System.getenv().getOrDefault("UNI_ENDPOINT", Constant.HTTPS_PREFIX + "uni.apistd.com"); public static String accessKeyId = System.getenv("UNI_ACCESS_KEY_ID"); private static String accessKeySecret = System.getenv("UNI_ACCESS_KEY_SECRET"); @@ -86,7 +87,7 @@ public class Uni { builder.isSimple(false); } builder.endpoint(Uni.endpoint); - builder.signingAlgorithm(Uni.signingAlgorithm); + builder.signingAlgorithm(Uni.SIGNING_ALGORITHM); builder.setRetryInterval(retryInterval); builder.setMaxRetries(maxRetries); return builder.build(); diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/unisms/core/UniClient.java b/sms4j-provider/src/main/java/org/dromara/sms4j/unisms/core/UniClient.java index f60b46e5..8c82dd4a 100644 --- a/sms4j-provider/src/main/java/org/dromara/sms4j/unisms/core/UniClient.java +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/unisms/core/UniClient.java @@ -85,8 +85,8 @@ public class UniClient { public UniResponse request(final String action, final Map data) throws SmsBlendException { Map headers = new HashMap<>(); headers.put("User-Agent", USER_AGENT); - headers.put("Content-Type", Constant.APPLICATION_JSON_UTF8); - headers.put("Accept", Constant.ACCEPT); + headers.put(Constant.CONTENT_TYPE, Constant.APPLICATION_JSON_UTF8); + headers.put(Constant.ACCEPT, Constant.APPLICATION_JSON); String url; if (this.isSimple) { url = this.endpoint + "?action=" + action + "&accessKeyId=" + this.accessKeyId; diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/unisms/core/UniResponse.java b/sms4j-provider/src/main/java/org/dromara/sms4j/unisms/core/UniResponse.java index 33f1c415..4dd2e33e 100644 --- a/sms4j-provider/src/main/java/org/dromara/sms4j/unisms/core/UniResponse.java +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/unisms/core/UniResponse.java @@ -6,7 +6,6 @@ import org.dromara.sms4j.comm.exception.SmsBlendException; import java.util.Objects; public class UniResponse { - public static final String REQUEST_ID_HEADER_KEY = "x-uni-request-id"; public String requestId; public String code; public String message; diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/unisms/service/UniSmsImpl.java b/sms4j-provider/src/main/java/org/dromara/sms4j/unisms/service/UniSmsImpl.java index 090b1bb5..cc1c16a0 100644 --- a/sms4j-provider/src/main/java/org/dromara/sms4j/unisms/service/UniSmsImpl.java +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/unisms/service/UniSmsImpl.java @@ -3,6 +3,7 @@ package org.dromara.sms4j.unisms.service; import cn.hutool.core.map.MapUtil; import lombok.extern.slf4j.Slf4j; import org.dromara.sms4j.api.entity.SmsResponse; +import org.dromara.sms4j.api.utils.SmsRespUtils; import org.dromara.sms4j.comm.constant.SupplierConstant; import org.dromara.sms4j.comm.delayedTime.DelayedTime; import org.dromara.sms4j.comm.exception.SmsBlendException; @@ -96,16 +97,12 @@ public class UniSmsImpl extends AbstractSmsBlend { } private SmsResponse getSmsResponse(Map data) { - SmsResponse smsResponse = new SmsResponse(); try { UniResponse send = Uni.getClient(getConfig().getRetryInterval(), getConfig().getMaxRetries()).request("sms.message.send", data); - smsResponse.setSuccess("0".equals(send.code)); - smsResponse.setData(send); - smsResponse.setConfigId(getConfigId()); + return SmsRespUtils.resp(send, "0".equals(send.code), getConfigId()); } catch (Exception e) { - smsResponse.setSuccess(false); + return errorResp(e.getMessage()); } - return smsResponse; } } diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/yunpian/service/YunPianSmsImpl.java b/sms4j-provider/src/main/java/org/dromara/sms4j/yunpian/service/YunPianSmsImpl.java index b7b9e20a..a5c4b930 100644 --- a/sms4j-provider/src/main/java/org/dromara/sms4j/yunpian/service/YunPianSmsImpl.java +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/yunpian/service/YunPianSmsImpl.java @@ -3,6 +3,7 @@ package org.dromara.sms4j.yunpian.service; import cn.hutool.json.JSONObject; import lombok.extern.slf4j.Slf4j; import org.dromara.sms4j.api.entity.SmsResponse; +import org.dromara.sms4j.api.utils.SmsRespUtils; import org.dromara.sms4j.comm.constant.Constant; import org.dromara.sms4j.comm.constant.SupplierConstant; import org.dromara.sms4j.comm.delayedTime.DelayedTime; @@ -40,15 +41,10 @@ public class YunPianSmsImpl extends AbstractSmsBlend { } private SmsResponse getResponse(JSONObject execute) { - SmsResponse smsResponse = new SmsResponse(); if (execute == null) { - smsResponse.setSuccess(false); - return smsResponse; + return SmsRespUtils.error(getConfigId()); } - smsResponse.setSuccess(execute.getInt("code") == 0); - smsResponse.setData(execute); - smsResponse.setConfigId(getConfigId()); - return smsResponse; + return SmsRespUtils.resp(execute, execute.getInt("code") == 0, getConfigId()); } @Override @@ -60,9 +56,7 @@ public class YunPianSmsImpl extends AbstractSmsBlend { try { smsResponse = getResponse(http.postFrom(Constant.YUNPIAN_URL + "/sms/tpl_single_send.json", headers, body)); } catch (SmsBlendException e) { - smsResponse = new SmsResponse(); - smsResponse.setSuccess(false); - smsResponse.setData(e.getMessage()); + smsResponse = errorResp(e.message); } if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) { retry = 0; @@ -119,7 +113,7 @@ public class YunPianSmsImpl extends AbstractSmsBlend { if (phones.size() > 1000) { throw new SmsBlendException("单次发送超过最大发送上限,建议每次群发短信人数低于1000"); } - return sendMessage(SmsUtils.listToString(phones), message); + return sendMessage(SmsUtils.joinComma(phones), message); } @Override @@ -130,7 +124,7 @@ public class YunPianSmsImpl extends AbstractSmsBlend { if (phones.size() > 1000) { throw new SmsBlendException("单次发送超过最大发送上限,建议每次群发短信人数低于1000"); } - return sendMessage(SmsUtils.listToString(phones), templateId, messages); + return sendMessage(SmsUtils.joinComma(phones), templateId, messages); } private String formattingMap(Map messages) { @@ -170,8 +164,8 @@ public class YunPianSmsImpl extends AbstractSmsBlend { private Map getHeaders() { Map headers = new HashMap<>(); - headers.put("Accept", Constant.APPLICATION_JSON_UTF8); - headers.put("Content-Type", Constant.FROM_URLENCODED); + headers.put(Constant.ACCEPT, Constant.APPLICATION_JSON_UTF8); + headers.put(Constant.CONTENT_TYPE, Constant.APPLICATION_FROM_URLENCODED); return headers; } } diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/zhutong/config/ZhutongConfig.java b/sms4j-provider/src/main/java/org/dromara/sms4j/zhutong/config/ZhutongConfig.java index 90af3779..eed870f9 100644 --- a/sms4j-provider/src/main/java/org/dromara/sms4j/zhutong/config/ZhutongConfig.java +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/zhutong/config/ZhutongConfig.java @@ -2,6 +2,7 @@ package org.dromara.sms4j.zhutong.config; import lombok.Data; import lombok.EqualsAndHashCode; +import org.dromara.sms4j.comm.constant.Constant; import org.dromara.sms4j.comm.constant.SupplierConstant; import org.dromara.sms4j.provider.config.BaseConfig; @@ -20,15 +21,15 @@ import org.dromara.sms4j.provider.config.BaseConfig; public class ZhutongConfig extends BaseConfig { /** * 模板变量名称 - * 查看地址:https://mix2.zthysms.com/index.html#/TemplateManagement + * 查看地址:Constant.HTTPS_PREFIX + mix2.zthysms.com/index.html#/TemplateManagement * 允许为空,为空,使用无模板形式,发送短信 */ private String templateName; /** * 默认请求地址 - * 不同区域,可切换请求地址,也可以不修改,请参考官方文档:https://doc.zthysms.com/web/#/1/236 + * 不同区域,可切换请求地址,也可以不修改,请参考官方文档:Constant.HTTPS_PREFIX + doc.zthysms.com/web/#/1/236 */ - private String requestUrl = "https://api.mix2.zthysms.com/"; + private String requestUrl = Constant.HTTPS_PREFIX + "api.mix2.zthysms.com/"; /** * 获取供应商 diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/zhutong/service/ZhutongSmsImpl.java b/sms4j-provider/src/main/java/org/dromara/sms4j/zhutong/service/ZhutongSmsImpl.java index 1c39904e..2b15ee6a 100644 --- a/sms4j-provider/src/main/java/org/dromara/sms4j/zhutong/service/ZhutongSmsImpl.java +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/zhutong/service/ZhutongSmsImpl.java @@ -10,6 +10,7 @@ import cn.hutool.json.JSONArray; import cn.hutool.json.JSONObject; import lombok.extern.slf4j.Slf4j; import org.dromara.sms4j.api.entity.SmsResponse; +import org.dromara.sms4j.api.utils.SmsRespUtils; import org.dromara.sms4j.comm.constant.Constant; import org.dromara.sms4j.comm.constant.SupplierConstant; import org.dromara.sms4j.comm.delayedTime.DelayedTime; @@ -147,14 +148,12 @@ public class ZhutongSmsImpl extends AbstractSmsBlend { json.put("content", content); Map headers = MapUtil.newHashMap(1, true); - headers.put("Content-Type", Constant.APPLICATION_JSON_UTF8); + headers.put(Constant.CONTENT_TYPE, Constant.APPLICATION_JSON_UTF8); SmsResponse smsResponse; try { smsResponse = getResponse(http.postJson(url, headers, json)); } catch (SmsBlendException e) { - smsResponse = new SmsResponse(); - smsResponse.setSuccess(false); - smsResponse.setData(e.getMessage()); + smsResponse = errorResp(e.message); } if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) { retry = 0; @@ -235,14 +234,12 @@ public class ZhutongSmsImpl extends AbstractSmsBlend { requestJson.set("records", records); Map headers = MapUtil.newHashMap(1, true); - headers.put("Content-Type", Constant.APPLICATION_JSON_UTF8); + headers.put(Constant.CONTENT_TYPE, Constant.APPLICATION_JSON_UTF8); SmsResponse smsResponse; try { smsResponse = getResponse(http.postJson(url, headers, requestJson.toString())); } catch (SmsBlendException e) { - smsResponse = new SmsResponse(); - smsResponse.setSuccess(false); - smsResponse.setData(e.getMessage()); + smsResponse = errorResp(e.message); } if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) { retry = 0; @@ -263,11 +260,7 @@ public class ZhutongSmsImpl extends AbstractSmsBlend { } private SmsResponse getResponse(JSONObject jsonObject) { - SmsResponse smsResponse = new SmsResponse(); - smsResponse.setSuccess(jsonObject.getInt("code", -1) <= 200); - smsResponse.setData(jsonObject); - smsResponse.setConfigId(getConfigId()); - return smsResponse; + return SmsRespUtils.resp(jsonObject, jsonObject.getInt("code", -1) <= 200, getConfigId()); } private void validator(String requestUrl, String username, String password) { diff --git a/sms4j-solon-plugin-example/pom.xml b/sms4j-solon-plugin-example/pom.xml new file mode 100644 index 00000000..2f3ff314 --- /dev/null +++ b/sms4j-solon-plugin-example/pom.xml @@ -0,0 +1,52 @@ + + + 4.0.0 + + + org.dromara.sms4j + sms4j + ${revision} + ../pom.xml + + + sms4j-solon-plugin-example + + + true + true + + + + + org.noear + solon-web + ${solon.version} + + + + org.noear + solon-test-junit5 + ${solon.version} + test + + + org.dromara.sms4j + sms4j-oa-core + ${revision} + + + org.dromara.sms4j + sms4j-solon-plugin + ${revision} + + + + com.jdcloud.sdk + sms + ${jdcloud.version} + + + + \ No newline at end of file diff --git a/sms4j-solon-plugin-example/src/main/java/org/dromara/sms4j/example/Sms4jApp.java b/sms4j-solon-plugin-example/src/main/java/org/dromara/sms4j/example/Sms4jApp.java new file mode 100644 index 00000000..c1a9ca95 --- /dev/null +++ b/sms4j-solon-plugin-example/src/main/java/org/dromara/sms4j/example/Sms4jApp.java @@ -0,0 +1,15 @@ +package org.dromara.sms4j.example; + +import org.noear.solon.Solon; + +/** + * 主类 + * + * @author handy + */ +public class Sms4jApp { + + public static void main(String[] args) { + Solon.start(Sms4jApp.class, args); + } +} diff --git a/sms4j-solon-plugin-example/src/main/java/org/dromara/sms4j/example/zhangjun/ZhangJunApp.java b/sms4j-solon-plugin-example/src/main/java/org/dromara/sms4j/example/zhangjun/ZhangJunApp.java new file mode 100644 index 00000000..a50d0669 --- /dev/null +++ b/sms4j-solon-plugin-example/src/main/java/org/dromara/sms4j/example/zhangjun/ZhangJunApp.java @@ -0,0 +1,18 @@ +package org.dromara.sms4j.example.zhangjun; + +import org.dromara.sms4j.core.factory.SmsFactory; +import org.noear.solon.Solon; + +/** + * 自定义广州掌骏短信实现 + * + * @author 4n + */ +public class ZhangJunApp { + + public static void main(String[] args) { + Solon.start(ZhangJunApp.class, args); + SmsFactory.getBySupplier("zhangjun").sendMessage("17*****598", "154468"); + } + +} diff --git a/sms4j-solon-plugin-example/src/main/java/org/dromara/sms4j/example/zhangjun/ZhangJunConfig.java b/sms4j-solon-plugin-example/src/main/java/org/dromara/sms4j/example/zhangjun/ZhangJunConfig.java new file mode 100644 index 00000000..ecef072c --- /dev/null +++ b/sms4j-solon-plugin-example/src/main/java/org/dromara/sms4j/example/zhangjun/ZhangJunConfig.java @@ -0,0 +1,22 @@ +package org.dromara.sms4j.example.zhangjun; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.sms4j.provider.config.BaseConfig; + +/** + * @author 4n + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class ZhangJunConfig extends BaseConfig { + private String appId; + private String sid; + private String url; + + @Override + public String getSupplier() { + return "zhangjun"; + } + +} diff --git a/sms4j-solon-plugin-example/src/main/java/org/dromara/sms4j/example/zhangjun/ZhangJunFactory.java b/sms4j-solon-plugin-example/src/main/java/org/dromara/sms4j/example/zhangjun/ZhangJunFactory.java new file mode 100644 index 00000000..b982d70b --- /dev/null +++ b/sms4j-solon-plugin-example/src/main/java/org/dromara/sms4j/example/zhangjun/ZhangJunFactory.java @@ -0,0 +1,26 @@ +package org.dromara.sms4j.example.zhangjun; + +import lombok.NoArgsConstructor; +import org.dromara.sms4j.provider.factory.AbstractProviderFactory; + +/** + * + *

掌骏短信 + * + * @author :4n + * 2023/10/31 14:54 + **/ +@NoArgsConstructor +public class ZhangJunFactory extends AbstractProviderFactory { + + @Override + public ZhangJunSmsImpl createSms(ZhangJunConfig ZhangJunConfig) { + return new ZhangJunSmsImpl(ZhangJunConfig); + } + + @Override + public String getSupplier() { + return "zhangjun"; + } + +} diff --git a/sms4j-solon-plugin-example/src/main/java/org/dromara/sms4j/example/zhangjun/ZhangJunSmsImpl.java b/sms4j-solon-plugin-example/src/main/java/org/dromara/sms4j/example/zhangjun/ZhangJunSmsImpl.java new file mode 100644 index 00000000..fc99662e --- /dev/null +++ b/sms4j-solon-plugin-example/src/main/java/org/dromara/sms4j/example/zhangjun/ZhangJunSmsImpl.java @@ -0,0 +1,119 @@ +package org.dromara.sms4j.example.zhangjun; + +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import lombok.extern.slf4j.Slf4j; +import org.dromara.sms4j.api.entity.SmsResponse; +import org.dromara.sms4j.comm.delayedTime.DelayedTime; +import org.dromara.sms4j.comm.exception.SmsBlendException; +import org.dromara.sms4j.comm.utils.SmsUtils; +import org.dromara.sms4j.provider.service.AbstractSmsBlend; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executor; + +/** + * @author :4n + **/ +@Slf4j +public class ZhangJunSmsImpl extends AbstractSmsBlend { + + private int retry = 0; + + /** + * ZhangJunSmsImpl + *

构造器,用于构造短信实现模块 + * + * @author :Wind + */ + public ZhangJunSmsImpl(ZhangJunConfig config, Executor pool, DelayedTime delayedTime) { + super(config, pool, delayedTime); + } + + /** + * ZhangJunSmsImpl + *

构造器,用于构造短信实现模块 + */ + public ZhangJunSmsImpl(ZhangJunConfig config) { + super(config); + } + + @Override + public String getSupplier() { + return "zhangjun"; + } + private LinkedHashMap buildBody(String phone, String message){ + LinkedHashMap map = new LinkedHashMap<>(); + map.put("appId", getConfig().getAppId()); + map.put("sid", getConfig().getSid()); + map.put("templateId", getConfig().getTemplateId()); + map.put("phone", phone); + Map data = new HashMap<>(); + data.put("code", message); + map.put("data", JSONUtil.toJsonStr(data)); + return map; + } + @Override + public SmsResponse sendMessage(String phone, String message) { + return sendMessage(phone, getConfig().getTemplateId(), buildBody(phone,message)); + } + + @Override + public SmsResponse sendMessage(String phone, LinkedHashMap messages) { + return sendMessage(phone, getConfig().getTemplateId(), messages); + } + + @Override + public SmsResponse sendMessage(String phone, String templateId, LinkedHashMap messages) { + String messageStr = JSONUtil.toJsonStr(messages); + return getSmsResponse(phone, messageStr, templateId); + } + + @Override + public SmsResponse massTexting(List phones, String message) { + LinkedHashMap map = new LinkedHashMap<>(); +// map.put(getConfig().getTemplateName(), message); + return massTexting(phones, getConfig().getTemplateId(), map); + } + + @Override + public SmsResponse massTexting(List phones, String templateId, LinkedHashMap messages) { + String messageStr = JSONUtil.toJsonStr(messages); + return getSmsResponse(SmsUtils.addCodePrefixIfNot(phones), messageStr, templateId); + } + + private SmsResponse getSmsResponse(String phone, String message, String templateId) { + SmsResponse smsResponse; + try { + smsResponse = getResponse(http.postJson(getConfig().getUrl(), null, message)); + } catch (SmsBlendException e) { + smsResponse = new SmsResponse(); + smsResponse.setSuccess(false); + smsResponse.setData(e.getMessage()); + } + if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) { + retry = 0; + return smsResponse; + } + return requestRetry(phone, message, templateId); + } + + private SmsResponse requestRetry(String phone, String message, String templateId) { + http.safeSleep(getConfig().getRetryInterval()); + retry++; + log.warn("短信第 {} 次重新发送", retry); + return getSmsResponse(phone, message, templateId); + } + + private SmsResponse getResponse(JSONObject resJson) { + SmsResponse smsResponse = new SmsResponse(); + smsResponse.setSuccess("OK".equals(resJson.getStr("Code"))); + smsResponse.setData(resJson); + smsResponse.setConfigId(getConfigId()); + return smsResponse; + } + +} \ No newline at end of file diff --git a/sms4j-solon-plugin-example/src/main/resources/app.yml b/sms4j-solon-plugin-example/src/main/resources/app.yml new file mode 100644 index 00000000..1780baae --- /dev/null +++ b/sms4j-solon-plugin-example/src/main/resources/app.yml @@ -0,0 +1,176 @@ +sms: + # 标注从yml读取配置 + config-type: yaml + # 账户上限 + account-max: 1 + blends: + # 阿里短信例子 + ali: + #厂商标识,标定此配置是哪个厂商,详细请看厂商标识介绍部分 + supplier: alibaba + #您的accessKey + access-key-id: 您的accessKey + #您的accessKeySecret + access-key-secret: 您的accessKeySecret + #您的短信签名 + signature: 测试签名 + #模板ID 非必须配置,如果使用sendMessage的快速发送需此配置 + template-id: SMS_272470496 + # 模版名称 + templateName: code + # 腾讯短信例子 + tx: + #厂商标识 + supplier: tencent + #您的accessKey + access-key-id: 您的accessKey + #您的accessKeySecret + access-key-secret: 您的accessKeySecret + #您的短信签名 + signature: 测试签名 + #模板ID + template-id: 1603670 + #您的sdkAppId + sdk-app-id: 1400761645 + # 华为短信例子 + hw: + #厂商标识 + supplier: huawei + #您的accessKey + access-key-id: 您的accessKey + #您的accessKeySecret + access-key-secret: 您的accessKeySecret + #您的短信签名 + signature: 测试签名 + #模板ID + template-id: ac4888205c274b2a8263479b954c1ab5 + # APP接入地址 + url: https://smsapi.cn-north-4.myhuaweicloud.com:443 + # 模版名称 + templateName: code + # 通道号 + sender: 8823040504797 + # 合一短信例子 + uni: + #厂商标识 + supplier: unisms + #您的accessKey + access-key-id: 您的accessKey + #您的短信签名 + signature: 测试签名 + #模板ID + template-id: pub_verif_short + # 模版名称 + templateName: code + # 渠道上限 + maximum: 2 + lianlu: + supplier: lianlu + templateId: 模板id + appId: 100116 + appKey: d42d7 + mchId: 100 + signName: 【test】 + cloopen: + # 短信厂商 + supplier: cloopen + base-url: https://app.cloopen.com:8883/2013-12-26 + access-key-id: 你的Access Key + access-key-secret: 你的Access Key Secret + sdkAppId: 你的应用ID + #自定义广州掌骏短信,添加factory全路径。config,factory,SmsImpl复制其他默认实现即可,修改对应的supplier和发送核心逻辑即可 +# zhangjun: +# supplier: zhangjun +# factory: org.dromara.sms4j.example.zhangjun.ZhangJunFactory +# templateId: d2a****777 +# appId: 64c52d2a****77775fe72e3 +# sid: d2a****777 +# url: https://sms.idowe.com/**/**/**/send + qiniu: + access-key-id: EQcDflLTCYnU1******CmqIYLhog1lkWHb2 + access-key-secret: NeS2ptvZQoIy*****err2DdLe7wxFfQvji1 + templateId: 1752130****15859456 + signatureId: 175185*****1624960 + templateName: code + # 中国移动 云MAS + mas: + supplier: mas + # 请求方式默认为 HTTP + # 请求方法 默认为 HTTP模式下的 tmpsubmit + # norsubmit 无模板接口 不需要配置templateId + # tmpsubmit 有模板接口 需要配置templateId + # HTTPS 模式下 请求方法有 submit tmpsubmit + action: tmpsubmit + # 请求地址 HTTP模式下可不配置 默认为 http://112.35.1.155:1992/sms/ + # HTTPS模式下 请设置 https://****:****/sms/ + request-url: http://112.35.1.155:1992/sms/ + sdk-app-id: 接口账号用户名 + access-key-secret: 用户密码 + ec-name: 企业名称 + signature: 签名编码 + # 当请求方法为 tmpsubmit 时 需要配置templateId + template-id: + # 可为空 不为空时请遵守中国移动云MAS开发文档中的描述[服务代码加扩展码总长度不能超过20位。] + add-serial: + # 百度智能云 sms + baidu: + access-key-id: 访问密钥ID + access-key-secret: 用户密钥 + ec-name: 企业名称 + signature: 签名编码 + template-id: 模板ID + # 模板变量名称 + template-name: code + custom: 用户自定义参数,格式为字符串,状态回调时会回传该值 可不传 + user-ext-id: 通道自定义扩展码 可不传 + # 创蓝 + chuanglan: + access-key-id: 111111 + access-key-secret: 111111 + templateId: 【253云通讯】{$var}您的验证码是:{$var},{$var}分钟内有效 + # 极光 + jiguang: + supplier: jiguang + signId: 签名 ID,该字段为空则使用应用默认签名 + action: 默认请求方法 messages + templateName: 模板变量名称 + voice: action设置为voice_codes有效 语音验证码播报语言选择,0:中文播报,1:英文播报,2:中英混合播报 + ttl: action设置为voice_codes有效 验证码有效期,默认为 60 秒 + tag: action设置为messages/batch有效 标签 + # 螺丝帽 + luosimao: + accessKeyId: 后台提取的API key + action: 默认请求方法 send.json + # submail + submail: + accessKeyId: APPID + accessKeySecret: APPKEY + action: 默认请求方法 send.json + signType: MD5 或 SHA-1 默认MD5 填写任意值,不为即为 密匙明文验证模式 + signVersion: signature加密计算方式 为2时会忽略某些字段 + templateId: 模板ID + signature: 签名 + # danmi + danmi: + accessKeyId: ACCOUNT SID + accessKeySecret: AUTH TOKEN + action: 默认请求方法 distributor/sendSMS + +sms-oa: + config-type: yaml + oas: + oaDingTalkByYaml: # configId + isEnable: true # 表示该配置是否生效(默认生效,false表示不生效) + supplier: dingding # 厂商标识 + tokenId: 您的accessKey + sign: 您的sign + oaByteTalkByYaml: # configId + supplier: feishu # 厂商标识 + tokenId: 您的accessKey + sign: 您的sign + oaWeTalkByYaml: + supplier: wetalk # 厂商标识 + tokenId: 您的sign + core-pool-size: 20 + queue-capacity: 20 + max-pool-size: 20 \ No newline at end of file diff --git a/sms4j-solon-plugin-example/src/test/java/org/dromara/sms4j/example/Sms4jTest.java b/sms4j-solon-plugin-example/src/test/java/org/dromara/sms4j/example/Sms4jTest.java new file mode 100644 index 00000000..af1f2c43 --- /dev/null +++ b/sms4j-solon-plugin-example/src/test/java/org/dromara/sms4j/example/Sms4jTest.java @@ -0,0 +1,531 @@ +package org.dromara.sms4j.example; + +import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.lang.UUID; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; +import lombok.extern.slf4j.Slf4j; +import org.dromara.sms4j.api.SmsBlend; +import org.dromara.sms4j.api.entity.SmsResponse; +import org.dromara.sms4j.baidu.service.BaiduSmsImpl; +import org.dromara.sms4j.comm.constant.SupplierConstant; +import org.dromara.sms4j.comm.utils.SmsUtils; +import org.dromara.sms4j.core.factory.SmsFactory; +import org.dromara.sms4j.danmi.service.DanMiSmsImpl; +import org.dromara.sms4j.jg.service.JgSmsImpl; +import org.dromara.sms4j.lianlu.service.LianLuSmsImpl; +import org.dromara.sms4j.luosimao.service.LuoSiMaoSmsImpl; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.noear.solon.test.SolonJUnit5Extension; +import org.noear.solon.test.SolonTest; + +import java.util.*; + +@Slf4j +@ExtendWith(SolonJUnit5Extension.class) +@SolonTest +public class Sms4jTest { + + /** + * 填测试手机号 + */ + private static final String PHONE = ""; + + @Test + public void byLoadTest() { + if (StrUtil.isBlank(PHONE)) { + return; + } + // 通过负载均衡服务获取短信服务对象 + SmsResponse smsResponse = SmsFactory.getSmsBlend().sendMessage(PHONE, SmsUtils.getRandomInt(6)); + Assert.isTrue(smsResponse.isSuccess()); + } + + @Test + public void alibabaSmsTest() { + // 阿里 + SmsResponse smsResponse = SmsFactory.getBySupplier(SupplierConstant.ALIBABA).sendMessage(PHONE, SmsUtils.getRandomInt(6)); + Assert.isTrue(smsResponse.isSuccess()); + } + + @Test + public void huaweiSmsTest() { + if (StrUtil.isBlank(PHONE)) { + return; + } + // 华为 + SmsResponse smsResponse = SmsFactory.getBySupplier(SupplierConstant.HUAWEI).sendMessage(PHONE, SmsUtils.getRandomInt(6)); + log.info(JSONUtil.toJsonStr(smsResponse)); + Assert.isTrue(smsResponse.isSuccess()); + } + + @Test + public void cloopenSmsTest() { + if (StrUtil.isBlank(PHONE)) { + return; + } + // 容联云 + Map messageMap = MapUtil.newHashMap(2, true); + messageMap.put("captcha", SmsUtils.getRandomInt(4)); + messageMap.put("expirationInMinutes", "5"); + SmsResponse smsResponse = SmsFactory.getBySupplier(SupplierConstant.CLOOPEN) + .sendMessage(PHONE, "1", (LinkedHashMap) messageMap); + log.info(JSONUtil.toJsonStr(smsResponse)); + Assert.isTrue(smsResponse.isSuccess()); + } + + @Test + public void emaySmsTest() { + if (StrUtil.isBlank(PHONE)) { + return; + } + // 亿美软通 + SmsResponse smsResponse = SmsFactory.getBySupplier(SupplierConstant.EMAY).sendMessage(PHONE, SmsUtils.getRandomInt(6)); + log.info(JSONUtil.toJsonStr(smsResponse)); + Assert.isTrue(smsResponse.isSuccess()); + } + + @Test + public void jdCloudSmsTest() { + if (StrUtil.isBlank(PHONE)) { + return; + } + // 京东云 + SmsResponse smsResponse = SmsFactory.getBySupplier(SupplierConstant.JDCLOUD).sendMessage(PHONE, SmsUtils.getRandomInt(6)); + log.info(JSONUtil.toJsonStr(smsResponse)); + Assert.isTrue(smsResponse.isSuccess()); + } + + @Test + public void yunPianSmsTest() { + if (StrUtil.isBlank(PHONE)) { + return; + } + // 云片 + SmsResponse smsResponse = SmsFactory.getBySupplier(SupplierConstant.YUNPIAN).sendMessage(PHONE, SmsUtils.getRandomInt(6)); + log.info(JSONUtil.toJsonStr(smsResponse)); + Assert.isTrue(smsResponse.isSuccess()); + } + + @Test + public void tencentSmsTest() { + if (StrUtil.isBlank(PHONE)) { + return; + } + LinkedHashMap newMap = SmsUtils.getNewMap(); + // 验证码 + newMap.put("1", SmsUtils.getRandomInt(4)); + // 有效时间 + newMap.put("2", "2"); + SmsResponse smsResponse = SmsFactory.getBySupplier(SupplierConstant.TENCENT) + .sendMessage(PHONE, "1603670", newMap); + log.info(JSONUtil.toJsonStr(smsResponse)); + Assert.isTrue(smsResponse.isSuccess()); + } + + @Test + public void uniSmsTest() { + if (StrUtil.isBlank(PHONE)) { + return; + } + // 合一 + SmsResponse smsResponse = SmsFactory.getBySupplier(SupplierConstant.UNISMS).sendMessage(PHONE, SmsUtils.getRandomInt(6)); + log.info(JSONUtil.toJsonStr(smsResponse)); + Assert.isTrue(smsResponse.isSuccess()); + } + + @Test + public void cyYunSmsTest() { + if (StrUtil.isBlank(PHONE)) { + return; + } + // 天翼云 + SmsResponse smsResponse = SmsFactory.getBySupplier(SupplierConstant.CTYUN).sendMessage(PHONE, SmsUtils.getRandomInt(6)); + log.info(JSONUtil.toJsonStr(smsResponse)); + Assert.isTrue(smsResponse.isSuccess()); + } + + @Test + public void neteaseSmsTest() { + if (StrUtil.isBlank(PHONE)) { + return; + } + // 网易云短信 + SmsResponse smsResponse = SmsFactory.getBySupplier(SupplierConstant.NETEASE).sendMessage(PHONE, SmsUtils.getRandomInt(6)); + log.info(JSONUtil.toJsonStr(smsResponse)); + Assert.isTrue(smsResponse.isSuccess()); + } + + /** + * 助通短信测试1:无模板 + */ + @Test + public void zhutongSms1Test() { + if (StrUtil.isBlank(PHONE)) { + return; + } + // 助通短信短信 + String msg = StrUtil.format("【图书商城】您好,你的验证码是{}:(5分钟失效)", SmsUtils.getRandomInt(6)); + SmsResponse smsResponse = SmsFactory.getBySupplier(SupplierConstant.ZHUTONG).sendMessage(PHONE, msg); + log.info(JSONUtil.toJsonStr(smsResponse)); + Assert.isTrue(smsResponse.isSuccess()); + } + + /** + * 助通短信测试2:有模板 + */ + @Test + public void zhutongSmsTest2Template() { + if (StrUtil.isBlank(PHONE)) { + return; + } + // 助通短信短信 + LinkedHashMap messages = new LinkedHashMap<>(1); + messages.put("code", SmsUtils.getRandomInt(6)); + SmsResponse smsResponse = SmsFactory.getBySupplier(SupplierConstant.ZHUTONG).sendMessage(PHONE, "59264", messages); + log.info(JSONUtil.toJsonStr(smsResponse)); + Assert.isTrue(smsResponse.isSuccess()); + } + + /** + * 助通短信测试3:无模板群发 + */ + @Test + public void zhutongSms3MoreTest() { + if (StrUtil.isBlank(PHONE)) { + return; + } + // 助通短信短信 + String msg = StrUtil.format("【图书商城】您好,你的验证码是{}:(5分钟失效)", SmsUtils.getRandomInt(6)); + SmsResponse smsResponse = SmsFactory.getBySupplier(SupplierConstant.ZHUTONG).massTexting(ListUtil.of(PHONE, "180****1111"), msg); + log.info(JSONUtil.toJsonStr(smsResponse)); + Assert.isTrue(smsResponse.isSuccess()); + } + + /** + * 助通短信测试4:有模板 多人群发 + */ + @Test + public void zhutongSms4TemplateTest() { + if (StrUtil.isBlank(PHONE)) { + return; + } + // 助通短信短信 + LinkedHashMap messages = new LinkedHashMap<>(1); + messages.put("code", SmsUtils.getRandomInt(6)); + SmsResponse smsResponse = SmsFactory.getBySupplier(SupplierConstant.ZHUTONG).massTexting(ListUtil.of(PHONE, "180****1111"), "59264", messages); + log.info(JSONUtil.toJsonStr(smsResponse)); + Assert.isTrue(smsResponse.isSuccess()); + } + + /** + * 联麓模板短信 + */ + @Test + public void lianLuTemplateSmsTest() { + if (StrUtil.isBlank(PHONE)) { + return; + } + SmsResponse smsResponse = SmsFactory.getBySupplier(SupplierConstant.LIANLU) + .sendMessage(PHONE, SmsUtils.getRandomInt(6)); + log.info(JSONUtil.toJsonStr(smsResponse)); + Assert.isTrue(smsResponse.isSuccess()); + } + + /** + * 联麓普通短信 + */ + @Test + public void lianLuNormalSmsTest() { + if (StrUtil.isBlank(PHONE)) { + return; + } + LianLuSmsImpl lianLuSms = (LianLuSmsImpl) SmsFactory.getBySupplier(SupplierConstant.LIANLU); + SmsResponse smsResponse = lianLuSms.sendNormalMessage(PHONE, "测试短信" + SmsUtils.getRandomInt(6)); + log.info(JSONUtil.toJsonStr(smsResponse)); + Assert.isTrue(smsResponse.isSuccess()); + } + + /** + * 鼎众普通短信 + */ + @Test + public void dingZhongSmsTest() { + if (StrUtil.isBlank(PHONE)) { + return; + } + SmsBlend dz = SmsFactory.getBySupplier(SupplierConstant.DINGZHONG); + + LinkedHashMap messages = new LinkedHashMap<>(); + messages.put("code", SmsUtils.getRandomInt(6)); + + ArrayList phones = new ArrayList<>(); + phones.add(PHONE); + phones.add(PHONE); + + SmsResponse smsResponse = dz.sendMessage(PHONE, "测试短信" + SmsUtils.getRandomInt(6)); + log.info(JSONUtil.toJsonStr(smsResponse)); + Assert.isTrue(smsResponse.isSuccess()); + + SmsResponse smsResponse1 = dz.sendMessage(PHONE, messages); + log.info(JSONUtil.toJsonStr(smsResponse1)); + Assert.isTrue(smsResponse1.isSuccess()); + + SmsResponse smsResponse3 = dz.massTexting(phones, "测试短信" + SmsUtils.getRandomInt(6)); + log.info(JSONUtil.toJsonStr(smsResponse3)); + Assert.isTrue(smsResponse3.isSuccess()); + + SmsResponse smsResponse4 = dz.massTexting(phones, "" ,messages); + log.info(JSONUtil.toJsonStr(smsResponse4)); + Assert.isTrue(smsResponse4.isSuccess()); + + } + + /** + * 中国移动 云MAS + */ + @Test + public void masSmsTest() { + if (StrUtil.isBlank(PHONE)) { + return; + } + // 发送一对一/一对多普通短信 + // HTTP模式下 action请配置为 norsubmit + // HTTPS模式下 action请配置为 submit + SmsResponse oneToMany = SmsFactory.getBySupplier(SupplierConstant.MAS) + .sendMessage(PHONE, "测试短信" + SmsUtils.getRandomInt(6)); + log.info(JSONUtil.toJsonStr(oneToMany)); + + // 发送多对多普通短信 + // HTTP模式下 action请配置为 norsubmit + // HTTPS模式下 action请配置为 submit + LinkedHashMap content = new LinkedHashMap<>(); + content.put("18***1", "测试短信1"); + content.put("18***2", "测试短信2"); + content.put("18***3", "测试短信3"); + content.put("18***4", "测试短信4"); + SmsResponse manyToMany1 = SmsFactory.getBySupplier(SupplierConstant.MAS) + .sendMessage(PHONE, content); + log.info(JSONUtil.toJsonStr(manyToMany1)); + + // 或者 + SmsResponse manyToMany2 = SmsFactory.getBySupplier(SupplierConstant.MAS) + .sendMessage(PHONE, JSONUtil.toJsonStr(content)); + log.info(JSONUtil.toJsonStr(manyToMany2)); + + // 发送模板短信 + // HTTP模式下或者HTTPS模式下 action请都配置为 tmpsubmit + // 无参数 + SmsResponse strRes = SmsFactory.getBySupplier(SupplierConstant.MAS) + .sendMessage(PHONE, StrUtil.EMPTY); + log.info(JSONUtil.toJsonStr(strRes)); + + //数组格式 + String[] paramsArr = {"param1", "param2"}; + SmsResponse arrRes = SmsFactory.getBySupplier(SupplierConstant.MAS) + .sendMessage(PHONE, JSONUtil.toJsonStr(paramsArr)); + log.info(JSONUtil.toJsonStr(arrRes)); + + //list格式 + List paramsList = new ArrayList<>(); + paramsList.add("param1"); + paramsList.add("param2"); + SmsResponse listRes = SmsFactory.getBySupplier(SupplierConstant.MAS) + .sendMessage(PHONE, JSONUtil.toJsonStr(paramsList)); + log.info(JSONUtil.toJsonStr(listRes)); + } + + /** + * 百度短信 + */ + @Test + public void baiduSmsTest() { + if (StrUtil.isBlank(PHONE)) { + return; + } + + // 发送短信 + SmsResponse resp = SmsFactory.getBySupplier(SupplierConstant.BAIDU) + .sendMessage(PHONE, SmsUtils.getRandomInt(6)); + log.info(JSONUtil.toJsonStr(resp)); + + // 发送携带幂等性参数短信 + BaiduSmsImpl sendWithClientToken = (BaiduSmsImpl) SmsFactory.getBySupplier(SupplierConstant.BAIDU); + String clientToken = UUID.fastUUID().toString(true); + SmsResponse respWithClientToken = sendWithClientToken.sendMessageWithClientToken(PHONE, + SmsUtils.getRandomInt(6), + clientToken); + log.info(JSONUtil.toJsonStr(respWithClientToken)); + } + + /** + * 创蓝短信 + */ + @Test + public void chungLanTest() { + if (StrUtil.isBlank(PHONE)) { + return; + } + + //测试群发【模板】 + List arrayList = new ArrayList<>(); + arrayList.add(PHONE); + arrayList.add("135****000"); + LinkedHashMap map = new LinkedHashMap<>(); + map.put("1", "1544"); + map.put("2", "2222"); + SmsResponse smsResponse2 = SmsFactory.getBySupplier(SupplierConstant.CHUANGLAN).massTexting(arrayList, "[test]你的验证码是{$val},{$val}", map); + log.info("smsResponse2:{}", smsResponse2); + + //测试单条发送 + SmsResponse smsResponse1 = SmsFactory.getBySupplier(SupplierConstant.CHUANGLAN).sendMessage(PHONE, "1544&2222"); + log.info("smsResponse1:{}", smsResponse1); + + //测试单条模板发送 + LinkedHashMap map3 = new LinkedHashMap<>(); + map3.put("1", "1544"); + map3.put("2", "2222"); + SmsResponse smsResponse3 = SmsFactory.getBySupplier(SupplierConstant.CHUANGLAN).sendMessage(PHONE, "[test]你的验证码是{$val},{$val}", map3); + log.info("smsResponse3:{}", smsResponse3); + } + + /** + * 极光短信 + */ + @Test + public void jgSmsTest() { + // 极光 发送文本验证码短信 API 不需要传入具体验证码 返回 {"msg_id": "288193860302"} + SmsResponse smsResponse1 = SmsFactory.getBySupplier(SupplierConstant.JIGUANG).sendMessage(PHONE, ""); + Assert.isTrue(smsResponse1.isSuccess()); + + // 极光 发送语音验证码短信 请确保action配置为voice_codes + JgSmsImpl voiceCode = (JgSmsImpl) SmsFactory.getBySupplier(SupplierConstant.JIGUANG); + SmsResponse voiceResp = voiceCode.sendVoiceCode(PHONE, + SmsUtils.getRandomInt(6)); + Assert.isTrue(voiceResp.isSuccess()); + + // 验证验证码是否有效 请确保action配置为voice_codes + JgSmsImpl verify = (JgSmsImpl) SmsFactory.getBySupplier(SupplierConstant.JIGUANG); + SmsResponse verifyResp = verify.verifyCode("123456", "288193860302"); + Assert.isTrue(verifyResp.isSuccess()); + + // 极光 发送单条模板短信 API 发送自定义验证码 sendTemplateSMS + LinkedHashMap map1 = new LinkedHashMap<>(); + map1.put("code", "123456"); + SmsResponse smsResponse2 = SmsFactory.getBySupplier(SupplierConstant.JIGUANG).sendMessage(PHONE, map1); + Assert.isTrue(smsResponse2.isSuccess()); + + // 极光 发送单条模板短信 API 发送多参数自定义模板短信 sendTemplateSMS_with_multipleParameters + LinkedHashMap map2 = new LinkedHashMap<>(); + map2.put("name", "test"); + map2.put("password", "test"); + SmsResponse smsResponse3 = SmsFactory.getBySupplier(SupplierConstant.JIGUANG).sendMessage(PHONE, "226992", map2); + Assert.isTrue(smsResponse3.isSuccess()); + + // sendBatchTemplateSMS + LinkedHashMap map3 = new LinkedHashMap<>(); + map3.put("name", "test"); + map3.put("password", "test"); + List phones = new ArrayList<>(); + phones.add(PHONE); + phones.add("xxx"); + SmsResponse smsResponse4 = SmsFactory.getBySupplier(SupplierConstant.JIGUANG).massTexting(phones, "226992", map3); + Assert.isTrue(smsResponse4.isSuccess()); + } + + /** + * 螺丝帽短信 + */ + @Test + public void luosimaoSmsTest() { + // 螺丝帽 发送短信接口详细 发送短信接口详细 send.json + SmsResponse smsResponse1 = SmsFactory.getBySupplier(SupplierConstant.LUO_SI_MAO).sendMessage(PHONE, ""); + Assert.isTrue(smsResponse1.isSuccess()); + + // 螺丝帽 批量发送接口详细 send_batch.json + SmsResponse smsResponse2 = SmsFactory.getBySupplier(SupplierConstant.LUO_SI_MAO).massTexting(Collections.singletonList(PHONE), ""); + Assert.isTrue(smsResponse2.isSuccess()); + + // 螺丝帽 定时批量发送接口详细 send_batch.json + LuoSiMaoSmsImpl luoSiMao = (LuoSiMaoSmsImpl) SmsFactory.getBySupplier(SupplierConstant.LUO_SI_MAO); + SmsResponse smsResponse3 = luoSiMao.massTextingOnTime(Collections.singletonList(PHONE), "", new Date()); + Assert.isTrue(smsResponse3.isSuccess()); + + // 螺丝帽 查询账户余额 status.json + SmsResponse smsResponse4 = luoSiMao.queryAccountBalance(); + Assert.isTrue(smsResponse4.isSuccess()); + } + + /** + * SUBMAIL短信 + */ + @Test + public void mysubmailSmsTest() { + // 短信发送 send.json 【xxxx】签名可配置 系统自动带入 + SmsResponse smsResponse1 = SmsFactory.getBySupplier(SupplierConstant.MY_SUBMAIL).sendMessage(PHONE, "【SUBMAIL】你好,你的验证码是2257"); + Assert.isTrue(smsResponse1.isSuccess()); + + // 短信模板发送 xsend.json + LinkedHashMap vars = new LinkedHashMap<>(); + vars.put("code", "123456"); + SmsResponse smsResponse2 = SmsFactory.getBySupplier(SupplierConstant.MY_SUBMAIL).sendMessage(PHONE, "xxx", vars); + Assert.isTrue(smsResponse2.isSuccess()); + + // 短信一对多发送 multisend.json 【xxxx】签名可配置 系统自动带入 content字段说明:短信正文 案例没有说明无需传 + LinkedHashMap vars1 = new LinkedHashMap<>(); + vars1.put("content", "【SUBMAIL】您的短信验证码:4438,请在10分钟内输入。"); + vars1.put("code", "123456"); + vars1.put("time", "10"); + SmsResponse smsResponse3 = SmsFactory.getBySupplier(SupplierConstant.MY_SUBMAIL).massTexting(Collections.singletonList(PHONE), JSONUtil.toJsonStr(vars1)); + Assert.isTrue(smsResponse3.isSuccess()); + + // 短信模板一对多发送 multixsend.json + LinkedHashMap vars2 = new LinkedHashMap<>(); + vars2.put("code", "123456"); + SmsResponse smsResponse4 = SmsFactory.getBySupplier(SupplierConstant.MY_SUBMAIL).massTexting(Collections.singletonList(PHONE), "xxx", vars2); + Assert.isTrue(smsResponse4.isSuccess()); + + // 短信批量群发 batchsend.json 【xxxx】签名可配置 系统自动带入 content字段说明:短信正文 案例没有说明无需传 + LinkedHashMap vars3 = new LinkedHashMap<>(); + vars3.put("content", "123456"); + SmsResponse smsResponse5 = SmsFactory.getBySupplier(SupplierConstant.MY_SUBMAIL).massTexting(Collections.singletonList(PHONE), JSONUtil.toJsonStr(vars3)); + Assert.isTrue(smsResponse5.isSuccess()); + + // 短信批量模板群发 batchxsend.json + LinkedHashMap vars4 = new LinkedHashMap<>(); + vars4.put("code", "123456"); + vars4.put("time", "10"); + SmsResponse smsResponse6 = SmsFactory.getBySupplier(SupplierConstant.MY_SUBMAIL).massTexting(Collections.singletonList(PHONE), "xxx", vars4); + Assert.isTrue(smsResponse6.isSuccess()); + } + + /** + * danmi短信 + */ + @Test + public void danmiSmsTest() { + // 短信发送 distributor/sendSMS 群发 massTexting + SmsResponse smsResponse1 = SmsFactory.getBySupplier(SupplierConstant.DAN_MI).sendMessage(PHONE, "【danmi】你好,你的验证码是666"); + Assert.isTrue(smsResponse1.isSuccess()); + + DanMiSmsImpl danMiSms = (DanMiSmsImpl) SmsFactory.getBySupplier(SupplierConstant.DAN_MI); + // 短信余额查询 distributor/user/query + SmsResponse smsResponse2 = danMiSms.queryBalance(); + Assert.isTrue(smsResponse2.isSuccess()); + + // 语音验证码发送 voice/voiceCode + SmsResponse smsResponse3 = danMiSms.voiceCode(PHONE, "666"); + Assert.isTrue(smsResponse3.isSuccess()); + + // 语音通知文件发送 voice/voiceNotify + SmsResponse smsResponse4 = danMiSms.voiceNotify(PHONE, "sjkhduiq"); + Assert.isTrue(smsResponse4.isSuccess()); + + // 语音模板通知发送 voice/voiceTemplate + SmsResponse smsResponse5 = danMiSms.voiceTemplate(PHONE, "opipedlqza", "111,222,333"); + Assert.isTrue(smsResponse5.isSuccess()); + } +} diff --git a/sms4j-solon-plugin-example/src/test/java/org/dromara/sms4j/example/SmsOaTest.java b/sms4j-solon-plugin-example/src/test/java/org/dromara/sms4j/example/SmsOaTest.java new file mode 100644 index 00000000..f1c1d76d --- /dev/null +++ b/sms4j-solon-plugin-example/src/test/java/org/dromara/sms4j/example/SmsOaTest.java @@ -0,0 +1,444 @@ +package org.dromara.sms4j.example; + +import lombok.extern.slf4j.Slf4j; +import org.dromara.oa.api.OaSender; +import org.dromara.oa.comm.entity.Request; +import org.dromara.oa.comm.entity.WeTalkRequestArticle; +import org.dromara.oa.comm.enums.MessageType; +import org.dromara.oa.core.byteTalk.config.ByteTalkConfig; +import org.dromara.oa.core.dingTalk.config.DingTalkConfig; +import org.dromara.oa.core.provider.factory.OaFactory; +import org.dromara.oa.core.weTalk.config.WeTalkConfig; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.noear.solon.test.SolonJUnit5Extension; +import org.noear.solon.test.SolonTest; + +import java.util.ArrayList; +import java.util.Random; +import java.util.concurrent.CountDownLatch; + + +@Slf4j +@ExtendWith(SolonJUnit5Extension.class) +@SolonTest +public class SmsOaTest { + //***********************DingTalk-Test************************// + /** + * 填测试手机号 + */ + private static final String DingTalkPHONE = ""; + /** + * 填access_token + */ + private static final String DingTalkTOKENID = ""; + /** + * 填secret + */ + private static final String DingTalkSIGN = ""; + + + /** + * DingTalk的Text测试 + */ + @Test + public void oaDingTalkText() { + String key = "oaDingTalk"; + DingTalkConfig dingTalkConfig = new DingTalkConfig(); + dingTalkConfig.setConfigId(key); + dingTalkConfig.setSign(DingTalkSIGN); + dingTalkConfig.setTokenId(DingTalkTOKENID); + + // 根据配置创建服务实例并注册 + OaFactory.createAndRegisterOaSender(dingTalkConfig); + OaSender alarm = OaFactory.getSmsOaBlend(key); + + Request request = new Request(); + ArrayList phones = new ArrayList<>(); + phones.add(DingTalkPHONE); + // 支持通过手机号@ + request.setPhoneList(phones); + // 支持@all +// request.setIsNoticeAll(true); + request.setContent("测试消息"); + + alarm.sender(request, MessageType.DING_TALK_TEXT); + + } + + /** + * DingTalk的Markdown测试 + */ + @Test + public void oaDingTalkMarkdown() { + String key = "oaDingTalk"; + DingTalkConfig dingTalkConfig = new DingTalkConfig(); + dingTalkConfig.setConfigId(key); + dingTalkConfig.setSign(DingTalkSIGN); + dingTalkConfig.setTokenId(DingTalkTOKENID); + + // 根据配置创建服务实例并注册 + OaFactory.createAndRegisterOaSender(dingTalkConfig); + OaSender alarm = OaFactory.getSmsOaBlend(key); + + Request request = new Request(); + // 支持@all + request.setIsNoticeAll(true); + request.setContent("#### 杭州天气 @150XXXXXXXX \n > 9度,西北风1级,空气良89,相对温度73%\n > ![screenshot](https://img.alicdn.com/tfs/TB1NwmBEL9TBuNjy1zbXXXpepXa-2400-1218.png)\n > ###### 10点20分发布 [天气](https://www.dingtalk.com) \n"); + request.setTitle("标题"); + alarm.sender(request, MessageType.DING_TALK_MARKDOWN); + + } + + /** + * DingTalk的Link测试 + */ + @Test + public void oaDingTalkLink() { + String key = "oaDingTalk"; + DingTalkConfig dingTalkConfig = new DingTalkConfig(); + dingTalkConfig.setConfigId(key); + dingTalkConfig.setSign(DingTalkSIGN); + dingTalkConfig.setTokenId(DingTalkTOKENID); + + // 根据配置创建服务实例并注册 + OaFactory.createAndRegisterOaSender(dingTalkConfig); + OaSender alarm = OaFactory.getSmsOaBlend(key); + + Request request = new Request(); + request.setContent("这个即将发布的新版本,创始人xx称它为红树林。而在此之前,每当面临重大升级,产品经理们都会取一个应景的代号,这一次,为什么是红树林"); + request.setTitle("点击跳转到钉钉"); + request.setMessageUrl("https://www.dingtalk.com/s?__biz=MzA4NjMwMTA2Ng==&mid=2650316842&idx=1&sn=60da3ea2b29f1dcc43a7c8e4a7c97a16&scene=2&srcid=09189AnRJEdIiWVaKltFzNTw&from=timeline&isappinstalled=0&key=&ascene=2&uin=&devicetype=android-23&version=26031933&nettype=WIFI"); + request.setPicUrl("https://img.alicdn.com/tfs/TB1NwmBEL9TBuNjy1zbXXXpepXa-2400-1218.png"); + alarm.sender(request, MessageType.DING_TALK_LINK); + + + } + + /** + * DingTalk的异步消息发送 + */ + @Test + public void oaDingTalkAsyncTest() { + String key = "oaDingTalk"; + DingTalkConfig dingTalkConfig = new DingTalkConfig(); + dingTalkConfig.setConfigId(key); + dingTalkConfig.setSign(DingTalkSIGN); + dingTalkConfig.setTokenId(DingTalkTOKENID); + + // 根据配置创建服务实例并注册 + OaFactory.createAndRegisterOaSender(dingTalkConfig); + OaSender alarm = OaFactory.getSmsOaBlend(key); + + + Request request = new Request(); + ArrayList phones = new ArrayList<>(); + phones.add(DingTalkPHONE); + // 支持通过手机号@ + request.setPhoneList(phones); + // 支持@all +// request.setIsNoticeAll(true); + request.setContent("测试消息"); + + // 异步发送方式 + alarm.senderAsync(request, MessageType.DING_TALK_TEXT); + alarm.senderAsync(request, MessageType.DING_TALK_TEXT, smsResponse -> System.out.println("ConfigId为" + smsResponse.getOaConfigId() + "的异步任务发送成功")); + + try { + Thread.sleep(3000L); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + /** + * 异步优先级尽可能优先级高的消息先发送,但是获取响应会受网络影响 + */ + @Test + public void oaDingTalkAsyncByPriority() { + String key = "oaDingTalk"; + DingTalkConfig dingTalkConfig = new DingTalkConfig(); + dingTalkConfig.setConfigId(key); + dingTalkConfig.setSign(DingTalkSIGN); + dingTalkConfig.setTokenId(DingTalkTOKENID); + + // 根据配置创建服务实例并注册 + OaFactory.createAndRegisterOaSender(dingTalkConfig); + OaSender alarm = OaFactory.getSmsOaBlend(key); + CountDownLatch user = new CountDownLatch(1); + // 模拟10条不同优先级的消息 + for (int i = 0; i < 3; i++) { + Random random = new Random(); + new Thread(() -> { + int priority = random.nextInt(10); + try { + // 等待十个请求 + System.out.println(priority + "等待"); + user.await(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + Request request = new Request(); + ArrayList phones = new ArrayList<>(); + phones.add(DingTalkPHONE); + request.setPhoneList(phones); + request.setIsNoticeAll(false); + request.setPriority(priority); + //测试-1-TEXT + request.setContent("该消息优先级为" + priority); + alarm.senderAsyncByPriority(request, MessageType.DING_TALK_TEXT); + System.out.println("优先级为" + priority + "的异步任务已提交"); + + }).start(); + } + System.out.println("开始模拟"); + user.countDown(); + // 防止主线程挂掉 + try { + Thread.sleep(2000L); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + @Test + public void oaDingTalkByYamlTest() { + String configId = "oaDingTalkByYaml"; + OaSender alarm = OaFactory.getSmsOaBlend(configId); + Request request = new Request(); + ArrayList phones = new ArrayList<>(); + phones.add(DingTalkPHONE); + // 支持通过手机号@ + request.setPhoneList(phones); + // 支持@all +// request.setIsNoticeAll(true); + request.setContent("测试消息"); + alarm.sender(request, MessageType.DING_TALK_TEXT); + } + //***********************ByteTalk-Test************************// + /** + * 填测试手机号 + */ + private static final String ByteTalkUSERID = ""; + /** + * 填access_token + */ + private static final String ByteTalkTOKENID = ""; + /** + * 填secret + */ + private static final String ByteTalkSIGN = ""; + + /** + * ByteTalk的Text测试 + */ + @Test + public void oaByteTalkText() { + String key = "oaByteTalk"; + ByteTalkConfig byteTalkConfig = new ByteTalkConfig(); + byteTalkConfig.setConfigId(key); + byteTalkConfig.setTokenId(ByteTalkTOKENID); + byteTalkConfig.setSign(ByteTalkSIGN); + // 根据配置创建服务实例并注册 + OaFactory.createAndRegisterOaSender(byteTalkConfig); + OaSender alarm = OaFactory.getSmsOaBlend(key); + + Request request = new Request(); + ArrayList userIds = new ArrayList<>(); + userIds.add(ByteTalkUSERID); + //测试text + request.setUserIdList(userIds); + request.setIsNoticeAll(true); + request.setContent("测试消息"); + alarm.sender(request, MessageType.BYTE_TALK_TEXT); + } + + + /** + * ByteTalk的异步消息发送 + */ + @Test + public void oaByteTalkAsyncText() { + String key = "oaByteTalk"; + ByteTalkConfig byteTalkConfig = new ByteTalkConfig(); + byteTalkConfig.setConfigId(key); + byteTalkConfig.setTokenId(ByteTalkTOKENID); + byteTalkConfig.setSign(ByteTalkSIGN); + // 根据配置创建服务实例并注册 + OaFactory.createAndRegisterOaSender(byteTalkConfig); + OaSender alarm = OaFactory.getSmsOaBlend(key); + + Request request = new Request(); + ArrayList userIds = new ArrayList<>(); + userIds.add(ByteTalkUSERID); + //测试text + request.setUserIdList(userIds); + request.setIsNoticeAll(true); + request.setContent("测试消息"); + + alarm.senderAsync(request, MessageType.BYTE_TALK_TEXT); + alarm.senderAsync(request, MessageType.BYTE_TALK_TEXT, smsResponse -> System.out.println("ConfigId为" + smsResponse.getOaConfigId() + "的异步任务发送成功")); + + // 防止主线程挂掉 + try { + Thread.sleep(3000L); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + @Test + public void oaByteTalkByYamlTest() { + String configId = "oaByteTalkByYaml"; + OaSender alarm = OaFactory.getSmsOaBlend(configId); + Request request = new Request(); + ArrayList userIds = new ArrayList<>(); + userIds.add(ByteTalkUSERID); + //测试text + request.setUserIdList(userIds); + request.setIsNoticeAll(true); + request.setContent("测试消息"); + alarm.sender(request, MessageType.BYTE_TALK_TEXT); + } + //***********************WeTalk-Test************************// + /** + * 填测试手机号 + */ + private static final String WeTalkPHONE = ""; + /** + * 填测试UserId + */ + private static final String WeTalkUSERID = ""; + /** + * 填access_token + */ + private static final String WeTalkTOKENID = ""; + + /** + * WeTalk的Text测试 + */ + @Test + public void oaWeTalkText() { + String key = "oaWeTalk"; + WeTalkConfig WeTalkConfig = new WeTalkConfig(); + WeTalkConfig.setConfigId(key); + WeTalkConfig.setTokenId(WeTalkTOKENID); + + // 根据配置创建服务实例并注册 + OaFactory.createAndRegisterOaSender(WeTalkConfig); + OaSender alarm = OaFactory.getSmsOaBlend(key); + + Request request = new Request(); + ArrayList phones = new ArrayList<>(); + phones.add(WeTalkPHONE); + //测试text + request.setPhoneList(phones); + request.setIsNoticeAll(true); + request.setContent("测试消息"); + + alarm.sender(request, MessageType.WE_TALK_TEXT); + + } + + /** + * WeTalk的Markdown测试--不支持@all,只能通过userId进行 + */ + @Test + public void oaWeTalkMarkdown() { + String key = "oaWeTalk"; + WeTalkConfig WeTalkConfig = new WeTalkConfig(); + WeTalkConfig.setConfigId(key); + WeTalkConfig.setTokenId(WeTalkTOKENID); + + // 根据配置创建服务实例并注册 + OaFactory.createAndRegisterOaSender(WeTalkConfig); + OaSender alarm = OaFactory.getSmsOaBlend(key); + + Request request = new Request(); + // 管理后台-通讯录 账号就是userid,或者通过接口获取部门列表 再获取部门成员详情获取userid + ArrayList userIdList = new ArrayList<>(); + userIdList.add(WeTalkUSERID); + request.setUserIdList(userIdList); + + request.setContent( + "实时新增用户反馈132例,请相关同事注意。\n" + + ">类型:用户反馈" + + ">普通用户反馈:117例" + + ">VIP用户反馈:15例"); + + alarm.sender(request, MessageType.WE_TALK_MARKDOWN); + + + } + + /** + * WeTalk的News测试 + */ + @Test + public void oaWeTalkNews() { + String key = "oaWeTalk"; + WeTalkConfig WeTalkConfig = new WeTalkConfig(); + WeTalkConfig.setConfigId(key); + WeTalkConfig.setTokenId(WeTalkTOKENID); + + // 根据配置创建服务实例并注册 + OaFactory.createAndRegisterOaSender(WeTalkConfig); + OaSender alarm = OaFactory.getSmsOaBlend(key); + + Request request = new Request(); + ArrayList articles = new ArrayList<>(); + articles.add(new WeTalkRequestArticle("中秋节礼品领取", "今年中秋节公司有豪礼相送", "www.qq.com", "http://res.mail.qq.com/node/ww/wwopenmng/images/independent/doc/test_pic_msg1.png")); + request.setArticleList(articles); + + alarm.sender(request, MessageType.WE_TALK_NEWS); + } + + /** + * WeTalk的异步消息发送 + */ + @Test + public void oaWeTalkAsyncText() { + String key = "oaWeTalk"; + WeTalkConfig WeTalkConfig = new WeTalkConfig(); + WeTalkConfig.setConfigId(key); + WeTalkConfig.setTokenId(WeTalkTOKENID); + + // 根据配置创建服务实例并注册 + OaFactory.createAndRegisterOaSender(WeTalkConfig); + OaSender alarm = OaFactory.getSmsOaBlend(key); + + Request request = new Request(); + ArrayList phones = new ArrayList<>(); + phones.add(WeTalkPHONE); + //测试text + request.setPhoneList(phones); + request.setIsNoticeAll(true); + request.setContent("测试消息"); + + // 异步发送方式 + alarm.senderAsync(request, MessageType.WE_TALK_TEXT); + alarm.senderAsync(request, MessageType.WE_TALK_TEXT, smsResponse -> System.out.println("ConfigId为" + smsResponse.getOaConfigId() + "的异步任务发送成功")); + + // 防止主线程挂掉 + try { + Thread.sleep(3000L); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + @Test + public void oaWeTalkByYamlTest() { + String configId = "oaWeTalkByYaml"; + OaSender alarm = OaFactory.getSmsOaBlend(configId); + Request request = new Request(); + ArrayList phones = new ArrayList<>(); + phones.add(WeTalkPHONE); + request.setPhoneList(phones); + request.setIsNoticeAll(true); + request.setContent("SMS4JContent"); + alarm.sender(request, MessageType.WE_TALK_TEXT); + } +} + + + diff --git a/sms4j-solon-plugin-example/src/test/java/org/dromara/sms4j/example/SmsProcessorTest.java b/sms4j-solon-plugin-example/src/test/java/org/dromara/sms4j/example/SmsProcessorTest.java new file mode 100644 index 00000000..c5f28537 --- /dev/null +++ b/sms4j-solon-plugin-example/src/test/java/org/dromara/sms4j/example/SmsProcessorTest.java @@ -0,0 +1,192 @@ +package org.dromara.sms4j.example; + +import cn.hutool.core.lang.Assert; +import lombok.extern.slf4j.Slf4j; +import org.dromara.sms4j.api.SmsBlend; +import org.dromara.sms4j.api.entity.SmsResponse; +import org.dromara.sms4j.comm.constant.SupplierConstant; +import org.dromara.sms4j.comm.exception.SmsBlendException; +import org.dromara.sms4j.comm.utils.SmsUtils; +import org.dromara.sms4j.core.factory.SmsFactory; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.noear.solon.test.SolonJUnit5Extension; +import org.noear.solon.test.SolonTest; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; + +/** + * @author sh1yu + */ +@Slf4j +@ExtendWith(SolonJUnit5Extension.class) +@SolonTest +public class SmsProcessorTest { + /** + * 填测试手机号 + */ + private static final String PHONE = "11111111111"; + private static final String PHONE1 = "22222222222"; + + //基础发送测试,即黑名单、账户限制、渠道限制都不预设直接发送(新增参数全为空) + @Test + public void test1() { + System.out.println("------------"); + SmsBlend smsBlend = SmsFactory.getBySupplier(SupplierConstant.UNISMS); + SmsResponse smsResponse = smsBlend.sendMessage(PHONE, SmsUtils.getRandomInt(6)); + Assert.isTrue(smsResponse.isSuccess()); + System.out.println(smsResponse.getData()); + + + } + + //第二个账号的测试 + @Test + public void test2() { + System.out.println("------------"); + + SmsBlend smsBlend = SmsFactory.getBySupplier(SupplierConstant.HUAWEI); + SmsResponse smsResponse = smsBlend.sendMessage(PHONE1, SmsUtils.getRandomInt(6)); + Assert.isTrue(smsResponse.isSuccess()); + System.out.println(smsResponse.getData()); + + } + + //参数校验测试 + @Test + public void test3() { + System.out.println("------------"); + + SmsBlendException knowEx = null; + try { + SmsFactory.getBySupplier(SupplierConstant.UNISMS).sendMessage(PHONE, new LinkedHashMap<>()); + } catch (SmsBlendException e) { + knowEx = e; + System.out.println(knowEx.getMessage()); + } + Assert.notNull(knowEx); + knowEx = null; + try { + SmsFactory.getBySupplier(SupplierConstant.UNISMS).sendMessage("", SmsUtils.getRandomInt(6)); + } catch (SmsBlendException e) { + knowEx = e; + System.out.println(knowEx.getMessage()); + } + Assert.notNull(knowEx); + knowEx = null; + try { + SmsFactory.getBySupplier(SupplierConstant.UNISMS).sendMessage(PHONE, ""); + } catch (SmsBlendException e) { + knowEx = e; + System.out.println(knowEx.getMessage()); + } + Assert.notNull(knowEx); + knowEx = null; + try { + SmsFactory.getBySupplier(SupplierConstant.UNISMS).sendMessage(PHONE, "111", new LinkedHashMap<>()); + } catch (SmsBlendException e) { + knowEx = e; + System.out.println(knowEx.getMessage()); + } + Assert.notNull(knowEx); + knowEx = null; + try { + SmsFactory.getBySupplier(SupplierConstant.UNISMS).massTexting(Collections.singletonList(PHONE), ""); + } catch (SmsBlendException e) { + knowEx = e; + System.out.println(knowEx.getMessage()); + } + Assert.notNull(knowEx); + knowEx = null; + try { + SmsFactory.getBySupplier(SupplierConstant.UNISMS).massTexting(Collections.singletonList(PHONE), "2222", new LinkedHashMap<>()); + } catch (SmsBlendException e) { + knowEx = e; + System.out.println(knowEx.getMessage()); + } + Assert.notNull(knowEx); + knowEx = null; + try { + SmsFactory.getBySupplier(SupplierConstant.UNISMS).massTexting(new ArrayList<>(), "321321"); + } catch (SmsBlendException e) { + knowEx = e; + System.out.println(knowEx.getMessage()); + } + Assert.notNull(knowEx); + } + + //黑名单测试 + @Test + public void test4() { + System.out.println("------------"); + + SmsBlend smsBlend = SmsFactory.getBySupplier(SupplierConstant.UNISMS); + //单黑名单添加 + smsBlend.joinInBlacklist(PHONE); + SmsBlendException knowEx = null; + try { + smsBlend.sendMessage(PHONE, SmsUtils.getRandomInt(6)); + } catch (SmsBlendException e) { + knowEx = e; + System.out.println(knowEx.getMessage()); + } + Assert.notNull(knowEx); + //单黑名单移除 + smsBlend.removeFromBlacklist(PHONE); + SmsResponse smsResponse = smsBlend.sendMessage(PHONE, SmsUtils.getRandomInt(6)); + Assert.isTrue(smsResponse.isSuccess()); + //批量黑名单添加 + smsBlend.batchJoinBlacklist(Collections.singletonList(PHONE)); + knowEx = null; + try { + smsBlend.sendMessage(PHONE, SmsUtils.getRandomInt(6)); + } catch (SmsBlendException e) { + knowEx = e; + System.out.println(knowEx.getMessage()); + } + Assert.notNull(knowEx); + //批量黑名单添加 + smsBlend.batchRemovalFromBlacklist(Collections.singletonList(PHONE)); + smsResponse = smsBlend.sendMessage(PHONE, SmsUtils.getRandomInt(6)); + Assert.isTrue(smsResponse.isSuccess()); + + } + + //账号级上限测试、需成功发送4笔,再发就会报错 参数配置 4 + @Test + public void test5() { + System.out.println("------------"); + + SmsResponse smsResponse = SmsFactory.getBySupplier(SupplierConstant.UNISMS).sendMessage(PHONE, SmsUtils.getRandomInt(6)); + Assert.isTrue(smsResponse.isSuccess()); + SmsBlendException knowEx = null; + try { + SmsFactory.getBySupplier(SupplierConstant.UNISMS).sendMessage(PHONE, SmsUtils.getRandomInt(6)); + } catch (SmsBlendException e) { + knowEx = e; + System.out.println(knowEx.getMessage()); + } + Assert.notNull(knowEx); + } + + //渠道级上限测试、需成功发送6笔,再发就会报错 参数配置 6 + @Test + public void test6() { + System.out.println("------------"); + + SmsResponse smsResponse = SmsFactory.getBySupplier(SupplierConstant.UNISMS).sendMessage(PHONE1, SmsUtils.getRandomInt(6)); + Assert.isTrue(smsResponse.isSuccess()); + + SmsBlendException knowEx = null; + try { + SmsFactory.getBySupplier(SupplierConstant.UNISMS).sendMessage(PHONE1, SmsUtils.getRandomInt(6)); + } catch (SmsBlendException e) { + knowEx = e; + System.out.println(knowEx.getMessage()); + } + Assert.notNull(knowEx); + } + +} diff --git a/sms4j-solon-plugin-example/src/test/java/org/dromara/sms4j/example/SmsUtilsTest.java b/sms4j-solon-plugin-example/src/test/java/org/dromara/sms4j/example/SmsUtilsTest.java new file mode 100644 index 00000000..3c294863 --- /dev/null +++ b/sms4j-solon-plugin-example/src/test/java/org/dromara/sms4j/example/SmsUtilsTest.java @@ -0,0 +1,102 @@ +package org.dromara.sms4j.example; + +import cn.hutool.core.lang.Assert; +import lombok.extern.slf4j.Slf4j; +import org.dromara.sms4j.aliyun.config.AlibabaConfig; +import org.dromara.sms4j.comm.utils.SmsUtils; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.noear.solon.test.SolonJUnit5Extension; +import org.noear.solon.test.SolonTest; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author handy + */ +@Slf4j +@ExtendWith(SolonJUnit5Extension.class) +@SolonTest +public class SmsUtilsTest { + + @Test + public void getRandomString() { + String randomString = SmsUtils.getRandomString(); + log.info(randomString); + Assert.isTrue(randomString.length() == 6); + } + + @Test + public void testGetRandomString() { + String randomString = SmsUtils.getRandomString(4); + log.info(randomString); + Assert.isTrue(randomString.length() == 4); + } + + @Test + public void getRandomInt() { + String randomInt = SmsUtils.getRandomInt(4); + log.info(randomInt); + Assert.isTrue(randomInt.length() == 4); + } + + @Test + public void isEmpty() { + Assert.isTrue(SmsUtils.isEmpty("")); + } + + @Test + public void isNotEmpty() { + Assert.isTrue(SmsUtils.isNotEmpty("not")); + } + + @Test + public void jsonForObject() { + AlibabaConfig alibabaConfig = SmsUtils.jsonForObject("{'templateName':'Test'}", AlibabaConfig.class); + Assert.isTrue(alibabaConfig.getTemplateName().equals("Test")); + } + + @Test + public void copyBean() { + AlibabaConfig alibabaConfig = SmsUtils.jsonForObject("{'templateName':'Test'}", AlibabaConfig.class); + AlibabaConfig alibabaConfig1 = new AlibabaConfig(); + SmsUtils.copyBean(alibabaConfig, alibabaConfig1); + Assert.isTrue(alibabaConfig1.getTemplateName().equals("Test")); + } + + @Test + public void getNewMap() { + SmsUtils.getNewMap(); + } + + @Test + public void joinComma() { + List list = new ArrayList<>(); + list.add("12312341234"); + list.add("12312341235"); + String str = SmsUtils.joinComma(list); + log.info(str); + Assert.isTrue(str.equals("12312341234,12312341235")); + } + + @Test + public void addCodePrefixIfNot() { + List list = new ArrayList<>(); + list.add("12312341234"); + list.add("12312341235"); + String str = SmsUtils.addCodePrefixIfNot(list); + log.info(str); + Assert.isTrue(str.equals("+8612312341234,+8612312341235")); + } + + @Test + public void addCodePrefixIfNotToArray() { + List list = new ArrayList<>(); + list.add("12312341234"); + list.add("12312341235"); + String[] str = SmsUtils.addCodePrefixIfNotToArray(list); + Assert.isTrue(str[0].equals("+8612312341234") && str[1].equals("+8612312341235")); + } + +} diff --git a/sms4j-solon-plugin/src/main/java/org/dromara/sms4j/solon/configuration/SmsBlendsInitializer.java b/sms4j-solon-plugin/src/main/java/org/dromara/sms4j/solon/configuration/SmsBlendsInitializer.java index 3da0edea..0442aa34 100644 --- a/sms4j-solon-plugin/src/main/java/org/dromara/sms4j/solon/configuration/SmsBlendsInitializer.java +++ b/sms4j-solon-plugin/src/main/java/org/dromara/sms4j/solon/configuration/SmsBlendsInitializer.java @@ -1,4 +1,4 @@ -package org.dromara.sms4j.solon.configuration; +package org.dromara.sms4j.solon.config; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONObject; @@ -8,6 +8,8 @@ import org.dromara.sms4j.aliyun.config.AlibabaFactory; import org.dromara.sms4j.api.SmsBlend; import org.dromara.sms4j.api.universal.SupplierConfig; import org.dromara.sms4j.api.verify.PhoneVerify; +import org.dromara.sms4j.baidu.config.BaiduFactory; +import org.dromara.sms4j.budingyun.config.BudingV2Factory; import org.dromara.sms4j.cloopen.config.CloopenFactory; import org.dromara.sms4j.comm.constant.Constant; import org.dromara.sms4j.comm.utils.SmsUtils; @@ -20,16 +22,23 @@ import org.dromara.sms4j.core.proxy.processor.CoreMethodParamValidateProcessor; import org.dromara.sms4j.core.proxy.processor.RestrictedProcessor; import org.dromara.sms4j.core.proxy.processor.SingleBlendRestrictedProcessor; import org.dromara.sms4j.ctyun.config.CtyunFactory; +import org.dromara.sms4j.danmi.config.DanMiFactory; import org.dromara.sms4j.dingzhong.config.DingZhongFactory; import org.dromara.sms4j.emay.config.EmayFactory; import org.dromara.sms4j.huawei.config.HuaweiFactory; import org.dromara.sms4j.jdcloud.config.JdCloudFactory; +import org.dromara.sms4j.chuanglan.config.ChuangLanFactory; +import org.dromara.sms4j.jg.config.JgFactory; import org.dromara.sms4j.lianlu.config.LianLuFactory; +import org.dromara.sms4j.luosimao.config.LuoSiMaoFactory; +import org.dromara.sms4j.mas.config.MasFactory; import org.dromara.sms4j.netease.config.NeteaseFactory; import org.dromara.sms4j.provider.config.SmsConfig; import org.dromara.sms4j.provider.factory.BaseProviderFactory; import org.dromara.sms4j.provider.factory.ProviderFactoryHolder; +import org.dromara.sms4j.qiniu.config.QiNiuFactory; import org.dromara.sms4j.solon.holder.SolonSmsDaoHolder; +import org.dromara.sms4j.submail.config.SubMailFactory; import org.dromara.sms4j.tencent.config.TencentFactory; import org.dromara.sms4j.unisms.config.UniFactory; import org.dromara.sms4j.yunpian.config.YunPianFactory; @@ -95,7 +104,7 @@ public class SmsBlendsInitializer { continue; } configMap.put("config-id", configId); - SmsUtils.replaceKeysSeperator(configMap, "-", "_"); + SmsUtils.replaceKeysSeparator(configMap, "-", "_"); JSONObject configJson = new JSONObject(configMap); SupplierConfig supplierConfig = JSONUtil.toBean(configJson, providerFactory.getConfigClass()); SmsFactory.createSmsBlend(supplierConfig); @@ -119,6 +128,15 @@ public class SmsBlendsInitializer { ProviderFactoryHolder.registerFactory(ZhutongFactory.instance()); ProviderFactoryHolder.registerFactory(LianLuFactory.instance()); ProviderFactoryHolder.registerFactory(DingZhongFactory.instance()); + ProviderFactoryHolder.registerFactory(QiNiuFactory.instance()); + ProviderFactoryHolder.registerFactory(ChuangLanFactory.instance()); + ProviderFactoryHolder.registerFactory(JgFactory.instance()); + ProviderFactoryHolder.registerFactory(BudingV2Factory.instance()); + ProviderFactoryHolder.registerFactory(MasFactory.instance()); + ProviderFactoryHolder.registerFactory(BaiduFactory.instance()); + ProviderFactoryHolder.registerFactory(LuoSiMaoFactory.instance()); + ProviderFactoryHolder.registerFactory(SubMailFactory.instance()); + ProviderFactoryHolder.registerFactory(DanMiFactory.instance()); if(SmsUtils.isClassExists("com.jdcloud.sdk.auth.CredentialsProvider")) { ProviderFactoryHolder.registerFactory(JdCloudFactory.instance()); } diff --git a/sms4j-solon-plugin/src/main/java/org/dromara/sms4j/solon/configuration/SupplierConfiguration.java b/sms4j-solon-plugin/src/main/java/org/dromara/sms4j/solon/configuration/SupplierConfiguration.java index c6b94cdf..aae1dc8f 100644 --- a/sms4j-solon-plugin/src/main/java/org/dromara/sms4j/solon/configuration/SupplierConfiguration.java +++ b/sms4j-solon-plugin/src/main/java/org/dromara/sms4j/solon/configuration/SupplierConfiguration.java @@ -9,6 +9,7 @@ import org.noear.solon.annotation.Configuration; import org.noear.solon.annotation.Inject; import org.noear.solon.core.AppContext; +import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -16,7 +17,7 @@ import java.util.Map; /** * smsConfig参数意义为确保注入时smsConfig已经存在 */ -@Condition(onProperty = "${sms.configType}=config_file") +@Condition(onProperty = "${sms.configType}=yaml") @Configuration public class SupplierConfiguration { @Inject @@ -27,16 +28,25 @@ public class SupplierConfiguration { context.cfg().getProp(prefix).bindTo(obj); return obj; } + @Bean - protected Map> blends(){ - return injectObj("sms.blends",new LinkedHashMap<>()); + protected Map> blends() { + return injectObj("sms.blends", new LinkedHashMap<>()); } @Bean - protected SmsBlendsInitializer smsBlendsInitializer(List> factoryList, + protected SmsBlendsInitializer smsBlendsInitializer(List factoryList, SmsConfig smsConfig, - Map> blends){ - return new SmsBlendsInitializer(factoryList,smsConfig,blends,context); + Map> blends) { + + //todo: solon 不支持泛型的 List[Bean] 注入 + List> factoryList2 = new ArrayList<>(factoryList.size()); + for (BaseProviderFactory factory : factoryList) { + factoryList2.add((BaseProviderFactory) factory); + } + + + return new SmsBlendsInitializer(factoryList2, smsConfig, blends, context); } } diff --git a/sms4j-spring-boot-example/src/main/java/org/dromara/sms4j/example/zhangjun/ZhangJunSmsImpl.java b/sms4j-spring-boot-example/src/main/java/org/dromara/sms4j/example/zhangjun/ZhangJunSmsImpl.java index 55f9192b..bcb6ef29 100644 --- a/sms4j-spring-boot-example/src/main/java/org/dromara/sms4j/example/zhangjun/ZhangJunSmsImpl.java +++ b/sms4j-spring-boot-example/src/main/java/org/dromara/sms4j/example/zhangjun/ZhangJunSmsImpl.java @@ -4,6 +4,7 @@ import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import lombok.extern.slf4j.Slf4j; import org.dromara.sms4j.api.entity.SmsResponse; +import org.dromara.sms4j.api.utils.SmsRespUtils; import org.dromara.sms4j.comm.delayedTime.DelayedTime; import org.dromara.sms4j.comm.exception.SmsBlendException; import org.dromara.sms4j.comm.utils.SmsUtils; @@ -82,7 +83,7 @@ public class ZhangJunSmsImpl extends AbstractSmsBlend { @Override public SmsResponse massTexting(List phones, String templateId, LinkedHashMap messages) { String messageStr = JSONUtil.toJsonStr(messages); - return getSmsResponse(SmsUtils.arrayToString(phones), messageStr, templateId); + return getSmsResponse(SmsUtils.addCodePrefixIfNot(phones), messageStr, templateId); } private SmsResponse getSmsResponse(String phone, String message, String templateId) { @@ -90,9 +91,7 @@ public class ZhangJunSmsImpl extends AbstractSmsBlend { try { smsResponse = getResponse(http.postJson(getConfig().getUrl(), null, message)); } catch (SmsBlendException e) { - smsResponse = new SmsResponse(); - smsResponse.setSuccess(false); - smsResponse.setData(e.getMessage()); + smsResponse = errorResp(e.message); } if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) { retry = 0; @@ -109,11 +108,7 @@ public class ZhangJunSmsImpl extends AbstractSmsBlend { } private SmsResponse getResponse(JSONObject resJson) { - SmsResponse smsResponse = new SmsResponse(); - smsResponse.setSuccess("OK".equals(resJson.getStr("Code"))); - smsResponse.setData(resJson); - smsResponse.setConfigId(getConfigId()); - return smsResponse; + return SmsRespUtils.resp(resJson, "OK".equals(resJson.getStr("Code")), getConfigId()); } } \ No newline at end of file diff --git a/sms4j-spring-boot-example/src/main/resources/application.yml b/sms4j-spring-boot-example/src/main/resources/application.yml index a7b89ab2..1780baae 100644 --- a/sms4j-spring-boot-example/src/main/resources/application.yml +++ b/sms4j-spring-boot-example/src/main/resources/application.yml @@ -4,114 +4,157 @@ sms: # 账户上限 account-max: 1 blends: - alibaba: # 阿里短信例子 - ali1: - #厂商标识,标定此配置是哪个厂商,详细请看厂商标识介绍部分 - supplier: alibaba - #您的accessKey - access-key-id: 您的accessKey - #您的accessKeySecret - access-key-secret: 您的accessKeySecret - #您的短信签名 - signature: 测试签名 - #模板ID 非必须配置,如果使用sendMessage的快速发送需此配置 - template-id: SMS_272470496 - # 模版名称 - templateName: code - ali2: - #厂商标识,标定此配置是哪个厂商,详细请看厂商标识介绍部分 - supplier: alibaba - #您的accessKey - access-key-id: 您的accessKey - #您的accessKeySecret - access-key-secret: 您的accessKeySecret - #您的短信签名 - signature: 测试签名 - #模板ID 非必须配置,如果使用sendMessage的快速发送需此配置 - template-id: SMS_272470496 - # 模版名称 - templateName: code - # 腾讯短信例子: - tencent: - tx: - #厂商标识 - supplier: tencent - #您的accessKey - access-key-id: 您的accessKey - #您的accessKeySecret - access-key-secret: 您的accessKeySecret - #您的短信签名 - signature: 测试签名 - #模板ID - template-id: 1603670 - #您的sdkAppId - sdk-app-id: 1400761645 - # 华为短信例子 - huawei: - hw: - #厂商标识 - supplier: huawei - #您的accessKey - access-key-id: 您的accessKey - #您的accessKeySecret - access-key-secret: 您的accessKeySecret - #您的短信签名 - signature: 测试签名 - #模板ID - template-id: ac4888205c274b2a8263479b954c1ab5 - # APP接入地址 - url: https://smsapi.cn-north-4.myhuaweicloud.com:443 - # 模版名称 - templateName: code - # 通道号 - sender: 8823040504797 - # 合一短信例子 - unisms: - uni: - #厂商标识 - supplier: unisms - #您的accessKey - access-key-id: 您的accessKey - #您的短信签名 - signature: 测试签名 - #模板ID - template-id: pub_verif_short - # 模版名称 - templateName: code - # 渠道上限 - maximum: 2 + ali: + #厂商标识,标定此配置是哪个厂商,详细请看厂商标识介绍部分 + supplier: alibaba + #您的accessKey + access-key-id: 您的accessKey + #您的accessKeySecret + access-key-secret: 您的accessKeySecret + #您的短信签名 + signature: 测试签名 + #模板ID 非必须配置,如果使用sendMessage的快速发送需此配置 + template-id: SMS_272470496 + # 模版名称 + templateName: code + # 腾讯短信例子 + tx: + #厂商标识 + supplier: tencent + #您的accessKey + access-key-id: 您的accessKey + #您的accessKeySecret + access-key-secret: 您的accessKeySecret + #您的短信签名 + signature: 测试签名 + #模板ID + template-id: 1603670 + #您的sdkAppId + sdk-app-id: 1400761645 + # 华为短信例子 + hw: + #厂商标识 + supplier: huawei + #您的accessKey + access-key-id: 您的accessKey + #您的accessKeySecret + access-key-secret: 您的accessKeySecret + #您的短信签名 + signature: 测试签名 + #模板ID + template-id: ac4888205c274b2a8263479b954c1ab5 + # APP接入地址 + url: https://smsapi.cn-north-4.myhuaweicloud.com:443 + # 模版名称 + templateName: code + # 通道号 + sender: 8823040504797 + # 合一短信例子 + uni: + #厂商标识 + supplier: unisms + #您的accessKey + access-key-id: 您的accessKey + #您的短信签名 + signature: 测试签名 + #模板ID + template-id: pub_verif_short + # 模版名称 + templateName: code + # 渠道上限 + maximum: 2 lianlu: - lianlu: - supplier: lianlu - templateId: 模板id - appId: 100116 - appKey: d42d7 - mchId: 100 - signName: 【test】 + supplier: lianlu + templateId: 模板id + appId: 100116 + appKey: d42d7 + mchId: 100 + signName: 【test】 cloopen: - cloopen: - # 短信厂商 - supplier: cloopen - base-url: https://app.cloopen.com:8883/2013-12-26 - access-key-id: 你的Access Key - access-key-secret: 你的Access Key Secret - sdkAppId: 你的应用ID - #自定义广州掌骏短信,添加factory全路径。config,factory,SmsImpl复制其他默认实现即可,修改对应的supplier和发送核心逻辑即可 - # zhangjun: - # supplier: zhangjun - # factory: org.dromara.sms4j.example.zhangjun.ZhangJunFactory - # templateId: d2a****777 - # appId: 64c52d2a****77775fe72e3 - # sid: d2a****777 - # url: https://sms.idowe.com/**/**/**/send + # 短信厂商 + supplier: cloopen + base-url: https://app.cloopen.com:8883/2013-12-26 + access-key-id: 你的Access Key + access-key-secret: 你的Access Key Secret + sdkAppId: 你的应用ID + #自定义广州掌骏短信,添加factory全路径。config,factory,SmsImpl复制其他默认实现即可,修改对应的supplier和发送核心逻辑即可 +# zhangjun: +# supplier: zhangjun +# factory: org.dromara.sms4j.example.zhangjun.ZhangJunFactory +# templateId: d2a****777 +# appId: 64c52d2a****77775fe72e3 +# sid: d2a****777 +# url: https://sms.idowe.com/**/**/**/send qiniu: - qiniu: - access-key-id: EQcDflLTCYnU1******CmqIYLhog1lkWHb2 - access-key-secret: NeS2ptvZQoIy*****err2DdLe7wxFfQvji1 - templateId: 1752130****15859456 - signatureId: 175185*****1624960 - templateName: code + access-key-id: EQcDflLTCYnU1******CmqIYLhog1lkWHb2 + access-key-secret: NeS2ptvZQoIy*****err2DdLe7wxFfQvji1 + templateId: 1752130****15859456 + signatureId: 175185*****1624960 + templateName: code + # 中国移动 云MAS + mas: + supplier: mas + # 请求方式默认为 HTTP + # 请求方法 默认为 HTTP模式下的 tmpsubmit + # norsubmit 无模板接口 不需要配置templateId + # tmpsubmit 有模板接口 需要配置templateId + # HTTPS 模式下 请求方法有 submit tmpsubmit + action: tmpsubmit + # 请求地址 HTTP模式下可不配置 默认为 http://112.35.1.155:1992/sms/ + # HTTPS模式下 请设置 https://****:****/sms/ + request-url: http://112.35.1.155:1992/sms/ + sdk-app-id: 接口账号用户名 + access-key-secret: 用户密码 + ec-name: 企业名称 + signature: 签名编码 + # 当请求方法为 tmpsubmit 时 需要配置templateId + template-id: + # 可为空 不为空时请遵守中国移动云MAS开发文档中的描述[服务代码加扩展码总长度不能超过20位。] + add-serial: + # 百度智能云 sms + baidu: + access-key-id: 访问密钥ID + access-key-secret: 用户密钥 + ec-name: 企业名称 + signature: 签名编码 + template-id: 模板ID + # 模板变量名称 + template-name: code + custom: 用户自定义参数,格式为字符串,状态回调时会回传该值 可不传 + user-ext-id: 通道自定义扩展码 可不传 + # 创蓝 + chuanglan: + access-key-id: 111111 + access-key-secret: 111111 + templateId: 【253云通讯】{$var}您的验证码是:{$var},{$var}分钟内有效 + # 极光 + jiguang: + supplier: jiguang + signId: 签名 ID,该字段为空则使用应用默认签名 + action: 默认请求方法 messages + templateName: 模板变量名称 + voice: action设置为voice_codes有效 语音验证码播报语言选择,0:中文播报,1:英文播报,2:中英混合播报 + ttl: action设置为voice_codes有效 验证码有效期,默认为 60 秒 + tag: action设置为messages/batch有效 标签 + # 螺丝帽 + luosimao: + accessKeyId: 后台提取的API key + action: 默认请求方法 send.json + # submail + submail: + accessKeyId: APPID + accessKeySecret: APPKEY + action: 默认请求方法 send.json + signType: MD5 或 SHA-1 默认MD5 填写任意值,不为即为 密匙明文验证模式 + signVersion: signature加密计算方式 为2时会忽略某些字段 + templateId: 模板ID + signature: 签名 + # danmi + danmi: + accessKeyId: ACCOUNT SID + accessKeySecret: AUTH TOKEN + action: 默认请求方法 distributor/sendSMS sms-oa: config-type: yaml diff --git a/sms4j-spring-boot-example/src/test/java/org/dromara/sms4j/example/Sms4jTest.java b/sms4j-spring-boot-example/src/test/java/org/dromara/sms4j/example/Sms4jTest.java index 9323ee71..ebeef4b7 100644 --- a/sms4j-spring-boot-example/src/test/java/org/dromara/sms4j/example/Sms4jTest.java +++ b/sms4j-spring-boot-example/src/test/java/org/dromara/sms4j/example/Sms4jTest.java @@ -2,26 +2,29 @@ package org.dromara.sms4j.example; import cn.hutool.core.collection.ListUtil; import cn.hutool.core.lang.Assert; +import cn.hutool.core.lang.UUID; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONUtil; import lombok.extern.slf4j.Slf4j; import org.dromara.sms4j.api.SmsBlend; import org.dromara.sms4j.api.entity.SmsResponse; +import org.dromara.sms4j.baidu.service.BaiduSmsImpl; import org.dromara.sms4j.comm.constant.SupplierConstant; import org.dromara.sms4j.comm.utils.SmsUtils; import org.dromara.sms4j.core.factory.SmsFactory; +import org.dromara.sms4j.danmi.service.DanMiSmsImpl; +import org.dromara.sms4j.jg.service.JgSmsImpl; import org.dromara.sms4j.lianlu.service.LianLuSmsImpl; +import org.dromara.sms4j.luosimao.service.LuoSiMaoSmsImpl; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.Map; +import java.util.*; @Slf4j @SpringBootTest -class Sms4jTest { +public class Sms4jTest { /** * 填测试手机号 @@ -278,4 +281,249 @@ class Sms4jTest { } -} \ No newline at end of file + /** + * 中国移动 云MAS + */ + @Test + public void masSmsTest() { + if (StrUtil.isBlank(PHONE)) { + return; + } + // 发送一对一/一对多普通短信 + // HTTP模式下 action请配置为 norsubmit + // HTTPS模式下 action请配置为 submit + SmsResponse oneToMany = SmsFactory.getBySupplier(SupplierConstant.MAS) + .sendMessage(PHONE, "测试短信" + SmsUtils.getRandomInt(6)); + log.info(JSONUtil.toJsonStr(oneToMany)); + + // 发送多对多普通短信 + // HTTP模式下 action请配置为 norsubmit + // HTTPS模式下 action请配置为 submit + LinkedHashMap content = new LinkedHashMap<>(); + content.put("18***1", "测试短信1"); + content.put("18***2", "测试短信2"); + content.put("18***3", "测试短信3"); + content.put("18***4", "测试短信4"); + SmsResponse manyToMany1 = SmsFactory.getBySupplier(SupplierConstant.MAS) + .sendMessage(PHONE, content); + log.info(JSONUtil.toJsonStr(manyToMany1)); + + // 或者 + SmsResponse manyToMany2 = SmsFactory.getBySupplier(SupplierConstant.MAS) + .sendMessage(PHONE, JSONUtil.toJsonStr(content)); + log.info(JSONUtil.toJsonStr(manyToMany2)); + + // 发送模板短信 + // HTTP模式下或者HTTPS模式下 action请都配置为 tmpsubmit + // 无参数 + SmsResponse strRes = SmsFactory.getBySupplier(SupplierConstant.MAS) + .sendMessage(PHONE, StrUtil.EMPTY); + log.info(JSONUtil.toJsonStr(strRes)); + + //数组格式 + String[] paramsArr = {"param1", "param2"}; + SmsResponse arrRes = SmsFactory.getBySupplier(SupplierConstant.MAS) + .sendMessage(PHONE, JSONUtil.toJsonStr(paramsArr)); + log.info(JSONUtil.toJsonStr(arrRes)); + + //list格式 + List paramsList = new ArrayList<>(); + paramsList.add("param1"); + paramsList.add("param2"); + SmsResponse listRes = SmsFactory.getBySupplier(SupplierConstant.MAS) + .sendMessage(PHONE, JSONUtil.toJsonStr(paramsList)); + log.info(JSONUtil.toJsonStr(listRes)); + } + + /** + * 百度短信 + */ + @Test + public void baiduSmsTest() { + if (StrUtil.isBlank(PHONE)) { + return; + } + + // 发送短信 + SmsResponse resp = SmsFactory.getBySupplier(SupplierConstant.BAIDU) + .sendMessage(PHONE, SmsUtils.getRandomInt(6)); + log.info(JSONUtil.toJsonStr(resp)); + + // 发送携带幂等性参数短信 + BaiduSmsImpl sendWithClientToken = (BaiduSmsImpl) SmsFactory.getBySupplier(SupplierConstant.BAIDU); + String clientToken = UUID.fastUUID().toString(true); + SmsResponse respWithClientToken = sendWithClientToken.sendMessageWithClientToken(PHONE, + SmsUtils.getRandomInt(6), + clientToken); + log.info(JSONUtil.toJsonStr(respWithClientToken)); + } + + /** + * 创蓝短信 + */ + @Test + public void chungLanTest() { + if (StrUtil.isBlank(PHONE)) { + return; + } + + //测试群发【模板】 + List arrayList = new ArrayList<>(); + arrayList.add(PHONE); + arrayList.add("135****000"); + LinkedHashMap map = new LinkedHashMap<>(); + map.put("1", "1544"); + map.put("2", "2222"); + SmsResponse smsResponse2 = SmsFactory.getBySupplier(SupplierConstant.CHUANGLAN).massTexting(arrayList, "[test]你的验证码是{$val},{$val}", map); + log.info("smsResponse2:{}", smsResponse2); + + //测试单条发送 + SmsResponse smsResponse1 = SmsFactory.getBySupplier(SupplierConstant.CHUANGLAN).sendMessage(PHONE, "1544&2222"); + log.info("smsResponse1:{}", smsResponse1); + + //测试单条模板发送 + LinkedHashMap map3 = new LinkedHashMap<>(); + map3.put("1", "1544"); + map3.put("2", "2222"); + SmsResponse smsResponse3 = SmsFactory.getBySupplier(SupplierConstant.CHUANGLAN).sendMessage(PHONE, "[test]你的验证码是{$val},{$val}", map3); + log.info("smsResponse3:{}", smsResponse3); + } + + /** + * 极光短信 + */ + @Test + public void jgSmsTest() { + // 极光 发送文本验证码短信 API 不需要传入具体验证码 返回 {"msg_id": "288193860302"} + SmsResponse smsResponse1 = SmsFactory.getBySupplier(SupplierConstant.JIGUANG).sendMessage(PHONE, ""); + Assert.isTrue(smsResponse1.isSuccess()); + + // 极光 发送语音验证码短信 请确保action配置为voice_codes + JgSmsImpl voiceCode = (JgSmsImpl) SmsFactory.getBySupplier(SupplierConstant.JIGUANG); + SmsResponse voiceResp = voiceCode.sendVoiceCode(PHONE, + SmsUtils.getRandomInt(6)); + Assert.isTrue(voiceResp.isSuccess()); + + // 验证验证码是否有效 请确保action配置为voice_codes + JgSmsImpl verify = (JgSmsImpl) SmsFactory.getBySupplier(SupplierConstant.JIGUANG); + SmsResponse verifyResp = verify.verifyCode("123456", "288193860302"); + Assert.isTrue(verifyResp.isSuccess()); + + // 极光 发送单条模板短信 API 发送自定义验证码 sendTemplateSMS + LinkedHashMap map1 = new LinkedHashMap<>(); + map1.put("code", "123456"); + SmsResponse smsResponse2 = SmsFactory.getBySupplier(SupplierConstant.JIGUANG).sendMessage(PHONE, map1); + Assert.isTrue(smsResponse2.isSuccess()); + + // 极光 发送单条模板短信 API 发送多参数自定义模板短信 sendTemplateSMS_with_multipleParameters + LinkedHashMap map2 = new LinkedHashMap<>(); + map2.put("name", "test"); + map2.put("password", "test"); + SmsResponse smsResponse3 = SmsFactory.getBySupplier(SupplierConstant.JIGUANG).sendMessage(PHONE, "226992", map2); + Assert.isTrue(smsResponse3.isSuccess()); + + // sendBatchTemplateSMS + LinkedHashMap map3 = new LinkedHashMap<>(); + map3.put("name", "test"); + map3.put("password", "test"); + List phones = new ArrayList<>(); + phones.add(PHONE); + phones.add("xxx"); + SmsResponse smsResponse4 = SmsFactory.getBySupplier(SupplierConstant.JIGUANG).massTexting(phones, "226992", map3); + Assert.isTrue(smsResponse4.isSuccess()); + } + + /** + * 螺丝帽短信 + */ + @Test + public void luosimaoSmsTest() { + // 螺丝帽 发送短信接口详细 发送短信接口详细 send.json + SmsResponse smsResponse1 = SmsFactory.getBySupplier(SupplierConstant.LUO_SI_MAO).sendMessage(PHONE, ""); + Assert.isTrue(smsResponse1.isSuccess()); + + // 螺丝帽 批量发送接口详细 send_batch.json + SmsResponse smsResponse2 = SmsFactory.getBySupplier(SupplierConstant.LUO_SI_MAO).massTexting(Collections.singletonList(PHONE), ""); + Assert.isTrue(smsResponse2.isSuccess()); + + // 螺丝帽 定时批量发送接口详细 send_batch.json + LuoSiMaoSmsImpl luoSiMao = (LuoSiMaoSmsImpl) SmsFactory.getBySupplier(SupplierConstant.LUO_SI_MAO); + SmsResponse smsResponse3 = luoSiMao.massTextingOnTime(Collections.singletonList(PHONE), "", new Date()); + Assert.isTrue(smsResponse3.isSuccess()); + + // 螺丝帽 查询账户余额 status.json + SmsResponse smsResponse4 = luoSiMao.queryAccountBalance(); + Assert.isTrue(smsResponse4.isSuccess()); + } + + /** + * SUBMAIL短信 + */ + @Test + public void mysubmailSmsTest() { + // 短信发送 send.json 【xxxx】签名可配置 系统自动带入 + SmsResponse smsResponse1 = SmsFactory.getBySupplier(SupplierConstant.MY_SUBMAIL).sendMessage(PHONE, "【SUBMAIL】你好,你的验证码是2257"); + Assert.isTrue(smsResponse1.isSuccess()); + + // 短信模板发送 xsend.json + LinkedHashMap vars = new LinkedHashMap<>(); + vars.put("code", "123456"); + SmsResponse smsResponse2 = SmsFactory.getBySupplier(SupplierConstant.MY_SUBMAIL).sendMessage(PHONE, "xxx", vars); + Assert.isTrue(smsResponse2.isSuccess()); + + // 短信一对多发送 multisend.json 【xxxx】签名可配置 系统自动带入 content字段说明:短信正文 案例没有说明无需传 + LinkedHashMap vars1 = new LinkedHashMap<>(); + vars1.put("content", "【SUBMAIL】您的短信验证码:4438,请在10分钟内输入。"); + vars1.put("code", "123456"); + vars1.put("time", "10"); + SmsResponse smsResponse3 = SmsFactory.getBySupplier(SupplierConstant.MY_SUBMAIL).massTexting(Collections.singletonList(PHONE), JSONUtil.toJsonStr(vars1)); + Assert.isTrue(smsResponse3.isSuccess()); + + // 短信模板一对多发送 multixsend.json + LinkedHashMap vars2 = new LinkedHashMap<>(); + vars2.put("code", "123456"); + SmsResponse smsResponse4 = SmsFactory.getBySupplier(SupplierConstant.MY_SUBMAIL).massTexting(Collections.singletonList(PHONE), "xxx", vars2); + Assert.isTrue(smsResponse4.isSuccess()); + + // 短信批量群发 batchsend.json 【xxxx】签名可配置 系统自动带入 content字段说明:短信正文 案例没有说明无需传 + LinkedHashMap vars3 = new LinkedHashMap<>(); + vars3.put("content", "123456"); + SmsResponse smsResponse5 = SmsFactory.getBySupplier(SupplierConstant.MY_SUBMAIL).massTexting(Collections.singletonList(PHONE), JSONUtil.toJsonStr(vars3)); + Assert.isTrue(smsResponse5.isSuccess()); + + // 短信批量模板群发 batchxsend.json + LinkedHashMap vars4 = new LinkedHashMap<>(); + vars4.put("code", "123456"); + vars4.put("time", "10"); + SmsResponse smsResponse6 = SmsFactory.getBySupplier(SupplierConstant.MY_SUBMAIL).massTexting(Collections.singletonList(PHONE), "xxx", vars4); + Assert.isTrue(smsResponse6.isSuccess()); + } + + /** + * danmi短信 + */ + @Test + public void danmiSmsTest() { + // 短信发送 distributor/sendSMS 群发 massTexting + SmsResponse smsResponse1 = SmsFactory.getBySupplier(SupplierConstant.DAN_MI).sendMessage(PHONE, "【danmi】你好,你的验证码是666"); + Assert.isTrue(smsResponse1.isSuccess()); + + DanMiSmsImpl danMiSms = (DanMiSmsImpl) SmsFactory.getBySupplier(SupplierConstant.DAN_MI); + // 短信余额查询 distributor/user/query + SmsResponse smsResponse2 = danMiSms.queryBalance(); + Assert.isTrue(smsResponse2.isSuccess()); + + // 语音验证码发送 voice/voiceCode + SmsResponse smsResponse3 = danMiSms.voiceCode(PHONE, "666"); + Assert.isTrue(smsResponse3.isSuccess()); + + // 语音通知文件发送 voice/voiceNotify + SmsResponse smsResponse4 = danMiSms.voiceNotify(PHONE, "sjkhduiq"); + Assert.isTrue(smsResponse4.isSuccess()); + + // 语音模板通知发送 voice/voiceTemplate + SmsResponse smsResponse5 = danMiSms.voiceTemplate(PHONE, "opipedlqza", "111,222,333"); + Assert.isTrue(smsResponse5.isSuccess()); + } + +} diff --git a/sms4j-spring-boot-example/src/test/java/org/dromara/sms4j/example/SmsUtilsTest.java b/sms4j-spring-boot-example/src/test/java/org/dromara/sms4j/example/SmsUtilsTest.java index c72e30c9..aa458ce4 100644 --- a/sms4j-spring-boot-example/src/test/java/org/dromara/sms4j/example/SmsUtilsTest.java +++ b/sms4j-spring-boot-example/src/test/java/org/dromara/sms4j/example/SmsUtilsTest.java @@ -68,31 +68,31 @@ public class SmsUtilsTest { } @Test - public void listToString() { + public void joinComma() { List list = new ArrayList<>(); list.add("12312341234"); list.add("12312341235"); - String str = SmsUtils.listToString(list); + String str = SmsUtils.joinComma(list); log.info(str); Assert.isTrue(str.equals("12312341234,12312341235")); } @Test - public void arrayToString() { + public void addCodePrefixIfNot() { List list = new ArrayList<>(); list.add("12312341234"); list.add("12312341235"); - String str = SmsUtils.arrayToString(list); + String str = SmsUtils.addCodePrefixIfNot(list); log.info(str); Assert.isTrue(str.equals("+8612312341234,+8612312341235")); } @Test - public void listToArray() { + public void addCodePrefixIfNotToArray() { List list = new ArrayList<>(); list.add("12312341234"); list.add("12312341235"); - String[] str = SmsUtils.listToArray(list); + String[] str = SmsUtils.addCodePrefixIfNotToArray(list); Assert.isTrue(str[0].equals("+8612312341234") && str[1].equals("+8612312341235")); } diff --git a/sms4j-spring-boot-starter/src/main/java/org/dromara/sms4j/starter/adepter/ConfigCombineMapAdeptor.java b/sms4j-spring-boot-starter/src/main/java/org/dromara/sms4j/starter/adepter/ConfigCombineMapAdaptor.java similarity index 96% rename from sms4j-spring-boot-starter/src/main/java/org/dromara/sms4j/starter/adepter/ConfigCombineMapAdeptor.java rename to sms4j-spring-boot-starter/src/main/java/org/dromara/sms4j/starter/adepter/ConfigCombineMapAdaptor.java index 34bdf581..1320e42f 100644 --- a/sms4j-spring-boot-starter/src/main/java/org/dromara/sms4j/starter/adepter/ConfigCombineMapAdeptor.java +++ b/sms4j-spring-boot-starter/src/main/java/org/dromara/sms4j/starter/adepter/ConfigCombineMapAdaptor.java @@ -10,7 +10,7 @@ import java.util.List; import java.util.Map; import java.util.Set; -public class ConfigCombineMapAdeptor extends HashMap { +public class ConfigCombineMapAdaptor extends HashMap { @Override public M get(Object key) { Object o = super.get(key); @@ -41,6 +41,4 @@ public class ConfigCombineMapAdeptor extends HashMap { } return (M)o; } - - } diff --git a/sms4j-spring-boot-starter/src/main/java/org/dromara/sms4j/starter/configration/SmsBlendsInitializer.java b/sms4j-spring-boot-starter/src/main/java/org/dromara/sms4j/starter/configration/SmsBlendsInitializer.java index 0190d14a..057d8a6c 100644 --- a/sms4j-spring-boot-starter/src/main/java/org/dromara/sms4j/starter/configration/SmsBlendsInitializer.java +++ b/sms4j-spring-boot-starter/src/main/java/org/dromara/sms4j/starter/configration/SmsBlendsInitializer.java @@ -7,10 +7,12 @@ import lombok.extern.slf4j.Slf4j; import org.dromara.sms4j.aliyun.config.AlibabaFactory; import org.dromara.sms4j.api.SmsBlend; import org.dromara.sms4j.api.universal.SupplierConfig; +import org.dromara.sms4j.baidu.config.BaiduFactory; +import org.dromara.sms4j.budingyun.config.BudingV2Factory; import org.dromara.sms4j.api.verify.PhoneVerify; import org.dromara.sms4j.cloopen.config.CloopenFactory; import org.dromara.sms4j.comm.constant.Constant; -import org.dromara.sms4j.comm.enumerate.ConfigType; +import org.dromara.sms4j.comm.enums.ConfigType; import org.dromara.sms4j.comm.utils.SmsUtils; import org.dromara.sms4j.core.datainterface.SmsReadConfig; import org.dromara.sms4j.core.factory.SmsFactory; @@ -22,17 +24,23 @@ import org.dromara.sms4j.core.proxy.processor.CoreMethodParamValidateProcessor; import org.dromara.sms4j.core.proxy.processor.RestrictedProcessor; import org.dromara.sms4j.core.proxy.processor.SingleBlendRestrictedProcessor; import org.dromara.sms4j.ctyun.config.CtyunFactory; +import org.dromara.sms4j.danmi.config.DanMiFactory; import org.dromara.sms4j.dingzhong.config.DingZhongFactory; import org.dromara.sms4j.emay.config.EmayFactory; import org.dromara.sms4j.huawei.config.HuaweiFactory; import org.dromara.sms4j.jdcloud.config.JdCloudFactory; +import org.dromara.sms4j.chuanglan.config.ChuangLanFactory; +import org.dromara.sms4j.jg.config.JgFactory; import org.dromara.sms4j.lianlu.config.LianLuFactory; +import org.dromara.sms4j.luosimao.config.LuoSiMaoFactory; +import org.dromara.sms4j.mas.config.MasFactory; import org.dromara.sms4j.netease.config.NeteaseFactory; import org.dromara.sms4j.provider.config.SmsConfig; import org.dromara.sms4j.provider.factory.BaseProviderFactory; import org.dromara.sms4j.provider.factory.ProviderFactoryHolder; import org.dromara.sms4j.qiniu.config.QiNiuFactory; -import org.dromara.sms4j.starter.adepter.ConfigCombineMapAdeptor; +import org.dromara.sms4j.starter.adepter.ConfigCombineMapAdaptor; +import org.dromara.sms4j.submail.config.SubMailFactory; import org.dromara.sms4j.tencent.config.TencentFactory; import org.dromara.sms4j.unisms.config.UniFactory; import org.dromara.sms4j.yunpian.config.YunPianFactory; @@ -71,7 +79,7 @@ public class SmsBlendsInitializer { if (ConfigType.YAML.equals(this.smsConfig.getConfigType())) { //持有初始化配置信息 - Map> blendsInclude = new ConfigCombineMapAdeptor>(); + Map> blendsInclude = new ConfigCombineMapAdaptor>(); blendsInclude.putAll(this.blends); int num = 0; for (SmsReadConfig smsReadConfig : extendsSmsConfigs) { @@ -90,10 +98,10 @@ public class SmsBlendsInitializer { //如果手机号校验器存在实现,则注册手机号校验器 ServiceLoader loader = ServiceLoader.load(PhoneVerify.class); if (loader.iterator().hasNext()) { - loader.forEach(f->{ + loader.forEach(f -> { SmsProxyFactory.addProcessor(new CoreMethodParamValidateProcessor(f)); }); - }else { + } else { SmsProxyFactory.addProcessor(new CoreMethodParamValidateProcessor(null)); } @@ -109,7 +117,7 @@ public class SmsBlendsInitializer { continue; } configMap.put("config-id", configId); - SmsUtils.replaceKeysSeperator(configMap, "-", "_"); + SmsUtils.replaceKeysSeparator(configMap, "-", "_"); JSONObject configJson = new JSONObject(configMap); org.dromara.sms4j.api.universal.SupplierConfig supplierConfig = JSONUtil.toBean(configJson, providerFactory.getConfigClass()); SmsFactory.createSmsBlend(supplierConfig); @@ -136,10 +144,20 @@ public class SmsBlendsInitializer { ProviderFactoryHolder.registerFactory(LianLuFactory.instance()); ProviderFactoryHolder.registerFactory(DingZhongFactory.instance()); ProviderFactoryHolder.registerFactory(QiNiuFactory.instance()); + ProviderFactoryHolder.registerFactory(ChuangLanFactory.instance()); + ProviderFactoryHolder.registerFactory(JgFactory.instance()); + ProviderFactoryHolder.registerFactory(BudingV2Factory.instance()); + ProviderFactoryHolder.registerFactory(MasFactory.instance()); + ProviderFactoryHolder.registerFactory(BaiduFactory.instance()); + ProviderFactoryHolder.registerFactory(LuoSiMaoFactory.instance()); + ProviderFactoryHolder.registerFactory(SubMailFactory.instance()); + ProviderFactoryHolder.registerFactory(DanMiFactory.instance()); if (SmsUtils.isClassExists("com.jdcloud.sdk.auth.CredentialsProvider")) { - ProviderFactoryHolder.registerFactory(JdCloudFactory.instance()); + if (SmsUtils.isClassExists("com.jdcloud.sdk.auth.CredentialsProvider")) { + ProviderFactoryHolder.registerFactory(JdCloudFactory.instance()); + } + log.debug("加载内置运营商完成!"); } - log.debug("加载内置运营商完成!"); - } -} + } +} \ No newline at end of file diff --git a/sms4j-spring-boot-starter/src/main/java/org/dromara/sms4j/starter/configration/SupplierConfiguration.java b/sms4j-spring-boot-starter/src/main/java/org/dromara/sms4j/starter/configration/SupplierConfiguration.java index 278a1f4f..c329f1fe 100644 --- a/sms4j-spring-boot-starter/src/main/java/org/dromara/sms4j/starter/configration/SupplierConfiguration.java +++ b/sms4j-spring-boot-starter/src/main/java/org/dromara/sms4j/starter/configration/SupplierConfiguration.java @@ -6,7 +6,7 @@ import cn.hutool.json.JSONUtil; import lombok.SneakyThrows; import org.dromara.sms4j.api.SmsBlend; import org.dromara.sms4j.comm.constant.Constant; -import org.dromara.sms4j.comm.enumerate.ConfigType; +import org.dromara.sms4j.comm.enums.ConfigType; import org.dromara.sms4j.core.datainterface.SmsReadConfig; import org.dromara.sms4j.provider.config.BaseConfig; import org.dromara.sms4j.provider.config.SmsConfig;