SpringBoot整合SpringSecurity实现权限控制:菜单管理

Posted 智慧zhuhuix

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot整合SpringSecurity实现权限控制:菜单管理相关的知识,希望对你有一定的参考价值。

系列文章目录
《SpringBoot整合SpringSecurity实现权限控制(一):实现原理》
《SpringBoot整合SpringSecurity实现权限控制(二):权限数据基本模型设计》
《SpringBoot整合SpringSecurity实现权限控制(三):前端动态装载路由与菜单》
《SpringBoot整合SpringSecurity实现权限控制(四):角色管理》
《SpringBoot整合SpringSecurity实现权限控制(五):用户管理》


一、前言

  • 后台管理系统可以通过菜单管理来实现系统的功能模块管理。通过清晰的树形菜单结构展现各种系统功能,无疑会大大提升系统的使用效率。

二、需求分析

  1. 系统功能模块需要按各个分类,形成菜单结构。比如说系统管理分类目录下,存在用户管理、角色管理、菜单管理等功能;系统设置分类目录下,存在商品设置、仓库设置、储位设置等功能。
  2. 每个菜单都需要包含以下信息:菜单id,菜单名称,父级菜单id,路由地址(vue-router),组件页面,图标,排序顺序等

    3、菜单管理需要实现基本的增删改查

三、后端实现

3.1 创建菜单实体表

  • 根据菜单的基本信息,创建菜单实体类。
/**
 * 菜单表
 *
 * @author zhuhuix
 * @date 2021-10-06
 */
@ApiModel(value = "菜单表")
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@TableName("sys_menu")
public class SysMenu {

    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    private String name;

    private String path;

    private String component;

    private String type;

    @TableField(value = "p_id", updateStrategy = FieldStrategy.IGNORED,jdbcType = JdbcType.BIGINT)
    private Long pid;

    private String icon;

    private Integer sort;

    private Boolean hidden;

    private Boolean cache;

    private String redirect;

    private String url;

    private Integer level;

    @JsonIgnore
    @Builder.Default
    @TableLogic
    private Boolean enabled = true;

    private Timestamp createTime;

    @Builder.Default
    private Timestamp updateTime = Timestamp.valueOf(LocalDateTime.now());

    public String getLabel() {
        return name;
    }
}

3.2 添加操作菜单表的Mapper接口

  • 通过继承mybatis-plus的BaseMapper接口创建操作菜单表的DAO接口,该BaseMapper接口已经包含了基本的增删改查操作。
/**
 * 菜单DAO接口
 *
 * @author zhuhuix
 * @date 2021-10-06
 */
@Mapper
public interface SysMenuMapper extends BaseMapper<SysMenu> {

    /**
     * 根据父级菜单id查出下级菜单
     * @param pid 父级菜单id
     * @return 下级菜单列表
     */
    @Select("select * from sys_menu where p_id=#{pid} ")
    List<SysMenu> selectChilds(Long pid);
}

3.3 实现菜单的增删改查服务

  • 服务接口定义:
/**
 * 菜单资源服务接口
 *
 * @author zhuhuix
 * @date 2021-10-06
 */
public interface SysMenuService {

    /**
     * 创建菜单
     *
     * @param menu 待新增的菜单
     * @return  新增成功的菜单
     */
    SysMenu create (SysMenu menu);

    /**
     * 删除菜单
     *
     * @param ids 菜单id列表
     * @return 是否删除成功
     */
    Boolean delete (Set<Long> ids);

    /**
     * 更新菜单
     *
     * @param menu 待更新的菜单
     * @return  更新成功的菜单
     */
    SysMenu update (SysMenu menu);

    /**
     * 根据id查找菜单
     *
     * @param id 菜单id
     * @return 查找到的菜单
     */
    SysMenu findById(Long id);

    /**
     * 根据菜单名称查找菜单
     *
     * @param name 菜单名称
     * @return 查找到的菜单
     */
    SysMenu findByName(String name);

    /**
     * 根据菜单完整路由获取菜单信息
     *
     * @param path 路由
     * @param pId  父菜单
     * @return 完整路由
     */
    SysMenu findByMenuPath(String path,Long pId);


    /**
     * 根据查询条件查找菜单信息
     *
     * @param sysMenuQueryDto 查询条件
     * @return 菜单列表
     */
    List<SysMenu> list(SysMenuQueryDto sysMenuQueryDto);
}
  • 服务实现类:
/**
 * 菜单资源服务实现类
 *
 * @author zhuhuix
 * @date 2021-10-06
 */
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
public class SysMenuServiceImpl implements SysMenuService {

    private final SysMenuMapper sysMenuMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public SysMenu create(SysMenu menu) {
        if (findByName(menu.getName()) != null) {
            throw new RuntimeException("该菜单名称已存在,不得重复添加!!");
        }
        if (findByMenuPath(menu.getPath(), menu.getPid()) != null) {
            throw new RuntimeException("该菜单路由已存在,不得重复添加!!");
        }
        menu.setCreateTime(Timestamp.valueOf(LocalDateTime.now()));
        if (sysMenuMapper.insert(menu) > 0) {
            return menu;
        }

        throw new RuntimeException("增加菜单失败!!");
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean delete(Set<Long> ids) {
        if (sysMenuMapper.deleteBatchIds(ids) > 0) {
            return true;
        }
        throw new RuntimeException("删除菜单失败!!");
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public SysMenu update(SysMenu menu) {
        SysMenu sysMenu = findByName(menu.getName());
        if (sysMenu != null && !sysMenu.getId().equals(menu.getId())) {
            throw new RuntimeException("该菜单名称已存在,不得重复添加!!");
        }
        sysMenu = findByMenuPath(menu.getPath(), menu.getId());
        if (sysMenu != null && !sysMenu.getId().equals(menu.getId())) {
            throw new RuntimeException("该菜单路由已存在,不得重复添加!!");
        }

        // 判断修改菜单的上级菜单不能是该修改菜单原有的子菜单
        if (menu.getPid() != null) {
            List<SysMenu> childMenus = new ArrayList<>();
            childLoop(menu.getId(), childMenus);
            if (childMenus.stream().filter(m -> m.getId().equals(menu.getPid())).count() > 0) {
                throw new RuntimeException("上级菜单不能设置为下级子菜单,防止引起嵌套循环错误!!");
            }
        }

        if (sysMenuMapper.updateById(menu) > 0) {
            return menu;
        }
        throw new RuntimeException("更新菜单失败!!");

    }

    /**
     * 返回菜单下所有的子菜单
     *
     * @param id 菜单id
     */
    private void childLoop(Long id, List<SysMenu> childMenus) {
        List<SysMenu> sysMenus = sysMenuMapper.selectChilds(id);
        if (sysMenus == null || sysMenus.size() ==0) {
            return;
        }
        for (SysMenu m : sysMenus) {
            childMenus.add(m);
            childLoop(m.getId(), childMenus);
        }

    }

    @Override
    public SysMenu findById(Long id) {
        return sysMenuMapper.selectById(id);
    }

    @Override
    public SysMenu findByName(String name) {
        return sysMenuMapper.selectOne(new QueryWrapper<SysMenu>().lambda().eq(SysMenu::getName, name));
    }

    @Override
    public SysMenu findByMenuPath(String path, Long pId) {
        return sysMenuMapper.selectOne(new QueryWrapper<SysMenu>().lambda().eq(SysMenu::getPath, path)
                .and(wrapper -> wrapper.eq(SysMenu::getPid, pId)));
    }

    @Override
    public List<SysMenu> list(SysMenuQueryDto sysMenuQueryDto) {
        QueryWrapper<SysMenu> queryWrapper = new QueryWrapper<>();
        if (!StringUtils.isEmpty(sysMenuQueryDto.getName())) {
            queryWrapper.lambda().like(SysMenu::getName, sysMenuQueryDto.getName());
        }

        if (!StringUtils.isEmpty(sysMenuQueryDto.getCreateTimeStart())
                && !StringUtils.isEmpty(sysMenuQueryDto.getCreateTimeEnd())) {
            queryWrapper.lambda().between(SysMenu::getCreateTime,
                    new Timestamp(sysMenuQueryDto.getCreateTimeStart()),
                    new Timestamp(sysMenuQueryDto.getCreateTimeEnd()));
        }

        return sysMenuMapper.selectList(queryWrapper);
    }
}

3.4 编写Controller层

  • 形成以下API访问接口
/**
 * api菜单资源
 *
 * @author zhuhuix
 * @date 2021-10-16
 */
@Slf4j
@RestController
@AllArgsConstructor
@RequestMapping("/api/menu")
@Api(tags = "菜单资源接口")
public class SysMenuController {

    private final SysMenuService sysMenuService;

    @ApiOperation("根据件查询菜单资源")
    @PostMapping("/list")
    public ResponseEntity<Object> getMenuList(@RequestBody SysMenuQueryDto sysMenuQueryDto) {
        return ResponseEntity.ok(sysMenuService.list(sysMenuQueryDto));
    }

    @ApiOperation("根据id获取单个菜单资源")
    @GetMapping("{id}")
    public ResponseEntity<Object> getMenuById(@PathVariable Long id) {
        return ResponseEntity.ok(sysMenuService.findById(id));
    }

    @ApiOperation("保存菜单资源")
    @PostMapping
    public ResponseEntity<Object> saveMenu(@RequestBody SysMenu sysMenu) {
        if (sysMenu.getId() != null) {
            return ResponseEntity.ok(sysMenuService.update(sysMenu));
        } else {
            return ResponseEntity.ok(sysMenuService.create(sysMenu));
        }
    }

    @ApiOperation("删除菜单资源")
    @DeleteMapping
    public ResponseEntity<Object> deleteMenu(@RequestBody Set<Long> ids) {
        return ResponseEntity.ok(sysMenuService.delete(ids));
    }
}

四、前端实现

4.1 添加菜单api访问接口

  • 根据后端的API在前端添加相应的访问接口
// menu.js 
import request from '@/utils/request'
// 根据条件查询
export function getMenuList(params) {
  return request({
    url: '/api/menu/list',
    method: 'post',
    data: JSON.stringify(params)
  })
}
// 根据菜单id获取菜单信息
export function getMenuById(id) {
  return request({
    url: '/api/menu/' + id,
    method: 'get'
  })
}
// 保存菜单信息
export function saveMenu(data) {
  return request({
    url: '/api/menu',
    method: 'post',
    data
  })
}
// 删除菜单
export function deleteMenu(ids) {
  return request({
    url: '/api/menu',
    method: 'delete',
    data: ids
  })
}

4.2 编写前端页面

  1. 构成查询条件增删改查按钮菜单树形表的布局
  2. 树形菜单可以展开或折叠
  3. 点击增加或编辑菜单按钮,填写相应菜单信息后,进行保存。
  • 专门编写了一个图标选择组件,该组件可以选择element-ui自带的图标
// SelectIcon.js
<template>
  <div class="ui-fas">
    <el-input v-model="name" class="inputIcon" suffix-icon="el-icon-search" placeholder="请输入图标名称" @input.native="filterIcons" />
    <ul class="fas-icon-list">
      <li v-for="(item, index) in icons" :key="index" @click="selectedIcon(item)">
        <i class="fas" :class="[item]" />
      </li>
    </ul>
  </div>
</template>
<script>
export default {
  name: 'CompIcons',
  data() {
    return {
      name: '',
      icons: [],
      iconList: ['el-icon-platform-eleme', 'el-icon-delete-solid', 'el-icon-eleme', 'el-icon-c-scale-to-original', 'el-icon-sort-up', 'el-icon-sort-down', 'el-icon-upload', 'el-icon-goods', 'el-icon-video-pause', 'el-icon-video-play', 'el-icon-s-cooperation', 'el-icon-s-order', 'el-icon-s-platform', 'el-icon-s-unfold', 'el-icon-s-operation', 'el-icon-s-promotion', 'el-icon-s-home', 'el-icon-s-release', 'el-icon-s-ticket', 'el-icon-s-management', 'el-icon-s-open', 'el-icon-s-shop', 'el-icon-s-help', 'el-icon-s-goods', 'el-icon-s-marketing', 'el-icon-s-flag', 

以上是关于SpringBoot整合SpringSecurity实现权限控制:菜单管理的主要内容,如果未能解决你的问题,请参考以下文章

springboot整合系列

SpringBoot系列八:SpringBoot整合消息服务(SpringBoot 整合 ActiveMQSpringBoot 整合 RabbitMQSpringBoot 整合 Kafka)

[SpringBoot系列]SpringBoot如何整合SSMP

springboot怎么整合activiti

SpringBoot完成SSM整合之SpringBoot整合junit

springboot整合jedis