java中基于 Hutool 工具库实现的图形验证码工具类

java中基于 Hutool 工具库实现的图形验证码工具类

一、介绍

基于 Hutool 工具库实现的图形验证码工具类 CaptchaUtil,适配 Spring 生态,提供生成图形验证码(Base64 / 流)+ 验证码存储(Session/Redis)+ 输入校验的完整能力,支持登录、注册等场景的人机校验。

注:Spring 生态 = 核心 Spring 框架 + 一系列配套组件 / 子项目 + 第三方集成方案

二、实现

1.引入的依赖

需在 Maven/Gradle 中引入 Hutool 核心包(图形验证码依赖 hutool-captcha 模块,已包含在核心包中),若使用 Redis 存储需额外引入 Spring Data Redis:

xml 复制代码
<!-- Hutool 核心包(含图形验证码功能) -->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.28</version> <!-- 推荐使用最新稳定版 -->
</dependency>

<!-- Spring Web(用于 HttpServletRequest/Response、Session) -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>${spring.version}</version> <!-- 与项目 Spring 版本一致 -->
</dependency>

<!-- 可选:Redis 存储(分布式环境推荐) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <version>${spring-boot.version}</version>
</dependency>

2.实现工具类

支持 2 种存储策略(Session 单机版 / Redis 分布式版)、3 种验证码类型(算术验证码、字符验证码、中文验证码),生成结果支持 Base64 编码(前端直接渲染)或字节流(直接响应给浏览器)。

java 复制代码
import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.CircleCaptcha;
import cn.hutool.captcha.ChineseCaptcha;
import cn.hutool.captcha.LineCaptcha;
import cn.hutool.captcha.ArithmeticCaptcha;
import cn.hutool.captcha.Captcha;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.concurrent.TimeUnit;

/**
 * 基于 Hutool 的图形验证码工具类(适配 Spring 生态)
 * 支持:生成验证码(Base64/流)、存储验证码(Session/Redis)、校验验证码
 */
public class CaptchaUtil {

    // -------------------------- 配置常量(可根据项目需求调整) --------------------------
    /** 验证码默认宽度 */
    private static final int DEFAULT_WIDTH = 120;
    /** 验证码默认高度 */
    private static final int DEFAULT_HEIGHT = 40;
    /** 字符验证码默认长度 */
    private static final int DEFAULT_CHAR_LENGTH = 4;
    /** 验证码默认过期时间(秒) */
    private static final int DEFAULT_EXPIRE_SECONDS = 180;
    /** Session 中验证码存储的 key */
    private static final String SESSION_CAPTCHA_KEY = "SESSION_CAPTCHA_CODE";
    /** Redis 中验证码存储的前缀(分布式环境) */
    private static final String REDIS_CAPTCHA_PREFIX = "captcha:";

    // -------------------------- 存储策略枚举 --------------------------
    public enum StoreType {
        /** 本地 Session 存储(单机部署) */
        SESSION,
        /** Redis 存储(分布式部署) */
        REDIS
    }

    // -------------------------- 验证码类型枚举 --------------------------
    public enum CaptchaType {
        /** 算术验证码(如:3+5=?) */
        ARITHMETIC,
        /** 字符验证码(字母+数字) */
        CHAR,
        /** 中文验证码(中文汉字) */
        CHINESE
    }

    // -------------------------- 核心生成方法 --------------------------

    /**
     * 生成图形验证码(返回 Base64 编码,前端可直接用 <img src="data:image/png;base64,..." /> 渲染)
     * @param captchaType 验证码类型
     * @param storeType 存储策略
     * @return 验证码信息(Base64 编码 + 验证码标识,Redis 存储时需返回标识给前端)
     */
    public static CaptchaResult generateBase64(CaptchaType captchaType, StoreType storeType) {
        // 1. 创建对应类型的验证码对象
        Captcha captcha = createCaptcha(captchaType);
        // 2. 获取验证码内容(算术验证码为计算结果,其他为显示的字符)
        String code = getCaptchaCode(captcha);
        // 3. 存储验证码(Session/Redis)
        String captchaKey = storeCaptcha(code, storeType);
        // 4. 将验证码图片转为 Base64 编码
        String base64 = imageToBase64(captcha);
        // 5. 返回结果(Redis 需携带 captchaKey 供校验,Session 无需)
        return new CaptchaResult(base64, captchaKey);
    }

