前后端分离微服务管理系统项目实战SaaS-HRM项目(三)——SaaS系统用户权限设计

三、SaaS系统用户权限设计

1、组织机构管理

<1>、需求分析
(1)、需求分析

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

(2)、数据库表设计
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
<2>、微服务实现
(1)、抽取公共部分

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

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);
			}
		}; 
	}
}
(2)、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 pid;
    /**
     * 企业ID
     */
    private String companyId;
    /**
     * 部门名称
     */
    private String name;
    /**
     * 部门编码,同级部门不可重复
     */
    private String code;

    /**
     * 负责人ID
     */
    private String managerId;
    /**
	*  负责人名称
	*/
    private String manager;

    /**
     * 介绍
     */
    private String introduce;
    /**
     * 创建时间
     */
    private Date createTime;
}

2、DAO层
在 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;

/**
 * 部门dao接口
 */
public interface DepartmentDao extends JpaRepository<Department,String> ,JpaSpecificationExecutor<Department> {
}

3、Service层
在 com.ihrm.company.service 包下创建DepartmentService

package com.ihrm.company.service;

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.List;

@Service
public class DepartmentService extends BaseService {

    @Autowired
    private DepartmentDao departmentDao;

    @Autowired
    private IdWorker idWorker;

    /**
     * 1.保存部门
     */
    public void save(Department department) {
        //设置主键的值
        String id = idWorker.nextId()+"";
        department.setId(id);
        //调用dao保存部门
        departmentDao.save(department);
    }

    /**
     * 2.更新部门
     */
    public void update(Department department) {
        //1.根据id查询部门
        Department dept = departmentDao.findById(department.getId()).get();
        //2.设置部门属性
        dept.setCode(department.getCode());
        dept.setIntroduce(department.getIntroduce());
        dept.setName(department.getName());
        //3.更新部门
        departmentDao.save(dept);
    }

    /**
     * 3.根据id查询部门
     */
    public Department findById(String id) {
        return departmentDao.findById(id).get();
    }

    /**
     * 4.查询全部部门列表
     */
    public List<Department> findAll(String companyId) {
        /**
         * 用户构造查询条件
         *      1.只查询companyId
         *      2.很多的地方都需要根据companyId查询
         *      3.很多的对象中都具有companyId
         *
         */
        return departmentDao.findAll(getSpec(companyId));
    }

    /**
     * 5.根据id删除部门
     */
    public void deleteById(String id) {
        departmentDao.deleteById(id);
    }
}

4、Controller层
在 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.web.bind.annotation.*;

import java.util.List;

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

    @Autowired
    private DepartmentService departmentService;

    @Autowired
    private CompanyService companyService;
    /**
     * 保存
     */
    @RequestMapping(value="/department",method = RequestMethod.POST)
    public Result save(@RequestBody Department department) {
        //1.设置保存的企业id
        /**
         * 企业id:目前使用固定值1,以后会解决
         */
        department.setCompanyId(companyId);
        //2.调用service完成保存企业
        departmentService.save(department);
        //3.构造返回结果
        return new Result(ResultCode.SUCCESS);
    }

    /**
     * 查询企业的部门列表
     * 指定企业id
     */
    @RequestMapping(value="/department",method = RequestMethod.GET)
    public Result findAll() {
        //1.指定企业id
        Company company = companyService.findById(companyId);
        //2.完成查询
        List<Department> list = departmentService.findAll(companyId);
        //3.构造返回结果
        DeptListResult deptListResult = new DeptListResult(company,list);
        return new Result(ResultCode.SUCCESS,deptListResult);
    }

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

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

    /**
     * 根据id删除
     */
    @RequestMapping(value="/department/{id}",method = RequestMethod.DELETE)
    public Result delete(@PathVariable(value="id") String id) {
        departmentService.deleteById(id);
        return new Result(ResultCode.SUCCESS);
    }
}
<3>、前端实现
(1)、创建模块

1、使用命令行创建module-departments模块并引入到工程中

itheima moduleAdd departments

2、在src/main.js中注册模块

 import departments from '@/module-departments/' // 组织机构管理 
 Vue.use(departments, store)

3、在 /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}
		} 
	 ]
  } 
]
(2)、配置请求API

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

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

