Browse Source

add:中台增加权限管理(角色与菜单权限crud)

lyx 5 days ago
parent
commit
a0cdb927cf

+ 44 - 0
alien-entity/src/main/java/shop/alien/entity/store/LifeSysRoleMenu.java

@@ -0,0 +1,44 @@
+package shop.alien.entity.store;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 角色菜单关联表
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude
+@TableName("life_sys_role_menu")
+@ApiModel(value = "LifeSysRoleMenu对象", description = "角色菜单关联表")
+public class LifeSysRoleMenu implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "主键ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @ApiModelProperty(value = "角色ID(关联life_sys_role.role_id)")
+    @TableField("role_id")
+    private Long roleId;
+
+    @ApiModelProperty(value = "菜单ID(关联life_sys_menu.menu_id)")
+    @TableField("menu_id")
+    private Long menuId;
+
+    @ApiModelProperty(value = "创建时间")
+    @TableField(value = "created_time", fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+}
+

+ 123 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/MenuWithRoleVo.java

@@ -0,0 +1,123 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import shop.alien.entity.store.LifeSysMenu;
+
+import java.util.List;
+
+/**
+ * 菜单权限VO(包含角色拥有状态)
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@JsonInclude
+@ApiModel(value = "MenuWithRoleVo对象", description = "菜单权限VO(包含角色拥有状态)")
+public class MenuWithRoleVo {
+
+    @ApiModelProperty(value = "菜单ID")
+    private Long menuId;
+
+    @ApiModelProperty(value = "菜单名称")
+    private String menuName;
+
+    @ApiModelProperty(value = "父菜单ID(0表示根菜单)")
+    private Long parentId;
+
+    @ApiModelProperty(value = "菜单类型(M目录 C菜单 F按钮)")
+    private String menuType;
+
+    @ApiModelProperty(value = "显示顺序")
+    private Integer menuSort;
+
+    @ApiModelProperty(value = "路由地址")
+    private String path;
+
+    @ApiModelProperty(value = "组件路径")
+    private String component;
+
+    @ApiModelProperty(value = "权限标识(如:sys:user:list)")
+    private String perms;
+
+    @ApiModelProperty(value = "菜单图标")
+    private String icon;
+
+    @ApiModelProperty(value = "菜单状态(0正常 1停用)")
+    private String status;
+
+    @ApiModelProperty(value = "是否显示(0显示 1隐藏)")
+    private String visible;
+
+    @ApiModelProperty(value = "是否为外链(0是 1否)")
+    private String isFrame;
+
+    @ApiModelProperty(value = "是否缓存(0缓存 1不缓存)")
+    private String isCache;
+
+    @ApiModelProperty(value = "删除标志(0存在 2删除)")
+    private String delFlag;
+
+    @ApiModelProperty(value = "创建者")
+    private String createBy;
+
+    @ApiModelProperty(value = "创建时间")
+    private java.util.Date createdTime;
+
+    @ApiModelProperty(value = "更新者")
+    private String updateBy;
+
+    @ApiModelProperty(value = "更新时间")
+    private java.util.Date updatedTime;
+
+    @ApiModelProperty(value = "备注")
+    private String remark;
+
+    @ApiModelProperty(value = "是否拥有该权限(true:拥有 false:未拥有)")
+    private Boolean hasPermission;
+
+    @ApiModelProperty(value = "子菜单列表(用于树形结构)")
+    private List<MenuWithRoleVo> children;
+
+    /**
+     * 从LifeSysMenu转换为MenuWithRoleVo
+     *
+     * @param menu 菜单实体
+     * @param hasPermission 是否拥有权限
+     * @return MenuWithRoleVo
+     */
+    public static MenuWithRoleVo fromEntity(LifeSysMenu menu, Boolean hasPermission) {
+        if (menu == null) {
+            return null;
+        }
+        MenuWithRoleVo vo = new MenuWithRoleVo();
+        vo.setMenuId(menu.getMenuId());
+        vo.setMenuName(menu.getMenuName());
+        vo.setParentId(menu.getParentId());
+        vo.setMenuType(menu.getMenuType());
+        vo.setMenuSort(menu.getMenuSort());
+        vo.setPath(menu.getPath());
+        vo.setComponent(menu.getComponent());
+        vo.setPerms(menu.getPerms());
+        vo.setIcon(menu.getIcon());
+        vo.setStatus(menu.getStatus());
+        vo.setVisible(menu.getVisible());
+        vo.setIsFrame(menu.getIsFrame());
+        vo.setIsCache(menu.getIsCache());
+        vo.setDelFlag(menu.getDelFlag());
+        vo.setCreateBy(menu.getCreateBy());
+        vo.setCreatedTime(menu.getCreatedTime());
+        vo.setUpdateBy(menu.getUpdateBy());
+        vo.setUpdatedTime(menu.getUpdatedTime());
+        vo.setRemark(menu.getRemark());
+        vo.setHasPermission(hasPermission != null ? hasPermission : false);
+        vo.setChildren(null); // 子菜单会在构建树形结构时设置
+        return vo;
+    }
+}
+

+ 16 - 0
alien-entity/src/main/java/shop/alien/mapper/LifeSysRoleMenuMapper.java

@@ -0,0 +1,16 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import shop.alien.entity.store.LifeSysRoleMenu;
+
+/**
+ * 角色菜单关联表 Mapper 接口
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Mapper
+public interface LifeSysRoleMenuMapper extends BaseMapper<LifeSysRoleMenu> {
+}
+

+ 19 - 0
alien-entity/src/main/resources/mapper/LifeSysRoleMenuMapper.xml

@@ -0,0 +1,19 @@
+<?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="shop.alien.mapper.LifeSysRoleMenuMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="shop.alien.entity.store.LifeSysRoleMenu">
+        <id column="id" property="id" />
+        <result column="role_id" property="roleId" />
+        <result column="menu_id" property="menuId" />
+        <result column="created_time" property="createdTime" />
+    </resultMap>
+
+    <!-- 通用查询结果列 -->
+    <sql id="Base_Column_List">
+        id, role_id, menu_id, created_time
+    </sql>
+
+</mapper>
+

+ 112 - 0
alien-store/src/main/java/shop/alien/store/controller/LifeSysRoleController.java

@@ -2,11 +2,14 @@ package shop.alien.store.controller;
 
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import io.swagger.annotations.*;
+import lombok.Data;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.web.bind.annotation.*;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.LifeSysRole;
+import shop.alien.entity.store.vo.MenuWithRoleVo;
+import shop.alien.store.service.LifeSysRoleMenuService;
 import shop.alien.store.service.LifeSysRoleService;
 
 import java.util.List;
@@ -27,6 +30,7 @@ import java.util.List;
 public class LifeSysRoleController {
 
     private final LifeSysRoleService lifeSysRoleService;
+    private final LifeSysRoleMenuService lifeSysRoleMenuService;
 
     @ApiOperation("分页查询角色列表")
     @ApiOperationSupport(order = 1)
@@ -131,5 +135,113 @@ public class LifeSysRoleController {
         return R.data(roles);
     }
 
+    @ApiOperation("根据角色ID查询所有权限(区分已拥有和未拥有的权限)")
+    @ApiOperationSupport(order = 7)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "roleId", value = "角色ID", dataType = "Long", paramType = "query", required = true),
+    })
+    @GetMapping(value = "/getMenusWithRoleStatus")
+    public R<List<MenuWithRoleVo>> getMenusWithRoleStatus(@RequestParam Long roleId) {
+        log.info("LifeSysRoleMenuController.getMenusWithRoleStatus => roleId={}", roleId);
+        return lifeSysRoleMenuService.getMenusWithRoleStatus(roleId);
+    }
+
+    @ApiOperation("批量为角色分配菜单")
+    @ApiOperationSupport(order = 1)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "roleId", value = "角色ID", dataType = "Long", paramType = "body", required = true),
+            @ApiImplicitParam(name = "menuIds", value = "菜单ID列表", dataType = "List", paramType = "body", required = true),
+    })
+    @PostMapping(value = "/assignMenus")
+    public R<Boolean> assignMenus(@RequestBody AssignMenusDto assignMenusDto) {
+        log.info("LifeSysRoleMenuController.assignMenus => roleId={}, menuIds={}",
+                assignMenusDto.getRoleId(), assignMenusDto.getMenuIds());
+        return lifeSysRoleMenuService.assignMenus(assignMenusDto.getRoleId(), assignMenusDto.getMenuIds());
+    }
+
+    @ApiOperation("移除角色的所有菜单")
+    @ApiOperationSupport(order = 2)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "roleId", value = "角色ID", dataType = "Long", paramType = "query", required = true),
+    })
+    @DeleteMapping(value = "/removeAllMenus")
+    public R<Boolean> removeAllMenus(@RequestParam Long roleId) {
+        log.info("LifeSysRoleMenuController.removeAllMenus => roleId={}", roleId);
+        return lifeSysRoleMenuService.removeAllMenus(roleId);
+    }
+
+
+    @ApiOperation("根据角色ID获取菜单ID列表")
+    @ApiOperationSupport(order = 3)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "roleId", value = "角色ID", dataType = "Long", paramType = "query", required = true),
+    })
+    @GetMapping(value = "/getMenuIdsByRoleId")
+    public R<List<Long>> getMenuIdsByRoleId(@RequestParam Long roleId) {
+        log.info("LifeSysRoleMenuController.getMenuIdsByRoleId => roleId={}", roleId);
+        return lifeSysRoleMenuService.getMenuIdsByRoleId(roleId);
+    }
+
+    @ApiOperation("为角色添加单个菜单")
+    @ApiOperationSupport(order = 4)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "roleId", value = "角色ID", dataType = "Long", paramType = "body", required = true),
+            @ApiImplicitParam(name = "menuId", value = "菜单ID", dataType = "Long", paramType = "body", required = true),
+    })
+    @PostMapping(value = "/addMenu")
+    public R<Boolean> addMenu(@RequestBody AddMenuDto addMenuDto) {
+        log.info("LifeSysRoleMenuController.addMenu => roleId={}, menuId={}",
+                addMenuDto.getRoleId(), addMenuDto.getMenuId());
+        return lifeSysRoleMenuService.addMenu(addMenuDto.getRoleId(), addMenuDto.getMenuId());
+    }
+
+    @ApiOperation("移除角色的单个菜单")
+    @ApiOperationSupport(order = 5)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "roleId", value = "角色ID", dataType = "Long", paramType = "query", required = true),
+            @ApiImplicitParam(name = "menuId", value = "菜单ID", dataType = "Long", paramType = "query", required = true),
+    })
+    @DeleteMapping(value = "/removeMenu")
+    public R<Boolean> removeMenu(@RequestParam Long roleId, @RequestParam Long menuId) {
+        log.info("LifeSysRoleMenuController.removeMenu => roleId={}, menuId={}", roleId, menuId);
+        return lifeSysRoleMenuService.removeMenu(roleId, menuId);
+    }
+
+    @ApiOperation("根据菜单ID获取角色ID列表")
+    @ApiOperationSupport(order = 6)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "menuId", value = "菜单ID", dataType = "Long", paramType = "query", required = true),
+    })
+    @GetMapping(value = "/getRoleIdsByMenuId")
+    public R<List<Long>> getRoleIdsByMenuId(@RequestParam Long menuId) {
+        log.info("LifeSysRoleMenuController.getRoleIdsByMenuId => menuId={}", menuId);
+        return lifeSysRoleMenuService.getRoleIdsByMenuId(menuId);
+    }
+
+    /**
+     * 批量分配菜单请求DTO
+     */
+    @Data
+    @ApiModel(value = "AssignMenusDto", description = "批量分配菜单请求参数")
+    static class AssignMenusDto {
+        @ApiModelProperty(value = "角色ID", required = true)
+        private Long roleId;
+
+        @ApiModelProperty(value = "菜单ID列表", required = true)
+        private List<Long> menuIds;
+    }
+
+    /**
+     * 添加菜单请求DTO
+     */
+    @Data
+    @ApiModel(value = "AddMenuDto", description = "添加菜单请求参数")
+    static class AddMenuDto {
+        @ApiModelProperty(value = "角色ID", required = true)
+        private Long roleId;
+
+        @ApiModelProperty(value = "菜单ID", required = true)
+        private Long menuId;
+    }
 }
 

+ 76 - 0
alien-store/src/main/java/shop/alien/store/service/LifeSysRoleMenuService.java

@@ -0,0 +1,76 @@
+package shop.alien.store.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.LifeSysRoleMenu;
+
+import java.util.List;
+
+/**
+ * 角色菜单关联表 服务接口
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+public interface LifeSysRoleMenuService extends IService<LifeSysRoleMenu> {
+
+    /**
+     * 批量为角色分配菜单
+     *
+     * @param roleId  角色ID
+     * @param menuIds 菜单ID列表
+     * @return R<Boolean>
+     */
+    R<Boolean> assignMenus(Long roleId, List<Long> menuIds);
+
+    /**
+     * 移除角色的所有菜单
+     *
+     * @param roleId 角色ID
+     * @return R<Boolean>
+     */
+    R<Boolean> removeAllMenus(Long roleId);
+
+    /**
+     * 根据角色ID获取菜单ID列表
+     *
+     * @param roleId 角色ID
+     * @return R<List<Long>> 菜单ID列表
+     */
+    R<List<Long>> getMenuIdsByRoleId(Long roleId);
+
+    /**
+     * 为角色添加单个菜单
+     *
+     * @param roleId 角色ID
+     * @param menuId 菜单ID
+     * @return R<Boolean>
+     */
+    R<Boolean> addMenu(Long roleId, Long menuId);
+
+    /**
+     * 移除角色的单个菜单
+     *
+     * @param roleId 角色ID
+     * @param menuId 菜单ID
+     * @return R<Boolean>
+     */
+    R<Boolean> removeMenu(Long roleId, Long menuId);
+
+    /**
+     * 根据菜单ID获取角色ID列表
+     *
+     * @param menuId 菜单ID
+     * @return R<List<Long>> 角色ID列表
+     */
+    R<List<Long>> getRoleIdsByMenuId(Long menuId);
+
+    /**
+     * 根据角色ID查询所有权限(区分已拥有和未拥有的权限)
+     *
+     * @param roleId 角色ID
+     * @return R<List<MenuWithRoleVo>> 菜单列表(包含是否拥有标记)
+     */
+    R<List<shop.alien.entity.store.vo.MenuWithRoleVo>> getMenusWithRoleStatus(Long roleId);
+}
+

+ 352 - 0
alien-store/src/main/java/shop/alien/store/service/impl/LifeSysRoleMenuServiceImpl.java

@@ -0,0 +1,352 @@
+package shop.alien.store.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.LifeSysMenu;
+import shop.alien.entity.store.LifeSysRoleMenu;
+import shop.alien.entity.store.vo.MenuWithRoleVo;
+import shop.alien.mapper.LifeSysRoleMenuMapper;
+import shop.alien.store.service.LifeSysMenuService;
+import shop.alien.store.service.LifeSysRoleMenuService;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 角色菜单关联表 服务实现类
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+@Transactional
+public class LifeSysRoleMenuServiceImpl extends ServiceImpl<LifeSysRoleMenuMapper, LifeSysRoleMenu> implements LifeSysRoleMenuService {
+
+    private final LifeSysRoleMenuMapper lifeSysRoleMenuMapper;
+
+    private final LifeSysMenuService lifeSysMenuService;
+
+    @Override
+    public R<Boolean> assignMenus(Long roleId, List<Long> menuIds) {
+        log.info("LifeSysRoleMenuServiceImpl.assignMenus?roleId={}, menuIds={}", roleId, menuIds);
+
+        if (roleId == null) {
+            log.error("角色ID不能为空");
+            return R.fail("角色ID不能为空");
+        }
+
+        if (menuIds == null || menuIds.isEmpty()) {
+            log.warn("菜单ID列表为空,将移除角色的所有菜单");
+            return removeAllMenus(roleId);
+        }
+
+        try {
+            // 先移除角色的所有菜单
+            LambdaQueryWrapper<LifeSysRoleMenu> deleteWrapper = new LambdaQueryWrapper<>();
+            deleteWrapper.eq(LifeSysRoleMenu::getRoleId, roleId);
+            lifeSysRoleMenuMapper.delete(deleteWrapper);
+
+            // 批量分配新菜单
+            for (Long menuId : menuIds) {
+                if (menuId != null) {
+                        // 创建新的关联关系
+                        LifeSysRoleMenu roleMenu = new LifeSysRoleMenu();
+                        roleMenu.setRoleId(roleId);
+                        roleMenu.setMenuId(menuId);
+                        lifeSysRoleMenuMapper.insert(roleMenu);
+                }
+            }
+
+            log.info("为角色分配菜单成功,角色ID={}, 菜单数量={}", roleId, menuIds.size());
+            return R.success("分配菜单成功");
+        } catch (Exception e) {
+            log.error("为角色分配菜单失败,角色ID={}", roleId, e);
+            return R.fail("分配菜单失败:" + e.getMessage());
+        }
+    }
+
+    @Override
+    public R<Boolean> removeAllMenus(Long roleId) {
+        log.info("LifeSysRoleMenuServiceImpl.removeAllMenus?roleId={}", roleId);
+
+        if (roleId == null) {
+            log.error("角色ID不能为空");
+            return R.fail("角色ID不能为空");
+        }
+
+        try {
+            LambdaQueryWrapper<LifeSysRoleMenu> deleteWrapper = new LambdaQueryWrapper<>();
+            deleteWrapper.eq(LifeSysRoleMenu::getRoleId, roleId);
+            int result = lifeSysRoleMenuMapper.delete(deleteWrapper);
+
+            log.info("移除角色的所有菜单成功,角色ID={}, 删除数量={}", roleId, result);
+            return R.success("移除菜单成功");
+        } catch (Exception e) {
+            log.error("移除角色的所有菜单失败,角色ID={}", roleId, e);
+            return R.fail("移除菜单失败:" + e.getMessage());
+        }
+    }
+
+    @Override
+    public R<List<Long>> getMenuIdsByRoleId(Long roleId) {
+        log.info("LifeSysRoleMenuServiceImpl.getMenuIdsByRoleId?roleId={}", roleId);
+
+        if (roleId == null) {
+            log.error("角色ID不能为空");
+            return R.fail("角色ID不能为空");
+        }
+
+        try {
+            LambdaQueryWrapper<LifeSysRoleMenu> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.eq(LifeSysRoleMenu::getRoleId, roleId);
+            List<LifeSysRoleMenu> roleMenuList = lifeSysRoleMenuMapper.selectList(queryWrapper);
+
+            List<Long> menuIds = roleMenuList.stream()
+                    .map(LifeSysRoleMenu::getMenuId)
+                    .filter(menuId -> menuId != null)
+                    .collect(Collectors.toList());
+
+            log.info("查询角色菜单成功,角色ID={}, 菜单数量={}", roleId, menuIds.size());
+            return R.data(menuIds, "查询成功");
+        } catch (Exception e) {
+            log.error("查询角色菜单失败,角色ID={}", roleId, e);
+            return R.fail("查询菜单失败:" + e.getMessage());
+        }
+    }
+
+    @Override
+    public R<Boolean> addMenu(Long roleId, Long menuId) {
+        log.info("LifeSysRoleMenuServiceImpl.addMenu?roleId={}, menuId={}", roleId, menuId);
+
+        if (roleId == null) {
+            log.error("角色ID不能为空");
+            return R.fail("角色ID不能为空");
+        }
+        if (menuId == null) {
+            log.error("菜单ID不能为空");
+            return R.fail("菜单ID不能为空");
+        }
+
+        try {
+            // 检查是否已存在该关联关系
+            LambdaQueryWrapper<LifeSysRoleMenu> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.eq(LifeSysRoleMenu::getRoleId, roleId)
+                    .eq(LifeSysRoleMenu::getMenuId, menuId);
+            LifeSysRoleMenu existing = lifeSysRoleMenuMapper.selectOne(queryWrapper);
+            if (existing != null) {
+                log.warn("角色ID={}已拥有菜单ID={},无需重复添加", roleId, menuId);
+                return R.success("菜单已存在,无需重复添加");
+            }
+
+            // 创建新的关联关系
+            LifeSysRoleMenu roleMenu = new LifeSysRoleMenu();
+            roleMenu.setRoleId(roleId);
+            roleMenu.setMenuId(menuId);
+            int result = lifeSysRoleMenuMapper.insert(roleMenu);
+
+            if (result > 0) {
+                log.info("为角色添加菜单成功,角色ID={}, 菜单ID={}", roleId, menuId);
+                return R.success("添加菜单成功");
+            } else {
+                return R.fail("添加菜单失败");
+            }
+        } catch (Exception e) {
+            log.error("为角色添加菜单失败,角色ID={}, 菜单ID={}", roleId, menuId, e);
+            return R.fail("添加菜单失败:" + e.getMessage());
+        }
+    }
+
+    @Override
+    public R<Boolean> removeMenu(Long roleId, Long menuId) {
+        log.info("LifeSysRoleMenuServiceImpl.removeMenu?roleId={}, menuId={}", roleId, menuId);
+
+        if (roleId == null) {
+            log.error("角色ID不能为空");
+            return R.fail("角色ID不能为空");
+        }
+        if (menuId == null) {
+            log.error("菜单ID不能为空");
+            return R.fail("菜单ID不能为空");
+        }
+
+        try {
+            LambdaQueryWrapper<LifeSysRoleMenu> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.eq(LifeSysRoleMenu::getRoleId, roleId)
+                    .eq(LifeSysRoleMenu::getMenuId, menuId);
+            int result = lifeSysRoleMenuMapper.delete(queryWrapper);
+
+            if (result > 0) {
+                log.info("移除角色菜单成功,角色ID={}, 菜单ID={}", roleId, menuId);
+                return R.success("移除菜单成功");
+            } else {
+                log.warn("移除角色菜单失败,关联关系不存在,角色ID={}, 菜单ID={}", roleId, menuId);
+                return R.fail("关联关系不存在");
+            }
+        } catch (Exception e) {
+            log.error("移除角色菜单失败,角色ID={}, 菜单ID={}", roleId, menuId, e);
+            return R.fail("移除菜单失败:" + e.getMessage());
+        }
+    }
+
+    @Override
+    public R<List<Long>> getRoleIdsByMenuId(Long menuId) {
+        log.info("LifeSysRoleMenuServiceImpl.getRoleIdsByMenuId?menuId={}", menuId);
+
+        if (menuId == null) {
+            log.error("菜单ID不能为空");
+            return R.fail("菜单ID不能为空");
+        }
+
+        try {
+            LambdaQueryWrapper<LifeSysRoleMenu> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.eq(LifeSysRoleMenu::getMenuId, menuId);
+            List<LifeSysRoleMenu> roleMenuList = lifeSysRoleMenuMapper.selectList(queryWrapper);
+
+            List<Long> roleIds = roleMenuList.stream()
+                    .map(LifeSysRoleMenu::getRoleId)
+                    .filter(roleId -> roleId != null)
+                    .collect(Collectors.toList());
+
+            log.info("查询菜单关联的角色成功,菜单ID={}, 角色数量={}", menuId, roleIds.size());
+            return R.data(roleIds, "查询成功");
+        } catch (Exception e) {
+            log.error("查询菜单关联的角色失败,菜单ID={}", menuId, e);
+            return R.fail("查询角色失败:" + e.getMessage());
+        }
+    }
+
+    @Override
+    public R<List<MenuWithRoleVo>> getMenusWithRoleStatus(Long roleId) {
+        log.info("LifeSysRoleMenuServiceImpl.getMenusWithRoleStatus?roleId={}", roleId);
+
+        if (roleId == null) {
+            log.error("角色ID不能为空");
+            return R.fail("角色ID不能为空");
+        }
+
+        try {
+            // 1. 查询所有未删除的菜单
+            R<List<LifeSysMenu>> menuTreeResult = lifeSysMenuService.getMenuTree();
+            if (menuTreeResult == null || menuTreeResult.getData() == null || menuTreeResult.getData().isEmpty()) {
+                log.warn("未查询到任何菜单");
+                return R.data(new ArrayList<>(), "查询成功");
+            }
+            List<LifeSysMenu> allMenus = menuTreeResult.getData();
+
+            // 2. 查询角色已拥有的菜单ID列表
+            LambdaQueryWrapper<LifeSysRoleMenu> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.eq(LifeSysRoleMenu::getRoleId, roleId);
+            List<LifeSysRoleMenu> roleMenuList = lifeSysRoleMenuMapper.selectList(queryWrapper);
+
+            Set<Long> ownedMenuIds = roleMenuList.stream()
+                    .map(LifeSysRoleMenu::getMenuId)
+                    .filter(menuId -> menuId != null)
+                    .collect(Collectors.toSet());
+
+            log.info("角色ID={}已拥有的菜单数量={}", roleId, ownedMenuIds.size());
+
+            // 3. 将菜单列表转换为MenuWithRoleVo,并标记是否拥有
+            List<MenuWithRoleVo> menuVoList = convertToMenuWithRoleVo(allMenus, ownedMenuIds);
+
+            // 4. 构建树形结构
+            List<MenuWithRoleVo> treeList = buildMenuTreeWithRole(menuVoList);
+
+            log.info("查询角色权限成功,角色ID={}, 总菜单数={}, 已拥有数={}", 
+                    roleId, allMenus.size(), ownedMenuIds.size());
+            return R.data(treeList, "查询成功");
+        } catch (Exception e) {
+            log.error("查询角色权限失败,角色ID={}", roleId, e);
+            return R.fail("查询权限失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 将菜单列表转换为MenuWithRoleVo,并标记是否拥有
+     *
+     * @param menus 菜单列表
+     * @param ownedMenuIds 已拥有的菜单ID集合
+     * @return MenuWithRoleVo列表
+     */
+    private List<MenuWithRoleVo> convertToMenuWithRoleVo(List<LifeSysMenu> menus, Set<Long> ownedMenuIds) {
+        List<MenuWithRoleVo> voList = new ArrayList<>();
+        if (menus == null || menus.isEmpty()) {
+            return voList;
+        }
+
+        for (LifeSysMenu menu : menus) {
+            if (menu == null) {
+                continue;
+            }
+            Boolean hasPermission = ownedMenuIds.contains(menu.getMenuId());
+            MenuWithRoleVo vo = MenuWithRoleVo.fromEntity(menu, hasPermission);
+
+            // 递归处理子菜单
+            if (menu.getChildren() != null && !menu.getChildren().isEmpty()) {
+                List<MenuWithRoleVo> children = convertToMenuWithRoleVo(menu.getChildren(), ownedMenuIds);
+                vo.setChildren(children);
+            }
+
+            voList.add(vo);
+        }
+
+        return voList;
+    }
+
+    /**
+     * 构建菜单树形结构(带角色权限标记)
+     *
+     * @param flatList 扁平列表
+     * @return 树形结构列表
+     */
+    private List<MenuWithRoleVo> buildMenuTreeWithRole(List<MenuWithRoleVo> flatList) {
+        if (flatList == null || flatList.isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        // 创建映射:ID到节点的映射,父ID到子节点列表的映射
+        Map<Long, MenuWithRoleVo> nodeMap = new HashMap<>();
+        Map<Long, List<MenuWithRoleVo>> parentChildMap = new HashMap<>();
+        List<MenuWithRoleVo> result = new ArrayList<>();
+
+        // 第一步:建立映射关系
+        for (MenuWithRoleVo menu : flatList) {
+            Long menuId = menu.getMenuId();
+            Long parentId = menu.getParentId() != null ? menu.getParentId() : 0L;
+
+            // 存入节点映射
+            nodeMap.put(menuId, menu);
+
+            // 如果是根节点(parentId为0或null),直接添加到结果
+            if (parentId == null || parentId == 0) {
+                result.add(menu);
+            } else {
+                // 否则,记录父子关系
+                parentChildMap.computeIfAbsent(parentId, k -> new ArrayList<>()).add(menu);
+            }
+        }
+
+        // 第二步:建立父子关系
+        for (MenuWithRoleVo menu : nodeMap.values()) {
+            Long menuId = menu.getMenuId();
+            if (parentChildMap.containsKey(menuId)) {
+                // 对子节点按排序值排序
+                List<MenuWithRoleVo> children = parentChildMap.get(menuId);
+                children.sort(Comparator.comparing(MenuWithRoleVo::getMenuSort, Comparator.nullsLast(Integer::compareTo)));
+                menu.setChildren(children);
+            }
+        }
+
+        // 对根节点按排序值排序
+        result.sort(Comparator.comparing(MenuWithRoleVo::getMenuSort, Comparator.nullsLast(Integer::compareTo)));
+
+        return result;
+    }
+}
+