免 JAR 依赖纯 API 调用阿里云短信接口

Posted sp42a

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了免 JAR 依赖纯 API 调用阿里云短信接口相关的知识,希望对你有一定的参考价值。

缘起

发送短信一般依赖于各大厂商的 SDK,用起来也比较简单,例如阿里云的加入以下 Maven 依赖。

<dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>dysmsapi20170525</artifactId>
    <version>2.0.4</version>
</dependency>

可见 2017 版本比较旧的,而且又加入一大堆其他三方的依赖,


我心想,发个短信也不容易啊,于是瞧瞧有没有纯 HTTP API 的,——其实一般是有的,直接调用吧,忘记 jar 包、忘记 Maven。——阿里云的文档写的挺好的,一个 Java main 函数就是完整的程序

重构

main 函数一测就通过,不过比较粗糙,咱们重构之~~首先定义一个 bean AliyunSmsEntity ,描述用到的各个字段。

/**
 * 阿里云短信实体
 */
public class AliyunSmsEntity {
    private String accessKeyId;

    private String accessSecret;

    private String phoneNumbers;

    private String signName;

    private String templateCode;

    private String templateParam;

    public String getAccessKeyId() {
        return accessKeyId;
    }

    public void setAccessKeyId(String accessKeyId) {
        this.accessKeyId = accessKeyId;
    }

    public String getAccessSecret() {
        return accessSecret;
    }

    public void setAccessSecret(String accessSecret) {
        this.accessSecret = accessSecret;
    }

    public String getPhoneNumbers() {
        return phoneNumbers;
    }

    public void setPhoneNumbers(String phoneNumbers) {
        this.phoneNumbers = phoneNumbers;
    }

    public String getSignName() {
        return signName;
    }

    public void setSignName(String signName) {
        this.signName = signName;
    }

    public String getTemplateParam() {
        return templateParam;
    }

    public void setTemplateParam(String templateParam) {
        this.templateParam = templateParam;
    }

    public String getTemplateCode() {
        return templateCode;
    }

    public void setTemplateCode(String templateCode) {
        this.templateCode = templateCode;
    }
}

接着是组装参数去发送,比较复杂,我拆分了几个小函数,并较多地利用了 Spring 自带的工具函数,例如 URL 编码、Base64、UUID 等,最后发送 GET 请求采用了我自己封装的 HTTP 请求工具 NetUtil。

import com.ajaxjs.net.http.NetUtil;
import com.ajaxjs.util.StrUtil;
import com.ajaxjs.util.logger.LogHelper;
import com.ajaxjs.util.map.JsonHelper;
import org.springframework.util.AlternativeJdkIdGenerator;
import org.springframework.util.Base64Utils;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * 阿里云发送短信
 */
public class AliyunSMS {
    private static final LogHelper LOGGER = LogHelper.getLog(AliyunSMS.class);

    /**
     * 请求的时间戳。按照ISO8601 标准表示,并需要使用UTC时间,格式为yyyy-MM-ddTHH:mm:ssZ。
     */
    static String getTimestamp() {
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
        df.setTimeZone(new SimpleTimeZone(0, "GMT"));// 这里一定要设置GMT时区

        return df.format(new Date());
    }

    /**
     * 初始化请求参数
     *
     * @return
     */
    static Map<String, String> initParams() {
        Map<String, String> paras = new HashMap<>();
        // 1. 系统参数
        paras.put("SignatureMethod", "HMAC-SHA1");
        paras.put("SignatureNonce", new AlternativeJdkIdGenerator().generateId().toString());
        paras.put("SignatureVersion", "1.0");
        paras.put("Timestamp", getTimestamp());
        paras.put("Format", "JSON");
        paras.put("Action", "SendSms");
        paras.put("Version", "2017-05-25");

        return paras;
    }

    /**
     * 设置业务API参数
     *
     * @param paras
     * @param entity
     */
    static void setParams(Map<String, String> paras, AliyunSmsEntity entity) {
        // 2. 业务API参数
        paras.put("AccessKeyId", entity.getAccessKeyId());
        paras.put("PhoneNumbers", entity.getPhoneNumbers());
        paras.put("SignName", entity.getSignName());
        paras.put("TemplateParam", entity.getTemplateParam());
        paras.put("TemplateCode", entity.getTemplateCode());

        // 3. 去除签名关键字Key
        if (paras.containsKey("Signature"))
            paras.remove("Signature");
    }

    /**
     * 根据参数Key排序(顺序)
     *
     * @param paras
     * @return
     */
    static String sort(Map<String, String> paras) {
        // 4. 参数KEY排序
        TreeMap<String, String> sortParas = new TreeMap<>();
        sortParas.putAll(paras);

        // 5. 构造待签名的字符串
        Iterator<String> it = sortParas.keySet().iterator();
        StringBuilder sortQueryStringTmp = new StringBuilder();

        while (it.hasNext()) {
            String key = it.next();
            sortQueryStringTmp.append("&").append(StrUtil.urlEncode(key)).append("=").append(StrUtil.urlEncode(paras.get(key)));
        }

        return sortQueryStringTmp.toString();
    }

    /**
     * 构造待签名的请求串
     *
     * @param sortQueryStringTmp
     * @param accessSecret
     * @return
     */
    static String makeSignature(String sortQueryStringTmp, String accessSecret) {
        StringBuilder stringToSign = new StringBuilder();
        stringToSign.append("GET").append("&");

        stringToSign.append(StrUtil.urlEncode("/")).append("&");
        stringToSign.append(StrUtil.urlEncode(sortQueryStringTmp.substring(1)));// 去除第一个多余的&符号

        String sign = sign(accessSecret + "&", stringToSign.toString());

        // 6. 签名最后也要做特殊URL编码
        return StrUtil.urlEncode(sign);
    }

    /**
     * 签名采用HmacSHA1算法 + Base64,编码采用UTF-8
     *
     * @param accessSecret
     * @param stringToSign
     * @return
     */
    static String sign(String accessSecret, String stringToSign) {
        try {
            Mac mac = Mac.getInstance("HmacSHA1");
            mac.init(new SecretKeySpec(accessSecret.getBytes("UTF-8"), "HmacSHA1"));
            byte[] signData = mac.doFinal(stringToSign.getBytes("UTF-8"));

            return Base64Utils.encodeToString(signData);
        } catch (Exception e) {
            LOGGER.warning(e);
            return null;
        }
    }

    private final static String SMS_API = "http://dysmsapi.aliyuncs.com/?Signature=";

    /**
     * @param entity
     * @return true 表示发送成功
     */
    public static String send(AliyunSmsEntity entity) {
        Map<String, String> paras = initParams();
        setParams(paras, entity);

        String sortQueryStringTmp = sort(paras);
        String signature = makeSignature(sortQueryStringTmp, entity.getAccessSecret());

        // 最终打印出合法GET请求的URL
        String url = SMS_API + signature + sortQueryStringTmp;

        String json = NetUtil.get(url);
        LOGGER.info("发送短信[{0}],结果:[{1}]", url, json);
        Map<String, Object> map = JsonHelper.parseMap(json);

        return "OK".equals(map.get("Code")) ? "OK" : map.get("Message").toString();
    }
}

使用方法如下。

public static void main(String[] args) {
    AliyunSmsEntity entity = new AliyunSmsEntity();
    entity.setAccessKeyId("xxx");
    entity.setAccessSecret("xxx");
    entity.setSignName("测试");
    entity.setTemplateCode("SMS_xxx");
    entity.setTemplateParam("{\\"code\\":878799}");
    entity.setPhoneNumbers("xxx");

    System.out.println(send(entity));
}

测试通过!没那么多 jar 包,简单清爽!

以上是关于免 JAR 依赖纯 API 调用阿里云短信接口的主要内容,如果未能解决你的问题,请参考以下文章

各位大神,Python怎么调用阿里云API

各位大神,Python怎么调用阿里云API

laravel 怎么响应阿里云短信异步通知接口?

java调用阿里云短信接口

[python][工具]阿里云平台短信验证功能

[python][工具]阿里云平台短信验证功能