Browse Source

!805 重构操作日志记录实现;CRM-数据权限:完善数据权限补全管理员和公海相关情况处理逻辑
Merge pull request !805 from puhui999/develop

芋道源码 1 năm trước cách đây
mục cha
commit
525d41bad1
70 tập tin đã thay đổi với 1135 bổ sung408 xóa
  1. 1 1
      yudao-dependencies/pom.xml
  2. 7 0
      yudao-framework/yudao-spring-boot-starter-biz-operatelog/pom.xml
  3. 9 14
      yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelog/config/YudaoOperateLogAutoConfiguration.java
  4. 33 0
      yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelogv2/config/YudaoOperateLogV2Configuration.java
  5. 329 0
      yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelogv2/core/aop/OperateLogV2Aspect.java
  6. 34 0
      yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelogv2/core/enums/OperateLogV2Constants.java
  7. 1 0
      yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelogv2/core/package-info.java
  8. 38 0
      yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelogv2/core/service/ILogRecordServiceImpl.java
  9. 20 0
      yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelogv2/core/vo/OperateLogV2PageReqVO.java
  10. 1 0
      yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelogv2/package-info.java
  11. 2 1
      yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  12. 0 1
      yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java
  13. 27 0
      yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/permission/CrmPermissionRoleCodeEnum.java
  14. 2 2
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.http
  15. 10 16
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java
  16. 3 3
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/CrmPermissionController.java
  17. 1 4
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessMapper.java
  18. 1 4
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/clue/CrmClueMapper.java
  19. 1 4
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contact/CrmContactMapper.java
  20. 1 4
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java
  21. 1 4
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java
  22. 1 4
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivableMapper.java
  23. 1 4
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivablePlanMapper.java
  24. 0 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/bizlog/package-info.java
  25. 0 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/core/package-info.java
  26. 1 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/package-info.java
  27. 1 2
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/parse/CrmIndustryParseFunction.java
  28. 1 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/parse/CrmLevelParseFunction.java
  29. 1 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/parse/CrmSourceParseFunction.java
  30. 1 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/annotations/CrmPermission.java
  31. 25 17
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/aop/CrmPermissionAspect.java
  32. 1 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/package-info.java
  33. 48 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/util/CrmPermissionUtils.java
  34. 1 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/package-info.java
  35. 1 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java
  36. 1 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java
  37. 1 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java
  38. 1 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contactbusinesslink/CrmContactBusinessLinkServiceImpl.java
  39. 1 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java
  40. 21 18
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java
  41. 2 3
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionServiceImpl.java
  42. 1 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanServiceImpl.java
  43. 1 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java
  44. 28 46
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/util/CrmQueryWrapperUtils.java
  45. 96 25
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/activity/AppActivityController.java
  46. 17 1
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/discount/DiscountActivityMapper.java
  47. 18 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/discount/DiscountProductMapper.java
  48. 31 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/reward/RewardActivityMapper.java
  49. 12 1
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/discount/DiscountActivityService.java
  50. 20 2
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/discount/DiscountActivityServiceImpl.java
  51. 12 1
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityService.java
  52. 19 3
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImpl.java
  53. 0 20
      yudao-module-system/yudao-module-system-api/pom.xml
  54. 14 7
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/logger/OperateLogApi.java
  55. 48 10
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/logger/dto/OperateLogV2CreateReqDTO.java
  56. 28 0
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/logger/dto/OperateLogV2PageReqDTO.java
  57. 40 18
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/logger/dto/OperateLogV2RespDTO.java
  58. 23 10
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/logger/OperateLogApiImpl.java
  59. 59 10
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/logger/OperateLogV2DO.java
  60. 6 16
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/logger/OperateLogV2Mapper.java
  61. 0 16
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/bizlog/config/YudaoOperateLogV2Configuration.java
  62. 0 1
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/bizlog/package-info.java
  63. 0 85
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/bizlog/service/ILogRecordServiceImpl.java
  64. 1 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/operatelog/package-info.java
  65. 1 1
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/operatelog/parse/AdminUserParseFunction.java
  66. 1 1
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/operatelog/parse/AreaParseFunction.java
  67. 7 10
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/logger/OperateLogService.java
  68. 8 5
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/logger/OperateLogServiceImpl.java
  69. 1 0
      yudao-server/src/main/resources/application-local.yaml
  70. 11 1
      yudao-server/src/main/resources/application.yaml

+ 1 - 1
yudao-dependencies/pom.xml

@@ -26,7 +26,7 @@
         <mybatis-plus.version>3.5.4.1</mybatis-plus.version>
         <mybatis-plus-generator.version>3.5.4.1</mybatis-plus-generator.version>
         <dynamic-datasource.version>4.2.0</dynamic-datasource.version>
-        <mybatis-plus-join.version>1.4.7.2</mybatis-plus-join.version>
+        <mybatis-plus-join.version>1.4.8.1</mybatis-plus-join.version>
         <redisson.version>3.25.0</redisson.version>
         <dm8.jdbc.version>8.1.3.62</dm8.jdbc.version>
         <!-- 消息队列 -->

+ 7 - 0
yudao-framework/yudao-spring-boot-starter-biz-operatelog/pom.xml

@@ -46,6 +46,13 @@
             <groupId>com.google.guava</groupId>
             <artifactId>guava</artifactId>
         </dependency>
+        <!-- Springboot-注解-通用操作日志组件 -->
+        <!-- 此组件解决的问题是: 「谁」在「什么时间」对「什么」做了「什么事」 -->
+        <dependency>
+            <groupId>io.github.mouzt</groupId>
+            <artifactId>bizlog-sdk</artifactId>
+        </dependency>
+
     </dependencies>
 
 </project>

+ 9 - 14
yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelog/config/YudaoOperateLogAutoConfiguration.java

@@ -1,23 +1,18 @@
 package cn.iocoder.yudao.framework.operatelog.config;
 
-import cn.iocoder.yudao.framework.operatelog.core.aop.OperateLogAspect;
-import cn.iocoder.yudao.framework.operatelog.core.service.OperateLogFrameworkService;
-import cn.iocoder.yudao.framework.operatelog.core.service.OperateLogFrameworkServiceImpl;
-import cn.iocoder.yudao.module.system.api.logger.OperateLogApi;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
-import org.springframework.context.annotation.Bean;
 
 @AutoConfiguration
 public class YudaoOperateLogAutoConfiguration {
 
-    @Bean
-    public OperateLogAspect operateLogAspect() {
-        return new OperateLogAspect();
-    }
-
-    @Bean
-    public OperateLogFrameworkService operateLogFrameworkService(OperateLogApi operateLogApi) {
-        return new OperateLogFrameworkServiceImpl(operateLogApi);
-    }
+    //@Bean
+    //public OperateLogAspect operateLogAspect() {
+    //    return new OperateLogAspect();
+    //}
+    //
+    //@Bean
+    //public OperateLogFrameworkService operateLogFrameworkService(OperateLogApi operateLogApi) {
+    //    return new OperateLogFrameworkServiceImpl(operateLogApi);
+    //}
 
 }

+ 33 - 0
yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelogv2/config/YudaoOperateLogV2Configuration.java

@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.framework.operatelogv2.config;
+
+import cn.iocoder.yudao.framework.operatelogv2.core.aop.OperateLogV2Aspect;
+import cn.iocoder.yudao.framework.operatelogv2.core.service.ILogRecordServiceImpl;
+import com.mzt.logapi.service.ILogRecordService;
+import com.mzt.logapi.starter.annotation.EnableLogRecord;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Primary;
+
+/**
+ * mzt-biz-log 配置类
+ *
+ * @author HUIHUI
+ */
+@EnableLogRecord(tenant = "") // 貌似用不上 tenant 这玩意给个空好啦
+@AutoConfiguration
+@Slf4j
+public class YudaoOperateLogV2Configuration {
+
+    @Bean
+    @Primary
+    public ILogRecordService iLogRecordServiceImpl() {
+        return new ILogRecordServiceImpl();
+    }
+
+    @Bean
+    public OperateLogV2Aspect operateLogV2Aspect() {
+        return new OperateLogV2Aspect();
+    }
+
+}

+ 329 - 0
yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelogv2/core/aop/OperateLogV2Aspect.java

