微信小程序聊天室:WebSocket实时通信与图片消息实现
上个月接了个需求要做微信小程序聊天室支持文字消息和图片消息图片要能直接发到聊天流里。团队里一位同事提议先上HTTP轮询2秒拉一次省事。我说这个方案在简单场景下能跑但聊天室里撑不住多少用户。后面我们还是用了“微信小程序聊天室 WebSocket 文件上传发送图片”这套组合。这篇文章就把完整的实现思路、关键代码、以及上线前被反复摩擦的坑都整理出来。想照着做一个能跑的小程序聊天室或者想搞懂WebSocket在小程序里怎么落地的人都适合读。1. 聊天室场景为什么非WebSocket不可1.1 轮询不是不行只是这个场景不合适先说需求本质。聊天室和普通页面最大的区别是消息是双向、高频、实时的。用户A发一条消息房间里的其他用户希望在几百毫秒内看到而不是等下一次轮询。轮询方案大概是这样的客户端每隔2秒请求一次接口服务端把新消息返回。表面上能实现但算一笔账就不好看了。假设一个房间有100人每人2秒拉一次平均每秒就有50个请求打到服务端这50个请求里绝大多数是空轮询——没有新消息白白消耗带宽和服务器连接资源。如果是10个房间同时在线每秒就是500个请求。而且轮询的响应报文带HTTP头、Cookie、各种元数据单次请求的实际传输成本远大于一条消息本身的体积。WebSocket解决的就是这个问题。它通过一次HTTP握手升级为长连接之后服务端可以主动向客户端推送消息客户端随时也能发送消息真正做到了全双工通信。聊天室里一条消息从发出到别人收到延迟大概是毫秒级而且连接建立后每次收发消息的头部开销非常小不需要反复携带Cookie和认证信息。所以结论很直接聊天室这种实时双向交互场景用WebSocket不是追时髦是技术选型上的必然选择。轮询不是不能用而是它在并发、实时性、服务器压力三方面都不划算。1.2 微信小程序WebSocket API的边界与限制小程序把WebSocket封装成了wx.connectSocket这一组API。和浏览器里的new WebSocket()相比调用方式更偏回调风格底层协议还是标准的WebSocket所以服务端不需要做特殊适配只要是支持WebSocket的服务端都能对接。但小程序有几个限制必须在动手前就弄清楚线上环境连接地址必须走wss://协议走ws://会被拦。也就是说服务端必须有TLS证书WebSocket服务要挂在HTTPS容器后面。wx.connectSocket的域名必须配置在小程序后台的“socket合法域名”里上传文件则要配在“uploadFile合法域名”里。两个域名分开配置很多人在这里踩坑。一个小程序可以同时创建多个WebSocket连接但业务上不建议这么做。多处建连会导致消息重复、状态难以管理后面我会详细讲为什么最好做全局单例。另外一个容易混淆的点是图片上传走的是wx.uploadFile聊天消息走的是wx.sendSocketMessage两条通道各管各的。图片不是塞进WebSocket里发而是先上传到服务器拿到URL再把URL通过WebSocket广播出去。这个思路贯穿整个项目后面会拆开细讲。1.3 技术栈与前后端协议设计前提后端我用的是Node.js环境下的ws库轻量、稳定写聊天室足够用。如果你用Java的Netty、Go的gorilla/websocket、Python的websockets协议层都是通用的下面的设计思路完全能搬过去。真正需要提前定死的是消息协议。WebSocket只负责把数据从一个点搬到另一个点不管里面装的是什么。如果前后端各写各的前段发的是{content:你好}后段期待的是{type:chat, data:{...}}联调阶段就会变成互相甩锅的现场。我习惯用这样的统一信封结构{ type: chat, data: {} }type表示消息类型至少包含以下几种chat聊天消息文本或图片都走这个类型ping/pong心跳检测broadcast系统通知比如用户进房、退房error服务端返回的错误信息协议结构一旦定下来后面加新功能就只加枚举不改骨架。这个习惯帮我省掉了大量联调争吵建议你也这么做。2. 小程序端连接生命周期从建立到保活2.1 全局单例连接管理页面别自己连聊天室项目最容易踩的第一个坑就是把WebSocket连接写在Page里。假如你在onLoad里创建连接、在onUnload里关闭连接会出现什么问题页面栈跳转的时候聊天页A退出、聊天页B进入连接会被反复创建和销毁。每创建一次连接服务端那边的旧连接不一定立刻释放客户端的消息监听也会乱掉。更麻烦的是小程序里不止聊天页需要消息消息列表页、会话列表页可能也要订阅新消息通知。每个页面各管各的连接光同步未读数就能写出一堆补丁代码。我的做法是单独维护一个SocketManager单例模块连接只有一份所有页面共用。大致结构如下// socketManager.js class SocketManager { constructor(url) { this.url url; this.task null; this.retryCount 0; this.isManualClose false; this.heartbeatTimer null; this.messageHandlers []; } connect() { this.isManualClose false; this.task wx.connectSocket({ url: this.url, success: () console.log(socket connecting) }); this.task.onOpen(() { this.retryCount 0; this.startHeartbeat(); }); this.task.onMessage((res) { const msg JSON.parse(res.data); this.messageHandlers.forEach(fn fn(msg)); }); this.task.onClose(() { this.stopHeartbeat(); if (!this.isManualClose) { this.scheduleReconnect(); } }); this.task.onError(() { // 错误会在onClose里统一处理 }); } send(obj) { if (this.task this.task.readyState 1) { this.task.send({ data: JSON.stringify(obj) }); } } onMessage(handler) { this.messageHandlers.push(handler); } close() { this.isManualClose true; this.stopHeartbeat(); this.task this.task.close(); } }页面侧只关心两件事发送消息调用manager.send()接收消息调用manager.onMessage()。连接什么时候建、什么时候断由Manager统一负责。这里有个细节页面里注册的onMessage回调在页面卸载时一定要注销否则页面都销毁了回调还挂在Manager里一条消息来了会触发多个页面的渲染逻辑轻则报错重则内存泄漏。2.2 心跳保活与指数退避重连WebSocket连接建立起来之后并不是永远稳稳当当的。移动网络下运营商的NAT设备、代理服务器、甚至小程序自身在切换前后台时都可能把空闲的长连接回收掉。表现是连接表面上还开着实际上消息已经发不过去也收不到了。解决这个问题就靠心跳。客户端每隔一段时间发一个ping包服务端回pong包。如果客户端在限定时间内没收到pong就认为连接已经死亡主动关闭并重连。具体参数我一般这样设置每20秒发一次ping发完ping后5秒内没收到pong判定连接异常主动调用task.close()触发onClose再走重连逻辑心跳实现startHeartbeat() { this.stopHeartbeat(); this.heartbeatTimer setInterval(() { this.send({ type: ping }); this.waitPongTimer setTimeout(() { this.task this.task.close(); }, 5000); }, 20000); } stopHeartbeat() { clearInterval(this.heartbeatTimer); clearTimeout(this.waitPongTimer); }收到服务端pong时把waitPongTimer清掉即可。重连不能失败后立刻重试。原因很简单如果服务端刚好宕机重启所有在线的客户端会同时触发重连瞬间的高并发连接请求可能再次把服务端打死。正确做法是指数退避第一次重连等1秒第二次等2秒第三次等4秒以此类推最大不超过30秒。连接成功后退避计数归零。scheduleReconnect() { const delay Math.min(30 * 1000, 1000 * Math.pow(2, this.retryCount)); this.retryCount; setTimeout(() this.connect(), delay); }另外小程序从后台切回前台时App.onShow里也要检查当前连接状态。SocketTask.readyState为0表示连接中1表示已连接2表示正在关闭3表示已关闭。如果状态不是1就主动重连。2.3 消息到达回调的全局分发设计聊天室里的消息不只是聊天页要用。用户可能停留在会话列表页这时新消息要更新未读数用户可能在看历史消息这时新消息要追加到列表底部。如果每处都直接绑定Socket回调回调会越来越多越来越乱。我推荐做一层轻量级的事件分发器SocketManager只负责拆包拆完扔给事件总线业务页面按需订阅。const eventBus { handlers: {}, on(event, fn) { if (!this.handlers[event]) this.handlers[event] []; this.handlers[event].push(fn); }, off(event, fn) { const fns this.handlers[event] || []; const index fns.indexOf(fn); if (index -1) fns.splice(index, 1); }, emit(event, data) { (this.handlers[event] || []).forEach(fn fn(data)); } }; // SocketManager收到消息后 socketManager.onMessage(msg { eventBus.emit(message: msg.type, msg.data); });聊天页里这样订阅onLoad() { this.onChatMessage (data) this.appendMessage(data); eventBus.on(message:chat, this.onChatMessage); } onUnload() { eventBus.off(message:chat, this.onChatMessage); }这套模式本身没什么技术含量但能有效避免多页面消息同步的混乱。尤其是后面加了“图片消息”、系统通知、踢人下线之类的事件事件总线能让你少掉很多头发。3. 发送图片上传通道与实时通道的协作3.1 图片选择与压缩小程序端的内存博弈聊天室发图片第一步是选择图片。小程序端我用的是wx.chooseMedia支持相册选图和拍摄一次可以选多张。选完之后拿到的是临时文件路径这个路径只在当前小程序运行期间有效不适合长期保存。选完图之后必须先压缩。现在的手机随便拍一张照片都有3MB到5MB如果直接把原图上传不仅慢还费流量。聊天场景下图片主要是在手机屏幕上展示压缩到宽度1000像素左右、品质80%肉眼几乎看不出差别体积能压到200KB上下。压缩用wx.compressImagewx.compressImage({ src: tempFilePath, quality: 80, success(res) { const compressedPath res.tempFilePath; // 继续走上传流程 } });如果有特别大的图比如超过5MB我会直接提示用户重新选择。不要相信客户端能处理一切限制输入才是最省事的策略。3.2 wx.uploadFile上传流程与回包设计压缩完成之后走wx.uploadFile把图片传到服务器。这一步是HTTP上传不是WebSocket。function uploadImage(filePath, callback) { wx.uploadFile({ url: https://api.example.com/upload, filePath: filePath, name: file, formData: { senderId: u123, msgId: generateMsgId(), scene: chat }, success(res) { const data JSON.parse(res.data); if (data.code 0) { callback(data.data); } else { callback(null); } }, fail() { callback(null); } }); }服务端收到文件后把文件保存到对象存储或者服务器本地然后返回一个结构化JSON{ code: 0, data: { url: https://cdn.example.com/images/xxxx.jpg, width: 1200, height: 900 } }url是图片的访问地址width和height是图片尺寸。为什么要回传尺寸因为聊天界面渲染图片消息时需要提前知道宽高比来占位不然消息列表会在图片加载完成后突然跳动体验很差。需要注意整个上传过程必须保证url是HTTPS的否则小程序里图片加载不出来。如果你暂时没有对象存储放服务器本地目录也行但要用一个支持HTTPS的静态资源域名来访问。3.3 图片消息的WebSocket推送与三态展示上传拿到URL之后才轮到WebSocket登场。客户端把图片消息发给服务端{ type: chat, data: { msgId: m001, from: u123, content: { type: image, url: https://cdn.example.com/images/xxxx.jpg, width: 1200, height: 900 }, timestamp: 1730000000000 } }这里可以对比一下“为什么不用WebSocket直接传图片二进制”。二进制数据塞进WebSocket消息里一条几MB的图片会让消息通道瞬间变重所有在线用户的连接都会被拖慢而且断点续传、失败重试都很难做。先上传拿URL再通过WebSocket只传一段很轻的JSON连接始终轻盈图片渲染还能直接走CDN缓存一举多得。客户端展示图片消息时我建议做成三态发送中、已发送、发送失败。发送前的处理生成本地临时msgId把消息先插入列表显示为“发送中”上传图片上传成功后通过WebSocket发送图片消息收到服务端ack后把状态更新为“已发送”如果上传或发送失败状态变为“发送失败”允许点击重发这里有一点特别重要msgId必须客户端生成。这样即使网络抖动导致重复提交服务端也能根据msgId去重客户端也能准确匹配哪条消息发送成功了。4. 消息协议与前后端联调细节4.1 统一JSON信封图片和文本一条协议文本消息和图片消息如果各走一套结构联调时前后端各看各的文档很容易错位。我用的是同一套信封只是data.content里的结构不同。文本消息{ type: chat, data: { msgId: m002, from: u456, content: { type: text, text: 你好 }, timestamp: 1730000000001 } }图片消息{ type: chat, data: { msgId: m003, from: u456, content: { type: image, url: https://cdn.example.com/images/xxxx.jpg, width: 1200, height: 900 }, timestamp: 1730000000002 } }服务端和客户端都只判断content.type然后走不同的渲染分支。历史消息的存储结构也完全统一拉取接口只用一套分页参数省掉大量重复代码。4.2 时间戳、消息序号与乱序处理聊天消息排序是个容易被低估的细节。客户端收到消息后直接按接收顺序渲染是省事但补拉历史消息时本地消息和服务端消息混在一起按什么排我见过有人用本地Date.now()排序结果两台手机时间差了2分钟消息顺序完全乱掉。正确做法是所有消息以服务端时间戳为准客户端本地时间只用来做临时展示。服务端在落库时统一生成timestamp客户端排序时只看这个字段。另外分页拉取历史消息时不要用时间戳做游标要用msgId。因为同一毫秒内可能有多条消息时间戳会撞车而msgId是唯一的。客户端记录当前可见的最后一条msgId上拉加载时传lastMsgId服务端返回更早的消息。断线重连后的消息补偿也是这样重连成功后客户端传一个lastMsgId服务端把该ID之后的消息批量推给客户端。这个方案比“拉最近N条”更精准不会漏消息也不会重复渲染。4.3 服务端广播与落库策略服务端收到一条聊天消息后处理顺序应该是先落库再广播。先落库保证消息持久化成功广播过程中即使某个客户端断线重连后也能通过补拉接口取回消息。反过来如果先广播再落库一旦落库失败这条消息就在历史记录里永久消失了。广播时按房间维度过滤。假设一个消息带roomId字段服务端维护一张roomId - Setsocket的映射表收到消息后只给这个房间内的连接推送。单机部署时内存映射表就够了。如果服务端要多实例部署一个房间里的人可能分散连接到不同实例这时就要加一层Redis pub/sub实例A收到消息后发到Redis频道所有订阅了该频道的实例再各自推送给本机连接的用户。这个方案我在项目里实际用过代码量不大但扩展性立刻不一样了。不要在一开始就上Kafka那套重型方案聊天室先保证WebSocket稳定再考虑跨实例消息分发已经足够。5. 真实项目中的坑踩过你才知道5.1 合法域名、wss与开发环境的坑新手做小程序聊天室第一个卡点往往不是代码而是真机调试时连接不上。线上环境wx.connectSocket和wx.uploadFile的域名都必须在小程序后台配置wx.connectSocket对应“socket合法域名”wx.uploadFile对应“uploadFile合法域名”两个域名分别配置。如果服务端只部署了一个域名比如api.example.com那WebSocket连接地址、上传地址、静态图片地址都可以挂在它的子路径下分别按不同合法域名类型配置即可。开发阶段可以在开发者工具里勾选“不校验合法域名”这样不用每次都去后台改配置。但真机预览时必须配好否则报错信息直接写明fail url not in domain list一眼就能认出是这个坑。还有TLS的问题。小程序真机上基本不接受ws://必须wss://。开发环境如果不想买证书可以用自签名证书但真机上也要安装信任否则还是连不上。我在本地方案是直接用一台挂载了证书的服务器反向代理WebSocket服务省去一堆证书信任的破事。5.2 Android/iOS差异化表现与网络切换小程序在Android和iOS上对WebSocket的“照顾”完全不一样。Android手机上很多机型为了省电会在锁屏后掐断长连接。iOS上长连接保留得相对久一些但Wi-Fi和4G/5G切换时TCP连接大概率也会断开。这两种情况都不是代码BUG而是移动网络环境决定的。应对方案分两层第一监听网络状态变化。用wx.onNetworkStatusChange监听网络切换事件切换后延迟1到2秒检查SocketTask.readyState不是1就触发重连。第二监听小程序前后台切换。App.onShow里检查连接状态必要时立即重连。不要等心跳超时那样用户在回到前台后的几秒内会看不到新消息体验很糟糕。还有一个小细节iOS的wx.connectSocket在onClose之后如果紧接着调用connect()偶尔会出现“connectSocket:fail url not in domain list”这种莫名其妙的问题。后来发现是域名配置没问题是旧连接还没完全释放。解决办法是在重连前加一个setTimeout延迟300毫秒再建连让底层连接彻底清理干净。5.3 图片并发上传与文件存储的优化用户发图片经常是连发好几张的。朋友圈式操作选图的时候恨不得一次勾9张。如果9张图同时发起wx.uploadFile既有流量冲击也可能触发小程序的并发限制后续还有可能收到“uploadFile:fail”的报错。我的建议是维护一个上传队列并发数控制在2到3个。队列的实现不复杂class UploadQueue { constructor(maxConcurrent 2) { this.maxConcurrent maxConcurrent; this.running 0; this.queue []; } add(task) { this.queue.push(task); this.next(); } next() { while (this.running this.maxConcurrent this.queue.length) { const task this.queue.shift(); this.running; task().finally(() { this.running--; this.next(); }); } } }每张图片的完整流程是选择 - 压缩 - 入队 - 上传 - 拿URL - 发WebSocket消息。整个流程串行化消息列表里每张图按顺序显示“发送中”用户不会感觉到明显卡顿。存储层面的建议是图片不要放在应用服务器的本机磁盘。聊天图片的访问频次很高本机方案既占磁盘出口带宽也撑不住。正确做法是上传后转存到对象存储访问路径用绑定了自定义域名的HTTPS地址。这样小程序端加载图片能走CDN缓存服务端也没有静态文件IO的压力。最后一个跟图片相关的坑小程序里的image组件加载网络图片时如果URL没有固定宽高比列表渲染会反复抖动。所以服务端回传图片URL时一定要带上width和height客户端渲染时先用宽高比占位图片加载完成后再替换。这一个小改动聊天体验直接上升一个档次。最后分享一个我实际项目里很受益的设计原则聊天室的WebSocket连接里永远不要传大的二进制内容一切消息都通过URL引用。文本走socket图片先上传再广播URL文件走uploadFile通道这样WebSocket连接始终保持轻量稳定图片缓存和CDN扩展也都很方便。后续如果要在聊天室里加语音消息、视频消息这个原则依然适用只需要在上传通道里多做一步转码处理就够了。

相关新闻

SQLite FTS5+BM25实现上下文感知搜索(context-mode)

SQLite FTS5+BM25实现上下文感知搜索(context-mode)

1. “context-mode”到底是什么?别被术语唬住,它本质是让工具真正“听懂你话”的底层机制 “context-mode”这个词最近在开发者社区、低代码平台和本地AI工具链里高频出现,但翻遍官方文档,几乎找不到一句清晰定义。我最初看到它时…

2026/10/14 6:38:29 阅读更多 →
QGIS 加载天地图 403 权限类型错误(301013):User-Agent 分流与三种解法

QGIS 加载天地图 403 权限类型错误(301013):User-Agent 分流与三种解法

场景QGIS 里挂天地图底图,画面全空。F12 或者 QGIS 的网络日志里刷一片 403,响应体是:{"code":301013,"msg":"权限类型错误"}天地图控制台里 key 明明状态正常、配额也没用完,换一个浏览器端 key 试…

2026/10/14 6:38:29 阅读更多 →
极地环境计算设备可靠性测试:低温启动与温循实战指南

极地环境计算设备可靠性测试:低温启动与温循实战指南

把服务器从20℃的调试间一路运到零下四十多度的极地站点,开机那一刻,谁都不敢保证屏幕上是正常的登录界面还是黑屏。我在嵌入式测试这一行摸爬滚打了十几年,实验室里跑高低温箱和真正面对“跨越温差的可靠性挑战”,是完全两种体验…

2026/10/14 6:38:29 阅读更多 →

最新新闻

AI编程教程为何爆火2万Star:从零基础到生产落地的完整路径

AI编程教程为何爆火2万Star:从零基础到生产落地的完整路径

1. 这个“2万Star”到底意味着什么:从GitHub数字看AI编程教程的真实分量很多人看到标题里“2万Star”第一反应是:哇,火了。但作为在开发者教育领域摸爬滚打十多年的老手,我得先泼一盆冷静的水——Star数从来不是教学内容质量的直接…

2026/10/14 7:19:37 阅读更多 →
DNN多分类任务核心设计:Softmax、交叉熵与PyTorch实战

DNN多分类任务核心设计:Softmax、交叉熵与PyTorch实战

2. 多分类任务的三个核心设计问题先说结论:DNN跑多分类,真正需要想清楚的只有三件事——输出层的神经元数量、输出层的激活函数、损失函数的选择。这三件事定下来,模型框架基本就定死了,后面做的都是调参和优化。2.1 输出层神经元…

2026/10/14 7:19:37 阅读更多 →
离线环境部署MySQL 5.7:tar.gz包安装与避坑指南

离线环境部署MySQL 5.7:tar.gz包安装与避坑指南

简介:本资源为 MySQL 5.7.40 在 Linux-glibc2.12 环境下的 x86_64 离线安装包,面向需要在无外网或内网环境中部署关系型数据库的运维与后端开发人员。压缩包共 379 个文件,约 646.59MB,以 h 头文件、so 动态库、xml 配置、sql 脚本…

