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

This commit is contained in:
puhui999 2023-12-13 15:18:19 +08:00
parent 13d6c42a48
commit c74881c8f0
11 changed files with 145 additions and 94 deletions

View File

@ -7,12 +7,21 @@ package cn.iocoder.yudao.module.crm.enums;
*/
public interface LogRecordConstants {
String WHO = "【{getAdminUserById{#userId}}】";
//======================= 客户模块类型 =======================
// TODO puhui999: 确保模块命名方式为 module + 子模块名称的方式统一定义模块名称是为了方便查询各自记录的操作日志列如说查询客户张三的操作日志就可以 module + bizId
String CRM_LEADS = "CRM-线索";
String CRM_CUSTOMER = "CRM-客户";
String CRM_CONTACT = "CRM-联系人";
String CRM_BUSINESS = "CRM-商机";
String CRM_CONTRACT = "CRM-合同";
String CRM_PRODUCT = "CRM-产品";
String CRM_RECEIVABLE = "CRM-回款";
String CRM_RECEIVABLE_PLAN = "CRM-回款计划";
//======================= 客户转移操作日志 =======================
String TRANSFER_CUSTOMER_LOG_TYPE = "客户转移";
String TRANSFER_CUSTOMER_LOG_SUCCESS = WHO + "把客户【{{#crmCustomer.name}}】负责人【{getAdminUserById{#crmCustomer.ownerUserId}}】转移给了【{getAdminUserById{#reqVO.newOwnerUserId}}】";
String TRANSFER_CUSTOMER_LOG_SUCCESS = "把客户【{{#crmCustomer == null ? '' : #crmCustomer.name}}】负责人从" +
"【{getAdminUserById{#crmCustomer == null ? '' : #crmCustomer.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】";
String TRANSFER_CUSTOMER_LOG_FAIL = "";
}

View File

@ -5,9 +5,8 @@ Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
{
"id": 11,
"newOwnerUserId": 127,
"oldOwnerPermissionLevel": 2
"id": 10,
"newOwnerUserId": 127
}

View File

@ -123,7 +123,6 @@ public class CrmCustomerController {
}
@PutMapping("/transfer")
@Operation(summary = "客户转移")
@PreAuthorize("@ss.hasPermission('crm:customer:update')")
public CommonResult<Boolean> transfer(@Valid @RequestBody CrmCustomerTransferReqVO reqVO) {
customerService.transferCustomer(reqVO, getLoginUserId());

View File

@ -9,7 +9,6 @@ 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;
@ -27,6 +26,8 @@ import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.CRM_CUSTOMER;
import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.TRANSFER_CUSTOMER_LOG_SUCCESS;
import static java.util.Collections.singletonList;
/**
@ -129,8 +130,7 @@ 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}}")
@LogRecord(success = TRANSFER_CUSTOMER_LOG_SUCCESS, type = CRM_CUSTOMER, subType = "客户转移", bizNo = "{{#reqVO.id}}")
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#reqVO.id", level = CrmPermissionLevelEnum.OWNER)
public void transferCustomer(CrmCustomerTransferReqVO reqVO, Long userId) {
// 1. 校验客户是否存在

View File

@ -1,22 +1,15 @@
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;
/**
* 操作日志表
* 操作日志表 V2
*
* @author 芋道源码
*/
@ -26,16 +19,6 @@ import java.util.Map;
@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;
/**
* 日志主键
*/
@ -68,22 +51,14 @@ public class OperateLogV2DO extends BaseDO {
*/
private String name;
/**
* 操作分类
*
* 枚举 {@link OperateTypeEnum}
* 操作模块业务编号
*/
private Integer type;
private Long bizId;
/**
* 操作内容记录整个操作的明细
* 例如说修改编号为 1 的用户信息将性别从男改成女将姓名从芋道改成源码
*/
private String content;
/**
* 拓展字段有些复杂的业务需要记录一些字段
* 例如说记录订单编号则可以添加 key "orderId"value 为订单编号
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private Map<String, Object> exts;
/**
* 请求方法名
@ -102,43 +77,4 @@ public class OperateLogV2DO extends BaseDO {
*/
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

@ -1,6 +1,5 @@
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;
@ -16,14 +15,7 @@ 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());
}
.inIfPresent(OperateLogV2DO::getUserId, userIds);
query.orderByDesc(OperateLogV2DO::getId); // 降序
return selectPage(reqVO, query);
}

View File

@ -3,14 +3,13 @@ package cn.iocoder.yudao.module.system.framework.bizlog.config;
import com.mzt.logapi.starter.annotation.EnableLogRecord;
import org.springframework.context.annotation.Configuration;
/**
*
* mzt-biz-log 配置类
*
* @author HUIHUI
*/
@Configuration(proxyBeanMethods = false)
@EnableLogRecord(tenant = "${yudao.info.base-package}")
@EnableLogRecord(tenant = "") // 貌似用不上 tenant 这玩意给个空好啦
public class YudaoOperateLogV2Configuration {
}

