Laravel 回撥系統的設計 Cybereits.com 白名單系統的設計

geyunfei發表於2018-02-01

2017年12月末,Cybereits.com 專案上線,本拐和一眾小夥伴奉命開發了白名單登計系統,在這篇文章中,本拐在以下幾個點和各位看官爸爸做一下分享:

  • 整體系統設計
  • 環境的區分
  • 回撥機制的設計

整體設計

整體設計方式採用了原有的技術棧,即PHP/Laravel + RabbitMQ + Node.js 的形式。
整體系統如下:

其中,PHP/Laravel 提供核心服務,同時,為了保證系統在併發時的響應速度,採用了RabbitMQ 做為訊息佇列,即將長請求(三方API呼叫,郵件通知)做為事件Push到佇列中,而Node.js 實現的Worker只負責將事件取出進行回撥。

----以反射工廠形式實現的環境區分----
由於系統涉及到很多三方API,如簡訊,郵件,個人資訊驗證等,為了保證正式環境與測試環境的區分,採用工廠的形式的實現了這些API,以郵件部分為例,在建立郵件API介面時,採用瞭如下的實現:

private  $_emaillogic = [
    "local"=>\Cybereits\Modules\KYC\API\TestMail::class,
    "testing"=>\Cybereits\Modules\KYC\API\TestMail::class,
    "alpha"=>\Cybereits\Modules\KYC\API\TestMail::class,
     "production"=>\Cybereits\Modules\KYC\API\SendCloud::class,
     ];
public function CreateSendMailLogic(){
return  ReflectionHelper::CreateImplementsLogic($this->_emaillogic);
    }

其中,ReflectionHelper::CreateImplementsLogic 是一個很簡單的工廠實現,只是負責根據系統當前的環境配置返回對應的實現類:

    class ReflectionHelper{
        public static functionCreateImplementsLogic($env_array){
                $env = config("app.env");
                if(array_key_exists($env,$env_array)){
                    $class = $env_array[$env];
                    return new $class;
            }
        return null;
        }
    }

這樣,根據相應lavrel 中.env 的環境配置,我們將不用環境下API的呼叫做了對應的區分。
----回撥機制的設計----

  1. 事件的觸發
    這裡的回撥觸發事件,典形的例子是,使用者在請求驗證碼時,系統會向使用者傳送郵件,再比如,使用者在註冊成功時,會傳送註冊成功的郵件。
    雖然不同的事件五花八門,但是總結下來,無外乎資料庫的增,刪,改操作。 而Laravel 中對資料庫實現Model 已經有了封裝的操作,因此,只要監聽對應的Model的created,updated,deleted事件即可,同時,為了保證系統的效率,有相應的事件以後,系統只是將對應的事件push到訊息對佇列中。
    為了實現系統的可擴充套件性,我們採用如下方式:
    將所有要處理的事件都儲存在配置檔案中
    底層實現一個observer,只在事件發生時將事件以 {event,model}的形式傳送到相應的佇列中。
    底層實現一個observerLoader ,在appServiceProvider中進行呼叫,載入所有的事件
    其中,配置檔案示意如下:

    <?php
    return [
    "\Cybereits\Modules\KYC\Model\EmailCheck"=>"\Cybereits\Common\Event\EventObserver",
    "\Cybereits\Modules\KYC\Model\EthAddress"=>"\Cybereits\Common\Event\EventObserver"
    ];

而observer 則也是一個很簡單的操作。

    class   EventObserver
    {
            use RaiseEvent;
            protected $event_queue=null;
            public function created($data)
            {
                     $this->_addToQueue($data,'created');
            }
            public function updated($data)
            {
                    $this->_addToQueue($data,'updated');
            }
            public function deleted($data)
            {
                    $this->_addToQueue($data,"deleted");
            }
            private function_addToQueue($data,$event,$messageType = "")
            {
                    $model_class = get_class($data);
                    $queueData=(object)array();
                    $queueData->event = $event;
                    $queueData->time=date("Y-m-d H:i:s");
                    $queueData->model=$model_class;
                    $data->queueKey=$event;
                    $queueData->data=$data;
                    $queueData->messagetype=$messageType;
                    $queueData->source=null;
                 $this->AddQueueEvent($this->event_queue, $queueData);
            }
    }

這裡用了 RaiseEvent 這個特性,設計這個特性有兩個呼叫:

  1. AddQueueEvent ,在observer 的_addToQueue中呼叫,將所有的事件臨時儲存在一個陣列中 。
  2. RaiseEvent , 在controller 基類中呼叫,將組中的事件依次push到訊息佇列中。
    通過這種設計,將事件回撥的處理與業務實現本身完全分隔開,達到系統的可配置 。
  3. 事件的回撥
    當事件被推送到佇列中後,node.js 的worker 將事件從佇列中取出,原封不動的回撥給PHP服務,為了保證維護,所有的事件都回撥一個PHP服務/api/event/queuecallback
    當api/event/queuecallback 被呼叫時,收到的資訊有:
    發生的模型 class ,2. 模型的資料 data ,3. 模型操作的事件 event
    這時callback 只要根據這三種資料找到對應的回撥處理類進行處理即可。
    在回撥處理類上,與觸發型別,我們做了如下工作:
    所有要處理的事件與處理類的對應關係都儲存在配置檔案中
    實現了一個從配置檔案載入處理類並處理回撥業務的Handler類
    為了為方便,我們設計了一個基本的Ihandler 介面。
    其中,配置檔案如下示:

    <?php
    return [
     "Cybereits\Modules\KYC\Model\EmailCheck"=>[
            "created"=>[
             "\Cybereits\Modules\KYC\Handler\SendMail",
            ],
        ],
        "Cybereits\Modules\KYC\Model\EthAddress"=>[
            "created"=>[
             "\Cybereits\Modules\KYC\Handler\SendRegMail"
            ]
        ]
     ];

注意,這裡與event 的不同,我們在這裡設定了Model->event ->handler 的三級配置.
載入回撥的類也很簡單:

    class   EventHandler
    {
     public function Handle($queueData)
            {
                    $event = $queueData->event;
                    $model = $queueData->model;
                    $data =  $queueData->data;

                    $cfg = config("handler");
                    if (array_key_exists($model, $cfg) === true) {
                            $setting = $cfg [$model];
                            if (array_key_exists($event, $setting) === true) {
                                    $handleClasses = $setting [$event];
                                    foreach ($handleClasses as $handleClass) {          
                                            $interface = class_implements($handleClass);
                                            if(array_key_exists("Cybereits\Common\Event\IHandleLogic", $interface)) {
                                                    $handleObj = new$handleClass;
                                                    $handleObj -> Handle($data);
                                            }
                                    }
                            }
                    }
            }
    }

那麼,以配置檔案中的SendMail為例,我們的回撥就會變的很簡單:

    class SendMail implements IHandleLogic
    {
            public function Handle($data)
            {
                    $mail = $data["email"];
                    $code = $data["checkcode"];
                    $fac = new APIFactory();
                    $sendCloud = $fac->CreateSendMailLogic();
                    $sendCloud->SendCheckCodeEmail($mail, $code);
            }
    }

通過這種機制,我們將事件的觸發,回撥做了完全的分離,在這個應用下,看似有些過渡設計,實際上,這是一種非常高效並且易於維護的設計,因為咔咔買房的服務部分,好多邏輯都是使用這種方式進行的解耦。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章