SaaS系统用户权限设计

news2024/11/18 11:43:08

SaaS系统用户权限设计

学习目标:

理解RBAC模型的基本概念及设计思路

了解SAAS-HRM中权限控制的需求及表结构分析完成组织机构的基本CRUD操作

完成用户管理的基本CRUD操作完成角色管理的基本CRUD操作

组织机构管理

需求分析

需求分析

实现企业组织结构管理,实现部门的基本CRUD操作

数据库表设计

CREATE TABLE `co_department` (
 `id` varchar(40) NOT NULL,
`company_id` varchar(255) NOT NULL COMMENT '企业ID',
`parent_id` varchar(255) DEFAULT NULL COMMENT '父级部门ID',
`name` varchar(255) NOT NULL COMMENT '部门名称',
`code` varchar(255) NOT NULL COMMENT '部门编码',
`category` varchar(255) DEFAULT NULL COMMENT '部门类别',
`manager_id` varchar(255) DEFAULT NULL COMMENT '负责人ID',
`city` varchar(255) DEFAULT NULL COMMENT '城市',
`introduce` text COMMENT '介绍',
`create_time` datetime NOT NULL COMMENT '创建时间',
`manager` varchar(40) DEFAULT NULL COMMENT '部门负责人',
 PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

微服务实现

抽取公共代码

(1) 在公共controller

ihrm_commoncom.模块下的 ihrm.common.controller 包下添加公共controller

package com.ihrm.common.controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 公共controller
*     获取request,response
*     获取企业id,获取企业名称
*/
public class BaseController {
    protected HttpServletRequest request;
    protected HttpServletResponse response;
    @ModelAttribute
    public void setReqAndResp(HttpServletRequest request, HttpServletResponse response) 
{
        this.request = request;
        this.response = response;
   }
    //企业id,(暂时使用1,以后会动态获取)
    public String parseCompanyId() {
        return "1";
   }
    public String parseCompanyName() {
        return "江苏传智播客教育股份有限公司";
   }
}

骚戴理解:@ModelAttribute是Spring MVC中的一个注释,它的作用是将HTTP请求参数绑定到指定的ModelAttribute对象并添加到ModelAndView中。 通俗地讲,它可以在请求处理程序方法之前预处理模型属性,以便在请求处理程序方法中使用。在Spring MVC中,当从浏览器提交表单时,所有表单字段的名称和值都被收集到一个名为“请求参数”的数据结构中。 @ModelAttribute注释可以将此请求参数映射到Java对象的属性中。

(2) 公共service

ihrm_commoncom.模块下的 ihrm.common.service 包下添加公共BaseService

public class BaseService<T> {
    protected Specification<T> getSpecification(String companyId) {
        return new Specification<T>() {
            @Override
            public Predicate toPredicate(Root<T> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder cb) {
                return cb.equal(root.get("companyId").as(String.class),companyId);
           }
       };
   }
}

骚戴理解:在SpringData里面提供的findAll方法如果是没有任何参数就是获取所有的数据,如果需要查询所有符合条件的数据,那么将就需要传入一个参数Specification,Specification参数其实就是条件 ,这里的getSpecification方法是用来创建这个条件的,这里是抽出来了,这个条件的意思是查询companyId这个公司id下的所有部门(组织架构)

criteriabuilder.equal(root.get("companyid").as(string.class), companyid)中的两个companyid指的是不同的变量。

  • 第一个companyid:表示root对象所代表的实体类中的属性名,该属性用于和后面传递进来的companyid进行比较。
  • 第二个companyid:表示方法参数中传递进来的某个值,用于与实体的companyid属性进行比较。

简而言之,这个代码片段的作用是使用criteriabuilder构造一个查询条件,即根据传入的companyid值筛选出实体类中属性名为companyid的值等于该值的记录。

public predicate topredicate(root<t> root, criteriaquery<?> criteriaquery, criteriabuilder cb) 是一个接口方法,用于创建 criteria api 中的查询谓语(predicate),它接收三个参数:

  • root<t> root:代表查询的根节点,可以从中获取实体类的属性。
  • criteriaquery<?> criteriaquery:代表将被执行的查询对象。
  • criteriabuilder cb:代表 criteria api 的工厂类,用于创建各种查询条件。

该方法会返回一个 predicate 类型的查询条件,表示要在给定的查询中使用的过滤器或限制条件。predicate 是 criteria api 中与 boolean 表达式相关的基础接口,用于构造 where 子句中的条件表达式。

这个方法是通用的,可以在不同的场景下使用。例如,在 spring data jpa 中,我们可以使用它来创建基于查询方法名的动态查询。具体而言,我们可以定义一个接口方法并使用 @query 注解,以便将该方法与特定的 jpql 查询一起使用。然后,我们可以在该方法中编写自定义查询逻辑,并使用 topredicate() 方法创建基于标准的查询谓词。

(3)公共DeptListResult

package com.ihrm.common.response;

import com.ihrm.domain.company.Company;
import com.ihrm.domain.company.Department;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.List;

@Getter
@Setter
@NoArgsConstructor
public class DeptListResult {
    private String companyId;
    private String companyName;
    private String companyManage;
    private List<Department> depts;

    public DeptListResult(Company company,List<Department> list) {
        this.companyId = company.getId();
        this.companyName = company.getName();
        this.companyManage = company.getLegalRepresentative();
        this.depts = list;
    }

}

骚戴理解:DeptListResult这个类的作用就是封装前端需要的企业下所有组织架构(部门)信息,因为前端展示的时候不但要一个企业下所有的部门列表信息,还要企业的相关信息,所以这里就封装成了DeptListResult类

    /**
     * 组织架构列表
     */
    @RequestMapping(value = "/departments", method = RequestMethod.GET)
    public Result findAll() throws Exception {
        Company company = companyService.findById(parseCompanyId());
        List<Department> list = departmentService.findAll(parseCompanyId());
        return  new Result(ResultCode.SUCCESS,new DeptListResult(company,list));
   }

实现基本CRUD操作

(1)实体类

在com.ihrm.domain.company包下创建Department实体类

package com.ihrm.domain.company;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Transient;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
/**
* (Department)实体类
*/
@Entity
@Table(name = "co_department")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Department implements Serializable {
    private static final long serialVersionUID = -9084332495284489553L;
    //ID
    @Id
    private String id;
    /**
     * 父级ID
     */
    private String parentId;
    /**
     * 企业ID
     */
    private String companyId;
    /**
     * 部门名称
     */
    private String name;
    /**
     * 部门编码,同级部门不可重复
     */
    private String code;
    /**
     * 负责人ID
     */
    private String managerId;
    /**
 * 负责人名称
 */
    private String manager;
    /**
     * 介绍
     */
    private String introduce;
    /**
     * 创建时间
     */
    private Date createTime;
}

(2)持久化层

在 com.ihrm.company.dao 包下创建DepartmentDao

package com.ihrm.company.dao;
import com.ihrm.domain.company.Department;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
/**
* 部门操作持久层
*/
public interface DepartmentDao extends JpaRepository<Department, String>, 
JpaSpecificationExecutor<Department> {
}

(3)业务层

在 com.ihrm.company.service 包下创建DepartmentService

package com.ihrm.company.service;
import com.ihrm.common.entity.ResultCode;
import com.ihrm.common.exception.CommonException;
import com.ihrm.common.service.BaseService;
import com.ihrm.common.utils.IdWorker;
import com.ihrm.company.dao.DepartmentDao;
import com.ihrm.domain.company.Department;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.Date;
import java.util.List;
/**
* 部门操作业务逻辑层
*/
@Service
public class DepartmentService extends BaseService {
    @Autowired
    private IdWorker idWorker;
    @Autowired
    private DepartmentDao departmentDao;
    /**
     * 添加部门
     */
    public void save(Department department) {
        //填充其他参数
        department.setId(idWorker.nextId() + "");
        department.setCreateTime(new Date());
        departmentDao.save(department);
   }
    /**
     * 更新部门信息
     */
    public void update(Department department) {
        Department sourceDepartment = departmentDao.findById(department.getId()).get();
        sourceDepartment.setName(department.getName());
        sourceDepartment.setPid(department.getPid());
        sourceDepartment.setManagerId(department.getManagerId());
        sourceDepartment.setIntroduce(department.getIntroduce());
        sourceDepartment.setManager(department.getManager());
        departmentDao.save(sourceDepartment);
   }
    /**
     * 根据ID获取部门信息
     *
     * @param id 部门ID
     * @return 部门信息
     */
    public Department findById(String id) {
        return departmentDao.findById(id).get();
   }
    /**
     * 删除部门
     *
     * @param id 部门ID
     */
    public void delete(String id) {
        departmentDao.deleteById(id);
   }
    /**
     * 获取部门列表
     */
    public List<Department> findAll(String companyId) {
        return departmentDao.findAll(getSpecification(companyId));
   }
}

骚戴理解:以前用mybatis的时候写的修改都是直接把新的对象穿给dao层,在dao层动态更新(动态SQL便签),这里由于用的是SpringData的API,看下面的接口可以发现这个框架并没有提供update方法,所以这里的修改都是通过save新增方法来实现的

还要注意findById方法是有一个.get()后缀的,例如 departmentDao.findById(id).get(),容易忘/漏

getSpecification方法是创建条件谓语,这个方法是来自于父类BaseService的方法

(4)控制层

在 ihrm.company.controller 创建控制器类DepartmentController

package com.ihrm.company.controller;
import com.ihrm.common.controller.BaseController;
import com.ihrm.common.entity.Result;
import com.ihrm.common.entity.ResultCode;
import com.ihrm.company.service.CompanyService;
import com.ihrm.company.service.DepartmentService;
import com.ihrm.domain.company.Company;
import com.ihrm.domain.company.Department;
import com.ihrm.domain.company.response.DeptListResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import java.util.*;
import java.util.stream.Collectors;
/**
* 控制器层
*/
@CrossOrigin
@RestController
@RequestMapping("/company")
public class DepartmentController extends BaseController{
    @Autowired
    private DepartmentService departmentService;
    @Autowired
    private CompanyService companyService;
    /**
     * 添加部门
     */
    @RequestMapping(value = "/departments", method = RequestMethod.POST)
    public Result add(@RequestBody Department department) throws Exception {
        department.setCompanyId(parseCompanyId());
        departmentService.save(department);
        return Result.SUCCESS();
   }
    /**
     * 修改部门信息
     */
    @RequestMapping(value = "/departments/{id}", method = RequestMethod.PUT)
    public Result update(@PathVariable(name = "id") String id, @RequestBody Department
department) throws Exception {
        department.setCompanyId(parseCompanyId());
        department.setId(id);
        departmentService.update(department);
        return Result.SUCCESS();
   }
    /**
     * 删除部门
     */
    @RequestMapping(value = "/departments/{id}", method = RequestMethod.DELETE)
    public Result delete(@PathVariable(name = "id") String id) throws Exception {
        departmentService.delete(id);
        return Result.SUCCESS();
   }
    /**
     * 根据id查询
     */
    @RequestMapping(value = "/departments/{id}", method = RequestMethod.GET)
    public Result findById(@PathVariable(name = "id") String id) throws Exception {
        Department department = departmentService.findById(id);
        return new Result(ResultCode.SUCCESS,department);
   }
    /**
     * 组织架构列表
     */
    @RequestMapping(value = "/departments", method = RequestMethod.GET)
    public Result findAll() throws Exception {
        Company company = companyService.findById(parseCompanyId());
        List<Department> list = departmentService.findAll(parseCompanyId());
        return  new Result(ResultCode.SUCCESS,new DeptListResult(company,list));
   }
}

骚戴理解:控制器调用的parseCompanyId方法和parseCompanyName方法都是来自父类BaseController中

前端实现

创建模块

  • 使用命令行创建module-departments模块并引入到工程中
  • 在src/main.js中注册模块
import departments from '@/module-departments/' // 组织机构管理
Vue.use(departments, store)
  • 在/module-departments/router/index.js配置路由
import Layout from '@/module-dashboard/pages/layout'
const _import = require('@/router/import_' + process.env.NODE_ENV)
export default [
 {
    root: true,
    path: '/departments',
    component: Layout,
    redirect: 'noredirect',
    name: 'departments',
    meta: {
      title: '组织架构管理',
      icon: 'architecture'
   },
    children: [
     {
        path: 'index',
        component: _import('departments/pages/index'),
        name: 'organizations-index',
        meta: {title: '组织架构', icon: 'architecture', noCache: true}
     }
   ]
 }
]

配置请求API

在/src/api/base/创建departments.js作为组织机构管理的API公共接口方法

import {createAPI} from '@/utils/request'

//查询部门列表
export const list = data => createAPI('/company/department', 'get', data)
//保存部门
//data  {id:“”,name:“”}
export const save = data => createAPI('/company/department', 'post', data)
//根据id查询部门 {id:“”}
export const find = data => createAPI(`/company/department/${data.id}`, 'get', data)
//根据id删除部门 {id:""}
export const deleteById = data => createAPI(`/company/department/${data.id}`, 'delete', data)
//根据id更新部门 {id:"",name:"",code:""}
export const update = data => createAPI(`/company/department/${data.id}`, 'put', data)
//保存或更新的方法
export const saveOrupdate = data => {return data.id?update(data):save(data)}

骚戴理解: {return data.id?update(data):add(data)}的意思是判断data里面有没有id,如果有的话就调update方法,没有就调用add方法

构造树形列表

(1)构造页面样式

<template>
  <div class="dashboard-container">
    <div class="app-container">
      <el-card shadow="never">
            <div class='organization-index'>
              <div class='organization-index-top'>
                <div class='main-top-title'>
                  <el-tabs v-model="activeName">
                    <el-tab-pane label="组织结构" name="first"></el-tab-pane>
                    <div class="el-tabs-report">
                      <a class="el-button el-button--primary el-button--mini" title="导出" >导入</a>
                      <a class="el-button el-button--primary el-button--mini" title="导出" >导出</a>
                    </div>
                  </el-tabs>
                </div>
              </div>
              <div style="overflow: scroll;white-space:nowrap"  class="treBox">
                <div class="treeCon clearfix">
                    <span>
                      <i class="fa fa-university" aria-hidden="true"></i>
                      <span ><strong>{{departData.companyName}}</strong></span>
                    </span>
                    <div class="fr">
                      <span class="treeRinfo">
                        <div class="treeRinfo">
                          <span>{{departData.companyManage}}</span>
                          <span>在职  <em class="colGreen" title="在职人数">---</em>&nbsp;&nbsp;(<em class="colGreen" title="正式员工">---</em>&nbsp;/&nbsp;<em class="colRed" title="非正式员工">---</em>)</span>
                        </div>
                        <div class="treeRinfo">
                          <el-dropdown class="item">
                            <span class="el-dropdown-link">
                              操作<i class="el-icon-arrow-down el-icon--right"></i>
                            </span>
                            <el-dropdown-menu slot="dropdown">
                                <el-dropdown-item>
                                  <el-button type="text" @click="handlAdd('')">添加子部门</el-button>
                                </el-dropdown-item>
                              <el-dropdown-item>
                                <el-button type="text" @click="handleList()">查看待分配员工</el-button>
                              </el-dropdown-item>
                            </el-dropdown-menu>
                          </el-dropdown>
                        </div>
                      </span>  
                    </div>
                  </div>

                  <!-- 
                    构造树形列表
                      叶子 <i class="fa fa-male"></i>
                      非叶子 
                        展开 <i class="fa fa-minus-square-o">
                        闭合 <i class="fa fa-plus-square-o">
                    <div class="generalClass" slot-scope="{node,data}" style="width:99%">
                  -->
                  <el-tree :props="{label:'name'}" :data="depts" node-key="id" default-expand-all>
                    <!--
                      node : 是否展开,是否叶子节点
                      data:部门对象
                            id,name
                     -->
                    <div class="generalClass" slot-scope="{node,data}" style="width:99%">
                        <span>
                           <i v-if="node.isLeaf" class="fa fa-male"></i>
                           <i v-else :class="node.expanded?'fa fa-minus-square-o':'fa fa-plus-square-o'"></i>
                          <span>{{ node.label }}</span>
                        </span>
                         <div class="fr">
                          <span class="treeRinfo">
                            <div class="treeRinfo">
                              <span>{{departData.companyManage}}</span>
                              <span>在职  <em class="colGreen" title="在职人数">---</em>&nbsp;&nbsp;(<em class="colGreen" title="正式员工">---</em>&nbsp;/&nbsp;<em class="colRed" title="非正式员工">---</em>)</span>
                            </div>
                            <div class="treeRinfo">
                              <el-dropdown class="item">
                                <span class="el-dropdown-link">
                                  操作<i class="el-icon-arrow-down el-icon--right"></i>
                                </span>
                                <el-dropdown-menu slot="dropdown">
                                    <el-dropdown-item>
                                      <el-button type="text" @click="handlAdd(data.id)">添加子部门</el-button>
                                    </el-dropdown-item>
                                    <el-dropdown-item>
                                      <el-button type="text" @click="handUpdate(data.id)">查看部门</el-button>
                                    </el-dropdown-item>
                                    <el-dropdown-item>
                                      <el-button type="text" @click="handleList()">查看待分配员工</el-button>
                                    </el-dropdown-item>
                                    <el-dropdown-item>
                                    <el-button type="text" @click="handleDelete(data.id)">删除部门</el-button>
                                  </el-dropdown-item>
                                </el-dropdown-menu>
                              </el-dropdown>
                            </div>
                          </span>  
                        </div>
                      </div>
                  </el-tree>
              </div>
            </div>    
      </el-card>
    </div>
    <!--:visible.sync 是否显示 -->
    <!--引入组件-->
    <component v-bind:is="deptAdd" ref="addDept"></component>
</div>
</template>
 
<!-- 引入组件 -->
<script>
//引入api
import {list,saveOrupdate,find,deleteById} from "@/api/base/dept"
import commonApi from '@/utils/common'
import deptAdd from './../components/add'
export default {
  components:{deptAdd},
  data() {
    return {
      deptAdd:'deptAdd',
      activeName: 'first', 
      departData:{},
      depts:[]
    }
  },
  methods: {
      //添加部门
    handlAdd(parentId) {
      //父页面调用子组件中的内容
      this.$refs.addDept.parentId = parentId;
      this.$refs.addDept.dialogFormVisible = true
    },
    //查看部门
    handUpdate(id) {
      //根据id查询部门
      find({id:id}).then(res => {
         //数据绑定到dept对象中
         this.$refs.addDept.dept = res.data.data;
         this.$refs.addDept.dialogFormVisible = true
      })
    },
  
    handleDelete(id) {
       this.$confirm('是否删除此条记录?', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        }).then(() => {
               deleteById({id:id}).then(res=> {
                this.$message({
                  message: res.data.message,
                  type: res.data.success?'success':'error'
                });
                if(res.data.success) {
                  location.reload();
                }
              })
        })
    },

    //构造查询方法
    getList() {
      list().then(res => {
       this.departData = res.data.data
       //将普通的数据转化为父子接口
       this.depts = commonApi.transformTozTreeFormat(res.data.data.depts);
       console.log(this.depts)
      })
    }
  },
  created: function() {
    this.getList();
  },
}
</script>
<style rel="stylesheet/scss" lang="scss">
.el-dropdown {
  color: #000000
}
.el-tree-node__content>.el-tree-node__expand-icon {
  padding:0px;
}
.el-tree-node__expand-icon {
  color:#ffffff
}
.generalClassNode {
  padding-left: 20px;
}
.el-tree-node__content{
  font-size: 16px;
  line-height: 36px;
  height:36px;
}
.custom-tree-node{
  padding-left: 20px;
}
.objectTree {
  overflow: auto;
  z-index: 100;
  width: 300px;
  border: 1px solid #dcdfe6;
  margin-top: 5px;
  left: 70px;
}
.el-tabs__content {
  overflow: initial;
}
.boxpad {
  margin-left: -40px;
}
</style>
<style  rel="stylesheet/scss" lang="scss" scoped>
.el-tree-node__expand-icon{
 
}
.el-icon-caret-right{}
.el-tree-node__content{
  font-size: 14px;
  line-height: 36px;
}
.generalClass {
  font-size: 14px;
  line-height: 36px;
  color:#000000
}
.all {
  position: relative;
  min-height: 100%;
  padding-bottom: 200px;
}
.organization-main:after,
.organization-index-top:after {
  display: block;
  clear: both;
  content: '';
  visibility: hidden;
  height: 0;
}
.organization-main {
  font-size: 14px;
  font-size: 14px;
}

