集成sms4j修改源码实现发送不带短信模版id的短信

1.前言

1.1 sms4j是什么?

  sms4j:让发送短信变得更简单,是一款开源、优雅、开箱即用、切换灵活。功能丰富等特性的发短信的开源好用开源好用的轮子,支持的短信提供商也比较的多,支持的短信提供厂商有:阿里云、容联云、天翼云、亿美软通、华为云短信、京东云短信、 网易云信、腾讯云短信、合一短信、云片短信、助通短信、鼎众短信、联麓短信、七牛云短信 、创蓝短信、极光短信、布丁云V2 、中国移动 云MAS 、百度云短信、螺丝帽短信、SUBMAIL短信、 单米短信;官网上也有详细的说明,本文以华为云短信为例。

1.2 缘由

  由于最近项目需要一个发送短信的功能,所以我就选择想集成sms4j这个开源的轮子,其实有其他同事写好的接口可以调用,然后我一对比一看,还是集成sms4j实现比较优雅一点,毕竟不用去调用其他的服务接口,本项目中就直接实现了这个功能,避免了网路接口调用,做到了服务自己的高内聚低耦合,然后就看了下sms4j的官网,也看了sms4j的源码,华为云短信写的还是优雅的,就是不支持不带短信模版id的实现,所以我决定修改sms4j的源码来实现,sms4j的厂商yaml配置差异化的配置都是一个map注入的,具体的可以去看下它的源码,写的还是可以的,每一个短信厂商都有一个单列的工厂类,然后会给每个单列的工厂类生成一个SmsBlend 动态代理类(jdk的动态代理类,代理的对象是具体的短信厂商实现类,比如:HuaweiSmsImpl),然后在短信方法调用之前和之后会有一系列的处理器类,具体的可以去看源码。

1.3 官方地址

https://sms4j.com/
https://gitee.com/dromara/sms4j
https://github.com/dromara/SMS4J

  sms4j官方提供了两个版本:2.x和3.x,具体可以参看官方文档,本文使用3.x的版本。

1.4 华为短信接口响应码文档地址

https://support.huaweicloud.com/api-msgsms/sms_05_0050.html#toTop

2.配置

2.1 依赖配置

<dependency>
   <groupId>org.dromara.sms4j</groupId>
   <artifactId>sms4j-spring-boot-starter</artifactId>
   <version>3.2.1</version>
</dependency>

  版本可以看如下官方说明:

https://sms4j.com/title/log.html

2.2 yaml配置

  nacos项目yaml配置如下:

sms:
  # 标注从yml读取配置
  config-type: yaml
  blends:
    # 自定义的标识,也就是configId这里可以是任意值(最好不要是中文)
    hw1:
      # 接入地址
      url: https://smsapi.cn-north-4.myhuaweicloud.com:443
      # 国内短信签名通道号
      sender: xxxxx
      # 厂商标识,标定此配置是哪个厂商,详细请看厂商标识介绍部分
      supplier: huawei
      # 您的accessKey
      access-key-id: xxxxx
      # 您的accessKeySecret
      access-key-secret: xxxxxxx
      # 您的短信签名
      signature: xxxxxx
      # 模板ID 非必须配置,如果使用sendMessage的快速发送需此配置
      #template-id: xxxxxxxx
      # 您的sdkAppId
      sdk-app-id: hw1
      # 自定义的标识,也就是configId这里可以是任意值(最好不要是中文)

  各个厂商的差异配置都是可以配置成key-vule的map的一个元素,然后starter自动装配会自动注入到各自的config配置类中,比如本文的华为云短信就是HuaweiConfig类

3.重写

3.1项目中重写如下类

image-20240730154311072

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;

@Slf4j
public class HuaweiSmsImpl extends AbstractSmsBlend<HuaweiConfig> {

    private int retry = 0;

    public HuaweiSmsImpl(HuaweiConfig config, Executor pool, DelayedTime delayed) {
        super(config, pool, delayed);
    }

    public HuaweiSmsImpl(HuaweiConfig config) {
        super(config);
    }

    @Override
    public String getSupplier() {
        return SupplierConstant.HUAWEI;
    }

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

    @Override
    public SmsResponse sendMessage(String phone, LinkedHashMap<String, String> messages) {
        if (Objects.isNull(messages)) {
            messages = new LinkedHashMap<>();
        }
        return sendMessage(phone, getConfig().getTemplateId(), messages);
    }

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

    @Override
    public SmsResponse massTexting(List<String> phones, String message) {
        return sendMessage(CollUtil.join(phones, ","), message);
    }

    @Override
    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
     * <p>构造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());
        // Nonce
        String 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);
        }
        // PasswordDigest
        String 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() {
                    @Override
                    public void checkClientTrusted(X509Certificate[] chain, String authType) {
                    }

                    @Override
                    public void checkServerTrusted(X509Certificate[] chain, String authType) {
                    }

                    @Override
                    public X509Certificate[] getAcceptedIssuers() {
                        return null;
                    }
                }
        };
        SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, trustAllCerts, null);
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
    }

    /**
     * buildRequestBody
     * <p>构造请求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;


@Slf4j
public 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;

@Slf4j
@Service
public class HwSmsService {


    public SmsBlend getSmsBlend() {
        SmsBlend hw1 = SmsFactory.getSmsBlend("hw1");
        return hw1;
    }


    public Boolean sendMsg(List<String> phones, 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;

@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {
    
    @Autowired
    private HwSmsService hwSmsService;

    @GetMapping("/test3")
    public SmsResponse test3() {
        SmsBlend smsBlend = hwSmsService.getSmsBlend();
        SmsResponse response = smsBlend.sendMessage("xxxxxxx手机号码", "【短信签名xxxx】 测试短信发送");
        log.info("smsResponse:{}", JSON.toJSONString(response));
        return response;
   }

    @GetMapping("/test8")
    public SmsResponse test8() {
       SmsBlend smsBlend = hwSmsService.getSmsBlend();
       SmsResponse response = smsBlend.massTexting(Collections.singletonList("xxxxxxx手机号码"), "【短信签名xxxx】测试短信发送");
       log.info("smsResponse:{}", JSON.toJSONString(response));
       return response;
    }
    
}

  经过上面源码的修改,输入正确的手机号和正确的短信内容(注意这里需要注意的是短信内容里面需要带有短信签名信息【短信签名xxxx】+ 文本字符串(注意不要有-,如果有-会被转换替换为_,具体可以去看源码,不符合你的需求可以改源码实现)),经过上面的修改,这两个接口就可以正常调用了,这两个接口亲测是可以收到短信的,至于项目中使用这两个接口只需要准备手机号和构造文本短信,例如:文本短信模版是:【短信签名xxxx】您的订单:orderNo已经支付,然后使用String的replace(“orderNo”,202407300000018)替换就可构造好短信内容,具体的根据自己的业务来搞一个短信模版,然后替换参数就得到一个文本的字符串,不使用短信模版id也可以发送短信,只需要短信内容里面包含短信签名即可。

5.总结

  经过以上对sms4j的源码改造,实现了华为云发送不带短信模版id的短信,可以去把sms4j的源码拉下来,趴一趴看一看,集成到项目中,然后debug打断点跟一波源码,其实也还是简单的,华为云发短信是这种玩的,其他厂商的也是同样的道理,举一反三,触类旁通,从开源的轮子中可以学习到优秀的设计思想和设计实现,而不是CRUD,就算是写CRUD也要写了优雅、干净、整洁、帅气、复用性-可拓展性-鲁棒性-可读性-可维护性都好一点,这种bug自然是没有啥bug的,养成良好的编码习惯和编码风格可以降低很多的bug,我的分享到此结束了,希望我的分享对你有所启发和帮助,创作不易,都是亲身实践经验总结,不吝分享,还请不要照抄过去就成了你自己的原创,转发请标注原文及作者出处,发现抄袭(不带思考的原模原样的抄袭,没有多大意义,全成为千篇一律的文章,写文章还是要有一点思考、实践的,否则就是坑文水文,没有一点深度,过于肤浅),则直接举报(或者可以联系本人来举报),请一键三连,么么么哒!

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值