diff --git a/src/backend/commons/cmdb-sdk/src/main/java/com/tencent/bk/job/common/cc/config/CmdbAutoConfiguration.java b/src/backend/commons/cmdb-sdk/src/main/java/com/tencent/bk/job/common/cc/config/CmdbAutoConfiguration.java index 0295ff73db..b1b6a5ce2c 100644 --- a/src/backend/commons/cmdb-sdk/src/main/java/com/tencent/bk/job/common/cc/config/CmdbAutoConfiguration.java +++ b/src/backend/commons/cmdb-sdk/src/main/java/com/tencent/bk/job/common/cc/config/CmdbAutoConfiguration.java @@ -34,6 +34,7 @@ import com.tencent.bk.job.common.esb.config.BkApiGatewayProperties; import com.tencent.bk.job.common.esb.config.EsbProperties; import com.tencent.bk.job.common.esb.constants.EsbLang; +import com.tencent.bk.job.common.tenant.TenantEnvService; import com.tencent.bk.job.common.util.FlowController; import io.micrometer.core.instrument.MeterRegistry; import lombok.extern.slf4j.Slf4j; @@ -98,7 +99,8 @@ public BizCmdbClient bizCmdbClient(AppProperties appProperties, ThreadPoolExecutor cmdbThreadPoolExecutor, ThreadPoolExecutor cmdbLongTermThreadPoolExecutor, MeterRegistry meterRegistry, - ObjectProvider flowControllerProvider) { + ObjectProvider flowControllerProvider, + TenantEnvService tenantEnvService) { return new BizCmdbClient( appProperties, esbProperties, @@ -108,7 +110,8 @@ public BizCmdbClient bizCmdbClient(AppProperties appProperties, cmdbThreadPoolExecutor, cmdbLongTermThreadPoolExecutor, flowControllerProvider.getIfAvailable(), - meterRegistry + meterRegistry, + tenantEnvService ); } @@ -120,7 +123,8 @@ public BizCmdbClient cnBizCmdbClient(AppProperties appProperties, ThreadPoolExecutor cmdbThreadPoolExecutor, ThreadPoolExecutor cmdbLongTermThreadPoolExecutor, MeterRegistry meterRegistry, - ObjectProvider flowControllerProvider) { + ObjectProvider flowControllerProvider, + TenantEnvService tenantEnvService) { return new BizCmdbClient( appProperties, esbProperties, @@ -130,7 +134,8 @@ public BizCmdbClient cnBizCmdbClient(AppProperties appProperties, cmdbThreadPoolExecutor, cmdbLongTermThreadPoolExecutor, flowControllerProvider.getIfAvailable(), - meterRegistry + meterRegistry, + tenantEnvService ); } @@ -140,14 +145,16 @@ public BizSetCmdbClient bizSetCmdbClient(AppProperties appProperties, BkApiGatewayProperties bkApiGatewayProperties, CmdbConfig cmdbConfig, MeterRegistry meterRegistry, - ObjectProvider flowControllerProvider) { + ObjectProvider flowControllerProvider, + TenantEnvService tenantEnvService) { return new BizSetCmdbClient( appProperties, esbProperties, bkApiGatewayProperties, cmdbConfig, flowControllerProvider.getIfAvailable(), - meterRegistry + meterRegistry, + tenantEnvService ); } diff --git a/src/backend/commons/cmdb-sdk/src/main/java/com/tencent/bk/job/common/cc/sdk/BaseCmdbApiClient.java b/src/backend/commons/cmdb-sdk/src/main/java/com/tencent/bk/job/common/cc/sdk/BaseCmdbApiClient.java index eea37a52f0..c5ac8f2fba 100644 --- a/src/backend/commons/cmdb-sdk/src/main/java/com/tencent/bk/job/common/cc/sdk/BaseCmdbApiClient.java +++ b/src/backend/commons/cmdb-sdk/src/main/java/com/tencent/bk/job/common/cc/sdk/BaseCmdbApiClient.java @@ -37,10 +37,11 @@ import com.tencent.bk.job.common.esb.model.EsbReq; import com.tencent.bk.job.common.esb.model.EsbResp; import com.tencent.bk.job.common.esb.model.OpenApiRequestInfo; -import com.tencent.bk.job.common.esb.sdk.BkApiClient; +import com.tencent.bk.job.common.esb.sdk.BkApiV1Client; import com.tencent.bk.job.common.exception.InternalCmdbException; import com.tencent.bk.job.common.exception.InternalException; import com.tencent.bk.job.common.metrics.CommonMetricNames; +import com.tencent.bk.job.common.tenant.TenantEnvService; import com.tencent.bk.job.common.util.ApiUtil; import com.tencent.bk.job.common.util.FlowController; import com.tencent.bk.job.common.util.http.HttpHelper; @@ -101,11 +102,11 @@ public class BaseCmdbApiClient { /** * CMDB ESB API 客户端 */ - protected BkApiClient esbCmdbApiClient; + protected BkApiV1Client esbCmdbApiClient; /** * CMDB 蓝鲸网关 API 客户端 */ - protected BkApiClient apiGwCmdbApiClient; + protected BkApiV1Client apiGwCmdbApiClient; static { interfaceNameMap.put(SEARCH_BIZ_INST_TOPO, "search_biz_inst_topo"); @@ -134,20 +135,23 @@ protected BaseCmdbApiClient(FlowController flowController, BkApiGatewayProperties bkApiGatewayProperties, CmdbConfig cmdbConfig, MeterRegistry meterRegistry, + TenantEnvService tenantEnvService, String lang) { WatchableHttpHelper httpHelper = HttpHelperFactory.getRetryableHttpHelper(); - this.esbCmdbApiClient = new BkApiClient(meterRegistry, + this.esbCmdbApiClient = new BkApiV1Client(meterRegistry, CmdbMetricNames.CMDB_API_PREFIX, esbProperties.getService().getUrl(), httpHelper, - lang + lang, + tenantEnvService ); this.esbCmdbApiClient.setLogger(LoggerFactory.getLogger(this.getClass())); - this.apiGwCmdbApiClient = new BkApiClient(meterRegistry, + this.apiGwCmdbApiClient = new BkApiV1Client(meterRegistry, CmdbMetricNames.CMDB_API_PREFIX, bkApiGatewayProperties.getCmdb().getUrl(), httpHelper, - lang + lang, + tenantEnvService ); this.apiGwCmdbApiClient.setLogger(LoggerFactory.getLogger(this.getClass())); this.globalFlowController = flowController; @@ -218,7 +222,7 @@ protected EsbResp requestCmdbApi(ApiGwType apiGwType, } } - private BkApiClient getApiClientByApiGwType(ApiGwType apiGwType) { + private BkApiV1Client getApiClientByApiGwType(ApiGwType apiGwType) { switch (apiGwType) { case ESB: return esbCmdbApiClient; diff --git a/src/backend/commons/cmdb-sdk/src/main/java/com/tencent/bk/job/common/cc/sdk/BizCmdbClient.java b/src/backend/commons/cmdb-sdk/src/main/java/com/tencent/bk/job/common/cc/sdk/BizCmdbClient.java index da251d7a34..86bf697f6a 100644 --- a/src/backend/commons/cmdb-sdk/src/main/java/com/tencent/bk/job/common/cc/sdk/BizCmdbClient.java +++ b/src/backend/commons/cmdb-sdk/src/main/java/com/tencent/bk/job/common/cc/sdk/BizCmdbClient.java @@ -120,6 +120,7 @@ import com.tencent.bk.job.common.model.dto.HostDTO; import com.tencent.bk.job.common.model.dto.ResourceScope; import com.tencent.bk.job.common.model.error.ErrorType; +import com.tencent.bk.job.common.tenant.TenantEnvService; import com.tencent.bk.job.common.util.CollectionUtil; import com.tencent.bk.job.common.util.FlowController; import com.tencent.bk.job.common.util.JobContextUtil; @@ -189,7 +190,8 @@ public BizCmdbClient(AppProperties appProperties, ThreadPoolExecutor threadPoolExecutor, ThreadPoolExecutor longTermThreadPoolExecutor, FlowController flowController, - MeterRegistry meterRegistry) { + MeterRegistry meterRegistry, + TenantEnvService tenantEnvService) { super( flowController, appProperties, @@ -197,6 +199,7 @@ public BizCmdbClient(AppProperties appProperties, bkApiGatewayProperties, cmdbConfig, meterRegistry, + tenantEnvService, lang ); this.threadPoolExecutor = threadPoolExecutor; diff --git a/src/backend/commons/cmdb-sdk/src/main/java/com/tencent/bk/job/common/cc/sdk/BizSetCmdbClient.java b/src/backend/commons/cmdb-sdk/src/main/java/com/tencent/bk/job/common/cc/sdk/BizSetCmdbClient.java index ce2bda25a1..50be0a3be0 100644 --- a/src/backend/commons/cmdb-sdk/src/main/java/com/tencent/bk/job/common/cc/sdk/BizSetCmdbClient.java +++ b/src/backend/commons/cmdb-sdk/src/main/java/com/tencent/bk/job/common/cc/sdk/BizSetCmdbClient.java @@ -51,6 +51,7 @@ import com.tencent.bk.job.common.esb.model.EsbReq; import com.tencent.bk.job.common.esb.model.EsbResp; import com.tencent.bk.job.common.exception.InternalCmdbException; +import com.tencent.bk.job.common.tenant.TenantEnvService; import com.tencent.bk.job.common.util.FlowController; import com.tencent.bk.job.common.util.http.HttpHelperFactory; import io.micrometer.core.instrument.MeterRegistry; @@ -77,9 +78,10 @@ public BizSetCmdbClient(AppProperties appProperties, BkApiGatewayProperties bkApiGatewayProperties, CmdbConfig cmdbConfig, FlowController flowController, - MeterRegistry meterRegistry) { + MeterRegistry meterRegistry, + TenantEnvService tenantEnvService) { super(flowController, appProperties, esbProperties, - bkApiGatewayProperties, cmdbConfig, meterRegistry, null); + bkApiGatewayProperties, cmdbConfig, meterRegistry, tenantEnvService, null); } /** diff --git a/src/backend/commons/common-audit/src/main/java/com/tencent/bk/job/common/audit/AddResourceScopeAuditPostFilter.java b/src/backend/commons/common-audit/src/main/java/com/tencent/bk/job/common/audit/AddResourceScopeAuditPostFilter.java index 989ee7f2e6..02d310d2e9 100644 --- a/src/backend/commons/common-audit/src/main/java/com/tencent/bk/job/common/audit/AddResourceScopeAuditPostFilter.java +++ b/src/backend/commons/common-audit/src/main/java/com/tencent/bk/job/common/audit/AddResourceScopeAuditPostFilter.java @@ -26,6 +26,7 @@ import com.tencent.bk.audit.filter.AuditPostFilter; import com.tencent.bk.audit.model.AuditEvent; +import com.tencent.bk.job.common.model.BasicApp; import com.tencent.bk.job.common.model.dto.AppResourceScope; import com.tencent.bk.job.common.util.JobContextUtil; import lombok.extern.slf4j.Slf4j; @@ -41,13 +42,13 @@ public AuditEvent map(AuditEvent auditEvent) { if (auditEvent == null) { return null; } - AppResourceScope appResourceScope = JobContextUtil.getAppResourceScope(); - if (appResourceScope != null) { + BasicApp app = JobContextUtil.getApp(); + if (app != null && app.getScope() != null) { if (log.isDebugEnabled()) { - log.debug("Add resource scope for audit event, resourceScope: {}", appResourceScope); + log.debug("Add resource scope for audit event, resourceScope: {}", app.getScope()); } - auditEvent.setScopeType(appResourceScope.getType().getValue()); - auditEvent.setScopeId(appResourceScope.getId()); + auditEvent.setScopeType(app.getScope().getType().getValue()); + auditEvent.setScopeId(app.getScope().getId()); } return auditEvent; } diff --git a/src/backend/commons/common-i18n/src/main/resources/i18n/exception/message.properties b/src/backend/commons/common-i18n/src/main/resources/i18n/exception/message.properties index 65f76cb227..a572087b79 100644 --- a/src/backend/commons/common-i18n/src/main/resources/i18n/exception/message.properties +++ b/src/backend/commons/common-i18n/src/main/resources/i18n/exception/message.properties @@ -63,9 +63,10 @@ 1211003=根据动态分组 ID 查找主机失败,动态分组 ID :{0},原因:{1},请确认指定的动态分组在业务下是否存在 1211004=根据业务ID查找动态分组失败,业务 ID :{0},原因:{1},请确认指定的业务是否存在动态分组 1213001=CMSI 接口访问异常 -1213002=用户管理接口访问异常 1213003=调用 CMSI 接口获取通知渠道数据异常 1213004=调用 CMSI 接口发送通知失败,错误码:{0},错误信息:{1} +1219001=蓝鲸登录接口访问异常 +1220001=用户管理接口访问异常 1214001=ARTIFACTORY API 返回数据异常 1214002=制品库中找不到节点:{0},请到制品库核实 diff --git a/src/backend/commons/common-i18n/src/main/resources/i18n/exception/message_en.properties b/src/backend/commons/common-i18n/src/main/resources/i18n/exception/message_en.properties index 3721e11e79..c647973f31 100644 --- a/src/backend/commons/common-i18n/src/main/resources/i18n/exception/message_en.properties +++ b/src/backend/commons/common-i18n/src/main/resources/i18n/exception/message_en.properties @@ -63,9 +63,10 @@ 1211003=Fail to find host by dynamic group, id:{0}, reason:{1}, please confirm the specified dynamic group in business 1211004=Fail to find dynamic group by biz, id:{0}, reason:{1}, please confirm dynamic group in the specified business 1213001=Fail to request CMSI API -1213002=Fail to request UserManage API 1213003=CMSI exception when get notify channels 1213004=CMSI exception when send notify, error_code={0}, error_msg={1} +1219001=Fail to request bk-login API +1220001=Fail to request bk-user API 1214001=ARTIFACTORY API returned data exception 1214002=Cannot find node in bkrepo:{0}, please check in bkrepo diff --git a/src/backend/commons/common-i18n/src/main/resources/i18n/exception/message_en_US.properties b/src/backend/commons/common-i18n/src/main/resources/i18n/exception/message_en_US.properties index 3721e11e79..251adb49e9 100644 --- a/src/backend/commons/common-i18n/src/main/resources/i18n/exception/message_en_US.properties +++ b/src/backend/commons/common-i18n/src/main/resources/i18n/exception/message_en_US.properties @@ -63,9 +63,9 @@ 1211003=Fail to find host by dynamic group, id:{0}, reason:{1}, please confirm the specified dynamic group in business 1211004=Fail to find dynamic group by biz, id:{0}, reason:{1}, please confirm dynamic group in the specified business 1213001=Fail to request CMSI API -1213002=Fail to request UserManage API 1213003=CMSI exception when get notify channels 1213004=CMSI exception when send notify, error_code={0}, error_msg={1} +1220001=Fail to request bk-user API 1214001=ARTIFACTORY API returned data exception 1214002=Cannot find node in bkrepo:{0}, please check in bkrepo diff --git a/src/backend/commons/common-i18n/src/main/resources/i18n/exception/message_zh.properties b/src/backend/commons/common-i18n/src/main/resources/i18n/exception/message_zh.properties index 65f76cb227..a572087b79 100644 --- a/src/backend/commons/common-i18n/src/main/resources/i18n/exception/message_zh.properties +++ b/src/backend/commons/common-i18n/src/main/resources/i18n/exception/message_zh.properties @@ -63,9 +63,10 @@ 1211003=根据动态分组 ID 查找主机失败,动态分组 ID :{0},原因:{1},请确认指定的动态分组在业务下是否存在 1211004=根据业务ID查找动态分组失败,业务 ID :{0},原因:{1},请确认指定的业务是否存在动态分组 1213001=CMSI 接口访问异常 -1213002=用户管理接口访问异常 1213003=调用 CMSI 接口获取通知渠道数据异常 1213004=调用 CMSI 接口发送通知失败,错误码:{0},错误信息:{1} +1219001=蓝鲸登录接口访问异常 +1220001=用户管理接口访问异常 1214001=ARTIFACTORY API 返回数据异常 1214002=制品库中找不到节点:{0},请到制品库核实 diff --git a/src/backend/commons/common-i18n/src/main/resources/i18n/exception/message_zh_CN.properties b/src/backend/commons/common-i18n/src/main/resources/i18n/exception/message_zh_CN.properties index eb820bfee7..2846c9ddd1 100644 --- a/src/backend/commons/common-i18n/src/main/resources/i18n/exception/message_zh_CN.properties +++ b/src/backend/commons/common-i18n/src/main/resources/i18n/exception/message_zh_CN.properties @@ -63,9 +63,10 @@ 1211003=根据动态分组 ID 查找主机失败,动态分组 ID :{0},原因:{1},请确认指定的动态分组在业务下是否存在 1211004=根据业务ID查找动态分组失败,业务 ID :{0},原因:{1},请确认指定的业务是否存在动态分组 1213001=CMSI 接口访问异常 -1213002=用户管理接口访问异常 1213003=调用 CMSI 接口获取通知渠道数据异常 1213004=调用 CMSI 接口发送通知失败,错误码:{0},错误信息:{1} +1219001=蓝鲸登录接口访问异常 +1220001=用户管理接口访问异常 1214001=ARTIFACTORY API 返回数据异常 1214002=制品库中找不到节点:{0},请到制品库核实 diff --git a/src/backend/commons/common-iam/src/main/java/com/tencent/bk/job/common/iam/client/EsbIamClient.java b/src/backend/commons/common-iam/src/main/java/com/tencent/bk/job/common/iam/client/EsbIamClient.java index 7de3a870d7..4d012c4caf 100644 --- a/src/backend/commons/common-iam/src/main/java/com/tencent/bk/job/common/iam/client/EsbIamClient.java +++ b/src/backend/commons/common-iam/src/main/java/com/tencent/bk/job/common/iam/client/EsbIamClient.java @@ -33,7 +33,7 @@ import com.tencent.bk.job.common.esb.model.EsbReq; import com.tencent.bk.job.common.esb.model.EsbResp; import com.tencent.bk.job.common.esb.model.OpenApiRequestInfo; -import com.tencent.bk.job.common.esb.sdk.BkApiClient; +import com.tencent.bk.job.common.esb.sdk.BkApiV1Client; import com.tencent.bk.job.common.exception.InternalIamException; import com.tencent.bk.job.common.iam.dto.AuthByPathReq; import com.tencent.bk.job.common.iam.dto.BatchAuthByPathReq; @@ -47,6 +47,7 @@ import com.tencent.bk.job.common.iam.dto.GetApplyUrlResponse; import com.tencent.bk.job.common.iam.dto.RegisterResourceRequest; import com.tencent.bk.job.common.metrics.CommonMetricNames; +import com.tencent.bk.job.common.tenant.TenantEnvService; import com.tencent.bk.job.common.util.http.HttpHelperFactory; import com.tencent.bk.job.common.util.http.HttpMetricUtil; import com.tencent.bk.sdk.iam.constants.SystemId; @@ -64,7 +65,7 @@ * IAM API 调用客户端 */ @Slf4j -public class EsbIamClient extends BkApiClient implements IIamClient { +public class EsbIamClient extends BkApiV1Client implements IIamClient { private static final String API_GET_APPLY_URL = "/api/c/compapi/v2/iam/application/"; private static final String API_REGISTER_RESOURCE_URL = @@ -78,14 +79,16 @@ public class EsbIamClient extends BkApiClient implements IIamClient { public EsbIamClient(MeterRegistry meterRegistry, AppProperties appProperties, - EsbProperties esbProperties) { + EsbProperties esbProperties, + TenantEnvService tenantEnvService) { super( meterRegistry, IAM_API, esbProperties.getService().getUrl(), HttpHelperFactory.createHttpHelper( httpClientBuilder -> httpClientBuilder.addInterceptorLast(getLogBkApiRequestIdInterceptor()) - ) + ), + tenantEnvService ); this.authorization = BkApiAuthorization.appAuthorization(appProperties.getCode(), appProperties.getSecret(), "admin"); diff --git a/src/backend/commons/common-iam/src/main/java/com/tencent/bk/job/common/iam/config/IamAutoConfiguration.java b/src/backend/commons/common-iam/src/main/java/com/tencent/bk/job/common/iam/config/IamAutoConfiguration.java index a03490d893..1e59798f3b 100644 --- a/src/backend/commons/common-iam/src/main/java/com/tencent/bk/job/common/iam/config/IamAutoConfiguration.java +++ b/src/backend/commons/common-iam/src/main/java/com/tencent/bk/job/common/iam/config/IamAutoConfiguration.java @@ -37,6 +37,7 @@ import com.tencent.bk.job.common.iam.service.impl.BusinessAuthServiceImpl; import com.tencent.bk.job.common.iam.service.impl.WebAuthServiceImpl; import com.tencent.bk.job.common.iam.util.BusinessAuthHelper; +import com.tencent.bk.job.common.tenant.TenantEnvService; import com.tencent.bk.sdk.iam.config.IamConfiguration; import com.tencent.bk.sdk.iam.helper.AuthHelper; import com.tencent.bk.sdk.iam.service.HttpClientService; @@ -111,9 +112,10 @@ public AuthService authService(AuthHelper authHelper, IamConfiguration iamConfiguration, EsbProperties esbProperties, MessageI18nService i18nService, - ObjectProvider meterRegistryObjectProvider) { + ObjectProvider meterRegistryObjectProvider, + TenantEnvService tenantEnvService) { return new AuthServiceImpl(authHelper, iamConfiguration, esbProperties, i18nService, - meterRegistryObjectProvider.getIfAvailable()); + meterRegistryObjectProvider.getIfAvailable(), tenantEnvService); } @Bean @@ -123,7 +125,8 @@ public AppAuthService appAuthService(AuthHelper authHelper, PolicyService policyService, JobIamProperties jobIamProperties, EsbProperties esbProperties, - ObjectProvider meterRegistryObjectProvider) { + ObjectProvider meterRegistryObjectProvider, + TenantEnvService tenantEnvService) { return new AppAuthServiceImpl( authHelper, businessAuthHelper, @@ -131,7 +134,8 @@ public AppAuthService appAuthService(AuthHelper authHelper, policyService, jobIamProperties, esbProperties, - meterRegistryObjectProvider.getIfAvailable() + meterRegistryObjectProvider.getIfAvailable(), + tenantEnvService ); } diff --git a/src/backend/commons/common-iam/src/main/java/com/tencent/bk/job/common/iam/interceptor/AuthAppInterceptor.java b/src/backend/commons/common-iam/src/main/java/com/tencent/bk/job/common/iam/interceptor/AuthAppInterceptor.java index 537015c025..4f1244b5f0 100644 --- a/src/backend/commons/common-iam/src/main/java/com/tencent/bk/job/common/iam/interceptor/AuthAppInterceptor.java +++ b/src/backend/commons/common-iam/src/main/java/com/tencent/bk/job/common/iam/interceptor/AuthAppInterceptor.java @@ -28,14 +28,17 @@ package com.tencent.bk.job.common.iam.interceptor; import com.tencent.bk.job.common.annotation.JobInterceptor; +import com.tencent.bk.job.common.constant.ErrorCode; import com.tencent.bk.job.common.constant.InterceptorOrder; +import com.tencent.bk.job.common.exception.NotFoundException; import com.tencent.bk.job.common.iam.exception.PermissionDeniedException; import com.tencent.bk.job.common.iam.model.AuthResult; import com.tencent.bk.job.common.iam.service.BusinessAuthService; +import com.tencent.bk.job.common.model.BasicApp; import com.tencent.bk.job.common.model.dto.AppResourceScope; import com.tencent.bk.job.common.util.JobContextUtil; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.tuple.Pair; +import org.apache.commons.lang3.StringUtils; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; @@ -56,33 +59,37 @@ public AuthAppInterceptor(BusinessAuthService businessAuthService) { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String url = request.getRequestURI(); - Pair userScopePair; - userScopePair = findUserAndScope(); - if (userScopePair != null) { - String username = userScopePair.getLeft(); - AppResourceScope appResourceScope = userScopePair.getRight(); - if (appResourceScope != null) { - log.debug("Auth {} access_business {}", username, appResourceScope); - AuthResult authResult = businessAuthService.authAccessBusiness(username, appResourceScope); - if (!authResult.isPass()) { - throw new PermissionDeniedException(authResult); - } - } else { - log.debug("Ignore auth {} access_business public scope", username); + String username = JobContextUtil.getUsername(); + String tenantId = JobContextUtil.getTenantId(); + if (StringUtils.isEmpty(username)) { + log.debug("Can not find username for url:{}", url); + return true; + } + if (StringUtils.isEmpty(tenantId)) { + log.debug("Can not find tenantId for url:{}", url); + return true; + } + BasicApp app = JobContextUtil.getApp(); + if (app != null) { + checkAppTenant(app, tenantId); + AppResourceScope appResourceScope = new AppResourceScope(app.getId(), app.getScope()); + log.debug("Auth {} access_business {}", username, appResourceScope); + AuthResult authResult = businessAuthService.authAccessBusiness(username, appResourceScope); + if (!authResult.isPass()) { + throw new PermissionDeniedException(authResult); } } else { - log.debug("Can not find username/scope for url:{}", url); + log.debug("Ignore auth {} access_business public scope", username); } return true; } - private Pair findUserAndScope() { - String username = JobContextUtil.getUsername(); - AppResourceScope appResourceScope = JobContextUtil.getAppResourceScope(); - if (username != null && appResourceScope != null) { - return Pair.of(username, appResourceScope); + private void checkAppTenant(BasicApp app, String tenantId) { + if (!app.getTenantId().equals(tenantId)) { + log.warn("App is not belong to tenant, app: {}, userTenantId: {}", + app, tenantId); + throw new NotFoundException(ErrorCode.APP_NOT_EXIST); } - return null; } @Override diff --git a/src/backend/commons/common-iam/src/main/java/com/tencent/bk/job/common/iam/service/impl/AppAuthServiceImpl.java b/src/backend/commons/common-iam/src/main/java/com/tencent/bk/job/common/iam/service/impl/AppAuthServiceImpl.java index 95bf443960..c9cfd28e43 100644 --- a/src/backend/commons/common-iam/src/main/java/com/tencent/bk/job/common/iam/service/impl/AppAuthServiceImpl.java +++ b/src/backend/commons/common-iam/src/main/java/com/tencent/bk/job/common/iam/service/impl/AppAuthServiceImpl.java @@ -40,6 +40,7 @@ import com.tencent.bk.job.common.iam.util.BusinessAuthHelper; import com.tencent.bk.job.common.iam.util.IamUtil; import com.tencent.bk.job.common.model.dto.AppResourceScope; +import com.tencent.bk.job.common.tenant.TenantEnvService; import com.tencent.bk.sdk.iam.config.IamConfiguration; import com.tencent.bk.sdk.iam.constants.ExpressionOperationEnum; import com.tencent.bk.sdk.iam.constants.SystemId; @@ -77,7 +78,8 @@ public AppAuthServiceImpl(AuthHelper authHelper, PolicyService policyService, JobIamProperties jobIamProperties, EsbProperties esbProperties, - MeterRegistry meterRegistry) { + MeterRegistry meterRegistry, + TenantEnvService tenantEnvService) { this.authHelper = authHelper; this.businessAuthHelper = businessAuthHelper; this.policyService = policyService; @@ -85,7 +87,8 @@ public AppAuthServiceImpl(AuthHelper authHelper, this.iamClient = new EsbIamClient( meterRegistry, new AppProperties(iamConfiguration.getAppCode(), iamConfiguration.getAppSecret()), - esbProperties); + esbProperties, + tenantEnvService); } @Override diff --git a/src/backend/commons/common-iam/src/main/java/com/tencent/bk/job/common/iam/service/impl/AuthServiceImpl.java b/src/backend/commons/common-iam/src/main/java/com/tencent/bk/job/common/iam/service/impl/AuthServiceImpl.java index a6a12645b5..37e48016e7 100644 --- a/src/backend/commons/common-iam/src/main/java/com/tencent/bk/job/common/iam/service/impl/AuthServiceImpl.java +++ b/src/backend/commons/common-iam/src/main/java/com/tencent/bk/job/common/iam/service/impl/AuthServiceImpl.java @@ -29,7 +29,7 @@ import com.tencent.bk.job.common.esb.config.EsbProperties; import com.tencent.bk.job.common.esb.model.EsbResp; import com.tencent.bk.job.common.esb.model.iam.EsbActionDTO; -import com.tencent.bk.job.common.esb.model.iam.EsbApplyPermissionDTO; +import com.tencent.bk.job.common.esb.model.iam.OpenApiApplyPermissionDTO; import com.tencent.bk.job.common.esb.model.iam.EsbInstanceDTO; import com.tencent.bk.job.common.esb.model.iam.EsbRelatedResourceTypeDTO; import com.tencent.bk.job.common.exception.InternalException; @@ -45,6 +45,7 @@ import com.tencent.bk.job.common.iam.model.PermissionResourceGroup; import com.tencent.bk.job.common.iam.service.AuthService; import com.tencent.bk.job.common.iam.service.ResourceNameQueryService; +import com.tencent.bk.job.common.tenant.TenantEnvService; import com.tencent.bk.job.common.util.CustomCollectionUtils; import com.tencent.bk.sdk.iam.config.IamConfiguration; import com.tencent.bk.sdk.iam.constants.SystemId; @@ -78,13 +79,16 @@ public AuthServiceImpl(AuthHelper authHelper, IamConfiguration iamConfiguration, EsbProperties esbProperties, MessageI18nService i18nService, - MeterRegistry meterRegistry) { + MeterRegistry meterRegistry, + TenantEnvService tenantEnvService) { this.authHelper = authHelper; this.i18nService = i18nService; this.iamClient = new EsbIamClient( meterRegistry, new AppProperties(iamConfiguration.getAppCode(), iamConfiguration.getAppSecret()), - esbProperties); + esbProperties, + tenantEnvService + ); } @Override @@ -313,7 +317,7 @@ private Map>> groupResourcesByActio @Override public EsbResp buildEsbAuthFailResp(List permissionActionResources) { List actions = buildApplyActions(permissionActionResources); - EsbApplyPermissionDTO applyPermission = new EsbApplyPermissionDTO(); + OpenApiApplyPermissionDTO applyPermission = new OpenApiApplyPermissionDTO(); applyPermission.setSystemId(SystemId.JOB); applyPermission.setSystemName(i18nService.getI18n("system.bk_job")); applyPermission.setActions(actions.stream().map(this::convertToEsbAction).collect(Collectors.toList())); diff --git a/src/backend/commons/common-web/src/main/java/com/tencent/bk/job/common/web/interceptor/AppResourceScopeInterceptor.java b/src/backend/commons/common-web/src/main/java/com/tencent/bk/job/common/web/interceptor/BasicAppInterceptor.java similarity index 74% rename from src/backend/commons/common-web/src/main/java/com/tencent/bk/job/common/web/interceptor/AppResourceScopeInterceptor.java rename to src/backend/commons/common-web/src/main/java/com/tencent/bk/job/common/web/interceptor/BasicAppInterceptor.java index cd972ee575..1952563330 100644 --- a/src/backend/commons/common-web/src/main/java/com/tencent/bk/job/common/web/interceptor/AppResourceScopeInterceptor.java +++ b/src/backend/commons/common-web/src/main/java/com/tencent/bk/job/common/web/interceptor/BasicAppInterceptor.java @@ -30,9 +30,9 @@ import com.tencent.bk.job.common.constant.HttpRequestSourceEnum; import com.tencent.bk.job.common.constant.InterceptorOrder; import com.tencent.bk.job.common.constant.ResourceScopeTypeEnum; -import com.tencent.bk.job.common.model.dto.AppResourceScope; +import com.tencent.bk.job.common.model.BasicApp; import com.tencent.bk.job.common.model.dto.ResourceScope; -import com.tencent.bk.job.common.service.AppScopeMappingService; +import com.tencent.bk.job.common.service.AppCacheService; import com.tencent.bk.job.common.util.JobContextUtil; import com.tencent.bk.job.common.util.RequestUtil; import com.tencent.bk.job.common.util.json.JsonUtils; @@ -58,29 +58,29 @@ import static com.tencent.bk.job.common.constant.JobConstants.JOB_BUILD_IN_BIZ_SET_ID_MIN; /** - * Job AppResourceScope 处理 + * Job 业务信息处理拦截器 */ @Slf4j @JobInterceptor(pathPatterns = {"/web/**", "/service/**", "/esb/api/**"}, order = InterceptorOrder.Init.REWRITE_REQUEST) -public class AppResourceScopeInterceptor implements AsyncHandlerInterceptor { +public class BasicAppInterceptor implements AsyncHandlerInterceptor { private static final Pattern SCOPE_PATTERN = Pattern.compile("/scope/(\\w+)/(\\d+)"); private static final Pattern APP_PATTERN = Pattern.compile("/app/(\\d+)"); - private final AppScopeMappingService appScopeMappingService; + private final AppCacheService appCacheService; - private final AppResourceScopeParser webAppResourceScopeParser; + private final AppParser webAppParser; - private final AppResourceScopeParser esbAppResourceScopeParser; + private final AppParser esbAppParser; - private final AppResourceScopeParser internalAppResourceScopeParser; + private final AppParser internalAppParser; - public AppResourceScopeInterceptor(AppScopeMappingService appScopeMappingService) { - this.appScopeMappingService = appScopeMappingService; - this.webAppResourceScopeParser = new WebAppResourceScopeParser(); - this.esbAppResourceScopeParser = new EsbAppResourceScopeParser(); - this.internalAppResourceScopeParser = new InternalAppResourceScopeParser(); + public BasicAppInterceptor(AppCacheService appCacheService) { + this.appCacheService = appCacheService; + this.webAppParser = new WebAppParser(); + this.esbAppParser = new EsbAppParser(); + this.internalAppParser = new InternalAppParser(); } @Override @@ -90,7 +90,7 @@ public boolean preHandle(@NonNull HttpServletRequest request, if (!shouldFilter(request)) { return true; } - addAppResourceScope(request); + addApp(request); return true; } @@ -101,58 +101,56 @@ private boolean shouldFilter(HttpServletRequest request) { return uri.startsWith("/web/") || uri.startsWith("/service/") || uri.startsWith("/esb/"); } - private void addAppResourceScope(HttpServletRequest request) { + private void addApp(HttpServletRequest request) { HttpRequestSourceEnum requestSource = RequestUtil.parseHttpRequestSource(request); if (requestSource == HttpRequestSourceEnum.UNKNOWN) { return; } - AppResourceScope appResourceScope = null; + BasicApp app = null; switch (requestSource) { case WEB: - appResourceScope = webAppResourceScopeParser.parseAppResourceScope(request); - log.debug("Scope from web:{}", appResourceScope); + app = webAppParser.parseApp(request); break; case ESB: - appResourceScope = esbAppResourceScopeParser.parseAppResourceScope(request); - log.debug("Scope from esb:{}", appResourceScope); + app = esbAppParser.parseApp(request); break; case INTERNAL: - appResourceScope = internalAppResourceScopeParser.parseAppResourceScope(request); - log.debug("Scope from internal:{}", appResourceScope); + app = internalAppParser.parseApp(request); break; default: - log.debug("Ignore invalid scope: {}", requestSource); + log.debug("Ignore invalid app: {}", requestSource); break; } - if (appResourceScope != null) { - request.setAttribute("appResourceScope", appResourceScope); - JobContextUtil.setAppResourceScope(appResourceScope); + + if (log.isDebugEnabled()) { + log.debug("Parse app, requestSource: {}, app: {}", requestSource.name(), app); + } + + if (app != null) { + request.setAttribute("appResourceScope", app.getAppResourceScope()); + JobContextUtil.setApp(app); } else { - log.debug("AppResourceScope is empty"); + log.debug("Parsed app is empty"); } } /** - * 从 http 请求中解析 AppResourceScope + * 从 http 请求中解析 BasicApp */ - public interface AppResourceScopeParser { - AppResourceScope parseAppResourceScope(HttpServletRequest request); + public interface AppParser { + BasicApp parseApp(HttpServletRequest request); } /** - * 从 http 请求中解析 AppResourceScope - Web 请求 + * 从 http 请求中解析 BasicApp - Web 请求 */ - public class WebAppResourceScopeParser implements AppResourceScopeParser { + public class WebAppParser implements AppParser { @Override - public AppResourceScope parseAppResourceScope(HttpServletRequest request) { - return parseAppResourceScopeFromPath(request.getRequestURI()); - } - - private AppResourceScope parseAppResourceScopeFromPath(String requestURI) { - ResourceScope resourceScope = parseResourceScopeFromURI(requestURI); + public BasicApp parseApp(HttpServletRequest request) { + ResourceScope resourceScope = parseResourceScopeFromURI(request.getRequestURI()); if (resourceScope != null) { - return buildAppResourceScope(resourceScope); + return appCacheService.getApp(resourceScope); } return null; @@ -166,23 +164,18 @@ private ResourceScope parseResourceScopeFromURI(String requestURI) { } return resourceScope; } - - private AppResourceScope buildAppResourceScope(ResourceScope resourceScope) { - Long appId = appScopeMappingService.getAppIdByScope(resourceScope); - return new AppResourceScope(appId, resourceScope); - } } /** - * 从 http 请求中解析 AppResourceScope - ESB/蓝鲸网关请求 + * 从 http 请求中解析 BasicApp - ESB/蓝鲸网关请求 */ - public class EsbAppResourceScopeParser implements AppResourceScopeParser { + public class EsbAppParser implements AppParser { @Override - public AppResourceScope parseAppResourceScope(HttpServletRequest request) { + public BasicApp parseApp(HttpServletRequest request) { return parseAppResourceScopeFromQueryStringOrBody(request); } - private AppResourceScope parseAppResourceScopeFromQueryStringOrBody(HttpServletRequest request) { + private BasicApp parseAppResourceScopeFromQueryStringOrBody(HttpServletRequest request) { Map params = parseMultiValueFromQueryStringOrBody(request, "bk_scope_type", "bk_scope_id", "bk_biz_id"); String scopeType = params.get("bk_scope_type"); @@ -190,26 +183,25 @@ private AppResourceScope parseAppResourceScopeFromQueryStringOrBody(HttpServletR String bizIdStr = params.get("bk_biz_id"); if (StringUtils.isNotBlank(scopeType) && StringUtils.isNotBlank(scopeId)) { - return new AppResourceScope(scopeType, scopeId, null); + // 优先使用 bk_scope_type & bk_scope_id + return appCacheService.getApp(new ResourceScope(scopeType, scopeId)); } // 如果兼容bk_biz_id参数 if (FeatureToggle.checkFeature(FeatureIdConstants.FEATURE_BK_BIZ_ID_COMPATIBLE, ToggleEvaluateContext.EMPTY)) { + ResourceScope resourceScope = null; // 兼容当前业务ID参数 if (StringUtils.isNotBlank(bizIdStr)) { long bizId = Long.parseLong(bizIdStr); // [8000000,9999999]是迁移业务集之前约定的业务集ID范围。为了兼容老的API调用方,在这个范围内的bizId解析为业务集 scopeId = bizIdStr; if (bizId >= JOB_BUILD_IN_BIZ_SET_ID_MIN && bizId <= JOB_BUILD_IN_BIZ_SET_ID_MAX) { - Long appId = appScopeMappingService.getAppIdByScope(ResourceScopeTypeEnum.BIZ_SET.getValue(), - scopeId); - return new AppResourceScope(ResourceScopeTypeEnum.BIZ_SET, scopeId, appId); + resourceScope = new ResourceScope(ResourceScopeTypeEnum.BIZ_SET, scopeId); } else { - Long appId = appScopeMappingService.getAppIdByScope(ResourceScopeTypeEnum.BIZ.getValue(), - scopeId); - return new AppResourceScope(ResourceScopeTypeEnum.BIZ, scopeId, appId); + resourceScope = new ResourceScope(ResourceScopeTypeEnum.BIZ, scopeId); } + return appCacheService.getApp(resourceScope); } } // 其他情况返回null,后续拦截器会处理null @@ -270,11 +262,11 @@ private Map parseMultiValueFromQueryStringOrBody(HttpServletRequ } /** - * 从 http 请求中解析 AppResourceScope - Job 内部请求 + * 从 http 请求中解析 BasicApp - Job 内部请求 */ - public class InternalAppResourceScopeParser implements AppResourceScopeParser { + public class InternalAppParser implements AppParser { @Override - public AppResourceScope parseAppResourceScope(HttpServletRequest request) { + public BasicApp parseApp(HttpServletRequest request) { // 优先从 path 解析 Long appId = parseAppIdFromPath(request.getRequestURI()); if (appId == null) { @@ -288,7 +280,7 @@ public AppResourceScope parseAppResourceScope(HttpServletRequest request) { if (appId == null) { return null; } - return appScopeMappingService.getAppResourceScope(appId); + return appCacheService.getApp(appId); } private Long parseAppIdFromPath(String requestURI) { diff --git a/src/backend/commons/common-web/src/main/java/com/tencent/bk/job/common/web/interceptor/JobCommonInterceptor.java b/src/backend/commons/common-web/src/main/java/com/tencent/bk/job/common/web/interceptor/JobCommonInterceptor.java index 84961e1d41..9a29102ca3 100644 --- a/src/backend/commons/common-web/src/main/java/com/tencent/bk/job/common/web/interceptor/JobCommonInterceptor.java +++ b/src/backend/commons/common-web/src/main/java/com/tencent/bk/job/common/web/interceptor/JobCommonInterceptor.java @@ -81,6 +81,7 @@ public boolean preHandle(@NonNull HttpServletRequest request, addUsername(request); addLang(request); + addTenantId(request); return true; } @@ -184,7 +185,7 @@ public void postHandle(@NonNull HttpServletRequest request, ModelAndView modelAndView) { if (log.isDebugEnabled()) { log.debug("Post handler|{}|{}|{}|{}|{}", JobContextUtil.getRequestId(), - JobContextUtil.getAppResourceScope(), + JobContextUtil.getApp(), JobContextUtil.getUsername(), System.currentTimeMillis() - JobContextUtil.getStartTime(), request.getRequestURI()); } @@ -230,4 +231,15 @@ public void afterCompletion(@NonNull HttpServletRequest request, private boolean isClientOrServerError(HttpServletResponse response) { return response.getStatus() >= HttpStatus.SC_BAD_REQUEST; } + + private void addTenantId(HttpServletRequest request) { + // 使用 job-gateway 设置的租户 Header + String tenantId = request.getHeader(JobCommonHeaders.BK_TENANT_ID); + if (StringUtils.isEmpty(tenantId)) { + log.warn("Invalid request, tenant is not set"); + return; + } + log.debug("Add tenant id to JobContext, tenantId: {}", tenantId); + JobContextUtil.setTenantId(tenantId); + } } diff --git a/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/constant/ErrorCode.java b/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/constant/ErrorCode.java index ae221decd1..dbbfc6b3de 100644 --- a/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/constant/ErrorCode.java +++ b/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/constant/ErrorCode.java @@ -58,11 +58,8 @@ public class ErrorCode { // 根据业务ID查找动态分组失败,业务ID:{0},原因:{1},请确认指定的业务是否存在动态分组 public static final int FAIL_TO_FIND_DYNAMIC_GROUP_BY_BIZ = 1211004; - // PaaS异常 // CMSI接口访问异常 public static final int CMSI_API_ACCESS_ERROR = 1213001; - // 用户管理接口访问异常 - public static final int USER_MANAGE_API_ACCESS_ERROR = 1213002; // 调用CMSI接口获取通知渠道数据异常 public static final int CMSI_MSG_CHANNEL_DATA_ERROR = 1213003; // 调用CMSI接口发送通知失败,错误码:{0},错误信息:{1} @@ -96,6 +93,13 @@ public class ErrorCode { // 蓝鲸OpenAI接口数据超时 public static final int BK_OPEN_AI_API_DATA_TIMEOUT = 1218003; + // bk-login(蓝鲸登录) 接口调用异常 + public static final int BK_LOGIN_API_ERROR = 1219001; + + // bk-user(用户管理) 接口调用异常 + public static final int BK_USER_MANAGE_API_ERROR = 1220001; + + // ======== 系统错误-权限错误 ==================// // 用户({0})权限不足,请前往权限中心确认并申请补充后重试 public static final int PERMISSION_DENIED = 1238001; diff --git a/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/constant/JobCommonHeaders.java b/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/constant/JobCommonHeaders.java index cafe8f8a9f..7370d8f028 100644 --- a/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/constant/JobCommonHeaders.java +++ b/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/constant/JobCommonHeaders.java @@ -24,6 +24,9 @@ package com.tencent.bk.job.common.constant; +/** + * Job 通用 http header 定义 + */ public interface JobCommonHeaders { String APP_CODE = "X-AppCode"; @@ -47,4 +50,9 @@ public interface JobCommonHeaders { * 蓝鲸网关-从网关来的请求,与ESB请求区分 */ String BK_GATEWAY_FROM = "X-Bkapi-From"; + + /** + * 租户 ID + */ + String BK_TENANT_ID = "X-Bk-Tenant-Id"; } diff --git a/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/constant/TenantIdConstants.java b/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/constant/TenantIdConstants.java new file mode 100644 index 0000000000..bf15fd93d1 --- /dev/null +++ b/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/constant/TenantIdConstants.java @@ -0,0 +1,12 @@ +package com.tencent.bk.job.common.constant; + +/** + * 租户 ID 常量 + */ +public interface TenantIdConstants { + + /** + * 默认租户 ID + */ + String DEFAULT_TENANT_ID = "default"; +} diff --git a/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/context/JobContext.java b/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/context/JobContext.java index 6be922e8cc..628bab10e9 100644 --- a/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/context/JobContext.java +++ b/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/context/JobContext.java @@ -24,6 +24,7 @@ package com.tencent.bk.job.common.context; +import com.tencent.bk.job.common.model.BasicApp; import com.tencent.bk.job.common.model.dto.AppResourceScope; import io.micrometer.core.instrument.Tag; import lombok.Data; @@ -46,7 +47,7 @@ public class JobContext { private String username; - private AppResourceScope appResourceScope; + private BasicApp app; private String requestId; @@ -69,4 +70,9 @@ public class JobContext { private String httpMetricName; private AbstractList httpMetricTags; + + /** + * 租户 ID + */ + private String tenantId; } diff --git a/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/metrics/CommonMetricNames.java b/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/metrics/CommonMetricNames.java index 1b659596bf..c8b73c69ed 100644 --- a/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/metrics/CommonMetricNames.java +++ b/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/metrics/CommonMetricNames.java @@ -31,19 +31,19 @@ public class CommonMetricNames { /** * 仅统计调用ESB BK-LOGIN API的HTTP请求过程 */ - public static final String ESB_BK_LOGIN_API_HTTP = "job.client.bk.login.api.http"; + public static final String BK_LOGIN_API_HTTP = "job.client.bk.login.api.http"; /** * 仅统计调用ESB BK-LOGIN API的整个过程,含反序列化 */ - public static final String ESB_BK_LOGIN_API = "job.client.bk.login.api"; + public static final String BK_LOGIN_API = "job.client.bk.login.api"; /** * 仅统计调用ESB USER-MANAGE API的HTTP请求过程 */ - public static final String ESB_USER_MANAGE_API_HTTP = "job.client.user.manage.api.http"; + public static final String USER_MANAGE_API_HTTP = "job.client.user.manage.api.http"; /** * 统计调用ESB 用户管理 API的整个过程,含反序列化 */ - public static final String ESB_USER_MANAGE_API = "job.client.user.manage.api"; + public static final String USER_MANAGE_API = "job.client.user.manage.api"; /** * 仅统计调用ESB CMSI API的HTTP请求过程 */ diff --git a/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/model/BasicApp.java b/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/model/BasicApp.java new file mode 100644 index 0000000000..9793302ecb --- /dev/null +++ b/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/model/BasicApp.java @@ -0,0 +1,52 @@ +package com.tencent.bk.job.common.model; + +import com.tencent.bk.job.common.model.dto.AppResourceScope; +import com.tencent.bk.job.common.model.dto.ResourceScope; +import lombok.Data; + +import java.util.StringJoiner; + +/** + * 业务基础信息 + */ +@Data +public class BasicApp { + /** + * 业务ID(内部ID) + */ + private Long id; + + /** + * 资源管理空间 + */ + private ResourceScope scope; + + /** + * 业务名称 + */ + private String name; + + /** + * 业务所属租户 ID + */ + private String tenantId; + + private AppResourceScope appResourceScope; + + public AppResourceScope getAppResourceScope() { + if (appResourceScope == null) { + appResourceScope = new AppResourceScope(id, scope); + } + return appResourceScope; + } + + @Override + public String toString() { + return new StringJoiner(", ", BasicApp.class.getSimpleName() + "[", "]") + .add("id=" + id) + .add("scope=" + scope) + .add("name='" + name + "'") + .add("tenantId='" + tenantId + "'") + .toString(); + } +} diff --git a/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/model/dto/ApplicationDTO.java b/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/model/dto/ApplicationDTO.java index b9e7bdc59a..e6344053db 100644 --- a/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/model/dto/ApplicationDTO.java +++ b/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/model/dto/ApplicationDTO.java @@ -84,6 +84,11 @@ public class ApplicationDTO { */ private ApplicationAttrsDO attrs; + /** + * 业务所属租户 ID + */ + private String tenantId; + @JsonIgnore public boolean isBiz() { return scope != null && scope.getType() == ResourceScopeTypeEnum.BIZ; diff --git a/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/model/dto/BkUserDTO.java b/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/model/dto/BkUserDTO.java index 2f71fcb040..f2abf4439d 100644 --- a/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/model/dto/BkUserDTO.java +++ b/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/model/dto/BkUserDTO.java @@ -28,6 +28,8 @@ import lombok.Setter; import lombok.ToString; +import java.util.Objects; + /** * 作业平台通用的用户DTO */ @@ -40,11 +42,7 @@ public class BkUserDTO { */ private Long id; /** - * 用户 UID - */ - private String uid; - /** - * 用户名 + * 用户 id */ private String username; /** @@ -72,4 +70,34 @@ public class BkUserDTO { * 用户微信 */ private String wxUserId; + + /** + * 用户所属租户 ID + */ + private String tenantId; + + /** + * 用户语言,枚举值:zh-cn / en + */ + private String language; + + /** + * 获取用户的完整账号名称 + */ + public String getFullName() { + return displayName + ":" + username + "@" + tenantId; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BkUserDTO bkUserDTO = (BkUserDTO) o; + return Objects.equals(username, bkUserDTO.username); + } + + @Override + public int hashCode() { + return Objects.hash(username); + } } diff --git a/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/service/AppCacheService.java b/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/service/AppCacheService.java new file mode 100644 index 0000000000..ced21176c3 --- /dev/null +++ b/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/service/AppCacheService.java @@ -0,0 +1,13 @@ +package com.tencent.bk.job.common.service; + +import com.tencent.bk.job.common.model.BasicApp; +import com.tencent.bk.job.common.model.dto.ResourceScope; + +public interface AppCacheService extends AppScopeMappingService { + + BasicApp getApp(ResourceScope resourceScope); + + BasicApp getApp(Long appId); + + +} diff --git a/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/dao/notify/EsbUserInfoDAO.java b/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/tenant/TenantAutoConfiguration.java similarity index 72% rename from src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/dao/notify/EsbUserInfoDAO.java rename to src/backend/commons/common/src/main/java/com/tencent/bk/job/common/tenant/TenantAutoConfiguration.java index 4eb88a3fd5..2bfb13e700 100644 --- a/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/dao/notify/EsbUserInfoDAO.java +++ b/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/tenant/TenantAutoConfiguration.java @@ -22,25 +22,18 @@ * IN THE SOFTWARE. */ -package com.tencent.bk.job.manage.dao.notify; +package com.tencent.bk.job.common.tenant; -import com.tencent.bk.job.manage.model.dto.notify.EsbUserInfoDTO; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; -import java.util.Collection; -import java.util.List; - -public interface EsbUserInfoDAO { - - int insertEsbUserInfo(EsbUserInfoDTO esbUserInfoDTO); - - int deleteEsbUserInfoById(Long id); - - List listEsbUserInfo(); - - List listEsbUserInfo(String prefixStr, Long limit); - - List listEsbUserInfo(Collection userNames); - - List listExistUserName(Collection userNames); +@Configuration(proxyBeanMethods = false) +@EnableConfigurationProperties(TenantProperties.class) +public class TenantAutoConfiguration { + @Bean + public TenantEnvService tenantEnvService(TenantProperties tenantProperties) { + return new TenantEnvServiceImpl(tenantProperties); + } } diff --git a/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/tenant/TenantEnvService.java b/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/tenant/TenantEnvService.java new file mode 100644 index 0000000000..cd59f4eb1d --- /dev/null +++ b/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/tenant/TenantEnvService.java @@ -0,0 +1,11 @@ +package com.tencent.bk.job.common.tenant; + +/** + * 租户环境信息 Service + */ +public interface TenantEnvService { + /** + * 该环境是否支持多租户 + */ + boolean isTenantEnabled(); +} diff --git a/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/tenant/TenantEnvServiceImpl.java b/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/tenant/TenantEnvServiceImpl.java new file mode 100644 index 0000000000..ef46974ab2 --- /dev/null +++ b/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/tenant/TenantEnvServiceImpl.java @@ -0,0 +1,17 @@ +package com.tencent.bk.job.common.tenant; + +public class TenantEnvServiceImpl implements TenantEnvService { + private final TenantProperties tenantProperties; + + public TenantEnvServiceImpl(TenantProperties tenantProperties) { + this.tenantProperties = tenantProperties; + } + + + /** + * 该环境是否支持多租户 + */ + public boolean isTenantEnabled() { + return tenantProperties.isEnabled(); + } +} diff --git a/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/tenant/TenantProperties.java b/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/tenant/TenantProperties.java new file mode 100644 index 0000000000..62c78f9add --- /dev/null +++ b/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/tenant/TenantProperties.java @@ -0,0 +1,44 @@ +/* + * Tencent is pleased to support the open source community by making BK-JOB蓝鲸智云作业平台 available. + * + * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-JOB蓝鲸智云作业平台 is licensed under the MIT License. + * + * License for BK-JOB蓝鲸智云作业平台: + * -------------------------------------------------------------------- + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and + * to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of + * the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO + * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +package com.tencent.bk.job.common.tenant; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * 多租户配置 + */ +@Getter +@Setter +@ToString +@ConfigurationProperties(prefix = "tenant") +public class TenantProperties { + /** + * 是否支持多租户 + */ + private boolean enabled; +} diff --git a/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/util/JobContextUtil.java b/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/util/JobContextUtil.java index 75d14b8250..a055ec152b 100644 --- a/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/util/JobContextUtil.java +++ b/src/backend/commons/common/src/main/java/com/tencent/bk/job/common/util/JobContextUtil.java @@ -27,7 +27,7 @@ import com.tencent.bk.job.common.context.JobContext; import com.tencent.bk.job.common.context.JobContextThreadLocal; import com.tencent.bk.job.common.i18n.locale.LocaleUtils; -import com.tencent.bk.job.common.model.dto.AppResourceScope; +import com.tencent.bk.job.common.model.BasicApp; import io.micrometer.core.instrument.Tag; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.tuple.Pair; @@ -91,19 +91,19 @@ public static void setUsername(String username) { jobContext.setUsername(username); } - public static AppResourceScope getAppResourceScope() { + public static BasicApp getApp() { JobContext jobContext = JobContextThreadLocal.get(); - AppResourceScope appResourceScope = null; + BasicApp app = null; if (jobContext != null) { - appResourceScope = jobContext.getAppResourceScope(); + app = jobContext.getApp(); } - return appResourceScope; + return app; } - public static void setAppResourceScope(AppResourceScope appResourceScope) { + public static void setApp(BasicApp app) { JobContext jobContext = getOrInitContext(); - jobContext.setAppResourceScope(appResourceScope); + jobContext.setApp(app); } public static String getRequestId() { @@ -263,4 +263,14 @@ public static void setRequestFrom(String requestFrom) { JobContext jobContext = getOrInitContext(); jobContext.setRequestFrom(requestFrom); } + + public static String getTenantId() { + JobContext jobContext = JobContextThreadLocal.get(); + return jobContext == null ? null : jobContext.getTenantId(); + } + + public static void setTenantId(String tenantId) { + JobContext jobContext = getOrInitContext(); + jobContext.setTenantId(tenantId); + } } diff --git a/src/backend/commons/common/src/main/resources/META-INF/spring.factories b/src/backend/commons/common/src/main/resources/META-INF/spring.factories index 09450a20ce..af1fafec4d 100644 --- a/src/backend/commons/common/src/main/resources/META-INF/spring.factories +++ b/src/backend/commons/common/src/main/resources/META-INF/spring.factories @@ -1,2 +1,3 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ -com.tencent.bk.job.common.service.config.JobCommonAutoConfiguration +com.tencent.bk.job.common.service.config.JobCommonAutoConfiguration, \ +com.tencent.bk.job.common.tenant.TenantAutoConfiguration diff --git a/src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/config/BkApiGatewayProperties.java b/src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/config/BkApiGatewayProperties.java index f143a2b4d8..42188f86ba 100644 --- a/src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/config/BkApiGatewayProperties.java +++ b/src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/config/BkApiGatewayProperties.java @@ -45,6 +45,16 @@ public class BkApiGatewayProperties { private ApiGwConfig bkApiGateway; + /** + * 蓝鲸登录相关配置 + */ + private ApiGwConfig bkLogin; + + /** + * 蓝鲸用户管理相关配置 + */ + private ApiGwConfig bkUser; + @Getter @Setter @ToString diff --git a/src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/constants/BkErrorCodeEnum.java b/src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/constants/BkErrorCodeEnum.java new file mode 100644 index 0000000000..282c1372f0 --- /dev/null +++ b/src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/constants/BkErrorCodeEnum.java @@ -0,0 +1,120 @@ +package com.tencent.bk.job.common.esb.constants; + +/* + * Tencent is pleased to support the open source community by making BK-JOB蓝鲸智云作业平台 available. + * + * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-JOB蓝鲸智云作业平台 is licensed under the MIT License. + * + * License for BK-JOB蓝鲸智云作业平台: + * -------------------------------------------------------------------- + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and + * to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of + * the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO + * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +/** + * 蓝鲸通用错误分类定义 + */ +public enum BkErrorCodeEnum { + /** + * 参数不合法 + */ + INVALID_ARGUMENT("INVALID_ARGUMENT", 400), + /** + * 参数符合参数格式,但参数不符合业务规则 + */ + INVALID_REQUEST("INVALID_REQUEST", 400), + /** + * 客户端指定了无效范围 + */ + OUT_OF_RANGE("OUT_OF_RANGE", 400), + /** + * 请求无法在当前系统状态下执行,例如删除非空目录 + */ + FAILED_PRECONDITION("FAILED_PRECONDITION", 400), + /** + * 未提供身份认证凭证 + */ + UNAUTHENTICATED("UNAUTHENTICATED", 401), + /** + * 权限中心没有相关权限(有协议要求) + */ + IAM_NO_PERMISSION("IAM_NO_PERMISSION", 403), + /** + * 没有相关权限(非权限中心) + */ + NO_PERMISSION("NO_PERMISSION", 403), + /** + * 资源不存在 + */ + NOT_FOUND("NOT_FOUND", 404), + /** + * 客户端尝试创建的资源已存在 + */ + ALREADY_EXISTS("ALREADY_EXISTS", 409), + /** + * 并发冲突,例如读取/修改/写入冲突 + */ + ABORTED("ABORTED", 409), + /** + * 超过频率限制 + */ + RATELIMIT_EXCEED("RATELIMIT_EXCEED", 429), + /** + * 资源配额不足 + */ + RESOURCE_EXHAUSTED("RESOURCE_EXHAUSTED", 429), + /** + * 出现内部服务器错误 + */ + INTERNAL("INTERNAL", 500), + /** + * 出现未知的服务器错误 + */ + UNKNOWN("UNKNOWN", 500), + /** + * API 方法未通过服务器实现 + */ + NOT_IMPLEMENTED("NOT_IMPLEMENTED", 501), + /** + * 服务不可用。通常是由于服务器宕机了 + */ + UNAVAILABLE("UNAVAILABLE", 503); + + private final String errorCode; + private final int statusCode; + + BkErrorCodeEnum(String errorCode, int statusCode) { + this.errorCode = errorCode; + this.statusCode = statusCode; + } + + public String getErrorCode() { + return errorCode; + } + + public int getStatusCode() { + return statusCode; + } + + public static BkErrorCodeEnum valOf(String errorCode) { + for (BkErrorCodeEnum errorType : values()) { + if (errorType.getErrorCode().equals(errorCode)) { + return errorType; + } + } + throw new IllegalArgumentException("No BkErrorCodeEnum constant: " + errorCode); + } +} diff --git a/src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/exception/BkOpenApiException.java b/src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/exception/BkOpenApiException.java new file mode 100644 index 0000000000..ce1292a5fc --- /dev/null +++ b/src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/exception/BkOpenApiException.java @@ -0,0 +1,43 @@ +package com.tencent.bk.job.common.esb.exception; + +import com.tencent.bk.job.common.esb.model.OpenApiError; +import com.tencent.bk.job.common.esb.model.OpenApiV1Error; +import lombok.Getter; + +/** + * 蓝鲸 Open API 调用异常 + */ +@Getter +public class BkOpenApiException extends RuntimeException { + + /** + * http 状态码 + */ + private final int statusCode; + + /** + * 蓝鲸 v2(新版)错误信息 + */ + private OpenApiError error; + + /** + * 蓝鲸 v1 版本错误信息 + */ + private OpenApiV1Error openApiV1Error; + + + public BkOpenApiException(int statusCode) { + this.statusCode = statusCode; + } + + public BkOpenApiException(int statusCode, OpenApiError error) { + this.statusCode = statusCode; + this.error = error; + } + + public BkOpenApiException(int statusCode, OpenApiV1Error openApiV1Error) { + this.statusCode = statusCode; + this.openApiV1Error = openApiV1Error; + } + +} diff --git a/src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/model/EsbResp.java b/src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/model/EsbResp.java index a6722407e1..1ffa686698 100644 --- a/src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/model/EsbResp.java +++ b/src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/model/EsbResp.java @@ -28,7 +28,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.tencent.bk.job.common.constant.ErrorCode; -import com.tencent.bk.job.common.esb.model.iam.EsbApplyPermissionDTO; +import com.tencent.bk.job.common.esb.model.iam.OpenApiApplyPermissionDTO; import com.tencent.bk.job.common.exception.ServiceException; import com.tencent.bk.job.common.model.ValidateResult; import com.tencent.bk.job.common.model.error.ErrorDetailDTO; @@ -61,7 +61,7 @@ public class EsbResp { /** * 无权限返回数据 */ - private EsbApplyPermissionDTO permission; + private OpenApiApplyPermissionDTO permission; private EsbResp(T data) { this.code = ErrorCode.RESULT_OK; @@ -101,7 +101,7 @@ public static EsbResp buildCommonFailResp(ServiceException e) { return buildCommonFailResp(e.getErrorCode(), e.getErrorParams(), null); } - public static EsbResp buildAuthFailResult(EsbApplyPermissionDTO permission) { + public static EsbResp buildAuthFailResult(OpenApiApplyPermissionDTO permission) { EsbResp esbResp = buildCommonFailResp(ErrorCode.BK_PERMISSION_DENIED, new String[]{JobContextUtil.getUsername()}, null); esbResp.setPermission(permission); diff --git a/src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/model/OpenApiError.java b/src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/model/OpenApiError.java new file mode 100644 index 0000000000..95c320ace0 --- /dev/null +++ b/src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/model/OpenApiError.java @@ -0,0 +1,76 @@ +package com.tencent.bk.job.common.esb.model; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.tencent.bk.job.common.esb.constants.BkErrorCodeEnum; +import com.tencent.bk.job.common.esb.model.iam.OpenApiApplyPermissionDTO; +import lombok.Data; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +/** + * 蓝鲸新版 http open api 协议定义的标准响应 - 错误信息 + */ +@Data +@JsonDeserialize(using = OpenApiError.OpenApiErrorDeserializer.class) +public class OpenApiError { + + /** + * 语义化的错误英文标识, 整个蓝鲸会定义一套通用的错误大分类; 作用: 上游编码基于这个做代码层面的逻辑判断(所以必须是确定的枚举); + * + * @see BkErrorCodeEnum + */ + private String code; + + /** + * 给用户看到的错误说明, 需要支持国际化 + */ + private String message; + + /** + * 返回的数据用于给调用方针对这个code做相应的一些处理, 例如无权限返回申请权限信息, 登录认证失败返回跳转url等 + */ + private Object data; + + /** + * 错误详情 + */ + private List> details; + + /** + * OpenApiError 自定义反序列化 + */ + static class OpenApiErrorDeserializer extends JsonDeserializer { + @Override + public OpenApiError deserialize(JsonParser p, DeserializationContext ctx) throws IOException { + ObjectMapper mapper = (ObjectMapper) p.getCodec(); + JsonNode node = mapper.readTree(p); + + OpenApiError error = new OpenApiError(); + error.setCode(node.get("code").asText()); + error.setMessage(node.get("message").asText()); + List> details = mapper.convertValue( + node.get("details"), + mapper.getTypeFactory().constructCollectionType(List.class, Map.class) + ); + error.setDetails(details); + + // 根据 code 字段的值来处理 data 字段 + String code = error.getCode(); + if (BkErrorCodeEnum.IAM_NO_PERMISSION.getErrorCode().equals(code)) { + error.setData(mapper.treeToValue(node.get("data"), OpenApiApplyPermissionDTO.class)); + } else { + // 处理其他类型的 data,蓝鲸 API 规范暂未定义具体的 schema,所以直接设置为原始的 JsonNode + error.setData(node.get("data")); + } + + return error; + } + } +} diff --git a/src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/model/OpenApiRequestInfo.java b/src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/model/OpenApiRequestInfo.java index 67b4b57bf1..947f3c0496 100644 --- a/src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/model/OpenApiRequestInfo.java +++ b/src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/model/OpenApiRequestInfo.java @@ -28,10 +28,13 @@ import com.tencent.bk.job.common.util.http.RetryModeEnum; import lombok.Data; import org.apache.commons.lang3.StringUtils; +import org.apache.http.Header; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -62,6 +65,8 @@ public class OpenApiRequestInfo { */ private final Boolean idempotent; + private final List
headers; + public OpenApiRequestInfo(Builder builder) { this.method = builder.method; this.uri = builder.uri; @@ -71,6 +76,7 @@ public OpenApiRequestInfo(Builder builder) { this.authorization = builder.authorization; this.retryMode = builder.retryMode; this.idempotent = builder.idempotent; + this.headers = builder.headers; } public static Builder builder() { @@ -86,6 +92,7 @@ public static class Builder { private BkApiAuthorization authorization; private RetryModeEnum retryMode = RetryModeEnum.SAFE_GUARANTEED; private Boolean idempotent; + private List
headers; public Builder method(HttpMethodEnum method) { this.method = method; @@ -145,6 +152,19 @@ public Builder setIdempotent(Boolean idempotent) { return this; } + public Builder setHeaders(List
herders) { + this.headers = herders; + return this; + } + + public Builder addHeader(Header header) { + if (headers == null) { + headers = new ArrayList<>(); + } + headers.add(header); + return this; + } + public OpenApiRequestInfo build() { return new OpenApiRequestInfo<>(this); } diff --git a/src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/model/OpenApiResponse.java b/src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/model/OpenApiResponse.java new file mode 100644 index 0000000000..d88f227e38 --- /dev/null +++ b/src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/model/OpenApiResponse.java @@ -0,0 +1,36 @@ +package com.tencent.bk.job.common.esb.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.tencent.bk.job.common.esb.model.iam.OpenApiApplyPermissionDTO; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + * 蓝鲸新版 http open api 协议定义的标准响应 + */ +@Data +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@Slf4j +public class OpenApiResponse { + + private T data; + + private OpenApiError error; + + /** + * 无权限返回数据 + */ + private OpenApiApplyPermissionDTO permission; + + private OpenApiResponse(T data) { + this.data = data; + } + + + public static OpenApiResponse success(T data) { + return new OpenApiResponse<>(data); + } + +} diff --git a/src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/model/OpenApiV1Error.java b/src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/model/OpenApiV1Error.java new file mode 100644 index 0000000000..3a366aeaca --- /dev/null +++ b/src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/model/OpenApiV1Error.java @@ -0,0 +1,33 @@ +package com.tencent.bk.job.common.esb.model; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.tencent.bk.job.common.esb.constants.BkErrorCodeEnum; +import com.tencent.bk.job.common.esb.model.iam.OpenApiApplyPermissionDTO; +import lombok.Data; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +/** + * 蓝鲸旧版 http open api 协议定义的标准响应 + */ +@Data +public class OpenApiV1Error { + + /** + * 错误码 + * @see com.tencent.bk.job.common.constant.ErrorCode + */ + private Integer code; + + /** + * 给用户看到的错误说明 + */ + private String message; +} diff --git a/src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/model/iam/EsbApplyPermissionDTO.java b/src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/model/iam/OpenApiApplyPermissionDTO.java similarity index 97% rename from src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/model/iam/EsbApplyPermissionDTO.java rename to src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/model/iam/OpenApiApplyPermissionDTO.java index b6c1637029..10f05359a4 100644 --- a/src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/model/iam/EsbApplyPermissionDTO.java +++ b/src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/model/iam/OpenApiApplyPermissionDTO.java @@ -34,7 +34,7 @@ * 返回给第三方api调用者的无权限申请url信息 */ @Data -public class EsbApplyPermissionDTO { +public class OpenApiApplyPermissionDTO { @JsonProperty("system_id") private String systemId; @JsonProperty("system_name") diff --git a/src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/sdk/BkApiClient.java b/src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/sdk/BaseBkApiClient.java similarity index 62% rename from src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/sdk/BkApiClient.java rename to src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/sdk/BaseBkApiClient.java index 4888ff8aec..36211469ce 100644 --- a/src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/sdk/BkApiClient.java +++ b/src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/sdk/BaseBkApiClient.java @@ -1,3 +1,5 @@ +package com.tencent.bk.job.common.esb.sdk; + /* * Tencent is pleased to support the open source community by making BK-JOB蓝鲸智云作业平台 available. * @@ -22,19 +24,19 @@ * IN THE SOFTWARE. */ -package com.tencent.bk.job.common.esb.sdk; - import com.fasterxml.jackson.core.type.TypeReference; import com.tencent.bk.job.common.constant.ErrorCode; import com.tencent.bk.job.common.constant.HttpMethodEnum; import com.tencent.bk.job.common.constant.JobCommonHeaders; +import com.tencent.bk.job.common.constant.TenantIdConstants; import com.tencent.bk.job.common.esb.constants.EsbLang; +import com.tencent.bk.job.common.esb.exception.BkOpenApiException; import com.tencent.bk.job.common.esb.interceptor.LogBkApiRequestIdInterceptor; import com.tencent.bk.job.common.esb.metrics.EsbMetricTags; import com.tencent.bk.job.common.esb.model.BkApiAuthorization; -import com.tencent.bk.job.common.esb.model.EsbResp; import com.tencent.bk.job.common.esb.model.OpenApiRequestInfo; import com.tencent.bk.job.common.exception.InternalException; +import com.tencent.bk.job.common.tenant.TenantEnvService; import com.tencent.bk.job.common.util.JobContextUtil; import com.tencent.bk.job.common.util.http.HttpHelper; import com.tencent.bk.job.common.util.http.HttpRequest; @@ -44,6 +46,7 @@ import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.Tags; +import lombok.Setter; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.http.Header; @@ -51,29 +54,35 @@ import org.apache.http.message.BasicHeader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; import javax.servlet.http.HttpServletRequest; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.concurrent.TimeUnit; import static com.tencent.bk.job.common.constant.HttpHeader.HDR_BK_LANG; import static com.tencent.bk.job.common.i18n.locale.LocaleUtils.COMMON_LANG_HEADER; /** - * 蓝鲸API(组件 API(ESB)、网关 API(蓝鲸 ApiGateway))调用客户端 + * 蓝鲸API(组件 API(ESB)、网关 API(蓝鲸 ApiGateway))调用客户端基础类 */ -public class BkApiClient { +public class BaseBkApiClient { private String lang; private Logger log = LoggerFactory.getLogger(this.getClass()); private final String baseAccessUrl; private final HttpHelper defaultHttpHelper; private final MeterRegistry meterRegistry; + private final TenantEnvService tenantEnvService; private static final String BK_API_AUTH_HEADER = "X-Bkapi-Authorization"; /** * API调用度量指标名称 */ private final String metricName; + + @Setter private JsonMapper jsonMapper = JsonMapper.nonNullMapper(); /** @@ -82,34 +91,28 @@ public class BkApiClient { * @param baseAccessUrl API 服务访问地址 * @param defaultHttpHelper http 请求处理客户端 */ - public BkApiClient(MeterRegistry meterRegistry, - String metricName, - String baseAccessUrl, - HttpHelper defaultHttpHelper) { + public BaseBkApiClient(MeterRegistry meterRegistry, + String metricName, + String baseAccessUrl, + HttpHelper defaultHttpHelper, + TenantEnvService tenantEnvService) { this.meterRegistry = meterRegistry; this.metricName = metricName; this.baseAccessUrl = baseAccessUrl; this.defaultHttpHelper = defaultHttpHelper; + this.tenantEnvService = tenantEnvService; } - public BkApiClient(MeterRegistry meterRegistry, - String metricName, - String baseAccessUrl, - HttpHelper defaultHttpHelper, - String lang) { - this(meterRegistry, metricName, baseAccessUrl, defaultHttpHelper); + public BaseBkApiClient(MeterRegistry meterRegistry, + String metricName, + String baseAccessUrl, + HttpHelper defaultHttpHelper, + String lang, + TenantEnvService tenantEnvService) { + this(meterRegistry, metricName, baseAccessUrl, defaultHttpHelper, tenantEnvService); this.lang = lang; } - /** - * 配置自定义的 JsonMapper, 用于序列化 Json 数据 - * - * @param jsonMapper jsonMapper - */ - public void setJsonMapper(JsonMapper jsonMapper) { - this.jsonMapper = jsonMapper; - } - /** * 配置自定义的日志 logger * @@ -119,21 +122,21 @@ public void setLogger(Logger logger) { this.log = logger; } - public EsbResp doRequest(OpenApiRequestInfo requestInfo, - TypeReference> typeReference, - HttpHelper httpHelper) { + public R doRequest(OpenApiRequestInfo requestInfo, + TypeReference typeReference, + HttpHelper httpHelper) throws BkOpenApiException { return doRequest(requestInfo, typeReference, null, httpHelper); } - public EsbResp doRequest(OpenApiRequestInfo requestInfo, - TypeReference> typeReference) { + public R doRequest(OpenApiRequestInfo requestInfo, + TypeReference typeReference) throws BkOpenApiException { return doRequest(requestInfo, typeReference, null); } - public EsbResp doRequest(OpenApiRequestInfo requestInfo, - TypeReference> typeReference, - BkApiLogStrategy logStrategy, - HttpHelper httpHelper) { + public R doRequest(OpenApiRequestInfo requestInfo, + TypeReference typeReference, + BkApiLogStrategy logStrategy, + HttpHelper httpHelper) throws BkOpenApiException { HttpMethodEnum httpMethod = requestInfo.getMethod(); BkApiContext apiContext = new BkApiContext<>(httpMethod.name(), requestInfo.getUri(), requestInfo.getBody(), null, null, 0, false); @@ -173,65 +176,12 @@ public EsbResp doRequest(OpenApiRequestInfo requestInfo, } } - public R requestApiAndWrapResponse(OpenApiRequestInfo requestInfo, - TypeReference typeReference, - HttpHelper httpHelper) { - if (log.isInfoEnabled()) { - log.info("[AbstractBkApiClient] Request|method={}|uri={}|reqStr={}", - requestInfo.getMethod().name(), requestInfo.getUri(), - requestInfo.getBody() != null ? JsonUtils.toJsonWithoutSkippedFields(requestInfo.getBody()) : null); - } - String uri = requestInfo.getUri(); - String respStr = null; - String status = EsbMetricTags.VALUE_STATUS_OK; - HttpMethodEnum httpMethod = requestInfo.getMethod(); - long start = System.currentTimeMillis(); - String bkApiRequestId = null; - boolean success = true; - try { - HttpResponse response = requestApi(httpHelper, requestInfo); - bkApiRequestId = extractBkApiRequestId(response); - respStr = response.getEntity(); - if (StringUtils.isBlank(respStr)) { - String errorMsg = "[AbstractBkApiClient] " + httpMethod.name() + " " - + uri + ", error: " + "Response is blank"; - log.warn( - "[AbstractBkApiClient] fail: Response is blank| requestId={}|method={}|uri={}", - bkApiRequestId, - httpMethod.name(), - uri - ); - status = EsbMetricTags.VALUE_STATUS_ERROR; - throw new InternalException(errorMsg, ErrorCode.API_ERROR); - } - return jsonMapper.fromJson(respStr, typeReference); - } catch (Throwable e) { - success = false; - String errorMsg = "Fail to request api|method=" + httpMethod.name() - + "|uri=" + uri; - log.error(errorMsg, e); - status = EsbMetricTags.VALUE_STATUS_ERROR; - throw new InternalException("Fail to request bk api", e, ErrorCode.API_ERROR); - } finally { - long cost = System.currentTimeMillis() - start; - if (meterRegistry != null) { - meterRegistry.timer(metricName, buildMetricTags(uri, status)) - .record(cost, TimeUnit.MILLISECONDS); - } - if (log.isInfoEnabled()) { - log.info("[AbstractBkApiClient] Response|requestId={}|method={}|uri={}|success={}" - + "|costTime={}|resp={}", - bkApiRequestId, httpMethod.name(), requestInfo.getUri(), success, cost, respStr); - } - } - } - - private EsbResp requestApiAndWrapResponse(OpenApiRequestInfo requestInfo, - BkApiContext apiContext, - TypeReference> typeReference, - HttpHelper httpHelper) { + private R requestApiAndWrapResponse(OpenApiRequestInfo requestInfo, + BkApiContext apiContext, + TypeReference typeReference, + HttpHelper httpHelper) throws BkOpenApiException { String uri = apiContext.getUri(); - EsbResp esbResp; + R responseBody; String respStr; String status = EsbMetricTags.VALUE_STATUS_OK; HttpMethodEnum httpMethod = requestInfo.getMethod(); @@ -246,8 +196,7 @@ private EsbResp requestApiAndWrapResponse(OpenApiRequestInfo reques apiContext.setOriginResp(response.getEntity()); if (StringUtils.isBlank(respStr)) { - String errorMsg = "[AbstractBkApiClient] " + httpMethod.name() + " " - + uri + ", error: " + "Response is blank"; + String errorMsg = httpMethod.name() + " " + uri + ", error: " + "Response is blank"; log.warn("[AbstractBkApiClient] fail: Response is blank| bkApiRequestId={}|method={}|uri={}", apiContext.getRequestId(), httpMethod.name(), uri); status = EsbMetricTags.VALUE_STATUS_ERROR; @@ -255,45 +204,25 @@ private EsbResp requestApiAndWrapResponse(OpenApiRequestInfo reques } long deserializeStartTimestamp = System.currentTimeMillis(); - esbResp = jsonMapper.fromJson(respStr, typeReference); + responseBody = jsonMapper.fromJson(respStr, typeReference); apiContext.setDeserializeCostTime(System.currentTimeMillis() - deserializeStartTimestamp); - apiContext.setResp(esbResp); - if (!esbResp.isSuccess()) { - log.warn( - "[AbstractBkApiClient] fail:response code!=0" + - "|bkApiRequestId={}|code={}|message={}|method={}|uri={}|reqStr={}|respStr={}", - apiContext.getRequestId(), - esbResp.getCode(), - esbResp.getMessage(), - httpMethod.name(), - uri, - apiContext.getReq() != null ? JsonUtils.toJsonWithoutSkippedFields(apiContext.getReq()) : null, - respStr - ); + apiContext.setResp(responseBody); + if (isResponseError(response, responseBody)) { status = EsbMetricTags.VALUE_STATUS_ERROR; + apiContext.setSuccess(false); + handleResponseError(response, responseBody); + } else { + apiContext.setSuccess(true); } - if (esbResp.getData() == null) { - log.warn( - "[AbstractBkApiClient] warn: response data is null" + - "|bkApiRequestId={}|code={}|message={}|method={}|uri={}|reqStr={}|respStr={}", - apiContext.getRequestId(), - esbResp.getCode(), - esbResp.getMessage(), - httpMethod.name(), - uri, - apiContext.getReq() != null ? JsonUtils.toJsonWithoutSkippedFields(apiContext.getReq()) : null, - respStr - ); - } - apiContext.setSuccess(true); - return esbResp; + return responseBody; + } catch (BkOpenApiException e) { + throw e; } catch (Throwable e) { - String errorMsg = "Fail to request api|method=" + httpMethod.name() - + "|uri=" + uri; + String errorMsg = "Fail to request api|method=" + httpMethod.name() + "|uri=" + uri; log.error(errorMsg, e); apiContext.setSuccess(false); status = EsbMetricTags.VALUE_STATUS_ERROR; - throw new InternalException("Fail to request bk api", e, ErrorCode.API_ERROR); + throw new InternalException("Request bk open api error", ErrorCode.API_ERROR); } finally { long cost = System.currentTimeMillis() - startTimestamp; apiContext.setCostTime(cost); @@ -328,7 +257,7 @@ private Iterable buildMetricTags(String uri, String status) { private HttpResponse requestApi(HttpHelper httpHelper, OpenApiRequestInfo requestInfo) { String url = buildApiUrl(requestInfo.buildFinalUri()); - Header[] headers = buildBkApiRequestHeaders(requestInfo.getAuthorization()); + Header[] headers = buildBkApiRequestHeaders(requestInfo); HttpRequest httpRequest = HttpRequest.builder(requestInfo.getMethod(), url) .setHeaders(headers) .setKeepAlive(true) @@ -337,7 +266,7 @@ private HttpResponse requestApi(HttpHelper httpHelper, OpenApiRequestInfo .setStringEntity(requestInfo.getBody() != null ? jsonMapper.toJson(requestInfo.getBody()) : null) .build(); - return chooseHttpHelper(httpHelper).requestForSuccessResp(httpRequest); + return chooseHttpHelper(httpHelper).request(httpRequest); } private HttpHelper chooseHttpHelper(HttpHelper httpHelper) { @@ -354,16 +283,46 @@ private String buildApiUrl(String uri) { return url; } - private Header[] buildBkApiRequestHeaders(BkApiAuthorization authorization) { - Header[] header = new Header[3]; - header[0] = new BasicHeader("Content-Type", "application/json"); - header[1] = buildBkApiAuthorizationHeader(authorization); + private Header[] buildBkApiRequestHeaders(OpenApiRequestInfo requestInfo) { + List
headers = new ArrayList<>(); + headers.add(new BasicHeader("Content-Type", "application/json")); + headers.add(buildBkApiAuthorizationHeader(requestInfo.getAuthorization())); + headers.add(buildBkApiAuthorizationHeader(requestInfo.getAuthorization())); if (StringUtils.isNotEmpty(lang)) { - header[2] = new BasicHeader(HDR_BK_LANG, lang); + headers.add(new BasicHeader(HDR_BK_LANG, lang)); } else { - header[2] = new BasicHeader(HDR_BK_LANG, getLangFromRequest()); + headers.add(new BasicHeader(HDR_BK_LANG, getLangFromRequest())); + } + + if (CollectionUtils.isNotEmpty(requestInfo.getHeaders())) { + headers.addAll(requestInfo.getHeaders()); + } + + checkTenantHeader(headers); + + Header[] headerArray = new Header[headers.size()]; + return headers.toArray(headerArray); + } + + /** + * 检查调用蓝鲸 Open API 的请求是否包含租户 header + * + * @param headers 请求 headers + */ + private void checkTenantHeader(List
headers) { + boolean containsTenantHeader = headers.stream() + .anyMatch(header -> header.getName().equalsIgnoreCase(JobCommonHeaders.BK_TENANT_ID)); + if (!containsTenantHeader) { + if (tenantEnvService.isTenantEnabled()) { + // 临时方案,为了尽快联调;后续这里需要抛出异常 + log.warn("Add default tenant header : {}", TenantIdConstants.DEFAULT_TENANT_ID); + headers.add(new BasicHeader(TenantIdConstants.DEFAULT_TENANT_ID, TenantIdConstants.DEFAULT_TENANT_ID)); +// throw new InternalException("Header: " + JobCommonHeaders.BK_TENANT_ID + " is required", +// ErrorCode.API_ERROR); + } else { + headers.add(new BasicHeader(TenantIdConstants.DEFAULT_TENANT_ID, TenantIdConstants.DEFAULT_TENANT_ID)); + } } - return header; } private Header buildBkApiAuthorizationHeader(BkApiAuthorization authorization) { @@ -402,4 +361,19 @@ protected static HttpResponseInterceptor getLogBkApiRequestIdInterceptor() { return new LogBkApiRequestIdInterceptor(); } + protected boolean isResponseError(HttpResponse httpResponse, Object responseBody) { + // http code - 2xx + HttpStatus httpStatus = HttpStatus.resolve(httpResponse.getStatusCode()); + if (httpStatus == null) { + return true; + } + return !httpStatus.is2xxSuccessful(); + } + + protected void handleResponseError(HttpResponse httpResponse, Object responseBody) + throws BkOpenApiException { + throw new BkOpenApiException(httpResponse.getStatusCode()); + } + } + diff --git a/src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/sdk/BkApiContext.java b/src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/sdk/BkApiContext.java index 58d60ae8d9..85f1023ec9 100644 --- a/src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/sdk/BkApiContext.java +++ b/src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/sdk/BkApiContext.java @@ -24,7 +24,6 @@ package com.tencent.bk.job.common.esb.sdk; -import com.tencent.bk.job.common.esb.model.EsbResp; import lombok.Data; /** @@ -49,7 +48,7 @@ public class BkApiContext { /** * 反序列化之后的 API 响应 */ - private EsbResp resp; + private R resp; /** * API 调用耗时 */ @@ -76,7 +75,7 @@ public BkApiContext(String method, String uri, T req, String originResp, - EsbResp resp, + R resp, long costTime, boolean success) { this.method = method; diff --git a/src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/sdk/BkApiV1Client.java b/src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/sdk/BkApiV1Client.java new file mode 100644 index 0000000000..7660224071 --- /dev/null +++ b/src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/sdk/BkApiV1Client.java @@ -0,0 +1,100 @@ +/* + * Tencent is pleased to support the open source community by making BK-JOB蓝鲸智云作业平台 available. + * + * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-JOB蓝鲸智云作业平台 is licensed under the MIT License. + * + * License for BK-JOB蓝鲸智云作业平台: + * -------------------------------------------------------------------- + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and + * to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of + * the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO + * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +package com.tencent.bk.job.common.esb.sdk; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.tencent.bk.job.common.esb.exception.BkOpenApiException; +import com.tencent.bk.job.common.esb.model.EsbResp; +import com.tencent.bk.job.common.esb.model.OpenApiRequestInfo; +import com.tencent.bk.job.common.esb.model.OpenApiV1Error; +import com.tencent.bk.job.common.tenant.TenantEnvService; +import com.tencent.bk.job.common.util.http.HttpHelper; +import com.tencent.bk.job.common.util.http.HttpResponse; +import io.micrometer.core.instrument.MeterRegistry; + +/** + * 蓝鲸API(组件 API(ESB)、网关 API(蓝鲸 ApiGateway))调用客户端 (v1版本) + */ +public class BkApiV1Client extends BaseBkApiClient { + + /** + * @param meterRegistry MeterRegistry + * @param metricName API http 请求指标名称 + * @param baseAccessUrl API 服务访问地址 + * @param defaultHttpHelper http 请求处理客户端 + * @param tenantEnvService 租户环境信息 Service + */ + public BkApiV1Client(MeterRegistry meterRegistry, + String metricName, + String baseAccessUrl, + HttpHelper defaultHttpHelper, + TenantEnvService tenantEnvService) { + super(meterRegistry, metricName, baseAccessUrl, defaultHttpHelper, tenantEnvService); + } + + /** + * @param meterRegistry MeterRegistry + * @param metricName API http 请求指标名称 + * @param baseAccessUrl API 服务访问地址 + * @param defaultHttpHelper http 请求处理客户端 + * @param lang 语言 + * @param tenantEnvService 租户环境信息 Service + */ + public BkApiV1Client(MeterRegistry meterRegistry, + String metricName, + String baseAccessUrl, + HttpHelper defaultHttpHelper, + String lang, + TenantEnvService tenantEnvService) { + super(meterRegistry, metricName, baseAccessUrl, defaultHttpHelper, lang, tenantEnvService); + } + + public EsbResp request(OpenApiRequestInfo requestInfo, + TypeReference> typeReference, + HttpHelper httpHelper) throws BkOpenApiException { + return doRequest(requestInfo, typeReference, null, httpHelper); + } + + public EsbResp request(OpenApiRequestInfo requestInfo, + TypeReference> typeReference) throws BkOpenApiException { + return doRequest(requestInfo, typeReference, null); + } + + @Override + protected boolean isResponseError(HttpResponse httpResponse, Object responseBody) { + EsbResp esbResp = (EsbResp) responseBody; + return super.isResponseError(httpResponse, responseBody) && !esbResp.isSuccess(); + } + + @Override + protected void handleResponseError(HttpResponse httpResponse, Object responseBody) + throws BkOpenApiException { + EsbResp esbResp = (EsbResp) responseBody; + OpenApiV1Error error = new OpenApiV1Error(); + error.setCode(esbResp.getCode()); + error.setMessage(esbResp.getMessage()); + throw new BkOpenApiException(httpResponse.getStatusCode(), error); + } +} diff --git a/src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/sdk/BkApiV2Client.java b/src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/sdk/BkApiV2Client.java new file mode 100644 index 0000000000..ef97f1e61f --- /dev/null +++ b/src/backend/commons/esb-sdk/src/main/java/com/tencent/bk/job/common/esb/sdk/BkApiV2Client.java @@ -0,0 +1,75 @@ +/* + * Tencent is pleased to support the open source community by making BK-JOB蓝鲸智云作业平台 available. + * + * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-JOB蓝鲸智云作业平台 is licensed under the MIT License. + * + * License for BK-JOB蓝鲸智云作业平台: + * -------------------------------------------------------------------- + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and + * to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of + * the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO + * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +package com.tencent.bk.job.common.esb.sdk; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.tencent.bk.job.common.esb.exception.BkOpenApiException; +import com.tencent.bk.job.common.esb.model.OpenApiRequestInfo; +import com.tencent.bk.job.common.esb.model.OpenApiResponse; +import com.tencent.bk.job.common.tenant.TenantEnvService; +import com.tencent.bk.job.common.util.http.HttpHelper; +import com.tencent.bk.job.common.util.http.HttpResponse; +import io.micrometer.core.instrument.MeterRegistry; + +/** + * 网关 API(蓝鲸 ApiGateway))调用客户端 (v2版本) + */ +public class BkApiV2Client extends BaseBkApiClient { + + /** + * @param meterRegistry MeterRegistry + * @param metricName API http 请求指标名称 + * @param baseAccessUrl API 服务访问地址 + * @param defaultHttpHelper http 请求处理客户端 + * @param tenantEnvService 租户环境信息 Service + */ + public BkApiV2Client(MeterRegistry meterRegistry, + String metricName, + String baseAccessUrl, + HttpHelper defaultHttpHelper, + TenantEnvService tenantEnvService) { + super(meterRegistry, metricName, baseAccessUrl, defaultHttpHelper, tenantEnvService); + } + + public OpenApiResponse request(OpenApiRequestInfo requestInfo, + TypeReference> typeReference, + HttpHelper httpHelper) throws BkOpenApiException { + return doRequest(requestInfo, typeReference, null, httpHelper); + } + + public OpenApiResponse request( + OpenApiRequestInfo requestInfo, + TypeReference> typeReference) throws BkOpenApiException { + + return doRequest(requestInfo, typeReference, null); + } + + @Override + protected void handleResponseError(HttpResponse httpResponse, Object responseBody) + throws BkOpenApiException { + OpenApiResponse response = (OpenApiResponse) responseBody; + throw new BkOpenApiException(httpResponse.getStatusCode(), response.getError()); + } +} diff --git a/src/backend/commons/esb-sdk/src/test/java/com/tencent/bk/job/common/esb/OpenApiErrorDeserializerTest.java b/src/backend/commons/esb-sdk/src/test/java/com/tencent/bk/job/common/esb/OpenApiErrorDeserializerTest.java new file mode 100644 index 0000000000..263393a3a4 --- /dev/null +++ b/src/backend/commons/esb-sdk/src/test/java/com/tencent/bk/job/common/esb/OpenApiErrorDeserializerTest.java @@ -0,0 +1,116 @@ +package com.tencent.bk.job.common.esb; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.tencent.bk.job.common.esb.model.OpenApiError; +import com.tencent.bk.job.common.esb.model.iam.OpenApiApplyPermissionDTO; +import com.tencent.bk.job.common.util.json.JsonUtils; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +public class OpenApiErrorDeserializerTest { + @Test + public void testDeserializeWithoutDataField() { + String errorStr = "{\"message\": \"invalid params\", \"code\": \"INVALID_ARGUMENT\"}"; + OpenApiError error = JsonUtils.fromJson( + errorStr, + new TypeReference() { + } + ); + assertThat(error).isNotNull(); + assertThat(error.getCode()).isEqualTo("INVALID_ARGUMENT"); + assertThat(error.getMessage()).isEqualTo("invalid params"); + } + + @Test + public void testDeserializeIamNoPermissionError() { + String errorStr = "{\n" + + " \"code\": \"IAM_NO_PERMISSION\",\n" + + " \"message\": \"User no permission\",\n" + + " \"data\":\n" + + " {\n" + + " \"system_id\": \"bk-job\",\n" + + " \"system_name\": \"bk-job\",\n" + + " \"actions\":\n" + + " [\n" + + " {\n" + + " \"id\": \"execute_plan\",\n" + + " \"related_resource_types\":\n" + + " [\n" + + " {\n" + + " \"system_id\": \"bk-job\",\n" + + " \"system_name\": \"bk-job\",\n" + + " \"type\": \"plan\",\n" + + " \"instances\":\n" + + " [\n" + + " [\n" + + " {\n" + + " \"id\": \"1\",\n" + + " \"type\": \"plan\",\n" + + " \"name\": \"job_plan_test\"\n" + + " }\n" + + " ]\n" + + " ]\n" + + " }\n" + + " ]\n" + + " }\n" + + " ]\n" + + " }\n" + + "}"; + OpenApiError error = JsonUtils.fromJson( + errorStr, + new TypeReference() { + } + ); + assertThat(error).isNotNull(); + assertThat(error.getCode()).isEqualTo("IAM_NO_PERMISSION"); + assertThat(error.getMessage()).isEqualTo("User no permission"); + assertThat(error.getData()).isInstanceOf(OpenApiApplyPermissionDTO.class); + OpenApiApplyPermissionDTO permissionData = (OpenApiApplyPermissionDTO) error.getData(); + assertThat(permissionData.getSystemId()).isEqualTo("bk-job"); + assertThat(permissionData.getSystemName()).isEqualTo("bk-job"); + assertThat(permissionData.getActions().size()).isEqualTo(1); + assertThat(permissionData.getActions().get(0).getId()).isEqualTo("execute_plan"); + assertThat(permissionData.getActions().get(0).getRelatedResourceTypes().size()).isEqualTo(1); + assertThat(permissionData.getActions().get(0).getRelatedResourceTypes().get(0).getSystemId()).isEqualTo("bk" + + "-job"); + assertThat(permissionData.getActions().get(0).getRelatedResourceTypes().get(0).getSystemName()).isEqualTo("bk" + + "-job"); + assertThat(permissionData.getActions().get(0).getRelatedResourceTypes().get(0).getType()).isEqualTo("plan"); + assertThat(permissionData.getActions().get(0).getRelatedResourceTypes().get(0).getInstance().size()).isEqualTo(1); + assertThat(permissionData.getActions().get(0).getRelatedResourceTypes().get(0).getInstance().get(0).get(0).getId()).isEqualTo("1"); + assertThat(permissionData.getActions().get(0).getRelatedResourceTypes().get(0).getInstance().get(0).get(0).getType()).isEqualTo("plan"); + assertThat(permissionData.getActions().get(0).getRelatedResourceTypes().get(0).getInstance().get(0).get(0).getName()).isEqualTo("job_plan_test"); + } + + @Test + public void testDeserializeWithData() { + String errorStr = "{\n" + + " \"code\": \"INVALID_ARGUMENT\",\n" + + " \"message\": \"Invalid param\",\n" + + " \"data\":\n" + + " [\n" + + " {\n" + + " \"paramName\": \"name\",\n" + + " \"reason\": \"missing\"\n" + + " }\n" + + " ]\n" + + "}"; + OpenApiError error = JsonUtils.fromJson( + errorStr, + new TypeReference() { + } + ); + assertThat(error).isNotNull(); + assertThat(error.getCode()).isEqualTo("INVALID_ARGUMENT"); + assertThat(error.getMessage()).isEqualTo("Invalid param"); + assertThat(error.getData()).isInstanceOf(JsonNode.class); + JsonNode dataNode = (JsonNode) error.getData(); + assertThat(dataNode.isArray()).isTrue(); + assertThat(dataNode.get(0).isObject()).isTrue(); + assertThat(dataNode.get(0).get("paramName").asText()).isEqualTo("name"); + assertThat(dataNode.get(0).get("reason").asText()).isEqualTo("missing"); + } + +} diff --git a/src/backend/commons/gse-sdk/src/main/java/com/tencent/bk/job/common/gse/v2/GseV2ApiClient.java b/src/backend/commons/gse-sdk/src/main/java/com/tencent/bk/job/common/gse/v2/GseV2ApiClient.java index bba3048092..bbf2f7c7ed 100644 --- a/src/backend/commons/gse-sdk/src/main/java/com/tencent/bk/job/common/gse/v2/GseV2ApiClient.java +++ b/src/backend/commons/gse-sdk/src/main/java/com/tencent/bk/job/common/gse/v2/GseV2ApiClient.java @@ -31,7 +31,7 @@ import com.tencent.bk.job.common.esb.model.BkApiAuthorization; import com.tencent.bk.job.common.esb.model.EsbResp; import com.tencent.bk.job.common.esb.model.OpenApiRequestInfo; -import com.tencent.bk.job.common.esb.sdk.BkApiClient; +import com.tencent.bk.job.common.esb.sdk.BkApiV1Client; import com.tencent.bk.job.common.esb.sdk.BkApiContext; import com.tencent.bk.job.common.esb.sdk.BkApiLogStrategy; import com.tencent.bk.job.common.gse.IGseClient; @@ -48,6 +48,7 @@ import com.tencent.bk.job.common.gse.v2.model.TransferFileRequest; import com.tencent.bk.job.common.gse.v2.model.req.ListAgentStateReq; import com.tencent.bk.job.common.gse.v2.model.resp.AgentState; +import com.tencent.bk.job.common.tenant.TenantEnvService; import com.tencent.bk.job.common.util.StringUtil; import com.tencent.bk.job.common.util.http.HttpHelperFactory; import com.tencent.bk.job.common.util.http.JobHttpRequestRetryHandler; @@ -61,7 +62,7 @@ import java.util.List; @Slf4j -public class GseV2ApiClient extends BkApiClient implements IGseClient { +public class GseV2ApiClient extends BkApiV1Client implements IGseClient { private static final String URI_LIST_AGENT_STATE = "/api/v2/cluster/list_agent_state"; private static final String URI_ASYNC_EXECUTE_SCRIPT = "/api/v2/task/extensions/async_execute_script"; @@ -76,7 +77,8 @@ public class GseV2ApiClient extends BkApiClient implements IGseClient { public GseV2ApiClient(MeterRegistry meterRegistry, AppProperties appProperties, - BkApiGatewayProperties bkApiGatewayProperties) { + BkApiGatewayProperties bkApiGatewayProperties, + TenantEnvService tenantEnvService) { super(meterRegistry, GseMetricNames.GSE_V2_API_METRICS_NAME_PREFIX, @@ -91,7 +93,8 @@ public GseV2ApiClient(MeterRegistry meterRegistry, true, new JobHttpRequestRetryHandler(), httpClientBuilder -> httpClientBuilder.addInterceptorLast(getLogBkApiRequestIdInterceptor()) - ) + ), + tenantEnvService ); gseBkApiAuthorization = BkApiAuthorization.appAuthorization(appProperties.getCode(), appProperties.getSecret()); log.info("Init GseV2ApiClient, bkGseApiGatewayUrl: {}, appCode: {}", diff --git a/src/backend/commons/gse-sdk/src/main/java/com/tencent/bk/job/common/gse/v2/GseV2AutoConfiguration.java b/src/backend/commons/gse-sdk/src/main/java/com/tencent/bk/job/common/gse/v2/GseV2AutoConfiguration.java index 61a8eb2905..1b8e9a1436 100644 --- a/src/backend/commons/gse-sdk/src/main/java/com/tencent/bk/job/common/gse/v2/GseV2AutoConfiguration.java +++ b/src/backend/commons/gse-sdk/src/main/java/com/tencent/bk/job/common/gse/v2/GseV2AutoConfiguration.java @@ -27,6 +27,7 @@ import com.tencent.bk.job.common.esb.config.AppProperties; import com.tencent.bk.job.common.esb.config.BkApiGatewayProperties; import com.tencent.bk.job.common.gse.config.GseV2Properties; +import com.tencent.bk.job.common.tenant.TenantEnvService; import io.micrometer.core.instrument.MeterRegistry; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -41,7 +42,8 @@ public class GseV2AutoConfiguration { @Bean("gseV2ApiClient") public GseV2ApiClient gseV2ApiClient(MeterRegistry meterRegistry, AppProperties appProperties, - BkApiGatewayProperties bkApiGatewayProperties) { - return new GseV2ApiClient(meterRegistry, appProperties, bkApiGatewayProperties); + BkApiGatewayProperties bkApiGatewayProperties, + TenantEnvService tenantEnvService) { + return new GseV2ApiClient(meterRegistry, appProperties, bkApiGatewayProperties, tenantEnvService); } } diff --git a/src/backend/commons/notice-sdk/src/main/java/com/tencent/bk/job/common/notice/config/NoticeAutoConfiguration.java b/src/backend/commons/notice-sdk/src/main/java/com/tencent/bk/job/common/notice/config/NoticeAutoConfiguration.java index a51a34904a..6ad029d4de 100644 --- a/src/backend/commons/notice-sdk/src/main/java/com/tencent/bk/job/common/notice/config/NoticeAutoConfiguration.java +++ b/src/backend/commons/notice-sdk/src/main/java/com/tencent/bk/job/common/notice/config/NoticeAutoConfiguration.java @@ -27,6 +27,7 @@ import com.tencent.bk.job.common.esb.config.AppProperties; import com.tencent.bk.job.common.esb.config.BkApiGatewayProperties; import com.tencent.bk.job.common.notice.impl.BkNoticeClient; +import com.tencent.bk.job.common.tenant.TenantEnvService; import io.micrometer.core.instrument.MeterRegistry; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -41,8 +42,9 @@ public class NoticeAutoConfiguration { @Bean public BkNoticeClient bkNoticeClient(MeterRegistry meterRegistry, AppProperties appProperties, - BkApiGatewayProperties bkApiGatewayProperties) { - return new BkNoticeClient(meterRegistry, appProperties, bkApiGatewayProperties); + BkApiGatewayProperties bkApiGatewayProperties, + TenantEnvService tenantEnvService) { + return new BkNoticeClient(meterRegistry, appProperties, bkApiGatewayProperties, tenantEnvService); } } diff --git a/src/backend/commons/notice-sdk/src/main/java/com/tencent/bk/job/common/notice/impl/BkNoticeClient.java b/src/backend/commons/notice-sdk/src/main/java/com/tencent/bk/job/common/notice/impl/BkNoticeClient.java index 9f45eb0a51..780d16540b 100644 --- a/src/backend/commons/notice-sdk/src/main/java/com/tencent/bk/job/common/notice/impl/BkNoticeClient.java +++ b/src/backend/commons/notice-sdk/src/main/java/com/tencent/bk/job/common/notice/impl/BkNoticeClient.java @@ -29,18 +29,18 @@ import com.tencent.bk.job.common.constant.HttpMethodEnum; import com.tencent.bk.job.common.esb.config.AppProperties; import com.tencent.bk.job.common.esb.config.BkApiGatewayProperties; +import com.tencent.bk.job.common.esb.exception.BkOpenApiException; import com.tencent.bk.job.common.esb.model.BkApiAuthorization; import com.tencent.bk.job.common.esb.model.EsbReq; import com.tencent.bk.job.common.esb.model.EsbResp; import com.tencent.bk.job.common.esb.model.OpenApiRequestInfo; -import com.tencent.bk.job.common.esb.sdk.BkApiClient; -import com.tencent.bk.job.common.exception.HttpStatusException; -import com.tencent.bk.job.common.exception.InternalException; +import com.tencent.bk.job.common.esb.sdk.BkApiV1Client; import com.tencent.bk.job.common.metrics.CommonMetricNames; import com.tencent.bk.job.common.notice.IBkNoticeClient; import com.tencent.bk.job.common.notice.exception.BkNoticeException; import com.tencent.bk.job.common.notice.model.AnnouncementDTO; import com.tencent.bk.job.common.notice.model.BkNoticeApp; +import com.tencent.bk.job.common.tenant.TenantEnvService; import com.tencent.bk.job.common.util.http.HttpHelperFactory; import com.tencent.bk.job.common.util.http.HttpMetricUtil; import io.micrometer.core.instrument.MeterRegistry; @@ -51,7 +51,7 @@ import java.util.List; @SuppressWarnings("SameParameterValue") -public class BkNoticeClient extends BkApiClient implements IBkNoticeClient { +public class BkNoticeClient extends BkApiV1Client implements IBkNoticeClient { private static final String URI_REGISTER_APPLICATION = "/apigw/v1/register/"; private static final String URI_GET_CURRENT_ANNOUNCEMENTS = "/apigw/v1/announcement/get_current_announcements/"; @@ -61,14 +61,16 @@ public class BkNoticeClient extends BkApiClient implements IBkNoticeClient { public BkNoticeClient(MeterRegistry meterRegistry, AppProperties appProperties, - BkApiGatewayProperties bkApiGatewayProperties) { + BkApiGatewayProperties bkApiGatewayProperties, + TenantEnvService tenantEnvService) { super( meterRegistry, CommonMetricNames.BK_NOTICE_API, getBkNoticeUrlSafely(bkApiGatewayProperties), HttpHelperFactory.createHttpHelper( httpClientBuilder -> httpClientBuilder.addInterceptorLast(getLogBkApiRequestIdInterceptor()) - ) + ), + tenantEnvService ); this.appProperties = appProperties; authorization = BkApiAuthorization.appAuthorization(appProperties.getCode(), appProperties.getSecret()); @@ -154,13 +156,10 @@ private EsbResp requestBkNoticeApi(HttpMethodEnum method, .setIdempotent(idempotent) .build(); return doRequest(requestInfo, typeReference); - } catch (InternalException e) { + } catch (BkOpenApiException e) { // 接口不存在的场景需要使用指定错误码以便前端兼容处理 - if (e.getCause() instanceof HttpStatusException) { - HttpStatusException httpStatusException = (HttpStatusException) e.getCause(); - if (httpStatusException.getHttpStatus() == HttpStatus.SC_NOT_FOUND) { - throw new BkNoticeException(e, ErrorCode.BK_NOTICE_API_NOT_FOUND, new String[]{uri}); - } + if (e.getStatusCode() == HttpStatus.SC_NOT_FOUND) { + throw new BkNoticeException(e, ErrorCode.BK_NOTICE_API_NOT_FOUND, new String[]{uri}); } throw new BkNoticeException(e, ErrorCode.BK_NOTICE_API_DATA_ERROR, null); } catch (Exception e) { diff --git a/src/backend/commons/paas-sdk/src/main/java/com/tencent/bk/job/common/paas/cmsi/CmsiApiClient.java b/src/backend/commons/paas-sdk/src/main/java/com/tencent/bk/job/common/paas/cmsi/CmsiApiClient.java index c9d4f9a606..28bebc1ab0 100644 --- a/src/backend/commons/paas-sdk/src/main/java/com/tencent/bk/job/common/paas/cmsi/CmsiApiClient.java +++ b/src/backend/commons/paas-sdk/src/main/java/com/tencent/bk/job/common/paas/cmsi/CmsiApiClient.java @@ -34,13 +34,14 @@ import com.tencent.bk.job.common.esb.model.EsbReq; import com.tencent.bk.job.common.esb.model.EsbResp; import com.tencent.bk.job.common.esb.model.OpenApiRequestInfo; -import com.tencent.bk.job.common.esb.sdk.BkApiClient; +import com.tencent.bk.job.common.esb.sdk.BkApiV1Client; import com.tencent.bk.job.common.exception.InternalCmsiException; import com.tencent.bk.job.common.metrics.CommonMetricNames; import com.tencent.bk.job.common.model.error.ErrorType; import com.tencent.bk.job.common.paas.exception.PaasException; import com.tencent.bk.job.common.paas.model.EsbNotifyChannelDTO; import com.tencent.bk.job.common.paas.model.PostSendMsgReq; +import com.tencent.bk.job.common.tenant.TenantEnvService; import com.tencent.bk.job.common.util.http.HttpHelperFactory; import com.tencent.bk.job.common.util.http.HttpMetricUtil; import io.micrometer.core.instrument.MeterRegistry; @@ -57,7 +58,7 @@ * 消息通知 API 客户端 */ @Slf4j -public class CmsiApiClient extends BkApiClient { +public class CmsiApiClient extends BkApiV1Client { private static final String API_GET_NOTIFY_CHANNEL_LIST = "/api/c/compapi/cmsi/get_msg_type/"; private static final String API_POST_SEND_MSG = "/api/c/compapi/cmsi/send_msg/"; @@ -66,14 +67,16 @@ public class CmsiApiClient extends BkApiClient { public CmsiApiClient(EsbProperties esbProperties, AppProperties appProperties, - MeterRegistry meterRegistry) { + MeterRegistry meterRegistry, + TenantEnvService tenantEnvService) { super( meterRegistry, ESB_CMSI_API, esbProperties.getService().getUrl(), HttpHelperFactory.createHttpHelper( httpClientBuilder -> httpClientBuilder.addInterceptorLast(getLogBkApiRequestIdInterceptor()) - ) + ), + tenantEnvService ); this.authorization = BkApiAuthorization.appAuthorization(appProperties.getCode(), appProperties.getSecret(), "admin"); diff --git a/src/backend/commons/paas-sdk/src/main/java/com/tencent/bk/job/common/paas/config/CmsiAutoConfiguration.java b/src/backend/commons/paas-sdk/src/main/java/com/tencent/bk/job/common/paas/config/CmsiAutoConfiguration.java index fe5f4a1fd9..d6ebc3c71d 100644 --- a/src/backend/commons/paas-sdk/src/main/java/com/tencent/bk/job/common/paas/config/CmsiAutoConfiguration.java +++ b/src/backend/commons/paas-sdk/src/main/java/com/tencent/bk/job/common/paas/config/CmsiAutoConfiguration.java @@ -27,6 +27,7 @@ import com.tencent.bk.job.common.esb.config.AppProperties; import com.tencent.bk.job.common.esb.config.EsbProperties; import com.tencent.bk.job.common.paas.cmsi.CmsiApiClient; +import com.tencent.bk.job.common.tenant.TenantEnvService; import io.micrometer.core.instrument.MeterRegistry; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.ObjectProvider; @@ -40,9 +41,15 @@ public class CmsiAutoConfiguration { @Bean public CmsiApiClient cmsiApiClient(AppProperties appProperties, EsbProperties esbProperties, - ObjectProvider meterRegistryObjectProvider) { + ObjectProvider meterRegistryObjectProvider, + TenantEnvService tenantEnvService) { log.info("Init CmsiApiClient"); - return new CmsiApiClient(esbProperties, appProperties, meterRegistryObjectProvider.getIfAvailable()); + return new CmsiApiClient( + esbProperties, + appProperties, + meterRegistryObjectProvider.getIfAvailable(), + tenantEnvService + ); } } diff --git a/src/backend/commons/paas-sdk/src/main/java/com/tencent/bk/job/common/paas/config/LoginAutoConfiguration.java b/src/backend/commons/paas-sdk/src/main/java/com/tencent/bk/job/common/paas/config/LoginAutoConfiguration.java index 631c85326a..f7df50a2b4 100644 --- a/src/backend/commons/paas-sdk/src/main/java/com/tencent/bk/job/common/paas/config/LoginAutoConfiguration.java +++ b/src/backend/commons/paas-sdk/src/main/java/com/tencent/bk/job/common/paas/config/LoginAutoConfiguration.java @@ -25,10 +25,12 @@ package com.tencent.bk.job.common.paas.config; import com.tencent.bk.job.common.esb.config.AppProperties; -import com.tencent.bk.job.common.esb.config.EsbProperties; +import com.tencent.bk.job.common.esb.config.BkApiGatewayProperties; import com.tencent.bk.job.common.paas.login.CustomLoginClient; import com.tencent.bk.job.common.paas.login.ILoginClient; import com.tencent.bk.job.common.paas.login.StandardLoginClient; +import com.tencent.bk.job.common.paas.login.v3.BkLoginApiClient; +import com.tencent.bk.job.common.tenant.TenantEnvService; import io.micrometer.core.instrument.MeterRegistry; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.ObjectProvider; @@ -51,18 +53,23 @@ public ILoginClient customLoginClient(@Autowired LoginConfiguration loginConfigu return new CustomLoginClient(loginConfiguration.getCustomLoginApiUrl()); } + @Bean + @ConditionalOnProperty(value = "paas.login.custom.enabled", havingValue = "false", matchIfMissing = true) + public BkLoginApiClient bkLoginApiClient(BkApiGatewayProperties bkApiGatewayProperties, + AppProperties appProperties, + ObjectProvider meterRegistryObjectProvider, + TenantEnvService tenantEnvService) { + log.info("Init LoginApiClient"); + return new BkLoginApiClient(bkApiGatewayProperties, appProperties, + meterRegistryObjectProvider.getIfAvailable(), tenantEnvService); + } + @Bean @ConditionalOnProperty(value = "paas.login.custom.enabled", havingValue = "false", matchIfMissing = true) @Primary - public ILoginClient standardLoginClient(AppProperties appProperties, - EsbProperties esbProperties, - ObjectProvider meterRegistryObjectProvider) { + public ILoginClient standardLoginClient(BkLoginApiClient bkLoginApiClient) { log.info("Init StandardLoginClient"); - return new StandardLoginClient( - esbProperties, - appProperties, - meterRegistryObjectProvider.getIfAvailable() - ); + return new StandardLoginClient(bkLoginApiClient); } } diff --git a/src/backend/commons/paas-sdk/src/main/java/com/tencent/bk/job/common/paas/config/UserMgrAutoConfiguration.java b/src/backend/commons/paas-sdk/src/main/java/com/tencent/bk/job/common/paas/config/UserMgrAutoConfiguration.java index ebc6f48c35..40fb1d20e4 100644 --- a/src/backend/commons/paas-sdk/src/main/java/com/tencent/bk/job/common/paas/config/UserMgrAutoConfiguration.java +++ b/src/backend/commons/paas-sdk/src/main/java/com/tencent/bk/job/common/paas/config/UserMgrAutoConfiguration.java @@ -25,8 +25,9 @@ package com.tencent.bk.job.common.paas.config; import com.tencent.bk.job.common.esb.config.AppProperties; -import com.tencent.bk.job.common.esb.config.EsbProperties; +import com.tencent.bk.job.common.esb.config.BkApiGatewayProperties; import com.tencent.bk.job.common.paas.user.UserMgrApiClient; +import com.tencent.bk.job.common.tenant.TenantEnvService; import io.micrometer.core.instrument.MeterRegistry; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.ObjectProvider; @@ -39,10 +40,16 @@ public class UserMgrAutoConfiguration { @Bean public UserMgrApiClient userMgrApiClient(AppProperties appProperties, - EsbProperties esbProperties, - ObjectProvider meterRegistryObjectProvider) { + BkApiGatewayProperties bkApiGatewayProperties, + ObjectProvider meterRegistryObjectProvider, + TenantEnvService tenantEnvService) { log.info("Init UserMgrApiClient"); - return new UserMgrApiClient(esbProperties, appProperties, meterRegistryObjectProvider.getIfAvailable()); + return new UserMgrApiClient( + bkApiGatewayProperties, + appProperties, + meterRegistryObjectProvider.getIfAvailable(), + tenantEnvService + ); } } diff --git a/src/backend/commons/paas-sdk/src/main/java/com/tencent/bk/job/common/paas/login/StandardLoginClient.java b/src/backend/commons/paas-sdk/src/main/java/com/tencent/bk/job/common/paas/login/StandardLoginClient.java index 3f41589aae..e03290a9f9 100644 --- a/src/backend/commons/paas-sdk/src/main/java/com/tencent/bk/job/common/paas/login/StandardLoginClient.java +++ b/src/backend/commons/paas-sdk/src/main/java/com/tencent/bk/job/common/paas/login/StandardLoginClient.java @@ -24,112 +24,38 @@ package com.tencent.bk.job.common.paas.login; -import com.fasterxml.jackson.core.type.TypeReference; -import com.tencent.bk.job.common.constant.ErrorCode; -import com.tencent.bk.job.common.constant.HttpMethodEnum; -import com.tencent.bk.job.common.esb.config.AppProperties; -import com.tencent.bk.job.common.esb.config.EsbProperties; -import com.tencent.bk.job.common.esb.model.BkApiAuthorization; -import com.tencent.bk.job.common.esb.model.EsbResp; -import com.tencent.bk.job.common.esb.model.OpenApiRequestInfo; -import com.tencent.bk.job.common.esb.sdk.BkApiClient; -import com.tencent.bk.job.common.exception.InternalUserManageException; -import com.tencent.bk.job.common.metrics.CommonMetricNames; import com.tencent.bk.job.common.model.dto.BkUserDTO; -import com.tencent.bk.job.common.paas.exception.AppPermissionDeniedException; -import com.tencent.bk.job.common.paas.model.EsbUserDto; -import com.tencent.bk.job.common.util.http.HttpHelperFactory; -import com.tencent.bk.job.common.util.http.HttpMetricUtil; -import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.core.instrument.Tag; +import com.tencent.bk.job.common.paas.login.v3.BkLoginApiClient; +import com.tencent.bk.job.common.paas.login.v3.OpenApiBkUser; import lombok.extern.slf4j.Slf4j; -import static com.tencent.bk.job.common.metrics.CommonMetricNames.ESB_BK_LOGIN_API; - @Slf4j -public class StandardLoginClient extends BkApiClient implements ILoginClient { - - // 用户认证失败,即用户登录态无效 - private static final Integer ESB_CODE_USER_NOT_LOGIN = 1302100; - // 用户不存在 - private static final Integer ESB_CODE_USER_NOT_EXIST = 1302103; - // 用户认证成功,但用户无应用访问权限 - private static final Integer ESB_CODE_USER_NO_APP_PERMISSION = 1302403; +public class StandardLoginClient implements ILoginClient { - private static final String API_GET_USER_INFO = "/api/c/compapi/v2/bk_login/get_user/"; + private final BkLoginApiClient bkLoginApiClient; - private final AppProperties appProperties; - - public StandardLoginClient(EsbProperties esbProperties, AppProperties appProperties, MeterRegistry meterRegistry) { - super( - meterRegistry, - ESB_BK_LOGIN_API, - esbProperties.getService().getUrl(), - HttpHelperFactory.createHttpHelper( - httpClientBuilder -> httpClientBuilder.addInterceptorLast(getLogBkApiRequestIdInterceptor()) - ) - ); - this.appProperties = appProperties; + public StandardLoginClient(BkLoginApiClient bkLoginApiClient) { + this.bkLoginApiClient = bkLoginApiClient; } /** - * 获取指定用户信息 + * 根据 token 获取指定用户信息 * * @param bkToken 用户登录 token * @return 用户信息 */ @Override public BkUserDTO getUserInfoByToken(String bkToken) { - try { - HttpMetricUtil.setHttpMetricName(CommonMetricNames.ESB_BK_LOGIN_API_HTTP); - HttpMetricUtil.addTagForCurrentMetric( - Tag.of("api_name", API_GET_USER_INFO) - ); - EsbResp esbResp = doRequest( - OpenApiRequestInfo.builder() - .method(HttpMethodEnum.GET) - .uri(API_GET_USER_INFO) - .addQueryParam("bk_token", bkToken) - .authorization(BkApiAuthorization.bkTokenUserAuthorization( - appProperties.getCode(), appProperties.getSecret(), bkToken)) - .build(), - new TypeReference>() { - } - ); - Integer code = esbResp.getCode(); - if (ErrorCode.RESULT_OK == code) { - return convertToBkUserDTO(esbResp.getData()); - } else { - handleNotOkResp(esbResp); - return null; - } - } catch (Exception e) { - String errorMsg = "Get " + API_GET_USER_INFO + " error"; - log.error(errorMsg, e); - throw new InternalUserManageException(errorMsg, e, ErrorCode.USER_MANAGE_API_ACCESS_ERROR); - } finally { - HttpMetricUtil.clearHttpMetric(); - } - } - - private void handleNotOkResp(EsbResp esbResp) { - Integer code = esbResp.getCode(); - if (ESB_CODE_USER_NO_APP_PERMISSION.equals(code)) { - throw new AppPermissionDeniedException(esbResp.getMessage()); - } else if (ESB_CODE_USER_NOT_LOGIN.equals(code)) { - log.info("User not login, esbResp.code={}, esbResp.message={}", esbResp.getCode(), esbResp.getMessage()); - } else if (ESB_CODE_USER_NOT_EXIST.equals(code)) { - log.info("User not exist, esbResp.code={}, esbResp.message={}", esbResp.getCode(), esbResp.getMessage()); + OpenApiBkUser bkUser = bkLoginApiClient.getBkUserByToken(bkToken); + if (bkUser == null) { + return null; } - } - - private BkUserDTO convertToBkUserDTO(EsbUserDto esbUserDto) { BkUserDTO bkUserDTO = new BkUserDTO(); - bkUserDTO.setUsername(esbUserDto.getUsername()); - bkUserDTO.setEmail(esbUserDto.getEmail()); - bkUserDTO.setPhone(esbUserDto.getPhone()); - bkUserDTO.setWxUserId(esbUserDto.getWxUserId()); - bkUserDTO.setTimeZone(esbUserDto.getTimeZone()); + bkUserDTO.setUsername(bkUser.getUsername()); + bkUserDTO.setDisplayName(bkUser.getDisplayName()); + bkUserDTO.setTimeZone(bkUser.getTimeZone()); + bkUserDTO.setTenantId(bkUser.getTenantId()); + bkUserDTO.setLanguage(bkUser.getLanguage()); return bkUserDTO; } } diff --git a/src/backend/commons/paas-sdk/src/main/java/com/tencent/bk/job/common/paas/login/v3/BkLoginApiClient.java b/src/backend/commons/paas-sdk/src/main/java/com/tencent/bk/job/common/paas/login/v3/BkLoginApiClient.java new file mode 100644 index 0000000000..bda1a94205 --- /dev/null +++ b/src/backend/commons/paas-sdk/src/main/java/com/tencent/bk/job/common/paas/login/v3/BkLoginApiClient.java @@ -0,0 +1,118 @@ +package com.tencent.bk.job.common.paas.login.v3; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.tencent.bk.job.common.constant.ErrorCode; +import com.tencent.bk.job.common.constant.HttpMethodEnum; +import com.tencent.bk.job.common.constant.JobCommonHeaders; +import com.tencent.bk.job.common.esb.config.AppProperties; +import com.tencent.bk.job.common.esb.config.BkApiGatewayProperties; +import com.tencent.bk.job.common.esb.exception.BkOpenApiException; +import com.tencent.bk.job.common.esb.model.BkApiAuthorization; +import com.tencent.bk.job.common.esb.model.OpenApiRequestInfo; +import com.tencent.bk.job.common.esb.model.OpenApiResponse; +import com.tencent.bk.job.common.esb.sdk.BkApiV2Client; +import com.tencent.bk.job.common.exception.InternalException; +import com.tencent.bk.job.common.tenant.TenantEnvService; +import com.tencent.bk.job.common.util.http.HttpHelperFactory; +import com.tencent.bk.job.common.util.http.HttpMetricUtil; +import com.tencent.bk.job.common.util.json.JsonUtils; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Tag; +import lombok.extern.slf4j.Slf4j; +import org.apache.http.HttpStatus; +import org.apache.http.message.BasicHeader; + +import java.util.function.Function; + +import static com.tencent.bk.job.common.metrics.CommonMetricNames.BK_LOGIN_API; +import static com.tencent.bk.job.common.metrics.CommonMetricNames.BK_LOGIN_API_HTTP; + +/** + * bk-login API 客户端 + */ +@Slf4j +public class BkLoginApiClient extends BkApiV2Client { + + public static final String API_URL_USERINFO = "/login/api/v3/open/bk-tokens/userinfo"; + + protected final BkApiAuthorization authorization; + + + public BkLoginApiClient(BkApiGatewayProperties bkApiGatewayProperties, + AppProperties appProperties, + MeterRegistry meterRegistry, + TenantEnvService tenantEnvService) { + super( + meterRegistry, + BK_LOGIN_API, + bkApiGatewayProperties.getBkLogin().getUrl(), + HttpHelperFactory.createHttpHelper( + httpClientBuilder -> httpClientBuilder.addInterceptorLast(getLogBkApiRequestIdInterceptor()) + ), + tenantEnvService + ); + this.authorization = BkApiAuthorization.appAuthorization( + appProperties.getCode(), appProperties.getSecret()); + } + + + /** + * 根据 token 获取用户信息 + * + * @param token 用户登录 token + * @return 用户信息 + */ + public OpenApiBkUser getBkUserByToken(String token) { + OpenApiResponse response = requestBkLoginApi( + "get_bk_token_userinfo", + OpenApiRequestInfo + .builder() + .method(HttpMethodEnum.GET) + .uri(API_URL_USERINFO) + .queryParams("bk_token=" + token) + .body(null) + // 该API 与租户无关,写死租户 ID 为 system以便通过蓝鲸网关验证 + .addHeader(new BasicHeader(JobCommonHeaders.BK_TENANT_ID, "system")) + .authorization(authorization) + .build(), + request -> { + try { + return doRequest(request, new TypeReference>() { + }); + } catch (BkOpenApiException e) { + if (e.getStatusCode() == HttpStatus.SC_BAD_REQUEST) { + // http status = 400 表示登录态已过期 + return OpenApiResponse.success(null); + } else { + throw e; + } + } + } + ); + + return response.getData(); + } + + + protected OpenApiResponse requestBkLoginApi( + String apiName, + OpenApiRequestInfo request, + Function, OpenApiResponse> requestHandler) { + + try { + HttpMetricUtil.setHttpMetricName(BK_LOGIN_API_HTTP); + HttpMetricUtil.addTagForCurrentMetric(Tag.of("api_name", apiName)); + return requestHandler.apply(request); + } catch (Throwable e) { + String errorMsg = "Fail to request bk-login api|method=" + request.getMethod() + + "|uri=" + request.getUri() + "|queryParams=" + + request.getQueryParams() + "|body=" + + JsonUtils.toJsonWithoutSkippedFields(JsonUtils.toJsonWithoutSkippedFields(request.getBody())); + log.error(errorMsg, e); + throw new InternalException(e.getMessage(), e, ErrorCode.BK_LOGIN_API_ERROR); + } finally { + HttpMetricUtil.clearHttpMetric(); + } + } + +} diff --git a/src/backend/commons/paas-sdk/src/main/java/com/tencent/bk/job/common/paas/login/v3/OpenApiBkUser.java b/src/backend/commons/paas-sdk/src/main/java/com/tencent/bk/job/common/paas/login/v3/OpenApiBkUser.java new file mode 100644 index 0000000000..5b3cc2bcc5 --- /dev/null +++ b/src/backend/commons/paas-sdk/src/main/java/com/tencent/bk/job/common/paas/login/v3/OpenApiBkUser.java @@ -0,0 +1,43 @@ +package com.tencent.bk.job.common.paas.login.v3; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 用户信息 + */ +@Data +public class OpenApiBkUser { + + /** + * 用户唯一标识,全局唯一 + */ + @JsonProperty("bk_username") + private String username; + + /** + * 用户所属租户 ID + */ + @JsonProperty("tenant_id") + private String tenantId; + + /** + * 用户展示名 + */ + @JsonProperty("display_name") + private String displayName; + + /** + * 用户语言,枚举值:zh-cn / en + */ + @JsonProperty("language") + private String language; + + /** + * 用户所在时区 + */ + @JsonProperty("time_zone") + private String timeZone; + + +} diff --git a/src/backend/commons/paas-sdk/src/main/java/com/tencent/bk/job/common/paas/model/OpenApiTenant.java b/src/backend/commons/paas-sdk/src/main/java/com/tencent/bk/job/common/paas/model/OpenApiTenant.java new file mode 100644 index 0000000000..66042e8307 --- /dev/null +++ b/src/backend/commons/paas-sdk/src/main/java/com/tencent/bk/job/common/paas/model/OpenApiTenant.java @@ -0,0 +1,27 @@ +package com.tencent.bk.job.common.paas.model; + +import lombok.Data; + +/** + * 租户信息 + */ +@Data +public class OpenApiTenant { + + /** + * 租户 ID + */ + private String id; + + /** + * 租户名 + */ + private String name; + + /** + * 租户状态,enabled 表示启用,disabled 表示禁用 + * + * @see TenantStatusEnum + */ + private String status; +} diff --git a/src/backend/commons/paas-sdk/src/main/java/com/tencent/bk/job/common/paas/model/TenantStatusEnum.java b/src/backend/commons/paas-sdk/src/main/java/com/tencent/bk/job/common/paas/model/TenantStatusEnum.java new file mode 100644 index 0000000000..9e437f941d --- /dev/null +++ b/src/backend/commons/paas-sdk/src/main/java/com/tencent/bk/job/common/paas/model/TenantStatusEnum.java @@ -0,0 +1,40 @@ +package com.tencent.bk.job.common.paas.model; + +/** + * 租户状态 + */ +public enum TenantStatusEnum { + + /** + * 启用 + */ + ENABLED(Constants.ENABLED), + /** + * 禁用 + */ + DISABLED(Constants.DISABLED); + + TenantStatusEnum(String status) { + this.status = status; + } + + public interface Constants { + String ENABLED = "enabled"; + String DISABLED = "disabled"; + } + + private final String status; + + public static TenantStatusEnum valOf(String status) { + for (TenantStatusEnum value : values()) { + if (value.status.equalsIgnoreCase(status)) { + return value; + } + } + throw new IllegalArgumentException("No TenantStatusEnum constant: " + status); + } + + public String getStatus() { + return status; + } +} diff --git a/src/backend/commons/paas-sdk/src/main/java/com/tencent/bk/job/common/paas/user/TenantLocalCache.java b/src/backend/commons/paas-sdk/src/main/java/com/tencent/bk/job/common/paas/user/TenantLocalCache.java new file mode 100644 index 0000000000..d981eee3f6 --- /dev/null +++ b/src/backend/commons/paas-sdk/src/main/java/com/tencent/bk/job/common/paas/user/TenantLocalCache.java @@ -0,0 +1,46 @@ +package com.tencent.bk.job.common.paas.user; + +import com.tencent.bk.job.common.paas.model.OpenApiTenant; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * 租户信息缓存 + */ +public class TenantLocalCache { + + private final UserMgrApiClient userMgrApiClient; + + private ScheduledThreadPoolExecutor refresher = new ScheduledThreadPoolExecutor(1); + + + private final Map tenantCache = new HashMap<>(); + + private volatile boolean initialed = false; + + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + + + public TenantLocalCache(UserMgrApiClient userMgrApiClient) { + this.userMgrApiClient = userMgrApiClient; +// refresher.scheduleAtFixedRate(this::loadCache, ) + } + + public OpenApiTenant get(String tenantId) { + if (!initialed) { + loadCache(); + initialed = true; + } + return null; + } + + private void loadCache() { + + } +} diff --git a/src/backend/commons/paas-sdk/src/main/java/com/tencent/bk/job/common/paas/user/UserMgrApiClient.java b/src/backend/commons/paas-sdk/src/main/java/com/tencent/bk/job/common/paas/user/UserMgrApiClient.java index adaad49bf7..9363644cf5 100644 --- a/src/backend/commons/paas-sdk/src/main/java/com/tencent/bk/job/common/paas/user/UserMgrApiClient.java +++ b/src/backend/commons/paas-sdk/src/main/java/com/tencent/bk/job/common/paas/user/UserMgrApiClient.java @@ -27,113 +27,99 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.tencent.bk.job.common.constant.ErrorCode; import com.tencent.bk.job.common.constant.HttpMethodEnum; +import com.tencent.bk.job.common.constant.JobCommonHeaders; +import com.tencent.bk.job.common.constant.TenantIdConstants; import com.tencent.bk.job.common.esb.config.AppProperties; -import com.tencent.bk.job.common.esb.config.EsbProperties; -import com.tencent.bk.job.common.esb.constants.EsbLang; -import com.tencent.bk.job.common.esb.metrics.EsbMetricTags; +import com.tencent.bk.job.common.esb.config.BkApiGatewayProperties; import com.tencent.bk.job.common.esb.model.BkApiAuthorization; -import com.tencent.bk.job.common.esb.model.EsbResp; import com.tencent.bk.job.common.esb.model.OpenApiRequestInfo; -import com.tencent.bk.job.common.esb.sdk.BkApiClient; -import com.tencent.bk.job.common.exception.InternalUserManageException; -import com.tencent.bk.job.common.metrics.CommonMetricNames; +import com.tencent.bk.job.common.esb.model.OpenApiResponse; +import com.tencent.bk.job.common.esb.sdk.BkApiV2Client; +import com.tencent.bk.job.common.exception.InternalException; import com.tencent.bk.job.common.model.dto.BkUserDTO; -import com.tencent.bk.job.common.paas.model.EsbListUsersResult; -import com.tencent.bk.job.common.paas.model.GetUserListReq; +import com.tencent.bk.job.common.paas.model.OpenApiTenant; +import com.tencent.bk.job.common.tenant.TenantEnvService; import com.tencent.bk.job.common.util.http.HttpHelperFactory; import com.tencent.bk.job.common.util.http.HttpMetricUtil; +import com.tencent.bk.job.common.util.json.JsonUtils; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Tag; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import org.springframework.util.CollectionUtils; +import org.apache.http.message.BasicHeader; -import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.function.Function; -import static com.tencent.bk.job.common.metrics.CommonMetricNames.ESB_USER_MANAGE_API; +import static com.tencent.bk.job.common.metrics.CommonMetricNames.USER_MANAGE_API; +import static com.tencent.bk.job.common.metrics.CommonMetricNames.USER_MANAGE_API_HTTP; /** * 用户管理 API 客户端 */ @Slf4j -public class UserMgrApiClient extends BkApiClient { - - private static final String API_GET_USER_LIST = "/api/c/compapi/v2/usermanage/list_users/"; +public class UserMgrApiClient extends BkApiV2Client { private final BkApiAuthorization authorization; - public UserMgrApiClient(EsbProperties esbProperties, + public UserMgrApiClient(BkApiGatewayProperties bkApiGatewayProperties, AppProperties appProperties, - MeterRegistry meterRegistry) { + MeterRegistry meterRegistry, + TenantEnvService tenantEnvService) { super(meterRegistry, - ESB_USER_MANAGE_API, - esbProperties.getService().getUrl(), + USER_MANAGE_API, + bkApiGatewayProperties.getBkUser().getUrl(), HttpHelperFactory.getRetryableHttpHelper(), - EsbLang.EN + tenantEnvService ); this.authorization = BkApiAuthorization.appAuthorization(appProperties.getCode(), - appProperties.getSecret(), "admin"); + appProperties.getSecret()); } - public List getAllUserList() { - String fields = "id,username,display_name,logo"; - List esbUserList; - try { - GetUserListReq req = buildGetUserListReq(fields); - - HttpMetricUtil.setHttpMetricName(CommonMetricNames.ESB_USER_MANAGE_API_HTTP); - HttpMetricUtil.addTagForCurrentMetric( - Tag.of(EsbMetricTags.KEY_API_NAME, API_GET_USER_LIST) - ); - EsbResp> esbResp = doRequest( - OpenApiRequestInfo.builder() - .method(HttpMethodEnum.GET) - .uri(API_GET_USER_LIST) - .queryParams(req.toUrlParams()) - .authorization(authorization) - .build(), - new TypeReference>>() { - } - ); - esbUserList = esbResp.getData(); - } catch (Exception e) { - String errorMsg = "Get " + API_GET_USER_LIST + " error"; - log.error(errorMsg, e); - throw new InternalUserManageException(errorMsg, e, ErrorCode.USER_MANAGE_API_ACCESS_ERROR); - } finally { - HttpMetricUtil.clearHttpMetric(); - } - return convert(esbUserList); + public List getAllUserList(String tenantId) { + return Collections.emptyList(); } - private GetUserListReq buildGetUserListReq(String fields) { - GetUserListReq req = new GetUserListReq(); - if (StringUtils.isNotBlank(fields)) { - req.setFields(fields); - } - req.setPage(0L); - req.setPageSize(0L); - req.setNoPage(true); - return req; + /** + * 获取全量租户 + */ + public List listAllTenant() { + OpenApiResponse> response = requestBkUserApi( + "list_tenant", + OpenApiRequestInfo + .builder() + .method(HttpMethodEnum.GET) + .uri("/api/v3/open/tenants") + .addHeader(new BasicHeader(JobCommonHeaders.BK_TENANT_ID, TenantIdConstants.DEFAULT_TENANT_ID)) + .authorization(authorization) + .build(), + request -> doRequest(request, new TypeReference>>() { + }) + ); + + return response.getData(); } - private List convert(List esbUserList) { - if (CollectionUtils.isEmpty(esbUserList)) { - return Collections.emptyList(); - } - List userList = new ArrayList<>(); - for (EsbListUsersResult esbUser : esbUserList) { - BkUserDTO user = new BkUserDTO(); - user.setId(esbUser.getId()); - user.setUsername(esbUser.getUsername()); - user.setDisplayName(esbUser.getDisplayName()); - user.setLogo(esbUser.getLogo()); - user.setUid(esbUser.getUid()); - userList.add(user); + + protected OpenApiResponse requestBkUserApi( + String apiName, + OpenApiRequestInfo request, + Function, OpenApiResponse> requestHandler) { + + try { + HttpMetricUtil.setHttpMetricName(USER_MANAGE_API_HTTP); + HttpMetricUtil.addTagForCurrentMetric(Tag.of("api_name", apiName)); + return requestHandler.apply(request); + } catch (Throwable e) { + String errorMsg = "Fail to request bk-user api|method=" + request.getMethod() + + "|uri=" + request.getUri() + "|queryParams=" + + request.getQueryParams() + "|body=" + + JsonUtils.toJsonWithoutSkippedFields(JsonUtils.toJsonWithoutSkippedFields(request.getBody())); + log.error(errorMsg, e); + throw new InternalException(e.getMessage(), e, ErrorCode.BK_USER_MANAGE_API_ERROR); + } finally { + HttpMetricUtil.clearHttpMetric(); } - return userList; } } diff --git a/src/backend/job-analysis/boot-job-analysis/src/main/java/com/tencent/bk/job/analysis/config/JobAnalysisConfiguration.java b/src/backend/job-analysis/boot-job-analysis/src/main/java/com/tencent/bk/job/analysis/config/JobAnalysisConfiguration.java index 06bfc737d9..25e5e4c9cb 100644 --- a/src/backend/job-analysis/boot-job-analysis/src/main/java/com/tencent/bk/job/analysis/config/JobAnalysisConfiguration.java +++ b/src/backend/job-analysis/boot-job-analysis/src/main/java/com/tencent/bk/job/analysis/config/JobAnalysisConfiguration.java @@ -24,9 +24,9 @@ package com.tencent.bk.job.analysis.config; -import com.tencent.bk.job.common.service.AppScopeMappingService; -import com.tencent.bk.job.common.web.interceptor.AppResourceScopeInterceptor; -import com.tencent.bk.job.manage.AppScopeMappingServiceImpl; +import com.tencent.bk.job.common.service.AppCacheService; +import com.tencent.bk.job.common.web.interceptor.BasicAppInterceptor; +import com.tencent.bk.job.manage.AppCacheServiceImpl; import com.tencent.bk.job.manage.api.inner.ServiceApplicationResource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -34,12 +34,12 @@ @Configuration public class JobAnalysisConfiguration { @Bean - AppScopeMappingService appScopeMappingService(ServiceApplicationResource applicationResource) { - return new AppScopeMappingServiceImpl(applicationResource); + AppCacheService appScopeMappingService(ServiceApplicationResource applicationResource) { + return new AppCacheServiceImpl(applicationResource); } @Bean - public AppResourceScopeInterceptor appResourceScopeInterceptor(AppScopeMappingService appScopeMappingService) { - return new AppResourceScopeInterceptor(appScopeMappingService); + public BasicAppInterceptor basicAppInterceptor(AppCacheService appCacheService) { + return new BasicAppInterceptor(appCacheService); } } diff --git a/src/backend/job-assemble/src/main/java/com/tencent/bk/job/assemble/config/JobAssembleConfiguration.java b/src/backend/job-assemble/src/main/java/com/tencent/bk/job/assemble/config/JobAssembleConfiguration.java index 1a1d720fe8..83d1e8c83e 100644 --- a/src/backend/job-assemble/src/main/java/com/tencent/bk/job/assemble/config/JobAssembleConfiguration.java +++ b/src/backend/job-assemble/src/main/java/com/tencent/bk/job/assemble/config/JobAssembleConfiguration.java @@ -24,15 +24,16 @@ package com.tencent.bk.job.assemble.config; +import com.tencent.bk.job.common.service.AppCacheService; import com.tencent.bk.job.common.service.AppScopeMappingService; -import com.tencent.bk.job.common.web.interceptor.AppResourceScopeInterceptor; +import com.tencent.bk.job.common.web.interceptor.BasicAppInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class JobAssembleConfiguration { @Bean - public AppResourceScopeInterceptor appResourceScopeInterceptor(AppScopeMappingService appScopeMappingService) { - return new AppResourceScopeInterceptor(appScopeMappingService); + public BasicAppInterceptor basicAppInterceptor(AppCacheService appCacheService) { + return new BasicAppInterceptor(appCacheService); } } diff --git a/src/backend/job-backup/boot-job-backup/src/main/java/com/tencent/bk/job/backup/config/JobBackupConfiguration.java b/src/backend/job-backup/boot-job-backup/src/main/java/com/tencent/bk/job/backup/config/JobBackupConfiguration.java index fb856f1307..96863b086d 100644 --- a/src/backend/job-backup/boot-job-backup/src/main/java/com/tencent/bk/job/backup/config/JobBackupConfiguration.java +++ b/src/backend/job-backup/boot-job-backup/src/main/java/com/tencent/bk/job/backup/config/JobBackupConfiguration.java @@ -24,9 +24,9 @@ package com.tencent.bk.job.backup.config; -import com.tencent.bk.job.common.service.AppScopeMappingService; -import com.tencent.bk.job.common.web.interceptor.AppResourceScopeInterceptor; -import com.tencent.bk.job.manage.AppScopeMappingServiceImpl; +import com.tencent.bk.job.common.service.AppCacheService; +import com.tencent.bk.job.common.web.interceptor.BasicAppInterceptor; +import com.tencent.bk.job.manage.AppCacheServiceImpl; import com.tencent.bk.job.manage.api.inner.ServiceApplicationResource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -34,12 +34,12 @@ @Configuration public class JobBackupConfiguration { @Bean - AppScopeMappingService appScopeMappingService(ServiceApplicationResource applicationResource) { - return new AppScopeMappingServiceImpl(applicationResource); + AppCacheService appScopeMappingService(ServiceApplicationResource applicationResource) { + return new AppCacheServiceImpl(applicationResource); } @Bean - public AppResourceScopeInterceptor appResourceScopeInterceptor(AppScopeMappingService appScopeMappingService) { - return new AppResourceScopeInterceptor(appScopeMappingService); + public BasicAppInterceptor basicAppInterceptor(AppCacheService appCacheService) { + return new BasicAppInterceptor(appCacheService); } } diff --git a/src/backend/job-crontab/boot-job-crontab/src/main/java/com/tencent/bk/job/crontab/config/JobCrontabConfiguration.java b/src/backend/job-crontab/boot-job-crontab/src/main/java/com/tencent/bk/job/crontab/config/JobCrontabConfiguration.java index 5fce17c9de..be5b200f83 100644 --- a/src/backend/job-crontab/boot-job-crontab/src/main/java/com/tencent/bk/job/crontab/config/JobCrontabConfiguration.java +++ b/src/backend/job-crontab/boot-job-crontab/src/main/java/com/tencent/bk/job/crontab/config/JobCrontabConfiguration.java @@ -24,9 +24,10 @@ package com.tencent.bk.job.crontab.config; +import com.tencent.bk.job.common.service.AppCacheService; import com.tencent.bk.job.common.service.AppScopeMappingService; -import com.tencent.bk.job.common.web.interceptor.AppResourceScopeInterceptor; -import com.tencent.bk.job.manage.AppScopeMappingServiceImpl; +import com.tencent.bk.job.common.web.interceptor.BasicAppInterceptor; +import com.tencent.bk.job.manage.AppCacheServiceImpl; import com.tencent.bk.job.manage.api.inner.ServiceApplicationResource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -34,12 +35,12 @@ @Configuration public class JobCrontabConfiguration { @Bean - AppScopeMappingService appScopeMappingService(ServiceApplicationResource applicationResource) { - return new AppScopeMappingServiceImpl(applicationResource); + AppCacheService appScopeMappingService(ServiceApplicationResource applicationResource) { + return new AppCacheServiceImpl(applicationResource); } @Bean - public AppResourceScopeInterceptor appResourceScopeInterceptor(AppScopeMappingService appScopeMappingService) { - return new AppResourceScopeInterceptor(appScopeMappingService); + public BasicAppInterceptor basicAppInterceptor(AppCacheService appCacheService) { + return new BasicAppInterceptor(appCacheService); } } diff --git a/src/backend/job-execute/boot-job-execute/src/main/java/com/tencent/bk/job/execute/config/JobExecuteConfiguration.java b/src/backend/job-execute/boot-job-execute/src/main/java/com/tencent/bk/job/execute/config/JobExecuteConfiguration.java index ce050bf526..5ba65ba11d 100644 --- a/src/backend/job-execute/boot-job-execute/src/main/java/com/tencent/bk/job/execute/config/JobExecuteConfiguration.java +++ b/src/backend/job-execute/boot-job-execute/src/main/java/com/tencent/bk/job/execute/config/JobExecuteConfiguration.java @@ -24,9 +24,10 @@ package com.tencent.bk.job.execute.config; +import com.tencent.bk.job.common.service.AppCacheService; import com.tencent.bk.job.common.service.AppScopeMappingService; -import com.tencent.bk.job.common.web.interceptor.AppResourceScopeInterceptor; -import com.tencent.bk.job.manage.AppScopeMappingServiceImpl; +import com.tencent.bk.job.common.web.interceptor.BasicAppInterceptor; +import com.tencent.bk.job.manage.AppCacheServiceImpl; import com.tencent.bk.job.manage.api.inner.ServiceApplicationResource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -34,12 +35,12 @@ @Configuration public class JobExecuteConfiguration { @Bean - AppScopeMappingService appScopeMappingService(ServiceApplicationResource applicationResource) { - return new AppScopeMappingServiceImpl(applicationResource); + AppCacheService appScopeMappingService(ServiceApplicationResource applicationResource) { + return new AppCacheServiceImpl(applicationResource); } @Bean - public AppResourceScopeInterceptor appResourceScopeInterceptor(AppScopeMappingService appScopeMappingService) { - return new AppResourceScopeInterceptor(appScopeMappingService); + public BasicAppInterceptor basicAppInterceptor(AppCacheService appCacheService) { + return new BasicAppInterceptor(appCacheService); } } diff --git a/src/backend/job-execute/service-job-execute/src/main/java/com/tencent/bk/job/execute/common/interceptor/JobExecuteContextHttpInterceptor.java b/src/backend/job-execute/service-job-execute/src/main/java/com/tencent/bk/job/execute/common/interceptor/JobExecuteContextHttpInterceptor.java index b399788c5c..a12fabcf72 100644 --- a/src/backend/job-execute/service-job-execute/src/main/java/com/tencent/bk/job/execute/common/interceptor/JobExecuteContextHttpInterceptor.java +++ b/src/backend/job-execute/service-job-execute/src/main/java/com/tencent/bk/job/execute/common/interceptor/JobExecuteContextHttpInterceptor.java @@ -57,7 +57,8 @@ public boolean preHandle(@NonNull HttpServletRequest request, JobContext jobContext = JobContextUtil.getContext(); if (jobContext != null) { JobExecuteContext jobExecuteContext = new JobExecuteContext(); - jobExecuteContext.setResourceScope(jobContext.getAppResourceScope()); + jobExecuteContext.setResourceScope( + jobContext.getApp() != null ? jobContext.getApp().getAppResourceScope() : null); jobExecuteContext.setUsername(jobContext.getUsername()); JobExecuteContextThreadLocalRepo.set(jobExecuteContext); if (log.isDebugEnabled()) { diff --git a/src/backend/job-file-gateway/boot-job-file-gateway/src/main/java/com/tencent/bk/job/file_gateway/config/JobFileGatewayConfiguration.java b/src/backend/job-file-gateway/boot-job-file-gateway/src/main/java/com/tencent/bk/job/file_gateway/config/JobFileGatewayConfiguration.java index e4140523f5..31bbbffec8 100644 --- a/src/backend/job-file-gateway/boot-job-file-gateway/src/main/java/com/tencent/bk/job/file_gateway/config/JobFileGatewayConfiguration.java +++ b/src/backend/job-file-gateway/boot-job-file-gateway/src/main/java/com/tencent/bk/job/file_gateway/config/JobFileGatewayConfiguration.java @@ -24,9 +24,10 @@ package com.tencent.bk.job.file_gateway.config; +import com.tencent.bk.job.common.service.AppCacheService; import com.tencent.bk.job.common.service.AppScopeMappingService; -import com.tencent.bk.job.common.web.interceptor.AppResourceScopeInterceptor; -import com.tencent.bk.job.manage.AppScopeMappingServiceImpl; +import com.tencent.bk.job.common.web.interceptor.BasicAppInterceptor; +import com.tencent.bk.job.manage.AppCacheServiceImpl; import com.tencent.bk.job.manage.api.inner.ServiceApplicationResource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -34,12 +35,12 @@ @Configuration public class JobFileGatewayConfiguration { @Bean - AppScopeMappingService appScopeMappingService(ServiceApplicationResource applicationResource) { - return new AppScopeMappingServiceImpl(applicationResource); + AppCacheService appScopeMappingService(ServiceApplicationResource applicationResource) { + return new AppCacheServiceImpl(applicationResource); } @Bean - public AppResourceScopeInterceptor appResourceScopeInterceptor(AppScopeMappingService appScopeMappingService) { - return new AppResourceScopeInterceptor(appScopeMappingService); + public BasicAppInterceptor basicAppInterceptor(AppCacheService appCacheService) { + return new BasicAppInterceptor(appCacheService); } } diff --git a/src/backend/job-gateway/src/main/java/com/tencent/bk/job/gateway/config/RouteConfig.java b/src/backend/job-gateway/src/main/java/com/tencent/bk/job/gateway/config/RouteConfig.java index 27a6815fe3..1138a1af89 100644 --- a/src/backend/job-gateway/src/main/java/com/tencent/bk/job/gateway/config/RouteConfig.java +++ b/src/backend/job-gateway/src/main/java/com/tencent/bk/job/gateway/config/RouteConfig.java @@ -25,7 +25,6 @@ package com.tencent.bk.job.gateway.config; import com.tencent.bk.job.common.constant.ErrorCode; -import com.tencent.bk.job.common.i18n.service.MessageI18nService; import com.tencent.bk.job.common.model.Response; import com.tencent.bk.job.common.model.dto.BkUserDTO; import com.tencent.bk.job.common.util.RequestUtil; @@ -58,23 +57,20 @@ public RouterFunction loginUserRouting(@Autowired UserHandler us } @Bean - public UserHandler userHandler(@Autowired LoginService loginService, @Autowired MessageI18nService i18nService, + public UserHandler userHandler(@Autowired LoginService loginService, @Autowired LoginExemptionConfig loginExemptionConfig) { - return new UserHandler(loginService, i18nService, loginExemptionConfig); + return new UserHandler(loginService, loginExemptionConfig); } static class UserHandler { - private LoginService loginService; - private MessageI18nService i18nService; - private LoginExemptionConfig loginExemptionConfig; + private final LoginService loginService; + private final LoginExemptionConfig loginExemptionConfig; UserHandler( LoginService loginService, - MessageI18nService i18nService, LoginExemptionConfig loginExemptionConfig ) { this.loginService = loginService; - this.i18nService = i18nService; this.loginExemptionConfig = loginExemptionConfig; } @@ -83,7 +79,6 @@ private BkUserDTO getLoginExemptUser() { bkUserDTO.setId(1L); bkUserDTO.setUsername(loginExemptionConfig.getDefaultUser()); bkUserDTO.setDisplayName(loginExemptionConfig.getDefaultUser()); - bkUserDTO.setUid(loginExemptionConfig.getDefaultUser()); return bkUserDTO; } diff --git a/src/backend/job-gateway/src/main/java/com/tencent/bk/job/gateway/filter/esb/CheckOpenApiJwtGatewayFilterFactory.java b/src/backend/job-gateway/src/main/java/com/tencent/bk/job/gateway/filter/esb/CheckOpenApiJwtGatewayFilterFactory.java index 99ac5a8ae6..320960443f 100644 --- a/src/backend/job-gateway/src/main/java/com/tencent/bk/job/gateway/filter/esb/CheckOpenApiJwtGatewayFilterFactory.java +++ b/src/backend/job-gateway/src/main/java/com/tencent/bk/job/gateway/filter/esb/CheckOpenApiJwtGatewayFilterFactory.java @@ -25,12 +25,14 @@ package com.tencent.bk.job.gateway.filter.esb; import com.tencent.bk.job.common.constant.JobCommonHeaders; +import com.tencent.bk.job.common.constant.TenantIdConstants; import com.tencent.bk.job.common.crypto.util.RSAUtils; import com.tencent.bk.job.common.security.autoconfigure.ServiceSecurityProperties; import com.tencent.bk.job.common.service.SpringProfile; +import com.tencent.bk.job.common.tenant.TenantEnvService; import com.tencent.bk.job.common.util.JobContextUtil; import com.tencent.bk.job.common.util.RequestUtil; -import com.tencent.bk.job.gateway.model.esb.EsbJwtInfo; +import com.tencent.bk.job.gateway.model.esb.BkGwJwtInfo; import com.tencent.bk.job.gateway.service.OpenApiJwtService; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -53,14 +55,18 @@ public class CheckOpenApiJwtGatewayFilterFactory private final SpringProfile springProfile; private final ServiceSecurityProperties securityProperties; + private final TenantEnvService tenantEnvService; + @Autowired public CheckOpenApiJwtGatewayFilterFactory(OpenApiJwtService openApiJwtService, SpringProfile springProfile, - ServiceSecurityProperties securityProperties) { + ServiceSecurityProperties securityProperties, + TenantEnvService tenantEnvService) { super(Config.class); this.openApiJwtService = openApiJwtService; this.springProfile = springProfile; this.securityProperties = securityProperties; + this.tenantEnvService = tenantEnvService; } @Override @@ -82,7 +88,7 @@ public GatewayFilter apply(Config config) { return response.setComplete(); } - EsbJwtInfo authInfo; + BkGwJwtInfo authInfo; if (isOpenApiTestActive(request)) { // 如果是 OpenApi 测试请求,使用 Job 的 JWT 认证方式,不使用 ESB JWT(避免依赖 ESB) authInfo = openApiJwtService.extractFromJwt(token, @@ -91,20 +97,46 @@ public GatewayFilter apply(Config config) { authInfo = openApiJwtService.extractFromJwt(token); } - if (authInfo == null) { - log.warn("Untrusted esb request, request-id:{}", RequestUtil.getHeaderValue(request, - JobCommonHeaders.BK_GATEWAY_REQUEST_ID)); + if (!validateJwt(authInfo)) { + log.warn("Untrusted open api request, request-id:{}, authInfo: {}", + RequestUtil.getHeaderValue(request, JobCommonHeaders.BK_GATEWAY_REQUEST_ID), authInfo); + response.setStatusCode(HttpStatus.UNAUTHORIZED); + return response.setComplete(); + } + + String tenantId = extractTenantId(request); + if (tenantId == null) { response.setStatusCode(HttpStatus.UNAUTHORIZED); return response.setComplete(); } - // set app code header + // set header request.mutate().header(JobCommonHeaders.APP_CODE, new String[]{authInfo.getAppCode()}).build(); request.mutate().header(JobCommonHeaders.USERNAME, new String[]{authInfo.getUsername()}).build(); + request.mutate().header(JobCommonHeaders.BK_TENANT_ID, new String[]{tenantId}).build(); return chain.filter(exchange.mutate().request(request).build()); }; } + private String extractTenantId(ServerHttpRequest request) { + if (tenantEnvService.isTenantEnabled()) { + String tenantId = RequestUtil.getHeaderValue(request, JobCommonHeaders.BK_TENANT_ID); + if (StringUtils.isEmpty(tenantId)) { + log.error("Missing tenant header from bkApiGateway"); + return null; + } else { + return tenantId; + } + } else { + // 如果未开启多租户特性,设置默认租户 default(蓝鲸约定) + return TenantIdConstants.DEFAULT_TENANT_ID; + } + } + + public boolean validateJwt(BkGwJwtInfo jwtInfo) { + return StringUtils.isNotEmpty(jwtInfo.getUsername()) && StringUtils.isNotEmpty(jwtInfo.getAppCode()); + } + private boolean isOpenApiTestActive(ServerHttpRequest request) { if (!springProfile.isProfileActive("openApiTestEnv")) { return false; diff --git a/src/backend/job-gateway/src/main/java/com/tencent/bk/job/gateway/filter/web/AuthorizeGatewayFilterFactory.java b/src/backend/job-gateway/src/main/java/com/tencent/bk/job/gateway/filter/web/AuthorizeGatewayFilterFactory.java index cdaa72e141..1b47d91127 100644 --- a/src/backend/job-gateway/src/main/java/com/tencent/bk/job/gateway/filter/web/AuthorizeGatewayFilterFactory.java +++ b/src/backend/job-gateway/src/main/java/com/tencent/bk/job/gateway/filter/web/AuthorizeGatewayFilterFactory.java @@ -25,11 +25,14 @@ package com.tencent.bk.job.gateway.filter.web; import com.tencent.bk.job.common.constant.ErrorCode; +import com.tencent.bk.job.common.constant.JobCommonHeaders; +import com.tencent.bk.job.common.constant.TenantIdConstants; import com.tencent.bk.job.common.exception.InternalUserManageException; import com.tencent.bk.job.common.i18n.locale.LocaleUtils; import com.tencent.bk.job.common.model.Response; import com.tencent.bk.job.common.model.dto.BkUserDTO; import com.tencent.bk.job.common.paas.exception.AppPermissionDeniedException; +import com.tencent.bk.job.common.tenant.TenantEnvService; import com.tencent.bk.job.common.util.RequestUtil; import com.tencent.bk.job.common.util.json.JsonUtils; import com.tencent.bk.job.gateway.config.LoginExemptionConfig; @@ -63,12 +66,16 @@ public class AuthorizeGatewayFilterFactory extends AbstractGatewayFilterFactory< private final LoginService loginService; private final LoginExemptionConfig loginExemptionConfig; + private final TenantEnvService tenantEnvService; @Autowired - public AuthorizeGatewayFilterFactory(LoginService loginService, LoginExemptionConfig loginExemptionConfig) { + public AuthorizeGatewayFilterFactory(LoginService loginService, + LoginExemptionConfig loginExemptionConfig, + TenantEnvService tenantEnvService) { super(Config.class); this.loginService = loginService; this.loginExemptionConfig = loginExemptionConfig; + this.tenantEnvService = tenantEnvService; } private GatewayFilter getLoginExemptionFilter() { @@ -123,9 +130,11 @@ private GatewayFilter getLoginFilter() { response.getHeaders().add("x-login-url", loginService.getLoginRedirectUrl()); return response.setComplete(); } - String username = user.getUsername(); - request.mutate().header("username", new String[]{username}).build(); + request.mutate().header("username", new String[]{user.getUsername()}).build(); + String tenantId = tenantEnvService.isTenantEnabled() ? user.getTenantId() : + TenantIdConstants.DEFAULT_TENANT_ID; + request.mutate().header(JobCommonHeaders.BK_TENANT_ID, new String[]{tenantId}).build(); return chain.filter(exchange.mutate().request(request).build()); }; } diff --git a/src/backend/job-gateway/src/main/java/com/tencent/bk/job/gateway/model/esb/EsbJwtInfo.java b/src/backend/job-gateway/src/main/java/com/tencent/bk/job/gateway/model/esb/BkGwJwtInfo.java similarity index 93% rename from src/backend/job-gateway/src/main/java/com/tencent/bk/job/gateway/model/esb/EsbJwtInfo.java rename to src/backend/job-gateway/src/main/java/com/tencent/bk/job/gateway/model/esb/BkGwJwtInfo.java index e61d5631f3..946309c095 100644 --- a/src/backend/job-gateway/src/main/java/com/tencent/bk/job/gateway/model/esb/EsbJwtInfo.java +++ b/src/backend/job-gateway/src/main/java/com/tencent/bk/job/gateway/model/esb/BkGwJwtInfo.java @@ -27,9 +27,12 @@ import lombok.Data; import lombok.NoArgsConstructor; +/** + * 蓝鲸网关 JWT + */ @Data @NoArgsConstructor -public class EsbJwtInfo { +public class BkGwJwtInfo { /** * jwt失效时间 */ @@ -43,9 +46,10 @@ public class EsbJwtInfo { */ private String appCode; - public EsbJwtInfo(Long tokenExpireAt, String username, String appCode) { + public BkGwJwtInfo(Long tokenExpireAt, String username, String appCode) { this.tokenExpireAt = tokenExpireAt; this.username = username; this.appCode = appCode; } + } diff --git a/src/backend/job-gateway/src/main/java/com/tencent/bk/job/gateway/service/OpenApiJwtService.java b/src/backend/job-gateway/src/main/java/com/tencent/bk/job/gateway/service/OpenApiJwtService.java index 2db8be8a1c..3823d669ad 100644 --- a/src/backend/job-gateway/src/main/java/com/tencent/bk/job/gateway/service/OpenApiJwtService.java +++ b/src/backend/job-gateway/src/main/java/com/tencent/bk/job/gateway/service/OpenApiJwtService.java @@ -24,7 +24,7 @@ package com.tencent.bk.job.gateway.service; -import com.tencent.bk.job.gateway.model.esb.EsbJwtInfo; +import com.tencent.bk.job.gateway.model.esb.BkGwJwtInfo; import java.security.PublicKey; @@ -36,7 +36,7 @@ public interface OpenApiJwtService { * @param token token * @return 解析 JWT 结果 */ - EsbJwtInfo extractFromJwt(String token); + BkGwJwtInfo extractFromJwt(String token); /** * 从jwt提取数据 @@ -45,5 +45,5 @@ public interface OpenApiJwtService { * @param publicKey 公钥 * @return 解析 JWT 结果 */ - EsbJwtInfo extractFromJwt(String token, PublicKey publicKey); + BkGwJwtInfo extractFromJwt(String token, PublicKey publicKey); } diff --git a/src/backend/job-gateway/src/main/java/com/tencent/bk/job/gateway/service/impl/OpenApiJwtServiceImpl.java b/src/backend/job-gateway/src/main/java/com/tencent/bk/job/gateway/service/impl/OpenApiJwtServiceImpl.java index 0dcfa4c929..27d7b67ca5 100644 --- a/src/backend/job-gateway/src/main/java/com/tencent/bk/job/gateway/service/impl/OpenApiJwtServiceImpl.java +++ b/src/backend/job-gateway/src/main/java/com/tencent/bk/job/gateway/service/impl/OpenApiJwtServiceImpl.java @@ -30,7 +30,7 @@ import com.tencent.bk.job.common.util.JobContextUtil; import com.tencent.bk.job.common.util.ThreadUtils; import com.tencent.bk.job.gateway.config.BkGatewayConfig; -import com.tencent.bk.job.gateway.model.esb.EsbJwtInfo; +import com.tencent.bk.job.gateway.model.esb.BkGwJwtInfo; import com.tencent.bk.job.gateway.service.OpenApiJwtPublicKeyService; import com.tencent.bk.job.gateway.service.OpenApiJwtService; import io.jsonwebtoken.Claims; @@ -67,7 +67,7 @@ public class OpenApiJwtServiceImpl implements OpenApiJwtService { */ private static final String REQUEST_FROM_BK_API_GW = "bk-job-apigw"; - private final Cache tokenCache = CacheBuilder.newBuilder() + private final Cache tokenCache = CacheBuilder.newBuilder() .maximumSize(99999).expireAfterWrite(30, TimeUnit.SECONDS).build(); @Autowired @@ -157,7 +157,7 @@ private PublicKey buildPublicKey(String pemContent) } @Override - public EsbJwtInfo extractFromJwt(String token) { + public BkGwJwtInfo extractFromJwt(String token) { if (requestFromApiGw()) { log.debug("Extract bkApiGateway jwt"); return extractFromJwt(token, this.bkApiGatewayJwtPublicKey); @@ -168,9 +168,9 @@ public EsbJwtInfo extractFromJwt(String token) { } @Override - public EsbJwtInfo extractFromJwt(String token, PublicKey publicKey) { + public BkGwJwtInfo extractFromJwt(String token, PublicKey publicKey) { long start = System.currentTimeMillis(); - EsbJwtInfo cacheJwtInfo = tokenCache.getIfPresent(token); + BkGwJwtInfo cacheJwtInfo = tokenCache.getIfPresent(token); if (cacheJwtInfo != null) { Long tokenExpireAt = cacheJwtInfo.getTokenExpireAt(); // 如果未超时 @@ -179,13 +179,14 @@ public EsbJwtInfo extractFromJwt(String token, PublicKey publicKey) { } } - EsbJwtInfo esbJwtInfo; + BkGwJwtInfo bkGwJwtInfo; try { Claims claims = Jwts.parser() .setSigningKey(publicKey) .parseClaimsJws(token) .getBody(); - String appCode = ""; + String appCode = null; + String username = null; if (claims.get("app") != null) { LinkedHashMap appProps = claims.get("app", LinkedHashMap.class); if (appProps == null) { @@ -193,39 +194,29 @@ public EsbJwtInfo extractFromJwt(String token, PublicKey publicKey) { return null; } boolean isVerified = appProps.get("verified") != null && (boolean) appProps.get("verified"); - appCode = (String) appProps.get("app_code"); - if (StringUtils.isEmpty(appCode)) { - appCode = (String) appProps.get("bk_app_code"); - } - if (!isVerified || StringUtils.isEmpty(appCode)) { - log.warn("App code not verified or empty, isVerified:{}, jwtAppCode:{}", isVerified, appCode); + if (!isVerified) { + log.warn("App info not verified"); return null; } + appCode = extractAppCode(appProps); } - String username = ""; if (claims.get("user") != null) { LinkedHashMap userProps = claims.get("user", LinkedHashMap.class); if (userProps == null) { log.warn("Invalid JWT token, user is null!"); return null; } - username = (String) userProps.get("username"); - if (StringUtils.isEmpty(username)) { - username = (String) userProps.get("bk_username"); - } - if (StringUtils.isEmpty(username)) { - log.warn("Username is empty!"); - return null; - } + username = extractUsername(userProps); } + Date expireAt = claims.get("exp", Date.class); if (expireAt == null) { log.warn("Invalid JWT token, exp is null!"); return null; } - esbJwtInfo = new EsbJwtInfo(expireAt.getTime(), username, appCode); - tokenCache.put(token, esbJwtInfo); + bkGwJwtInfo = new BkGwJwtInfo(expireAt.getTime(), username, appCode); + tokenCache.put(token, bkGwJwtInfo); } catch (Exception e) { log.warn("Verify jwt caught exception", e); if (log.isDebugEnabled()) { @@ -238,7 +229,29 @@ public EsbJwtInfo extractFromJwt(String token, PublicKey publicKey) { log.warn("Verify jwt cost too much, cost:{}", cost); } } - return esbJwtInfo; + return bkGwJwtInfo; + } + + private String extractAppCode(LinkedHashMap appProps) { + String appCode = (String) appProps.get("app_code"); + if (StringUtils.isEmpty(appCode)) { + appCode = (String) appProps.get("bk_app_code"); + } + return appCode; + } + + /** + * 获取用户账号 ID + * + * @param userProps 用户信息 + * @return 用户账号 ID + */ + private String extractUsername(LinkedHashMap userProps) { + String username = (String) userProps.get("username"); + if (StringUtils.isEmpty(username)) { + username = (String) userProps.get("bk_username"); + } + return username; } // 请求是否来自蓝鲸网关 diff --git a/src/backend/job-manage/api-job-manage/src/main/java/com/tencent/bk/job/manage/AbstractBasicAppCache.java b/src/backend/job-manage/api-job-manage/src/main/java/com/tencent/bk/job/manage/AbstractBasicAppCache.java new file mode 100644 index 0000000000..3e833e6f9a --- /dev/null +++ b/src/backend/job-manage/api-job-manage/src/main/java/com/tencent/bk/job/manage/AbstractBasicAppCache.java @@ -0,0 +1,84 @@ +/* + * Tencent is pleased to support the open source community by making BK-JOB蓝鲸智云作业平台 available. + * + * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-JOB蓝鲸智云作业平台 is licensed under the MIT License. + * + * License for BK-JOB蓝鲸智云作业平台: + * -------------------------------------------------------------------- + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and + * to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of + * the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO + * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +package com.tencent.bk.job.manage; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.util.concurrent.UncheckedExecutionException; +import com.tencent.bk.job.common.constant.ErrorCode; +import com.tencent.bk.job.common.exception.InternalException; +import com.tencent.bk.job.common.exception.ServiceException; +import com.tencent.bk.job.common.model.dto.ResourceScope; +import com.tencent.bk.job.common.model.BasicApp; +import lombok.extern.slf4j.Slf4j; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +/** + * 业务基础信息缓存公共抽象类 + */ +@Slf4j +public abstract class AbstractBasicAppCache { + + private final LoadingCache scopeAndAppCache = + CacheBuilder.newBuilder().maximumSize(100_000).expireAfterWrite(1, TimeUnit.HOURS) + .build(new CacheLoader() { + @Override + public BasicApp load(ResourceScope resourceScope) { + return loadAppToCache(resourceScope); + } + } + ); + + public BasicApp get(ResourceScope resourceScope) { + try { + return scopeAndAppCache.get(resourceScope); + } catch (ExecutionException e) { + // 处理被CacheLoader包装的原始异常 + log.error("Get immutable application from cache error", e); + throw new InternalException("Get immutable application from cache error", e, ErrorCode.INTERNAL_ERROR); + } catch (UncheckedExecutionException e) { + // 处理被CacheLoader包装的原始异常 + Throwable t = e.getCause(); + if (t instanceof ServiceException) { + throw (ServiceException) e.getCause(); + } else { + log.error("Get immutable application from cache error", e); + throw new InternalException("Get immutable application from cache error", e, ErrorCode.INTERNAL_ERROR); + } + } + } + + /** + * 加载到缓存 + * + * @param resourceScope 资源管理空间 + * @return 业务基础信息 + */ + protected abstract BasicApp loadAppToCache(ResourceScope resourceScope); + +} diff --git a/src/backend/job-manage/api-job-manage/src/main/java/com/tencent/bk/job/manage/AbstractLocalCacheAppScopeMappingService.java b/src/backend/job-manage/api-job-manage/src/main/java/com/tencent/bk/job/manage/AbstractLocalCacheAppService.java similarity index 66% rename from src/backend/job-manage/api-job-manage/src/main/java/com/tencent/bk/job/manage/AbstractLocalCacheAppScopeMappingService.java rename to src/backend/job-manage/api-job-manage/src/main/java/com/tencent/bk/job/manage/AbstractLocalCacheAppService.java index 36547bb319..60f0cfc6a0 100644 --- a/src/backend/job-manage/api-job-manage/src/main/java/com/tencent/bk/job/manage/AbstractLocalCacheAppScopeMappingService.java +++ b/src/backend/job-manage/api-job-manage/src/main/java/com/tencent/bk/job/manage/AbstractLocalCacheAppService.java @@ -32,9 +32,10 @@ import com.tencent.bk.job.common.exception.InternalException; import com.tencent.bk.job.common.exception.NotFoundException; import com.tencent.bk.job.common.exception.ServiceException; +import com.tencent.bk.job.common.model.BasicApp; import com.tencent.bk.job.common.model.dto.AppResourceScope; import com.tencent.bk.job.common.model.dto.ResourceScope; -import com.tencent.bk.job.common.service.AppScopeMappingService; +import com.tencent.bk.job.common.service.AppCacheService; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -45,60 +46,38 @@ import java.util.concurrent.TimeUnit; /** - * 业务与资源范围转换公共实现 - 使用本地缓存 + * 业务信息缓存 */ @Slf4j -public abstract class AbstractLocalCacheAppScopeMappingService implements AppScopeMappingService { +public abstract class AbstractLocalCacheAppService implements AppCacheService { - /** - * appId 与 resourceScope 的映射关系缓存 - * 由于appId与resourceScope映射关系一旦确定之后就不会再发生变化,所以使用本地缓存来优化查询性能 - */ - private final LoadingCache appIdAndScopeCache = + private final LoadingCache appIdAndAppCache = CacheBuilder.newBuilder().maximumSize(100_000).expireAfterWrite(1, TimeUnit.HOURS) - .build(new CacheLoader() { + .build(new CacheLoader() { @Override - public ResourceScope load(Long appId) { - return queryScopeByAppId(appId); + public BasicApp load(Long appId) { + return queryAppByAppId(appId); } } ); - /** - * resourceScope 与 appId 的映射关系缓存 - * 由于resourceScope与appId映射关系一旦确定之后就不会再发生变化,所以使用本地缓存来优化查询性能 - */ - private final LoadingCache scopeAndAppIdCache = + private final LoadingCache scopeAndAppCache = CacheBuilder.newBuilder().maximumSize(100_000).expireAfterWrite(1, TimeUnit.HOURS) - .build(new CacheLoader() { + .build(new CacheLoader() { @Override - public Long load(ResourceScope resourceScope) { + public BasicApp load(ResourceScope resourceScope) { return queryAppByScope(resourceScope); } } ); - public AbstractLocalCacheAppScopeMappingService() { + public AbstractLocalCacheAppService() { } public Long getAppIdByScope(ResourceScope resourceScope) { - try { - return scopeAndAppIdCache.get(resourceScope); - } catch (ExecutionException e) { - // 处理被CacheLoader包装的原始异常 - log.error("Get appId from cache error", e); - throw new InternalException("Get appId from cache error", e, ErrorCode.INTERNAL_ERROR); - } catch (UncheckedExecutionException e) { - // 处理被CacheLoader包装的原始异常 - Throwable t = e.getCause(); - if (t instanceof ServiceException) { - throw (ServiceException) e.getCause(); - } else { - log.error("Get appId from cache error", e); - throw new InternalException("Get appId from cache error", e, ErrorCode.INTERNAL_ERROR); - } - } + BasicApp app = getApp(resourceScope); + return app == null ? null : app.getId(); } @Override @@ -107,22 +86,8 @@ public Long getAppIdByScope(String scopeType, String scopeId) { } public ResourceScope getScopeByAppId(Long appId) { - try { - return appIdAndScopeCache.get(appId); - } catch (ExecutionException e) { - // 处理被CacheLoader包装的原始异常 - log.error("Get scope from cache error", e); - throw new InternalException("Get scope from cache error", e, ErrorCode.INTERNAL_ERROR); - } catch (UncheckedExecutionException e) { - // 处理被CacheLoader包装的原始异常 - Throwable t = e.getCause(); - if (t instanceof ServiceException) { - throw (ServiceException) e.getCause(); - } else { - log.error("Get scope from cache error", e); - throw new InternalException("Get scope from cache error", e, ErrorCode.INTERNAL_ERROR); - } - } + BasicApp app = queryAppByAppId(appId); + return app == null ? null : app.getScope(); } @Override @@ -170,20 +135,54 @@ public Map getAppIdByScopeList(Collection sc } /** - * 根据资源范围查询JOB业务ID + * 根据资源管理空间查询JOB业务信息 * - * @param resourceScope 资源范围 - * @return JOB业务ID + * @param resourceScope 资源管理空间 ID + * @return 业务 * @throws NotFoundException 如果业务不存在,抛出NotFoundException */ - public abstract Long queryAppByScope(ResourceScope resourceScope) throws NotFoundException; + protected abstract BasicApp queryAppByScope(ResourceScope resourceScope) throws NotFoundException; /** - * 根据JOB业务ID查询资源范围 + * 根据 appId 查询 业务 * * @param appId Job业务ID - * @return 资源范围 + * @return 业务 * @throws NotFoundException 如果业务不存在,抛出NotFoundException */ - public abstract ResourceScope queryScopeByAppId(Long appId) throws NotFoundException; + protected abstract BasicApp queryAppByAppId(Long appId) throws NotFoundException; + + @Override + public BasicApp getApp(ResourceScope resourceScope) { + return queryCache(() -> scopeAndAppCache.get(resourceScope)); + } + + @Override + public BasicApp getApp(Long appId) { + return queryCache(() -> appIdAndAppCache.get(appId)); + } + + @FunctionalInterface + public interface QueryCache { + T query() throws ExecutionException, UncheckedExecutionException; + } + + private BasicApp queryCache(QueryCache query) { + try { + return query.query(); + } catch (ExecutionException e) { + // 处理被CacheLoader包装的原始异常 + log.error("Get app from cache error", e); + throw new InternalException("Get app from cache error", e, ErrorCode.INTERNAL_ERROR); + } catch (UncheckedExecutionException e) { + // 处理被CacheLoader包装的原始异常 + Throwable t = e.getCause(); + if (t instanceof ServiceException) { + throw (ServiceException) e.getCause(); + } else { + log.error("Get app from cache error", e); + throw new InternalException("Get app from cache error", e, ErrorCode.INTERNAL_ERROR); + } + } + } } diff --git a/src/backend/job-manage/api-job-manage/src/main/java/com/tencent/bk/job/manage/AppScopeMappingServiceImpl.java b/src/backend/job-manage/api-job-manage/src/main/java/com/tencent/bk/job/manage/AppCacheServiceImpl.java similarity index 80% rename from src/backend/job-manage/api-job-manage/src/main/java/com/tencent/bk/job/manage/AppScopeMappingServiceImpl.java rename to src/backend/job-manage/api-job-manage/src/main/java/com/tencent/bk/job/manage/AppCacheServiceImpl.java index 4993a254d4..ab32c1bae9 100644 --- a/src/backend/job-manage/api-job-manage/src/main/java/com/tencent/bk/job/manage/AppScopeMappingServiceImpl.java +++ b/src/backend/job-manage/api-job-manage/src/main/java/com/tencent/bk/job/manage/AppCacheServiceImpl.java @@ -27,6 +27,7 @@ import com.tencent.bk.job.common.constant.ErrorCode; import com.tencent.bk.job.common.exception.InternalException; import com.tencent.bk.job.common.exception.NotFoundException; +import com.tencent.bk.job.common.model.BasicApp; import com.tencent.bk.job.common.model.dto.ResourceScope; import com.tencent.bk.job.manage.api.inner.ServiceApplicationResource; import com.tencent.bk.job.manage.model.inner.resp.ServiceApplicationDTO; @@ -34,20 +35,20 @@ import org.apache.commons.lang3.StringUtils; /** - * 业务与资源范围转换 + * 业务基础信息缓存 */ @Slf4j -public class AppScopeMappingServiceImpl extends AbstractLocalCacheAppScopeMappingService { +public class AppCacheServiceImpl extends AbstractLocalCacheAppService { private final ServiceApplicationResource applicationResource; - public AppScopeMappingServiceImpl(ServiceApplicationResource applicationResource) { + public AppCacheServiceImpl(ServiceApplicationResource applicationResource) { this.applicationResource = applicationResource; GlobalAppScopeMappingService.register(this); } @Override - public Long queryAppByScope(ResourceScope resourceScope) throws NotFoundException { + protected BasicApp queryAppByScope(ResourceScope resourceScope) throws NotFoundException { ServiceApplicationDTO app = applicationResource.queryAppByScope( resourceScope.getType().getValue(), resourceScope.getId()); if (app == null) { @@ -59,11 +60,11 @@ public Long queryAppByScope(ResourceScope resourceScope) throws NotFoundExceptio log.error("Empty appId for application, reject cache! query scope: {}", resourceScope); throw new InternalException("Empty appId for application", ErrorCode.INTERNAL_ERROR); } - return app.getId(); + return convertToBasicApp(app); } @Override - public ResourceScope queryScopeByAppId(Long appId) throws NotFoundException { + protected BasicApp queryAppByAppId(Long appId) throws NotFoundException { ServiceApplicationDTO app = applicationResource.queryAppById(appId); if (app == null) { log.error("App not found, query appId: {}", appId); @@ -74,6 +75,17 @@ public ResourceScope queryScopeByAppId(Long appId) throws NotFoundException { log.error("Empty scopeType|scopeId for application, reject cache! query appId: {}", appId); throw new InternalException("Empty scopeType|scopeId for application", ErrorCode.INTERNAL_ERROR); } - return new ResourceScope(app.getScopeType(), app.getScopeId()); + return convertToBasicApp(app); } + + private BasicApp convertToBasicApp(ServiceApplicationDTO app) { + BasicApp basicApp = new BasicApp(); + basicApp.setId(app.getId()); + basicApp.setName(app.getName()); + basicApp.setScope(new ResourceScope(app.getScopeType(), app.getScopeId())); + basicApp.setTenantId(app.getTenantId()); + return basicApp; + } + + } diff --git a/src/backend/job-manage/api-job-manage/src/main/java/com/tencent/bk/job/manage/BasicAppCache.java b/src/backend/job-manage/api-job-manage/src/main/java/com/tencent/bk/job/manage/BasicAppCache.java new file mode 100644 index 0000000000..59be1b9752 --- /dev/null +++ b/src/backend/job-manage/api-job-manage/src/main/java/com/tencent/bk/job/manage/BasicAppCache.java @@ -0,0 +1,34 @@ +package com.tencent.bk.job.manage; + +import com.tencent.bk.job.common.model.dto.ResourceScope; +import com.tencent.bk.job.manage.api.inner.ServiceApplicationResource; +import com.tencent.bk.job.common.model.BasicApp; +import com.tencent.bk.job.manage.model.inner.resp.ServiceApplicationDTO; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class BasicAppCache extends AbstractBasicAppCache { + + private final ServiceApplicationResource applicationResource; + + + public BasicAppCache(ServiceApplicationResource applicationResource) { + this.applicationResource = applicationResource; + } + + @Override + protected BasicApp loadAppToCache(ResourceScope resourceScope) { + ServiceApplicationDTO app = + applicationResource.queryAppByScope(resourceScope.getType().getValue(), resourceScope.getId()); + if (app == null) { + return null; + } + + BasicApp basicApp = new BasicApp(); + basicApp.setId(app.getId()); + basicApp.setName(app.getName()); + basicApp.setScope(new ResourceScope(app.getScopeType(), app.getScopeId())); + basicApp.setTenantId(app.getTenantId()); + return basicApp; + } +} diff --git a/src/backend/job-manage/api-job-manage/src/main/java/com/tencent/bk/job/manage/model/inner/resp/ServiceApplicationDTO.java b/src/backend/job-manage/api-job-manage/src/main/java/com/tencent/bk/job/manage/model/inner/resp/ServiceApplicationDTO.java index b51aad50cb..23446a98a1 100644 --- a/src/backend/job-manage/api-job-manage/src/main/java/com/tencent/bk/job/manage/model/inner/resp/ServiceApplicationDTO.java +++ b/src/backend/job-manage/api-job-manage/src/main/java/com/tencent/bk/job/manage/model/inner/resp/ServiceApplicationDTO.java @@ -79,6 +79,11 @@ public class ServiceApplicationDTO { */ private ServiceApplicationAttrsDTO attrs; + /** + * 租户 ID + */ + private String tenantId; + public boolean isBiz() { return ResourceScopeTypeEnum.BIZ.getValue().equals(scopeType); } diff --git a/src/backend/job-manage/boot-job-manage/src/main/java/com/tencent/bk/job/manage/config/JobManageConfiguration.java b/src/backend/job-manage/boot-job-manage/src/main/java/com/tencent/bk/job/manage/config/JobManageConfiguration.java index d915818158..998ddd4ea7 100644 --- a/src/backend/job-manage/boot-job-manage/src/main/java/com/tencent/bk/job/manage/config/JobManageConfiguration.java +++ b/src/backend/job-manage/boot-job-manage/src/main/java/com/tencent/bk/job/manage/config/JobManageConfiguration.java @@ -24,8 +24,8 @@ package com.tencent.bk.job.manage.config; -import com.tencent.bk.job.common.service.AppScopeMappingService; -import com.tencent.bk.job.common.web.interceptor.AppResourceScopeInterceptor; +import com.tencent.bk.job.common.service.AppCacheService; +import com.tencent.bk.job.common.web.interceptor.BasicAppInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -33,7 +33,7 @@ public class JobManageConfiguration { @Bean - public AppResourceScopeInterceptor appResourceScopeInterceptor(AppScopeMappingService appScopeMappingService) { - return new AppResourceScopeInterceptor(appScopeMappingService); + public BasicAppInterceptor basicAppInterceptor(AppCacheService appCacheService) { + return new BasicAppInterceptor(appCacheService); } } diff --git a/src/backend/job-manage/boot-job-manage/src/test/resources/application-test.yml b/src/backend/job-manage/boot-job-manage/src/test/resources/application-test.yml index 0400e83847..e02914156a 100644 --- a/src/backend/job-manage/boot-job-manage/src/test/resources/application-test.yml +++ b/src/backend/job-manage/boot-job-manage/src/test/resources/application-test.yml @@ -33,6 +33,11 @@ bk-api-gateway: url: bk-notice.apigw.com cmdb: url: cmdb.apigw.com + bk-login: + url: bk-login.apigw.com + bk-user: + url: bk-user.apigw.com esb: service: url: esb.service + diff --git a/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/api/inner/impl/ServiceApplicationResourceImpl.java b/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/api/inner/impl/ServiceApplicationResourceImpl.java index 51d8cd3245..16ce3d3248 100644 --- a/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/api/inner/impl/ServiceApplicationResourceImpl.java +++ b/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/api/inner/impl/ServiceApplicationResourceImpl.java @@ -110,6 +110,7 @@ private ServiceApplicationDTO convertToServiceApp(ApplicationDTO appInfo) { attrs.setSubBizIds(appInfo.getAttrs().getSubBizIds()); app.setAttrs(attrs); } + app.setTenantId(appInfo.getTenantId()); return app; } diff --git a/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/dao/UserDAO.java b/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/dao/UserDAO.java new file mode 100644 index 0000000000..a507402b89 --- /dev/null +++ b/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/dao/UserDAO.java @@ -0,0 +1,21 @@ +package com.tencent.bk.job.manage.dao; + +import com.tencent.bk.job.common.model.dto.BkUserDTO; + +import java.util.Collection; +import java.util.List; + +public interface UserDAO { + + void saveUser(BkUserDTO user); + + int deleteUser(String username); + + List listTenantUsers(String tenantId); + + List listUsersByDisplayNamePrefix(String tenantId, String prefixStr, Long limit); + + List listUsersByUsernames(String tenantId, Collection usernames); + + List listExistUserName(String tenantId, Collection usernames); +} diff --git a/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/dao/impl/ApplicationDAOImpl.java b/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/dao/impl/ApplicationDAOImpl.java index 7db8be6cb3..f2a100b814 100644 --- a/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/dao/impl/ApplicationDAOImpl.java +++ b/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/dao/impl/ApplicationDAOImpl.java @@ -72,7 +72,8 @@ public class ApplicationDAOImpl implements ApplicationDAO { T_APP.TIMEZONE, T_APP.LANGUAGE, T_APP.IS_DELETED, - T_APP.ATTRS + T_APP.ATTRS, + T_APP.TENANT_ID }; private final DSLContext dslContext; @@ -122,6 +123,7 @@ public static ApplicationDTO extract(Record record) { applicationDTO.setLanguage(record.get(T_APP.LANGUAGE)); applicationDTO.setAttrs(JsonUtils.fromJson(record.get(T_APP.ATTRS), ApplicationAttrsDO.class)); applicationDTO.setDeleted(Bool.isTrue(record.get(T_APP.IS_DELETED).byteValue())); + applicationDTO.setTenantId(record.get(T_APP.TENANT_ID)); return applicationDTO; } @@ -222,7 +224,8 @@ public Long insertApp(ApplicationDTO applicationDTO) { T_APP.BK_SCOPE_TYPE, T_APP.BK_SCOPE_ID, T_APP.ATTRS, - T_APP.IS_DELETED + T_APP.IS_DELETED, + T_APP.TENANT_ID ).values( applicationDTO.getName(), applicationDTO.getBkSupplierAccount(), @@ -231,7 +234,8 @@ public Long insertApp(ApplicationDTO applicationDTO) { scope == null ? null : scope.getType().getValue(), scope == null ? null : scope.getId(), applicationDTO.getAttrs() == null ? null : JsonUtils.toJson(applicationDTO.getAttrs()), - UByte.valueOf(Bool.FALSE.byteValue()) + UByte.valueOf(Bool.FALSE.byteValue()), + applicationDTO.getTenantId() ); try { val record = query.returning(T_APP.APP_ID).fetchOne(); diff --git a/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/dao/impl/UserDAOImpl.java b/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/dao/impl/UserDAOImpl.java new file mode 100644 index 0000000000..49498b6782 --- /dev/null +++ b/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/dao/impl/UserDAOImpl.java @@ -0,0 +1,126 @@ +package com.tencent.bk.job.manage.dao.impl; + +import com.tencent.bk.job.common.model.dto.BkUserDTO; +import com.tencent.bk.job.manage.dao.UserDAO; +import com.tencent.bk.job.manage.model.tables.User; +import lombok.extern.slf4j.Slf4j; +import org.jooq.DSLContext; +import org.jooq.Record; +import org.jooq.Result; +import org.jooq.TableField; +import org.jooq.types.ULong; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Repository; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +@Slf4j +@Repository +public class UserDAOImpl implements UserDAO { + + private static final User T_USER = User.USER; + private static final TableField[] ALL_FIELDS = { + T_USER.USERNAME, + T_USER.TENANT_ID, + T_USER.DISPLAY_NAME, + T_USER.LAST_MODIFY_TIME + }; + + private final DSLContext dslContext; + + @Autowired + public UserDAOImpl(@Qualifier("job-manage-dsl-context") DSLContext dslContext) { + this.dslContext = dslContext; + } + + @Override + public void saveUser(BkUserDTO user) { + dslContext + .insertInto( + T_USER, + T_USER.USERNAME, + T_USER.DISPLAY_NAME, + T_USER.TENANT_ID, + T_USER.LAST_MODIFY_TIME + ).values( + user.getUsername(), + user.getDisplayName(), + user.getTenantId(), + ULong.valueOf(System.currentTimeMillis()) + ).execute(); + } + + @Override + public int deleteUser(String username) { + return dslContext + .deleteFrom(T_USER) + .where(T_USER.USERNAME.eq(username)) + .execute(); + } + + @Override + public List listTenantUsers(String tenantId) { + Result result = dslContext + .select(ALL_FIELDS) + .from(T_USER) + .where(T_USER.TENANT_ID.eq(tenantId)) + .fetch(); + if (result.isEmpty()) { + return null; + } + return result.stream().map(this::extract).collect(Collectors.toList()); + } + + private BkUserDTO extract(Record record) { + BkUserDTO user = new BkUserDTO(); + user.setUsername(record.get(T_USER.USERNAME)); + user.setDisplayName(record.get(T_USER.DISPLAY_NAME)); + user.setTenantId(record.get(T_USER.TENANT_ID)); + return user; + } + + @Override + public List listUsersByDisplayNamePrefix(String tenantId, String prefixStr, Long limit) { + Result result = dslContext + .select(ALL_FIELDS) + .from(T_USER) + .where(T_USER.TENANT_ID.eq(tenantId)) + .and(T_USER.DISPLAY_NAME.startsWith(prefixStr)) + .fetch(); + if (result.isEmpty()) { + return null; + } + return result.stream().map(this::extract).collect(Collectors.toList()); + } + + @Override + public List listUsersByUsernames(String tenantId, Collection usernames) { + Result result = dslContext + .select(ALL_FIELDS) + .from(T_USER) + .where(T_USER.TENANT_ID.eq(tenantId)) + .and(T_USER.USERNAME.in(usernames)) + .fetch(); + if (result.isEmpty()) { + return null; + } + return result.stream().map(this::extract).collect(Collectors.toList()); + } + + @Override + public List listExistUserName(String tenantId, Collection usernames) { + Result result = dslContext + .select(ALL_FIELDS) + .from(T_USER) + .where(T_USER.TENANT_ID.eq(tenantId)) + .and(T_USER.USERNAME.in(usernames)) + .fetch(); + if (result.isEmpty()) { + return null; + } + return result.stream().map(record -> record.get(T_USER.USERNAME)).collect(Collectors.toList()); + } +} diff --git a/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/dao/notify/impl/EsbUserInfoDAOImpl.java b/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/dao/notify/impl/EsbUserInfoDAOImpl.java deleted file mode 100644 index 7f5db83781..0000000000 --- a/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/dao/notify/impl/EsbUserInfoDAOImpl.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Tencent is pleased to support the open source community by making BK-JOB蓝鲸智云作业平台 available. - * - * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. - * - * BK-JOB蓝鲸智云作业平台 is licensed under the MIT License. - * - * License for BK-JOB蓝鲸智云作业平台: - * -------------------------------------------------------------------- - * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated - * documentation files (the "Software"), to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and - * to permit persons to whom the Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all copies or substantial portions of - * the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO - * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF - * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - -package com.tencent.bk.job.manage.dao.notify.impl; - -import com.tencent.bk.job.manage.dao.notify.EsbUserInfoDAO; -import com.tencent.bk.job.manage.model.dto.notify.EsbUserInfoDTO; -import com.tencent.bk.job.manage.model.tables.EsbUserInfo; -import com.tencent.bk.job.manage.model.tables.records.EsbUserInfoRecord; -import lombok.val; -import org.jooq.Condition; -import org.jooq.DSLContext; -import org.jooq.Record1; -import org.jooq.Result; -import org.jooq.conf.ParamType; -import org.jooq.types.ULong; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.stereotype.Repository; -import org.springframework.util.CollectionUtils; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -@Repository -public class EsbUserInfoDAOImpl implements EsbUserInfoDAO { - - private final DSLContext dslContext; - private static final Logger logger = LoggerFactory.getLogger(EsbUserInfoDAOImpl.class); - private static final EsbUserInfo T_ESB_USER_INFO = EsbUserInfo.ESB_USER_INFO; - private static final EsbUserInfo defaultTable = T_ESB_USER_INFO; - - @Autowired - public EsbUserInfoDAOImpl(@Qualifier("job-manage-dsl-context") DSLContext dslContext) { - this.dslContext = dslContext; - } - - @Override - public int insertEsbUserInfo(EsbUserInfoDTO esbUserInfoDTO) { - val query = dslContext.insertInto(defaultTable, - defaultTable.ID, - defaultTable.USERNAME, - defaultTable.DISPLAY_NAME, - defaultTable.LOGO, - defaultTable.LAST_MODIFY_TIME - ).values( - esbUserInfoDTO.getId(), - esbUserInfoDTO.getUsername(), - esbUserInfoDTO.getDisplayName(), - esbUserInfoDTO.getLogo(), - ULong.valueOf(esbUserInfoDTO.getLastModifyTime()) - ); - val sql = query.getSQL(ParamType.INLINED); - try { - return query.execute(); - } catch (Exception e) { - logger.error(sql); - throw e; - } - } - - @Override - public int deleteEsbUserInfoById(Long id) { - return dslContext.deleteFrom(defaultTable).where( - defaultTable.ID.eq(id) - ).execute(); - } - - @Override - public List listEsbUserInfo() { - val records = dslContext.selectFrom(defaultTable).fetch(); - if (records.isEmpty()) { - return new ArrayList<>(); - } else { - return records.map(record -> new EsbUserInfoDTO( - record.getId(), - record.getUsername(), - record.getDisplayName(), - record.getLogo(), - record.getLastModifyTime().longValue() - )); - } - } - - @Override - public List listEsbUserInfo(String prefixStr, Long limit) { - List conditions = new ArrayList<>(); - conditions.add(defaultTable.USERNAME.startsWith(prefixStr)); - return listEsbUserInfoByConditions(dslContext, conditions, limit); - } - - @Override - public List listEsbUserInfo(Collection userNames) { - List conditions = new ArrayList<>(); - conditions.add(defaultTable.USERNAME.in(userNames)); - return listEsbUserInfoByConditions(dslContext, conditions, null); - } - - @Override - public List listExistUserName(Collection userNames) { - if (CollectionUtils.isEmpty(userNames)) { - return Collections.emptyList(); - } - List conditions = new ArrayList<>(); - conditions.add(defaultTable.USERNAME.in(userNames)); - val baseQuery = dslContext.select(defaultTable.USERNAME) - .from(defaultTable) - .where(conditions); - Result> records = baseQuery.fetch(); - return records.map(record -> record.get(defaultTable.USERNAME)); - } - - private List listEsbUserInfoByConditions(DSLContext dslContext, List conditions, - Long limit) { - val baseQuery = dslContext.selectFrom(defaultTable) - .where(conditions); - Result records; - if (null != limit && limit > 0) { - records = baseQuery.limit(limit).fetch(); - } else { - records = baseQuery.fetch(); - } - if (records.isEmpty()) { - return new ArrayList<>(); - } else { - return records.map(record -> new EsbUserInfoDTO( - record.getId(), - record.getUsername(), - record.getDisplayName(), - record.getLogo(), - record.getLastModifyTime().longValue() - )); - } - } -} diff --git a/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/model/db/CacheAppDO.java b/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/model/db/CacheAppDO.java index f4d242bd79..684584c987 100644 --- a/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/model/db/CacheAppDO.java +++ b/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/model/db/CacheAppDO.java @@ -65,6 +65,8 @@ public class CacheAppDO { */ private ApplicationAttrsDO attrs; + private String tenantId; + public static CacheAppDO fromApplicationDTO(ApplicationDTO application) { if (application == null) { return null; @@ -75,6 +77,7 @@ public static CacheAppDO fromApplicationDTO(ApplicationDTO application) { cacheAppDO.setScopeId(application.getScope().getId()); cacheAppDO.setName(application.getName()); cacheAppDO.setAttrs(application.getAttrs()); + cacheAppDO.setTenantId(application.getTenantId()); return cacheAppDO; } @@ -87,6 +90,7 @@ public static ApplicationDTO toApplicationDTO(CacheAppDO cacheAppDO) { application.setScope(new ResourceScope(cacheAppDO.getScopeType(), cacheAppDO.getScopeId())); application.setName(cacheAppDO.getName()); application.setAttrs(cacheAppDO.getAttrs()); + application.setTenantId(cacheAppDO.getTenantId()); return application; } } diff --git a/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/service/UserCacheService.java b/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/service/UserCacheService.java new file mode 100644 index 0000000000..223a4549be --- /dev/null +++ b/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/service/UserCacheService.java @@ -0,0 +1,26 @@ +package com.tencent.bk.job.manage.service; + +import com.tencent.bk.job.common.model.dto.BkUserDTO; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +/** + * 用户信息缓存服务 + */ +public interface UserCacheService { + void saveUser(BkUserDTO user); + + int deleteUser(String username); + + List listTenantUsers(String tenantId); + + List listUsersByDisplayNamePrefix(String tenantId, String prefixStr, Long limit); + + List listUsersByUsernames(String tenantId, Collection usernames); + + List listExistUserName(String tenantId, Collection usernames); + + void batchPatchUsers(Set deleteUsers, Set addUsers); +} diff --git a/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/service/impl/AppScopeMappingServiceImpl.java b/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/service/impl/AppCacheServiceImpl.java similarity index 69% rename from src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/service/impl/AppScopeMappingServiceImpl.java rename to src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/service/impl/AppCacheServiceImpl.java index 15137e845a..721590e384 100644 --- a/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/service/impl/AppScopeMappingServiceImpl.java +++ b/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/service/impl/AppCacheServiceImpl.java @@ -26,8 +26,10 @@ import com.tencent.bk.job.common.constant.ErrorCode; import com.tencent.bk.job.common.exception.NotFoundException; +import com.tencent.bk.job.common.model.BasicApp; +import com.tencent.bk.job.common.model.dto.ApplicationDTO; import com.tencent.bk.job.common.model.dto.ResourceScope; -import com.tencent.bk.job.manage.AbstractLocalCacheAppScopeMappingService; +import com.tencent.bk.job.manage.AbstractLocalCacheAppService; import com.tencent.bk.job.manage.GlobalAppScopeMappingService; import com.tencent.bk.job.manage.service.ApplicationService; import lombok.extern.slf4j.Slf4j; @@ -36,33 +38,42 @@ @Slf4j @Service -public class AppScopeMappingServiceImpl extends AbstractLocalCacheAppScopeMappingService { +public class AppCacheServiceImpl extends AbstractLocalCacheAppService { private final ApplicationService applicationService; @Autowired - public AppScopeMappingServiceImpl(ApplicationService applicationService) { + public AppCacheServiceImpl(ApplicationService applicationService) { this.applicationService = applicationService; GlobalAppScopeMappingService.register(this); } @Override - public Long queryAppByScope(ResourceScope resourceScope) throws NotFoundException { - Long appId = applicationService.getAppIdByScope(resourceScope); - if (appId == null) { + protected BasicApp queryAppByScope(ResourceScope resourceScope) throws NotFoundException { + ApplicationDTO app = applicationService.getAppByScope(resourceScope); + if (app == null) { log.error("App not exist, resourceScope: {}", resourceScope); throw new NotFoundException(ErrorCode.APP_NOT_EXIST); } - return appId; + return convertToBasicApp(app); + } + + private BasicApp convertToBasicApp(ApplicationDTO app) { + BasicApp basicApp = new BasicApp(); + basicApp.setId(app.getId()); + basicApp.setName(app.getName()); + basicApp.setScope(app.getScope()); + basicApp.setTenantId(app.getTenantId()); + return basicApp; } @Override - public ResourceScope queryScopeByAppId(Long appId) throws NotFoundException { - ResourceScope resourceScope = applicationService.getScopeByAppId(appId); - if (resourceScope == null) { + protected BasicApp queryAppByAppId(Long appId) throws NotFoundException { + ApplicationDTO app = applicationService.getAppByAppId(appId); + if (app == null) { log.error("App not exist, appId: {}", appId); throw new NotFoundException(ErrorCode.APP_NOT_EXIST); } - return resourceScope; + return convertToBasicApp(app); } } diff --git a/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/service/impl/BasicAppCache.java b/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/service/impl/BasicAppCache.java new file mode 100644 index 0000000000..0fd9f5d43a --- /dev/null +++ b/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/service/impl/BasicAppCache.java @@ -0,0 +1,36 @@ +package com.tencent.bk.job.manage.service.impl; + +import com.tencent.bk.job.common.model.dto.ApplicationDTO; +import com.tencent.bk.job.common.model.dto.ResourceScope; +import com.tencent.bk.job.manage.AbstractBasicAppCache; +import com.tencent.bk.job.common.model.BasicApp; +import com.tencent.bk.job.manage.service.ApplicationService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class BasicAppCache extends AbstractBasicAppCache { + + private final ApplicationService applicationService; + + + public BasicAppCache(ApplicationService applicationService) { + this.applicationService = applicationService; + } + + @Override + protected BasicApp loadAppToCache(ResourceScope resourceScope) { + ApplicationDTO app = applicationService.getAppByScope(resourceScope); + if (app == null) { + return null; + } + + BasicApp basicApp = new BasicApp(); + basicApp.setId(app.getId()); + basicApp.setName(app.getName()); + basicApp.setScope(app.getScope()); + basicApp.setTenantId(app.getTenantId()); + return basicApp; + } +} diff --git a/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/service/impl/UserCacheServiceImpl.java b/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/service/impl/UserCacheServiceImpl.java new file mode 100644 index 0000000000..7a13f3d7e8 --- /dev/null +++ b/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/service/impl/UserCacheServiceImpl.java @@ -0,0 +1,60 @@ +package com.tencent.bk.job.manage.service.impl; + +import com.tencent.bk.job.common.model.dto.BkUserDTO; +import com.tencent.bk.job.common.mysql.JobTransactional; +import com.tencent.bk.job.manage.dao.UserDAO; +import com.tencent.bk.job.manage.service.UserCacheService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +@Slf4j +@Service +public class UserCacheServiceImpl implements UserCacheService { + + private final UserDAO userDAO; + + public UserCacheServiceImpl(UserDAO userDAO) { + this.userDAO = userDAO; + } + + @Override + public void saveUser(BkUserDTO user) { + userDAO.saveUser(user); + } + + @Override + public int deleteUser(String username) { + return userDAO.deleteUser(username); + } + + @Override + public List listTenantUsers(String tenantId) { + return userDAO.listTenantUsers(tenantId); + } + + @Override + public List listUsersByDisplayNamePrefix(String tenantId, String prefixStr, Long limit) { + return userDAO.listUsersByDisplayNamePrefix(tenantId, prefixStr, limit); + } + + @Override + public List listUsersByUsernames(String tenantId, Collection usernames) { + return userDAO.listUsersByUsernames(tenantId, usernames); + } + + @Override + public List listExistUserName(String tenantId, Collection usernames) { + return userDAO.listExistUserName(tenantId, usernames); + } + + @Override + @JobTransactional(transactionManager = "jobManageTransactionManager") + public void batchPatchUsers(Set deleteUsers, Set addUsers) { + deleteUsers.forEach(user -> userDAO.deleteUser(user.getUsername())); + addUsers.forEach(userDAO::saveUser); + } +} diff --git a/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/service/impl/notify/NotifySendService.java b/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/service/impl/notify/NotifySendService.java index ad9b086574..3f64818cff 100644 --- a/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/service/impl/notify/NotifySendService.java +++ b/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/service/impl/notify/NotifySendService.java @@ -24,8 +24,8 @@ package com.tencent.bk.job.manage.service.impl.notify; -import com.tencent.bk.job.manage.dao.notify.EsbUserInfoDAO; import com.tencent.bk.job.manage.metrics.MetricsConstants; +import com.tencent.bk.job.manage.service.UserCacheService; import com.tencent.bk.job.manage.service.impl.WatchableSendMsgService; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Tag; @@ -47,15 +47,15 @@ public class NotifySendService { //发通知专用线程池 private final ThreadPoolExecutor notifySendExecutor; private final WatchableSendMsgService watchableSendMsgService; - private final EsbUserInfoDAO esbUserInfoDAO; + private final UserCacheService userCacheService; @Autowired public NotifySendService(WatchableSendMsgService watchableSendMsgService, - EsbUserInfoDAO esbUserInfoDAO, + UserCacheService userCacheService, @Qualifier("notifySendExecutor") ThreadPoolExecutor notifySendExecutor, MeterRegistry meterRegistry) { this.watchableSendMsgService = watchableSendMsgService; - this.esbUserInfoDAO = esbUserInfoDAO; + this.userCacheService = userCacheService; this.notifySendExecutor = notifySendExecutor; measureNotifySendExecutor(meterRegistry); } @@ -90,7 +90,7 @@ private SendNotifyTask buildSendTask(Long appId, .title(title) .content(content) .build(); - task.bindService(watchableSendMsgService, esbUserInfoDAO); + task.bindService(watchableSendMsgService, userCacheService); return task; } diff --git a/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/service/impl/notify/NotifyUserService.java b/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/service/impl/notify/NotifyUserService.java index e0a57915e6..ebe9411745 100644 --- a/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/service/impl/notify/NotifyUserService.java +++ b/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/service/impl/notify/NotifyUserService.java @@ -24,14 +24,14 @@ package com.tencent.bk.job.manage.service.impl.notify; +import com.tencent.bk.job.common.model.dto.BkUserDTO; import com.tencent.bk.job.manage.api.common.constants.notify.NotifyConsts; -import com.tencent.bk.job.manage.dao.notify.EsbUserInfoDAO; import com.tencent.bk.job.manage.dao.notify.NotifyBlackUserInfoDAO; -import com.tencent.bk.job.manage.model.dto.notify.EsbUserInfoDTO; import com.tencent.bk.job.manage.model.dto.notify.NotifyBlackUserInfoDTO; import com.tencent.bk.job.manage.model.web.request.notify.NotifyBlackUsersReq; import com.tencent.bk.job.manage.model.web.vo.notify.NotifyBlackUserInfoVO; import com.tencent.bk.job.manage.model.web.vo.notify.UserVO; +import com.tencent.bk.job.manage.service.UserCacheService; import lombok.extern.slf4j.Slf4j; import lombok.val; import org.apache.commons.lang3.StringUtils; @@ -49,13 +49,13 @@ public class NotifyUserService { private final NotifyBlackUserInfoDAO notifyBlackUserInfoDAO; - private final EsbUserInfoDAO esbUserInfoDAO; + private final UserCacheService userCacheService; @Autowired public NotifyUserService(NotifyBlackUserInfoDAO notifyBlackUserInfoDAO, - EsbUserInfoDAO esbUserInfoDAO) { + UserCacheService userCacheService) { this.notifyBlackUserInfoDAO = notifyBlackUserInfoDAO; - this.esbUserInfoDAO = esbUserInfoDAO; + this.userCacheService = userCacheService; } public List listNotifyBlackUsers(Integer start, Integer pageSize) { @@ -131,8 +131,8 @@ public List listUsers( if (null == limit || limit <= 0) { limit = -1L; } - List esbUserInfoDTOList = searchUserByPrefix(prefixStr); - List userVOList = esbUserInfoDTOList.stream().map(it -> + List userList = searchUserByPrefix(prefixStr); + List userVOList = userList.stream().map(it -> new UserVO(it.getUsername(), it.getDisplayName(), it.getLogo(), true) ).collect(Collectors.toList()); if (excludeBlackUsers) { @@ -141,7 +141,7 @@ public List listUsers( return calcFinalListByOffsetAndLimit(userVOList, offset, limit); } - private List searchUserByPrefix(String prefixStr) { + private List searchUserByPrefix(String prefixStr) { // 从数据库查 if (prefixStr.contains(NotifyConsts.SEPERATOR_COMMA)) { // 前端回显,传全量 @@ -149,9 +149,9 @@ private List searchUserByPrefix(String prefixStr) { while (userNames.contains("")) { userNames.remove(""); } - return esbUserInfoDAO.listEsbUserInfo(userNames); + return userCacheService.listUsersByUsernames(null, userNames); } else { - return esbUserInfoDAO.listEsbUserInfo(prefixStr, -1L); + return userCacheService.listUsersByDisplayNamePrefix(null, prefixStr, -1L); } } diff --git a/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/service/impl/notify/SendNotifyTask.java b/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/service/impl/notify/SendNotifyTask.java index 397deaa618..a7a1b95b3a 100644 --- a/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/service/impl/notify/SendNotifyTask.java +++ b/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/service/impl/notify/SendNotifyTask.java @@ -25,7 +25,7 @@ package com.tencent.bk.job.manage.service.impl.notify; import com.tencent.bk.job.common.util.ThreadUtils; -import com.tencent.bk.job.manage.dao.notify.EsbUserInfoDAO; +import com.tencent.bk.job.manage.service.UserCacheService; import com.tencent.bk.job.manage.service.impl.WatchableSendMsgService; import lombok.Builder; import lombok.extern.slf4j.Slf4j; @@ -45,7 +45,7 @@ public class SendNotifyTask implements Runnable { private final int NOTIFY_MAX_RETRY_COUNT = 1; private WatchableSendMsgService watchableSendMsgService; - private EsbUserInfoDAO esbUserInfoDAO; + private UserCacheService userCacheService; private final Long appId; private final long createTimeMillis; @@ -58,9 +58,9 @@ public class SendNotifyTask implements Runnable { private Set validReceivers; public void bindService(WatchableSendMsgService watchableSendMsgService, - EsbUserInfoDAO esbUserInfoDAO) { + UserCacheService userCacheService) { this.watchableSendMsgService = watchableSendMsgService; - this.esbUserInfoDAO = esbUserInfoDAO; + this.userCacheService = userCacheService; } @Override @@ -87,7 +87,8 @@ public void run() { } private void pickValidReceivers() { - List existUserNameList = esbUserInfoDAO.listExistUserName(receivers); + // TODO 需要加入租户 ID + List existUserNameList = userCacheService.listExistUserName(null, receivers); validReceivers = new HashSet<>(existUserNameList); } diff --git a/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/task/ScheduledTasks.java b/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/task/ScheduledTasks.java index 4bde99c310..e0f3e296a3 100644 --- a/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/task/ScheduledTasks.java +++ b/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/task/ScheduledTasks.java @@ -37,7 +37,7 @@ @EnableScheduling public class ScheduledTasks { - private final EsbUserInfoUpdateTask esbUserInfoUpdateTask; + private final UserSyncService userSyncService; private final SyncService syncService; private final UserUploadFileCleanTask userUploadFileCleanTask; private final ClearDeletedHostsTask clearDeletedHostsTask; @@ -45,11 +45,12 @@ public class ScheduledTasks { @Autowired public ScheduledTasks( - EsbUserInfoUpdateTask esbUserInfoUpdateTask, + UserSyncService userSyncService, SyncService syncService, UserUploadFileCleanTask userUploadFileCleanTask, - ClearDeletedHostsTask clearDeletedHostsTask, ApplicationCache applicationCache) { - this.esbUserInfoUpdateTask = esbUserInfoUpdateTask; + ClearDeletedHostsTask clearDeletedHostsTask, + ApplicationCache applicationCache) { + this.userSyncService = userSyncService; this.syncService = syncService; this.userUploadFileCleanTask = userUploadFileCleanTask; this.clearDeletedHostsTask = clearDeletedHostsTask; @@ -60,12 +61,12 @@ public ScheduledTasks( * 每间隔1h更新一次人员数据 */ @Scheduled(initialDelay = 2 * 1000, fixedDelay = 60 * 60 * 1000) - public void updateEsbUserInfo() { - log.info("updateEsbUserInfo"); + public void syncUser() { + log.info("syncUser"); try { - esbUserInfoUpdateTask.execute(); + userSyncService.execute(); } catch (Exception e) { - log.error("updateEsbUserInfo fail", e); + log.error("syncUser fail", e); } } diff --git a/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/task/EsbUserInfoUpdateTask.java b/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/task/UserSyncService.java similarity index 55% rename from src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/task/EsbUserInfoUpdateTask.java rename to src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/task/UserSyncService.java index 6d3ec60685..703d956f43 100644 --- a/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/task/EsbUserInfoUpdateTask.java +++ b/src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/task/UserSyncService.java @@ -25,67 +25,56 @@ package com.tencent.bk.job.manage.task; import com.tencent.bk.job.common.model.dto.BkUserDTO; -import com.tencent.bk.job.common.mysql.JobTransactional; +import com.tencent.bk.job.common.paas.model.OpenApiTenant; import com.tencent.bk.job.common.paas.user.UserMgrApiClient; import com.tencent.bk.job.common.redis.util.LockUtils; import com.tencent.bk.job.common.redis.util.RedisKeyHeartBeatThread; import com.tencent.bk.job.common.util.ip.IpUtils; -import com.tencent.bk.job.manage.dao.notify.EsbUserInfoDAO; -import com.tencent.bk.job.manage.model.dto.notify.EsbUserInfoDTO; +import com.tencent.bk.job.manage.service.UserCacheService; import lombok.extern.slf4j.Slf4j; -import lombok.val; -import lombok.var; +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import org.springframework.util.StopWatch; -import java.util.ArrayList; -import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; /** - * @Description - * @Date 2020/1/12 - * @Version 1.0 + * 用户同步服务 */ @Slf4j @Component -public class EsbUserInfoUpdateTask { +public class UserSyncService { private static final String REDIS_KEY_SYNC_USER_JOB_LOCK = "sync-user-job-lock"; private static final String machineIp = IpUtils.getFirstMachineIP(); - private static final Logger logger = LoggerFactory.getLogger(EsbUserInfoUpdateTask.class); static { - List keyList = Arrays.asList(REDIS_KEY_SYNC_USER_JOB_LOCK); - keyList.forEach(key -> { - try { - //进程重启首先尝试释放上次加上的锁避免死锁 - LockUtils.releaseDistributedLock(key, machineIp); - } catch (Throwable t) { - logger.info("Redis key:" + key + " does not need to be released, ignore"); - } - }); + try { + //进程重启首先尝试释放上次加上的锁避免死锁 + LockUtils.releaseDistributedLock(REDIS_KEY_SYNC_USER_JOB_LOCK, machineIp); + } catch (Throwable t) { + log.info("Redis key:" + REDIS_KEY_SYNC_USER_JOB_LOCK + " does not need to be released, ignore"); + } } private final String REDIS_KEY_SYNC_USER_JOB_RUNNING_MACHINE = "sync-user-job-running-machine"; private final RedisTemplate redisTemplate; - private UserMgrApiClient userMgrApiClient; - private final EsbUserInfoDAO esbUserInfoDAO; + private final UserMgrApiClient userMgrApiClient; + private final UserCacheService userCacheService; @Autowired - public EsbUserInfoUpdateTask(UserMgrApiClient userMgrApiClient, - EsbUserInfoDAO esbUserInfoDAO, - RedisTemplate redisTemplate) { + public UserSyncService(UserMgrApiClient userMgrApiClient, + UserCacheService userCacheService, + RedisTemplate redisTemplate) { this.userMgrApiClient = userMgrApiClient; - this.esbUserInfoDAO = esbUserInfoDAO; + this.userCacheService = userCacheService; this.redisTemplate = redisTemplate; } @@ -113,51 +102,58 @@ public boolean execute() { ); userSyncRedisKeyHeartBeatThread.setName("userSyncRedisKeyHeartBeatThread"); userSyncRedisKeyHeartBeatThread.start(); - logger.info("updateEsbUserInfo:beigin"); - StopWatch watch = new StopWatch("syncUser"); - watch.start("total"); + log.info("Begin sync all tenant users"); + boolean isAllSuccess = true; try { - // 1.接口数据拉取 - List userList = userMgrApiClient.getAllUserList(); - if (null == userList) { - userList = new ArrayList<>(); + List allTenants = userMgrApiClient.listAllTenant(); + if (CollectionUtils.isEmpty(allTenants)) { + log.info("Empty tenant list, skip sync"); + } else { + log.info("Sync user, tenantList: {}", + allTenants.stream().map(OpenApiTenant::getId).collect(Collectors.toList())); } - // 2.组装 - var remoteUserSet = userList.stream().map(it -> new EsbUserInfoDTO(it.getId(), it.getUsername(), - it.getDisplayName(), it.getLogo(), System.currentTimeMillis())).collect(Collectors.toSet()); - if (remoteUserSet.isEmpty()) { - logger.warn("updateEsbUserInfo: fail to fetch remote userInfo, return"); - return false; + for (OpenApiTenant tenant : allTenants) { + boolean isSuccess = syncUsersByTenant(tenant.getId()); + isAllSuccess = isAllSuccess && isSuccess; } - // 3.计算差异数据 - val localUserSet = new HashSet<>(esbUserInfoDAO.listEsbUserInfo()); - val clonedRemoteUserSet = new HashSet<>(remoteUserSet); - remoteUserSet.removeAll(localUserSet); - val insertSet = remoteUserSet; - logger.info("insertUserInfoSet=" + insertSet.stream() - .map(EsbUserInfoDTO::toString).collect(Collectors.joining(","))); - localUserSet.removeAll(clonedRemoteUserSet); - val deleteSet = localUserSet; - logger.info("deleteUserInfoSet=" + deleteSet.stream() - .map(EsbUserInfoDTO::toString).collect(Collectors.joining(","))); - - // 4.入库 - saveEsbUserInfos(deleteSet, insertSet); } catch (Throwable t) { log.error("FATAL: syncUser thread fail", t); } finally { userSyncRedisKeyHeartBeatThread.setRunFlag(false); - watch.stop(); - log.info("syncUser time consuming:" + watch.toString()); + log.info("Sync all tenant users done, result: {}", isAllSuccess); } return true; } - @JobTransactional(transactionManager = "jobManageTransactionManager") - public void saveEsbUserInfos(Set deleteSet, Set insertSet) { - deleteSet.forEach(esbUserInfoDTO -> esbUserInfoDAO.deleteEsbUserInfoById( - esbUserInfoDTO.getId())); - insertSet.forEach(esbUserInfoDTO -> esbUserInfoDAO.insertEsbUserInfo(esbUserInfoDTO)); + private boolean syncUsersByTenant(String tenantId) { + log.info("Sync user by tenant : {}", tenantId); + boolean isSuccess = true; + try { + // 1.获取租户下的所有用户列表 + List remoteUserList = userMgrApiClient.getAllUserList(tenantId); + Set remoteUserSet = CollectionUtils.isEmpty(remoteUserList) ? + Collections.emptySet(): new HashSet<>(remoteUserList); + + // 2.计算差异数据 + Set localUserSet = new HashSet<>(userCacheService.listTenantUsers(tenantId)); + Set addUsers = remoteUserSet.stream() + .filter(user -> !localUserSet.contains(user)).collect(Collectors.toSet()); + log.info("[{}] New users : {}", + tenantId, + addUsers.stream().map(BkUserDTO::getFullName).collect(Collectors.joining(","))); + Set deleteUsers = localUserSet.stream() + .filter(user -> !remoteUserSet.contains(user)).collect(Collectors.toSet()); + log.info("[{}] Delete users : {}", + tenantId, + deleteUsers.stream().map(BkUserDTO::getFullName).collect(Collectors.joining(","))); + + // 3.保存 + userCacheService.batchPatchUsers(deleteUsers, addUsers); + } catch (Throwable t) { + log.error("Sync user fail", t); + isSuccess = false; + } + return isSuccess; } } diff --git a/src/backend/upgrader/src/main/java/com/tencent/bk/job/upgrader/iam/ApiClientUtils.java b/src/backend/upgrader/src/main/java/com/tencent/bk/job/upgrader/iam/ApiClientUtils.java index 74ac3cd4b6..87a49b3a76 100644 --- a/src/backend/upgrader/src/main/java/com/tencent/bk/job/upgrader/iam/ApiClientUtils.java +++ b/src/backend/upgrader/src/main/java/com/tencent/bk/job/upgrader/iam/ApiClientUtils.java @@ -27,6 +27,7 @@ import com.tencent.bk.job.common.esb.config.AppProperties; import com.tencent.bk.job.common.esb.config.EsbProperties; import com.tencent.bk.job.common.iam.client.EsbIamClient; +import com.tencent.bk.job.common.tenant.TenantEnvService; import com.tencent.bk.job.upgrader.task.param.ParamNameConsts; import java.util.Properties; @@ -45,8 +46,16 @@ public static EsbIamClient buildEsbIamClient(Properties properties) { (String) properties.get(ParamNameConsts.CONFIG_PROPERTY_APP_CODE), (String) properties.get(ParamNameConsts.CONFIG_PROPERTY_APP_SECRET) ), - esbProperties + esbProperties, + new NonTenantEnvService() ); } + private static class NonTenantEnvService implements TenantEnvService { + @Override + public boolean isTenantEnabled() { + return false; + } + } + } diff --git a/support-files/kubernetes/charts/bk-job/VALUES_LOG.md b/support-files/kubernetes/charts/bk-job/VALUES_LOG.md index bbcb2462bc..c377a8b7cd 100644 --- a/support-files/kubernetes/charts/bk-job/VALUES_LOG.md +++ b/support-files/kubernetes/charts/bk-job/VALUES_LOG.md @@ -1,4 +1,21 @@ # chart values 更新日志 +## 0.9.0 +1. 新增 bk-login/bk-user蓝鲸网关配置 +```yaml +# 蓝鲸登录 API Gateway url +bkLoginApiGatewayUrl: "http://bkapi.example.com/api/bk-login" +# 蓝鲸用户管理 API Gateway url +bkUserApiGatewayUrl: "http://bkapi.example.com/api/bk-user" +``` + +2. 新增租户配置 +```yaml +# 多租户配置 +tenant: + # 是否启用多租户 + enabled: false +``` + ## 0.8.2 1. AI小鲸支持配置使用的大模型 ```yaml diff --git a/support-files/kubernetes/charts/bk-job/templates/configmap-common.yaml b/support-files/kubernetes/charts/bk-job/templates/configmap-common.yaml index 8f6a8f32f0..ce9d12f56c 100644 --- a/support-files/kubernetes/charts/bk-job/templates/configmap-common.yaml +++ b/support-files/kubernetes/charts/bk-job/templates/configmap-common.yaml @@ -50,6 +50,10 @@ data: url: {{ .Values.bkCmdbApiGatewayUrl }} bkApiGateway: url: {{ .Values.bkApiGatewayConfig.url }} + bkUser: + url: {{ .Values.bkUserApiGatewayUrl }} + bkLogin: + url: {{ .Values.bkLoginApiGatewayUrl }} gse: enabled: {{ .Values.gse.enabled }} cache: @@ -180,3 +184,5 @@ data: enabled: {{ .Values.jvmDiagnosticFile.clearByLastModifyTime.enabled }} # JVM诊断文件保留的小时数,默认168小时(7天) keep-hours: {{ .Values.jvmDiagnosticFile.clearByLastModifyTime.keepHours }} + tenant: + enabled: {{ .Values.tenant.enabled }} diff --git a/support-files/kubernetes/charts/bk-job/values.yaml b/support-files/kubernetes/charts/bk-job/values.yaml index 6a6c1aa324..177decd633 100644 --- a/support-files/kubernetes/charts/bk-job/values.yaml +++ b/support-files/kubernetes/charts/bk-job/values.yaml @@ -433,6 +433,10 @@ bkNoticeApiGatewayUrl: "http://bkapi.example.com/api/bk-notice" bkCmdbApiGatewayUrl: "http://bkapi.example.com/api/cmdb" # 蓝鲸 AIDev API Gateway url bkAIDevApiGatewayUrl: "http://bkapi.example.com/api/aidev" +# 蓝鲸登录 API Gateway url +bkLoginApiGatewayUrl: "http://bkapi.example.com/api/bk-login" +# 蓝鲸用户管理 API Gateway url +bkUserApiGatewayUrl: "http://bkapi.example.com/api/bk-user" # 调用蓝鲸AIDev API使用的appCode,如果AIDev API与当前Job在同一个环境则无需配置,直接使用appCode的值 bkAIDevAppCode: "" # 调用蓝鲸AIDev API使用的appSecret,如果AIDev API与当前Job在同一个环境则无需配置,直接使用appSecret的值 @@ -1808,3 +1812,6 @@ assembleConfig: # pod删除时等待优雅关闭的最大时间,单位为秒(超出后强制删除) podTerminationGracePeriodSeconds: 60 +# 多租户配置 +tenant: + enabled: false diff --git a/support-files/sql/job-manage/0032_job_manage_20250103-1000_V3.12.0_mysql.sql b/support-files/sql/job-manage/0032_job_manage_20250103-1000_V3.12.0_mysql.sql new file mode 100644 index 0000000000..e28aefb2d5 --- /dev/null +++ b/support-files/sql/job-manage/0032_job_manage_20250103-1000_V3.12.0_mysql.sql @@ -0,0 +1,48 @@ +USE job_manage; + +SET NAMES utf8mb4; + +DROP PROCEDURE IF EXISTS job_schema_update; + +DELIMITER + +CREATE PROCEDURE job_schema_update() +BEGIN + + DECLARE db VARCHAR(100); + SET AUTOCOMMIT = 0; + SELECT DATABASE() INTO db; + + IF NOT EXISTS(SELECT 1 + FROM information_schema.columns + WHERE TABLE_SCHEMA = db + AND TABLE_NAME = 'application' + AND COLUMN_NAME = 'tenant_id') THEN + ALTER TABLE application ADD COLUMN tenant_id VARCHAR(32) NOT NULL DEFAULT 'default'; + END IF; + + IF NOT EXISTS(SELECT 1 + FROM information_schema.statistics + WHERE TABLE_SCHEMA = db + AND TABLE_NAME = 'application' + AND INDEX_NAME = 'idx_tenant_id') THEN + ALTER TABLE application ADD INDEX idx_tenant_id(`tenant_id`); + END IF; + + CREATE TABLE IF NOT EXISTS `user` ( + `username` varchar(64) NOT NULL, + `tenant_id` varchar(32) NOT NULL, + `display_name` varchar(128) DEFAULT NULL, + `row_update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `last_modify_time` bigint(20) unsigned DEFAULT NULL, + PRIMARY KEY (`username`) USING BTREE, + KEY `idx_tenant_id` (`tenant_id`) USING BTREE, + KEY `idx_display_name` (`display_name`) USING BTREE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +COMMIT; +END +DELIMITER ; +CALL job_schema_update(); + +DROP PROCEDURE IF EXISTS job_schema_update;