切面工具
核心POM依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
接口限流切面
【限流枚举】
/**
* 限流枚举
*/
public enum LimitType {
// 默认
CUSTOMER,
// ip限流
IP
}
【限流注解】
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author JHL
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Limit {
// 资源名称,用于描述接口功能
String name() default "";
// 资源 key
String key() default "";
// key prefix
String prefix() default "";
// 时间的,单位秒
int period();
// 限制访问次数
int count();
// 限制类型
LimitType limitType() default LimitType.CUSTOMER;
}
【异常类】
import lombok.Getter;
import org.springframework.http.HttpStatus;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
/**
* 统一异常处理
*/
@Getter
public class BadRequestException extends RuntimeException{
private Integer status = BAD_REQUEST.value();
public BadRequestException(String msg){
super(msg);
}
public BadRequestException(HttpStatus status,String msg){
super(msg);
this.status = status.value();
}
}
【切面类】
import cn.hutool.core.util.StrUtil;
import com.xxx.Limit;
import com.xxx.BadRequestException;
import com.google.common.collect.ImmutableList;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
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.lang.reflect.Method;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Objects;
/**
* 接口限流切面
*/
@Aspect
@Component
@Slf4j
public class LimitAspect {
private static final String UNKNOWN = "unknown";
private final RedisTemplate<Object, Object> redisTemplate;
private static final Logger logger = LoggerFactory.getLogger(LimitAspect.class);
public LimitAspect(RedisTemplate<Object, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Pointcut("@annotation(com.xxx.annotation.Limit)")
public void pointcut() {
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method signatureMethod = signature.getMethod();
Limit limit = signatureMethod.getAnnotation(Limit.class);
LimitType limitType = limit.limitType();
String key = limit.key();
if (StrUtil.isEmpty(key)) {
if (limitType == LimitType.IP) {
key = getIp(request);
} else {
key = signatureMethod.getName();
}
}
ImmutableList<Object> keys = ImmutableList.of(StringUtils.join(limit.prefix(), "_", key, "_", request.getRequestURI().replace("/", "_")));
String luaScript = buildLuaScript();
RedisScript<Number> redisScript = new DefaultRedisScript<>(luaScript, Number.class);
Number count = redisTemplate.execute(redisScript, keys, limit.count(), limit.period());
if (null != count && count.intValue() <= limit.count()) {
logger.info("第{}次访问key为 {},描述为 [{}] 的接口", count, keys, limit.name());
return joinPoint.proceed();
} else {
throw new BadRequestException("访问次数受限制");
}
}
/**
* 限流脚本
*/
private String buildLuaScript() {
return "local c" +
"\nc = redis.call('get',KEYS[1])" +
"\nif c and tonumber(c) > tonumber(ARGV[1]) then" +
"\nreturn c;" +
"\nend" +
"\nc = redis.call('incr',KEYS[1])" +
"\nif tonumber(c) == 1 then" +
"\nredis.call('expire',KEYS[1],ARGV[2])" +
"\nend" +
"\nreturn c;";
}
public static String getIp(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.getRemoteAddr();
}
String comma = ",";
String localhost = "127.0.0.1";
if (ip.contains(comma)) {
ip = ip.split(",")[0];
}
if (localhost.equals(ip)) {
// 获取本机真正的ip地址
try {
ip = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
log.error(e.getMessage(), e);
}
}
return ip;
}
}
日志切面
【注解】
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author JHL
* @version 1.0
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
String value() default "";
}
【切面类】
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
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.Arrays;
import static org.slf4j.LoggerFactory.getLogger;
/**
* @author JHL
* @version 1.0
*/
@Aspect
@Component
public class LogAspect {
private static final Logger LOG = getLogger(LogAspect.class);
@Pointcut("execution(public * com.xxx.common.controller.*.*(..))")
public void webLog() {
}
@Pointcut("@annotation(com.xxx.common.annotation.Log)")
public void log() {
}
@Before("webLog()")
public void deBefore(JoinPoint joinPoint) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
LOG.info("######################### \t[ 收到请求 ]\t #########################");
LOG.info("######################### \t[ URL : {} ]\t #########################", request.getRequestURL().toString());
LOG.info("######################### \t[ HTTP_METHOD : {} ]\t #########################", request.getMethod());
LOG.info("######################### \t[ IP : {} ]\t #########################", request.getRemoteAddr());
LOG.info("######################### \t[ CLASS_METHOD : {} ]\t #########################", joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
LOG.info("######################### \t[ ARGS : {} ]\t #########################", Arrays.toString(joinPoint.getArgs()));
}
// @AfterReturning(returning = "ret", pointcut = "webLog()")
// public void doAfterReturning(Object ret) {
// }
//
// @AfterThrowing("webLog()")
// public void throwss(JoinPoint jp) {
// }
//
// @After("webLog()")
// public void after(JoinPoint jp) {
// }
@Around("webLog()")
public Object arround(ProceedingJoinPoint pjp) throws Throwable {
try {
Object o = pjp.proceed();
System.out.println("方法,结果是 :" + o);
LOG.info("######################### \t[ 方法签名为 【{}】 的返回结果是:{} ]\t #########################", pjp.getSignature().toString(), o.toString());
return o;
} catch (Throwable e) {
e.printStackTrace();
throw e;
}
}
@Around("log()")
public Object around(ProceedingJoinPoint point) {
Object obj = null;
long beginTime = System.currentTimeMillis();
try {
// 执行方法
obj = point.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
// 执行时长(毫秒)
long time = System.currentTimeMillis() - beginTime;
// 保存日志
saveLog(point, time);
return obj;
}
private void saveLog(ProceedingJoinPoint joinPoint, long time) {
System.out.println("######################### \t[ 方法待实现 ]\t #########################");
}
}