PHP 接口开发规范:统一返回格式、异常处理与参数校验
在中大型 PHP 项目中,接口的一致性直接决定了前端联调效率、问题排查速度和系统可维护性。很多团队在初期"怎么快怎么写",结果后期陷入"一个接口一种返回格式"的泥潭。
本文将围绕 统一返回格式、全局异常处理、参数校验 三大核心,给出一套可落地的 PHP 接口开发规范,适用于 Laravel、ThinkPHP、Hyperf、原生 PHP 等主流框架。
一、为什么需要接口规范?
混乱接口的常见症状:
-
有的返回
{code:0, data:{}},有的返回{error_code:200, result:{}} -
成功返回 HTTP 200,业务失败却返回 HTTP 500
-
错误信息一会儿是中文,一会儿是英文
-
参数错误只返回 "error",没有字段名
-
异常直接抛给前端,暴露 SQL、路径等敏感信息
后果:
-
前端需要为不同接口写不同解析逻辑
-
联调成本高,沟通频繁
-
线上问题难以快速定位
二、统一返回格式规范
1. 标准 JSON 返回结构
推荐使用如下结构:
{
"code": 0,
"message": "success",
"data": {},
"timestamp": 1700000000
}
字段说明
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| code | int | ✅ | 业务状态码(非 HTTP 状态码) |
| message | string | ✅ | 提示信息 |
| data | mixed | ✅ | 业务数据(成功时返回) |
| timestamp | int | ❌ | 响应时间戳(秒) |
2. 业务状态码设计建议
| code | 含义 |
|---|---|
| 0 | 成功 |
| 400xx | 客户端错误(参数、权限等) |
| 40100 | 未登录 |
| 40300 | 无权限 |
| 40400 | 资源不存在 |
| 42200 | 参数校验失败 |
| 500xx | 服务端错误 |
| 50001 | 系统异常 |
| 50002 | 数据库异常 |
⚠️ 注意:
code≠ HTTP 状态码HTTP 状态码负责传输层语义,code 负责业务语义。
3. 成功返回示例
{
"code": 0,
"message": "success",
"data": {
"id": 1,
"username": "zhangsan"
},
"timestamp": 1700000000
}
空数据统一返回:
{
"code": 0,
"message": "success",
"data": {},
"timestamp": 1700000000
}
4. 失败返回示例
{
"code": 42200,
"message": "参数校验失败",
"data": {
"username": "用户名不能为空"
},
"timestamp": 1700000000
}
三、全局异常处理规范
1. 异常分类设计
推荐分层异常体系:
Throwable
├── Exception
│ ├── BusinessException # 业务异常(可预期)
│ ├── ValidateException # 参数校验异常
│ ├── AuthException # 认证异常
│ └── SystemException # 系统异常(不可预期)
2. 自定义业务异常(示例)
namespace App\Exceptions;
use RuntimeException;
class BusinessException extends RuntimeException
{
protected int $code = 50001;
public function __construct(string $message = '', int $code = 0)
{
parent::__construct($message ?: '业务处理失败', $code ?: $this->code);
}
}
3. 全局异常捕获(以 Laravel 为例)
app/Exceptions/Handler.php
public function render($request, Throwable $e)
{
if ($request->expectsJson()) {
return $this->handleApiException($e);
}
return parent::render($request, $e);
}
protected function handleApiException(Throwable $e)
{
$code = method_exists($e, 'getCode') ? $e->getCode() : 50000;
$message = $e->getMessage();
// 兜底处理
if ($e instanceof \Error || $e instanceof \TypeError) {
$code = 50001;
$message = '系统内部错误';
}
return response()->json([
'code' => $code,
'message' => $message,
'data' => [],
'timestamp' => time(),
], $this->getHttpStatus($code));
}
protected function getHttpStatus(int $code): int
{
return match (true) {
$code >= 40000 && $code < 50000 => 400,
default => 500,
};
}
✅ 好处:
-
前端无需区分异常类型
-
敏感错误信息不会泄露
-
所有接口返回格式一致
4. 异常使用规范
✅ 正确做法:
if (!$user) {
throw new BusinessException('用户不存在', 40400);
}
❌ 错误做法:
return json_encode(['error' => 'fail']);
四、参数校验规范
1. 校验原则
-
所有外部参数必须校验
-
校验逻辑集中处理,不在 Controller 里写大量 if
-
校验失败抛异常,不走业务逻辑
2. 基础校验示例(原生 PHP)
$data = $_POST;
if (!isset($data['username']) || trim($data['username']) === '') {
throw new ValidateException('用户名不能为空', 42200);
}
if (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
throw new ValidateException('邮箱格式不正确', 42200);
}
3. Laravel 校验(推荐)
public function store(Request $request)
{
$validated = $request->validate([
'username' => 'required|string|min:3|max:20',
'email' => 'required|email',
'age' => 'integer|min:1|max:120',
]);
// 校验通过后直接使用 $validated
}
统一返回格式示例:
{
"code": 42200,
"message": "参数校验失败",
"data": {
"username": "用户名必须为字符串",
"email": "邮箱格式不正确"
},
"timestamp": 1700000000
}
4. ThinkPHP 校验
$validate = new \think\Validate([
'username' => 'require|length:3,20',
'email' => 'require|email',
]);
if (!$validate->check($this->request->post())) {
throw new ValidateException(
'参数校验失败',
42200,
$validate->getError()
);
}
5. 自定义校验规则
例如:手机号校验
Validator::extend('mobile', function ($attribute, $value) {
return preg_match('/^1[3-9]\d{9}$/', $value);
});
五、Controller 层最佳实践
推荐 Controller 结构
class UserController
{
public function store(UserRequest $request, UserService $service)
{
$result = $service->create($request->validated());
return $this->success($result);
}
protected function success($data = [], string $message = 'success')
{
return response()->json([
'code' => 0,
'message' => $message,
'data' => $data,
'timestamp' => time(),
]);
}
}
✅ Controller 只做三件事:
-
接收请求
-
参数校验
-
调用 Service
-
返回统一格式
六、HTTP 状态码使用建议
| 场景 | HTTP 状态码 | code |
|---|---|---|
| 成功 | 200 | 0 |
| 参数错误 | 400 | 42200 |
| 未登录 | 401 | 40100 |
| 无权限 | 403 | 40300 |
| 资源不存在 | 404 | 40400 |
| 系统异常 | 500 | 50001 |
❗ 不要把业务错误全部返回 200,也不要把系统异常返回 200。
七、接口规范速查表 ✅
| 项目 | 规范 |
|---|---|
| 返回格式 | JSON,统一结构 |
| code | 业务状态码,0 表示成功 |
| 异常 | 全局捕获,统一返回 |
| 参数校验 | 集中处理,失败抛异常 |
| Controller | 薄层,不写业务逻辑 |
| 错误信息 | 友好、可控、不暴露系统细节 |
八、总结
一套好的接口规范,核心价值在于:
-
✅ 前端不再"猜接口"
-
✅ 后端异常可控、可追踪
-
✅ 新成员快速上手
-
✅ 系统长期可维护
接口一致性,不是美观问题,而是工程质量问题。