后台管理系统-菜单管理模块

1.菜单管理页面设计

1.1业务设计

菜单管理又称为资源管理,是系统资源对外的表现形式。本模块主要是实现对菜单进行添加、修改、查询、删除等操作。

CREATE TABLE `sys_menus` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(50) DEFAULT NULL COMMENT '资源名称',
  `url` varchar(200) DEFAULT NULL COMMENT '资源URL',
  `type` int(11) DEFAULT NULL COMMENT '类型     1:菜单   2:按钮',
  `sort` int(11) DEFAULT NULL COMMENT '排序',
  `note` varchar(100) DEFAULT NULL COMMENT '备注',
  `parentId` int(11) DEFAULT NULL COMMENT '父菜单ID,一级菜单为0',
  `permission` varchar(500) DEFAULT NULL COMMENT '授权(如:sys:user:create)',
  `createdTime` datetime DEFAULT NULL COMMENT '创建时间',
  `modifiedTime` datetime DEFAULT NULL COMMENT '修改时间',
  `createdUser` varchar(20) DEFAULT NULL COMMENT '创建用户',
  `modifiedUser` varchar(20) DEFAULT NULL COMMENT '修改用户',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='资源管理';

菜单表与角色表是多对多的关系,在表设计时,多对多关系通常由中间表(关系表)进行维护
image
基于角色菜单表的设计,其角色和菜单对应的关系数据要存储到关系表中,其具体存储形式
image
菜单与角色的关系表脚本设计如下:

CREATE TABLE `sys_role_menus` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `role_id` int(11) DEFAULT NULL COMMENT '角色ID',
  `menu_id` int(11) DEFAULT NULL COMMENT 'ID',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='角色与菜单对应关系';

1.2 原型设计

当在主页左侧菜单栏,点击菜单管理时,在主页内容呈现区,呈现菜单列表页面
image
当在菜单列表页面点击添加按钮时,异步加载菜单编辑页面,并在列表内容呈现区,呈现菜单编辑页面,如图-4所示。
image
在菜单编辑页面选择上级菜单时,异步加载菜单信息,并以树结构的形式呈现上级菜单
image

1.3 API设计

菜单管理业务后台API分层架构及调用关系如图
image

2.菜单管理页面代码实现

2.1 菜单列表数据实现

2.1.1 数据加载时序

页面数据展示
菜单列表页面加载完成,启动菜单数据异步加载操作,本次菜单列表页面要呈现菜单以及上级菜单信息,其数据查询时,数据的封装及传递过程
image
页面菜单数据删除展示
基于用户在列表页面上选择的的菜单记录ID,执行删除操作,本次删除业务实现中,首先要基于id判断当前菜单是否有子菜单,假如有子菜单则不允许删除,没有则先删除菜单角色关系数据,然后再删除菜单自身信息。
image
上级菜单页面展示
在菜单编辑页面上,点击上级菜单时,其数据加载时序分析
image
菜单数据添加实现
用户在菜单编辑页面输入数据,然后异步提交到服务端,其简易数据传递基本架构
image
用户在菜单添加页面中填写好菜单数据,然后点击保存按钮,将用户填写的数据添加到数据库
image
菜单数据更新展示
当点击编辑页面更新按钮时
image

2.1.2 代码实现

第一步:创建SysMenuDao层接口

package com.cy.pj.sys.dao;
import com.cy.pj.common.pojo.Node;
import com.cy.pj.sys.pojo.SysMenu;
import com.cy.pj.sys.pojo.SysUserMenu;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
import java.util.Map;
@Mapper
public interface SysMenuDao {
    List<SysUserMenu> findMenusByIds(List<Integer> menuIds);
    //基于菜单ID获取授权表示
    List<String> findPermissions(List<Integer> menuIds);
    int updateObject(SysMenu entity);
    @Insert(" insert into sys_menus " +
            "(name,url,type,sort,note,parentId,permission,createdTime,modifiedTime,createdUser,modifiedUser) " +
            "values " +
            "(#{name},#{url},#{type},#{sort},#{note},#{parentId},#{permission},now(),now(),#{createdUser},#{modifiedUser})")
    int insertObject(SysMenu entity);
    @Select("select id,name,parentId from sys_menus")
    List<Node> findZtreeMenuNodes();
    
    @Select("select count(*) from sys_menus where parentId=#{id}")
    int getChildCount(Integer id);
    @Delete("delete from sys_menus where id=#{id}")
    int deleteObject(Integer id);
    //查询所有菜单信息
    List<Map<String,Object>> findObjects();
}

第二步:创建SysMenuMapper.xml映射文件

