经验分享 – SpringBoot|Spring-Data-Redis 验证码短信存储服务

接着上一篇继续说,上一篇主要的还是连接邮箱和发信测试,这次主要就是对于接口制作和测试了

首先,按照先一篇的接着写

SpringBoot 验证码生成+SMTP邮箱服务配置 – Karos (wzl1.top)

POM-Maven依赖引入 Spring Data Redis以及Pool连接池

具体为什么我不用Jedis,主要是线程安全问题

       <!--redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
       <!--pool连接池-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>

application.yml配置(用的properties的配置这里也有)

#yml配置
spring:
    redis:
        auth: 123456
        host: 127.0.0.1
        port: 6379
        lettuce:
            pool:
                max-active: 8
                max-idle: 8
                max-wait: 100
                min-idle: 0

#properties配置
spring.redis.auth=123456
spring.redis.host=127.0.0.1
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.max-wait=100
spring.redis.lettuce.pool.min-idle=0
spring.redis.port=6379

编写验证码查找、删除、匹配服务层(虽然是服务层,但我仍然划在工具类中)

如果我们通过邮箱发送验证码,那么肯定要给验证码设置一个有效期,同一个邮箱在同一时间片段只能过有一个短信验证码,如果在同一时间内重复申请没有,但是没有用,为了避免计算开销,我们可以直接返回(当然,你也可以重新生成)。

一般验证码我们实在注册账号的时候用,我们在注册的时候也会判断用户等级(这个一般是交给前端做,但是后端也可以做做【花里胡哨】)

============重点来了=============

我们存储验证码采用redis,使用SpringDataRedis框架

我们在用户安全类中写个 RedisTmplate 类,并且自动装配,redistemplate的具体用法自查,不做解释,这里就只是实现

/**
 * Title
 *
 * @ClassName: UserSafetyUtil
 * @Description: 用户安全工具,生成验证码,密码加密等
 * @author: Karos
 * @date: 2022/10/15 9:32
 * @Blog: https://www.wzl1.top/
 */

package com.karos.td.Util.SafetyUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.DigestUtils;

import java.util.Date;
import java.util.Locale;

@Component
@Scope("singleton")
public class UserSafetyUtil {

//验证码过期时间,我设置的是5分钟
    public static long CHECKCODEEXPTIME=1000L*60*5;
//当前验证码状态
    public interface CHECKCODE{
        int notExist=0;//该邮箱不存在
        int OK=1;//匹配成功
        int EXPTIME=2;//过期
        int ISUSED=3;//被使用
        int NoMatch=4;//不匹配
    }
    @Autowired
   private RedisTemplate redisTemplate;
//使用某个邮箱的验证码
    public boolean useCheckCodeByMail(String mailAddress){
        if (redisTemplate.hasKey(mailAddress)){
            redisTemplate.opsForHash().put(mailAddress,"isUsed",1);
            return true;
        }
        return false;
    }

//查找并返回邮箱mailAddress的验证码,没有或者过期了返回空,如果过期了,将验证码删除
    public String findUsercode(String mailAddress){
        if (redisTemplate.opsForHash().hasKey(mailAddress,"checkCode")){
            if ((long)redisTemplate.opsForHash().get(mailAddress,"expDate")<new Date().getTime()) {
                String code = (String) redisTemplate.opsForHash().get(mailAddress, "checkCode");
               return code;
            }
            redisTemplate.delete(mailAddress);
            return null;
        }
        return null;
    }

//删除验证码,这里的str是邮箱
    public boolean delCheckCode(String mailAddress){
        if (redisTemplate.hasKey(mailAddress)){
            redisTemplate.delete(mailAddress);
            return true;
        }
        return false;
    }

//验证码匹配
    public int matchCheckCode(String mailAddress,String code){
        if (redisTemplate.opsForHash().hasKey(mailAddress,"checkCode")){
            if ((long)redisTemplate.opsForHash().get(mailAddress,"expDate")<new Date().getTime()) {
              return CHECKCODE.EXPTIME;
            }
            if ((int)redisTemplate.opsForHash().get(mailAddress,"isUsed")!=0){
                return CHECKCODE.ISUSED;
            }
            if (redisTemplate.opsForHash().get(mailAddress,"checkCode").equals(code))return CHECKCODE.OK;
            return CHECKCODE.NoMatch;
        }
        return CHECKCODE.notExist;
    }
//通过邮箱生成验证码,存入Redis并返回
    public String MakeCodetoDb(String mailAddress){
        String code=UserSafetyUtil.touchCheckCode(mailAddress,null);
        redisTemplate.opsForHash().put(mailAddress,"checkCode",code);
        redisTemplate.opsForHash().put(mailAddress,"expDate",new Date().getTime()+UserSafetyUtil.CHECKCODEEXPTIME);
        redisTemplate.opsForHash().put(mailAddress,"isUsed",0);
        return code;
    }
//通过字符串str生成对应的验证码
    public static String touchCheckCode(String str,String key){
        if(key==null)key=UserSafetyUtil.getTIMEstr();
        String s=StrHex(new StringBuffer(str),new StringBuffer(key));
        return getMD5Hex(s.substring(5,10)).substring(5,11).toUpperCase(Locale.ROOT);
    }
//字符串加密算法
    public static String StrHex(StringBuffer str, StringBuffer key){
        str.append(key);
        str.insert(0,getMD5Hex(key.toString()));
        return getMD5Hex(str.insert(5,"wzl03").toString());
    }
//MD5加密
    public static String getMD5Hex(String str){
        return DigestUtils.md5DigestAsHex(str.getBytes());
    }
//通过时间获取加密密钥key
    public static String getTIMEstr() {
        return new Long((new Date().getTime()>>3|1024|8086)%3000).toString();
    }
}

