欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

阿里云接口实现发送短信验证码

程序员文章站 2022-06-04 16:21:55
...


前言–很久没更新过东西了,突然想起来短信,回顾了一下之前的小项目,把阿里短信的内容简要讲述一下.

1. 阿里云后台配置短信相关

1.1 开通短信服务

打开官网,直接搜索短信服务,点击短信控制台进入开通短信服务。
阿里云接口实现发送短信验证码阿里短信服务:https://www.aliyun.com/product/sms
阿里云接口实现发送短信验证码

1.2 添加模板签名

开通之后,配置模板及签名,可根据自己需求配置,审核时间官方给得:
一般模板预计2小时内审核完成,审核通过后可使用。工作时间:9:00-23:00 (法定节日顺延),建议您尽量在18:00前提交申请。

1.3 创建秘钥

AccessKey ID:***************
AccessKey Secret:**********
(建议保存好,很关键得一个AK,具有该账户完全的权限)

1.4 短信需要后台授权–注意点

(之前遇到得一个问题)没授权报错,发送失败

2 java–简单实现短信验证码发送

2.1 引入pom依赖

阿里短信服务文档、sdk下载https://help.aliyun.com/product/44282.html如果不是maven项目直接下载两个jar导入。
以下是maven

<!-- 阿里云短信 -->
<dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>aliyun-java-sdk-core</artifactId>
    <version>4.1.0</version>
</dependency>
<dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>aliyun-java-sdk-dysmsapi</artifactId>
    <version>1.1.0</version>
</dependency>

2.2 application.yml配置阿里AK

ali:
  #阿里AK 换成自己的
  accessKeyId: ************************
  accessKeySecret: *******************
  #短信每天发送最大次数
  sendMaxTime: 10
  #短信失效时间 例如2分钟= 60*1000*2 =120000
  codeFailureTime: 120000

2.3 编写阿里配置文件实体类

package com.jzyb.common.utils.wx;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "ali")
public class AliConfig {

	/** accessKeyId */
	private static String accessKeyId;

	/** accessKeySecret */
	private static String accessKeySecret;
	
	/** 短信每天发送最大次数 */
	private static Integer sendMaxTime;
	
	/** 短信失效时间 */
	private static Long codeFailureTime;
	
	public static Integer getSendMaxTime() {
		return sendMaxTime;
	}

	public void setSendMaxTime(Integer sendMaxTime) {
		AliConfig.sendMaxTime = sendMaxTime;
	}

	public static Long getCodeFailureTime() {
		return codeFailureTime;
	}

	public void setCodeFailureTime(Long codeFailureTime) {
		AliConfig.codeFailureTime = codeFailureTime;
	}

	public static String getAccessKeyId() {
		return accessKeyId;
	}

	public void setAccessKeyId(String accessKeyId) {
		AliConfig.accessKeyId = accessKeyId;
	}

	public static String getAccessKeySecret() {
		return accessKeySecret;
	}

	public void setAccessKeySecret(String accessKeySecret) {
		AliConfig.accessKeySecret = accessKeySecret;
	}

}

2.4 编写阿里工具类

package com.jzyb.common.utils.wx;

import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;

public class AliyunSmsUtil {
   // 产品名称:云通信短信API产品,开发者无需替换
   static final String product = "Dysmsapi";
   // 产品域名,开发者无需替换
   static final String domain = "dysmsapi.aliyuncs.com";

   static final String accessKeyId = AliConfig.getAccessKeyId();
   static final String accessKeySecret = AliConfig.getAccessKeySecret();

