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)保证消息可靠投递。
核心思路
- 同一事务内:业务表写入 + outbox消息表写入;
- 独立消费队列轮询outbox表,分发广播/任务;
- 只有消息分发成功后,标记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();
六、线上生产优化补充
- 缓存分层:Redis缓存列表、详情数据,配合Cache::remember自动过期,减少数据库压力;
- 读写分离:config/database配置read/write主机,查询走从库、写入走主库;
- 路由缓存:线上执行
artisan route:cache、config:cache提升启动速度; - 队列监控:Horizon可视化管理Redis队列,配置失败任务重试、延迟任务;
- 异常统一处理:重写Handler,API统一返回JSON错误,记录完整日志。
结语
本篇覆盖的查询性能、实时广播、分层架构、事务一致性、IoC容器均是Laravel从Demo项目过渡到商用系统的必备能力。下一篇进阶分享三,我们将聚焦Horizon队列深度调优、Blade组件高阶、单元测试与接口自动化测试、Laravel Swoole异步改造,补齐高性能、可测试、高并发场景最后一块短板。