CRM:完善数据分页查询条件构造 CrmQueryWrapperUtils

This commit is contained in:
puhui999 2023-12-19 10:29:41 +08:00
parent 3bab9748db
commit 3e91ac73c4
13 changed files with 91 additions and 82 deletions

View File

@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.crm.enums.permission;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* Crm 数据权限角色枚举
*
* @author HUIHUI
*/
@Getter
@AllArgsConstructor
public enum CrmPermissionRoleCodeEnum {
CRM_ADMIN("crm_admin", "CRM 管理员");
/**
* 角色标识
*/
private String code;
/**
* 角色名称
*/
private String name;
}

View File

@ -1,8 +1,8 @@
### 请求 /transfer
PUT {{baseUrl}}/crm/customer/transfer
Content-Type: application/json
Content-Type: application/-id: {{adminTenentId}}json
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
tenant
{
"id": 10,

View File

@ -3,8 +3,10 @@ package cn.iocoder.yudao.module.crm.controller.admin.customer;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import cn.iocoder.yudao.framework.operatelogv2.core.vo.OperateLogV2PageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.*;
import cn.iocoder.yudao.module.crm.convert.customer.CrmCustomerConvert;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
@ -12,6 +14,7 @@ import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.logger.OperateLogApi;
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2PageReqDTO;
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2RespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
@ -135,23 +138,14 @@ public class CrmCustomerController {
return success(true);
}
// TODO @puhui999operate-log-list 或者 operate-log-page 如果分页
@GetMapping("/operate-log")
@GetMapping("/operate-log-page")
@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));
public CommonResult<PageResult<OperateLogV2RespDTO>> getCustomerOperateLog(OperateLogV2PageReqVO reqVO) {
reqVO.setPageSize(PAGE_SIZE_NONE); // 不分页
reqVO.setBizType(CRM_CUSTOMER);
return success(operateLogApi.getOperateLogPage(BeanUtils.toBean(reqVO, OperateLogV2PageReqDTO.class)));
}
// TODO @Joey单独建一个属于自己业务的 ReqVO因为前端如果模拟请求是不是可以更新其它字段了

View File

