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

相关推荐
Tony Bai8 小时前
告别 Flaky Tests:Go 官方拟引入 testing/nettest,重塑内存网络测试标准
开发语言·网络·后端·golang·php
程序猿_极客13 小时前
【2026】分享一套优质的 Php+MySQL的 校园二手交易平台的设计与实现(万字文档+源码+视频讲解)
vue.js·毕业设计·php·mysql数据库·二手交易系统
ZHOUPUYU14 小时前
PHP 8.0+ 千万级订单系统的分布式事务实战:TCC模式破解高并发难题
php
VXbishe16 小时前
基于Spring Boot的老年社区资源分享平台设计与实现-计算机毕设 附源码 25337
javascript·vue.js·spring boot·python·node.js·php·html5
样子201816 小时前
PHP 之分片上传
开发语言·php
爱敲代码的小冰18 小时前
php dockerfile安装依赖详解
android·开发语言·php
hartyu19 小时前
纯PHP + Selenium + ChromeDriver方案实现原理,半自动化内容抓取
开发语言·selenium·php
FJW02081419 小时前
Nginx + Redis + srcache + PHP-FPM架构部署
redis·nginx·php
不是二师兄的八戒20 小时前
PHP字符串模糊匹配技术深度解析:从Levenshtein到中文优化
开发语言·php