feat(admin-server): 新增角色与菜单权限管理相关功能及数据库表结构

This commit is contained in:
FalingCliff 2025-06-15 23:27:43 +08:00
parent ca189682e5
commit d4ac89bd28
25 changed files with 1337 additions and 0 deletions

View File

@ -0,0 +1,35 @@
package com.example.admin_server.common.exception;
/**
* 业务异常类
*/
public class BusinessException extends RuntimeException {
private static final long serialVersionUID = 1L;
private Integer code;
public BusinessException(String message) {
super(message);
this.code = 500;
}
public BusinessException(String message, Throwable cause) {
super(message, cause);
this.code = 500;
}
public BusinessException(Integer code, String message) {
super(message);
this.code = code;
}
public BusinessException(Integer code, String message, Throwable cause) {
super(message, cause);
this.code = code;
}
public Integer getCode() {
return code;
}
}

View File

@ -0,0 +1,86 @@
package com.example.admin_server.controller.admin;
import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.example.admin_server.common.Result;
import com.example.admin_server.model.dto.MenuDTO;
import com.example.admin_server.model.entity.SysMenu;
import com.example.admin_server.service.ISysMenuService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 菜单管理控制器
*/
@RestController
@RequestMapping("/api/admin/menu")
@RequiredArgsConstructor
@Api(tags = {"菜单管理"})
public class SysMenuController {
private final ISysMenuService menuService;
@PostMapping("/add")
@ApiOperation("创建菜单")
public Result<?> createMenu(@Validated @RequestBody MenuDTO menuDTO) {
SysMenu menu = new SysMenu();
BeanUtil.copyProperties(menuDTO, menu);
return Result.ok(menuService.save(menu));
}
@PutMapping("/edit")
@ApiOperation("更新菜单")
public Result<?> updateMenu(@Validated @RequestBody MenuDTO menuDTO) {
SysMenu menu = new SysMenu();
BeanUtil.copyProperties(menuDTO, menu);
return Result.ok(menuService.updateById(menu));
}
@DeleteMapping("/delete")
@ApiOperation("删除菜单")
public Result<?> deleteMenu(@RequestParam Long id) {
// 检查是否有子菜单
int count = Math.toIntExact(menuService.count(new LambdaQueryWrapper<SysMenu>()
.eq(SysMenu::getParentId, id)));
if (count > 0) {
return Result.fail("存在子菜单,无法删除");
}
return Result.ok(menuService.removeById(id));
}
@GetMapping("/detail")
@ApiOperation("获取菜单详情")
public Result<SysMenu> getMenuDetail(@RequestParam Long id) {
SysMenu menu = menuService.getById(id);
return menu != null ? Result.ok(menu) : Result.fail("菜单不存在");
}
@GetMapping("/tree")
@ApiOperation("获取菜单树")
public Result<List<SysMenu>> getMenuTree() {
List<SysMenu> menuTree = menuService.getAllMenuTree();
return Result.ok(menuTree);
}
@GetMapping("/current")
@ApiOperation("获取当前管理员的菜单树")
public Result<List<SysMenu>> getCurrentAdminMenus() {
// 这里应该从安全上下文中获取当前登录的管理员ID
// 为了演示这里使用一个固定的ID
Long adminId = 1L; // 实际应用中应该从SecurityContext获取
List<SysMenu> menuTree = menuService.getMenusByAdminId(adminId);
return Result.ok(menuTree);
}
@GetMapping("/list")
@ApiOperation("获取所有菜单列表")
public Result<List<SysMenu>> getMenuList() {
List<SysMenu> menus = menuService.list();
return Result.ok(menus);
}
}

View File

