Merge branch 'refs/heads/dev-3.0.x'

# Conflicts:
#	pom.xml
#	sms4j-comm/src/main/java/org/dromara/sms4j/comm/constant/SupplierConstant.java
#	sms4j-javase-plugin/src/main/java/org/dromara/sms4j/javase/config/SEInitializer.java
#	sms4j-solon-plugin/src/main/java/org/dromara/sms4j/solon/configuration/SmsBlendsInitializer.java
#	sms4j-spring-boot-example/src/main/resources/application.yml
This commit is contained in:
wind 2024-07-28 20:59:06 +08:00
commit de15297e2c
104 changed files with 5767 additions and 561 deletions

35
pom.xml
View File

@ -17,8 +17,9 @@
<module>sms4j-provider</module> <module>sms4j-provider</module>
<module>sms4j-core</module> <module>sms4j-core</module>
<module>sms4j-spring-boot-starter</module> <module>sms4j-spring-boot-starter</module>
<module>sms4j-solon-plugin</module>
<module>sms4j-spring-boot-example</module> <module>sms4j-spring-boot-example</module>
<module>sms4j-solon-plugin</module>
<module>sms4j-solon-plugin-example</module>
<module>sms4j-javase-plugin</module> <module>sms4j-javase-plugin</module>
<module>sms4j-Email-plugin</module> <module>sms4j-Email-plugin</module>
<module>sms4j-oa-plugin</module> <module>sms4j-oa-plugin</module>
@ -52,7 +53,7 @@
</scm> </scm>
<properties> <properties>
<revision>3.2.1</revision> <revision>3.2.1-SNAPSHOT</revision>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
@ -60,7 +61,7 @@
<solon.version>2.6.5</solon.version> <solon.version>2.6.5</solon.version>
<redisson.version>3.17.0</redisson.version> <redisson.version>3.17.0</redisson.version>
<jdcloud.version>1.3.3</jdcloud.version> <jdcloud.version>1.3.3</jdcloud.version>
<hutool.version>5.8.26</hutool.version> <hutool.version>5.8.28</hutool.version>
<xmlblend.version>2.3.0</xmlblend.version> <xmlblend.version>2.3.0</xmlblend.version>
<activation.version>1.1.1</activation.version> <activation.version>1.1.1</activation.version>
<mail.version>1.6.2</mail.version> <mail.version>1.6.2</mail.version>
@ -242,20 +243,20 @@
</execution> </execution>
</executions> </executions>
</plugin> </plugin>
<!-- GPG --> <!-- &lt;!&ndash; GPG &ndash;&gt;-->
<plugin> <!-- <plugin>-->
<groupId>org.apache.maven.plugins</groupId> <!-- <groupId>org.apache.maven.plugins</groupId>-->
<artifactId>maven-gpg-plugin</artifactId> <!-- <artifactId>maven-gpg-plugin</artifactId>-->
<version>1.6</version> <!-- <version>1.6</version>-->
<executions> <!-- <executions>-->
<execution> <!-- <execution>-->
<phase>verify</phase> <!-- <phase>verify</phase>-->
<goals> <!-- <goals>-->
<goal>sign</goal> <!-- <goal>sign</goal>-->
</goals> <!-- </goals>-->
</execution> <!-- </execution>-->
</executions> <!-- </executions>-->
</plugin> <!-- </plugin>-->
<!--Compiler --> <!--Compiler -->
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>

View File

