应用接入阿里云短信服务

川长思鸟来 2022-02-20 16:19 566阅读 0赞

背景

  1. 在日常开发中,我们可能会遇到短信验证之类的需求,这也是我们使用各类app或系统中比较常见的。在对比了各个平台提供的短信服务后,从价格,稳定性,接入便捷性进行考虑,最终选择了阿里云所提供的[短信服务][Link 1],之前在做课设的时候接入过,但是当时没有作总结,此次毕设也有该需求,在完成之后做以下总结。

开通应用

开通阿里云账号,选择短信服务,开通该应用

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poYW5nX2ppYXl1YW4_size_16_color_FFFFFF_t_70

作为第三方短信服务,阿里云提供了多种语言丰富的sdk支持,能够很方便的帮助我们集成常用的接口,加快开发速度。

选择sdk参考,安装java sdk,阿里云官方推荐通过maven形式引入依赖,仓库坐标如下。(原本官方提供的是4.1.0版本,貌似有bug,在开发中执行 IAcsClient client = new DefaultAcsClient(profile); 会报NotSuchMethodException异常,排查了很久无果,切换成4.0.6版本,成功启动)

  1. <dependency>
  2. <groupId>com.aliyun</groupId>
  3. <artifactId>aliyun-java-sdk-core</artifactId>
  4. <version>4.0.6</version>
  5. </dependency>

在安装和使用阿里云Java SDK前,确保已经:

  • 安装Java环境。阿里云Java SDK要求使用JDK1.6或更高版本。
  • 已经注册阿里云账号并生成访问访问密钥(AccessKey)。

创建AccessKey

阿里云官方对AccessKey的说明,概括起来,就是调用所有 API的凭证/验证信息(比如我们待会要调用的短信服务API)

20190414124300624.png

AccessKey信息可以在安全信息管理控制台看到,这里还提供了一种子账号Accesskey的方式,通过权限管理降低AccessKey泄露的风险

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poYW5nX2ppYXl1YW4_size_16_color_FFFFFF_t_70 1

添加短信签名和模板

打开短信服务控制台,选择国内消息,添加短信签名和模板,按照自己业务需求和系统指引填写,审核通过后即可使用

emmm,现在一点半,工作人员应该还没上班,显示待审核

20190414133000565.png

在application.yml中配置服务参数

配置如下:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poYW5nX2ppYXl1YW4_size_16_color_FFFFFF_t_70 2

通过spring ioc将配置装载到spring bean 中去

  1. /**
  2. * 阿里云短信服务配置
  3. *
  4. * @author jiayuan9608@163.com
  5. * 2019-04-01 15:30
  6. * @version 1.0.0
  7. */
  8. @Data
  9. @Component
  10. @ConfigurationProperties(prefix = "aliyun.msg")
  11. public class AliyunMsgConfig {
  12. /**
  13. * 阿里云短信API 是否开启短信接口,0为开启(收费接口),1为关闭,2为开启模拟短信(免费接口)
  14. */
  15. private Integer openMsg;
  16. /**
  17. * 开发者访问id
  18. */
  19. private String accessKeyId;
  20. /**
  21. * 开发者访问密钥
  22. */
  23. private String accessKeysecret;
  24. /**
  25. * 短信签名
  26. */
  27. private String signName;
  28. /**
  29. * 短信模板
  30. */
  31. private String templateCode;
  32. /**
  33. * 随机验证码的范围-最小值
  34. */
  35. private Integer min;
  36. /**
  37. * 随机验证码的范围-最大值
  38. */
  39. private Integer max;
  40. /**
  41. * 产品域名,开发者无需替换
  42. */
  43. private String domain;
  44. /**
  45. * 版本日期
  46. */
  47. private String version;
  48. /**
  49. * 行为
  50. */
  51. private String action;
  52. }

编写短信发送服务

阿里云官方提供了短信发送服务的demo,根据自己实际情况进行改写。

将配置类注入进来,这里使用了自定义springUtil的形式完成注入,采用@Autowired的形式发现始终无法完成初始化,在idea中也看到了AliyunMsgConfig成功装载到spring容器中了,尝试指定bean进行加载也无法解决,emmm有点奇怪,具体原因有待考察。所以最后采用 springUtil 帮助完成了配置的初始化工作。

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