控制层编写–邮箱服务

在后面的项目开发中,我们邮箱服务不一定只用来发送验证码,也有可能拿来做一些消息预警推送或者一些信息推送

所以这里我们做一个控制层

/**
 * Title
 *
 * @ClassName: MailController
 * @Description:
 * @author: Karos
 * @date: 2022/10/17 0:41
 * @Blog: https://www.wzl1.top/
 */

package com.karos.td.Controller;

import com.karos.td.Common.http.HttpCode;
import com.karos.td.Util.EmailUtil.EmailSend;
import com.karos.td.Util.JsonUtil.JsonRes;
import com.karos.td.Util.SafetyUtil.UserSafetyUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController//Controller+RepouseBody
@RequestMapping("/api/mail")
public class MailController {
    @Autowired
    private EmailSend es;
    @Autowired
    private UserSafetyUtil usu;
    //@PathVariable /xxxx地址内引入var
    //@RequestParam ?add=xxxx
    @PostMapping
    public JsonRes send(@RequestParam("add") String mailAddress){
        String code = usu.findUsercode(mailAddress);
        if(code!=null){
            es.setMessage(mailAddress,"【OK服务】验证码接收","您好,这是您的验证码,请在5分钟内使用,谢谢:【"+code+"】");
            return new JsonRes(HttpCode.Success,"success");
        }
        code=usu.MakeCodetoDb(mailAddress);
        es.setMessage(mailAddress,"【OK服务】验证码接收","您好,这是您的验证码,请在5分钟内使用,谢谢:【"+code+"】");
        es.send();
        return new JsonRes(HttpCode.Success,"success");
    }
}

控制层编写–用户层

/**
 * Title
 *
 * @ClassName: UserController
 * @Description: 用户Controller层
 * @author: Karos
 * @date: 2022/10/16 20:26
 * @Blog: https://www.wzl1.top/
 */

package com.karos.td.Controller;