   public static SendSmsResponse sendCode(String mobile, String code) throws ClientException {

      // 可自助调整超时时间
      System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
      System.setProperty("sun.net.client.defaultReadTimeout", "10000");

      // 初始化acsClient,暂不支持region化
      IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret);
      DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain);
      IAcsClient acsClient = new DefaultAcsClient(profile);

      // 组装请求对象-具体描述见控制台-文档部分内容
      SendSmsRequest request = new SendSmsRequest();
      // 必填:待发送手机号
      request.setPhoneNumbers(mobile);
      // 必填:短信签名-可在短信控制台中找到
      // request.setSignName("兰考县人力资源和社会保障");
      request.setSignName("巩义人社");
      // 必填:短信模板-可在短信控制台中找到
      request.setTemplateCode("SMS_154500049");
      // 可选:模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为
      request.setTemplateParam("{\"code\":\"" + code + "\"}");

      // 选填-上行短信扩展码(无特殊需求用户请忽略此字段)
      // request.setSmsUpExtendCode("90997");

      // 可选:outId为提供给业务方扩展字段,最终在短信回执消息中将此值带回给调用者
      request.setOutId("yourOutId");

      // hint 此处可能会抛出异常,注意catch
      SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request);

      return sendSmsResponse;
   }

   public static SendSmsResponse sendCode(String signName, String templateCode, String mobile, String code)
         throws ClientException {

      // 可自助调整超时时间
      System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
      System.setProperty("sun.net.client.defaultReadTimeout", "10000");

      // 初始化acsClient,暂不支持region化
      IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret);
      DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain);
      IAcsClient acsClient = new DefaultAcsClient(profile);

      // 组装请求对象-具体描述见控制台-文档部分内容
      SendSmsRequest request = new SendSmsRequest();
      // 必填:待发送手机号
      request.setPhoneNumbers(mobile);
      // 必填:短信签名-可在短信控制台中找到
      request.setSignName(signName);
      // 必填:短信模板-可在短信控制台中找到
      request.setTemplateCode(templateCode);
      // 可选:模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为
      request.setTemplateParam("{\"code\":\"" + code + "\"}");

      // 选填-上行短信扩展码(无特殊需求用户请忽略此字段)
      // request.setSmsUpExtendCode("90997");

      // 可选:outId为提供给业务方扩展字段,最终在短信回执消息中将此值带回给调用者
      request.setOutId("yourOutId");

      // hint 此处可能会抛出异常,注意catch
      SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request);

      return sendSmsResponse;
   }

   public static int getCode() {
      return (int) ((Math.random() * 9 + 1) * 100000);
   }

   public static void main(String[] args) {
      try {
         sendCode("模板名", "SMS_*********", "手机号", getCode() + "");
      } catch (ClientException e) {
         e.printStackTrace();
      }
   }
}

2.5 编写短信缓存

package com.jzyb.common.utils.wx;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.*;

public class Cache {
   // 键值对集合
   private final static Map<String, Entity> map = new HashMap<>();
   // 定时器线程池,用于清除过期缓存
   private final static ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();

   /**
    * 添加缓存
    *
    * @param key
    *            键
    * @param data
    *            值
    */
   public synchronized static void put(String key, Object data) {
      Cache.put(key, data, 0);
   }

   /**
    * 添加缓存
    *
    * @param key
    *            键
    * @param data
    *            值
    * @param expire
    *            过期时间,单位:毫秒, 0表示无限长
    */
   public synchronized static void put(final String key, Object data, long expire) {
      // 清除原键值对
      Cache.remove(key);
      // 设置过期时间
      if (expire > 0) {
         Future future = executor.schedule(new Runnable() {
            @Override
            public void run() {
               // 过期后清除该键值对
               synchronized (Cache.class) {
                  map.remove(key);
               }
            }
         }, expire, TimeUnit.MILLISECONDS);
         map.put(key, new Entity(data, future));
      } else {
         // 不设置过期时间
         map.put(key, new Entity(data, null));
      }
   }

   /**
    * 读取缓存
    *
    * @param key
    *            键
    * @return
    */
   public synchronized static Object get(String key) {
      Entity entity = map.get(key);
      return entity == null ? null : entity.getValue();
   }

   /**
    * 读取缓存
    *
    * @param key
    *            键 * @param clazz 值类型
    * @return
    */
   public synchronized static <T> T get(String key, Class<T> clazz) {
      return clazz.cast(Cache.get(key));
   }

   /**
    * 清除缓存
    *
    * @param key
    * @return
    */
   public synchronized static Object remove(String key) {
      // 清除原缓存数据
      Entity entity = map.remove(key);
      if (entity == null)
         return null;
      // 清除原键值对定时器
      Future future = entity.getFuture();
      if (future != null)
         future.cancel(true);
      return entity.getValue();
   }

   /**
    * 查询当前缓存的键值对数量
    *
    * @return
    */
   public synchronized static int size() {
      return map.size();
   }

   /**
    * 缓存实体类
    */
   private static class Entity {
      // 键值对的value
      private Object value;
      // 定时器Future
      private Future future;

      public Entity(Object value, Future future) {
         this.value = value;
         this.future = future;
      }

      /**
       * 获取值
       *
       * @return
       */
      public Object getValue() {
         return value;
      }

      /**
       * 获取Future对象
       *
       * @return
       */
      public Future getFuture() {
         return future;
      }
   }
}

2.6 编写发送短信随机码–6位数

public static int getCode(){
    return (int)((Math.random()*9+1)*100000);
}

2.7 发送短信接口

@ApiOperation(value = "发送短信验证码", notes = "通过手机号发送短信,参数:手机号")
@ApiImplicitParams({
        @ApiImplicitParam(name = "mobile", value = "15858585858", required = true, paramType = "form", dataType = "String"), })
@GetMapping(value = "sendCode", produces = { "application/json;charset=UTF-8" })
public AjaxResult sendCode(String mobile) {
    try {
        String code = getCode()+"";
        SendSmsResponse sr=AliyunSmsUtil.sendCode("模板名", "模板id",mobile, code);

        if("OK".equals(sr.getCode())) {
            Cache.put(mobile.trim(), code, 120000);
            String code11 = (String) Cache.get(mobile);

            return AjaxResult.success("发送成功");
        }
    } catch (ClientException e) {
        e.printStackTrace();
    }
    return AjaxResult.error("操作失败");
}

2.8 校验验证码有没有过期

	String oldCode = (String) Cache.get(mobile);

	if(StringUtils.isNotEmpty(oldCode)) {

    return AjaxResult.warn("之前验证码还没失效");

}

2.9 判断手机号是否为空,手机格式是否有误

    if(!isPhone(mobile)) {

    	return AjaxResult.warn("手机号格式有误");

    }

   public static boolean isPhone(String phone) {

        String regex = "^((13[0-9])|(14[5,7,9])|(15([0-3]|[5-9]))|(166)|(17[0,1,3,5,6,7,8])|(18[0-9])|(19[8|9]))\\d{8}$";

        if (phone.length() != 11) {

            //MToast.showToast("手机号应为11位数");

            return false;

        } else {

            Pattern p = Pattern.compile(regex);

            Matcher m = p.matcher(phone);

            boolean isMatch = m.matches();

            //LogUtil.e(isMatch);

            if (!isMatch) {

               // MToast.showToast("请填入正确的手机号");

            }

            return isMatch;

        }

    }

2.10 验证成功后,移除缓存中的手机号验证码

Cache.remove(webUser.getMobile());

如果没啥意外,即可发送成功!

{
  "msg": "发送成功",
  "code": 0
}

3 修改短信接口

