今日内容

  • 数据库设计
  • 部门管理
  • 用户管理
  • 模块管理
  • RBAC权限模式
  • 角色管理


第一章 数据库设计( 理解 )

1. 多租户数据库设计

1.1 多租户技术介绍

传统软件模式,指客户通过买断的方式获取软件,将软件部署在企业内部,不同的企业各自部署一套自己的软件系统

SaaS模式,指客户购买的是软件提供出来的服务而不是软件,软件统一部署到服务提供商的服务器上,用户只有使用权

那么在SaaS模式下,就要考虑到如何保证多个用户数据存储的隔离和安全问题,这样就有了多租户技术




python 多租户架构设计 多租户系统权限设计_大数据


多租户技术是一种软件架构技术,目的是确保多个用户信息存储在一个数据中心的情况下,数据的隔离和安全问题

简单讲:在一台服务器上运行应用实例为多个用户提供服务,并且要尽量保证多用户数据的隔离。

1.2 多租户数据库设计方案

目前基于多租户的数据库设计方案通常有如下三种:

  1. 独立服务器、独立库:每个租户一个数据库服务器
  2. 共享服务器、独立库:多个租户使用同一个数据库服务器,但是每个租户一个数据库
  3. 共享服务器、共享库:租户数据同库同表,使用特定标识区分数据所属租户

三种方案对比下来:开发、部署、维护的成本依次降低,数据的安全、隔离性依也次降低

在SAAS-Export平台中,处于教学的目的,采用的是共享服务器、共享库方式设计。

2. 数据库设计三范式与反三范式

范式指的是数据库表的设计规则,好的设计可以减少数据冗余,提高硬盘利用率

2.1 三范式(1970 硬盘贵)
  1. 第一范式(1NF):确保每一列的原子性,做到每列不可拆分(每一列都表述一个独立完整内容)
  2. 第二范式(2NF):每张表只描述一件事
  3. 第三范式(3NF):消除推导关系(凡是可以通过其它列推导出来的列就不要再出现在数据表的设计中 ,计算结果等内容)
2.2 反三范式(用户体验 贵)

反三范式是基于第三范式所调整的,它指的是: 有时为了提高查询效率,可以适当保留冗余数据

3. 数据库建模

了解了数据的设计思想,那对于数据库表的表设计应该怎么做呢?答案是数据库建模

数据库建模:在设计数据库时,对现实世界进行分析、抽象、并从中找出内在联系,进而确定数据库的结构。它主要包括两部分内容:确定最基本的数据结构;对约束建模。

3.1 建模工具

对于数据模型的建模,最有名的要数PowerDesigner,PowerDesigner是在中国软件公司中非常有名的,其易用性、功能、对流行技术框架的支持、以及它的模型库的管理理念,都深受设计师们喜欢。他的优势在于:不用在使用create table等语句创建表结构,数据库设计人员只关注如何进行数据建模即可,将来的数据库语句,可以自动生成

3.2 使用PD建模

(1) 选择新建数据库模型

打开PowerDesigner,文件->建立新模型->model types(选择类型)->Physical Data Model(物理模型)


python 多租户架构设计 多租户系统权限设计_mysql_02


(2) 控制面板


python 多租户架构设计 多租户系统权限设计_java_03


(3) 创建数据库表

点即面板按钮中的创建数据库按钮创建数据库模型:


python 多租户架构设计 多租户系统权限设计_mysql_04


切换columns标签,可以对表中的所有字段进行配置



python 多租户架构设计 多租户系统权限设计_java_05


(4) 导出sql

菜单->数据库(database)->生成数据库表结构(GenerateDatabase)


python 多租户架构设计 多租户系统权限设计_java_06


第二章 部门管理

1. 需求和分析

1.1 需求描述

对部门进行基本CRUD操作

1.2 数据模型分析


python 多租户架构设计 多租户系统权限设计_大数据_07


2. 代码实现

2.1 domain

在domain模块的com.itheima.domain.system包下建立Dept

@Data
public class Dept implements Serializable {
    private String id;
    private String deptName;
    private Dept parent;//注意这个字段, 代表的是父部门
    private Integer state;
    private String companyId;
    private String companyName;
}
2.2 dao

在dao模块的com.itheima.dao.system包下建立DeptDao接口

package com.itheima.dao.system;


import com.itheima.domain.system.Dept;

import java.util.List;

/**
 * 创建dao接口
 */
public interface DeptDao {
	/**
	 * 查询列表
	 */
	// 此id用于做企业隔离
	List<Dept> findAll(String companyId);

	void save(Dept dept);

	Dept findById(String id);

	void update(Dept dept);

	void deleteById(String id);	
}

