Kotlin Retrofit Multipart上传文件至Laravel服务器遇401错误
使用Retrofit向Laravel后端API发送文件时遇到以下问题:
- 使用
@Body注解且不带文件时,数据能正常提交; - 使用
@Multipart注解时,始终返回401状态码; - Postman测试带文件的接口完全正常,问题出在Kotlin应用与Retrofit的实现上。
已尝试多种方式:不带文件的Multipart请求、仅发送文件,结果均返回401,无法定位问题根源。@Body能正常传输数据,但Multipart请求似乎无法正常传递数据。以下是相关代码及解决建议:
API接口定义
interface FarmSpaceApi { @POST("auth/register") fun RegisterUserApi(@Body user: User):Call<User> @POST("auth/login") fun LoginUserApi(@Body user: Userlogin):Call<LoginData> @Headers("Content-Type: application/json;charset=UTF-8") @POST("fields/show") fun getFields(@Body userId: UserId,@Header("Authorization")token:String):Call<Field2> @Multipart @Headers("Content-Type: application/json;charset=UTF-8") @POST("fields/create") fun addfield(@Header("Authorization") token:String, @Part image:MultipartBody.Part, @Part("fieldstype") fieldstype: RequestBody, @Part("field_size_in_ha") field_size_in_ha: RequestBody, @Part("harvest_in_this_year") harvest_in_this_year: RequestBody, @Part("user_id") user_id: RequestBody):Call<FieldNewResponse> }
点击事件处理代码
clicker.setOnClickListener { val tokenManager=TokenManager(this) val filepath=tokenManager.getfilename() val file= File(filepath) Log.d("File ", file.name.toString()) Log.d("fieldname ",fieldname.toString()) Log.d("fieldsize ",fieldsize.toString()) Log.d("fieldcurrent ",fieldcurrentyear.toString()) val request=RequestBody.create(MediaType.parse("image/*"),file) val filepart=MultipartBody.Part.createFormData("file",file.name,request) viewModel.sendfield( tokenManager.getTokenfromManager() ,filepart ,fieldname.toString() ,fieldsize.toString().toDouble() ,fieldcurrentyear.toString().toDouble() ,tokenManager.getidentyficator()!!.toInt()) }
文件路径获取代码
@Deprecated("Deprecated in Java") override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (resultCode == RESULT_OK && requestCode == 100) { imageuri = data?.data!! Log.d("Uri", imageuri.toString()) imagepreview.setImageURI(imageuri) val name: String = getNameFile(this, imageuri) Log.d("name: ", name) val tokenManager=TokenManager(this) tokenManager.addfilename(name) } } fun getNameFile(context: Context, uri: Uri): String { val projection: Array<String> = (arrayOf(MediaStore.Images.Media.DATA)) val cursor = context.contentResolver.query(uri, projection, null, null, null) val index: Int = cursor!!.getColumnIndexOrThrow(MediaStore.Images.Media.DATA) cursor.moveToFirst() val name: String = cursor.getString(index) return name }
ViewModel请求方法
fun sendfield(token:String,filepart:MultipartBody.Part,name:String,size:Double,year:Double,id:Int) { viewModelScope.launch { Log.d("token ", token) val retro = InstanceApi.getInstanceApiRetrofit().create(FarmSpaceApi::class.java) val newname= RequestBody.create(MediaType.parse("text/plain"),name) val newsize= RequestBody.create(MediaType.parse("text/plain"),size.toString()) val newyear= RequestBody.create(MediaType.parse("text/plain"),year.toString()) val newid= RequestBody.create(MediaType.parse("text/plain"),id.toString()) retro.addfield("Bearer " + token,filepart,newname,newsize,newyear,newid).enqueue(object : Callback<FieldNewResponse> { override fun onResponse(call: Call<FieldNewResponse>, response: Response<FieldNewResponse>) { Log.d("Git ", response.message().toString()) Log.d("Test ", response.body().toString()) Log.d("Testcode ", response.code().toString()) Log.d("Testerror ", response.errorBody().toString()) } override fun onFailure(call: Call<FieldNewResponse>, t: Throwable) { Log.d("Nie Git ", t.message.toString()) } }) } }
解决建议
移除Multipart请求的Content-Type头
@Multipart注解会自动生成正确的Content-Type: multipart/form-data; boundary=xxx请求头,手动添加application/json头会导致请求格式冲突,Laravel无法正确解析请求,进而触发token验证失败返回401。请删除addfield方法上的@Headers("Content-Type: application/json;charset=UTF-8")。修复文件路径获取逻辑
Android 10及以上版本中,MediaStore.Images.Media.DATA字段已被废弃,无法获取真实文件路径,直接用该路径创建File对象可能导致文件不存在。改用ContentResolver读取文件流:// 修改点击事件中的文件处理逻辑 val inputStream = contentResolver.openInputStream(imageuri) ?: return val fileBytes = inputStream.readBytes() inputStream.close() val request = RequestBody.create(MediaType.parse("image/*"), fileBytes) val filepart = MultipartBody.Part.createFormData("file", name, request)同时无需再保存文件路径,直接保存文件名即可。
验证Token拼接与有效性
确保"Bearer " + token中的空格存在,打印token内容与Postman中使用的token对比,确认token未过期且格式正确。检查Laravel后端验证逻辑
确认后端对Multipart请求的Authorization头处理逻辑是否与普通请求一致,是否存在特殊的验证规则导致token不被识别。
内容的提问来源于stack exchange,提问作者HeyDay

