当计划作业中的代码为 运行 时,如何在多租户 grails 应用程序中解析数据库租户?

How to resolve a database tenant in a multi tenant grails app when the code is run from a scheduled job?

我正在使用 grails 4.0.3,该应用程序在数据库级别是多租户的。我正在根据请求的子域解析租户。多租户适用于除预定作业之外的所有情况。我的理解是,由于计划作业没有请求范围,因此永远不会调用租户解析器,因为没有请求触发租户解析器。 我有一个在计划作业中进行的数据库事务。并且这些事务总是指向默认数据库。

如何在没有请求范围的情况下解析租户。

我的 application.yml 租户解析器配置如下:

grails:
    profile: web
    codegen:
        defaultPackage: com.pomco.middleware
    gorm:
        reactor:
            # Whether to translate GORM events into Reactor events
            # Disabled by default for performance reasons
            events: false
        multiTenancy:
            mode: DATABASE
            tenantResolverClass: com.pomco.middleware.multitenant.CustomSubDomainTenantResolver

CustomSubDomainTenantResolver 看起来像这样

class CustomSubDomainTenantResolver implements TenantResolver{

    @Override
    Serializable resolveTenantIdentifier() {

        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes()
        if(requestAttributes instanceof ServletWebRequest) {
            HttpServletRequest httpServletRequest = ((ServletWebRequest) requestAttributes).getRequest()
            def env = DatasourceEnvironment.lookupEnvironmentByHostname(httpServletRequest.getServerName())
            String subDomainId = env?.tenantId
            if( subDomainId) {
                if(!DatabaseProvisioningService.getSourceByTenantId(subDomainId) && subDomainId != 'DEFAULT'){
                    def tenantByUrl = DatabaseProvisioningService.tenantSources.find {it.value == DatabaseProvisioningService.getDbUrlByEnv(env)}
                    if (tenantByUrl){
                        return tenantByUrl.key
                    }
                }
                return subDomainId
            }
            else {
                return ConnectionSource.DEFAULT
            }
        }
        else if(!requestAttributes)
            return ConnectionSource.DEFAULT
        throw new TenantNotFoundException("Tenant could not be resolved outside a web request")
    }
}

此问题的解决方案是通过在计划作业中使用 withId 闭包手动使用租户。

例如,我无法在以下预定作业中获得租户。

@CurrentTenant
def ExampleService{

// this method runs with required tenant as this is a service method annotated with @CurrentTenant
def methodThatHasTenantScope(){
    ScheduledFuture<?> helperFuture = null
    def result = null

    ScheduledFuture<?> future = ses.scheduleWithFixedDelay(new Runnable() {
        // BUT here there is no tenant scope as this is a threaded task which doesn't have a request scope
        // so this job runs with default tenant
        private long count = 0
        @Override
        void run() {
            // job task here runs with default tenant
        }
    }, DELAY, FREQUENCY_MILLIS, TimeUnit.MILLISECONDS)
    return result
}
}

现在我们需要在方法methodThatHasTenantScope中手动获取租户id,并传递给调度作业方法如下:

@CurrentTenant
def ExampleService{

        @Autowired
        HibernateDatastore hibernateDatastore

        def methodThatHasTenantScope(){
            Serializable tenantId = Tenants.currentId(HibernateDatastore)
            log.trace("Current tenant id: $tenantId")
            ScheduledFuture<?> helperFuture = null
            def result = null

            ScheduledFuture<?> future = ses.scheduleWithFixedDelay(new Runnable() {
                private long count = 0
                @Override
                void run() {
                 //here we have tenantId from the scope of the method
                    withId(tenantId){
                        // job task runs with required tenant
                    }
                }
            }, DELAY, FREQUENCY_MILLIS, TimeUnit.MILLISECONDS)
            return result
        }
 }