【Spring Boot】2.3 VerifyCodeImageUtil 工具类

本文介绍了两种Spring Boot实现验证码的方式:基于Session的简单高效方案和使用Redis支持集群部署的版本。Session版直接输出图片,前端接收显示;Redis版返回JSON包含Base64图片数据,前端需处理。Redis版还涉及JPEG压缩,适用于集群环境。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前提

图片验证码的工具是两个版本 简单的图片验证码是网上翻来的
业务逻辑很简单,生成一个图片验证码,保存到Session里,然后将图片输入注入Response对象 传递给前端,显示出来
前端用户把验证码图片中的数字提交到后端
后端从Session把验证码拿到 跟用户输入的作比对 如此就完成了验证码的逻辑
复杂版的是纯原创,基于Redis的缓存功能 将工具生成的图片正码数字保存到Redis,同时利用MyBatisPlus提供的雪花算法生成一个Redis的Key值,将key值和验证码图片一起发送给前端,前端再用户输入验证码后,将验证码和对应的Key值一起传回后端
后端通过Key值去Redis拿到验证码的号码,于用户输入的数据做比对 如此也能完成验证码的逻辑

这里有两个小细节

Session模式是直出图片给前端 从前端角度来讲 接收到的就是一个普通图片的 直接显示就完事儿了
Redis模式是返回一个Json数据 里边包含了Base64之后的图片数据 需要前端进行一定的处理才能显示

另外

Redis版本的图片 还用到了JPEGEncode的图片压缩 貌似这种尺度的压缩不是很明显 这个后续再研究

Session版 简单高效

  • Util 工具类
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Random;

/***
 * Author: YL.Lou
 * Class: RandomValidateCodeUtil
 * Project: base
 * Introduce:
 * DateTime: 2022-06-30 13:59
 ***/
 
@Slf4j
@Component
public class VerifyCodeImageUtil {

    @Autowired
    private Result result;

    public static final String RANDOM_CODE_KEY= "RANDOM_VALIDATE_CODE_KEY";//放到session中的key

    private int width = 95;// 图片宽
    private int height = 25;// 图片高
    private int stringCount = 4;// 随机产生字符数量
    private int fontSize = 18; // 字符大小

    private Random random = new Random();

    /**
     * 获得字体 HANGING_BASELINE CENTER_BASELINE PLAIN
     */
    private Font getFont() {
        return new Font("Fixedsys", Font.BOLD, fontSize);
    }

    /**
     * 获得颜色
     */
    private Color getRandColor(int fc, int bc) {
        int r = BaseUtil.getRandomNum(fc,bc);
        int g = BaseUtil.getRandomNum(fc,bc);
        int b = BaseUtil.getRandomNum(fc,bc);
        return new Color(r, g, b);
    }

    /**
     * 生成随机图片
     */
    public void getRandCode(
            Integer w,
            Integer h,
            Integer l,
            Integer s,
            HttpServletRequest request,
            HttpServletResponse response
    ) {
        HttpSession session = request.getSession();

        if (null != w) this.width = w;
        if (null != h) this.height = h;
        if (null != l) this.stringCount = l;
        if (null != s) this.fontSize = s;

        // BufferedImage类是具有缓冲区的Image类,Image类是用于描述图像信息的类
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);

        Graphics g = image.getGraphics();// 产生Image对象的Graphics对象,改对象可以在图像上进行各种绘制操作

        g.fillRect(0, 0, width, height);//图片大小

        // 绘制背景干扰线
        for (int i = 0; i <= 10; i++) {
            drawLine(g);
        }

        // 绘制随机字符
        String randomString = BaseUtil.getRandom(stringCount);

        for (int i = 0; i < stringCount; i++) {
            drawString(g, randomString, i);
        }

        // 绘制前景干扰线
        for (int i = 0; i <= 5; i++) {
            drawLine(g);
        }

        //将生成的随机字符串保存到session中
        session.removeAttribute(RANDOM_CODE_KEY);
        session.setAttribute(RANDOM_CODE_KEY, randomString);

