記錄內容
- 接口名稱
- 瀏覽器名稱
- 操作系統
- 請求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;
添加依賴
<!-- 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()); } }

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; }

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(); } }

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; } }

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 + '\'' + '}'; } }
控制器添加@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; }
效果演示