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: 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 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;
}
@Bean
public 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 模板
*/
@Autowired
public RedisServiceImpl(RedisTemplate<String, String> 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<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
*/
@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<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
*/
@Override
public 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
*/
@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<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
*/
@Override
public boolean cacheSet(String k, Set<String> v) {
return cacheSet(k,v,-1);
}
/**
* 获取缓存set数据
* @param k key
* @return set集合
*/
@Override
public 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
*/
@Override
public 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
*/
@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<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
*/
@Override
public boolean cacheList(String k, List<String> v) {
return cacheList(k,v,-1);
}
/**
* 根据 key 获取 list 缓存
* @param k key
* @param start 开始
* @param end 结束
* @return 获取缓存区间内 所有value
*/
@Override
public 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 条数
*/
@Override
public 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
*/
@Override
public 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
*/
@Override
public 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
*/
@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,发现缓存的值自动删除了
这时再去发起验证接口试一试
可以看到,这个验证码已经不能用了
到这里我们就实现了短信的发送和验证功能