ARTICLE DETAIL

资讯详情

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

Uniapp+SpringBoot即时通讯源码:跑通、改造与上架实战指南

Uniapp+SpringBoot即时通讯源码:跑通、改造与上架实战指南 简介一套基于Uniapp与SpringBoot构建的即时通讯聊天安卓APP完整源码面向移动端与Web端开发者提供从前端界面到后端服务的全链路实现参考。资源共857个文件压缩包约62.13MB以Java源码201个、Vue组件140个、JSON配置、SQL脚本及工程说明文档为主辅以PNG/JPG图片与音视频素材覆盖后端接口、前端页面、资源文件与部署配置等层次。已有1265人学习下载具备一定参考热度。源码包含登录注册、聊天室、消息推送等业务模块后端基于SpringBoot配置WebSocket长连接与消息处理机制前端通过uniapp封装连接管理、心跳检测和断线重连逻辑并附带数据库表设计用户表、会话表、消息表及Dockerfile等部署文件适合正在学习SpringBoot后端开发、Vue跨端应用或即时通讯技术的开发者作为完整项目参考与二次开发基础。1. Uniapp SpringBoot 即时通讯安卓 APP这套源码值不值得你动手改拿到一份叫“UniappSpringBoot即时通讯聊天安卓APP源码.zip”的项目多数人的第一反应是别急着解压先弄清两件事里面那套能跑的聊天到底长什么样以及我改它要动多少刀。这类源码的典型形态是前端用 Uniapp 写一套跨端页面编译成安卓 APK后端用 SpringBoot 提供接口和 WebSocket 长连接数据库落在 MySQLRedis 管在线状态和未读计数。它能解决的是“从零写一个聊天 App 最麻烦的那部分”——连接管理、收发链路、消息存储和离线推送接缝这些骨架代码通常已经在了。适合谁适合有 Java 基础和一点 Vue 经验、想做自己的聊天应用或接企业 IM 需求的开发者也适合拿它当课程设计和毕业设计的底子。但“能跑”和“能上线”是两回事源码里的坑比功能多下面按我改这类项目的顺序把联通、连接管理、消息可靠性和打包上架一次讲透。2. 把源码跑起来环境、数据库和前后端联通的三步走拿到 zip 先别急着用 HBuilderX 双击导入先把后端跑起来前端后连这样排错时你能分清是接口挂了还是页面问题。2.1 解压后先摸清目录常见结构和你要改的文件我见过的这类源码解压后通常是一层薄薄的壳一个 uni-app 前端目录一个 springboot 后端目录外加一份 SQL 脚本和一个 README。前端目录里pages放聊天页面、uni_modules或components放组件App.vue管生命周期manifest.json管打包配置后端目录里controller管 HTTP 接口websocket包管长连接mapper管数据库读写。动手前先把这两个目录单独拎出来备份一份原版以后改坏了有后悔药。你要改的文件集中在三处后端的application.yml、前端的manifest.json里配置的应用名称和 AppID以及前端utils/request.js或config.js里写死的接口地址。很多源码默认接口地址是http://localhost:8080或某台测试服务器 IP这个不改成你本机的局域网 IP后面真机上连不上会一直“网络异常”。# application.yml 里常见的三段配置按你的环境改 server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/im_chat?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/Shanghai username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver redis: host: localhost port: 6379 password:这段配置的逻辑是后端服务的端口决定前端 WebSocket 和 HTTP 请求往哪连数据源指向 MySQL字符集必须用utf8mb4否则 Emoji 表情入库直接变问号Redis 如果连不上在线状态和未读数功能会退化成降级逻辑但主流程不一定会崩。serverTimezoneAsia/Shanghai是个容易忽略的坑少了它 MySQL 连接经常报时区错误。2.2 数据库初始化SQL 脚本和手工补表源码自带的 SQL 脚本一般会建好user、friend、chat_message这几张核心表。但很多开源聊天源码的脚本只覆盖单聊群里有人发图片、发语音时消息表里就缺msg_type字段来区分。我的习惯是先把脚本跑通再手工补上常用字段避免后面改表时又要清库。-- 常见的消息表结构含消息去重和类型扩展 CREATE TABLE chat_message ( id BIGINT AUTO_INCREMENT PRIMARY KEY, message_id VARCHAR(64) NOT NULL COMMENT 客户端生成的全局唯一ID, from_id VARCHAR(32) NOT NULL COMMENT 发送人ID, to_id VARCHAR(32) NOT NULL COMMENT 接收人ID单聊是用户ID群聊是群ID, msg_type TINYINT DEFAULT 0 COMMENT 0文本 1图片 2语音 3系统, content TEXT COMMENT 文本内容或文件URL, status TINYINT DEFAULT 0 COMMENT 0未读 1已读 2已撤回, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_message_id (message_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这张表的设计逻辑是message_id由客户端生成长连接重试时后端靠它去重这是聊天系统不丢消息、不重复消息的根基msg_type多留几个扩展位后续加图片、语音不用改表结构status只存接收方的阅读状态发送方自己的状态在另一张会话表里维护。utf8mb4是硬要求用默认的utf8会导致特殊字符和表情写入报错。2.3 启动顺序和联通验证先接口后 WebSocket启动顺序有讲究。先启动 Redis再启动 MySQL最后启动 SpringBoot 后端。后端启动成功后先别急着开前端用浏览器或 Postman 打一下登录接口确认 HTTP 层通了再用在线 WebSocket 测试工具连一下后端的/ws地址确认长连接端口没被防火墙挡。这两步过了前端连不上的时候你就能确定问题在前端。前端在 HBuilderX 里导入项目后要改三个地方manifest.json里的基础配置、接口地址、以及如果是真机调试把request.js里的baseUrl从http://localhost改成你电脑的局域网 IP。HBuilderX 运行到安卓真机时手机和电脑必须在同一 Wi-Fi 下否则请求直接超时。// utils/request.js 里的基础配置示例 const BASE_URL http://192.168.1.100:8080; // 改成你电脑的局域网IP const WS_URL ws://192.168.1.100:8080/ws; // WebSocket 地址 export function getBaseUrl() { return BASE_URL; } export function getWsUrl(userId) { return WS_URL / userId; }这里的关键点在于localhost在手机上指向的是手机自己不是你的电脑这是新人最常见的翻车现场。改完后在 HBuilderX 控制台看请求日志先看登录接口是否返回 token再打开聊天页看 WebSocket 是否显示已连接。3. 连接管理怎么做前端 uniapp 生命周期与后端 WebSocket 会话的配合聊天 App 的命根子是连接。HTTP 接口挂了顶多报个错WebSocket 连接断了消息就彻底收不到。这一章把前端连接管理和后端会话管理拆开讲清楚。3.1 前端连接管理uniapp 生命周期里该做什么uniapp 里管理 WebSocket 的难点不在uni.connectSocket这个 API而在于连接要跟着页面生命周期走。聊天页在onLoad时建立连接onHide时不能断onUnload时才关闭——但用户在聊天页按了 Home 键切到后台连接如果立刻断掉再切回来就得重连。// 一个带心跳和自动重连的 WebSocket 封装片段 let socketOpen false; let heartbeatTimer null; let reconnectTimer null; let reconnectCount 0; function connectSocket(userId) { const wsUrl getWsUrl(userId); uni.connectSocket({ url: wsUrl, success: () { console.log(WebSocket 连接发起); } }); uni.onSocketOpen(() { socketOpen true; reconnectCount 0; startHeartbeat(); }); uni.onSocketMessage((res) { const data JSON.parse(res.data); if (data.type pong) { // 心跳响应什么都不用做 return; } handleMessage(data); }); uni.onSocketClose(() { socketOpen false; stopHeartbeat(); scheduleReconnect(userId); }); uni.onSocketError(() { socketOpen false; stopHeartbeat(); scheduleReconnect(userId); }); } function startHeartbeat() { heartbeatTimer setInterval(() { if (socketOpen) { uni.sendSocketMessage({ data: JSON.stringify({ type: ping }) }); } }, 30000); } function scheduleReconnect(userId) { if (reconnectTimer) return; reconnectTimer setTimeout(() { reconnectTimer null; console.log(WebSocket 重连第, reconnectCount 1, 次); connectSocket(userId); }, 3000); }这段代码的逻辑心跳每 30 秒发一次后端收到ping回pong如果连接断了onSocketClose会触发重连。重连必须做递增退避否则后端一抖动几十个客户端同时重连会把它打挂。reconnectCount可以用来做递增延迟比如每次重连间隔 3 秒、6 秒、12 秒最多到 60 秒封顶。这里必须处理的生命周期问题是页面在onShow时要检查socketOpen如果为 false 就主动重连App.vue的onLaunch只负责首次登录后的连接建立。把连接逻辑全塞进聊天页的onLoad是最常见的错误写法用户从聊天页退到会话列表连接就断了消息到达时收不到。3.2 uniapp 生命周期与 App 前后台切换的坑uniapp 编译成安卓 App 后生命周期和浏览器里完全不同。App 切后台时系统可能冻结 WebSocket甚至直接杀掉进程。这时候你不能指望onHide里写关闭逻辑反而要保持连接。正确的做法是在onHide里只做本地状态标记不做closeSocket操作让连接在后台尽量存活需要保活的话走原生插件或一个前台服务Foreground Service来维持但这类源码通常不带这个能力你要评估是否需要自己加。// App.vue 里处理应用级前后台切换 onHide() { // 标记应用进入后台但不关闭 WebSocket uni.setStorageSync(app_background, true); // 后端感知离线靠心跳超时不是靠这里调用关闭 }, onShow() { uni.setStorageSync(app_background, false); // 检查连接状态如果断了就重连 if (!socketOpen) { const userId uni.getStorageSync(userId); if (userId) connectSocket(userId); } }关键点在于安卓系统回收资源时不会给你机会优雅地发送关闭帧。你唯一能做的是让连接尽快重连、消息通过chat_message表在下一次登录时拉取未读补齐。后台被杀死后冷启动前端要从本地缓存恢复登录态再重新建立连接。3.3 后端会话管理如何维护在线用户和它的心跳后端用 SpringBoot 做 WebSocket常见做法有两种一种是ServerEndpoint注解方式一种是实现WebSocketHandler接口。源码里多半是前者因为它配置简单OnOpen、OnMessage、OnClose三个注解就把生命周期钩子给你了。ServerEndpoint(/ws/{userId}) Component public class ChatWebSocketServer { // 在线会话表userId - session private static ConcurrentHashMapString, Session liveSessions new ConcurrentHashMap(); OnOpen public void onOpen(Session session, PathParam(userId) String userId) { liveSessions.put(userId, session); System.out.println(用户上线 userId 当前在线 liveSessions.size()); } OnClose public void onClose(PathParam(userId) String userId) { liveSessions.remove(userId); System.out.println(用户下线 userId); } OnMessage public void onMessage(String message, PathParam(userId) String userId) throws Exception { // 心跳处理 if (ping.equals(message)) { session.getBasicRemote().sendText({\type\:\pong\}); return; } // 业务消息转发给 MessageService 处理 MessageService.handleMessage(userId, message); } public static void sendToUser(String userId, String message) { Session session liveSessions.get(userId); if (session ! null session.isOpen()) { session.getBasicRemote().sendText(message); } // 不在线时消息进入离线表由 MessageService 处理 } }ConcurrentHashMap做在线表是常见做法但要注意它是 JVM 内存态多实例部署时不共享。如果后端要横向扩展在线状态必须挪到 Redis Hash 里这台机器才能知道用户在不在另一台机器上。用户量上千之前不用急着换但你要知道这个边界。心跳处理的核心是收到ping回pong同时更新 Redis 里的最近活跃时间。后端不能主动依赖前端的定时器来判定在线正确姿势是设置一个空闲超时比如 90 秒内没收到任何消息就视为离线——这个值要比前端心跳间隔大留出至少两倍的余量。4. 消息收发与去重幂等从发送到已读回执的链路设计连接建好后剩下就是消息链路。这块设计得好不好决定你的聊天 App 在弱网下会不会丢消息、在重连后会不会重复。我见过太多源码在“能发能收”之后就交差了结果一上真机测试就翻车。4.1 一条消息从发送到展示要经过几个环节一条消息的真实路径比大多数人想的长客户端生成message_id→ 写入本地待发送队列 → 通过 WebSocket 发给后端 → 后端校验入库 → 推送给在线接收方 → 接收方确认已读 → 发送方更新消息状态为“已送达/已读”。// 后端消息处理核心代码示意 public void handleMessage(String fromUserId, String rawMessage) throws Exception { JsonNode node objectMapper.readTree(rawMessage); String messageId node.get(messageId).asText(); String toId node.get(toId).asText(); int msgType node.get(msgType).asInt(); String content node.get(content).asText(); // 1. 幂等去重同一 message_id 直接丢弃 if (messageMapper.existsByMessageId(messageId)) { System.out.println(重复消息已丢弃 messageId); return; } // 2. 落库 ChatMessage msg new ChatMessage(); msg.setMessageId(messageId); msg.setFromId(fromUserId); msg.setToId(toId); msg.setMsgType(msgType); msg.setContent(content); msg.setStatus(0); messageMapper.insert(msg); // 3. 构造待推送消息 JsonNode pushMsg objectMapper.createObjectNode() .put(type, chat) .put(messageId, messageId) .put(fromId, fromUserId) .put(toId, toId) .put(content, content) .put(timestamp, System.currentTimeMillis()); // 4. 尝试推送给在线接收方 ChatWebSocketServer.sendToUser(toId, pushMsg.toString()); // 5. 发送回执给发送方告知消息已到服务端 ChatWebSocketServer.sendToUser(fromUserId, {\type\:\ack\,\messageId\:\ messageId \}); }这段代码的逻辑很直白但每一步都有坑。幂等去重必须放在入库之前用message_id的唯一索引兜底否则并发场景下两条相同请求同时到达existsByMessageId判断完还没 insert另一条也判断完两条都入库——这就是为什么表里要有UNIQUE KEY数据库层做最后一道防线。推送和落库的顺序也有讲究。先落库再推送接收方哪怕当时不在线下次登录也能从离线消息里拉全先推送再落库推送成功但入库失败消息就丢了。宁可推送时接收方不在线多走一次离线拉取也不能落库失败。4.2 离线消息和已读回执怎么设计不打架消息表只存消息本身离线消息和已读回执是两件不同的事。离线消息的逻辑是接收方不在线时不推送登录后按to_id和create_time拉取最近 N 天未读消息已读回执的逻辑是接收方读到消息后上报message_id后端更新status。-- 拉取离线未读消息的典型查询 SELECT * FROM chat_message WHERE to_id #{userId} AND status 0 AND create_time DATE_SUB(NOW(), INTERVAL 7 DAY) ORDER BY create_time ASC LIMIT #{limit};这个查询的关键点status 0是未读拉取后不能马上把所有记录更新为已读要等客户端逐条确认。客户端拿到未读列表后展示出来再批量上报已读这样如果用户只是打开会话列表没点进聊天页那一批消息仍然算未读未读红点逻辑才不会乱。已读回执上报接口要做批量处理杜绝一条消息一个 HTTP 请求。聊天场景消息密集批量接口能省掉大量无效请求。// 前端批量上报已读的写法 function reportRead(messageIds) { uni.request({ url: getBaseUrl() /api/message/read, method: POST, data: { messageIds: messageIds }, success: (res) { // 成功就更新本地状态 } }); }已读回执的失败处理不能忽略。reportRead失败时消息在当前会话里已经展示了但服务端状态还是未读。常见的处理是本地先乐观置为已读上报失败就静默重试几次不阻塞用户操作。这个细节很多人不做结果就是聊天记录里明明读过了未读数量却一直不清零。4.3 群聊消息的直接推送和隔离群聊和单聊的链路差别在于单聊是点对点推送群聊要遍历群成员逐个推送。源码里如果带群聊通常会多一张group表和group_member表。推送时先查群成员列表再逐个调sendToUser。// 群聊推送的核心片段 public void sendGroupMessage(String groupId, String fromUserId, String message) { ListString memberIds groupMapper.selectMemberIdsByGroupId(groupId); for (String memberId : memberIds) { if (!memberId.equals(fromUserId)) { ChatWebSocketServer.sendToUser(memberId, message); } } }这里要注意的是遍历推送是串行的群成员多的时候会拖慢接口响应。优化方式是用线程池并发推或者直接走广播模式——但广播会推给所有在线用户群里 500 人、全站 5000 人同时在线的场景下广播就是灾难。群聊消息量大的话建议在群消息表里加group_id索引接收方按群拉取离线消息而不是靠推送保证必达。5. 接入这套源码最容易翻车的地方6 个踩坑记录与解决办法这套源码的坑大多数不是逻辑难而是环境、配置和平台差异。下面几条是我改过多个同类项目后总结的真实翻车现场按现象、原因、解决三段写直接照着排查。5.1 真机上连不上后端接口地址和网络权限双重坑现象HBuilderX 运行到安卓真机登录接口一直request:fail后端控制台看不到任何请求。原因一request.js里baseUrl写的是http://localhost:8080手机上的 localhost 指向手机自己。原因二安卓 9 以上默认禁止明文 HTTP 流量http://请求会被系统直接拦截报错却不提示原因。解决把baseUrl改成电脑局域网 IP在manifest.json里配置usesCleartextTraffic为 true。后者的配置位置在打包配置的应用权限和原生设置里不同 HBuilderX 版本位置略有差异一般找manifest.json→ App 模块配置 → Android 隐私合规设置勾选允许明文流量。局域网部署调试阶段前后端都连同一路由器的 Wi-FiIP 地址填电脑的ipconfig查出来的 IPv4 地址。5.2 SpringBoot 版本和 WebSocket 依赖冲突现象后端启动报NoClassDefFoundError或者ServerEndpoint注解完全不生效前端连接直接被拒。原因项目里同时引入了旧版spring-boot-starter-web和新版spring-boot-starter-websocket或者干脆没引spring-boot-starter-websocket只抄了ServerEndpoint的代码。SpringBoot 内置容器处理 WebSocket 的类是版本强相关的版本不对就找不到类。解决别自己手动指定 WebSocket 相关 jar 的版本号让 SpringBoot 的 parent BOM 统一管理。检查pom.xml里是否有spring-boot-starter-parent并确认版本和你用的 SpringBoot 核心库一致再把spring-boot-starter-websocket依赖加进去版本号留空。dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-websocket/artifactId /dependency还有一种情况项目用的内嵌 Tomcat 版本和 WebSocket 实现不兼容这种直接统一升级到 SpringBoot 2.7 或 3.x 对应版本别在旧版本上死磕。5.3 聊天页杀后台再打开消息一条都收不到现象App 切换到后台几分钟再回来时 WebSocket 已经断开重新回到聊天页期间的新消息一条都没到。原因安卓系统的 Doze 省电策略和进程回收机制会冻结后台应用的网络连接尤其是一加、小米、华为这些激进省电的 ROM会直接杀掉不活跃的后台进程。解决技术上做两件事——前端在onShow时检查连接状态断开就重连后端保证离线消息能补齐。真正要保活需要集成原生前台服务或使用第三方推送通道但这类纯源码项目通常没做。你要评估自己的使用场景如果是内部工具类应用冷启动拉取未读消息就够了如果是用户日常高频聊天工具必须接入厂商推送或前台服务。别把希望全押在 WebSocket 保活上安卓系统里这是玄学。5.4 消息重复到达重连后重发的数据没去重现象弱网环境下发送方点了重发按钮接收方收到了两条相同的消息。原因发送方 WebSocket 断开后不知道消息到底到没到服务端于是重发服务端没有按message_id去重的逻辑或者去重代码放在了入库之后而不是之前。解决严格按第 4 章的链路走——客户端生成message_id重发时沿用同一个 ID服务端在入库前查唯一索引重复 ID 直接丢弃并返回 ack。这里有一个取舍返回 ack 给发送方意味着“服务端已收到”但如果服务端返回 ack 之后、推送之前崩了接收方没收到发送方认为已送达——这是分布式系统里的经典两难。小项目不必引入消息队列和最终一致性方案只要保证“服务端不重复落库”和“接收方有离线拉取兜底”用户感知就正常。5.5 uniapp 真机调试时 console.log 不打印现象HBuilderX 连接真机调试前端代码里的console.log在控制台没有输出找半天不知道代码走到哪一步。原因HBuilderX 的真机调试有两种模式——标准基座调试和自定义基座调试。标准基座在某些安卓版本上日志输出会被过滤尤其是 WebSocket 回调里的日志另外uni.onSocketMessage这类 API 的日志不走console.log默认不输出。解决在代码里主动把关键状态写进页面或本地存储用 UI 显示连接状态或者用uni.showToast把关键节点的结果弹出来。调试 WebSocket 时在onSocketOpen、onSocketClose、onSocketError三个回调里都加 toast一眼就能看到连接状态变化。上线前有人偏好把日志分级输出但真机调试阶段直白的 UI 反馈比日志更快。提示真机调试连不上时先拔掉 USB 线用 Wi-Fi 真机调试模式试试。有些情况下 USB 调试的端口冲突会拦截日志输出。5.6 打包后应用闪退或白屏manifest 配置与模块不匹配现象HBuilderX 里运行正常云打包成 APK 安装到手机上启动白屏或者一进聊天页就闪退。原因云打包时没有勾选正确的模块。Uniapp 的每个 API 都有对应的原生模块比如uni.request基础网络请求在部分情况下不需要额外勾选但地图、推送、摄像头这些必须到manifest.json的模块配置里手动勾选漏了模块打包出来的 APK 里没有对应原生能力调用时崩溃。解决在manifest.json的 App 模块配置里逐个核对当前代码用到的 API 所属模块——WebSocket 相关一般属于基础模块但如果你用了plus.android的原生调用就必须勾选对应的原生插件。建议打包前用 HBuilderX 的“运行 → 运行到手机”先在自定义基座里跑通所有功能再云打包别直接拿标准基座跑过的项目去打包上线。6. uniapp 怎么打包上架安卓应用市场签名、权限和上线前验证最后把源码从开发机变成应用市场里的安装包这步拦住了不少人。很多人源码跑通了却在打包上架时反复被打回。6.1 云打包配置包名、证书和权限声明HBuilderX 的云打包界面里核心配置是包名应用 ID、证书和权限。包名一旦确定就不要改上架后改包名等于换了一个应用用户数据全部不通用证书用 Android 官方要求的 keystore 文件HBuilderX 的云打包界面支持生成自签名证书但你要把证书密码和别名保存好后面每次打包都用同一个证书。权限声明要克制。聊天 App 需要的权限集中在网络、存储收图片时要用、录音语音消息时要用但很多源码的manifest.json里默认勾了一堆权限比如定位、相机、通讯录。应用市场审核时会逐条核对权限和实际功能的对应关系多余的权限会被判定为“权限滥用”打回。权限是否必需说明网络访问必需不解释读写存储必需图片、语音消息的临时文件读写录音按需没做语音消息就去掉定位非必需源码没这功能就去掉相机按需聊天页没拍照入口就去掉权限配置在manifest.json里勾选保存后它会自动生成对应的安卓权限声明。这里要留意部分安卓应用市场要求提供隐私政策 URL应用内也要有可访问的隐私政策页面否则审核直接以“未提供隐私政策”打回。隐私政策不是找法务写而是把采集的数据类型、用途、存储方式说清楚自己写一份放服务器上就行。6.2 上架前的最终验证清单上架前一定做这几轮真机测试别只在 HBuilderX 模拟器里点几下就打包。第一轮两台手机不同 Wi-Fi——一台开热点另一台连上去验证跨网络消息收发第二轮把后端服务重启验证客户端自动重连和离线消息拉取第三轮杀掉 App 进程再冷启动验证登录态恢复、未读消息数量正确。# 后端健康检查的最基本命令 curl http://127.0.0.1:8080/api/health # 检查 WebSocket 握手是否正常 curl -i -N -H Connection: Upgrade -H Upgrade: websocket \ -H Sec-WebSocket-Version: 13 \ -H Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw \ http://127.0.0.1:8080/ws/1001第二条 curl 命令会看到101 Switching Protocols的响应说明 WebSocket 握手层通。这里有个细节Sec-WebSocket-Key是一段固定的 Base64 字符串服务端只校验它的存在不校验具体值所以这段命令可以反复用。上架审核被拒的高频原因里“聊天功能没有内容审核”排得很靠前。如果你的 App 要公开发行就必须在消息发送接口里接入敏感词过滤或人工审核机制源码里一般没有这层。内部使用或课程设计场景可以不加但上了应用市场就绕不开。真机测试时我习惯在会话列表页长按消息做一次“撤回”再杀掉进程冷启动看撤回状态是否同步——这类边界操作最容易发现状态同步漏洞。每次改完代码重新打包至少留一台旧版本应用不更新用来对比新旧版本的行为差异。一个项目改到后期我常因为“模拟器没问题”而忽略真机差异后来固定成一条规矩所有涉及连接和推送的改动只在真机上验收。希望帮到你。本文还有配套的精品资源点击获取
返回列表