SpringBoot+AOP+注解实现用户操作日志

定义数据表:

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) {
  
  

结果:

有需要其他操作,需在自定义切面做业务操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值