前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >自定义权限功能之角色增删改查及分配路由资源的实现

自定义权限功能之角色增删改查及分配路由资源的实现

作者头像
用户3587585
发布2021-09-29 15:31:39
1.5K0
发布2021-09-29 15:31:39
举报
文章被收录于专栏:阿福谈Web编程阿福谈Web编程

本文目录

前言
1 效果预览
2 后端接口开发
  • 「2.1 查询全量角色接口」
  • 「2.2 新增角色接口」
  • 「2.3 修改角色接口」
  • 「2.4 删除角色接口」
  • 「2.5 角色授权接口」
3 前端Vue开发
  • 「3.1 统一管理后台新增接口方法」
  • 「3.2 页面模板绘制」
  • 「3.3 页面js逻辑」
  • 「3.4 页面样式」

前言

笔者采用前后端分离项目开发自定义权限功能模块有一段时间了,今天这部分的收尾篇了。在这个系列的文章里笔者后端采用一个开源的springboot项目blog-server,前端采用基于vueelement-ui技术栈的开源项目vue-element-admin先后实现了「根据当前登录用户角色动态加载左侧菜单、用户分页查询和给用户授予角色」等功能的实现。本文则是这个权限功能的扫尾部分,笔者带领大家来继续实现「角色的增删改和给角色分配路由资源」这部分功能,以后有时间的化还会继续补角色-按钮级别的权限控制。为了利于笔者和我的读者朋友往高级开发和架构师方向发展,后面发文的重点将放在redis、rabbitmq、rocketmq和springcloud等分布式技术栈的学习和实践上。

1 效果预览

图 1 角色列表页

图 2 编辑角色界面

图 3 角色分配路由资源界面

这个效果如是笔者最终实现的效果图,鉴于前端水平有限,没有对界面样式进行特别的美化调整,还请读者们将就着看,我们实现功能即可,界面的美化后续可以继续通过调样式实现。

2 后端接口开发

2.1 查询全量角色接口

从效果图中,我们可以整理出需要开发的接口主要有「查询全量角色、新增角色、修改角色、删除角色、给角色添加路由资源」等5个接口。由于新建角色时增加了「角色描述」一列,所以笔者给roles表添加了一列description, sql如下:

代码语言:javascript
复制
alter table roles add (description varchar(50));

「Dao层编码实现」

RolesMapper.java

代码语言:javascript
复制
List<Role> getAllRole();

RolesMapper.xml

代码语言:javascript
复制
<select id="getAllRole" resultType="org.sang.pojo.Role">
        select id, role_code as roleCode,role_name as roleName,description
        from roles where 1=1
</select>

「Servcie层代码实现」

RoleService.java

代码语言:javascript
复制
@Autowired
private RolesMapper rolesMapper;

public List<Role> getAllRoles(){
    List<Role> roles = rolesMapper.getAllRole();
    return roles;
}

「Controller层代码实现」

RoleController.java

代码语言:javascript
复制
@Autowired
private RoleService roleService; 

@GetMapping("/allRoles")
    @ApiOperation(value = "getAllRoles", notes = "获取所有角色列表", produces = "application/json", consumes = "application/json", response = RespBean.class)
    public RespBean<List<Role>> getAllRoles() {
        List<Role> roles = roleService.getAllRoles();
        RespBean<List<Role>> respBean = new RespBean<>(200, "success");
        respBean.setData(roles);
        return respBean;
    }

2.2 新增角色接口

「Dao层编码实现」

RolesMapper.java

代码语言:javascript
复制
int addRole(Role role);

RoleMapper.xml

