SMSSendUtils.java 8.03 KB
package com.zhongzhi.common.utils;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.HmacAlgorithms;
import org.apache.commons.codec.digest.HmacUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.HttpVersion;
import org.apache.http.client.fluent.Request;
import org.apache.http.entity.ContentType;
import springfox.documentation.service.ApiKey;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

/***
 * 短信发送工具类
 */
@Slf4j
public class SMSSendUtils {
    /**
     * 短信平台接口配置中申请,替换为企业自己的API_KEY
     */
    private static  String API_KEY = "01b0312e7d016d2af41a47e5ba2c2748";
    /**
     * 短信平台接口配置中申请,替换为企业自己的SECRET_KEY
     */
    private static String SECRET_KEY = "bd985a3772ca3b362f8b049a3017b6edac604815e34be96631513e29afbcac47";

    private static final String DOMAIN = "https://opassapi.infocloud.cc/";
    private static final String MESSAGE_SEND="message/send";
    private static final String MESSAGE_SEND_BATCH = "message/sendBatch";

    public static String SMS_API_KEY = "sms.api.key";
    public static String SMS_SECRET_KEY = "sms.secret.key";
    public static final int CONNECT_TIMEOUT= 30_000;
    public static final int SOCKET_TIMEOUT = 15_000;

    private static ReentrantLock lock = new ReentrantLock(true);
    private static AtomicBoolean init = new AtomicBoolean(false);

    static void init(){
//        String apiKey = API_KEY;
//        String secretKey = SECRET_KEY;
//        System.setProperty(SMSSendUtils.SMS_API_KEY,API_KEY);
//        System.setProperty(SMSSendUtils.SMS_SECRET_KEY,SECRET_KEY);
//        if(StringUtils.isAnyEmpty(apiKey,secretKey)){
//            throw new NullPointerException("短信发送apiKey、secretKey不能为空。\n解决方式:\n1.代码设置。" +
//                    "System.setProperty(SMSSendUtils.SMS_API_KEY,\"从短信平台接口配置中获取\");" +
//                    "System.setProperty(SMSSendUtils.SMS_SECRET_KEY,\"从短信平台接口配置中获取\");\n" +
//                    "2.启动配置。java -Dsms.api.key=xx -Dsms.secret.key=xxx");
//        }
//        API_KEY = apiKey;
//        SECRET_KEY = secretKey;
    }

    private SMSSendUtils(){}
    /**
     * 短信发送。无参短信模板内容发送,即所有手机号接收到的短信内容一样
     * @param templateCode
     * @param phones 手机号。手机号最大个数限制1000
     * @return
     */
    public static HttpResponse send(String templateCode, String ...phones) throws IOException {

        return send(templateCode,null,phones);
    }

    /**
     * 短信发送。不同手机号对应相同的短信模板变量值,即所有手机号接收到的短信内容一样
     * @param templateCode
     * @param params 模板参数变量值
     * @param phones 手机号。手机号最大个数限制1000
     * @return
     */
    public static HttpResponse send(String  templateCode, LinkedList<String> params, String ... phones) throws IOException {
        //手机号先去重,然后再转换
        return sendBatch(templateCode,params!=null&&!params.isEmpty()?JSON.toJSONString(params):null,Arrays.stream(phones).collect(Collectors.toSet()).stream().collect(Collectors.joining(",")), false);
    }


    /**
     * 短信发送
     * @param templateCode 模板ID
     * @param paramsJson 模板参数
     * @param phonesJson 手机号
     * @param batch  true:使用MESSAGE_SEND_BATCH接口发送;false:使用MESSAGE_SEND接口发送
     * @return HttpResponse
     * @throws IOException
     */
    private static HttpResponse sendBatch(String  templateCode,String paramsJson,String phonesJson,boolean batch) throws IOException {
        if(StringUtils.isAnyEmpty(templateCode)){
            throw new NullPointerException("模板id或手机号不能为空");
        }

        Map<String, Object> headers = getHeaders();
        Map<String, Object> bodyParams = new HashMap<>(4);

        if(batch){
            if(StringUtils.isAnyEmpty(paramsJson)){
                throw new NullPointerException("短信发送所有手机号接收到的短信内容不一样时,模板参数值不能为空");
            }
            bodyParams.put("phonesJson", phonesJson);
            //模板变量内容
            bodyParams.put("templateParamJson", paramsJson);
        }else{
            bodyParams.put("phones", phonesJson);
            //模板变量内容
            if(StringUtils.isNotEmpty(paramsJson)) {
                bodyParams.put("templateParam", paramsJson);
            }
        }
        bodyParams.put("templateCode", templateCode);

        //排序
//        bd985a3772ca3b362f8b049a3017b6edac604815e34be96631513e29afbcac47

        SortedMap<String, Object> sortedMap = new TreeMap<>(bodyParams);
        headers.forEach((k, v) -> sortedMap.put(k, v));
        //生成签名
        headers.put("x-sign", getSignature(SECRET_KEY, sortedMap, HmacAlgorithms.HMAC_SHA_224));

        Request request = Request.Post(DOMAIN+(batch?MESSAGE_SEND_BATCH:MESSAGE_SEND))
                .version(HttpVersion.HTTP_1_1)
                //设置连接超时
                .connectTimeout(CONNECT_TIMEOUT)
                //设置文本读取超时
                .socketTimeout(SOCKET_TIMEOUT);
        headers.forEach((k,v)->request.addHeader(k,String.valueOf(v)));


        return request.bodyString(JSON.toJSONString(bodyParams), ContentType.APPLICATION_JSON)
                .execute()
                .returnResponse();
    }

    private static  Map<String, Object> getHeaders(){
        if(init.get()==false){
            lock.lock();
            try {
                if(init.get()==false) {
                    init.set(true);
                    init();
                }
            }catch (Exception e){
                throw e;
            }finally {
                lock.unlock();
            }
        }

        Map<String, Object> headers = new HashMap<>();
        headers.put("x-api-key", API_KEY);
        headers.put("x-sign-method", HmacAlgorithms.HMAC_SHA_224.getName());
        headers.put("x-nonce", getRandomNickname(10));
        headers.put("x-timestamp", String.valueOf(System.currentTimeMillis()));
        return headers;
    }

    /**
     * 签名生成
     * @param secret
     * @param sortedMap
     * @param hmacAlgorithms
     * @return
     */
    private static String getSignature(String secret, SortedMap<String, Object> sortedMap, HmacAlgorithms hmacAlgorithms) {
        //  将参数拼接为字符串
        //  e.g. "key1=value1&key2=value2"
        StringBuffer plainText = new StringBuffer();
        for (Map.Entry<String, Object> entry : sortedMap.entrySet()) {
            plainText.append(entry.getKey() + "=" + entry.getValue());
            plainText.append("&");
        }
        if(StringUtils.isNotEmpty(plainText)) {
            plainText.deleteCharAt(plainText.length() - 1);
        }
        log.info("\n加密方式:{} \n排序后的请求参数:{}",hmacAlgorithms.getName(),plainText);
        return new HmacUtils(hmacAlgorithms, secret).hmacHex(plainText.toString());
    }

    /**
     * 随机数生成
     * @param length
     * @return
     */
    private static String getRandomNickname(int length) {
        String val = "";
        Random random = new Random();
        for (int i = 0; i < length; i++) {
            // 输出字母还是数字
            String charOrNum = random.nextInt(2) % 2 == 0 ? "char" : "num";
            // 字符串
            if ("char".equalsIgnoreCase(charOrNum)) {
                // 取得大写字母还是小写字母
                int choice = random.nextInt(2) % 2 == 0 ? 65 : 97;
                val += (char) (choice + random.nextInt(26));
            } else if ("num".equalsIgnoreCase(charOrNum)) { // 数字
                val += String.valueOf(random.nextInt(10));
            }
        }
        return val;
    }
}