應用接入阿里雲短信服務

背景

       在日常開發中,我們可能會遇到短信驗證之類的需求,這也是我們使用各類app或系統中比較常見的。在對比了各個平臺提供的短信服務後,從價格,穩定性,接入便捷性進行考慮,最終選擇了阿里雲所提供的短信服務,之前在做課設的時候接入過,但是當時沒有作總結,此次畢設也有該需求,在完成之後做以下總結。

開通應用

開通阿里雲賬號,選擇短信服務,開通該應用

作爲第三方短信服務,阿里雲提供了多種語言豐富的sdk支持,能夠很方便的幫助我們集成常用的接口,加快開發速度。

選擇sdk參考,安裝java sdk,阿里雲官方推薦通過maven形式引入依賴,倉庫座標如下。(原本官方提供的是4.1.0版本,貌似有bug,在開發中執行 IAcsClient client = new DefaultAcsClient(profile);  會報NotSuchMethodException異常,排查了很久無果,切換成4.0.6版本,成功啓動)

<dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>aliyun-java-sdk-core</artifactId>
    <version>4.0.6</version>
</dependency>

在安裝和使用阿里雲Java SDK前,確保已經:

  • 安裝Java環境。阿里雲Java SDK要求使用JDK1.6或更高版本。
  • 已經註冊阿里雲賬號並生成訪問訪問密鑰(AccessKey)。

創建AccessKey

阿里雲官方對AccessKey的說明,概括起來,就是調用所有 API的憑證/驗證信息(比如我們待會要調用的短信服務API)

AccessKey信息可以在安全信息管理控制檯看到,這裏還提供了一種子賬號Accesskey的方式,通過權限管理降低AccessKey泄露的風險

添加短信簽名和模板

打開短信服務控制檯,選擇國內消息,添加短信簽名和模板,按照自己業務需求和系統指引填寫,審覈通過後即可使用

emmm,現在一點半,工作人員應該還沒上班,顯示待審覈

在application.yml中配置服務參數

配置如下:

通過spring ioc將配置裝載到spring bean 中去

/**
 * 阿里雲短信服務配置
 *
 * @author [email protected]
 * 2019-04-01 15:30
 * @version 1.0.0
 */

@Data
@Component
@ConfigurationProperties(prefix = "aliyun.msg")
public class AliyunMsgConfig {

    /**
     * 阿里雲短信API  是否開啓短信接口,0爲開啓(收費接口),1爲關閉,2爲開啓模擬短信(免費接口)
     */
    private Integer openMsg;


    /**
     * 開發者訪問id
     */
    private String accessKeyId;

    /**
     * 開發者訪問密鑰
     */
    private String accessKeysecret;

    /**
     * 短信簽名
     */
    private String signName;

    /**
     * 短信模板
     */
    private String templateCode;

    /**
     * 隨機驗證碼的範圍-最小值
     */
    private Integer min;

    /**
     * 隨機驗證碼的範圍-最大值
     */
    private Integer max;

    /**
     * 產品域名,開發者無需替換
     */
    private String domain;

    /**
     * 版本日期
     */
    private String version;

    /**
     * 行爲
     */
    private String action;
}

編寫短信發送服務

阿里雲官方提供了短信發送服務的demo,根據自己實際情況進行改寫。

將配置類注入進來,這裏使用了自定義springUtil的形式完成注入,採用@Autowired的形式發現始終無法完成初始化,在idea中也看到了AliyunMsgConfig成功裝載到spring容器中了,嘗試指定bean進行加載也無法解決,emmm有點奇怪,具體原因有待考察。所以最後採用 springUtil 幫助完成了配置的初始化工作。

private AliyunMsgConfig aliyunMsgConfig = SpringUtil.getBean(AliyunMsgConfig.class);

附springUtil代碼:

/**
 * 自定義Spring相關工具類
 *
 * @author [email protected]
 * 2019-02-20 10:55
 * @version 1.0.0
 */
@Component
@Slf4j
public class SpringUtil implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if (SpringUtil.applicationContext == null) {
            SpringUtil.applicationContext = applicationContext;
        }
        log.info("SpringUtil loaded into spring bean successed : {}", SpringUtil.applicationContext);
    }
    //======獲取spring bean=======

    //獲取applicationContext
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    //通過name獲取Bean
    public static Object getBean(String name) {
        return getApplicationContext().getBean(name);
    }

    //通過class獲取Bean
    public static <T> T getBean(Class<T> clazz) {
        return getApplicationContext().getBean(clazz);
    }

    //通過name,以及Clazz返回指定的Bean
    public static <T> T getBean(String name, Class<T> clazz) {
        return getApplicationContext().getBean(name, clazz);
    }
}