    /**
     * 生成图形验证码(直接响应字节流到浏览器,前端用 <img src="/captcha" /> 调用)
     * @param captchaType 验证码类型
     * @param storeType 存储策略
     * @throws IOException 流写入异常
     */
    public static void generateStream(CaptchaType captchaType, StoreType storeType) throws IOException {
        // 1. 创建对应类型的验证码对象
        Captcha captcha = createCaptcha(captchaType);
        // 2. 获取验证码内容并存储
        String code = getCaptchaCode(captcha);
        storeCaptcha(code, storeType);
        // 3. 直接响应到浏览器(设置响应头为图片类型)
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes != null) {
            captcha.write(attributes.getResponse().getOutputStream());
        }
    }

    // -------------------------- 核心校验方法 --------------------------

    /**
     * 校验用户输入的验证码是否正确
     * @param userInputCode 用户输入的验证码
     * @param storeType 存储策略(需与生成时一致)
     * @param captchaKey Redis 存储时需传入前端返回的标识(Session 存储时可传 null)
     * @return true=校验通过,false=校验失败
     */
    public static boolean validate(String userInputCode, StoreType storeType, String captchaKey) {
        // 1. 参数校验
        if (StrUtil.isBlank(userInputCode)) {
            return false;
        }
        // 2. 获取存储的验证码
        String storedCode = getStoredCaptcha(storeType, captchaKey);
        if (StrUtil.isBlank(storedCode)) {
            return false; // 验证码已过期或不存在
        }
        // 3. 忽略大小写校验(字符/中文验证码),算术验证码严格匹配
        boolean isMatch = storedCode.equalsIgnoreCase(userInputCode);
        // 4. 校验通过后删除验证码(防止重复使用)
        if (isMatch) {
            removeCaptcha(storeType, captchaKey);
        }
        return isMatch;
    }

    // -------------------------- 内部辅助方法 --------------------------

    /**
     * 创建指定类型的验证码对象
     */
    private static Captcha createCaptcha(CaptchaType captchaType) {
        switch (captchaType) {
            case ARITHMETIC:
                // 算术验证码(位数默认 2 位,可调整)
                return new ArithmeticCaptcha(DEFAULT_WIDTH, DEFAULT_HEIGHT, 2);
            case CHAR:
                // 字符验证码(干扰线+圆圈干扰)
                LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_CHAR_LENGTH, 10);
                lineCaptcha.setGenerator(new RandomGenerator("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", DEFAULT_CHAR_LENGTH));
                return lineCaptcha;
            case CHINESE:
                // 中文验证码
                return CaptchaUtil.createChineseCaptcha(DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_CHAR_LENGTH);
            default:
                throw new IllegalArgumentException("不支持的验证码类型:" + captchaType);
        }
    }

    /**
     * 获取验证码的核心内容(算术验证码取结果,其他取显示字符)
     */
    private static String getCaptchaCode(Captcha captcha) {
        if (captcha instanceof ArithmeticCaptcha) {
            // 算术验证码的 code 是计算表达式(如 "3+5="),结果存在 arithmeticResult 中
            return String.valueOf(((ArithmeticCaptcha) captcha).getArithmeticResult());
        } else {
            // 字符/中文验证码的 code 就是显示的内容
            return captcha.getCode();
        }
    }

    /**
     * 存储验证码(Session/Redis)
     * @return 存储的 key(Redis 需返回给前端,Session 直接用固定 key)
     */
    private static String storeCaptcha(String code, StoreType storeType) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes == null) {
            throw new IllegalStateException("当前环境不存在 HttpServletRequest");
        }

        switch (storeType) {
            case SESSION:
                // Session 存储(单机部署)
                HttpSession session = attributes.getRequest().getSession();
                session.setAttribute(SESSION_CAPTCHA_KEY, code);
                session.setMaxInactiveInterval(DEFAULT_EXPIRE_SECONDS); // 设置过期时间
                return SESSION_CAPTCHA_KEY;

            case REDIS:
                // Redis 存储(分布式部署):生成唯一标识作为 key
                String captchaKey = REDIS_CAPTCHA_PREFIX + IdUtil.fastSimpleUUID();
                StringRedisTemplate redisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
                redisTemplate.opsForValue().set(captchaKey, code, DEFAULT_EXPIRE_SECONDS, TimeUnit.SECONDS);
                return captchaKey;

            default:
                throw new IllegalArgumentException("不支持的存储策略:" + storeType);
        }
    }

    /**
     * 获取存储的验证码
     */
    private static String getStoredCaptcha(StoreType storeType, String captchaKey) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes == null) {
            return null;
        }

        switch (storeType) {
            case SESSION:
                HttpSession session = attributes.getRequest().getSession(false);
                return session != null ? (String) session.getAttribute(SESSION_CAPTCHA_KEY) : null;

            case REDIS:
                if (StrUtil.isBlank(captchaKey) || !captchaKey.startsWith(REDIS_CAPTCHA_PREFIX)) {
                    return null;
                }
                StringRedisTemplate redisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
                return redisTemplate.opsForValue().get(captchaKey);

            default:
                return null;
        }
    }

    /**
     * 删除已校验通过的验证码(防止重复使用)
     */
    private static void removeCaptcha(StoreType storeType, String captchaKey) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes == null) {
            return;
        }

        switch (storeType) {
            case SESSION:
                HttpSession session = attributes.getRequest().getSession(false);
                if (session != null) {
                    session.removeAttribute(SESSION_CAPTCHA_KEY);
                }
                break;

            case REDIS:
                if (StrUtil.isNotBlank(captchaKey)) {
                    StringRedisTemplate redisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
                    redisTemplate.delete(captchaKey);
                }
                break;
        }
    }

    /**
     * 验证码图片转为 Base64 编码(带 DataURI 前缀,前端可直接使用)
     */
    private static String imageToBase64(Captcha captcha) {
        try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
            captcha.write(outputStream);
            byte[] imageBytes = outputStream.toByteArray();
            // 拼接 DataURI 前缀(png 格式)
            return "data:image/png;base64," + Base64.encode(imageBytes);
        } catch (IOException e) {
            throw new RuntimeException("验证码图片转 Base64 失败", e);
        }
    }

    // -------------------------- 内部结果封装类 --------------------------
    /**
     * 验证码生成结果(Base64 编码 + 验证码标识)
     */
    public static class CaptchaResult {
        /** 验证码 Base64 编码(前端可直接渲染) */
        private final String base64;
        /** 验证码标识(Redis 存储时需返回给前端,校验时传入) */
        private final String captchaKey;

        public CaptchaResult(String base64, String captchaKey) {
            this.base64 = base64;
            this.captchaKey = captchaKey;
        }

        // getter
        public String getBase64() {
            return base64;
        }

        public String getCaptchaKey() {
            return captchaKey;
        }
    }
}

三、使用

1. 配置 SpringUtil(Hutool 提供,用于获取 Spring Bean)

需在 Spring 配置类中启用 Hutool 的 SpringUtil,用于工具类中获取 StringRedisTemplate

java 复制代码
import cn.hutool.extra.spring.SpringUtil;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class HutoolConfig {
    @Bean
    public SpringUtil springUtil() {
        return new SpringUtil();
    }
}
2.Controller 层调用示例
java 复制代码
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@RestController
@RequestMapping("/captcha")
public class CaptchaController {

    /**
     * 生成 Base64 验证码(适合前后端分离项目)
     * 前端请求后,用 <img src="${base64}" /> 渲染,校验时传入 captchaKey 和用户输入
     */
    @GetMapping("/base64")
    public CaptchaUtil.CaptchaResult generateBase64Captcha() {
        // 生成算术验证码,使用 Redis 存储(分布式环境)
        return CaptchaUtil.generateBase64(CaptchaUtil.CaptchaType.ARITHMETIC, CaptchaUtil.StoreType.REDIS);
    }

    /**
     * 生成流验证码(适合传统 JSP/Thymeleaf 项目)
     * 前端用 <img src="/captcha/stream" onclick="this.src='/captcha/stream?'+Math.random()" /> 渲染
     */
    @GetMapping("/stream")
    public void generateStreamCaptcha(HttpServletResponse response) throws IOException {
        // 生成字符验证码,使用 Session 存储(单机环境)
        response.setContentType("image/png"); // 设置响应类型为图片
        response.setHeader("Pragma", "no-cache"); // 禁止缓存
        response.setHeader("Cache-Control", "no-cache");
        response.setDateHeader("Expires", 0);
        CaptchaUtil.generateStream(CaptchaUtil.CaptchaType.CHAR, CaptchaUtil.StoreType.SESSION);
    }