在dao模块的resources/com/itheima/dao/system包下建立DeptDao.xml文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.dao.system.DeptDao">
    <!--自定义结果集映射-->
    <resultMap id="BaseResultMap" type="com.itheima.domain.system.Dept">
        <id property="id" column="dept_id"/>
        <result property="deptName" column="dept_name"/>

        <result property="state" column="state"/>
        <result property="companyId" column="company_id"/>
        <result property="companyName" column="company_name"/>

        <!--这个有问题  暂时不处理-->
        <!--        <result property="parent" column="parent_id"/>-->
    </resultMap>


    <update id="update">
        update pe_dept
        <set>
            <if test="deptName != null and deptName != ''">
                dept_name=#{deptName},
            </if>
            <if test="parent.id != null and parent.id != ''">
                parent_id=#{parent.id},
            </if>
            <if test="state != null">
                state=#{state},
            </if>
            <if test="companyId != null and companyId != ''">
                company_id=#{companyId},
            </if>
            <if test="companyName != null and companyName != ''">
                company_name=#{companyName},
            </if>
        </set>
        where dept_id = #{id}
    </update>

    <delete id="deleteById">
        delete from  pe_dept where dept_id = #{id}
    </delete>

    <select id="findAll" resultMap="BaseResultMap">
        select * from pe_dept where company_id = #{companyId}
    </select>


    <select id="findById" resultMap="BaseResultMap">
        select * from pe_dept where dept_id = #{id}
    </select>



    <insert id="save">
         insert into pe_dept (
            dept_id,
            dept_name,
            parent_id,
            state,
            company_id,
            company_name
         ) values (
            #{id},
            #{deptName},
            #{parent.id},
            #{state},
            #{companyId},
            #{companyName}
         );
    </insert>
</mapper>
2.3 service

在service模块的com.itheima.service.system包下建立DeptService接口

package com.itheima.service.system;

import com.github.pagehelper.PageInfo;
import com.itheima.domain.system.Dept;

import java.util.List;

public interface DeptService {

    List<Dept> findAll(String companyId);

    void save(Dept dept);

    Dept findById(String id);

    void update(Dept dept);

    void deleteById(String id);

    PageInfo findByPage(String companyId, Integer pageNum, Integer pageSize);

}

在service模块的com.itheima.service.system.impl包下建立DeptServiceImpl实现类

package com.itheima.service.system.impl;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.itheima.dao.system.DeptDao;
import com.itheima.domain.system.Dept;
import com.itheima.service.system.DeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
@Service
public class DeptServiceImpl implements DeptService {
	
	@Autowired
	private DeptDao deptDao;

	@Override
	public List<Dept> findAll(String companyId) {

		return deptDao.findAll(companyId);
	}

	@Override
	public void save(Dept dept) {
		deptDao.save(dept);
	}

	@Override
	public Dept findById(String id) {
		return deptDao.findById(id);
	}

	@Override
	public void update(Dept dept) {
		deptDao.update(dept);
	}

	@Override
	public void delete(String id) {
		deptDao.deleteById(id);
	}

	@Override
	public PageInfo findByPage(String companyId,Integer pageNum, Integer pageSize) {
		//1. 设置pageNum和pageSize
		PageHelper.startPage(pageNum,pageSize);

		//2. 调用一个查询所有的方法
		List<Dept> list = deptDao.findAll(companyId);

		//3. 直接返回PageInfo
		return new PageInfo(list,10);
	}
}

3. 部门列表

3.1 代码开发

在web模块的com.itheima.web.controller.system包下建立DeptController

package com.itheima.web.controller.system;

import com.github.pagehelper.PageInfo;
import com.itheima.domain.system.Dept;
import com.itheima.service.system.DeptService;
import com.itheima.web.controller.BaseController;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.UUID;

@Controller
@RequestMapping("/system/dept")
public class DeptController extends BaseController {

    //todo 模拟一下企业Id和企业name,等到登录之后,再改成真的
    public String getCompanyId() {
        return "1";
    }

    public String getCompanyName() {
        return "大脸猫皮具外贸有限公司";
    }


    @Autowired
    private DeptService deptService;

    @RequestMapping(value = "/list", name = "部门列表查询")
    public String list(
            @RequestParam(defaultValue = "1", name = "page") Integer pageNum,
            @RequestParam(defaultValue = "10") Integer pageSize) {

        PageInfo pageInfo = deptService.findByPage(getCompanyId(), pageNum, pageSize);
        request.setAttribute("page", pageInfo);

        return "/system/dept/dept-list";
    }
}
3.2 父部门不显示
3.2.1 问题分析


python 多租户架构设计 多租户系统权限设计_mysql_08


3.2.2 解决方案
<association property="parent" column="parent_id" select="findById" />

4. 新增部门

4.1 代码开发
@RequestMapping(value = "/toAdd", name = "跳转部门新增页面")
public String toAdd() {
    //1. 查询所有部门
    List<Dept> deptList = deptService.findAll(getCompanyId());
    request.setAttribute("deptList",deptList);

    return "/system/dept/dept-add";
}

//新增和修改统一用一个方法处理
@RequestMapping(value = "/edit", name = "部门新增")
public String edit(Dept dept) {
    if (StringUtils.isEmpty(dept.getId())) {
        //1. 设置主键
        dept.setId(UUID.randomUUID().toString());

        //2. 设置企业信息
        dept.setCompanyId(getCompanyId());
        dept.setCompanyName(getCompanyName());

        deptService.save(dept);
    } else {
        deptService.update(dept);
    }

    //重定向到list方法
    return "redirect:/system/dept/list.do";
}
4.2 新增顶级部门时报错
4.2.1 问题分析
Error updating database.  Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot add or update a child row: a foreign key constraint fails (`saas-export`.`pe_dept`, CONSTRAINT `SYS_C005596` FOREIGN KEY (`parent_id`) REFERENCES `pe_dept` (`dept_id`))
   
