1. 新增 api 绑定客户凭证进行权限校验

2. 去除 api 定义的缓存策略
3. 新增短信渠道
4. 新增用户信息模糊查询
5. 修复全局的单元测试
This commit is contained in:
chenbowen
2025-12-12 10:03:10 +08:00
parent 99645c5ac8
commit cae0b9e4af
66 changed files with 1323 additions and 211 deletions

View File

@@ -11,6 +11,7 @@ import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiCreden
import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiDefinitionAggregate;
import com.zt.plat.module.databus.framework.integration.gateway.model.ApiGatewayResponse;
import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext;
import com.zt.plat.module.databus.framework.integration.gateway.security.GatewayJwtResolver;
import com.zt.plat.module.databus.framework.integration.gateway.security.GatewaySecurityFilter;
import com.zt.plat.module.databus.service.gateway.ApiDefinitionService;
import lombok.RequiredArgsConstructor;
@@ -171,6 +172,7 @@ public class ApiGatewayExecutionService {
if (reqVO.getHeaders() != null) {
requestHeaders.putAll(reqVO.getHeaders());
}
normalizeJwtHeaders(requestHeaders, reqVO.getQueryParams());
requestHeaders.putIfAbsent(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
builder.setHeader(HEADER_REQUEST_HEADERS, requestHeaders);
requestHeaders.forEach((key, value) -> {
@@ -304,4 +306,37 @@ public class ApiGatewayExecutionService {
builder.queryParam(key, value);
}
private void normalizeJwtHeaders(Map<String, Object> headers, Map<String, Object> queryParams) {
String token = GatewayJwtResolver.resolveJwtToken(headers, queryParams, objectMapper);
if (!StringUtils.hasText(token)) {
return;
}
ensureHeaderValue(headers, GatewayJwtResolver.HEADER_ZT_AUTH_TOKEN, token);
ensureHeaderValue(headers, HttpHeaders.AUTHORIZATION, "Bearer " + token);
}
private void ensureHeaderValue(Map<String, Object> headers, String headerName, String value) {
if (!StringUtils.hasText(headerName) || value == null) {
return;
}
String existingKey = findHeaderKey(headers, headerName);
if (existingKey != null) {
headers.put(existingKey, value);
} else {
headers.put(headerName, value);
}
}
private String findHeaderKey(Map<String, Object> headers, String headerName) {
if (headers == null || !StringUtils.hasText(headerName)) {
return null;
}
for (String key : headers.keySet()) {
if (headerName.equalsIgnoreCase(key)) {
return key;
}
}
return null;
}
}

View File

@@ -4,8 +4,6 @@ import cn.hutool.core.collection.CollUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.common.util.object.BeanUtils;
@@ -27,20 +25,16 @@ import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiTransf
import com.zt.plat.module.databus.service.gateway.ApiDefinitionService;
import com.zt.plat.module.databus.service.gateway.ApiVersionService;
import com.zt.plat.module.databus.service.gateway.ApiVersionSnapshotContextHolder;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.*;
@@ -50,8 +44,6 @@ import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErro
@RequiredArgsConstructor
public class ApiDefinitionServiceImpl implements ApiDefinitionService {
private static final String REDIS_CACHE_PREFIX = "databus:api:def:";
private final ApiDefinitionMapper apiDefinitionMapper;
private final ApiStepMapper apiStepMapper;
private final ApiTransformMapper apiTransformMapper;
@@ -60,19 +52,8 @@ public class ApiDefinitionServiceImpl implements ApiDefinitionService {
private final ApiDefinitionCredentialMapper apiDefinitionCredentialMapper;
private final ApiClientCredentialMapper apiClientCredentialMapper;
private final ObjectMapper objectMapper;
private final StringRedisTemplate stringRedisTemplate;
private final ObjectProvider<ApiVersionService> apiVersionServiceProvider;
private LoadingCache<String, Optional<ApiDefinitionAggregate>> definitionCache;
@PostConstruct
public void initCache() {
definitionCache = Caffeine.newBuilder()
.maximumSize(512)
.expireAfterWrite(Duration.ofMinutes(5))
.build(this::loadAggregateSync);
}
@Override
public List<ApiDefinitionAggregate> loadActiveDefinitions() {
List<ApiDefinitionDO> definitions = apiDefinitionMapper.selectActiveDefinitions(Collections.singletonList(ApiStatusEnum.ONLINE.getStatus()));
@@ -88,12 +69,9 @@ public class ApiDefinitionServiceImpl implements ApiDefinitionService {
@Override
public Optional<ApiDefinitionAggregate> findByCodeAndVersion(String apiCode, String version) {
String cacheKey = buildCacheKey(apiCode, version);
try {
return definitionCache.get(cacheKey);
} catch (RuntimeException ex) {
throw ServiceExceptionUtil.exception(API_DEFINITION_NOT_FOUND);
}
return apiDefinitionMapper.selectByCodeAndVersion(apiCode, version)
.filter(definition -> ApiStatusEnum.isOnline(definition.getStatus()))
.map(this::buildAggregate);
}
@Override
@@ -104,16 +82,12 @@ public class ApiDefinitionServiceImpl implements ApiDefinitionService {
@Override
public Optional<ApiDefinitionAggregate> refresh(String apiCode, String version) {
String cacheKey = buildCacheKey(apiCode, version);
definitionCache.invalidate(cacheKey);
deleteRedis(cacheKey);
return findByCodeAndVersion(apiCode, version);
}
@Override
public void refreshAllCache() {
definitionCache.invalidateAll();
clearRedisCacheForTenant(TenantContextHolder.getTenantId());
// 缓存已移除,此处留空以保持接口兼容
}
@Override
@@ -165,14 +139,12 @@ public class ApiDefinitionServiceImpl implements ApiDefinitionService {
ApiDefinitionDO updateObj = buildDefinitionDO(reqVO, existing);
apiDefinitionMapper.updateById(updateObj);
invalidateCache(existing.getTenantId(), existing.getApiCode(), existing.getVersion());
apiTransformMapper.deleteByApiId(existing.getId());
apiStepMapper.deleteByApiId(existing.getId());
apiDefinitionCredentialMapper.deleteByApiId(existing.getId());
persistApiLevelTransforms(existing.getId(), reqVO.getApiLevelTransforms());
persistSteps(existing.getId(), reqVO.getSteps());
persistCredentialBindings(existing.getId(), reqVO.getCredentialIds());
invalidateCache(updateObj.getTenantId(), updateObj.getApiCode(), updateObj.getVersion());
} finally {
if (skipSnapshot) {
ApiVersionSnapshotContextHolder.clear();
@@ -192,78 +164,12 @@ public class ApiDefinitionServiceImpl implements ApiDefinitionService {
@Transactional(rollbackFor = Exception.class)
public void delete(Long id) {
ApiDefinitionDO existing = ensureExists(id);
invalidateCache(existing.getTenantId(), existing.getApiCode(), existing.getVersion());
apiTransformMapper.deleteByApiId(id);
apiStepMapper.deleteByApiId(id);
apiDefinitionCredentialMapper.deleteByApiId(id);
apiDefinitionMapper.deleteById(id);
}
private Optional<ApiDefinitionAggregate> loadAggregateSync(String cacheKey) {
Optional<ApiDefinitionAggregate> cached = loadFromRedis(cacheKey);
if (cached.isPresent()) {
return cached;
}
String[] parts = cacheKey.split(":");
String apiCode = parts[1];
String version = parts[2];
Optional<ApiDefinitionAggregate> aggregate = apiDefinitionMapper.selectByCodeAndVersion(apiCode, version)
.filter(definition -> ApiStatusEnum.isOnline(definition.getStatus()))
.map(this::buildAggregate);
aggregate.ifPresent(value -> persistToRedis(cacheKey, value));
return aggregate;
}
private Optional<ApiDefinitionAggregate> loadFromRedis(String cacheKey) {
try {
String json = stringRedisTemplate.opsForValue().get(REDIS_CACHE_PREFIX + cacheKey);
if (!StringUtils.hasText(json)) {
return Optional.empty();
}
ApiDefinitionAggregate aggregate = objectMapper.readValue(json, ApiDefinitionAggregate.class);
return Optional.of(aggregate);
} catch (JsonProcessingException | DataAccessException ex) {
log.warn("反序列化 Redis 中 key {} 的 API 定义聚合失败", cacheKey, ex);
return Optional.empty();
}
}
private void persistToRedis(String cacheKey, ApiDefinitionAggregate aggregate) {
try {
String json = objectMapper.writeValueAsString(aggregate);
stringRedisTemplate.opsForValue().set(REDIS_CACHE_PREFIX + cacheKey, json, 5, TimeUnit.MINUTES);
} catch (JsonProcessingException | DataAccessException ex) {
log.warn("将 API 定义聚合写入 Redis key {} 失败", cacheKey, ex);
}
}
private void deleteRedis(String cacheKey) {
try {
stringRedisTemplate.delete(REDIS_CACHE_PREFIX + cacheKey);
} catch (DataAccessException ex) {
log.warn("删除 Redis 中 key {} 的 API 定义聚合失败", cacheKey, ex);
}
}
private void clearRedisCacheForTenant(Long tenantId) {
String tenantPart = tenantId == null ? "global" : tenantId.toString();
String pattern = REDIS_CACHE_PREFIX + tenantPart + ":*";
try {
Set<String> keys = stringRedisTemplate.keys(pattern);
if (CollectionUtils.isEmpty(keys)) {
return;
}
stringRedisTemplate.delete(keys);
} catch (DataAccessException ex) {
log.warn("批量删除 Redis 中匹配 {} 的 API 定义聚合失败", pattern, ex);
}
}
private String buildCacheKey(String apiCode, String version) {
Long tenantId = TenantContextHolder.getTenantId();
return buildCacheKeyForTenant(tenantId, apiCode, version);
}
/**
* 构建包含步骤、变换、策略等元数据的聚合对象,供缓存与运行时直接使用。
*/
@@ -526,20 +432,6 @@ public class ApiDefinitionServiceImpl implements ApiDefinitionService {
return TenantContextHolder.getTenantId();
}
private void invalidateCache(Long tenantId, String apiCode, String version) {
if (!StringUtils.hasText(apiCode) || !StringUtils.hasText(version)) {
return;
}
String cacheKey = buildCacheKeyForTenant(tenantId, apiCode, version);
definitionCache.invalidate(cacheKey);
deleteRedis(cacheKey);
}
private String buildCacheKeyForTenant(Long tenantId, String apiCode, String version) {
String tenantPart = tenantId == null ? "global" : tenantId.toString();
return tenantPart + ":" + apiCode.toLowerCase(Locale.ROOT) + ":" + version;
}
/**
* 先删除旧绑定,再对去重后的 credentialIds 批量插入,避免唯一约束冲突。
*/