背景

背景就是产品经理提了一个需要,实现工作赠送积分(不能应用),签收后积分理论到账,如果遇到退款须要回收积分,工作是大略是这样的:

  1. 每天首次退出购物车赠送 10 积分
  2. 每天首单能够赠送 100 积分
  3. 购物累积金额达到 99 元赠送 100 积分
  4. 购物次数满 10 次赠送 100 积分
  5. 每日签到送 10 积分
  6. 还有很多奇奇怪怪的工作...

实现过程

剖析

当退出购物车时赠送积分,工作完结,当购买商品时就有可能会同时命中多个条件同时赠送积分,命中的所有条件都赠送后,工作完结。

剖析完需要,接下来就想如何实现,最简略的办法也就是 if else 实现:

// 领取胜利触发赠送积分if ("当天首单") { // Reward shopping points }if ("累积99元") { // Reward shopping points }if ("买满10次") { // Reward shopping points }// ...

提需要的时候产品曾经想到二期的积分工作需要了,所以随着工作的增多,可维护性肯定会升高,所以立马否决了用if else 实现的想法

紧接着想到了之前做领取时用到的「简略工厂」+ 「策略模式」教训,应该是有符合要求的设计模式能解决这类问题。因为整体流程是一条直线的流程,顺次执行,就想到责任链模式。通过查问相干材料,责任链模式的变种「管道模式」仿佛更适宜利用至此。

管道模式

管道模式也称为流水线模式,英文:Pipeline。

看到 Pipeline 这个单词十分相熟,仿佛在那里见过,思来想去,是在 Laravel 外面见过,之前剖析 Laravel 依赖注入和管制反转 时见到过。

Laravel 通过 Pipeline 实现 Middleware: https://github.com/laravel/framework/blob/9.x/src/Illuminate/Foundation/Http/Kernel.php#L131

use Illuminate\Routing\Pipeline;protected function sendRequestThroughRouter($request){    $this->app->instance('request', $request);    Facade::clearResolvedInstance('request');    $this->bootstrap();    return (new Pipeline($this->app))                ->send($request)                ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)                ->then($this->dispatchToRouter());}

持续往上追 Pipeline 的实现,发现 Laravel 是实现了一个 Pipleline 契约接口,实现了两个管道别离是专用的Pipleline和一个 Routing 相干的 Pipleline,其中Routing Pipleline是继承了专用的 Pipleline 重写了局部办法。

Pipleline 契约接口

![上传中...]()

  • send() 须要传递的数据。
  • through() 须要解决的工作
  • via() 调用的办法名,默认为 handel()
  • then() 对于返回数据的解决

看到这里,既然Laravel 曾经实现了 Pipleline 的专用办法,那就能够间接拿来用了,刚开始还想着要实现的话还得加加班呢,当初不必了。真优雅~

编码

整体构建目录

├── PointTask│   ├── OverRmb.php          // 满 N 元工作│   ├── SignIn.php           // 签到工作│   ├── TodayFirst.php       // 每日首单任务│   ├──│   ├── PointTask.php        // abstract 束缚│   └── PointTaskService.php // 对外调用办法

既然要思考到当前的批改以及通用性,那就要形象出专用办法,对立继承实现。

通过剖析次要办法有两个:别离是发送积分和回收积分,所以先形象这两个办法。

abstract class PointTask{    // 发送积分    abstract function send($next, $orderInfo);    // 回收积分    public function recycle($next, $orderInfo)    {        return $next($orderInfo);    }}

因为有些工作是只有赠送,没有回收的状况,所以定义了 abstract 形象办法,而不是 interface ,这样在具体任务的实现时能够不去实现 recycle 办法。

  • 每日首单任务

    class TodayFirst extends PointTask{    function send($next, $orderInfo) {        // 有订单间接执行下一个工作        if (!app(PayOrderService::class)->isTodayFirst($orderInfo['orderSn'])) {            return $next($orderInfo);        }        // 赠送积分        app(PayOrderService::class)->sendPoint(100);        return $next($orderInfo);    }    function recycle($next, $orderInfo) {        // 回收积分, code...        $next($orderInfo);    }}
  • 买满多少钱赠送积分

    class OverRmb extends PointTask{    function send($next, $orderInfo) {        // 小于 100 元间接执行下一个工作        if ($orderInfo['price'] < 100) {            return $next($orderInfo);        }        // 赠送积分, code...        return $next($orderInfo);    }    function recycle($next, $orderInfo) {        // 回收积分, code...        $next($orderInfo);    }}
  • 每日签到

    class SignIn extends PointTask{    function send($next, $orderInfo)    {        // 已签到间接执行下一个工作        if (app(UserService::class)->todayIsSinIn()) {            return $next($orderInfo);        }        // 赠送积分, code...        app(PayOrderService::class)->sendPoint(10);        return $next($orderInfo);    }}

案例曾经实现了办法的形象,实现了 3 个具体积分工作,接下来编写 PointTaskService 实现 Pipeline 的组织。对 Laravel 提供的 Pipeline 不太明确的敌人,能够参考下方的参考文章。

PointTaskService

class PointTaskService{    // 定义了可能同时触发的工作    public $shopping = [TodayFirst::class, OverRmb::class];    // 购物赠送积分    public function shoppingSend($orderSn) {        $orderInfo = app(PayOrderService::class)->getOrderInfoByOrderNo($orderSn);        return (new Pipeline(app()))            ->send($orderInfo)            ->via('send')            ->through($this->shopping)            ->thenReturn();    }    // 购物退款回收积分    public function shoppingRecycle($orderSn) {        $orderInfo = app(PayOrderService::class)->getOrderInfoByOrderNo($orderSn);        return (new Pipeline(app()))            ->send($orderInfo)            ->via('recycle')            ->through($this->shopping)            ->thenReturn();    }    // 每日签到    public function signIn() {        return (new Pipeline(app()))            ->via('send')            ->through(SignIn::class)            ->thenReturn();    }}

thenReturn() 办法

thenReturn() 办法是对Pipleline 契约接口的 then() 办法的包装,默认的返回值是调用 send() 时传入的参数,如果对返回值须要再进行解决,则可调用 then(), 传入一个匿名函数进行解决。

领取胜利后调用:

if ($isPaid) {   // 赠送积分实效能够不必那么及时,可推到队列异步执行。   app(PointTaskService::class)->shoppingSend("0722621373");}

退款胜利后调用:

if ($isRefund) {   app(PointTaskService::class)->shoppingRecycle("0722621373");}

每日签到调用:

if ($signIn) {   app(PointTaskService::class)->signIn();}

文件看起来仿佛挺多的,但条理还是比拟清晰的:

  1. 如有新工作,则新建一个工作类继承 PointTask 实现 send 办法,如有可能发出积分则再实现 recycle 办法。
  2. 再在 PointTaskService 对外开放的 Service 中退出到指定地位,即可实现,不会影响到其余的业务逻辑。
  3. 已有的调用处也不必变动代码。

总结

  1. 认真剖析过的源码可能会遗记,但能在适合的工夫回想起来就证实过后是无效的剖析浏览。
  2. 平时缝缝补补的小需要遇到糟心的代码根本也是往上持续堆代码,但如果有机会接手残缺的性能点,那就尽可能的写好点吧。

源码

https://github.com/zxr615/rewrite-pay-module/tree/main/app/Http/Services/PointTask

参考

Laravel 中的 Pipeline — 管道设计范式

Pipeline 管道操作实现申请中间件过滤