精华内容
下载资源
问答
  • AopLog是基于SpringAop和ThreadLocal实现的一个对请求方法埋点信息收集与处理的日志工具包。git地址(有详细的介绍和使用方法):跳转 使用方法: 1、先在pom中引入依赖: <dependency> <groupId&...

     一、AopLog介绍与使用

             AopLog是基于SpringAop和ThreadLocal实现的一个对请求方法埋点信息收集与处理的日志工具包。git地址(有详细的介绍和使用方法):跳转

         使用方法:

            1、先在pom中引入依赖:

        <dependency>
    	    <groupId>com.github.ealenxie</groupId>
    	    <artifactId>aop-log</artifactId>
    	    <version>2.5</version>
        </dependency>

            2、新建自定义全局的日志收集器实现收集 LogCollector:

    @Slf4j
    // 向Spring容器中注册bean
    @Component
    // 继承LogCollector
    public class AopLogCollector implements LogCollector {
    
        @Autowired
        // 注入数据库持久层(项目使用的是Spring-Data-JPA)
        private SysLogDataRepository sysLogDataRepository;
    
        /**
         * 实现LogCollector的collect方法
         * @param logData 埋点日志对象,存储各种埋点数据
         */
        @Override
        public void collect(LogData logData) {
            // 项目中的DO类,属性参数类似LogData
            SysLogData sysLogData = new SysLogData();
            // 将LogData中的属性拷贝到DO对象中
            BeanUtil.copyProperties(logData, sysLogData);
            // 获取当前的操作用户
            String username = SysJwtTokenUtil.getUsername();
            // 设置当前操作用户
            sysLogData.setUserName(username);
            // 设置创建时间(操作时间)
            sysLogData.setCreateTime(new Date());
            // 存入数据库
            sysLogDataRepository.saveAndFlush(sysLogData);
        }
    
    }

     跳转到埋点日志对象logdata属性说明

            3、使用AopLog注解进行埋点收集(这里使用项目中的删除接口为例):

    @Slf4j
    @RestController
    public class AppBannerController extends AdminController {
    
        @Autowired
        private AppBannerService appBannerService;
    
        // 使用@AopLog注解进行埋点收集
        @AopLog(type = "banner配置删除操作", headers = "token")
        @PostMapping("/banner/del")
        public ApiResult delete(@RequestBody AppBanner appBanner) {
            if (appBanner.getId() == null || appBanner.getId() < 1) {
                return ApiResult.fail(-1, "id错误");
            }
            appBannerService.delete(appBanner.getId());
            // 将删除的详情日志使用step方法加入到日志中
            LogData.step("删除banner配置,id=" + appBanner.getId());
            return ApiResult.ok("操作成功!");
        }
    }

        效果图:

        当调用/banner/del接口时,会自动将日志数据持久化到数据库中 

     二、equator介绍与使用

            Equator 基于`Spring Boot`对象比较框架,可以将对象之间的区别比较出来生成出一个list列表。git地址(有详细的介绍和使用方法):跳转

            将equator用在AopLog打印对象的修改详情日志中:

            1、在pom中引入依赖:

    	<dependency>
    		<groupId>com.github.dadiyang</groupId>
    		<artifactId>equator</artifactId>
    		<version>1.0.4</version>
    	</dependency>

             2、新建一个注解,用来存放字段的中文备注以及字段值的中文映射,用来打印出可读性更好的中文字段(值)说明:

    @Target({ElementType.FIELD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface FieldLabel {
        // 中文备注
        String value() default "";
    
        // 是否打印日志
        boolean isDisplay() default true;
    
        /**
         * 字段中文映射关系对应关系
         * 格式   :   映射前字段值:映射后字段值(多个用逗号","隔开)
         * 例    :   0:初始化,1:启用,2:禁用
         * @return
         */
        String mappingString() default "";
    }

             3、新建一个工具类,用来生成对象修改的具体详情:

    @Slf4j
    public class LogUtils {
        // 由于SimpleDateFormat不是线程安全的,所以使用ThreadLocal来存储
        private static ThreadLocal<DateFormat> dateFormatThreadLocal = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        // 将对象字段注解的FieldLabel缓存进Map中,第二次可以直接从Map中获取
        private static HashMap<String, HashMap<String, FieldLabel>> classAnnotationMap = new HashMap<>();
    
        /**
         * 生成修改操作详情
         * @param first  修改前的对象
         * @param second 修改后的对象
         * @return
         */
        public static String genDiffFieldsJSONString(Object first, Object second) {
            StringBuilder builder = new StringBuilder();
            // 这里使用了比较粗暴的方式将对象的id取出来
            String id = JSONObject.parseObject(JSONObject.toJSONString(first)).getString("id");
            if (StringUtils.isNotBlank(id)) {
                builder.append(String.format("id : %s\n", id));
            }
            // 使用Equator对比修改前后的属性变化
            Equator equator = new GetterBaseEquator();
            List<FieldInfo> fieldInfoList = equator.getDiffFields(first, second);
            // 若列表为空或者列表size为0,则表示修改前后的数据相同,之间返回
            if (fieldInfoList == null || fieldInfoList.size() == 0) {
                builder.append("-");
                return builder.toString();
            }
            // 获取类的class
            Class<?> firstClass = first.getClass();
            for (int i = 0; i < fieldInfoList.size(); i++) {
                try {
                    FieldInfo fieldInfo = fieldInfoList.get(i);
                    // 获取对象字段的FieldLabel注解
                    FieldLabel annotation = getAndSetAnnotation(firstClass, fieldInfo.getFieldName());
                    // 是否打印
                    if (!annotation.isDisplay()) {
                        continue;
                    }
                    Object firstVal = fieldInfo.getFirstVal();
                    Object secondVal = fieldInfo.getSecondVal();
                    // 处理时间格式
                    if ("java.util.Date".equals(fieldInfo.getFirstFieldType().getName()) && firstVal != null) {
                        firstVal = dateFormatThreadLocal.get().format(firstVal);
                    }
                    if ("java.util.Date".equals(fieldInfo.getSecondFieldType().getName()) && secondVal != null) {
                        secondVal = dateFormatThreadLocal.get().format(secondVal);
                    }
                    // 处理映射关系
                    if (StringUtils.isNotBlank(annotation.mappingString())) {
                        Map<String, String> mappingMap = mappingStringToMap(annotation.mappingString());
                        String firstMappingVal = mappingMap.get(firstVal.toString());
                        if (StringUtils.isNotBlank(firstMappingVal)) {
                            firstVal = String.format("%s (%s)", firstMappingVal, firstVal);
                        }
                        String secondMappingVal = mappingMap.get(secondVal.toString());
                        if (StringUtils.isNotBlank(secondMappingVal)) {
                            secondVal = String.format("%s (%s)", secondMappingVal, secondVal);
                        }
                    }
                    // 将处理后的日志append进去
                    builder.append(String.format("%s [%s]  :  %s  →  %s", annotation.value(), fieldInfo.getFieldName(), firstVal, secondVal));
                    // 列表遍历到最后一个时不加回车
                    if (i < (fieldInfoList.size() - 1)) {
                        builder.append("\n");
                    }
                } catch (Exception e) {
                    log.warn("日志保存报错:", e);
                }
            }
            dateFormatThreadLocal.remove();
            return builder.toString();
        }
    
        /**
         * 获取对象字段的FieldLabel注解并缓存
         * @param ClassInfo
         * @param annotationName
         * @return
         */
        public static FieldLabel getAndSetAnnotation(Class<?> ClassInfo, String annotationName) {
            String classInfoName = ClassInfo.getName();
            // 通过对象名获取对象的字段注解Map
            HashMap<String, FieldLabel> annotationMap = classAnnotationMap.get(classInfoName);
            // 不存在则put进去一个新的Map
            if (annotationMap == null) {
                annotationMap = new HashMap<>();
                classAnnotationMap.put(classInfoName, annotationMap);
            }
            // 从对象的字段注解Map中取注解
            FieldLabel fieldLabel = annotationMap.get(annotationName);
            // 取不到,则通过反射获取并加入缓存
            if (fieldLabel == null) {
                try {
                    fieldLabel = ClassInfo.getDeclaredField(annotationName).getAnnotation(FieldLabel.class);
                    if (fieldLabel != null) {
                        annotationMap.put(annotationName, fieldLabel);
                    }
                } catch (Exception e) {
                    log.warn("日志取得注解信息报错:", e);
                }
            }
            return fieldLabel;
        }
    
        /**
         * 将映字段中文映射关系对应关系使用split和流转化为hashMap
         * @param str
         * @return
         */
        public static Map<String, String> mappingStringToMap(String str) {
            return Arrays.asList(str.trim().split(",|,")).stream().map(item -> item.trim().split(":|:"))
                    .collect(Collectors.toMap(e -> e.length > 0 ? e[0].trim() : "", e -> e.length > 1 ? e[1].trim() : ""));
        }
    
    }

            4、在想要使用的对象字段中加入@FieldLabel注解:

    @Data
    public class MarketingPageJump implements Serializable {
        private static final long serialVersionUID = 1444701574692353535L;
    
        private Long id;
    
        @FieldLabel(value = "配置标识")
        private String code;
    
        @FieldLabel(value = "标题")
        private String title;
    
        @FieldLabel(value = "关联优惠券礼包ID")
        private String couponPackId;
    
        @FieldLabel(value = "封面图链接")
        private String imgUrl;
    
        @FieldLabel(value = "目标跳转链接")
        private String targetUrl;
    
        @FieldLabel(value = "状态", mappingString = "0:初始化,1:启用,2:禁用")
        private Integer status;
    
        @FieldLabel(value = "创建时间")
        private Date createTime;
    
        @FieldLabel(value = "更新时间")
        private Date updateTime;
    }
    

            5、在接口中设置埋点:

    @AopLog(type = "营销弹幕更新状态", headers = "token")
    @PostMapping("/marketingPageJump/updateStatus")
    public ApiResult updateStatus(@RequestBody MarketingPageJump marketingPageJump) {
        MarketingPageJump marketingPageJumpDB = marketingPageJumpService.getById(marketingPageJump.getId());
        if (marketingPageJumpDB == null) {
            return ApiResult.fail("未找到对应记录!");
        }
        Integer status = marketingPageJump.getStatus();
        if (marketingPageJumpDB.getStatus() == status) {
            return ApiResult.ok("未作任何修改");
        }
        BeanUtils.copyProperties(marketingPageJumpDB, marketingPageJump);
        marketingPageJump.setStatus(status);
        marketingPageJump.setUpdateTime(new Date());
        // 生成对象更改前后的详情数据并使用step方法写到日志中
        LogData.step(LogUtils.genDiffFieldsJSONString(marketingPageJumpDB, marketingPageJump));
        marketingPageJumpService.save(marketingPageJump);
        return ApiResult.ok();
    }

            效果图:

            调用/marketingPageJump/updateStatus接口修改状态时,会生成一条对象修改的详细记录

    展开全文
  • 最近在项目中要求把后台的一些关键操作记录下来,想了好半天能想到的也就那两三种方式,要么就是写一个拦截器,然后再web.xml里面进行配置,要么就是就是在每个需要记录操作日志的代码里面进行拦截,最后我选择了第...

    最近在项目中要求把后台的一些关键操作记录下来,想了好半天能想到的也就那两三种方式,要么就是写一个拦截器,然后再web.xml里面进行配置,要么就是就是在每个需要记录操作日志的代码里面进行拦截,最后我选择了第三种,也就是基于AOP的拦截,用这种方式,只需要在需记录操作的接口方法上,添加上自定义注解就好了。其实在项目开发里面真正用到AOP感觉不是很多,我也一样很少就没有怎么用到。刚好这次碰见了,就记录下来,也算是自己学习一次。
    我在做的项目是基于SSH(Spring,Spring MVC,Hibernate)框架。

    1. 首先我们先想想我们做的功能到底是什么,我们要记录操作,我们首先就应该有一张记录操作记录的表
      `@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
    @Entity
    @Table(name = "表名")
    public class LogModel extends IdEntity {
    
        //用户
        private User user;
        //IP
        private String remoteAddr;
        //异常
        private String exception;
        //日志标题
        private String title;
        //请求地址
        private String requestUri;
        //日志类型
        private String type;
        //日志记录描述
        private String description;
    以上是我所用到的实体类
    

    2. 关于实体类的Service层,Dao层,Impl等等我就不再这里过多叙述,无非就是一些与数据库操作挂钩的一些代码。
    3. 自定义一个切面注解

    @Target({ElementType.METHOD,ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface MethodLog {
        /**
         * 该注解作用于方法上时需要备注信息
         */
        String remarks() default "";
        }

    4. 接下来就是重点了,切面类的具体实现。

    @Component
    @Aspect
    public class SystemLogAspect {
    //这段代码调用了org.slf4j.LoggerFactory line:280
    private static final Logger logger = LoggerFactory.getLogger(SystemLogAspect.class);
    private static final ThreadLocal<Date> beginTimeThreadLocal =
                new NamedThreadLocal("ThreadLocal beginTime");
        private static final ThreadLocal<LogModel> logThreadLocal =
                new NamedThreadLocal("ThreadLocal log");
    
        private static final ThreadLocal<User> currentUser = new NamedThreadLocal("ThreadLocal user");
        @Autowired
        private ThreadPoolTaskExecutor threadPoolTaskExecutor;
        /**
         * Controller层切点 注解拦截
         */
        @Pointcut("@annotation(com.shopping.logrecord.MethodLog)")
        public void controllerAspect() {
        }
        /**
         * 用于拦截Controller层记录用户的操作的开始时间
         *
         * @param joinPoint 切点
         * @throws InterruptedException
         */
        @Before("controllerAspect()")
        public void doBefore(JoinPoint joinPoint) throws InterruptedException {
            Date beginTime = new Date();
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
                    .getRequestAttributes()).getRequest();
            beginTimeThreadLocal.set(beginTime);//线程绑定变量(该数据只有当前请求的线程可见)
            if (logger.isDebugEnabled()) {//这里日志级别为debug
                logger.debug("开始计时: {}  URI: {}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS")
                        .format(beginTime), request.getRequestURI());
            }
            //读取session中的用户
            HttpSession session = request.getSession();
            User user = (User) session.getAttribute("user");
            System.out.println(user);
            currentUser.set(user);
        }
        /**
         * 用于拦截Controller层记录用户的操作
         *
         * @param joinPoint 切点
         */
        @SuppressWarnings("unchecked")
        @After("controllerAspect()")
        public void doAfter(JoinPoint joinPoint)throws Exception {
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
                    .getRequestAttributes()).getRequest();
            User user = currentUser.get();
            if (user != null) {
                String title = "";
                String type = "info";                       //日志类型(info:入库,error:错误)
                String remoteAddr = SystemLogAspect.getIp();//请求的IP
                String requestUri = request.getRequestURI();//请求的Uri
                try {
                    title = getControllerMethodDescription2(joinPoint);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                LogModel logModel = new LogModel();
                logModel.setTitle(title);
                logModel.setType(type);
                logModel.setRemoteAddr(remoteAddr);
                logModel.setRequestUri(requestUri);
                logModel.setException("无异常");
                logModel.setUserId(user.getId());
                User users = userService.getObjById(user.getId());
                Date date = new Date();
                SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                String format = simpleDateFormat.format(date);
                logModel.setAddTime(date);
                logModel.setUserName(users.getUserName());
                //通过线程池来执行日志保存
                threadPoolTaskExecutor.execute(new SaveLogThread(logModel, logModelService));
                logThreadLocal.set(logModel);
                }
             }
             /**
         *  异常通知 记录操作报错日志
         * @param joinPoint
         * @param e
         */
        @AfterThrowing(pointcut = "controllerAspect()", throwing = "e")
        public  void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
            LogModel logModel = logThreadLocal.get();
            logModel.setType("error");
            logModel.setException(e.toString());
            new UpdateLogThread(logModel, logModelService).start();
        }
    /**
             * 获取注解中对方法的描述信息 用于Controller层注解
             * @param joinPoint 切点
             * @return 方法描述
             */
    
        public static String getControllerMethodDescription2(JoinPoint joinPoint) {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            MethodLog controllerLog = method
                    .getAnnotation(MethodLog.class);
            String discription = controllerLog.remarks();
            return discription;
        }
    
    
        /**
         * 获取请求ip
         */
        public static String getIp()throws Exception {
            InetAddress ia=null;
                ia=ia.getLocalHost();
                String localip=ia.getHostAddress();
                return localip;
        }
    
        /**
         * 保存日志线程
         *
         * @author lin.r.x
         *
         */
        private static class SaveLogThread implements Runnable {
            private LogModel logModel;
            private LogModelService logModelService;
    
            public SaveLogThread(LogModel logModel, LogModelService logModelService) {
                this.logModel = logModel;
                this.logModelService = logModelService;
            }
    
            @Override
            public void run() {
                logModelService.save(logModel);
            }
        }
    
        /**
         * 日志更新线程
         *
         * @author lin.r.x
         *
         */
        private static class UpdateLogThread extends Thread {
            private LogModel logModel;
            private LogModelService logModelService;
            public UpdateLogThread(LogModel logModel, LogModelService logModelService) {
                super(UpdateLogThread.class.getSimpleName());
                this.logModel = logModel;
                this.logModelService = logModelService;
            }
    
            @Override
            public void run() {
                this.logModelService.update(logModel);
            }
        }
    }

    接下来我们需要做一步操作,那就是在XML中开启对AOP的支持

    <!-- 启动对@AspectJ注解的支持 -->
        <aop:aspectj-autoproxy/>
        <!-- 扫描切点类组件 -->
        //放置切面实现类的包路径
        <context:component-scan base-package="com.shopping.logrecord.logss"/>
        //日志表的service的包路径
        <context:component-scan base-package="com.shopping.logrecord.logservicess"/>
        <bean id="threadPoolTaskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
            <property name="corePoolSize" value="5" />
            <property name="maxPoolSize" value="10" />
        </bean>

    OKAY,到这我们就基本算是配置完成了。
    接下来,加入我们需要执行编辑操作
    在编辑接口方法上添加自定义的注解
    这里写图片描述
    okay,完成,**注意**remarks的值。
    看一下表数据
    这里写图片描述
    大功告成!

    展开全文
  • 后台一些涉及到新增、编辑、删除等敏感操作的需要记录下操作日志,包含操作人、操作内容、请求参数等等信息。 1.2 思路 统一在Controller层进行拦截,记录下请求信息 通过自定义注解,包含操作内容 通过Spring AOP ...

    一、需求

    1.1 问题

    后台一些涉及到新增、编辑、删除等敏感操作的需要记录下操作日志,包含操作人、操作内容、请求参数等等信息。

    1.2 思路

    • 统一对Controller层的方法进行拦截,记录下请求信息
    • 通过自定义注解,定义操作内容
    • 通过Spring AOP集成AspectJ来实现,利用AspectJ里的相关注解可以方便的配置切面、定义切点

    二、实现

    2.1 环境准备

    Maven引入 spring-boot-starter-aop stater ,这里会自动引入spring-aopaspectjweaver 两个jar包:

    • spring-aop:基于代理的AOP支持
    • aspectjweaver:AspectJ,一个AOP框架,扩展了Java语言。通过AspectJ注解,简称@AspectJ,可以快速配置切面,定义切点。由于Spring支持方法级的切点,所以仅对@AspectJ提供了有限的支持,也就是只使用了AspectJ的一部分功能。
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    

    2.2 定义注解

    定义个方法级OptLog注解,该注解注释的方法表示要记录操作日志。

    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface OptLogger {
        /**
         * 描述信息
         */
        String value() default "";
    }
    

    2.3 使用@AspectJ配置切面、定义切点

    主要用到了@Aspect、@Pointcut、@Before这三个注解:

    • @Aspect:定义切面
    • @Pointcut:定义切点,借助@annotation(com.xxx.OptLogger)扫描注释了指定注解的方法
    • @Before:前置通知,在方法执行前执行,类似还有@After、@Around等等

    实现思路:

    • 通过@Pointcut("@annotation(com.xxx.OptLogger)")定义切点,表示注释了OptLogger注解的方法
    • 通过前置通知@Before("")拦截
    • 获取请求相关信息
      • 假设请求参数中有个token可以获取到用户信息,记录下用户名(不管是什么机制,只要能获取到用户信息即可)
      • 获取请求ip
      • 通过切点获取拦截方法上的OptLogger注解的描述信息
      • 通过切点获取拦截方法的参数
    • 线程池异步记录操作信息
    @Aspect
    @Component
    public class OptLogAspect {
        private static final Logger LOGGER = LoggerFactory.getLogger(OptLogAspect.class);
    
        @Autowired
        private UserService userService;
        @Autowired
        private OptLogService optLogService;
        @Resource(name = "taskExecutor")
        private ThreadPoolTaskExecutor taskExecutor;
    
        /**
         * 定义切点,表示注释了OptLogger注解的目标类方法
         */
        @Pointcut("@annotation(com.momo.optlog.web.optlog.annotation.OptLogger)")
        public void optLogAspect() {
        }
    
        /**
         * 前置通知 用于拦截有OptLogger注解注释的方法
         *
         * @param joinPoint 连接点
         * @return
         */
        @Before("optLogAspect()")
        public void before(JoinPoint joinPoint) {
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
    
            try {
                String token = request.getParameter("token");
                if (StringUtils.isBlank(token)) {
                    return;
                }
                User user = userService.getByToken(token);
                if (user == null) {
                    return;
                }
    
                String ip = IpUtil.getRemoteHost(request);
                String opt = getOptLogMethodDesc(joinPoint);
                String args = JsonUtil.obj2json(getMethodArgs(joinPoint));
                this.recordOptLog(ip, user.getUserName(), opt, args);
            } catch (Exception e) {
                LOGGER.error("record admin opt fail", e);
            }
        }
    
        private void recordOptLog(String ip, String userName, String opt, String args) {
            taskExecutor.submit(() -> {
                OptLogAddDTO optLogDTO = OptLogAddDTO.builder()
                        .userName(userName)
                        .opt(opt)
                        .ip(ip)
                        .args(args)
                        .build();
                if (!optLogService.add(optLogDTO)) {
                    LOGGER.error("save opt log fail, params={}", JsonUtil.obj2json(optLogDTO));
                }
            });
        }
    
        /**
         * 获取OptLogger注解的描述信息
         *
         * @param joinPoint 切点
         * @return 方法描述
         * @throws NoSuchMethodException
         */
        public static String getOptLogMethodDesc(JoinPoint joinPoint) throws NoSuchMethodException {
            // 获取拦截的方法
            MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
            String methodName = methodSignature.getName();
            Class[] parameterTypes = methodSignature.getMethod().getParameterTypes();
            Method method = methodSignature.getDeclaringType().getMethod(methodName, parameterTypes);
    
            // 方法上的注解
            boolean isPresent = method.isAnnotationPresent(OptLogger.class);
            if (isPresent) {
                OptLogger optLogger = method.getAnnotation(OptLogger.class);
                return optLogger.value();
            }
            return "";
        }
    
        /**
         * 获取方法参数
         *
         * @param joinPoint
         * @return
         */
        public static List<Object> getMethodArgs(JoinPoint joinPoint) {
            Object[] args = joinPoint.getArgs();
            if (args == null || args.length == 0) {
                return new ArrayList<>(0);
            }
    
            return Arrays.stream(args)
                    .filter(arg ->
                            !(arg instanceof Model)
                            && !(arg instanceof ModelMap)
                            && !(arg instanceof BeanPropertyBindingResult)
                            && !(arg instanceof MultipartFile))
                    .collect(Collectors.toList());
        }
    }
    

    2.4 记录操作日志

    以UserController为例,在需要记录日志的接口上添加注解@OptLogger(“描述信息”)。

    • 例如新增用户接口,在执行addUser()方法前,会先执行OptLogAspect#before()方法,并记录操作人、ip、操作内容(新增用户)、请求参数等信息。其他接口都类似。
    @RestController
    public class UserController {
    
        @Autowired
        private UserService userService;
    
        @GetMapping("/user")
        public CommonResponse list() {
            List<User> userList = userService.getUesrList();
            return CommonResponse.okResult(userList);
        }
    
        @PostMapping("/user")
        @OptLogger("新增用户")
        public CommonResponse addUser(@RequestBody UserAddReqVO req) {
            boolean isSuccess = userService.add(req);
            return CommonResponse.okResult(isSuccess);
        }
    
        @PutMapping("/user")
        @OptLogger("编辑用户")
        public CommonResponse updateUser(@RequestBody UserUpdateReqVO req) {
            boolean isSuccess = userService.update(req);
            return CommonResponse.okResult(isSuccess);
        }
    
        @DeleteMapping("/user/{id}")
        @OptLogger("删除用户")
        public CommonResponse deleteUser(@PathVariable long id) {
            boolean isSuccess = userService.delete(id);
            return CommonResponse.okResult(isSuccess);
        }
    
        @DeleteMapping("/user/{id}")
        @OptLogger("删除用户")
        public CommonResponse deleteUser(@PathVariable long id) {
            boolean isSuccess = userService.delete(id);
            return CommonResponse.okResult(isSuccess);
        }
    
    }
    

    2.5 总结

    实现的核心逻辑基本上都列出来了,实现的比较粗糙,仅供参考。
    项目源码:https://github.com/mytt-10566/springboot-optlog

    展开全文
  • SSMAOP日志 数据库与表结构 日志表信息描述sysLog 基于AOP日志处理 创建切面类处理日志 package com.sibd.travel.controller; import com.sibd.travel.controller.SyslogController; import ...

    SSMAOP日志

    数据库与表结构

    日志表信息描述sysLog

    在这里插入图片描述

    基于AOP日志处理

    创建切面类处理日志

    package com.sibd.travel.controller;
    
    import com.sibd.travel.controller.SyslogController;
    import com.sibd.travel.pojo.SysLog;
    import com.sibd.travel.pojo.Users;
    import com.sibd.travel.service.SyslogService;
    import com.sibd.travel.util.UuidUtil;
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.After;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.core.context.SecurityContext;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.security.core.userdetails.User;
    import org.springframework.stereotype.Component;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    import javax.servlet.http.HttpServletRequest;
    import java.lang.reflect.Method;
    import java.util.Date;
    
    @Component
    @Aspect
    public class LogAop {
        @Autowired
        private SyslogService sysLogService;
    
        @Autowired
        private HttpServletRequest request;
    
        private Date startTime; // 访问时间
        private Class executionClass;// 访问的类
        private Method executionMethod; // 访问的方法
    
        @Before("execution(* com.sibd.travel.controller.*.*(..))")
        public void doBefore(JoinPoint jp) throws NoSuchMethodException, SecurityException {
            // 访问时间
            startTime=new Date();
    
            // 获取访问的类字节码文件
            executionClass = jp.getTarget().getClass();
            // 获取访问的方法
            String methodName = jp.getSignature().getName();// 获取访问的方法的名称
            Object[] args = jp.getArgs();// 获取访问的方法的参数
            if (args == null || args.length == 0) {// 无参数
                executionMethod = executionClass.getMethod(methodName); // 只能获取无参数方法
            } else {
                // 有参数,就将args中所有元素遍历,获取对应的Class,装入到一个Class[]
                Class[] classArgs = new Class[args.length];
                for (int i = 0; i < args.length; i++) {
                    classArgs[i] = args[i].getClass();
                }
                executionMethod = executionClass.getMethod(methodName, classArgs);// 获取有参数方法
            }
    
    
        }
    
        // 主要获取日志中其它信息,时长、ip、url...
        @After("execution(* com.sibd.travel.controller.*.*(..))")
        public void doAfter(JoinPoint jp) throws Exception {
            // 获取类上的@RequestMapping对象
            if (executionClass != SyslogController.class) {
                RequestMapping classAnnotation = (RequestMapping) executionClass.getAnnotation(RequestMapping.class);
                if (classAnnotation != null) {
                    // 获取方法上的@RequestMapping对象
                    RequestMapping methodAnnotation = executionMethod.getAnnotation(RequestMapping.class);
                    if (methodAnnotation != null) {
                        String url = ""; // 它的值应该是类上的@RequestMapping的value+方法上的@RequestMapping的value
                        url = classAnnotation.value()[0] + methodAnnotation.value()[0];
    
                        SysLog sysLog = new SysLog();
                        sysLog.setId(UuidUtil.getId());
    
                        // 获取访问时长
                        Long executionTime = new Date().getTime() - startTime.getTime();
                        // 将sysLog对象属性封装
                        sysLog.setExecutionTime(executionTime);
                        // 获取ip
                        String ip = request.getRemoteAddr();
                        sysLog.setIp(ip);
                        sysLog.setMethod("[类名]" + executionClass.getName() + "[方法名]" + executionMethod.getName());
                        // 可以通过securityContext获取,也可以从request.getSession中获取
                        SecurityContext context = SecurityContextHolder.getContext();
                        //request.getSession().getAttribute("SPRING_SECURITY_CONTEXT");
                        String username = ((User)
                                (context.getAuthentication().getPrincipal())).getUsername();
                        sysLog.setUsername(username);
    
                        sysLog.setUrl(url);
                        sysLog.setVisitTime(startTime);
                        // 调用Service,调用dao将sysLog insert数据库
                        sysLogService.save(sysLog);
                    }
    
                }
            }
        }
    }
    
    

    在切面类中我们需要获取登录用户的username,还需要获取ip地址,我们怎么处理?

    • username获取

      SecurityContextHolder获取

    • ip地址获取

      ip地址的获取我们可以通过request.getRemoteAddr()方法获取到。

      在Spring中可以通过RequestContextListener来获取request或session对象。

    @Aspect:作用是把当前类标识为一个切面供容器读取
     
    @Pointcut:Pointcut是植入Advice的触发条件。每个Pointcut的定义包括2部分,一是表达式,二是方法签名。方法签名必须是 public及void型。可以将Pointcut中的方法看作是一个被Advice引用的助记符,因为表达式不直观,因此我们可以通过方法签名的方式为 此表达式命名。因此Pointcut中的方法只需要方法签名,而不需要在方法体内编写实际代码。
    @Around:环绕增强,相当于MethodInterceptor
    @AfterReturning:后置增强,相当于AfterReturningAdvice,方法正常退出时执行
    @Before:标识一个前置增强方法,相当于BeforeAdvice的功能,相似功能的还有
    @AfterThrowing:异常抛出增强,相当于ThrowsAdvice
    @After: final增强,不管是抛出异常或者正常退出都会执行
    

    SysLogController

    package com.sibd.travel.controller;
    
    import com.sibd.travel.pojo.SysLog;
    import com.sibd.travel.service.SyslogService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.servlet.ModelAndView;
    
    import java.util.List;
    
    @Controller
    @RequestMapping("sysLog")
    public class SyslogController {
        @Autowired
        private SyslogService syslogService;
        @RequestMapping("findAll.do")
        public ModelAndView findAll(){
            ModelAndView mv = new ModelAndView();
            List<SysLog> sysLogs = syslogService.findAll();
            mv.setViewName("syslog-list");
            mv.addObject("sysLogs",sysLogs);
            return mv;
        }
    }
    

    Service

    package com.sibd.travel.service.impl;
    
    import com.sibd.travel.mapper.SysLogMapper;
    import com.sibd.travel.pojo.SysLog;
    import com.sibd.travel.service.SyslogService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    import java.util.List;
    
    @Service
    @Transactional
    public class SyslogServiceImpl implements SyslogService {
        @Autowired
        private SysLogMapper sysLogMapper;
        @Override
        public List<SysLog> findAll() {
            return sysLogMapper.findAll();
        }
    
        @Override
        public void save(SysLog sysLog) {
            sysLogMapper.save(sysLog);
        }
    }
    

    Dao

    package com.sibd.travel.mapper;
    
    import com.sibd.travel.pojo.SysLog;
    import org.apache.ibatis.annotations.Insert;
    import org.apache.ibatis.annotations.Select;
    
    import java.util.List;
    
    public interface SysLogMapper {
    
        @Select("select * from syslog")
        List<SysLog> findAll();
    
        @Insert("insert into syslog(id,visitTime,username,ip,url,executionTime,method) values(#{id},#{visitTime},#{username},#{ip},#{url},#{executionTime},#{method})")
        void save(SysLog sysLog);
    }
    
    展开全文
  • 公司老大写的aop 拦截后台操作日志 1.自定义注解 import com.redpacket.contract.sys.SysMenu; import com.redpacket.contract.sys.SysMethod; import java.lang.annotation.*; @Target({ElementType....
  • Spring AOP实现后台管理系统日志管理

    万次阅读 多人点赞 2017-02-08 15:31:52
    AOP+注解 实现后台管理系统日志管理
  • 通过AOP在Controller之前进行切面,新建一个自定义注解,然后在AOP配置里面设置当前操作的模块和功能,然后设置进日志的实体类中,最后保存到数据库中即可完成 首先自定义一个注解: package com.sure.crm.web....
  • springboot基于AOP实现操作日志记录

    千次阅读 2019-03-27 12:12:29
    在开发系统时,尤其是后台管理系统,几乎每一个操作,都要求记录其操作日志。 二、实现 如果在每一个操作结束之后,都加上一个记录日志操作,那样代码会非常臃肿,耦合度高、代码可读性差,维护难。本例中,采用...
  • 使用AOP 记录操作日志

    2019-12-18 17:38:27
    最近在做后台管理系统,需要记录下操作日志到数据库 采用AOP 的方式来实现 springboot + aop + mybatis 表设计与实体类 日志表 sys_log CREATE TABLE `sys_log` ( `id` bigint(20) NOT NULL AUTO_INCREMENT ...
  • Spring Boot 使用AOP切面实现后台日志管理模块

    万次阅读 多人点赞 2018-06-12 12:06:43
    元注解方式结合AOP,灵活记录操作日志 能够记录详细错误日志为运维提供支持 日志记录尽可能减少性能影响 1.定义日志记录元注解 2.定义用于记录日志的实体类 import java.io.Serializable; import ...
  • 基于SSM环境下进行AOP日志的记录,完成存入数据库,取出展示所有数据的需求。
  • 功能需求:用户在saas-export项目中使用了什么方法,进行了什么操作,都要记录在日志中,然后在日志管理页面可以查看得到。 功能分析 两个功能,一个是分页显示,一个是保存日志 系统日志管理 (1)分析 记录...
  • AOP 面向切面编程。主要功能是对方法的加强。其实现是居于代理模式使用。 Spring事务就是居于AOP的实现。 首先了解一下相关概念 切面(Aspect): 通常是一个类,定义切入点和通知 连接点(Join point): 程序执行...
  • 基于SpringBoot使用AOP技术实现操作日志管理

    千次阅读 多人点赞 2019-10-23 15:51:00
    操作日志对于程序员或管理员而言,可以快速定位到系统中相关的操作,而对于操作日志的管理的实现不能对正常业务实现进行影响,否则即不满足单一原则,也会导致后续代码维护困难,因此我们考虑使用AOP切面技术来实现...
  • 使用AOP进行日志记录 ... 操作日志我采用了环绕通知类型(@Around) 错误日志我采用了异常通知类型(@AfterThrowing)   需要引入lib commons-logging.jarspring.jaraspectjrt.jaraspectjweaver.j...
  • 管理后台一般会有一些比较敏感的操作,这些配置操作的修改会影响线上功能的表现,进而影响用户的使用体验。为了记录管理后台中的敏感操作记录,实现操作记录可追溯,数据可恢复等目的;对后台操作进行记录是十分...
  • 利用AOP进行简单的操作日志记录 1.业务需求 针对方法的修改、添加、删除进行简单操作记录,要求数据展示格式为业务逻辑名称,操作方式,内容(修改的内容为前后属性修改,添加为操作对象名称,删除为删除对象名称...
  • 文章目录本篇要点简单回顾SpringAOP的相关知识点1、AOP关键术语2、通知的五种类型3、切入点表达式execution@annotation4、AOP应用场景快速开始引入依赖定义日志信息封装自定义注解@Log定义测试接口定义切面Aspect与...
  • 在开发系统时,尤其是后台管理系统,几乎每一个操作,都要求记录其操作日志。 二、实现 如果在每一个操作结束之后,都加上一个记录日志操作,那样代码会非常臃肿,耦合度高、代码可读性差,维护难。本例中,采用...
  • AOP日志

    2019-07-29 13:15:10
    我们在一般做后台管理系统的时候日志系统是一定要有的,因为我们系统一旦数据有哪些异常我们就可以很方便的通过日志记录中谁在什么时间干了什么事情来判断出哪些用户的什么操作给系统带来了异常 日志分析 我们来...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 14,065
精华内容 5,626
关键字:

后台操作日志aop