【智能排班系统】AOP+自定义注解 实现操作日志自动记录

操作日志介绍

在这里插入图片描述

操作日志是对系统或应用程序中所有用户操作、系统事件、后台任务等进行详细记录的文本文件或数据库条目。它是系统运行过程中的“黑匣子”,详尽地记载了每一次交互、每一次状态变更以及每一次异常情况。在数字化时代,操作日志的作用与重要性不容忽视,主要体现在以下几个方面:

  • 故障排查与问题定位当系统出现故障或异常行为时,操作日志是首要的诊断工具。通过查阅相关时间段的日志记录,工程师可以快速定位到问题发生的精确时间点、涉及的功能模块以及可能触发问题的操作步骤,大大缩短了故障排查的时间,提高了问题解决效率。日志中的错误代码、异常堆栈信息等详细数据,更是为精准定位问题根源提供了关键线索

  • 审计追踪与合规性要求:对于许多行业(如金融、医疗、政府等),法律法规往往要求对关键业务操作进行详细的记录和长期保存,以满足审计需求和合规性监管。操作日志能够完整记录用户的操作行为、操作时间、操作结果等信息,确保了业务流程的透明度和可追溯性。在发生争议或安全事件时,操作日志可以作为重要的证据材料,帮助厘清责任归属,保障各方权益

  • 性能分析与优化:操作日志不仅记录错误和异常,也包含系统正常运行时的各项指标和状态变化。通过对日志数据进行深度分析,可以揭示系统的性能瓶颈、资源使用情况、用户访问模式等信息,为系统优化提供数据支持。例如,通过分析请求响应时间、并发量等指标,可以发现并优化慢查询、资源争抢等问题,提升系统整体性能

  • 安全监控与威胁检测:在网络安全领域,操作日志是实时监控系统安全状态、及时发现并响应潜在威胁的重要手段。通过对登录尝试、权限变更、敏感数据访问等操作的记录与分析,可以及时发现异常行为,如暴力破解、未授权访问、数据泄露等安全事件,从而启动应急预案,防止或减轻损失

  • 业务洞察与决策支持:对于业务运营人员而言,操作日志中蕴含的用户行为数据是了解产品使用情况、用户偏好、功能受欢迎程度等关键信息的重要来源。通过对日志进行统计分析和数据挖掘,可以得出诸如活跃用户数、功能使用频率、转化率等业务指标,为产品优化、市场策略制定提供数据驱动的决策支持

自动保存操作日志

基本实现思路

定义注解,将注解添加到需要记录日志的方法上,当方法执行完成或者抛异常后,通过AOP获取方法的参数、响应等信息记录到数据库中。

定义注解

import com.dam.enums.log.BusinessTypeEnum;
import com.dam.enums.log.OperatorTypeEnum;

import java.lang.annotation.*;


/**
 * @author dam
 */
// 表示注解可以用在参数、方法上面
@Target({ElementType.PARAMETER, ElementType.METHOD})
// 设置注解的保留策略为 RUNTIME,这意味着该注解信息将在编译后的字节码中保留,并能在运行时通过反射获取
@Retention(RetentionPolicy.RUNTIME)
// 标记该注解将被包含在生成的 JavaDoc 文档中,便于开发者查阅和理解
@Documented
public @interface OperationLog {

    /**
     * 模块名称
     */
    public String title() default "";

    /**
     * 方法名称
     */
    public String detail() default "";

    /**
     * 业务类型
     */
    public BusinessTypeEnum businessType() default BusinessTypeEnum.OTHER;

    /**
     * 操作人类别(手机用户、网页用户)
     */
    public OperatorTypeEnum operatorType() default OperatorTypeEnum.MANAGE;

    /**
     * 是否保存请求的参数
     */
    public boolean isSaveRequestData() default true;

    /**
     * 是否保存响应的参数
     */
    public boolean isSaveResponseData() default true;

}

枚举

业务类型枚举

/**
 * 业务操作类型
 */
public enum BusinessTypeEnum {
    /**
     * 其它
     */
    OTHER(0,"其他"),