<?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.cy.pj.sys.dao.SysMenuDao">
    <resultMap type="com.cy.pj.sys.pojo.SysUserMenu" id="sysUserMenu">
        <!-- 一级菜单映射 -->
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="url" column="url"/>
        <!-- 二级菜单映射 -->
        <collection property="childs" ofType="com.cy.pj.sys.pojo.SysUserMenu">
            <id property="id" column="cid"/>
            <result property="name" column="cname"/>
            <result property="url" column="curl"/>
        </collection>
    </resultMap>
    <select id="findMenusByIds"
        resultMap="sysUserMenu">
        select p.id,p.name,p.url,c.id cid,c.name cname,c.url curl
        from sys_menus p join sys_menus c
        on c.parentId=p.id
        <where>
            <foreach collection="menuIds"
                 open="("
                 close=")"
                 separator="or"
                 item="menuId">
                c.id=#{menuId}
            </foreach>
            and p.parentId is null
        </where>
    </select>
    <select id="findPermissions"
        resultType="string">
        select permission <!-- sys:user:update -->
        from sys_menus
        where id in
        <foreach collection="menuIds"
             open="("
             close=")"
             separator=","
             item="item">
            #{item}
        </foreach>
    </select>
    <!--查询所有菜单以及菜单对应的上级菜单,当没有上级菜单时,当前菜单的上级菜单显示为null-->
    <select id="findObjects" resultType="map">
    /*方法1(左外关联查询)*/
        /*select c.*,p.name parentName
        from sys_menus c left join sys_menus p
        on c.parentId=p.id*/
    /*方法2(嵌套查询)*/
        select c.*,(select p.name
                    from sys_menus p
 where c.parentId=p.id) parentName
 from sys_menus c
 </select>
    <update id="updateObject"
        parameterType="com.cy.pj.sys.pojo.SysMenu">
         update sys_menus
         set
           name=#{name},
           type=#{type},
           sort=#{sort},
           url=#{url},
           parentId=#{parentId},
           permission=#{permission},
           modifiedUser=#{modifiedUser},
           modifiedTime=now()
        where id=#{id}
    </update>
</mapper>

第三步:创建SysMenuService接口

在菜单查询中,业务层对象主要是借助数据层对象完成菜单数据的查询。后续还可以基于AOP对数据进行缓存,记录访问日志等。

package com.cy.pj.sys.servive;
import com.cy.pj.common.pojo.Node;
import com.cy.pj.sys.pojo.SysMenu;
import com.cy.pj.sys.pojo.SysUserMenu;
import java.util.List;
import java.util.Map;
public interface SysMenuService {
    List<SysUserMenu> findUserMenusByUserId(Integer id);
    int updateObject(SysMenu entity);
    int saveObject(SysMenu entity);
    List<Node> findZtreeMenuNodes();
    int deleteObject(Integer id);
    List<Map<String,Object>> findObjects();
}

第四步:创建SysMenuServiceImpl实现类

package com.cy.pj.sys.servive.impl;
import com.cy.pj.common.exception.ServiceException;
import com.cy.pj.common.pojo.Node;
import com.cy.pj.sys.dao.SysUserRoleDao;
import com.cy.pj.sys.pojo.SysMenu;
import com.cy.pj.sys.dao.SysMenuDao;
import com.cy.pj.sys.dao.SysRoleMenuDao;
import com.cy.pj.sys.pojo.SysUserMenu;
import com.cy.pj.sys.servive.SysMenuService;
import io.micrometer.core.instrument.util.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
@Service
public class SysMenuServiceImpl implements SysMenuService {
    @Autowired
    private SysMenuDao sysMenuDao;
    @Autowired
    private SysRoleMenuDao sysRoleMenuDao;
    @Autowired
    private SysUserRoleDao sysUserRoleDao;
    @Override
    public List<SysUserMenu> findUserMenusByUserId(Integer id) {
        //1.对用户id进行判断
        //2.基于用户id查找用户对应的角色id
        List<Integer> roleIds=
                sysUserRoleDao.findRoleIdsByUserId(id);
        //3.基于角色id获取角色对应的菜单信息,并进行封装.
        List<Integer> menuIds=
                sysRoleMenuDao.findMenuIdsByRoleIds(roleIds);
        //4.基于菜单id获取用户对应的菜单信息并返回
        return sysMenuDao.findMenusByIds(menuIds);
    }
    @Override
    public int updateObject(SysMenu entity) {
        //1.合法验证
        if(entity==null)
            throw new ServiceException("保存对象不能为空");
        if(StringUtils.isEmpty(entity.getName()))
            throw new ServiceException("菜单名不能为空");
        //2.更新数据
        int rows=sysMenuDao.updateObject(entity);
        if(rows==0)
            throw new ServiceException("记录可能已经不存在");
        //3.返回数据
        return rows;
    }
    @Override
    public int saveObject(SysMenu entity) {
        //1.合法验证
        if(entity==null)
            throw new IllegalArgumentException("保存对象不能为空");
        if(StringUtils.isEmpty(entity.getName()))
            throw new IllegalArgumentException("菜单名不能为空");
        //2.保存数据
        int rows=sysMenuDao.insertObject(entity);
        //3.返回数据
        return rows;
    }
    @Override
    public List<Node> findZtreeMenuNodes() {
        return sysMenuDao.findZtreeMenuNodes();
    }
    @Override
    public int deleteObject(Integer id) {
        //1,参数校验
        if(id==null||id<1)
            throw new IllegalArgumentException("id值无效");
        //2,判定菜单是否有子菜单,有则不允许删除
        int childCount=sysMenuDao.getChildCount(id);
        if(childCount>0)
            throw new ServiceException("请先删除子菜单");
        //3,删除关系数据
        sysRoleMenuDao.deleteObjectsByMenuId(id);
        int rows=sysMenuDao.deleteObject(id);
        //4,删除自身信息
        if(rows==0)
            throw new ServiceException("记录可能已经不存在");
        return rows;
    }
    @Override
    public List<Map<String, Object>> findObjects() {
        return sysMenuDao.findObjects();
    }
}

