系列文章目录
《SpringBoot整合SpringSecurity实现权限控制(一):实现原理》
《SpringBoot整合SpringSecurity实现权限控制(二):权限数据基本模型设计》
《SpringBoot整合SpringSecurity实现权限控制(三):前端动态装载路由与菜单》
《SpringBoot整合SpringSecurity实现权限控制(四):角色管理》
《SpringBoot整合SpringSecurity实现权限控制(五):用户管理》
本文目录
- 一、前言
- 二、需求分析
- 三、后端实现
- 3.1 创建菜单实体表
- 3.2 添加操作菜单表的Mapper接口
- 3.3 实现菜单的增删改查服务
- 3.4 编写Controller层
- 四、前端实现
- 4.1 添加菜单api访问接口
- 4.2 编写前端页面
- 4.3 菜单渲染
- 五、效果演示
- 六、源码
二、需求分析
- 后台管理系统可以通过菜单管理来实现系统的功能模块管理。通过清晰的树形菜单结构展现各种系统功能,无疑会大大提升系统的使用效率。
- 系统功能模块需要按各个分类,形成菜单结构。比如说系统管理分类目录下,存在用户管理、角色管理、菜单管理等功能;系统设置分类目录下,存在商品设置、仓库设置、储位设置等功能。
- 每个菜单都需要包含以下信息:菜单id,菜单名称,父级菜单id,路由地址(vue-router),组件页面,图标,排序顺序等
3、菜单管理需要实现基本的增删改查
- 根据菜单的基本信息,创建菜单实体类。
@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接口已经包含了基本的增删改查操作。
@Mapper public interface SysMenuMapper extends baseMapper3.3 实现菜单的增删改查服务{ @Select("select * from sys_menu where p_id=#{pid} ") List selectChilds(Long pid); }
- 服务接口定义:
public interface SysMenuService {
SysMenu create (SysMenu menu);
Boolean delete (Set ids);
SysMenu update (SysMenu menu);
SysMenu findById(Long id);
SysMenu findByName(String name);
SysMenu findByMenuPath(String path,Long pId);
List list(SysMenuQueryDto sysMenuQueryDto);
}
- 服务实现类:
@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 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 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("更新菜单失败!!");
}
private void childLoop(Long id, List childMenus) {
List 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().lambda().eq(SysMenu::getName, name));
}
@Override
public SysMenu findByMenuPath(String path, Long pId) {
return sysMenuMapper.selectOne(new QueryWrapper().lambda().eq(SysMenu::getPath, path)
.and(wrapper -> wrapper.eq(SysMenu::getPid, pId)));
}
@Override
public List list(SysMenuQueryDto sysMenuQueryDto) {
QueryWrapper 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访问接口
@Slf4j
@RestController
@AllArgsConstructor
@RequestMapping("/api/menu")
@Api(tags = "菜单资源接口")
public class SysMenuController {
private final SysMenuService sysMenuService;
@ApiOperation("根据件查询菜单资源")
@PostMapping("/list")
public ResponseEntity
四、前端实现
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 编写前端页面
- 构成查询条件,增删改查按钮与菜单树形表的布局
- 树形菜单可以展开或折叠
- 点击增加或编辑菜单按钮,填写相应菜单信息后,进行保存。
- 专门编写了一个图标选择组件,该组件可以选择element-ui自带的图标
// SelectIcon.js
-
上级菜单选择时,引用了TreeSelect组件,使用可参考https://www.vue-treeselect.cn/
-
前端完整代码
– /src/menu/index.vue
4.3 菜单渲染搜索 增加 删除{{ selections.length }} {{ scope.row.sort }} {{ parseTime(scope.row.createTime) }} 编辑菜单
-
通过element-ui的 NavMenu可以实现菜单的前端渲染,本期请大家先了解一下该组件的基本情况,菜单动态渲染将在下期文章中详细说明。
- 五、效果演示
- 前端
https://gitee.com/zhuhuix/startup-frontend
https://github.com/zhuhuix/startup-frontend - 后端
https://gitee.com/zhuhuix/startup-backend
https://github.com/zhuhuix/startup-backend



