使用 Retrofit 2 在对象数组中上传文件
Uploading a file in an array of object using Retrofit 2
我使用 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
}
但是我有这个错误:
@Body parameters cannot be used with form or multi-part encoding.
(parameter #3)
我哪里做的不好?
API 文档是这样说的:
结果必须是这样的:
解决方案 1
如果您想完全按照您提到的结构发送数据,您应该将文件内容转换为 Base64
并将它们包装在可序列化的 class 和 post 中作为正文。这是包装器 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)
}
}
而你的Api
是这样的:
@POST("api/exercice/{id}")
fun submitExercice(
@Path("id") id: Int,
@Header("Authorization") token: String,
@Body data: AnswerExerciceBase64
): Call<Void>
然后post发送到服务器的数据将如下所示:
{
"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
。根据 "Is it possible to have a nested MultipartEntities or FormBodyPart in a multipart POST?" 问题及其答案,multipart/form-data
具有平面结构并且没有层次结构,因此您不能拥有所需的数据结构,但您仍然可以将所有输入传递给 Api
通过单个对象。
根据这个article,你可以在一个List中发送多个文件,所以如果你的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 来处理所有这些东西。你可以使用它。
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)))
}
}
您可以创建此 class 的实例,添加您的文件,然后将其作为输入传递给 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>
媒体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)
我使用 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
}
但是我有这个错误:
@Body parameters cannot be used with form or multi-part encoding. (parameter #3)
我哪里做的不好?
API 文档是这样说的:
结果必须是这样的:
解决方案 1
如果您想完全按照您提到的结构发送数据,您应该将文件内容转换为 Base64
并将它们包装在可序列化的 class 和 post 中作为正文。这是包装器 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)
}
}
而你的Api
是这样的:
@POST("api/exercice/{id}")
fun submitExercice(
@Path("id") id: Int,
@Header("Authorization") token: String,
@Body data: AnswerExerciceBase64
): Call<Void>
然后post发送到服务器的数据将如下所示:
{
"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
。根据 "Is it possible to have a nested MultipartEntities or FormBodyPart in a multipart POST?" 问题及其答案,multipart/form-data
具有平面结构并且没有层次结构,因此您不能拥有所需的数据结构,但您仍然可以将所有输入传递给 Api
通过单个对象。
根据这个article,你可以在一个List中发送多个文件,所以如果你的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 来处理所有这些东西。你可以使用它。
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)))
}
}
您可以创建此 class 的实例,添加您的文件,然后将其作为输入传递给 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>
媒体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)