export const organList = data => createAPI('/company/departments', 'get', data)
export const add = data => createAPI('/company/departments', 'post', data)
export const update = data => createAPI(`/company/departments/${data.id}`, 'put', data)
export const detail = data => createAPI(`/company/departments/${data.id}`, 'get', data)
export const remove = data => createAPI(`/company/departments/${data.id}`, 'delete',
data)
export const changeDept = data => createAPI(`/company/departments/changeDept`, 'put',
data)
export const saveOrUpdate = data => {return data.id?update(data):add(data)}
(3)、构造列表

1、构造基本页面样式
找到/module-departments/page/index.vue ,使用element-ui提供的Card组件构造卡片式容器

<template>
  <div class="dashboard-container">
    <div class="app-container">
      <el-card shadow="never">
            <div class='organization-index'>
              <div class='organization-index-top'>" >导入</a> 出" >导出</a>
  <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 class="el-button el-button--primary el-button--mini" title="导
      </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.name}}</strong></span>
      </span>
      <div class="fr">
<div class="treeRinfo">
<span>负责人</span>
<span>在职 <em class="colGreen" title="在职人数">--- </em>&nbsp;&nbsp;(<em class="colGreen" title="正式员工">---</em>&nbsp;/&nbsp;<em
class="colRed" title="非正式员工">---</em>)</span> </div>
</el-button>
<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-dropdown-item>
      <el-dropdown-item>
<el-button type="text" @click="handleList(organizationTree,0)">查看待分配员工</el-button>
                              </el-dropdown-item>
                            </el-dropdown-menu>
                          </el-dropdown>
                        </div>
                    </div>
                  </div>
<!--
构造树形列表
-->
              </div>
            </div>
      </el-card>
    </div>
</div>
</template>

2、树形机构列表

 <el-tree :data="departData.children" :indent="20">
                  <div class="generalClass" slot-scope="{node,data}" style="width:99%">
                    <span>
                      <span>
                        <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><strong>{{ data.name }}</strong></span>
                        </span>
                      </span>
                    </span>
                    <div class=fr>
                      <span class="treeRinfo">
<div class="treeRinfo">
<span>负责人</span>
<span>在职 <em class="colGreen" title="在职人数">---
</em>&nbsp;&nbsp;(<em class="colGreen" title="正式员工">---</em>&nbsp;/&nbsp;<em class="colRed" title="非正式员工">---</em>)</span>
                        </div>
                        <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-button>
部门</el-button>
看员工</el-button>
</el-button>
<el-dropdown-item>
<el-button type="text" @click="handlAdd(data.id)">添加子
              </el-dropdown-item>
              <el-dropdown-item>
<el-button type="text" @click="handleEdit(data.id)">编辑
              </el-dropdown-item>
            <el-dropdown-item>
<el-button type="text" @click="handleList(treeRoot,1)"></el-dropdown-item>
            <el-dropdown-item>
<el-button type="text" @click="handleDelete(data)">删除
            </el-dropdown-item>
          </el-dropdown-menu>
        </el-dropdown>
      </span>
    </div>
  </div>
</el-tree>

3、构造数据

 //数据绑定模型 data() {
return {
activeName: 'first', //激活pane的名称 dialogFormVisible:false,//是否显示弹出层标识
parentId:'',
    departData:{},
    formData:{}
//自定义方法 methods: {
//父id //部门列表 //表单提交数据
} },
  getObject(params) {
    organList().then(res => {
      this.departData = res.data.data
    })
} },
//钩子函数
created: function() {
  this.getObject()
},
(4)、组织机构的CRUD

1、新增部门
使用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>

配置保存方法

 createData:function() {
  this.formData.parentId = this.parentId
  saveOrUpdate(this.formData)
    .then(res => {
 this.$message({message:res.data.message,type:res.data.success?"success":"error"});
      location.reload()
      this.dialogFormVisible=false
  })
}

2、修改部门

  • 根据id查询部门
	handleEdit(id) {
	  detail({id}).then( res=> {
	    this.formData = res.data.data
	    this.dialogFormVisible = true
	    this.parentId = res.data.data.parentId
	}) }
  • 调用方法更新部门

3、删除部门

	handleDelete(obj) {
	     this.$confirm(
	`本次操作将删除${obj.name},删除后将不可恢复,您确认删除吗?` ).then(() => {
	       remove({id:obj.id}).then( res=> {
	this.$message({message:res.data.message,type:res.data.success?"success":"error"});
	         location.reload()
	}) })
	},
(5)、抽取组件

组件(Component)是Vue.js 最强大的功能。可以通过将不同的业务拆分为不同的组件进行开发,让代码更加优雅提供可读性。当然也可以封装可重用的代码,通过传入对象的不同,实现组件的复用。
1、抽取新增/修改页面到 /module-departments/components/add.vue 中

	<template>
	<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>
	</template>
	<script>
	import { saveOrUpdate } from '@/api/base/departments'
	export default {
	  name: 'dept-add',
	  data() {
	    return {
	      dialogFormVisible:false,
	      formData:{},
	      parentId:''
	} 
},
	 methods: {
	    createData:function() {
	      this.formData.parentId = this.parentId
	      saveOrUpdate(this.formData)
	        .then(res => {
	 this.$message({message:res.data.message,type:res.data.success?"success":"error"});
	          location.reload()
	          this.dialogFormVisible=false
	      })
	} }
	}
</script>

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

2、RBAC模型

<1>、什么是RBAC

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

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

<2>、基于RBAC的设计思路

基于角色的访问控制基本原理是在用户和访问权限之间加入角色这一层,实现用户和权限的分离,用户只有通过激活角色才能获得访问权限。通过角色对权限分组,大大简化了用户权限分配表,间接地实现了对用户的分组,提高了权限的分配效率。且加入角色层后,访问控制机制更接近真实世界中的职业分配,便于权限管理。
在这里插入图片描述
在RBAC模型中,角色是系统根据管理中相对稳定的职权和责任来划分,每种角色可以完成一定的职能。用户通过 饰演不同的角色获得角色所拥有的权限,一旦某个用户成为某角色的成员,则此用户可以完成该角色所具有的职 能。通过将权限指定给角色而不是用户,在权限分派上提供了极大的灵活性和极细的权限指定粒度。

<3>、表结构分析

在这里插入图片描述
一个用户拥有若干角色,每一个角色拥有若干权限。这样,就构成“用户-角色-权限”的授权模型,在这种模型中,用户与角色之间,角色与权限之间,一般都是多对多的关系。

3、SaaS-HRM中的权限设计

<1>、需求分析
(1)、SaaS平台的基本元素

在这里插入图片描述
SaaS平台管理员:负责平台的日常维护和管理,包括用户日志的管理、租户账号审核、租户状态管理、租户费用的管理,要注意的是平台管理员不能对租户的具体业务进行管理
企业租户:指访问SaaS平台的用户企业,在SaaS平台中各租户之间信息是独立的
租户管理员:为租户角色分配权限和相关系统管理、维护
租户角色:根据业务功能租户管理员进行角色划分,划分角色之后,租户管理员可以对相应的角色进行权限分配
租户用户:需要对租户用户进行角色分配,租户用户只能访问授权的模块信息

(2)、需求分析

在应用系统中,权限是以什么样的形式展现出来的?对菜单的访问,页面上按钮的可见性,后端接口的控制,都要进行充分考虑
前端
前端菜单:根据是否有请求菜单权限进行动态加载
按钮:根据是否有此权限点进行显示/隐藏的控制
后端
前端发送请求到后端接口,有必要对接口的访问进行权限的验证

<2>、权限设计

针对这样的需求,在有些设计中可以将菜单,按钮,后端API请求等作为资源,这样就构成了基于RBAC的另一种授权模型(用户-角色-权限-资源)。在SaaS-HRM系统的权限设计中就是使用此方案
在这里插入图片描述
针对此种权限模型,其中权限究竟是属于菜单,按钮,还是API的权限呢?那就需要在设计数据库权限表的时候添加类型加以区分(如权限类型1为菜单 2为功能 3为API)

<3>、表结构分析

在这里插入图片描述
这里要注意的是,权限表与权限菜单表、页面元素表与API接口表都是一对一的关系
与传统的RBAC模型对比不难发现此种设计的好处:
1、不需要区分哪些是操作,哪些是资源
2、方便扩展、当系统要对新的东西进行权限控制时,我们只需要建立一个新的资源表,并确定这类权限的权限类型标识即可

4、用户管理

<1>、需求分析

用户其实就是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
<2>、配置系统微服务

1、创建系统微服务模块(ihrm_system),引入相关依赖

	<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>
		<dependency>
			<groupId>com.ihrm</groupId>
			<artifactId>ihrm_common_model</artifactId>
			<version>1.0-SNAPSHOT</version>
		</dependency>
	</dependencies>

2、配置application.yml

#服务配置
server:
  port: 9002
#spring配置
spring:
  #1.应用配置
  application:
    name: ihrm-system #指定服务名
  #2.数据库连接池
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/ihrm?useUnicode=true&characterEncoding=utf8
    username: root
    password: 12345678
  #3.JPA
  jpa:
    database: MySQL
    show-sql: true
    open-in-view: true
# JWT的相关配置
jwt:
  config:
    key: saas-ihrm
    ttl: 3600000

3、配置启动类

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;

//1.配置springboot的包扫描
@SpringBootApplication(scanBasePackages = "com.ihrm")
//2.配置jpa注解的扫描
@EntityScan(value="com.ihrm.domain.system")
public class SystemApplication {
    /**
     * 启动方法
     */
    public static void main(String[] args) {
        SpringApplication.run(SystemApplication.class,args);
    }

    @Bean
    public IdWorker idWorker() {
        return new IdWorker();
    }
}
<3>、后端用户基本操作

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;

    /**
     * level
     *     String
     *          saasAdmin:saas管理员具备所有权限
     *          coAdmin:企业管理(创建租户企业的时候添加)
     *          user:普通用户(需要分配角色)
     */
    private String level;

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

2、DAO层

package com.ihrm.system.dao;

import com.ihrm.domain.system.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

public interface UserDao extends JpaRepository<User,String>,JpaSpecificationExecutor<User> {
}

3、Service层

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.apache.shiro.crypto.hash.Md5Hash;
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.util.StringUtils;

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 UserDao userDao;

    @Autowired
    private RoleDao roleDao;

    @Autowired
    private IdWorker idWorker;


    /**
     * 根据mobile查询用户
     */
    public User findByMobile(String mobile) {
        return userDao.findByMobile(mobile);
    }

    /**
     * 1.保存用户
     */
    public void save(User user) {
        //设置主键的值
        String id = idWorker.nextId()+"";
        String password = new Md5Hash("123456", user.getMobile(), 3).toString();
        user.setLevel("user");
        user.setPassword(password);//设置初始密码
        user.setEnableState(1);
        user.setId(id);
        //调用dao保存部门
        userDao.save(user);
    }

    /**
     * 2.更新用户
     */
    public void update(User user) {
        //1.根据id查询部门
        User target = userDao.findById(user.getId()).get();
        //2.设置部门属性
        target.setUsername(user.getUsername());
        target.setPassword(user.getPassword());
        target.setDepartmentId(user.getDepartmentId());
        target.setDepartmentName(user.getDepartmentName());
        //3.更新部门
        userDao.save(target);
    }

    /**
     * 3.根据id查询用户
     */
    public User findById(String id) {
        return userDao.findById(id).get();
    }

    /**
     * 4.查询全部用户列表
     *      参数:map集合的形式
     *          hasDept
     *          departmentId
     *          companyId
     *
     */
    public Page findAll(Map<String,Object> map,int page, int size) {
        //1.需要查询条件
        Specification<User> spec = new Specification<User>() {
            /**
             * 动态拼接查询条件
             */
            public Predicate toPredicate(Root<User> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                List<Predicate> list = new ArrayList<>();
                //根据请求的companyId是否为空构造查询条件
                if(!StringUtils.isEmpty(map.get("companyId"))) {
                    list.add(criteriaBuilder.equal(root.get("companyId").as(String.class),(String)map.get("companyId")));
                }
                //根据请求的部门id构造查询条件
                if(!StringUtils.isEmpty(map.get("departmentId"))) {
                    list.add(criteriaBuilder.equal(root.get("departmentId").as(String.class),(String)map.get("departmentId")));
                }
                if(!StringUtils.isEmpty(map.get("hasDept"))) {
                    //根据请求的hasDept判断  是否分配部门 0未分配(departmentId = null),1 已分配 (departmentId != null)
                    if("0".equals((String) map.get("hasDept"))) {
                        list.add(criteriaBuilder.isNull(root.get("departmentId")));
                    }else {
                        list.add(criteriaBuilder.isNotNull(root.get("departmentId")));
                    }
                }
                return criteriaBuilder.and(list.toArray(new Predicate[list.size()]));
            }
        };

        //2.分页
        Page<User> pageUser = userDao.findAll(spec, new PageRequest(page-1, size));
        return pageUser;
    }

    /**
     * 5.根据id删除用户
     */
    public void deleteById(String id) {
        userDao.deleteById(id);
    }

    /**
     * 分配角色
     */
    public void assignRoles(String userId,List<String> roleIds) {
        //1.根据id查询用户
        User user = userDao.findById(userId).get();
        //2.设置用户的角色集合
        Set<Role> roles = new HashSet<>();
        for (String roleId : roleIds) {
            Role role = roleDao.findById(roleId).get();
            roles.add(role);
        }
        //设置用户和角色集合的关系
        user.setRoles(roles);
        //3.更新用户
        userDao.save(user);
    }
}