import com.karos.td.Common.http.HttpCode;
import com.karos.td.Common.safety.PassWordSafetyLevelMatches;
import com.karos.td.Dto.UserDto;
import com.karos.td.Service.impl.UserServiceimpl;
import com.karos.td.Util.JsonUtil.JsonRes;
import com.karos.td.Util.SafetyUtil.UserSafetyUtil;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController//Controller+RepouseBody
@RequestMapping("/api/users")
public class UserController {
    /**Restful风格
     * 【GET】 /users # 查询用户信息列表
     * 【GET】 /users/1001 # 查看某个用户信息
     * 【POST】 /users # 新建用户信息
     * 【PUT】 /users/1001 # 更新用户信息(全部字段)
     * 【PATCH】 /users/1001 # 更新用户信息(部分字段)
     * 【DELETE】 /users/1001 # 删除用户信息
     */
    @Autowired
    private UserServiceimpl usi;
    @Autowired
    private UserSafetyUtil usu;
    @PostMapping
    public String Register(@RequestBody @NotNull UserDto user){
        //通过正则表达式判断用户密码等级,即使前端做了,后端再做一次,基于第三方sdk开发时减少开发者工作量(假好人)
        if (PassWordSafetyLevelMatches.getPassWordLevel(user.getUpassword())<3){
            return new JsonRes(HttpCode.Success,"PassWord level is very low!",null).NoData();
        }
        //判断当前邮箱号是否存在(Dao层我还没写,先把邮箱写好,哈哈)
        if (usi.isExsitUserByMail(user.getEmail())==true){
            return new JsonRes(HttpCode.Success,"The E-mail Addres is Used,Please Change the E-mail!",null).toString();
        }
        int state = usu.matchCheckCode(user.getEmail(), user.getCheckcode());
        if (state==UserSafetyUtil.CHECKCODE.notExist){
            return new JsonRes(HttpCode.Success,"Please Set CheckCode!",null).NoData();
        }
        if (state==UserSafetyUtil.CHECKCODE.EXPTIME){
            return new JsonRes(HttpCode.Success,"The CheckCode is Exp!",null).NoData();
        }
        if (state==UserSafetyUtil.CHECKCODE.ISUSED){
            return new JsonRes(HttpCode.Success,"The CheckCode is Used!Please Reset CheckCode!",null).NoData();
        }
        if (state==UserSafetyUtil.CHECKCODE.NoMatch){
            return new JsonRes(HttpCode.Success,"The CheckCode isn't Match").NoData();
        }
        usu.useCheckCodeByMail(user.getEmail());//这里不改变问题也不大【手动滑稽】
        boolean b = usu.delCheckCode(user.getEmail());
        return new JsonRes(HttpCode.Accepted,"Regesiter OK!",user).AllData();
    }
}

代码测试:

写完了,我们Run一下

是不是感觉有那味儿了?

10.18 补充 前面是模拟的过期与被使用的情况,不太建议使用,可以看看下面的代码

UserController层

/**
 * Title
 *
 * @ClassName: UserController
 * @Description: 用户Controller层
 * @author: Karos
 * @date: 2022/10/16 20:26
 * @Blog: https://www.wzl1.top/
 */

package com.karos.td.Controller;

import com.karos.td.Common.http.HttpCode;
import com.karos.td.Common.safety.PassWordSafetyLevelMatches;
import com.karos.td.Dto.UserDto;
import com.karos.td.Service.impl.UserServiceimpl;
import com.karos.td.Util.JsonUtil.JsonRes;
import com.karos.td.Util.SafetyUtil.UserSafetyUtil;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController//Controller+RepouseBody
@RequestMapping("/api/users")
public class UserController {
    /**Restful风格
     * 【GET】 /users # 查询用户信息列表
     * 【GET】 /users/1001 # 查看某个用户信息
     * 【POST】 /users # 新建用户信息
     * 【PUT】 /users/1001 # 更新用户信息(全部字段)
     * 【PATCH】 /users/1001 # 更新用户信息(部分字段)
     * 【DELETE】 /users/1001 # 删除用户信息
     */
    @Autowired
    private UserServiceimpl usi;
    @Autowired
    private UserSafetyUtil usu;
    @PostMapping
    public String Register(@RequestBody @NotNull UserDto user){
        //通过正则表达式判断用户密码等级,即使前端做了,后端再做一次,基于第三方sdk开发时减少开发者工作量(假好人)
        if (PassWordSafetyLevelMatches.getPassWordLevel(user.getUpassword())<3){
            return new JsonRes(HttpCode.Success,"PassWord level is very low!",null).NoData();
        }
        //判断当前邮箱号是否存在
        if (usi.isExsitUserByMail(user.getEmail())==true){
            return new JsonRes(HttpCode.Success,"The E-mail Addres is Used,Please Change the E-mail!",null).toString();
        }
        int state = usu.matchCheckCode(user.getEmail(), user.getCheckcode());
        if (state==UserSafetyUtil.CHECKCODE.notExist){
            return new JsonRes(HttpCode.Success,"Please Set CheckCode!",null).NoData();
        }

        if (state==UserSafetyUtil.CHECKCODE.NoMatch){
            return new JsonRes(HttpCode.Success,"The CheckCode isn't Match").NoData();
        }
        usu.useCheckCodeByMail(user.getEmail());
        return new JsonRes(HttpCode.Accepted,"Regesiter OK!",user).AllData();
    }
}

MailController层

/**
 * Title
 *
 * @ClassName: MailController
 * @Description:
 * @author: Karos
 * @date: 2022/10/17 0:41
 * @Blog: https://www.wzl1.top/
 */

