diff --git a/sms4j-core/src/main/java/org/dromara/sms4j/core/SupplierSqlConfig.java b/sms4j-core/src/main/java/org/dromara/sms4j/core/SupplierSqlConfig.java index 48c9bf92..b7c43438 100644 --- a/sms4j-core/src/main/java/org/dromara/sms4j/core/SupplierSqlConfig.java +++ b/sms4j-core/src/main/java/org/dromara/sms4j/core/SupplierSqlConfig.java @@ -9,6 +9,7 @@ import org.dromara.sms4j.ctyun.config.CtyunConfig; import org.dromara.sms4j.emay.config.EmayConfig; import org.dromara.sms4j.huawei.config.HuaweiConfig; import org.dromara.sms4j.jdcloud.config.JdCloudConfig; +import org.dromara.sms4j.netease.config.NeteaseConfig; import org.dromara.sms4j.provider.enumerate.SupplierType; import org.dromara.sms4j.tencent.config.TencentConfig; import org.dromara.sms4j.unisms.config.UniConfig; @@ -29,7 +30,7 @@ public class SupplierSqlConfig { * readSqlConfig *

读取数据库配置信息 * @author :Wind - */ + */ public static void readSqlConfig(){ select = JDBCTool.selectConfig(); } @@ -38,7 +39,7 @@ public class SupplierSqlConfig { * refreshSqlConfig *

读取并刷新数据库配置 * @author :Wind - */ + */ public static void refreshSqlConfig(){ readSqlConfig(); alibaba(); @@ -50,6 +51,7 @@ public class SupplierSqlConfig { cloopen(); emay(); ctyun(); + netease(); } public SupplierSqlConfig() { @@ -64,17 +66,17 @@ public class SupplierSqlConfig { * alibaba *

数据库读取并设置阿里云短信 * @author :Wind - */ + */ public static void alibaba(){ AlibabaConfig alibabaConfig = SmsUtil.jsonForObject(select.get(SupplierType.ALIBABA.getName()), AlibabaConfig.class); - SupplierFactory.setAlibabaConfig(alibabaConfig); + SupplierFactory.setAlibabaConfig(alibabaConfig); } /** * huawei *

数据库读取并设置华为短信 * @author :Wind - */ + */ public static void huawei(){ HuaweiConfig huaweiConfig = SmsUtil.jsonForObject(select.get(SupplierType.HUAWEI.getName()), HuaweiConfig.class); SupplierFactory.setHuaweiConfig(huaweiConfig); @@ -84,7 +86,7 @@ public class SupplierSqlConfig { * jingdong *

数据库读取并设置京东短信 * @author :Wind - */ + */ public static void jingdong(){ JdCloudConfig jdCloudConfig = SmsUtil.jsonForObject(select.get(SupplierType.JD_CLOUD.getName()), JdCloudConfig.class); SupplierFactory.setJdCloudConfig(jdCloudConfig); @@ -94,7 +96,7 @@ public class SupplierSqlConfig { * tencent *

数据库读取并设置腾讯短信 * @author :Wind - */ + */ public static void tencent(){ TencentConfig tencentConfig = SmsUtil.jsonForObject(select.get(SupplierType.TENCENT.getName()), TencentConfig.class); SupplierFactory.setTencentConfig(tencentConfig); @@ -104,7 +106,7 @@ public class SupplierSqlConfig { * uniSms *

数据库读取并设置合一短信配置 * @author :Wind - */ + */ public static void uniSms(){ UniConfig uniConfig = SmsUtil.jsonForObject(select.get(SupplierType.UNI_SMS.getName()), UniConfig.class); SupplierFactory.setUniConfig(uniConfig); @@ -114,7 +116,7 @@ public class SupplierSqlConfig { * yunPian *

数据库读取并设置云片短信 * @author :Wind - */ + */ public static void yunPian(){ YunpianConfig yunpianConfig = SmsUtil.jsonForObject(select.get(SupplierType.YUNPIAN.getName()), YunpianConfig.class); SupplierFactory.setYunpianConfig(yunpianConfig); @@ -124,7 +126,7 @@ public class SupplierSqlConfig { * cloopen *

数据库读取并设置容联云短信 * @author :Wind - */ + */ public static void cloopen(){ CloopenConfig cloopenConfig = SmsUtil.jsonForObject(select.get(SupplierType.CLOOPEN.getName()), CloopenConfig.class); SupplierFactory.setCloopenConfig(cloopenConfig); @@ -143,9 +145,19 @@ public class SupplierSqlConfig { * ctyun *

数据库读取并设置天翼云短信 * @author :Wind - */ + */ public static void ctyun(){ CtyunConfig ctyunConfig = SmsUtil.jsonForObject(select.get(SupplierType.CTYUN.getName()), CtyunConfig.class); SupplierFactory.setCtyunConfig(ctyunConfig); } + + /** + * netease + *

数据库读取并设置网易云短信 + * @author : Adam + */ + public static void netease(){ + NeteaseConfig neteaseConfig = SmsUtil.jsonForObject(select.get(SupplierType.NETEASE.getName()), NeteaseConfig.class); + SupplierFactory.setNeteaseConfig(neteaseConfig); + } } diff --git a/sms4j-core/src/main/java/org/dromara/sms4j/core/config/SupplierFactory.java b/sms4j-core/src/main/java/org/dromara/sms4j/core/config/SupplierFactory.java index d9765bf2..f8c75f3d 100644 --- a/sms4j-core/src/main/java/org/dromara/sms4j/core/config/SupplierFactory.java +++ b/sms4j-core/src/main/java/org/dromara/sms4j/core/config/SupplierFactory.java @@ -15,6 +15,8 @@ import org.dromara.sms4j.huawei.config.HuaweiConfig; import org.dromara.sms4j.huawei.config.HuaweiFactory; import org.dromara.sms4j.jdcloud.config.JdCloudConfig; import org.dromara.sms4j.jdcloud.config.JdCloudFactory; +import org.dromara.sms4j.netease.config.NeteaseConfig; +import org.dromara.sms4j.netease.config.NeteaseFactory; import org.dromara.sms4j.provider.enumerate.SupplierType; import org.dromara.sms4j.tencent.config.TencentConfig; import org.dromara.sms4j.tencent.config.TencentFactory; @@ -96,12 +98,19 @@ public class SupplierFactory { return CtyunFactory.instance().getConfig(); } + /** + * 网易云信配置获取 + */ + public static NeteaseConfig getNeteaseConfig() { + return NeteaseFactory.instance().getConfig(); + } + /** * setSupplierConfig *

通用化set,用于设置 * @param t 配置对象 * @author :Wind - */ + */ public static void setSupplierConfig(T t) { if (t instanceof AlibabaConfig) { setAlibabaConfig((AlibabaConfig) t); @@ -121,7 +130,9 @@ public class SupplierFactory { setEmayConfig((EmayConfig) t); } else if (t instanceof CtyunConfig) { setCtyunConfig((CtyunConfig) t); - }else { + } else if (t instanceof NeteaseConfig) { + setNeteaseConfig((NeteaseConfig) t); + } else { throw new SmsBlendException("Loading failure! Please check the configuration type."); } } @@ -197,4 +208,12 @@ public class SupplierFactory { CtyunFactory.instance().setConfig(ctyunConfig); SmsFactory.refresh(SupplierType.CTYUN); } + + /** + * 设置 neteaseConfig + */ + public static void setNeteaseConfig(NeteaseConfig neteaseConfig) { + NeteaseFactory.instance().setConfig(neteaseConfig); + SmsFactory.refresh(SupplierType.NETEASE); + } } diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/netease/config/NeteaseConfig.java b/sms4j-provider/src/main/java/org/dromara/sms4j/netease/config/NeteaseConfig.java new file mode 100644 index 00000000..a14182db --- /dev/null +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/netease/config/NeteaseConfig.java @@ -0,0 +1,56 @@ +package org.dromara.sms4j.netease.config; + +import lombok.Builder; +import lombok.Data; +import lombok.experimental.SuperBuilder; +import org.dromara.sms4j.api.universal.SupplierConfig; +import org.dromara.sms4j.comm.config.BaseConfig; + +/** + * @author adam + */ +@Data +@SuperBuilder +public class NeteaseConfig extends BaseConfig implements SupplierConfig { + + /** + * 模板变量名称 + */ + private String templateId; + + /** + * appKey + */ + private String appKey; + + /** + * secret + */ + private String secret; + + /** + * 模板短信请求地址 + */ + @Builder.Default + private String templateUrl = "https://api.netease.im/sms/sendtemplate.action"; + + + /** + * 验证码短信请求地址 + */ + @Builder.Default + private String codeUrl = "https://api.netease.im/sms/sendcode.action"; + + /** + * 验证码验证请求地址 + */ + @Builder.Default + private String verifyUrl = "https://api.netease.im/sms/verifycode.action"; + + /** + * 是否需要支持短信上行。true:需要,false:不需要 + * 说明:如果开通了短信上行抄送功能,该参数需要设置为true,其它情况设置无效 + */ + private final Boolean needUp; + +} diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/netease/config/NeteaseFactory.java b/sms4j-provider/src/main/java/org/dromara/sms4j/netease/config/NeteaseFactory.java new file mode 100644 index 00000000..36feb4ea --- /dev/null +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/netease/config/NeteaseFactory.java @@ -0,0 +1,81 @@ +package org.dromara.sms4j.netease.config; + +import org.dromara.sms4j.comm.factory.BeanFactory; +import org.dromara.sms4j.netease.service.NeteaseSmsImpl; +import org.dromara.sms4j.provider.base.BaseProviderFactory; + +/** + * NeteaseSmsConfig + *

网易云信短信 + * + * @author :adam + * 2023-05-30 + **/ +public class NeteaseFactory implements BaseProviderFactory { + + private static NeteaseSmsImpl neteaseSms; + + private static final NeteaseFactory INSTANCE = new NeteaseFactory(); + + private static final class ConfigHolder { + private static NeteaseConfig config = NeteaseConfig.builder().build(); + } + + private NeteaseFactory() { + } + + /** + * 获取建造者实例 + * @return 建造者实例 + */ + public static NeteaseFactory instance() { + return INSTANCE; + } + + /** + * 建造一个网易云的短信实现 + */ + @Override + public NeteaseSmsImpl createSms(NeteaseConfig neteaseConfig) { + if (neteaseSms == null) { + neteaseSms = new NeteaseSmsImpl( + neteaseConfig, + BeanFactory.getExecutor(), + BeanFactory.getDelayedTime() + ); + } + return neteaseSms; + } + + /** + * 刷新对象 + */ + @Override + public NeteaseSmsImpl refresh(NeteaseConfig neteaseConfig) { + neteaseSms = new NeteaseSmsImpl( + neteaseConfig, + BeanFactory.getExecutor(), + BeanFactory.getDelayedTime() + ); + return neteaseSms; + } + + /** + * 获取配置 + * @return 配置对象 + */ + @Override + public NeteaseConfig getConfig() { + return ConfigHolder.config; + } + + /** + * 设置配置 + * @param config 配置对象 + */ + @Override + public void setConfig(NeteaseConfig config) { + ConfigHolder.config = config; + } + +} diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/netease/service/NeteaseSmsImpl.java b/sms4j-provider/src/main/java/org/dromara/sms4j/netease/service/NeteaseSmsImpl.java new file mode 100644 index 00000000..f12baa1e --- /dev/null +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/netease/service/NeteaseSmsImpl.java @@ -0,0 +1,134 @@ +package org.dromara.sms4j.netease.service; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.IdUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.dromara.sms4j.api.AbstractSmsBlend; +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.netease.config.NeteaseConfig; +import org.dromara.sms4j.netease.utils.NeteaseUtils; + +import java.util.*; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Created with IntelliJ IDEA. + * + * @author adam + * @create 2023/5/29 17:34 + */ + +@Slf4j +public class NeteaseSmsImpl extends AbstractSmsBlend { + + private NeteaseConfig config; + + public NeteaseSmsImpl(NeteaseConfig config, Executor pool, DelayedTime delayed) { + super(pool, delayed); + this.config = config; + } + + /** + * + * @param phone 接收短信的手机号 + * @param message 短信变量 ["xxx","yyy"] + * @return + */ + @Override + @Restricted + public SmsResponse sendMessage(String phone, String message) { + Optional.ofNullable(phone).orElseThrow(() -> new SmsBlendException("手机号不能为空")); + Optional.ofNullable(config.getTemplateId()).orElseThrow(() -> new SmsBlendException("模板ID不能为空")); + return getSmsResponse(config.getTemplateUrl(), Collections.singletonList(phone), message, config.getTemplateId()); + } + + + /** + * + * @param phone + * @param templateId 模板id + * @param messages 短信变量 key为默认 params value为模板变量值 ["xxx","yyy"] + * @return + */ + @Override + @Restricted + public SmsResponse sendMessage(String phone, String templateId, LinkedHashMap messages) { + Optional.ofNullable(phone).orElseThrow(() -> new SmsBlendException("手机号不能为空")); + Optional.ofNullable(config.getTemplateId()).orElseThrow(() -> new SmsBlendException("模板ID不能为空")); + String messageStr = messages.get("params"); + return getSmsResponse(config.getTemplateUrl(), Collections.singletonList(phone), messageStr, templateId); + } + + @Override + @Restricted + public SmsResponse massTexting(List phones, String message) { + if (phones.size() < 1) { + throw new SmsBlendException("手机号不能为空"); + } + if (phones.size() > 100) { + throw new SmsBlendException("单次发送超过最大发送上限,建议每次群发短信人数低于100"); + } + Optional.ofNullable(config.getTemplateId()).orElseThrow(() -> new SmsBlendException("模板ID不能为空")); + return getSmsResponse(config.getTemplateUrl(), phones, config.getTemplateId(), message); + } + + @Override + @Restricted + public SmsResponse massTexting(List phones, String templateId, LinkedHashMap messages) { + if (phones.size() > 100) { + throw new SmsBlendException("单次发送超过最大发送上限,建议每次群发短信人数低于100"); + } + Optional.ofNullable(config.getTemplateId()).orElseThrow(() -> new SmsBlendException("模板ID不能为空")); + String messageStr = messages.get("message"); + return getSmsResponse(config.getTemplateUrl(), phones, messageStr, templateId); + } + + private SmsResponse getSmsResponse(String requestUrl, List phones, String message, String templateId) { + AtomicReference reference = new AtomicReference<>(); + String nonce = IdUtil.fastSimpleUUID(); + String curTime = String.valueOf(DateUtil.currentSeconds()); + String checkSum = NeteaseUtils.getCheckSum(config.getSecret(), nonce, curTime); + Map body = new LinkedHashMap<>(4); + body.put("templateid", templateId); + body.put("mobiles", JSONArray.parseArray(JSON.toJSONString(phones)).toJSONString()); + body.put("params", message); + body.put("needUp", config.getNeedUp()); + http.post(requestUrl) + .addHeader("Content-Type", "application/x-www-form-urlencoded") + .addHeader("AppKey", config.getAppKey()) + .addHeader("Nonce", nonce) + .addHeader("CurTime", curTime) + .addHeader("CheckSum", checkSum) + .addBody(body) + .onSuccess(((data, req, res) -> { + reference.set(this.getResponse(res.get(JSONObject.class))); + })) + .onError((ex, req, res) -> { + reference.set(this.getResponse(res.get(JSONObject.class))); + }) + .execute(); + return reference.get(); + } + + private SmsResponse getResponse(JSONObject jsonObject) { + SmsResponse response = new SmsResponse(); + Integer code = jsonObject.getInteger("code"); + if (code > 200) { + response.setErrorCode(String.valueOf(code)); + response.setErrMessage(jsonObject.getString("msg")); + } else { + response.setCode(String.valueOf(code)); + response.setMessage(jsonObject.getString("msg")); + response.setData(jsonObject.get("obj")); + } + return response; + } + +} diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/netease/utils/NeteaseUtils.java b/sms4j-provider/src/main/java/org/dromara/sms4j/netease/utils/NeteaseUtils.java new file mode 100644 index 00000000..7eadc895 --- /dev/null +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/netease/utils/NeteaseUtils.java @@ -0,0 +1,51 @@ +package org.dromara.sms4j.netease.utils; + +import org.dromara.sms4j.comm.exception.SmsBlendException; + +import java.security.MessageDigest; + +/** + * Created with IntelliJ IDEA. + * + * @author adam + * @create 2023/5/29 17:49 + */ + +public class NeteaseUtils { + + /** + * 计算并获取CheckSum + * @param appSecret + * @param nonce + * @param curTime + * @return + */ + public static String getCheckSum(String appSecret, String nonce, String curTime) { + return encode("sha1", appSecret + nonce + curTime); + } + + private static String encode(String algorithm, String value) { + if (value == null) { + return null; + } + try { + MessageDigest messageDigest + = MessageDigest.getInstance(algorithm); + messageDigest.update(value.getBytes()); + return getFormattedText(messageDigest.digest()); + } catch (Exception e) { + throw new SmsBlendException(e.getMessage()); + } + } + private static String getFormattedText(byte[] bytes) { + int len = bytes.length; + StringBuilder buf = new StringBuilder(len * 2); + for (int j = 0; j < len; j++) { + buf.append(HEX_DIGITS[(bytes[j] >> 4) & 0x0f]); + buf.append(HEX_DIGITS[bytes[j] & 0x0f]); + } + return buf.toString(); + } + private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', + '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; +} diff --git a/sms4j-provider/src/main/java/org/dromara/sms4j/provider/enumerate/SupplierType.java b/sms4j-provider/src/main/java/org/dromara/sms4j/provider/enumerate/SupplierType.java index 9725047c..3d2cbb6c 100644 --- a/sms4j-provider/src/main/java/org/dromara/sms4j/provider/enumerate/SupplierType.java +++ b/sms4j-provider/src/main/java/org/dromara/sms4j/provider/enumerate/SupplierType.java @@ -8,6 +8,7 @@ import org.dromara.sms4j.ctyun.config.CtyunFactory; import org.dromara.sms4j.emay.config.EmayFactory; import org.dromara.sms4j.huawei.config.HuaweiFactory; import org.dromara.sms4j.jdcloud.config.JdCloudFactory; +import org.dromara.sms4j.netease.config.NeteaseFactory; import org.dromara.sms4j.provider.base.BaseProviderFactory; import org.dromara.sms4j.tencent.config.TencentFactory; import org.dromara.sms4j.unisms.config.UniFactory; @@ -38,6 +39,8 @@ public enum SupplierType { EMAY("亿美软通", EmayFactory.instance()), /** 天翼云 */ CTYUN("天翼云短信", CtyunFactory.instance()), + /** 网易云信 */ + NETEASE("网易云短信", NeteaseFactory.instance()) ; diff --git a/sms4j-spring-boot-starter/src/main/java/org/dromara/sms4j/starter/config/SupplierConfig.java b/sms4j-spring-boot-starter/src/main/java/org/dromara/sms4j/starter/config/SupplierConfig.java index ab1c56dd..98144247 100644 --- a/sms4j-spring-boot-starter/src/main/java/org/dromara/sms4j/starter/config/SupplierConfig.java +++ b/sms4j-spring-boot-starter/src/main/java/org/dromara/sms4j/starter/config/SupplierConfig.java @@ -7,6 +7,7 @@ import org.dromara.sms4j.ctyun.config.CtyunConfig; import org.dromara.sms4j.emay.config.EmayConfig; import org.dromara.sms4j.huawei.config.HuaweiConfig; import org.dromara.sms4j.jdcloud.config.JdCloudConfig; +import org.dromara.sms4j.netease.config.NeteaseConfig; import org.dromara.sms4j.tencent.config.TencentConfig; import org.dromara.sms4j.unisms.config.UniConfig; import org.dromara.sms4j.yunpian.config.YunpianConfig; @@ -82,4 +83,14 @@ public class SupplierConfig { protected CtyunConfig ctyunConfig(){ return SupplierFactory.getCtyunConfig(); } + + + /** + * 网易云信差异化配置 + */ + @Bean + @ConfigurationProperties(prefix = "sms.netease") + protected NeteaseConfig neteaseConfig(){ + return SupplierFactory.getNeteaseConfig(); + } }