最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

基于SpringBoot實(shí)現(xiàn)圖片滑動驗(yàn)證碼功能

 更新時間:2026年02月04日 08:56:46   作者:J_liaty  
本文詳細(xì)講解如何在Spring Boot項(xiàng)目中實(shí)現(xiàn)圖片滑動驗(yàn)證碼功能,從原理闡述到完整代碼實(shí)現(xiàn),幫助開發(fā)者快速掌握這一實(shí)用的安全驗(yàn)證技術(shù),需要的朋友可以參考下

一、功能原理闡述

1.1 滑動驗(yàn)證碼的工作原理

滑動驗(yàn)證碼是一種基于行為驗(yàn)證的人機(jī)識別機(jī)制,其核心思想是通過用戶的交互行為來區(qū)分真人操作與自動化程序。整個驗(yàn)證流程包括以下幾個關(guān)鍵步驟:

圖像生成階段

  • 系統(tǒng)從預(yù)設(shè)圖片庫中隨機(jī)選擇一張背景圖
  • 在背景圖上隨機(jī)選擇一個位置,通過預(yù)定義的mask模板生成缺口形狀
  • 將缺口位置的圖像區(qū)域裁剪出來作為滑塊拼圖
  • 對背景圖的缺口位置進(jìn)行半透明或模糊處理,形成明顯的視覺提示

用戶交互階段

  • 前端展示帶有缺口的背景圖和對應(yīng)的滑塊拼圖
  • 用戶通過鼠標(biāo)拖拽或手指滑動,將滑塊移動到正確位置
  • 系統(tǒng)實(shí)時記錄用戶的滑動軌跡、速度、加速度等行為數(shù)據(jù)

驗(yàn)證判斷階段

  • 用戶釋放滑塊后,前端收集最終的位移坐標(biāo)和完整軌跡數(shù)據(jù)
  • 將數(shù)據(jù)提交到后端進(jìn)行驗(yàn)證
  • 后端對比滑塊位置與實(shí)際缺口位置的匹配度
  • 結(jié)合行為特征(軌跡平滑度、滑動時間、加速度變化等)綜合判斷

1.2 安全價值分析

滑動驗(yàn)證碼相比傳統(tǒng)驗(yàn)證碼具有顯著的安全優(yōu)勢:

防爬蟲能力

  • 傳統(tǒng)字符驗(yàn)證碼容易被OCR技術(shù)自動識別
  • 滑動驗(yàn)證碼要求用戶進(jìn)行復(fù)雜的空間定位操作,增加自動化難度
  • 引入行為軌跡分析,機(jī)器人難以模擬真實(shí)用戶的操作特征

防暴力 破解

  • 每次驗(yàn)證都生成隨機(jī)缺口位置,防止預(yù)先破解
  • 圖像庫多樣化,避免固定模式被機(jī)器學(xué)習(xí)模型識別
  • 設(shè)置合理的容錯范圍,在用戶體驗(yàn)和安全性之間取得平衡

防重放攻擊

  • 驗(yàn)證令牌具有時效性,過期即失效
  • 結(jié)合用戶會話信息,防止驗(yàn)證碼被跨會話使用
  • 前端加密傳輸,防止中間人攻擊

1.3 應(yīng)用場景

滑動驗(yàn)證碼適用于多種安全敏感的業(yè)務(wù)場景:

用戶認(rèn)證場景

  • 用戶登錄:防止惡意暴力 破解密碼
  • 用戶注冊:攔截批量虛假賬號注冊
  • 密碼找回:保護(hù)用戶賬號安全

業(yè)務(wù)操作場景

  • 發(fā)表評論:防止惡意刷 評論、垃圾信息
  • 內(nèi)容發(fā)布:防止自動化內(nèi)容提交
  • 搶購活動:防止機(jī)器人搶購商品
  • 表單提交:保護(hù)重要業(yè)務(wù)表單

數(shù)據(jù)保護(hù)場景

  • 敏感信息查詢:防止惡意數(shù)據(jù)爬取
  • 導(dǎo)出功能:防止批量數(shù)據(jù)導(dǎo)出
  • API接口調(diào)用:保護(hù)接口不被濫用

二、技術(shù)選型說明

2.1 核心技術(shù)棧

基于Java生態(tài)體系,我們選擇以下技術(shù)棧實(shí)現(xiàn)滑動驗(yàn)證碼功能:

技術(shù)組件選型選擇理由
后端框架Spring Boot 2.7+快速開發(fā),生態(tài)完善,配置簡化
圖像處理Java BufferedImage + 自定義算法無需額外依賴,輕量高效
緩存存儲Redis分布式部署支持,高性能
前端技術(shù)HTML5 + Canvas + JavaScript原生實(shí)現(xiàn),兼容性好,無框架依賴
加密算法AES-128對稱加密,性能好,滿足安全需求
數(shù)據(jù)格式JSON標(biāo)準(zhǔn)格式,前后端交互友好

2.2 技術(shù)選型理由詳解

Java BufferedImage

  • JDK自帶,無需引入第三方圖像處理庫
  • 提供豐富的圖像操作API:裁剪、縮放、像素級操作
  • 支持多種圖像格式:PNG、JPEG等
  • 內(nèi)存占用合理,適合Web應(yīng)用場景

Redis緩存

  • 滑動驗(yàn)證碼需要存儲驗(yàn)證狀態(tài)和臨時數(shù)據(jù)
  • Redis提供高性能的key-value存儲
  • 支持過期時間設(shè)置,自動清理過期驗(yàn)證碼
  • 分布式環(huán)境下保證多節(jié)點(diǎn)數(shù)據(jù)一致性

