
1.前言
1.1 sms4j是什么?
1.2 缘由
1.3 官方地址
https://sms4j.com/https://gitee.com/dromara/sms4jhttps://github.com/dromara/SMS4J
1.4 华为短信接口响应码文档地址
https://support.huaweicloud.com/api-msgsms/sms_05_0050.html#toTop2.配置
2.1 依赖配置
<dependency><groupId>org.dromara.sms4jgroupId><artifactId>sms4j-spring-boot-starterartifactId><version>3.2.1version>dependency>
https://sms4j.com/title/log.html
2.2 yaml配置
sms:# 标注从yml读取配置: yamlblends:# 自定义的标识,也就是configId这里可以是任意值(最好不要是中文)hw1:# 接入地址url: https://smsapi.cn-north-4.myhuaweicloud.com:443# 国内短信签名通道号sender: xxxxx# 厂商标识,标定此配置是哪个厂商,详细请看厂商标识介绍部分supplier: huawei# 您的accessKey: xxxxx# 您的accessKeySecret: xxxxxxx# 您的短信签名signature: xxxxxx# 模板ID 非必须配置,如果使用sendMessage的快速发送需此配置#template-id: xxxxxxxx# 您的sdkAppId: hw1# 自定义的标识,也就是configId这里可以是任意值(最好不要是中文)
3.重写
3.1项目中重写如下类