package com.karos.td.Controller;

import com.karos.td.Common.http.HttpCode;
import com.karos.td.Util.EmailUtil.EmailSend;
import com.karos.td.Util.JsonUtil.JsonRes;
import com.karos.td.Util.SafetyUtil.UserSafetyUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController//Controller+RepouseBody
@RequestMapping("/api/mail")
public class MailController {
    @Autowired
    private EmailSend es;
    @Autowired
    private UserSafetyUtil usu;
    //@PathVariable /xxxx地址内引入var
    //@RequestParam ?add=xxxx
    @PostMapping
    public String send(@RequestParam("add") String mailAddress){
        String code = usu.findUsercode(mailAddress);
        if(code!=null){
            es.setMessage(mailAddress,"【OK服务】验证码接收","您好,这是您的验证码,请在5分钟内使用,谢谢:【"+code+"】");
            es.send();
            return new JsonRes(HttpCode.Success,"success").NoData().toString();
        }
        code=usu.MakeCodetoDb(mailAddress);
        es.setMessage(mailAddress,"【OK服务】验证码接收","您好,这是您的验证码,请在5分钟内使用,谢谢:【"+code+"】");
        es.send();
        return new JsonRes(HttpCode.Success,"success").NoData().toString();
    }
}

UserSafetyUntil

/**
 * Title
 *
 * @ClassName: UserSafetyUtil
 * @Description: 用户安全工具,生成验证码,密码加密等
 * @author: 巫宗霖
 * @date: 2022/10/15 9:32
 * @Blog: https://www.wzl1.top/
 */

package com.karos.td.Util.SafetyUtil;
import org.apache.ibatis.annotations.Delete;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.DigestUtils;

import java.util.Date;
import java.util.Locale;
import java.util.concurrent.TimeUnit;

@Component
@Scope("singleton")
public class UserSafetyUtil {

    public static long CHECKCODEEXPTIME=1000L*60*5;
    public interface CHECKCODE{
        int notExist=0;
        int OK=1;
        int EXPTIME=2;
        int ISUSED=3;
        int NoMatch=4;
    }
    @Autowired
    private RedisTemplate redisTemplate;

    public boolean useCheckCodeByMail(String mailAddress){
        if (redisTemplate.hasKey(mailAddress)){
            redisTemplate.delete(mailAddress);//使用了就删除
            return true;
        }
        return false;
    }

    public String findUsercode(String mailAddress){
        if (redisTemplate.opsForHash().hasKey(mailAddress,"checkCode")){
                String code = (String) redisTemplate.opsForHash().get(mailAddress, "checkCode");
               return code;
        }
        return null;
    }

    //过期用法,重复造轮子了
    @Deprecated
    public boolean delCheckCode(String mailAddress){
        if (redisTemplate.hasKey(mailAddress)){
            redisTemplate.delete(mailAddress);
            return true;
        }
        return false;
    }
    public int matchCheckCode(String mailAddress,String code){
        if (redisTemplate.opsForHash().hasKey(mailAddress,"checkCode")){
            if (redisTemplate.opsForHash().get(mailAddress,"checkCode").equals(code))return CHECKCODE.OK;
            return CHECKCODE.NoMatch;
        }
        return CHECKCODE.notExist;
    }
    public static String touchCheckCode(String mailAddress,String key){
        if(key==null)key=UserSafetyUtil.getTIMEstr();
        String s=StrHex(new StringBuffer(mailAddress),new StringBuffer(key));
        return getMD5Hex(s.substring(5,10)).substring(5,11).toUpperCase(Locale.ROOT);
    }
    public String MakeCodetoDb(String mailAddress){
        String code=UserSafetyUtil.touchCheckCode(mailAddress,null);
        redisTemplate.opsForHash().put(mailAddress,"checkCode",code);
        redisTemplate.expire(mailAddress,CHECKCODEEXPTIME, TimeUnit.MILLISECONDS);
        return code;
    }

    public static String StrHex(StringBuffer str, StringBuffer key){
        str.append(key);
        str.insert(0,getMD5Hex(key.toString()));
        return getMD5Hex(str.insert(5,"wzl03").toString());
    }

    public static String getMD5Hex(String str){
        return DigestUtils.md5DigestAsHex(str.getBytes());
    }

    public static String getTIMEstr() {
        return new Long((new Date().getTime()>>3|1024|8086)%3000).toString();
    }
}

正文完