@ -0,0 +1,72 @@
package com.example.admin_server.controller.admin;
import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.example.admin_server.common.Result;
import com.example.admin_server.common.query.IPageRequest;
import com.example.admin_server.model.dto.IdListDTO;
import com.example.admin_server.model.dto.RoleDTO;
import com.example.admin_server.model.entity.SysRole;
import com.example.admin_server.service.ISysRoleService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/admin/role")
@RequiredArgsConstructor
@Api(tags = {"角色管理"})
public class SysRoleController {
private final ISysRoleService roleService;
@PostMapping("/add")
@ApiOperation("创建角色")
public Result<?> createRole(@Validated @RequestBody RoleDTO roleDTO) {
SysRole role = BeanUtil.copyProperties(roleDTO, SysRole.class);
return Result.ok(roleService.save(role));
}
@PutMapping("/edit")
@ApiOperation("更新角色")
public Result<?> updateRole(@Validated @RequestBody RoleDTO roleDTO) {
SysRole role = BeanUtil.copyProperties(roleDTO, SysRole.class);
return Result.ok(roleService.updateById(role));
}
@DeleteMapping("/delete")
@ApiOperation("批量删除角色")
public Result<?> deleteRoles(@Validated @RequestBody IdListDTO idList) {
return Result.ok(roleService.removeByIds(idList.getIdList()));
}
@GetMapping("/detail")
@ApiOperation("获取角色详情")
public Result<SysRole> getRoleDetail(@RequestParam Long id) {
SysRole role = roleService.getById(id);
return role != null ? Result.ok(role) : Result.fail("角色不存在");
}
@GetMapping("/list")
@ApiOperation("获取角色列表")
public Result<List<SysRole>> getRoleList() {
return Result.ok(roleService.list());
}
@PostMapping("/menu/assign")
@ApiOperation("分配角色菜单")
public Result<?> assignRoleMenu(@RequestParam Long roleId, @RequestBody List<Long> menuIds) {
roleService.assignMenus(roleId, menuIds);
return Result.ok();
}
@GetMapping("/menu/list")
@ApiOperation("获取角色菜单")
public Result<List<Long>> getRoleMenus(@RequestParam Long roleId) {
return Result.ok(roleService.getMenuIdsByRoleId(roleId));
}
}

View File

@ -0,0 +1,32 @@
package com.example.admin_server.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.admin_server.model.entity.SysAdminRole;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
* 管理员角色关联Mapper接口
*/
@Mapper
public interface SysAdminRoleMapper extends BaseMapper<SysAdminRole> {
/**
* 根据管理员ID查询角色ID列表
* @param adminId 管理员ID
* @return 角色ID列表
*/
@Select("SELECT role_id FROM sys_admin_role WHERE admin_id = #{adminId}")
List<Long> selectRoleIdsByAdminId(@Param("adminId") Long adminId);
/**
* 批量插入管理员角色关联
* @param adminId 管理员ID
* @param roleIds 角色ID列表
* @return 影响行数
*/
int batchInsert(@Param("adminId") Long adminId, @Param("roleIds") List<Long> roleIds);
}

View File

@ -0,0 +1,36 @@
package com.example.admin_server.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.admin_server.model.entity.SysMenu;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
* 菜单Mapper接口
*/
@Mapper
public interface SysMenuMapper extends BaseMapper<SysMenu> {
/**
* 根据管理员ID查询菜单列表
* @param adminId 管理员ID
* @return 菜单列表
*/
@Select("SELECT DISTINCT m.* FROM sys_menu m " +
"INNER JOIN sys_role_menu rm ON m.id = rm.menu_id " +
"INNER JOIN sys_admin_role ar ON rm.role_id = ar.role_id " +
"WHERE ar.admin_id = #{adminId} AND m.status = 1 " +
"ORDER BY m.sort_order")
List<SysMenu> selectMenusByAdminId(@Param("adminId") Long adminId);
/**
* 根据角色ID查询菜单ID列表
* @param roleId 角色ID
* @return 菜单ID列表
*/
@Select("SELECT menu_id FROM sys_role_menu WHERE role_id = #{roleId}")
List<Long> selectMenuIdsByRoleId(@Param("roleId") Long roleId);
}

View File

@ -0,0 +1,12 @@
package com.example.admin_server.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.admin_server.model.entity.SysRole;
import org.apache.ibatis.annotations.Mapper;
/**
* 角色Mapper接口
*/
@Mapper
public interface SysRoleMapper extends BaseMapper<SysRole> {
}

View File

@ -0,0 +1,23 @@
package com.example.admin_server.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.admin_server.model.entity.SysRoleMenu;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 角色菜单关联Mapper接口
*/
@Mapper
public interface SysRoleMenuMapper extends BaseMapper<SysRoleMenu> {
/**
* 批量插入角色菜单关联
* @param roleId 角色ID
* @param menuIds 菜单ID列表
* @return 影响行数
*/
int batchInsert(@Param("roleId") Long roleId, @Param("menuIds") List<Long> menuIds);
}

View File

@ -0,0 +1,146 @@
package com.example.admin_server.model.common;
import lombok.Data;
import java.io.Serializable;
/**
* 通用响应结果类
*/
@Data
public class Result<T> implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 状态码
*/
private Integer code;
/**
* 消息
*/
private String message;
/**
* 数据
*/
private T data;
/**
* 是否成功
*/
private boolean success;
/**
* 私有构造方法通过静态方法创建实例
*/
private Result() {
}
/**
* 成功结果
* @param <T> 数据类型
* @return 结果对象
*/
public static <T> Result<T> success() {
return success(null);
}
/**
* 成功结果
* @param data 数据
* @param <T> 数据类型
* @return 结果对象
*/
public static <T> Result<T> success(T data) {
return success(data, "操作成功");
}
/**
* 成功结果
* @param data 数据
* @param message 消息
* @param <T> 数据类型
* @return 结果对象
*/
public static <T> Result<T> success(T data, String message) {
Result<T> result = new Result<>();
result.setCode(200);
result.setMessage(message);
result.setData(data);
result.setSuccess(true);
return result;
}
/**
* 失败结果
* @param <T> 数据类型
* @return 结果对象
*/
public static <T> Result<T> failed() {
return failed("操作失败");
}
/**
* 失败结果
* @param message 消息
* @param <T> 数据类型
* @return 结果对象
*/
public static <T> Result<T> failed(String message) {
return failed(message, 500);
}
/**
* 失败结果
* @param message 消息
* @param code 状态码
* @param <T> 数据类型
* @return 结果对象
*/
public static <T> Result<T> failed(String message, Integer code) {
Result<T> result = new Result<>();
result.setCode(code);
result.setMessage(message);
result.setSuccess(false);
return result;
}
/**
* 参数验证失败结果
* @param <T> 数据类型
* @return 结果对象
*/
public static <T> Result<T> validateFailed() {
return validateFailed("参数验证失败");
}
/**
* 参数验证失败结果
* @param message 消息
* @param <T> 数据类型
* @return 结果对象
*/
public static <T> Result<T> validateFailed(String message) {
return failed(message, 400);
}
/**
* 未授权结果
* @param <T> 数据类型
* @return 结果对象
*/
public static <T> Result<T> unauthorized() {
return failed("暂未登录或token已经过期", 401);
}
/**
* 无权限结果
* @param <T> 数据类型
* @return 结果对象
*/
public static <T> Result<T> forbidden() {
return failed("没有相关权限", 403);
}
}

View File

@ -0,0 +1,74 @@
package com.example.admin_server.model.dto;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* 菜单数据传输对象
*/
@Data
public class MenuDTO {
/**
* 菜单ID
*/
private Long id;
/**
* 父级ID
*/
private Long parentId;
/**
* 菜单名称
*/
@NotBlank(message = "菜单名称不能为空")
private String name;
/**
* 菜单图标
*/
private String icon;
/**
* 路由路径
*/
@NotBlank(message = "路由路径不能为空")
private String path;
/**
* 组件路径
*/
private String component;
/**
* 排序
*/
@NotNull(message = "排序不能为空")
private Integer sort;
/**
* 是否隐藏0-显示1-隐藏
*/
@NotNull(message = "显示状态不能为空")
private Integer hidden;
/**
* 菜单类型0-目录1-菜单2-按钮
*/
@NotNull(message = "菜单类型不能为空")
private Integer type;
/**
* 权限标识
*/
private String permission;
/**
* 状态0-禁用1-启用
*/
@NotNull(message = "状态不能为空")
private Integer status;
}

View File

@ -0,0 +1,53 @@
package com.example.admin_server.model.dto;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
* 角色数据传输对象
*/
@Data
public class RoleDTO {
/**
* 角色ID
*/
private Long id;
/**
* 角色名称
*/
@NotBlank(message = "角色名称不能为空")
private String name;
/**
* 角色编码
*/
@NotBlank(message = "角色编码不能为空")
private String code;
/**
* 角色描述
*/
private String description;
/**
* 排序
*/
@NotNull(message = "排序不能为空")
private Integer sort;
/**
* 状态0-禁用1-启用
*/
@NotNull(message = "状态不能为空")
private Integer status;
/**
* 菜单ID列表
*/
private List<Long> menuIds;
}

View File

@ -0,0 +1,37 @@
package com.example.admin_server.model.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 管理员角色关联实体类
*/
@Data
@TableName("sys_admin_role")
public class SysAdminRole {
/**
* ID
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 管理员ID
*/
private Long adminId;
/**
* 角色ID
*/
private Long roleId;
/**
* 创建时间
*/
private LocalDateTime createTime;
}

View File

@ -0,0 +1,77 @@
package com.example.admin_server.model.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* 系统菜单实体类
*/
@Data
@TableName("sys_menu")
public class SysMenu implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 菜单ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 父菜单ID
*/
private Long parentId;
/**
* 菜单名称
*/
private String menuName;
/**
* 菜单URL
*/
private String menuUrl;
/**
* 权限标识
*/
private String perms;
/**
* 图标
*/
private String icon;
/**
* 类型 0:目录 1:菜单 2:按钮
*/
private Integer type;
/**
* 排序
*/
private Integer sortOrder;
/**
* 是否可见 0:不可见 1:可见
*/
private Integer visible;
/**
* 状态 0:禁用 1:启用
*/
private Integer status;
/**
* 子菜单列表
*/
@TableField(exist = false)
private List<SysMenu> children;
}

View File

