基于TP5使用Websocket框架之GatewayWorker开发电商平台买家与卖家实时通讯
https://www.cnblogs.com/wt645631686/p/7366924.html
前段时间公司提了一个新的需求,在商品的详情页要实现站内买家和商品卖家实时通讯的功能以方便沟通促成交易,要开发此功能当时首先考虑到的就是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;
}
}
微信的图片上传
/* 退款选择图片 -------- 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就可以慢慢实现并开发出更为健壮的项目!
基于TP5使用Websocket框架之GatewayWorker开发电商平台买家与卖家实时通讯的更多相关文章
- PHP基于TP5使用Websocket框架之GatewayWorker开发电商平台买家与卖家实时通讯
前段时间公司提了一个新的需求,在商品的详情页要实现站内买家和商品卖家实时通讯的功能以方便沟通促成交易,要开发此功能当时首先考虑到的就是swoole和workerman了,从网上大概了解了一下关于这两款 ...
- 使用Websocket框架之GatewayWorker开发电商平台买家与卖家实时通讯
前段时间公司提了一个新的需求,在商品的详情页要实现站内买家和商品卖家实时通讯的功能以方便沟通促成交易,要开发此功能当时首先考虑到的就是swoole和workerman了,从网上大概了解了一下关于这两款 ...
- 开发电商平台用PHP语言和JAVA语言有什么区别?哪种语言更好?
现在很多行业都通过电子商务拓展业务,所以商城系统开发成为很多企业的刚性需求.一般有一点技术基础的客户应该知道目前商城系统开发主流语言有两个,PHP和Java.那么很多客户朋友会纠结是选择哪个语言开发好 ...
- 【基于微信小程序的社区电商平台】需求分析心得——小豆芽
一.项目内容 基于微信小程序,做一个社区电商平台,抓住社区电商的特点,做出特色,与微信集成,实现商品的个性化发布,以及个性化营销. 个性化发布:用户可以在应用上直接发布自己的商品,通过搜索心愿单可以查 ...
- Android通用框架设计与完整电商APP开发系列文章
作者|傅猿猿 责编|Javen205 有福利 有福利 有福利 鸣谢 感谢@傅猿猿 邀请写此系列文章 Android通用框架设计与完整电商APP开发 课程介绍 [导学视频] [课程详细介绍] 以下是部分 ...
- Thinkphp5.0 仿百度糯米 开发多商家 电商平台(完整版)
目录第1章 课程简介第2章 需求分析第3章 快速掌握thinkphp5第4章 任性的TP5模块第5章 生活服务分类管理模块第6章 百度地图应用封装第7章 打造属于TP5自己的发送邮件服务第8章 商户模 ...
- 以太坊开发DApp实战教程——用区块链、星际文件系统(IPFS)、Node.js和MongoDB来构建电商平台(一)
第一节 简介 欢迎和我们一起来用以太坊开发构建一个去中心化电商DApp!我们将用区块链.星际文件系统(IPFS).Node.js和MongoDB来构建电商平台类似淘宝的在线电商应用,卖家可以自由地出售 ...
- 【基于微信小程序的社区电商平台】Alpha迭代心得
项目团队:小豆芽 开发周期:11.5-12.2(Alpha版本) 设想和目标 1. 我们的软件要解决什么问题?是否定义得很清楚?是否对典型用户和典型场景有清晰的描述? 解决问题:当前电商平台卖家买家角 ...
- Java生鲜电商平台-SpringCloud微服务开发中的数据架构设计实战精讲
Java生鲜电商平台-SpringCloud微服务开发中的数据架构设计实战精讲 Java生鲜电商平台: 微服务是当前非常流行的技术框架,通过服务的小型化.原子化以及分布式架构的弹性伸缩和高可用性, ...
随机推荐
- 第18课-数据库开发及ado.net 连接数据库.增.删.改向表中插入数据并且返回自动编号.SQLDataReade读取数据
第18课-数据库开发及ado.net 连接数据库.增.删.改向表中插入数据并且返回自动编号.SQLDataReade读取数据 ADO.NET 为什么要学习? 我们要搭建一个平台(Web/Winform ...
- SQL Server ——用 join on 连接多个表
select * from table1 inner join table2 on table1.id=table2.id 其实 INNER JOIN ……ON的语法格式可以概括为: FROM ((( ...
- 多线程系列(1)多线程基础和Thread
因为现项目中有用到多线程和并发的知识,所以打算近期补习一下多线程相关的内容.第一篇文章从最基础的开始,就是如何开启一个线程,如何启动线程和阻塞线程等,这篇文章分以下几点进行总结. 多线程初印象 多线程 ...
- php pdo对象使用详解: 连接数据库与exec方法
要使用pdo,首先需要开启pdo扩展,我这里已经开启了mysql的pdo扩展 ghostwu@dev:~$ php -m | grep pdo pdo_mysql ghostwu@dev:~$ 1,连 ...
- centos下Nginx安装和配置多个域名的虚拟主机
nginx安装步骤,源码编译安装(源码编译,可以自定制更多功能) openssl #user nobody; worker_processes ; #error_log logs/error.log; ...
- canvas :原生javascript编写动态时钟
canvas :原生javascript编写动态时钟 此时针是以画布的中心为圆心: g.translate(width/2,width/2); 此函数是将画布的原点移到(width/2,wid ...
- Nginx控制客户端请求的速率
使用ngx_http_limit_req_module模块的两个参数 ngx_http_limit_req_module模块用于限制每个IP访问每个定义key的请求速率 1.limit_req_zon ...
- MySql 简单统计查询消耗时间脚本
MySql 简单统计查询消耗时间脚本 by:授客 QQ:1033553122 drop procedure if exists selectTime; delimiter; create proced ...
- VC中添加头文件以及库
原文:http://blog.csdn.net/lwb102063/article/details/52068389 附加头文件包含 VC6.0中: VC6.0默认include包含路径:Tool ...
- SQL Server 2000中的并行处理和执行计划中的位图运算符
SQL Server 2000中的并行处理和执行计划中的位图运算符 摘抄自:SQLServer 2000并行处理和位图简介 刘志斌 并行查询介绍Degree of Parallelism(并行度) 一 ...