caipan_shop_admin/app/traits/account/AccountMessageTrait.php

535 lines
18 KiB
PHP
Executable File
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?php
namespace app\traits\account;
use app\exception\RepositoryException;
use app\service\ali\Sms;
use app\service\wx\WechatApplets;
use app\validate\MessageValidate;
use Exception;
use GuzzleHttp\Exception\GuzzleException;
use app\model\{Account, Config, Message, MessageLog, SmsLog};
use app\exception\TraitException;
use think\Collection;
use think\facade\Log;
use think\Model;
use think\Validate;
/**
* 用户消息
*
* Trait MessageTrait
* @package app\traits\account
*/
trait AccountMessageTrait
{
/**
* 消息类型文本描述
* @return string[]
*/
public function messageTypeTextList(): array
{
return Message::typeTextList();
}
/**
* 目标人群文本描述
* @return string[]
*/
public function messageTargetTextList(): array
{
return Message::targetTextList();
}
/**
* 获取消息列表
*
* @param array $where
* @param array $fields
* @param int $page
* @param int $size
* @param callable|null $callback
* @param array $orders
* @return array
*/
public function messageList(array $where = [], array $fields = [], int $page = 1, int $size = 10, callable $callback = null, array $orders = []): array
{
try {
return Message::findList($where, $fields, $page, $size, $callback, $orders);
} catch (Exception $e) {
return [
'total' => 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()
];
}
}
}