SpringBoot+Vue实现请求后台获取Base64编码的图片验证码并使用Redis缓存实现2分钟内有效

118 篇文章 11 订阅

场景

前端Vue的登录页面,验证码请求后台,后台生成验证码照片后使用Base64编码后,

返回给前端,前端进行显示。

 

注:

博客:
https://blog.csdn.net/badao_liumang_qizhi
关注公众号
霸道的程序猿
获取编程相关电子书、教程推送与免费下载。

实现

首先看前端页面login.vue

      <el-form-item prop="code">
        <el-input
          v-model="loginForm.code"
          auto-complete="off"
          placeholder="验证码"
          style="width: 63%"
          @keyup.enter.native="handleLogin"
        >
          <svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon" />
        </el-input>
        <div class="login-code">
          <img :src="codeUrl" @click="getCode" />
        </div>

这里验证码的图片的src属性是绑定的codeUrl,此属性来自于请求后台,并且绑定点击事件为getCode

此方法也是请求后台获取验证码。

所以需要提前声明codeUrl属性

  data() {
    return {
      codeUrl: "",

然后在页面加载完时请求后台接口获取验证码图片。

所以在created函数中执行请求验证码图片的方法

  created() {
    this.getCode();
  },

此方法的具体实现

  methods: {
    getCode() {
      getCodeImg().then(res => {
        this.codeUrl = "data:image/gif;base64," + res.img;
        this.loginForm.uuid = res.uuid;
      });
    },

在此方法中调用了请求后台接口获取验证码图片编码后的代码并在前面拼接上

"data:image/gif;base64,"

data:image/png;base64的用法详解

网页上有些图片的src或css背景图片的url后面跟了一大串字符,比如:

<img src='data:img/jpg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsK
CwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQU
FBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCAGuAa4DASIA
AhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQA
AAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3
ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWm
p6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEA
AwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSEx
BhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElK
U1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3
uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD9U6KK
KACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAoooo
AKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiuR+
KPxR8M/BjwJqfjHxlqY0bw5ppi+1Xv2eWfy/MlSJPkiVnOXkQcKcZyeATQB11FfKn/D0b9mL/opv
/lA1T/5Go/4ejfsxf9FN/wDKBqn/AMjUAfVdFfKn/D0b9mL/AKKb/wCUDVP/AJGo/wCHo37MX/RT
f/KBqn/yNQB9V0V8qf8AD0b9mL/opv8A5QNU/wDkaj/h6N+zF/0U3/ygap/8jUAfVdFfKn/D0b9m
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
n/hI/wCxf7U+3/6fa2vk+d9k8r/Xypuz5Un3c4284yM+AfsHfsG/HX4LftX+BvGXjHwP/YvhvTft
32u9/tewn8vzLC4iT5Ip2c5eRBwpxnJ4BNfr/RQAV/KvX9VFfyr0Afv7/wAEuf8AkxP4Zf8AcT/9
Ol3XlX/BVj9l34n/ALSf/Cr/APhXPhn/AISP+xf7U+3/AOn2tr5PnfZPK/18qbs+VJ93ONvOMjPq
v/BLn/kxP4Zf9xP/ANOl3X1XQB/Ox8Uf2DPjp8F/A2p+MvGPgb+x/Dem+V9rvf7XsJ/L8yRIk+SK
dnOXkQcKcZyeATXz9X7+/wDBUb/kxP4m/wDcM/8ATpaV+AVAH7+/8PRv2Yv+im/+UDVP/kaj/h6N
+zF/0U3/AMoGqf8AyNX4BUUAfv7/AMPRv2Yv+im/+UDVP/kavAP28f28vgV8af2UPHPg3wd44/tr
xJqX2E2ll/ZN/B5nl39vK/zywKgwkbnlhnGByQK/IGigAr6r/wCHXP7Tn/RMv/K/pf8A8k18qV/V
RQB8+/sGfC/xP8GP2T/A3g3xhpn9jeI9NN99rsvtEU/l+Zf3EqfPEzIcpIh4Y4zg8giur+Of7UXw
x/Zs/sT/AIWP4m/4Rz+2vP8AsH+gXV153k+X5v8AqIn2482P72M7uM4OPV6/Kv8A4Lnf80T/AO43
/wC2FAH1T/w9G/Zi/wCim/8AlA1T/wCRqP8Ah6N+zF/0U3/ygap/8jV+AVFAH9VFFFFABRRRQAUU
UUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRR
QAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFA
BRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAF
FFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQB//2Q=='/>

这是Data URI scheme。

Data URI scheme是在RFC2397中定义的,目的是将一些小的数据,直接嵌入到网页中,从而不用再从外部文件载入。比如上面那串字符,其实是一张图片。

在上面的Data URI中,data表示取得数据的协定名称,image/jpg 是数据类型名称,base64 是数据的编码方法,逗号后面就是这个image/png文件base64编码后的数据。 目前,Data URI scheme支持的类型有:

data:,文本数据
data:text/plain,文本数据
data:text/html,HTML代码
data:text/html;base64,base64编码的HTML代码
data:text/css,CSS代码
data:text/css;base64,base64编码的CSS代码
data:text/javascript,Javascript代码
data:text/javascript;base64,base64编码的Javascript代码
data:image/gif;base64,base64编码的gif图片数据
data:image/png;base64,base64编码的png图片数据
data:image/jpeg;base64,base64编码的jpeg图片数据
data:image/x-icon;base64,base64编码的icon图片数据

base64简单地说,它把一些 8-bit 数据翻译成标准 ASCII 字符。

我们把图像文件的内容直接写在了HTML 文件中,这样做的好处是,节省了一个HTTP 请求。坏处呢,就是浏览器不会缓存这种图像。

我们可以找一些在线网站将图片进行Base64编码。

https://tool.oschina.net/encrypt?type=4

 

所以在上面的请求图片验证的图片的src绑定的codeUrl中的路径是如下这种拼接的,所以后台SpringBoot只需要生成图片验证码并将其使用Base64b编码并返回即可。就是这里的res.img。

除了返回了img还返回了uuid这个属性,这就是用来控制验证码是否生效的字段。

UUID 是指Universally Unique Identifier,翻译为中文是通用唯一识别码,UUID 的目的是让分布式系统中的所有元素都能有唯一的识别信息

    getCode() {
      getCodeImg().then(res => {
        this.codeUrl = "data:image/gif;base64," + res.img;
        this.loginForm.uuid = res.uuid;
      });

将获取的uuid赋值给登录表单的uuid属性,所以需要提前声明uuid属性

  data() {
    return {
      codeUrl: "",
      loginForm: {
        username: "",
        password: "",
        rememberMe: false,
        code: "",
        uuid: ""
      },

使验证码在2分钟有效的逻辑是

在每次请求验证码的接口时会生成一个uuid,并且将这个uuid存储进Redis缓存中并设置2分钟有效期。

然后验证码接口将验证码和uuid一起返回给前端,前端将其接受并将其存储进表单对象的uuid属性。

在登录接口中接收前端提交过来的表单对象的uuid属性然后去Redis缓存中去查询,如果为空则证明已经过了2分钟则是验证码失效。

上面的请求验证码的接口方法 getCodeImg()是引用的外部js的方法

import { getCodeImg } from "@/api/login";

在login.js中

// 获取验证码
export function getCodeImg() {
  return request({
    url: '/captchaImage',
    method: 'get'
  })
}

实现向后台指定的url发动get请求,这里的request是封装的axios请求的对象,不再细讲。

下面看验证码接口的SpringBoot代码

    @GetMapping("/captchaImage")
    public AjaxResult getCode(HttpServletResponse response) throws IOException
    {
        // 生成随机字串
        String verifyCode = VerifyCodeUtils.generateVerifyCode(4);
        // 唯一标识
        String uuid = IdUtils.simpleUUID();
        String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;

        redisCache.setCacheObject(verifyKey, verifyCode, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);
        // 生成图片
        int w = 111, h = 36;
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        VerifyCodeUtils.outputImage(w, h, stream, verifyCode);
        try
        {
            AjaxResult ajax = AjaxResult.success();
            ajax.put("uuid", uuid);
            ajax.put("img", Base64.encode(stream.toByteArray()));
            return ajax;
        }
        catch (Exception e)
        {
            e.printStackTrace();
            return AjaxResult.error(e.getMessage());
        }
        finally
        {
            stream.close();
        }
    }

首先是生成随机字符串verifyCode ,这里调用了一个工具类的方法generateVerifyCode,参数4代表验证码的长度。

    /**
     * 使用系统默认字符源生成验证码
     *
     * @param verifySize 验证码长度
     * @return
     */
    public static String generateVerifyCode(int verifySize)
    {
        return generateVerifyCode(verifySize, VERIFY_CODES);
    }

此方法调用了generateVerifyCode,并增加了一个验证码的取值范围的参数。

VERIFY_CODES声明

public static final String VERIFY_CODES = "123456789ABCDEFGHJKLMNPQRSTUVWXYZ";

去掉了1,0,i,o几个容易混淆的字符

生成字符串的方法实现为

    /**
     * 使用指定源生成验证码
     *
     * @param verifySize 验证码长度
     * @param sources 验证码字符源
     * @return
     */
    public static String generateVerifyCode(int verifySize, String sources)
    {
        if (sources == null || sources.length() == 0)
        {
            sources = VERIFY_CODES;
        }
        int codesLen = sources.length();
        Random rand = new Random(System.currentTimeMillis());
        StringBuilder verifyCode = new StringBuilder(verifySize);
        for (int i = 0; i < verifySize; i++)
        {
            verifyCode.append(sources.charAt(rand.nextInt(codesLen - 1)));
        }
        return verifyCode.toString();
    }

再回到接口Controller

生成字符串之后生成UUID即唯一标识

        String uuid = IdUtils.simpleUUID();
        String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;

这里也调用了一个工具类的方法

    /**
     * 简化的UUID,去掉了横线
     *
     * @return 简化的UUID,去掉了横线
     */
    public static String simpleUUID()
    {
        return UUID.randomUUID().toString(true);
    }

此工具类方法又调用了UUID的randomUUID方法

UUID.java全部代码

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
import com.ruoyi.common.exception.UtilException;

/**
 * 提供通用唯一识别码(universally unique identifier)(UUID)实现
 *

 */
public final class UUID implements java.io.Serializable, Comparable<UUID>
{
    private static final long serialVersionUID = -1185015143654744140L;

    /**
     * SecureRandom 的单例
     *
     */
    private static class Holder
    {
        static final SecureRandom numberGenerator = getSecureRandom();
    }

    /** 此UUID的最高64有效位 */
    private final long mostSigBits;

    /** 此UUID的最低64有效位 */
    private final long leastSigBits;

    /**
     * 私有构造
     * 
     * @param data 数据
     */
    private UUID(byte[] data)
    {
        long msb = 0;
        long lsb = 0;
        assert data.length == 16 : "data must be 16 bytes in length";
        for (int i = 0; i < 8; i++)
        {
            msb = (msb << 8) | (data[i] & 0xff);
        }
        for (int i = 8; i < 16; i++)
        {
            lsb = (lsb << 8) | (data[i] & 0xff);
        }
        this.mostSigBits = msb;
        this.leastSigBits = lsb;
    }

    /**
     * 使用指定的数据构造新的 UUID。
     *
     * @param mostSigBits 用于 {@code UUID} 的最高有效 64 位
     * @param leastSigBits 用于 {@code UUID} 的最低有效 64 位
     */
    public UUID(long mostSigBits, long leastSigBits)
    {
        this.mostSigBits = mostSigBits;
        this.leastSigBits = leastSigBits;
    }

    /**
     * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的本地线程伪随机数生成器生成该 UUID。
     * 
     * @return 随机生成的 {@code UUID}
     */
    public static UUID fastUUID()
    {
        return randomUUID(false);
    }

    /**
     * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的强伪随机数生成器生成该 UUID。
     * 
     * @return 随机生成的 {@code UUID}
     */
    public static UUID randomUUID()
    {
        return randomUUID(true);
    }

    /**
     * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的强伪随机数生成器生成该 UUID。
     * 
     * @param isSecure 是否使用{@link SecureRandom}如果是可以获得更安全的随机码,否则可以得到更好的性能
     * @return 随机生成的 {@code UUID}
     */
    public static UUID randomUUID(boolean isSecure)
    {
        final Random ng = isSecure ? Holder.numberGenerator : getRandom();

        byte[] randomBytes = new byte[16];
        ng.nextBytes(randomBytes);
        randomBytes[6] &= 0x0f; /* clear version */
        randomBytes[6] |= 0x40; /* set to version 4 */
        randomBytes[8] &= 0x3f; /* clear variant */
        randomBytes[8] |= 0x80; /* set to IETF variant */
        return new UUID(randomBytes);
    }

    /**
     * 根据指定的字节数组获取类型 3(基于名称的)UUID 的静态工厂。
     *
     * @param name 用于构造 UUID 的字节数组。
     *
     * @return 根据指定数组生成的 {@code UUID}
     */
    public static UUID nameUUIDFromBytes(byte[] name)
    {
        MessageDigest md;
        try
        {
            md = MessageDigest.getInstance("MD5");
        }
        catch (NoSuchAlgorithmException nsae)
        {
            throw new InternalError("MD5 not supported");
        }
        byte[] md5Bytes = md.digest(name);
        md5Bytes[6] &= 0x0f; /* clear version */
        md5Bytes[6] |= 0x30; /* set to version 3 */
        md5Bytes[8] &= 0x3f; /* clear variant */
        md5Bytes[8] |= 0x80; /* set to IETF variant */
        return new UUID(md5Bytes);
    }

    /**
     * 根据 {@link #toString()} 方法中描述的字符串标准表示形式创建{@code UUID}。
     *
     * @param name 指定 {@code UUID} 字符串
     * @return 具有指定值的 {@code UUID}
     * @throws IllegalArgumentException 如果 name 与 {@link #toString} 中描述的字符串表示形式不符抛出此异常
     *
     */
    public static UUID fromString(String name)
    {
        String[] components = name.split("-");
        if (components.length != 5)
        {
            throw new IllegalArgumentException("Invalid UUID string: " + name);
        }
        for (int i = 0; i < 5; i++)
        {
            components[i] = "0x" + components[i];
        }

        long mostSigBits = Long.decode(components[0]).longValue();
        mostSigBits <<= 16;
        mostSigBits |= Long.decode(components[1]).longValue();
        mostSigBits <<= 16;
        mostSigBits |= Long.decode(components[2]).longValue();

        long leastSigBits = Long.decode(components[3]).longValue();
        leastSigBits <<= 48;
        leastSigBits |= Long.decode(components[4]).longValue();

        return new UUID(mostSigBits, leastSigBits);
    }

    /**
     * 返回此 UUID 的 128 位值中的最低有效 64 位。
     *
     * @return 此 UUID 的 128 位值中的最低有效 64 位。
     */
    public long getLeastSignificantBits()
    {
        return leastSigBits;
    }

    /**
     * 返回此 UUID 的 128 位值中的最高有效 64 位。
     *
     * @return 此 UUID 的 128 位值中最高有效 64 位。
     */
    public long getMostSignificantBits()
    {
        return mostSigBits;
    }

    /**
     * 与此 {@code UUID} 相关联的版本号. 版本号描述此 {@code UUID} 是如何生成的。
     * <p>
     * 版本号具有以下含意:
     * <ul>
     * <li>1 基于时间的 UUID
     * <li>2 DCE 安全 UUID
     * <li>3 基于名称的 UUID
     * <li>4 随机生成的 UUID
     * </ul>
     *
     * @return 此 {@code UUID} 的版本号
     */
    public int version()
    {
        // Version is bits masked by 0x000000000000F000 in MS long
        return (int) ((mostSigBits >> 12) & 0x0f);
    }

    /**
     * 与此 {@code UUID} 相关联的变体号。变体号描述 {@code UUID} 的布局。
     * <p>
     * 变体号具有以下含意:
     * <ul>
     * <li>0 为 NCS 向后兼容保留
     * <li>2 <a href="http://www.ietf.org/rfc/rfc4122.txt">IETF&nbsp;RFC&nbsp;4122</a>(Leach-Salz), 用于此类
     * <li>6 保留,微软向后兼容
     * <li>7 保留供以后定义使用
     * </ul>
     *
     * @return 此 {@code UUID} 相关联的变体号
     */
    public int variant()
    {
        // This field is composed of a varying number of bits.
        // 0 - - Reserved for NCS backward compatibility
        // 1 0 - The IETF aka Leach-Salz variant (used by this class)
        // 1 1 0 Reserved, Microsoft backward compatibility
        // 1 1 1 Reserved for future definition.
        return (int) ((leastSigBits >>> (64 - (leastSigBits >>> 62))) & (leastSigBits >> 63));
    }

    /**
     * 与此 UUID 相关联的时间戳值。
     *
     * <p>
     * 60 位的时间戳值根据此 {@code UUID} 的 time_low、time_mid 和 time_hi 字段构造。<br>
     * 所得到的时间戳以 100 毫微秒为单位,从 UTC(通用协调时间) 1582 年 10 月 15 日零时开始。
     *
     * <p>
     * 时间戳值仅在在基于时间的 UUID(其 version 类型为 1)中才有意义。<br>
     * 如果此 {@code UUID} 不是基于时间的 UUID,则此方法抛出 UnsupportedOperationException。
     *
     * @throws UnsupportedOperationException 如果此 {@code UUID} 不是 version 为 1 的 UUID。
     */
    public long timestamp() throws UnsupportedOperationException
    {
        checkTimeBase();
        return (mostSigBits & 0x0FFFL) << 48//
                | ((mostSigBits >> 16) & 0x0FFFFL) << 32//
                | mostSigBits >>> 32;
    }

    /**
     * 与此 UUID 相关联的时钟序列值。
     *
     * <p>
     * 14 位的时钟序列值根据此 UUID 的 clock_seq 字段构造。clock_seq 字段用于保证在基于时间的 UUID 中的时间唯一性。
     * <p>
     * {@code clockSequence} 值仅在基于时间的 UUID(其 version 类型为 1)中才有意义。 如果此 UUID 不是基于时间的 UUID,则此方法抛出
     * UnsupportedOperationException。
     *
     * @return 此 {@code UUID} 的时钟序列
     *
     * @throws UnsupportedOperationException 如果此 UUID 的 version 不为 1
     */
    public int clockSequence() throws UnsupportedOperationException
    {
        checkTimeBase();
        return (int) ((leastSigBits & 0x3FFF000000000000L) >>> 48);
    }

    /**
     * 与此 UUID 相关的节点值。
     *
     * <p>
     * 48 位的节点值根据此 UUID 的 node 字段构造。此字段旨在用于保存机器的 IEEE 802 地址,该地址用于生成此 UUID 以保证空间唯一性。
     * <p>
     * 节点值仅在基于时间的 UUID(其 version 类型为 1)中才有意义。<br>
     * 如果此 UUID 不是基于时间的 UUID,则此方法抛出 UnsupportedOperationException。
     *
     * @return 此 {@code UUID} 的节点值
     *
     * @throws UnsupportedOperationException 如果此 UUID 的 version 不为 1
     */
    public long node() throws UnsupportedOperationException
    {
        checkTimeBase();
        return leastSigBits & 0x0000FFFFFFFFFFFFL;
    }

    /**
     * 返回此{@code UUID} 的字符串表现形式。
     *
     * <p>
     * UUID 的字符串表示形式由此 BNF 描述:
     * 
     * <pre>
     * {@code
     * UUID                   = <time_low>-<time_mid>-<time_high_and_version>-<variant_and_sequence>-<node>
     * time_low               = 4*<hexOctet>
     * time_mid               = 2*<hexOctet>
     * time_high_and_version  = 2*<hexOctet>
     * variant_and_sequence   = 2*<hexOctet>
     * node                   = 6*<hexOctet>
     * hexOctet               = <hexDigit><hexDigit>
     * hexDigit               = [0-9a-fA-F]
     * }
     * </pre>
     * 
     * </blockquote>
     *
     * @return 此{@code UUID} 的字符串表现形式
     * @see #toString(boolean)
     */
    @Override
    public String toString()
    {
        return toString(false);
    }

    /**
     * 返回此{@code UUID} 的字符串表现形式。
     *
     * <p>
     * UUID 的字符串表示形式由此 BNF 描述:
     * 
     * <pre>
     * {@code
     * UUID                   = <time_low>-<time_mid>-<time_high_and_version>-<variant_and_sequence>-<node>
     * time_low               = 4*<hexOctet>
     * time_mid               = 2*<hexOctet>
     * time_high_and_version  = 2*<hexOctet>
     * variant_and_sequence   = 2*<hexOctet>
     * node                   = 6*<hexOctet>
     * hexOctet               = <hexDigit><hexDigit>
     * hexDigit               = [0-9a-fA-F]
     * }
     * </pre>
     * 
     * </blockquote>
     *
     * @param isSimple 是否简单模式,简单模式为不带'-'的UUID字符串
     * @return 此{@code UUID} 的字符串表现形式
     */
    public String toString(boolean isSimple)
    {
        final StringBuilder builder = new StringBuilder(isSimple ? 32 : 36);
        // time_low
        builder.append(digits(mostSigBits >> 32, 8));
        if (false == isSimple)
        {
            builder.append('-');
        }
        // time_mid
        builder.append(digits(mostSigBits >> 16, 4));
        if (false == isSimple)
        {
            builder.append('-');
        }
        // time_high_and_version
        builder.append(digits(mostSigBits, 4));
        if (false == isSimple)
        {
            builder.append('-');
        }
        // variant_and_sequence
        builder.append(digits(leastSigBits >> 48, 4));
        if (false == isSimple)
        {
            builder.append('-');
        }
        // node
        builder.append(digits(leastSigBits, 12));

        return builder.toString();
    }

    /**
     * 返回此 UUID 的哈希码。
     *
     * @return UUID 的哈希码值。
     */
    @Override
    public int hashCode()
    {
        long hilo = mostSigBits ^ leastSigBits;
        return ((int) (hilo >> 32)) ^ (int) hilo;
    }

    /**
     * 将此对象与指定对象比较。
     * <p>
     * 当且仅当参数不为 {@code null}、而是一个 UUID 对象、具有与此 UUID 相同的 varriant、包含相同的值(每一位均相同)时,结果才为 {@code true}。
     *
     * @param obj 要与之比较的对象
     *
     * @return 如果对象相同,则返回 {@code true};否则返回 {@code false}
     */
    @Override
    public boolean equals(Object obj)
    {
        if ((null == obj) || (obj.getClass() != UUID.class))
        {
            return false;
        }
        UUID id = (UUID) obj;
        return (mostSigBits == id.mostSigBits && leastSigBits == id.leastSigBits);
    }

    // Comparison Operations

    /**
     * 将此 UUID 与指定的 UUID 比较。
     *
     * <p>
     * 如果两个 UUID 不同,且第一个 UUID 的最高有效字段大于第二个 UUID 的对应字段,则第一个 UUID 大于第二个 UUID。
     *
     * @param val 与此 UUID 比较的 UUID
     *
     * @return 在此 UUID 小于、等于或大于 val 时,分别返回 -1、0 或 1。
     *
     */
    @Override
    public int compareTo(UUID val)
    {
        // The ordering is intentionally set up so that the UUIDs
        // can simply be numerically compared as two numbers
        return (this.mostSigBits < val.mostSigBits ? -1 : //
                (this.mostSigBits > val.mostSigBits ? 1 : //
                        (this.leastSigBits < val.leastSigBits ? -1 : //
                                (this.leastSigBits > val.leastSigBits ? 1 : //
                                        0))));
    }

    // -------------------------------------------------------------------------------------------------------------------
    // Private method start
    /**
     * 返回指定数字对应的hex值
     * 
     * @param val 值
     * @param digits 位
     * @return 值
     */
    private static String digits(long val, int digits)
    {
        long hi = 1L << (digits * 4);
        return Long.toHexString(hi | (val & (hi - 1))).substring(1);
    }

    /**
     * 检查是否为time-based版本UUID
     */
    private void checkTimeBase()
    {
        if (version() != 1)
        {
            throw new UnsupportedOperationException("Not a time-based UUID");
        }
    }

    /**
     * 获取{@link SecureRandom},类提供加密的强随机数生成器 (RNG)
     * 
     * @return {@link SecureRandom}
     */
    public static SecureRandom getSecureRandom()
    {
        try
        {
            return SecureRandom.getInstance("SHA1PRNG");
        }
        catch (NoSuchAlgorithmException e)
        {
            throw new UtilException(e);
        }
    }

    /**
     * 获取随机数生成器对象<br>
     * ThreadLocalRandom是JDK 7之后提供并发产生随机数,能够解决多个线程发生的竞争争夺。
     * 
     * @return {@link ThreadLocalRandom}
     */
    public static ThreadLocalRandom getRandom()
    {
        return ThreadLocalRandom.current();
    }
}

这样就能生成UUID作为唯一标志。在将其存进Redi缓存时添加一个常量类中定义的指定前缀

String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;

常量

public static final String CAPTCHA_CODE_KEY = "captcha_codes:";

然后将其存储进redis中并设置2分钟有效

redisCache.setCacheObject(verifyKey, verifyCode, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);

有效期也是在常量类中定义

public static final Integer CAPTCHA_EXPIRATION = 2;

然后下面就是生成图片的操作,调用了验证码工具类VerifyCodeUtils的方法outputImage

工具类全部代码

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Random;
import javax.imageio.ImageIO;

/**
 * 验证码工具类
 *
 */
public class VerifyCodeUtils
{
    // 使用到Algerian字体,系统里没有的话需要安装字体,字体只显示大写,去掉了1,0,i,o几个容易混淆的字符
    public static final String VERIFY_CODES = "123456789ABCDEFGHJKLMNPQRSTUVWXYZ";

    private static Random random = new SecureRandom();

    /**
     * 使用系统默认字符源生成验证码
     *
     * @param verifySize 验证码长度
     * @return
     */
    public static String generateVerifyCode(int verifySize)
    {
        return generateVerifyCode(verifySize, VERIFY_CODES);
    }

    /**
     * 使用指定源生成验证码
     *
     * @param verifySize 验证码长度
     * @param sources 验证码字符源
     * @return
     */
    public static String generateVerifyCode(int verifySize, String sources)
    {
        if (sources == null || sources.length() == 0)
        {
            sources = VERIFY_CODES;
        }
        int codesLen = sources.length();
        Random rand = new Random(System.currentTimeMillis());
        StringBuilder verifyCode = new StringBuilder(verifySize);
        for (int i = 0; i < verifySize; i++)
        {
            verifyCode.append(sources.charAt(rand.nextInt(codesLen - 1)));
        }
        return verifyCode.toString();
    }

    /**
     * 输出指定验证码图片流
     *
     * @param w
     * @param h
     * @param os
     * @param code
     * @throws IOException
     */
    public static void outputImage(int w, int h, OutputStream os, String code) throws IOException
    {
        int verifySize = code.length();
        BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
        Random rand = new Random();
        Graphics2D g2 = image.createGraphics();
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        Color[] colors = new Color[5];
        Color[] colorSpaces = new Color[] { Color.WHITE, Color.CYAN, Color.GRAY, Color.LIGHT_GRAY, Color.MAGENTA,
                Color.ORANGE, Color.PINK, Color.YELLOW };
        float[] fractions = new float[colors.length];
        for (int i = 0; i < colors.length; i++)
        {
            colors[i] = colorSpaces[rand.nextInt(colorSpaces.length)];
            fractions[i] = rand.nextFloat();
        }
        Arrays.sort(fractions);

        g2.setColor(Color.GRAY);// 设置边框色
        g2.fillRect(0, 0, w, h);

        Color c = getRandColor(200, 250);
        g2.setColor(c);// 设置背景色
        g2.fillRect(0, 2, w, h - 4);

        // 绘制干扰线
        Random random = new Random();
        g2.setColor(getRandColor(160, 200));// 设置线条的颜色
        for (int i = 0; i < 20; i++)
        {
            int x = random.nextInt(w - 1);
            int y = random.nextInt(h - 1);
            int xl = random.nextInt(6) + 1;
            int yl = random.nextInt(12) + 1;
            g2.drawLine(x, y, x + xl + 40, y + yl + 20);
        }

        // 添加噪点
        float yawpRate = 0.05f;// 噪声率
        int area = (int) (yawpRate * w * h);
        for (int i = 0; i < area; i++)
        {
            int x = random.nextInt(w);
            int y = random.nextInt(h);
            int rgb = getRandomIntColor();
            image.setRGB(x, y, rgb);
        }

        shear(g2, w, h, c);// 使图片扭曲

        g2.setColor(getRandColor(100, 160));
        int fontSize = h - 4;
        Font font = new Font("Algerian", Font.ITALIC, fontSize);
        g2.setFont(font);
        char[] chars = code.toCharArray();
        for (int i = 0; i < verifySize; i++)
        {
            AffineTransform affine = new AffineTransform();
            affine.setToRotation(Math.PI / 4 * rand.nextDouble() * (rand.nextBoolean() ? 1 : -1),
                    (w / verifySize) * i + fontSize / 2, h / 2);
            g2.setTransform(affine);
            g2.drawChars(chars, i, 1, ((w - 10) / verifySize) * i + 5, h / 2 + fontSize / 2 - 10);
        }

        g2.dispose();
        ImageIO.write(image, "jpg", os);
    }

    private static Color getRandColor(int fc, int bc)
    {
        if (fc > 255) {
            fc = 255;
        }
        if (bc > 255) {
            bc = 255;
        }
        int r = fc + random.nextInt(bc - fc);
        int g = fc + random.nextInt(bc - fc);
        int b = fc + random.nextInt(bc - fc);
        return new Color(r, g, b);
    }

    private static int getRandomIntColor()
    {
        int[] rgb = getRandomRgb();
        int color = 0;
        for (int c : rgb)
        {
            color = color << 8;
            color = color | c;
        }
        return color;
    }

    private static int[] getRandomRgb()
    {
        int[] rgb = new int[3];
        for (int i = 0; i < 3; i++)
        {
            rgb[i] = random.nextInt(255);
        }
        return rgb;
    }

    private static void shear(Graphics g, int w1, int h1, Color color)
    {
        shearX(g, w1, h1, color);
        shearY(g, w1, h1, color);
    }

    private static void shearX(Graphics g, int w1, int h1, Color color)
    {

        int period = random.nextInt(2);

        boolean borderGap = true;
        int frames = 1;
        int phase = random.nextInt(2);

        for (int i = 0; i < h1; i++)
        {
            double d = (double) (period >> 1)
                    * Math.sin((double) i / (double) period + (6.2831853071795862D * (double) phase) / (double) frames);
            g.copyArea(0, i, w1, 1, (int) d, 0);
            if (borderGap)
            {
                g.setColor(color);
                g.drawLine((int) d, i, 0, i);
                g.drawLine((int) d + w1, i, w1, i);
            }
        }

    }

    private static void shearY(Graphics g, int w1, int h1, Color color)
    {

        int period = random.nextInt(40) + 10; // 50;

        boolean borderGap = true;
        int frames = 20;
        int phase = 7;
        for (int i = 0; i < w1; i++)
        {
            double d = (double) (period >> 1)
                    * Math.sin((double) i / (double) period + (6.2831853071795862D * (double) phase) / (double) frames);
            g.copyArea(i, 0, 1, h1, 0, (int) d);
            if (borderGap)
            {
                g.setColor(color);
                g.drawLine(i, (int) d, i, 0);
                g.drawLine(i, (int) d + h1, i, h1);
            }

        }
    }
}

然后就是将图片使用Base64编码,这里也调用了Base64工具类,全部代码

/**
 * Base64工具类
 *
 */
public final class Base64
{
    static private final int     BASELENGTH           = 128;
    static private final int     LOOKUPLENGTH         = 64;
    static private final int     TWENTYFOURBITGROUP   = 24;
    static private final int     EIGHTBIT             = 8;
    static private final int     SIXTEENBIT           = 16;
    static private final int     FOURBYTE             = 4;
    static private final int     SIGN                 = -128;
    static private final char    PAD                  = '=';
    static final private byte[]  base64Alphabet       = new byte[BASELENGTH];
    static final private char[]  lookUpBase64Alphabet = new char[LOOKUPLENGTH];

    static
    {
        for (int i = 0; i < BASELENGTH; ++i)
        {
            base64Alphabet[i] = -1;
        }
        for (int i = 'Z'; i >= 'A'; i--)
        {
            base64Alphabet[i] = (byte) (i - 'A');
        }
        for (int i = 'z'; i >= 'a'; i--)
        {
            base64Alphabet[i] = (byte) (i - 'a' + 26);
        }

        for (int i = '9'; i >= '0'; i--)
        {
            base64Alphabet[i] = (byte) (i - '0' + 52);
        }

        base64Alphabet['+'] = 62;
        base64Alphabet['/'] = 63;

        for (int i = 0; i <= 25; i++)
        {
            lookUpBase64Alphabet[i] = (char) ('A' + i);
        }

        for (int i = 26, j = 0; i <= 51; i++, j++)
        {
            lookUpBase64Alphabet[i] = (char) ('a' + j);
        }

        for (int i = 52, j = 0; i <= 61; i++, j++)
        {
            lookUpBase64Alphabet[i] = (char) ('0' + j);
        }
        lookUpBase64Alphabet[62] = (char) '+';
        lookUpBase64Alphabet[63] = (char) '/';
    }

    private static boolean isWhiteSpace(char octect)
    {
        return (octect == 0x20 || octect == 0xd || octect == 0xa || octect == 0x9);
    }

    private static boolean isPad(char octect)
    {
        return (octect == PAD);
    }

    private static boolean isData(char octect)
    {
        return (octect < BASELENGTH && base64Alphabet[octect] != -1);
    }

    /**
     * Encodes hex octects into Base64
     *
     * @param binaryData Array containing binaryData
     * @return Encoded Base64 array
     */
    public static String encode(byte[] binaryData)
    {
        if (binaryData == null)
        {
            return null;
        }

        int lengthDataBits = binaryData.length * EIGHTBIT;
        if (lengthDataBits == 0)
        {
            return "";
        }

        int fewerThan24bits = lengthDataBits % TWENTYFOURBITGROUP;
        int numberTriplets = lengthDataBits / TWENTYFOURBITGROUP;
        int numberQuartet = fewerThan24bits != 0 ? numberTriplets + 1 : numberTriplets;
        char encodedData[] = null;

        encodedData = new char[numberQuartet * 4];

        byte k = 0, l = 0, b1 = 0, b2 = 0, b3 = 0;

        int encodedIndex = 0;
        int dataIndex = 0;

        for (int i = 0; i < numberTriplets; i++)
        {
            b1 = binaryData[dataIndex++];
            b2 = binaryData[dataIndex++];
            b3 = binaryData[dataIndex++];

            l = (byte) (b2 & 0x0f);
            k = (byte) (b1 & 0x03);

            byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
            byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);
            byte val3 = ((b3 & SIGN) == 0) ? (byte) (b3 >> 6) : (byte) ((b3) >> 6 ^ 0xfc);

            encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
            encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)];
            encodedData[encodedIndex++] = lookUpBase64Alphabet[(l << 2) | val3];
            encodedData[encodedIndex++] = lookUpBase64Alphabet[b3 & 0x3f];
        }

        // form integral number of 6-bit groups
        if (fewerThan24bits == EIGHTBIT)
        {
            b1 = binaryData[dataIndex];
            k = (byte) (b1 & 0x03);
            byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
            encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
            encodedData[encodedIndex++] = lookUpBase64Alphabet[k << 4];
            encodedData[encodedIndex++] = PAD;
            encodedData[encodedIndex++] = PAD;
        }
        else if (fewerThan24bits == SIXTEENBIT)
        {
            b1 = binaryData[dataIndex];
            b2 = binaryData[dataIndex + 1];
            l = (byte) (b2 & 0x0f);
            k = (byte) (b1 & 0x03);

            byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
            byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);

            encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
            encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)];
            encodedData[encodedIndex++] = lookUpBase64Alphabet[l << 2];
            encodedData[encodedIndex++] = PAD;
        }
        return new String(encodedData);
    }

    /**
     * Decodes Base64 data into octects
     *
     * @param encoded string containing Base64 data
     * @return Array containind decoded data.
     */
    public static byte[] decode(String encoded)
    {
        if (encoded == null)
        {
            return null;
        }

        char[] base64Data = encoded.toCharArray();
        // remove white spaces
        int len = removeWhiteSpace(base64Data);

        if (len % FOURBYTE != 0)
        {
            return null;// should be divisible by four
        }

        int numberQuadruple = (len / FOURBYTE);

        if (numberQuadruple == 0)
        {
            return new byte[0];
        }

        byte decodedData[] = null;
        byte b1 = 0, b2 = 0, b3 = 0, b4 = 0;
        char d1 = 0, d2 = 0, d3 = 0, d4 = 0;

        int i = 0;
        int encodedIndex = 0;
        int dataIndex = 0;
        decodedData = new byte[(numberQuadruple) * 3];

        for (; i < numberQuadruple - 1; i++)
        {

            if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++]))
                    || !isData((d3 = base64Data[dataIndex++])) || !isData((d4 = base64Data[dataIndex++])))
            {
                return null;
            } // if found "no data" just return null

            b1 = base64Alphabet[d1];
            b2 = base64Alphabet[d2];
            b3 = base64Alphabet[d3];
            b4 = base64Alphabet[d4];

            decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
            decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
            decodedData[encodedIndex++] = (byte) (b3 << 6 | b4);
        }

        if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++])))
        {
            return null;// if found "no data" just return null
        }

        b1 = base64Alphabet[d1];
        b2 = base64Alphabet[d2];

        d3 = base64Data[dataIndex++];
        d4 = base64Data[dataIndex++];
        if (!isData((d3)) || !isData((d4)))
        {// Check if they are PAD characters
            if (isPad(d3) && isPad(d4))
            {
                if ((b2 & 0xf) != 0)// last 4 bits should be zero
                {
                    return null;
                }
                byte[] tmp = new byte[i * 3 + 1];
                System.arraycopy(decodedData, 0, tmp, 0, i * 3);
                tmp[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
                return tmp;
            }
            else if (!isPad(d3) && isPad(d4))
            {
                b3 = base64Alphabet[d3];
                if ((b3 & 0x3) != 0)// last 2 bits should be zero
                {
                    return null;
                }
                byte[] tmp = new byte[i * 3 + 2];
                System.arraycopy(decodedData, 0, tmp, 0, i * 3);
                tmp[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
                tmp[encodedIndex] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
                return tmp;
            }
            else
            {
                return null;
            }
        }
        else
        { // No PAD e.g 3cQl
            b3 = base64Alphabet[d3];
            b4 = base64Alphabet[d4];
            decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
            decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
            decodedData[encodedIndex++] = (byte) (b3 << 6 | b4);

        }
        return decodedData;
    }

    /**
     * remove WhiteSpace from MIME containing encoded Base64 data.
     *
     * @param data the byte array of base64 data (with WS)
     * @return the new length
     */
    private static int removeWhiteSpace(char[] data)
    {
        if (data == null)
        {
            return 0;
        }

        // count characters that's not whitespace
        int newSize = 0;
        int len = data.length;
        for (int i = 0; i < len; i++)
        {
            if (!isWhiteSpace(data[i]))
            {
                data[newSize++] = data[i];
            }
        }
        return newSize;
    }
}

这样验证码接口就会把UUID和验证码都返回给前端。

其中AjaxResult是自定义响应结果类

import java.util.HashMap;
import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.utils.StringUtils;

/**
 * 操作消息提醒
 *
 */
public class AjaxResult extends HashMap<String, Object>
{
    private static final long serialVersionUID = 1L;

    /** 状态码 */
    public static final String CODE_TAG = "code";

    /** 返回内容 */
    public static final String MSG_TAG = "msg";

    /** 数据对象 */
    public static final String DATA_TAG = "data";

    /**
     * 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。
     */
    public AjaxResult()
    {
    }

    /**
     * 初始化一个新创建的 AjaxResult 对象
     *
     * @param code 状态码
     * @param msg 返回内容
     */
    public AjaxResult(int code, String msg)
    {
        super.put(CODE_TAG, code);
        super.put(MSG_TAG, msg);
    }

    /**
     * 初始化一个新创建的 AjaxResult 对象
     *
     * @param code 状态码
     * @param msg 返回内容
     * @param data 数据对象
     */
    public AjaxResult(int code, String msg, Object data)
    {
        super.put(CODE_TAG, code);
        super.put(MSG_TAG, msg);
        if (StringUtils.isNotNull(data))
        {
            super.put(DATA_TAG, data);
        }
    }