附springUtil代码:

  1. /**
  2. * 自定义Spring相关工具类
  3. *
  4. * @author jiayuan9608@163.com
  5. * 2019-02-20 10:55
  6. * @version 1.0.0
  7. */
  8. @Component
  9. @Slf4j
  10. public class SpringUtil implements ApplicationContextAware {
  11. private static ApplicationContext applicationContext;
  12. @Override
  13. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
  14. if (SpringUtil.applicationContext == null) {
  15. SpringUtil.applicationContext = applicationContext;
  16. }
  17. log.info("SpringUtil loaded into spring bean successed : {}", SpringUtil.applicationContext);
  18. }
  19. //======获取spring bean=======
  20. //获取applicationContext
  21. public static ApplicationContext getApplicationContext() {
  22. return applicationContext;
  23. }
  24. //通过name获取Bean
  25. public static Object getBean(String name) {
  26. return getApplicationContext().getBean(name);
  27. }
  28. //通过class获取Bean
  29. public static <T> T getBean(Class<T> clazz) {
  30. return getApplicationContext().getBean(clazz);
  31. }
  32. //通过name,以及Clazz返回指定的Bean
  33. public static <T> T getBean(String name, Class<T> clazz) {
  34. return getApplicationContext().getBean(name, clazz);
  35. }
  36. }

业务需要,定义一个存放随机验证码map集合,防止多个手机号同时注册导致验证码混乱(覆盖)。注意,像这类公共变量存放,常规操作应该是放在缓存中(比如redis, mongdb等),若通过静态变量存储,当类被卸载的时候,静态变量的生命周期随之结束,我们存储的信息也会随之丢失。

  1. /**
  2. * 随机验证码map集合,防止多个手机号同时注册导致验证码混乱
  3. */
  4. private static Map<String, String> myAuthCodeMap = Maps.newHashMap();

发送短信,这里设置了三种状态,是否开启短信接口(实际发送短信接口需要花钱,所以在配置上面做了个开关=_=,土豪请随意~):0为开启(收费接口),1为关闭,2为开启模拟短信(免费接口),均为业务需要进行的自定义,与官方API无关

  1. public int sendMsg(String phoneNumbers) {
  2. if (1 == aliyunMsgConfig.getOpenMsg()) {
  3. return ErrorCode.MSG_TURN_OFFED;
  4. }
  5. if (2 == aliyunMsgConfig.getOpenMsg()) {
  6. this.buildAuthCode(phoneNumbers);
  7. return ErrorCode.SIMULATION_MSG;
  8. }
  9. if(0 == aliyunMsgConfig.getOpenMsg()) {
  10. return this.doMsgSend(phoneNumbers);
  11. }else{
  12. return ErrorCode.SYSTEM_ERROR;
  13. }
  14. }

核心发送逻辑

  1. private int doMsgSend(String phoneNumbers){
  2. IClientProfile profile = DefaultProfile.getProfile("default",
  3. aliyunMsgConfig.getAccessKeyId(),
  4. aliyunMsgConfig.getAccessKeysecret());
  5. IAcsClient client = new DefaultAcsClient(profile);
  6. String authCode = this.buildAuthCode(phoneNumbers);
  7. CommonRequest request = buildCommonRequest(phoneNumbers, authCode);
  8. try {
  9. CommonResponse response = client.getCommonResponse(request);
  10. System.out.println(response.getData());
  11. return ErrorCode.NO_ERROR;
  12. } catch (ServerException e) {
  13. log.warn("【阿里云短信发送异常】");
  14. e.printStackTrace();
  15. return ErrorCode.SYSTEM_ERROR;
  16. } catch (ClientException e) {
  17. log.warn("【阿里云短信发送异常】");
  18. e.printStackTrace();
  19. return ErrorCode.SYSTEM_ERROR;
  20. }
  21. }

构造发送请求(官方sdk的CommonRequest类没有添加@Builder注解,=_= ,初始化对象时只能一个个set,令人窒息的操作)

  1. private CommonRequest buildCommonRequest(String phoneNumbers, String code) {
  2. CommonRequest request = new CommonRequest();
  3. //request.setProtocol(ProtocolType.HTTPS);
  4. request.setMethod(MethodType.POST);
  5. request.setDomain(aliyunMsgConfig.getDomain());
  6. request.setVersion(aliyunMsgConfig.getVersion());
  7. request.setAction(aliyunMsgConfig.getAction());
  8. request.putQueryParameter("PhoneNumbers", phoneNumbers);
  9. request.putQueryParameter("TemplateCode", aliyunMsgConfig.getTemplateCode());
  10. request.putQueryParameter("SignName", aliyunMsgConfig.getSignName());
  11. request.putQueryParameter("TemplateParam", "{\"code\":" + code + "}");
  12. return request;
  13. }

生成预发送的验证码信息

  1. /**
  2. * 生成6位随机验证码
  3. *
  4. * @return
  5. */
  6. public String buildAuthCode(String phoneNumbers) {
  7. Integer max = aliyunMsgConfig.getMax();
  8. Integer min = aliyunMsgConfig.getMin();
  9. Integer authCode = (new Random().nextInt(max) % (max - min + 1) + min);
  10. myAuthCodeMap.put(phoneNumbers, authCode.toString());
  11. log.info("手机号 "+phoneNumbers+" 短信验证码:"+authCode);
  12. return authCode.toString();
  13. }

编写一些后门服务用于开发调试方便

  1. /**
  2. * 获取当前的6位随机验证码
  3. *
  4. * @return
  5. */
  6. public static String getMyAuthCode(String phoneNumbers) {
  7. return myAuthCodeMap.get(phoneNumbers);
  8. }
  9. /**
  10. * 清除所有的临时随机验证码
  11. */
  12. public static void cleanMyAuthCodeMap() {
  13. myAuthCodeMap.clear();
  14. }
  15. /**
  16. * 清除指定号码的验证码
  17. * @param phoneNumber
  18. */
  19. public static void cleanMyAuthCodeMapByKey(String phoneNumber){
  20. myAuthCodeMap.remove(phoneNumber);
  21. }
  22. /**
  23. * 获取验证码集合,仅供开发测试!客户端不可调用!
  24. *
  25. * @return
  26. */
  27. public static Map<String, String> getMyAuthCodeMap() {
  28. return myAuthCodeMap;
  29. }

