1.saas系统

1.1 saas系统概念

SAAS:软件即服务

本系统是基于saas服务的。统一开放,维护,租户(注册付费的公司)需要在本系统中进行注册,并付费,然后根据付费情况使用系统功能。

1.2 saas平台数据库隔离设计

多租户数据库隔离的三种设计模式:

①: 针对每一个租户或者都设计单独的数据库:

在应用服务器中配制不同的数据源,或者使用不同的连接池。

优点:不同客户的数据物理分离,安全性比较好。

缺点:数据库连接的利用效率不高,技术难度大,需要动态的切换数据源。

 

② :共享数据库,独立的scheme(表)

这个方案基本是方案①的变种。同一个数据库下可以有多个Schema

优点:除了方案①的优点以外,共享数据源或连接池,效率更高。

缺点:数据库连接池开销会比较大,在mysql数据库中,schema概念不像oracle那么明了。

 

③ :共享数据库,共享scheme,共享数据库表,通过租户id进行区别:

所有租户的数据都存放在一个数据库的同一套表中, 在表中增加tenant_id标志字段,表明该记录是属于哪个租户的。

优点:数据源和数据库的管理都比较简单。和原来的应用没有差别。

缺点:数据权限比较复杂,增加程序的复杂性。如果应用比较复杂,很多数据表都需

要加入客户标志字段,很多查询都需要包括该字段,会比较麻烦。如果有遗漏,、特别是查询条件中遗漏该字段,就会造成一个客户看到另一个客户的数据。

1.3 代码实现(部分)

租户TenantMapper.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="cn.itsource.mapper.TenantMapper">
<!-- 保存部门 -->
<insert id="save" parameterType="Tenant" useGeneratedKeys="true"
keyProperty="id" keyColumn="id">
insert into
t_tenant(companyName,companyNum,registerTime,state,address,logo)
values(#{companyName},#{companyNum},#{registerTime},#{state},#{address},#{logo})
</insert>

<!-- 修改部门 -->
<update id="update" parameterType="Tenant">
update t_tenant set
companyName=#{companyName},companyNum=#{companyNum},registerTime=#{registerTime},
state=#{state},address=#{address},logo=#{logo}
where id=#{id}
</update>

<!-- 删除部门 -->
<delete id="remove" parameterType="long">
delete from t_tenant where
id=#{id}
</delete>

<!-- 通过部门id获取部门 -->
<select id="loadById" parameterType="long" resultType="Tenant">
select * from
t_tenant where d.id = #{id}
</select>

<!-- 获取所有部门 -->
<select id="loadAll" resultType="Tenant">
select * from t_tenant
</select>

</mapper>

EmployeeMapper.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="cn.itsource.mapper.EmployeeMapper">

<!-- 保存员工 -->
<insert id="save" parameterType="Employee" useGeneratedKeys="true"
keyProperty="id" keyColumn="id">
insert into
t_employee(username,realName,password,tel,email,dept_id,inputTime,state,type,tenant_id)
values(#{username},#{realName},#{password},#{tel},#{email},#{dept.id},#{inputTime},#{state},
#{type},#{tenant.id})
</insert>
<!-- 修改员工 -->
<update id="update" parameterType="Employee">
update t_employee set
username=#{username},realName=#{realName},password=#{password},tel=#{tel},
email=#{email},dept_id=#{dept.id},inputTime=#{inputTime},state=#{state},type=#{type},tenant_id=#{tenant.id}
where id=#{id}
</update>
<!-- 删除 -->
<delete id="romove" parameterType="long">
delete from t_employee where id=#{id}
</delete>


<select id="loadById" parameterType="long" resultType="Employee">
select * from
t_employee where id = #{id}
</select>
<!-- 获取所有员工 -->
<select id="loadAll" resultType="Employee">
select *
from t_employee
</select>
</mapper>

Service层:EmployeeServiceImpl

@Service
public class EmployeeServiceImpl extends BaseServiceImpl<Employee> implements IEmployeeService {

   @Autowired
   private TenantMapper tenantMapper;

   @Autowired
   private EmployeeMapper employeeMapper;
   @Override
   public void addTenantEmployee(Employee employee) {
       Tenant tenant = employee.getTenant();
       tenant.setRegisterTime(new Date());
       tenant.setState(0);
       //添加租户返回租户id   添加前对象里面没有id,添加完成后就有了
       tenantMapper.save(tenant);
       //把租户id设置给员工
       employee.setTenant(tenant);
       //在保存员工
       employee.setRealName(employee.getUsername());
       employeeMapper.save(employee);
  }

Controller层:EmployeeController

 */
@Controller
@RequestMapping("/employee") //定位资源
@CrossOrigin
public class EmployeeController implements BaseController<Employee> {
  @Autowired
  private IEmployeeService employeeService;

  //添加租户员工
  @RequestMapping(value = "/tenant",method = RequestMethod.PUT)
  @ResponseBody
  public AjaxResult addTenantEmployee(@RequestBody Employee employee){
      try {
          employeeService.addTenantEmployee(employee);
          return AjaxResult.me();
      } catch (Exception e) {
          e.printStackTrace();
          return AjaxResult.me().setSuccess(false).setMessage("入驻失败!"+e.getMessage());
      }
  }

前端:部分代码

密码验证:

            var validatePass2 = (rule, value, callback) => {
               console.log(value); //确认密码
               if (value === '') {
                   callback(new Error('请再次输入密码'))
              } else if (value !== this.employee.password) {
                   callback(new Error('两次输入密码不一致!'))
              } else {
                   callback()
              }
          }

methods:

        methods: {
           settledIn(){
               this.$refs.tenantForm.validate((valid) => {
                   //校验表单成功后才做一下操作
                   if (valid) {
                       this.$confirm('确认入驻吗?', '提示', {}).then(() => {
                           //拷贝后面对象的值到新对象,防止后面代码改动引起模型变化
                           let para = Object.assign({}, this.employee); //employee
                           let tenant = {
                               companyName: para.companyName,
                               companyNum: para.companyNum,
                               address: para.address,
                               logo:para.logo,
                          }
                           para.tenant = tenant;
                           //判断是否有id有就是修改,否则就是添加
                           this.$http.put("/employee/tenant",para).then((res) => {
                               if(res.data.success){
                                   this.$message({
                                       message: '操作成功!',
                                       type: 'success'
                                  });
                                   //重置表单
                                   this.$refs['tenantForm'].resetFields();
                                   //跳转登录页面
                                   this.$router.push({ path: '/login' });
                              }
                               else{
                                   this.$message({
                                       message: res.data.message,
                                       type: 'error'
                                  });
                              }

                          });
                      });
                  }
              })
          }
      }