1. 程式人生 > >【Java】阿里雲簡訊傳送功能實現

【Java】阿里雲簡訊傳送功能實現

前言

在移動端,我們除了使用賬號密碼、第三方社交平臺賬號(例如:微信、QQ、微博等)這幾種登入方式以外,也會通過手機簡訊驗證碼的方式來做登入。
博主最近正在做移動端的手機簡訊驗證登入。原本為了簡單起見,選用的是某個不知名科技公司的簡訊服務,但是收費貴,服務也不太穩定等一系列問題的出現,導致博主開始另尋他路。

簡訊服務選擇

博主挑選了幾家大公司的簡訊服務,以下為各服務官網的產品價格:

在參考了各個服務的費用以及公司系統使用者量等具體情況以後,博主最終選擇了 阿里雲簡訊服務

阿里雲簡訊配置

在我們實現 阿里雲簡訊傳送API 之前,其實在阿里雲平臺上還有一系列的操作需要我們去申請、配置等(例如:開通簡訊服務、獲取AccessKey、建立簡訊的簽名和模板等等)。但是這些並不是本文的重點,所以博主這裡不做詳細的敘述。
大家可以自行參考阿里雲提供的 簡訊服務文件使用指引,根據步驟操作。

API所需配置提取

假設上述配置操作大家已完成,博主這裡用公司已註冊好的模板舉個例子。我們需要提取記錄的配置有以下幾種:

  • AccessKeyID和AccessKeySecret: 關於這個內容大家自行參考官方文件 如何獲取AccessKeyID和AccessKeySecret,步驟寫的也比較詳細,博主在這裡就不做過多的敘述。
  • 簽名名稱: 開啟阿里簡訊服務管理控制檯,按順序點開:國內訊息->簽名管理,我這裡選用阿里贈送的簽名 阿里雲簡訊測試專用
圖1 獲取簽名名稱
  • 模板CODE和內容: 開啟阿里簡訊服務管理控制檯,按順序點開:國內訊息->模板管理->點選對應模板操作中的詳情,我這裡選用阿里贈送的 身份驗證驗證碼 模板。
圖2 獲取模板CODE和內容

對以上的內容先做記錄,因為後續在程式碼中我們相應會用到。

程式碼實現

基本的工作已經做完了,下面我們來看下具體程式碼的實現

1.新增依賴

	<!-- 阿里雲簡訊服務sdk -->
	<dependency>
	    <groupId>com.aliyun</groupId>
	    <artifactId>aliyun-java-sdk-core</artifactId>
	    <version>3.3.1</version>
	</dependency>
	<dependency>
	    <groupId>com.aliyun</groupId>
	    <artifactId>aliyun-java-sdk-dysmsapi</artifactId>
	    <version>1.1.0</version>
	</dependency>

2.阿里雲簡訊構造類

博主這裡採用鏈式的寫法,程式碼中的accessKeyIdaccessKeySecret 需要替換成我們從雲平臺提取的對應值。

/**
 * 阿里雲簡訊構造類
 * Created by Hilox on 2018/11/22 0022.
 */
public class AliSmsBuilder implements Serializable {

    private static final long serialVersionUID = -8277819898310935813L;

    /**
     * 阿里雲簡訊服務accessKeyId
     */
    private String accessKeyId;

    /**
     * 阿里雲簡訊服務accessKeySecret
     */
    private String accessKeySecret;

    /**
     * 必填:待發送手機號
     */
    private String phoneNum;

    /**
     * 必填:簡訊簽名-可在簡訊控制檯中找到
     */
    private String signName;

    /**
     * 必填:簡訊模板-可在簡訊控制檯中找到
     */
    private String templateCode;

    /**
     * 可選:驗證碼
     */
    private String verifyCode;

    /**
     * 可選:上行簡訊擴充套件碼(無特殊需求使用者請忽略此欄位)
     */
    private String smsUpExtendCode;

    /**
     * 可選:outId為提供給業務方擴充套件欄位,最終在簡訊回執訊息中將此值帶回給呼叫者
     */
    private String outId;

    private String defaultConnectTimeout;

    private String defaultReadTimeout;

    public AliSmsBuilder() {
        // 預設初始化
        accessKeyId = "***";   // 替換成自己的阿里雲簡訊accessKeyId
        accessKeySecret = "***";   // 替換成自己的阿里雲簡訊accessKeySecret
        this.defaultConnectTimeout = "10000";
        this.defaultReadTimeout = "10000";
    }

    public AliSmsBuilder(String accessKeyId, String accessKeySecret) {
        // 自定義初始化
        this.accessKeyId = accessKeyId;
        this.accessKeySecret = accessKeySecret;
    }

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

    public String getAccessKeyId() {
        return accessKeyId;
    }

    public AliSmsBuilder setAccessKeySecret(String accessKeySecret) {
        this.accessKeySecret = accessKeySecret;
        return this;
    }

    public String getDefaultConnectTimeout() {
        return defaultConnectTimeout;
    }

    public AliSmsBuilder setDefaultConnectTimeout(String defaultConnectTimeout) {
        this.defaultConnectTimeout = defaultConnectTimeout;
        return this;
    }

    public String getDefaultReadTimeout() {
        return defaultReadTimeout;
    }

    public AliSmsBuilder setDefaultReadTimeout(String defaultReadTimeout) {
        this.defaultReadTimeout = defaultReadTimeout;
        return this;
    }

    public String getAccessKeySecret() {
        return accessKeySecret;
    }

    public AliSmsBuilder setPhoneNum(String phoneNum) {
        this.phoneNum = phoneNum;
        return this;
    }

    public String getPhoneNum() {
        return phoneNum;
    }

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

    public String getSignName() {
        return signName;
    }

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

    public String getTemplateCode() {
        return templateCode;
    }

    public AliSmsBuilder setVerifyCode(String verifyCode) {
        this.verifyCode = verifyCode;
        return this;
    }

    public String getVerifyCode() {
        return verifyCode;
    }

    public AliSmsBuilder setSmsUpExtendCode(String smsUpExtendCode) {
        this.smsUpExtendCode = smsUpExtendCode;
        return this;
    }

    public String getSmsUpExtendCode() {
        return smsUpExtendCode;
    }

    public AliSmsBuilder setOutId(String outId) {
        this.outId = outId;
        return this;
    }

    public String getOutId() {
        return outId;
    }

    @Override
    public String toString() {
        return "AliSmsBuilder{" +
                "accessKeyId='" + accessKeyId + '\'' +
                ", accessKeySecret='" + accessKeySecret + '\'' +
                ", phoneNum='" + phoneNum + '\'' +
                ", signName='" + signName + '\'' +
                ", templateCode='" + templateCode + '\'' +
                ", verifyCode='" + verifyCode + '\'' +
                ", smsUpExtendCode='" + smsUpExtendCode + '\'' +
                ", outId='" + outId + '\'' +
                ", defaultConnectTimeout='" + defaultConnectTimeout + '\'' +
                ", defaultReadTimeout='" + defaultReadTimeout + '\'' +
                '}';
    }

    /**
     * 傳送簡訊訊息
     * @return
     * @throws ClientException
     */
    public String send() throws ClientException {
        return AliSmsSDK.getInstance().sendSmsAli(this);
    }
}

3.阿里雲簡訊SDK

在這裡有一點要強調下,我們從平臺提取的簡訊模板內容為 驗證碼${code},您正在進行身份驗證,打死不要告訴別人哦!,所以在以下程式碼當中我們設定模板引數的JSON串的程式碼為 request.setTemplateParam("{\"code\":\"" + 變數替換值 + "\"}"); 同理,如果你的模板內容是 親愛的${name},您的驗證碼為${code} 那麼在程式碼當中我們設定模板引數的JSON串的程式碼應該是 request.setTemplateParam("{\"name\":\"" + 變數替換值 + "\", \"code\":\"" + 變數替換值 + "\"}"); 。阿里雲對於變數替換值也是有要求的:變數替換值<=6位數字或字母
換句話說以上JSON串該如何寫,主要還是看你的模板內容如何配置。當然 request.setTemplateParam("{\"code\":\"" + 變數替換值 + "\"}"); 這段程式碼也可以忽略(只要對應的模板內容不配置佔位符),因為這並不是必須的。

/**
 * 阿里雲簡訊SDK
 * Created by Hilox on 2018/11/22 0022.
 */
@Slf4j
public class AliSmsSDK {

    // 產品名稱:雲通訊簡訊API產品,開發者無需替換
    private static final String product = "Dysmsapi";
    // 產品域名,開發者無需替換
    private static final String domain = "dysmsapi.aliyuncs.com";

    /**
     * 私有化構造
     */
    private AliSmsSDK() {}

    /**
     * 靜態內部類
     */
    private static class AliSmsSDKHolder {
        private static final AliSmsSDK INSTANCE = new AliSmsSDK();
    }

    /**
     * 選用靜態內部類實現單例:
     *     1.防止懶漢式單例多執行緒額外開銷
     *     2.防止餓漢式單例記憶體資源浪費
     * @return
     */
    public static final AliSmsSDK getInstance() {
        return AliSmsSDKHolder.INSTANCE;
    }

    /**
     * 傳送簡訊驗證碼(阿里雲簡訊平臺)
     * @param aliSmsBuilder
     * @return
     */
    public String sendSmsAli(AliSmsBuilder aliSmsBuilder) throws ClientException {

        log.info("==== 開始傳送簡訊驗證碼訊息體: {} =====", aliSmsBuilder.toString());

        // 設定超時時間-可自行調整
        System.setProperty("sun.net.client.defaultConnectTimeout", aliSmsBuilder.getDefaultConnectTimeout());
        System.setProperty("sun.net.client.defaultReadTimeout", aliSmsBuilder.getDefaultReadTimeout());

        // 初始化acsClient, 暫不支援region化
        IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", aliSmsBuilder.getAccessKeyId(), aliSmsBuilder.getAccessKeySecret());
        DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain);
        IAcsClient acsClient = new DefaultAcsClient(profile);
        // 組裝請求物件-具體描述見控制檯-文件部分內容
        SendSmsRequest request = new SendSmsRequest();
        // 必填:待發送手機號
        request.setPhoneNumbers(aliSmsBuilder.getPhoneNum());
        // 必填:簡訊簽名-可在簡訊控制檯中找到
        request.setSignName(aliSmsBuilder.getSignName());
        // 必填:簡訊模板-可在簡訊控制檯中找到
        request.setTemplateCode(aliSmsBuilder.getTemplateCode());

        String verifyCode = aliSmsBuilder.getVerifyCode();
        // 可選:模板中的變數替換JSON串,如模板內容為"親愛的${name},您的驗證碼為${code}"時,此處的值為 request.setTemplateParam("{\"name\":\"Tom\", \"code\":\"123\"}");
        if (!StringUtils.isEmpty(verifyCode)) {
            request.setTemplateParam("{\"code\":\"" + verifyCode + "\"}");
        }

        String smsUpExtendCode = aliSmsBuilder.getSmsUpExtendCode();
        // 選填-上行簡訊擴充套件碼(無特殊需求使用者請忽略此欄位)
        if (!StringUtils.isEmpty(smsUpExtendCode)) {
            request.setSmsUpExtendCode(smsUpExtendCode);
        }

        String outId = aliSmsBuilder.getOutId();
        // 可選:outId為提供給業務方擴充套件欄位,最終在簡訊回執訊息中將此值帶回給呼叫者
        if (!StringUtils.isEmpty(outId)) {
            request.setOutId(outId);
        }

        //hint 此處可能會丟擲異常,注意catch
        SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request);
        String code = sendSmsResponse.getCode();

        log.info("==== 傳送簡訊驗證碼成功, 返回code: {} =====", code);
        return code;
    }
}

4.阿里簡訊驗證碼返回code列舉

/**
 * 阿里簡訊驗證碼返回code列表
 * Created by Hilox on 2018/11/13 0013.
 */
public enum AliSmsCodeEnum {

