本文深入探討了Go語言中方法的各個方面,包括基礎概念、定義與聲明、特性、實戰應用以及性能考量。文章充滿技術深度,通過實例和代碼演示,力圖幫助讀者全面理解Go方法的設計哲學和最佳實踐。 關註【TechLeadCloud】,分享互聯網架構、雲服務技術的全維度知識。作者擁有10+年互聯網服務架構、AI產品 ...
項目地址
HTTP
http協議
- 超文本傳輸協議
- 無狀態協議
- 基於tcp協議的一個應用層的協議
- http是單向的,瀏覽器發起向伺服器的連接,伺服器預先並不知道
http協議工作過程
- 客戶端和服務端建立連接(三次握手),http開始工作
- 建立連接後客戶端發送給請求伺服器
- 伺服器接受到請求後,給予相應的響應信息
WebSoket
websoket協議
- websocket是H5提出的在單個TCP協議上進行的全雙工通訊協議
- 實現了瀏覽器與伺服器全雙工通信,能更好的節省伺服器資源和帶寬並達到實事通訊的目的
- WebSokcet是一個持久化的協議
工作過程
- 客戶端發送http請求,經過三次握手,建立TCP連接,在http 請求裡面存放 websocket 支持的版本號信息
- 伺服器接收請求,同樣以http協議回應
- 連接成功,客戶端與伺服器建立持久性的連接
websocket 與 http 差異
相同點
都是基於tcp的,都是可靠的性傳輸協議
不同點
- websocket是雙向通信協議,模擬socket協議,可以雙向發送或接受信息
- websocket是持久化連接,http 是短連接
- websocket是有狀態的,http 是無狀態的
- websocket 連接之後伺服器和客戶端可以雙向發送數據,http只能是客戶端發起一次請求之後,伺服器才能返回數據
輪詢
過程
- 客戶端發起長輪詢,如果服務端的數據沒有發生變化,就會 hold 住請求,知道服務端的數據發生變化
- 優點 是解決了http不能實時更新的弊端,實現了 "偽-長連接"
- 輪詢的本質依然是 request <-> response
弊端
- 推送延遲
- 服務端壓力
- 推送延遲和服務端壓力無法中和
websocket改進
JS Websocket
簡單示例
ws = new WebSocket('ws://127.0.0.1:2000');
//當 websocket 創建成功後 觸發onopen事件
ws.onopen = function () {
var data = {};
data.type = 'login';
//標識 客戶還是客服
data.group = 'member';
//發送信息
ws.send(JSON.stringify(data));
}
//收到服務端發來的消息 觸發 onmessage
ws.onmessage = function (e) {
var data = JSON.parse(e.data);
}
Workerman基礎
安裝
Composer安裝:
composer require workerman/workerman
啟動停止
# 以debug(調試)方式啟動
php start.php start
# 以daemon(守護進程)方式啟動
php start.php start -d
# 停止
php start.php stop
# 重啟
php start.php restart
# 平滑重啟
php start.php reload
# 查看狀態
php start.php status
簡單示例
實例一、使用HTTP協議對外提供Web服務
創建start.php文件
<?php
use Workerman\Worker;
use Workerman\Connection\TcpConnection;
use Workerman\Protocols\Http\Request;
require_once __DIR__ . '/vendor/autoload.php';
// 創建一個Worker監聽2345埠,使用http協議通訊
$http_worker = new Worker("http://0.0.0.0:2345");
// 啟動4個進程對外提供服務
$http_worker->count = 4;
// 接收到瀏覽器發送的數據時回覆hello world給瀏覽器
$http_worker->onMessage = function(TcpConnection $connection, Request $request)
{
// 向瀏覽器發送hello world
$connection->send('hello world');
};
// 運行worker
Worker::runAll();
實例二、使用WebSocket協議對外提供服務
創建ws_test.php文件
<?php
use Workerman\Worker;
use Workerman\Connection\TcpConnection;
require_once __DIR__ . '/vendor/autoload.php';
// 註意:這裡與上個例子不同,使用的是websocket協議
$ws_worker = new Worker("websocket://0.0.0.0:2000");
// 啟動4個進程對外提供服務
$ws_worker->count = 4;
// 當收到客戶端發來的數據後返回hello $data給客戶端
$ws_worker->onMessage = function(TcpConnection $connection, $data)
{
// 向客戶端發送hello $data
$connection->send('hello ' . $data);
};
// 運行worker
Worker::runAll();
測試
打開chrome瀏覽器,按F12打開調試控制台,在Console一欄輸入(或者把下麵代碼放入到html頁面用js運行)
// 假設服務端ip為127.0.0.1
ws = new WebSocket("ws://127.0.0.1:2000");
ws.onopen = function() {
alert("連接成功");
ws.send('tom');
alert("給服務端發送一個字元串:tom");
};
ws.onmessage = function(e) {
alert("收到服務端的消息:" + e.data);
};
TP的資料庫類
composer require topthink/think-orm
ThinkPhp
安裝
# 安裝
composer create-project topthink/think tp
# 視圖擴展
composer require topthink/think-view
# 多應用擴展
composer require topthink/think-multi-app
# 驗證碼擴展
composer require topthink/think-captcha
開啟多應用
- 刪除原始的
app/controller
目錄 - 在項目跟目錄下 使用命令
php think build admin
來創建應用 - 將全局的
config
和route
複製一份到創建的應用裡面- 開器多應用後全局的
route
會失效, - 應用裡面的
config
參數 可以覆蓋全選的config
參數 - 可以針對不同的應用設置不同的配置參數和相同的配置
- 開器多應用後全局的
運行thinkphp
直接運行tp
php think run
設置埠
php think run -p 8081
訪問地址
http://127.0.0.1:8000/
開啟多應用後 通過 地址+應用名 +參數
來訪問不同的應用
http://127.0.0.1:8000/admin
預設的應用是index
可以忽略不寫
http://127.0.0.1:8000/index
更多的配置查看 手冊
創建對應的控制器
php think make:controller admin@Service --plain
php think make:controller admin@Service --plain
獲取URL
//助手函數 返回buildUrl()
//如果需要返回客戶端 需要先強制轉換為字元串類型後再返回。
url();
(string)url();
//控制器方法路徑 參數
// suffix URL尾碼
// domain domain
// root 入口文件
url('index/blog/read', ['id'=>5])
->suffix('html')
->domain(true)
->root('/index.php');
中間件
生成命令
//多應用模式
php think make:middleware admin@Check
在 對應的應用 route/app.php
文件裡面註冊 路由中間件
use think\facade\Route;
Route::group(function(){
Route::get('index/index','index/index');
Route::get('service/index','service/index');
})->middleware(\app\admin\middleware\Check::class);
Route::role('login/login','login/login','get|post');
使用驗證碼擴展
驗證碼庫需要開啟Session才能生效。
在 app/middleware.php
設置
// 全局中間件定義文件
return [
// Session初始化
\think\middleware\SessionInit::class
];
config/captcha.php
為驗證碼的配置文件
示例
<!-- 獲取驗證碼 -->
<div>{:captcha_img()}</div>
<div><img src="{:captcha_src()}" alt="captcha" /></div>
//兩種方式
//校驗驗證碼
$this->validate($data,[
'captcha|驗證碼'=>'require|captcha'
]);
if(!captcha_check($captcha)){
// 驗證失敗
};
HTTP Requests for PHP
安裝
文檔 https://requests.ryanmccue.info/download/
composer require rmccue/requests
使用案例
$response = WpOrg\Requests\Requests::get('https://api.github.com/events');
var_dump($response->body);
// string(42865) "[{"id":"15624773365","type":"PushEvent","actor":{...
//post請求
$response = WpOrg\Requests\Requests::post('https://httpbin.org/post');
//設置請求頭
$url = 'https://api.github.com/some/endpoint';
$headers = array('Content-Type' => 'application/json');
$data = array('some' => 'data');
$response = WpOrg\Requests\Requests::post($url, $headers, json_encode($data));
即時通訊聊天系統
簡單的群聊功能
前端頁面
聊天框內容分析
前端代碼
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="/static/layui/css/layui.css">
<title>客戶端聊天視窗</title>
<style type="text/css">
html,
body {
width: 98%;
height: 100%;
margin: 0 auto;
padding: 0px
}
.head_icon {
display: inline-block;
width: 50px;
height: 50px;
overflow: hidden;
border-radius: 20%;
}
#contentor{
overflow-y: auto; /* 垂直方向滾動 */
height: 500px; /* 高度自適應 */
width: 100%; /* 寬度自適應 */
}
</style>
</head>
<body>
<div class="layui-panel" >
<div class="layui-row layui-col-space32
" style="padding: 32px;">
<div class="layui-col-xs12 ">
<div style="border: 1px solid #f9f9f9;" id="contentor">
</div>
</div>
<div class="layui-col-md12">
<div class="layui-row">
<span class="layui-col-xs8">
<input type="text" name="send" placeholder="輸入要發送的內容" class="layui-input">
</span>
<span class="layui-col-xs4">
<button type="button" class="layui-btn layui-bg-blue btn" style="width: 100%;">發送信息</button>
</span>
</div>
</div>
</div>
</div>
<script src="/static/layui/layui.js"></script>
<script type="text/javascript">
layui.use(function () {
$ = layui.jquery;
layer = layui.layer;
ws_connect()
send()
})
//發送消息
function send() {
var button = $('.btn'),
text = $('input[name="send"]');
//發送按鈕點擊後
button.click(function () {
//給框體裡面添加對應的顯示代碼
// 獲取輸入框內容
if (text.val() === "") {
layer.msg('請輸入內容')
} else {
data = { msg: text.val() };
ws.send(JSON.stringify(data));
data['avatarRam'] = avatarRam;
auto_chat(data);
//清空內容框
text.val('')
}
})
}
function ws_connect() {
ws = new WebSocket('ws://127.0.0.1:2000');
//當 websocket 創建成功後 觸發onopen事件
ws.onopen = function () {
// auto_chat('你是零基礎的嗎','老手覅');
// setTimeout(()=>{auto_chat('同學你好','老手覅')}, 2000)
var data = {};
data.type = 'login';
//標識 客戶還是客服
data.group = 'member';
ws.send(JSON.stringify(data));
}
//收到服務端發來的消息 觸發 onmessage
ws.onmessage = function (e) {
var data = JSON.parse(e.data);
if (data.type == 'login') {
avatarRam = data.avatarRam
return ''
}
auto_chat(data)
}
}
//發送消息
function auto_chat(data) {
let html_other = `
<div class="layui-col-md12">
<div class="layui-row ">
<div class=" layui-col-xs1" style="text-align: left;">
<div class="head_icon">
<img src="/img/avatar${data.avatarRam}.png" alt=""
style="width: 100%;height: auto;display: inline-block;">
</div>
</div>
<div class=" layui-col-xs11">
<strong>游客${data.uid}</strong>
<span class="layui-font-green layui-font-16"> ${getCurrentTime()} </span>
<br>
<button class="layui-btn layui-btn-radius">${data.msg}</button>
</div>
</div>
</div>
`
let html_my = `
<div class="layui-col-md12">
<div class="layui-row">
<div class=" layui-col-xs11 " style="text-align: right;">
<span style="display: inline-block;" class="layui-font-green layui-font-16"> ${getCurrentTime()}
</span>
<br>
<button class="layui-btn layui-bg-blue layui-btn-radius">${data.msg}</button>
</div>
<div class="layui-col-xs1" style="text-align: right;">
<div class="head_icon " style="display: inline-block;">
<img src="/img/avatar${data.avatarRam}.png" alt=""
style="width: 100%;height: auto;">
</div>
</div>
</div>
</div>
`;
console.log(data);
console.log(data.uid);
//將信息添加到對應的框體內
if (data.uid === undefined) {
$('#contentor').append(html_my);
} else {
$('#contentor').append(html_other);
}
}
//獲取當前時間
function getCurrentTime() {
const now = new Date();
const formattedTime = `${now.getFullYear()}-${('0' + (now.getMonth() + 1)).slice(-2)}-${('0' + now.getDate()).slice(-2)} ${('0' + now.getHours()).slice(-2)}:${('0' + now.getMinutes()).slice(-2)}:${('0' + now.getSeconds()).slice(-2)}`;
return formattedTime;
}
</script>
</body>
</html>
workerman代碼
<?php
use Workerman\Worker;
use Workerman\Connection\TcpConnection;
require_once __DIR__ . '/vendor/autoload.php';
// 註意:這裡與上個例子不同,使用的是websocket協議
$ws_worker = new Worker("websocket://0.0.0.0:2000");
$global_uid = 0;
//有新的客戶端與workman建立連接後
$ws_worker->onConnect = function (TcpConnection $connection) use (&$global_uid, $ws_worker) {
//用戶id
$connection->uid = ++$global_uid;
//用戶頭像
$connection->avatarRam = mt_rand(0,5);
};
$ws_worker->onMessage = function (TcpConnection $connection, $data) use ($ws_worker) {
$data = json_decode($data,true);
$data['uid'] = $connection->uid;
$data['avatarRam'] = $connection->avatarRam;
//如果是login表示初次登錄 返回 avatarRam 頭像信息
if($data['type']=='login'){
$connection->send(json_encode($data));
}
foreach ($ws_worker->connections as $conn) {
//除了自身之外 其他人都發送
if ($connection->id != $conn->id) {
//返回的信息包含id和 頭像 和 接受的msg
$conn->send(json_encode($data));
}
}
// $connection->send("游客{$connection->uid}:$data");
};
// 運行worker
Worker::runAll();
游客 客服聊天
大體框架
游客前端代碼
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>客戶端</title>
<link href="//unpkg.com/[email protected]/dist/css/layui.css" rel="stylesheet">
<style>
.box1 {
margin: auto;
border: 1px solid red;
width: 800px;
height: 500px;
position: relative;
/* margin-top: 1%; */
/* float: left; */
}
.member-list {
float: left;
background-color: #dbe4ff;
width: 200px;
height: 100%;
display: inline;
}
.msg-container {
float: left;
width: 596px;
height: 100%;
border-color: black;
}
.msg-container .msg-list {
height: 400px;
width: 100%;
background-color: bisque;
}
.msg-container .msg-send {
height: 100px;
background-color: black;
}
.member-item {
width: 100%;
height: 50px;
font-size: 20px;
/* color:rgb(22, 186, 170); */
}
</style>
</head>
<body>
<div class="box1">
<div class="member-list layui-row"> </div>
<div class="msg-container">
<!-- 聊天區域 -->
<div class="msg-list ">
</div>
<div class="msg-send">
<div class="layui-row">
<div class="layui-col-xs10">
<textarea name="desc" placeholder="多行文本框" class="layui-textarea"></textarea>
</div>
<div class="layui-col-xs2">
<button type="button" id="send" onclick="sendmsg(this)" from_id="" class="layui-btn"
style="width: 100%;height: 100px;">發送</button>
</div>
</div>
</div>
</div>
</div>
<script src="//unpkg.com/[email protected]/dist/layui.js"></script>
<script>
layui.use(['layer'], function () {
layer = layui.layer,
$ = layui.jquery;
//客服點擊發送信息
// $('#send').click(function(){
// //獲取當前回覆的客服id
// from_id=parseInt($(this).attr('from_id'));
// if(isNaN(from_id )){
// //說明為空 沒有選中
// layer.alert('請選擇一個用戶');
// return '';
// }
// });
})
//客服發送信息
function sendmsg(obj) {
touid = parseInt($(obj).attr('from_id'));
if (isNaN(touid)) {
//說明為空 沒有選中
return layer.alert('請選擇一個用戶');
}
//發送信息
//獲取信息
var data = {
type: 'msg',
group: 'admin',
touid: touid,
msg: $('textarea[name="desc"]').val(),
}
if (data.msg.trim == '' || data.msg.length == 0) {
return layer.alert('信息不能為空', { icon: 0 })
}
ws.send(JSON.stringify(data));
$('textarea[name="desc"]').val('');
auto_chat(data)
}
// 客戶列表
var userList = [];
ws = new WebSocket('ws://127.0.0.1:2000')
//建立連接後觸發 onopen時間
ws.onopen = function () {
let data = {};
//進行登錄
data.type = 'login';
//用戶標識 為客服
data.group = 'admin';
// 發送信息
ws.send(JSON.stringify(data));
init_load_user_list();
}
//接收到伺服器發送的消息後
ws.onmessage = function (e) {
var data = JSON.parse(e.data);
//拉取客戶列表
if (data.type == 'load_user_list') {
var uList = data.userlist;
$.each(uList, function (i, v) {
userList.push(v);
})
//構建客戶列表
get_user_list();
return false;
}
//用用戶退出
if (data.type == 'logout') {
// 判斷是否在列表裡面
var index = $.inArray(data.disc_id, userList);
if (index > -1) {
$('#' + data.disc_id).remove();
$('#member_' + data.disc_id).remove();
if ($('#send').attr('from_id') == data.disc_id) {
$('#send').attr('from_id', '')
}
}
}
//有新用戶來
if (data.type == 'login') {
//如果沒有找到 說明沒有這個用戶的信息
if ($.inArray(data.from_id, userList) == -1) {
userList.push(data.from_id)
}
get_user_list();
return;
}
//收到新的消息
if (data.type == 'msg') {
//將信息顯示到對應的框裡面
// var member_id = data.from_id;
// 獲取到對應的對話框
// $('#member_'+member_id).
data.avatarRam = Math.ceil(5);
auto_chat(data)
}
}
//初始化拉取客戶列表
function init_load_user_list() {
$data = {
type: 'load_user_list',
group: 'admin'
};
ws.send(JSON.stringify($data))
}
//部署客戶端客戶列表ui 並初始化對應的聊天框
function get_user_list() {
var html = '';
$.each(userList, function (i, v) {
html += `<div class="member-item layui-col-xs12 layui-btn layui-btn-primary layui-btn-fluid " style="margin:0" id="${v}" onclick="checkme(this)" member_id="${v}">客戶${v}</div>`;
var htmlmsg = `<div id="member_${v}" class="layui-row" style="display: none;"> </div>`;
$('.msg-list').append(htmlmsg);
});
$('.member-list').html(html);
//
}
// 和單獨某個用戶聊天
function checkme(obj) {
$(obj).removeClass('layui-btn-primary').siblings('div').addClass('layui-btn-primary');
var member_id = $(obj).attr('member_id');
//創建對應的內容顯示體
//如果等於0 說明不存在 進行創建 並且設置為顯示
console.log($(`#member_${member_id}`).length);
if ($(`#member_${member_id}`).length <= 0) {
var htmlmsg = `<div id="member_${member_id}" class="layui-row" > <div>`;
$('.msg-list').append(htmlmsg);
}
$(`#member_${member_id}`).show().siblings().hide();
$('#send').attr('from_id', member_id);
}
//發送消息
function auto_chat(data) {
let html_other = `
<div class="layui-col-md12">
<div class="layui-row ">
<div class=" layui-col-xs1" style="text-align: left;">
<div class="head_icon">
<img src="/img/avatar${data.avatarRam}.png" alt=""
style="width: 100%;height: auto;display: inline-block;">
</div>
</div>
<div class=" layui-col-xs11">
<strong>用戶${data.from_id}</strong>
<span class="layui-font-green layui-font-16"> ${getCurrentTime()} </span>
<br>
<button class="layui-btn layui-btn-radius">${data.msg}</button>
</div>
</div>
</div>
`
let html_my = `
<div class="layui-col-md12">
<div class="layui-row">
<div class=" layui-col-xs11 " style="text-align: right;">
<span style="display: inline-block;" class="layui-font-green layui-font-16"> ${getCurrentTime()}
</span>
<br>
<button class="layui-btn layui-bg-blue layui-btn-radius">${data.msg}</button>
</div>
<div class="layui-col-xs1" style="text-align: right;">
<div class="head_icon " style="display: inline-block;">
<img src="https://pic.qqtn.com/up/2017-12/15132234795879682.jpg" alt=""
style="width: 100%;height: auto;">
</div>
</div>
</div>
</div>
`;
//將信息添加到對應的框體內
//如果 group = admin 說明是發消息
if (data.group === 'admin') {
$('#member_' + data.touid + '').append(html_my);
} else {
//收到消息
$('#member_' + data.from_id + '').append(html_other);
}
}
//獲取當前時間
function getCurrentTime() {
const now = new Date();
const formattedTime = `${now.getFullYear()}-${('0' + (now.getMonth() + 1)).slice(-2)}-${('0' + now.getDate()).slice(-2)} ${('0' + now.getHours()).slice(-2)}:${('0' + now.getMinutes()).slice(-2)}:${('0' + now.getSeconds()).slice(-2)}`;
return formattedTime;
}
</script>
</body>
</html>
後端workerman
<?php
use Workerman\Worker;
use Workerman\Timer;
use Workerman\Connection\TcpConnection;
use WpOrg\Requests\Requests;
use think\facade\Db;
require_once __DIR__ . '/vendor/autoload.php';
$ws_workder = new Worker("websocket://127.0.0.1:2000");
//接受到信息
$ws_workder->onMessage = function (TcpConnection $connection, $data) use ($ws_workder) {
$data = json_decode($data, true);
//說明是初次登錄 上線操作
if ($data['type'] == 'login') {
//設置分組
$connection->group = $data['group'];
//給鏈接對象添加屬性 isreplied false
$connection->isreplied = false;
//當客戶進來的時候
if ($connection->group == 'member') {
$serviceList = [];
foreach ($ws_workder->connections as $conn) {
// 找當前線上的客服
if ($conn->group == 'admin') {
$serviceList[] = $conn->id;
}
}
//如果當前客服有線上的
if (!empty($serviceList)) {
//隨機取出一個客服的id
$connection->touid = $serviceList[array_rand($serviceList, 1)];
foreach ($ws_workder->connections as $conn) {
// 找到對應的客服id 準備接待
if ($connection->touid == $conn->id) {
$data['from_id'] = $connection->id;
$conn->send(json_encode($data));
$connection->isreplied = true;
}
}
}
}
}
//有新消息發送
if ($data['type'] == 'msg') {
//客戶發送信息
if ($data['group'] == 'member') {
// 獲取當前用戶的id
foreach ($ws_workder->connections as $conn) {
//如果有客服並且客服線上
//如果用戶的touid 等於 連接的id 說明匹配到了對應的客服
if ($conn->id == $connection->touid) {
$data['from_id'] = $connection->id;
//把消息客服發送消息
$conn->send(json_encode($data));
$posts = ['from_id' => $connection->id, 'to_id' => $connection->touid, 'msg' => $data['msg']];
//方案1 提交保存
$response=Requests::post('http://127.0.0.1:8000/admin/service/save_msg',data:$posts);
return ;
}
// 如果沒有客服 還沒有製作 可以直接保存發送的信息到資料庫 等客服上線後發送給客服
}
}
//當客服發送信息
if ($data['group'] == 'admin') {
$touid = $data['touid'];
foreach ($ws_workder->connections as $con) {
if ($touid == $con->id) {
// 發送信息
$msgData=[
'type'=>'msg',
'from_id'=>$connection->id,
'msg'=>$data['msg'],
];
$con->send(json_encode($msgData));
$posts = ['from_id' => $connection->id, 'to_id' => $touid, 'msg' => $data['msg']];
//方案1 提交保存
// $response=Requests::post('http://127.0.0.1:8000/admin/service/save_msg',data:$posts);
//方案2 直接保存到資料庫
// Db::table('msg')->save($posts);
return ;
}
}
}
}
// 客服發來消息,請求客戶列表
if ($connection->group == 'admin' and $data['type'] == 'load_user_list') {
$userlist = [];
foreach ($ws_workder->connections as $conn) {
//如果這個人是客戶 並且 沒有客服對象
if ($conn->group == 'member' and !$conn->isreplied) {
$userlist[] = $conn->id;
$conn->isreplied = true;
$conn->touid = $connection->id;
}
}
$data['type'] = 'load_user_list';
$data['userlist'] = $userlist;
$connection->send(json_encode($data));
}
};
//連接斷開
$ws_workder->onClose = function (TcpConnection $connection) use ($ws_workder) {
//客戶斷開連接 發送給對應的客服發送下線提醒
if ($connection->group == 'member') {
//遍歷當前客戶所屬客服的id
foreach ($ws_workder->connections as $conn) {
//如果有分配客服
if (!empty($connection->touid) and $conn->id == $connection->touid) {
$data['type'] = 'logout';
$data['disc_id'] = $connection->id;
$conn->send(json_encode($data));
}
}
};
//客服 下線
if ($connection->group == 'admin') {
// 遍歷出這位客服所管理的線上客戶
foreach ($ws_workder->connections as $conn) {
//如果有分配客服
if ($conn->group == 'member' and $conn->touid == $connection->id) {
//將所管理的客戶回歸
$conn->isreplied = false;
}
}
};
};
//存儲交流的信息
// 1 發送到tp伺服器去存儲
// 2. 直接在workerman中去請求mysql存儲
// 運行worker
Worker::runAll();
客服代碼
登錄前端
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>登錄頁面</title>
<!-- 請勿在項目正式環境中引用該 layui.css 地址 -->
<link href="//unpkg.com/[email protected]/dist/css/layui.css" rel="stylesheet">
</head>
<body>
<style>
.demo-login-container {
width: 320px;
margin: 21px auto 0;
}
.demo-login-other .layui-icon {
position: relative;
display: inline-block;
margin: 0 2px;
top: 2px;
font-size: 26px;
}
.layui-panel {
height: 98vh;
min-height: 500px;
display: flex;
align-items: center;
justify-content: center;
/* 如果你也希望水平居中 */
}
.layui-panel>div {
width: 360px;
height: 330px;
border: 1px solid red;
}
</style>
<div class="layui-panel">
<div style="padding: 32px;">
<form class="layui-form">
<div class="demo-login-container">
<div class="layui-form-item">
<div class="layui-input-wrap">
<div class="layui-input-prefix">
<i class="layui-icon layui-icon-username"></i>
</div>
<input type="text" name="username" value="" lay-verify="required" placeholder="用戶名"
lay-reqtext="請填寫用戶名" autocomplete="off" class="layui-input" lay-affix="clear">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-wrap">
<div class="layui-input-prefix">
<i class="layui-icon layui-icon-password"></i>
</div>
<input type="password" name="password" value="" lay-verify="required" placeholder="密 碼"
lay-reqtext="請填寫密碼" autocomplete="off" class="layui-input" lay-affix="eye">
</div>
</div>
<div class="layui-form-item">
<div class="layui-row">
<div class="layui-col-xs7">
<div class="layui-input-wrap">
<div class="layui-input-prefix">
<i class="layui-icon layui-icon-vercode"></i>
</div>
<input type="text" name="captcha" value="" lay-verify="required|captcha"
placeholder="驗證碼" lay-reqtext="請填寫驗證碼" autocomplete="off" class="layui-input"
lay-affix="clear">
</div>
</div>
<div class="layui-col-xs5">
<div style="margin-left: 10px;">
<img width="100%" src="{:captcha_src()}"
onclick="this.src='{:captcha_src()}?_='+ new Date().getTime();">
</div>
</div>
</div>
</div>
<div class="layui-form-item">
<input type="checkbox" name="remember" lay-skin="primary" title="記住密碼">
<a href="#forget" style="float: right; margin-top: 7px;">忘記密碼?</a>
</div>
<div class="layui-form-item">
<button class="layui-btn layui-btn-fluid" lay-submit lay-filter="demo-login">登錄</button>
</div>
<div class="layui-form-item demo-login-other">
<label>社交賬號登錄</label>
<span style="padding: 0 21px 0 6px;">
<a href="javascript:;"><i class="layui-icon layui-icon-login-qq"
style="color: #3492ed;"></i></a>
<a href="javascript:;"><i class="layui-icon layui-icon-login-wechat"
style="color: #4daf29;"></i></a>
<a href="javascript:;"><i class="layui-icon layui-icon-login-weibo"
style="color: #cf1900;"></i></a>
</span>
或 <a href="#reg">註冊帳號</a>
</div>
</div>
</form>
</div>
</div>
<!-- 請勿在項目正式環境中引用該 layui.js 地址 -->
<script src="//unpkg.com/[email protected]/dist/layui.js"></script>
<script>
layui.use(function () {
var form = layui.form;
var layer = layui.layer;
var $ = layui.jquery;
//自定義驗證
form.verify({
captcha: function (value, elem) {
var msg = '';
// console.log(value);
if (value.length == 4) {
//如果為4發送ajax驗證測試驗證碼是否通過
var obj = $.ajax({
url: '{:url("admin/login/captchaCheck")}',
method: 'post',
async: false,
data: { captcha: value },
})
obj.done((res) => {
if (res.code == 0) {
} else {
//沒有通過驗證
// return res.msg;
msg = res.msg;
}
// console.log(res);
})
obj.fail((err) => {
// console.log(err);
})
return msg;
} else {
return '長度不對'
}
}
})
// 提交事件
form.on('submit(demo-login)', function (data) {
var field = data.field; // 獲取表單欄位值
$.ajax({
url: '{:url(domain:true)}',
method: 'post',
data: field,
}).then((res) => {
if (res.code != 0) {
layer.msg(res.msg)
} else {
location.href = res.href
}
})
return false; // 阻止預設 form 跳轉
});
});
</script>
</body>
</html>
後端登錄校驗
Login.php
<?php
declare(strict_types=1);
namespace app\admin\controller;
use app\BaseController;
class Login extends BaseController
{
function login()
{
if ($this->request->isGet()) {
return view('index/login');
} elseif ($this->request->isPost()) {
// return '12345';
$data = $this->request->post();
//進行登錄驗證
$this->validate($data, [
//校驗規則
'username' => 'require',
'password' => 'require',
// 'captcha|驗證碼'=>'require|captcha'
], [
//校驗校驗失敗返回
'username.require' => '用戶名不能為空',
'password.require' => '密碼不能為空',
// 'captcha.require'=>'驗證碼不能為空',
]);
//就不查詢資料庫處理了
$pwd = md5('123456');
// 直接判斷
if ($data['username'] == 'admin' and md5($data['password']) == $pwd) {
//通過校驗 跳轉到控制頁面
//讓前端去跳轉頁面
//將用戶登錄信息寫入到 緩存 或者session中去
$href = (string)url('admin/index/index');
session('user',$data['username']);
return json(['code' => 0, 'msg' => '登錄成功', 'href' => $href]);
// return view('index/index');
}
//沒有通過校驗 返回錯誤信息
return json(['code' => 1001, 'msg' => '密碼錯誤']);
}
}
function captchaCheck()
{
return json(['code' => 0, 'msg' => '通過']);
// return '12345';
//單獨對驗證碼進行校驗
//獲取驗證碼內容
$captcha = $this->request->param('captcha');
if (!captcha_check($captcha)) {
//失敗
return json(['code' => 1001, 'msg' => '驗證碼錯誤']);
} else {
return json(['code' => 0, 'msg' => '通過']);
}
}
}
客服頁面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="/static/layui/css/layui.css">
<title>客戶端聊天視窗</title>
<style type="text/css">
html,
body {
width: 98%;
height: 100%;
margin: 0 auto;
padding: 0px
}
.head_icon {
display: inline-block;
width: 50px;
height: 50px;
overflow: hidden;
border-radius: 20%;
}
#contentor{
overflow-y: auto; /* 垂直方向滾動 */
height: 500px; /* 高度自適應 */
width: 100%; /* 寬度自適應 */
}
</style>
</head>
<body>
<div class="layui-panel" >
<div class="layui-row layui-col-space32
" style="padding: 32px;">
<div class="layui-col-xs12 ">
<div style="border: 1px solid #f9f9f9;" id="contentor">
</div>
</div>
<div class="layui-col-md12">
<div class="layui-row">
<span class="layui-col-xs8">
<input type="text" name="send" placeholder="輸入要發送的內容" class="layui-input">
</span>
<span class="layui-col-xs4">
<button type="button" class="layui-btn layui-bg-blue btn" style="width: 100%;">發送信息</button>
</span>
</div>
</div>
</div>
</div>
<script src="/static/layui/layui.js"></script>
<script type="text/javascript">
layui.use(function () {
$ = layui.jquery;
layer = layui.layer;
ws_connect()
send()
})
//發送消息
function send() {
var button = $('.btn'),
text = $('input[name="send"]');
//發送按鈕點擊後
button.click(function () {
//給框體裡面添加對應的顯示代碼
// 獲取輸入框內容
if (text.val() === "") {
layer.msg('請輸入內容')
} else {
data = {
group:'member',
type:'msg',
msg: text.val()
};
ws.send(JSON.stringify(data));
// data['avatarRam'] =avatarRam;
data['avatarRam'] = 1;
auto_chat(data);
//清空內容框
text.val('')
}
})
}
function ws_connect() {
ws = new WebSocket('ws://127.0.0.1:2000');
//當 websocket 創建成功後 觸發onopen事件
ws.onopen = function () {
// auto_chat('你是零基礎的嗎','老手覅');
// setTimeout(()=>{auto_chat('同學你好','老手覅')}, 2000)
var data = {};
data.type = 'login';
//標識 客戶還是客服
data.group = 'member';
ws.send(JSON.stringify(data));
}
//收到服務端發來的消息 觸發 onmessage
ws.onmessage = function (e) {
var data = JSON.parse(e.data);
if (data.type == 'login') {
// avatarRam = data.avatarRam
avatarRam = 1
return ''
}
auto_chat(data)
}
}
//發送消息
function auto_chat(data) {
let html_other = `
<div class="layui-col-md12">
<div class="layui-row ">
<div class=" layui-col-xs1" style="text-align: left;">
<div class="head_icon">
<img src="https://pic.qqtn.com/up/2017-12/15132234795879682.jpg" alt=""
style="width: 100%;height: auto;display: inline-block;">
</div>
</div>
<div class=" layui-col-xs11">
<strong>游客${data.uid}</strong>
<span class="layui-font-green layui-font-16"> ${getCurrentTime()} </span>
<br>
<button class="layui-btn layui-btn-radius">${data.msg}</button>
</div>
</div>
</div>
`
let html_my = `
<div class="layui-col-md12">
<div class="layui-row">
<div class=" layui-col-xs11 " style="text-align: right;">
<span style="display: inline-block;" class="layui-font-green layui-font-16"> ${getCurrentTime()}
</span>
<br>
<button class="layui-btn layui-bg-blue layui-btn-radius">${data.msg}</button>
</div>
<div class="layui-col-xs1" style="text-align: right;">
<div class="head_icon " style="display: inline-block;">
<img src="/img/avatar${data.avatarRam}.png" alt=""
style="width: 100%;height: auto;">
</div>
</div>
</div>
</div>
`;
console.log(data);
console.log(data.uid);
//將信息添加到對應的框體內
if (data.group === 'member') {
$('#contentor').append(html_my);
} else {
$('#contentor').append(html_other);
}
}
//獲取當前時間
function getCurrentTime() {
const now = new Date();
const formattedTime = `${now.getFullYear()}-${('0' + (now.getMonth() + 1)).slice(-2)}-${('0' + now.getDate()).slice(-2)} ${('0' + now.getHours()).slice(-2)}:${('0' + now.getMinutes()).slice(-2)}:${('0' + now.getSeconds()).slice(-2)}`;
return formattedTime;
}
</script>
</body>
</html>
簡單總結
課程鏈接
用到的知識點
- php
- thinkphp
- wrokerman
- http request
- 前端
- jquery
- layui
存在問題
以游客的身份也無法進行鑒權
可以通過 $connection->getRemoteIp()獲得對方ip 但是如果游客的ip也在變化就沒啥用了
註意:onConnect事件僅僅代表客戶端與Workerman完成了TCP三次握手,這時客戶端還沒有發來任何數據,此時除了通過$connection->getRemoteIp()獲得對方ip,
可以通過隱藏對話框來模擬關閉
頁面不夠美觀
代碼
layer.open({
type: 2,
closeBtn: 0,
maxmin: true,
title: '聊天通信',
area: ['800px', '800px'],
// btn: ['發送'],
shade: 0,
content: '/index/index/chat',
//點擊按鈕後觸發的函數
yes: function (index, layero) {
//獲取到打開iframe對象
var iframeWin = window[layero.find('iframe')[0]['name']];
// console.log(iframeWin);
//調用對應的send方法
iframeWin.send();
}
})