📖 code review 操作日志的实现

This commit is contained in:
YunaiV 2023-12-30 21:36:46 +08:00
parent 9c3cf1d6b6
commit 8c9b483ac5
20 changed files with 76 additions and 80 deletions

View File

@ -12,7 +12,10 @@
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>用户的认证、权限的校验</description>
<description>
1. security用户的认证、权限的校验实现「谁」可以做「什么事」
2. operatelog操作日志实现「谁」在「什么时间」对「什么」做了「什么事」
</description>
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
<dependencies>

View File

@ -8,9 +8,8 @@ import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
/**
* mzt-biz-log 配置类
* 操作日志配置类
*
* @author HUIHUI
*/

View File

@ -1 +1,4 @@
/**
* 占位无特殊作用
*/
package cn.iocoder.yudao.framework.operatelog.core;

View File

@ -11,9 +11,9 @@ import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import java.util.Collections;
import java.util.List;
// TODO @puhui999LogRecordServiceImpl 改成这个名字哈
/**
* 操作日志 ILogRecordService 实现类
*
@ -29,8 +29,8 @@ public class ILogRecordServiceImpl implements ILogRecordService {
@Override
public void record(LogRecord logRecord) {
// 1. 补全通用字段
OperateLogV2CreateReqDTO reqDTO = new OperateLogV2CreateReqDTO();
// 补全通用字段
reqDTO.setTraceId(TracerUtils.getTraceId());
// 补充用户信息
fillUserFields(reqDTO);
@ -38,22 +38,24 @@ public class ILogRecordServiceImpl implements ILogRecordService {
fillModuleFields(reqDTO, logRecord);
// 补全请求信息
fillRequestFields(reqDTO);
// 异步记录日志
// 2. 异步记录日志
operateLogApi.createOperateLogV2(reqDTO);
// TODO 测试结束删除或搞个开关
log.info("操作日志 ===> {}", reqDTO);
}
private static void fillUserFields(OperateLogV2CreateReqDTO reqDTO) {
// TODO @puhui999使用 SecurityFrameworkUtils因为要考虑rpcmqjob它其实不是 web
reqDTO.setUserId(WebFrameworkUtils.getLoginUserId());
reqDTO.setUserType(WebFrameworkUtils.getLoginUserType());
}
public static void fillModuleFields(OperateLogV2CreateReqDTO reqDTO, LogRecord logRecord) {
reqDTO.setType(logRecord.getType()); // 大模块类型 crm 客户
reqDTO.setSubType(logRecord.getSubType());// 操作名称 转移客户
reqDTO.setBizId(Long.parseLong(logRecord.getBizNo())); // 操作模块业务编号
reqDTO.setAction(logRecord.getAction());// 例如说修改编号为 1 的用户信息将性别从男改成女将姓名从芋道改成源码
reqDTO.setType(logRecord.getType()); // 大模块类型例如CRM 客户
reqDTO.setSubType(logRecord.getSubType());// 操作名称例如转移客户
reqDTO.setBizId(Long.parseLong(logRecord.getBizNo())); // 业务编号例如客户编号
reqDTO.setAction(logRecord.getAction());// 操作内容例如修改编号为 1 的用户信息将性别从男改成女将姓名从芋道改成源码
reqDTO.setExtra(logRecord.getExtra()); // 拓展字段有些复杂的业务需要记录一些字段 ( JSON 格式 )例如说记录订单编号{ orderId: "1"}
}
@ -72,12 +74,12 @@ public class ILogRecordServiceImpl implements ILogRecordService {
@Override
public List<LogRecord> queryLog(String bizNo, String type) {
return Collections.emptyList();
throw new UnsupportedOperationException("使用 OperateLogApi 进行操作日志的查询");
}
@Override
public List<LogRecord> queryLogByBizNo(String bizNo, String type, String subType) {
return Collections.emptyList();
throw new UnsupportedOperationException("使用 OperateLogApi 进行操作日志的查询");
}
}

View File

@ -0,0 +1,7 @@
/**
* 基于 mzt-log 框架
* 实现操作日志功能
*
* @author HUIHUI
*/
package cn.iocoder.yudao.framework.operatelog;

View File

