Java实现短信发送并校验,华为云短信配合Redis实现发送与校验

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 RedisTemplate redisTemplate(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, Set v, 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:Set set      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,发现缓存的值自动删除了

这时再去发起验证接口试一试

可以看到,这个验证码已经不能用了

到这里我们就实现了短信的发送和验证功能