«

Laravel 进阶内容分享(二)

itamour 发布于 阅读:139


一、Eloquent查询构造器高阶优化:告别低效循环查询

基础查询仅满足简单CRUD,生产环境多条件筛选、关联统计、分页场景极易出现N+1查询、冗余条件判断问题。

1. when() 动态条件,消除多层if嵌套

传统写法大量if拼接查询,可读性差:

// 低效写法
$query = User::query();
if($request->status) $query->where('status', $request->status);
if($request->keyword) $query->where('name','like',"%{$request->keyword}%");
$list = $query->paginate(15);

使用when优雅改造:

$list = User::query()
    ->when($request->status, fn($q)=>$q->where('status',$request->status))
    ->when($request->keyword, fn($q)=>$q->where('name','like',"%{$request->keyword}%"))
    ->paginate(15);

2. 子查询、关联预加载杜绝N+1

// 预加载关联with,一次性查询用户+订单
$users = User::with(['orders'=>fn($q)=>$q->where('pay_status',1)])->get();

// 子查询统计:获取每个用户订单总数
$users = User::addSelect([
    'order_count' => Order::selectRaw('count(id)')
        ->whereColumn('user_id','users.id')
])->get();

3. 悲观锁/乐观锁解决并发超卖

电商库存、秒杀场景必须使用锁机制:

// 悲观锁:事务内锁定行,防止并发修改
DB::transaction(function(){
    $goods = Goods::where('id',1)->lockForUpdate()->first();
    if($goods->stock < 1) throw new \Exception('库存不足');
    $goods->decrement('stock');
});

// 乐观锁:版本号无阻塞控制
$goods = Goods::where('id',1)->where('version',$oldVersion)
    ->update(['stock'=>$stock-1, 'version'=>$oldVersion+1]);
if(!$goods) throw new \Exception('并发冲突,请重试');

二、Laravel 广播与实时推送完整落地(Echo+Pusher/Redis)

上篇简单提及消息推送,本篇完整实现前后端实时通知、聊天室、消息提醒,适配APP推送+网页实时刷新双场景。

1. 环境配置

.env核心配置(Redis驱动无需第三方付费)

BROADCAST_DRIVER=redis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379

2. 创建广播事件并实现ShouldBroadcast

// app/Events/MessagePush.php
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;

class MessagePush implements ShouldBroadcast
{
    public $data;
    public function __construct($data)
    {
        $this->data = $data;
    }
    // 私有频道,指定接收用户
    public function broadcastOn()
    {
        return new PrivateChannel('user.'.$this->data['user_id']);
    }
}

3. 触发广播 + 前端Echo监听

// 控制器分发事件
event(new MessagePush([
    'user_id' => 1001,
    'title' => '系统通知',
    'content' => '您的订单已发货'
]));

前端JS监听私有频道:

import Echo from 'laravel-echo';
window.Echo = new Echo({broadcaster: 'redis'});
window.Echo.private(`user.1001`)
    .listen('MessagePush', (res)=>{
        console.log('收到实时消息', res.data);
        alert(res.data.content);
    })

4. FCM移动端推送拓展

服务端集成Firebase,给安卓/iOS推送离线通知,搭配广播实现在线实时、离线推送完整闭环,适配APP后台消息提醒场景。

三、架构分层:Service+Repository 解耦臃肿控制器

很多项目将业务逻辑全部写在Controller,代码上千行、无法复用、单元测试困难,采用控制器只做转发、Service承载业务、Repository封装数据查询三层架构规范项目代码。

1. Repository 数据层(统一封装数据库操作)

// app/Repositories/UserRepo.php
class UserRepo
{
    public function getList($filter)
    {
        return User::query()
            ->when($filter['status']??null, fn($q)=>$q->where('status',$filter['status']))
            ->paginate(15);
    }
}

2. Service 业务层(事务、多模型组合逻辑)

// app/Services/UserService.php
class UserService
{
    public function __construct(private UserRepo $repo){}
    public function getUserList($filter)
    {
        // 可增加权限校验、数据转换、缓存逻辑
        return $this->repo->getList($filter);
    }
}

3. 精简控制器

class UserController extends Controller
{
    public function __construct(private UserService $service){}
    public function list(Request $request)
    {
        $data = $this->service->getUserList($request->all());
        return response()->json($data);
    }
}

分层优势:业务逻辑可复用、数据查询统一维护、单元测试仅需Mock Repository,大幅降低后期迭代维护成本。

四、数据库事务与Outbox模式解决分布式消息丢失

普通DB::transaction仅适用于单体单库,当数据库写入+队列广播同时执行,服务器崩溃会出现数据库入库成功、消息未发送的数据不一致问题,采用事务发件箱(Outbox Pattern)保证消息可靠投递。

核心思路

  1. 同一事务内:业务表写入 + outbox消息表写入;
  2. 独立消费队列轮询outbox表,分发广播/任务;
  3. 只有消息分发成功后,标记outbox记录已处理,彻底杜绝消息丢失。

    简化示例

    DB::transaction(function(){
    // 1. 写入业务订单
    $order = Order::create($orderData);
    // 2. 事务内写入消息记录
    Outbox::create([
        'type' => 'order_create',
        'payload' => json_encode($order),
        'is_sent' => 0
    ]);
    });
    // 独立定时任务消费未发送消息
    Outbox::where('is_sent',0)->chunk(100, function($records){
    foreach($records as $row){
        event(new OrderPush(json_decode($row->payload,true)));
        $row->update(['is_sent'=>1]);
    }
    });

五、服务容器(IoC)高阶用法:依赖注入、绑定接口

Laravel容器是框架核心,但多数开发者仅使用自动注入,高阶绑定可实现灵活切换驱动、缓存装饰器、管道模式。

1. 接口绑定,无缝切换存储驱动

// 定义接口
interface FileStorageInterface
{
    public function save($file);
}
// 本地存储实现
class LocalStorage implements FileStorageInterface{}
// 阿里云OSS实现
class OssStorage implements FileStorageInterface{}

// AppServiceProvider绑定
public function register()
{
    // 根据环境切换存储驱动
    if(env('STORAGE_DRIVER') === 'oss'){
        $this->app->bind(FileStorageInterface::class, OssStorage::class);
    }else{
        $this->app->bind(FileStorageInterface::class, LocalStorage::class);
    }
}

控制器直接注入接口,无需修改业务代码即可切换存储方案。

2. 管道Pipeline链式处理数据

适合图片处理、参数校验、数据格式化等多步骤流水线场景:

$result = Pipeline::send($article)
    ->through([
        FilterSensitiveWord::class, // 过滤敏感词
        GenerateSeoMeta::class,     // 生成SEO标签
        CompressImage::class        // 压缩配图
    ])->thenReturn();

六、线上生产优化补充

  1. 缓存分层:Redis缓存列表、详情数据,配合Cache::remember自动过期,减少数据库压力;
  2. 读写分离:config/database配置read/write主机,查询走从库、写入走主库;
  3. 路由缓存:线上执行artisan route:cache、config:cache提升启动速度;
  4. 队列监控:Horizon可视化管理Redis队列,配置失败任务重试、延迟任务;
  5. 异常统一处理:重写Handler,API统一返回JSON错误,记录完整日志。

结语

本篇覆盖的查询性能、实时广播、分层架构、事务一致性、IoC容器均是Laravel从Demo项目过渡到商用系统的必备能力。下一篇进阶分享三,我们将聚焦Horizon队列深度调优、Blade组件高阶、单元测试与接口自动化测试、Laravel Swoole异步改造,补齐高性能、可测试、高并发场景最后一块短板。