Commit 668fae9398e95e67530f81606363750729029c87
1 parent
ecf72896
CRM-数据权限:完善 code review 提到的问题
Showing
36 changed files
with
345 additions
and
352 deletions
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/CrmBusinessTransferReqVO.java
| ... | ... | @@ -21,19 +21,11 @@ public class CrmBusinessTransferReqVO { |
| 21 | 21 | @Schema(description = "新负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430") |
| 22 | 22 | private Long newOwnerUserId; |
| 23 | 23 | |
| 24 | - // TODO @puhui999:joinTeam 可以合并成 permissionLevel 里;oldOwnerPermissionLevel;这样 null 说明移除,因为都换负责人啦; | |
| 25 | 24 | /** |
| 26 | - * 老负责人是否加入团队,是/否 | |
| 27 | - */ | |
| 28 | - @Schema(description = "老负责人是否加入团队", requiredMode = Schema.RequiredMode.REQUIRED, example = "false") | |
| 29 | - @NotNull(message = "老负责人是否加入团队不能为空") | |
| 30 | - private Boolean joinTeam; | |
| 31 | - | |
| 32 | - /** | |
| 33 | - * 老负责人加入团队后的权限级别。如果 {@link #joinTeam} 为 false, permissionLevel 为 null | |
| 25 | + * 老负责人加入团队后的权限级别。如果 null 说明移除 | |
| 34 | 26 | * 关联 {@link CrmPermissionLevelEnum} |
| 35 | 27 | */ |
| 36 | 28 | @Schema(description = "老负责人加入团队后的权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") |
| 37 | - private Integer permissionLevel; | |
| 29 | + private Integer oldOwnerPermissionLevel; | |
| 38 | 30 | |
| 39 | 31 | } | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactTransferReqVO.java
| ... | ... | @@ -21,18 +21,11 @@ public class CrmContactTransferReqVO { |
| 21 | 21 | @Schema(description = "新负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430") |
| 22 | 22 | private Long newOwnerUserId; |
| 23 | 23 | |
| 24 | - // TODO @puhui999:joinTeam 可以合并成 permissionLevel 里;oldOwnerPermissionLevel;这样 null 说明移除,因为都换负责人啦; | |
| 25 | 24 | /** |
| 26 | - * 老负责人是否加入团队,是/否 | |
| 27 | - */ | |
| 28 | - @Schema(description = "老负责人是否加入团队", requiredMode = Schema.RequiredMode.REQUIRED, example = "false") | |
| 29 | - @NotNull(message = "老负责人是否加入团队不能为空") | |
| 30 | - private Boolean joinTeam; | |
| 31 | - /** | |
| 32 | - * 老负责人加入团队后的权限级别。如果 {@link #joinTeam} 为 false, permissionLevel 为 null | |
| 25 | + * 老负责人加入团队后的权限级别。如果 null 说明移除 | |
| 33 | 26 | * 关联 {@link CrmPermissionLevelEnum} |
| 34 | 27 | */ |
| 35 | 28 | @Schema(description = "老负责人加入团队后的权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") |
| 36 | - private Integer permissionLevel; | |
| 29 | + private Integer oldOwnerPermissionLevel; | |
| 37 | 30 | |
| 38 | 31 | } | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractTransferReqVO.java
| ... | ... | @@ -21,19 +21,11 @@ public class CrmContractTransferReqVO { |
| 21 | 21 | @Schema(description = "新负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430") |
| 22 | 22 | private Long newOwnerUserId; |
| 23 | 23 | |
| 24 | - // TODO @puhui999:joinTeam 可以合并成 permissionLevel 里;oldOwnerPermissionLevel;这样 null 说明移除,因为都换负责人啦; | |
| 25 | 24 | /** |
| 26 | - * 老负责人是否加入团队,是/否 | |
| 27 | - */ | |
| 28 | - @Schema(description = "老负责人是否加入团队", requiredMode = Schema.RequiredMode.REQUIRED, example = "false") | |
| 29 | - @NotNull(message = "老负责人是否加入团队不能为空") | |
| 30 | - private Boolean joinTeam; | |
| 31 | - | |
| 32 | - /** | |
| 33 | - * 老负责人加入团队后的权限级别。如果 {@link #joinTeam} 为 false, permissionLevel 为 null | |
| 25 | + * 老负责人加入团队后的权限级别。如果 null 说明移除 | |
| 34 | 26 | * 关联 {@link CrmPermissionLevelEnum} |
| 35 | 27 | */ |
| 36 | 28 | @Schema(description = "老负责人加入团队后的权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") |
| 37 | - private Integer permissionLevel; | |
| 29 | + private Integer oldOwnerPermissionLevel; | |
| 38 | 30 | |
| 39 | 31 | } | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerTransferReqVO.java
| ... | ... | @@ -21,19 +21,11 @@ public class CrmCustomerTransferReqVO { |
| 21 | 21 | @Schema(description = "新负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430") |
| 22 | 22 | private Long newOwnerUserId; |
| 23 | 23 | |
| 24 | - // TODO @puhui999:joinTeam 可以合并成 permissionLevel 里;oldOwnerPermissionLevel;这样 null 说明移除,因为都换负责人啦; | |
| 25 | 24 | /** |
| 26 | - * 老负责人是否加入团队,是/否 | |
| 27 | - */ | |
| 28 | - @Schema(description = "老负责人是否加入团队", requiredMode = Schema.RequiredMode.REQUIRED, example = "false") | |
| 29 | - @NotNull(message = "老负责人是否加入团队不能为空") | |
| 30 | - private Boolean joinTeam; | |
| 31 | - | |
| 32 | - /** | |
| 33 | - * 老负责人加入团队后的权限级别。如果 {@link #joinTeam} 为 false, permissionLevel 为 null | |
| 25 | + * 老负责人加入团队后的权限级别。如果 null 说明移除 | |
| 34 | 26 | * 关联 {@link CrmPermissionLevelEnum} |
| 35 | 27 | */ |
| 36 | 28 | @Schema(description = "老负责人加入团队后的权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") |
| 37 | - private Integer permissionLevel; | |
| 29 | + private Integer oldOwnerPermissionLevel; | |
| 38 | 30 | |
| 39 | 31 | } | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/CrmPermissionController.http
0 → 100644
| 1 | +### 请求 /add | |
| 2 | +PUT {{baseUrl}}/crm/permission/add | |
| 3 | +Content-Type: application/json | |
| 4 | +Authorization: Bearer {{token}} | |
| 5 | +tenant-id: {{adminTenentId}} | |
| 6 | + | |
| 7 | +{ | |
| 8 | + "userId": 1, | |
| 9 | + "bizType": 2, | |
| 10 | + "bizId": 2, | |
| 11 | + "level": 1 | |
| 12 | +} | |
| 13 | + | |
| 14 | +### 请求 /update | |
| 15 | +PUT {{baseUrl}}/crm/permission/update | |
| 16 | +Content-Type: application/json | |
| 17 | +Authorization: Bearer {{token}} | |
| 18 | +tenant-id: {{adminTenentId}} | |
| 19 | + | |
| 20 | +{ | |
| 21 | + "userId": 1, | |
| 22 | + "bizType": 2, | |
| 23 | + "bizId": 2, | |
| 24 | + "level": 1, | |
| 25 | + "id": 1 | |
| 26 | +} | |
| 27 | + | |
| 28 | +### 请求 /delete | |
| 29 | +DELETE {{baseUrl}}/crm/permission/delete?bizType=2&bizId=1&id=1 | |
| 30 | +Authorization: Bearer {{token}} | |
| 31 | +tenant-id: {{adminTenentId}} | |
| 32 | + | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/CrmPermissionController.java
| ... | ... | @@ -8,8 +8,14 @@ import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionR |
| 8 | 8 | import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionUpdateReqVO; |
| 9 | 9 | import cn.iocoder.yudao.module.crm.convert.permission.CrmPermissionConvert; |
| 10 | 10 | import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO; |
| 11 | -import cn.iocoder.yudao.module.crm.framework.core.service.CrmPermissionValidateService; | |
| 11 | +import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission; | |
| 12 | +import cn.iocoder.yudao.module.crm.framework.enums.CrmBizTypeEnum; | |
| 13 | +import cn.iocoder.yudao.module.crm.framework.enums.CrmPermissionLevelEnum; | |
| 12 | 14 | import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; |
| 15 | +import cn.iocoder.yudao.module.system.api.dept.DeptApi; | |
| 16 | +import cn.iocoder.yudao.module.system.api.dept.PostApi; | |
| 17 | +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; | |
| 18 | +import cn.iocoder.yudao.module.system.api.dept.dto.PostRespDTO; | |
| 13 | 19 | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| 14 | 20 | import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; |
| 15 | 21 | import io.swagger.v3.oas.annotations.Operation; |
| ... | ... | @@ -24,15 +30,12 @@ import javax.annotation.Resource; |
| 24 | 30 | import javax.validation.Valid; |
| 25 | 31 | import java.util.Collections; |
| 26 | 32 | import java.util.List; |
| 33 | +import java.util.Map; | |
| 34 | +import java.util.Set; | |
| 35 | +import java.util.stream.Collectors; | |
| 27 | 36 | |
| 28 | -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | |
| 29 | 37 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| 30 | 38 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; |
| 31 | -import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; | |
| 32 | -import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CRM_PERMISSION_DENIED; | |
| 33 | -import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CRM_PERMISSION_MODEL_NOT_EXISTS; | |
| 34 | -import static cn.iocoder.yudao.module.crm.framework.enums.CrmBizTypeEnum.getNameByType; | |
| 35 | -import static cn.iocoder.yudao.module.crm.framework.enums.CrmPermissionLevelEnum.isOwner; | |
| 36 | 39 | |
| 37 | 40 | @Tag(name = "管理后台 - CRM 数据权限(数据团队成员操作)") |
| 38 | 41 | @RestController |
| ... | ... | @@ -46,34 +49,16 @@ public class CrmPermissionController { |
| 46 | 49 | @Resource |
| 47 | 50 | private AdminUserApi adminUserApi; |
| 48 | 51 | @Resource |
| 49 | - private List<CrmPermissionValidateService> permissionValidateServices; | |
| 50 | - | |
| 51 | - // TODO @puhui999:这个能不能使用 CrmPermission 注解替代? | |
| 52 | - private void validatePermission(Integer bizType, Long bizId) { | |
| 53 | - // 1. TODO 校验是否为超级管理员 | |
| 54 | - // 2. 防御一手,如果是超级管理员不校验权限还是得校验一下数据是否存在 | |
| 55 | - // TODO @puhui999:是不是不用校验每个业务方的数据是否存在;其实不是很关键哈;简单一点~ 说白了,负责人只要在,它的数据就是存在~ | |
| 56 | - permissionValidateServices.forEach(item -> { | |
| 57 | - if (!item.validateBizIdExists(bizType, bizId)) { | |
| 58 | - throw exception(CRM_PERMISSION_MODEL_NOT_EXISTS, getNameByType(bizType)); | |
| 59 | - } | |
| 60 | - }); | |
| 61 | - // 3. 校验数据权限 (如果存在则表示 bizId 也存在) | |
| 62 | - CrmPermissionDO permission = crmPermissionService.getPermissionByBizTypeAndBizIdAndUserId( | |
| 63 | - bizType, bizId, getLoginUserId()); | |
| 64 | - if (isOwner(permission.getPermissionLevel())) { // 只有负责人才可以操作团队成员 | |
| 65 | - return; | |
| 66 | - } | |
| 67 | - throw exception(CRM_PERMISSION_DENIED, getNameByType(bizType)); | |
| 68 | - } | |
| 52 | + private DeptApi deptApi; | |
| 53 | + @Resource | |
| 54 | + private PostApi postApi; | |
| 69 | 55 | |
| 70 | 56 | @PutMapping("/add") |
| 71 | 57 | @Operation(summary = "添加团队成员") |
| 72 | 58 | @PreAuthorize("@ss.hasPermission('crm:permission:create')") |
| 59 | + @CrmPermission(bizType = CrmBizTypeEnum.CRM_PERMISSION, bizTypeValue = "#reqVO.bizType", bizId = "#reqVO.bizId" | |
| 60 | + , level = CrmPermissionLevelEnum.OWNER) | |
| 73 | 61 | public CommonResult<Boolean> addPermission(@Valid @RequestBody CrmPermissionCreateReqVO reqVO) { |
| 74 | - // 1. 前置校验 | |
| 75 | - validatePermission(reqVO.getBizType(), reqVO.getBizId()); | |
| 76 | - | |
| 77 | 62 | // 2. 加入成员 |
| 78 | 63 | crmPermissionService.createPermission(CrmPermissionConvert.INSTANCE.convert(reqVO)); |
| 79 | 64 | return success(true); |
| ... | ... | @@ -83,53 +68,30 @@ public class CrmPermissionController { |
| 83 | 68 | @PutMapping("/update") |
| 84 | 69 | @Operation(summary = "编辑团队成员") |
| 85 | 70 | @PreAuthorize("@ss.hasPermission('crm:permission:update')") |
| 71 | + @CrmPermission(bizType = CrmBizTypeEnum.CRM_PERMISSION, bizTypeValue = "#updateReqVO.bizType", bizId = "#updateReqVO.bizId" | |
| 72 | + , level = CrmPermissionLevelEnum.WRITE) | |
| 86 | 73 | public CommonResult<Boolean> updatePermission(@Valid @RequestBody CrmPermissionUpdateReqVO updateReqVO) { |
| 87 | - // 1. 前置校验 | |
| 88 | - validatePermission(updateReqVO.getBizType(), updateReqVO.getBizId()); | |
| 89 | - | |
| 90 | - // 2. 编辑团队成员 | |
| 91 | 74 | crmPermissionService.updatePermission(CrmPermissionConvert.INSTANCE.convert(updateReqVO)); |
| 92 | 75 | return success(true); |
| 93 | 76 | } |
| 94 | 77 | |
| 95 | - // TODO @puhui999:deletemapping | |
| 96 | - @GetMapping("/delete") | |
| 78 | + @DeleteMapping("/delete") | |
| 97 | 79 | @Operation(summary = "移除团队成员") |
| 98 | - @Parameter(name = "id", description = "团队成员编号", required = true) | |
| 99 | - // TODO @puhui999:是不是 id 参数就够了? | |
| 80 | + @Parameters({ | |
| 81 | + @Parameter(name = "bizType", description = "CRM 类型", required = true, example = "2"), | |
| 82 | + @Parameter(name = "bizId", description = "CRM 类型数据编号", required = true, example = "1024"), | |
| 83 | + @Parameter(name = "id", description = "团队成员编号", required = true, example = "1024") | |
| 84 | + }) | |
| 100 | 85 | @PreAuthorize("@ss.hasPermission('crm:permission:delete')") |
| 86 | + @CrmPermission(bizType = CrmBizTypeEnum.CRM_PERMISSION, bizTypeValue = "#bizType", bizId = "#bizId" | |
| 87 | + , level = CrmPermissionLevelEnum.OWNER) | |
| 101 | 88 | public CommonResult<Boolean> deletePermission(@RequestParam("bizType") Integer bizType, |
| 102 | 89 | @RequestParam("bizId") Long bizId, |
| 103 | 90 | @RequestParam("id") Long id) { |
| 104 | - // 1. 前置校验 | |
| 105 | - validatePermission(bizType, bizId); | |
| 106 | - | |
| 107 | - // 2. 移除团队成员 | |
| 108 | 91 | crmPermissionService.deletePermission(id); |
| 109 | 92 | return success(true); |
| 110 | 93 | } |
| 111 | 94 | |
| 112 | - // TODO @puhui999:这个是哪个地方使用到哈? | |
| 113 | - // TODO @puhui999:是不是 deletemapping 呀; | |
| 114 | - @GetMapping("/quit") | |
| 115 | - @Operation(summary = "退出团队") | |
| 116 | - @Parameters({ | |
| 117 | - @Parameter(name = "bizType", description = "CRM 类型", required = true, example = "2"), | |
| 118 | - @Parameter(name = "bizId", description = "CRM 类型数据编号", required = true, example = "1024") | |
| 119 | - }) | |
| 120 | - @PreAuthorize("@ss.hasPermission('crm:permission:delete')") | |
| 121 | - public CommonResult<Boolean> quitPermission(@RequestParam("bizType") Integer bizType, | |
| 122 | - @RequestParam("bizId") Long bizId) { | |
| 123 | - // 没有就不是团队成员 | |
| 124 | - CrmPermissionDO permission = crmPermissionService.getPermissionByBizTypeAndBizIdAndUserId( | |
| 125 | - bizType, bizId, getLoginUserId()); | |
| 126 | - if (permission == null) { | |
| 127 | - return success(false); | |
| 128 | - } | |
| 129 | - crmPermissionService.deletePermission(permission.getId()); | |
| 130 | - return success(true); | |
| 131 | - } | |
| 132 | - | |
| 133 | 95 | @GetMapping("/list") |
| 134 | 96 | @Operation(summary = "获取团队成员") |
| 135 | 97 | @Parameters({ |
| ... | ... | @@ -148,7 +110,10 @@ public class CrmPermissionController { |
| 148 | 110 | |
| 149 | 111 | // 拼接数据 |
| 150 | 112 | List<AdminUserRespDTO> userList = adminUserApi.getUserList(convertSet(permission, CrmPermissionDO::getUserId)); |
| 151 | - return success(CrmPermissionConvert.INSTANCE.convert(permission, userList)); | |
| 113 | + Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userList, AdminUserRespDTO::getDeptId)); | |
| 114 | + Set<Long> postIds = userList.stream().flatMap(item -> item.getPostIds().stream()).collect(Collectors.toSet()); | |
| 115 | + Map<Long, PostRespDTO> postMap = postApi.getPostMap(postIds); | |
| 116 | + return success(CrmPermissionConvert.INSTANCE.convert(permission, userList, deptMap, postMap)); | |
| 152 | 117 | } |
| 153 | 118 | |
| 154 | 119 | } | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/vo/CrmPermissionBaseVO.java
| ... | ... | @@ -30,10 +30,9 @@ public class CrmPermissionBaseVO { |
| 30 | 30 | @NotNull(message = "Crm 类型数据编号不能为空") |
| 31 | 31 | private Long bizId; |
| 32 | 32 | |
| 33 | - // TODO @puhui999:level; | |
| 34 | 33 | @Schema(description = "权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") |
| 35 | 34 | @InEnum(CrmPermissionLevelEnum.class) |
| 36 | 35 | @NotNull(message = "权限级别不能为空") |
| 37 | - private Integer permissionLevel; | |
| 36 | + private Integer level; | |
| 38 | 37 | |
| 39 | 38 | } | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/vo/CrmPermissionRespVO.java
| ... | ... | @@ -12,18 +12,13 @@ public class CrmPermissionRespVO extends CrmPermissionBaseVO { |
| 12 | 12 | @Schema(description = "数据权限编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") |
| 13 | 13 | private Long id; |
| 14 | 14 | |
| 15 | - // TODO @puhui999:搞到字典里; | |
| 16 | - @Schema(description = "团队级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "负责人") | |
| 17 | - private String permissionLevelName; | |
| 18 | - | |
| 19 | - // TODO @puhui999:deptId、postIds 是不是要提供中文名哈; | |
| 20 | - @Schema(description = "部门编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | |
| 21 | - private Long deptId; | |
| 15 | + @Schema(description = "部门名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "研发部") | |
| 16 | + private String deptName; | |
| 22 | 17 | |
| 23 | 18 | @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") |
| 24 | 19 | private String nickname; |
| 25 | 20 | |
| 26 | - @Schema(description = "岗位编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1,2,3]") | |
| 27 | - private Set<Long> postIds; | |
| 21 | + @Schema(description = "岗位名称数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[BOOS,经理]") | |
| 22 | + private Set<String> postNames; | |
| 28 | 23 | |
| 29 | 24 | } | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/vo/CrmPermissionUpdateReqVO.java
| ... | ... | @@ -5,6 +5,8 @@ import lombok.Data; |
| 5 | 5 | import lombok.EqualsAndHashCode; |
| 6 | 6 | import lombok.ToString; |
| 7 | 7 | |
| 8 | +import javax.validation.constraints.NotNull; | |
| 9 | + | |
| 8 | 10 | @Schema(description = "管理后台 - CRM 数据权限更新 Request VO") |
| 9 | 11 | @Data |
| 10 | 12 | @EqualsAndHashCode(callSuper = true) |
| ... | ... | @@ -12,9 +14,9 @@ import lombok.ToString; |
| 12 | 14 | public class CrmPermissionUpdateReqVO extends CrmPermissionBaseVO { |
| 13 | 15 | |
| 14 | 16 | @Schema(description = "数据权限编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") |
| 15 | - // TODO @puhui999:非空判断; | |
| 17 | + @NotNull(message = "数据权限编号不能为空") | |
| 16 | 18 | private Long id; |
| 17 | 19 | |
| 18 | - // TODO @puhui999:是不是只更新 permission??? | |
| 20 | + // TODO @puhui999:是不是只更新 permission??? 是的 | |
| 19 | 21 | |
| 20 | 22 | } | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessConvert.java
| ... | ... | @@ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | 4 | import cn.iocoder.yudao.module.crm.controller.admin.business.vo.*; |
| 5 | 5 | import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; |
| 6 | 6 | import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionPageReqBO; |
| 7 | -import cn.iocoder.yudao.module.crm.service.permission.bo.CrmTransferPermissionReqBO; | |
| 7 | +import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO; | |
| 8 | 8 | import org.mapstruct.Mapper; |
| 9 | 9 | import org.mapstruct.Mapping; |
| 10 | 10 | import org.mapstruct.Mappings; |
| ... | ... | @@ -36,7 +36,7 @@ public interface CrmBusinessConvert { |
| 36 | 36 | @Mapping(target = "bizId", source = "reqVO.id"), |
| 37 | 37 | @Mapping(target = "newOwnerUserId", source = "reqVO.id") |
| 38 | 38 | }) |
| 39 | - CrmTransferPermissionReqBO convert(CrmBusinessTransferReqVO reqVO, Long userId); | |
| 39 | + CrmPermissionTransferReqBO convert(CrmBusinessTransferReqVO reqVO, Long userId); | |
| 40 | 40 | |
| 41 | 41 | CrmPermissionPageReqBO convert(CrmBusinessPageReqVO pageReqVO); |
| 42 | 42 | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contact/ContactConvert.java
| ... | ... | @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.crm.convert.contact; |
| 3 | 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | 4 | import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.*; |
| 5 | 5 | import cn.iocoder.yudao.module.crm.dal.dataobject.contact.ContactDO; |
| 6 | -import cn.iocoder.yudao.module.crm.service.permission.bo.CrmTransferPermissionReqBO; | |
| 6 | +import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO; | |
| 7 | 7 | import org.mapstruct.Mapper; |
| 8 | 8 | import org.mapstruct.Mapping; |
| 9 | 9 | import org.mapstruct.Mappings; |
| ... | ... | @@ -37,6 +37,6 @@ public interface ContactConvert { |
| 37 | 37 | @Mapping(target = "bizId", source = "reqVO.id"), |
| 38 | 38 | @Mapping(target = "newOwnerUserId", source = "reqVO.id") |
| 39 | 39 | }) |
| 40 | - CrmTransferPermissionReqBO convert(CrmContactTransferReqVO reqVO, Long userId); | |
| 40 | + CrmPermissionTransferReqBO convert(CrmContactTransferReqVO reqVO, Long userId); | |
| 41 | 41 | |
| 42 | 42 | } | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contract/ContractConvert.java
| ... | ... | @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.crm.convert.contract; |
| 3 | 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | 4 | import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.*; |
| 5 | 5 | import cn.iocoder.yudao.module.crm.dal.dataobject.contract.ContractDO; |
| 6 | -import cn.iocoder.yudao.module.crm.service.permission.bo.CrmTransferPermissionReqBO; | |
| 6 | +import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO; | |
| 7 | 7 | import org.mapstruct.Mapper; |
| 8 | 8 | import org.mapstruct.Mapping; |
| 9 | 9 | import org.mapstruct.Mappings; |
| ... | ... | @@ -37,6 +37,6 @@ public interface ContractConvert { |
| 37 | 37 | @Mapping(target = "bizId", source = "reqVO.id"), |
| 38 | 38 | @Mapping(target = "newOwnerUserId", source = "reqVO.id") |
| 39 | 39 | }) |
| 40 | - CrmTransferPermissionReqBO convert(CrmContractTransferReqVO reqVO, Long userId); | |
| 40 | + CrmPermissionTransferReqBO convert(CrmContractTransferReqVO reqVO, Long userId); | |
| 41 | 41 | |
| 42 | 42 | } | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/customer/CrmCustomerConvert.java
| ... | ... | @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.crm.convert.customer; |
| 3 | 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | 4 | import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.*; |
| 5 | 5 | import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; |
| 6 | -import cn.iocoder.yudao.module.crm.service.permission.bo.CrmTransferPermissionReqBO; | |
| 6 | +import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO; | |
| 7 | 7 | import org.mapstruct.Mapper; |
| 8 | 8 | import org.mapstruct.Mapping; |
| 9 | 9 | import org.mapstruct.Mappings; |
| ... | ... | @@ -35,6 +35,6 @@ public interface CrmCustomerConvert { |
| 35 | 35 | @Mapping(target = "bizId", source = "reqVO.id"), |
| 36 | 36 | @Mapping(target = "newOwnerUserId", source = "reqVO.id") |
| 37 | 37 | }) |
| 38 | - CrmTransferPermissionReqBO convert(CrmCustomerTransferReqVO reqVO, Long userId); | |
| 38 | + CrmPermissionTransferReqBO convert(CrmCustomerTransferReqVO reqVO, Long userId); | |
| 39 | 39 | |
| 40 | 40 | } | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/permission/CrmPermissionConvert.java
| ... | ... | @@ -8,15 +8,16 @@ import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionU |
| 8 | 8 | import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO; |
| 9 | 9 | import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; |
| 10 | 10 | import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionUpdateReqBO; |
| 11 | +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; | |
| 12 | +import cn.iocoder.yudao.module.system.api.dept.dto.PostRespDTO; | |
| 11 | 13 | import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; |
| 14 | +import com.google.common.collect.Multimaps; | |
| 12 | 15 | import org.mapstruct.Mapper; |
| 13 | 16 | import org.mapstruct.factory.Mappers; |
| 14 | 17 | |
| 15 | 18 | import java.util.List; |
| 16 | 19 | import java.util.Map; |
| 17 | 20 | |
| 18 | -import static cn.iocoder.yudao.module.crm.framework.enums.CrmPermissionLevelEnum.getNameByLevel; | |
| 19 | - | |
| 20 | 21 | /** |
| 21 | 22 | * Crm 数据权限 Convert |
| 22 | 23 | * |
| ... | ... | @@ -37,12 +38,17 @@ public interface CrmPermissionConvert { |
| 37 | 38 | |
| 38 | 39 | List<CrmPermissionRespVO> convert(List<CrmPermissionDO> permission); |
| 39 | 40 | |
| 40 | - default List<CrmPermissionRespVO> convert(List<CrmPermissionDO> permission, List<AdminUserRespDTO> userList) { | |
| 41 | + default List<CrmPermissionRespVO> convert(List<CrmPermissionDO> permission, List<AdminUserRespDTO> userList, | |
| 42 | + Map<Long, DeptRespDTO> deptMap, Map<Long, PostRespDTO> postMap) { | |
| 41 | 43 | Map<Long, AdminUserRespDTO> userMap = CollectionUtils.convertMap(userList, AdminUserRespDTO::getId); |
| 42 | 44 | return CollectionUtils.convertList(convert(permission), item -> { |
| 43 | 45 | MapUtils.findAndThen(userMap, item.getId(), user -> { |
| 44 | - item.setNickname(user.getNickname()).setDeptId(user.getDeptId()).setPostIds(user.getPostIds()) | |
| 45 | - .setPermissionLevelName(getNameByLevel(item.getPermissionLevel())); | |
| 46 | + item.setNickname(user.getNickname()); | |
| 47 | + MapUtils.findAndThen(deptMap, user.getDeptId(), deptRespDTO -> { | |
| 48 | + item.setDeptName(deptRespDTO.getName()); | |
| 49 | + }); | |
| 50 | + List<PostRespDTO> postRespList = MapUtils.getList(Multimaps.forMap(postMap), user.getPostIds()); | |
| 51 | + item.setPostNames(CollectionUtils.convertSet(postRespList, PostRespDTO::getName)); | |
| 46 | 52 | }); |
| 47 | 53 | return item; |
| 48 | 54 | }); | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/permission/CrmPermissionDO.java
| ... | ... | @@ -23,7 +23,7 @@ import lombok.*; |
| 23 | 23 | @AllArgsConstructor |
| 24 | 24 | public class CrmPermissionDO extends BaseDO { |
| 25 | 25 | |
| 26 | - // TODO puhui999:是不是公海的数据,就不插入了; | |
| 26 | + // TODO puhui999:是不是公海的数据,就不插入了;这样方便获取公海数据鸭 | |
| 27 | 27 | /** |
| 28 | 28 | * 当数据变为公海数据时,也就是数据团队成员中没有负责人的时候,将原本的负责人 userId 设置为 POOL_USER_ID 方便查询公海数据。 |
| 29 | 29 | * 也就是说每条数据到最后都有一个负责人,如果有人领取则 userId 为领取人 |
| ... | ... | @@ -59,12 +59,11 @@ public class CrmPermissionDO extends BaseDO { |
| 59 | 59 | */ |
| 60 | 60 | private Long userId; |
| 61 | 61 | |
| 62 | - // TODO @puhui999:是不是搞成 level 字段;简洁一点,主要表明已经 perssmion 实体里了; | |
| 63 | 62 | /** |
| 64 | 63 | * 权限级别 |
| 65 | 64 | * |
| 66 | 65 | * 关联 {@link CrmPermissionLevelEnum} |
| 67 | 66 | */ |
| 68 | - private Integer permissionLevel; | |
| 67 | + private Integer level; | |
| 69 | 68 | |
| 70 | 69 | } | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/permission/CrmPermissionMapper.java
| ... | ... | @@ -4,7 +4,6 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | 4 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| 5 | 5 | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| 6 | 6 | import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO; |
| 7 | -import cn.iocoder.yudao.module.crm.framework.enums.CrmBizTypeEnum; | |
| 8 | 7 | import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionPageReqBO; |
| 9 | 8 | import org.apache.ibatis.annotations.Mapper; |
| 10 | 9 | |
| ... | ... | @@ -18,15 +17,6 @@ import java.util.List; |
| 18 | 17 | @Mapper |
| 19 | 18 | public interface CrmPermissionMapper extends BaseMapperX<CrmPermissionDO> { |
| 20 | 19 | |
| 21 | - // TODO @puhui999:是不是不用谢这个注释;因为方法名,可以自解释; | |
| 22 | - /** | |
| 23 | - * 获取用户数据权限通过 数据类型 x 某个数据 x 用户编号 | |
| 24 | - * | |
| 25 | - * @param bizType 数据类型,关联 {@link CrmBizTypeEnum} | |
| 26 | - * @param bizId 数据编号,关联 {@link CrmBizTypeEnum} 对应模块 DO#getId() | |
| 27 | - * @param userId 用户编号,AdminUser#id | |
| 28 | - * @return Crm 数据权限 | |
| 29 | - */ | |
| 30 | 20 | default CrmPermissionDO selectByBizTypeAndBizIdByUserId(Integer bizType, Long bizId, Long userId) { |
| 31 | 21 | return selectOne(new LambdaQueryWrapperX<CrmPermissionDO>() |
| 32 | 22 | .eq(CrmPermissionDO::getBizType, bizType) |
| ... | ... | @@ -34,14 +24,6 @@ public interface CrmPermissionMapper extends BaseMapperX<CrmPermissionDO> { |
| 34 | 24 | .eq(CrmPermissionDO::getUserId, userId)); |
| 35 | 25 | } |
| 36 | 26 | |
| 37 | - // TODO @puhui999:是不是不用谢这个注释;因为方法名,可以自解释; | |
| 38 | - /** | |
| 39 | - * 获取数据权限列表通过 数据类型 x 某个数据 | |
| 40 | - * | |
| 41 | - * @param bizType 数据类型,关联 {@link CrmBizTypeEnum} | |
| 42 | - * @param bizId 数据编号,关联 {@link CrmBizTypeEnum} 对应模块 DO#getId() | |
| 43 | - * @return Crm 数据权限列表 | |
| 44 | - */ | |
| 45 | 27 | default List<CrmPermissionDO> selectByBizTypeAndBizId(Integer bizType, Long bizId) { |
| 46 | 28 | return selectList(new LambdaQueryWrapperX<CrmPermissionDO>() |
| 47 | 29 | .eq(CrmPermissionDO::getBizType, bizType) | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/core/annotations/CrmPermission.java
| ... | ... | @@ -26,17 +26,21 @@ public @interface CrmPermission { |
| 26 | 26 | */ |
| 27 | 27 | CrmBizTypeEnum bizType(); |
| 28 | 28 | |
| 29 | - // TODO @puhui999:id,通过 spring el 表达式获取; | |
| 30 | 29 | /** |
| 31 | - * 数据编号获取来源类,确保数据 id 编号在此类中,不能在父类中。 | |
| 32 | - * 例:如果在 baseVO 中需要把 id 弄到 updateVO 中。 | |
| 30 | + * crm 类型扩展 | |
| 31 | + * 用于 CrmPermissionController 团队权限校验 | |
| 33 | 32 | */ |
| 34 | - Class<?>[] getIdFor() default {}; | |
| 33 | + String bizTypeValue() default ""; | |
| 35 | 34 | |
| 36 | - // TODO @puhui999:是不是搞成 level 字段;简洁一点,主要表明已经 perssmion 实体里了; | |
| 37 | 35 | /** |
| 38 | - * 操作类型 | |
| 36 | + * 数据编号,通过 spring el 表达式获取 | |
| 37 | + * TODO 数据权限完成后去除 default "" | |
| 39 | 38 | */ |
| 40 | - CrmPermissionLevelEnum permissionLevel(); | |
| 39 | + String bizId() default ""; | |
| 40 | + | |
| 41 | + /** | |
| 42 | + * 操作所需权限级别 | |
| 43 | + */ | |
| 44 | + CrmPermissionLevelEnum level(); | |
| 41 | 45 | |
| 42 | 46 | } | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/core/aop/CrmPermissionAspect.java
| ... | ... | @@ -2,24 +2,32 @@ package cn.iocoder.yudao.module.crm.framework.core.aop; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | 4 | import cn.hutool.core.util.ObjUtil; |
| 5 | +import cn.iocoder.yudao.framework.common.core.KeyValue; | |
| 5 | 6 | import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; |
| 6 | 7 | import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO; |
| 7 | 8 | import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission; |
| 9 | +import cn.iocoder.yudao.module.crm.framework.enums.CrmBizTypeEnum; | |
| 10 | +import cn.iocoder.yudao.module.crm.framework.enums.CrmPermissionLevelEnum; | |
| 8 | 11 | import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; |
| 9 | 12 | import lombok.extern.slf4j.Slf4j; |
| 10 | 13 | import org.aspectj.lang.JoinPoint; |
| 11 | 14 | import org.aspectj.lang.annotation.Aspect; |
| 12 | 15 | import org.aspectj.lang.annotation.Before; |
| 16 | +import org.aspectj.lang.reflect.MethodSignature; | |
| 17 | +import org.springframework.core.LocalVariableTableParameterNameDiscoverer; | |
| 18 | +import org.springframework.expression.EvaluationContext; | |
| 19 | +import org.springframework.expression.Expression; | |
| 20 | +import org.springframework.expression.spel.standard.SpelExpressionParser; | |
| 21 | +import org.springframework.expression.spel.support.StandardEvaluationContext; | |
| 13 | 22 | import org.springframework.stereotype.Component; |
| 14 | 23 | |
| 15 | 24 | import javax.annotation.Resource; |
| 16 | -import java.lang.reflect.Field; | |
| 25 | +import java.lang.reflect.Method; | |
| 17 | 26 | import java.util.List; |
| 18 | 27 | |
| 19 | 28 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 20 | 29 | import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; |
| 21 | 30 | import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CRM_PERMISSION_DENIED; |
| 22 | -import static cn.iocoder.yudao.module.crm.framework.enums.CrmPermissionLevelEnum.*; | |
| 23 | 31 | |
| 24 | 32 | /** |
| 25 | 33 | * Crm 数据权限校验 AOP 切面 |
| ... | ... | @@ -32,6 +40,11 @@ import static cn.iocoder.yudao.module.crm.framework.enums.CrmPermissionLevelEnum |
| 32 | 40 | public class CrmPermissionAspect { |
| 33 | 41 | |
| 34 | 42 | @Resource |
| 43 | + private LocalVariableTableParameterNameDiscoverer discoverer; | |
| 44 | + @Resource | |
| 45 | + private SpelExpressionParser parser; | |
| 46 | + | |
| 47 | + @Resource | |
| 35 | 48 | private CrmPermissionService crmPermissionService; |
| 36 | 49 | |
| 37 | 50 | /** |
| ... | ... | @@ -43,83 +56,109 @@ public class CrmPermissionAspect { |
| 43 | 56 | return WebFrameworkUtils.getLoginUserId(); |
| 44 | 57 | } |
| 45 | 58 | |
| 46 | - // TODO @puhui999:id,通过 spring el 表达式获取; | |
| 47 | - private Long getBizId(JoinPoint joinPoint, CrmPermission crmPermission) throws NoSuchFieldException, IllegalAccessException { | |
| 48 | - Object[] args = joinPoint.getArgs(); | |
| 49 | - for (Object arg : args) { | |
| 50 | - if (arg == null) { | |
| 51 | - continue; | |
| 52 | - } | |
| 53 | - if (ObjUtil.notEqual(arg.getClass().getName(), crmPermission.getIdFor()[0].getName())) { | |
| 54 | - continue; | |
| 55 | - } | |
| 56 | - // 使用反射获取id属性 | |
| 57 | - Field idValue = arg.getClass().getDeclaredField("id"); | |
| 58 | - // 设置字段为可访问 | |
| 59 | - idValue.setAccessible(true); | |
| 60 | - return (Long) idValue.get(arg); | |
| 61 | - } | |
| 62 | - return null; | |
| 63 | - } | |
| 64 | - | |
| 65 | - // TODO @puhui999:一般核心的方法,放到最前面,private 放后面。主要是,主次要分出来哈; | |
| 66 | 59 | @Before("@annotation(crmPermission)") |
| 67 | - public void doBefore(JoinPoint joinPoint, CrmPermission crmPermission) { | |
| 60 | + public void doBefore(JoinPoint joinPoint, CrmPermission crmPermission) throws NoSuchMethodException { | |
| 68 | 61 | // TODO 芋艿:临时,方便大家调试 |
| 69 | - if (true) { | |
| 62 | + //if (true) { | |
| 63 | + // return; | |
| 64 | + //} | |
| 65 | + KeyValue<Long, Integer> bizIdAndBizType = getBizIdAndBizType(joinPoint, crmPermission); | |
| 66 | + Integer bizType = bizIdAndBizType.getValue(); // 模块类型 | |
| 67 | + Long bizId = bizIdAndBizType.getKey(); // 模块数据编号 | |
| 68 | + Integer permissionLevel = crmPermission.level().getLevel(); // 需要的权限级别 | |
| 69 | + | |
| 70 | + // TODO 如果是超级管理员则直接通过 | |
| 71 | + //if (superAdmin){ | |
| 72 | + // return; | |
| 73 | + //} | |
| 74 | + | |
| 75 | + // 1. 获取数据权限 | |
| 76 | + List<CrmPermissionDO> bizPermissions = crmPermissionService.getPermissionByBizTypeAndBizId(bizType, bizId); | |
| 77 | + // 2.1 情况一:如果自己是负责人,则默认有所有权限 | |
| 78 | + // TODO @puhui999:会不会存在空指针的问题? | |
| 79 | + CrmPermissionDO userPermission = CollUtil.findOne(bizPermissions, item -> ObjUtil.equal(item.getUserId(), getUserId())); | |
| 80 | + if (CrmPermissionLevelEnum.isOwner(userPermission.getLevel())) { | |
| 70 | 81 | return; |
| 71 | 82 | } |
| 72 | - try { | |
| 73 | - Long bizId = crmPermission.getIdFor().length > 0 ? getBizId(joinPoint, crmPermission) : (Long) joinPoint.getArgs()[0]; // 获取操作数据的编号 | |
| 74 | - Integer bizType = crmPermission.bizType().getType(); // 模块类型 | |
| 75 | - Integer permissionLevel = crmPermission.permissionLevel().getLevel(); // 需要的权限级别 | |
| 76 | - | |
| 77 | - // TODO 如果是超级管理员则直接通过 | |
| 78 | - //if (superAdmin){ | |
| 79 | - // return; | |
| 80 | - //} | |
| 81 | - | |
| 82 | - // 1. 获取数据权限 | |
| 83 | - List<CrmPermissionDO> bizPermissions = crmPermissionService.getPermissionByBizTypeAndBizId(bizType, bizId); | |
| 84 | - // TODO puhui999:这种情况下,最好是 CrmPermissionLevelEnum.isOwner | |
| 85 | - // 2.1 情况一:如果自己是负责人,则默认有所有权限 | |
| 86 | - // TODO @puhui999:会不会存在空指针的问题? | |
| 87 | - CrmPermissionDO userPermission = CollUtil.findOne(bizPermissions, item -> ObjUtil.equal(item.getUserId(), getUserId())); | |
| 88 | - if (isOwner(userPermission.getPermissionLevel())) { | |
| 83 | + // 2.2 情况二:校验自己是否有读权限 | |
| 84 | + if (CrmPermissionLevelEnum.isRead(permissionLevel)) { | |
| 85 | + // 如果没有数据权限或没有负责人则表示此记录为公海数据所有人都有只读权限可以领取成为负责人(团队成员领取的) | |
| 86 | + // TODO @puhui999:89 到 92 这块的逻辑,感觉可以不用 @CrmPermission,公海那自己 check 即可; | |
| 87 | + if (CollUtil.isEmpty(bizPermissions) || CollUtil.anyMatch(bizPermissions, | |
| 88 | + item -> ObjUtil.equal(item.getUserId(), CrmPermissionDO.POOL_USER_ID))) { // 详见 CrmPermissionDO.POOL_USER_ID 注释 | |
| 89 | + return; | |
| 90 | + } | |
| 91 | + if (CrmPermissionLevelEnum.isRead(userPermission.getLevel())) { // 校验当前用户是否有读权限 | |
| 89 | 92 | return; |
| 90 | 93 | } |
| 91 | - // 2.2 情况二:校验自己是否有读权限 | |
| 92 | - if (isRead(permissionLevel)) { | |
| 93 | - // 如果没有数据权限或没有负责人则表示此记录为公海数据所有人都有只读权限可以领取成为负责人(团队成员领取的) | |
| 94 | - // TODO @puhui999:89 到 92 这块的逻辑,感觉可以不用 @CrmPermission,公海那自己 check 即可; | |
| 95 | - if (CollUtil.isEmpty(bizPermissions) || CollUtil.anyMatch(bizPermissions, | |
| 96 | - item -> ObjUtil.equal(item.getUserId(), CrmPermissionDO.POOL_USER_ID))) { // 详见 CrmPermissionDO.POOL_USER_ID 注释 | |
| 97 | - return; | |
| 98 | - } | |
| 99 | - if (isRead(userPermission.getPermissionLevel())) { // 校验当前用户是否有读权限 | |
| 100 | - return; | |
| 101 | - } | |
| 102 | - // 如果查询数据的话拥有写权限的也能查询 | |
| 103 | - if (isWrite(userPermission.getPermissionLevel())) { // 校验当前用户是否有写权限 | |
| 104 | - return; | |
| 105 | - } | |
| 94 | + // 如果查询数据的话拥有写权限的也能查询 | |
| 95 | + if (CrmPermissionLevelEnum.isWrite(userPermission.getLevel())) { // 校验当前用户是否有写权限 | |
| 96 | + return; | |
| 106 | 97 | } |
| 107 | - // 2.3 情况三:校验自己是否有写权限 | |
| 108 | - if (isWrite(permissionLevel)) { | |
| 109 | - if (isWrite(userPermission.getPermissionLevel())) { // 校验当前用户是否有写权限 | |
| 110 | - return; | |
| 111 | - } | |
| 98 | + } | |
| 99 | + // 2.3 情况三:校验自己是否有写权限 | |
| 100 | + if (CrmPermissionLevelEnum.isWrite(permissionLevel)) { | |
| 101 | + if (CrmPermissionLevelEnum.isWrite(userPermission.getLevel())) { // 校验当前用户是否有写权限 | |
| 102 | + return; | |
| 112 | 103 | } |
| 104 | + } | |
| 113 | 105 | |
| 114 | - // 3. 没通过结束,报错 {} 操作失败,原因:没有权限 | |
| 115 | - // TODO @puhui999:这里打个 info 日志,方便后续排查问题、审计; | |
| 116 | - throw exception(CRM_PERMISSION_DENIED, crmPermission.bizType().getName()); | |
| 117 | - } catch (Exception ex) { | |
| 118 | - // TODO @puhui999:不用 catch 掉,就是系统异常; | |
| 119 | - log.error("[doBefore][crmPermission({}) 数据校验错误]", toJsonString(crmPermission), ex); | |
| 120 | - // TODO 报错抛个什么异常好呢 | |
| 121 | - throw exception(CRM_PERMISSION_DENIED, crmPermission.bizType().getName()); | |
| 106 | + // 打个 info 日志,方便后续排查问题、审计; | |
| 107 | + log.info("[doBefore][crmPermission({}) 数据校验错误]", toJsonString(userPermission)); | |
| 108 | + throw exception(CRM_PERMISSION_DENIED, crmPermission.bizType().getName()); | |
| 109 | + } | |
| 110 | + | |
| 111 | + private KeyValue<Long, Integer> getBizIdAndBizType(JoinPoint joinPoint, CrmPermission crmPermission) throws NoSuchMethodException { | |
| 112 | + Method method = getMethod(joinPoint); | |
| 113 | + // 1. 获取方法的参数值 | |
| 114 | + Object[] args = joinPoint.getArgs(); | |
| 115 | + EvaluationContext context = bindParam(method, args); | |
| 116 | + | |
| 117 | + // 2. 根据spel表达式获取值 | |
| 118 | + KeyValue<Long, Integer> keyValue = new KeyValue<>(); | |
| 119 | + // 2.1 获取模块数据编号 | |
| 120 | + Expression expression = parser.parseExpression(crmPermission.bizId()); | |
| 121 | + keyValue.setKey(expression.getValue(context, Long.class)); | |
| 122 | + // 2.2 获取模块类型 | |
| 123 | + if (ObjUtil.equal(crmPermission.bizType().getType(), CrmBizTypeEnum.CRM_PERMISSION.getType())) { | |
| 124 | + // 情况一:用于 CrmPermissionController 中数据权限校验 | |
| 125 | + Expression expression2 = parser.parseExpression(crmPermission.bizTypeValue()); | |
| 126 | + keyValue.setValue(expression2.getValue(context, Integer.class)); | |
| 127 | + return keyValue; | |
| 128 | + } | |
| 129 | + // 情况二:正常数据权限校验 | |
| 130 | + keyValue.setValue(crmPermission.bizType().getType()); | |
| 131 | + return keyValue; | |
| 132 | + } | |
| 133 | + | |
| 134 | + /** | |
| 135 | + * 获取当前执行的方法 | |
| 136 | + */ | |
| 137 | + private Method getMethod(JoinPoint joinPoint) throws NoSuchMethodException { | |
| 138 | + MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); | |
| 139 | + Method method = methodSignature.getMethod(); | |
| 140 | + return joinPoint.getTarget().getClass().getMethod(method.getName(), method.getParameterTypes()); | |
| 141 | + } | |
| 142 | + | |
| 143 | + /** | |
| 144 | + * 将方法的参数名和参数值绑定 | |
| 145 | + * | |
| 146 | + * @param method 方法,根据方法获取参数名 | |
| 147 | + * @param args 方法的参数值 | |
| 148 | + * @return 求值上下文 | |
| 149 | + */ | |
| 150 | + private EvaluationContext bindParam(Method method, Object[] args) { | |
| 151 | + //获取方法的参数名 | |
| 152 | + String[] params = discoverer.getParameterNames(method); | |
| 153 | + | |
| 154 | + //将参数名与参数值对应起来 | |
| 155 | + EvaluationContext context = new StandardEvaluationContext(); | |
| 156 | + if (params != null) { | |
| 157 | + for (int len = 0; len < params.length; len++) { | |
| 158 | + context.setVariable(params[len], args[len]); | |
| 159 | + } | |
| 122 | 160 | } |
| 161 | + return context; | |
| 123 | 162 | } |
| 124 | 163 | |
| 125 | 164 | } | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/core/config/SpelConfig.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.crm.framework.core.config; | |
| 2 | + | |
| 3 | +import org.springframework.context.annotation.Bean; | |
| 4 | +import org.springframework.context.annotation.Configuration; | |
| 5 | +import org.springframework.expression.spel.standard.SpelExpressionParser; | |
| 6 | + | |
| 7 | +/** | |
| 8 | + * 注册 Spel 所需 Bean | |
| 9 | + * | |
| 10 | + * @author HUIHUI | |
| 11 | + */ | |
| 12 | +@Configuration | |
| 13 | +public class SpelConfig { | |
| 14 | + | |
| 15 | + @Bean | |
| 16 | + public SpelExpressionParser spelExpressionParser() { | |
| 17 | + return new SpelExpressionParser(); | |
| 18 | + } | |
| 19 | + | |
| 20 | +} | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/core/service/CrmPermissionValidateService.java deleted
| 1 | -package cn.iocoder.yudao.module.crm.framework.core.service; | |
| 2 | - | |
| 3 | -/** | |
| 4 | - * 校验数据是否存在 service 接口 | |
| 5 | - * TODO 需要使用团队成员相关操作的业务接口都需要继承此接口 | |
| 6 | - * | |
| 7 | - * @author HUIHUI | |
| 8 | - */ | |
| 9 | -public interface CrmPermissionValidateService { | |
| 10 | - | |
| 11 | - /** | |
| 12 | - * 校验数据是否存在 | |
| 13 | - * | |
| 14 | - * @param bizType CRM 类型 | |
| 15 | - * @param bizId 数据编号 | |
| 16 | - * @return 是/否 | |
| 17 | - */ | |
| 18 | - boolean validateBizIdExists(Integer bizType, Long bizId); | |
| 19 | - | |
| 20 | -} |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/enums/CrmBizTypeEnum.java
| 1 | 1 | package cn.iocoder.yudao.module.crm.framework.enums; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.collection.CollUtil; | |
| 3 | 4 | import cn.hutool.core.util.ObjUtil; |
| 4 | 5 | import cn.iocoder.yudao.framework.common.core.IntArrayValuable; |
| 5 | 6 | import lombok.Getter; |
| ... | ... | @@ -16,6 +17,7 @@ import java.util.Arrays; |
| 16 | 17 | @Getter |
| 17 | 18 | public enum CrmBizTypeEnum implements IntArrayValuable { |
| 18 | 19 | |
| 20 | + CRM_PERMISSION(0, "团队"), // CrmPermissionController 中使用 | |
| 19 | 21 | CRM_LEADS(1, "线索"), |
| 20 | 22 | CRM_CUSTOMER(2, "客户"), |
| 21 | 23 | CRM_CONTACTS(3, "联系人"), |
| ... | ... | @@ -33,13 +35,9 @@ public enum CrmBizTypeEnum implements IntArrayValuable { |
| 33 | 35 | private final String name; |
| 34 | 36 | |
| 35 | 37 | public static String getNameByType(Integer type) { |
| 36 | - // TODO @puhui999:可以 findone,更简洁;另外,不存在返回 null 即可啦; | |
| 37 | - for (CrmBizTypeEnum crmBizTypeEnum : CrmBizTypeEnum.values()) { | |
| 38 | - if (ObjUtil.equal(crmBizTypeEnum.type, type)) { | |
| 39 | - return crmBizTypeEnum.name; | |
| 40 | - } | |
| 41 | - } | |
| 42 | - return ""; | |
| 38 | + CrmBizTypeEnum typeEnum = CollUtil.findOne(CollUtil.newArrayList(CrmBizTypeEnum.values()), | |
| 39 | + item -> ObjUtil.equal(item.type, type)); | |
| 40 | + return typeEnum == null ? null : typeEnum.getName(); | |
| 43 | 41 | } |
| 44 | 42 | |
| 45 | 43 | @Override | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/enums/CrmPermissionLevelEnum.java
| ... | ... | @@ -48,14 +48,4 @@ public enum CrmPermissionLevelEnum implements IntArrayValuable { |
| 48 | 48 | return ObjUtil.equal(WRITE.level, level); |
| 49 | 49 | } |
| 50 | 50 | |
| 51 | - public static String getNameByLevel(Integer level) { | |
| 52 | - // TODO @puhui999:可以 findone,更简洁;另外,不存在返回 null 即可啦; | |
| 53 | - for (CrmPermissionLevelEnum levelEnum : CrmPermissionLevelEnum.values()) { | |
| 54 | - if (ObjUtil.equal(levelEnum.level, level)) { | |
| 55 | - return levelEnum.name; | |
| 56 | - } | |
| 57 | - } | |
| 58 | - return ""; | |
| 59 | - } | |
| 60 | - | |
| 61 | 51 | } | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessService.java
| ... | ... | @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.crm.service.business; |
| 3 | 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | 4 | import cn.iocoder.yudao.module.crm.controller.admin.business.vo.*; |
| 5 | 5 | import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; |
| 6 | -import cn.iocoder.yudao.module.crm.framework.core.service.CrmPermissionValidateService; | |
| 7 | 6 | |
| 8 | 7 | import javax.validation.Valid; |
| 9 | 8 | import java.util.Collection; |
| ... | ... | @@ -14,7 +13,7 @@ import java.util.List; |
| 14 | 13 | * |
| 15 | 14 | * @author ljlleo |
| 16 | 15 | */ |
| 17 | -public interface CrmBusinessService extends CrmPermissionValidateService { | |
| 16 | +public interface CrmBusinessService { | |
| 18 | 17 | |
| 19 | 18 | /** |
| 20 | 19 | * 创建商机 | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java
| ... | ... | @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.crm.service.business; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | 4 | import cn.hutool.core.collection.ListUtil; |
| 5 | -import cn.hutool.core.util.ObjUtil; | |
| 6 | 5 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 7 | 6 | import cn.iocoder.yudao.module.crm.controller.admin.business.vo.*; |
| 8 | 7 | import cn.iocoder.yudao.module.crm.convert.business.CrmBusinessConvert; |
| ... | ... | @@ -51,7 +50,7 @@ public class CrmBusinessServiceImpl implements CrmBusinessService { |
| 51 | 50 | |
| 52 | 51 | // 创建数据权限 |
| 53 | 52 | crmPermissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_BUSINESS.getType()) |
| 54 | - .setBizId(business.getId()).setUserId(userId).setPermissionLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 设置当前操作的人为负责人 | |
| 53 | + .setBizId(business.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 设置当前操作的人为负责人 | |
| 55 | 54 | |
| 56 | 55 | // 返回 |
| 57 | 56 | return business.getId(); |
| ... | ... | @@ -59,8 +58,8 @@ public class CrmBusinessServiceImpl implements CrmBusinessService { |
| 59 | 58 | |
| 60 | 59 | @Override |
| 61 | 60 | @Transactional(rollbackFor = Exception.class) |
| 62 | - @CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, getIdFor = CrmBusinessUpdateReqVO.class, | |
| 63 | - permissionLevel = CrmPermissionLevelEnum.WRITE) | |
| 61 | + @CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#updateReqVO.id", | |
| 62 | + level = CrmPermissionLevelEnum.WRITE) | |
| 64 | 63 | public void updateBusiness(CrmBusinessUpdateReqVO updateReqVO) { |
| 65 | 64 | // 校验存在 |
| 66 | 65 | validateBusinessExists(updateReqVO.getId()); |
| ... | ... | @@ -71,7 +70,7 @@ public class CrmBusinessServiceImpl implements CrmBusinessService { |
| 71 | 70 | |
| 72 | 71 | @Override |
| 73 | 72 | @Transactional(rollbackFor = Exception.class) |
| 74 | - @CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, permissionLevel = CrmPermissionLevelEnum.WRITE) | |
| 73 | + @CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, level = CrmPermissionLevelEnum.WRITE) | |
| 75 | 74 | public void deleteBusiness(Long id) { |
| 76 | 75 | // 校验存在 |
| 77 | 76 | validateBusinessExists(id); |
| ... | ... | @@ -88,7 +87,7 @@ public class CrmBusinessServiceImpl implements CrmBusinessService { |
| 88 | 87 | } |
| 89 | 88 | |
| 90 | 89 | @Override |
| 91 | - @CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, permissionLevel = CrmPermissionLevelEnum.READ) | |
| 90 | + @CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, level = CrmPermissionLevelEnum.READ) | |
| 92 | 91 | public CrmBusinessDO getBusiness(Long id) { |
| 93 | 92 | return businessMapper.selectById(id); |
| 94 | 93 | } |
| ... | ... | @@ -134,14 +133,4 @@ public class CrmBusinessServiceImpl implements CrmBusinessService { |
| 134 | 133 | // 3. TODO 记录转移日志 |
| 135 | 134 | } |
| 136 | 135 | |
| 137 | - @Override | |
| 138 | - public boolean validateBizIdExists(Integer bizType, Long bizId) { | |
| 139 | - // 1. 校验模块类型 | |
| 140 | - if (!ObjUtil.equal(CrmBizTypeEnum.CRM_BUSINESS.getType(), bizId)) { | |
| 141 | - return false; | |
| 142 | - } | |
| 143 | - // 2. 校验是否存在 | |
| 144 | - return businessMapper.selectById(bizId) != null; | |
| 145 | - } | |
| 146 | - | |
| 147 | 136 | } | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/ContactServiceImpl.java
| ... | ... | @@ -47,7 +47,7 @@ public class ContactServiceImpl implements ContactService { |
| 47 | 47 | |
| 48 | 48 | // 创建数据权限 |
| 49 | 49 | crmPermissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CONTACTS.getType()) |
| 50 | - .setBizId(contact.getId()).setUserId(userId).setPermissionLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 设置当前操作的人为负责人 | |
| 50 | + .setBizId(contact.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 设置当前操作的人为负责人 | |
| 51 | 51 | |
| 52 | 52 | // 返回 |
| 53 | 53 | return contact.getId(); |
| ... | ... | @@ -55,8 +55,7 @@ public class ContactServiceImpl implements ContactService { |
| 55 | 55 | |
| 56 | 56 | @Override |
| 57 | 57 | @Transactional(rollbackFor = Exception.class) |
| 58 | - @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACTS, getIdFor = ContactUpdateReqVO.class, | |
| 59 | - permissionLevel = CrmPermissionLevelEnum.WRITE) | |
| 58 | + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACTS, level = CrmPermissionLevelEnum.WRITE) | |
| 60 | 59 | public void updateContact(ContactUpdateReqVO updateReqVO) { |
| 61 | 60 | // 校验存在 |
| 62 | 61 | validateContactExists(updateReqVO.getId()); |
| ... | ... | @@ -69,7 +68,7 @@ public class ContactServiceImpl implements ContactService { |
| 69 | 68 | |
| 70 | 69 | @Override |
| 71 | 70 | @Transactional(rollbackFor = Exception.class) |
| 72 | - @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACTS, permissionLevel = CrmPermissionLevelEnum.WRITE) | |
| 71 | + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACTS, level = CrmPermissionLevelEnum.WRITE) | |
| 73 | 72 | public void deleteContact(Long id) { |
| 74 | 73 | // 校验存在 |
| 75 | 74 | validateContactExists(id); |
| ... | ... | @@ -86,7 +85,7 @@ public class ContactServiceImpl implements ContactService { |
| 86 | 85 | } |
| 87 | 86 | |
| 88 | 87 | @Override |
| 89 | - @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACTS, permissionLevel = CrmPermissionLevelEnum.READ) | |
| 88 | + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACTS, level = CrmPermissionLevelEnum.READ) | |
| 90 | 89 | public ContactDO getContact(Long id) { |
| 91 | 90 | return contactMapper.selectById(id); |
| 92 | 91 | } | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/ContractServiceImpl.java
| ... | ... | @@ -46,7 +46,7 @@ public class ContractServiceImpl implements ContractService { |
| 46 | 46 | |
| 47 | 47 | // 创建数据权限 |
| 48 | 48 | crmPermissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CONTRACT.getType()) |
| 49 | - .setBizId(contract.getId()).setUserId(userId).setPermissionLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 设置当前操作的人为负责人 | |
| 49 | + .setBizId(contract.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 设置当前操作的人为负责人 | |
| 50 | 50 | |
| 51 | 51 | // 返回 |
| 52 | 52 | return contract.getId(); |
| ... | ... | @@ -54,8 +54,7 @@ public class ContractServiceImpl implements ContractService { |
| 54 | 54 | |
| 55 | 55 | @Override |
| 56 | 56 | @Transactional(rollbackFor = Exception.class) |
| 57 | - @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTRACT, getIdFor = ContractUpdateReqVO.class, | |
| 58 | - permissionLevel = CrmPermissionLevelEnum.WRITE) | |
| 57 | + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTRACT, level = CrmPermissionLevelEnum.WRITE) | |
| 59 | 58 | public void updateContract(ContractUpdateReqVO updateReqVO) { |
| 60 | 59 | // 校验存在 |
| 61 | 60 | validateContractExists(updateReqVO.getId()); |
| ... | ... | @@ -66,7 +65,7 @@ public class ContractServiceImpl implements ContractService { |
| 66 | 65 | |
| 67 | 66 | @Override |
| 68 | 67 | @Transactional(rollbackFor = Exception.class) |
| 69 | - @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTRACT, permissionLevel = CrmPermissionLevelEnum.WRITE) | |
| 68 | + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTRACT, level = CrmPermissionLevelEnum.WRITE) | |
| 70 | 69 | public void deleteContract(Long id) { |
| 71 | 70 | // 校验存在 |
| 72 | 71 | validateContractExists(id); |
| ... | ... | @@ -83,7 +82,7 @@ public class ContractServiceImpl implements ContractService { |
| 83 | 82 | } |
| 84 | 83 | |
| 85 | 84 | @Override |
| 86 | - @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTRACT, permissionLevel = CrmPermissionLevelEnum.READ) | |
| 85 | + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTRACT, level = CrmPermissionLevelEnum.READ) | |
| 87 | 86 | public ContractDO getContract(Long id) { |
| 88 | 87 | return contractMapper.selectById(id); |
| 89 | 88 | } | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java
| 1 | 1 | package cn.iocoder.yudao.module.crm.service.customer; |
| 2 | 2 | |
| 3 | -import cn.hutool.core.collection.CollUtil; | |
| 4 | -import cn.hutool.core.collection.ListUtil; | |
| 5 | 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 6 | 4 | import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.*; |
| 7 | 5 | import cn.iocoder.yudao.module.crm.convert.customer.CrmCustomerConvert; |
| ... | ... | @@ -12,13 +10,11 @@ import cn.iocoder.yudao.module.crm.framework.enums.CrmBizTypeEnum; |
| 12 | 10 | import cn.iocoder.yudao.module.crm.framework.enums.CrmPermissionLevelEnum; |
| 13 | 11 | import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; |
| 14 | 12 | import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; |
| 15 | -import cn.iocoder.yudao.module.system.api.dept.DeptApi; | |
| 16 | 13 | import org.springframework.stereotype.Service; |
| 17 | 14 | import org.springframework.transaction.annotation.Transactional; |
| 18 | 15 | import org.springframework.validation.annotation.Validated; |
| 19 | 16 | |
| 20 | 17 | import javax.annotation.Resource; |
| 21 | -import java.util.Collection; | |
| 22 | 18 | import java.util.List; |
| 23 | 19 | import java.util.Objects; |
| 24 | 20 | |
| ... | ... | @@ -47,7 +43,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { |
| 47 | 43 | |
| 48 | 44 | // 创建数据权限 |
| 49 | 45 | crmPermissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType()) |
| 50 | - .setBizId(customer.getId()).setUserId(userId).setPermissionLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 设置当前操作的人为负责人 | |
| 46 | + .setBizId(customer.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 设置当前操作的人为负责人 | |
| 51 | 47 | |
| 52 | 48 | // 返回 |
| 53 | 49 | return customer.getId(); |
| ... | ... | @@ -55,8 +51,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { |
| 55 | 51 | |
| 56 | 52 | @Override |
| 57 | 53 | @Transactional(rollbackFor = Exception.class) |
| 58 | - @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, getIdFor = CrmCustomerUpdateReqVO.class, | |
| 59 | - permissionLevel = CrmPermissionLevelEnum.WRITE) | |
| 54 | + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, level = CrmPermissionLevelEnum.WRITE) | |
| 60 | 55 | public void updateCustomer(CrmCustomerUpdateReqVO updateReqVO) { |
| 61 | 56 | // 校验存在 |
| 62 | 57 | validateCustomerExists(updateReqVO.getId()); |
| ... | ... | @@ -69,7 +64,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { |
| 69 | 64 | |
| 70 | 65 | @Override |
| 71 | 66 | @Transactional(rollbackFor = Exception.class) |
| 72 | - @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, permissionLevel = CrmPermissionLevelEnum.WRITE) | |
| 67 | + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, level = CrmPermissionLevelEnum.WRITE) | |
| 73 | 68 | public void deleteCustomer(Long id) { |
| 74 | 69 | // 校验存在 |
| 75 | 70 | validateCustomerExists(id); |
| ... | ... | @@ -86,7 +81,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { |
| 86 | 81 | } |
| 87 | 82 | |
| 88 | 83 | @Override |
| 89 | - @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, permissionLevel = CrmPermissionLevelEnum.READ) | |
| 84 | + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, level = CrmPermissionLevelEnum.READ) | |
| 90 | 85 | public CrmCustomerDO getCustomer(Long id) { |
| 91 | 86 | return customerMapper.selectById(id); |
| 92 | 87 | } | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionService.java
| ... | ... | @@ -6,8 +6,8 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO; |
| 6 | 6 | import cn.iocoder.yudao.module.crm.framework.enums.CrmBizTypeEnum; |
| 7 | 7 | import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; |
| 8 | 8 | import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionPageReqBO; |
| 9 | +import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO; | |
| 9 | 10 | import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionUpdateReqBO; |
| 10 | -import cn.iocoder.yudao.module.crm.service.permission.bo.CrmTransferPermissionReqBO; | |
| 11 | 11 | |
| 12 | 12 | import javax.validation.Valid; |
| 13 | 13 | import java.util.List; |
| ... | ... | @@ -63,9 +63,9 @@ public interface CrmPermissionService { |
| 63 | 63 | /** |
| 64 | 64 | * 数据权限转移 |
| 65 | 65 | * |
| 66 | - * @param crmTransferPermissionReqBO 数据权限转移请求 | |
| 66 | + * @param crmPermissionTransferReqBO 数据权限转移请求 | |
| 67 | 67 | */ |
| 68 | - void transferPermission(@Valid CrmTransferPermissionReqBO crmTransferPermissionReqBO); | |
| 68 | + void transferPermission(@Valid CrmPermissionTransferReqBO crmPermissionTransferReqBO); | |
| 69 | 69 | |
| 70 | 70 | /** |
| 71 | 71 | * 获取数据权限分页数据 | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionServiceImpl.java
| ... | ... | @@ -10,8 +10,8 @@ import cn.iocoder.yudao.module.crm.framework.enums.CrmBizTypeEnum; |
| 10 | 10 | import cn.iocoder.yudao.module.crm.framework.enums.CrmPermissionLevelEnum; |
| 11 | 11 | import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; |
| 12 | 12 | import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionPageReqBO; |
| 13 | +import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO; | |
| 13 | 14 | import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionUpdateReqBO; |
| 14 | -import cn.iocoder.yudao.module.crm.service.permission.bo.CrmTransferPermissionReqBO; | |
| 15 | 15 | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| 16 | 16 | import org.springframework.stereotype.Service; |
| 17 | 17 | import org.springframework.transaction.annotation.Transactional; |
| ... | ... | @@ -55,13 +55,12 @@ public class CrmPermissionServiceImpl implements CrmPermissionService { |
| 55 | 55 | @Override |
| 56 | 56 | @Transactional(rollbackFor = Exception.class) |
| 57 | 57 | public void updatePermission(CrmPermissionUpdateReqBO updateBO) { |
| 58 | - // TODO @puhui999:这里 1.1 1.2;下面 2.;这样更有序一点; | |
| 59 | - // 1. 校验用户是否存在 | |
| 58 | + // 1.1 校验用户是否存在 | |
| 60 | 59 | adminUserApi.validateUserList(Collections.singletonList(updateBO.getUserId())); |
| 61 | - // 2. 校验存在 | |
| 60 | + // 1.2 校验存在 | |
| 62 | 61 | validateCrmPermissionExists(updateBO.getId()); |
| 63 | 62 | |
| 64 | - // 更新操作 | |
| 63 | + // 2. 更新操作 | |
| 65 | 64 | CrmPermissionDO updateDO = CrmPermissionConvert.INSTANCE.convert(updateBO); |
| 66 | 65 | crmPermissionMapper.updateById(updateDO); |
| 67 | 66 | } |
| ... | ... | @@ -93,43 +92,41 @@ public class CrmPermissionServiceImpl implements CrmPermissionService { |
| 93 | 92 | } |
| 94 | 93 | |
| 95 | 94 | @Override |
| 96 | - public void transferPermission(CrmTransferPermissionReqBO transferReqBO) { | |
| 95 | + public void transferPermission(CrmPermissionTransferReqBO transferReqBO) { | |
| 97 | 96 | // 1. 校验数据权限-是否是负责人,只有负责人才可以转移 |
| 98 | 97 | CrmPermissionDO oldPermission = crmPermissionMapper.selectByBizTypeAndBizIdByUserId(transferReqBO.getBizType(), |
| 99 | 98 | transferReqBO.getBizId(), transferReqBO.getUserId()); |
| 100 | 99 | String crmName = CrmBizTypeEnum.getNameByType(transferReqBO.getBizType()); |
| 101 | 100 | // TODO 校验是否为超级管理员 || 1 |
| 102 | - if (oldPermission == null || !isOwner(oldPermission.getPermissionLevel())) { | |
| 101 | + if (oldPermission == null || !isOwner(oldPermission.getLevel())) { | |
| 103 | 102 | throw exception(CRM_PERMISSION_DENIED, crmName); |
| 104 | 103 | } |
| 105 | - | |
| 106 | - // TODO @puhui999:这个顺序编号,看看调整下;2. 后面是 2.1 ,结果没 2.2 有点怪; | |
| 107 | - // 2. 校验转移对象是否已经是该负责人 | |
| 104 | + // 1.1 校验转移对象是否已经是该负责人 | |
| 108 | 105 | if (ObjUtil.equal(transferReqBO.getNewOwnerUserId(), oldPermission.getUserId())) { |
| 109 | 106 | throw exception(CRM_PERMISSION_MODEL_TRANSFER_FAIL_OWNER_USER_EXISTS, crmName); |
| 110 | 107 | } |
| 111 | - // 2.1 校验新负责人是否存在 | |
| 108 | + // 1.2 校验新负责人是否存在 | |
| 112 | 109 | adminUserApi.validateUserList(Collections.singletonList(transferReqBO.getNewOwnerUserId())); |
| 113 | - // 3. 权限转移 | |
| 110 | + | |
| 111 | + // 2. 权限转移 | |
| 114 | 112 | List<CrmPermissionDO> permissions = crmPermissionMapper.selectByBizTypeAndBizId( |
| 115 | 113 | transferReqBO.getBizType(), transferReqBO.getBizId()); // 获取所有团队成员 |
| 116 | - // 3.1 校验新负责人是否在团队成员中 | |
| 114 | + // 2.1 校验新负责人是否在团队成员中 | |
| 117 | 115 | CrmPermissionDO permission = CollUtil.findOne(permissions, |
| 118 | 116 | item -> ObjUtil.equal(item.getUserId(), transferReqBO.getNewOwnerUserId())); |
| 119 | 117 | if (permission == null) { // 不存在则以负责人的级别加入这个团队 |
| 120 | 118 | crmPermissionMapper.insert(new CrmPermissionDO().setBizType(transferReqBO.getBizType()) |
| 121 | 119 | .setBizId(transferReqBO.getBizId()).setUserId(transferReqBO.getNewOwnerUserId()) |
| 122 | - .setPermissionLevel(CrmPermissionLevelEnum.OWNER.getLevel())); | |
| 120 | + .setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); | |
| 123 | 121 | |
| 124 | 122 | } else { // 存在则修改权限级别 |
| 125 | 123 | crmPermissionMapper.updateById(new CrmPermissionDO().setId(permission.getId()) |
| 126 | - .setPermissionLevel(CrmPermissionLevelEnum.OWNER.getLevel())); | |
| 124 | + .setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); | |
| 127 | 125 | } |
| 128 | - | |
| 129 | - // 4. 老负责人处理 | |
| 130 | - if (transferReqBO.getJoinTeam()) { // 加入团队 | |
| 126 | + // 2.2. 老负责人处理 | |
| 127 | + if (transferReqBO.getOldOwnerPermissionLevel() != null) { // 加入团队 | |
| 131 | 128 | crmPermissionMapper.updateById(new CrmPermissionDO().setId(oldPermission.getId()) |
| 132 | - .setPermissionLevel(transferReqBO.getPermissionLevel())); // 设置加入团队后的级别 | |
| 129 | + .setLevel(transferReqBO.getOldOwnerPermissionLevel())); // 设置加入团队后的级别 | |
| 133 | 130 | return; |
| 134 | 131 | } |
| 135 | 132 | crmPermissionMapper.deleteById(oldPermission.getId()); // 移除 | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/bo/CrmPermissionCreateReqBO.java
| ... | ... | @@ -33,12 +33,11 @@ public class CrmPermissionCreateReqBO { |
| 33 | 33 | @NotNull(message = "Crm 数据编号不能为空") |
| 34 | 34 | private Long bizId; |
| 35 | 35 | |
| 36 | - // TODO @puhui999:简化成 level | |
| 37 | 36 | /** |
| 38 | 37 | * 权限级别 |
| 39 | 38 | */ |
| 40 | 39 | @NotNull(message = "权限级别不能为空") |
| 41 | 40 | @InEnum(CrmPermissionLevelEnum.class) |
| 42 | - private Integer permissionLevel; | |
| 41 | + private Integer level; | |
| 43 | 42 | |
| 44 | 43 | } | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/bo/CrmTransferPermissionReqBO.java renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/bo/CrmPermissionTransferReqBO.java
| ... | ... | @@ -7,14 +7,13 @@ import lombok.Data; |
| 7 | 7 | |
| 8 | 8 | import javax.validation.constraints.NotNull; |
| 9 | 9 | |
| 10 | -// TODO @puhui999:CrmPermissionTransferReqBO | |
| 11 | 10 | /** |
| 12 | 11 | * 数据权限转移 Request BO |
| 13 | 12 | * |
| 14 | 13 | * @author HUIHUI |
| 15 | 14 | */ |
| 16 | 15 | @Data |
| 17 | -public class CrmTransferPermissionReqBO { | |
| 16 | +public class CrmPermissionTransferReqBO { | |
| 18 | 17 | |
| 19 | 18 | /** |
| 20 | 19 | * 当前登录用户编号 |
| ... | ... | @@ -40,17 +39,10 @@ public class CrmTransferPermissionReqBO { |
| 40 | 39 | @NotNull(message = "新负责人的用户编号不能为空") |
| 41 | 40 | private Long newOwnerUserId; |
| 42 | 41 | |
| 43 | - // TODO @puhui999:joinTeam 可以合并成 permissionLevel 里;oldOwnerPermissionLevel;这样 null 说明移除,因为都换负责人啦; | |
| 44 | 42 | /** |
| 45 | - * 老负责人是否加入团队,是/否 | |
| 46 | - */ | |
| 47 | - @NotNull(message = "老负责人是否加入团队不能为空") | |
| 48 | - private Boolean joinTeam; | |
| 49 | - | |
| 50 | - /** | |
| 51 | - * 老负责人加入团队后的权限级别。如果 {@link #joinTeam} 为 false, permissionLevel 为 null | |
| 43 | + * 老负责人加入团队后的权限级别。如果 null 说明移除 | |
| 52 | 44 | * 关联 {@link CrmPermissionLevelEnum} |
| 53 | 45 | */ |
| 54 | - private Integer permissionLevel; | |
| 46 | + private Integer oldOwnerPermissionLevel; | |
| 55 | 47 | |
| 56 | 48 | } | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/bo/CrmPermissionUpdateReqBO.java
| 1 | 1 | package cn.iocoder.yudao.module.crm.service.permission.bo; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.common.validation.InEnum; |
| 4 | -import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO; | |
| 5 | -import cn.iocoder.yudao.module.crm.framework.enums.CrmBizTypeEnum; | |
| 6 | 4 | import cn.iocoder.yudao.module.crm.framework.enums.CrmPermissionLevelEnum; |
| 7 | 5 | import lombok.Data; |
| 8 | 6 | |
| ... | ... | @@ -28,25 +26,11 @@ public class CrmPermissionUpdateReqBO { |
| 28 | 26 | @NotNull(message = "用户编号不能为空") |
| 29 | 27 | private Long userId; |
| 30 | 28 | |
| 31 | - // TODO @puhui999:id 字段,和 bizType + bizId 是否二选一; | |
| 32 | - /** | |
| 33 | - * Crm 类型 | |
| 34 | - */ | |
| 35 | - @NotNull(message = "Crm 类型不能为空") | |
| 36 | - @InEnum(CrmBizTypeEnum.class) | |
| 37 | - private Integer bizType; | |
| 38 | - /** | |
| 39 | - * 数据编号 | |
| 40 | - */ | |
| 41 | - @NotNull(message = "Crm 数据编号不能为空") | |
| 42 | - private Long bizId; | |
| 43 | - | |
| 44 | - // TODO @puhui999:简化成 level | |
| 45 | 29 | /** |
| 46 | 30 | * 权限级别 |
| 47 | 31 | */ |
| 48 | 32 | @NotNull(message = "权限级别不能为空") |
| 49 | 33 | @InEnum(CrmPermissionLevelEnum.class) |
| 50 | - private Integer permissionLevel; | |
| 34 | + private Integer level; | |
| 51 | 35 | |
| 52 | 36 | } | ... | ... |
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/dept/PostApi.java
| 1 | 1 | package cn.iocoder.yudao.module.system.api.dept; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | |
| 4 | +import cn.iocoder.yudao.module.system.api.dept.dto.PostRespDTO; | |
| 5 | + | |
| 3 | 6 | import java.util.Collection; |
| 7 | +import java.util.List; | |
| 8 | +import java.util.Map; | |
| 4 | 9 | |
| 5 | 10 | /** |
| 6 | 11 | * 岗位 API 接口 |
| ... | ... | @@ -18,4 +23,11 @@ public interface PostApi { |
| 18 | 23 | */ |
| 19 | 24 | void validPostList(Collection<Long> ids); |
| 20 | 25 | |
| 26 | + List<PostRespDTO> getPostList(Collection<Long> ids); | |
| 27 | + | |
| 28 | + default Map<Long, PostRespDTO> getPostMap(Collection<Long> ids) { | |
| 29 | + List<PostRespDTO> list = getPostList(ids); | |
| 30 | + return CollectionUtils.convertMap(list, PostRespDTO::getId); | |
| 31 | + } | |
| 32 | + | |
| 21 | 33 | } | ... | ... |
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/dept/dto/PostRespDTO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.system.api.dept.dto; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | |
| 4 | +import lombok.Data; | |
| 5 | + | |
| 6 | +/** | |
| 7 | + * 岗位 Response DTO | |
| 8 | + * | |
| 9 | + * @author 芋道源码 | |
| 10 | + */ | |
| 11 | +@Data | |
| 12 | +public class PostRespDTO { | |
| 13 | + | |
| 14 | + /** | |
| 15 | + * 岗位序号 | |
| 16 | + */ | |
| 17 | + private Long id; | |
| 18 | + /** | |
| 19 | + * 岗位名称 | |
| 20 | + */ | |
| 21 | + private String name; | |
| 22 | + /** | |
| 23 | + * 岗位编码 | |
| 24 | + */ | |
| 25 | + private String code; | |
| 26 | + /** | |
| 27 | + * 岗位排序 | |
| 28 | + */ | |
| 29 | + private Integer sort; | |
| 30 | + /** | |
| 31 | + * 状态 | |
| 32 | + * | |
| 33 | + * 枚举 {@link CommonStatusEnum} | |
| 34 | + */ | |
| 35 | + private Integer status; | |
| 36 | + | |
| 37 | +} | ... | ... |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/dept/PostApiImpl.java
| 1 | 1 | package cn.iocoder.yudao.module.system.api.dept; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.module.system.api.dept.dto.PostRespDTO; | |
| 4 | +import cn.iocoder.yudao.module.system.convert.dept.PostConvert; | |
| 3 | 5 | import cn.iocoder.yudao.module.system.service.dept.PostService; |
| 4 | 6 | import org.springframework.stereotype.Service; |
| 5 | 7 | |
| 6 | 8 | import javax.annotation.Resource; |
| 7 | 9 | import java.util.Collection; |
| 10 | +import java.util.List; | |
| 8 | 11 | |
| 9 | 12 | /** |
| 10 | 13 | * 岗位 API 实现类 |
| ... | ... | @@ -22,4 +25,9 @@ public class PostApiImpl implements PostApi { |
| 22 | 25 | postService.validatePostList(ids); |
| 23 | 26 | } |
| 24 | 27 | |
| 28 | + @Override | |
| 29 | + public List<PostRespDTO> getPostList(Collection<Long> ids) { | |
| 30 | + return PostConvert.INSTANCE.convert(postService.getPostList(ids)); | |
| 31 | + } | |
| 32 | + | |
| 25 | 33 | } | ... | ... |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/dept/PostConvert.java
| 1 | 1 | package cn.iocoder.yudao.module.system.convert.dept; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | +import cn.iocoder.yudao.module.system.api.dept.dto.PostRespDTO; | |
| 4 | 5 | import cn.iocoder.yudao.module.system.controller.admin.dept.vo.post.*; |
| 5 | 6 | import cn.iocoder.yudao.module.system.dal.dataobject.dept.PostDO; |
| 6 | 7 | import org.mapstruct.Mapper; |
| ... | ... | @@ -25,4 +26,6 @@ public interface PostConvert { |
| 25 | 26 | |
| 26 | 27 | List<PostExcelVO> convertList03(List<PostDO> list); |
| 27 | 28 | |
| 29 | + List<PostRespDTO> convert(List<PostDO> postList); | |
| 30 | + | |
| 28 | 31 | } | ... | ... |