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

springboot自定義注解RateLimiter限流注解技術(shù)文檔詳解

 更新時(shí)間:2025年07月25日 15:44:14   作者:yzhSWJ  
文章介紹了限流技術(shù)的概念、作用及實(shí)現(xiàn)方式,通過(guò)Spring AOP攔截方法、緩存存儲(chǔ)計(jì)數(shù)器,結(jié)合注解、枚舉、異常類(lèi)等核心組件,實(shí)現(xiàn)對(duì)訪問(wèn)頻率的控制,旨在防止攻擊、保障系統(tǒng)穩(wěn)定、提升用戶體驗(yàn)及節(jié)約成本

什么是限流

限流是一種控制系統(tǒng)訪問(wèn)頻率的技術(shù)手段,就像高速公路的收費(fèi)站控制車(chē)流量一樣。

生活場(chǎng)景類(lèi)比:

  • 銀行ATM機(jī):每張卡每天最多取款5次
  • 手機(jī)驗(yàn)證碼:每個(gè)手機(jī)號(hào)每分鐘最多發(fā)送1條
  • 網(wǎng)站登錄:每個(gè)IP每分鐘最多嘗試5次

技術(shù)價(jià)值:

  1. 防止惡意攻擊:阻止暴力破解、惡意爬蟲(chóng)
  2. 保護(hù)系統(tǒng)穩(wěn)定:避免瞬間大量請(qǐng)求壓垮服務(wù)器
  3. 提升用戶體驗(yàn):確保正常用戶的訪問(wèn)質(zhì)量
  4. 節(jié)約成本:減少不必要的資源消耗

系統(tǒng)架構(gòu)

┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   用戶請(qǐng)求      │───→│   限流切面      │───→│   業(yè)務(wù)接口      │
│   (HTTP API)   │    │  (AOP攔截)     │    │  (Controller)   │
└─────────────────┘    └─────────────────┘    └─────────────────┘
                              │
                              ▼
                    ┌─────────────────┐
                    │   限流服務(wù)      │
                    │ (核心邏輯處理)  │
                    └─────────────────┘
                              │
                              ▼
                    ┌─────────────────┐
                    │   緩存存儲(chǔ)      │
                    │ (EhCache/Redis) │
                    └─────────────────┘

工作流程:

  1. 用戶發(fā)起HTTP請(qǐng)求
  2. Spring AOP切面攔截帶有@RateLimiter注解的方法
  3. 限流服務(wù)根據(jù)注解配置生成限流鍵
  4. 從緩存中獲取當(dāng)前訪問(wèn)次數(shù)
  5. 判斷是否超過(guò)限制,決定放行或拒絕
  6. 更新緩存中的計(jì)數(shù)器

核心組件詳解

1. 限流注解 (@RateLimiter)

這是系統(tǒng)的核心注解,定義了限流的各種參數(shù):

package cn.jbolt.config.anno.rateLimiter;

import org.springframework.core.annotation.AliasFor;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface RateLimiter {

    /**
     * 緩存前綴 - 用于區(qū)分不同業(yè)務(wù)的限流數(shù)據(jù)
     */
    String prefix() default "jblimit:";

    /**
     * 時(shí)間窗口(秒) - 限流的時(shí)間范圍
     */
    int time() default 60;

    /**
     * 允許訪問(wèn)次數(shù) - 時(shí)間窗口內(nèi)最大訪問(wèn)次數(shù)
     */
    @AliasFor(attribute = "count")
    int value() default 12;

    /**
     * 限制類(lèi)型 - 決定按什么維度限流
     */
    RateLimitType limitType() default RateLimitType.DEFAULT;

    /**
     * 限制提示消息 - 觸發(fā)限流時(shí)返回的錯(cuò)誤信息
     */
    String msg() default "操作過(guò)于頻繁,請(qǐng)稍后重試";

    /**
     * 允許訪問(wèn)次數(shù) - 與value互為別名
     */
    @AliasFor(attribute = "value")
    int count() default 12;

    /**
     * 自定義鍵 - 當(dāng)limitType為CUSTOM時(shí)使用
     */
    String customKey() default "";

    /**
     * 是否啟用 - 可用于動(dòng)態(tài)開(kāi)關(guān)限流功能
     */
    boolean enabled() default true;

    /**
     * 額外的時(shí)間窗口限制(秒)
     * 實(shí)現(xiàn)雙重限流:比如1秒最多1次 + 1分鐘最多10次
     */
    int extraTime() default -1;

    /**
     * 額外時(shí)間窗口內(nèi)的允許訪問(wèn)次數(shù)
     */
    int extraCount() default -1;

    /**
     * 額外限制的提示消息
     */
    String extraMsg() default "";
}

