【智能排班系统】AOP实现操作日志自动记录

news2024/11/19 22:33:38

文章目录

  • 操作日志介绍
  • 自动保存操作日志
    • 基本实现思路
    • 定义注解
    • 枚举
      • 业务类型枚举
      • 操作人员类型枚举
    • AOP具体实现
    • 方法上添加注解
  • 日志增删改查
    • 日志表sql
    • 实体类
    • Service
    • Controller
    • Vo

操作日志介绍

在这里插入图片描述

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

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

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

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

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

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

自动保存操作日志

基本实现思路

定义注解,将注解添加到需要记录日志的方法上,当方法执行完成或者抛异常后,通过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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1572987.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

代码随想录算法训练营Day46|LC139 单词拆分

一句话总结&#xff1a;完全背包&#xff01; 原题链接&#xff1a;139 单词拆分 动态规划之完全背包五部曲&#xff1a; 确定dp数组与下标含义&#xff1a;表示字符串长度为i时&#xff0c;dp[i] true 的话&#xff0c;可以拆分为一个或多个在字典中出现的单词。确定递归公…

贪心算法|122.买卖股票的最佳时机II

力扣题目链接 class Solution { public:int maxProfit(vector<int>& prices) {int result 0;for (int i 1; i < prices.size(); i) {result max(prices[i] - prices[i - 1], 0);}return result;} }; 贪心思路出来了&#xff0c;代码居然如此简单啊&#xff0…

16.Python多线程

如果想让我们的程序同时执行多个任务&#xff0c;就需要使用多线程技术了 。到目前为止&#xff0c;我们编写的程序都是单线程的&#xff0c;在运行时一次只能执行 一个任务。 1 线程相关的知识 1.1 进程 一个进程就是一个正在执行的程序&#xff0c;每一个进程都有自己独立…

软考高级架构师:嵌入式处理器体系结构

一、AI 讲解 嵌入式处理器体系结构中&#xff0c;冯诺依曼结构和哈佛结构是两种最基本的设计模式&#xff0c;它们各有特点和典型应用场景。 结构定义特点典型应用冯诺依曼结构一种将程序存储器和数据存储器合并在同一存储器中的计算机体系结构。这意味着指令和数据共享同一个…

基于javassm实现的水果销售管理网站

开发语言&#xff1a;Java 框架&#xff1a;ssm 技术&#xff1a;JSP JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09; 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclip…

【C++航海王:追寻罗杰的编程之路】C++的类型转换

目录 1 -> C语言中的类型转换 2 -> 为什么C需要四种类型转换 3 -> C强制类型转换 3.1 -> static_cast 3.2 -> reinterpret_cast 3.3 -> const_cast 3.4 -> dynamic_cast 4 -> RTTI 1 -> C语言中的类型转换 在C语言中&#xff0c;如果赋值运…

【攻防世界】FlatScience

dirsearch 扫描发现四个文件 在login.php 中发现 输入 http://61.147.171.105:61912/login.php/?debug 发现源码 <?php if(isset($_POST[usr]) && isset($_POST[pw])){$user $_POST[usr];$pass $_POST[pw];$db new SQLite3(../fancy.db);$res $db->query(…

【STM32】存储器和位带映射(bit band mapping)

文章目录 0 前言1 关于地址和存储器2 STM32内部存储器3 位带映射&#xff08;bit band mapping&#xff09;4 扩展&#xff1a;IAP 0 前言 最近在研究stm32标准库&#xff0c;对使用宏定义实现位操作的函数非常感兴趣&#xff0c;简单的一句PAout(1) 0;就能实现某个引脚电平的…

【JavaWeb】Day35.MySQL概述——数据库设计-DDL(二)

表操作 关于表结构的操作也是包含四个部分&#xff1a;创建表、查询表、修改表、删除表。 1.创建 语法 create table 表名( 字段1 字段1类型 [约束] [comment 字段1注释 ], 字段2 字段2类型 [约束] [comment 字段2注释 ], ...... 字段n 字段n类型 [约束] [comment …

phpstorm设置头部注释和自定义注释内容

先说设置位置&#xff1a; PhpStorm中文件、类、函数等注释的设置在&#xff1a;setting-》Editor-》FIle and Code Template-》Includes-》PHP Function Doc Comment下设置即可&#xff0c;其中方法的默认是这样的&#xff1a; /** ${PARAM_DOC} #if (${TYPE_HINT} ! "…