    /**
     * 校验验证码
     * @param userInput 用户输入的验证码
     * @param captchaKey Redis 存储时的验证码标识(Session 存储时可传 null)
     */
    @PostMapping("/validate")
    public boolean validateCaptcha(@RequestParam String userInput, @RequestParam(required = false) String captchaKey) {
        // 若生成时用 Redis 存储,这里 storeType 传 REDIS,否则传 SESSION
        return CaptchaUtil.validate(userInput, CaptchaUtil.StoreType.REDIS, captchaKey);
    }
}
3. 前端使用示例(Vue 为例)
vue 复制代码
<template>
  <div>
    <!-- 渲染 Base64 验证码 -->
    <img :src="captchaBase64" @click="refreshCaptcha" style="cursor: pointer;" />
    <input v-model="userInput" placeholder="请输入验证码" />
    <button @click="validate">校验</button>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      captchaBase64: '',
      captchaKey: '', // Redis 存储的验证码标识
      userInput: ''
    };
  },
  mounted() {
    this.refreshCaptcha(); // 初始化加载验证码
  },
  methods: {
    // 刷新验证码
    async refreshCaptcha() {
      const res = await axios.get('/captcha/base64');
      this.captchaBase64 = res.data.base64;
      this.captchaKey = res.data.captchaKey;
    },
    // 校验验证码
    async validate() {
      const res = await axios.post('/captcha/validate', {}, {
        params: {
          userInput: this.userInput,
          captchaKey: this.captchaKey
        }
      });
      if (res.data) {
        alert('校验通过!');
      } else {
        alert('验证码错误或已过期!');
        this.refreshCaptcha(); // 校验失败刷新验证码
      }
    }
  }
};
</script>

四、特性说明

  1. 多类型支持:算术验证码(防机器人破解更强)、字符验证码、中文验证码,可按需选择。
  2. 多存储策略:
    • Session 存储:适合单机部署,无需额外依赖。
    • Redis 存储:适合分布式部署,通过唯一标识关联验证码,支持集群共享。
  3. 多返回格式:
    • Base64 编码:适合前后端分离项目,前端直接渲染,无需额外请求。
    • 字节流:适合传统服务端渲染项目,通过 <img src="/captcha/stream" /> 直接调用。
  4. 安全特性:
    • 验证码过期时间可配置(默认 3 分钟)。
    • 校验通过后自动删除验证码,防止重复使用。
    • 字符验证码支持大小写忽略校验,算术验证码严格匹配。

五、扩展与调整

​ 1.配置调整 :可修改工具类中的常量(宽度、高度、过期时间等),或新增配置类通过 @Value 注入(更灵活)。

​ 2.验证码样式 :Hutool 支持自定义验证码样式(如干扰线数量、字体、颜色),可在 createCaptcha 方法中扩展,例如:

java 复制代码
// 自定义字符验证码字体
LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(120, 40, 4, 10);
lineCaptcha.setFont(new Font("微软雅黑", Font.BOLD, 20)); // 设置字体
lineCaptcha.setBackColor(Color.WHITE); // 设置背景色

​ 3.分布式部署:若项目为分布式,就可以使用 Redis 存储策略,确保多实例共享验证码。

​ 4.异常处理 :可在工具类中添加自定义异常(如 CaptchaExpiredExceptionCaptchaInvalidException),替代默认的 RuntimeException,便于业务层统一处理。

六、补充

1.在分布式系统中也可以使用别的分布式缓存中间件来替代Redis,例如:
  • Memcached:轻量级分布式缓存,支持键值存储 + 过期时间,适合简单场景(仅存验证码 + TTL)。
  • Redisson :基于 Redis 的分布式工具集,兼容 Redis API,还提供分布式锁、原子操作等增强功能(若项目已用 Redisson,可直接替换 StringRedisTemplate)。
  • Tair/ Codis:阿里 / 字节开源的分布式缓存,兼容 Redis 协议,适合大规模集群场景。
2.Hutool还有什么常用工具?
1.StrUtil(字符串工具)

示例:

java 复制代码
import cn.hutool.core.util.StrUtil;

public class StrUtilDemo {
    public static void main(String[] args) {
        String str = "  Hello Hutool!  ";
        
        // 1. 空判断(避免 NPE,比 String.isEmpty() 更全面)
        StrUtil.isEmpty(str); // false(有空格)
        StrUtil.isBlank(str); // false(空格不算"有效字符")
        StrUtil.isNotBlank(str); // true
        
        // 2. 去除空格(灵活处理前后/中间空格)
        StrUtil.trim(str); // "Hello Hutool!"(去除前后空格)
        StrUtil.trimAll(str); // "HelloHutool!"(去除所有空格)
        
        // 3. 字符串截取(安全截取,避免索引越界)
        StrUtil.sub(str, 2, 8); // "Hello "(从索引2到8,左闭右开)
        StrUtil.subAfter(str, "Hello", false); // " Hutool!  "(截取"Hello"之后的内容)
        
        // 4. 字符串拼接(比 + 号高效,支持多参数)
        StrUtil.format("姓名:{},年龄:{}", "张三", 25); // "姓名:张三,年龄:25"
        StrUtil.join(",", "A", "B", "C"); // "A,B,C"
        
        // 5. 格式校验(无需正则功底)
        StrUtil.isEmail("test@163.com"); // true
        StrUtil.isMobile("13800138000"); // true(支持国内手机号校验)
        StrUtil.isNumber("123456"); // true(判断是否为纯数字字符串)
    }
}
2.DateUtil(日期时间工具)

示例:

java 复制代码
/*
优势:
线程安全(内部使用 ThreadLocal 管理格式化器);
支持 50+ 种常见日期格式自动识别;
日期计算 API 直观(无需手动处理毫秒数)。
*/

import cn.hutool.core.date.DateUtil;
import java.util.Date;

public class DateUtilDemo {
    public static void main(String[] args) {
        // 1. 获取当前时间(多种格式)
        Date now = DateUtil.date(); // 当前 Date 对象
        String nowStr = DateUtil.now(); // "2025-11-26 15:30:00"(yyyy-MM-dd HH:mm:ss)
        String today = DateUtil.today(); // "2025-11-26"(yyyy-MM-dd)
        
        // 2. 日期格式化(无需手动创建 SimpleDateFormat)
        String format = DateUtil.format(now, "yyyy年MM月dd日 HH时mm分ss秒"); // "2025年11月26日 15时30分00秒"
        
        // 3. 字符串转日期(自动识别常见格式)
        Date date1 = DateUtil.parse("2025-11-26");
        Date date2 = DateUtil.parse("2025/11/26 15:30");
        Date date3 = DateUtil.parse("2025年11月26日", "yyyy年MM月dd日"); // 自定义格式
        
        // 4. 日期计算(加减、相差天数/小时等)
        Date tomorrow = DateUtil.offsetDay(now, 1); // 明天此时
        Date lastWeek = DateUtil.offsetWeek(now, -1); // 上周此时
        long days = DateUtil.between(date1, date2, DateUnit.DAY); // 两个日期相差的天数
        boolean isLeapYear = DateUtil.isLeapYear(2024); // 判断闰年(true)
        
        // 5. 常用日期判断
        DateUtil.isSameDay(date1, date2); // 判断是否同一天
        DateUtil.isWeekend(now); // 判断是否周末
    }
}
3.CollUtil(集合工具)

简化 ListMapSet 的创建、遍历、筛选、转换,解决集合操作的重复代码。

示例:

java 复制代码
/*
快速创建集合(一行代码替代多行 add);
安全操作(CollUtil.get 避免 Map 获取值时的 NullPointerException);
简化集合转换(无需手动遍历)。
*/

import cn.hutool.core.collection.CollUtil;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class CollUtilDemo {
    public static void main(String[] args) {
        // 1. 快速创建集合(无需 new ArrayList/HashMap)
        List<String> list = CollUtil.newArrayList("A", "B", "C");
        Map<String, Integer> map = CollUtil.newHashMap();
        map = CollUtil.of(new String[][]{{"name", "张三"}, {"age", 25}}); // 数组转 Map
        
        // 2. 集合判空(避免 NPE)
        CollUtil.isEmpty(list); // false
        CollUtil.isNotEmpty(list); // true
        
        // 3. 集合操作(拼接、筛选、分组)
        String joinStr = CollUtil.join(list, ","); // "A,B,C"(集合转字符串)
        List<String> subList = CollUtil.sub(list, 0, 2); // [A,B](安全截取)
        List<String> filterList = CollUtil.filter(list, s -> s.startsWith("A")); // 筛选以 A 开头的元素
        
        // 4. Map 操作(快速获取值,避免 NPE)
        Integer age = CollUtil.get(map, "age", 0); // 获取 key 为 age 的值,默认 0
        Map<String, List<String>> groupMap = CollUtil.groupBy(list, s -> s.length() + ""); // 按字符串长度分组
        
        // 5. 集合转换(List <-> 数组、Map <-> List)
        String[] arr = CollUtil.toArray(list, String.class); // List 转数组
        List<Map.Entry<String, Integer>> entryList = CollUtil.entryToList(map); // Map 转 Entry 列表
    }
}
其他工具:

IO 与文件工具(FileUtil/IoUtil),替代 java.io 原生 API(繁琐、需手动关流),简化文件读写、复制、移动、压缩等操作

流工具(IoUtil),处理 InputStream/OutputStream/Reader/Writer,自动关流,避免资源泄露:

加密解密工具(SecureUtil),封装 MD5、SHA、AES、RSA 等常见加密算法,无需关注底层实现,一行代码完成加密。

网络工具(HttpUtil),替代 HttpClient(原生 API 繁琐),简化 HTTP 请求(GET/POST/ 文件上传下载),支持 HTTPS、超时设置、请求头自定义。

等等,就不一一介绍了。

相关推荐
Highcharts.js1 小时前
Highcharts Gantt 甘特图任务配置文档说明
java·数据库·甘特图·模板模式·highcharts·任务关系
h***04775 小时前
SpringBoot(7)-Swagger
java·spring boot·后端
v***91306 小时前
Spring boot创建时常用的依赖
java·spring boot·后端
代码or搬砖9 小时前
MyBatisPlus讲解(二)
java·mybatis
lcu1119 小时前
Java 学习42:抽象
java
Mr.朱鹏9 小时前
RocketMQ安装与部署指南
java·数据库·spring·oracle·maven·rocketmq·seata
雨中飘荡的记忆9 小时前
Spring表达式详解:SpEL从入门到实战
java·spring
Coder-coco9 小时前
个人健康管理|基于springboot+vue+个人健康管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·mysql·论文
5***262210 小时前
Spring Boot问题总结
java·spring boot·后端