@ -17,7 +17,7 @@ import java.util.Map;
* 2023/6/8 22:35 * 2023/6/8 22:35
**/ **/
public class MailFactory{ public class MailFactory{
private static final Map<Object,MailSmtpConfig> configs = new HashMap<>(); private static final Map<Object,MailSmtpConfig> CONFIGS = new HashMap<>();
/** /**
* createMailClient * createMailClient
@ -27,7 +27,7 @@ public class MailFactory{
*/ */
public static MailClient createMailClient(Object key){ public static MailClient createMailClient(Object key){
try { try {
return MailBuild.build(configs.get(key)); return MailBuild.build(CONFIGS.get(key));
} catch (MessagingException e) { } catch (MessagingException e) {
throw new MailException(e); throw new MailException(e);
} }
@ -43,7 +43,7 @@ public class MailFactory{
*/ */
public static MailClient createMailClient(Object key, Blacklist blacklist){ public static MailClient createMailClient(Object key, Blacklist blacklist){
try { try {
return MailBuild.build(configs.get(key),blacklist); return MailBuild.build(CONFIGS.get(key),blacklist);
} catch (MessagingException e) { } catch (MessagingException e) {
throw new MailException(e); throw new MailException(e);
} }
@ -57,7 +57,7 @@ public class MailFactory{
* @author :Wind * @author :Wind
*/ */
public static void put(Object key, MailSmtpConfig config){ public static void put(Object key, MailSmtpConfig config){
configs.put(key,config); CONFIGS.put(key,config);
} }
} }

View File

@ -43,12 +43,13 @@ public interface SmsBlend {
SmsResponse sendMessage(String phone, String message); SmsResponse sendMessage(String phone, String message);
/** /**
* sendMessage * sendMessage
* <p>说明发送固定消息模板多模板参数短信 * <p>说明发送固定消息模板多模板参数短信
* @param phone 接收短信的手机号 *
* @param phone 接收短信的手机号
* @param messages 模板内容 * @param messages 模板内容
* @author :Wind * @author :Wind
*/ */
SmsResponse sendMessage(String phone, LinkedHashMap<String, String> messages); SmsResponse sendMessage(String phone, LinkedHashMap<String, String> messages);
/** /**
@ -190,7 +191,7 @@ public interface SmsBlend {
* @param phones 需要加入黑名单的手机号数组 * @param phones 需要加入黑名单的手机号数组
* @author :sh1yu * @author :sh1yu
*/ */
default void batchJoinBlacklist(List<String > phones) { default void batchJoinBlacklist(List<String> phones) {
} }
/** /**
@ -200,6 +201,6 @@ public interface SmsBlend {
* @param phones 需要移除黑名单的手机号数组 * @param phones 需要移除黑名单的手机号数组
* @author :sh1yu * @author :sh1yu
*/ */
default void batchRemovalFromBlacklist(List<String > phones) { default void batchRemovalFromBlacklist(List<String> phones) {
} }
} }

View File

@ -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;
}
}

View File

@ -17,21 +17,45 @@ public abstract class Constant {
* 用于格式化鉴权头域,"Authorization"参数赋值 * 用于格式化鉴权头域,"Authorization"参数赋值
*/ */
public static final String HUAWEI_AUTH_HEADER_VALUE = "WSSE realm=\"SDP\",profile=\"UsernameToken\",type=\"Appkey\""; public static final String HUAWEI_AUTH_HEADER_VALUE = "WSSE realm=\"SDP\",profile=\"UsernameToken\",type=\"Appkey\"";
/** /**
* 用于格式化鉴权头域,"X-WSSE"参数赋值 * 用于格式化鉴权头域,"X-WSSE"参数赋值
*/ */
public static final String HUAWEI_WSSE_HEADER_FORMAT = "UsernameToken Username=\"%s\",PasswordDigest=\"%s\",Nonce=\"%s\",Created=\"%s\""; public static final String HUAWEI_WSSE_HEADER_FORMAT = "UsernameToken Username=\"%s\",PasswordDigest=\"%s\",Nonce=\"%s\",Created=\"%s\"";
/** /**
* 华为云国内短信访问URI * 华为云国内短信访问URI
*/ */
public static final String HUAWEI_REQUEST_URL = "/sms/batchSendSms/v1"; public static final String HUAWEI_REQUEST_URL = "/sms/batchSendSms/v1";
/** /**
* Content-Type * 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"; 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请求前缀 * https请求前缀

View File

@ -10,7 +10,7 @@ public abstract class SupplierConstant {
*/ */
public static final String ALIBABA = "alibaba"; public static final String ALIBABA = "alibaba";
/** /**
* *
*/ */
public static final String CLOOPEN = "cloopen"; public static final String CLOOPEN = "cloopen";
/** /**
@ -49,20 +49,48 @@ public abstract class SupplierConstant {
* 助通 * 助通
*/ */
public static final String ZHUTONG = "zhutong"; public static final String ZHUTONG = "zhutong";
/** /**
* 联麓 * 联麓
*/ */
public static final String LIANLU = "lianlu"; public static final String LIANLU = "lianlu";
/** /**
* 鼎众 * 鼎众
*/ */
public static final String DINGZHONG = "dingzhong"; public static final String DINGZHONG = "dingzhong";
/** /**
* 七牛 * 七牛
*/ */
public static final String QINIU = "qiniu"; 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";
} }

View File

@ -1,4 +1,4 @@
package org.dromara.sms4j.comm.enumerate; package org.dromara.sms4j.comm.enums;
import lombok.Getter; import lombok.Getter;

View File

@ -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;
/**
* <p>类名: SmsDateUtils
* <p>说明 时间日期工具类
*
* @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);
}
}

View File

@ -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<String, String> headers, String username, String password, Map<String, Object> 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传输 * 发送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());
}
}
/** /**
* 线程睡眠 * 线程睡眠
* *

View File

@ -2,15 +2,17 @@ package org.dromara.sms4j.comm.utils;
import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil; 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.ObjectUtil;
import cn.hutool.core.util.RandomUtil; import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil; import cn.hutool.json.JSONUtil;
import java.util.ArrayList; import java.util.*;
import java.util.LinkedHashMap; import java.util.function.Function;
import java.util.List; import java.util.function.Predicate;
import java.util.Map;
/** /**
* @author wind * @author wind
@ -113,18 +115,38 @@ public class SmsUtils {
* @param list 要转换的list * @param list 要转换的list
* @author :Wind * @author :Wind
*/ */
public static String listToString(List<String> list) { public static String joinComma(List<String> list) {
return CollUtil.join(list, ","); return CollUtil.join(list, StrUtil.COMMA);
} }
/** /**
* conjunction 为分隔符将集合转换为字符串 * 切分字符串
* *
* @param list 集合 * @param str 被切分的字符串
* @return 分割后的数据列表
*/
public static List<String> splitTrimComma(String str) {
return StrUtil.splitTrim(str, StrUtil.COMMA);
}
/**
* 将手机号码 添加+86中国的电话国际区号前缀
*
* @param phones 手机号码集合
* @return 结果字符串 * @return 结果字符串
*/ */
public static String arrayToString(List<String> list) { public static String addCodePrefixIfNot(List<String> phones) {
return CollUtil.join(list, ",", str -> StrUtil.addPrefixIfNot(str, "+86")); 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 集合 * @param list 集合
* @return 结果字符串 * @return 结果字符串
*/ */
public static String[] listToArray(List<String> list) { public static String[] addCodePrefixIfNotToArray(List<String> list) {
List<String> toStr = new ArrayList<>(); List<String> toStr = new ArrayList<>();
for (String s : list) { for (String s : list) {
toStr.add(StrUtil.addPrefixIfNot(s, "+86")); toStr.add(addCodePrefixIfNot(s));
} }
return toStr.toArray(new String[list.size()]); return toStr.toArray(new String[list.size()]);
} }
@ -144,20 +166,20 @@ public class SmsUtils {
/** /**
* 将Map中所有key的分隔符转换为新的分隔符 * 将Map中所有key的分隔符转换为新的分隔符
* @param map map对象 * @param map map对象
* @param seperator 旧分隔符 * @param separator 旧分隔符
* @param newSeperator 新分隔符 * @param newSeparator 新分隔符
*/ */
public static void replaceKeysSeperator(Map<String, Object> map, String seperator, String newSeperator) { public static void replaceKeysSeparator(Map<String, Object> map, String separator, String newSeparator) {
if(CollUtil.isEmpty(map)) { if(CollUtil.isEmpty(map)) {
return; return;
} }
List<String> keySet = new ArrayList<>(map.keySet()); List<String> keySet = new ArrayList<>(map.keySet());
for(String key : keySet) { for(String key : keySet) {
if(StrUtil.isEmpty(key) || !key.contains(seperator)) { if(StrUtil.isEmpty(key) || !key.contains(separator)) {
continue; continue;
} }
String value = String.valueOf(map.get(key)); String value = String.valueOf(map.get(key));
String newKey = key.replaceAll(seperator, newSeperator); String newKey = key.replaceAll(separator, newSeparator);
map.putIfAbsent(newKey, value); map.putIfAbsent(newKey, value);
map.remove(key); map.remove(key);
} }
@ -172,4 +194,76 @@ public class SmsUtils {
} }
} }
public static LinkedHashMap<String, String> buildMessageByAmpersand(String message) {
if (isEmpty(message)){
return new LinkedHashMap<>();
}
String[] split = message.split("&");
LinkedHashMap<String, String> 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 <T> 集合泛型
* @param <E> 想要的数组类型
*/
public static <E, T> E[] toArray(Collection<T> list, Predicate<T> 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<String, String> 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<String, Object> params, String... excludes) {
if (MapUtil.isEmpty(params)){
return StrUtil.EMPTY;
}
List<String> keys = new ArrayList<>(params.keySet());
if (CollUtil.isEmpty(keys)){
return StrUtil.EMPTY;
}
if (ArrayUtil.isNotEmpty(excludes)){
ArrayList<String> 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();
}
} }

View File

@ -20,7 +20,7 @@ public class SmsLoad {
// 服务器列表每个服务器有一个权重和当前权重 // 服务器列表每个服务器有一个权重和当前权重
private final List<LoadServer> LoadServers = new ArrayList<>(); private final List<LoadServer> LoadServers = new ArrayList<>();
private static final SmsLoad smsLoad = new SmsLoad(); private static final SmsLoad SMS_LOAD = new SmsLoad();
private SmsLoad() { private SmsLoad() {
} }
@ -100,15 +100,15 @@ public class SmsLoad {
public static void starConfig(SmsBlend smsBlend, SupplierConfig supplierConfig) { public static void starConfig(SmsBlend smsBlend, SupplierConfig supplierConfig) {
Map<String, Object> supplierConfigMap = BeanUtil.beanToMap(supplierConfig); Map<String, Object> supplierConfigMap = BeanUtil.beanToMap(supplierConfig);
Object weight = supplierConfigMap.getOrDefault("weight", 1); 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) { public static void starConfig(SmsBlend smsBlend,Integer weight) {
smsLoad.addLoadServer(smsBlend,weight); SMS_LOAD.addLoadServer(smsBlend,weight);
} }
public static SmsLoad getBeanLoad() { public static SmsLoad getBeanLoad() {
return smsLoad; return SMS_LOAD;
} }
} }

View File

@ -27,10 +27,10 @@ import java.util.stream.Collectors;
*/ */
@Slf4j @Slf4j
public abstract class SmsProxyFactory { public abstract class SmsProxyFactory {
private static final LinkedList<SmsProcessor> processors = new LinkedList<>(); private static final LinkedList<SmsProcessor> PROCESSORS = new LinkedList<>();
public static SmsBlend getProxySmsBlend(SmsBlend smsBlend) { public static SmsBlend getProxySmsBlend(SmsBlend smsBlend) {
LinkedList<SmsProcessor> ownerProcessors = processors.stream().filter(processor -> !shouldSkipProcess(processor,smsBlend)).collect(Collectors.toCollection(LinkedList::new)); LinkedList<SmsProcessor> 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)); 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); processorValidate(processor);
awareTransfer(processor); awareTransfer(processor);
processors.add(processor); PROCESSORS.add(processor);
processors.sort(Comparator.comparingInt(Order::getOrder)); PROCESSORS.sort(Comparator.comparingInt(Order::getOrder));
} }
/* /*

View File

@ -93,7 +93,6 @@ public class CoreMethodParamValidateProcessor implements CoreMethodProcessor {
} }
} }
} }
throw new SmsBlendException("cant send message to null!");
} }
public void validateMessages(String templateId, LinkedHashMap<String, String> messages) { public void validateMessages(String templateId, LinkedHashMap<String, String> messages) {

View File

@ -64,39 +64,57 @@ public class RestrictedProcessor implements CoreMethodProcessor, SmsDaoAware {
throw new SmsBlendException("The smsDao tool could not be found"); throw new SmsBlendException("The smsDao tool could not be found");
} }
SmsConfig config = BeanFactory.getSmsConfig(); SmsConfig config = BeanFactory.getSmsConfig();
// 如果未开始限制则不做处理
if (!config.getRestricted()){
return;
}
// 每日最大发送量 // 每日最大发送量
Integer accountMax = config.getAccountMax(); Integer accountMax = config.getAccountMax();
// 每分钟最大发送量 // 每分钟最大发送量
Integer minuteMax = config.getMinuteMax(); 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) { for (String phone : phones) {
// 分钟发送量缓存key
String minuteMaxKey = REDIS_KEY + phone;
// 天发送量缓存key
String accountMaxKey = minuteMaxKey.concat("max");
// 是否配置了每日限制 // 是否配置了每日限制
if (SmsUtils.isNotEmpty(accountMax)) { if (dailyMaxLimitExists) {
Integer i = (Integer) smsDao.get(REDIS_KEY + phone + "max"); Integer dailyCount = (Integer) smsDao.get(accountMaxKey);
if (SmsUtils.isEmpty(i)) { if (SmsUtils.isEmpty(dailyCount)) {
smsDao.set(REDIS_KEY + phone + "max", 1, accTimer / 1000); smsDao.set(accountMaxKey, 1, accTimer / 1000);
} else if (i >= accountMax) { } else if (dailyCount >= accountMax) {
log.info("The phone: {},number of short messages reached the maximum today", phone); 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); throw new SmsBlendException("The phone: {},number of short messages reached the maximum today", phone);
} else { } else {
smsDao.set(REDIS_KEY + phone + "max", i + 1, accTimer / 1000); smsDao.set(accountMaxKey, dailyCount + 1, accTimer / 1000);
} }
} }
// 是否配置了每分钟最大限制 // 是否配置了每分钟最大限制
if (SmsUtils.isNotEmpty(minuteMax)) { if (perMinuteLimitExists) {
Integer o = (Integer) smsDao.get(REDIS_KEY + phone); Integer minuteCount = (Integer) smsDao.get(REDIS_KEY + phone);
if (SmsUtils.isNotEmpty(o)) { if (SmsUtils.isNotEmpty(minuteCount)) {
if (o < minuteMax) { if (minuteCount < minuteMax) {
smsDao.set(REDIS_KEY + phone, o + 1, minTimer / 1000); smsDao.set(minuteMaxKey, minuteCount + 1, minTimer / 1000);
} else { } 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); throw new SmsBlendException("The phone: {} Text messages are sent too often", phone);
} }
} else { } else {
smsDao.set(REDIS_KEY + phone, 1, minTimer / 1000); smsDao.set(minuteMaxKey, 1, minTimer / 1000);
} }
} }
} }

View File

@ -17,7 +17,7 @@ import java.util.Map;
* 2023/6/8 22:35 * 2023/6/8 22:35
**/ **/
public class MailFactory{ public class MailFactory{
private static final Map<Object,MailSmtpConfig> configs = new HashMap<>(); private static final Map<Object,MailSmtpConfig> CONFIGS = new HashMap<>();
/** /**
* createMailClient * createMailClient
@ -27,7 +27,7 @@ public class MailFactory{
*/ */
public static MailClient createMailClient(Object key){ public static MailClient createMailClient(Object key){
try { try {
return MailBuild.build(configs.get(key)); return MailBuild.build(CONFIGS.get(key));
} catch (MessagingException e) { } catch (MessagingException e) {
throw new MailException(e); throw new MailException(e);
} }
@ -43,7 +43,7 @@ public class MailFactory{
*/ */
public static MailClient createMailClient(Object key, Blacklist blacklist){ public static MailClient createMailClient(Object key, Blacklist blacklist){
try { try {
return MailBuild.build(configs.get(key),blacklist); return MailBuild.build(CONFIGS.get(key),blacklist);
} catch (MessagingException e) { } catch (MessagingException e) {
throw new MailException(e); throw new MailException(e);
} }
@ -57,7 +57,7 @@ public class MailFactory{
* @author :Wind * @author :Wind
*/ */
public static void put(Object key, MailSmtpConfig config){ public static void put(Object key, MailSmtpConfig config){
configs.put(key,config); CONFIGS.put(key,config);
} }
} }

View File

@ -16,6 +16,8 @@ import org.dromara.sms4j.api.dao.SmsDao;
import org.dromara.sms4j.api.dao.SmsDaoDefaultImpl; import org.dromara.sms4j.api.dao.SmsDaoDefaultImpl;
import org.dromara.sms4j.api.universal.SupplierConfig; import org.dromara.sms4j.api.universal.SupplierConfig;
import org.dromara.sms4j.api.verify.PhoneVerify; 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.cloopen.config.CloopenFactory;
import org.dromara.sms4j.comm.constant.Constant; import org.dromara.sms4j.comm.constant.Constant;
import org.dromara.sms4j.comm.exception.SmsBlendException; 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.RestrictedProcessor;
import org.dromara.sms4j.core.proxy.processor.SingleBlendRestrictedProcessor; import org.dromara.sms4j.core.proxy.processor.SingleBlendRestrictedProcessor;
import org.dromara.sms4j.ctyun.config.CtyunFactory; 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.dingzhong.config.DingZhongFactory;
import org.dromara.sms4j.emay.config.EmayFactory; import org.dromara.sms4j.emay.config.EmayFactory;
import org.dromara.sms4j.huawei.config.HuaweiFactory; import org.dromara.sms4j.huawei.config.HuaweiFactory;
import org.dromara.sms4j.javase.util.YamlUtils; import org.dromara.sms4j.javase.util.YamlUtils;
import org.dromara.sms4j.jdcloud.config.JdCloudFactory; 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.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.netease.config.NeteaseFactory;
import org.dromara.sms4j.provider.config.SmsConfig; import org.dromara.sms4j.provider.config.SmsConfig;
import org.dromara.sms4j.provider.factory.BaseProviderFactory; import org.dromara.sms4j.provider.factory.BaseProviderFactory;
import org.dromara.sms4j.provider.factory.BeanFactory; import org.dromara.sms4j.provider.factory.BeanFactory;
import org.dromara.sms4j.provider.factory.ProviderFactoryHolder; 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.tencent.config.TencentFactory;
import org.dromara.sms4j.unisms.config.UniFactory; import org.dromara.sms4j.unisms.config.UniFactory;
import org.dromara.sms4j.yunpian.config.YunPianFactory; import org.dromara.sms4j.yunpian.config.YunPianFactory;
@ -228,7 +237,7 @@ public class SEInitializer {
continue; continue;
} }
configMap.put("config-id", configId); configMap.put("config-id", configId);
SmsUtils.replaceKeysSeperator(configMap, "-", "_"); SmsUtils.replaceKeysSeparator(configMap, "-", "_");
JSONObject configJson = new JSONObject(configMap); JSONObject configJson = new JSONObject(configMap);
SupplierConfig supplierConfig = JSONUtil.toBean(configJson, providerFactory.getConfigClass()); SupplierConfig supplierConfig = JSONUtil.toBean(configJson, providerFactory.getConfigClass());
SmsFactory.createSmsBlend(supplierConfig); SmsFactory.createSmsBlend(supplierConfig);
@ -251,6 +260,15 @@ public class SEInitializer {
ProviderFactoryHolder.registerFactory(ZhutongFactory.instance()); ProviderFactoryHolder.registerFactory(ZhutongFactory.instance());
ProviderFactoryHolder.registerFactory(LianLuFactory.instance()); ProviderFactoryHolder.registerFactory(LianLuFactory.instance());
ProviderFactoryHolder.registerFactory(DingZhongFactory.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")) { if (SmsUtils.isClassExists("com.jdcloud.sdk.auth.CredentialsProvider")) {
ProviderFactoryHolder.registerFactory(JdCloudFactory.instance()); ProviderFactoryHolder.registerFactory(JdCloudFactory.instance());
} }

View File

@ -16,13 +16,13 @@ import java.util.concurrent.ConcurrentHashMap;
public class ProviderFactoryHolder { public class ProviderFactoryHolder {
private static final Map<String, OaBaseProviderFactory<? extends OaSender, ? extends OaSupplierConfig>> factories = new ConcurrentHashMap<>(); private static final Map<String, OaBaseProviderFactory<? extends OaSender, ? extends OaSupplierConfig>> FACTORIES = new ConcurrentHashMap<>();
public static void registerFactory(OaBaseProviderFactory<? extends OaSender, ? extends OaSupplierConfig> factory) { public static void registerFactory(OaBaseProviderFactory<? extends OaSender, ? extends OaSupplierConfig> factory) {
if (factory == null) { if (factory == null) {
throw new OaException("注册供应商工厂失败,工厂实例不能为空"); throw new OaException("注册供应商工厂失败,工厂实例不能为空");
} }
factories.put(factory.getSupplier(), factory); FACTORIES.put(factory.getSupplier(), factory);
} }
public static void registerFactory(List<OaBaseProviderFactory<? extends OaSender, ? extends OaSupplierConfig>> factoryList) { public static void registerFactory(List<OaBaseProviderFactory<? extends OaSender, ? extends OaSupplierConfig>> factoryList) {
@ -38,6 +38,6 @@ public class ProviderFactoryHolder {
} }
public static OaBaseProviderFactory<? extends OaSender, ? extends OaSupplierConfig> requireForSupplier(String supplier) { public static OaBaseProviderFactory<? extends OaSender, ? extends OaSupplierConfig> requireForSupplier(String supplier) {
return factories.getOrDefault(supplier, null); return FACTORIES.getOrDefault(supplier, null);
} }
} }

View File

@ -7,6 +7,7 @@ import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.aliyun.config.AlibabaConfig; import org.dromara.sms4j.aliyun.config.AlibabaConfig;
import org.dromara.sms4j.aliyun.utils.AliyunUtils; import org.dromara.sms4j.aliyun.utils.AliyunUtils;
import org.dromara.sms4j.api.entity.SmsResponse; 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.Constant;
import org.dromara.sms4j.comm.constant.SupplierConstant; import org.dromara.sms4j.comm.constant.SupplierConstant;
import org.dromara.sms4j.comm.delayedTime.DelayedTime; import org.dromara.sms4j.comm.delayedTime.DelayedTime;
@ -57,7 +58,7 @@ public class AlibabaSmsImpl extends AbstractSmsBlend<AlibabaConfig> {
@Override @Override
public SmsResponse sendMessage(String phone, String message) { public SmsResponse sendMessage(String phone, String message) {
LinkedHashMap<String, String> map = new LinkedHashMap<>(); LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
map.put(getConfig().getTemplateName(), message); map.put(getConfig().getTemplateName(), message);
return sendMessage(phone, getConfig().getTemplateId(), map); return sendMessage(phone, getConfig().getTemplateId(), map);
} }
@ -92,7 +93,7 @@ public class AlibabaSmsImpl extends AbstractSmsBlend<AlibabaConfig> {
messages = new LinkedHashMap<>(); messages = new LinkedHashMap<>();
} }
String messageStr = JSONUtil.toJsonStr(messages); String messageStr = JSONUtil.toJsonStr(messages);
return getSmsResponse(SmsUtils.arrayToString(phones), messageStr, templateId); return getSmsResponse(SmsUtils.addCodePrefixIfNot(phones), messageStr, templateId);
} }
private SmsResponse getSmsResponse(String phone, String message, String templateId) { private SmsResponse getSmsResponse(String phone, String message, String templateId) {
@ -108,14 +109,12 @@ public class AlibabaSmsImpl extends AbstractSmsBlend<AlibabaConfig> {
log.debug("requestUrl {}", requestUrl); log.debug("requestUrl {}", requestUrl);
Map<String, String> headers = MapUtil.newHashMap(1, true); Map<String, String> headers = MapUtil.newHashMap(1, true);
headers.put("Content-Type", Constant.FROM_URLENCODED); headers.put(Constant.CONTENT_TYPE, Constant.APPLICATION_FROM_URLENCODED);
SmsResponse smsResponse; SmsResponse smsResponse;
try { try {
smsResponse = getResponse(http.postJson(requestUrl, headers, paramStr)); smsResponse = getResponse(http.postJson(requestUrl, headers, paramStr));
} catch (SmsBlendException e) { } catch (SmsBlendException e) {
smsResponse = new SmsResponse(); smsResponse = errorResp(e.message);
smsResponse.setSuccess(false);
smsResponse.setData(e.getMessage());
} }
if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) { if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) {
retry = 0; retry = 0;
@ -132,11 +131,7 @@ public class AlibabaSmsImpl extends AbstractSmsBlend<AlibabaConfig> {
} }
private SmsResponse getResponse(JSONObject resJson) { private SmsResponse getResponse(JSONObject resJson) {
SmsResponse smsResponse = new SmsResponse(); return SmsRespUtils.resp(resJson, "OK".equals(resJson.getStr("Code")), getConfigId());
smsResponse.setSuccess("OK".equals(resJson.getStr("Code")));
smsResponse.setData(resJson);
smsResponse.setConfigId(getConfigId());
return smsResponse;
} }
} }

View File

@ -4,15 +4,14 @@ import cn.hutool.crypto.digest.HMac;
import cn.hutool.crypto.digest.HmacAlgorithm; import cn.hutool.crypto.digest.HmacAlgorithm;
import org.dromara.sms4j.aliyun.config.AlibabaConfig; import org.dromara.sms4j.aliyun.config.AlibabaConfig;
import org.dromara.sms4j.comm.constant.Constant; import org.dromara.sms4j.comm.constant.Constant;
import org.dromara.sms4j.comm.utils.SmsDateUtils;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.SimpleTimeZone;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.UUID; import java.util.UUID;
@ -27,18 +26,15 @@ public class AliyunUtils {
*/ */
private static final String ALGORITHM = "HMAC-SHA1"; 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 { public static String generateSendSmsRequestUrl(AlibabaConfig alibabaConfig, String message, String phone, String templateId) throws Exception {
// 这里一定要设置GMT时区 // 这里一定要设置GMT时区
SDF.setTimeZone(new SimpleTimeZone(0, "GMT"));
Map<String, String> paras = new HashMap<>(); Map<String, String> paras = new HashMap<>();
// 1. 公共请求参数 // 1. 公共请求参数
paras.put("SignatureMethod", ALGORITHM); paras.put("SignatureMethod", ALGORITHM);
paras.put("SignatureNonce", UUID.randomUUID().toString()); paras.put("SignatureNonce", UUID.randomUUID().toString());
paras.put("AccessKeyId", alibabaConfig.getAccessKeyId()); paras.put("AccessKeyId", alibabaConfig.getAccessKeyId());
paras.put("SignatureVersion", "1.0"); 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("Format", "JSON");
paras.put("Action", alibabaConfig.getAction()); paras.put("Action", alibabaConfig.getAction());
paras.put("Version", alibabaConfig.getVersion()); paras.put("Version", alibabaConfig.getVersion());
@ -110,10 +106,10 @@ public class AliyunUtils {
/** /**
* 生成请求参数body字符串 * 生成请求参数body字符串
* *
* @param alibabaConfig * @param alibabaConfig 配置数据
* @param phone * @param phone 手机号
* @param message * @param message 短信内容
* @param templateId * @param templateId 模板id
*/ */
public static String generateParamBody(AlibabaConfig alibabaConfig, String phone, String message, String templateId) throws Exception { public static String generateParamBody(AlibabaConfig alibabaConfig, String phone, String message, String templateId) throws Exception {
Map<String, String> paramMap = generateParamMap(alibabaConfig, phone, message, templateId); Map<String, String> paramMap = generateParamMap(alibabaConfig, phone, message, templateId);

View File

@ -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;
/**
* <p>类名: BaiduConfig
* <p>说明百度智能云 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;
}
}

View File

@ -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;
/**
* <p>类名: BaiduFactory
* <p>说明百度智能云 sms
*
* @author :bleachtred
* 2024/4/25 13:40
**/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class BaiduFactory extends AbstractProviderFactory<BaiduSmsImpl, BaiduConfig> {
private static final BaiduFactory INSTANCE = new BaiduFactory();
/**
* 获取建造者实例
* @return 建造者实例
*/
public static BaiduFactory instance() {
return INSTANCE;
}
/**
* createSms
* <p> 建造一个短信实现对像
*
* @author :bleachtred
*/
@Override
public BaiduSmsImpl createSms(BaiduConfig baiduConfig) {
return new BaiduSmsImpl(baiduConfig);
}
/**
* 获取供应商
* @return 供应商
*/
@Override
public String getSupplier() {
return SupplierConstant.BAIDU;
}
}

View File

@ -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;
/**
* <p>类名: BaiduSmsImpl
* <p>说明百度智能云 sms
*
* @author :bleachtred
* 2024/4/25 13:40
**/
@Slf4j
public class BaiduSmsImpl extends AbstractSmsBlend<BaiduConfig> {
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<String, String> map = new LinkedHashMap<>(1);
map.put(getConfig().getTemplateName(), message);
return sendMessage(phone, getConfig().getTemplateId(), map);
}
@Override
public SmsResponse sendMessage(String phone, LinkedHashMap<String, String> messages) {
if (CollUtil.isEmpty(messages)){
messages = new LinkedHashMap<>();
}
return sendMessage(phone, getConfig().getTemplateId(), messages);
}
@Override
public SmsResponse sendMessage(String phone, String templateId, LinkedHashMap<String, String> messages) {
if (CollUtil.isEmpty(messages)){
messages = new LinkedHashMap<>();
}
return getSmsResponse(phone, templateId, messages);
}
@Override
public SmsResponse massTexting(List<String> phones, String message) {
LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
map.put(getConfig().getTemplateName(), message);
return massTexting(phones, getConfig().getTemplateId(), map);
}
@Override
public SmsResponse massTexting(List<String> phones, String templateId, LinkedHashMap<String, String> messages) {
if (CollUtil.isEmpty(messages)){
messages = new LinkedHashMap<>();
}
return getSmsResponse(SmsUtils.addCodePrefixIfNot(phones), templateId, messages);
}
private SmsResponse getSmsResponse(String phone, String templateId, LinkedHashMap<String, String> 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<String, String> map = new LinkedHashMap<>(1);
map.put(getConfig().getTemplateName(), message);
return sendMessageWithClientToken(phone, getConfig().getTemplateId(), map, clientToken);
}
public SmsResponse sendMessageWithClientToken(String phone, LinkedHashMap<String, String> 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<String, String> messages, String clientToken) {
checkClientToken(clientToken);
if (CollUtil.isEmpty(messages)){
messages = new LinkedHashMap<>();
}
return getSmsResponseWithClientToken(phone, templateId, messages, clientToken);
}
public SmsResponse massTextingWithClientToken(List<String> phones, String message, String clientToken) {
checkClientToken(clientToken);
LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
map.put(getConfig().getTemplateName(), message);
return massTextingWithClientToken(phones, getConfig().getTemplateId(), map, clientToken);
}
public SmsResponse massTextingWithClientToken(List<String> phones, String templateId, LinkedHashMap<String, String> 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<String, String> 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<String, String> headers;
Map<String, Object> 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<String, String> 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());
}
}

View File

@ -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<String, String> 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<String, String> 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<String, Object> buildBody(String mobile, String template, String signatureId,
LinkedHashMap<String, String> contentVar, String custom, String userExtId) {
Map<String, Object> 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;
}
}

View File

@ -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
* <p> 布丁云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;
}
}

View File

@ -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
* <p> 布丁云V2短信对象建造
*
* @author NicholaslD
* @date 2024/03/21 12:00
* */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class BudingV2Factory extends AbstractProviderFactory<BudingV2SmsImpl, BudingV2Config> {
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;
}
}

View File

@ -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<BudingV2Config> {
/**
* 重试次数
*/
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<String, Object> 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<String, String> 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<String, String> 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<String, String> messages) {
return sendMessage(phone, messages);
}
/**
* 群发短信
* @param phones 手机号列表
* @param message 消息内容
* @return 发送结果
*/
@Override
public SmsResponse massTexting(List<String> 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<String> phones, String templateId, LinkedHashMap<String, String> messages) {
throw new SmsBlendException("布丁云V2暂不支持多条短信发送");
}
private Map<String, String> getHeaders() {
Map<String, String> headers = new HashMap<>();
headers.put(Constant.ACCEPT, Constant.APPLICATION_JSON_UTF8);
headers.put(Constant.CONTENT_TYPE, Constant.APPLICATION_FROM_URLENCODED);
return headers;
}
}

View File

@ -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;
}
}

View File

@ -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<ChuangLanSmsImpl, ChuangLanConfig> {
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;
}
}

View File

@ -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<ChuangLanConfig> {
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<String, String> messages) {
return sendMessage(phone, getConfig().getTemplateId(), messages);
}
@Override
public SmsResponse sendMessage(String phone, String templateId, LinkedHashMap<String, String> messages) {
if (SmsUtils.isEmpty(messages)){
messages = new LinkedHashMap<>();
}
String message = String.join(",", messages.values());
ChuangLanConfig config = getConfig();
LinkedHashMap<String, Object> body = buildBody(config.getAccessKeyId(), config.getAccessKeySecret(), templateId);
body.put("params", phone + "," + message);
return getSmsResponse(body);
}
@Override
public SmsResponse massTexting(List<String> phones, String message) {
return massTexting(phones, getConfig().getTemplateId(), SmsUtils.buildMessageByAmpersand(message));
}
@Override
public SmsResponse massTexting(List<String> phones, String templateId, LinkedHashMap<String, String> 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<String, Object> 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<String, String> buildHeaders(){
LinkedHashMap<String, String> headers = new LinkedHashMap<>(1);
headers.put(Constant.CONTENT_TYPE, Constant.APPLICATION_JSON);
return headers;
}
private static LinkedHashMap<String, Object> buildBody(String accessKeyId, String accessKeySecret, String templateId){
LinkedHashMap<String, Object> body = new LinkedHashMap<>(3);
body.put("account", accessKeyId);
body.put("password", accessKeySecret);
body.put("msg", templateId);
return body;
}
private SmsResponse getSmsResponse(LinkedHashMap<String, Object> 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<String, Object> 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());
}
}

View File

@ -2,6 +2,7 @@ package org.dromara.sms4j.cloopen.config;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.dromara.sms4j.comm.constant.Constant;
import org.dromara.sms4j.comm.constant.SupplierConstant; import org.dromara.sms4j.comm.constant.SupplierConstant;
import org.dromara.sms4j.provider.config.BaseConfig; import org.dromara.sms4j.provider.config.BaseConfig;
@ -18,7 +19,7 @@ public class CloopenConfig extends BaseConfig {
/** /**
* REST API Base URL * 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";
/** /**
* 获取供应商 * 获取供应商

View File

@ -27,7 +27,7 @@ public class CloopenFactory extends AbstractProviderFactory<CloopenSmsImpl, Cloo
} }
/** /**
* 创建容云短信实现对象 * 创建容云短信实现对象
* *
* @param cloopenConfig 短信配置对象 * @param cloopenConfig 短信配置对象
* @return 短信实现对象 * @return 短信实现对象

View File

@ -8,6 +8,7 @@ import cn.hutool.crypto.SecureUtil;
import cn.hutool.json.JSONObject; import cn.hutool.json.JSONObject;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.entity.SmsResponse; import org.dromara.sms4j.api.entity.SmsResponse;
import org.dromara.sms4j.api.utils.SmsRespUtils;
import org.dromara.sms4j.cloopen.config.CloopenConfig; import org.dromara.sms4j.cloopen.config.CloopenConfig;
import org.dromara.sms4j.comm.constant.Constant; import org.dromara.sms4j.comm.constant.Constant;
import org.dromara.sms4j.comm.exception.SmsBlendException; import org.dromara.sms4j.comm.exception.SmsBlendException;
@ -43,16 +44,14 @@ public class CloopenHelper {
config.getAccessKeyId(), config.getAccessKeyId(),
this.generateSign(config.getAccessKeyId(), config.getAccessKeySecret(), timestamp)); this.generateSign(config.getAccessKeyId(), config.getAccessKeySecret(), timestamp));
Map<String, String> headers = MapUtil.newHashMap(3, true); Map<String, String> headers = MapUtil.newHashMap(3, true);
headers.put("Accept", Constant.ACCEPT); headers.put(Constant.ACCEPT, Constant.APPLICATION_JSON);
headers.put("Content-Type", Constant.APPLICATION_JSON_UTF8); headers.put(Constant.CONTENT_TYPE, Constant.APPLICATION_JSON_UTF8);
headers.put("Authorization", this.generateAuthorization(config.getAccessKeyId(), timestamp)); headers.put(Constant.AUTHORIZATION, this.generateAuthorization(config.getAccessKeyId(), timestamp));
SmsResponse smsResponse = null; SmsResponse smsResponse;
try { try {
smsResponse = getResponse(http.postJson(url, headers, paramMap)); smsResponse = getResponse(http.postJson(url, headers, paramMap));
} catch (SmsBlendException e) { } catch (SmsBlendException e) {
smsResponse = new SmsResponse(); smsResponse = SmsRespUtils.error(e.message, config.getConfigId());
smsResponse.setSuccess(false);
smsResponse.setData(e.getMessage());
} }
if (smsResponse.isSuccess() || retry == config.getMaxRetries()) { if (smsResponse.isSuccess() || retry == config.getMaxRetries()) {
retry = 0; retry = 0;
@ -70,11 +69,7 @@ public class CloopenHelper {
} }
private SmsResponse getResponse(JSONObject resJson) { private SmsResponse getResponse(JSONObject resJson) {
SmsResponse smsResponse = new SmsResponse(); return SmsRespUtils.resp(resJson, "000000".equals(resJson.getStr("statusCode")), config.getConfigId());
smsResponse.setSuccess("000000".equals(resJson.getStr("statusCode")));
smsResponse.setData(resJson);
smsResponse.setConfigId(this.config.getConfigId());
return smsResponse;
} }
/** /**

View File

@ -2,6 +2,7 @@ package org.dromara.sms4j.ctyun.config;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.dromara.sms4j.comm.constant.Constant;
import org.dromara.sms4j.comm.constant.SupplierConstant; import org.dromara.sms4j.comm.constant.SupplierConstant;
import org.dromara.sms4j.provider.config.BaseConfig; import org.dromara.sms4j.provider.config.BaseConfig;
@ -9,7 +10,7 @@ import org.dromara.sms4j.provider.config.BaseConfig;
* <p>类名: CtyunConfig * <p>类名: CtyunConfig
* <p>说明 天翼云短信差异配置 * <p>说明 天翼云短信差异配置
* *
* @author :bleachhtred * @author :bleachtred
* 2023/5/12 15:06 * 2023/5/12 15:06
**/ **/
@Data @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";
/** /**
* 接口名称 * 接口名称

View File

@ -10,7 +10,7 @@ import org.dromara.sms4j.provider.factory.AbstractProviderFactory;
* <p>类名: CtyunSmsConfig * <p>类名: CtyunSmsConfig
* <p>说明 天翼云 云通信短信配置器 * <p>说明 天翼云 云通信短信配置器
* *
* @author :bleachhtred * @author :bleachtred
* 2023/5/12 15:06 * 2023/5/12 15:06
**/ **/
@NoArgsConstructor(access = AccessLevel.PRIVATE) @NoArgsConstructor(access = AccessLevel.PRIVATE)
@ -30,7 +30,7 @@ public class CtyunFactory extends AbstractProviderFactory<CtyunSmsImpl, CtyunCon
* getCtyunSms * getCtyunSms
* <p> 建造一个短信实现对像 * <p> 建造一个短信实现对像
* *
* @author :bleachhtred * @author :bleachtred
*/ */
@Override @Override
public CtyunSmsImpl createSms(CtyunConfig ctyunConfig) { public CtyunSmsImpl createSms(CtyunConfig ctyunConfig) {

View File

@ -4,6 +4,7 @@ import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil; import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.entity.SmsResponse; 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.constant.SupplierConstant;
import org.dromara.sms4j.comm.delayedTime.DelayedTime; import org.dromara.sms4j.comm.delayedTime.DelayedTime;
import org.dromara.sms4j.comm.exception.SmsBlendException; import org.dromara.sms4j.comm.exception.SmsBlendException;
@ -21,7 +22,7 @@ import java.util.concurrent.Executor;
* <p>类名: CtyunSmsImpl * <p>类名: CtyunSmsImpl
* <p>说明 天翼云短信实现 * <p>说明 天翼云短信实现
* *
* @author :bleachhtred * @author :bleachtred
* 2023/5/12 15:06 * 2023/5/12 15:06
**/ **/
@Slf4j @Slf4j
@ -79,15 +80,16 @@ public class CtyunSmsImpl extends AbstractSmsBlend<CtyunConfig> {
messages = new LinkedHashMap<>(); messages = new LinkedHashMap<>();
} }
String messageStr = JSONUtil.toJsonStr(messages); String messageStr = JSONUtil.toJsonStr(messages);
return getSmsResponse(SmsUtils.arrayToString(phones), messageStr, templateId); return getSmsResponse(SmsUtils.addCodePrefixIfNot(phones), messageStr, templateId);
} }
private SmsResponse getSmsResponse(String phone, String message, String templateId) { private SmsResponse getSmsResponse(String phone, String message, String templateId) {
CtyunConfig config = getConfig();
String requestUrl; String requestUrl;
String paramStr; String paramStr;
try { try {
requestUrl = getConfig().getRequestUrl(); requestUrl = config.getRequestUrl();
paramStr = CtyunUtils.generateParamJsonStr(getConfig(), phone, message, templateId); paramStr = CtyunUtils.generateParamJsonStr(config, phone, message, templateId);
} catch (Exception e) { } catch (Exception e) {
log.error("ctyun send message error", e); log.error("ctyun send message error", e);
throw new SmsBlendException(e.getMessage()); throw new SmsBlendException(e.getMessage());
@ -96,14 +98,12 @@ public class CtyunSmsImpl extends AbstractSmsBlend<CtyunConfig> {
SmsResponse smsResponse; SmsResponse smsResponse;
try { try {
smsResponse = getResponse(http.postJson(requestUrl, smsResponse = getResponse(http.postJson(requestUrl,
CtyunUtils.signHeader(paramStr, getConfig().getAccessKeyId(), getConfig().getAccessKeySecret()), CtyunUtils.signHeader(paramStr, config.getAccessKeyId(), config.getAccessKeySecret()),
paramStr)); paramStr));
} catch (SmsBlendException e) { } catch (SmsBlendException e) {
smsResponse = new SmsResponse(); smsResponse = errorResp(e.message);
smsResponse.setSuccess(false);
smsResponse.setData(e.getMessage());
} }
if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) { if (smsResponse.isSuccess() || retry == config.getMaxRetries()) {
retry = 0; retry = 0;
return smsResponse; return smsResponse;
} }
@ -118,11 +118,7 @@ public class CtyunSmsImpl extends AbstractSmsBlend<CtyunConfig> {
} }
private SmsResponse getResponse(JSONObject resJson) { private SmsResponse getResponse(JSONObject resJson) {
SmsResponse smsResponse = new SmsResponse(); return SmsRespUtils.resp(resJson, "OK".equals(resJson.getStr("code")), getConfigId());
smsResponse.setSuccess("OK".equals(resJson.getStr("code")));
smsResponse.setData(resJson);
smsResponse.setConfigId(getConfigId());
return smsResponse;
} }
} }

View File

@ -1,22 +1,18 @@
package org.dromara.sms4j.ctyun.utils; package org.dromara.sms4j.ctyun.utils;
import cn.hutool.core.codec.Base64; import cn.hutool.core.codec.Base64;
import cn.hutool.crypto.digest.HMac; import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.digest.HmacAlgorithm; import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.json.JSONUtil; import cn.hutool.json.JSONUtil;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.dromara.sms4j.comm.constant.Constant; 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 org.dromara.sms4j.ctyun.config.CtyunConfig;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -28,8 +24,7 @@ public class CtyunUtils {
* 获取签名时间戳 * 获取签名时间戳
*/ */
private static String signatureTime(){ private static String signatureTime(){
SimpleDateFormat timeFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"); return SmsDateUtils.pureDateUtcGmt(new Date());
return timeFormat.format(new Date());
} }
/** /**
@ -39,9 +34,7 @@ public class CtyunUtils {
Map<String, String> map = new ConcurrentHashMap<>(4); Map<String, String> map = new ConcurrentHashMap<>(4);
// 构造时间戳 // 构造时间戳
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd"); String signatureDate = SmsDateUtils.pureDateGmt(new Date());
Date now = new Date();
String signatureDate = dateFormat.format(now);
String signatureTime = signatureTime(); String signatureTime = signatureTime();
// 构造请求流水号 // 构造请求流水号
String uuid = UUID.randomUUID().toString(); String uuid = UUID.randomUUID().toString();
@ -59,7 +52,7 @@ public class CtyunUtils {
// 构造签名 // 构造签名
String signature = Base64.encode(hmacSHA256(signatureStr.getBytes(StandardCharsets.UTF_8), kDate)); 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); 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("ctyun-eop-request-id", uuid);
map.put("Eop-date", signatureTime); map.put("Eop-date", signatureTime);
map.put("Eop-Authorization", signHeader); map.put("Eop-Authorization", signHeader);
@ -84,36 +77,11 @@ public class CtyunUtils {
return JSONUtil.toJsonStr(paramMap); 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) { private static String getSHA256(String text) {
try { return DigestUtil.sha256Hex(text);
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(text.getBytes(StandardCharsets.UTF_8));
return toHex(md.digest());
} catch (NoSuchAlgorithmException var3) {
return null;
}
} }
private static byte[] hmacSHA256(byte[] data, byte[] key){ private static byte[] hmacSHA256(byte[] data, byte[] key){
try { return SecureUtil.hmacSha256(key).digest(data);
HMac hMac = new HMac(HmacAlgorithm.HmacSHA256, key);
return hMac.digest(data);
} catch (Exception e) {
throw new SmsBlendException(e.getMessage());
}
} }
} }

View File

@ -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;
/**
* <p>类名: DanMiConfig
* <p>说明 旦米短信差异配置
*
* @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;
}
}

View File

@ -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;
/**
* <p>类名: DanMiFactory
*
* @author :bleachtred
* 2024/6/23 17:06
**/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class DanMiFactory extends AbstractProviderFactory<DanMiSmsImpl, DanMiConfig> {
private static final DanMiFactory INSTANCE = new DanMiFactory();
/**
* 获取建造者实例
* @return 建造者实例
*/
public static DanMiFactory instance() {
return INSTANCE;
}
/**
* createSms
* <p> 建造一个短信实现对像
*
* @author :bleachtred
*/
@Override
public DanMiSmsImpl createSms(DanMiConfig config) {
return new DanMiSmsImpl(config);
}
/**
* 获取供应商
* @return 供应商
*/
@Override
public String getSupplier() {
return SupplierConstant.DAN_MI;
}
}

View File

@ -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;
/**
* <p>类名: DanMiSmsImpl
*
* @author :bleachtred
* 2024/6/23 17:06
**/
@Slf4j
public class DanMiSmsImpl extends AbstractSmsBlend<DanMiConfig> {
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<String> phones = phone.contains(StrUtil.COMMA) ? SmsUtils.splitTrimComma(phone) : Collections.singletonList(phone);
return massTexting(phones, message);
}
@Override
public SmsResponse sendMessage(String phone, LinkedHashMap<String, String> messages) {
throw new SmsBlendException("不支持此方法");
}
@Override
public SmsResponse sendMessage(String phone, String templateId, LinkedHashMap<String, String> messages) {
throw new SmsBlendException("不支持此方法");
}
@Override
public SmsResponse massTexting(List<String> phones, String message) {
return getSmsResponse(phones, message, getConfig().getTemplateId());
}
@Override
public SmsResponse massTexting(List<String> phones, String templateId, LinkedHashMap<String, String> 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<String> 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<String> 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());
}
}

View File

@ -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;
/**
* <p>类名: DanMiUtils
*
* @author :bleachtred
* 2024/6/23 17:06
**/
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class DanMiUtils {
public static LinkedHashMap<String, String> buildHeaders(){
LinkedHashMap<String, String> 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<String, Object> buildBody(DanMiConfig config, List<String> phones, String message, String templateId) {
LinkedHashMap<String, Object> 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);
}
}

View File

@ -86,11 +86,11 @@ public class DingZhongSmsImpl extends AbstractSmsBlend<DingZhongConfig> {
@Override @Override
public SmsResponse massTexting(List<String> phones, String message) { public SmsResponse massTexting(List<String> phones, String message) {
return sendMessage(SmsUtils.arrayToString(phones), message); return sendMessage(SmsUtils.addCodePrefixIfNot(phones), message);
} }
@Override @Override
public SmsResponse massTexting(List<String> phones, String templateId, LinkedHashMap<String, String> messages) { public SmsResponse massTexting(List<String> phones, String templateId, LinkedHashMap<String, String> messages) {
return sendMessage(SmsUtils.arrayToString(phones), templateId, messages); return sendMessage(SmsUtils.addCodePrefixIfNot(phones), templateId, messages);
} }
} }

View File

@ -4,6 +4,7 @@ import cn.hutool.core.map.MapUtil;
import cn.hutool.json.JSONObject; import cn.hutool.json.JSONObject;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.entity.SmsResponse; 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.Constant;
import org.dromara.sms4j.comm.exception.SmsBlendException; import org.dromara.sms4j.comm.exception.SmsBlendException;
import org.dromara.sms4j.comm.utils.SmsHttpUtils; import org.dromara.sms4j.comm.utils.SmsHttpUtils;
@ -33,15 +34,13 @@ public class DingZhongHelper {
public SmsResponse smsResponse(Map<String, Object> paramMap) { public SmsResponse smsResponse(Map<String, Object> paramMap) {
String url = String.format("%s/%s", config.getRequestUrl(), SmsUtils.isEmpty(paramMap.get("templateId"))?config.getBaseAction():config.getTemplateAction()); String url = String.format("%s/%s", config.getRequestUrl(), SmsUtils.isEmpty(paramMap.get("templateId"))?config.getBaseAction():config.getTemplateAction());
Map<String, String> headers = MapUtil.newHashMap(2, true); Map<String, String> headers = MapUtil.newHashMap(2, true);
headers.put("Accept", Constant.ACCEPT); headers.put(Constant.ACCEPT, Constant.APPLICATION_JSON);
headers.put("Content-Type", Constant.FROM_URLENCODED); headers.put(Constant.CONTENT_TYPE, Constant.APPLICATION_FROM_URLENCODED);
SmsResponse smsResponse = null; SmsResponse smsResponse;
try { try {
smsResponse = getResponse(http.postFrom(url, headers, paramMap)); smsResponse = getResponse(http.postFrom(url, headers, paramMap));
} catch (SmsBlendException e) { } catch (SmsBlendException e) {
smsResponse = new SmsResponse(); smsResponse = SmsRespUtils.error(e.message, config.getConfigId());
smsResponse.setSuccess(false);
smsResponse.setData(e.getMessage());
} }
if (smsResponse.isSuccess() || retry == config.getMaxRetries()) { if (smsResponse.isSuccess() || retry == config.getMaxRetries()) {
retry = 0; retry = 0;
@ -59,10 +58,6 @@ public class DingZhongHelper {
} }
private SmsResponse getResponse(JSONObject resJson) { private SmsResponse getResponse(JSONObject resJson) {
SmsResponse smsResponse = new SmsResponse(); return SmsRespUtils.resp(resJson, "0".equals(resJson.getStr("resCode")), config.getConfigId());
smsResponse.setSuccess("0".equals(resJson.getStr("resCode")));
smsResponse.setData(resJson);
smsResponse.setConfigId(this.config.getConfigId());
return smsResponse;
} }
} }

View File

@ -4,6 +4,7 @@ import cn.hutool.core.map.MapUtil;
import cn.hutool.json.JSONObject; import cn.hutool.json.JSONObject;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.entity.SmsResponse; 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.Constant;
import org.dromara.sms4j.comm.constant.SupplierConstant; import org.dromara.sms4j.comm.constant.SupplierConstant;
import org.dromara.sms4j.comm.delayedTime.DelayedTime; import org.dromara.sms4j.comm.delayedTime.DelayedTime;
@ -44,20 +45,19 @@ public class EmaySmsImpl extends AbstractSmsBlend<EmayConfig> {
@Override @Override
public SmsResponse sendMessage(String phone, String message) { public SmsResponse sendMessage(String phone, String message) {
String url = getConfig().getRequestUrl(); EmayConfig config = getConfig();
Map<String, Object> params = EmayBuilder.buildRequestBody(getConfig().getAccessKeyId(), getConfig().getAccessKeySecret(), phone, message); String url = config.getRequestUrl();
Map<String, Object> params = EmayBuilder.buildRequestBody(config.getAccessKeyId(), config.getAccessKeySecret(), phone, message);
Map<String, String> headers = MapUtil.newHashMap(1, true); Map<String, String> headers = MapUtil.newHashMap(1, true);
headers.put("Content-Type", Constant.FROM_URLENCODED); headers.put(Constant.CONTENT_TYPE, Constant.APPLICATION_FROM_URLENCODED);
SmsResponse smsResponse; SmsResponse smsResponse;
try { try {
smsResponse = getResponse(http.postUrl(url, headers, params)); smsResponse = getResponse(http.postUrl(url, headers, params));
} catch (SmsBlendException e) { } catch (SmsBlendException e) {
smsResponse = new SmsResponse(); smsResponse = errorResp(e.message);
smsResponse.setSuccess(false);
smsResponse.setData(e.getMessage());
} }
if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) { if (smsResponse.isSuccess() || retry == config.getMaxRetries()) {
retry = 0; retry = 0;
return smsResponse; return smsResponse;
} }
@ -97,7 +97,7 @@ public class EmaySmsImpl extends AbstractSmsBlend<EmayConfig> {
if (phones.size() > 500) { if (phones.size() > 500) {
throw new SmsBlendException("单次发送超过最大发送上限建议每次群发短信人数低于500"); throw new SmsBlendException("单次发送超过最大发送上限建议每次群发短信人数低于500");
} }
return sendMessage(SmsUtils.listToString(phones), message); return sendMessage(SmsUtils.joinComma(phones), message);
} }
@Override @Override
@ -109,15 +109,11 @@ public class EmaySmsImpl extends AbstractSmsBlend<EmayConfig> {
for (Map.Entry<String, String> entry : messages.entrySet()) { for (Map.Entry<String, String> entry : messages.entrySet()) {
list.add(entry.getValue()); 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) { private SmsResponse getResponse(JSONObject resJson) {
SmsResponse smsResponse = new SmsResponse(); return SmsRespUtils.resp(resJson, "success".equalsIgnoreCase(resJson.getStr("code")), getConfigId());
smsResponse.setSuccess("success".equalsIgnoreCase(resJson.getStr("code")));
smsResponse.setData(resJson);
smsResponse.setConfigId(getConfigId());
return smsResponse;
} }
} }

View File

@ -5,6 +5,7 @@ import cn.hutool.core.map.MapUtil;
import cn.hutool.json.JSONObject; import cn.hutool.json.JSONObject;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.entity.SmsResponse; 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.Constant;
import org.dromara.sms4j.comm.constant.SupplierConstant; import org.dromara.sms4j.comm.constant.SupplierConstant;
import org.dromara.sms4j.comm.delayedTime.DelayedTime; import org.dromara.sms4j.comm.delayedTime.DelayedTime;
@ -70,16 +71,14 @@ public class HuaweiSmsImpl extends AbstractSmsBlend<HuaweiConfig> {
String requestBody = HuaweiBuilder.buildRequestBody(getConfig().getSender(), phone, templateId, mess, getConfig().getStatusCallBack(), getConfig().getSignature()); String requestBody = HuaweiBuilder.buildRequestBody(getConfig().getSender(), phone, templateId, mess, getConfig().getStatusCallBack(), getConfig().getSignature());
Map<String, String> headers = MapUtil.newHashMap(3, true); Map<String, String> 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("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; SmsResponse smsResponse;
try { try {
smsResponse = getResponse(http.postJson(url, headers, requestBody)); smsResponse = getResponse(http.postJson(url, headers, requestBody));
} catch (SmsBlendException e) { } catch (SmsBlendException e) {
smsResponse = new SmsResponse(); smsResponse = errorResp(e.message);
smsResponse.setSuccess(false);
smsResponse.setData(e.getMessage());
} }
if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) { if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) {
retry = 0; retry = 0;
@ -109,11 +108,7 @@ public class HuaweiSmsImpl extends AbstractSmsBlend<HuaweiConfig> {
} }
private SmsResponse getResponse(JSONObject resJson) { private SmsResponse getResponse(JSONObject resJson) {
SmsResponse smsResponse = new SmsResponse(); return SmsRespUtils.resp(resJson, "000000".equals(resJson.getStr("code")), getConfigId());
smsResponse.setSuccess("000000".equals(resJson.getStr("code")));
smsResponse.setData(resJson);
smsResponse.setConfigId(getConfigId());
return smsResponse;
} }
} }

View File

@ -2,6 +2,11 @@ package org.dromara.sms4j.huawei.utils;
import cn.hutool.core.codec.Base64; import cn.hutool.core.codec.Base64;
import cn.hutool.core.date.DateUtil; 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.constant.Constant;
import org.dromara.sms4j.comm.exception.SmsBlendException; 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.SSLContext;
import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager; 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.security.cert.X509Certificate;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID;
@Slf4j
public class HuaweiBuilder { public class HuaweiBuilder {
private HuaweiBuilder() { private HuaweiBuilder() {
} }
@ -32,22 +33,13 @@ public class HuaweiBuilder {
*/ */
public static String buildWsseHeader(String appKey, String appSecret) { public static String buildWsseHeader(String appKey, String appSecret) {
if (null == appKey || null == appSecret || appKey.isEmpty() || appSecret.isEmpty()) { if (null == appKey || null == appSecret || appKey.isEmpty() || appSecret.isEmpty()) {
System.out.println("buildWsseHeader(): appKey or appSecret is null."); log.error("buildWsseHeader(): appKey or appSecret is null.");
return null; throw new SmsBlendException("buildWsseHeader(): appKey or appSecret is null.");
} }
String time = dateFormat(new Date()); String time = dateFormat(new Date());
// Nonce // Nonce
String nonce = UUID.randomUUID().toString().replace("-", ""); String nonce = UUID.fastUUID().toString(true);
MessageDigest md; byte[] passwordDigest = DigestUtil.sha256(nonce + time + appSecret);
byte[] passwordDigest;
try {
md = MessageDigest.getInstance("SHA-256");
md.update((nonce + time + appSecret).getBytes());
passwordDigest = md.digest();
} catch (NoSuchAlgorithmException e) {
throw new SmsBlendException(e);
}
// PasswordDigest // PasswordDigest
String passwordDigestBase64Str = Base64.encode(passwordDigest); String passwordDigestBase64Str = Base64.encode(passwordDigest);
//若passwordDigestBase64Str中包含换行符,请执行如下代码进行修正 //若passwordDigestBase64Str中包含换行符,请执行如下代码进行修正
@ -91,12 +83,11 @@ public class HuaweiBuilder {
*/ */
public static String buildRequestBody(String sender, String receiver, String templateId, String templateParas, public static String buildRequestBody(String sender, String receiver, String templateId, String templateParas,
String statusCallBack, String signature) { String statusCallBack, String signature) {
if (null == sender || null == receiver || null == templateId || sender.isEmpty() || receiver.isEmpty() if (StrUtil.hasBlank(sender, receiver, templateId)) {
|| templateId.isEmpty()) { log.error("buildRequestBody(): sender, receiver or templateId is null.");
System.out.println("buildRequestBody(): sender, receiver or templateId is null."); throw new SmsBlendException("buildRequestBody(): sender, receiver or templateId is null.");
return null;
} }
Map<String, String> map = new HashMap<>(); Map<String, String> map = new HashMap<>(3);
map.put("from", sender); map.put("from", sender);
map.put("to", receiver); map.put("to", receiver);
@ -112,17 +103,7 @@ public class HuaweiBuilder {
} }
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
String temp; map.keySet().forEach(s -> sb.append(s).append("=").append(URLEncodeUtil.encode(map.get(s))).append("&"));
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("&");
}
return sb.deleteCharAt(sb.length() - 1).toString(); return sb.deleteCharAt(sb.length() - 1).toString();
} }

View File

@ -6,6 +6,7 @@ import com.jdcloud.sdk.service.sms.model.BatchSendRequest;
import com.jdcloud.sdk.service.sms.model.BatchSendResult; import com.jdcloud.sdk.service.sms.model.BatchSendResult;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.entity.SmsResponse; 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.constant.SupplierConstant;
import org.dromara.sms4j.comm.delayedTime.DelayedTime; import org.dromara.sms4j.comm.delayedTime.DelayedTime;
import org.dromara.sms4j.comm.exception.SmsBlendException; import org.dromara.sms4j.comm.exception.SmsBlendException;
@ -96,9 +97,7 @@ public class JdCloudSmsImpl extends AbstractSmsBlend<JdCloudConfig> {
try { try {
smsResponse = getSmsResponse(result); smsResponse = getSmsResponse(result);
} catch (SmsBlendException e) { } catch (SmsBlendException e) {
smsResponse = new SmsResponse(); smsResponse = errorResp(e.message);
smsResponse.setSuccess(false);
smsResponse.setData(e.getMessage());
} }
if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) { if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) {
retry = 0; retry = 0;
@ -121,10 +120,6 @@ public class JdCloudSmsImpl extends AbstractSmsBlend<JdCloudConfig> {
* @return 发送短信返回信息 * @return 发送短信返回信息
*/ */
private SmsResponse getSmsResponse(BatchSendResult res) { private SmsResponse getSmsResponse(BatchSendResult res) {
SmsResponse smsResponse = new SmsResponse(); return SmsRespUtils.resp(res, res.getStatus() != null && res.getStatus(), getConfigId());
smsResponse.setSuccess(res.getStatus() != null && res.getStatus());
smsResponse.setData(res);
smsResponse.setConfigId(getConfigId());
return smsResponse;
} }
} }

View File

@ -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;
/**
* <p>类名: JgConfig
* <p>说明极光 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;
}
}

View File

@ -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;
/**
* <p>类名: JgFactory
* <p>说明极光 sms
*
* @author :SmartFire
* 2024/3/15
**/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class JgFactory extends AbstractProviderFactory<JgSmsImpl, JgConfig> {
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;
}
}

View File

@ -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;
/**
* <p>类名: JgSmsImpl
* <p>说明极光 sms
*
* @author :SmartFire
* 2024/3/15
**/
@Slf4j
public class JgSmsImpl extends AbstractSmsBlend<JgConfig> {
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<String, String> 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<String, String> messages) {
if (SmsUtils.isEmpty(messages)){
messages = new LinkedHashMap<>();
}
return sendMessage(phone, getConfig().getTemplateId(), messages);
}
@Override
public SmsResponse sendMessage(String phone, String templateId, LinkedHashMap<String, String> messages) {
if (SmsUtils.isEmpty(messages)){
messages = new LinkedHashMap<>();
}
return getSmsResponse(phone, messages, templateId, null, null);
}
@Override
public SmsResponse massTexting(List<String> phones, String message) {
LinkedHashMap<String, String> 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<String> phones, String templateId, LinkedHashMap<String, String> 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<String, String> messages,
String templateId, String code, String msgId) {
SmsResponse smsResponse;
JgConfig config = getConfig();
String url = JgUtils.buildUrl(config.getRequestUrl(), config.getAction(), msgId);
Map<String, String> headers = JgUtils.buildHeaders(config.getAccessKeyId(), config.getAccessKeySecret());
Map<String, Object> 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<String, String> 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());
}
}

View File

@ -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;
/**
* <p>类名: JgHelper
* <p>说明极光 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<String, String> buildHeaders(String accessKeyId, String accessKeySecret){
check(accessKeyId);
check(accessKeySecret);
Map<String, String> 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<String, Object> buildBody(String phone, LinkedHashMap<String, String> 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<String, Object> buildBody(String phone, String signId, String templateId) {
checkSingle(phone);
Map<String, Object> 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<String, Object> buildBody(String phone, String code, String voice, Integer ttl) {
checkSingle(phone);
Map<String, Object> 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<String, Object> buildBody(String code) {
check(code);
Map<String, Object> 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<String, Object> buildBody(String phone, String signId, String templateId, LinkedHashMap<String, String> messages) {
checkSingle(phone);
Map<String, Object> 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<String, Object> buildBody(String phone, String signId, String templateId,
String tag, LinkedHashMap<String, String> messages) {
Set<String> phones = build(phone);
Map<String, Object> 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<Map<String, Object>> recipients = new ArrayList<>(phones.size());
phones.forEach(mobile -> {
Map<String, Object> 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<String> 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<String> 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<String, String> 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);
}
}
}

View File

@ -3,6 +3,7 @@ package org.dromara.sms4j.lianlu.config;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
import org.dromara.sms4j.comm.constant.Constant;
import org.dromara.sms4j.comm.constant.SupplierConstant; import org.dromara.sms4j.comm.constant.SupplierConstant;
import org.dromara.sms4j.lianlu.req.LianLuRequest; import org.dromara.sms4j.lianlu.req.LianLuRequest;
import org.dromara.sms4j.lianlu.utils.LianLuUtils; import org.dromara.sms4j.lianlu.utils.LianLuUtils;
@ -10,7 +11,7 @@ import org.dromara.sms4j.provider.config.BaseConfig;
/** /**
* 联麓短信 * 联麓短信
* <a href="https://console.shlianlu.com/#/document/smsDoc">官方文档</a> * <a href=Constant.HTTPS_PREFIX + "console.shlianlu.com/#/document/smsDoc">官方文档</a>
* *
* @author lym * @author lym
*/ */
@ -35,7 +36,7 @@ public class LianLuConfig extends BaseConfig {
*/ */
private String signType = LianLuUtils.SIGN_TYPE_MD5; 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 @Override
public String getSupplier() { public String getSupplier() {

View File

@ -3,6 +3,8 @@ package org.dromara.sms4j.lianlu.service;
import cn.hutool.json.JSONObject; import cn.hutool.json.JSONObject;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.entity.SmsResponse; 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.constant.SupplierConstant;
import org.dromara.sms4j.comm.delayedTime.DelayedTime; import org.dromara.sms4j.comm.delayedTime.DelayedTime;
import org.dromara.sms4j.comm.exception.SmsBlendException; import org.dromara.sms4j.comm.exception.SmsBlendException;
@ -177,8 +179,8 @@ public class LianLuSmsImpl extends AbstractSmsBlend<LianLuConfig> {
try { try {
Map<String, String> headers = new HashMap<>(2); Map<String, String> headers = new HashMap<>(2);
headers.put("Content-Type", "application/json;charset=utf-8"); headers.put(Constant.CONTENT_TYPE, Constant.APPLICATION_JSON_UTF8);
headers.put("Accept", "application/json"); headers.put(Constant.ACCEPT, Constant.APPLICATION_JSON);
SmsResponse smsResponse = this.getResponse(this.http.postJson(reqUrl, headers, requestBody)); SmsResponse smsResponse = this.getResponse(this.http.postJson(reqUrl, headers, requestBody));
if (!smsResponse.isSuccess() && this.retry != this.getConfig().getMaxRetries()) { if (!smsResponse.isSuccess() && this.retry != this.getConfig().getMaxRetries()) {
return this.requestRetry(req); return this.requestRetry(req);
@ -199,10 +201,6 @@ public class LianLuSmsImpl extends AbstractSmsBlend<LianLuConfig> {
} }
private SmsResponse getResponse(JSONObject resJson) { private SmsResponse getResponse(JSONObject resJson) {
SmsResponse smsResponse = new SmsResponse(); return SmsRespUtils.resp(resJson, "00".equals(resJson.getStr("status")), getConfigId());
smsResponse.setSuccess("00".equals(resJson.getStr("status")));
smsResponse.setData(resJson);
smsResponse.setConfigId(this.getConfigId());
return smsResponse;
} }
} }

View File

@ -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;
/**
* <p>类名: LuoSiMaoConfig
* <p>说明 螺丝帽短信差异配置
*
* @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;
}
}

View File

@ -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;
/**
* <p>类名: LuoSiMaoFactory
*
* @author :bleachtred
* 2024/6/21 23:59
**/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class LuoSiMaoFactory extends AbstractProviderFactory<LuoSiMaoSmsImpl, LuoSiMaoConfig> {
private static final LuoSiMaoFactory INSTANCE = new LuoSiMaoFactory();
/**
* 获取建造者实例
* @return 建造者实例
*/
public static LuoSiMaoFactory instance() {
return INSTANCE;
}
/**
* <p> 建造一个短信实现对像
*
* @author :bleachtred
*/
@Override
public LuoSiMaoSmsImpl createSms(LuoSiMaoConfig config) {
return new LuoSiMaoSmsImpl(config);
}
/**
* 获取供应商
* @return 供应商
*/
@Override
public String getSupplier() {
return SupplierConstant.LUO_SI_MAO;
}
}

View File

@ -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;
/**
* <p>类名: LuoSiMaoSmsImpl
* <p>说明 螺丝帽短信差异配置
*
* @author :bleachtred
* 2024/6/21 23:59
**/
@Slf4j
public class LuoSiMaoSmsImpl extends AbstractSmsBlend<LuoSiMaoConfig> {
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<String, String> messages) {
throw new SmsBlendException("不支持此方法");
}
@Override
public SmsResponse sendMessage(String phone, String templateId, LinkedHashMap<String, String> messages) {
throw new SmsBlendException("不支持此方法");
}
@Override
public SmsResponse massTexting(List<String> phones, String message) {
return getSmsResponse(phones, message, null, false, false);
}
@Override
public SmsResponse massTexting(List<String> phones, String templateId, LinkedHashMap<String, String> messages) {
throw new SmsBlendException("不支持此方法");
}
/**
* 定时批量发送
* @param phones 手机号
* @param message 信息
* @param date 时间
* @return SmsResponse
*/
public SmsResponse massTextingOnTime(List<String> 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<String> phones, String message, Date date, boolean batch, boolean status) {
LuoSiMaoConfig config = getConfig();
SmsResponse smsResponse;
try {
String url = config.getHost() + config.getAction();
LinkedHashMap<String, Object> 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<String> 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());
}
}

View File

@ -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<String, String> buildHeaders(){
LinkedHashMap<String, String> headers = new LinkedHashMap<>(1);
headers.put(Constant.CONTENT_TYPE, Constant.APPLICATION_FROM_URLENCODED);
return headers;
}
public static LinkedHashMap<String, Object> buildBody(String phone, String message){
LinkedHashMap<String, Object> body = new LinkedHashMap<>(2);
body.put("mobile", StrUtil.addPrefixIfNot(phone, "+86"));
body.put("message", message);
return body;
}
public static LinkedHashMap<String, Object> buildBody(List<String> phones, String message, Date date){
LinkedHashMap<String, Object> 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;
}
}

View File

@ -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;
/**
* <p>类名: MasConfig
* <p>说明中国移动 云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;
}
}

View File

@ -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;
/**
* <p>类名: MasFactory
* <p>说明中国移动 云MAS短信配置器
*
* @author :bleachtred
* 2024/4/22 13:40
**/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class MasFactory extends AbstractProviderFactory<MasSmsImpl, MasConfig> {
private static final MasFactory INSTANCE = new MasFactory();
/**
* 获取建造者实例
* @return 建造者实例
*/
public static MasFactory instance() {
return INSTANCE;
}
/**
* createSms
* <p> 建造一个短信实现对像
*
* @author :bleachtred
*/
@Override
public MasSmsImpl createSms(MasConfig masConfig) {
return new MasSmsImpl(masConfig);
}
/**
* 获取供应商
* @return 供应商
*/
@Override
public String getSupplier() {
return SupplierConstant.MAS;
}
}

View File

@ -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;
/**
* <p>类名: MasSmsImpl
* <p>说明中国移动 云MAS短信实现
*
* @author :bleachtred
* 2024/4/22 13:40
**/
@Slf4j
public class MasSmsImpl extends AbstractSmsBlend<MasConfig> {
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<String, String> 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<String, String> messages) {
if (CollUtil.isEmpty(messages)){
messages = new LinkedHashMap<>();
}
String messageStr = JSONUtil.toJsonStr(messages);
return getSmsResponse(phone, messageStr, templateId);
}
@Override
public SmsResponse massTexting(List<String> phones, String message) {
return getSmsResponse(SmsUtils.addCodePrefixIfNot(phones), message, getConfig().getTemplateId());
}
@Override
public SmsResponse massTexting(List<String> phones, String templateId, LinkedHashMap<String, String> 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());
}
}

View File

@ -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<String, String> 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);
}
}

View File

@ -2,6 +2,7 @@ package org.dromara.sms4j.netease.config;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.dromara.sms4j.comm.constant.Constant;
import org.dromara.sms4j.comm.constant.SupplierConstant; import org.dromara.sms4j.comm.constant.SupplierConstant;
import org.dromara.sms4j.provider.config.BaseConfig; 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:不需要 * 是否需要支持短信上行true:需要false:不需要

View File

@ -9,6 +9,7 @@ import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil; import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.entity.SmsResponse; 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.Constant;
import org.dromara.sms4j.comm.constant.SupplierConstant; import org.dromara.sms4j.comm.constant.SupplierConstant;
import org.dromara.sms4j.comm.delayedTime.DelayedTime; import org.dromara.sms4j.comm.delayedTime.DelayedTime;
@ -97,7 +98,7 @@ public class NeteaseSmsImpl extends AbstractSmsBlend<NeteaseConfig> {
throw new SmsBlendException("单次发送超过最大发送上限建议每次群发短信人数低于100"); throw new SmsBlendException("单次发送超过最大发送上限建议每次群发短信人数低于100");
} }
Optional.ofNullable(getConfig().getTemplateId()).orElseThrow(() -> new SmsBlendException("模板ID不能为空")); 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 @Override
@ -133,7 +134,7 @@ public class NeteaseSmsImpl extends AbstractSmsBlend<NeteaseConfig> {
body.put("needUp", getConfig().getNeedUp()); body.put("needUp", getConfig().getNeedUp());
Map<String, String> headers = MapUtil.newHashMap(5, true); Map<String, String> 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("AppKey", getConfig().getAccessKeyId());
headers.put("Nonce", nonce); headers.put("Nonce", nonce);
headers.put("CurTime", curTime); headers.put("CurTime", curTime);
@ -142,9 +143,7 @@ public class NeteaseSmsImpl extends AbstractSmsBlend<NeteaseConfig> {
try { try {
smsResponse = getResponse(http.postFrom(requestUrl, headers, body)); smsResponse = getResponse(http.postFrom(requestUrl, headers, body));
} catch (SmsBlendException e) { } catch (SmsBlendException e) {
smsResponse = new SmsResponse(); smsResponse = errorResp(e.message);
smsResponse.setSuccess(false);
smsResponse.setData(e.getMessage());
} }
if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) { if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) {
retry = 0; retry = 0;
@ -161,11 +160,7 @@ public class NeteaseSmsImpl extends AbstractSmsBlend<NeteaseConfig> {
} }
private SmsResponse getResponse(JSONObject jsonObject) { private SmsResponse getResponse(JSONObject jsonObject) {
SmsResponse smsResponse = new SmsResponse(); return SmsRespUtils.resp(jsonObject, jsonObject.getInt("code") <= 200, getConfigId());
smsResponse.setSuccess(jsonObject.getInt("code") <= 200);
smsResponse.setData(jsonObject);
smsResponse.setConfigId(getConfigId());
return smsResponse;
} }
} }

View File

@ -22,6 +22,7 @@ public abstract class BaseConfig implements SupplierConfig {
* Access Key * Access Key
*/ */
private String accessKeyId; private String accessKeyId;
/** /**
* Sdk App Id * Sdk App Id
*/ */

View File

@ -1,7 +1,7 @@
package org.dromara.sms4j.provider.config; package org.dromara.sms4j.provider.config;
public class SmsBanner { public class SmsBanner {
private static final String banner = private static final String BANNER =
" ________ _____ ______ ________ ___ ___ ___ \n" + " ________ _____ ______ ________ ___ ___ ___ \n" +
"|\\ ____\\|\\ _ \\ _ \\|\\ ____\\|\\ \\ |\\ \\ |\\ \\ \n" + "|\\ ____\\|\\ _ \\ _ \\|\\ ____\\|\\ \\ |\\ \\ |\\ \\ \n" +
"\\ \\ \\___|\\ \\ \\\\\\__\\ \\ \\ \\ \\___|\\ \\ \\\\_\\ \\ \\ \\ \\ \n" + "\\ \\ \\___|\\ \\ \\\\\\__\\ \\ \\ \\ \\___|\\ \\ \\\\_\\ \\ \\ \\ \\ \n" +
@ -12,6 +12,6 @@ public class SmsBanner {
" \\|_________| \\|_________| \n"; " \\|_________| \\|_________| \n";
/** 初始化配置文件时打印banner*/ /** 初始化配置文件时打印banner*/
public static void PrintBanner(String version) { public static void PrintBanner(String version) {
System.out.println(banner+version); System.out.println(BANNER +version);
} }
} }

View File

@ -2,7 +2,7 @@ package org.dromara.sms4j.provider.config;
import lombok.Data; import lombok.Data;
import org.dromara.sms4j.comm.enumerate.ConfigType; import org.dromara.sms4j.comm.enums.ConfigType;
import java.util.ArrayList; import java.util.ArrayList;

View File

@ -17,13 +17,13 @@ import java.util.concurrent.ConcurrentHashMap;
*/ */
public class ProviderFactoryHolder { public class ProviderFactoryHolder {
private static final Map<String, BaseProviderFactory<? extends SmsBlend, ? extends SupplierConfig>> factories = new ConcurrentHashMap<>(); private static final Map<String, BaseProviderFactory<? extends SmsBlend, ? extends SupplierConfig>> FACTORIES = new ConcurrentHashMap<>();
public static void registerFactory(BaseProviderFactory<? extends SmsBlend, ? extends SupplierConfig> factory) { public static void registerFactory(BaseProviderFactory<? extends SmsBlend, ? extends SupplierConfig> factory) {
if(factory == null) { if(factory == null) {
throw new SmsBlendException("注册供应商工厂失败,工厂实例不能为空"); throw new SmsBlendException("注册供应商工厂失败,工厂实例不能为空");
} }
factories.put(factory.getSupplier(), factory); FACTORIES.put(factory.getSupplier(), factory);
} }
public static void registerFactory(List<BaseProviderFactory<? extends SmsBlend, ? extends SupplierConfig>> factoryList) { public static void registerFactory(List<BaseProviderFactory<? extends SmsBlend, ? extends SupplierConfig>> factoryList) {
@ -39,7 +39,7 @@ public class ProviderFactoryHolder {
} }
public static BaseProviderFactory<? extends SmsBlend, ? extends SupplierConfig> requireForSupplier(String supplier) { public static BaseProviderFactory<? extends SmsBlend, ? extends SupplierConfig> requireForSupplier(String supplier) {
return factories.getOrDefault(supplier, null); return FACTORIES.getOrDefault(supplier, null);
} }
} }

View File

@ -6,6 +6,7 @@ import org.dromara.sms4j.api.SmsBlend;
import org.dromara.sms4j.api.callback.CallBack; import org.dromara.sms4j.api.callback.CallBack;
import org.dromara.sms4j.api.entity.SmsResponse; import org.dromara.sms4j.api.entity.SmsResponse;
import org.dromara.sms4j.api.universal.SupplierConfig; 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.delayedTime.DelayedTime;
import org.dromara.sms4j.comm.utils.SmsHttpUtils; import org.dromara.sms4j.comm.utils.SmsHttpUtils;
import org.dromara.sms4j.provider.factory.BeanFactory; import org.dromara.sms4j.provider.factory.BeanFactory;
@ -62,7 +63,6 @@ public abstract class AbstractSmsBlend<C extends SupplierConfig> implements SmsB
* message 消息内容 * message 消息内容
* @author :Wind * @author :Wind
*/ */
@Override @Override
public abstract SmsResponse sendMessage(String phone, String message); public abstract SmsResponse sendMessage(String phone, String message);
@ -84,7 +84,6 @@ public abstract class AbstractSmsBlend<C extends SupplierConfig> implements SmsB
* @param messages key为模板变量名称 value为模板变量值 * @param messages key为模板变量名称 value为模板变量值
* @author :Wind * @author :Wind
*/ */
@Override @Override
public abstract SmsResponse sendMessage(String phone, String templateId, LinkedHashMap<String, String> messages); public abstract SmsResponse sendMessage(String phone, String templateId, LinkedHashMap<String, String> messages);
@ -94,7 +93,6 @@ public abstract class AbstractSmsBlend<C extends SupplierConfig> implements SmsB
* *
* @author :Wind * @author :Wind
*/ */
@Override @Override
public abstract SmsResponse massTexting(List<String> phones, String message); public abstract SmsResponse massTexting(List<String> phones, String message);
@ -104,7 +102,6 @@ public abstract class AbstractSmsBlend<C extends SupplierConfig> implements SmsB
* *
* @author :Wind * @author :Wind
*/ */
@Override @Override
public abstract SmsResponse massTexting(List<String> phones, String templateId, LinkedHashMap<String, String> messages); public abstract SmsResponse massTexting(List<String> phones, String templateId, LinkedHashMap<String, String> messages);
@ -145,7 +142,6 @@ public abstract class AbstractSmsBlend<C extends SupplierConfig> implements SmsB
* @param callBack 回调 * @param callBack 回调
* @author :Wind * @author :Wind
*/ */
@Override @Override
public final void sendMessageAsync(String phone, String templateId, LinkedHashMap<String, String> messages, CallBack callBack){ public final void sendMessageAsync(String phone, String templateId, LinkedHashMap<String, String> messages, CallBack callBack){
CompletableFuture<SmsResponse> smsResponseCompletableFuture = CompletableFuture.supplyAsync(() -> sendMessage(phone,templateId, messages), pool); CompletableFuture<SmsResponse> smsResponseCompletableFuture = CompletableFuture.supplyAsync(() -> sendMessage(phone,templateId, messages), pool);
@ -240,4 +236,13 @@ public abstract class AbstractSmsBlend<C extends SupplierConfig> implements SmsB
} }
}, delayedTime); }, delayedTime);
} }
/**
* 返回异常
* @param errorMsg 异常信息
* @return SmsResponse
*/
public SmsResponse errorResp(String errorMsg){
return SmsRespUtils.error(errorMsg, config.getConfigId());
}
} }