@ -38,11 +38,8 @@ public interface CrmBusinessMapper extends BaseMapperX<CrmBusinessDO> {
default PageResult<CrmBusinessDO> selectPage(CrmBusinessPageReqVO pageReqVO, Long userId) {
MPJLambdaWrapperX<CrmBusinessDO> query = new MPJLambdaWrapperX<>();
// 拼接数据权限的查询条件
boolean condition = CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_BUSINESS.getType(),
CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_BUSINESS.getType(),
CrmBusinessDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE);
if (!condition) {
return PageResult.empty();
}
// 拼接自身的查询条件
query.selectAll(CrmBusinessDO.class)
.likeIfPresent(CrmBusinessDO::getName, pageReqVO.getName())

View File

@ -30,11 +30,8 @@ public interface CrmClueMapper extends BaseMapperX<CrmClueDO> {
default PageResult<CrmClueDO> selectPage(CrmCluePageReqVO pageReqVO, Long userId) {
MPJLambdaWrapperX<CrmClueDO> query = new MPJLambdaWrapperX<>();
// 拼接数据权限的查询条件
boolean condition = CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_LEADS.getType(),
CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_LEADS.getType(),
CrmClueDO::getId, userId, pageReqVO.getSceneType(), pageReqVO.getPool());
if (!condition) {
return PageResult.empty();
}
// 拼接自身的查询条件
query.selectAll(CrmClueDO.class)
.likeIfPresent(CrmClueDO::getName, pageReqVO.getName())

View File

@ -43,11 +43,8 @@ public interface CrmContactMapper extends BaseMapperX<CrmContactDO> {
default PageResult<CrmContactDO> selectPage(CrmContactPageReqVO pageReqVO, Long userId) {
MPJLambdaWrapperX<CrmContactDO> query = new MPJLambdaWrapperX<>();
// 拼接数据权限的查询条件
boolean condition = CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CONTACT.getType(),
CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CONTACT.getType(),
CrmContactDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE);
if (!condition) {
return PageResult.empty();
}
// 拼接自身的查询条件
query.selectAll(CrmContactDO.class)
.likeIfPresent(CrmContactDO::getName, pageReqVO.getName())

View File

@ -41,11 +41,8 @@ public interface CrmContractMapper extends BaseMapperX<CrmContractDO> {
default PageResult<CrmContractDO> selectPage(CrmContractPageReqVO pageReqVO, Long userId) {
MPJLambdaWrapperX<CrmContractDO> mpjLambdaWrapperX = new MPJLambdaWrapperX<>();
// 拼接数据权限的查询条件
boolean condition = CrmQueryWrapperUtils.appendPermissionCondition(mpjLambdaWrapperX, CrmBizTypeEnum.CRM_CONTACT.getType(),
CrmQueryWrapperUtils.appendPermissionCondition(mpjLambdaWrapperX, CrmBizTypeEnum.CRM_CONTACT.getType(),
CrmContractDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE);
if (!condition) {
return PageResult.empty();
}
// 拼接自身的查询条件
mpjLambdaWrapperX.selectAll(CrmContractDO.class)
.likeIfPresent(CrmContractDO::getNo, pageReqVO.getNo())

View File

@ -30,11 +30,8 @@ public interface CrmCustomerMapper extends BaseMapperX<CrmCustomerDO> {
default PageResult<CrmCustomerDO> selectPage(CrmCustomerPageReqVO pageReqVO, Long userId) {
MPJLambdaWrapperX<CrmCustomerDO> query = new MPJLambdaWrapperX<>();
// 拼接数据权限的查询条件
boolean condition = CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CUSTOMER.getType(),
CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CUSTOMER.getType(),
CrmCustomerDO::getId, userId, pageReqVO.getSceneType(), pageReqVO.getPool());
if (!condition) {
return PageResult.empty();
}
// 拼接自身的查询条件
query.selectAll(CrmCustomerDO.class)
.likeIfPresent(CrmCustomerDO::getName, pageReqVO.getName())

View File

@ -39,11 +39,8 @@ public interface CrmReceivableMapper extends BaseMapperX<CrmReceivableDO> {
default PageResult<CrmReceivableDO> selectPage(CrmReceivablePageReqVO pageReqVO, Long userId) {
MPJLambdaWrapperX<CrmReceivableDO> query = new MPJLambdaWrapperX<>();
// 拼接数据权限的查询条件
boolean condition = CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE.getType(),
CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE.getType(),
CrmReceivableDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE);
if (!condition) {
return PageResult.empty();
}
// 拼接自身的查询条件
query.selectAll(CrmReceivableDO.class)
.eqIfPresent(CrmReceivableDO::getNo, pageReqVO.getNo())

View File

@ -38,11 +38,8 @@ public interface CrmReceivablePlanMapper extends BaseMapperX<CrmReceivablePlanDO
default PageResult<CrmReceivablePlanDO> selectPage(CrmReceivablePlanPageReqVO pageReqVO, Long userId) {
MPJLambdaWrapperX<CrmReceivablePlanDO> query = new MPJLambdaWrapperX<>();
// 拼接数据权限的查询条件
boolean condition = CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType(),
CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType(),
CrmReceivablePlanDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE);
if (!condition) {
return PageResult.empty();
}
// 拼接自身的查询条件
query.selectAll(CrmReceivablePlanDO.class)
.eqIfPresent(CrmReceivablePlanDO::getCustomerId, pageReqVO.getCustomerId())

View File

@ -7,9 +7,12 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionRoleCodeEnum;
import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.github.yulichang.autoconfigure.MybatisPlusJoinProperties;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import java.util.Collection;
@ -33,46 +36,41 @@ public class CrmQueryWrapperUtils {
* @param userId 用户编号
* @param sceneType 场景类型
* @param pool 公海
* @return 是否 需要执行查询不需要查询调用方法直接返回空
*/
// 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) {
public static <T extends MPJLambdaWrapper<?>, S> void appendPermissionCondition(T query, Integer bizType, SFunction<S, ?> bizId,
Long userId, Integer sceneType, Boolean pool) {
final String ownerUserIdField = SingletonManager.getMybatisPlusJoinProperties().getTableAlias() + ".owner_user_id";
// 1. 构建数据权限连表条件
if (ObjUtil.notEqual(validateAdminUser(userId), Boolean.TRUE)) { // 管理员不需要数据权限
if (ObjUtil.notEqual(validateAdminUser(userId), Boolean.TRUE) && ObjUtil.notEqual(pool, Boolean.TRUE)) { // 管理员公海不需要数据权限
query.innerJoin(CrmPermissionDO.class, on -> on.eq(CrmPermissionDO::getBizType, bizType)
.eq(CrmPermissionDO::getBizId, bizId)
.eq(CrmPermissionDO::getBizId, bizId) // 只能使用 SFunction 如果传 id 解析出来的 sql 不对
.eq(CrmPermissionDO::getUserId, userId));
}
// 2.1 场景一我负责的数据
if (CrmSceneTypeEnum.isOwner(sceneType)) {
query.eq("owner_user_id", userId);
query.eq(ownerUserIdField, userId);
}
// 2.2 场景二我参与的数据
if (CrmSceneTypeEnum.isInvolved(sceneType)) {
query.ne("owner_user_id", userId)
// TODO @puhui999IN 是不是更合适哈
.and(q -> q.eq(CrmPermissionDO::getLevel, CrmPermissionLevelEnum.READ.getLevel())
.or()
.eq(CrmPermissionDO::getLevel, CrmPermissionLevelEnum.WRITE.getLevel()));
query.ne(ownerUserIdField, userId)
.in(CrmPermissionDO::getLevel, CrmPermissionLevelEnum.READ.getLevel(), CrmPermissionLevelEnum.WRITE.getLevel());
}
// 2.3 场景三下属负责的数据
if (CrmSceneTypeEnum.isSubordinate(sceneType)) {
// TODO @puhui999要不如果没有下属拼一个 owner_user_id in null不返回结果就好啦
List<AdminUserRespDTO> subordinateUsers = getAdminUserApi().getUserListBySubordinate(userId);
List<AdminUserRespDTO> subordinateUsers = SingletonManager.getAdminUserApi().getUserListBySubordinate(userId);
if (CollUtil.isEmpty(subordinateUsers)) {
return false;
query.eq(ownerUserIdField, -1); // 不返回任何结果
} else {
query.in(ownerUserIdField, convertSet(subordinateUsers, AdminUserRespDTO::getId));
}
query.in("owner_user_id", convertSet(subordinateUsers, AdminUserRespDTO::getId));
}
// 3. 拼接公海的查询条件
if (ObjUtil.equal(pool, Boolean.TRUE)) { // 情况一公海
query.isNull("owner_user_id");
query.isNull(ownerUserIdField);
} else { // 情况二不是公海
query.isNotNull("owner_user_id");
query.isNotNull(ownerUserIdField);
}
return true;
}
/**
@ -93,38 +91,38 @@ public class CrmQueryWrapperUtils {
.in(CollUtil.isNotEmpty(bizIds), CrmPermissionDO::getUserId, userId));
}
private static AdminUserApi getAdminUserApi() {
return AdminUserApiHolder.ADMIN_USER_API;
}
/**
* 校验用户是否是管理员
* 校验用户是否是 CRM 管理员
*
* @param userId 用户编号
* @return /
*/
private static boolean validateAdminUser(Long userId) {
// TODO 查询权限配置表用户的角色信息
// TODO @puhui999查询用户的角色;CRM_ADMIN("crm_admin", "CRM 管理员"),
//CrmPermissionConfig permissionConfig = crmPermissionConfigService.getPermissionConfigByUserId(userId);
//if (permissionConfig == null) {
// return false;
//}
//// 校验是否为管理员
//if (permissionConfig.getIsAdmin()){
// return true;
//}
return false;
return SingletonManager.getPermissionApi().hasAnyRoles(userId, CrmPermissionRoleCodeEnum.CRM_ADMIN.getCode());
}
/**
* 静态内部类实现 AdminUserApi 单例获取
* 静态内部类实现单例获取
*
* @author HUIHUI
*/
private static class AdminUserApiHolder {
private static class SingletonManager {
private static final AdminUserApi ADMIN_USER_API = SpringUtil.getBean(AdminUserApi.class);
private static final PermissionApi PERMISSION_API = SpringUtil.getBean(PermissionApi.class);
private static final MybatisPlusJoinProperties MYBATIS_PLUS_JOIN_PROPERTIES = SpringUtil.getBean(MybatisPlusJoinProperties.class);
public static AdminUserApi getAdminUserApi() {
return ADMIN_USER_API;
}
public static PermissionApi getPermissionApi() {
return PERMISSION_API;
}
public static MybatisPlusJoinProperties getMybatisPlusJoinProperties() {
return MYBATIS_PLUS_JOIN_PROPERTIES;
}
}

View File

@ -188,6 +188,7 @@ logging:
cn.iocoder.yudao.module.trade.dal.mysql: debug
cn.iocoder.yudao.module.promotion.dal.mysql: debug
cn.iocoder.yudao.module.statistics.dal.mysql: debug
cn.iocoder.yudao.module.crm.dal.mysql: debug
debug: false

View File

@ -79,7 +79,17 @@ mybatis-plus:
password: XDV71a+xqStEA3WH # 加解密的秘钥,可使用 https://www.imaegoo.com/2020/aes-key-generator/ 网站生成
mybatis-plus-join:
banner: false # 关闭控制台的 Banner 打印
#是否打印 mybatis plus join banner 默认true
banner: false
#全局启用副表逻辑删除(默认true) 关闭后关联查询不会加副表逻辑删除
sub-table-logic: true
#拦截器MappedStatement缓存(默认true)
ms-cache: true
#表别名(默认 t)
table-alias: t
#副表逻辑删除条件的位置支持where、on
#默认ON 1.4.7.2及之前版本默认为where
logic-del-type: on
# Spring Data Redis 配置
spring: