Загрузка файла в массив объекта с помощью Retrofit 2 - PullRequest
0 голосов
/ 21 января 2020

Я использую Retrofit2, и мне нужно загружать различные файлы, используя file в массив объектов Media, например:

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

Это функция моего Interface:

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

А это мой объект Media:

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

Но у меня есть эта ошибка:

@ Параметры тела нельзя использовать с формой или несколькими кодировка части. (параметр # 3)

Что я не очень хорошо делаю?

Вот что говорится в документации API: tat

Результат должен быть таким: enter image description here

1 Ответ

1 голос
/ 25 января 2020

Раствор 1 Если вы хотите отправить ваши данные в точности как структура, которую вы упомянули, вы должны преобразовать содержимое файлов в Base64, обернуть их в сериализуемый класс и опубликовать в виде тела. Вот пример класса оболочки:

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)
    }
}

И ваш Api выглядит следующим образом:

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

Тогда отправленные данные на сервер будут выглядеть так:

{
    "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"
        }
    }]
}

Этот подход настолько близок к тому, что вы хотите, но вы должны знать, что вы должны самостоятельно декодировать содержимое файлов на стороне сервера, поэтому вам нужно больше усилий на стороне сервера. Решение 2 Лучше использовать multipart/form-data для загрузки файлов и данных. На основании «Возможно ли иметь вложенный MultipartEntities или FormBodyPart в составном POST?» вопрос и ответ на него, multipart/form-data имеет плоскую структуру и отсутствует иерархия, поэтому вы не можете иметь желаемый структура данных, но вы все равно можете передавать все входные данные в Api через один объект.
Согласно этой статье , вы можете отправлять несколько файлов в списке, поэтому, если ваш Api будет как это

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

тогда вы сможете загружать несколько файлов. Вам просто нужно создать список MultipartBody.Part и добавить в него свои файлы, как показано ниже:

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

Теперь вы должны добавить параметр state в этот список. Вы можете сделать это так:

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

Я разработал класс, который обрабатывает все эти вещи. Вы можете использовать его.

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)))
    }
}

Вы можете создать экземпляр этого класса, добавить свои файлы и затем передать его в метод submitExercice Api в качестве входных данных. Обновление Этот ответ основан на вашей документации Api. Я проверил мой ответ и пример, который вы упомянули в своем вопросе через https://postman-echo.com, и результат был таким же. Попробуйте следующий фрагмент кода: 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
        }
    }
}

и затем вызовите Api следующим образом:

ApiHelper.Instance.submitExercice(1, "Authorization Token", "Answer", Media.mediaListToMultipart(mediaList), "State").enqueue(callback)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...