系统操作日志:集成 mzt-biz-log

This commit is contained in:
puhui999 2023-12-12 22:29:40 +08:00
parent 72cebfca14
commit c5cc818a49
14 changed files with 399 additions and 6 deletions

View File

@ -62,6 +62,7 @@
<jsch.version>0.1.55</jsch.version>
<tika-core.version>2.9.1</tika-core.version>
<ip2region.version>2.7.0</ip2region.version>
<bizlog-sdk.version>3.0.6</bizlog-sdk.version>
<!-- 三方云服务相关 -->
<okio.version>3.5.0</okio.version>
<okhttp3.version>4.11.0</okhttp3.version>
@ -99,6 +100,17 @@
<artifactId>yudao-spring-boot-starter-biz-operatelog</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>io.github.mouzt</groupId>
<artifactId>bizlog-sdk</artifactId>
<version>${bizlog-sdk.version}</version>
<exclusions>
<exclusion> <!-- 排除掉springboot依赖使用项目的 -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-biz-dict</artifactId>

View File

@ -0,0 +1,18 @@
package cn.iocoder.yudao.module.crm.enums;
/**
* CRM 操作日志枚举
*
* @author HUIHUI
*/
public interface LogRecordConstants {
String WHO = "【{getAdminUserById{#userId}}】";
//======================= 客户转移操作日志 =======================
String TRANSFER_CUSTOMER_LOG_TYPE = "客户转移";
String TRANSFER_CUSTOMER_LOG_SUCCESS = WHO + "把客户【{{#crmCustomer.name}}】负责人【{getAdminUserById{#crmCustomer.ownerUserId}}】转移给了【{getAdminUserById{#reqVO.newOwnerUserId}}】";
String TRANSFER_CUSTOMER_LOG_FAIL = "";
}

View File

@ -1,6 +1,13 @@
### 请求 /update
GET {{baseUrl}}/crm/customer/page?pageNo=1&pageSize=10&name="张三"
### 请求 /transfer
PUT {{baseUrl}}/crm/customer/transfer
Content-Type: application/json
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
{
"id": 11,
"newOwnerUserId": 127,
"oldOwnerPermissionLevel": 2
}

View File

@ -17,14 +17,14 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.mapstruct.ap.internal.util.Collections;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import java.io.IOException;
import java.util.List;
import java.util.Map;

View File

@ -9,12 +9,15 @@ import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerUpdat
import cn.iocoder.yudao.module.crm.convert.customer.CrmCustomerConvert;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
import cn.iocoder.yudao.module.crm.dal.mysql.customer.CrmCustomerMapper;
import cn.iocoder.yudao.module.crm.enums.LogRecordConstants;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission;
import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import com.mzt.logapi.context.LogRecordContext;
import com.mzt.logapi.starter.annotation.LogRecord;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -126,11 +129,14 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
@Override
@Transactional(rollbackFor = Exception.class)
@LogRecord(success = LogRecordConstants.TRANSFER_CUSTOMER_LOG_SUCCESS,
type = LogRecordConstants.TRANSFER_CUSTOMER_LOG_TYPE, bizNo = "{{#reqVO.id}}")
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#reqVO.id", level = CrmPermissionLevelEnum.OWNER)
public void transferCustomer(CrmCustomerTransferReqVO reqVO, Long userId) {
// 1. 校验客户是否存在
validateCustomer(reqVO.getId());
LogRecordContext.putVariable("crmCustomer", customerMapper.selectById(reqVO.getId()));
// 2.1 数据权限转移
crmPermissionService.transferPermission(
CrmCustomerConvert.INSTANCE.convert(reqVO, userId).setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType()));

View File

