笔记-Laravel12 开发API前置准备

1. 在 /config/app 设置时区

php 复制代码
'timezone' => 'Asia/Shanghai',

可支持的时区列表

注意这里使用 'Asia/Shanghai' 而不是 'PRC' (老版本不推荐使用),这两个是一样的都是东八区时间

2. 添加 Accept MidlleWare,解决验证参数返回json问题

  1. 创建 MiddleWare
shell 复制代码
php artisan make:middleware AcceptJsonHeader
  1. 添加 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);
    }
}
  1. /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. 国际化

  1. 创建语言文件
shell 复制代码
php artisan lang:publish
  1. 配置语言
  • .env 中的 APP_LOCALE
  • app()->setLocale($locale)
  1. 添加语言文件 zh_CN
  2. 使用 __ 函数显示翻译字符串

参考Laravel 本地化

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. 响应

  1. 编写统一响应
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()
        );
    }
}
  1. 配置错误处理
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是具体的错误。

相关推荐
ServBay1 天前
垃圾堆里编码?真的不要怪 PHP 不行
后端·php
用户962377954481 天前
CTF 伪协议
php
BingoGo3 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php
JaguarJack3 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php·服务端
BingoGo4 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php
JaguarJack4 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php·服务端
JaguarJack5 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
后端·php·服务端
BingoGo5 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
php
BingoGo6 天前
告别 Laravel 缓慢的 Blade!Livewire Blaze 来了,为你的 Laravel 性能提速
后端·laravel
JaguarJack6 天前
告别 Laravel 缓慢的 Blade!Livewire Blaze 来了,为你的 Laravel 性能提速
后端·php·laravel