    /**
     * 新增
     */
    INSERT(1,"新增"),

    /**
     * 修改
     */
    UPDATE(2,"修改"),

    /**
     * 删除
     */
    DELETE(3,"删除"),

    /**
     * 授权
     */
    ASSGIN(4,"授权"),

    /**
     * 导出
     */
    EXPORT(5,"导出"),

    /**
     * 导入
     */
    IMPORT(6,"导入"),

    /**
     * 强退
     */
    FORCE(7,"强退"),

    /**
     * 更新状态
     */
    STATUS(8,"更新状态"),

    /**
     * 清空数据
     */
    CLEAN(9,"清空数据"),
    PASS(10,"通过"),
    REJECT(11,"拒绝"),
    ;

    private Integer code;
    private String name;

    BusinessTypeEnum(Integer code, String name) {
        this.code = code;
        this.name = name;
    }

    public Integer getCode() {
        return code;
    }

    public String getName() {
        return name;
    }
}

操作人员类型枚举

public enum OperatorTypeEnum {
    /**
     * 其它
     */
    OTHER(0,"其他"),

    /**
     * 后台用户
     */
    MANAGE(1,"后台用户"),

    /**
     * 手机端用户
     */
    MOBILE(2,"手机端用户");

    private Integer code;
    private String name;

    OperatorTypeEnum(Integer code, String name) {
        this.code = code;
        this.name = name;
    }

    public Integer getCode() {
        return code;
    }

    public String getName() {
        return name;
    }
}

AOP具体实现

这个类是一个使用 Spring AOP(面向切面编程)技术实现的操作日志记录处理器。

  • 当应用中某个方法执行前后(成功返回或抛出异常)触发相应的通知方法(doAfterReturning 和 doAfterThrowing),这两个方法都会调用通用的日志处理方法 handleLog。
  • handleLog 方法负责收集日志所需的各种信息,如请求方法、URL、IP 地址、地理位置、用户身份(从 JWT 令牌中解析)、企业及门店信息、操作状态(成功或异常)、异常消息(如果有)等。
  • 收集到的日志信息封装在 OperationLogEntity 对象中,然后调用 OperationLogService 的 save 方法将日志数据持久化到数据库。
package com.dam.aop;

import com.alibaba.fastjson.JSON;
import com.dam.annotation.OperationLog;
import com.dam.model.entity.system.OperationLogEntity;
import com.dam.service.OperationLogService;
import com.dam.utils.JwtUtil;
import com.dam.utils.ip.IpAddressUtils;
import com.dam.utils.ip.IpUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Collection;
import java.util.Map;


/**
 * 操作日志记录处理
 */
// 使用 AOP(面向切面编程)技术实现操作日志记录
@Aspect
// 将该类作为 Spring 管理的 Bean
@Component
public class OperationLogAspect {
    private static final Logger log = LoggerFactory.getLogger(OperationLogAspect.class);

    /**
     * 注入 OperationLogService 依赖,用于持久化操作日志到数据库
     */
    @Resource
    private OperationLogService operationLogService;

    /**
     * 处理完请求后执行
     * 在带有 @OperationLog 注解的方法执行成功并返回后触发
     * pointcut:切入点
     *
     * @param joinPoint     切点
     * @param controllerLog 注解实例
     * @param jsonResult    返回结果对象(如果有的话)
     */
    @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
    public void doAfterReturning(JoinPoint joinPoint, OperationLog controllerLog, Object jsonResult) {
        // 调用通用日志处理方法,记录正常操作日志
        handleLog(joinPoint, controllerLog, null, jsonResult);
    }

