仿QQ即时通信项目实战:Java Socket多线程全链路解析
很多找我推荐练手项目的人都问过同一个问题管理系统做腻了有没有一个项目能让我把Java核心知识全部串起来做完还能产生我写的软件真的能跑起来的成就感我一般都会推荐仿QQ的即时通信项目。原因很简单——它不依赖任何重框架只用Socket、多线程、Swing、JSON就能搭出一个完整的C/S系统网络通信、并发处理、UI交互这些最考验基本功的点全都能碰到。这篇是第一期目标很明确把最基础的骨架跑通——客户端登录、服务端验证、好友状态展示、两个人一对一聊天。不追求豪华功能先把链路打通。当你亲手把两个客户端通过服务端互发消息调通对Java网络编程的理解会上一个台阶。适合读完Java基础、接触过Socket和Swing但没完整做过C/S项目的人也适合用来准备毕业设计或履历项目。我会按项目实际开发顺序来讲每一步都给你看代码和思路最后再把踩过的坑集中抖出来。1. 为什么偏偏选仿QQ当练手项目目标定清楚再动手1.1 这个项目能帮你练到哪些真本事先说说为什么不是管理系统也不是简单Web项目。仿QQ这类即时通信软件技术覆盖面非常集中几乎每一样都是Java后端面试里躲不掉的东西。第一是网络通信。客户端和服务端之间要维持长连接不是HTTP那种请求一次就断开的短连接而是建立一个Socket后持续双向传数据。这逼着你去理解TCP协议的实际表现粘包、半包、断线重连、心跳保活都是真实业务里躲不开的问题。第二是多线程。服务端要同时服务几百个客户端不可能一次只处理一个连接客户端也不能一边点界面一边卡住收消息。生产者消费者模型、线程池、共享资源的并发安全在这个项目里不是抽象概念而是每天要面对的代码。第三是自定义协议。HTTP、JSON这些现成协议好归好但模拟QQ业务时你总会遇到消息该带什么字段端别如何区分的问题。自己设计一套轻量协议能让你站在设计者的角度思考通信格式这是背一百道面试题都换不来的体验。第四是GUI线程模型。Swing本身是单线程模型所有UI更新必须在事件分发线程EDT里做。网络线程收到消息后怎么安全地通知界面刷新这个联动逻辑比单纯写CRUD有意思得多。1.2 第一期的功能边界先把骨架立起来任何成规模的项目都要控制范围。第一期我给自己划的功能线是用户注册用户名密码存到内存里Java集合直接管理先不引数据库登录验证密码校验、重复登录拦截好友列表展示用户A登录后能看到好友在线状态一对一私聊A发消息给BB实时收到并显示客户端上下线状态通知B下线A的好友列表里B的图标变灰简易心跳探测服务端定期探活防止僵尸连接占资源不做的东西我也列一下群聊、文件传输、离线消息、头像自定义、消息加密、数据持久化。这些不是不重要是放到第一期的范围里会让项目变得不可控。先把消息通路做到稳定后续再扩展节奏合理得多。2. 全局架构怎么搭客户端、服务端职责划分和连接生命线2.1 一条TCP长连接搞定登录、好友与聊天整个项目最核心的架构决策是一个客户端只和服务端建立一条TCP长连接这条连接既用来发登录请求、又用来收好友列表、还用来收发聊天消息。听起来简单实际上这就是QQ类软件的经典模型——连接不是用完就扔而是伴随整个在线周期。服务端的角色是消息路由器。每个客户端连接上来后服务端把该连接登录的用户名记下来存到一个全局Map里username - Socket连接。A给B发私聊时A把消息发到服务端服务端从Map里找到B的连接再转发过去。整个过程A并不知道B的IP和端口这是中心化架构最大的好处——客户端之间不直接通信服务端统一调度安全性好管理也符合大多数真实IM产品的设计。第一期的数据存储特别简单我用一个 ConcurrentHashMap 就当用户库用结构是 username - password。好友关系也不建表预置几组关系比如登录后直接查一个固定的好友矩阵把mock数据加载进来。别觉得这种实现不够工程它恰好能让你把注意力集中在通信链路上。等第二期接MySQL改动也只局限在数据访问层。2.2 自定义消息协议用JSON包一层好扩展还不容易出事网络传输不可能直接传Java对象必须序列化成字节。第一期我选了JSON作为消息载体配一个 Gateway 类统一序列化和反序列化。为什么不选Java原生的序列化因为它生成的数据流里挤满了类描述信息体积大不说客户端换个包名就得报错也看不出消息内容排错非常痛苦。JSON能直接在控制台打印出来一个冒号一个括号看得清清楚楚对前期调试简直是救命级友好。消息结构我设计成固定五个字段字段类型含义typeString消息类型如 LOGIN, LOGIN_RESP, FRIEND_LIST, CHAT, HEARTBEATcodeint状态码0成功非0表示失败类型fromString发送者用户名toString接收者用户名contentString具体内容聊天文本或登录提示语对应的Java类public class Message { private String type; private int code; private String from; private String to; private String content; private long timestamp; public static Message parse(String json) { // 使用一个轻量JSON解析库比如 Jackson 或 fastjson return OBJECT_MAPPER.readValue(json, Message.class); } public String toJson() { return OBJECT_MAPPER.writeValueAsString(this); } }type字段用来区分消息意图code字段用来表达业务状态。这里有个容易忽略的细节每次发送都必须附带 from 和 to哪怕是登录请求也带 from用户名这样服务端路由逻辑可以统一处理不用为每种消息类型单独写寻址逻辑。3. 服务端三大核心模块用户管理、消息路由、状态监控3.1 用户登录验证与重复登录拦截服务端主逻辑长这样先开一个ServerSocket监听端口然后在死循环里 accept()每接进来一个Socket就丢给一个独立线程处理。第一期用每连接一线程的模型虽然在高并发下不高效但逻辑简单到让人心里踏实。后面想优化就换成线程池或NIO。每个连接对应一个 SessionHandler它既负责读客户端的消息也要保存当前登录的用户名、Socket输出流、最后心跳时间这几个关键状态。public class SessionHandler implements Runnable { private Socket socket; private String username; private PrintWriter writer; private volatile long lastHeartbeat; Override public void run() { try (BufferedReader reader new BufferedReader( new InputStreamReader(socket.getInputStream()))) { String line; while ((line reader.readLine()) ! null) { Message msg Message.parse(line); dispatch(msg); } } catch (IOException e) { // 连接异常走下线清理流程 } } }登录验证逻辑里最值得讲的是重复登录拦截同一个用户名不能同时登录两个客户端。实现方式是在userSessions这个全局Map里做检查put之前先判断有没有旧连接。如果用户已经在线要主动给旧连接发一条被顶下线的消息并关闭旧Socket再让新登录成功。不这么做就会出现两个客户端同时在线、消息来回乱串的诡异场景。3.2 消息路由规则私聊消息怎么从A端到B端服务端收到一条 CHAT 类型的消息核心转发代码只有几行public void routeChat(Message msg) { SessionHandler targetSession onlineUsers.get(msg.getTo()); if (targetSession ! null targetSession.isActive()) { targetSession.send(msg.toJson()); } else { Message receipt new Message(); receipt.setType(CHAT_RESP); receipt.setCode(1004); receipt.setContent(对方不在线); // 回执给发送者 onlineUsers.get(msg.getFrom()).send(receipt.toJson()); } }这里有几个值得琢磨的细节。第一个是回执机制A发消息给B如果B在线服务端是不是该给A回个已送达第一期我简化了只在B不在线时回错误码让发送方立刻知道没发出去。真实IM里还会有已发送已读两种状态那是第二期扩展方向。第二个细节是消息带上服务端时间戳。客户端显示消息时按这个时间戳排序防止因为网络延迟导致消息顺序颠倒。如果你按客户端本地时间排序两台机器时钟不同步时聊天顺序会很乱。第三个细节是输出流的安全写入。多个线程可能同时往同一个Socket的writer里写数据如果不加锁两条消息会交错串成一条乱码。我给SessionHandler的send方法上了锁确保同一连接上的写入原子性。3.3 心跳保活与掉线清理TCP连接看起来正常实际上可能已经断了——比如客户端电脑直接断电、网络被拔掉。如果不做任何处理服务端永远不知道这条连接已经死了onlineUsers里会堆满僵尸连接。解决方式就是心跳机制。第一期的心跳策略是双向的客户端每隔30秒主动发一条 HEARTBEAT 消息服务端每次收到就更新 lastHeartbeat 时间戳同时服务端启动一个定时任务每60秒扫一遍所有会话把超过90秒没有心跳的连接强制关闭并从onlineUsers移除。public void checkAlive() { long now System.currentTimeMillis(); onlineUsers.values().removeIf(session - { boolean dead now - session.getLastHeartbeat() 90_000; if (dead) { session.close(); notifyFriendsOffline(session.getUsername()); } return dead; }); }这个机制的背后逻辑是客户端可能没来得及发下线通知就断了服务端必须主动兜底。等后续做移动端弱网环境下心跳频率还要跟进网状态动态调整第一期的固定频率只是为了保证骨架正确。4. 客户端实现细节界面不动收发消息的线程不能乱4.1 登录窗口与主面板的状态切换客户端的入口平台很简单一个登录窗口输入用户名密码点登录按钮。这里最关键的认知是网络操作绝对不能放在按钮的点击事件里同步执行。Swing的事件分发线程负责绘制界面如果直接在按钮回调里调用socket连接的阻塞方法窗口会卡住不动几百毫秒还能忍一旦网络超时就是十几秒的假死。正确做法是点完登录按钮后立刻弹出一个连接中的提示然后把连接服务器的操作提交到单独的线程里。loginButton.addActionListener(e - { new Thread(() - { boolean ok connectAndLogin(username, password); if (ok) { SwingUtilities.invokeLater(() - switchToMainFrame()); } else { SwingUtilities.invokeLater(() - showError(登录失败)); } }).start(); });SwingUtilities.invokeLater 是个宝藏方法它把需要更新UI的操作插回事件分发线程队列里执行。所有网络线程只有通过它才能碰界面组件这是Swing并发编程的铁律。4.2 收到的消息如何安全地塞进聊天窗口连接成功后客户端要启动一个接收循环线程一直阻塞读服务端推送的消息。这个线程的任务有两个把登录响应、好友列表、聊天消息分发到对应处理器。解决UI更新问题的方法不止invokeLater一种也可以用SwingWorker或BlockingQueue但第一期用invokeLater最直观。收到CHAT消息时代码大概是这样private void onChatMessage(Message msg) { SwingUtilities.invokeLater(() - { ChatPanel panel openChatPanels.get(msg.getFrom()); if (panel null) { panel createChatPanel(msg.getFrom()); openChatPanels.put(msg.getFrom(), panel); } panel.appendMessage(msg); }); }注意这里openChatPanels是本地内存里缓存的所有已打开聊天窗口。A给B连发了几条消息B的窗口如果已经打开消息就会追加到同一个窗口里如果没打开就自动创建一个。真实QQ的表现是闪动任务栏提醒第一期先不引入消息提醒的复杂度能正确显示消息就算达标。接收线程还有一个优点天然解决消息到达时界面正忙的问题。invokeLater只是一个入队操作永远不会阻塞网络收包消息吞吐不会因为UI卡顿而下降。4.3 发送消息要做的检查与回显发送消息看似简单——从输入框取文本、封装成Message、写进Socket。但有几件事必须处理。第一发送前做本地空判断。空字符串发出去既浪费流量又让对方看到一堆空消息输入框的空格也要trim掉。第二发送后要不要立即在自己的聊天窗口显示这条消息第一期我选择回显本地消息 等待服务端回执。因为消息会先走到服务端再转发回来如果不做本地回显界面会有明显的延迟感体验很不好。public void sendChat(String to, String text) { Message msg new Message(CHAT, 0, myUsername, to, text, now()); writer.println(msg.toJson()); // 本地回显 appendMyMessage(msg); }第三异常抛出时要给用户一个可读的错误提示不能把堆栈打在控制台就完事。我试过吞异常不提示结果用户以为消息发出去了实际上网络断了半天这种体验极差。5. 跑通第一版后我踩过的坑和修复过程5.1 粘包/半包问题自定义帧头帧尾并缓存处理第一个大坑出现在两个客户端互相发消息的瞬间。A连续快速发了两条消息B收到后偶尔会合并成一条乱数据或者拆成半个包读出来解析失败。我排查了很久最后确认这就是TCP经典的粘包问题。TCP是字节流协议它自己不保证消息边界多条消息可能被塞进同一个TCP段一起到达。解决思路有两种方向一种是规定每条消息以换行符结尾读端用 readLine() 按行解析另一种是自定义帧协议消息头固定N字节前4字节存放体长。第一种方案实现简单只要消息内容本身不包含换行符。JSON里天然的字符串传输用换行分割是够用的。但因为聊天内容不可能永远没有换行符我把消息正文里的换行统一替换成转义符从源头堵住漏洞。半包问题更隐蔽客户端写入了1000字节但服务端一次 readLine() 只读到了700字节就返回了底层Socket缓冲区切断了数据流。第一次遇到时我以为readLine会把不完整的数据返回实际上BufferedReader.readLine会一直阻塞到读到换行符才返回。所以只要消息以换行结尾半包问题就在框架层面避免了。这是比我预期简单得多的答案但排查过程中我对TCP缓冲的理解深了不少。5.2 Swing的并发陷阱满天飞的异常是怎么修好的刚开始聊天窗口老是间歇性闪退控制台报一堆空指针异常。我的第一反应是消息解析出错结果加了日志才发现问题出在事件分发线程之外直接操作JTextArea。接收消息的线程从网络里拿到数据后直接调用 chatPanel.appendMessage() 更新UI这违反了Swing线程模型轻则界面闪烁重则异常崩溃。修起来不难就是把所有UI操作包进 invokeLater。但这个问题提醒我一个更重要的认知网络线程要设计的嗓子干净它只负责把消息对象交给一个UI调度器自己不碰任何可视化组件。后来我干脆封装了一个UiDispatcher 工具类对外只暴露 post(Runnable) 方法内部统一走invokeLater所有网络层代码只和它打交道彻底根绝了这类问题。5.3 消息丢失的排查发送窗口的抖动测试时发现一个特别玄学的BugA给B发消息偶尔B收不到但A界面显示已发送。因为没有抛异常我一开始怀疑是网络不可靠差点想去研究TCP丢包重传冷静下来重新看代码才发现是发送窗口的问题。问题出在客户端的writer被多个线程同时用。虽然我看过别的资料说要在send方法加锁但我只在Service端处理了并发写客户端聊天面板的回显和接收到的服务端响应共用了同一个PrintWriter两个线程同时写偶尔会把对方的输出流状态搞坏。修复方案很简单给客户端的writer也套上一个独立的写锁或者干脆使用一个写队列所有待发送消息进队列由单一线程冲刷。这也解释了为什么第一版服务端的锁不加会出问题——同一连接上多线程写是并发bug的温床。6. 给第二期留的口子按现在的协议做扩展能省多少事6.1 群聊和离线消息怎么接进来第一期跑通之后第二期的路其实已经铺好了。首先是群聊。现在协议里的to字段既能装单个人名也可以约定为群ID服务端路由时增加一个判断如果to是群号就广播给所有群成员连接。共享内存里只需要增加一个群号到成员的Map改动量极小消息结构完全不用变。离线消息稍微麻烦一点。现在服务端检测到B不在线直接给A返回错误码。第二期要做离线消息只需在失败分支里把消息内容存到一张表里等B上线后服务端主动推给他。消息结构、路由逻辑全都能复用相当于只改服务端一行失败处理代码。6.2 更稳的长连接体验心跳与重连第一期的心跳是固定频率30秒真正做产品的话这个参数不能拍脑袋。真实环境里移动网络切换、WiFi断连都会导致连接失效光靠服务端兜底清理不够客户端也要能主动感知连接断开立即重连并自动补发未确认消息。设计时我建议给每条消息加一个自增的消息序号客户端发出去之后先存在待确认队列里收到服务端回执再从队列删除超时未确认就重发。这套消息序号 确认重传机制是可靠通信的标配协议层面现在就要留个字段的位置。另外登录界面和聊天面板我建议二期彻底改头换面把好友列表改成JList的ListModel绑定实时数据状态变化直接驱动UI刷新。第一期是用定时刷新去轮询好友状态能用但不够优雅。改成监听者模式后服务端一个上下线通知就能精准更新那一个好友条目性能和体验都会好很多。这个项目做到目前这个程度最大的收获不是代码量而是建立了一种先跑通再优化的心智模型。很多初学者一上来就想着设计得完美无缺结果卡在架构细节里出不来。我个人的体会是直接把连通链路打通你会立刻获得正反馈之后每个优化点都是明确的、可验证的。第二期折腾完群聊和离线消息我可能会再写一篇文件传输的实现笔记能看到不同网络技术在同一个项目里落地这种感觉确实上瘾。

相关新闻

图像融合评估指标详解:从PSNR到SSIM的Python实现与避坑指南

图像融合评估指标详解:从PSNR到SSIM的Python实现与避坑指南

简介:面向图像融合算法研究的Python评估指标工具包,实现了EN、SF、SD、PSNR、MSE、MI、VIF、AG、CC、SCD、Qabf、SSIM、MS-SSIM、Nabf等十余种主流融合质量指标,支持单幅图像、单算法全部结果批量评估,也可一键计算所有对比算法并…

2026/10/13 7:49:14 阅读更多 →
Windows下MySQL安装配置全攻略:从环境准备到卸载清理

Windows下MySQL安装配置全攻略:从环境准备到卸载清理

1. 安装前的准备:版本选择与环境检查 1.1 先搞清楚该装哪个版本 提起MySQL在Windows上的安装,很多人第一反应是去官网点“Download”,结果面对一堆版本号直接懵了。这里我直接就事论事:目前主流选择是 MySQL Community Server 8…

2026/10/13 7:49:14 阅读更多 →
PhotoImpact v13 兼容指南:在 Win10/11 与虚拟机中重跑老图像软件

PhotoImpact v13 兼容指南:在 Win10/11 与虚拟机中重跑老图像软件

简介:Ulead PhotoImpact 13.0版是一套功能完整的图像编辑与设计软件,主要面向网页设计师、平面爱好者以及需要快速处理图片的办公用户。软件在图像制作、编修、克隆修复、滤镜填充、快速特效模板、抓图与动画方面都有完善表现,可高效产出网页…

2026/10/13 7:48:13 阅读更多 →

最新新闻

基于PaddleOCR与PyQt5的GUI车票识别系统设计与实现

基于PaddleOCR与PyQt5的GUI车票识别系统设计与实现

简介:这是一套基于GUI的OCR车票识别完整毕业设计项目,面向计算机视觉、机器学习方向的本科生及入门开发者,重点解决车票日期、车次、座位号等关键信息的自动提取与可视化操作问题。压缩包共82个文件,整体约4.01MB,包含…

2026/10/13 8:37:52 阅读更多 →
Prim算法详解:最小生成树的原理、实现与工程实践

Prim算法详解:最小生成树的原理、实现与工程实践

1. “Prime算法”到底在讲什么先把这个名字聊透。“Prime算法”在计算机与数据结构的语境下,最常被对应到两条线:一条是图论里求最小生成树的经典贪心算法——Prim算法,另一条则是素数(Prime Number)相关的数论算法&am…

2026/10/13 8:37:52 阅读更多 →
改限制不如改记录:AI内容安全审查记录实战解析

改限制不如改记录:AI内容安全审查记录实战解析

我先不跟你聊那些参数、榜单、跑分,最近这轮更新日志读下来,真正值得关心的变化往往藏在模型之外的“运行记录”里。不少团队把“每日AI观察”的焦点,从“这次又收紧了多少条限制规则”,转向了“审查记录到底长什么样”。我最近正…

2026/10/13 8:37:52 阅读更多 →
终端显示‘impeccable‘?揭秘CLI认证成功标识与误搜根源

终端显示‘impeccable‘?揭秘CLI认证成功标识与误搜根源

1. 项目概述:一个被误读的 CLI 工具命名现象最近在多个技术社区和开发者群聊里,频繁看到“impeccable”这个词被当作某个命令行工具的名称反复提及——它既不是 npm 官方包、也不是主流开源组织发布的 CLI,更没有对应的 GitHub 仓库或文档站。…

2026/10/13 8:37:52 阅读更多 →
UG/NX教程筛选与学习路径:从版本匹配到建模、加工实战

UG/NX教程筛选与学习路径:从版本匹配到建模、加工实战

简介:这是一份整理50套UG(Unigraphics NX)学习教程下载地址的PDF文档,面向使用UG进行产品设计、模具设计、逆向工程及数控加工的工程师和学员,旨在解决教程资源分散、难以系统查找的问题。文档收录了从UG NX4.0到NX7.5…

2026/10/13 8:37:52 阅读更多 →
视频目标跟踪标注工具VATIC非Docker安装全攻略

视频目标跟踪标注工具VATIC非Docker安装全攻略

如果你要做目标检测或者视频目标跟踪,第一步往往不是调模型,而是先把训练数据准备好。标注一个视频里每个目标每一帧的位置,听起来不复杂,真做起来又磨人又容易出错。VATIC 就是干这个的开源视频标注工具:打开浏览器&a…

2026/10/13 8:36:51 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 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/12 21:05:09 阅读更多 →
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/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练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 阅读更多 →