文章目录
Spring Boot 通过AOP+自定义注解实现日志管理
操作日志的记录:
记录用户的行为(Controller 请求日志),这叫做业务运行日志
业务运行日志的作用:
1,记录用户的行为 用于后续的分析
2,记录用户的所有的操作
1:操作日志数据结构表
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS `xcy_operation_log`;
CREATE TABLE `xcy_operation_log` (
`id` bigint(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '操作日志自增id',
`user_id` bigint(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '操作人id',
`ad_account` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '操作人AD账号',
`operation_ip` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '请求ip地址',
`operation_model` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '操作模块',
`operation_type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '操作类型',
`operation_desc` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '操作描述',
`operation_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '操作时间',
`operation_uri` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '请求uri',
`operation_method` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '请求方法',
`request_param` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '请求参数',
`response_param` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '响应参数',
`result` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '操作结果',
`create_time` datetime(0) NOT NULL COMMENT '创建时间',
`update_time` datetime(0) NOT NULL COMMENT '修改时间',
PRIMARY KEY (`id`) USING BTREE,
INDEX `user_id`(`user_id`) USING BTREE,
INDEX `operation_model`(`operation_model`) USING BTREE,
INDEX `ad_account`(`ad_account`) USING BTREE,
INDEX `id`(`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '操作日志表' ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
2:操作日志实体类
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("xcy_operation_log")
public class OperationLog implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 操作日志自增id
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 操作人id
*/
private Long userId;
/**
* 操作人AD账号
*/
private String adAccount;
/**
* 请求ip地址
*/
private String operationIp;
/**
* 操作模块
*/
private String operationModel;
/**
* 操作类型
*/
private String operationType;
/**
* 操作描述
*/
private String operationDesc;
/**
* 操作时间
*/
private LocalDateTime operationTime;
/**
* 请求uri
*/
private String operationUri;
/**
* 请求方法
*/
private String operationMethod;
/**
* 请求参数
*/
private String requestParam;
/**
* 响应参数
*/
private String responseParam;
/**
* 操作结果
*/
private String result;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 修改时间
*/
private LocalDateTime updateTime;
}
3:操作日志服务类
public interface OperationLogService extends IService<OperationLog> {
}
4:操作日志实现类
@Service
public class OperationLogServiceImpl extends ServiceImpl<OperationLogMapper, OperationLog> implements OperationLogService {
}
5:mapper接口层
public interface OperationLogMapper extends BaseMapper<OperationLog> {
}
6:mapper.xml层
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xc.contract.dao.mapper.OperationLogMapper">
</mapper>
7:操作日志注解类
@Target(ElementType.METHOD)//注解放置的目标位置即方法级别
@Retention(RetentionPolicy.RUNTIME)//注解在哪个阶段执行
@Documented
public @interface OperLog {
/**
* 操作模块
* @return
*/
String module() default "";
/**
* 操作类型
* @return
*/
String type() default "";
/**
* 操作说明
* @return
*/
String desc() default "";
}
8:操作日志切面处理类
@Slf4j
@Aspect
@Component
public class OperationLogAspect {
@Autowired
private OperationLogService logService;
/**
* 设置操作日志切入点 记录操作日志 在注解的位置切入代码
*/
@Pointcut("@annotation(OperLog)")
public void operLogPoinCut() {}
/**
* 正常返回通知,拦截用户操作日志,连接点正常执行完成后执行, 如果连接点抛出异常,则不会执行
* @param joinPoint 切入点
* @param data 返回结果
*/
@AfterReturning(value = "operLogPoinCut()", returning = "data")
public void saveOperLog(JoinPoint joinPoint, Object data) {
SessionPayload sessionPayload = SessionPayload.current();
// 获取RequestAttributes
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
// 从获取RequestAttributes中获取HttpServletRequest的信息
HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
//操作日志
WqOperationLog operationLog = new WqOperationLog();
try{
// 从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//获取切入点所在的方法
Method method = signature.getMethod();
OperLog annotation = method.getAnnotation(OperLog.class);
if (annotation!=null){
//操作模块
operationLog.setOperationModel(annotation.module());
//操作类型
operationLog.setOperationType(annotation.type());
//操作描述
operationLog.setOperationDesc(annotation.desc());
}
//获取请求的类名
String className = joinPoint.getTarget().getClass().getName();
String methodName = method.getName();
//请求的方法名
methodName = className+"."+methodName;
//操作方法
operationLog.setOperationMethod(methodName);
//请求参数
operationLog.setRequestParam(getRequestParams(joinPoint.getArgs()));
//响应参数
operationLog.setResponseParam(JsonUtil.toJson(data));
//操作员
operationLog.setUserId(sessionPayload.getUserId());
operationLog.setAccount(sessionPayload.getAccount());
operationLog.setOperationTime(LocalDateTime.now());
//请求uri
operationLog.setOperationUri(request.getRequestURI());
//请求ip
operationLog.setOperationIp(IpAddressUtil.getIpAddr(request));
//操作结果
ReturnData returnData = (ReturnData) data;
if (returnData!=null){
operationLog.setResult(returnData.getMessage());
}else {
operationLog.setResult("");
}
//创建时间
operationLog.setCreateTime(LocalDateTime.now());
//修改时间
operationLog.setUpdateTime(LocalDateTime.now());
//保存日志
logService.save(operationLog);
}catch (Exception e){
log.error(e.getMessage());
}
}
/**
* 转换request参数
* @param paramsArray
* @return
*/
public String getRequestParams(Object[] paramsArray) throws Exception{
String params = "";
if (paramsArray != null && paramsArray.length > 0) {
for (Object o : paramsArray) {
String jsonObj = JSONUtil.toJsonStr(o);
params += jsonObj.toString()+"";
}
}
return params.trim();
}
}
IP地址工具类
public class IpAddressUtil {
public static String getIpAddr(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;
}
}
9:RequestWrapper类(无需)
RequestWrapper继承了HttpServletRequestWrapper,初始化的时候读取Request的整个请求体。然后重载了getInputStream和getReader方法,这两个方法会从类变量body中读取内容
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
public class RequestWrapper extends HttpServletRequestWrapper {
private final String body;
public RequestWrapper(HttpServletRequest request) {
super(request);
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = null;
InputStream inputStream = null;
try {
inputStream = request.getInputStream();
if (inputStream != null) {
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
char[] charBuffer = new char[128];
int bytesRead = -1;
while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
stringBuilder.append(charBuffer, 0, bytesRead);
}
} else {
stringBuilder.append("");
}
} catch (IOException ex) {
} finally {
if (inputStream != null) {
try {
inputStream.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
if (bufferedReader != null) {
try {
bufferedReader.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
}
body = stringBuilder.toString();
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
ServletInputStream servletInputStream = new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
@Override
public int read() throws IOException {
return byteArrayInputStream.read();
}
};
return servletInputStream;
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
public String getBody() {
return this.body;
}
}
10:使用你定义的自定义注解