diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/montnets/config/MontnetsConfig.java b/sms4j-provider/src/main/java/org/dromara/sms4j/montnets/config/MontnetsConfig.java new file mode 100644 index 00000000..068a30a2 --- /dev/null +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/montnets/config/MontnetsConfig.java @@ -0,0 +1,41 @@ +package org.dromara.sms4j.montnets.config; + + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.sms4j.comm.constant.SupplierConstant; +import org.dromara.sms4j.provider.config.BaseConfig; + +/** + * @author SU + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class MontnetsConfig extends BaseConfig { + + /** + * 请求地址 + */ + private String url; + /** + * 接口名称 + */ + private String api; + /** + * 模板code + */ + private String templateId; + /** + * 模板变量名称 + */ + private String templateParam; + + /** + * 获取供应商 + */ + @Override + public String getSupplier() { + return SupplierConstant.MONTNETS; + } + +} diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/montnets/config/MontnetsFactory.java b/sms4j-provider/src/main/java/org/dromara/sms4j/montnets/config/MontnetsFactory.java new file mode 100644 index 00000000..1d7708ab --- /dev/null +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/montnets/config/MontnetsFactory.java @@ -0,0 +1,48 @@ +package org.dromara.sms4j.montnets.config; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.dromara.sms4j.comm.constant.SupplierConstant; +import org.dromara.sms4j.montnets.service.MontnetsSmsImpl; +import org.dromara.sms4j.provider.factory.AbstractProviderFactory; + + +/** + * MontnetsSmsConfig + *

梦网对象建造者 + * + * @author :SU + * 2025/4/23 10:32 + **/ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class MontnetsFactory extends AbstractProviderFactory { + + private static final MontnetsFactory INSTANCE = new MontnetsFactory(); + + /** + * 获取建造者实例 + * @return 建造者实例 + */ + public static MontnetsFactory instance() { + return INSTANCE; + } + + /** + * 创建短信实现对象 + * @param montnetsConfig 短信配置对象 + * @return 短信实现对象 + */ + @Override + public MontnetsSmsImpl createSms(MontnetsConfig montnetsConfig) { + return new MontnetsSmsImpl(montnetsConfig); + } + + /** + * 获取供应商 + * @return 供应商 + */ + @Override + public String getSupplier() { + return SupplierConstant.MONTNETS; + } +} diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/montnets/service/MontnetsSmsImpl.java b/sms4j-provider/src/main/java/org/dromara/sms4j/montnets/service/MontnetsSmsImpl.java new file mode 100644 index 00000000..2f067996 --- /dev/null +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/montnets/service/MontnetsSmsImpl.java @@ -0,0 +1,152 @@ +package org.dromara.sms4j.montnets.service; + +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.net.URLDecoder; +import cn.hutool.core.net.URLEncodeUtil; +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; +import org.dromara.sms4j.comm.utils.SmsUtils; +import org.dromara.sms4j.montnets.config.MontnetsConfig; +import org.dromara.sms4j.montnets.utils.MontnetsUtils; +import org.dromara.sms4j.provider.service.AbstractSmsBlend; + +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.concurrent.Executor; + +/** + *

类名: MontnetsSmsImpl + *

说明: 梦网短信实现 + * + * @author :SU + * 2025/4/23 10:32 + **/ +@Slf4j +public class MontnetsSmsImpl extends AbstractSmsBlend { + + private int retry = 0; + + /** + * MontnetsSmsImpl + *

构造器,用于构造短信实现模块 + */ + public MontnetsSmsImpl(MontnetsConfig config, Executor pool, DelayedTime delayedTime) { + super(config, pool, delayedTime); + } + + /** + * MontnetsSmsImpl + *

构造器,用于构造短信实现模块 + */ + public MontnetsSmsImpl(MontnetsConfig config) { + super(config); + } + + @Override + public String getSupplier() { + return SupplierConstant.MONTNETS; + } + + @Override + public SmsResponse sendMessage(String phone, String message) { + LinkedHashMap map = new LinkedHashMap<>(1); + map.put(getConfig().getTemplateParam(), message); + return sendMessage(phone, this.getConfig().getTemplateId(), map); + } + + @Override + public SmsResponse sendMessage(String phone, LinkedHashMap messages) { + if (Objects.isNull(messages)) { + messages = new LinkedHashMap<>(); + } + return sendMessage(phone, getConfig().getTemplateId(), messages); + } + + @Override + public SmsResponse sendMessage(String phone, String templateId, LinkedHashMap messages) { + if (Objects.isNull(messages)) { + messages = new LinkedHashMap<>(); + } + String messageStr = formatMessage(messages); + return getSmsResponse(phone, messageStr, templateId); + } + + + @Override + public SmsResponse massTexting(List phones, String message) { + LinkedHashMap map = new LinkedHashMap<>(); + map.put(getConfig().getTemplateParam(), message); + return massTexting(phones, getConfig().getTemplateId(), map); + } + + + @Override + public SmsResponse massTexting(List phones, String templateId, LinkedHashMap messages) { + if (Objects.isNull(messages)) { + messages = new LinkedHashMap<>(); + } + String messageStr = formatMessage(messages); + return getSmsResponse(SmsUtils.addCodePrefixIfNot(phones), messageStr, templateId); + } + + private SmsResponse getSmsResponse(String phone, String message, String templateId) { + String requestUrl; + String paramStr; + try { + requestUrl = MontnetsUtils.generateSendSmsRequestUrl(this.getConfig()); + paramStr = MontnetsUtils.generateParamBody(this.getConfig(), phone, message, templateId); + } catch (Exception e) { + log.error("montnets send message error", e); + throw new SmsBlendException(e.getMessage()); + } + + log.debug("requestUrl {}", requestUrl); + Map headers = MapUtil.newHashMap(1, true); + headers.put(Constant.CONTENT_TYPE, Constant.APPLICATION_JSON_UTF8); + + SmsResponse smsResponse; + try { + smsResponse = this.getResponse(this.http.postJson(requestUrl, headers, paramStr)); + } catch (SmsBlendException e) { + smsResponse = this.errorResp(e.message); + } + + if (!smsResponse.isSuccess() && this.retry != this.getConfig().getMaxRetries()) { + return this.requestRetry(phone, message, templateId); + } else { + this.retry = 0; + return smsResponse; + } + } + + private SmsResponse requestRetry(String phone, String message, String templateId) { + this.http.safeSleep(this.getConfig().getRetryInterval()); + ++this.retry; + log.warn("短信第 {} 次重新发送", this.retry); + return this.getSmsResponse(phone, message, templateId); + } + + private SmsResponse getResponse(JSONObject resJson) { + return SmsRespUtils.resp(URLDecoder.decode(resJson.getStr("desc"), StandardCharsets.UTF_8), "0".equals(resJson.getStr("result")), getConfigId()); + } + + /** + *

说明:格式化消息 + * + * @param messages 模板参数key-value集合 + * @return 格式化后的消息 key1=urlEncode(value1)&key2=urlEncode(value2) + */ + private String formatMessage(LinkedHashMap messages) { + StringJoiner joiner = new StringJoiner("&"); + for (Map.Entry entry : messages.entrySet()) { + joiner.add(entry.getKey() + "=" + URLEncodeUtil.encode(entry.getValue(), StandardCharsets.UTF_8)); + } + return URLEncodeUtil.encode(joiner.toString()); + } +} diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/montnets/utils/MontnetsUtils.java b/sms4j-provider/src/main/java/org/dromara/sms4j/montnets/utils/MontnetsUtils.java new file mode 100644 index 00000000..8e8d3930 --- /dev/null +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/montnets/utils/MontnetsUtils.java @@ -0,0 +1,62 @@ +package org.dromara.sms4j.montnets.utils; + +import cn.hutool.crypto.digest.DigestUtil; +import cn.hutool.json.JSONUtil; +import org.dromara.sms4j.comm.utils.SmsDateUtils; +import org.dromara.sms4j.montnets.config.MontnetsConfig; + +import java.util.Date; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +/** + * @author SU + * 2025/4/23 10:32 + */ +public class MontnetsUtils { + + public static String generateSendSmsRequestUrl(MontnetsConfig montnetsConfig) { + String url = montnetsConfig.getUrl(); + String api = montnetsConfig.getApi(); + return url + api; + } + + /** + * 生成请求body参数 + * + * @param montnetsConfig 短信配置 + * @param phone 手机号 + * @param message 短信内容 + * @param templateId 模板ID + */ + public static Map generateParamMap(MontnetsConfig montnetsConfig, String phone, String message, String templateId) { + Map paramMap = new HashMap<>(); + + String accessKeyId = montnetsConfig.getAccessKeyId(); + String accessKeySecret = montnetsConfig.getAccessKeySecret(); + String timeStamp = SmsDateUtils.formatDateToStr(new Date(), "MMddHHmmss", SmsDateUtils.gmt8()); + + paramMap.put("userid", accessKeyId); + paramMap.put("pwd", DigestUtil.md5Hex(accessKeyId.toUpperCase(Locale.ENGLISH) + "00000000" + accessKeySecret + timeStamp)); + paramMap.put("timestamp", timeStamp); + paramMap.put("mobile", phone); + paramMap.put("content", message); + paramMap.put("tmplid", templateId); + + return paramMap; + } + + /** + * 生成请求参数body字符串 + * + * @param montnetsConfig 短信配置 + * @param phone 手机号 + * @param message 短信内容 + * @param templateId 模板ID + */ + public static String generateParamBody(MontnetsConfig montnetsConfig, String phone, String message, String templateId) { + Map paramMap = generateParamMap(montnetsConfig, phone, message, templateId); + return JSONUtil.toJsonStr(paramMap); + } +} \ No newline at end of file