记录内容
- 接口名称
- 浏览器名称
- 操作系统
- 请求ip
- 接口入参、出参
- 接口耗时
- 。。。。
表结构


SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for sys_log
-- ----------------------------
DROP TABLE IF EXISTS `sys_log`;
CREATE TABLE `sys_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`module_name` varchar(256) DEFAULT NULL COMMENT '模块名称',
`browser_name` varchar(1024) DEFAULT NULL COMMENT '浏览器名称',
`os_name` varchar(256) DEFAULT NULL COMMENT '操作系统名称',
`ip_addr` varchar(256) DEFAULT NULL COMMENT '请求ip',
`app_name` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '服务名称',
`class_name` varchar(1024) DEFAULT NULL COMMENT '类名',
`method_name` varchar(512) DEFAULT NULL COMMENT '方法',
`request_url` varchar(1024) DEFAULT NULL COMMENT '请求url',
`request_method` varchar(255) DEFAULT NULL COMMENT '请求方式,POST、GET',
`request_param` text COMMENT '请求参数',
`result_text` text CHARACTER SET utf8 COLLATE utf8_general_ci COMMENT '响应参数',
`status` tinyint(1) DEFAULT NULL COMMENT '接口状态(0成功 1失败)',
`error_text` text COMMENT '错误信息',
`take_up_time` varchar(64) DEFAULT NULL COMMENT '耗时',
`edit_table_id` bigint(20) DEFAULT NULL COMMENT '编辑的表主键,只有修改时才有值',
`edit_table_name` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '编辑的表名称,只有修改时才有值',
`create_time` datetime DEFAULT NULL COMMENT '操作时间',
`create_user_id` bigint(20) DEFAULT NULL COMMENT '创建人id',
`create_phone_number` varchar(11) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '创建人手机号',
`create_user_name` varchar(64) DEFAULT NULL COMMENT '创建人姓名',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='系统操作日志';
SET FOREIGN_KEY_CHECKS = 1;
sys_log.sql
添加依赖
<!-- AOP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<!-- 获取浏览器信息 -->
<dependency>
<groupId>eu.bitwalker</groupId>
<artifactId>UserAgentUtils</artifactId>
<version>1.21</version>
</dependency>
自定义注解(一)
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
}
备注:被该注解修饰的方法,会被记录到日志中
自定义注解(二)
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogPlus {
/**
* 编辑的表主键
* @return
*/
String editTableId() default "id";
/**
* 编辑的表名称
* @return
*/
String editTableName() default "未知";
}
备注:被该注解修饰的类,会记录从表的id值和从表的表名(用于记录某张表的一行记录,历史修改信息,不需要可忽略)
日志表实体类
import com.baomidou.mybatisplus.annotation.IdType;
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 java.io.Serializable;
import java.util.Date;
/**
* <p>
* 系统操作日志
* </p>
*
* @author chenyanbin
* @since 2021-10-13
*/
@Data
@TableName("sys_log")
@ApiModel(value = "SysLogDO对象", description = "系统操作日志")
public class LogDO implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "主键")
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@ApiModelProperty(value = "模块名称")
private String moduleName;
@ApiModelProperty(value = "浏览器名称")
private String browserName;
@ApiModelProperty(value = "操作系统名称")
private String osName;
@ApiModelProperty(value = "请求ip")
private String ipAddr;
@ApiModelProperty(value = "服务名称")
private String appName;
@ApiModelProperty(value = "类名")
private String className;
@ApiModelProperty(value = "方法")
private String methodName;
@ApiModelProperty(value = "请求url")
private String requestUrl;
@ApiModelProperty(value = "请求方式,POST、GET")
private String requestMethod;
@ApiModelProperty(value = "请求参数")
private String requestParam;
@ApiModelProperty(value = "响应参数")
private String resultText;
@ApiModelProperty(value = "接口状态(0成功 1失败)")
private Byte status;
@ApiModelProperty(value = "错误信息")
private String errorText;
@ApiModelProperty(value = "耗时")
private String takeUpTime;
@ApiModelProperty(value = "编辑的表主键,只有修改时才有值")
private Long editTableId;
@ApiModelProperty(value = "编辑的表名称,只有修改时才有值")
private String editTableName;
@ApiModelProperty(value = "操作时间")
private Date createTime;
@ApiModelProperty(value = "创建人id")
private Long createUserId;
@ApiModelProperty(value = "创建人手机号")
private String createPhoneNumber;
@ApiModelProperty(value = "创建人姓名")
private String createUserName;
}
Mapper.java
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public interface LogMapper extends BaseMapper<LogDO> {
}
Aspect (AOP)
import com.alibaba.fastjson.JSON;
import eu.bitwalker.useragentutils.Browser;
import eu.bitwalker.useragentutils.OperatingSystem;
import eu.bitwalker.useragentutils.UserAgent;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
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.beans.factory.annotation.Value;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import javax.servlet.http.HttpServletRequest;
/**
* 操作日志处理
*
* @Author:chenyanbin
*/
@Slf4j
@Aspect
@Component
public class LogAspect {
@Autowired
LogMapper logMapper;
@Value("${spring.application.name}")
private String appNname;
@Pointcut("@annotation(com.yida.annotation.Log)")
public void logPoint() {
}
@Around("logPoint()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
Object result = null;
LogDO logDO = new LogDO();
try {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
HttpServletRequest request = (HttpServletRequest) requestAttributes
.resolveReference(RequestAttributes.REFERENCE_REQUEST);
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
//浏览器对象
Browser browser = userAgent.getBrowser();
//操作系统对象
OperatingSystem operatingSystem = userAgent.getOperatingSystem();
logDO.setBrowserName(browser.getName());
ApiOperation aon = methodSignature.getMethod().getAnnotation(ApiOperation.class);
if (aon != null) {
logDO.setModuleName(aon.value());
}
logDO.setOsName(operatingSystem.getName());
logDO.setIpAddr(CommonUtil.getIpAddr(request));
logDO.setAppName(appNname);
logDO.setClassName(joinPoint.getTarget().getClass().getName());
logDO.setMethodName(methodSignature.getMethod().getName());
logDO.setRequestUrl(request.getRequestURI());
logDO.setRequestMethod(request.getMethod());
//获取请求参数
CommonUtil.getRequestParam(joinPoint, methodSignature, logDO);
logDO.setResultText(JSON.toJSONString(result));
logDO.setStatus((byte) 0);
logDO.setCreateTime(CommonUtil.getCurrentDate());
logDO.setCreateUserId(CommonUtil.getCurrentUserId());
logDO.setCreatePhoneNumber(CommonUtil.getCurrentPhoneNumber());
logDO.setCreateUserName(CommonUtil.getCurrentUserName());
long startTime = System.currentTimeMillis();
result = joinPoint.proceed();
long endTime = System.currentTimeMillis();
logDO.setTakeUpTime(String.format("耗时:%s 毫秒", endTime - startTime));
logDO.setResultText(result.toString());
} catch (Exception e) {
logDO.setStatus((byte) 1);
if (e instanceof BizException) {
BizException bizException = (BizException) e;
result = JsonData.buildCodeAndMsg(bizException.getCode(), bizException.getMessage());
logDO.setErrorText(result.toString());
} else if (e instanceof RpvException) {
RpvException ve = (RpvException) e;
result = JsonData.buildCodeAndMsg(ve.getCode(), ve.getMessage());
logDO.setErrorText(result.toString());
} else {
logDO.setErrorText(e.getMessage());
result = e.getMessage();
}
} finally {
logMapper.insert(logDO);
}
return result;
}
}
String agent=request.getHeader("User-Agent");
//解析agent字符串
UserAgent userAgent = UserAgent.parseUserAgentString(agent);
//获取浏览器对象
Browser browser = userAgent.getBrowser();
//获取操作系统对象
OperatingSystem operatingSystem = userAgent.getOperatingSystem();
System.out.println("浏览器名:"+browser.getName());
System.out.println("浏览器类型:"+browser.getBrowserType());
System.out.println("浏览器家族:"+browser.getGroup());
System.out.println("浏览器生产厂商:"+browser.getManufacturer());
System.out.println("浏览器使用的渲染引擎:"+browser.getRenderingEngine());
System.out.println("浏览器版本:"+userAgent.getBrowserVersion());
System.out.println("操作系统名:"+operatingSystem.getName());
System.out.println("访问设备类型:"+operatingSystem.getDeviceType());
System.out.println("操作系统家族:"+operatingSystem.getGroup());
System.out.println("操作系统生产厂商:"+operatingSystem.getManufacturer());
其他类

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.MessageDigest;
import java.util.*;
/**
* 公共工具类
*
* @Author:chenyanbin
*/
@Slf4j
public class CommonUtil {
private static Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
/**
* 获取ip
*
* @param request
* @return
*/
public static String getIpAddr(HttpServletRequest request) {
String ipAddress = null;
try {
ipAddress = request.getHeader("x-forwarded-for");
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("WL-Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getRemoteAddr();
if (ipAddress.equals("127.0.0.1")) {
// 根据网卡取本机配置的IP
InetAddress inet = null;
try {
inet = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
e.printStackTrace();
}
ipAddress = inet.getHostAddress();
}
}
// 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
if (ipAddress != null && ipAddress.length() > 15) {
// "***.***.***.***".length()
// = 15
if (ipAddress.indexOf(",") > 0) {
ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
}
}
} catch (Exception e) {
ipAddress = "";
}
return ipAddress;
}
/**
* 获取当前时间戳
*
* @return
*/
public static long getCurrentTimestamp() {
return System.currentTimeMillis();
}
/**
* 获取当前日期
*
* @return
*/
public static Date getCurrentDate() {
return new Date();
}
/**
* 获取当前操作用户的主键id
*
* @return
*/
public static Long getCurrentUserId() {
LoginUser loginUser = getLoginUser();
if (loginUser == null) {
return null;
}
return loginUser.getId();
}
private static LoginUser getLoginUser() {
return JwtFilter.threadLocal.get();
}
/**
* 获取当前操作用户的手机号
*
* @return
*/
public static String getCurrentPhoneNumber() {
LoginUser loginUser = getLoginUser();
if (loginUser == null) {
return null;
}
return loginUser.getPhoneNumber();
}
/**
* 获取当前操作用户的名称
*
* @return
*/
public static String getCurrentUserName() {
LoginUser loginUser = getLoginUser();
if (loginUser == null) {
return null;
}
return loginUser.getUserName();
}
/**
* 判断当前用户是否管理员
*/
public static boolean isAdmin() {
return getCurrentUserId() == 1;
}
/**
* 获取请求参数
*
* @param joinPoint 切入点
* @param signature 方法签名
* @param logDO 日志对象
*/
public static void getRequestParam(ProceedingJoinPoint joinPoint, MethodSignature signature, LogDO logDO) {
// 参数值
Object[] args = joinPoint.getArgs();
ParameterNameDiscoverer pnd = new DefaultParameterNameDiscoverer();
Method method = signature.getMethod();
String[] parameterNames = pnd.getParameterNames(method);
Map<String, Object> paramMap = new HashMap<>(32);
for (int i = 0; i < parameterNames.length; i++) {
paramMap.put(parameterNames[i], args[i]);
if (args[i] != null) {
//反射获取具体的值
LogPlus logPlus = args[i].getClass().getAnnotation(LogPlus.class);
if (logPlus != null) {
Field f = null;
try {
f = args[i].getClass().getDeclaredField(logPlus.editTableId());
f.setAccessible(true);
Object obj = f.get(args[i]);
logDO.setEditTableId(Long.valueOf(obj + ""));
logDO.setEditTableName(logPlus.editTableName());
} catch (Exception e) {
log.error("反射获取编辑的表主键异常:{}", e.getMessage());
} finally {
if (f != null) {
f.setAccessible(false);
}
}
}
}
}
logDO.setRequestParam(paramMap.toString());
}
}
CommonUtil.java

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/**
* 登录用户
* @Author:chenyanbin
*/
@Data
public class LoginUser {
/**
* 主键
*/
private Long id;
/**
* 手机号
*/
@JsonProperty("phone_number")
private String phoneNumber;
/**
* 用户昵称
*/
@JsonProperty("user_name")
private String userName;
/**
* 是否货主(0是 1否)
*/
@JsonProperty("cargo_master")
private byte cargoMaster;
/**
* 管理员 (0是 1否)
*/
private byte admin;
}
LoginUser.java

import lombok.Data;
/**
* 业务异常类
* @Author:chenyanbin
*/
@Data
public class BizException extends RuntimeException {
private int code;
private String message;
public BizException(int code, String message) {
super(message);
this.code = code;
this.message = message;
}
public BizException(BizCodeEnum bizCodeEnum) {
super(bizCodeEnum.getMessage());
this.code = bizCodeEnum.getCode();
this.message = bizCodeEnum.getMessage();
}
}
BizException.java

import lombok.Data;
/**
* Request Param Value:请求参数异常
* @Author:chenyanbin
*/
@Data
public class RpvException extends RuntimeException {
private int code;
private String message;
public RpvException(int code, String message) {
this.code = code;
this.message = message;
}
}
RpvException.java

package com.yida.utils;
import com.yida.enums.BizCodeEnum;
import java.io.Serializable;
/**
* @Description:统一协议JsonData工具类
* @Author:chenyanbin
* @Date:2021/5/9 下午8:09
* @Versiion:1.0
*/
public class JsonData<T> implements Serializable {
/**
* 状态码 0 表示成功,1表示处理中,-1表示失败
*/
private Integer code;
/**
* 数据
*/
private T data;
/**
* 描述
*/
private String msg;
private JsonData() {
}
private static <T> JsonData<T> build(Integer code, T data, String msg) {
JsonData json = new JsonData();
json.setCode(code);
json.setData(data);
json.setMsg(msg);
return json;
}
/**
* 成功,传⼊数据
*
* @return
*/
public static <T> JsonData<T> buildSuccess() {
return build(0, (T) "", "");
}
/**
* 默认添加成功
*
* @return
*/
public static <T> JsonData<T> buildAddSuccess() {
return build(0, (T) "添加成功", "");
}
/**
* 默认修改成功
*
* @return
*/
public static <T> JsonData<T> buildEditSuccess() {
return build(0, (T) "修改成功", "");
}
/**
* 默认删除成功
*
* @return
*/
public static <T> JsonData<T> buildRemoveSuccess() {
return build(0, (T) "删除成功", "");
}
/**
* 成功,传⼊数据
*
* @param data
* @return
*/
public static <T> JsonData<T> buildSuccess(T data) {
return build(0, data, "");
}
/**
* 失败,传⼊描述信息
*
* @param msg
* @return
*/
public static <T> JsonData<T> buildError(String msg) {
return build(-1, (T) "", msg);
}
/**
* ⾃定义状态码和错误信息
*
* @param code
* @param msg
* @return
*/
public static <T> JsonData<T> buildCodeAndMsg(int code, String msg) {
return build(code, null, msg);
}
/**
* 传⼊枚举,返回信息
*
* @param codeEnum
* @return
*/
public static <T> JsonData<T> buildResult(BizCodeEnum codeEnum) {
return buildCodeAndMsg(codeEnum.getCode(), codeEnum.getMessage());
}
/**
* 判断接口响应是否成功,只是判断状态码是否等于:0
*
* @param data
* @return
*/
public static boolean isSuccess(JsonData data) {
return data.getCode() == 0;
}
/**
* 判断接口响应是否失败,状态码除了0以外的,默认调用失败
*
* @param data
* @return
*/
public static boolean isFailure(JsonData data) {
return !isSuccess(data);
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
@Override
public String toString() {
return "JsonData{" +
"code=" + code +
", data=" + data +
", msg='" + msg + '\'' +
'}';
}
}
JsonData.java
控制器添加@Log注解
@ApiOperation("用户登录")
@PostMapping("login")
@Log
public JsonData login(
@ApiParam("用户登录对象") @RequestBody UserLoginRequest userLoginRequest
) {
return userService.login(userLoginRequest);
}
修改接口,json对象添加@LogPlus
@Data
@ApiModel(value = "角色编辑对象", description = "用户编辑请求对象")
@LogPlus(editTableId = "id", editTableName = "sys_role")
public class RoleEditRequest {
@ApiModelProperty(value = "主键id", example = "-1")
@DecimalMin(value = "1", message = "角色id最小为1")
private Long id;
@ApiModelProperty(value = "角色名称", example = "货主")
@JsonProperty("role_name")
@NotBlank(message = "角色名称不能为空")
private String roleName;
@ApiModelProperty(value = "角色状态(0正常 1停用)", example = "0")
@Min(value = 0, message = "角色状态(0正常 1停用)")
@Max(value = 1, message = "角色状态(0正常 1停用)")
private byte status;
@ApiModelProperty(value = "备注", example = "货主角色")
private String remark;
@ApiModelProperty(value = "菜单id列表", example = "[1,2,3,4]")
private List<Long> menuIds;
}
效果演示
