一、aop类
package com.skyable.common.aop;
import com.skyable.common.annotation.OperationLog;
import com.skyable.common.anscy.AsyncService;
import com.skyable.common.config.CloudApplicationContext;
import com.skyable.common.constants.param.CommonParams;
import com.skyable.common.dto.CommonDTO;
import com.skyable.common.dto.LogDTO;
import com.skyable.common.utils.IpAddressUtil;
import com.skyable.common.utils.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
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 java.util.Objects;
/**
* 类功能描述。
*
* @Filename: WebLogAcpect.java
* @Description:
* @Version: 1.0
* @Author: 老友
* @History: Author: GaoYuan/Date: 2022年11月07日/Version:1.0/Content: create
*/
@Aspect
@Component
@Slf4j
public class WebLogAspect {
private final AsyncService asyncService;
public WebLogAspect(AsyncService asyncService) {
this.asyncService = asyncService;
}
/**
* 定义切入点,切入点为com.skyable.device.controller下的所有函数
*/
@Pointcut("@annotation(com.skyable.common.annotation.OperationLog)")
public void webLog() {
}
/**
* 前置通知:在连接点之前执行的通知
*
* @param joinPoint
* @throws Throwable
*/
@Before("webLog()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
// 接收到请求,记录请求内容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
String methodName = joinPoint.getSignature().getName();
//设置ThreadLocal值
String domainId = request.getHeader(CommonParams.HEADER_USER_DOMAIN_ID);
if (!StringUtil.isEmpty(domainId)) {
CloudApplicationContext.domainId.set(Long.valueOf(domainId));
}
String userName = request.getHeader(CommonParams.HEADER_USER_NAME);
if (!StringUtil.isEmpty(userName)) {
CloudApplicationContext.user.set(userName);
}
CloudApplicationContext.joinPoint.set(joinPoint);
// 记录下请求内容
log.info("method[" + methodName + "]request path: " + request.getRequestURL().toString());
log.info("method[" + methodName + "]request method: " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
log.info("method[" + methodName + "]request param: " + Arrays.toString(joinPoint.getArgs()));
}
/**
* 后置通知
*
* @param joinPoint
* @param ret
* @throws Throwable
*/
@AfterReturning(returning = "ret", pointcut = "webLog()")
public void doAfterReturning(JoinPoint joinPoint, Object ret) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
OperationLog operationLog = signature.getMethod().getAnnotation(OperationLog.class);
saveLog(operationLog, 0, CommonDTO.INFO);
String methodName = joinPoint.getSignature().getName();
// 处理完请求,返回内容
log.info("method[" + methodName + "]return content: " + ret);
}
// /**
// * 环绕通知:
// * 环绕通知非常强大,可以决定目标method是否执行,什么时候执行,执行时是否需要替换method参数,执行完毕是否需要替换返回值。
// * 环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型
// */
// @Around(value = "webLog()")
// public Object doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
// String methodName = proceedingJoinPoint.getSignature().getName();
// long startTime = System.currentTimeMillis();
// Object obj = proceedingJoinPoint.proceed();
// long endTime = System.currentTimeMillis();
// log.info("method["+methodName+"]deal tine: " + (endTime - startTime) + " ms");
// return obj;
// }
/**
* 该注解标注的method在所有的Advice执行完成后执行,无论业务模块是否抛出异常,类似于finally的作用;
*
* @param joinPoint
*/
@After(value = "webLog()")
public void logEnd(JoinPoint joinPoint) {
//移除ThreadLocal值
removeThreadLocal();
String methodName = joinPoint.getSignature().getName();
log.info("method[" + methodName + "]remove thread local content");
}
private void removeThreadLocal() {
CloudApplicationContext.domainId.remove();
CloudApplicationContext.user.remove();
CloudApplicationContext.joinPoint.remove();
}
/**
* 异常通知
*
* @param ex
*/
@AfterThrowing(throwing = "ex", value = "webLog()")
public void logEnd(Exception ex) {
JoinPoint joinPoint = CloudApplicationContext.joinPoint.get();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
OperationLog operationLog = signature.getMethod().getAnnotation(OperationLog.class);
// 保存数据
saveLog(operationLog, 1, ex.getMessage());
}
/**
* 添加日志
*
* @param operationLog
* @param status
* @param failCause
*/
private void saveLog(OperationLog operationLog, Integer status, String failCause) {
try {
String username = null;
if (Objects.nonNull(CloudApplicationContext.user)) {
username = CloudApplicationContext.user.get();
}
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
String ipAddr = IpAddressUtil.getIpAddr(attributes.getRequest());
String requestURI = attributes.getRequest().getRequestURI();
log.info("日志url:{}", requestURI);
String module = attributes.getRequest().getRequestURI().split("/")[1];
LogDTO logDTO = new LogDTO();
logDTO.setFailCause(failCause)
.setIpAddr(ipAddr)
.setStatus(status)
.setLogDetails(operationLog.logDetails())
.setModule(module).setUsername(username)
.setOperate(operationLog.operate().getKey());
asyncService.saveLog(logDTO);
} catch (Exception e) {
removeThreadLocal();
}
}
}
二、自定义注解
package com.skyable.common.annotation;
import com.skyable.common.enums.OperationType;
import java.lang.annotation.*;
/**
* @author 老友
* @date 2022/12/10 15:19
* @desc 操作日志注解
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface OperationLog {
OperationType operate(); //操作类型
String logDetails(); //日志详情
}
三、操作日志枚举类
package com.skyable.common.enums;
/**
* @author 老友
* @date 2022/12/10 15:19
* @desc 操作日志枚举类
*/
public enum OperationType {
/**
* 操作类型
*/
UNKNOWN("unknown","未知操作"),
INSERT("insert","新增"),
DELETE("delete","删除"),
SELECT("select","查询"),
UPDATE("update","更新"),
LOGIN("login","用户登录-%s账户登录"),
LOGOUT("logout","登出"),
IMPORT("import","导入"),
EXPORT("export","导出"),
AUTH("auth","授权");
private String key;
private String value;
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
OperationType(String key, String value) {
this.key = key;
this.value = value;
}
}
四、日志结果类型
package com.skyable.common.enums;
/**
* @author 老友
* @title: LogType
* @projectName private-cloud
* @description: TODO
* @date 2022/11/211:06
*/
public enum LogType {
/**
* INFO
*/
INFO("info"),
/**
* WARNING
*/
WARNING("warning"),
/**
* DEBUG
*/
DEBUG("debug"),
/**
* ERROR
*/
ERROR("error"),;
private String logLevel;
public String getLogLevel() {
return logLevel;
}
public void setLogLevel(String logLevel) {
this.logLevel = logLevel;
}
LogType(String logLevel) {
this.logLevel = logLevel;
}
}
五、日志controller
package com.skyable.account.controller;
import com.skyable.account.entity.Log;
import com.skyable.account.service.ILogService;
import com.skyable.common.common.ResponseResult;
import com.skyable.common.dto.CommonDTO;
import com.skyable.common.dto.LogDTO;
import com.skyable.common.dto.LogPageDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Author : 老友
* @Date: 2022/011/14 10:56
* @Description: <描述>
*/
@RestController
@RequestMapping("/log")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
@Tag(name = "日志管理")
public class LogController {
private final ILogService logService;
@PostMapping("/saveLog")
@Schema(name = "添加日志")
public ResponseResult saveLog(@RequestBody LogDTO logDTO) {
return logService.saveLog(logDTO);
}
@PostMapping("/logPage")
@Schema(name = "查询日志分页")
public ResponseResult logPage(@RequestBody @Validated LogPageDTO logPageDTO) {
return logService.logPage(logPageDTO);
}
}
六、controller注解
/**
* 新增用户信息
*
* @param sysUser
* @return
*/
@Schema(description = "新增用户")
@PostMapping("/insert")
@OperationLog(operate = OperationType.INSERT, logDetails = "用户管理-新增用户")
public ResponseResult insertRole(@RequestBody @Validated SysUser sysUser, HttpServletRequest request) {
setInsertInfo(sysUser, request);
return userService.insertUser(sysUser);
七、LogDTO
@Data
@Accessors(chain = true)
public class LogDTO implements Serializable {
@Schema(name = "日志详情")
private String logDetails;
@Schema(name = "操作")
private String operate;
@Schema(name = "用户名")
private String username;
@Schema(name = "IP地址")
private String ipAddr;
@Schema(name = "状态 0 成功 1失败")
private Integer status;
@Schema(name = "失败原因")
private String failCause;
@Schema(name = "所属模块")
private String module;
}