从零开始的SpringBoot前后端分离入门级项目(六)

   日期:2020-09-26     浏览:94    评论:0    
核心提示:一个不正经的目录生成验证码新建验证码实体类新建验证码工具类修改拦截器配置类修改两个DTOSpringBoot集成Redis更新yml配置文件修改UserController生成验证码验证码是一个保障接口和用户密码安全的良好工具,在项目中进行一些比较“危险”的操作的时候我们需要让输入验证码才能进入到下一步,话不多说,先来编写一个验证码工具类:新建验证码实体类在bean子包下新建VerifyCode实体类:/** * @Author Alfalfa99 * @Date 2020/9/19 22:1

一个不正经的目录

  • 生成验证码
    • 新建验证码实体类
    • 新建验证码工具类
    • 修改拦截器配置类
    • 修改两个DTO
  • SpringBoot集成Redis
    • 更新yml配置文件
    • 修改UserController

生成验证码

验证码是一个保障接口和用户密码安全的良好工具,在项目中进行一些比较“危险”的操作的时候我们需要让输入验证码才能进入到下一步,话不多说,先来编写一个验证码工具类:

新建验证码实体类

在bean子包下新建VerifyCode实体类:


@Data
@AllArgsConstructor
@NoArgsConstructor
public class VerifyCode { 
    private String code;
    private byte[] imgBytes;
    private long expireTime;
}

新建验证码工具类

在util包下新建一个VerifyCodeUtil:

import Echo.alfalfa.MemberManager.model.bean.VerifyCode;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Random;




public class VerifyCodeUtil extends org.apache.commons.lang3.RandomUtils { 

    private static final char[] CODE_SEQ = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J',
            'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
            'X', 'Y', 'Z', '2', '3', '4', '5', '6', '7', '8', '9'};

    private static final char[] NUMBER_ARRAY = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};

    private static Random random = new Random();

    public static String randomString(int length) { 
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < length; i++) { 
            sb.append(String.valueOf(CODE_SEQ[random.nextInt(CODE_SEQ.length)]));
        }
        return sb.toString();
    }

    public static String randomNumberString(int length) { 
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < length; i++) { 
            sb.append(String.valueOf(NUMBER_ARRAY[random.nextInt(NUMBER_ARRAY.length)]));
        }
        return sb.toString();
    }

    public static Color randomColor(int fc, int bc) { 
        int f = fc;
        int b = bc;
        Random random = new Random();
        if (f > 255) { 
            f = 255;
        }
        if (b > 255) { 
            b = 255;
        }
        return new Color(f + random.nextInt(b - f), f + random.nextInt(b - f),
                f + random.nextInt(b - f));
    }

    public static int nextInt(int bound) { 
        return random.nextInt(bound);
    }

    private static final String[] FONT_TYPES = {  "\u5b8b\u4f53", "\u65b0\u5b8b\u4f53", "\u9ed1\u4f53",
            "\u6977\u4f53", "\u96b6\u4e66" };

    //验证码字符位数
    private static final int VALICATE_CODE_LENGTH = 5;

    
    private static void fillBackground(Graphics graphics, int width, int height) { 
        // 填充背景
        graphics.setColor(Color.WHITE);
        //设置矩形坐标x y 为0
        graphics.fillRect(0, 0, width, height);

        // 加入干扰线条
        for (int i = 0; i < 8; i++) { 
            //设置随机颜色算法参数
            graphics.setColor(randomColor(40, 150));
            Random random = new Random();
            int x = random.nextInt(width);
            int y = random.nextInt(height);
            int x1 = random.nextInt(width);
            int y1 = random.nextInt(height);
            graphics.drawLine(x, y, x1, y1);
        }
    }

    
    public String generate(int width, int height, OutputStream os) throws IOException { 
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics graphics = image.getGraphics();
        fillBackground(graphics, width, height);
        String randomStr = randomString(VALICATE_CODE_LENGTH);
        createCharacter(graphics, randomStr);
        graphics.dispose();
        //设置JPEG格式
        ImageIO.write(image, "JPEG", os);
        return randomStr;
    }

    
    public VerifyCode generate(int width, int height) { 
        VerifyCode verifyCode = null;
        try (
                //将流的初始化放到这里就不需要手动关闭流
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ) { 
            String code = generate(width, height, baos);
            verifyCode = new VerifyCode();
            verifyCode.setCode(code);
            verifyCode.setImgBytes(baos.toByteArray());
        } catch (IOException e) { 
            verifyCode = null;
        }
        return verifyCode;
    }

    
    private void createCharacter(Graphics g, String randomStr) { 
        char[] charArray = randomStr.toCharArray();
        for (int i = 0; i < charArray.length; i++) { 
            //设置RGB颜色算法参数
            g.setColor(new Color(50 + nextInt(100),
                    50 + nextInt(100), 50 + nextInt(100)));
            //设置字体大小,类型
            g.setFont(new Font(FONT_TYPES[nextInt(FONT_TYPES.length)], Font.BOLD, 26));
            //设置x y 坐标
            g.drawString(String.valueOf(charArray[i]), 15 * i + 5, 19 + nextInt(8));
        }
    }
}

通过该类中的generate()方法我们就可以获得一个图片验证码了,我们需要在UserController中添加这个方法,将这个图片验证码返回给前端:

	@PostMapping("/verifyCode")
    public void verifyCode(HttpServletResponse response) throws IOException { 
        VerifyCodeUtil verifyCodeUtil = new VerifyCodeUtil();
        //设置长宽
        VerifyCode verifyCode = verifyCodeUtil.generate(80, 28);
        //设置响应头
        response.setHeader("Pragma", "no-cache");
        //设置响应头
        response.setHeader("Cache-Control", "no-cache");
        //在代理服务器端防止缓冲
        response.setDateHeader("Expires", 0);
        //设置响应内容类型
        response.setContentType("image/jpeg");
        response.getOutputStream().write(verifyCode.getImgBytes());
        response.getOutputStream().flush();
    }

修改拦截器配置类

我们在InterceptorConfig拦截器配置类中要放行通向获取验证码接口的道路噢,不然就会陷入“死锁”–我要验证码才能登陆,我要登录才能获取验证码。

	@Override
    public void addInterceptors(InterceptorRegistry registry) { 

        //拦截所有目录,除了通向login和register的接口
        registry.addInterceptor(tokenInterceptor)
                .addPathPatterns("loginregisterverifyCode*.html", "*.js", "*.css");
    }

那么我们现在测试一下我们的验证码是否能成功的返回给前端

可以看到我们现在确实完成了验证码的生成,并且将其返回给了前端,但是问题来了,用户登录或注册时,如何携带这个验证码和验证其是否正确呢?我们先来解决第一个问题!

修改两个DTO

我们分别在UserLoginDTO和UserRegisterDTO中增加一个verifyCode字段即可完成:

//登录
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserLoginDTO { 
    @ApiModelProperty(example = "登录名")
    @NotNull(message = "账号不允许为空")
    private String username;
    @ApiModelProperty(example = "密码")
    @NotNull(message = "密码不允许为空")
    private String password;
    @ApiModelProperty(example = "验证码")
    @NotNull(message = "验证码不允许为空")
    private String verifyCode;
}
//注册
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserRegisterDTO { 
    @ApiModelProperty(example = "登录名")
    @NotNull(message = "用户名不允许为空")
    private String username;
    @ApiModelProperty(example = "密码")
    @NotNull(message = "密码不允许为空")
    private String password;
    @ApiModelProperty(example = "昵称")
    @NotNull(message = "昵称不允许为空")
    private String nickname;
    @ApiModelProperty(example = "验证码")
    @NotNull(message = "验证码不允许为空")
    private String verifyCode;
}

那么我们就要解决第二个问题了,如何服务端如何保存这个生成的验证码并验证其是否正确呢?

SpringBoot集成Redis

在本系列第一篇博客中,如果有细心的读者应该已经发现了,我们引入了SpringBoot集成Redis的相关依赖spring-boot-starter-data-redis,那么接下来对我们的代码进行更新吧!(redis的安装和开启过于简单,不在此处赘述了,一分钟就能启动)

更新yml配置文件

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: xxx
    password: xxx
    url: xxx
 #在下面追加如下内容
  redis:
    host: 127.0.0.1
    port: 6379

在yml中追加如下内容,我们连接本地的redis,监听端口为默认的6379。

修改UserController

首先我们先通过构造方法注入操作redis

private final UserService userService;
    private final RedisTemplate redisTemplate;

    public UserController(UserService userService, RedisTemplate redisTemplate) { 
        this.userService = userService;
        this.redisTemplate = redisTemplate;
    }

RedisTemplate 是Spring框架为我们提供的用于操作redis的工具类,如果读者有使用过jedis,那么对其应该并不陌生。
然后我们前往生成验证码的方法中添加代码:

	@PostMapping("/verifyCode")
    public void verifyCode(HttpServletResponse response) throws IOException { 
        VerifyCodeUtil verifyCodeUtil = new VerifyCodeUtil();
        //设置长宽
        VerifyCode verifyCode = verifyCodeUtil.generate(80, 28);
        //获取验证码中的字符
        String code = verifyCode.getCode();
        //将验证码中的字符写入redis、过期时间为300秒钟
        redisTemplate.opsForValue().set("vc_" + code, "1", 300, TimeUnit.SECONDS);
        //设置响应头
        response.setHeader("Pragma", "no-cache");
        //设置响应头
        response.setHeader("Cache-Control", "no-cache");
        //在代理服务器端防止缓冲
        response.setDateHeader("Expires", 0);
        //设置响应内容类型
        response.setContentType("image/jpeg");
        response.getOutputStream().write(verifyCode.getImgBytes());
        response.getOutputStream().flush();
    }

在这里我们存入redis的内容为{vc_code:1}(在此处不去处理两个验证码相同的情况,300秒过期时间还能相同那确实挺幸运的)

在登录和注册时,我们从相应的DTO中取出验证码,并与在Redis中进行查询,如果存在该验证码则进行下面的业务流程,如果不存在该验证码则抛出验证码错误异常(VerifyException)
请在exception包下新建该自定义异常类



public class VerifyException extends AuthenticationException { 
    public VerifyException(String msg) { 
        super(msg);
    }
    public VerifyException(String msg, Throwable t) { 
        super(msg, t);
    }
}

之后我们去全局异常处理类中添加上这个异常

		```
		else if (e instanceof VerifyException){ 
            //验证码错误
            return new CommonResult<>(40006, "Error", e.getMessage());
        }
        ```

完成上述步骤之后回到我们的UserController中,对我们的登录以及注册方法进行验证码验证

	@PostMapping("/login")
    public CommonResult<String> login(@RequestBody UserLoginDTO ulDTO, HttpServletRequest request) { 
        if (null == redisTemplate.opsForValue().get("vc_" + ulDTO.getVerifyCode())) { 
            throw new VerifyException("验证码错误");
        }
        redisTemplate.delete("vc_" + ulDTO.getVerifyCode());
        String token = userService.userLogin(ulDTO);

        return new CommonResult<>(20000, "OK", token);
    }

    @PostMapping("/register")
    public CommonResult<String> register(@RequestBody UserRegisterDTO urDTO) { 
        if (null == redisTemplate.opsForValue().get("vc_" + urDTO.getVerifyCode())) { 
            throw new VerifyException("验证码错误");
        }
        userService.userRegister(urDTO);
        return new CommonResult<>(20000, "OK", "注册成功");
    }

我们通过redisTemplate.opsForValue().get()方法去获得相应的验证码,如果验证码存在,那么执行接下来的业务代码,如果验证码不存在,则抛出异常要求用户重新填写验证码。

我们这篇博客的内容到这里就告一段落了,在下一篇博客就是本系列的最后一篇博客,我们会讲解分页和排序相关的内容

本次博客的内容也到此为止了,如果对博客内容有疑问可以私信联系笔者,如果这篇文章对你有用希望你能点一个赞,谢谢~

 
打赏
 本文转载自:网络 
所有权利归属于原作者,如文章来源标示错误或侵犯了您的权利请联系微信13520258486
更多>最近资讯中心
更多>最新资讯中心
0相关评论

推荐图文
推荐资讯中心
点击排行
最新信息
新手指南
采购商服务
供应商服务
交易安全
关注我们
手机网站:
新浪微博:
微信关注:

13520258486

周一至周五 9:00-18:00
(其他时间联系在线客服)

24小时在线客服