diff --git a/examples/fit-example/07-http-client-proxy/plugin-http-client/src/main/java/modelengine/fit/example/client/TestAuthClient.java b/examples/fit-example/07-http-client-proxy/plugin-http-client/src/main/java/modelengine/fit/example/client/TestAuthClient.java index 61df588a4..f02f6aa7c 100644 --- a/examples/fit-example/07-http-client-proxy/plugin-http-client/src/main/java/modelengine/fit/example/client/TestAuthClient.java +++ b/examples/fit-example/07-http-client-proxy/plugin-http-client/src/main/java/modelengine/fit/example/client/TestAuthClient.java @@ -27,86 +27,105 @@ @HttpProxy @RequestAddress(protocol = "http", host = "localhost", port = "8080") @RequestMapping(path = "/http-server/auth") -/** - * 接口级别的默认鉴权:API Key - */ @RequestAuth(type = AuthType.API_KEY, name = "X-Service-Key", value = "service-default-key") public interface TestAuthClient extends TestAuthInterface { - @Override - @GetMapping(path = "/bearer-static") /** * 方法级别覆盖:使用 Bearer Token */ + @Override + @GetMapping(path = "/bearer-static") @RequestAuth(type = AuthType.BEARER, value = "static-bearer-token-12345") String testBearerStatic(); - @Override - @GetMapping(path = "/bearer-dynamic") /** * 方法级别覆盖:使用参数驱动的 Bearer Token */ + @Override + @GetMapping(path = "/bearer-dynamic") String testBearerDynamic(@RequestAuth(type = AuthType.BEARER) String token); - @Override - @GetMapping(path = "/basic-static") /** * 方法级别覆盖:使用 Basic Auth */ + @Override + @GetMapping(path = "/basic-static") @RequestAuth(type = AuthType.BASIC, username = "admin", password = "secret123") String testBasicStatic(); - @Override - @GetMapping(path = "/apikey-header-static") /** * 方法级别覆盖:API Key 在 Header 中 */ + @Override + @GetMapping(path = "/apikey-header-static") @RequestAuth(type = AuthType.API_KEY, name = "X-API-Key", value = "static-api-key-67890") String testApiKeyHeaderStatic(); - @Override - @GetMapping(path = "/apikey-query-static") /** * 方法级别覆盖:API Key 在 Query 参数中 */ + + @Override + @GetMapping(path = "/apikey-query-static") @RequestAuth(type = AuthType.API_KEY, name = "api_key", value = "query-api-key-111", location = Source.QUERY) String testApiKeyQueryStatic(); - @Override - @GetMapping(path = "/apikey-dynamic") /** * 参数驱动的 API Key */ + @Override + @GetMapping(path = "/apikey-dynamic") String testApiKeyDynamic(@RequestAuth(type = AuthType.API_KEY, name = "X-Dynamic-Key") String apiKey); - @Override - @GetMapping(path = "/dynamic-provider") /** * 方法级别覆盖:使用动态 Token Provider */ + @Override + @GetMapping(path = "/dynamic-provider") @RequestAuth(type = AuthType.BEARER, provider = DynamicTokenProvider.class) String testDynamicProvider(); - @Override - @GetMapping(path = "/custom-provider") /** * 方法级别覆盖:使用自定义签名 Provider */ + @Override + @GetMapping(path = "/custom-provider") @RequestAuth(type = AuthType.CUSTOM, provider = CustomSignatureProvider.class) String testCustomProvider(); - @Override - @GetMapping(path = "/method-override") /** * 方法级别覆盖:使用 API Key Provider */ + @Override + @GetMapping(path = "/method-override") @RequestAuth(type = AuthType.API_KEY, provider = ApiKeyProvider.class) String testMethodOverride(); - @Override - @GetMapping(path = "/combined-auth") /** * 组合鉴权:服务级 API Key + 用户 Token */ + @Override + @GetMapping(path = "/combined-auth") @RequestAuth(type = AuthType.BEARER, provider = DynamicTokenProvider.class) String testCombinedAuth(@RequestAuth(type = AuthType.API_KEY, name = "X-User-Context") String userToken); + + /** + * 参数级别的 Basic Auth - 使用参数覆盖静态配置的 username + *
演示:方法级别提供完整的 BASIC 认证(username + password), + * 参数级别动态覆盖 username 字段(不指定 name 时默认更新 username)
+ */ + @Override + @GetMapping(path = "/basic-dynamic-username") + @RequestAuth(type = AuthType.BASIC, username = "static-user", password = "static-password") + String testBasicDynamicUsername(@RequestAuth(type = AuthType.BASIC) String username); + + /** + * 参数级别的 Basic Auth - 使用参数分别覆盖 username 和 password + *演示:方法级别提供完整的 BASIC 认证作为基础, + * 参数级别使用 name 属性明确指定要覆盖的字段(username 或 password)
+ */ + @Override + @GetMapping(path = "/basic-dynamic-both") + @RequestAuth(type = AuthType.BASIC, username = "base-user", password = "base-password") + String testBasicDynamicBoth(@RequestAuth(type = AuthType.BASIC, name = "username") String username, + @RequestAuth(type = AuthType.BASIC, name = "password") String password); } \ No newline at end of file diff --git a/examples/fit-example/07-http-client-proxy/plugin-http-client/src/main/java/modelengine/fit/example/client/TestAuthInterface.java b/examples/fit-example/07-http-client-proxy/plugin-http-client/src/main/java/modelengine/fit/example/client/TestAuthInterface.java index acecf93ba..46c3583cc 100644 --- a/examples/fit-example/07-http-client-proxy/plugin-http-client/src/main/java/modelengine/fit/example/client/TestAuthInterface.java +++ b/examples/fit-example/07-http-client-proxy/plugin-http-client/src/main/java/modelengine/fit/example/client/TestAuthInterface.java @@ -86,4 +86,21 @@ public interface TestAuthInterface { * @return 鉴权测试结果 */ String testCombinedAuth(String userToken); + + /** + * 测试参数级别的 Basic Auth - 单参数更新 username(向后兼容)。 + * + * @param username 用户名 + * @return 鉴权测试结果 + */ + String testBasicDynamicUsername(String username); + + /** + * 测试参数级别的 Basic Auth - 双参数分别更新 username 和 password。 + * + * @param username 用户名 + * @param password 密码 + * @return 鉴权测试结果 + */ + String testBasicDynamicBoth(String username, String password); } \ No newline at end of file diff --git a/examples/fit-example/07-http-client-proxy/plugin-http-client/src/main/java/modelengine/fit/example/controller/TestClientController.java b/examples/fit-example/07-http-client-proxy/plugin-http-client/src/main/java/modelengine/fit/example/controller/TestClientController.java index f5cf101e6..f69a50618 100644 --- a/examples/fit-example/07-http-client-proxy/plugin-http-client/src/main/java/modelengine/fit/example/controller/TestClientController.java +++ b/examples/fit-example/07-http-client-proxy/plugin-http-client/src/main/java/modelengine/fit/example/controller/TestClientController.java @@ -104,7 +104,9 @@ public Object test(@RequestQuery("type") String type, @RequestQuery("method") St */ @GetMapping(path = "/auth-test") public Object authTest(@RequestQuery("method") String method, - @RequestQuery(value = "token", required = false) String token) { + @RequestQuery(value = "token", required = false) String token, + @RequestQuery(value = "username", required = false) String username, + @RequestQuery(value = "password", required = false) String password) { switch (method) { case "bearerStatic": return authClient.testBearerStatic(); @@ -112,6 +114,11 @@ public Object authTest(@RequestQuery("method") String method, return authClient.testBearerDynamic(token != null ? token : "dynamic-test-token"); case "basicStatic": return authClient.testBasicStatic(); + case "basicDynamicUsername": + return authClient.testBasicDynamicUsername(username != null ? username : "testuser"); + case "basicDynamicBoth": + return authClient.testBasicDynamicBoth(username != null ? username : "testuser", + password != null ? password : "testpass"); case "apiKeyHeaderStatic": return authClient.testApiKeyHeaderStatic(); case "apiKeyQueryStatic": diff --git a/examples/fit-example/07-http-client-proxy/plugin-http-server/src/main/java/modelengine/fit/example/controller/TestAuthServerController.java b/examples/fit-example/07-http-client-proxy/plugin-http-server/src/main/java/modelengine/fit/example/controller/TestAuthServerController.java index 55f4bda4b..57f4f8848 100644 --- a/examples/fit-example/07-http-client-proxy/plugin-http-server/src/main/java/modelengine/fit/example/controller/TestAuthServerController.java +++ b/examples/fit-example/07-http-client-proxy/plugin-http-server/src/main/java/modelengine/fit/example/controller/TestAuthServerController.java @@ -106,4 +106,14 @@ public String testCombinedAuth(@RequestHeader(name = "Authorization") String aut } return result; } + + @GetMapping(path = "/basic-dynamic-username") + public String testBasicDynamicUsername(@RequestHeader(name = "Authorization") String authorization) { + return "Basic Dynamic Username: " + authorization; + } + + @GetMapping(path = "/basic-dynamic-both") + public String testBasicDynamicBoth(@RequestHeader(name = "Authorization") String authorization) { + return "Basic Dynamic Both: " + authorization; + } } \ No newline at end of file diff --git a/examples/fit-example/07-http-client-proxy/run_tests.sh b/examples/fit-example/07-http-client-proxy/run_tests.sh index 3c897edaa..b520feb86 100755 --- a/examples/fit-example/07-http-client-proxy/run_tests.sh +++ b/examples/fit-example/07-http-client-proxy/run_tests.sh @@ -174,6 +174,18 @@ run_basic_tests() { run_test "Basic Static Auth" \ "curl -s --max-time $TIMEOUT -X GET \"$BASE_URL/basic-static\" -H \"Authorization: Basic YWRtaW46c2VjcmV0MTIz\" -H \"X-Service-Key: service-default-key\"" \ "Basic Static Auth: Basic YWRtaW46c2VjcmV0MTIz" + + # testuser:static-password 的 base64 编码 (dGVzdHVzZXI6c3RhdGljLXBhc3N3b3Jk) + # 参数覆盖 username: testuser,保留方法级别的 password: static-password + run_test "Basic Dynamic Username" \ + "curl -s --max-time $TIMEOUT -X GET \"$BASE_URL/basic-dynamic-username\" -H \"Authorization: Basic dGVzdHVzZXI6c3RhdGljLXBhc3N3b3Jk\"" \ + "Basic Dynamic Username: Basic dGVzdHVzZXI6c3RhdGljLXBhc3N3b3Jk" + + # testuser:testpass 的 base64 编码 (dGVzdHVzZXI6dGVzdHBhc3M=) + # 参数分别覆盖 username 和 password + run_test "Basic Dynamic Both" \ + "curl -s --max-time $TIMEOUT -X GET \"$BASE_URL/basic-dynamic-both\" -H \"Authorization: Basic dGVzdHVzZXI6dGVzdHBhc3M=\"" \ + "Basic Dynamic Both: Basic dGVzdHVzZXI6dGVzdHBhc3M=" } # API Key 测试 diff --git a/framework/fit/java/fit-builtin/plugins/fit-client-http/src/main/java/modelengine/fit/client/http/support/HttpProxyCreator.java b/framework/fit/java/fit-builtin/plugins/fit-client-http/src/main/java/modelengine/fit/client/http/support/HttpProxyCreator.java index 8dda7d05c..7b41afca8 100644 --- a/framework/fit/java/fit-builtin/plugins/fit-client-http/src/main/java/modelengine/fit/client/http/support/HttpProxyCreator.java +++ b/framework/fit/java/fit-builtin/plugins/fit-client-http/src/main/java/modelengine/fit/client/http/support/HttpProxyCreator.java @@ -67,7 +67,7 @@ public void onBeanContainerInitialized(BeanContainer container) { } List对于 API_KEY 类型:
*对于 BASIC 类型(仅参数级别):
+ *{@code
+ * String login(
+ * @RequestAuth(type = BASIC, name = "username") String user,
+ * @RequestAuth(type = BASIC, name = "password") String pwd
+ * );
+ * }对于 BEARER 类型:
+ *name 属性的语义总结:
+ *| 鉴权类型 | + *应用位置 | + *name 属性的含义 | + *示例值 | + *
|---|---|---|---|
| API_KEY | + *静态配置/参数级别 | + *HTTP Header/Query/Cookie 的名称 | + *"X-API-Key", "api_key" | + *
| BASIC | + *仅参数级别 | + *Authorization 对象的字段名 | + *"username", "password" | + *
| BEARER | + *任意位置 | + *无效(被忽略) | + *- | + *
用于确定参数级别鉴权应该更新 {@link modelengine.fit.http.client.proxy.Authorization} + * 对象的哪个字段。
+ * + *设计背景
+ *参数级别的鉴权(如 {@code @RequestAuth(type = BEARER) String token})需要通过 + * {@code authorizationInfo(key, value)} 方法动态更新已存在的 Authorization 对象。 + * 这个 "key" 必须与 Authorization 实现类中 {@code setValue(String key, Object value)} + * 方法能识别的字段名一致。
+ * + *字段映射关系
+ *| 鉴权类型 | + *Authorization 实现 | + *可更新字段 | + *字段含义 | + *字段常量 | + *
|---|---|---|---|---|
| BEARER | + *BearerAuthorization | + *"token" | + *Bearer Token 值 | + *BearerAuthorization.AUTH_TOKEN | + *
| BASIC | + *BasicAuthorization | + *"username" "password" |
+ * 用户名或密码 (参数级别建议只更新一个) |
+ * BasicAuthorization.AUTH_USER_NAME BasicAuthorization.AUTH_USER_PWD |
+ *
| API_KEY | + *ApiKeyAuthorization | + *"key" "value" |
+ * HTTP Header/Query 名称 实际的 API Key 值 |
+ * ApiKeyAuthorization.AUTH_KEY ApiKeyAuthorization.AUTH_VALUE |
+ *
使用示例
+ *{@code
+ * // 参数级别 Bearer Token
+ * String api(@RequestAuth(type = BEARER) String token);
+ * // → 更新 BearerAuthorization.token 字段
+ *
+ * // 参数级别 API Key(更新值)
+ * String search(@RequestAuth(type = API_KEY, name = "X-API-Key") String key);
+ * // → 更新 ApiKeyAuthorization.value 字段
+ * // → ApiKeyAuthorization.key 从注解的 name 属性获取("X-API-Key")
+ * }
+ *
+ * 与 Tool 系统的一致性
+ *此类的设计与 Tool 系统中的 JSON 配置保持一致。例如:
+ *{@code
+ * // Tool JSON 配置
+ * {
+ * "mappings": {
+ * "people": {
+ * "name": {
+ * "key": "token", // ← 字段名
+ * "httpSource": "AUTHORIZATION"
+ * }
+ * }
+ * }
+ * }
+ *
+ * // 对应的注解使用
+ * String api(@RequestAuth(type = BEARER) String token);
+ * // → AuthFieldMapper.getParameterAuthField(BEARER) 返回 "token"
+ * // → 与 JSON 中的 "key": "token" 完全一致
+ * }
+ *
+ * @author 季聿阶
+ * @see modelengine.fit.http.client.proxy.Authorization
+ * @see modelengine.fit.http.client.proxy.support.setter.AuthorizationDestinationSetter
+ * @see BearerAuthorization
+ * @see BasicAuthorization
+ * @see ApiKeyAuthorization
+ * @since 2025-10-01
+ */
+public final class AuthFieldMapper {
+ private AuthFieldMapper() {
+ // 工具类,禁止实例化
+ }
+
+ /**
+ * 获取参数级别鉴权应该更新的 Authorization 字段名。
+ *
+ * 此方法返回的字段名将用于 {@code requestBuilder.authorizationInfo(key, value)} 调用, + * 进而调用 {@code authorization.set(key, value)} 来更新对应字段。
+ * + *重要说明:
+ *{@code
+ * String login(
+ * @RequestAuth(type = BASIC, name = "username") String user,
+ * @RequestAuth(type = BASIC, name = "password") String pwd
+ * );
+ * }
+ * 效果:同时更新 {@code username} 和 {@code password} 字段name 属性的语义重载:
+ *{@code name} 属性在不同鉴权类型下有不同含义:
+ *负责将 {@link RequestAuth} 注解转换为可用于设置 HTTP 请求鉴权信息的 {@link DestinationSetterInfo} 对象。
+ *复用底层的 {@link AuthorizationDestinationSetter} 机制,确保与 FEL Tool 系统架构一致。
+ * + *工作原理
+ *参数级别的鉴权通过 {@link AuthorizationDestinationSetter} 动态更新已存在的 Authorization 对象。 + * 使用 {@link AuthFieldMapper} 确定应该更新 Authorization 对象的哪个字段。
+ * + *使用示例
+ *{@code
+ * // Bearer Token
+ * String api(@RequestAuth(type = BEARER) String token);
+ * // → 更新 BearerAuthorization.token 字段
+ *
+ * // API Key
+ * String search(@RequestAuth(type = API_KEY, name = "X-API-Key") String apiKey);
+ * // → 更新 ApiKeyAuthorization.value 字段
+ * // → ApiKeyAuthorization.key 从注解的 name 属性获取
+ * }
*
* @author 季聿阶
* @since 2025-09-30
+ * @see AuthFieldMapper
*/
public class RequestAuthResolver implements ParamResolver用于处理类级别和方法级别的 @RequestAuth 注解,将静态鉴权信息应用到 HTTP 请求中。
+ *复用底层的 {@link Authorization} 机制,确保架构一致性。
* * @author 季聿阶 * @since 2025-09-30 */ public class StaticAuthApplier implements PropertyValueApplier { - private final AuthDestinationSetter authSetter; + private final Authorization authorization; /** - * 使用指定的鉴权注解初始化 {@link StaticAuthApplier} 的新实例。 + * 使用指定的鉴权注解和 BeanContainer 初始化 {@link StaticAuthApplier} 的新实例。 * * @param authAnnotation 表示鉴权注解的 {@link RequestAuth}。 + * @param beanContainer 表示 Bean 容器,用于获取 AuthProvider。 */ - public StaticAuthApplier(RequestAuth authAnnotation) { - this.authSetter = new AuthDestinationSetter(authAnnotation); + public StaticAuthApplier(RequestAuth authAnnotation, BeanContainer beanContainer) { + notNull(beanContainer, "The bean container cannot be null."); + this.authorization = this.createAuthorizationFromAnnotation(authAnnotation, beanContainer); } @Override public void apply(RequestBuilder requestBuilder, Object value) { - // 静态鉴权不需要参数值,传入 null 即可 - authSetter.set(requestBuilder, null); + // 静态鉴权不需要参数值,直接将 Authorization 对象设置到 RequestBuilder + requestBuilder.authorization(this.authorization); } + + private Authorization createAuthorizationFromAnnotation(RequestAuth annotation, BeanContainer beanContainer) { + // 如果指定了 Provider,需要 BeanContainer + if (annotation.provider() != AuthProvider.class) { + AuthProvider provider = beanContainer.beans().get(annotation.provider()); + if (provider == null) { + throw new IllegalStateException( + "AuthProvider not found in BeanContainer: " + annotation.provider().getName()); + } + return provider.provide(); + } + + // 基于注解类型创建 Authorization + AuthType type = annotation.type(); + switch (type) { + case BEARER: + String token = annotation.value(); + if (StringUtils.isEmpty(token)) { + throw new IllegalArgumentException("Bearer token cannot be empty for static auth"); + } + return Authorization.createBearer(token); + + case BASIC: + String username = annotation.username(); + String password = annotation.password(); + if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) { + throw new IllegalArgumentException("Username and password cannot be empty for Basic auth"); + } + return Authorization.createBasic(username, password); + + case API_KEY: + String keyName = annotation.name(); + String keyValue = annotation.value(); + if (StringUtils.isEmpty(keyName) || StringUtils.isEmpty(keyValue)) { + throw new IllegalArgumentException("API Key name and value cannot be empty for static auth"); + } + return Authorization.createApiKey(keyName, keyValue, annotation.location()); + + case CUSTOM: + throw new IllegalArgumentException("CUSTOM auth type requires a provider"); + + default: + throw new IllegalArgumentException("Unsupported auth type: " + type); + } + } + } \ No newline at end of file diff --git a/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/support/setter/AuthDestinationSetter.java b/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/support/setter/AuthDestinationSetter.java deleted file mode 100644 index fc6e4eafe..000000000 --- a/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/support/setter/AuthDestinationSetter.java +++ /dev/null @@ -1,135 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -package modelengine.fit.http.client.proxy.support.setter; - -import modelengine.fit.http.annotation.RequestAuth; -import modelengine.fit.http.client.proxy.Authorization; -import modelengine.fit.http.client.proxy.DestinationSetter; -import modelengine.fit.http.client.proxy.RequestBuilder; -import modelengine.fit.http.client.proxy.auth.AuthProvider; -import modelengine.fit.http.client.proxy.auth.AuthType; -import modelengine.fit.http.server.handler.Source; -import modelengine.fitframework.ioc.BeanContainer; -import modelengine.fitframework.util.StringUtils; - -/** - * 表示向 HTTP 请求设置鉴权信息的 {@link DestinationSetter}。 - *支持多种鉴权类型和动态 Provider。
- * - * @author 季聿阶 - * @since 2025-09-30 - */ -public class AuthDestinationSetter implements DestinationSetter { - private final RequestAuth authAnnotation; - private final BeanContainer beanContainer; - - /** - * 使用指定的鉴权注解初始化 {@link AuthDestinationSetter} 的新实例。 - * - * @param authAnnotation 表示鉴权注解的 {@link RequestAuth}。 - */ - public AuthDestinationSetter(RequestAuth authAnnotation) { - this(authAnnotation, null); - } - - /** - * 使用指定的鉴权注解和 Bean 容器初始化 {@link AuthDestinationSetter} 的新实例。 - * - * @param authAnnotation 表示鉴权注解的 {@link RequestAuth}。 - * @param beanContainer 表示 Bean 容器的 {@link BeanContainer}。 - */ - public AuthDestinationSetter(RequestAuth authAnnotation, BeanContainer beanContainer) { - this.authAnnotation = authAnnotation; - this.beanContainer = beanContainer; - } - - @Override - public void set(RequestBuilder requestBuilder, Object value) { - Authorization authorization = createAuthorization(value); - if (authorization != null) { - authorization.assemble(requestBuilder); - } - } - - private Authorization createAuthorization(Object value) { - // 如果指定了 Provider,优先使用 Provider - if (authAnnotation.provider() != AuthProvider.class) { - if (beanContainer != null) { - AuthProvider provider = beanContainer.beans().get(authAnnotation.provider()); - if (provider != null) { - return provider.provide(); - } else { - throw new IllegalStateException("AuthProvider " + authAnnotation.provider().getName() + " not found in container"); - } - } else { - // TODO: MVP 版本暂时不支持 Provider,后续版本再实现 - throw new UnsupportedOperationException("AuthProvider support is not implemented in this version"); - } - } - - // 基于注解类型创建 Authorization - AuthType type = authAnnotation.type(); - switch (type) { - case BEARER: - String token = getBearerToken(value); - if (StringUtils.isNotEmpty(token)) { - return Authorization.createBearer(token); - } - break; - case BASIC: - String username = getBasicUsername(); - String password = getBasicPassword(); - if (StringUtils.isNotEmpty(username) && StringUtils.isNotEmpty(password)) { - return Authorization.createBasic(username, password); - } - break; - case API_KEY: - String keyName = getApiKeyName(); - String keyValue = getApiKeyValue(value); - Source location = authAnnotation.location(); - if (StringUtils.isNotEmpty(keyName) && StringUtils.isNotEmpty(keyValue)) { - return Authorization.createApiKey(keyName, keyValue, location); - } - break; - case CUSTOM: - // CUSTOM 类型必须使用 Provider - throw new IllegalArgumentException("CUSTOM auth type requires a provider"); - } - - return null; - } - - private String getBearerToken(Object value) { - // 如果是参数驱动,使用参数值 - if (value instanceof String) { - return (String) value; - } - // 否则使用注解中的静态值 - return StringUtils.isNotEmpty(authAnnotation.value()) ? authAnnotation.value() : null; - } - - private String getBasicUsername() { - return StringUtils.isNotEmpty(authAnnotation.username()) ? authAnnotation.username() : null; - } - - private String getBasicPassword() { - return StringUtils.isNotEmpty(authAnnotation.password()) ? authAnnotation.password() : null; - } - - private String getApiKeyName() { - return StringUtils.isNotEmpty(authAnnotation.name()) ? authAnnotation.name() : null; - } - - private String getApiKeyValue(Object value) { - // 如果是参数驱动,使用参数值 - if (value instanceof String) { - return (String) value; - } - // 否则使用注解中的静态值 - return StringUtils.isNotEmpty(authAnnotation.value()) ? authAnnotation.value() : null; - } -} \ No newline at end of file diff --git a/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/test/java/modelengine/fit/http/client/proxy/scanner/HttpInvocationHandlerTest.java b/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/test/java/modelengine/fit/http/client/proxy/scanner/HttpInvocationHandlerTest.java new file mode 100644 index 000000000..1439b4a6c --- /dev/null +++ b/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/test/java/modelengine/fit/http/client/proxy/scanner/HttpInvocationHandlerTest.java @@ -0,0 +1,264 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fit.http.client.proxy.scanner; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import modelengine.fit.http.client.HttpClassicClient; +import modelengine.fit.http.client.HttpClassicClientFactory; +import modelengine.fit.http.client.HttpClassicClientResponse; +import modelengine.fit.http.client.HttpClientException; +import modelengine.fit.http.client.proxy.PropertyValueApplier; +import modelengine.fit.http.client.proxy.RequestBuilder; +import modelengine.fit.http.client.proxy.scanner.entity.Address; +import modelengine.fit.http.client.proxy.scanner.entity.HttpInfo; +import modelengine.fit.http.entity.TextEntity; +import modelengine.fit.http.protocol.HttpRequestMethod; +import modelengine.fitframework.ioc.BeanContainer; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * 表示 {@link HttpInvocationHandler} 的单元测试。 + * + * @author 季聿阶 + * @since 2025-09-30 + */ +class HttpInvocationHandlerTest { + @Mock + private HttpClassicClientFactory mockFactory; + + @Mock + private BeanContainer mockContainer; + + @Mock + private HttpClassicClient mockClient; + + @Mock + private HttpClassicClientResponse