0, 'current' => $page, 'size' => $size, 'list' => new Collection(), ]; } } /** * 消息详情 * * @param int $messageId * @param array $fields * @return null */ public function messageInfo(int $messageId, array $fields = []) { try { return Message::findById($messageId, $fields); } catch (Exception $e) { return null; } } /** * 获取已读消息记录 * @param int $accountId * @param array $messageIds * @return array */ public function getHadReadMessageIds(int $accountId, array $messageIds): array { try { $hadItems = MessageLog::whereIn('message_id', $messageIds) ->where('account_id', $accountId) ->select(); return $hadItems->column('message_id'); } catch (Exception $e) { return []; } } /** * 添加阅读记录 * * @param int $accountId * @param array $messageIds * @return bool */ public function addReadLogs(int $accountId, array $messageIds): bool { try { $readMsgIds = $this->getHadReadMessageIds($accountId, $messageIds); $newLogMsgIds = array_diff($messageIds, $readMsgIds); if ($newLogMsgIds) { $newLogs = []; foreach ($newLogMsgIds as $msgId) { $newLogs[] = [ 'message_id' => $msgId, 'account_id' => $accountId, 'created_at' => date('Y-m-d H:i:s'), ]; } MessageLog::insertAll($newLogs); } } catch (Exception $e) { return false; } return true; } /** * 统计用户未读消息数量 * * @param int $accountId * @return int */ public function countUnReadMessage(int $accountId): int { $unReadCount = 0; try { $regTime = ''; $account = Account::findById($accountId); if ($account) { $regTime = $account['created_at']; } $allCount = Message::whereTime('send_at', '<=', date('Y-m-d H:i:s')) ->when(!empty($regTime), function ($q2) use ($regTime) { $q2->whereTime('send_at', '>=', $regTime); }) ->where(function ($q2) use ($accountId) { $q2->whereRaw('(target = "part" and find_in_set("'.$accountId.'", target_list)) or target <> "part"'); }) ->count(); $readCount = MessageLog::where('account_id', $accountId) ->when(!empty($regTime), function ($q2) use ($regTime) { $q2->whereTime('created_at', '>=', $regTime); }) ->distinct(true) ->field(['message_id']) ->count(); $unReadCount = $allCount - $readCount; $unReadCount = $unReadCount > 0 ? $unReadCount : 0; } catch (Exception $e) { } return $unReadCount; } /** * 创建消息 * * @param string $type 类型 ['system'|'notice'] * @param string $target 目标人群 ['all'| 'part'] * @param array $targetList 消息目标人群ID, $target='part' 时启用 []表示发送所有人 * @param array $messageData 消息内容 ['title' 标题|'summary' 概述|'content' 详情|'send_at' 发送时间] * @return Message|Model * @throws TraitException */ public function addMessage(string $type, string $target, array $targetList = [], array $messageData = []) { try { if (!in_array($type, array_keys(Message::typeTextList()))) { throw new TraitException('消息类型错误, 无此选项'); } if (!in_array($target, array_keys(Message::targetTextList()))) { throw new TraitException('目标人群错误, 无此选项'); } $targetList = array_filter($targetList); if ($target == Message::TARGET_PART && empty($targetList)) { throw new TraitException('目标人群为部分接收时必须指定消息接收人员列表'); } if ($target == Message::TARGET_ALL) { $targetList = []; } $validate = new MessageValidate(); if (!$validate->scene('add')->check($messageData)) { throw new TraitException($validate->getError()); } $dataFields = [ 'title', 'summary', 'content', 'send_at', 'is_push', 'to_subscribe', 'to_sms', 'subscribe_data', 'sms_data', 'subscribe_temp_id', 'sms_temp_id' ]; $messageData = arrayKeysFilter($messageData, $dataFields); $messageData['send_at'] = empty($messageData['send_at'] ?? '') ? date('Y-m-d H:i:s') : $messageData['send_at']; $messageData['created_at'] = date('Y-m-d H:i:s'); $messageData['type'] = $type; $messageData['target'] = $target; $messageData['target_list'] = empty($targetList) ? '' : implode(',', $targetList); return Message::create($messageData); } catch (TraitException $e) { throw $e; } catch (Exception $e) { Log::error('消息创建失败:'.$e->getMessage().' file:'.$e->getFile().' line:'.$e->getLine()); throw new TraitException('消息创建失败'); } } /** * 编辑消息 * * @param int $id 消息ID * @param string $type 类型 ['system'|'notice'] * @param string $target 目标人群 ['all'| 'part'] * @param array $targetList 消息目标人群ID, $target='part' 时启用 * @param array $messageData 消息内容 ['title' 标题|'summary' 概述|'content' 详情|'send_at' 发送时间] * @return Message * @throws TraitException */ public function editMessage(int $id, string $type, string $target, array $targetList = [], array $messageData = []) { try { if (!in_array($type, array_keys(Message::typeTextList()))) { throw new TraitException('消息类型错误, 无此选项'); } if (!in_array($target, array_keys(Message::targetTextList()))) { throw new TraitException('目标人群错误, 无此选项'); } $targetList = array_filter($targetList); if ($target == Message::TARGET_PART && empty($targetList)) { throw new TraitException('目标人群为部分接收时必须指定消息接收人员列表'); } if ($target == Message::TARGET_ALL) { $targetList = []; } $msg = Message::findById($id); if (empty($msg)) { throw new TraitException('没有相关的消息记录'); } $validate = new MessageValidate(); if (!$validate->scene('edit')->check($messageData)) { throw new TraitException($validate->getError()); } $dataFields = ['title', 'summary', 'content', 'send_at']; $messageData = arrayKeysFilter($messageData, $dataFields); if (isset($messageData['send_at']) && empty($messageData['send_at'])) { unset($messageData['send_at']); } $messageData['updated_at'] = date('Y-m-d H:i:s'); $messageData['type'] = $type; $messageData['target'] = $target; $messageData['target_list'] = empty($targetList) ? '' : implode(',', $targetList); return Message::where('id', $id)->update($messageData); } catch (TraitException $e) { throw $e; } catch (Exception $e) { throw new TraitException('消息更新失败'); } } /** * 删除消息记录 * * @param array $ids * @return bool */ public function deleteMessages(array $ids): bool { try { Message::deleteByIds($ids); // 删除关联的阅读记录 MessageLog::whereIn('message_id', $ids)->delete(); } catch (Exception $e) { return false; } return true; } /** * 发送预约审核的订阅消息 * * 注1:订阅消息需要在用户预约后订阅成功后才能发送,每次订阅只能推送一条订阅消息; * 注2:用户取消订阅后无法下发订阅消息 * 注3:$msgData 参数格式必须严格按照消息模板字段的格式限制填写 * * @param string $openid 接收者openid * @param array $msgData 消息体 样例:['date1'=>date('Y年m月d日'), 'thing2'=>'植发', 'character_string12'=>'8:30 ~ 9:30'] * @param string $state 跳转小程序类型。 developer为开发版;trial为体验版;formal为正式版;默认为正式版 * @return array ['status'=>[false|true], 'msgid'=>''] */ public function sendAppointmentSubscribeMessage(string $openid, array $msgData, string $state = 'formal'): array { $resp = ['status' => false, 'msgid' => '']; // 跳转到预约列表界面 $page = Config::MINI_PATH_APPOINTMENT; try { if (empty($openid) || empty($msgData)) { return $resp; } $wxApp = WechatApplets::getInstance(); $dataList = []; foreach ($msgData as $tk => $tv) { $dataList[$tk]['value'] = $tv; } $templateId = WechatApplets::SUBSCRIBE_TPL_APPOINTMENT; $data = [ // 所需下发的订阅模板id 'template_id' => $templateId, // 接收者(用户)的 openid 'touser' => $openid, // 点击模板卡片后的跳转页面,仅限本小程序内的页面。支持带参数,(示例index?foo=bar)。该字段不填则模板无跳转。 'page' => $page, //跳转小程序类型:developer为开发版;trial为体验版;formal为正式版;默认为正式版 'miniprogram_state' => $state, // 模板内容,格式形如 { "key1": { "value": any }, "key2": { "value": any } } 'data' => $dataList, ]; $res = $wxApp->subscribe_message->send($data); if (isset($res['errcode']) && $res['errcode'] == 0) { $resp = ['status' => true, 'msgid' => $res['msgid'] ?? '']; } } catch (GuzzleException | Exception $e) { } return $resp; } /** * 订阅消息发送 * * @param int $messageId * @param string $templateId * @param array $targetList * @param array $messageData * @param string $page * @param string $state * @throws GuzzleException * @throws RepositoryException */ public function subscribeSend(int $messageId, string $templateId, array $targetList = [], array $messageData = [], string $page = '', string $state = '') { try { $accountModel = new Account(); if (!empty($targetList)) { $accountModel = $accountModel->whereIn('id', $targetList); } $accountList = $accountModel->field('openid, id, nickname')->select(); // ['template_id' => '模版ID', 'msg' => msgBody, 'page' => '小程序跳转页面', 'state' => '小程序类型'] $data = [ 'template_id' => $templateId, 'msg' => $messageData, 'page' => $page ]; if (!empty($state)) { $data['state'] = $state; } WechatApplets::sendBatchSubscribeMessage($messageId, $accountList->column('openid'), $data); } catch (RepositoryException | Exception $e) { throw $e; } } /** * 短信发送 * * @param int $messageId * @param string $templateId * @param array $targetList * @param array $messageData * @throws GuzzleException * @throws RepositoryException */ public function smsSendBak(int $messageId, string $templateId, array $targetList = [], array $messageData = []) { try { // var_dump($messageData); // exit; $accountModel = new Account(); if (!empty($targetList)) { $accountModel = $accountModel->whereIn('id', $targetList); } $accountList = $accountModel->field('mobile, id, nickname')->select(); // ['template_id' => '模版ID', 'msg' => msgBody] $data = [ 'phoneNumberJson' => '', // 如'["13541194xxxx"]' 'signNameJson' => '',// 如 '["商城","商城"]' 'templateCode' => '', 'templateParamJson' => '', //如 '[{"code":"12345"},{"code":"333"}]' ]; if ($accountList->count() <= 1000) { $templateParamArr = []; $signNameArr = []; $mobileArr = []; foreach ($accountModel->column('mobile') as $mobile) { if (!empty($mobile)) { array_push($mobileArr, $mobile); array_push($signNameArr, Sms::SMS_SIGN); if (!empty($messageData)) { array_push($templateParamArr, $messageData); } } } $data['phoneNumberJson'] = json_encode($mobileArr, JSON_UNESCAPED_UNICODE); $data['signNameJson'] = json_encode($signNameArr, JSON_UNESCAPED_UNICODE); $data['templateCode'] = $templateId; $data['templateParamJson'] = json_encode($templateParamArr, JSON_UNESCAPED_UNICODE); Log::error('[短信批量发送日志]'.json_encode($data, JSON_UNESCAPED_UNICODE)); Sms::batchSendByApi($data); } else { Sms::batchSendByApi($data); } } catch (RepositoryException | Exception $e) { throw $e; } } /** * 短信发送[支持批量] * * @param int $messageId * @param string $templateId * @param array $targetList 用户ID列表 * @param array $messageData * @throws Exception * @return array */ public function smsSend(int $messageId, string $templateId, array $targetList = [], array $messageData = []): array { try { $accountModel = new Account(); if (!empty($targetList)) { $accountModel = $accountModel->whereIn('id', $targetList); } $accountList = $accountModel->whereNotNull('mobile')->field('mobile, id, nickname')->select(); Log::error('[短信批量发送日志]'.json_encode($messageData, JSON_UNESCAPED_UNICODE)); $smsLog = []; $now = date('Y-m-d H:i:s'); $successNum = 0; $failNum = 0; $total = $accountList->count(); foreach ($accountList->column('mobile') as $mobile) { if (empty($mobile)) { continue; } $arr = []; $arr['phone'] = $mobile; $arr['type'] = $templateId; $arr['created_at'] = $now; $arr['data'] = !empty($messageData) ? json_encode($messageData, JSON_UNESCAPED_UNICODE) : ''; $arr['message_id'] = $messageId; $arr['status'] = Sms::STATUS_SUCCESS; $res = Sms::send($mobile, $messageData, $templateId); $success = true; if ($res !== true) { $arr['status'] = Sms::STATUS_FAIL; $arr['remarks'] = $res; $success = false; } if ($success) { $successNum++; } else { $failNum++; } $smsLog[] = $arr; } if (!empty($smsLog)) { (new SmsLog())->insertAll($smsLog); } return [ 'status' => $total == $successNum, 'msg' => sprintf("总数%d条,发送成功%d条,发送失败%d条", $total, $successNum, $failNum) ]; } catch (Exception $e) { return [ 'status' => false, 'msg' => $e->getMessage() ]; } } }