You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin Retrofit Multipart上传文件至Laravel服务器遇401错误

Retrofit多部分请求返回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())
            }
        })
    }
}

解决建议

  1. 移除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")。

  2. 修复文件路径获取逻辑
    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)
    

    同时无需再保存文件路径,直接保存文件名即可。

  3. 验证Token拼接与有效性
    确保"Bearer " + token中的空格存在,打印token内容与Postman中使用的token对比,确认token未过期且格式正确。

  4. 检查Laravel后端验证逻辑
    确认后端对Multipart请求的Authorization头处理逻辑是否与普通请求一致,是否存在特殊的验证规则导致token不被识别。

内容的提问来源于stack exchange,提问作者HeyDay

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 06:31:12