@ -1,6 +1,5 @@
package cn.iocoder.yudao.module.crm.enums;
// TODO 芋艿操作日志看看这个类怎么搞个好点的规范
/**
* CRM 操作日志枚举
*
@ -23,4 +22,12 @@ public interface LogRecordConstants {
String TRANSFER_CUSTOMER_LOG_SUCCESS = "把客户【{{#crmCustomer.name}}】的负责人从【{getAdminUserById{#crmCustomer.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】";
// TODO @puhui999这里格式是不是可以这样;目的是统一管理也减少 Service 里各种复杂字符串
// ======================= Customer 客户 =======================
String CUSTOMER_TYPE = "CRM 客户";
String CUSTOMER_CREATE_SUB_TYPE = "创建客户";
String CUSTOMER_CREATE_SUCCESS = "更新了客户{_DIFF{#updateReqVO}}";
String CUSTOMER_UPDATE_SUB_TYPE = "更新客户";
}

View File

@ -60,7 +60,7 @@ public class CrmCustomerController {
@PostMapping("/create")
@Operation(summary = "创建客户")
@OperateLog(enable = false) // TODO 关闭原有日志记录
@OperateLog(enable = false) // TODO 关闭原有日志记录@puhui999注解都先删除先记录没关系我们下个迭代就都删除掉操作日志了
@PreAuthorize("@ss.hasPermission('crm:customer:create')")
public CommonResult<Long> createCustomer(@Valid @RequestBody CrmCustomerCreateReqVO createReqVO) {
return success(customerService.createCustomer(createReqVO, getLoginUserId()));
@ -102,6 +102,7 @@ public class CrmCustomerController {
return success(CrmCustomerConvert.INSTANCE.convert(customer, userMap, deptMap));
}
// TODO @puhui999这个查询会查出多个微信发你图了
@GetMapping("/page")
@Operation(summary = "获得客户分页")
@PreAuthorize("@ss.hasPermission('crm:customer:query')")
@ -141,6 +142,7 @@ public class CrmCustomerController {
return success(true);
}
// TODO @puhui999是不是接口只要传递 bizId Controller 自己组装出 OperateLogV2PageReqDTO
@GetMapping("/operate-log-page")
@Operation(summary = "获得客户操作日志")
@PreAuthorize("@ss.hasPermission('crm:customer:query')")

View File

@ -1,6 +1,5 @@
package cn.iocoder.yudao.module.crm.framework.operatelog.core;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
import com.mzt.logapi.service.IParseFunction;
@ -10,7 +9,7 @@ import org.springframework.stereotype.Component;
import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_INDUSTRY;
/**
* 自定义函数-通过行业编号获取行业信息
* 行业的 {@link IParseFunction} 实现类
*
* @author HUIHUI
*/
@ -30,14 +29,9 @@ public class CrmIndustryParseFunction implements IParseFunction {
@Override
public String apply(Object value) {
if (ObjUtil.isEmpty(value)) {
if (StrUtil.isEmptyIfStr(value)) {
return "";
}
if (StrUtil.isEmpty(value.toString())) {
return "";
}
// 获取行业信息
return DictFrameworkUtils.getDictDataLabel(CRM_CUSTOMER_INDUSTRY, value.toString());
}

View File

@ -1,6 +1,5 @@
package cn.iocoder.yudao.module.crm.framework.operatelog.core;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
import com.mzt.logapi.service.IParseFunction;
@ -10,7 +9,7 @@ import org.springframework.stereotype.Component;
import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_LEVEL;
/**
* 自定义函数-通过客户等级编号获取客户等级信息
* 客户等级的 {@link IParseFunction} 实现类
*
* @author HUIHUI
*/
@ -30,14 +29,9 @@ public class CrmLevelParseFunction implements IParseFunction {
@Override
public String apply(Object value) {
if (ObjUtil.isEmpty(value)) {
if (StrUtil.isEmptyIfStr(value)) {
return "";
}
if (StrUtil.isEmpty(value.toString())) {
return "";
}
// 获取客户等级信息
return DictFrameworkUtils.getDictDataLabel(CRM_CUSTOMER_LEVEL, value.toString());
}

View File

@ -1,6 +1,5 @@
package cn.iocoder.yudao.module.crm.framework.operatelog.core;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
import com.mzt.logapi.service.IParseFunction;
@ -10,7 +9,7 @@ import org.springframework.stereotype.Component;
import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_SOURCE;
/**
* 自定义函数-通过客户来源编号获取客户来源信息
* 客户来源的 {@link IParseFunction} 实现类
*
* @author HUIHUI
*/
@ -30,14 +29,9 @@ public class CrmSourceParseFunction implements IParseFunction {
@Override
public String apply(Object value) {
if (ObjUtil.isEmpty(value)) {
if (StrUtil.isEmptyIfStr(value)) {
return "";
}
if (StrUtil.isEmpty(value.toString())) {
return "";
}
// 获取客户来源信息
return DictFrameworkUtils.getDictDataLabel(CRM_CUSTOMER_SOURCE, value.toString());
}

View File

@ -13,7 +13,6 @@ import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUti
*/
public class CrmPermissionUtils {
/**
* 校验用户是否是 CRM 管理员
*

View File

@ -62,7 +62,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
crmPermissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType())
.setBizId(customer.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 设置当前操作的人为负责人
// 添加日志上下文所需
// 记录操作日志
LogRecordContext.putVariable("customerId", customer.getId());
return customer.getId();
}
@ -73,15 +73,15 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE)
public void updateCustomer(CrmCustomerUpdateReqVO updateReqVO) {
// 校验存在
CrmCustomerDO oldCustomerDO = validateCustomerExists(updateReqVO.getId());
CrmCustomerDO oldCustomer = validateCustomerExists(updateReqVO.getId());
// 更新
CrmCustomerDO updateObj = CrmCustomerConvert.INSTANCE.convert(updateReqVO);
customerMapper.updateById(updateObj);
// __DIFF 函数传递了一个参数传递的参数是修改之后的对象这种方式需要在方法内部向 LogRecordContext put 一个变量代表是之前的对象这个对象可以是null
LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldCustomerDO, CrmCustomerUpdateReqVO.class));
// TODO 扩展信息测试
// 记录操作日志
LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldCustomer, CrmCustomerUpdateReqVO.class));
// TODO 扩展信息测试 @puhui999看着没啥问题可以删除啦
HashMap<String, Object> extra = new HashMap<>();
extra.put("tips", "随便记录一点啦");
LogRecordContext.putVariable("extra", extra);

View File

@ -90,7 +90,8 @@ public class CrmPermissionServiceImpl implements CrmPermissionService {
CrmPermissionDO oldPermission = crmPermissionMapper.selectByBizTypeAndBizIdByUserId(
transferReqBO.getBizType(), transferReqBO.getBizId(), transferReqBO.getUserId());
String bizTypeName = CrmBizTypeEnum.getNameByType(transferReqBO.getBizType());
if (oldPermission == null || (!isOwner(oldPermission.getLevel()) && !CrmPermissionUtils.isCrmAdmin())) { // 不是拥有者并且不是超管
if (oldPermission == null // 不是拥有者并且不是超管
|| (!isOwner(oldPermission.getLevel()) && !CrmPermissionUtils.isCrmAdmin())) {
throw exception(CRM_PERMISSION_DENIED, bizTypeName);
}
// 1.1 校验转移对象是否已经是该负责人

View File

@ -40,7 +40,7 @@ public class CrmQueryWrapperUtils {
Long userId, Integer sceneType, Boolean pool) {
final String ownerUserIdField = SingletonManager.getMybatisPlusJoinProperties().getTableAlias() + ".owner_user_id";
// 1. 构建数据权限连表条件
if (ObjUtil.notEqual(CrmPermissionUtils.isCrmAdmin(), Boolean.TRUE) && ObjUtil.notEqual(pool, Boolean.TRUE)) { // 管理员公海不需要数据权限
if (!CrmPermissionUtils.isCrmAdmin() && ObjUtil.notEqual(pool, Boolean.TRUE)) { // 管理员公海不需要数据权限
query.innerJoin(CrmPermissionDO.class, on -> on.eq(CrmPermissionDO::getBizType, bizType)
.eq(CrmPermissionDO::getBizId, bizId) // 只能使用 SFunction 如果传 id 解析出来的 sql 不对
.eq(CrmPermissionDO::getUserId, userId));
@ -81,7 +81,7 @@ public class CrmQueryWrapperUtils {
* @param userId 用户编号
*/
public static <T extends MPJLambdaWrapper<?>> void appendPermissionCondition(T query, Integer bizType, Collection<Long> bizIds, Long userId) {
if (ObjUtil.equal(CrmPermissionUtils.isCrmAdmin(), Boolean.TRUE)) {// 管理员不需要数据权限
if (CrmPermissionUtils.isCrmAdmin()) {// 管理员不需要数据权限
return;
}

View File

@ -53,7 +53,8 @@ public class OperateLogApiImpl implements OperateLogApi {
}
// 获取用户
List<AdminUserDO> userList = adminUserService.getUserList(convertSet(operateLogPage.getList(), OperateLogV2DO::getUserId));
List<AdminUserDO> userList = adminUserService.getUserList(
convertSet(operateLogPage.getList(), OperateLogV2DO::getUserId));
return OperateLogConvert.INSTANCE.convertPage(operateLogPage, userList);
}

View File

@ -10,12 +10,12 @@ import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface OperateLogV2Mapper extends BaseMapperX<OperateLogV2DO> {
default PageResult<OperateLogV2DO> selectPage(OperateLogV2PageReqDTO pageReqVO) {
return selectPage(pageReqVO, new LambdaQueryWrapperX<OperateLogV2DO>()
.eqIfPresent(OperateLogV2DO::getType, pageReqVO.getBizType())
.eqIfPresent(OperateLogV2DO::getBizId, pageReqVO.getBizId())
.eqIfPresent(OperateLogV2DO::getUserId, pageReqVO.getUserId())
.orderByDesc(OperateLogV2DO::getCreateTime));
default PageResult<OperateLogV2DO> selectPage(OperateLogV2PageReqDTO pageReqDTO) {
return selectPage(pageReqDTO, new LambdaQueryWrapperX<OperateLogV2DO>()
.eqIfPresent(OperateLogV2DO::getType, pageReqDTO.getBizType())
.eqIfPresent(OperateLogV2DO::getBizId, pageReqDTO.getBizId())
.eqIfPresent(OperateLogV2DO::getUserId, pageReqDTO.getUserId())
.orderByDesc(OperateLogV2DO::getId));
}
}

View File

@ -1,6 +1,5 @@
package cn.iocoder.yudao.module.system.framework.operatelog.core;
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;
@ -10,7 +9,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 自定义函数-通过用户编号获取用户信息
* 管理员名字的 {@link IParseFunction} 实现类
*
* @author HUIHUI
*/
@ -28,25 +27,22 @@ public class AdminUserParseFunction implements IParseFunction {
@Override
public String apply(Object value) {
if (ObjUtil.isEmpty(value)) {
return "";
}
if (StrUtil.isEmpty(value.toString())) {
if (StrUtil.isEmptyIfStr(value)) {
return "";
}
// 获取用户信息
AdminUserRespDTO user = adminUserApi.getUser(Long.parseLong(value.toString()));
if (user == null) {
log.warn("(getAdminUserById) 获取用户信息失败,参数为:{}", value);
log.warn("[apply][获取用户{{}}为空", value);
return "";
}
// 返回格式 芋道源码(13888888888)
String nickname = user.getNickname();
if (ObjUtil.isNotEmpty(user.getMobile())) {
return nickname.concat("(").concat(user.getMobile()).concat(")");
}
if (StrUtil.isEmpty(user.getMobile())) {
return nickname;
}
return StrUtil.format("{}({})", nickname, user.getMobile());
}
}

View File

@ -1,6 +1,5 @@
package cn.iocoder.yudao.module.system.framework.operatelog.core;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
import com.mzt.logapi.service.IParseFunction;
@ -8,7 +7,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 自定义函数-通过区域编号获取区域信息
* 地名的 {@link IParseFunction} 实现类
*
* @author HUIHUI
*/
@ -28,13 +27,9 @@ public class AreaParseFunction implements IParseFunction {
@Override
public String apply(Object value) {
if (ObjUtil.isEmpty(value)) {
if (StrUtil.isEmptyIfStr(value)) {
return "";
}
if (StrUtil.isEmpty(value.toString())) {
return "";
}
return AreaUtils.format(Integer.parseInt(value.toString()));
}

View File

@ -35,9 +35,9 @@ public interface OperateLogService {
/**
* 记录操作日志 V2
*
* @param createReqBO 创建请求
* @param createReqDTO 创建请求
*/
void createOperateLogV2(OperateLogV2CreateReqDTO createReqBO);
void createOperateLogV2(OperateLogV2CreateReqDTO createReqDTO);
/**
* 获得操作日志分页列表

View File

@ -69,15 +69,14 @@ public class OperateLogServiceImpl implements OperateLogService {
// ======================= LOG V2 =======================
@Override
public void createOperateLogV2(OperateLogV2CreateReqDTO createReqBO) {
OperateLogV2DO log = BeanUtils.toBean(createReqBO, OperateLogV2DO.class);
public void createOperateLogV2(OperateLogV2CreateReqDTO createReqDTO) {
OperateLogV2DO log = BeanUtils.toBean(createReqDTO, OperateLogV2DO.class);
operateLogV2Mapper.insert(log);
}
@Override
public PageResult<OperateLogV2DO> getOperateLogPage(OperateLogV2PageReqDTO pageReqVO) {
return operateLogV2Mapper.selectPage(pageReqVO);
public PageResult<OperateLogV2DO> getOperateLogPage(OperateLogV2PageReqDTO pageReqDTO) {
return operateLogV2Mapper.selectPage(pageReqDTO);
}
}