第五步:创建SysMenuController控制层

控制层对象主要负责请求和响应数据的处理,例如,本模块通过业务层对象执行业务逻辑,再通过VO对象封装响应结果(主要对业务层数据添加状态信息),最后将响应结果转换为JSON格式的字符串响应到客户端。

package com.cy.pj.sys.controller;
import com.cy.pj.common.pojo.JsonResult;
import com.cy.pj.sys.pojo.SysMenu;
import com.cy.pj.sys.servive.SysMenuService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class SysMenuController {
    @Autowired
    private SysMenuService sysMenuService;
    @RequestMapping("/menu/doUpdateObject")
    public JsonResult doUpdateObject(SysMenu entity){
        sysMenuService.updateObject(entity);
        return new JsonResult("update ok");
    }
    @RequestMapping("/menu/doSaveObject")
    public JsonResult doSaveObject(SysMenu entity){
        sysMenuService.saveObject(entity);
        return new JsonResult("save ok");
    }
    @RequestMapping("/menu/doFindZtreeMenuNodes")
    public JsonResult doFindZtreeMenuNodes(){
        return new JsonResult(
                sysMenuService.findZtreeMenuNodes());
    }
    @RequestMapping("/menu/doDeleteObject")
    public JsonResult doDeleteObject(Integer id){
        sysMenuService.deleteObject(id);
        return new JsonResult("delete ok");
    }
    @GetMapping("menu/doFindObjects")
    public JsonResult doFindObjects(){
        return new JsonResult(sysMenuService.findObjects());
    }
}

第六步:创建SysRoleMenuDao接口

package com.cy.pj.sys.dao;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface SysRoleMenuDao {
    List<Integer> findMenuIdsByRoleIds(
            @Param("roleIds")List<Integer> roleIds);
    int insertObjects(
            @Param("roleId")Integer roleId,
            @Param("menuIds")Integer[] menuIds);
    @Delete("delete from sys_role_menus where role_id=#{roleId}")
    int deleteObjectsByRoleId(Integer roleId);
    //基于菜单id执行关系
 @Delete("delete from sys_role_menus where menu_id=#{menuId}")
    int deleteObjectsByMenuId(Integer menuId);
}

第七步:创建SysRoleMenuMapper.xml文件

<?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.cy.pj.sys.dao.SysRoleMenuDao">
    <select id="findMenuIdsByRoleIds"
 resultType="int">
        select menu_id
        from sys_role_menus
        where role_id in
        <foreach collection="roleIds"
 open="("
 close=")"
 separator=","
 item="item">
            #{item}
        </foreach>
    </select>
    <select id="findMenuIdsByRoleId"
 resultType="int">
            select menu_id
            from sys_role_menus
            where role_id=#{id}
    </select>
    <insert id="insertObjects">
        insert into sys_role_menus
        (role_id,menu_id)
        values
        <foreach collection="menuIds"
 separator=","
 item="menuId">
            (#{roleId},#{menuId})
        </foreach>
    </insert>
</mapper>

第八步:创建Node 类
定义值对象封装查询到的上级菜单id,name,parentId信息。

package com.cy.pj.common.pojo;
import lombok.Data;
import java.io.Serializable;
@Data
public class Node implements Serializable {
    private static final long serialVersionUID = -7022202313802285223L;
    private Integer id;
    private String name;
    private Integer parentId;
}

第九步:创建SysMenu 类
定义持久化对象,封装客户端请求数据,并将数据传递到数据层进行持久化。

package com.cy.pj.sys.pojo;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
@Data
public class SysMenu implements Serializable {
    private static final long serialVersionUID = -1086340530112865421L;
    private Integer id;
    /**菜单名称*/
 private String name;
    /**菜单url: log/doFindPageObjects*/
 private String url;
    /**菜单类型(两种:按钮,普通菜单)*/
 private Integer type=1;
    /**排序(序号)*/
 private Integer sort;
    /**备注*/
 private String note;
    /**上级菜单id*/
 private Integer parentId;
    /**菜单对应的权限标识(sys:log:delete)*/
 private String permission;
    /**创建用户*/
 private String createdUser;
    /**修改用户*/
 private String modifiedUser;
    private Date createdTime;
    private Date modifiedTime;
}

来源:https://segmentfault.com/a/1190000038458944

posted @ 2022-07-18 10:10  西门长海  阅读(2155)  评论(0编辑  收藏  举报