24282 преди 1 месец
родител
ревизия
0c9b4d4c6f
променени са 19 файла, в които са добавени 1052 реда и са изтрити 6 реда
  1. 29 3
      jy-business/src/main/java/com/jy/business/corporation/controller/CorporationController.java
  2. 2 0
      jy-business/src/main/java/com/jy/business/corporation/service/CorporationService.java
  3. 2 0
      jy-business/src/main/java/com/jy/business/corporation/service/impl/CorporationServiceImpl.java
  4. 38 0
      jy-framework/src/main/java/com/jy/framework/model/annotation/RecordAudit.java
  5. 3 3
      jy-generator/src/main/java/com/jy/generator/LogGenerator.java
  6. 71 0
      jy-log/src/main/java/com/jy/log/controller/LogUserOperationController.java
  7. 57 0
      jy-log/src/main/java/com/jy/log/dao/LogUserOperationDao.java
  8. 16 0
      jy-log/src/main/java/com/jy/log/mapper/LogUserOperationMapper.java
  9. 5 0
      jy-log/src/main/java/com/jy/log/mapper/xml/LogUserOperationMapper.xml
  10. 17 0
      jy-log/src/main/java/com/jy/log/model/dto/LogUserOperationDto.java
  11. 17 0
      jy-log/src/main/java/com/jy/log/model/dto/LogUserOperationSelectDto.java
  12. 111 0
      jy-log/src/main/java/com/jy/log/model/entity/LogUserOperation.java
  13. 114 0
      jy-log/src/main/java/com/jy/log/model/table/LogUserOperationTable.java
  14. 17 0
      jy-log/src/main/java/com/jy/log/model/vo/LogUserOperationVo.java
  15. 117 0
      jy-log/src/main/java/com/jy/log/record/RecordAuditAspect.java
  16. 47 0
      jy-log/src/main/java/com/jy/log/service/LogUserOperationService.java
  17. 108 0
      jy-log/src/main/java/com/jy/log/service/impl/LogUserOperationServiceImpl.java
  18. 27 0
      jy-ui/src/api/business/log/userOperation.ts
  19. 254 0
      jy-ui/src/views/business/log/userOperation/index.vue

+ 29 - 3
jy-business/src/main/java/com/jy/business/corporation/controller/CorporationController.java

@@ -5,7 +5,9 @@ import com.jy.business.corporation.model.dto.CorporationDto;
 import com.jy.business.corporation.model.dto.CorporationSelectDto;
 import com.jy.business.corporation.model.vo.CorporationVo;
 import com.jy.business.corporation.service.CorporationService;
+import com.jy.framework.model.annotation.RecordAudit;
 import com.jy.framework.model.base.BaseSelectDto;
+import com.jy.framework.model.enums.BusinessType;
 import com.jy.framework.model.validation.AddGroup;
 import com.jy.framework.model.validation.BatchDeleteGroup;
 import com.jy.framework.model.validation.DetailGroup;
