mirror of
https://gitee.com/huangge1199_admin/vue-pro.git
synced 2025-01-19 03:30:06 +08:00
📖 CRM:code review 客户管理的数据权限、操作权限
This commit is contained in:
parent
bada32750c
commit
ddb6fe7ec8
@ -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:这个可以删除哈,一般不搞失败的日志
|
||||
|
||||
}
|
||||
|
@ -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 说明移除
|
||||
|
||||
}
|
||||
|
@ -135,18 +135,22 @@ public class CrmCustomerController {
|
||||
return success(true);
|
||||
}
|
||||
|
||||
// TODO @puhui999:operate-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));
|
||||
}
|
||||
|
||||
|
@ -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 说明移除
|
||||
|
||||
}
|
||||
|
@ -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 说明移除
|
||||
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import org.springframework.stereotype.Component;
|
||||
|
||||
import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_INDUSTRY;
|
||||
|
||||
// TODO @puhui999:包名使用 operatelog 更合适哈;
|
||||
/**
|
||||
* 自定义函数-通过行业编号获取行业信息
|
||||
*
|
||||
|
@ -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 切面
|
||||
*
|
||||
|
@ -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 @puhui999:customer 不用查询,从 1. 拿到哈;然后 put这个动作,可以放到 3.;这样逻辑结构就是,校验、逻辑、日志,更加清晰
|
||||
LogRecordContext.putVariable("crmCustomer", customerMapper.selectById(reqVO.getId()));
|
||||
// 2.1 数据权限转移
|
||||
crmPermissionService.transferPermission(
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -35,14 +35,14 @@ public class CrmQueryWrapperUtils {
|
||||
* @param pool 公海
|
||||
* @return 是否 (是:需要执行查询,否:不需要查询调用方法直接返回空)
|
||||
*/
|
||||
// TODO @puhui999:bizId 直接传递会不会简单点 回复:还是需要 SFunction 因为分页连表时不知道 bizId 是多少
|
||||
// TODO @puhui999:bizId 直接传递会不会简单点 回复:还是需要 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 @puhui999:IN 是不是更合适哈;
|
||||
.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;
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
/**
|
||||
|
@ -42,6 +42,7 @@ public class OperateLogV2DO extends BaseDO {
|
||||
* 关联 {@link UserTypeEnum}
|
||||
*/
|
||||
private Integer userType;
|
||||
// TODO @puhui999:module 改成 type,name 改成 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 @芋艿:requestUrl、requestMethod
|
||||
// TODO @芋艿:javaMethod、javaMethodArgs
|
||||
// TODO @芋艿:startTime、duration
|
||||
// TODO @芋艿:resultMsg、resultData
|
||||
|
||||
}
|
||||
|
@ -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 配置类
|
||||
*
|
||||
|
@ -9,6 +9,7 @@ import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
// TODO @puhui999:这个微信讨论下,function 叫啥好哈;
|
||||
/**
|
||||
* 自定义函数-通过用户编号获取用户信息
|
||||
*
|
||||
|
@ -15,6 +15,7 @@ import org.springframework.stereotype.Service;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
// TODO @puhui999:这个应该搞到 operatelog 组件里哈;
|
||||
/**
|
||||
* 操作日志 ILogRecordService 实现类
|
||||
*
|
||||
|
@ -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 @puhui999:module 改成 type
|
||||
/**
|
||||
* 获取指定模块的指定数据的操作日志
|
||||
*
|
||||
|
@ -66,7 +66,7 @@ public class OperateLogServiceImpl implements OperateLogService {
|
||||
return operateLogMapper.selectPage(pageReqVO, userIds);
|
||||
}
|
||||
|
||||
//======================= LOG V2 =======================
|
||||
// ======================= LOG V2 =======================
|
||||
|
||||
@Override
|
||||
public void createOperateLogV2(OperateLogV2CreateReqBO createReqBO) {
|
||||
|
Loading…
Reference in New Issue
Block a user