阿里云短信服务接口(Springboot例)
一、准备工作
1、进入阿里云官网,注册,登录,进入短信服务模块
顶部菜单——产品——热门产品——短信服务
免费开通(学习阶段用免费的,测试用),正式项目里需要花钱购买
2、进入费用页面,充值点测试费用。一条短信几分钱,一般充一块钱,测试就够了。
3、左侧栏——快速学习——API发送测试——绑定测试手机号。学习测试阶段,只能给自己绑定的手机号发短信。给别的手机号发短信,收不到。
测试签名模板,就是短信内容的格式。测试的我们不能自定义,只能用官方提供的。购买之后,我们可以自定义短信模板。
完成操作后,点击“调用API发送短信”,进入控制台。
4、点击页面右侧“获取AK”,获取AccessKey ID和AccessKey Secret,代码里要用到。
点击“继续使用AccessKey”,也可以创建子用户。其实就是主用户和次用户的区别。
5、刚开始是没有AccessKey的,我们新创建一个。
创建好的id为账号,Secret为密码,我们需要记住或者保存下来(后期需要也可以过来查看,但是需要验证码才能获取密码,很麻烦)。建议存起来。
至此,阿里云网站上的操作进行完了。
二、Maven项目中代码
1、回到阿里云短信服务控制台,复制官方提供的依赖。建议使用新版SDK。
<!--阿里云短信服务--><dependency><groupId>com.aliyun</groupId><artifactId>dysmsapi20170525</artifactId><version>2.0.16</version></dependency>
粘贴依赖到pom.xml里,刷新pom.xml。
2、创建SMSUtils工具类。
@Slf4j@ComponentpublicclassSMSUtils{/**
* 使用AK&SK初始化账号Client
* @param accessKeyId
* @param accessKeySecret
* @return Client
* @throws Exception
*/// 这里填写网站获取的accessKeystaticString accessKeyId ="XXXX";staticString accessKeySecret ="XXXX";publicstaticcom.aliyun.dysmsapi20170525.ClientcreateClient(String accessKeyId,String accessKeySecret)throwsException{Config config =newConfig()// 您的 AccessKey ID.setAccessKeyId(accessKeyId)// 您的 AccessKey Secret.setAccessKeySecret(accessKeySecret);// 访问的域名
config.endpoint ="dysmsapi.aliyuncs.com";returnnewcom.aliyun.dysmsapi20170525.Client(config);}// 生成4位随机数publicstaticStringsmsCode(){// 传统方式遍历生成4位随机数/*String code = "";
Random random = new Random();
// 生成9以内随机数
for (int i=0;i<=3;i++){
int randomInt = random.nextInt(9);
code = code + randomInt;
}
String msg = "{\"code\":\""+code+"\"}";
log.info("msg = "+msg);*/// 用自带工具类,生成指定位数的随机码。String code =RandomStringUtils.randomNumeric(4);String msg ="{\"code\":\""+code+"\"}";
log.info("msg = "+msg);return code;}// 发送验证码之后,返回验证码publicStringsendCode(String phoneNumber)throwsException{//这里的accessKeyId需要我们填入刚刚添加的AccessKey的账号,后面那个参数为密码com.aliyun.dysmsapi20170525.Client client =SMSUtils.createClient(accessKeyId, accessKeySecret);// 查看发送信息String code =smsCode();
log.info("code = "+code);String msg ="{\"code\":\""+ code +"\"}";
log.info("msg = "+msg);//发送SendSmsRequest sendSmsRequest =newSendSmsRequest()//短信的签名 测试阶段不能改.setSignName("阿里云短信测试")//短信的模板码 测试阶段不能改.setTemplateCode("SMS_154950909")// 测试手机号 与绑定的测试手机号一致.setPhoneNumbers(phoneNumber)// 这里手机号只能填18039934120,已经绑定过的测试号。实际开发中可自定义手机号//code后面的值为验证码,code的值只支持4-6位纯数字// 最终格式: {"code":"1234"}.setTemplateParam("{\"code\":\""+ code +"\"}");RuntimeOptions runtime =newRuntimeOptions();try{// 复制代码运行请自行打印 API 的返回值SendSmsResponse sendSmsResponse = client.sendSmsWithOptions(sendSmsRequest, runtime);// 响应结果转为json返回ObjectMapper objectMapper =newObjectMapper();String respBody = objectMapper.writeValueAsString(sendSmsResponse.body);
log.info("响应信息为:"+respBody);}catch(TeaException error){// 如有需要,请打印 errorcom.aliyun.teautil.Common.assertAsString(error.message);}catch(Exception _error){TeaException error =newTeaException(_error.getMessage(), _error);// 如有需要,请打印 errorcom.aliyun.teautil.Common.assertAsString(error.message);}return code;}}
3、写发送短信的controller。调用短信服务后,我们将返回的验证码保存到redis里,并设置时长,比如一分钟有效,并设置一个是否过期的key,如果有未过期的验证码,不能重复发送短信。
RedisPrfix.java
接口中的常量都是静态常量,所以我们可以定义一些常用的前缀、常量到里面。
public interface RedisPrfix {
// 接口中的数据都为静态常量 public static
String SESSION_KEY = "session_";
// 是否有未过期验证码的key
String TOEKN_TIMEOUT = "timeout_";
// 保存验证码的key
String TOKEN_CODEKEY = "phone_";
// 编解码格式
String CHARACTERENCODING = "UTF-8";
}
smsController.java
发送验证码的控制器。
@Data@AllArgsConstructor@NoArgsConstructor@EqualsAndHashCode(callSuper =false)@Accessors(chain =true)@JsonInclude(JsonInclude.Include.NON_NULL)// 只要json中不为空的数据publicclassMsgVoimplementsSerializable{// 用来接收手机号+验证码@JsonProperty("phone")privateString phone;@JsonProperty("captcha")privateString captcha;}
@RestController@RequestMapping("/sms")@Slf4jpublicclassSmsController{// reids模板注入@AutowiredprivateRedisTemplate redisTemplate;// 阿里发验证码工具类@AutowiredprivateSMSUtils smsUtils;/* 通过MsgVo对象来接收手机号、验证码。发送验证码,并将其存入redis
1、redis中定义一个timeout_phone,记录该手机号是否存在未过期验证码。
有效期一分钟,一分钟之内不允许重复发送验证码
每次发送新的验证码,更新timeout_phone为true
每次判断timeout_phone这个键是否存在,如果存在,则不能重复发送验证码
2、redis有定义一个“phone”_+phone的键,值为新获取的验证码
有效期十分钟,十分钟内该验证码有效
*/// 前端点击“发送验证码”,就请求这个方法@PostMapping("/captchas")publicvoidcaptchas(@RequestBodyMsgVo msgVo){// redis操作对象ValueOperations valueOperations = redisTemplate.opsForValue();// 获取手机号String phone = msgVo.getPhone();
log.info("phone = "+phone);// 每次发送验证码之前,检验该手机号是否存在没有过期的验证码if(redisTemplate.hasKey(RedisPrfix.TOEKN_TIMEOUT+phone)){// 如果存在,则提示不能重复发送 抛出异常,方法中止步thrownewRuntimeException("提示:不能重复发送");}// 如果不存在未过期验证码,则发送新验证码,并存入redistry{// 获取发送的验证码String code = smsUtils.sendCode(phone);// 验证码放入redis 有效期600秒,即十分钟
valueOperations.set(RedisPrfix.TOKEN_CODEKEY+phone,code,Duration.ofSeconds(60*10));// 更改未过期验证码的redis 有效期60秒,即一分钟
valueOperations.set(RedisPrfix.TOEKN_TIMEOUT+phone,"true",Duration.ofSeconds(60*2));}catch(Exception e){
e.printStackTrace();thrownewRuntimeException("短信发送失败!");}}}
接收验证码,比对验证码,登录控制器。
@RestController@RequestMapping("/user")@Slf4jpublicclassUserController{@AutowiredprivateIUserService userService;@AutowiredprivateRedisTemplate redisTemplate;/* 用户登录
点击登录之后,根据该手机号在redis里查询真实的验证码,与用户输入的验证码对比。
若不对,则抛异常。若对,在数据库查询该手机号是否已存在。
如果不存在,则视为注册,添加用户。并在map存入<"token",sessionId>
如果存在,直接在map存入,<"token",sessionId>。
并在redis存入该用户信息,key=sessionId,value=User
后续可以根据sessionId查询登录用户的信息
*/@PostMapping("/tokens")// MsgVo = phone + captcha,接收前台对象publicMap<String,Object>tokens(@RequestBodyMsgVo msgVo,HttpSession session){// redis操作对象ValueOperations valueOperations = redisTemplate.opsForValue();// map集合,保存返回值Map<String,Object> resultMap =newHashMap<>();// 获取手机号和验证码String phone = msgVo.getPhone();String code = msgVo.getCaptcha();
log.info("phone = "+phone+" captcha = "+code);// 获取redis中保存的验证码,先判断是否存在,如果存在,则对比用户输入的验证码if(!redisTemplate.hasKey(RedisPrfix.TOKEN_CODEKEY+phone)){// 如果不存在该key,则验证码过期了。thrownewRuntimeException("验证码过期了,请重新发送!");}// 如果没过期,进行对比String realCode =(String) valueOperations.get(RedisPrfix.TOKEN_CODEKEY+phone);if(!StringUtils.equals(code,realCode)){// 空字符串与非空字符串比较,也是false,所以不进行输入验证码的非空判断thrownewRuntimeException("验证码输入错误!");// 抛出异常后,程序终止}// 如果验证码输入正确,判断是否是第一次登录QueryWrapper<User> queryWrapper =newQueryWrapper();
queryWrapper.eq("phone",phone);User user = userService.getOne(queryWrapper);
log.info(String.valueOf(user));// 如果用户不存在,执行注册方法。如果存在,直接跳过,进行下一步if(ObjectUtils.isEmpty(user)){// 为什么空user不能赋值?不能调用getClass方法?User newuser =newUser();
newuser.setPhone(phone);
newuser.setName(phone);// 初始用户名为手机号
newuser.setAvatar("http://t14.baidu.com/it/u=4235480105,3516779440&fm=224&app=112&f=JPEG?w=400&h=400");// 初始头像
newuser.setCreatedAt(LocalDateTime.now());// 创建时间
newuser.setPhoneLinked(true);// 是否绑定手机号
newuser.setWechatLinked(false);// 是否绑定微信
newuser.setIntro("这个人很懒,什么也没留下");// 个人简介
newuser.setOpenid(null);// 微信OpenId
newuser.setUpdatedAt(LocalDateTime.now());// 更新时间
newuser.setFollowingCount(0);//关注数
newuser.setFollowersCount(0);// 粉丝数// 调用insert方法,注册
userService.save(newuser);}// 保存用户登录标记String token = session.getId();// 获取sessionId
resultMap.put("token",token);// 前台根据token从后台获取user// 将sessionId=user保存到redis 登录信息保存1天
valueOperations.set(RedisPrfix.SESSION_KEY+token,user,Duration.ofSeconds(60*60*24*3));
log.info("生成token:"+token);// 返回return resultMap;}// 查询已登录用户信息@GetMapping("/user")@RequriedToken// 自定义注解,只要加了这个注解,使用拦截器,直接拿到用户信息。传入的参数token,也可以用过req.getParameter(“token”)获取publicUserloginUser(HttpServletRequest request){// 无注解方式 User user = (User) redisTemplate.opsForValue().get(RedisPrfix.SESSION_KEY+token);User user =(User) request.getAttribute("user");
log.info("登录用户信息:"+user);return user;}// 注销,退出登录。从redis删除用户信息@DeleteMapping("/tokens")publicvoidexist(String token){
log.info("要删除的token:"+token);
redisTemplate.delete(RedisPrfix.SESSION_KEY+token);}
三、PS
ps1:Springboot集成redis
pom.xml依赖:
<!--springboot跟redis关联的一个依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!--redis数据库连接池的相关依赖--><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId></dependency>
application.yml:spring:application:name: sms-api-module
cloud:nacos:server-addr: 192.168.58.16:8848datasource:url: jdbc:mysql://192.168.58.16:3306/yingxue?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghaiusername: root
password:123driver-class-name: com.mysql.jdbc.Driver
redis:host: 192.168.58.16 #redis主机的ipport:6379#redis端口号jedis:pool:max-active:500max-idle:50min-idle:10max-wait:30000server:port:8074mybatis-plus:mapper-locations: classpath:com/qhx/mapper/xml/*Mapper.xml
redisConfig.java
@ConfigurationpublicclassRedisConfig{@BeanpublicRedisTemplateredisTemplate(RedisConnectionFactory redisConnectionFactory){RedisTemplate<Object,Object> redisTemplate =newRedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);StringRedisSerializer stringRedisSerializer=newStringRedisSerializer();GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer =newGenericJackson2JsonRedisSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);
redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);return redisTemplate;}}
控制器使用的时候:
// reids模板注入@AutowiredprivateRedisTemplate redisTemplate;
ps2:自定义注解
//1、自定义一个接口先:RequriedToken.java@Retention(RetentionPolicy.RUNTIME)// 运行时有效@Target(ElementType.METHOD)// 加载方法上public@interfaceRequriedToken{}// 2、拦截器赋予其功能,如果控制器方法上有这个注解,自动执行操作。// 此例:如果控制器上有@RequriedToken注解,自动获取请求携带的token参数,自动查询redis里保存的登录用户信息,并存入request作用域。用户无需主动接收token(参数里可以不写,但是要有HttpServletRequest req),通过req作用域获取登录用户的信息 TokenInterceptor.java@Component@Slf4jpublicclassTokenInterceptorimplementsHandlerInterceptor{@AutowiredprivateRedisTemplate redisTemplate;// 前置拦截@OverridepublicbooleanpreHandle(HttpServletRequest request,HttpServletResponse response,Object handler)throwsException{// token:xxx// tokenKey:user// 获取当前请求上是否有RequeriedToken注解boolean requiredToken =((HandlerMethod)handler).getMethod().isAnnotationPresent(RequriedToken.class);System.out.println(("是否存在注解:"+ requiredToken));// 如果存在该注解,则在req作用中取if(requiredToken){// 获取token信息String token = request.getParameter("token");System.out.println(("收到的token:"+ token));// 判断token是否为空if(StringUtils.isEmpty(token)){// 如果token为空,则存入token为空user为空,提前放行
request.setAttribute("user",null);
request.setAttribute("token",null);//return true; // 这里该返回还是抛异常thrownewRuntimeException("无效token");}// 根据token获取用户信息// redis操作对象User user =(User) redisTemplate.opsForValue().get(RedisPrfix.SESSION_KEY+token);// 如果不存在该用户信息,抛异常if(ObjectUtils.isEmpty(user)){thrownewRuntimeException("令牌无效!无效token");}// 将用户保存到req作用域
request.setAttribute("token",token);
request.setAttribute("user",user);}returntrue;}}// 3、config里配置该拦截器。@Configuration@Slf4jpublicclassMyInterceptorConfigimplementsWebMvcConfigurer{// 注意,由于Spring工厂的加载机制,拦截器先于Bean的创建。所以此处要先进行Bean的实例化,在进行拦截器的执行。否则拦截器里注入的Bean(包括redisTemplate等)不起效果,会报错。@BeanpublicTokenInterceptorgetTokenInterceptor(){returnnewTokenInterceptor();}@OverridepublicvoidaddInterceptors(InterceptorRegistry registry){// 添加TokenInterceptor到注册机里,拦截所有请求,也可以排除静态资源或某些请求。
registry.addInterceptor(getTokenInterceptor()).addPathPatterns("/**");}}
ps3:阿里云短信流控
获取短信验证码时,突然某次提示短信发送失败,查了发现错误信息如下:
{“Message”:“触发小时级流控Permits:5”,“RequestId”:“818EC3A3-A165-4501-9C6E-7EFCF145F579”,“Code”:“isv.BUSINESS_LIMIT_CONTROL”}
短信发送失败,原因:触发小时级流控Permits:5解决:原来阿里云短信发送有默认的频率限制:
短信验证码 :使用同一个签名,对同一个手机号码发送短信验证码,支持1条/分钟,5条/小时 ,累计10条/天
短信通知: 使用同一个签名和同一个短信模板ID,对同一个手机号码发送短信通知,支持50条/日
{
// 添加TokenInterceptor到注册机里,拦截所有请求,也可以排除静态资源或某些请求。
registry.addInterceptor(getTokenInterceptor()).addPathPatterns(“/**”);
}
}
ps3:阿里云短信流控
> 获取短信验证码时,突然某次提示短信发送失败,查了发现错误信息如下:
> {“Message”:“触发小时级流控Permits:5”,“RequestId”:“818EC3A3-A165-4501-9C6E-7EFCF145F579”,“Code”:“isv.BUSINESS_LIMIT_CONTROL”}
> 短信发送失败,原因:触发小时级流控Permits:5
>
> 解决:原来阿里云短信发送有默认的频率限制:
> 短信验证码 :使用同一个签名,对同一个手机号码发送短信验证码,支持1条/分钟,5条/小时 ,累计10条/天
> 短信通知: 使用同一个签名和同一个短信模板ID,对同一个手机号码发送短信通知,支持50条/日
版权归原作者 一只咸鱼秦 所有, 如有侵权,请联系我们删除。