// +---------------------------------------------------------------------- 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 ''; } } }