定义数据表:
CREATE TABLE `sys_oper_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`username` varchar(255) DEFAULT NULL,
`title` varchar(255) DEFAULT NULL COMMENT '模块标题',
`business_type` tinyint(4) DEFAULT NULL COMMENT '业务类型(1新增,2修改,3删除,4查询,5上传)',
`url` varchar(255) DEFAULT NULL COMMENT '路径名称',
`opt_param` varchar(5000) DEFAULT NULL COMMENT '参数',
`status` tinyint(4) DEFAULT NULL COMMENT '操作状态(0正常 1异常)',
`error_msg` varchar(255) DEFAULT NULL COMMENT '错误消息',
`oper_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '操作时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=68 DEFAULT CHARSET=utf8mb4;
2.引入依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.9</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
自定义日志注解:
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperationUserLog {
// 自定义模块名
String title() default "";
// 方法传入的参数
String optParam() default "";
// 操作类型,eg:INSERT, UPDATE...
BusinessType businessType() default BusinessType.OTHER;
}
枚举类:
public enum BusinessType {
// 查找
SELECT,
// 新增
INSERT,
// 修改
UPDATE,
// 删除
DELETE,
//上传
UPLOAD,
}
自定义切面:
/**
* @author liz
*/
@Order(1)
@Aspect
@Component
@Log4j2
public class OperationLogAspect {
@Autowired
private SysOperLogService sysOperLogService;
//定义切点 这个切点是注解类,通过 @annotation 来定义这个切点监控是注解类。
@Pointcut("@annotation(com.*.*.annotation.OperationUserLog)")
public void logPointCut() {}
/**
* 处理完请求后执行
* @param joinPoint 切点
*/
@AfterReturning(pointcut = "logPointCut()")
public void doAfterReturning(JoinPoint joinPoint) {
handleLog(joinPoint, null);
}
/**
* 拦截异常操作
* @param joinPoint 切点
* @param e 异常
*/
@AfterThrowing(value = "logPointCut()", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
handleLog(joinPoint, e);
}
private void handleLog(final JoinPoint joinPoint, final Exception e) {
try {
String adminName = "";
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
adminName = request.getAttribute("adminName")==null?"系统调用":request.getAttribute("adminName").toString();
}
OperationUserLog controllerLog = getAnnotationLog(joinPoint);
if (controllerLog == null) {
return;
}
SysOperLog operLog = new SysOperLog();
operLog.setStatus(0);
String uri = UserUrlUtils.getUri();
operLog.setUrl(uri);
operLog.setUsername(adminName);
// 处理注解上的参数
getControllerMethodDescription(joinPoint, controllerLog, operLog);
// 保存数据库
sysOperLogService.addOperlog(operLog.getTitle(), operLog.getBusinessType(), operLog.getUrl(), operLog.getStatus(), operLog.getOptParam(), operLog.getErrorMsg(),operLog.getUsername());
}catch (Exception exception){
log.error("操作日志保存异常"+exception);
}
}
/**
* 是否存在注解@OperationUserLog,如果存在就获取,不存在则返回null
* @param joinPoint
* @return
*/
private OperationUserLog getAnnotationLog(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (method != null) {
return method.getAnnotation(OperationUserLog.class);
}
return null;
}
/**
* 获取Controller层上OperationUserLog注解中对方法的描述信息
*/
private void getControllerMethodDescription(JoinPoint joinPoint, OperationUserLog myLog, SysOperLog operLog) {
// 设置业务类型(0其它 1新增 2修改 3删除)
operLog.setBusinessType(myLog.businessType().ordinal());
// 设置模块标题
operLog.setTitle(myLog.title());
// 对方法上的参数进行处理,处理完:userName=xxx,password=xxx
String optParam = getAnnotationValue(joinPoint, myLog.optParam());
operLog.setOptParam(optParam);
}
/**
* 对方法上的参数进行处理
* @param joinPoint
* @param name
* @return
*/
private String getAnnotationValue(JoinPoint joinPoint, String name) {
String paramName = name;
// 获取方法中所有的参数
Map<String, Object> params = getParams(joinPoint);
// 参数是否是动态的:#{paramName}
if (paramName.matches("^#\\{[^}]*\\}(,#[^}]*\\})*$")) {
// 获取参数名,去掉#{ }
paramName = paramName.replace("#{", "").replace("}", "");
// 是否是复杂的参数类型:对象.参数名
if (paramName.contains(".")) {
String[] split = paramName.split("\\.");
// 获取方法中对象的内容
Object object = getValue(params, split[0]);
// 转换为JsonObject
JSONObject jsonObject = (JSONObject) JSONObject.toJSON(object);
if(jsonObject!=null){
// 获取值
Object o = jsonObject.get(split[1]);
return String.valueOf(o);
}
} else {// 简单的动态参数直接返回
StringBuilder str = new StringBuilder();
String[] paraNames = paramName.split(",");
for (String paraName : paraNames) {
String val = String.valueOf(getValue(params, paraName));
// 组装成 userName=xxx,password=xxx,
str.append(paraName).append("=").append(val).append(",");
}
// 去掉末尾的,
if (str.toString().endsWith(",")) {
return str.substring(0, str.length() - 1);
} else {
return str.toString();
}
}
}
// 非动态参数直接返回
return name;
}
/**
* 获取方法上的所有参数,返回Map类型, eg: 键:"userName",值:xxx 键:"password",值:xxx
* @param joinPoint
* @return
*/
public Map<String, Object> getParams(JoinPoint joinPoint) {
Map<String, Object> params = new HashMap<>(8);
// 通过切点获取方法所有参数值["zhangsan", "123456"]
Object[] args = joinPoint.getArgs();
// 通过切点获取方法所有参数名 eg:["userName", "password"]
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
String[] names = signature.getParameterNames();
for (int i = 0; i < args.length; i++) {
params.put(names[i], args[i]);
}
return params;
}
/**
* 从map中获取键为paramName的值,不存在放回null
* @param map
* @param paramName
* @return
*/
private Object getValue(Map<String, Object> map, String paramName) {
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (entry.getKey().equals(paramName)) {
return entry.getValue();
}
}
return "";
}
}
拦截器
@Log4j2
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Autowired
private UrlInterceptor urlInterceptor;
//可以多个并列拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(LoginInterceptor()).addPathPatterns("/**");
registry.addInterceptor(urlInterceptor).addPathPatterns("/**");
}
获取系统URL路径
public class UserUrlUtils {
private static final ThreadLocal<String> uriHolder = new ThreadLocal<>();
public static void setUri(String uri) {
uriHolder.set(uri);
}
public static String getUri() {
return uriHolder.get();
}
public static void clear() {
uriHolder.remove();
}
}
注解使用:参数形式:
@ResponseBody
@RequestMapping(value = "list", method = RequestMethod.GET)
@OperationUserLog(title = "讲师-查询讲师列表", optParam = "#{name},#{address}", businessType = BusinessType.SELECT)
public Result selectLecturerList(@RequestParam(value = "pageNo", defaultValue = "1", required = false) Integer pageNo,
@RequestParam(value = "pageSize", defaultValue = "10", required = false) Integer pageSize,
@RequestParam(value = "name", required = false) String name,
@RequestParam(value = "address", required = false) String address
) {
return Result.ok(list);
}
对象形式:
@EnableXssFilter
@ApiIgnore(value = "讲师-插入讲师信息")
@ResponseBody
@RequestMapping(value = "save", method = RequestMethod.POST)
@OperationUserLog(title = "讲师-新增讲师", optParam = "#{lecturer}", businessType = BusinessType.INSERT)
public Result saveLecturer(@RequestBody LecturerVo lecturer, HttpServletRequest request) {
结果:
有需要其他操作,需在自定义切面做业务操作。