3.2 HuaweiSmsImpl类
package org.dromara.sms4j.huawei.service;import cn.hutool.core.collection.CollUtil;import cn.hutool.core.map.MapUtil;import cn.hutool.json.JSONObject;import lombok.extern.slf4j.Slf4j;import org.apache.commons.lang3.StringUtils;import org.dromara.sms4j.api.entity.SmsResponse;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.huawei.config.HuaweiConfig;import org.dromara.sms4j.huawei.utils.HuaweiBuilder;import org.dromara.sms4j.provider.service.AbstractSmsBlend;import java.util.ArrayList;import java.util.LinkedHashMap;import java.util.List;import java.util.Map;import java.util.Objects;import java.util.UUID;import java.util.concurrent.Executor;import java.util.stream.Collectors;import static org.dromara.sms4j.huawei.utils.HuaweiBuilder.listToString;public class HuaweiSmsImpl extends AbstractSmsBlend{ private int retry = 0;public HuaweiSmsImpl(HuaweiConfig config, Executor pool, DelayedTime delayed) {super(config, pool, delayed);}public HuaweiSmsImpl(HuaweiConfig config) {super(config);}public String getSupplier() {return SupplierConstant.HUAWEI;}public SmsResponse sendMessage(String phone, String message) {LinkedHashMap<String, String> mes = new LinkedHashMap<>();mes.put(UUID.randomUUID().toString().replaceAll("-", ""), message);return sendMessage(phone, getConfig().getTemplateId(), mes);}public SmsResponse sendMessage(String phone, LinkedHashMap<String, String> messages) {if (Objects.isNull(messages)) {messages = new LinkedHashMap<>();}return sendMessage(phone, getConfig().getTemplateId(), messages);}public SmsResponse sendMessage(String phone, String templateId, LinkedHashMap<String, String> messages) {if (Objects.isNull(messages)) {messages = new LinkedHashMap<>();}String url = getConfig().getUrl() + Constant.HUAWEI_REQUEST_URL;List<String> list = new ArrayList<>();for (Map.Entry<String, String> entry : messages.entrySet()) {list.add(entry.getValue());}String mess = "";if (StringUtils.isNotEmpty(templateId)) {mess = listToString(list);} else {if (list.size() == 1) {mess = list.stream().collect(Collectors.joining(""));} else {mess = list.stream().collect(Collectors.joining(","));}}if (StringUtils.isEmpty(mess)) {throw new RuntimeException("华为发送短信消息不为空!");}String requestBody = HuaweiBuilder.buildRequestBody(getConfig().getSender(), phone, templateId, mess, getConfig().getStatusCallBack(), getConfig().getSignature());Map<String, String> headers = MapUtil.newHashMap(3, true);headers.put("Authorization", Constant.HUAWEI_AUTH_HEADER_VALUE);headers.put("X-WSSE", HuaweiBuilder.buildWsseHeader(getConfig().getAccessKeyId(), getConfig().getAccessKeySecret()));headers.put("Content-Type", Constant.FROM_URLENCODED);SmsResponse smsResponse;try {smsResponse = getResponse(http.postJson(url, headers, requestBody));} catch (SmsBlendException e) {smsResponse = new SmsResponse();smsResponse.setSuccess(false);smsResponse.setData(e.getMessage());}if (smsResponse.isSuccess() || retry == getConfig().getMaxRetries()) {retry = 0;return smsResponse;}return requestRetry(phone, templateId, messages);}private SmsResponse requestRetry(String phone, String templateId, LinkedHashMap<String, String> messages) {http.safeSleep(getConfig().getRetryInterval());retry++;log.warn("短信第 {} 次重新发送", retry);return sendMessage(phone, templateId, messages);}public SmsResponse massTexting(List<String> phones, String message) {return sendMessage(CollUtil.join(phones, ","), message);}public SmsResponse massTexting(List<String> phones, String templateId, LinkedHashMap<String, String> messages) {if (Objects.isNull(messages)) {messages = new LinkedHashMap<>();}return sendMessage(CollUtil.join(phones, ","), templateId, messages);}private SmsResponse getResponse(JSONObject resJson) {SmsResponse smsResponse = new SmsResponse();smsResponse.setSuccess("000000".equals(resJson.getStr("code")));smsResponse.setData(resJson);smsResponse.setConfigId(getConfigId());return smsResponse;}}
3.3 HuaweiBuilder类
package org.dromara.sms4j.huawei.utils;import cn.hutool.core.codec.Base64;import cn.hutool.core.date.DateUtil;import org.apache.commons.lang3.StringUtils;import org.dromara.sms4j.comm.constant.Constant;import org.dromara.sms4j.comm.exception.SmsBlendException;import javax.net.ssl.HttpsURLConnection;import javax.net.ssl.SSLContext;import javax.net.ssl.TrustManager;import javax.net.ssl.X509TrustManager;import java.io.UnsupportedEncodingException;import java.net.URLEncoder;import java.security.MessageDigest;import java.security.NoSuchAlgorithmException;import java.security.cert.X509Certificate;import java.util.Date;import java.util.HashMap;import java.util.List;import java.util.Map;import java.util.UUID;public class HuaweiBuilder {private HuaweiBuilder() {}/*** buildWsseHeader*构造X-WSSE参数值
** @author :Wind*/public static String buildWsseHeader(String appKey, String appSecret) {if (null == appKey || null == appSecret || appKey.isEmpty() || appSecret.isEmpty()) {System.out.println("buildWsseHeader(): appKey or appSecret is null.");return null;}String time = dateFormat(new Date());// NonceString nonce = UUID.randomUUID().toString().replace("-", "");MessageDigest md;byte[] passwordDigest;try {md = MessageDigest.getInstance("SHA-256");md.update((nonce + time + appSecret).getBytes());passwordDigest = md.digest();} catch (NoSuchAlgorithmException e) {throw new SmsBlendException(e);}// PasswordDigestString passwordDigestBase64Str = Base64.encode(passwordDigest);//若passwordDigestBase64Str中包含换行符,请执行如下代码进行修正//passwordDigestBase64Str = passwordDigestBase64Str.replaceAll("[\\s*\t\n\r]", "");return String.format(Constant.HUAWEI_WSSE_HEADER_FORMAT, appKey, passwordDigestBase64Str, nonce, time);}static void trustAllHttpsCertificates() throws Exception {TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {public void checkClientTrusted(X509Certificate[] chain, String authType) {}public void checkServerTrusted(X509Certificate[] chain, String authType) {}public X509Certificate[] getAcceptedIssuers() {return null;}}};SSLContext sc = SSLContext.getInstance("SSL");sc.init(null, trustAllCerts, null);HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());}/*** buildRequestBody*构造请求Body体
** @param sender 国内短信签名通道号* @param receiver 短信接收者* @param templateId 短信模板id* @param templateParas 模板参数* @param statusCallBack 短信状态报告接收地* @param signature | 签名名称,使用国内短信通用模板时填写* @author :Wind*/public static String buildRequestBody(String sender, String receiver, String templateId, String templateParas,String statusCallBack, String signature) {if (null == sender || null == receiver || sender.isEmpty() || receiver.isEmpty()) {System.out.println("buildRequestBody(): sender, receiver is null.");return null;}Map<String, String> map = new HashMap<>();map.put("from", sender);map.put("to", receiver);if (StringUtils.isNotEmpty(templateId)) {map.put("templateId", templateId);if (null != templateParas && !templateParas.isEmpty()) {map.put("templateParas", templateParas);}}else {map.put("body", templateParas);}if (null != statusCallBack && !statusCallBack.isEmpty()) {map.put("statusCallback", statusCallBack);}if (null != signature && !signature.isEmpty()) {map.put("signature", signature);}StringBuilder sb = new StringBuilder();String temp;for (String s : map.keySet()) {try {temp = URLEncoder.encode(map.get(s), "UTF-8");} catch (UnsupportedEncodingException e) {throw new SmsBlendException(e);}sb.append(s).append("=").append(temp).append("&");}return sb.deleteCharAt(sb.length() - 1).toString();}public static String listToString(List<String> list) {if (null == list || list.isEmpty()) {return null;}StringBuilder stringBuffer = new StringBuilder();stringBuffer.append("[\"");for (String s : list) {stringBuffer.append(s);stringBuffer.append("\"");stringBuffer.append(",");stringBuffer.append("\"");}stringBuffer.delete(stringBuffer.length() - 3, stringBuffer.length() - 1);stringBuffer.append("]");return stringBuffer.toString();}private static String dateFormat(Date date) {return DateUtil.format(date, Constant.HUAWEI_JAVA_DATE);}}
3.4 SmsBlendsInitializer类
package org.dromara.sms4j.starter.config;import cn.hutool.core.util.StrUtil;import cn.hutool.json.JSONObject;import cn.hutool.json.JSONUtil;import lombok.extern.slf4j.Slf4j;import org.dromara.sms4j.aliyun.config.AlibabaFactory;import org.dromara.sms4j.api.SmsBlend;import org.dromara.sms4j.api.universal.SupplierConfig;import org.dromara.sms4j.api.verify.PhoneVerify;import org.dromara.sms4j.cloopen.config.CloopenFactory;import org.dromara.sms4j.comm.constant.Constant;import org.dromara.sms4j.comm.enumerate.ConfigType;import org.dromara.sms4j.comm.utils.SmsUtils;import org.dromara.sms4j.core.datainterface.SmsReadConfig;import org.dromara.sms4j.core.factory.SmsFactory;import org.dromara.sms4j.core.proxy.EnvirmentHolder;import org.dromara.sms4j.core.proxy.SmsProxyFactory;import org.dromara.sms4j.core.proxy.processor.BlackListProcessor;import org.dromara.sms4j.core.proxy.processor.BlackListRecordingProcessor;import org.dromara.sms4j.core.proxy.processor.CoreMethodParamValidateProcessor;import org.dromara.sms4j.core.proxy.processor.RestrictedProcessor;import org.dromara.sms4j.core.proxy.processor.SingleBlendRestrictedProcessor;import org.dromara.sms4j.ctyun.config.CtyunFactory;import org.dromara.sms4j.dingzhong.config.DingZhongFactory;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.lianlu.config.LianLuFactory;import org.dromara.sms4j.netease.config.NeteaseFactory;import org.dromara.sms4j.provider.config.SmsConfig;import org.dromara.sms4j.provider.factory.BaseProviderFactory;import org.dromara.sms4j.provider.factory.ProviderFactoryHolder;import org.dromara.sms4j.qiniu.config.QiNiuFactory;import org.dromara.sms4j.starter.adepter.ConfigCombineMapAdeptor;import org.dromara.sms4j.tencent.config.TencentFactory;import org.dromara.sms4j.unisms.config.UniFactory;import org.dromara.sms4j.yunpian.config.YunPianFactory;import org.dromara.sms4j.zhutong.config.ZhutongFactory;import org.springframework.beans.factory.ObjectProvider;import java.util.HashMap;import java.util.List;import java.util.Map;import java.util.ServiceLoader;@Slf4jpublic class SmsBlendsInitializer {private final List<BaseProviderFactory extends SmsBlend, ? extends SupplierConfig>> factoryList;private final SmsConfig smsConfig;private final Map<String, Map<String, Object>> blends;private final ObjectProvider<SmsReadConfig> extendsSmsConfigs;public SmsBlendsInitializer(List<BaseProviderFactory extends SmsBlend, ? extends SupplierConfig>> factoryList,SmsConfig smsConfig,Map<String, Map<String, Object>> blends,ObjectProvider<SmsReadConfig> extendsSmsConfigs) {this.factoryList = factoryList;this.smsConfig = smsConfig;this.blends = blends;this.extendsSmsConfigs = extendsSmsConfigs;onApplicationEvent();}public void onApplicationEvent() {this.registerDefaultFactory();// 注册短信对象工厂ProviderFactoryHolder.registerFactory(factoryList);if (ConfigType.YAML.equals(this.smsConfig.getConfigType())) {//持有初始化配置信息Map<String, Map<String, Object>> blendsInclude = new ConfigCombineMapAdeptor<String, Map<String, Object>>();blendsInclude.putAll(this.blends);int num = 0;for (SmsReadConfig smsReadConfig : extendsSmsConfigs) {String key = SmsReadConfig.class.getSimpleName() + num;Map<String, Object> insideMap = new HashMap<>();insideMap.put(key, smsReadConfig);blendsInclude.put(key, insideMap);num++;}EnvirmentHolder.frozenEnvirmet(smsConfig, blendsInclude);//注册执行器实现SmsProxyFactory.addProcessor(new RestrictedProcessor());SmsProxyFactory.addProcessor(new BlackListProcessor());SmsProxyFactory.addProcessor(new BlackListRecordingProcessor());SmsProxyFactory.addProcessor(new SingleBlendRestrictedProcessor());//如果手机号校验器存在实现,则注册手机号校验器ServiceLoader<PhoneVerify> loader = ServiceLoader.load(PhoneVerify.class);if (loader.iterator().hasNext()) {loader.forEach(f -> {SmsProxyFactory.addProcessor(new CoreMethodParamValidateProcessor(f));});} else {//这里需要把处理器注释掉,否则smsBlend.massTexting(phones, msg)接口调用会抛异常:cant send message to null!,这里改造就不需要这些前置和后置的处理器了//SmsProxyFactory.addProcessor(new CoreMethodParamValidateProcessor(null));}// 解析供应商配置for (String configId : blends.keySet()) {Map<String, Object> configMap = blends.get(configId);Object supplierObj = configMap.get(Constant.SUPPLIER_KEY);String supplier = supplierObj == null ? "" : String.valueOf(supplierObj);supplier = StrUtil.isEmpty(supplier) ? configId : supplier;BaseProviderFactory<SmsBlend, SupplierConfig> providerFactory = (BaseProviderFactory<SmsBlend, org.dromara.sms4j.api.universal.SupplierConfig>) ProviderFactoryHolder.requireForSupplier(supplier);if (providerFactory == null) {log.warn("创建\"{}\"的短信服务失败,未找到供应商为\"{}\"的服务", configId, supplier);continue;}configMap.put("config-id", configId);SmsUtils.replaceKeysSeperator(configMap, "-", "_");JSONObject configJson = new JSONObject(configMap);org.dromara.sms4j.api.universal.SupplierConfig supplierConfig = JSONUtil.toBean(configJson, providerFactory.getConfigClass());SmsFactory.createSmsBlend(supplierConfig);}}}/*** 注册默认工厂实例*/private void registerDefaultFactory() {ProviderFactoryHolder.registerFactory(AlibabaFactory.instance());ProviderFactoryHolder.registerFactory(CloopenFactory.instance());ProviderFactoryHolder.registerFactory(CtyunFactory.instance());ProviderFactoryHolder.registerFactory(EmayFactory.instance());ProviderFactoryHolder.registerFactory(HuaweiFactory.instance());ProviderFactoryHolder.registerFactory(NeteaseFactory.instance());ProviderFactoryHolder.registerFactory(TencentFactory.instance());ProviderFactoryHolder.registerFactory(UniFactory.instance());ProviderFactoryHolder.registerFactory(YunPianFactory.instance());ProviderFactoryHolder.registerFactory(ZhutongFactory.instance());ProviderFactoryHolder.registerFactory(LianLuFactory.instance());ProviderFactoryHolder.registerFactory(DingZhongFactory.instance());ProviderFactoryHolder.registerFactory(QiNiuFactory.instance());if (SmsUtils.isClassExists("com.jdcloud.sdk.auth.CredentialsProvider")) {ProviderFactoryHolder.registerFactory(JdCloudFactory.instance());}log.debug("加载内置运营商完成!");}}
4.测试
4.1 HwSmsService类
package xxxxx.service.impl;import com.alibaba.fastjson.JSON;import lombok.extern.slf4j.Slf4j;import org.dromara.sms4j.api.SmsBlend;import org.dromara.sms4j.api.entity.SmsResponse;import org.dromara.sms4j.core.factory.SmsFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import java.util.List;import java.util.Objects;public class HwSmsService {public SmsBlend getSmsBlend() {SmsBlend hw1 = SmsFactory.getSmsBlend("hw1");return hw1;}public Boolean sendMsg(Listphones, String msg) { SmsBlend smsBlend = this.getSmsBlend();SmsResponse smsResponse = smsBlend.massTexting(phones, msg);log.info("HwSmsService.smsResponse:{}", JSON.toJSONString(smsResponse));if (smsResponse.isSuccess()) {return Boolean.TRUE;}return Boolean.FALSE;}}
4.2 TestController测试类
package xxxxx.controller;import com.alibaba.fastjson.JSON;import com.xxxxx.impl.HwSmsService;import com.xxl.job.core.biz.model.ReturnT;import lombok.extern.slf4j.Slf4j;import org.dromara.sms4j.api.SmsBlend;import org.dromara.sms4j.api.entity.SmsResponse;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.util.Collections;import java.util.List;public class TestController {private HwSmsService hwSmsService;public SmsResponse test3() {SmsBlend smsBlend = hwSmsService.getSmsBlend();SmsResponse response = smsBlend.sendMessage("xxxxxxx手机号码", "【短信签名xxxx】 测试短信发送");log.info("smsResponse:{}", JSON.toJSONString(response));return response;}public SmsResponse test8() {SmsBlend smsBlend = hwSmsService.getSmsBlend();SmsResponse response = smsBlend.massTexting(Collections.singletonList("xxxxxxx手机号码"), "【短信签名xxxx】测试短信发送");log.info("smsResponse:{}", JSON.toJSONString(response));return response;}}