@ -22,6 +22,13 @@
<artifactId>yudao-common</artifactId>
</dependency>
<!-- Springboot-注解-通用操作日志组件 -->
<!-- 此组件解决的问题是: 「谁」在「什么时间」对「什么」做了「什么事」 -->
<dependency>
<groupId>io.github.mouzt</groupId>
<artifactId>bizlog-sdk</artifactId>
</dependency>
<!-- 参数校验 -->
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@ -0,0 +1,144 @@
package cn.iocoder.yudao.module.system.dal.dataobject.logger;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
import java.util.Map;
/**
* 操作日志表
*
* @author 芋道源码
*/
@TableName(value = "system_operate_log_v2", autoResultMap = true)
@KeySequence("system_operate_log_seq_v2") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写
@Data
@EqualsAndHashCode(callSuper = true)
public class OperateLogV2DO extends BaseDO {
/**
* {@link #javaMethodArgs} 的最大长度
*/
public static final Integer JAVA_METHOD_ARGS_MAX_LENGTH = 8000;
/**
* {@link #resultData} 的最大长度
*/
public static final Integer RESULT_MAX_LENGTH = 4000;
/**
* 日志主键
*/
@TableId
private Long id;
/**
* 链路追踪编号
*
* 一般来说通过链路追踪编号可以将访问日志错误日志链路追踪日志logger 打印日志等结合在一起从而进行排错
*/
private String traceId;
/**
* 用户编号
*
* 关联 MemberUserDO id 属性或者 AdminUserDO id 属性
*/
private Long userId;
/**
* 用户类型
*
* 关联 {@link UserTypeEnum}
*/
private Integer userType;
/**
* 操作模块
*/
private String module;
/**
* 操作名
*/
private String name;
/**
* 操作分类
*
* 枚举 {@link OperateTypeEnum}
*/
private Integer type;
/**
* 操作内容记录整个操作的明细
* 例如说修改编号为 1 的用户信息将性别从男改成女将姓名从芋道改成源码
*/
private String content;
/**
* 拓展字段有些复杂的业务需要记录一些字段
* 例如说记录订单编号则可以添加 key "orderId"value 为订单编号
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private Map<String, Object> exts;
/**
* 请求方法名
*/
private String requestMethod;
/**
* 请求地址
*/
private String requestUrl;
/**
* 用户 IP
*/
private String userIp;
/**
* 浏览器 UA
*/
private String userAgent;
/**
* Java 方法名
*/
private String javaMethod;
/**
* Java 方法的参数
*
* 实际格式为 Map<String, Object>
* 不使用 @TableField(typeHandler = FastjsonTypeHandler.class) 注解的原因是数据库存储有长度限制会进行裁剪会导致 JSON 反序列化失败
* 其中key 为参数名value 为参数值
*/
private String javaMethodArgs;
/**
* 开始时间
*/
private LocalDateTime startTime;
/**
* 执行时长单位毫秒
*/
private Integer duration;
/**
* 结果码
*
* 目前使用的 {@link CommonResult#getCode()} 属性
*/
private Integer resultCode;
/**
* 结果提示
*
* 目前使用的 {@link CommonResult#getMsg()} 属性
*/
private String resultMsg;
/**
* 结果数据
*
* 如果是对象则使用 JSON 格式化
*/
private String resultData;
}

View File

@ -0,0 +1,31 @@
package cn.iocoder.yudao.module.system.dal.mysql.logger;
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.system.controller.admin.logger.vo.operatelog.OperateLogPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.logger.OperateLogV2DO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
@Mapper
public interface OperateLogV2Mapper extends BaseMapperX<OperateLogV2DO> {
default PageResult<OperateLogV2DO> selectPage(OperateLogPageReqVO reqVO, Collection<Long> userIds) {
LambdaQueryWrapperX<OperateLogV2DO> query = new LambdaQueryWrapperX<OperateLogV2DO>()
.likeIfPresent(OperateLogV2DO::getModule, reqVO.getModule())
.inIfPresent(OperateLogV2DO::getUserId, userIds)
.eqIfPresent(OperateLogV2DO::getType, reqVO.getType())
.betweenIfPresent(OperateLogV2DO::getStartTime, reqVO.getStartTime());
if (Boolean.TRUE.equals(reqVO.getSuccess())) {
query.eq(OperateLogV2DO::getResultCode, GlobalErrorCodeConstants.SUCCESS.getCode());
} else if (Boolean.FALSE.equals(reqVO.getSuccess())) {
query.gt(OperateLogV2DO::getResultCode, GlobalErrorCodeConstants.SUCCESS.getCode());
}
query.orderByDesc(OperateLogV2DO::getId); // 降序
return selectPage(reqVO, query);
}
}

View File

@ -0,0 +1,53 @@
package cn.iocoder.yudao.module.system.framework.bizlog.config;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import cn.iocoder.yudao.module.system.api.logger.OperateLogApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.framework.bizlog.service.AdminUserParseFunction;
import cn.iocoder.yudao.module.system.framework.bizlog.service.ILogRecordServiceImpl;
import com.mzt.logapi.beans.Operator;
import com.mzt.logapi.service.IOperatorGetService;
import com.mzt.logapi.starter.annotation.EnableLogRecord;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Optional;
/**
* 使用 @Configuration 是因为 mzt-biz-log 的配置类是 @Configuration
*
* @author HUIHUI
*/
@Configuration(proxyBeanMethods = false)
@EnableLogRecord(tenant = "${yudao.info.base-package}")
@Slf4j
public class YudaoOperateLogV2Configuration {
//======================= mzt-biz-log =======================
@Bean
public ILogRecordServiceImpl iLogRecordServiceImpl(OperateLogApi operateLogApi) {
log.info("ILogRecordServiceImpl 初始化");
return new ILogRecordServiceImpl(operateLogApi);
}
@Bean
public IOperatorGetService operatorGetLoginUserIdService() {
// 获取操作用户编号
return () -> Optional.of(WebFrameworkUtils.getLoginUserId())
.map(a -> {
Operator operator = new Operator();
operator.setOperatorId(a.toString());
return operator;
})
.orElseThrow(() -> new IllegalArgumentException("user is null"));
}
@Bean
public AdminUserParseFunction adminUserParseFunction(AdminUserApi adminUserApi) {
return new AdminUserParseFunction(adminUserApi);
}
}