2. 限流類(lèi)型枚舉 (RateLimitType)

package cn.jbolt.config.anno.rateLimiter;

public enum RateLimitType {
    /**
     * 默認(rèn)限制(全局)
     * 所有請(qǐng)求共享一個(gè)計(jì)數(shù)器
     */
    DEFAULT,
    
    /**
     * 基于IP地址限制
     * 每個(gè)IP獨(dú)立計(jì)數(shù)
     */
    IP,
    
    /**
     * 基于用戶ID限制
     * 每個(gè)登錄用戶獨(dú)立計(jì)數(shù)
     */
    USER,
    
    /**
     * 基于自定義KEY限制
     * 根據(jù)業(yè)務(wù)邏輯自定義限流維度
     */
    CUSTOM
}

3. 限流異常類(lèi) (RateLimitException)

package cn.jbolt.config.exception;

public class RateLimitException extends RuntimeException {
    
    private final String message;
    private final int retryAfter;
    
    public RateLimitException(String message) {
        this(message, 0);
    }
    
    public RateLimitException(String message, int retryAfter) {
        super(message);
        this.message = message;
        this.retryAfter = retryAfter;
    }
    
    @Override
    public String getMessage() {
        return message;
    }
    
    public int getRetryAfter() {
        return retryAfter;
    }
}

4. 全局異常處理器 (RateLimitExceptionHandler)

package cn.jbolt.config.handler;

import cn.jbolt.config.exception.RateLimitException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;

@RestControllerAdvice
public class RateLimitExceptionHandler {
    
    @ExceptionHandler(RateLimitException.class)
    public ResponseEntity<Map<String, Object>> handleRateLimitException(
            RateLimitException e, HttpServletResponse response) {
        
        Map<String, Object> result = new HashMap<>();
        result.put("code", HttpStatus.TOO_MANY_REQUESTS.value());
        result.put("message", e.getMessage());
        result.put("data", null);
        
        // 設(shè)置HTTP響應(yīng)頭,告訴客戶端多久后可以重試
        if (e.getRetryAfter() > 0) {
            response.setHeader("Retry-After", String.valueOf(e.getRetryAfter()));
        }
        response.setHeader("X-RateLimit-Window", "60");
        
        return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).body(result);
    }
}

5. IP工具類(lèi) (IpUtils)

package cn.jbolt.util;

import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;

public class IpUtils {
    
    private static final String[] IP_HEADER_NAMES = {
        "X-Forwarded-For",
        "X-Real-IP", 
        "Proxy-Client-IP",
        "WL-Proxy-Client-IP",
        "HTTP_CLIENT_IP",
        "HTTP_X_FORWARDED_FOR"
    };
    
    private static final String UNKNOWN = "unknown";
    private static final String LOCALHOST_IPV4 = "127.0.0.1";
    private static final String LOCALHOST_IPV6 = "0:0:0:0:0:0:0:1";
    
