前言
在微信朋友圈中经常见到一种诱导分享型的活动,活动规则如:分享给3位好友,即可开启礼包,领取优惠券。整个流程大致分为:领取任务、朋友或自己帮忙执行任务、兑换奖品。因此我把它定义为任务型活动。
在实现这类型活动的后端接口时,以最直接的MVC去实现了。但这样的活动,一两个月来一次,每次的任务规则,和兑换条件又有些许不一样。于是,顺其自然的在原有的基础上去增加case去区分逻辑,这样的处理方式使得Model层越来越臃肿。所以,决定用监听者模式去重构。
一、目录结构
- 接口层
- TaskController
- 模型
- TaskModel
- 事件
- 基础类 Base
- 领取任务 Receive
- 朋友或自己帮忙执行任务 Execute
- 兑换奖品 Exchange
- 监听者
- Common 公用处理逻辑
- Candle 点蜡烛活动的处理逻辑(举个栗子)
二、处理逻辑---- 点蜡烛活动为例
活动规则:需要朋友好帮忙点亮3根蜡烛,才可兑换优惠券。
<small>ThinkPHP框架实现为例</small>
TaskController
<small>统一处理接口,通过参数<code>activity</code>区分不同活动</small>
/**
* 拆礼包一类任务活动的接口
* Class TaskController
* @package Activity\Controller
*/
class TaskController extends ApiController
{
/**
* 领取活动任务
*/
public function receive_post()
{
//获取参数
$activity = I('request.activity', '');//不同活动,传不同活动指定的type
//其他逻辑...
$param = array();
$param['activity'] = $activity;
$taskModel = D('Activity/Task');
$result = $taskModel->receiveTask($param);
exit(json_encode($result));
}
/**
* (朋友帮我/自己)执行任务
*/
public function execute_post()
{
//获取参数
$activity = I('request.activity', '');//不同活动,传不同活动指定的type
//其他逻辑...
$param = array();
$param['activity'] = $activity;
//朋友帮忙
$taskModel = D('Activity/Task');
$ret = $taskModel->executeTask($param);
exit(json_encode($result));
}
/**
* 兑换礼品券
*/
public function exchange_post()
{
//获取参数
$activity = I('request.activity', '');//不同活动,传不同活动指定的type
//其他逻辑...
$param = array();
$param['activity'] = $activity;
//朋友帮忙
$taskModel = D('Activity/Task');
$ret = $taskModel->exchange($param);
exit(json_encode($result));
}
}
TaskModel
Model层写上对应的执行方法
class TaskModel extends DataModel
{
//属性...
//其他方法...
/**
* 领取任务(发起活动)
* @param $param
* @return array|bool
*/
public function receiveTask($param)
{
//领取任务
$receiveEvent = new Event\Receive($param);
$ret = $receiveEvent->execute();
if (!$ret) {
$this->errorCode = $receiveEvent->getErrorCode();
$this->errorDesc = $receiveEvent->getErrorMsg();
return false;
} else {
return true;
}
}
/**
* (朋友帮我/自己)执行任务
* @param $param
* @return bool
*/
public function executeTask($param)
{
//执行任务
$executeEvent = new Event\Execute($param);
$ret = $executeEvent->execute();
if (!$ret) {
$this->errorCode = $executeEvent->getErrorCode();
$this->errorDesc = $executeEvent->getErrorMsg();
return false;
} else {
return true;
}
}
/**
* 兑换礼品
* @param $param
* @return bool
*/
public function exchange($param)
{
$exchangeEvent = new Event\Exchange($param);
$ret = $exchangeEvent->execute();
if (!$ret) {
$this->errorCode = $exchangeEvent->getErrorCode();
$this->errorDesc = $exchangeEvent->getErrorMsg();
return false;
} else {
return true;
}
}
}
Event\Base.class.php
Base.class.php中核心方法execute
- 实例化对应的事件Listener
- 循环处理预先定义所实例化的事件的三个过程on/before/after
- 试过在其中的过程处理失败则返回,不往下执行
/**
* 任务型活动基类
* Class Base
* @package Activity\Model\Task\Event
*/
class Base
{
//设置监听者
protected $listeners = array(
'normal' => array(
'before' => array(),
'on' => array(),
'after' => array()
)
);
protected $errorCode = 0;
protected $errorMsg;
protected $actionName;
protected $activityType = 'normal';
public $activityInfo = array();
/*
* 获取listerner实例
*/
private function getListener($listenerName)
{
return new $listenerName();
}
/*
* 执行
*/
public function execute()
{
$actionName = get_class($this);// Activity\Model\Task\Event\Receive
foreach (array('before', 'on', 'after') as $when) {
foreach ($this->listeners[$this->activityType][$when] as $listenerName) {
$listener = $this->getListener($listenerName);
$methodName = $when . $actionName;
if (method_exists($listener, $methodName)) {
$ret = $listener->$methodName($this);
if (!$ret) {
return false;
}
}
}
}
return true;
}
/*
* 拒绝\终止
*/
public function error($code, $msg)
{
$this->errorCode = $code;
$this->errorMsg = $msg;
}
public function getErrorCode()
{
return $this->errorCode;
}
public function getErrorMsg()
{
return $this->errorMsg;
}
}
为了说明此模式能够比较好的处理「每个活动都会有些许不一样」的情况,以朋友帮忙执行任务为例子。
<small>因为朋友帮忙执行任务前,判断是否允许帮的条件会有所不同</small>
Event\Execute.class.php
/**
* 执行任务
* Class Execute
* @package Activity\Model\Task\Event
*/
class Execute extends Base
{
public function __construct($activityInfo)
{
//设置监听者
$this->listeners = array(
'light_candle' => array(
'before' => array('Common', 'Candle'),
'on' => array('Common'),
'after' => array('Common')
)
);
$this->activityType = $activityInfo['act'];
$this->activityInfo = $activityInfo;
}
public function execute()
{
return parent::execute();
}
}
设置监听者
//比如设置监听者
$this->listeners = array(
'light_candle' => array(
'before' => array('Common', 'Candle'),
'on' => array('Common'),
'after' => array('Common')
)
);
light_candle
接口层接收到的活动type,即参数activity
。
'before'=>array('Common', 'Candle'),代表before阶段执行的Listener。on和after如是。
那么如此配置后,light_candle活动Execute事件的执行过程调用顺序是:
- Listener\Common.class.php的beforeExecute
- Listener\Candle.class.php的beforeExecute
- Listener\Common.class.php的onExecute
- Listener\Common.class.php的afterExecute
所以公共的逻辑可以放到Common的Listener中,每个活动的差异逻辑新建一个对应活动的Listener中,即使再多的活动,也不会耦合,而且每次活动只需要修改差异逻辑即可,大大减少开始和调试时间。
三、新建活动
三个步骤:
- 定义活动的activity。
- 在Event\Receive、Event\Execute、Event\Exchange中设置对应活动的监听者。如:
//比如设置监听者
$this->listeners = array(
'light_candle' => array(
'before' => array('Common'),
'on' => array('Common'),
'after' => array('Common')
),
//增加活动
'activity1' => array(
'before' => array('Common', ...),
'on' => array('Common', ...),
'after' => array('Common', ...)
),
);
- 新建Listener,编写当前活动的差异代码。完成
五、结语
当我完成了重构完,有效提升了开发效率以后。微信要发布了公众号活动xxx规定,封杀了此类分享活动。。。
======================================
Listener\Common的demo伪代码
/**
* 拆礼包一类活动的的通用逻辑
* Class Common
* @package Activity\Model\Task\Listener
*/
class Common
{
/**
* 发起活动前,检查是否有发起过活动
* @param Receive $receive
* @return bool
*/
public function beforeReceive(Receive &$receive)
{
}
/**
* 发起活动过程
* @param Receive $receive
* @return bool
*/
public function onReceive(Receive &$receive)
{
}
/**
* 成功发起活动之后
* @param Receive $receive
* @return bool
*/
public function afterReceive(Receive &$receive)
{
}
/**
* 朋友帮忙前
* @param Execute $execute
* @return bool
*/
public function beforeExecute(Execute &$execute)
{
}
/**
* 朋友帮忙过程
* @param Execute $execute
* @return mixed
*/
public function onExecute(Execute &$execute)
{
}
/**
* 朋友成功帮忙以后
* @param Execute $execute
* @return bool
*/
public function afterExecute(Execute &$execute)
{
}
/**
* 兑换礼品前
* @param Exchange $exchange
* @return bool
*/
public function beforeExchange(Exchange $exchange)
{
}
/**
* 兑换以后,刷新兑换的字段缓存
* @param Exchange $exchange
* @return bool
*/
public function afterExchange(Exchange &$exchange)
{
}
}