通过改造发送多部分文件格式的图像永远不会到达后端

问题描述 投票:0回答:1

背景

我在后端有一份合同,如下图所示:

enter image description here

如您所见,后端需要每个多部分/表单数据合约一个图像文件。

我的后端工作完美,因为当我通过邮递员/失眠者将图像作为文件发送来测试服务时,图像确实已注册。

问题

当我通过Android应用程序发送图像时,您可以看到它实际上正在发送,如下图所示:

enter image description here

但由于某种原因,这个参数在我的后端总是显示为空。再说一次,当图像是由模拟 http 请求的程序(如邮递员或 insomnia)发送时,它可以正常工作。

我的代码

我正在为我的改造实例提供刀柄:


import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import java.util.concurrent.TimeUnit
import javax.inject.Singleton
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory


@Module
@InstallIn(SingletonComponent::class)
object RetrofitModule {

    private const val BASE_URL = "http://10.0.2.2:8080"
    private const val TIMEOUT_MINUTES = 3L

    @Provides
    @Singleton
    fun providesOkHttpClient(): OkHttpClient = OkHttpClient.Builder()
        .connectTimeout(TIMEOUT_MINUTES, TimeUnit.MINUTES)
        .readTimeout(TIMEOUT_MINUTES, TimeUnit.MINUTES)
        .writeTimeout(TIMEOUT_MINUTES, TimeUnit.MINUTES)
        .build()

    @Provides
    @Singleton
    fun providesRetrofit(okHttpClient: OkHttpClient): Retrofit = Retrofit.Builder()
        .baseUrl(BASE_URL)
        .addConverterFactory(GsonConverterFactory.create())
        .client(okHttpClient)
        .build()
}

我用来生成此图像的代码如下:

import android.net.Uri
import okhttp3.MultipartBody

interface MultipartImageProvider {

    fun getImage(uri: Uri): MultipartBody.Part
}
import android.content.ContentResolver
import android.content.Context
import android.net.Uri
import com.blitzsplit.create_group.domain.model.MultipartImageProvider
import dagger.hilt.android.qualifiers.ApplicationContext
import java.io.File
import java.io.FileOutputStream
import javax.inject.Inject
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.RequestBody.Companion.asRequestBody

class AndroidMultipartImageProvider @Inject constructor(
    @ApplicationContext private val context: Context,
    private val contentResolver: ContentResolver,
) : MultipartImageProvider  {

    override fun getImage(uri: Uri): MultipartBody.Part {
        val fileDir = context.filesDir
        val file = File(fileDir, "image.png")
        val inputStream = contentResolver.openInputStream(uri)
        val outputStream = FileOutputStream(file)
        inputStream?.copyTo(outputStream)
        val requestBody = file.asRequestBody(MEDIA_TYPE_IMAGE.toMediaTypeOrNull())
        return MultipartBody.Part.createFormData(
            "image",
            file.name,
            requestBody
        )
    }


    companion object {
        private const val MEDIA_TYPE_IMAGE = "image/*"
    }
}
data class CreateGroupRequestModel(
    val userId: String,
    val name: String,
    val image: MultipartBody.Part?,
)
class CreateGroupUseCase @Inject constructor(
    private val createGroupRepository: CreateGroupRepository,
    private val userRepository: UserRepository,
    private val loadGroups: LoadGroupsUseCase,
    private val multipartImageProvider: MultipartImageProvider,
) {
    suspend operator fun invoke(
        groupName: String,
        groupImageType: GroupImageType,
    ): Result<Unit> = Result.runCatching {
        createGroupRepository.createGroup(
            CreateGroupRequestModel(
                userId = userRepository.getUser()?.id ?: throw IllegalStateException("User not found"),
                name = groupName,
                image = (groupImageType as? GroupImageType.Loaded)?.let {
                    multipartImageProvider.getImage(it.uri)
                }
            )
        ).onSuccess {
            loadGroups()
        }
    }
}
interface CreateGroupRepository {

    suspend fun createGroup(
        requestModel: CreateGroupRequestModel
    ) : Result<Unit>
}
class CreateGroupRepositoryImpl @Inject constructor(
    private val dataSource: CreateGroupDataSource,
) : CreateGroupRepository {

    override suspend fun createGroup(
        requestModel: CreateGroupRequestModel
    ): Result<Unit> = dataSource.creteGroup(
        requestModel
    )
}
class CreateGroupDataSource @Inject constructor(
    private val api: CreateGroupApi,
    private val networkDataSource: NetworkDataSource,
) {
    suspend fun creteGroup(
        requestModel: CreateGroupRequestModel
    ): Result<Unit> = networkDataSource.call {
        api.createGroup(
            id = requestModel.userId,
            photo = requestModel.image,
            name = requestModel.name.toMultipartBody()
        )
    }
}
import com.quare.blitzsplit.utils.Endpoints
import okhttp3.MultipartBody
import retrofit2.Response
import retrofit2.http.Header
import retrofit2.http.Multipart
import retrofit2.http.POST
import retrofit2.http.Part

interface CreateGroupApi {

    @Multipart
    @POST(Endpoints.GROUPS)
    suspend fun createGroup(
        @Header("userId") id: String,
        @Part photo: MultipartBody.Part?,
        @Part name: MultipartBody.Part,
    ): Response<Unit>
}
import retrofit2.Response

interface NetworkDataSource {

    suspend fun <T> call(callback: suspend () -> Response<T>): Result<T>
}
android kotlin retrofit multipartfile
1个回答
0
投票

解决方案比看起来更简单:

createFormData
MultipartBody.Part
方法的第一个参数与后端期望的参数具有相同的值就足够了(它的工作方式类似于 gson 的
@SerializedName
)。因此,在我的情况下,后端参数是“照片”,我唯一应该做的就是调用如下方法:

    return MultipartBody.Part.createFormData(
        "phto",
        file.name,
        requestBody
    )

在我打电话之前(失败):

    return MultipartBody.Part.createFormData(
        "image",
        file.name,
        requestBody
    )

enter image description here

© www.soinside.com 2019 - 2024. All rights reserved.