169 lines
5.9 KiB
PHP
169 lines
5.9 KiB
PHP
<?php
|
||
// +----------------------------------------------------------------------
|
||
// | YwxApp [ WE CAN DO IT JUST THINK ]
|
||
// +----------------------------------------------------------------------
|
||
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
|
||
// +----------------------------------------------------------------------
|
||
// | Author: ywxapp<admin@ywxapp.cn>
|
||
// +----------------------------------------------------------------------
|
||
declare (strict_types = 1);
|
||
|
||
namespace addon\wxchat\worker\handle;
|
||
|
||
use addon\wxchat\model\WxchatMessage;
|
||
use addon\wxchat\model\WxchatUnreadCount;
|
||
use addon\wxchat\service\SafetyService;
|
||
use addon\wxchat\worker\enum\MsgFrame;
|
||
use addon\wxchat\worker\enum\MsgType;
|
||
use addon\wxchat\worker\Message;
|
||
use addon\wxchat\worker\MsgReply;
|
||
use GatewayWorker\Lib\Gateway;
|
||
|
||
/**
|
||
* 聊天消息处理(实时落库 + 转发 + ACK)
|
||
*
|
||
* 协议约定(客户端上行):
|
||
* {
|
||
* "header": { "type":"chat", "target":"single"|"group", "timestamp":... },
|
||
* "payload": { "uid":123, "receiver":456, "session_id":789, "type":"text"|"image"|"voice"|"video"|"file"|"gift", "content":"...", "ext":{} }
|
||
* }
|
||
*/
|
||
class ChatHandle
|
||
{
|
||
// 内容类型字符串 -> 数据库 content_type(int)
|
||
private const CONTENT_TYPE_MAP = [
|
||
'text' => 0,
|
||
'image' => 1,
|
||
'voice' => 2,
|
||
'video' => 3,
|
||
'file' => 4,
|
||
'gift' => 5,
|
||
];
|
||
|
||
|
||
public static function handle($client_id, $header, $payload)
|
||
{
|
||
$uid = Gateway::getUidByClientId($client_id);
|
||
if (empty($uid)) {
|
||
MsgReply::sendError($client_id, '未认证,请先登录');
|
||
return;
|
||
}
|
||
$payloadUid = (int)($payload->uid ?? 0);
|
||
if ($payloadUid && $payloadUid != $uid) {
|
||
MsgReply::sendError($client_id, '身份不一致');
|
||
return;
|
||
}
|
||
|
||
$contentType = (string)($payload->type ?? 'text');
|
||
if (! isset(self::CONTENT_TYPE_MAP[$contentType])) {
|
||
MsgReply::sendError($client_id, '不支持的消息类型: ' . $contentType);
|
||
return;
|
||
}
|
||
|
||
// 礼物消息:仅做实时通知,落库由 HTTP Gift::send 负责
|
||
if ($contentType === 'gift') {
|
||
self::relayGift($client_id, $uid, $payload);
|
||
return;
|
||
}
|
||
|
||
$target = (string)($header->target ?? 'single');
|
||
$receiverType = ($target === 'group') ? 1 : 0;
|
||
$receiverId = (int)($target === 'group'
|
||
? ($payload->session_id ?? 0)
|
||
: ($payload->receiver ?? 0));
|
||
|
||
if (! $receiverId) {
|
||
MsgReply::sendError($client_id, '接收者无效');
|
||
return;
|
||
}
|
||
|
||
$content = $payload->content ?? '';
|
||
// 文本类消息做敏感词过滤
|
||
if ($contentType === 'text') {
|
||
$content = SafetyService::instance()->filter($content);
|
||
}
|
||
|
||
$msgId = self::saveMessage($uid, $receiverType, $receiverId, self::CONTENT_TYPE_MAP[$contentType], $content);
|
||
|
||
// 单聊:对方未读 +1
|
||
if ($receiverType == 0) {
|
||
WxchatUnreadCount::increment($receiverId, 0, $uid);
|
||
}
|
||
|
||
// 构造下发消息并转发
|
||
$out = self::buildOutbound($msgId, $uid, $contentType, $content, $payload);
|
||
if ($receiverType == 0) {
|
||
if (Gateway::isUidOnline($receiverId)) {
|
||
Gateway::sendToUid($receiverId, json_encode($out));
|
||
}
|
||
// 离线:消息已落库,接收方下次拉取 history 获取
|
||
} else {
|
||
Gateway::sendToGroup((string)$receiverId, json_encode($out));
|
||
}
|
||
|
||
// 发送方回执
|
||
MsgReply::send($client_id, MsgFrame::STATUS->value, MsgType::DELIVERED->value, 'delivered', [
|
||
'msg_id' => $msgId,
|
||
'receiver' => $receiverId,
|
||
'session_id' => $receiverId,
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* 消息落库(可被 HTTP / WebSocket 共用,返回消息ID)
|
||
*/
|
||
public static function saveMessage($senderId, $receiverType, $receiverId, $contentType, $content)
|
||
{
|
||
$msg = new WxchatMessage();
|
||
$msg->sender_id = $senderId;
|
||
$msg->receiver_type = $receiverType;
|
||
$msg->receiver_id = $receiverId;
|
||
$msg->content = $content;
|
||
$msg->content_type = $contentType;
|
||
$msg->save();
|
||
return $msg->id;
|
||
}
|
||
|
||
|
||
private static function buildOutbound($msgId, $senderId, $contentType, $content, $payload)
|
||
{
|
||
$out = new Message();
|
||
$out->header->frame = MsgFrame::CHAT->value;
|
||
$out->header->type = MsgFrame::CHAT->value;
|
||
$out->payload->msg_id = $msgId;
|
||
$out->payload->uid = $senderId;
|
||
$out->payload->type = $contentType; // 文本类为字符串,媒体类为URL
|
||
$out->payload->content = $content;
|
||
$out->payload->receiver = $payload->receiver ?? 0;
|
||
$out->payload->session_id = $payload->session_id ?? 0;
|
||
$out->payload->status = 'delivered';
|
||
if (! empty($payload->ext)) {
|
||
$out->payload->ext = $payload->ext;
|
||
}
|
||
return $out;
|
||
}
|
||
|
||
|
||
private static function relayGift($client_id, $uid, $payload)
|
||
{
|
||
$receiver = (int)($payload->receiver ?? 0);
|
||
if (! $receiver) {
|
||
MsgReply::sendError($client_id, '礼物接收者无效');
|
||
return;
|
||
}
|
||
$out = new Message();
|
||
$out->header->frame = MsgFrame::CHAT->value;
|
||
$out->header->type = 'gift';
|
||
$out->payload->uid = $uid;
|
||
$out->payload->receiver = $receiver;
|
||
$out->payload->content = $payload->content ?? '';
|
||
$out->payload->ext = $payload->ext ?? null;
|
||
if (Gateway::isUidOnline($receiver)) {
|
||
Gateway::sendToUid($receiver, json_encode($out));
|
||
}
|
||
MsgReply::send($client_id, MsgFrame::STATUS->value, MsgType::DELIVERED->value, 'delivered', [
|
||
'receiver' => $receiver,
|
||
]);
|
||
}
|
||
}
|