diff --git a/.flattened-pom.xml b/.flattened-pom.xml index 64458d8e..5f584724 100644 --- a/.flattened-pom.xml +++ b/.flattened-pom.xml @@ -1,10 +1,10 @@ - 4.0.0 org.dromara.sms4j sms4j - 1.0.5 + 2.0.1 pom sms4j sms_aggregation @@ -55,16 +55,16 @@ - 0.0.4 - 3.14.9 - 2.0.23 2.0.15 - 1.5.30 - 2.7.10 5.8.16 + 3.14.9 3.1.622 - 1.0.5 + 2.0.1 + 0.0.4 + 2.7.10 + 1.5.30 1.3.3 + 2.0.23 @@ -83,67 +83,67 @@ org.dromara.sms4j sms4j-spring-boot-starter - 1.0.5 + 2.0.1 org.dromara.sms4j sms4j-autoimmit - 1.0.5 + 2.0.1 org.dromara.sms4j sms4j-core - 1.0.5 + 2.0.1 org.dromara.sms4j sms4j-comm - 1.0.5 + 2.0.1 org.dromara.sms4j sms4j-api - 1.0.5 + 2.0.1 org.dromara.sms4j sms4j-aliyun - 1.0.5 + 2.0.1 org.dromara.sms4j sms4j-tencent - 1.0.5 + 2.0.1 org.dromara.sms4j sms4j-unisms - 1.0.5 + 2.0.1 org.dromara.sms4j sms4j-yunpian - 1.0.5 + 2.0.1 org.dromara.sms4j sms4j-huawei - 1.0.5 + 2.0.1 org.dromara.sms4j sms4j-jdcloud - 1.0.5 + 2.0.1 org.dromara.sms4j sms4j-cloopen - 1.0.5 + 2.0.1 org.dromara.sms4j sms4j-emay - 1.0.5 + 2.0.1 com.aliyun @@ -268,10 +268,6 @@ UTF-8 - - maven-release-plugin - 2.5.1 - maven-jar-plugin 3.2.2 diff --git a/pom.xml b/pom.xml index 3dc8e43b..52d4258d 100644 --- a/pom.xml +++ b/pom.xml @@ -342,11 +342,11 @@ - - org.apache.maven.plugins - maven-release-plugin - 2.5.1 - + + + + + org.apache.maven.plugins maven-jar-plugin diff --git a/sms4j-aliyun/pom.xml b/sms4j-aliyun/pom.xml index 44e522af..7a32a122 100644 --- a/sms4j-aliyun/pom.xml +++ b/sms4j-aliyun/pom.xml @@ -18,18 +18,6 @@ - - - com.aliyun - dysmsapi20170525 - - - com.squareup.okhttp3 - okhttp - - - - org.dromara.sms4j sms4j-comm diff --git a/sms4j-aliyun/src/main/java/org/dromara/sms4j/aliyun/config/AlibabaConfig.java b/sms4j-aliyun/src/main/java/org/dromara/sms4j/aliyun/config/AlibabaConfig.java index 098b179e..912b4d16 100644 --- a/sms4j-aliyun/src/main/java/org/dromara/sms4j/aliyun/config/AlibabaConfig.java +++ b/sms4j-aliyun/src/main/java/org/dromara/sms4j/aliyun/config/AlibabaConfig.java @@ -1,26 +1,44 @@ -package org.dromara.sms4j.aliyun.config; - -import lombok.Builder; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; -import lombok.experimental.SuperBuilder; -import org.dromara.sms4j.comm.config.BaseConfig; - -@Data -@SuperBuilder -@ToString(callSuper = true) -@EqualsAndHashCode(callSuper = true) -public class AlibabaConfig extends BaseConfig { - - /** - * 模板变量名称 - */ - private String templateName; - - /** - * 请求地址 - */ - @Builder.Default - private String requestUrl = "dysmsapi.aliyuncs.com"; -} +package org.dromara.sms4j.aliyun.config; + +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import lombok.experimental.SuperBuilder; +import org.dromara.sms4j.comm.config.BaseConfig; + +@Data +@SuperBuilder +@ToString(callSuper = true) +@EqualsAndHashCode(callSuper = true) +public class AlibabaConfig extends BaseConfig { + + /** + * 模板变量名称 + */ + private String templateName; + + /** + * 请求地址 + */ + @Builder.Default + private String requestUrl = "dysmsapi.aliyuncs.com"; + + /** + * 接口名称 + */ + @Builder.Default + private String action = "SendSms"; + + /** + * 接口版本号 + */ + @Builder.Default + private String version = "2017-05-25"; + + /** + * 地域信息默认为 cn-hangzhou + */ + @Builder.Default + private String regionId = "cn-hangzhou"; +} diff --git a/sms4j-aliyun/src/main/java/org/dromara/sms4j/aliyun/config/AlibabaSmsConfig.java b/sms4j-aliyun/src/main/java/org/dromara/sms4j/aliyun/config/AlibabaSmsConfig.java index 3100cad2..813f158c 100644 --- a/sms4j-aliyun/src/main/java/org/dromara/sms4j/aliyun/config/AlibabaSmsConfig.java +++ b/sms4j-aliyun/src/main/java/org/dromara/sms4j/aliyun/config/AlibabaSmsConfig.java @@ -1,78 +1,62 @@ -package org.dromara.sms4j.aliyun.config; - -import com.aliyun.dysmsapi20170525.Client; -import com.aliyun.teaopenapi.models.Config; -import org.dromara.sms4j.aliyun.service.AlibabaSmsImpl; -import org.dromara.sms4j.comm.exception.SmsBlendException; -import org.dromara.sms4j.comm.factory.BeanFactory; -import lombok.extern.slf4j.Slf4j; - - -/** - * AlibabaSmsConfig - *

阿里巴巴对象建造者 - * @author :Wind - * 2023/4/8 14:54 - **/ -@Slf4j -public class AlibabaSmsConfig { - - private static AlibabaSmsImpl alibabaSms; - - private static AlibabaSmsConfig alibabaSmsConfig; - - private Client client(AlibabaConfig alibabaConfig){ - try { - Config config = new Config() - // AccessKey ID - .setAccessKeyId(alibabaConfig.getAccessKeyId()) - // AccessKey Secret - .setAccessKeySecret(alibabaConfig.getAccessKeySecret()); - // 访问的域名 - config.endpoint = alibabaConfig.getRequestUrl(); - return new Client(config); - }catch (Exception e){ - log.error(e.getMessage()); - throw new SmsBlendException(e.getMessage()); - } - } - - /** - * getAlibabaSms - *

建造一个短信实现对像 - * @author :Wind - */ - public static AlibabaSmsImpl createAlibabaSms(AlibabaConfig alibabaConfig) { - if (alibabaSmsConfig == null){ - alibabaSmsConfig = new AlibabaSmsConfig(); - } - if (alibabaSms == null){ - alibabaSms = new AlibabaSmsImpl(alibabaSmsConfig.client(alibabaConfig), - alibabaConfig, - BeanFactory.getExecutor(), - BeanFactory.getDelayedTime()); - } - return alibabaSms; - } - - /** - * refresh - *

刷新对象 - * @author :Wind - */ - public static AlibabaSmsImpl refresh(AlibabaConfig alibabaConfig){ - // 如果配置对象为空则创建一个 - if (alibabaSmsConfig == null){ - alibabaSmsConfig = new AlibabaSmsConfig(); - } - //重新构造一个实现对象 - alibabaSms= new AlibabaSmsImpl(alibabaSmsConfig.client(alibabaConfig), - alibabaConfig, - BeanFactory.getExecutor(), - BeanFactory.getDelayedTime()); - return alibabaSms; - } - - private AlibabaSmsConfig() { - } -} +package org.dromara.sms4j.aliyun.config; + +import lombok.extern.slf4j.Slf4j; +import org.dromara.sms4j.aliyun.service.AlibabaSmsImpl; +import org.dromara.sms4j.comm.factory.BeanFactory; + + +/** + * AlibabaSmsConfig + *

阿里巴巴对象建造者 + * + * @author :Wind + * 2023/4/8 14:54 + **/ +@Slf4j +public class AlibabaSmsConfig { + + private static AlibabaSmsImpl alibabaSms; + + private static AlibabaSmsConfig alibabaSmsConfig; + + /** + * getAlibabaSms + *

建造一个短信实现对像 + * + * @author :Wind + */ + public static AlibabaSmsImpl createAlibabaSms(AlibabaConfig alibabaConfig) { + if (alibabaSmsConfig == null) { + alibabaSmsConfig = new AlibabaSmsConfig(); + } + if (alibabaSms == null) { + alibabaSms = new AlibabaSmsImpl( + alibabaConfig, + BeanFactory.getExecutor(), + BeanFactory.getDelayedTime()); + } + return alibabaSms; + } + + /** + * refresh + *

刷新对象 + * + * @author :Wind + */ + public static AlibabaSmsImpl refresh(AlibabaConfig alibabaConfig) { + // 如果配置对象为空则创建一个 + if (alibabaSmsConfig == null) { + alibabaSmsConfig = new AlibabaSmsConfig(); + } + //重新构造一个实现对象 + alibabaSms = new AlibabaSmsImpl( + alibabaConfig, + BeanFactory.getExecutor(), + BeanFactory.getDelayedTime()); + return alibabaSms; + } + + private AlibabaSmsConfig() { + } +} diff --git a/sms4j-aliyun/src/main/java/org/dromara/sms4j/aliyun/service/AlibabaSmsImpl.java b/sms4j-aliyun/src/main/java/org/dromara/sms4j/aliyun/service/AlibabaSmsImpl.java index 1ff7405c..52a3dcaa 100644 --- a/sms4j-aliyun/src/main/java/org/dromara/sms4j/aliyun/service/AlibabaSmsImpl.java +++ b/sms4j-aliyun/src/main/java/org/dromara/sms4j/aliyun/service/AlibabaSmsImpl.java @@ -1,215 +1,196 @@ -package org.dromara.sms4j.aliyun.service; - -import com.alibaba.fastjson.JSONObject; -import com.aliyun.dysmsapi20170525.Client; -import com.aliyun.dysmsapi20170525.models.SendBatchSmsRequest; -import com.aliyun.dysmsapi20170525.models.SendBatchSmsResponse; -import com.aliyun.dysmsapi20170525.models.SendSmsRequest; -import com.aliyun.dysmsapi20170525.models.SendSmsResponse; -import com.aliyun.tea.TeaException; -import com.aliyun.teautil.models.RuntimeOptions; -import org.dromara.sms4j.aliyun.config.AlibabaConfig; -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.utils.http.HttpJsonTool; -import lombok.extern.slf4j.Slf4j; - -import java.util.LinkedHashMap; -import java.util.List; -import java.util.TimerTask; -import java.util.concurrent.Executor; - -/** - *

类名: AlibabaSmsImpl - *

说明: 阿里云短信实现 - * @author :Wind - * 2023/3/26 17:16 - **/ - -@Slf4j -public class AlibabaSmsImpl implements SmsBlend { - - private final Client client; - - private final AlibabaConfig alibabaSmsConfig; - - private final Executor pool; - - private final DelayedTime delayed; - - /** - * AlibabaSmsImpl - *

构造器,用于构造短信实现模块 - * @author :Wind - */ - - public AlibabaSmsImpl(Client client, AlibabaConfig alibabaSmsConfig,Executor pool,DelayedTime delayedTime){ - this.client = client; - this.alibabaSmsConfig = alibabaSmsConfig; - this.pool = pool; - this.delayed = delayedTime; - } - - @Override - @Restricted - public SmsResponse sendMessage(String phone, String message) { - LinkedHashMap map = new LinkedHashMap<>(); - map.put(alibabaSmsConfig.getTemplateName(), message); - return sendMessage(phone, alibabaSmsConfig.getTemplateId(), map); - } - - @Override - @Restricted - public SmsResponse sendMessage(String phone, String templateId, LinkedHashMap messages) { - SendSmsRequest sendSmsRequest = new SendSmsRequest(); - String s = JSONObject.toJSONString(messages); - sendSmsRequest.setPhoneNumbers(phone) - .setTemplateCode(templateId) - .setTemplateParam(s) - .setSignName(alibabaSmsConfig.getSignature()); - RuntimeOptions runtime = new RuntimeOptions(); - SmsResponse smsResponse = new SmsResponse(); - try { - SendSmsResponse sendSmsResponse = client.sendSmsWithOptions(sendSmsRequest, runtime); - smsResponse.setBizId(sendSmsResponse.body.getBizId()); - smsResponse.setData(sendSmsResponse.body); - smsResponse.setCode(String.valueOf(sendSmsResponse.statusCode)); - if (!"OK".equals(sendSmsResponse.body.code)) { - smsResponse.setErrMessage((sendSmsResponse.body.message)); - smsResponse.setErrorCode(sendSmsResponse.body.code); - } else { - smsResponse.setMessage(sendSmsResponse.body.message); - } - } catch (TeaException error) { - log.error(error.getMessage()); - throw new SmsBlendException(error.message); - } catch (Exception _error) { - TeaException error = new TeaException(_error.getMessage(), _error); - log.error(_error.getMessage()); - throw new SmsBlendException(error.message); - } - return smsResponse; - } - - @Override - @Restricted - public SmsResponse massTexting(List phones, String message) { - LinkedHashMap map = new LinkedHashMap<>(); - map.put(alibabaSmsConfig.getTemplateName(), message); - return massTexting(phones, alibabaSmsConfig.getTemplateId(), map); - } - - @Override - @Restricted - public SmsResponse massTexting(List phones, String templateId, LinkedHashMap messages) { - SendBatchSmsRequest sendBatchSmsRequest = new SendBatchSmsRequest(); - sendBatchSmsRequest.setPhoneNumberJson(JSONObject.toJSONString(phones))//群发的手机号 - .setTemplateCode(templateId)//模板id - .setTemplateParamJson(JSONObject.toJSONString(messages))//消息内容 - .setSignNameJson(alibabaSmsConfig.getSignature());//短信签名 - RuntimeOptions runtime = new RuntimeOptions(); - SmsResponse smsResponse = new SmsResponse(); - try { - SendBatchSmsResponse sendBatchSmsResponse = client.sendBatchSmsWithOptions(sendBatchSmsRequest, runtime); - smsResponse.setBizId(sendBatchSmsResponse.body.getBizId()); - smsResponse.setData(HttpJsonTool.getJSONObject(sendBatchSmsResponse.body)); - smsResponse.setCode(String.valueOf(sendBatchSmsResponse.statusCode)); - if (!"OK".equals(sendBatchSmsResponse.body.code)) { - smsResponse.setErrMessage((sendBatchSmsResponse.body.message)); - smsResponse.setErrorCode(sendBatchSmsResponse.body.code); - } else { - smsResponse.setMessage(sendBatchSmsResponse.body.message); - } - } catch (TeaException error) { - log.error(error.getMessage()); - throw new SmsBlendException(error.message); - } catch (Exception _error) { - TeaException error = new TeaException(_error.getMessage(), _error); - log.error(error.getMessage()); - throw new SmsBlendException(error.message); - } - return smsResponse; - } - - @Override - @Restricted - public void sendMessageAsync(String phone, String message, CallBack callBack) { - pool.execute(() -> { - SmsResponse smsResponse = sendMessage(phone, message); - callBack.callBack(smsResponse); - }); - } - - @Override - @Restricted - public void sendMessageAsync(String phone, String message) { - pool.execute(() -> { - sendMessage(phone, message); - }); - } - - @Override - @Restricted - public void sendMessageAsync(String phone, String templateId, LinkedHashMap messages, CallBack callBack) { - pool.execute(()->{ - SmsResponse smsResponse = sendMessage(phone,templateId,messages); - callBack.callBack(smsResponse); - }); - } - - @Override - @Restricted - public void sendMessageAsync(String phone, String templateId, LinkedHashMap 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 messages, Long delayedTime) { - this.delayed.schedule(new TimerTask() { - @Override - public void run() { - sendMessage(phone,templateId,messages); - } - },delayedTime); - } - - @Override - @Restricted - public void delayMassTexting(List phones, String message, Long delayedTime) { - this.delayed.schedule(new TimerTask() { - @Override - public void run() { - massTexting(phones,message); - } - },delayedTime); - } - - @Override - @Restricted - public void delayMassTexting(List phones, String templateId, LinkedHashMap messages, Long delayedTime) { - this.delayed.schedule(new TimerTask() { - @Override - public void run() { - massTexting(phones,templateId,messages); - } - },delayedTime); - } -} +package org.dromara.sms4j.aliyun.service; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.dtflys.forest.config.ForestConfiguration; +import lombok.extern.slf4j.Slf4j; +import org.dromara.sms4j.aliyun.config.AlibabaConfig; +import org.dromara.sms4j.aliyun.utils.AliyunUtils; +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 java.util.LinkedHashMap; +import java.util.List; +import java.util.TimerTask; +import java.util.concurrent.Executor; + +/** + *

类名: AlibabaSmsImpl + *

说明: 阿里云短信实现 + * + * @author :Wind + * 2023/3/26 17:16 + **/ + +@Slf4j +public class AlibabaSmsImpl implements SmsBlend { + + private final AlibabaConfig alibabaSmsConfig; + + private final Executor pool; + + private final DelayedTime delayed; + + private final ForestConfiguration http = BeanFactory.getForestConfiguration(); + + /** + * AlibabaSmsImpl + *

构造器,用于构造短信实现模块 + * + * @author :Wind + */ + + public AlibabaSmsImpl(AlibabaConfig alibabaSmsConfig, Executor pool, DelayedTime delayedTime) { + this.alibabaSmsConfig = alibabaSmsConfig; + this.pool = pool; + this.delayed = delayedTime; + } + + @Override + @Restricted + public SmsResponse sendMessage(String phone, String message) { + LinkedHashMap map = new LinkedHashMap<>(); + map.put(alibabaSmsConfig.getTemplateName(), message); + return sendMessage(phone, alibabaSmsConfig.getTemplateId(), map); + } + + @Override + @Restricted + public SmsResponse sendMessage(String phone, String templateId, LinkedHashMap messages) { + String messageStr = JSON.toJSONString(messages); + return getSmsResponse(phone, messageStr, templateId); + } + + @Override + @Restricted + public SmsResponse massTexting(List phones, String message) { + LinkedHashMap map = new LinkedHashMap<>(); + map.put(alibabaSmsConfig.getTemplateName(), message); + return massTexting(phones, alibabaSmsConfig.getTemplateId(), map); + } + + @Override + @Restricted + public SmsResponse massTexting(List phones, String templateId, LinkedHashMap messages) { + String messageStr = JSON.toJSONString(messages); + return getSmsResponse(arrayToString(phones), messageStr, templateId); + } + + private SmsResponse getSmsResponse(String phone, String message, String templateId) { + SmsResponse smsResponse = new SmsResponse(); + String requestUrl; + String paramStr; + try { + requestUrl = AliyunUtils.generateSendSmsRequestUrl(this.alibabaSmsConfig, message, phone, templateId); + paramStr = AliyunUtils.generateParamBody(alibabaSmsConfig, phone, message, templateId); + } catch (Exception e) { + log.error("aliyun send message error", e); + throw new SmsBlendException(e.getMessage()); + } + log.info("requestUrl {}", requestUrl); + http.post(requestUrl) + .addHeader("Content-Type", "application/x-www-form-urlencoded") + .addBody(paramStr) + .onSuccess(((data, req, res) -> { + JSONObject jsonBody = res.get(JSONObject.class); + log.info(jsonBody.toJSONString()); + })) + .onError((ex, req, res) -> { + JSONObject jsonBody = res.get(JSONObject.class); + log.info(jsonBody.toJSONString()); + }) + .execute(); + return smsResponse; + } + + @Override + @Restricted + public void sendMessageAsync(String phone, String message, CallBack callBack) { + pool.execute(() -> { + SmsResponse smsResponse = sendMessage(phone, message); + callBack.callBack(smsResponse); + }); + } + + @Override + @Restricted + public void sendMessageAsync(String phone, String message) { + pool.execute(() -> { + sendMessage(phone, message); + }); + } + + @Override + @Restricted + public void sendMessageAsync(String phone, String templateId, LinkedHashMap messages, CallBack callBack) { + pool.execute(() -> { + SmsResponse smsResponse = sendMessage(phone, templateId, messages); + callBack.callBack(smsResponse); + }); + } + + @Override + @Restricted + public void sendMessageAsync(String phone, String templateId, LinkedHashMap 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 messages, Long delayedTime) { + this.delayed.schedule(new TimerTask() { + @Override + public void run() { + sendMessage(phone, templateId, messages); + } + }, delayedTime); + } + + @Override + @Restricted + public void delayMassTexting(List phones, String message, Long delayedTime) { + this.delayed.schedule(new TimerTask() { + @Override + public void run() { + massTexting(phones, message); + } + }, delayedTime); + } + + @Override + @Restricted + public void delayMassTexting(List phones, String templateId, LinkedHashMap messages, Long delayedTime) { + this.delayed.schedule(new TimerTask() { + @Override + public void run() { + massTexting(phones, templateId, messages); + } + }, delayedTime); + } + + private String arrayToString(List list) { + StringBuilder sb = new StringBuilder(); + for (String s : list) { + sb.append(",").append("+86").append(s); + } + return sb.substring(1); + } +} diff --git a/sms4j-aliyun/src/main/java/org/dromara/sms4j/aliyun/utils/AliyunUtils.java b/sms4j-aliyun/src/main/java/org/dromara/sms4j/aliyun/utils/AliyunUtils.java new file mode 100644 index 00000000..beaf4e14 --- /dev/null +++ b/sms4j-aliyun/src/main/java/org/dromara/sms4j/aliyun/utils/AliyunUtils.java @@ -0,0 +1,124 @@ +package org.dromara.sms4j.aliyun.utils; + +import org.dromara.sms4j.aliyun.config.AlibabaConfig; +import org.dromara.sms4j.comm.constant.Constant; +import sun.misc.BASE64Encoder; + +import javax.crypto.Mac; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; +import java.util.*; + +/** + * @author Richard + * @date 2023/4/20 16:55 + */ +public class AliyunUtils { + + /** + * 加密方式 + */ + private static final String ALGORITHM = "HMAC-SHA1"; + + private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + + public static String generateSendSmsRequestUrl(AlibabaConfig alibabaConfig, String message, String phone, String templateId) throws Exception { + // 这里一定要设置GMT时区 + sdf.setTimeZone(new SimpleTimeZone(0, "GMT")); + Map paras = new HashMap<>(); + // 1. 公共请求参数 + paras.put("SignatureMethod", ALGORITHM); + paras.put("SignatureNonce", UUID.randomUUID().toString()); + paras.put("AccessKeyId", alibabaConfig.getAccessKeyId()); + paras.put("SignatureVersion", "1.0"); + paras.put("Timestamp", sdf.format(new Date())); + paras.put("Format", "JSON"); + paras.put("Action", alibabaConfig.getAction()); + paras.put("Version", alibabaConfig.getVersion()); + paras.put("RegionId", alibabaConfig.getRegionId()); + // 2. 业务API参数 + Map paramMap = generateParamMap(alibabaConfig, phone, message, templateId); + // 3. 参数KEY排序 + Map sortParas = new TreeMap<>(paras); + sortParas.putAll(paramMap); + // 4. 构造待签名的字符串 + Iterator it = sortParas.keySet().iterator(); + StringBuilder sortQueryStringTmp = new StringBuilder(); + while (it.hasNext()) { + String key = it.next(); + sortQueryStringTmp.append("&").append(specialUrlEncode(key)).append("=").append(specialUrlEncode(sortParas.get(key))); + } + + String stringToSign = "POST" + "&" + + specialUrlEncode("/") + "&" + + specialUrlEncode(sortQueryStringTmp.substring(1)); + String signature = sign(alibabaConfig.getAccessKeySecret() + "&", stringToSign); + // 5. 生成请求的url参数 + StringBuilder sortQueryString = new StringBuilder(); + it = paras.keySet().iterator(); + while (it.hasNext()) { + String key = it.next(); + sortQueryString.append("&").append(specialUrlEncode(key)).append("=").append(specialUrlEncode(paras.get(key))); + } + // 6.生成合法请求URL + return Constant.HTTPS_PREFIX + alibabaConfig.getRequestUrl() + "/?Signature=" + signature + sortQueryString; + } + + /** + * url编码 + */ + private static String specialUrlEncode(String value) throws Exception { + return URLEncoder.encode(value, StandardCharsets.UTF_8.name()).replace("+", "%20") + .replace("*", "%2A").replace("%7E", "~"); + } + + /** + * 生成签名 + * + * @param accessSecret accessSecret + * @param stringToSign 待生成签名的字符串 + */ + private static String sign(String accessSecret, String stringToSign) throws Exception { + Mac mac = Mac.getInstance("HmacSHA1"); + mac.init(new javax.crypto.spec.SecretKeySpec(accessSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA1")); + byte[] signData = mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8)); + return new BASE64Encoder().encode(signData); + } + + /** + * 生成请求body参数 + * + * @param alibabaConfig 配置数据 + * @param phone 手机号 + * @param message 短信内容 + * @param templateId 模板id + */ + public static Map generateParamMap(AlibabaConfig alibabaConfig, String phone, String message, String templateId) { + Map paramMap = new HashMap<>(); + paramMap.put("PhoneNumbers", phone); + paramMap.put("SignName", alibabaConfig.getSignature()); + paramMap.put("TemplateParam", message); + paramMap.put("TemplateCode", templateId); + return paramMap; + } + + /** + * 生成请求参数body字符串 + * + * @param alibabaConfig + * @param phone + * @param message + * @param templateId + */ + public static String generateParamBody(AlibabaConfig alibabaConfig, String phone, String message, String templateId) throws Exception { + Map paramMap = generateParamMap(alibabaConfig, phone, message, templateId); + StringBuilder sortQueryString = new StringBuilder(); + for (String key : paramMap.keySet()) { + sortQueryString.append("&").append(specialUrlEncode(key)).append("=") + .append(specialUrlEncode(paramMap.get(key))); + } + return sortQueryString.substring(1); + } + +} diff --git a/sms4j-comm/.flattened-pom.xml b/sms4j-comm/.flattened-pom.xml index 28df8320..2c791da5 100644 --- a/sms4j-comm/.flattened-pom.xml +++ b/sms4j-comm/.flattened-pom.xml @@ -1,15 +1,15 @@ - 4.0.0 org.dromara.sms4j sms4j - 1.0.5 + 2.0.1 org.dromara.sms4j sms4j-comm - 1.0.5 + 2.0.1 sms4j-comm sms4j-comm diff --git a/sms4j-comm/src/main/java/org/dromara/sms4j/comm/constant/Constant.java b/sms4j-comm/src/main/java/org/dromara/sms4j/comm/constant/Constant.java index 7f1010bb..273853d9 100644 --- a/sms4j-comm/src/main/java/org/dromara/sms4j/comm/constant/Constant.java +++ b/sms4j-comm/src/main/java/org/dromara/sms4j/comm/constant/Constant.java @@ -1,6 +1,9 @@ package org.dromara.sms4j.comm.constant; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + /** * Constant *

短信应用常量 @@ -27,6 +30,8 @@ public abstract class Constant { */ public static final String FROM_URLENCODED = "application/x-www-form-urlencoded"; + public static final String APPLICATION_JSON_UTF8 = "application/json; charset=utf-8"; + /** * 华为云规定 java时间格式 */ @@ -35,6 +40,10 @@ public abstract class Constant { /** 云片短信国内短信请求地址*/ public static final String YUNPIAN_URL = "https://sms.yunpian.com/v2"; + /** + * https请求前缀 + */ + public static final String HTTPS_PREFIX = "https://"; private Constant() { } diff --git a/sms4j-tencent/src/main/java/org/dromara/sms4j/tencent/config/TencentConfig.java b/sms4j-tencent/src/main/java/org/dromara/sms4j/tencent/config/TencentConfig.java index cc3a4239..170a698a 100644 --- a/sms4j-tencent/src/main/java/org/dromara/sms4j/tencent/config/TencentConfig.java +++ b/sms4j-tencent/src/main/java/org/dromara/sms4j/tencent/config/TencentConfig.java @@ -1,32 +1,52 @@ -package org.dromara.sms4j.tencent.config; - -import lombok.Builder; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; -import lombok.experimental.SuperBuilder; -import org.dromara.sms4j.comm.config.BaseConfig; - -@Data -@SuperBuilder -@ToString(callSuper = true) -@EqualsAndHashCode(callSuper = true) -public class TencentConfig extends BaseConfig { - - /** - * 短信sdkAppId - */ - private String sdkAppId; - - /** - * 地域信息默认为 ap-guangzhou - */ - @Builder.Default - private String territory = "ap-guangzhou"; - - /** - * 请求超时时间 - */ - @Builder.Default - private Integer connTimeout = 60; -} +package org.dromara.sms4j.tencent.config; + +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import lombok.experimental.SuperBuilder; +import org.dromara.sms4j.comm.config.BaseConfig; + +@Data +@SuperBuilder +@ToString(callSuper = true) +@EqualsAndHashCode(callSuper = true) +public class TencentConfig extends BaseConfig { + + /** + * 短信sdkAppId + */ + private String sdkAppId; + + /** + * 地域信息默认为 ap-guangzhou + */ + @Builder.Default + private String territory = "ap-guangzhou"; + + /** + * 请求超时时间 + */ + @Builder.Default + private Integer connTimeout = 60; + /** 请求地址*/ + @Builder.Default + private String requestUrl = "sms.tencentcloudapi.com"; + /** + * 接口名称 + */ + @Builder.Default + private String action = "SendSms"; + + /** + * 接口版本 + */ + @Builder.Default + private String version = "2021-01-11"; + + /** + * 服务名 + */ + @Builder.Default + private String service = "sms"; +} diff --git a/sms4j-tencent/src/main/java/org/dromara/sms4j/tencent/config/TencentSmsConfig.java b/sms4j-tencent/src/main/java/org/dromara/sms4j/tencent/config/TencentSmsConfig.java index 94a798eb..e6cada2d 100644 --- a/sms4j-tencent/src/main/java/org/dromara/sms4j/tencent/config/TencentSmsConfig.java +++ b/sms4j-tencent/src/main/java/org/dromara/sms4j/tencent/config/TencentSmsConfig.java @@ -1,67 +1,52 @@ -package org.dromara.sms4j.tencent.config; - -import com.tencentcloudapi.common.Credential; -import com.tencentcloudapi.common.profile.ClientProfile; -import com.tencentcloudapi.common.profile.HttpProfile; -import com.tencentcloudapi.sms.v20210111.SmsClient; -import org.dromara.sms4j.comm.factory.BeanFactory; -import org.dromara.sms4j.tencent.service.TencentSmsImpl; - -/** - * TencentSmsConfig - *

建造腾讯云短信 - * @author :Wind - * 2023/4/8 16:05 - **/ -public class TencentSmsConfig { - private TencentSmsConfig() { - } - - private static TencentSmsImpl tencentSms; - - private static TencentSmsConfig tencentSmsConfig; - - - - private SmsClient tencentBean( TencentConfig tencentConfig) { - Credential cred = new Credential(tencentConfig.getAccessKeyId(),tencentConfig.getAccessKeySecret()); - HttpProfile httpProfile = new HttpProfile(); - httpProfile.setReqMethod("POST"); - httpProfile.setConnTimeout(tencentConfig.getConnTimeout()); - httpProfile.setEndpoint("sms.tencentcloudapi.com"); - ClientProfile clientProfile = new ClientProfile(); - clientProfile.setSignMethod("HmacSHA256"); - clientProfile.setHttpProfile(httpProfile); - return new SmsClient(cred, tencentConfig.getTerritory(),clientProfile); - } - - /** 建造一个腾讯云的短信实现*/ - public static TencentSmsImpl createTencentSms(TencentConfig tencentConfig){ - if (tencentSmsConfig == null){ - tencentSmsConfig = new TencentSmsConfig(); - } - if (tencentSms == null){ - tencentSms = new TencentSmsImpl( - tencentConfig, - tencentSmsConfig.tencentBean(tencentConfig), - BeanFactory.getExecutor(), - BeanFactory.getDelayedTime() - ); - } - return tencentSms; - } - - /** 刷新对象*/ - public static TencentSmsImpl refresh(TencentConfig tencentConfig){ - if (tencentSmsConfig == null){ - tencentSmsConfig = new TencentSmsConfig(); - } - tencentSms = new TencentSmsImpl( - tencentConfig, - tencentSmsConfig.tencentBean(tencentConfig), - BeanFactory.getExecutor(), - BeanFactory.getDelayedTime() - ); - return tencentSms; - } -} +package org.dromara.sms4j.tencent.config; + +import org.dromara.sms4j.comm.factory.BeanFactory; +import org.dromara.sms4j.tencent.service.TencentSmsImpl; + +/** + * TencentSmsConfig + *

建造腾讯云短信 + * + * @author :Wind + * 2023/4/8 16:05 + **/ +public class TencentSmsConfig { + private TencentSmsConfig() { + } + + private static TencentSmsImpl tencentSms; + + private static TencentSmsConfig tencentSmsConfig; + + /** + * 建造一个腾讯云的短信实现 + */ + public static TencentSmsImpl createTencentSms(TencentConfig tencentConfig) { + if (tencentSmsConfig == null) { + tencentSmsConfig = new TencentSmsConfig(); + } + if (tencentSms == null) { + tencentSms = new TencentSmsImpl( + tencentConfig, + BeanFactory.getExecutor(), + BeanFactory.getDelayedTime() + ); + } + return tencentSms; + } + + /** + * 刷新对象 + */ + public static TencentSmsImpl refresh(TencentConfig tencentConfig) { + if (tencentSmsConfig == null) { + tencentSmsConfig = new TencentSmsConfig(); + } + tencentSms = new TencentSmsImpl( + tencentConfig, + BeanFactory.getExecutor(), + BeanFactory.getDelayedTime() + ); + return tencentSms; + } +} diff --git a/sms4j-tencent/src/main/java/org/dromara/sms4j/tencent/service/TencentSmsImpl.java b/sms4j-tencent/src/main/java/org/dromara/sms4j/tencent/service/TencentSmsImpl.java index b572297b..7ad82729 100644 --- a/sms4j-tencent/src/main/java/org/dromara/sms4j/tencent/service/TencentSmsImpl.java +++ b/sms4j-tencent/src/main/java/org/dromara/sms4j/tencent/service/TencentSmsImpl.java @@ -1,210 +1,205 @@ -package org.dromara.sms4j.tencent.service; - -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONObject; -import com.tencentcloudapi.common.exception.TencentCloudSDKException; -import com.tencentcloudapi.sms.v20210111.SmsClient; -import com.tencentcloudapi.sms.v20210111.models.SendSmsRequest; -import com.tencentcloudapi.sms.v20210111.models.SendSmsResponse; -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.tencent.config.TencentConfig; -import lombok.extern.slf4j.Slf4j; - -import java.util.*; -import java.util.concurrent.Executor; -@Slf4j -public class TencentSmsImpl implements SmsBlend { - - private TencentConfig tencentSmsConfig; - - private SmsClient client; - - private Executor pool; - - private DelayedTime delayed; - - public TencentSmsImpl(TencentConfig tencentSmsConfig, SmsClient client, Executor pool, DelayedTime delayed) { - this.tencentSmsConfig = tencentSmsConfig; - this.client = client; - this.pool = pool; - this.delayed = delayed; - } - - @Override - @Restricted - public SmsResponse sendMessage(String phone, String message) { - String[] split = message.split("&"); - LinkedHashMap map = new LinkedHashMap<>(); - for (int i = 0; i < split.length; i++) { - map.put(String.valueOf(i),split[i]); - } - return sendMessage(phone, tencentSmsConfig.getTemplateId(),map); - } - - @Override - @Restricted - public SmsResponse sendMessage(String phone, String templateId, LinkedHashMap messages) { - SmsResponse smsResponse = new SmsResponse(); - try { - SendSmsRequest req = new SendSmsRequest(); - req.setSignName(tencentSmsConfig.getSignature()); - req.setTemplateId(templateId); - req.setSmsSdkAppId(tencentSmsConfig.getSdkAppId()); - List list = new ArrayList<>(); - for (Map.Entry entry : messages.entrySet()) { - list.add(entry.getValue()); - } - String[] s = new String[list.size()]; - req.setTemplateParamSet(list.toArray(s)); - req.setPhoneNumberSet(new String[]{"+86" + phone}); - SendSmsResponse res = client.SendSms(req); - String s1 = SendSmsResponse.toJsonString(res); - JSONObject jsonObject = JSON.parseObject(s1); - if (!"Ok".equals(jsonObject.getString("Code"))) { - smsResponse.setErrMessage(jsonObject.getString("Message")); - log.debug(smsResponse.getErrMessage()); - } - smsResponse.setMessage(jsonObject.getString("Message")); - smsResponse.setBizId(res.getRequestId()); - smsResponse.setData(jsonObject); - } catch (TencentCloudSDKException e) { - throw new SmsBlendException(e.getMessage()); - } - return smsResponse; - } - - @Override - @Restricted - public SmsResponse massTexting(List phones, String message) { - String[] split = message.split("&"); - LinkedHashMap map = new LinkedHashMap<>(); - for (int i = 0; i < split.length; i++) { - map.put(String.valueOf(i),split[i]); - } - return massTexting(phones,tencentSmsConfig.getTemplateId(),map); - } - - @Override - @Restricted - public SmsResponse massTexting(List phones, String templateId, LinkedHashMap messages) { - SmsResponse smsResponse = new SmsResponse(); - try { - SendSmsRequest req = new SendSmsRequest(); - req.setSignName(tencentSmsConfig.getSignature()); - req.setTemplateId(templateId); - req.setSmsSdkAppId(tencentSmsConfig.getSdkAppId()); - List list = new ArrayList<>(); - for (Map.Entry entry : messages.entrySet()) { - list.add(entry.getValue()); - } - String[] s = new String[list.size()]; - req.setTemplateParamSet(list.toArray(s)); - req.setPhoneNumberSet(arrayToString(phones)); - SendSmsResponse res = client.SendSms(req); - String s1 = SendSmsResponse.toJsonString(res); - JSONObject jsonObject = JSON.parseObject(s1); - if (!"Ok".equals(jsonObject.getString("Code"))) { - smsResponse.setErrMessage(jsonObject.getString("Message")); - log.debug(jsonObject.getString("Message")); - } - smsResponse.setMessage(jsonObject.getString("Message")); - smsResponse.setBizId(res.getRequestId()); - smsResponse.setData(jsonObject); - } catch (TencentCloudSDKException e) { - throw new SmsBlendException(e.getMessage()); - } - return smsResponse; - } - - @Override - @Restricted - public void sendMessageAsync(String phone, String message, CallBack callBack) { - pool.execute(() -> { - SmsResponse smsResponse = sendMessage(phone, message); - callBack.callBack(smsResponse); - }); - } - - @Override - @Restricted - public void sendMessageAsync(String phone, String message) { - pool.execute(() -> { - sendMessage(phone, message); - }); - } - - @Override - @Restricted - public void sendMessageAsync(String phone, String templateId, LinkedHashMap messages, CallBack callBack) { - pool.execute(()->{ - SmsResponse smsResponse = sendMessage(phone,templateId,messages); - callBack.callBack(smsResponse); - }); - } - - @Override - @Restricted - public void sendMessageAsync(String phone, String templateId, LinkedHashMap 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 messages, Long delayedTime) { - this.delayed.schedule(new TimerTask() { - @Override - public void run() { - sendMessage(phone,templateId,messages); - } - },delayedTime); - } - - @Override - @Restricted - public void delayMassTexting(List phones, String message, Long delayedTime) { - this.delayed.schedule(new TimerTask() { - @Override - public void run() { - massTexting(phones,message); - } - },delayedTime); - } - - @Override - @Restricted - public void delayMassTexting(List phones, String templateId, LinkedHashMap messages, Long delayedTime) { - this.delayed.schedule(new TimerTask() { - @Override - public void run() { - massTexting(phones,templateId,messages); - } - },delayedTime); - } - - private String[] arrayToString(List list){ - String[] strs = new String[list.size()]; - List toStr = new ArrayList<>(); - for (String s : list) { - toStr.add("+86"+s); - } - return toStr.toArray(strs); - } -} +package org.dromara.sms4j.tencent.service; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +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.constant.Constant; +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.tencent.config.TencentConfig; +import org.dromara.sms4j.tencent.utils.TencentUtils; + +import java.util.*; +import java.util.concurrent.Executor; + +@Slf4j +public class TencentSmsImpl implements SmsBlend { + + private TencentConfig tencentSmsConfig; + + private Executor pool; + + private DelayedTime delayed; + + private final ForestConfiguration http = BeanFactory.getForestConfiguration(); + + public TencentSmsImpl(TencentConfig tencentSmsConfig, Executor pool, DelayedTime delayed) { + this.tencentSmsConfig = tencentSmsConfig; + this.pool = pool; + this.delayed = delayed; + } + + @Override + @Restricted + public SmsResponse sendMessage(String phone, String message) { + String[] split = message.split("&"); + LinkedHashMap map = new LinkedHashMap<>(); + for (int i = 0; i < split.length; i++) { + map.put(String.valueOf(i), split[i]); + } + return sendMessage(phone, tencentSmsConfig.getTemplateId(), map); + } + + @Override + @Restricted + public SmsResponse sendMessage(String phone, String templateId, LinkedHashMap messages) { + List list = new ArrayList<>(); + for (Map.Entry entry : messages.entrySet()) { + list.add(entry.getValue()); + } + String[] s = new String[list.size()]; + return getSmsResponse(new String[]{"+86" + phone}, list.toArray(s), templateId); + } + + @Override + @Restricted + public SmsResponse massTexting(List phones, String message) { + String[] split = message.split("&"); + LinkedHashMap map = new LinkedHashMap<>(); + for (int i = 0; i < split.length; i++) { + map.put(String.valueOf(i), split[i]); + } + return massTexting(phones, tencentSmsConfig.getTemplateId(), map); + } + + @Override + @Restricted + public SmsResponse massTexting(List phones, String templateId, LinkedHashMap messages) { + List list = new ArrayList<>(); + for (Map.Entry entry : messages.entrySet()) { + list.add(entry.getValue()); + } + String[] s = new String[list.size()]; + return getSmsResponse(arrayToString(phones), list.toArray(s), templateId); + } + + private SmsResponse getSmsResponse(String[] phones, String[] messages, String templateId) { + String timestamp = String.valueOf(System.currentTimeMillis() / 1000); + String signature; + try { + signature = TencentUtils.generateSignature(this.tencentSmsConfig, templateId, messages, phones, timestamp); + } catch (Exception e) { + log.error("tencent send message error", e); + throw new SmsBlendException(e.getMessage()); + } + Map headsMap = TencentUtils.generateHeadsMap(signature, timestamp, tencentSmsConfig.getAction(), + tencentSmsConfig.getVersion(), tencentSmsConfig.getTerritory(), tencentSmsConfig.getRequestUrl()); + Map requestBody = TencentUtils.generateRequestBody(phones, tencentSmsConfig.getSdkAppId(), + tencentSmsConfig.getSignature(), templateId, messages); + SmsResponse smsResponse = new SmsResponse(); + String url = Constant.HTTPS_PREFIX + tencentSmsConfig.getRequestUrl(); + http.post(url) + .addHeader(headsMap) + .addBody(requestBody) + .onSuccess(((data, req, res) -> { + JSONObject jsonBody = res.get(JSONObject.class); + JSONObject response = jsonBody.getJSONObject("Response"); + JSONArray sendStatusSet = response.getJSONArray("SendStatusSet"); + smsResponse.setBizId(sendStatusSet.getJSONObject(0).getString("SerialNo")); + smsResponse.setMessage(sendStatusSet.getJSONObject(0).getString("Message")); + smsResponse.setCode(sendStatusSet.getJSONObject(0).getString("Code")); + })) + .onError((ex, req, res) -> { + JSONObject jsonBody = res.get(JSONObject.class); + JSONObject response = jsonBody.getJSONObject("Response"); + JSONArray sendStatusSet = response.getJSONArray("SendStatusSet"); + smsResponse.setErrMessage(sendStatusSet.getJSONObject(0).getString("Message")); + smsResponse.setErrorCode(sendStatusSet.getJSONObject(0).getString("Code")); + }) + .execute(); + return smsResponse; + } + + @Override + @Restricted + public void sendMessageAsync(String phone, String message, CallBack callBack) { + pool.execute(() -> { + SmsResponse smsResponse = sendMessage(phone, message); + callBack.callBack(smsResponse); + }); + } + + @Override + @Restricted + public void sendMessageAsync(String phone, String message) { + pool.execute(() -> { + sendMessage(phone, message); + }); + } + + @Override + @Restricted + public void sendMessageAsync(String phone, String templateId, LinkedHashMap messages, CallBack callBack) { + pool.execute(() -> { + SmsResponse smsResponse = sendMessage(phone, templateId, messages); + callBack.callBack(smsResponse); + }); + } + + @Override + @Restricted + public void sendMessageAsync(String phone, String templateId, LinkedHashMap 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 messages, Long delayedTime) { + this.delayed.schedule(new TimerTask() { + @Override + public void run() { + sendMessage(phone, templateId, messages); + } + }, delayedTime); + } + + @Override + @Restricted + public void delayMassTexting(List phones, String message, Long delayedTime) { + this.delayed.schedule(new TimerTask() { + @Override + public void run() { + massTexting(phones, message); + } + }, delayedTime); + } + + @Override + @Restricted + public void delayMassTexting(List phones, String templateId, LinkedHashMap messages, Long delayedTime) { + this.delayed.schedule(new TimerTask() { + @Override + public void run() { + massTexting(phones, templateId, messages); + } + }, delayedTime); + } + + private String[] arrayToString(List list) { + String[] strs = new String[list.size()]; + List toStr = new ArrayList<>(); + for (String s : list) { + toStr.add("+86" + s); + } + return toStr.toArray(strs); + } +} diff --git a/sms4j-tencent/src/main/java/org/dromara/sms4j/tencent/utils/TencentUtils.java b/sms4j-tencent/src/main/java/org/dromara/sms4j/tencent/utils/TencentUtils.java new file mode 100644 index 00000000..043fb50d --- /dev/null +++ b/sms4j-tencent/src/main/java/org/dromara/sms4j/tencent/utils/TencentUtils.java @@ -0,0 +1,138 @@ +package org.dromara.sms4j.tencent.utils; + +import com.alibaba.fastjson.JSON; +import lombok.extern.slf4j.Slf4j; +import org.dromara.sms4j.tencent.config.TencentConfig; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +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 + * @date 2023-04-18 19:50 + */ +@Slf4j +public class TencentUtils { + /** + * 加密方式 + */ + private static final String ALGORITHM = "TC3-HMAC-SHA256"; + /** + * 请求方式 + */ + 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) throws Exception { + Mac mac = Mac.getInstance("HmacSHA256"); + SecretKeySpec secretKeySpec = new SecretKeySpec(key, mac.getAlgorithm()); + mac.init(secretKeySpec); + return mac.doFinal(msg.getBytes(StandardCharsets.UTF_8)); + } + + 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(); + } + + /** + * 生成腾讯云发送短信接口签名 + * + * @param templateId 模板id + * @param messages 短信内容 + * @param phones 手机号 + * @param timestamp 时间戳 + * @throws Exception + */ + public static String generateSignature(TencentConfig tencentConfig, String templateId, String[] messages, String[] phones, + String timestamp) throws Exception { + // ************* 步骤 1:拼接规范请求串 ************* + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + sdf.setTimeZone(TimeZone.getTimeZone("UTC")); + String date = sdf.format(new Date(Long.parseLong(timestamp + "000"))); + String canonicalUri = "/"; + String canonicalQueryString = ""; + String canonicalHeaders = "content-type:application/json; charset=utf-8\n" + "host:" + tencentConfig.getRequestUrl() + "\n"; + String signedHeaders = "content-type;host"; + HashMap params = new HashMap<>(); + // 实际调用需要更新参数,这里仅作为演示签名验证通过的例子 + params.put("PhoneNumberSet", phones); + params.put("SmsSdkAppId", tencentConfig.getSdkAppId()); + params.put("SignName", tencentConfig.getSignature()); + params.put("TemplateId", templateId); + params.put("TemplateParamSet", messages); + String payload = JSON.toJSONString(params); + String hashedRequestPayload = sha256Hex(payload); + String canonicalRequest = HTTP_REQUEST_METHOD + "\n" + canonicalUri + "\n" + canonicalQueryString + "\n" + + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestPayload; + // ************* 步骤 2:拼接待签名字符串 ************* + String credentialScope = date + "/" + tencentConfig.getService() + "/" + "tc3_request"; + String hashedCanonicalRequest = sha256Hex(canonicalRequest); + String stringToSign = ALGORITHM + "\n" + timestamp + "\n" + credentialScope + "\n" + hashedCanonicalRequest; + // ************* 步骤 3:计算签名 ************* + byte[] secretDate = hmac256(("TC3" + tencentConfig.getAccessKeySecret()).getBytes(StandardCharsets.UTF_8), date); + byte[] secretService = hmac256(secretDate, tencentConfig.getService()); + byte[] secretSigning = hmac256(secretService, "tc3_request"); + String signature = DatatypeConverter.printHexBinary(hmac256(secretSigning, stringToSign)).toLowerCase(); + // ************* 步骤 4:拼接 Authorization ************* + return ALGORITHM + " " + "Credential=" + tencentConfig.getAccessKeyId() + "/" + credentialScope + ", " + + "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature; + } + + /** + * 生成腾讯云短信请求头map + * + * @param authorization 签名信息 + * @param timestamp 时间戳 + * @param action 接口名称 + * @param version 接口版本 + * @param territory 服务器地区 + * @param requestUrl 请求地址 + */ + public static Map generateHeadsMap(String authorization, String timestamp, String action, + String version, String territory, String requestUrl) { + Map headers = new HashMap<>(); + headers.put("Authorization", authorization); + headers.put("Content-Type", CT_JSON); + headers.put("Host", requestUrl); + headers.put("X-TC-Action", action); + headers.put("X-TC-Timestamp", timestamp); + headers.put("X-TC-Version", version); + headers.put("X-TC-Region", territory); + return headers; + } + + /** + * 生成腾讯云短信请求body + * + * @param phones 手机号 + * @param sdkAppId appid + * @param signatureName 短信签名 + * @param templateId 模板id + * @param templateParamSet 模板参数 + * @return + */ + public static Map generateRequestBody(String[] phones, String sdkAppId, String signatureName, + String templateId, String[] templateParamSet) { + Map requestBody = new HashMap<>(); + requestBody.put("PhoneNumberSet", phones); + requestBody.put("SmsSdkAppId", sdkAppId); + requestBody.put("SignName", signatureName); + requestBody.put("TemplateId", templateId); + requestBody.put("TemplateParamSet", templateParamSet); + return requestBody; + } + +} \ No newline at end of file