diff --git a/docs/content/usage/part-map.md b/docs/content/usage/part-map.md new file mode 100644 index 0000000..ed3db63 --- /dev/null +++ b/docs/content/usage/part-map.md @@ -0,0 +1,63 @@ +--- +title: PartMap +status: + type: new +--- + +LightCall 提供了文件上传的支持。 + +!!! danger "注意" + + LightCall 需要在参数上添加 `@PartMap` 注解来标识该参数是一个文件上传请求。并且添加 `@PartMap` 的类必须是一个接口。 + +!!! + +我们使用的模拟数据是,他的代码可以在 [这里](https://github.com/devlive-community/lightcall/blob/dev/src/test/java/org/devlive/lightcall/example/part/map/PartMapService.java "PartMapService" "_blank") 查看。 + +```java +public interface PartMapService +{} +``` + +### 用法 + +在参数上添加 `@PartMap` 注解,就可以实现文件上传请求了。 + +```java +@Post("/upload/multiple") +Object apply(@PartMap(value = "files") Map files); +``` + +该示例中的 `apply` 是一个文件上传请求,参数 `files` 表示要上传的文件,`"files"` 是文件参数的名称。请求路径是 `/upload/multiple`。 + +### 文件类型 + +--- + +`@PartMap` 注解支持 `java.io.File` 类型的参数,用于上传本地文件。 + +```java +@Post("/upload/multiple") +Object apply(@PartMap(value = "files") Map files); +``` + +该示例中的 `apply` 方法用于上传图片文件,参数 `image` 表示要上传的图片文件,请求路径是 `/upload/multiple`。 + +!!! info "提示" + + 文件上传请求会自动设置 `Content-Type` 为 `multipart/form-data`,并将文件作为表单的一部分上传。 + +!!! + +### `mime` 类型 + +--- + +`@Part` 注解支持 `mime` 参数,用于指定上传文件的 MIME 类型,默认值为 `application/octet-stream`。 + +```java +@Post("/upload/multiple") +Object apply(@PartMap(value = "files") Map files); +``` + +该示例中的 `apply` 方法用于上传图片文件,参数 `image` 表示要上传的图片文件,请求路径是 `/upload`,文件的 MIME 类型是 `image/jpeg`。 \ No newline at end of file diff --git a/docs/pageforge.yaml b/docs/pageforge.yaml index 62976d3..5987bd4 100644 --- a/docs/pageforge.yaml +++ b/docs/pageforge.yaml @@ -1,8 +1,13 @@ site: title: LightCall description: LightCall 的设计目标是提供一个简单、直观且功能强大的服务调用框架。通过声明式的方式,让开发者专注于业务逻辑而不是底层的 HTTP 调用细节。 + keywords: LightCall,服务调用框架,声明式服务调用,声明式HTTP调用,Client logo: /assets/logo.svg favicon: assets/logo.svg + baseUrl: https://lightcall.devlive.org + +banner: + content: 💗 如果喜欢我们的软件,请点击这里支持我们 ❤️ repo: owner: devlive-community @@ -25,6 +30,22 @@ feature: enable: true backToTop: enable: true + sitemap: + enable: true + statistics: + enable: true + content: | + + + footer: copyright: © 2024 PageForge All Rights Reserved. @@ -34,11 +55,11 @@ footer: href: https://github.com/devlive-community/lightcall links: - 开源项目: - - title: 开源数据中台 + - title: 开源数据中台 DataCap href: https://github.com/devlive-community/datacap - - title: 开源 UI 框架 + - title: 前端框架 View Shadcn UI href: https://github.com/devlive-community/view-shadcn-ui - - title: 开源网站构建工具 + - title: 网站构建 PageForge href: https://github.com/devlive-community/pageforge nav: @@ -59,5 +80,6 @@ nav: - /usage/options - /usage/head - /usage/part + - /usage/part-map - 发布日志: - /release/latest \ No newline at end of file diff --git a/lightcall-core/src/main/java/org/devlive/lightcall/annotation/PartMap.java b/lightcall-core/src/main/java/org/devlive/lightcall/annotation/PartMap.java new file mode 100644 index 0000000..7c30ce2 --- /dev/null +++ b/lightcall-core/src/main/java/org/devlive/lightcall/annotation/PartMap.java @@ -0,0 +1,25 @@ +package org.devlive.lightcall.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Documented +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +public @interface PartMap +{ + String value(); + + /** + * 指定所有文件的 MIME 类型,如果为空则自动检测 + */ + String mimeType() default "application/octet-stream"; + + /** + * 是否必需 + */ + boolean required() default true; +} diff --git a/lightcall-core/src/main/java/org/devlive/lightcall/handler/ParameterHandlerFactory.java b/lightcall-core/src/main/java/org/devlive/lightcall/handler/ParameterHandlerFactory.java index cb7b3ed..6d96b6f 100644 --- a/lightcall-core/src/main/java/org/devlive/lightcall/handler/ParameterHandlerFactory.java +++ b/lightcall-core/src/main/java/org/devlive/lightcall/handler/ParameterHandlerFactory.java @@ -19,6 +19,7 @@ public static List createHandlers( handlers.add(HeaderHandler.create(context.getRequestBuilder(), method)); handlers.add(BodyHandler.create(context)); handlers.add(PartHandler.create(context)); + handlers.add(PartMapHandler.create(context)); return handlers; } } diff --git a/lightcall-core/src/main/java/org/devlive/lightcall/handler/PartMapHandler.java b/lightcall-core/src/main/java/org/devlive/lightcall/handler/PartMapHandler.java new file mode 100644 index 0000000..131fffb --- /dev/null +++ b/lightcall-core/src/main/java/org/devlive/lightcall/handler/PartMapHandler.java @@ -0,0 +1,93 @@ +package org.devlive.lightcall.handler; + +import com.fasterxml.jackson.core.JsonProcessingException; +import lombok.extern.slf4j.Slf4j; +import okhttp3.MediaType; +import okhttp3.MultipartBody; +import okhttp3.RequestBody; +import org.devlive.lightcall.RequestContext; +import org.devlive.lightcall.RequestException; +import org.devlive.lightcall.annotation.PartMap; + +import java.io.File; +import java.lang.reflect.Parameter; +import java.util.Map; + +@Slf4j +public class PartMapHandler + implements ParameterHandler +{ + private final RequestContext context; + + private PartMapHandler(RequestContext context) + { + this.context = context; + } + + public static PartMapHandler create(RequestContext context) + { + if (context == null) { + throw new NullPointerException("RequestContext cannot be null"); + } + log.debug("Creating PartMapHandler"); + return new PartMapHandler(context); + } + + @Override + public boolean canHandle(Parameter parameter) + { + return parameter.isAnnotationPresent(PartMap.class); + } + + @Override + public String handle(Parameter parameter, Object arg, String path) + { + if (arg == null) { + PartMap annotation = parameter.getAnnotation(PartMap.class); + if (annotation.required()) { + throw new IllegalArgumentException("PartMap is required but was null"); + } + return path; + } + + if (!(arg instanceof Map)) { + throw new IllegalArgumentException("Parameter annotated with @PartMap must be a Map"); + } + + Map map = (Map) arg; + MultipartBody.Builder multipartBuilder = new MultipartBody.Builder() + .setType(MultipartBody.FORM); + + PartMap annotation = parameter.getAnnotation(PartMap.class); + String formName = annotation.value(); // 获取表单字段名前缀 + String mimeType = annotation.mimeType(); + + for (Map.Entry entry : map.entrySet()) { + Object value = entry.getValue(); + + if (value instanceof File) { + File file = (File) value; + RequestBody fileBody = RequestBody.create( + file, + MediaType.parse(mimeType) + ); + + multipartBuilder.addFormDataPart( + formName, + file.getName(), + fileBody + ); + log.debug("Added file part {}: {}", formName, file.getName()); + } + } + + try { + context.setBody(multipartBuilder.build(), MultipartBody.FORM); + } + catch (JsonProcessingException e) { + throw new RequestException("Failed to set form parts", e); + } + + return path; + } +} diff --git a/lightcall-core/src/test/java/org/devlive/lightcall/example/part/map/PartMapService.java b/lightcall-core/src/test/java/org/devlive/lightcall/example/part/map/PartMapService.java new file mode 100644 index 0000000..381d888 --- /dev/null +++ b/lightcall-core/src/test/java/org/devlive/lightcall/example/part/map/PartMapService.java @@ -0,0 +1,13 @@ +package org.devlive.lightcall.example.part.map; + +import org.devlive.lightcall.annotation.PartMap; +import org.devlive.lightcall.annotation.Post; + +import java.io.File; +import java.util.Map; + +public interface PartMapService +{ + @Post("/upload/multiple") + Object apply(@PartMap(value = "files") Map files); +} diff --git a/lightcall-core/src/test/java/org/devlive/lightcall/example/part/map/PartMapServiceTest.java b/lightcall-core/src/test/java/org/devlive/lightcall/example/part/map/PartMapServiceTest.java new file mode 100644 index 0000000..04100a5 --- /dev/null +++ b/lightcall-core/src/test/java/org/devlive/lightcall/example/part/map/PartMapServiceTest.java @@ -0,0 +1,40 @@ +package org.devlive.lightcall.example.part.map; + +import org.devlive.lightcall.LightCall; +import org.devlive.lightcall.config.LightCallConfig; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.File; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; + +class PartMapServiceTest +{ + // 使用一个支持文件上传的测试服务 + private final LightCallConfig config = LightCallConfig.create("http://mockaroo.devlive.org"); + private final PartMapService service = LightCall.create(PartMapService.class, config); + + @Test + void testFileUpload(@TempDir Path tempDir) + throws Exception + { + // 创建测试文件 + File testFile = tempDir.resolve("test.txt").toFile(); + testFile.createNewFile(); + + // 创建测试文件 + File testFile2 = tempDir.resolve("test2.txt").toFile(); + testFile2.createNewFile(); + + Map files = new HashMap<>(); + files.put("file1", testFile); + files.put("file2", testFile2); + + // 测试文件上传 + Object response = service.apply(files); + Assertions.assertNotNull(response); + } +} diff --git a/pom.xml b/pom.xml index 92c616d..7d07b7c 100644 --- a/pom.xml +++ b/pom.xml @@ -50,7 +50,7 @@ 4.12.0 2.16.1 - 1.2.12 + 1.3.14 5.10.1 5.10.0 1.18.34