4、Controller层

//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(companyId);
        user.setCompanyName(companyName);
        //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",companyId);
        //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) {
        // 添加 roleIds (用户已经具有的角色id数组)
        User user = userService.findById(id);
        UserResult userResult = new UserResult(user);
        return new Result(ResultCode.SUCCESS, userResult);
    }

    /**
     * 修改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删除
     */
    @RequiresPermissions(value = "API-USER-DELETE")
    @RequestMapping(value = "/user/{id}", method = RequestMethod.DELETE,name = "API-USER-DELETE")
    public Result delete(@PathVariable(value = "id") String id) {
        userService.deleteById(id);
        return new Result(ResultCode.SUCCESS);
    }
}
<4>、前端用户基本操作
(1)、配置接口请求路径

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

	'/api/sys': {
	  target: 'http://localhost:9002/sys',
	  changeOrigin: true,
	  pathRewrite: {
	    '^/api/sys': ''
	  }
	},
(2)、导入员工模块

注册模块

 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)
(3)、用户列表展示

1、页面代码

<el-table :data="dataList" fit style="width: 100%;" border>
<el-table-column type="index" :index="1" label="序号" width="150"> </el-table-
<el-table-column sortable prop="username" label="姓名" width="150"></el-table- <el-table-column sortable prop="mobile" label="手机号" width="150"></el-table- <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="handleDelete(scope.row)" type="text" size="small">删除
</el-button>
            </template>
          </el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination">
          <PageTool :paginationPage="requestParameters.page"
:paginationPagesize="requestParameters.pagesize" :total="counts"
@pageChange="handleCurrentChange" @pageSizeChange="handleSizeChange">
          </PageTool>
        </div>

2、构造数据

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'
var _this = null
export default {
  name: 'employeesList',
  components: {
    PageTool,employeesAdd
  },
  data() {
    return {
      employeesAdd: 'employeesAdd',
      baseData: constantApi,
      dataList: [],
      counts: '',
       requestParameters:{
        page: 1,
        pagesize: 10,
} }
}, methods: {
// 业务方法 
doQuery(params) {
        list(this.requestParameters).then(res => {
          this.dataList = res.data.data.rows
          this.counts = res.data.data.total
		}) 
	}
},
// 创建完毕状态 
created: function() {
    this.doQuery()
  },
}
(4)、用户详情

1、配置路由

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

2、完成用户详情界面

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

5、角色管理

<1>、需求分析

实现角色的基本CRUD操作

<2>、后端分析

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

2、DAO层

package com.ihrm.system.dao;

import com.ihrm.domain.system.Role;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

/**
  * 企业数据访问接口
  */
public interface RoleDao extends JpaRepository<Role, String>, JpaSpecificationExecutor<Role> {
}

3、Service层

package com.ihrm.system.service;

import com.ihrm.common.service.BaseService;
import com.ihrm.common.utils.IdWorker;
import com.ihrm.common.utils.PermissionConstants;
import com.ihrm.domain.system.Permission;
import com.ihrm.domain.system.Role;
import com.ihrm.system.dao.PermissionDao;
import com.ihrm.system.dao.RoleDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import java.util.*;

/**
 * 角色操作业务逻辑层
 */
@Service
public class RoleService extends BaseService {

    @Autowired
    private IdWorker idWorker;

    @Autowired
    private RoleDao roleDao;

    @Autowired
    private PermissionDao permissionDao;

