ARTICLE DETAIL

资讯详情

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

ThinkPHP+Vue大学生社交实时聊天交友系统开发实践

ThinkPHP+Vue大学生社交实时聊天交友系统开发实践 做这个“ThinkPHPVue 大学生线上社交实时聊天交友系统”最深的体会是聊天功能真正难的点不是写“发送消息”那个按钮而是把连接管理、消息可靠送达、三端数据同步以及上线后的各种异常情况都想清楚。整个项目我是按用户 Web 端、移动 H5 端、管理后台端三个端来拆的后端用 ThinkPHP 提供接口前端用 Vue 负责 SPA实时聊天通讯单独拉了一个 WebSocket 服务。这篇文章按我实际开发顺序整理从需求拆分到技术选型从数据库设计到核心聊天链路最后把上线过程里踩过的坑都列出来想照着做或者正在做类似项目的朋友可以直接参考。1. 项目概述与需求拆解1.1 这个系统到底要解决什么问题大学生社交这个场景和普通社交软件不一样用户的身份相对明确基本都在同一个校园环境里标签也很有意思学校、专业、年级、社团、兴趣爱好。所以这个系统要解决的并不是“陌生人随机匹配聊天”这种泛社交问题而是“帮你在校内找到同好然后能随时发起聊天、看到对方动态”的小型社交闭环。我最初接到的需求很简单做一个在线聊天交友系统要有实时消息要有三端。但把需求拆开之后发现这里藏着三条主业务线账号与关系链注册登录、资料设置、兴趣标签、好友申请、好友列表、黑名单。实时通讯单聊、群聊、在线状态、未读消息、聊天记录、离线消息补拉。内容互动与管理个人动态发布、点赞评论、后台内容审核、用户封禁。为什么要把这三条线先分清楚因为三端不是做三个完全不同的系统而是同一套后端能力在不同入口的复用。我的结论是Web 端和 H5 端共用大部分业务组件管理后台单独做一个小工程后端所有能力全部走 API 接口这样手机端、网页端、后台端的数据口径完全统一。1.2 三端功能边界划分三端听起来唬人实际上功能边界一定要划清楚否则到后期就是灾难。我是这样划分的端目标用户核心功能技术形态用户 Web 端PC 浏览器用户注册登录、完善资料、好友管理、实时聊天、群聊、动态社区、个性化推荐Vue 3 SPA移动 H5 端手机浏览器 / App 内嵌 WebView 用户保留 Web 端核心功能UI 改为移动端布局重点优化聊天体验同一套 Vue 工程加响应式适配管理后台端平台运营人员用户管理、聊天内容监管、动态审核、数据统计、敏感词过滤独立 Vue 管理后台工程这里有个细节值得说很多第一次做“三端”的人会把三端当成三个项目结果重复代码一堆同步接口都要改三遍。我实际操作下来最合理的方式是“两个前端工程”“一个后端工程”一个工程给用户端 Web/H5 共用一个工程给管理后台后端统一一套 API。用户端的 Web 和 H5 共用路由、共用接口层只差异在样式和部分交互。2. 技术选型与架构设计思路2.1 后端用 ThinkPHP图的就是快速和易部署后端我选了 ThinkPHP主要是这三个原因。第一是服务器部署友好。ThinkPHP 是 PHP 生态里的传统框架装在 Nginx PHP-FPM 上非常简单很多大学的服务器环境都是现成的 LNMP/LAMP 环境不用额外折腾容器化这套东西。对一个小型社交项目来说部署成本低非常加分。第二是开发效率。ThinkPHP 的路由、ORM、校验器、中间件这些能力都是现成的写 API 接口的速度非常快。我用 Composer 创建一个 ThinkPHP 6.1 应用然后按多应用模式把 API 和后台管理拆成不同的应用目录composer create-project topthink/think tp-chat cd tp-chat php think run然后开启多应用模式在项目根目录的app下建立api和admin两个应用目录。用户端所有接口都走api应用管理后台接口走admin应用两边在中间件、鉴权、参数校验上都可以独立控制。第三是生态件够用。ThinkPHP 对 Workerman 有官方扩展实时聊天通讯可以直接用 Workerman 起独立的 WebSocket 服务后端到底层逻辑还是那套 PHP 代码学习成本低调试也顺手。这对后面实时聊天通讯方案落地非常关键。还有个朋友问我为什么不用 Java 或者 Go我只能说需求决定选型这种等级的项目用 ThinkPHP 完全够而且我一个人能前端后端一起搞定。真要追求高并发那也不是这个项目的定位。2.2 前端 Vue 的工程化搭配前端选 Vue 是顺理成章的。Vue 3 的组件化很适合这种功能模块多的系统而且生态完整路由、状态管理、UI 组件库都有非常成熟的方案。用户端工程我用的 Vite 构建装依赖命令很简单npm create vitelatest web-user -- --template vue cd web-user npm install npm install vue-router4 pinia axios这里有个容易栽跟头的地方Vue 3 对应的路由是 Vue Router 4不是老版本。如果你直接npm install vue-router装到最高版本一般没问题但如果你对标的是网上老教程很可能会装成 Vue Router 3版本不匹配路由注册方式完全不一样报错会让你头疼。状态管理我用的 Pinia比 Vuex 写起来干净得多几个聊天需要的状态放在 store 里管理很方便后面我详细说。移动 H5 端我没有另起炉灶还是在同一个 Vue 工程里用响应式布局实现。核心思路是组件层尽量通用页面层根据设备宽度调整栅格和交互方式。聊天这种重交互页面移动端的输入框、消息气泡、键盘弹出处理其实和 Web 端差异很大我会单独做移动端适配组件。管理后台端我单独建了一个 Vue 工程用 Element Plus 做 UI采用独立的登录鉴权和路由守卫。后台和用户端的数据走同一套 API只是多了管理侧权限接口。2.3 实时通信方案对比为什么最终选 WebSocket实时聊天通讯的核心是“服务器主动推送消息给客户端”。这个“主动”很关键。HTTP 轮询方案最简单就是前端定时 1 秒或 3 秒请求一次最新消息但问题是延迟高、请求浪费严重、服务器压力大。SSEServer-Sent Events可以实现服务器单向推送但客户端往服务器发消息还是得走 HTTP双向交互很别扭。所以聊天项目最合适的还是 WebSocket建立一条长连接客户端和服务器可以随时互相发数据。选型上我又对比了两个方向自己用 Workerman 写一个 WebSocket 服务还是用 GatewayWorker 这种更重量级的方案。我的结论是项目聊天规模没有到需要网关集群和分布式通讯的时候用 Workerman 自己维护一个单机 WebSocket 服务足够了逻辑清楚出了问题我也能直接看源码调试。具体架构是这样ThinkPHP API 负责业务接口比如登录、好友、动态、聊天记录。独立 Workerman WebSocket 服务监听一个端口负责维护客户端连接、心跳检测、在线状态、消息转发。客户端登录后从 API 拿 Token再用 Token 去连接 WebSocket 服务。私聊消息先经过 WebSocket 服务转发同时落库到数据库确保消息可追溯。这套架构最大的好处是 API 服务和 WebSocket 服务的职责分得很清楚。API 挂了不影响在线用户的聊天通道WebSocket 服务重启也不影响用户登录和拉历史消息只是短暂连不上而已。用户体验不至于全崩。3. 数据库设计与个性化推荐逻辑3.1 核心表结构与字段设计数据库设计是很多新手容易忽略的一步但实际上数据库设计错了后面改起来比写代码还痛苦。我按业务线把核心表拆成四组用户与关系链表名用途关键字段user用户信息id, username, password, nickname, avatar, gender, school, major, grade, intro, interest_tags, statusfriend_apply好友申请表id, from_uid, to_uid, remark, status, create_timefriend好友关系表id, uid, friend_uid, remark, create_timefeedback / report举报反馈id, report_uid, target_uid, reason, status聊天通讯表名用途关键字段chat_message单聊消息表id, from_uid, to_uid, msg_type, content, read_status, create_timechat_group群组表id, name, avatar, owner_uid, create_timechat_group_member群成员表id, group_id, uid, role, join_timechat_group_message群聊消息表id, group_id, from_uid, msg_type, content, create_timeoffline_message离线消息表id, to_uid, from_uid, content, create_time内容互动表名用途关键字段post用户动态表id, uid, content, images, like_count, comment_count, status, create_timepost_like点赞表id, post_id, uid, create_timepost_comment评论表id, post_id, uid, content, create_time说说几个设计上的关键点。密码字段我用的哈希存储不是明文也不是简单的 MD5而是password_hash函数生成校验用password_verify。这个细节虽然基础但社交类项目用户数据极其敏感密码必须这样存。好友关系表为什么单独建一张friend而不是在 user 表里加一个 friends 字段因为加字段只能存储 id 列表查询一个用户的好友列表时要拆字符串做“是否好友”判断时更麻烦。单独建表后查好友列表就是一条 JOIN 或子查询而且可以带上成为好友的时间后续按时间排序做“最近联系人”非常方便。聊天消息表里我加了read_status字段用来区分已读和未读。未读数统计就是一条简单的COUNT查询配合消息列表的接口前端显示未读红点就非常轻量。离线消息我单独建了表没有直接放在消息表里过滤状态是因为离线消息需要在上线时批量补拉而且补拉完之后还要标记为已处理。如果直接在 chat_message 表里加字段很容易和历史消息逻辑混在一起。3.2 个性化推荐逻辑这个项目标题里有“个性化”三个字很多人的做法是给用户打一堆标签然后做个标签匹配就完事但实际体验往往很差。我做的推荐分三层基础过滤、相似度计算、热度加权。基础过滤很简单排除已经拉黑、已经是好友、ID 相同、性别定向过滤的用户。先把候选集缩小再做计算推荐接口不会慢。相似度计算我用了标签集合的 Jaccard 相似度。假设用户 A 的标签集合是{篮球, 电影, 吉他}用户 B 是{篮球, 吉他, 摄影}交集是两个并集是四个相似度就是 0.5。计算上我让标签在 user 表里以逗号分隔存储推荐时先把当前用户的标签拆成数组再查候选用户的时候用 MySQL 的FIND_IN_SET做初步筛选把完全没有交集的人过滤掉避免每次全表扫描。热度加权考虑的是活跃度比如最近 7 天登录次数、最近动态数量。对于几位相似度差不多的用户优先推荐更活跃的那个这样用户点过去看到对方最后上线时间是很久以前体验就很差。最后生成一个推荐分推荐分 标签相似度 * 0.7 活跃度权重 * 0.3按这个分数倒序取前 20 个返回。接口代码其实很短但推荐效果提升明显后来我实测比单纯标签匹配的点击率高出不少。4. 实时聊天通讯核心实现4.1 服务端在线管理和消息路由实时聊天通讯的服务端我用 Workerman 实现。核心逻辑分三块连接鉴权、在线状态维护、消息路由。连接鉴权这一步很关键不能允许任何人随便连上来就发消息。客户端连接 WebSocket 时我在 URL 后面带一个 Token 参数服务端收到连接后先用 Token 去查用户信息查不到就关闭连接。这样能有效防止伪造身份连接。Workerman 服务的骨架大概是这样的use Workerman\Worker; use Workerman\Timer; $worker new Worker(websocket://0.0.0.0:8282); $worker-count 4; $worker-onConnect function ($connection) { // 连接建立进入等待鉴权状态 $connection-auth false; }; $worker-onMessage function ($connection, $data) use ($worker) { $data json_decode($data, true); $type $data[type] ?? ; switch ($type) { case auth: // 用 token 查用户成功则绑定 uid $uid checkToken($data[token]); if ($uid 0) { $connection-uid $uid; $connection-auth true; // 建立 uid 到 connection 的映射 ChatMap::bindUid($uid, $connection); } break; case chat: if ($connection-auth) { handleChat($data); } break; case ping: $connection-send(json_encode([type pong])); break; } }; $worker-onClose function ($connection) { if (isset($connection-uid)) { ChatMap::unbindUid($connection-uid, $connection); } };这里有个非常重要的设计uid到connection的映射。为什么要映射因为一个用户可能同时有 Web 端和手机端两个连接或者一个用户在多个浏览器标签页打开聊天。如果只存一个 connection就会出现“手机上能看到消息电脑上收不到”的诡异体验。所以我用ChatMap把 uid 映射到一个 connection 数组发消息时遍历该用户所有连接实现多端同步。消息路由也很简单客户端发送的消息里带着to_uid服务端根据to_uid去ChatMap里找对应连接有连接就直接推送没有连接就标识为离线。这比自己去查数据库判断在线状态快得多。4.2 消息落库与离线消息补拉实时消息不能只发不收。如果只走 WebSocket 转发不落库那用户刷新页面或者换设备之后聊天记录就没了这是绝对不能接受的。我的流程是客户端发消息时把消息内容同时发给 WebSocket 服务服务端先调用 ThinkPHP 的内部接口把消息写入chat_message表再把这个消息内容广播给接收者。为什么先落库再推送因为落库成功说明消息已经被服务器接受了就算推送失败接收方上线后也能通过拉取历史消息补回来。写消息这个动作本来是想直接写 MySQL但后来我改成了“WebSocket 服务发送给 ThinkPHP API由 API 写库”。这样数据库的连接池、读写逻辑、事务控制全部集中在 API 侧WebSocket 服务保持轻量避免两个服务各建一套数据库连接后期维护起来也统一。离线消息补拉的实现是客户端每次建立 WebSocket 连接成功后先调一个 API 接口pullOfflineMessages接口从离线消息表里查出当前用户所有离线消息然后标记为已读返回给客户端。客户端拿到离线消息后按会话维度聚合分别插入对应聊天会话里界面上的未读红点自然就出来了。4.3 Vue 端 Socket 封装和心跳重连前端这块我踩过的坑不少。WebSocket 不是连上就万事大吉了移动端网络切换、后台进程被系统回收、长时间没有消息导致连接被服务端断开这些都是常态。所以前端的 WebSocket 封装必须有这套能力自动重连、心跳检测、重发队列。我用 Vue 3 的 Composition API 写了一个组合式函数useChatSocket核心逻辑是let socket null let heartbeatTimer null let reconnectTimer null let messageQueue [] function connect(token) { socket new WebSocket(ws://your-server:8282?token${token}) socket.onopen () { // 连接建立后发送心跳 heartbeatTimer setInterval(() { socket.send(JSON.stringify({ type: ping })) }, 30000) // 发送重发队列里的消息 while (messageQueue.length) { socket.send(JSON.stringify(messageQueue.shift())) } } socket.onmessage (event) { const data JSON.parse(event.data) if (data.type message) { handleMessage(data) } } socket.onclose () { clearInterval(heartbeatTimer) // 5秒后重连 reconnectTimer setTimeout(() connect(token), 5000) } socket.onerror () { socket.close() } } function sendMessage(payload) { if (socket socket.readyState WebSocket.OPEN) { socket.send(JSON.stringify({ type: chat, ...payload })) } else { messageQueue.push({ type: chat, ...payload }) } }心跳为什么是 30 秒这是有讲究的。大多数服务器和 Nginx 对空闲连接的空闲超时时间设置在 60 秒到 90 秒之间如果心跳间隔太短比如 5 秒流量浪费如果太长比如超过服务端超时时间连接就会被服务端断开。我实测 30 秒比较稳既能保证连接活跃又不会太占带宽。断线重连我加了 5 秒的延迟而不是立刻重连是因为立刻重连在服务端还没释放连接的时候容易连续报错稍等几秒反而成功率更高。重连成功后要做三件事重新拉取未读消息、重新获取好友在线状态、把队列里的消息发出去。不然用户可能会以为消息发出去了实际上对方根本没收到。4.4 聊天消息体验打磨聊天界面看起来简单实际上体验细节非常多。时间分组、消息按时间排序、新消息自动滚到底部这些都要单独处理。时间分组我是这样做的聊天记录接口返回数据时按自然日打个标记同一天的放一起跨天的中间插一个时间分隔条。排序和分页统一按消息 ID 排序而不是按 create_time因为同一秒内可能有多条消息时间戳相同会导致顺序不稳定。新消息滚动到底部时要注意一个细节如果用户正在往上翻历史消息突然来一条新消息强行把页面拉到底部体验会非常差。我的处理是对滚动区域监听滚动事件如果当前滚动位置已经接近底部新消息就自动滚到底部如果用户还在上面翻历史新消息只更新未读计数不打断滚动。移动端聊天页还有个“键盘顶起来”的问题。手机上输入法弹出时输入框和消息列表的100vh布局会变混乱。我的方案是移动端聊天页不写死高度而是用window.innerHeight动态计算并且监听resize事件实时调整消息列表高度。这个细节不做的话H5 端聊天体验基本就是残废的。5. 三端落地与部署实操5.1 Web 端和 H5 端一套代码适配用户端工程我做成了一套代码同时适配 Web 和 H5。实现方案是按页面维度划分列表页、详情页、个人中心这些页面用响应式栅格聊天页单独做两种布局。判断当前是 Web 还是移动端我在入口文件里通过navigator.userAgent判断后存到 Pinia页面组件根据这个状态决定渲染哪种布局。这样做的好处是路由可以完全复用不同端访问同一个 URL 看到的是适配过的布局不是跳转到另一个域名。这里有一个容易忽略的问题移动端浏览器对100vh的渲染有偏差地址栏出现和隐藏会导致高度闪烁。我的做法是写一个useViewportHeight组合式函数用监听resize的方式动态设置 CSS 变量--vh所有高度都用这个变量计算。还有移动端的底部安全区iPhone 的 Home Indicator 会遮挡底部导航栏需要加env(safe-area-inset-bottom)适配。这类小细节很多建议统一写在一个全局 CSS 里。5.2 管理后台独立小工程管理后台我用 Vue 3 Element Plus 单独建工程。为什么不用同一個工程因为用户端和管理后台的路由结构、菜单体系、组件体系差异很大硬凑在一起反而互相干扰。两个工程共用的是接口层约定和部分 util 工具这可以通过复制公共代码或者抽成 npm 包解决但对我这个项目来说复制公共代码就够了。管理后台的功能主要是用户管理、聊天内容监管、动态审核、敏感词过滤。这里我说下敏感词检测的实现思路我在 ThinkPHP API 里加了一个敏感词过滤中间件发帖、发评论、聊天消息落库前都会过一遍。命中敏感词的消息不是直接拦截而是先标识为“待审核”管理员在后台能看到并处理。这样既保证了社区内容安全又不像简单拦截那样误伤用户。内容审核这块也要做操作记录哪个管理员审核的、什么时间、审核结果是什么都要有日志。这个对长期运营很重要出了问题能追溯。5.3 线上的 Nginx 与部署配置部署这一块直接决定项目能不能稳定跑。我整理了一份 Nginx 配置把 API、Vue 静态文件、WebSocket 反向代理都配在一个站点里server { listen 80; server_name your-domain.com; root /www/wwwroot/tp-chat/public; index index.php index.html; # ThinkPHP 伪静态 location / { if (!-e $request_filename) { rewrite ^(.*)$ /index.php?s$1 last; break; } } location ~ \.php$ { fastcgi_pass unix:/tmp/php-cgi.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } # Vue 前端 location /web { alias /www/wwwroot/web-user/dist; try_files $uri $uri/ /web/index.html; } # WebSocket 代理 location /ws { proxy_pass http://127.0.0.1:8282; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_read_timeout 3600s; proxy_send_timeout 3600s; } }几个关键配置我需要重点说。WebSocket 的/ws路径必须加Upgrade头加proxy_http_version 1.1否则 WebSocket 握手过不去。proxy_read_timeout 3600s是给长连接设置的不设置的话默认 60 秒就会把空闲连接断开前端心跳再勤也扛不住服务端断连。这两个参数漏一个部署完之后 WebSocket 就是各种掉线、连不上。Vue 用 history 模式路由后刷新页面会出现 404原因很简单Nginx 在找不到对应文件时直接返回 404而不是回到index.html让前端路由接管。我在这里用try_files把所有非真实文件请求都指向index.html问题就解决了。但这里有个坑try_files里的$uri必须是相对 root 的路径我用 alias 的时候路径要格外小心否则配上之后 Vue 能加载但 JS 路径全是 404。6. 常见问题与排查技巧实录6.1 WebSocket 连接不稳怎么排查我上线初期遇到过一个很奇怪的问题本地连接 WebSocket 一切正常一到线上就频繁掉线。排查之后发现原因是 Nginx 没有配置proxy_read_timeout默认的空闲超时把连接断了。这算一个典型套路凡是线上 WebSocket 频繁断开的先去看代理层的超时时间而不是怀疑代码。还有一个常见问题是用户连接后马上就被服务端关闭。这种情况多半是鉴权失败Token 过期、Token 格式不对、跨域导致请求头丢失都有可能。排查时先在服务端 light up 一个日志每个连接关闭前都打印一个 close reason特别是“auth failed”和“normal close”要区分开不然只能瞎猜。心跳一定要做。很多人觉得 WebSocket 只要不断服务端就会一直保留连接实际上中间链路运营商、云厂商、Nginx都会对空闲连接做回收。心跳不只是为了“保活”更多是为了“及时发现死连接”。客户端断了网服务端其实感知不到只有等心跳超时才能清理无效连接。否则连接数会一直涨最后把服务端拖垮。6.2 跨域与 Token 问题的排查前后端分离之后跨域问题基本避不开。我的做法是后端统一加 CORS 中间件允许指定域名访问允许携带Authorization请求头。千万不能图省事直接*全部放开尤其这种带用户账号和聊天记录的系统跨域放太开等于给刷接口的人开了一扇大门。Token 我存在前端本地存储里每次请求通过Authorization请求头发给后端。这个选择有争议有人更推荐 HTTP Only Cookie安全性确实更高但 H5 端和 App 内嵌 WebView 环境下 Cookie 的限制非常多获取和更新都麻烦。我权衡之后还是用了本地存储但服务端做了一套刷新机制Token 过期前自动刷新有效降低被盗风险。6.3 Vue 路由与构建部署的坑Vue 路由的坑主要有三个我一个个说。动态路由。后台管理端是根据用户角色动态生成菜单和路由的如果直接把所有路由都静态注册进去后台管理员和普通管理员看到的菜单权限就分不开。我是在登录后通过接口拿到当前管理员的路由权限表用router.addRoute动态添加。这里要注意addRoute添加的路由在用户重新登录后不会自动清除必须在登录前调用router.removeRoute清理否则会出现权限残留切账号之后学生管理菜单还在。history 路由刷新 404这个我前面说过了Nginxtry_files解决即可。还有一个是 Vite 打包的 base path。如果你把 Vue 工程部署在 Nginx 的子路径下比如/web/必须设置base: /web/否则打包出来的 JS 和 CSS 路径全是根目录/assets/访问时全都 404。这个坑很多人踩部署之前一定要先确认静态资源路径。6.4 数据库与消息可靠性的坑聊天项目的数据库和普通业务系统不太一样特点是写多读多、时间顺序敏感。我最开始把chat_message的主键设计成自增 ID结果是按时间查消息没问题但做分页的时候如果消息被删除后面翻页会出现内容跳变。后来我直接按id排序删除消息只做逻辑删除加一个deleted状态不物理删除。这样历史记录的分页稳定而且删除后用户聊天界面上还有占位不会出现“明明有 20 条消息但只显示 18 条”的奇怪情况。再有一个坑是消息落库和 WebSocket 推送的一致性。如果先推消息再入库推送成功但写库失败接收方看到消息刷新后消息没了如果先入库再推送入库成功但推送失败接收方只能等上线补拉离线消息。我选了“先落库再推送”并在消息记录里加了一个push_status字段推送失败的标记为“推送失败”下次客户端上线时可以补推。这个设计虽小但很关键能保证最终消息不丢。数据库连接池也会出问题。WebSocket 服务如果直接在线程里频繁创建 MySQL 连接高并发时很容易达到连接数上限。我后来把数据库访问全部收敛到 ThinkPHP API 层WebSocket 服务只保留一个内部 HTTP 调用的通道数据库连接数瞬间降下来了。6.5 三端联调的一些经验三端联调是整个项目里最耗时间的部分因为它不是单一链路问题而是 Web、H5、后台三套入口同时操作同一份数据。我建议提前做三件事。第一件事是统一接口返回格式。所有接口必须返回统一的 JSON 结构比如{code: 0, message: ok, data: {...}}。这个一定要写在接口文档里并且后端必须严格执行。不然前端每个请求都要写特殊判断每个端处理方式还不一样到后面就是灾难。第二件事是统一时间格式。聊天消息要展示“3 分钟前”“昨天”“2025-06-01 12:30”三端如果各自格式化结果必然不一致。我的做法是后端统一返回时间戳三端各自按本地时区格式化。这样不管是 Web 还是 H5展示的都是用户本地的相对时间。第三件事是统一 WebSocket 事件格式。事件名type必须规范比如chat表示聊天消息、read表示已读回执、online表示上线通知、offline表示下线通知。三端都按这套事件名解析消息就不会出现 Web 端认识typechat而 H5 端还在用typesendMessage的情况。还有一个小细节三端联调时最好用同一个测试账号库不能每个端各注册一套账号那样好友关系、聊天记录对不上调试问题会非常混乱。我后来单独建了测试账号组Web、H5、后台都用这些账号操作消息串起来看问题一眼就能定位。我个人在实际操作中最大的体会是这种社交聊天项目难点主要不在某个单点功能上而是所有功能都互相牵连。登录状态影响 WebSocket 鉴权好友关系影响聊天数据聊天数据又影响后台审核任何一个环节出问题都会在另一端的表现上暴露出来。所以设计阶段把接口格式、事件格式、数据字段约定清楚比后面疯狂补代码有效得多。最后再分享一个建议不要把“三端”想成三个平行项目要想成一套核心能力加三个入口。后端只管数据和服务能力前端优先复用差异部分再做适配。这样开发量可控维护也省心。做类似毕业设计或者真实社交项目的话这套思路可以直接套用能帮你避开很多弯路。
返回列表