Laravel Laravel
  • 前言

    • 发行说明
    • 升级向导
    • 贡献导引
  • 入门指南

    • 安装
    • 配置
    • 文件夹结构
    • 入门套件
    • 部署
  • 架构思想

    • 请求生命周期
    • 服务容器
    • 服务提供者
    • Facades:门面
  • 基础功能

    • 路由
    • 中间件
    • CSRF 保护
    • 控制器
    • 请求
    • 响应
    • 视图
    • Blade 模板
    • URL 生成
    • Session
    • 表单验证
    • 错误处理
    • 日志
  • 深入话题

    • Artisan 命令行
    • 广播
    • 缓存
    • 集合
    • 编译资源
    • Contracts:契约
    • 事件
    • 文件存储
    • 辅助函数
    • HTTP 客户端
    • 本地化
    • Mail
    • 消息通知
    • 扩展包开发
    • 队列
    • Rate Limiting
    • 任务调度
  • 安全

    • 身份认证
    • 授权
    • 邮件验证
    • 加密
    • 哈希
    • 重置密码
  • 数据库

    • 快速入门
    • 查询构造器
    • 分页
    • 数据库迁移
    • 数据填充
    • Redis
  • Eloquent ORM

    • 快速入门
    • 关联关系
    • Eloquent 集合
    • 修改器
    • API 资源
    • 序列化
  • 测试

    • 快速入门
    • HTTP 测试
    • 命令行测试
    • 浏览器测试
    • 数据库测试
    • 测试模拟器Mocking
  • 官方扩展包

    • Breeze
    • Cashier (Stripe)
    • Cashier (Paddle)
    • Dusk 浏览器测试
    • Envoy 部署工具
    • Fortify 授权生成器
    • Homestead 虚拟机
    • Horizon 队列管理工具
    • Jetstream:全栈开发
    • Octane 加速引擎
    • Passport OAuth 认证
    • Sail 开发环境
    • Sanctum 轻量级 API 认证
    • Scout 全文搜索
    • Socialite 社会化登录
    • Telescope 调试工具
    • Valet 集成环境
  • API Documentation
Icon

提示 您正在浏览旧版本的 Laravel 的文档. 请考虑将你的项目升级到 Laravel 11.x.

25 4

HTTP 测试
8.5
8.5 8.x 7.x 6.x 5.8 5.7 5.6 5.5 5.4 5.3 5.2 5.1

Laravel 8 中文文档 /

未匹配的标注

HTTP Tests

  • 介绍
  • 创建请求
    • 自定义请求头
    • Cookies
    • Session / 身份验证
    • 调试响应
  • 测试 JSON APIs
  • 测试文件上传
  • 测试视图
    • 渲染模板 & 组件
  • 可用断言
    • 响应断言
    • 身份验证断言

介绍

Laravel 提供了一个非常流畅的 API,用于向您的应用程序发出 HTTP 请求并检查输出。例如,看一下下面定义的功能测试:

<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Tests\TestCase;

class ExampleTest extends TestCase
{
    /**
     * 一个最基础的例子
     *
     * @return void
     */
    public function test_a_basic_request()
    {
        $response = $this->get('/');

        $response->assertStatus(200);
    }
}

例子中 get 方法向应用程序发出 GET 请求,而 assertStatus 方法则断言返回的响应具有给定的 HTTP 状态代码。除了这个简单的断言之外,Laravel 还包含用于检查响应头、内容、JSON 结构等的各种断言。

创建请求

要向您的应用程序发出请求,您可以在测试中调用 get,post,put,patch 或 delete 方法。这些方法实际上不会向您的应用程序发出 「真实的」 HTTP 请求,而是在内部模拟整个网络请求。

测试请求方法不返回 Illuminate\Http\Response 实例,而是返回 Illuminate\Testing\TestResponse 实例,该实例提供了 各种有用的断言,可让您检查应用程序的响应:

<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Tests\TestCase;

class ExampleTest extends TestCase
{
    /**
     * 一个最基础的例子
     *
     * @return void
     */
    public function test_a_basic_request()
    {
        $response = $this->get('/');

        $response->assertStatus(200);
    }
}

技巧:为了方便起见,运行测试时会自动禁用 CSRF 中间件。

自定义请求头

您可以使用此 withHeaders 方法自定义请求的标头,然后再将其发送到应用程序。这使您可以将任何想要的自定义标头添加到请求中:

<?php

namespace Tests\Feature;

use Tests\TestCase;

class ExampleTest extends TestCase
{
    /**
     * 一个基本的功能测试示例
     *
     * @return void
     */
    public function test_interacting_with_headers()
    {
        $response = $this->withHeaders([
            'X-Header' => 'Value',
        ])->post('/user', ['name' => 'Sally']);

        $response->assertStatus(201);
    }
}

Cookies

在发送请求前你可以使用withCookie 或 withCookies 方法设置 cookie 。withCookie 接受 cookie 的名称和值这两个参数,而 withCookies方法接受一个名称 / 值对数组:

<?php

namespace Tests\Feature;

use Tests\TestCase;

class ExampleTest extends TestCase
{
    public function test_interacting_with_cookies()
    {
        $response = $this->withCookie('color', 'blue')->get('/');

        $response = $this->withCookies([
            'color' => 'blue',
            'name' => 'Taylor',
        ])->get('/');
    }
}

Session / Authentication

Laravel 提供了几个可在 HTTP 测试时使用 Session 的辅助函数。首先,你需要传递一个数组给 withSession 方法来设置 session 数据。这样在应用程序的测试请求发送之前,就会先去给数据加载 session:

<?php

namespace Tests\Feature;

use Tests\TestCase;

class ExampleTest extends TestCase
{
    public function test_interacting_with_the_session()
    {
        $response = $this->withSession(['banned' => false])->get('/');
    }
}

Laravel 的 session 通常用于维护当前已验证用户的状态。因此,actingAs 方法提供了一种将给定用户作为当前用户进行身份验证的便捷方法。例如, 我们可以使用 工厂模式 生成并验证用户:

<?php

namespace Tests\Feature;

use App\Models\User;
use Tests\TestCase;

class ExampleTest extends TestCase
{
    public function test_an_action_that_requires_authentication()
    {
        $user = User::factory()->create();

        $response = $this->actingAs($user)
                         ->withSession(['banned' => false])
                         ->get('/');
    }
}

你也可以通过传递看守器名称作为 actingAs 方法的第二参数以指定用户通过哪种看守器来认证:

$this->actingAs($user, 'api')

调试响应

在向您的应用程序发出测试请求之后,可以使用 dump、dumpHeaders 和 dumpSession 方法来检查和调试响应内容:

<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Tests\TestCase;

class ExampleTest extends TestCase
{
    /**
     * 一个最基础的测试例子
     *
     * @return void
     */
    public function test_basic_test()
    {
        $response = $this->get('/');

        $response->dumpHeaders();

        $response->dumpSession();

        $response->dump();
    }
}

测试 JSON APIs

Laravel 也提供了几个辅助函数来测试 JSON APIs 和其响应。例如,json、getJson、postJson、putJson、patchJson、deleteJson 以及 optionsJson 可以被用于发送各种 HTTP 动作。你也可以轻松地将数据和请求头传递到这些方法中。首先,让我们实现一个测试示例, 发送 POST 请求到 /api/user,并断言返回的期望数据:

<?php

namespace Tests\Feature;

use Tests\TestCase;

class ExampleTest extends TestCase
{
    /**
     * 基本功能测试示例子
     *
     * @return void
     */
    public function test_making_an_api_request()
    {
        $response = $this->postJson('/api/user', ['name' => 'Sally']);

        $response
            ->assertStatus(201)
            ->assertJson([
                'created' => true,
            ]);
    }
}

此外,JSON 响应数据可以作为响应上的数组变量进行访问,从而使您可以方便地检查 JSON 响应中返回的各个值:

$this->assertTrue($response['created']);

技巧:AssertJson 方法将响应转换为数组,并利用 PHPUnit::assertArraySubset 验证给定数组是否存在于应用程序返回的 JSON 响应中。因此,如果 JSON 响应中还有其他属性,则只要存在给定的片段,此测试仍将通过。

验证 JSON 完全匹配

如前所述,assertJson 方法可用于断言 JSON 响应中存在 JSON 片段。如果您想验证给定数组是否与应用程序返回的 JSON 完全匹配,则应使用 assertExactJson 方法:

<?php

namespace Tests\Feature;

use Tests\TestCase;

class ExampleTest extends TestCase
{
    /**
     * A basic functional test example.
     *
     * @return void
     */
    public function test_asserting_an_exact_json_match()
    {
        $response = $this->json('POST', '/user', ['name' => 'Sally']);

        $response
            ->assertStatus(201)
            ->assertExactJson([
                'created' => true,
            ]);
    }
}

验证 JSON 路径

如果你想验证 JSON 响应是否包含指定路径上的某些给定数据,可以使用 assertJsonPath 方法:

<?php

namespace Tests\Feature;

use Tests\TestCase;

class ExampleTest extends TestCase
{
    /**
     * A basic functional test example.
     *
     * @return void
     */
    public function test_asserting_a_json_paths_value()
    {
        $response = $this->json('POST', '/user', ['name' => 'Sally']);

        $response
            ->assertStatus(201)
            ->assertJsonPath('team.owner.name', 'Darian');
    }
}

测试文件上传

Illuminate\Http\UploadedFile 提供了一个 fake 方法用于生成虚拟的文件或者图像以供测试之用。它可以和 Storage facade 的 fake 方法相结合,大幅度简化了文件上传测试。举个例子,你可以结合这两者的功能非常方便地进行头像上传表单测试:

<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Tests\TestCase;

class ExampleTest extends TestCase
{
    public function test_avatars_can_be_uploaded()
    {
        Storage::fake('avatars');

        $file = UploadedFile::fake()->image('avatar.jpg');

        $response = $this->post('/avatar', [
            'avatar' => $file,
        ]);

        Storage::disk('avatars')->assertExists($file->hashName());
    }
}

如果您想断言一个给定的文件不存在,则可以使用由 Storage facade 提供的 AssertMissing 方法:

Storage::fake('avatars');

// ...

Storage::disk('avatars')->assertMissing('missing.jpg');

虚拟文件定制

在使用 fake 方法创建文件时,你可以指定图像的宽高以及大小,从而更好的验证测试规则:

UploadedFile::fake()->image('avatar.jpg', $width, $height)->size(100);

除创建图像外,你也可以用 create 方法创建其他类型的文件:

UploadedFile::fake()->create('document.pdf', $sizeInKilobytes);

如果需要,可以向该方法传递一个 $mimeType 参数,以显式定义文件应返回的 MIME 类型:

UploadedFile::fake()->create(
    'document.pdf', $sizeInKilobytes, 'application/pdf'
);

测试视图

Laravel 允许在不向应用程序发出模拟 HTTP 请求的情况下独立呈现视图。为此,可以在测试中使用 view 方法。view 方法接受视图名称和一个可选的数据数组。这个方法返回一个 Illuminate\Testing\TestView 的实例,它提供了几个方法来方便地断言视图的内容:

<?php

namespace Tests\Feature;

use Tests\TestCase;

class ExampleTest extends TestCase
{
    public function test_a_welcome_view_can_be_rendered()
    {
        $view = $this->view('welcome', ['name' => 'Taylor']);

        $view->assertSee('Taylor');
    }
}

TestView 对象提供了以下断言方法:assertSee、assertSeeInOrder、assertSeeText、assertSeeTextInOrder、assertDontSee 和 assertDontSeeText。

如果需要,你可以通过将 TestView 实例转换为一个字符串获得原始的视图内容:

$contents = (string) $this->view('welcome');

共享错误

一些视图可能依赖于 Laravel 提供的 全局错误包 中共享的错误。要在错误包中生成错误消息,可以使用 withViewErrors 方法:

$view = $this->withViewErrors([
    'name' => ['Please provide a valid name.']
])->view('form');

$view->assertSee('Please provide a valid name.');

渲染模板 & 组件

必要的话,你可以使用 blade 方法来计算和呈现原始的 Blade 字符串。与 view 方法一样,blade 方法返回的是 Illuminate\Testing\TestView 的实例:

$view = $this->blade(
    '<x-component :name="$name" />',
    ['name' => 'Taylor']
);

$view->assertSee('Taylor');

您可以使用 component 方法来评估和渲染 Blade组件。类似于 view 方法,component 方法返回一个 Illuminate\Testing\TestView 的实例:

$view = $this->component(Profile::class, ['name' => 'Taylor']);

$view->assertSee('Taylor');

可用断言

响应断言

Laravel的 Illuminate \ Testing \ TestResponse 类提供了各种自定义断言方法,您可以在测试应用程序时使用它们。可以在由 json、get、post、put 和 delete 方法返回的响应上访问这些断言:

assertCookie
assertCookieExpired
assertCookieNotExpired
assertCookieMissing
assertCreated
assertDontSee
assertDontSeeText
assertExactJson
assertForbidden
assertHeader
assertHeaderMissing
assertJson
assertJsonCount
assertJsonFragment
assertJsonMissing
assertJsonMissingExact
assertJsonMissingValidationErrors
assertJsonPath
assertJsonStructure
assertJsonValidationErrors
assertLocation
assertNoContent
assertNotFound
assertOk
assertPlainCookie
assertRedirect
assertSee
assertSeeInOrder
assertSeeText
assertSeeTextInOrder
assertSessionHas
assertSessionHasInput
assertSessionHasAll
assertSessionHasErrors
assertSessionHasErrorsIn
assertSessionHasNoErrors
assertSessionDoesntHaveErrors
assertSessionMissing
assertStatus
assertSuccessful
assertUnauthorized
assertViewHas
assertViewHasAll
assertViewIs
assertViewMissing

assertCookie

断言响应中包含给定的 cookie:

$response->assertCookie($cookieName, $value = null);

assertCookieExpired

断言响应包含给定的过期的 cookie:

$response->assertCookieExpired($cookieName);

assertCookieNotExpired

断言响应包含给定的未过期的 cookie:

$response->assertCookieNotExpired($cookieName);

assertCookieMissing

断言响应不包含给定的 cookie:

$response->assertCookieMissing($cookieName);

assertCreated

断言做状态代码为 201 的响应:

$response->assertCreated();

assertDontSee

断言给定的字符串不包含在响应中。除非传递第二个参数 false,否则此断言将给定字符串进行转义后匹配:

$response->assertDontSee($value, $escaped = true);

assertDontSeeText

断言给定的字符串不包含在响应文本中。除非您传递第二个参数 false,否则该断言将自动转义给定的字符串。该方法将在做出断言之前将响应内容传递给 PHP 的 strip_tags 函数:

$response->assertDontSeeText($value, $escaped = true);

assertExactJson

断言响应包含与给定 JSON 数据的完全匹配:

$response->assertExactJson(array $data);

assertForbidden

断言响应中有禁止访问 (403) 状态码:

$response->assertForbidden();

assertHeader

断言给定的 header 在响应中存在:

$response->assertHeader($headerName, $value = null);

assertHeaderMissing

断言给定的 header 在响应中不存在:

$response->assertHeaderMissing($headerName);

assertJson

断言响应包含给定的 JSON 数据:

$response->assertJson(array $data, $strict = false);

AssertJson 方法将响应转换为数组,并利用 PHPUnit::assertArraySubset 验证给定数组是否存在于应用程序返回的 JSON 响应中。因此,如果 JSON 响应中还有其他属性,则只要存在给定的片段,此测试仍将通过。

assertJsonCount

断言响应 JSON 中有一个数组,其中包含给定键的预期元素数量:

$response->assertJsonCount($count, $key = null);

assertJsonFragment

断言响应包含给定 JSON 片段:

Route::get('/users', function () {
    return [
        'users' => [
            [
                'name' => 'Taylor Otwell',
            ],
        ],
    ];
});

$response->assertJsonFragment(['name' => 'Taylor Otwell']);

assertJsonMissing

断言响应未包含给定的 JSON 片段:

$response->assertJsonMissing(array $data);

assertJsonMissingExact

断言响应不包含确切的 JSON 片段:

$response->assertJsonMissingExact(array $data);

assertJsonMissingValidationErrors

断言响应响应对于给定的键没有 JSON 验证错误:

Assert that the response has no JSON validation errors for the given keys:

$response->assertJsonMissingValidationErrors($keys);