    OK("OK", "請求成功"),
    RAM_PERMISSION_DENY("isp.RAM_PERMISSION_DENY", "RAM許可權DENY"),
    OUT_OF_SERVICE("isv.OUT_OF_SERVICE", "業務停機"),
    PRODUCT_UN_SUBSCRIPT("isv.PRODUCT_UN_SUBSCRIPT", "未開通雲通訊產品的阿里雲客戶"),
    PRODUCT_UNSUBSCRIBE("isv.PRODUCT_UNSUBSCRIBE", "產品未開通"),
    ACCOUNT_NOT_EXISTS("isv.ACCOUNT_NOT_EXISTS", "賬戶不存在"),
    ACCOUNT_ABNORMAL("isv.ACCOUNT_ABNORMAL", "賬戶異常"),
    SMS_TEMPLATE_ILLEGAL("isv.SMS_TEMPLATE_ILLEGAL", "簡訊模板不合法"),
    SMS_SIGNATURE_ILLEGAL("isv.SMS_SIGNATURE_ILLEGAL", "簡訊簽名不合法"),
    INVALID_PARAMETERS("isv.INVALID_PARAMETERS", "引數異常"),
    SYSTEM_ERROR("isp.SYSTEM_ERROR", "系統錯誤"),
    MOBILE_NUMBER_ILLEGAL("isv.MOBILE_NUMBER_ILLEGAL", "非法手機號"),
    MOBILE_COUNT_OVER_LIMIT("isv.MOBILE_COUNT_OVER_LIMIT", "手機號碼數量超過限制"),
    TEMPLATE_MISSING_PARAMETERS("isv.TEMPLATE_MISSING_PARAMETERS", "模板缺少變數"),
    BUSINESS_LIMIT_CONTROL("isv.BUSINESS_LIMIT_CONTROL", "業務限流"),
    INVALID_JSON_PARAM("isv.INVALID_JSON_PARAM", "JSON引數不合法,只接受字串值"),
    BLACK_KEY_CONTROL_LIMIT("isv.BLACK_KEY_CONTROL_LIMIT", "黑名單管控"),
    PARAM_LENGTH_LIMIT("isv.PARAM_LENGTH_LIMIT", "引數超出長度限制"),
    PARAM_NOT_SUPPORT_URL("isv.PARAM_NOT_SUPPORT_URL", "不支援URL"),
    AMOUNT_NOT_ENOUGH("isv.AMOUNT_NOT_ENOUGH", "賬戶餘額不足"),
    ;

    private String code;

    private String msg;

    AliSmsCodeEnum(String code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public String getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }

    /**
     * 根據code獲取msg
     * @param code
     * @return
     */
    public static String getMsgByCode(String code) {
        if (StringUtils.isEmpty(code)) {
            return null;
        }
        AliSmsCodeEnum[] values = AliSmsCodeEnum.values();
        for (AliSmsCodeEnum aliSmsCodeEnum : values) {
            if (aliSmsCodeEnum.getCode().equals(code)) {
                return aliSmsCodeEnum.getMsg();
            }
        }
        return null;
    }
}

5.測試用例

博主在這裡簡單的寫了個測試用例,後續呼叫方式大家可以從這裡做參考。
對應的值需要替換成從雲平臺提取出來的相應值。

/**
 * 測試用例模板
 * Created by Hilox on 2018/11/22 0022.
 */
public class AliSmsSDKTest {

    @Test
    public void sendSmsAli() {
        AliSmsBuilder aliSmsBuilder = new AliSmsBuilder();
        String code;
        try {
            code = aliSmsBuilder.setPhoneNum("150****8879")    // 替換成自己的手機號
                    .setSignName("阿里雲簡訊測試專用")  // 替換成自己的阿里雲簡訊服務簽名
                    .setTemplateCode("SMS_108565014") // 替換成自己的阿里雲簡訊模板編號
                    .setVerifyCode("Hilox") // 替換成自己隨機生成的驗證碼
                    .send();
        } catch (ClientException e) {
            e.printStackTrace();
            // 簡訊傳送異常提示
            return;
        }

        // 簡訊異常碼處理
        if (code == null || !"OK".equals(code)) {
            String errorMsg = AliSmsCodeEnum.getMsgByCode(code);
            if (!StringUtils.isEmpty(errorMsg)) {
                // 對應簡訊異常錯誤提示
                return;
            }
            // 簡訊傳送異常提示
        }
    }
}

原始碼傳送門

【原始碼地址】sms-ali

測試效果展示

圖3 測試效果展示