springBoot AOP記錄操作日志和異常日志
1.創建日志表
-- ----------------------------
-- Table structure for sys_log
-- ----------------------------
DROP TABLE IF EXISTS "public"."sys_log";
CREATE TABLE "public"."sys_log" (
"id" varchar(64) COLLATE "pg_catalog"."default" NOT NULL,
"thread" int4,
"ip" varchar(30) COLLATE "pg_catalog"."default",
"uri" varchar(100) COLLATE "pg_catalog"."default",
"operate_module" varchar(100) COLLATE "pg_catalog"."default",
"operate_type" varchar(50) COLLATE "pg_catalog"."default",
"operate_method" varchar(100) COLLATE "pg_catalog"."default",
"operate_desc" varchar(500) COLLATE "pg_catalog"."default",
"request_state" int4,
"request_param" varchar(500) COLLATE "pg_catalog"."default",
"response_param" varchar(1000) COLLATE "pg_catalog"."default",
"exception_name" varchar(255) COLLATE "pg_catalog"."default",
"exception_message" text COLLATE "pg_catalog"."default",
"create_time" timestamp(6),
"user_id" varchar(50) COLLATE "pg_catalog"."default",
"user_name" varchar(255) COLLATE "pg_catalog"."default",
"session_time" int4
)
;
COMMENT ON COLUMN "public"."sys_log"."id" IS 'id';
COMMENT ON COLUMN "public"."sys_log"."thread" IS '線程id';
COMMENT ON COLUMN "public"."sys_log"."ip" IS 'ip';
COMMENT ON COLUMN "public"."sys_log"."uri" IS '請求連接';
COMMENT ON COLUMN "public"."sys_log"."operate_module" IS '功能模塊';
COMMENT ON COLUMN "public"."sys_log"."operate_type" IS '操作類型';
COMMENT ON COLUMN "public"."sys_log"."operate_method" IS '操作方法';
COMMENT ON COLUMN "public"."sys_log"."operate_desc" IS '操作描述';
COMMENT ON COLUMN "public"."sys_log"."request_state" IS '請求狀態(1正常 2異常)';
COMMENT ON COLUMN "public"."sys_log"."request_param" IS '請求參數';
COMMENT ON COLUMN "public"."sys_log"."response_param" IS '返回參數';
COMMENT ON COLUMN "public"."sys_log"."exception_name" IS '異常名稱';
COMMENT ON COLUMN "public"."sys_log"."exception_message" IS '異常信息';
COMMENT ON COLUMN "public"."sys_log"."create_time" IS '操作時間';
COMMENT ON COLUMN "public"."sys_log"."user_id" IS '用戶id';
COMMENT ON COLUMN "public"."sys_log"."user_name" IS '用戶名';
COMMENT ON COLUMN "public"."sys_log"."session_time" IS '請求時長(秒)';
-- ----------------------------
-- Primary Key structure for table sys_log
-- ----------------------------
ALTER TABLE "public"."sys_log" ADD CONSTRAINT "sys_log_pkey" PRIMARY KEY ("id");
2. 添加maven依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
3.創建操作日志注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperateLog {
// 操作模塊
String operateModul() default "";
// 操作類型
String operateType() default "";
// 操作說明
String operateDesc() default "";
}
4.對應數據庫的實體類
@Data
@TableName("sys_log")
public class SysLog implements Serializable {
private static final long serialVersionUID = 115492983341579610L;
/** id **/
@TableId(value = "id",type = IdType.ASSIGN_UUID)
private String id;
/** 線程id **/
private Integer thread;
/** ip **/
private String ip;
/** 請求連接 **/
private String uri;
/** 功能模塊 **/
private String operateModule;
/** 操作類型 **/
private String operateType;
/** 操作方法 **/
private String operateMethod;
/** 操作描述 **/
private String operateDesc;
/** 請求狀態(1正常 2異常) **/
private Integer requestState;
/** 請求參數 **/
private String requestParam;
/** 返回參數 **/
private String responseParam;
/** 異常名稱 **/
private String exceptionName;
/** 異常信息 **/
private String exceptionMessage;
/** 操作時間 **/
private Date createTime;
/** 用戶id **/
private String userId;
/** 用戶名 **/
private String userName;
/** 請求時長(秒) **/
private int sessionTime;
}
5.創建切面類來記錄日志
@Aspect
@Component
public class OperateLogAspect {
@Autowired
private SysLogMapper sysLogMapper;
private static ThreadLocal<Long> startTime = new ThreadLocal<Long>();
// 設置操作日志切入點 記錄操作日志 在注解的位置切入代碼
@Pointcut("@annotation(com.gisquest.entity.OperateLog)")
public void operatePointCut() {
}
/**
* 設置操作異常切入點記錄異常日志 掃描所有controller包下操作
*/
@Pointcut("execution(* com.gisquest.controller..*.*(..))")
public void operateExceptionPoinCut() {
}
/**
* 前置通知
* 獲取開始的毫秒值
*/
@Before("operatePointCut()||operateExceptionPoinCut()")
public void doBefore() {
startTime.set(System.currentTimeMillis());
}
/**
* 返回通知
* 攔截用戶操作日志,連接點正常執行完成后執行, 如果連接點拋出異常,則不會執行
*
* @param joinPoint 切入點
* @param keys 返回結果
*/
@AfterReturning(value = "operatePointCut()", returning = "keys")
public void saveOperLog(JoinPoint joinPoint, Object keys) {
saveLogInfo(joinPoint, keys, null);
}
/**
* 異常返回通知,用於攔截異常日志信息 連接點拋出異常后執行
*
* @param joinPoint 切入點
* @param e 異常信息
*/
@AfterThrowing(pointcut = "operateExceptionPoinCut()", throwing = "e")
public void saveExceptionLog(JoinPoint joinPoint, Throwable e) {
saveLogInfo(joinPoint, null, e);
}
/**
* 保存日志信息
*
* @param joinPoint 切點
* @param keys 返回值
* @param e 異常類
*/
public void saveLogInfo(JoinPoint joinPoint, Object keys, Throwable e) {
// 獲取RequestAttributes
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
// 從獲取RequestAttributes中獲取HttpServletRequest的信息
HttpServletRequest request = (HttpServletRequest) requestAttributes
.resolveReference(RequestAttributes.REFERENCE_REQUEST);
SysLog sysLog = new SysLog();
try {
// 從切面織入點處通過反射機制獲取織入點處的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 獲取切入點所在的方法
Method method = signature.getMethod();
// 獲取請求的類名
String className = joinPoint.getTarget().getClass().getName();
// 獲取請求的方法名
String methodName = method.getName();
methodName = className + "." + methodName;
sysLog.setOperateMethod(methodName); // 請求方法
// 請求的參數
Map<String, String> rtnMap = converMap(request.getParameterMap());
// 將參數所在的數組轉換成json
String params = JSON.toJSONString(rtnMap);
// 獲取操作
OperateLog opLog = method.getAnnotation(OperateLog.class);
if (opLog != null) {
String operModul = opLog.operateModul();
String operType = opLog.operateType();
String operDesc = opLog.operateDesc();
sysLog.setOperateModule(operModul);
sysLog.setOperateType(operType);
sysLog.setOperateDesc(operDesc);
}
if (e != null) {
// 異常名稱
sysLog.setExceptionName(e.getClass().getName());
// 異常信息
sysLog.setExceptionMessage(stackTraceToString(e.getClass().getName(), e.getMessage(), e.getStackTrace()));
}
//獲取線程id
int threadId = (int) Thread.currentThread().getId();
sysLog.setThread(threadId);
sysLog.setRequestParam(params);
sysLog.setResponseParam(JSON.toJSONString(keys));
sysLog.setUserId(UserUtils.userId()); // 這邊自己公司的工具類
sysLog.setUserName(UserUtils.nickname());// 你們可把用戶信息放在Request中獲取
sysLog.setIp(getIpAddressa(request));
sysLog.setUri(request.getRequestURI());
sysLog.setCreateTime(new Date());
sysLog.setRequestState(e == null ? 1 : 2);
sysLog.setSessionTime((int) ((System.currentTimeMillis() - startTime.get()) / 1000));
//插入數據到數據庫
sysLogMapper.insert(sysLog);
} catch (Exception exception) {
exception.printStackTrace();
}
}
/**
* 轉換request 請求參數
*
* @param paramMap request獲取的參數數組
*/
public Map<String, String> converMap(Map<String, String[]> paramMap) {
Map<String, String> rtnMap = new HashMap<String, String>();
for (String key : paramMap.keySet()) {
rtnMap.put(key, paramMap.get(key)[0]);
}
return rtnMap;
}
/**
* 轉換異常信息為字符串
*
* @param exceptionName 異常名稱
* @param exceptionMessage 異常信息
* @param elements 堆棧信息
*/
public String stackTraceToString(String exceptionName, String exceptionMessage, StackTraceElement[] elements) {
StringBuffer strbuff = new StringBuffer();
for (StackTraceElement stet : elements) {
strbuff.append(stet + "\n");
}
String message = exceptionName + ":" + exceptionMessage + "\n\t" + strbuff.toString();
return message;
}
/**
* 獲取IP
*
* @param request
* @return
*/
public static String getIpAddressa(HttpServletRequest request) {
String Xip = request.getHeader("X-Real-IP");
String XFor = request.getHeader("X-Forwarded-For");
//多次反向代理后會有多個ip值,第一個ip才是真實ip
if (StringUtils.isNotEmpty(XFor) && !"unKnown".equalsIgnoreCase(XFor)) {
int index = XFor.indexOf(",");
if (index != -1) {
return "0:0:0:0:0:0:0:1".equals(XFor.substring(0, index)) ? "127.0.0.1" : XFor.substring(0, index);
} else {
return "0:0:0:0:0:0:0:1".equals(XFor) ? "127.0.0.1" : XFor;
}
}
XFor = Xip;
if (StringUtils.isNotEmpty(XFor) && !"unKnown".equalsIgnoreCase(XFor))
return "0:0:0:0:0:0:0:1".equals(XFor) ? "127.0.0.1" : 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 "0:0:0:0:0:0:0:1".equals(XFor) ? "127.0.0.1" : XFor;
}
}
6.在Controller中添加注解
@OperateLog(operateModul = "模塊名", operateType = "查詢" ,operateDesc = "描述")
@ApiOperation("通過名字獲取詳細信息") //swagger注解
@GetMapping("selectByName/{name}")
public Result selectByName(@PathVariable("name") String name) {
return baseInfoService.selectByName(name);
}