.organization-index {
  padding-bottom: 20px;
  margin-left: 20px;
}
.main-top-title {
  padding-left: 20px;
  padding-top: 20px;
  text-align: left;
}

::-webkit-scrollbar-thumb {
  background-color: #018ee8;
  height: 50px;
  outline-offset: -2px;
  outline: 8px solid #fff;
  -webkit-border-radius: 4px;
}
::-webkit-scrollbar-track-piece {
  background-color: #fff;
  -webkit-border-radius: 0;
}
::-webkit-scrollbar {
  width: 8px;
  height: 8px;
}
::-webkit-scrollbar-thumb:hover {
  background-color: #fb4446;
  height: 50px;
  -webkit-border-radius: 4px;
}
.modal-total {
  width: 100%;
  height: 100%;
  position: fixed;
  top: 0;
  left: 0;
  background: #000;
  z-index: 90;
  opacity: 0.2;
}
.modal {
  width: 400px;
  height: 300px;
  background-color: #ffffff;
  z-index: 999;
  position: absolute;
  left: 45%;
  top: 20%;
  text-align: center;
}
.treBox {
  padding: 30px 120px 0;
}
.organization-index-top {
  position: relative;

  .el-tabs-report {
    position: absolute;
    top: -50px;
    right: 15px;
  }
}
.treeCon {
  border-bottom: 1px solid #cfcfcf;
  padding: 10px 0;
  margin-bottom: 10px;
  .el-dropdown {
    color: #333;
  }
}
.treeRinfo {
  display: inline-block;
}
.treeRinfo span {
  padding-left: 30px;
}
</style>