    /**
     * 獲取客戶端真實(shí)IP地址
     * 處理代理服務(wù)器、負(fù)載均衡器等場(chǎng)景
     */
    public static String getClientIp(HttpServletRequest request) {
        if (request == null) {
            return UNKNOWN;
        }
        
        String ip = null;
        
        // 依次檢查各種可能的IP頭
        for (String header : IP_HEADER_NAMES) {
            ip = request.getHeader(header);
            if (isValidIp(ip)) {
                break;
            }
        }
        
        // 如果頭信息中沒(méi)有找到,則使用getRemoteAddr
        if (!isValidIp(ip)) {
            ip = request.getRemoteAddr();
            if (LOCALHOST_IPV6.equals(ip)) {
                ip = LOCALHOST_IPV4;
            }
        }
        
        // 處理多個(gè)IP的情況(X-Forwarded-For可能包含多個(gè)IP)
        if (StringUtils.hasText(ip) && ip.contains(",")) {
            ip = ip.split(",")[0].trim();
        }
        
        return StringUtils.hasText(ip) ? ip : UNKNOWN;
    }
    
    /**
     * 檢查IP是否有效
     */
    private static boolean isValidIp(String ip) {
        return StringUtils.hasText(ip) && !UNKNOWN.equalsIgnoreCase(ip);
    }
}

技術(shù)實(shí)現(xiàn)原理

1. AOP切面攔截

系統(tǒng)使用Spring AOP在方法執(zhí)行前進(jìn)行攔截,這是一個(gè)核心的限流切面類(lèi):

package cn.jbolt.config.aspect;

import cn.jbolt.config.anno.rateLimiter.RateLimiter;
import cn.jbolt.config.anno.rateLimiter.RateLimitType;
import cn.jbolt.config.exception.RateLimitException;
import cn.jbolt.util.IpUtils;
import cn.jbolt.util.cache.RateLimiterCache;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.TimeUnit;

@Aspect
@Component
public class RateLimiterAspect {
    
    private static final Logger logger = LoggerFactory.getLogger(RateLimiterAspect.class);
    
    @Around("@annotation(rateLimiter)")
    public Object around(ProceedingJoinPoint point, RateLimiter rateLimiter) throws Throwable {
        
        // 檢查是否啟用限流
        if (!rateLimiter.enabled()) {
            return point.proceed();
        }
        
        // 獲取HTTP請(qǐng)求對(duì)象
        HttpServletRequest request = getCurrentRequest();
        if (request == null) {
            logger.warn("無(wú)法獲取HttpServletRequest,跳過(guò)限流檢查");
            return point.proceed();
        }
        
        // 生成限流鍵
        String limitKey = generateLimitKey(point, rateLimiter, request);
        
        // 執(zhí)行主要限流檢查
        checkRateLimit(limitKey, rateLimiter.time(), rateLimiter.count(), rateLimiter.msg());
        
        // 執(zhí)行額外限流檢查(如果配置了)
        if (rateLimiter.extraTime() > 0 && rateLimiter.extraCount() > 0) {
            String extraLimitKey = limitKey + ":extra";
            String extraMsg = rateLimiter.extraMsg().isEmpty() ? rateLimiter.msg() : rateLimiter.extraMsg();
            checkRateLimit(extraLimitKey, rateLimiter.extraTime(), rateLimiter.extraCount(), extraMsg);
        }
        
        // 所有限流檢查通過(guò),繼續(xù)執(zhí)行業(yè)務(wù)方法
        return point.proceed();
    }
    
    /**
     * 執(zhí)行限流檢查
     */
    private void checkRateLimit(String key, int timeWindow, int maxCount, String message) {
        try {
            // 增加計(jì)數(shù)器并獲取當(dāng)前訪問(wèn)次數(shù)
            int currentCount = RateLimiterCache.incrementAndGet(key, timeWindow, TimeUnit.SECONDS);
            
            logger.debug("限流檢查: key={}, 當(dāng)前次數(shù)={}, 限制次數(shù)={}", key, currentCount, maxCount);
            
            // 檢查是否超過(guò)限制
            if (currentCount > maxCount) {
                long ttl = RateLimiterCache.getTtl(key);
                logger.warn("觸發(fā)限流: key={}, 當(dāng)前次數(shù)={}, 限制次數(shù)={}, 剩余時(shí)間={}秒", 
                          key, currentCount, maxCount, ttl);
                throw new RateLimitException(message, (int) ttl);
            }
            
        } catch (RateLimitException e) {
            throw e;
        } catch (Exception e) {
            logger.error("限流檢查異常: key={}", key, e);
            // 限流服務(wù)異常時(shí),選擇放行而不是阻塞
        }
    }
    