    /**
     * 拦截异常操作
     * 在带有 @OperationLog 注解的方法抛出异常时触发
     *
     * @param joinPoint     切点
     * @param controllerLog 注解实例
     * @param e             异常对象
     */
    @AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, OperationLog controllerLog, Exception e) {
        // 调用通用日志处理方法,记录异常操作日志
        handleLog(joinPoint, controllerLog, e, null);
    }

    /**
     * 处理日志,填充日志信息,并将日志存储到数据库
     *
     * @param joinPoint     切点信息
     * @param controllerLog 操作日志注解实例
     * @param e             异常对象(如果有的话)
     * @param jsonResult    返回结果对象(如果有的话)
     */
    protected void handleLog(final JoinPoint joinPoint, OperationLog controllerLog, final Exception e, Object jsonResult) {
        try {
             根据请求上下文获取 HttpServletRequest 相关信息
            RequestAttributes ra = RequestContextHolder.getRequestAttributes();
            ServletRequestAttributes sra = (ServletRequestAttributes) ra;
            // 获取request之后,请求的内容都可以知道
            HttpServletRequest request = sra.getRequest();

             操作日志对象信息存储
            OperationLogEntity operationLog = new OperationLogEntity();
            // 操作状态
            operationLog.setStatus(0);
            // 请求的地址
            String ip = IpUtil.getIpAddress(request);
            operationLog.setOperIp(ip);
            // 设置请求的 URL
            operationLog.setOperUrl(request.getRequestURI());
            // 设置请求的地理位置信息(根据 IP 解析)
            operationLog.setOperLocation(IpAddressUtils.getRealAddressByIP(ip));
            // 从请求头中提取 JWT 令牌,获取用户名和企业、店铺 ID
            String token = request.getHeader("token");
            String userName = JwtUtil.getUsername(token);
            operationLog.setOperName(userName);
            // 设置企业信息
            operationLog.setEnterpriseId(Long.parseLong(JwtUtil.getEnterpriseId(token)));
            // 设置门店信息
            operationLog.setStoreId(Long.parseLong(JwtUtil.getStoreId(token)));

            // 如果存在异常,更新操作状态并记录异常信息
            if (e != null) {
                // 异常
                operationLog.setStatus(1);
                operationLog.setErrorMsg(e.getMessage());
            }

            // 获取类名和方法名
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();
            operationLog.setMethod(className + "." + methodName + "()");

            // 设置请求方法(GET、POST、PUT 等)
            operationLog.setRequestMethod(request.getMethod());

            // 处理设置注解上的参数
            getControllerMethodDescription(joinPoint, controllerLog, operationLog, jsonResult);

            // 将操作数据保存数据库
            operationLogService.save(operationLog);
        } catch (Exception exp) {
            // 记录本地异常日志
            log.error("异常信息:{}", exp.getMessage());
            exp.printStackTrace();
        }
    }

    /**
     * 获取注解中对方法的描述信息 用于Controller层注解
     *
     * @param log          日志
     * @param operationLog 操作日志
     * @throws Exception
     */
    public void getControllerMethodDescription(JoinPoint joinPoint, OperationLog log, OperationLogEntity operationLog, Object jsonResult) throws Exception {
        // 设置业务类型(从注解中获取)
        operationLog.setBusinessType(log.businessType().getCode());
        // 设置操作标题(从注解中获取)
        operationLog.setTitle(log.title());
        // 设置操作详情(从注解中获取)
        operationLog.setDetail(log.detail());
        // 设置操作人类型(从注解中获取)
        operationLog.setOperatorType(log.operatorType().getCode());
        // 如果需要保存请求数据,提取并设置请求参数
        if (log.isSaveRequestData()) {
            this.setRequestValue(joinPoint, operationLog);
        }
        // 如果需要保存响应数据且有返回结果,将其序列化并设置到日志中
        if (log.isSaveResponseData() && !StringUtils.isEmpty(jsonResult)) {
            operationLog.setJsonResult(JSON.toJSONString(jsonResult));
        }
    }

    /**
     * 获取请求的参数,放到操作日志中
     *
     * @param joinPoint    切点信息
     * @param operationLog 操作日志实体对象
     */
    private void setRequestValue(JoinPoint joinPoint, OperationLogEntity operationLog) {
        String requestMethod = operationLog.getRequestMethod();
        if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) {
            String params = argsArrayToString(joinPoint.getArgs());
            operationLog.setOperParam(params);
        }
    }

    /**
     * 将方法参数数组转化为字符串表示形式
     *
     * @param paramArr 方法参数数组
     * @return 参数字符串
     */
    private String argsArrayToString(Object[] paramArr) {
        String params = "";
        if (paramArr != null && paramArr.length > 0) {
            for (Object param : paramArr) {
                // 每个param包含了 健值
                if (!StringUtils.isEmpty(param) && !isFilterObject(param)) {
                    try {
                        Object jsonObj = JSON.toJSON(param);
                        params += jsonObj.toString() + " ";
                    } catch (Exception e) {
                    }
                }
            }
        }
        return params.trim();
    }

    /**
     * 判断是否需要过滤的对象
     *
     * @param o 对象信息
     * @return 如果是需要过滤的对象,则返回true;否则返回false
     */