SpringBoot新增员工模块开发

需求分析与设计 一&#xff1a;产品原型 一般在做需求分析时&#xff0c;往往都是对照着产品原型进行分析&#xff0c;因为产品原型比较直观&#xff0c;便于我们理解业务。 后台系统中可以管理员工信息&#xff0c;通过新增员工来添加后台系统用户。 新增员工原型&#xf…

4.1 JavaScript的使用

JavaScript有两种使用方式&#xff1a;一是在HTML文档中直接添加代码&#xff1b;二是将JavaScript脚本代码写到外部的JavaScript文件中&#xff0c;再在HTML文档中引用该文件的路径地址。 这两种使用方式的效果完全相同&#xff0c;可以根据使用率和代码量选择相应的开发方式。…

【ControlNet v3版本论文阅读】

网络部分最好有LDM或者Stable Diffusion的基础&#xff0c;有基础的话会看的很轻松 Abstract 1.提出了一种网络结构支持额外输入条件控制大型预训练的扩散模型。利用预训练模型学习一组不同的条件控制。 2.ControlNet对于小型&#xff08;<50k&#xff09;或大型&#xff…

经典机器学习模型(九)EM算法在高斯混合模型中的应用

经典机器学习模型(九)EM算法在高斯混合模型中的应用 EM算法的推导可以参考&#xff1a; 经典机器学习模型(九)EM算法的推导 若随机变量X服从一个数学期望为 μ μ μ、方差为 σ 2 σ^2 σ2的正态分布&#xff0c;可以记为 N ( μ &#xff0c; σ 2 ) N(μ&#xff0c;σ2)…

二叉树进阶——手撕二叉搜索树

troop主页&#xff1a;troop 手撕二叉搜索树 1.二叉搜索树的定义2.实现&#xff08;非递归&#xff09;补充结构2.1查找2.2插入2.3删除&#xff08;重要&#xff09;情况1(无孩子&&一个孩子&#xff09; 3.二叉搜索树的应用3.1K模型3.2KV模型3.2.1KV模型的实现 总结二叉…

「每日跟读」英语常用句型公式 第4篇

「每日跟读」英语常用句型公式 第4篇 1. I’ve decided to ____ 我决定要____了 I’ve decided to take a vacation (我决定要去度假) I’ve decided to change my lifestyle (我决定要改变我的生活方式) I’ve decided to adopt a dog (我决定要收养一条狗了) I’ve dec…

【AOSP】手把手教你编译和调试AOSP源码

一、下载AOSP源码 在开始之前&#xff0c;我们先安装编译AOSP需要的一些系统基本依赖&#xff0c;如下命令 sudo apt-get install git-core gnupg flex bison gperf build-essential zip curl zlib1g-dev gcc-multilib g-multilib libc6-dev-i386 lib32ncurses5-dev x11proto…

三子棋游戏----C语言版【超级详细 + 视频演示 + 完整源码】

㊙️小明博客主页&#xff1a;➡️ 敲键盘的小明 ㊙️ ✅关注小明了解更多知识☝️ 文章目录 前言一、三子棋的实现思路二、三子棋的实现步骤2.1 先显示游戏的菜单2.2 游戏的具体实现2.2.1 棋盘的初始化2.2.2 展示棋盘2.2.3 下棋&#x1f534;玩家下棋&#x1f534;电脑下棋2.2…

OpenHarmony开发-系统烧录

本文详细介绍了烧录OpenHarmony系统到开发板的操作流程。从基础的硬件准备和软件环境设置入手&#xff0c;详细说明了如何配置开发环境、构建系统镜像等过程&#xff0c;详细描述了烧录过程中的关键步骤&#xff0c;以及如何使用专用工具将OpenHarmony系统镜像传输到开发板。同…

【Rust】环境搭建

Rust 支持很多的集成开发环境&#xff08;IDE&#xff09;或开发专用的文本编辑器。 官方网站公布支持的工具如下&#xff08;工具 - Rust 程序设计语言&#xff09; 本课程将使用 Visual Studio Code 作为我们的开发环境&#xff08;Eclipse 有专用于 Rust 开发的版本&#…