Canvas前端繪圖

  • HTML5原生支持,無需插件
  • 可以動態(tài)繪制背景圖和滑塊
  • 實(shí)現(xiàn)拖拽交互響應(yīng)流暢
  • 移動端和PC端統(tǒng)一實(shí)現(xiàn)

AES加密

  • 滑動坐標(biāo)等敏感信息需要加密傳輸
  • AES算法成熟穩(wěn)定,性能優(yōu)異
  • 防止客戶端篡改驗(yàn)證數(shù)據(jù)
  • Java提供標(biāo)準(zhǔn)加密庫支持

三、實(shí)現(xiàn)步驟詳解

3.1 項(xiàng)目環(huán)境搭建與依賴配置

創(chuàng)建Spring Boot項(xiàng)目
使用Spring Initializr創(chuàng)建新項(xiàng)目,選擇以下依賴:

  • Spring Web
  • Spring Data Redis
  • Lombok(可選,簡化代碼)

配置pom.xml依賴

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.14</version>
        <relativePath/>
    </parent>
    
    <groupId>com.example</groupId>
    <artifactId>slider-captcha</artifactId>
    <version>1.0.0</version>
    <name>slider-captcha</name>
    <description>Spring Boot Slider Captcha</description>
    
    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    
    <dependencies>
        <!-- Spring Boot Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <!-- Spring Data Redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        
        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        
        <!-- JSON處理 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.83</version>
        </dependency>
        
        <!-- Apache Commons -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
        
        <!-- Spring Boot Test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

application.yml配置文件

server:
  port: 8080
  servlet:
    context-path: /captcha

spring:
  # Redis配置
  redis:
    host: localhost
    port: 6379
    password:
    database: 0
    timeout: 3000
    lettuce:
      pool:
        max-active: 8
        max-wait: -1
        max-idle: 8
        min-idle: 0

# 驗(yàn)證碼配置
captcha:
  # 圖片配置
  image:
    width: 320
    height: 160
    slider-width: 60
    slider-height: 60
    tolerance: 5  # 容錯像素
    
  # 過期時間(秒)
  expire-time: 300
  
  # 圖片資源路徑
  resource-path: classpath:images/
  
  # 加密密鑰
  secret-key: slider_captcha_secret_key_123456

3.2 核心算法實(shí)現(xiàn)

3.2.1 圖像處理工具類

創(chuàng)建圖片處理工具類,實(shí)現(xiàn)滑塊驗(yàn)證碼的核心圖像處理功能:

package com.example.slidercaptcha.util;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Random;

/**
 * 滑塊驗(yàn)證碼圖片處理工具類
 */
public class CaptchaImageUtil {
    
    private static final Random RANDOM = new Random();
    
    // 滑塊圓角半徑
    private static final int ARC_RADIUS = 10;
    
    /**
     * 生成驗(yàn)證碼圖片
     * 
     * @param originalImage 原始背景圖
     * @param x 缺口x坐標(biāo)
     * @param y 缺口y坐標(biāo)
     * @param width 滑塊寬度
     * @param height 滑塊高度
     * @return 驗(yàn)證碼結(jié)果
     */
    public static CaptchaResult generateCaptcha(BufferedImage originalImage, 
                                               int x, int y, 
                                               int width, int height) {
        try {
            // 1. 創(chuàng)建帶缺口的背景圖
            BufferedImage backgroundImage = createBackgroundWithHole(
                originalImage, x, y, width, height);
            
            // 2. 創(chuàng)建滑塊圖片
            BufferedImage sliderImage = createSliderImage(
                originalImage, x, y, width, height);
            
            // 3. 轉(zhuǎn)換為Base64
            String backgroundBase64 = imageToBase64(backgroundImage);
            String sliderBase64 = imageToBase64(sliderImage);
            
            return new CaptchaResult(x, y, backgroundBase64, sliderBase64);
            
        } catch (Exception e) {
            throw new RuntimeException("生成驗(yàn)證碼圖片失敗", e);
        }
    }
    
