简体   繁体   English

使用 Retrofit 2 在对象数组中上传文件

[英]Uploading a file in an array of object using Retrofit 2

I use Retrofit2 and I need to upload various files using file in an array of objects Media like this:我使用 Retrofit2,我需要使用对象Media数组中的file上传各种文件,如下所示:

{
   "state" = "done",
   "medias" = [
     {
      "file" = THE_FILE1
     },
     {
      "file" = THE_FILE2
     },
     {
      "file" = THE_FILE3
     }
   ]
}

This is the function of my Interface :这是我的Interface的功能:

@Multipart
@POST("api/exercice/{id}")
fun submitExercice(
    @Path("id") id: Int,
    @Header("Authorization") token: String,
    @Body data: AnswerExercice
): Call<Void>

And this is my object Media :这是我的对象Media

    data class AnswerExercice(
    val state: String = "done",
    val medias: List<Media>
) : Serializable {
    data class Media(
        @Part val file: MultipartBody.Part
    ) : Serializable
}

But I have this error:但我有这个错误:

@Body parameters cannot be used with form or multi-part encoding. @Body 参数不能与表单或多部分编码一起使用。 (parameter #3) (参数#3)

What am i not doing well?我做的不好吗?

This is what the API documentation say: API 文档是这样说的: 达

The result have to be like this:结果必须是这样的: 在此处输入图像描述

Solution 1解决方案 1
If you like to send your data exactly like the structure you mentioned, you should convert files content to Base64 and wrap them in a serializable class and post it as the body.如果您想像您提到的结构一样发送数据,您应该将文件内容转换为Base64并将它们包装在一个可序列化的类中并将其作为正文发布。 Here is the sample of wrapper class:这是包装类的示例:

data class AnswerExerciceBase64(val state: String, val medias: List<Media>) : Serializable

data class Media(val file: Base64File) : Serializable

class Base64File(file: File) : Serializable {

    val name: String
    val content: String

    init {
        name = file.name
        content = Base64.encodeToString(FileInputStream(file).readBytes(), Base64.DEFAULT)
    }
}

And your Api is like this:而你的Api是这样的:

@POST("api/exercice/{id}")
fun submitExercice(
        @Path("id") id: Int,
        @Header("Authorization") token: String,
        @Body data: AnswerExerciceBase64
): Call<Void>

Then posted data to server will be like below:然后将数据发布到服务器将如下所示:

{
    "state": "this is state",
    "medias": [{
        "file": {
            "content": "Base64 file content",
            "name": "f1.txt"
        }
    }, {
        "file": {
            "content": "Base64 file content",
            "name": "f2.txt"
        }
    }, {
        "file": {
            "content": "Base64 file content",
            "name": "f3.txt"
        }
    }]
}

This approach is so close to what you want but you should know you must decode files content on the server-side by yourself, so you need more effort on the server-side.这种方法非常接近你想要的,但你应该知道你必须自己在服务器端解码文件内容,所以你需要在服务器端做更多的工作。


Solution 2方案二
It's better to use multipart/form-data to upload files and data.最好使用multipart/form-data来上传文件和数据。 Based on "Is it possible to have a nested MultipartEntities or FormBodyPart in a multipart POST?"基于“是否可以在多部分 POST 中嵌套 MultipartEntities 或 FormBodyPart?” question and its answer, multipart/form-data has a flat structure and there is no hierarchy, so you can't have desired data structure but you can still pass all of the inputs to Api through a single object.问题及其答案, multipart/form-data具有平面结构并且没有层次结构,因此您不能拥有所需的数据结构,但您仍然可以通过单个对象将所有输入传递给Api
According to this article , you can send multiple files in a List, so if your Api be like this根据这篇文章,您可以在一个列表中发送多个文件,所以如果您的Api是这样的

@Multipart
@POST("post")
fun submitExercice(@Part data: List<MultipartBody.Part>): Call<ResponseBody>

then you will be able to upload multiple files.那么您将能够上传多个文件。 You just need to create a List of MultipartBody.Part and add your files to it like below:您只需要创建一个MultipartBody.Part列表并将您的文件添加到其中,如下所示:

list.add(MultipartBody.Part.createFormData(name, fileName, RequestBody.create(mediaType, file)))

Now you must add the state parameter to this list.现在您必须将state参数添加到此列表中。 You can do it like this:你可以这样做:

list.add(MultipartBody.Part.createFormData("state", state))

I developed a class that handles all this stuff.我开发了一个类来处理所有这些东西。 You can use it.你可以使用它。

class AnswerExerciceList(state: String) : ArrayList<MultipartBody.Part>() {

    init {
        add(MultipartBody.Part.createFormData("state", state))
    }

    fun addFile(name: String, fileName: String, mediaType: MediaType?, file: File) {
        add(MultipartBody.Part.createFormData(name, fileName,
                RequestBody.create(mediaType, file)))
    }
}

You can create an instance of this class, add your files and then pass it to the submitExercice Api method as input.您可以创建此类的一个实例,添加您的文件,然后将其作为输入传递给submitExercice Api 方法。

Update更新
This answer is based on your Api documnetation.此答案基于您的 Api 文档。 I tested my answer and the example that you mentioned in your question via https://postman-echo.com and result was the same.我通过https://postman-echo.com测试了我的答案和你在问题中提到的例子,结果是一样的。 Please try the following code snippet: Api请尝试以下代码片段: Api

@Multipart
@POST("api/exercice/{id}")
fun submitExercice(@Path("id") id: Int,
                   @Header("Authorization") authorization: String,
                   @Part("answer") answer: String,
                   @Part medias: List<MultipartBody.Part>,
                   @Part("state") state: String): Call<ResponseBody>

Media Class媒体类

data class Media(val urlVidel: String, val file: File?, val mediaType: MediaType?) {
    companion object {
        fun mediaListToMultipart(mediaList: List<Media>): List<MultipartBody.Part> {
            val list = ArrayList<MultipartBody.Part>()
            for (i in mediaList.indices) {
                mediaList[i].let {
                    if (!TextUtils.isEmpty(it.urlVidel))
                        list.add(MultipartBody.Part.createFormData("medias[$i][urlVideo]", it.urlVidel))
                    if (it.file != null) {
                        val requestFile = RequestBody.create(
                                it.mediaType,
                                it.file
                        )
                        list.add(MultipartBody.Part.createFormData("medias[$i][file]", it.file.getName(), requestFile))
                    }
                }
            }
            return list
        }
    }
}

and then call Api like this:然后像这样调用 Api:

ApiHelper.Instance.submitExercice(1, "Authorization Token", "Answer", Media.mediaListToMultipart(mediaList), "State").enqueue(callback)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM