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 super T, ? extends E> 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 extends OaSender, ? extends OaSupplierConfig> 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 extends OaSender, ? extends OaSupplierConfig> 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 extends SmsBlend, ? extends SupplierConfig> 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 extends SmsBlend, ? extends SupplierConfig> 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; + +/** + *
说明:发送固定消息模板多模板参数短信 - * @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 super T, ? extends E> 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 extends OaSender, ? extends OaSupplierConfig> 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 extends OaSender, ? extends OaSupplierConfig> 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 extends SmsBlend, ? extends SupplierConfig> 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 extends SmsBlend, ? extends SupplierConfig> 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; + +/** + *
类名: 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 super T, ? extends E> 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 extends OaSender, ? extends OaSupplierConfig> 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 extends OaSender, ? extends OaSupplierConfig> 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 extends SmsBlend, ? extends SupplierConfig> 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 extends SmsBlend, ? extends SupplierConfig> 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; + +/** + *
类名: 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 extends SmsBlend, ? extends SupplierConfig> 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 extends SmsBlend, ? extends SupplierConfig> 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; + +/** + *
建造一个短信实现对像 + * + * @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 extends SmsBlend, ? extends SupplierConfig> 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 extends SmsBlend, ? extends SupplierConfig> 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; + +/** + *
布丁云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 extends SmsBlend, ? extends SupplierConfig> 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 extends SmsBlend, ? extends SupplierConfig> 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; + +/** + *
类名: 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 extends SmsBlend, ? extends SupplierConfig> 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 extends SmsBlend, ? extends SupplierConfig> 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; + +/** + *
类名: 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 extends SmsBlend, ? extends SupplierConfig> 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 extends SmsBlend, ? extends SupplierConfig> 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; + +/** + *
类名: 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 extends SmsBlend, ? extends SupplierConfig> 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 extends SmsBlend, ? extends SupplierConfig> 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; + +/** + *
建造一个短信实现对像 + * + * @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 extends SmsBlend, ? extends SupplierConfig> 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 extends SmsBlend, ? extends SupplierConfig> 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; + +/** + *
类名: 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 extends SmsBlend, ? extends SupplierConfig> 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 extends SmsBlend, ? extends SupplierConfig> 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; + +/** + *
类名: 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 extends SmsBlend, ? extends SupplierConfig> 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 extends SmsBlend, ? extends SupplierConfig> 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; + +/** + *
类名: 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 extends SmsBlend, ? extends SupplierConfig> 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 extends SmsBlend, ? extends SupplierConfig> 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; + +/** + *
类名: 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 extends SmsBlend, ? extends SupplierConfig> 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 extends SmsBlend, ? extends SupplierConfig> 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; + +/** + *
类名: 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 extends SmsBlend, ? extends SupplierConfig> 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 extends SmsBlend, ? extends SupplierConfig> 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; + +/** + *
建造一个短信实现对像 + * + * @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 extends SmsBlend, ? extends SupplierConfig> 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 extends SmsBlend, ? extends SupplierConfig> 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; + +/** + *
类名: 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 extends SmsBlend, ? extends SupplierConfig> 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 extends SmsBlend, ? extends SupplierConfig> 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; + +/** + *
建造一个短信实现对像 + * + * @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 extends SmsBlend, ? extends SupplierConfig> 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 extends SmsBlend, ? extends SupplierConfig> 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; + +/** + *