View File

@ -2,11 +2,12 @@ package org.dromara.sms4j.qiniu.config;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.dromara.sms4j.comm.constant.Constant;
import org.dromara.sms4j.comm.constant.SupplierConstant; import org.dromara.sms4j.comm.constant.SupplierConstant;
import org.dromara.sms4j.provider.config.BaseConfig; import org.dromara.sms4j.provider.config.BaseConfig;
/** /**
* @author Administrator * @author YYM
* @Date: 2024/1/30 15:56 30 * @Date: 2024/1/30 15:56 30
* @描述: QiNiuConfig * @描述: 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";
/** /**
* 模板变量名称 * 模板变量名称

View File

@ -7,7 +7,7 @@ import org.dromara.sms4j.provider.factory.AbstractProviderFactory;
import org.dromara.sms4j.qiniu.service.QiNiuSmsImpl; import org.dromara.sms4j.qiniu.service.QiNiuSmsImpl;
/** /**
* @author Administrator * @author YYM
* @Date: 2024/1/30 16:06 29 * @Date: 2024/1/30 16:06 29
* @描述: QiNiuFactory * @描述: QiNiuFactory
**/ **/

View File

@ -1,11 +1,13 @@
package org.dromara.sms4j.qiniu.service; package org.dromara.sms4j.qiniu.service;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONObject; import cn.hutool.json.JSONObject;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.entity.SmsResponse; 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.constant.SupplierConstant;
import org.dromara.sms4j.comm.delayedTime.DelayedTime; 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.provider.service.AbstractSmsBlend;
import org.dromara.sms4j.qiniu.config.QiNiuConfig; import org.dromara.sms4j.qiniu.config.QiNiuConfig;
import org.dromara.sms4j.qiniu.util.QiNiuUtils; import org.dromara.sms4j.qiniu.util.QiNiuUtils;
@ -17,7 +19,7 @@ import java.util.Objects;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
/** /**
* @author Administrator * @author YYM
* @Date: 2024/1/30 16:06 59 * @Date: 2024/1/30 16:06 59
* @描述: QiNiuSmsImpl * @描述: QiNiuSmsImpl
**/ **/
@ -71,7 +73,6 @@ public class QiNiuSmsImpl extends AbstractSmsBlend<QiNiuConfig> {
return senMassMsg(phones, templateId, messages); return senMassMsg(phones, templateId, messages);
} }
/** /**
* @return SmsResponse * @return SmsResponse
* @author 初拥 * @author 初拥
@ -79,11 +80,14 @@ public class QiNiuSmsImpl extends AbstractSmsBlend<QiNiuConfig> {
* @Description: 统一处理返回结果 * @Description: 统一处理返回结果
*/ */
public SmsResponse handleRes(String url, HashMap<String, Object> params) { public SmsResponse handleRes(String url, HashMap<String, Object> params) {
JSONObject jsonObject = http.postJson(url, QiNiuUtils.getHeaderAndSign(url, params, getConfig()), params); JSONObject jsonObject;
SmsResponse smsResponse = new SmsResponse(); SmsResponse smsResponse;
smsResponse.setSuccess(ObjectUtil.isEmpty(jsonObject.getStr("error"))); try {
smsResponse.setData(jsonObject); jsonObject = http.postJson(url, QiNiuUtils.getHeaderAndSign(url, params, getConfig()), params);
smsResponse.setConfigId(getConfigId()); smsResponse = SmsRespUtils.resp(jsonObject, SmsUtils.isEmpty(jsonObject.getStr("error")), getConfigId());
}catch (SmsBlendException e){
smsResponse = errorResp(e.message);
}
if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) { if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) {
retry = 0; retry = 0;
return smsResponse; return smsResponse;

View File

@ -8,19 +8,18 @@ import lombok.Data;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.comm.constant.Constant; import org.dromara.sms4j.comm.constant.Constant;
import org.dromara.sms4j.comm.exception.SmsBlendException; import org.dromara.sms4j.comm.exception.SmsBlendException;
import org.dromara.sms4j.comm.utils.SmsDateUtils;
import org.dromara.sms4j.qiniu.config.QiNiuConfig; import org.dromara.sms4j.qiniu.config.QiNiuConfig;
import java.net.URI; import java.net.URI;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Base64; import java.util.Base64;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.TimeZone;
/** /**
* @author Administrator * @author YYM
* @Date: 2024/1/30 16:37 50 * @Date: 2024/1/30 16:37 50
* @描述: QiNiuUtils * @描述: QiNiuUtils
**/ **/
@ -35,7 +34,7 @@ public class QiNiuUtils {
StringBuilder dataToSign = new StringBuilder(); StringBuilder dataToSign = new StringBuilder();
dataToSign.append(method.toUpperCase()).append(" ").append(reqUrl.getPath()); dataToSign.append(method.toUpperCase()).append(" ").append(reqUrl.getPath());
dataToSign.append("\nHost: ").append(reqUrl.getHost()); 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").append("X-Qiniu-Date").append(": ").append(signDate);
dataToSign.append("\n\n"); dataToSign.append("\n\n");
if (ObjectUtil.isNotEmpty(body)) { if (ObjectUtil.isNotEmpty(body)) {
@ -50,9 +49,7 @@ public class QiNiuUtils {
public static Map<String, String> getHeaderAndSign(String url, HashMap<String, Object> hashMap, QiNiuConfig qiNiuConfig) { public static Map<String, String> getHeaderAndSign(String url, HashMap<String, Object> hashMap, QiNiuConfig qiNiuConfig) {
String signature; String signature;
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"); String signDate = SmsDateUtils.pureDateUtcGmt(new Date());
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
String signDate = dateFormat.format(new Date());
try { try {
signature = getSignature("POST", url, qiNiuConfig, JSONUtil.toJsonStr(hashMap), signDate); signature = getSignature("POST", url, qiNiuConfig, JSONUtil.toJsonStr(hashMap), signDate);
} catch (Exception e) { } catch (Exception e) {
@ -62,9 +59,9 @@ public class QiNiuUtils {
//请求头 //请求头
Map<String, String> header = new HashMap<>(3); Map<String, String> header = new HashMap<>(3);
header.put("Authorization", signature); header.put(Constant.AUTHORIZATION, signature);
header.put("X-Qiniu-Date", signDate); header.put("X-Qiniu-Date", signDate);
header.put("Content-Type", "application/json"); header.put(Constant.CONTENT_TYPE, "application/json");
return header; return header;
} }
} }

View File

@ -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;
/**
* <p>类名: SubMailConfig
* <p>说明 SUBMAIL短信差异配置
*
* @author :bleachtred
* 2024/6/22 13:59
**/
@Data
@EqualsAndHashCode(callSuper = true)
public class SubMailConfig extends BaseConfig {
/**
* 请求地址
*/
private String host = Constant.HTTPS_PREFIX + "api-v4.mysubmail.com/sms/";
/**
* 接口名称
* 短信发送 send.json
* 短信模板发送 xsend.json
* 短信一对多发送 multisend.json
* 短信模板一对多发送 multixsend.json
* 短信批量群发 batchsend.json
* 短信批量模板群发 batchxsend.json
*/
private String action = "send.json";
/**
* MD5 SHA-1 默认MD5 填写任意值不为即为 密匙明文验证模式
*/
private String signType = "MD5";
/**
* signature加密计算方式
* (当sign_version传2时会忽略某些字段)
*/
private String signVersion;
/**
* 获取供应商
*
* @since 3.0.0
*/
@Override
public String getSupplier() {
return SupplierConstant.MY_SUBMAIL;
}
}

View File

@ -0,0 +1,47 @@
package org.dromara.sms4j.submail.config;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.dromara.sms4j.comm.constant.SupplierConstant;
import org.dromara.sms4j.provider.factory.AbstractProviderFactory;
import org.dromara.sms4j.submail.service.SubMailSmsImpl;
/**
* <p>类名: SubMailFactory
*
* @author :bleachtred
* 2024/6/22 13:59
**/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class SubMailFactory extends AbstractProviderFactory<SubMailSmsImpl, SubMailConfig> {
private static final SubMailFactory INSTANCE = new SubMailFactory();
/**
* 获取建造者实例
* @return 建造者实例
*/
public static SubMailFactory instance() {
return INSTANCE;
}
/**
* <p> 建造一个短信实现对像
*
* @author :bleachtred
*/
@Override
public SubMailSmsImpl createSms(SubMailConfig config) {
return new SubMailSmsImpl(config);
}
/**
* 获取供应商
* @return 供应商
*/
@Override
public String getSupplier() {
return SupplierConstant.MY_SUBMAIL;
}
}

View File

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

View File

@ -0,0 +1,49 @@
package org.dromara.sms4j.submail.utils;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestAlgorithm;
import cn.hutool.crypto.digest.DigestUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.comm.constant.Constant;
import org.dromara.sms4j.comm.utils.SmsUtils;
import java.util.LinkedHashMap;
/**
* <p>类名: SubMailUtils
*
* @author :bleachtred
* 2024/6/22 13:59
**/
@Slf4j
public class SubMailUtils {
public static LinkedHashMap<String, String> buildHeaders(){
LinkedHashMap<String, String> headers = new LinkedHashMap<>(1);
headers.put(Constant.ACCEPT, Constant.APPLICATION_JSON);
headers.put(Constant.CONTENT_TYPE, Constant.APPLICATION_JSON);
return headers;
}
public static String signature(LinkedHashMap<String, Object> body, String signType, String accessKeyId, String accessKeySecret, String... excludes){
if (StrUtil.containsAnyIgnoreCase(signType, DigestAlgorithm.MD5.getValue(), DigestAlgorithm.SHA1.getValue())){
StringBuilder sb = new StringBuilder();
sb.append(accessKeyId).append(accessKeySecret);
if ("2".equals(Convert.toStr(body.get("sign_version")))){
sb.append(SmsUtils.sortedParamsAsc(body, excludes));
}else {
sb.append(SmsUtils.sortedParamsAsc(body));
}
sb.append(accessKeyId).append(accessKeySecret);
if (signType.equalsIgnoreCase(DigestAlgorithm.MD5.getValue())){
return DigestUtil.md5Hex(sb.toString());
}else {
return DigestUtil.sha1Hex(sb.toString());
}
}else {
return accessKeySecret;
}
}
}

View File

@ -5,6 +5,7 @@ import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject; import cn.hutool.json.JSONObject;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.entity.SmsResponse; 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.Constant;
import org.dromara.sms4j.comm.constant.SupplierConstant; import org.dromara.sms4j.comm.constant.SupplierConstant;
import org.dromara.sms4j.comm.delayedTime.DelayedTime; import org.dromara.sms4j.comm.delayedTime.DelayedTime;
@ -14,11 +15,9 @@ import org.dromara.sms4j.provider.service.AbstractSmsBlend;
import org.dromara.sms4j.tencent.config.TencentConfig; import org.dromara.sms4j.tencent.config.TencentConfig;
import org.dromara.sms4j.tencent.utils.TencentUtils; import org.dromara.sms4j.tencent.utils.TencentUtils;
import java.util.ArrayList;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
/** /**
@ -44,12 +43,7 @@ public class TencentSmsImpl extends AbstractSmsBlend<TencentConfig> {
@Override @Override
public SmsResponse sendMessage(String phone, String message) { public SmsResponse sendMessage(String phone, String message) {
String[] split = message.split("&"); return sendMessage(phone, getConfig().getTemplateId(), SmsUtils.buildMessageByAmpersand(message));
LinkedHashMap<String, String> map = new LinkedHashMap<>();
for (int i = 0; i < split.length; i++) {
map.put(String.valueOf(i), split[i]);
}
return sendMessage(phone, getConfig().getTemplateId(), map);
} }
@Override @Override
@ -59,38 +53,17 @@ public class TencentSmsImpl extends AbstractSmsBlend<TencentConfig> {
@Override @Override
public SmsResponse sendMessage(String phone, String templateId, LinkedHashMap<String, String> messages) { public SmsResponse sendMessage(String phone, String templateId, LinkedHashMap<String, String> messages) {
if (Objects.isNull(messages)){ return getSmsResponse(new String[]{StrUtil.addPrefixIfNot(phone, "+86")}, SmsUtils.toArray(messages), templateId);
messages = new LinkedHashMap<>();
}
List<String> list = new ArrayList<>();
for (Map.Entry<String, String> entry : messages.entrySet()) {
list.add(entry.getValue());
}
String[] s = new String[list.size()];
return getSmsResponse(new String[]{StrUtil.addPrefixIfNot(phone, "+86")}, list.toArray(s), templateId);
} }
@Override @Override
public SmsResponse massTexting(List<String> phones, String message) { public SmsResponse massTexting(List<String> phones, String message) {
String[] split = message.split("&"); return massTexting(phones, getConfig().getTemplateId(), SmsUtils.buildMessageByAmpersand(message));
LinkedHashMap<String, String> map = new LinkedHashMap<>();
for (int i = 0; i < split.length; i++) {
map.put(String.valueOf(i), split[i]);
}
return massTexting(phones, getConfig().getTemplateId(), map);
} }
@Override @Override
public SmsResponse massTexting(List<String> phones, String templateId, LinkedHashMap<String, String> messages) { public SmsResponse massTexting(List<String> phones, String templateId, LinkedHashMap<String, String> messages) {
if (Objects.isNull(messages)){ return getSmsResponse(SmsUtils.addCodePrefixIfNotToArray(phones), SmsUtils.toArray(messages), templateId);
messages = new LinkedHashMap<>();
}
List<String> list = new ArrayList<>();
for (Map.Entry<String, String> entry : messages.entrySet()) {
list.add(entry.getValue());
}
String[] s = new String[list.size()];
return getSmsResponse(SmsUtils.listToArray(phones), list.toArray(s), templateId);
} }
private SmsResponse getSmsResponse(String[] phones, String[] messages, String templateId) { private SmsResponse getSmsResponse(String[] phones, String[] messages, String templateId) {
@ -112,9 +85,7 @@ public class TencentSmsImpl extends AbstractSmsBlend<TencentConfig> {
try { try {
smsResponse = getResponse(http.postJson(url, headsMap, requestBody)); smsResponse = getResponse(http.postJson(url, headsMap, requestBody));
} catch (SmsBlendException e) { } catch (SmsBlendException e) {
smsResponse = new SmsResponse(); smsResponse = errorResp(e.message);
smsResponse.setSuccess(false);
smsResponse.setData(e.getMessage());
} }
if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) { if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) {
retry = 0; retry = 0;
@ -131,15 +102,12 @@ public class TencentSmsImpl extends AbstractSmsBlend<TencentConfig> {
} }
private SmsResponse getResponse(JSONObject resJson) { private SmsResponse getResponse(JSONObject resJson) {
SmsResponse smsResponse = new SmsResponse();
JSONObject response = resJson.getJSONObject("Response"); JSONObject response = resJson.getJSONObject("Response");
// 根据 Error 判断是否配置错误 // 根据 Error 判断是否配置错误
String error = response.getStr("Error"); boolean success = StrUtil.isBlank(response.getStr("Error"));
smsResponse.setSuccess(StrUtil.isBlank(error));
// 根据 SendStatusSet 判断是否不为Ok // 根据 SendStatusSet 判断是否不为Ok
JSONArray sendStatusSet = response.getJSONArray("SendStatusSet"); JSONArray sendStatusSet = response.getJSONArray("SendStatusSet");
if (sendStatusSet != null) { if (sendStatusSet != null) {
boolean success = true;
for (Object obj : sendStatusSet) { for (Object obj : sendStatusSet) {
JSONObject jsonObject = (JSONObject) obj; JSONObject jsonObject = (JSONObject) obj;
String code = jsonObject.getStr("Code"); String code = jsonObject.getStr("Code");
@ -148,11 +116,8 @@ public class TencentSmsImpl extends AbstractSmsBlend<TencentConfig> {
break; break;
} }
} }
smsResponse.setSuccess(success);
} }
smsResponse.setData(resJson); return SmsRespUtils.resp(resJson, success, getConfigId());
smsResponse.setConfigId(getConfigId());
return smsResponse;
} }
} }

View File

@ -1,20 +1,19 @@
package org.dromara.sms4j.tencent.utils; package org.dromara.sms4j.tencent.utils;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.crypto.digest.HMac; import cn.hutool.crypto.digest.HMac;
import cn.hutool.crypto.digest.HmacAlgorithm; import cn.hutool.crypto.digest.HmacAlgorithm;
import cn.hutool.json.JSONUtil; import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.comm.constant.Constant;
import org.dromara.sms4j.comm.utils.SmsDateUtils;
import org.dromara.sms4j.tencent.config.TencentConfig; import org.dromara.sms4j.tencent.config.TencentConfig;
import javax.xml.bind.DatatypeConverter; import javax.xml.bind.DatatypeConverter;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.TimeZone;
/** /**
* @author Richard * @author Richard
@ -31,18 +30,14 @@ public class TencentUtils {
*/ */
private static final String HTTP_REQUEST_METHOD = "POST"; private static final String HTTP_REQUEST_METHOD = "POST";
private static final String CT_JSON = "application/json; charset=utf-8";
private static byte[] hmac256(byte[] key, String msg) { private static byte[] hmac256(byte[] key, String msg) {
HMac hMac = new HMac(HmacAlgorithm.HmacSHA256, key); HMac hMac = new HMac(HmacAlgorithm.HmacSHA256, key);
return hMac.digest(msg.getBytes(StandardCharsets.UTF_8)); return hMac.digest(msg);
} }
private static String sha256Hex(String s) throws Exception { private static String sha256Hex(String s) throws Exception {
MessageDigest md = MessageDigest.getInstance("SHA-256"); return DatatypeConverter.printHexBinary(DigestUtil.sha256(s)).toLowerCase();
byte[] d = md.digest(s.getBytes(StandardCharsets.UTF_8));
return DatatypeConverter.printHexBinary(d).toLowerCase();
} }
/** /**
@ -52,13 +47,11 @@ public class TencentUtils {
* @param messages 短信内容 * @param messages 短信内容
* @param phones 手机号 * @param phones 手机号
* @param timestamp 时间戳 * @param timestamp 时间戳
* @throws Exception * @throws Exception Exception
*/ */
public static String generateSignature(TencentConfig tencentConfig, String templateId, String[] messages, String[] phones, public static String generateSignature(TencentConfig tencentConfig, String templateId, String[] messages, String[] phones,
String timestamp) throws Exception { String timestamp) throws Exception {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); String date = SmsDateUtils.normDateGmt(new Date(Long.parseLong(timestamp + "000")));
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
String date = sdf.format(new Date(Long.parseLong(timestamp + "000")));
String canonicalUri = "/"; String canonicalUri = "/";
String canonicalQueryString = ""; String canonicalQueryString = "";
String canonicalHeaders = "content-type:application/json; charset=utf-8\nhost:" + tencentConfig.getRequestUrl() + "\n"; String canonicalHeaders = "content-type:application/json; charset=utf-8\nhost:" + tencentConfig.getRequestUrl() + "\n";
@ -95,8 +88,8 @@ public class TencentUtils {
public static Map<String, String> generateHeadsMap(String authorization, String timestamp, String action, public static Map<String, String> generateHeadsMap(String authorization, String timestamp, String action,
String version, String territory, String requestUrl) { String version, String territory, String requestUrl) {
Map<String, String> headers = new HashMap<>(); Map<String, String> headers = new HashMap<>();
headers.put("Authorization", authorization); headers.put(Constant.AUTHORIZATION, authorization);
headers.put("Content-Type", CT_JSON); headers.put(Constant.CONTENT_TYPE, Constant.APPLICATION_JSON_UTF8);
headers.put("Host", requestUrl); headers.put("Host", requestUrl);
headers.put("X-TC-Action", action); headers.put("X-TC-Action", action);
headers.put("X-TC-Timestamp", timestamp); headers.put("X-TC-Timestamp", timestamp);
@ -113,7 +106,7 @@ public class TencentUtils {
* @param signatureName 短信签名 * @param signatureName 短信签名
* @param templateId 模板id * @param templateId 模板id
* @param templateParamSet 模板参数 * @param templateParamSet 模板参数
* @return * @return Map
*/ */
public static Map<String, Object> generateRequestBody(String[] phones, String sdkAppId, String signatureName, public static Map<String, Object> generateRequestBody(String[] phones, String sdkAppId, String signatureName,
String templateId, String[] templateParamSet) { String templateId, String[] templateParamSet) {

View File

@ -1,5 +1,6 @@
package org.dromara.sms4j.unisms.core; package org.dromara.sms4j.unisms.core;
import org.dromara.sms4j.comm.constant.Constant;
/** /**
* 初始化统一环境的单例类. * 初始化统一环境的单例类.
@ -8,8 +9,8 @@ public class Uni {
/** 模仿SDK版本*/ /** 模仿SDK版本*/
public static final String VERSION = "0.0.4"; public static final String VERSION = "0.0.4";
public static final String signingAlgorithm = "hmac-sha256"; public static final String SIGNING_ALGORITHM = "hmac-sha256";
public static String endpoint = System.getenv().getOrDefault("UNI_ENDPOINT", "https://uni.apistd.com"); public static String endpoint = System.getenv().getOrDefault("UNI_ENDPOINT", Constant.HTTPS_PREFIX + "uni.apistd.com");
public static String accessKeyId = System.getenv("UNI_ACCESS_KEY_ID"); public static String accessKeyId = System.getenv("UNI_ACCESS_KEY_ID");
private static String accessKeySecret = System.getenv("UNI_ACCESS_KEY_SECRET"); private static String accessKeySecret = System.getenv("UNI_ACCESS_KEY_SECRET");
@ -86,7 +87,7 @@ public class Uni {
builder.isSimple(false); builder.isSimple(false);
} }
builder.endpoint(Uni.endpoint); builder.endpoint(Uni.endpoint);
builder.signingAlgorithm(Uni.signingAlgorithm); builder.signingAlgorithm(Uni.SIGNING_ALGORITHM);
builder.setRetryInterval(retryInterval); builder.setRetryInterval(retryInterval);
builder.setMaxRetries(maxRetries); builder.setMaxRetries(maxRetries);
return builder.build(); return builder.build();

View File

@ -85,8 +85,8 @@ public class UniClient {
public UniResponse request(final String action, final Map<String, Object> data) throws SmsBlendException { public UniResponse request(final String action, final Map<String, Object> data) throws SmsBlendException {
Map<String, String> headers = new HashMap<>(); Map<String, String> headers = new HashMap<>();
headers.put("User-Agent", USER_AGENT); headers.put("User-Agent", USER_AGENT);
headers.put("Content-Type", Constant.APPLICATION_JSON_UTF8); headers.put(Constant.CONTENT_TYPE, Constant.APPLICATION_JSON_UTF8);
headers.put("Accept", Constant.ACCEPT); headers.put(Constant.ACCEPT, Constant.APPLICATION_JSON);
String url; String url;
if (this.isSimple) { if (this.isSimple) {
url = this.endpoint + "?action=" + action + "&accessKeyId=" + this.accessKeyId; url = this.endpoint + "?action=" + action + "&accessKeyId=" + this.accessKeyId;

View File

@ -6,7 +6,6 @@ import org.dromara.sms4j.comm.exception.SmsBlendException;
import java.util.Objects; import java.util.Objects;
public class UniResponse { public class UniResponse {
public static final String REQUEST_ID_HEADER_KEY = "x-uni-request-id";
public String requestId; public String requestId;
public String code; public String code;
public String message; public String message;

View File

@ -3,6 +3,7 @@ package org.dromara.sms4j.unisms.service;
import cn.hutool.core.map.MapUtil; import cn.hutool.core.map.MapUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.entity.SmsResponse; 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.constant.SupplierConstant;
import org.dromara.sms4j.comm.delayedTime.DelayedTime; import org.dromara.sms4j.comm.delayedTime.DelayedTime;
import org.dromara.sms4j.comm.exception.SmsBlendException; import org.dromara.sms4j.comm.exception.SmsBlendException;
@ -96,16 +97,12 @@ public class UniSmsImpl extends AbstractSmsBlend<UniConfig> {
} }
private SmsResponse getSmsResponse(Map<String, Object> data) { private SmsResponse getSmsResponse(Map<String, Object> data) {
SmsResponse smsResponse = new SmsResponse();
try { try {
UniResponse send = Uni.getClient(getConfig().getRetryInterval(), getConfig().getMaxRetries()).request("sms.message.send", data); UniResponse send = Uni.getClient(getConfig().getRetryInterval(), getConfig().getMaxRetries()).request("sms.message.send", data);
smsResponse.setSuccess("0".equals(send.code)); return SmsRespUtils.resp(send, "0".equals(send.code), getConfigId());
smsResponse.setData(send);
smsResponse.setConfigId(getConfigId());
} catch (Exception e) { } catch (Exception e) {
smsResponse.setSuccess(false); return errorResp(e.getMessage());
} }
return smsResponse;
} }
} }

View File

@ -3,6 +3,7 @@ package org.dromara.sms4j.yunpian.service;
import cn.hutool.json.JSONObject; import cn.hutool.json.JSONObject;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.entity.SmsResponse; 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.Constant;
import org.dromara.sms4j.comm.constant.SupplierConstant; import org.dromara.sms4j.comm.constant.SupplierConstant;
import org.dromara.sms4j.comm.delayedTime.DelayedTime; import org.dromara.sms4j.comm.delayedTime.DelayedTime;
@ -40,15 +41,10 @@ public class YunPianSmsImpl extends AbstractSmsBlend<YunpianConfig> {
} }
private SmsResponse getResponse(JSONObject execute) { private SmsResponse getResponse(JSONObject execute) {
SmsResponse smsResponse = new SmsResponse();
if (execute == null) { if (execute == null) {
smsResponse.setSuccess(false); return SmsRespUtils.error(getConfigId());
return smsResponse;
} }
smsResponse.setSuccess(execute.getInt("code") == 0); return SmsRespUtils.resp(execute, execute.getInt("code") == 0, getConfigId());
smsResponse.setData(execute);
smsResponse.setConfigId(getConfigId());
return smsResponse;
} }
@Override @Override
@ -60,9 +56,7 @@ public class YunPianSmsImpl extends AbstractSmsBlend<YunpianConfig> {
try { try {
smsResponse = getResponse(http.postFrom(Constant.YUNPIAN_URL + "/sms/tpl_single_send.json", headers, body)); smsResponse = getResponse(http.postFrom(Constant.YUNPIAN_URL + "/sms/tpl_single_send.json", headers, body));
} catch (SmsBlendException e) { } catch (SmsBlendException e) {
smsResponse = new SmsResponse(); smsResponse = errorResp(e.message);
smsResponse.setSuccess(false);
smsResponse.setData(e.getMessage());
} }
if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) { if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) {
retry = 0; retry = 0;
@ -119,7 +113,7 @@ public class YunPianSmsImpl extends AbstractSmsBlend<YunpianConfig> {
if (phones.size() > 1000) { if (phones.size() > 1000) {
throw new SmsBlendException("单次发送超过最大发送上限建议每次群发短信人数低于1000"); throw new SmsBlendException("单次发送超过最大发送上限建议每次群发短信人数低于1000");
} }
return sendMessage(SmsUtils.listToString(phones), message); return sendMessage(SmsUtils.joinComma(phones), message);
} }
@Override @Override
@ -130,7 +124,7 @@ public class YunPianSmsImpl extends AbstractSmsBlend<YunpianConfig> {
if (phones.size() > 1000) { if (phones.size() > 1000) {
throw new SmsBlendException("单次发送超过最大发送上限建议每次群发短信人数低于1000"); throw new SmsBlendException("单次发送超过最大发送上限建议每次群发短信人数低于1000");
} }
return sendMessage(SmsUtils.listToString(phones), templateId, messages); return sendMessage(SmsUtils.joinComma(phones), templateId, messages);
} }
private String formattingMap(Map<String, String> messages) { private String formattingMap(Map<String, String> messages) {
@ -170,8 +164,8 @@ public class YunPianSmsImpl extends AbstractSmsBlend<YunpianConfig> {
private Map<String, String> getHeaders() { private Map<String, String> getHeaders() {
Map<String, String> headers = new HashMap<>(); Map<String, String> headers = new HashMap<>();
headers.put("Accept", Constant.APPLICATION_JSON_UTF8); headers.put(Constant.ACCEPT, Constant.APPLICATION_JSON_UTF8);
headers.put("Content-Type", Constant.FROM_URLENCODED); headers.put(Constant.CONTENT_TYPE, Constant.APPLICATION_FROM_URLENCODED);
return headers; return headers;
} }
} }

View File

@ -2,6 +2,7 @@ package org.dromara.sms4j.zhutong.config;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.dromara.sms4j.comm.constant.Constant;
import org.dromara.sms4j.comm.constant.SupplierConstant; import org.dromara.sms4j.comm.constant.SupplierConstant;
import org.dromara.sms4j.provider.config.BaseConfig; import org.dromara.sms4j.provider.config.BaseConfig;
@ -20,15 +21,15 @@ import org.dromara.sms4j.provider.config.BaseConfig;
public class ZhutongConfig extends BaseConfig { public class ZhutongConfig extends BaseConfig {
/** /**
* 模板变量名称 * 模板变量名称
* 查看地址https://mix2.zthysms.com/index.html#/TemplateManagement * 查看地址Constant.HTTPS_PREFIX + mix2.zthysms.com/index.html#/TemplateManagement
* 允许为空为空使用无模板形式发送短信 * 允许为空为空使用无模板形式发送短信
*/ */
private String templateName; private String templateName;
/** /**
* 默认请求地址 * 默认请求地址
* 不同区域可切换请求地址也可以不修改请参考官方文档https://doc.zthysms.com/web/#/1/236 * 不同区域可切换请求地址也可以不修改请参考官方文档Constant.HTTPS_PREFIX + doc.zthysms.com/web/#/1/236
*/ */
private String requestUrl = "https://api.mix2.zthysms.com/"; private String requestUrl = Constant.HTTPS_PREFIX + "api.mix2.zthysms.com/";
/** /**
* 获取供应商 * 获取供应商

View File

@ -10,6 +10,7 @@ import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject; import cn.hutool.json.JSONObject;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.entity.SmsResponse; 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.Constant;
import org.dromara.sms4j.comm.constant.SupplierConstant; import org.dromara.sms4j.comm.constant.SupplierConstant;
import org.dromara.sms4j.comm.delayedTime.DelayedTime; import org.dromara.sms4j.comm.delayedTime.DelayedTime;
@ -147,14 +148,12 @@ public class ZhutongSmsImpl extends AbstractSmsBlend<ZhutongConfig> {
json.put("content", content); json.put("content", content);
Map<String, String> headers = MapUtil.newHashMap(1, true); Map<String, String> headers = MapUtil.newHashMap(1, true);
headers.put("Content-Type", Constant.APPLICATION_JSON_UTF8); headers.put(Constant.CONTENT_TYPE, Constant.APPLICATION_JSON_UTF8);
SmsResponse smsResponse; SmsResponse smsResponse;
try { try {
smsResponse = getResponse(http.postJson(url, headers, json)); smsResponse = getResponse(http.postJson(url, headers, json));
} catch (SmsBlendException e) { } catch (SmsBlendException e) {
smsResponse = new SmsResponse(); smsResponse = errorResp(e.message);
smsResponse.setSuccess(false);
smsResponse.setData(e.getMessage());
} }
if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) { if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) {
retry = 0; retry = 0;
@ -235,14 +234,12 @@ public class ZhutongSmsImpl extends AbstractSmsBlend<ZhutongConfig> {
requestJson.set("records", records); requestJson.set("records", records);
Map<String, String> headers = MapUtil.newHashMap(1, true); Map<String, String> headers = MapUtil.newHashMap(1, true);
headers.put("Content-Type", Constant.APPLICATION_JSON_UTF8); headers.put(Constant.CONTENT_TYPE, Constant.APPLICATION_JSON_UTF8);
SmsResponse smsResponse; SmsResponse smsResponse;
try { try {
smsResponse = getResponse(http.postJson(url, headers, requestJson.toString())); smsResponse = getResponse(http.postJson(url, headers, requestJson.toString()));
} catch (SmsBlendException e) { } catch (SmsBlendException e) {
smsResponse = new SmsResponse(); smsResponse = errorResp(e.message);
smsResponse.setSuccess(false);
smsResponse.setData(e.getMessage());
} }
if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) { if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) {
retry = 0; retry = 0;
@ -263,11 +260,7 @@ public class ZhutongSmsImpl extends AbstractSmsBlend<ZhutongConfig> {
} }
private SmsResponse getResponse(JSONObject jsonObject) { private SmsResponse getResponse(JSONObject jsonObject) {
SmsResponse smsResponse = new SmsResponse(); return SmsRespUtils.resp(jsonObject, jsonObject.getInt("code", -1) <= 200, getConfigId());
smsResponse.setSuccess(jsonObject.getInt("code", -1) <= 200);
smsResponse.setData(jsonObject);
smsResponse.setConfigId(getConfigId());
return smsResponse;
} }
private void validator(String requestUrl, String username, String password) { private void validator(String requestUrl, String username, String password) {

View File

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.dromara.sms4j</groupId>
<artifactId>sms4j</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>sms4j-solon-plugin-example</artifactId>
<properties>
<maven.install.skip>true</maven.install.skip>
<maven.deploy.skip>true</maven.deploy.skip>
</properties>
<dependencies>
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon-web</artifactId>
<version>${solon.version}</version>
</dependency>
<!--单元测试-->
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon-test-junit5</artifactId>
<version>${solon.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.dromara.sms4j</groupId>
<artifactId>sms4j-oa-core</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.dromara.sms4j</groupId>
<artifactId>sms4j-solon-plugin</artifactId>
<version>${revision}</version>
</dependency>
<!-- 京东云短信依赖 -->
<dependency>
<groupId>com.jdcloud.sdk</groupId>
<artifactId>sms</artifactId>
<version>${jdcloud.version}</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,15 @@
package org.dromara.sms4j.example;
import org.noear.solon.Solon;
/**
* 主类
*
* @author handy
*/
public class Sms4jApp {
public static void main(String[] args) {
Solon.start(Sms4jApp.class, args);
}
}

View File

@ -0,0 +1,18 @@
package org.dromara.sms4j.example.zhangjun;
import org.dromara.sms4j.core.factory.SmsFactory;
import org.noear.solon.Solon;
/**
* 自定义广州掌骏短信实现
*
* @author 4n
*/
public class ZhangJunApp {
public static void main(String[] args) {
Solon.start(ZhangJunApp.class, args);
SmsFactory.getBySupplier("zhangjun").sendMessage("17*****598", "154468");
}
}

View File

@ -0,0 +1,22 @@
package org.dromara.sms4j.example.zhangjun;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.sms4j.provider.config.BaseConfig;
/**
* @author 4n
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class ZhangJunConfig extends BaseConfig {
private String appId;
private String sid;
private String url;
@Override
public String getSupplier() {
return "zhangjun";
}
}

View File

@ -0,0 +1,26 @@
package org.dromara.sms4j.example.zhangjun;
import lombok.NoArgsConstructor;
import org.dromara.sms4j.provider.factory.AbstractProviderFactory;
/**
*
* <p> 掌骏短信
*
* @author :4n
* 2023/10/31 14:54
**/
@NoArgsConstructor
public class ZhangJunFactory extends AbstractProviderFactory<ZhangJunSmsImpl, ZhangJunConfig> {
@Override
public ZhangJunSmsImpl createSms(ZhangJunConfig ZhangJunConfig) {
return new ZhangJunSmsImpl(ZhangJunConfig);
}
@Override
public String getSupplier() {
return "zhangjun";
}
}

View File

@ -0,0 +1,119 @@
package org.dromara.sms4j.example.zhangjun;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.entity.SmsResponse;
import org.dromara.sms4j.comm.delayedTime.DelayedTime;
import org.dromara.sms4j.comm.exception.SmsBlendException;
import org.dromara.sms4j.comm.utils.SmsUtils;
import org.dromara.sms4j.provider.service.AbstractSmsBlend;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
/**
* @author :4n
**/
@Slf4j
public class ZhangJunSmsImpl extends AbstractSmsBlend<ZhangJunConfig> {
private int retry = 0;
/**
* ZhangJunSmsImpl
* <p>构造器用于构造短信实现模块
*
* @author :Wind
*/
public ZhangJunSmsImpl(ZhangJunConfig config, Executor pool, DelayedTime delayedTime) {
super(config, pool, delayedTime);
}
/**
* ZhangJunSmsImpl
* <p>构造器用于构造短信实现模块
*/
public ZhangJunSmsImpl(ZhangJunConfig config) {
super(config);
}
@Override
public String getSupplier() {
return "zhangjun";
}
private LinkedHashMap<String, String> buildBody(String phone, String message){
LinkedHashMap<String, String> map = new LinkedHashMap<>();
map.put("appId", getConfig().getAppId());
map.put("sid", getConfig().getSid());
map.put("templateId", getConfig().getTemplateId());
map.put("phone", phone);
Map<String, Object> data = new HashMap<>();
data.put("code", message);
map.put("data", JSONUtil.toJsonStr(data));
return map;
}
@Override
public SmsResponse sendMessage(String phone, String message) {
return sendMessage(phone, getConfig().getTemplateId(), buildBody(phone,message));
}
@Override
public SmsResponse sendMessage(String phone, LinkedHashMap<String, String> messages) {
return sendMessage(phone, getConfig().getTemplateId(), messages);
}
@Override
public SmsResponse sendMessage(String phone, String templateId, LinkedHashMap<String, String> messages) {
String messageStr = JSONUtil.toJsonStr(messages);
return getSmsResponse(phone, messageStr, templateId);
}
@Override
public SmsResponse massTexting(List<String> phones, String message) {
LinkedHashMap<String, String> map = new LinkedHashMap<>();
// map.put(getConfig().getTemplateName(), message);
return massTexting(phones, getConfig().getTemplateId(), map);
}
@Override
public SmsResponse massTexting(List<String> phones, String templateId, LinkedHashMap<String, String> messages) {
String messageStr = JSONUtil.toJsonStr(messages);
return getSmsResponse(SmsUtils.addCodePrefixIfNot(phones), messageStr, templateId);
}
private SmsResponse getSmsResponse(String phone, String message, String templateId) {
SmsResponse smsResponse;
try {
smsResponse = getResponse(http.postJson(getConfig().getUrl(), null, message));
} catch (SmsBlendException e) {
smsResponse = new SmsResponse();
smsResponse.setSuccess(false);
smsResponse.setData(e.getMessage());
}
if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) {
retry = 0;
return smsResponse;
}
return requestRetry(phone, message, templateId);
}
private SmsResponse requestRetry(String phone, String message, String templateId) {
http.safeSleep(getConfig().getRetryInterval());
retry++;
log.warn("短信第 {} 次重新发送", retry);
return getSmsResponse(phone, message, templateId);
}
private SmsResponse getResponse(JSONObject resJson) {
SmsResponse smsResponse = new SmsResponse();
smsResponse.setSuccess("OK".equals(resJson.getStr("Code")));
smsResponse.setData(resJson);
smsResponse.setConfigId(getConfigId());
return smsResponse;
}
}

View File

@ -0,0 +1,176 @@
sms:
# 标注从yml读取配置
config-type: yaml
# 账户上限
account-max: 1
blends:
# 阿里短信例子
ali:
#厂商标识,标定此配置是哪个厂商,详细请看厂商标识介绍部分
supplier: alibaba
#您的accessKey
access-key-id: 您的accessKey
#您的accessKeySecret
access-key-secret: 您的accessKeySecret
#您的短信签名
signature: 测试签名
#模板ID 非必须配置如果使用sendMessage的快速发送需此配置
template-id: SMS_272470496
# 模版名称
templateName: code
# 腾讯短信例子
tx:
#厂商标识
supplier: tencent
#您的accessKey
access-key-id: 您的accessKey
#您的accessKeySecret
access-key-secret: 您的accessKeySecret
#您的短信签名
signature: 测试签名
#模板ID
template-id: 1603670
#您的sdkAppId
sdk-app-id: 1400761645
# 华为短信例子
hw:
#厂商标识
supplier: huawei
#您的accessKey
access-key-id: 您的accessKey
#您的accessKeySecret
access-key-secret: 您的accessKeySecret
#您的短信签名
signature: 测试签名
#模板ID
template-id: ac4888205c274b2a8263479b954c1ab5
# APP接入地址
url: https://smsapi.cn-north-4.myhuaweicloud.com:443
# 模版名称
templateName: code
# 通道号
sender: 8823040504797
# 合一短信例子
uni:
#厂商标识
supplier: unisms
#您的accessKey
access-key-id: 您的accessKey
#您的短信签名
signature: 测试签名
#模板ID
template-id: pub_verif_short
# 模版名称
templateName: code
# 渠道上限
maximum: 2
lianlu:
supplier: lianlu
templateId: 模板id
appId: 100116
appKey: d42d7
mchId: 100
signName: 【test】
cloopen:
# 短信厂商
supplier: cloopen
base-url: https://app.cloopen.com:8883/2013-12-26
access-key-id: 你的Access Key
access-key-secret: 你的Access Key Secret
sdkAppId: 你的应用ID
#自定义广州掌骏短信添加factory全路径。config,factory,SmsImpl复制其他默认实现即可修改对应的supplier和发送核心逻辑即可
# zhangjun:
# supplier: zhangjun
# factory: org.dromara.sms4j.example.zhangjun.ZhangJunFactory
# templateId: d2a****777
# appId: 64c52d2a****77775fe72e3
# sid: d2a****777
# url: https://sms.idowe.com/**/**/**/send
qiniu:
access-key-id: EQcDflLTCYnU1******CmqIYLhog1lkWHb2
access-key-secret: NeS2ptvZQoIy*****err2DdLe7wxFfQvji1
templateId: 1752130****15859456
signatureId: 175185*****1624960
templateName: code
# 中国移动 云MAS
mas:
supplier: mas
# 请求方式默认为 HTTP
# 请求方法 默认为 HTTP模式下的 tmpsubmit
# norsubmit 无模板接口 不需要配置templateId
# tmpsubmit 有模板接口 需要配置templateId
# HTTPS 模式下 请求方法有 submit tmpsubmit
action: tmpsubmit
# 请求地址 HTTP模式下可不配置 默认为 http://112.35.1.155:1992/sms/
# HTTPS模式下 请设置 https://****:****/sms/
request-url: http://112.35.1.155:1992/sms/
sdk-app-id: 接口账号用户名
access-key-secret: 用户密码
ec-name: 企业名称
signature: 签名编码
# 当请求方法为 tmpsubmit 时 需要配置templateId
template-id:
# 可为空 不为空时请遵守中国移动云MAS开发文档中的描述[服务代码加扩展码总长度不能超过20位。]
add-serial:
# 百度智能云 sms
baidu:
access-key-id: 访问密钥ID
access-key-secret: 用户密钥
ec-name: 企业名称
signature: 签名编码
template-id: 模板ID
# 模板变量名称
template-name: code
custom: 用户自定义参数,格式为字符串,状态回调时会回传该值 可不传
user-ext-id: 通道自定义扩展码 可不传
# 创蓝
chuanglan:
access-key-id: 111111
access-key-secret: 111111
templateId: 【253云通讯】{$var}您的验证码是:{$var}{$var}分钟内有效
# 极光
jiguang:
supplier: jiguang
signId: 签名 ID该字段为空则使用应用默认签名
action: 默认请求方法 messages
templateName: 模板变量名称
voice: action设置为voice_codes有效 语音验证码播报语言选择0中文播报1英文播报2中英混合播报
ttl: action设置为voice_codes有效 验证码有效期,默认为 60 秒
tag: action设置为messages/batch有效 标签
# 螺丝帽
luosimao:
accessKeyId: 后台提取的API key
action: 默认请求方法 send.json
# submail
submail:
accessKeyId: APPID
accessKeySecret: APPKEY
action: 默认请求方法 send.json
signType: MD5 或 SHA-1 默认MD5 填写任意值,不为即为 密匙明文验证模式
signVersion: signature加密计算方式 为2时会忽略某些字段
templateId: 模板ID
signature: 签名
# danmi
danmi:
accessKeyId: ACCOUNT SID
accessKeySecret: AUTH TOKEN
action: 默认请求方法 distributor/sendSMS
sms-oa:
config-type: yaml
oas:
oaDingTalkByYaml: # configId
isEnable: true # 表示该配置是否生效(默认生效,false表示不生效)
supplier: dingding # 厂商标识
tokenId: 您的accessKey
sign: 您的sign
oaByteTalkByYaml: # configId
supplier: feishu # 厂商标识
tokenId: 您的accessKey
sign: 您的sign
oaWeTalkByYaml:
supplier: wetalk # 厂商标识
tokenId: 您的sign
core-pool-size: 20
queue-capacity: 20
max-pool-size: 20

View File

@ -0,0 +1,531 @@
package org.dromara.sms4j.example;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.SmsBlend;
import org.dromara.sms4j.api.entity.SmsResponse;
import org.dromara.sms4j.baidu.service.BaiduSmsImpl;
import org.dromara.sms4j.comm.constant.SupplierConstant;
import org.dromara.sms4j.comm.utils.SmsUtils;
import org.dromara.sms4j.core.factory.SmsFactory;
import org.dromara.sms4j.danmi.service.DanMiSmsImpl;
import org.dromara.sms4j.jg.service.JgSmsImpl;
import org.dromara.sms4j.lianlu.service.LianLuSmsImpl;
import org.dromara.sms4j.luosimao.service.LuoSiMaoSmsImpl;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.noear.solon.test.SolonJUnit5Extension;
import org.noear.solon.test.SolonTest;
import java.util.*;
@Slf4j
@ExtendWith(SolonJUnit5Extension.class)
@SolonTest
public class Sms4jTest {
/**
* 填测试手机号
*/
private static final String PHONE = "";
@Test
public void byLoadTest() {
if (StrUtil.isBlank(PHONE)) {
return;
}
// 通过负载均衡服务获取短信服务对象
SmsResponse smsResponse = SmsFactory.getSmsBlend().sendMessage(PHONE, SmsUtils.getRandomInt(6));
Assert.isTrue(smsResponse.isSuccess());
}
@Test
public void alibabaSmsTest() {
// 阿里
SmsResponse smsResponse = SmsFactory.getBySupplier(SupplierConstant.ALIBABA).sendMessage(PHONE, SmsUtils.getRandomInt(6));
Assert.isTrue(smsResponse.isSuccess());
}
@Test
public void huaweiSmsTest() {
if (StrUtil.isBlank(PHONE)) {
return;
}
// 华为
SmsResponse smsResponse = SmsFactory.getBySupplier(SupplierConstant.HUAWEI).sendMessage(PHONE, SmsUtils.getRandomInt(6));
log.info(JSONUtil.toJsonStr(smsResponse));
Assert.isTrue(smsResponse.isSuccess());
}
@Test
public void cloopenSmsTest() {
if (StrUtil.isBlank(PHONE)) {
return;
}
// 容联云
Map<String, String> messageMap = MapUtil.newHashMap(2, true);
messageMap.put("captcha", SmsUtils.getRandomInt(4));
messageMap.put("expirationInMinutes", "5");
SmsResponse smsResponse = SmsFactory.getBySupplier(SupplierConstant.CLOOPEN)
.sendMessage(PHONE, "1", (LinkedHashMap<String, String>) messageMap);
log.info(JSONUtil.toJsonStr(smsResponse));
Assert.isTrue(smsResponse.isSuccess());
}
@Test
public void emaySmsTest() {
if (StrUtil.isBlank(PHONE)) {
return;
}
// 亿美软通
SmsResponse smsResponse = SmsFactory.getBySupplier(SupplierConstant.EMAY).sendMessage(PHONE, SmsUtils.getRandomInt(6));
log.info(JSONUtil.toJsonStr(smsResponse));
Assert.isTrue(smsResponse.isSuccess());
}
@Test
public void jdCloudSmsTest() {
if (StrUtil.isBlank(PHONE)) {
return;
}
// 京东云
SmsResponse smsResponse = SmsFactory.getBySupplier(SupplierConstant.JDCLOUD).sendMessage(PHONE, SmsUtils.getRandomInt(6));
log.info(JSONUtil.toJsonStr(smsResponse));
Assert.isTrue(smsResponse.isSuccess());
}
@Test
public void yunPianSmsTest() {
if (StrUtil.isBlank(PHONE)) {
return;
}
// 云片
SmsResponse smsResponse = SmsFactory.getBySupplier(SupplierConstant.YUNPIAN).sendMessage(PHONE, SmsUtils.getRandomInt(6));
log.info(JSONUtil.toJsonStr(smsResponse));
Assert.isTrue(smsResponse.isSuccess());
}
@Test
public void tencentSmsTest() {
if (StrUtil.isBlank(PHONE)) {
return;
}
LinkedHashMap<String, String> newMap = SmsUtils.getNewMap();
// 验证码
newMap.put("1", SmsUtils.getRandomInt(4));
// 有效时间
newMap.put("2", "2");
SmsResponse smsResponse = SmsFactory.getBySupplier(SupplierConstant.TENCENT)
.sendMessage(PHONE, "1603670", newMap);
log.info(JSONUtil.toJsonStr(smsResponse));
Assert.isTrue(smsResponse.isSuccess());
}
@Test
public void uniSmsTest() {
if (StrUtil.isBlank(PHONE)) {
return;
}
// 合一
SmsResponse smsResponse = SmsFactory.getBySupplier(SupplierConstant.UNISMS).sendMessage(PHONE, SmsUtils.getRandomInt(6));
log.info(JSONUtil.toJsonStr(smsResponse));
Assert.isTrue(smsResponse.isSuccess());
}
@Test
public void cyYunSmsTest() {
if (StrUtil.isBlank(PHONE)) {
return;
}
// 天翼云
SmsResponse smsResponse = SmsFactory.getBySupplier(SupplierConstant.CTYUN).sendMessage(PHONE, SmsUtils.getRandomInt(6));
log.info(JSONUtil.toJsonStr(smsResponse));
Assert.isTrue(smsResponse.isSuccess());
}
@Test
public void neteaseSmsTest() {
if (StrUtil.isBlank(PHONE)) {
return;
}
// 网易云短信
SmsResponse smsResponse = SmsFactory.getBySupplier(SupplierConstant.NETEASE).sendMessage(PHONE, SmsUtils.getRandomInt(6));
log.info(JSONUtil.toJsonStr(smsResponse));
Assert.isTrue(smsResponse.isSuccess());
}
/**
* 助通短信测试1无模板
*/
@Test
public void zhutongSms1Test() {
if (StrUtil.isBlank(PHONE)) {
return;
}
// 助通短信短信
String msg = StrUtil.format("【图书商城】您好,你的验证码是{}5分钟失效", SmsUtils.getRandomInt(6));
SmsResponse smsResponse = SmsFactory.getBySupplier(SupplierConstant.ZHUTONG).sendMessage(PHONE, msg);
log.info(JSONUtil.toJsonStr(smsResponse));
Assert.isTrue(smsResponse.isSuccess());
}
/**
* 助通短信测试2有模板
*/
@Test
public void zhutongSmsTest2Template() {
if (StrUtil.isBlank(PHONE)) {
return;
}
// 助通短信短信
LinkedHashMap<String, String> messages = new LinkedHashMap<>(1);
messages.put("code", SmsUtils.getRandomInt(6));
SmsResponse smsResponse = SmsFactory.getBySupplier(SupplierConstant.ZHUTONG).sendMessage(PHONE, "59264", messages);
log.info(JSONUtil.toJsonStr(smsResponse));
Assert.isTrue(smsResponse.isSuccess());
}
/**
* 助通短信测试3无模板群发
*/
@Test
public void zhutongSms3MoreTest() {
if (StrUtil.isBlank(PHONE)) {
return;
}
// 助通短信短信
String msg = StrUtil.format("【图书商城】您好,你的验证码是{}5分钟失效", SmsUtils.getRandomInt(6));
SmsResponse smsResponse = SmsFactory.getBySupplier(SupplierConstant.ZHUTONG).massTexting(ListUtil.of(PHONE, "180****1111"), msg);
log.info(JSONUtil.toJsonStr(smsResponse));
Assert.isTrue(smsResponse.isSuccess());
}
/**
* 助通短信测试4有模板 多人群发
*/
@Test
public void zhutongSms4TemplateTest() {
if (StrUtil.isBlank(PHONE)) {
return;
}
// 助通短信短信
LinkedHashMap<String, String> messages = new LinkedHashMap<>(1);
messages.put("code", SmsUtils.getRandomInt(6));
SmsResponse smsResponse = SmsFactory.getBySupplier(SupplierConstant.ZHUTONG).massTexting(ListUtil.of(PHONE, "180****1111"), "59264", messages);
log.info(JSONUtil.toJsonStr(smsResponse));
Assert.isTrue(smsResponse.isSuccess());
}
/**
* 联麓模板短信
*/
@Test
public void lianLuTemplateSmsTest() {
if (StrUtil.isBlank(PHONE)) {
return;
}
SmsResponse smsResponse = SmsFactory.getBySupplier(SupplierConstant.LIANLU)
.sendMessage(PHONE, SmsUtils.getRandomInt(6));
log.info(JSONUtil.toJsonStr(smsResponse));
Assert.isTrue(smsResponse.isSuccess());
}
/**
* 联麓普通短信
*/
@Test
public void lianLuNormalSmsTest() {
if (StrUtil.isBlank(PHONE)) {
return;
}
LianLuSmsImpl lianLuSms = (LianLuSmsImpl) SmsFactory.getBySupplier(SupplierConstant.LIANLU);
SmsResponse smsResponse = lianLuSms.sendNormalMessage(PHONE, "测试短信" + SmsUtils.getRandomInt(6));
log.info(JSONUtil.toJsonStr(smsResponse));
Assert.isTrue(smsResponse.isSuccess());
}
/**
* 鼎众普通短信
*/
@Test
public void dingZhongSmsTest() {
if (StrUtil.isBlank(PHONE)) {
return;
}
SmsBlend dz = SmsFactory.getBySupplier(SupplierConstant.DINGZHONG);
LinkedHashMap<String, String> messages = new LinkedHashMap<>();
messages.put("code", SmsUtils.getRandomInt(6));
ArrayList<String> phones = new ArrayList<>();
phones.add(PHONE);
phones.add(PHONE);
SmsResponse smsResponse = dz.sendMessage(PHONE, "测试短信" + SmsUtils.getRandomInt(6));
log.info(JSONUtil.toJsonStr(smsResponse));
Assert.isTrue(smsResponse.isSuccess());
SmsResponse smsResponse1 = dz.sendMessage(PHONE, messages);
log.info(JSONUtil.toJsonStr(smsResponse1));
Assert.isTrue(smsResponse1.isSuccess());
SmsResponse smsResponse3 = dz.massTexting(phones, "测试短信" + SmsUtils.getRandomInt(6));
log.info(JSONUtil.toJsonStr(smsResponse3));
Assert.isTrue(smsResponse3.isSuccess());
SmsResponse smsResponse4 = dz.massTexting(phones, "" ,messages);
log.info(JSONUtil.toJsonStr(smsResponse4));
Assert.isTrue(smsResponse4.isSuccess());
}
/**
* 中国移动 云MAS
*/
@Test
public void masSmsTest() {
if (StrUtil.isBlank(PHONE)) {
return;
}
// 发送一对一/一对多普通短信
// HTTP模式下 action请配置为 norsubmit
// HTTPS模式下 action请配置为 submit
SmsResponse oneToMany = SmsFactory.getBySupplier(SupplierConstant.MAS)
.sendMessage(PHONE, "测试短信" + SmsUtils.getRandomInt(6));
log.info(JSONUtil.toJsonStr(oneToMany));
// 发送多对多普通短信
// HTTP模式下 action请配置为 norsubmit
// HTTPS模式下 action请配置为 submit
LinkedHashMap<String, String> content = new LinkedHashMap<>();
content.put("18***1", "测试短信1");
content.put("18***2", "测试短信2");
content.put("18***3", "测试短信3");
content.put("18***4", "测试短信4");
SmsResponse manyToMany1 = SmsFactory.getBySupplier(SupplierConstant.MAS)
.sendMessage(PHONE, content);
log.info(JSONUtil.toJsonStr(manyToMany1));
// 或者
SmsResponse manyToMany2 = SmsFactory.getBySupplier(SupplierConstant.MAS)
.sendMessage(PHONE, JSONUtil.toJsonStr(content));
log.info(JSONUtil.toJsonStr(manyToMany2));
// 发送模板短信
// HTTP模式下或者HTTPS模式下 action请都配置为 tmpsubmit
// 无参数
SmsResponse strRes = SmsFactory.getBySupplier(SupplierConstant.MAS)
.sendMessage(PHONE, StrUtil.EMPTY);
log.info(JSONUtil.toJsonStr(strRes));
//数组格式
String[] paramsArr = {"param1", "param2"};
SmsResponse arrRes = SmsFactory.getBySupplier(SupplierConstant.MAS)
.sendMessage(PHONE, JSONUtil.toJsonStr(paramsArr));
log.info(JSONUtil.toJsonStr(arrRes));
//list格式
List<String> paramsList = new ArrayList<>();
paramsList.add("param1");
paramsList.add("param2");
SmsResponse listRes = SmsFactory.getBySupplier(SupplierConstant.MAS)
.sendMessage(PHONE, JSONUtil.toJsonStr(paramsList));
log.info(JSONUtil.toJsonStr(listRes));
}
/**
* 百度短信
*/
@Test
public void baiduSmsTest() {
if (StrUtil.isBlank(PHONE)) {
return;
}
// 发送短信
SmsResponse resp = SmsFactory.getBySupplier(SupplierConstant.BAIDU)
.sendMessage(PHONE, SmsUtils.getRandomInt(6));
log.info(JSONUtil.toJsonStr(resp));
// 发送携带幂等性参数短信
BaiduSmsImpl sendWithClientToken = (BaiduSmsImpl) SmsFactory.getBySupplier(SupplierConstant.BAIDU);
String clientToken = UUID.fastUUID().toString(true);
SmsResponse respWithClientToken = sendWithClientToken.sendMessageWithClientToken(PHONE,
SmsUtils.getRandomInt(6),
clientToken);
log.info(JSONUtil.toJsonStr(respWithClientToken));
}
/**
* 创蓝短信
*/
@Test
public void chungLanTest() {
if (StrUtil.isBlank(PHONE)) {
return;
}
//测试群发模板
List<String> arrayList = new ArrayList<>();
arrayList.add(PHONE);
arrayList.add("135****000");
LinkedHashMap<String, String> map = new LinkedHashMap<>();
map.put("1", "1544");
map.put("2", "2222");
SmsResponse smsResponse2 = SmsFactory.getBySupplier(SupplierConstant.CHUANGLAN).massTexting(arrayList, "[test]你的验证码是{$val},{$val}", map);
log.info("smsResponse2:{}", smsResponse2);
//测试单条发送
SmsResponse smsResponse1 = SmsFactory.getBySupplier(SupplierConstant.CHUANGLAN).sendMessage(PHONE, "1544&2222");
log.info("smsResponse1:{}", smsResponse1);
//测试单条模板发送
LinkedHashMap<String, String> map3 = new LinkedHashMap<>();
map3.put("1", "1544");
map3.put("2", "2222");
SmsResponse smsResponse3 = SmsFactory.getBySupplier(SupplierConstant.CHUANGLAN).sendMessage(PHONE, "[test]你的验证码是{$val},{$val}", map3);
log.info("smsResponse3:{}", smsResponse3);
}
/**
* 极光短信
*/
@Test
public void jgSmsTest() {
// 极光 发送文本验证码短信 API 不需要传入具体验证码 返回 {"msg_id": "288193860302"}
SmsResponse smsResponse1 = SmsFactory.getBySupplier(SupplierConstant.JIGUANG).sendMessage(PHONE, "");
Assert.isTrue(smsResponse1.isSuccess());
// 极光 发送语音验证码短信 请确保action配置为voice_codes
JgSmsImpl voiceCode = (JgSmsImpl) SmsFactory.getBySupplier(SupplierConstant.JIGUANG);
SmsResponse voiceResp = voiceCode.sendVoiceCode(PHONE,
SmsUtils.getRandomInt(6));
Assert.isTrue(voiceResp.isSuccess());
// 验证验证码是否有效 请确保action配置为voice_codes
JgSmsImpl verify = (JgSmsImpl) SmsFactory.getBySupplier(SupplierConstant.JIGUANG);
SmsResponse verifyResp = verify.verifyCode("123456", "288193860302");
Assert.isTrue(verifyResp.isSuccess());
// 极光 发送单条模板短信 API 发送自定义验证码 sendTemplateSMS
LinkedHashMap<String, String> map1 = new LinkedHashMap<>();
map1.put("code", "123456");
SmsResponse smsResponse2 = SmsFactory.getBySupplier(SupplierConstant.JIGUANG).sendMessage(PHONE, map1);
Assert.isTrue(smsResponse2.isSuccess());
// 极光 发送单条模板短信 API 发送多参数自定义模板短信 sendTemplateSMS_with_multipleParameters
LinkedHashMap<String, String> map2 = new LinkedHashMap<>();
map2.put("name", "test");
map2.put("password", "test");
SmsResponse smsResponse3 = SmsFactory.getBySupplier(SupplierConstant.JIGUANG).sendMessage(PHONE, "226992", map2);
Assert.isTrue(smsResponse3.isSuccess());
// sendBatchTemplateSMS
LinkedHashMap<String, String> map3 = new LinkedHashMap<>();
map3.put("name", "test");
map3.put("password", "test");
List<String> phones = new ArrayList<>();
phones.add(PHONE);
phones.add("xxx");
SmsResponse smsResponse4 = SmsFactory.getBySupplier(SupplierConstant.JIGUANG).massTexting(phones, "226992", map3);
Assert.isTrue(smsResponse4.isSuccess());
}
/**
* 螺丝帽短信
*/
@Test
public void luosimaoSmsTest() {
// 螺丝帽 发送短信接口详细 发送短信接口详细 send.json
SmsResponse smsResponse1 = SmsFactory.getBySupplier(SupplierConstant.LUO_SI_MAO).sendMessage(PHONE, "");
Assert.isTrue(smsResponse1.isSuccess());
// 螺丝帽 批量发送接口详细 send_batch.json
SmsResponse smsResponse2 = SmsFactory.getBySupplier(SupplierConstant.LUO_SI_MAO).massTexting(Collections.singletonList(PHONE), "");
Assert.isTrue(smsResponse2.isSuccess());
// 螺丝帽 定时批量发送接口详细 send_batch.json
LuoSiMaoSmsImpl luoSiMao = (LuoSiMaoSmsImpl) SmsFactory.getBySupplier(SupplierConstant.LUO_SI_MAO);
SmsResponse smsResponse3 = luoSiMao.massTextingOnTime(Collections.singletonList(PHONE), "", new Date());
Assert.isTrue(smsResponse3.isSuccess());
// 螺丝帽 查询账户余额 status.json
SmsResponse smsResponse4 = luoSiMao.queryAccountBalance();
Assert.isTrue(smsResponse4.isSuccess());
}
/**
* SUBMAIL短信
*/
@Test
public void mysubmailSmsTest() {
// 短信发送 send.json xxxx签名可配置 系统自动带入
SmsResponse smsResponse1 = SmsFactory.getBySupplier(SupplierConstant.MY_SUBMAIL).sendMessage(PHONE, "【SUBMAIL】你好你的验证码是2257");
Assert.isTrue(smsResponse1.isSuccess());
// 短信模板发送 xsend.json
LinkedHashMap<String, String> vars = new LinkedHashMap<>();
vars.put("code", "123456");
SmsResponse smsResponse2 = SmsFactory.getBySupplier(SupplierConstant.MY_SUBMAIL).sendMessage(PHONE, "xxx", vars);
Assert.isTrue(smsResponse2.isSuccess());
// 短信一对多发送 multisend.json xxxx签名可配置 系统自动带入 content字段说明短信正文 案例没有说明无需传
LinkedHashMap<String, String> vars1 = new LinkedHashMap<>();
vars1.put("content", "【SUBMAIL】您的短信验证码4438请在10分钟内输入。");
vars1.put("code", "123456");
vars1.put("time", "10");
SmsResponse smsResponse3 = SmsFactory.getBySupplier(SupplierConstant.MY_SUBMAIL).massTexting(Collections.singletonList(PHONE), JSONUtil.toJsonStr(vars1));
Assert.isTrue(smsResponse3.isSuccess());
// 短信模板一对多发送 multixsend.json
LinkedHashMap<String, String> vars2 = new LinkedHashMap<>();
vars2.put("code", "123456");
SmsResponse smsResponse4 = SmsFactory.getBySupplier(SupplierConstant.MY_SUBMAIL).massTexting(Collections.singletonList(PHONE), "xxx", vars2);
Assert.isTrue(smsResponse4.isSuccess());
// 短信批量群发 batchsend.json xxxx签名可配置 系统自动带入 content字段说明短信正文 案例没有说明无需传
LinkedHashMap<String, String> vars3 = new LinkedHashMap<>();
vars3.put("content", "123456");
SmsResponse smsResponse5 = SmsFactory.getBySupplier(SupplierConstant.MY_SUBMAIL).massTexting(Collections.singletonList(PHONE), JSONUtil.toJsonStr(vars3));
Assert.isTrue(smsResponse5.isSuccess());
// 短信批量模板群发 batchxsend.json
LinkedHashMap<String, String> vars4 = new LinkedHashMap<>();
vars4.put("code", "123456");
vars4.put("time", "10");
SmsResponse smsResponse6 = SmsFactory.getBySupplier(SupplierConstant.MY_SUBMAIL).massTexting(Collections.singletonList(PHONE), "xxx", vars4);
Assert.isTrue(smsResponse6.isSuccess());
}
/**
* danmi短信
*/
@Test
public void danmiSmsTest() {
// 短信发送 distributor/sendSMS 群发 massTexting
SmsResponse smsResponse1 = SmsFactory.getBySupplier(SupplierConstant.DAN_MI).sendMessage(PHONE, "【danmi】你好你的验证码是666");
Assert.isTrue(smsResponse1.isSuccess());
DanMiSmsImpl danMiSms = (DanMiSmsImpl) SmsFactory.getBySupplier(SupplierConstant.DAN_MI);
// 短信余额查询 distributor/user/query
SmsResponse smsResponse2 = danMiSms.queryBalance();
Assert.isTrue(smsResponse2.isSuccess());
// 语音验证码发送 voice/voiceCode
SmsResponse smsResponse3 = danMiSms.voiceCode(PHONE, "666");
Assert.isTrue(smsResponse3.isSuccess());
// 语音通知文件发送 voice/voiceNotify
SmsResponse smsResponse4 = danMiSms.voiceNotify(PHONE, "sjkhduiq");
Assert.isTrue(smsResponse4.isSuccess());
// 语音模板通知发送 voice/voiceTemplate
SmsResponse smsResponse5 = danMiSms.voiceTemplate(PHONE, "opipedlqza", "111,222,333");
Assert.isTrue(smsResponse5.isSuccess());
}
}

View File

@ -0,0 +1,444 @@
package org.dromara.sms4j.example;
import lombok.extern.slf4j.Slf4j;
import org.dromara.oa.api.OaSender;
import org.dromara.oa.comm.entity.Request;
import org.dromara.oa.comm.entity.WeTalkRequestArticle;
import org.dromara.oa.comm.enums.MessageType;
import org.dromara.oa.core.byteTalk.config.ByteTalkConfig;
import org.dromara.oa.core.dingTalk.config.DingTalkConfig;
import org.dromara.oa.core.provider.factory.OaFactory;
import org.dromara.oa.core.weTalk.config.WeTalkConfig;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.noear.solon.test.SolonJUnit5Extension;
import org.noear.solon.test.SolonTest;
import java.util.ArrayList;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
@Slf4j
@ExtendWith(SolonJUnit5Extension.class)
@SolonTest
public class SmsOaTest {
//***********************DingTalk-Test************************//
/**
* 填测试手机号
*/
private static final String DingTalkPHONE = "";
/**
* 填access_token
*/
private static final String DingTalkTOKENID = "";
/**
* 填secret
*/
private static final String DingTalkSIGN = "";
/**
* DingTalk的Text测试
*/
@Test
public void oaDingTalkText() {
String key = "oaDingTalk";
DingTalkConfig dingTalkConfig = new DingTalkConfig();
dingTalkConfig.setConfigId(key);
dingTalkConfig.setSign(DingTalkSIGN);
dingTalkConfig.setTokenId(DingTalkTOKENID);
// 根据配置创建服务实例并注册
OaFactory.createAndRegisterOaSender(dingTalkConfig);
OaSender alarm = OaFactory.getSmsOaBlend(key);
Request request = new Request();
ArrayList<String> phones = new ArrayList<>();
phones.add(DingTalkPHONE);
// 支持通过手机号@
request.setPhoneList(phones);
// 支持@all
// request.setIsNoticeAll(true);
request.setContent("测试消息");
alarm.sender(request, MessageType.DING_TALK_TEXT);
}
/**
* DingTalk的Markdown测试
*/
@Test
public void oaDingTalkMarkdown() {
String key = "oaDingTalk";
DingTalkConfig dingTalkConfig = new DingTalkConfig();
dingTalkConfig.setConfigId(key);
dingTalkConfig.setSign(DingTalkSIGN);
dingTalkConfig.setTokenId(DingTalkTOKENID);
// 根据配置创建服务实例并注册
OaFactory.createAndRegisterOaSender(dingTalkConfig);
OaSender alarm = OaFactory.getSmsOaBlend(key);
Request request = new Request();
// 支持@all
request.setIsNoticeAll(true);
request.setContent("#### 杭州天气 @150XXXXXXXX \n > 9度西北风1级空气良89相对温度73%\n > ![screenshot](https://img.alicdn.com/tfs/TB1NwmBEL9TBuNjy1zbXXXpepXa-2400-1218.png)\n > ###### 10点20分发布 [天气](https://www.dingtalk.com) \n");
request.setTitle("标题");
alarm.sender(request, MessageType.DING_TALK_MARKDOWN);
}
/**
* DingTalk的Link测试
*/
@Test
public void oaDingTalkLink() {
String key = "oaDingTalk";
DingTalkConfig dingTalkConfig = new DingTalkConfig();
dingTalkConfig.setConfigId(key);
dingTalkConfig.setSign(DingTalkSIGN);
dingTalkConfig.setTokenId(DingTalkTOKENID);
// 根据配置创建服务实例并注册
OaFactory.createAndRegisterOaSender(dingTalkConfig);
OaSender alarm = OaFactory.getSmsOaBlend(key);
Request request = new Request();
request.setContent("这个即将发布的新版本创始人xx称它为红树林。而在此之前每当面临重大升级产品经理们都会取一个应景的代号这一次为什么是红树林");
request.setTitle("点击跳转到钉钉");
request.setMessageUrl("https://www.dingtalk.com/s?__biz=MzA4NjMwMTA2Ng==&mid=2650316842&idx=1&sn=60da3ea2b29f1dcc43a7c8e4a7c97a16&scene=2&srcid=09189AnRJEdIiWVaKltFzNTw&from=timeline&isappinstalled=0&key=&ascene=2&uin=&devicetype=android-23&version=26031933&nettype=WIFI");
request.setPicUrl("https://img.alicdn.com/tfs/TB1NwmBEL9TBuNjy1zbXXXpepXa-2400-1218.png");
alarm.sender(request, MessageType.DING_TALK_LINK);
}
/**
* DingTalk的异步消息发送
*/
@Test
public void oaDingTalkAsyncTest() {
String key = "oaDingTalk";
DingTalkConfig dingTalkConfig = new DingTalkConfig();
dingTalkConfig.setConfigId(key);
dingTalkConfig.setSign(DingTalkSIGN);
dingTalkConfig.setTokenId(DingTalkTOKENID);
// 根据配置创建服务实例并注册
OaFactory.createAndRegisterOaSender(dingTalkConfig);
OaSender alarm = OaFactory.getSmsOaBlend(key);
Request request = new Request();
ArrayList<String> phones = new ArrayList<>();
phones.add(DingTalkPHONE);
// 支持通过手机号@
request.setPhoneList(phones);
// 支持@all
// request.setIsNoticeAll(true);
request.setContent("测试消息");
// 异步发送方式
alarm.senderAsync(request, MessageType.DING_TALK_TEXT);
alarm.senderAsync(request, MessageType.DING_TALK_TEXT, smsResponse -> System.out.println("ConfigId为" + smsResponse.getOaConfigId() + "的异步任务发送成功"));
try {
Thread.sleep(3000L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
/**
* 异步优先级尽可能优先级高的消息先发送,但是获取响应会受网络影响
*/
@Test
public void oaDingTalkAsyncByPriority() {
String key = "oaDingTalk";
DingTalkConfig dingTalkConfig = new DingTalkConfig();
dingTalkConfig.setConfigId(key);
dingTalkConfig.setSign(DingTalkSIGN);
dingTalkConfig.setTokenId(DingTalkTOKENID);
// 根据配置创建服务实例并注册
OaFactory.createAndRegisterOaSender(dingTalkConfig);
OaSender alarm = OaFactory.getSmsOaBlend(key);
CountDownLatch user = new CountDownLatch(1);
// 模拟10条不同优先级的消息
for (int i = 0; i < 3; i++) {
Random random = new Random();
new Thread(() -> {
int priority = random.nextInt(10);
try {
// 等待十个请求
System.out.println(priority + "等待");
user.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
Request request = new Request();
ArrayList<String> phones = new ArrayList<>();
phones.add(DingTalkPHONE);
request.setPhoneList(phones);
request.setIsNoticeAll(false);
request.setPriority(priority);
//测试-1-TEXT
request.setContent("该消息优先级为" + priority);
alarm.senderAsyncByPriority(request, MessageType.DING_TALK_TEXT);
System.out.println("优先级为" + priority + "的异步任务已提交");
}).start();
}
System.out.println("开始模拟");
user.countDown();
// 防止主线程挂掉
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
@Test
public void oaDingTalkByYamlTest() {
String configId = "oaDingTalkByYaml";
OaSender alarm = OaFactory.getSmsOaBlend(configId);
Request request = new Request();
ArrayList<String> phones = new ArrayList<>();
phones.add(DingTalkPHONE);
// 支持通过手机号@
request.setPhoneList(phones);
// 支持@all
// request.setIsNoticeAll(true);
request.setContent("测试消息");
alarm.sender(request, MessageType.DING_TALK_TEXT);
}
//***********************ByteTalk-Test************************//
/**
* 填测试手机号
*/
private static final String ByteTalkUSERID = "";
/**
* 填access_token
*/
private static final String ByteTalkTOKENID = "";
/**
* 填secret
*/
private static final String ByteTalkSIGN = "";
/**
* ByteTalk的Text测试
*/
@Test
public void oaByteTalkText() {
String key = "oaByteTalk";
ByteTalkConfig byteTalkConfig = new ByteTalkConfig();
byteTalkConfig.setConfigId(key);
byteTalkConfig.setTokenId(ByteTalkTOKENID);
byteTalkConfig.setSign(ByteTalkSIGN);
// 根据配置创建服务实例并注册
OaFactory.createAndRegisterOaSender(byteTalkConfig);
OaSender alarm = OaFactory.getSmsOaBlend(key);
Request request = new Request();
ArrayList<String> userIds = new ArrayList<>();
userIds.add(ByteTalkUSERID);
//测试text
request.setUserIdList(userIds);
request.setIsNoticeAll(true);
request.setContent("测试消息");
alarm.sender(request, MessageType.BYTE_TALK_TEXT);
}
/**
* ByteTalk的异步消息发送
*/
@Test
public void oaByteTalkAsyncText() {
String key = "oaByteTalk";
ByteTalkConfig byteTalkConfig = new ByteTalkConfig();
byteTalkConfig.setConfigId(key);
byteTalkConfig.setTokenId(ByteTalkTOKENID);
byteTalkConfig.setSign(ByteTalkSIGN);
// 根据配置创建服务实例并注册
OaFactory.createAndRegisterOaSender(byteTalkConfig);
OaSender alarm = OaFactory.getSmsOaBlend(key);
Request request = new Request();
ArrayList<String> userIds = new ArrayList<>();
userIds.add(ByteTalkUSERID);
//测试text
request.setUserIdList(userIds);
request.setIsNoticeAll(true);
request.setContent("测试消息");
alarm.senderAsync(request, MessageType.BYTE_TALK_TEXT);
alarm.senderAsync(request, MessageType.BYTE_TALK_TEXT, smsResponse -> System.out.println("ConfigId为" + smsResponse.getOaConfigId() + "的异步任务发送成功"));
// 防止主线程挂掉
try {
Thread.sleep(3000L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
@Test
public void oaByteTalkByYamlTest() {
String configId = "oaByteTalkByYaml";
OaSender alarm = OaFactory.getSmsOaBlend(configId);
Request request = new Request();
ArrayList<String> userIds = new ArrayList<>();
userIds.add(ByteTalkUSERID);
//测试text
request.setUserIdList(userIds);
request.setIsNoticeAll(true);
request.setContent("测试消息");
alarm.sender(request, MessageType.BYTE_TALK_TEXT);
}
//***********************WeTalk-Test************************//
/**
* 填测试手机号
*/
private static final String WeTalkPHONE = "";
/**
* 填测试UserId
*/
private static final String WeTalkUSERID = "";
/**
* 填access_token
*/
private static final String WeTalkTOKENID = "";
/**
* WeTalk的Text测试
*/
@Test
public void oaWeTalkText() {
String key = "oaWeTalk";
WeTalkConfig WeTalkConfig = new WeTalkConfig();
WeTalkConfig.setConfigId(key);
WeTalkConfig.setTokenId(WeTalkTOKENID);
// 根据配置创建服务实例并注册
OaFactory.createAndRegisterOaSender(WeTalkConfig);
OaSender alarm = OaFactory.getSmsOaBlend(key);
Request request = new Request();
ArrayList<String> phones = new ArrayList<>();
phones.add(WeTalkPHONE);
//测试text
request.setPhoneList(phones);
request.setIsNoticeAll(true);
request.setContent("测试消息");
alarm.sender(request, MessageType.WE_TALK_TEXT);
}
/**
* WeTalk的Markdown测试--不支持@all,只能通过userId进行
*/
@Test
public void oaWeTalkMarkdown() {
String key = "oaWeTalk";
WeTalkConfig WeTalkConfig = new WeTalkConfig();
WeTalkConfig.setConfigId(key);
WeTalkConfig.setTokenId(WeTalkTOKENID);
// 根据配置创建服务实例并注册
OaFactory.createAndRegisterOaSender(WeTalkConfig);
OaSender alarm = OaFactory.getSmsOaBlend(key);
Request request = new Request();
// 管理后台-通讯录 账号就是userid或者通过接口获取部门列表 再获取部门成员详情获取userid
ArrayList<String> userIdList = new ArrayList<>();
userIdList.add(WeTalkUSERID);
request.setUserIdList(userIdList);
request.setContent(
"实时新增用户反馈<font color=\"warning\">132例</font>,请相关同事注意。\n" +
">类型:<font color=\"comment\">用户反馈</font>" +
">普通用户反馈:<font color=\"comment\">117例</font>" +
">VIP用户反馈:<font color=\"comment\">15例</font>");
alarm.sender(request, MessageType.WE_TALK_MARKDOWN);
}
/**
* WeTalk的News测试
*/
@Test
public void oaWeTalkNews() {
String key = "oaWeTalk";
WeTalkConfig WeTalkConfig = new WeTalkConfig();
WeTalkConfig.setConfigId(key);
WeTalkConfig.setTokenId(WeTalkTOKENID);
// 根据配置创建服务实例并注册
OaFactory.createAndRegisterOaSender(WeTalkConfig);
OaSender alarm = OaFactory.getSmsOaBlend(key);
Request request = new Request();
ArrayList<WeTalkRequestArticle> articles = new ArrayList<>();
articles.add(new WeTalkRequestArticle("中秋节礼品领取", "今年中秋节公司有豪礼相送", "www.qq.com", "http://res.mail.qq.com/node/ww/wwopenmng/images/independent/doc/test_pic_msg1.png"));
request.setArticleList(articles);
alarm.sender(request, MessageType.WE_TALK_NEWS);
}
/**
* WeTalk的异步消息发送
*/
@Test
public void oaWeTalkAsyncText() {
String key = "oaWeTalk";
WeTalkConfig WeTalkConfig = new WeTalkConfig();
WeTalkConfig.setConfigId(key);
WeTalkConfig.setTokenId(WeTalkTOKENID);
// 根据配置创建服务实例并注册
OaFactory.createAndRegisterOaSender(WeTalkConfig);
OaSender alarm = OaFactory.getSmsOaBlend(key);
Request request = new Request();
ArrayList<String> phones = new ArrayList<>();
phones.add(WeTalkPHONE);
//测试text
request.setPhoneList(phones);
request.setIsNoticeAll(true);
request.setContent("测试消息");
// 异步发送方式
alarm.senderAsync(request, MessageType.WE_TALK_TEXT);
alarm.senderAsync(request, MessageType.WE_TALK_TEXT, smsResponse -> System.out.println("ConfigId为" + smsResponse.getOaConfigId() + "的异步任务发送成功"));
// 防止主线程挂掉
try {
Thread.sleep(3000L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
@Test
public void oaWeTalkByYamlTest() {
String configId = "oaWeTalkByYaml";
OaSender alarm = OaFactory.getSmsOaBlend(configId);
Request request = new Request();
ArrayList<String> phones = new ArrayList<>();
phones.add(WeTalkPHONE);
request.setPhoneList(phones);
request.setIsNoticeAll(true);
request.setContent("SMS4JContent");
alarm.sender(request, MessageType.WE_TALK_TEXT);
}
}

View File

@ -0,0 +1,192 @@
package org.dromara.sms4j.example;
import cn.hutool.core.lang.Assert;
import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.SmsBlend;
import org.dromara.sms4j.api.entity.SmsResponse;
import org.dromara.sms4j.comm.constant.SupplierConstant;
import org.dromara.sms4j.comm.exception.SmsBlendException;
import org.dromara.sms4j.comm.utils.SmsUtils;
import org.dromara.sms4j.core.factory.SmsFactory;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.noear.solon.test.SolonJUnit5Extension;
import org.noear.solon.test.SolonTest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
/**
* @author sh1yu
*/
@Slf4j
@ExtendWith(SolonJUnit5Extension.class)
@SolonTest
public class SmsProcessorTest {
/**
* 填测试手机号
*/
private static final String PHONE = "11111111111";
private static final String PHONE1 = "22222222222";
//基础发送测试即黑名单账户限制渠道限制都不预设直接发送新增参数全为空
@Test
public void test1() {
System.out.println("------------");
SmsBlend smsBlend = SmsFactory.getBySupplier(SupplierConstant.UNISMS);
SmsResponse smsResponse = smsBlend.sendMessage(PHONE, SmsUtils.getRandomInt(6));
Assert.isTrue(smsResponse.isSuccess());
System.out.println(smsResponse.getData());
}
//第二个账号的测试
@Test
public void test2() {
System.out.println("------------");
SmsBlend smsBlend = SmsFactory.getBySupplier(SupplierConstant.HUAWEI);
SmsResponse smsResponse = smsBlend.sendMessage(PHONE1, SmsUtils.getRandomInt(6));
Assert.isTrue(smsResponse.isSuccess());
System.out.println(smsResponse.getData());
}
//参数校验测试
@Test
public void test3() {
System.out.println("------------");
SmsBlendException knowEx = null;
try {
SmsFactory.getBySupplier(SupplierConstant.UNISMS).sendMessage(PHONE, new LinkedHashMap<>());
} catch (SmsBlendException e) {
knowEx = e;
System.out.println(knowEx.getMessage());
}
Assert.notNull(knowEx);
knowEx = null;
try {
SmsFactory.getBySupplier(SupplierConstant.UNISMS).sendMessage("", SmsUtils.getRandomInt(6));
} catch (SmsBlendException e) {
knowEx = e;
System.out.println(knowEx.getMessage());
}
Assert.notNull(knowEx);
knowEx = null;
try {
SmsFactory.getBySupplier(SupplierConstant.UNISMS).sendMessage(PHONE, "");
} catch (SmsBlendException e) {
knowEx = e;
System.out.println(knowEx.getMessage());
}
Assert.notNull(knowEx);
knowEx = null;
try {
SmsFactory.getBySupplier(SupplierConstant.UNISMS).sendMessage(PHONE, "111", new LinkedHashMap<>());
} catch (SmsBlendException e) {
knowEx = e;
System.out.println(knowEx.getMessage());
}
Assert.notNull(knowEx);
knowEx = null;
try {
SmsFactory.getBySupplier(SupplierConstant.UNISMS).massTexting(Collections.singletonList(PHONE), "");
} catch (SmsBlendException e) {
knowEx = e;
System.out.println(knowEx.getMessage());
}
Assert.notNull(knowEx);
knowEx = null;
try {
SmsFactory.getBySupplier(SupplierConstant.UNISMS).massTexting(Collections.singletonList(PHONE), "2222", new LinkedHashMap<>());
} catch (SmsBlendException e) {
knowEx = e;
System.out.println(knowEx.getMessage());
}
Assert.notNull(knowEx);
knowEx = null;
try {
SmsFactory.getBySupplier(SupplierConstant.UNISMS).massTexting(new ArrayList<>(), "321321");
} catch (SmsBlendException e) {
knowEx = e;
System.out.println(knowEx.getMessage());
}
Assert.notNull(knowEx);
}
//黑名单测试
@Test
public void test4() {
System.out.println("------------");
SmsBlend smsBlend = SmsFactory.getBySupplier(SupplierConstant.UNISMS);
//单黑名单添加
smsBlend.joinInBlacklist(PHONE);
SmsBlendException knowEx = null;
try {
smsBlend.sendMessage(PHONE, SmsUtils.getRandomInt(6));
} catch (SmsBlendException e) {
knowEx = e;
System.out.println(knowEx.getMessage());
}
Assert.notNull(knowEx);
//单黑名单移除
smsBlend.removeFromBlacklist(PHONE);
SmsResponse smsResponse = smsBlend.sendMessage(PHONE, SmsUtils.getRandomInt(6));
Assert.isTrue(smsResponse.isSuccess());
//批量黑名单添加
smsBlend.batchJoinBlacklist(Collections.singletonList(PHONE));
knowEx = null;
try {
smsBlend.sendMessage(PHONE, SmsUtils.getRandomInt(6));
} catch (SmsBlendException e) {
knowEx = e;
System.out.println(knowEx.getMessage());
}
Assert.notNull(knowEx);
//批量黑名单添加
smsBlend.batchRemovalFromBlacklist(Collections.singletonList(PHONE));
smsResponse = smsBlend.sendMessage(PHONE, SmsUtils.getRandomInt(6));
Assert.isTrue(smsResponse.isSuccess());
}
//账号级上限测试需成功发送4笔再发就会报错 参数配置 4
@Test
public void test5() {
System.out.println("------------");
SmsResponse smsResponse = SmsFactory.getBySupplier(SupplierConstant.UNISMS).sendMessage(PHONE, SmsUtils.getRandomInt(6));
Assert.isTrue(smsResponse.isSuccess());
SmsBlendException knowEx = null;
try {
SmsFactory.getBySupplier(SupplierConstant.UNISMS).sendMessage(PHONE, SmsUtils.getRandomInt(6));
} catch (SmsBlendException e) {
knowEx = e;
System.out.println(knowEx.getMessage());
}
Assert.notNull(knowEx);
}
//渠道级上限测试需成功发送6笔再发就会报错 参数配置 6
@Test
public void test6() {
System.out.println("------------");
SmsResponse smsResponse = SmsFactory.getBySupplier(SupplierConstant.UNISMS).sendMessage(PHONE1, SmsUtils.getRandomInt(6));
Assert.isTrue(smsResponse.isSuccess());
SmsBlendException knowEx = null;
try {
SmsFactory.getBySupplier(SupplierConstant.UNISMS).sendMessage(PHONE1, SmsUtils.getRandomInt(6));
} catch (SmsBlendException e) {
knowEx = e;
System.out.println(knowEx.getMessage());
}
Assert.notNull(knowEx);
}
}

View File

@ -0,0 +1,102 @@
package org.dromara.sms4j.example;
import cn.hutool.core.lang.Assert;
import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.aliyun.config.AlibabaConfig;
import org.dromara.sms4j.comm.utils.SmsUtils;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.noear.solon.test.SolonJUnit5Extension;
import org.noear.solon.test.SolonTest;
import java.util.ArrayList;
import java.util.List;
/**
* @author handy
*/
@Slf4j
@ExtendWith(SolonJUnit5Extension.class)
@SolonTest
public class SmsUtilsTest {
@Test
public void getRandomString() {
String randomString = SmsUtils.getRandomString();
log.info(randomString);
Assert.isTrue(randomString.length() == 6);
}
@Test
public void testGetRandomString() {
String randomString = SmsUtils.getRandomString(4);
log.info(randomString);
Assert.isTrue(randomString.length() == 4);
}
@Test
public void getRandomInt() {
String randomInt = SmsUtils.getRandomInt(4);
log.info(randomInt);
Assert.isTrue(randomInt.length() == 4);
}
@Test
public void isEmpty() {
Assert.isTrue(SmsUtils.isEmpty(""));
}
@Test
public void isNotEmpty() {
Assert.isTrue(SmsUtils.isNotEmpty("not"));
}
@Test
public void jsonForObject() {
AlibabaConfig alibabaConfig = SmsUtils.jsonForObject("{'templateName':'Test'}", AlibabaConfig.class);
Assert.isTrue(alibabaConfig.getTemplateName().equals("Test"));
}
@Test
public void copyBean() {
AlibabaConfig alibabaConfig = SmsUtils.jsonForObject("{'templateName':'Test'}", AlibabaConfig.class);
AlibabaConfig alibabaConfig1 = new AlibabaConfig();
SmsUtils.copyBean(alibabaConfig, alibabaConfig1);
Assert.isTrue(alibabaConfig1.getTemplateName().equals("Test"));
}
@Test
public void getNewMap() {
SmsUtils.getNewMap();
}
@Test
public void joinComma() {
List<String> list = new ArrayList<>();
list.add("12312341234");
list.add("12312341235");
String str = SmsUtils.joinComma(list);
log.info(str);
Assert.isTrue(str.equals("12312341234,12312341235"));
}
@Test
public void addCodePrefixIfNot() {
List<String> list = new ArrayList<>();
list.add("12312341234");
list.add("12312341235");
String str = SmsUtils.addCodePrefixIfNot(list);
log.info(str);
Assert.isTrue(str.equals("+8612312341234,+8612312341235"));
}
@Test
public void addCodePrefixIfNotToArray() {
List<String> list = new ArrayList<>();
list.add("12312341234");
list.add("12312341235");
String[] str = SmsUtils.addCodePrefixIfNotToArray(list);
Assert.isTrue(str[0].equals("+8612312341234") && str[1].equals("+8612312341235"));
}
}

View File

@ -1,4 +1,4 @@
package org.dromara.sms4j.solon.configuration; package org.dromara.sms4j.solon.config;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject; import cn.hutool.json.JSONObject;
@ -8,6 +8,8 @@ import org.dromara.sms4j.aliyun.config.AlibabaFactory;
import org.dromara.sms4j.api.SmsBlend; import org.dromara.sms4j.api.SmsBlend;
import org.dromara.sms4j.api.universal.SupplierConfig; import org.dromara.sms4j.api.universal.SupplierConfig;
import org.dromara.sms4j.api.verify.PhoneVerify; 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.cloopen.config.CloopenFactory;
import org.dromara.sms4j.comm.constant.Constant; import org.dromara.sms4j.comm.constant.Constant;
import org.dromara.sms4j.comm.utils.SmsUtils; import org.dromara.sms4j.comm.utils.SmsUtils;
@ -20,16 +22,23 @@ import org.dromara.sms4j.core.proxy.processor.CoreMethodParamValidateProcessor;
import org.dromara.sms4j.core.proxy.processor.RestrictedProcessor; import org.dromara.sms4j.core.proxy.processor.RestrictedProcessor;
import org.dromara.sms4j.core.proxy.processor.SingleBlendRestrictedProcessor; import org.dromara.sms4j.core.proxy.processor.SingleBlendRestrictedProcessor;
import org.dromara.sms4j.ctyun.config.CtyunFactory; 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.dingzhong.config.DingZhongFactory;
import org.dromara.sms4j.emay.config.EmayFactory; import org.dromara.sms4j.emay.config.EmayFactory;
import org.dromara.sms4j.huawei.config.HuaweiFactory; import org.dromara.sms4j.huawei.config.HuaweiFactory;
import org.dromara.sms4j.jdcloud.config.JdCloudFactory; 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.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.netease.config.NeteaseFactory;
import org.dromara.sms4j.provider.config.SmsConfig; import org.dromara.sms4j.provider.config.SmsConfig;
import org.dromara.sms4j.provider.factory.BaseProviderFactory; import org.dromara.sms4j.provider.factory.BaseProviderFactory;
import org.dromara.sms4j.provider.factory.ProviderFactoryHolder; import org.dromara.sms4j.provider.factory.ProviderFactoryHolder;
import org.dromara.sms4j.qiniu.config.QiNiuFactory;
import org.dromara.sms4j.solon.holder.SolonSmsDaoHolder; import org.dromara.sms4j.solon.holder.SolonSmsDaoHolder;
import org.dromara.sms4j.submail.config.SubMailFactory;
import org.dromara.sms4j.tencent.config.TencentFactory; import org.dromara.sms4j.tencent.config.TencentFactory;
import org.dromara.sms4j.unisms.config.UniFactory; import org.dromara.sms4j.unisms.config.UniFactory;
import org.dromara.sms4j.yunpian.config.YunPianFactory; import org.dromara.sms4j.yunpian.config.YunPianFactory;
@ -95,7 +104,7 @@ public class SmsBlendsInitializer {
continue; continue;
} }
configMap.put("config-id", configId); configMap.put("config-id", configId);
SmsUtils.replaceKeysSeperator(configMap, "-", "_"); SmsUtils.replaceKeysSeparator(configMap, "-", "_");
JSONObject configJson = new JSONObject(configMap); JSONObject configJson = new JSONObject(configMap);
SupplierConfig supplierConfig = JSONUtil.toBean(configJson, providerFactory.getConfigClass()); SupplierConfig supplierConfig = JSONUtil.toBean(configJson, providerFactory.getConfigClass());
SmsFactory.createSmsBlend(supplierConfig); SmsFactory.createSmsBlend(supplierConfig);
@ -119,6 +128,15 @@ public class SmsBlendsInitializer {
ProviderFactoryHolder.registerFactory(ZhutongFactory.instance()); ProviderFactoryHolder.registerFactory(ZhutongFactory.instance());
ProviderFactoryHolder.registerFactory(LianLuFactory.instance()); ProviderFactoryHolder.registerFactory(LianLuFactory.instance());
ProviderFactoryHolder.registerFactory(DingZhongFactory.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")) { if(SmsUtils.isClassExists("com.jdcloud.sdk.auth.CredentialsProvider")) {
ProviderFactoryHolder.registerFactory(JdCloudFactory.instance()); ProviderFactoryHolder.registerFactory(JdCloudFactory.instance());
} }

View File

@ -9,6 +9,7 @@ import org.noear.solon.annotation.Configuration;
import org.noear.solon.annotation.Inject; import org.noear.solon.annotation.Inject;
import org.noear.solon.core.AppContext; import org.noear.solon.core.AppContext;
import java.util.ArrayList;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -16,7 +17,7 @@ import java.util.Map;
/** /**
* smsConfig参数意义为确保注入时smsConfig已经存在 * smsConfig参数意义为确保注入时smsConfig已经存在
*/ */
@Condition(onProperty = "${sms.configType}=config_file") @Condition(onProperty = "${sms.configType}=yaml")
@Configuration @Configuration
public class SupplierConfiguration { public class SupplierConfiguration {
@Inject @Inject
@ -27,16 +28,25 @@ public class SupplierConfiguration {
context.cfg().getProp(prefix).bindTo(obj); context.cfg().getProp(prefix).bindTo(obj);
return obj; return obj;
} }
@Bean @Bean
protected Map<String, Map<String, Object>> blends(){ protected Map<String, Map<String, Object>> blends() {
return injectObj("sms.blends",new LinkedHashMap<>()); return injectObj("sms.blends", new LinkedHashMap<>());
} }
@Bean @Bean
protected SmsBlendsInitializer smsBlendsInitializer(List<BaseProviderFactory<? extends SmsBlend, ? extends org.dromara.sms4j.api.universal.SupplierConfig>> factoryList, protected SmsBlendsInitializer smsBlendsInitializer(List<BaseProviderFactory> factoryList,
SmsConfig smsConfig, SmsConfig smsConfig,
Map<String, Map<String, Object>> blends){ Map<String, Map<String, Object>> blends) {
return new SmsBlendsInitializer(factoryList,smsConfig,blends,context);
//todo: solon 不支持泛型的 List[Bean] 注入
List<BaseProviderFactory<? extends SmsBlend, ? extends org.dromara.sms4j.api.universal.SupplierConfig>> factoryList2 = new ArrayList<>(factoryList.size());
for (BaseProviderFactory factory : factoryList) {
factoryList2.add((BaseProviderFactory<? extends SmsBlend, ? extends org.dromara.sms4j.api.universal.SupplierConfig>) factory);
}
return new SmsBlendsInitializer(factoryList2, smsConfig, blends, context);
} }
} }

View File

@ -4,6 +4,7 @@ import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil; import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.entity.SmsResponse; import org.dromara.sms4j.api.entity.SmsResponse;
import org.dromara.sms4j.api.utils.SmsRespUtils;
import org.dromara.sms4j.comm.delayedTime.DelayedTime; import org.dromara.sms4j.comm.delayedTime.DelayedTime;
import org.dromara.sms4j.comm.exception.SmsBlendException; import org.dromara.sms4j.comm.exception.SmsBlendException;
import org.dromara.sms4j.comm.utils.SmsUtils; import org.dromara.sms4j.comm.utils.SmsUtils;
@ -82,7 +83,7 @@ public class ZhangJunSmsImpl extends AbstractSmsBlend<ZhangJunConfig> {
@Override @Override
public SmsResponse massTexting(List<String> phones, String templateId, LinkedHashMap<String, String> messages) { public SmsResponse massTexting(List<String> phones, String templateId, LinkedHashMap<String, String> messages) {
String messageStr = JSONUtil.toJsonStr(messages); String messageStr = JSONUtil.toJsonStr(messages);
return getSmsResponse(SmsUtils.arrayToString(phones), messageStr, templateId); return getSmsResponse(SmsUtils.addCodePrefixIfNot(phones), messageStr, templateId);
} }
private SmsResponse getSmsResponse(String phone, String message, String templateId) { private SmsResponse getSmsResponse(String phone, String message, String templateId) {
@ -90,9 +91,7 @@ public class ZhangJunSmsImpl extends AbstractSmsBlend<ZhangJunConfig> {
try { try {
smsResponse = getResponse(http.postJson(getConfig().getUrl(), null, message)); smsResponse = getResponse(http.postJson(getConfig().getUrl(), null, message));
} catch (SmsBlendException e) { } catch (SmsBlendException e) {
smsResponse = new SmsResponse(); smsResponse = errorResp(e.message);
smsResponse.setSuccess(false);
smsResponse.setData(e.getMessage());
} }
if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) { if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) {
retry = 0; retry = 0;
@ -109,11 +108,7 @@ public class ZhangJunSmsImpl extends AbstractSmsBlend<ZhangJunConfig> {
} }
private SmsResponse getResponse(JSONObject resJson) { private SmsResponse getResponse(JSONObject resJson) {
SmsResponse smsResponse = new SmsResponse(); return SmsRespUtils.resp(resJson, "OK".equals(resJson.getStr("Code")), getConfigId());
smsResponse.setSuccess("OK".equals(resJson.getStr("Code")));
smsResponse.setData(resJson);
smsResponse.setConfigId(getConfigId());
return smsResponse;
} }
} }

View File

@ -4,114 +4,157 @@ sms:
# 账户上限 # 账户上限
account-max: 1 account-max: 1
blends: blends:
alibaba:
# 阿里短信例子 # 阿里短信例子
ali1: ali:
#厂商标识,标定此配置是哪个厂商,详细请看厂商标识介绍部分 #厂商标识,标定此配置是哪个厂商,详细请看厂商标识介绍部分
supplier: alibaba supplier: alibaba
#您的accessKey #您的accessKey
access-key-id: 您的accessKey access-key-id: 您的accessKey
#您的accessKeySecret #您的accessKeySecret
access-key-secret: 您的accessKeySecret access-key-secret: 您的accessKeySecret
#您的短信签名 #您的短信签名
signature: 测试签名 signature: 测试签名
#模板ID 非必须配置如果使用sendMessage的快速发送需此配置 #模板ID 非必须配置如果使用sendMessage的快速发送需此配置
template-id: SMS_272470496 template-id: SMS_272470496
# 模版名称 # 模版名称
templateName: code templateName: code
ali2: # 腾讯短信例子
#厂商标识,标定此配置是哪个厂商,详细请看厂商标识介绍部分 tx:
supplier: alibaba #厂商标识
#您的accessKey supplier: tencent
access-key-id: 您的accessKey #您的accessKey
#您的accessKeySecret access-key-id: 您的accessKey
access-key-secret: 您的accessKeySecret #您的accessKeySecret
#您的短信签名 access-key-secret: 您的accessKeySecret
signature: 测试签名 #您的短信签名
#模板ID 非必须配置如果使用sendMessage的快速发送需此配置 signature: 测试签名
template-id: SMS_272470496 #模板ID
# 模版名称 template-id: 1603670
templateName: code #您的sdkAppId
# 腾讯短信例子: sdk-app-id: 1400761645
tencent: # 华为短信例子
tx: hw:
#厂商标识 #厂商标识
supplier: tencent supplier: huawei
#您的accessKey #您的accessKey
access-key-id: 您的accessKey access-key-id: 您的accessKey
#您的accessKeySecret #您的accessKeySecret
access-key-secret: 您的accessKeySecret access-key-secret: 您的accessKeySecret
#您的短信签名 #您的短信签名
signature: 测试签名 signature: 测试签名
#模板ID #模板ID
template-id: 1603670 template-id: ac4888205c274b2a8263479b954c1ab5
#您的sdkAppId # APP接入地址
sdk-app-id: 1400761645 url: https://smsapi.cn-north-4.myhuaweicloud.com:443
# 华为短信例子 # 模版名称
huawei: templateName: code
hw: # 通道号
#厂商标识 sender: 8823040504797
supplier: huawei # 合一短信例子
#您的accessKey uni:
access-key-id: 您的accessKey #厂商标识
#您的accessKeySecret supplier: unisms
access-key-secret: 您的accessKeySecret #您的accessKey
#您的短信签名 access-key-id: 您的accessKey
signature: 测试签名 #您的短信签名
#模板ID signature: 测试签名
template-id: ac4888205c274b2a8263479b954c1ab5 #模板ID
# APP接入地址 template-id: pub_verif_short
url: https://smsapi.cn-north-4.myhuaweicloud.com:443 # 模版名称
# 模版名称 templateName: code
templateName: code # 渠道上限
# 通道号 maximum: 2
sender: 8823040504797
# 合一短信例子
unisms:
uni:
#厂商标识
supplier: unisms
#您的accessKey
access-key-id: 您的accessKey
#您的短信签名
signature: 测试签名
#模板ID
template-id: pub_verif_short
# 模版名称
templateName: code
# 渠道上限
maximum: 2
lianlu: lianlu:
lianlu: supplier: lianlu
supplier: lianlu templateId: 模板id
templateId: 模板id appId: 100116
appId: 100116 appKey: d42d7
appKey: d42d7 mchId: 100
mchId: 100 signName: 【test】
signName: 【test】
cloopen: cloopen:
cloopen: # 短信厂商
# 短信厂商 supplier: cloopen
supplier: cloopen base-url: https://app.cloopen.com:8883/2013-12-26
base-url: https://app.cloopen.com:8883/2013-12-26 access-key-id: 你的Access Key
access-key-id: 你的Access Key access-key-secret: 你的Access Key Secret
access-key-secret: 你的Access Key Secret sdkAppId: 你的应用ID
sdkAppId: 你的应用ID #自定义广州掌骏短信添加factory全路径。config,factory,SmsImpl复制其他默认实现即可修改对应的supplier和发送核心逻辑即可
#自定义广州掌骏短信添加factory全路径。config,factory,SmsImpl复制其他默认实现即可修改对应的supplier和发送核心逻辑即可 # zhangjun:
# zhangjun: # supplier: zhangjun
# supplier: zhangjun # factory: org.dromara.sms4j.example.zhangjun.ZhangJunFactory
# factory: org.dromara.sms4j.example.zhangjun.ZhangJunFactory # templateId: d2a****777
# templateId: d2a****777 # appId: 64c52d2a****77775fe72e3
# appId: 64c52d2a****77775fe72e3 # sid: d2a****777
# sid: d2a****777 # url: https://sms.idowe.com/**/**/**/send
# url: https://sms.idowe.com/**/**/**/send
qiniu: qiniu:
qiniu: access-key-id: EQcDflLTCYnU1******CmqIYLhog1lkWHb2
access-key-id: EQcDflLTCYnU1******CmqIYLhog1lkWHb2 access-key-secret: NeS2ptvZQoIy*****err2DdLe7wxFfQvji1
access-key-secret: NeS2ptvZQoIy*****err2DdLe7wxFfQvji1 templateId: 1752130****15859456
templateId: 1752130****15859456 signatureId: 175185*****1624960
signatureId: 175185*****1624960 templateName: code
templateName: code # 中国移动 云MAS
mas:
supplier: mas
# 请求方式默认为 HTTP
# 请求方法 默认为 HTTP模式下的 tmpsubmit
# norsubmit 无模板接口 不需要配置templateId
# tmpsubmit 有模板接口 需要配置templateId
# HTTPS 模式下 请求方法有 submit tmpsubmit
action: tmpsubmit
# 请求地址 HTTP模式下可不配置 默认为 http://112.35.1.155:1992/sms/
# HTTPS模式下 请设置 https://****:****/sms/
request-url: http://112.35.1.155:1992/sms/
sdk-app-id: 接口账号用户名
access-key-secret: 用户密码
ec-name: 企业名称
signature: 签名编码
# 当请求方法为 tmpsubmit 时 需要配置templateId
template-id:
# 可为空 不为空时请遵守中国移动云MAS开发文档中的描述[服务代码加扩展码总长度不能超过20位。]
add-serial:
# 百度智能云 sms
baidu:
access-key-id: 访问密钥ID
access-key-secret: 用户密钥
ec-name: 企业名称
signature: 签名编码
template-id: 模板ID
# 模板变量名称
template-name: code
custom: 用户自定义参数,格式为字符串,状态回调时会回传该值 可不传
user-ext-id: 通道自定义扩展码 可不传
# 创蓝
chuanglan:
access-key-id: 111111
access-key-secret: 111111
templateId: 【253云通讯】{$var}您的验证码是:{$var}{$var}分钟内有效
# 极光
jiguang:
supplier: jiguang
signId: 签名 ID该字段为空则使用应用默认签名
action: 默认请求方法 messages
templateName: 模板变量名称
voice: action设置为voice_codes有效 语音验证码播报语言选择0中文播报1英文播报2中英混合播报
ttl: action设置为voice_codes有效 验证码有效期,默认为 60 秒
tag: action设置为messages/batch有效 标签
# 螺丝帽
luosimao:
accessKeyId: 后台提取的API key
action: 默认请求方法 send.json
# submail
submail:
accessKeyId: APPID
accessKeySecret: APPKEY
action: 默认请求方法 send.json
signType: MD5 或 SHA-1 默认MD5 填写任意值,不为即为 密匙明文验证模式
signVersion: signature加密计算方式 为2时会忽略某些字段
templateId: 模板ID
signature: 签名
# danmi
danmi:
accessKeyId: ACCOUNT SID
accessKeySecret: AUTH TOKEN
action: 默认请求方法 distributor/sendSMS
sms-oa: sms-oa:
config-type: yaml config-type: yaml

View File

@ -2,26 +2,29 @@ package org.dromara.sms4j.example;
import cn.hutool.core.collection.ListUtil; import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.map.MapUtil; import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil; import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.SmsBlend; import org.dromara.sms4j.api.SmsBlend;
import org.dromara.sms4j.api.entity.SmsResponse; import org.dromara.sms4j.api.entity.SmsResponse;
import org.dromara.sms4j.baidu.service.BaiduSmsImpl;
import org.dromara.sms4j.comm.constant.SupplierConstant; import org.dromara.sms4j.comm.constant.SupplierConstant;
import org.dromara.sms4j.comm.utils.SmsUtils; import org.dromara.sms4j.comm.utils.SmsUtils;
import org.dromara.sms4j.core.factory.SmsFactory; import org.dromara.sms4j.core.factory.SmsFactory;
import org.dromara.sms4j.danmi.service.DanMiSmsImpl;
import org.dromara.sms4j.jg.service.JgSmsImpl;
import org.dromara.sms4j.lianlu.service.LianLuSmsImpl; import org.dromara.sms4j.lianlu.service.LianLuSmsImpl;
import org.dromara.sms4j.luosimao.service.LuoSiMaoSmsImpl;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import java.util.ArrayList; import java.util.*;
import java.util.LinkedHashMap;
import java.util.Map;
@Slf4j @Slf4j
@SpringBootTest @SpringBootTest
class Sms4jTest { public class Sms4jTest {
/** /**
* 填测试手机号 * 填测试手机号
@ -278,4 +281,249 @@ class Sms4jTest {
} }
} /**
* 中国移动 云MAS
*/
@Test
public void masSmsTest() {
if (StrUtil.isBlank(PHONE)) {
return;
}
// 发送一对一/一对多普通短信
// HTTP模式下 action请配置为 norsubmit
// HTTPS模式下 action请配置为 submit
SmsResponse oneToMany = SmsFactory.getBySupplier(SupplierConstant.MAS)
.sendMessage(PHONE, "测试短信" + SmsUtils.getRandomInt(6));
log.info(JSONUtil.toJsonStr(oneToMany));
// 发送多对多普通短信
// HTTP模式下 action请配置为 norsubmit
// HTTPS模式下 action请配置为 submit
LinkedHashMap<String, String> content = new LinkedHashMap<>();
content.put("18***1", "测试短信1");
content.put("18***2", "测试短信2");
content.put("18***3", "测试短信3");
content.put("18***4", "测试短信4");
SmsResponse manyToMany1 = SmsFactory.getBySupplier(SupplierConstant.MAS)
.sendMessage(PHONE, content);
log.info(JSONUtil.toJsonStr(manyToMany1));
// 或者
SmsResponse manyToMany2 = SmsFactory.getBySupplier(SupplierConstant.MAS)
.sendMessage(PHONE, JSONUtil.toJsonStr(content));
log.info(JSONUtil.toJsonStr(manyToMany2));
// 发送模板短信
// HTTP模式下或者HTTPS模式下 action请都配置为 tmpsubmit
// 无参数
SmsResponse strRes = SmsFactory.getBySupplier(SupplierConstant.MAS)
.sendMessage(PHONE, StrUtil.EMPTY);
log.info(JSONUtil.toJsonStr(strRes));
//数组格式
String[] paramsArr = {"param1", "param2"};
SmsResponse arrRes = SmsFactory.getBySupplier(SupplierConstant.MAS)
.sendMessage(PHONE, JSONUtil.toJsonStr(paramsArr));
log.info(JSONUtil.toJsonStr(arrRes));
//list格式
List<String> paramsList = new ArrayList<>();
paramsList.add("param1");
paramsList.add("param2");
SmsResponse listRes = SmsFactory.getBySupplier(SupplierConstant.MAS)
.sendMessage(PHONE, JSONUtil.toJsonStr(paramsList));
log.info(JSONUtil.toJsonStr(listRes));
}
/**
* 百度短信
*/
@Test
public void baiduSmsTest() {
if (StrUtil.isBlank(PHONE)) {
return;
}
// 发送短信
SmsResponse resp = SmsFactory.getBySupplier(SupplierConstant.BAIDU)
.sendMessage(PHONE, SmsUtils.getRandomInt(6));
log.info(JSONUtil.toJsonStr(resp));
// 发送携带幂等性参数短信
BaiduSmsImpl sendWithClientToken = (BaiduSmsImpl) SmsFactory.getBySupplier(SupplierConstant.BAIDU);
String clientToken = UUID.fastUUID().toString(true);
SmsResponse respWithClientToken = sendWithClientToken.sendMessageWithClientToken(PHONE,
SmsUtils.getRandomInt(6),
clientToken);
log.info(JSONUtil.toJsonStr(respWithClientToken));
}
/**
* 创蓝短信
*/
@Test
public void chungLanTest() {
if (StrUtil.isBlank(PHONE)) {
return;
}
//测试群发模板
List<String> arrayList = new ArrayList<>();
arrayList.add(PHONE);
arrayList.add("135****000");
LinkedHashMap<String, String> map = new LinkedHashMap<>();
map.put("1", "1544");
map.put("2", "2222");
SmsResponse smsResponse2 = SmsFactory.getBySupplier(SupplierConstant.CHUANGLAN).massTexting(arrayList, "[test]你的验证码是{$val},{$val}", map);
log.info("smsResponse2:{}", smsResponse2);
//测试单条发送
SmsResponse smsResponse1 = SmsFactory.getBySupplier(SupplierConstant.CHUANGLAN).sendMessage(PHONE, "1544&2222");
log.info("smsResponse1:{}", smsResponse1);
//测试单条模板发送
LinkedHashMap<String, String> map3 = new LinkedHashMap<>();
map3.put("1", "1544");
map3.put("2", "2222");
SmsResponse smsResponse3 = SmsFactory.getBySupplier(SupplierConstant.CHUANGLAN).sendMessage(PHONE, "[test]你的验证码是{$val},{$val}", map3);
log.info("smsResponse3:{}", smsResponse3);
}
/**
* 极光短信
*/
@Test
public void jgSmsTest() {
// 极光 发送文本验证码短信 API 不需要传入具体验证码 返回 {"msg_id": "288193860302"}
SmsResponse smsResponse1 = SmsFactory.getBySupplier(SupplierConstant.JIGUANG).sendMessage(PHONE, "");
Assert.isTrue(smsResponse1.isSuccess());
// 极光 发送语音验证码短信 请确保action配置为voice_codes
JgSmsImpl voiceCode = (JgSmsImpl) SmsFactory.getBySupplier(SupplierConstant.JIGUANG);
SmsResponse voiceResp = voiceCode.sendVoiceCode(PHONE,
SmsUtils.getRandomInt(6));
Assert.isTrue(voiceResp.isSuccess());
// 验证验证码是否有效 请确保action配置为voice_codes
JgSmsImpl verify = (JgSmsImpl) SmsFactory.getBySupplier(SupplierConstant.JIGUANG);
SmsResponse verifyResp = verify.verifyCode("123456", "288193860302");
Assert.isTrue(verifyResp.isSuccess());
// 极光 发送单条模板短信 API 发送自定义验证码 sendTemplateSMS
LinkedHashMap<String, String> map1 = new LinkedHashMap<>();
map1.put("code", "123456");
SmsResponse smsResponse2 = SmsFactory.getBySupplier(SupplierConstant.JIGUANG).sendMessage(PHONE, map1);
Assert.isTrue(smsResponse2.isSuccess());
// 极光 发送单条模板短信 API 发送多参数自定义模板短信 sendTemplateSMS_with_multipleParameters
LinkedHashMap<String, String> map2 = new LinkedHashMap<>();
map2.put("name", "test");
map2.put("password", "test");
SmsResponse smsResponse3 = SmsFactory.getBySupplier(SupplierConstant.JIGUANG).sendMessage(PHONE, "226992", map2);
Assert.isTrue(smsResponse3.isSuccess());
// sendBatchTemplateSMS
LinkedHashMap<String, String> map3 = new LinkedHashMap<>();
map3.put("name", "test");
map3.put("password", "test");
List<String> phones = new ArrayList<>();
phones.add(PHONE);
phones.add("xxx");
SmsResponse smsResponse4 = SmsFactory.getBySupplier(SupplierConstant.JIGUANG).massTexting(phones, "226992", map3);
Assert.isTrue(smsResponse4.isSuccess());
}
/**
* 螺丝帽短信
*/
@Test
public void luosimaoSmsTest() {
// 螺丝帽 发送短信接口详细 发送短信接口详细 send.json
SmsResponse smsResponse1 = SmsFactory.getBySupplier(SupplierConstant.LUO_SI_MAO).sendMessage(PHONE, "");
Assert.isTrue(smsResponse1.isSuccess());
// 螺丝帽 批量发送接口详细 send_batch.json
SmsResponse smsResponse2 = SmsFactory.getBySupplier(SupplierConstant.LUO_SI_MAO).massTexting(Collections.singletonList(PHONE), "");
Assert.isTrue(smsResponse2.isSuccess());
// 螺丝帽 定时批量发送接口详细 send_batch.json
LuoSiMaoSmsImpl luoSiMao = (LuoSiMaoSmsImpl) SmsFactory.getBySupplier(SupplierConstant.LUO_SI_MAO);
SmsResponse smsResponse3 = luoSiMao.massTextingOnTime(Collections.singletonList(PHONE), "", new Date());
Assert.isTrue(smsResponse3.isSuccess());
// 螺丝帽 查询账户余额 status.json
SmsResponse smsResponse4 = luoSiMao.queryAccountBalance();
Assert.isTrue(smsResponse4.isSuccess());
}
/**
* SUBMAIL短信
*/
@Test
public void mysubmailSmsTest() {
// 短信发送 send.json xxxx签名可配置 系统自动带入
SmsResponse smsResponse1 = SmsFactory.getBySupplier(SupplierConstant.MY_SUBMAIL).sendMessage(PHONE, "【SUBMAIL】你好你的验证码是2257");
Assert.isTrue(smsResponse1.isSuccess());
// 短信模板发送 xsend.json
LinkedHashMap<String, String> vars = new LinkedHashMap<>();
vars.put("code", "123456");
SmsResponse smsResponse2 = SmsFactory.getBySupplier(SupplierConstant.MY_SUBMAIL).sendMessage(PHONE, "xxx", vars);
Assert.isTrue(smsResponse2.isSuccess());
// 短信一对多发送 multisend.json xxxx签名可配置 系统自动带入 content字段说明短信正文 案例没有说明无需传
LinkedHashMap<String, String> vars1 = new LinkedHashMap<>();
vars1.put("content", "【SUBMAIL】您的短信验证码4438请在10分钟内输入。");
vars1.put("code", "123456");
vars1.put("time", "10");
SmsResponse smsResponse3 = SmsFactory.getBySupplier(SupplierConstant.MY_SUBMAIL).massTexting(Collections.singletonList(PHONE), JSONUtil.toJsonStr(vars1));
Assert.isTrue(smsResponse3.isSuccess());
// 短信模板一对多发送 multixsend.json
LinkedHashMap<String, String> vars2 = new LinkedHashMap<>();
vars2.put("code", "123456");
SmsResponse smsResponse4 = SmsFactory.getBySupplier(SupplierConstant.MY_SUBMAIL).massTexting(Collections.singletonList(PHONE), "xxx", vars2);
Assert.isTrue(smsResponse4.isSuccess());
// 短信批量群发 batchsend.json xxxx签名可配置 系统自动带入 content字段说明短信正文 案例没有说明无需传
LinkedHashMap<String, String> vars3 = new LinkedHashMap<>();
vars3.put("content", "123456");
SmsResponse smsResponse5 = SmsFactory.getBySupplier(SupplierConstant.MY_SUBMAIL).massTexting(Collections.singletonList(PHONE), JSONUtil.toJsonStr(vars3));
Assert.isTrue(smsResponse5.isSuccess());
// 短信批量模板群发 batchxsend.json
LinkedHashMap<String, String> vars4 = new LinkedHashMap<>();
vars4.put("code", "123456");
vars4.put("time", "10");
SmsResponse smsResponse6 = SmsFactory.getBySupplier(SupplierConstant.MY_SUBMAIL).massTexting(Collections.singletonList(PHONE), "xxx", vars4);
Assert.isTrue(smsResponse6.isSuccess());
}
/**
* danmi短信
*/
@Test
public void danmiSmsTest() {
// 短信发送 distributor/sendSMS 群发 massTexting
SmsResponse smsResponse1 = SmsFactory.getBySupplier(SupplierConstant.DAN_MI).sendMessage(PHONE, "【danmi】你好你的验证码是666");
Assert.isTrue(smsResponse1.isSuccess());
DanMiSmsImpl danMiSms = (DanMiSmsImpl) SmsFactory.getBySupplier(SupplierConstant.DAN_MI);
// 短信余额查询 distributor/user/query
SmsResponse smsResponse2 = danMiSms.queryBalance();
Assert.isTrue(smsResponse2.isSuccess());
// 语音验证码发送 voice/voiceCode
SmsResponse smsResponse3 = danMiSms.voiceCode(PHONE, "666");
Assert.isTrue(smsResponse3.isSuccess());
// 语音通知文件发送 voice/voiceNotify
SmsResponse smsResponse4 = danMiSms.voiceNotify(PHONE, "sjkhduiq");
Assert.isTrue(smsResponse4.isSuccess());
// 语音模板通知发送 voice/voiceTemplate
SmsResponse smsResponse5 = danMiSms.voiceTemplate(PHONE, "opipedlqza", "111,222,333");
Assert.isTrue(smsResponse5.isSuccess());
}
}

Some files were not shown because too many files have changed in this diff Show More