实现原理

com/lc/ibps/base/db/config/DatabaseConfigure.java
    ...
    @Bean(/*initMethod = "initialize"*/)
    @DependsOn({"j2CacheUtil","appUtil"})
    //@ConditionalOnMissingBean(DatabaseInitializable.class)
    public DatabaseInitializable databaseInitializable() {
        return new DatabaseInitializable();
    }

    @Bean(/*initMethod = "initialize"*/)
    @DependsOn({"j2CacheUtil","dataSourceDef"})
    //@ConditionalOnMissingBean(DatabaseUpgradeInitializable.class)
    public DatabaseUpgradeInitializable databaseUpgradeInitializable() {
        return new DatabaseUpgradeInitializable();
    }
    ...
com/lc/ibps/base/db/bootstrap/DatabaseInitializable.java

    ...
    private void execute() {
        try {
            if (ITableMeta.isEmpty()) {
                logger.warn("Initialize database cannot work under IDE environment!!!");
                logger.debug("Starting to initialize database------------------>");

                FileFilter filter = new FileFilter() {
                    @Override
                    public boolean accept(File file) {
                        return FileUtil.isFile(file);
                    }
                };

                // 建表sql文件
                String dirCreate = DbConstant.getDirAutoDDLSQL(DbUtil.getCurDBtype());
                URL url = URLUtil.getURL(dirCreate);
                if(null == url) {
                    return;
                }

                logger.debug("Dir of create table sql {}.", url.getFile());
                File dirCreateFile = new File(url.getFile());
                // 判断目录是否多个,jar文件中也会存在
                if(FileUtil.exist(dirCreateFile) && FileUtil.isDirectory(dirCreateFile)) {
                    File[] createFiles = dirCreateFile.listFiles(filter);
                    if(createFiles == null) {
                        // 没有文件直接返回
                        return;
                    }

                    for(File createFile : createFiles) {
                        execute(createFile);
                        logger.debug("Execute sql file {} success.", createFile.getAbsolutePath());
                    }
                }
                else {
                    return;
                }

                // 初始化数据sql文件
                String dirInsert = DbConstant.getDirAutoDMLSQL(DbUtil.getCurDBtype());
                logger.debug("Dir of insert data sql {}.", dirInsert);
                url = URLUtil.getURL(dirInsert);
                if(null == url) {
                    return;
                }

                File dirInsertFile = new File(url.getFile());
                if(FileUtil.exist(dirInsertFile) && FileUtil.isDirectory(dirInsertFile)) {
                    File[] insertFiles = dirInsertFile.listFiles(filter);
                    if(null == insertFiles) {
                        // 没有文件直接返回
                        return;
                    }

                    for(File insertFile : insertFiles) {
                        execute(insertFile);
                        logger.debug("Execute sql file {} success.", insertFile.getAbsolutePath());
                    }
                }

                logger.debug("Ending of initialize database------------------>");
            }
        } catch (Exception e) {
            throw new BaseException(e);
        } finally {
        }
    }
    ...
com/lc/ibps/base/db/bootstrap/DatabaseUpgradeInitializable.java

    ...
        private void record(String templateScript, String dsAlias, String schema, String cause) throws IOException {
        File recordFile = getRecordFile();
        if(BeanUtils.isNotEmpty(recordFile)) {
            String sha256 = EncryptUtil.encryptSha256(templateScript);
            InputStream is = new ClassPathResource(getRecordFilePath()).getStream();
            String record = com.lc.ibps.base.core.util.FileUtil.readFile(is, StringPool.UTF_8, true);
            if(StringUtil.isNotBlank(schema)) {
                record = TenantSqlScriptUtil.formatSchemaForDML(record, schema);
            }
            getSepcJdbcTemplate(dsAlias).update(record, sha256, new Date(), cause);
        }
    }
    ...

通过注入DatabaseInitializableDatabaseUpgradeInitializable这两个bean,实现租户创建空间后,自动执行相关DDL和DML语句的执行,以及执行对应的数据库更新脚本,并记录更新结果。

租户基础数据在哪?

com/lc/ibps/saas/base/db/tenant/operator/BaseTenantOperator.java

    ...
    protected List<SchemaResultEntity> createTenantInternal(SchemaCreateEntity param) {
        //1. 读取需要创建哪些数据库
        Map<String, String> tenantDatabases = listTenantDatabases();
        if(BeanUtils.isEmpty(tenantDatabases)) {
            logger.warn("Database type {} hasn't tenant sql!", getDbType());
            return new ArrayList<SchemaResultEntity>();
        }

        //2. 生成数据库信息
        String tenantPrefix = AppUtil.getProperty(TenantConstant.DB_TENANT_PREFIX, TenantConstant.DB_TENANT_PREFIX_DEFAULT);
        String active = AppUtil.getProfilesActive();
        tenantPrefix += "_" + active;
        tenantPrefix = tenantPrefix.replaceAll("-", "_");
        logger.debug("tenantPrefix => {}", tenantPrefix);
        List<SchemaResultEntity> result = createTenantSchema(param, tenantPrefix, tenantDatabases);

        try {
            //3. 创建数据库用户/模式
            logger.info("Database type {} create tenant space starting!", getDbType());
            createTenantInternal0(result.get(0));
            logger.info("Database type {} create tenant space finished!", getDbType());

            //4. 执行每个数据库用户/模式下DDL/DML SQL文件
            logger.info("Database type {} execute tenant sql starting!", getDbType());
            executeSqlScript(getDbType(), result.get(0));
            logger.info("Database type {} execute tenant sql finished!", getDbType());
        } catch (Exception e) {
            try {
                // 创建租户空间异常后是否自动删除已创建的库
                boolean autoDelete = AppUtil.getProperty("db.tenant.schema.auto-delete", Boolean.class, true);
                if(autoDelete) {
                    dropTenant(result);
                }
            } catch (Exception ignore) {}

            throw e;
        }

        return result;
    }
    ...

租户的基础数据在DML文件夹下的SQL脚本中,以及管理员创建空间时,tenant_type_(租户资源类型)为init(初始化资源)的数据也会同步到该租户下。

租户数据库信息如何记录?

com/lc/ibps/saas/process/callback/LocalTenantSchemaCreateProcessCallback.java

    ...
    public void success(SchemaCreateEntity param, List<SchemaResultEntity> result) {
        super.success(param, result);

        for(SchemaResultEntity entity : result) {
            String dsAlias = entity.getDsAlias();
            String schema = entity.getSchema();
            SaasTenantSchemaRepository saasTenantSchemaRepository = AppUtil.getBean(SaasTenantSchemaRepository.class);
            SaasTenantSchemaPo tenantSchemaPo = saasTenantSchemaRepository.getByTenantProviderId(entity.getTenantId(), entity.getProviderId());
            // 设置成已生成
            tenantSchemaPo.setSchemaStatus(TenantSchemaStatus.CREATED.getValue());
            tenantSchemaPo.setDsAlias(dsAlias);
            tenantSchemaPo.setSchema(schema);
            // 然后更新数据库
            SaasTenantSchema saasTenantSchema = saasTenantSchemaRepository.newInstance(tenantSchemaPo);
            saasTenantSchema.update();
        }

        for (SchemaResultEntity entity : result) {
            // 同步初始化资源数据到该服务的空间内
            // PC资源根据ibps_auth_res表的字段tenant_type_=init的全部读出来写到空间内;
            if(TenantUtil.getProviderId().equalsIgnoreCase(entity.getProviderId())) {
                ResourcesRepository resourcesRepository = AppUtil.getBean(ResourcesRepository.class);
                List<ResourcesPo> resourcesPos = resourcesRepository.findByTenantType("init");
                try {
                    TenantContext.forceTenantObject(TenantQueryUtil.get(entity.getTenantId()));
                    String dsAlias = TenantUtil.TenantSchemaUtil.getRealDsAlias(entity.getTenantId(), TenantUtil.getProviderId());
                    if (StringUtil.isNotBlank(dsAlias)) {//TODO 事务内不允许切换数据源,逻辑要调整
                        DbContextHolder.setDataSource(dsAlias, DbUtil.getCurDBtype());
                    }

                    Resources resources = resourcesRepository.newInstance();
                    resources.setDatas(resourcesPos);
                    resources.createBatch();

                } catch (Exception e) {
                    throw new BaseException(e);
                }
                finally {
                    DbContextHolder.clearDataSource();
                    TenantContext.clearForceTenantObject();
                }
            }
        }

        String tenantName =  OperatorParamter.Builder.create().get("tenantName", result.get(0).getOperatorParamters());
        StringBuilder content = new StringBuilder();
        send(tenantName, content.toString(), "成功");
    }
    ...

租户的数据库信息通过对应的回调方法,同步更新到主平台的ibps_saas_tenant_schema(SaaS核心-租户(企业)数据库信息表)

文档更新时间: 2020-10-08 18:18   作者:Eddy