View File

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

View File

@ -0,0 +1,56 @@
package cn.iocoder.yudao.module.system.framework.bizlog.service;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import com.mzt.logapi.service.IParseFunction;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* 自定义函数-通过用户编号获取用户信息
*
* @author HUIHUI
*/
@Slf4j
@RequiredArgsConstructor
public class AdminUserParseFunction implements IParseFunction {
private final AdminUserApi adminUserApi;
@Override
public boolean executeBefore() {
return true;
}
@Override
public String functionName() {
return "getAdminUserById";
}
@Override
public String apply(Object value) {
if (value == null) {
log.warn("(getAdminUserById) 解析异常参数为 null");
return "";
}
if (StrUtil.isEmpty(value.toString())) {
log.warn("(getAdminUserById) 解析异常参数为空");
return "";
}
// 获取用户信息
AdminUserRespDTO user = adminUserApi.getUser(Long.parseLong(value.toString()));
if (user == null) {
log.warn("(getAdminUserById) 获取用户信息失败,参数为:{}", value);
return "";
}
// 返回格式 芋道源码(13888888888)
String nickname = user.getNickname();
if (ObjUtil.isNotEmpty(user.getMobile())) {
return nickname.concat("(").concat(user.getMobile()).concat(")");
}
return nickname;
}
}

View File

@ -0,0 +1,39 @@
package cn.iocoder.yudao.module.system.framework.bizlog.service;
import cn.iocoder.yudao.module.system.api.logger.OperateLogApi;
import com.mzt.logapi.beans.LogRecord;
import com.mzt.logapi.service.ILogRecordService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.util.Collections;
import java.util.List;
/**
* 操作日志 ILogRecordService 实现类
*
* 基于 {@link OperateLogApi} 实现记录操作日志
*
* @author HUIHUI
*/
@Slf4j
@RequiredArgsConstructor
public class ILogRecordServiceImpl implements ILogRecordService {
private final OperateLogApi operateLogApi;
@Override
public void record(LogRecord logRecord) {
log.info("【logRecord】log={}", logRecord);
}
@Override
public List<LogRecord> queryLog(String bizNo, String type) {
return Collections.emptyList();
}
@Override
public List<LogRecord> queryLogByBizNo(String bizNo, String type, String subType) {
return Collections.emptyList();
}
}

View File

@ -19,6 +19,13 @@ public interface OperateLogService {
*/
void createOperateLog(OperateLogCreateReqDTO createReqDTO);
/**
* 记录操作日志 V2
*
* @param createReqDTO 操作日志请求
*/
void createOperateLogV2(OperateLogCreateReqDTO createReqDTO);
/**
* 获得操作日志分页列表
*

View File

@ -8,14 +8,16 @@ import cn.iocoder.yudao.framework.common.util.string.StrUtils;
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogCreateReqDTO;
import cn.iocoder.yudao.module.system.controller.admin.logger.vo.operatelog.OperateLogPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.logger.OperateLogDO;
import cn.iocoder.yudao.module.system.dal.dataobject.logger.OperateLogV2DO;
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
import cn.iocoder.yudao.module.system.dal.mysql.logger.OperateLogMapper;
import cn.iocoder.yudao.module.system.dal.mysql.logger.OperateLogV2Mapper;
import cn.iocoder.yudao.module.system.service.user.AdminUserService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import jakarta.annotation.Resource;
import java.util.Collection;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
@ -34,6 +36,8 @@ public class OperateLogServiceImpl implements OperateLogService {
@Resource
private OperateLogMapper operateLogMapper;
@Resource
private OperateLogV2Mapper operateLogV2Mapper;
@Resource
private AdminUserService userService;
@ -46,6 +50,14 @@ public class OperateLogServiceImpl implements OperateLogService {
operateLogMapper.insert(log);
}
@Override
public void createOperateLogV2(OperateLogCreateReqDTO createReqDTO) {
OperateLogV2DO log = BeanUtils.toBean(createReqDTO, OperateLogV2DO.class);
log.setJavaMethodArgs(StrUtils.maxLength(log.getJavaMethodArgs(), JAVA_METHOD_ARGS_MAX_LENGTH));
log.setResultData(StrUtils.maxLength(log.getResultData(), RESULT_MAX_LENGTH));
operateLogV2Mapper.insert(log);
}
@Override
public PageResult<OperateLogDO> getOperateLogPage(OperateLogPageReqVO pageReqVO) {
// 处理基于用户昵称的查询