        // 释放资源
        g.dispose();

        try {
            // 将内存中的图片通过流动形式输出到客户端
            ImageIO.write(image, "JPEG", response.getOutputStream());

        } catch (Exception e) {
            log.error("将图片流输出到客户端失败", e);
        }
    }

    /**
     * 绘制字符串
     */
    private void drawString(Graphics g, String randomString, int i) {

        g.setFont(getFont());
        g.setColor(getRandColor(33, 99));

        String str = getRandomString(randomString,i);

        int x = fontSize * i + BaseUtil.getRandomNum(1, 3);
        int y = fontSize + BaseUtil.getRandomNum(-2, 2) ;

        g.drawString(str,x,y);
    }

    /**
     * 绘制干扰线
     */
    private void drawLine(Graphics g) {
        g.setColor(getRandColor(150,250));

        int x = random.nextInt(width);
        int y = random.nextInt(height);
        int xl = random.nextInt(width / 2);
        int yl = random.nextInt(height / 2);

        g.drawLine(x, y, x + xl, y + yl);
    }

    /**
     * 获取随机的字符
     */
    public String getRandomString(String randomString, int num) {
        char[] chars = randomString.toCharArray();
        return String.valueOf(chars[num]);
    }

    /**
     * 校验图片验证码
     */
    public boolean checkCode(HttpServletRequest request, String code){
        HttpSession session = request.getSession();

        if (null == session){
            return false;
        }

        Object attribute = session.getAttribute(RANDOM_CODE_KEY);

        if (null == attribute || null == code){
            return false;
        }

        String sessionCode = attribute.toString();

        if (null == sessionCode || !code.equals(sessionCode)){
            return false;
        }

        return true;
    }
}
  • Controller 层 输出验证码
		/**
     * 生成并返回验证码图片
     */
    @GetMapping(value = "/imgCode")
    public void getImgCode(
            @Min(value = 48, message = "验证码图片宽度不能低于 48")@RequestParam(value = "width", required = false, defaultValue = "95") Integer width,
            @Min(value = 16, message = "验证码图片高度不能低于 16")@RequestParam(value = "height", required = false, defaultValue = "25") Integer height,
            @Min(value = 4, message = "验证码字符长度不能低于 4")@RequestParam(value = "length",required = false, defaultValue = "4") Integer length,
            @Min(value = 12, message = "验证码字符大小不能低于 12") @RequestParam(value = "size",required = false, defaultValue = "18") Integer size,
            HttpServletRequest request,
            HttpServletResponse response) {
        try {
            response.setContentType("image/jpeg");//设置相应类型,告诉浏览器输出的内容为图片
            response.setHeader("Pragma", "No-cache");//设置响应头信息,告诉浏览器不要缓存此内容
            response.setHeader("Cache-Control", "no-cache");
            response.setDateHeader("Expire", 0);

            verifyCodeImageUtil.getRandCode(width, height, length, size, request, response);//输出验证码图片方法
        } catch (Exception e) {
            log.error("获取验证码失败", e);
        }
    }
  • Controller 层 核对验证码
    /**
     * 获取手机验证码
     * @param uPhone 手机号
     * @param imgCode 图片验证码
     * @param request request
     * @return
     */
    @PostMapping(value = "/smsCode")
    public Result getSmsCode(
            @NotBlank(message = "手机号码不能为空") @Pattern(regexp = "^\\d{11}$", message = "手机号码应为11位数字") @RequestParam("uPhone") String uPhone,
            @NotBlank(message = "图片验证码不能为空") @Pattern(regexp = "^\\d{4}$", message = "图片验证码应为4位数字") @RequestParam("imgCode") String imgCode,
            HttpServletRequest request
    ){
        // 验证图片验证码是否正确
        if (!verifyCodeImageUtil.checkCode(request, imgCode)){
            return result.failed("图片验证码输入错误");
        }

        //调起service 获得短信发送的最终状态
        return publicService.getSmsCode(uPhone);
    }

