Flutter对接PHP登录接口报FormatException解析异常如何解决
问题表现
开发Flutter登录功能对接PHP后端接口时,触发未捕获异常导致登录流程中断:
- 控制台抛出
FormatException,提示响应内容第1个字符为非法字符 - 日志显示接口返回内容开头存在
<br />HTML标签,导致jsonDecode方法解析响应体失败
核心报错日志如下:
E/flutter ( 6084): [ERROR:flutter/lib/ui/ui_dart_state.cc(198)]
Unhandled Exception: FormatException: Unexpected character (at character 1)
E/flutter ( 6084):
E/flutter ( 6084): ^
E/flutter ( 6084):
E/flutter ( 6084): #0 _ChunkedJsonParser.fail (dart:convert-patch/convert_patch.dart:1383:5)
E/flutter ( 6084): #1 _ChunkedJsonParser.parseNumber (dart:convert-patch/convert_patch.dart:1250:9)
E/flutter ( 6084): #2 _ChunkedJsonParser.parse (dart:convert-patch/convert_patch.dart:915:22)
E/flutter ( 6084): #3 _parseJson (dart:convert-patch/convert_patch.dart:35:10)
E/flutter ( 6084): #4 JsonDecoder.convert (dart:convert/json.dart:612:36)
E/flutter ( 6084): #5 JsonCodec.decode (dart:convert/json.dart:216:41)
E/flutter ( 6084): #6 jsonDecode (dart:convert/json.dart:155:10)
E/flutter ( 6084): #7 Login.login (package:mes_v1/pages/Authentification/login.dart:25:18)
问题根因
报错核心原因是PHP接口返回内容混入了HTML格式的错误提示,不是纯JSON格式,同时前后端代码存在多处不匹配和逻辑漏洞:
- PHP端问题:
- 变量名不一致:接收表单密码参数时赋值给
$passworda,但SQL拼接时使用未定义的$password变量,PHP会自动抛出Notice级错误,错误内容默认以<br />标签开头,直接输出在JSON响应前 - 未提前设置JSON响应头,未关闭前端错误输出,无法保证返回内容为纯JSON格式
- 直接拼接用户输入到SQL语句,存在严重SQL注入风险
- 变量名不一致:接收表单密码参数时赋值给
- Flutter端问题:
- 参数字段不匹配:POST请求传的密码字段是
pass,和PHP端接收的passworda字段名不一致 - 请求头拼写错误:
Accept头值将application/json错写为applicarion/json - 无异常捕获和响应校验逻辑:未判断HTTP请求状态,未捕获JSON解析异常,遇到非预期响应直接崩溃
- 参数字段不匹配:POST请求传的密码字段是
修复方案
PHP端修复
首先统一响应格式,修正变量名错误,关闭前端错误输出,改用预处理语句防范SQL注入,修复后代码如下:
<?php // 强制设置JSON响应头,指定编码 header('Content-Type: application/json; charset=utf-8'); // 关闭前端错误显示,避免HTML格式错误信息混入JSON响应,生产环境建议开启日志记录错误 ini_set('display_errors', 0); error_reporting(0); // 补全自己的数据库连接逻辑,保证$db为有效的mysqli连接实例 $email = isset($_POST['email']) ? trim($_POST['email']) : ''; $password = isset($_POST['passworda']) ? trim($_POST['passworda']) : ''; // 参数为空直接返回错误 if (empty($email) || empty($password)) { echo json_encode("error"); exit; } // 使用预处理语句查询,避免SQL注入 $stmt = mysqli_prepare($db, "SELECT * FROM user WHERE email = ? AND passworda = ?"); mysqli_stmt_bind_param($stmt, "ss", $email, $password); mysqli_stmt_execute($stmt); $result = mysqli_stmt_get_result($stmt); $count = mysqli_num_rows($result); if ($count >= 1) { echo json_encode("success"); } else { echo json_encode("error"); } exit; ?>
Flutter端修复
修正请求参数名、请求头拼写错误,增加HTTP状态校验和异常捕获逻辑,修复后代码如下:
import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:http/http.dart' as http; class Login extends StatelessWidget { final TextEditingController email = TextEditingController(); final TextEditingController password = TextEditingController(); Future login(BuildContext cont) async { if (email.text.isEmpty || password.text.isEmpty) { Fluttertoast.showToast( msg: "请填写完整登录信息!", toastLength: Toast.LENGTH_SHORT, gravity: ToastGravity.CENTER, fontSize: 16.0, ); return; } // 捕获所有请求、解析环节的异常,避免页面崩溃 try { var url = "http://192.168.43.150/v1_flutter/lib/php/connection.php"; var response = await http.post( Uri.parse(url), body: { "email": email.text.trim(), // 字段名和PHP端保持一致 "passworda": password.text.trim(), }, // 修正Accept头拼写错误 headers: {"Accept": "application/json"}, ); // 先校验HTTP响应状态 if (response.statusCode != 200) { Fluttertoast.showToast( msg: "网络请求异常,请稍后重试", toastLength: Toast.LENGTH_SHORT, gravity: ToastGravity.CENTER, fontSize: 16.0, ); return; } // 状态正常再解析JSON var data = jsonDecode(response.body); if (data == "success") { Navigator.pop(cont); Navigator.pushNamed(cont, "/registre"); } else { Fluttertoast.showToast( msg: "账号或密码错误!", toastLength: Toast.LENGTH_SHORT, gravity: ToastGravity.CENTER, fontSize: 16.0, ); } } catch (e) { Fluttertoast.showToast( msg: "登录失败,请稍后重试", toastLength: Toast.LENGTH_SHORT, gravity: ToastGravity.CENTER, fontSize: 16.0, ); } } @override Widget build(BuildContext context) { // 保留原有登录页布局代码即可 return Scaffold(); } }
额外注意事项
- 禁止明文存储用户密码,生产环境必须使用
password_hash等不可逆哈希算法处理密码后存储,比对时使用password_verify验证 - Android 9及以上版本默认禁止HTTP明文请求,调试时需要在应用配置中放开明文网络权限,生产环境建议改用HTTPS协议
- 建议统一接口返回结构,例如固定返回
{"code":业务状态码,"msg":"提示信息","data":业务数据}格式,方便前端做统一拦截处理
内容的提问来源于stack exchange,提问作者Fath MOUNASSIF

