Blame view

SMSSendUtils.java 8.03 KB
涂亚平 committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
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;
    }
}