    /**
     * 創(chuàng)建帶缺口的背景圖
     */
    private static BufferedImage createBackgroundWithHole(
            BufferedImage originalImage, int x, int y, int width, int height) {
        
        BufferedImage newImage = new BufferedImage(
            originalImage.getWidth(), 
            originalImage.getHeight(), 
            BufferedImage.TYPE_INT_RGB
        );
        
        Graphics2D g2d = newImage.createGraphics();
        
        // 繪制原圖
        g2d.drawImage(originalImage, 0, 0, null);
        
        // 設(shè)置半透明填充
        g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f));
        g2d.setColor(new Color(0, 0, 0, 100));
        
        // 繪制缺口區(qū)域
        drawSliderShape(g2d, x, y, width, height);
        g2d.fill(new RoundRectangle2D.Double(x, y, width, height, ARC_RADIUS, ARC_RADIUS));
        
        // 繪制缺口邊框
        g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f));
        g2d.setColor(new Color(255, 255, 255, 200));
        g2d.setStroke(new BasicStroke(2f));
        drawSliderShape(g2d, x, y, width, height);
        
        g2d.dispose();
        return newImage;
    }
    
    /**
     * 創(chuàng)建滑塊圖片
     */
    private static BufferedImage createSliderImage(
            BufferedImage originalImage, int x, int y, int width, int height) {
        
        BufferedImage sliderImage = new BufferedImage(
            width, height, BufferedImage.TYPE_INT_ARGB
        );
        
        Graphics2D g2d = sliderImage.createGraphics();
        
        // 啟用抗鋸齒
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
                            RenderingHints.VALUE_ANTIALIAS_ON);
        
        // 裁剪滑塊區(qū)域
        BufferedImage croppedImage = originalImage.getSubimage(x, y, width, height);
        
        // 繪制圓角滑塊
        drawRoundedSlider(g2d, croppedImage, width, height);
        
        g2d.dispose();
        return sliderImage;
    }
    
    /**
     * 繪制圓角滑塊
     */
    private static void drawRoundedSlider(Graphics2D g2d, 
                                          BufferedImage image, 
                                          int width, int height) {
        // 創(chuàng)建圓角形狀
        RoundRectangle2D roundRect = new RoundRectangle2D.Double(
            0, 0, width, height, ARC_RADIUS, ARC_RADIUS
        );
        
        g2d.setClip(roundRect);
        g2d.drawImage(image, 0, 0, null);
        
        // 繪制邊框
        g2d.setColor(new Color(255, 255, 255));
        g2d.setStroke(new BasicStroke(2f));
        g2d.draw(roundRect);
    }
    
    /**
     * 繪制滑塊形狀
     */
    private static void drawSliderShape(Graphics2D g2d, 
                                        int x, int y, 
                                        int width, int height) {
        RoundRectangle2D shape = new RoundRectangle2D.Double(
            x, y, width, height, ARC_RADIUS, ARC_RADIUS
        );
        g2d.draw(shape);
    }
    
    /**
     * 圖片轉(zhuǎn)Base64
     */
    private static String imageToBase64(BufferedImage image) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ImageIO.write(image, "png", baos);
        byte[] imageBytes = baos.toByteArray();
        return "data:image/png;base64," + 
               java.util.Base64.getEncoder().encodeToString(imageBytes);
    }
    
    /**
     * 從資源目錄加載圖片
     */
    public static BufferedImage loadImageFromClasspath(String path) throws IOException {
        InputStream inputStream = CaptchaImageUtil.class
            .getClassLoader()
            .getResourceAsStream(path);
        
        if (inputStream == null) {
            throw new IOException("無法加載圖片: " + path);
        }
        
        return ImageIO.read(inputStream);
    }
    
    /**
     * 驗(yàn)證碼結(jié)果類
     */
    public static class CaptchaResult {
        private final int x;  // 缺口x坐標(biāo)
        private final int y;  // 缺口y坐標(biāo)
        private final String backgroundImage;  // 背景圖Base64
        private final String sliderImage;  // 滑塊圖Base64
        
        public CaptchaResult(int x, int y, String backgroundImage, String sliderImage) {
            this.x = x;
            this.y = y;
            this.backgroundImage = backgroundImage;
            this.sliderImage = sliderImage;
        }
        
        public int getX() { return x; }
        public int getY() { return y; }
        public String getBackgroundImage() { return backgroundImage; }
        public String getSliderImage() { return sliderImage; }
    }
}

3.2.2 加密工具類

package com.example.slidercaptcha.util;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

/**
 * AES加密工具類
 */
public class AESUtil {
    
    private static final String ALGORITHM = "AES";
    private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
    
    /**
     * 加密
     */
    public static String encrypt(String content, String key) throws Exception {
        // 處理密鑰,確保16位
        byte[] keyBytes = padKey(key.getBytes(StandardCharsets.UTF_8));
        
        SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, ALGORITHM);
        IvParameterSpec ivParameterSpec = new IvParameterSpec(keyBytes);
        
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
        
        byte[] encrypted = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(encrypted);
    }
    
    /**
     * 解密
     */
    public static String decrypt(String encrypted, String key) throws Exception {
        // 處理密鑰,確保16位
        byte[] keyBytes = padKey(key.getBytes(StandardCharsets.UTF_8));
        
        SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, ALGORITHM);
        IvParameterSpec ivParameterSpec = new IvParameterSpec(keyBytes);
        
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
        
        byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(encrypted));
        return new String(decrypted, StandardCharsets.UTF_8);
    }
    
    /**
     * 填充密鑰到16位
     */
    private static byte[] padKey(byte[] key) {
        byte[] result = new byte[16];
        System.arraycopy(key, 0, result, 0, Math.min(key.length, 16));
        return result;
    }
}

3.2.3 驗(yàn)證碼服務(wù)類

package com.example.slidercaptcha.service;

import com.example.slidercaptcha.config.CaptchaProperties;
import com.example.slidercaptcha.util.AESUtil;
import com.example.slidercaptcha.util.CaptchaImageUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * 驗(yàn)證碼服務(wù)類
 */
@Slf4j
@Service
public class CaptchaService {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    @Autowired
    private CaptchaProperties captchaProperties;
    
    private static final Random RANDOM = new Random();
    private static final String CAPTCHA_PREFIX = "captcha:";
    private static final String VERIFY_PREFIX = "verify:";
    