问题定位:
     如果新增或者更新为顶级部门, 前台传递的parent_id为"", 而这一列有外键约束, 不能为""  但是可以为null
4.2.2 解决方案
/**
 * 新增,修改功能
 */
@RequestMapping(value = "/edit", name = "部门新增")
public String edit(Dept dept) {
   // 如果父id为"",设置为null
   if (StringUtils.isEmpty(dept.getParent().getId())){
      dept.getParent().setId(null);
   }
   if (StringUtils.isEmpty(dept.getId())) {
      //1. 设置主键
      dept.setId(UUID.randomUUID().toString());

      //2. 设置企业信息
      dept.setCompanyId(getCompanyId());
      dept.setCompanyName(getCompanyName());

      deptService.save(dept);
   } else {
      deptService.update(dept);
   }

   //重定向到list方法
   return "redirect:/system/dept/list.do";
}

5. 修改部门

5.1 代码开发
@RequestMapping(value = "/toUpdate", name = "跳转部门编辑页面")
public String toUpdate(String id) {
    //1. 根据id查询当前部门信息
    Dept dept = deptService.findById(id);
    request.setAttribute("dept", dept);

    //2. 查询所有部门
    List<Dept> deptList = deptService.findAll(getCompanyId());
    request.setAttribute("deptList", deptList);

    //3. 转发到修改页面
    return "/system/dept/dept-update";
}
5.2 修改部门为顶级部门时失败
5.2.1 问题分析


python 多租户架构设计 多租户系统权限设计_mysql_09


5.2.1 解决方案

去掉此项的非空判断

<update id="update">
    update pe_dept
    <set>
        <if test="deptName != null and deptName != ''">
            dept_name=#{deptName},
        </if>

            parent_id=#{parent.id},
        
        <if test="state != null">
            state=#{state},
        </if>
        <if test="companyId != null and companyId != ''">
            company_id=#{companyId},
        </if>
        <if test="companyName != null and companyName != ''">
            company_name=#{companyName},
        </if>
    </set>
    where dept_id = #{id}
</update>

6. 删除部门

6.1 代码开发
@RequestMapping(value = "/delete", name = "部门删除")
public String delete(String id) {
    //调用service删除
    deptService.deleteById(id);

    //重定向到list方法
    return "redirect:/system/dept/list.do";
}
6.2 如果一个部门有子部门, 删除报错
6.2.1 问题分析

由于部门表是自关联的设计。就表示我们在直接删除父部门时成功了,子部门数据其实就变成了孤儿,所以外键会约束,不让删除

6.2.2 解决方案
  1. 在删除部门数据时,做一个判断,如果该部门有子部门,做出友情提示,不允许删除…【异步实现 课下作业】
  2. 级联操作: 连同子(孙子)部门一起删除


python 多租户架构设计 多租户系统权限设计_python 多租户架构设计_10


补充知识:
	CASCADE:  父表delete、update的时候,子表会delete、update掉关联记录;
	SET NULL: 父表delete、update的时候,子表会将关联记录的外键字段所在列设为null,所以注意在设计子表时外键不能设为not null;
	RESTRICT: 如果想要删除父表的记录时,而在子表中有关联该父表的记录,则不允许删除父表中的记录;
	NO ACTION:同 RESTRICT,也是首先先检查外键;

第三章 用户管理

1. 需求和分析

对用户进行基本CRUD操作

2. 代码实现

2.1 domain

在domain模块的com.itheima.domain.system包下建立User

package com.itheima.domain.system;

import lombok.Data;

import java.io.Serializable;
import java.util.Date;

@Data
public class User implements Serializable {

    private String id; // 用户id
    private String deptId; // 部门id
    private String email; // 邮箱
    private String userName; //用户名
    private String station; // 职务名称
    private String password; // 密码
    private String state; // 状态  1:可用、0:不可用
    private String companyId; // 企业id
    private String companyName; // 企业名称
    private String deptName;  // 部门名称
    private String managerId; // 上级id
    private String gender; // 性别
    private String telephone; // 手机号
    private String birthday; // 生日

    /*
       0-saas管理员
       1-企业管理员
       2-总经理
       3-部门经理
       4-普通员工
   */
    private Integer degree; // 等级
    private Double salary; // 工资
    private String joinDate; // 入职日期
    private Integer orderNo; // 排序号
    private String createBy; // 创建人
    private String createDept; // 创建人所在部门
    private Date createTime; // 创建时间
    private String updateBy; // 更新人
    private Date updateTime; // 更新时间
    private String remark; // 备注
}
2.2 dao

在dao模块的com.itheima.domain.system包下建立UserDao接口

public interface UserDao {
    
    List<User> findAll(String companyId);

    void save(User user);

    User findById(String id);

    void update(User user);

    void delete(String id);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.itheima.dao.system.UserDao">

