您当前的位置: 首页 > 学无止境 > 心得笔记 网站首页心得笔记
LetsgoMessaging即时聊天开发(SignalR+php+websocket)
发布时间:2020-06-07 14:42:04编辑:雪饮阅读()
有个需求是对接马来西亚一个外卖平台的web端群聊功能
对方提供的接口如
https://api.testletsgomessenger.com/index.html
文档中所涉及的客户端对于这个需求来说当然是JavaScript咯
但是想想看JavaScript在跨域方面根本就是鸡肋,那么本文就是通过php来为JavaScript提供跨域服务,只有最终的websocket连接是链接到他们的。
那么JavaScript方面如
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="/jquery.min.js"></script>
<script src="/layer-v3.1.1/layer/layer.js"></script>
<script src="/signalr.min.js"></script>
<script src="/moment-with-locales.min.js"></script>
<script src="/lodash.min.js"></script>
<script>
var user="https://api.testletsgomessenger.com/api/v1/User";
var tenant="https://api.testletsgomessenger.com/api/v1/Tenant";
// var socketDomain="";
var AccessToken="";
//console.log("signalR.LogLevel.Information",signalR.LogLevel.Information);
var connection;
async function start() {
try {
await connection.start();
console.log("connected");
} catch (err) {
console.log(err);
setTimeout(() => start(), 5000);
}
};
// Start the connection.
//start();
</script>
<style>
.groupBody{
display: flex;
}
.membersBody{
float:left;
display: none;
text-align: center;
}
.messagesBody{
float:right;
display: none;
text-align: center;
}
.members{
border:1px solid green;
}
.messages{
border:1px solid green;
text-align: left;
}
#groupListUl li{
clear: both;
}
#createGroup{
display: none;
}
#getGroups{
display: none;
}
</style>
<script>
//var AccessToken="";
var userData;
var groups;
var socketErrorNum=3;
$(document).ready(function(){
function layerLoad(){
return layer.load(1, {
shade: [0.1,'#fff'],
time:false
});
}
function wsSendData(jsonObj){
return JSON.stringify(jsonObj)+"\u001E";
}
function wsGetData(event){
var eData=event.data.substring(0,event.data.lastIndexOf("\u001E"));
eData=JSON.parse(eData);
return eData;
}
async function createWebSocket(socketUrl){
var res=await new Promise((resolve)=>{
var ws = new WebSocket(socketUrl);
ws.onopen = function (evt) {
console.log("Connection open ...");
var hello={"protocol":"json","version":1};
ws.send(wsSendData(hello));
resolve(true);
};
ws.onmessage = function (event) {
console.log("Received event Data",event.data);
var eData=wsGetData(event);
//new_group(只给自己推送)
if(eData.target=="new_group"){
var CreatedByName=eData.arguments[0].CreatedBy.Username;
var Name=eData.arguments[0].Name;
var ID=eData.arguments[0].CreatedBy.ID;
alert(CreatedByName+"创建了"+Name+"组");
/*
if(ID!=userData.ID){
}
*/
}
//user_joined_group(只给参与的两个对象推送)
if(eData.target=="user_joined_group"){
var Members=eData.arguments[0].Members;
var Username=Members[Members.length-1].Username;
var CreatedByName=eData.arguments[0].CreatedBy.Username;
var Name=eData.arguments[0].Name;
var ID=eData.arguments[0].CreatedBy.ID;
//alert(CreatedByName+"将"+Username+"加入了"+Name+"组");
layer.msg(Username+"加入了"+Name+"组");
if(ID!=userData.ID){
getGroups(true);
}
}
if(eData.target=="receive"){
var CreatedByName=eData.arguments[0].Member.Username;
var CreatedByID=eData.arguments[0].Member.UserID;
var Message=eData.arguments[0].Message;
var CreatedDate=eData.arguments[0].CreatedDate;
var GroupID=eData.arguments[0].GroupID;
var Group=groups.find((value,index,arr)=>{
return value.ID==GroupID;
})
if(typeof(Group)!="undefined"){
//var GroupName=Group.Name
layer.msg(CreatedByName+"在组"+Group.Name+"中发言:"+Message);
/*
var visible=$(`#groupListUl li[data_id=${GroupID}] div .members`).is(':visible');
if(visible){
seeGroupMessage(GroupID,false);
}
*/
if(CreatedByID!=userData.ID){
groupMessageAppend(GroupID,Message,{Username:CreatedByName,UserID:CreatedByID},CreatedDate);
}
}
}
console.log("Received eData",eData);
};
ws.onclose = function (evt) {
console.log("Connection closed.");
};
ws.onerror = function (event) {
console.log("Connection error.",event);
layer.msg("连接中断,正在尝试重新连接");
if(socketErrorNum>0){
socketErrorNum--;
createWebSocket(socketUrl);
}
else{
socketErrorNum=0;
login(userData.Username);
}
};
});
return res;
}
async function createWssConnect(){
var load = layerLoad();
var data=await new Promise((resolve=>{
$.ajax({
url:"index.php?api=createWssConnect",
type:'post',
dataType:'json',
data:{AccessToken:userData.AccessToken},
success:function(data){
console.log("createWssConnect",data);
layer.close(load);
resolve(data);
},
error:function(xmlHttpRequest,info,exception){
layer.close(load);
layer.msg(info);
}
});
}));
var socketUrl=`wss://${data.socketDomain}/chat?id=${data.connectionId}&access_token=${userData.AccessToken}`;
createWebSocket(socketUrl);
}
async function getGroups(updateUi){
var load = layerLoad();
var data=await new Promise((resolve=>{
$.ajax({
url:"index.php?api=getGroups",
type:'post',
dataType:'json',
data:{AccessToken:userData.AccessToken},
success:function(data){
console.log("getGroups",data);
resolve(data);
}});
}));
if(updateUi==true){
var lis="";
for(var i in data){
lis+=`<li data_id="${data[i].ID}">${data[i].Name}(${data[i].ID})<button class="seeGroup">查看组</button><button class="seeGroupMember">查看組成員</button><button class="seeGroupMessage">查看組消息</button><button class="addUserToGroup">添加新成員</button><button class="leaveGroup">离开该组</button>
<div class="groupBody">
<div class="membersBody">
<h2>成员列表</h2>
<ul class="members"></ul>
</div>
<div class="messagesBody">
<h2>消息列表</h2>
<ul class="messages"></ul>
<button class="sendMessageToGroup">向組内發送消息</button>
</div>
</div>
</li>`;
}
$("#groupListUl").html(lis);
}
layer.close(load);
groups=data;
return data;
}
function groupMessageAppend(groupId,Message,Member,CreatedDate){
var messages=$(`#groupListUl li[data_id=${groupId}] .groupBody .messagesBody .messages`);
var li=`<li>${Member.Username}(${Member.UserID}):${Message} ${moment(CreatedDate).format('YYYY-MM-DD HH:mm:ss')}</li>`;
console.log("groupMessageAppend li",li);
console.log("groupMessageAppend messages",messages);
messages.append(li);
}
async function seeGroupMember(groupId,obj){
var load = layerLoad();
$.ajax({
url:"index.php?api=seeGroup",
type:'post',
dataType:'json',
data:{AccessToken:userData.AccessToken,groupId:groupId},
success:function(data){
console.log("seeGroup",data);
var lis="";
var Members=data.Members;
for(var i in Members){
lis+=`<li>${Members[i].Username}(${Members[i].UserID})---${Members[i].Role}<button data_gid="${groupId}" data_mid="${Members[i].UserID}" class="delUserFromGroup">踢出该组</button></li>`;
}
if(lis){
var membersBody=$(`#groupListUl li[data_id=${groupId}] .groupBody .membersBody`);
var members=membersBody.find(".members");
console.log("members",members);
console.log("lis",lis);
members.html(lis);
membersBody.show();
}
console.log("seeGroupMember close");
layer.close(load);
}});
}
async function seeGroupMessage(groupId,obj){
var load = layerLoad();
$.ajax({
url:"index.php?api=getGroupMessage",
type:'post',
dataType:'json',
data:{AccessToken:userData.AccessToken,groupId:groupId},
success:function(data){
console.log("getGroupMessage",data);
_.reverse(data);
var lis="";
for(var i in data){
lis+=`<li>${data[i].Member.Username}(${data[i].Member.UserID}):${data[i].Message} ${moment(data[i].CreatedDate).format('YYYY-MM-DD HH:mm:ss')}</li>`;
}
if(lis){
var messagesBody=$(`#groupListUl li[data_id=${groupId}] .groupBody .messagesBody`);
var messages=messagesBody.find(".messages");
messages.html(lis);
messagesBody.show();
}
layer.close(load);
}});
}
//登錄
async function login(Username){
if(!Username) Username=prompt("Username","");
if(!Username) return;
var load = layerLoad();
var data=await new Promise((resolve => {
$.ajax({
url:"index.php?api=login",
type:'post',
dataType:'json',
data:{Username:Username},
success:function(data){
console.log("data",data);
resolve(data);
},
error:function(xmlHttpRequest,info,exception){
layer.close(load);
layer.msg(info);
}});
}));
userData=data;
await createWssConnect();
await getGroups(true);
var userInfo=`
<li>用戶名:${data.Username}</li>
<li>用戶id:${data.ID}</li>
`;
$("#userInfo").html(userInfo);
$("#createGroup").show();
$("#getGroups").show();
layer.close(load);
}
$("#login").click(function(){
login("");
});
//訂閲
$("#subscribe").click(function(){
$.ajax({
url:"index.php?api=subscribe",
type:'post',
dataType:'json',
data:{AccessToken:userData.AccessToken},
success:function(data){
console.log("subscribe",data);
}});
});
//getGroups
$("#getGroups").click(function(){
getGroups(true);
});
//createGroup
$("#createGroup").click(function(){
var Name=prompt("Name","");
var load = layerLoad();
$.ajax({
url:"index.php?api=createGroup",
type:'post',
dataType:'json',
data:{AccessToken:userData.AccessToken,Name:Name},
success:function(data){
console.log("createGroup from api",data);
layer.close(load);
getGroups(true);
}});
});
//sendMessageToGroup
$("#groupListUl").on("click",".sendMessageToGroup",function(){
var groupId=$(this).parents("li").attr("data_id");
/*
var membersBody=$(`#groupListUl li[data_id=${groupId}] .groupBody .membersBody`);
var visible=membersBody.is(':visible');
if(visible){
}
*/
var Message=prompt("Message","");
if(!Message) return;
var that=$(this);
var load = layerLoad();
$.ajax({
url:"index.php?api=sendMessageToGroup",
type:'post',
dataType:'json',
data:{AccessToken:userData.AccessToken,Message:Message,groupId:groupId},
success:function(data){
console.log("sendMessageToGroup",data);
layer.close(load);
if(typeof(data.Code)!="undefined"){
return layer.msg(data.Message);
}
//seeGroupMember(groupId,that);
//seeGroupMessage(groupId,that);
groupMessageAppend(groupId,Message,{UserID:userData.ID,Username:userData.Username},new Date());
},
error:function(xmlHttpRequest,info,exception){
return layer.msg(info);
}});
})
//seeGroup
$("#groupListUl").on("click",".seeGroup",function(){
var groupId=$(this).parent().attr("data_id");
seeGroupMember(groupId,$(this));
seeGroupMessage(groupId,$(this));
});
//seeGroupMember
$("#groupListUl").on("click",".seeGroupMember",function(){
var groupId=$(this).parent().attr("data_id");
seeGroupMember(groupId,$(this));
});
//seeGroupMessage
$("#groupListUl").on("click",".seeGroupMessage",function(){
var groupId=$(this).parent().attr("data_id");
seeGroupMessage(groupId,$(this));
});
//addUserToGroup
$("#groupListUl").on("click",".addUserToGroup",function(){
var UserID=prompt("UserID","");
if(!UserID) return;
var groupId=$(this).parent().attr("data_id");
var that=$(this);
var load = layerLoad();
$.ajax({
url:"index.php?api=addUserToGroup",
type:'post',
dataType:'json',
data:{AccessToken:userData.AccessToken,UserID:UserID,groupId:groupId},
success:function(data){
console.log("addUserToGroup",data);
if(typeof(data.errors)!="undefined"){
layer.close(load);
return layer.msg(data.title);
}
seeGroupMember(groupId,that);
}});
});
//leaveGroup
$("#groupListUl").on("click",".leaveGroup",function(){
var groupId=$(this).parent().attr("data_id");
var that=$(this);
var load = layerLoad();
$.ajax({
url:"index.php?api=leaveGroup",
type:'post',
dataType:'json',
data:{AccessToken:userData.AccessToken,groupId:groupId},
success:function(data){
console.log("leaveGroup",data);
layer.close(load);
getGroups(true);
}});
});
//delUserFromGroup
$("#groupListUl").on("click",".delUserFromGroup",function(){
var groupId=$(this).attr("data_gid");
var userId=$(this).attr("data_mid");
var that=$(this);
var load = layerLoad();
$.ajax({
url:"index.php?api=delUserFromGroup",
type:'post',
dataType:'json',
data:{AccessToken:userData.AccessToken,groupId:groupId,userId:userId},
success:function(data){
console.log("delUserFromGroup",data);
if(typeof(data.Code)!="undefined"){
layer.close(load);
return layer.msg(data.Message);
}
console.log("delUserFromGroup close");
layer.close(load);
seeGroupMember(groupId,false);
},
error:function(xmlHttpRequest,info,exception){
layer.close(load);
return layer.msg(info);
}});
});
//addUser
$("#addUserView").click(function(){
layer.open({
type: 1,
skin: 'layui-layer-rim', //加上边框
area: ['420px', '240px'], //宽高
content: `
<div class="registerUser">
<div>Mobile:<input name="Mobile"></div>
<div>Email:<input name="Email"></div>
<div>FirstName:<input name="FirstName"></div>
<div><button id="addUser">提交</button></div>
</div>
`
});
});
$("body").on("click","#addUser",function(){
var Mobile=$("input[name=Mobile]").val();
var Email=$("input[name=Email]").val();
var FirstName=$("input[name=FirstName]").val();
$.ajax({
url:"index.php?api=addUser",
type:'post',
dataType:'json',
data:{AccessToken:userData.AccessToken,Mobile:Mobile,Email:Email,FirstName:FirstName},
success:function(data){
console.log("addUser",data);
if(typeof(data.Code)!="undefined"){
return alert(data.Message);
}
//getGroups();
}});
});
//getUser
$("#getUser").click(function(){
$.ajax({
url:"index.php?api=getUser",
type:'post',
dataType:'json',
data:{AccessToken:userData.AccessToken},
success:function(data){
console.log("getUser",data);
if(typeof(data.Code)!="undefined"){
return alert(data.Message);
}
}});
});
});
</script>
</head>
<body>
<button id="login">登錄</button>
<!--
<button id="adminLogin">管理員登錄</button>
-->
<!--
<button id="subscribe">訂閲</button>
-->
<button id="createGroup">創建組</button>
<button id="getGroups">獲取組列表</button>
<!--
<button id="sendMessageToGroup">向組内發送消息</button>
-->
<!--
<button id="getNewMessage">查看新消息</button>
-->
<!--
<button id="addUserView">添加用戶(注冊用戶)</button>
-->
<!--
<button id="getUser">獲取用戶配置</button>
-->
<h1>用戶信息</h1>
<ul id="userInfo">
</ul>
<h1>組列表</h1>
<ul id="groupListUl">
</ul>
<!--
<h1>消息列表</h1>
<ul id="messageListUl">
</ul>
-->
</body>
</html>
Php做为中间层咯
<?php
function postUrl($url, $postData = false, $header = false) {
$ch = curl_init($url);
curl_setopt($ch,CURLOPT_HEADER,0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); //返回数据不直接输出
curl_setopt($ch, CURLOPT_ENCODING, "gzip"); //指定gzip压缩
//add header
if(!empty($header)) {
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
}
//add ssl support
if(substr($url, 0, 5) == 'https') {
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); //SSL 报错时使用
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); //SSL 报错时使用
}
//add 302 support
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
//curl_setopt($ch,CURLOPT_COOKIEFILE, $this->lastCookieFile); //使用提交后得到的cookie数据
//add post data support
if(!empty($postData)) {
curl_setopt($ch,CURLOPT_POST, 1);
curl_setopt($ch,CURLOPT_POSTFIELDS, $postData);
}
try {
$content = curl_exec($ch); //执行并存储结果
} catch (\Exception $e) {
$this->_log($e->getMessage());
}
$curlError = curl_error($ch);
if(!empty($curlError)) {
$this->_log($curlError);
}
curl_close($ch);
return $content;
}
function getUrl($url, $header = false) {
$ch = curl_init($url);
curl_setopt($ch,CURLOPT_HEADER,0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); //返回数据不直接输出
curl_setopt($ch, CURLOPT_ENCODING, "gzip"); //指定gzip压缩
//add header
if(!empty($header)) {
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
}
//add ssl support
if(substr($url, 0, 5) == 'https') {
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); //SSL 报错时使用
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); //SSL 报错时使用
}
//add 302 support
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
// curl_setopt($ch,CURLOPT_COOKIEFILE, $this->lastCookieFile); //使用提交后得到的cookie数据
try {
$content = curl_exec($ch); //执行并存储结果
} catch (\Exception $e) {
$this->_log($e->getMessage());
}
$curlError = curl_error($ch);
if(!empty($curlError)) {
$this->_log($curlError);
}
curl_close($ch);
return $content;
}
function delUrl($url,$header) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
//设置头
curl_setopt($ch, CURLOPT_HTTPHEADER, $header); //设置请求头
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36');
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);//SSL认证。
$output = curl_exec($ch);
curl_close($ch);
return $output;
}
$TenantCode="T0001";
$admin="T0001";
$SecurityToken="sh6ZaT7VtuQdRdg51bHu78TRKZE8wp";
$apiUrl="https://api.testletsgomessenger.com/api/v1";
$socketDomain="socket.testletsgomessenger.com";
if(isset($_GET["api"])){
//登錄
if($_GET["api"]=="login"){
$data=array(
"Type"=>"generate_token",
"Username"=>$_POST["Username"],
"TenantCode"=>$TenantCode,
"SecurityToken"=>$SecurityToken
);
$response=postUrl($apiUrl."/User/Login",json_encode($data),array("Content-type:application/json-patch+json"));
echo $response;
exit();
}
//adminLogin
if($_GET["api"]=="adminLogin"){
$data=array(
"Type"=>"generate_token",
"Username"=>$admin,
"TenantCode"=>$TenantCode,
"SecurityToken"=>$SecurityToken
);
$response=postUrl($apiUrl."/User/Login",json_encode($data),array("Content-type:application/json-patch+json"));
echo $response;
exit();
}
if($_GET["api"]=="subscribe"){
$sdata=array(
"Endpoint"=>"https://fcm.googleapis.com/fcm/send/db8l5GEwVgI:APA91bH7KuWkKm-hW8oTgrgScV23gOVJ73nuL7KroEV-OapN-U97Moidy85vWzYAQf3f25SpZW1BXDlPPZyrR_qS0042gFQirEgSbZtd1ibTJ6yy7QxNMlQC0s3cTKTZkW9eI1r22UsA",
"ExpirationTime"=>null,
"Keys"=>array(
"p256dh"=>"BKEjg6kco2_isou-usqm0fnSDkJLsDvSeNrthgI1TK6zcqBpME7WBbHAB-3K-w8bQ-v9KJ-O6L4a4Q7H60DCg-o",
"auth"=>"htZLIenRp5sAPeLYAWY0DA"
)
);
$data=array(
"DeviceToken"=>$_POST["AccessToken"],
"TokenPlatform"=>"Web",
"DevicePlatform"=>"web",
"WebPushRequestJson"=>json_encode($sdata)
);
$response=postUrl($apiUrl."/User/Subscription/PushNotification",json_encode($data),array("Content-type:application/json-patch+json"));
echo $response;
exit();
}
//getGroups
if($_GET["api"]=="getGroups"){
$response=getUrl($apiUrl."/Group?limit=10",array("accept:application/json","Authorization:Bearer ".$_POST["AccessToken"]));
echo $response;
exit();
}
//createGroup
if($_GET["api"]=="createGroup"){
$data=array(
"Name"=>$_POST["Name"]
);
$response=postUrl($apiUrl."/Group",json_encode($data),array("accept:application/json","Content-type:application/json-patch+json","Authorization:Bearer ".$_POST["AccessToken"]));
echo $response;
exit();
}
//sendMessageToGroup
if($_GET["api"]=="sendMessageToGroup"){
$data=array(
"Message"=>$_POST["Message"]
);
$response=postUrl($apiUrl."/Group/".$_POST["groupId"]."/Messages",json_encode($data),array("accept:application/json","Content-type:application/json-patch+json","Authorization:Bearer ".$_POST["AccessToken"]));
if(empty($response)){
$response=array("content"=>"");
$response=json_encode($response);
}
echo $response;
exit();
}
//getGroupMessage
if($_GET["api"]=="getGroupMessage"){
$response=getUrl($apiUrl."/Group/".$_POST["groupId"]."/Messages?limit=50",array("accept:application/json","Authorization:Bearer ".$_POST["AccessToken"]));
echo $response;
exit();
}
//addUserToGroup
if($_GET["api"]=="addUserToGroup"){
$data=array(
array("UserID"=>$_POST["UserID"])
);
$response=postUrl($apiUrl."/Group/".$_POST["groupId"]."/User",json_encode($data),array("accept:application/json","Content-type:application/json-patch+json","Authorization:Bearer ".$_POST["AccessToken"]));
echo $response;
exit();
}
//delUserFromGroup
if($_GET["api"]=="delUserFromGroup"){
$response=delUrl($apiUrl."/Group/".$_POST["groupId"]."/User/".$_POST["userId"],array("accept:application/json","Authorization:Bearer ".$_POST["AccessToken"]));
if(empty($response)){
$response=array("content"=>"");
$response=json_encode($response);
}
echo $response;
exit();
}
//seeGroup
if($_GET["api"]=="seeGroup"){
$response=getUrl($apiUrl."/Group/".$_POST["groupId"],array("accept:application/json","Authorization:Bearer ".$_POST["AccessToken"]));
echo $response;
exit();
}
//addUser
if($_GET["api"]=="addUser"){
$data=array(
"Mobile"=>$_POST["Mobile"],
"Email"=>$_POST["Email"],
"FirstName"=>$_POST["FirstName"],
"TenantCode"=>$TenantCode,
"SecurityToken"=>$SecurityToken
);
$response=postUrl($apiUrl."/User",json_encode($data),array("accept:application/json","Content-type:application/json-patch+json","Authorization:Bearer ".$_POST["AccessToken"]));
echo $response;
exit();
}
//getUser
if($_GET["api"]=="getUser"){
$response=getUrl($apiUrl."/User",array("accept:application/json","Authorization:Bearer ".$_POST["AccessToken"]));
echo $response;
exit();
}
//createWssConnect
if($_GET["api"]=="createWssConnect"){
$data=array();
$response=postUrl("https://".$socketDomain."/chat/negotiate",json_encode($data),array("Authorization:Bearer ".$_POST["AccessToken"]));
$data=json_decode($response,true);
$data["socketDomain"]=$socketDomain;
echo json_encode($data);
exit();
}
//delGroup
if($_GET["api"]=="leaveGroup"){
$response=delUrl($apiUrl."/Group/".$_POST["groupId"]."/User/@me",array("accept:application/json","Authorization:Bearer ".$_POST["AccessToken"]));
if(empty($response)){
$response=array("content"=>"");
$response=json_encode($response);
}
echo $response;
exit();
}
}
关键字词:LetsgoMessaging,SignalR,php,websocket,js,javascript
上一篇:laravel解决跨域