add 新增天翼云通信

This commit is contained in:
bleachtred 2023-05-12 16:25:27 +08:00
parent 55ac38175e
commit 901327b8b6
7 changed files with 444 additions and 0 deletions

View File

@ -26,6 +26,8 @@ public enum SupplierType {
* 亿美软通
*/
EMAY("亿美软通"),
/** 天翼云 */
CTYUN("天翼云短信"),
;

View File

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

View File

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

View File

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

View File

@ -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() {
}
}

View File

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

View File

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