assertJsonPath

断言响应包含指定路径上的给定数据:

$response->assertJsonPath($path, array $data, $strict = true);

例如,如果您的应用程序返回的 JSON 响应包含以下数据:

{
    "user": {
        "name": "Steve Schoger"
    }
}

您可以断言 user 对象的 name 属性匹配给定值,如下所示:

$response->assertJsonPath('user.name', 'Steve Schoger');

assertJsonStructure

断言响应具有给定的 JSON 结构:

$response->assertJsonStructure(array $structure);

例如,如果您的应用程序返回的 JSON 响应包含以下数据:

{
    "user": {
        "name": "Steve Schoger"
    }
}

您可以断言 JSON 结构符合您的期望,如下所示:

$response->assertJsonStructure([
    'user' => [
        'name',
    ]
]);

assertJsonValidationErrors

断言响应具有给定键的给定 JSON 验证错误。在对响应进行断言时应使用此方法,在该响应中,验证错误以 JSON 结构形式返回,而不是闪存到 session :

$response->assertJsonValidationErrors(array $data);

assertLocation

断言响应在 Location 头部中具有给定的 URI 值:

$response->assertLocation($uri);

assertNoContent

断言响应具有给定的 HTTP 状态码且没有内容:

$response->assertNoContent($status = 204);

assertNotFound

断言响应具有未找到(404)HTTP 状态码:

$response->assertNotFound();

assertOk

断言响应有 200 状态码:

$response->assertOk();

assertPlainCookie

断言响应包含给定的 cookie (未加密):

$response->assertPlainCookie($cookieName, $value = null);

assertRedirect

断言响应会重定向到给定的 URI:

$response->assertRedirect($uri);

assertSee

断言给定的字符串包含在响应中。除非传递第二个参数 false ,否则此断言将给定字符串进行转义后匹配:

$response->assertSee($value, $escaped = true);

assertSeeInOrder

断言给定的字符串按顺序包含在响应中。除非传递第二个参数 false ,否则此断言将给定字符串进行转义后匹配:

$response->assertSeeInOrder(array $values, $escaped = true);

assertSeeText

断言给定字符串包含在响应文本中。除非传递第二个参数 false,否则此断言将给定字符串进行转义后匹配。在做出断言之前,响应内容将被传递到 PHP 的 strip_tags 函数:

$response->assertSeeText($value, $escaped = true);

assertSeeTextInOrder

断言给定的字符串按顺序包含在响应的文本中。除非传递第二个参数 false ,否则此断言将给定字符串进行转义后匹配。在做出断言之前,响应内容将被传递到 PHP 的 strip_tags 函数:

$response->assertSeeTextInOrder(array $values, $escaped = true);

assertSessionHas

断言 Session 包含给定的数据段:

$response->assertSessionHas($key, $value = null);

assertSessionHasInput

session 在 闪存输入数组 中断言具有给定值:

$response->assertSessionHasInput($key, $value = null);

assertSessionHasAll

断言 Session 中具有给定的键/值对列表:

$response->assertSessionHasAll(array $data);

例如,如果您的应用程序会话包含 name 和 status 键,则可以断言它们存在并且具有指定的值,如下所示:

$response->assertSessionHasAll([
    'name' => 'Taylor Otwell',
    'status' => 'active',
]);

assertSessionHasErrors

断言 session 包含给定 $keys 的 Laravel 验证错误。如果 $keys 是关联数组,则断言 session 包含每个字段(key)的特定错误消息(value)。测试将闪存验证错误到 session 的路由时,应使用此方法,而不是将其作为 JSON 结构返回:

$response->assertSessionHasErrors(
    array $keys, $format = null, $errorBag = 'default'
);

例如,要断言 name 和 email 字段具有已闪现到 session 的验证错误消息,可以调用assertSessionHasErrors 方法,如下所示:

$response->assertSessionHasErrors(['name', 'email']);

或者,您可以断言给定字段具有特定的验证错误消息:

$response->assertSessionHasErrors([
    'name' => 'The given name was invalid.'
]);

assertSessionHasErrorsIn

断言会话在特定的 错误包 中包含给定 $keys 的错误。如果 $keys 是一个关联数组,则断言该 session 在错误包内包含每个字段(键)的特定错误消息(值):

$response->assertSessionHasErrorsIn($errorBag, $keys = [], $format = null);

assertSessionHasNoErrors

断言 session 没有验证错误:

$response->assertSessionHasNoErrors();

assertSessionDoesntHaveErrors

断言 session 没有验证错误:
Assert that the session has no validation errors for the given keys:

$response->assertSessionDoesntHaveErrors($keys = [], $format = null, $errorBag = 'default');

assertSessionMissing

断言 session 中缺少指定的 $key:

$response->assertSessionMissing($key);

assertStatus

断言响应指定的 http 状态码:

$response->assertStatus($code);

assertSuccessful

断言响应一个成功的状态码 (>= 200 且 < 300) :

$response->assertSuccessful();

assertUnauthorized

断言一个未认证的状态码 (401):

$response->assertUnauthorized();

assertViewHas

断言为响应视图提供了一个键值对数据:

$response->assertViewHas($key, $value = null);

另外,视图数据可以作为响应上的数组变量访问,从而使您可以方便地检查它:

$this->assertEquals('Taylor', $response['name']);

assertViewHasAll

断言响应视图具有给定的数据列表:

$response->assertViewHasAll(array $data);

该方法可用于断言该视图仅包含与给定键匹配的数据:

$response->assertViewHasAll([
    'name',
    'email',
]);

或者,您可以断言该视图数据存在并且具有特定值:

$response->assertViewHasAll([
    'name' => 'Taylor Otwell',
    'email' => 'taylor@example.com,',
]);

assertViewIs

断言当前路由返回的的视图是给定的视图:

$response->assertViewIs($value);

assertViewMissing

断言视图缺少某个键:

$response->assertViewMissing($key);

身份验证断言

Laravel 还提供了各种与身份验证相关的断言,您可以在应用程序的功能测试中使用这些断言。注意,这些方法是在测试类本身上调用的,而不是由诸如 get 和 post 之类的方法返回的 Illuminate\Testing\TestResponse 实例。

assertAuthenticated

断言用户已通过身份验证:

$this->assertAuthenticated($guard = null);

assertGuest

断言用户未通过身份验证:

$this->assertGuest($guard = null);

assertAuthenticatedAs

断言特定用户已通过身份验证:

$this->assertAuthenticatedAs($user, $guard = null);

本文章首发在 网站上。


上一篇 下一篇

成为Laravel合作伙伴

Laravel Partners是提供一流Laravel开发和咨询服务的精英商店。我们每个合作伙伴都可以帮助您制定一个精美,结构完善的项目.

我们的伙伴
Laravel
亮点
  • Our Team
  • 发布说明
  • 入门
  • 路由
  • Blade 模板
  • 身份验证
  • 用户授权
  • Artisan 控制台
  • 数据库
  • Eloquent ORM
  • 测试
资源
  • Laravel Bootcamp
  • Laracasts
  • Laravel News
  • Laracon
  • Laracon EU
  • Laracon India
  • Jobs
  • Forums
  • Trademark
  • 版本发布时间
  • 包开发
  • 命令行应用
  • TALL stack全栈开发
  • Blade UI Kit
  • 前端资源构建
伙伴
  • WebReinvent
  • Vehikl
  • Tighten
  • 64 Robots
  • Active Logic
  • Byte 5
  • Curotec
  • Cyber-Duck
  • DevSquad
  • Jump24
  • Kirschbaum
生态系统
  • Cashier
  • Dusk
  • Echo
  • Envoyer
  • Forge
  • Horizon
  • Nova
  • Octane
  • Sail
  • Sanctum
  • Scout
  • Spark
  • Telescope
  • Valet
  • Vapor

Laravel是一个具有表达力,优雅语法的Web应用程序框架。我们认为,发展必须是一种令人愉悦的创造力,才能真正实现。Laravel试图通过减轻大多数Web项目中使用的常见任务来减轻开发的痛苦.

Laravel是Taylor Otwell的商标.
Copyright © 2011-2025 Laravel中文网 LLC.

  • Twitter
  • GitHub
  • Discord
Laravel 全栈开发网 推荐使用阿里云 按Ctrl+D试试