3.1 添加短信类型

 	@ApiOperation(value = "发送短信验证码", notes = "通过手机号发送短信,参数:手机号,type类别type 1.注册 2.修改密码 3.修改手机号 4 修改个人信息 5 忘记密码")
	@ApiImplicitParams({
	@ApiImplicitParam(name = "mobile", value = "15858585858", required = true, paramType = "form", dataType = "String"), 
	@ApiImplicitParam(name = "type", value = "2", required = true, paramType = "form", dataType = "String"),})
	@GetMapping(value = "sendCode")
	public AjaxResult sendCode(String mobile,String type) {
    	if(StringUtils.isNotBlank(mobile)&&StringUtils.isNotBlank(type)){
    	//类别不为空的情况
    	}else{
    	//返回参数为空
    	}

3.2 获取配置文件中短信时间跟最大次数

if(webSmsLogService.countByMobile(mobile)>AliConfig.getSendMaxTime()) {
    return AjaxResult.warn("当天接收验证码超限制");
 }

3.3 对特别类型下进行校验

/* 
* 1.注册:拿注册举例,如果用户表已经有了改用户,就没有必要给这个人发送短信,因为已经注册过
* 2.忘记密码:如果用户表根本就没有这个手机号,就没有必要发忘记密码的短信--可以根据自己短信种类做判断。规避短信滥用
*/
if("1".equals(type)) {
    	try {
    		//判断该手机号是否已经存在--存在表示用户已经注册过,不存在的情况才能发注册的验证码
    		if(webUserService.checkMobileSole(mobile)) {
    			return AjaxResult.warn("手机号已经存在");
    		}
    		String oldCode = (String) Cache.get(mobile);
    		if(StringUtils.isNotEmpty(oldCode)) {
    			return AjaxResult.warn("之前验证码还没失效");
    		}
    		String code = getCode()+"";
    		SendSmsResponse sr=AliyunSmsUtil.sendCode("模板名", "模板id",mobile, code);

    		if("OK".equals(sr.getCode())) {
    			if("OK".equals(sr.getCode())) {
				//发送成功将手机号 验证码 失效时间保存到Cache中
				Cache.put(mobile.trim(), code, AliConfig.getCodeFailureTime());
		 		
		 		return AjaxResult.success("发送成功");
			}
		} catch (ClientException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return AjaxResult.error("调用阿里短信验证码失败");
		}
 }

3.4 动态设置失效时间

	Cache.put(mobile.trim(), code, AliConfig.getCodeFailureTime());

3.5 规定时间内没有失效的校验

	String oldCode = (String) Cache.get(mobile);
  	if(StringUtils.isNotEmpty(oldCode)) {
  		return AjaxResult.warn("之前验证码还没失效");
	}

3.6新建表,保存短信日志,

CREATE TABLE `sys_sms_log` (
	  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
	  `phone` varchar(11) DEFAULT NULL COMMENT '手机号',
	  `send_time` datetime DEFAULT NULL COMMENT '发送时间',
	  `send_status` char(1) DEFAULT NULL COMMENT '发送状态:0失败;1成功',
	  `content` varchar(100) DEFAULT NULL COMMENT '发送内容',
	  `send_type` char(1) DEFAULT NULL COMMENT '调用方式:1.注册 2.修改密码 3.修改手机号 4 修改个人信息 5 忘记密码.',
	  `result_code` varchar(50) DEFAULT NULL COMMENT '短信运营商的返回码',
	  `send_bizId` varchar(50) DEFAULT NULL COMMENT '短信发送回执ID',
	  `send_ip` varchar(20) DEFAULT NULL COMMENT '发送短信的IP地址',
	  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='短信发送日志'

每次发送成功后将信息保存短信日志表

SendSmsResponse sr=AliyunSmsUtil.sendCode("模板名", "模板id",mobile, code);
if("OK".equals(sr.getCode())) {
		//发送成功将手机号 验证码 失效时间保存到Cache中
		Cache.put(mobile.trim(), code, AliConfig.getCodeFailureTime());
 		String code11 = (String) Cache.get(mobile);
 		WebSmsLog smslog = new WebSmsLog();
  		smslog.setContent("");
  		smslog.setPhone(mobile);
  		smslog.setResultCode("OK");
  		smslog.setSendBizid(sr.getRequestId());
  		//smslog.setSendIp(sendIp);
  		smslog.setSendStatus(1);
  		smslog.setSendTime(DateUtils.getNowDate());
  		smslog.setSendType(type);
 		sysSmsLogService.insertSysSmsLog(smslog);
 			
 		return AjaxResult.success("发送成功");
}
		    		

3.8 结果图

阿里云接口实现发送短信验证码
发送结果
阿里云接口实现发送短信验证码

4 注意点

4.1 模板签名正确性

	AK --模板名--模板id这些都是对应的,项目多了有时候会把另外一个项目的AK拿到这个项目用,但是模板id却是这个项目的,尽量多审查几遍,保证模板设置要与控制台的短信模板相对应

4.2 授权问题

(遇到过一次)需要阿里后台点一下

4.3 尽量把次数,失效时间,AK写到配置文件,方便修改

4.4 谢谢点赞 ^o^

本人小白,以上是自己的一知半解,有任何问题请留言评论,及时改正,及时回复,互相学习互相进步。手打2小时,也希望对新手有帮助,看的开心话点赞关注。原创–转载请备明出处–叩谢!