@@ -18,6 +20,8 @@ import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
+import java.util.List;
+
 /**
  * <p>
  * 公司信息 前端控制器
@@ -33,9 +37,6 @@ public class CorporationController {
     @Resource
     private CorporationService corporationService;
 
-    /**
-     * 公司信息分页
-     */
     @GetMapping("/getPage")
     public Page<CorporationVo> getPage(CorporationSelectDto dto) {
         return corporationService.getPage(dto);
@@ -52,6 +53,11 @@ public class CorporationController {
     /**
      * 公司信息新增
      */
+    @RecordAudit(
+            moduleFlag = "corporation-add",
+            moduleName = "公司管理-新增",
+            businessType = BusinessType.INSERT
+    )
     @PostMapping("/add")
     public void add(@Validated(AddGroup.class) @RequestBody CorporationDto dto) {
         corporationService.add(dto);
@@ -60,6 +66,12 @@ public class CorporationController {
     /**
      * 公司信息编辑
      */
+    @RecordAudit(
+            moduleFlag = "corporation-edit",
+            moduleName = "公司管理-修改",
+            businessType = BusinessType.UPDATE,
+            getOldDataFunName = "getDetail"
+    )
     @PostMapping("/edit")
     public void edit(@Validated(EditGroup.class) @RequestBody CorporationDto dto) {
         corporationService.edit(dto);
@@ -68,9 +80,23 @@ public class CorporationController {
     /**
      * 公司信息删除
      */
+    @RecordAudit(
+            moduleFlag = "corporation-delete",
+            moduleName = "公司管理-删除",
+            businessType = BusinessType.DELETE,
+            getOldDataFunName = "getDetailList"
+    )
     @PostMapping("/delete")
     public void delete(@Validated(BatchDeleteGroup.class) @RequestBody BaseSelectDto dto) {
         corporationService.delete(dto.getIdList());
     }
 
+    public CorporationVo getDetail(CorporationDto dto) {
+        return corporationService.getDetail(dto.getId());
+    }
+
+    public List<CorporationVo> getDetailList(BaseSelectDto dto) {
+        return dto.getIdList().stream().map(id -> corporationService.getDetail(id)).toList();
+    }
+
 }

+ 2 - 0
jy-business/src/main/java/com/jy/business/corporation/service/CorporationService.java

@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.jy.business.corporation.model.dto.CorporationDto;
 import com.jy.business.corporation.model.dto.CorporationSelectDto;
 import com.jy.business.corporation.model.vo.CorporationVo;
+import com.jy.framework.model.annotation.RecordAudit;
 
 import java.util.List;
 
@@ -15,6 +16,7 @@ import java.util.List;
  * @author
  * @since 2024-10-16
  */
+
 public interface CorporationService {
 
     /**

+ 2 - 0
jy-business/src/main/java/com/jy/business/corporation/service/impl/CorporationServiceImpl.java

@@ -7,6 +7,8 @@ import com.jy.business.corporation.model.dto.CorporationDto;
 import com.jy.business.corporation.model.dto.CorporationSelectDto;
 import com.jy.business.corporation.model.vo.CorporationVo;
 import com.jy.business.corporation.service.CorporationService;
+import com.jy.framework.model.annotation.RecordAudit;
+import com.jy.framework.model.enums.BusinessType;
 import com.jy.framework.utils.AssertUtil;
 import jakarta.annotation.Resource;
 import org.springframework.stereotype.Service;

+ 38 - 0
jy-framework/src/main/java/com/jy/framework/model/annotation/RecordAudit.java

@@ -0,0 +1,38 @@
+package com.jy.framework.model.annotation;
+
+import com.jy.framework.model.enums.BusinessType;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface RecordAudit {
+
+    String moduleFlag();
+
+    /**
+     * 方法名称
+     */
+    String moduleName();
+
+    /**
+     * 功能
+     */
+    BusinessType businessType();
+
+    /**
+     * 获取旧数据bean
+     */
+    String getOldDataBeanName() default "";
+
+    /**
+     * 获取旧数据方法
+     */
+    String getOldDataFunName() default "";
+
+}

+ 3 - 3
jy-generator/src/main/java/com/jy/generator/LogGenerator.java

@@ -7,9 +7,9 @@ public class LogGenerator {
     public static void main(String[] args) {
         GeneratorBuilder
                 .builder(
-                        "jdbc:mysql://127.0.0.1:3306/jy4.0?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true",
-                        "root",
-                        "root"
+                        "jdbc:mysql://rm-gw0i0555rlxx31n834o.mysql.cn-chengdu.rds.aliyuncs.com:9642/jy_erp_test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=Asia/Shanghai",
+                        "e4O5x_OTKr4w_hH",
+                        "cn0J5y3DsvdP1M-Y8AyD"
                 )
                 .port(9091)
                 .module("jy-log")

+ 71 - 0
jy-log/src/main/java/com/jy/log/controller/LogUserOperationController.java

@@ -0,0 +1,71 @@
+package com.jy.log.controller;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.jy.log.model.dto.LogUserOperationDto;
+import com.jy.log.model.dto.LogUserOperationSelectDto;
+import com.jy.log.model.vo.LogUserOperationVo;
+import com.jy.log.service.LogUserOperationService;
+import com.jy.framework.model.base.BaseSelectDto;
+import jakarta.annotation.Resource;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * <p>
+ * 用户操作日志 前端控制器
+ * </p>
+ *
+ * @author 
+ * @since 2025-08-12
+ */
+@RestController
+@RequestMapping("/logUserOperation")
+public class LogUserOperationController {
+
+    @Resource
+    private LogUserOperationService logUserOperationService;
+
+    /**
+     * 用户操作日志分页
+     */
+    @GetMapping("/getPage")
+    public Page<LogUserOperationVo> getPage(LogUserOperationSelectDto dto) {
+        return logUserOperationService.getPage(dto);
+    }
+
+    /**
+     * 用户操作日志明细
+     */
+    @GetMapping("/getDetail")
+    public LogUserOperationVo getDetail(BaseSelectDto dto) {
+        return logUserOperationService.getDetail(dto.getId());
+    }
+
+    /**
+     * 用户操作日志新增
+     */
+    @PostMapping("/add")
+    public void add(@RequestBody LogUserOperationDto dto) {
+        logUserOperationService.add(dto);
+    }
+
+    /**
+     * 用户操作日志编辑
+     */
+    @PostMapping("/edit")
+    public void edit(@RequestBody LogUserOperationDto dto) {
+        logUserOperationService.edit(dto);
+    }
+
+    /**
+     * 用户操作日志删除
+     */
+    @PostMapping("/delete")
+    public void delete(@RequestBody BaseSelectDto dto) {
+        logUserOperationService.delete(dto.getIdList());
+    }
+
+}

+ 57 - 0
jy-log/src/main/java/com/jy/log/dao/LogUserOperationDao.java

@@ -0,0 +1,57 @@
+package com.jy.log.dao;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.jy.log.mapper.LogUserOperationMapper;
+import com.jy.log.model.dto.LogUserOperationSelectDto;
+import com.jy.log.model.entity.LogUserOperation;
+import com.jy.log.model.table.LogUserOperationTable;
+import com.jy.log.model.vo.LogUserOperationVo;
+import com.jy.framework.model.base.BaseDao;
+import com.jy.system.service.AuthService;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+
+@Service
+public class LogUserOperationDao extends BaseDao<LogUserOperationMapper, LogUserOperation> {
+
+    @Resource
+    private AuthService authService;
+
+    /**
+     * 用户操作日志分页
+     */
+    public Page<LogUserOperationVo> getPage(LogUserOperationSelectDto dto) {
+        LogUserOperationTable luo = LogUserOperationTable.luo;
+
+        return sql(LogUserOperationVo.class)
+                .select(
+                        luo.all
+                )
+                .from(luo)
+                .where(
+                        luo.createUser.in(authService.getUserPermissionSet())
+                )
+                .orderBy(
+                        luo.id.desc()
+                )
+                .page(dto.getPage());
+    }
+
+    /**
+     * 用户操作日志明细
+     */
+    public LogUserOperationVo getDetail(Long id) {
+        LogUserOperationTable luo = LogUserOperationTable.luo;
+
+        return sql(LogUserOperationVo.class)
+                .select(
+                        luo.all
+                )
+                .from(luo)
+                .where(
+                        luo.id.eq(id)
+                )
+                .one();
+    }
+
+}

+ 16 - 0
jy-log/src/main/java/com/jy/log/mapper/LogUserOperationMapper.java

@@ -0,0 +1,16 @@
+package com.jy.log.mapper;
+
+import com.jy.log.model.entity.LogUserOperation;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * <p>
+ * 用户操作日志 Mapper 接口
+ * </p>
+ *
+ * @author 
+ * @since 2025-08-12
+ */
+public interface LogUserOperationMapper extends BaseMapper<LogUserOperation> {
+
+}

+ 5 - 0
jy-log/src/main/java/com/jy/log/mapper/xml/LogUserOperationMapper.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.jy.log.mapper.LogUserOperationMapper">
+
+</mapper>

+ 17 - 0
jy-log/src/main/java/com/jy/log/model/dto/LogUserOperationDto.java

@@ -0,0 +1,17 @@
+package com.jy.log.model.dto;
+
+import com.jy.log.model.entity.LogUserOperation;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 用户操作日志新增编辑入参实体
+ *
+ * @author 
+ * @since 2025-08-12
+ */
+@Getter
+@Setter
+public class LogUserOperationDto extends LogUserOperation {
+
+}

+ 17 - 0
jy-log/src/main/java/com/jy/log/model/dto/LogUserOperationSelectDto.java

@@ -0,0 +1,17 @@
+package com.jy.log.model.dto;
+
+import com.jy.framework.model.base.BaseSelectDto;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 用户操作日志列表查询入参实体
+ *
+ * @author 
+ * @since 2025-08-12
+ */
+@Getter
+@Setter
+public class LogUserOperationSelectDto extends BaseSelectDto {
+
+}

+ 111 - 0
jy-log/src/main/java/com/jy/log/model/entity/LogUserOperation.java

@@ -0,0 +1,111 @@
+package com.jy.log.model.entity;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.jy.framework.model.base.BaseIdPo;
+import java.util.Date;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * <p>
+ * 用户操作日志
+ * </p>
+ *
+ * @author 
+ * @since 2025-08-12
+ */
+@Getter
+@Setter
+@TableName("log_user_operation")
+public class LogUserOperation extends BaseIdPo {
+
+    /**
+     * 方法标签
+     */
+    private String moduleFlag;
+
+    /**
+     * 模块标题
+     */
+    private String moduleName;
+
+    /**
+     * service的Bean名称
+     */
+    private String serviceBeanName;
+
+    /**
+     * 调用的方法名
+     */
+    private String methodName;
+
+    /**
+     * 入参类名
+     */
+    private String reqClasses;
+
+    /**
+     * 操作人ID
+     */
+    private Long operatorId;
+
+    /**
+     * 操作人姓名
+     */
+    private String operatorName;
+
+    /**
+     * 操作类型 2新增 3修改 4删除
+     */
+    private Integer operationType;
+
+    /**
+     * 业务id
+     */
+    private Long businessId;
+
+    /**
+     * 操作数据(JSON格式)
+     */
+    private String operationData;
+
+    /**
+     * 原始数据(JSON格式)
+     */
+    private String sourceData;
+
+    /**
+     * 操作描述
+     */
+    private String description;
+
+    /**
+     * 审核状态 0待审核 1通过 2拒绝
+     */
+    private Integer auditStatus;
+
+    /**
+     * 审核时间
+     */
+    private Date auditTime;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    /**
+     * 创建者
+     */
+    @TableField(fill = FieldFill.INSERT)
+    private Long createUser;
+
+    /**
+     * 创建时间
+     */
+    @TableField(fill = FieldFill.INSERT)
+    private Date createTime;
+
+}

+ 114 - 0
jy-log/src/main/java/com/jy/log/model/table/LogUserOperationTable.java

@@ -0,0 +1,114 @@
+package com.jy.log.model.table;
+
+import com.jy.log.model.entity.LogUserOperation;
+import com.jy.framework.mybatis.join.QueryColumn;
+import com.jy.framework.mybatis.join.Table;
+
+public class LogUserOperationTable extends Table<LogUserOperation> {
+
+    public static LogUserOperationTable log_user_operation = new LogUserOperationTable();
+    public static LogUserOperationTable luo = log_user_operation.as("luo");
+
+    /**
+     * 用户操作日志id
+     */
+    public QueryColumn id = this.field(LogUserOperation::getId);
+
+    /**
+     * 方法标签
+     */
+    public QueryColumn moduleFlag = this.field(LogUserOperation::getModuleFlag);
+
+    /**
+     * 模块标题
+     */
+    public QueryColumn moduleName = this.field(LogUserOperation::getModuleName);
+
+    /**
+     * service的Bean名称
+     */
+    public QueryColumn serviceBeanName = this.field(LogUserOperation::getServiceBeanName);
+
+    /**
+     * 调用的方法名
+     */
+    public QueryColumn methodName = this.field(LogUserOperation::getMethodName);
+
+    /**
+     * 入参类名
+     */
+    public QueryColumn reqClasses = this.field(LogUserOperation::getReqClasses);
+
+    /**
+     * 操作人ID
+     */
+    public QueryColumn operatorId = this.field(LogUserOperation::getOperatorId);
+
+    /**
+     * 操作人姓名
+     */
+    public QueryColumn operatorName = this.field(LogUserOperation::getOperatorName);
+
+    /**
+     * 操作类型 2新增 3修改 4删除
+     */
+    public QueryColumn operationType = this.field(LogUserOperation::getOperationType);
+
+    /**
+     * 业务id
+     */
+    public QueryColumn businessId = this.field(LogUserOperation::getBusinessId);
+
+    /**
+     * 操作数据(JSON格式)
+     */
+    public QueryColumn operationData = this.field(LogUserOperation::getOperationData);
+
+    /**
+     * 原始数据(JSON格式)
+     */
+    public QueryColumn sourceData = this.field(LogUserOperation::getSourceData);
+
+    /**
+     * 操作描述
+     */
+    public QueryColumn description = this.field(LogUserOperation::getDescription);
+
+    /**
+     * 审核状态 0待审核 1通过 2拒绝
+     */
+    public QueryColumn auditStatus = this.field(LogUserOperation::getAuditStatus);
+
+    /**
+     * 审核时间
+     */
+    public QueryColumn auditTime = this.field(LogUserOperation::getAuditTime);
+
+    /**
+     * 备注
+     */
+    public QueryColumn remark = this.field(LogUserOperation::getRemark);
+
+    /**
+     * 创建者
+     */
+    public QueryColumn createUser = this.field(LogUserOperation::getCreateUser);
+
+    /**
+     * 创建时间
+     */
+    public QueryColumn createTime = this.field(LogUserOperation::getCreateTime);
+
+    private LogUserOperationTable() {
+        super(LogUserOperation.class);
+    }
+
+    private LogUserOperationTable(String alias) {
+        super(LogUserOperation.class, alias);
+    }
+
+    public LogUserOperationTable as(String alias) {
+        return new LogUserOperationTable(alias);
+    }
+
+}

+ 17 - 0
jy-log/src/main/java/com/jy/log/model/vo/LogUserOperationVo.java

@@ -0,0 +1,17 @@
+package com.jy.log.model.vo;
+
+import com.jy.log.model.entity.LogUserOperation;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 用户操作日志列表查询返回值实体
+ *
+ * @author 
+ * @since 2025-08-12
+ */
+@Getter
+@Setter
+public class LogUserOperationVo extends LogUserOperation {
+
+}

+ 117 - 0
jy-log/src/main/java/com/jy/log/record/RecordAuditAspect.java

@@ -0,0 +1,117 @@
+
+package com.jy.log.record;
+
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.extra.spring.SpringUtil;
+import com.alibaba.fastjson2.JSONObject;
+import com.jy.framework.model.annotation.RecordAudit;
+import com.jy.framework.model.base.BaseIdPo;
+import com.jy.framework.satoken.LoginContext;
+import com.jy.log.model.entity.LogUserOperation;
+import com.jy.log.service.LogUserOperationService;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.lang.reflect.Method;
+
+/**
+ * 操作日志记录切面
+ */
+@Slf4j
+@Aspect
+@Component
+public class RecordAuditAspect {
+
+    public static final ThreadLocal<Boolean> EXIST_FUN = ThreadLocal.withInitial(() -> false);
+
+    @Autowired
+    private LogUserOperationService logUserOperationService;
+
+    @Around("@annotation(recordAudit)")
+    public Object around(ProceedingJoinPoint point, RecordAudit recordAudit) throws Throwable {
+        if (EXIST_FUN.get()) {
+            return point.proceed();
+        }
+
+        Object result = null;
+
+        Object[] args = point.getArgs();
+
+        // 获取方法签名
+        MethodSignature signature = (MethodSignature) point.getSignature();
+        // 获取完整方法路径
+        String declaringTypeName = signature.getDeclaringTypeName();
+        // 获取方法名称
+        String methodName = signature.getName();
+
+        // 获取参数类型数组
+        Class<?>[] parameterTypes = signature.getParameterTypes();
+        String jsonString = JSONObject.toJSONString(parameterTypes);
+
+        LogUserOperation logUserOperation = new LogUserOperation();
+        logUserOperation.setModuleFlag(recordAudit.moduleFlag());
+        logUserOperation.setModuleName(recordAudit.moduleName());
+        logUserOperation.setServiceBeanName(declaringTypeName);
+        logUserOperation.setMethodName(methodName);
+        logUserOperation.setReqClasses(jsonString);
+        logUserOperation.setOperatorId(LoginContext.getUserId());
+        logUserOperation.setOperatorName(LoginContext.getNickname());
+        logUserOperation.setOperationType(recordAudit.businessType().getKey());
+        logUserOperation.setOperationData(JSONObject.toJSONString(args));
+
+        switch (recordAudit.businessType()) {
+            case INSERT -> {
+                logUserOperation.setAuditStatus(1);
+                result = point.proceed();
+
+                if (result instanceof Long id) {
+                    logUserOperation.setBusinessId(id);
+                }
+                if (result instanceof BaseIdPo baseIdPo) {
+                    logUserOperation.setBusinessId(baseIdPo.getId());
+                }
+            }
+            case UPDATE -> {
+                logUserOperation.setAuditStatus(0);
+
+                if (args != null && args.length == 1 && args[0] instanceof BaseIdPo) {
+                    logUserOperation.setBusinessId(((BaseIdPo) args[0]).getId());
+                }
+
+                Object oldData = getOldData(point, signature, recordAudit);
+                logUserOperation.setSourceData(JSONObject.toJSONString(oldData));
+            }
+            case DELETE -> {
+                logUserOperation.setAuditStatus(0);
+
+                Object oldData = getOldData(point, signature, recordAudit);
+                logUserOperation.setSourceData(JSONObject.toJSONString(oldData));
+            }
+        }
+
+        logUserOperationService.add(logUserOperation);
+
+        return result;
+    }
+
+
+    /**
+     * 获取原数据
+     */
+    @SneakyThrows
+    private Object getOldData(ProceedingJoinPoint point, MethodSignature signature, RecordAudit recordAudit) {
+        String oldDataBeanName = recordAudit.getOldDataBeanName();
+        String oldDataFunName = recordAudit.getOldDataFunName();
+
+        Object bean = StrUtil.isEmpty(oldDataBeanName) ? point.getTarget() : SpringUtil.getBean(oldDataBeanName);
+        Method method = bean.getClass().getMethod(oldDataFunName, signature.getParameterTypes());
+        return method.invoke(bean, point.getArgs());
+    }
+
+}

+ 47 - 0
jy-log/src/main/java/com/jy/log/service/LogUserOperationService.java

@@ -0,0 +1,47 @@
+package com.jy.log.service;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.jy.log.model.dto.LogUserOperationDto;
+import com.jy.log.model.dto.LogUserOperationSelectDto;
+import com.jy.log.model.entity.LogUserOperation;
+import com.jy.log.model.vo.LogUserOperationVo;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.List;
+
+/**
+ * <p>
+ * 用户操作日志 服务类
+ * </p>
+ *
+ * @author
+ * @since 2025-08-12
+ */
+public interface LogUserOperationService {
+
+    /**
+     * 用户操作日志分页
+     */
+    Page<LogUserOperationVo> getPage(LogUserOperationSelectDto dto);
+
+    /**
+     * 用户操作日志明细
+     */
+    LogUserOperationVo getDetail(Long id);
+
+    /**
+     * 用户操作日志新增
+     */
+    void add(LogUserOperation dto);
+
+    /**
+     * 用户操作日志编辑
+     */
+    void edit(LogUserOperationDto dto) ;
+
+    /**
+     * 用户操作日志删除
+     */
+    void delete(List<Long> idList);
+
+}

+ 108 - 0
jy-log/src/main/java/com/jy/log/service/impl/LogUserOperationServiceImpl.java

@@ -0,0 +1,108 @@
+package com.jy.log.service.impl;
+
+import cn.hutool.extra.spring.SpringUtil;
+import com.alibaba.fastjson2.JSONArray;
+import com.alibaba.fastjson2.JSONObject;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.jy.framework.exception.ServiceException;
+import com.jy.framework.utils.AssertUtil;
+import com.jy.log.dao.LogUserOperationDao;
+import com.jy.log.model.dto.LogUserOperationDto;
+import com.jy.log.model.dto.LogUserOperationSelectDto;
+import com.jy.log.model.entity.LogUserOperation;
+import com.jy.log.model.vo.LogUserOperationVo;
+import com.jy.log.record.RecordAuditAspect;
+import com.jy.log.service.LogUserOperationService;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.lang.reflect.Method;
+import java.util.List;
+
+/**
+ * <p>
+ * 用户操作日志 服务实现类
+ * </p>
+ *
+ * @author
+ * @since 2025-08-12
+ */
+@Slf4j
+@Service
+public class LogUserOperationServiceImpl implements LogUserOperationService {
+
+    @Resource
+    private LogUserOperationDao logUserOperationDao;
+
+    @Override
+    public Page<LogUserOperationVo> getPage(LogUserOperationSelectDto dto) {
+        return logUserOperationDao.getPage(dto);
+    }
+
+    @Override
+    public LogUserOperationVo getDetail(Long id) {
+        LogUserOperationVo vo = logUserOperationDao.getDetail(id);
+        AssertUtil.notNull(vo, "未知数据");
+        return vo;
+    }
+
+    @Override
+    public void add(LogUserOperation dto) {
+        logUserOperationDao.save(dto);
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public void edit(LogUserOperationDto dto) {
+        logUserOperationDao.updateById(dto);
+
+        //if (dto.getAuditStatus().equals(1)) {
+            try {
+                RecordAuditAspect.EXIST_FUN.set(true);
+
+                // 获取springBean
+                Object bean = SpringUtil.getBean(Class.forName(dto.getServiceBeanName()));
+
+                // 获取入参class
+                Class<?>[] array = JSONArray.parseArray(dto.getReqClasses()).toJavaList(String.class).stream()
+                        .map(item -> {
+                            try {
+                                return Class.forName(item);
+                            } catch (ClassNotFoundException e) {
+                                throw new RuntimeException(e);
+                            }
+                        }).toArray(Class[]::new);
+
+                // 获取执行方法
+                Method method = bean.getClass().getMethod(dto.getMethodName(), array);
+
+                // 获取方法参数
+                Object[] params = new Object[array.length];
+                List<JSONObject> paramList = JSONArray.parseArray(dto.getOperationData()).toJavaList(JSONObject.class);
+                for (int i = 0; i < paramList.size(); i++) {
+                    params[i] = paramList.get(i).toJavaObject(array[i]);
+                }
+
+                // 执行方法
+                method.invoke(bean, params);
+
+                System.out.print("");
+            } catch (Exception e) {
+                log.error(e.getMessage(), e);
+                throw new ServiceException("业务执行失败");
+            } finally {
+                RecordAuditAspect.EXIST_FUN.remove();
+            }
+        //}
+
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public void delete(List<Long> idList) {
+        logUserOperationDao.removeBatchByIds(idList);
+    }
+
+}

+ 27 - 0
jy-ui/src/api/business/log/userOperation.ts

@@ -0,0 +1,27 @@
+import request from '@/utils/request'
+import { PageType, StrAnyObj } from '@/typings'
+
+// 用户操作日志分页
+export function getPageApi(params: StrAnyObj): Promise<PageType<StrAnyObj>> {
+  return request.get('/logUserOperation/getPage', params)
+}
+
+// 用户操作日志明细
+export function getDetailApi(params: StrAnyObj): Promise<StrAnyObj> {
+  return request.get('/logUserOperation/getDetail', params)
+}
+
+// 用户操作日志新增
+export function addApi(data: StrAnyObj): Promise<void> {
+  return request.post('/logUserOperation/add', data)
+}
+
+// 用户操作日志编辑
+export function editApi(data: StrAnyObj): Promise<void> {
+  return request.post('/logUserOperation/edit', data)
+}
+
+// 用户操作日志删除
+export function deleteApi(data: StrAnyObj): Promise<void> {
+  return request.post('/logUserOperation/delete', data)
+}

+ 254 - 0
jy-ui/src/views/business/log/userOperation/index.vue

@@ -0,0 +1,254 @@
+<script setup lang="ts">
+import AForm from '@/components/AForm/index.vue'
+import { FormConfigType } from '@/components/AForm/type'
+import { ToolbarConfigType } from '@/components/AToolbar/type'
+import { ColumnConfigType } from '@/components/ATable/type'
+import { StrAnyObj, StrAnyObjArr } from '@/typings'
+import { useHandleData } from '@/utils/useHandleData'
+import { getPageApi, getDetailApi, addApi, editApi, deleteApi } from '@/api/business/log/userOperation'
+
+const queryRef = ref<InstanceType<typeof AForm>>()
+const formRef = ref<InstanceType<typeof AForm>>()
+
+const showQuery = ref<boolean>(true)
+const selectKeys = ref<string[]>([])
+const pageTotal = ref<number>(0)
+
+const queryData = ref<StrAnyObj>({ pageNum: 1, pageSize: 10 })
+const tableData = ref<StrAnyObjArr>([])
+const formData = ref<StrAnyObj>({})
+
+const dialogTitle = ref<string>('')
+const dialogVisible = ref<boolean>(false)
+
+const queryConfig: FormConfigType[] = [
+  {
+    type: 'input',
+    prop: 'moduleName',
+    label: '操作模块'
+  },
+  {
+    type: 'input',
+    prop: 'operatorName',
+    label: '操作人'
+  },
+  {
+    type: 'select',
+    prop: 'operationType',
+    label: '操作类型',
+    option: [
+      {
+        key: 2,
+        label: '新增'
+      },
+      {
+        key: 3,
+        label: '修改'
+      },
+      {
+        key: 4,
+        label: '删除'
+      }
+    ]
+  },
+  {
+    type: 'select',
+    prop: 'auditStatus',
+    label: '审核状态',
+    option: [
+      {
+        key: 0,
+        label: '待审核'
+      },
+      {
+        key: 1,
+        label: '通过'
+      },
+      {
+        key: 2,
+        label: '拒绝'
+      }
+    ]
+  }
+]
+
+const toolbarConfig: ToolbarConfigType[] = [
+  {
+    common: 'search',
+    click() {
+      queryData.value.pageNum = 1
+      getPage()
+    }
+  },
+  {
+    common: 'reset',
+    click() {
+      queryRef.value?.resetFields()
+      getPage()
+    }
+  },
+  {
+    common: 'add',
+    click() {
+      dialogVisible.value = true
+      dialogTitle.value = '新增'
+    }
+  }
+  // {
+  //   common: 'delete',
+  //   disabled() {
+  //     return selectKeys.value.length == 0
+  //   },
+  //   click() {
+  //     handleRemove(selectKeys.value)
+  //   }
+  // }
+]
+
+const columnConfig: ColumnConfigType[] = [
+  {
+    prop: 'moduleName',
+    label: '模块标题'
+  },
+  {
+    prop: 'operatorName',
+    label: '操作人姓名'
+  },
+  {
+    prop: 'operationType',
+    label: '操作类型',
+    formatter: (row) => {
+      switch (row.operationType) {
+        case 2:
+          return '新增'
+        case 3:
+          return '修改'
+        case 4:
+          return '删除'
+      }
+      return ''
+    }
+  },
+  {
+    prop: 'createTime',
+    label: '操作时间'
+  },
+  {
+    prop: 'description',
+    label: '操作描述'
+  },
+  {
+    prop: 'auditStatus',
+    label: '审核状态',
+    formatter: (row) => {
+      switch (row.auditStatus) {
+        case 0:
+          return '待审核'
+        case 1:
+          return '通过'
+        case 2:
+          return '拒绝'
+      }
+      return ''
+    }
+  },
+  {
+    prop: 'auditTime',
+    label: '审核时间'
+  },
+  {
+    prop: 'remark',
+    label: '审核注释'
+  },
+  {
+    width: 250,
+    handleConfig: [
+      {
+        text: '审核',
+        click(row) {
+          dialogVisible.value = true
+          dialogTitle.value = '审核'
+          getDetailApi({ id: row.id }).then((resp: StrAnyObj) => {
+            formData.value = resp
+          })
+        }
+      }
+      // {
+      //   common: 'delete',
+      //   click(row) {
+      //     handleRemove([row.id])
+      //   }
+      // }
+    ]
+  }
+]
+
+const formConfig: FormConfigType[] = [
+
+]
+
+onMounted(() => {
+  getPage()
+})
+
+function getPage() {
+  getPageApi(queryData.value).then((resp) => {
+    tableData.value = resp.records
+    pageTotal.value = resp.total
+  })
+}
+
+function tableSelectionChange(item: StrAnyObjArr) {
+  selectKeys.value = item.map((item) => item.id)
+}
+
+function formSubmit() {
+  formRef.value?.validate(() => {
+    editApi(formData.value).then(() => {
+      dialogVisible.value = false
+      ElMessage.success('操作成功')
+      getPage()
+    })
+  })
+}
+
+function formClosed() {
+  formRef.value?.resetFields()
+}
+
+function handleRemove(idList: string[]) {
+  useHandleData('是否确认删除?', () => {
+    deleteApi({ idList }).then(() => {
+      ElMessage.success('删除成功')
+      getPage()
+    })
+  })
+}
+</script>
+
+<template>
+  <div>
+    <el-card v-if="showQuery">
+      <a-form ref="queryRef" v-model="queryData" :config="queryConfig" :span="6"> </a-form>
+    </el-card>
+
+    <a-table
+      selection
+      :data="tableData"
+      :page-total="pageTotal"
+      :toolbar-config="toolbarConfig"
+      :column-config="columnConfig"
+      v-model:show-query="showQuery"
+      v-model:page-num="queryData.pageNum"
+      v-model:page-size="queryData.pageSize"
+      @page-num-change="getPage"
+      @page-size-change="getPage"
+      @selection-change="tableSelectionChange"
+    >
+    </a-table>
+
+    <a-dialog v-model="dialogVisible" :title="dialogTitle" @submit="formSubmit" @closed="formClosed">
+      <a-form ref="formRef" v-model="formData" :config="formConfig" :span="24"> </a-form>
+    </a-dialog>
+  </div>
+</template>