mirror of
https://gitee.com/dromara/sms4j.git
synced 2025-12-08 01:48:38 +08:00
Compare commits
91 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc5af3a68a | ||
|
|
3be4e34d6b | ||
|
|
9baa8bba57 | ||
|
|
03d30271dd | ||
|
|
920f9304ea | ||
|
|
cedae2696b | ||
|
|
7582982d44 | ||
|
|
6aa173501e | ||
|
|
5bc7d0152e | ||
|
|
bfdc75d538 | ||
|
|
4e3845e2ce | ||
|
|
b168d5235f | ||
|
|
bc8e329bfa | ||
|
|
09a3dba016 | ||
|
|
a62bca40b6 | ||
|
|
8498fafa65 | ||
|
|
aeecaaa8f5 | ||
|
|
2ef232d9ff | ||
|
|
e6173c5d2f | ||
|
|
44287e62d4 | ||
|
|
49c5af9f70 | ||
|
|
98e779376d | ||
|
|
ff4d6d8bf4 | ||
|
|
76e9ec060e | ||
|
|
d58f2d0eaf | ||
|
|
ec501e441d | ||
|
|
f9f197935a | ||
|
|
197dc65cf8 | ||
|
|
1c0d518fdb | ||
|
|
46f3ccfbf2 | ||
|
|
109f50cfd1 | ||
|
|
30c4488cde | ||
|
|
8a0dd4bc3d | ||
|
|
8aca0fd957 | ||
|
|
1a31bb5189 | ||
|
|
e81eeffe40 | ||
|
|
95601131cd | ||
|
|
3f9c22a04d | ||
|
|
463f3e568b | ||
|
|
298e7c48e5 | ||
|
|
d7589aa7a1 | ||
|
|
2ee0389eda | ||
|
|
36348b54b1 | ||
|
|
2df3db09d9 | ||
|
|
ea6e6a9fa6 | ||
|
|
eea34ace98 | ||
|
|
319a0d2cd1 | ||
|
|
21fe49f627 | ||
|
|
cf8b977a9e | ||
|
|
506acbf906 | ||
|
|
12ab72d6d1 | ||
|
|
b045df8be0 | ||
|
|
d921679af4 | ||
|
|
2dec57ad80 | ||
|
|
4fbea6e6a1 | ||
|
|
a8e2ddb420 | ||
|
|
1a3abc4be3 | ||
|
|
f0e018b855 | ||
|
|
8ec8cf8bd6 | ||
|
|
5020771a7a | ||
|
|
9623b28b31 | ||
|
|
98fa90ee1f | ||
|
|
780caef8cd | ||
|
|
f5ce5b9be4 | ||
|
|
a2a740a339 | ||
|
|
f7c53c5f4e | ||
|
|
4a5e342d15 | ||
|
|
bea0c20e67 | ||
|
|
fb198304a0 | ||
|
|
f3327c33fa | ||
|
|
b5744a92ed | ||
|
|
2b36985012 | ||
|
|
154b7c3d3e | ||
|
|
525166c102 | ||
|
|
2d1c50337b | ||
|
|
a5f7ba37a4 | ||
|
|
b19287cd58 | ||
|
|
81341fcf7c | ||
|
|
9366a4d0f5 | ||
|
|
7efa3319c7 | ||
|
|
ef44c1a2df | ||
|
|
d4b213c8f2 | ||
|
|
f54331acc5 | ||
|
|
6c29ca195d | ||
|
|
fc4830b58c | ||
|
|
067c08ab46 | ||
|
|
c2adc018e0 | ||
|
|
157a19bc8e | ||
|
|
18fe81286a | ||
|
|
40e0460e15 | ||
|
|
ca13119acc |
15
README.md
15
README.md
@ -1,9 +1,9 @@
|
||||
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">sms4j v3.2.0</h1>
|
||||
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">sms4j v3.3.5</h1>
|
||||
<h4 align="center" style="margin: 30px 0 30px; font-weight: bold;">sms4j -- 让发送短信变的更简单</h4>
|
||||
<p align="center">
|
||||
<a href="https://gitee.com/dromara/sms4j/stargazers"><img src="https://gitee.com/dromara/sms4j/badge/star.svg?theme=gvp"></a>
|
||||
<a href="https://gitee.com/dromara/sms4j/blob/master/LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-green"></a>
|
||||
<a href="https://gitee.com/dromara/sms4j"><img src="https://img.shields.io/badge/version-v3.2.0-blue"></a>
|
||||
<a href="https://gitee.com/dromara/sms4j"><img src="https://img.shields.io/badge/version-v3.3.5-blue"></a>
|
||||
</p>
|
||||
<img src="/public/logo.png">
|
||||
|
||||
@ -16,8 +16,9 @@
|
||||
如果我们的项目对你产生了帮助,或者你觉得还算值得鼓励,请用你发财的小手帮助点上一个start
|
||||
[gitee](https://gitee.com/dromara/sms4j)
|
||||
[github](https://github.com/dromara/sms4j)
|
||||
[gitcode](https://gitcode.com/dromara/SMS4J)
|
||||
|
||||
#### [官方文档](http://wind.kim)
|
||||
#### [官方文档](https://sms4j.com)
|
||||
#### [JavaDoc文档](https://apidoc.gitee.com/dromara/sms4j/)
|
||||
|
||||
## 支持厂商一览
|
||||
@ -35,6 +36,13 @@
|
||||
- **[助通短信](https://www.ztinfo.cn/products/sms)**
|
||||
- **[联麓短信](https://console.shlianlu.com/#/document/smsDoc)**
|
||||
- **[鼎众短信](http://demoapi.321sms.com:8201/index.html)**
|
||||
- **[创蓝短信](https://doc.chuanglan.com/document/HAQYSZKH9HT5Z50L)**
|
||||
- **[极光短信](https://docs.jiguang.cn/jsms)**
|
||||
- **[布丁云短信](https://console-docs.apipost.cn/preview/986c24caf79228ed/d3d8a6d5faf6ef51)**
|
||||
- **[中国移动 云MAS短信](https://mas.10086.cn/)**
|
||||
- **[螺丝帽短信](https://luosimao.com)**
|
||||
- **[SUBMAIL短信](https://www.mysubmail.com/)**
|
||||
- **[单米科技短信](https://www.danmi.com/)**
|
||||
|
||||
## 在SpringBoot环境集成
|
||||
|
||||
@ -145,6 +153,7 @@ sms:
|
||||
## 贡献原则
|
||||
- 我们原则上欢迎任何人为sms4j添加加瓦贡献代码
|
||||
- 贡献代码应注释完备,按照javaDoc标准对 类,方法,变量,参数,返回值等信息说明
|
||||
- 如新增短信厂商需要同時以MD格式编写厂商的使用文档
|
||||
- 新增的方法模块不能破坏原有结构和兼容性
|
||||
- 如果我们关闭了你的issues或者pr请查看回复内容,我们会在回复中做出解释
|
||||
|
||||
|
||||
50
pom.xml
50
pom.xml
@ -17,8 +17,9 @@
|
||||
<module>sms4j-provider</module>
|
||||
<module>sms4j-core</module>
|
||||
<module>sms4j-spring-boot-starter</module>
|
||||
<module>sms4j-solon-plugin</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-Email-plugin</module>
|
||||
<module>sms4j-oa-plugin</module>
|
||||
@ -52,15 +53,15 @@
|
||||
</scm>
|
||||
|
||||
<properties>
|
||||
<revision>3.2.0</revision>
|
||||
<revision>3.3.5</revision>
|
||||
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<spring.boot.version>2.7.18</spring.boot.version>
|
||||
<solon.version>2.6.5</solon.version>
|
||||
<solon.version>3.0.1</solon.version>
|
||||
<redisson.version>3.17.0</redisson.version>
|
||||
<jdcloud.version>1.3.3</jdcloud.version>
|
||||
<hutool.version>5.8.25</hutool.version>
|
||||
<hutool.version>5.8.28</hutool.version>
|
||||
<xmlblend.version>2.3.0</xmlblend.version>
|
||||
<activation.version>1.1.1</activation.version>
|
||||
<mail.version>1.6.2</mail.version>
|
||||
@ -242,20 +243,20 @@
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<!-- <!– GPG –>-->
|
||||
<!-- <plugin>-->
|
||||
<!-- <groupId>org.apache.maven.plugins</groupId>-->
|
||||
<!-- <artifactId>maven-gpg-plugin</artifactId>-->
|
||||
<!-- <version>1.6</version>-->
|
||||
<!-- <executions>-->
|
||||
<!-- <execution>-->
|
||||
<!-- <phase>verify</phase>-->
|
||||
<!-- <goals>-->
|
||||
<!-- <goal>sign</goal>-->
|
||||
<!-- </goals>-->
|
||||
<!-- </execution>-->
|
||||
<!-- </executions>-->
|
||||
<!-- </plugin>-->
|
||||
<!-- GPG -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
<version>1.6</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>sign</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<!--Compiler -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
@ -313,6 +314,19 @@
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.sonatype.central</groupId>
|
||||
<artifactId>central-publishing-maven-plugin</artifactId>
|
||||
<version>0.6.0</version>
|
||||
<extensions>true</extensions>
|
||||
<configuration>
|
||||
<publishingServerId>ossrh</publishingServerId>
|
||||
<!-- 自动发布 -->
|
||||
<autoPublish>true</autoPublish>
|
||||
<!-- 等待发布 -->
|
||||
<waitUntil>published</waitUntil>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ import java.util.Map;
|
||||
* 2023/6/8 22:35
|
||||
**/
|
||||
public class MailFactory{
|
||||
private static final Map<Object,MailSmtpConfig> configs = new HashMap<>();
|
||||
private static final Map<Object,MailSmtpConfig> CONFIGS = new HashMap<>();
|
||||
|
||||
/**
|
||||
* createMailClient
|
||||
@ -27,7 +27,7 @@ public class MailFactory{
|
||||
*/
|
||||
public static MailClient createMailClient(Object key){
|
||||
try {
|
||||
return MailBuild.build(configs.get(key));
|
||||
return MailBuild.build(CONFIGS.get(key));
|
||||
} catch (MessagingException e) {
|
||||
throw new MailException(e);
|
||||
}
|
||||
@ -43,7 +43,7 @@ public class MailFactory{
|
||||
*/
|
||||
public static MailClient createMailClient(Object key, Blacklist blacklist){
|
||||
try {
|
||||
return MailBuild.build(configs.get(key),blacklist);
|
||||
return MailBuild.build(CONFIGS.get(key),blacklist);
|
||||
} catch (MessagingException e) {
|
||||
throw new MailException(e);
|
||||
}
|
||||
@ -57,7 +57,7 @@ public class MailFactory{
|
||||
* @author :Wind
|
||||
*/
|
||||
public static void put(Object key, MailSmtpConfig config){
|
||||
configs.put(key,config);
|
||||
CONFIGS.put(key,config);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -45,6 +45,7 @@ public interface SmsBlend {
|
||||
/**
|
||||
* sendMessage
|
||||
* <p>说明:发送固定消息模板多模板参数短信
|
||||
*
|
||||
* @param phone 接收短信的手机号
|
||||
* @param messages 模板内容
|
||||
* @author :Wind
|
||||
@ -190,7 +191,7 @@ public interface SmsBlend {
|
||||
* @param phones 需要加入黑名单的手机号数组
|
||||
* @author :sh1yu
|
||||
*/
|
||||
default void batchJoinBlacklist(List<String > phones) {
|
||||
default void batchJoinBlacklist(List<String> phones) {
|
||||
}
|
||||
|
||||
/**
|
||||
@ -200,6 +201,6 @@ public interface SmsBlend {
|
||||
* @param phones 需要移除黑名单的手机号数组
|
||||
* @author :sh1yu
|
||||
*/
|
||||
default void batchRemovalFromBlacklist(List<String > phones) {
|
||||
default void batchRemovalFromBlacklist(List<String> phones) {
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,6 +22,9 @@ public interface CoreMethodProcessor extends SmsProcessor {
|
||||
return param;
|
||||
}
|
||||
if (NumberOfParasmeters.THREE == NumberOfParasmeters.getNumberOfParasmetersEnum(parameterCount)) {
|
||||
if (null == param[2]){
|
||||
param[2] = new LinkedHashMap<>();
|
||||
}
|
||||
sendMessageByTemplatePreProcess((String)param[0],(String) param[1],(LinkedHashMap<String, String>)param[2]);
|
||||
return param;
|
||||
}
|
||||
@ -32,6 +35,9 @@ public interface CoreMethodProcessor extends SmsProcessor {
|
||||
return param;
|
||||
}
|
||||
if (NumberOfParasmeters.THREE == NumberOfParasmeters.getNumberOfParasmetersEnum(parameterCount)) {
|
||||
if (null == param[2]){
|
||||
param[2] = new LinkedHashMap<>();
|
||||
}
|
||||
massTextingByTemplatePreProcess((List<String>)param[0],(String)param[1],(LinkedHashMap<String, String>)param[2]);
|
||||
return param;
|
||||
}
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
package org.dromara.sms4j.api.proxy;
|
||||
/**
|
||||
* 排序接口
|
||||
*
|
||||
* 排序接口 用户拦截器排序并进行有序执行,请注意,排序值应大于等于0。
|
||||
* 拦截器的排序依照从小到大的规则进行排序
|
||||
* @author sh1yu
|
||||
* @since 2023/10/27 13:03
|
||||
*/
|
||||
public interface Order {
|
||||
default public int getOrder(){
|
||||
|
||||
default int getOrder(){
|
||||
return 999;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
package org.dromara.sms4j.api.proxy;
|
||||
|
||||
|
||||
import org.dromara.sms4j.api.entity.SmsResponse;
|
||||
import org.dromara.sms4j.comm.exception.SmsBlendException;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
/**
|
||||
* 执行器接口
|
||||
@ -9,15 +12,40 @@ import java.lang.reflect.Method;
|
||||
* @since 2023/10/27 13:03
|
||||
*/
|
||||
public interface SmsProcessor extends Order {
|
||||
/**
|
||||
* preProcessor
|
||||
* <p> 前置拦截方法 此方法将在短信发送方法之前进行执行,但请勿在此方法中修改实例对象或者方法对象,否则可能会导致并发问题
|
||||
* @param method 方法对象
|
||||
* @param source 实例对象
|
||||
* @param param 参数列表
|
||||
* @author :Wind
|
||||
*/
|
||||
default Object[] preProcessor(Method method, Object source, Object[] param) {
|
||||
return null;
|
||||
}
|
||||
|
||||
default Object postProcessor(Object result, Object[] param) {
|
||||
/**
|
||||
* postProcessor
|
||||
* <p> 后置拦截方法 此方法执行在发送方法执行完毕之后获取到返回值之后
|
||||
* @param result 返回值
|
||||
* @param param 参数列表
|
||||
* @author :Wind
|
||||
*/
|
||||
default Object postProcessor(SmsResponse result, Object[] param) {
|
||||
return null;
|
||||
}
|
||||
|
||||
default Object exceptionHandleProcessor(Method method, Object source, Object[] param,Exception exception) {
|
||||
return null;
|
||||
/**
|
||||
* exceptionHandleProcessor
|
||||
* <p> 异常拦截执行器,在发送方法执行过程中发生异常,将会通过此方法进行反馈。
|
||||
* <p> 请注意,此方法一旦捕捉到相应异常并抛出新异常后,会中断后续执行器的进行 如果在所有的异常执行器中均没有抛出异常,则后续会进入后置方法执行器
|
||||
* @param method 方法对象
|
||||
* @param source 实例对象
|
||||
* @param param 参数列表
|
||||
* @param exception 异常
|
||||
* @author :Wind
|
||||
*/
|
||||
default void exceptionHandleProcessor(Method method, Object source, Object[] param, Exception exception) throws RuntimeException {
|
||||
throw new SmsBlendException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,27 @@
|
||||
package org.dromara.sms4j.api.universal;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 短信代理配置类
|
||||
*/
|
||||
@Data
|
||||
public class ProxyConfig implements Serializable {
|
||||
|
||||
/**
|
||||
* 是否启用代理 默认不启用
|
||||
*/
|
||||
private Boolean enable = false;
|
||||
|
||||
/**
|
||||
* 代理服务器地址
|
||||
*/
|
||||
private String host;
|
||||
|
||||
/**
|
||||
* 代理服务器端口
|
||||
*/
|
||||
private Integer port;
|
||||
}
|
||||
@ -22,4 +22,9 @@ public interface SupplierConfig {
|
||||
*/
|
||||
String getSupplier();
|
||||
|
||||
/**
|
||||
* 获取代理配置
|
||||
*
|
||||
*/
|
||||
ProxyConfig getProxy();
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
package org.dromara.sms4j.api.verify;
|
||||
|
||||
/**
|
||||
* PhoneVerify
|
||||
* <p> 实现校验手机号合规的接口
|
||||
* @author :Wind
|
||||
* 2024/3/28 14:15
|
||||
**/
|
||||
public interface PhoneVerify{
|
||||
|
||||
/**
|
||||
* verifyPhone
|
||||
* <p>用于校验手机号是否合理的规则方法,可以尝试重写此方法以改变规则,例如你可以选择使用正则表达式来进行
|
||||
* 一系列更加精准和严格的校验,此校验优先级最高,会在黑名单和其他拦截之前执行。
|
||||
* 当此校验触发时候,将会直接以异常形式进行抛出,并终止后续向厂商请求的动作,故而不会有返回值。
|
||||
* <p>当校验手机号合格时应返回 true 否则返回 false
|
||||
* @param phone 被校验的手机号
|
||||
* @author :Wind
|
||||
*/
|
||||
default boolean verifyPhone(String phone){
|
||||
return phone.length() == 11;
|
||||
}
|
||||
}
|
||||
@ -11,27 +11,51 @@ public abstract class Constant {
|
||||
/**
|
||||
* 项目版本号
|
||||
*/
|
||||
public static final String VERSION = "V 3.0.1";
|
||||
public static final String VERSION = "V 3.3.5";
|
||||
|
||||
/**
|
||||
* 用于格式化鉴权头域,给"Authorization"参数赋值
|
||||
*/
|
||||
public static final String HUAWEI_AUTH_HEADER_VALUE = "WSSE realm=\"SDP\",profile=\"UsernameToken\",type=\"Appkey\"";
|
||||
|
||||
/**
|
||||
* 用于格式化鉴权头域,给"X-WSSE"参数赋值
|
||||
*/
|
||||
public static final String HUAWEI_WSSE_HEADER_FORMAT = "UsernameToken Username=\"%s\",PasswordDigest=\"%s\",Nonce=\"%s\",Created=\"%s\"";
|
||||
|
||||
/**
|
||||
* 华为云国内短信访问URI
|
||||
*/
|
||||
public static final String HUAWEI_REQUEST_URL = "/sms/batchSendSms/v1";
|
||||
|
||||
/**
|
||||
* Content-Type
|
||||
*/
|
||||
public static final String FROM_URLENCODED = "application/x-www-form-urlencoded";
|
||||
public static final String CONTENT_TYPE = "Content-Type";
|
||||
|
||||
public static final String ACCEPT = "application/json";
|
||||
/**
|
||||
* Authorization
|
||||
*/
|
||||
public static final String AUTHORIZATION = "Authorization";
|
||||
|
||||
/**
|
||||
* Accept
|
||||
*/
|
||||
public static final String ACCEPT = "Accept";
|
||||
|
||||
/**
|
||||
* x-www-form-urlencoded
|
||||
*/
|
||||
public static final String APPLICATION_FROM_URLENCODED = "application/x-www-form-urlencoded";
|
||||
|
||||
/**
|
||||
* application/json
|
||||
*/
|
||||
public static final String APPLICATION_JSON = "application/json";
|
||||
|
||||
/**
|
||||
* application/json; charset=utf-8
|
||||
*/
|
||||
public static final String APPLICATION_JSON_UTF8 = "application/json; charset=utf-8";
|
||||
|
||||
/**
|
||||
@ -42,7 +66,7 @@ public abstract class Constant {
|
||||
/**
|
||||
* 云片短信国内短信请求地址
|
||||
*/
|
||||
public static final String YUNPIAN_URL = "https://sms.yunpian.com/v2";
|
||||
public static final String YUNPIAN_URL = Constant.HTTPS_PREFIX + "sms.yunpian.com/v2";
|
||||
|
||||
/**
|
||||
* https请求前缀
|
||||
|
||||
@ -10,7 +10,7 @@ public abstract class SupplierConstant {
|
||||
*/
|
||||
public static final String ALIBABA = "alibaba";
|
||||
/**
|
||||
* 容连云
|
||||
* 容联云
|
||||
*/
|
||||
public static final String CLOOPEN = "cloopen";
|
||||
/**
|
||||
@ -49,16 +49,52 @@ public abstract class SupplierConstant {
|
||||
* 助通
|
||||
*/
|
||||
public static final String ZHUTONG = "zhutong";
|
||||
|
||||
/**
|
||||
* 联麓
|
||||
*/
|
||||
public static final String LIANLU = "lianlu";
|
||||
|
||||
/**
|
||||
* 鼎众
|
||||
*/
|
||||
public static final String DINGZHONG = "dingzhong";
|
||||
/**
|
||||
* 七牛云
|
||||
*/
|
||||
public static final String QINIU = "qiniu";
|
||||
|
||||
/**
|
||||
* 创蓝
|
||||
*/
|
||||
public static final String CHUANGLAN = "chuanglan";
|
||||
/**
|
||||
* 极光
|
||||
*/
|
||||
public static final String JIGUANG = "jiguang";
|
||||
/**
|
||||
* 布丁云V2
|
||||
*/
|
||||
public static final String BUDING_V2 = "buding_v2";
|
||||
/**
|
||||
* 中国移动 云MAS
|
||||
*/
|
||||
public static final String MAS = "mas";
|
||||
/**
|
||||
* 百度云 sms
|
||||
*/
|
||||
public static final String BAIDU = "baidu";
|
||||
/**
|
||||
* 螺丝帽 sms
|
||||
*/
|
||||
public static final String LUO_SI_MAO = "luosimao";
|
||||
/**
|
||||
* SUBMAIL sms
|
||||
*/
|
||||
public static final String MY_SUBMAIL = "mysubmail";
|
||||
/**
|
||||
* danmi sms
|
||||
*/
|
||||
public static final String DAN_MI = "danmi";
|
||||
/**
|
||||
* 联通一信通 sms
|
||||
*/
|
||||
public static final String YIXINTONG = "yixintong";
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package org.dromara.sms4j.comm.enumerate;
|
||||
package org.dromara.sms4j.comm.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@ -22,4 +22,9 @@ public class SmsBlendException extends RuntimeException{
|
||||
this.code = code;
|
||||
this.requestId = requestId;
|
||||
}
|
||||
|
||||
public SmsBlendException(Exception e){
|
||||
super(e);
|
||||
this.message = e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,11 @@
|
||||
package org.dromara.sms4j.comm.utils;
|
||||
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import cn.hutool.http.HttpResponse;
|
||||
import cn.hutool.http.Method;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import org.dromara.sms4j.comm.exception.SmsBlendException;
|
||||
@ -12,15 +14,108 @@ import java.util.Map;
|
||||
|
||||
public class SmsHttpUtils {
|
||||
|
||||
/**
|
||||
* 是否启用代理 默认不启用
|
||||
*/
|
||||
private final Boolean enable;
|
||||
|
||||
/**
|
||||
* 代理服务器地址
|
||||
*/
|
||||
private final String host;
|
||||
|
||||
/**
|
||||
* 代理服务器端口
|
||||
*/
|
||||
private final Integer port;
|
||||
|
||||
// 无代理单例(饿汉式加载)
|
||||
private static final SmsHttpUtils NON_PROXY_INSTANCE = new SmsHttpUtils();
|
||||
|
||||
// 代理单例(双重校验锁延迟加载)
|
||||
private static volatile SmsHttpUtils PROXY_INSTANCE;
|
||||
|
||||
// 无代理构造方法
|
||||
private SmsHttpUtils() {
|
||||
this.enable = false;
|
||||
this.host = null;
|
||||
this.port = null;
|
||||
}
|
||||
|
||||
private static class SmsHttpHolder {
|
||||
private static final SmsHttpUtils INSTANCE = new SmsHttpUtils();
|
||||
// 代理构造方法
|
||||
private SmsHttpUtils(String host, Integer port) {
|
||||
this.enable = true;
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取无代理单例
|
||||
*/
|
||||
public static SmsHttpUtils instance() {
|
||||
return SmsHttpHolder.INSTANCE;
|
||||
return NON_PROXY_INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取代理单例(线程安全 + 参数校验)
|
||||
*/
|
||||
public static SmsHttpUtils instance(String host, Integer port) {
|
||||
if (PROXY_INSTANCE == null) {
|
||||
synchronized (SmsHttpUtils.class) {
|
||||
if (PROXY_INSTANCE == null) {
|
||||
validateProxyParams(host, port);
|
||||
PROXY_INSTANCE = new SmsHttpUtils(host, port);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 二次调用时校验参数一致性
|
||||
if (!PROXY_INSTANCE.host.equals(host) || !PROXY_INSTANCE.port.equals(port)) {
|
||||
throw new IllegalStateException("Proxy parameters cannot be modified after initialization");
|
||||
}
|
||||
}
|
||||
return PROXY_INSTANCE;
|
||||
}
|
||||
|
||||
// 代理参数校验
|
||||
private static void validateProxyParams(String host, Integer port) {
|
||||
if (StrUtil.isBlank(host) || port == null || port <= 0) {
|
||||
throw new IllegalArgumentException("Invalid proxy host or port");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置请求 是否走代理
|
||||
* @param url 请求地址
|
||||
* @return HttpRequest
|
||||
*/
|
||||
private HttpRequest request(String url){
|
||||
HttpRequest request = HttpRequest.of(url);
|
||||
if (enable){
|
||||
request.setHttpProxy(host, port);
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造post请求
|
||||
* @param url 请求地址
|
||||
* @return HttpRequest
|
||||
*/
|
||||
private HttpRequest post(String url){
|
||||
HttpRequest post = request(url);
|
||||
post.setMethod(Method.POST);
|
||||
return post;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造get请求
|
||||
* @param url 请求地址
|
||||
* @return HttpRequest
|
||||
*/
|
||||
private HttpRequest get(String url){
|
||||
HttpRequest get = request(url);
|
||||
get.setMethod(Method.GET);
|
||||
return get;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -32,7 +127,7 @@ public class SmsHttpUtils {
|
||||
* @return 返回体
|
||||
*/
|
||||
public JSONObject postJson(String url, Map<String, String> headers, String body) {
|
||||
try (HttpResponse response = HttpRequest.post(url)
|
||||
try (HttpResponse response = post(url)
|
||||
.addHeaders(headers)
|
||||
.body(body)
|
||||
.execute()) {
|
||||
@ -63,7 +158,7 @@ public class SmsHttpUtils {
|
||||
* @return 返回体
|
||||
*/
|
||||
public JSONObject postFrom(String url, Map<String, String> headers, Map<String, Object> body) {
|
||||
try (HttpResponse response = HttpRequest.post(url)
|
||||
try (HttpResponse response = post(url)
|
||||
.addHeaders(headers)
|
||||
.form(body)
|
||||
.execute()) {
|
||||
@ -73,6 +168,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 = 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传输
|
||||
@ -84,7 +200,7 @@ public class SmsHttpUtils {
|
||||
*/
|
||||
public JSONObject postUrl(String url, Map<String, String> headers, Map<String, Object> params) {
|
||||
String urlWithParams = url + "?" + URLUtil.buildQuery(params, null);
|
||||
try (HttpResponse response = HttpRequest.post(urlWithParams)
|
||||
try (HttpResponse response = post(urlWithParams)
|
||||
.addHeaders(headers)
|
||||
.execute()) {
|
||||
return JSONUtil.parseObj(response.body());
|
||||
@ -93,6 +209,37 @@ public class SmsHttpUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送get
|
||||
*
|
||||
* @param url 请求地址
|
||||
* @return 返回体
|
||||
*/
|
||||
public JSONObject getBasic(String url, String username, String password) {
|
||||
try (HttpResponse response = 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 = get(url)
|
||||
.execute()) {
|
||||
return JSONUtil.parseObj(response.body());
|
||||
} catch (Exception e) {
|
||||
throw new SmsBlendException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 线程睡眠
|
||||
*
|
||||
|
||||
@ -2,15 +2,22 @@ package org.dromara.sms4j.comm.utils;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* @author wind
|
||||
@ -113,18 +120,38 @@ public class SmsUtils {
|
||||
* @param list 要转换的list
|
||||
* @author :Wind
|
||||
*/
|
||||
public static String listToString(List<String> list) {
|
||||
return CollUtil.join(list, ",");
|
||||
public static String joinComma(List<String> 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 结果字符串
|
||||
*/
|
||||
public static String arrayToString(List<String> list) {
|
||||
return CollUtil.join(list, ",", str -> StrUtil.addPrefixIfNot(str, "+86"));
|
||||
public static String addCodePrefixIfNot(List<String> phones) {
|
||||
return CollUtil.join(phones, StrUtil.COMMA, SmsUtils::addCodePrefixIfNot);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将手机号码 添加+86电话区号前缀
|
||||
*
|
||||
* @param phone 手机号码
|
||||
* @return 结果字符串
|
||||
*/
|
||||
public static String addCodePrefixIfNot(String phone) {
|
||||
return StrUtil.addPrefixIfNot(phone, "+86");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -133,10 +160,10 @@ public class SmsUtils {
|
||||
* @param list 集合
|
||||
* @return 结果字符串
|
||||
*/
|
||||
public static String[] listToArray(List<String> list) {
|
||||
public static String[] addCodePrefixIfNotToArray(List<String> list) {
|
||||
List<String> toStr = new ArrayList<>();
|
||||
for (String s : list) {
|
||||
toStr.add(StrUtil.addPrefixIfNot(s, "+86"));
|
||||
toStr.add(addCodePrefixIfNot(s));
|
||||
}
|
||||
return toStr.toArray(new String[list.size()]);
|
||||
}
|
||||
@ -144,20 +171,20 @@ public class SmsUtils {
|
||||
/**
|
||||
* 将Map中所有key的分隔符转换为新的分隔符
|
||||
* @param map map对象
|
||||
* @param seperator 旧分隔符
|
||||
* @param newSeperator 新分隔符
|
||||
* @param separator 旧分隔符
|
||||
* @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)) {
|
||||
return;
|
||||
}
|
||||
List<String> keySet = new ArrayList<>(map.keySet());
|
||||
for(String key : keySet) {
|
||||
if(StrUtil.isEmpty(key) || !key.contains(seperator)) {
|
||||
if(StrUtil.isEmpty(key) || !key.contains(separator)) {
|
||||
continue;
|
||||
}
|
||||
String value = String.valueOf(map.get(key));
|
||||
String newKey = key.replaceAll(seperator, newSeperator);
|
||||
String newKey = key.replaceAll(separator, newSeparator);
|
||||
map.putIfAbsent(newKey, value);
|
||||
map.remove(key);
|
||||
}
|
||||
@ -172,4 +199,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[map.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将所有提交的参数升序排列,并排除部分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();
|
||||
}
|
||||
}
|
||||
@ -20,7 +20,7 @@ public class SmsLoad {
|
||||
// 服务器列表,每个服务器有一个权重和当前权重
|
||||
private final List<LoadServer> LoadServers = new ArrayList<>();
|
||||
|
||||
private static final SmsLoad smsLoad = new SmsLoad();
|
||||
private static final SmsLoad SMS_LOAD = new SmsLoad();
|
||||
|
||||
private SmsLoad() {
|
||||
}
|
||||
@ -100,15 +100,15 @@ public class SmsLoad {
|
||||
public static void starConfig(SmsBlend smsBlend, SupplierConfig supplierConfig) {
|
||||
Map<String, Object> supplierConfigMap = BeanUtil.beanToMap(supplierConfig);
|
||||
Object weight = supplierConfigMap.getOrDefault("weight", 1);
|
||||
smsLoad.addLoadServer(smsBlend, Integer.parseInt(weight.toString()));
|
||||
SMS_LOAD.addLoadServer(smsBlend, Integer.parseInt(weight.toString()));
|
||||
}
|
||||
|
||||
public static void starConfig(SmsBlend smsBlend,Integer weight) {
|
||||
smsLoad.addLoadServer(smsBlend,weight);
|
||||
SMS_LOAD.addLoadServer(smsBlend,weight);
|
||||
}
|
||||
|
||||
public static SmsLoad getBeanLoad() {
|
||||
return smsLoad;
|
||||
return SMS_LOAD;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -2,11 +2,12 @@ package org.dromara.sms4j.core.proxy;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.sms4j.api.SmsBlend;
|
||||
import org.dromara.sms4j.api.entity.SmsResponse;
|
||||
import org.dromara.sms4j.api.proxy.SmsProcessor;
|
||||
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Objects;
|
||||
|
||||
|
||||
/**
|
||||
@ -18,12 +19,10 @@ import java.util.LinkedList;
|
||||
@Slf4j
|
||||
public class SmsInvocationHandler implements InvocationHandler {
|
||||
private final SmsBlend smsBlend;
|
||||
private final LinkedList<SmsProcessor> processors;
|
||||
|
||||
|
||||
public SmsInvocationHandler(SmsBlend smsBlend, LinkedList<SmsProcessor> processors) {
|
||||
public SmsInvocationHandler(SmsBlend smsBlend) {
|
||||
this.smsBlend = smsBlend;
|
||||
this.processors = processors;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -38,30 +37,40 @@ public class SmsInvocationHandler implements InvocationHandler {
|
||||
doErrorHandleProcess(smsBlend, method, objects,e);
|
||||
}
|
||||
//后置执行器
|
||||
doPostrocess(smsBlend, method, objects, result);
|
||||
return result;
|
||||
return doPostrocess(smsBlend, method, objects, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 前置执行器
|
||||
* */
|
||||
public Object[] doPreProcess(Object o, Method method, Object[] objects) {
|
||||
for (SmsProcessor processor : processors) {
|
||||
for (SmsProcessor processor : SmsProxyFactory.getProcessors()) {
|
||||
objects = processor.preProcessor(method, o, objects);
|
||||
}
|
||||
return objects;
|
||||
}
|
||||
|
||||
public void doErrorHandleProcess(Object o, Method method, Object[] objects,Exception e) {
|
||||
for (SmsProcessor processor : processors) {
|
||||
/**
|
||||
* 异常执行器
|
||||
* */
|
||||
public void doErrorHandleProcess(Object o, Method method, Object[] objects,Exception e) throws RuntimeException{
|
||||
for (SmsProcessor processor : SmsProxyFactory.getProcessors()) {
|
||||
processor.exceptionHandleProcessor(method, o, objects,e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 后置执行器
|
||||
* */
|
||||
public Object doPostrocess(Object o, Method method, Object[] objects, Object result) {
|
||||
for (SmsProcessor processor : processors) {
|
||||
Object overrideResult = processor.postProcessor(result, objects);
|
||||
for (SmsProcessor processor : SmsProxyFactory.getProcessors()) {
|
||||
if (Objects.nonNull(result) && result instanceof SmsResponse){
|
||||
Object overrideResult = processor.postProcessor((SmsResponse)result, objects);
|
||||
if (overrideResult != null) {
|
||||
return overrideResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,6 +10,8 @@ import org.dromara.sms4j.api.proxy.SuppotFilter;
|
||||
import org.dromara.sms4j.api.proxy.aware.SmsBlendConfigAware;
|
||||
import org.dromara.sms4j.api.proxy.aware.SmsConfigAware;
|
||||
import org.dromara.sms4j.api.proxy.aware.SmsDaoAware;
|
||||
import org.dromara.sms4j.api.verify.PhoneVerify;
|
||||
import org.dromara.sms4j.core.proxy.processor.CoreMethodParamValidateProcessor;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
@ -17,32 +19,65 @@ import java.lang.reflect.Proxy;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* SmsBlend代理工厂
|
||||
*
|
||||
* 代理工厂
|
||||
* 可用于增加和移除拦截器
|
||||
* @author sh1yu
|
||||
* @since 2023/10/27 13:03
|
||||
*/
|
||||
@Slf4j
|
||||
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) {
|
||||
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));
|
||||
// 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));
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加拦截器
|
||||
*/
|
||||
public static void addProcessor(SmsProcessor processor) {
|
||||
public static void addPreProcessor(SmsProcessor processor) {
|
||||
//校验拦截器是否正确
|
||||
processorValidate(processor);
|
||||
awareTransfer(processor);
|
||||
processors.add(processor);
|
||||
processors.sort(Comparator.comparingInt(Order::getOrder));
|
||||
PROCESSORS.add(processor);
|
||||
PROCESSORS.sort(Comparator.comparingInt(Order::getOrder));
|
||||
}
|
||||
|
||||
/**
|
||||
* removeProcessor
|
||||
* <p> 移除拦截器
|
||||
* @param processor 拦截器对象
|
||||
* @author :Wind
|
||||
*/
|
||||
public static void removePreProcessor(SmsProcessor processor) {
|
||||
PROCESSORS.remove(processor);
|
||||
}
|
||||
|
||||
/**
|
||||
* getProcessors
|
||||
* <p> 获取全部拦截器
|
||||
* @author :Wind
|
||||
*/
|
||||
public static LinkedList<SmsProcessor> getProcessors() {
|
||||
return PROCESSORS;
|
||||
}
|
||||
|
||||
/**
|
||||
* setPhoneProcessor
|
||||
* <p> 添加手机号验证器,手机号验证器只且只能存在一个,如果重复置入则会替换先前的验证器。
|
||||
* 如果在验证器之后还需进行额外操作,请参考使用前置拦截器进行处理
|
||||
* @param phoneVerify 手机号验证器
|
||||
* @author :Wind
|
||||
*/
|
||||
public static void setPhoneProcessor(PhoneVerify phoneVerify) {
|
||||
PROCESSORS.forEach(processor -> {
|
||||
if (processor instanceof CoreMethodParamValidateProcessor){
|
||||
((CoreMethodParamValidateProcessor) processor).setPhoneVerify(phoneVerify);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
@ -101,7 +136,7 @@ public abstract class SmsProxyFactory {
|
||||
Class<?> clazz = Class.forName(className);
|
||||
Method getSmsDao = clazz.getMethod("getSmsDao", null);
|
||||
SmsDao smsDao = (SmsDao) getSmsDao.invoke(null, null);
|
||||
log.info("{}:加载SmsDao成功,使用{}", frameworkName,smsDao.getClass().getName());
|
||||
log.debug("{}:加载SmsDao成功,使用{}", frameworkName,smsDao.getClass().getName());
|
||||
return smsDao;
|
||||
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
|
||||
log.debug("{}:尝试其他框架加载......", frameworkName);
|
||||
|
||||
@ -55,7 +55,7 @@ public class BlackListProcessor implements CoreMethodProcessor, SmsDaoAware {
|
||||
}
|
||||
for (String phone : phones) {
|
||||
if (blackList.stream().anyMatch(black -> black.replace("-","").equals(phone))) {
|
||||
throw new SmsBlendException("The phone:", phone + " hit global blacklist!");
|
||||
throw new SmsBlendException(String.format("The phone: %s hit global blacklist!", phone));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
package org.dromara.sms4j.core.proxy.processor;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.sms4j.api.proxy.CoreMethodProcessor;
|
||||
import org.dromara.sms4j.api.verify.PhoneVerify;
|
||||
import org.dromara.sms4j.comm.exception.SmsBlendException;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
@ -16,11 +18,34 @@ import java.util.Objects;
|
||||
* @author sh1yu
|
||||
* @since 2023/10/27 13:03
|
||||
*/
|
||||
@Setter
|
||||
@Slf4j
|
||||
public class CoreMethodParamValidateProcessor implements CoreMethodProcessor {
|
||||
|
||||
/**
|
||||
* -- SETTER --
|
||||
* 设置 phoneVerify
|
||||
*/
|
||||
private PhoneVerify phoneVerify;
|
||||
|
||||
public CoreMethodParamValidateProcessor(PhoneVerify phoneVerify) {
|
||||
this.phoneVerify = phoneVerify;
|
||||
}
|
||||
|
||||
/**
|
||||
* setter
|
||||
* <p> 用于设置手机号验证器,可以在通过重写验证器规则来实现自己的验证器逻辑
|
||||
* <p> 默认验证规则仅仅验证手机号是否为空和手机号是否为11位
|
||||
* @param phoneVerify 手机号验证器
|
||||
* @author :Wind
|
||||
*/
|
||||
public void setPhoneVerify(PhoneVerify phoneVerify) {
|
||||
this.phoneVerify = phoneVerify;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return -1;
|
||||
return -100;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -63,6 +88,9 @@ public class CoreMethodParamValidateProcessor implements CoreMethodProcessor {
|
||||
if (StrUtil.isBlank(phone)) {
|
||||
throw new SmsBlendException("cant send message to null!");
|
||||
}
|
||||
if (phoneVerify != null && !phoneVerify.verifyPhone(phone)){
|
||||
throw new SmsBlendException("The mobile phone number format is invalid!");
|
||||
}
|
||||
}
|
||||
|
||||
public void validatePhones(List<String> phones) {
|
||||
@ -71,10 +99,11 @@ public class CoreMethodParamValidateProcessor implements CoreMethodProcessor {
|
||||
}
|
||||
for (String phone : phones) {
|
||||
if (StrUtil.isNotBlank(phone)) {
|
||||
return;
|
||||
if (phoneVerify != null && !phoneVerify.verifyPhone(phone)){
|
||||
throw new SmsBlendException("The mobile phone number format is invalid!");
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new SmsBlendException("cant send message to null!");
|
||||
}
|
||||
|
||||
public void validateMessages(String templateId, LinkedHashMap<String, String> messages) {
|
||||
|
||||
@ -10,6 +10,8 @@ import org.dromara.sms4j.comm.utils.SmsUtils;
|
||||
import org.dromara.sms4j.provider.config.SmsConfig;
|
||||
import org.dromara.sms4j.provider.factory.BeanFactory;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
@ -26,7 +28,6 @@ import java.util.Objects;
|
||||
@Slf4j
|
||||
public class RestrictedProcessor implements CoreMethodProcessor, SmsDaoAware {
|
||||
static Long minTimer = 60 * 1000L;
|
||||
static Long accTimer = 24 * 60 * 60 * 1000L;
|
||||
private static final String REDIS_KEY = "sms:restricted:";
|
||||
|
||||
/**
|
||||
@ -59,44 +60,69 @@ public class RestrictedProcessor implements CoreMethodProcessor, SmsDaoAware {
|
||||
doRestricted(phones);
|
||||
}
|
||||
|
||||
private long calculateExpiryTime() {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
LocalDateTime tomorrowMidnight = now.toLocalDate().plusDays(1).atStartOfDay();
|
||||
Duration duration = Duration.between(now, tomorrowMidnight);
|
||||
return duration.getSeconds();
|
||||
}
|
||||
|
||||
public void doRestricted(List<String> phones) {
|
||||
if (Objects.isNull(smsDao)) {
|
||||
throw new SmsBlendException("The smsDao tool could not be found");
|
||||
}
|
||||
SmsConfig config = BeanFactory.getSmsConfig();
|
||||
// 如果未开始限制则不做处理
|
||||
if (!config.getRestricted()){
|
||||
return;
|
||||
}
|
||||
// 每日最大发送量
|
||||
Integer accountMax = config.getAccountMax();
|
||||
// 每分钟最大发送量
|
||||
Integer minuteMax = config.getMinuteMax();
|
||||
// 配置了每日最大发送量
|
||||
boolean dailyMaxLimitExists = SmsUtils.isNotEmpty(accountMax);
|
||||
// 配置了每分钟最大发送量
|
||||
boolean perMinuteLimitExists = SmsUtils.isNotEmpty(minuteMax);
|
||||
// 如果未开启限制或未配置任何限制发送量,不做处理
|
||||
boolean isNoProcessing = !config.getRestricted() || (!dailyMaxLimitExists && !perMinuteLimitExists);
|
||||
if (isNoProcessing) {
|
||||
return;
|
||||
}
|
||||
for (String phone : phones) {
|
||||
// 分钟发送量缓存key
|
||||
String minuteMaxKey = REDIS_KEY + phone;
|
||||
// 天发送量缓存key
|
||||
String accountMaxKey = minuteMaxKey.concat("max");
|
||||
// 是否配置了每日限制
|
||||
if (SmsUtils.isNotEmpty(accountMax)) {
|
||||
Integer i = (Integer) smsDao.get(REDIS_KEY + phone + "max");
|
||||
if (SmsUtils.isEmpty(i)) {
|
||||
smsDao.set(REDIS_KEY + phone + "max", 1, accTimer / 1000);
|
||||
} else if (i >= accountMax) {
|
||||
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);
|
||||
if (dailyMaxLimitExists) {
|
||||
Integer dailyCount = (Integer) smsDao.get(accountMaxKey);
|
||||
if (SmsUtils.isEmpty(dailyCount)) {
|
||||
smsDao.set(accountMaxKey, 1, calculateExpiryTime());
|
||||
} else if (dailyCount >= accountMax) {
|
||||
log.info("The phone: {} number of short messages reached the maximum today", phone);
|
||||
throw new SmsBlendException(String.format("The phone: %s number of short messages reached the maximum today", phone));
|
||||
} else {
|
||||
smsDao.set(REDIS_KEY + phone + "max", i + 1, accTimer / 1000);
|
||||
smsDao.set(accountMaxKey, dailyCount + 1, calculateExpiryTime());
|
||||
}
|
||||
}
|
||||
// 是否配置了每分钟最大限制
|
||||
if (SmsUtils.isNotEmpty(minuteMax)) {
|
||||
Integer o = (Integer) smsDao.get(REDIS_KEY + phone);
|
||||
if (SmsUtils.isNotEmpty(o)) {
|
||||
if (o < minuteMax) {
|
||||
smsDao.set(REDIS_KEY + phone, o + 1, minTimer / 1000);
|
||||
if (perMinuteLimitExists) {
|
||||
Integer minuteCount = (Integer) smsDao.get(REDIS_KEY + phone);
|
||||
if (SmsUtils.isNotEmpty(minuteCount)) {
|
||||
if (minuteCount < minuteMax) {
|
||||
smsDao.set(minuteMaxKey, minuteCount + 1, minTimer / 1000);
|
||||
} else {
|
||||
log.info("The phone: {},number of short messages reached the maximum today", phone);
|
||||
throw new SmsBlendException("The phone: {} Text messages are sent too often!", phone);
|
||||
//如果能走到这里且存在每日限制,说明每日限制已经计数,这里将之前的计数减一次
|
||||
if (dailyMaxLimitExists) {
|
||||
Integer dailyCount = (Integer) smsDao.get(accountMaxKey);
|
||||
if (dailyCount > 1) {
|
||||
smsDao.set(accountMaxKey, dailyCount - 1, calculateExpiryTime());
|
||||
} else {
|
||||
smsDao.remove(accountMaxKey);
|
||||
}
|
||||
}
|
||||
log.info("The phone: {} text messages are sent too often!", phone);
|
||||
throw new SmsBlendException(String.format("The phone: %s text messages are sent too often!", phone));
|
||||
}
|
||||
} else {
|
||||
smsDao.set(REDIS_KEY + phone, 1, minTimer / 1000);
|
||||
smsDao.set(minuteMaxKey, 1, minTimer / 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,72 +0,0 @@
|
||||
package org.dromara.sms4j.core.proxy.processor;
|
||||
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.sms4j.api.SmsBlend;
|
||||
import org.dromara.sms4j.api.dao.SmsDao;
|
||||
import org.dromara.sms4j.api.proxy.SmsProcessor;
|
||||
import org.dromara.sms4j.api.proxy.aware.SmsBlendConfigAware;
|
||||
import org.dromara.sms4j.api.proxy.aware.SmsDaoAware;
|
||||
import org.dromara.sms4j.comm.exception.SmsBlendException;
|
||||
import org.dromara.sms4j.comm.utils.SmsUtils;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* 短信发送渠道级上限前置拦截执行器
|
||||
*
|
||||
* @author sh1yu
|
||||
* @since 2023/10/27 13:03
|
||||
*/
|
||||
@Setter
|
||||
@Slf4j
|
||||
public class SingleBlendRestrictedProcessor implements SmsProcessor, SmsDaoAware, SmsBlendConfigAware {
|
||||
|
||||
private static final String REDIS_KEY = "sms:restricted:";
|
||||
|
||||
/**
|
||||
* 缓存实例
|
||||
*/
|
||||
private SmsDao smsDao;
|
||||
|
||||
Map smsBlendsConfig;
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] preProcessor(Method method, Object source, Object[] param) {
|
||||
String name = method.getName();
|
||||
if (!"sendMessage".equals(name) && !"massText".equals(name)) {
|
||||
return param;
|
||||
}
|
||||
SmsBlend smsBlend = (SmsBlend) source;
|
||||
String configId = smsBlend.getConfigId();
|
||||
Map targetConfig = (Map) smsBlendsConfig.get(configId);
|
||||
Object maximumObj = targetConfig.get("maximum");
|
||||
if (SmsUtils.isEmpty(maximumObj)) {
|
||||
return param;
|
||||
}
|
||||
int maximum = 0;
|
||||
try{
|
||||
maximum = Integer.parseInt(String.valueOf(maximumObj)) ;
|
||||
}catch (Exception e){
|
||||
log.error("获取厂商级发送上限参数错误!请检查!");
|
||||
throw new IllegalArgumentException("获取厂商级发送上限参数错误");
|
||||
}
|
||||
Integer i = (Integer) smsDao.get(REDIS_KEY + configId + "maximum");
|
||||
if (SmsUtils.isEmpty(i)) {
|
||||
smsDao.set(REDIS_KEY + configId + "maximum", 1);
|
||||
} else if (i >= maximum) {
|
||||
log.info("The channel: {},messages reached the maximum", configId);
|
||||
throw new SmsBlendException("The channel: {},messages reached the maximum", configId);
|
||||
} else {
|
||||
smsDao.set(REDIS_KEY + configId + "maximum", i + 1);
|
||||
}
|
||||
return param;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
/**
|
||||
* <p> 邮件插件api模块
|
||||
* @author :Wind
|
||||
* 2024/10/23 10:58
|
||||
**/
|
||||
package org.dromara.email.jakarta.api;
|
||||
@ -3,6 +3,7 @@ package org.dromara.email.jakarta.comm.entity;
|
||||
import lombok.Getter;
|
||||
import org.dromara.email.jakarta.comm.utils.ReflectUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
@ -27,6 +28,12 @@ public class MailMessage {
|
||||
/** html模板文件的输入流,可来自任意可读取位置*/
|
||||
private InputStream htmlInputStream;
|
||||
|
||||
/** html内容,可以存在模板变量*/
|
||||
private String htmlContent;
|
||||
|
||||
/** html 模板文件的File对象*/
|
||||
private File htmlFile;
|
||||
|
||||
/** html 模板参数*/
|
||||
private Map<String,String> htmlValues;
|
||||
|
||||
@ -141,6 +148,18 @@ public class MailMessage {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** html模板文件的File对象*/
|
||||
public MailsBuilder html(File htmlFile){
|
||||
mailMessage.htmlFile = htmlFile;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** html内容直接输入*/
|
||||
public MailsBuilder htmlContent(String htmlContent){
|
||||
mailMessage.htmlContent = htmlContent;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** html 模板参数*/
|
||||
public MailsBuilder htmlValues(String key, String value){
|
||||
if (mailMessage.htmlValues == null){
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
/**
|
||||
* <p> 邮件插件通用模块
|
||||
* @author :Wind
|
||||
* 2024/10/23 10:58
|
||||
**/
|
||||
package org.dromara.email.jakarta.comm;
|
||||
@ -32,8 +32,8 @@ public final class HtmlUtil {
|
||||
* @param name 模板文件名
|
||||
* @author :Wind
|
||||
*/
|
||||
public static List<String> readHtml(String name) throws MailException {
|
||||
try (InputStream is = HtmlUtil.class.getResourceAsStream("/template/" + name)) {
|
||||
public static List<String> readHtml(String name,Class<?> clazz) throws MailException {
|
||||
try (InputStream is = clazz.getResourceAsStream("/template/" + name)) {
|
||||
return readHtml(is);
|
||||
} catch (IOException e) {
|
||||
throw new MailException(e);
|
||||
@ -75,6 +75,12 @@ public final class HtmlUtil {
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new MailException(e);
|
||||
} finally {
|
||||
try {
|
||||
inputStream.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
@ -17,7 +17,7 @@ import java.util.Map;
|
||||
* 2023/6/8 22:35
|
||||
**/
|
||||
public class MailFactory{
|
||||
private static final Map<Object,MailSmtpConfig> configs = new HashMap<>();
|
||||
private static final Map<Object,MailSmtpConfig> CONFIGS = new HashMap<>();
|
||||
|
||||
/**
|
||||
* createMailClient
|
||||
@ -27,7 +27,7 @@ public class MailFactory{
|
||||
*/
|
||||
public static MailClient createMailClient(Object key){
|
||||
try {
|
||||
return MailBuild.build(configs.get(key));
|
||||
return MailBuild.build(CONFIGS.get(key));
|
||||
} catch (MessagingException e) {
|
||||
throw new MailException(e);
|
||||
}
|
||||
@ -43,7 +43,7 @@ public class MailFactory{
|
||||
*/
|
||||
public static MailClient createMailClient(Object key, Blacklist blacklist){
|
||||
try {
|
||||
return MailBuild.build(configs.get(key),blacklist);
|
||||
return MailBuild.build(CONFIGS.get(key),blacklist);
|
||||
} catch (MessagingException e) {
|
||||
throw new MailException(e);
|
||||
}
|
||||
@ -57,7 +57,7 @@ public class MailFactory{
|
||||
* @author :Wind
|
||||
*/
|
||||
public static void put(Object key, MailSmtpConfig config){
|
||||
configs.put(key,config);
|
||||
CONFIGS.put(key,config);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -26,6 +26,7 @@ import org.dromara.email.jakarta.comm.utils.ZipUtils;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@ -50,8 +51,14 @@ public class MailService implements MailClient {
|
||||
if (mailMessage.getHtmlInputStream() != null) {
|
||||
html = HtmlUtil.readHtml(mailMessage.getHtmlInputStream());
|
||||
}
|
||||
if (StrUtil.isNotBlank(mailMessage.getHtmlPath())){
|
||||
html = HtmlUtil.readHtml(mailMessage.getHtmlPath());
|
||||
if (StrUtil.isNotBlank(mailMessage.getHtmlPath())) {
|
||||
html = HtmlUtil.readHtml(mailMessage.getHtmlPath(), MailService.class);
|
||||
}
|
||||
if (mailMessage.getHtmlFile() != null) {
|
||||
html = HtmlUtil.readHtml(mailMessage.getHtmlFile());
|
||||
}
|
||||
if (StrUtil.isNotBlank(mailMessage.getHtmlContent())) {
|
||||
html = Arrays.asList(mailMessage.getHtmlContent().split("\n"));
|
||||
}
|
||||
send(mailMessage.getMailAddress(),
|
||||
mailMessage.getTitle(),
|
||||
@ -186,11 +193,17 @@ public class MailService implements MailClient {
|
||||
message.setSubject(title);
|
||||
|
||||
Multipart multipart = new MimeMultipart("alternative");
|
||||
if (CollUtil.isNotEmpty(html) && MapUtil.isNotEmpty(parameter)) {
|
||||
if (CollUtil.isNotEmpty(html)) {
|
||||
String htmlData;
|
||||
List<String> strings;
|
||||
if (MapUtil.isNotEmpty(parameter)) {
|
||||
//读取模板并进行变量替换
|
||||
List<String> strings = HtmlUtil.replacePlaceholder(html, parameter);
|
||||
strings = HtmlUtil.replacePlaceholder(html, parameter);
|
||||
//拼合HTML数据
|
||||
String htmlData = HtmlUtil.pieceHtml(strings);
|
||||
htmlData = HtmlUtil.pieceHtml(strings);
|
||||
}else {
|
||||
htmlData = HtmlUtil.pieceHtml(html);
|
||||
}
|
||||
MimeBodyPart htmlPart = new MimeBodyPart();
|
||||
htmlPart.setContent(htmlData, "text/html;charset=UTF-8");
|
||||
multipart.addBodyPart(htmlPart);
|
||||
|
||||
@ -15,6 +15,10 @@ import org.dromara.sms4j.api.SmsBlend;
|
||||
import org.dromara.sms4j.api.dao.SmsDao;
|
||||
import org.dromara.sms4j.api.dao.SmsDaoDefaultImpl;
|
||||
import org.dromara.sms4j.api.universal.SupplierConfig;
|
||||
import org.dromara.sms4j.api.verify.PhoneVerify;
|
||||
import org.dromara.sms4j.baidu.config.BaiduFactory;
|
||||
import org.dromara.sms4j.budingyun.config.BudingV2Factory;
|
||||
import org.dromara.sms4j.chuanglan.config.ChuangLanFactory;
|
||||
import org.dromara.sms4j.cloopen.config.CloopenFactory;
|
||||
import org.dromara.sms4j.comm.constant.Constant;
|
||||
import org.dromara.sms4j.comm.exception.SmsBlendException;
|
||||
@ -26,21 +30,27 @@ import org.dromara.sms4j.core.proxy.processor.BlackListProcessor;
|
||||
import org.dromara.sms4j.core.proxy.processor.BlackListRecordingProcessor;
|
||||
import org.dromara.sms4j.core.proxy.processor.CoreMethodParamValidateProcessor;
|
||||
import org.dromara.sms4j.core.proxy.processor.RestrictedProcessor;
|
||||
import org.dromara.sms4j.core.proxy.processor.SingleBlendRestrictedProcessor;
|
||||
import org.dromara.sms4j.ctyun.config.CtyunFactory;
|
||||
import org.dromara.sms4j.danmi.config.DanMiFactory;
|
||||
import org.dromara.sms4j.dingzhong.config.DingZhongFactory;
|
||||
import org.dromara.sms4j.emay.config.EmayFactory;
|
||||
import org.dromara.sms4j.huawei.config.HuaweiFactory;
|
||||
import org.dromara.sms4j.javase.util.YamlUtils;
|
||||
import org.dromara.sms4j.jdcloud.config.JdCloudFactory;
|
||||
import org.dromara.sms4j.jg.config.JgFactory;
|
||||
import org.dromara.sms4j.lianlu.config.LianLuFactory;
|
||||
import org.dromara.sms4j.luosimao.config.LuoSiMaoFactory;
|
||||
import org.dromara.sms4j.mas.config.MasFactory;
|
||||
import org.dromara.sms4j.netease.config.NeteaseFactory;
|
||||
import org.dromara.sms4j.provider.config.SmsConfig;
|
||||
import org.dromara.sms4j.provider.factory.BaseProviderFactory;
|
||||
import org.dromara.sms4j.provider.factory.BeanFactory;
|
||||
import org.dromara.sms4j.provider.factory.ProviderFactoryHolder;
|
||||
import org.dromara.sms4j.qiniu.config.QiNiuFactory;
|
||||
import org.dromara.sms4j.submail.config.SubMailFactory;
|
||||
import org.dromara.sms4j.tencent.config.TencentFactory;
|
||||
import org.dromara.sms4j.unisms.config.UniFactory;
|
||||
import org.dromara.sms4j.yixintong.config.YiXintongFactory;
|
||||
import org.dromara.sms4j.yunpian.config.YunPianFactory;
|
||||
import org.dromara.sms4j.zhutong.config.ZhutongFactory;
|
||||
|
||||
@ -51,6 +61,7 @@ import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
/**
|
||||
* 初始化类
|
||||
@ -128,11 +139,16 @@ public class SEInitializer {
|
||||
EnvirmentHolder.frozenEnvirmet(smsConfig, blends);
|
||||
|
||||
//注册执行器实现
|
||||
SmsProxyFactory.addProcessor(new RestrictedProcessor());
|
||||
SmsProxyFactory.addProcessor(new BlackListProcessor());
|
||||
SmsProxyFactory.addProcessor(new BlackListRecordingProcessor());
|
||||
SmsProxyFactory.addProcessor(new SingleBlendRestrictedProcessor());
|
||||
SmsProxyFactory.addProcessor(new CoreMethodParamValidateProcessor());
|
||||
SmsProxyFactory.addPreProcessor(new RestrictedProcessor());
|
||||
SmsProxyFactory.addPreProcessor(new BlackListProcessor());
|
||||
SmsProxyFactory.addPreProcessor(new BlackListRecordingProcessor());
|
||||
//如果手机号校验器存在实现,则注册手机号校验器
|
||||
ServiceLoader<PhoneVerify> loader = ServiceLoader.load(PhoneVerify.class);
|
||||
if (loader.iterator().hasNext()) {
|
||||
loader.forEach(f -> SmsProxyFactory.addPreProcessor(new CoreMethodParamValidateProcessor(f)));
|
||||
} else {
|
||||
SmsProxyFactory.addPreProcessor(new CoreMethodParamValidateProcessor(null));
|
||||
}
|
||||
}catch (Exception e){
|
||||
log.error("配置对象转换配置信息失败,但不影响基础功能的使用。【注意】:未加载SMS4J扩展功能模块,拦截器,参数校验可能失效!");
|
||||
}
|
||||
@ -194,11 +210,16 @@ public class SEInitializer {
|
||||
EnvirmentHolder.frozenEnvirmet(smsConfig, blends);
|
||||
|
||||
//注册执行器实现
|
||||
SmsProxyFactory.addProcessor(new RestrictedProcessor());
|
||||
SmsProxyFactory.addProcessor(new BlackListProcessor());
|
||||
SmsProxyFactory.addProcessor(new BlackListRecordingProcessor());
|
||||
SmsProxyFactory.addProcessor(new SingleBlendRestrictedProcessor());
|
||||
SmsProxyFactory.addProcessor(new CoreMethodParamValidateProcessor());
|
||||
SmsProxyFactory.addPreProcessor(new RestrictedProcessor());
|
||||
SmsProxyFactory.addPreProcessor(new BlackListProcessor());
|
||||
SmsProxyFactory.addPreProcessor(new BlackListRecordingProcessor());
|
||||
//如果手机号校验器存在实现,则注册手机号校验器
|
||||
ServiceLoader<PhoneVerify> loader = ServiceLoader.load(PhoneVerify.class);
|
||||
if (loader.iterator().hasNext()) {
|
||||
loader.forEach(f -> SmsProxyFactory.addPreProcessor(new CoreMethodParamValidateProcessor(f)));
|
||||
} else {
|
||||
SmsProxyFactory.addPreProcessor(new CoreMethodParamValidateProcessor(null));
|
||||
}
|
||||
for (String configId : blends.keySet()) {
|
||||
Map<String, Object> configMap = blends.get(configId);
|
||||
Object supplierObj = configMap.get(Constant.SUPPLIER_KEY);
|
||||
@ -210,7 +231,7 @@ public class SEInitializer {
|
||||
continue;
|
||||
}
|
||||
configMap.put("config-id", configId);
|
||||
SmsUtils.replaceKeysSeperator(configMap, "-", "_");
|
||||
SmsUtils.replaceKeysSeparator(configMap, "-", "_");
|
||||
JSONObject configJson = new JSONObject(configMap);
|
||||
SupplierConfig supplierConfig = JSONUtil.toBean(configJson, providerFactory.getConfigClass());
|
||||
SmsFactory.createSmsBlend(supplierConfig);
|
||||
@ -233,6 +254,16 @@ public class SEInitializer {
|
||||
ProviderFactoryHolder.registerFactory(ZhutongFactory.instance());
|
||||
ProviderFactoryHolder.registerFactory(LianLuFactory.instance());
|
||||
ProviderFactoryHolder.registerFactory(DingZhongFactory.instance());
|
||||
ProviderFactoryHolder.registerFactory(QiNiuFactory.instance());
|
||||
ProviderFactoryHolder.registerFactory(ChuangLanFactory.instance());
|
||||
ProviderFactoryHolder.registerFactory(JgFactory.instance());
|
||||
ProviderFactoryHolder.registerFactory(BudingV2Factory.instance());
|
||||
ProviderFactoryHolder.registerFactory(MasFactory.instance());
|
||||
ProviderFactoryHolder.registerFactory(BaiduFactory.instance());
|
||||
ProviderFactoryHolder.registerFactory(LuoSiMaoFactory.instance());
|
||||
ProviderFactoryHolder.registerFactory(SubMailFactory.instance());
|
||||
ProviderFactoryHolder.registerFactory(DanMiFactory.instance());
|
||||
ProviderFactoryHolder.registerFactory(YiXintongFactory.instance());
|
||||
if (SmsUtils.isClassExists("com.jdcloud.sdk.auth.CredentialsProvider")) {
|
||||
ProviderFactoryHolder.registerFactory(JdCloudFactory.instance());
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ package org.dromara.oa.core.config;
|
||||
import org.dromara.oa.api.OaSender;
|
||||
import org.dromara.oa.core.provider.config.OaConfig;
|
||||
import org.dromara.oa.core.provider.factory.OaBaseProviderFactory;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@ -31,7 +32,7 @@ public class OaSupplierConfig {
|
||||
protected OaBlendsInitializer smsOasInitializer(
|
||||
List<OaBaseProviderFactory<? extends OaSender, ? extends org.dromara.oa.comm.config.OaSupplierConfig>> factoryList,
|
||||
OaConfig oaConfig,
|
||||
Map<String, Map<String, Object>> oas) {
|
||||
@Qualifier("oas") Map<String, Map<String, Object>> oas) {
|
||||
return new OaBlendsInitializer(factoryList,oaConfig,oas);
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,13 +16,13 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
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) {
|
||||
if (factory == null) {
|
||||
throw new OaException("注册供应商工厂失败,工厂实例不能为空");
|
||||
}
|
||||
factories.put(factory.getSupplier(), factory);
|
||||
FACTORIES.put(factory.getSupplier(), factory);
|
||||
}
|
||||
|
||||
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) {
|
||||
return factories.getOrDefault(supplier, null);
|
||||
return FACTORIES.getOrDefault(supplier, null);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ package org.dromara.oa.core.provider.service;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.oa.api.OaCallBack;
|
||||
import org.dromara.oa.api.OaSender;
|
||||
import org.dromara.oa.comm.config.OaSupplierConfig;
|
||||
@ -19,6 +20,7 @@ import java.util.concurrent.PriorityBlockingQueue;
|
||||
* @author dongfeng
|
||||
* 2023-10-22 21:03
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class AbstractOaBlend<C extends OaSupplierConfig> implements OaSender {
|
||||
|
||||
@Getter
|
||||
@ -57,12 +59,16 @@ public abstract class AbstractOaBlend<C extends OaSupplierConfig> implements OaS
|
||||
pool.execute(() -> {
|
||||
Thread.currentThread().setName("oa-priorityQueueMap-thread");
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
Request request = priorityQueueMap.poll();
|
||||
if (!Objects.isNull(request)) {
|
||||
try{
|
||||
Request request = priorityQueueMap.take() ;
|
||||
pool.execute(() -> {
|
||||
System.out.println("优先级为"+request.getPriority()+"已发送");
|
||||
log.info("优先级为"+request.getPriority()+"已发送");
|
||||
sender(request, request.getMessageType());
|
||||
});
|
||||
}catch (InterruptedException e){
|
||||
log.info("[Dispatcher]-priorityQueueMap-task-dispatcher has been interrupt to close.");
|
||||
Thread.currentThread().interrupt();
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -7,6 +7,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.sms4j.aliyun.config.AlibabaConfig;
|
||||
import org.dromara.sms4j.aliyun.utils.AliyunUtils;
|
||||
import org.dromara.sms4j.api.entity.SmsResponse;
|
||||
import org.dromara.sms4j.api.utils.SmsRespUtils;
|
||||
import org.dromara.sms4j.comm.constant.Constant;
|
||||
import org.dromara.sms4j.comm.constant.SupplierConstant;
|
||||
import org.dromara.sms4j.comm.delayedTime.DelayedTime;
|
||||
@ -57,7 +58,7 @@ public class AlibabaSmsImpl extends AbstractSmsBlend<AlibabaConfig> {
|
||||
|
||||
@Override
|
||||
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);
|
||||
return sendMessage(phone, getConfig().getTemplateId(), map);
|
||||
}
|
||||
@ -92,7 +93,7 @@ public class AlibabaSmsImpl extends AbstractSmsBlend<AlibabaConfig> {
|
||||
messages = new LinkedHashMap<>();
|
||||
}
|
||||
String messageStr = JSONUtil.toJsonStr(messages);
|
||||
return getSmsResponse(SmsUtils.arrayToString(phones), messageStr, templateId);
|
||||
return getSmsResponse(SmsUtils.addCodePrefixIfNot(phones), messageStr, templateId);
|
||||
}
|
||||
|
||||
private SmsResponse getSmsResponse(String phone, String message, String templateId) {
|
||||
@ -108,16 +109,14 @@ public class AlibabaSmsImpl extends AbstractSmsBlend<AlibabaConfig> {
|
||||
log.debug("requestUrl {}", requestUrl);
|
||||
|
||||
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;
|
||||
try {
|
||||
smsResponse = getResponse(http.postJson(requestUrl, headers, paramStr));
|
||||
} catch (SmsBlendException e) {
|
||||
smsResponse = new SmsResponse();
|
||||
smsResponse.setSuccess(false);
|
||||
smsResponse.setData(e.getMessage());
|
||||
smsResponse = errorResp(e.message);
|
||||
}
|
||||
if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) {
|
||||
if (smsResponse.isSuccess() || retry >= getConfig().getMaxRetries()) {
|
||||
retry = 0;
|
||||
return smsResponse;
|
||||
}
|
||||
@ -132,11 +131,7 @@ public class AlibabaSmsImpl extends AbstractSmsBlend<AlibabaConfig> {
|
||||
}
|
||||
|
||||
private SmsResponse getResponse(JSONObject resJson) {
|
||||
SmsResponse smsResponse = new SmsResponse();
|
||||
smsResponse.setSuccess("OK".equals(resJson.getStr("Code")));
|
||||
smsResponse.setData(resJson);
|
||||
smsResponse.setConfigId(getConfigId());
|
||||
return smsResponse;
|
||||
return SmsRespUtils.resp(resJson, "OK".equals(resJson.getStr("Code")), getConfigId());
|
||||
}
|
||||
|
||||
}
|
||||
@ -4,15 +4,14 @@ import cn.hutool.crypto.digest.HMac;
|
||||
import cn.hutool.crypto.digest.HmacAlgorithm;
|
||||
import org.dromara.sms4j.aliyun.config.AlibabaConfig;
|
||||
import org.dromara.sms4j.comm.constant.Constant;
|
||||
import org.dromara.sms4j.comm.utils.SmsDateUtils;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.SimpleTimeZone;
|
||||
import java.util.TreeMap;
|
||||
import java.util.UUID;
|
||||
|
||||
@ -27,18 +26,15 @@ public class AliyunUtils {
|
||||
*/
|
||||
private static final String ALGORITHM = "HMAC-SHA1";
|
||||
|
||||
private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
|
||||
|
||||
public static String generateSendSmsRequestUrl(AlibabaConfig alibabaConfig, String message, String phone, String templateId) throws Exception {
|
||||
// 这里一定要设置GMT时区
|
||||
SDF.setTimeZone(new SimpleTimeZone(0, "GMT"));
|
||||
Map<String, String> paras = new HashMap<>();
|
||||
// 1. 公共请求参数
|
||||
paras.put("SignatureMethod", ALGORITHM);
|
||||
paras.put("SignatureNonce", UUID.randomUUID().toString());
|
||||
paras.put("AccessKeyId", alibabaConfig.getAccessKeyId());
|
||||
paras.put("SignatureVersion", "1.0");
|
||||
paras.put("Timestamp", SDF.format(new Date()));
|
||||
paras.put("Timestamp", SmsDateUtils.utcGmt(new Date()));
|
||||
paras.put("Format", "JSON");
|
||||
paras.put("Action", alibabaConfig.getAction());
|
||||
paras.put("Version", alibabaConfig.getVersion());
|
||||
@ -110,10 +106,10 @@ public class AliyunUtils {
|
||||
/**
|
||||
* 生成请求参数body字符串
|
||||
*
|
||||
* @param alibabaConfig
|
||||
* @param phone
|
||||
* @param message
|
||||
* @param templateId
|
||||
* @param alibabaConfig 配置数据
|
||||
* @param phone 手机号
|
||||
* @param message 短信内容
|
||||
* @param templateId 模板id
|
||||
*/
|
||||
public static String generateParamBody(AlibabaConfig alibabaConfig, String phone, String message, String templateId) throws Exception {
|
||||
Map<String, String> paramMap = generateParamMap(alibabaConfig, phone, message, templateId);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
package org.dromara.sms4j.baidu.config;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.dromara.sms4j.baidu.service.BaiduSmsImpl;
|
||||
import org.dromara.sms4j.comm.constant.SupplierConstant;
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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.baidu.config.BaiduConfig;
|
||||
import org.dromara.sms4j.baidu.utils.BaiduUtils;
|
||||
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.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());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,133 @@
|
||||
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.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@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());
|
||||
headers.put("x-bce-date", SmsDateUtils.normDateGmt8(new Date()));
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,168 @@
|
||||
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.ArrayList;
|
||||
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) {
|
||||
List<SmsResponse> list = new ArrayList<>();
|
||||
for (String phone : phones) {
|
||||
SmsResponse smsResponse = sendMessage(phone, templateId, messages);
|
||||
list.add(smsResponse);
|
||||
}
|
||||
return SmsRespUtils.resp(list, true, getConfigId());
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@ -2,6 +2,7 @@ package org.dromara.sms4j.cloopen.config;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.dromara.sms4j.comm.constant.Constant;
|
||||
import org.dromara.sms4j.comm.constant.SupplierConstant;
|
||||
import org.dromara.sms4j.provider.config.BaseConfig;
|
||||
|
||||
@ -18,7 +19,7 @@ public class CloopenConfig extends BaseConfig {
|
||||
/**
|
||||
* REST API Base URL
|
||||
*/
|
||||
private String baseUrl = "https://app.cloopen.com:8883/2013-12-26";
|
||||
private String baseUrl = Constant.HTTPS_PREFIX + "app.cloopen.com:8883/2013-12-26";
|
||||
|
||||
/**
|
||||
* 获取供应商
|
||||
|
||||
@ -27,7 +27,7 @@ public class CloopenFactory extends AbstractProviderFactory<CloopenSmsImpl, Cloo
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建容连云短信实现对象
|
||||
* 创建容联云短信实现对象
|
||||
*
|
||||
* @param cloopenConfig 短信配置对象
|
||||
* @return 短信实现对象
|
||||
|
||||
@ -8,6 +8,7 @@ import cn.hutool.crypto.SecureUtil;
|
||||
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.cloopen.config.CloopenConfig;
|
||||
import org.dromara.sms4j.comm.constant.Constant;
|
||||
import org.dromara.sms4j.comm.exception.SmsBlendException;
|
||||
@ -43,16 +44,14 @@ public class CloopenHelper {
|
||||
config.getAccessKeyId(),
|
||||
this.generateSign(config.getAccessKeyId(), config.getAccessKeySecret(), timestamp));
|
||||
Map<String, String> headers = MapUtil.newHashMap(3, true);
|
||||
headers.put("Accept", Constant.ACCEPT);
|
||||
headers.put("Content-Type", Constant.APPLICATION_JSON_UTF8);
|
||||
headers.put("Authorization", this.generateAuthorization(config.getAccessKeyId(), timestamp));
|
||||
SmsResponse smsResponse = null;
|
||||
headers.put(Constant.ACCEPT, Constant.APPLICATION_JSON);
|
||||
headers.put(Constant.CONTENT_TYPE, Constant.APPLICATION_JSON_UTF8);
|
||||
headers.put(Constant.AUTHORIZATION, this.generateAuthorization(config.getAccessKeyId(), timestamp));
|
||||
SmsResponse smsResponse;
|
||||
try {
|
||||
smsResponse = getResponse(http.postJson(url, headers, paramMap));
|
||||
} catch (SmsBlendException e) {
|
||||
smsResponse = new SmsResponse();
|
||||
smsResponse.setSuccess(false);
|
||||
smsResponse.setData(e.getMessage());
|
||||
smsResponse = SmsRespUtils.error(e.message, config.getConfigId());
|
||||
}
|
||||
if (smsResponse.isSuccess() || retry == config.getMaxRetries()) {
|
||||
retry = 0;
|
||||
@ -70,11 +69,7 @@ public class CloopenHelper {
|
||||
}
|
||||
|
||||
private SmsResponse getResponse(JSONObject resJson) {
|
||||
SmsResponse smsResponse = new SmsResponse();
|
||||
smsResponse.setSuccess("000000".equals(resJson.getStr("statusCode")));
|
||||
smsResponse.setData(resJson);
|
||||
smsResponse.setConfigId(this.config.getConfigId());
|
||||
return smsResponse;
|
||||
return SmsRespUtils.resp(resJson, "000000".equals(resJson.getStr("statusCode")), config.getConfigId());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -2,6 +2,7 @@ package org.dromara.sms4j.ctyun.config;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.dromara.sms4j.comm.constant.Constant;
|
||||
import org.dromara.sms4j.comm.constant.SupplierConstant;
|
||||
import org.dromara.sms4j.provider.config.BaseConfig;
|
||||
|
||||
@ -9,7 +10,7 @@ import org.dromara.sms4j.provider.config.BaseConfig;
|
||||
* <p>类名: CtyunConfig
|
||||
* <p>说明: 天翼云短信差异配置
|
||||
*
|
||||
* @author :bleachhtred
|
||||
* @author :bleachtred
|
||||
* 2023/5/12 15:06
|
||||
**/
|
||||
@Data
|
||||
@ -24,7 +25,7 @@ public class CtyunConfig extends BaseConfig {
|
||||
/**
|
||||
* 请求地址
|
||||
*/
|
||||
private String requestUrl = "https://sms-global.ctapi.ctyun.cn/sms/api/v1";
|
||||
private String requestUrl = Constant.HTTPS_PREFIX + "sms-global.ctapi.ctyun.cn/sms/api/v1";
|
||||
|
||||
/**
|
||||
* 接口名称
|
||||
|
||||
@ -10,7 +10,7 @@ import org.dromara.sms4j.provider.factory.AbstractProviderFactory;
|
||||
* <p>类名: CtyunSmsConfig
|
||||
* <p>说明: 天翼云 云通信短信配置器
|
||||
*
|
||||
* @author :bleachhtred
|
||||
* @author :bleachtred
|
||||
* 2023/5/12 15:06
|
||||
**/
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
@ -30,7 +30,7 @@ public class CtyunFactory extends AbstractProviderFactory<CtyunSmsImpl, CtyunCon
|
||||
* getCtyunSms
|
||||
* <p> 建造一个短信实现对像
|
||||
*
|
||||
* @author :bleachhtred
|
||||
* @author :bleachtred
|
||||
*/
|
||||
@Override
|
||||
public CtyunSmsImpl createSms(CtyunConfig ctyunConfig) {
|
||||
|
||||
@ -4,6 +4,7 @@ import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.sms4j.api.entity.SmsResponse;
|
||||
import org.dromara.sms4j.api.utils.SmsRespUtils;
|
||||
import org.dromara.sms4j.comm.constant.SupplierConstant;
|
||||
import org.dromara.sms4j.comm.delayedTime.DelayedTime;
|
||||
import org.dromara.sms4j.comm.exception.SmsBlendException;
|
||||
@ -21,7 +22,7 @@ import java.util.concurrent.Executor;
|
||||
* <p>类名: CtyunSmsImpl
|
||||
* <p>说明: 天翼云短信实现
|
||||
*
|
||||
* @author :bleachhtred
|
||||
* @author :bleachtred
|
||||
* 2023/5/12 15:06
|
||||
**/
|
||||
@Slf4j
|
||||
@ -79,15 +80,16 @@ public class CtyunSmsImpl extends AbstractSmsBlend<CtyunConfig> {
|
||||
messages = new LinkedHashMap<>();
|
||||
}
|
||||
String messageStr = JSONUtil.toJsonStr(messages);
|
||||
return getSmsResponse(SmsUtils.arrayToString(phones), messageStr, templateId);
|
||||
return getSmsResponse(SmsUtils.addCodePrefixIfNot(phones), messageStr, templateId);
|
||||
}
|
||||
|
||||
private SmsResponse getSmsResponse(String phone, String message, String templateId) {
|
||||
CtyunConfig config = getConfig();
|
||||
String requestUrl;
|
||||
String paramStr;
|
||||
try {
|
||||
requestUrl = getConfig().getRequestUrl();
|
||||
paramStr = CtyunUtils.generateParamJsonStr(getConfig(), phone, message, templateId);
|
||||
requestUrl = config.getRequestUrl();
|
||||
paramStr = CtyunUtils.generateParamJsonStr(config, phone, message, templateId);
|
||||
} catch (Exception e) {
|
||||
log.error("ctyun send message error", e);
|
||||
throw new SmsBlendException(e.getMessage());
|
||||
@ -96,14 +98,12 @@ public class CtyunSmsImpl extends AbstractSmsBlend<CtyunConfig> {
|
||||
SmsResponse smsResponse;
|
||||
try {
|
||||
smsResponse = getResponse(http.postJson(requestUrl,
|
||||
CtyunUtils.signHeader(paramStr, getConfig().getAccessKeyId(), getConfig().getAccessKeySecret()),
|
||||
CtyunUtils.signHeader(paramStr, config.getAccessKeyId(), config.getAccessKeySecret()),
|
||||
paramStr));
|
||||
} catch (SmsBlendException e) {
|
||||
smsResponse = new SmsResponse();
|
||||
smsResponse.setSuccess(false);
|
||||
smsResponse.setData(e.getMessage());
|
||||
smsResponse = errorResp(e.message);
|
||||
}
|
||||
if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) {
|
||||
if (smsResponse.isSuccess() || retry == config.getMaxRetries()) {
|
||||
retry = 0;
|
||||
return smsResponse;
|
||||
}
|
||||
@ -118,11 +118,7 @@ public class CtyunSmsImpl extends AbstractSmsBlend<CtyunConfig> {
|
||||
}
|
||||
|
||||
private SmsResponse getResponse(JSONObject resJson) {
|
||||
SmsResponse smsResponse = new SmsResponse();
|
||||
smsResponse.setSuccess("OK".equals(resJson.getStr("code")));
|
||||
smsResponse.setData(resJson);
|
||||
smsResponse.setConfigId(getConfigId());
|
||||
return smsResponse;
|
||||
return SmsRespUtils.resp(resJson, "OK".equals(resJson.getStr("code")), getConfigId());
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,22 +1,18 @@
|
||||
package org.dromara.sms4j.ctyun.utils;
|
||||
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import cn.hutool.crypto.digest.HMac;
|
||||
import cn.hutool.crypto.digest.HmacAlgorithm;
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
import cn.hutool.crypto.digest.DigestUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.dromara.sms4j.comm.constant.Constant;
|
||||
import org.dromara.sms4j.comm.exception.SmsBlendException;
|
||||
import org.dromara.sms4j.comm.utils.SmsDateUtils;
|
||||
import org.dromara.sms4j.ctyun.config.CtyunConfig;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@ -28,8 +24,7 @@ public class CtyunUtils {
|
||||
* 获取签名时间戳
|
||||
*/
|
||||
private static String signatureTime(){
|
||||
SimpleDateFormat timeFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
|
||||
return timeFormat.format(new Date());
|
||||
return SmsDateUtils.pureDateUtcGmt8(new Date());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -39,9 +34,7 @@ public class CtyunUtils {
|
||||
Map<String, String> map = new ConcurrentHashMap<>(4);
|
||||
|
||||
// 构造时间戳
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
|
||||
Date now = new Date();
|
||||
String signatureDate = dateFormat.format(now);
|
||||
String signatureDate = SmsDateUtils.pureDateGmt8(new Date());
|
||||
String signatureTime = signatureTime();
|
||||
// 构造请求流水号
|
||||
String uuid = UUID.randomUUID().toString();
|
||||
@ -59,7 +52,7 @@ public class CtyunUtils {
|
||||
// 构造签名
|
||||
String signature = Base64.encode(hmacSHA256(signatureStr.getBytes(StandardCharsets.UTF_8), kDate));
|
||||
String signHeader = String.format("%s Headers=ctyun-eop-request-id;eop-date Signature=%s", key, signature);
|
||||
map.put("Content-Type", Constant.APPLICATION_JSON_UTF8);
|
||||
map.put(Constant.CONTENT_TYPE, Constant.APPLICATION_JSON_UTF8);
|
||||
map.put("ctyun-eop-request-id", uuid);
|
||||
map.put("Eop-date", signatureTime);
|
||||
map.put("Eop-Authorization", signHeader);
|
||||
@ -84,36 +77,11 @@ public class CtyunUtils {
|
||||
return JSONUtil.toJsonStr(paramMap);
|
||||
}
|
||||
|
||||
private static String toHex(byte[] data) {
|
||||
StringBuilder sb = new StringBuilder(data.length * 2);
|
||||
for (byte b : data) {
|
||||
String hex = Integer.toHexString(b);
|
||||
if (hex.length() == 1) {
|
||||
sb.append("0");
|
||||
} else if (hex.length() == 8) {
|
||||
hex = hex.substring(6);
|
||||
}
|
||||
sb.append(hex);
|
||||
}
|
||||
return sb.toString().toLowerCase(Locale.getDefault());
|
||||
}
|
||||
|
||||
private static String getSHA256(String text) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
md.update(text.getBytes(StandardCharsets.UTF_8));
|
||||
return toHex(md.digest());
|
||||
} catch (NoSuchAlgorithmException var3) {
|
||||
return null;
|
||||
}
|
||||
return DigestUtil.sha256Hex(text);
|
||||
}
|
||||
|
||||
private static byte[] hmacSHA256(byte[] data, byte[] key){
|
||||
try {
|
||||
HMac hMac = new HMac(HmacAlgorithm.HmacSHA256, key);
|
||||
return hMac.digest(data);
|
||||
} catch (Exception e) {
|
||||
throw new SmsBlendException(e.getMessage());
|
||||
}
|
||||
return SecureUtil.hmacSha256(key).digest(data);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,151 @@
|
||||
package org.dromara.sms4j.danmi.service;
|
||||
|
||||
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.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.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());
|
||||
}
|
||||
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -86,11 +86,11 @@ public class DingZhongSmsImpl extends AbstractSmsBlend<DingZhongConfig> {
|
||||
|
||||
@Override
|
||||
public SmsResponse massTexting(List<String> phones, String message) {
|
||||
return sendMessage(SmsUtils.arrayToString(phones), message);
|
||||
return sendMessage(SmsUtils.addCodePrefixIfNot(phones), message);
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -4,6 +4,7 @@ import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.sms4j.api.entity.SmsResponse;
|
||||
import org.dromara.sms4j.api.utils.SmsRespUtils;
|
||||
import org.dromara.sms4j.comm.constant.Constant;
|
||||
import org.dromara.sms4j.comm.exception.SmsBlendException;
|
||||
import org.dromara.sms4j.comm.utils.SmsHttpUtils;
|
||||
@ -33,15 +34,13 @@ public class DingZhongHelper {
|
||||
public SmsResponse smsResponse(Map<String, Object> paramMap) {
|
||||
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);
|
||||
headers.put("Accept", Constant.ACCEPT);
|
||||
headers.put("Content-Type", Constant.FROM_URLENCODED);
|
||||
SmsResponse smsResponse = null;
|
||||
headers.put(Constant.ACCEPT, Constant.APPLICATION_JSON);
|
||||
headers.put(Constant.CONTENT_TYPE, Constant.APPLICATION_FROM_URLENCODED);
|
||||
SmsResponse smsResponse;
|
||||
try {
|
||||
smsResponse = getResponse(http.postFrom(url, headers, paramMap));
|
||||
} catch (SmsBlendException e) {
|
||||
smsResponse = new SmsResponse();
|
||||
smsResponse.setSuccess(false);
|
||||
smsResponse.setData(e.getMessage());
|
||||
smsResponse = SmsRespUtils.error(e.message, config.getConfigId());
|
||||
}
|
||||
if (smsResponse.isSuccess() || retry == config.getMaxRetries()) {
|
||||
retry = 0;
|
||||
@ -59,10 +58,6 @@ public class DingZhongHelper {
|
||||
}
|
||||
|
||||
private SmsResponse getResponse(JSONObject resJson) {
|
||||
SmsResponse smsResponse = new SmsResponse();
|
||||
smsResponse.setSuccess("0".equals(resJson.getStr("resCode")));
|
||||
smsResponse.setData(resJson);
|
||||
smsResponse.setConfigId(this.config.getConfigId());
|
||||
return smsResponse;
|
||||
return SmsRespUtils.resp(resJson, "0".equals(resJson.getStr("resCode")), config.getConfigId());
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.sms4j.api.entity.SmsResponse;
|
||||
import org.dromara.sms4j.api.utils.SmsRespUtils;
|
||||
import org.dromara.sms4j.comm.constant.Constant;
|
||||
import org.dromara.sms4j.comm.constant.SupplierConstant;
|
||||
import org.dromara.sms4j.comm.delayedTime.DelayedTime;
|
||||
@ -44,20 +45,19 @@ public class EmaySmsImpl extends AbstractSmsBlend<EmayConfig> {
|
||||
|
||||
@Override
|
||||
public SmsResponse sendMessage(String phone, String message) {
|
||||
String url = getConfig().getRequestUrl();
|
||||
Map<String, Object> params = EmayBuilder.buildRequestBody(getConfig().getAccessKeyId(), getConfig().getAccessKeySecret(), phone, message);
|
||||
EmayConfig config = getConfig();
|
||||
String url = config.getRequestUrl();
|
||||
Map<String, Object> params = EmayBuilder.buildRequestBody(config.getAccessKeyId(), config.getAccessKeySecret(), phone, message);
|
||||
|
||||
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;
|
||||
try {
|
||||
smsResponse = getResponse(http.postUrl(url, headers, params));
|
||||
} catch (SmsBlendException e) {
|
||||
smsResponse = new SmsResponse();
|
||||
smsResponse.setSuccess(false);
|
||||
smsResponse.setData(e.getMessage());
|
||||
smsResponse = errorResp(e.message);
|
||||
}
|
||||
if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) {
|
||||
if (smsResponse.isSuccess() || retry == config.getMaxRetries()) {
|
||||
retry = 0;
|
||||
return smsResponse;
|
||||
}
|
||||
@ -97,7 +97,7 @@ public class EmaySmsImpl extends AbstractSmsBlend<EmayConfig> {
|
||||
if (phones.size() > 500) {
|
||||
throw new SmsBlendException("单次发送超过最大发送上限,建议每次群发短信人数低于500");
|
||||
}
|
||||
return sendMessage(SmsUtils.listToString(phones), message);
|
||||
return sendMessage(SmsUtils.joinComma(phones), message);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -109,15 +109,11 @@ public class EmaySmsImpl extends AbstractSmsBlend<EmayConfig> {
|
||||
for (Map.Entry<String, String> entry : messages.entrySet()) {
|
||||
list.add(entry.getValue());
|
||||
}
|
||||
return sendMessage(SmsUtils.listToString(phones), EmayBuilder.listToString(list));
|
||||
return sendMessage(SmsUtils.joinComma(phones), EmayBuilder.listToString(list));
|
||||
}
|
||||
|
||||
private SmsResponse getResponse(JSONObject resJson) {
|
||||
SmsResponse smsResponse = new SmsResponse();
|
||||
smsResponse.setSuccess("success".equalsIgnoreCase(resJson.getStr("code")));
|
||||
smsResponse.setData(resJson);
|
||||
smsResponse.setConfigId(getConfigId());
|
||||
return smsResponse;
|
||||
return SmsRespUtils.resp(resJson, "success".equalsIgnoreCase(resJson.getStr("code")), getConfigId());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.sms4j.api.entity.SmsResponse;
|
||||
import org.dromara.sms4j.api.utils.SmsRespUtils;
|
||||
import org.dromara.sms4j.comm.constant.Constant;
|
||||
import org.dromara.sms4j.comm.constant.SupplierConstant;
|
||||
import org.dromara.sms4j.comm.delayedTime.DelayedTime;
|
||||
@ -20,13 +21,14 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.dromara.sms4j.huawei.utils.HuaweiBuilder.listToString;
|
||||
|
||||
@Slf4j
|
||||
public class HuaweiSmsImpl extends AbstractSmsBlend<HuaweiConfig> {
|
||||
|
||||
private int retry = 0;
|
||||
private volatile int retry = 0;
|
||||
|
||||
public HuaweiSmsImpl(HuaweiConfig config, Executor pool, DelayedTime delayed) {
|
||||
super(config, pool, delayed);
|
||||
@ -70,18 +72,16 @@ public class HuaweiSmsImpl extends AbstractSmsBlend<HuaweiConfig> {
|
||||
String requestBody = HuaweiBuilder.buildRequestBody(getConfig().getSender(), phone, templateId, mess, getConfig().getStatusCallBack(), getConfig().getSignature());
|
||||
|
||||
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("Content-Type", Constant.FROM_URLENCODED);
|
||||
headers.put(Constant.CONTENT_TYPE, Constant.APPLICATION_FROM_URLENCODED);
|
||||
SmsResponse smsResponse;
|
||||
try {
|
||||
smsResponse = getResponse(http.postJson(url, headers, requestBody));
|
||||
} catch (SmsBlendException e) {
|
||||
smsResponse = new SmsResponse();
|
||||
smsResponse.setSuccess(false);
|
||||
smsResponse.setData(e.getMessage());
|
||||
smsResponse = errorResp(e.message);
|
||||
}
|
||||
if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) {
|
||||
if (smsResponse.isSuccess() || retry >= getConfig().getMaxRetries()) {
|
||||
retry = 0;
|
||||
return smsResponse;
|
||||
}
|
||||
@ -109,11 +109,7 @@ public class HuaweiSmsImpl extends AbstractSmsBlend<HuaweiConfig> {
|
||||
}
|
||||
|
||||
private SmsResponse getResponse(JSONObject resJson) {
|
||||
SmsResponse smsResponse = new SmsResponse();
|
||||
smsResponse.setSuccess("000000".equals(resJson.getStr("code")));
|
||||
smsResponse.setData(resJson);
|
||||
smsResponse.setConfigId(getConfigId());
|
||||
return smsResponse;
|
||||
return SmsRespUtils.resp(resJson, "000000".equals(resJson.getStr("code")), getConfigId());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -2,23 +2,25 @@ package org.dromara.sms4j.huawei.utils;
|
||||
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.lang.UUID;
|
||||
import cn.hutool.core.net.URLEncodeUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.crypto.digest.DigestUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.sms4j.comm.constant.Constant;
|
||||
import org.dromara.sms4j.comm.exception.SmsBlendException;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@Slf4j
|
||||
public class HuaweiBuilder {
|
||||
private HuaweiBuilder() {
|
||||
}
|
||||
@ -31,22 +33,13 @@ public class HuaweiBuilder {
|
||||
*/
|
||||
public static String buildWsseHeader(String appKey, String appSecret) {
|
||||
if (null == appKey || null == appSecret || appKey.isEmpty() || appSecret.isEmpty()) {
|
||||
System.out.println("buildWsseHeader(): appKey or appSecret is null.");
|
||||
return null;
|
||||
log.error("buildWsseHeader(): appKey or appSecret is null.");
|
||||
throw new SmsBlendException("buildWsseHeader(): appKey or appSecret is null.");
|
||||
}
|
||||
String time = dateFormat(new Date());
|
||||
// Nonce
|
||||
String nonce = UUID.randomUUID().toString().replace("-", "");
|
||||
MessageDigest md;
|
||||
byte[] passwordDigest = null;
|
||||
|
||||
try {
|
||||
md = MessageDigest.getInstance("SHA-256");
|
||||
md.update((nonce + time + appSecret).getBytes());
|
||||
passwordDigest = md.digest();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
String nonce = UUID.fastUUID().toString(true);
|
||||
byte[] passwordDigest = DigestUtil.sha256(nonce + time + appSecret);
|
||||
// PasswordDigest
|
||||
String passwordDigestBase64Str = Base64.encode(passwordDigest);
|
||||
//若passwordDigestBase64Str中包含换行符,请执行如下代码进行修正
|
||||
@ -90,12 +83,11 @@ public class HuaweiBuilder {
|
||||
*/
|
||||
public static String buildRequestBody(String sender, String receiver, String templateId, String templateParas,
|
||||
String statusCallBack, String signature) {
|
||||
if (null == sender || null == receiver || null == templateId || sender.isEmpty() || receiver.isEmpty()
|
||||
|| templateId.isEmpty()) {
|
||||
System.out.println("buildRequestBody(): sender, receiver or templateId is null.");
|
||||
return null;
|
||||
if (StrUtil.hasBlank(sender, receiver, templateId)) {
|
||||
log.error("buildRequestBody(): sender, receiver or templateId is null.");
|
||||
throw new SmsBlendException("buildRequestBody(): sender, receiver or templateId is null.");
|
||||
}
|
||||
Map<String, String> map = new HashMap<>();
|
||||
Map<String, String> map = new HashMap<>(3);
|
||||
|
||||
map.put("from", sender);
|
||||
map.put("to", receiver);
|
||||
@ -111,21 +103,14 @@ public class HuaweiBuilder {
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String temp = "";
|
||||
|
||||
for (String s : map.keySet()) {
|
||||
try {
|
||||
temp = URLEncoder.encode(map.get(s), "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
sb.append(s).append("=").append(temp).append("&");
|
||||
}
|
||||
|
||||
map.keySet().forEach(s -> sb.append(s).append("=").append(URLEncodeUtil.encode(map.get(s))).append("&"));
|
||||
return sb.deleteCharAt(sb.length() - 1).toString();
|
||||
}
|
||||
|
||||
public static String listToString(List<String> list) {
|
||||
if (null == list || list.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
StringBuilder stringBuffer = new StringBuilder();
|
||||
stringBuffer.append("[\"");
|
||||
for (String s : list) {
|
||||
|
||||
@ -6,6 +6,7 @@ import com.jdcloud.sdk.service.sms.model.BatchSendRequest;
|
||||
import com.jdcloud.sdk.service.sms.model.BatchSendResult;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.sms4j.api.entity.SmsResponse;
|
||||
import org.dromara.sms4j.api.utils.SmsRespUtils;
|
||||
import org.dromara.sms4j.comm.constant.SupplierConstant;
|
||||
import org.dromara.sms4j.comm.delayedTime.DelayedTime;
|
||||
import org.dromara.sms4j.comm.exception.SmsBlendException;
|
||||
@ -96,11 +97,9 @@ public class JdCloudSmsImpl extends AbstractSmsBlend<JdCloudConfig> {
|
||||
try {
|
||||
smsResponse = getSmsResponse(result);
|
||||
} catch (SmsBlendException e) {
|
||||
smsResponse = new SmsResponse();
|
||||
smsResponse.setSuccess(false);
|
||||
smsResponse.setData(e.getMessage());
|
||||
smsResponse = errorResp(e.message);
|
||||
}
|
||||
if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) {
|
||||
if (smsResponse.isSuccess() || retry >= getConfig().getMaxRetries()) {
|
||||
retry = 0;
|
||||
return smsResponse;
|
||||
}
|
||||
@ -121,10 +120,6 @@ public class JdCloudSmsImpl extends AbstractSmsBlend<JdCloudConfig> {
|
||||
* @return 发送短信返回信息
|
||||
*/
|
||||
private SmsResponse getSmsResponse(BatchSendResult res) {
|
||||
SmsResponse smsResponse = new SmsResponse();
|
||||
smsResponse.setSuccess(res.getStatus() != null && res.getStatus());
|
||||
smsResponse.setData(res);
|
||||
smsResponse.setConfigId(getConfigId());
|
||||
return smsResponse;
|
||||
return SmsRespUtils.resp(res, res.getStatus() != null && res.getStatus(), getConfigId());
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,268 @@
|
||||
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.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3,6 +3,7 @@ package org.dromara.sms4j.lianlu.config;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.dromara.sms4j.comm.constant.Constant;
|
||||
import org.dromara.sms4j.comm.constant.SupplierConstant;
|
||||
import org.dromara.sms4j.lianlu.req.LianLuRequest;
|
||||
import org.dromara.sms4j.lianlu.utils.LianLuUtils;
|
||||
@ -10,7 +11,7 @@ import org.dromara.sms4j.provider.config.BaseConfig;
|
||||
|
||||
/**
|
||||
* 联麓短信:
|
||||
* <a href="https://console.shlianlu.com/#/document/smsDoc">官方文档</a>
|
||||
* <a href=Constant.HTTPS_PREFIX + "console.shlianlu.com/#/document/smsDoc">官方文档</a>
|
||||
*
|
||||
* @author lym
|
||||
*/
|
||||
@ -35,7 +36,7 @@ public class LianLuConfig extends BaseConfig {
|
||||
*/
|
||||
private String signType = LianLuUtils.SIGN_TYPE_MD5;
|
||||
|
||||
private String requestUrl = "https://apis.shlianlu.com/sms/trade";
|
||||
private String requestUrl = Constant.HTTPS_PREFIX + "apis.shlianlu.com/sms/trade";
|
||||
|
||||
@Override
|
||||
public String getSupplier() {
|
||||
|
||||
@ -4,6 +4,9 @@ import org.dromara.sms4j.comm.constant.SupplierConstant;
|
||||
import org.dromara.sms4j.lianlu.service.LianLuSmsImpl;
|
||||
import org.dromara.sms4j.provider.factory.BaseProviderFactory;
|
||||
|
||||
/**
|
||||
* 联鹿短信
|
||||
* */
|
||||
public class LianLuFactory implements BaseProviderFactory<LianLuSmsImpl, LianLuConfig> {
|
||||
private static final LianLuFactory INSTANCE = new LianLuFactory();
|
||||
|
||||
|
||||
@ -3,6 +3,8 @@ package org.dromara.sms4j.lianlu.service;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.sms4j.api.entity.SmsResponse;
|
||||
import org.dromara.sms4j.api.utils.SmsRespUtils;
|
||||
import org.dromara.sms4j.comm.constant.Constant;
|
||||
import org.dromara.sms4j.comm.constant.SupplierConstant;
|
||||
import org.dromara.sms4j.comm.delayedTime.DelayedTime;
|
||||
import org.dromara.sms4j.comm.exception.SmsBlendException;
|
||||
@ -177,8 +179,8 @@ public class LianLuSmsImpl extends AbstractSmsBlend<LianLuConfig> {
|
||||
|
||||
try {
|
||||
Map<String, String> headers = new HashMap<>(2);
|
||||
headers.put("Content-Type", "application/json;charset=utf-8");
|
||||
headers.put("Accept", "application/json");
|
||||
headers.put(Constant.CONTENT_TYPE, Constant.APPLICATION_JSON_UTF8);
|
||||
headers.put(Constant.ACCEPT, Constant.APPLICATION_JSON);
|
||||
SmsResponse smsResponse = this.getResponse(this.http.postJson(reqUrl, headers, requestBody));
|
||||
if (!smsResponse.isSuccess() && this.retry != this.getConfig().getMaxRetries()) {
|
||||
return this.requestRetry(req);
|
||||
@ -199,10 +201,6 @@ public class LianLuSmsImpl extends AbstractSmsBlend<LianLuConfig> {
|
||||
}
|
||||
|
||||
private SmsResponse getResponse(JSONObject resJson) {
|
||||
SmsResponse smsResponse = new SmsResponse();
|
||||
smsResponse.setSuccess("00".equals(resJson.getStr("status")));
|
||||
smsResponse.setData(resJson);
|
||||
smsResponse.setConfigId(this.getConfigId());
|
||||
return smsResponse;
|
||||
return SmsRespUtils.resp(resJson, "00".equals(resJson.getStr("status")), getConfigId());
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,145 @@
|
||||
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.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
package org.dromara.sms4j.luosimao.utils;
|
||||
|
||||
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", phone);
|
||||
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.joinComma(phones));
|
||||
body.put("message", message);
|
||||
if (date != null) {
|
||||
body.put("time", SmsDateUtils.normDatetimeGmt8(date));
|
||||
}
|
||||
return body;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,120 @@
|
||||
package org.dromara.sms4j.mas.service;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
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 cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* <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.values()), 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.values());
|
||||
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.values());
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,75 @@
|
||||
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());
|
||||
}else {
|
||||
map.put("addSerial", "");
|
||||
}
|
||||
|
||||
map.put("mac", DigestUtil.md5Hex(sb.toString(), StandardCharsets.UTF_8));
|
||||
return Base64.encode(JSONUtil.toJsonStr(map), StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
@ -2,6 +2,7 @@ package org.dromara.sms4j.netease.config;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.dromara.sms4j.comm.constant.Constant;
|
||||
import org.dromara.sms4j.comm.constant.SupplierConstant;
|
||||
import org.dromara.sms4j.provider.config.BaseConfig;
|
||||
|
||||
@ -21,18 +22,17 @@ public class NeteaseConfig extends BaseConfig {
|
||||
/**
|
||||
* 模板短信请求地址
|
||||
*/
|
||||
private String templateUrl = "https://api.netease.im/sms/sendtemplate.action";
|
||||
|
||||
private String templateUrl = Constant.HTTPS_PREFIX + "api.netease.im/sms/sendtemplate.action";
|
||||
|
||||
/**
|
||||
* 验证码短信请求地址
|
||||
*/
|
||||
private String codeUrl = "https://api.netease.im/sms/sendcode.action";
|
||||
private String codeUrl = Constant.HTTPS_PREFIX + "api.netease.im/sms/sendcode.action";
|
||||
|
||||
/**
|
||||
* 验证码验证请求地址
|
||||
*/
|
||||
private String verifyUrl = "https://api.netease.im/sms/verifycode.action";
|
||||
private String verifyUrl = Constant.HTTPS_PREFIX + "api.netease.im/sms/verifycode.action";
|
||||
|
||||
/**
|
||||
* 是否需要支持短信上行。true:需要,false:不需要
|
||||
|
||||
@ -9,6 +9,7 @@ import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.sms4j.api.entity.SmsResponse;
|
||||
import org.dromara.sms4j.api.utils.SmsRespUtils;
|
||||
import org.dromara.sms4j.comm.constant.Constant;
|
||||
import org.dromara.sms4j.comm.constant.SupplierConstant;
|
||||
import org.dromara.sms4j.comm.delayedTime.DelayedTime;
|
||||
@ -97,7 +98,7 @@ public class NeteaseSmsImpl extends AbstractSmsBlend<NeteaseConfig> {
|
||||
throw new SmsBlendException("单次发送超过最大发送上限,建议每次群发短信人数低于100");
|
||||
}
|
||||
Optional.ofNullable(getConfig().getTemplateId()).orElseThrow(() -> new SmsBlendException("模板ID不能为空"));
|
||||
return getSmsResponse(getConfig().getTemplateUrl(), phones, getConfig().getTemplateId(), message);
|
||||
return getSmsResponse(getConfig().getTemplateUrl(), phones,message, getConfig().getTemplateId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -133,7 +134,7 @@ public class NeteaseSmsImpl extends AbstractSmsBlend<NeteaseConfig> {
|
||||
body.put("needUp", getConfig().getNeedUp());
|
||||
|
||||
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("Nonce", nonce);
|
||||
headers.put("CurTime", curTime);
|
||||
@ -142,11 +143,9 @@ public class NeteaseSmsImpl extends AbstractSmsBlend<NeteaseConfig> {
|
||||
try {
|
||||
smsResponse = getResponse(http.postFrom(requestUrl, headers, body));
|
||||
} catch (SmsBlendException e) {
|
||||
smsResponse = new SmsResponse();
|
||||
smsResponse.setSuccess(false);
|
||||
smsResponse.setData(e.getMessage());
|
||||
smsResponse = errorResp(e.message);
|
||||
}
|
||||
if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) {
|
||||
if (smsResponse.isSuccess() || retry >= getConfig().getMaxRetries()) {
|
||||
retry = 0;
|
||||
return smsResponse;
|
||||
}
|
||||
@ -161,11 +160,7 @@ public class NeteaseSmsImpl extends AbstractSmsBlend<NeteaseConfig> {
|
||||
}
|
||||
|
||||
private SmsResponse getResponse(JSONObject jsonObject) {
|
||||
SmsResponse smsResponse = new SmsResponse();
|
||||
smsResponse.setSuccess(jsonObject.getInt("code") <= 200);
|
||||
smsResponse.setData(jsonObject);
|
||||
smsResponse.setConfigId(getConfigId());
|
||||
return smsResponse;
|
||||
return SmsRespUtils.resp(jsonObject, jsonObject.getInt("code") <= 200, getConfigId());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package org.dromara.sms4j.provider.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.dromara.sms4j.api.universal.ProxyConfig;
|
||||
import org.dromara.sms4j.api.universal.SupplierConfig;
|
||||
import org.dromara.sms4j.comm.exception.SmsBlendException;
|
||||
|
||||
@ -22,6 +23,7 @@ public abstract class BaseConfig implements SupplierConfig {
|
||||
* Access Key
|
||||
*/
|
||||
private String accessKeyId;
|
||||
|
||||
/**
|
||||
* Sdk App Id
|
||||
*/
|
||||
@ -55,6 +57,12 @@ public abstract class BaseConfig implements SupplierConfig {
|
||||
*/
|
||||
private String configId;
|
||||
|
||||
/**
|
||||
* 代理配置
|
||||
*
|
||||
*/
|
||||
private ProxyConfig proxy;
|
||||
|
||||
/**
|
||||
* 重试间隔(单位:秒),默认为5秒
|
||||
*/
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
package org.dromara.sms4j.provider.config;
|
||||
|
||||
public class SmsBanner {
|
||||
private static final String banner =
|
||||
private static final String BANNER =
|
||||
" ________ _____ ______ ________ ___ ___ ___ \n" +
|
||||
"|\\ ____\\|\\ _ \\ _ \\|\\ ____\\|\\ \\ |\\ \\ |\\ \\ \n" +
|
||||
"\\ \\ \\___|\\ \\ \\\\\\__\\ \\ \\ \\ \\___|\\ \\ \\\\_\\ \\ \\ \\ \\ \n" +
|
||||
@ -12,6 +12,6 @@ public class SmsBanner {
|
||||
" \\|_________| \\|_________| \n";
|
||||
/** 初始化配置文件时打印banner*/
|
||||
public static void PrintBanner(String version) {
|
||||
System.out.println(banner+version);
|
||||
System.out.println(BANNER +version);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ package org.dromara.sms4j.provider.config;
|
||||
|
||||
|
||||
import lombok.Data;
|
||||
import org.dromara.sms4j.comm.enumerate.ConfigType;
|
||||
import org.dromara.sms4j.comm.enums.ConfigType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
|
||||
@ -17,13 +17,13 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
*/
|
||||
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) {
|
||||
if(factory == null) {
|
||||
throw new SmsBlendException("注册供应商工厂失败,工厂实例不能为空");
|
||||
}
|
||||
factories.put(factory.getSupplier(), factory);
|
||||
FACTORIES.put(factory.getSupplier(), factory);
|
||||
}
|
||||
|
||||
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) {
|
||||
return factories.getOrDefault(supplier, null);
|
||||
return FACTORIES.getOrDefault(supplier, null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -5,7 +5,9 @@ import lombok.Getter;
|
||||
import org.dromara.sms4j.api.SmsBlend;
|
||||
import org.dromara.sms4j.api.callback.CallBack;
|
||||
import org.dromara.sms4j.api.entity.SmsResponse;
|
||||
import org.dromara.sms4j.api.universal.ProxyConfig;
|
||||
import org.dromara.sms4j.api.universal.SupplierConfig;
|
||||
import org.dromara.sms4j.api.utils.SmsRespUtils;
|
||||
import org.dromara.sms4j.comm.delayedTime.DelayedTime;
|
||||
import org.dromara.sms4j.comm.utils.SmsHttpUtils;
|
||||
import org.dromara.sms4j.provider.factory.BeanFactory;
|
||||
@ -31,13 +33,19 @@ public abstract class AbstractSmsBlend<C extends SupplierConfig> implements SmsB
|
||||
|
||||
protected final DelayedTime delayed;
|
||||
|
||||
protected final SmsHttpUtils http = SmsHttpUtils.instance();
|
||||
protected final SmsHttpUtils http;
|
||||
|
||||
protected AbstractSmsBlend(C config, Executor pool, DelayedTime delayed) {
|
||||
this.configId = StrUtil.isEmpty(config.getConfigId()) ? getSupplier() : config.getConfigId();
|
||||
this.config = config;
|
||||
this.pool = pool;
|
||||
this.delayed = delayed;
|
||||
ProxyConfig proxy = config.getProxy();
|
||||
if (proxy != null && proxy.getEnable()){
|
||||
this.http = SmsHttpUtils.instance(proxy.getHost(), proxy.getPort());
|
||||
}else {
|
||||
this.http = SmsHttpUtils.instance();
|
||||
}
|
||||
}
|
||||
|
||||
protected AbstractSmsBlend(C config) {
|
||||
@ -45,6 +53,12 @@ public abstract class AbstractSmsBlend<C extends SupplierConfig> implements SmsB
|
||||
this.config = config;
|
||||
this.pool = BeanFactory.getExecutor();
|
||||
this.delayed = BeanFactory.getDelayedTime();
|
||||
ProxyConfig proxy = config.getProxy();
|
||||
if (proxy != null && proxy.getEnable()){
|
||||
this.http = SmsHttpUtils.instance(proxy.getHost(), proxy.getPort());
|
||||
}else {
|
||||
this.http = SmsHttpUtils.instance();
|
||||
}
|
||||
}
|
||||
|
||||
protected C getConfig() {
|
||||
@ -62,7 +76,6 @@ public abstract class AbstractSmsBlend<C extends SupplierConfig> implements SmsB
|
||||
* message 消息内容
|
||||
* @author :Wind
|
||||
*/
|
||||
|
||||
@Override
|
||||
public abstract SmsResponse sendMessage(String phone, String message);
|
||||
|
||||
@ -84,7 +97,6 @@ public abstract class AbstractSmsBlend<C extends SupplierConfig> implements SmsB
|
||||
* @param messages key为模板变量名称 value为模板变量值
|
||||
* @author :Wind
|
||||
*/
|
||||
|
||||
@Override
|
||||
public abstract SmsResponse sendMessage(String phone, String templateId, LinkedHashMap<String, String> messages);
|
||||
|
||||
@ -94,7 +106,6 @@ public abstract class AbstractSmsBlend<C extends SupplierConfig> implements SmsB
|
||||
*
|
||||
* @author :Wind
|
||||
*/
|
||||
|
||||
@Override
|
||||
public abstract SmsResponse massTexting(List<String> phones, String message);
|
||||
|
||||
@ -104,7 +115,6 @@ public abstract class AbstractSmsBlend<C extends SupplierConfig> implements SmsB
|
||||
*
|
||||
* @author :Wind
|
||||
*/
|
||||
|
||||
@Override
|
||||
public abstract SmsResponse massTexting(List<String> phones, String templateId, LinkedHashMap<String, String> messages);
|
||||
|
||||
@ -145,7 +155,6 @@ public abstract class AbstractSmsBlend<C extends SupplierConfig> implements SmsB
|
||||
* @param callBack 回调
|
||||
* @author :Wind
|
||||
*/
|
||||
|
||||
@Override
|
||||
public final void sendMessageAsync(String phone, String templateId, LinkedHashMap<String, String> messages, CallBack callBack){
|
||||
CompletableFuture<SmsResponse> smsResponseCompletableFuture = CompletableFuture.supplyAsync(() -> sendMessage(phone,templateId, messages), pool);
|
||||
@ -240,4 +249,13 @@ public abstract class AbstractSmsBlend<C extends SupplierConfig> implements SmsB
|
||||
}
|
||||
}, delayedTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回异常
|
||||
* @param errorMsg 异常信息
|
||||
* @return SmsResponse
|
||||
*/
|
||||
public SmsResponse errorResp(String errorMsg){
|
||||
return SmsRespUtils.error(errorMsg, config.getConfigId());
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,11 +2,12 @@ package org.dromara.sms4j.qiniu.config;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.dromara.sms4j.comm.constant.Constant;
|
||||
import org.dromara.sms4j.comm.constant.SupplierConstant;
|
||||
import org.dromara.sms4j.provider.config.BaseConfig;
|
||||
|
||||
/**
|
||||
* @author Administrator
|
||||
* @author YYM
|
||||
* @Date: 2024/1/30 15:56 30
|
||||
* @描述: QiNiuConfig
|
||||
**/
|
||||
@ -18,7 +19,7 @@ public class QiNiuConfig extends BaseConfig {
|
||||
/**
|
||||
* 请求地址
|
||||
*/
|
||||
private String baseUrl = "https://sms.qiniuapi.com";
|
||||
private String baseUrl = Constant.HTTPS_PREFIX + "sms.qiniuapi.com";
|
||||
|
||||
/**
|
||||
* 模板变量名称
|
||||
|
||||
@ -7,7 +7,7 @@ import org.dromara.sms4j.provider.factory.AbstractProviderFactory;
|
||||
import org.dromara.sms4j.qiniu.service.QiNiuSmsImpl;
|
||||
|
||||
/**
|
||||
* @author Administrator
|
||||
* @author YYM
|
||||
* @Date: 2024/1/30 16:06 29
|
||||
* @描述: QiNiuFactory
|
||||
**/
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
package org.dromara.sms4j.qiniu.service;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.sms4j.api.entity.SmsResponse;
|
||||
import org.dromara.sms4j.api.utils.SmsRespUtils;
|
||||
import org.dromara.sms4j.comm.constant.SupplierConstant;
|
||||
import org.dromara.sms4j.comm.delayedTime.DelayedTime;
|
||||
import org.dromara.sms4j.comm.exception.SmsBlendException;
|
||||
import org.dromara.sms4j.comm.utils.SmsUtils;
|
||||
import org.dromara.sms4j.provider.service.AbstractSmsBlend;
|
||||
import org.dromara.sms4j.qiniu.config.QiNiuConfig;
|
||||
import org.dromara.sms4j.qiniu.util.QiNiuUtils;
|
||||
@ -17,7 +19,7 @@ import java.util.Objects;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* @author Administrator
|
||||
* @author YYM
|
||||
* @Date: 2024/1/30 16:06 59
|
||||
* @描述: QiNiuSmsImpl
|
||||
**/
|
||||
@ -71,7 +73,6 @@ public class QiNiuSmsImpl extends AbstractSmsBlend<QiNiuConfig> {
|
||||
return senMassMsg(phones, templateId, messages);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return SmsResponse
|
||||
* @author 初拥。
|
||||
@ -79,12 +80,15 @@ public class QiNiuSmsImpl extends AbstractSmsBlend<QiNiuConfig> {
|
||||
* @Description: 统一处理返回结果
|
||||
*/
|
||||
public SmsResponse handleRes(String url, HashMap<String, Object> params) {
|
||||
JSONObject jsonObject = http.postJson(url, QiNiuUtils.getHeaderAndSign(url, params, getConfig()), params);
|
||||
SmsResponse smsResponse = new SmsResponse();
|
||||
smsResponse.setSuccess(ObjectUtil.isEmpty(jsonObject.getStr("error")));
|
||||
smsResponse.setData(jsonObject);
|
||||
smsResponse.setConfigId(getConfigId());
|
||||
if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) {
|
||||
JSONObject jsonObject;
|
||||
SmsResponse smsResponse;
|
||||
try {
|
||||
jsonObject = http.postJson(url, QiNiuUtils.getHeaderAndSign(url, params, getConfig()), params);
|
||||
smsResponse = SmsRespUtils.resp(jsonObject, SmsUtils.isEmpty(jsonObject.getStr("error")), getConfigId());
|
||||
}catch (SmsBlendException e){
|
||||
smsResponse = errorResp(e.message);
|
||||
}
|
||||
if (smsResponse.isSuccess() || retry >= getConfig().getMaxRetries()) {
|
||||
retry = 0;
|
||||
return smsResponse;
|
||||
}
|
||||
|
||||
@ -8,19 +8,18 @@ import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.sms4j.comm.constant.Constant;
|
||||
import org.dromara.sms4j.comm.exception.SmsBlendException;
|
||||
import org.dromara.sms4j.comm.utils.SmsDateUtils;
|
||||
import org.dromara.sms4j.qiniu.config.QiNiuConfig;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* @author Administrator
|
||||
* @author YYM
|
||||
* @Date: 2024/1/30 16:37 50
|
||||
* @描述: QiNiuUtils
|
||||
**/
|
||||
@ -35,7 +34,7 @@ public class QiNiuUtils {
|
||||
StringBuilder dataToSign = new StringBuilder();
|
||||
dataToSign.append(method.toUpperCase()).append(" ").append(reqUrl.getPath());
|
||||
dataToSign.append("\nHost: ").append(reqUrl.getHost());
|
||||
dataToSign.append("\n").append("Content-Type").append(": ").append(Constant.ACCEPT);
|
||||
dataToSign.append("\n").append(Constant.CONTENT_TYPE).append(": ").append(Constant.APPLICATION_JSON);
|
||||
dataToSign.append("\n").append("X-Qiniu-Date").append(": ").append(signDate);
|
||||
dataToSign.append("\n\n");
|
||||
if (ObjectUtil.isNotEmpty(body)) {
|
||||
@ -50,9 +49,7 @@ public class QiNiuUtils {
|
||||
|
||||
public static Map<String, String> getHeaderAndSign(String url, HashMap<String, Object> hashMap, QiNiuConfig qiNiuConfig) {
|
||||
String signature;
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
|
||||
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||
String signDate = dateFormat.format(new Date());
|
||||
String signDate = SmsDateUtils.pureDateUtcGmt(new Date());
|
||||
try {
|
||||
signature = getSignature("POST", url, qiNiuConfig, JSONUtil.toJsonStr(hashMap), signDate);
|
||||
} catch (Exception e) {
|
||||
@ -62,9 +59,9 @@ public class QiNiuUtils {
|
||||
|
||||
//请求头
|
||||
Map<String, String> header = new HashMap<>(3);
|
||||
header.put("Authorization", signature);
|
||||
header.put(Constant.AUTHORIZATION, signature);
|
||||
header.put("X-Qiniu-Date", signDate);
|
||||
header.put("Content-Type", "application/json");
|
||||
header.put(Constant.CONTENT_TYPE, "application/json");
|
||||
return header;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,368 @@
|
||||
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.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
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 - 短信模板一对多发送
|
||||
* 建议: 单线程提交数量控制在50—200个联系人, 可以开多个线程同时发送
|
||||
* @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("timestamp");
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,10 +1,12 @@
|
||||
package org.dromara.sms4j.tencent.service;
|
||||
|
||||
import cn.hutool.core.collection.ListUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONArray;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.sms4j.api.entity.SmsResponse;
|
||||
import org.dromara.sms4j.api.utils.SmsRespUtils;
|
||||
import org.dromara.sms4j.comm.constant.Constant;
|
||||
import org.dromara.sms4j.comm.constant.SupplierConstant;
|
||||
import org.dromara.sms4j.comm.delayedTime.DelayedTime;
|
||||
@ -15,10 +17,10 @@ import org.dromara.sms4j.tencent.config.TencentConfig;
|
||||
import org.dromara.sms4j.tencent.utils.TencentUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
@ -44,12 +46,7 @@ public class TencentSmsImpl extends AbstractSmsBlend<TencentConfig> {
|
||||
|
||||
@Override
|
||||
public SmsResponse sendMessage(String phone, String message) {
|
||||
String[] split = message.split("&");
|
||||
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);
|
||||
return sendMessage(phone, getConfig().getTemplateId(), SmsUtils.buildMessageByAmpersand(message));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -59,38 +56,32 @@ public class TencentSmsImpl extends AbstractSmsBlend<TencentConfig> {
|
||||
|
||||
@Override
|
||||
public SmsResponse sendMessage(String phone, String templateId, LinkedHashMap<String, String> messages) {
|
||||
if (Objects.isNull(messages)){
|
||||
messages = new LinkedHashMap<>();
|
||||
// 如果包含 - 的话,则认为是国际短信 ,不进行+86拼接
|
||||
if (phone.contains("-")) {
|
||||
String result = phone.replace("-", "");
|
||||
return getSmsResponse(new String[]{result}, SmsUtils.toArray(messages), templateId);
|
||||
} else {
|
||||
return getSmsResponse(new String[]{StrUtil.addPrefixIfNot(phone, "+86")}, SmsUtils.toArray(messages), templateId);
|
||||
}
|
||||
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
|
||||
public SmsResponse massTexting(List<String> phones, String message) {
|
||||
String[] split = message.split("&");
|
||||
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);
|
||||
return massTexting(phones, getConfig().getTemplateId(), SmsUtils.buildMessageByAmpersand(message));
|
||||
}
|
||||
|
||||
@Override
|
||||
public SmsResponse massTexting(List<String> phones, String templateId, LinkedHashMap<String, String> messages) {
|
||||
if (Objects.isNull(messages)){
|
||||
messages = new LinkedHashMap<>();
|
||||
}
|
||||
List<String> list = new ArrayList<>();
|
||||
for (Map.Entry<String, String> entry : messages.entrySet()) {
|
||||
list.add(entry.getValue());
|
||||
for (String phone : phones) {
|
||||
if (phone.contains("-")) {
|
||||
String result = phone.replace("-", "");
|
||||
list.add(result);
|
||||
} else {
|
||||
list.add(StrUtil.addPrefixIfNot(phone, "+86"));
|
||||
}
|
||||
String[] s = new String[list.size()];
|
||||
return getSmsResponse(SmsUtils.listToArray(phones), list.toArray(s), templateId);
|
||||
}
|
||||
return getSmsResponse(list.toArray(new String[0]), SmsUtils.toArray(messages), templateId);
|
||||
}
|
||||
|
||||
private SmsResponse getSmsResponse(String[] phones, String[] messages, String templateId) {
|
||||
@ -112,11 +103,9 @@ public class TencentSmsImpl extends AbstractSmsBlend<TencentConfig> {
|
||||
try {
|
||||
smsResponse = getResponse(http.postJson(url, headsMap, requestBody));
|
||||
} catch (SmsBlendException e) {
|
||||
smsResponse = new SmsResponse();
|
||||
smsResponse.setSuccess(false);
|
||||
smsResponse.setData(e.getMessage());
|
||||
smsResponse = errorResp(e.message);
|
||||
}
|
||||
if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) {
|
||||
if (smsResponse.isSuccess() || retry >= getConfig().getMaxRetries()) {
|
||||
retry = 0;
|
||||
return smsResponse;
|
||||
}
|
||||
@ -131,15 +120,12 @@ public class TencentSmsImpl extends AbstractSmsBlend<TencentConfig> {
|
||||
}
|
||||
|
||||
private SmsResponse getResponse(JSONObject resJson) {
|
||||
SmsResponse smsResponse = new SmsResponse();
|
||||
JSONObject response = resJson.getJSONObject("Response");
|
||||
// 根据 Error 判断是否配置错误
|
||||
String error = response.getStr("Error");
|
||||
smsResponse.setSuccess(StrUtil.isBlank(error));
|
||||
boolean success = StrUtil.isBlank(response.getStr("Error"));
|
||||
// 根据 SendStatusSet 判断是否不为Ok
|
||||
JSONArray sendStatusSet = response.getJSONArray("SendStatusSet");
|
||||
if (sendStatusSet != null) {
|
||||
boolean success = true;
|
||||
for (Object obj : sendStatusSet) {
|
||||
JSONObject jsonObject = (JSONObject) obj;
|
||||
String code = jsonObject.getStr("Code");
|
||||
@ -148,11 +134,8 @@ public class TencentSmsImpl extends AbstractSmsBlend<TencentConfig> {
|
||||
break;
|
||||
}
|
||||
}
|
||||
smsResponse.setSuccess(success);
|
||||
}
|
||||
smsResponse.setData(resJson);
|
||||
smsResponse.setConfigId(getConfigId());
|
||||
return smsResponse;
|
||||
return SmsRespUtils.resp(resJson, success, getConfigId());
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,20 +1,19 @@
|
||||
package org.dromara.sms4j.tencent.utils;
|
||||
|
||||
import cn.hutool.crypto.digest.DigestUtil;
|
||||
import cn.hutool.crypto.digest.HMac;
|
||||
import cn.hutool.crypto.digest.HmacAlgorithm;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.sms4j.comm.constant.Constant;
|
||||
import org.dromara.sms4j.comm.utils.SmsDateUtils;
|
||||
import org.dromara.sms4j.tencent.config.TencentConfig;
|
||||
|
||||
import javax.xml.bind.DatatypeConverter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.TimeZone;
|
||||
|
||||
|
||||
/**
|
||||
* @author Richard
|
||||
@ -31,18 +30,14 @@ public class TencentUtils {
|
||||
*/
|
||||
private static final String HTTP_REQUEST_METHOD = "POST";
|
||||
|
||||
private static final String CT_JSON = "application/json; charset=utf-8";
|
||||
|
||||
|
||||
private static byte[] hmac256(byte[] key, String msg) {
|
||||
HMac hMac = new HMac(HmacAlgorithm.HmacSHA256, key);
|
||||
return hMac.digest(msg.getBytes(StandardCharsets.UTF_8));
|
||||
return hMac.digest(msg);
|
||||
}
|
||||
|
||||
private static String sha256Hex(String s) throws Exception {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
byte[] d = md.digest(s.getBytes(StandardCharsets.UTF_8));
|
||||
return DatatypeConverter.printHexBinary(d).toLowerCase();
|
||||
return DatatypeConverter.printHexBinary(DigestUtil.sha256(s)).toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -52,13 +47,11 @@ public class TencentUtils {
|
||||
* @param messages 短信内容
|
||||
* @param phones 手机号
|
||||
* @param timestamp 时间戳
|
||||
* @throws Exception
|
||||
* @throws Exception Exception
|
||||
*/
|
||||
public static String generateSignature(TencentConfig tencentConfig, String templateId, String[] messages, String[] phones,
|
||||
String timestamp) throws Exception {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
||||
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
String date = sdf.format(new Date(Long.parseLong(timestamp + "000")));
|
||||
String date = SmsDateUtils.normDateGmt(new Date(Long.parseLong(timestamp + "000")));
|
||||
String canonicalUri = "/";
|
||||
String canonicalQueryString = "";
|
||||
String canonicalHeaders = "content-type:application/json; charset=utf-8\nhost:" + tencentConfig.getRequestUrl() + "\n";
|
||||
@ -95,8 +88,8 @@ public class TencentUtils {
|
||||
public static Map<String, String> generateHeadsMap(String authorization, String timestamp, String action,
|
||||
String version, String territory, String requestUrl) {
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
headers.put("Authorization", authorization);
|
||||
headers.put("Content-Type", CT_JSON);
|
||||
headers.put(Constant.AUTHORIZATION, authorization);
|
||||
headers.put(Constant.CONTENT_TYPE, Constant.APPLICATION_JSON_UTF8);
|
||||
headers.put("Host", requestUrl);
|
||||
headers.put("X-TC-Action", action);
|
||||
headers.put("X-TC-Timestamp", timestamp);
|
||||
@ -113,7 +106,7 @@ public class TencentUtils {
|
||||
* @param signatureName 短信签名
|
||||
* @param templateId 模板id
|
||||
* @param templateParamSet 模板参数
|
||||
* @return
|
||||
* @return Map
|
||||
*/
|
||||
public static Map<String, Object> generateRequestBody(String[] phones, String sdkAppId, String signatureName,
|
||||
String templateId, String[] templateParamSet) {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package org.dromara.sms4j.unisms.core;
|
||||
|
||||
import org.dromara.sms4j.comm.constant.Constant;
|
||||
|
||||
/**
|
||||
* 初始化统一环境的单例类.
|
||||
@ -8,8 +9,8 @@ public class Uni {
|
||||
/** 模仿SDK版本*/
|
||||
public static final String VERSION = "0.0.4";
|
||||
|
||||
public static final String signingAlgorithm = "hmac-sha256";
|
||||
public static String endpoint = System.getenv().getOrDefault("UNI_ENDPOINT", "https://uni.apistd.com");
|
||||
public static final String SIGNING_ALGORITHM = "hmac-sha256";
|
||||
public static String endpoint = System.getenv().getOrDefault("UNI_ENDPOINT", Constant.HTTPS_PREFIX + "uni.apistd.com");
|
||||
public static String accessKeyId = System.getenv("UNI_ACCESS_KEY_ID");
|
||||
|
||||
private static String accessKeySecret = System.getenv("UNI_ACCESS_KEY_SECRET");
|
||||
@ -86,7 +87,7 @@ public class Uni {
|
||||
builder.isSimple(false);
|
||||
}
|
||||
builder.endpoint(Uni.endpoint);
|
||||
builder.signingAlgorithm(Uni.signingAlgorithm);
|
||||
builder.signingAlgorithm(Uni.SIGNING_ALGORITHM);
|
||||
builder.setRetryInterval(retryInterval);
|
||||
builder.setMaxRetries(maxRetries);
|
||||
return builder.build();
|
||||
|
||||
@ -85,8 +85,8 @@ public class UniClient {
|
||||
public UniResponse request(final String action, final Map<String, Object> data) throws SmsBlendException {
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
headers.put("User-Agent", USER_AGENT);
|
||||
headers.put("Content-Type", Constant.APPLICATION_JSON_UTF8);
|
||||
headers.put("Accept", Constant.ACCEPT);
|
||||
headers.put(Constant.CONTENT_TYPE, Constant.APPLICATION_JSON_UTF8);
|
||||
headers.put(Constant.ACCEPT, Constant.APPLICATION_JSON);
|
||||
String url;
|
||||
if (this.isSimple) {
|
||||
url = this.endpoint + "?action=" + action + "&accessKeyId=" + this.accessKeyId;
|
||||
|
||||
@ -6,7 +6,6 @@ import org.dromara.sms4j.comm.exception.SmsBlendException;
|
||||
import java.util.Objects;
|
||||
|
||||
public class UniResponse {
|
||||
public static final String REQUEST_ID_HEADER_KEY = "x-uni-request-id";
|
||||
public String requestId;
|
||||
public String code;
|
||||
public String message;
|
||||
|
||||
@ -3,6 +3,7 @@ package org.dromara.sms4j.unisms.service;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.sms4j.api.entity.SmsResponse;
|
||||
import org.dromara.sms4j.api.utils.SmsRespUtils;
|
||||
import org.dromara.sms4j.comm.constant.SupplierConstant;
|
||||
import org.dromara.sms4j.comm.delayedTime.DelayedTime;
|
||||
import org.dromara.sms4j.comm.exception.SmsBlendException;
|
||||
@ -96,16 +97,12 @@ public class UniSmsImpl extends AbstractSmsBlend<UniConfig> {
|
||||
}
|
||||
|
||||
private SmsResponse getSmsResponse(Map<String, Object> data) {
|
||||
SmsResponse smsResponse = new SmsResponse();
|
||||
try {
|
||||
UniResponse send = Uni.getClient(getConfig().getRetryInterval(), getConfig().getMaxRetries()).request("sms.message.send", data);
|
||||
smsResponse.setSuccess("0".equals(send.code));
|
||||
smsResponse.setData(send);
|
||||
smsResponse.setConfigId(getConfigId());
|
||||
return SmsRespUtils.resp(send, "0".equals(send.code), getConfigId());
|
||||
} catch (Exception e) {
|
||||
smsResponse.setSuccess(false);
|
||||
return errorResp(e.getMessage());
|
||||
}
|
||||
return smsResponse;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user