//    @SuppressWarnings("rawtypes")
    public boolean isFilterObject(final Object o) {
        Class<?> clazz = o.getClass();
        if (clazz.isArray()) {
            // 如果是数组,检查其组件类型是否为 MultipartFile
            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
        } else if (Collection.class.isAssignableFrom(clazz)) {
            // 如果是集合,检查其中是否有 MultipartFile 类型的元素
            Collection collection = (Collection) o;
            for (Object value : collection) {
                return value instanceof MultipartFile;
            }
        } else if (Map.class.isAssignableFrom(clazz)) {
            // 如果是 Map,检查其中是否有 MultipartFile 类型的值
            Map map = (Map) o;
            for (Object value : map.entrySet()) {
                Map.Entry entry = (Map.Entry) value;
                return entry.getValue() instanceof MultipartFile;
            }
        }
        // 检查对象是否为 MultipartFile、HttpServletRequest、HttpServletResponse 或 BindingResult 类型
        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
                || o instanceof BindingResult;
    }
}

注:isFilterObject 方法的作用是判断给定的 Object 是否属于需要在操作日志记录过程中进行过滤的类型。也就是说,如果一个对象通过了这个方法的检查,那么在生成操作日志时,不应将其具体内容包含在日志记录中。该方法主要服务于日志记录的精细化控制,避免某些特定类型或敏感信息被无差别地写入日志,可能造成日志冗余

  • MultipartFile:代表上传的文件对象,通常包含文件内容等大量二进制数据,不适合记录在日志中
  • HttpServletRequest、HttpServletResponse:分别代表 HTTP 请求和响应对象,它们通常包含大量的请求/响应头、Cookie、Session 等信息,记录全部内容既没有必要,也可能包含敏感信息
  • BindingResult:Spring MVC 中用于绑定和验证表单数据的结果对象,通常包含详细的验证错误信息,可能不适合全部写入日志

方法上添加注解

通过在方法上面添加@OperationLog注解,即可为其添加日志记录功能,注意,并不是所有方法都适合做日志记录,例如一些非关键业务的查询方法,都记录反而造成数据库的日志数据较多。应该对一些关键业务做日志记录,例如排班计算(计算出错时方便找到方法参数进行bug修复)、数据增加、数据修改(当数据被错误修改时、可以看到是谁修改的,方便追责和数据修复、数据删除)

/**
 * 保存
 */
@RequestMapping("/save")
@PreAuthorize("hasAuthority('bnt.task.add')")
@OperationLog(title = SchedulingTaskController.title, businessType = BusinessTypeEnum.INSERT, detail = "新增排班任务")
public R save(@RequestBody SchedulingTaskEntity schedulingTask, HttpServletRequest httpServletRequest) throws SSSException {
    long storeId = Long.parseLong(JwtUtil.getStoreId(httpServletRequest.getHeader("token")));
    schedulingTask.setStoreId(storeId);

    //默认复制门店的排班规则
    R r = enterpriseFeignService.copySchedulingRule(storeId);
    if (r.getCode() == ResultCodeEnum.SUCCESS.getCode().intValue()) {
        Long ruleId = r.getData("ruleId", new TypeReference<Long>() {
        });
        if (ruleId == null) {
            throw new SSSException(ResultCodeEnum.FAIL.getCode(), "门店规则还没有设置,请先设置规则再添加任务");
        }
        schedulingTask.setSchedulingRuleId(ruleId);
    }

    schedulingTaskService.save(schedulingTask);

    return R.ok();
}

日志增删改查

日志表sql

DROP TABLE IF EXISTS `operation_log`;
CREATE TABLE `operation_log` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `is_deleted` tinyint NOT NULL DEFAULT '0' COMMENT '是否删除(0-未删, 1-已删)',
  `title` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '模块标题',
  `business_type` tinyint DEFAULT NULL COMMENT '业务类型 (0其它 1新增 2修改 3删除)',
  `method` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '方法名称',
  `detail` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '说明',
  `request_method` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '请求方式',
  `operator_type` tinyint DEFAULT NULL COMMENT '操作类别(0其它 1后台用户 2手机端用户)',
  `oper_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '操作人员',
  `enterprise_id` bigint NOT NULL COMMENT '企业id',
  `store_id` bigint DEFAULT NULL COMMENT '门店名称',
  `oper_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '请求URL',
  `oper_ip` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '主机地址',
  `oper_location` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '操作地点',
  `oper_param` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin COMMENT '请求参数',
  `json_result` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin COMMENT '返回参数',
  `status` tinyint DEFAULT NULL COMMENT '操作状态 (0正常 1异常)',
  `error_msg` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '错误消息',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1776520698685394945 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=DYNAMIC COMMENT='操作日志表';

SET FOREIGN_KEY_CHECKS = 1;

实体类

import com.baomidou.mybatisplus.annotation.TableName;
import com.dam.model.entity.BaseEntity;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;

import java.io.Serializable;

/**
 * 操作日志表
 *
 * @author dam
 * @email 1782067308@qq.com
 * @date 2023-03-13 16:42:08
 */
@Data
@TableName("operation_log")
public class OperationLogEntity extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * 模块标题
     */
    private String title;
    /**
     * 业务类型 (0其它 1新增 2修改 3删除)
     */
    private Integer businessType;
    /**
     * 方法名称
     */
    private String method;
    /**
     * 说明
     */
    private String detail;
    /**
     * 请求方式
     */
    private String requestMethod;
    /**
     * 操作类别(0其它 1后台用户 2手机端用户)
     */
    private Integer operatorType;
    /**
     * 操作人员
     */
    private String operName;
    /**
     * 企业id
     */
    @JsonSerialize(using = ToStringSerializer.class)
    private Long enterpriseId;
    /**
     * 门店名称
     */
    @JsonSerialize(using = ToStringSerializer.class)
    private Long storeId;
    /**
     * 请求URL
     */
    private String operUrl;
    /**
     * 主机地址
     */
    private String operIp;
    /**
     * 操作地点
     */
    private String operLocation;
    /**
     * 请求参数
     */
    private String operParam;
    /**
     * 返回参数
     */
    private String jsonResult;
    /**
     * 操作状态 (0正常 1异常)
     */
    private Integer status;
    /**
     * 错误消息
     */
    private String errorMsg;
}

Service

import com.baomidou.mybatisplus.extension.service.IService;
import com.dam.model.entity.system.OperationLogEntity;
import com.dam.utils.PageUtils;

import java.util.Map;

/**
 * 操作日志表
 *
 * @author dam
 * @email 1782067308@qq.com
 * @date 2023-03-13 16:42:08
 */
public interface OperationLogService extends IService<OperationLogEntity> {

    PageUtils queryPage(Map<String, Object> params, String token);

    OperationLogEntity getById(Long id);
}

实现类

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dam.dao.OperationLogDao;
import com.dam.model.entity.system.OperationLogEntity;
import com.dam.model.entity.system.UserEntity;
import com.dam.model.enums.system.UserCodeEnum;
import com.dam.service.OperationLogService;
import com.dam.service.UserService;
import com.dam.utils.JwtUtil;
import com.dam.utils.PageUtils;
import com.dam.utils.Query;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Map;


@Service("operationLogService")
class OperationLogServiceImpl extends ServiceImpl<OperationLogDao, OperationLogEntity> implements OperationLogService {
    @Autowired
    private OperationLogDao OperationLogDao;
    @Autowired
    private UserService userService;

    @Override
    public PageUtils queryPage(Map<String, Object> params, String token) {
        Long userId = Long.parseLong(JwtUtil.getUserId(token));
        UserEntity user = userService.getById(userId);
        String enterpriseId = JwtUtil.getEnterpriseId(token);
        String storeId = JwtUtil.getStoreId(token);
        QueryWrapper<OperationLogEntity> queryWrapper = new QueryWrapper<>();

        if (user.getType() == UserCodeEnum.TYPE_SYSTEM_MANAGER.getCode().intValue()) {
            //--if--系统管理员,可以查询所有日志
        } else if (user.getType() == UserCodeEnum.TYPE_ENTERPRISE_MANAGER.getCode().intValue()) {
            //--if--企业管理员,只能查询企业的日志
            queryWrapper.eq("enterprise_id", enterpriseId);
        } else if (user.getType() == UserCodeEnum.TYPE_STORE_MANAGER.getCode().intValue()) {
            //--if--门店管理员,只能查询门店的日志
            queryWrapper.eq("store_id", storeId);
        } else {
            //--if--普通用户,什么都查不出来
            queryWrapper.eq("id", -1);
        }

        queryWrapper.orderByDesc("create_time");
        IPage<OperationLogEntity> page = this.page(
                new Query<OperationLogEntity>().getPage(params),
                queryWrapper
        );

        return new PageUtils(page);
    }

    @Override
    public OperationLogEntity getById(Long id) {
        return OperationLogDao.selectById(id);
    }

}

Controller

只讲日志查询和删除开放给用户

import com.alibaba.fastjson.TypeReference;
import com.dam.feign.EnterpriseFeignService;
import com.dam.model.entity.enterprise.EnterpriseEntity;
import com.dam.model.entity.enterprise.StoreEntity;
import com.dam.model.entity.system.OperationLogEntity;
import com.dam.model.enums.ResultCodeEnum;
import com.dam.model.result.R;
import com.dam.model.vo.system.OperationLogVo;
import com.dam.service.OperationLogService;
import com.dam.utils.PageUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.util.*;


/**
 * 操作日志表
 *
 * @author dam
 * @email 1782067308@qq.com
 * @date 2023-03-13 16:42:08
 */
@RestController
@RequestMapping("system/operationLog")
public class OperationLogController {
    @Autowired
    private OperationLogService operationLogService;
    @Autowired
    private EnterpriseFeignService enterpriseFeignService;
    private static final String title = "操作日志管理";

    /**
     * 列表
     */
    @RequestMapping("/list")
    @PreAuthorize("hasAuthority('bnt.operLog.list')")
    public R list(@RequestParam Map<String, Object> params, HttpServletRequest httpRequest) {

        String token = httpRequest.getHeader("token");
        
        查询数据
        PageUtils page = operationLogService.queryPage(params,token);

        封装数据给前端展示
        List<OperationLogEntity> operationLogEntityList = (List<OperationLogEntity>) page.getList();
        List<OperationLogVo> operationLogVoList = new ArrayList<>();
        Set<Long> storeIdList = new HashSet<>();
        Set<Long> enterpriseIdList = new HashSet<>();
        for (OperationLogEntity operationLogEntity : operationLogEntityList) {
            OperationLogVo operationLogVo = new OperationLogVo();
            BeanUtils.copyProperties(operationLogEntity, operationLogVo);
            if (operationLogEntity.getStoreId() != null) {
                enterpriseIdList.add(operationLogEntity.getEnterpriseId());
                storeIdList.add(operationLogEntity.getStoreId());
                operationLogVoList.add(operationLogVo);
            }
        }
        //设置企业名称
        R r1 = enterpriseFeignService.getEnterpriseMapByIdList(new ArrayList<>(enterpriseIdList));
        if (r1.getCode() == ResultCodeEnum.SUCCESS.getCode().intValue()) {
            Map<Long, EnterpriseEntity> idAndEnterpriseEntityMap = r1.getData("idAndEnterpriseEntityMap",
                    new TypeReference<Map<Long, EnterpriseEntity>>() {
                    });
            for (OperationLogVo operationLogVo : operationLogVoList) {
                Long enterpriseId = operationLogVo.getEnterpriseId();
                operationLogVo.setEnterpriseName(idAndEnterpriseEntityMap.get(enterpriseId).getName());
            }
        }
        //设置门店名称
        R r2 = enterpriseFeignService.getStoreMapByIdList(new ArrayList<>(storeIdList));
        if (r2.getCode() == ResultCodeEnum.SUCCESS.getCode().intValue()) {
            Map<Long, StoreEntity> idAndStoreEntityMap = r2.getData("idAndStoreEntityMap",
                    new TypeReference<Map<Long, StoreEntity>>() {
                    });
            for (OperationLogVo operationLogVo : operationLogVoList) {
                Long storeId = operationLogVo.getStoreId();
                operationLogVo.setStoreName(idAndStoreEntityMap.get(storeId).getName());
            }
        }
        page.setList(operationLogVoList);

        return R.ok().addData("page", page);
    }


