<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:atom="http://www.w3.org/2005/Atom"
>
<channel>
<title><![CDATA[Itamour Blog]]></title> 
<atom:link href="https://www.etfail.cn/rss.php" rel="self" type="application/rss+xml" />
<description><![CDATA[Itamour 的分享]]></description>
<link>https://www.etfail.cn/</link>
<language>zh-cn</language>
<generator>www.emlog.net</generator>
<item>
    <title>Laravel 进阶内容分享三｜线上真实性能优化实战案例</title>
    <link>https://www.etfail.cn/?post=14</link>
    <description><![CDATA[<blockquote>
<p>背景：某内部B端订单管理系统，Laravel8 单体项目。业务场景：<strong>订单列表查询接口</strong>。<br />
现象：订单量约80万条，前端分页查询接口，首页加载耗时 2.8‑4s，偶尔超时504；数据库CPU经常冲到80%，高峰期多个用户同时打开列表，接口大面积慢。<br />
原始接口：获取订单列表，附带用户信息、支付记录、订单商品明细，支持多条件筛选、时间范围、状态筛选，返回分页数据。</p>
</blockquote>
<h2>原始问题代码（线上最初版本）</h2>
<p>控制器直接写查询，循环加载关联，大量 <code>if</code> 判断拼接条件。</p>
<pre><code class="language-php">public function index(Request $request)
{
    $status = $request-&gt;status;
    $startTime = $request-&gt;start_time;
    $endTime = $request-&gt;end_time;
    $keyword = $request-&gt;keyword;

    $query = Order::query();
    if (!empty($status)) {
        $query-&gt;where('status', $status);
    }
    if (!empty($startTime)) {
        $query-&gt;where('created_at', '&gt;=', $startTime);
    }
    if (!empty($endTime)) {
        $query-&gt;where('created_at', '&lt;=', $endTime);
    }
    if (!empty($keyword)) {
        $query-&gt;where('order_no', 'like', "%{$keyword}%");
    }

    $list = $query-&gt;paginate(15);

    // 循环内再查关联，典型 N+1 问题
    foreach ($list as $item) {
        $item-&gt;user;
        $item-&gt;payRecord;
        $item-&gt;orderGoods;
    }

    return response()-&gt;json($list);
}</code></pre>
<h3>原始代码存在的性能问题</h3>
<ol>
<li><strong>N+1 查询</strong>：分页15条，循环里每次去查用户、支付、商品；一次接口额外产生 15*3=45 条SQL。</li>
<li><strong>没有索引</strong>：<code>status</code>、<code>created_at</code>、<code>order_no</code> 筛选字段缺少联合索引，全表扫描。</li>
<li><code>like %xxx%</code> 前置通配符，索引失效。</li>
<li>没有缓存，每次请求都直查数据库。</li>
<li>控制器厚重，查询逻辑散落在Controller，无法复用。</li>
<li>时间范围查询没有做边界校验，传非法时间导致数据库异常。</li>
</ol>
<blockquote>
<p>数据库监控：一次接口总共执行 <strong>48条SQL</strong>，执行时间大部分消耗在全表扫描。</p>
</blockquote>
<hr />
<h2>第一轮优化：代码层面优化（不改动数据库）</h2>
<h3>1. 使用 <code>when()</code> 动态条件 + <code>with()</code> 预加载消除N+1</h3>
<pre><code class="language-php">public function index(Request $request)
{
    $list = Order::query()
        -&gt;when($request-&gt;status, fn($q) =&gt; $q-&gt;where('status', $request-&gt;status))
        -&gt;when($request-&gt;start_time, fn($q) =&gt; $q-&gt;where('created_at', '&gt;=', $request-&gt;start_time))
        -&gt;when($request-&gt;end_time, fn($q) =&gt; $q-&gt;where('created_at', '&lt;=', $request-&gt;end_time))
        -&gt;when($request-&gt;keyword, function ($q) use ($request) {
            // 去掉前置%，改为后缀匹配，让索引生效
            $q-&gt;where('order_no', 'like', "{$request-&gt;keyword}%");
        })
        // 预加载关联，一次性3条SQL取出全部关联数据
        -&gt;with(['user', 'payRecord', 'orderGoods'])
        -&gt;paginate(15);

    return response()-&gt;json($list);
}</code></pre>
<p>优化后效果：SQL数量从48条降到 <strong>4条SQL</strong>，接口耗时从3s下降到1.2~1.6s。</p>
<blockquote>
<p>但高峰期并发上来，依然会到1s以上，数据库CPU压力依旧不小。</p>
</blockquote>
<h2>第二轮优化：数据库索引优化</h2>
<p>分析慢查询日志，筛选条件经常组合：<code>status + created_at</code>。<br />
创建联合索引迁移文件：</p>
<pre><code class="language-php">// create_index_orders_status_created_at.php
public function up()
{
    Schema::table('orders', function ($table) {
        // 常用筛选联合索引
        $table-&gt;index(['status', 'created_at']);
        // 订单号单独索引，用于订单号搜索
        $table-&gt;index('order_no');
    });
}</code></pre>
<p>执行迁移：</p>
<pre><code class="language-bash">php artisan migrate</code></pre>
<blockquote>
<p>注意：不要盲目建索引，索引会降低写入性能；只给查询高频字段建立索引。</p>
</blockquote>
<p>建立索引之后，大部分条件查询走索引，<strong>耗时降到 400‑700ms</strong>。<br />
但是：无筛选条件的“全部订单”首页，仍然扫描大量数据，700ms左右，并发高时压力依旧。</p>
<h2>第三轮优化：增加Redis缓存，热点页面缓存</h2>
<p>业务特点：订单列表数据允许短时间延迟（5秒缓存窗口，B端业务可接受）。<br />
使用 <code>Cache::remember</code> 对分页结果做缓存；<strong>缓存key需要包含所有筛选条件、页码、每页条数</strong>。</p>
<pre><code class="language-php">public function index(Request $request)
{
    $params = $request-&gt;only(['status','start_time','end_time','keyword','page','per_page']);
    // 生成唯一缓存key
    $cacheKey = 'order:list:' . md5(json_encode($params));

    $list = Cache::remember($cacheKey, 5, function () use ($request) {
        return Order::query()
            -&gt;when($request-&gt;status, fn($q) =&gt; $q-&gt;where('status', $request-&gt;status))
            -&gt;when($request-&gt;start_time, fn($q) =&gt; $q-&gt;where('created_at', '&gt;=', $request-&gt;start_time))
            -&gt;when($request-&gt;end_time, fn($q) =&gt; $q-&gt;where('created_at', '&lt;=', $request-&gt;end_time))
            -&gt;when($request-&gt;keyword, function ($q) use ($request) {
                $q-&gt;where('order_no', 'like', "{$request-&gt;keyword}%");
            })
            -&gt;with(['user', 'payRecord', 'orderGoods'])
            -&gt;paginate(15);
    });

    return response()-&gt;json($list);
}</code></pre>
<blockquote>
<p>缓存失效策略：订单新增/修改/关闭时，清除相关缓存</p>
<pre><code class="language-php">// 订单更新后清除订单列表缓存
Cache::forget('order:list:*');</code></pre>
<p>这里生产环境不建议直接 <code>forget通配符</code>，redis需要开启key扫描；更好的做法：业务更新时，只清除用户相关的业务缓存key。</p>
</blockquote>
<p>优化后效果：</p>
<ul>
<li>缓存命中：接口 <strong>30‑80ms</strong>，几乎不打数据库；</li>
<li>缓存未命中：400‑700ms；</li>
<li>数据库CPU高峰期从80%降到20‑30%，不再出现接口超时504。</li>
</ul>
<h2>第四轮进阶优化：大表深度分页问题暴露</h2>
<p>当用户翻到第100页之后，<code>paginate()</code>底层 <code>limit offset 1500,15</code>，offset越大，数据库扫描数据越多，翻页越慢。</p>
<blockquote>
<p>问题：offset分页，深度分页性能断崖下跌。</p>
</blockquote>
<p><strong>方案：改用游标分页（Keyset分页），适合列表场景</strong></p>
<pre><code class="language-php">// 前端传最后一条记录id last_id
$lastId = $request-&gt;last_id;

$query = Order::query()
    -&gt;when($lastId, fn($q) =&gt; $q-&gt;where('id','&lt;',$lastId))
    -&gt;when($request-&gt;status, fn($q) =&gt; $q-&gt;where('status', $request-&gt;status))
    -&gt;when($request-&gt;start_time, fn($q) =&gt; $q-&gt;where('created_at', '&gt;=', $request-&gt;start_time))
    -&gt;when($request-&gt;end_time, fn($q) =&gt; $q-&gt;where('created_at', '&lt;=', $request-&gt;end_time))
    -&gt;with(['user','payRecord','orderGoods'])
    -&gt;orderBy('id','desc')
    -&gt;limit(15)
    -&gt;get();</code></pre>
<blockquote>
<p>不再使用offset，利用主键索引，无论翻多少页性能稳定。<br />
缺点：传统页码（第N页）无法使用，只适合“上拉加载更多”。</p>
</blockquote>
<h2>第五轮：业务架构优化，把查询逻辑迁移到Repository</h2>
<p>把查询逻辑抽离，控制器只负责入参接收、返回响应。</p>
<pre><code class="language-php">// app/Repositories/OrderRepository.php
class OrderRepository
{
    public function getList(array $params)
    {
        return Order::query()
            -&gt;when($params['status']??null, fn($q) =&gt; $q-&gt;where('status', $params['status']))
            -&gt;when($params['start_time']??null, fn($q) =&gt; $q-&gt;where('created_at', '&gt;=', $params['start_time']))
            -&gt;when($params['end_time']??null, fn($q) =&gt; $q-&gt;where('created_at', '&lt;=', $params['end_time']))
            -&gt;when($params['keyword']??null, function ($q) use ($params) {
                $q-&gt;where('order_no', 'like', "{$params['keyword']}%");
            })
            -&gt;with(['user','payRecord','orderGoods']);
    }
}</code></pre>
<p>控制器变得很轻薄：</p>
<pre><code class="language-php">public function index(Request $request, OrderRepository $orderRepo)
{
    $params = $request-&gt;only(['status','start_time','end_time','keyword','page','per_page']);
    $cacheKey = 'order:list:' . md5(json_encode($params));

    $list = Cache::remember($cacheKey,5,function() use($params,$orderRepo){
        return $orderRepo-&gt;getList($params)-&gt;paginate(15);
    });
    return response()-&gt;json($list);
}</code></pre>
<h2>优化前后对比汇总表</h2>
<table>
<thead>
<tr>
<th>优化阶段</th>
<th>单次接口SQL数量</th>
<th>平均耗时</th>
<th>数据库CPU</th>
<th>备注</th>
</tr>
</thead>
<tbody>
<tr>
<td>原始版本</td>
<td>48条</td>
<td>2.8‑4s</td>
<td>70‑85%</td>
<td>高峰期504超时</td>
</tr>
<tr>
<td>代码优化（with+when）</td>
<td>4条</td>
<td>1.2‑1.6s</td>
<td>50‑65%</td>
<td>消除N+1</td>
</tr>
<tr>
<td>增加索引</td>
<td>4条</td>
<td>400‑700ms</td>
<td>35‑50%</td>
<td>索引生效</td>
</tr>
<tr>
<td>增加Redis缓存</td>
<td>命中0‑4条；未命中4条</td>
<td>命中30‑80ms；未命中400‑700ms</td>
<td>20‑30%</td>
<td>解决并发压力</td>
</tr>
<tr>
<td>keyset游标分页</td>
<td>4条</td>
<td>稳定300‑600ms</td>
<td>20‑30%</td>
<td>解决深度翻页慢</td>
</tr>
</tbody>
</table>
<h2>踩过的坑（实战总结）</h2>
<ol>
<li><strong>不要滥用缓存</strong>：实时性极高的业务不能用长缓存；B端5‑10秒窗口是比较稳妥的选择。</li>
<li><strong>索引不是越多越好</strong>，索引会降低insert/update性能。</li>
<li><code>like "%关键词%"</code> 前置通配符索引失效，尽量改成后缀匹配；如果必须全文检索，考虑引入Elasticsearch。</li>
<li>大表尽量避免 offset深度分页，优先keyset游标分页。</li>
<li>N+1问题肉眼很难发现，建议开启 <code>debugbar</code> 开发环境查看SQL数量。</li>
<li>Cache::remember 内部发生异常不会自动清除缓存，要做好异常捕获。</li>
</ol>
<h2>后续可继续演进方向</h2>
<ol>
<li>数据量继续上涨，可做读写分离，查询走从库；</li>
<li>高频复杂筛选列表，迁移ES搜索引擎；</li>
<li>大表做分表，按时间水平分表；</li>
<li>使用Horizon队列做预计算统计，把统计计算从接口移到异步。</li>
</ol>]]></description>
    <pubDate>Tue, 11 Aug 2026 10:23:43 +0800</pubDate>
    <dc:creator>itamour</dc:creator>
    <guid>https://www.etfail.cn/?post=14</guid>