業務需要,定義一個存放隨機驗證碼map集合,防止多個手機號同時註冊導致驗證碼混亂(覆蓋)

    /**
     * 隨機驗證碼map集合,防止多個手機號同時註冊導致驗證碼混亂
     */
    private static Map<String, String> myAuthCodeMap = Maps.newHashMap();

發送短信,這裏設置了三種狀態,是否開啓短信接口(實際發送短信接口需要花錢,所以在配置上面做了個開關=_=,土豪請隨意~):0爲開啓(收費接口),1爲關閉,2爲開啓模擬短信(免費接口),均爲業務需要進行的自定義,與官方API無關

public int sendMsg(String phoneNumbers) {
        if (1 == aliyunMsgConfig.getOpenMsg()) {
            return ErrorCode.MSG_TURN_OFFED;
        }
        if (2 == aliyunMsgConfig.getOpenMsg()) {
            this.buildAuthCode(phoneNumbers);
            return ErrorCode.SIMULATION_MSG;
        }
        if(0 == aliyunMsgConfig.getOpenMsg()) {
            return this.doMsgSend(phoneNumbers);
        }else{
            return ErrorCode.SYSTEM_ERROR;
        }
    }

核心發送邏輯

private int doMsgSend(String phoneNumbers){
        IClientProfile profile = DefaultProfile.getProfile("default",
                aliyunMsgConfig.getAccessKeyId(),
                aliyunMsgConfig.getAccessKeysecret());
        IAcsClient client = new DefaultAcsClient(profile);
        String authCode = this.buildAuthCode(phoneNumbers);
        CommonRequest request = buildCommonRequest(phoneNumbers, authCode);
        try {
            CommonResponse response = client.getCommonResponse(request);
            System.out.println(response.getData());
            return ErrorCode.NO_ERROR;
        } catch (ServerException e) {
            log.warn("【阿里雲短信發送異常】");
            e.printStackTrace();
            return ErrorCode.SYSTEM_ERROR;
        } catch (ClientException e) {
            log.warn("【阿里雲短信發送異常】");
            e.printStackTrace();
            return ErrorCode.SYSTEM_ERROR;
        }
    }

構造發送請求(官方sdk的CommonRequest類沒有添加@Builder註解,=_= ,只能一個個set,令人窒息的操作)

private CommonRequest buildCommonRequest(String phoneNumbers, String code) {
        CommonRequest request = new CommonRequest();
        //request.setProtocol(ProtocolType.HTTPS);
        request.setMethod(MethodType.POST);
        request.setDomain(aliyunMsgConfig.getDomain());
        request.setVersion(aliyunMsgConfig.getVersion());
        request.setAction(aliyunMsgConfig.getAction());
        request.putQueryParameter("PhoneNumbers", phoneNumbers);
        request.putQueryParameter("TemplateCode", aliyunMsgConfig.getTemplateCode());
        request.putQueryParameter("SignName", aliyunMsgConfig.getSignName());
        request.putQueryParameter("TemplateParam", "{\"code\":" + code + "}");
        return request;
    }

生成預發送的驗證碼信息

/**
     * 生成6位隨機驗證碼
     *
     * @return
     */
    public String buildAuthCode(String phoneNumbers) {
        Integer max = aliyunMsgConfig.getMax();
        Integer min = aliyunMsgConfig.getMin();
        Integer authCode = (new Random().nextInt(max) % (max - min + 1) + min);
        myAuthCodeMap.put(phoneNumbers, authCode.toString());
        log.info("手機號 "+phoneNumbers+" 短信驗證碼:"+authCode);
        return authCode.toString();
    }

編寫一些後門服務用於開發調試方便

/**
     * 獲取當前的6位隨機驗證碼
     *
     * @return
     */
    public static String getMyAuthCode(String phoneNumbers) {
        return myAuthCodeMap.get(phoneNumbers);
    }

    /**
     * 清除所有的臨時隨機驗證碼
     */
    public static void cleanMyAuthCodeMap() {
        myAuthCodeMap.clear();
    }

    /**
     * 清除指定號碼的驗證碼
     * @param phoneNumber
     */
    public static void cleanMyAuthCodeMapByKey(String phoneNumber){
        myAuthCodeMap.remove(phoneNumber);
    }

    /**
     * 獲取驗證碼集合,僅供開發測試!客戶端不可調用!
     *
     * @return
     */
    public static Map<String, String> getMyAuthCodeMap() {
        return myAuthCodeMap;
    }

短信發送服務完整代碼:(注:這裏本類實例bean加載到spring容器中設置了懶加載@Lazy,是爲了防止springUtil在獲取AliyunMsgConfig bean實例的時候,AliyunMsgConfig還沒裝載完畢,導致找不到指定bean對象而拋出異常)

