1. 多人聊天
2. 多房间
3. 传输信息加密,基于base64+字符替换实现
4. 基于长连接读取(ngnix使用PHP sleep有问题)
5. 支持昵称自定义,并使用浏览器保存。
6. 需要在程序目录创建chat_data文件夹,用来存储历史聊天数据(仅保留最近1分钟)
<?php
// 系统入口
date_default_timezone_set("PRC");
error_reporting(E_ALL & ~E_NOTICE);
set_time_limit(30);
$room = $_REQUEST['room'] ?? 'default';
$type = $_REQUEST['type'] ?? 'enter';
$type = strtolower($type);
// 创建新房间
function newRoom($room)
{
$room_file = './chat_data/' . $room . '.txt';
$key_list = array_merge(range(48, 57), range(65, 90), range(97, 122), [43, 47, 61]);
$key1_list = $key_list;
shuffle($key1_list);
$room_data = [
'name' => $room,
'encode' => array_combine($key_list, $key1_list),
'list' => [],
'time' => date('Y-m-d H:i:s'),
];
file_put_contents($room_file, json_encode($room_data));
}
// 获取消息列表
function getMsg($room, $last_id)
{
$room_file = './chat_data/' . $room . '.txt';
$msg_list = [];
$room_data = json_decode(file_get_contents($room_file), true);
$list = $room_data['list'];
// 清楚1分钟前消息
$cur_list = [];
$del_time = date('Y-m-d H:i:s', time() - 60);
foreach ($list as $r)
{
if ($r['time'] > $del_time)
{
$cur_list[] = $r;
}
}
if (count($cur_list) != count($list) && count($list) > 0)
{
$room_data['list'] = $cur_list;
file_put_contents($room_file, json_encode($room_data));
}
// 查找最新消息
foreach ($list as $r)
{
if ($r['id'] > $last_id)
{
$msg_list[] = $r;
}
}
return $msg_list;
}
$room_file = './chat_data/' . $room . '.txt';
switch ($type)
{
case 'enter': // 进入房间
break;
case 'get': // 获取消息
$last_id = $_REQUEST['last_id'];
$msg_list = [];
if (strpos($_SERVER['SERVER_SOFTWARE'], 'nginx') !== false)
{
$msg_list = getMsg($room, $last_id);
}
else
{
// nginx 使用sleep将会把整个网站卡死
for ($i=0; $i<20; $i++)
{
$msg_list = getMsg($room, $last_id);
if (!empty($msg_list))
{
break;
}
usleep(500000);
}
}
echo json_encode(['result' => 'ok', 'list' => $msg_list]);
break;
case 'send': // 发送消息
$item = [
'id' => round(microtime(true) * 1000),
'user' => $_REQUEST['user'],
'content' => $_REQUEST['content'],
'time' => date('Y-m-d H:i:s'),
];
$room_data = json_decode(file_get_contents($room_file), true);
$room_data['list'][] = $item;
file_put_contents($room_file, json_encode($room_data));
echo json_encode(['result' => 'ok']);
break;
case 'new': // 新建房间
mt_srand();
$room = strtoupper(md5(uniqid(mt_rand(), true)));
$room = substr($room, 0, 10);
newRoom($room);
header('Location:chat.php?room=' . $room);
break;
default:
echo 'ERROR:no type!';
break;
}
if ($type != 'enter')
{
exit;
}
if (!file_exists($room_file))
{
if ($room == 'default')
{
newRoom($room);
}
else
{
echo 'ERROR:room not exists!';
exit;
}
}
$room_data = json_decode(file_get_contents($room_file), true);
unset($room_data['list']);
$user = 'User' . str_pad((time() % 99 + 1), 2, '0', STR_PAD_LEFT);
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>PHP 在线聊天</title>
<link href="https://lib.baomitu.com/normalize/latest/normalize.min.css" rel="stylesheet">
<style>
/* css style */
body{
padding:0 10px;
}
.divMain{
font-size:14px;
line-height: 2;
}
#divList span{
color:gray;
}
</style>
<script src="https://lib.baomitu.com/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>
<h1>PHP 在线聊天</h1>
<div class="divMain">
<?php if ($room != 'default'){?>
<div>复制网页链接发给朋友一起聊天!</div>
<?php } ?>
昵称:<input id="txtUser" type="text" maxlength="50" value="<?=$user?>" />
<button onclick="$('#divList').html('');">清空</button>
<a href="chat.php?type=new">新房间</a>
<br>
内容:<input id="txtContent" type="text" value="" maxlength="100" style="width: 300px;" />
<button onclick="sendMsg();">发送</button>
<hr>
<div id="divList"></div>
</div>
<!--使用worker获取消息数据,注意ngnix会阻塞整个进程-->
<script id="worker" type="app/worker">
var room = '<?=$room_data['name']?>';
var isBusy = false;
var lastId = -1;
var urlBase = '';
addEventListener('message', function (evt) {
urlBase = evt.data;
}, false);
setInterval(function(){
if (isBusy) return;
isBusy = true;
let url = new URL( 'chat.php?type=get&room=' + room + '&last_id=' + lastId, urlBase );
fetch(url)
.then(res=>res.json())
.then(function(res){
isBusy = false;
if (res.list.length > 0)
{
lastId = res.list[res.list.length-1].id;
}
self.postMessage(res);
})
.catch(function(err){
isBusy = false;
});
}, 1000);
</script>
<script>
var blob = new Blob([document.querySelector('#worker').textContent]);
var url = window.URL.createObjectURL(blob);
var worker = new Worker(url);
worker.onmessage = function (e) {
let res = e.data;
let html = '';
for (let k in res.list)
{
let r = res.list[k];
html = '<div><span>' + r.time + '</span> <b>' + r.user + ':</b> ' + decodeContent(r.content) + '</div>' + html;
}
$('#divList').prepend(html);
};
worker.postMessage(document.baseURI);
</script>
<script>
var room = <?=json_encode($room_data)?>;
room['decode'] = {};
for (let k in room.encode)
{
room['decode'][room.encode[k]] = k;
}
// 发送消息
var lastSendTime = 0;
function sendMsg()
{
let user = $('#txtUser').val().trim();
let content = $('#txtContent').val().trim();
if (content == '')
{
return;
}
if (user == '')
{
alert('昵称不能为空');
return;
}
window.localStorage.setItem('r_' + room.name, user);
// 限制0.5秒内仅允许发送1条消息
let curTime = new Date().getTime();
if (curTime - lastSendTime < 500)
{
return;
}
lastSendTime = curTime;
$.ajax({
url:'chat.php?type=send',
data:{room:room.name, user:user, content:encodeContent(content)},
type:'POST',
dataType:'json',
success:function(){
$('#txtContent').val('');
$('#txtContent').focus();
},
});
}
// 消息加密
function encodeContent(content)
{
content = encodeURIComponent(content);
content = window.btoa(content);
let str = '';
for (let i=0; i<content.length; i++)
{
str += String.fromCharCode(room.encode[content.charCodeAt(i)]);
}
return str;
}
// 消息解密
function decodeContent(content)
{
let str = '';
for (let i=0; i<content.length; i++)
{
str += String.fromCharCode(room.decode[content.charCodeAt(i)]);
}
str = window.atob(str);
str = decodeURIComponent(str);
return str;
}
$(function(){
let userName = window.localStorage.getItem('r_' + room.name);
if (userName)
{
$('#txtUser').val(userName);
}
$('#txtContent').keydown(function(e){
if(e.keyCode==13){
event.preventDefault();
sendMsg();
}
});
$('#txtContent').val('😀 我来了!');
sendMsg();
});
</script>
</body>
</html>