Java实现短信发送并校验,华为云短信配合Redis实现发送与校验
安装sms4j和redis
org.dromara.sms4j sms4j-spring-boot-starter 3.2.1 org.springframework.boot spring-boot-starter-data-redis
sms4j使用
使用sms4j可以非常简单的实现短信发送功能,并且适配了主流的云平台
官方文档地址
添加配置
# redis配置 spring: redis: database: 0 host: xxx.xxx.xxx.xxx port: 6379 timeout: 1200 # 发送短信的配置 sms: # 标注从yml读取配置 config-type: yaml # 用于标定yml中的配置是否开启短信拦截,接口配置不受此限制 restricted: true # 短信拦截限制单手机号每日最大发送量,只对开启了拦截的配置有效 account-max: 8 # 短信拦截限制单手机号每分钟最大发送,只对开启了拦截的配置有效 minute-max: 2 # 是否打印http log http-log: true # 是否打印banner is-print: false # 短信厂商核心配置容纳 blends: tx1: supplier: huawei #您的accessKey access-key-id: 您的accessKey #您的accessKeySecret access-key-secret:您的accessKeySecret #您的短信签名 signature: 您的短信签名 #模板ID 非必须配置,如果使用sendMessage的快速发送需此配置 template-id: 模板ID # 通道号 sender: 通道号 # 配置Id config-id: tx1 #华为回调地址,如不需要可不设置或为空 statusCallBack: #华为分配的app请求地址 url: https://smsapi.cn-north-4.myhuaweicloud.com:443
accessKey、accessKeySecret等可以在华为云控制台,短信服务-我的应用查看
template-id、sender 在短信模板审核通过后可以获知
更多配置可见:https://sms4j.com/doc3/config.html
发送短信
新建一个 SmsController,这里编写了两个接口,一个用于发送短信,一个用于验证短信。
这里用到了Redis做验证码有效期缓存,缓存5分钟
还用到了 SmsUtils.getRandomInt(6) 生成一个6位数纯数字的随机数,这个 SmsUtils 是 org.dromara.sms4j 内置提供好的
package com.szx.edu.controller; import com.szx.commonutils.Msg; import com.szx.edu.utils.redisUtil.RedisServiceImpl; import io.swagger.annotations.Api; import org.apache.commons.lang.StringUtils; import org.dromara.sms4j.api.SmsBlend; import org.dromara.sms4j.api.entity.SmsResponse; import org.dromara.sms4j.comm.utils.SmsUtils; import org.dromara.sms4j.core.factory.SmsFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; /** * @author songzx * @create 2024-04-12 21:22 */ @Api(tags = "发送短信") @RestController @RequestMapping("/sms") public class SmsController { @Autowired RedisServiceImpl redisService; @GetMapping("getCode") public Msg getCode(String phone){ // 1.生成一个随机验证码 String randomCode = SmsUtils.getRandomInt(6); // 2.获取指定配置 SmsBlend smsBlend = SmsFactory.getSmsBlend(); // 3.发送短信 SmsResponse smsResponse = smsBlend.sendMessage(phone,randomCode); // 4.判断是否成功 boolean success = smsResponse.isSuccess(); if(success){ // 如果发送成功,再吧验证码保存到缓存中,并设置缓存时长300秒 redisService.cacheValue(phone,randomCode,300); return Msg.Ok(); }else{ return Msg.Error().msg("验证码发送失败"); } } @PostMapping("checkCode") public Msg checkCode(String phone,String code){ // 获取缓存的code String cacheCode = redisService.getValue(phone); // 判断得到的code和用户传递进来的code是否一样 if(StringUtils.isNotEmpty(cacheCode) && code.equals(cacheCode)){ return Msg.Ok().data("status","ok"); }else{ return Msg.Error().msg("请输入正确的验证码"); } } }
Redis配置
添加配置类
新建 RedisConfig
package com.szx.edu.config; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; import java.time.Duration; /** * @author songzx * @create 2024-03-08 10:58 */ @EnableCaching @Configuration public class RedisConfig { @Bean public RedisTemplateredisTemplate(RedisConnectionFactory factory) { RedisTemplate template = new RedisTemplate<>(); RedisSerializer redisSerializer = new StringRedisSerializer(); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); template.setConnectionFactory(factory); //key序列化方式 template.setKeySerializer(redisSerializer); //value序列化 template.setValueSerializer(jackson2JsonRedisSerializer); //value hashmap序列化 template.setHashValueSerializer(jackson2JsonRedisSerializer); return template; } @Bean public CacheManager cacheManager(RedisConnectionFactory factory) { RedisSerializer redisSerializer = new StringRedisSerializer(); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); //解决查询缓存转换异常的问题 ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); // 配置序列化(解决乱码的问题),过期时间600秒 RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofSeconds(600)) //变双冒号为单冒号 .computePrefixWith(name -> name +":") .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)) .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)) .disableCachingNullValues(); RedisCacheManager cacheManager = RedisCacheManager.builder(factory) .cacheDefaults(config) .build(); return cacheManager; } }
添加工具类
新建 RedisService,这个文件是 interface 类型
package com.szx.edu.utils.redisUtil; import org.springframework.data.redis.core.ListOperations; import java.util.List; import java.util.Set; /** * @author songzx * @create 2024-03-08 10:49 */ public interface RedisService { /** * 添加 key:string 缓存 * * @param key key * @param value value * @param time time * @return */ boolean cacheValue(String key, String value, long time); /** * 添加 key:string 缓存 * * @param key key * @param value value * @return */ boolean cacheValue(String key, String value); /** * 根据 key:string 判断缓存是否存在 * * @param key key * @return boolean */ boolean containsValueKey(String key); /** * 判断缓存 key:set集合 是否存在 * * @param key key * @return */ boolean containsSetKey(String key); /** * 判断缓存 key:list集合 是否存在 * * @param key key * @return boolean */ boolean containsListKey(String key); /** * 查询缓存 key 是否存在 * @param key key * @return true/false */ boolean containsKey(String key); /** * 根据 key 获取缓存value * * @param key key * @return value */ String getValue(String key); /** * 根据 key 移除 value 缓存 * * @param key key * @return true/false */ boolean removeValue(String key); /** * 根据 key 移除 set 缓存 * * @param key key * @return true/false */ boolean removeSet(String key); /** * 根据 key 移除 list 缓存 * * @param key key * @return true/false */ boolean removeList(String key); /** * 缓存set操作 * * @param key key * @param value value * @param time time * @return boolean */ boolean cacheSet(String key, String value, long time); /** * 添加 set 缓存 * * @param key key * @param value value * @return true/false */ boolean cacheSet(String key, String value); /** * 添加 缓存 set * * @param k key * @param v value * @param time 时间 * @return */ boolean cacheSet(String k, Setv, long time); /** * 缓存 set * @param k key * @param v value * @return */ boolean cacheSet(String k, Set v); /** * 获取缓存set数据 * @param k key * @return set集合 */ Set getSet(String k); /** * list 缓存 * @param k key * @param v value * @param time 时间 * @return true/false */ boolean cacheList(String k, String v, long time); /** * 缓存 list * @param k key * @param v value * @return true/false */ boolean cacheList(String k, String v); /** * 缓存 list 集合 * @param k key * @param v value * @param time 时间 * @return */ boolean cacheList(String k, List v, long time); /** * 缓存 list * @param k key * @param v value * @return true/false */ boolean cacheList(String k, List v); /** * 根据 key 获取 list 缓存 * @param k key * @param start 开始 * @param end 结束 * @return 获取缓存区间内 所有value */ List getList(String k, long start, long end); /** * 根据 key 获取总条数 用于分页 * @param key key * @return 条数 */ long getListSize(String key); /** * 获取总条数 用于分页 * @param listOps =redisTemplate.opsForList(); * @param k key * @return size */ long getListSize(ListOperations listOps, String k); /** * 根据 key 移除 list 缓存 * @param k key * @return */ boolean removeOneOfList(String k); }
然后添加接口实现类
RedisServiceImpl
package com.szx.edu.utils.redisUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.ListOperations; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.SetOperations; import org.springframework.data.redis.core.ValueOperations; import org.springframework.stereotype.Service; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; /** * @author songzx * @create 2024-03-08 10:50 */ @Service public class RedisServiceImpl implements RedisService { /** * slf4j 日志 */ private final Logger log = LoggerFactory.getLogger(this.getClass()); /** * 自定义 key 三种 * String key:String value 普通key:value * String key:Setset key:set集合 * String key:List list key:list集合 */ private static final String KEY_PREFIX_KEY = "info:bear:key"; private static final String KEY_PREFIX_SET = "info:bear:set"; private static final String KEY_PREFIX_LIST = "info:bear:list"; private final RedisTemplate redisTemplate; /** * 注入 * @param redisTemplate 模板 */ @Autowired public RedisServiceImpl(RedisTemplate redisTemplate) { this.redisTemplate = redisTemplate; } /** * 添加 key:string 缓存 * * @param k key * @param v value * @param time time * @return */ @Override public boolean cacheValue(String k, String v, long time) { try { String key = KEY_PREFIX_KEY + k; ValueOperations ops = redisTemplate.opsForValue(); ops.set(key, v); if (time > 0) { redisTemplate.expire(key, time, TimeUnit.SECONDS); } return true; } catch (Throwable e) { log.error("缓存存入失败key:[{}] value:[{}]", k, v); } return false; } /** * 添加 key:string 缓存 * * @param key key * @param value value * @return */ @Override public boolean cacheValue(String key, String value) { return cacheValue(key, value, -1); } /** * 根据 key:string 判断缓存是否存在 * * @param key key * @return boolean */ @Override public boolean containsValueKey(String key) { return containsKey(KEY_PREFIX_KEY + key); } /** * 判断缓存 key:set集合 是否存在 * * @param key key * @return */ @Override public boolean containsSetKey(String key) { return containsKey(KEY_PREFIX_SET + key); } /** * 判断缓存 key:list集合 是否存在 * * @param key key * @return boolean */ @Override public boolean containsListKey(String key) { return containsKey(KEY_PREFIX_LIST + key); } /** * 查询缓存 key 是否存在 * @param key key * @return true/false */ @Override public boolean containsKey(String key) { try { return redisTemplate.hasKey(key); } catch (Throwable e) { log.error("判断缓存存在失败key:[" + key + "],错误信息 Codeor[{}]", e); } return false; } /** * 根据 key 获取缓存value * * @param key key * @return value */ @Override public String getValue(String key) { try { ValueOperations ops = redisTemplate.opsForValue(); return ops.get(KEY_PREFIX_KEY + key); } catch (Throwable e) { log.error("根据 key 获取缓存失败,当前key:[{}],失败原因 Codeor:[{}]", key, e); } return null; } /** * 缓存set操作 * * @param k key * @param v value * @param time time * @return boolean */ @Override public boolean cacheSet(String k, String v, long time) { try { String key = KEY_PREFIX_SET + k; SetOperations opsForSet = redisTemplate.opsForSet(); opsForSet.add(key, v); if (time > 0) { redisTemplate.expire(key, time, TimeUnit.SECONDS); } return true; } catch (Throwable e) { log.error("缓存 set 失败 当前 key:[{}] 失败原因 [{}]", k, e); } return false; } /** * 添加 set 缓存 * * @param key key * @param value value * @return true/false */ @Override public boolean cacheSet(String key, String value) { return cacheSet(key, value, -1); } /** * 添加 缓存 set * * @param k key * @param v value * @param time 时间 * @return */ @Override public boolean cacheSet(String k, Set v, long time) { try { String key = KEY_PREFIX_SET + k; SetOperations opsForSet = redisTemplate.opsForSet(); opsForSet.add(key, v.toArray(new String[v.size()])); if (time > 0){ redisTemplate.expire(key,time,TimeUnit.SECONDS); } return true; } catch (Throwable e) { log.error("缓存 set 失败 当前 key:[{}],失败原因 [{}]", k, e); } return false; } /** * 缓存 set * @param k key * @param v value * @return */ @Override public boolean cacheSet(String k, Set v) { return cacheSet(k,v,-1); } /** * 获取缓存set数据 * @param k key * @return set集合 */ @Override public Set getSet(String k) { try { String key = KEY_PREFIX_SET + k; SetOperations opsForSet = redisTemplate.opsForSet(); return opsForSet.members(key); }catch (Throwable e){ log.error("获取缓存set失败 当前 key:[{}],失败原因 [{}]", k, e); } return null; } /** * list 缓存 * @param k key * @param v value * @param time 时间 * @return true/false */ @Override public boolean cacheList(String k, String v, long time) { try { String key = KEY_PREFIX_LIST + k; ListOperations opsForList = redisTemplate.opsForList(); //此处为right push 方法/ 也可以 left push .. opsForList.rightPush(key,v); if (time > 0){ redisTemplate.expire(key,time,TimeUnit.SECONDS); } return true; }catch (Throwable e){ log.error("缓存list失败 当前 key:[{}],失败原因 [{}]", k, e); } return false; } /** * 缓存 list * @param k key * @param v value * @return true/false */ @Override public boolean cacheList(String k, String v) { return cacheList(k,v,-1); } /** * 缓存 list 集合 * @param k key * @param v value * @param time 时间 * @return */ @Override public boolean cacheList(String k, List v, long time) { try { String key = KEY_PREFIX_LIST + k; ListOperations opsForList = redisTemplate.opsForList(); opsForList.rightPushAll(key,v); if (time > 0){ redisTemplate.expire(key,time,TimeUnit.SECONDS); } return true; }catch (Throwable e){ log.error("缓存list失败 当前 key:[{}],失败原因 [{}]", k, e); } return false; } /** * 缓存 list * @param k key * @param v value * @return true/false */ @Override public boolean cacheList(String k, List v) { return cacheList(k,v,-1); } /** * 根据 key 获取 list 缓存 * @param k key * @param start 开始 * @param end 结束 * @return 获取缓存区间内 所有value */ @Override public List getList(String k, long start, long end) { try { String key = KEY_PREFIX_LIST + k; ListOperations opsForList = redisTemplate.opsForList(); return opsForList.range(key,start,end); }catch (Throwable e){ log.error("获取list缓存失败 当前 key:[{}],失败原因 [{}]", k, e); } return null; } /** * 根据 key 获取总条数 用于分页 * @param key key * @return 条数 */ @Override public long getListSize(String key) { try { ListOperations opsForList = redisTemplate.opsForList(); return opsForList.size(KEY_PREFIX_LIST + key); }catch (Throwable e){ log.error("获取list长度失败key[" + KEY_PREFIX_LIST + key + "], Codeor[" + e + "]"); } return 0; } /** * 获取总条数 用于分页 * @param listOps =redisTemplate.opsForList(); * @param k key * @return size */ @Override public long getListSize(ListOperations listOps, String k) { try { return listOps.size(k); }catch (Throwable e){ log.error("获取list长度失败key[" + KEY_PREFIX_LIST + k + "], Codeor[" + e + "]"); } return 0; } /** * 根据 key 移除 list 缓存 * @param k key * @return */ @Override public boolean removeOneOfList(String k) { try { String key = KEY_PREFIX_LIST + k; ListOperations opsForList = redisTemplate.opsForList(); opsForList.rightPop(key); return true; }catch (Throwable e){ log.error("移除list缓存失败 key[" + KEY_PREFIX_LIST + k + "], Codeor[" + e + "]"); } return false; } /** * 根据 key 移除 value 缓存 * * @param key key * @return true/false */ @Override public boolean removeValue(String key) { return remove(KEY_PREFIX_KEY + key); } /** * 根据 key 移除 set 缓存 * * @param key key * @return true/false */ @Override public boolean removeSet(String key) { return remove(KEY_PREFIX_SET + key); } /** * 根据 key 移除 list 缓存 * * @param key key * @return true/false */ @Override public boolean removeList(String key) { return remove(KEY_PREFIX_LIST + key); } /** * 移除缓存 * * @param key key * @return boolean */ private boolean remove(String key) { try { redisTemplate.delete(key); return true; } catch (Throwable e) { log.error("移除缓存失败 key:[{}] 失败原因 [{}]", key, e); } return false; } }
测试接口
首先测试发送短信,可以看到正常发送
然后去Redis中查看,缓存的值是 232868
此时我手机接收到的也是 232868
然后故意输入错误的试一试
然后再输入正确的
然后等待五分钟之后,刷新Redis,发现缓存的值自动删除了
这时再去发起验证接口试一试
可以看到,这个验证码已经不能用了
到这里我们就实现了短信的发送和验证功能