背景
在日常開發中,我們可能會遇到短信驗證之類的需求,這也是我們使用各類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完成短信服務的發送,驗證效果如下: