最稳妥的api错误返回方式是直接return json(),而非混用fail();fail()仅用于验证器或db异常抛出,不控制响应格式;应统一在基类封装apierror()并用异常处理器拦截所有异常转json。

ThinkPHP 6 的 json() 和 fail() 别混用
API 接口返回错误时,直接 return json(['code'=>1, 'msg'=>'xxx']) 是最稳妥的写法。别在控制器里调用 fail() —— 这是验证器(Validate)或查询构造器(Db::failException(true))抛异常用的,不是给 API 返回设计的接口。
常见错误现象:fail() 调用后页面空白、返回 HTML 错误页、或触发全局异常处理器却没走 JSON 流程,本质是它不控制响应格式,只影响执行流。
- 验证失败要返回 JSON?用
$validate->batch()->check($data)后手动判断,再return json() - 数据库操作失败?捕获
Db::table()->insert()的布尔返回值,而不是依赖fail()自动跳转 - 想统一错误结构?别改
fail()行为,改你的控制器基类或中间件
统一错误响应结构推荐用基类封装 apiError()
每个接口都手写 json(['code'=>-1, 'msg'=>'xxx', 'data'=>[]]) 容易漏字段、不一致。建议在 app\BaseController.php 里加一个方法:
protected function apiError(string $msg = '操作失败', int $code = -1, array $data = [])
{
return json([
'code' => $code,
'msg' => $msg,
'data' => $data,
'time' => time(),
]);
}
使用场景:参数校验不通过、业务规则拒绝(如余额不足)、第三方服务调用失败等非异常型错误。
- 不要把 PHP 致命错误或未捕获异常塞进这个方法 —— 那该交给
app\common\exception\Handler -
$code建议按业务分段:-1 系统错误,1001 起为业务码(如 1001=用户不存在,1002=token 过期) - 别在
apiError()里做日志记录 —— 日志应在调用处显式写,方便定位上下文
异常必须被拦截,否则返回的是 HTML 页面
TP6 默认开启 app_debug = true 时,未捕获异常会渲染调试页面;线上环境关了 debug,但没配异常处理器,就会返回空响应或 500 文本,前端收不到 JSON。
正确做法:在 app\common\exception\Handler.php 的 render() 方法里区分请求类型:
public function render(Request $request, Throwable $e): Response
{
if ($request->isAjax() || $request->header('accept') === 'application/json') {
return json([
'code' => -500,
'msg' => app()->isDebug() ? $e->getMessage() : '服务器开小差了',
'data' => [],
])->code(200); // 注意:这里用 200,避免前端 CORS 或状态码判断混乱
}
return parent::render($request, $e);
}
关键点:
-
$request->isAjax()并不可靠,有些 API 请求头没带X-Requested-With,所以补充accept判断更稳 - 状态码别盲目设 500 —— 前端常只监听 2xx,设成 500 可能被拦截器吞掉
- 开发环境暴露
$e->getMessage()没问题,但上线前务必删掉或替换为泛化提示
Validate 抛出的 ValidateException 怎么转 JSON
验证器默认抛 ValidateException,它继承自 think\Exception,会被上面的 Handler::render() 拦住。但它的 $e->getError() 是字符串(批量验证时是数组),需手动提取:
if ($e instanceof ValidateException) {
$errors = $e->getError();
$msg = is_array($errors) ? implode(';', $errors) : $errors;
return json(['code'=>1000, 'msg'=>$msg, 'data'=>[]])->code(200);
}
容易踩的坑:
- 没重写
render()就直接 throw new ValidateException(),结果返回 HTML 调试页 - 用了
scene()但没在验证器里定义对应场景,导致getError()为空字符串 - 在模型 validate() 中抛异常,但模型层不该负责响应格式 —— 异常仍应由控制器或异常处理器统一处理
Handler::render() 中对 Accept 头的判断逻辑,以及把所有错误都往 200 状态码塞——前者导致 H5/小程序调用失败不报错,后者让前端无法用 HTTP 状态码做基础分流。大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!