@ -0,0 +1,52 @@
package com.example.admin_server.model.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 角色实体类
*/
@Data
@TableName("sys_role")
public class SysRole {
/**
* 角色ID
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 角色名称
*/
private String roleName;
/**
* 角色编码
*/
private String roleCode;
/**
* 角色描述
*/
private String description;
/**
* 状态(0:禁用,1:启用)
*/
private Integer status;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,37 @@
package com.example.admin_server.model.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 角色菜单关联实体类
*/
@Data
@TableName("sys_role_menu")
public class SysRoleMenu {
/**
* ID
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 角色ID
*/
private Long roleId;
/**
* 菜单ID
*/
private Long menuId;
/**
* 创建时间
*/
private LocalDateTime createTime;
}

View File

@ -0,0 +1,34 @@
package com.example.admin_server.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.admin_server.model.entity.SysAdminRole;
import java.util.List;
/**
* 管理员角色关联服务接口
*/
public interface ISysAdminRoleService extends IService<SysAdminRole> {
/**
* 保存管理员角色关联
* @param adminId 管理员ID
* @param roleIds 角色ID列表
* @return 是否成功
*/
boolean saveAdminRoles(Long adminId, List<Long> roleIds);
/**
* 获取管理员的角色ID列表
* @param adminId 管理员ID
* @return 角色ID列表
*/
List<Long> getRoleIdsByAdminId(Long adminId);
/**
* 删除管理员的所有角色关联
* @param adminId 管理员ID
* @return 是否成功
*/
boolean removeByAdminId(Long adminId);
}

View File

@ -0,0 +1,38 @@
package com.example.admin_server.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.admin_server.model.entity.SysMenu;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* 菜单服务接口
*/
public interface ISysMenuService extends IService<SysMenu> {
/**
* 获取管理员的菜单列表
* @param adminId 管理员ID
* @return 菜单列表
*/
List<SysMenu> getMenusByAdminId(Long adminId);
/**
* 构建菜单树
* @param menus 菜单列表
* @return 菜单树
*/
List<SysMenu> buildMenuTree(List<SysMenu> menus);
/**
* 获取所有菜单树
* @return 菜单树
*/
List<SysMenu> getAllMenuTree();
@Transactional(rollbackFor = Exception.class)
@CacheEvict(allEntries = true)
boolean removeById(Long id);
}

View File

@ -0,0 +1,27 @@
package com.example.admin_server.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.admin_server.model.entity.SysRoleMenu;
import java.util.List;
/**
* 角色菜单关联服务接口
*/
public interface ISysRoleMenuService extends IService<SysRoleMenu> {
/**
* 保存角色菜单关联
* @param roleId 角色ID
* @param menuIds 菜单ID列表
* @return 是否成功
*/
boolean saveRoleMenus(Long roleId, List<Long> menuIds);
/**
* 删除角色的所有菜单关联
* @param roleId 角色ID
* @return 是否成功
*/
boolean removeByRoleId(Long roleId);
}

View File

@ -0,0 +1,51 @@
package com.example.admin_server.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.admin_server.model.entity.SysRole;
import com.example.admin_server.model.query.RoleQuery;
import java.util.List;
/**
* 角色服务接口
*/
public interface ISysRoleService extends IService<SysRole> {
/**
* 创建角色
* @param role 角色信息
* @param menuIds 菜单ID列表
* @return 是否成功
*/
boolean createRole(SysRole role, List<Long> menuIds);
/**
* 更新角色
* @param role 角色信息
* @param menuIds 菜单ID列表
* @return 是否成功
*/
boolean updateRole(SysRole role, List<Long> menuIds);
/**
* 删除角色
* @param roleId 角色ID
* @return 是否成功
*/
boolean deleteRole(Long roleId);
/**
* 获取角色的菜单ID列表
* @param roleId 角色ID
* @return 菜单ID列表
*/
List<Long> getRoleMenuIds(Long roleId);
/**
* 角色分页查询
* @param query 查询参数
* @return 分页结果
*/
IPage<SysRole> pageList(RoleQuery query);
}

View File

@ -0,0 +1,53 @@
package com.example.admin_server.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.admin_server.mapper.SysAdminRoleMapper;
import com.example.admin_server.model.entity.SysAdminRole;
import com.example.admin_server.service.ISysAdminRoleService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.stream.Collectors;
/**
* 管理员角色关联服务实现类
*/
@Service
public class SysAdminRoleServiceImpl extends ServiceImpl<SysAdminRoleMapper, SysAdminRole> implements ISysAdminRoleService {
@Override
@Transactional(rollbackFor = Exception.class)
public boolean saveAdminRoles(Long adminId, List<Long> roleIds) {
// 先删除原有的关联
removeByAdminId(adminId);
// 如果roleIds为空则直接返回true表示只是清空关联
if (roleIds == null || roleIds.isEmpty()) {
return true;
}
// 批量插入新的关联
return baseMapper.batchInsert(adminId, roleIds) > 0;
}
@Override
public List<Long> getRoleIdsByAdminId(Long adminId) {
// 查询管理员的角色关联
List<SysAdminRole> adminRoles = list(new LambdaQueryWrapper<SysAdminRole>()
.eq(SysAdminRole::getAdminId, adminId));
// 提取角色ID
return adminRoles.stream()
.map(SysAdminRole::getRoleId)
.collect(Collectors.toList());
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean removeByAdminId(Long adminId) {
return remove(new LambdaQueryWrapper<SysAdminRole>()
.eq(SysAdminRole::getAdminId, adminId));
}
}

View File

@ -0,0 +1,150 @@
package com.example.admin_server.service.impl;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.admin_server.common.exception.BusinessException;
import com.example.admin_server.mapper.SysMenuMapper;
import com.example.admin_server.model.entity.SysMenu;
import com.example.admin_server.service.ISysMenuService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 菜单服务实现类
*/
@Slf4j
@Service
@CacheConfig(cacheNames = "sys_menu")
public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu> implements ISysMenuService {
@Override
@Cacheable(key = "'admin_menu:' + #adminId")
public List<SysMenu> getMenusByAdminId(Long adminId) {
if (adminId == null) {
throw new BusinessException("管理员ID不能为空");
}
log.info("获取管理员[{}]的菜单列表", adminId);
// 获取管理员的菜单列表
List<SysMenu> menus = baseMapper.selectMenusByAdminId(adminId);
if (CollectionUtil.isEmpty(menus)) {
return new ArrayList<>();
}
// 构建菜单树并排序
return buildMenuTree(menus);
}
@Override
public List<SysMenu> buildMenuTree(List<SysMenu> menus) {
if (CollectionUtil.isEmpty(menus)) {
return new ArrayList<>();
}
// 将菜单列表转换为Map方便查找
Map<Long, SysMenu> menuMap = menus.stream()
.collect(Collectors.toMap(SysMenu::getId, menu -> menu));
List<SysMenu> rootMenus = new ArrayList<>();
// 遍历所有菜单将子菜单添加到父菜单的children列表中
menus.forEach(menu -> {
Long parentId = menu.getParentId();
if (parentId == null || parentId == 0) {
// 如果是根菜单直接添加到结果列表
rootMenus.add(menu);
} else {
// 如果是子菜单添加到父菜单的children列表
SysMenu parentMenu = menuMap.get(parentId);
if (parentMenu != null) {
if (parentMenu.getChildren() == null) {
parentMenu.setChildren(new ArrayList<>());
}
parentMenu.getChildren().add(menu);
// 对子菜单列表进行排序
parentMenu.getChildren().sort(Comparator.comparing(SysMenu::getSortOrder));
}
}
});
// 对根菜单进行排序
rootMenus.sort(Comparator.comparing(SysMenu::getSortOrder));
return rootMenus;
}
@Override
@Cacheable(key = "'all_menu_tree'")
public List<SysMenu> getAllMenuTree() {
log.info("获取所有菜单树");
// 获取所有菜单按照排序字段升序排列
List<SysMenu> allMenus = list(new LambdaQueryWrapper<SysMenu>()
.orderByAsc(SysMenu::getSortOrder));
// 构建菜单树
return buildMenuTree(allMenus);
}
@Override
@Transactional(rollbackFor = Exception.class)
@CacheEvict(allEntries = true)
public boolean save(SysMenu menu) {
validateMenu(menu);
return super.save(menu);
}
@Override
@Transactional(rollbackFor = Exception.class)
@CacheEvict(allEntries = true)
public boolean updateById(SysMenu menu) {
validateMenu(menu);
return super.updateById(menu);
}
@Transactional(rollbackFor = Exception.class)
@CacheEvict(allEntries = true)
@Override
public boolean removeById(Long id) {
// 检查是否存在子菜单
long count = count(new LambdaQueryWrapper<SysMenu>()
.eq(SysMenu::getParentId, id));
if (count > 0) {
throw new BusinessException("存在子菜单,无法删除");
}
return super.removeById(id);
}
/**
* 验证菜单信息
*
* @param menu 菜单信息
*/
private void validateMenu(SysMenu menu) {
if (menu == null) {
throw new BusinessException("菜单信息不能为空");
}
if (menu.getParentId() != null && menu.getParentId() != 0) {
// 验证父菜单是否存在
SysMenu parentMenu = getById(menu.getParentId());
if (parentMenu == null) {
throw new BusinessException("父菜单不存在");
}
}
// 验证菜单名称是否重复
long count = count(new LambdaQueryWrapper<SysMenu>()
.eq(SysMenu::getMenuName, menu.getMenuName())
.ne(menu.getId() != null, SysMenu::getId, menu.getId()));
if (count > 0) {
throw new BusinessException("菜单名称已存在");
}
}
}

View File

@ -0,0 +1,40 @@
package com.example.admin_server.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.admin_server.mapper.SysRoleMenuMapper;
import com.example.admin_server.model.entity.SysRoleMenu;
import com.example.admin_server.service.ISysRoleMenuService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* 角色菜单关联服务实现类
*/
@Service
public class SysRoleMenuServiceImpl extends ServiceImpl<SysRoleMenuMapper, SysRoleMenu> implements ISysRoleMenuService {
@Override
@Transactional(rollbackFor = Exception.class)
public boolean saveRoleMenus(Long roleId, List<Long> menuIds) {
// 先删除原有的关联
removeByRoleId(roleId);
// 如果menuIds为空则直接返回true表示只是清空关联
if (menuIds == null || menuIds.isEmpty()) {
return true;
}
// 批量插入新的关联
return baseMapper.batchInsert(roleId, menuIds) > 0;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean removeByRoleId(Long roleId) {
return remove(new LambdaQueryWrapper<SysRoleMenu>()
.eq(SysRoleMenu::getRoleId, roleId));
}
}

View File

@ -0,0 +1,80 @@
package com.example.admin_server.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.admin_server.mapper.SysRoleMapper;
import com.example.admin_server.model.entity.SysRole;
import com.example.admin_server.model.query.RoleQuery;
import com.example.admin_server.service.ISysRoleMenuService;
import com.example.admin_server.service.ISysRoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* 角色服务实现类
*/
@Service
public class SysRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRole> implements ISysRoleService {
@Autowired
private ISysRoleMenuService roleMenuService;
@Override
@Transactional(rollbackFor = Exception.class)
public boolean createRole(SysRole role, List<Long> menuIds) {
// 保存角色基本信息
boolean saved = save(role);
if (saved && menuIds != null && !menuIds.isEmpty()) {
// 保存角色菜单关联
return roleMenuService.saveRoleMenus(role.getId(), menuIds);
}
return saved;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean updateRole(SysRole role, List<Long> menuIds) {
// 更新角色基本信息
boolean updated = updateById(role);
if (updated) {
// 先删除原有的菜单关联
roleMenuService.removeByRoleId(role.getId());
// 如果有新的菜单关联则保存
if (menuIds != null && !menuIds.isEmpty()) {
return roleMenuService.saveRoleMenus(role.getId(), menuIds);
}
}
return updated;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean deleteRole(Long roleId) {
// 先删除角色菜单关联
roleMenuService.removeByRoleId(roleId);
// 删除角色
return removeById(roleId);
}
@Override
public List<Long> getRoleMenuIds(Long roleId) {
return baseMapper.selectMenuIdsByRoleId(roleId);
}
@Override
public IPage<SysRole> pageList(RoleQuery query) {
Page<SysRole> page = new Page<>(query.getPageNum(), query.getPageSize());
LambdaQueryWrapper<SysRole> wrapper = new LambdaQueryWrapper<>();
if (query.getKeyword() != null && !query.getKeyword().trim().isEmpty()) {
wrapper.like(SysRole::getName, query.getKeyword())
.or()
.like(SysRole::getCode, query.getKeyword());
}
wrapper.orderByAsc(SysRole::getSort);
return this.page(page, wrapper);
}
}

View File

@ -0,0 +1,64 @@
-- 角色表
CREATE TABLE sys_role (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '角色ID',
role_name VARCHAR(50) NOT NULL COMMENT '角色名称',
role_code VARCHAR(50) NOT NULL COMMENT '角色编码',
description VARCHAR(255) COMMENT '角色描述',
status TINYINT DEFAULT 1 COMMENT '状态(0:禁用,1:启用)',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
UNIQUE KEY uk_role_code (role_code)
) COMMENT '角色表';
-- 菜单表
CREATE TABLE sys_menu (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '菜单ID',
parent_id BIGINT DEFAULT 0 COMMENT '父菜单ID',
menu_name VARCHAR(50) NOT NULL COMMENT '菜单名称',
path VARCHAR(200) COMMENT '路由路径',
component VARCHAR(255) COMMENT '组件路径',
redirect VARCHAR(255) COMMENT '重定向地址',
icon VARCHAR(50) COMMENT '菜单图标',
sort_order INT DEFAULT 0 COMMENT '排序',
keep_alive TINYINT DEFAULT 0 COMMENT '是否缓存(0:不缓存,1:缓存)',
hidden TINYINT DEFAULT 0 COMMENT '是否隐藏(0:显示,1:隐藏)',
type TINYINT NOT NULL COMMENT '菜单类型(1:目录,2:菜单,3:按钮)',
status TINYINT DEFAULT 1 COMMENT '状态(0:禁用,1:启用)',
permission VARCHAR(100) COMMENT '权限标识',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
) COMMENT '菜单表';
-- 角色菜单关联表
CREATE TABLE sys_role_menu (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID',
role_id BIGINT NOT NULL COMMENT '角色ID',
menu_id BIGINT NOT NULL COMMENT '菜单ID',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
UNIQUE KEY uk_role_menu (role_id, menu_id)
) COMMENT '角色菜单关联表';
-- 管理员角色关联表
CREATE TABLE sys_admin_role (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID',
admin_id BIGINT NOT NULL COMMENT '管理员ID',
role_id BIGINT NOT NULL COMMENT '角色ID',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
UNIQUE KEY uk_admin_role (admin_id, role_id)
) COMMENT '管理员角色关联表';
-- 初始化超级管理员角色
INSERT INTO sys_role (role_name, role_code, description) VALUES
('超级管理员', 'ROLE_SUPER_ADMIN', '系统超级管理员');
-- 初始化基础菜单数据
INSERT INTO sys_menu (parent_id, menu_name, path, component, icon, type, permission) VALUES
(0, '仪表盘', 'dashboard', 'Dashboard', 'dashboard', 2, 'dashboard:view'),
(0, '系统管理', 'system', null, 'setting', 1, 'system:view'),
(2, '用户管理', 'user', 'system/UserManagement', 'user', 2, 'system:user:view'),
(2, '角色管理', 'role', 'system/RoleManagement', 'team', 2, 'system:role:view'),
(2, '菜单管理', 'menu', 'system/MenuManagement', 'menu', 2, 'system:menu:view');
-- 为超级管理员分配所有菜单
INSERT INTO sys_role_menu (role_id, menu_id)
SELECT 1, id FROM sys_menu;

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.admin_server.mapper.SysAdminRoleMapper">
<!-- 批量插入管理员角色关联 -->
<insert id="batchInsert">
INSERT INTO sys_admin_role (admin_id, role_id, create_time)
VALUES
<foreach collection="roleIds" item="roleId" separator=",">
(#{adminId}, #{roleId}, NOW())
</foreach>
</insert>
</mapper>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.admin_server.mapper.SysRoleMenuMapper">
<!-- 批量插入角色菜单关联 -->
<insert id="batchInsert">
INSERT INTO sys_role_menu (role_id, menu_id, create_time)
VALUES
<foreach collection="menuIds" item="menuId" separator=",">
(#{roleId}, #{menuId}, NOW())
</foreach>
</insert>
</mapper>