1. 程式人生 > 實用技巧 >Java生鮮電商平臺-介面限流的技術分析與原始碼下載(小程式/APP)

Java生鮮電商平臺-介面限流的技術分析與原始碼下載(小程式/APP)

Java生鮮電商平臺-介面限流的技術分析與原始碼下載(小程式/APP)

說明:在實際的Java生鮮電商平臺中,在對外暴露的介面中存在某些人為或者攻擊者的惡意呼叫與攻擊,這個時候為了系統的安全,就需要對某些介面進行限流操作,網上的大部分的接

口限流都是基於guava或者阿里巴巴的Sentinel,本文只是根據實際的業務出發,採用自定義註解來進行方法限流,滿足我們的日常業務的要求。

1。閱讀本文你需要掌握的知識為:

如何限流。如何基於註解限流,相關原始碼等

2. 首先我們看下POM檔案. 基於SpringBoot2.2.2 這個版本

<?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.2.2.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.ctg.test</groupId> <artifactId>springboot-limit-api</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springboot-limit-api</name> <description>springboot-limit-api</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>22.0</version> </dependency> <!-- https://
mvnrepository.com/artifact/com.alibaba/fastjson --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.47</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>

2. 限流的註解:可以根據自己的業務,靈活設定預設的值

import java.lang.annotation.*;

@Inherited
@Documented
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE,ElementType.PACKAGE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
    double limitNum() default 20;  //預設每秒放入桶中的token
}

3. 基於sping的AOP進行業務限流

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.util.concurrent.RateLimiter;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;

@Component
@Scope
@Aspect
public class RateLimitAspect {
    private Logger log = LoggerFactory.getLogger(this.getClass());
    //用來存放不同介面的RateLimiter(key為介面名稱,value為RateLimiter)
    private ConcurrentHashMap<String, RateLimiter> map = new ConcurrentHashMap<>();

    private static ObjectMapper objectMapper = new ObjectMapper();

    private RateLimiter rateLimiter;

    @Autowired
    private HttpServletResponse response;

    @Pointcut("@annotation(com.ctg.test.limit.RateLimit)")
    public void serviceLimit() {
    }

    @Around("serviceLimit()")
    public Object around(ProceedingJoinPoint joinPoint) throws NoSuchMethodException {
        Object obj = null;
        //獲取攔截的方法名
        Signature sig = joinPoint.getSignature();
        //獲取攔截的方法名
        MethodSignature msig = (MethodSignature) sig;
        //返回被織入增加處理目標物件
        Object target = joinPoint.getTarget();
        //為了獲取註解資訊
        Method currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
        //獲取註解資訊
        RateLimit annotation = currentMethod.getAnnotation(RateLimit.class);
        double limitNum = annotation.limitNum(); //獲取註解每秒加入桶中的token
        String functionName = msig.getName(); // 註解所在方法名區分不同的限流策略

        //獲取rateLimiter
        if(map.containsKey(functionName)){
            rateLimiter = map.get(functionName);
        }else {
            map.put(functionName, RateLimiter.create(limitNum));
            rateLimiter = map.get(functionName);
        }

        try {
            if (rateLimiter.tryAcquire()) {
                //執行方法
                obj = joinPoint.proceed();
            } else {
                //拒絕了請求(服務降級)
                String result = objectMapper.writeValueAsString(ResultUtil.error(201, "你操作太過頻繁,請稍後再試!"));
                log.info("拒絕了請求:" + result);
                outErrorResult(result);
            }
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return obj;
    }
    //將結果返回
    public void outErrorResult(String result) {
        response.setContentType("application/json;charset=UTF-8");
        try (ServletOutputStream outputStream = response.getOutputStream()) {
            outputStream.write(result.getBytes("utf-8"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    static {
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
    }

}

4. 基礎的業務類與返回實現. 包括基礎的程式碼與工具類

/**
 * @Description: 統一介面返回值
 */
public class Result<T> {

    /** 錯誤碼. */
    private Integer code;

    /** 提示資訊. */
    private String msg;

    /** 具體的內容. */
    private T data;

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}
public class ResultCode {
    public static final Integer SUCCESS = 200;
    public static final Integer ERROR = 201;
    public static final Integer UNKNOWERROR = 202;
}

public class ResultUtil {
    public static Result success() {
        return success(null);
    }
    public static Result success(Object object) {
        Result result = new Result();
        result.setCode(ResultCode.SUCCESS);
        result.setMsg("成功");
        result.setData(object);
        return result;
    }
    public static Result success(Integer code,Object object) {
        Result result = new Result();
        result.setCode(code);
        result.setMsg("成功");
        result.setData(object);
        return result;
    }

    public static Result error( String msg) {
        Result result = new Result();
        result.setCode(ResultCode.ERROR);
        result.setMsg(msg);
        return result;
    }
    public static Result error(Integer code, String msg) {
        Result result = new Result();
        result.setCode(code);
        result.setMsg(msg);
        result.setData("");
        return result;
    }
}

5. 進行業務程式碼測試

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.HashMap;
import java.util.Map;

/**
 * @Description:
 * http://localhost:8080/test1
 * http://localhost:8080/test2
 */
@Controller
public class TestController {
    @RateLimit(limitNum = 1.0)
    @RequestMapping("/test1")
    @ResponseBody
    public Object getResults() {
        return ResultUtil.success(200,"test1");
    }

    @RateLimit(limitNum = 10.0)
    @RequestMapping("/test2")
    @ResponseBody
    public Object getResultTwo(){
    Map<String,Object>map =new HashMap<>();

        return ResultUtil.success(200,"test2");
    }
}

最終的結果為:

結語

覆盤與總結.

總結:

做Java生鮮電商平臺的網際網路應用,無論是生鮮小程式還是APP,在高可用的系統設計中限流設計思路是非常重要的,本文只是起一個拋磚引玉的作用,

希望用生鮮小程式的搭建限流的設計思路實戰經驗告訴大家一些實際的專案經驗,希望對大家有用.

QQ:137071249

共同學習QQ群:793305035