Commit 82a25474f60be3a89186e53e4ee46ccd4b3e016d

Authored by YunaiV
2 parents 7542b6a7 525d41ba

Merge remote-tracking branch 'origin/develop' into develop

Showing 70 changed files with 1135 additions and 408 deletions
yudao-dependencies/pom.xml
... ... @@ -26,7 +26,7 @@
26 26 <mybatis-plus.version>3.5.4.1</mybatis-plus.version>
27 27 <mybatis-plus-generator.version>3.5.4.1</mybatis-plus-generator.version>
28 28 <dynamic-datasource.version>4.2.0</dynamic-datasource.version>
29   - <mybatis-plus-join.version>1.4.7.2</mybatis-plus-join.version>
  29 + <mybatis-plus-join.version>1.4.8.1</mybatis-plus-join.version>
30 30 <redisson.version>3.25.0</redisson.version>
31 31 <dm8.jdbc.version>8.1.3.62</dm8.jdbc.version>
32 32 <!-- 消息队列 -->
... ...
yudao-framework/yudao-spring-boot-starter-biz-operatelog/pom.xml
... ... @@ -46,6 +46,13 @@
46 46 <groupId>com.google.guava</groupId>
47 47 <artifactId>guava</artifactId>
48 48 </dependency>
  49 + <!-- Springboot-注解-通用操作日志组件 -->
  50 + <!-- 此组件解决的问题是: 「谁」在「什么时间」对「什么」做了「什么事」 -->
  51 + <dependency>
  52 + <groupId>io.github.mouzt</groupId>
  53 + <artifactId>bizlog-sdk</artifactId>
  54 + </dependency>
  55 +
49 56 </dependencies>
50 57  
51 58 </project>
... ...
yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelog/config/YudaoOperateLogAutoConfiguration.java
1 1 package cn.iocoder.yudao.framework.operatelog.config;
2 2  
3   -import cn.iocoder.yudao.framework.operatelog.core.aop.OperateLogAspect;
4   -import cn.iocoder.yudao.framework.operatelog.core.service.OperateLogFrameworkService;
5   -import cn.iocoder.yudao.framework.operatelog.core.service.OperateLogFrameworkServiceImpl;
6   -import cn.iocoder.yudao.module.system.api.logger.OperateLogApi;
7 3 import org.springframework.boot.autoconfigure.AutoConfiguration;
8   -import org.springframework.context.annotation.Bean;
9 4  
10 5 @AutoConfiguration
11 6 public class YudaoOperateLogAutoConfiguration {
12 7  
13   - @Bean
14   - public OperateLogAspect operateLogAspect() {
15   - return new OperateLogAspect();
16   - }
17   -
18   - @Bean
19   - public OperateLogFrameworkService operateLogFrameworkService(OperateLogApi operateLogApi) {
20   - return new OperateLogFrameworkServiceImpl(operateLogApi);
21   - }
  8 + //@Bean
  9 + //public OperateLogAspect operateLogAspect() {
  10 + // return new OperateLogAspect();
  11 + //}
  12 + //
  13 + //@Bean
  14 + //public OperateLogFrameworkService operateLogFrameworkService(OperateLogApi operateLogApi) {
  15 + // return new OperateLogFrameworkServiceImpl(operateLogApi);
  16 + //}
22 17  
23 18 }
... ...
yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelogv2/config/YudaoOperateLogV2Configuration.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.operatelogv2.config;
  2 +
  3 +import cn.iocoder.yudao.framework.operatelogv2.core.aop.OperateLogV2Aspect;
  4 +import cn.iocoder.yudao.framework.operatelogv2.core.service.ILogRecordServiceImpl;
  5 +import com.mzt.logapi.service.ILogRecordService;
  6 +import com.mzt.logapi.starter.annotation.EnableLogRecord;
  7 +import lombok.extern.slf4j.Slf4j;
  8 +import org.springframework.boot.autoconfigure.AutoConfiguration;
  9 +import org.springframework.context.annotation.Bean;
  10 +import org.springframework.context.annotation.Primary;
  11 +
  12 +/**
  13 + * mzt-biz-log 配置类
  14 + *
  15 + * @author HUIHUI
  16 + */
  17 +@EnableLogRecord(tenant = "") // 貌似用不上 tenant 这玩意给个空好啦
  18 +@AutoConfiguration
  19 +@Slf4j
  20 +public class YudaoOperateLogV2Configuration {
  21 +
  22 + @Bean
  23 + @Primary
  24 + public ILogRecordService iLogRecordServiceImpl() {
  25 + return new ILogRecordServiceImpl();
  26 + }
  27 +
  28 + @Bean
  29 + public OperateLogV2Aspect operateLogV2Aspect() {
  30 + return new OperateLogV2Aspect();
  31 + }
  32 +
  33 +}
... ...
yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelogv2/core/aop/OperateLogV2Aspect.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.operatelogv2.core.aop;
  2 +
  3 +import cn.hutool.core.date.LocalDateTimeUtil;
  4 +import cn.hutool.core.exceptions.ExceptionUtil;
  5 +import cn.hutool.core.util.ArrayUtil;
  6 +import cn.hutool.core.util.ObjUtil;
  7 +import cn.hutool.core.util.StrUtil;
  8 +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
  9 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  10 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
  11 +import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
  12 +import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
  13 +import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
  14 +import cn.iocoder.yudao.module.system.api.logger.OperateLogApi;
  15 +import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2CreateReqDTO;
  16 +import com.google.common.collect.Maps;
  17 +import com.mzt.logapi.beans.LogRecord;
  18 +import io.swagger.v3.oas.annotations.Operation;
  19 +import jakarta.annotation.Resource;
  20 +import jakarta.servlet.http.HttpServletRequest;
  21 +import jakarta.servlet.http.HttpServletResponse;
  22 +import lombok.extern.slf4j.Slf4j;
  23 +import org.aspectj.lang.ProceedingJoinPoint;
  24 +import org.aspectj.lang.annotation.Around;
  25 +import org.aspectj.lang.annotation.Aspect;
  26 +import org.aspectj.lang.reflect.MethodSignature;
  27 +import org.springframework.core.annotation.AnnotationUtils;
  28 +import org.springframework.validation.BindingResult;
  29 +import org.springframework.web.bind.annotation.RequestMapping;
  30 +import org.springframework.web.bind.annotation.RequestMethod;
  31 +import org.springframework.web.multipart.MultipartFile;
  32 +
  33 +import java.lang.annotation.Annotation;
  34 +import java.lang.reflect.Array;
  35 +import java.time.LocalDateTime;
  36 +import java.util.Arrays;
  37 +import java.util.Collection;
  38 +import java.util.HashMap;
  39 +import java.util.Map;
  40 +import java.util.function.Predicate;
  41 +import java.util.stream.IntStream;
  42 +
  43 +import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
  44 +import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.SUCCESS;
  45 +import static cn.iocoder.yudao.framework.operatelogv2.core.enums.OperateLogV2Constants.*;
  46 +
  47 +/**
  48 + * 拦截使用 @Operation 注解, 获取操作类型、开始时间、持续时间、方法相关信息、执行结果等信息
  49 + * 对 mzt-biz-log 日志信息进行增强
  50 + *
  51 + * @author HUIHUI
  52 + */
  53 +@Aspect
  54 +@Slf4j
  55 +public class OperateLogV2Aspect {
  56 +
  57 + /**
  58 + * 用于记录操作内容的上下文
  59 + *
  60 + * @see OperateLogV2CreateReqDTO#getContent()
  61 + */
  62 + private static final ThreadLocal<LogRecord> CONTENT = new ThreadLocal<>();
  63 + /**
  64 + * 用于记录拓展字段的上下文
  65 + *
  66 + * @see OperateLogV2CreateReqDTO#getExtra()
  67 + */
  68 + private static final ThreadLocal<Map<String, Object>> EXTRA = new ThreadLocal<>();
  69 +
  70 + @Resource
  71 + private OperateLogApi operateLogApi;
  72 +
  73 + @Around("@annotation(operation)")
  74 + public Object around(ProceedingJoinPoint joinPoint, Operation operation) throws Throwable {
  75 + RequestMethod requestMethod = obtainFirstMatchRequestMethod(obtainRequestMethod(joinPoint));
  76 + if (requestMethod == RequestMethod.GET) { // 跳过 get 方法
  77 + return joinPoint.proceed();
  78 + }
  79 +
  80 + // 目前,只有管理员,才记录操作日志!所以非管理员,直接调用,不进行记录
  81 + Integer userType = WebFrameworkUtils.getLoginUserType();
  82 + if (ObjUtil.notEqual(userType, UserTypeEnum.ADMIN.getValue())) {
  83 + return joinPoint.proceed();
  84 + }
  85 +
  86 + // 记录开始时间
  87 + LocalDateTime startTime = LocalDateTime.now();
  88 + try {
  89 + // 执行原有方法
  90 + Object result = joinPoint.proceed();
  91 + // 记录正常执行时的操作日志
  92 + this.log(joinPoint, operation, startTime, result, null);
  93 + return result;
  94 + } catch (Throwable exception) {
  95 + this.log(joinPoint, operation, startTime, null, exception);
  96 + throw exception;
  97 + } finally {
  98 + clearThreadLocal();
  99 + }
  100 + }
  101 +
  102 + public static void setContent(LogRecord content) {
  103 + CONTENT.set(content);
  104 + }
  105 +
  106 + public static void addExtra(String key, Object value) {
  107 + if (EXTRA.get() == null) {
  108 + EXTRA.set(new HashMap<>());
  109 + }
  110 + EXTRA.get().put(key, value);
  111 + }
  112 +
  113 + public static void addExtra(Map<String, Object> extra) {
  114 + if (EXTRA.get() == null) {
  115 + EXTRA.set(new HashMap<>());
  116 + }
  117 + EXTRA.get().putAll(extra);
  118 + }
  119 +
  120 + private static void clearThreadLocal() {
  121 + CONTENT.remove();
  122 + EXTRA.remove();
  123 + }
  124 +
  125 + private void log(ProceedingJoinPoint joinPoint, Operation operation,
  126 + LocalDateTime startTime, Object result, Throwable exception) {
  127 + try {
  128 + // 判断不记录的情况(默认没有值就是记录)
  129 + if (EXTRA.get() != null && EXTRA.get().get(ENABLE) != null) {
  130 + return;
  131 + }
  132 + if (CONTENT.get() == null) { // 没有值说明没有日志需要记录
  133 + return;
  134 + }
  135 +
  136 + // 真正记录操作日志
  137 + this.log0(joinPoint, operation, startTime, result, exception);
  138 + } catch (Throwable ex) {
  139 + log.error("[log][记录操作日志时,发生异常,其中参数是 joinPoint({}) apiOperation({}) result({}) exception({}) ]",
  140 + joinPoint, operation, result, exception, ex);
  141 + }
  142 + }
  143 +
  144 + private void log0(ProceedingJoinPoint joinPoint, Operation operation,
  145 + LocalDateTime startTime, Object result, Throwable exception) {
  146 + OperateLogV2CreateReqDTO reqDTO = new OperateLogV2CreateReqDTO();
  147 + // 补全通用字段
  148 + reqDTO.setTraceId(TracerUtils.getTraceId());
  149 + reqDTO.setStartTime(startTime);
  150 + // 补充用户信息
  151 + fillUserFields(reqDTO);
  152 + // 补全模块信息
  153 + fillModuleFields(reqDTO, operation);
  154 + // 补全请求信息
  155 + fillRequestFields(reqDTO);
  156 + // 补全方法信息
  157 + fillMethodFields(reqDTO, joinPoint, startTime, result, exception);
  158 +
  159 + // 异步记录日志
  160 + operateLogApi.createOperateLogV2(reqDTO);
  161 + }
  162 +
  163 + private static void fillUserFields(OperateLogV2CreateReqDTO reqDTO) {
  164 + reqDTO.setUserId(WebFrameworkUtils.getLoginUserId());
  165 + reqDTO.setUserType(WebFrameworkUtils.getLoginUserType());
  166 + }
  167 +
  168 + private static void fillModuleFields(OperateLogV2CreateReqDTO reqDTO, Operation operation) {
  169 + LogRecord logRecord = CONTENT.get();
  170 + reqDTO.setBizId(Long.parseLong(logRecord.getBizNo())); // 操作模块业务编号
  171 + reqDTO.setContent(logRecord.getAction());// 例如说,修改编号为 1 的用户信息,将性别从男改成女,将姓名从芋道改成源码。
  172 +
  173 + // type 属性
  174 + reqDTO.setType(logRecord.getType()); // 大模块类型如 crm 客户
  175 + // subType 属性
  176 + if (logRecord.getSubType() != null) {
  177 + reqDTO.setSubType(logRecord.getSubType());// 操作名称如 转移客户
  178 + }
  179 + if (StrUtil.isEmpty(reqDTO.getSubType()) && operation != null) {
  180 + reqDTO.setSubType(operation.summary());
  181 + }
  182 +
  183 + // 拓展字段,有些复杂的业务,需要记录一些字段 ( JSON 格式 ),例如说,记录订单编号,{ orderId: "1"}
  184 + Map<String, Object> objectMap = EXTRA.get();
  185 + if (objectMap != null) {
  186 + Object object = objectMap.get(EXTRA_KEY);
  187 + if (object instanceof Map<?, ?> extraMap) {
  188 + if (extraMap.keySet().stream().allMatch(String.class::isInstance)) {
  189 + @SuppressWarnings("unchecked")
  190 + Map<String, Object> extra = (Map<String, Object>) extraMap;
  191 + reqDTO.setExtra(extra);
  192 + return;
  193 + }
  194 + }
  195 + // 激进一点不是 map 直接当 value 处理
  196 + Map<String, Object> extra = Maps.newHashMapWithExpectedSize(1);
  197 + extra.put(EXTRA_KEY, object);
  198 + reqDTO.setExtra(extra);
  199 + }
  200 +
  201 + }
  202 +
  203 + private static void fillRequestFields(OperateLogV2CreateReqDTO reqDTO) {
  204 + // 获得 Request 对象
  205 + HttpServletRequest request = ServletUtils.getRequest();
  206 + if (request == null) {
  207 + return;
  208 + }
  209 + // 补全请求信息
  210 + reqDTO.setRequestMethod(request.getMethod());
  211 + reqDTO.setRequestUrl(request.getRequestURI());
  212 + reqDTO.setUserIp(ServletUtils.getClientIP(request));
  213 + reqDTO.setUserAgent(ServletUtils.getUserAgent(request));
  214 + }
  215 +
  216 + private static void fillMethodFields(OperateLogV2CreateReqDTO reqDTO, ProceedingJoinPoint joinPoint, LocalDateTime startTime,
  217 + Object result, Throwable exception) {
  218 + MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
  219 + reqDTO.setJavaMethod(methodSignature.toString());
  220 + if (EXTRA.get().get(IS_LOG_ARGS) == null) {
  221 + reqDTO.setJavaMethodArgs(obtainMethodArgs(joinPoint));
  222 + }
  223 + if (EXTRA.get().get(IS_LOG_RESULT_DATA) == null) {
  224 + reqDTO.setResultData(obtainResultData(result));
  225 + }
  226 + reqDTO.setDuration((int) (LocalDateTimeUtil.between(startTime, LocalDateTime.now()).toMillis()));
  227 + // (正常)处理 resultCode 和 resultMsg 字段
  228 + if (result instanceof CommonResult) {
  229 + CommonResult<?> commonResult = (CommonResult<?>) result;
  230 + reqDTO.setResultCode(commonResult.getCode());
  231 + reqDTO.setResultMsg(commonResult.getMsg());
  232 + } else {
  233 + reqDTO.setResultCode(SUCCESS.getCode());
  234 + }
  235 + // (异常)处理 resultCode 和 resultMsg 字段
  236 + if (exception != null) {
  237 + reqDTO.setResultCode(INTERNAL_SERVER_ERROR.getCode());
  238 + reqDTO.setResultMsg(ExceptionUtil.getRootCauseMessage(exception));
  239 + }
  240 + }
  241 +
  242 + @SuppressWarnings("SameParameterValue")
  243 + private static <T extends Annotation> T getClassAnnotation(ProceedingJoinPoint joinPoint, Class<T> annotationClass) {
  244 + return ((MethodSignature) joinPoint.getSignature()).getMethod().getDeclaringClass().getAnnotation(annotationClass);
  245 + }
  246 +
  247 + private static String obtainMethodArgs(ProceedingJoinPoint joinPoint) {
  248 + // TODO 提升:参数脱敏和忽略
  249 + MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
  250 + String[] argNames = methodSignature.getParameterNames();
  251 + Object[] argValues = joinPoint.getArgs();
  252 + // 拼接参数
  253 + Map<String, Object> args = Maps.newHashMapWithExpectedSize(argValues.length);
  254 + for (int i = 0; i < argNames.length; i++) {
  255 + String argName = argNames[i];
  256 + Object argValue = argValues[i];
  257 + // 被忽略时,标记为 ignore 字符串,避免和 null 混在一起
  258 + args.put(argName, !isIgnoreArgs(argValue) ? argValue : "[ignore]");
  259 + }
  260 + return JsonUtils.toJsonString(args);
  261 + }
  262 +
  263 + private static String obtainResultData(Object result) {
  264 + // TODO 提升:结果脱敏和忽略
  265 + if (result instanceof CommonResult) {
  266 + result = ((CommonResult<?>) result).getData();
  267 + }
  268 + return JsonUtils.toJsonString(result);
  269 + }
  270 +
  271 + private static boolean isIgnoreArgs(Object object) {
  272 + Class<?> clazz = object.getClass();
  273 + // 处理数组的情况
  274 + if (clazz.isArray()) {
  275 + return IntStream.range(0, Array.getLength(object))
  276 + .anyMatch(index -> isIgnoreArgs(Array.get(object, index)));
  277 + }
  278 + // 递归,处理数组、Collection、Map 的情况
  279 + if (Collection.class.isAssignableFrom(clazz)) {
  280 + return ((Collection<?>) object).stream()
  281 + .anyMatch((Predicate<Object>) OperateLogV2Aspect::isIgnoreArgs);
  282 + }
  283 + if (Map.class.isAssignableFrom(clazz)) {
  284 + return isIgnoreArgs(((Map<?, ?>) object).values());
  285 + }
  286 + // obj
  287 + return object instanceof MultipartFile
  288 + || object instanceof HttpServletRequest
  289 + || object instanceof HttpServletResponse
  290 + || object instanceof BindingResult;
  291 + }
  292 +
  293 + private static RequestMethod obtainFirstMatchRequestMethod(RequestMethod[] requestMethods) {
  294 + if (ArrayUtil.isEmpty(requestMethods)) {
  295 + return null;
  296 + }
  297 + // 优先,匹配最优的 POST、PUT、DELETE
  298 + RequestMethod result = obtainFirstLogRequestMethod(requestMethods);
  299 + if (result != null) {
  300 + return result;
  301 + }
  302 + // 然后,匹配次优的 GET
  303 + result = Arrays.stream(requestMethods).filter(requestMethod -> requestMethod == RequestMethod.GET)
  304 + .findFirst().orElse(null);
  305 + if (result != null) {
  306 + return result;
  307 + }
  308 + // 兜底,获得第一个
  309 + return requestMethods[0];
  310 + }
  311 +
  312 + private static RequestMethod[] obtainRequestMethod(ProceedingJoinPoint joinPoint) {
  313 + RequestMapping requestMapping = AnnotationUtils.getAnnotation( // 使用 Spring 的工具类,可以处理 @RequestMapping 别名注解
  314 + ((MethodSignature) joinPoint.getSignature()).getMethod(), RequestMapping.class);
  315 + return requestMapping != null ? requestMapping.method() : new RequestMethod[]{};
  316 + }
  317 +
  318 + private static RequestMethod obtainFirstLogRequestMethod(RequestMethod[] requestMethods) {
  319 + if (ArrayUtil.isEmpty(requestMethods)) {
  320 + return null;
  321 + }
  322 + return Arrays.stream(requestMethods).filter(requestMethod ->
  323 + requestMethod == RequestMethod.POST
  324 + || requestMethod == RequestMethod.PUT
  325 + || requestMethod == RequestMethod.DELETE)
  326 + .findFirst().orElse(null);
  327 + }
  328 +
  329 +}
