pom.xml文件
org.springframework.boot
spring-boot-starter-aop
自定义注解记录系统操作日志
package com.central.user.aop;
import java.lang.annotation.*;
@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog
{
/**
* 模块标题
*/
String title() default "";
/**
* 日志内容
*/
String content() default "";
}
AOP工具类
@Aspect
@Component
public class OperLogAspect {
@Autowired
private IOperLogService operLogService;
//为了记录方法的执行时间
ThreadLocal startTime = new ThreadLocal<>();
@Pointcut("@annotation(com.central.user.aop.MyLog)")//在注解的位置切入代码
public void operLogPoinCut() {
}
@Before("operLogPoinCut()")
public void beforMethod(JoinPoint point){
startTime.set(System.currentTimeMillis());
}
/**
* 设置操作异常切入点记录异常日志 扫描所有DevelopController包下操作
* @Pointcut("execution(* com.central.user.controller..*.*(..))")扫描所有包
*/
@Pointcut("execution(* com.central.user.controller..DevelopController.*(..))")//扫描某个controller
public void operExceptionLogPoinCut() {
}
/**
* 正常返回通知,拦截用户操作日志,连接点正常执行完成后执行, 如果连接点抛出异常,则不会执行
*
* @param joinPoint 切入点
* @param result 返回结果
*/
@AfterReturning(value = "operLogPoinCut()", returning = "result")
public void saveOperLog(JoinPoint joinPoint, Object result) {
// 获取RequestAttributes
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
// 从获取RequestAttributes中获取HttpServletRequest的信息
HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
try {
// 从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取切入点所在的方法
Method method = signature.getMethod();
// 获取操作
MyLog myLog = method.getAnnotation(MyLog.class);
Synchronous operlog = new Synchronous();
if (myLog != null) {
operlog.setTitle(myLog.title());//设置模块名称
operlog.setContent(myLog.content());//设置日志内容
}
// 将入参转换成json
String params = argsArrayToString(joinPoint.getArgs());
// 获取请求的类名
String className = joinPoint.getTarget().getClass().getName();
// 获取请求的方法名
String methodName = method.getName();
methodName = className + "." + methodName + "()";
operlog.setMethod(methodName); //设置请求方法
operlog.setRequestMethod(request.getMethod());//设置请求方式
operlog.setRequestParam(params); // 请求参数
if(!"GET".equals(request.getMethod())) {
operlog.setResponseResult(JSON.toJSONString(result)); // 返回结果
}
operlog.setTenantId(request.getParameter("tenantId"));
operlog.setIp(getIp(request)); // IP地址
operlog.setRequestUrl(request.getRequestURI()); // 请求URI
operlog.setOperTime(new Date()); // 时间
operlog.setStatus(0);//操作状态(0正常 1异常)
Long takeTime = System.currentTimeMillis() - startTime.get();//记录方法执行耗时时间(单位:毫秒)
operlog.setTakeTime(takeTime);
//插入数据库
operLogService.saveIOperLog(operlog);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 异常返回通知,用于拦截异常日志信息 连接点抛出异常后执行
*/
@AfterThrowing(pointcut = "operExceptionLogPoinCut()", throwing = "e")
public void saveExceptionLog(JoinPoint joinPoint, Throwable e) {
// 获取RequestAttributes
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
// 从获取RequestAttributes中获取HttpServletRequest的信息
HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
Synchronous operlog = new Synchronous();
try {
// 从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取切入点所在的方法
Method method = signature.getMethod();
// 获取请求的类名
String className = joinPoint.getTarget().getClass().getName();
// 获取请求的方法名
String methodName = method.getName();
methodName = className + "." + methodName + "()";
// 获取操作
MyLog myLog = method.getAnnotation(MyLog.class);
if (myLog != null) {
operlog.setTitle(myLog.title());//设置模块名称
operlog.setContent(myLog.content());//设置日志内容
}
// 将入参转换成json
String params = argsArrayToString(joinPoint.getArgs());
operlog.setMethod(methodName); //设置请求方法
operlog.setRequestMethod(request.getMethod());//设置请求方式
operlog.setRequestParam(params); // 请求参数
operlog.setTenantId(request.getParameter("tenantId")); // 获取应用标识
operlog.setIp(getIp(request)); // IP地址
operlog.setRequestUrl(request.getRequestURI()); // 请求URI
operlog.setOperTime(new Date()); // 时间
operlog.setStatus(1);//操作状态(0正常 1异常)
operlog.setErrorMsg(stackTraceToString(e.getClass().getName(), e.getMessage(), e.getStackTrace()));//记录异常信息
//插入数据库
operLogService.saveIOperLog(operlog);
} catch (Exception e2) {
e2.printStackTrace();
}
}
/**
* 转换异常信息为字符串
*/
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();
message = substring(message,0 ,2000);
return message;
}
/**
* 参数拼装
*/
private String argsArrayToString(Object[] paramsArray)
{
String params = "";
if (paramsArray != null && paramsArray.length > 0)
{
for (Object o : paramsArray)
{
if (o != null)
{
try
{
Object jsonObj = JSON.toJSON(o);
params += jsonObj.toString() + " ";
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
}
return params.trim();
}
//字符串截取
public static String substring(String str, int start, int end) {
if (str == null) {
return null;
} else {
if (end < 0) {
end += str.length();
}
if (start < 0) {
start += str.length();
}
if (end > str.length()) {
end = str.length();
}
if (start > end) {
return "";
} else {
if (start < 0) {
start = 0;
}
if (end < 0) {
end = 0;
}
return str.substring(start, end);
}
}
}
/**
* 转换request 请求参数
* @param paramMap request获取的参数数组
*/
public Map converMap(Map paramMap) {
Map returnMap = new HashMap<>();
for (String key : paramMap.keySet()) {
returnMap.put(key, paramMap.get(key)[0]);
}
return returnMap;
}
//根据HttpServletRequest获取访问者的IP地址
public static String getIp(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}
日志管理实体类
@Data
public class Synchronous extends Model {
private static final long serialVersionUID = 1L;
/**
* 日志主键
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 模块标题
*/
private String title;
/**
* 日志内容
*/
private String content;
/**
* 方法名称
*/
private String method;
/**
* 请求方式
*/
private String requestMethod;
/**
* 应用标识
*/
private String tenantId;
/**
* 请求URL
*/
private String requestUrl;
/**
* 请求IP地址
*/
private String ip;
/**
* 请求参数
*/
private String requestParam;
/**
* 方法响应参数
*/
private String responseResult;
/**
* 操作状态(0正常 1异常)
*/
private Integer status;
/**
* 错误消息
*/
private String errorMsg;
/**
* 操作时间
*/
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date operTime;
/**
* 方法执行耗时(单位:毫秒)
*/
private Long takeTime;
}
mysql数据库脚本
CREATE TABLE `sys_synchronous_log` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '日志主键',
`title` varchar(50) DEFAULT '' COMMENT '模块标题',
`content` varchar(100) DEFAULT NULL COMMENT '日志内容',
`method` varchar(100) DEFAULT '' COMMENT '方法名称',
`request_method` varchar(10) DEFAULT '' COMMENT '请求方式',
`tenant_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '' COMMENT '应用系统标识',
`request_url` varchar(255) DEFAULT '' COMMENT '请求URL',
`ip` varchar(128) DEFAULT '' COMMENT '请求IP地址',
`request_param` varchar(2000) DEFAULT '' COMMENT '请求参数',
`response_result` varchar(2000) DEFAULT '' COMMENT '方法响应参数',
`status` int DEFAULT NULL COMMENT '操作状态(0正常 1异常)',
`error_msg` varchar(2000) DEFAULT NULL COMMENT '错误消息',
`oper_time` datetime DEFAULT NULL COMMENT '操作时间',
`take_time` bigint DEFAULT NULL COMMENT '方法执行耗时(单位:毫秒)',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COMMENT='同步接口操作日志记录';
达梦数据库脚本
CREATE TABLE "USER_CENTER"."SYS_SYNCHRONOUS_LOG"
(
"ID" BIGINT IDENTITY(1, 1) NOT NULL,
"TITLE" VARCHAR(500) DEFAULT '',
"CONTENT" VARCHAR(500),
"METHOD" VARCHAR(500) DEFAULT '',
"REQUEST_METHOD" VARCHAR(500) DEFAULT '',
"TENANT_ID" VARCHAR(500) DEFAULT '',
"REQUEST_URL" VARCHAR(500) DEFAULT '',
"IP" VARCHAR(500) DEFAULT '',
"REQUEST_PARAM" VARCHAR(4000) DEFAULT '',
"RESPONSE_RESULT" VARCHAR(4000) DEFAULT '',
"STATUS" INT,
"ERROR_MSG" VARCHAR(4000),
"OPER_TIME" TIMESTAMP(0),
"TAKE_TIME" BIGINT,
NOT CLUSTER PRIMARY KEY("ID")) STORAGE(ON "MAIN", CLUSTERBTR) ;
COMMENT ON TABLE "USER_CENTER"."SYS_SYNCHRONOUS_LOG" IS '同步接口操作日志记录';
COMMENT ON COLUMN "USER_CENTER"."SYS_SYNCHRONOUS_LOG"."CONTENT" IS '日志内容';
COMMENT ON COLUMN "USER_CENTER"."SYS_SYNCHRONOUS_LOG"."ERROR_MSG" IS '错误消息';
COMMENT ON COLUMN "USER_CENTER"."SYS_SYNCHRONOUS_LOG"."ID" IS '日志主键';
COMMENT ON COLUMN "USER_CENTER"."SYS_SYNCHRONOUS_LOG"."IP" IS '请求IP地址';
COMMENT ON COLUMN "USER_CENTER"."SYS_SYNCHRONOUS_LOG"."METHOD" IS '方法名称';
COMMENT ON COLUMN "USER_CENTER"."SYS_SYNCHRONOUS_LOG"."OPER_TIME" IS '操作时间';
COMMENT ON COLUMN "USER_CENTER"."SYS_SYNCHRONOUS_LOG"."REQUEST_METHOD" IS '请求方式';
COMMENT ON COLUMN "USER_CENTER"."SYS_SYNCHRONOUS_LOG"."REQUEST_PARAM" IS '请求参数';
COMMENT ON COLUMN "USER_CENTER"."SYS_SYNCHRONOUS_LOG"."REQUEST_URL" IS '请求URL';
COMMENT ON COLUMN "USER_CENTER"."SYS_SYNCHRONOUS_LOG"."RESPONSE_RESULT" IS '方法响应参数';
COMMENT ON COLUMN "USER_CENTER"."SYS_SYNCHRONOUS_LOG"."STATUS" IS '操作状态(0正常 1异常)';
COMMENT ON COLUMN "USER_CENTER"."SYS_SYNCHRONOUS_LOG"."TAKE_TIME" IS '方法执行耗时(单位:毫秒)';
COMMENT ON COLUMN "USER_CENTER"."SYS_SYNCHRONOUS_LOG"."TENANT_ID" IS '应用系统标识';
COMMENT ON COLUMN "USER_CENTER"."SYS_SYNCHRONOUS_LOG"."TITLE" IS '模块标题';
CREATE UNIQUE INDEX "INDEX189491221572200" ON "USER_CENTER"."SYS_SYNCHRONOUS_LOG"("ID" ASC) STORAGE(ON "MAIN", CLUSTERBTR) ;
mapper层
@Mapper
public interface IOperLogMapper {
int saveIOperLog(Synchronous synchronous);
}
service层
public interface IOperLogService {
int saveIOperLog(Synchronous synchronous);
}
service实现层
@Service
public class IOperLogServiceImpl implements IOperLogService {
@Autowired
IOperLogMapper iOperLogMapper;
@Override
public int saveIOperLog(Synchronous synchronous) {
return iOperLogMapper.saveIOperLog(synchronous);
}
controller层
@RestController
@RequestMapping("/ssss")
public class DevelopController {
@Autowired
DevelopService developService;
@PostMapping("/saveUserData")
@MyLog(title = "新增用户账号", content = "新增用户账号")
public ResponseResult saveUserData(@RequestBody SysUser sysUser){
return developService.saveUserData(sysUser);
}
}
sql插入语句
insert into sys_synchronous_log
title,
content,
method,
request_method,
tenant_id,
request_url,
ip,
request_param,
response_result,
status,
error_msg,
oper_time,
take_time,
#{title},
#{content},
#{method},
#{requestMethod},
#{tenantId},
#{requestUrl},
#{ip},
#{requestParam},
#{responseResult},
#{status},
#{errorMsg},
#{operTime},
#{takeTime},