    /**
     * 生成限流鍵
     */
    private String generateLimitKey(ProceedingJoinPoint point, RateLimiter rateLimiter, HttpServletRequest request) {
        StringBuilder keyBuilder = new StringBuilder();
        keyBuilder.append(rateLimiter.prefix());
        
        // 添加方法簽名
        String methodSignature = point.getSignature().toShortString();
        keyBuilder.append(methodSignature);
        
        // 根據(jù)限流類(lèi)型添加不同的標(biāo)識(shí)
        switch (rateLimiter.limitType()) {
            case IP:
                keyBuilder.append(":ip:").append(IpUtils.getClientIp(request));
                break;
            case USER:
                String userId = getCurrentUserId(request);
                keyBuilder.append(":user:").append(userId != null ? userId : "anonymous");
                break;
            case CUSTOM:
                keyBuilder.append(":custom:").append(rateLimiter.customKey());
                break;
            case DEFAULT:
            default:
                keyBuilder.append(":default:global");
                break;
        }
        
        // 添加時(shí)間窗口,確保不同時(shí)間窗口的限流獨(dú)立
        keyBuilder.append(":").append(rateLimiter.time());
        
        String finalKey = keyBuilder.toString();
        logger.debug("生成限流鍵: {}", finalKey);
        return finalKey;
    }
    
    /**
     * 獲取當(dāng)前HTTP請(qǐng)求
     */
    private HttpServletRequest getCurrentRequest() {
        try {
            ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            return attrs != null ? attrs.getRequest() : null;
        } catch (Exception e) {
            logger.warn("獲取HttpServletRequest失敗", e);
            return null;
        }
    }
    
    /**
     * 獲取當(dāng)前用戶ID
     * 這里需要根據(jù)實(shí)際的用戶認(rèn)證體系來(lái)實(shí)現(xiàn)
     */
    private String getCurrentUserId(HttpServletRequest request) {
        // 方案1:從Session中獲取
        Object userId = request.getSession().getAttribute("userId");
        if (userId != null) {
            return userId.toString();
        }
        
        // 方案2:從JWT Token中獲取
        String token = request.getHeader("Authorization");
        if (token != null && token.startsWith("Bearer ")) {
            // 解析JWT獲取用戶ID
            // return JwtUtils.getUserIdFromToken(token);
        }
        
        // 方案3:從請(qǐng)求參數(shù)中獲取
        String userIdParam = request.getParameter("userId");
        if (userIdParam != null) {
            return userIdParam;
        }
        
        return null;
    }
}

2. 緩存數(shù)據(jù)結(jié)構(gòu)

系統(tǒng)使用一個(gè)包裝類(lèi)來(lái)存儲(chǔ)緩存數(shù)據(jù):

package cn.jbolt.util.cache;

import java.io.Serializable;
import java.util.concurrent.TimeUnit;

public class CacheWrapper implements Serializable {
    
    private static final long serialVersionUID = 1L;
    
    private Object value;
    private long timestamp;
    private long durationMillis;
    
    public CacheWrapper() {
    }
    
    public CacheWrapper(Object value, long duration, TimeUnit unit) {
        this.value = value;
        this.timestamp = System.currentTimeMillis();
        this.durationMillis = unit.toMillis(duration);
    }
    
    /**
     * 檢查是否已過(guò)期
     */
    public boolean isExpired() {
        return System.currentTimeMillis() - timestamp > durationMillis;
    }
    
    /**
     * 獲取剩余過(guò)期時(shí)間(毫秒)
     */
    public long getRemainingTime() {
        long elapsed = System.currentTimeMillis() - timestamp;
        return Math.max(0, durationMillis - elapsed);
    }
    