(2)树形机构列表

  <!-- 
    构造树形列表
      叶子 <i class="fa fa-male"></i>
      非叶子 
        展开 <i class="fa fa-minus-square-o">
        闭合 <i class="fa fa-plus-square-o">
    <div class="generalClass" slot-scope="{node,data}" style="width:99%">
  -->
  <el-tree :props="{label:'name'}" :data="depts" node-key="id" default-expand-all>
    <!--
      node : 是否展开,是否叶子节点
      data:部门对象
            id,name
     -->
    <div class="generalClass" slot-scope="{node,data}" style="width:99%">
        <span>
           <i v-if="node.isLeaf" class="fa fa-male"></i>
           <i v-else :class="node.expanded?'fa fa-minus-square-o':'fa fa-plus-square-o'"></i>
          <span>{{ node.label }}</span>
        </span>
         <div class="fr">
          <span class="treeRinfo">
            <div class="treeRinfo">
              <span>{{departData.companyManage}}</span>
              <span>在职  <em class="colGreen" title="在职人数">---</em>&nbsp;&nbsp;(<em class="colGreen" title="正式员工">---</em>&nbsp;/&nbsp;<em class="colRed" title="非正式员工">---</em>)</span>
            </div>
            <div class="treeRinfo">
              <el-dropdown class="item">
                <span class="el-dropdown-link">
                  操作<i class="el-icon-arrow-down el-icon--right"></i>
                </span>
                <el-dropdown-menu slot="dropdown">
                    <el-dropdown-item>
                      <el-button type="text" @click="handlAdd(data.id)">添加子部门</el-button>
                    </el-dropdown-item>
                    <el-dropdown-item>
                      <el-button type="text" @click="handUpdate(data.id)">查看部门</el-button>
                    </el-dropdown-item>
                    <el-dropdown-item>
                      <el-button type="text" @click="handleList()">查看待分配员工</el-button>
                    </el-dropdown-item>
                    <el-dropdown-item>
                    <el-button type="text" @click="handleDelete(data.id)">删除部门</el-button>
                  </el-dropdown-item>
                </el-dropdown-menu>
              </el-dropdown>
            </div>
          </span>  
        </div>
      </div>
  </el-tree>

骚戴理解:

解释<el-tree :props="{label:'name'}" :data="depts" node-key="id" default-expand-all>

这是vue.js中使用的一个el-tree组件,用于显示层次结构的树形数据。以下是对代码中每个属性的解释:

  • :props="{label:'name'}":这个属性设置了要显示在树节点上的文本内容的属性名为“name”,即树节点将显示"data"数组中每个元素的"name"属性。 后面可以通过{{ node.label }}去获取name,也就是部门的名称
  • :data="depts":这个属性设置了树形数据的来源为一个名为“depts”的数据数组,即树的每个节点由数组中的一个元素表示。
  • node-key="id":这个属性设置了树节点的唯一标识符为"data"数组中每个元素的"id"属性,这将有助于在树中添加、删除或更新节点时进行准确定位。
  • default-expand-all:这个属性设置了默认情况下,所有树节点都将展开显示。

