TP8中间件须在AppInit前且param()调用前读取rawInput,推荐用getRawContent()并按Content-Type分支解析,解析后需merge并setPost同步缓存,再结构化清洗字段。

TP8 中间件必须在 AppInit 阶段前读取 rawInput
JSON 请求的 php://input 只能读一次,而 ThinkPHP 的 $request->param() 默认会在第一次调用时自动解析并缓存结果。一旦 param() 被触发(比如路由匹配、验证器提前加载),rawInput 就变为空,后续中间件再调用 file_get_contents('php://input') 或 $request->getRawContent() 会返回空字符串。
所以处理 rawInput 的中间件必须注册在 app/middleware.php 的 'http' 数组中,并确保它排在所有可能触发 param() 的中间件之前——最稳妥的位置是放在数组第一个,或显式设置 priority => -100。
- 别等控制器里再读:
$this->request->getRawContent()在中间件之后调用已无效 - 别在
AppInit事件里做:此时$request实例尚未完全初始化,getRawContent()可能抛出异常 - TP8.0+ 推荐统一用
$request->getRawContent(),它内部已处理流重置逻辑;低版本 TP6.x 建议 fallback 到file_get_contents('php://input')
区分 Content-Type 后解析 rawInput 才有效
rawInput 内容格式取决于前端传的 Content-Type,不能一概 json_decode。中间件里必须先判断 header,再分支解析:
-
application/json→json_decode($raw, true) ?: [] -
application/x-www-form-urlencoded或multipart/form-data→ 不应直接解析 rawInput,而是走框架原生流程:$request->param()已足够,强行解析反而绕过文件上传等逻辑 - 没设 Content-Type 或为
text/plain→ 按需处理,常见于调试或自定义协议,建议记录原始字符串而非尝试 decode
错误示例:json_decode($request->getRawContent(), true) 直接套用在表单请求上,会导致 null,后续 merge 失败。
立即学习“PHP免费学习笔记(深入)”;
merge() 之后还要同步 setPost() 才能覆盖 post() 缓存
即使你成功解析 rawInput、清洗字段、调用 $request->merge($cleaned),控制器里若仍写 $this->request->post(),大概率拿到的是旧值——因为 post() 是只读缓存,首次调用即固化,merge() 完全不影响它。
- TP6.0.13+ 和 TP8.0 支持
$request->setPost($cleaned),必须在中间件末尾显式调用 - 检查是否可用:打开
thinkphp/library/think/Request.php,搜索public function setPost - 若不支持该方法,唯一安全做法是全程弃用
post(),只用param()(它受merge()影响) - 切勿在控制器里先调
post()再改参数,顺序错乱会导致部分逻辑读旧值、部分读新值
空值归一化和白名单 trim 必须在解析后立即做
rawInput 解析出来的数组,往往混杂 null、""、" "、0、false 等值。校验规则对它们行为不一致,所以清洗不能只靠 trim(),得结构化处理:
- 对每个字段执行:
is_null($v) ? '' : (is_string($v) ? trim($v) : $v) - trim 仅限白名单字段(如
['name', 'email', 'mobile']),跳过sign、token、富文本、base64 字段 - 保留数字
0、字符串"0"、布尔false,避免误删有效值 - 合并 query 参数:
array_merge($request->get(), $parsedBody),否则带 URL 参数的 POST 请求会丢?page=2这类值
这步看似简单,但漏掉 is_null 判断或盲目 array_filter,就会让 0 和 false 消失——这是线上最常被忽略的兼容性断点。



















