SAAS 系统按租户分库实现

SAAS 按租户分库方案

第一步

实现spring 的AbstractRoutingDataSource 抽象类:

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * Created by chenwenshun on 2018/12/12.
 */

public class RoutingDataSource extends AbstractRoutingDataSource{

    @Override
    protected Object determineCurrentLookupKey() {
        return RoutingDataSourceContextHolder.get();
    }
}

写一个线程容器保存每个请求的数据源信息:

/**
 * Created by chenwenshun on 2018/12/12.
 */
public class RoutingDataSourceContextHolder {

    private static final ThreadLocal<DataSourceEnum> threadlocalDataSourceKey = new ThreadLocal<>();

    public static  void set(DataSourceEnum key){
        threadlocalDataSourceKey.set(key);
    }

    public static DataSourceEnum get(){
         return threadlocalDataSourceKey.get();
    }

    public static void clear()  {
        threadlocalDataSourceKey.remove();
    }

}

第二步

修改application.yml数据源配置:

datasource:
  druid:
    url: jdbc:mysql://111.231.82.13:6630/dict?useUnicode=true&characterEncoding=utf8
    driver-class: com.mysql.jdbc.Driver
    username: yanfa
    password: yqKjKHX.1:bv
    initial-size: 1
    min-idle: 1
    max-active: 20
    test-on-borrow: true
    filters: stat

datasource2:
  druid:
    url: jdbc:mysql://111.231.82.13:6630/dict2?useUnicode=true&characterEncoding=utf8
    driver-class: com.mysql.jdbc.Driver
    username: yanfa
    password: yqKjKHX.1:bv
    initial-size: 1
    min-idle: 1
    max-active: 20
    test-on-borrow: true
    filters: stat

对应的java 配置:

    

/**
 * Created by chenwenshun on 2018/8/31.
 */
@Configuration
public class DataSourceConfig {

    @Bean
    @ConfigurationProperties(prefix = "datasource.druid")
    public DataSource dataSource_0(){
        return DataSourceBuilder.create()
                .type(DruidDataSource.class)
                .build();
    }

    @Bean
    @ConfigurationProperties(prefix = "datasource2.druid")
    public DataSource dataSource_1(){
        return DataSourceBuilder.create()
                .type(DruidDataSource.class)
                .build();
    }

    @Bean
    @Primary
    public DataSource RoutingDataSource(
            @Autowired @Qualifier("dataSource_0") DataSource dataSource_0,
            @Autowired @Qualifier("dataSource_1") DataSource dataSource_1
    ){
        Map<Object, Object> map = new HashMap<>();
        map.put(DataSourceEnum.DS_0, dataSource_0);
        map.put(DataSourceEnum.DS_1, dataSource_1);

        RoutingDataSource routingDataSource = new RoutingDataSource();
        routingDataSource.setTargetDataSources(map);
        routingDataSource.setDefaultTargetDataSource(dataSource_0);
        return routingDataSource;
    }

第三步

实现一个商户与数据的映射逻辑,接口类似定义如下:


/**
 * Created by chenwenshun on 2018/12/14.
 * 商户与数据源的映射关系
 * 具体项目不同实现
 * 如:用数据库配置或者apollo,等方式
 */

public interface DataSourceMapping {

    /**
     * 商户ID 返回对应的数据源,可以采取两种方式
     *
     * 1、通过具体的配置
     *
     * 2、通过自己实现路由算法返回对应的数据源
     * @param availableDataSources 可用数据源
     * @param shareValue 业务ID如:商户ID
     * @return 数据源标示
     */
    String getDataSource(List<String> availableDataSources, String shareValue);
}

第四步

通过切面拦截所有controller 请求,获取商户ID,然后更具商户ID 调用DataSourceMapping.getDataSource 获取不同商户对应的数据源, 设置到RoutingDataSourceContextHolder 线程变量:

/**
 * Created by chenwenshun on 2018/12/12.
 */
@Aspect
@Component
public class RoutingDataSourceAspect {

    @Autowired
    private DataSourceMapping dataSourceMapping;

    @Pointcut("execution(public * com.freemud.springbootdemo.controller.*.*(..))")
    public void point() {
    }

    @Before("point()")
    public void doBefore(JoinPoint joinPoint) throws ClassNotFoundException, NotFoundException {
        String classType = joinPoint.getTarget().getClass().getName();
//        Class<?> clazz = Class.forName(classType);
//        String clazzName = clazz.getName();
        String methodName = joinPoint.getSignature().getName(); //获取方法名称
        Object[] args = joinPoint.getArgs();//参数
        Map<String,Object> nameAndArgs = this.getFieldsName(this.getClass(),classType,methodName,args);

        String companyId = null;

        if (nameAndArgs.containsKey("companyId")){
            companyId = (String)nameAndArgs.get("companyId");
        }else if (nameAndArgs.containsKey("requestBody")){

             BaseRequest request = (BaseRequest)nameAndArgs.get("requestBody");
             companyId = request.getCompanyId();

        }else {
            BaseRequest request = (BaseRequest)Lists.newArrayList(nameAndArgs.values()).get(0);
            companyId = request.getCompanyId();
        }

        if (StringUtils.isBlank(companyId)){
            throw new  UnsupportedOperationException("companyId can not be null!");
        }

        String ds = DataSourceEnum.DS_0.name();
        if (dataSourceMapping != null){
            List<String> dataSourceList = Lists.newArrayList();
            for ( DataSourceEnum dataSourceEnum :DataSourceEnum.values() ) {
                dataSourceList.add( dataSourceEnum.name() );
            }

            ds = dataSourceMapping.getDataSource(dataSourceList , companyId) ;
        }

        RoutingDataSourceContextHolder.set(DataSourceEnum.valueOf(ds));
    }

    @After("point()")
    public void doAfter(){
        RoutingDataSourceContextHolder.clear();
    }

    private Map<String,Object> getFieldsName(Class cls, String clazzName, String methodName, Object[] args) throws NotFoundException {
        Map<String,Object > map=new LinkedHashMap<>();
        ClassPool pool = ClassPool.getDefault();
        ClassClassPath classPath = new ClassClassPath(cls);
        pool.insertClassPath(classPath);

        CtClass cc = pool.get(clazzName);
        CtMethod cm = cc.getDeclaredMethod(methodName);
        MethodInfo methodInfo = cm.getMethodInfo();
        CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
        LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
        int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;
        for (int i = 0; i < cm.getParameterTypes().length; i++){
            map.put( attr.variableName(i + pos),args[i]);//paramNames即参数名
        }
        return map;
    }

}

over ^0^

body { font-family: Helvetica, arial, sans-serif; font-size: 14px; line-height: 1.6; padding-top: 10px; padding-bottom: 10px; background-color: white; padding: 30px }
body>*:first-child { margin-top: 0 !important }
body>*:last-child { margin-bottom: 0 !important }
a { color: #4183C4 }
a.absent { color: #cc0000 }
a.anchor { display: block; padding-left: 30px; margin-left: -30px; cursor: pointer; position: absolute; top: 0; left: 0; bottom: 0 }
h1,h2,h3,h4,h5,h6 { margin: 20px 0 10px; padding: 0; font-weight: bold; cursor: text; position: relative }
h1:hover a.anchor,h2:hover a.anchor,h3:hover a.anchor,h4:hover a.anchor,h5:hover a.anchor,h6:hover a.anchor { background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA09pVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoMTMuMCAyMDEyMDMwNS5tLjQxNSAyMDEyLzAzLzA1OjIxOjAwOjAwKSAgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OUM2NjlDQjI4ODBGMTFFMTg1ODlEODNERDJBRjUwQTQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OUM2NjlDQjM4ODBGMTFFMTg1ODlEODNERDJBRjUwQTQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo5QzY2OUNCMDg4MEYxMUUxODU4OUQ4M0REMkFGNTBBNCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo5QzY2OUNCMTg4MEYxMUUxODU4OUQ4M0REMkFGNTBBNCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PsQhXeAAAABfSURBVHjaYvz//z8DJYCRUgMYQAbAMBQIAvEqkBQWXI6sHqwHiwG70TTBxGaiWwjCTGgOUgJiF1J8wMRAIUA34B4Q76HUBelAfJYSA0CuMIEaRP8wGIkGMA54bgQIMACAmkXJi0hKJQAAAABJRU5ErkJggg==") no-repeat 10px center; text-decoration: none }
h1 tt,h1 code { font-size: inherit }
h2 tt,h2 code { font-size: inherit }
h3 tt,h3 code { font-size: inherit }
h4 tt,h4 code { font-size: inherit }
h5 tt,h5 code { font-size: inherit }
h6 tt,h6 code { font-size: inherit }
h1 { font-size: 28px; color: black }
h2 { font-size: 24px; border-bottom: 1px solid #cccccc; color: black }
h3 { font-size: 18px }
h4 { font-size: 16px }
h5 { font-size: 14px }
h6 { color: #777777; font-size: 14px }
p,blockquote,ul,ol,dl,li,table,pre { margin: 15px 0 }
hr { background: transparent url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAECAYAAACtBE5DAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OENDRjNBN0E2NTZBMTFFMEI3QjRBODM4NzJDMjlGNDgiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OENDRjNBN0I2NTZBMTFFMEI3QjRBODM4NzJDMjlGNDgiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo4Q0NGM0E3ODY1NkExMUUwQjdCNEE4Mzg3MkMyOUY0OCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo4Q0NGM0E3OTY1NkExMUUwQjdCNEE4Mzg3MkMyOUY0OCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PqqezsUAAAAfSURBVHjaYmRABcYwBiM2QSA4y4hNEKYDQxAEAAIMAHNGAzhkPOlYAAAAAElFTkSuQmCC") repeat-x 0 0; border: 0 none; color: #cccccc; height: 4px; padding: 0 }
body>h2:first-child { margin-top: 0; padding-top: 0 }
body>h1:first-child { margin-top: 0; padding-top: 0 }
body>h1:first-child+h2 { margin-top: 0; padding-top: 0 }
body>h3:first-child,body>h4:first-child,body>h5:first-child,body>h6:first-child { margin-top: 0; padding-top: 0 }
a:first-child h1,a:first-child h2,a:first-child h3,a:first-child h4,a:first-child h5,a:first-child h6 { margin-top: 0; padding-top: 0 }
h1 p,h2 p,h3 p,h4 p,h5 p,h6 p { margin-top: 0 }
li p.first { display: inline-block }
li { margin: 0 }
ul,ol { padding-left: 30px }
ul :first-child,ol :first-child { margin-top: 0 }
dl { padding: 0 }
dl dt { font-size: 14px; font-weight: bold; font-style: italic; padding: 0; margin: 15px 0 5px }
dl dt:first-child { padding: 0 }
dl dt>:first-child { margin-top: 0 }
dl dt>:last-child { margin-bottom: 0 }
dl dd { margin: 0 0 15px; padding: 0 15px }
dl dd>:first-child { margin-top: 0 }
dl dd>:last-child { margin-bottom: 0 }
blockquote { border-left: 4px solid #dddddd; padding: 0 15px; color: #777777 }
blockquote>:first-child { margin-top: 0 }
blockquote>:last-child { margin-bottom: 0 }
table { padding: 0; border-collapse: collapse }
table tr { border-top: 1px solid #cccccc; background-color: white; margin: 0; padding: 0 }
table tr:nth-child(2n) { background-color: #f8f8f8 }
table tr th { font-weight: bold; border: 1px solid #cccccc; margin: 0; padding: 6px 13px }
table tr td { border: 1px solid #cccccc; margin: 0; padding: 6px 13px }
table tr th :first-child,table tr td :first-child { margin-top: 0 }
table tr th :last-child,table tr td :last-child { margin-bottom: 0 }
img { max-width: 100% }
span.frame { display: block; overflow: hidden }
span.frame>span { border: 1px solid #dddddd; display: block; float: left; overflow: hidden; margin: 13px 0 0; padding: 7px; width: auto }
span.frame span img { display: block; float: left }
span.frame span span { clear: both; color: #333333; display: block; padding: 5px 0 0 }
span.align-center { display: block; overflow: hidden; clear: both }
span.align-center>span { display: block; overflow: hidden; margin: 13px auto 0; text-align: center }
span.align-center span img { margin: 0 auto; text-align: center }
span.align-right { display: block; overflow: hidden; clear: both }
span.align-right>span { display: block; overflow: hidden; margin: 13px 0 0; text-align: right }
span.align-right span img { margin: 0; text-align: right }
span.float-left { display: block; margin-right: 13px; overflow: hidden; float: left }
span.float-left span { margin: 13px 0 0 }
span.float-right { display: block; margin-left: 13px; overflow: hidden; float: right }
span.float-right>span { display: block; overflow: hidden; margin: 13px auto 0; text-align: right }
code,tt { margin: 0 2px; padding: 0 5px; white-space: nowrap; border: 1px solid #eaeaea; background-color: #f8f8f8 }
pre code { margin: 0; padding: 0; white-space: pre; border: none; background: transparent }
.highlight pre { background-color: #f8f8f8; border: 1px solid #cccccc; font-size: 13px; line-height: 19px; overflow: auto; padding: 6px 10px }
pre { background-color: #f8f8f8; border: 1px solid #cccccc; font-size: 13px; line-height: 19px; overflow: auto; padding: 6px 10px }
pre code,pre tt { background-color: transparent; border: none }
sup { font-size: 0.83em; vertical-align: super; line-height: 0 }
kbd { display: inline-block; padding: 3px 5px; font-size: 11px; line-height: 10px; color: #555; vertical-align: middle; background-color: #fcfcfc; border: solid 1px #ccc; border-bottom-color: #bbb }
* { }

原文地址:https://www.cnblogs.com/wenshun/p/10160960.html

时间: 2024-08-30 12:37:32

SAAS 系统按租户分库实现的相关文章

saas系统多租户数据隔离的实现(一)数据隔离方案

0. 前言 前几天跟朋友聚会的时候,朋友说他们公司准备自己搞一套saas系统,以实现多个第三方平台的业务接入需求.聊完以后,实在手痒难耐,于是花了两天时间自己实现了两个saas系统多租户数据隔离实现方案.俗话说“独乐乐不如众乐乐”,所以我把我的“研究成果”写出来,让大家乐呵乐呵. 在分享我的研究成果之前,我们先了解一下相关的定义吧.如果对这部分内容熟悉的同学,可以直接略过. 1. 什么是saas系统 引用百度百科上面的描述, “SaaS平台是运营saas软件的平台.SaaS提供商为企业搭建信息化

saas 系统租户个性化域名&amp;&amp;租户绑定自己域名的解决方案

实际的需求就类似github 的自定义page 1. 个性化域名 github 实现原理就是用户个性化域名使用泛域名解析,这个比较简单,大部分域名提供商都可以解决 具体操作不用赘述 使用nginx 的配置比较简单 配置如下: server { listen 8080default; index index.html index.htm index.php; root html; location /{ root html; } location /app { root html; } locat

saas系统架构经验总结

2B Saas系统最近几年都很火.很多创业公司都在尝试创建企业级别的应用 cRM, HR,销售, Desk Saas系统.很多Saas创业公司也拿了大额风投.毕竟Saas相对传统软件的优势非常明显. 最近一年,有幸架构一个Crm saas 系统,上线了几个月来,各方面都比满意.整个系统创建过程,踩了很多坑,收获也比较多.总结一下Saas系统架构一些特点: 1.分层设计 saas系统分层大概是: 租户识别>应用层>数据访问层>缓存层>数据库 业务代码都是写在应用层. 租户识别可以用s

SaaS 系统架构设计经验总结

2B SaaS系统最近几年都很火.很多创业公司都在尝试创建企业级别的应用 cRM, HR,销售, Desk SaaS系统.很多SaaS创业公司也拿了大额风投.毕竟SaaS相对传统软件的优势非常明显. 最近一年,有幸架构一个Crm SaaS 系统,上线了几个月来,各方面都比满意.整个系统创建过程,踩了很多坑,收获也比较多.总结一下SaaS系统架构一些特点: 1.分层设计 SaaS系统分层大概是: 租户识别>应用层>数据访问层>缓存层>数据库 业务代码都是写在应用层. 租户识别可以用s

使用ABP打造SAAS系统(1)——环境准备

一.前言 使用ABP也有一段时间了,很多东西是懂非懂,打算试着使用abp来搭建一套SAAS系统,与实际项目相互验证. 主要实现以下目标: 将ABP源码与实际项目相结合,后续可以修改相关源码来支持项目,使得开源发挥相关作用 由浅入深,争取做到每一步清晰,让新人也容易入手 具备常见模块: 发布源码到GITHUB 每一次源码发到CSDN下载站 本教程适用人群: 对DDD有一定了解(不了解可以参考:http://www.cnblogs.com/landeanfen/p/4816706.html) 对AB

SaaS系列介绍之十三: SaaS系统体系架构

1 系统体系架构设计 软件开发中系统体系架构决定了一个系统稳定性.健壮性.可扩展性.兼容性和可用性,它是系统的灵魂.体系架构是架构师所关注的核心.良好的体系架构是系统成功的开端,否则,再好的代码与设计也无济于事. 2 当前.net主要的开发框架简介 l Castle Castle是针对.NET平台的一个开源项目,从数据访问框架ORM到IOC容器,再到WEB层的MVC框架.AOP,基本包括了整个开发过程中的所有东西,为我们快速的构建企业级的应用程序提供了很好的服务.其中关键的技术是ActiveRe

在.net core中完美解决多租户分库分表的问题

前几天有人想做一个多租户的平台,每个租户一个库,可以进行水平扩展,应用端根据登录信息,切换到不同的租户库 计划用ef core实现,他们说做不出来,需要动态创建dbContext,不好实现 然而这个使用CRL很轻松就能解决了 以下为演示数据库,有两个库testdb和testdb2,查询结果如下 目标: 根据传入登录信息连不不同的库,查询返回结果,如登录人为01,返回d1.default,登录人为02 返回 d2.default 实际上这个需求就是分库分表的实现,通过设置数据库/表映射关系,根据传

整理几个互联网公司常用的概念: SaaS是软件即服务 OA系统 意为办公自动化系统 BOSS指的是业务运营支撑系统 CRM客户关系管理 ERP系统是企业资源计划

整理几个互联网公司常用的概念: 解决以下几个疑问以及容易忘记或者混淆的概念: SaaS系统是什么 OA系统是什么  BOSS系统是什么 CRM系统是什么客户关系管理 ERP系统是什么 SaaS是Software-as-a-Service(软件即服务OA系统是Office Automation System 意为办公自动化系统.BOSS(Business & Operation Support System)指的是业务运营支撑系统CRM(Customer Relationship Manageme

SAAS多租户数据逻辑隔离

基于Mybatis 的SAAS应用多租户数据逻辑隔离 package com.opencloud.common.interceptor;import org.apache.commons.lang3.StringUtils;import org.apache.ibatis.executor.statement.StatementHandler;import org.apache.ibatis.mapping.BoundSql;import org.apache.ibatis.mapping.Ma