全书目录

第二十七篇:Laravel 完全指南 —— 工匠城区宇宙

第十章:完整示例 —— 工匠城区里的订单工坊

1 分钟 366 字 第 487 / 962 个阅读单元

下面做一个完整但尽量简洁的例子。

场景:

  • 人类操作员通过 Blade 页面创建订单
  • 外部系统也可以通过 API 创建同一类订单
  • 两条入口共用同一个 OrderService
  • 订单落库后广播 OrderCreated
  • 监听器排入队列,在后台准备物料

先看总流转图:

text
浏览器表单 / API 请求
        |
        v
      Route
        |
        v
   EnsureWorkshopOpen
        |
        v
Controller
        |
        v
   OrderService
        |
        v
    Eloquent
        |
        v
   orders 表
        |
        v
OrderCreated Event
        |
        v
Queued Listener
        |
        v
 Queue Worker
#1. 迁移:orders 仓库表
php
<?php
// database/migrations/xxxx_xx_xx_xxxxxx_create_orders_table.php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('orders', function (Blueprint $table) {
            $table->id();
            $table->string('customer_name');
            $table->string('product');
            $table->unsignedInteger('quantity');
            $table->string('status')->default('pending');
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('orders');
    }
};
#2. 模型:账册翻译官
php
<?php
// app/Models/Order.php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Order extends Model
{
    protected $fillable = [
        'customer_name',
        'product',
        'quantity',
        'status',
    ];
}
#3. 中间件:工坊是否接单
php
<?php
// app/Http/Middleware/EnsureWorkshopOpen.php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class EnsureWorkshopOpen
{
    public function handle(Request $request, Closure $next): Response
    {
        abort_if(config('workshop.closed'), 503, '工坊暂停接单');

        return $next($request);
    }
}
#4. 事件:订单已创建
php
<?php
// app/Events/OrderCreated.php

namespace App\Events;

use App\Models\Order;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class OrderCreated
{
    use Dispatchable, SerializesModels;

    public function __construct(
        public Order $order,
    ) {}
}
#5. 队列监听器:后台准备物料
php
<?php
// app/Listeners/PrepareWorkshopMaterials.php

namespace App\Listeners;

use App\Events\OrderCreated;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Queueable;
use Illuminate\Support\Facades\Log;

class PrepareWorkshopMaterials implements ShouldQueue
{
    use InteractsWithQueue, Queueable;

    public function handle(OrderCreated $event): void
    {
        Log::info('后台物料准备中', [
            'order_id' => $event->order->id,
            'product' => $event->order->product,
            'quantity' => $event->order->quantity,
        ]);
    }
}
#6. 事件注册:把广播接到后场
php
<?php
// app/Providers/AppServiceProvider.php

namespace App\Providers;

use App\Events\OrderCreated;
use App\Listeners\PrepareWorkshopMaterials;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        //
    }

    public function boot(): void
    {
        Event::listen(OrderCreated::class, PrepareWorkshopMaterials::class);
    }
}
#7. 服务层:真正接活的工位
php
<?php
// app/Services/OrderService.php

namespace App\Services;

use App\Events\OrderCreated;
use App\Models\Order;

class OrderService
{
    public function create(array $input): Order
    {
        $order = Order::query()->create([
            'customer_name' => $input['customer_name'],
            'product' => $input['product'],
            'quantity' => (int) $input['quantity'],
            'status' => 'pending',
        ]);

        OrderCreated::dispatch($order);

        return $order;
    }
}
#8. Blade 控制器:给人看的窗口
php
<?php
// app/Http/Controllers/OrderPageController.php

namespace App\Http\Controllers;

use App\Models\Order;
use App\Services\OrderService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;

class OrderPageController extends Controller
{
    public function __construct(
        private OrderService $orders,
    ) {}

    public function index(): View
    {
        return view('orders.index', [
            'orders' => Order::query()->latest()->get(),
        ]);
    }

    public function store(Request $request): RedirectResponse
    {
        $data = $request->validate([
            'customer_name' => ['required', 'string', 'max:50'],
            'product' => ['required', 'string', 'max:100'],
            'quantity' => ['required', 'integer', 'min:1'],
        ]);

        $order = $this->orders->create($data);

        return redirect()
            ->route('orders.index')
            ->with('status', "工单 #{$order->id} 已创建");
    }
}
#9. API 控制器:给程序看的窗口
php
<?php
// app/Http/Controllers/OrderApiController.php

namespace App\Http\Controllers;

use App\Services\OrderService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;

class OrderApiController extends Controller
{
    public function __construct(
        private OrderService $orders,
    ) {}

    public function index(): JsonResponse
    {
        return response()->json(
            \App\Models\Order::query()->latest()->get()
        );
    }

    public function store(Request $request): JsonResponse
    {
        $data = $request->validate([
            'customer_name' => ['required', 'string', 'max:50'],
            'product' => ['required', 'string', 'max:100'],
            'quantity' => ['required', 'integer', 'min:1'],
        ]);

        $order = $this->orders->create($data);

        return response()->json($order, 201);
    }
}
#10. 路由:两种入口,共用同一工坊核心
php
<?php
// routes/web.php

use App\Http\Controllers\OrderPageController;
use App\Http\Middleware\EnsureWorkshopOpen;
use Illuminate\Support\Facades\Route;

Route::middleware(EnsureWorkshopOpen::class)->group(function () {
    Route::get('/orders', [OrderPageController::class, 'index'])->name('orders.index');
    Route::post('/orders', [OrderPageController::class, 'store'])->name('orders.store');
});
php
<?php
// routes/api.php

use App\Http\Controllers\OrderApiController;
use App\Http\Middleware\EnsureWorkshopOpen;
use Illuminate\Support\Facades\Route;

Route::middleware(EnsureWorkshopOpen::class)->group(function () {
    Route::get('/orders', [OrderApiController::class, 'index']);
    Route::post('/orders', [OrderApiController::class, 'store']);
});
#11. Blade 视图:直接给人看的页面
blade
{{-- resources/views/orders/index.blade.php --}}

<h1>订单工坊</h1>

@if (session('status'))
    <p>{{ session('status') }}</p>
@endif

<form method="POST" action="{{ route('orders.store') }}">
    @csrf

    <label>
        客户名
        <input type="text" name="customer_name" required>
    </label>

    <label>
        产品
        <input type="text" name="product" required>
    </label>

    <label>
        数量
        <input type="number" name="quantity" min="1" required>
    </label>

    <button type="submit">创建订单</button>
</form>

<hr>

<ul>
    @foreach ($orders as $order)
        <li>#{{ $order->id }} / {{ $order->customer_name }} / {{ $order->product }} / {{ $order->status }}</li>
    @endforeach
</ul>
#12. 运行这套工坊
bash
php artisan migrate
php artisan queue:work

这套示例最值得你注意的不是语法,而是结构:

text
人类入口和程序入口不同
但它们没有各写一套业务

两边都经过:
Route
-> Middleware
-> Controller
-> OrderService
-> Eloquent
-> Event
-> Queue

这就是 Laravel 的城区感。