Java实现短信发送并校验,华为云短信配合Redis实现发送与校验
安装sms4j和redis
<dependency><groupId>org.dromara.sms4j</groupId><artifactId>sms4j-spring-boot-starter</artifactId><version>3.2.1</version>
</dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
sms4j使用
使用sms4j可以非常简单的实现短信发送功能,并且适配了主流的云平台
官方文档地址
添加配置
# redis配置
spring:redis:database: 0host: xxx.xxx.xxx.xxxport: 6379timeout: 1200# 发送短信的配置
sms:# 标注从yml读取配置config-type: yaml# 用于标定yml中的配置是否开启短信拦截,接口配置不受此限制restricted: true# 短信拦截限制单手机号每日最大发送量,只对开启了拦截的配置有效account-max: 8# 短信拦截限制单手机号每分钟最大发送,只对开启了拦截的配置有效minute-max: 2# 是否打印http loghttp-log: true# 是否打印banneris-print: false# 短信厂商核心配置容纳blends:tx1:supplier: huawei#您的accessKeyaccess-key-id: 您的accessKey#您的accessKeySecretaccess-key-secret:您的accessKeySecret#您的短信签名signature: 您的短信签名#模板ID 非必须配置,如果使用sendMessage的快速发送需此配置template-id: 模板ID# 通道号sender: 通道号# 配置Idconfig-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 {@AutowiredRedisServiceImpl 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){// 获取缓存的codeString 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 {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {RedisTemplate<String, Object> template = new RedisTemplate<>();RedisSerializer<String> 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;}@Beanpublic CacheManager cacheManager(RedisConnectionFactory factory) {RedisSerializer<String> 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, Set<String> v, long time);/*** 缓存 set* @param k key* @param v value* @return*/boolean cacheSet(String k, Set<String> v);/*** 获取缓存set数据* @param k key* @return set集合*/Set<String> 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<String> v, long time);/*** 缓存 list* @param k key* @param v value* @return true/false*/boolean cacheList(String k, List<String> v);/*** 根据 key 获取 list 缓存* @param k key* @param start 开始* @param end 结束* @return 获取缓存区间内 所有value*/List<String> 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<String, String> 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:Set<String> set key:set集合* String key:List<String> 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<String, String> redisTemplate;/*** 注入* @param redisTemplate 模板*/@Autowiredpublic RedisServiceImpl(RedisTemplate<String, String> redisTemplate) {this.redisTemplate = redisTemplate;}/*** 添加 key:string 缓存** @param k key* @param v value* @param time time* @return*/@Overridepublic boolean cacheValue(String k, String v, long time) {try {String key = KEY_PREFIX_KEY + k;ValueOperations<String, String> 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*/@Overridepublic boolean cacheValue(String key, String value) {return cacheValue(key, value, -1);}/*** 根据 key:string 判断缓存是否存在** @param key key* @return boolean*/@Overridepublic boolean containsValueKey(String key) {return containsKey(KEY_PREFIX_KEY + key);}/*** 判断缓存 key:set集合 是否存在** @param key key* @return*/@Overridepublic boolean containsSetKey(String key) {return containsKey(KEY_PREFIX_SET + key);}/*** 判断缓存 key:list集合 是否存在** @param key key* @return boolean*/@Overridepublic boolean containsListKey(String key) {return containsKey(KEY_PREFIX_LIST + key);}/*** 查询缓存 key 是否存在* @param key key* @return true/false*/@Overridepublic 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*/@Overridepublic String getValue(String key) {try {ValueOperations<String, String> 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*/@Overridepublic boolean cacheSet(String k, String v, long time) {try {String key = KEY_PREFIX_SET + k;SetOperations<String, String> 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*/@Overridepublic boolean cacheSet(String key, String value) {return cacheSet(key, value, -1);}/*** 添加 缓存 set** @param k key* @param v value* @param time 时间* @return*/@Overridepublic boolean cacheSet(String k, Set<String> v, long time) {try {String key = KEY_PREFIX_SET + k;SetOperations<String, String> 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*/@Overridepublic boolean cacheSet(String k, Set<String> v) {return cacheSet(k,v,-1);}/*** 获取缓存set数据* @param k key* @return set集合*/@Overridepublic Set<String> getSet(String k) {try {String key = KEY_PREFIX_SET + k;SetOperations<String, String> 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*/@Overridepublic boolean cacheList(String k, String v, long time) {try {String key = KEY_PREFIX_LIST + k;ListOperations<String, String> 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*/@Overridepublic boolean cacheList(String k, String v) {return cacheList(k,v,-1);}/*** 缓存 list 集合* @param k key* @param v value* @param time 时间* @return*/@Overridepublic boolean cacheList(String k, List<String> v, long time) {try {String key = KEY_PREFIX_LIST + k;ListOperations<String, String> 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*/@Overridepublic boolean cacheList(String k, List<String> v) {return cacheList(k,v,-1);}/*** 根据 key 获取 list 缓存* @param k key* @param start 开始* @param end 结束* @return 获取缓存区间内 所有value*/@Overridepublic List<String> getList(String k, long start, long end) {try {String key = KEY_PREFIX_LIST + k;ListOperations<String, String> 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 条数*/@Overridepublic long getListSize(String key) {try {ListOperations<String, String> 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*/@Overridepublic long getListSize(ListOperations<String, String> 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*/@Overridepublic boolean removeOneOfList(String k) {try {String key = KEY_PREFIX_LIST + k;ListOperations<String, String> 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*/@Overridepublic boolean removeValue(String key) {return remove(KEY_PREFIX_KEY + key);}/*** 根据 key 移除 set 缓存** @param key key* @return true/false*/@Overridepublic boolean removeSet(String key) {return remove(KEY_PREFIX_SET + key);}/*** 根据 key 移除 list 缓存** @param key key* @return true/false*/@Overridepublic 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,发现缓存的值自动删除了
这时再去发起验证接口试一试
可以看到,这个验证码已经不能用了
到这里我们就实现了短信的发送和验证功能