使用 Retrofit 将图像列表上传到 Java Spring boot 服务器

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

所以我正在构建一个简单的 Android 应用程序,并尝试使用改造将图像列表上传到 Spring boot 服务器。 Retrofit 位于 kotlin 客户端。因此,我首先从手机图库中选择图像并将其显示在屏幕上。它们显示得很好。第二步是将它们上传到服务器上,这就是问题所在。首先,我将 imageUrls 转换为

MultipartBody.Part
对象:

var imageParts = ArrayList<MultipartBody.Part>()
        _imagesUiState.value.images.forEach {imageUrl ->
            val inputStream = context.contentResolver.openInputStream(imageUrl)
            try {
                inputStream?.let {
                    val extension = context.contentResolver.getType(imageUrl)?.let {
                        MimeTypeMap.getSingleton().getExtensionFromMimeType(it)
                    } ?: "jpg"
                    val file = File(context.cacheDir, "${System.currentTimeMillis()}.$extension")
                    val outputStream = FileOutputStream(file)
                    inputStream.copyTo(outputStream)
                    val requestFile = file.asRequestBody("image/*".toMediaTypeOrNull())
                    imageParts.add(MultipartBody.Part.createFormData("image", file.name, requestFile))

                    outputStream.close()
                    inputStream.close()
                }
            } catch (e: Exception) {
                Log.e("FILE_READ_ERROR", "Error reading file: ${e.message}")
            }
        }

然后我调用 API 并将图像与此代码中的一些其他数据一起传递:

viewModelScope.launch {
            try {
                Log.i("UPLOAD_PROPERTY", "Before making network request")

                val response = pManagerApiRepository.createProperty(
                    token = "Bearer ${userDetails.token}",
                    userId = userDetails.userId.toString(),
                    property = property,
                    images = imageParts
                )

                if (response.isSuccessful) {
                    val responseBody = response.body()
                    if (responseBody != null) {
                        Log.i("CREATE_PROPERTY_RESPONSE", "Status Code: ${responseBody.statusCode}, Message: ${responseBody.message}")
                    } else {
                        Log.i("CREATE_PROPERTY_RESPONSE", "Response body is null")
                    }
                } else {
                    Log.e("CREATE_PROPERTY_RESPONSE", "Unsuccessful response: ${response.code()}")
                }
            } catch (e: Exception) {
                Log.e("CREATE_PROPERTY_EXCEPTION", "Exception: ${e.message}")
            }
        }

for(part in imageParts) {
            Log.i("IMAGE_PARTS", part.toString())
        }

我尝试上传两张图片,所以两张

parts
看起来像这样:

okhttp3.MultipartBody$Part@c8aade3
okhttp3.MultipartBody$Part@89963e0

这是用于发送整个数据的客户端 API 端点:

@Multipart
    @POST("api/property/userId={id}/create")
    suspend fun uploadPropertyDetails(
        @Header("Authorization") token: String,
        @Path("id") userId: String,
        @Part("data") property: Property,
        @Part imageFiles: List<MultipartBody.Part>
    ): Response<PropertyUploadResponse>

这就是我进行改造的方式:

class DefaultContainer(context: Context): AppContainer {
    private val json = Json { ignoreUnknownKeys = true }

    private val baseUrl = "http://192.168.246.6:8080/pManager/"
    private val retrofit = Retrofit.Builder()
        .baseUrl(baseUrl)
        .addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
        .build()

    private val retrofitService: PMangerApiService by lazy {
        retrofit.create(PMangerApiService::class.java)
    }
    override val pMangerApiRepository: PMangerApiRepository by lazy {
        NetworkPManagerApiRepository(retrofitService)
    }

}

服务器端API端点是:

@PostMapping(value = "property/userId={userId}/create", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
    public ResponseEntity<Response> addProperty(@RequestPart(value = "data") @Valid PropertyDTO propertyDTO,@PathVariable int userId,
                                                @RequestParam (value = "imageFiles", required = false) MultipartFile[] imageFiles) throws IOException {
        System.out.println("RESPONSE");
        System.out.println();
        System.out.println(propertyDTO.toString());
        System.out.println();
        System.out.println("IMAGE FILES:");
        for(MultipartFile file : imageFiles) {
            System.out.println(file);
        }

        System.out.println();
        return buildResponse("property",propertyService.createProperty(propertyDTO, imageFiles, userId), "Created successfully", HttpStatus.CREATED);
    }

我在服务器控制台中收到此错误:

java.lang.NullPointerException: Cannot read the array length because "<local4>" is null

imageFiles
为空

property
发布成功但图片同时失败,错误信息如上所示。

但是,当我从邮递员调用同一端点时,图像上传成功。

imageFiles
是:

org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile@3eb7d8a7
org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile@3a9c47fc

所以基本上我正在尝试将表单数据上传到 Spring boot 服务器。如果由邮递员完成,则数据发布成功。但是,如果通过移动应用程序完成请求,则会失败。其实只是照片没有发送,因为

property
已经发布成功了。

spring-boot kotlin android-jetpack-compose retrofit multipartform-data
1个回答
0
投票

当我更新此行时它起作用了:

imageParts.add(MultipartBody.Part.createFormData(name = "image", file.name, requestFile))

新线路:

imageParts.add(MultipartBody.Part.createFormData(name = "imageFiles", file.name, requestFile))

基本上,kotlin 客户端中

name
createFormData
参数应该与 java spring boot api 端点中
value
部分 (
@RequestPart
) 的
images
注释的
imageFiles
参数相匹配。这是 Spring boot 的终点:

@PostMapping(value = "property/userId={userId}/create", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
    public ResponseEntity<Response> addProperty(@RequestPart(value = "data") @Valid PropertyDTO propertyDTO,@PathVariable int userId,
                                                @RequestPart (value = "imageFiles", required = false) MultipartFile[] imageFiles) throws IOException {

        System.out.println();
        return buildResponse("property",propertyService.createProperty(propertyDTO, imageFiles, userId), "Created successfully", HttpStatus.CREATED);
    } 

这是创建

MultipartBody.Part
对象的客户端代码:

var imageParts = ArrayList<MultipartBody.Part>()
        _imagesUiState.value.images.forEach {imageUrl ->
            Log.i("IMAGE_PATH", imageUrl.path)
            val imageFile = File(imageUrl.path)
            val requestFile = imageFile.asRequestBody("image/*".toMediaTypeOrNull())
            val imagePart = MultipartBody.Part.createFormData(name = "imageFiles", imageFile.name, requestFile)
            imageParts.add(imagePart)
        }
© www.soinside.com 2019 - 2024. All rights reserved.