📖 CRM:code review 客户管理的数据权限、操作权限

This commit is contained in:
YunaiV 2023-12-17 10:57:49 +08:00
parent bada32750c
commit ddb6fe7ec8
19 changed files with 63 additions and 64 deletions

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.crm.enums;
// TODO 芋艿操作日志看看这个类怎么搞个好点的规范
/**
* CRM 操作日志枚举
*
@ -9,18 +10,18 @@ public interface LogRecordConstants {
//======================= 客户模块类型 =======================
// 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 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_SUCCESS = "把客户【{{#crmCustomer.name}}】的负责人从【{getAdminUserById{#crmCustomer.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】";
String TRANSFER_CUSTOMER_LOG_FAIL = "";
String TRANSFER_CUSTOMER_LOG_FAIL = ""; // TODO @puhui999这个可以删除哈一般不搞失败的日志
}

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.crm.controller.admin.clue.vo;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
@ -13,19 +14,12 @@ public class CrmClueTransferReqVO {
@NotNull(message = "线索编号不能为空")
private Long id;
/**
* 新负责人的用户编号
*/
@Schema(description = "新负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
@NotNull(message = "新负责人的用户编号不能为空")
private Long newOwnerUserId;
/**
* 老负责人加入团队后的权限级别如果 null 说明移除
*
* 关联 {@link CrmPermissionLevelEnum}
*/
@Schema(description = "老负责人加入团队后的权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
private Integer oldOwnerPermissionLevel;
@InEnum(value = CrmPermissionLevelEnum.class)
private Integer oldOwnerPermissionLevel; // 老负责人加入团队后的权限级别如果 null 说明移除
}

View File

@ -135,18 +135,22 @@ public class CrmCustomerController {
return success(true);
}
// TODO @puhui999operate-log-list 或者 operate-log-page 如果分页
@GetMapping("/operate-log")
@Operation(summary = "获得客户操作日志")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('crm:customer:query')")
// TODO @puhui999最好有读权限方法名改成 getCustomerOperateLog
public CommonResult<List<OperateLogV2RespDTO>> getOperateLog(@RequestParam("id") Long id) {
// 1. 获取客户
// TODO @puhui999这个校验可以去掉哈
CrmCustomerDO customer = customerService.getCustomer(id);
if (customer == null) {
return success(null);
}
// 2. 获取操作日志
// TODO @puhui999操作日志返回可能要分页哈
return success(operateLogApi.getOperateLogByModuleAndBizId(CRM_CUSTOMER, id));
}

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
@ -13,19 +14,12 @@ public class CrmReceivablePlanTransferReqVO {
@NotNull(message = "回款计划编号不能为空")
private Long id;
/**
* 新负责人的用户编号
*/
@Schema(description = "新负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
@NotNull(message = "新负责人的用户编号不能为空")
private Long newOwnerUserId;
/**
* 老负责人加入团队后的权限级别如果 null 说明移除
*
* 关联 {@link CrmPermissionLevelEnum}
*/
@Schema(description = "老负责人加入团队后的权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
private Integer oldOwnerPermissionLevel;
@InEnum(value = CrmPermissionLevelEnum.class)
private Integer oldOwnerPermissionLevel; // 老负责人加入团队后的权限级别如果 null 说明移除
}

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
@ -13,19 +14,12 @@ public class CrmReceivableTransferReqVO {
@NotNull(message = "回款编号不能为空")
private Long id;
/**
* 新负责人的用户编号
*/
@Schema(description = "新负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
@NotNull(message = "新负责人的用户编号不能为空")
private Long newOwnerUserId;
/**
* 老负责人加入团队后的权限级别如果 null 说明移除
*
* 关联 {@link CrmPermissionLevelEnum}
*/
@Schema(description = "老负责人加入团队后的权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
private Integer oldOwnerPermissionLevel;
@InEnum(value = CrmPermissionLevelEnum.class)
private Integer oldOwnerPermissionLevel; // 老负责人加入团队后的权限级别如果 null 说明移除
}

View File

@ -8,6 +8,7 @@ import org.springframework.stereotype.Component;
import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_INDUSTRY;
// TODO @puhui999包名使用 operatelog 更合适哈
/**
* 自定义函数-通过行业编号获取行业信息
*

View File

@ -26,6 +26,7 @@ import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CRM_PERMISSION_DENIED;
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CRM_PERMISSION_MODEL_NOT_EXISTS;
// TODO 这个包改成 permission然后搞 config core 这个类在 core 包里目的是framework 最好分类下
/**
* Crm 数据权限校验 AOP 切面
*

View File

@ -137,12 +137,14 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
@Override
@Transactional(rollbackFor = Exception.class)
// TODO @puhui999@LogRecord(type = CRM_CUSTOMER, subType = "客户转移", bizNo = "{{#reqVO.id}}", success = TRANSFER_CUSTOMER_LOG_SUCCESS)
@LogRecord(success = TRANSFER_CUSTOMER_LOG_SUCCESS, type = CRM_CUSTOMER, subType = "客户转移", bizNo = "{{#reqVO.id}}")
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#reqVO.id", level = CrmPermissionLevelEnum.OWNER)
public void transferCustomer(CrmCustomerTransferReqVO reqVO, Long userId) {
// 1. 校验客户是否存在
validateCustomer(reqVO.getId());
// 添加 crmCustomer 到日志上下文 TODO 日志记录放在 service 里是因为已经过了权限校验查询时不用走两次校验
// TODO @puhui999customer 不用查询 1. 拿到哈然后 put这个动作可以放到 3.这样逻辑结构就是校验逻辑日志更加清晰
LogRecordContext.putVariable("crmCustomer", customerMapper.selectById(reqVO.getId()));
// 2.1 数据权限转移
crmPermissionService.transferPermission(

View File

@ -138,11 +138,10 @@ public class CrmPermissionServiceImpl implements CrmPermissionService {
}
@Override
@Transactional(rollbackFor = Exception.class)
@Transactional(rollbackFor = Exception.class) // TODO @puhui999这里不用加的就一个操作哈
public void deletePermission(Integer bizType, Long bizId) {
// 删除数据权限
int deletedCol = crmPermissionMapper.deletePermission(bizType, bizId);
if (deletedCol == 0) {
int deletedCount = crmPermissionMapper.deletePermission(bizType, bizId);
if (deletedCount == 0) {
throw exception(CRM_PERMISSION_NOT_EXISTS);
}
}

View File

@ -61,9 +61,9 @@ public class CrmReceivablePlanServiceImpl implements CrmReceivablePlanService {
receivablePlanMapper.insert(receivablePlan);
// 创建数据权限
crmPermissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType())
.setBizId(receivablePlan.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel()));
// 返回
crmPermissionService.createPermission(new CrmPermissionCreateReqBO().setUserId(userId)
.setBizType(CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType()).setBizId(receivablePlan.getId())
.setLevel(CrmPermissionLevelEnum.OWNER.getLevel()));
return receivablePlan.getId();
}

View File

@ -35,14 +35,14 @@ public class CrmQueryWrapperUtils {
* @param pool 公海
* @return 是否 需要执行查询不需要查询调用方法直接返回空
*/
// TODO @puhui999bizId 直接传递会不会简单点 回复还是需要 SFunction 因为分页连表时不知道 bizId 是多少
// TODO @puhui999bizId 直接传递会不会简单点 回复还是需要 SFunction 因为分页连表时不知道 bizId 是多少是不是把 bizId 传入就好啦
public static <T extends MPJLambdaWrapper<?>, S> boolean appendPermissionCondition(T query, Integer bizType, SFunction<S, ?> bizId,
Long userId, Integer sceneType, Boolean pool) {
// 1. 构建数据权限连表条件
if (ObjUtil.notEqual(validateAdminUser(userId), Boolean.TRUE)) { // 管理员不需要数据权限
query.innerJoin(CrmPermissionDO.class, on ->
on.eq(CrmPermissionDO::getBizType, bizType).eq(CrmPermissionDO::getBizId, bizId)
.eq(CrmPermissionDO::getUserId, userId));
query.innerJoin(CrmPermissionDO.class, on -> on.eq(CrmPermissionDO::getBizType, bizType)
.eq(CrmPermissionDO::getBizId, bizId)
.eq(CrmPermissionDO::getUserId, userId));
}
// 2.1 场景一我负责的数据
if (CrmSceneTypeEnum.isOwner(sceneType)) {
@ -50,15 +50,15 @@ public class CrmQueryWrapperUtils {
}
// 2.2 场景二我参与的数据
if (CrmSceneTypeEnum.isInvolved(sceneType)) {
query
.ne("owner_user_id", userId)
query.ne("owner_user_id", userId)
// TODO @puhui999IN 是不是更合适哈
.and(q -> q.eq(CrmPermissionDO::getLevel, CrmPermissionLevelEnum.READ.getLevel())
.or()
.eq(CrmPermissionDO::getLevel, CrmPermissionLevelEnum.WRITE.getLevel()));
}
// 2.3 场景三下属负责的数据
if (CrmSceneTypeEnum.isSubordinate(sceneType)) {
// TODO @puhui999要不如果没有下属拼一个 owner_user_id in null不返回结果就好啦
List<AdminUserRespDTO> subordinateUsers = getAdminUserApi().getUserListBySubordinate(userId);
if (CollUtil.isEmpty(subordinateUsers)) {
return false;
@ -72,7 +72,6 @@ public class CrmQueryWrapperUtils {
} else { // 情况二不是公海
query.isNotNull("owner_user_id");
}
return true;
}
@ -106,6 +105,7 @@ public class CrmQueryWrapperUtils {
*/
private static boolean validateAdminUser(Long userId) {
// TODO 查询权限配置表用户的角色信息
// TODO @puhui999查询用户的角色;CRM_ADMIN("crm_admin", "CRM 管理员"),
//CrmPermissionConfig permissionConfig = crmPermissionConfigService.getPermissionConfigByUserId(userId);
//if (permissionConfig == null) {
// return false;

View File

@ -22,6 +22,7 @@
<artifactId>yudao-common</artifactId>
</dependency>
<!-- TODO @puhui999 & 芋艿:操作日志,要不要这么引入? -->
<!-- Springboot-注解-通用操作日志组件 -->
<!-- 此组件解决的问题是: 「谁」在「什么时间」对「什么」做了「什么事」 -->
<dependency>
@ -29,6 +30,7 @@
<artifactId>bizlog-sdk</artifactId>
</dependency>
<!-- TODO @puhui999 & 芋艿:要不要移除掉 -->
<!--工具类相关-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>

View File

@ -19,20 +19,14 @@ public class OperateLogV2RespDTO {
/**
* 链路追踪编号
*
* 一般来说通过链路追踪编号可以将访问日志错误日志链路追踪日志logger 打印日志等结合在一起从而进行排错
*/
private String traceId;
/**
* 用户编号
*
* 关联 MemberUserDO id 属性或者 AdminUserDO id 属性
*/
private Long userId;
/**
* 用户类型
*
* 关联 {@link UserTypeEnum}
*/
private Integer userType;
/**
@ -48,13 +42,11 @@ public class OperateLogV2RespDTO {
*/
private Long bizId;
/**
* 操作内容记录整个操作的明细
* 例如说修改编号为 1 的用户信息将性别从男改成女将姓名从芋道改成源码
* 操作内容
*/
private String content;
/**
* 拓展字段有些复杂的业务需要记录一些字段 ( JSON 格式 )
* 例如说记录订单编号{ orderId: "1"}
* 拓展字段
*/
private String extra;
@ -81,8 +73,10 @@ public class OperateLogV2RespDTO {
// TODO puhui999: 木得效果怎么肥事
@JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
private LocalDateTime createTime;
// TODO @puhui999下面 2 个字段不用返回 userId 返回一个 userName
/**
* 创建者关联 AdminUserDO#getId
* 创建者
*/
private String creator;
/**

View File

@ -42,6 +42,7 @@ public class OperateLogV2DO extends BaseDO {
* 关联 {@link UserTypeEnum}
*/
private Integer userType;
// TODO @puhui999module 改成 typename 改成 subType
/**
* 操作模块
*/
@ -56,13 +57,16 @@ public class OperateLogV2DO extends BaseDO {
private Long bizId;
/**
* 操作内容记录整个操作的明细
*
* 例如说修改编号为 1 的用户信息将性别从男改成女将姓名从芋道改成源码
*/
private String content;
/**
* 拓展字段有些复杂的业务需要记录一些字段 ( JSON 格式 )
*
* 例如说记录订单编号{ orderId: "1"}
*/
// TODO @puhui999看看能不能类似 exts json 格式
private String extra;
/**
* 请求方法名
@ -81,4 +85,9 @@ public class OperateLogV2DO extends BaseDO {
*/
private String userAgent;
// TODO @芋艿requestUrlrequestMethod
// TODO @芋艿javaMethodjavaMethodArgs
// TODO @芋艿startTimeduration
// TODO @芋艿resultMsgresultData
}

View File

@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.system.framework.bizlog.config;
import com.mzt.logapi.starter.annotation.EnableLogRecord;
import org.springframework.context.annotation.Configuration;
// TODO @puhui999挪到 yudao-spring-boot-starter-biz-operatelog 搞个 cn.iocoder.yudao.framework.operatelogv2跑通后我们直接就删除老的实现了
/**
* mzt-biz-log 配置类
*

View File

@ -9,6 +9,7 @@ import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
// TODO @puhui999这个微信讨论下function 叫啥好哈
/**
* 自定义函数-通过用户编号获取用户信息
*

View File

@ -15,6 +15,7 @@ import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.List;
// TODO @puhui999这个应该搞到 operatelog 组件里哈
/**
* 操作日志 ILogRecordService 实现类
*

View File

@ -31,7 +31,7 @@ public interface OperateLogService {
*/
PageResult<OperateLogDO> getOperateLogPage(OperateLogPageReqVO pageReqVO);
//======================= LOG V2 =======================
// ======================= LOG V2 =======================
/**
* 记录操作日志 V2
@ -40,6 +40,7 @@ public interface OperateLogService {
*/
void createOperateLogV2(OperateLogV2CreateReqBO createReqBO);
// TODO @puhui999module 改成 type
/**
* 获取指定模块的指定数据的操作日志
*

View File

@ -66,7 +66,7 @@ public class OperateLogServiceImpl implements OperateLogService {
return operateLogMapper.selectPage(pageReqVO, userIds);
}
//======================= LOG V2 =======================
// ======================= LOG V2 =======================
@Override
public void createOperateLogV2(OperateLogV2CreateReqBO createReqBO) {