    /**
     * 分配权限
     */
    public void assignPerms(String roleId,List<String> permIds) {
        //1.获取分配的角色对象
        Role role = roleDao.findById(roleId).get();
        //2.构造角色的权限集合
        Set<Permission> perms = new HashSet<>();
        for (String permId : permIds) {
            Permission permission = permissionDao.findById(permId).get();
            //需要根据父id和类型查询API权限列表
            List<Permission> apiList = permissionDao.findByTypeAndPid(PermissionConstants.PERMISSION_API, permission.getId());
            perms.addAll(apiList);//自定赋予API权限
            perms.add(permission);//当前菜单或按钮的权限
        }
        System.out.println(perms.size());
        //3.设置角色和权限的关系
        role.setPermissions(perms);
        //4.更新角色
        roleDao.save(role);
    }

    /**
     * 添加角色
     */
    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 List<Role> findAll(String companyId) {
        return roleDao.findAll(getSpec(companyId));
    }

    /**
     * 删除角色
     */
    public void delete(String id) {
        roleDao.deleteById(id);
    }

    /**
     * 分页查询
     */
    public Page<Role> findByPage(String companyId, int page, int size) {
        return roleDao.findAll(getSpec(companyId), PageRequest.of(page-1, size));
    }
}

4、Controller层

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.Role;
import com.ihrm.domain.system.response.RoleResult;
import com.ihrm.system.service.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/sys")
public class RoleController extends BaseController{
    
    @Autowired
    private RoleService roleService;

    /**
     * 分配权限
     */
    @RequestMapping(value = "/role/assignPrem", method = RequestMethod.PUT)
    public Result assignPrem(@RequestBody Map<String,Object> map) {
        //1.获取被分配的角色的id
        String roleId = (String) map.get("id");
        //2.获取到权限的id列表
        List<String> permIds = (List<String>) map.get("permIds");
        //3.调用service完成权限分配
        roleService.assignPerms(roleId,permIds);
        return new Result(ResultCode.SUCCESS);
    }


    //添加角色
    @RequestMapping(value = "/role", method = RequestMethod.POST)
    public Result add(@RequestBody Role role) throws Exception {
        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);
        RoleResult roleResult = new RoleResult(role);
        return new Result(ResultCode.SUCCESS,roleResult);
    }

    /**
     * 分页查询角色
     */
    @RequestMapping(value = "/role", method = RequestMethod.GET)
    public Result findByPage(int page,int pagesize,Role role) throws Exception {
        Page<Role> searchPage = roleService.findByPage(companyId, page, pagesize);
        PageResult<Role> pr = new PageResult(searchPage.getTotalElements(),searchPage.getContent());
        return new Result(ResultCode.SUCCESS,pr);
    }

    @RequestMapping(value="/role/list" ,method=RequestMethod.GET)
    public Result findAll() throws Exception {
        List<Role> roleList = roleService.findAll(companyId);
        return new Result(ResultCode.SUCCESS,roleList);
    }
}
手把手视频详细讲解项目开发全过程,需要的小伙伴自行百度网盘下载,链接见附件,永久有效。 项目介绍: iHRM是一款基于SaaS平台的人力资源管理系统, 企业通过该系统可以完成员工管理、审批管理、考勤管理、社保公积金管理、薪资管理等功能,为企业的人力资源管理提供一站式解决方案。 掌握的核心能力:   1、能够了解SaaS的基本概念   2、掌握Activiti7工作流引擎的使用   3、能够掌握商用权限方案的设计   4、能够使用JasperReport生成报表。   5、能够使用PowerDesigner构建数据库模型   6、了解SAAS-HRM中权限控制   7、理解前端权限控制思路   8、熟练构造Chart图形报表 解决方法:   1、Spring全家桶解决方案|   2、SPA工程构建解决方案|   3、Saas系统数据库设计方案|   4、统一会话管理的解决方案|   5、企业级报表解决方案|   6、系统认证授权的解决方案|   7、云存储解决方案|   8、RBAC权限设计方案|   9、刷脸登录解决方案|   10、自定义代码生成器|   11、Activiti工作流开发| 涵盖知识点:   1.结合Activiti7工作流引擎的应用教程。   2.基于Shiro+Redis的分布式session解决方案。   3.可商用的权限设计方案(提供菜单,按钮,超链接,API粒度的权限控制)。   4.完整的代码生成器教程。   5.采用JasperReport完成企业级PDF报表生成。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值