目录
1.创建@Limit注解
package com.mimiwang.common.annotation;
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Limit {
// redisKey
String key() default "";
// 最大次数
int num() default 5;
// 过期时效
int duration() default 3;
// 时间单位
TimeUnit timeUnit() default TimeUnit.MINUTES;
}
2.自定义切面
package com.mimiwang.common.aop;
import com.mimiwang.common.annotation.Limit;
import com.mimiwang.common.constant.HttpStatus;
import com.mimiwang.common.utils.NetworkUtils;
import com.mimiwang.common.utils.ResponseUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.aspectj.lang.ProceedingJoinPoint;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
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 javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
@Aspect
@Component
@Slf4j
public class LimitAspect {
@Autowired
RedisTemplate<String,Object> redisTemplate;
@Pointcut("@annotation(com.mimiwang.common.annotation.Limit) || @within(com.mimiwang.common.annotation.Limit)")
void test(){
}
@Around("test()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
// 获取方法注解
Limit annotation = method.getAnnotation(Limit.class);
// 获取注解参数
String preKey = annotation.key();
int maxNum = annotation.num();
int duration = annotation.duration();
TimeUnit timeUnit = annotation.timeUnit();
// 如果没有设置key,则默认 key=类名:方法名:ip
if(Strings.isBlank(preKey)){
preKey=signature.getDeclaringTypeName()+":"+method.getName();
}
// 获取ServletRequestAttributes
ServletRequestAttributes requestAttributes =(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
HttpServletResponse response = requestAttributes.getResponse();
// 获取ip
String ip = NetworkUtils.getIpAddress(request);
String key= preKey+":"+ip;
// redis获取
Object o = redisTemplate.opsForValue().get(key);
// 第一次访问
if(o == null){
// 设置redis
redisTemplate.opsForValue().set(key,1,duration,timeUnit);
log.info("[Limit] [key]:{} | [Num]:{} | Status:{} | Max:{}",key,1,"ok",maxNum);
// 放行
return joinPoint.proceed(joinPoint.getArgs());
}else {
int num=(int)o;
// 达到最大次数,禁止访问
if( num == maxNum){
log.error("[Limit] [key]:{} | [Num]:{} | Status:{} | Max:{}",key,num,"Ban",maxNum);
return this.returnLimit();
}
// 未达到最大值, 值+1
Long increment = redisTemplate.opsForValue().increment(key);
log.info("[Limit] [key]:{} | [Num]:{} | Status:{} | Max:{}",key,increment,"ok",maxNum);
// 放行
return joinPoint.proceed(joinPoint.getArgs());
}
}
// 错误响应
public String returnLimit() throws IOException {
HttpStatus MAXLIMIT = HttpStatus.MAXLIMIT;
return ResponseUtil.errorJSONData(MAXLIMIT.getCode(), MAXLIMIT.getMsg());
}
}
3.解决依赖问题
此处说明
(1).HttpStatus.MAXLIMIT
枚举类型
public enum HttpStatus {
MAXLIMIT(403,"限制最大访问次数");
private String msg;
private int code;
private HttpStatus(int code, String msg) {
this.code=code;
this.msg=msg;
}
public int getCode(){
return this.code;
}
public String getMsg(){
return this.msg;
}
}
(2).NetworkUtils
自定义Utils,用于获取请求ip
public class NetworkUtils {
public static String getIpAddress(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
// 如果是多级代理,那么取第一个ip为客户端ip
if (ip != null && ip.contains(",")) {
ip = ip.substring(0, ip.indexOf(",")).trim();
}
return ip;
}
}
(3).ResponseUtil
基于fastjson的自定义响应包装类
import com.alibaba.fastjson.JSONObject;
public class ResponseUtil extends JSONObject {
public static BaseUtil successJSONObject(String msg){
return successJSONObject(msg,null);
}
public static BaseUtil successJSONObject(String msg, Object data){
return successJSONObject(200,msg,data);
}
public static BaseUtil successJSONObject(int code,String msg, Object data){
return new BaseUtil(code, msg, data);
}
public static BaseUtil errorJSONObject(String msg){
return errorJSONObject(msg,null);
}
public static BaseUtil errorJSONObject(String msg, Object data){
return errorJSONObject(500,msg,data);
}
public static BaseUtil errorJSONObject(int code,String msg, Object data){
return new BaseUtil(code, msg, data);
}
public static String successJSONData(int code, String msg, Object data){
return toJSONString(new BaseUtil(code, msg, data));
}
public static String successJSONData(String msg, Object data){
return toJSONString(new BaseUtil(200,msg,data));
}
public static String successJSONData(Object data){
return toJSONString(new BaseUtil(200,"请求成功",data));
}
public static String successJSONData(){
return toJSONString(new BaseUtil(200,"操作成功",null));
}
public static String errorJSONData(int code, String msg, Object data){
return toJSONString(new BaseUtil(code,msg,data));
}
public static String errorJSONData(String msg, Object data){
return toJSONString(new BaseUtil(500,msg,data));
}
public static String errorJSONData(int code, String msg){
return toJSONString(new BaseUtil(code,msg,null));
}
public static String errorJSONData(){
return toJSONString(new BaseUtil(500,"系统异常,操作失败!",null));
}
public static String errorJSONData(String msg){
return toJSONString(new BaseUtil(500,msg,null));
}
public static Class<BaseUtil> getResponseClass(){
return BaseUtil.class;
}
public static class BaseUtil{
/*响应状态码*/
private int code;
/*响应信息*/
private String msg;
/*响应数据*/
private Object data;
public BaseUtil(int code, String msg, Object data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}
}
4.使用
默认
@Limit()
@RestController
@RequestMapping("/animal")
public class AnimalController {
@Autowired
private ApplicationContext applicationContext;
@Autowired
IAnimalService animalService;
@Autowired
LoginAspect loginAspect;
@Limit()
@GetMapping("/info/id/{id}")
public String getAnimalById(@PathVariable long id){
return ResponseUtil.successJSONData(animalService.selectAnimalById(id));
}
}
自定义
@Limit(key = "aniInfo",num = 3,duration = 20,timeUnit = TimeUnit.SECONDS)
@RestController
@RequestMapping("/animal")
public class AnimalController {
@Autowired
IAnimalService animalService;
@Limit(key = "aniInfo",num = 3,duration = 20,timeUnit = TimeUnit.SECONDS)
@GetMapping("/info/id/{id}")
public String getAnimalById(@PathVariable long id){
return ResponseUtil.successJSONData(animalService.selectAnimalById(id));
}
}
5.运行错误可能的原因
(1).yaml没有配置redis
(2).LimitAspect没被扫描到
多模块下,A模块使用B模块Component,则A模块启动类需要加上@ComponentScan注解
参考:SpringBoot关于多模块调用其他模块自定义的bean_引用另一个模块的bean_要加油!的博客-CSDN博客