    // getter和setter方法
    public Object getValue() {
        return value;
    }
    
    public void setValue(Object value) {
        this.value = value;
    }
    
    public long getTimestamp() {
        return timestamp;
    }
    
    public void setTimestamp(long timestamp) {
        this.timestamp = timestamp;
    }
    
    public long getDurationMillis() {
        return durationMillis;
    }
    
    public void setDurationMillis(long durationMillis) {
        this.durationMillis = durationMillis;
    }
}

完整代碼示例

1. 控制器示例

package cn.jbolt.controller;

import cn.jbolt.config.anno.rateLimiter.RateLimiter;
import cn.jbolt.config.anno.rateLimiter.RateLimitType;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api")
public class DemoController {
    
    /**
     * 登錄接口 - 防止暴力破解
     * 每個(gè)IP每分鐘最多嘗試5次
     */
    @PostMapping("/login")
    @RateLimiter(
        limitType = RateLimitType.IP,
        time = 60, 
        count = 5,
        msg = "登錄嘗試過(guò)于頻繁,請(qǐng)1分鐘后重試"
    )
    public Result login(@RequestBody LoginRequest request) {
        // 登錄邏輯
        if (isValidUser(request.getUsername(), request.getPassword())) {
            return Result.success("登錄成功");
        } else {
            return Result.error("用戶名或密碼錯(cuò)誤");
        }
    }
    
    /**
     * 發(fā)送驗(yàn)證碼 - 防止惡意發(fā)送
     * 每個(gè)IP每分鐘最多3次
     */
    @PostMapping("/sms/send")
    @RateLimiter(
        limitType = RateLimitType.IP,
        time = 60, 
        count = 3,
        msg = "驗(yàn)證碼發(fā)送過(guò)于頻繁,請(qǐng)稍后重試"
    )
    public Result sendSms(@RequestBody SmsRequest request) {
        // 發(fā)送短信邏輯
        boolean success = smsService.sendCode(request.getPhone());
        return success ? Result.success("發(fā)送成功") : Result.error("發(fā)送失敗");
    }
    
    /**
     * 查詢(xún)接口 - 防止爬蟲(chóng)
     * 每個(gè)IP每分鐘最多100次
     */
    @GetMapping("/products")
    @RateLimiter(
        limitType = RateLimitType.IP,
        time = 60, 
        count = 100,
        msg = "查詢(xún)過(guò)于頻繁,請(qǐng)稍后重試"
    )
    public Result getProducts(@RequestParam(defaultValue = "1") int page) {
        // 查詢(xún)商品邏輯
        List<Product> products = productService.getProducts(page);
        return Result.success(products);
    }
    
    /**
     * 用戶操作 - 防止頻繁操作
     * 每個(gè)用戶每分鐘最多30次
     */
    @PostMapping("/user/update")
    @RateLimiter(
        limitType = RateLimitType.USER,
        time = 60, 
        count = 30,
        msg = "操作過(guò)于頻繁,請(qǐng)稍后重試"
    )
    public Result updateUser(@RequestBody UserUpdateRequest request) {
        // 更新用戶信息邏輯
        boolean success = userService.updateUser(request);
        return success ? Result.success("更新成功") : Result.error("更新失敗");
    }
    
    /**
     * 關(guān)鍵操作 - 嚴(yán)格限流
     * 1秒最多1次 + 1分鐘最多5次
     */
    @PostMapping("/transfer")
    @RateLimiter(
        limitType = RateLimitType.USER,
        time = 1, count = 1, msg = "操作過(guò)于頻繁,請(qǐng)稍后再試",
        extraTime = 60, extraCount = 5, extraMsg = "您在1分鐘內(nèi)的操作次數(shù)已達(dá)上限"
    )
    public Result transfer(@RequestBody TransferRequest request) {
        // 轉(zhuǎn)賬邏輯
        boolean success = transferService.transfer(request);
        return success ? Result.success("轉(zhuǎn)賬成功") : Result.error("轉(zhuǎn)賬失敗");
    }
    
    /**
     * 自定義限流 - 按商品限制
     * 每個(gè)商品每分鐘最多下單20次
     */
    @PostMapping("/order/{productId}")
    @RateLimiter(
        limitType = RateLimitType.CUSTOM,
        customKey = "product_order",
        time = 60, 
        count = 20,
        msg = "該商品下單過(guò)于頻繁,請(qǐng)稍后重試"
    )
    public Result createOrder(@PathVariable String productId, @RequestBody OrderRequest request) {
        // 創(chuàng)建訂單邏輯
        Order order = orderService.createOrder(productId, request);
        return Result.success(order);
    }
    
    // 輔助方法
    private boolean isValidUser(String username, String password) {
        // 實(shí)際的用戶驗(yàn)證邏輯
        return "admin".equals(username) && "123456".equals(password);
    }
}

2. 統(tǒng)一返回對(duì)象

package cn.jbolt.common;

public class Result {
    private int code;
    private String message;
    private Object data;
    
    public static Result success(Object data) {
        Result result = new Result();
        result.code = 200;
        result.message = "success";
        result.data = data;
        return result;
    }
    
    public static Result error(String message) {
        Result result = new Result();
        result.code = 500;
        result.message = message;
        result.data = null;
        return result;
    }
    
    // getter和setter方法
    public int getCode() {
        return code;
    }
    
    public void setCode(int code) {
        this.code = code;
    }
    
    public String getMessage() {
        return message;
    }
    
    public void setMessage(String message) {
        this.message = message;
    }
    
    public Object getData() {
        return data;
    }
    
    public void setData(Object data) {
        this.data = data;
    }
}

使用指南

1. 基本使用

// 最簡(jiǎn)單的用法 - 使用默認(rèn)配置
@RateLimiter(limitType = RateLimitType.IP)
public String simpleApi() {
    return "success";
}

// 自定義時(shí)間窗口和次數(shù)
@RateLimiter(
    limitType = RateLimitType.IP,
    time = 60,    // 60秒
    count = 100   // 最多100次
)
public String customApi() {
    return "success";
}

2. 不同場(chǎng)景的配置建議

// 登錄接口 - 嚴(yán)格限制
@RateLimiter(
    limitType = RateLimitType.IP,
    time = 60, count = 5,
    msg = "登錄嘗試過(guò)于頻繁,請(qǐng)1分鐘后重試"
)

// 查詢(xún)接口 - 適中限制
@RateLimiter(
    limitType = RateLimitType.IP,
    time = 60, count = 100,
    msg = "查詢(xún)過(guò)于頻繁,請(qǐng)稍后重試"
)

// 用戶操作 - 按用戶限制
@RateLimiter(
    limitType = RateLimitType.USER,
    time = 60, count = 30,
    msg = "操作過(guò)于頻繁,請(qǐng)稍后重試"
)

// 全局保護(hù) - 系統(tǒng)級(jí)限制
@RateLimiter(
    limitType = RateLimitType.DEFAULT,
    time = 60, count = 200,
    msg = "系統(tǒng)繁忙,請(qǐng)稍后重試"
)

3. 雙重限流配置

// 嚴(yán)格的雙重限流:秒級(jí) + 分鐘級(jí)
@RateLimiter(
    limitType = RateLimitType.IP,
    time = 1, count = 1, msg = "請(qǐng)求過(guò)于頻繁,請(qǐng)稍后再試",
    extraTime = 60, extraCount = 10, extraMsg = "您在1分鐘內(nèi)的請(qǐng)求次數(shù)已達(dá)上限"
)

// 適中的雙重限流:分鐘級(jí) + 小時(shí)級(jí)
@RateLimiter(
    limitType = RateLimitType.IP,
    time = 60, count = 100, msg = "1分鐘內(nèi)請(qǐng)求過(guò)多",
    extraTime = 3600, extraCount = 1000, extraMsg = "1小時(shí)內(nèi)請(qǐng)求過(guò)多"
)

總結(jié)

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • JMETER用戶變量作用域測(cè)試流程

    JMETER用戶變量作用域測(cè)試流程

    這篇文章主要介紹了JMETER用戶變量作用域測(cè)試流程,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-05-05
  • MybatisPlus分頁(yè)排序查詢(xún)字段帶有下劃線的坑及解決

    MybatisPlus分頁(yè)排序查詢(xún)字段帶有下劃線的坑及解決

    這篇文章主要介紹了MybatisPlus分頁(yè)排序查詢(xún)字段帶有下劃線的坑及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • java前后端使用ajax數(shù)據(jù)交互問(wèn)題(簡(jiǎn)單demo)

    java前后端使用ajax數(shù)據(jù)交互問(wèn)題(簡(jiǎn)單demo)

    這篇文章主要介紹了java前后端使用ajax數(shù)據(jù)交互問(wèn)題(簡(jiǎn)單demo),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。
    2023-06-06
  • java 將一個(gè)數(shù)組逆序輸出的方法

    java 將一個(gè)數(shù)組逆序輸出的方法

    今天小編就為大家分享一篇java 將一個(gè)數(shù)組逆序輸出的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2018-06-06
  • SpringBoot內(nèi)部外部配置文件加載順序解析

    SpringBoot內(nèi)部外部配置文件加載順序解析

    這篇文章主要介紹了SpringBoot內(nèi)部外部配置文件加載順序解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-07-07
  • SpringBoot整合knife4j3.0.3實(shí)現(xiàn)過(guò)程

    SpringBoot整合knife4j3.0.3實(shí)現(xiàn)過(guò)程

    文章介紹了如何在SpringBoot項(xiàng)目中使用Swagger和Knife4j來(lái)生成和增強(qiáng)API文檔,Swagger通過(guò)注解自動(dòng)生成API文檔,而Knife4j則提供了更豐富的功能,如在線調(diào)試、多語(yǔ)言支持和離線文檔導(dǎo)出等,通過(guò)配置和使用這些工具,可以提高API文檔的管理和使用效率
    2026-01-01
  • IDEA2023.3.4開(kāi)啟SpringBoot項(xiàng)目的熱部署(圖文)

    IDEA2023.3.4開(kāi)啟SpringBoot項(xiàng)目的熱部署(圖文)

    本文使用的開(kāi)發(fā)工具是idea,使用的是springboot框架開(kāi)發(fā)的項(xiàng)目,配置熱部署,可以提高開(kāi)發(fā)效率,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2024-02-02
  • java實(shí)現(xiàn)動(dòng)態(tài)圖片效果

    java實(shí)現(xiàn)動(dòng)態(tài)圖片效果

    這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)動(dòng)態(tài)圖片效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-03-03
  • 使用?mybatis?自定義日期類(lèi)型轉(zhuǎn)換器的示例代碼

    使用?mybatis?自定義日期類(lèi)型轉(zhuǎn)換器的示例代碼

    這篇文章主要介紹了使用?mybatis?自定義日期類(lèi)型轉(zhuǎn)換器的示例代碼,這里使用mybatis中的typeHandlers?實(shí)現(xiàn)的,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2022-03-03
  • spring注解@Import用法詳解

    spring注解@Import用法詳解

    這篇文章主要介紹了spring注解@Import用法詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-10-10

最新評(píng)論

苗栗县| 茂名市| 镇赉县| 长宁区| 呈贡县| 长沙市| 贡山| 桓台县| 青冈县| 治多县| 福海县| 泰来县| 大城县| 东莞市| 威远县| 泸水县| 三都| 宁陕县| 会同县| 竹山县| 平定县| 竹山县| 和硕县| 新安县| 廊坊市| 咸丰县| 那坡县| 乌苏市| 图片| 临澧县| 蓬安县| 衡阳县| 威海市| 中方县| 榆中县| 集安市| 龙泉市| 集贤县| 吴旗县| 游戏| 英德市|