View File

@ -34,11 +34,11 @@ public class AdminUserParseFunction implements IParseFunction {
@Override
public String apply(Object value) {
if (value == null) {
log.warn("(getAdminUserById) 解析异常参数为 null");
//log.warn("(getAdminUserById) 解析异常参数为 null");
return "";
}
if (StrUtil.isEmpty(value.toString())) {
log.warn("(getAdminUserById) 解析异常参数为空");
//log.warn("(getAdminUserById) 解析异常参数为空");
return "";
}

View File

@ -1,9 +1,14 @@
package cn.iocoder.yudao.module.system.framework.bizlog.service;
import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import cn.iocoder.yudao.module.system.api.logger.OperateLogApi;
import cn.iocoder.yudao.module.system.service.logger.bo.OperateLogV2CreateReqBO;
import com.mzt.logapi.beans.LogRecord;
import com.mzt.logapi.service.ILogRecordService;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@ -26,7 +31,42 @@ public class ILogRecordServiceImpl implements ILogRecordService {
@Override
public void record(LogRecord logRecord) {
log.info("【logRecord】log={}", logRecord);
OperateLogV2CreateReqBO reqBO = new OperateLogV2CreateReqBO();
// 补全通用字段
reqBO.setTraceId(TracerUtils.getTraceId());
// 补充用户信息
fillUserFields(reqBO);
// 补全模块信息
fillModuleFields(reqBO, logRecord);
// 补全请求信息
fillRequestFields(reqBO);
// 异步记录日志
log.info("操作日志 ===> {}", reqBO);
}
private static void fillUserFields(OperateLogV2CreateReqBO reqBO) {
reqBO.setUserId(WebFrameworkUtils.getLoginUserId());
reqBO.setUserType(WebFrameworkUtils.getLoginUserType());
}
public static void fillModuleFields(OperateLogV2CreateReqBO reqBO, LogRecord logRecord) {
reqBO.setModule(logRecord.getType()); // 大模块类型如 crm-客户
reqBO.setName(logRecord.getSubType());// 操作名称如 转移客户
reqBO.setBizId(Long.parseLong(logRecord.getBizNo())); // 操作模块业务编号
reqBO.setContent(logRecord.getAction());// 例如说修改编号为 1 的用户信息将性别从男改成女将姓名从芋道改成源码
}
private static void fillRequestFields(OperateLogV2CreateReqBO reqBO) {
// 获得 Request 对象
HttpServletRequest request = ServletUtils.getRequest();
if (request == null) {
return;
}
// 补全请求信息
reqBO.setRequestMethod(request.getMethod());
reqBO.setRequestUrl(request.getRequestURI());
reqBO.setUserIp(ServletUtils.getClientIP(request));
reqBO.setUserAgent(ServletUtils.getUserAgent(request));
}
@Override
@ -38,4 +78,5 @@ public class ILogRecordServiceImpl implements ILogRecordService {
public List<LogRecord> queryLogByBizNo(String bizNo, String type, String subType) {
return Collections.emptyList();
}
}

View File

@ -53,8 +53,6 @@ public class OperateLogServiceImpl implements OperateLogService {
@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);
}

View File

@ -0,0 +1,78 @@
package cn.iocoder.yudao.module.system.service.logger.bo;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
/**
* 系统操作日志 Create Req BO
*
* @author HUIHUI
*/
@Data
public class OperateLogV2CreateReqBO {
/**
* 链路追踪编号
*
* 一般来说通过链路追踪编号可以将访问日志错误日志链路追踪日志logger 打印日志等结合在一起从而进行排错
*/
private String traceId;
/**
* 用户编号
*
* 关联 MemberUserDO id 属性或者 AdminUserDO id 属性
*/
@NotEmpty(message = "用户编号不能为空")
private Long userId;
/**
* 用户类型
*
* 关联 {@link UserTypeEnum}
*/
@NotEmpty(message = "用户类型不能为空")
private Integer userType;
/**
* 操作模块
*/
@NotEmpty(message = "操作模块不能为空")
private String module;
/**
* 操作名
*/
@NotEmpty(message = "操作名不能为空")
private String name;
/**
* 操作模块业务编号
*/
@NotEmpty(message = "操作模块业务编号不能为空")
private Long bizId;
/**
* 操作内容记录整个操作的明细
* 例如说修改编号为 1 的用户信息将性别从男改成女将姓名从芋道改成源码
*/
@NotEmpty(message = "操作内容不能为空")
private String content;
/**
* 请求方法名
*/
@NotEmpty(message = "请求方法名不能为空")
private String requestMethod;
/**
* 请求地址
*/
@NotEmpty(message = "请求地址不能为空")
private String requestUrl;
/**
* 用户 IP
*/
@NotEmpty(message = "用户 IP 不能为空")
private String userIp;
/**
* 浏览器 UA
*/
@NotEmpty(message = "浏览器 UA 不能为空")
private String userAgent;
}