1. 在 /config/app 设置时区
php
'timezone' => 'Asia/Shanghai',
注意这里使用 'Asia/Shanghai' 而不是 'PRC' (老版本不推荐使用),这两个是一样的都是东八区时间
2. 添加 Accept MidlleWare,解决验证参数返回json问题
- 创建 MiddleWare
shell
php artisan make:middleware AcceptJsonHeader
- 添加 AcceptHeader 为
application/json
php
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class AcceptJsonHeader
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
// 设置 为 application/json
$request->headers->set("Accept", "application/json");
return $next($request);
}
}
- 在
/bootstrap/app.php配置 middleware
php
<?php
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
api: __DIR__.'/../routes/api.php',
web: __DIR__.'/../routes/web.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware): void {
// 为 api 配置 midlleware
$middleware->appendToGroup('api', [
\App\Http\Middleware\AcceptJsonHeader::class
]);
})
->withExceptions(function (Exceptions $exceptions): void {
//
})->create();
3. 国际化
- 创建语言文件
shell
php artisan lang:publish
- 配置语言
.env中的APP_LOCALEapp()->setLocale($locale)
- 添加语言文件
zh_CN - 使用
__函数显示翻译字符串
VsCode 中配置
json{ "i18n-ally.localesPaths": ["lang"], "i18n-ally.keystyle": "nested", "i18n-ally.sortKeys": true, "i18n-ally.namespace": true, "i18n-ally.pathMatcher": "{locale}/{namespaces}.{ext}", "i18n-ally.enabledParsers": ["php"], "i18n-ally.sourceLanguage": "zh_CN", "i18n-ally.displayLanguage": "zh_CN", "i18n-ally.enabledFrameworks": ["laravel"], }
4. 响应
- 编写统一响应
php
<?php
namespace App\Http\Response;
/**
* API 响应状态码枚举
*
* 遵循阿里巴巴 Java 开发手册错误码规范
* - 00000: 成功
* - A 系列: 用户端错误 (Client Error)
* - B 系列: 系统执行错误 (System Error)
* - C 系列: 第三方服务错误 (Third-party Error)
*/
enum ApiResponseEnum: string
{
// ==================== 成功 ====================
case SUCCESS = '00000';
// ==================== A 系列:用户端错误 ====================
case USER_ERROR = 'A0001'; // 用户端错误
case INVALID_PARAMS = 'A0400'; // 请求参数错误
case VALIDATION_FAILED = 'A0402'; // 数据校验失败
case UNAUTHORIZED = 'A0300'; // 用户未登录
case FORBIDDEN = 'A0301'; // 访问权限不足
case NOT_FOUND = 'A0404'; // 请求资源不存在
case METHOD_NOT_ALLOWED = 'A0405'; // 请求方法不允许
case TOO_MANY_REQUESTS = 'A0429'; // 请求过于频繁
// ==================== B 系列:系统执行错误 ====================
case SYSTEM_ERROR = 'B0001'; // 系统执行错误
case SYSTEM_TIMEOUT = 'B0100'; // 系统执行超时
case DB_ERROR = 'B0300'; // 数据库执行异常
case DISK_FULL = 'B0101'; // 系统磁盘空间不足
// ==================== C 系列:第三方服务错误 ====================
case THIRD_PARTY_ERROR = 'C0001'; // 第三方服务错误
case THIRD_PARTY_TIMEOUT = 'C0100'; // 第三方服务超时
/**
* 获取状态码对应的翻译键
*/
public function translationKey(): string
{
return match($this) {
self::SUCCESS => 'response.success',
self::USER_ERROR => 'response.user_error',
self::INVALID_PARAMS => 'response.invalid_params',
self::VALIDATION_FAILED => 'response.validation_failed',
self::UNAUTHORIZED => 'response.unauthorized',
self::FORBIDDEN => 'response.forbidden',
self::NOT_FOUND => 'response.not_found',
self::METHOD_NOT_ALLOWED => 'response.method_not_allowed',
self::TOO_MANY_REQUESTS => 'response.too_many_requests',
self::SYSTEM_ERROR => 'response.system_error',
self::SYSTEM_TIMEOUT => 'response.system_timeout',
self::DB_ERROR => 'response.db_error',
self::DISK_FULL => 'response.disk_full',
self::THIRD_PARTY_ERROR => 'response.third_party_error',
self::THIRD_PARTY_TIMEOUT => 'response.third_party_timeout',
default => 'response.unknown_error',
};
}
/**
* 获取状态码对应的默认消息(支持国际化)
*/
public function message(): string
{
return __($this->translationKey());
}
/**
* 获取对应的 HTTP 状态码
*/
public function httpStatus(): int
{
return match($this) {
self::SUCCESS => 200,
self::INVALID_PARAMS, self::VALIDATION_FAILED => 422,
self::UNAUTHORIZED => 401,
self::FORBIDDEN => 403,
self::NOT_FOUND => 404,
self::METHOD_NOT_ALLOWED => 405,
self::TOO_MANY_REQUESTS => 429,
self::SYSTEM_ERROR, self::SYSTEM_TIMEOUT,
self::DB_ERROR, self::DISK_FULL => 500,
self::THIRD_PARTY_ERROR, self::THIRD_PARTY_TIMEOUT => 503,
default => 500,
};
}
}
php
<?php
namespace App\Http\Response;
use Illuminate\Http\JsonResponse;
/**
* 统一 API 响应类
*
* 响应格式:
* {
* "code": "00000",
* "message": "操作成功",
* "data": {...}
* }
*/
class ApiResponse
{
/**
* 成功响应
*
* @param mixed $data 响应数据
* @param string|null $message 自定义消息
* @return JsonResponse
*/
public static function success(mixed $data = null, ?string $message = null): JsonResponse
{
return self::response(
ApiResponseEnum::SUCCESS,
$message ?? ApiResponseEnum::SUCCESS->message(),
$data
);
}
/**
* 失败响应
*
* @param ApiResponseEnum $code 错误码枚举
* @param string|null $message 自定义错误消息
* @param mixed $data 附加数据
* @return JsonResponse
*/
public static function fail(
ApiResponseEnum $code = ApiResponseEnum::USER_ERROR,
?string $message = null,
mixed $data = null
): JsonResponse {
return self::response(
$code,
$message ?? $code->message(),
$data,
$code->httpStatus()
);
}
/**
* 参数验证失败响应
*
* @param string|array $errors 验证错误信息
* @param mixed $data 附加数据
* @return JsonResponse
*/
public static function validationFailed(string|array $errors, mixed $data = null): JsonResponse
{
$message = is_array($errors) ? implode('; ', $errors) : $errors;
return self::response(
ApiResponseEnum::VALIDATION_FAILED,
$message,
$data,
ApiResponseEnum::VALIDATION_FAILED->httpStatus()
);
}
/**
* 未授权响应 (401)
*
* @param string|null $message 自定义消息
* @return JsonResponse
*/
public static function unauthorized(?string $message = null): JsonResponse
{
return self::fail(ApiResponseEnum::UNAUTHORIZED, $message);
}
/**
* 禁止访问响应 (403)
*
* @param string|null $message 自定义消息
* @return JsonResponse
*/
public static function forbidden(?string $message = null): JsonResponse
{
return self::fail(ApiResponseEnum::FORBIDDEN, $message);
}
/**
* 资源不存在响应 (404)
*
* @param string|null $message 自定义消息
* @return JsonResponse
*/
public static function notFound(?string $message = null): JsonResponse
{
return self::fail(ApiResponseEnum::NOT_FOUND, $message);
}
/**
* 系统错误响应 (500)
*
* @param string|null $message 自定义消息
* @param mixed $data 附加数据
* @return JsonResponse
*/
public static function systemError(?string $message = null, mixed $data = null): JsonResponse
{
return self::fail(ApiResponseEnum::SYSTEM_ERROR, $message, $data);
}
/**
* 第三方服务错误响应 (503)
*
* @param string|null $message 自定义消息
* @return JsonResponse
*/
public static function thirdPartyError(?string $message = null): JsonResponse
{
return self::fail(ApiResponseEnum::THIRD_PARTY_ERROR, $message);
}
/**
* 构建统一响应
*
* @param ApiResponseEnum $code 业务状态码
* @param string $message 响应消息
* @param mixed $data 响应数据
* @param int|null $httpStatus HTTP 状态码
* @return JsonResponse
*/
private static function response(
ApiResponseEnum $code,
string $message,
mixed $data = null,
?int $httpStatus = null
): JsonResponse {
$response = [
'code' => $code->value,
'message' => $message,
'data' => $data,
];
return response()->json(
$response,
$httpStatus ?? $code->httpStatus()
);
}
}
- 配置错误处理
php
<?php
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
use Illuminate\Validation\ValidationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Auth\Access\AuthorizationException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException;
use App\Http\Response\ApiResponse;
use App\Http\Middleware\SetLocale;
use Illuminate\Http\Request;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
api: __DIR__.'/../routes/api.php',
web: __DIR__.'/../routes/web.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware): void {
// 全局中间件:自动设置语言
$middleware->append(SetLocale::class);
})
->withExceptions(function (Exceptions $exceptions): void {
// 422 - 验证失败
$exceptions->render(function (ValidationException $e, Request $request) {
if ($request->expectsJson() || $request->is('api/*')) {
return ApiResponse::validationFailed($e->validator->errors()->all());
}
});
// 404 - 模型未找到
$exceptions->render(function (ModelNotFoundException $e, Request $request) {
if ($request->expectsJson() || $request->is('api/*')) {
return ApiResponse::notFound(__('response.resource_not_found'));
}
});
// 404 - 路由未找到
$exceptions->render(function (NotFoundHttpException $e, Request $request) {
if ($request->expectsJson() || $request->is('api/*')) {
return ApiResponse::notFound(__('response.route_not_found'));
}
});
// 405 - 请求方法不允许
$exceptions->render(function (MethodNotAllowedHttpException $e, Request $request) {
if ($request->expectsJson() || $request->is('api/*')) {
return ApiResponse::fail(
\App\Http\Response\ApiResponseEnum::METHOD_NOT_ALLOWED
);
}
});
// 401 - 未认证
$exceptions->render(function (AuthenticationException $e, Request $request) {
if ($request->expectsJson() || $request->is('api/*')) {
return ApiResponse::unauthorized();
}
});
// 403 - 未授权
$exceptions->render(function (AuthorizationException $e, Request $request) {
if ($request->expectsJson() || $request->is('api/*')) {
return ApiResponse::forbidden();
}
});
// 429 - 请求过于频繁
$exceptions->render(function (TooManyRequestsHttpException $e, Request $request) {
if ($request->expectsJson() || $request->is('api/*')) {
return ApiResponse::fail(
\App\Http\Response\ApiResponseEnum::TOO_MANY_REQUESTS
);
}
});
// 500 - 系统错误(生产环境不暴露详细错误信息)
$exceptions->render(function (Throwable $e, Request $request) {
if ($request->expectsJson() || $request->is('api/*')) {
$message = app()->environment('local', 'testing')
? $e->getMessage()
: null;
return ApiResponse::systemError($message);
}
});
})->create();
参考文章 新手使用 Laravel 开发 API 的前置准备
业务APICode设计规范 A-BB-CC 其中A是大类/错误级别,BB是业务模块,CC是具体的错误。