短信发送服务完整代码:(注:这里本类实例bean加载到spring容器中设置了懒加载@Lazy,是为了防止springUtil在获取AliyunMsgConfig bean实例的时候,AliyunMsgConfig还没装载完毕,导致找不到指定bean对象而抛出异常)

  1. package cn.jyycode.common.message;
  2. import cn.jyycode.common.config.AliyunMsgConfig;
  3. import cn.jyycode.common.constant.ErrorCode;
  4. import cn.jyycode.common.utils.SpringUtil;
  5. import com.aliyuncs.CommonRequest;
  6. import com.aliyuncs.CommonResponse;
  7. import com.aliyuncs.DefaultAcsClient;
  8. import com.aliyuncs.IAcsClient;
  9. import com.aliyuncs.exceptions.ClientException;
  10. import com.aliyuncs.exceptions.ServerException;
  11. import com.aliyuncs.http.MethodType;
  12. import com.aliyuncs.profile.DefaultProfile;
  13. import com.aliyuncs.profile.IClientProfile;
  14. import lombok.extern.slf4j.Slf4j;
  15. import org.springframework.context.annotation.Lazy;
  16. import org.springframework.stereotype.Component;
  17. import java.util.HashMap;
  18. import java.util.Map;
  19. import java.util.Random;
  20. /**
  21. * 阿里云短信服务
  22. *
  23. * @author jiayuan9608@163.com
  24. * 2019-04-01 15:49
  25. * @version 1.0.0
  26. */
  27. @Slf4j
  28. @Component
  29. @Lazy
  30. public class CommonRpc {
  31. private AliyunMsgConfig aliyunMsgConfig = SpringUtil.getBean(AliyunMsgConfig.class);
  32. /**
  33. * 随机验证码map集合,防止多个手机号同时注册导致验证码混乱
  34. */
  35. private static Map<String, String> myAuthCodeMap = new HashMap<String, String>();
  36. public int sendMsg(String phoneNumbers) {
  37. if (1 == aliyunMsgConfig.getOpenMsg()) {
  38. return ErrorCode.MSG_TURN_OFFED;
  39. }
  40. if (2 == aliyunMsgConfig.getOpenMsg()) {
  41. this.buildAuthCode(phoneNumbers);
  42. return ErrorCode.SIMULATION_MSG;
  43. }
  44. if(0 == aliyunMsgConfig.getOpenMsg()) {
  45. return this.doMsgSend(phoneNumbers);
  46. }else{
  47. return ErrorCode.SYSTEM_ERROR;
  48. }
  49. }
  50. private int doMsgSend(String phoneNumbers){
  51. IClientProfile profile = DefaultProfile.getProfile("default",
  52. aliyunMsgConfig.getAccessKeyId(),
  53. aliyunMsgConfig.getAccessKeysecret());
  54. IAcsClient client = new DefaultAcsClient(profile);
  55. String authCode = this.buildAuthCode(phoneNumbers);
  56. CommonRequest request = buildCommonRequest(phoneNumbers, authCode);
  57. try {
  58. CommonResponse response = client.getCommonResponse(request);
  59. System.out.println(response.getData());
  60. return ErrorCode.NO_ERROR;
  61. } catch (ServerException e) {
  62. log.warn("【阿里云短信发送异常】");
  63. e.printStackTrace();
  64. return ErrorCode.SYSTEM_ERROR;
  65. } catch (ClientException e) {
  66. log.warn("【阿里云短信发送异常】");
  67. e.printStackTrace();
  68. return ErrorCode.SYSTEM_ERROR;
  69. }
  70. }
  71. private CommonRequest buildCommonRequest(String phoneNumbers, String code) {
  72. CommonRequest request = new CommonRequest();
  73. //request.setProtocol(ProtocolType.HTTPS);
  74. request.setMethod(MethodType.POST);
  75. request.setDomain(aliyunMsgConfig.getDomain());
  76. request.setVersion(aliyunMsgConfig.getVersion());
  77. request.setAction(aliyunMsgConfig.getAction());
  78. request.putQueryParameter("PhoneNumbers", phoneNumbers);
  79. request.putQueryParameter("TemplateCode", aliyunMsgConfig.getTemplateCode());
  80. request.putQueryParameter("SignName", aliyunMsgConfig.getSignName());
  81. request.putQueryParameter("TemplateParam", "{\"code\":" + code + "}");
  82. return request;
  83. }
  84. /**
  85. * 生成6位随机验证码
  86. *
  87. * @return
  88. */
  89. public String buildAuthCode(String phoneNumbers) {
  90. Integer max = aliyunMsgConfig.getMax();
  91. Integer min = aliyunMsgConfig.getMin();
  92. Integer authCode = (new Random().nextInt(max) % (max - min + 1) + min);
  93. myAuthCodeMap.put(phoneNumbers, authCode.toString());
  94. log.info("手机号 "+phoneNumbers+" 短信验证码:"+authCode);
  95. return authCode.toString();
  96. }
  97. /**
  98. * 获取当前的6位随机验证码
  99. *
  100. * @return
  101. */
  102. public static String getMyAuthCode(String phoneNumbers) {
  103. return myAuthCodeMap.get(phoneNumbers);
  104. }
  105. /**
  106. * 清除所有的临时随机验证码
  107. */
  108. public static void cleanMyAuthCodeMap() {
  109. myAuthCodeMap.clear();
  110. }
  111. /**
  112. * 清除指定号码的验证码
  113. * @param phoneNumber
  114. */
  115. public static void cleanMyAuthCodeMapByKey(String phoneNumber){
  116. myAuthCodeMap.remove(phoneNumber);
  117. }
  118. /**
  119. * 获取验证码集合,仅供开发测试!
  120. *
  121. * @return
  122. */
  123. public static Map<String, String> getMyAuthCodeMap() {
  124. return myAuthCodeMap;
  125. }
  126. }

至此,一个短信发送验证码服务就完成了,使用者可以通过实例化CommonRpc类,调用sendMsg方法完成短信服务的发送,验证效果如下:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poYW5nX2ppYXl1YW4_size_16_color_FFFFFF_t_70 3

发表评论

表情:
评论列表 (有 0 条评论,566人围观)

还没有评论,来说两句吧...

相关阅读

    相关 阿里服务

    在开发过程中遇到过短信服务,推荐使用阿里云。个人申请阿里云短信服务确实有些困难,下面我先来简单说一下我的个人申请过程(仅供参考)。 1、申请签名流程 作为开发者,我们大

    相关 应用接入阿里服务

    背景        在日常开发中,我们可能会遇到短信验证之类的需求,这也是我们使用各类app或系统中比较常见的。在对比了各个平台提供的短信服务后,从价格,稳定性,接入便捷