PHP基於TP5使用Websocket框架之GatewayWorker開發電商平台買家與賣家實時通訊


前段時間公司提了一個新的需求,在商品的詳情頁要實現站內買家和商品賣家實時通訊的功能以方便溝通促成交易,要開發此功能當時首先考慮到的就是swoole和workerman了,從網上大概了解了一下關於這兩款工具的闡述,功能都是相當強大的,考慮到項目的進度問題,還是選擇上手容易比較快的GatewayWorker和框架TP5。

先看一下我們前端設計高大上的模板,分別是用戶和賣家后台。 功能還是比較全的,幾乎模仿的是QQ。

業務上的大概需求是,用戶在進入某個商品詳情頁下,給用戶提供一個和賣家溝通的接口,根據商品的ID找到對應的賣家,類似於淘寶,還有發送圖片,發送對應的商品鏈接;商戶后台也差不多。

我們的平台上有虛擬商品和實體商品兩大分類,當時也考慮到了消息的讀取狀態。我的表最初設計如下,沒有加任何的索引,考慮的或許也不夠周全,有見地的前輩還望指點一二!

DROP TABLE IF EXISTS `hp_chat_log`; CREATE TABLE `hp_chat_log` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '聊天記錄表主鍵id', `user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '用戶id', `merchant_id` varchar(15) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '商家id', `send_message` text COLLATE utf8_unicode_ci NOT NULL, `send_message_type` tinyint(1) NOT NULL DEFAULT '1' COMMENT '發送消息類型(1:普通文本;2:商品鏈接,3:用戶發送圖片)', `sender` tinyint(1) NOT NULL DEFAULT '1' COMMENT '發送方。1:用戶。2:商家', `send_time` int(11) NOT NULL DEFAULT '0' COMMENT '發送時間', `read_status` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '是否已讀。0:未讀取。1:已讀取', `acc_isonline` tinyint(1) NOT NULL DEFAULT '0' COMMENT '接收方是否在線 (0:不在線;1:在線)', PRIMARY KEY (`id`) ) ENGINE=MyISAM AUTO_INCREMENT=157 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

 模板有了,表設計好了,接下來就是搭建服務了,當前項目開發的框架用的是TP5,選擇的Websocket框架是GatewayWorker框架,關於GatewayWorker與TP5的整合方法可以看我的這篇文章,講到了在Linux和

Windows下的整合安裝。

http://www.cnblogs.com/wt645631686/p/7219519.html

整合好了之后需要根據當前服務器的一些端口配置在修改一些默認的配置,因為需要客戶端通過指定的端口建立連接。

TP5整合好了之后Gateway和workerman的主體目錄結構都在TP5的框架目錄vendor下的workerman目錄下。需要修改里面gateway目錄下的一些文件的端口及IP地址配置。

 配置完成之后,進入項目目錄,按照workerman官方手冊提供的使用方法,用命令php start.php start啟動socket服務,如以下截圖,分別是1238和8282端口。當然可以在后台運行,詳細的使用方法請參考手冊。

啟動好了之后那么就需要在客戶端開始下手了,我們項目里是在前端頁面里用建立的鏈接。看前端代碼

當前的所有代碼並不是最終的,目前只是階段性開發,后期在項目中逐步完善。

var ws; // 連接服務端
 function connect() { // 創建websocket
       ws = new WebSocket("ws://"+document.domain+":8282");  //這里如果使用127.0.0.1或者localhost會出現連接失敗。當時為了方便以后的維護,這里在php的全局文件里定義了一個常量來定義ip,后來本地開發完提交到linux服務器環境之后發現鏈接失敗!按照此行代碼會有效連接~ console.log(ws); ws.onopen = onopen; ws.onmessage = onmessage; ws.onclose = function(e) { console.log(e); console.log("連接關閉,定時重連"); connect(); }; ws.onerror = function(e) { console.log(e); console.log("出現錯誤"); }; } // 握手
 function onopen() { var joint = '{"type":"handshake","role":"user"}'; ws.send(joint); } // 服務端發來消息時
 function onmessage(e) { var data = JSON.parse(e.data); console.log(data); switch(data['type']){ // 服務端ping客戶端
            case 'ping': ws.send('{"type":"pong"}'); break; // 登錄 更新用戶列表
            case 'handshake': bindUid(data.client_id); $('#client_id').val(data.client_id);
                break; // 提醒
            case 'reception': //{"type":"say","from_client_id":xxx,"to_client_id":"all/client_id","content":"xxx","time":"xxx"}
                warn(data['content'], data['time'], data['timestamp']); break;
 } } //綁定uid
 function bindUid (client_id) { var bindUrl = "{:url('push/push/BindUserClientId')}"; $.post(bindUrl, {client_id: client_id}, function(data){ console.log(data); }, 'json'); } //發送連接
 function sendLink () { sendTrigger('link'); } // 發送信息
 function sendMessage (){ sendTrigger('message'); } function sendTrigger(sendType) { var toMid     = $('#toMid').val(); var pid       = $('#pid').val(); var message   = $("footer .send_content").val(); var client_id = $('#client_id').val(); var sendUrl   = "{:url('push/push/SendMessageToMerchant')}"; $.ajax({ url:sendUrl, type:'POST', data:{message:message,toMid:toMid,pid:pid,client_id:client_id,sendType:sendType}, async:false, dataType:'JSON', success:function(data){ data = JSON.parse(data); if (data.status < 0) { alert('發送失敗,請稍后再試!'); } else { $('#send_timestamp').val(data.timeStamp); $('#send_timestr').val(data.timeStr); if (sendType == 'link') { $('#main').append(data.html); } } } }) } // 提醒
 function warn(content, time, prevTmestamp){ var V_image = $('#V_image').val(); var str = '<div class="chat-receiver">' + timestampWarn(prevTmestamp, time) + '<div class="chat-avatar"><img src="'+ V_image+ '" alt=""></div>
        <div class="chat-content"><div class="chat-triangle"></div><span>
' + content + '</span></div>'; domChange(str); $("#main").scrollTop($("#main")[0].scrollHeight); } //發送 function sender(content, time, prevTmestamp) { var user_image = $('#user_image').val(); var str = '<div class="chat-sender">' + timestampWarn(prevTmestamp, time) + '<div class="chat-avatar"><img src="' +user_image + '" alt="">
</div><div class="chat-content"><div class="chat-triangle"></div>
' + '<span>' + content + '</span></div></div>'; domChange(str); } //消息時間控制 function timestampWarn (nowTimestamp , nowTime) { var prevTimestamp = $('#prev_timestamp').val(); $('#prev_timestamp').val(nowTimestamp); var timeOffset = 6; var accTime = ''; if ((nowTimestamp - prevTimestamp) > timeOffset) { accTime = '<div style="clear:both;"></div><p class="chat-history-date">' + nowTime + '</p>'; } return accTime; }
<body onload="connect();">
</body>

 在開發過程中,修改的GatewayWorker文件並不多,除了幾個主要的端口及IP需要修改之外,僅僅修改一個重要的文件就夠了,那就是Push模塊(項目的通訊模塊)同級的Events.php文件。看一下項目需求里

修改后的代碼,一個方法;

/** * 當客戶端發來消息時觸發 * @param int $client_id 連接id * @param mixed $message 具體消息 */
   public static function onMessage($client_id, $message) { $message_data = json_decode($message,true); if (!$message_data) { return ; } switch($message_data['type']) { case 'pong': return; case 'handshake': $new_message = [ 'type'      => $message_data['type'], 'client_id' => $client_id, 'time'      => date('H:i:s') ]; Gateway::sendToClient($client_id, json_encode($new_message)); return; case 'send': if (!isset($message_data['toClientUid'])) { throw new \Exception("toClient not set. client_ip:{$_SERVER['REMOTE_ADDR']}"); } $toUid = $message_data['toClientUid']; $message = $message_data['content']; $new_message = [ 'type'      => 'reception', 'content'   => $message, 'time'      => date('H:i:s'), 'timestamp' => time(), 'c_type'    => $message_data['c_type'], 'primary'   => $message_data['Db_id'] ]; //發送者角色
                $source_info = explode('_', $message_data['source']); if ($source_info[0] == 'U') {
            //為了安全,特意做了加密 $new_message[
'source'] = encrypt_hopeband($source_info[1], 'E', 'XXXXXXX');     } return Gateway::sendToUid($toUid, json_encode($new_message)); } }

 然后看一下Push模塊下的控制器文件,在配合前端在綁定客戶端ID及發送信息做的一些處理。

class Push extends Base{ protected static $user_headimage = ''; protected static $uid = null; public function __construct () { parent::__construct(); $this->checkUserLogin(); //用戶頭像昵稱等信息
        self::$uid            = session('userinfo.uid'); $user_info = Hmodel\User::getUserChatinfoById(self::$uid); self::$user_headimage = json_decode($user_info['headimgurl'],true)[0]; } public function chatAction () { $product_id = intval(input('param.pid', 0, 'int')); $toMid = Hmodel\Product::getMidByProductid($product_id); if ($toMid === false) notFund(); $productHtml = $this->returnProductData2Html($product_id, 'default'); $int_toMid = substr($toMid, 2); $V_headInfo = Pmodel\Push::getVmerchantHeadImageByVid($int_toMid); $V_headimage = is_not_empty_array($V_headInfo) ? json_decode($V_headInfo['headimgurl'])[0] :'/uploads/logo.png'; if (substr($toMid, 0, 1) == 'V') { $chatLogData = Pmodel\Push::getChatlogByUseridAndVid(self::$uid, $int_toMid); if (is_not_empty_array($chatLogData)) { $chatLog = self::chatlogData2Html($chatLogData, $V_headimage); } } elseif (substr($toMid, 0, 1) == 'E') { } $view = new View; $view->assign('pHtml', $productHtml); $view->assign('toMid', $toMid); $view->assign('pid', $product_id); $view->assign('chatlogHtml', $chatLog); $view->assign('role', 'user'); $view->assign('user_image', self::$user_headimage); $view->assign('V_image', $V_headimage); return $view->fetch(); } private static function chatlogData2Html ($data = [], $V_headimage = '') { $todayTimestamp = strtotime(date('Y-m-d')); $html = ''; foreach ($data as $k => $v) { $date = $v['send_time'] < $todayTimestamp ? date('Y/m/d H:i:s', $v['send_time']) : date('H:i:s', $v['send_time']); $time_nodes = ''; if (($data[$k]['send_time'] - $data[$k-1]['send_time']) > 180) { $time_nodes = '<div style="clear:both;"></div><p class="chat-history-date">' .$date.'</p>'; } //sender->發送方 1:用戶。2:商家
            if ($v['sender'] == 1) { //send_message_type->發送消息類型 (1:普通文本;2:商品鏈接)
                if ($v['send_message_type'] == 1) { $html.= '<div class="chat-sender">'; $html.= $time_nodes; $html.= '<div class="chat-avatar"><img src="' . self::$user_headimage . '" alt=""></div>'; $html.= '<div class="chat-content"><div class="chat-triangle"></div><span>' . $v['send_message'].'</span></div>'; $html.= '</div>'; }elseif ($v['send_message_type'] == 2) { $html.= $time_nodes; $product_info = json_decode($v['send_message'],true); $html.= self::productData2SendHtml($product_info); }elseif ($v['send_message_type'] == 3) { $images_arr = json_decode($v['send_message'],true); $html.= '<div class="chat-sender">'; $html.= $time_nodes; $html.= '<div class="chat-avatar"><img src="' . self::$user_headimage . '" alt=""></div>'; $html.= '<div class="chat-content"><div class="chat-triangle"></div>'; foreach ($images_arr as $v ) { $html.= '<img src="' .WEB_SITE. '/' . $v . '" style="max-width:85%">'; } $html.= '</div>'; $html.= '</div>'; } }else { if ($v['send_message_type'] == 1) { $html.= '<div class="chat-receiver">'; $html.= '<div class="chat-avatar"><img src="' . $V_headimage . '" alt=""></div>'; $html.= '<div class="chat-content"><div class="chat-triangle"></div><span>' . $v['send_message'].'</span></div>'; $html.= '</div>'; }elseif ($v['send_message_type'] == 2) { } } } return $html; }
public function BindUserClientIdAction () { if (!Request::instance()->isPost()) { notFund(); } $bindUserid = 'U_' . session('userinfo.uid'); $client_id = input("param.client_id", 0, "string"); // 設置GatewayWorker服務的Register服務ip和端口
        Gateway::$registerAddress = SOCKET_SERVER_PORT; // client_id與uid綁定 // Gateway::closeClient($client_id);
        return Gateway::bindUid($client_id, $bindUserid); } //用戶發送消息給商家
    public function SendMessageToMerchantAction () { if (!Request::instance()->isPost()) { notFund(); } $message = $_POST['message']; $toMid = input('post.toMid', '' , 'string'); $product_id= input('post.pid', 0, 'int'); $client_id = input('post.client_id', '', 'string'); $sendType = input('post.sendType', '', 'string'); if (!in_array($sendType,['link', 'message'])) {                             //客戶端錯誤
            return json_encode(['status' => -1]); } if (strlen($client_id) != 20 ) {                                            //客戶端錯誤
            return json_encode(['status' => -1]); } if (!is_not_empty_string($toMid) || !is_positive_integer($product_id)) {    //系統錯誤
            return json_encode(['status' => -2]); } $db_toMid = Hmodel\Product::getMidByProductid($product_id);            //數據錯誤
        if ($db_toMid != $toMid) { return json_encode(['status' => -3]); } require_once dirname(dirname(__FILE__)) . '/Events.php'; $uid = session('userinfo.uid'); $accIsOnline = Gateway::isUidOnline($toMid) == 1 ? 1 : 0;                //判讀商家是否在線
        $message_type   = 1; if ($sendType == 'link') { $message_type = 2; $productData = $this->referProductData($product_id); unset($productData['product_price']); unset($productData['score']); unset($productData['product_stock']); unset($productData['product_param']); unset($productData['product_desc']); unset($productData['product_main']); unset($productData['category_id']); unset($productData['merchant_id']); $message = json_encode($productData); } //Log入庫
        $insertId       = Pmodel\Push::addChatLog($uid, $toMid, $message, $message_type, 1, $accIsOnline); if($message_type == 1){ if(!is_numeric($message)){ $message = '"'.$message.'"'; } if ($message == '') { $message = ''; } } if ($insertId === false) {                                                  //入庫失敗(服務器故障)
            return json_encode(['status' => -3]); } $Worker = new \Events; $message_json = '{"type":"send","source":"U_' . $uid . '","toClientUid":"' . $toMid . '","content":' . $message .',
               "c_type":
' . $message_type .', "Db_id":' . $insertId . '}'; $Worker::onMessage($client_id, $message_json); //成功返回相關數據 return json_encode([ 'status' => 1, 'timeStamp' => time(), 'timeStr' => date('H:i:s'), 'html' => $message_type == 1 ? '' : self::productData2SendHtml($productData) ]); } //商家發送信息給用戶 public function sendMessageToUserAction () { if (!Request::instance()->isPost()) { notFund(); } $post_message = is_not_empty_string($_POST['message']) ? $_POST['message'] : ''; $toUserCode = input('post.toUserCode', '' , 'string'); $toU_uid = encrypt_hopeband($toUserCode, 'D', 'xxxxx'); $V_client_id = input('post.client_id', '', 'string'); $V_uid_code = input('post.myCode', '', 'string'); $V_uid = encrypt_hopeband($V_uid_code, 'D', 'xxxxx'); $make_message = []; $message = ''; self::trimImageAndTextinfo2str($post_message, $make_message); if (is_not_empty_array($make_message)) { foreach ( $make_message as &$v ) { $message .= self::checkIflegalAndReturn($v); } } if (strlen($V_client_id) != 20 || Gateway::isOnline($V_client_id) != 1) { //客戶端錯誤 return json_encode(['status' => -2]); } $V_merchantInfo = Pmodel\Push::getVmerchantInfoByVid($V_uid); if (!is_not_empty_array($V_merchantInfo)) { //商家信息不存在 return json_encode(['status' => -1]); } require_once dirname(dirname(__FILE__)) . '/Events.php'; $accIsOnline = Gateway::isUidOnline('U_' . $toU_uid) == 1 ? 1 : 0; //判讀用戶是否在線 $message_type = 1; //Log入庫 $insertId = Pmodel\Push::addChatLog($toU_uid, 'V_' . $V_uid, $message, 1, 2, $accIsOnline); if ($insertId === false) { //入庫失敗(服務器故障) return json_encode(['status' => -3]); } $Worker = new \Events; $img_encrypt_code = encrypt_hopeband('Hp_(legal)', 'E', 'Hp_HopeBand_Chat_img'); $message = str_replace($img_encrypt_code .' src="', $img_encrypt_code . " src='", $message); $message = str_replace('">', "'>", $message); $message_json = '{"type":"send","toClientUid":"U_' . $toU_uid . '","content":"' . $message .'","Db_id": "' . $insertId . '"}'; $Worker::onMessage($client_id, $message_json); //成功返回相關數據 return json_encode([ 'status' => 1, 'timeStamp' => time(), 'timeStr' => date('H:i:s'), ]); }

 

額外還有一些關於消息處理方面的;

 //驗證是否是否是圖片,如果是並且返回圖片地址,否則返回字符串
    private static function checkIflegalAndReturn ( $message = '') { header('content-type:text/html; charset=utf-8'); $img_encrypt_code = encrypt_hopeband('Hp_(legal)', 'E', 'Hp_HopeBand_Chat_img'); if (substr($message, 0, 21) != '<img src="data:image/' || substr($message, strpos($message, ';', 0) ,8) != ';base64,' || substr($message, -2, 2) != '">') { return $message; } $preg = '/<img.*?src="(.*?)".*?>/is'; preg_match( $preg, $message,$arr); $img_src = self::base64_upload($arr[1]); return '<img '.$img_encrypt_code.' src="' . $img_src . '">'; } //把接受到的消息文本和圖片有序提出並解析
    private static function trimImageAndTextinfo2str ($message = '', &$message_arr = []) { if (!is_not_empty_string($message)) return ''; $img_start_code = '<img src="data:image/'; $img_end_code = '">'; $tmp_message = strlen($message); $initial = substr($message,0,strlen($img_start_code)); if ($initial == $img_start_code) { $start = strpos($message, $img_start_code, 0); $end = strpos($message, $img_end_code , 0); $message_arr[] = substr($message, 0, $end + 2); $message = substr($message, $end + 2); }else{ $start = strpos($message, $img_start_code); if ($start !== false) { $message_arr[] = substr($message, 0, $start); $message = substr($message, $start); }else{ //防止xss攻擊
                $message_arr[]= string_remove_xss(htmlspecialchars_decode($message)); } } if (($tmp_message) != strlen($message) && is_not_empty_string($message)) { self::trimImageAndTextinfo2str($message, $message_arr); } return $message_arr; } private static function base64_upload($base64 = '') { $base64_image = str_replace(' ', '+', $base64); if (preg_match('/^(data:\s*image\/(\w+);base64,)/', $base64_image, $result)){ if($result[2] == 'jpeg'){ $image_name = getCode(16,4).'.jpg'; }else{ $image_name = getCode(16,4).'.'.$result[2]; } $image_file = "./upload/chat".'/'.date('Y').'/'.date('m').'/'.date('d').'/'.$image_name;    //服務器文件存儲路徑 //判斷文件路徑是否存在
        $path = "./upload/chat".'/'.date('Y').'/'.date('m').'/'.date('d').'/'; is_dir($path) or mkdir($path,0777,true); if (file_put_contents($image_file, base64_decode(str_replace($result[1], '', $base64_image)))){ return $image_file; }else{ return false; } }else{ return false; } }

微信的圖片上傳

JS部分

/* 退款選擇圖片 -------- start */ $("#chooseImage").click(function() { wx.chooseImage( { count: 9,                                 // 默認9
                    sizeType: ['original','compressed'],     // 可以指定是原圖還是壓縮圖,默認二者都有
                    sourceType: ['album', 'camera'],         // 可以指定來源是相冊還是相機,默認二者都有
 success: function (res) { images.localId = res.localIds; // 返回選定照片的本地ID列表,localId可以作為img標簽的src屬性顯示圖片 
                            //for(var j = 0;j<images.localId.length;j++) // { // var str = '<div serverId='+images.serverId[j]+' class="chat-sender"><div class="chat-avatar">
                     //<img src="/home/push/img/1.png" alt="">
                   //</div><div class="chat-content"><div class="chat-triangle"></div>
                     //<img src='+images.localId[j]+' data-originalDrawing-src='+images.localId[j]+' data-preview-src="" data-preview-group="1"/></div></div>'
// $("#main").append(str); // } var t = 0; var i = 0, length = images.localId.length; images.serverId = []; /* upload 方法 -------- start */ function upload() { wx.uploadImage( { localId: images.localId[i], success: function (res) { i++; images.serverId.push(res.serverId); if (i < length) { upload(); } var str = '<div serverId='+res.serverId+' class="chat-sender"><div class="chat-avatar">
                         <img src="/home/push/img/1.png" alt="">
                            </div><div class="chat-content"><div class="chat-triangle"></div>
                         <img src=
'+images.localId[i-1]+' data-originalDrawing-src='+images.localId[i-1]+' data-preview-src="" data-preview-group="1"/>
                           </div></div>
' $("#main").append(str); if(i >= length ) uploadImageToDb(images.serverId); }, fail: function (res){ } }); } /* upload 方法 -------- end */ upload(); } }) }); function uploadImageToDb(images){ var str = ""; var upUrl = "http://xxxxxx.com/push/push/uploadImgage"; var toMid = $('#toMid').val(); var client_id = $('#client_id').val(); $.post(upUrl,{images:images,toMid:toMid,client_id:client_id},function(data){ if(data == 1){ for(var n = 0 ; n < $(".chat-sender").length ; n++){ str = $(".chat-sender").eq(n).attr("serverId")+","; for(var z=0;z<data.length;z++){ if(data[z] == str){ $(".chat-sender").eq(n).find(".chat-content").append('<div class="chat-sender">上傳失敗</div>'); } } } } }) } /* 退款選擇圖片 -------- end */

后台部分

//微信上傳圖片
    public function uploadImgageAction () { if (!Request::instance()->isPost()) { notFund(); } $images = $_POST['images']; if (empty($images)) die; $toMid = input('post.toMid', '' , 'string'); $client_id = input('post.client_id', '', 'string'); if (strlen($client_id) != 20 ) {                                            //客戶端錯誤
            return json_encode(['status' => -1]); } if (!is_not_empty_string($toMid)) {                                         //系統錯誤
            return json_encode(['status' => -2]); } require_once dirname(dirname(__FILE__)) . '/Events.php'; $accIsOnline = Gateway::isUidOnline($toMid) == 1 ? 1 : 0;                //判讀商家是否在線
        $message_type   = 3; //微信上傳圖片處理Start
        $res = json_decode(file_get_contents("access_token.json")); foreach ($res as $key => $value) { if($key == 'access_token'){ $access_token = $value; } } $data = []; foreach ($images as $k => $v) { $str = date('YmdHis').rand(1000,9999).'.jpg'; $targetName = './upload/chat/'.$str; if (!file_exists("./upload/chat/")) { mkdir("./upload/chat/", 0777, true); } $ch = curl_init("http://file.api.weixin.qq.com/cgi-bin/media/get?access_token=".$access_token."&media_id=".$v); $fp = fopen($targetName, 'wb'); curl_setopt($ch, CURLOPT_FILE, $fp); curl_setopt($ch, CURLOPT_HEADER, 0); $msg["status"] = curl_exec($ch); $msg["filename"] = $str; curl_close($ch); fclose($fp); $data[] = $targetName; } //微信上傳圖片處理End
        if (!is_not_empty_array($data)) {                                       //微信服務器端圖片上傳錯誤
            return json_encode(['status' => -2]); } $message = json_encode($data); //Log入庫
        $insertId       = Pmodel\Push::addChatLog(self::$uid, $toMid, $message, $message_type, 1, $accIsOnline); if ($insertId === false) {                                                  //入庫失敗(服務器故障)
            return json_encode(['status' => -3]); } $Worker = new \Events; $message_json = '{"type":"send","source":"U_' . self::$uid . '","toClientUid":"' . $toMid . '","content":' . $message .',
               "c_type":
' . $message_type .', "Db_id":' . $insertId . '}'; $Worker::onMessage($client_id, $message_json); //成功返回相關數據 return json_encode([ 'status' => 1, 'timeStamp' => time(), 'timeStr' => date('H:i:s') ]); }

其他一些不是很重要的代碼就不拿出來了。

當前項目只是一個簡單的需求,並沒有把GatewayWorker很多強大的功能體現出來,大家以后在項目開發中遇到更為復雜的需求,參考官方手冊提供的一些Demo就可以慢慢實現並開發出更為健壯的項目!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM