前後端分離架構下,統一的 API 回應格式至關重要。本文示範如何封裝標準化 JSON 回應類別,包含狀態碼、訊息、資料三要素,提升介面一致性與維護性。
{
"code": 200, // 狀態碼:200 成功、400 失敗、401 未授權、500 伺服器錯誤
"message": "操作成功", // 人類可讀訊息
"data": { ... } // 實際業務資料(物件/陣列/純值)
}
<?php
class Response {
/**
* 輸出標準 JSON 並終止腳本
* @param int $code 狀態碼
* @param string $message 提示訊息
* @param mixed $data 回傳資料
*/
public static function json(int $code, string $message = '', $data = []) {
// 強制 UTF-8,避免中文編碼問題
header('Content-Type: application/json; charset=utf-8');
// 狀態碼合法性檢查
if (!is_numeric($code)) {
$code = 500;
$message = '內部錯誤:非法狀態碼';
}
$result = [
'code' => (int)$code,
'message' => $message,
'data' => $data,
];
// JSON_UNESCAPED_UNICODE:中文不轉義,體積更小、可讀性佳
// JSON_UNESCAPED_SLASHES:URL 不轉義斜線
echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
exit;
}
// 便利靜態方法
public static function success($data = [], $message = '成功') {
self::json(200, $message, $data);
}
public static function fail($message = '失敗', $code = 400, $data = []) {
self::json($code, $message, $data);
}
public static function unauthorized($message = '未授權') {
self::json(401, $message);
}
public static function forbidden($message = '權限不足') {
self::json(403, $message);
}
public static function notFound($message = '資源不存在') {
self::json(404, $message);
}
public static function serverError($message = '伺服器內部錯誤') {
self::json(500, $message);
}
}
?>
<?php
// login.php
require 'Response.php';
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
if (!$username || !$password) {
Response::fail('帳號密碼不可為空');
}
$user = $userModel->findByUsername($username);
if (!$user || !password_verify($password, $user['password_hash'])) {
Response::fail('帳號或密碼錯誤', 401);
}
// 產生 Token(JWT 或 Session)
$token = generateToken($user['id']);
Response::success([
'token' => $token,
'user' => ['id' => $user['id'], 'name' => $user['name']],
], '登入成功');
?>
<?php
// api/users.php
require 'Response.php';
$page = max(1, (int)($_GET['page'] ?? 1));
$limit = min(100, max(1, (int)($_GET['limit'] ?? 20)));
$users = $userModel->getList($page, $limit);
$total = $userModel->getTotalCount();
Response::success([
'list' => $users,
'pagination' => [
'page' => $page,
'limit' => $limit,
'total' => $total,
'pages' => ceil($total / $limit),
],
]);
?>
<?php
// bootstrap.php
set_exception_handler(function (Throwable $e) {
// 記錄完整錯誤到日誌
error_log("[API Exception] " . $e->getMessage() . "\n" . $e->getTraceAsString());
// 依環境決定回傳細節
$debug = (getenv('APP_DEBUG') ?? 'false') === 'true';
$message = $debug ? $e->getMessage() : '伺服器內部錯誤';
Response::serverError($message);
});
set_error_handler(function ($severity, $message, $file, $line) {
if (!(error_reporting() & $severity)) return false; // 未在報告等級內
throw new ErrorException($message, 0, $severity, $file, $line);
});
register_shutdown_function(function () {
$error = error_get_last();
if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
error_log("[Fatal Error] {$error['message']} in {$error['file']}:{$error['line']}");
Response::serverError('系統異常,請稍後再試');
}
});
?>
| 項目 | 建議 |
|---|---|
| 編碼 | 資料庫、PHP、HTML、JSON 全鏈路 UTF-8 |
| json_encode | 加上 JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES |
| 狀態碼 | 遵循 HTTP 語意:2xx 成功、4xx 客戶端錯誤、5xx 伺服器錯誤 |
| 錯誤資訊 | 正式環境隱藏堆疊細節,僅記錄日誌 |
| 資料結構 | data 固定為陣列或物件,避免前端判斷型別 |
| 分頁 | 統一欄位名:page, limit, total, pages, list |
timestamp、request_id 利於追蹤希望本文助您建立乾淨、一致、易維護的 API 介面規範。