    /**
     * 返回成功消息
     *
     * @return 成功消息
     */
    public static AjaxResult success()
    {
        return AjaxResult.success("操作成功");
    }

    /**
     * 返回成功数据
     *
     * @return 成功消息
     */
    public static AjaxResult success(Object data)
    {
        return AjaxResult.success("操作成功", data);
    }

    /**
     * 返回成功消息
     *
     * @param msg 返回内容
     * @return 成功消息
     */
    public static AjaxResult success(String msg)
    {
        return AjaxResult.success(msg, null);
    }

    /**
     * 返回成功消息
     *
     * @param msg 返回内容
     * @param data 数据对象
     * @return 成功消息
     */
    public static AjaxResult success(String msg, Object data)
    {
        return new AjaxResult(HttpStatus.SUCCESS, msg, data);
    }

    /**
     * 返回错误消息
     *
     * @return
     */
    public static AjaxResult error()
    {
        return AjaxResult.error("操作失败");
    }

    /**
     * 返回错误消息
     *
     * @param msg 返回内容
     * @return 警告消息
     */
    public static AjaxResult error(String msg)
    {
        return AjaxResult.error(msg, null);
    }

    /**
     * 返回错误消息
     *
     * @param msg 返回内容
     * @param data 数据对象
     * @return 警告消息
     */
    public static AjaxResult error(String msg, Object data)
    {
        return new AjaxResult(HttpStatus.ERROR, msg, data);
    }

