SaaS system user permission design

SaaS system user permission design

learning target:

Understand the basic concepts and design ideas of the RBAC model

Understand the requirements of authority control in SAAS-HRM and analyze the table structure to complete the basic CRUD operations of the organization

Complete the basic CRUD operations of user management Complete the basic CRUD operations of role management

Organization Management

demand analysis

demand analysis

Realize enterprise organizational structure management and basic CRUD operations of departments

database table design

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

Microservice implementation

extract common code

(1) In the public controller

Add a public controller under the ihrm.common.controller package under the ihrm_commoncom. module

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 "江苏传智播客教育股份有限公司";
   }
}

Sao Dai understands: @ModelAttribute is an annotation in Spring MVC, its function is to bind HTTP request parameters to the specified ModelAttribute object and add it to ModelAndView. In layman's terms, it preprocesses model attributes before request handler methods for use in request handler methods. In Spring MVC, when a form is submitted from a browser, all form field names and values ​​are collected into a data structure called "request parameters". The @ModelAttribute annotation can map this request parameter to a property of a Java object.

(2) Public service

Add a public BaseService under the ihrm.common.service package under the ihrm_commoncom. module

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);
           }
       };
   }
}

Sao Dai understands: If the findAll method provided in SpringData does not have any parameters, it will get all the data. If you need to query all eligible data, you will need to pass in a parameter Specification. The Specification parameter is actually a condition. Here, getSpecification The method is used to create this condition, which is extracted here. This condition means to query all departments (organizational structure) under the company id of companyId

criteriabuilder.equal(root.get("companyid").as(string.class), companyid)The two in companyidrefer to different variables.

  • The first one companyid: Indicates rootthe attribute name in the entity class represented by the object, which is used for companyidcomparison with the one passed in later.
  • The second one companyid: represents a value passed in the method parameter, which is used to companyidcompare with the attribute of the entity.

In short, the function of this code fragment is to criteriabuilderconstruct a query condition, that is, to filter out records whose companyidattribute name in the entity class is equal to the value according to the value passed in.companyid

public predicate topredicate(root<t> root, criteriaquery<?> criteriaquery, criteriabuilder cb) is an interface method used to create a query predicate (predicate) in the criteria api, which receives three parameters:

  • root<t> root: represents the root node of the query, from which the attributes of the entity class can be obtained.
  • criteriaquery<?> criteriaquery: Represents the query object to be executed.
  • criteriabuilder cb: represents the factory class of criteria api, used to create various query conditions.

This method returns a query condition of predicate type, which represents the filter or restriction condition to be used in the given query. Predicate is the basic interface related to boolean expression in the criteria api, which is used to construct the conditional expression in the where clause.

This method is general and can be used in different scenarios. For example, in spring data jpa, we can use it to create dynamic queries based on query method names. Specifically, we can define an interface method and annotate it with @query to use that method with a specific jpql query. We can then write custom query logic in this method and use the topredicate() method to create criteria-based query predicates.

(3) Public 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;
    }

}

Sao Dai understands: The function of the DeptListResult class is to encapsulate all the organizational structure (department) information of the enterprise required by the front-end, because when the front-end displays, not only the list information of all departments under an enterprise, but also the relevant information of the enterprise, so here is Encapsulated into the DeptListResult class

    /**
     * 组织架构列表
     */
    @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));
   }

Implement basic CRUD operations

(1) Entity class

Create a Department entity class under the com.ihrm.domain.company package

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) Persistence layer

Create DepartmentDao under the com.ihrm.company.dao package

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) Business layer

Create DepartmentService under com.ihrm.company.service package

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));
   }
}

Sao Dai’s understanding: When I used mybatis before, the modification I wrote was to directly pass the new object to the dao layer, and dynamically update it in the dao layer (dynamic SQL sticky notes). Here, because the API of SpringData is used, you can find it by looking at the interface below. This framework does not provide an update method, so the modifications here are all implemented through the new method of save

Also note that the findById method has a .get() suffix, such as departmentDao.findById(id).get(), which is easy to forget/miss

The getSpecification method is to create a conditional predicate, which is a method from the parent class BaseService

(4) Control layer

Create the controller class DepartmentController in ihrm.company.controller

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));
   }
}

Sao Dai understands: the parseCompanyId method and parseCompanyName method called by the controller are both from the parent class BaseController

Front-end implementation

create module

  • Use the command line to create a module-departments module and import it into the project
  • Register the module in src/main.js
import departments from '@/module-departments/' // 组织机构管理
Vue.use(departments, store)
  • Configure routing in /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}
     }
   ]
 }
]

Configuration request API

Create departments.js in /src/api/base/ as the API public interface method of organization management

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)}

Sao Dai understands: {return data.id?update(data):add(data)} means to judge whether there is an id in the data, if so, call the update method, and if not, call the add method

Construct a tree list

(1) Construct page style

<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) Tree organization list

  <!-- 
    构造树形列表
      叶子 <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>

Sao Dai understands:

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

This is an el-tree component used in vue.js to display hierarchical tree data. Here is an explanation of each property in the code:

  • :props="{label:'name'}": This attribute sets the property name of the text content to be displayed on the tree node to "name", that is, the tree node will display the "name" of each element in the "data" array "Attributes. Later, you can use { { node.label }} to get the name, which is the name of the department
  • :data="depts": This attribute sets the source of the tree data to a data array named "depts", that is, each node of the tree is represented by an element in the array.
  • node-key="id": This attribute sets the unique identifier of the tree node to the "id" attribute of each element in the "data" array, which will help to accurately add, delete or update nodes in the tree position.
  • default-expand-all: This property sets that by default, all tree nodes will be displayed expanded.

解释 <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>

  • If node.isleaf is true (judging whether it is a leaf node), then render a male icon <i class="fa fa-male"></i>.
  • Otherwise, an expandable and collapsible rectangular icon is rendered. The style of the rectangular icon depends on the value of the node.expanded property:
    • Renders the icon <i class="fa fa-minus-square-o"></i> with a minus sign if node.expanded is true (if the node is expanded).
    • If node.expanded is false (the node is closed), render the icon with a plus sign `<i class="fa fa-plus-square-o">

explain the following code

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

The <em> tag is used to emphasize certain words in text, making it visually distinguishable from other text content.   means a space, the effect is as follows

<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>

Through the above code to achieve the following effects

(3) Structure data

<!-- 引入组件 -->
<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) Tree components

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;
  }
}

Sao Dai understands: This is a good thing, it can parse the List collection passed from the backend into a tree-like data structure

Additions, deletions, revisions and checks of organizations

new department

Use the popup layer of the dialog provided by element-ui to construct a popup add page

<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>

Configuration save method

<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>

Sao Dai's understanding: there are two steps to add, pop up the add box and click save. There are two types of new additions, one with a parent node and one without a parent node. You only need to pass the id of the parent node. saveOrupdate(this.dept) is one of the APIs. If the passed this.dept has an id, it is a modification operation, and if there is no id, it is a new operation. Here you can find that the this.dept model has no assigned id, so it is a new operation. This should be done so that both the new addition and the modification use the same component, that is, both use this dialog box, res.data.success?' success':'error' means res.data.success returns false, which is the error type, and if it returns true, it is the success type.

location.reload(); Refreshing the page is obviously slow, so I still use the getList method to get new data, but how to refer to the method of the parent class Vue in the component? this.$parent.getList(); This way you can adjust the method of the parent class

modify department

Query department by id

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

Sao Dai’s understanding: the same pop-up window is used for adding and modifying, and it is also separated into an add.vue page later. Both adding and modifying call the saveOrupdate API, which can be judged based on whether there is an id value Call save or update these two APIs.

On the front end, {id:id} represents an object, as long as it is in the format of {}, it is an object, so the following update interface uses a data object instead of an id!

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

Here I couldn't figure out why the front end only passed an id, but the back end could receive the Department object? In fact, I was confused. The data of the above API actually corresponds to the Department object of the backend, and then the id of the backend is obtained from the url path, and the id passed by the frontend is obtained from the data.

delete department

   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();
                }
              })
        })
    },

Extract components

Components are the most powerful feature of Vue.js. Different businesses can be developed by splitting them into different components to make the code more elegant and readable. Of course, the page can encapsulate reusable code, and realize the reuse of components by passing in different objects.

(1) Extract new/modified pages to /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) Reference components in /module-departments/page/index.vue

  • import component
import deptAdd from './../components/add'  //导入组件
export default { 
  
  components: { deptAdd }, //声明组件
  data() {
    return {
      deptAdd: 'deptAdd', //配置组件别名
      activeName: 'first', 
      departData:{},
   }
 },
  ....
}
  • use components
//v-bind:is (绑定的组件名称)
//ref : 引用子组件中内容的别名
<component v-bind:is="deptAdd" ref="deptAdd"></component>
  • Retrofit new modification method
    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
     })
   },

Sao Dai understands: this.$refs.deptAdd.formData can be written in the parent Vue to call the property formData of the component

RBAC model

What is RBAC

RBAC (full name: Role-Based Access Control) role-based access control, as a promising replacement for traditional access control (voluntary access, mandatory access), has received widespread attention. In RBAC, permissions are associated with roles, and users gain the permissions of those roles by becoming members of the appropriate roles. This greatly simplifies the management of permissions. In an organization, roles are created to accomplish various tasks, and users are assigned corresponding roles according to their responsibilities and qualifications. Users can be easily assigned from one role to another. Roles can be given new permissions according to new requirements and system integration, and permissions can also be withdrawn from a role as needed. Character-to-character relationships can be built to encompass broader objective situations.

Access control is a defense measure against unauthorized use of resources, the purpose of which is to limit the access rights of access subjects (such as users, etc.) to access objects (such as database resources, etc.). Most of the access control strategies in the enterprise environment adopt the role-based access control (RBAC) model, which is currently recognized as an effective method to solve the unified resource access control of large enterprises

Design ideas based on RBAC

The basic principle of role-based access control is to add a layer of roles between users and access rights to separate users and rights. Users can only obtain access rights by activating roles. Grouping permissions by roles greatly simplifies the user permission allocation table, indirectly realizes the grouping of users, and improves the efficiency of permission allocation. And after adding the role layer, the access control mechanism is closer to the occupation assignment in the real world, which is convenient for authority management.

In the RBAC model, roles are divided by the system according to relatively stable powers and responsibilities in management, and each role can perform certain functions. Users obtain the permissions of the role by playing different roles. Once a user becomes a member of a role, the user can complete the functions of the role. By assigning permissions to roles instead of users, it provides great flexibility and fine granularity in permission assignment.

Sao Dai understands: The RBAC model is actually to assign fixed permissions to roles, and then assign roles to users, so that users have all the permissions of this role, so as to achieve the purpose of permission control. If not, then you need to manually Add permissions one by one for users to implement permission management

Table structure analysis

A user has several roles, and each role has several permissions. In this way, an authorization model of "user-role-permission" is constructed. In this model, there is generally a many-to-many relationship between users and roles, and between roles and permissions.

Authorization Design in SAAS-HRM

demand analysis

Basic elements of a SAAS platform

SAAS platform administrator: responsible for the daily maintenance and management of the platform, including user log management, tenant account review, tenant status management, and tenant fee management. It should be noted that the platform administrator cannot manage the tenant's specific business

Enterprise tenant: refers to the user enterprise that accesses the SaaS platform, and the information between each tenant in the SaaS platform is independent.

Tenant administrator: Assign permissions to tenant roles and related system management and maintenance.

Tenant role: According to the business function, the tenant administrator divides the roles. After the roles are divided, the tenant administrator can assign permissions to the corresponding roles

Tenant users: Tenant users need to be assigned roles, and tenant users can only access authorized module information.

demand analysis

In the application system, what form is the authority displayed in? The access to the menu, the visibility of the buttons on the page, and the control of the back-end interface must be fully considered

front end

Front-end menu: Dynamically load according to whether there is a request for menu permission Button: Display/hide control according to whether there is this permission point

rear end

When the front-end sends a request to the back-end interface, it is necessary to verify the access to the interface

Authorization design

In response to such requirements, in some designs, menus, buttons, back-end API requests, etc. can be used as resources, thus forming another authorization model based on RBAC (user-role-permission-resource). We just used this scheme in the permission design of the SAAS-HRM system

For this permission model, does the permission belong to the menu, button, or API permission? Then you need to add types to distinguish when designing the database permission table (for example, permission type 1 is menu 2 is function 3 is API).

Table structure analysis

It should be noted here that there is a one-to-one relationship between the permission table and the permission menu table, the page element table and the API interface table. Compared with the traditional RBAC model, it is not difficult to find the benefits of this design:

  • There is no need to distinguish which are operations and which are resources
  • It is convenient for expansion. When the system wants to control the permissions of new things, I only need to create a new resource table and determine the permission type identification of such permissions.

Sao Dai understands: the permissions here may be menus, page elements, and API interfaces. The specific permission is controlled by the permission field in the permission table. If the permission field is 1, it is a menu. If it is 2, it is a page element. , if it is 3, it is the API interface, and then go to the corresponding table to query which menus, page elements, and API interfaces are available for this permission. What I understood at the beginning is the one-to-many relationship, and it Here is actually referring to the permission table and a set of menus, a set of page elements, and a set of API, so it is a one-to-one relationship

User Management

demand analysis

The user is actually the employee who visits the saas enterprise, and the basic CRUD operation table structure for the enterprise employee is as follows:

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

Configure system microservices

  • Build the system microservice module (ihrm_system), import dependencies into 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>
  • Configure 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
  • Configure the startup class
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);
   }
}

Basic back-end user operations

Entity class

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>();//用户与角色   多对多
}

Sao Dai understands:

  • The @jsonignore annotation is used to ignore the specified attribute (field), that is, the field is not used as part of the json data during serialization/deserialization. This annotation is usually used to protect sensitive information or avoid problems such as infinite circular references. This is to avoid circular references, because in User private Set<Role> roles = new HashSet<Role>(); and in Role private Set<User> users = new HashSet<User>(0); so the cycle Dependent, adding this annotation will be fine
  • The @manytomany annotation indicates a many-to-many relationship, that is, an instance of an entity class (entity) can be associated with multiple instances of other entity classes, and an instance of an entity class can also be associated with multiple instances of other entity classes. In jpa, the many-to-many relationship needs to be implemented through an intermediate table.
  • The @jointable annotation is used to define the structural details and association methods of the intermediate tables in the many-to-many relationship. It includes information such as the name of the intermediate table, related fields, and foreign keys. These information can be configured in detail through the joincolumns and inversejoincolumns sub-annotations.
    • name: Specify the name of the association table (intermediate table), here it is pe_user_role.
    • The joinColumns in joinColumns={@JoinColumn(name="user_id",referencedColumnName="id")} indicates the relationship between the current class and the fields of the intermediate table and the external table, and name indicates that the primary key of the table corresponding to the current class is in the associated table ( The field name in the intermediate table), referencedColumnName represents the field of the external table, that is, the id in the role table
    • inverseJoinColumns={@JoinColumn(name="role_id",referencedColumnName="id")} inverseJoinColumns indicates the relationship between the associated external table and the table corresponding to the current class and the fields of the intermediate table, and name indicates that the primary key of the external table is in the associated table ( The field name in the intermediate table), referencedColumnName represents the field of the table corresponding to the current class, that is, the id in the user table

Persistence layer

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> {
}

business logic layer

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()]));
           }
       };
   }
}

Sao Dai's understanding: In SpringData's JPA, the save method is to maintain data. When the entity contains a primary key, the JPA save method will perform an update operation. All new additions to this system are given ids, so new additions are actually modification operations.

page-1 in new PageRequest(page-1, size) is because the subscript starts from 0

criteriabuilder.and(list.toarray(new predicate[list.size()])); is a java language code snippet for creating a jpa query with an and condition.

The code is explained as follows:

  • CriteriaBuilder represents a CriteriaBuilder instance, which is an auxiliary class in the JPA Criteria API. Criteriabuilder can be used to create some query-related objects, such as predicate (query conditions).
  • list is a collection of list<predicate> type, which is used to store multiple query conditions for a certain field (for example: equal, greater than, less than, etc.), and the relationship between multiple conditions is an "and" relationship.
  • The toarray() method converts the predicate collection into an array type.
  • new predicate[list.size()] creates a predicate array of length list.size().
  • The simple understanding of list.toarray(new predicate[list.size()]) is to convert the list into a predicate array
  • criteriabuilder.and(...) means to connect all the conditions in the predicate array using the "and" relationship.

So what this code snippet does is add multiple and conditions in the jpa query

controller layer

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);
    }
}

Basic operation of front-end users

Due to the limited time, in line with the principle of not wasting time, the basic functions of the page parts are roughly similar. Build the module information using the provided base module code.

Configure the interface request path

Since the backend is a microservice, the port number of each project is different, so here you need to use the proxy provided by vue, modify 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"'
})

In config/index.js, configure proxyTable to configure the request backend address forwarded by the proxy

   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': ''
          }
        }
    },

Sao Dai's understanding: The above code is a configuration file in a vue.js project, which is used to proxy local requests to corresponding remote services to solve cross-domain problems. Among them, proxytable is an object, which includes different paths that need to be proxied and their corresponding remote services.

For example, when a request is made to '/api/company', the remote service cannot be directly accessed on the local server

'http://localhost:9001/company/', so we proxy it to the corresponding remote service by configuring proxytable here. changeorigin is true to indicate whether to change the source, pathrewrite is a mapping rule, will request the path

The '/api/company' part is replaced with '', that is, the prefix of /api/company is removed. The same applies to requests to /api/sys.

import employee module

registration module

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

Configure the API (user.js) under /src/api/base/

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)

User list display

page code

<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 structure data

<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>

user details

configure routing

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

Complete the user details page

<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>

User Information Component

<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>

new user

It is similar to the addition, deletion, modification and query of organizations

Job-Role Management

demand analysis

Complete the basic CRUD operations of the role

backend implementation

Entity class

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);//角色与模块 多对多
}

Persistence layer

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> {
}

business logic layer

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));
   }
}

controller layer

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);
   }
}

Guess you like

Origin blog.csdn.net/qq_50954361/article/details/131336165