使用Retrofit 2上传多个文件到Spring Webflux后端



当我尝试用retrofit 2上传多个文件时,我得到以下错误:

org.springframework.core.codec。DecodingException:无法找到第一个边界

当我在同一个API端点上用postman上传多个文件时,它工作得很好。

服务器控制器端点:

@PostMapping("{loveSpotId}/photos")
suspend fun uploadToSpot(
@PathVariable loveSpotId: Long,
@RequestPart("photos") filePartFlux: Flux<FilePart>
) {
loveSpotPhotoService.uploadToSpot(loveSpotId, filePartFlux.asFlow())
}

Retrofit API定义:


interface LoveSpotPhotoApi {
@Multipart
@POST("/lovespots/{loveSpotId}/photos")
fun uploadToLoveSpot(
@Path("loveSpotId") loveSpotId: Long,
@Part photos: List<MultipartBody.Part>
): Call<ResponseBody>
// ...
}

在Android设备上阅读照片:


if (activityResult.resultCode == Activity.RESULT_OK) {
val itemCount: Int = activityResult.data?.clipData?.itemCount ?: 0
val files = ArrayList<File>()
for (i in 0 until itemCount) {
val clipData = activityResult.data!!.clipData!!
val uri = clipData.getItemAt(i).uri
files.add(File(uri.path!!))
}
loveSpotPhotoService.uploadToLoveSpot(loveSpotId, files, this@LoveSpotDetailsActivity)
}

客户端代码使用Retrofit:


suspend fun uploadToLoveSpot(loveSpotId: Long, photos: List<File>, activity: Activity) {
val loadingBarShower = LoadingBarShower(activity).show()
withContext(Dispatchers.IO) {
val parts: List<MultipartBody.Part> = photos.map { prepareFilePart(it) }
val call = loveSpotPhotoApi.uploadToLoveSpot(loveSpotId, parts)
try {
val response = call.execute()
loadingBarShower.onResponse()
if (response.isSuccessful) {
toaster.showToast(R.string.photo_uploaded_succesfully)
} else {
toaster.showResponseError(response)
}
} catch (e: Exception) {
loadingBarShower.onResponse()
toaster.showToast(R.string.photo_upload_failed)
}
}
}
private fun prepareFilePart(file: File): MultipartBody.Part {
// create RequestBody instance from file
val requestFile = RequestBody.create(
MediaType.get("image/*"),
file
)
// MultipartBody.Part is used to send also the actual file name
return MultipartBody.Part.createFormData("photos", file.name, requestFile)
}

示例头登录服务器时,我与邮差上传,它的工作:

[Authorization:"Bearer…", User-Agent:"PostmanRuntime/7.30.0", Accept:">/", post - token:" 7ad875eb2fe5 -40ea-99f0-3ad34c3fa875", Host:"localhost:8090", Accept- encoding:"gzip, deflate, br", Connection:"keep-alive", Content-Type:"multipart/form-data;]边界 =-------------------------- 内容长度877409032202838734061440",:"1555045")

示例头登录服务器时,我上传改造客户端,它失败了:

[Authorization:"Bearer…" Content-Type:"multipart/form-data;]border =c6177139-6b31-4d91-b66d-54772a51d963", Host:"192.168.0.143:8090", Connection:"Keep-Alive", Accept-Encoding:"gzip", User-Agent:"okhttp/3.14.9", content-length:"528"]

问题是我不能正确地从Android设备读取照片。下面是我的代码,解决了这个问题:

launcher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult ->
handlePhotoPickerResult(activityResult)
}
fun startPickerIntent(launcher: ActivityResultLauncher<Intent>) {
val intent = Intent()
intent.type = "image/*"
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
intent.action = Intent.ACTION_PICK
launcher.launch(intent)
}

private fun handlePhotoPickerResult(activityResult: ActivityResult) {
MainScope().launch {
if (activityResult.resultCode == RESULT_OK) {
val files = PhotoUploadUtils.readResultToFiles(activityResult, contentResolver)
Log.i(this@LoveSpotDetailsActivity::class.simpleName, "Starting upload")
val result: Boolean = if (photoUploadReviewId != null) {
loveSpotPhotoService.uploadToReview(
loveSpotId,
photoUploadReviewId!!,
files,
this@LoveSpotDetailsActivity
)
} else {
loveSpotPhotoService.uploadToLoveSpot(
loveSpotId,
files,
this@LoveSpotDetailsActivity
)
}
photosLoaded = !result
photosRefreshed = !result
photoUploadReviewId = null
if (result) {
Log.i(
this@LoveSpotDetailsActivity::class.simpleName,
"Upload finished, starting refreshing views."
)
startPhotoRefreshSequence()
}
} else {
toaster.showToast(R.string.failed_to_access_photos)
}
}
}
fun readResultToFiles(
activityResult: ActivityResult,
contentResolver: ContentResolver
): List<File> {
val itemCount: Int = activityResult.data?.clipData?.itemCount ?: 0
val files = ArrayList<File>()
for (i in 0 until itemCount) {
val clipData = activityResult.data!!.clipData!!
val uri = clipData.getItemAt(i).uri
Log.i("uri", "$uri")
addToFilesFromUri(uri, files, contentResolver)
}
return files
}
private fun addToFilesFromUri(
uri: Uri,
files: ArrayList<File>,
contentResolver: ContentResolver
) {
val projection = arrayOf(MediaStore.MediaColumns.DATA)
contentResolver.query(uri, projection, null, null, null)
?.use { cursor ->
if (cursor.moveToFirst()) {
val columnIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DATA)
Log.i("columnIndex", "$columnIndex")
val filePath = cursor.getString(columnIndex)
Log.i("filePath", " $filePath")
if (filePath != null) {
val file = File(filePath)
Log.i("file", "$file")
files.add(file)
}
}
}
}

剩下的代码没问题。

最新更新