奥丁9
奥丁9
后端
数据库
redis
mysql
mongoDB
达梦
php
laravel
laravel-admin
dcat
表单
表格
java
spring
python
go
c
c++
前端
vue
nodejs
sass/less
html/css
前端框架
javascript
微信生态
公众号
小程序
uniapp
typescript
其他
AI
数据结构
安全
linux
seo
git
健身
算法
正则表达式
docker
待分类
后端
/
php
/
laravel
laravel状态机
1年前
aoding9
108
php
laravel
萌新的时候搞的一个烂尾项目(搞到最后没搞了),最近没事翻以前写的代码想起来这个,顺便水一下。 为了实现多层审批,各种状态切换的功能,一开始直接ifelse,但是搞一半甲方又要改,好吧改完,他又不满意还要改,改来改去把我自己给绕晕了,最后参考网上大佬的文章,自己折腾了一个比较简单的状态机,效率可能低点,但是改起来方便,反正能跑就行,终于达到了甲方爸爸的需求,之后又是各种扯皮,总之就是很逆天。 回到正题,系统包含3层角色:中队->大队->支队,2种流程:外出审批流程和归队销假流程 销假流程和审批类似,也是层级审批,就没画图。   首先针对外出审批LeaveAudit和外出记录LeaveRecord模型,创建两个状态机,定义状态和事件。 然后通过这样`$fsm->trigger($fsm::ZHONGD_RECORD_EVENT, $model, $input);`和这样`$fsm->triggerEventEasy($primaryKey,'agree');`,来触发模型的事件,triggerEventEasy是对trigger的封装,可以根据角色slug,拼接完整的事件名。 当事件触发时,状态机首先会查找并触发can+事件名的方法,以验证当前模型是否可以执行该事件。 然后查找并触发before+事件名的回调,回调预处理方法。 接着更新状态,然后查找触发on+事件名的方法,然后保存。最后再查找触发after+事件名回调 ```php <?php namespace App\Services\StateMachineService; use App\Models\Holiday; use App\Models\LeaveRecord; use App\Models\Snapshot; use App\Models\AdminUser; use App\Models\LeaveAudit; use App\Models\Member; use App\Models\Out; use Dcat\Admin\Models\Role; use Illuminate\Support\Carbon; use Illuminate\Support\Collection; class LeaveAuditFSM extends BaseStateMachineService { // 初始状态 public $init = self::CREATED_STATE; // 状态机管理的模型名称 public $modelClass = LeaveAudit::class; /** * @var LeaveAudit 模型实例 */ public $model; // 定义状态 public const DAD_WAITING_STATE = ['code' => 11, 'name' => '二级待审']; public const DAD_REFUSED_STATE = ['code' => 12, 'name' => '二级拒审']; public const ZHID_WAITING_STATE = ['code' => 21, 'name' => '一级待审']; public const ZHID_REFUSED_STATE = ['code' => 22, 'name' => '一级拒审']; public const ZHID_AGREED_STATE = ['code' => 23, 'name' => '一级通过']; // 定义事件 _EVENT结尾 //撤销 public const CANCEL_EVENT = ['code' => 1, 'from' => self::ANY_STATE, 'to' => self::CANCELED_STATE, 'name' => '撤销']; //三级创建、提交 public const ZHONGD_CREATE_EVENT = ['code' => 2, 'from' => self::CREATED_STATE, 'to' => self::CREATED_STATE, 'name' => '三级创建']; public const ZHONGD_SUBMIT_EVENT = ['code' => 21, 'from' => self::CREATED_STATE, 'to' => self::DAD_WAITING_STATE, 'name' => '三级提交']; //三级更新 public const ZHONGD_UPDATE_EVENT = ['code' => 3, 'from' => self::DAD_REFUSED_STATE, 'to' => self::DAD_WAITING_STATE, 'name' => '三级更新']; //二级创建、提交 public const DAD_CREATE_EVENT = ['code' => 4, 'from' => self::CREATED_STATE, 'to' => self::CREATED_STATE, 'name' => '二级创建']; public const DAD_SUBMIT_EVENT = ['code' => 41, 'from' => self::CREATED_STATE, 'to' => self::ZHID_WAITING_STATE, 'name' => '二级提交']; //二级拒绝 public const DAD_REFUSE_EVENT = ['code' => 5, 'from' => self::DAD_WAITING_STATE, 'to' => self::DAD_REFUSED_STATE, 'name' => '二级拒绝']; //二级同意 public const DAD_AGREE_EVENT = ['code' => 6, 'from' => self::DAD_WAITING_STATE, 'to' => self::ZHID_WAITING_STATE, 'name' => '二级同意']; // 二级更新 public const DAD_UPDATE_EVENT = ['code' => 7, 'from' => self::ZHID_REFUSED_STATE, 'to' => self::ZHID_WAITING_STATE, 'name' => ' 二级更新']; //一级拒绝 public const ZHID_REFUSE_EVENT = ['code' => 8, 'from' => self::ZHID_WAITING_STATE, 'to' => self::ZHID_REFUSED_STATE, 'name' => '一级拒绝']; //一级同意 public const ZHID_AGREE_EVENT = ['code' => 9, 'from' => self::ZHID_WAITING_STATE, 'to' => self::ZHID_AGREED_STATE, 'name' => '一级同意']; // public function canDadCreate() // { // return true; // } public function canCancel() { // 一级同意后无法撤销,已经撤销的也无法撤销 $flag1 = $flag2 = false; if (!$this->inStates([self::ZHID_AGREED_STATE, self::CANCELED_STATE])) { $flag1 = true; } //只有创建者才可以撤销 if ($this->isCreator(Member::find($this->model->member_id)->unit_id)) { $flag2 = true; } return $flag1 && $flag2; } public function onCancel() { $model = $this->model; $member = Member::find($model->member_id); //待审批申请数-1 --$member->is_leave_audit; $member->save(); } // // public function beforeDadCreate() // { // dump('二级创建之前'); // } // // public function afterDadCreate() // { // dump('二级创建之后'); // } // public function onZhongdCreate($data) { $this->createHandle($data); } public function onDadCreate($data) { $this->createHandle($data); } // 提交和创建处理大部分是一样的,但是创建事件不改变状态码,提交事件改变状态码,而且会增加待批假数 public function onZhongdSubmit($data) { $this->createHandle($data); $this->submitHandle(); } public function onDadSubmit($data) { $this->createHandle($data); $this->submitHandle(); } public function updateHandle($data) { // 创建者修改后对快照表的字段进行更新 $model = $this->model; if (!$this->isCreator($model->member->unit_id)) { return; } $out = $model->out; $model->travel_day = $this->getTravelDay($model->return_at, $model->leave_at); $snapshot = $model->snapshot; $snapshot->out = $out->name; $snapshot->is_expend = $out->is_expend; $snapshot->holiday_expend = $this->getHolidayExpend($snapshot->is_expend, $snapshot->holiday_expend); // 计算不扣假的假期 $this->getHolidayUnexpended($out, $model); $snapshot->holidays = json_encode($this->holidays); $snapshot->holiday_unexpended = $this->holiday_unexpended; $snapshot->save(); } // 创建事件的通用处理方法 public function createHandle($data) { // 获取模型 /** @var LeaveAudit $model */ $model = $this->model; // 获取必要的模型 $member = Member::with(['job', 'rank', 'separation', 'unit.roles'])->find($model->member_id); $unit = $member->unit; $job = $member->job; $rank = $member->rank; $separation = $member->separation; /** @var Role $role */ $role = $unit->roles->first(); $out = $model->out; // 设置主表模型参数 $model->unit_id = $unit->id; $model->travel_day = $this->getTravelDay($model->return_at, $model->leave_at); // 创建快照实例 $snapshot = $model->snapshot; // 设置从表模型参数 $snapshot->out = $out->name; $snapshot->is_expend = $out->is_expend; $snapshot->holiday_expend = $this->getHolidayExpend($snapshot->is_expend, $snapshot->holiday_expend); // 计算不扣假的假期 $this->getHolidayUnexpended($snapshot->is_expend, $model); $snapshot->holidays = json_encode($this->holidays); $snapshot->holiday_unexpended = $this->holiday_unexpended; // dd($snapshot); $snapshot->unit = $unit->name; $snapshot->level = $role->level; $snapshot->job = $job->name; $snapshot->rank = $rank->name; $snapshot->separation = $separation->name; $snapshot->name = $member->name; $snapshot->mobile = $member->mobile; $snapshot->is_matrimony = $member->is_matrimony; $snapshot->photo = $member->photo; $snapshot->hometown = $member->hometown; $snapshot->join_at = $member->join_at; $snapshot->order = $unit->order; $snapshot->save(); // ss(__FILE__,__LINE__); } // 提交事件的通用处理 public function submitHandle() { //待审批的外出申请数+1 $this->model->member->is_leave_audit++; $this->model->member->save(); } /** * 根据外出类型 获取消耗的假期天数 * @param $is_expend * @param $travelDay * @return int */ public function getHolidayExpend($is_expend, $travelDay) { if($is_expend === 0){ return 0; } return $travelDay; } /** @var Collection */ public $holidays; public $holiday_unexpended = 0; public function getHolidayUnexpended($is_expend, LeaveAudit $model) { //不耗假的休假类型不计算 if (!$is_expend) { $this->holiday_unexpended = 0; return; } // 查询离开和返回所在年度的所有节假日 $allHolidays = Holiday::active()->where('year', $model->leave_at->year)->orWhere('year', $model->return_at->year)->get(['id', 'name', 'status', 'holidays']); $this->holidays = collect(); $this->holiday_unexpended = 0; // 不耗假天数总数 //循环所在年度的所有节假日 $leave_at = $model->leave_at->toDateString(); $return_at = $model->return_at->toDateString(); $allHolidays->each(function(Holiday $holiday) use (&$leave_at, &$return_at) { $holiday->getHolidays()->each(function($i) use ($holiday, &$leave_at, &$return_at) { //如果在离开返回时间之间 $date = new Carbon($i->date); if ($date->between($leave_at, $return_at, true)) { $i->name = $holiday->name; $this->holidays->push($i); } }); }); // dd($this->holidays->pluck('date')); //排除耗假的节假日,然后用date分组,去重 $this->holiday_unexpended = $this->holidays->filter(function($i) { return $i->is_expend === 0; })->groupBy('date')->count(); } // public function onDadUpdate($data) { $this->updateHandle($data); } public function onZhongdUpdate($data) { $this->updateHandle($data); } public function onDadRefuse() { $this->model->reason = $this->data['reason']; } public function onDadAgree($data) { $this->model->pic_dad = $data['pic']; } // public function onZhidRefuse($data) { $this->model->reason = $data['reason']; } /** * Desc: 一级同意action,变更is_leave_audit并扣假 * @param $data * Date: 2021/11/22 10:04 */ public function onZhidAgree($data) { $this->model->pic_zhid = $data['pic']; // 更新假期字段(人员剩余假期和快照耗假) $this->updateHoliday($this->model); } /** * Desc:更新人员剩余假期和快照的预计耗假,is_leave_audit待批假数减1 * @param $model * Date: 2021/11/22 10:14 */ public function updateHoliday($model) { /** @var Member $member */ $member = $model->member; /** @var Snapshot $snapshot */ $snapshot = $model->snapshot; // 待批假记录-1 $member->is_leave_audit--; // 是否需要扣假 if ($snapshot->is_expend === 1) { // 需要扣假,则计算耗假天数 // 三级、二级销假时间即实际销假时间,有三级销假说明是三级请假,否则是二级请假 // 预计耗假 即 返回时间 - 离开时间 (向上取整) if (is_null($snapshot->holiday_expend)) { $snapshot->holiday_expend = $model->travel_day; } //$snapshot->holiday_expend = $model->leave_audit->travel_day; // 执行扣假 $member->holiday_remaining -= $snapshot->getRealExpend(); if ($model->is_beyond) { admin_toastr('本次外出已超假,剩余假期为:' . $member->holiday_remaining . '天'); } if ($member->holiday_remaining < 0) { admin_toastr('假期超额扣除,已不足0天,当前为:' . $member->holiday_remaining . '天'); } } $member->save(); $snapshot->save(); } public function afterZhidAgree() { // 触发离开记录创建 $leaveRecordFSM = app(LeaveRecordFSM::class); $leaveRecord = new LeaveRecord(); // 必须给new的记录关联离开审核id,否则状态机处理action时拿不到关联模型会报错 $leaveRecord->leave_audit_id = $this->model->id; // 刚创建时为初始状态create $leaveRecord->status = 0; $leaveRecord->save(); $leaveRecordFSM->triggerEventEasy($leaveRecord->id, 'create', $this->model, $this->model->snapshot->level); } } ``` 使用: ```php // 提交草稿或创建审批记录 public function afterSave(Form $form) { //是否提交审批? $is_submit=(boolean)$form->input('is_submit'); $inputs = $form->input(); // 获取状态机 $fsm = self::$fsm; //获取主键 $id = $form->getKey(); if($is_submit){ $fsm->triggerEventEasy($id,'submit',$inputs); }else{ // 不是提交审批,则仅创建,不改变状态码,编辑草稿也视作创建(只不过不改变id) $fsm->triggerEventEasy($id, 'create', $inputs); } } // 审批同意或者拒绝 public function handle(array $input) { // 获取状态机 $fsm = self::$fsm; // 获取主键 $primaryKey = $this->payload['id']; // 触发事件 $fsm->triggerEventEasy($primaryKey, 'agree', $input); // $fsm->triggerEventEasy($primaryKey, 'refuse', $input); return $this->response() ->success('操作成功')->refresh(); } // 销假 public function handle(array $input) { // exit(); // 获取状态机 $fsm = self::$fsm; // 获取模型 /** @var LeaveRecord $model */ $model = $this->getModel(); // 触发事件 switch (self::$fsm->roleLevel()) { case 3: $fsm->trigger($fsm::ZHONGD_RECORD_EVENT, $model, $input); break; case 2: $fsm->trigger($fsm::DAD_RECORD_EVENT, $model, $input); break; case 1: // 判断待销假状态是否超假 if (!$model->is_beyond) { $fsm->trigger($fsm::ZHID_NORMAL_RECORD_EVENT, $model, $input); } else { $fsm->trigger($fsm::ZHID_BEYOND_RECORD_EVENT, $model, $input); } break; default: $fsm::throwErr('执行失败:当前账号无法执行此操作'); } return $this->response() ->success('操作成功')->refresh(); } ``` ```php <?php /** * Desc: * User: yangyang * Date: 2021/7/13 9:31 * Ver: 1.0 */ namespace App\Services\StateMachineService; use App\Models\LeaveRecord; use App\Models\Snapshot; use App\Models\AdminUser; use App\Models\LeaveAudit; use App\Models\Member; use App\Models\Out; use Illuminate\Support\Carbon; class LeaveRecordFSM extends BaseStateMachineService { // 销假状态: 51三级待销假 52二级待销假 53一级待销假 61正常销假 62超假销假 // public $initial=static:: // 初始状态 public $init = self::CREATED_STATE; // 状态机管理的模型名称 public $modelClass = LeaveRecord::class; /** * @var LeaveRecord 模型实例 */ public $model; // 定义状态 public const ZHONGD_WAITING_STATE = ['code' => 51, 'name' => '三级待销假']; public const DAD_WAITING_STATE = ['code' => 52, 'name' => '二级待销假']; public const ZHID_WAITING_STATE = ['code' => 53, 'name' => '一级待销假']; public const NORMAL_RECORDED_STATE = ['code' => 61, 'name' => '正常销假']; public const BEYOND_RECORDED_STATE = ['code' => 62, 'name' => '超假销假']; // 待销假状态集合 public const WAITINGSTATES = [ self::ZHONGD_WAITING_STATE, self::DAD_WAITING_STATE, self::ZHID_WAITING_STATE, ]; // 已销假状态集合 public const RECORDEDSTATES = [ self::NORMAL_RECORDED_STATE, self::BEYOND_RECORDED_STATE, ]; // 定义事件 public const ZHONGD_CREATE_EVENT = ['code' => 1, 'from' => self::CREATED_STATE, 'to' => self::ZHONGD_WAITING_STATE, 'name' => '三级创建']; public const DAD_CREATE_EVENT = ['code' => 2, 'from' => self::CREATED_STATE, 'to' => self::DAD_WAITING_STATE, 'name' => '二级创建']; public const ZHONGD_RECORD_EVENT = ['code' => 3, 'from' => self::ZHONGD_WAITING_STATE, 'to' => self::DAD_WAITING_STATE, 'name' => '三级销假']; public const DAD_RECORD_EVENT = ['code' => 4, 'from' => self::DAD_WAITING_STATE, 'to' => self::ZHID_WAITING_STATE, 'name' => '二级销假']; public const ZHID_NORMAL_RECORD_EVENT = ['code' => 5, 'from' => self::ZHID_WAITING_STATE, 'to' => self::NORMAL_RECORDED_STATE, 'name' => '一级正常销假']; public const ZHID_BEYOND_RECORD_EVENT = ['code' => 6, 'from' => self::ZHID_WAITING_STATE, 'to' => self::BEYOND_RECORDED_STATE, 'name' => '一级超假销假']; //记录创建 public function createHandle($data) { /** @var LeaveRecord $model */ $model = $this->model; //$model->leave_audit_id = $data->id; $model->snapshot_id = $data->snapshot->id; $model->leave_audit->member->is_out++; // 待销假+1 $model->leave_audit->member->leave_times++; // 外出次数+1 $model->leave_audit->member->save(); //dump('正在创建'); //$this->createHanle($data); } public function onZhongdCreate($data) { //dd('三级正在创建'); $this->createHandle($data); } public function onDadCreate($data) { //dd('二级正在创建'); $this->createHandle($data); } public function canZhongdRecord(){ if($this->roleLevel()===3){ return true; } return false; } public function canDadRecord(){ if($this->roleLevel()===2){ return true; } return false; } public function canZhidNormalRecord(){ if($this->roleLevel()===1){ return true; } return false; } public function canZhidBeyondRecord(){ if($this->roleLevel()===1){ return true; } return false; } /** * Desc:销假处理的重复部分。 * @param $data * Date: 2021/11/3 11:05 */ public function recordHandle($data) { /** * 根据是否为创建者走分支 * 创建者销假:人员变更为已返回 * 非创建者销假:只记录销假时间和销假回复 */ $model = $this->model; $member = $model->leave_audit->member; //$snapshot = $model->snapshot; //是否为创建者,如果是则填写信息 if ($this->isCreator($model->leave_audit->member->unit_id)) { //通过魔术方法触发访问器,判断是否超假 // 从input赋值给模型 if ($model->is_beyond && $data['is_contacted'] && $data['reason']) { $model->reason = $data['reason']; $model->is_contacted = $data['is_contacted']; } //$today=today(); //销假时间不足return_at(未超假)按return_at算 如果超出,则按today()算(超额扣假) 即2者取其大 //$record_at=$today->greaterThan($model->leave_audit->return_at)?$today:$model->leave_audit->return_at; //$snapshot->holiday_expend = $this->getTravelDay($record_at, $model->leave_audit->leave_at); //保存 //$snapshot->save(); $member->save(); } else { // 非创建者 switch ($this->roleLevel()) { //一级销假 case 1: $model->zhid_record_at = now(); $model->zhid_reply = $data['reply'] ?? "暂无回复"; //一级是否修改返回日期 if (array_key_exists('change_return_at', $data) && $data['change_return_at'] === '1') { self::validateAndThrow($data,[ 'return_at'=>'required|min:0', 'holiday_expend'=>'required|numeric|min:0' ]); $leave_audit = $model->leave_audit; $snapshot = $model->snapshot; //在form里验证字段 // 更新模型 $leave_audit->return_at = $data['return_at']; // 修改返回时间后重新计算是否超假 $model->is_beyond = $model->getIsBeyondAttribute(); $leave_audit->travel_day = $this->getTravelDay($leave_audit->return_at, $leave_audit->leave_at); $old = $snapshot->getRealExpend(); // 旧的耗假天数 $snapshot->holiday_expend = $data['holiday_expend']; if ($snapshot->is_expend === 1) { // 首先恢复到扣假前:加上旧耗假天数,然后减新耗假天数 $leave_audit->member->holiday_remaining += $old - $snapshot->getRealExpend(); } $leave_audit->save(); $snapshot->save(); } // 待销假记录数-1 --$member->is_out; $member->save(); break; // 二级销假 case 2: $model->dad_reply = $data['reply'] ?? "暂无回复"; break; } } } public function onZhongdRecord($data) { $this->model->zhongd_record_at = now(); $this->recordHandle($data); } public function onDadRecord($data) { $this->model->dad_record_at = now(); $this->recordHandle($data); } //正常销假 public function onZhidNormalRecord($data) { $this->recordHandle($data); } //超假销假 public function onZhidBeyondRecord($data) { $this->recordHandle($data); } } ``` 状态机核心部分 ```php <?php /** * Desc: 状态机 * User: yangyang * Date: 2021/7/12 18:02 */ namespace App\Services\StateMachineService; use App\Services\BaseService; use DB; use Exception; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Validator; use Illuminate\Support\Str; use ReflectionClass; abstract class BaseStateMachineService extends BaseService { public const ANY_STATE = ['code' => 999, 'name' => '任何状态']; public const CREATED_STATE = ['code' => 0, 'name' => '已创建']; public const CANCELED_STATE = ['code' => -1, 'name' => '已撤销']; /** * @var string 初始状态 */ public $init = self::CREATED_STATE; /** * @var Collection */ public $events; /** * @var Collection */ public $states; /** * @var FSMEvent 事件 */ public $event; /** * @var Model 模型实例 */ public $model; /** * @var FSMState 状态 */ public $state; /** * @var array 临时存事件可以执行的状态码数组 */ public $canStateCodes; /** * @var array 可以执行的actions */ public $canDoActions; /** * @var ReflectionClass 反射实例 */ public $rfc; // 所有事件开始和结束回调 public $start = 'start'; public $finish = 'finish'; /** * @var string 模型的状态字段名 */ public $stateField = 'status'; /** * @var mixed 用于挂载要传给生命周期回调的数据 */ public $data; public function __construct() { //static $i=0; //dump($i++); $this->events = collect([]); $this->states = collect([]); $this->setReflection()->setStatesAndEvents($this->rfc)->initState()->setModelClass($this->modelClass); } protected $modelClass; public function setModelClass($modelClass) { $this->modelClass = $modelClass; return $this; } /** * Desc:通过反射,匹配以_STATE和_EVENT结尾的常量,设置状态机的状态和事件 * User: yangyang * Date: 2021/7/14 18:21 */ public function setStatesAndEvents(ReflectionClass $rfc) { collect($rfc->getConstants())->each( function($v, $k) { if (preg_match('/_STATE$/', $k) === 1) { //if (preg_match('/[\w]+_STATE/', $k) === 1) $this->pushState($v, $k); } elseif (preg_match('/_EVENT$/', $k) === 1) { $this->pushEvent($v, $k); } } ); return $this; } /** * Desc:添加状态 * @param $state * @param $key * User: yangyang * Date: 2021/7/14 18:21 */ public function pushState($state, $key) { $state['key'] = Str::lower($key); $this->states[$state['code']] = new FSMState($state, $key); } /** * Desc:添加事件 * @param $event * @param $key * User: yangyang * Date: 2021/7/14 18:36 */ public function pushEvent($event, $key) { self::toCollect($event); $key = FSMEvent::getKey($event, $key); $this->events[$key] = new FSMEvent($event, $key); return $this; } /** * Desc:获取反射 * @return $this * User: yangyang * Date: 2021/7/14 18:37 */ public function setReflection() { $this->rfc = new ReflectionClass(static::class); return $this; } /** * @Desc 初始化state * @return $this * Date: 2021/7/14 18:43 * @throws Exception */ public function initState(): self { if (!is_null($this->init)) { $this->setState($this->init); } return $this; } /** * Desc:设置事件 * @param array|Collection $event * @return $this * @throws Exception * Date: 2021/7/14 23:51 */ public function setEvent(Collection $event) { FSMEvent::validateWithRules($event); $this->event = $this->getEventByCode($event['code']); return $this; } /** * Desc:设置模型 * @param Model $model * @return $this * Date: 2021/7/14 23:44 */ public function setModel(Model $model) { $this->model = $model; return $this; } /** * Desc: 通过模型设置状态机state * @param Model $model * @return $this * @throws Exception * User: yangyang * Date: 2021/7/14 12:01 */ public function setStateByModel(Model $model) { $this->setModel($model); //dump($this->model); $stateCode = $this->model->{$this->stateField}; if (is_null($stateCode)) { $stateCode = $this->initState()->state->code; } self::validateAndThrow( ['code' => $stateCode], ['code' => 'required|integer'] ); $this->state = $this->getStateByCode($stateCode); return $this; } /** * Desc:根据code查找state * @param $code * @return Collection * @throws Exception * Date: 2021/7/15 0:01 */ public function getStateByCode(int $code): FSMState { $state = $this->states->where('code', $code)->first(); if (is_null($state)) { static::throwErr('状态不存在。code:' . $code); } return $state; } /** * Desc:根据key查找state * @param $key * @return Collection * @throws Exception * Date: 2021/7/15 0:01 */ public function getStateByKey($key): FSMState { $state = $this->states->where('key', $key)->first(); if (is_null($state)) { static::throwErr('状态不存在。key:' . $key); } return $state; } /** * Desc:根据code查找event * @param $code * @return Collection|mixed * @throws Exception * Date: 2021/7/14 23:27 */ public function getEventByCode($code): FSMEvent { $event = $this->events->where('code', $code)->first(); if (is_null($event)) { static::throwErr('事件不存在。code:' . $code); } return $event; } /** * Desc:根据key查找event * @param $key * @return Collection * @throws Exception Date: 2021/7/14 23:27 */ public function getEventByKey($key): FSMEvent { $event = $this->events->where('key', $key)->first(); if (is_null($event)) { static::throwErr('事件不存在。key:' . $key); } return $event; } /** * Desc:设置状态 * @param $state * @return static * @throws Exception Date: 2021/7/14 23:31 */ public function setState($state) { self::toCollect($state); // 通过code查找状态 $this->state = $this->getStateByCode($state['code']); return $this; } /** * Desc:事件触发器 * @param array|FSMEvent $event * @param Model $model * @param null $data * @return static * Date: 2021/7/15 0:21 * @throws Exception */ public function trigger($event, Model $model, $data = null) { // 生命周期:事件开始 self::doExsistMethod($this, $this->start, $this->data); //设置data if (!is_null($data)) { $this->setData($data); } self::toCollect($event); // 设置事件参数集合、模型、当前状态集合 设置data变量 $this->setEvent($event)->setStateByModel($model); $state = $this->state; $event = $this->event; // 判断当前状态是否可以执行事件 if (!$this->canDo($state, $event)) { static::throwErr('状态:' . $state->name . '。无法执行动作:' . $event->name); } // 验证完毕,开始处理事件,开启数据库事务 try { DB::beginTransaction(); // 生命周期:执行action前 self::doExsistMethod($this, $event->before, $this->data); //更新模型状态 $this->updateModelState($this->model, $event->toCode); // 此处仅验证和更新模型状态码 //执行action 由于开启了事务,所以出错会回滚 self::doExsistMethod($this, $event->action, $this->data); // 执行action $model->save(); // 保存模型 self::doExsistMethod($this, $event->after, $this->data); // 执行afterAction DB::commit(); } catch (\Exception $e) { DB::rollBack(); self::throwErr($e->getMessage(), $e->getCode(), $e->errs ?? []); } //清除重置不需要的变量 $this->clear(); // 生命周期:事件结束 self::doExsistMethod($this, $this->finish, $this->data); return $this; } /** * Desc:更新模型的状态字段 * Date: 2021/7/15 3:44 */ public function updateModelState(Model $model, int $to) { $model->{$this->stateField} = $to; } /** * Desc:判断当前状态是否可以执行事件 * @param $state * @param $event * @return bool * Date: 2021/7/15 0:25 */ public function canDo(FSMState $state, FSMEvent $event): bool { //首先根据fromCode获取事件列表 $this->canDoActions = $this->getCanDoActions($state, $event); // 看事件列表里有没有对应的key return $this->canDoActions->has($event->key); } /** * Desc:挂载要传递给生命周期回调的数据 * @param $data * @return $this * Date: 2021/7/15 3:41 */ public function setData($data) { $this->data = $data; return $this; } // 获取状态可以执行的事件 public function getCanDoActions(FSMState $state, FSMEvent $event): Collection { // 条件1:fromCode为传入状态的code或者'任何状态'的code $this->canStateCodes = [$state->code, static::ANY_STATE['code']]; $eventKey = $event->key; return $this->events->filter( function(FSMEvent $event, $k) use ($eventKey) { $flag1 = $flag2 = false; if (in_array($event->fromCode, $this->canStateCodes, true)) { $flag1 = true; } // 条件2:调用当前事件的can方法,判断是否可执行 (如果方法不存在,则直接为true) if ($eventKey === $event->key && method_exists($this, $event->can)) { $flag2 = call_user_func([$this, $event->can], $this->data) === true; } else { $flag2 = true; } // 同时满足以上2个条件。则可以执行 return $flag1 && $flag2; } ); //return $canDoActions; } public function clear(): void { // $this->canDoActions = null; $this->event = null; $this->data = null; } /** * Desc: 根据状态数组。返回状态码数组 * User: yangyang * Date: 2021/7/13 17:13 * @param mixed ...$states */ public function getStateCodes($states) { if (!is_array($states)) { $states = func_get_args(); } self::toCollect($states); /** @var Collection $states */ return $states->pluck('code')->toArray(); } // 触发事件简单封装:根据主键、事件,触发对应事件 public function triggerEventEasy(int $primaryKey, string $event, $data = null, $roleLevel = null) { //获取模型 $model = $this->modelClass::find($primaryKey); //获取角色slug $roleInfo = RoleLevelMap[$roleLevel ?? $this->roleLevel()]; //拼接事件名 if ($event !== 'cancel') { $eventName = $roleInfo['slug'] . '_' . $event; } else { $eventName = $event; } //查询事件是否存在 $FSMEvent = $this->getEventByKey($eventName); //触发对应事件 $this->trigger($FSMEvent, $model, $data); } // public function beforeAllActions(){ // dump('所有动作之前'); // } // public function afterAllActions(){ // dump('所有动作之后'); // } // public function cancelBeforeHandle() // { // dump('取消之前'); // } // public function cancelAfterHandle() // { // dump('取消之后'); // } // public function cancelHandle() // { // dump('在取消'); // } /** * Desc:判断当前是否为某个状态 * @param $state * @return bool * @throws Exception * Date: 2021/7/18 23:35 */ public function isState($state, $modelStateCode = null) { if (is_null($modelStateCode)) { $modelStateCode = $this->state->code; } return $this->getStateByCode($modelStateCode)->code === $state['code']; } public function inStates(array $states, int $modelStateCode = null) { if (is_null($modelStateCode)) { $modelStateCode = $this->state->code; } $statesCode = array_column($states, 'code'); return in_array($modelStateCode, $statesCode); } } ``` ```php <?php /** * Desc:事件类 * User: yangyang * Date: 2021/7/15 0:49 */ namespace App\Services\StateMachineService; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Str; class FSMEvent extends BaseFSMCollection { public $code; public $key; public $name; public $can; public $fromCode; public $fromName; public $toCode; public $toName; public $before; public $after; public $action; protected static $rules = [ 'from' => 'required', 'to' => 'required', 'name' => 'string', 'code' => 'required|integer', 'do' => 'string', 'from.name' => 'required', 'from.code' => 'required|integer', 'to.name' => 'required|string', 'to.code' => 'required|integer', ]; protected static $fill = ['code', 'key', 'name', 'can', 'fromCode', 'fromName', 'toCode', 'toName', 'before', 'after', 'action']; public static function getKey($event, $key) { return Str::replace('_event', '', static::myGet($event, 'key', Str::lower($key))); } public function init($data, $key) { $key = self::getKey($data, $key); $from = self::myGet($data, 'from'); $to = self::myGet($data, 'to'); $name = self::myGet($data, 'name', $key); $code = self::myGet($data, 'code'); $do = self::myGet($data, 'do', $key); // 转换成驼峰形式 $do = Str::studly($do); $data['key'] = $key; $data['code'] = $code; $data['name'] = $name; $data['fromName'] = $from['name']; $data['fromCode'] = $from['code']; $data['toName'] = $to['name']; $data['toCode'] = $to['code']; $data['can'] = 'can' . $do; $data['do'] = $do; $data['action'] = 'on' . $do; $data['before'] = 'before' . $do; $data['after'] = 'after' . $do; $this->pushItemsToObject($this, $data, self::$fill); } /** * Desc:触发事件自己 * @param Model $model * Date: 2021/7/15 17:25 */ public function triggerSelf(BaseStateMachineService $fsm,Model $model) { $fsm->trigger($this, $model); } } ``` ```php <?php /** * Desc:状态类 * User: yangyang * Date: 2021/7/15 0:51 */ namespace App\Services\StateMachineService; use App\Services\BaseService; use App\Traits\MyHelper; use Illuminate\Support\Collection; use Illuminate\Support\Str; class FSMState extends BaseFSMCollection { public $key; public $code; public $name; protected static $fill=['key','code', 'name']; protected static $rules = [ 'name' => 'required|string', 'code' => 'required|integer', ]; public static function getKey($state, $key) { return Str::replace('_state','',static::myGet($state, 'key', Str::lower($key))); } public function init($data,$key) { $data['key']=self::getKey($data, $key); $this->pushItemsToObject($this,$data,self::$fill); } } ``` ```php <?php /** * Desc: 状态和事件通用集合 * User: yangyang * Date: 2021/7/15 19:33 */ namespace App\Services\StateMachineService; use App\Traits\DoFirstTrait; use App\Traits\MyHelper; use Illuminate\Support\Collection; class BaseFSMCollection extends Collection { use MyHelper; use DoFirstTrait; protected static $fill = []; protected static $rules = []; public function __construct($params, $key = null) { parent::__construct($params); //验证参数是否合法 self::validateWithRules($params); //将params转换为集合,用起来顺手些 self::toCollect($params); //将params集合挂载到FSMEvent属性上,便于箭头访问 if (!is_null($key)) { $this->init($params, $key); } } public function init($data, $key) { } public static function getKey($params,$key) { } } ```
本作品采用
《CC 协议》
,转载必须注明作者和本文链接