aop原理:
将共同方法封装起来,然后找到切面点插入进业务流程即可
JoinPoint要了解
框架:springcloud+consul+gateway+springboot
业务环境:系统log日志,每个操作得参数都要加入到数据库中
数据库:oracle
日志表:
package com.cuslink.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* <p>
* 日志表
* </p>
*
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("TBL_LOG")
@ApiModel(value="TblLog对象", description="日志表")
public class TblLog implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "ID",type = IdType.UUID)
private String id;
@ApiModelProperty(value = "访问接口路径")
@TableField("URL")
private String url;
@ApiModelProperty(value = "客户端类型,1电脑端,2移动端")
@TableField("CLIENT_TYPE")
private Double clientType;
@ApiModelProperty(value = "IP地址")
@TableField("IP_ADDRESS")
private String ipAddress;
@ApiModelProperty(value = "方法名称")
@TableField("FUNC_NAME")
private String funcName;
@ApiModelProperty(value = "方法参数")
@TableField("FUNC_PARAM")
private String funcParam;
@ApiModelProperty(value = "类型,1操作日志,2错误日志,3系统日志")
@TableField("TYPE")
private Double type;
@ApiModelProperty(value = "内容")
@TableField("CONTENT")
private String content;
@ApiModelProperty("请求状态 0正常 1错误")
@TableField("STATUS")
private Double status;
@ApiModelProperty("错误信息")
@TableField("ERROR_MSG")
private String errorMsg;
@ApiModelProperty(value = "创建时间")
@TableField("CREATE_TIME")
private LocalDateTime createTime;
@ApiModelProperty(value = "更新时间")
@TableField("UPDATE_TIME")
private LocalDateTime updateTime;
@ApiModelProperty(value = "创建人")
@TableField("CREATE_ID")
private String createId;
@ApiModelProperty(value = "更新人")
@TableField("UPDATE_ID")
private String updateId;
@ApiModelProperty(value = "是否删除,0不删除,1删除")
@TableField("DELETE_FLAG")
private Double deleteFlag;
}
切面编程设置一个log注解
package com.cuslink.annotation;
import java.lang.annotation.*;
/**
* 操作日志元注解
*
*/
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
/**
* 日志类型,1操作日志,2错误日志,3系统日志
*/
double type() default 1;
/**
* 是否保存请求数据
*/
boolean isSaveRequestData() default true;
}
log注解写在方法头上
@RestController
@RequestMapping("/v1/param-type")
@Validated
@Api(value = "TblParamTypeController",description = "参数类型")
public class TblParamTypeController {
@Autowired
private ITblParamTypeService iTblParamTypeService;
@Log
@PostMapping("/save")
@ApiOperation("新增参数类型")
public AjaxResult save(@Valid @RequestBody TblParamType tblParamType){
if(iTblParamTypeService.saveParamType(tblParamType)){
return new AjaxResult().success("true","新增成功");
}
return new AjaxResult().fail(ConstantsCode.FAIL_TO_SAVE,"false","新增失败");
}
接下来是核心重点:
找到切入点,然后将再接口运行后将信息存到log表中
package com.cuslink.common.aspectj;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.cuslink.annotation.Log;
import com.cuslink.common.mapper.TblLogMapper;
import com.cuslink.common.util.IpUtil;
import com.cuslink.common.util.ServletUtils;
import com.cuslink.entity.TblLog;
import com.cuslink.service.AjaxResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
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.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Arrays;
/**
*/
@Aspect
@Component
@Slf4j
public class LogAspectj {
private TblLogMapper tblLogMapper;
@Autowired
public void setTblLogMapper(TblLogMapper tblLogMapper) {
this.tblLogMapper = tblLogMapper;
}
private final String[] mobileAgents = {"iphone", "android", "phone", "mobile", "wap", "netfront", "java", "opera mobi", "opera mini", "ucweb", "windows ce", "symbian", "series", "webos", "sony", "blackberry", "dopod", "nokia", "samsung", "palmsource", "xda", "pieplus", "meizu", "midp", "cldc", "motorola", "foma", "docomo", "up.browser", "up.link", "blazer", "helio", "hosin", "huawei", "novarra", "coolpad", "webos", "techfaith", "palmsource", "alcatel", "amoi", "ktouch", "nexian", "ericsson", "philips", "sagem", "wellcom", "bunjalloo", "maui", "smartphone", "iemobile", "spice", "bird", "zte-", "longcos", "pantech", "gionee", "portalmmm", "jig browser", "hiptop", "benq", "haier", "^lct", "320x320", "240x320", "176x220", "w3c ", "acs-", "alav", "alca", "amoi", "audi", "avan", "benq", "bird", "blac", "blaz", "brew", "cell", "cldc", "cmd-", "dang", "doco", "eric", "hipt", "inno", "ipaq", "java", "jigs", "kddi", "keji", "leno", "lg-c", "lg-d", "lg-g", "lge-", "maui", "maxo", "midp", "mits", "mmef", "mobi", "mot-", "moto", "mwbp", "nec-", "newt", "noki", "oper", "palm", "pana", "pant", "phil", "play", "port", "prox", "qwap", "sage", "sams", "sany", "sch-", "sec-", "send", "seri", "sgh-", "shar", "sie-", "siem", "smal", "smar", "sony", "sph-", "symb", "t-mo", "teli", "tim-", "tosh", "tsm-", "upg1", "upsi", "vk-v", "voda", "wap-", "wapa", "wapi", "wapp", "wapr", "webc", "winw", "winw", "xda", "xda-", "Googlebot-Mobile"};
@Pointcut("@annotation(com.cuslink.annotation.Log)")
public void logPointCut() {
}
/**
* 处理完请求后执行
*
* @param joinPoint 切点
*/
@AfterReturning(pointcut = "logPointCut()", returning = "jsonResult")
public void doAfterReturning(JoinPoint joinPoint, Object jsonResult) {
handleLog(joinPoint, null, jsonResult);
}
/**
* 拦截异常操作
*
* @param joinPoint 切点
* @param e 异常
*/
@AfterThrowing(value = "logPointCut()", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
handleLog(joinPoint, e, null);
}
protected void handleLog(final JoinPoint joinPoint, final Exception e, Object o) {
try {
// 获得注解
Log controllerLog = getAnnotationLog(joinPoint);
if (controllerLog == null) {
return;
}
HttpServletRequest request = ServletUtils.getRequest();
// *========数据库日志=========*//
TblLog tblLog = new TblLog();
// 请求的地址
String ip = IpUtil.getIpAddress(request);
tblLog.setIpAddress(ip);
// 返回参数
// tblLog.setContent(JSONObject.toJSONString(o));
tblLog.setType(controllerLog.type());
tblLog.setUrl(ServletUtils.getRequest().getRequestURI());
tblLog.setStatus(0d);
tblLog.setCreateTime(LocalDateTime.now());
tblLog.setUpdateTime(LocalDateTime.now());
tblLog.setClientType(isMobile(request));
if (joinPoint.getArgs() != null && joinPoint.getArgs().length > 0) {
tblLog.setFuncParam(JSON.toJSONString(joinPoint.getArgs()[0]));
}
if (o != null) {
tblLog.setContent(JSON.toJSONString(o));
if (o instanceof AjaxResult) {
String status = ((AjaxResult) o).getError();
if ("true".equals(status)) {
tblLog.setStatus(0d);
} else {
tblLog.setStatus(1d);
}
}
}
if (e != null) {
//如果有异常
tblLog.setStatus(1d);
//tblLog.setErrorMsg(ExceptionUtils.getStackTrace(e)); 文本过大
tblLog.setErrorMsg(e.getMessage());
}
log.info("URL : {}", request.getRequestURL().toString());
log.info("HTTP_METHOD : {}", request.getMethod());
log.info("IP : {}", IpUtil.getIpAddress(request));
log.info("CLASS_METHOD : {}", joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
log.info("ARGS : {}", Arrays.toString(joinPoint.getArgs()));
log.info("RESPONSE : {}", o);
// 设置方法名称
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
tblLog.setFuncName(className + "." + methodName + "()");
// 保存数据库
saveLogData(tblLog);
} catch (Exception exp) {
// 记录本地异常日志
log.error("[日志服务]----日志记录日常,e->: {}", ExceptionUtils.getStackTrace(exp));
}
}
protected void saveLogData(TblLog tblLog) throws Exception {
tblLogMapper.insert(tblLog);
}
/**
* 判断是否为移动端
*
* @return 1:PC端 2.移动端
*/
private Double isMobile(HttpServletRequest request) {
String userAgent = request.getHeader("User-Agent");
if (StringUtils.isNotBlank(userAgent)) {
for (String mobileAgent : mobileAgents) {
if (userAgent.toLowerCase().contains(mobileAgent)) {
return 2d;
}
}
}
return 1d;
}
/**
* 是否存在注解,如果存在就获取
*/
private Log getAnnotationLog(JoinPoint joinPoint) throws Exception {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (method != null) {
return method.getAnnotation(Log.class);
}
return null;
}
}
用到得工具类
package com.cuslink.common.util;
import io.micrometer.core.instrument.util.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import javax.servlet.http.HttpServletRequest;
import java.util.Objects;
/**
* IP操作相关工具类
*/
public class IpUtil {
private IpUtil() {
}
/**
* 获取IP地址
*
* @param request 当前请求
* @return IP地址
*/
public static String getIpAddress(HttpServletRequest request) {
String xIp = request.getHeader("X-Real-IP");
String xFor = request.getHeader("X-Forwarded-For");
if (StringUtils.isNotEmpty(xFor) && !"unKnown".equalsIgnoreCase(xFor)) {
//多次反向代理后会有多个IP值,第一个IP才是真实IP
int index = xFor.indexOf(",");
if (index != -1) {
return xFor.substring(0, index);
} else {
return xFor;
}
}
xFor = xIp;
if (StringUtils.isNotEmpty(xFor) && !"unKnown".equalsIgnoreCase(xFor)) {
return xFor;
}
if (StringUtils.isBlank(xFor) || "unKnown".equalsIgnoreCase(xFor)) {
xFor = request.getHeader("Proxy-Client-IP");
}
if (StringUtils.isBlank(xFor) || "unKnown".equalsIgnoreCase(xFor)) {
xFor = request.getHeader("WL-Proxy-Client-IP");
}
if (StringUtils.isBlank(xFor) || "unKnown".equalsIgnoreCase(xFor)) {
xFor = request.getHeader("HTTP-CLIENT-IP");
}
if (StringUtils.isBlank(xFor) || "unKnown".equalsIgnoreCase(xFor)) {
xFor = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (StringUtils.isBlank(xFor) || "unKnown".equalsIgnoreCase(xFor)) {
xFor = request.getRemoteAddr();
}
return xFor;
}
public static String getIpAddress(ServerHttpRequest request){
HttpHeaders headers = request.getHeaders();
String xIp = headers.getFirst("X-Real-IP");
String xFor = headers.getFirst("X-Forwarded-For");
if (StringUtils.isNotEmpty(xFor) && !"unKnown".equalsIgnoreCase(xFor)) {
//多次反向代理后会有多个IP值,第一个IP才是真实IP
int index = xFor.indexOf(",");
if (index != -1) {
return xFor.substring(0, index);
} else {
return xFor;
}
}
xFor = xIp;
if (StringUtils.isNotEmpty(xFor) && !"unKnown".equalsIgnoreCase(xFor)) {
return xFor;
}
if (StringUtils.isBlank(xFor) || "unKnown".equalsIgnoreCase(xFor)) {
xFor = headers.getFirst("Proxy-Client-IP");
}
if (StringUtils.isBlank(xFor) || "unKnown".equalsIgnoreCase(xFor)) {
xFor = headers.getFirst("WL-Proxy-Client-IP");
}
if (StringUtils.isBlank(xFor) || "unKnown".equalsIgnoreCase(xFor)) {
xFor = headers.getFirst("HTTP-CLIENT-IP");
}
if (StringUtils.isBlank(xFor) || "unKnown".equalsIgnoreCase(xFor)) {
xFor = headers.getFirst("HTTP_X_FORWARDED_FOR");
}
if (StringUtils.isBlank(xFor) || "unKnown".equalsIgnoreCase(xFor)) {
xFor = Objects.requireNonNull(request.getRemoteAddress()).getAddress().getHostAddress();
}
return xFor;
}
}