Flutter新手求助:flutter_uploader上传PHP端接收与响应问题
核心错误定位
你遇到的所有问题本质上都是由一个配置错误引发的:你在上传请求头中手动设置了Content-Type: application/json; charset=UTF-8,直接覆盖了插件自动生成的multipart表单请求头。
注意:multipart/form-data类型的请求必须携带自动生成的boundary分隔符参数,插件会在构造请求时自动生成符合规范的
Content-Type: multipart/form-data; boundary=xxx头,手动设置其他类型的Content-Type会导致PHP无法按照表单规则解析请求体,直接造成$_POST为空、$_FILES为空、php://input读取到的原始内容带多余分隔符等连锁问题。
分步修复方案
1. 修正Flutter端上传配置
直接删除headers中手动配置的Content-Type字段,仅保留业务需要的鉴权头即可,修正后的上传代码片段:
_taskId = await _uploaderService.enqueue( MultipartFormDataUpload( url: '你的PHP接口地址', files: [FileItem(path: '本地图片完整路径', field: "userfile")], method: UploadMethod.POST, headers: { // 删掉Content-Type配置,让插件自动生成 'Authorization': 'Bearer $token', }, data: { "groupId": "87878" }, tag: filename, ), ); _uploaderService.result.listen((result) { // 调试阶段先打印全量结果,方便定位问题 print('上传状态码:${result.statusCode}'); print('上传响应内容:${result.response}'); print('上传错误信息:${result.error}'); if (result.statusCode == 200 && result.response != null) { try { dynamic body = jsonDecode(result.response!); print('响应解析结果:$body'); } catch (e) { print('JSON解析失败,响应内容不是合法JSON:$e'); } _uploaderService.clearUploads(); } }, onError: (ex, stacktrace) { print('上传抛出异常:$ex'); print('异常堆栈:$stacktrace'); _taskId = null; _uploaderService.clearUploads(); });
额外注意:result流是全局单例,不要每次触发上传都重复注册监听,建议在应用初始化阶段只注册一次监听,避免响应事件被重复消费导致回调不触发。
2. 修正PHP端接收逻辑
修正Content-Type后,PHP会自动解析multipart请求,填充$_POST和$_FILES超全局变量,你之前拿不到文件还有一个原因是字段名不匹配:Flutter端设置的文件字段名是userfile,你之前代码里写的是fileToUpload,自然读不到内容。
可直接使用的PHP端代码示例:
<?php // 脚本开头先关闭页面错误输出,避免非JSON内容污染响应 ini_set('display_errors', 0); error_reporting(E_ALL); ini_set('log_errors', 1); ini_set('error_log', __DIR__ . '/upload_error.log'); // 设置响应头为JSON格式 header('Content-Type: application/json; charset=utf-8'); // 初始化返回结构 $res = [ 'code' => 0, 'msg' => '', 'data' => [] ]; // 创建上传存储目录,提前给目录设置0755可写权限 $saveDir = __DIR__ . '/upload_files/'; if (!is_dir($saveDir)) { mkdir($saveDir, 0755, true); } // 读取普通表单参数,data字段传的内容直接通过$_POST获取 $groupId = isset($_POST['groupId']) ? trim($_POST['groupId']) : ''; if (empty($groupId)) { $res['code'] = -1; $res['msg'] = '缺少必填参数groupId'; echo json_encode($res); exit; } // 读取上传文件,字段名和Flutter端FileItem的field参数一致 if (!isset($_FILES['userfile'])) { $res['code'] = -2; $res['msg'] = '未接收到上传文件'; echo json_encode($res); exit; } $uploadFile = $_FILES['userfile']; // 校验上传错误 if ($uploadFile['error'] !== UPLOAD_ERR_OK) { $res['code'] = -3; $res['msg'] = '文件上传失败,错误码:' . $uploadFile['error']; echo json_encode($res); exit; } // 生成不重复的存储文件名 $ext = pathinfo($uploadFile['name'], PATHINFO_EXTENSION); $saveName = uniqid('img_') . '.' . $ext; $savePath = $saveDir . $saveName; // 移动临时文件到正式存储路径 if (move_uploaded_file($uploadFile['tmp_name'], $savePath)) { $res['code'] = 200; $res['msg'] = '上传成功'; $res['data'] = [ 'groupId' => $groupId, 'fileUrl' => '/upload_files/' . $saveName, 'fileName' => $uploadFile['name'] ]; } else { $res['code'] = -4; $res['msg'] = '文件保存失败,请检查存储目录权限'; } // 输出纯JSON内容,不要在这之后输出任何其他字符 echo json_encode($res); exit; ?>
之前你用file_get_contents('php://input')拿到的是带multipart分隔符的原始请求体,直接写入文件会得到损坏的无效文件,Content-Type配置修正后,不需要再手动解析原始请求体,直接用$_POST和$_FILES即可。
3. 响应回调不触发的排查点
- 确保PHP脚本输出的是纯JSON内容,不要有BOM头、PHP报错信息、调试打印内容等多余字符,否则会导致Flutter端JSON解析失败,甚至插件无法正常读取响应。
- 确保PHP接口返回的状态码是200,其他状态码会被插件判定为请求异常,走到onError逻辑。
- 检查服务器是否有跨域限制,如果是跨域场景,需要在PHP脚本开头加上跨域头:
header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Methods: POST, OPTIONS'); header('Access-Control-Allow-Headers: Authorization, Content-Type'); // 处理OPTIONS预检请求 if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(200); exit; }
验证顺序
- 修改Flutter代码删除错误的Content-Type配置,重新编译运行
- 给PHP端的
upload_files目录设置可写权限 - 直接访问PHP接口地址,确认输出的是合法JSON格式内容,没有PHP语法错误
- 触发上传,查看调试控制台打印的全量结果,对照错误信息排查剩余问题
内容的提问来源于stack exchange,提问作者RoB2022

