我在后端有一份合同,如下图所示:
如您所见,后端需要每个多部分/表单数据合约一个图像文件。
我的后端工作完美,因为当我通过邮递员/失眠者将图像作为文件发送来测试服务时,图像确实已注册。
当我通过Android应用程序发送图像时,您可以看到它实际上正在发送,如下图所示:
但由于某种原因,这个参数在我的后端总是显示为空。再说一次,当图像是由模拟 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>
}
解决方案比看起来更简单:
createFormData
的 MultipartBody.Part
方法的第一个参数与后端期望的参数具有相同的值就足够了(它的工作方式类似于 gson 的 @SerializedName
)。因此,在我的情况下,后端参数是“照片”,我唯一应该做的就是调用如下方法:
return MultipartBody.Part.createFormData(
"phto",
file.name,
requestBody
)
在我打电话之前(失败):
return MultipartBody.Part.createFormData(
"image",
file.name,
requestBody
)