... ...
yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelogv2/core/enums/OperateLogV2Constants.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.operatelogv2.core.enums;
  2 +
  3 +/**
  4 + * 操作日志常量接口
  5 + *
  6 + * @author HUIHUI
  7 + */
  8 +public interface OperateLogV2Constants {
  9 +
  10 + // ========== 开关字段-如果没有值默认就是记录 ==========
  11 +
  12 + /**
  13 + * 是否记录日志
  14 + */
  15 + String ENABLE = "enable";
  16 +
  17 + /**
  18 + * 是否记录方法参数
  19 + */
  20 + String IS_LOG_ARGS = "isLogArgs";
  21 +
  22 + /**
  23 + * 是否记录方法结果的数据
  24 + */
  25 + String IS_LOG_RESULT_DATA = "isLogResultData";
  26 +
  27 + // ========== 扩展 ==========
  28 +
  29 + /**
  30 + * 扩展信息
  31 + */
  32 + String EXTRA_KEY = "extra";
  33 +
  34 +}
... ...
yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelogv2/core/package-info.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.operatelogv2.core;
0 2 \ No newline at end of file
... ...
yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelogv2/core/service/ILogRecordServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.operatelogv2.core.service;
  2 +
  3 +import cn.iocoder.yudao.framework.operatelogv2.core.aop.OperateLogV2Aspect;
  4 +import com.mzt.logapi.beans.LogRecord;
  5 +import com.mzt.logapi.context.LogRecordContext;
  6 +import com.mzt.logapi.service.ILogRecordService;
  7 +import lombok.extern.slf4j.Slf4j;
  8 +
  9 +import java.util.Collections;
  10 +import java.util.List;
  11 +
  12 +/**
  13 + * 操作日志 ILogRecordService 实现类
  14 + *
  15 + * 基于 {@link OperateLogV2Aspect} 实现,记录操作日志
  16 + *
  17 + * @author HUIHUI
  18 + */
  19 +@Slf4j
  20 +public class ILogRecordServiceImpl implements ILogRecordService {
  21 +
  22 + @Override
  23 + public void record(LogRecord logRecord) {
  24 + OperateLogV2Aspect.setContent(logRecord); // 操作日志
  25 + OperateLogV2Aspect.addExtra(LogRecordContext.getVariables()); // 扩展信息
  26 + }
  27 +
  28 + @Override
  29 + public List<LogRecord> queryLog(String bizNo, String type) {
  30 + return Collections.emptyList();
  31 + }
  32 +
  33 + @Override
  34 + public List<LogRecord> queryLogByBizNo(String bizNo, String type, String subType) {
  35 + return Collections.emptyList();
  36 + }
  37 +
  38 +}