</item>
<item>
    <title>Laravel 进阶内容分享（二）</title>
    <link>https://www.etfail.cn/?post=13</link>
    <description><![CDATA[<h2>一、Eloquent查询构造器高阶优化：告别低效循环查询</h2>
<p>基础查询仅满足简单CRUD，生产环境多条件筛选、关联统计、分页场景极易出现N+1查询、冗余条件判断问题。</p>
<h3>1. when() 动态条件，消除多层if嵌套</h3>
<p>传统写法大量if拼接查询，可读性差：</p>
<pre><code class="language-php">// 低效写法
$query = User::query();
if($request-&gt;status) $query-&gt;where('status', $request-&gt;status);
if($request-&gt;keyword) $query-&gt;where('name','like',"%{$request-&gt;keyword}%");
$list = $query-&gt;paginate(15);</code></pre>
<p>使用when优雅改造：</p>
<pre><code class="language-php">$list = User::query()
    -&gt;when($request-&gt;status, fn($q)=&gt;$q-&gt;where('status',$request-&gt;status))
    -&gt;when($request-&gt;keyword, fn($q)=&gt;$q-&gt;where('name','like',"%{$request-&gt;keyword}%"))
    -&gt;paginate(15);</code></pre>
<h3>2. 子查询、关联预加载杜绝N+1</h3>
<pre><code class="language-php">// 预加载关联with，一次性查询用户+订单
$users = User::with(['orders'=&gt;fn($q)=&gt;$q-&gt;where('pay_status',1)])-&gt;get();

// 子查询统计：获取每个用户订单总数
$users = User::addSelect([
    'order_count' =&gt; Order::selectRaw('count(id)')
        -&gt;whereColumn('user_id','users.id')
])-&gt;get();</code></pre>
<h3>3. 悲观锁/乐观锁解决并发超卖</h3>
<p>电商库存、秒杀场景必须使用锁机制：</p>
<pre><code class="language-php">// 悲观锁：事务内锁定行，防止并发修改
DB::transaction(function(){
    $goods = Goods::where('id',1)-&gt;lockForUpdate()-&gt;first();
    if($goods-&gt;stock &lt; 1) throw new \Exception('库存不足');
    $goods-&gt;decrement('stock');
});

// 乐观锁：版本号无阻塞控制
$goods = Goods::where('id',1)-&gt;where('version',$oldVersion)
    -&gt;update(['stock'=&gt;$stock-1, 'version'=&gt;$oldVersion+1]);
if(!$goods) throw new \Exception('并发冲突，请重试');</code></pre>
<h2>二、Laravel 广播与实时推送完整落地（Echo+Pusher/Redis）</h2>
<p>上篇简单提及消息推送，本篇完整实现前后端实时通知、聊天室、消息提醒，适配APP推送+网页实时刷新双场景。</p>
<h3>1. 环境配置</h3>
<p>.env核心配置（Redis驱动无需第三方付费）</p>
<pre><code class="language-env">BROADCAST_DRIVER=redis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379</code></pre>
<h3>2. 创建广播事件并实现ShouldBroadcast</h3>
<pre><code class="language-php">// 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-&gt;data = $data;
    }
    // 私有频道，指定接收用户
    public function broadcastOn()
    {
        return new PrivateChannel('user.'.$this-&gt;data['user_id']);
    }
}</code></pre>
<h3>3. 触发广播 + 前端Echo监听</h3>
<pre><code class="language-php">// 控制器分发事件
event(new MessagePush([
    'user_id' =&gt; 1001,
    'title' =&gt; '系统通知',
    'content' =&gt; '您的订单已发货'
]));</code></pre>
<p>前端JS监听私有频道：</p>
<pre><code class="language-js">import Echo from 'laravel-echo';
window.Echo = new Echo({broadcaster: 'redis'});
window.Echo.private(`user.1001`)
    .listen('MessagePush', (res)=&gt;{
        console.log('收到实时消息', res.data);
        alert(res.data.content);
    })</code></pre>