@@ -0,0 +1,329 @@
+package cn.iocoder.yudao.framework.operatelogv2.core.aop;
+
+import cn.hutool.core.date.LocalDateTimeUtil;
+import cn.hutool.core.exceptions.ExceptionUtil;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.ObjUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
+import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
+import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
+import cn.iocoder.yudao.module.system.api.logger.OperateLogApi;
+import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2CreateReqDTO;
+import com.google.common.collect.Maps;
+import com.mzt.logapi.beans.LogRecord;
+import io.swagger.v3.oas.annotations.Operation;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+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.core.annotation.AnnotationUtils;
+import org.springframework.validation.BindingResult;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Array;
+import java.time.LocalDateTime;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Predicate;
+import java.util.stream.IntStream;
+
+import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
+import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.SUCCESS;
+import static cn.iocoder.yudao.framework.operatelogv2.core.enums.OperateLogV2Constants.*;
+
+/**
+ * 拦截使用 @Operation 注解, 获取操作类型、开始时间、持续时间、方法相关信息、执行结果等信息
+ * 对 mzt-biz-log 日志信息进行增强
+ *
+ * @author HUIHUI
+ */
+@Aspect
+@Slf4j
+public class OperateLogV2Aspect {
+
+    /**
+     * 用于记录操作内容的上下文
+     *
+     * @see OperateLogV2CreateReqDTO#getContent()
+     */
+    private static final ThreadLocal<LogRecord> CONTENT = new ThreadLocal<>();
+    /**
+     * 用于记录拓展字段的上下文
+     *
+     * @see OperateLogV2CreateReqDTO#getExtra()
+     */
+    private static final ThreadLocal<Map<String, Object>> EXTRA = new ThreadLocal<>();
+
+    @Resource
+    private OperateLogApi operateLogApi;
+
+    @Around("@annotation(operation)")
+    public Object around(ProceedingJoinPoint joinPoint, Operation operation) throws Throwable {
+        RequestMethod requestMethod = obtainFirstMatchRequestMethod(obtainRequestMethod(joinPoint));
+        if (requestMethod == RequestMethod.GET) { // 跳过 get 方法
+            return joinPoint.proceed();
+        }
+
+        // 目前,只有管理员,才记录操作日志!所以非管理员,直接调用,不进行记录
+        Integer userType = WebFrameworkUtils.getLoginUserType();
+        if (ObjUtil.notEqual(userType, UserTypeEnum.ADMIN.getValue())) {
+            return joinPoint.proceed();
+        }
+
+        // 记录开始时间
+        LocalDateTime startTime = LocalDateTime.now();
+        try {
+            // 执行原有方法
+            Object result = joinPoint.proceed();
+            // 记录正常执行时的操作日志
+            this.log(joinPoint, operation, startTime, result, null);
+            return result;
+        } catch (Throwable exception) {
+            this.log(joinPoint, operation, startTime, null, exception);
+            throw exception;
+        } finally {
+            clearThreadLocal();
+        }
+    }
+
+    public static void setContent(LogRecord content) {
+        CONTENT.set(content);
+    }
+
+    public static void addExtra(String key, Object value) {
+        if (EXTRA.get() == null) {
+            EXTRA.set(new HashMap<>());
+        }
+        EXTRA.get().put(key, value);
+    }
+
+    public static void addExtra(Map<String, Object> extra) {
+        if (EXTRA.get() == null) {
+            EXTRA.set(new HashMap<>());
+        }
+        EXTRA.get().putAll(extra);
+    }
+
+    private static void clearThreadLocal() {
+        CONTENT.remove();
+        EXTRA.remove();
+    }
+
+    private void log(ProceedingJoinPoint joinPoint, Operation operation,
+                     LocalDateTime startTime, Object result, Throwable exception) {
+        try {
+            // 判断不记录的情况(默认没有值就是记录)
+            if (EXTRA.get() != null && EXTRA.get().get(ENABLE) != null) {
+                return;
+            }
+            if (CONTENT.get() == null) { // 没有值说明没有日志需要记录
+                return;
+            }
+
+            // 真正记录操作日志
+            this.log0(joinPoint, operation, startTime, result, exception);
+        } catch (Throwable ex) {
+            log.error("[log][记录操作日志时,发生异常,其中参数是 joinPoint({}) apiOperation({}) result({}) exception({}) ]",
+                    joinPoint, operation, result, exception, ex);
+        }
+    }
+
+    private void log0(ProceedingJoinPoint joinPoint, Operation operation,
+                      LocalDateTime startTime, Object result, Throwable exception) {
+        OperateLogV2CreateReqDTO reqDTO = new OperateLogV2CreateReqDTO();
+        // 补全通用字段
+        reqDTO.setTraceId(TracerUtils.getTraceId());
+        reqDTO.setStartTime(startTime);
+        // 补充用户信息
+        fillUserFields(reqDTO);
+        // 补全模块信息
+        fillModuleFields(reqDTO, operation);
+        // 补全请求信息
+        fillRequestFields(reqDTO);
+        // 补全方法信息
+        fillMethodFields(reqDTO, joinPoint, startTime, result, exception);
+
+        // 异步记录日志
+        operateLogApi.createOperateLogV2(reqDTO);
+    }
+
+    private static void fillUserFields(OperateLogV2CreateReqDTO reqDTO) {
+        reqDTO.setUserId(WebFrameworkUtils.getLoginUserId());
+        reqDTO.setUserType(WebFrameworkUtils.getLoginUserType());
+    }
+
+    private static void fillModuleFields(OperateLogV2CreateReqDTO reqDTO, Operation operation) {
+        LogRecord logRecord = CONTENT.get();
+        reqDTO.setBizId(Long.parseLong(logRecord.getBizNo())); // 操作模块业务编号
+        reqDTO.setContent(logRecord.getAction());// 例如说,修改编号为 1 的用户信息,将性别从男改成女,将姓名从芋道改成源码。
+
+        // type 属性
+        reqDTO.setType(logRecord.getType()); // 大模块类型如 crm 客户
+        // subType 属性
+        if (logRecord.getSubType() != null) {
+            reqDTO.setSubType(logRecord.getSubType());// 操作名称如 转移客户
+        }
+        if (StrUtil.isEmpty(reqDTO.getSubType()) && operation != null) {
+            reqDTO.setSubType(operation.summary());
+        }
+
+        // 拓展字段,有些复杂的业务,需要记录一些字段 ( JSON 格式 ),例如说,记录订单编号,{ orderId: "1"}
+        Map<String, Object> objectMap = EXTRA.get();
+        if (objectMap != null) {
+            Object object = objectMap.get(EXTRA_KEY);
+            if (object instanceof Map<?, ?> extraMap) {
+                if (extraMap.keySet().stream().allMatch(String.class::isInstance)) {
+                    @SuppressWarnings("unchecked")
+                    Map<String, Object> extra = (Map<String, Object>) extraMap;
+                    reqDTO.setExtra(extra);
+                    return;
+                }
+            }
+            // 激进一点不是 map 直接当 value 处理
+            Map<String, Object> extra = Maps.newHashMapWithExpectedSize(1);
+            extra.put(EXTRA_KEY, object);
+            reqDTO.setExtra(extra);
+        }
+
+    }
+
+    private static void fillRequestFields(OperateLogV2CreateReqDTO reqDTO) {
+        // 获得 Request 对象
+        HttpServletRequest request = ServletUtils.getRequest();
+        if (request == null) {
+            return;
+        }
+        // 补全请求信息
+        reqDTO.setRequestMethod(request.getMethod());
+        reqDTO.setRequestUrl(request.getRequestURI());
+        reqDTO.setUserIp(ServletUtils.getClientIP(request));
+        reqDTO.setUserAgent(ServletUtils.getUserAgent(request));
+    }
+
+    private static void fillMethodFields(OperateLogV2CreateReqDTO reqDTO, ProceedingJoinPoint joinPoint, LocalDateTime startTime,
+                                         Object result, Throwable exception) {
+        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
+        reqDTO.setJavaMethod(methodSignature.toString());
+        if (EXTRA.get().get(IS_LOG_ARGS) == null) {
+            reqDTO.setJavaMethodArgs(obtainMethodArgs(joinPoint));
+        }
+        if (EXTRA.get().get(IS_LOG_RESULT_DATA) == null) {
+            reqDTO.setResultData(obtainResultData(result));
+        }
+        reqDTO.setDuration((int) (LocalDateTimeUtil.between(startTime, LocalDateTime.now()).toMillis()));
+        // (正常)处理 resultCode 和 resultMsg 字段
+        if (result instanceof CommonResult) {
+            CommonResult<?> commonResult = (CommonResult<?>) result;
+            reqDTO.setResultCode(commonResult.getCode());
+            reqDTO.setResultMsg(commonResult.getMsg());
+        } else {
+            reqDTO.setResultCode(SUCCESS.getCode());
+        }
+        // (异常)处理 resultCode 和 resultMsg 字段
+        if (exception != null) {
+            reqDTO.setResultCode(INTERNAL_SERVER_ERROR.getCode());
+            reqDTO.setResultMsg(ExceptionUtil.getRootCauseMessage(exception));
+        }
+    }
+
+    @SuppressWarnings("SameParameterValue")
+    private static <T extends Annotation> T getClassAnnotation(ProceedingJoinPoint joinPoint, Class<T> annotationClass) {
+        return ((MethodSignature) joinPoint.getSignature()).getMethod().getDeclaringClass().getAnnotation(annotationClass);
+    }
+
+    private static String obtainMethodArgs(ProceedingJoinPoint joinPoint) {
+        // TODO 提升:参数脱敏和忽略
+        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
+        String[] argNames = methodSignature.getParameterNames();
+        Object[] argValues = joinPoint.getArgs();
+        // 拼接参数
+        Map<String, Object> args = Maps.newHashMapWithExpectedSize(argValues.length);
+        for (int i = 0; i < argNames.length; i++) {
+            String argName = argNames[i];
+            Object argValue = argValues[i];
+            // 被忽略时,标记为 ignore 字符串,避免和 null 混在一起
+            args.put(argName, !isIgnoreArgs(argValue) ? argValue : "[ignore]");
+        }
+        return JsonUtils.toJsonString(args);
+    }
+
+    private static String obtainResultData(Object result) {
+        // TODO 提升:结果脱敏和忽略
+        if (result instanceof CommonResult) {
+            result = ((CommonResult<?>) result).getData();
+        }
+        return JsonUtils.toJsonString(result);
+    }
+
+    private static boolean isIgnoreArgs(Object object) {
+        Class<?> clazz = object.getClass();
+        // 处理数组的情况
+        if (clazz.isArray()) {
+            return IntStream.range(0, Array.getLength(object))
+                    .anyMatch(index -> isIgnoreArgs(Array.get(object, index)));
+        }
+        // 递归,处理数组、Collection、Map 的情况
+        if (Collection.class.isAssignableFrom(clazz)) {
+            return ((Collection<?>) object).stream()
+                    .anyMatch((Predicate<Object>) OperateLogV2Aspect::isIgnoreArgs);
+        }
+        if (Map.class.isAssignableFrom(clazz)) {
+            return isIgnoreArgs(((Map<?, ?>) object).values());
+        }
+        // obj
+        return object instanceof MultipartFile
+                || object instanceof HttpServletRequest
+                || object instanceof HttpServletResponse
+                || object instanceof BindingResult;
+    }
+
+    private static RequestMethod obtainFirstMatchRequestMethod(RequestMethod[] requestMethods) {
+        if (ArrayUtil.isEmpty(requestMethods)) {
+            return null;
+        }
+        // 优先,匹配最优的 POST、PUT、DELETE
+        RequestMethod result = obtainFirstLogRequestMethod(requestMethods);
+        if (result != null) {
+            return result;
+        }
+        // 然后,匹配次优的 GET
+        result = Arrays.stream(requestMethods).filter(requestMethod -> requestMethod == RequestMethod.GET)
+                .findFirst().orElse(null);
+        if (result != null) {
+            return result;
+        }
+        // 兜底,获得第一个
+        return requestMethods[0];
+    }
+
+    private static RequestMethod[] obtainRequestMethod(ProceedingJoinPoint joinPoint) {
+        RequestMapping requestMapping = AnnotationUtils.getAnnotation( // 使用 Spring 的工具类,可以处理 @RequestMapping 别名注解
+                ((MethodSignature) joinPoint.getSignature()).getMethod(), RequestMapping.class);
+        return requestMapping != null ? requestMapping.method() : new RequestMethod[]{};
+    }
+
+    private static RequestMethod obtainFirstLogRequestMethod(RequestMethod[] requestMethods) {
+        if (ArrayUtil.isEmpty(requestMethods)) {
+            return null;
+        }
+        return Arrays.stream(requestMethods).filter(requestMethod ->
+                        requestMethod == RequestMethod.POST
+                                || requestMethod == RequestMethod.PUT
+                                || requestMethod == RequestMethod.DELETE)
+                .findFirst().orElse(null);
+    }
+
+}

+ 34 - 0
yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelogv2/core/enums/OperateLogV2Constants.java

@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.framework.operatelogv2.core.enums;
+
+/**
+ * 操作日志常量接口
+ *
+ * @author HUIHUI
+ */
+public interface OperateLogV2Constants {
+
+    // ========== 开关字段-如果没有值默认就是记录 ==========
+
+    /**
+     * 是否记录日志
+     */
+    String ENABLE = "enable";
+
+    /**
+     * 是否记录方法参数
+     */
+    String IS_LOG_ARGS = "isLogArgs";
+
+    /**
+     * 是否记录方法结果的数据
+     */
+    String IS_LOG_RESULT_DATA = "isLogResultData";
+
+    // ========== 扩展 ==========
+
+    /**
+     * 扩展信息
+     */
+    String EXTRA_KEY = "extra";
+
+}

+ 1 - 0
yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelogv2/core/package-info.java

@@ -0,0 +1 @@
+package cn.iocoder.yudao.framework.operatelogv2.core;

+ 38 - 0
yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelogv2/core/service/ILogRecordServiceImpl.java

@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.framework.operatelogv2.core.service;
+
+import cn.iocoder.yudao.framework.operatelogv2.core.aop.OperateLogV2Aspect;
+import com.mzt.logapi.beans.LogRecord;
+import com.mzt.logapi.context.LogRecordContext;
+import com.mzt.logapi.service.ILogRecordService;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 操作日志 ILogRecordService 实现类
+ *
+ * 基于 {@link OperateLogV2Aspect} 实现,记录操作日志
+ *
+ * @author HUIHUI
+ */
+@Slf4j
+public class ILogRecordServiceImpl implements ILogRecordService {
+
+    @Override
+    public void record(LogRecord logRecord) {
+        OperateLogV2Aspect.setContent(logRecord); // 操作日志
+        OperateLogV2Aspect.addExtra(LogRecordContext.getVariables()); // 扩展信息
+    }
+
+    @Override
+    public List<LogRecord> queryLog(String bizNo, String type) {
+        return Collections.emptyList();
+    }
+
+    @Override
+    public List<LogRecord> queryLogByBizNo(String bizNo, String type, String subType) {
+        return Collections.emptyList();
+    }
+
+}

+ 20 - 0
yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelogv2/core/vo/OperateLogV2PageReqVO.java

