ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

从零搭建PHP聊天室:ChatNet源码部署、数据库设计与私聊二次开发实战

从零搭建PHP聊天室:ChatNet源码部署、数据库设计与私聊二次开发实战 简介聊天室系统是Web开发中的经典应用场景其核心在于如何用PHP与MySQL构建实时互动的消息收发机制。部署一套聊天室源码不仅涉及Web服务器环境选型、PHP扩展配置、数据库字符集设计还需要理解公共房间与私聊消息的底层数据表结构。本文从技术原理出发先厘清环境部署与数据库连接的基本要点再深入消息表的字段设计、增量轮询拉取策略及在线状态维护最后结合完整汉化版ChatNet V1.11源码演示如何实现导航栏未读提醒、已读回执等二次开发需求。无论你是站长、企业内部工具开发者还是想快速搭建临时活动聊天页的技术人员都能从这套实战路径中获得可落地的部署方案与改码思路。1. ChatNet 聊天室源码到底能干什么公共房间、私聊和私有化部署一条线ChatNet V1.11 这个完整汉化版的聊天室源码属于典型的源码建站型项目下载下来的不是一个在线 SaaS 账号而是一套可以完整部署到自己服务器上的 PHP 程序。它解决的诉求很直接——你想在自己的站点上开一个公共聊天室让用户同时在一个房间里发言又希望用户之间能开启一对一的私人聊天同时聊天记录、用户数据都攥在自己手里不受第三方平台约束。这套源码打包了用户注册登录、公共房间列表、消息发送、私聊窗口、表情和头像这些最小可用的功能闭环适合站长、企业内部沟通工具、活动运营临时聊天页这类场景。我第一次拿到这包源码时压缩包里就是一个站点目录加一个 SQL 文件没有安装向导所以整个部署思路可以总结为四步选环境、导库、改配置、调权限。2. 把 ChatNet 部署起来环境选型与最小可用命令这类 PHP 源码最难装的往往不是聊天业务本身而是环境差异。ChatNet 的压缩包绝大多数情况下是 PHP MySQL 的组合Windows 上用 phpstudyLinux 上用宝塔或手工搭的 LNMP都能跑。但版本选不对后面全是白费功夫。2.1 环境怎么选PHP 版本和 Web 服务器是第一个关卡我先给结论不要一上来就装最新版 PHP先打开源码根目录里的配置文件扫一眼看它用的是哪种数据库封装。V1.9 这个时期的包很多还在用mysql_connect系列函数那是 PHP 5 时代的写法PHP 7 开始这些函数全部被移除直接装上去连数据库连接对象都建不起来。V1.10 和 V1.11 的包多数改成了 PDO 或 mysqli用 PHP 7.2 到 7.4 跑最省心。PHP 8 也不是不行但老代码里经常有把参数默认类型写死的情况PHP 8 对隐式类型转换收紧了可能在一个看似无关的传参地方崩掉。Web 服务器选 Nginx 还是 Apache我的建议是先用本地最熟悉的那一个。Windows 上直接 phpstudy 一键起 Apache MySQL 最快因为 Apache 默认允许.htaccess覆盖规则ChatNet 老版本多半带了 rewrite 规则Apache 零配置就能跑。Nginx 需要手动补一条伪静态规则也不算麻烦。关键是 PHP 扩展别漏pdo_mysql、mysqli、mbstring、gd这四个是聊天室最常见的依赖。头像上传和表情图都要用到 gd漏了会导致头像裁剪功能直接白屏。部署前可以先确认一次扩展情况省得后面反复翻车# 查看 PHP 已加载的扩展和版本 php -v php -m | grep -E pdo_mysql|mysqli|mbstring|gd命令输出里如果四个扩展名都在环境这一关基本就过了。如果发现缺少扩展Linux 上用包管理器安装对应扩展并重启 PHP 服务Windows 上打开 phpstudy 的 PHP 设置页面勾选扩展改完记得重载配置。这个检查动作只需要一分钟但能省下一晚上的排查时间。2.2 解压、导库、改配置三条命令跑通最小部署假设你已经在服务器上拿到了 ChatNet V1.11 汉化版的压缩包我习惯在 Linux 环境下操作命令按顺序走一遍。# 1. 解压到站点根目录注意保留压缩包里的目录结构 unzip ChatNet_V1.11_zh.zip -d /var/www/chatnet # 2. 先用 root 建好空库注意默认字符集 mysql -uroot -p -e CREATE DATABASE IF NOT EXISTS chatnet_db DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci; # 3. 导入数据库SQL 文件通常在源码根目录或 database 文件夹下 mysql -uroot -p chatnet_db /var/www/chatnet/chatnet.sql第一步的-d参数有个细节目标目录最好先建好避免解压出二级嵌套目录比如/var/www/chatnet/chatnet这种结构会让后面的站点路径配置绕一大圈。第二步建库时把字符集显式写成utf8mb4这一步很关键。聊天室里用户会发各种表情符号MySQL 老版本的utf8存不住四字节 Unicode导完库之后表情内容就会变成乱码。第三步导入前确认 SQL 文件内部有没有带USE语句如果带了库名对不上时会导入到别的库或者直接报错。导库完成之后去改配置文件。常见配置文件名字是config.php、include/config.php或者application/database.php取决于包用的什么框架。拿最常见的config.php举例。// config.php 里最核心的数据库配置段 define(DB_HOST, 127.0.0.1); define(DB_USER, chatnet_user); define(DB_PASS, 你的数据库密码); define(DB_NAME, chatnet_db); define(DB_PREFIX, tn_); // 表前缀导库时用的什么前缀就保持一致改配置时有三个高频坑第一DB_HOST 在本地写127.0.0.1比localhost更稳因为某些机器的localhost会优先解析成 IPv6 地址::1而 MySQL 只监听了 IPv4就会出现连接失败第二DB_PASS 两侧的引号必须是英文半角从 Word 文档里复制过来的全角引号会让 PHP 解析直接报错第三表前缀必须和 SQL 文件里的一致如果默认是tn_导库时没改就不要在配置里乱改前缀。接下来启动。Nginx 用户补一条伪静态规则保证聊天室的房间路由能正常解析。# Nginx 站点配置里的 location 段 location / { if (!-e $request_filename) { rewrite ^(.*)$ /index.php?s$1 last; } }这段规则的意思是请求的文件在磁盘上不存在时统一交给index.php处理。聊天室的房间地址、私聊会话地址都要依赖这套路由逻辑。Apache 用户则确认站点根目录有.htaccess文件没有就自己放一份内容只要包含RewriteEngine On开头的几行就能复用同一套逻辑。写完配置重启 PHP 和 Web 服务访问首页能看到跳转到登录页说明最基础的部署已经通了。如果页面一片空白大概率是 PHP 报错被屏蔽了此时去 PHP 配置文件里把display_errors临时设为On再刷新页面最上方输出的错误信息能直接告诉你缺哪个类、哪段语法有问题比翻日志猜原因高效很多。2.3 装完立刻做的三件收尾改管理员密码、目录权限、关调试部署起来只是第一步真正能对外用还要做三件收尾。第一件登录后台把默认管理员的密码换掉。很多聊天室源码的默认后台账号写在 SQL 文件里例如admin/admin123如果你不换等于把管理权限挂在公共场合。第二件设置目录权限。聊天室的头像上传目录、表情缓存目录通常需要写权限在 Linux 下执行# 先让 Web 用户拥有整个站点再放开运行目录写权限 chown -R www-data:www-data /var/www/chatnet chmod -R 755 /var/www/chatnet # 对 runtime、upload 这类目录放宽写权限 chmod -R 777 /var/www/chatnet/runtime chmod -R 777 /var/www/chatnet/upload注意最后的777只给运行时目录和上传目录不要对整个站点 777。整个站点放开写权限之后一旦有上传漏洞攻击者可以直接在站点根目录里落一个 PHP 文件后果是灾难性的。第三件关闭调试输出。找到config.php里的DEBUG或APP_DEBUG选项改成false。部署阶段开着能看到错误信息但是正式跑起来还开着错误信息会把数据库密码和绝对路径直接展示给访问者这是最容易被忽略的信息泄漏点。到这里,一套 ChatNet 已经能注册用户、进房间聊天了。接下来进入核心逻辑层看看公共聊天和私聊到底是怎么在数据表里转起来的。3. 公共聊天和私人聊天的核心逻辑消息表怎么设计、私聊怎么投递很多人在拿到源码后直接去改界面却不知道消息是怎么存的结果改完页面发现公共消息和私聊串了。ChatNet 这类源码里有一个常见设计公共聊天和私聊不拆两张表而是共用一张消息表用字段来区分消息类型。搞懂这个字段组合就搞懂了大半个系统的运转原理。3.1 一张消息表同时装公共消息和私聊room_id 与 to_uid 的分工公共聊天室的特点是所有人进同一个房间发言对房间内所有人可见私聊的特点是消息只有发送者和接收者两个人能看见。这两类消息放到同一张表里靠两个字段区分room_id和to_uid。room_id表示消息属于哪个公共房间to_uid表示这条消息是不是定向发给某个用户。我按这套源码最常见的表结构写了一个建表语句可以直接对照理解。-- 聊天室核心消息表公共消息和私聊共用 CREATE TABLE chat_message ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, room_id INT UNSIGNED NOT NULL DEFAULT 0 COMMENT 0私聊大于0公共房间ID, from_uid INT UNSIGNED NOT NULL COMMENT 发送者用户ID, to_uid INT UNSIGNED NOT NULL DEFAULT 0 COMMENT 私聊接收者ID0表示公共消息, content TEXT NOT NULL COMMENT 消息内容, msg_type TINYINT(1) NOT NULL DEFAULT 0 COMMENT 0文本1图片2表情, is_read TINYINT(1) NOT NULL DEFAULT 0 COMMENT 私聊已读标记, create_time INT UNSIGNED NOT NULL COMMENT Unix时间戳, PRIMARY KEY (id), KEY idx_room_time (room_id, id), KEY idx_to_uid_read (to_uid, is_read) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这个表设计里有三个关键点值得细看。第一个是room_id和to_uid的互补关系。公共消息的room_id是具体的房间编号比如 1 号房间to_uid保持为 0私聊消息反过来room_id固定为 0to_uid写接收者的用户 ID。查询的时候公共消息走room_id id索引私聊未读走to_uid is_read索引两类查询互不干扰。第二个是msg_type字段。聊天室的老版本源码经常把文本、图片、表情都存成纯文本通过msg_type告诉前端怎么渲染。图片消息的content存的是图片 URL表情消息的content存的是表情代码前端识别到msg_type2就把它替换成对应的表情图片。改动表情库时只要遵循这个协议前端就不需要改逻辑。第三个是is_read字段。公共消息不需要已读概念发送出来所有人拉取即可私聊必须知道对方有没有看到这个字段就是私聊未读数的数据来源。有细心的读者可能会问公共消息和私聊共用一张表数据量大了之后会不会查询变慢实际上一个聊天室系统日活几千时这张表一年也才几十万行加好两个组合索引MySQL 完全扛得住。真正容易翻车的是索引没建对导致每次查询都全表扫描。3.2 消息拉取策略公共房间用增量轮询私聊靠未读标记ChatNet 这类传统聊天室源码消息推送用的不是 WebSocket而是前端定时向后端拉取新消息。公共房间的拉取逻辑很标准前端记下当前已加载的最大消息 ID每次请求只拿大于这个 ID 的记录。这样省带宽也方便做消息历史翻页。下面这段 PHP 是核心接口最常见的写法。?php // api/public_messages.php — 公共房间增量拉取消息接口 $pdo new PDO(mysql:host127.0.0.1;dbnamechatnet;charsetutf8mb4, chatnet_user, 密码); $roomId (int)($_GET[room_id] ?? 1); $lastId (int)($_GET[last_id] ?? 0); // 前端上一次拿到的最大消息ID $stmt $pdo-prepare( SELECT * FROM chat_message WHERE room_id ? AND id ? ORDER BY id ASC LIMIT 100 ); $stmt-execute([$roomId, $lastId]); $rows $stmt-fetchAll(PDO::FETCH_ASSOC); header(Content-Type: application/json; charsetutf-8); echo json_encode($rows, JSON_UNESCAPED_UNICODE);这个接口的三个参数要理解清楚。room_id控制取哪个房间的消息last_id是增量拉取的游标前端每次拿到新的消息列表后把列表最后一条的id记下来下一次请求时传进来能保证拉到的都是新消息不会重复也不会漏LIMIT 100是一个安全上限防止某个房间刷屏太快导致一次返回的数据包过大。实际部署时如果想做历史翻页可以把条件改成id last_id ORDER BY id DESC LIMIT 50拿更早的消息逻辑和增量拉取正好相反。前端配合的轮询代码也很简单通常放在聊天页面的 JavaScript 中。设置一个定时器每 5 到 10 秒调一次上面的接口把返回的 HTML 片段追加到消息列表里。这个间隔是性能和人眼感知的折中5 秒内感觉不到延迟10 秒也能接受但小区宽带环境下 3 秒轮询会把服务器请求量翻三倍完全没必要。我把这个轮询间隔当作默认参数除非是活动大屏这种需要即时感的场景一般不会小于 4 秒。私聊的拉取逻辑则在公共房间之外多走一路只查room_id 0 AND to_uid 当前登录用户 AND is_read 0的消息。这个查询用到了idx_to_uid_read索引MySQL 能快速定位未读记录。私聊窗口打开时把对方发给当前用户的所有未读消息标记为已读导航栏上的未读数就立刻归零。这个已读逻辑在第六章还要继续改造先留一个钩子。3.3 在线状态和好友列表两张辅助表决定私聊体验公共聊天室只靠消息表就能转起来但私聊体验还依赖两个辅助功能好友关系和在线状态。ChatNet 的老版本里这两块往往没有做成独立模块而是挂在用户表和两张扩展表上。第一张是用户扩展表通常叫user_profile字段包括nickname、avatar、signature、last_online_time。其中last_online_time最关键聊天室判断一个用户在不在线绝大多数源码的做法不是维护一个长连接状态表而是每次用户刷新页面或轮询时把这个字段刷新成当前时间。判断在线的方法就是比较last_online_time和当前时间的差值小于 5 分钟就算在线。这个设计在轮询架构下非常务实因为它不需要额外的推送通道。第二张是好友关系表最简单的结构只有三个字段uid、friend_uid、status。私聊时先查这张表确认两个人有好友关系再决定是否放行消息。虽然 ChatNet 这类源码通常也允许在公共房间里点击用户头像直接发起私聊但好友表为后续做“只看好友消息”或“屏蔽某用户”留下了扩展位。在线状态和好友表的数据量都不会大一般不需要做缓存处理。但要注意last_online_time一定要和用户表的主键配合加普通索引否则私聊列表页按“在线状态”排序时每个用户行都要比较一次时间数据量稍大会拖慢响应。我用这套逻辑排查过不少聊天室源码很多慢查询都出在这个字段没建索引上。4. 汉化包与 V1.9 到 V1.11 的版本差异先看这三个文件再决定升不升汉化版源码最麻烦的地方不是中文翻译而是你拿到手的版本和网上流传的 V1.9 之间可能隔了一两次底层更新。改动点如果没搞清楚就升级轻则界面错乱重则数据库迁移失败。这一章我把 V1.9 到 V1.11 区间内最常被改动的三个方向理清楚。4.1 完整汉化版真正改掉的东西语言文件、日期格式和默认时区所谓的“完整汉化版”大多数情况下不是把代码里所有字符串一个个替换而是把文案抽离到了语言目录让界面显示内容变得可控。ChatNet 这类源码的汉化包通常包含三个文件层面一是根目录lang文件夹下的中文语言文件比如zh_CN.php二是 JavaScript 文件里写死的提示语比如“正在加载”“发送失败”这类前端弹窗三是模板文件里的日期格式和时区默认值。我拿到汉化版后的第一个动作是去lang目录里搜两个关键词date_format和timezone。如果语言文件里默认时区还是Europe/Moscow或UTC说明汉化者只翻译了界面文字没有处理时区。这时候即使程序跑通了公共消息列表里的时间也会比北京时间早 5 到 8 个小时用户看到的消息时间完全对不上。解决方法是把config.php里的默认时区改成PRC或者Asia/Shanghai同时把语言文件里的date_format调成Y-m-d H:i:s这种中国人习惯的格式。第二个需要检查的地方是模板文件里的编码声明。老版本模板写的是meta charsetgb2312汉化版一般会改成utf-8。如果某个页面打开后是乱码先看这个文件头部声明的字符集再看数据库连接有没有加SET NAMES utf8mb4。这两处不一致时数据库存进去的是正常中文读出显示也正常但页面 HTML 声明的字符集不对浏览器就会按错误编码渲染看起来全是问号。第三个坑藏在表情和图片资源文件里。汉化包通常会把第三方版权的水印图片替换掉或者删除原作者英文站点的推广位。如果你发现聊天室里的表情图裂了大概率是语言包更新只改了 PHP 文件没有把对应的 images 目录一起覆盖。这时把汉化包里的images、static目录整体重新上传一次即可不要只覆盖个别文件。4.2 V1.9 到 V1.11 的版本差异一张对照表看清改动重点我经常需要同时维护老版本和新版本源码对比之后发现V1.9 到 V1.11 的改动基本集中在私聊提醒、表情库和移动端适配这三个维度。下面这张表是我拿到新版本源码时最先核对的项目你可以把它当作自查清单。关注点V1.9 常见形态V1.10/V1.11 常见形态升级注意事项私聊未读提示轮询公共接口时顺带返回未读数页面收到后靠 alert 提醒私聊未读数拆成独立接口导航栏可单独拉取前端升级后要清缓存否则还在调旧接口表情/图片消息表情存成文本代码前端解析代码换图图片靠外链 URL内置表情库文件图片走上传目录老消息里的表情代码要校验存的是旧格式就替换移动端适配固定宽度表格布局手机上要缩放才能看响应式模板、房间列表可折叠升级后重点测登录和私聊弹窗用户资料字段只有用户名、密码字段无头像增加头像、个性签名等资料字段导库前必须跑升级 SQL否则前端会报字段不存在这张表不是某个版本的官方发布说明而是我拿到新包之后做差异对比的定位方法。里面每一条都值得注意私聊未读接口拆出来后旧的轮询前端如果不改导航栏永远不会显示未读数表情库内置后老用户发过的表情代码可能和新的表情库对不上历史消息里显示成一串方括号加数字移动端响应式改造则意味着模板文件大面积重写升级时要重点回归私聊弹窗这个最复杂的交互。4.3 老项目怎么升 V1.11先备份再分步覆盖如果你手上跑着 V1.9 的老聊天室要升级到 V1.11我建议按这个顺序操作每一步都不要跳。第一步备份数据库和整个站点目录。聊天室的数据库虽然不大但用户表和消息表是累积数据升级 SQL 执行错了没法回滚。用命令把两样东西都打包带走# 备份数据库到 home 目录 mysqldump -uroot -p chatnet_db /root/chatnet_db_bak.sql # 备份整个站点目录排除 runtime 缓存可以加快打包速度 tar czf /root/chatnet_site_bak.tar.gz --excluderuntime/* /var/www/chatnet第二步仔细阅读新包里的update.txt或upgrade.sql。大多数版本升级都会附带一个数据库增量脚本里面是新增字段和新增表的ALTER TABLE语句。如果直接覆盖新源码而不执行这个脚本前端代码会去查一个不存在的字段报错信息往往是Unknown column xxx in field list而这个报错只在特定页面触发排查起来很容易绕圈子。第三步覆盖站点文件。建议保留config.php和upload目录其余文件全部用新包覆盖。覆盖时如果用了tar -xzf解压注意保留文件权限解压后重新执行一遍 2.3 节里的chown和chmod避免因为权限不对导致上传功能失效。最后一步是回归测试。升级完先用管理员账号登录逐个房间发一条消息再开两个浏览器测试私聊。没问题的标准是公共消息所有人都能立刻看到私聊消息在另一个浏览器里能收到未读提示且在线用户列表能正确显示。这套流程走下来30 分钟能升完一个中小型聊天室。5. ChatNet 部署与汉化的常见问题和排查五条血泪经验凡是老 PHP 项目部署时总能遇到一堆看似离奇的问题。这里整理五条我实际排查过的典型问题按“现象→原因→解决”的方式写照着做能省下不少时间。5.1 中文变成一排问号数据库里也是问号现象用户昵称、聊天消息中文显示成?????数据库表里存的也是问号。原因建库时用了latin1或utf8字符集而程序连接时用的utf8mb4。MySQL 的utf8字符集最多存 3 字节字符中文不在话下但聊天里常见的 emoji 表情是 4 字节编码拉不开就会补偿成问号。更隐蔽的情况是建表语句里的字段虽然写了utf8mb4但整个库的默认字符集还是latin1新表建出来就会回到默认值。解决数据库、表、字段三级都改成utf8mb4然后重启服务。改动前先备份执行完ALTER TABLE后把旧的乱码数据重灌一遍。这个问题的根源常常不在源码而在你建库时复制了别人老教程里的命令行字符集参数没带进去。5.2 私聊发出去的消息对方一直收不到提示现象A 用户给 B 用户发私聊B 的聊天页面不闪烁、没有未读数字但去数据库查消息已经写进去了。原因私聊消息的to_uid写错了。常见情况是 A 用户页面上选中的目标用户和实际传参不是同一个 ID比如前端传的是用户名后端存的时候没有先换成用户 ID。第二种常见原因是room_id没有按约定设为 0把私聊消息写成了某个公共房间消息公共房间拉取接口不会返回这条记录私聊接口又因为room_id0的条件查不到它消息就成了悬浮数据。解决先登录数据库执行一条排查语句查这条消息的完整字段-- 查最近一条私聊确认 room_id 和 to_uid SELECT id, room_id, from_uid, to_uid, is_read, content FROM chat_message WHERE to_uid 2 ORDER BY id DESC LIMIT 5;如果发现room_id不是 0问题出在发送接口的入参上如果发现to_uid不对问题出在前端选人逻辑上。按这两条线去改对应代码即可。5.3 登录成功后跳两秒又回到登录页现象用户输入正确的用户名密码登录成功跳转到聊天室首页页面一闪又被迫回到登录页或者刷新一下就要重新登录。原因Session 没有被正确保存。最常见原因是 PHP 没有写 session 目录的权限或者站点配置里session.save_path指向了一个不存在的目录。第二种原因是前后端域名不一致登录页在http://localhost聊天室页面跳转到http://127.0.0.1Session Cookie 作用域不同等于每跳一次就换一次身份。解决先把 PHP 配置里的session.save_path目录建好并给予写权限然后在登录请求的返回头里看Set-Cookie的域名和路径。用统一的域名访问系统不要一会儿localhost一会儿127.0.0.1。改完清掉浏览器 Cookie 再测试登录流程。5.4 公共房间一直刷不出新消息但数据库里有现象用户发言后消息列表不更新刷新页面能看到刚才发的消息但轮询接口不返回新内容。原因增量轮询参数last_id被前端存进了内存变量而页面某段 JavaScript 报错导致last_id一直保持初始值 0。每次请求都从头拉旧消息接口返回了一批id last_id的处理逻辑没写好就什么都显示不出来。另一种情况是前端轮询的last_id存到了 localStorage但浏览器缓存了旧值导致增量拉取一直偏移。解决打开浏览器开发者工具切到 Console 面板看有没有 JavaScript 报错再切到 Network 面板查看轮询请求实际发出去的last_id参数。// 在 Console 里手动验证先拿最新一条消息的 id再调一次接口 let lastId 1000; fetch(/api/public_messages.php?room_id1last_id lastId) .then(r r.json()) .then(data console.log(新消息数量, data.length));如果手动请求能返回数据问题一定出在前端轮询的变量维护上。检查lastId这个变量有没有在拿到响应后正确更新这是轮询聊天室最常见也最隐蔽的 bug。5.5 上传头像后图片不显示路径是个不完整地址现象头像上传提示成功但页面上头像裂了图片地址看起来是uploads/avatar/2024/xxx.jpg这种相对路径浏览器从根目录解析不到。原因上传接口返回的是相对路径而前端模板拼接地址时缺少了站点根路径变量。如果部署在二级目录比如http://你的域名/chatnet/那么uploads/avatar/xxx.jpg必须拼成/chatnet/uploads/avatar/xxx.jpg才能访问。很多源码默认部署在域名根目录所以这个 bug 只在二级目录部署时暴露。解决查模板里头像标签的src属性如果是直接输出了相对路径在路径前面拼上系统配置的BASE_URL或APP_URL常量。改完之后再确认站点 Nginx 规则里没有把uploads目录交给 PHP 解析否则上传的图片文件会被当成 PHP 执行这是另一种更严重的安全问题。正确做法是在 Nginx 里对这个目录单独关闭 PHP 执行权限。6. 二次开发 ChatNet 的进阶技巧私聊未读数和消息已读回执当基础部署跑通之后大多数人的下一步需求是让私聊体验更接近现成 IM导航栏显示未读数、点开对话后对方能看到“已读”状态。这两个功能在这个源码架构下都能用较小的改动实现。先说导航栏未读数。ChatNet V1.10 之后的版本已经拆了一个独立的未读接口但很多站点没有把这个接口接到前端导航栏上。我一般在页面底部加一段轮询专门负责更新未读数字。// chat.js — 启动时读取一次未读数之后每 15 秒刷新 let unreadTimer null; const originalTitle document.title; function pollPrivateUnread() { fetch(api/private_unread.php, { credentials: same-origin }) .then(res res.json()) .then(data { const n data.unread || 0; document.title n 0 ? (${n}) ${originalTitle} : originalTitle; document.querySelector(#unread-badge).textContent n; }) .catch(() {}); // 静默失败避免轮询报错刷屏 } unreadTimer setInterval(pollPrivateUnread, 15000); window.addEventListener(beforeunload, () clearInterval(unreadTimer));对应后端的api/private_unread.php只需要一条聚合查询。?php session_start(); $uid (int)($_SESSION[uid] ?? 0); if ($uid 0) { http_response_code(401); exit(json_encode([code 401])); } $pdo new PDO(mysql:host127.0.0.1;dbnamechatnet;charsetutf8mb4, chatnet_user, 密码); $stmt $pdo-prepare( SELECT COUNT(*) FROM chat_message WHERE room_id 0 AND to_uid ? AND is_read 0 ); $stmt-execute([$uid]); echo json_encode([code 0, unread (int)$stmt-fetchColumn()]);这里credentials: same-origin的作用是让请求带上当前站点的会话 Cookie否则后端读不到$_SESSION[uid]接口一直返回 401。15 秒的轮询间隔足够及时又不会给数据库造成压力。如果追求实时性可以把这个接口改成 SSE 方式推送但老 PHP 项目的轮询架构下15 秒已经达到了体验和性能的平衡点。接着做已读回执。当用户点开某个私聊窗口时前端把对方的用户 ID 传给后端后端把两个人之间的私聊标记为已读。// api/mark_read.php — 打开私聊窗口时标记已读 session_start(); $uid (int)($_SESSION[uid] ?? 0); $peerId (int)($_POST[peer_id] ?? 0); if ($uid 0 || $peerId 0) { exit(json_encode([code 1, msg 参数错误])); } $stmt $pdo-prepare( UPDATE chat_message SET is_read 1 WHERE room_id 0 AND to_uid ? AND from_uid ? AND is_read 0 ); $stmt-execute([$uid, $peerId]); echo json_encode([code 0]);这条 UPDATE 语句只在打开私聊窗口时执行一次不会反复更新已读消息性能开销很低。配合前面的未读接口整体就能形成一个完整闭环导航栏显示未读数字点开对话后数字归零对方那边如果也做了已读回显就能看到你已经读了他的消息。最后说一个我做二次开发的习惯每次只动一条链路改完立刻用两个浏览器开两个账号回归。一个浏览器登录 A另一个登录 BA 给 B 发私聊观察 B 的未读数字和页面标题是否变化B 点开窗口再查数据库确认is_read变成 1。把这个验证流程固化成习惯能避免很多改完代码却不知道是否真正生效的窘境。二次开发聊天室项目最忌讳的就是在没确认消息链路完整的情况下直接改界面等发布到线上再回头查往往要花三倍时间。希望这篇 ChatNet 从部署到改造的实战记录能帮到你。本文还有配套的精品资源点击获取
返回列表