代码语言:javascript
复制
<insert id="addRole" keyProperty="id" useGeneratedKeys="true"      parameterType="org.sang.pojo.Role">
       insert into roles(role_code,role_name,description)
       values(#{roleCode,jdbcType=VARCHAR},
              #{roleName,jdbcType=VARCHAR},
              #{description,jdbcType=VARCHAR})
</insert>

「Service层编码实现」

RoleService.java

代码语言:javascript
复制
@Autowired
private RolesMapper rolesMapper;

public int updateRole(Role role) {
     int count = rolesMapper.updateRole(role);
     return count;
}

「Controller层编码实现」

RoleController.java

代码语言:javascript
复制
@Autowired
private RoleService roleService;

 privatestaticfinal Logger logger = LoggerFactory.getLogger(RoleController.class);

    @PostMapping(path = "/addRole")
    @ApiOperation(value = "addRole", notes = "添加角色", produces = "application/json",
            consumes = "application/json", response = RespBean.class)
    @ApiImplicitParam(name="role", value = "角色对象", dataTypeClass = Role.class, paramType="body", required = true)
public RespBean<Integer> addRole(@RequestBody Role role) {
      logger.info("roleCode={},roleName={}",role.getRoleCode(),role.getRoleName());
      int addCount = roleService.addRole(role);
      RespBean<Integer> respBean = new RespBean<>(200, "success");
      respBean.setData(addCount);
      return respBean;
 }

2.3 修改角色接口

「Dao层代码实现」

RolesMapper.java

代码语言:javascript
复制
int updateRole(Role role);
<update id="updateRole" parameterType="org.sang.pojo.Role">
        update roles set role_code = #{roleCode,jdbcType=VARCHAR},
                         role_name = #{roleName,jdbcType=VARCHAR},
                         description = #{description,jdbcType=VARCHAR}
        where id = #{id, jdbcType=INTEGER}
</update>

「Service层代码实现」

RoleService.java

代码语言:javascript
复制
public int updateRole(Role role) {
     int count = rolesMapper.updateRole(role);
     return count;
}

「Controller层代码实现」

RoleController

代码语言:javascript
复制
@Autowired
private RoleService roleService;

 private static final Logger logger = LoggerFactory.getLogger(RoleController.class);

    @PostMapping(path = "/addRole")
    @ApiOperation(value = "addRole", notes = "添加角色", produces = "application/json",
            consumes = "application/json", response = RespBean.class)
    @ApiImplicitParam(name="role", value = "角色对象", dataTypeClass = Role.class, paramType="body", required = true)
public RespBean<Integer> addRole(@RequestBody Role role) {
      logger.info("roleCode={},roleName={}",role.getRoleCode(),role.getRoleName());
      int addCount = roleService.addRole(role);
      RespBean<Integer> respBean = new RespBean<>(200, "success");
      respBean.setData(addCount);
      return respBean;
 }

2.4 删除角色接口

删除角色接口涉及到多个与角色关联表中的数据也要一并删除,尤其以角色ID为外键的表中的记录,必须先删除以关联表中的记录,才能成功删除角色,否则直接删除角色时会导致删除失败,所以这个接口稍微复杂一点,在Service层方法中还要加上声明式事务注解。

「Dao 层代码实现」

RolesMapper.java

代码语言:javascript
复制
// 根据角色ID删除角色
int delRoleById(Integer roleId);

// 删除角色-用户表中与角色关联的记录
int delRoleUserByRoleId(Integer roleId);

RolesMapper.xml

代码语言:javascript
复制
<delete id="delRoleById" parameterType="java.lang.Integer">
  delete from roles where id = #{roleId, jdbcType=INTEGER}
</delete>

<delete id="delRoleUserByRoleId" parameterType="java.lang.Integer">
   delete from roles_user where rid=#{roleId,jdbcType=INTEGER}
</delete>

RoleRouterMapper.java

代码语言:javascript
复制
//角色-路由资源表中删除与角色关联的记录
Integer delRoleResourceByRoleId(Integer roleId);

RoleRouterMapper.xml

代码语言:javascript
复制
<delete id="delRoleResourceByRoleId" parameterType="java.lang.Integer">
  delete from role_resources where role_id = #{roleId, jdbcType=INTEGER}
</delete>

「Service层代码实现」

RoleService.java

代码语言:javascript
复制
@Autowired
private RolesMapper rolesMapper;

@Autowired
private RoleRouterMapper roleRouterMapper;

@Transactional(rollbackFor = Exception.class)
public int delRoleById(Integer roleId){
     rolesMapper.delRoleUserByRoleId(roleId);
     roleRouterMapper.delRoleResourceByRoleId(roleId);
     return rolesMapper.delRoleById(roleId);
}

「Controller层代码实现」

RoleController.java

代码语言:javascript
复制
@DeleteMapping("/delRole/{roleId}")
@ApiOperation(value = "delRoleById", notes = "删除角色", produces = "application/json",
            consumes = "application/json", response = RespBean.class)
@ApiImplicitParam(name="roleId", value = "角色ID", required = true, paramType = "path", dataType = "java.lang.Integer")
public RespBean<Integer> delRoleById(@PathVariable Integer roleId){
      logger.info("roleId={}", roleId);
      RespBean<Integer> respBean = new RespBean<>(200,"success");
      Integer count = roleService.delRoleById(roleId);
      respBean.setData(count);
      return respBean;
}

2.5 角色添加路由资源接口

「Dao层代码实现」

RoleRouterMapper.java

代码语言:javascript
复制
Integer addRouteIdsForRole(List<Integer> roleIds, Integer roleId);

RoleRouterMapper.xml

代码语言:javascript
复制
<insert id="addRouteIdsForRole" useGeneratedKeys="true">
   insert into    role_resources(role_id,resource_id,created_by,created_time,last_updated_by,last_updated_time)
   values
   <foreach collection="param1" item="routeId"  separator=",">
            (#{param2, jdbcType=INTEGER}, #{routeId, jdbcType=INTEGER}, 'heshengfu', now(), 'heshengfu', now())
   </foreach>
</insert>

「Service层代码实现」

RoleRouterService.java

代码语言:javascript
复制
@Transactional(rollbackFor = Exception.class)
public Integer addRouteIdsForRole(List<Integer> routeIds, Integer roleId){
    // 删除原来的角色-路由资源关系
    roleRouterMapper.delRoleResourceByRoleId(roleId);
    Integer count =  roleRouterMapper.addRouteIdsForRole(routeIds, roleId);
    return count;
}

「Controller层代码实现」

RouterResourceController.java

代码语言:javascript
复制
privatestaticfinal Logger logger = LoggerFactory.getLogger(RouterResourceController.class);

@Autowired
private RoleRouterService roleRouterService;

@PostMapping("/addRouteIds")
@ApiOperation(value = "addRouteIdsForRole", notes = "给角色添加路由资源", response = RespBean.class)
public RespBean<Integer> addRouteIdsForRole(@RequestBody List<Integer> routeIds, @RequestParam("roleId") Integer roleId){
    logger.info("http request addRouteIds start");
    logger.info("roleId={}",roleId);
    RespBean<Integer> respBean = new RespBean<>(200, "success");
    Integer count = roleRouterService.addRouteIdsForRole(routeIds, roleId);
    respBean.setData(count);
    return respBean;
 }

接口开发完毕,可借助postman接口UI工具或者启动项目后在进入接口文档页面http://localhost:8081/blog/doc.html#/home, 找到对应的接口界面进入起调试界面输入相关参数后对接口进行测试。详情可参考笔者之前发过的文章SpringBoot项目集成knif4j,从此告别手写Api文档

3 前端Vue代码实现

更具需求我们可以整理出前端要做的工作就是绘制一个展示角色列表的页面、增加或修改角色信息的弹出框及给角色分配路由资源的树形控件对话框。同时还要通过axios请求调用后台接口拿到5个后台接口的数据后,将数据在页面渲染。本文功能的实现在修改vue-element-admin开源项目中src/views/permission/role.vue组件的基础上进行。

3.1 统一管理新增接口方法

api/role.js文件中添加暴露调用后台接口方法

代码语言:javascript
复制

// 获取全量角色
exportfunction getAllRoles() {
  return request({
    url: '/role/allRoles',
    method: 'get',
    headers: {
      'Content-Type': 'x-www-form-urlencoded'
    }
  })
}
//添加角色
exportfunction addRole(data) {
  return request({
    url: '/role/addRole',
    method: 'post',
    data
  })
}

// 修改角色
exportfunction updateRole(data) {
  return request({
    url: `/role/updateRole`,
    method: 'post',
    data
  })
}

// 删除角色
exportfunction deleteRole(roleId) {
  return request({
    url: `/role/delRole/${roleId}`,
    method: 'delete'
  })
}
// 给角色分配路由资源
exportfunction addRouteIdsForRole(routeIds, roleId) {
  return request({
    url: `/routerResource/addRouteIds?roleId=${roleId}`,
    method: 'post',
    data: routeIds
  })
}

3.2 页面模板绘制

role.vue

代码语言:javascript
复制
<template>
  <div class="app-container">
    <el-button type="primary" @click="handleAddRole">新增角色</el-button>
    <el-table :data="rolesList" style="width: 100%;margin-top:30px;" border>
      <el-table-column align="center" label="角色代码" width="220">
        <template slot-scope="scope">
          {{ scope.row.roleCode}}
        </template>
      </el-table-column>
      <el-table-column align="center" label="角色名称" width="220">
        <template slot-scope="scope">
          {{ scope.row.roleName}}
        </template>
      </el-table-column>
      <el-table-column align="center" label="角色描述">
        <template slot-scope="scope">
          {{ scope.row.description }}
        </template>
      </el-table-column>
      <el-table-column align="center" label="操作">
        <template slot-scope="scope">
          <el-button type="primary" size="small" @click="handleEdit(scope)">修改角色</el-button>
          <el-button type="danger" size="small" @click="handleDelete(scope)">删除角色</el-button>
          <el-button type="primary" size="small" @click="allocateRoutes(scope)">分配路由</el-button>
        </template>
      </el-table-column>
    </el-table>

    <el-dialog :visible.sync="dialogVisible" :title="dialogType==='edit'?'修改角色':'添加角色'">
      <el-form :model="activeRole" label-width="80px" label-position="left">
        <el-form-item label="角色编码">
          <el-input v-model="activeRole.roleCode" placeholder="角色编码" />
        </el-form-item>
        <el-form-item label="角色名称">
          <el-input v-model="activeRole.roleName" placeholder="角色名称" />
        </el-form-item>
        <el-form-item label="角色描述">
          <el-input
            v-model="activeRole.description"
            type="text"
            placeholder="角色描述"
          />
        </el-form-item>
      </el-form>
      <div style="text-align:right;">
        <el-button type="danger" @click="dialogVisible=false">取消</el-button>
        <el-button type="primary" @click="confirmRole">确认</el-button>
      </div>
    </el-dialog>
    <el-dialog :visible.sync="treeVisible" width="500px"  :title="treeTitle" class="tree-dialog">
      <el-tree
         ref="tree"
         :data="routesData"
         show-checkbox
         node-key="id"
         :default-checked-keys="checkedKeys"
         class="permission-tree"
        />
        <div style="text-align:right;">
        <el-button type="danger" @click="treeVisible=false">取消</el-button>
        <el-button type="primary" @click="confirmRoutes">确认</el-button>
      </div>
    </el-dialog>
  </div>
</template>

3.3 页面js逻辑

role.vue

代码语言:javascript
复制
<script>
import { asyncRoutes } from'@/router/index'
import { getRouteIds, getAllRoles, addRole, deleteRole, updateRole, addRouteIdsForRole} from'@/api/role'

exportdefault {
  data() {
    return {
      routesData: [],
      rolesList: [],
      dialogVisible: false,
      treeVisible: false,
      dialogType: 'new',
      activeRole: '',
      treeTitle: '',
      checkedKeys: [],
      defaultProps: {
        children: 'children',
        label: 'label'
      }
    }
  },
  created() {
    // Mock: get all routes and roles list from server
    this.getRoles()
  },
  methods: {
    // 获取路由数据方法
    getRoutes() {
      this.routesData = this.generateRoutes(asyncRoutes)
    },
    //获取全部角色方法
    getRoles() {
      getAllRoles().then(res=>{
        if(res.status===200 && res.data.status===200){
            this.rolesList = res.data.data
        }else{
          this.$message({
            message: 'get getAllRoles error:'+res.data.msg,
            type: 'error'
          })
        }
      }).catch(err=>{
          this.$message({
            message: 'get getAllRoles error:'+err,
            type: 'error'
          })
      })
    },

    // 生产树形路由数据
    generateRoutes(routes) {
      let routeDisplayData = []
      for(let i=0;i<routes.length;i++){
        let routeItem = routes[i]
        let cateItem = {id: parseInt(routeItem.id), label: routeItem.meta && routeItem.meta.title? routeItem.meta.title: routeItem.name, children: []}
        routeDisplayData.push(cateItem)
        if(routeItem.children && routeItem.children.length>0){
            cateItem.children = this.generateRoutes(routeItem.children)
        }
      }
      return routeDisplayData
    },
    // 打开添加角色对话框  
    handleAddRole() {
      this.dialogType = 'new'
      this.dialogVisible = true
      // 当前选中角色信息清空  
      this.activeRole = {};
    },
    //打开修改角色对话框
    handleEdit(scope) {
      this.dialogType = 'edit'
      this.dialogVisible = true
      this.checkStrictly = true
      //变换当前选中角色  
      this.activeRole = scope.row;
    },
    //删除角色  
    handleDelete(scope) {
      this.$confirm('删除角色将一并删除角色用户关系表及角色资源关系表中与该角色关联的记录,是否确定删除?', 'Warning', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      })
        .then(() => {
          const roleId = scope.row.id
          deleteRole(roleId).then(res=>{
            if(res.status===200 && res.data.status===200){
              this.$message({
                type: 'success',
                message: '删除角色成功'
              })
              this.getRoles()
            }else{
              this.$message({
                type: 'error',
                message: '删除角色失败'
              })
            }
          })
        })
        .catch(err => { console.error(err) })
    },
    // 打开分配路由资源对话框  
    allocateRoutes(scope) {
       this.treeVisible = true;
       this.activeRole = scope.row
       this.treeTitle = this.activeRole.roleName+'分配路由资源'
       const roleId = scope.row.id;
       if(this.routesData.length===0){
         this.routesData = this.generateRoutes(asyncRoutes)
       }
       this.checkedKeys = []
       getRouteIds(roleId).then(res=>{
         if(res.status===200 && res.data.status===200){
           const chekedRouteIds = res.data.data
           for(let i=0;i<chekedRouteIds.length;i++){
             this.checkedKeys.push(parseInt(chekedRouteIds[i]))
             // 设置角色已有的路由资源
             this.$refs.tree.setCheckedKeys(this.checkedKeys)
           }
         }else{
           this.$message({
            type: 'error',
            message: 'getRouteIds error: ' + res.msg
          })
         }
       })
    },
    // 确认提交添加或修改角色
    confirmRole() {
      const isEdit = this.dialogType === 'edit'
      // this.role.routes = this.generateTree(deepClone(this.serviceRoutes), '/', checkedKeys)
      if (isEdit) {
        const role = this.activeRole
        updateRole(role).then(res=>{
          if(res.status===200 && res.data.status===200){
            this.$message({
              type: 'success',
              message: '修改角色成功'
            })
            this.getRoles()
          } else {
             this.$message({
              type: 'error',
              message: '修改角色失败'
            })
          }
        })
        this.dialogVisible = false
      } else {
        const role = this.activeRole
        addRole(role).then(res=>{
           if(res.status===200 && res.data.status===200){
             this.$message({
              type: 'success',
              message: '添加角色成功'
            })
            this.getRoles()
           }else{
              this.$message({
                type: 'error',
                message: '添加角色失败'
              })
           }
        })
        this.dialogVisible = false
      }
    },
    // 确认提交添加路由资源列表
    confirmRoutes(){
      const roleId = this.activeRole.id
      const routeIds = this.$refs.tree.getCheckedKeys()
      if(routeIds.length===0){
        this.$message({
          type: 'warning',
          message: '选中的路由ID不能为空'
        })
        this.treeVisible = false
        return
      }
      addRouteIdsForRole(routeIds, roleId).then(res=>{
        if(res.status===200 && res.data.status===200){
           this.$message({
              type: 'success',
              message: '添加路由资源成功'
            })
        }else{
            this.$message({
                type: 'error',
                message: '添加路由资源失败'
              })
        }
      })
      this.treeVisible = false
    },
  }
}
</script>

3.4 样式修改

role.vue

为了让界面卡看起来稍微美观一点,保持树形控件长度超过一定高度后显示垂直滚动条,方便操作人员在弹出的对话框界面看到确认和取消按钮,需要调整对话框的样式进行部分调整,修改后的样式代码如下。

代码语言:javascript
复制
<style lang="scss" scoped>
.app-container {
  .roles-table {
    margin-top: 30px;
  }
  .el-dialog_header .el-dialog__title{
    font-size: 20px;
  }
  .tree-dialog {
    margin-left: 200px;
  }
  .permission-tree {
    margin-bottom: 30px;
    height: 320px;
    overflow-y: auto;
  }
}
</style>

4 效果体验

启动后台springboot项目blog-server服务后,然后在开发环境下启动vue-element-admin项目(在vue-element-admin项目根目录右键->Git Bash Here进入控制台输入命令npm run dev后回车即可)

前后端项目启动成功后在谷歌浏览器中输入网址: http://localhost:3000/ 回车后重定向到登录界面,输入用户名和密码登录成功后点击右侧的「权限管理->角色管理」菜单即可进入角色管理操作界面测试本文开发的各项功能,感兴趣的读者可从笔者的代码仓库克隆下来后在本地跑起来然后亲自体验一番点击页面及各个按钮的效果,所有功能都经过了笔者的测试并通过,效果图在「效果预览」部分已经给出,其他就不再一一贴图了。

  • 本文后端项目gitee地址:https://gitee.com/heshengfu1211/blogserver.git
  • 本文前端项目gitee地址:https://gitee.com/heshengfu1211/vue-element-admin.git

5 参考链接

【1】 https://element.eleme.cn/#/zh-CN/component/dialog

【2】 https://element.eleme.cn/#/zh-CN/component/message-box

【3】 https://element.eleme.cn/#/zh-CN/component/tree

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-09-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 阿福谈Web编程 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 本文目录
  • 前言
  • 1 效果预览
  • 2 后端接口开发
    • 2.1 查询全量角色接口
      • 2.2 新增角色接口
        • 2.3 修改角色接口
          • 2.4 删除角色接口
            • 2.5 角色添加路由资源接口
            • 3 前端Vue代码实现
              • 3.1 统一管理新增接口方法
                • 3.2 页面模板绘制
                  • 3.3 页面js逻辑
                    • 3.4 样式修改
                    • 4 效果体验
                    • 5 参考链接
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档