WorkermanHandler.php 8.7 KB
<?php
/**
+-----------------------------------------------------------------------------------------------------------------------
 * workerman 操作类
+-----------------------------------------------------------------------------------------------------------------------
 *
 * PHP version 7
 *
 * @category  App\Handler
 * @package   App\Handler
 * @author    Richer <yangzi1028@163.com>
 * @date      2020年12月29日09:24:01
 * @copyright 2020-2022 Richer (http://www.Richer.com/)
 * @license   http://www.Richer.com/ License
 * @link      http://www.Richer.com/
 */
namespace App\Handler;

use App\Models\Traits\WarningTrait;
use App\Models\Warning;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Workerman\Lib\Timer;
use Workerman\Worker;

/**
 * Class WorkermanHandler
 *
 * @category  App\Handler
 * @package   App\Handler
 * @author    Richer <yangzi1028@163.com>
 * @date      2020年12月29日09:24:01
 * @copyright 2020-2022 Richer (http://www.Richer.com/)
 * @license   http://www.Richer.com/ License
 * @link      http://www.Richer.com/
 */
class WorkermanHandler
{
    use WarningTrait;

    private $heartbeat_time = 5;//心跳间隔55秒

    // 消息类型 - 心跳包检测
    const MESSAGE_TYPE_IS_PONG  = 'pong';
    const MESSAGE_TYPE_IS_PING  = 'ping';
    // 消息类型 - 消息发送
    const MESSAGE_TYPE_IS_SAY   = 'say';
    // 消息类型 - 管理员
    const MESSAGE_TYPE_IS_ADMIN = 'admin';
    // 消息类型 - 火警警告
    const MESSAGE_TYPE_IS_WARNING = 'warning';

    //当客户端连上来时分配uid,并保存链接
//    public function handleConnection($connection)
//    {
//        global $text_worker;
//        //判断是否设置了UID
//        if(!isset($connection->uid)) {
//            //给用户分配一个UID
//            $connection->uid = Str::random(10);
//            //保存用户的uid
//            $text_worker->uidConnections["{$connection->uid}"] = $connection;
//            //向用户返回创建成功的信息
//            $connection->send(
//                json_encode(
//                    [
//                        'type' => self::MESSAGE_TYPE_IS_SAY,
//                        'content' => "用户:[{$connection->uid}] 创建成功"
//                    ]
//                )
//            );
//        }
//    }

    /**
     * @throws \Exception
     */
    public function handleStart()
    {
        global $text_worker;

        // 开启一个内部端口,方便内部系统推送数据,Text协议格式 文本+换行符
        $inner_text_worker = new Worker('Text://0.0.0.0:5678');
        $inner_text_worker->onMessage = function ($connection, $buffer) use (&$text_worker) {
            // $data数组格式,里面有uid,表示向那个uid的页面推送数据
            $data = json_decode($buffer, true);
            Log::channel('workerman')->info('服务端推送消息内容', $data);
            $uid = Arr::get($data, 'to_uid');
            // 通过workerman,向uid的页面推送数据
            $res = $this->sendMessageByUid($uid, $data);
            if ($res) {
                $warning_id = Arr::get($data, 'data.id');
                $model = Warning::find($warning_id);
                if ($model) {
                    $model->is_prompt_by_socket = 1;
                    $model->save();
                }
            }
        };
        $inner_text_worker->listen();

        //每隔30秒就向客户端发送一条心跳验证
        Timer::add($this->heartbeat_time, function () use ($text_worker) {
            foreach ($text_worker->connections as $conn) {
                $conn->send(
                    json_encode(
                        [
                            'type' => self::MESSAGE_TYPE_IS_PING,
                            'content' => '心跳包检测成功!'
                        ]
                    )
                );
            }
        });
    }

    /**
     * 当客户端发送消息过来时
     *
     * @param $connection
     * @param $data
     */
    public function handleMessage($connection, $data)
    {
        global $text_worker;

        $data_info = json_decode($data, true);
        //判断是否设置了UID
        if (!isset($connection->uid)) {
            //给用户分配一个UID
            $connection->uid = Arr::get($data_info, 'uid');
            //保存用户的uid
            $text_worker->uidConnections["{$connection->uid}"] = $connection;
        }

        $type = trim(Arr::get($data_info, 'type'));
        Log::channel('workerman')->info('消息内容:', $data_info);
        Log::channel('workerman')->info('消息类型:' . $type);
        switch ($type) {
            // 管理员握手成功后记录uid
            case self::MESSAGE_TYPE_IS_ADMIN:
            case self::MESSAGE_TYPE_IS_PONG:
                // 缓存管理员的uid
                if (!Cache::has('WORKERMAN_UID_FOR_ADMIN')) {
                    Cache::add('WORKERMAN_UID_FOR_ADMIN', $connection->uid);
                    $connection->send(
                        json_encode(
                            [
                                'type' => self::MESSAGE_TYPE_IS_SAY,
                                'content' => "管理员:[{$connection->uid}] 创建成功"
                            ]
                        )
                    );
                }
                // 查询记录,是否有未提醒的记录
                $warning = Warning::where('is_prompt_by_socket', 0)->first();
                Log::channel('workerman')->info('查询推送的火警数据:' . $type);

                if ($warning) {
                    $this->listenWarning($warning);
                }
                break;
            // 发送消息
            case self::MESSAGE_TYPE_IS_SAY:
                if (!isset($text_worker->uidInfo["{$connection->uid}"]) || empty($text_worker->uidInfo["{$connection->uid}"])) {
                    $connection->send(
                        json_encode(
                            [
                                'type' => 'error',
                                'content' => '你已经掉线了'
                            ]
                        )
                    );
                }
                //获取到当前用户的信息
                $user_info = $text_worker->uidInfo["{$connection->uid}"];
                //私聊
                $return_data = array(
                    "type"              => self::MESSAGE_TYPE_IS_SAY,
                    "from_uid"          => $connection->uid,
                    "from_user_name"    => $user_info['user_name'],
                    "from_user_header"  => $user_info['user_header'],
                    "to_uid"            => $data_info['to_uid'],
                    "content"           => nl2br(htmlspecialchars($data_info['content'])),
                    "send_time"         => date("Y-m-d H:i")
                );
                //判断用户是否存在,并向对方发送数据
                if (isset($text_worker->uidConnections["{$data_info['to_uid']}"])) {
                    $to_connection = $text_worker->uidConnections["{$data_info['to_uid']}"];
                    $to_connection->send(json_encode($return_data));
                }
            // 心跳包检测
            case self::MESSAGE_TYPE_IS_PONG:
                return;
                break;
            default:
                break;
        }
    }

    /**
     * 当客户端断开时,广播给所有客户端
     *
     * @param $connection
     */
    public function handleClose($connection)
    {
        global $worker;

        if (isset($connection->uid)) {
            // 连接断开时删除映射
            unset($worker->uidConnections[$connection->uid]);
        }
    }

    /**
     * 服务端针对uid发送消息
     *
     * @param $uid
     * @param $data
     * @return bool
     */
    function sendMessageByUid($uid, $data)
    {
        global $text_worker;

        $content    = Arr::get($data, 'content');
        $data       = Arr::get($data, 'data');

        Log::channel('workerman')->info('发送的uid:'. $uid);
        Log::channel('workerman')->info('发送的消息:'. $content);
        Log::channel('workerman')->info('uid List:', $text_worker->uidConnections);

        if (isset($text_worker->uidConnections["{$uid}"])) {
            Log::channel('workerman')->info('go here');
            $connection = $text_worker->uidConnections["{$uid}"];
            $connection->send(
                json_encode(
                    [
                        'type'      => self::MESSAGE_TYPE_IS_WARNING,
                        'content'   => $content,
                        'data'      => $data
                    ]
                )
            );
            return true;
        }

        return false;
    }
}