    /**
     * 返回错误消息
     *
     * @param code 状态码
     * @param msg 返回内容
     * @return 警告消息
     */
    public static AjaxResult error(int code, String msg)
    {
        return new AjaxResult(code, msg, null);
    }
}

然后在登录接口中

        String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
        String captcha = redisCache.getCacheObject(verifyKey);
        redisCache.deleteObject(verifyKey);
        if (captcha == null)
        {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
            throw new CaptchaExpireException();
        }

获取前端传递的uuid并加上同样的前缀,去缓存中查询,并且从缓存中删除,如果查不出来则返回验证码失效。

 

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
以下是使用Spring Boot和Vue.js实现图片验证码登录验证的前后端代码: ## 后端代码(Spring Boot) ### 1. 引入依赖 在`pom.xml`中添加以下依赖: ```xml <dependency> <groupId>com.github.axet</groupId> <artifactId>kaptcha</artifactId> <version>0.0.9</version> </dependency> ``` ### 2. 创建验证码生成器 在`config`包下创建一个`KaptchaConfig`类,用于配置验证码生成器: ```java @Configuration public class KaptchaConfig { @Bean public Producer captchaProducer() { Properties properties = new Properties(); // 配置验证码生成器 // ... return new DefaultKaptcha(); } } ``` ### 3. 创建验证码接口 在`controller`包下创建一个`CaptchaController`类,用于生成验证码图片: ```java @RestController @RequestMapping("/captcha") public class CaptchaController { @Autowired private Producer captchaProducer; @GetMapping("/image") public void captchaImage(HttpServletRequest request, HttpServletResponse response) throws Exception { // 设置响应头信息,告诉浏览器返回的是图片 response.setContentType("image/jpeg"); // 配置验证码生成器 // ... // 生成验证码文本和图片 String captchaText = captchaProducer.createText(); BufferedImage captchaImage = captchaProducer.createImage(captchaText); // 将验证码文本存入session HttpSession session = request.getSession(); session.setAttribute("captchaText", captchaText); // 将验证码图片输出到响应流中 ServletOutputStream out = response.getOutputStream(); ImageIO.write(captchaImage, "jpeg", out); out.flush(); out.close(); } } ``` ### 4. 创建登录接口 在`controller`包下创建一个`LoginController`类,用于处理登录请求: ```java @RestController @RequestMapping("/login") public class LoginController { @PostMapping("/check") public boolean check(@RequestParam String captcha, HttpSession session) { // 获取session中存储的验证码文本 String captchaText = (String) session.getAttribute("captchaText"); // 比较用户输入的验证码和session中存储的验证码是否一致 return captchaText != null && captchaText.equalsIgnoreCase(captcha); } } ``` ## 前端代码(Vue.js) ### 1. 安装依赖 在项目目录下执行以下命令安装依赖: ```bash npm install axios vue-axios vue-qriously ``` ### 2. 创建组件 在`components`目录下创建一个`CaptchaLogin`组件,包含一个输入框、一个验证码图片和一个登录按钮: ```html <template> <div> <input type="text" v-model="captcha" placeholder="请输入验证码" /> <qriously :value="captchaImageUrl"></qriously> <button @click="login">登录</button> </div> </template> <script> import axios from "axios"; import VueAxios from "vue-axios"; import Qriously from "vue-qriously"; export default { name: "CaptchaLogin", components: { Qriously, }, data() { return { captcha: "", captchaImageUrl: "", }; }, created() { this.refreshCaptcha(); }, methods: { refreshCaptcha() { const captchaUrl = `/captcha/image?timestamp=${new Date().getTime()}`; this.captchaImageUrl = captchaUrl; }, login() { axios .post("/login/check", { captcha: this.captcha }) .then((response) => { if (response.data) { alert("登录成功"); } else { alert("验证码错误"); } this.refreshCaptcha(); }); }, }, mounted() { Vue.use(VueAxios, axios); }, }; </script> ``` ### 3. 在页面中使用组件 在需要登录验证的页面中使用`CaptchaLogin`组件: ```html <template> <div> <CaptchaLogin /> </div> </template> <script> import CaptchaLogin from "@/components/CaptchaLogin.vue"; export default { name: "LoginPage", components: { CaptchaLogin, }, }; </script> ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

霸道流氓气质

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值