mirror of
https://gitee.com/dromara/sms4j.git
synced 2025-12-06 17:08:40 +08:00
add 新增天翼云通信
This commit is contained in:
parent
55ac38175e
commit
901327b8b6
@ -26,6 +26,8 @@ public enum SupplierType {
|
||||
* 亿美软通
|
||||
*/
|
||||
EMAY("亿美软通"),
|
||||
/** 天翼云 */
|
||||
CTYUN("天翼云短信"),
|
||||
;
|
||||
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ package org.dromara.sms4j.core.config;
|
||||
|
||||
import org.dromara.sms4j.comm.enumerate.SupplierType;
|
||||
import org.dromara.sms4j.core.factory.SmsFactory;
|
||||
import org.dromara.sms4j.ctyun.config.CtyunConfig;
|
||||
import org.dromara.sms4j.emay.config.EmayConfig;
|
||||
import org.dromara.sms4j.aliyun.config.AlibabaConfig;
|
||||
import org.dromara.sms4j.cloopen.config.CloopenConfig;
|
||||
@ -47,6 +48,11 @@ public class SupplierFactory {
|
||||
*/
|
||||
private static EmayConfig emayConfig;
|
||||
|
||||
/**
|
||||
* 天翼云短信差异配置
|
||||
*/
|
||||
private static CtyunConfig ctyunConfig;
|
||||
|
||||
/** 阿里云配置获取*/
|
||||
public static AlibabaConfig getAlibabaConfig() {
|
||||
if (alibabaConfig == null){
|
||||
@ -113,6 +119,16 @@ public class SupplierFactory {
|
||||
return emayConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 天翼云配置获取
|
||||
*/
|
||||
public static CtyunConfig getCtyunConfig() {
|
||||
if (ctyunConfig == null) {
|
||||
ctyunConfig = CtyunConfig.builder().build();
|
||||
}
|
||||
return ctyunConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 alibabaConfig
|
||||
*/
|
||||
@ -176,4 +192,12 @@ public class SupplierFactory {
|
||||
SupplierFactory.emayConfig = emayConfig;
|
||||
SmsFactory.refresh(SupplierType.EMAY);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 emayConfig
|
||||
*/
|
||||
public static void setCtyunConfig(CtyunConfig ctyunConfig) {
|
||||
SupplierFactory.ctyunConfig = ctyunConfig;
|
||||
SmsFactory.refresh(SupplierType.EMAY);
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ import org.dromara.sms4j.comm.enumerate.SupplierType;
|
||||
import org.dromara.sms4j.comm.exception.SmsBlendException;
|
||||
import org.dromara.sms4j.core.SupplierSqlConfig;
|
||||
import org.dromara.sms4j.core.config.SupplierFactory;
|
||||
import org.dromara.sms4j.ctyun.config.CtyunSmsConfig;
|
||||
import org.dromara.sms4j.emay.config.EmaySmsConfig;
|
||||
import org.dromara.sms4j.huawei.config.HuaweiSmsConfig;
|
||||
import org.dromara.sms4j.jdcloud.config.JdCloudSmsConfig;
|
||||
@ -49,6 +50,8 @@ public abstract class SmsFactory {
|
||||
return CloopenSmsConfig.createCloopenSms(SupplierFactory.getCloopenConfig());
|
||||
case EMAY:
|
||||
return EmaySmsConfig.createEmaySms(SupplierFactory.getEmayConfig());
|
||||
case CTYUN:
|
||||
return CtyunSmsConfig.createCtyunSms(SupplierFactory.getCtyunConfig());
|
||||
}
|
||||
throw new SmsBlendException("An attempt to construct a SmsBlend object failed. Please check that the enumeration is valid");
|
||||
}
|
||||
@ -68,6 +71,7 @@ public abstract class SmsFactory {
|
||||
JdCloudSmsConfig.refresh(SupplierFactory.getJdCloudConfig());
|
||||
CloopenSmsConfig.refresh(SupplierFactory.getCloopenConfig());
|
||||
EmaySmsConfig.refresh(SupplierFactory.getEmayConfig());
|
||||
CtyunSmsConfig.refresh(SupplierFactory.getCtyunConfig());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -100,6 +104,9 @@ public abstract class SmsFactory {
|
||||
case EMAY:
|
||||
EmaySmsConfig.refresh(SupplierFactory.getEmayConfig());
|
||||
break;
|
||||
case CTYUN:
|
||||
CtyunSmsConfig.refresh(SupplierFactory.getCtyunConfig());
|
||||
break;
|
||||
default:
|
||||
throw new SmsBlendException("An attempt to construct a SmsBlend object failed. Please check that the enumeration is valid");
|
||||
}
|
||||
|
||||
@ -0,0 +1,39 @@
|
||||
package org.dromara.sms4j.ctyun.config;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import org.dromara.sms4j.comm.config.BaseConfig;
|
||||
|
||||
/**
|
||||
* <p>类名: CtyunConfig
|
||||
* <p>说明: 天翼云短信差异配置
|
||||
*
|
||||
* @author :bleachhtred
|
||||
* 2023/5/12 15:06
|
||||
**/
|
||||
@Data
|
||||
@SuperBuilder
|
||||
@ToString(callSuper = true)
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class CtyunConfig extends BaseConfig {
|
||||
|
||||
/**
|
||||
* 模板变量名称
|
||||
*/
|
||||
private String templateName;
|
||||
|
||||
/**
|
||||
* 请求地址
|
||||
*/
|
||||
@Builder.Default
|
||||
private String requestUrl = "https://sms-global.ctapi.ctyun.cn/sms/api/v1";
|
||||
|
||||
/**
|
||||
* 接口名称
|
||||
*/
|
||||
@Builder.Default
|
||||
private String action = "SendSms";
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
package org.dromara.sms4j.ctyun.config;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.sms4j.comm.factory.BeanFactory;
|
||||
import org.dromara.sms4j.ctyun.service.CtyunSmsImpl;
|
||||
|
||||
/**
|
||||
* <p>类名: CtyunSmsConfig
|
||||
* <p>说明: 天翼云 云通信短信配置器
|
||||
*
|
||||
* @author :bleachhtred
|
||||
* 2023/5/12 15:06
|
||||
**/
|
||||
@Slf4j
|
||||
public class CtyunSmsConfig {
|
||||
|
||||
private static CtyunSmsImpl ctyunSms;
|
||||
|
||||
private static CtyunSmsConfig ctyunSmsConfig;
|
||||
|
||||
/**
|
||||
* getCtyunSms
|
||||
* <p> 建造一个短信实现对像
|
||||
*
|
||||
* @author :bleachhtred
|
||||
*/
|
||||
public static CtyunSmsImpl createCtyunSms(CtyunConfig alibabaConfig) {
|
||||
if (ctyunSmsConfig == null) {
|
||||
ctyunSmsConfig = new CtyunSmsConfig();
|
||||
}
|
||||
if (ctyunSms == null) {
|
||||
ctyunSms = new CtyunSmsImpl(
|
||||
alibabaConfig,
|
||||
BeanFactory.getExecutor(),
|
||||
BeanFactory.getDelayedTime());
|
||||
}
|
||||
return ctyunSms;
|
||||
}
|
||||
|
||||
/**
|
||||
* refresh
|
||||
* <p> 刷新对象
|
||||
*
|
||||
* @author :bleachhtred
|
||||
*/
|
||||
public static CtyunSmsImpl refresh(CtyunConfig alibabaConfig) {
|
||||
// 如果配置对象为空则创建一个
|
||||
if (ctyunSmsConfig == null) {
|
||||
ctyunSmsConfig = new CtyunSmsConfig();
|
||||
}
|
||||
//重新构造一个实现对象
|
||||
ctyunSms = new CtyunSmsImpl(
|
||||
alibabaConfig,
|
||||
BeanFactory.getExecutor(),
|
||||
BeanFactory.getDelayedTime());
|
||||
return ctyunSms;
|
||||
}
|
||||
|
||||
private CtyunSmsConfig() {
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,197 @@
|
||||
package org.dromara.sms4j.ctyun.service;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.dtflys.forest.config.ForestConfiguration;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.sms4j.api.SmsBlend;
|
||||
import org.dromara.sms4j.api.callback.CallBack;
|
||||
import org.dromara.sms4j.api.entity.SmsResponse;
|
||||
import org.dromara.sms4j.comm.annotation.Restricted;
|
||||
import org.dromara.sms4j.comm.delayedTime.DelayedTime;
|
||||
import org.dromara.sms4j.comm.exception.SmsBlendException;
|
||||
import org.dromara.sms4j.comm.factory.BeanFactory;
|
||||
import org.dromara.sms4j.ctyun.config.CtyunConfig;
|
||||
import org.dromara.sms4j.ctyun.utils.CtyunUtils;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* <p>类名: CtyunSmsImpl
|
||||
* <p>说明: 天翼云短信实现
|
||||
*
|
||||
* @author :bleachhtred
|
||||
* 2023/5/12 15:06
|
||||
**/
|
||||
@Slf4j
|
||||
public class CtyunSmsImpl implements SmsBlend {
|
||||
|
||||
private final CtyunConfig ctyunConfig;
|
||||
|
||||
private final Executor pool;
|
||||
|
||||
private final DelayedTime delayed;
|
||||
|
||||
private final ForestConfiguration http = BeanFactory.getForestConfiguration();
|
||||
|
||||
/**
|
||||
* AlibabaSmsImpl
|
||||
* <p>构造器,用于构造短信实现模块
|
||||
*
|
||||
* @author :Wind
|
||||
*/
|
||||
|
||||
public CtyunSmsImpl(CtyunConfig ctyunConfig, Executor pool, DelayedTime delayedTime) {
|
||||
this.ctyunConfig = ctyunConfig;
|
||||
this.pool = pool;
|
||||
this.delayed = delayedTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Restricted
|
||||
public SmsResponse sendMessage(String phone, String message) {
|
||||
LinkedHashMap<String, String> map = new LinkedHashMap<>();
|
||||
map.put(ctyunConfig.getTemplateName(), message);
|
||||
return sendMessage(phone, ctyunConfig.getTemplateId(), map);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Restricted
|
||||
public SmsResponse sendMessage(String phone, String templateId, LinkedHashMap<String, String> messages) {
|
||||
String messageStr = JSON.toJSONString(messages);
|
||||
return getSmsResponse(phone, messageStr, templateId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Restricted
|
||||
public SmsResponse massTexting(List<String> phones, String message) {
|
||||
LinkedHashMap<String, String> map = new LinkedHashMap<>();
|
||||
map.put(ctyunConfig.getTemplateName(), message);
|
||||
return massTexting(phones, ctyunConfig.getTemplateId(), map);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Restricted
|
||||
public SmsResponse massTexting(List<String> phones, String templateId, LinkedHashMap<String, String> messages) {
|
||||
String messageStr = JSON.toJSONString(messages);
|
||||
return getSmsResponse(arrayToString(phones), messageStr, templateId);
|
||||
}
|
||||
|
||||
private SmsResponse getSmsResponse(String phone, String message, String templateId) {
|
||||
AtomicReference<SmsResponse> smsResponse = new AtomicReference<>();
|
||||
String requestUrl;
|
||||
String paramStr;
|
||||
try {
|
||||
requestUrl = ctyunConfig.getRequestUrl();
|
||||
paramStr = CtyunUtils.generateParamJsonStr(ctyunConfig, phone, message, templateId);
|
||||
} catch (Exception e) {
|
||||
log.error("ctyun send message error", e);
|
||||
throw new SmsBlendException(e.getMessage());
|
||||
}
|
||||
log.debug("requestUrl {}", requestUrl);
|
||||
http.post(requestUrl)
|
||||
.addHeader(CtyunUtils.signHeader(paramStr, ctyunConfig.getAccessKeyId(), ctyunConfig.getAccessKeySecret()))
|
||||
.addBody(paramStr)
|
||||
.onSuccess(((data, req, res) -> {
|
||||
Map map = res.get(Map.class);
|
||||
smsResponse.set(getResponse(map));
|
||||
}))
|
||||
.onError((ex, req, res) -> {
|
||||
Map map = res.get(Map.class);
|
||||
smsResponse.set(getResponse(map));
|
||||
})
|
||||
.execute();
|
||||
return smsResponse.get();
|
||||
}
|
||||
|
||||
private static SmsResponse getResponse(Map map) {
|
||||
SmsResponse smsResponse = new SmsResponse();
|
||||
smsResponse.setCode((String) map.get("code"));
|
||||
smsResponse.setMessage((String) map.get("message"));
|
||||
smsResponse.setBizId((String) map.get("requestId"));
|
||||
return smsResponse;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Restricted
|
||||
public void sendMessageAsync(String phone, String message, CallBack callBack) {
|
||||
CompletableFuture<SmsResponse> smsResponseCompletableFuture = CompletableFuture.supplyAsync(() -> sendMessage(phone, message), pool);
|
||||
smsResponseCompletableFuture.thenAcceptAsync(callBack::callBack);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Restricted
|
||||
public void sendMessageAsync(String phone, String message) {
|
||||
pool.execute(() -> sendMessage(phone, message));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Restricted
|
||||
public void sendMessageAsync(String phone, String templateId, LinkedHashMap<String, String> messages, CallBack callBack) {
|
||||
CompletableFuture<SmsResponse> smsResponseCompletableFuture = CompletableFuture.supplyAsync(() -> sendMessage(phone,templateId, messages), pool);
|
||||
smsResponseCompletableFuture.thenAcceptAsync(callBack::callBack);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Restricted
|
||||
public void sendMessageAsync(String phone, String templateId, LinkedHashMap<String, String> messages) {
|
||||
pool.execute(() -> sendMessage(phone, templateId, messages));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Restricted
|
||||
public void delayedMessage(String phone, String message, Long delayedTime) {
|
||||
this.delayed.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
sendMessage(phone, message);
|
||||
}
|
||||
}, delayedTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Restricted
|
||||
public void delayedMessage(String phone, String templateId, LinkedHashMap<String, String> messages, Long delayedTime) {
|
||||
this.delayed.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
sendMessage(phone, templateId, messages);
|
||||
}
|
||||
}, delayedTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Restricted
|
||||
public void delayMassTexting(List<String> phones, String message, Long delayedTime) {
|
||||
this.delayed.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
massTexting(phones, message);
|
||||
}
|
||||
}, delayedTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Restricted
|
||||
public void delayMassTexting(List<String> phones, String templateId, LinkedHashMap<String, String> messages, Long delayedTime) {
|
||||
this.delayed.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
massTexting(phones, templateId, messages);
|
||||
}
|
||||
}, delayedTime);
|
||||
}
|
||||
|
||||
private String arrayToString(List<String> list) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String s : list) {
|
||||
sb.append(",").append("+86").append(s);
|
||||
}
|
||||
return sb.substring(1);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,114 @@
|
||||
package org.dromara.sms4j.ctyun.utils;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.dromara.sms4j.comm.exception.SmsBlendException;
|
||||
import org.dromara.sms4j.ctyun.config.CtyunConfig;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class CtyunUtils {
|
||||
|
||||
/**
|
||||
* 获取签名时间戳
|
||||
*/
|
||||
private static String signatureTime(){
|
||||
SimpleDateFormat timeFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
|
||||
return timeFormat.format(new Date());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取签名请求头
|
||||
*/
|
||||
public static Map<String, String> signHeader(String body, String key, String secret){
|
||||
Map<String, String> map = new ConcurrentHashMap<>();
|
||||
|
||||
// 构造时间戳
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
|
||||
Date now = new Date();
|
||||
String signatureDate = dateFormat.format(now);
|
||||
String signatureTime = signatureTime();
|
||||
// 构造请求流水号
|
||||
String uuid = UUID.randomUUID().toString();
|
||||
|
||||
String calculateContentHash = getSHA256(body);
|
||||
|
||||
byte[] kTime = hmacSHA256(signatureTime.getBytes(), secret.getBytes());
|
||||
byte[] kAk = hmacSHA256(key.getBytes(), kTime);
|
||||
byte[] kDate = hmacSHA256(signatureDate.getBytes(), kAk);
|
||||
|
||||
// 构造待签名字符串
|
||||
|
||||
// 报文原封不动进行sha256摘要
|
||||
String signatureStr = String.format("ctyun-eop-request-id:%s\neop-date:%s\n", uuid, signatureTime) + "\n\n" + calculateContentHash;
|
||||
// 构造签名
|
||||
String signature = Base64.getEncoder().encodeToString(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", "application/json;charset=UTF-8");
|
||||
map.put("ctyun-eop-request-id", uuid);
|
||||
map.put("Eop-date", signatureTime);
|
||||
map.put("Eop-Authorization", signHeader);
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成请求body参数
|
||||
*
|
||||
* @param ctyunConfig 配置数据
|
||||
* @param phone 手机号
|
||||
* @param message 短信内容
|
||||
* @param templateId 模板id
|
||||
*/
|
||||
public static String generateParamJsonStr(CtyunConfig ctyunConfig, String phone, String message, String templateId) {
|
||||
Map<String, String> paramMap = new HashMap<>();
|
||||
paramMap.put("action", ctyunConfig.getAction());
|
||||
paramMap.put("phoneNumber", phone);
|
||||
paramMap.put("signName", ctyunConfig.getSignature());
|
||||
paramMap.put("templateParam", message);
|
||||
paramMap.put("templateCode", templateId);
|
||||
return JSONObject.toJSONString(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;
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] hmacSHA256(byte[] data, byte[] key){
|
||||
try {
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(new SecretKeySpec(key, "HmacSHA256"));
|
||||
return mac.doFinal(data);
|
||||
} catch (Exception e) {
|
||||
throw new SmsBlendException(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user