You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1278 lines
106 KiB

1 month ago
��diff --git a/crm-auth/src/main/java/com/crm/auth/config/DataInitializer.java b/crm-auth/src/main/java/com/crm/auth/config/DataInitializer.java
index 189453e..a13cefb 100644
--- a/crm-auth/src/main/java/com/crm/auth/config/DataInitializer.java
+++ b/crm-auth/src/main/java/com/crm/auth/config/DataInitializer.java
@@ -1,15 +1,23 @@
package com.crm.auth.config;
-import com.crm.auth.domain.entity.*;
-import com.crm.auth.mapper.*;
-import com.crm.auth.service.ISysRoleService;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.crm.auth.domain.entity.SysMenu;
+import com.crm.auth.domain.entity.SysRole;
+import com.crm.auth.domain.entity.SysRoleMenu;
+import com.crm.auth.mapper.SysMenuMapper;
+import com.crm.auth.mapper.SysRoleMapper;
+import com.crm.auth.mapper.SysRoleMenuMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
+import java.util.List;
+
/**
- * �~d��|��HrA]R��oP�V�5h0}�h(h��Z���YÓ�`�Zɓ�Q�jt$1��~4s}���c�S�i?j{��~��`[�?jW�y�B\� }OM_W��f� }OW�y�?"�&1�Wb~u��?+ * �~d��|��HrA]R��oP�V�5h}���P�t�Y0}�Y�_��Z���Y��0a 0�Y� ̓�0}Y�G��y��k��?+ * <p>R��mP�oT�u�~��`[�?jW�y�B\� zO���qx���UM_W��f� }OW�y�,�x��?button ɓ�Q�j�xv}
+ * �����vO�)14Q�t�R.|R��U�Skx���U�aYt�c�X�WDR-0012�Y� ?/p>
*/
@Slf4j
@Component
@@ -19,41 +27,87 @@ public class DataInitializer implements CommandLineRunner {
private final SysRoleMapper sysRoleMapper;
private final SysMenuMapper sysMenuMapper;
private final SysRoleMenuMapper sysRoleMenuMapper;
- private final SysUserRoleMapper sysUserRoleMapper;
@Override
public void run(String... args) {
- if (sysRoleMapper.selectCount(null) > 0) {
- log.info("ɓ�Q�j��HrA]�[�c�tf���}�t�[C~R��oP�V�?);
- return;
+ log.info("��F�ɓ�Q�j��HrA]R��oP�V�(h��̓?..");
+
+ // ---- P�oT�uYt�c�X�0kDMIN ----
+ SysRole adminRole = sysRoleMapper.selectOne(
+ new LambdaQueryWrapper<SysRole>().eq(SysRole::getRoleCode, "ROLE_ADMIN"));
+ if (adminRole == null) {
+ adminRole = new SysRole();
+ adminRole.setRoleName("�~��`[�?);
+ adminRole.setRoleCode("ROLE_ADMIN");
+ adminRole.setDataScope(4);
+ adminRole.setSort(0);
+ adminRole.setBuiltin(true);
+ sysRoleMapper.insert(adminRole);
+ log.info("R��mP�oT�uYt�c�X�k}", adminRole.getRoleCode());
+ } else if (!Boolean.TRUE.equals(adminRole.getBuiltin())) {
+ adminRole.setBuiltin(true);
+ sysRoleMapper.updateById(adminRole);
+ log.info("t�0��P�oT�uYt�c�X͓�V���k}", adminRole.getRoleCode());
+ }
+
+ // ---- �m� �~E0m0��f0}�~d��|�~��` ----
+ SysMenu sysManage = findMenu("�~d��|�~��`", 0L);
+ if (sysManage == null) {
+ sysManage = insertMenu("�~d��|�~��`", 0L, 1, null, null, 0);
+ }
+
+ // ---- \m�\����n] ----
+ SysMenu roleManage = findMenu("Yt�c�X�~��`", sysManage.getId());
+ if (roleManage == null) {
+ roleManage = insertMenu("Yt�c�X�~��`", sysManage.getId(), 2,
+ "/system/role", "system/role/index", 1);
+ }
+ SysMenu menuManage = findMenu("��n]�~��`", sysManage.getId());
+ if (menuManage == null) {
+ menuManage = insertMenu("��n]�~��`", sysManage.getId(), 2,
+ "/system/menu", "system/menu/index", 2);
+ }
+ SysMenu deptManage = findMenu("��)1�h�~��`", sysManage.getId());
+ if (deptManage == null) {
+ deptManage = insertMenu("��)1�h�~��`", sysManage.getId(), 2,
+ "/system/dept", "system/dept/index", 3);
+ }
+
+ // ---- Yt�c�X�~��` button ɓ�Q�j�xv}ADR-0012�?----
+ insertButtonIfAbsent("Yt�c�XR�Di0", roleManage.getId(), "crm:role:list", "/api/roles/page", 1);
+ insertButtonIfAbsent("Yt�c�X�t=�Q", roleManage.getId(), "crm:role:detail", "/api/roles/detail", 2);
+ insertButtonIfAbsent("Yt�c�X�m�o�t", roleManage.getId(), "crm:role:save", "/api/roles/saveOrUpdate", 3);
+ insertButtonIfAbsent("Yt�c�XR��r�j", roleManage.getId(), "crm:role:delete", "/api/roles/delete", 4);
+ insertButtonIfAbsent("ɓ�Q�jR��U�S", roleManage.getId(), "crm:role:assign", "/api/roles/assign-resources", 5);
+
+ // ---- Yt�c�X-��n]b~u�"Xx���U�a���0An��� ȓYM_W��f�bɓ�Q�j�xv} ----
+ bindIfAbsent(adminRole.getId(), sysManage.getId());
+ bindIfAbsent(adminRole.getId(), roleManage.getId());
+ bindIfAbsent(adminRole.getId(), menuManage.getId());
+ bindIfAbsent(adminRole.getId(), deptManage.getId());
+ // b~uYt�c�X�~��`�m,[�kO�)14Q button ɓ�Q�j�?+ List<SysMenu> roleButtons = sysMenuMapper.selectList(
+ new LambdaQueryWrapper<SysMenu>()
+ .eq(SysMenu::getParentId, roleManage.getId())
+ .eq(SysMenu::getMenuType, 3));
+ for (SysMenu btn : roleButtons) {
+ bindIfAbsent(adminRole.getId(), btn.getId());
}
- log.info("�h(h��Z���Y�}\�W�o*[�[�i?j{�ɓ�Q�j��HrA]...");
-
- // ---- Yt�c�X ----
- SysRole adminRole = new SysRole();
- adminRole.setRoleName("�~��`[�?);
- adminRole.setRoleCode("ROLE_ADMIN");
- adminRole.setDataScope(4); // O�)14Q��HrA]
- adminRole.setSort(0);
- sysRoleMapper.insert(adminRole);
-
- // ---- ��n] ----
- // �m� �~E0m0�?- SysMenu sysManage = menu("�~d��|�~��`", 0L, 1, null, null, 0);
- SysMenu roleManage = menu("Yt�c�X�~��`", sysManage.getId(), 2, "/system/role", "system/role/index", 1);
- SysMenu menuManage = menu("��n]�~��`", sysManage.getId(), 2, "/system/menu", "system/menu/index", 2);
- SysMenu deptManage = menu("��)1�h�~��`", sysManage.getId(), 2, "/system/dept", "system/dept/index", 3);
-
- // ---- Yt�c�X-��n]b~u�"Xx���U�a���0An��� ȓYM_W��f} ----
- bind(adminRole.getId(), sysManage.getId());
- bind(adminRole.getId(), roleManage.getId());
- bind(adminRole.getId(), menuManage.getId());
- bind(adminRole.getId(), deptManage.getId());
-
- log.info("�i?j{�ɓ�Q�j��HrA]R��oP�V�'hlu��?);
+
+ log.info("ɓ�Q�j��HrA]R��oP�V�(h��̓�0lu��?);
+ }
+
+ // ==================== ���P�tHgmT�Y“ĉvx ====================
+
+ private SysMenu findMenu(String name, Long parentId) {
+ return sysMenuMapper.selectOne(
+ new LambdaQueryWrapper<SysMenu>()
+ .eq(SysMenu::getMenuName, name)
+ .eq(SysMenu::getParentId, parentId));
}
- private SysMenu menu(String name, Long parentId, int type, String path, String component, int sort) {
+ private SysMenu insertMenu(String name, Long parentId, int type,
+ String path, String component, int sort) {
SysMenu m = new SysMenu();
m.setParentId(parentId);
m.setMenuName(name);
@@ -63,10 +117,39 @@ public class DataInitializer implements CommandLineRunner {
m.setSort(sort);
m.setVisible(true);
sysMenuMapper.insert(m);
+ log.info("R��m��n]z��PcP�k} (type={})", name, type);
return m;
}
- private void bind(Long roleId, Long menuId) {
+ private void insertButtonIfAbsent(String name, Long parentId,
+ String perms, String apiUrl, int sort) {
+ Long count = sysMenuMapper.selectCount(
+ new LambdaQueryWrapper<SysMenu>().eq(SysMenu::getPerms, perms));
+ if (count > 0) {
+ return;
+ }
+ SysMenu btn = new SysMenu();
+ btn.setParentId(parentId);
+ btn.setMenuName(name);
+ btn.setMenuType(3);
+ btn.setSort(sort);
+ btn.setVisible(true);
+ btn.setPerms(perms);
+ btn.setDenyBehavior("hide");
+ btn.setApiUrl(apiUrl);
+ btn.setStatus("enabled");
+ sysMenuMapper.insert(btn);
+ log.info("R��mɓ�Q�j�xv0}{} -> {}", name, perms);
+ }
+
+ private void bindIfAbsent(Long roleId, Long menuId) {
+ Long count = sysRoleMenuMapper.selectCount(
+ new LambdaQueryWrapper<SysRoleMenu>()
+ .eq(SysRoleMenu::getRoleId, roleId)
+ .eq(SysRoleMenu::getMenuId, menuId));
+ if (count > 0) {
+ return;
+ }
SysRoleMenu rm = new SysRoleMenu();
rm.setRoleId(roleId);
rm.setMenuId(menuId);
diff --git a/crm-auth/src/main/java/com/crm/auth/constant/AuthConstants.java b/crm-auth/src/main/java/com/crm/auth/constant/AuthConstants.java
index 4f1f19c..4f4f17c 100644
--- a/crm-auth/src/main/java/com/crm/auth/constant/AuthConstants.java
+++ b/crm-auth/src/main/java/com/crm/auth/constant/AuthConstants.java
@@ -51,4 +51,13 @@ public interface AuthConstants {
/** ȕ�ppn�R�|��,a�a�"X�|�VoVO��0Nr`i~\mn��3lC~�?*/
int CODE_NOT_ORG_MEMBER = 61008;
+
+ /** Yt�c�XY��P�fȕ�pvx�"X*}.�wO��~R`� yO�f�����[e��x�y#��\�t�?*/
+ int CODE_ROLE_INVALID = 61010;
+
+ /** Yt�c�X+hr�[�c�tf�?*/
+ int CODE_ROLE_CODE_DUPLICATE = 61011;
+
+ /** P�oT�uYt�c�X�m�o�Y�"X���Y 2�Wĕ�0�W��-W*}.��O} */
+ int CODE_BUILTIN_ROLE_PROTECTED = 61012;
}
diff --git a/crm-auth/src/main/java/com/crm/auth/controller/AuthController.java b/crm-auth/src/main/java/com/crm/auth/controller/AuthController.java
index c14454f..34a50e7 100644
--- a/crm-auth/src/main/java/com/crm/auth/controller/AuthController.java
+++ b/crm-auth/src/main/java/com/crm/auth/controller/AuthController.java
@@ -27,20 +27,20 @@ public class AuthController {
private final IAuthService authService;
- @Operation(summary = "��Y�b����r'����}")
+ @Operation(summary = "��Y�b'����}�X�X.�?O��]�j��1lde�? , tags = "'����}")
@PostMapping("/login/dingtalk")
public Result<LoginResultDTO> loginByDingTalk(@RequestParam String authCode) {
return Result.success(authService.login(IdentityTypeEnum.DINGTALK, authCode));
}
- @Operation(summary = " Z)1"e")
+ @Operation(summary = " Z)1"e" , tags = "'����}")
@PostMapping("/logout")
public Result<Void> logout(HttpServletRequest request) {
authService.logout(resolveToken(request));
return Result.success();
}
- @Operation(summary = "~��\G_0��d�X'����}"�&1�W�m!O")
+ @Operation(summary = "~��\G_0��d�X'����}"�&1�W�m!O" , tags = "'����}")
@GetMapping("/me")
public Result<UserInfoDTO> me() {
return Result.success(authService.getCurrentUserInfo());
diff --git a/crm-auth/src/main/java/com/crm/auth/controller/ResourceController.java b/crm-auth/src/main/java/com/crm/auth/controller/ResourceController.java
index ee26018..9951b59 100644
--- a/crm-auth/src/main/java/com/crm/auth/controller/ResourceController.java
+++ b/crm-auth/src/main/java/com/crm/auth/controller/ResourceController.java
@@ -4,6 +4,7 @@ import com.crm.auth.domain.dto.ResourceNode;
import com.crm.auth.service.IResourceService;
import com.crm.base.domain.result.Result;
import com.crm.file.domain.dto.FileInfoDTO;
+import io.swagger.v3.oas.annotations.Operation;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@@ -25,18 +26,21 @@ public class ResourceController {
/** O�)1zV�t�R.|͓/b}��xO����nb�|�}\�X�~��[ parentId �R2r�?*/
@GetMapping("/list")
+ @Operation(summary = "~��\G_ɓ�Q�j�t�R.|͓? , tags = "ɓ�Q�j�-Wx��?)
public Result<List<ResourceNode>> listAll() {
return Result.success(resourceService.listAll());
}
/** “g�崓+h*}Hg#b�Y�xv}Z���Nr`i�\} */
@PostMapping("/saveOrUpdate")
+ @Operation(summary = "“g��,h�}�m� �~FM_W�?\m�\����n]/ɓ�Q�j�? , tags = "ɓ�Q�j�-Wx��?)
public Result<ResourceNode> saveOrUpdate(ResourceNode node) {
return Result.success(resourceService.save(node));
}
/** R��r�jz��PcP�X�`p/a�Y�ĉ��̓?+ �~F�NZoT�`�?*/
@PostMapping("/delete")
+ @Operation(summary = "R��r�j�m� �~FM_W�?\m�\����n]/ɓ�Q�j�? , tags = "ɓ�Q�j�-Wx��?)
public Result<Void> delete(@RequestParam Long id) {
resourceService.delete(id);
return Result.success();
@@ -44,6 +48,7 @@ public class ResourceController {
/** e��p#r�mAZ6}�Xxr�[?�oC�v͓��Ys�?*/
@PostMapping("/icon/upload")
+ @Operation(summary = "�mAZ6}e��p#r" , tags = "ɓ�Q�j�-Wx��?)
public Result<FileInfoDTO> uploadIcon(@RequestParam("file") MultipartFile file) {
return Result.success(resourceService.uploadIcon(file));
}
diff --git a/crm-auth/src/main/java/com/crm/auth/controller/RoleController.java b/crm-auth/src/main/java/com/crm/auth/controller/RoleController.java
new file mode 100644
index 0000000..fc5178d
--- /dev/null
+++ b/crm-auth/src/main/java/com/crm/auth/controller/RoleController.java
@@ -0,0 +1,97 @@
+package com.crm.auth.controller;
+
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.crm.auth.domain.dto.RoleDetailVO;
+import com.crm.auth.domain.entity.SysRole;
+import com.crm.auth.service.ISysRoleService;
+import com.crm.base.domain.result.PageResult;
+import com.crm.base.domain.result.Result;
+import io.swagger.v3.oas.annotations.Operation;
+import lombok.RequiredArgsConstructor;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Yt�c�X�~��`�~�4^Y�O�}ADR-0012�?+ * <p>��[��`O�%1,w���0[_�o b�[�6ljo�m�0xr RESTful�}\SU���]�} POST + T�$1�}Z��^}��\0W��f�tZ�WyeY�?/p>
+ * <p>ɓ�Q�j��C�W�1l�Q hasAuthority�~\Hoĕ.ar"����f�����W�o*[�[c�'1R�p,a�[R�HrHoĕ/akygZ-a2r</p>
+ */
+@RestController
+@RequestMapping("/api/roles")
+@RequiredArgsConstructor
+public class RoleController {
+
+ private final ISysRoleService sysRoleService;
+
+ @PostMapping("/page")
+ @PreAuthorize("hasAuthority('crm:role:list')")
+ @Operation(summary = "R��U 0̓�0��Yt�c�X", tags = "Yt�c�X�~��`")
+ public Result<PageResult<SysRole>> page(
+ @RequestParam(defaultValue = "1") Long current,
+ @RequestParam(defaultValue = "10") Long size,
+ @RequestParam(required = false) String keyword) {
+ Page<SysRole> page = new Page<>(current, size);
+ sysRoleService.lambdaQuery()
+ .like(StrUtil.isNotBlank(keyword), SysRole::getRoleName, keyword)
+ .page(page);
+ return Result.success(new PageResult<>(page));
+ }
+
+ @PostMapping("/saveOrUpdate")
+ @PreAuthorize("hasAuthority('crm:role:save')")
+ @Operation(summary = "“g�崓+h*}Hg#bW�y�?, tags = "Yt�c�X�~��`")
+ public Result<Void> saveOrUpdate(
+ @RequestParam(required = false) Long id,
+ @RequestParam String roleName,
+ @RequestParam String roleCode,
+ @RequestParam(defaultValue = "1") Integer dataScope,
+ @RequestParam(defaultValue = "0") Integer sort,
+ @RequestParam(required = false) String remark) {
+ SysRole role = new SysRole();
+ role.setId(id);
+ role.setRoleName(roleName);
+ role.setRoleCode(roleCode);
+ role.setDataScope(dataScope);
+ role.setSort(sort);
+ role.setRemark(remark);
+ sysRoleService.saveRole(role);
+ return Result.success();
+ }
+
+ @GetMapping("/detail")
+ @PreAuthorize("hasAuthority('crm:role:detail')")
+ @Operation(summary = "Yt�c�X�t=�Q�X�`��XHo�t�R.|ƕ�U�`�?, tags = "Yt�c�X�~��`")
+ public Result<RoleDetailVO> detail(@RequestParam Long roleId) {
+ return Result.success(sysRoleService.getRoleDetail(roleId));
+ }
+
+ @PostMapping("/assign-resources")
+ @PreAuthorize("hasAuthority('crm:role:assign')")
+ @Operation(summary = "R��U�SYt�c�Xɓ�Q�j�t�R.|�"X��O�#X�O�?O�)1zVǓc2]�?, tags = "Yt�c�X�~��`")
+ public Result<Void> assignResources(
+ @RequestParam Long roleId,
+ @RequestParam String resourceIds) {
+ List<Long> menuIdList = StrUtil.isBlank(resourceIds)
+ ? Collections.emptyList()
+ : Arrays.stream(resourceIds.split(","))
+ .filter(StrUtil::isNotBlank)
+ .map(idStr -> Long.valueOf(idStr.trim()))
+ .collect(Collectors.toList());
+ sysRoleService.assignResources(roleId, menuIdList);
+ return Result.success();
+ }
+
+ @PostMapping("/delete")
+ @PreAuthorize("hasAuthority('crm:role:delete')")
+ @Operation(summary = "R��r�jYt�c�X�"X��q��e�z��U�Sq��e0�?, tags = "Yt�c�X�~��`")
+ public Result<Void> delete(@RequestParam Long id) {
+ sysRoleService.deleteRoleCascade(id);
+ return Result.success();
+ }
+}
diff --git a/crm-auth/src/main/java/com/crm/auth/controller/SystemController.java b/crm-auth/src/main/java/com/crm/auth/controller/SystemController.java
index 790bf34..5535bcc 100644
--- a/crm-auth/src/main/java/com/crm/auth/controller/SystemController.java
+++ b/crm-auth/src/main/java/com/crm/auth/controller/SystemController.java
@@ -6,12 +6,9 @@ import com.crm.auth.domain.dto.UserListDTO;
import com.crm.auth.domain.dto.UserStatsVO;
import com.crm.auth.domain.entity.SysDept;
import com.crm.auth.domain.entity.SysMenu;
-import com.crm.auth.domain.entity.SysRole;
import com.crm.auth.domain.param.UserPageParam;
import com.crm.auth.service.IAuthUserService;
import com.crm.auth.service.ISysDeptService;
-import com.crm.auth.service.ISysMenuService;
-import com.crm.auth.service.ISysRoleService;
import com.crm.auth.security.PermissionResolver;
import com.crm.base.domain.result.PageResult;
import com.crm.base.domain.result.Result;
@@ -29,8 +26,6 @@ import java.util.stream.Collectors;
@RequiredArgsConstructor
public class SystemController {
- private final ISysRoleService sysRoleService;
- private final ISysMenuService sysMenuService;
private final ISysDeptService sysDeptService;
private final IAuthUserService authUserService;
private final PermissionResolver permissionResolver;
@@ -43,62 +38,6 @@ public class SystemController {
return Result.success(permissionResolver.visibleMenuTree(userId));
}
- // ========== Yt�c�X ==========
-
- @PostMapping("/roles/page")
- public Result<PageResult<SysRole>> rolePage(
- @RequestParam(defaultValue = "1") Long current,
- @RequestParam(defaultValue = "10") Long size,
- @RequestParam(required = false) String keyword) {
- Page<SysRole> page = new Page<>(current, size);
- sysRoleService.lambdaQuery()
- .like(StrUtil.isNotBlank(keyword), SysRole::getRoleName, keyword)
- .page(page);
- return Result.success(new PageResult<>(page));
- }
-
- @PostMapping("/roles/save")
- public Result<Void> saveRole(
- @RequestParam(required = false) Long id,
- @RequestParam String roleName,
- @RequestParam String roleCode,
- @RequestParam(defaultValue = "1") Integer dataScope,
- @RequestParam(defaultValue = "0") Integer sort,
- @RequestParam(required = false) String remark) {
- SysRole role = new SysRole();
- role.setId(id);
- role.setRoleName(roleName);
- role.setRoleCode(roleCode);
- role.setDataScope(dataScope);
- role.setSort(sort);
- role.setRemark(remark);
- sysRoleService.saveOrUpdate(role);
- return Result.success();
- }
-
- @PostMapping("/roles/delete")
- public Result<Void> deleteRole(@RequestParam Long id) {
- sysRoleService.deleteRoleCascade(id);
- return Result.success();
- }
-
- @PostMapping("/roles/assign-menus")
- public Result<Void> assignRoleMenus(@RequestParam Long roleId, @RequestParam String menuIds) {
- List<Long> menuIdList = StrUtil.isBlank(menuIds)
- ? Collections.emptyList()
- : Arrays.stream(menuIds.split(","))
- .filter(StrUtil::isNotBlank)
- .map(idStr -> Long.valueOf(idStr.trim()))
- .collect(Collectors.toList());
- sysRoleService.assignMenus(roleId, menuIdList);
- return Result.success();
- }
-
- @GetMapping("/roles/menus")
- public Result<List<Long>> roleMenus(@RequestParam Long roleId) {
- return Result.success(sysMenuService.getMenuIdsByRoleId(roleId));
- }
-
// ========== ��)1�h ==========
@GetMapping("/depts/tree")
diff --git a/crm-auth/src/main/java/com/crm/auth/domain/dto/RoleDetailVO.java b/crm-auth/src/main/java/com/crm/auth/domain/dto/RoleDetailVO.java
new file mode 100644
index 0000000..4365cbd
--- /dev/null
+++ b/crm-auth/src/main/java/com/crm/auth/domain/dto/RoleDetailVO.java
@@ -0,0 +1,60 @@
+package com.crm.auth.domain.dto;
+
+import com.crm.auth.domain.entity.SysRole;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * Yt�c�X�t=�Q DTO�WDR-0012�?+ * <p>Yt�c�X�~��`�t=�Q���0[_(��R�}Q�?�Gmcm�d1}resourceIds �m�T�Qt�0�S�~'h�S(��Rlu��X[�]ɓ�Q�lZ�,X}ADR-0005�?/p>
+ */
+@Data
+public class RoleDetailVO implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ @Schema(description = "Yt�c�X ID")
+ private Long id;
+
+ @Schema(description = "Yt�c�XZ��]")
+ private String roleName;
+
+ @Schema(description = "Yt�c�X+hr")
+ private String roleCode;
+
+ @Schema(description = "��HrA]|��Q?m 1=ȓ��Il 2=ȓ�4Q•?3=ȓ�4Q•%17_p0a4Q•?4=O�)14Q")
+ private Integer dataScope;
+
+ @Schema(description = "���c-|")
+ private Integer sort;
+
+ @Schema(description = "�o�V^e")
+ private String remark;
+
+ @Schema(description = "�`P�oT�uYt�c�X")
+ private Boolean builtin;
+
+ @Schema(description = "�[6��O�'1��O�"X�k9p~\�f��XHoƕ�U�`�X�tL�&1� �O}")
+ private List<Long> resourceIds;
+
+ /** SysRole +�?RoleDetailVO i��ppnpAi��ē�r�v�X {Z�?resourceIds�?*/
+ public static RoleDetailVO fromEntity(SysRole entity) {
+ if (entity == null) {
+ return null;
+ }
+ RoleDetailVO vo = new RoleDetailVO();
+ vo.setId(entity.getId());
+ vo.setRoleName(entity.getRoleName());
+ vo.setRoleCode(entity.getRoleCode());
+ vo.setDataScope(entity.getDataScope());
+ vo.setSort(entity.getSort());
+ vo.setRemark(entity.getRemark());
+ vo.setBuiltin(entity.getBuiltin());
+ return vo;
+ }
+}
diff --git a/crm-auth/src/main/java/com/crm/auth/domain/entity/SysRole.java b/crm-auth/src/main/java/com/crm/auth/domain/entity/SysRole.java
index 819e2f2..adb2d31 100644
--- a/crm-auth/src/main/java/com/crm/auth/domain/entity/SysRole.java
+++ b/crm-auth/src/main/java/com/crm/auth/domain/entity/SysRole.java
@@ -33,4 +33,8 @@ public class SysRole extends BaseEntity {
@Comment("�o�V^e")
@Column(columnDefinition = "varchar(200) comment '�o�V^e'")
private String remark;
+
+ @Comment("P�oT�uYt�c�X͓�V����[rue=�~d��|P�oT�u�m�]r_R��r�j�m�]r_��-W*}.�?)
+ @Column(columnDefinition = "boolean default false")
+ private Boolean builtin;
}
diff --git a/crm-auth/src/main/java/com/crm/auth/service/ISysMenuService.java b/crm-auth/src/main/java/com/crm/auth/service/ISysMenuService.java
index af633c1..272a9c4 100644
--- a/crm-auth/src/main/java/com/crm/auth/service/ISysMenuService.java
+++ b/crm-auth/src/main/java/com/crm/auth/service/ISysMenuService.java
@@ -3,10 +3,17 @@ package com.crm.auth.service;
import com.crm.auth.domain.entity.SysMenu;
import com.crm.base.service.IBaseService;
-import java.util.List;
+import java.util.Collection;
+import java.util.Set;
public interface ISysMenuService extends IBaseService<SysMenu> {
- /** ~��\G_̓/aW�y�,��k��n]IDR�Di0 */
- List<Long> getMenuIdsByRoleId(Long roleId);
+ /**
+ * Z?parentId Z�b{���]�]��\�O�'1�|9p3l�Y�R_�lZ�"X�kO�)14Q�~'h�S catalog/menu ID�WDR-0005�Y� ?+ * <p>��R_zV̓�0�St&1/p�[?ID+��barentId ē�r�v�}\4Up@j�NX��U}���W�S��/a�Y�ĉaq DB��?/p>
+ *
+ * @param menuIds "�&1�WU�C�� Y�k�t�R.|z��PcP ID�Xr_s�b {Z�����O�,X}
+ * @return V�mT�`O�)14Q�~'h�S(��Rlu���d�lZ�,X}Hg�d�S ID + �~'h�S ID�Y}p:jMP��?+ */
+ Set<Long> getAncestorIds(Collection<Long> menuIds);
}
diff --git a/crm-auth/src/main/java/com/crm/auth/service/ISysRoleService.java b/crm-auth/src/main/java/com/crm/auth/service/ISysRoleService.java
index 8735b8d..c50ee13 100644
--- a/crm-auth/src/main/java/com/crm/auth/service/ISysRoleService.java
+++ b/crm-auth/src/main/java/com/crm/auth/service/ISysRoleService.java
@@ -1,5 +1,6 @@
package com.crm.auth.service;
+import com.crm.auth.domain.dto.RoleDetailVO;
import com.crm.auth.domain.entity.SysRole;
import com.crm.base.service.IBaseService;
@@ -8,12 +9,29 @@ import java.util.List;
public interface ISysRoleService extends IBaseService<SysRole> {
/**
- * R��U�SYt�c�X(��RM_W��fHoĕ6a}O�)1zVǓc2]�|\(|T�2 Z~���0}
+ * �m�o�tYt�c�X�X+u͓��Ys�skoleCode ^���z��xO4U��W�y�m�Z~���0� hNataScope |��Q?m��zO���t?builtin�? *
- * @param roleId Yt�c�X ID
- * @param menuIds ��n] ID R�Di0
+ * @param role Yt�c�X�m!O�UWd �my�%Ó5��g�o�p� �Ojo�~�pi,h�}�?+ * @return �m�o�tZ��^�kYt�c�X�X�` ID�? */
- void assignMenus(Long roleId, List<Long> menuIds);
+ SysRole saveRole(SysRole role);
+
+ /**
+ * ~��\G_Yt�c�X�t=�Q�X�qȓ���O��?+ �[�S�]ɓ�QkygZ?ID ƕ�U�`�?+ *
+ * @param roleId Yt�c�X ID
+ * @return RoleDetailVO�}\�` resourceIds�X�| sys_role_menu ̓�0�V(��Rlu��X[�]ɓ�Q�lZ�,X}
+ */
+ RoleDetailVO getRoleDetail(Long roleId);
+
+ /**
+ * R��U�SYt�c�Xɓ�Q�j�t�R.|�"X��O�#X�O�?+ O�)1zVǓc2]�|\(|T�2 Z~���0}
+ * <p>S��]l�Y�C�6}U�C�� Y�kY����tz��PcP�}\�`�~��v"�?ISysMenuService.getAncestorIds t�0�S�~'h�SZ��^�S���_SUO�?sys_role_menu</p>
+ *
+ * @param roleId Yt�c�X ID
+ * @param resourceIds S��]l�U�C�� Y�k�t�R.|z��PcP ID�Xr_s�b {Z�����O�,X}
+ */
+ void assignResources(Long roleId, List<Long> resourceIds);
/**
* R��r�jYt�c�X��AR��q��e�z��UW�y�?��n]O��[�N�X(|T�2 Z~���0}
diff --git a/crm-auth/src/main/java/com/crm/auth/service/impl/SysMenuServiceImpl.java b/crm-auth/src/main/java/com/crm/auth/service/impl/SysMenuServiceImpl.java
index 6cb99a6..db54cd2 100644
--- a/crm-auth/src/main/java/com/crm/auth/service/impl/SysMenuServiceImpl.java
+++ b/crm-auth/src/main/java/com/crm/auth/service/impl/SysMenuServiceImpl.java
@@ -1,32 +1,42 @@
package com.crm.auth.service.impl;
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.crm.auth.domain.entity.SysMenu;
-import com.crm.auth.domain.entity.SysRoleMenu;
import com.crm.auth.mapper.SysMenuMapper;
-import com.crm.auth.mapper.SysRoleMenuMapper;
import com.crm.auth.service.ISysMenuService;
import com.crm.base.service.impl.BaseServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
-import java.util.Collections;
-import java.util.List;
+import java.util.*;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class SysMenuServiceImpl extends BaseServiceImpl<SysMenuMapper, SysMenu> implements ISysMenuService {
- private final SysRoleMenuMapper sysRoleMenuMapper;
-
@Override
- public List<Long> getMenuIdsByRoleId(Long roleId) {
- List<SysRoleMenu> list = sysRoleMenuMapper.selectList(
- new LambdaQueryWrapper<SysRoleMenu>().eq(SysRoleMenu::getRoleId, roleId));
- if (list == null || list.isEmpty()) {
- return Collections.emptyList();
+ public Set<Long> getAncestorIds(Collection<Long> menuIds) {
+ if (menuIds == null || menuIds.isEmpty()) {
+ return Collections.emptySet();
+ }
+ // ��R_zV̓�0�St��}˓�R ID+��barentId ē�r�v
+ List<SysMenu> allMenus = list();
+ Map<Long, Long> parentIdMap = allMenus.stream()
+ .collect(Collectors.toMap(SysMenu::getId, SysMenu::getParentId, (a, b) -> a));
+
+ // 5pĉ!v�mG��}O��0�Y�xv} Z?parentId Z�b{���]�]��X�l�~'h�S
+ Set<Long> result = new LinkedHashSet<>(menuIds);
+ for (Long menuId : menuIds) {
+ Long current = menuId;
+ while (current != null) {
+ Long parent = parentIdMap.get(current);
+ if (parent == null || parent == 0L || result.contains(parent)) {
+ break;
+ }
+ result.add(parent);
+ current = parent;
+ }
}
- return list.stream().map(SysRoleMenu::getMenuId).collect(Collectors.toList());
+ return result;
}
}
diff --git a/crm-auth/src/main/java/com/crm/auth/service/impl/SysRoleServiceImpl.java b/crm-auth/src/main/java/com/crm/auth/service/impl/SysRoleServiceImpl.java
index f8423c8..95e2a87 100644
--- a/crm-auth/src/main/java/com/crm/auth/service/impl/SysRoleServiceImpl.java
+++ b/crm-auth/src/main/java/com/crm/auth/service/impl/SysRoleServiceImpl.java
@@ -1,47 +1,135 @@
package com.crm.auth.service.impl;
+import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.crm.auth.constant.AuthConstants;
+import com.crm.auth.domain.dto.RoleDetailVO;
import com.crm.auth.domain.entity.SysRole;
import com.crm.auth.domain.entity.SysRoleMenu;
+import com.crm.auth.domain.entity.SysUserRole;
import com.crm.auth.mapper.SysRoleMapper;
import com.crm.auth.mapper.SysRoleMenuMapper;
+import com.crm.auth.mapper.SysUserRoleMapper;
+import com.crm.auth.service.ISysMenuService;
import com.crm.auth.service.ISysRoleService;
+import com.crm.base.domain.exception.BusinessErrorException;
import com.crm.base.service.impl.BaseServiceImpl;
import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
+import java.util.Set;
+import static com.crm.auth.constant.AuthConstants.*;
+
+@Slf4j
@Service
@RequiredArgsConstructor
public class SysRoleServiceImpl extends BaseServiceImpl<SysRoleMapper, SysRole> implements ISysRoleService {
private final SysRoleMenuMapper sysRoleMenuMapper;
+ private final SysUserRoleMapper sysUserRoleMapper;
+ private final ISysMenuService sysMenuService;
+
+ @Override
+ public SysRole saveRole(SysRole role) {
+ // i��ppnȕ�p%͓��Ys
+ if (StrUtil.isBlank(role.getRoleName())) {
+ throw new BusinessErrorException(CODE_ROLE_INVALID, "Yt�c�XZ��]�m�]XQ�my�%");
+ }
+ if (StrUtil.isBlank(role.getRoleCode())) {
+ throw new BusinessErrorException(CODE_ROLE_INVALID, "Yt�c�X+hr�m�]XQ�my�%");
+ }
+
+ // dataScope |��Q?m͓��Ys
+ if (role.getDataScope() != null && (role.getDataScope() < 1 || role.getDataScope() > 4)) {
+ throw new BusinessErrorException(CODE_ROLE_INVALID, "��HrA]|��Q?mG�tT0f�?1-4 |��Q?mP�?);
+ }
+
+ // P�oT�uYt�c�X�m�o�Y�+l {O�}O�᫕3lC~���0[_R��mP�oT�uYt�c�X�X�h�t�p�g�[?�U~ē�*}Hgb {p:jjn(��R��0��f}
+ SysRole existing = role.getId() != null ? baseMapper.selectById(role.getId()) : null;
+ if (Boolean.TRUE.equals(role.getBuiltin())
+ && (existing == null || !Boolean.TRUE.equals(existing.getBuiltin()))) {
+ throw new BusinessErrorException(CODE_BUILTIN_ROLE_PROTECTED, "�m�]�S�td� 3lC~���0[_R��mP�oT�uYt�c�X");
+ }
+ if (role.getId() == null) {
+ role.setBuiltin(false);
+ } else if (existing != null && Boolean.TRUE.equals(existing.getBuiltin())
+ && !existing.getRoleCode().equals(role.getRoleCode())) {
+ // ,h�}�,l4U��W�y�,����Y!2|e roleCode
+ throw new BusinessErrorException(CODE_BUILTIN_ROLE_PROTECTED, "P�oT�uYt�c�X(��R*}.�wO {Y��ha��?);
+ }
+
+ // roleCode ^���z��DNr`i?+ Long count = baseMapper.selectCount(
+ new LambdaQueryWrapper<SysRole>()
+ .eq(SysRole::getRoleCode, role.getRoleCode())
+ .ne(role.getId() != null, SysRole::getId, role.getId()));
+ if (count != null && count > 0) {
+ throw new BusinessErrorException(CODE_ROLE_CODE_DUPLICATE,
+ "Yt�c�X+hr�[�c�tf���0}" + role.getRoleCode());
+ }
+
+ saveOrUpdate(role);
+ log.info("�m�o�tYt�c�X�ikd={}, roleCode={}", role.getId(), role.getRoleCode());
+ return role;
+ }
+
+ @Override
+ public RoleDetailVO getRoleDetail(Long roleId) {
+ SysRole role = baseMapper.selectById(roleId);
+ if (role == null) {
+ throw new BusinessErrorException(CODE_ROLE_INVALID, "Yt�c�X�m�]�tf���0}" + roleId);
+ }
+ RoleDetailVO vo = RoleDetailVO.fromEntity(role);
+ // ̓?sys_role_menu �[@i�W9p~\�f��XHoƕ�U�`�X�tL�&1� �O}�[�c�`�~'h�S�?+ List<SysRoleMenu> bindings = sysRoleMenuMapper.selectList(
+ new LambdaQueryWrapper<SysRoleMenu>().eq(SysRoleMenu::getRoleId, roleId));
+ List<Long> resourceIds = bindings.stream()
+ .map(SysRoleMenu::getMenuId)
+ .toList();
+ vo.setResourceIds(resourceIds);
+ return vo;
+ }
@Override
@Transactional(rollbackFor = Exception.class)
- public void assignMenus(Long roleId, List<Long> menuIds) {
- // O�X�WZ��^C_�X�S���_[m��n�}
+ public void assignResources(Long roleId, List<Long> resourceIds) {
+ // �~'h�St�0�S�,l�X�~�g_|m�r[�� Y�kY����tz��PcP�}\�`�~��O�%1�S��'1��O�?catalog/menu ID
+ Set<Long> completeIds = sysMenuService.getAncestorIds(resourceIds);
+ log.info("R��U�SYt�c�X�t�R.|�skoleId={}, Hg�d�S {} �m?-> t�0�SZ�?{} �m?, roleId,
+ resourceIds != null ? resourceIds.size() : 0, completeIds.size());
+
+ // O�X�WZ��^C_�X�S���_[m��?sys_role_menu�? sysRoleMenuMapper.delete(
new LambdaQueryWrapper<SysRoleMenu>().eq(SysRoleMenu::getRoleId, roleId));
- if (menuIds != null && !menuIds.isEmpty()) {
- for (Long menuId : menuIds) {
- SysRoleMenu rm = new SysRoleMenu();
- rm.setRoleId(roleId);
- rm.setMenuId(menuId);
- sysRoleMenuMapper.insert(rm);
- }
+ for (Long menuId : completeIds) {
+ SysRoleMenu rm = new SysRoleMenu();
+ rm.setRoleId(roleId);
+ rm.setMenuId(menuId);
+ sysRoleMenuMapper.insert(rm);
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteRoleCascade(Long roleId) {
+ // P�oT�uYt�c�X�m�o�Y�\kuiltin=true (��RW�y��S�Z�o�Wĕ?+ SysRole role = baseMapper.selectById(roleId);
+ if (role != null && Boolean.TRUE.equals(role.getBuiltin())) {
+ throw new BusinessErrorException(CODE_BUILTIN_ROLE_PROTECTED, "P�oT�uYt�c�X�m�]�S�t?z�Wĕ?);
+ }
// �~F�NZoT�`Yt�c�X-��n]O��[�N
sysRoleMenuMapper.delete(
new LambdaQueryWrapper<SysRoleMenu>().eq(SysRoleMenu::getRoleId, roleId));
+ // �~F�NZoT�`"�&1�W-Yt�c�XO��[�N
+ sysUserRoleMapper.delete(
+ new LambdaQueryWrapper<SysUserRole>()
+ .eq(SysUserRole::getRoleId, roleId));
// R��r�jYt�c�X
removeById(roleId);
+ log.info("R��r�jYt�c�XY�DZ��q��e�Sq��e0}roleId={}", roleId);
}
}
diff --git a/crm-auth/src/test/java/com/crm/auth/security/scope/DataScopeIntegrationTest.java b/crm-auth/src/test/java/com/crm/auth/security/scope/DataScopeIntegrationTest.java
index 565ced7..2301fad 100644
--- a/crm-auth/src/test/java/com/crm/auth/security/scope/DataScopeIntegrationTest.java
+++ b/crm-auth/src/test/java/com/crm/auth/security/scope/DataScopeIntegrationTest.java
@@ -460,7 +460,8 @@ class DataScopeIntegrationTest {
create table sys_role (
id bigint primary key, creator_id varchar(50), create_time datetime,
updater_id varchar(50), update_time datetime, deleted tinyint not null default 0,
- role_name varchar(50), role_code varchar(50), data_scope tinyint, sort int, remark varchar(200))
+ role_name varchar(50), role_code varchar(50), data_scope tinyint, sort int, remark varchar(200),
+ builtin boolean default false)
""",
"""
create table sys_user_role (
diff --git a/crm-auth/src/test/java/com/crm/auth/service/impl/SysMenuServiceImplTest.java b/crm-auth/src/test/java/com/crm/auth/service/impl/SysMenuServiceImplTest.java
new file mode 100644
index 0000000..805dea1
--- /dev/null
+++ b/crm-auth/src/test/java/com/crm/auth/service/impl/SysMenuServiceImplTest.java
@@ -0,0 +1,127 @@
+package com.crm.auth.service.impl;
+
+import com.crm.auth.domain.entity.SysMenu;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Spy;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.doReturn;
+
+/**
+ * {@link SysMenuServiceImpl} W��f�S4Z-[/v�3l��)�+h��O�#X�O�)1� f�}�WDR-0005�?+ */
+@ExtendWith(MockitoExtension.class)
+class SysMenuServiceImplTest {
+
+ @Spy
+ @InjectMocks
+ private SysMenuServiceImpl sysMenuService;
+
+ // ==================== �~'h�St�0�S ====================
+
+ @Test
+ @DisplayName("W��f|dZ�b{���]�] -> |m�r�SY����t button��\Q~e�?button+menu+catalog 9p~\�f��?)
+ void singleChainAncestorCompletion() {
+ SysMenu catalog = buildMenu(1L, 0L, "�~d��|�~��`", 1);
+ SysMenu menu = buildMenu(10L, 1L, "Yt�c�X�~��`", 2);
+ SysMenu button = buildMenu(100L, 10L, "Yt�c�XR�Di0", 3);
+ doReturn(List.of(catalog, menu, button)).when(sysMenuService).list();
+
+ Set<Long> result = sysMenuService.getAncestorIds(Set.of(100L));
+
+ assertThat(result).containsExactlyInAnyOrder(100L, 10L, 1L);
+ }
+
+ @Test
+ @DisplayName("�o6l|d�Y�V�` -> �m�0oo���s��gR�kY����t��\Q~e��p��ɓ��|dO�)14Q�~'h�S(��R߂ƕ?)
+ void multipleChainsAncestorCompletion() {
+ // ��?�1l���qx��?1) -> Yt�c�X�~��`(10) -> Yt�c�XR�Di0(100)
+ // ��?�,lyᴓΘx��?2) -> 9p!2�WR�Di0(20) -> “g��9p!2�W(200)
+ SysMenu catalog1 = buildMenu(1L, 0L, "�~d��|�~��`", 1);
+ SysMenu menu1 = buildMenu(10L, 1L, "Yt�c�X�~��`", 2);
+ SysMenu button1 = buildMenu(100L, 10L, "Yt�c�XR�Di0", 3);
+
+ SysMenu catalog2 = buildMenu(2L, 0L, "9p!2�W�~��`", 1);
+ SysMenu menu2 = buildMenu(20L, 2L, "9p!2�WR�Di0", 2);
+ SysMenu button2 = buildMenu(200L, 20L, "“g��9p!2�W", 3);
+
+ doReturn(List.of(catalog1, menu1, button1, catalog2, menu2, button2))
+ .when(sysMenuService).list();
+
+ Set<Long> result = sysMenuService.getAncestorIds(Set.of(100L, 200L));
+
+ assertThat(result).containsExactlyInAnyOrder(100L, 10L, 1L, 200L, 20L, 2L);
+ }
+
+ @Test
+ @DisplayName("͓Ex�Y�?-> parentId=0 (�?catalog��\Q~e��p`uw�D�A~�X�h�~'h�SY����?)
+ void rootNodeNoAncestor() {
+ SysMenu catalog = buildMenu(1L, 0L, "�~d��|�~��`", 1);
+ SysMenu menu = buildMenu(10L, 1L, "Yt�c�X�~��`", 2);
+ doReturn(List.of(catalog, menu)).when(sysMenuService).list();
+
+ Set<Long> result = sysMenuService.getAncestorIds(Set.of(1L));
+
+ assertThat(result).containsExactly(1L);
+ }
+
+ @Test
+ @DisplayName("�~?��}O�?-> |m�r�S�~*m�lZ�,X}ig�em�~*m�lZ�?)
+ void emptyInputReturnsEmpty() {
+ Set<Long> result = sysMenuService.getAncestorIds(Collections.emptySet());
+
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ @DisplayName("null Hg�d�S -> |m�r�S null��\Q~e��p%ƕ�U�`")
+ void nullInputReturnsEmpty() {
+ Set<Long> result = sysMenuService.getAncestorIds(null);
+
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ @DisplayName("�[�c�`�~'h�S -> |m�r�S9p~\�fƕ�U�`��\Q~e��pImZ��\�lZ�,X}���P�t�m�]xV�o�]}")
+ void alreadyCompleteSetIsIdempotent() {
+ SysMenu catalog = buildMenu(1L, 0L, "�~d��|�~��`", 1);
+ SysMenu menu = buildMenu(10L, 1L, "Yt�c�X�~��`", 2);
+ SysMenu button = buildMenu(100L, 10L, "Yt�c�XR�Di0", 3);
+ doReturn(List.of(catalog, menu, button)).when(sysMenuService).list();
+
+ Set<Long> result = sysMenuService.getAncestorIds(Set.of(100L, 10L, 1L));
+
+ assertThat(result).containsExactlyInAnyOrder(100L, 10L, 1L);
+ }
+
+ @Test
+ @DisplayName("Hg�d�SZ��� {p:jjn\m?DB (�?ID -> �t?ID �m�o�ff�'1�|˓�nQ��|\~}�m�]��"��q��O�?)
+ void nonExistentIdKeptButNoAncestors() {
+ SysMenu catalog = buildMenu(1L, 0L, "�~d��|�~��`", 1);
+ SysMenu menu = buildMenu(10L, 1L, "Yt�c�X�~��`", 2);
+ doReturn(List.of(catalog, menu)).when(sysMenuService).list();
+
+ Set<Long> result = sysMenuService.getAncestorIds(Set.of(10L, 999L));
+
+ assertThat(result).containsExactlyInAnyOrder(10L, 1L, 999L);
+ }
+
+ // ==================== �YB%�f��?====================
+
+ private SysMenu buildMenu(Long id, Long parentId, String name, int type) {
+ SysMenu m = new SysMenu();
+ m.setId(id);
+ m.setParentId(parentId);
+ m.setMenuName(name);
+ m.setMenuType(type);
+ return m;
+ }
+}
diff --git a/crm-auth/src/test/java/com/crm/auth/service/impl/SysRoleServiceImplTest.java b/crm-auth/src/test/java/com/crm/auth/service/impl/SysRoleServiceImplTest.java
new file mode 100644
index 0000000..70c49b9
--- /dev/null
+++ b/crm-auth/src/test/java/com/crm/auth/service/impl/SysRoleServiceImplTest.java
@@ -0,0 +1,300 @@
+package com.crm.auth.service.impl;
+
+import com.crm.auth.domain.dto.RoleDetailVO;
+import com.crm.auth.domain.entity.SysRole;
+import com.crm.auth.domain.entity.SysRoleMenu;
+import com.crm.auth.domain.entity.SysUserRole;
+import com.crm.auth.mapper.SysRoleMapper;
+import com.crm.auth.mapper.SysRoleMenuMapper;
+import com.crm.auth.mapper.SysUserRoleMapper;
+import com.crm.auth.service.ISysMenuService;
+import com.crm.base.domain.exception.BusinessErrorException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.junit.jupiter.MockitoSettings;
+import org.mockito.quality.Strictness;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.util.List;
+import java.util.Set;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * {@link SysRoleServiceImpl} W��f�S4Z-[/v�3l��)�?saveRole ͓��YsYt�R�W�WDR-0012�?+ */
+@ExtendWith(MockitoExtension.class)
+@MockitoSettings(strictness = Strictness.LENIENT)
+class SysRoleServiceImplTest {
+
+ @Mock
+ private SysRoleMapper sysRoleMapper;
+ @Mock
+ private SysRoleMenuMapper sysRoleMenuMapper;
+ @Mock
+ private SysUserRoleMapper sysUserRoleMapper;
+ @Mock
+ private ISysMenuService sysMenuService;
+
+ @InjectMocks
+ private SysRoleServiceImpl sysRoleService;
+
+ @BeforeEach
+ void setUp() {
+ // ServiceImpl (�?baseMapper pAi��f�'1�W�~O}@InjectMocks Y��XQ�m�]^eO��0}��*[�Y�tgR�u
+ ReflectionTestUtils.setField(sysRoleService, "baseMapper", sysRoleMapper);
+ }
+
+ // ==================== roleCode ^���z��?====================
+
+ @Test
+ @DisplayName("R��mYt�c�X -> roleCode �[�c�tf�?-> ���c�|")
+ void createRole_duplicateCode_rejected() {
+ SysRole role = buildRole(null, "��� ^�?, "ROLE_SALES", 1);
+ when(sysRoleMapper.selectCount(any())).thenReturn(1L);
+
+ assertThatThrownBy(() -> sysRoleService.saveRole(role))
+ .isInstanceOf(BusinessErrorException.class)
+ .hasMessageContaining("Yt�c�X+hr�[�c�tf�?);
+ }
+
+ @Test
+ @DisplayName(",h�}Yt�c�X -> roleCode �m�]I_�X�` id�?-> ��3lC~")
+ void editRole_sameCodeSameId_passes() {
+ SysRole role = buildRole(1L, "��� ^�?, "ROLE_SALES", 1);
+ when(sysRoleMapper.selectById(1L)).thenReturn(role);
+ when(sysRoleMapper.selectCount(any())).thenReturn(0L);
+ when(sysRoleMapper.updateById(any(SysRole.class))).thenReturn(1);
+
+ assertThatCode(() -> sysRoleService.saveRole(role))
+ .doesNotThrowAnyException();
+ }
+
+ // ==================== P�oT�uYt�c�X�m�o�Y ====================
+
+ @Test
+ @DisplayName(",h�}P�oT�uYt�c�X -> �m��|e roleCode -> ���c�|")
+ void editBuiltinRole_changeCode_rejected() {
+ SysRole existing = buildRole(1L, "�~��`[�?, "ROLE_ADMIN", 4);
+ existing.setBuiltin(true);
+ SysRole input = buildRole(1L, "�~��`[�?, "ROLE_SUPER", 4);
+ when(sysRoleMapper.selectById(1L)).thenReturn(existing);
+
+ assertThatThrownBy(() -> sysRoleService.saveRole(input))
+ .isInstanceOf(BusinessErrorException.class)
+ .hasMessageContaining("P�oT�uYt�c�X(��R*}.�wO {Y��ha��?);
+ }
+
+ @Test
+ @DisplayName(",h�}P�oT�uYt�c�X -> roleCode �m�]I_ -> ��3lC~")
+ void editBuiltinRole_sameCode_passes() {
+ SysRole existing = buildRole(1L, "�~��`[�?, "ROLE_ADMIN", 4);
+ existing.setBuiltin(true);
+ SysRole input = buildRole(1L, "�~��`[�;j|e", "ROLE_ADMIN", 4);
+ when(sysRoleMapper.selectById(1L)).thenReturn(existing);
+ when(sysRoleMapper.selectCount(any())).thenReturn(0L);
+ when(sysRoleMapper.updateById(any(SysRole.class))).thenReturn(1);
+
+ assertThatCode(() -> sysRoleService.saveRole(input))
+ .doesNotThrowAnyException();
+ }
+
+ @Test
+ @DisplayName("R��mYt�c�X -> �tgR�u builtin=true -> ���c�|")
+ void createRole_setBuiltin_rejected() {
+ SysRole role = buildRole(null, "K��Vx���U�a", "ROLE_FAKE", 4);
+ role.setBuiltin(true);
+
+ assertThatThrownBy(() -> sysRoleService.saveRole(role))
+ .isInstanceOf(BusinessErrorException.class)
+ .hasMessageContaining("�m�]�S�td� 3lC~���0[_R��mP�oT�uYt�c�X");
+ }
+
+ @Test
+ @DisplayName(",h�}�m�]�tf�'1�k id -> �tgR�u builtin=true -> ���c�|�%X�i�Y?saveOrUpdate (�?insert �fC~�?)
+ void editNonExistentRole_setBuiltin_rejected() {
+ SysRole role = buildRole(999L, "K��Vx���U�a", "ROLE_FAKE", 4);
+ role.setBuiltin(true);
+ when(sysRoleMapper.selectById(999L)).thenReturn(null);
+
+ assertThatThrownBy(() -> sysRoleService.saveRole(role))
+ .isInstanceOf(BusinessErrorException.class)
+ .hasMessageContaining("�m�]�S�td� 3lC~���0[_R��mP�oT�uYt�c�X");
+ }
+
+ // ==================== dataScope |��Q?m͓��Ys ====================
+
+ @Test
+ @DisplayName("R��mYt�c�X -> dataScope=0�#X�y#��\} -> ���c�|")
+ void createRole_dataScopeTooLow_rejected() {
+ SysRole role = buildRole(null, "��� ^�?, "ROLE_SALES", 0);
+
+ assertThatThrownBy(() -> sysRoleService.saveRole(role))
+ .isInstanceOf(BusinessErrorException.class)
+ .hasMessageContaining("��HrA]|��Q?mG�tT0f�?1-4 |��Q?mP�?);
+ }
+
+ @Test
+ @DisplayName("R��mYt�c�X -> dataScope=5�#X�y#��\} -> ���c�|")
+ void createRole_dataScopeTooHigh_rejected() {
+ SysRole role = buildRole(null, "��� ^�?, "ROLE_SALES", 5);
+
+ assertThatThrownBy(() -> sysRoleService.saveRole(role))
+ .isInstanceOf(BusinessErrorException.class)
+ .hasMessageContaining("��HrA]|��Q?mG�tT0f�?1-4 |��Q?mP�?);
+ }
+
+ @Test
+ @DisplayName("R��mYt�c�X -> dataScope=3�X�` Z�f} -> ��3lC~")
+ void createRole_validDataScope_passes() {
+ SysRole role = buildRole(null, "��� ^�?, "ROLE_SALES", 3);
+ when(sysRoleMapper.selectCount(any())).thenReturn(0L);
+ when(sysRoleMapper.insert(any(SysRole.class))).thenReturn(1);
+
+ assertThatCode(() -> sysRoleService.saveRole(role))
+ .doesNotThrowAnyException();
+ }
+
+ // ==================== i��ppnȕ�p%͓��Ys ====================
+
+ @Test
+ @DisplayName("R��mYt�c�X -> roleName �my�% -> ���c�|")
+ void createRole_blankName_rejected() {
+ SysRole role = buildRole(null, "", "ROLE_SALES", 1);
+
+ assertThatThrownBy(() -> sysRoleService.saveRole(role))
+ .isInstanceOf(BusinessErrorException.class)
+ .hasMessageContaining("Yt�c�XZ��]�m�]XQ�my�%");
+ }
+
+ @Test
+ @DisplayName("R��mYt�c�X -> roleCode �my�% -> ���c�|")
+ void createRole_blankCode_rejected() {
+ SysRole role = buildRole(null, "��� ^�?, "", 1);
+
+ assertThatThrownBy(() -> sysRoleService.saveRole(role))
+ .isInstanceOf(BusinessErrorException.class)
+ .hasMessageContaining("Yt�c�X+hr�m�]XQ�my�%");
+ }
+
+ // ==================== ɓ�Q�jR��U�S�"X��O�#X�O���} ====================
+
+ @Test
+ @DisplayName("assignResources -> �t�Qde�~'h�St�0�S -> P�k�S(�?menuIds V�mT�`�~'h�S")
+ void assignResources_ancestorCompletion_writesAllIds() {
+ // Hg�d�SY�C�6}Y����tz��PcP 100L��\��O�#X�O�%1�`4d�e�[Z�?100L + 10L + 1L
+ when(sysMenuService.getAncestorIds(List.of(100L)))
+ .thenReturn(Set.of(100L, 10L, 1L));
+ when(sysRoleMenuMapper.delete(any())).thenReturn(1);
+ when(sysRoleMenuMapper.insert(any(SysRoleMenu.class))).thenReturn(1);
+
+ sysRoleService.assignResources(1L, List.of(100L));
+
+ // "�?ArgumentCaptor ���fO^P�k�S(�?SysRoleMenu��\Ys�t?menuId ƕ�U�`V�mT�`�~'h�S
+ ArgumentCaptor<SysRoleMenu> captor = ArgumentCaptor.forClass(SysRoleMenu.class);
+ verify(sysRoleMenuMapper, atLeastOnce()).insert(captor.capture());
+ List<Long> writtenMenuIds = captor.getAllValues().stream()
+ .map(SysRoleMenu::getMenuId).toList();
+ assertThat(writtenMenuIds).containsExactlyInAnyOrder(100L, 10L, 1L);
+ assertThat(captor.getAllValues()).allSatisfy(rm ->
+ assertThat(rm.getRoleId()).isEqualTo(1L));
+ }
+
+ @Test
+ @DisplayName("assignResources -> O�X�WZ��^C_O�)1zVǓc2] sys_role_menu")
+ void assignResources_fullReplacement() {
+ when(sysMenuService.getAncestorIds(List.of(200L)))
+ .thenReturn(Set.of(200L));
+ when(sysRoleMenuMapper.delete(any())).thenReturn(1);
+ when(sysRoleMenuMapper.insert(any(SysRoleMenu.class))).thenReturn(1);
+
+ sysRoleService.assignResources(1L, List.of(200L));
+
+ // `i�\ vP�k�S(�?menuId ƕ�U�``mlT�t�0�SZ��^�k {200L}
+ ArgumentCaptor<SysRoleMenu> captor = ArgumentCaptor.forClass(SysRoleMenu.class);
+ verify(sysRoleMenuMapper, atLeastOnce()).insert(captor.capture());
+ assertThat(captor.getAllValues()).hasSize(1);
+ assertThat(captor.getAllValues().get(0).getMenuId()).isEqualTo(200L);
+ }
+
+ // ==================== Yt�c�X�t=�Q ====================
+
+ @Test
+ @DisplayName("getRoleDetail -> ig�em RoleDetailVO Z����qȓ���O����b resourceIds")
+ void getRoleDetail_returnsVO() {
+ SysRole role = buildRole(1L, "��� ^�?, "ROLE_SALES", 1);
+ when(sysRoleMapper.selectById(1L)).thenReturn(role);
+ SysRoleMenu rm1 = new SysRoleMenu();
+ rm1.setMenuId(100L);
+ SysRoleMenu rm2 = new SysRoleMenu();
+ rm2.setMenuId(200L);
+ when(sysRoleMenuMapper.selectList(any())).thenReturn(List.of(rm1, rm2));
+
+ RoleDetailVO vo = sysRoleService.getRoleDetail(1L);
+
+ assertThat(vo).isNotNull();
+ assertThat(vo.getRoleName()).isEqualTo("��� ^�?);
+ assertThat(vo.getRoleCode()).isEqualTo("ROLE_SALES");
+ assertThat(vo.getResourceIds()).containsExactly(100L, 200L);
+ }
+
+ @Test
+ @DisplayName("getRoleDetail -> Yt�c�X�m�]�tf�?-> ���c�|")
+ void getRoleDetail_notFound_rejected() {
+ when(sysRoleMapper.selectById(999L)).thenReturn(null);
+
+ assertThatThrownBy(() -> sysRoleService.getRoleDetail(999L))
+ .isInstanceOf(BusinessErrorException.class)
+ .hasMessageContaining("Yt�c�X�m�]�tf�?);
+ }
+
+ // ==================== Yt�c�XR��r�j�"X��q��e�z��U} ====================
+
+ @Test
+ @DisplayName("R��r�jP�oT�uYt�c�X -> builtin=true -> ���c�|")
+ void deleteBuiltinRole_rejected() {
+ SysRole role = buildRole(1L, "�~��`[�?, "ROLE_ADMIN", 4);
+ role.setBuiltin(true);
+ when(sysRoleMapper.selectById(1L)).thenReturn(role);
+
+ assertThatThrownBy(() -> sysRoleService.deleteRoleCascade(1L))
+ .isInstanceOf(BusinessErrorException.class)
+ .hasMessageContaining("P�oT�uYt�c�X�m�]�S�t?z�Wĕ?);
+ }
+
+ @Test
+ @DisplayName("R��r�jœ��� 3lW�y�?-> builtin=false -> ��3lC~��5��z�?sys_role_menu + sys_user_role")
+ void deleteNormalRole_passes() {
+ SysRole role = buildRole(2L, "��� ^�?, "ROLE_SALES", 1);
+ when(sysRoleMapper.selectById(2L)).thenReturn(role);
+ when(sysRoleMenuMapper.delete(any())).thenReturn(1);
+ when(sysUserRoleMapper.delete(any())).thenReturn(1);
+
+ assertThatCode(() -> sysRoleService.deleteRoleCascade(2L))
+ .doesNotThrowAnyException();
+ }
+
+ // ==================== �YB%�f��?====================
+
+ private SysRole buildRole(Long id, String name, String code, int dataScope) {
+ SysRole role = new SysRole();
+ role.setId(id);
+ role.setRoleName(name);
+ role.setRoleCode(code);
+ role.setDataScope(dataScope);
+ role.setSort(0);
+ return role;
+ }
+}
diff --git a/crm-auth/src/test/java/com/crm/auth/service/impl/UserListIntegrationTest.java b/crm-auth/src/test/java/com/crm/auth/service/impl/UserListIntegrationTest.java
index 2e0c173..be66a5e 100644
--- a/crm-auth/src/test/java/com/crm/auth/service/impl/UserListIntegrationTest.java
+++ b/crm-auth/src/test/java/com/crm/auth/service/impl/UserListIntegrationTest.java
@@ -332,7 +332,8 @@ class UserListIntegrationTest {
create table sys_role (
id bigint primary key, creator_id varchar(50), create_time datetime,
updater_id varchar(50), update_time datetime, deleted tinyint not null default 0,
- role_name varchar(50), role_code varchar(50), data_scope tinyint, sort int, remark varchar(200))
+ role_name varchar(50), role_code varchar(50), data_scope tinyint, sort int, remark varchar(200),
+ builtin boolean default false)
""",
"""
create table sys_user_role (