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