<h3>4. FCM移动端推送拓展</h3>
<p>服务端集成Firebase，给安卓/iOS推送离线通知，搭配广播实现在线实时、离线推送完整闭环，适配APP后台消息提醒场景。</p>
<h2>三、架构分层：Service+Repository 解耦臃肿控制器</h2>
<p>很多项目将业务逻辑全部写在Controller，代码上千行、无法复用、单元测试困难，采用<strong>控制器只做转发、Service承载业务、Repository封装数据查询</strong>三层架构规范项目代码。</p>
<h3>1. Repository 数据层（统一封装数据库操作）</h3>
<pre><code class="language-php">// app/Repositories/UserRepo.php
class UserRepo
{
    public function getList($filter)
    {
        return User::query()
            -&gt;when($filter['status']??null, fn($q)=&gt;$q-&gt;where('status',$filter['status']))
            -&gt;paginate(15);
    }
}</code></pre>
<h3>2. Service 业务层（事务、多模型组合逻辑）</h3>
<pre><code class="language-php">// app/Services/UserService.php
class UserService
{
    public function __construct(private UserRepo $repo){}
    public function getUserList($filter)
    {
        // 可增加权限校验、数据转换、缓存逻辑
        return $this-&gt;repo-&gt;getList($filter);
    }
}</code></pre>
<h3>3. 精简控制器</h3>
<pre><code class="language-php">class UserController extends Controller
{
    public function __construct(private UserService $service){}
    public function list(Request $request)
    {
        $data = $this-&gt;service-&gt;getUserList($request-&gt;all());
        return response()-&gt;json($data);
    }
}</code></pre>
<p>分层优势：业务逻辑可复用、数据查询统一维护、单元测试仅需Mock Repository，大幅降低后期迭代维护成本。</p>
<h2>四、数据库事务与Outbox模式解决分布式消息丢失</h2>
<p>普通<code>DB::transaction</code>仅适用于单体单库，当数据库写入+队列广播同时执行，服务器崩溃会出现<strong>数据库入库成功、消息未发送</strong>的数据不一致问题，采用事务发件箱（Outbox Pattern）保证消息可靠投递。</p>
<h3>核心思路</h3>
<ol>
<li>同一事务内：业务表写入 + outbox消息表写入；</li>
<li>独立消费队列轮询outbox表，分发广播/任务；</li>
<li>只有消息分发成功后，标记outbox记录已处理，彻底杜绝消息丢失。
<h3>简化示例</h3>
<pre><code class="language-php">DB::transaction(function(){
// 1. 写入业务订单
$order = Order::create($orderData);
// 2. 事务内写入消息记录
Outbox::create([
    'type' =&gt; 'order_create',
    'payload' =&gt; json_encode($order),
    'is_sent' =&gt; 0
]);
});
// 独立定时任务消费未发送消息
Outbox::where('is_sent',0)-&gt;chunk(100, function($records){
foreach($records as $row){
    event(new OrderPush(json_decode($row-&gt;payload,true)));
    $row-&gt;update(['is_sent'=&gt;1]);
}
});</code></pre></li>
</ol>
<h2>五、服务容器（IoC）高阶用法：依赖注入、绑定接口</h2>
<p>Laravel容器是框架核心，但多数开发者仅使用自动注入，高阶绑定可实现灵活切换驱动、缓存装饰器、管道模式。</p>
<h3>1. 接口绑定，无缝切换存储驱动</h3>
<pre><code class="language-php">// 定义接口
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-&gt;app-&gt;bind(FileStorageInterface::class, OssStorage::class);
    }else{
        $this-&gt;app-&gt;bind(FileStorageInterface::class, LocalStorage::class);
    }
}</code></pre>
<p>控制器直接注入接口，无需修改业务代码即可切换存储方案。</p>
<h3>2. 管道Pipeline链式处理数据</h3>
<p>适合图片处理、参数校验、数据格式化等多步骤流水线场景：</p>
<pre><code class="language-php">$result = Pipeline::send($article)
    -&gt;through([
        FilterSensitiveWord::class, // 过滤敏感词
        GenerateSeoMeta::class,     // 生成SEO标签
        CompressImage::class        // 压缩配图
    ])-&gt;thenReturn();</code></pre>
<h2>六、线上生产优化补充</h2>
<ol>
<li><strong>缓存分层</strong>：Redis缓存列表、详情数据，配合Cache::remember自动过期，减少数据库压力；</li>
<li><strong>读写分离</strong>：config/database配置read/write主机，查询走从库、写入走主库；</li>
<li><strong>路由缓存</strong>：线上执行<code>artisan route:cache</code>、<code>config:cache</code>提升启动速度；</li>
<li><strong>队列监控</strong>：Horizon可视化管理Redis队列，配置失败任务重试、延迟任务；</li>
<li><strong>异常统一处理</strong>：重写Handler，API统一返回JSON错误，记录完整日志。</li>
</ol>
<h2>结语</h2>
<p>本篇覆盖的查询性能、实时广播、分层架构、事务一致性、IoC容器均是Laravel从Demo项目过渡到商用系统的必备能力。下一篇进阶分享三，我们将聚焦<strong>Horizon队列深度调优、Blade组件高阶、单元测试与接口自动化测试、Laravel Swoole异步改造</strong>，补齐高性能、可测试、高并发场景最后一块短板。</p>]]></description>
    <pubDate>Tue, 11 Aug 2026 10:21:16 +0800</pubDate>
    <dc:creator>itamour</dc:creator>
    <guid>https://www.etfail.cn/?post=13</guid>
</item>
<item>
    <title>如何在Laravel中使用推送通知服务</title>
    <link>https://www.etfail.cn/?post=12</link>
    <description><![CDATA[<p>在Laravel中使用推送通知服务通常涉及几个关键步骤，包括配置服务、定义通知、触发通知以及（在前端）接收和处理通知。以下是一个基本的指南，帮助你开始在Laravel中推送通知：</p>
<h3>1. 配置推送服务</h3>
<p>首先，你需要配置你选择的推送服务。这可能包括Firebase Cloud Messaging (FCM)、Apple Push Notification Service (APNS)、Twilio SendGrid（用于电子邮件）、OneSignal等。</p>
<ul>
<li><strong>环境变量</strong>：在<code>.env</code>文件中，添加你的推送服务所需的凭据。</li>
<li><strong>服务提供者</strong>：在<code>config/services.php</code>或类似的配置文件中，设置服务提供者的配置。</li>
<li><strong>广播配置</strong>：如果你使用Laravel Echo和Pusher等实时通信服务，确保在<code>config/broadcasting.php</code>中正确配置了广播驱动。</li>
</ul>
<h3>2. 定义通知类</h3>
<p>Laravel使用通知类来封装通知的逻辑。你可以使用Artisan命令创建一个新的通知类：</p>
<pre><code class="language-bash">php artisan make:notification YourNotificationName</code></pre>
<p>在生成的通知类中，你可以定义<code>toDatabase</code>、<code>toMail</code>、<code>toNexmo</code>、<code>toSlack</code>、<code>toBroadcast</code>等方法，根据你的需求来处理不同类型的通知。</p>
<p>例如，对于推送通知，你可能会使用<code>toNexmo</code>（短信）、<code>toFcm</code>（Firebase Cloud Messaging）或自定义方法来处理。注意，Laravel本身可能不直接支持所有推送服务，因此你可能需要编写自定义的发送逻辑或使用第三方包。</p>
<h3>3. 触发通知</h3>
<p>在你的控制器或其他业务逻辑中，你可以使用<code>Notify</code>门面来触发通知。例如：</p>
<pre><code class="language-php">use Illuminate\Support\Facades\Notification;
use App\Notifications\YourNotificationName;
use App\Models\User;

$user = User::find(1);
Notification::send($user, new YourNotificationName($data));</code></pre>
<h3>4. 接收和处理通知（前端）</h3>
<p>对于前端，如果你使用的是Laravel Echo和Pusher等实时通信服务，你需要设置前端代码来监听通道并处理通知。</p>
<ul>
<li><strong>安装Laravel Echo</strong>：在你的前端项目中安装Laravel Echo。</li>
<li><strong>监听通道</strong>：使用Echo监听特定的通道，并定义当接收到新消息时应该执行的操作。</li>
</ul>
<p>例如，使用JavaScript：</p>
<pre><code class="language-javascript">import Echo from "laravel-echo";

window.Echo = new Echo({
    broadcaster: 'pusher',
    key: process.env.MIX_PUSHER_APP_KEY,
    cluster: process.env.MIX_PUSHER_APP_CLUSTER,
    forceTLS: true
});

window.Echo.channel('your-channel-name')
    .listen('YourEventName', (e) =&gt; {
        console.log(e.message); // 处理接收到的消息
    });</code></pre>
<p>注意：<code>your-channel-name</code>和<code>YourEventName</code>应该与你在Laravel中广播通知时使用的通道和事件名称相匹配。</p>
<h3>5. 调试和测试</h3>
<p>最后，不要忘记进行调试和测试。确保你的通知能够正确地发送到目标用户，并且前端能够正确地接收和处理这些通知。你可以使用Laravel的日志功能、调试器或Xdebug等工具来帮助你进行调试。</p>
<h3>注意事项</h3>
<ul>
<li><strong>安全性</strong>：确保你的推送服务凭据是安全的，不要将它们硬编码到前端代码中。</li>
<li><strong>兼容性</strong>：不同的推送服务可能有不同的要求和限制，确保你的实现与所选服务兼容。</li>
<li><strong>用户隐私</strong>：在发送推送通知时，要尊重用户的隐私和偏好，不要发送未经请求的通知。</li>
</ul>
<p>通过遵循这些步骤，你应该能够在Laravel中成功实现推送通知服务。</p>]]></description>
    <pubDate>Wed, 04 Dec 2024 10:01:17 +0800</pubDate>
    <dc:creator>itamour</dc:creator>
    <guid>https://www.etfail.cn/?post=12</guid>
</item>
<item>
    <title>Laravel消息推送的技术要点</title>
    <link>https://www.etfail.cn/?post=11</link>
    <description><![CDATA[<p>Laravel消息推送的技术要点主要包括以下几个方面：</p>
<h3>1. 推送技术选择</h3>
<ul>
<li><strong>Laravel Echo和Pusher</strong>：使用Laravel Echo和Pusher可以实现实时消息推送。这要求安装Laravel Echo和Pusher的依赖包，并在.env文件中配置Pusher的相关参数。在前端，通过Laravel Echo监听消息。</li>
<li><strong>数据库或Redis作为消息队列</strong>：Laravel支持使用数据库或Redis作为消息队列，将需要推送的消息放入队列中，然后异步处理这些消息。</li>
<li><strong>WebSocket服务器</strong>：使用WebSocket服务器（如Ratchet或Workerman）可以实现自定义的消息推送。这需要对WebSocket服务器进行配置，并编写相应的事件监听和处理逻辑。</li>
</ul>
<h3>2. 推送服务集成</h3>
<p>Laravel可以与多种推送通知服务进行集成，如Firebase Cloud Messaging (FCM)、Apple Push Notification Service (APNS)、Google Cloud Messaging (GCM)等。通过与这些服务的集成，可以向用户发送实时消息和提醒。</p>
<h3>3. 实时事件监听</h3>
<p>Laravel的事件系统允许监听和捕获应用程序中的实时事件。当特定事件发生时，可以触发相应的推送通知。例如，当用户发布新消息时，可以触发一个事件，然后向订阅该事件的用户发送推送通知。</p>
<h3>4. 用户订阅和消息传递</h3>
<p>利用Laravel的用户认证和授权功能，可以实现用户的订阅和消息传递机制。用户可以选择订阅特定的消息类型或主题，并接收相应的推送通知。当有新消息需要传递时，可以利用推送通知服务将消息实时发送给订阅的用户。</p>
<h3>5. 消息推送配置</h3>
<ul>
<li><strong>安装依赖</strong>：根据选择的推送技术，安装相应的依赖包。例如，使用Laravel Echo和Pusher时，需要安装pusher/pusher-php-server和laravel-echo等依赖包。</li>
<li><strong>配置环境变量</strong>：在.env文件中配置推送服务的相关参数，如Pusher的APP ID、APP KEY、APP SECRET和CLUSTER等。</li>
<li><strong>配置广播服务</strong>：在config/broadcasting.php文件中配置广播服务的连接信息。</li>
</ul>
<h3>6. 编写代码实现推送</h3>
<ul>
<li><strong>创建事件</strong>：使用artisan命令创建一个事件类，用于触发消息推送。在事件类中实现事件的逻辑，如广播消息到特定的频道。</li>
<li><strong>触发事件</strong>：在控制器或其他地方触发事件，将需要推送的消息包装成事件数据，并调用event()函数触发事件。</li>
<li><strong>前端监听</strong>：在前端使用Laravel Echo监听指定的频道和事件，当事件发生时执行相应的回调函数处理消息。</li>
</ul>
<h3>7. 测试和调试</h3>
<p>在完成消息推送的实现后，需要进行测试和调试。可以通过记录日志或使用调试工具来检查消息是否成功推送到用户端，并处理可能出现的错误和异常情况。</p>
<p>综上所述，Laravel消息推送的技术要点包括推送技术选择、推送服务集成、实时事件监听、用户订阅和消息传递、消息推送配置、编写代码实现推送以及测试和调试等方面。这些要点涵盖了从技术选择到实现细节的全过程，有助于在Laravel应用程序中实现高效、可靠的消息推送功能。</p>]]></description>
    <pubDate>Wed, 04 Dec 2024 09:58:33 +0800</pubDate>
    <dc:creator>itamour</dc:creator>
    <guid>https://www.etfail.cn/?post=11</guid>
</item>
<item>
    <title>Laravel 数据库快速入门</title>
    <link>https://www.etfail.cn/?post=10</link>
    <description><![CDATA[<p>Laravel 数据库快速入门<br />
Laravel 是一个流行的 PHP 框架，它提供了一套简单且强大的工具用于与数据库进行交互。Laravel 支持多种数据库系统，包括 MySQL、PostgreSQL、SQLite 和 SQL Server。以下是 Laravel 数据库快速入门的指南。</p>
<ol>
<li>配置数据库连接<br />
Laravel 的数据库配置文件位于 config/database.php。在这个文件中，你可以定义所有的数据库连接，并指定默认的数据库连接。配置文件提供了大部分 Laravel 支持的数据库配置示例。</li>
</ol>
<p>默认情况下，Laravel 使用环境变量来管理数据库配置，这使得在不同环境中切换数据库配置变得简单。你可以在 .env 文件中设置这些环境变量，例如：</p>
<p>plaintext<br />
DB_CONNECTION=mysql<br />
DB_HOST=127.0.0.1<br />
DB_PORT=3306<br />
DB_DATABASE=your_database_name<br />
DB_USERNAME=your_username<br />
DB_PASSWORD=your_password<br />
对于 SQLite 数据库，你只需要创建一个 SQLite 数据库文件，并配置 DB_DATABASE 环境变量指向这个文件。</p>
<ol start="2">
<li>使用原生 SQL 查询<br />
一旦配置好数据库连接，你可以使用 Laravel 的 DB facade 来运行原生 SQL 查询。DB facade 为每种类型的查询提供了方法：select、update、insert、delete 和 statement。</li>
</ol>
<p>例如，运行一个 SELECT 查询：</p>
<p>php<br />
use Illuminate\Support\Facades\DB;</p>
<p>$users = DB::select('SELECT * FROM users WHERE active = ?', [1]);<br />
在这个例子中，select 方法的第一个参数是原生 SQL 查询语句，第二个参数是绑定到查询中的参数值，用于防止 SQL 注入。</p>
<ol start="3">
<li>使用查询构造器<br />
Laravel 的查询构造器提供了一种更优雅的方式来构建和运行数据库查询。查询构造器允许你使用链式方法来构建查询，而不需要写原生 SQL。</li>
</ol>
<p>例如，检索所有活跃的用户并按名称排序：</p>
<p>php<br />
$users = DB::table('users')<br />
-&gt;where('active', 1)<br />
-&gt;orderBy('name', 'desc')<br />
-&gt;get();<br />
查询构造器还提供了分页、聚合函数和其他高级功能。</p>
<ol start="4">
<li>使用 Eloquent ORM<br />
Eloquent 是 Laravel 附带的 ORM（对象关系映射器），它提供了一个简单、优雅的 ActiveRecord 实现，用于与数据库交互。每个数据库表都对应一个 Eloquent 模型，用于与该表进行交互。</li>
</ol>
<p>首先，定义一个 Eloquent 模型。例如，定义一个 Flight 模型：</p>
<p>bash<br />
php artisan make:model Flight<br />
然后，你可以使用模型来检索、插入、更新和删除数据。例如，检索所有航班：</p>
<p>php<br />
$flights = App\Flight::all();<br />
或者，根据条件检索航班：</p>
<p>php<br />
$flight = App\Flight::where('active', 1)-&gt;first();<br />
Eloquent 还支持关系、作用域、事件和观察者等高级功能。</p>
<ol start="5">
<li>数据库读写分离<br />
有时候，你可能希望使用一个数据库连接来执行 SELECT 语句，而 INSERT、UPDATE 和 DELETE 语句则由另一个数据库连接来执行。在 Laravel 中，这可以通过在 config/database.php 文件中配置读写分离来实现。</li>
</ol>
<p>例如，配置 MySQL 数据库的读写分离：</p>
<p>php<br />
'mysql' =&gt; [<br />
'read' =&gt; [<br />
'host' =&gt; '192.168.1.1',<br />
],<br />
'write' =&gt; [<br />
'host' =&gt; '196.168.1.2',<br />
],<br />
'sticky' =&gt; true,<br />
'driver' =&gt; 'mysql',<br />
'database' =&gt; 'database',<br />
'username' =&gt; 'root',<br />
'password' =&gt; '',<br />
'charset' =&gt; 'utf8mb4',<br />
'collation' =&gt; 'utf8mb4_unicode_ci',<br />
'prefix' =&gt; '',<br />
],<br />
在这个例子中，read 连接将使用 192.168.1.1 作为主机，而 write 连接将使用 196.168.1.2 作为主机。sticky 选项用于允许 Laravel 立即读取在当前请求周期内写入到数据库的记录。</p>
<p>总结<br />
Laravel 提供了一套简单且强大的工具用于与数据库进行交互。通过配置数据库连接、使用原生 SQL 查询、查询构造器和 Eloquent ORM，你可以轻松地构建复杂的数据库应用程序。希望这份快速入门指南能帮助你快速上手 Laravel 数据库操作。</p>]]></description>
    <pubDate>Tue, 03 Dec 2024 13:23:17 +0800</pubDate>
    <dc:creator>itamour</dc:creator>
    <guid>https://www.etfail.cn/?post=10</guid>
</item>
<item>
    <title>Laravel 路由深度解析</title>
    <link>https://www.etfail.cn/?post=9</link>
    <description><![CDATA[<h1>Laravel 路由深度解析</h1>
<h2>引言</h2>
<p>Laravel，作为PHP领域最流行的框架之一，以其优雅的代码风格、强大的功能集和丰富的社区资源而闻名。在Laravel中，路由（Routing）是应用的核心组成部分，它定义了应用的URL结构及其对应的处理逻辑。本文将深入探讨Laravel路由的基本概念、配置方法、常用功能以及最佳实践，帮助你更好地理解和使用Laravel路由。</p>
<h2>一、Laravel路由基础</h2>
<h3>1.1 路由定义</h3>
<p>在Laravel中，所有路由都在<code>routes</code>目录下的不同文件中定义，如<code>web.php</code>、<code>api.php</code>、<code>console.php</code>和<code>channels.php</code>。其中，<code>web.php</code>用于定义Web界面的路由，<code>api.php</code>用于定义API路由。</p>
<pre><code class="language-php">// routes/web.php
Route::get('/hello', function () {
    return 'Hello, World!';
});</code></pre>
<p>上述代码定义了一个简单的GET路由，当用户访问<code>/hello</code>时，将返回&quot;Hello, World!&quot;。</p>
<h3>1.2 路由方法</h3>
<p>Laravel支持多种HTTP请求方法，包括GET、POST、PUT、DELETE等。你可以通过不同的方法来定义路由：</p>
<pre><code class="language-php">Route::get('/user', 'UserController@show');
Route::post('/user', 'UserController@store');
Route::put('/user/{id}', 'UserController@update');
Route::delete('/user/{id}', 'UserController@destroy');</code></pre>
<h3>1.3 路由参数</h3>
<p>路由参数允许你捕获URL片段，并在路由处理逻辑中使用它们：</p>
<pre><code class="language-php">Route::get('/user/{id}', function ($id) {
    return 'User ID is: ' . $id;
});</code></pre>
<h3>1.4 命名路由</h3>
<p>为路由命名可以使你在应用中更容易地引用它们，尤其是在生成URL或重定向时：</p>
<pre><code class="language-php">Route::get('/user/{id}', function ($id) {
    // ...
})-&gt;name('user.show');</code></pre>
<h2>二、高级路由功能</h2>
<h3>2.1 路由组</h3>
<p>路由组允许你共享路由属性，如中间件、命名空间、前缀等：</p>
<pre><code class="language-php">Route::prefix('admin')-&gt;group(function () {
    Route::get('/dashboard', 'AdminController@dashboard');
    Route::get('/profile', 'AdminController@profile');
});</code></pre>
<h3>2.2 中间件</h3>
<p>你可以为路由分配中间件，以执行特定的过滤逻辑，如身份验证、日志记录等：</p>
<pre><code class="language-php">Route::middleware(['auth'])-&gt;get('/profile', function () {
    // 只有经过身份验证的用户才能访问
});</code></pre>
<h3>2.3 路由模型绑定</h3>
<p>路由模型绑定可以将路由参数自动解析为模型实例，简化控制器中的代码：</p>
<pre><code class="language-php">// 在RouteServiceProvider中绑定模型
public function boot()
{
    parent::boot();

    Route::model('user', User::class);
}

// 使用绑定
Route::get('/user/{user}', function (User $user) {
    // $user已经是自动解析的User模型实例
});</code></pre>
<h3>2.4 隐式路由模型绑定</h3>
<p>Laravel 8及以上版本支持隐式路由模型绑定，无需在<code>RouteServiceProvider</code>中手动绑定：</p>
<pre><code class="language-php">Route::get('/user/{user:id}', [UserController::class, 'show']);</code></pre>
<h2>三、路由缓存</h2>
<p>为了提高应用性能，Laravel提供了路由缓存功能。通过运行<code>php artisan route:cache</code>命令，你可以将所有路由序列化并存储在一个文件中，从而减少框架在每次请求时解析路由定义的开销。</p>
<pre><code class="language-bash">php artisan route:cache</code></pre>
<p><strong>注意</strong>：在缓存路由后，不要修改<code>routes</code>目录下的文件，因为更改将不会生效，直到你清除路由缓存（<code>php artisan route:clear</code>）。</p>
<h2>四、最佳实践</h2>
<ol>
<li><strong>清晰命名</strong>：为路由命名时使用有意义的名称，便于理解和维护。</li>
<li><strong>分组管理</strong>：利用路由组来组织相关路由，提高代码可读性。</li>
<li><strong>利用中间件</strong>：通过中间件来管理路由的访问控制，增强安全性。</li>
<li><strong>文档化</strong>：为你的路由添加注释或文档，帮助团队成员快速理解。</li>
<li><strong>测试</strong>：编写单元测试来验证路由的行为，确保代码质量。</li>
</ol>
<h2>结语</h2>
<p>Laravel路由是构建现代Web应用的重要基石。通过理解路由的基本概念、掌握高级功能以及遵循最佳实践，你可以创建出既高效又易于维护的Web应用。希望本文能帮助你更好地掌握Laravel路由，并在实际项目中灵活运用。</p>]]></description>
    <pubDate>Mon, 02 Dec 2024 10:11:51 +0800</pubDate>
    <dc:creator>itamour</dc:creator>
    <guid>https://www.etfail.cn/?post=9</guid>
</item>
<item>
    <title>Laravel 测试分享</title>
    <link>https://www.etfail.cn/?post=8</link>
    <description><![CDATA[<h1>Laravel 测试分享</h1>
<h2>引言</h2>
<p>Laravel 作为一个流行的 PHP 框架，不仅提供了强大的功能和灵活的架构，还内置了丰富的测试工具。通过编写测试，你可以确保你的代码在修改或扩展时仍然按预期工作。本文将介绍如何在 Laravel 中进行单元测试、功能测试和集成测试，以及如何利用 Laravel 提供的一些测试辅助函数。</p>
<h2>安装 Laravel 和测试环境</h2>
<p>首先，确保你已经安装了 Composer 和 PHP。然后，你可以通过 Composer 创建一个新的 Laravel 项目：</p>
<pre><code class="language-bash">composer create-project --prefer-dist laravel/laravel laravel-test-demo</code></pre>
<p>进入项目目录并安装依赖：</p>
<pre><code class="language-bash">cd laravel-test-demo
composer install</code></pre>
<p>Laravel 使用 PHPUnit 作为测试框架。你可以通过 Laravel 自带的命令来运行测试：</p>
<pre><code class="language-bash">./vendor/bin/phpunit</code></pre>
<h2>编写单元测试</h2>
<p>单元测试主要用于测试单个类的功能。在 Laravel 中，你可以使用 <code>php artisan make:test</code> 命令来创建一个测试类。例如，创建一个名为 <code>ExampleTest</code> 的测试类：</p>
<pre><code class="language-bash">php artisan make:test ExampleTest</code></pre>
<p>这个命令会在 <code>tests/Feature</code> 目录下创建一个新的测试文件。你可以在这个文件中编写你的测试逻辑。</p>
<p>以下是一个简单的单元测试示例：</p>
<pre><code class="language-php">&lt;?php

namespace Tests\Feature;

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

class ExampleTest extends TestCase
{
    /**
     * A basic test example.
     *
     * @return void
     */
    public function testBasicTest()
    {
        $response = $this-&gt;get('/');

        $response-&gt;assertStatus(200);
    }
}</code></pre>
<p>在这个例子中，我们测试了根路由的 GET 请求是否返回 200 状态码。</p>
<h2>编写功能测试</h2>
<p>功能测试通常用于测试应用程序的特定功能。例如，测试用户注册和登录功能。以下是一个功能测试的示例：</p>
<pre><code class="language-php">&lt;?php

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Hash;
use App\Models\User;

class AuthenticationTest extends TestCase
{
    use RefreshDatabase;

    public function testUserCanRegister()
    {
        $response = $this-&gt;post('/register', [
            'name' =&gt; 'John Doe',
            'email' =&gt; 'john@example.com',
            'password' =&gt; 'password',
            'password_confirmation' =&gt; 'password',
        ]);

        $response-&gt;assertRedirect('/home');

        $this-&gt;assertAuthenticatedAs(User::where('email', 'john@example.com')-&gt;first());
    }

    public function testUserCanLogin()
    {
        $user = User::create([
            'name' =&gt; 'John Doe',
            'email' =&gt; 'john@example.com',
            'password' =&gt; Hash::make('password'),
        ]);

        $response = $this-&gt;post('/login', [
            'email' =&gt; 'john@example.com',
            'password' =&gt; 'password',
        ]);

        $response-&gt;assertRedirect('/home');

        $this-&gt;assertAuthenticatedAs($user);
    }
}</code></pre>
<p>在这个例子中，我们使用了 <code>RefreshDatabase</code> trait 来重置数据库状态，确保每个测试都在干净的环境中运行。</p>
<h2>编写集成测试</h2>
<p>集成测试通常用于测试多个组件之间的交互。Laravel 并没有为集成测试提供特定的命令或结构，但你可以将集成测试放在 <code>tests/Feature</code> 或 <code>tests/Integration</code> 目录下（后者需要手动创建）。</p>
<p>以下是一个简单的集成测试示例：</p>
<pre><code class="language-php">&lt;?php

namespace Tests\Integration;

use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;
use App\Models\Post;
use App\Models\User;

class PostTest extends TestCase
{
    use RefreshDatabase;

    public function testUserCanCreatePost()
    {
        $user = User::create([
            'name' =&gt; 'John Doe',
            'email' =&gt; 'john@example.com',
            'password' =&gt; bcrypt('password'),
        ]);

        $this-&gt;actingAs($user)-&gt;post('/posts', [
            'title' =&gt; 'My First Post',
            'content' =&gt; 'This is the content of my first post.',
        ]);

        $this-&gt;assertDatabaseHas('posts', [
            'title' =&gt; 'My First Post',
            'content' =&gt; 'This is the content of my first post.',
            'user_id' =&gt; $user-&gt;id,
        ]);
    }
}</code></pre>
<p>在这个例子中，我们测试了用户是否能够创建帖子，并验证帖子是否存储在数据库中。</p>
<h2>使用测试辅助函数</h2>
<p>Laravel 提供了一些测试辅助函数来简化测试编写。例如：</p>
<ul>
<li><code>assertStatus($code)</code>：断言响应状态码。</li>
<li><code>assertJson($data)</code>：断言响应 JSON 数据。</li>
<li><code>assertSee($text)</code>：断言响应内容中包含特定文本。</li>
<li><code>assertAuthenticatedAs($user)</code>：断言当前用户是特定用户。</li>
<li><code>actingAs($user)</code>：模拟当前用户。</li>
</ul>
<p>这些辅助函数可以大大提高测试代码的可读性和简洁性。</p>
<h2>结论</h2>
<p>通过编写测试，你可以确保你的 Laravel 应用程序在开发过程中保持高质量和稳定性。Laravel 提供了丰富的测试工具和辅助函数，使得编写测试变得更加简单和高效。希望这篇文章能帮助你更好地理解和应用 Laravel 测试。</p>]]></description>
    <pubDate>Mon, 02 Dec 2024 10:11:21 +0800</pubDate>
    <dc:creator>itamour</dc:creator>
    <guid>https://www.etfail.cn/?post=8</guid>
</item>
<item>
    <title>Laravel 队列深度解析</title>
    <link>https://www.etfail.cn/?post=7</link>
    <description><![CDATA[<h1>Laravel 队列深度解析</h1>
<h2>引言</h2>
<p>在构建高性能的Web应用程序时，处理大量并发请求和耗时任务是一个常见的挑战。Laravel队列提供了一种优雅的方式来处理这些任务，使得你的应用程序能够保持响应迅速，同时处理后台作业。本文将深入探讨Laravel队列的基本概念、配置、使用以及最佳实践。</p>
<h2>一、Laravel队列简介</h2>
<h3>1.1 什么是队列？</h3>
<p>队列是一种先进先出（FIFO）的数据结构，用于存储待处理的任务。在Laravel中，队列允许你将耗时的任务（如发送邮件、生成报告等）推迟执行，从而释放Web服务器资源，提高应用程序的响应速度。</p>
<h3>1.2 Laravel队列的优势</h3>
<ul>
<li><strong>提高响应速度</strong>：通过异步处理耗时任务，减少用户等待时间。</li>
<li><strong>负载均衡</strong>：将任务分配给多个工作进程，提高处理效率。</li>
<li><strong>可靠性</strong>：支持任务重试机制，确保任务最终得到处理。</li>
</ul>
<h2>二、配置Laravel队列</h2>
<h3>2.1 安装队列驱动</h3>
<p>Laravel支持多种队列驱动，包括数据库、Beanstalkd、Amazon SQS等。以下以数据库驱动为例进行配置。</p>
<ol>
<li>
<p><strong>配置数据库连接</strong>：<br />
在<code>.env</code>文件中，确保数据库连接配置正确。</p>
<pre><code class="language-plaintext">DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=your_database
DB_USERNAME=your_username
DB_PASSWORD=your_password</code></pre>
</li>
<li>
<p><strong>创建队列表</strong>：<br />
运行以下命令创建队列所需的数据库表。</p>
<pre><code class="language-bash">php artisan queue:table
php artisan migrate</code></pre>
</li>
<li>
<p><strong>配置队列驱动</strong>：<br />
在<code>config/queue.php</code>文件中，设置默认队列驱动为<code>database</code>。</p>
<pre><code class="language-php">'default' =&gt; env('QUEUE_CONNECTION', 'database'),

'connections' =&gt; [
   'database' =&gt; [
       'driver' =&gt; 'database',
       'table' =&gt; 'jobs',
       'queue' =&gt; 'default',
       'retry_after' =&gt; 90,
   ],
   // 其他驱动配置...
],</code></pre>
</li>
</ol>
<h3>2.2 配置队列工作进程</h3>
<p>使用<code>php artisan queue:work</code>命令启动队列工作进程。你可以使用<code>--queue</code>选项指定处理特定队列的任务，或使用<code>--tries</code>选项设置任务重试次数。</p>
<pre><code class="language-bash">php artisan queue:work --queue=high,low --tries=3</code></pre>
<h2>三、使用Laravel队列</h2>
<h3>3.1 创建队列任务</h3>
<p>使用<code>php artisan make:job</code>命令创建一个新的队列任务类。</p>
<pre><code class="language-bash">php artisan make:job SendWelcomeEmail</code></pre>
<p>这将在<code>app/Jobs</code>目录下生成一个<code>SendWelcomeEmail.php</code>文件。</p>
<h3>3.2 定义任务逻辑</h3>
<p>在任务类中，实现<code>handle</code>方法以定义任务的具体逻辑。</p>
<pre><code class="language-php">namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use App\Models\User;
use Illuminate\Support\Facades\Mail;

class SendWelcomeEmail implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    protected $user;

    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct(User $user)
    {
        $this-&gt;user = $user;
    }

    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
        Mail::to($this-&gt;user-&gt;email)-&gt;send(new WelcomeMail($this-&gt;user));
    }
}</code></pre>
<h3>3.3 分发队列任务</h3>
<p>在控制器或其他地方分发队列任务。</p>
<pre><code class="language-php">use App\Jobs\SendWelcomeEmail;
use App\Models\User;

// 假设你有一个用户实例
$user = User::find(1);

// 分发队列任务
SendWelcomeEmail::dispatch($user);</code></pre>
<h2>四、最佳实践</h2>
<h3>4.1 监控与日志</h3>
<ul>
<li><strong>监控队列状态</strong>：使用Laravel Horizon或第三方监控工具监控队列状态和工作进程。</li>
<li><strong>日志记录</strong>：在任务处理过程中添加日志记录，便于调试和追踪问题。</li>
</ul>
<h3>4.2 错误处理与重试</h3>
<ul>
<li><strong>捕获异常</strong>：在<code>handle</code>方法中使用<code>try-catch</code>块捕获异常，避免任务失败导致整个工作进程崩溃。</li>
<li><strong>设置重试逻辑</strong>：在任务类中定义<code>failed</code>方法，处理任务失败时的逻辑，如记录错误日志、发送通知等。</li>
</ul>
<h3>4.3 优化性能</h3>
<ul>
<li><strong>批量处理</strong>：对于大量小任务，考虑使用批量处理来提高效率。</li>
<li><strong>调整工作进程数量</strong>：根据服务器性能和任务量调整工作进程数量，避免资源浪费或任务积压。</li>
</ul>
<h2>结语</h2>
<p>Laravel队列是一个强大的工具，能够帮助你构建高性能、可扩展的Web应用程序。通过合理配置和使用队列，你可以显著提高应用程序的响应速度和处理能力。希望本文能帮助你更好地理解和使用Laravel队列。如果你有任何疑问或建议，请随时留言交流！</p>]]></description>
    <pubDate>Mon, 02 Dec 2024 10:09:55 +0800</pubDate>
    <dc:creator>itamour</dc:creator>
    <guid>https://www.etfail.cn/?post=7</guid>
</item>
<item>
    <title>Laravel Eloquent ORM 进阶分享</title>
    <link>https://www.etfail.cn/?post=6</link>
    <description><![CDATA[<h1>Laravel Eloquent ORM 进阶分享</h1>
<h2>引言</h2>
<p>Laravel 的 Eloquent ORM（Object-Relational Mapping）提供了一种优雅、简洁的方式来与数据库进行交互。它不仅仅是一个简单的 ORM，还包含了丰富的功能，能够帮助开发者高效地管理数据库操作。本文将深入探讨 Eloquent ORM 的一些进阶用法，帮助你更好地利用这一强大的工具。</p>
<h2>一、Eloquent 模型基础回顾</h2>
<p>在深入探讨进阶用法之前，我们先回顾一下 Eloquent 模型的基础。</p>
<h3>1.1 定义模型</h3>
<p>在 Laravel 中，模型通常放在 <code>app/Models</code> 目录下。每个模型都对应数据库中的一张表。例如，我们有一个 <code>User</code> 模型：</p>
<pre><code class="language-php">&lt;?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    // 定义表名（如果不指定，默认为模型名的复数形式）
    protected $table = 'users';

    // 定义主键（如果不指定，默认为 'id'）
    public $primaryKey = 'id';

    // 定义可批量赋值的字段
    protected $fillable = ['name', 'email', 'password'];

    // 定义隐藏字段（不会出现在 JSON 序列化结果中）
    protected $hidden = ['password', 'remember_token'];
}</code></pre>
<h3>1.2 基本查询</h3>
<p>Eloquent 提供了多种方法来执行数据库查询。例如：</p>
<pre><code class="language-php">// 获取所有用户
$users = User::all();

// 根据主键获取单个用户
$user = User::find(1);

// 根据条件获取用户
$user = User::where('name', 'John')-&gt;first();</code></pre>
<h2>二、Eloquent 进阶用法</h2>
<h3>2.1 全局作用域（Global Scopes）</h3>
<p>全局作用域允许你定义适用于模型所有查询的约束条件。例如，我们可能希望所有查询都忽略被软删除的记录：</p>
<pre><code class="language-php">&lt;?php

namespace App\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class User extends Model
{
    use SoftDeletes;

    // 定义全局作用域
    protected static function boot()
    {
        parent::boot();

        static::addGlobalScope('active', function (Builder $builder) {
            $builder-&gt;where('deleted_at', null);
        });
    }
}</code></pre>
<h3>2.2 本地作用域（Local Scopes）</h3>
<p>本地作用域允许你在模型中定义可重用的查询约束。例如，我们定义一个查询活跃用户的本地作用域：</p>
<pre><code class="language-php">&lt;?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    // 定义本地作用域
    public function scopeActive($query)
    {
        return $query-&gt;where('status', 'active');
    }
}