    /**
     * 生成驗(yàn)證碼
     */
    public CaptchaResponse generate() {
        try {
            // 1. 生成驗(yàn)證碼token
            String token = UUID.randomUUID().toString().replace("-", "");
            
            // 2. 加載隨機(jī)背景圖
            BufferedImage originalImage = loadRandomBackgroundImage();
            
            // 3. 隨機(jī)生成滑塊位置
            int width = captchaProperties.getImage().getWidth();
            int height = captchaProperties.getImage().getHeight();
            int sliderWidth = captchaProperties.getImage().getSliderWidth();
            int sliderHeight = captchaProperties.getImage().getSliderHeight();
            
            // x坐標(biāo)范圍:[sliderWidth, width - sliderWidth]
            int maxX = width - sliderWidth - captchaProperties.getImage().getTolerance();
            int minX = sliderWidth + captchaProperties.getImage().getTolerance();
            int x = RANDOM.nextInt(maxX - minX) + minX;
            
            // y坐標(biāo)范圍:[0, height - sliderHeight]
            int maxY = height - sliderHeight;
            int y = RANDOM.nextInt(maxY);
            
            // 4. 生成驗(yàn)證碼圖片
            CaptchaImageUtil.CaptchaResult captchaResult = 
                CaptchaImageUtil.generateCaptcha(
                    originalImage, x, y, sliderWidth, sliderHeight
                );
            
            // 5. 加密滑塊位置信息
            String positionData = x + "," + y;
            String encryptedData = AESUtil.encrypt(positionData, 
                                                   captchaProperties.getSecretKey());
            
            // 6. 存儲驗(yàn)證信息到Redis
            String captchaKey = CAPTCHA_PREFIX + token;
            redisTemplate.opsForValue().set(
                captchaKey, 
                encryptedData, 
                captchaProperties.getExpireTime(), 
                TimeUnit.SECONDS
            );
            
            // 7. 返回結(jié)果
            return CaptchaResponse.builder()
                .token(token)
                .backgroundImage(captchaResult.getBackgroundImage())
                .sliderImage(captchaResult.getSliderImage())
                .width(width)
                .height(height)
                .sliderWidth(sliderWidth)
                .sliderHeight(sliderHeight)
                .build();
                
        } catch (Exception e) {
            log.error("生成驗(yàn)證碼失敗", e);
            throw new RuntimeException("生成驗(yàn)證碼失敗", e);
        }
    }
    
    /**
     * 驗(yàn)證滑塊位置
     */
    public boolean verify(String token, int moveX, int moveY) {
        try {
            // 1. 從Redis獲取驗(yàn)證信息
            String captchaKey = CAPTCHA_PREFIX + token;
            String encryptedData = redisTemplate.opsForValue().get(captchaKey);
            
            if (encryptedData == null) {
                log.warn("驗(yàn)證碼已過期或不存在: {}", token);
                return false;
            }
            
            // 2. 解密獲取正確位置
            String positionData = AESUtil.decrypt(encryptedData, 
                                                  captchaProperties.getSecretKey());
            String[] positions = positionData.split(",");
            int correctX = Integer.parseInt(positions[0]);
            int correctY = Integer.parseInt(positions[1]);
            
            // 3. 驗(yàn)證位置誤差
            int tolerance = captchaProperties.getImage().getTolerance();
            boolean xValid = Math.abs(moveX - correctX) <= tolerance;
            boolean yValid = Math.abs(moveY - correctY) <= tolerance;
            
            // 4. 刪除驗(yàn)證碼(一次性使用)
            redisTemplate.delete(captchaKey);
            
            if (xValid && yValid) {
                // 5. 生成驗(yàn)證通過令牌
                String verifyToken = UUID.randomUUID().toString().replace("-", "");
                String verifyKey = VERIFY_PREFIX + verifyToken;
                redisTemplate.opsForValue().set(
                    verifyKey, 
                    "verified", 
                    300, 
                    TimeUnit.SECONDS
                );
                
                log.info("驗(yàn)證碼驗(yàn)證成功: token={}, verifyToken={}", token, verifyToken);
                return true;
            } else {
                log.warn("驗(yàn)證碼驗(yàn)證失敗: token={}, moveX={}, moveY={}, correctX={}, correctY={}", 
                        token, moveX, moveY, correctX, correctY);
                return false;
            }
            
        } catch (Exception e) {
            log.error("驗(yàn)證碼驗(yàn)證異常", e);
            return false;
        }
    }
    
    /**
     * 二次校驗(yàn)(用于業(yè)務(wù)接口驗(yàn)證)
     */
    public boolean verifyToken(String verifyToken) {
        try {
            String verifyKey = VERIFY_PREFIX + verifyToken;
            String value = redisTemplate.opsForValue().get(verifyKey);
            
            if (value != null) {
                // 刪除令牌(一次性使用)
                redisTemplate.delete(verifyKey);
                return true;
            }
            return false;
            
        } catch (Exception e) {
            log.error("驗(yàn)證令牌校驗(yàn)異常", e);
            return false;
        }
    }
    
    /**
     * 加載隨機(jī)背景圖
     */
    private BufferedImage loadRandomBackgroundImage() throws Exception {
        String resourcePath = captchaProperties.getResourcePath();
        String[] imageFiles = {
            "bg1.jpg", "bg2.jpg", "bg3.jpg", 
            "bg4.jpg", "bg5.jpg"
        };
        
        int index = RANDOM.nextInt(imageFiles.length);
        String imagePath = resourcePath + imageFiles[index];
        
        return CaptchaImageUtil.loadImageFromClasspath(imagePath);
    }
}

3.3 前后端交互設(shè)計(jì)

3.3.1 Controller層實(shí)現(xiàn)

package com.example.slidercaptcha.controller;

import com.example.slidercaptcha.service.CaptchaService;
import com.example.slidercaptcha.vo.CaptchaRequest;
import com.example.slidercaptcha.vo.CaptchaResponse;
import com.example.slidercaptcha.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * 驗(yàn)證碼控制器
 */
@Slf4j
@RestController
@RequestMapping("/api")
@CrossOrigin(origins = "*")
public class CaptchaController {
    
    @Autowired
    private CaptchaService captchaService;
    
    /**
     * 生成驗(yàn)證碼
     */
    @GetMapping("/captcha/generate")
    public Result<CaptchaResponse> generate() {
        try {
            CaptchaResponse response = captchaService.generate();
            return Result.success(response);
        } catch (Exception e) {
            log.error("生成驗(yàn)證碼失敗", e);
            return Result.error("生成驗(yàn)證碼失敗");
        }
    }
    