    /**
     * 信息
     */
    @RequestMapping("/info/{id}")
    @PreAuthorize("hasAuthority('bnt.operLog.list')")
    public R info(@PathVariable("id") Long id) {
        OperationLogEntity operationLog = operationLogService.getById(id);

        return R.ok().addData("operationLog", operationLog);
    }

    /**
     * 保存
     */
//    @RequestMapping("/save")
//    @PreAuthorize("hasAuthority('bnt.operLog.add')")
//    public R save(@RequestBody OperationLogEntity operationLog) {
//        operationLogService.save(operationLog);
//
//        return R.ok();
//    }

    /**
     * 修改
     */
//    @RequestMapping("/update")
//    @PreAuthorize("hasAuthority('bnt.operLog.update')")
//    public R update(@RequestBody OperationLogEntity operationLog) {
//        operationLogService.updateById(operationLog);
//
//        return R.ok();
//    }

    /**
     * 删除
     */
    @RequestMapping("/deleteBatch")
    @PreAuthorize("hasAuthority('bnt.operLog.delete')")
    public R delete(@RequestBody Long[] ids) {
        operationLogService.removeByIds(Arrays.asList(ids));
        return R.ok();
    }

}

Vo

import com.baomidou.mybatisplus.annotation.TableName;
import com.dam.model.entity.BaseEntity;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;

import java.io.Serializable;

/**
 * 操作日志表
 *
 * @author dam
 * @email 1782067308@qq.com
 * @date 2023-03-13 16:42:08
 */
@Data
@TableName("operation_log")
public class OperationLogVo extends BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
     * 模块标题
     */
    private String title;
    /**
     * 业务类型 (0其它 1新增 2修改 3删除)
     */
    private Integer businessType;
    /**
     * 方法名称
     */
    private String method;
    /**
     * 说明
     */
    private String detail;
    /**
     * 请求方式
     */
    private String requestMethod;
    /**
     * 操作类别(0其它 1后台用户 2手机端用户)
     */
    private Integer operatorType;
    /**
     * 操作人员
     */
    private String operName;
    /**
     * 企业id
     */
    @JsonSerialize(using = ToStringSerializer.class)
    private Long enterpriseId;
    /**
     * 企业名称
     */
    private String enterpriseName;
    /**
     * 门店id
     */
    @JsonSerialize(using = ToStringSerializer.class)
    private Long storeId;
    /**
     * 门店名称
     */
    private String storeName;
    /**
     * 请求URL
     */
    private String operUrl;
    /**
     * 主机地址
     */
    private String operIp;
    /**
     * 操作地点
     */
    private String operLocation;
    /**
     * 请求参数
     */
    private String operParam;
    /**
     * 返回参数
     */
    private String jsonResult;
    /**
     * 操作状态 (0正常 1异常)
     */
    private Integer status;
    /**
     * 错误消息
     */
    private String errorMsg;
}

  • 14
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hello Dam

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值