package cn.jyycode.common.message;

import cn.jyycode.common.config.AliyunMsgConfig;
import cn.jyycode.common.constant.ErrorCode;
import cn.jyycode.common.utils.SpringUtil;
import com.aliyuncs.CommonRequest;
import com.aliyuncs.CommonResponse;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.exceptions.ServerException;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;

/**
 * 阿里雲短信服務
 *
 * @author [email protected]
 * 2019-04-01 15:49
 * @version 1.0.0
 */
@Slf4j
@Component
@Lazy
public class CommonRpc {

    private AliyunMsgConfig aliyunMsgConfig = SpringUtil.getBean(AliyunMsgConfig.class);

    /**
     * 隨機驗證碼map集合,防止多個手機號同時註冊導致驗證碼混亂
     */
    private static Map<String, String> myAuthCodeMap = new HashMap<String, String>();

    public int sendMsg(String phoneNumbers) {
        if (1 == aliyunMsgConfig.getOpenMsg()) {
            return ErrorCode.MSG_TURN_OFFED;
        }
        if (2 == aliyunMsgConfig.getOpenMsg()) {
            this.buildAuthCode(phoneNumbers);
            return ErrorCode.SIMULATION_MSG;
        }
        if(0 == aliyunMsgConfig.getOpenMsg()) {
            return this.doMsgSend(phoneNumbers);
        }else{
            return ErrorCode.SYSTEM_ERROR;
        }
    }

    private int doMsgSend(String phoneNumbers){
        IClientProfile profile = DefaultProfile.getProfile("default",
                aliyunMsgConfig.getAccessKeyId(),
                aliyunMsgConfig.getAccessKeysecret());
        IAcsClient client = new DefaultAcsClient(profile);
        String authCode = this.buildAuthCode(phoneNumbers);
        CommonRequest request = buildCommonRequest(phoneNumbers, authCode);
        try {
            CommonResponse response = client.getCommonResponse(request);
            System.out.println(response.getData());
            return ErrorCode.NO_ERROR;
        } catch (ServerException e) {
            log.warn("【阿里雲短信發送異常】");
            e.printStackTrace();
            return ErrorCode.SYSTEM_ERROR;
        } catch (ClientException e) {
            log.warn("【阿里雲短信發送異常】");
            e.printStackTrace();
            return ErrorCode.SYSTEM_ERROR;
        }
    }

    private CommonRequest buildCommonRequest(String phoneNumbers, String code) {
        CommonRequest request = new CommonRequest();
        //request.setProtocol(ProtocolType.HTTPS);
        request.setMethod(MethodType.POST);
        request.setDomain(aliyunMsgConfig.getDomain());
        request.setVersion(aliyunMsgConfig.getVersion());
        request.setAction(aliyunMsgConfig.getAction());
        request.putQueryParameter("PhoneNumbers", phoneNumbers);
        request.putQueryParameter("TemplateCode", aliyunMsgConfig.getTemplateCode());
        request.putQueryParameter("SignName", aliyunMsgConfig.getSignName());
        request.putQueryParameter("TemplateParam", "{\"code\":" + code + "}");
        return request;
    }

    /**
     * 生成6位隨機驗證碼
     *
     * @return
     */
    public String buildAuthCode(String phoneNumbers) {
        Integer max = aliyunMsgConfig.getMax();
        Integer min = aliyunMsgConfig.getMin();
        Integer authCode = (new Random().nextInt(max) % (max - min + 1) + min);
        myAuthCodeMap.put(phoneNumbers, authCode.toString());
        log.info("手機號 "+phoneNumbers+" 短信驗證碼:"+authCode);
        return authCode.toString();
    }

    /**
     * 獲取當前的6位隨機驗證碼
     *
     * @return
     */
    public static String getMyAuthCode(String phoneNumbers) {
        return myAuthCodeMap.get(phoneNumbers);
    }

    /**
     * 清除所有的臨時隨機驗證碼
     */
    public static void cleanMyAuthCodeMap() {
        myAuthCodeMap.clear();
    }

    /**
     * 清除指定號碼的驗證碼
     * @param phoneNumber
     */
    public static void cleanMyAuthCodeMapByKey(String phoneNumber){
        myAuthCodeMap.remove(phoneNumber);
    }

    /**
     * 獲取驗證碼集合,僅供開發測試!
     *
     * @return
     */
    public static Map<String, String> getMyAuthCodeMap() {
        return myAuthCodeMap;
    }
}

至此,一個短信發送驗證碼服務就完成了,使用者可以通過實例化CommonRpc類,調用sendMsg完成短信服務的發送,驗證效果如下:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章