Redis版本 支持集群部署

  • Util 工具类

import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGEncodeParam;
import com.sun.image.codec.jpeg.JPEGImageEncoder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Base64;
import java.util.HashMap;
import java.util.concurrent.TimeUnit;

/***
 * Author: YL.Lou
 * Class: RandomValidateCodeUtil
 * Project: base
 * Introduce: 生成一个随机字符串图片 利用Redis进行图片验证
 ***/
@Slf4j
@Component
public class VerifyCodeImageUtil {
    // 包装返回结果用的
    @Autowired
    private Result result;

    // 存储Redis数据用的
    @Autowired
    private RedisUtil redisUtil;

    // 定义图片的默认大小
    private int width = 95;// 图片宽
    private int height = 25;// 图片高
    private int stringCount = 4;// 随机产生字符数量
    private int fontSize = 18; // 字符大小

    /**
     * 获得字体 HANGING_BASELINE CENTER_BASELINE PLAIN
     */
    private Font getFont() {
        return new Font("Fixedsys", Font.BOLD, fontSize);
    }

    /**
     * 获得颜色
     */
    private Color getRandColor(int fc, int bc) {
        int r = BaseUtil.getRandomNum(fc,bc);
        int g = BaseUtil.getRandomNum(fc,bc);
        int b = BaseUtil.getRandomNum(fc,bc);
        return new Color(r, g, b);
    }

    /**
     * 生成随机图片
     */
    public Result getRandCode(
            Integer w,
            Integer h,
            Integer l,
            Integer s
    ) {

        if (null != w) this.width = w;
        if (null != h) this.height = h;
        if (null != l) this.stringCount = l;
        if (null != s) this.fontSize = s;

        // BufferedImage类是具有缓冲区的Image类,Image类是用于描述图像信息的类
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);

        Graphics g = image.getGraphics();// 产生Image对象的Graphics对象,改对象可以在图像上进行各种绘制操作

        g.fillRect(0, 0, width, height);//图片大小

        // 绘制背景干扰线
        for (int i = 0; i <= 5; i++) {
            drawLine(g);
        }

        // 绘制随机字符
        String randomString = BaseUtil.getRandom(stringCount);

        for (int i = 0; i < stringCount; i++) {
            drawString(g, randomString, i);
        }

        // 绘制前景干扰线
        for (int i = 0; i <= 5; i++) {
            drawLine(g);
        }

        // 释放画笔资源
        g.dispose();

        // 保存缓存值
        String key = String.valueOf(IdWorker.getId());

        // 保存code到Redis 60分钟过期
        redisUtil.add(RedisPrefix.IMG_CODE + key, randomString, 30, TimeUnit.MINUTES);

        // 图片数据的容器
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

        // 将数据写入容器
        try {
            ImageIO.write(image, "JPEG", byteArrayOutputStream);
        } catch (IOException e) {
            return result.failed("生成验证码图片失败");
        }

        // 将容器中的数据进行JPEG压缩
        JPEGImageEncoder jpegEncoder = JPEGCodec.createJPEGEncoder(byteArrayOutputStream);
        JPEGEncodeParam jpegEncodeParam = jpegEncoder.getDefaultJPEGEncodeParam(image);
        jpegEncodeParam.setQuality(0.75f, true);
        jpegEncoder.setJPEGEncodeParam(jpegEncodeParam);
        try {
            jpegEncoder.encode(image, jpegEncodeParam);
        } catch (IOException e) {
            return result.failed("图片压缩失败");
        }
        // 释放图片资源
        image.flush();

        // 得到JPEG数据
        Base64.Encoder encoder = Base64.getEncoder();
        String data = encoder.encodeToString(byteArrayOutputStream.toByteArray());

        // 将Redis的Key值 和 Base64数据打包
        HashMap<String, String> stringStringHashMap = new HashMap<>();
        stringStringHashMap.put("imgKey",key);
        stringStringHashMap.put("imgData","data:image/jpeg;base64," + data);

        // 将打包好的数据返回给Controller
        return result.success("生成验证码图片成功", stringStringHashMap);
    }

    /**
     * 绘制字符串
     */
    private void drawString(Graphics g, String randomString, int i) {

        g.setFont(getFont());
        g.setColor(getRandColor(30, 90));

        String str = getRandomString(randomString,i);

        int x = fontSize * i + BaseUtil.getRandomNum(1, 3);
        int y = fontSize + BaseUtil.getRandomNum(-2, 2) ;

        g.drawString(str,x,y);
    }

    /**
     * 绘制干扰线
     */
    private void drawLine(Graphics g) {
        g.setColor(getRandColor(100,200));

        int x = BaseUtil.getRandomNum(5, width - 5);
        int y = BaseUtil.getRandomNum(5, height - 5);
        int xl = BaseUtil.getRandomNum(5, width - 5);
        int yl = BaseUtil.getRandomNum(5, height - 5);

        g.drawLine(x, y, xl, yl);
    }

    /**
     * 获取随机的字符
     */
    public String getRandomString(String randomString, int num) {
        char[] chars = randomString.toCharArray();
        return String.valueOf(chars[num]);
    }

    /**
     * 核对图片验证码输入是否正确(Redis isMulti = true)
     * @param code 用户输入的验证码
     * @param key 前端传回的 用来去Redis拉取数据的 Key值
     * @return 是否核对成功 true 成功 false 失败
     */
    public boolean checkImgCode(String code, String key) {

        // 去Redis 拿到 Code
        String originCode = redisUtil.get(RedisPrefix.IMG_CODE + key);

        // 判断是否拿到codeFromRedis
        if (null == originCode){
            return false;
        }

        // 判断code是否匹配
        if (!code.equals(originCode)){
            return false;
        }

        // 将Redis删除 留着也没用
        redisUtil.delete(RedisPrefix.IMG_CODE + key);

        // 最后返回核验成功
        return true;
    }
}
  • Controller 层 输出验证码
/**
     * 生成并返回验证码图片Base64方式返回
     */
    @GetMapping(value = "/imgCode")
    public Result getImgCode(
            @Min(value = 48, message = "验证码图片宽度不能低于 48")@RequestParam(value = "width", required = false, defaultValue = "95") Integer width,
            @Min(value = 16, message = "验证码图片高度不能低于 16")@RequestParam(value = "height", required = false, defaultValue = "25") Integer height,
            @Min(value = 4, message = "验证码字符长度不能低于 4")@RequestParam(value = "length",required = false, defaultValue = "4") Integer length,
            @Min(value = 12, message = "验证码字符大小不能低于 12") @RequestParam(value = "size",required = false, defaultValue = "18") Integer size
    ) {
        // 返回二维码图片的Base64数据
        return verifyCodeImageUtil.getRandCode(width, height, length, size);
    }
  • Controller 层 核对验证码
/**
     * 获取手机验证码
     * @param uPhone 手机号
     * @param imgCode 图片验证码
     * @param imgKey 验证码图片Key值
     * @return
     */
    @PostMapping(value = "/smsCode")
    public Result getSmsCode(
            @NotBlank(message = "手机号码不能为空") @Pattern(regexp = "^\\d{11}$", message = "手机号码应为11位数字") @RequestParam("uPhone") String uPhone,
            @NotBlank(message = "图片验证码不能为空") @Pattern(regexp = "^\\d{4}$", message = "图片验证码应为4位数字") @RequestParam("imgCode") String imgCode,
            @NotBlank(message = "前端未传回Key值") @RequestParam("imgKey") String imgKey
    ){
        // 验证图片验证码是否正确
        if (verifyCodeImageUtil.checkImgCode(imgCode, imgKey)){
            return result.failed("图片验证码输入错误");
        }

        //调起service 获得短信发送的最终状态
        return publicService.getSmsCode(uPhone);
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值