2026/10/14 7:19:37 阅读更多 →
华为HCIP-AI测试卷高效复习指南:从背答案到动手验证

华为HCIP-AI测试卷高效复习指南:从背答案到动手验证

简介:这份华为认证HCIP-AI测试卷与答案文档,面向备考华为人工智能高级认证的考生及希望系统检验AI基础知识的开发者,帮助梳理图像处理、语音合成、HiAI生态与快应用开发等核心考点。资源包共1个docx文件,约14KB,内容以…

2026/10/14 7:19:37 阅读更多 →
基于rSVD与软阈值的Matlab高效信号去噪方法

基于rSVD与软阈值的Matlab高效信号去噪方法

1. 项目背景与核心挑战谐波噪声是信号处理领域常见的干扰类型,广泛存在于电力系统监测、机械振动分析、生物医学信号采集等场景。传统去噪方法如傅里叶滤波、小波变换在面对高维大数据集时往往面临计算效率低下、噪声鲁棒性不足的问题。这个Matlab项目提出了一种融合…

2026/10/14 7:19:37 阅读更多 →
智慧农业最后一百米:破解乡村数字化落地的设备与运维难题

智慧农业最后一百米:破解乡村数字化落地的设备与运维难题

做了快八年农业数字化项目,我最深的一个体会是:智慧农业真正难的从来不是算法有多高级、传感器有多精密,而是怎么让技术在没有网线、没有专职IT人员、甚至不少农户只用老年机的村子里,持续稳定地转起来。前段时间复盘了一个扎根基…

2026/10/14 7:18:36 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →