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本身非常的类似于一个简单的工作流体系,相比工作流不一样的部分在于它没有工作流的复杂逻辑处理机制(比如会签),没有条件分支 ...
随机推荐
- getaddrinfo工作原理分析
getaddrinfo工作原理分析 将域名解析成ip地址是所有涉及网络通讯功能程序的基本步骤之一,常用的两个接口是gethostbyname和getaddrinfo,而后者是Posix标准推荐在新应用 ...
- 微信小程序 input 的 type属性 text、number、idcard、digit 区别
微信小程序的 input 有个属性叫 type,这个 type 有几个可选值: text:不必解释 number:数字键盘(无小数点) idcard:数字键盘(无小数点.有个 X 键) digit:数 ...
- 使用Faker来随机生成接近真实数据的数据
在很多场景我们需要造一些假数据或者mock数据,如果我们写死类似[XXXX]类似的无意义的其实不是很优雅,Faker能提供常用的一些名词的随机数据. 1.引入POM: <dependency&g ...
- postgre with递归查询组织路径
with递归查询组织路径 SELECT r.id, (array_to_string( array( select name from ( with recursive rec as( select ...
- django入门5使用xadmin搭建管理后台
环境搭建: pip install django==1.9.8 pip install MySQL_python-1.2.5-cp27-none-win_amd64.whl pip install f ...
- 微信支付:URL未注册问题
起因:一个项目已经做好了,微信支付也调通的,域名 www.xxxx.com ,某天客户需要换域名,改为weixin.xxxx.com, 原先的www转向客户自己的官网,结果换了之后,发现微信支付出错: ...
- PHP $$符号的作用与使用方法
php中$$符号的定义与作用 在PHP中单个美元符号变量($str),表示一个名为str的普通变量,它可以存储字符串.整数.数组.布尔等任何类型的值. 双美元符号的变量($$str):表示一个可变变量 ...
- 第一本docker书 学习笔记(一)
Docker的核心组件: Docker客户端和服务端 Docker镜像 Registry Docker容器 # Docker客户端和服务端 docker 是一个 C/S架构程序.客户端只需要向dock ...
- sql server数据库显示“单用户”的解决方法
USE master; GO DECLARE @SQL VARCHAR(MAX); SET @SQL='' SELECT @SQL=@SQL+'; KILL '+RTRIM(SPID) --杀掉该进程 ...
- 图像拼接(image stitching)
# OpenCV中stitching的使用 OpenCV提供了高级别的函数封装在Stitcher类中,使用很方便,不用考虑太多的细节. 低级别函数封装在detail命名空间中,展示了OpenCV算法实 ...