Laravel 回调系统的设计 Cybereits.com 白名单系统的设计

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 协议》,转载必须注明作者和本文链接
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
讨论数量: 1

@Summer 已经处理了,谢谢

6年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!