feat(品牌服务): 内部接口按 uid 供用户代理提取链接;代理检测改真实提取+转发探测
- 新增 /api/internal/user-proxy(X-Internal-Token 自校验,18080 公网可达必须校验), 供主机 A 品牌检测服务(15126)按 uid 取用户代理提取链接,未配置返回空串 - 代理检测修复"假通过":旧实现把提取链接直接当静态代理交给 HttpClientPool, 链接无显式端口解析失败静默回退直连,检测到的是自家站点直连响应(40 个用户 3-39ms 全 301 假通过);现在先真实提取一次(余额不足/解析失败如实报), 再经提取到的代理请求自家域名,兼容旧静态代理地址
This commit is contained in:
+76
@@ -0,0 +1,76 @@
|
||||
package com.nanri.aiimage.modules.usersecret.controller;
|
||||
|
||||
import com.nanri.aiimage.common.api.ApiResponse;
|
||||
import com.nanri.aiimage.common.exception.BusinessException;
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.modules.usersecret.service.UserApiSecretService;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
class InternalUserProxyControllerTest {
|
||||
|
||||
private final UserApiSecretService userApiSecretService = mock(UserApiSecretService.class);
|
||||
private final AdminAuthSupport adminAuthSupport = mock(AdminAuthSupport.class);
|
||||
|
||||
private InternalUserProxyController newController() {
|
||||
return new InternalUserProxyController(userApiSecretService, adminAuthSupport);
|
||||
}
|
||||
|
||||
private HttpServletRequest request() {
|
||||
return mock(HttpServletRequest.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsRequestWithoutTrustedInternalTokenAndSkipsLookup() {
|
||||
when(adminAuthSupport.isTrustedInternalToken(any())).thenReturn(false);
|
||||
|
||||
assertThatThrownBy(() -> newController().userProxy(request(), 1098L))
|
||||
.isInstanceOf(BusinessException.class)
|
||||
.hasMessageContaining("未授权");
|
||||
verify(userApiSecretService, never()).findPlainValue(anyLong(), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void returnsProxyUrlForConfiguredUser() {
|
||||
when(adminAuthSupport.isTrustedInternalToken(any())).thenReturn(true);
|
||||
when(userApiSecretService.findPlainValue(1098L, "proxy")).thenReturn("http://p.example/x");
|
||||
|
||||
ApiResponse<Map<String, Object>> resp = newController().userProxy(request(), 1098L);
|
||||
|
||||
assertThat(resp.isSuccess()).isTrue();
|
||||
assertThat(resp.getData()).containsEntry("userId", 1098L);
|
||||
assertThat(resp.getData()).containsEntry("proxyUrl", "http://p.example/x");
|
||||
}
|
||||
|
||||
@Test
|
||||
void returnsEmptyUrlForUnconfiguredUser() {
|
||||
when(adminAuthSupport.isTrustedInternalToken(any())).thenReturn(true);
|
||||
when(userApiSecretService.findPlainValue(1098L, "proxy")).thenReturn("");
|
||||
|
||||
ApiResponse<Map<String, Object>> resp = newController().userProxy(request(), 1098L);
|
||||
|
||||
assertThat(resp.getData()).containsEntry("proxyUrl", "");
|
||||
}
|
||||
|
||||
@Test
|
||||
void skipsLookupForMissingOrInvalidUserId() {
|
||||
when(adminAuthSupport.isTrustedInternalToken(any())).thenReturn(true);
|
||||
InternalUserProxyController controller = newController();
|
||||
|
||||
assertThat(controller.userProxy(request(), null).getData()).containsEntry("proxyUrl", "");
|
||||
assertThat(controller.userProxy(request(), 0L).getData()).containsEntry("proxyUrl", "");
|
||||
verify(userApiSecretService, never()).findPlainValue(any(), anyString());
|
||||
}
|
||||
}
|
||||
+52
@@ -136,4 +136,56 @@ class UserApiSecretCheckServiceTest {
|
||||
assertThat(outcome.status()).isEqualTo(UserApiSecretCheckService.STATUS_ERROR);
|
||||
assertThat(outcome.code()).isEqualTo(UserApiSecretCheckService.CODE_SERVER_ERROR);
|
||||
}
|
||||
|
||||
// ===== 代理配置探测(提取链接语义;2026-09-13 修复「直连自家站点假通过」)=====
|
||||
|
||||
@Test
|
||||
void looksLikeStaticProxyOnlyForHostPortWithoutPathOrQuery() {
|
||||
assertThat(UserApiSecretCheckService.looksLikeStaticProxy("http://1.2.3.4:8080")).isTrue();
|
||||
assertThat(UserApiSecretCheckService.looksLikeStaticProxy("http://u:p@1.2.3.4:8080")).isTrue();
|
||||
assertThat(UserApiSecretCheckService.looksLikeStaticProxy("https://1.2.3.4:8080/")).isTrue();
|
||||
// 提取链接(带 path/query 或无显式端口)不能按静态代理直连探测
|
||||
assertThat(UserApiSecretCheckService.looksLikeStaticProxy(
|
||||
"https://api.jikip.com/ip-get?num=1&mode=2&key=xx")).isFalse();
|
||||
assertThat(UserApiSecretCheckService.looksLikeStaticProxy("https://api.jikip.com")).isFalse();
|
||||
assertThat(UserApiSecretCheckService.looksLikeStaticProxy("")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void extractProxyUrlPrefersJsonWithCredentials() {
|
||||
String body = "{\"data\":{\"list\":[{\"ip\":\"218.95.39.19\",\"port\":\"15902\","
|
||||
+ "\"account\":\"acc1\",\"password\":\"pw1\"}]},\"code\":0,\"message\":\"\"}";
|
||||
|
||||
assertThat(service.extractProxyUrlFromBody(body))
|
||||
.isEqualTo("http://acc1:pw1@218.95.39.19:15902");
|
||||
}
|
||||
|
||||
@Test
|
||||
void extractProxyUrlWithoutCredentialsWhenWhitelistMode() {
|
||||
String body = "{\"data\":{\"list\":[{\"ip\":\"1.2.3.4\",\"port\":8080}]}}";
|
||||
|
||||
assertThat(service.extractProxyUrlFromBody(body)).isEqualTo("http://1.2.3.4:8080");
|
||||
}
|
||||
|
||||
@Test
|
||||
void extractProxyUrlFallsBackToPlainText() {
|
||||
assertThat(service.extractProxyUrlFromBody("1.2.3.4:8080")).isEqualTo("http://1.2.3.4:8080");
|
||||
}
|
||||
|
||||
@Test
|
||||
void extractProxyUrlReturnsNullWhenNoIpPresent() {
|
||||
assertThat(service.extractProxyUrlFromBody("{\"code\":0,\"data\":null}")).isNull();
|
||||
assertThat(service.extractProxyUrlFromBody("")).isNull();
|
||||
assertThat(service.extractProxyUrlFromBody(null)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void insufficientBalanceDetectedByMessageOrCode() {
|
||||
assertThat(service.isInsufficientBalance(
|
||||
"{\"code\":-1,\"data\":null,\"status\":200,\"message\":\"余额不足\"}")).isTrue();
|
||||
assertThat(service.isInsufficientBalance("余额不足")).isTrue();
|
||||
assertThat(service.isInsufficientBalance(
|
||||
"{\"data\":{\"list\":[{\"ip\":\"1.2.3.4\",\"port\":8080}]},\"code\":0}")).isFalse();
|
||||
assertThat(service.isInsufficientBalance("")).isFalse();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user