Flutter http.post提交数据超时但数据仍插入的问题优化咨询
优化方案
你遇到的是典型的「请求已被服务器处理,但客户端因超时未收到响应」的场景,核心原因是POST请求非幂等+无法确认服务器处理状态。以下是针对性的代码优化方案:
1. 实现幂等请求(核心解决方案)
给每个请求生成唯一标识(Request ID),服务器先校验该标识是否已处理,从根源避免重复插入。
Flutter端修改:
生成唯一UUID作为请求ID,随请求体发送:
import 'package:uuid/uuid.dart'; postData() async { const url = 'https://www.testdomain.com/app/submit.php'; // 生成唯一请求ID final requestId = const Uuid().v4(); try { final response = await http.post(Uri.parse(url), body: { "id": user, "notes": notesController.text, "request_id": requestId // 添加唯一请求ID }).timeout(const Duration(seconds: 4), onTimeout: () { return http.Response('Error', 408); }); if (response.statusCode == 408) { // 超时后触发状态校验,见方案2 _checkRequestStatus(requestId); } else if (response.statusCode == 200) { final auth = json.decode(response.body); if (!mounted) return; setState(() { success = auth['success'].toString(); status = auth['status'].toString(); }); } else { if (!mounted) return; setState(() { status = '1'; }); } } catch (e) { if (!mounted) return; setState(() { status = '1'; }); } }
PHP端修改(submit.php):
先校验请求ID是否已处理,未处理再执行插入:
<?php $conn = mysqli_connect("localhost", "user", "pass", "db"); $requestId = $_POST['request_id']; $userId = $_POST['id']; $notes = $_POST['notes']; // 校验请求ID是否已存在 $checkQuery = "SELECT id FROM submissions WHERE request_id = ?"; $stmt = mysqli_prepare($conn, $checkQuery); mysqli_stmt_bind_param($stmt, "s", $requestId); mysqli_stmt_execute($stmt); mysqli_stmt_store_result($stmt); if (mysqli_stmt_num_rows($stmt) > 0) { // 请求已处理,直接返回成功状态 echo json_encode([ 'success' => '1', 'status' => '0', 'message' => '请求已处理' ]); mysqli_stmt_close($stmt); mysqli_close($conn); exit; } // 未处理则执行插入 $insertQuery = "INSERT INTO submissions (user_id, notes, request_id) VALUES (?, ?, ?)"; $stmt = mysqli_prepare($conn, $insertQuery); mysqli_stmt_bind_param($stmt, "sss", $userId, $notes, $requestId); if (mysqli_stmt_execute($stmt)) { echo json_encode([ 'success' => '1', 'status' => '0', 'message' => '提交成功' ]); } else { echo json_encode([ 'success' => '0', 'status' => '1', 'message' => '提交失败' ]); } mysqli_stmt_close($stmt); mysqli_close($conn); ?>
2. 超时后主动校验请求状态
客户端触发超时后,不直接提示失败,而是发起查询请求确认服务器是否已处理该请求:
Flutter端新增校验方法:
_checkRequestStatus(String requestId) async { const checkUrl = 'https://www.testdomain.com/app/check_status.php'; try { final response = await http.post(Uri.parse(checkUrl), body: { "request_id": requestId }).timeout(const Duration(seconds: 3)); if (response.statusCode == 200) { final result = json.decode(response.body); if (!mounted) return; setState(() { success = result['success'].toString(); status = result['status'].toString(); }); } else { if (!mounted) return; setState(() { status = '1'; }); } } catch (e) { if (!mounted) return; setState(() { status = '1'; }); } }
PHP端新增check_status.php:
<?php $conn = mysqli_connect("localhost", "user", "pass", "db"); $requestId = $_POST['request_id']; $query = "SELECT success FROM submissions WHERE request_id = ?"; $stmt = mysqli_prepare($conn, $query); mysqli_stmt_bind_param($stmt, "s", $requestId); mysqli_stmt_execute($stmt); mysqli_stmt_bind_result($stmt, $success); if (mysqli_stmt_fetch($stmt)) { echo json_encode([ 'success' => $success, 'status' => '0' ]); } else { echo json_encode([ 'success' => '0', 'status' => '1', 'message' => '请求未找到' ]); } mysqli_stmt_close($stmt); mysqli_close($conn); ?>
3. 服务器端优化响应及时性
确保服务器处理完请求后立刻返回响应,避免输出缓冲导致的响应延迟:
在PHP代码的响应输出后添加缓冲刷新代码:
// 输出响应内容后添加 ob_flush(); flush();
注:需确保服务器output_buffering配置允许手动刷新,部分主机可能禁用该功能。
4. 带幂等的有限重试策略
若需要重试,必须基于幂等请求,避免重复插入:
postData({int retryCount = 0}) async { const maxRetries = 2; const url = 'https://www.testdomain.com/app/submit.php'; final requestId = const Uuid().v4(); try { final response = await http.post(Uri.parse(url), body: { "id": user, "notes": notesController.text, "request_id": requestId }).timeout(const Duration(seconds: 4), onTimeout: () { return http.Response('Error', 408); }); if (response.statusCode == 408) { if (retryCount < maxRetries) { // 重试,递增重试次数 postData(retryCount: retryCount + 1); } else { // 重试耗尽,触发状态校验 _checkRequestStatus(requestId); } } else { // 正常响应处理逻辑 if (response.statusCode == 200) { final auth = json.decode(response.body); if (!mounted) return; setState(() { success = auth['success'].toString(); status = auth['status'].toString(); }); } else { if (!mounted) return; setState(() { status = '1'; }); } } } catch (e) { if (retryCount < maxRetries) { postData(retryCount: retryCount + 1); } else { if (!mounted) return; setState(() { status = '1'; }); } } }
内容的提问来源于stack exchange,提问作者Tom
相关产品推荐
相关产品推荐