解释 <i v-if="node.isLeaf" class="fa fa-male"></i> 和<i v-else :class="node.expanded?'fa fa-minus-square-o':'fa fa-plus-square-o'"></i>

  • 如果 node.isleaf 为真(判断是不是叶子节点),则渲染一个男性符号的图标 <i class="fa fa-male"></i>。
  • 否则,就渲染一个可以展开和折叠的矩形图标。该矩形图标的样式取决于 node.expanded 属性的值:
    • 如果 node.expanded 为 true(节点展开的话),则渲染带有减号的图标 <i class="fa fa-minus-square-o"></i>。
    • 如果 node.expanded 为 false(节点关闭的话),则渲染带有加号的图标 `<i class="fa fa-plus-square-o">

解释以下代码

 <span>在职  <em class="colGreen" title="在职人数">---</em>&nbsp;&nbsp;(<em class="colGreen" title="正式员工">---</em>&nbsp;/&nbsp;<em class="colRed" title="非正式员工">---</em>)</span>

<em> 标签用于在文本中强调某些词语,使其在视觉上与其他文本内容区别开来。&nbsp;表示空格,实现效果如下所示

<el-dropdown class="item">
<span class="el-dropdown-link">
  操作<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
    <el-dropdown-item>
      <el-button type="text" @click="handlAdd(data.id)">添加子部门</el-button>
    </el-dropdown-item>
    <el-dropdown-item>
      <el-button type="text" @click="handUpdate(data.id)">查看部门</el-button>
    </el-dropdown-item>
    <el-dropdown-item>
      <el-button type="text" @click="handleList()">查看待分配员工</el-button>
    </el-dropdown-item>
    <el-dropdown-item>
    <el-button type="text" @click="handleDelete(data.id)">删除部门</el-button>
  </el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>

通过以上代码实现以下效果

(3) 构造数据

<!-- 引入组件 -->
<script>
//引入api
import {list,saveOrupdate,find,deleteById} from "@/api/base/dept"
import commonApi from '@/utils/common'
import deptAdd from './../components/add'
export default {
  components:{deptAdd},
  data() {
    return {
      deptAdd:'deptAdd',
      activeName: 'first', 
      departData:{},
      depts:[]
    }
  },
  methods: {
      //添加部门
    handlAdd(parentId) {
      //父页面调用子组件中的内容
      this.$refs.addDept.parentId = parentId;
      this.$refs.addDept.dialogFormVisible = true
    },
    //查看部门
    handUpdate(id) {
      //根据id查询部门
      find({id:id}).then(res => {
         //数据绑定到dept对象中
         this.$refs.addDept.dept = res.data.data;
         this.$refs.addDept.dialogFormVisible = true
      })
    },
  
    handleDelete(id) {
       this.$confirm('是否删除此条记录?', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        }).then(() => {
               deleteById({id:id}).then(res=> {
                this.$message({
                  message: res.data.message,
                  type: res.data.success?'success':'error'
                });
                if(res.data.success) {
                  location.reload();
                }
              })
        })
    },

    //构造查询方法
    getList() {
      list().then(res => {
       this.departData = res.data.data
       //将普通的数据转化为父子接口
       this.depts = commonApi.transformTozTreeFormat(res.data.data.depts);
       console.log(this.depts)
      })
    }
  },
  created: function() {
    this.getList();
  },
}
</script>

(4)树形组件

export default {
  timestampToTime: (timestamp) => {
    let date = new Date(timestamp * 1000)
    let Y = date.getFullYear() + '-'
    let M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-'
    let D = date.getDate() + ' '
    let h = date.getHours() + ':'
    let m = date.getMinutes() + ':'
    let s = date.getSeconds()
    return Y + M + D + h + m + s
  },
  transformTozTreeFormat: function (sNodes) {
    var i, l;
    var r = [];
    var tmpMap = {};
    for (i = 0, l = sNodes.length; i < l; i++) {
      tmpMap[sNodes[i].id] = sNodes[i];
    }
    for (i = 0, l = sNodes.length; i < l; i++) {
      var p = tmpMap[sNodes[i].parentId];
      if (p && sNodes[i].id != sNodes[i].parentId) {
        var children = this.nodeChildren(p);
        if (!children) {
          children = this.nodeChildren(p, []);
        }
        children.push(sNodes[i]);
      } else {
        r.push(sNodes[i]);
      }
    }
    return r;
  },
  nodeChildren: function (node, newChildren) {
    if (typeof newChildren !== 'undefined') {
      node.children = newChildren;
    }
    return node.children;
  }
}

骚戴理解:这是个好东西,可以把后端传过来的List集合解析成树状的数据结构

组织机构的增删改查

新增部门

使用element-ui提供的dialog的弹出层构造弹出添加页面

<el-dialog title="编辑部门" :visible.sync="dialogFormVisible">
    <el-form ref="dataForm" :model="formData" label-width="120px">
        <el-form-item label="部门名称">
       <el-input v-model="formData.name" placeholder='请输入部门名称'></el-input>
        </el-form-item>
        <el-form-item label="部门编码">
       <el-input v-model="formData.code" placeholder='请输入部门编码'></el-input>
        </el-form-item>
        <el-form-item label="部门负责人">
       <el-input v-model="formData.manager" placeholder='请输入负责人'></el-input>
        </el-form-item>
        <el-form-item label="部门介绍">
       <el-input v-model="formData.introduce" placeholder='请输入介绍'></el-input>
        </el-form-item>
    </el-form>
    <div slot="footer" class="dialog-footer">
        <el-button type="primary" @click="createData">确定</el-button>
        <el-button @click="dialogFormVisible=false">取消</el-button>
    </div>
</el-dialog>

配置保存方法

<script>
import {list,saveOrupdate,find,deleteById} from "@/api/base/dept"
export default {
 data () {
     return {
      //添加部门的模型
      parentId:'',
      dialogFormVisible:false,
      dept:{}
     }
 },
  methods: {
    saveDept() {
      this.dept.parentId = this.parentId
      saveOrupdate(this.dept).then(res => {
        this.$message({
          message: res.data.message,
          type: res.data.success?'success':'error'
        });
        //保存成功
        if(res.data.success) {
          //如果成功
          location.reload();
        }
      })
    }
  }
}
</script>

骚戴理解:新增有两个步骤,弹出新增框和点击保存。这里有两种类型的新增,分别是有父节点的新增和没有父节点的新增,只需要把父节点的id传过来即可, saveOrupdate(this.dept)这是其中的一个API,如果传过去的this.dept有id,那就是修改操作,没有id就是新增操作。这里可以发现this.dept模型是没有赋值id的,所以是新增操作,这样搞应该是为了让新增和修改都使用同一个组件,也就是都用这个对话框,res.data.success?'success':'error'的意思的res.data.success返回的是false就是error类型,如果返回的是true,那就是success类型。

location.reload();刷新页面有很明显的迟钝,所以我还是用的getList方法 来获取新的数据,但是在组件中怎么引用父类Vue的方法呢? this.$parent.getList();这样写就可以调父类的方法了

修改部门

根据id查询部门

    //修改部门
    handUpdate(id) {
      //根据id查询部门
      find({id:id}).then(res => {
         //数据绑定到dept对象中
         this.$refs.addDept.dept = res.data.data;
         this.$refs.addDept.dialogFormVisible = true
      })
    }

骚戴理解:新增和修改用的都是同一个弹窗,后面也抽离成为一个add.vue页面,新增和修改都是调用的saveOrupdate这个API,这个API可以根据是否有id值才判断调用save或update这两个API。

在前端{id:id}就表示一个对象,只要是{}这样的格式就是对象,所以下面的update接口用的是data对象,而不是id!

export const saveOrupdate = data => {return data.id?update(data):save(data)}
export const update = data => createAPI(`/company/department/${data.id}`, 'put', data)

这里我一开始搞不懂为什么前端只传了一个id,后端却可以接收到Department对象?其实是我搞混淆了,上面API的data其实对应的是后端的Department对象,然后后端的id是从url路径中获取的,前端传过去的id是从data中获取的

删除部门

   handleDelete(id) {
       this.$confirm('是否删除此条记录?', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        }).then(() => {
               deleteById({id:id}).then(res=> {
                this.$message({
                  message: res.data.message,
                  type: res.data.success?'success':'error'
                });
                if(res.data.success) {
                  location.reload();
                }
              })
        })
    },

抽取组件

组件(Component)是Vue.js 最强大的功能。可以通过将不同的业务拆分为不同的组件进行开发,让代码更加优雅提供可读性。当然页可以封装可重用的代码,通过传入对象的不同,实现组件的复用。

(1)抽取新增/修改页面到 /module-departments/components/add.vue 中

<template>
    <el-dialog title="编辑部门" :visible.sync="dialogFormVisible">
      <!-- model : 数据模型 -->
      <el-form :model="dept" label-width="120px">
        <el-form-item label="部门名称">
          <el-input v-model="dept.name" autocomplete="off"></el-input>
        </el-form-item>
                <el-form-item label="部门编码">
          <el-input v-model="dept.code" autocomplete="off"></el-input>
        </el-form-item>
                <el-form-item label="部门负责人">
          <el-input v-model="dept.manager" autocomplete="off"></el-input>
        </el-form-item>
                <el-form-item label="部门介绍">
          <el-input v-model="dept.introduce" autocomplete="off"></el-input>
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button @click="dialogFormVisible = false">取 消</el-button>
        <el-button type="primary" @click="saveDept">确 定</el-button>
      </div>
    </el-dialog>
</template>

<script>
import {list,saveOrupdate,find,deleteById} from "@/api/base/dept"
export default {
 data () {
     return {
      //添加部门的模型
      parentId:'',
      dialogFormVisible:false,
      dept:{}
     }
 },
  methods: {
    saveDept() {
      this.dept.parentId = this.parentId
      saveOrupdate(this.dept).then(res => {
        this.$message({
          message: res.data.message,
          type: res.data.success?'success':'error'
        });
        //保存成功
        if(res.data.success) {
          //如果成功
          location.reload();
        }
      })
    }
  }
}
</script>

<style>

</style>

(2) 在 /module-departments/page/index.vue 中引用组件

  • 导入组件
import deptAdd from './../components/add'  //导入组件
export default { 
  
  components: { deptAdd }, //声明组件
  data() {
    return {
      deptAdd: 'deptAdd', //配置组件别名
      activeName: 'first', 
      departData:{},
   }
 },
  ....
}
  • 使用组件
//v-bind:is (绑定的组件名称)
//ref : 引用子组件中内容的别名
<component v-bind:is="deptAdd" ref="deptAdd"></component>
  • 改造新增修改方法
    handlAdd(parentId) {
      //对子组件中的属性复制
      this.$refs.deptAdd.formData = {};
      this.$refs.deptAdd.parentId = parentId
      this.$refs.deptAdd.dialogFormVisible = true;
   },
    handleEdit(id) {
      detail({id}).then( res=> {
        this.$refs.deptAdd.formData = res.data.data
        this.$refs.deptAdd.dialogFormVisible = true
        this.$refs.deptAdd.parentId = res.data.data.parentId
     })
   },

骚戴理解:this.$refs.deptAdd.formData这样写可以在父Vue中调用组件的属性formData

RBAC模型

什么是RBAC

RBAC(全称:Role-Based Access Control)基于角色的权限访问控制,作为传统访问控制(自主访问,强制访问)的有前景的代替受到广泛的关注。在RBAC中,权限与角色相关联,用户通过成为适当角色的成员而得到这些 角色的权限。这就极大地简化了权限的管理。在一个组织中,角色是为了完成各种工作而创造,用户则依据它的责 任和资格来被指派相应的角色,用户可以很容易地从一个角色被指派到另一个角色。角色可依新的需求和系统的合 并而赋予新的权限,而权限也可根据需要而从某角色中回收。角色与角色的关系可以建立起来以囊括更广泛的客观情况。

访问控制是针对越权使用资源的防御措施,目的是为了限制访问主体(如用户等) 对访问客体(如数据库资源等) 的访问权限。企业环境中的访问控制策略大部分都采用基于角色的访问控制(RBAC)模型,是目前公认的解决大型企业的统一资源访问控制的有效方法

基于RBAC的设计思路

基于角色的访问控制基本原理是在用户和访问权限之间加入角色这一层,实现用户和权限的分离,用户只有通过激 活角色才能获得访问权限。通过角色对权限分组,大大简化了用户权限分配表,间接地实现了对用户的分组,提高 了权限的分配效率。且加入角色层后,访问控制机制更接近真实世界中的职业分配,便于权限管理。

在RBAC模型中,角色是系统根据管理中相对稳定的职权和责任来划分,每种角色可以完成一定的职能。用户通过 饰演不同的角色获得角色所拥有的权限,一旦某个用户成为某角色的成员,则此用户可以完成该角色所具有的职能。通过将权限指定给角色而不是用户,在权限分派上提供了极大的灵活性和极细的权限指定粒度。

骚戴理解:RBAC模型其实就是给角色分配固定的权限,然后给用户去分配角色,这样用户就具有了这个角色有的所有权限,从而达到权限控制的目的,如果不这样的话,那就需要手动的为用户添加一个个的权限来实现权限管理

表结构分析

一个用户拥有若干角色,每一个角色拥有若干权限。这样,就构造成“用户-角色-权限”的授权模型。在这种模型中,用户与角色之间,角色与权限之间,一般者是多对多的关系。

SAAS-HRM中的权限设计

需求分析

SAAS平台的基本元素

SAAS平台管理员:负责平台的日常维护和管理,包括用户日志的管理、租户账号审核、租户状态管理、租户费用 的管理,要注意的是平台管理员不能对租户的具体业务进行管理

企业租户:指访问SaaS平台的用户企业,在SaaS平台中各租户之间信息是独立的。

租户管理员:为租户角色分配权限和相关系统管理、维护。

租户角色:根据业务功能租户管理员进行角色划分,划分好角色后,租户管理员可以对相应的角色进行权限分配

租户用户:需对租户用户进行角色分配,租户用户只能访问授权的模块信息。

需求分析

在应用系统中,权限是以什么样的形式展现出来的?对菜单的访问,页面上按钮的可见性,后端接口的控制,都要进行充分考虑

前端

前端菜单:根据是否有请求菜单权限进行动态加载按钮:根据是否具有此权限点进行显示/隐藏的控制

后端

前端发送请求到后端接口,有必要对接口的访问进行权限的验证

权限设计

针对这样的需求,在有些设计中可以将菜单,按钮,后端API请求等作为资源,这样就构成了基于RBAC的另一种授权模型(用户-角色-权限-资源)。在SAAS-HRM系统的权限设计中我们就是才用了此方案

针对此种权限模型,其中权限究竟是属于菜单,按钮,还是API的权限呢?那就需要在设计数据库权限表的时候添加类型加以区分(如权限类型 1为菜单 2为功能 3为API)。

表结构分析

这里要注意的是,权限表与权限菜单表、页面元素表与API接口表都是一对一的关系,与传统的RBAC模型对比不难发现此种设计的好处:

  • 不需要区分哪些是操作,哪些是资源
  • 方便扩展,当系统要对新的东西进行权限控制时,我只需要建立一个新的资源表,并确定这类权限的权限类 型标识即可。

骚戴理解:这里的权限可能是菜单,页面元素,API接口,具体这个权限是什么由权限表里的权限字段来控制,权限字段如果是1,那就是菜单,如果是2,那就是页面元素,如果是3,那就是API接口,然后再去对应的表中去查询这个权限有哪些菜单,有哪些页面元素,有哪些API接口,这里我一开始理解的是一对多得到关系,而它这里是其实是指的权限表和一套菜单,一套页面元素,一套API,所以是一对一关系

用户管理

需求分析

用户其实就是saas企业访问的员工,对企业员工完成基本的CRUD操作表结构如下:

CREATE TABLE `bs_user` (
`id` varchar(40) NOT NULL COMMENT 'ID',
`mobile` varchar(40) NOT NULL COMMENT '手机号码',
`username` varchar(255) NOT NULL COMMENT '用户名称',
`password` varchar(255) DEFAULT NULL COMMENT '密码',
`enable_state` int(2) DEFAULT '1' COMMENT '启用状态 0是禁用,1是启用',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`department_id` varchar(40) DEFAULT NULL COMMENT '部门ID',
`time_of_entry` datetime DEFAULT NULL COMMENT '入职时间',
`form_of_employment` int(1) DEFAULT NULL COMMENT '聘用形式',
`work_number` varchar(20) DEFAULT NULL COMMENT '工号',
`form_of_management` varchar(8) DEFAULT NULL COMMENT '管理形式',
`working_city` varchar(16) DEFAULT NULL COMMENT '工作城市',
`correction_time` datetime DEFAULT NULL COMMENT '转正时间',
`in_service_status` int(1) DEFAULT NULL COMMENT '在职状态 1.在职	2.离职',
`company_id` varchar(40) DEFAULT NULL COMMENT '企业ID',
`company_name` varchar(40) DEFAULT NULL,
`department_name` varchar(40) DEFAULT NULL, PRIMARY KEY (`id`),
UNIQUE KEY `idx_user_phone` (`mobile`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

配置系统微服务

  • 搭建系统微服务模块(ihrm_system),pom引入依赖
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.ihrm</groupId>
            <artifactId>ihrm_common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
  • 配置application.yml
server:
 port: 9002
spring:
 application:
   name: ihrm-system #指定服务名
 datasource:
   driver-class-name: com.mysql.jdbc.Driver
   url: jdbc:mysql://localhost:3306/ihrm?useUnicode=true&characterEncoding=utf8
   username: root
   password: 111111
 jpa:
   database: MySQL
   show-sql: true
   open-in-view: true
  • 配置启动类
package com.ihrm.system;
import com.ihrm.common.utils.IdWorker;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.Bean;
@SpringBootApplication(scanBasePackages = "com.ihrm")
@EntityScan("com.ihrm.domain.system")
public class SystemApplication {
    public static void main(String[] args) {
        SpringApplication.run(SystemApplication.class, args);
   }
    @Bean
    public IdWorker idWorkker() {
        return new IdWorker(1, 1);
   }
}

后端用户基本操作

实体类

package com.ihrm.domain.system;

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;

/**
 * 用户实体类
 */
@Entity
@Table(name = "bs_user")
@Getter
@Setter
public class User implements Serializable {
    private static final long serialVersionUID = 4297464181093070302L;
    /**
     * ID
     */
    @Id
    private String id;
    /**
     * 手机号码
     */
    private String mobile;
    /**
     * 用户名称
     */
    private String username;
    /**
     * 密码
     */
    private String password;

    /**
     * 启用状态 0为禁用 1为启用
     */
    private Integer enableState;
    /**
     * 创建时间
     */
    private Date createTime;

    private String companyId;

    private String companyName;

    /**
     * 部门ID
     */
    private String departmentId;

    /**
     * 入职时间
     */
    private Date timeOfEntry;

    /**
     * 聘用形式
     */
    private Integer formOfEmployment;

    /**
     * 工号
     */
    private String workNumber;

    /**
     * 管理形式
     */
    private String formOfManagement;

    /**
     * 工作城市
     */
    private String workingCity;

    /**
     * 转正时间
     */
    private Date correctionTime;

    /**
     * 在职状态 1.在职  2.离职
     */
    private Integer inServiceStatus;

    private String departmentName;

    /**
     *  JsonIgnore
     *     : 忽略json转化
     */
    @JsonIgnore
    @ManyToMany
    @JoinTable(name="pe_user_role",
            joinColumns={@JoinColumn(name="user_id",referencedColumnName="id")},
            inverseJoinColumns={@JoinColumn(name="role_id",referencedColumnName="id")}
    )
    private Set<Role> roles = new HashSet<Role>();//用户与角色   多对多
}

骚戴理解:

  • @jsonignore注解用于忽略指定的属性(字段),即在序列化/反序列化过程中不将该字段作为json数据的一部分。该注解通常用于保护敏感信息或避免无限循环引用等问题。这里是为了避免循环引用的情况,因为在User里private Set<Role> roles = new HashSet<Role>();而在Role里private Set<User> users = new HashSet<User>(0);所以循环依赖了,加这个注解就没事了
  • @manytomany注解表示多对多关系,即一个实体类(entity)实例可以关联到多个其他实体类例,同时一个实体类实例也可以被多个其他实体类实例所关联。在jpa中,多对多关系需要通过中间表来实现。
  • @jointable注解则用于定义多对多关系中的中间表的结构细节和关联方式。其中包括中间表的名称、关联字段、外键等信息。这些信息可以通过joincolumns和inversejoincolumns子注解进行详细配置。
    • name: 指定关联表(中间表)的名称,这里为pe_user_role。
    • joinColumns={@JoinColumn(name="user_id",referencedColumnName="id")}里的joinColumns表示关联当前类和中间表和外部表的字段的关系,name表示当前类所对应表的主键在关联表(中间表)中的字段名,referencedColumnName表示外部表的字段,也就是角色表中的id
    • inverseJoinColumns={@JoinColumn(name="role_id",referencedColumnName="id")}里的inverseJoinColumns表示关联外部表和当前类所对应的表和中间表的字段的关系,name表示外部表主键在关联表(中间表)中的字段名,referencedColumnName表示当前类所对应的表的字段,也就是用户表中的id

持久化层

package com.ihrm.system.dao;
import com.ihrm.system.domain.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
/**
  * 企业数据访问接口
  */
public interface UserDao extends JpaRepository<User, String>, 
JpaSpecificationExecutor<User> {
}

业务逻辑层

package com.ihrm.system.service;
import com.ihrm.common.utils.IdWorker;
import com.ihrm.domain.system.Role;
import com.ihrm.domain.system.User;
import com.ihrm.system.dao.RoleDao;
import com.ihrm.system.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.*;
/**
* 部门操作业务逻辑层
*/
@Service
public class UserService {
    @Autowired
    private IdWorker idWorker;
    @Autowired
    private UserDao userDao;
    @Autowired
    private RoleDao roleDao;
    public User findByMobileAndPassword(String mobile, String password) {
        User user = userDao.findByMobile(mobile);
        if (user != null && password.equals(user.getPassword())) {
            return user;
       } else {
            return null;
       }
   }
    /**
     * 添加用户
     */
    public void save(User user) {
        //填充其他参数
        user.setId(idWorker.nextId() + "");
        user.setCreateTime(new Date()); //创建时间
        user.setPassword("123456");//设置默认登录密码
        user.setEnableState(1);//状态
        userDao.save(user);
   }
    /**
     * 更新用户
     */
    public void update(User user) {
        User targer = userDao.getOne(user.getId());
        targer.setPassword(user.getPassword());
        targer.setUsername(user.getUsername());
        targer.setMobile(user.getMobile());
        targer.setDepartmentId(user.getDepartmentId());
        targer.setDepartmentName(user.getDepartmentName());
        userDao.save(targer);
   }
    /**
     * 根据ID查询用户
     */
    public User findById(String id) {
        return userDao.findById(id).get();
   }
    /**
     * 删除部门
     *
     * @param id 部门ID
     */
    public void delete(String id) {
        userDao.deleteById(id);
   }
    public Page<User> findSearch(Map<String,Object> map, int page, int size) {
        return userDao.findAll(createSpecification(map), PageRequest.of(page-1, size));
   }
    /**
     * 调整部门
     */
    public void changeDept(String deptId,String deptName,List<String> ids) {
        for (String id : ids) {
            User user = userDao.findById(id).get();
            user.setDepartmentName(deptName);
            user.setDepartmentId(deptId);
            userDao.save(user);
       }
   }
    /**
     * 分配角色
     */
    public void assignRoles(String userId,List<String> roleIds) {
        User user = userDao.findById(userId).get();
        Set<Role> roles = new HashSet<>();
        for (String id : roleIds) {
            Role role = roleDao.findById(id).get();
            roles.add(role);
       }
        //设置用户和角色之间的关系
        user.setRoles(roles);
        userDao.save(user);
   }
    /**
     * 动态条件构建
     * @param searchMap
     * @return
     */
    private Specification<User> createSpecification(Map searchMap) {
        return new Specification<User>() {
            @Override
            public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, 
CriteriaBuilder cb) {
                List<Predicate> predicateList = new ArrayList<Predicate>();
                // ID
                if (searchMap.get("id") !=null && !"".equals(searchMap.get("id"))) {
                    predicateList.add(cb.equal(root.get("id").as(String.class), 
(String)searchMap.get("id")));
               }
                // 手机号码
                if (searchMap.get("mobile")!=null &&
!"".equals(searchMap.get("mobile"))) {
                    predicateList.add(cb.equal(root.get("mobile").as(String.class), 
(String)searchMap.get("mobile")));
               }
                // 用户ID
                if (searchMap.get("departmentId")!=null &&
!"".equals(searchMap.get("departmentId"))) {
predicateList.add(cb.like(root.get("departmentId").as(String.class), 
(String)searchMap.get("departmentId")));
               }
                // 标题
                if (searchMap.get("formOfEmployment")!=null &&
!"".equals(searchMap.get("formOfEmployment"))) {
                   
predicateList.add(cb.like(root.get("formOfEmployment").as(String.class), 
(String)searchMap.get("formOfEmployment")));
               }
                if (searchMap.get("companyId")!=null &&
!"".equals(searchMap.get("companyId"))) {
                    predicateList.add(cb.like(root.get("companyId").as(String.class), 
(String)searchMap.get("companyId")));
               }
                if (searchMap.get("hasDept")!=null &&
!"".equals(searchMap.get("hasDept"))) {
                    if("0".equals((String)searchMap.get("hasDept"))) {
                        predicateList.add(cb.isNull(root.get("departmentId")));
                   }else{
                        predicateList.add(cb.isNotNull(root.get("departmentId")));
                   }
               }
                return cb.and( predicateList.toArray(new
Predicate[predicateList.size()]));
           }
       };
   }
}

骚戴理解:在SpringData的JPA中save方法是保持数据,当实体中包含主键时,JPA的save方法会进行更新操作。这个系统的所有新增都是给了id的,所以新增其实都是修改操作。

new PageRequest(page-1, size)中page-1是因为下标是从0开始的

criteriabuilder.and(list.toarray(new predicate[list.size()])); 是一个java语言的代码片段,用于创建一个and条件的jpa查询。

该代码的解释如下:

  • criteriabuilder代表一个criteriabuilder实例,它是jpa criteria api中的一个辅助类。criteriabuilder可以用于创建一些查询相关的对象,比如predicate(查询条件)。
  • list是一个list<predicate>类型的集合,用于存放对某个字段的多个查询条件(例如:相等,大于,小于等),多个条件之间的关系是“且”的关系。
  • toarray()方法将predicate集合转换成数组类型。
  • new predicate[list.size()] 创建了一个长度为list.size()的predicate数组。
  • list.toarray(new predicate[list.size()])简单理解就是把list转化成predicate数组
  • criteriabuilder.and(...)表示将predicate数组中的所有条件使用“且”的关系连接在一起。

因此,该代码片段的作用是在jpa查询中添加多个and条件

控制器层

package com.ihrm.system.controller;

import com.ihrm.common.controller.BaseController;
import com.ihrm.common.entity.PageResult;
import com.ihrm.common.entity.Result;
import com.ihrm.common.entity.ResultCode;

import com.ihrm.domain.system.User;
import com.ihrm.system.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.web.bind.annotation.*;

import javax.websocket.server.PathParam;
import java.util.List;
import java.util.Map;

//1.解决跨域
@CrossOrigin
//2.声明restContoller
@RestController
//3.设置父路径
@RequestMapping(value="/sys")
public class UserController extends BaseController {

    @Autowired
    private UserService userService;

    /**
     * 保存
     */
    @RequestMapping(value = "/user", method = RequestMethod.POST)
    public Result save(@RequestBody User user) {
        //1.设置保存的企业id
        user.setCompanyId(parseCompanyId());
        user.setCompanyName(parseCompanyName());
        //2.调用service完成保存企业
        userService.save(user);
        //3.构造返回结果
        return new Result(ResultCode.SUCCESS);
    }

    /**
     * 查询企业的用户列表
     * 指定企业id
     */
    @RequestMapping(value = "/user", method = RequestMethod.GET)
    public Result findAll(int page, int size, @RequestParam Map map) {
        //1.获取当前的企业id
        map.put("companyId",parseCompanyId());
        //2.完成查询
        Page<User> pageUser = userService.findAll(map,page,size);
        //3.构造返回结果
        PageResult pageResult = new PageResult(pageUser.getTotalElements(),pageUser.getContent());
        return new Result(ResultCode.SUCCESS, pageResult);
    }

    /**
     * 根据ID查询user
     */
    @RequestMapping(value = "/user/{id}", method = RequestMethod.GET)
    public Result findById(@PathVariable(value = "id") String id) {
        User user = userService.findById(id);
        return new Result(ResultCode.SUCCESS, user);
    }


    /**
     * 修改User
     */
    @RequestMapping(value = "/user/{id}", method = RequestMethod.PUT)
    public Result update(@PathVariable(value = "id") String id, @RequestBody User user) {
        //1.设置修改的部门id
        user.setId(id);
        //2.调用service更新
        userService.update(user);
        return new Result(ResultCode.SUCCESS);
    }

    /**
     * 根据id删除
     */
    @RequestMapping(value = "/user/{id}", method = RequestMethod.DELETE)
    public Result delete(@PathVariable(value = "id") String id) {
        userService.deleteById(id);
        return new Result(ResultCode.SUCCESS);
    }
}

前端用户基本操作

由于时间有限,本着不浪费时间的原则,页面部分的基本功能都是大致相似的。使用提供的基本模块代码构建模块信息。

配置接口请求路径

由于后端是微服务,每个工程的端口号都不一样,所以这里就需要用到vue提供的代理,修改config\dev.env.js

'use strict'
const merge = require('webpack-merge')
const prodEnv = require('./prod.env')

module.exports = merge(prodEnv, {
  NODE_ENV: '"development"',
  BASE_API: '"api"'
})

在config/index.js中通过proxyTable配置代理转发的请求后端地址

   proxyTable: {
      //企业信息请求的远程服务
      '/api/company': {
          target: 'http://localhost:9001/company/',
          changeOrigin: true,
          pathRewrite: {
            '^/api/company': ''
          }
        },
        //api/sys/     user
       '/api/sys': {
          target: 'http://localhost:9002/sys/',
          changeOrigin: true,
          pathRewrite: {
            '^/api/sys': ''
          }
        }
    },

骚戴理解:上面这段代码是一个vue.js项目中的配置文件,用于将本地请求代理到对应的远程服务上,以解决跨域问题。其中,proxytable是一个对象,它包含了需要代理的不同路径及其对应的远程服务。

例如,当有请求发起至'/api/company'时,在本地服务器上不能直接访问远程服务

'http://localhost:9001/company/',因此我们在这里通过配置proxytable将其代理到对应的远程服务上。changeorigin为true表示是否改变源,pathrewrite是一个映射规则,将请求的路径中的

'/api/company'部分替换为'',即去除了/api/company的前缀。同样的方式适用于/api/sys的请求。

导入员工模块

注册模块

import employees from '@/module-employees/' // 员工管理
Vue.use(employees, store)

在/src/api/base/下配置API(user.js)

import {createAPI} from '@/utils/request'
export const list = data => createAPI('/sys/user', 'get', data)
export const simple = data => createAPI('/sys/user/simple', 'get', data)
export const add = data => createAPI('/sys/user', 'post', data)
export const update = data => createAPI(`/sys/user/${data.id}`, 'put', data)
export const remove = data => createAPI(`/sys/user/${data.id}`, 'delete', data)
export const detail = data => createAPI(`/sys/user/${data.id}`, 'get', data)

用户列表展示

页面代码

<template>
  <div class="dashboard-container">
    <div class="app-container">
      <el-card>
        <span class="seleInfo">
          <el-select v-model="requestParameters.stausInfo">
            <el-option v-for="item in baseData.stausInfos" :key="item.value" :label="item.label" :value="item.value">
            </el-option>
          </el-select>
        </span>
        <span class="posInfo">
          本月&nbsp;&nbsp;(--)&nbsp;&nbsp;
          <span>在职:
            <em>--</em>
          </span>
          <span>入职:
            <em class="active">--</em>
          </span>
          <span>离职:
            <em class="disabled">--</em>
          </span>
        </span>
        <div class="fr">
            <router-link :to="{'path':'/employees/import/',query: {name: '员工'}}" class="el-button el-button--primary el-button--mini" title="导入">导入</router-link>
            <el-button type="primary" size="mini" title="设置">设置</el-button>
            <router-link :to="{'path':'/employees/archiving/'}" class="el-button el-button--primary el-button--mini" title="历史归档">历史归档</router-link>
            <router-link :to="{'path':'/employees/report/1'}" class="el-button el-button--primary el-button--mini" >1月份报表</router-link>
            <el-button  type="primary" size="mini" icon="el-icon-plus" @click="handlAdd">新增员工</el-button>
        </div>
      </el-card>
      <el-card shadow="never" class="boxMar">
        <el-table :data="dataList" fit style="width: 100%;" border>
          <el-table-column type="index" :index="1" label="序号" width="150"> </el-table-column>
          <el-table-column sortable prop="username" label="姓名" width="150"></el-table-column>
          <el-table-column sortable prop="mobile" label="手机号" width="150"></el-table-column>
          <el-table-column sortable prop="workNumber" label="工号" width="120"></el-table-column>
          <el-table-column sortable prop="formOfEmployment" label="聘用形势" width="200"></el-table-column>
          <el-table-column sortable prop="departmentName" label="部门" width="200"></el-table-column>
          <el-table-column sortable prop="timeOfEntry" label="入职时间" width="150"></el-table-column>
          <el-table-column sortable label="状态" width="120">
            <template slot-scope="scope">
              <el-switch 
              v-model="scope.row.accountStatus" 
              active-color="#13ce66"
              inactive-color="#ff4949"
               @change="handleStatus(scope.row)">
              </el-switch>
            </template>
          </el-table-column>
          <el-table-column fixed="right" label="操作" align="center" width="220">
            <template slot-scope="scope">
              <router-link :to="{'path':'/employees/details/' + scope.row.id}" class="el-button el-button--text el-button--small">
                查看
              </router-link>
              <el-button @click="handleRole(scope.row)" type="text" size="small">分配角色</el-button>
              <el-button @click="handleDelete(scope.row)" type="text" size="small">删除</el-button>
            </template>
          </el-table-column>
        </el-table>
        <!-- 分页 -->
        <div class="pagination">
          <PageTool :paginationPage="requestParameters.page" :paginationPagesize="requestParameters.size" :total="counts" @pageChange="handleCurrentChange" @pageSizeChange="handleSizeChange">
          </PageTool>
        </div>
        <!-- end -->
        <!-- 新增员工弹层 -->
        <component v-bind:is="employeesAdd" ref="addUser" @doQuery="doQuery"></component>
        <!--分配角色组件 -->

      </el-card>
    </div>
  </div>
</template>

js构造数据

<script>
import constantApi from '@/api/constant/employees'
import {list,remove} from "@/api/base/users"
import PageTool from './../../components/page/page-tool'
import employeesAdd from './../components/add'
export default {
  name: 'employeesList',
  components: {
    PageTool,employeesAdd
  },
  data() {
    return {
      employeesAdd: 'employeesAdd',
      baseData: constantApi,
      dataList: [],
      counts: '',
      requestParameters:{
        page: 1,
        size: 10,
      }    
    }
  },
  methods: {
    // 业务方法
    doQuery(params) {
        list(this.requestParameters)
        .then(res => {
          this.dataList = res.data.data.rows
          this.counts = res.data.data.total
        })
    },
    // 每页显示信息条数
    handleSizeChange(size) {
      this.requestParameters.size = size
      if (this.requestParameters.page === 1) {
        this.doQuery(this.requestParameters)
      }
    },
    // 进入某一页
    handleCurrentChange(val) {
      this.requestParameters.page = val
      this.doQuery()
    },
    // 添加新员工
    handlAdd() {
      this.$refs.addUser.dialogFormVisible=true
    },
    // 删除
    handleDelete(item) {
      this.$confirm(
        `本次操作将删除${item.username}删除后账号将不可恢复,您确认删除吗?`,{
         type: 'warning'
        }
      ).then(() => {
          remove({ id: item.id })
            .then(response => {
              this.$message.success('删除成功' + '!')
              this.doQuery();
            })
        })
    }
  },
  // 创建完毕状态
  created: function() {
    this.doQuery()
  },
}
</script>

<style rel="stylesheet/scss" lang="scss" scoped>
.iconInfo {
  .fa {
    color: #999;
    font-size: 14px;
    cursor: pointer;
  }
  a {
    padding: 0 5px;
  }
}
.serachInput {
  .el-input--medium {
    width: 150px;
    .el-input__inner {
    }
  }
}
.serachInput .el-input--medium .el-input__inner {
  height: 26px;
  line-height: 26px;
}
</style>

用户详情

配置路由

     {
        path: 'details/:id',
        component: _import('employees/pages/employees-details'),
        // hidden: true // 是否显示在左侧菜单
        name: 'details',
        meta: {
          title: '详情'
       }
     }

完成用户详情页面

<template>
  <div class="dashboard-container">
    <div class="app-container">
      <el-card :style="{minHeight:boxHeight}">
          <el-tabs v-model="activeName" class="infoPosin">
            <el-tab-pane name="first" class="rInfo">
              <span slot="label">登录账户设置</span>
              <component v-bind:is="accountInfo" :objId='objId' ref="user"></component>
            </el-tab-pane>
            <el-tab-pane name="two" class="rInfo">
                <span slot="label">个人详情</span>
            </el-tab-pane>
            <el-tab-pane name="third" class="rInfo">
                <span slot="label">岗位信息</span>
            </el-tab-pane>
        </el-tabs>
      </el-card>
    </div>
  </div>
</template>
<script>
import accountInfo from './../components/details-account-info'
export default {
  name: 'employeesDetails',
  components: { accountInfo},
  data() {
    return {
      accountInfo:'accountInfo',
      activeName: 'first',
      objId: this.$route.params.id,
      dataList: []
   }
 }
}
</script>

用户信息组件

<template>
  <div class="boxInfo">
    <!-- 表单内容 -->
    <div class="formInfo">
      <div>
        <!-- 头部信息  -->
        <div class="userInfo">
             <div class="headInfo clearfix">
               <div class="headText">
                 <el-form ref="formData" :model="formData" label-width="215px">
                     <el-form-item label="姓名:">
                       <el-input v-model="formData.username" placeholder="请输入"
class="inputW"></el-input>
                     </el-form-item>
                     <el-form-item label="密码:">
                       <el-input v-model="formData.password" placeholder="请输入"
class="inputW"></el-input>
                    </el-form-item>
                    <el-form-item label="部门:">
                        <el-input
                          placeholder="请选择"
                          v-model="formData.departmentName"
                          icon="caret-bottom"
                          class="inputW"
                          @click.native="isShowSelect = !isShowSelect">
                        </el-input>
                        <input v-model="formData.departmentId" type="hidden" >
                        <el-tree v-if="isShowSelect"
                         :expand-on-click-node="false"
                         :data="inspectionObjectOptions"
                         :props="{label:'name'}"
                          default-expand-all
                         :filter-node-method="filterNode"
                           @node-click="handleNodeClick"
                          class="objectTree"
                          ref="tree2">
                        </el-tree>
                    </el-form-item>
                      <el-form-item>
                        <el-button type="primary" @click="saveData">更新</el-button>
                        <router-link :to="{'path':'/employees/index'}" class="el-button 
el-button--text el-button--small">
                          取消
                        </router-link>
                      </el-form-item>
                 </el-form>
               </div>
             </div>
        </div>
      </div>
    </div>
    </div>
</template>
<script>
import constantApi from '@/api/constant/employees'
import {detail,update} from "@/api/base/users"
import { organList } from '@/api/base/departments'
export default {
  name: 'accountInfo',
  props: ['objId'],
  data() {
    return {
      baseData: constantApi,
      inspectionObjectOptions: [],
      isShowSelect:false,
      formData: {
        id: this.objId,
     }
   }
 },
  methods: {
    handleNodeClick(data) {
      this.formData.departmentName = data.name
      this.formData.departmentId = data.id
      this.isShowSelect = false
   },
    // 获取详情
    getObjInfo() {
      detail({ id: this.objId }).then(res => {
          this.formData = res.data.data
     })
   },
    saveData(obj) {
      update(this.formData)
       .then(res => {
          this.formData = res.data
          this.$message.success('保存成功!')
          this.getObjInfo()
     })
   },
 },
  // 创建完毕状态
  created: function() {
    this.getObjInfo()
    organList().then(ret => {
      this.inspectionObjectOptions.push(ret.data.data)
   })
 }
}
</script>

用户的新增

和组织机构的增删改查大同小异

作业-角色管理

需求分析

完成角色的基本CRUD操作

后端实现

实体类

package com.ihrm.domain.system;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
@Entity
@Table(name = "pe_role")
@Getter
@Setter
public class Role implements Serializable {
    private static final long serialVersionUID = 594829320797158219L;
    @Id
    private String id;
    /**
     * 角色名
     */
    private String name;
    /**
     * 说明
     */
    private String description;
    /**
     * 企业id
     */
    private String companyId;
    @JsonIgnore
    @ManyToMany(mappedBy="roles")
    private Set<User> users = new HashSet<User>(0);//角色与用户   多对多
    @JsonIgnore
    @ManyToMany
    @JoinTable(name="pe_role_permission",
            joinColumns={@JoinColumn(name="role_id",referencedColumnName="id")},
            inverseJoinColumns=
{@JoinColumn(name="permission_id",referencedColumnName="id")})
    private Set<Permission> permissions = new HashSet<Permission>(0);//角色与模块 多对多
}

持久化层

package com.ihrm.system.dao;
import com.ihrm.system.domain.Role;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
/**
  * 企业数据访问接口
  */
public interface RoleDao extends JpaRepository<Role, String>, 
JpaSpecificationExecutor<Role> {
}

业务逻辑层

package com.ihrm.system.service;
import com.ihrm.common.utils.IdWorker;
import com.ihrm.system.dao.CompanyDao;
import com.ihrm.system.dao.RoleDao;
import com.ihrm.system.dao.UserDao;
import com.ihrm.system.domain.Company;
import com.ihrm.system.domain.Role;
import com.ihrm.system.domain.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 角色操作业务逻辑层
*/
@Service
public class RoleService {
    @Autowired
    private IdWorker idWorker;
    @Autowired
    private RoleDao roleDao;
    /**
     * 添加角色
     */
    public void save(Role role) {
        //填充其他参数
        role.setId(idWorker.nextId() + "");
        roleDao.save(role);
   }
    /**
     * 更新角色
     */
    public void update(Role role) {
        Role targer = roleDao.getOne(role.getId());
        targer.setDescription(role.getDescription());
        targer.setName(role.getName());
        roleDao.save(targer);
   }
    /**
     * 根据ID查询角色
     */
    public Role findById(String id) {
        return roleDao.findById(id).get();
   }
    /**
     * 删除角色
     */
    public void delete(String id) {
        roleDao.deleteById(id);
   }
    public Page<Role> findSearch(String companyId, int page, int size) {
        Specification<Role> specification = new Specification<Role>() {
            @Override
            public Predicate toPredicate(Root<Role> root, CriteriaQuery<?> query, 
CriteriaBuilder cb) {
                return cb.equal(root.get("companyId").as(String.class),companyId);
           }
       };
        return roleDao.findAll(specification, PageRequest.of(page-1, size));
   }
}

控制器层

package com.ihrm.system.controller;
import com.ihrm.common.entity.PageResult;
import com.ihrm.common.entity.Result;
import com.ihrm.common.entity.ResultCode;
import com.ihrm.system.domain.Role;
import com.ihrm.system.domain.User;
import com.ihrm.system.service.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.web.bind.annotation.*;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/sys")
public class RoleController {
    
    @Autowired
    private RoleService roleService;
    //添加角色
    @RequestMapping(value = "/role", method = RequestMethod.POST)
    public Result add(@RequestBody Role role) throws Exception {
        String companyId = "1";
        role.setCompanyId(companyId);
        roleService.save(role);
        return Result.SUCCESS();
   }
    //更新角色
    @RequestMapping(value = "/role/{id}", method = RequestMethod.PUT)
    public Result update(@PathVariable(name = "id") String id, @RequestBody Role role) 
throws Exception {
        roleService.update(role);
        return Result.SUCCESS();
   }
    //删除角色
    @RequestMapping(value = "/role/{id}", method = RequestMethod.DELETE)
    public Result delete(@PathVariable(name = "id") String id) throws Exception {
        roleService.delete(id);
        return Result.SUCCESS();
   }
    /**
     * 根据ID获取角色信息
     */
    @RequestMapping(value = "/role/{id}", method = RequestMethod.GET)
    public Result findById(@PathVariable(name = "id") String id) throws Exception {
        Role role = roleService.findById(id);
        return new Result(ResultCode.SUCCESS,role);
   }
    /**
     * 分页查询角色
     */
    @RequestMapping(value = "/role", method = RequestMethod.GET)
    public Result findByPage(int page,int pagesize,Role role) throws Exception {
        String companyId = "1";
        Page<Role> searchPage = roleService.findSearch(companyId, page, pagesize);
        PageResult<Role> pr = new
PageResult(searchPage.getTotalElements(),searchPage.getContent());
        return new Result(ResultCode.SUCCESS,pr);
   }
}

前端实现

参考资料自行实现

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/546040.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PCB 基础~典型的PCB设计流程,典型的PCB制造流程

典型的PCB设计流程 典型的PCB制造流程 • 从客户手中拿到Gerber&#xff0c; Drill以及其它PCB相关文件 • 准备PCB基片和薄片 – 铜箔的底片会被粘合在基材上 • 内层图像蚀刻 – 抗腐蚀的化学药水会涂在需要保留的铜箔上&#xff08;例如走线和过孔&#xff09; – 其他药水…

如何封装React无限滚动加载列表组件【含源码】

前言 由于需要考虑后端接口的性能问题&#xff0c;我们在请求业务数据列表的时候并不能直接请求全量数据。所以我们在请求数据时常见的方式是做分页查询。 对于前端交互而言&#xff0c;我们需要考虑如何优雅的让用户触发请求下一页数据的接口。常用的方法有两种&#xff1a;…

i春秋春季赛2023

只有两道逆向和一道misc&#xff0c;其他的有时间再写 wordle 不断筛有什么和没什么字母猜就行了 [6x] Guess a 5-letter word : first first [5x] Guess a 5-letter word : ideas Please enter a real English word that exists. [5x] Guess a 5-letter word : icily first…

View的基础与滑动

View的基础与滑动 1.View的基础知识 1.1View是什么 View是一种界面层的控件的一种抽象 ,我们知道的大多数控件都是直接或间接继承自View 如EditText,TextView等等 注意: EditText在compose中并没有相关控件,而是通过TextField来进行组合 Android中View本身就可以是单个控…

rt-thread启动流程(最详细教程)

资料下载 RT-Thread Simulator 例程 操作流程 将上面的仿真例程下载并解压&#xff0c;通过MDK打开&#xff0c;编译&#xff0c;调试&#xff0c;并打开串口点击运行&#xff0c;就可以看到如下输出了&#xff1a; 添加自己的 thread&#xff1a;在main()函数中添加即可&am…

【5.19】四、性能测试—流程

4.4 性能测试的流程 性能测试也遵循测试需求分析→测试计划制订→测试用例设计→测试执行→编写测试报告的基本过程&#xff0c;但在实现细节上&#xff0c;性能测试有单独一套流程&#xff1a; 1. 分析性能测试需求 在性能测试需求分析阶段&#xff0c;测试人员需要收集有关…

【AI Earth试玩】权限配置与openAPI调用工具库

前言 AI earth是阿里达摩院出的遥感云计算平台&#xff0c;我简单体验下来感觉像是GEE的python版本遥感深度学习计算平台&#xff0c;整体体验还是挺不错的&#xff0c;尤其是多分类的结果还是挺惊艳的。 平台提供工具箱和notebook两种模式&#xff0c;工具箱整个交互简单易用…

DJ 5-4 以太网 Ethernet

目录 一、以太网的物理拓扑结构 二、以太网物理层标准 1、以太网技术&#xff1a;10Base-T 和 100Base-T 2、以太网技术&#xff1a;1000Base 系列 3、曼彻斯特编码* 4、差分曼彻斯特编码机制* 三、以太网链路层控制技术 四、以太网的帧结构 1、前同步码 2、MAC 地址…

Spring Boot 项目【前后端分离】之后端实现加 LambdaQueryWrapper实现源码分析和手动模拟

目录 Spring Boot 项目【前后端分离】 之架子搭建 技术栈 实现功能03-创建Spring Boot 后端项目 需求分析/图解 思路分析 代码实现 1. 创建springboot_furn 项目 2. 修改pom.xml , 引入mybatis-plus 等相关依赖 3. 创建application.yml 配置port & 配置DB 连接信息…

【数据结构】KMP算法:计算next与nextval函数值(图解)

例&#xff1a;计算模式串"abaabcac"的KMP算法中next函数值 由函数定义 n e x t [ j ] { 0 , j 1 M a x { k ∣ 1 < k < j 且 " t 1 t 2 ⋅ ⋅ ⋅ t k − 1 " " t j − k 1 t j − k 2 ⋅ ⋅ ⋅ t j − 1 " } 1 , k 1 next[j]\left…

asp.net高校运动会管理系统的设计与实现

本高校运动会管理系统是针对我院当前运动会工作需要而开发的B/S模式的网络系统&#xff0c;涉及到运动会赛前的报名录入准备与分组编排、赛中的成绩处理、赛后的成绩汇总与团体总分的统计。它将是一个完整统一、技术先进、高效稳定、安全可靠的基于Internet/Intranet的高校运动…

一、Git安装(Git+TortoiseGit图形化)

Git 是一个开源的分布式版本控制系统&#xff0c;用于敏捷高效地处理任何或小或大的项目。 Git 是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。 Git 与常用的版本控制工具 CVS, Subversion 等不同&#xff0c;它采用了分布式版本库的方式…

《计算机网络—自顶向下方法》 Wireshark实验(八):ICMP 协议分析

ICMP&#xff08;Internet Control Message Protocol&#xff09;网络控制报文协议。它是 TCP/IP 协议簇的一个子协议&#xff0c;用于在 IP 主机、路由器之间传递控制消息。控制消息是指网络通不通、主机是否可达、路由是否可用等网络本身的消息。这些控制消息虽然并不传输用户…

进程概念

目录 冯诺依曼体系结构 操作系统OS 系统调用和库函数概念 进程 task_struct内容分类 组织进程 初识fork 进程状态 Z(zombie)-僵尸进程 孤儿进程 进程优先级 环境变量 和环境变量相关的命令 环境变量的组织方式 程序地址空间 冯诺依曼体系结构 关于冯诺依曼&…

Linux 防火墙 iptables

iptables概述 Linux 系统的防火墙 &#xff1a;IP信息包过滤系统&#xff0c;它实际上由两个组件netfilter 和 iptables组成。 主要工作在网络层&#xff0c;针对IP数据包。体现在对包内的IP地址、端口、协议等信息的处理上。 iptables是Linux系统防火墙的一种&#xff0c;是Ce…

SpringBoot【开发实用篇】---- 整合第三方技术(消息)

SpringBoot【开发实用篇】---- 整合第三方技术&#xff08;消息&#xff09; 消息的概念Java处理消息的标准规范JMSAMQPMQTTKafka 购物订单发送手机短信案例订单业务短息处理业务 SpringBoot整合ActiveMQ安装整合 SpringBoot整合RabbitMQ安装整合&#xff08;direct模型&#x…

【操作系统复习】第7章 输入/输出系统1

I/O系统管理的主要对象 ➢ I/O设备和对应的设备控制器 I/O系统的主要任务 ➢ 完成用户提出的I/O请求 ➢ 提高I/O速率 ➢ 改善I/O设备的利用率 I/O系统的上、下接口 ➢ I/O系统接口&#xff08;上接口&#xff09; ➢ 软件/硬件接口&#xff08;下接口&#xff09…

实验三 传感器目标识别

【实验目的】 1、了解环境感知传感器目标识别的目的和方法&#xff0c; 掌握MATLAB中的目标检测方法。 2、了解MATLAB的目标检测器和检测函数&#xff0c;掌握车辆识别、行人识别、交通标志识别和道路识别等目标识别方法。 【实验性质】 验证性实验。 【实验要求】 MATLAB 202…

Kubernetes实战入门

文章目录 一、组件介绍&#xff08;一&#xff09;master主控节点&#xff08;二&#xff09;node工作节点 二、k8s核心概念&#xff08;一&#xff09;pod&#xff08;二&#xff09;controller&#xff08;三&#xff09;service 三、搭建k8s集群&#xff08;一&#xff09;基…

6.1 Python面向对象设计及应用

1 类和对象 对象是具有某些特性和功能的具体事物的抽象。每个对象都具有描述其特征的属性及附属于它的行为。如&#xff1a;一个人有姓名、性别、身高、体重等特征描述&#xff0c;也有走路、说话、学习、开车等行为。 每个对象都有一个类&#xff0c;类是创建对象实例的模板&…