... ...
yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelogv2/core/vo/OperateLogV2PageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.operatelogv2.core.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +
  7 +@Schema(description = "管理后台 - 操作日志分页 Request VO")
  8 +@Data
  9 +public class OperateLogV2PageReqVO extends PageParam {
  10 +
  11 + @Schema(description = "模块数据编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  12 + private Long bizId;
  13 +
  14 + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  15 + private Long userId;
  16 +
  17 + @Schema(description = "模块类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  18 + private String bizType;
  19 +
  20 +}
... ...
yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelogv2/package-info.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.operatelogv2;
0 2 \ No newline at end of file
... ...
yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
1   -cn.iocoder.yudao.framework.operatelog.config.YudaoOperateLogAutoConfiguration
2 1 \ No newline at end of file
  2 +cn.iocoder.yudao.framework.operatelog.config.YudaoOperateLogAutoConfiguration
  3 +cn.iocoder.yudao.framework.operatelogv2.config.YudaoOperateLogV2Configuration
3 4 \ No newline at end of file
... ...
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 {
22 22 //======================= 客户转移操作日志 =======================
23 23  
24 24 String TRANSFER_CUSTOMER_LOG_SUCCESS = "把客户【{{#crmCustomer.name}}】的负责人从【{getAdminUserById{#crmCustomer.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】";
25   - String TRANSFER_CUSTOMER_LOG_FAIL = ""; // TODO @puhui999:这个可以删除哈,一般不搞失败的日志
26 25  
27 26 }
... ...
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/permission/CrmPermissionRoleCodeEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.enums.permission;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Getter;
  5 +
  6 +/**
  7 + * Crm 数据权限角色枚举
  8 + *
  9 + * @author HUIHUI
  10 + */
  11 +@Getter
  12 +@AllArgsConstructor
  13 +public enum CrmPermissionRoleCodeEnum {
  14 +
  15 + CRM_ADMIN("crm_admin", "CRM 管理员");
  16 +
  17 + /**
  18 + * 角色标识
  19 + */
  20 + private String code;
  21 + /**
  22 + * 角色名称
  23 + */
  24 + private String name;
  25 +
  26 +}
  27 +
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.http
1 1 ### 请求 /transfer
2 2 PUT {{baseUrl}}/crm/customer/transfer
3   -Content-Type: application/json
  3 +Content-Type: application/-id: {{adminTenentId}}json
4 4 Authorization: Bearer {{token}}
5   -tenant-id: {{adminTenentId}}
  5 +tenant
6 6  
7 7 {
8 8 "id": 10,
... ...
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;
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
6 7 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
7 8 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
  9 +import cn.iocoder.yudao.framework.operatelogv2.core.vo.OperateLogV2PageReqVO;
8 10 import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.*;
9 11 import cn.iocoder.yudao.module.crm.convert.customer.CrmCustomerConvert;
10 12 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
... ... @@ -12,6 +14,7 @@ import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
12 14 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
13 15 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
14 16 import cn.iocoder.yudao.module.system.api.logger.OperateLogApi;
  17 +import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2PageReqDTO;
15 18 import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2RespDTO;
16 19 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
17 20 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
... ... @@ -64,7 +67,7 @@ public class CrmCustomerController {
64 67 }
65 68  
66 69 @PutMapping("/update")
67   - //@Operation(summary = "更新客户")
  70 + @Operation(summary = "更新客户")
68 71 @PreAuthorize("@ss.hasPermission('crm:customer:update')")
69 72 public CommonResult<Boolean> updateCustomer(@Valid @RequestBody CrmCustomerUpdateReqVO updateReqVO) {
70 73 customerService.updateCustomer(updateReqVO);
... ... @@ -128,30 +131,21 @@ public class CrmCustomerController {
128 131 }
129 132  
130 133 @PutMapping("/transfer")
131   - //@Operation(summary = "客户转移")
  134 + @Operation(summary = "客户转移")
132 135 @PreAuthorize("@ss.hasPermission('crm:customer:update')")
133 136 public CommonResult<Boolean> transfer(@Valid @RequestBody CrmCustomerTransferReqVO reqVO) {
134 137 customerService.transferCustomer(reqVO, getLoginUserId());
135 138 return success(true);
136 139 }
137 140  
138   - // TODO @puhui999:operate-log-list 或者 operate-log-page 如果分页
139   - @GetMapping("/operate-log")
  141 + @GetMapping("/operate-log-page")
140 142 @Operation(summary = "获得客户操作日志")
141 143 @Parameter(name = "id", description = "编号", required = true, example = "1024")
142 144 @PreAuthorize("@ss.hasPermission('crm:customer:query')")
143   - // TODO @puhui999:最好有读权限;方法名改成 getCustomerOperateLog
144   - public CommonResult<List<OperateLogV2RespDTO>> getOperateLog(@RequestParam("id") Long id) {
145   - // 1. 获取客户
146   - // TODO @puhui999:这个校验可以去掉哈;
147   - CrmCustomerDO customer = customerService.getCustomer(id);
148   - if (customer == null) {
149   - return success(null);
150   - }
151   -
152   - // 2. 获取操作日志
153   - // TODO @puhui999:操作日志,返回可能要分页哈;
154   - return success(operateLogApi.getOperateLogByModuleAndBizId(CRM_CUSTOMER, id));
  145 + public CommonResult<PageResult<OperateLogV2RespDTO>> getCustomerOperateLog(OperateLogV2PageReqVO reqVO) {
  146 + reqVO.setPageSize(PAGE_SIZE_NONE); // 不分页
  147 + reqVO.setBizType(CRM_CUSTOMER);
  148 + return success(operateLogApi.getOperateLogPage(BeanUtils.toBean(reqVO, OperateLogV2PageReqDTO.class)));
155 149 }
156 150  
157 151 // TODO @Joey:单独建一个属于自己业务的 ReqVO;因为前端如果模拟请求,是不是可以更新其它字段了;
... ...
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
8 8 import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionUpdateReqVO;
9 9 import cn.iocoder.yudao.module.crm.convert.permission.CrmPermissionConvert;
10 10 import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
11   -import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission;
12 11 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
  12 +import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
13 13 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
14 14 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
15 15 import cn.iocoder.yudao.module.system.api.dept.PostApi;
... ... @@ -21,12 +21,12 @@ import io.swagger.v3.oas.annotations.Operation;
21 21 import io.swagger.v3.oas.annotations.Parameter;
22 22 import io.swagger.v3.oas.annotations.Parameters;
23 23 import io.swagger.v3.oas.annotations.tags.Tag;
  24 +import jakarta.annotation.Resource;
  25 +import jakarta.validation.Valid;
24 26 import org.springframework.security.access.prepost.PreAuthorize;
25 27 import org.springframework.validation.annotation.Validated;
26 28 import org.springframework.web.bind.annotation.*;
27 29  
28   -import jakarta.annotation.Resource;
29   -import jakarta.validation.Valid;
30 30 import java.util.*;
31 31  
32 32 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
... ...
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&lt;CrmBusinessDO&gt; {
38 38 default PageResult<CrmBusinessDO> selectPage(CrmBusinessPageReqVO pageReqVO, Long userId) {
39 39 MPJLambdaWrapperX<CrmBusinessDO> query = new MPJLambdaWrapperX<>();
40 40 // 拼接数据权限的查询条件
41   - boolean condition = CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_BUSINESS.getType(),
  41 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_BUSINESS.getType(),
42 42 CrmBusinessDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE);
43   - if (!condition) {
44   - return PageResult.empty();
45   - }
46 43 // 拼接自身的查询条件
47 44 query.selectAll(CrmBusinessDO.class)
48 45 .likeIfPresent(CrmBusinessDO::getName, pageReqVO.getName())
... ...
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&lt;CrmClueDO&gt; {
30 30 default PageResult<CrmClueDO> selectPage(CrmCluePageReqVO pageReqVO, Long userId) {
31 31 MPJLambdaWrapperX<CrmClueDO> query = new MPJLambdaWrapperX<>();
32 32 // 拼接数据权限的查询条件
33   - boolean condition = CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_LEADS.getType(),
  33 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_LEADS.getType(),
34 34 CrmClueDO::getId, userId, pageReqVO.getSceneType(), pageReqVO.getPool());
35   - if (!condition) {
36   - return PageResult.empty();
37   - }
38 35 // 拼接自身的查询条件
39 36 query.selectAll(CrmClueDO.class)
40 37 .likeIfPresent(CrmClueDO::getName, pageReqVO.getName())
... ...
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&lt;CrmContactDO&gt; {
43 43 default PageResult<CrmContactDO> selectPage(CrmContactPageReqVO pageReqVO, Long userId) {
44 44 MPJLambdaWrapperX<CrmContactDO> query = new MPJLambdaWrapperX<>();
45 45 // 拼接数据权限的查询条件
46   - boolean condition = CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CONTACT.getType(),
  46 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CONTACT.getType(),
47 47 CrmContactDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE);
48   - if (!condition) {
49   - return PageResult.empty();
50   - }
51 48 // 拼接自身的查询条件
52 49 query.selectAll(CrmContactDO.class)
53 50 .likeIfPresent(CrmContactDO::getName, pageReqVO.getName())
... ...
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&lt;CrmContractDO&gt; {
41 41 default PageResult<CrmContractDO> selectPage(CrmContractPageReqVO pageReqVO, Long userId) {
42 42 MPJLambdaWrapperX<CrmContractDO> mpjLambdaWrapperX = new MPJLambdaWrapperX<>();
43 43 // 拼接数据权限的查询条件
44   - boolean condition = CrmQueryWrapperUtils.appendPermissionCondition(mpjLambdaWrapperX, CrmBizTypeEnum.CRM_CONTACT.getType(),
  44 + CrmQueryWrapperUtils.appendPermissionCondition(mpjLambdaWrapperX, CrmBizTypeEnum.CRM_CONTACT.getType(),
45 45 CrmContractDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE);
46   - if (!condition) {
47   - return PageResult.empty();
48   - }
49 46 // 拼接自身的查询条件
50 47 mpjLambdaWrapperX.selectAll(CrmContractDO.class)
51 48 .likeIfPresent(CrmContractDO::getNo, pageReqVO.getNo())
... ...
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&lt;CrmCustomerDO&gt; {
30 30 default PageResult<CrmCustomerDO> selectPage(CrmCustomerPageReqVO pageReqVO, Long userId) {
31 31 MPJLambdaWrapperX<CrmCustomerDO> query = new MPJLambdaWrapperX<>();
32 32 // 拼接数据权限的查询条件
33   - boolean condition = CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CUSTOMER.getType(),
  33 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CUSTOMER.getType(),
34 34 CrmCustomerDO::getId, userId, pageReqVO.getSceneType(), pageReqVO.getPool());
35   - if (!condition) {
36   - return PageResult.empty();
37   - }
38 35 // 拼接自身的查询条件
39 36 query.selectAll(CrmCustomerDO.class)
40 37 .likeIfPresent(CrmCustomerDO::getName, pageReqVO.getName())
... ...
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&lt;CrmReceivableDO&gt; {
39 39 default PageResult<CrmReceivableDO> selectPage(CrmReceivablePageReqVO pageReqVO, Long userId) {
40 40 MPJLambdaWrapperX<CrmReceivableDO> query = new MPJLambdaWrapperX<>();
41 41 // 拼接数据权限的查询条件
42   - boolean condition = CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE.getType(),
  42 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE.getType(),
43 43 CrmReceivableDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE);
44   - if (!condition) {
45   - return PageResult.empty();
46   - }
47 44 // 拼接自身的查询条件
48 45 query.selectAll(CrmReceivableDO.class)
49 46 .eqIfPresent(CrmReceivableDO::getNo, pageReqVO.getNo())
... ...
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&lt;CrmReceivablePlanDO
38 38 default PageResult<CrmReceivablePlanDO> selectPage(CrmReceivablePlanPageReqVO pageReqVO, Long userId) {
39 39 MPJLambdaWrapperX<CrmReceivablePlanDO> query = new MPJLambdaWrapperX<>();
40 40 // 拼接数据权限的查询条件
41   - boolean condition = CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType(),
  41 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType(),
42 42 CrmReceivablePlanDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE);
43   - if (!condition) {
44   - return PageResult.empty();
45   - }
46 43 // 拼接自身的查询条件
47 44 query.selectAll(CrmReceivablePlanDO.class)
48 45 .eqIfPresent(CrmReceivablePlanDO::getCustomerId, pageReqVO.getCustomerId())
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/bizlog/package-info.java deleted
1   -package cn.iocoder.yudao.module.crm.framework.bizlog;
2 0 \ No newline at end of file
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/core/package-info.java deleted
1   -package cn.iocoder.yudao.module.crm.framework.core;
2 0 \ No newline at end of file
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/package-info.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.framework.operatelog;
0 2 \ No newline at end of file
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/bizlog/function/CrmIndustryParseFunction.java renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/parse/CrmIndustryParseFunction.java
1   -package cn.iocoder.yudao.module.crm.framework.bizlog.function;
  1 +package cn.iocoder.yudao.module.crm.framework.operatelog.parse;
2 2  
3 3 import cn.hutool.core.util.StrUtil;
4 4 import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
... ... @@ -8,7 +8,6 @@ import org.springframework.stereotype.Component;
8 8  
9 9 import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_INDUSTRY;
10 10  
11   -// TODO @puhui999:包名使用 operatelog 更合适哈;
12 11 /**
13 12 * 自定义函数-通过行业编号获取行业信息
14 13 *
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/bizlog/function/CrmLevelParseFunction.java renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/parse/CrmLevelParseFunction.java
1   -package cn.iocoder.yudao.module.crm.framework.bizlog.function;
  1 +package cn.iocoder.yudao.module.crm.framework.operatelog.parse;
2 2  
3 3 import cn.hutool.core.util.StrUtil;
4 4 import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/bizlog/function/CrmSourceParseFunction.java renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/parse/CrmSourceParseFunction.java
1   -package cn.iocoder.yudao.module.crm.framework.bizlog.function;
  1 +package cn.iocoder.yudao.module.crm.framework.operatelog.parse;
2 2  
3 3 import cn.hutool.core.util.StrUtil;
4 4 import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/core/annotations/CrmPermission.java renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/annotations/CrmPermission.java
1   -package cn.iocoder.yudao.module.crm.framework.core.annotations;
  1 +package cn.iocoder.yudao.module.crm.framework.permission.core.annotations;
2 2  
3 3 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
4 4 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/core/aop/CrmPermissionAspect.java renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/aop/CrmPermissionAspect.java
1   -package cn.iocoder.yudao.module.crm.framework.core.aop;
  1 +package cn.iocoder.yudao.module.crm.framework.permission.core.aop;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.util.ObjUtil;
... ... @@ -6,27 +6,27 @@ import cn.hutool.core.util.StrUtil;
6 6 import cn.iocoder.yudao.framework.common.util.spring.SpringExpressionUtils;
7 7 import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
8 8 import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
9   -import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission;
10 9 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
11 10 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
  11 +import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
  12 +import cn.iocoder.yudao.module.crm.framework.permission.core.util.CrmPermissionUtils;
12 13 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
  14 +import jakarta.annotation.Resource;
13 15 import lombok.extern.slf4j.Slf4j;
14 16 import org.aspectj.lang.JoinPoint;
15 17 import org.aspectj.lang.annotation.Aspect;
16 18 import org.aspectj.lang.annotation.Before;
17 19 import org.springframework.stereotype.Component;
18 20  
19   -import jakarta.annotation.Resource;
20 21 import java.util.ArrayList;
21 22 import java.util.List;
22 23 import java.util.Map;
23 24  
24 25 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  26 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.anyMatch;
25 27 import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
26 28 import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CRM_PERMISSION_DENIED;
27   -import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CRM_PERMISSION_MODEL_NOT_EXISTS;
28 29  
29   -// TODO 这个包,改成 permission,然后搞 config 和 core 包,这个类在 core 包里;目的是:framework 最好分类下
30 30 /**
31 31 * Crm 数据权限校验 AOP 切面
32 32 *
... ... @@ -42,10 +42,6 @@ public class CrmPermissionAspect {
42 42  
43 43 @Before("@annotation(crmPermission)")
44 44 public void doBefore(JoinPoint joinPoint, CrmPermission crmPermission) {
45   - // TODO 芋艿:临时,方便大家调试
46   - //if (true) {
47   - // return;
48   - //}
49 45 // 获取相关属性值
50 46 Map<String, Object> expressionValues = parseExpressions(joinPoint, crmPermission);
51 47 Integer bizType = StrUtil.isEmpty(crmPermission.bizTypeValue()) ?
... ... @@ -53,16 +49,28 @@ public class CrmPermissionAspect {
53 49 Long bizId = (Long) expressionValues.get(crmPermission.bizId()); // 模块数据编号
54 50 Integer permissionLevel = crmPermission.level().getLevel(); // 需要的权限级别
55 51  
56   - // TODO 如果是超级管理员则直接通过
57   - //if (superAdmin){
58   - // return;
59   - //}
60   -
61   - // 1. 获取数据权限
  52 + // 1.1 如果是超级管理员则直接通过
  53 + if (CrmPermissionUtils.validateAdminUser()) {
  54 + return;
  55 + }
  56 + // 1.2 获取数据权限
62 57 List<CrmPermissionDO> bizPermissions = crmPermissionService.getPermissionListByBiz(bizType, bizId);
63   - if (CollUtil.isEmpty(bizPermissions)) { // 数据权限不存存那么数据也不存在
64   - throw exception(CRM_PERMISSION_MODEL_NOT_EXISTS, CrmBizTypeEnum.getNameByType(bizType));
  58 + if (CollUtil.isEmpty(bizPermissions)) { // 没有数据权限的情况
  59 + // 公海数据如果没有团队成员大家也因该有读权限才对
  60 + if (CrmPermissionLevelEnum.isRead(permissionLevel)) {
  61 + return;
  62 + }
  63 +
  64 + // 没有数据权限的情况下超出了读权限直接报错,避免后面校验空指针
  65 + throw exception(CRM_PERMISSION_DENIED, CrmBizTypeEnum.getNameByType(bizType));
  66 + } else { // 有数据权限但是没有负责人的情况
  67 + if (!anyMatch(bizPermissions, item -> CrmPermissionLevelEnum.isOwner(item.getLevel()))) {
  68 + if (CrmPermissionLevelEnum.isRead(permissionLevel)) {
  69 + return;
  70 + }
  71 + }
65 72 }
  73 +
66 74 // 2.1 情况一:如果自己是负责人,则默认有所有权限
67 75 CrmPermissionDO userPermission = CollUtil.findOne(bizPermissions, permission -> ObjUtil.equal(permission.getUserId(), getUserId()));
68 76 if (userPermission != null) {
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/package-info.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.framework.permission.core;
0 2 \ No newline at end of file
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/util/CrmPermissionUtils.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.framework.permission.core.util;
  2 +
  3 +import cn.hutool.extra.spring.SpringUtil;
  4 +import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
  5 +import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionRoleCodeEnum;
  6 +import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
  7 +
  8 +/**
  9 + * 数据权限工具类
  10 + *
  11 + * @author HUIHUI
  12 + */
  13 +public class CrmPermissionUtils {
  14 +
  15 + /**
  16 + * 校验用户是否是 CRM 管理员
  17 + *
  18 + * @return 是/否
  19 + */
  20 + public static boolean validateAdminUser() {
  21 + return SingletonManager.getPermissionApi().hasAnyRoles(getUserId(), CrmPermissionRoleCodeEnum.CRM_ADMIN.getCode());
  22 + }
  23 +
  24 + /**
  25 + * 获得用户编号
  26 + *
  27 + * @return 用户编号
  28 + */
  29 + private static Long getUserId() {
  30 + return WebFrameworkUtils.getLoginUserId();
  31 + }
  32 +
  33 + /**
  34 + * 静态内部类实现单例获取
  35 + *
  36 + * @author HUIHUI
  37 + */
  38 + private static class SingletonManager {
  39 +
  40 + private static final PermissionApi PERMISSION_API = SpringUtil.getBean(PermissionApi.class);
  41 +
  42 + public static PermissionApi getPermissionApi() {
  43 + return PERMISSION_API;
  44 + }
  45 +
  46 + }
  47 +
  48 +}
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/package-info.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.framework.permission;
0 2 \ No newline at end of file
... ...
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;
12 12 import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessMapper;
13 13 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
14 14 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
15   -import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission;
  15 +import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
16 16 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
17 17 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
18 18 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
... ...
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;
12 12 import cn.iocoder.yudao.module.crm.dal.mysql.clue.CrmClueMapper;
13 13 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
14 14 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
15   -import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission;
  15 +import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
16 16 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
17 17 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
18 18 import jakarta.annotation.Resource;
... ...
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;
9 9 import cn.iocoder.yudao.module.crm.dal.mysql.contact.CrmContactMapper;
10 10 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
11 11 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
12   -import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission;
  12 +import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
13 13 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
14 14 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
15 15 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
... ...
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
14 14 import cn.iocoder.yudao.module.crm.dal.mysql.contactbusinesslink.CrmContactBusinessLinkMapper;
15 15 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
16 16 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
17   -import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission;
  17 +import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
18 18 import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
19 19 import jakarta.annotation.Resource;
20 20 import org.springframework.stereotype.Service;
... ...
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;
12 12 import cn.iocoder.yudao.module.crm.dal.mysql.contract.CrmContractMapper;
13 13 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
14 14 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
15   -import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission;
  15 +import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
16 16 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
17 17 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
18 18 import jakarta.annotation.Resource;
... ...
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;
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  6 +import cn.iocoder.yudao.framework.operatelogv2.core.enums.OperateLogV2Constants;
6 7 import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerCreateReqVO;
7 8 import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerPageReqVO;
8 9 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;
12 13 import cn.iocoder.yudao.module.crm.dal.mysql.customer.CrmCustomerMapper;
13 14 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
14 15 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
15   -import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission;
  16 +import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
16 17 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
17 18 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
18 19 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
... ... @@ -52,6 +53,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
52 53  
53 54 @Override
54 55 @Transactional(rollbackFor = Exception.class)
  56 + @LogRecord(type = CRM_CUSTOMER, bizNo = "{{#customerId}}", success = "创建了客户")
55 57 public Long createCustomer(CrmCustomerCreateReqVO createReqVO, Long userId) {
56 58 // 插入
57 59 CrmCustomerDO customer = CrmCustomerConvert.INSTANCE.convert(createReqVO);
... ... @@ -60,26 +62,34 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
60 62 // 创建数据权限
61 63 crmPermissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType())
62 64 .setBizId(customer.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 设置当前操作的人为负责人
  65 +
  66 + // 添加日志上下文所需
  67 + LogRecordContext.putVariable("customerId", customer.getId());
63 68 return customer.getId();
64 69 }
65 70  
66 71 @Override
67 72 @Transactional(rollbackFor = Exception.class)
68   - @LogRecord(success = "更新了客户{_DIFF{#updateReqVO}}", type = CRM_CUSTOMER, subType = "更新客户", bizNo = "{{#updateReqVO.id}}")
  73 + @LogRecord(type = CRM_CUSTOMER, bizNo = "{{#updateReqVO.id}}", success = "更新了客户{_DIFF{#updateReqVO}}")
69 74 @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE)
70 75 public void updateCustomer(CrmCustomerUpdateReqVO updateReqVO) {
71 76 // 校验存在
72 77 CrmCustomerDO oldCustomerDO = validateCustomerExists(updateReqVO.getId());
73 78  
74   - // __DIFF 函数传递了一个参数,传递的参数是修改之后的对象,这种方式需要在方法内部向 LogRecordContext 中 put 一个变量,代表是之前的对象,这个对象可以是null
75   - LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldCustomerDO, CrmCustomerUpdateReqVO.class));
76 79 // 更新
77 80 CrmCustomerDO updateObj = CrmCustomerConvert.INSTANCE.convert(updateReqVO);
78 81 customerMapper.updateById(updateObj);
  82 +
  83 + // __DIFF 函数传递了一个参数,传递的参数是修改之后的对象,这种方式需要在方法内部向 LogRecordContext 中 put 一个变量,代表是之前的对象,这个对象可以是null
  84 + LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldCustomerDO, CrmCustomerUpdateReqVO.class));
  85 + HashMap<String, Object> extra = new HashMap<>();
  86 + extra.put("tips", "随便记录一点啦");
  87 + LogRecordContext.putVariable(OperateLogV2Constants.EXTRA_KEY, extra);
79 88 }
80 89  
81 90 @Override
82 91 @Transactional(rollbackFor = Exception.class)
  92 + @LogRecord(type = CRM_CUSTOMER, bizNo = "{{#id}}", success = "删除了客户")
83 93 @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#id", level = CrmPermissionLevelEnum.OWNER)
84 94 public void deleteCustomer(Long id) {
85 95 // 校验存在
... ... @@ -125,27 +135,17 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
125 135 */
126 136 @Override
127 137 public void validateCustomer(Long customerId) {
128   - // 校验客户是否存在
129   - if (customerId == null) {
130   - throw exception(CUSTOMER_NOT_EXISTS);
131   - }
132   - CrmCustomerDO customer = customerMapper.selectById(customerId);
133   - if (Objects.isNull(customer)) {
134   - throw exception(CUSTOMER_NOT_EXISTS);
135   - }
  138 + validateCustomerExists(customerId);
136 139 }
137 140  
138 141 @Override
139 142 @Transactional(rollbackFor = Exception.class)
140   - // TODO @puhui999:@LogRecord(type = CRM_CUSTOMER, subType = "客户转移", bizNo = "{{#reqVO.id}}", success = TRANSFER_CUSTOMER_LOG_SUCCESS)
141   - @LogRecord(success = TRANSFER_CUSTOMER_LOG_SUCCESS, type = CRM_CUSTOMER, subType = "客户转移", bizNo = "{{#reqVO.id}}")
  143 + @LogRecord(type = CRM_CUSTOMER, bizNo = "{{#reqVO.id}}", success = TRANSFER_CUSTOMER_LOG_SUCCESS)
142 144 @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#reqVO.id", level = CrmPermissionLevelEnum.OWNER)
143 145 public void transferCustomer(CrmCustomerTransferReqVO reqVO, Long userId) {
144 146 // 1. 校验客户是否存在
145   - validateCustomer(reqVO.getId());
146   - // 添加 crmCustomer 到日志上下文 TODO 日志记录放在 service 里是因为已经过了权限校验查询时不用走两次校验
147   - // TODO @puhui999:customer 不用查询,从 1. 拿到哈;然后 put这个动作,可以放到 3.;这样逻辑结构就是,校验、逻辑、日志,更加清晰
148   - LogRecordContext.putVariable("crmCustomer", customerMapper.selectById(reqVO.getId()));
  147 + CrmCustomerDO customerDO = validateCustomerExists(reqVO.getId());
  148 +
149 149 // 2.1 数据权限转移
150 150 crmPermissionService.transferPermission(
151 151 CrmCustomerConvert.INSTANCE.convert(reqVO, userId).setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType()));
... ... @@ -153,9 +153,11 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
153 153 customerMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId());
154 154  
155 155 // 3. TODO 记录转移日志
  156 + LogRecordContext.putVariable("crmCustomer", customerDO);
156 157 }
157 158  
158 159 @Override
  160 + @LogRecord(type = CRM_CUSTOMER, bizNo = "{{#updateReqVO.id}}", success = "锁定了客户")
159 161 public void lockCustomer(CrmCustomerUpdateReqVO updateReqVO) {
160 162 // 校验存在
161 163 validateCustomerExists(updateReqVO.getId());
... ... @@ -169,6 +171,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
169 171  
170 172 @Override
171 173 @Transactional(rollbackFor = Exception.class)
  174 + @LogRecord(type = CRM_CUSTOMER, bizNo = "{{#id}}", success = "将客户放入了公海")
172 175 @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#id", level = CrmPermissionLevelEnum.OWNER)
173 176 public void putCustomerPool(Long id) {
174 177 // 1. 校验存在
... ...
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;
8 8 import cn.iocoder.yudao.module.crm.dal.mysql.permission.CrmPermissionMapper;
9 9 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
10 10 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
  11 +import cn.iocoder.yudao.module.crm.framework.permission.core.util.CrmPermissionUtils;
11 12 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
12 13 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO;
13 14 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
... ... @@ -89,8 +90,7 @@ public class CrmPermissionServiceImpl implements CrmPermissionService {
89 90 CrmPermissionDO oldPermission = crmPermissionMapper.selectByBizTypeAndBizIdByUserId(
90 91 transferReqBO.getBizType(), transferReqBO.getBizId(), transferReqBO.getUserId());
91 92 String bizTypeName = CrmBizTypeEnum.getNameByType(transferReqBO.getBizType());
92   - // TODO 校验是否为超级管理员 || 1
93   - if (oldPermission == null || !isOwner(oldPermission.getLevel())) {
  93 + if (oldPermission == null || !isOwner(oldPermission.getLevel()) || !CrmPermissionUtils.validateAdminUser()) {
94 94 throw exception(CRM_PERMISSION_DENIED, bizTypeName);
95 95 }
96 96 // 1.1 校验转移对象是否已经是该负责人
... ... @@ -138,7 +138,6 @@ public class CrmPermissionServiceImpl implements CrmPermissionService {
138 138 }
139 139  
140 140 @Override
141   - @Transactional(rollbackFor = Exception.class) // TODO @puhui999:这里不用加的,就一个操作哈;
142 141 public void deletePermission(Integer bizType, Long bizId) {
143 142 int deletedCount = crmPermissionMapper.deletePermission(bizType, bizId);
144 143 if (deletedCount == 0) {
... ...
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
15 15 import cn.iocoder.yudao.module.crm.dal.mysql.receivable.CrmReceivablePlanMapper;
16 16 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
17 17 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
18   -import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission;
  18 +import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
19 19 import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
20 20 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
21 21 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
... ...
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;
18 18 import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum;
19 19 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
20 20 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
21   -import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission;
  21 +import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
22 22 import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
23 23 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
24 24 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
... ...
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;
7 7 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
8 8 import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum;
9 9 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
  10 +import cn.iocoder.yudao.module.crm.framework.permission.core.util.CrmPermissionUtils;
10 11 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
11 12 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
12 13 import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
  14 +import com.github.yulichang.autoconfigure.MybatisPlusJoinProperties;
13 15 import com.github.yulichang.wrapper.MPJLambdaWrapper;
14 16  
15 17 import java.util.Collection;
... ... @@ -33,46 +35,41 @@ public class CrmQueryWrapperUtils {
33 35 * @param userId 用户编号
34 36 * @param sceneType 场景类型
35 37 * @param pool 公海
36   - * @return 是否 (是:需要执行查询,否:不需要查询调用方法直接返回空)
37 38 */
38   - // TODO @puhui999:bizId 直接传递会不会简单点 回复:还是需要 SFunction 因为分页连表时不知道 bizId 是多少;是不是把 bizId 传入就好啦?
39   - public static <T extends MPJLambdaWrapper<?>, S> boolean appendPermissionCondition(T query, Integer bizType, SFunction<S, ?> bizId,
40   - Long userId, Integer sceneType, Boolean pool) {
  39 + public static <T extends MPJLambdaWrapper<?>, S> void appendPermissionCondition(T query, Integer bizType, SFunction<S, ?> bizId,
  40 + Long userId, Integer sceneType, Boolean pool) {
  41 + final String ownerUserIdField = SingletonManager.getMybatisPlusJoinProperties().getTableAlias() + ".owner_user_id";
41 42 // 1. 构建数据权限连表条件
42   - if (ObjUtil.notEqual(validateAdminUser(userId), Boolean.TRUE)) { // 管理员不需要数据权限
  43 + if (ObjUtil.notEqual(CrmPermissionUtils.validateAdminUser(), Boolean.TRUE) && ObjUtil.notEqual(pool, Boolean.TRUE)) { // 管理员,公海不需要数据权限
43 44 query.innerJoin(CrmPermissionDO.class, on -> on.eq(CrmPermissionDO::getBizType, bizType)
44   - .eq(CrmPermissionDO::getBizId, bizId)
  45 + .eq(CrmPermissionDO::getBizId, bizId) // 只能使用 SFunction 如果传 id 解析出来的 sql 不对
45 46 .eq(CrmPermissionDO::getUserId, userId));
46 47 }
47 48 // 2.1 场景一:我负责的数据
48 49 if (CrmSceneTypeEnum.isOwner(sceneType)) {
49   - query.eq("owner_user_id", userId);
  50 + query.eq(ownerUserIdField, userId);
50 51 }
51 52 // 2.2 场景二:我参与的数据
52 53 if (CrmSceneTypeEnum.isInvolved(sceneType)) {
53   - query.ne("owner_user_id", userId)
54   - // TODO @puhui999:IN 是不是更合适哈;
55   - .and(q -> q.eq(CrmPermissionDO::getLevel, CrmPermissionLevelEnum.READ.getLevel())
56   - .or()
57   - .eq(CrmPermissionDO::getLevel, CrmPermissionLevelEnum.WRITE.getLevel()));
  54 + query.ne(ownerUserIdField, userId)
  55 + .in(CrmPermissionDO::getLevel, CrmPermissionLevelEnum.READ.getLevel(), CrmPermissionLevelEnum.WRITE.getLevel());
58 56 }
59 57 // 2.3 场景三:下属负责的数据
60 58 if (CrmSceneTypeEnum.isSubordinate(sceneType)) {
61   - // TODO @puhui999:要不如果没有下属,拼一个 owner_user_id in null,不返回结果就好啦;
62   - List<AdminUserRespDTO> subordinateUsers = getAdminUserApi().getUserListBySubordinate(userId);
  59 + List<AdminUserRespDTO> subordinateUsers = SingletonManager.getAdminUserApi().getUserListBySubordinate(userId);
63 60 if (CollUtil.isEmpty(subordinateUsers)) {
64   - return false;
  61 + query.eq(ownerUserIdField, -1); // 不返回任何结果
  62 + } else {
  63 + query.in(ownerUserIdField, convertSet(subordinateUsers, AdminUserRespDTO::getId));
65 64 }
66   - query.in("owner_user_id", convertSet(subordinateUsers, AdminUserRespDTO::getId));
67 65 }
68 66  
69 67 // 3. 拼接公海的查询条件
70 68 if (ObjUtil.equal(pool, Boolean.TRUE)) { // 情况一:公海
71   - query.isNull("owner_user_id");
  69 + query.isNull(ownerUserIdField);
72 70 } else { // 情况二:不是公海
73   - query.isNotNull("owner_user_id");
  71 + query.isNotNull(ownerUserIdField);
74 72 }
75   - return true;
76 73 }
77 74  
78 75 /**
... ... @@ -84,7 +81,7 @@ public class CrmQueryWrapperUtils {
84 81 * @param userId 用户编号
85 82 */
86 83 public static <T extends MPJLambdaWrapper<?>> void appendPermissionCondition(T query, Integer bizType, Collection<Long> bizIds, Long userId) {
87   - if (ObjUtil.equal(validateAdminUser(userId), Boolean.TRUE)) {// 管理员不需要数据权限
  84 + if (ObjUtil.equal(CrmPermissionUtils.validateAdminUser(), Boolean.TRUE)) {// 管理员不需要数据权限
88 85 return;
89 86 }
90 87  
... ... @@ -93,38 +90,23 @@ public class CrmQueryWrapperUtils {
93 90 .in(CollUtil.isNotEmpty(bizIds), CrmPermissionDO::getUserId, userId));
94 91 }
95 92  
96   - private static AdminUserApi getAdminUserApi() {
97   - return AdminUserApiHolder.ADMIN_USER_API;
98   - }
99   -
100 93 /**
101   - * 校验用户是否是管理员
102   - *
103   - * @param userId 用户编号
104   - * @return 是/否
105   - */
106   - private static boolean validateAdminUser(Long userId) {
107   - // TODO 查询权限配置表用户的角色信息
108   - // TODO @puhui999:查询用户的角色;CRM_ADMIN("crm_admin", "CRM 管理员"),
109   - //CrmPermissionConfig permissionConfig = crmPermissionConfigService.getPermissionConfigByUserId(userId);
110   - //if (permissionConfig == null) {
111   - // return false;
112   - //}
113   - //// 校验是否为管理员
114   - //if (permissionConfig.getIsAdmin()){
115   - // return true;
116   - //}
117   - return false;
118   - }
119   -
120   - /**
121   - * 静态内部类实现 AdminUserApi 单例获取
  94 + * 静态内部类实现单例获取
122 95 *
123 96 * @author HUIHUI
124 97 */
125   - private static class AdminUserApiHolder {
  98 + private static class SingletonManager {
126 99  
127 100 private static final AdminUserApi ADMIN_USER_API = SpringUtil.getBean(AdminUserApi.class);
  101 + private static final MybatisPlusJoinProperties MYBATIS_PLUS_JOIN_PROPERTIES = SpringUtil.getBean(MybatisPlusJoinProperties.class);
  102 +
  103 + public static AdminUserApi getAdminUserApi() {
  104 + return ADMIN_USER_API;
  105 + }
  106 +
  107 + public static MybatisPlusJoinProperties getMybatisPlusJoinProperties() {
  108 + return MYBATIS_PLUS_JOIN_PROPERTIES;
  109 + }
128 110  
129 111 }
130 112  
... ...
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;
7 7 import cn.iocoder.yudao.module.promotion.controller.app.activity.vo.AppActivityRespVO;
8 8 import cn.iocoder.yudao.module.promotion.dal.dataobject.bargain.BargainActivityDO;
9 9 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO;
  10 +import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountActivityDO;
  11 +import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountProductDO;
  12 +import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO;
10 13 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillActivityDO;
  14 +import cn.iocoder.yudao.module.promotion.enums.common.PromotionActivityStatusEnum;
11 15 import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
12 16 import cn.iocoder.yudao.module.promotion.service.bargain.BargainActivityService;
13 17 import cn.iocoder.yudao.module.promotion.service.combination.CombinationActivityService;
  18 +import cn.iocoder.yudao.module.promotion.service.discount.DiscountActivityService;
  19 +import cn.iocoder.yudao.module.promotion.service.reward.RewardActivityService;
14 20 import cn.iocoder.yudao.module.promotion.service.seckill.SeckillActivityService;
15 21 import io.swagger.v3.oas.annotations.Operation;
16 22 import io.swagger.v3.oas.annotations.Parameter;
17 23 import io.swagger.v3.oas.annotations.tags.Tag;
  24 +import jakarta.annotation.Resource;
18 25 import org.springframework.validation.annotation.Validated;
19 26 import org.springframework.web.bind.annotation.GetMapping;
20 27 import org.springframework.web.bind.annotation.RequestMapping;
21 28 import org.springframework.web.bind.annotation.RequestParam;
22 29 import org.springframework.web.bind.annotation.RestController;
23 30  
24   -import jakarta.annotation.Resource;
25 31 import java.time.LocalDateTime;
26 32 import java.util.*;
  33 +import java.util.stream.Collectors;
27 34  
28 35 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
29   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap;
  36 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
30 37  
31 38 @Tag(name = "用户 APP - 营销活动") // 用于提供跨多个活动的 HTTP 接口
32 39 @RestController
... ... @@ -40,6 +47,10 @@ public class AppActivityController {
40 47 private SeckillActivityService seckillActivityService;
41 48 @Resource
42 49 private BargainActivityService bargainActivityService;
  50 + @Resource
  51 + private DiscountActivityService discountActivityService;
  52 + @Resource
  53 + private RewardActivityService rewardActivityService;
43 54  
44 55 @GetMapping("/list-by-spu-id")
45 56 @Operation(summary = "获得单个商品,近期参与的每个活动")
... ... @@ -64,45 +75,105 @@ public class AppActivityController {
64 75 if (CollUtil.isEmpty(spuIds)) {
65 76 return new ArrayList<>();
66 77 }
  78 +
67 79 LocalDateTime now = LocalDateTime.now();
  80 + // 获取开启的且开始的且没有结束的活动
68 81 List<AppActivityRespVO> activityList = new ArrayList<>();
  82 + // 1. 拼团活动
  83 + getCombinationActivities(spuIds, now, activityList);
  84 + // 2. 秒杀活动
  85 + getSeckillActivities(spuIds, now, activityList);
  86 + // 3. 砍价活动
  87 + getBargainActivities(spuIds, now, activityList);
  88 + // 4. 限时折扣活动
  89 + getDiscountActivities(spuIds, now, activityList);
  90 + // 5. 满减送活动
  91 + getRewardActivities(spuIds, now, activityList);
  92 + return activityList;
  93 + }
69 94  
70   - // 1. 拼团活动 - 获取开启的且开始的且没有结束的活动
  95 + private void getCombinationActivities(Collection<Long> spuIds, LocalDateTime now, List<AppActivityRespVO> activityList) {
71 96 List<CombinationActivityDO> combinationActivities = combinationActivityService.getCombinationActivityBySpuIdsAndStatusAndDateTimeLt(
72 97 spuIds, CommonStatusEnum.ENABLE.getStatus(), now);
73   - if (CollUtil.isNotEmpty(combinationActivities)) {
74   - combinationActivities.forEach(item -> {
75   - activityList.add(new AppActivityRespVO().setId(item.getId())
76   - .setType(PromotionTypeEnum.COMBINATION_ACTIVITY.getType()).setName(item.getName())
77   - .setSpuId(item.getSpuId()).setStartTime(item.getStartTime()).setEndTime(item.getEndTime()));
78   - });
  98 + if (CollUtil.isEmpty(combinationActivities)) {
  99 + return;
79 100 }
80 101  
81   - // 2. 秒杀活动 - 获取开启的且开始的且没有结束的活动
  102 + combinationActivities.forEach(item -> {
  103 + activityList.add(new AppActivityRespVO().setId(item.getId())
  104 + .setType(PromotionTypeEnum.COMBINATION_ACTIVITY.getType()).setName(item.getName())
  105 + .setSpuId(item.getSpuId()).setStartTime(item.getStartTime()).setEndTime(item.getEndTime()));
  106 + });
  107 + }
  108 +
  109 + private void getSeckillActivities(Collection<Long> spuIds, LocalDateTime now, List<AppActivityRespVO> activityList) {
82 110 List<SeckillActivityDO> seckillActivities = seckillActivityService.getSeckillActivityBySpuIdsAndStatusAndDateTimeLt(
83 111 spuIds, CommonStatusEnum.ENABLE.getStatus(), now);
84   - if (CollUtil.isNotEmpty(seckillActivities)) {
85   - seckillActivities.forEach(item -> {
86   - activityList.add(new AppActivityRespVO().setId(item.getId())
87   - .setType(PromotionTypeEnum.SECKILL_ACTIVITY.getType()).setName(item.getName())
88   - .setSpuId(item.getSpuId()).setStartTime(item.getStartTime()).setEndTime(item.getEndTime()));
89   - });
  112 + if (CollUtil.isEmpty(seckillActivities)) {
  113 + return;
90 114 }
91 115  
92   - // 3. 砍价活动 - 获取开启的且开始的且没有结束的活动
  116 + seckillActivities.forEach(item -> {
  117 + activityList.add(new AppActivityRespVO().setId(item.getId())
  118 + .setType(PromotionTypeEnum.SECKILL_ACTIVITY.getType()).setName(item.getName())
  119 + .setSpuId(item.getSpuId()).setStartTime(item.getStartTime()).setEndTime(item.getEndTime()));
  120 + });
  121 + }
  122 +
  123 + private void getBargainActivities(Collection<Long> spuIds, LocalDateTime now, List<AppActivityRespVO> activityList) {
93 124 List<BargainActivityDO> bargainActivities = bargainActivityService.getBargainActivityBySpuIdsAndStatusAndDateTimeLt(
94 125 spuIds, CommonStatusEnum.ENABLE.getStatus(), now);
95 126 if (CollUtil.isNotEmpty(bargainActivities)) {
96   - bargainActivities.forEach(item -> {
97   - activityList.add(new AppActivityRespVO().setId(item.getId())
98   - .setType(PromotionTypeEnum.BARGAIN_ACTIVITY.getType()).setName(item.getName())
99   - .setSpuId(item.getSpuId()).setStartTime(item.getStartTime()).setEndTime(item.getEndTime()));
100   - });
  127 + return;
101 128 }
102 129  
103   - // TODO 芋艿:满减送活动
104   - // TODO 芋艿:限时折扣活动
105   - return activityList;
  130 + bargainActivities.forEach(item -> {
  131 + activityList.add(new AppActivityRespVO().setId(item.getId())
  132 + .setType(PromotionTypeEnum.BARGAIN_ACTIVITY.getType()).setName(item.getName())
  133 + .setSpuId(item.getSpuId()).setStartTime(item.getStartTime()).setEndTime(item.getEndTime()));
  134 + });
  135 + }
  136 +
  137 + private void getDiscountActivities(Collection<Long> spuIds, LocalDateTime now, List<AppActivityRespVO> activityList) {
  138 + List<DiscountActivityDO> discountActivities = discountActivityService.getDiscountActivityBySpuIdsAndStatusAndDateTimeLt(
  139 + spuIds, CommonStatusEnum.ENABLE.getStatus(), now);
  140 + if (CollUtil.isEmpty(discountActivities)) {
  141 + return;
  142 + }
  143 +
  144 + List<DiscountProductDO> products = discountActivityService.getDiscountProductsByActivityId(
  145 + convertSet(discountActivities, DiscountActivityDO::getId));
  146 + Map<Long, Long> productMap = convertMap(products, DiscountProductDO::getActivityId, DiscountProductDO::getSpuId);
  147 + discountActivities.forEach(item -> {
  148 + activityList.add(new AppActivityRespVO().setId(item.getId())
  149 + .setType(PromotionTypeEnum.DISCOUNT_ACTIVITY.getType()).setName(item.getName())
  150 + .setSpuId(productMap.get(item.getId())).setStartTime(item.getStartTime()).setEndTime(item.getEndTime()));
  151 + });
  152 + }
  153 +
  154 + private void getRewardActivities(Collection<Long> spuIds, LocalDateTime now, List<AppActivityRespVO> activityList) {
  155 + List<RewardActivityDO> rewardActivityList = rewardActivityService.getRewardActivityBySpuIdsAndStatusAndDateTimeLt(
  156 + spuIds, PromotionActivityStatusEnum.RUN.getStatus(), now);
  157 + if (CollUtil.isEmpty(rewardActivityList)) {
  158 + return;
  159 + }
  160 +
  161 + Map<Long, Optional<RewardActivityDO>> spuIdAndActivityMap = spuIds.stream()
  162 + .collect(Collectors.toMap(
  163 + spuId -> spuId,
  164 + spuId -> rewardActivityList.stream()
  165 + .filter(activity -> activity.getProductSpuIds().contains(spuId))
  166 + .max(Comparator.comparing(RewardActivityDO::getCreateTime))));
  167 + for (Long supId : spuIdAndActivityMap.keySet()) {
  168 + if (spuIdAndActivityMap.get(supId).isEmpty()) {
  169 + continue;
  170 + }
  171 +
  172 + RewardActivityDO rewardActivityDO = spuIdAndActivityMap.get(supId).get();
  173 + activityList.add(new AppActivityRespVO().setId(rewardActivityDO.getId())
  174 + .setType(PromotionTypeEnum.REWARD_ACTIVITY.getType()).setName(rewardActivityDO.getName())
  175 + .setSpuId(supId).setStartTime(rewardActivityDO.getStartTime()).setEndTime(rewardActivityDO.getEndTime()));
  176 + }
106 177 }
107 178  
108 179 }
... ...
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
7 7 import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountActivityDO;
8 8 import org.apache.ibatis.annotations.Mapper;
9 9  
  10 +import java.time.LocalDateTime;
10 11 import java.util.Collection;
11 12 import java.util.List;
12   -import java.util.Set;
13 13  
14 14 /**
15 15 * 限时折扣活动 Mapper
... ... @@ -27,4 +27,20 @@ public interface DiscountActivityMapper extends BaseMapperX&lt;DiscountActivityDO&gt;
27 27 .orderByDesc(DiscountActivityDO::getId));
28 28 }
29 29  
  30 + /**
  31 + * 获取指定活动编号的活动列表且
  32 + * 开始时间和结束时间小于给定时间 dateTime 的活动列表
  33 + *
  34 + * @param ids 活动编号
  35 + * @param dateTime 指定日期
  36 + * @return 活动列表
  37 + */
  38 + default List<DiscountActivityDO> selectListByIdsAndDateTimeLt(Collection<Long> ids, LocalDateTime dateTime) {
  39 + return selectList(new LambdaQueryWrapperX<DiscountActivityDO>()
  40 + .in(DiscountActivityDO::getId, ids)
  41 + .lt(DiscountActivityDO::getStartTime, dateTime)
  42 + .gt(DiscountActivityDO::getEndTime, dateTime)// 开始时间 < 指定时间 < 结束时间,也就是说获取指定时间段的活动
  43 + .orderByDesc(DiscountActivityDO::getCreateTime));
  44 + }
  45 +
30 46 }
... ...
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;
2 2  
3 3 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
4 4 import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountProductDO;
  5 +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
5 6 import org.apache.ibatis.annotations.Mapper;
6 7 import org.apache.ibatis.annotations.Param;
7 8  
8 9 import java.util.Collection;
9 10 import java.util.List;
  11 +import java.util.Map;
10 12  
11 13 /**
12 14 * 限时折扣商城 Mapper
... ... @@ -30,4 +32,20 @@ public interface DiscountProductMapper extends BaseMapperX&lt;DiscountProductDO&gt; {
30 32  
31 33 // TODO @zhangshuai:逻辑里,尽量避免写 join 语句哈,你可以看看这个查询,有什么办法优化?目前的一个思路,是分 2 次查询,性能也是 ok 的
32 34 List<DiscountProductDO> getMatchDiscountProductList(@Param("skuIds") Collection<Long> skuIds);
  35 +
  36 + /**
  37 + * 查询出指定 spuId 的 spu 参加的活动最接近现在的一条记录。多个的话,一个 spuId 对应一个最近的活动编号
  38 + *
  39 + * @param spuIds spu 编号
  40 + * @param status 状态
  41 + * @return 包含 spuId 和 activityId 的 map 对象列表
  42 + */
  43 + default List<Map<String, Object>> selectSpuIdAndActivityIdMapsBySpuIdsAndStatus(Collection<Long> spuIds, Integer status) {
  44 + return selectMaps(new QueryWrapper<DiscountProductDO>()
  45 + .select("spu_id AS spuId, MAX(DISTINCT(activity_id)) AS activityId")
  46 + .in("spu_id", spuIds)
  47 + .eq("activity_status", status)
  48 + .groupBy("spu_id"));
  49 + }
  50 +
33 51 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/reward/RewardActivityMapper.java
1 1 package cn.iocoder.yudao.module.promotion.dal.mysql.reward;
2 2  
  3 +import cn.hutool.core.util.StrUtil;
3 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 5 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 6 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6 7 import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityPageReqVO;
7 8 import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO;
  9 +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
8 10 import org.apache.ibatis.annotations.Mapper;
9 11  
  12 +import java.time.LocalDateTime;
10 13 import java.util.Collection;
11 14 import java.util.List;
  15 +import java.util.function.Function;
  16 +import java.util.stream.Collectors;
12 17  
13 18 /**
14 19 * 满减送活动 Mapper
... ... @@ -35,4 +40,30 @@ public interface RewardActivityMapper extends BaseMapperX&lt;RewardActivityDO&gt; {
35 40 .eq(RewardActivityDO::getStatus, status));
36 41 }
37 42  
  43 + default List<RewardActivityDO> selectListBySpuIdsAndStatus(Collection<Long> spuIds, Integer status) {
  44 + Function<Collection<Long>, String> productScopeValuesFindInSetFunc = ids -> ids.stream()
  45 + .map(id -> StrUtil.format("FIND_IN_SET({}, product_spu_ids) ", id))
  46 + .collect(Collectors.joining(" OR "));
  47 + return selectList(new QueryWrapper<RewardActivityDO>()
  48 + .eq("status", status)
  49 + .apply(productScopeValuesFindInSetFunc.apply(spuIds)));
  50 + }
  51 +
  52 + /**
  53 + * 获取指定活动编号的活动列表且
  54 + * 开始时间和结束时间小于给定时间 dateTime 的活动列表
  55 + *
  56 + * @param ids 活动编号
  57 + * @param dateTime 指定日期
  58 + * @return 活动列表
  59 + */
  60 + default List<RewardActivityDO> selectListByIdsAndDateTimeLt(Collection<Long> ids, LocalDateTime dateTime) {
  61 + return selectList(new LambdaQueryWrapperX<RewardActivityDO>()
  62 + .in(RewardActivityDO::getId, ids)
  63 + .lt(RewardActivityDO::getStartTime, dateTime)
  64 + .gt(RewardActivityDO::getEndTime, dateTime)// 开始时间 < 指定时间 < 结束时间,也就是说获取指定时间段的活动
  65 + .orderByDesc(RewardActivityDO::getCreateTime)
  66 + );
  67 + }
  68 +
38 69 }
... ...
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
6 6 import cn.iocoder.yudao.module.promotion.controller.admin.discount.vo.DiscountActivityUpdateReqVO;
7 7 import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountActivityDO;
8 8 import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountProductDO;
9   -
10 9 import jakarta.validation.Valid;
  10 +
  11 +import java.time.LocalDateTime;
11 12 import java.util.Collection;
12 13 import java.util.List;
13 14  
... ... @@ -89,4 +90,14 @@ public interface DiscountActivityService {
89 90 */
90 91 List<DiscountProductDO> getDiscountProductsByActivityId(Collection<Long> activityIds);
91 92  
  93 + /**
  94 + * 获取指定 spu 编号最近参加的活动,每个 spuId 只返回一条记录
  95 + *
  96 + * @param spuIds spu 编号
  97 + * @param status 状态
  98 + * @param dateTime 当前日期时间
  99 + * @return 折扣活动列表
  100 + */
  101 + List<DiscountActivityDO> getDiscountActivityBySpuIdsAndStatusAndDateTimeLt(Collection<Long> spuIds, Integer status, LocalDateTime dateTime);
  102 +
92 103 }
... ...
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;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.collection.CollectionUtil;
  5 +import cn.hutool.core.map.MapUtil;
5 6 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
6 7 import cn.iocoder.yudao.framework.common.pojo.PageResult;
7 8 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
15 16 import cn.iocoder.yudao.module.promotion.dal.mysql.discount.DiscountProductMapper;
16 17 import cn.iocoder.yudao.module.promotion.enums.common.PromotionActivityStatusEnum;
17 18 import cn.iocoder.yudao.module.promotion.util.PromotionUtils;
  19 +import jakarta.annotation.Resource;
18 20 import org.springframework.stereotype.Service;
19 21 import org.springframework.validation.annotation.Validated;
20 22  
21   -import jakarta.annotation.Resource;
  23 +import java.time.LocalDateTime;
22 24 import java.util.Collection;
  25 +import java.util.Collections;
23 26 import java.util.List;
  27 +import java.util.Map;
24 28 import java.util.stream.Collectors;
25 29  
26 30 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
27 31 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  32 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
28 33 import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
29 34  
30 35 /**
... ... @@ -109,7 +114,7 @@ public class DiscountActivityServiceImpl implements DiscountActivityService {
109 114 /**
110 115 * 校验商品是否冲突
111 116 *
112   - * @param id 编号
  117 + * @param id 编号
113 118 * @param products 商品列表
114 119 */
115 120 private void validateDiscountActivityProductConflicts(Long id, List<DiscountActivityBaseVO.Product> products) {
... ... @@ -184,4 +189,17 @@ public class DiscountActivityServiceImpl implements DiscountActivityService {
184 189 return discountProductMapper.selectList("activity_id", activityIds);
185 190 }
186 191  
  192 + @Override
  193 + public List<DiscountActivityDO> getDiscountActivityBySpuIdsAndStatusAndDateTimeLt(Collection<Long> spuIds, Integer status, LocalDateTime dateTime) {
  194 + // 1. 查询出指定 spuId 的 spu 参加的活动最接近现在的一条记录。多个的话,一个 spuId 对应一个最近的活动编号
  195 + List<Map<String, Object>> spuIdAndActivityIdMaps = discountProductMapper.selectSpuIdAndActivityIdMapsBySpuIdsAndStatus(spuIds, status);
  196 + if (CollUtil.isEmpty(spuIdAndActivityIdMaps)) {
  197 + return Collections.emptyList();
  198 + }
  199 +
  200 + // 2. 查询活动详情
  201 + return discountActivityMapper.selectListByIdsAndDateTimeLt(
  202 + convertSet(spuIdAndActivityIdMaps, map -> MapUtil.getLong(map, "activityId")), dateTime);
  203 + }
  204 +
187 205 }
... ...
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
6 6 import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityPageReqVO;
7 7 import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityUpdateReqVO;
8 8 import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO;
9   -
10 9 import jakarta.validation.Valid;
  10 +
  11 +import java.time.LocalDateTime;
11 12 import java.util.Collection;
12 13 import java.util.List;
13 14  
... ... @@ -71,4 +72,14 @@ public interface RewardActivityService {
71 72 */
72 73 List<RewardActivityMatchRespDTO> getMatchRewardActivityList(Collection<Long> spuIds);
73 74  
  75 + /**
  76 + * 获取指定 spu 编号最近参加的活动,每个 spuId 只返回一条记录
  77 + *
  78 + * @param spuIds spu 编号
  79 + * @param status 状态
  80 + * @param dateTime 当前日期时间
  81 + * @return 满减送活动列表
  82 + */
  83 + List<RewardActivityDO> getRewardActivityBySpuIdsAndStatusAndDateTimeLt(Collection<Long> spuIds, Integer status, LocalDateTime dateTime);
  84 +
74 85 }
... ...
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;
11 11 import cn.iocoder.yudao.module.promotion.dal.mysql.reward.RewardActivityMapper;
12 12 import cn.iocoder.yudao.module.promotion.enums.common.PromotionActivityStatusEnum;
13 13 import cn.iocoder.yudao.module.promotion.util.PromotionUtils;
  14 +import jakarta.annotation.Resource;
14 15 import org.springframework.stereotype.Service;
15 16 import org.springframework.validation.annotation.Validated;
16 17  
17   -import jakarta.annotation.Resource;
  18 +import java.time.LocalDateTime;
18 19 import java.util.Collection;
  20 +import java.util.Collections;
19 21 import java.util.List;
20 22  
21 23 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  24 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
22 25 import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
23 26 import static java.util.Arrays.asList;
24 27  
... ... @@ -100,10 +103,11 @@ public class RewardActivityServiceImpl implements RewardActivityService {
100 103 }
101 104  
102 105 // TODO @芋艿:逻辑有问题,需要优化;要分成全场、和指定来校验;
  106 +
103 107 /**
104 108 * 校验商品参加的活动是否冲突
105 109 *
106   - * @param id 活动编号
  110 + * @param id 活动编号
107 111 * @param spuIds 商品 SPU 编号数组
108 112 */
109 113 private void validateRewardActivitySpuConflicts(Long id, Collection<Long> spuIds) {
... ... @@ -125,7 +129,7 @@ public class RewardActivityServiceImpl implements RewardActivityService {
125 129 /**
126 130 * 获得商品参加的满减送活动的数组
127 131 *
128   - * @param spuIds 商品 SPU 编号数组
  132 + * @param spuIds 商品 SPU 编号数组
129 133 * @param statuses 活动状态数组
130 134 * @return 商品参加的满减送活动的数组
131 135 */
... ... @@ -163,4 +167,16 @@ public class RewardActivityServiceImpl implements RewardActivityService {
163 167 return null;
164 168 }
165 169  
  170 + @Override
  171 + public List<RewardActivityDO> getRewardActivityBySpuIdsAndStatusAndDateTimeLt(Collection<Long> spuIds, Integer status, LocalDateTime dateTime) {
  172 + // 1. 查询出指定 spuId 的 spu 参加的活动
  173 + List<RewardActivityDO> rewardActivityList = rewardActivityMapper.selectListBySpuIdsAndStatus(spuIds, status);
  174 + if (CollUtil.isEmpty(rewardActivityList)) {
  175 + return Collections.emptyList();
  176 + }
  177 +
  178 + // 2. 查询活动详情
  179 + return rewardActivityMapper.selectListByIdsAndDateTimeLt(convertSet(rewardActivityList, RewardActivityDO::getId), dateTime);
  180 + }
  181 +
166 182 }
... ...
yudao-module-system/yudao-module-system-api/pom.xml
... ... @@ -22,32 +22,12 @@
22 22 <artifactId>yudao-common</artifactId>
23 23 </dependency>
24 24  
25   - <!-- TODO @puhui999 & 芋艿:操作日志,要不要这么引入? -->
26   - <!-- Springboot-注解-通用操作日志组件 -->
27   - <!-- 此组件解决的问题是: 「谁」在「什么时间」对「什么」做了「什么事」 -->
28   - <dependency>
29   - <groupId>io.github.mouzt</groupId>
30   - <artifactId>bizlog-sdk</artifactId>
31   - </dependency>
32   -
33   - <!-- TODO @puhui999 & 芋艿:要不要移除掉 -->
34   - <!--工具类相关-->
35   - <dependency>
36   - <groupId>com.fasterxml.jackson.core</groupId>
37   - <artifactId>jackson-databind</artifactId>
38   - </dependency>
39   - <dependency>
40   - <groupId>com.fasterxml.jackson.core</groupId>
41   - <artifactId>jackson-core</artifactId>
42   - </dependency>
43   -
44 25 <!-- 参数校验 -->
45 26 <dependency>
46 27 <groupId>org.springframework.boot</groupId>
47 28 <artifactId>spring-boot-starter-validation</artifactId>
48 29 <optional>true</optional>
49 30 </dependency>
50   -
51 31 </dependencies>
52 32  
53 33 </project>
... ...
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/logger/OperateLogApi.java
1 1 package cn.iocoder.yudao.module.system.api.logger;
2 2  
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
3 4 import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogCreateReqDTO;
  5 +import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2CreateReqDTO;
  6 +import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2PageReqDTO;
4 7 import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2RespDTO;
5 8 import jakarta.validation.Valid;
6 9  
7   -import java.util.List;
8   -
9 10 /**
10 11 * 操作日志 API 接口
11 12 *
... ... @@ -21,12 +22,18 @@ public interface OperateLogApi {
21 22 void createOperateLog(@Valid OperateLogCreateReqDTO createReqDTO);
22 23  
23 24 /**
24   - * 获取指定模块的指定数据的操作日志
  25 + * 创建操作日志
  26 + *
  27 + * @param createReqDTO 请求
  28 + */
  29 + void createOperateLogV2(@Valid OperateLogV2CreateReqDTO createReqDTO);
  30 +
  31 + /**
  32 + * 获取指定模块的指定数据的操作日志分页
25 33 *
26   - * @param module 操作模块
27   - * @param bizId 操作模块编号
28   - * @return 操作日志
  34 + * @param pageReqVO 请求
  35 + * @return 操作日志分页
29 36 */
30   - List<OperateLogV2RespDTO> getOperateLogByModuleAndBizId(String module, Long bizId);
  37 + PageResult<OperateLogV2RespDTO> getOperateLogPage(OperateLogV2PageReqDTO pageReqVO);
31 38  
32 39 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/logger/bo/OperateLogV2CreateReqBO.java renamed to yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/logger/dto/OperateLogV2CreateReqDTO.java
1   -package cn.iocoder.yudao.module.system.service.logger.bo;
  1 +package cn.iocoder.yudao.module.system.api.logger.dto;
2 2  
3 3 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
4 4 import jakarta.validation.constraints.NotEmpty;
  5 +import jakarta.validation.constraints.NotNull;
5 6 import lombok.Data;
6 7  
  8 +import java.time.LocalDateTime;
  9 +import java.util.Map;
  10 +
7 11 /**
8 12 * 系统操作日志 Create Req BO
9 13 *
10 14 * @author HUIHUI
11 15 */
12 16 @Data
13   -public class OperateLogV2CreateReqBO {
  17 +public class OperateLogV2CreateReqDTO {
14 18  
15 19 /**
16 20 * 链路追踪编号
... ... @@ -23,29 +27,29 @@ public class OperateLogV2CreateReqBO {
23 27 *
24 28 * 关联 MemberUserDO 的 id 属性,或者 AdminUserDO 的 id 属性
25 29 */
26   - @NotEmpty(message = "用户编号不能为空")
  30 + @NotNull(message = "用户编号不能为空")
27 31 private Long userId;
28 32 /**
29 33 * 用户类型
30 34 *
31 35 * 关联 {@link UserTypeEnum}
32 36 */
33   - @NotEmpty(message = "用户类型不能为空")
  37 + @NotNull(message = "用户类型不能为空")
34 38 private Integer userType;
35 39 /**
36   - * 操作模块
  40 + * 操作模块类型
37 41 */
38   - @NotEmpty(message = "操作模块不能为空")
39   - private String module;
  42 + @NotEmpty(message = "操作模块类型不能为空")
  43 + private String type;
40 44 /**
41 45 * 操作名
42 46 */
43 47 @NotEmpty(message = "操作名不能为空")
44   - private String name;
  48 + private String subType;
45 49 /**
46 50 * 操作模块业务编号
47 51 */
48   - @NotEmpty(message = "操作模块业务编号不能为空")
  52 + @NotNull(message = "操作模块业务编号不能为空")
49 53 private Long bizId;
50 54 /**
51 55 * 操作内容,记录整个操作的明细
... ... @@ -57,7 +61,7 @@ public class OperateLogV2CreateReqBO {
57 61 * 拓展字段,有些复杂的业务,需要记录一些字段 ( JSON 格式 )
58 62 * 例如说,记录订单编号,{ orderId: "1"}
59 63 */
60   - private String extra;
  64 + private Map<String, Object> extra;
61 65  
62 66 /**
63 67 * 请求方法名
... ... @@ -80,4 +84,38 @@ public class OperateLogV2CreateReqBO {
80 84 @NotEmpty(message = "浏览器 UA 不能为空")
81 85 private String userAgent;
82 86  
  87 + /**
  88 + * Java 方法名
  89 + */
  90 + private String javaMethod;
  91 + /**
  92 + * Java 方法的参数
  93 + */
  94 + private String javaMethodArgs;
  95 +
  96 + /**
  97 + * 开始时间
  98 + */
  99 + private LocalDateTime startTime;
  100 +
  101 + /**
  102 + * 执行时长,单位:毫秒
  103 + */
  104 + private Integer duration;
  105 +
  106 + /**
  107 + * 结果码
  108 + */
  109 + private Integer resultCode;
  110 +
  111 + /**
  112 + * 结果提示
  113 + */
  114 + private String resultMsg;
  115 +
  116 + /**
  117 + * 结果数据
  118 + */
  119 + private String resultData;
  120 +
83 121 }
... ...
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/logger/dto/OperateLogV2PageReqDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.api.logger.dto;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import lombok.Data;
  5 +
  6 +/**
  7 + * 操作日志分页 Request DTO
  8 + *
  9 + * @author HUIHUI
  10 + */
  11 +@Data
  12 +public class OperateLogV2PageReqDTO extends PageParam {
  13 +
  14 + /**
  15 + * 模块类型
  16 + */
  17 + private String bizType;
  18 + /**
  19 + * 模块数据编号
  20 + */
  21 + private Long bizId;
  22 +
  23 + /**
  24 + * 用户编号
  25 + */
  26 + private Long userId;
  27 +
  28 +}
... ...
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/logger/dto/OperateLogV2RespDTO.java
1 1 package cn.iocoder.yudao.module.system.api.logger.dto;
2 2  
3   -import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
4   -import com.fasterxml.jackson.annotation.JsonFormat;
5 3 import lombok.Data;
6 4  
7 5 import java.time.LocalDateTime;
8   -
9   -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
10   -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT;
  6 +import java.util.Map;
11 7  
12 8 /**
13 9 * 系统操作日志 Resp DTO
... ... @@ -26,17 +22,21 @@ public class OperateLogV2RespDTO {
26 22 */
27 23 private Long userId;
28 24 /**
  25 + * 用户名称
  26 + */
  27 + private String userName;
  28 + /**
29 29 * 用户类型
30 30 */
31 31 private Integer userType;
32 32 /**
33   - * 操作模块
  33 + * 操作模块类型
34 34 */
35   - private String module;
  35 + private String type;
36 36 /**
37 37 * 操作名
38 38 */
39   - private String name;
  39 + private String subType;
40 40 /**
41 41 * 操作模块业务编号
42 42 */
... ... @@ -48,7 +48,7 @@ public class OperateLogV2RespDTO {
48 48 /**
49 49 * 拓展字段
50 50 */
51   - private String extra;
  51 + private Map<String, Object> extra;
52 52  
53 53 /**
54 54 * 请求方法名
... ... @@ -68,20 +68,42 @@ public class OperateLogV2RespDTO {
68 68 private String userAgent;
69 69  
70 70 /**
71   - * 创建时间
  71 + * Java 方法名
72 72 */
73   - // TODO puhui999: 木得效果怎么肥事
74   - @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
75   - private LocalDateTime createTime;
  73 + private String javaMethod;
  74 + /**
  75 + * Java 方法的参数
  76 + */
  77 + private String javaMethodArgs;
  78 +
  79 + /**
  80 + * 开始时间
  81 + */
  82 + private LocalDateTime startTime;
  83 +
  84 + /**
  85 + * 执行时长,单位:毫秒
  86 + */
  87 + private Integer duration;
  88 +
  89 + /**
  90 + * 结果码
  91 + */
  92 + private Integer resultCode;
76 93  
77   - // TODO @puhui999:下面 2 个字段不用返回;用 userId 哈;返回一个 userName
78 94 /**
79   - * 创建者
  95 + * 结果提示
80 96 */
81   - private String creator;
  97 + private String resultMsg;
  98 +
82 99 /**
83   - * 创建者名称
  100 + * 结果数据
84 101 */
85   - private String creatorName;
  102 + private String resultData;
  103 +
  104 + /**
  105 + * 创建时间
  106 + */
  107 + private LocalDateTime createTime;
86 108  
87 109 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/logger/OperateLogApiImpl.java
1 1 package cn.iocoder.yudao.module.system.api.logger;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
5 6 import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogCreateReqDTO;
  7 +import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2CreateReqDTO;
  8 +import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2PageReqDTO;
6 9 import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2RespDTO;
7 10 import cn.iocoder.yudao.module.system.dal.dataobject.logger.OperateLogV2DO;
8 11 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
9 12 import cn.iocoder.yudao.module.system.service.logger.OperateLogService;
10 13 import cn.iocoder.yudao.module.system.service.user.AdminUserService;
11 14 import jakarta.annotation.Resource;
  15 +import org.springframework.scheduling.annotation.Async;
12 16 import org.springframework.stereotype.Service;
13 17 import org.springframework.validation.annotation.Validated;
14 18  
15   -import java.util.Collections;
16 19 import java.util.List;
17 20 import java.util.Map;
18 21  
... ... @@ -39,21 +42,31 @@ public class OperateLogApiImpl implements OperateLogApi {
39 42 }
40 43  
41 44 @Override
42   - public List<OperateLogV2RespDTO> getOperateLogByModuleAndBizId(String module, Long bizId) {
43   - List<OperateLogV2DO> logList = operateLogService.getOperateLogByModuleAndBizId(module, bizId);
44   - if (CollUtil.isEmpty(logList)) {
45   - return Collections.emptyList();
  45 + @Async
  46 + public void createOperateLogV2(OperateLogV2CreateReqDTO createReqDTO) {
  47 + operateLogService.createOperateLogV2(createReqDTO);
  48 + }
  49 +
  50 + @Override
  51 + public PageResult<OperateLogV2RespDTO> getOperateLogPage(OperateLogV2PageReqDTO pageReqVO) {
  52 + PageResult<OperateLogV2DO> operateLogPage = operateLogService.getOperateLogPage(pageReqVO);
  53 + if (CollUtil.isEmpty(operateLogPage.getList())) {
  54 + return PageResult.empty();
46 55 }
47 56  
48 57 // 获取用户
49   - List<AdminUserDO> userList = adminUserService.getUserList(convertSet(logList, item -> Long.parseLong(item.getCreator())));
  58 + List<AdminUserDO> userList = adminUserService.getUserList(convertSet(operateLogPage.getList(), OperateLogV2DO::getUserId));
  59 + return BeanUtils.toBean(operateLogPage, OperateLogV2RespDTO.class).setList(setUserInfo(operateLogPage.getList(), userList));
  60 + }
  61 +
  62 + private static List<OperateLogV2RespDTO> setUserInfo(List<OperateLogV2DO> logList, List<AdminUserDO> userList) {
50 63 Map<Long, AdminUserDO> userMap = convertMap(userList, AdminUserDO::getId);
51 64 return convertList(logList, item -> {
52   - OperateLogV2RespDTO bean = BeanUtils.toBean(item, OperateLogV2RespDTO.class);
53   - findAndThen(userMap, Long.parseLong(item.getCreator()), user -> {
54   - bean.setCreatorName(user.getNickname());
  65 + OperateLogV2RespDTO respDTO = BeanUtils.toBean(item, OperateLogV2RespDTO.class);
  66 + findAndThen(userMap, item.getUserId(), user -> {
  67 + respDTO.setUserName(user.getNickname());
55 68 });
56   - return bean;
  69 + return respDTO;
57 70 });
58 71 }
59 72  
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/logger/OperateLogV2DO.java
1 1 package cn.iocoder.yudao.module.system.dal.dataobject.logger;
2 2  
3 3 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
  4 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 5 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
5 6 import com.baomidou.mybatisplus.annotation.KeySequence;
  7 +import com.baomidou.mybatisplus.annotation.TableField;
6 8 import com.baomidou.mybatisplus.annotation.TableId;
7 9 import com.baomidou.mybatisplus.annotation.TableName;
  10 +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
8 11 import lombok.Data;
9 12 import lombok.EqualsAndHashCode;
10 13  
  14 +import java.time.LocalDateTime;
  15 +import java.util.Map;
  16 +
11 17 /**
12 18 * 操作日志表 V2
13 19 *
... ... @@ -20,6 +26,16 @@ import lombok.EqualsAndHashCode;
20 26 public class OperateLogV2DO extends BaseDO {
21 27  
22 28 /**
  29 + * {@link #javaMethodArgs} 的最大长度
  30 + */
  31 + public static final Integer JAVA_METHOD_ARGS_MAX_LENGTH = 8000;
  32 +
  33 + /**
  34 + * {@link #resultData} 的最大长度
  35 + */
  36 + public static final Integer RESULT_MAX_LENGTH = 4000;
  37 +
  38 + /**
23 39 * 日志主键
24 40 */
25 41 @TableId
... ... @@ -42,15 +58,14 @@ public class OperateLogV2DO extends BaseDO {
42 58 * 关联 {@link UserTypeEnum}
43 59 */
44 60 private Integer userType;
45   - // TODO @puhui999:module 改成 type,name 改成 subType;
46 61 /**
47   - * 操作模块
  62 + * 操作模块类型
48 63 */
49   - private String module;
  64 + private String type;
50 65 /**
51 66 * 操作名
52 67 */
53   - private String name;
  68 + private String subType;
54 69 /**
55 70 * 操作模块业务编号
56 71 */
... ... @@ -66,8 +81,8 @@ public class OperateLogV2DO extends BaseDO {
66 81 *
67 82 * 例如说,记录订单编号,{ orderId: "1"}
68 83 */
69   - // TODO @puhui999:看看能不能类似 exts 搞 json 格式;
70   - private String extra;
  84 + @TableField(typeHandler = JacksonTypeHandler.class)
  85 + private Map<String, Object> extra;
71 86 /**
72 87 * 请求方法名
73 88 */
... ... @@ -85,9 +100,43 @@ public class OperateLogV2DO extends BaseDO {
85 100 */
86 101 private String userAgent;
87 102  
88   - // TODO @芋艿:requestUrl、requestMethod
89   - // TODO @芋艿:javaMethod、javaMethodArgs
90   - // TODO @芋艿:startTime、duration
91   - // TODO @芋艿:resultMsg、resultData
  103 + /**
  104 + * Java 方法名
  105 + */
  106 + private String javaMethod;
  107 + /**
  108 + * Java 方法的参数
  109 + *
  110 + * 实际格式为 Map<String, Object>
  111 + * 不使用 @TableField(typeHandler = FastjsonTypeHandler.class) 注解的原因是,数据库存储有长度限制,会进行裁剪,会导致 JSON 反序列化失败
  112 + * 其中,key 为参数名,value 为参数值
  113 + */
  114 + private String javaMethodArgs;
  115 + /**
  116 + * 开始时间
  117 + */
  118 + private LocalDateTime startTime;
  119 + /**
  120 + * 执行时长,单位:毫秒
  121 + */
  122 + private Integer duration;
  123 + /**
  124 + * 结果码
  125 + *
  126 + * 目前使用的 {@link CommonResult#getCode()} 属性
  127 + */
  128 + private Integer resultCode;
  129 + /**
  130 + * 结果提示
  131 + *
  132 + * 目前使用的 {@link CommonResult#getMsg()} 属性
  133 + */
  134 + private String resultMsg;
  135 + /**
  136 + * 结果数据
  137 + *
  138 + * 如果是对象,则使用 JSON 格式化
  139 + */
  140 + private String resultData;
92 141  
93 142 }
... ...
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;
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6   -import cn.iocoder.yudao.module.system.controller.admin.logger.vo.operatelog.OperateLogPageReqVO;
  6 +import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2PageReqDTO;
7 7 import cn.iocoder.yudao.module.system.dal.dataobject.logger.OperateLogV2DO;
8 8 import org.apache.ibatis.annotations.Mapper;
9 9  
10   -import java.util.Collection;
11   -import java.util.List;
12   -
13 10 @Mapper
14 11 public interface OperateLogV2Mapper extends BaseMapperX<OperateLogV2DO> {
15 12  
16   - default PageResult<OperateLogV2DO> selectPage(OperateLogPageReqVO reqVO, Collection<Long> userIds) {
17   - LambdaQueryWrapperX<OperateLogV2DO> query = new LambdaQueryWrapperX<OperateLogV2DO>()
18   - .likeIfPresent(OperateLogV2DO::getModule, reqVO.getModule())
19   - .inIfPresent(OperateLogV2DO::getUserId, userIds);
20   - query.orderByDesc(OperateLogV2DO::getId); // 降序
21   - return selectPage(reqVO, query);
22   - }
23   -
24   - default List<OperateLogV2DO> selectListByModuleAndBizId(String module, Long bizId) {
25   - return selectList(new LambdaQueryWrapperX<OperateLogV2DO>()
26   - .eq(OperateLogV2DO::getModule, module)
27   - .eq(OperateLogV2DO::getBizId, bizId)
  13 + default PageResult<OperateLogV2DO> selectPage(OperateLogV2PageReqDTO pageReqVO) {
  14 + return selectPage(pageReqVO, new LambdaQueryWrapperX<OperateLogV2DO>()
  15 + .eqIfPresent(OperateLogV2DO::getType, pageReqVO.getBizType())
  16 + .eqIfPresent(OperateLogV2DO::getBizId, pageReqVO.getBizId())
  17 + .eqIfPresent(OperateLogV2DO::getUserId, pageReqVO.getUserId())
28 18 .orderByDesc(OperateLogV2DO::getCreateTime));
29 19 }
30 20  
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/bizlog/config/YudaoOperateLogV2Configuration.java deleted
1   -package cn.iocoder.yudao.module.system.framework.bizlog.config;
2   -
3   -import com.mzt.logapi.starter.annotation.EnableLogRecord;
4   -import org.springframework.context.annotation.Configuration;
5   -
6   -// TODO @puhui999:挪到 yudao-spring-boot-starter-biz-operatelog 下,搞个 cn.iocoder.yudao.framework.operatelogv2;跑通后,我们直接就删除老的实现了;
7   -/**
8   - * mzt-biz-log 配置类
9   - *
10   - * @author HUIHUI
11   - */
12   -@Configuration(proxyBeanMethods = false)
13   -@EnableLogRecord(tenant = "") // 貌似用不上 tenant 这玩意给个空好啦
14   -public class YudaoOperateLogV2Configuration {
15   -
16   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/bizlog/package-info.java deleted
1   -package cn.iocoder.yudao.module.system.framework.bizlog;
2 0 \ No newline at end of file
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/bizlog/service/ILogRecordServiceImpl.java deleted
1   -package cn.iocoder.yudao.module.system.framework.bizlog.service;
2   -
3   -import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
4   -import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
5   -import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
6   -import cn.iocoder.yudao.module.system.service.logger.OperateLogService;
7   -import cn.iocoder.yudao.module.system.service.logger.bo.OperateLogV2CreateReqBO;
8   -import com.mzt.logapi.beans.LogRecord;
9   -import com.mzt.logapi.service.ILogRecordService;
10   -import jakarta.annotation.Resource;
11   -import jakarta.servlet.http.HttpServletRequest;
12   -import lombok.extern.slf4j.Slf4j;
13   -import org.springframework.stereotype.Service;
14   -
15   -import java.util.Collections;
16   -import java.util.List;
17   -
18   -// TODO @puhui999:这个应该搞到 operatelog 组件里哈;
19   -/**
20   - * 操作日志 ILogRecordService 实现类
21   - *
22   - * 基于 {@link OperateLogService} 实现,记录操作日志
23   - *
24   - * @author HUIHUI
25   - */
26   -@Slf4j
27   -@Service
28   -public class ILogRecordServiceImpl implements ILogRecordService {
29   -
30   - @Resource
31   - private OperateLogService operateLogService;
32   -
33   - @Override
34   - public void record(LogRecord logRecord) {
35   - OperateLogV2CreateReqBO reqBO = new OperateLogV2CreateReqBO();
36   - // 补全通用字段
37   - reqBO.setTraceId(TracerUtils.getTraceId());
38   - // 补充用户信息
39   - fillUserFields(reqBO);
40   - // 补全模块信息
41   - fillModuleFields(reqBO, logRecord);
42   - // 补全请求信息
43   - fillRequestFields(reqBO);
44   - // 异步记录日志
45   - operateLogService.createOperateLogV2(reqBO);
46   - log.info("操作日志 ===> {}", reqBO);
47   - }
48   -
49   - private static void fillUserFields(OperateLogV2CreateReqBO reqBO) {
50   - reqBO.setUserId(WebFrameworkUtils.getLoginUserId());
51   - reqBO.setUserType(WebFrameworkUtils.getLoginUserType());
52   - }
53   -
54   - public static void fillModuleFields(OperateLogV2CreateReqBO reqBO, LogRecord logRecord) {
55   - reqBO.setModule(logRecord.getType()); // 大模块类型如 crm-客户
56   - reqBO.setName(logRecord.getSubType());// 操作名称如 转移客户
57   - reqBO.setBizId(Long.parseLong(logRecord.getBizNo())); // 操作模块业务编号
58   - reqBO.setContent(logRecord.getAction());// 例如说,修改编号为 1 的用户信息,将性别从男改成女,将姓名从芋道改成源码。
59   - reqBO.setExtra(logRecord.getExtra()); // 拓展字段,有些复杂的业务,需要记录一些字段 ( JSON 格式 ),例如说,记录订单编号,{ orderId: "1"}
60   - }
61   -
62   - private static void fillRequestFields(OperateLogV2CreateReqBO reqBO) {
63   - // 获得 Request 对象
64   - HttpServletRequest request = ServletUtils.getRequest();
65   - if (request == null) {
66   - return;
67   - }
68   - // 补全请求信息
69   - reqBO.setRequestMethod(request.getMethod());
70   - reqBO.setRequestUrl(request.getRequestURI());
71   - reqBO.setUserIp(ServletUtils.getClientIP(request));
72   - reqBO.setUserAgent(ServletUtils.getUserAgent(request));
73   - }
74   -
75   - @Override
76   - public List<LogRecord> queryLog(String bizNo, String type) {
77   - return Collections.emptyList();
78   - }
79   -
80   - @Override
81   - public List<LogRecord> queryLogByBizNo(String bizNo, String type, String subType) {
82   - return Collections.emptyList();
83   - }
84   -
85   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/operatelog/package-info.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.framework.operatelog;
0 2 \ No newline at end of file
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/bizlog/function/AdminUserParseFunction.java renamed to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/operatelog/parse/AdminUserParseFunction.java
1   -package cn.iocoder.yudao.module.system.framework.bizlog.function;
  1 +package cn.iocoder.yudao.module.system.framework.operatelog.parse;
2 2  
3 3 import cn.hutool.core.util.ObjUtil;
4 4 import cn.hutool.core.util.StrUtil;
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/bizlog/function/AreaParseFunction.java renamed to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/operatelog/parse/AreaParseFunction.java
1   -package cn.iocoder.yudao.module.system.framework.bizlog.function;
  1 +package cn.iocoder.yudao.module.system.framework.operatelog.parse;
2 2  
3 3 import cn.hutool.core.util.StrUtil;
4 4 import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
... ...
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;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogCreateReqDTO;
  5 +import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2CreateReqDTO;
  6 +import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2PageReqDTO;
5 7 import cn.iocoder.yudao.module.system.controller.admin.logger.vo.operatelog.OperateLogPageReqVO;
6 8 import cn.iocoder.yudao.module.system.dal.dataobject.logger.OperateLogDO;
7 9 import cn.iocoder.yudao.module.system.dal.dataobject.logger.OperateLogV2DO;
8   -import cn.iocoder.yudao.module.system.service.logger.bo.OperateLogV2CreateReqBO;
9   -
10   -import java.util.List;
11 10  
12 11 /**
13 12 * 操作日志 Service 接口
... ... @@ -38,16 +37,14 @@ public interface OperateLogService {
38 37 *
39 38 * @param createReqBO 创建请求
40 39 */
41   - void createOperateLogV2(OperateLogV2CreateReqBO createReqBO);
  40 + void createOperateLogV2(OperateLogV2CreateReqDTO createReqBO);
42 41  
43   - // TODO @puhui999:module 改成 type
44 42 /**
45   - * 获取指定模块的指定数据的操作日志
  43 + * 获得操作日志分页列表
46 44 *
47   - * @param module 操作模块
48   - * @param bizId 操作模块编号
49   - * @return 操作日志
  45 + * @param pageReqVO 分页条件
  46 + * @return 操作日志分页列表
50 47 */
51   - List<OperateLogV2DO> getOperateLogByModuleAndBizId(String module, Long bizId);
  48 + PageResult<OperateLogV2DO> getOperateLogPage(OperateLogV2PageReqDTO pageReqVO);
52 49  
53 50 }
... ...
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;
6 6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
7 7 import cn.iocoder.yudao.framework.common.util.string.StrUtils;
8 8 import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogCreateReqDTO;
  9 +import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2CreateReqDTO;
  10 +import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2PageReqDTO;
9 11 import cn.iocoder.yudao.module.system.controller.admin.logger.vo.operatelog.OperateLogPageReqVO;
10 12 import cn.iocoder.yudao.module.system.dal.dataobject.logger.OperateLogDO;
11 13 import cn.iocoder.yudao.module.system.dal.dataobject.logger.OperateLogV2DO;
12 14 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
13 15 import cn.iocoder.yudao.module.system.dal.mysql.logger.OperateLogMapper;
14 16 import cn.iocoder.yudao.module.system.dal.mysql.logger.OperateLogV2Mapper;
15   -import cn.iocoder.yudao.module.system.service.logger.bo.OperateLogV2CreateReqBO;
16 17 import cn.iocoder.yudao.module.system.service.user.AdminUserService;
17 18 import jakarta.annotation.Resource;
18 19 import lombok.extern.slf4j.Slf4j;
... ... @@ -20,7 +21,6 @@ import org.springframework.stereotype.Service;
20 21 import org.springframework.validation.annotation.Validated;
21 22  
22 23 import java.util.Collection;
23   -import java.util.List;
24 24  
25 25 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
26 26 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 {
69 69 // ======================= LOG V2 =======================
70 70  
71 71 @Override
72   - public void createOperateLogV2(OperateLogV2CreateReqBO createReqBO) {
  72 + public void createOperateLogV2(OperateLogV2CreateReqDTO createReqBO) {
73 73 OperateLogV2DO log = BeanUtils.toBean(createReqBO, OperateLogV2DO.class);
  74 + log.setJavaMethodArgs(StrUtils.maxLength(log.getJavaMethodArgs(), JAVA_METHOD_ARGS_MAX_LENGTH));
  75 + log.setResultData(StrUtils.maxLength(log.getResultData(), RESULT_MAX_LENGTH));
74 76 operateLogV2Mapper.insert(log);
75 77 }
76 78  
  79 +
77 80 @Override
78   - public List<OperateLogV2DO> getOperateLogByModuleAndBizId(String module, Long bizId) {
79   - return operateLogV2Mapper.selectListByModuleAndBizId(module, bizId);
  81 + public PageResult<OperateLogV2DO> getOperateLogPage(OperateLogV2PageReqDTO pageReqVO) {
  82 + return operateLogV2Mapper.selectPage(pageReqVO);
80 83 }
81 84  
82 85 }
... ...
yudao-server/src/main/resources/application-local.yaml
... ... @@ -188,6 +188,7 @@ logging:
188 188 cn.iocoder.yudao.module.trade.dal.mysql: debug
189 189 cn.iocoder.yudao.module.promotion.dal.mysql: debug
190 190 cn.iocoder.yudao.module.statistics.dal.mysql: debug
  191 + cn.iocoder.yudao.module.crm.dal.mysql: debug
191 192  
192 193 debug: false
193 194  
... ...
yudao-server/src/main/resources/application.yaml
... ... @@ -79,7 +79,17 @@ mybatis-plus:
79 79 password: XDV71a+xqStEA3WH # 加解密的秘钥,可使用 https://www.imaegoo.com/2020/aes-key-generator/ 网站生成
80 80  
81 81 mybatis-plus-join:
82   - banner: false # 关闭控制台的 Banner 打印
  82 + #是否打印 mybatis plus join banner 默认true
  83 + banner: false
  84 + #全局启用副表逻辑删除(默认true) 关闭后关联查询不会加副表逻辑删除
  85 + sub-table-logic: true
  86 + #拦截器MappedStatement缓存(默认true)
  87 + ms-cache: true
  88 + #表别名(默认 t)
  89 + table-alias: t
  90 + #副表逻辑删除条件的位置,支持where、on
  91 + #默认ON (1.4.7.2及之前版本默认为where)
  92 + logic-del-type: on
83 93  
84 94 # Spring Data Redis 配置
85 95 spring:
... ...