diff --git a/app/Http/Controllers/Api/WorkflowController.php b/app/Http/Controllers/Api/WorkflowController.php
new file mode 100755
index 000000000..8c95130bb
--- /dev/null
+++ b/app/Http/Controllers/Api/WorkflowController.php
@@ -0,0 +1,604 @@
+flow_url = 'http://dootask-workflow-'.env('APP_ID');
+ $this->flow_url = 'http://192.168.100.219:8700';
+ }
+
+ /**
+ * @api {post} api/workflow/procdef/all 01. 查询流程定义
+ *
+ * @apiDescription 需要token身份
+ * @apiVersion 1.0.0
+ * @apiGroup workflow
+ * @apiName procdef__all
+ *
+ * @apiQuery {String} name 流程名称
+ *
+ * @apiSuccess {Number} ret 返回状态码(1正确、0错误)
+ * @apiSuccess {String} msg 返回信息(错误描述)
+ * @apiSuccess {Object} data 返回数据
+ */
+ public function procdef__all()
+ {
+ //{"name": "请假"}
+ $data['name'] = Request::input('name');
+ $procdef = Ihttp::ihttp_post($this->flow_url.'/api/v1/workflow/procdef/findAll', json_encode($data));
+ $procdef = json_decode($procdef['data'] ?? '', true);
+ if (!$procdef || $procdef['status'] != 200) {
+ return Base::retError($procdef['message'] ?? '查询流程失败');
+ }
+ return Base::retSuccess('success', Base::arrayKeyToUnderline($procdef['data']));
+ }
+
+ /**
+ * @api {get} api/workflow/procdef/del 02. 删除流程定义
+ *
+ * @apiDescription 需要token身份
+ * @apiVersion 1.0.0
+ * @apiGroup workflow
+ * @apiName procdef__del
+ *
+ * @apiQuery {String} id 流程ID
+ *
+ * @apiSuccess {Number} ret 返回状态码(1正确、0错误)
+ * @apiSuccess {String} msg 返回信息(错误描述)
+ * @apiSuccess {Object} data 返回数据
+ */
+ public function procdef__del()
+ {
+ $data['id'] = Request::input('id');
+ $procdef = Ihttp::ihttp_get($this->flow_url.'/api/v1/workflow/procdef/delById?'.http_build_query($data));
+ $procdef = json_decode($procdef['data'] ?? '', true);
+ if (!$procdef || $procdef['status'] != 200) {
+ return Base::retError($procdef['message'] ?? '删除流程失败');
+ }
+ return Base::retSuccess('success', Base::arrayKeyToUnderline($procdef['data']));
+ }
+
+ /**
+ * @api {post} api/workflow/process/start 03. 启动流程
+ *
+ * @apiDescription 需要token身份
+ * @apiVersion 1.0.0
+ * @apiGroup workflow
+ * @apiName process__start
+ *
+ * @apiQuery {String} proc_name 流程名称
+ * @apiQuery {Number} userid 用户ID
+ * @apiQuery {Number} department_id 部门ID
+ * @apiQuery {Array} [var] 启动流程类型信息(格式:[{type,startTime,endTime,description}])
+ *
+ * @apiSuccess {Number} ret 返回状态码(1正确、0错误)
+ * @apiSuccess {String} msg 返回信息(错误描述)
+ * @apiSuccess {Object} data 返回数据
+ */
+ public function process__start()
+ {
+ //{"procName":"请假222","userID":"2", "departmentId":1, "var":{"type":"事假","startTime":"12", "endTime":"1", "description":"请假描述"}}
+ $data['userid'] = Request::input('userid');
+ $data['department_id'] = intval(Request::input('department_id'));
+ $data['proc_name'] = Request::input('proc_name');
+ //
+ $var = json_decode(Request::input('var'), true);
+ $data['var'] = $var;
+ $process = Ihttp::ihttp_post($this->flow_url.'/api/v1/workflow/process/start', json_encode(Base::arrayKeyToCamel($data)));
+ $process = json_decode($process['data'] ?? '', true);
+ if (!$process || $process['status'] != 200) {
+ return Base::retError($process['message'] ?? '流程启动失败');
+ }
+ //
+ $process = Base::arrayKeyToUnderline($process['data']);
+ $process = $this->getProcessById($process['id']); //获取最新的流程信息
+ if ($process['candidate']) {
+ $userid = explode(',', $process['candidate']);
+ $toUser = User::whereIn('userid', $userid)->get();
+ $botUser = User::botGetOrCreate('approval-alert');
+ if (empty($botUser)) {
+ return Base::retError('审批机器人不存在');
+ }
+ foreach ($toUser as $val) {
+ if ($val->bot) {
+ continue;
+ }
+ $dialog = WebSocketDialog::checkUserDialog($botUser, $val->userid);
+ if (empty($dialog)) {
+ continue;
+ }
+ $this->workflowMsg('workflow_reviewer', $dialog, $botUser, $val, $process, 'start');
+ }
+ // 抄送人
+ $notifier = $this->handleProcessNode($process);
+ if ($notifier) {
+ foreach ($notifier as $val) {
+ $dialog = WebSocketDialog::checkUserDialog($botUser, $val['target_id']);
+ $this->workflowMsg('workflow_notifier', $dialog, $botUser, $process, $process);
+ }
+ }
+ }
+
+ return Base::retSuccess('success', $process);
+ }
+
+ /**
+ * @api {post} api/workflow/task/complete 04. 审批
+ *
+ * @apiDescription 需要token身份
+ * @apiVersion 1.0.0
+ * @apiGroup workflow
+ * @apiName task__complete
+ *
+ * @apiQuery {Number} task_id 流程ID
+ * @apiQuery {String} pass 标题 [true-通过,false-拒绝]
+ * @apiQuery {Number} userid 用户ID
+ * @apiQuery {String} comment 评论
+ *
+ * @apiSuccess {Number} ret 返回状态码(1正确、0错误)
+ * @apiSuccess {String} msg 返回信息(错误描述)
+ * @apiSuccess {Object} data 返回数据
+ */
+ public function task__complete()
+ {
+ //{"taskID":51,"pass":"true","userID":"2","comment": "评论备注51"}
+ $data['task_id'] = intval(Request::input('task_id'));
+ $data['pass'] = Request::input('pass');
+ $data['userid'] = Request::input('userid');
+ $data['comment'] = Request::input('comment');
+ info(Base::arrayKeyToCamel($data));
+ $task = Ihttp::ihttp_post($this->flow_url.'/api/v1/workflow/task/complete', json_encode(Base::arrayKeyToCamel($data)));
+ info($task);
+ $task = json_decode($task['data'] ?? '', true);
+ if (!$task || $task['status'] != 200) {
+ return Base::retError($task['message'] ?? '审批失败');
+ }
+ //
+ $task = Base::arrayKeyToUnderline($task['data']);
+ $pass = $data['pass'] == 'true' ? 'pass' : 'refuse';
+ $process = $this->getProcessById($task['proc_inst_id']);
+ info($process);
+ $botUser = User::botGetOrCreate('approval-alert');
+ if (empty($botUser)) {
+ return Base::retError('审批机器人不存在');
+ }
+ // 在流程信息关联的用户中查找
+ $toUser = WorkflowProcMsg::where('proc_inst_id', $process['id'])->get()->toArray();
+ info($toUser);
+ foreach ($toUser as $val) {
+ $dialog = WebSocketDialog::checkUserDialog($botUser, $val['userid']);
+ if (empty($dialog)) {
+ continue;
+ }
+ info('11111111');
+ $this->workflowMsg('workflow_reviewer', $dialog, $botUser, $val, $process, $pass);
+ }
+ // 发起人
+ if($process['is_finished'] == true) {
+ $dialog = WebSocketDialog::checkUserDialog($botUser, $process['start_user_id']);
+ $this->workflowMsg('workflow_submitter', $dialog, $botUser, ['userid' => $data['userid']], $process, $pass);
+ }
+ // 抄送人
+ $notifier = $this->handleProcessNode($process, $task['step']);
+ if ($notifier && $pass == 'pass') {
+ foreach ($notifier as $val) {
+ $dialog = WebSocketDialog::checkUserDialog($botUser, $val['target_id']);
+ $this->workflowMsg('workflow_notifier', $dialog, $botUser, $process, $process);
+ }
+ }
+ return Base::retSuccess('success', $task);
+ }
+
+ /**
+ * @api {post} api/workflow/task/withdraw 05. 撤回
+ *
+ * @apiDescription 需要token身份
+ * @apiVersion 1.0.0
+ * @apiGroup workflow
+ * @apiName task__withdraw
+ *
+ * @apiQuery {Number} task_id 流程ID
+ * @apiQuery {String} userid 用户ID
+ * @apiQuery {Number} proc_inst_id 流程实例ID
+ *
+ * @apiSuccess {Number} ret 返回状态码(1正确、0错误)
+ * @apiSuccess {String} msg 返回信息(错误描述)
+ * @apiSuccess {Object} data 返回数据
+ */
+ public function task__withdraw()
+ {
+ //{"taskID":2,"userID":"2","procInstID":1}
+ $data['task_id'] = intval(Request::input('task_id'));
+ $data['userid'] = Request::input('userid');
+ $data['proc_inst_id'] = intval(Request::input('proc_inst_id'));
+ info(Base::arrayKeyToCamel($data));
+ $task = Ihttp::ihttp_post($this->flow_url.'/api/v1/workflow/task/withdraw', json_encode(Base::arrayKeyToCamel($data)));
+ info($task);
+ $task = json_decode($task['data'] ?? '', true);
+ if (!$task || $task['status'] != 200) {
+ return Base::retError($task['message'] ?? '撤回失败');
+ }
+ //
+ $process = $this->getProcessById($data['proc_inst_id']);
+ $botUser = User::botGetOrCreate('approval-alert');
+ if (empty($botUser)) {
+ return Base::retError('审批机器人不存在');
+ }
+ // 在流程信息关联的用户中查找
+ $toUser = WorkflowProcMsg::where('proc_inst_id', $process['id'])->get()->toArray();
+ info($toUser);
+ foreach ($toUser as $val) {
+ $dialog = WebSocketDialog::checkUserDialog($botUser, $val['userid']);
+ if (empty($dialog)) {
+ continue;
+ }
+ info('11111111');
+ //发送撤回提醒
+ $this->workflowMsg('workflow_reviewer', $dialog, $botUser, $val, $process, 'withdraw');
+ }
+ return Base::retSuccess('success', Base::arrayKeyToUnderline($task['data']));
+ }
+
+ /**
+ * @api {post} api/workflow/process/findTask 06. 查询需要我审批的流程
+ *
+ * @apiDescription 需要token身份
+ * @apiVersion 1.0.0
+ * @apiGroup workflow
+ * @apiName process__findTask
+ *
+ * @apiQuery {Number} userID 用户ID
+ * @apiQuery {String} groups 用户组
+ * @apiQuery {String} departments 部门
+ * @apiQuery {String} company 公司
+ * @apiQuery {String} procName 流程名称
+ * @apiQuery {Number} pageIndex 页码
+ * @apiQuery {Number} pageSize 每页条数
+ *
+ * @apiSuccess {Number} ret 返回状态码(1正确、0错误)
+ * @apiSuccess {String} msg 返回信息(错误描述)
+ * @apiSuccess {Object} data 返回数据
+ */
+ public function process__findTask()
+ {
+ //{"userID":"2","groups":["人事"],"departments":["技术中心"],"company":"A公司","procName": "请假","pageIndex":1,"pageSize":10}
+ $data['userID'] = Request::input('userID');
+ // $data['groups'] = Request::input('groups');
+ // $data['departments'] = Request::input('departments');
+ // $data['company'] = Request::input('company');
+ // $data['procName'] = Request::input('procName');
+ $data['pageIndex'] = Request::input('pageIndex');
+ $data['pageSize'] = Request::input('pageSize');
+ info($data);
+ $workflow = Ihttp::ihttp_post($this->flow_url.'/api/v1/workflow/process/findTask', json_encode($data));
+ info($workflow);
+ $workflow = json_decode($workflow['data'] ?? '', true);
+ if (!$workflow || $workflow['status'] != 200) {
+ return Base::retError($workflow['message'] ?? '查询失败');
+ }
+ return Base::retSuccess('success', Base::arrayKeyToUnderline($workflow['data']));
+ }
+
+ /**
+ * @api {post} api/workflow/identitylink/findParticipant 07. 查询流程审批人与评论
+ *
+ * @apiDescription 需要token身份
+ * @apiVersion 1.0.0
+ * @apiGroup workflow
+ * @apiName identitylink__findParticipant
+ *
+ * @apiQuery {Number} procInstID 流程ID
+ *
+ * @apiSuccess {Number} ret 返回状态码(1正确、0错误)
+ * @apiSuccess {String} msg 返回信息(错误描述)
+ * @apiSuccess {Object} data 返回数据
+ */
+ public function identitylink__findParticipant()
+ {
+ $data['procInstID'] = Request::input('procInstID');
+
+ $get = Ihttp::ihttp_get($this->flow_url.'/api/v1/workflow/identitylink/findParticipant?procInstID=' . $data['procInstID']);
+ info($get);
+ $workflow = json_decode($get['data'] ?? '', true);
+ if (!$workflow || $get['msg'] != 200) {
+ return Base::retError($workflow['message'] ?? 'fail');
+ }
+ return Base::retSuccess('success', Base::arrayKeyToUnderline($workflow));
+ }
+
+ /**
+ * @api {post} api/workflow/procHistory/findTask 08. 查询我审批的流程(已结束)
+ *
+ * @apiDescription 需要token身份
+ * @apiVersion 1.0.0
+ * @apiGroup workflow
+ * @apiName procHistory__findTask
+ *
+ * @apiQuery {Number} user_id 用户ID
+ * @apiQuery {String} company 公司
+ * @apiQuery {Number} pageIndex 页码
+ * @apiQuery {Number} pageSize 每页条数
+ *
+ * @apiSuccess {Number} ret 返回状态码(1正确、0错误)
+ * @apiSuccess {String} msg 返回信息(错误描述)
+ * @apiSuccess {Object} data 返回数据
+ */
+ public function procHistory__findTask()
+ {
+ //{"userID":"2","company":"A公司","pageIndex":1,"pageSize":2}
+ $data['userID'] = Request::input('userID');
+ $data['company'] = Request::input('company');
+ $data['pageIndex'] = Request::input('pageIndex');
+ $data['pageSize'] = Request::input('pageSize');
+
+ $workflow = Ihttp::ihttp_post($this->flow_url.'/api/v1/workflow/procHistory/findTask', json_encode($data));
+ info($workflow);
+ $workflow = json_decode($workflow['data'] ?? '', true);
+ if (!$workflow || $workflow['status'] != 200) {
+ return Base::retError($workflow['message'] ?? 'fail');
+ }
+ return Base::retSuccess('success', Base::arrayKeyToUnderline($workflow['data']));
+ }
+
+ /**
+ * @api {post} api/workflow/procHistory/startByMyself 09. 查询我发起的流程(已结束)
+ *
+ * @apiDescription 需要token身份
+ * @apiVersion 1.0.0
+ * @apiGroup workflow
+ * @apiName procHistory__startByMyself
+ *
+ * @apiQuery {Number} user_id 用户ID
+ * @apiQuery {String} company 公司
+ * @apiQuery {Number} pageIndex 页码
+ * @apiQuery {Number} pageSize 每页条数
+ *
+ * @apiSuccess {Number} ret 返回状态码(1正确、0错误)
+ * @apiSuccess {String} msg 返回信息(错误描述)
+ * @apiSuccess {Object} data 返回数据
+ */
+ public function procHistory__startByMyself()
+ {
+ //{"userID":"2","company":"A公司","pageIndex":1,"pageSize":1}
+ $data['userID'] = Request::input('userID');
+ $data['company'] = Request::input('company');
+ $data['pageIndex'] = Request::input('pageIndex');
+ $data['pageSize'] = Request::input('pageSize');
+
+ $workflow = Ihttp::ihttp_post($this->flow_url.'/api/v1/workflow/procHistory/startByMyself', json_encode($data));
+ info($workflow);
+ $workflow = json_decode($workflow['data'] ?? '', true);
+ if (!$workflow || $workflow['status'] != 200) {
+ return Base::retError($workflow['message'] ?? 'fail');
+ }
+ return Base::retSuccess('success', Base::arrayKeyToUnderline($workflow['data']));
+ }
+
+ /**
+ * @api {post} api/workflow/process/startByMyself 10. 查询我发起的流程(审批中)
+ *
+ * @apiDescription 需要token身份
+ * @apiVersion 1.0.0
+ * @apiGroup workflow
+ * @apiName process__startByMyself
+ *
+ * @apiQuery {Number} user_id 用户ID
+ * @apiQuery {String} company 公司
+ * @apiQuery {Number} pageIndex 页码
+ * @apiQuery {Number} pageSize 每页条数
+ *
+ * @apiSuccess {Number} ret 返回状态码(1正确、0错误)
+ * @apiSuccess {String} msg 返回信息(错误描述)
+ * @apiSuccess {Object} data 返回数据
+ */
+ public function process__startByMyself()
+ {
+ //{"userID":"2","company":"A公司","pageIndex":1,"pageSize":1}
+ $data['userID'] = Request::input('userID');
+ $data['company'] = Request::input('company');
+ $data['pageIndex'] = Request::input('pageIndex');
+ $data['pageSize'] = Request::input('pageSize');
+
+ $workflow = Ihttp::ihttp_post($this->flow_url.'/api/v1/workflow/process/startByMyself', json_encode($data));
+ info($workflow);
+ $workflow = json_decode($workflow['data'] ?? '', true);
+ if (!$workflow || $workflow['status'] != 200) {
+ return Base::retError($workflow['message'] ?? 'fail');
+ }
+ return Base::retSuccess('success', Base::arrayKeyToUnderline($workflow['data']));
+ }
+
+ /**
+ * @api {post} api/workflow/process/FindProcNotify 11. 查询抄送我的流程(审批中)
+ *
+ * @apiDescription 需要token身份
+ * @apiVersion 1.0.0
+ * @apiGroup workflow
+ * @apiName process__FindProcNotify
+ *
+ * @apiQuery {Number} user_id 用户ID
+ * @apiQuery {String} company 公司
+ * @apiQuery {Array} groups 抄送组
+ *
+ * @apiSuccess {Number} ret 返回状态码(1正确、0错误)
+ * @apiSuccess {String} msg 返回信息(错误描述)
+ * @apiSuccess {Object} data 返回数据
+ */
+ public function process__FindProcNotify()
+ {
+ //{"userID":"2","company":"A公司","groups":["人事","产品经理"]}
+ $data['userID'] = Request::input('userID');
+ $data['company'] = Request::input('company');
+ $data['groups'] = Request::input('groups');
+
+ $workflow = Ihttp::ihttp_post($this->flow_url.'/api/v1/workflow/process/FindProcNotify', json_encode($data));
+ info($workflow);
+ $workflow = json_decode($workflow['data'] ?? '', true);
+ if (!$workflow || $workflow['status'] != 200) {
+ return Base::retError($workflow['message'] ?? 'fail');
+ }
+ return Base::retSuccess('success', Base::arrayKeyToUnderline($workflow['data']));
+ }
+
+ /**
+ * @api {post} api/workflow/procHistory/FindProcNotify 12. 查询抄送我的流程(已结束)
+ *
+ * @apiDescription 需要token身份
+ * @apiVersion 1.0.0
+ * @apiGroup workflow
+ * @apiName procHistory__FindProcNotify
+ *
+ * @apiQuery {Number} user_id 用户ID
+ * @apiQuery {String} company 公司
+ * @apiQuery {Array} groups 抄送组
+ *
+ * @apiSuccess {Number} ret 返回状态码(1正确、0错误)
+ * @apiSuccess {String} msg 返回信息(错误描述)
+ * @apiSuccess {Object} data 返回数据
+ */
+ public function procHistory__FindProcNotify()
+ {
+ //{"userID":"2","company":"A公司","groups":["人事","产品经理"]}
+ $data['userID'] = Request::input('userID');
+ $data['company'] = Request::input('company');
+ $data['groups'] = Request::input('groups');
+
+ $workflow = Ihttp::ihttp_post($this->flow_url.'/api/v1/workflow/procHistory/FindProcNotify', json_encode($data));
+ info($workflow);
+ $workflow = json_decode($workflow['data'] ?? '', true);
+ if (!$workflow || $workflow['status'] != 200) {
+ return Base::retError($workflow['message'] ?? 'fail');
+ }
+ return Base::retSuccess('success', Base::arrayKeyToUnderline($workflow['data']));
+ }
+
+ /**
+ * @api {get} api/workflow/process/findById 13. 根据流程ID查询流程(所有)
+ *
+ * @apiDescription 需要token身份
+ * @apiVersion 1.0.0
+ * @apiGroup workflow
+ * @apiName process__findById
+ *
+ * @apiQuery {Number} id 流程ID
+ *
+ * @apiSuccess {Number} ret 返回状态码(1正确、0错误)
+ * @apiSuccess {String} msg 返回信息(错误描述)
+ * @apiSuccess {Object} data 返回数据
+ */
+ public function process__findById()
+ {
+ //{"id":"2"}
+ $data['id'] = intval(Request::input('id'));
+ $workflow = $this->getProcessById($data['id']);
+ return Base::retSuccess('success', $workflow);
+ }
+
+ // 审批机器人消息-审核人
+ public function workflowMsg($type, $dialog, $botUser, $toUser, $process, $action = null)
+ {
+ $data = [
+ 'nickname' => User::userid2nickname($type == 'workflow_submitter' ? $toUser['userid'] : $process['start_user_id']),
+ 'proc_def_name' => $process['proc_def_name'],
+ 'department' => $process['department'],
+ 'type' => $process['var']['type'],
+ 'start_time' => $process['var']['start_time'],
+ 'end_time' => $process['var']['end_time'],
+ ];
+ info($action);
+ $text = view('push.bot', ['type' => $type, 'action' => $action, 'data' => (object)$data])->render();
+ $text = preg_replace("/^\x20+/", "", $text);
+ $text = preg_replace("/\n\x20+/", "\n", $text);
+ $msg_action = null;
+ if ($action == 'withdraw' || $action == 'pass' || $action == 'refuse') {
+ info(222222222222);
+ // 如果任务没有完成,则不需要更新消息
+ info($process['is_finished'] == true);
+ if ($process['is_finished'] != true) {
+ return true;
+ }
+ info('===========22222222========');
+ // 任务完成,给发起人发送消息
+ if($type == 'workflow_submitter' && $action != 'withdraw'){
+ info('==================任务完成,给发起人发送消息========');
+ return WebSocketDialogMsg::sendMsg($msg_action, $dialog->id, 'text', ['text' => $text], $botUser->userid, false, false, true);
+ }
+ // 查找最后一条消息msg_id
+ $msg_action = 'update-'.$toUser['msg_id'];
+ info($msg_action);
+ }
+ info(33333333333333);
+ $msg = WebSocketDialogMsg::sendMsg($msg_action, $dialog->id, 'text', ['text' => $text], $botUser->userid, false, false, true);
+ // 保存关联信息
+ if ($action == 'start') {
+ info(666666666666);
+ // 创建关联信息
+ $proc_msg = new WorkflowProcMsg();
+ $proc_msg->proc_inst_id = $process['id'];
+ $proc_msg->msg_id = $msg['data']->id;
+ $proc_msg->userid = $toUser->userid;
+ $proc_msg->save();
+ }
+ return true;
+ }
+
+ // 根据ID获取流程
+ public function getProcessById($id)
+ {
+ $data['id'] = intval($id);
+ $process = Ihttp::ihttp_get($this->flow_url."/api/v1/workflow/process/findById?".http_build_query($data));
+ $process = json_decode($process['data'] ?? '', true);
+ if (!$process || $process['status'] != 200) {
+ return Base::retError($process['message'] ?? 'fail');
+ }
+ return Base::arrayKeyToUnderline($process['data']);
+ }
+
+ // 处理流程节点返回是否有抄送人
+ public function handleProcessNode($process, $step = 0)
+ {
+ // 获取流程节点
+ $process_node = $process['node_infos'];
+ //判断下一步是否有抄送人
+ $step = $step + 1;
+ $next_node = $process_node[$step] ?? [];
+ info($next_node);
+ if ($next_node) {
+ if ($next_node['type'] == 'notifier'){
+ return $next_node['node_user_list'] ?? [];
+ }
+ }
+ return [];
+ }
+
+ // 根据ID查询流程实例的参与者(审批中)
+ public function getUserProcessParticipantById($id)
+ {
+ $data['id'] = intval($id);
+ $user = Ihttp::ihttp_get($this->flow_url."/api/v1/user/identitylink/findParticipant?".http_build_query($data));
+ $user = json_decode($user['data'] ?? '', true);
+ if (!$user || $user['status'] != 200) {
+ return Base::retError($user['message'] ?? 'fail');
+ }
+ return $user;
+ }
+}
diff --git a/app/Http/Controllers/Api/apiswagger.php b/app/Http/Controllers/Api/apiswagger.php
new file mode 100644
index 000000000..8c9fcefce
--- /dev/null
+++ b/app/Http/Controllers/Api/apiswagger.php
@@ -0,0 +1,510 @@
+ $text) {
+ $original = $matchs[0][$key];
+ //
+ preg_match("/\*\s*\@api\s+\{(get|post|put|delete|head|options|patch|propfind|proppatch|mkcol|copy|move|lock|unlock)\}\s+(.*?)\s+(.*?)\n/i", $original, $matchApi);
+ preg_match("/\*\s*\@apiDescription\s+(.*?)\n/i", $original, $matchDesc);
+ preg_match("/\*\s*\@apiGroup\s+(.*?)\n/i", $original, $matchGroup);
+ if (empty($matchApi) || empty($matchGroup)) {
+ continue;
+ }
+ $tagText = $tags[$matchGroup[1]] ?? $matchGroup[1];
+ //
+ preg_match_all("/\*\s*\@(apiHeader|apiBody|apiParam|apiQuery)\s+\{(.*?)\}(.*?)\n([\s\S]*?)(?=(\*\s*@|\*\s*\/))/i", $original, $matchParam);
+ $paramArray = [];
+ foreach ($matchParam[1] as $index => $value) {
+ $paramItem = [];
+ $in = '';
+ if ($matchParam[1][$index] == 'apiHeader') {
+ $in = 'header';
+ } elseif ($matchParam[1][$index] == 'apiQuery') {
+ $in = 'query';
+ } elseif ($matchParam[1][$index] == 'apiBody') {
+ $in = 'formData';
+ } elseif ($matchParam[1][$index] == 'apiParam') {
+ $schema = $matchParam[4][$index];
+ $schema = preg_replace("/(\\n|^)\s*\*/", '', $schema);
+ $schemaData = json5_decode($schema);
+ if ($schemaData) {
+ $paramItem = [
+ "name" => "root",
+ "in" => "body",
+ "schema" => [
+ "\$schema" => "http://json-schema.org/draft-04/schema#",
+ ]
+ ];
+ $isArr = __isArr($schemaData);
+ $schemaData = $isArr ? $schemaData[1] : $schemaData;
+ $properties = __propertieData($schemaData);
+ if ($isArr) {
+ $paramItem["schema"]["type"] = "array";
+ $paramItem["schema"]["items"] = [
+ "type" => "object",
+ "properties" => $properties
+ ];
+ } else {
+ $paramItem["schema"]["type"] = "object";
+ $paramItem["schema"]["properties"] = $properties;
+ }
+ } else {
+ $in = 'query';
+ }
+ } else {
+ continue;
+ }
+ if ($in) {
+ preg_match("/^\s*(\[*(.*?)\]*)\s+(.*?)$/i", $matchParam[3][$index], $matchParam3);
+ if ($matchParam3) {
+ $type = strtolower($matchParam[2][$index]);
+ $name = $matchParam3[2] ?: $matchParam3[3];
+ $desc = trim($matchParam3[3]);
+ $matchParam4 = $matchParam[4][$index];
+ $matchParam4 = preg_replace("/(\\n|^)\s*\*/", "\n", $matchParam4);
+ if ($matchParam4) {
+ $matchParam4 = preg_replace("/(\\n|^)\s*-/", "$1", trim($matchParam4));
+ $desc = trim(sprintf("%s\n%s", $desc, $matchParam4));
+ }
+ $paramArray[] = [
+ "name" => $name,
+ "in" => $in,
+ "required" => !str_starts_with($matchParam3[1], '['),
+ "description" => $desc,
+ "type" => $type,
+ ];
+ }
+ }
+ if ($paramItem) {
+ $paramArray[] = $paramItem;
+ }
+ }
+ //
+ preg_match_all("/\*\s*\@apiSuccess\s+\{(.*?)\}(.*?)\n([\s\S]*?)(?=(\*\s*@|\*\s*\/))/i", $original, $matchSuccess);
+ $successArray = [];
+ $requiredArray = [];
+ foreach ($matchSuccess[1] as $index => $value) {
+ preg_match("/^\s*(\[*(.*?)\]*)\s+(.*?)$/i", $matchSuccess[2][$index], $matchSuccess2);
+ $name = $matchSuccess2[2] ?: $matchSuccess2[3];
+ if (!str_starts_with($matchSuccess2[1], '[')) {
+ $requiredArray[] = $name;
+ }
+ $type = strtolower($matchSuccess[1][$index]);
+ $successArray[$name] = [
+ 'type' => strtolower($matchSuccess[1][$index]),
+ 'description' => $matchSuccess2[3]
+ ];
+ if (in_array($type, ['object', 'array', 'json'])) {
+ $exampleSuccess = $matchSuccess[3];
+ preg_match("/\*\s*\@apiSuccessExample\s+\{(.*?)\}\s*{$name}:*([\s\S]*?)(?=(\*\s*@|\*\s*\/))/i", $original, $matchExample);
+ if ($matchExample) {
+ $exampleSuccess = [$matchExample[2]];
+ }
+ $schemaData = [];
+ if (array_filter($exampleSuccess, function ($item) use (&$schemaData) {
+ $item = preg_replace("/(\\n|^)\s*\*/", '', $item);
+ $itemData = json5_decode($item);
+ if ($itemData) {
+ $schemaData = $itemData;
+ return true;
+ }
+ return false;
+ })) {
+ $isArr = __isArr($schemaData);
+ $schemaData = $isArr ? $schemaData[1] : $schemaData;
+ $successArray[$name]["properties"] = __propertieData($schemaData);
+ }
+ }
+ }
+ $responsesSchema = [
+ "\$schema" => "http://json-schema.org/draft-04/schema#",
+ 'type' => 'object',
+ 'properties' => $successArray,
+ 'required' => $requiredArray,
+ ];
+ //
+ $paths[$matchApi[2]][$matchApi[1]] = [
+ 'tags' => [$tagText],
+ 'consumes' => strtolower($matchApi[1]) == 'post' ? ['multipart/form-data'] : [],
+ 'summary' => $matchApi[3],
+ 'description' => $matchDesc ? trim($matchDesc[1]) : '',
+ 'parameters' => $paramArray,
+ 'responses' => [
+ '200' => [
+ 'description' => 'successful operation',
+ 'schema' => $responsesSchema
+ ]
+ ]
+ ];
+ }
+}
+//
+$tagArray = [];
+foreach ($tags as $tag) {
+ $tagArray[] = [
+ 'name' => $tag,
+ 'description' => $tag,
+ ];
+}
+//
+$title = "Api";
+$version = "0.0.1";
+$packagePath = dirname($path, 4) . '/package.json';
+if (file_exists($packagePath)) {
+ $packageArray = json_decode(file_get_contents(dirname($path, 4) . '/package.json'), true);
+ if ($packageArray) {
+ $title = $packageArray['name'];
+ $version = $packageArray['version'];
+ }
+}
+//
+$content = [
+ 'swagger' => '2.0',
+ 'info' => [
+ 'title' => $title,
+ 'version' => $version,
+ ],
+ 'basePath' => '/',
+ 'tags' => $tagArray,
+ 'schemes' => ['http'],
+ 'paths' => $paths
+];
+
+$filePath = dirname($path, 4) . '/public/docs';
+mkdir($filePath, 0777, true);
+file_put_contents($filePath . '/swagger.json', __array2json($content, true));
+
+echo "Success \n";
+
+
+/** ************************************************************** */
+/** ************************************************************** */
+/** ************************************************************** */
+
+function __propertieData($jsonArray)
+{
+ list($jsonArray) = json5_value_note($jsonArray);
+ $properties = [];
+ foreach ($jsonArray as $k => $v) {
+ $properties[$k] = [
+ "type" => "string",
+ ];
+ list($value, $note) = json5_value_note($v);
+ if ($note) {
+ $properties[$k]["description"] = $note;
+ }
+ if (__isArr($value)) {
+ $properties[$k]["type"] = "array";
+ $properties[$k]["items"] = __propertieItem($value);
+ } elseif (__isJson($value)) {
+ $properties[$k]["type"] = "object";
+ $properties[$k]["properties"] = __propertieItem($value);
+ } elseif (floatval($value) == $value) {
+ $properties[$k]["type"] = str_contains("$value", ".") ? "integer" : "number";
+ }
+ }
+ return $properties;
+}
+
+function __propertieItem($data)
+{
+ if (__isArr($data)) {
+ return [
+ 'type' => 'object',
+ 'properties' => $data ? __propertieData($data[0]) : json_decode('{}'),
+ ];
+ } elseif (__isJson($data)) {
+ return $data ? __propertieData($data) : json_decode('{}');
+ }
+ return json_decode('{}');
+}
+
+function __array2json($array, $options = 0)
+{
+ if (!is_array($array)) {
+ return $array;
+ }
+ if ($options === true) {
+ $options = JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES;
+ }
+ try {
+ return json_encode($array, $options);
+ } catch (Exception $e) {
+ return '';
+ }
+}
+
+function __isArr($data)
+{
+ return is_array($data) && str_starts_with(__array2json($data), '[');
+}
+
+function __isJson($data)
+{
+ return is_array($data) && str_starts_with(__array2json($data), '{');
+}
+
+/** ************************************************************** */
+/** ************************************************************** */
+/** ************************************************************** */
+
+// JSON5 for PHP
+// [LICENSE] MIT
+// [URL] https://github.com/kujirahand/JSON5-PHP
+
+function json5_value_note($v)
+{
+ if (is_array($v) && $v['type'] === '__note__') {
+ return [$v['value'], $v['note']];
+ }
+ return [$v, null];
+}
+
+function json5_decode($json5, $assoc = true)
+{
+ return json5_value($json5, $assoc);
+}
+
+function json5_value(&$json5, $assoc)
+{
+ $json5 = trim($json5);
+ json5_comment($json5); // skip comment
+ // check 1char
+ $c = substr($json5, 0, 1);
+ // Object
+ if ($c == '{') {
+ return json5_note($json5, json5_object($json5, $assoc));
+ }
+ // Array
+ if ($c == '[') {
+ return json5_note($json5, json5_array($json5, $assoc));
+ }
+ // String
+ if ($c == '"' || $c == "'") {
+ return json5_note($json5, json5_string($json5));
+ }
+ // null / true / false / Infinity
+ if (strncasecmp($json5, "null", 4) == 0) {
+ $json5 = substr($json5, 4);
+ return json5_note($json5, null);
+ }
+ if (strncasecmp($json5, "true", 4) == 0) {
+ $json5 = substr($json5, 4);
+ return json5_note($json5, true);
+ }
+ if (strncasecmp($json5, "false", 5) == 0) {
+ $json5 = substr($json5, 5);
+ return json5_note($json5, false);
+ }
+ if (strncasecmp($json5, "infinity", 8) == 0) {
+ $json5 = substr($json5, 8);
+ return json5_note($json5, INF);
+ }
+ // hex
+ if (preg_match('/^(0x[a-zA-Z0-9]+)/', $json5, $m)) {
+ $num = $m[1];
+ $json5 = substr($json5, strlen($num));
+ return json5_note($json5, intval($num, 16));
+ }
+ // number
+ if (preg_match('/^((\+|\-)?\d*\.?\d*[eE]?(\+|\-)?\d*)/', $json5, $m)) {
+ $num = $m[1];
+ $json5 = substr($json5, strlen($num));
+ return json5_note($json5, floatval($num));
+ }
+ // other char ... maybe error
+ $json5 = substr($json5, 1);
+ return json5_note($json5, null);
+}
+
+function json5_note($original, $value)
+{
+ $array = explode("\n", $original);
+ preg_match("/\/\/(.*?)(\\n|$)/i", $array[0], $match);
+ if ($match) {
+ return [
+ 'type' => '__note__',
+ 'value' => $value,
+ 'note' => trim($match[1]),
+ ];
+ }
+ return $value;
+}
+
+function json5_comment(&$json5)
+{
+ while ($json5 != '') {
+ $json5 = ltrim($json5);
+ $c2 = substr($json5, 0, 2);
+ // block comment
+ if ($c2 == '/*') {
+ json5_token($json5, '*/');
+ continue;
+ }
+ // line comment
+ if ($c2 == '//') {
+ json5_token($json5, "\n");
+ continue;
+ }
+ break;
+ }
+}
+
+function json5_string(&$json5)
+{
+ // check flag
+ $flag = substr($json5, 0, 1);
+ $json5 = substr($json5, 1);
+ $str = "";
+ while ($json5 != "") {
+ // check
+ $c = mb_substr($json5, 0, 1);
+ $json5 = substr($json5, strlen($c));
+ // Is terminator?
+ if ($c == $flag) break;
+ // escape
+ if ($c == "\\") {
+ if (str_starts_with($json5, "\r\n")) {
+ $json5 = substr($json5, 2);
+ $str .= "\r\n";
+ continue;
+ }
+ if (str_starts_with($json5, "\n")) {
+ $json5 = substr($json5, 1);
+ $str .= "\n";
+ continue;
+ }
+ if (substr($json5, 0, 1) == $flag) {
+ $json5 = substr($json5, 1);
+ $str .= "\\" . $flag;
+ continue;
+ }
+ }
+ $str .= $c;
+ }
+ $str = json_decode('"' . $str . '"'); // extract scape chars...
+ return $str;
+}
+
+function json5_array(&$json5, $assoc)
+{
+ // skip '['
+ $json5 = substr($json5, 1);
+ $res = array();
+ while ($json5 != '') {
+ json5_comment($json5);
+ // Array terminator?
+ if (strncmp($json5, ']', 1) == 0) {
+ $json5 = substr($json5, 1);
+ break;
+ }
+ // get value
+ $res[] = json5_value($json5, $assoc);
+ // skip comma
+ $json5 = ltrim($json5);
+ if (str_starts_with($json5, ',')) {
+ $json5 = substr($json5, 1);
+ }
+ }
+ return $res;
+}
+
+function json5_object(&$json5, $assoc)
+{
+ // skip '{'
+ $json5 = substr($json5, 1);
+ if ($assoc) {
+ $res = array();
+ } else {
+ $res = new \stdClass();
+ }
+
+ while ($json5 != '') {
+ json5_comment($json5);
+ // Object terminator?
+ if (strncmp($json5, '}', 1) == 0) {
+ $json5 = substr($json5, 1);
+ break;
+ }
+ // get KEY
+ $c = substr($json5, 0, 1);
+ if ($c == '"' || $c == "'") {
+ $key = json5_string($json5);
+ json5_token($json5, ':');
+ } else {
+ $key = trim(json5_token($json5, ":"));
+ }
+ // get VALUE
+ $value = json5_value($json5, $assoc);
+
+ if ($assoc) {
+ $res[$key] = $value;
+ } else {
+ $res->$key = $value;
+ }
+
+ // skip Comma
+ $json5 = ltrim($json5);
+ if (strncmp($json5, ',', 1) == 0) {
+ $json5 = substr($json5, 1);
+ }
+ }
+ return $res;
+}
+
+function json5_token(&$str, $spl)
+{
+ $i = strpos($str, $spl);
+ if ($i === FALSE) {
+ $result = $str;
+ $str = "";
+ return $result;
+ }
+ $result = substr($str, 0, $i);
+ $str = substr($str, $i + strlen($spl));
+ return $result;
+}
+
+
+/**
+ * @param $path
+ * @return array
+ */
+function controllerFiles($path)
+{
+ //判断目录是否为空
+ if (!file_exists($path)) {
+ return [];
+ }
+ $files = scandir($path);
+ $fileItem = [];
+ foreach ($files as $v) {
+ $newPath = $path . DIRECTORY_SEPARATOR . $v;
+ if (is_dir($newPath) && $v != '.' && $v != '..') {
+ $fileItem = array_merge($fileItem, controllerFiles($newPath));
+ } else if (is_file($newPath) && str_ends_with($newPath, 'Controller.php')) {
+ $fileItem[] = $newPath;
+ }
+ }
+ return $fileItem;
+}
diff --git a/app/Models/User.php b/app/Models/User.php
index 8d573b0f3..40d63c24b 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -605,6 +605,9 @@ class User extends AbstractModel
case 'anon-msg':
$update['nickname'] = '匿名消息';
break;
+ case 'approval-alert':
+ $update['nickname'] = '审批';
+ break;
case 'bot-manager':
$update['nickname'] = '机器人管理';
break;
diff --git a/app/Models/WorkflowProcMsg.php b/app/Models/WorkflowProcMsg.php
new file mode 100644
index 000000000..c61dfc2cf
--- /dev/null
+++ b/app/Models/WorkflowProcMsg.php
@@ -0,0 +1,30 @@
+ $value) {
+ //如果是数组,递归调用
+ if (is_array($value)) {
+ $value = self::arrayKeyToUnderline($value);
+ }
+ $newKey = strtolower(preg_replace('/(?<=[a-z])([A-Z])/', '_$1', $key));
+ $newArray[$newKey] = $value;
+ }
+ return $newArray;
+ }
+
+ /**
+ * 多维数组字母转驼峰格式
+ *
+ * @param [type] $array
+ * @return array
+ */
+ public static function arrayKeyToCamel($array)
+ {
+ $newArray = [];
+ foreach ($array as $key => $value) {
+ //如果是数组,递归调用
+ if (is_array($value)) {
+ $value = self::arrayKeyToCamel($value);
+ }
+ $newKey = lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', $key))));
+ $newArray[$newKey] = $value;
+ }
+ return $newArray;
+ }
}
diff --git a/cmd b/cmd
index b7957bc67..05e9e1b9b 100755
--- a/cmd
+++ b/cmd
@@ -393,6 +393,7 @@ if [ $# -gt 0 ]; then
elif [[ "$1" == "doc" ]]; then
shift 1
run_exec php "php app/Http/Controllers/Api/apidoc.php"
+ run_exec php "php app/Http/Controllers/Api/apiswagger.php"
docker run -it --rm -v ${cur_path}:/home/node/apidoc kuaifan/apidoc -i app/Http/Controllers/Api -o public/docs
elif [[ "$1" == "debug" ]]; then
shift 1
diff --git a/database/migrations/2023_04_11_122557_create_workflow_proc_msgs.php b/database/migrations/2023_04_11_122557_create_workflow_proc_msgs.php
new file mode 100644
index 000000000..a96bd5d6b
--- /dev/null
+++ b/database/migrations/2023_04_11_122557_create_workflow_proc_msgs.php
@@ -0,0 +1,38 @@
+bigIncrements('id');
+ $table->bigInteger('proc_inst_id')->nullable()->default(0)->comment('流程实例ID');
+ $table->bigInteger('userid')->nullable()->default(0)->comment('会员ID');
+ $table->bigInteger('msg_id')->nullable()->default(0)->comment('消息ID');
+ $table->timestamps();
+ });
+ }
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::table('workflow_proc_msgs', function (Blueprint $table) {
+ Schema::dropIfExists('workflow_proc_msgs');
+ });
+ }
+}
diff --git a/docker-compose.yml b/docker-compose.yml
index 81424ff28..3acfef799 100755
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -148,6 +148,24 @@ services:
ipv4_address: "${APP_IPPR}.10"
restart: unless-stopped
+ workflow:
+ container_name: "dootask-workflow-${APP_ID}"
+ image: "weifashi/go-workflow:1.0.0"
+ ports:
+ - "8800:80"
+ environment:
+ TZ: "Asia/Shanghai"
+ MYSQL_HOST: "${DB_HOST}"
+ MYSQL_PORT: "${DB_PORT}"
+ MYSQL_DBNAME: "${DB_DATABASE}"
+ MYSQL_USERNAME: "${DB_USERNAME}"
+ MYSQL_PASSWORD: "${DB_PASSWORD}"
+ MYSQL_Prefix: "${DB_PREFIX}workflow_"
+ networks:
+ extnetwork:
+ ipv4_address: "${APP_IPPR}.11"
+ restart: always
+
networks:
extnetwork:
name: "dootask-networks-${APP_ID}"
diff --git a/resources/views/push/bot.blade.php b/resources/views/push/bot.blade.php
index 83dd031ff..0f1d338d1 100755
--- a/resources/views/push/bot.blade.php
+++ b/resources/views/push/bot.blade.php
@@ -108,6 +108,49 @@
version: 系统版本
@elseif ($type === 'notice')
{{$notice}}
+@elseif ($type === 'workflow_reviewer')
+{{$data->nickname}}提交的「{{$data->proc_def_name}}」待你审批
+
+申请人:{{$data->nickname}} {{$data->department}}
+
+审批事由
+假期类型:{{$data->type}}
+开始时间:{{$data->start_time}}
+结束时间:{{$data->end_time}}
+
+ @if ($action === 'pass')
+ 已同意
+ @elseif ($action === 'refuse')
+ 已拒绝
+ @elseif ($action === 'withdraw')
+ 已撤销
+ @else
+ 同意
+ 拒绝
+ @endif
+
+@elseif ($type === 'workflow_notifier')
+抄送{{$data->nickname}}提交的「{{$data->proc_def_name}}」记录
+
+申请人:{{$data->nickname}} {{$data->department}}
+
+审批事由
+假期类型:{{$data->type}}
+开始时间:{{$data->start_time}}
+结束时间:{{$data->end_time}}
+查看详情
+@elseif ($type === 'workflow_submitter')
+ @if ($action === 'pass')
+ 您发起的「{{$data->proc_def_name}}」已通过
+ @else
+ 您发起的「{{$data->proc_def_name}}」被{{$data->nickname}}拒绝
+ @endif
+
+审批事由
+假期类型:{{$data->type}}
+开始时间:{{$data->start_time}}
+结束时间:{{$data->end_time}}
+查看详情
@else
你好,我是你的机器人助理,你可以发送 /help 查看帮助菜单。
@endif
diff --git a/routes/web.php b/routes/web.php
index 5c320a075..09b8cd321 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -1,14 +1,15 @@
middleware(['webapi'])->group(function () {
// 公开接口
Route::any('public/{method}', PublicController::class);
Route::any('public/{method}/{action}', PublicController::class);
+ // 审批
+ Route::any('workflow/{method}', WorkflowController::class);
+ Route::any('workflow/{method}/{action}', WorkflowController::class);
});
/**