chore: 重写初始提交(清空历史,整理后全量提交)

This commit is contained in:
ywxapp
2026-08-16 16:54:14 +08:00
commit 6c1a106bc1
1808 changed files with 238144 additions and 0 deletions
+168
View File
@@ -0,0 +1,168 @@
<?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,
]);
}
}
@@ -0,0 +1,48 @@
<?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\worker\enum\MsgType;
/**
* ControlHandle 类
*
* @author ywxapp <admin@ywxapp.cn>
*/
class ControlHandle
{
public static function handle($client_id, $header, $payload)
{
switch ($header->type) {
case MsgType::HEARTBEAT->value:
self::Heartbeat($client_id, $header, $payload);
break;
case MsgType::AUTH->value:
self::Auth($client_id, $header, $payload);
break;
case MsgType::NOTICE->value:
self::Notification($client_id, $header, $payload);
break;
default:
ErrorHandle::sendErrorMsg($client_id, '不支持的消息协议! ');
}
}
/**
* 心跳响应
*/
public static function heartbeat($client_id, $packetId)
{
Error::sendErrorMsg($client_id, '不支持的消息类型! ');
}
}
@@ -0,0 +1,25 @@
<?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\worker\MsgReply;
/**
* 错误发送包装(兼容 ControlHandle / SystemHandle 的原有引用)
*/
class ErrorHandle
{
public static function sendErrorMsg($client_id, $msg)
{
MsgReply::sendError($client_id, $msg);
}
}
+34
View File
@@ -0,0 +1,34 @@
<?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\worker\enum\MsgFrame;
use addon\wxchat\worker\Message;
use GatewayWorker\Lib\Gateway;
/**
* Heartbeat 类
*
* @author ywxapp <admin@ywxapp.cn>
*/
class Heartbeat
{
public static function handle($client_id, $header, $payload)
{
if ($header->type === MsgFrame::PONG->value) {
return;
}
$newMessage = new Message();
$newMessage->header->type = MsgFrame::PONG->value;
echo "Received PING newMessage: " . json_encode($newMessage) . ", responding with PONG\n";
Gateway::sendToClient($client_id, json_encode($newMessage));
}
}
@@ -0,0 +1,109 @@
<?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\wprker\handle;
use GatewayWorker\Lib\Gateway;
/**
* MessageHandle 类
*
* @author ywxapp <admin@ywxapp.cn>
*/
class MessageHandle
{
protected static $sensitiveWords = ['fuck', 'shit', '赌博', '色情'];
/**
* 敏感词过滤
*/
protected static function filterSensitiveWords($text)
{
foreach (self::$sensitiveWords as $word) {
$text = str_replace($word, '**', $text);
}
return $text;
}
/**
* 处理普通消息(文本/图片/文件)
*/
protected static function handle($client_id, $message)
{
$header = $message->header;
$payload = $message->payload;
$fromUid = $header->from ?? '';
$uid = Gateway::getUidByClientId($client_id);
echo "Received message from UID: $fromUid, ClientID: $client_id , $uid\n";
if (! $uid || $uid != $fromUid) {
self::sendError($client_id, '未认证用户,无法发送消息');
return;
}
if ($header->type == 'text' && ! empty($payload->content)) {
$filteredContent = self::filterSensitiveWords($payload->content);
if ($filteredContent !== $payload->content) {
// file_put_contents('/tmp/sensitive_log.log', date('Y-m-d H:i:s') . " {$fromUid} 发送敏感词: {$data['payload']['content']}\n", FILE_APPEND);
$message->payload->content = $filteredContent;
}
}
// 消息持久化(重要!写入数据库)
// self::saveMessageToDB($data);
// 转发消息
if ($header->subtype === 'single') {
// 单聊:发送给目标用户
if (Gateway::isUidOnline($header->to)) {
Gateway::sendToUid($header->to, json_encode($message));
// 发送ACK给发送方(表示已送达)
$ackMsg = new Message();
$ackMsg->header->type = MessageType::System;
$ackMsg->header->subtype = MessageType::Delivered;
$ackMsg->payload->content = '不支持的消息类型! ';
Gateway::sendToUid($header->from, json_encode($message));
} else {
// 对方不在线,存离线消息(写入Redis/DB)
// self::saveOfflineMessage($toId, $data);
$newMessage = new Message();
$newMessage->header->type = MessageType::Error;
$newMessage->payload->content = "用户 {$toId} 不在线,消息已存为离线";
Gateway::sendToClient($client_id, json_encode($message));
}
} elseif ($header->subtype === 'group') {
// 群聊:发送给群内所有成员(排除自己)
// TODO: 从数据库获取群成员列表
// $members = self::getGroupMembers($toId);
// foreach ($members as $memberUid) {
// if ($memberUid != $fromUid && Gateway::hasUid($memberUid)) {
// Gateway::sendToUid($memberUid, json_encode($data));
// }
// }
Gateway::sendToGroup($header->to, json_encode($message));
$ackMsg = new Message();
$ackMsg->header->type = MessageType::System;
$ackMsg->header->subtype = MessageType::Delivered;
$ackMsg->payload->content = '不支持的消息类型! ';
Gateway::sendToUid($header->from, json_encode($message));
} else {
$errorMsg = new Message();
$errorMsg->header->type = MessageType::Error;
$errorMsg->payload->content = "未知会话类型";
Gateway::sendToClient($client_id, json_encode($errorMsg));
return;
}
// 回复发送方:消息已接收(前端更新状态用)
$receivedMsg = new Message();
$receivedMsg->header->type = MessageType::Error;
Gateway::sendToClient($client_id, json_encode($receivedMsg));
}
}
@@ -0,0 +1,263 @@
<?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\WxchatFriend;
use addon\wxchat\model\WxchatGroup;
use addon\wxchat\model\WxchatGroupMember;
use addon\wxchat\worker\enum\MsgFrame;
use addon\wxchat\worker\enum\MsgType;
use addon\wxchat\worker\MsgReply;
use GatewayWorker\Lib\Gateway;
use ywxapp\model\MemberUser;
/**
* 关系消息处理(加好友 / 同意 / 拒绝 / 加群 / 邀请)
* 真实落库:wxapp_wxchat_friend / wxapp_wxchat_group_members / wxapp_wxchat_groups
*
* 协议约定(客户端上行):
* {
* "header": { "type":"rel", "timestamp":... },
* "payload": { "type":"friend_request"|"friend_agree"|"friend_refuse"|"group_join"|"group_invite",
* "receiver":<对方UID>, "session_id":<群ID>, "remark":"备注/招呼语" }
* }
*/
class RelationHandle
{
public static function handle($client_id, $header, $payload)
{
$uid = Gateway::getUidByClientId($client_id);
if (empty($uid)) {
MsgReply::sendError($client_id, '未认证,请先登录');
return;
}
// 关系子类型:优先 payload.type(与 ChatHandle 一致),兼容 header.type
$subType = (string) ($payload->type ?? ($header->type ?? ''));
$peer = (int) ($payload->receiver ?? 0);
$groupId = (int) ($payload->session_id ?? ($payload->group_id ?? 0));
$remark = trim((string) ($payload->remark ?? ($payload->content ?? '')));
switch ($subType) {
case MsgType::FRIEND_REQUEST->value:
self::friendRequest($client_id, $uid, $peer, $remark);
break;
case MsgType::FRIEND_AGREE->value:
self::friendAgree($client_id, $uid, $peer);
break;
case MsgType::FRIEND_REFUSE->value:
self::friendRefuse($client_id, $uid, $peer);
break;
case MsgType::GROUP_JOIN->value:
self::groupJoin($client_id, $uid, $groupId);
break;
case MsgType::GROUP_INVITE->value:
self::groupInvite($client_id, $uid, $groupId, $peer);
break;
default:
MsgReply::sendError($client_id, '不支持的关系消息类型: ' . $subType);
}
}
/**
* 好友申请:写入待处理好友记录(status=0),并实时通知对方
*/
private static function friendRequest($client_id, $uid, $peer, $remark)
{
if (! $peer) {
MsgReply::sendError($client_id, '对方 UID 无效');
return;
}
if ($peer == $uid) {
MsgReply::sendError($client_id, '不能添加自己为好友');
return;
}
// 已是好友(双向任一存在 status=1)
if (self::isFriend($uid, $peer)) {
MsgReply::sendError($client_id, '你们已是好友');
return;
}
// 去重:已存在待处理请求则不重复写入
$exist = WxchatFriend::where('uid', $uid)->where('fid', $peer)->where('status', 0)->find();
if (! $exist) {
$fr = new WxchatFriend();
$fr->uid = $uid;
$fr->fid = $peer;
$fr->remark = $remark;
$fr->status = 0; // 0-请求中
$fr->create_at = time();
$fr->save();
}
$myName = self::nickname($uid);
// 实时通知对方
MsgReply::sendToUid($peer, MsgFrame::RELATION->value, MsgType::FRIEND_REQUEST->value, '收到好友申请', [
'from' => $uid,
'from_name' => $myName,
'remark' => $remark,
]);
// 回执发送方
MsgReply::send($client_id, MsgFrame::RELATION->value, MsgType::FRIEND_REQUEST->value, '已发送好友申请', [
'to' => $peer,
]);
}
/**
* 同意申请:$uid 同意 $peer 发来的请求,建立双向好友
*/
private static function friendAgree($client_id, $uid, $peer)
{
if (! $peer) {
MsgReply::sendError($client_id, '对方 UID 无效');
return;
}
$req = WxchatFriend::where('uid', $peer)->where('fid', $uid)->where('status', 0)->find();
if (! $req) {
MsgReply::sendError($client_id, '无待处理的好友申请');
return;
}
$req->status = 1; // 1-已通过
$req->update_at = time();
$req->save();
// 写入反向好友记录(保证双向查询一致)
if (! self::isFriend($uid, $peer)) {
$reverse = new WxchatFriend();
$reverse->uid = $uid;
$reverse->fid = $peer;
$reverse->status = 1;
$reverse->create_at = time();
$reverse->save();
}
// 通知申请方
MsgReply::sendToUid($peer, MsgFrame::RELATION->value, MsgType::FRIEND_AGREE->value, '对方已通过你的好友申请', [
'from' => $uid,
'from_name' => self::nickname($uid),
]);
MsgReply::send($client_id, MsgFrame::RELATION->value, MsgType::FRIEND_AGREE->value, '已同意', [
'to' => $peer,
]);
}
/**
* 拒绝申请
*/
private static function friendRefuse($client_id, $uid, $peer)
{
if (! $peer) {
MsgReply::sendError($client_id, '对方 UID 无效');
return;
}
$req = WxchatFriend::where('uid', $peer)->where('fid', $uid)->where('status', 0)->find();
if (! $req) {
MsgReply::sendError($client_id, '无待处理的好友申请');
return;
}
$req->status = 2; // 2-已拒绝
$req->update_at = time();
$req->save();
MsgReply::sendToUid($peer, MsgFrame::RELATION->value, MsgType::FRIEND_REFUSE->value, '对方拒绝了你的好友申请', [
'from' => $uid,
]);
MsgReply::send($client_id, MsgFrame::RELATION->value, MsgType::FRIEND_REFUSE->value, '已拒绝', [
'to' => $peer,
]);
}
/**
* 主动加群:加入 group_members
*/
private static function groupJoin($client_id, $uid, $groupId)
{
if (! $groupId) {
MsgReply::sendError($client_id, '群组 ID 无效');
return;
}
$group = WxchatGroup::find($groupId);
if (! $group) {
MsgReply::sendError($client_id, '群组不存在');
return;
}
if (WxchatGroupMember::where('gid', $groupId)->where('uid', $uid)->find()) {
MsgReply::sendError($client_id, '你已在群内');
return;
}
self::addGroupMember($groupId, $uid, 0);
MsgReply::send($client_id, MsgFrame::RELATION->value, MsgType::GROUP_JOIN->value, '已加入群组', [
'group_id' => $groupId,
]);
}
/**
* 邀请加群:$uid 邀请 $peer 加入群组
*/
private static function groupInvite($client_id, $uid, $groupId, $peer)
{
if (! $groupId || ! $peer) {
MsgReply::sendError($client_id, '群组或受邀人无效');
return;
}
$group = WxchatGroup::find($groupId);
if (! $group) {
MsgReply::sendError($client_id, '群组不存在');
return;
}
if (! WxchatGroupMember::where('gid', $groupId)->where('uid', $peer)->find()) {
self::addGroupMember($groupId, $peer, 0);
}
MsgReply::sendToUid($peer, MsgFrame::RELATION->value, MsgType::GROUP_INVITE->value, '你被邀请加入群组', [
'group_id' => $groupId,
'from' => $uid,
'from_name' => self::nickname($uid),
]);
MsgReply::send($client_id, MsgFrame::RELATION->value, MsgType::GROUP_INVITE->value, '邀请已发送', [
'group_id' => $groupId,
'to' => $peer,
]);
}
/**
* 写入群成员并自增群人数
*/
private static function addGroupMember($groupId, $uid, $role)
{
$m = new WxchatGroupMember();
$m->gid = $groupId;
$m->uid = $uid;
$m->role = $role;
$m->join_time = date('Y-m-d H:i:s');
$m->save();
WxchatGroup::where('id', $groupId)->inc('member_count')->update();
}
private static function isFriend($uid, $fid): bool
{
return (bool) WxchatFriend::where('uid', $uid)
->where('fid', $fid)
->where('status', 1)
->find();
}
private static function nickname($uid): string
{
try {
$name = MemberUser::where('uid', $uid)->value('nickname');
return $name ?: '';
} catch (\Throwable $e) {
return '';
}
}
}
@@ -0,0 +1,81 @@
<?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\worker\enum\MsgFrame;
use addon\wxchat\worker\enum\MsgType;
use addon\wxchat\worker\MsgReply;
use GatewayWorker\Lib\Gateway;
/**
* 状态消息处理:正在输入 / 送达 / 已读 / 撤回
* typing 仅实时转发不落库;read 清零未读;revoked 标记撤回
*/
class StatusHandle
{
public static function handle($client_id, $header, $payload)
{
$uid = Gateway::getUidByClientId($client_id);
if (empty($uid)) {
MsgReply::sendError($client_id, '未认证,请先登录');
return;
}
switch ($header->type) {
case MsgType::TYPING->value:
// 实时转发给对方,不落库
$to = (int)($payload->receiver ?? 0);
if ($to && Gateway::isUidOnline($to)) {
MsgReply::sendToUid($to, MsgFrame::STATUS->value, MsgType::TYPING->value, 'typing', [
'from' => $uid,
]);
}
break;
case MsgType::READ->value:
// 标记某会话已读:清零自己对该会话的未读
$peer = (int)($payload->receiver ?? 0);
if ($peer) {
WxchatUnreadCount::reset($uid, 0, $peer);
}
break;
case MsgType::REVOKED->value:
// 撤回消息
$msgId = (int)($payload->msg_id ?? 0);
$msg = WxchatMessage::find($msgId);
if ($msg && $msg->sender_id == $uid) {
$msg->is_recalled = 1;
$msg->recalled_at = date('Y-m-d H:i:s');
$msg->save();
// 通知对方撤回
$to = (int)($payload->receiver ?? 0);
if ($to && Gateway::isUidOnline($to)) {
MsgReply::sendToUid($to, MsgFrame::STATUS->value, MsgType::REVOKED->value, 'revoked', [
'msg_id' => $msgId,
'from' => $uid,
]);
}
}
break;
case MsgType::DELIVERED->value:
// 送达回执,无需处理
break;
default:
MsgReply::sendError($client_id, '不支持的状态消息类型');
}
}
}
+106
View File
@@ -0,0 +1,106 @@
<?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\worker\enum\MsgFrame;
use addon\wxchat\worker\enum\MsgType;
use addon\wxchat\worker\Message;
use addon\wxchat\worker\MsgReply;
use GatewayWorker\Lib\Gateway;
/**
* SystemHandle 类
*
* @author ywxapp <admin@ywxapp.cn>
*/
class SystemHandle
{
public static function handle($client_id, $header, $payload)
{
switch ($header->type) {
case MsgType::HEARTBEAT->value:
self::Heartbeat($client_id, $header, $payload);
break;
case MsgType::AUTH->value:
self::Auth($client_id, $header, $payload);
break;
case MsgType::NOTICE->value:
self::Notification($client_id, $header, $payload);
break;
default:
ErrorHandle::sendErrorMsg($client_id, '不支持的系统消息类型');
}
}
/**
* 心跳响应
*/
private static function Heartbeat($client_id, $header, $payload)
{
$newMessage = new Message();
$newMessage->header->frame = MsgFrame::SYSTEM->value;
$newMessage->header->type = MsgType::HEARTBEAT->value;
$newMessage->header->to = Gateway::getUidByClientId($client_id);
Gateway::sendToClient($client_id, json_encode($newMessage));
}
/**
* 登录认证:校验 JWT,绑定 uid,更新在线状态
*/
private static function Auth($client_id, $header, $payload)
{
try {
$app = \addon\wxchat\worker\ChatEvent::initApp();
$token = \ywxapp\service\JwtService::instance()->parseAndValidate($payload->content ?? '');
$claims = $token->claims()->all();
$uid = (int)($claims['uid'] ?? 0);
if (! $uid) {
MsgReply::sendError($client_id, '认证失败:无效令牌');
Gateway::closeClient($client_id);
return;
}
$userLib = \ywxapp\library\Auth::instance();
$userLib->initUser($uid);
if (! $userLib->isLogin) {
MsgReply::sendError($client_id, '认证失败:用户不存在');
Gateway::closeClient($client_id);
return;
}
Gateway::bindUid($client_id, $uid);
// 更新在线状态
\ywxapp\model\MemberProfile::where('uid', $uid)
->update(['online_status' => 1, 'last_active_at' => time()]);
$newMsg = new Message();
$newMsg->header->frame = MsgFrame::SYSTEM->value;
$newMsg->header->type = MsgType::AUTH->value;
$newMsg->header->to = $uid;
$newMsg->payload->content = ['uid' => $uid, 'status' => 'success'];
Gateway::sendToClient($client_id, json_encode($newMsg));
// TODO: 拉取离线消息(从 DB history 由客户端主动拉取,此处可推送未读数)
} catch (\Throwable $th) {
echo "Auth failed: " . $th->getMessage() . "\n";
MsgReply::sendError($client_id, '认证失败:' . $th->getMessage());
Gateway::closeClient($client_id);
}
}
private static function Notification($client_id, $header, $payload)
{
// 服务端下发通知(如系统公告),当前仅回显
}
}