Skip to content

Commit

Permalink
feat(内核): 支持 Part Map 类型
Browse files Browse the repository at this point in the history
  • Loading branch information
qianmoQ committed Feb 6, 2025
1 parent 2fb9480 commit c205fa7
Show file tree
Hide file tree
Showing 8 changed files with 261 additions and 4 deletions.
63 changes: 63 additions & 0 deletions docs/content/usage/part-map.md
Original file line number Diff line number Diff line change
@@ -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<String, File> files);
```

该示例中的 `apply` 是一个文件上传请求,参数 `files` 表示要上传的文件,`"files"` 是文件参数的名称。请求路径是 `/upload/multiple`

### 文件类型

---

`@PartMap` 注解支持 `java.io.File` 类型的参数,用于上传本地文件。

```java
@Post("/upload/multiple")
Object apply(@PartMap(value = "files") Map<String, File> 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<String, File> files);
```

该示例中的 `apply` 方法用于上传图片文件,参数 `image` 表示要上传的图片文件,请求路径是 `/upload`,文件的 MIME 类型是 `image/jpeg`
28 changes: 25 additions & 3 deletions docs/pageforge.yaml
Original file line number Diff line number Diff line change
@@ -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: 💗 <a href="https://afdian.com/a/devlive-community" target="_blank">如果喜欢我们的软件,请点击这里支持我们</a> ❤️

repo:
owner: devlive-community
Expand All @@ -25,6 +30,22 @@ feature:
enable: true
backToTop:
enable: true
sitemap:
enable: true
statistics:
enable: true
content: |
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-5403546756695673"
crossorigin="anonymous"></script>
<ins class="adsbygoogle"
style="display:block; text-align:center;"
data-ad-layout="in-article"
data-ad-format="fluid"
data-ad-client="ca-pub-5403546756695673"
data-ad-slot="7720669116"></ins>
<script>
(adsbygoogle = window.adsbygoogle || []).push({});
</script>
footer:
copyright: © 2024 PageForge All Rights Reserved.
Expand All @@ -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:
Expand All @@ -59,5 +80,6 @@ nav:
- /usage/options
- /usage/head
- /usage/part
- /usage/part-map
- 发布日志:
- /release/latest
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public static List<ParameterHandler> 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;
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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<String, File> files);
}
Original file line number Diff line number Diff line change
@@ -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<String, File> files = new HashMap<>();
files.put("file1", testFile);
files.put("file2", testFile2);

// 测试文件上传
Object response = service.apply(files);
Assertions.assertNotNull(response);
}
}
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
<!-- 依赖版本 -->
<okhttp.version>4.12.0</okhttp.version>
<jackson.version>2.16.1</jackson.version>
<logback.version>1.2.12</logback.version>
<logback.version>1.3.14</logback.version>
<junit.version>5.10.1</junit.version>
<mockito.version>5.10.0</mockito.version>
<lombok.version>1.18.34</lombok.version>
Expand Down

0 comments on commit c205fa7

Please sign in to comment.