From d4ac89bd28cba777b032d3edd03886ab42a80516 Mon Sep 17 00:00:00 2001 From: FalingCliff Date: Sun, 15 Jun 2025 23:27:43 +0800 Subject: [PATCH] =?UTF-8?q?feat(admin-server):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E8=A7=92=E8=89=B2=E4=B8=8E=E8=8F=9C=E5=8D=95=E6=9D=83=E9=99=90?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E7=9B=B8=E5=85=B3=E5=8A=9F=E8=83=BD=E5=8F=8A?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93=E8=A1=A8=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/exception/BusinessException.java | 35 ++++ .../controller/admin/SysMenuController.java | 86 ++++++++++ .../controller/admin/SysRoleController.java | 72 +++++++++ .../mapper/SysAdminRoleMapper.java | 32 ++++ .../admin_server/mapper/SysMenuMapper.java | 36 +++++ .../admin_server/mapper/SysRoleMapper.java | 12 ++ .../mapper/SysRoleMenuMapper.java | 23 +++ .../admin_server/model/common/Result.java | 146 +++++++++++++++++ .../admin_server/model/dto/MenuDTO.java | 74 +++++++++ .../admin_server/model/dto/RoleDTO.java | 53 +++++++ .../model/entity/SysAdminRole.java | 37 +++++ .../admin_server/model/entity/SysMenu.java | 77 +++++++++ .../admin_server/model/entity/SysRole.java | 52 ++++++ .../model/entity/SysRoleMenu.java | 37 +++++ .../service/ISysAdminRoleService.java | 34 ++++ .../admin_server/service/ISysMenuService.java | 38 +++++ .../service/ISysRoleMenuService.java | 27 ++++ .../admin_server/service/ISysRoleService.java | 51 ++++++ .../service/impl/SysAdminRoleServiceImpl.java | 53 +++++++ .../service/impl/SysMenuServiceImpl.java | 150 ++++++++++++++++++ .../service/impl/SysRoleMenuServiceImpl.java | 40 +++++ .../service/impl/SysRoleServiceImpl.java | 80 ++++++++++ src/main/resources/db/menu.sql | 64 ++++++++ .../resources/mapper/SysAdminRoleMapper.xml | 14 ++ .../resources/mapper/SysRoleMenuMapper.xml | 14 ++ 25 files changed, 1337 insertions(+) create mode 100644 src/main/java/com/example/admin_server/common/exception/BusinessException.java create mode 100644 src/main/java/com/example/admin_server/controller/admin/SysMenuController.java create mode 100644 src/main/java/com/example/admin_server/controller/admin/SysRoleController.java create mode 100644 src/main/java/com/example/admin_server/mapper/SysAdminRoleMapper.java create mode 100644 src/main/java/com/example/admin_server/mapper/SysMenuMapper.java create mode 100644 src/main/java/com/example/admin_server/mapper/SysRoleMapper.java create mode 100644 src/main/java/com/example/admin_server/mapper/SysRoleMenuMapper.java create mode 100644 src/main/java/com/example/admin_server/model/common/Result.java create mode 100644 src/main/java/com/example/admin_server/model/dto/MenuDTO.java create mode 100644 src/main/java/com/example/admin_server/model/dto/RoleDTO.java create mode 100644 src/main/java/com/example/admin_server/model/entity/SysAdminRole.java create mode 100644 src/main/java/com/example/admin_server/model/entity/SysMenu.java create mode 100644 src/main/java/com/example/admin_server/model/entity/SysRole.java create mode 100644 src/main/java/com/example/admin_server/model/entity/SysRoleMenu.java create mode 100644 src/main/java/com/example/admin_server/service/ISysAdminRoleService.java create mode 100644 src/main/java/com/example/admin_server/service/ISysMenuService.java create mode 100644 src/main/java/com/example/admin_server/service/ISysRoleMenuService.java create mode 100644 src/main/java/com/example/admin_server/service/ISysRoleService.java create mode 100644 src/main/java/com/example/admin_server/service/impl/SysAdminRoleServiceImpl.java create mode 100644 src/main/java/com/example/admin_server/service/impl/SysMenuServiceImpl.java create mode 100644 src/main/java/com/example/admin_server/service/impl/SysRoleMenuServiceImpl.java create mode 100644 src/main/java/com/example/admin_server/service/impl/SysRoleServiceImpl.java create mode 100644 src/main/resources/db/menu.sql create mode 100644 src/main/resources/mapper/SysAdminRoleMapper.xml create mode 100644 src/main/resources/mapper/SysRoleMenuMapper.xml diff --git a/src/main/java/com/example/admin_server/common/exception/BusinessException.java b/src/main/java/com/example/admin_server/common/exception/BusinessException.java new file mode 100644 index 0000000..266bb9a --- /dev/null +++ b/src/main/java/com/example/admin_server/common/exception/BusinessException.java @@ -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; + } +} diff --git a/src/main/java/com/example/admin_server/controller/admin/SysMenuController.java b/src/main/java/com/example/admin_server/controller/admin/SysMenuController.java new file mode 100644 index 0000000..6e3b036 --- /dev/null +++ b/src/main/java/com/example/admin_server/controller/admin/SysMenuController.java @@ -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() + .eq(SysMenu::getParentId, id))); + if (count > 0) { + return Result.fail("存在子菜单,无法删除"); + } + return Result.ok(menuService.removeById(id)); + } + + @GetMapping("/detail") + @ApiOperation("获取菜单详情") + public Result getMenuDetail(@RequestParam Long id) { + SysMenu menu = menuService.getById(id); + return menu != null ? Result.ok(menu) : Result.fail("菜单不存在"); + } + + @GetMapping("/tree") + @ApiOperation("获取菜单树") + public Result> getMenuTree() { + List menuTree = menuService.getAllMenuTree(); + return Result.ok(menuTree); + } + + @GetMapping("/current") + @ApiOperation("获取当前管理员的菜单树") + public Result> getCurrentAdminMenus() { + // 这里应该从安全上下文中获取当前登录的管理员ID + // 为了演示,这里使用一个固定的ID + Long adminId = 1L; // 实际应用中应该从SecurityContext获取 + List menuTree = menuService.getMenusByAdminId(adminId); + return Result.ok(menuTree); + } + + @GetMapping("/list") + @ApiOperation("获取所有菜单列表") + public Result> getMenuList() { + List menus = menuService.list(); + return Result.ok(menus); + } +} diff --git a/src/main/java/com/example/admin_server/controller/admin/SysRoleController.java b/src/main/java/com/example/admin_server/controller/admin/SysRoleController.java new file mode 100644 index 0000000..8333e47 --- /dev/null +++ b/src/main/java/com/example/admin_server/controller/admin/SysRoleController.java @@ -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 getRoleDetail(@RequestParam Long id) { + SysRole role = roleService.getById(id); + return role != null ? Result.ok(role) : Result.fail("角色不存在"); + } + + @GetMapping("/list") + @ApiOperation("获取角色列表") + public Result> getRoleList() { + return Result.ok(roleService.list()); + } + + @PostMapping("/menu/assign") + @ApiOperation("分配角色菜单") + public Result assignRoleMenu(@RequestParam Long roleId, @RequestBody List menuIds) { + roleService.assignMenus(roleId, menuIds); + return Result.ok(); + } + + @GetMapping("/menu/list") + @ApiOperation("获取角色菜单") + public Result> getRoleMenus(@RequestParam Long roleId) { + return Result.ok(roleService.getMenuIdsByRoleId(roleId)); + } +} diff --git a/src/main/java/com/example/admin_server/mapper/SysAdminRoleMapper.java b/src/main/java/com/example/admin_server/mapper/SysAdminRoleMapper.java new file mode 100644 index 0000000..5f1cd12 --- /dev/null +++ b/src/main/java/com/example/admin_server/mapper/SysAdminRoleMapper.java @@ -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 { + + /** + * 根据管理员ID查询角色ID列表 + * @param adminId 管理员ID + * @return 角色ID列表 + */ + @Select("SELECT role_id FROM sys_admin_role WHERE admin_id = #{adminId}") + List selectRoleIdsByAdminId(@Param("adminId") Long adminId); + + /** + * 批量插入管理员角色关联 + * @param adminId 管理员ID + * @param roleIds 角色ID列表 + * @return 影响行数 + */ + int batchInsert(@Param("adminId") Long adminId, @Param("roleIds") List roleIds); +} diff --git a/src/main/java/com/example/admin_server/mapper/SysMenuMapper.java b/src/main/java/com/example/admin_server/mapper/SysMenuMapper.java new file mode 100644 index 0000000..6b3166d --- /dev/null +++ b/src/main/java/com/example/admin_server/mapper/SysMenuMapper.java @@ -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 { + + /** + * 根据管理员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 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 selectMenuIdsByRoleId(@Param("roleId") Long roleId); +} diff --git a/src/main/java/com/example/admin_server/mapper/SysRoleMapper.java b/src/main/java/com/example/admin_server/mapper/SysRoleMapper.java new file mode 100644 index 0000000..193e334 --- /dev/null +++ b/src/main/java/com/example/admin_server/mapper/SysRoleMapper.java @@ -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 { +} diff --git a/src/main/java/com/example/admin_server/mapper/SysRoleMenuMapper.java b/src/main/java/com/example/admin_server/mapper/SysRoleMenuMapper.java new file mode 100644 index 0000000..5c1db1d --- /dev/null +++ b/src/main/java/com/example/admin_server/mapper/SysRoleMenuMapper.java @@ -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 { + + /** + * 批量插入角色菜单关联 + * @param roleId 角色ID + * @param menuIds 菜单ID列表 + * @return 影响行数 + */ + int batchInsert(@Param("roleId") Long roleId, @Param("menuIds") List menuIds); +} diff --git a/src/main/java/com/example/admin_server/model/common/Result.java b/src/main/java/com/example/admin_server/model/common/Result.java new file mode 100644 index 0000000..8ea9f57 --- /dev/null +++ b/src/main/java/com/example/admin_server/model/common/Result.java @@ -0,0 +1,146 @@ +package com.example.admin_server.model.common; + +import lombok.Data; + +import java.io.Serializable; + +/** + * 通用响应结果类 + */ +@Data +public class Result implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 状态码 + */ + private Integer code; + + /** + * 消息 + */ + private String message; + + /** + * 数据 + */ + private T data; + + /** + * 是否成功 + */ + private boolean success; + + /** + * 私有构造方法,通过静态方法创建实例 + */ + private Result() { + } + + /** + * 成功结果 + * @param 数据类型 + * @return 结果对象 + */ + public static Result success() { + return success(null); + } + + /** + * 成功结果 + * @param data 数据 + * @param 数据类型 + * @return 结果对象 + */ + public static Result success(T data) { + return success(data, "操作成功"); + } + + /** + * 成功结果 + * @param data 数据 + * @param message 消息 + * @param 数据类型 + * @return 结果对象 + */ + public static Result success(T data, String message) { + Result result = new Result<>(); + result.setCode(200); + result.setMessage(message); + result.setData(data); + result.setSuccess(true); + return result; + } + + /** + * 失败结果 + * @param 数据类型 + * @return 结果对象 + */ + public static Result failed() { + return failed("操作失败"); + } + + /** + * 失败结果 + * @param message 消息 + * @param 数据类型 + * @return 结果对象 + */ + public static Result failed(String message) { + return failed(message, 500); + } + + /** + * 失败结果 + * @param message 消息 + * @param code 状态码 + * @param 数据类型 + * @return 结果对象 + */ + public static Result failed(String message, Integer code) { + Result result = new Result<>(); + result.setCode(code); + result.setMessage(message); + result.setSuccess(false); + return result; + } + + /** + * 参数验证失败结果 + * @param 数据类型 + * @return 结果对象 + */ + public static Result validateFailed() { + return validateFailed("参数验证失败"); + } + + /** + * 参数验证失败结果 + * @param message 消息 + * @param 数据类型 + * @return 结果对象 + */ + public static Result validateFailed(String message) { + return failed(message, 400); + } + + /** + * 未授权结果 + * @param 数据类型 + * @return 结果对象 + */ + public static Result unauthorized() { + return failed("暂未登录或token已经过期", 401); + } + + /** + * 无权限结果 + * @param 数据类型 + * @return 结果对象 + */ + public static Result forbidden() { + return failed("没有相关权限", 403); + } +} diff --git a/src/main/java/com/example/admin_server/model/dto/MenuDTO.java b/src/main/java/com/example/admin_server/model/dto/MenuDTO.java new file mode 100644 index 0000000..3a874a7 --- /dev/null +++ b/src/main/java/com/example/admin_server/model/dto/MenuDTO.java @@ -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; +} diff --git a/src/main/java/com/example/admin_server/model/dto/RoleDTO.java b/src/main/java/com/example/admin_server/model/dto/RoleDTO.java new file mode 100644 index 0000000..873b4f9 --- /dev/null +++ b/src/main/java/com/example/admin_server/model/dto/RoleDTO.java @@ -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 menuIds; +} diff --git a/src/main/java/com/example/admin_server/model/entity/SysAdminRole.java b/src/main/java/com/example/admin_server/model/entity/SysAdminRole.java new file mode 100644 index 0000000..bba0763 --- /dev/null +++ b/src/main/java/com/example/admin_server/model/entity/SysAdminRole.java @@ -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; +} diff --git a/src/main/java/com/example/admin_server/model/entity/SysMenu.java b/src/main/java/com/example/admin_server/model/entity/SysMenu.java new file mode 100644 index 0000000..cd0e92c --- /dev/null +++ b/src/main/java/com/example/admin_server/model/entity/SysMenu.java @@ -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 children; +} diff --git a/src/main/java/com/example/admin_server/model/entity/SysRole.java b/src/main/java/com/example/admin_server/model/entity/SysRole.java new file mode 100644 index 0000000..7f6d2d7 --- /dev/null +++ b/src/main/java/com/example/admin_server/model/entity/SysRole.java @@ -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; +} diff --git a/src/main/java/com/example/admin_server/model/entity/SysRoleMenu.java b/src/main/java/com/example/admin_server/model/entity/SysRoleMenu.java new file mode 100644 index 0000000..42bed97 --- /dev/null +++ b/src/main/java/com/example/admin_server/model/entity/SysRoleMenu.java @@ -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; +} diff --git a/src/main/java/com/example/admin_server/service/ISysAdminRoleService.java b/src/main/java/com/example/admin_server/service/ISysAdminRoleService.java new file mode 100644 index 0000000..1d7e685 --- /dev/null +++ b/src/main/java/com/example/admin_server/service/ISysAdminRoleService.java @@ -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 { + + /** + * 保存管理员角色关联 + * @param adminId 管理员ID + * @param roleIds 角色ID列表 + * @return 是否成功 + */ + boolean saveAdminRoles(Long adminId, List roleIds); + + /** + * 获取管理员的角色ID列表 + * @param adminId 管理员ID + * @return 角色ID列表 + */ + List getRoleIdsByAdminId(Long adminId); + + /** + * 删除管理员的所有角色关联 + * @param adminId 管理员ID + * @return 是否成功 + */ + boolean removeByAdminId(Long adminId); +} diff --git a/src/main/java/com/example/admin_server/service/ISysMenuService.java b/src/main/java/com/example/admin_server/service/ISysMenuService.java new file mode 100644 index 0000000..1051fa3 --- /dev/null +++ b/src/main/java/com/example/admin_server/service/ISysMenuService.java @@ -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 { + + /** + * 获取管理员的菜单列表 + * @param adminId 管理员ID + * @return 菜单列表 + */ + List getMenusByAdminId(Long adminId); + + /** + * 构建菜单树 + * @param menus 菜单列表 + * @return 菜单树 + */ + List buildMenuTree(List menus); + + /** + * 获取所有菜单树 + * @return 菜单树 + */ + List getAllMenuTree(); + + @Transactional(rollbackFor = Exception.class) + @CacheEvict(allEntries = true) + boolean removeById(Long id); +} diff --git a/src/main/java/com/example/admin_server/service/ISysRoleMenuService.java b/src/main/java/com/example/admin_server/service/ISysRoleMenuService.java new file mode 100644 index 0000000..0aefa0a --- /dev/null +++ b/src/main/java/com/example/admin_server/service/ISysRoleMenuService.java @@ -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 { + + /** + * 保存角色菜单关联 + * @param roleId 角色ID + * @param menuIds 菜单ID列表 + * @return 是否成功 + */ + boolean saveRoleMenus(Long roleId, List menuIds); + + /** + * 删除角色的所有菜单关联 + * @param roleId 角色ID + * @return 是否成功 + */ + boolean removeByRoleId(Long roleId); +} diff --git a/src/main/java/com/example/admin_server/service/ISysRoleService.java b/src/main/java/com/example/admin_server/service/ISysRoleService.java new file mode 100644 index 0000000..847ecf1 --- /dev/null +++ b/src/main/java/com/example/admin_server/service/ISysRoleService.java @@ -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 { + + /** + * 创建角色 + * @param role 角色信息 + * @param menuIds 菜单ID列表 + * @return 是否成功 + */ + boolean createRole(SysRole role, List menuIds); + + /** + * 更新角色 + * @param role 角色信息 + * @param menuIds 菜单ID列表 + * @return 是否成功 + */ + boolean updateRole(SysRole role, List menuIds); + + /** + * 删除角色 + * @param roleId 角色ID + * @return 是否成功 + */ + boolean deleteRole(Long roleId); + + /** + * 获取角色的菜单ID列表 + * @param roleId 角色ID + * @return 菜单ID列表 + */ + List getRoleMenuIds(Long roleId); + + /** + * 角色分页查询 + * @param query 查询参数 + * @return 分页结果 + */ + IPage pageList(RoleQuery query); +} diff --git a/src/main/java/com/example/admin_server/service/impl/SysAdminRoleServiceImpl.java b/src/main/java/com/example/admin_server/service/impl/SysAdminRoleServiceImpl.java new file mode 100644 index 0000000..c4af019 --- /dev/null +++ b/src/main/java/com/example/admin_server/service/impl/SysAdminRoleServiceImpl.java @@ -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 implements ISysAdminRoleService { + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean saveAdminRoles(Long adminId, List roleIds) { + // 先删除原有的关联 + removeByAdminId(adminId); + + // 如果roleIds为空,则直接返回true(表示只是清空关联) + if (roleIds == null || roleIds.isEmpty()) { + return true; + } + + // 批量插入新的关联 + return baseMapper.batchInsert(adminId, roleIds) > 0; + } + + @Override + public List getRoleIdsByAdminId(Long adminId) { + // 查询管理员的角色关联 + List adminRoles = list(new LambdaQueryWrapper() + .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() + .eq(SysAdminRole::getAdminId, adminId)); + } +} diff --git a/src/main/java/com/example/admin_server/service/impl/SysMenuServiceImpl.java b/src/main/java/com/example/admin_server/service/impl/SysMenuServiceImpl.java new file mode 100644 index 0000000..7bd296c --- /dev/null +++ b/src/main/java/com/example/admin_server/service/impl/SysMenuServiceImpl.java @@ -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 implements ISysMenuService { + + @Override + @Cacheable(key = "'admin_menu:' + #adminId") + public List getMenusByAdminId(Long adminId) { + if (adminId == null) { + throw new BusinessException("管理员ID不能为空"); + } + + log.info("获取管理员[{}]的菜单列表", adminId); + // 获取管理员的菜单列表 + List menus = baseMapper.selectMenusByAdminId(adminId); + if (CollectionUtil.isEmpty(menus)) { + return new ArrayList<>(); + } + + // 构建菜单树并排序 + return buildMenuTree(menus); + } + + @Override + public List buildMenuTree(List menus) { + if (CollectionUtil.isEmpty(menus)) { + return new ArrayList<>(); + } + + // 将菜单列表转换为Map,方便查找 + Map menuMap = menus.stream() + .collect(Collectors.toMap(SysMenu::getId, menu -> menu)); + + List 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 getAllMenuTree() { + log.info("获取所有菜单树"); + // 获取所有菜单,按照排序字段升序排列 + List allMenus = list(new LambdaQueryWrapper() + .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() + .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() + .eq(SysMenu::getMenuName, menu.getMenuName()) + .ne(menu.getId() != null, SysMenu::getId, menu.getId())); + if (count > 0) { + throw new BusinessException("菜单名称已存在"); + } + } +} diff --git a/src/main/java/com/example/admin_server/service/impl/SysRoleMenuServiceImpl.java b/src/main/java/com/example/admin_server/service/impl/SysRoleMenuServiceImpl.java new file mode 100644 index 0000000..001afed --- /dev/null +++ b/src/main/java/com/example/admin_server/service/impl/SysRoleMenuServiceImpl.java @@ -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 implements ISysRoleMenuService { + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean saveRoleMenus(Long roleId, List 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() + .eq(SysRoleMenu::getRoleId, roleId)); + } +} diff --git a/src/main/java/com/example/admin_server/service/impl/SysRoleServiceImpl.java b/src/main/java/com/example/admin_server/service/impl/SysRoleServiceImpl.java new file mode 100644 index 0000000..b17b94d --- /dev/null +++ b/src/main/java/com/example/admin_server/service/impl/SysRoleServiceImpl.java @@ -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 implements ISysRoleService { + + @Autowired + private ISysRoleMenuService roleMenuService; + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean createRole(SysRole role, List 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 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 getRoleMenuIds(Long roleId) { + return baseMapper.selectMenuIdsByRoleId(roleId); + } + + @Override + public IPage pageList(RoleQuery query) { + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + LambdaQueryWrapper 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); + } +} diff --git a/src/main/resources/db/menu.sql b/src/main/resources/db/menu.sql new file mode 100644 index 0000000..369cdbe --- /dev/null +++ b/src/main/resources/db/menu.sql @@ -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; diff --git a/src/main/resources/mapper/SysAdminRoleMapper.xml b/src/main/resources/mapper/SysAdminRoleMapper.xml new file mode 100644 index 0000000..39a1c7b --- /dev/null +++ b/src/main/resources/mapper/SysAdminRoleMapper.xml @@ -0,0 +1,14 @@ + + + + + + + INSERT INTO sys_admin_role (admin_id, role_id, create_time) + VALUES + + (#{adminId}, #{roleId}, NOW()) + + + + diff --git a/src/main/resources/mapper/SysRoleMenuMapper.xml b/src/main/resources/mapper/SysRoleMenuMapper.xml new file mode 100644 index 0000000..0172a30 --- /dev/null +++ b/src/main/resources/mapper/SysRoleMenuMapper.xml @@ -0,0 +1,14 @@ + + + + + + + INSERT INTO sys_role_menu (role_id, menu_id, create_time) + VALUES + + (#{roleId}, #{menuId}, NOW()) + + + +