// 使用本地作用域
$activeUsers = User::active()-&gt;get();</code></pre>
<h3>2.3 访问器和修改器（Accessors &amp; Mutators）</h3>
<p>访问器和修改器允许你在获取或设置模型属性时自动执行代码。例如，我们希望自动格式化用户的电子邮件为小写：</p>
<pre><code class="language-php">&lt;?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    // 定义访问器
    public function getEmailAttribute($value)
    {
        return strtolower($value);
    }

    // 定义修改器
    public function setEmailAttribute($value)
    {
        $this-&gt;attributes['email'] = strtolower($value);
    }
}</code></pre>
<h3>2.4 多态关联（Polymorphic Relations）</h3>
<p>多态关联允许你在不同类型的模型之间建立关联。例如，我们有一个 <code>Post</code> 模型，它可以被 <code>User</code> 或 <code>Admin</code> 创建：</p>
<pre><code class="language-php">&lt;?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    public function creator()
    {
        return $this-&gt;morphTo();
    }
}

class User extends Model
{
    public function posts()
    {
        return $this-&gt;morphMany(Post::class, 'creator');
    }
}

class Admin extends Model
{
    public function posts()
    {
        return $this-&gt;morphMany(Post::class, 'creator');
    }
}</code></pre>
<h3>2.5 查询构造器与 Eloquent 的结合使用</h3>
<p>Eloquent 模型提供了丰富的查询方法，但有时你可能需要结合使用查询构造器来实现更复杂的查询。例如：</p>
<pre><code class="language-php">$users = User::whereHas('posts', function ($query) {
    $query-&gt;where('created_at', '&gt;', now()-&gt;subMonth());
})-&gt;with('posts')-&gt;get();</code></pre>
<p>在这个例子中，我们使用了 <code>whereHas</code> 方法来查询拥有最近一个月内发布的文章的用户，并使用 <code>with</code> 方法来预加载这些文章。</p>
<h2>三、总结</h2>
<p>Eloquent ORM 是 Laravel 框架中非常强大且灵活的一部分。通过掌握全局作用域、本地作用域、访问器和修改器、多态关联以及查询构造器与 Eloquent 的结合使用等进阶用法，你可以更高效地管理数据库操作，写出更简洁、可维护的代码。希望本文对你有所帮助，祝你在 Laravel 开发中取得更大的进步！</p>]]></description>
    <pubDate>Mon, 02 Dec 2024 10:08:23 +0800</pubDate>
    <dc:creator>itamour</dc:creator>
    <guid>https://www.etfail.cn/?post=6</guid>
</item>
<item>
    <title>Laravel 事件与监听器分享</title>
    <link>https://www.etfail.cn/?post=5</link>
    <description><![CDATA[<h1>Laravel 事件与监听器分享</h1>
<h2>引言</h2>
<p>在 Laravel 框架中，事件与监听器提供了一种优雅的方式来解耦应用中的逻辑。通过事件系统，你可以在不同的组件之间传递消息，而不需要它们直接相互依赖。这对于维护代码的可维护性和可扩展性非常有帮助。本文将详细介绍 Laravel 中的事件与监听器的基本概念、使用方法以及最佳实践。</p>
<h2>基本概念</h2>
<h3>事件（Events）</h3>
<p>事件是一个在应用中发生的特定动作或行为。在 Laravel 中，事件通常是一个继承自 <code>Illuminate\Foundation\Events\Dispatchable</code> 的类。事件类可以包含任何与事件相关的数据。</p>
<h3>监听器（Listeners）</h3>
<p>监听器是处理事件的类。当事件被触发时，相应的监听器会被调用，并执行其 <code>handle</code> 方法来处理事件。监听器可以执行任何与事件相关的业务逻辑。</p>
<h2>使用方法</h2>
<h3>创建事件</h3>
<p>你可以使用 Artisan 命令来生成一个事件类：</p>
<pre><code class="language-bash">php artisan make:event UserRegistered</code></pre>
<p>这个命令会在 <code>app/Events</code> 目录下创建一个名为 <code>UserRegistered.php</code> 的文件。打开这个文件，你会看到一个类似如下的类：</p>
<pre><code class="language-php">&lt;?php

namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;

class UserRegistered
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public $user;

    /**
     * Create a new event instance.
     *
     * @return void
     */
    public function __construct($user)
    {
        $this-&gt;user = $user;
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return Channel|array
     */
    public function broadcastOn()
    {
        return new PrivateChannel('channel.name');
    }
}</code></pre>
<p>在这个类中，你可以定义与事件相关的任何属性，并在构造函数中初始化它们。</p>
<h3>创建监听器</h3>
<p>同样地，你可以使用 Artisan 命令来生成一个监听器类：</p>
<pre><code class="language-bash">php artisan make:listener SendWelcomeEmail --event=UserRegistered</code></pre>
<p>这个命令会在 <code>app/Listeners</code> 目录下创建一个名为 <code>SendWelcomeEmail.php</code> 的文件，并将其与 <code>UserRegistered</code> 事件关联起来。打开这个文件，你会看到一个类似如下的类：</p>
<pre><code class="language-php">&lt;?php

namespace App\Listeners;

use App\Events\UserRegistered;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;

class SendWelcomeEmail
{
    /**
     * Create the event listener.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Handle the event.
     *
     * @param  \App\Events\UserRegistered  $event
     * @return void
     */
    public function handle(UserRegistered $event)
    {
        // 在这里处理事件逻辑，例如发送欢迎邮件
    }
}</code></pre>
<p>在 <code>handle</code> 方法中，你可以访问事件实例并处理相关的业务逻辑。</p>
<h3>注册事件和监听器</h3>
<p>在 Laravel 中，你需要在 <code>EventServiceProvider</code> 中注册事件和监听器。打开 <code>app/Providers/EventServiceProvider.php</code> 文件，并在 <code>listen</code> 属性中添加你的事件和监听器映射：</p>
<pre><code class="language-php">protected $listen = [
    'App\Events\UserRegistered' =&gt; [
        'App\Listeners\SendWelcomeEmail',
    ],
];</code></pre>
<h3>触发事件</h3>
<p>在应用的任何地方，你都可以使用 <code>event</code> 助手函数来触发事件：</p>
<pre><code class="language-php">use App\Events\UserRegistered;

event(new UserRegistered($user));</code></pre>
<p>当这个事件被触发时，所有注册到这个事件的监听器都会被调用。</p>
<h2>最佳实践</h2>
<ol>
<li><strong>保持事件类简单</strong>：事件类应该只包含与事件相关的数据，而不包含业务逻辑。</li>
<li><strong>单一职责原则</strong>：每个监听器应该只处理一个特定的任务。如果监听器变得过于复杂，考虑将其拆分为多个监听器。</li>
<li><strong>队列处理</strong>：对于耗时较长的任务，可以考虑将监听器放到队列中处理。你可以通过实现 <code>ShouldQueue</code> 接口来将监听器加入队列。</li>
<li><strong>事件日志</strong>：为了调试和监控，可以考虑记录事件的日志。</li>
</ol>
<h2>结论</h2>
<p>Laravel 的事件与监听器系统提供了一种强大的方式来解耦应用中的逻辑，使得代码更加清晰和易于维护。通过合理使用事件和监听器，你可以创建更加灵活和可扩展的应用。希望这篇文章对你有所帮助，祝你在 Laravel 开发中取得更多进展！</p>]]></description>
    <pubDate>Mon, 02 Dec 2024 10:07:55 +0800</pubDate>
    <dc:creator>itamour</dc:creator>
    <guid>https://www.etfail.cn/?post=5</guid>
</item></channel>
</rss>