    <resultMap id="BaseResultMap" type="com.itheima.domain.system.User">
        <id column="user_id" property="id"/>
        <result column="dept_id" property="deptId"/>
        <result column="email" property="email"/>
        <result column="user_name" property="userName"/>
        <result column="password" property="password"/>
        <result column="state" property="state"/>
        <result column="company_id" property="companyId"/>
        <result column="company_name" property="companyName"/>
        <result column="dept_name" property="deptName"/>
        <result column="manager_id" property="managerId"/>
        <result column="join_date" property="joinDate"/>
        <result column="salary" property="salary"/>
        <result column="birthday" property="birthday"/>
        <result column="gender" property="gender"/>
        <result column="station" property="station"/>
        <result column="telephone" property="telephone"/>
        <result column="degree" property="degree"/>
        <result column="remark" property="remark"/>
        <result column="order_no" property="orderNo"/>
    </resultMap>

    <select id="findAll" resultMap="BaseResultMap">
      select * from pe_user where company_id=#{companyId} order by order_no
    </select>

    <select id="findById" resultMap="BaseResultMap">
      select  * from pe_user where user_id = #{id}
    </select>

    <insert id="save">
        insert into pe_user (user_id, dept_id, email, user_name, password, state, company_id,
        company_name, dept_name, manager_id, join_date, salary, birthday,
        gender, station, telephone, degree, remark, order_no)
        values (#{id}, #{deptId}, #{email}, #{userName}, #{password}, #{state}, #{companyId},
        #{companyName}, #{deptName}, #{managerId}, #{joinDate}, #{salary}, #{birthday},
        #{gender}, #{station}, #{telephone}, #{degree}, #{remark}, #{orderNo})
    </insert>

    <update id="update">
        update pe_user
        <set>
            <if test="deptId!=null and deptId!=''">
                dept_id =#{deptId} ,
            </if>
            <if test="email!=null and email!=''">
                email =#{email} ,
            </if>
            <if test="userName!=null and userName!=''">
                user_name =#{userName} ,
            </if>
            <if test="station!=null and station!=''">
                station =#{station} ,
            </if>
            <if test="password!=null and password!=''">
                password =#{password} ,
            </if>
            <if test="state!=null and state!=''">
                state =#{state} ,
            </if>
            <if test="companyId!=null and companyId!=''">
                company_id =#{companyId} ,
            </if>
            <if test="companyName!=null and companyName!=''">
                company_name=#{companyName} ,
            </if>
            <if test="deptName!=null and deptName!=''">
                dept_name =#{deptName} ,
            </if>
            <if test="managerId!=null and managerId!=''">
                manager_id =#{managerId} ,
            </if>
            <if test="gender!=null and gender!=''">
                gender =#{gender} ,
            </if>
            <if test="telephone!=null and telephone!=''">
                telephone =#{telephone} ,
            </if>
            <if test="birthday!=null and birthday!=''">
                birthday =#{birthday} ,
            </if>
            <if test="degree!=null and degree!=''">
                degree =#{degree} ,
            </if>
            <if test="salary!=null and salary!=''">
                salary =#{salary} ,
            </if>
            <if test="joinDate!=null and joinDate!=''">
                join_date =#{joinDate} ,
            </if>
            <if test="orderNo!=null and orderNo!=''">
                order_no =#{orderNo} ,
            </if>
            <if test="createBy!=null and createBy!=''">
                create_by =#{createBy} ,
            </if>
            <if test="createDept!=null and createDept!=''">
                create_dept =#{createDept} ,
            </if>
            <if test="createTime!=null and createTime!=''">
                create_time =#{createTime} ,
            </if>
            <if test="updateBy!=null and updateBy!=''">
                update_by =#{updateBy} ,
            </if>
            <if test="updateTime!=null and updateTime!=''">
                update_time =#{updateTime} ,
            </if>
            <if test="remark!=null and remark!=''">
                remark =#{remark},
            </if>
        </set>
        where user_id = #{id}
    </update>

    <delete id="delete">
      delete from pe_user where user_id = #{id}
    </delete>
</mapper>
2.3 service

在service模块的com.itheima.service.system包下建立UserService接口

package com.itheima.service.system;

import com.github.pagehelper.PageInfo;
import com.itheima.domain.system.User;

import java.util.List;


public interface UserService {

	List<User> findAll(String companyId);

	void save(User user);

	User findById(String id);

	void update(User user);

	void delete(String id);

	PageInfo findByPage(String companyId, Integer pageNum, Integer pageSize);
}

在service模块的com.itheima.service.system.impl包下建立UserServiceImpl实现类(复制DeptServiceImpl,修改)

package com.itheima.service.system.impl;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.itheima.dao.system.UserDao;
import com.itheima.domain.system.User;
import com.itheima.service.system.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class UserServiceImpl implements UserService {
	
	@Autowired
	private UserDao userDao;

	@Override
	public List<User> findAll(String companyId) {

		return userDao.findAll(companyId);
	}

	@Override
	public void save(User user) {
		userDao.save(user);
	}

	@Override
	public User findById(String id) {
		return userDao.findById(id);
	}

	@Override
	public void update(User user) {
		userDao.update(user);
	}

	@Override
	public void delete(String id) {
		userDao.delete(id);
	}

	@Override
	public PageInfo findByPage(String companyId,Integer pageNum, Integer pageSize) {
		//1. 设置pageNum和pageSize
		PageHelper.startPage(pageNum,pageSize);

		//2. 调用一个查询所有的方法
		List<User> list = userDao.findAll(companyId);

		//3. 直接返回PageInfo
		return new PageInfo(list,10);
	}
}

3. UserController

在web模块的com.itheima.web.controller.system包下建立UserController类(复制DeptController,修改)

package com.itheima.web.controller.system;

import cn.hutool.core.lang.UUID;
import com.github.pagehelper.PageInfo;
import com.itheima.domain.system.Dept;
import com.itheima.domain.system.User;
import com.itheima.service.system.DeptService;
import com.itheima.service.system.UserService;
import com.itheima.web.controller.BaseController;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;

@Controller
@RequestMapping("/system/user")
public class UserController extends BaseController {

	//todo 模拟一下企业Id和企业name,等到登录之后,再改成真的
	public String getCompanyId() {
		return "1";
	}

	public String getCompanyName() {
		return "大脸猫皮具外贸有限公司";
	}


	@Autowired
	private UserService userService;
	@Autowired
	private DeptService deptService;

	/**
	 * 生成列表
	 */
	@RequestMapping(value = "/list", name = "部门列表查询")
	public String list(
			@RequestParam(defaultValue = "1", name = "page") Integer pageNum,
			@RequestParam(defaultValue = "10") Integer pageSize) {


		PageInfo pageInfo = userService.findByPage(getCompanyId(), pageNum, pageSize);
		request.setAttribute("page", pageInfo);

		return "/system/user/user-list";
	}

	/**
	 * 跳转新增页面
	 */
	@RequestMapping(value = "/toAdd", name = "跳转部门新增页面")
	public String toAdd() {


		// 查询所在部门
		List<Dept> deptList = deptService.findAll(getCompanyId());
		request.setAttribute("deptList", deptList);

		return "/system/user/user-add";
	}
	/**
	 * 新增,修改功能
	 */
	@RequestMapping(value = "/edit", name = "部门新增")
	public String edit(User user) {

		if (StringUtils.isEmpty(user.getId())) {
			//1. 设置主键
			user.setId(UUID.randomUUID().toString());

			//2. 设置企业信息
			user.setCompanyId(getCompanyId());
			user.setCompanyName(getCompanyName());

			userService.save(user);
		} else {
			userService.update(user);
		}

		//重定向到list方法
		return "redirect:/system/user/list.do";
	}

	/**
	 * 跳转到修改
	 */
	@RequestMapping(value = "/toUpdate", name = "跳转部门编辑页面")
	public String toUpdate(String id) {
		//1. 根据id查询当前部门信息
		User user = userService.findById(id);
		request.setAttribute("user", user);

		//2. 查询所有部门
		List<Dept> deptList = deptService.findAll(getCompanyId());
		request.setAttribute("deptList", deptList);

		//3. 转发到修改页面
		return "/system/user/user-update";
	}
	/**
	 * 删除功能
	 */
	@RequestMapping(value = "/delete", name = "部门删除")
	public String delete(String id) {
		//调用service删除
		userService.delete(id);

		//重定向到list方法
		return "redirect:/system/user/list.do";
	}
}

4. 问题处理

4.1 新增用户时密码问题
4.1.1问题分析

当前密码是明文入库的

4.1.2解决方案

md5 sha1

/**
* 将明文密码转成MD5密码
* 123456> e10adc3949ba59abbe56e057f20f883e
* 123456 > e10adc3949ba59abbe56e057f20f883e
* 特性:
* 恒等性: 多次对同一个字符串加密,得到的结果是一样的
* 定长输出: 无论输入的字符串长度为多少,输出的长度都是一致的32
* 雪崩效应: 输入的字符串,一旦出现一点点改变,结果大不一样
* 不可逆性: 无法根据加密之后的密码推导出加密之前的密码
*/

使用MD5加密 + 加盐技术

public static void main(String[] args) {
   // 加密工具类[密码,盐,散列次数]
   String md5Pwd = new Md5Hash("123456", "abc@qq.com", 2).toString();
   System.out.println(md5Pwd);
}
if (StringUtils.isNotEmpty(user.getPassword())){
   String pwd = new Md5Hash(user.getPassword(),user.getEmail(),2).toString();
   user.setPassword(pwd);
}
4.2 修改用户时密码问题
4.2.1 问题分析

进入修改页面的时候, 程序会把加密之后的密码返显到页面

如果我们在修改页面上, 修改了密码提交, 密码就又变成了明文入库了

4.2.1 解决方案

回显密码的时候,直接回显为空 当点击更新按钮之后, 做判断:

1 如果依旧为空, 代表用户不想修改密码 , 后台不做处理

2 如果不为空, 代表用户想修改密码 , 后台在修改方法中做加密处理

User user = userService.findById(id);
user.setPassword(null);
request.setAttribute("user", user);

第四章 代码优化

目前在UserControllerDeptController中存在同一段代码,可以提取到BaseController

public class BaseController {
   @Autowired
   protected HttpServletRequest request;

   @Autowired
   protected HttpServletResponse response;

   @Autowired
   protected HttpSession session;


   public String getCompanyId() {
      return "1";
   }

   public String getCompanyName() {
      return "大脸猫皮具外贸有限公司";
   }
}

第五章 模块管理

1. 需求和分析

1.1 需求描述


python 多租户架构设计 多租户系统权限设计_mysql_11


1.2 模块的类型

这里的模块主要分为这样几种: 一级菜单、二级菜单、页面按钮

1.3 数据模型分析

模块是SaaS的人在管理,跟具体企业没有关系,所以不需要使用companyId区分


python 多租户架构设计 多租户系统权限设计_python 多租户架构设计_12


2. 代码复制

2.1 domain
@Data
public class Module implements Serializable {
    private String id;
    private String parentId;
    private String parentName;
    private String name;
    private int layerNum;
    private int isLeaf;
    private String ico;
    private String cpermission;
    private String curl;
    private String ctype;//主菜单 二级菜单 按钮
    private String state;
    private String belong;//属于SaaS还是企业
    private String cwhich;
    private int quoteNum;
    private String remark;
    private int orderNo;
}
2.2 dao
public interface ModuleDao {
    List<Module> findAll();

    void save(Module module);

    Module findById(String id);

    void update(Module module);

    void delete(String id);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >

<mapper namespace="com.itheima.dao.system.ModuleDao" >

    <resultMap id="BaseResultMap" type="com.itheima.domain.system.Module">
        <id column="module_id" property="id"/>
        <result column="parent_id" property="parentId"/>
        <result column="parent_name" property="parentName"/>
        <result column="name" property="name"/>
        <result column="layer_num" property="layerNum"/>
        <result column="is_leaf" property="isLeaf"/>
        <result column="ico" property="ico"/>
        <result column="cpermission" property="cpermission"/>
        <result column="curl" property="curl"/>
        <result column="ctype" property="ctype"/>
        <result column="state" property="state"/>
        <result column="belong" property="belong"/>
        <result column="cwhich" property="cwhich"/>
        <result column="quote_num" property="quoteNum"/>
        <result column="remark" property="remark"/>
        <result column="order_no" property="orderNo"/>
    </resultMap>
    
    
    <select id="findAll" resultMap="BaseResultMap">
        select * from ss_module order by ctype asc, order_no asc
    </select>

    <insert id="save">
        insert INTO ss_module(
        module_id,
        parent_id,
        parent_name,
        name,
        layer_num,
        is_leaf,
        ico,
        cpermission,
        curl,
        ctype,
        state,
        belong,
        cwhich,
        quote_num,
        remark,
        order_no
        )
        VALUES (
        #{id},
        #{parentId},
        #{parentName},
        #{name},
        #{layerNum},
        #{isLeaf},
        #{ico},
        #{cpermission},
        #{curl},
        #{ctype},
        #{state},
        #{belong},
        #{cwhich},
        #{quoteNum},
        #{remark},
        #{orderNo}
        )
    </insert>

    <update id="update">
        update ss_module
        <set>
            <if test="parentId!=null and parentId!=''">
                parent_id  = #{parentId} ,
            </if>
            <if test="parentName!=null and parentName!=''">
                parent_name= #{parentName} ,
            </if>
            <if test="name!=null and name!=''">
                name       = #{name} ,
            </if>
            <if test="layerNum!=null and layerNum!=''">
                layer_num  = #{layerNum} ,
            </if>
            <if test="isLeaf!=null and isLeaf!=''">
                is_leaf    = #{isLeaf} ,
            </if>
            <if test="ico!=null and ico!=''">
                ico        = #{ico} ,
            </if>
            <if test="cpermission!=null and cpermission!=''">
                cpermission= #{cpermission} ,
            </if>
            <if test="curl!=null and curl!=''">
                curl       = #{curl} ,
            </if>
            <if test="ctype!=null and ctype!=''">
                ctype      = #{ctype} ,
            </if>
            <if test="state!=null and state!=''">
                state      = #{state} ,
            </if>
            <if test="belong!=null and belong!=''">
                belong     = #{belong} ,
            </if>
            <if test="cwhich!=null and cwhich!=''">
                cwhich     = #{cwhich} ,
            </if>
            <if test="quoteNum!=null and quoteNum!=''">
                quote_num  = #{quoteNum} ,
            </if>
            <if test="remark!=null and remark!=''">
                remark     = #{remark} ,
            </if>
            <if test="orderNo!=null and orderNo!=''">
                order_no   = #{orderNo} ,
            </if>
        </set>
        where module_id=#{id}
    </update>

    <select id="findById" resultMap="BaseResultMap">
        select * from ss_module where module_id=#{id}
    </select>
    
    <delete id="delete" >
        delete from ss_module where module_id=#{id}
    </delete>
</mapper>
2.3 service
public interface ModuleService {

    List<Module> findAll();

    void save(Module module);

    Module findById(String id);

    void update(Module module);

    void delete(String id);

    PageInfo<Module> findByPage(int pageNum, int pageSize);
}
package com.itheima.service.system.impl;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.itheima.dao.system.ModuleDao;
import com.itheima.domain.system.Module;
import com.itheima.service.system.ModuleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class ModuleServiceImpl implements ModuleService {

    @Autowired
    private ModuleDao moduleDao;

    @Override
    public List<Module> findAll() {
        return moduleDao.findAll();
    }

    @Override
    public void save(Module module) {
        moduleDao.save(module);
    }

    @Override
    public Module findById(String id) {
        return moduleDao.findById(id);
    }

    @Override
    public void update(Module module) {
        moduleDao.update(module);
    }

    @Override
    public void delete(String id) {
        moduleDao.delete(id);
    }

    public PageInfo<Module> findByPage(int pageNum, int pageSize) {
        PageHelper.startPage(pageNum, pageSize);
        List<Module> list = moduleDao.findAll();
        return new PageInfo<Module>(list, 5);
    }
}

3. ModuleController

在web模块的com.itheima.web.controller.system包下建立ModuleController

package com.itheima.web.controller.system;

import com.github.pagehelper.PageInfo;
import com.itheima.domain.system.Module;
import com.itheima.service.system.ModuleService;
import com.itheima.web.controller.BaseController;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;
import java.util.UUID;

@Controller
@RequestMapping("/system/module")
public class ModuleController extends BaseController {

    @Autowired
    private ModuleService moduleService;

    @RequestMapping(value = "/list", name = "模块列表查询")
    public String list(
            @RequestParam(defaultValue = "1", name = "page") Integer pageNum,
            @RequestParam(defaultValue = "10") Integer pageSize) {

        PageInfo pageInfo = moduleService.findByPage(pageNum, pageSize);
        request.setAttribute("page", pageInfo);

        return "/system/module/module-list";
    }


    @RequestMapping(value = "/toAdd", name = "跳转模块新增页面")
    public String toAdd() {
        //1. 查询所有模块
        List<Module> moduleList = moduleService.findAll();
        request.setAttribute("menus", moduleList);

        return "/system/module/module-add";
    }


    @RequestMapping(value = "/toUpdate", name = "跳转模块编辑页面")
    public String toUpdate(String id) {
        //1. 根据id查询当前模块信息
        Module module = moduleService.findById(id);
        request.setAttribute("module", module);

        //2. 查询所有模块
        List<Module> moduleList = moduleService.findAll();
        request.setAttribute("menus", moduleList);

        //3. 转发到修改页面
        return "/system/module/module-update";
    }

    //新增和修改统一用一个方法处理
    @RequestMapping(value = "/edit", name = "模块新增")
    public String edit(Module module) {

        if (StringUtils.isEmpty(module.getId())) {
            //1. 设置主键
            module.setId(UUID.randomUUID().toString());

            moduleService.save(module);
        } else {
            moduleService.update(module);
        }

        //重定向到list方法
        return "redirect:/system/module/list.do";
    }

    @RequestMapping(value = "/delete", name = "模块删除")
    public String delete(String id) {
        //调用service删除
        moduleService.delete(id);

        //重定向到list方法
        return "redirect:/system/module/list.do";
    }
}

第六章 RBAC权限模型(理解)

RBAC(全称:Role-Based Access Control)基于角色的权限访问控制。

在 RBAC 中,权限与角色相关联,用户通过成为适当角色的成员而得到这角色的权限。这就极大地简化了权限的管理。


python 多租户架构设计 多租户系统权限设计_python 多租户架构设计_13


第七章 角色管理

1. 需求和分析

1.1 需求描述

实现角色的 CRUD 操作,角色也是不同的企业管理自己管理的,所以也需要companyId做数据隔离


python 多租户架构设计 多租户系统权限设计_python 多租户架构设计_14


1.2 数据模型分析


python 多租户架构设计 多租户系统权限设计_大数据_15


2. 代码复制

2.1 domain
@Data
public class Role implements Serializable {

    private String id;
    private String name;
    private String remark;
    private String companyId;
    private String companyName;
    private Integer orderNo;
    private String createBy;
    private String createDept;
    private Date createTime;
    private String updateBy;
    private Date updateTime;
}
2.2 dao
public interface RoleDao {
    
    List<Role> findAll(String companyId);

    void save(Role role);

    Role findById(String id);

    void update(Role role);

    void delete(String id);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.itheima.dao.system.RoleDao" >

    <resultMap id="BaseResultMap" type="com.itheima.domain.system.Role">
        <id column="role_id" property="id"/>
        <result column="name" property="name"/>
        <result column="company_id" property="companyId"/>
        <result column="company_name" property="companyName"/>
        <result column="order_no" property="orderNo"/>
        <result column="create_by" property="createBy"/>
        <result column="create_dept" property="createDept"/>
        <result column="create_time" property="createTime"/>
        <result column="update_by" property="updateBy"/>
        <result column="update_time" property="updateTime"/>
        <result column="remark" property="remark"/>
    </resultMap>
    <select id="findAll" resultMap="BaseResultMap">
        select * from pe_role where company_id=#{companyId} order by order_no asc ,create_time desc
    </select>

    <insert id="save">
        insert INTO pe_role(
        role_id,
        name,
        company_id,
        company_name,
        order_no,
        create_by,
        create_dept,
        create_time,
        update_by,
        update_time,
        remark
        )
        VALUES (
        #{id},
        #{name} ,
        #{companyId} ,
        #{companyName} ,
        #{orderNo} ,
        #{createBy} ,
        #{createDept} ,
        #{createTime} ,
        #{updateBy} ,
        #{updateTime} ,
        #{remark}
        )
    </insert>

    <update id="update">
        update pe_role
        <set>
            <if test="name!=null and name!=''">
                name     =#{name} ,
            </if>
            <if test="companyId!=null and companyId!=''">
                company_id  =#{companyId} ,
            </if>
            <if test="companyName!=null and companyName!=''">
                company_name=#{companyName} ,
            </if>
            <if test="orderNo!=null and orderNo!=''">
                order_no    =#{orderNo} ,
            </if>
            <if test="createBy!=null and createBy!=''">
                create_by   =#{createBy} ,
            </if>
            <if test="createDept!=null and createDept!=''">
                create_dept =#{createDept} ,
            </if>
            <if test="createTime!=null and createTime!=''">
                create_time =#{createTime} ,
            </if>
            <if test="updateBy!=null and updateBy!=''">
                update_by   =#{updateBy} ,
            </if>
            <if test="updateTime!=null and updateTime!=''">
                update_time =#{updateTime} ,
            </if>
            <if test="remark!=null and remark!=''">
                remark      =#{remark},
            </if>
        </set>
        where role_id=#{id}
    </update>

    <select id="findById" resultMap="BaseResultMap">
        select * from pe_role where role_id=#{id}
    </select>

    <delete id="delete" parameterType="string">
        delete from pe_role where role_id=#{id}
    </delete>
</mapper>
2.3 service
public interface RoleService {

    List<Role> findAll(String companyId);

    void save(Role role);

    Role findById(String id);

    void update(Role role);

    void delete(String id);

    PageInfo<Role> findByPage(String companyId, int pageNum, int pageSize);
}
@Service
public class RoleServiceImpl implements RoleService  {

    @Autowired
    private RoleDao roleDao;

    @Override
    public List<Role> findAll(String companyId) {
        return roleDao.findAll(companyId);
    }

    @Override
    public void save(Role role) {
        roleDao.save(role);
    }

    @Override
    public Role findById(String id) {
        return  roleDao.findById(id);
    }

    @Override
    public void update(Role role) {
        roleDao.update(role);
    }

    @Override
    public void delete(String id) {
        roleDao.delete(id);
    }

    public PageInfo<Role> findByPage(String companyId,int pageNum, int pageSize) {
        PageHelper.startPage(pageNum,pageSize);
        List<Role> list = roleDao.findAll(companyId);
        return new PageInfo<Role>(list,5);
    }
}

3. RoleController

package com.itheima.web.controller.system;

import cn.hutool.core.lang.UUID;
import com.github.pagehelper.PageInfo;
import com.itheima.domain.system.Role;
import com.itheima.service.system.RoleService;
import com.itheima.web.controller.BaseController;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;

@Controller
@RequestMapping("/system/role")
public class RoleController extends BaseController {




	@Autowired
	private RoleService roleService;

	/**
	 * 生成列表
	 */
	@RequestMapping(value = "/list", name = "角色列表查询")
	public String list(
			@RequestParam(defaultValue = "1", name = "page") Integer pageNum,
			@RequestParam(defaultValue = "10") Integer pageSize) {

		PageInfo pageInfo = roleService.findByPage(getCompanyId(), pageNum, pageSize);
		request.setAttribute("page", pageInfo);

		return "/system/role/role-list";
	}

	/**
	 * 跳转新增页面
	 */
	@RequestMapping(value = "/toAdd", name = "跳转角色新增页面")
	public String toAdd() {
		//1. 查询所有角色
		List<Role> roleList = roleService.findAll(getCompanyId());
		request.setAttribute("roleList",roleList);

		return "/system/role/role-add";
	}
	/**
	 * 新增,修改功能
	 */
	//新增和修改统一用一个方法处理
	@RequestMapping(value = "/edit", name = "角色新增")
	public String edit(Role role) {
		if (StringUtils.isEmpty(role.getId())) {
			//1. 设置主键
			role.setId(UUID.randomUUID().toString());

			//2. 设置企业信息
			role.setCompanyId(getCompanyId());
			role.setCompanyName(getCompanyName());

			roleService.save(role);
		} else {
			roleService.update(role);
		}

		//重定向到list方法
		return "redirect:/system/role/list.do";
	}

	/**
	 * 跳转到修改
	 */
	@RequestMapping(value = "/toUpdate", name = "跳转角色编辑页面")
	public String toUpdate(String id) {
		//1. 根据id查询当前角色信息
		Role role = roleService.findById(id);
		request.setAttribute("role", role);

		//2. 查询所有角色
		List<Role> roleList = roleService.findAll(getCompanyId());
		request.setAttribute("roleList", roleList);

		//3. 转发到修改页面
		return "/system/role/role-update";
	}
	/**
	 * 删除功能
	 */
	@RequestMapping(value = "/delete", name = "角色删除")
	public String delete(String id) {
		//调用service删除
		roleService.delete(id);

		//重定向到list方法
		return "redirect:/system/role/list.do";
	}
}