笔记-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是具体的错误。

相关推荐
BingoGo2 天前
PHP 泛型之殇 泛型 RFC 提案被拒绝
后端·php
JaguarJack2 天前
PHP 泛型之殇 泛型 RFC 提案被拒绝
后端·php
用户3074596982073 天前
PHP 扩展——从入门到理解
php
鹏仔先生3 天前
拷贝漫画APP下载页PHP程序,后台带免费AI写作
php
云水一下4 天前
从零开始学 PHP 系列(一):PHP 的前世今生与开发环境搭建
开发语言·php
xingpanvip4 天前
星盘接口开发文档:本命盘接口指南
android·开发语言·css·php·lua
酉鬼女又兒4 天前
零基础入门计算机网络运输层:端到端通信核心作用、端口号分类规则、复用分用工作机制及UDP与TCP协议全方位对比详解
网络·网络协议·tcp/ip·计算机网络·考研·udp·php
dog2504 天前
不要再继续优化 TCP
网络协议·tcp/ip·php
Channing Lewis4 天前
PHP 解析 Excel 的那些坑:一次“行号错位”引发的数据丢失
开发语言·php·excel
Cheng小攸4 天前
渗透行为分析与检测
开发语言·php