    /**
     * 驗(yàn)證滑塊位置
     */
    @PostMapping("/captcha/verify")
    public Result<String> verify(@RequestBody CaptchaRequest request) {
        try {
            boolean success = captchaService.verify(
                request.getToken(), 
                request.getMoveX(), 
                request.getMoveY()
            );
            
            if (success) {
                // 返回驗(yàn)證通過令牌
                String verifyToken = java.util.UUID.randomUUID()
                    .toString().replace("-", "");
                return Result.success(verifyToken, "驗(yàn)證成功");
            } else {
                return Result.error("驗(yàn)證失敗");
            }
            
        } catch (Exception e) {
            log.error("驗(yàn)證碼校驗(yàn)失敗", e);
            return Result.error("驗(yàn)證失敗");
        }
    }
    
    /**
     * 二次校驗(yàn)接口(業(yè)務(wù)接口調(diào)用)
     */
    @PostMapping("/captcha/check")
    public Result<Void> check(@RequestParam String verifyToken) {
        try {
            boolean success = captchaService.verifyToken(verifyToken);
            
            if (success) {
                return Result.success(null, "校驗(yàn)通過");
            } else {
                return Result.error("校驗(yàn)失敗");
            }
            
        } catch (Exception e) {
            log.error("二次校驗(yàn)失敗", e);
            return Result.error("校驗(yàn)失敗");
        }
    }
}

3.3.2 VO類定義

package com.example.slidercaptcha.vo;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 驗(yàn)證碼響應(yīng)
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CaptchaResponse {
    private String token;           // 驗(yàn)證碼令牌
    private String backgroundImage; // 背景圖Base64
    private String sliderImage;     // 滑塊圖Base64
    private int width;             // 背景圖寬度
    private int height;            // 背景圖高度
    private int sliderWidth;        // 滑塊寬度
    private int sliderHeight;       // 滑塊高度
}

/**
 * 驗(yàn)證碼請求
 */
@Data
public class CaptchaRequest {
    private String token;  // 驗(yàn)證碼令牌
    private int moveX;     // 滑塊X軸移動距離
    private int moveY;     // 滑塊Y軸移動距離
    private String behavior; // 行為軌跡(可選)
}

/**
 * 通用結(jié)果
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result<T> {
    private int code;
    private String message;
    private T data;
    
    public static <T> Result<T> success(T data) {
        return new Result<>(200, "success", data);
    }
    
    public static <T> Result<T> success(T data, String message) {
        return new Result<>(200, message, data);
    }
    
    public static <T> Result<T> error(String message) {
        return new Result<>(500, message, null);
    }
}

3.3.3 配置屬性類

package com.example.slidercaptcha.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * 驗(yàn)證碼配置屬性
 */
@Data
@Component
@ConfigurationProperties(prefix = "captcha")
public class CaptchaProperties {
    
    private ImageConfig image = new ImageConfig();
    private int expireTime = 300;
    private String resourcePath = "classpath:images/";
    private String secretKey = "slider_captcha_secret_key_123456";
    
    @Data
    public static class ImageConfig {
        private int width = 320;
        private int height = 160;
        private int sliderWidth = 60;
        private int sliderHeight = 60;
        private int tolerance = 5;
    }
}