@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.framework.operatelogv2.core.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - 操作日志分页 Request VO")
+@Data
+public class OperateLogV2PageReqVO extends PageParam {
+
+    @Schema(description = "模块数据编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Long bizId;
+
+    @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Long userId;
+
+    @Schema(description = "模块类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private String bizType;
+
+}

+ 1 - 0
yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelogv2/package-info.java

@@ -0,0 +1 @@
+package cn.iocoder.yudao.framework.operatelogv2;

+ 2 - 1
yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@@ -1 +1,2 @@
-cn.iocoder.yudao.framework.operatelog.config.YudaoOperateLogAutoConfiguration
+cn.iocoder.yudao.framework.operatelog.config.YudaoOperateLogAutoConfiguration
+cn.iocoder.yudao.framework.operatelogv2.config.YudaoOperateLogV2Configuration

+ 0 - 1
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java

@@ -22,6 +22,5 @@ public interface LogRecordConstants {
     //======================= 客户转移操作日志 =======================
 
     String TRANSFER_CUSTOMER_LOG_SUCCESS = "把客户【{{#crmCustomer.name}}】的负责人从【{getAdminUserById{#crmCustomer.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】";
-    String TRANSFER_CUSTOMER_LOG_FAIL = ""; // TODO @puhui999:这个可以删除哈,一般不搞失败的日志
 
 }

+ 27 - 0
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/permission/CrmPermissionRoleCodeEnum.java

@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.crm.enums.permission;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * Crm 数据权限角色枚举
+ *
+ * @author HUIHUI
+ */
+@Getter
+@AllArgsConstructor
+public enum CrmPermissionRoleCodeEnum {
+
+    CRM_ADMIN("crm_admin", "CRM 管理员");
+
+    /**
+     * 角色标识
+     */
+    private String code;
+    /**
+     * 角色名称
+     */
+    private String name;
+
+}
+

+ 2 - 2
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.http

@@ -1,8 +1,8 @@
 ### 请求 /transfer
 PUT {{baseUrl}}/crm/customer/transfer
-Content-Type: application/json
+Content-Type: application/-id: {{adminTenentId}}json
 Authorization: Bearer {{token}}
-tenant-id: {{adminTenentId}}
+tenant
 
 {
   "id": 10,

+ 10 - 16
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java

@@ -3,8 +3,10 @@ package cn.iocoder.yudao.module.crm.controller.admin.customer;
 import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
+import cn.iocoder.yudao.framework.operatelogv2.core.vo.OperateLogV2PageReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.*;
 import cn.iocoder.yudao.module.crm.convert.customer.CrmCustomerConvert;
 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
@@ -12,6 +14,7 @@ import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
 import cn.iocoder.yudao.module.system.api.logger.OperateLogApi;
+import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2PageReqDTO;
 import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2RespDTO;
 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
@@ -64,7 +67,7 @@ public class CrmCustomerController {
     }
 
     @PutMapping("/update")
-    //@Operation(summary = "更新客户")
+    @Operation(summary = "更新客户")
     @PreAuthorize("@ss.hasPermission('crm:customer:update')")
     public CommonResult<Boolean> updateCustomer(@Valid @RequestBody CrmCustomerUpdateReqVO updateReqVO) {
         customerService.updateCustomer(updateReqVO);
@@ -128,30 +131,21 @@ public class CrmCustomerController {
     }
 
     @PutMapping("/transfer")
-    //@Operation(summary = "客户转移")
+    @Operation(summary = "客户转移")
     @PreAuthorize("@ss.hasPermission('crm:customer:update')")
     public CommonResult<Boolean> transfer(@Valid @RequestBody CrmCustomerTransferReqVO reqVO) {
         customerService.transferCustomer(reqVO, getLoginUserId());
         return success(true);
     }
 
-    // TODO @puhui999:operate-log-list 或者 operate-log-page 如果分页
-    @GetMapping("/operate-log")
+    @GetMapping("/operate-log-page")
     @Operation(summary = "获得客户操作日志")
     @Parameter(name = "id", description = "编号", required = true, example = "1024")
     @PreAuthorize("@ss.hasPermission('crm:customer:query')")
-    // TODO @puhui999:最好有读权限;方法名改成 getCustomerOperateLog
-    public CommonResult<List<OperateLogV2RespDTO>> getOperateLog(@RequestParam("id") Long id) {
-        // 1. 获取客户
-        // TODO @puhui999:这个校验可以去掉哈;
-        CrmCustomerDO customer = customerService.getCustomer(id);
-        if (customer == null) {
-            return success(null);
-        }
-
-        // 2. 获取操作日志
-        // TODO @puhui999:操作日志,返回可能要分页哈;
-        return success(operateLogApi.getOperateLogByModuleAndBizId(CRM_CUSTOMER, id));
+    public CommonResult<PageResult<OperateLogV2RespDTO>> getCustomerOperateLog(OperateLogV2PageReqVO reqVO) {
+        reqVO.setPageSize(PAGE_SIZE_NONE); // 不分页
+        reqVO.setBizType(CRM_CUSTOMER);
+        return success(operateLogApi.getOperateLogPage(BeanUtils.toBean(reqVO, OperateLogV2PageReqDTO.class)));
     }
 
     // TODO @Joey:单独建一个属于自己业务的 ReqVO;因为前端如果模拟请求,是不是可以更新其它字段了;

+ 3 - 3
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/CrmPermissionController.java

@@ -8,8 +8,8 @@ import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionR
 import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionUpdateReqVO;
 import cn.iocoder.yudao.module.crm.convert.permission.CrmPermissionConvert;
 import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
-import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission;
 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
+import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
 import cn.iocoder.yudao.module.system.api.dept.PostApi;
@@ -21,12 +21,12 @@ import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.Parameters;
 import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
-import jakarta.annotation.Resource;
-import jakarta.validation.Valid;
 import java.util.*;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;

+ 1 - 4
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessMapper.java

@@ -38,11 +38,8 @@ public interface CrmBusinessMapper extends BaseMapperX<CrmBusinessDO> {
     default PageResult<CrmBusinessDO> selectPage(CrmBusinessPageReqVO pageReqVO, Long userId) {
         MPJLambdaWrapperX<CrmBusinessDO> query = new MPJLambdaWrapperX<>();
         // 拼接数据权限的查询条件
-        boolean condition = CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_BUSINESS.getType(),
+        CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_BUSINESS.getType(),
                 CrmBusinessDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE);
-        if (!condition) {
-            return PageResult.empty();
-        }
         // 拼接自身的查询条件
         query.selectAll(CrmBusinessDO.class)
                 .likeIfPresent(CrmBusinessDO::getName, pageReqVO.getName())

+ 1 - 4
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/clue/CrmClueMapper.java

@@ -30,11 +30,8 @@ public interface CrmClueMapper extends BaseMapperX<CrmClueDO> {
     default PageResult<CrmClueDO> selectPage(CrmCluePageReqVO pageReqVO, Long userId) {
         MPJLambdaWrapperX<CrmClueDO> query = new MPJLambdaWrapperX<>();
         // 拼接数据权限的查询条件
-        boolean condition = CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_LEADS.getType(),
+        CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_LEADS.getType(),
                 CrmClueDO::getId, userId, pageReqVO.getSceneType(), pageReqVO.getPool());
-        if (!condition) {
-            return PageResult.empty();
-        }
         // 拼接自身的查询条件
         query.selectAll(CrmClueDO.class)
                 .likeIfPresent(CrmClueDO::getName, pageReqVO.getName())

+ 1 - 4
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contact/CrmContactMapper.java

@@ -43,11 +43,8 @@ public interface CrmContactMapper extends BaseMapperX<CrmContactDO> {
     default PageResult<CrmContactDO> selectPage(CrmContactPageReqVO pageReqVO, Long userId) {
         MPJLambdaWrapperX<CrmContactDO> query = new MPJLambdaWrapperX<>();
         // 拼接数据权限的查询条件
-        boolean condition = CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CONTACT.getType(),
+        CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CONTACT.getType(),
                 CrmContactDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE);
-        if (!condition) {
-            return PageResult.empty();
-        }
         // 拼接自身的查询条件
         query.selectAll(CrmContactDO.class)
                 .likeIfPresent(CrmContactDO::getName, pageReqVO.getName())

+ 1 - 4
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java

@@ -41,11 +41,8 @@ public interface CrmContractMapper extends BaseMapperX<CrmContractDO> {
     default PageResult<CrmContractDO> selectPage(CrmContractPageReqVO pageReqVO, Long userId) {
         MPJLambdaWrapperX<CrmContractDO> mpjLambdaWrapperX = new MPJLambdaWrapperX<>();
         // 拼接数据权限的查询条件
-        boolean condition = CrmQueryWrapperUtils.appendPermissionCondition(mpjLambdaWrapperX, CrmBizTypeEnum.CRM_CONTACT.getType(),
+        CrmQueryWrapperUtils.appendPermissionCondition(mpjLambdaWrapperX, CrmBizTypeEnum.CRM_CONTACT.getType(),
                 CrmContractDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE);
-        if (!condition) {
-            return PageResult.empty();
-        }
         // 拼接自身的查询条件
         mpjLambdaWrapperX.selectAll(CrmContractDO.class)
                 .likeIfPresent(CrmContractDO::getNo, pageReqVO.getNo())

+ 1 - 4
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java

@@ -30,11 +30,8 @@ public interface CrmCustomerMapper extends BaseMapperX<CrmCustomerDO> {
     default PageResult<CrmCustomerDO> selectPage(CrmCustomerPageReqVO pageReqVO, Long userId) {
         MPJLambdaWrapperX<CrmCustomerDO> query = new MPJLambdaWrapperX<>();
         // 拼接数据权限的查询条件
-        boolean condition = CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CUSTOMER.getType(),
+        CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CUSTOMER.getType(),
                 CrmCustomerDO::getId, userId, pageReqVO.getSceneType(), pageReqVO.getPool());
-        if (!condition) {
-            return PageResult.empty();
-        }
         // 拼接自身的查询条件
         query.selectAll(CrmCustomerDO.class)
                 .likeIfPresent(CrmCustomerDO::getName, pageReqVO.getName())

+ 1 - 4
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivableMapper.java

@@ -39,11 +39,8 @@ public interface CrmReceivableMapper extends BaseMapperX<CrmReceivableDO> {
     default PageResult<CrmReceivableDO> selectPage(CrmReceivablePageReqVO pageReqVO, Long userId) {
         MPJLambdaWrapperX<CrmReceivableDO> query = new MPJLambdaWrapperX<>();
         // 拼接数据权限的查询条件
-        boolean condition = CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE.getType(),
+        CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE.getType(),
                 CrmReceivableDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE);
-        if (!condition) {
-            return PageResult.empty();
-        }
         // 拼接自身的查询条件
         query.selectAll(CrmReceivableDO.class)
                 .eqIfPresent(CrmReceivableDO::getNo, pageReqVO.getNo())

+ 1 - 4
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivablePlanMapper.java

@@ -38,11 +38,8 @@ public interface CrmReceivablePlanMapper extends BaseMapperX<CrmReceivablePlanDO
     default PageResult<CrmReceivablePlanDO> selectPage(CrmReceivablePlanPageReqVO pageReqVO, Long userId) {
         MPJLambdaWrapperX<CrmReceivablePlanDO> query = new MPJLambdaWrapperX<>();
         // 拼接数据权限的查询条件
-        boolean condition = CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType(),
+        CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType(),
                 CrmReceivablePlanDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE);
-        if (!condition) {
-            return PageResult.empty();
-        }
         // 拼接自身的查询条件
         query.selectAll(CrmReceivablePlanDO.class)
                 .eqIfPresent(CrmReceivablePlanDO::getCustomerId, pageReqVO.getCustomerId())

+ 0 - 1
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/bizlog/package-info.java

@@ -1 +0,0 @@
-package cn.iocoder.yudao.module.crm.framework.bizlog;

+ 0 - 1
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/core/package-info.java

@@ -1 +0,0 @@
-package cn.iocoder.yudao.module.crm.framework.core;

+ 1 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/package-info.java

@@ -0,0 +1 @@
+package cn.iocoder.yudao.module.crm.framework.operatelog;

+ 1 - 2
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/bizlog/function/CrmIndustryParseFunction.java → yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/parse/CrmIndustryParseFunction.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.crm.framework.bizlog.function;
+package cn.iocoder.yudao.module.crm.framework.operatelog.parse;
 
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
@@ -8,7 +8,6 @@ import org.springframework.stereotype.Component;
 
 import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_INDUSTRY;
 
-// TODO @puhui999:包名使用 operatelog 更合适哈;
 /**
  * 自定义函数-通过行业编号获取行业信息
  *

+ 1 - 1
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/bizlog/function/CrmLevelParseFunction.java → yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/parse/CrmLevelParseFunction.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.crm.framework.bizlog.function;
+package cn.iocoder.yudao.module.crm.framework.operatelog.parse;
 
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;

+ 1 - 1
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/bizlog/function/CrmSourceParseFunction.java → yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/parse/CrmSourceParseFunction.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.crm.framework.bizlog.function;
+package cn.iocoder.yudao.module.crm.framework.operatelog.parse;
 
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;

+ 1 - 1
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/core/annotations/CrmPermission.java → yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/annotations/CrmPermission.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.crm.framework.core.annotations;
+package cn.iocoder.yudao.module.crm.framework.permission.core.annotations;
 
 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;

+ 25 - 17
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/core/aop/CrmPermissionAspect.java → yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/aop/CrmPermissionAspect.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.crm.framework.core.aop;
+package cn.iocoder.yudao.module.crm.framework.permission.core.aop;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ObjUtil;
@@ -6,27 +6,27 @@ import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.util.spring.SpringExpressionUtils;
 import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
 import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
-import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission;
 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
+import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
+import cn.iocoder.yudao.module.crm.framework.permission.core.util.CrmPermissionUtils;
 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
+import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
 import org.aspectj.lang.JoinPoint;
 import org.aspectj.lang.annotation.Aspect;
 import org.aspectj.lang.annotation.Before;
 import org.springframework.stereotype.Component;
 
-import jakarta.annotation.Resource;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.anyMatch;
 import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
 import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CRM_PERMISSION_DENIED;
-import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CRM_PERMISSION_MODEL_NOT_EXISTS;
 
-// TODO 这个包,改成 permission,然后搞 config 和 core 包,这个类在 core 包里;目的是:framework 最好分类下
 /**
  * Crm 数据权限校验 AOP 切面
  *
@@ -42,10 +42,6 @@ public class CrmPermissionAspect {
 
     @Before("@annotation(crmPermission)")
     public void doBefore(JoinPoint joinPoint, CrmPermission crmPermission) {
-        // TODO 芋艿:临时,方便大家调试
-        //if (true) {
-        //    return;
-        //}
         // 获取相关属性值
         Map<String, Object> expressionValues = parseExpressions(joinPoint, crmPermission);
         Integer bizType = StrUtil.isEmpty(crmPermission.bizTypeValue()) ?
@@ -53,16 +49,28 @@ public class CrmPermissionAspect {
         Long bizId = (Long) expressionValues.get(crmPermission.bizId()); // 模块数据编号
         Integer permissionLevel = crmPermission.level().getLevel(); // 需要的权限级别
 
-        // TODO 如果是超级管理员则直接通过
-        //if (superAdmin){
-        //    return;
-        //}
-
-        // 1. 获取数据权限
+        // 1.1 如果是超级管理员则直接通过
+        if (CrmPermissionUtils.validateAdminUser()) {
+            return;
+        }
+        // 1.2 获取数据权限
         List<CrmPermissionDO> bizPermissions = crmPermissionService.getPermissionListByBiz(bizType, bizId);
-        if (CollUtil.isEmpty(bizPermissions)) { // 数据权限不存存那么数据也不存在
-            throw exception(CRM_PERMISSION_MODEL_NOT_EXISTS, CrmBizTypeEnum.getNameByType(bizType));
+        if (CollUtil.isEmpty(bizPermissions)) { // 没有数据权限的情况
+            // 公海数据如果没有团队成员大家也因该有读权限才对
+            if (CrmPermissionLevelEnum.isRead(permissionLevel)) {
+                return;
+            }
+
+            // 没有数据权限的情况下超出了读权限直接报错,避免后面校验空指针
+            throw exception(CRM_PERMISSION_DENIED, CrmBizTypeEnum.getNameByType(bizType));
+        } else { // 有数据权限但是没有负责人的情况
+            if (!anyMatch(bizPermissions, item -> CrmPermissionLevelEnum.isOwner(item.getLevel()))) {
+                if (CrmPermissionLevelEnum.isRead(permissionLevel)) {
+                    return;
+                }
+            }
         }
+
         // 2.1 情况一:如果自己是负责人,则默认有所有权限
         CrmPermissionDO userPermission = CollUtil.findOne(bizPermissions, permission -> ObjUtil.equal(permission.getUserId(), getUserId()));
         if (userPermission != null) {

+ 1 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/package-info.java

@@ -0,0 +1 @@
+package cn.iocoder.yudao.module.crm.framework.permission.core;

+ 48 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/util/CrmPermissionUtils.java

@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.crm.framework.permission.core.util;
+
+import cn.hutool.extra.spring.SpringUtil;
+import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
+import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionRoleCodeEnum;
+import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
+
+/**
+ * 数据权限工具类
+ *
+ * @author HUIHUI
+ */
+public class CrmPermissionUtils {
+
+    /**
+     * 校验用户是否是 CRM 管理员
+     *
+     * @return 是/否
+     */
+    public static boolean validateAdminUser() {
+        return SingletonManager.getPermissionApi().hasAnyRoles(getUserId(), CrmPermissionRoleCodeEnum.CRM_ADMIN.getCode());
+    }
+
+    /**
+     * 获得用户编号
+     *
+     * @return 用户编号
+     */
+    private static Long getUserId() {
+        return WebFrameworkUtils.getLoginUserId();
+    }
+
+    /**
+     * 静态内部类实现单例获取
+     *
+     * @author HUIHUI
+     */
+    private static class SingletonManager {
+
+        private static final PermissionApi PERMISSION_API = SpringUtil.getBean(PermissionApi.class);
+
+        public static PermissionApi getPermissionApi() {
+            return PERMISSION_API;
+        }
+
+    }
+
+}

+ 1 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/package-info.java

@@ -0,0 +1 @@
+package cn.iocoder.yudao.module.crm.framework.permission;

+ 1 - 1
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java

@@ -12,7 +12,7 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
 import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessMapper;
 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
-import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission;
+import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;

+ 1 - 1
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java

@@ -12,7 +12,7 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO;
 import cn.iocoder.yudao.module.crm.dal.mysql.clue.CrmClueMapper;
 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
-import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission;
+import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
 import jakarta.annotation.Resource;

+ 1 - 1
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java

@@ -9,7 +9,7 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
 import cn.iocoder.yudao.module.crm.dal.mysql.contact.CrmContactMapper;
 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
-import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission;
+import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;

+ 1 - 1
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contactbusinesslink/CrmContactBusinessLinkServiceImpl.java

@@ -14,7 +14,7 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.contactbusinesslink.CrmContact
 import cn.iocoder.yudao.module.crm.dal.mysql.contactbusinesslink.CrmContactBusinessLinkMapper;
 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
-import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission;
+import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
 import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
 import jakarta.annotation.Resource;
 import org.springframework.stereotype.Service;

+ 1 - 1
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java

@@ -12,7 +12,7 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
 import cn.iocoder.yudao.module.crm.dal.mysql.contract.CrmContractMapper;
 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
-import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission;
+import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
 import jakarta.annotation.Resource;

+ 21 - 18
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java

@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.crm.service.customer;
 import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.operatelogv2.core.enums.OperateLogV2Constants;
 import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerCreateReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerPageReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerTransferReqVO;
@@ -12,7 +13,7 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
 import cn.iocoder.yudao.module.crm.dal.mysql.customer.CrmCustomerMapper;
 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
-import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission;
+import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
@@ -52,6 +53,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
 
     @Override
     @Transactional(rollbackFor = Exception.class)
+    @LogRecord(type = CRM_CUSTOMER, bizNo = "{{#customerId}}", success = "创建了客户")
     public Long createCustomer(CrmCustomerCreateReqVO createReqVO, Long userId) {
         // 插入
         CrmCustomerDO customer = CrmCustomerConvert.INSTANCE.convert(createReqVO);
@@ -60,26 +62,34 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
         // 创建数据权限
         crmPermissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType())
                 .setBizId(customer.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 设置当前操作的人为负责人
+
+        // 添加日志上下文所需
+        LogRecordContext.putVariable("customerId", customer.getId());
         return customer.getId();
     }
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    @LogRecord(success = "更新了客户{_DIFF{#updateReqVO}}", type = CRM_CUSTOMER, subType = "更新客户", bizNo = "{{#updateReqVO.id}}")
+    @LogRecord(type = CRM_CUSTOMER, bizNo = "{{#updateReqVO.id}}", success = "更新了客户{_DIFF{#updateReqVO}}")
     @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE)
     public void updateCustomer(CrmCustomerUpdateReqVO updateReqVO) {
         // 校验存在
         CrmCustomerDO oldCustomerDO = validateCustomerExists(updateReqVO.getId());
 
-        // __DIFF 函数传递了一个参数,传递的参数是修改之后的对象,这种方式需要在方法内部向 LogRecordContext 中 put 一个变量,代表是之前的对象,这个对象可以是null
-        LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldCustomerDO, CrmCustomerUpdateReqVO.class));
         // 更新
         CrmCustomerDO updateObj = CrmCustomerConvert.INSTANCE.convert(updateReqVO);
         customerMapper.updateById(updateObj);
+
+        // __DIFF 函数传递了一个参数,传递的参数是修改之后的对象,这种方式需要在方法内部向 LogRecordContext 中 put 一个变量,代表是之前的对象,这个对象可以是null
+        LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldCustomerDO, CrmCustomerUpdateReqVO.class));
+        HashMap<String, Object> extra = new HashMap<>();
+        extra.put("tips", "随便记录一点啦");
+        LogRecordContext.putVariable(OperateLogV2Constants.EXTRA_KEY, extra);
     }
 
     @Override
     @Transactional(rollbackFor = Exception.class)
+    @LogRecord(type = CRM_CUSTOMER, bizNo = "{{#id}}", success = "删除了客户")
     @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#id", level = CrmPermissionLevelEnum.OWNER)
     public void deleteCustomer(Long id) {
         // 校验存在
@@ -125,27 +135,17 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
      */
     @Override
     public void validateCustomer(Long customerId) {
-        // 校验客户是否存在
-        if (customerId == null) {
-            throw exception(CUSTOMER_NOT_EXISTS);
-        }
-        CrmCustomerDO customer = customerMapper.selectById(customerId);
-        if (Objects.isNull(customer)) {
-            throw exception(CUSTOMER_NOT_EXISTS);
-        }
+        validateCustomerExists(customerId);
     }
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    // TODO @puhui999:@LogRecord(type = CRM_CUSTOMER, subType = "客户转移", bizNo = "{{#reqVO.id}}", success = TRANSFER_CUSTOMER_LOG_SUCCESS)
-    @LogRecord(success = TRANSFER_CUSTOMER_LOG_SUCCESS, type = CRM_CUSTOMER, subType = "客户转移", bizNo = "{{#reqVO.id}}")
+    @LogRecord(type = CRM_CUSTOMER, bizNo = "{{#reqVO.id}}", success = TRANSFER_CUSTOMER_LOG_SUCCESS)
     @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#reqVO.id", level = CrmPermissionLevelEnum.OWNER)
     public void transferCustomer(CrmCustomerTransferReqVO reqVO, Long userId) {
         // 1. 校验客户是否存在
-        validateCustomer(reqVO.getId());
-        // 添加 crmCustomer 到日志上下文 TODO 日志记录放在 service 里是因为已经过了权限校验查询时不用走两次校验
-        // TODO @puhui999:customer 不用查询,从 1. 拿到哈;然后 put这个动作,可以放到 3.;这样逻辑结构就是,校验、逻辑、日志,更加清晰
-        LogRecordContext.putVariable("crmCustomer", customerMapper.selectById(reqVO.getId()));
+        CrmCustomerDO customerDO = validateCustomerExists(reqVO.getId());
+
         // 2.1 数据权限转移
         crmPermissionService.transferPermission(
                 CrmCustomerConvert.INSTANCE.convert(reqVO, userId).setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType()));
@@ -153,9 +153,11 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
         customerMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId());
 
         // 3. TODO 记录转移日志
+        LogRecordContext.putVariable("crmCustomer", customerDO);
     }
 
     @Override
+    @LogRecord(type = CRM_CUSTOMER, bizNo = "{{#updateReqVO.id}}", success = "锁定了客户")
     public void lockCustomer(CrmCustomerUpdateReqVO updateReqVO) {
         // 校验存在
         validateCustomerExists(updateReqVO.getId());
@@ -169,6 +171,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
 
     @Override
     @Transactional(rollbackFor = Exception.class)
+    @LogRecord(type = CRM_CUSTOMER, bizNo = "{{#id}}", success = "将客户放入了公海")
     @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#id", level = CrmPermissionLevelEnum.OWNER)
     public void putCustomerPool(Long id) {
         // 1. 校验存在

+ 2 - 3
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionServiceImpl.java

@@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
 import cn.iocoder.yudao.module.crm.dal.mysql.permission.CrmPermissionMapper;
 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
+import cn.iocoder.yudao.module.crm.framework.permission.core.util.CrmPermissionUtils;
 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO;
 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
@@ -89,8 +90,7 @@ public class CrmPermissionServiceImpl implements CrmPermissionService {
         CrmPermissionDO oldPermission = crmPermissionMapper.selectByBizTypeAndBizIdByUserId(
                 transferReqBO.getBizType(), transferReqBO.getBizId(), transferReqBO.getUserId());
         String bizTypeName = CrmBizTypeEnum.getNameByType(transferReqBO.getBizType());
-        // TODO 校验是否为超级管理员 || 1
-        if (oldPermission == null || !isOwner(oldPermission.getLevel())) {
+        if (oldPermission == null || !isOwner(oldPermission.getLevel()) || !CrmPermissionUtils.validateAdminUser()) {
             throw exception(CRM_PERMISSION_DENIED, bizTypeName);
         }
         // 1.1 校验转移对象是否已经是该负责人
@@ -138,7 +138,6 @@ public class CrmPermissionServiceImpl implements CrmPermissionService {
     }
 
     @Override
-    @Transactional(rollbackFor = Exception.class) // TODO @puhui999:这里不用加的,就一个操作哈;
     public void deletePermission(Integer bizType, Long bizId) {
         int deletedCount = crmPermissionMapper.deletePermission(bizType, bizId);
         if (deletedCount == 0) {

+ 1 - 1
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanServiceImpl.java

@@ -15,7 +15,7 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO
 import cn.iocoder.yudao.module.crm.dal.mysql.receivable.CrmReceivablePlanMapper;
 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
-import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission;
+import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
 import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;

+ 1 - 1
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java

@@ -18,7 +18,7 @@ import cn.iocoder.yudao.module.crm.dal.mysql.receivable.CrmReceivableMapper;
 import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum;
 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
-import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission;
+import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
 import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;

+ 28 - 46
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/util/CrmQueryWrapperUtils.java

@@ -7,9 +7,11 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
 import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum;
 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
+import cn.iocoder.yudao.module.crm.framework.permission.core.util.CrmPermissionUtils;
 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
 import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
+import com.github.yulichang.autoconfigure.MybatisPlusJoinProperties;
 import com.github.yulichang.wrapper.MPJLambdaWrapper;
 
 import java.util.Collection;
@@ -33,46 +35,41 @@ public class CrmQueryWrapperUtils {
      * @param userId    用户编号
      * @param sceneType 场景类型
      * @param pool      公海
-     * @return 是否 (是:需要执行查询,否:不需要查询调用方法直接返回空)
      */
-    // TODO @puhui999:bizId 直接传递会不会简单点 回复:还是需要 SFunction 因为分页连表时不知道 bizId 是多少;是不是把 bizId 传入就好啦?
-    public static <T extends MPJLambdaWrapper<?>, S> boolean appendPermissionCondition(T query, Integer bizType, SFunction<S, ?> bizId,
-                                                                                       Long userId, Integer sceneType, Boolean pool) {
+    public static <T extends MPJLambdaWrapper<?>, S> void appendPermissionCondition(T query, Integer bizType, SFunction<S, ?> bizId,
+                                                                                    Long userId, Integer sceneType, Boolean pool) {
+        final String ownerUserIdField = SingletonManager.getMybatisPlusJoinProperties().getTableAlias() + ".owner_user_id";
         // 1. 构建数据权限连表条件
-        if (ObjUtil.notEqual(validateAdminUser(userId), Boolean.TRUE)) { // 管理员不需要数据权限
+        if (ObjUtil.notEqual(CrmPermissionUtils.validateAdminUser(), Boolean.TRUE) && ObjUtil.notEqual(pool, Boolean.TRUE)) { // 管理员,公海不需要数据权限
             query.innerJoin(CrmPermissionDO.class, on -> on.eq(CrmPermissionDO::getBizType, bizType)
-                    .eq(CrmPermissionDO::getBizId, bizId)
+                    .eq(CrmPermissionDO::getBizId, bizId) // 只能使用 SFunction 如果传 id 解析出来的 sql 不对
                     .eq(CrmPermissionDO::getUserId, userId));
         }
         // 2.1 场景一:我负责的数据
         if (CrmSceneTypeEnum.isOwner(sceneType)) {
-            query.eq("owner_user_id", userId);
+            query.eq(ownerUserIdField, userId);
         }
         // 2.2 场景二:我参与的数据
         if (CrmSceneTypeEnum.isInvolved(sceneType)) {
-            query.ne("owner_user_id", userId)
-                    // TODO @puhui999:IN 是不是更合适哈;
-                    .and(q -> q.eq(CrmPermissionDO::getLevel, CrmPermissionLevelEnum.READ.getLevel())
-                            .or()
-                            .eq(CrmPermissionDO::getLevel, CrmPermissionLevelEnum.WRITE.getLevel()));
+            query.ne(ownerUserIdField, userId)
+                    .in(CrmPermissionDO::getLevel, CrmPermissionLevelEnum.READ.getLevel(), CrmPermissionLevelEnum.WRITE.getLevel());
         }
         // 2.3 场景三:下属负责的数据
         if (CrmSceneTypeEnum.isSubordinate(sceneType)) {
-            // TODO @puhui999:要不如果没有下属,拼一个 owner_user_id in null,不返回结果就好啦;
-            List<AdminUserRespDTO> subordinateUsers = getAdminUserApi().getUserListBySubordinate(userId);
+            List<AdminUserRespDTO> subordinateUsers = SingletonManager.getAdminUserApi().getUserListBySubordinate(userId);
             if (CollUtil.isEmpty(subordinateUsers)) {
-                return false;
+                query.eq(ownerUserIdField, -1); // 不返回任何结果
+            } else {
+                query.in(ownerUserIdField, convertSet(subordinateUsers, AdminUserRespDTO::getId));
             }
-            query.in("owner_user_id", convertSet(subordinateUsers, AdminUserRespDTO::getId));
         }
 
         // 3. 拼接公海的查询条件
         if (ObjUtil.equal(pool, Boolean.TRUE)) { // 情况一:公海
-            query.isNull("owner_user_id");
+            query.isNull(ownerUserIdField);
         } else { // 情况二:不是公海
-            query.isNotNull("owner_user_id");
+            query.isNotNull(ownerUserIdField);
         }
-        return true;
     }
 
     /**
@@ -84,7 +81,7 @@ public class CrmQueryWrapperUtils {
      * @param userId  用户编号
      */
     public static <T extends MPJLambdaWrapper<?>> void appendPermissionCondition(T query, Integer bizType, Collection<Long> bizIds, Long userId) {
-        if (ObjUtil.equal(validateAdminUser(userId), Boolean.TRUE)) {// 管理员不需要数据权限
+        if (ObjUtil.equal(CrmPermissionUtils.validateAdminUser(), Boolean.TRUE)) {// 管理员不需要数据权限
             return;
         }
 
@@ -93,38 +90,23 @@ public class CrmQueryWrapperUtils {
                         .in(CollUtil.isNotEmpty(bizIds), CrmPermissionDO::getUserId, userId));
     }
 
-    private static AdminUserApi getAdminUserApi() {
-        return AdminUserApiHolder.ADMIN_USER_API;
-    }
-
     /**
-     * 校验用户是否是管理员
-     *
-     * @param userId 用户编号
-     * @return 是/否
-     */
-    private static boolean validateAdminUser(Long userId) {
-        // TODO 查询权限配置表用户的角色信息
-        // TODO @puhui999:查询用户的角色;CRM_ADMIN("crm_admin", "CRM 管理员"),
-        //CrmPermissionConfig permissionConfig = crmPermissionConfigService.getPermissionConfigByUserId(userId);
-        //if (permissionConfig == null) {
-        //    return false;
-        //}
-        //// 校验是否为管理员
-        //if (permissionConfig.getIsAdmin()){
-        //    return true;
-        //}
-        return false;
-    }
-
-    /**
-     * 静态内部类实现 AdminUserApi 单例获取
+     * 静态内部类实现单例获取
      *
      * @author HUIHUI
      */
-    private static class AdminUserApiHolder {
+    private static class SingletonManager {
 
         private static final AdminUserApi ADMIN_USER_API = SpringUtil.getBean(AdminUserApi.class);
+        private static final MybatisPlusJoinProperties MYBATIS_PLUS_JOIN_PROPERTIES = SpringUtil.getBean(MybatisPlusJoinProperties.class);
+
+        public static AdminUserApi getAdminUserApi() {
+            return ADMIN_USER_API;
+        }
+
+        public static MybatisPlusJoinProperties getMybatisPlusJoinProperties() {
+            return MYBATIS_PLUS_JOIN_PROPERTIES;
+        }
 
     }
 

+ 96 - 25
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/activity/AppActivityController.java

@@ -7,26 +7,33 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.module.promotion.controller.app.activity.vo.AppActivityRespVO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.bargain.BargainActivityDO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountActivityDO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountProductDO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillActivityDO;
+import cn.iocoder.yudao.module.promotion.enums.common.PromotionActivityStatusEnum;
 import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
 import cn.iocoder.yudao.module.promotion.service.bargain.BargainActivityService;
 import cn.iocoder.yudao.module.promotion.service.combination.CombinationActivityService;
+import cn.iocoder.yudao.module.promotion.service.discount.DiscountActivityService;
+import cn.iocoder.yudao.module.promotion.service.reward.RewardActivityService;
 import cn.iocoder.yudao.module.promotion.service.seckill.SeckillActivityService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 
-import jakarta.annotation.Resource;
 import java.time.LocalDateTime;
 import java.util.*;
+import java.util.stream.Collectors;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
 
 @Tag(name = "用户 APP - 营销活动") // 用于提供跨多个活动的 HTTP 接口
 @RestController
@@ -40,6 +47,10 @@ public class AppActivityController {
     private SeckillActivityService seckillActivityService;
     @Resource
     private BargainActivityService bargainActivityService;
+    @Resource
+    private DiscountActivityService discountActivityService;
+    @Resource
+    private RewardActivityService rewardActivityService;
 
     @GetMapping("/list-by-spu-id")
     @Operation(summary = "获得单个商品,近期参与的每个活动")
@@ -64,45 +75,105 @@ public class AppActivityController {
         if (CollUtil.isEmpty(spuIds)) {
             return new ArrayList<>();
         }
+
         LocalDateTime now = LocalDateTime.now();
+        // 获取开启的且开始的且没有结束的活动
         List<AppActivityRespVO> activityList = new ArrayList<>();
+        // 1. 拼团活动
+        getCombinationActivities(spuIds, now, activityList);
+        // 2. 秒杀活动
+        getSeckillActivities(spuIds, now, activityList);
+        // 3. 砍价活动
+        getBargainActivities(spuIds, now, activityList);
+        // 4. 限时折扣活动
+        getDiscountActivities(spuIds, now, activityList);
+        // 5. 满减送活动
+        getRewardActivities(spuIds, now, activityList);
+        return activityList;
+    }
 
-        // 1. 拼团活动 - 获取开启的且开始的且没有结束的活动
+    private void getCombinationActivities(Collection<Long> spuIds, LocalDateTime now, List<AppActivityRespVO> activityList) {
         List<CombinationActivityDO> combinationActivities = combinationActivityService.getCombinationActivityBySpuIdsAndStatusAndDateTimeLt(
                 spuIds, CommonStatusEnum.ENABLE.getStatus(), now);
-        if (CollUtil.isNotEmpty(combinationActivities)) {
-            combinationActivities.forEach(item -> {
-                activityList.add(new AppActivityRespVO().setId(item.getId())
-                        .setType(PromotionTypeEnum.COMBINATION_ACTIVITY.getType()).setName(item.getName())
-                        .setSpuId(item.getSpuId()).setStartTime(item.getStartTime()).setEndTime(item.getEndTime()));
-            });
+        if (CollUtil.isEmpty(combinationActivities)) {
+            return;
         }
 
-        // 2. 秒杀活动 - 获取开启的且开始的且没有结束的活动
+        combinationActivities.forEach(item -> {
+            activityList.add(new AppActivityRespVO().setId(item.getId())
+                    .setType(PromotionTypeEnum.COMBINATION_ACTIVITY.getType()).setName(item.getName())
+                    .setSpuId(item.getSpuId()).setStartTime(item.getStartTime()).setEndTime(item.getEndTime()));
+        });
+    }
+
+    private void getSeckillActivities(Collection<Long> spuIds, LocalDateTime now, List<AppActivityRespVO> activityList) {
         List<SeckillActivityDO> seckillActivities = seckillActivityService.getSeckillActivityBySpuIdsAndStatusAndDateTimeLt(
                 spuIds, CommonStatusEnum.ENABLE.getStatus(), now);
-        if (CollUtil.isNotEmpty(seckillActivities)) {
-            seckillActivities.forEach(item -> {
-                activityList.add(new AppActivityRespVO().setId(item.getId())
-                        .setType(PromotionTypeEnum.SECKILL_ACTIVITY.getType()).setName(item.getName())
-                        .setSpuId(item.getSpuId()).setStartTime(item.getStartTime()).setEndTime(item.getEndTime()));
-            });
+        if (CollUtil.isEmpty(seckillActivities)) {
+            return;
         }
 
-        // 3. 砍价活动 - 获取开启的且开始的且没有结束的活动
+        seckillActivities.forEach(item -> {
+            activityList.add(new AppActivityRespVO().setId(item.getId())
+                    .setType(PromotionTypeEnum.SECKILL_ACTIVITY.getType()).setName(item.getName())
+                    .setSpuId(item.getSpuId()).setStartTime(item.getStartTime()).setEndTime(item.getEndTime()));
+        });
+    }
+
+    private void getBargainActivities(Collection<Long> spuIds, LocalDateTime now, List<AppActivityRespVO> activityList) {
         List<BargainActivityDO> bargainActivities = bargainActivityService.getBargainActivityBySpuIdsAndStatusAndDateTimeLt(
                 spuIds, CommonStatusEnum.ENABLE.getStatus(), now);
         if (CollUtil.isNotEmpty(bargainActivities)) {
-            bargainActivities.forEach(item -> {
-                activityList.add(new AppActivityRespVO().setId(item.getId())
-                        .setType(PromotionTypeEnum.BARGAIN_ACTIVITY.getType()).setName(item.getName())
-                        .setSpuId(item.getSpuId()).setStartTime(item.getStartTime()).setEndTime(item.getEndTime()));
-            });
+            return;
         }
 
-        // TODO 芋艿:满减送活动
-        // TODO 芋艿:限时折扣活动
-        return activityList;
+        bargainActivities.forEach(item -> {
+            activityList.add(new AppActivityRespVO().setId(item.getId())
+                    .setType(PromotionTypeEnum.BARGAIN_ACTIVITY.getType()).setName(item.getName())
+                    .setSpuId(item.getSpuId()).setStartTime(item.getStartTime()).setEndTime(item.getEndTime()));
+        });
+    }
+
+    private void getDiscountActivities(Collection<Long> spuIds, LocalDateTime now, List<AppActivityRespVO> activityList) {
+        List<DiscountActivityDO> discountActivities = discountActivityService.getDiscountActivityBySpuIdsAndStatusAndDateTimeLt(
+                spuIds, CommonStatusEnum.ENABLE.getStatus(), now);
+        if (CollUtil.isEmpty(discountActivities)) {
+            return;
+        }
+
+        List<DiscountProductDO> products = discountActivityService.getDiscountProductsByActivityId(
+                convertSet(discountActivities, DiscountActivityDO::getId));
+        Map<Long, Long> productMap = convertMap(products, DiscountProductDO::getActivityId, DiscountProductDO::getSpuId);
+        discountActivities.forEach(item -> {
+            activityList.add(new AppActivityRespVO().setId(item.getId())
+                    .setType(PromotionTypeEnum.DISCOUNT_ACTIVITY.getType()).setName(item.getName())
+                    .setSpuId(productMap.get(item.getId())).setStartTime(item.getStartTime()).setEndTime(item.getEndTime()));
+        });
+    }
+
+    private void getRewardActivities(Collection<Long> spuIds, LocalDateTime now, List<AppActivityRespVO> activityList) {
+        List<RewardActivityDO> rewardActivityList = rewardActivityService.getRewardActivityBySpuIdsAndStatusAndDateTimeLt(
+                spuIds, PromotionActivityStatusEnum.RUN.getStatus(), now);
+        if (CollUtil.isEmpty(rewardActivityList)) {
+            return;
+        }
+
+        Map<Long, Optional<RewardActivityDO>> spuIdAndActivityMap = spuIds.stream()
+                .collect(Collectors.toMap(
+                        spuId -> spuId,
+                        spuId -> rewardActivityList.stream()
+                                .filter(activity -> activity.getProductSpuIds().contains(spuId))
+                                .max(Comparator.comparing(RewardActivityDO::getCreateTime))));
+        for (Long supId : spuIdAndActivityMap.keySet()) {
+            if (spuIdAndActivityMap.get(supId).isEmpty()) {
+                continue;
+            }
+
+            RewardActivityDO rewardActivityDO = spuIdAndActivityMap.get(supId).get();
+            activityList.add(new AppActivityRespVO().setId(rewardActivityDO.getId())
+                    .setType(PromotionTypeEnum.REWARD_ACTIVITY.getType()).setName(rewardActivityDO.getName())
+                    .setSpuId(supId).setStartTime(rewardActivityDO.getStartTime()).setEndTime(rewardActivityDO.getEndTime()));
+        }
     }
 
 }

+ 17 - 1
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/discount/DiscountActivityMapper.java

@@ -7,9 +7,9 @@ import cn.iocoder.yudao.module.promotion.controller.admin.discount.vo.DiscountAc
 import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountActivityDO;
 import org.apache.ibatis.annotations.Mapper;
 
+import java.time.LocalDateTime;
 import java.util.Collection;
 import java.util.List;
-import java.util.Set;
 
 /**
  * 限时折扣活动 Mapper
@@ -27,4 +27,20 @@ public interface DiscountActivityMapper extends BaseMapperX<DiscountActivityDO>
                 .orderByDesc(DiscountActivityDO::getId));
     }
 
+    /**
+     * 获取指定活动编号的活动列表且
+     * 开始时间和结束时间小于给定时间 dateTime 的活动列表
+     *
+     * @param ids      活动编号
+     * @param dateTime 指定日期
+     * @return 活动列表
+     */
+    default List<DiscountActivityDO> selectListByIdsAndDateTimeLt(Collection<Long> ids, LocalDateTime dateTime) {
+        return selectList(new LambdaQueryWrapperX<DiscountActivityDO>()
+                .in(DiscountActivityDO::getId, ids)
+                .lt(DiscountActivityDO::getStartTime, dateTime)
+                .gt(DiscountActivityDO::getEndTime, dateTime)// 开始时间 < 指定时间 < 结束时间,也就是说获取指定时间段的活动
+                .orderByDesc(DiscountActivityDO::getCreateTime));
+    }
+
 }

+ 18 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/discount/DiscountProductMapper.java

@@ -2,11 +2,13 @@ package cn.iocoder.yudao.module.promotion.dal.mysql.discount;
 
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountProductDO;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Param;
 
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 
 /**
  * 限时折扣商城 Mapper
@@ -30,4 +32,20 @@ public interface DiscountProductMapper extends BaseMapperX<DiscountProductDO> {
 
     // TODO @zhangshuai:逻辑里,尽量避免写 join 语句哈,你可以看看这个查询,有什么办法优化?目前的一个思路,是分 2 次查询,性能也是 ok 的
     List<DiscountProductDO> getMatchDiscountProductList(@Param("skuIds") Collection<Long> skuIds);
+
+    /**
+     * 查询出指定 spuId 的 spu 参加的活动最接近现在的一条记录。多个的话,一个 spuId 对应一个最近的活动编号
+     *
+     * @param spuIds spu 编号
+     * @param status 状态
+     * @return 包含 spuId 和 activityId 的 map 对象列表
+     */
+    default List<Map<String, Object>> selectSpuIdAndActivityIdMapsBySpuIdsAndStatus(Collection<Long> spuIds, Integer status) {
+        return selectMaps(new QueryWrapper<DiscountProductDO>()
+                .select("spu_id AS spuId, MAX(DISTINCT(activity_id)) AS activityId")
+                .in("spu_id", spuIds)
+                .eq("activity_status", status)
+                .groupBy("spu_id"));
+    }
+
 }

+ 31 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/reward/RewardActivityMapper.java

@@ -1,14 +1,19 @@
 package cn.iocoder.yudao.module.promotion.dal.mysql.reward;
 
+import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityPageReqVO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import org.apache.ibatis.annotations.Mapper;
 
+import java.time.LocalDateTime;
 import java.util.Collection;
 import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 
 /**
  * 满减送活动 Mapper
@@ -35,4 +40,30 @@ public interface RewardActivityMapper extends BaseMapperX<RewardActivityDO> {
                 .eq(RewardActivityDO::getStatus, status));
     }
 
+    default List<RewardActivityDO> selectListBySpuIdsAndStatus(Collection<Long> spuIds, Integer status) {
+        Function<Collection<Long>, String> productScopeValuesFindInSetFunc = ids -> ids.stream()
+                .map(id -> StrUtil.format("FIND_IN_SET({}, product_spu_ids) ", id))
+                .collect(Collectors.joining(" OR "));
+        return selectList(new QueryWrapper<RewardActivityDO>()
+                .eq("status", status)
+                .apply(productScopeValuesFindInSetFunc.apply(spuIds)));
+    }
+
+    /**
+     * 获取指定活动编号的活动列表且
+     * 开始时间和结束时间小于给定时间 dateTime 的活动列表
+     *
+     * @param ids      活动编号
+     * @param dateTime 指定日期
+     * @return 活动列表
+     */
+    default List<RewardActivityDO> selectListByIdsAndDateTimeLt(Collection<Long> ids, LocalDateTime dateTime) {
+        return selectList(new LambdaQueryWrapperX<RewardActivityDO>()
+                .in(RewardActivityDO::getId, ids)
+                .lt(RewardActivityDO::getStartTime, dateTime)
+                .gt(RewardActivityDO::getEndTime, dateTime)// 开始时间 < 指定时间 < 结束时间,也就是说获取指定时间段的活动
+                .orderByDesc(RewardActivityDO::getCreateTime)
+        );
+    }
+
 }

+ 12 - 1
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/discount/DiscountActivityService.java

@@ -6,8 +6,9 @@ import cn.iocoder.yudao.module.promotion.controller.admin.discount.vo.DiscountAc
 import cn.iocoder.yudao.module.promotion.controller.admin.discount.vo.DiscountActivityUpdateReqVO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountActivityDO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountProductDO;
-
 import jakarta.validation.Valid;
+
+import java.time.LocalDateTime;
 import java.util.Collection;
 import java.util.List;
 
@@ -89,4 +90,14 @@ public interface DiscountActivityService {
      */
     List<DiscountProductDO> getDiscountProductsByActivityId(Collection<Long> activityIds);
 
+    /**
+     * 获取指定 spu 编号最近参加的活动,每个 spuId 只返回一条记录
+     *
+     * @param spuIds   spu 编号
+     * @param status   状态
+     * @param dateTime 当前日期时间
+     * @return 折扣活动列表
+     */
+    List<DiscountActivityDO> getDiscountActivityBySpuIdsAndStatusAndDateTimeLt(Collection<Long> spuIds, Integer status, LocalDateTime dateTime);
+
 }

+ 20 - 2
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/discount/DiscountActivityServiceImpl.java

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.promotion.service.discount;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.map.MapUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.promotion.controller.admin.discount.vo.DiscountActivityBaseVO;
@@ -15,16 +16,20 @@ import cn.iocoder.yudao.module.promotion.dal.mysql.discount.DiscountActivityMapp
 import cn.iocoder.yudao.module.promotion.dal.mysql.discount.DiscountProductMapper;
 import cn.iocoder.yudao.module.promotion.enums.common.PromotionActivityStatusEnum;
 import cn.iocoder.yudao.module.promotion.util.PromotionUtils;
+import jakarta.annotation.Resource;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
-import jakarta.annotation.Resource;
+import java.time.LocalDateTime;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.stream.Collectors;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
 import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
 
 /**
@@ -109,7 +114,7 @@ public class DiscountActivityServiceImpl implements DiscountActivityService {
     /**
      * 校验商品是否冲突
      *
-     * @param id 编号
+     * @param id       编号
      * @param products 商品列表
      */
     private void validateDiscountActivityProductConflicts(Long id, List<DiscountActivityBaseVO.Product> products) {
@@ -184,4 +189,17 @@ public class DiscountActivityServiceImpl implements DiscountActivityService {
         return discountProductMapper.selectList("activity_id", activityIds);
     }
 
+    @Override
+    public List<DiscountActivityDO> getDiscountActivityBySpuIdsAndStatusAndDateTimeLt(Collection<Long> spuIds, Integer status, LocalDateTime dateTime) {
+        // 1. 查询出指定 spuId 的 spu 参加的活动最接近现在的一条记录。多个的话,一个 spuId 对应一个最近的活动编号
+        List<Map<String, Object>> spuIdAndActivityIdMaps = discountProductMapper.selectSpuIdAndActivityIdMapsBySpuIdsAndStatus(spuIds, status);
+        if (CollUtil.isEmpty(spuIdAndActivityIdMaps)) {
+            return Collections.emptyList();
+        }
+
+        // 2. 查询活动详情
+        return discountActivityMapper.selectListByIdsAndDateTimeLt(
+                convertSet(spuIdAndActivityIdMaps, map -> MapUtil.getLong(map, "activityId")), dateTime);
+    }
+
 }

+ 12 - 1
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityService.java

@@ -6,8 +6,9 @@ import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivi
 import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityPageReqVO;
 import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityUpdateReqVO;
 import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO;
-
 import jakarta.validation.Valid;
+
+import java.time.LocalDateTime;
 import java.util.Collection;
 import java.util.List;
 
@@ -71,4 +72,14 @@ public interface RewardActivityService {
      */
     List<RewardActivityMatchRespDTO> getMatchRewardActivityList(Collection<Long> spuIds);
 
+    /**
+     * 获取指定 spu 编号最近参加的活动,每个 spuId 只返回一条记录
+     *
+     * @param spuIds   spu 编号
+     * @param status   状态
+     * @param dateTime 当前日期时间
+     * @return 满减送活动列表
+     */
+    List<RewardActivityDO> getRewardActivityBySpuIdsAndStatusAndDateTimeLt(Collection<Long> spuIds, Integer status, LocalDateTime dateTime);
+
 }

+ 19 - 3
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/reward/RewardActivityServiceImpl.java

@@ -11,14 +11,17 @@ import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO;
 import cn.iocoder.yudao.module.promotion.dal.mysql.reward.RewardActivityMapper;
 import cn.iocoder.yudao.module.promotion.enums.common.PromotionActivityStatusEnum;
 import cn.iocoder.yudao.module.promotion.util.PromotionUtils;
+import jakarta.annotation.Resource;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
-import jakarta.annotation.Resource;
+import java.time.LocalDateTime;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
 import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
 import static java.util.Arrays.asList;
 
@@ -100,10 +103,11 @@ public class RewardActivityServiceImpl implements RewardActivityService {
     }
 
     // TODO @芋艿:逻辑有问题,需要优化;要分成全场、和指定来校验;
+
     /**
      * 校验商品参加的活动是否冲突
      *
-     * @param id 活动编号
+     * @param id     活动编号
      * @param spuIds 商品 SPU 编号数组
      */
     private void validateRewardActivitySpuConflicts(Long id, Collection<Long> spuIds) {
@@ -125,7 +129,7 @@ public class RewardActivityServiceImpl implements RewardActivityService {
     /**
      * 获得商品参加的满减送活动的数组
      *
-     * @param spuIds 商品 SPU 编号数组
+     * @param spuIds   商品 SPU 编号数组
      * @param statuses 活动状态数组
      * @return 商品参加的满减送活动的数组
      */
@@ -163,4 +167,16 @@ public class RewardActivityServiceImpl implements RewardActivityService {
         return null;
     }
 
+    @Override
+    public List<RewardActivityDO> getRewardActivityBySpuIdsAndStatusAndDateTimeLt(Collection<Long> spuIds, Integer status, LocalDateTime dateTime) {
+        // 1. 查询出指定 spuId 的 spu 参加的活动
+        List<RewardActivityDO> rewardActivityList = rewardActivityMapper.selectListBySpuIdsAndStatus(spuIds, status);
+        if (CollUtil.isEmpty(rewardActivityList)) {
+            return Collections.emptyList();
+        }
+
+        // 2. 查询活动详情
+        return rewardActivityMapper.selectListByIdsAndDateTimeLt(convertSet(rewardActivityList, RewardActivityDO::getId), dateTime);
+    }
+
 }

+ 0 - 20
yudao-module-system/yudao-module-system-api/pom.xml

@@ -22,32 +22,12 @@
             <artifactId>yudao-common</artifactId>
         </dependency>
 
-        <!-- TODO @puhui999 & 芋艿:操作日志,要不要这么引入? -->
-        <!-- Springboot-注解-通用操作日志组件 -->
-        <!-- 此组件解决的问题是: 「谁」在「什么时间」对「什么」做了「什么事」 -->
-        <dependency>
-            <groupId>io.github.mouzt</groupId>
-            <artifactId>bizlog-sdk</artifactId>
-        </dependency>
-
-        <!-- TODO @puhui999 & 芋艿:要不要移除掉 -->
-        <!--工具类相关-->
-        <dependency>
-            <groupId>com.fasterxml.jackson.core</groupId>
-            <artifactId>jackson-databind</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>com.fasterxml.jackson.core</groupId>
-            <artifactId>jackson-core</artifactId>
-        </dependency>
-
         <!-- 参数校验 -->
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-validation</artifactId>
             <optional>true</optional>
         </dependency>
-
     </dependencies>
 
 </project>

+ 14 - 7
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/logger/OperateLogApi.java

@@ -1,11 +1,12 @@
 package cn.iocoder.yudao.module.system.api.logger;
 
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogCreateReqDTO;
+import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2CreateReqDTO;
+import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2PageReqDTO;
 import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2RespDTO;
 import jakarta.validation.Valid;
 
-import java.util.List;
-
 /**
  * 操作日志 API 接口
  *
@@ -21,12 +22,18 @@ public interface OperateLogApi {
     void createOperateLog(@Valid OperateLogCreateReqDTO createReqDTO);
 
     /**
-     * 获取指定模块的指定数据的操作日志
+     * 创建操作日志
+     *
+     * @param createReqDTO 请求
+     */
+    void createOperateLogV2(@Valid OperateLogV2CreateReqDTO createReqDTO);
+
+    /**
+     * 获取指定模块的指定数据的操作日志分页
      *
-     * @param module 操作模块
-     * @param bizId  操作模块编号
-     * @return 操作日志
+     * @param pageReqVO 请求
+     * @return 操作日志分页
      */
-    List<OperateLogV2RespDTO> getOperateLogByModuleAndBizId(String module, Long bizId);
+    PageResult<OperateLogV2RespDTO> getOperateLogPage(OperateLogV2PageReqDTO pageReqVO);
 
 }

+ 48 - 10
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/logger/bo/OperateLogV2CreateReqBO.java → yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/logger/dto/OperateLogV2CreateReqDTO.java

@@ -1,16 +1,20 @@
-package cn.iocoder.yudao.module.system.service.logger.bo;
+package cn.iocoder.yudao.module.system.api.logger.dto;
 
 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
 import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
 import lombok.Data;
 
+import java.time.LocalDateTime;
+import java.util.Map;
+
 /**
  * 系统操作日志 Create Req BO
  *
  * @author HUIHUI
  */
 @Data
-public class OperateLogV2CreateReqBO {
+public class OperateLogV2CreateReqDTO {
 
     /**
      * 链路追踪编号
@@ -23,29 +27,29 @@ public class OperateLogV2CreateReqBO {
      *
      * 关联 MemberUserDO 的 id 属性,或者 AdminUserDO 的 id 属性
      */
-    @NotEmpty(message = "用户编号不能为空")
+    @NotNull(message = "用户编号不能为空")
     private Long userId;
     /**
      * 用户类型
      *
      * 关联 {@link  UserTypeEnum}
      */
-    @NotEmpty(message = "用户类型不能为空")
+    @NotNull(message = "用户类型不能为空")
     private Integer userType;
     /**
-     * 操作模块
+     * 操作模块类型
      */
-    @NotEmpty(message = "操作模块不能为空")
-    private String module;
+    @NotEmpty(message = "操作模块类型不能为空")
+    private String type;
     /**
      * 操作名
      */
     @NotEmpty(message = "操作名不能为空")
-    private String name;
+    private String subType;
     /**
      * 操作模块业务编号
      */
-    @NotEmpty(message = "操作模块业务编号不能为空")
+    @NotNull(message = "操作模块业务编号不能为空")
     private Long bizId;
     /**
      * 操作内容,记录整个操作的明细
@@ -57,7 +61,7 @@ public class OperateLogV2CreateReqBO {
      * 拓展字段,有些复杂的业务,需要记录一些字段 ( JSON 格式 )
      * 例如说,记录订单编号,{ orderId: "1"}
      */
-    private String extra;
+    private Map<String, Object> extra;
 
     /**
      * 请求方法名
@@ -80,4 +84,38 @@ public class OperateLogV2CreateReqBO {
     @NotEmpty(message = "浏览器 UA 不能为空")
     private String userAgent;
 
+    /**
+     * Java 方法名
+     */
+    private String javaMethod;
+    /**
+     * Java 方法的参数
+     */
+    private String javaMethodArgs;
+
+    /**
+     * 开始时间
+     */
+    private LocalDateTime startTime;
+
+    /**
+     * 执行时长,单位:毫秒
+     */
+    private Integer duration;
+
+    /**
+     * 结果码
+     */
+    private Integer resultCode;
+
+    /**
+     * 结果提示
+     */
+    private String resultMsg;
+
+    /**
+     * 结果数据
+     */
+    private String resultData;
+
 }

+ 28 - 0
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/logger/dto/OperateLogV2PageReqDTO.java

@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.system.api.logger.dto;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import lombok.Data;
+
+/**
+ * 操作日志分页 Request DTO
+ *
+ * @author HUIHUI
+ */
+@Data
+public class OperateLogV2PageReqDTO extends PageParam {
+
+    /**
+     * 模块类型
+     */
+    private String bizType;
+    /**
+     * 模块数据编号
+     */
+    private Long bizId;
+
+    /**
+     * 用户编号
+     */
+    private Long userId;
+
+}

+ 40 - 18
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/logger/dto/OperateLogV2RespDTO.java

@@ -1,13 +1,9 @@
 package cn.iocoder.yudao.module.system.api.logger.dto;
 
-import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
-import com.fasterxml.jackson.annotation.JsonFormat;
 import lombok.Data;
 
 import java.time.LocalDateTime;
-
-import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
-import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT;
+import java.util.Map;
 
 /**
  * 系统操作日志 Resp DTO
@@ -25,18 +21,22 @@ public class OperateLogV2RespDTO {
      * 用户编号
      */
     private Long userId;
+    /**
+     * 用户名称
+     */
+    private String userName;
     /**
      * 用户类型
      */
     private Integer userType;
     /**
-     * 操作模块
+     * 操作模块类型
      */
-    private String module;
+    private String type;
     /**
      * 操作名
      */
-    private String name;
+    private String subType;
     /**
      * 操作模块业务编号
      */
@@ -48,7 +48,7 @@ public class OperateLogV2RespDTO {
     /**
      * 拓展字段
      */
-    private String extra;
+    private Map<String, Object> extra;
 
     /**
      * 请求方法名
@@ -68,20 +68,42 @@ public class OperateLogV2RespDTO {
     private String userAgent;
 
     /**
-     * 创建时间
+     * Java 方法名
      */
-    // TODO puhui999: 木得效果怎么肥事
-    @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
-    private LocalDateTime createTime;
+    private String javaMethod;
+    /**
+     * Java 方法的参数
+     */
+    private String javaMethodArgs;
+
+    /**
+     * 开始时间
+     */
+    private LocalDateTime startTime;
+
+    /**
+     * 执行时长,单位:毫秒
+     */
+    private Integer duration;
+
+    /**
+     * 结果码
+     */
+    private Integer resultCode;
 
-    // TODO @puhui999:下面 2 个字段不用返回;用 userId 哈;返回一个 userName
     /**
-     * 创建者
+     * 结果提示
      */
-    private String creator;
+    private String resultMsg;
+
     /**
-     * 创建者名称
+     * 结果数据
      */
-    private String creatorName;
+    private String resultData;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
 
 }

+ 23 - 10
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/logger/OperateLogApiImpl.java

@@ -1,18 +1,21 @@
 package cn.iocoder.yudao.module.system.api.logger;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogCreateReqDTO;
+import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2CreateReqDTO;
+import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2PageReqDTO;
 import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2RespDTO;
 import cn.iocoder.yudao.module.system.dal.dataobject.logger.OperateLogV2DO;
 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
 import cn.iocoder.yudao.module.system.service.logger.OperateLogService;
 import cn.iocoder.yudao.module.system.service.user.AdminUserService;
 import jakarta.annotation.Resource;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
-import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
@@ -39,21 +42,31 @@ public class OperateLogApiImpl implements OperateLogApi {
     }
 
     @Override
-    public List<OperateLogV2RespDTO> getOperateLogByModuleAndBizId(String module, Long bizId) {
-        List<OperateLogV2DO> logList = operateLogService.getOperateLogByModuleAndBizId(module, bizId);
-        if (CollUtil.isEmpty(logList)) {
-            return Collections.emptyList();
+    @Async
+    public void createOperateLogV2(OperateLogV2CreateReqDTO createReqDTO) {
+        operateLogService.createOperateLogV2(createReqDTO);
+    }
+
+    @Override
+    public PageResult<OperateLogV2RespDTO> getOperateLogPage(OperateLogV2PageReqDTO pageReqVO) {
+        PageResult<OperateLogV2DO> operateLogPage = operateLogService.getOperateLogPage(pageReqVO);
+        if (CollUtil.isEmpty(operateLogPage.getList())) {
+            return PageResult.empty();
         }
 
         // 获取用户
-        List<AdminUserDO> userList = adminUserService.getUserList(convertSet(logList, item -> Long.parseLong(item.getCreator())));
+        List<AdminUserDO> userList = adminUserService.getUserList(convertSet(operateLogPage.getList(), OperateLogV2DO::getUserId));
+        return BeanUtils.toBean(operateLogPage, OperateLogV2RespDTO.class).setList(setUserInfo(operateLogPage.getList(), userList));
+    }
+
+    private static List<OperateLogV2RespDTO> setUserInfo(List<OperateLogV2DO> logList, List<AdminUserDO> userList) {
         Map<Long, AdminUserDO> userMap = convertMap(userList, AdminUserDO::getId);
         return convertList(logList, item -> {
-            OperateLogV2RespDTO bean = BeanUtils.toBean(item, OperateLogV2RespDTO.class);
-            findAndThen(userMap, Long.parseLong(item.getCreator()), user -> {
-                bean.setCreatorName(user.getNickname());
+            OperateLogV2RespDTO respDTO = BeanUtils.toBean(item, OperateLogV2RespDTO.class);
+            findAndThen(userMap, item.getUserId(), user -> {
+                respDTO.setUserName(user.getNickname());
             });
-            return bean;
+            return respDTO;
         });
     }
 

+ 59 - 10
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/logger/OperateLogV2DO.java

@@ -1,13 +1,19 @@
 package cn.iocoder.yudao.module.system.dal.dataobject.logger;
 
 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
 import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 
+import java.time.LocalDateTime;
+import java.util.Map;
+
 /**
  * 操作日志表 V2
  *
@@ -19,6 +25,16 @@ import lombok.EqualsAndHashCode;
 @EqualsAndHashCode(callSuper = true)
 public class OperateLogV2DO extends BaseDO {
 
+    /**
+     * {@link #javaMethodArgs} 的最大长度
+     */
+    public static final Integer JAVA_METHOD_ARGS_MAX_LENGTH = 8000;
+
+    /**
+     * {@link #resultData} 的最大长度
+     */
+    public static final Integer RESULT_MAX_LENGTH = 4000;
+
     /**
      * 日志主键
      */
@@ -42,15 +58,14 @@ public class OperateLogV2DO extends BaseDO {
      * 关联 {@link  UserTypeEnum}
      */
     private Integer userType;
-    // TODO @puhui999:module 改成 type,name 改成 subType;
     /**
-     * 操作模块
+     * 操作模块类型
      */
-    private String module;
+    private String type;
     /**
      * 操作名
      */
-    private String name;
+    private String subType;
     /**
      * 操作模块业务编号
      */
@@ -66,8 +81,8 @@ public class OperateLogV2DO extends BaseDO {
      *
      * 例如说,记录订单编号,{ orderId: "1"}
      */
-    // TODO @puhui999:看看能不能类似 exts 搞 json 格式;
-    private String extra;
+    @TableField(typeHandler = JacksonTypeHandler.class)
+    private Map<String, Object> extra;
     /**
      * 请求方法名
      */
@@ -85,9 +100,43 @@ public class OperateLogV2DO extends BaseDO {
      */
     private String userAgent;
 
-    // TODO @芋艿:requestUrl、requestMethod
-    // TODO @芋艿:javaMethod、javaMethodArgs
-    // TODO @芋艿:startTime、duration
-    // TODO @芋艿:resultMsg、resultData
+    /**
+     * Java 方法名
+     */
+    private String javaMethod;
+    /**
+     * Java 方法的参数
+     *
+     * 实际格式为 Map<String, Object>
+     * 不使用 @TableField(typeHandler = FastjsonTypeHandler.class) 注解的原因是,数据库存储有长度限制,会进行裁剪,会导致 JSON 反序列化失败
+     * 其中,key 为参数名,value 为参数值
+     */
+    private String javaMethodArgs;
+    /**
+     * 开始时间
+     */
+    private LocalDateTime startTime;
+    /**
+     * 执行时长,单位:毫秒
+     */
+    private Integer duration;
+    /**
+     * 结果码
+     *
+     * 目前使用的 {@link CommonResult#getCode()} 属性
+     */
+    private Integer resultCode;
+    /**
+     * 结果提示
+     *
+     * 目前使用的 {@link CommonResult#getMsg()} 属性
+     */
+    private String resultMsg;
+    /**
+     * 结果数据
+     *
+     * 如果是对象,则使用 JSON 格式化
+     */
+    private String resultData;
 
 }

+ 6 - 16
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/logger/OperateLogV2Mapper.java

@@ -3,28 +3,18 @@ package cn.iocoder.yudao.module.system.dal.mysql.logger;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
-import cn.iocoder.yudao.module.system.controller.admin.logger.vo.operatelog.OperateLogPageReqVO;
+import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2PageReqDTO;
 import cn.iocoder.yudao.module.system.dal.dataobject.logger.OperateLogV2DO;
 import org.apache.ibatis.annotations.Mapper;
 
-import java.util.Collection;
-import java.util.List;
-
 @Mapper
 public interface OperateLogV2Mapper extends BaseMapperX<OperateLogV2DO> {
 
-    default PageResult<OperateLogV2DO> selectPage(OperateLogPageReqVO reqVO, Collection<Long> userIds) {
-        LambdaQueryWrapperX<OperateLogV2DO> query = new LambdaQueryWrapperX<OperateLogV2DO>()
-                .likeIfPresent(OperateLogV2DO::getModule, reqVO.getModule())
-                .inIfPresent(OperateLogV2DO::getUserId, userIds);
-        query.orderByDesc(OperateLogV2DO::getId); // 降序
-        return selectPage(reqVO, query);
-    }
-
-    default List<OperateLogV2DO> selectListByModuleAndBizId(String module, Long bizId) {
-        return selectList(new LambdaQueryWrapperX<OperateLogV2DO>()
-                .eq(OperateLogV2DO::getModule, module)
-                .eq(OperateLogV2DO::getBizId, bizId)
+    default PageResult<OperateLogV2DO> selectPage(OperateLogV2PageReqDTO pageReqVO) {
+        return selectPage(pageReqVO, new LambdaQueryWrapperX<OperateLogV2DO>()
+                .eqIfPresent(OperateLogV2DO::getType, pageReqVO.getBizType())
+                .eqIfPresent(OperateLogV2DO::getBizId, pageReqVO.getBizId())
+                .eqIfPresent(OperateLogV2DO::getUserId, pageReqVO.getUserId())
                 .orderByDesc(OperateLogV2DO::getCreateTime));
     }
 

+ 0 - 16
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/bizlog/config/YudaoOperateLogV2Configuration.java

@@ -1,16 +0,0 @@
-package cn.iocoder.yudao.module.system.framework.bizlog.config;
-
-import com.mzt.logapi.starter.annotation.EnableLogRecord;
-import org.springframework.context.annotation.Configuration;
-
-// TODO @puhui999:挪到 yudao-spring-boot-starter-biz-operatelog 下,搞个 cn.iocoder.yudao.framework.operatelogv2;跑通后,我们直接就删除老的实现了;
-/**
- * mzt-biz-log 配置类
- *
- * @author HUIHUI
- */
-@Configuration(proxyBeanMethods = false)
-@EnableLogRecord(tenant = "") // 貌似用不上 tenant 这玩意给个空好啦
-public class YudaoOperateLogV2Configuration {
-
-}

+ 0 - 1
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/bizlog/package-info.java

@@ -1 +0,0 @@
-package cn.iocoder.yudao.module.system.framework.bizlog;

+ 0 - 85
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/bizlog/service/ILogRecordServiceImpl.java

@@ -1,85 +0,0 @@
-package cn.iocoder.yudao.module.system.framework.bizlog.service;
-
-import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
-import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
-import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
-import cn.iocoder.yudao.module.system.service.logger.OperateLogService;
-import cn.iocoder.yudao.module.system.service.logger.bo.OperateLogV2CreateReqBO;
-import com.mzt.logapi.beans.LogRecord;
-import com.mzt.logapi.service.ILogRecordService;
-import jakarta.annotation.Resource;
-import jakarta.servlet.http.HttpServletRequest;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Service;
-
-import java.util.Collections;
-import java.util.List;
-
-// TODO @puhui999:这个应该搞到 operatelog 组件里哈;
-/**
- * 操作日志 ILogRecordService 实现类
- *
- * 基于 {@link OperateLogService} 实现,记录操作日志
- *
- * @author HUIHUI
- */
-@Slf4j
-@Service
-public class ILogRecordServiceImpl implements ILogRecordService {
-
-    @Resource
-    private OperateLogService operateLogService;
-
-    @Override
-    public void record(LogRecord logRecord) {
-        OperateLogV2CreateReqBO reqBO = new OperateLogV2CreateReqBO();
-        // 补全通用字段
-        reqBO.setTraceId(TracerUtils.getTraceId());
-        // 补充用户信息
-        fillUserFields(reqBO);
-        // 补全模块信息
-        fillModuleFields(reqBO, logRecord);
-        // 补全请求信息
-        fillRequestFields(reqBO);
-        // 异步记录日志
-        operateLogService.createOperateLogV2(reqBO);
-        log.info("操作日志 ===> {}", reqBO);
-    }
-
-    private static void fillUserFields(OperateLogV2CreateReqBO reqBO) {
-        reqBO.setUserId(WebFrameworkUtils.getLoginUserId());
-        reqBO.setUserType(WebFrameworkUtils.getLoginUserType());
-    }
-
-    public static void fillModuleFields(OperateLogV2CreateReqBO reqBO, LogRecord logRecord) {
-        reqBO.setModule(logRecord.getType()); // 大模块类型如 crm-客户
-        reqBO.setName(logRecord.getSubType());// 操作名称如 转移客户
-        reqBO.setBizId(Long.parseLong(logRecord.getBizNo())); // 操作模块业务编号
-        reqBO.setContent(logRecord.getAction());// 例如说,修改编号为 1 的用户信息,将性别从男改成女,将姓名从芋道改成源码。
-        reqBO.setExtra(logRecord.getExtra()); // 拓展字段,有些复杂的业务,需要记录一些字段 ( JSON 格式 ),例如说,记录订单编号,{ orderId: "1"}
-    }
-
-    private static void fillRequestFields(OperateLogV2CreateReqBO reqBO) {
-        // 获得 Request 对象
-        HttpServletRequest request = ServletUtils.getRequest();
-        if (request == null) {
-            return;
-        }
-        // 补全请求信息
-        reqBO.setRequestMethod(request.getMethod());
-        reqBO.setRequestUrl(request.getRequestURI());
-        reqBO.setUserIp(ServletUtils.getClientIP(request));
-        reqBO.setUserAgent(ServletUtils.getUserAgent(request));
-    }
-
-    @Override
-    public List<LogRecord> queryLog(String bizNo, String type) {
-        return Collections.emptyList();
-    }
-
-    @Override
-    public List<LogRecord> queryLogByBizNo(String bizNo, String type, String subType) {
-        return Collections.emptyList();
-    }
-
-}

+ 1 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/operatelog/package-info.java

@@ -0,0 +1 @@
+package cn.iocoder.yudao.module.system.framework.operatelog;

+ 1 - 1
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/bizlog/function/AdminUserParseFunction.java → yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/operatelog/parse/AdminUserParseFunction.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.system.framework.bizlog.function;
+package cn.iocoder.yudao.module.system.framework.operatelog.parse;
 
 import cn.hutool.core.util.ObjUtil;
 import cn.hutool.core.util.StrUtil;

+ 1 - 1
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/bizlog/function/AreaParseFunction.java → yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/operatelog/parse/AreaParseFunction.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.system.framework.bizlog.function;
+package cn.iocoder.yudao.module.system.framework.operatelog.parse;
 
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;

+ 7 - 10
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/logger/OperateLogService.java

@@ -2,12 +2,11 @@ package cn.iocoder.yudao.module.system.service.logger;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogCreateReqDTO;
+import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2CreateReqDTO;
+import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2PageReqDTO;
 import cn.iocoder.yudao.module.system.controller.admin.logger.vo.operatelog.OperateLogPageReqVO;
 import cn.iocoder.yudao.module.system.dal.dataobject.logger.OperateLogDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.logger.OperateLogV2DO;
-import cn.iocoder.yudao.module.system.service.logger.bo.OperateLogV2CreateReqBO;
-
-import java.util.List;
 
 /**
  * 操作日志 Service 接口
@@ -38,16 +37,14 @@ public interface OperateLogService {
      *
      * @param createReqBO 创建请求
      */
-    void createOperateLogV2(OperateLogV2CreateReqBO createReqBO);
+    void createOperateLogV2(OperateLogV2CreateReqDTO createReqBO);
 
-    // TODO @puhui999:module 改成 type
     /**
-     * 获取指定模块的指定数据的操作日志
+     * 获得操作日志分页列表
      *
-     * @param module 操作模块
-     * @param bizId  操作模块编号
-     * @return 操作日志
+     * @param pageReqVO 分页条件
+     * @return 操作日志分页列表
      */
-    List<OperateLogV2DO> getOperateLogByModuleAndBizId(String module, Long bizId);
+    PageResult<OperateLogV2DO> getOperateLogPage(OperateLogV2PageReqDTO pageReqVO);
 
 }

+ 8 - 5
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/logger/OperateLogServiceImpl.java

@@ -6,13 +6,14 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.framework.common.util.string.StrUtils;
 import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogCreateReqDTO;
+import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2CreateReqDTO;
+import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2PageReqDTO;
 import cn.iocoder.yudao.module.system.controller.admin.logger.vo.operatelog.OperateLogPageReqVO;
 import cn.iocoder.yudao.module.system.dal.dataobject.logger.OperateLogDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.logger.OperateLogV2DO;
 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
 import cn.iocoder.yudao.module.system.dal.mysql.logger.OperateLogMapper;
 import cn.iocoder.yudao.module.system.dal.mysql.logger.OperateLogV2Mapper;
-import cn.iocoder.yudao.module.system.service.logger.bo.OperateLogV2CreateReqBO;
 import cn.iocoder.yudao.module.system.service.user.AdminUserService;
 import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
@@ -20,7 +21,6 @@ import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
 import java.util.Collection;
-import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
 import static cn.iocoder.yudao.module.system.dal.dataobject.logger.OperateLogDO.JAVA_METHOD_ARGS_MAX_LENGTH;
@@ -69,14 +69,17 @@ public class OperateLogServiceImpl implements OperateLogService {
     // ======================= LOG V2 =======================
 
     @Override
-    public void createOperateLogV2(OperateLogV2CreateReqBO createReqBO) {
+    public void createOperateLogV2(OperateLogV2CreateReqDTO createReqBO) {
         OperateLogV2DO log = BeanUtils.toBean(createReqBO, OperateLogV2DO.class);
+        log.setJavaMethodArgs(StrUtils.maxLength(log.getJavaMethodArgs(), JAVA_METHOD_ARGS_MAX_LENGTH));
+        log.setResultData(StrUtils.maxLength(log.getResultData(), RESULT_MAX_LENGTH));
         operateLogV2Mapper.insert(log);
     }
 
+
     @Override
-    public List<OperateLogV2DO> getOperateLogByModuleAndBizId(String module, Long bizId) {
-        return operateLogV2Mapper.selectListByModuleAndBizId(module, bizId);
+    public PageResult<OperateLogV2DO> getOperateLogPage(OperateLogV2PageReqDTO pageReqVO) {
+        return operateLogV2Mapper.selectPage(pageReqVO);
     }
 
 }

+ 1 - 0
yudao-server/src/main/resources/application-local.yaml

@@ -188,6 +188,7 @@ logging:
     cn.iocoder.yudao.module.trade.dal.mysql: debug
     cn.iocoder.yudao.module.promotion.dal.mysql: debug
     cn.iocoder.yudao.module.statistics.dal.mysql: debug
+    cn.iocoder.yudao.module.crm.dal.mysql: debug
 
 debug: false
 

+ 11 - 1
yudao-server/src/main/resources/application.yaml

@@ -79,7 +79,17 @@ mybatis-plus:
     password: XDV71a+xqStEA3WH # 加解密的秘钥,可使用 https://www.imaegoo.com/2020/aes-key-generator/ 网站生成
 
 mybatis-plus-join:
-  banner: false # 关闭控制台的 Banner 打印
+  #是否打印 mybatis plus join banner 默认true
+  banner: false
+  #全局启用副表逻辑删除(默认true) 关闭后关联查询不会加副表逻辑删除
+  sub-table-logic: true
+  #拦截器MappedStatement缓存(默认true)
+  ms-cache: true
+  #表别名(默认 t)
+  table-alias: t
+  #副表逻辑删除条件的位置,支持where、on
+  #默认ON (1.4.7.2及之前版本默认为where)
+  logic-del-type: on
 
 # Spring Data Redis 配置
 spring: