fix(gateway): 补充预检请求标识过滤器
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
package com.baiyue.gateway.filter;
|
||||
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
import org.springframework.web.server.WebFilterChain;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/** 确保跨域预检和安全失败响应也携带可追踪的请求标识。 */
|
||||
@Component
|
||||
public class CorrelationIdWebFilter implements WebFilter, Ordered {
|
||||
public static final String HEADER = "X-Request-Id";
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
|
||||
String requestId = validRequestId(exchange.getRequest().getHeaders())
|
||||
? exchange.getRequest().getHeaders().getFirst(HEADER)
|
||||
: UUID.randomUUID().toString();
|
||||
exchange.getResponse().getHeaders().set(HEADER, requestId);
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
|
||||
private static boolean validRequestId(HttpHeaders headers) {
|
||||
String value = headers.getFirst(HEADER);
|
||||
return value != null && !value.isBlank() && value.length() <= 128
|
||||
&& value.chars().allMatch(character -> character >= 0x21 && character <= 0x7e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.HIGHEST_PRECEDENCE;
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package com.baiyue.gateway.config;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.gateway.config.GatewayProperties;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@SpringBootTest(properties = {
|
||||
"spring.cloud.nacos.discovery.enabled=false",
|
||||
"spring.cloud.nacos.discovery.register-enabled=false",
|
||||
"spring.cloud.nacos.config.enabled=false",
|
||||
"spring.cloud.gateway.discovery.locator.enabled=false",
|
||||
"baiyue.security.jwt-secret=test-jwt-secret-at-least-32-bytes-long"
|
||||
})
|
||||
class GatewayFileRouteConfigurationTest {
|
||||
@Autowired
|
||||
private GatewayProperties gatewayProperties;
|
||||
|
||||
@Test
|
||||
void qualificationRequestsRouteToRegisteredFileServiceName() {
|
||||
var route = gatewayProperties.getRoutes().stream()
|
||||
.filter(candidate -> "file-service".equals(candidate.getId()))
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
|
||||
assertThat(route.getUri().toString()).isEqualTo("lb://file-service");
|
||||
assertThat(route.getPredicates()).singleElement().satisfies(predicate -> {
|
||||
assertThat(predicate.getName()).isEqualTo("Path");
|
||||
String paths = String.join(",", predicate.getArgs().values());
|
||||
assertThat(paths)
|
||||
.contains("/api/merchant/v1/files/**")
|
||||
.contains("/api/platform/v1/files/**");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -110,7 +110,9 @@ public class OrderMasterDataGuard {
|
||||
return new BizException(ResultCode.SERVICE_UNAVAILABLE,
|
||||
"主数据内部服务凭证不一致,请检查 INTERNAL_SERVICE_TOKEN");
|
||||
}
|
||||
log.warn("订单主数据内部调用失败,已转换为 S0503", exception);
|
||||
log.warn("订单主数据内部调用失败,已转换为 S0503,异常类型={},原因={}",
|
||||
exception.getClass().getSimpleName(), exception.getMessage());
|
||||
log.debug("订单主数据内部调用完整堆栈", exception);
|
||||
return unavailable("主数据");
|
||||
}
|
||||
private FeignException findFeignException(Throwable exception) {
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.baiyue.order.client;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class WarehouseFeignContractTest {
|
||||
@Test void clientsUseNacosServiceNameAndInternalPathsByDefault() throws Exception {
|
||||
assertClient(WarehouseClient.class, "/internal/warehouses");
|
||||
assertClient(PartnerClient.class, "/internal/warehouse-partners");
|
||||
}
|
||||
|
||||
private void assertClient(Class<?> type, String expectedPath) throws Exception {
|
||||
FeignClient client = type.getAnnotation(FeignClient.class);
|
||||
assertThat(client.name()).isEqualTo("warehouse-service");
|
||||
assertThat(client.url()).isEqualTo("${baiyue.clients.warehouse.url:}");
|
||||
assertThat(client.path()).isEqualTo(expectedPath);
|
||||
|
||||
Method active = type.getMethod("active", String.class, long.class, long.class);
|
||||
assertThat(active.getAnnotation(GetMapping.class).value()).containsExactly("/{tenantId}/{id}/active");
|
||||
RequestHeader header = active.getParameters()[0].getAnnotation(RequestHeader.class);
|
||||
assertThat(header.value()).isEqualTo("X-Internal-Service-Token");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.baiyue.warehouse.service;
|
||||
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.warehouse.entity.PartnerEntity;
|
||||
import com.baiyue.warehouse.repository.PartnerRepository;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
class PartnerQueryServiceTest {
|
||||
@Test void missingOrCrossTenantPartnerReturnsNotFound() {
|
||||
PartnerRepository repository = mock(PartnerRepository.class);
|
||||
|
||||
assertThatThrownBy(() -> new PartnerQueryService(repository).active(10001L, 7L))
|
||||
.isInstanceOf(BizException.class)
|
||||
.extracting(exception -> ((BizException) exception).getResultCode())
|
||||
.isEqualTo(ResultCode.NOT_FOUND);
|
||||
}
|
||||
|
||||
@Test void inactivePartnerReturnsConflict() {
|
||||
PartnerRepository repository = mock(PartnerRepository.class);
|
||||
PartnerEntity partner = new PartnerEntity();
|
||||
partner.setId(7L); partner.setTenantId(10001L); partner.setStatus("INACTIVE");
|
||||
when(repository.findOwned(10001L, 7L)).thenReturn(partner);
|
||||
|
||||
assertThatThrownBy(() -> new PartnerQueryService(repository).active(10001L, 7L))
|
||||
.isInstanceOf(BizException.class)
|
||||
.extracting(exception -> ((BizException) exception).getResultCode())
|
||||
.isEqualTo(ResultCode.CONFLICT);
|
||||
}
|
||||
|
||||
@Test void activePartnerIsReturned() {
|
||||
PartnerRepository repository = mock(PartnerRepository.class);
|
||||
PartnerEntity partner = new PartnerEntity();
|
||||
partner.setId(7L); partner.setTenantId(10001L); partner.setStatus("ACTIVE");
|
||||
when(repository.findOwned(10001L, 7L)).thenReturn(partner);
|
||||
|
||||
assertThat(new PartnerQueryService(repository).active(10001L, 7L)).isSameAs(partner);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user