ThinkPHP validate()默认不校验请求头和JSON数据,需手动提取header、调用json(true),签名字段须提前剔除,灰度验证应使用condition声明式控制并禁用单例。

validate() 默认不校验请求头,必须手动提取
ThinkPHP 的 validate() 方法只处理 $request->param() 返回的数据,完全忽略 $request->header()。把 'header.token'=>'require' 这类规则写进验证器,根本不会触发校验。
正确做法是先单独取值,再合并进参数数组:
- 用
$token = $request->header('Authorization', '')或$appVersion = $request->header('X-App-Version', '')显式获取关键头信息 - 构造统一输入:
$input = array_merge($request->param(), ['token' => $token, 'app_version' => $appVersion]) - 再传给
$this->validate($input, $rule)—— 否则 Authorization、签名时间戳等关键因子永远验不到
JSON 请求体必须提前调用 $request->json(true)
ThinkPHP 对 JSON 是惰性解析:不主动调用 $request->json() 或 $request->json(true),$request->param() 就不会自动合并 JSON 字段。前端发了 {"user_id":123},$this->validate($request->param(), $rule) 却返回空数组,验证形同虚设。
两种可靠方式:
立即学习“PHP免费学习笔记(深入)”;
- 控制器开头加一行:
$this->request->json(true);(推荐,简单直接) - 中间件里统一执行:
$request->json(true);,确保所有接口一致 - 注意:
$request->json(true)会强制解析并合并到param(),后续param()才能拿到 JSON 字段;不加true参数只返回原始 JSON 数组,不合并
签名字段必须在拼接前剔除,否则循环依赖
验签时若把 sign、signature、sign_type 这类字段参与签名原文拼接,会导致“自己签自己”,永远验不过。
中间件中取参后务必立即清理:
- 表单/GET 混合请求:
$params = array_merge($request->get(), $request->post()); unset($params['sign'], $params['signature'], $params['sign_type']); - JSON 请求:
$body = json_decode($request->getContent(), true) ?: []; $params = array_merge($request->get(), $body); unset($params['sign']); - 更稳妥的做法是基于原始流:
$raw = file_get_contents('php://input');解析后手动剔除签名字段,避免框架自动转义干扰字节一致性
灰度校验不能靠 if-else 硬写,得用 condition 声明式控制
在控制器里写 if ($user->isInGrayGroup()) { $this->validate($data, 'NewRule'); } else { $this->validate($data, 'OldRule'); },看似可行,实则埋雷:规则耦合业务逻辑、无法复用、难以测试、上线后改个开关就得动代码。
ThinkPHP 6.1+ 支持字段级 condition,这才是生产级灰度的写法:
- 验证器 rule 中直接声明:
'price' => ['require', 'number', 'between:0,10000', 'condition' => function () { return $this->grayEnabled; }] - 构造函数里注入灰度状态:
$this->grayEnabled = $this->app->context['gray'] ?? false; - 禁用单例:
'validate_single' => false或显式new MyValidator(),否则第一次判断结果会被缓存,后续请求全走错分支
真正容易被忽略的是验证器实例复用问题——它不像日志或配置那样显眼,但一旦复用,灰度开关就彻底失效,且很难定位。



