3.4 前端頁面實(shí)現(xiàn)

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>滑動驗(yàn)證碼</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        
        body {
            font-family: Arial, sans-serif;
            background: #f5f5f5;
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
        }
        
        .captcha-container {
            background: white;
            padding: 30px;
            border-radius: 10px;
            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
        }
        
        .captcha-title {
            text-align: center;
            margin-bottom: 20px;
            color: #333;
            font-size: 18px;
        }
        
        .image-wrapper {
            position: relative;
            width: 320px;
            height: 160px;
            border: 2px solid #e0e0e0;
            border-radius: 4px;
            overflow: hidden;
            background: #f0f0f0;
        }
        
        .background-image {
            width: 100%;
            height: 100%;
            object-fit: cover;
        }
        
        .slider-image {
            position: absolute;
            width: 60px;
            height: 60px;
            top: 0;
            left: 0;
            cursor: grab;
            user-select: none;
        }
        
        .slider-image:active {
            cursor: grabbing;
        }
        
        .slider-track {
            width: 320px;
            height: 40px;
            background: #f0f0f0;
            border: 1px solid #e0e0e0;
            border-radius: 4px;
            margin-top: 10px;
            position: relative;
        }
        
        .slider-btn {
            width: 50px;
            height: 38px;
            background: linear-gradient(90deg, #4CAF50, #45a049);
            border-radius: 3px;
            position: absolute;
            left: 0;
            top: 0;
            cursor: grab;
            display: flex;
            justify-content: center;
            align-items: center;
            color: white;
            font-size: 20px;
            user-select: none;
        }
        
        .slider-btn:active {
            cursor: grabbing;
        }
        
        .slider-text {
            text-align: center;
            line-height: 40px;
            color: #999;
            font-size: 14px;
            pointer-events: none;
        }
        
        .refresh-btn {
            width: 100%;
            padding: 10px;
            margin-top: 10px;
            background: #2196F3;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 14px;
        }
        
        .refresh-btn:hover {
            background: #0b7dda;
        }
        
        .result-message {
            text-align: center;
            margin-top: 10px;
            font-size: 14px;
            padding: 8px;
            border-radius: 4px;
            display: none;
        }
        
        .success {
            background: #dff0d8;
            color: #3c763d;
            display: block !important;
        }
        
        .error {
            background: #f2dede;
            color: #a94442;
            display: block !important;
        }
        
        .loading {
            text-align: center;
            padding: 20px;
            color: #999;
        }
    </style>
</head>
<body>
    <div class="captcha-container">
        <h2 class="captcha-title">滑動驗(yàn)證碼</h2>
        
        <div id="captcha-content">
            <div class="image-wrapper">
                <img id="background-image" class="background-image" src="" alt="背景圖">
                <img id="slider-image" class="slider-image" src="" alt="滑塊">
            </div>
            
            <div class="slider-track">
                <div id="slider-btn" class="slider-btn">→</div>
                <div class="slider-text">拖動滑塊完成驗(yàn)證</div>
            </div>
            
            <button class="refresh-btn" onclick="loadCaptcha()">刷新驗(yàn)證碼</button>
            
            <div id="result-message" class="result-message"></div>
        </div>
        
        <div id="loading" class="loading" style="display: none;">
            加載中...
        </div>
    </div>

    <script>
        let currentToken = null;
        let sliderPosition = 0;
        let isDragging = false;
        let startX = 0;
        
        // 頁面加載時初始化
        document.addEventListener('DOMContentLoaded', function() {
            loadCaptcha();
            initSliderEvents();
        });
        
        // 加載驗(yàn)證碼
        async function loadCaptcha() {
            showLoading(true);
            
            try {
                const response = await fetch('/captcha/api/captcha/generate');
                const result = await response.json();
                
                if (result.code === 200) {
                    const data = result.data;
                    currentToken = data.token;
                    
                    document.getElementById('background-image').src = data.backgroundImage;
                    document.getElementById('slider-image').src = data.sliderImage;
                    document.getElementById('slider-image').style.top = '0px';
                    document.getElementById('slider-image').style.left = '0px';
                    sliderPosition = 0;
                    
                    hideMessage();
                } else {
                    showMessage('加載驗(yàn)證碼失敗', 'error');
                }
            } catch (error) {
                console.error('加載驗(yàn)證碼失敗:', error);
                showMessage('網(wǎng)絡(luò)錯誤', 'error');
            }
            
            showLoading(false);
        }
        
        // 初始化滑塊事件
        function initSliderEvents() {
            const sliderBtn = document.getElementById('slider-btn');
            
            // 鼠標(biāo)事件
            sliderBtn.addEventListener('mousedown', startDrag);
            document.addEventListener('mousemove', drag);
            document.addEventListener('mouseup', endDrag);
            
            // 觸摸事件
            sliderBtn.addEventListener('touchstart', startDragTouch);
            document.addEventListener('touchmove', dragTouch);
            document.addEventListener('touchend', endDrag);
        }
        
        // 開始拖拽(鼠標(biāo))
        function startDrag(e) {
            isDragging = true;
            startX = e.clientX;
            e.preventDefault();
        }
        
        // 開始拖拽(觸摸)
        function startDragTouch(e) {
            isDragging = true;
            startX = e.touches[0].clientX;
            e.preventDefault();
        }
        
        // 拖拽過程(鼠標(biāo))
        function drag(e) {
            if (!isDragging) return;
            
            const deltaX = e.clientX - startX;
            const maxMove = 320 - 60; // 容器寬度 - 滑塊寬度
            sliderPosition = Math.max(0, Math.min(maxMove, deltaX));
            
            updateSliderPosition();
        }
        
        // 拖拽過程(觸摸)
        function dragTouch(e) {
            if (!isDragging) return;
            
            const deltaX = e.touches[0].clientX - startX;
            const maxMove = 320 - 60;
            sliderPosition = Math.max(0, Math.min(maxMove, deltaX));
            
            updateSliderPosition();
            e.preventDefault();
        }
        
        // 更新滑塊位置
        function updateSliderPosition() {
            document.getElementById('slider-btn').style.left = sliderPosition + 'px';
            document.getElementById('slider-image').style.left = sliderPosition + 'px';
        }
        
        // 結(jié)束拖拽
        function endDrag() {
            if (!isDragging) return;
            isDragging = false;
            verifyCaptcha();
        }
        
        // 驗(yàn)證驗(yàn)證碼
        async function verifyCaptcha() {
            if (!currentToken) {
                showMessage('驗(yàn)證碼已過期', 'error');
                return;
            }
            
            try {
                const response = await fetch('/captcha/api/captcha/verify', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify({
                        token: currentToken,
                        moveX: Math.round(sliderPosition),
                        moveY: 0
                    })
                });
                
                const result = await response.json();
                
                if (result.code === 200) {
                    showMessage('驗(yàn)證成功!', 'success');
                    console.log('驗(yàn)證令牌:', result.data);
                } else {
                    showMessage('驗(yàn)證失敗,請重試', 'error');
                    setTimeout(loadCaptcha, 1500);
                }
            } catch (error) {
                console.error('驗(yàn)證失敗:', error);
                showMessage('網(wǎng)絡(luò)錯誤', 'error');
            }
        }
        
        // 顯示消息
        function showMessage(message, type) {
            const msgEl = document.getElementById('result-message');
            msgEl.textContent = message;
            msgEl.className = 'result-message ' + type;
        }
        
        // 隱藏消息
        function hideMessage() {
            document.getElementById('result-message').style.display = 'none';
        }
        
        // 顯示/隱藏加載狀態(tài)
        function showLoading(show) {
            document.getElementById('captcha-content').style.display = show ? 'none' : 'block';
            document.getElementById('loading').style.display = show ? 'block' : 'none';
        }
    </script>
</body>
</html>

四、完整代碼展示

4.1 后端核心代碼

4.1.1 主啟動類

package com.example.slidercaptcha;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SliderCaptchaApplication {
    public static void main(String[] args) {
        SpringApplication.run(SliderCaptchaApplication.class, args);
    }
}

4.1.2 Redis配置類

package com.example.slidercaptcha.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * Redis配置類
 */
@Configuration
public class RedisConfig {
    
    @Bean
    public RedisTemplate<String, Object> redisTemplate(
            RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        
        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
        
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.activateDefaultTyping(
            LaissezFaireSubTypeValidator.instance,
            ObjectMapper.DefaultTyping.NON_FINAL
        );
        
        serializer.setObjectMapper(mapper);
        
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);
        
        template.afterPropertiesSet();
        return template;
    }
}

4.2 配置文件

完整的application.yml配置:

server:
  port: 8080
  servlet:
    context-path: /captcha

spring:
  application:
    name: slider-captcha
  
  # Redis配置
  redis:
    host: localhost
    port: 6379
    password:
    database: 0
    timeout: 3000
    lettuce:
      pool:
        max-active: 8
        max-wait: -1
        max-idle: 8
        min-idle: 0

# 驗(yàn)證碼配置
captcha:
  # 圖片配置
  image:
    width: 320
    height: 160
    slider-width: 60
    slider-height: 60
    tolerance: 5
  
  # 過期時間(秒)
  expire-time: 300
  
  # 圖片資源路徑
  resource-path: classpath:images/
  
  # 加密密鑰
  secret-key: slider_captcha_secret_key_123456

# 日志配置
logging:
  level:
    com.example.slidercaptcha: debug
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"

4.3 依賴配置

完整的pom.xml文件已在3.1節(jié)提供,此處補(bǔ)充主要依賴版本信息:

<properties>
    <java.version>1.8</java.version>
    <spring-boot.version>2.7.14</spring-boot.version>
    <fastjson.version>1.2.83</fastjson.version>
    <lombok.version>1.18.26</lombok.version>
</properties>

五、效果演示與使用說明

5.1 最終實(shí)現(xiàn)效果

完成上述實(shí)現(xiàn)后,滑動驗(yàn)證碼功能將具備以下特性:

視覺效果

  • 背景圖:320x160像素的高質(zhì)量圖片
  • 滑塊圖:60x60像素的圓角矩形拼圖
  • 缺口效果:半透明黑色遮罩,白色邊框高亮
  • 滑動條:綠色漸變按鈕,拖動流暢

交互體驗(yàn)

  • 鼠標(biāo)拖拽:支持PC端鼠標(biāo)操作
  • 觸屏滑動:支持移動端觸摸操作
  • 實(shí)時反饋:滑塊跟隨鼠標(biāo)實(shí)時移動
  • 驗(yàn)證結(jié)果:成功/失敗即時提示
  • 自動刷新:驗(yàn)證失敗后自動刷新

5.2 使用指南

5.2.1 項(xiàng)目啟動步驟

環(huán)境準(zhǔn)備

  • JDK 1.8+
  • Maven 3.6+
  • Redis服務(wù)器

創(chuàng)建圖片資源
src/main/resources/images/目錄下準(zhǔn)備背景圖片:

src/main/resources/
└── images/
    ├── bg1.jpg
    ├── bg2.jpg
    ├── bg3.jpg
    ├── bg4.jpg
    └── bg5.jpg

圖片要求:

  • 尺寸:建議320x160像素
  • 格式:JPG/PNG
  • 內(nèi)容:風(fēng)景、建筑等清晰圖片

啟動Redis

# Linux/Mac
redis-server

# Windows
redis-server.exe

啟動項(xiàng)目

# Maven方式
mvn spring-boot:run

# 或打包后運(yùn)行
mvn clean package
java -jar target/slider-captcha-1.0.0.jar

訪問驗(yàn)證碼頁面
打開瀏覽器訪問:http://localhost:8080/captcha/index.html

5.2.2 API接口說明

1. 生成驗(yàn)證碼接口

GET /captcha/api/captcha/generate

Response:
{
  "code": 200,
  "message": "success",
  "data": {
    "token": "abc123...",
    "backgroundImage": "data:image/png;base64,iVBORw0KG...",
    "sliderImage": "data:image/png;base64,iVBORw0KG...",
    "width": 320,
    "height": 160,
    "sliderWidth": 60,
    "sliderHeight": 60
  }
}

2. 驗(yàn)證滑塊接口

POST /captcha/api/captcha/verify
Content-Type: application/json

Request:
{
  "token": "abc123...",
  "moveX": 150,
  "moveY": 0
}

Response:
{
  "code": 200,
  "message": "驗(yàn)證成功",
  "data": "verify_token_xyz..."
}

3. 二次校驗(yàn)接口

POST /captcha/api/captcha/check?verifyToken=verify_token_xyz...

Response:
{
  "code": 200,
  "message": "校驗(yàn)通過",
  "data": null
}

5.2.3 業(yè)務(wù)集成示例

在業(yè)務(wù)接口中集成驗(yàn)證碼驗(yàn)證:

@PostMapping("/login")
public Result<LoginResponse> login(@RequestBody LoginRequest request) {
    // 1. 驗(yàn)證驗(yàn)證碼
    boolean verified = captchaService.verifyToken(request.getVerifyToken());
    if (!verified) {
        return Result.error("驗(yàn)證碼校驗(yàn)失敗");
    }
    
    // 2. 執(zhí)行登錄邏輯
    User user = userService.authenticate(request.getUsername(), 
                                          request.getPassword());
    if (user == null) {
        return Result.error("用戶名或密碼錯誤");
    }
    
    // 3. 生成登錄令牌
    String token = generateLoginToken(user);
    
    return Result.success(new LoginResponse(token, user));
}

5.3 注意事項(xiàng)

安全性注意事項(xiàng)

  1. 驗(yàn)證碼一次性使用
    • 驗(yàn)證成功后立即刪除Redis中的驗(yàn)證信息
    • 防止同一驗(yàn)證碼被多次使用
  2. 加密傳輸
    • 滑塊坐標(biāo)等重要信息使用AES加密
    • 防止客戶端篡改驗(yàn)證數(shù)據(jù)
  3. 防止暴力 破解
    • 設(shè)置合理的容錯范圍(5像素)
    • 限制單個IP的驗(yàn)證頻率
    • 驗(yàn)證失敗次數(shù)過多時鎖定
  4. 分布式部署
    • 使用Redis存儲驗(yàn)證信息
    • 多個應(yīng)用實(shí)例共享驗(yàn)證狀態(tài)
    • 確保分布式環(huán)境下的一致性

性能優(yōu)化建議

  • 圖片緩存
    • 背景圖片預(yù)加載到內(nèi)存
    • 減少圖片IO操作
  • Redis優(yōu)化
    • 使用連接池管理連接
    • 設(shè)置合理的過期時間
    • 定期清理過期數(shù)據(jù)
  • 并發(fā)控制
    • 限制單個用戶同時生成的驗(yàn)證碼數(shù)量
    • 防止惡意請求占用系統(tǒng)資源

兼容性處理

  1. 移動端適配
    • 支持Touch事件
    • 調(diào)整滑塊大小以適應(yīng)手指操作
    • 優(yōu)化觸摸響應(yīng)速度
  2. 瀏覽器兼容
    • 使用標(biāo)準(zhǔn)CSS屬性
    • 避免使用實(shí)驗(yàn)性API
    • 提供降級方案
  3. 網(wǎng)絡(luò)優(yōu)化
    • 圖片壓縮傳輸
    • 使用CDN加速圖片加載
    • 提供離線降級方案

六、總結(jié)

本文詳細(xì)介紹了Spring Boot整合動態(tài)圖片滑動驗(yàn)證碼的完整實(shí)現(xiàn)方案,從原理闡述、技術(shù)選型、代碼實(shí)現(xiàn)到使用說明,涵蓋了開發(fā)過程中的各個環(huán)節(jié)。

通過本方案,開發(fā)者可以快速在自己的項(xiàng)目中集成滑動驗(yàn)證碼功能,提升系統(tǒng)的安全性和用戶體驗(yàn)。該方案具有以下優(yōu)勢:

  • 完整性強(qiáng):從后端到前端,提供完整的代碼實(shí)現(xiàn)
  • 易于擴(kuò)展:模塊化設(shè)計(jì),方便二次開發(fā)和功能擴(kuò)展
  • 安全可靠:采用多種安全機(jī)制,有效防止自動化攻擊
  • 性能優(yōu)良:基于Redis緩存,支持高并發(fā)場景

希望本文能幫助開發(fā)者更好地理解和實(shí)現(xiàn)滑動驗(yàn)證碼功能,在實(shí)際項(xiàng)目中發(fā)揮價值。

以上就是基于SpringBoot實(shí)現(xiàn)圖片滑動驗(yàn)證碼功能的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot圖片滑動驗(yàn)證碼的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Spring Boot框架中的@Conditional注解示例詳解

    Spring Boot框架中的@Conditional注解示例詳解

    這篇文章主要介紹了Spring Boot框架中的@Conditional系列注解,@ConditionalOnProperty注解的作用是解析application.yml/application.properties 里的配置生成條件來生效,也是與@Configuration注解一起使用,本文通過示例代碼給大家介紹的非常詳細(xì),需要的朋友一起看看吧
    2022-09-09
  • Mybatisplus實(shí)現(xiàn)JSON處理器的示例代碼

    Mybatisplus實(shí)現(xiàn)JSON處理器的示例代碼

    Mybatisplusjson是基于Mybatisplus開發(fā)的一個json工具庫,本文主要介紹了Mybatisplus實(shí)現(xiàn)JSON處理器的示例代碼,具有一定的參考價值,感興趣的可以了解一下
    2024-03-03
  • Springboot接口項(xiàng)目如何使用AOP記錄日志

    Springboot接口項(xiàng)目如何使用AOP記錄日志

    這篇文章主要介紹了Springboot接口項(xiàng)目如何使用AOP記錄日志,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-06-06
  • 淺談java object對象在heap中的結(jié)構(gòu)

    淺談java object對象在heap中的結(jié)構(gòu)

    本文主要介紹了淺談java object對象在heap中的結(jié)構(gòu),感興趣的同學(xué),可以參考下。
    2021-06-06
  • Java順序表實(shí)現(xiàn)的撲克牌小游戲(附詳細(xì)代碼)

    Java順序表實(shí)現(xiàn)的撲克牌小游戲(附詳細(xì)代碼)

    在撲克牌游戲中,我們有時需要判斷一手牌是否是一個順子,也就是判斷5張牌是否連續(xù),這篇文章主要介紹了Java順序表實(shí)現(xiàn)的撲克牌小游戲的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2025-10-10
  • Spring基本認(rèn)識和入門使用超詳細(xì)教程

    Spring基本認(rèn)識和入門使用超詳細(xì)教程

    Spring是一款主流的Java EE 輕量級開源框架,目的是用于簡化Java企業(yè)級引用的開發(fā)難度和開發(fā)周期,本文給大家介紹Spring基本認(rèn)識和入門使用超詳細(xì)教程,感興趣的朋友一起看看吧
    2025-11-11
  • 最新評論

    高雄县| 清水县| 阳高县| 康平县| 历史| 洪雅县| 鹰潭市| 巴马| 宜春市| 达州市| 平山县| 徐闻县| 马公市| 静宁县| 成安县| 共和县| 同江市| 闵行区| 东源县| 澄城县| 乡宁县| 长宁区| 开平市| 巴塘县| 龙山县| 始兴县| 紫金县| 南溪县| 砀山县| 宣恩县| 确山县| 嘉鱼县| 金秀| 潜江市| 永和县| 宜宾县| 永修县| 逊克县| 尚义县| 改则县| 南江县|