PHP基于TP5使用Websocket框架之GatewayWorker开发电商平台买家与卖家实时通讯
前段时间公司提了一个新的需求,在商品的详情页要实现站内买家和商品卖家实时通讯的功能以方便沟通促成交易,要开发此功能当时首先考虑到的就是swoole和workerman了,从网上大概了解了一下关于这两款工具的阐述,功能都是相当强大的,考虑到项目的进度问题,还是选择上手容易比较快的GatewayWorker和框架TP5。
先看一下我们前端设计高大上的模板,分别是用户和卖家后台。 功能还是比较全的,几乎模仿的是QQ。
业务上的大概需求是,用户在进入某个商品详情页下,给用户提供一个和卖家沟通的接口,根据商品的ID找到对应的卖家,类似于淘宝,还有发送图片,发送对应的商品链接;商户后台也差不多。
我们的平台上有虚拟商品和实体商品两大分类,当时也考虑到了消息的读取状态。我的表最初设计如下,没有加任何的索引,考虑的或许也不够周全,有见地的前辈还望指点一二!
DROP TABLE IF EXISTS `hp_chat_log`;
CREATE TABLE `hp_chat_log` (
`id` int() unsigned NOT NULL AUTO_INCREMENT COMMENT '聊天记录表主键id',
`user_id` int() unsigned NOT NULL DEFAULT '' COMMENT '用户id',
`merchant_id` varchar() COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '商家id',
`send_message` text COLLATE utf8_unicode_ci NOT NULL,
`send_message_type` tinyint() NOT NULL DEFAULT '' COMMENT '发送消息类型(1:普通文本;2:商品链接,3:用户发送图片)',
`sender` tinyint() NOT NULL DEFAULT '' COMMENT '发送方。1:用户。2:商家',
`send_time` int() NOT NULL DEFAULT '' COMMENT '发送时间',
`read_status` tinyint() unsigned NOT NULL DEFAULT '' COMMENT '是否已读。0:未读取。1:已读取',
`acc_isonline` tinyint() NOT NULL DEFAULT '' COMMENT '接收方是否在线 (0:不在线;1:在线)',
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT= 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 < ) {
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")[].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 = ;
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[] == 'U') {
//为了安全,特意做了加密
$new_message['source'] = encrypt_hopeband($source_info[], '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)[]; } public function chatAction () {
$product_id = intval(input('param.pid', , 'int'));
$toMid = Hmodel\Product::getMidByProductid($product_id);
if ($toMid === false) notFund(); $productHtml = $this->returnProductData2Html($product_id, 'default'); $int_toMid = substr($toMid, );
$V_headInfo = Pmodel\Push::getVmerchantHeadImageByVid($int_toMid);
$V_headimage = is_not_empty_array($V_headInfo) ? json_decode($V_headInfo['headimgurl'])[] :'/uploads/logo.png'; if (substr($toMid, , ) == 'V') {
$chatLogData = Pmodel\Push::getChatlogByUseridAndVid(self::$uid, $int_toMid);
if (is_not_empty_array($chatLogData)) {
$chatLog = self::chatlogData2Html($chatLogData, $V_headimage);
}
} elseif (substr($toMid, , ) == '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-]['send_time']) > ) {
$time_nodes = '<div style="clear:both;"></div><p class="chat-history-date">' .$date.'</p>';
}
//sender->发送方 1:用户。2:商家
if ($v['sender'] == ) { //send_message_type->发送消息类型 (1:普通文本;2:商品链接)
if ($v['send_message_type'] == ) {
$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'] == ) {
$html.= $time_nodes;
$product_info = json_decode($v['send_message'],true);
$html.= self::productData2SendHtml($product_info);
}elseif ($v['send_message_type'] == ) {
$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'] == ) {
$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'] == ) { }
}
}
return $html;
}
public function BindUserClientIdAction () {
if (!Request::instance()->isPost()) { notFund(); }
$bindUserid = 'U_' . session('userinfo.uid');
$client_id = input("param.client_id", , "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', , 'int');
$client_id = input('post.client_id', '', 'string');
$sendType = input('post.sendType', '', 'string'); if (!in_array($sendType,['link', 'message'])) { //客户端错误
return json_encode(['status' => -]);
}
if (strlen($client_id) != ) { //客户端错误
return json_encode(['status' => -]);
}
if (!is_not_empty_string($toMid) || !is_positive_integer($product_id)) { //系统错误
return json_encode(['status' => -]);
}
$db_toMid = Hmodel\Product::getMidByProductid($product_id); //数据错误
if ($db_toMid != $toMid) {
return json_encode(['status' => -]);
}
require_once dirname(dirname(__FILE__)) . '/Events.php'; $uid = session('userinfo.uid');
$accIsOnline = Gateway::isUidOnline($toMid) == ? : ; //判读商家是否在线
$message_type = ;
if ($sendType == 'link') {
$message_type = ;
$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, , $accIsOnline);
if($message_type == ){
if(!is_numeric($message)){
$message = '"'.$message.'"';
}
if ($message == '') {
$message = '';
}
}
if ($insertId === false) { //入库失败(服务器故障)
return json_encode(['status' => -]);
}
$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' => ,
'timeStamp' => time(),
'timeStr' => date('H:i:s'),
'html' => $message_type == ? '' : 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) != || Gateway::isOnline($V_client_id) != ) { //客户端错误
return json_encode(['status' => -]);
}
$V_merchantInfo = Pmodel\Push::getVmerchantInfoByVid($V_uid);
if (!is_not_empty_array($V_merchantInfo)) { //商家信息不存在
return json_encode(['status' => -]);
}
require_once dirname(dirname(__FILE__)) . '/Events.php'; $accIsOnline = Gateway::isUidOnline('U_' . $toU_uid) == ? : ; //判读用户是否在线
$message_type = ;
//Log入库
$insertId = Pmodel\Push::addChatLog($toU_uid, 'V_' . $V_uid, $message, , , $accIsOnline);
if ($insertId === false) { //入库失败(服务器故障)
return json_encode(['status' => -]);
}
$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' => ,
'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, , ) != '<img src="data:image/' || substr($message, strpos($message, ';', ) ,) != ';base64,' ||
substr($message, -, ) != '">') {
return $message;
}
$preg = '/<img.*?src="(.*?)".*?>/is';
preg_match( $preg, $message,$arr);
$img_src = self::base64_upload($arr[]);
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,,strlen($img_start_code)); if ($initial == $img_start_code) {
$start = strpos($message, $img_start_code, );
$end = strpos($message, $img_end_code , );
$message_arr[] = substr($message, , $end + );
$message = substr($message, $end + );
}else{
$start = strpos($message, $img_start_code);
if ($start !== false) {
$message_arr[] = substr($message, , $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[] == 'jpeg'){
$image_name = getCode(,).'.jpg';
}else{
$image_name = getCode(,).'.'.$result[];
}
$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,,true);
if (file_put_contents($image_file, base64_decode(str_replace($result[], '', $base64_image)))){
return $image_file;
}else{
return false;
}
}else{
return false;
}
}
微信的图片上传
JS部分
/* 退款选择图片 -------- start */ $("#chooseImage").click(function()
{ wx.chooseImage(
{
count: , // 默认9
sizeType: ['original','compressed'], // 可以指定是原图还是压缩图,默认二者都有
sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
success: function (res)
{
images.localId = res.localIds; // 返回选定照片的本地ID列表,localId可以作为img标签的src属性显示图片
//for(var j = ;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 = ;
var i = , 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-]+' data-originalDrawing-src='+images.localId[i-]+' 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 == ){
for(var n = ; n < $(".chat-sender").length ; n++){
str = $(".chat-sender").eq(n).attr("serverId")+",";
for(var z=;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) != ) { //客户端错误
return json_encode(['status' => -]);
}
if (!is_not_empty_string($toMid)) { //系统错误
return json_encode(['status' => -]);
} require_once dirname(dirname(__FILE__)) . '/Events.php'; $accIsOnline = Gateway::isUidOnline($toMid) == ? : ; //判读商家是否在线
$message_type = ;
//微信上传图片处理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(,).'.jpg';
$targetName = './upload/chat/'.$str;
if (!file_exists("./upload/chat/")) {
mkdir("./upload/chat/", , 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, );
$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' => -]);
}
$message = json_encode($data);
//Log入库
$insertId = Pmodel\Push::addChatLog(self::$uid, $toMid, $message, $message_type, , $accIsOnline);
if ($insertId === false) { //入库失败(服务器故障)
return json_encode(['status' => -]);
}
$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' => ,
'timeStamp' => time(),
'timeStr' => date('H:i:s')
]); }
其他一些不是很重要的代码就不拿出来了。
当前项目只是一个简单的需求,并没有把GatewayWorker很多强大的功能体现出来,大家以后在项目开发中遇到更为复杂的需求,参考官方手册提供的一些Demo就可以慢慢实现并开发出更为健壮的项目!
PHP基于TP5使用Websocket框架之GatewayWorker开发电商平台买家与卖家实时通讯的更多相关文章
- 基于TP5使用Websocket框架之GatewayWorker开发电商平台买家与卖家实时通讯
https://www.cnblogs.com/wt645631686/p/7366924.html 前段时间公司提了一个新的需求,在商品的详情页要实现站内买家和商品卖家实时通讯的功能以方便沟通促成交 ...
- 使用Websocket框架之GatewayWorker开发电商平台买家与卖家实时通讯
前段时间公司提了一个新的需求,在商品的详情页要实现站内买家和商品卖家实时通讯的功能以方便沟通促成交易,要开发此功能当时首先考虑到的就是swoole和workerman了,从网上大概了解了一下关于这两款 ...
- 【基于微信小程序的社区电商平台】需求分析心得——小豆芽
一.项目内容 基于微信小程序,做一个社区电商平台,抓住社区电商的特点,做出特色,与微信集成,实现商品的个性化发布,以及个性化营销. 个性化发布:用户可以在应用上直接发布自己的商品,通过搜索心愿单可以查 ...
- 【基于微信小程序的社区电商平台】Alpha迭代心得
项目团队:小豆芽 开发周期:11.5-12.2(Alpha版本) 设想和目标 1. 我们的软件要解决什么问题?是否定义得很清楚?是否对典型用户和典型场景有清晰的描述? 解决问题:当前电商平台卖家买家角 ...
- Android通用框架设计与完整电商APP开发系列文章
作者|傅猿猿 责编|Javen205 有福利 有福利 有福利 鸣谢 感谢@傅猿猿 邀请写此系列文章 Android通用框架设计与完整电商APP开发 课程介绍 [导学视频] [课程详细介绍] 以下是部分 ...
- 【基于微信小程序的社区电商平台】第一次迭代心得(非正式版本
一.迭代任务 团队在第八周确认迭代计划时,是想要在第一阶段实现电商小程序的核心功能,就是买和卖,也是前端和后台数据交换的核心模块.涉及到首页浏览商品信息,查看商品详情及评论,选择加入购物车.关注卖家以 ...
- python基于selenium+cv2+numpy实现登录某大型电商系统
首先贴上我的安装包 一.selenium安装 I.打开pycharm,点击Settings,找到Project Interpreter,点击右边的下拉菜单下的show All...选项 II.点击sh ...
- 手撸基于swoole 的分布式框架 实现分布式调用(20)讲
最近看的一个swoole的课程,前段时间被邀请的参与的这个课程 比较有特点跟一定的深度,swoole的实战教程一直也不多,结合swoole构建一个新型框架,最后讲解如何实现分布式RPC的调用. 内容听 ...
- 通过Dapr实现一个简单的基于.net的微服务电商系统(二十)——Saga框架实现思路分享
今天这篇博文的主要目的是分享一下我设计Saga的实现思路来抛砖引玉,其实Saga本身非常的类似于一个简单的工作流体系,相比工作流不一样的部分在于它没有工作流的复杂逻辑处理机制(比如会签),没有条件分支 ...
随机推荐
- uniapp - 更改项目生成模板、页面
每次生成项目目录都需要删除一些再添加太麻烦了,就想着能不能修改一下模板 - 当然自定义模板也能实现 好了,被我找到了. 修改以后源文件名称和格式覆盖回去即可,重新启动hbuilderx 这里可以修改e ...
- Java虚拟机解释器与JIT编译器
一.JAVA编译相关概念 1.动态编译(dynamic compilation)指的是“在运行时进行编译”:与之相对的是事前编译(ahead-of-time compilation,简称AOT),也叫 ...
- sparkStreaming读取kafka写入hive表
sparkStreaming: package hive import java.io.File import org.apache.kafka.clients.consumer.ConsumerRe ...
- git之github配置
1.安装好git以后,我们配置git秘钥,首先输入下面的命令: 2.接着上述操作,一路回车按键.如图所示:生成了秘钥,, 如下图,就是秘钥了: 3.我们打开注册好的github地址.找到ssh选项,将 ...
- Angular2 输入完成后触发函数
(blur)="keySearch($event)" ,鼠标点击其他地方触发 keySearch(e): void { var dom = $(e.target); var key ...
- logback不同业务的日志打印到不同文件
logback不同业务的日志打印到不同文件 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/mggwct/article/details/777 ...
- Toping Kagglers:Bestfitting,目前世界排名第一
Toping Kagglers:Bestfitting,目前世界排名第一 Kaggle团队 |2018年5月7日 我们在排行榜上排名第一 - 这是两年前令人惊讶地加入该平台的竞争对手.Shubin ...
- 【bat】实现数组,for循环取数据
1.数组对象 @echo off set objLength=2 set obj[0].name=test1 set obj[0].password=1234 set obj[1].name=test ...
- 如何改变 select 元素的高度
mozilla 对于美化 select 元素的样式有这样一段描述(用 CSS 美化 Select 元素): 众所周知,select 元素很难用 CSS 进行高效的设计.你可以影响任何元素的某些方面 - ...
- LeetCode二叉树Java模板
public class TreeNode { int val; TreeNode left; TreeNode right; TreeNode(int x) { val = x; } } impor ...