当前位置: 首页 > 后端技术 > PHP

使用流水线设计模式实现用户的积分任务需求

时间:2023-03-29 18:29:53 PHP

背景背景是产品经理提出完成任务送分的请求。如果有退款,需要找回积分,任务大致是这样的:每天第一次加10个积分到购物车。每天首单可得100积分,购物满99元可得100积分。购物10次可得100积分,每日签到可得100积分。积分送完,任务结束。购买商品时,可以同时命中多个条件,同时给予积分。满足所有条件后,任务结束。分析完需求,接下来就是思考如何实现。最简单的实现方式是ifelse://支付成功触发积分if("当天第一笔订单"){//奖励购物积分}if("累计99元"){//奖励购物积分}if("Buy10times"){//奖励购物积分}//...当需求提出时,产品已经想到了积分任务需求的第二阶段,所以随着任务的增加,可维护性会肯定会减少,所以我马上就否决了用ifelse来实现的想法。于是想到了之前在支付中使用的“简单工厂”+“策略模式”的体验。应该有一个符合要求的设计模式来解决问题。这种问题。因为整个过程是一个直线过程,按顺序执行,责任链模型就浮现在脑海中。通过查询相关资料,似乎职责链模式的变体“流水线模式”更适合这种应用。流水线模式流水线模式也称为流水线模式,英文:Pipeline。看到Pipeline这个词很眼熟,好像在那里见过,想了想,在Laravel里见过,之前在分析Laravel依赖注入和控制反转的时候也见过。Laravel通过Pipeline实现中间件:https://github.com/laravel/framework/blob/9.x/src/Illuminate/Foundation/Http/Kernel.php#L131useIlluminate\Routing\Pipeline;protectedfunctionsendRequestThroughRouter($request){$this->app->instance('request',$request);Facade::clearResolvedInstance('request');$this->bootstrap();返回(新管道($this->app))->发送($request)->through($this->app->shouldSkipMiddleware()?[]:$this->middleware)->then($this->dispatchToRouter());}继续追溯Pipeline的实现,发现Laravel实现了一个Pipleline契约接口,实现了两条pipeline,分别是publicPipleline和Routing-relatedPipleline,其中RoutingPipleline继承了publicPipleline和重写了一些方法。管道合约接口![Uploading...]()send()需要传递的数据。through()待处理的任务via()调用的方法名,默认为handel()then()返回数据的处理见这里,由于Laravel已经实现了Pipleline的public方法,可以直接使用,一开始我以为要实现就得加班加点,现在不需要了。真优雅~编码整体建设目录├──PointTask│├──OverRmb.php//满N元任务│├──SignIn.php//签到任务│├──TodayFirst.php//每日一单任务│├──│├──PointTask.php//抽象约束│└──PointTaskService.php//由于外部调用方法时要考虑到以后的修改和通用性,所以需要对公共方法进行抽象,实现统一继承.经过分析,主要有两个方法:发送点和恢复点,所以先抽象这两个方法。abstractclassPointTask{//发送点abstractfunctionsend($next,$orderInfo);//回收点publicfunctionrecycle($next,$orderInfo){return$next($orderInfo);}}因为有些任务只是礼物,没有回收,所以定义了abstract抽象方法而不是接口,这样在实现具体任务的时候就不能实现回收方法了。每日第一单任务classTodayFirstextendsPointTask{functionsend($next,$orderInfo){//如果有订单,直接执行下一个任务if(!app(PayOrderService::class)->isTodayFirst($orderInfo['orderSn'])){return$next($orderInfo);}//礼物积分app(PayOrderService::class)->sendPoint(100);返回$next($orderInfo);}functionrecycle($next,$orderInfo){//回收点数,code...$next($orderInfo);}}花多少钱获得积分classOverRmbextendsPointTask{functionsend($next,$orderInfo){//100元以下直接执行下一个任务if($orderInfo['price']<100){返回$next($orderInfo);}//礼品积分,代码...return$next($orderInfo);}functionrecycle($next,$orderInfo){//回收点,代码...$next($orderInfo);}}每日签到classSignInextendsPointTask{functionsend($next,$orderInfo){//签到后直接执行下一个任务if(app(UserService::class)->todayIsSinIn()){r返回$next($orderInfo);}//礼品积分,代码...app(PayOrderService::class)->sendPoint(10);返回$next($orderInfo);}}案例完成了方法抽象,实现了三个具体的积分任务。接下来组织编写PointTaskService实现Pipeline,对Laravel提供的Pipeline不了解的朋友可以参考下面的参考文章。PointTaskServiceclassPointTaskService{//定义可能同时触发的任务public$shopping=[TodayFirst::class,OverRmb::class];//购物积分publicfunctionshoppingSend($orderSn){$orderInfo=app(PayOrderService::class)->getOrderInfoByOrderNo($orderSn);return(newPipeline(app()))->send($orderInfo)->via('send')->through($this->shopping)->thenReturn();}//购物退款回收点publicfunctionshoppingRecycle($orderSn){$orderInfo=app(PayOrderService::class)->getOrderInfoByOrderNo($orderSn);return(newPipeline(app()))->send($orderInfo)->via('recycle')->through($this->shopping)->thenReturn();}//每日签到publicfunctionsignIn(){return(newPipeline(app()))->via('send')->through(SignIn::class)->thenReturn();}}thenReturn()方法然后Return()方法是Pipleline合约接口的then()方法的包装器。默认返回值是调用send()时传入的参数。如果需要处理返回值,可以调用then()并传入一个匿名函数,在支付处理成功后调用:if($isPaid){//礼物积分的实际效果可能不是那么及时,并且可以推入队列异步执行。app(PointTaskService::class)->shoppingSend("0722621373");}退款成功后调用:if($isRefund){app(PointTaskService::class)->shoppingRecycle("0722621373");}每日签到调用:if($signIn){app(PointTaskService::class)->signIn();}好像文件挺多的,但是组织的还是比较清晰的:如果有新任务,就新建一个任务继承PointTask的类实现send方法,如果可以回收点则实现recycle方法。然后在对外开放的Service中的指定位置添加PointTaskService,在不影响其他业务逻辑的情况下完成。现有的呼叫站点不需要更改代码。总结仔细分析过的源码可能会忘记,但能够在合适的时候回忆起来,证明是当时有效的分析阅读。通常缝补的小需求,遇到烂代码会继续堆代码,但如果有机会接手完整的功能点,那就尽量多写。源码https://github.com/zxr615/rewrite-pay-module/tree/main/app/Http/Services/PointTask参考Laravel中的Pipeline——PipelineDesignParadigm管道流水线操作实现请求中间件过滤LaravelPipeline组件实现原理