Tomcat8+Java7+ExtJS WebSocket聊天室实现与避坑指南
简介基于Tomcat8Java7ExtJS的WebSocket聊天室实现是一份完整的实时通信入门源码适合刚接触Java Web与前端开发的读者。项目演示了如何通过JSR 356标准API定义服务端端点并利用ExtJS组件搭建聊天界面帮助理解网页端与服务器之间的全双工交互过程。压缩包共1753个文件其中gif图片多达1359张可用于界面效果演示scss与css负责样式定制js与ExtJS脚本驱动前端交互jar与class文件构成服务端运行核心另外还包含工程配置和部署描述文件整体仅7.01MB便于下载和快速部署。目前已有262人学习下载通过阅读源码可以掌握ServerEndpoint生命周期处理方法、消息广播与会话管理逻辑以及Tomcat8下的WebSocket部署方式。丰富的gif预览资源也能辅助对照实际运行效果是一份理论与实践结合的参考案例。1. 基于 tomcat8 java7 extjs 的聊天室拆完能带走什么如果你的项目还锁在 Java 7又要加一个实时消息推送页面WebSocket 是绕不过去的一关。这个基于 tomcat8 java7 extjs 的 webscoket 聊天室实现就是一套完整的 WebSocket 消息分发样例浏览器通过 ws 协议连上服务端服务端维护在线 session 集合消息进来后广播给所有参与会话的客户端。它的价值不在“聊天”这两个字而在于把「连接建立 → 消息协议 → 广播分发 → 前端渲染」这条链路完整跑通。适合两类人一类是维护老工程、在既有 Java 7 环境里想快速加实时页面的开发者另一类是刚接触 WebSocket 协议、需要一份能直接 run 起来的源码当参照的新手。前者能从这里看到老版本 Tomcat 下的依赖关系和坑后者能照着把聊天室搭出来再改造成自己的项目。2. 技术选型与工程结构Tomcat8 Java7 ExtJS 这套组合的适配逻辑2.1 为什么是 Tomcat 8这个项目锁 Tomcat 8 不是随手的决定。Java 7 时代 Servlet 规范还在 3.0 / 3.1 过渡期WebSocket 协议RFC 6455对应的 Java 标准是 JSR 356Tomcat 7.0.x 里对 JSR 356 的实现不完整到了 Tomcat 8.0.x 才算把javax.websocket这套 API 稳定下来。另一方面Tomcat 9.0 开始最低要求 Java 8所以如果项目只能待在 Java 7Tomcat 8 基本是唯一合适的容器。这也解释了为什么网上搜“tomcat 8 websocket 聊天室”能搜到一堆结果——不是大家偏爱老版本而是这个组合确实是被环境逼出来的。在 Tomcat 8 里WebSocket 支持是开箱即用的tomcat/conf下不需要单独配置连接器容器启动时会自动扫描ServerEndpoint注解并注册端点。这种“注解驱动”的写法和 Servlet 3.0 之后的WebServlet风格一致代码量比继承Endpoint再自己写消息解析的原始方式少得多。如果你的团队还在用老式 Spring MVC 3.x / Struts2往这个容器里加 WebSocket 页面也不会破坏原有的 HTTP 请求链路。在 WebSocket 普及之前这种聊天页面无非两种实现短轮询或者长轮询。短轮询每 3 秒发一次 HTTP 请求服务端不管有没有新消息都返回页面一多人一多Tomcat 的线程池就撑不住了长轮询把连接挂起等消息虽然省了空转请求但连接数和 Tomcat 的 NIO 处理深度又成了新瓶颈。WebSocket 一次握手后保持 TCP 通道服务端主动推两端只维护一条长连接聊天这种高频率小消息的场景正合适。写这个服务端代码的时候脑子里要时刻记得 Java 7 的边界没有List.ofMap 初始化写不出双大括号里塞多个键值对的简洁语法字符串拼接也没有模板反引号。所有 JSON 协议只能靠StringBuilder或者第三方库。为了避免引入额外依赖这个项目用了一个最简单的 MessageBuilder 来拼 JSON后面核心代码里你会看到它——这也是 Java 7 时代最常见的组合拳。2.2 工程目录老式 Web 工程长什么样chat-room/ ├── src/ │ └── main/ │ ├── java/ │ │ └── com/demo/chat/ │ │ ├── ChatEndpoint.java # WebSocket 端点握手指令入口 │ │ ├── SessionManager.java # 在线 session 集合与广播 │ │ └── MessageBuilder.java # 统一拼 JSON 消息格式 │ └── webapp/ │ ├── WEB-INF/ │ │ └── web.xml │ ├── index.html │ ├── chat.js │ └── extjs/ │ ├── ext-all.js │ └── resources/ ├── pom.xml └── README.md这个目录是从实际项目里抽出来的最小结构。ChatEndpoint负责 WebSocket 生命周期回调SessionManager专门管在线 session 集合和广播MessageBuilder统一生成 JSON 字符串。为什么把消息协议单独抽一个类因为只要消息格式改一处其余代码不用动。前端 ExtJS 直接放在webapp/extjs下不经过 npm老项目的静态资源就是这么做的。2.3 Maven 依赖与 web.xml 配置dependency groupIdjavax.websocket/groupId artifactIdjavax.websocket-api/artifactId version1.1/version scopeprovided/scope /dependencyjavax.websocket-api只是编译期用的接口包运行时由 Tomcat 8 自己提供所以 scope 设为provided。如果改成默认的compile打包时会把这份接口打到 WAR 里部署到 Tomcat 时反倒可能出现类冲突或版本覆盖问题。web.xml 保持最简web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd version3.1 display-nameChatRoom/display-name welcome-file-list welcome-fileindex.html/welcome-file /welcome-file-list session-config session-timeout30/session-timeout /session-config /web-app注意web.xml根元素上如果标了metadata-completetrueTomcat 不会再扫描ServerEndpoint注解。这里故意不写这个属性保持默认的false让注解扫描生效。ExtJS 这只前端框架选型也很老到。在 Java 7 时代jQuery 固然简单但聊天室需要窗口、列表、输入框、弹窗成套组件ExtJS 4.x 的Ext.window.WindowExt.grid.Panel一拍即合回车事件绑定做桌面应用感很强。老项目里往往已经引了 ExtJS直接在静态目录放ext-all.js和resources文件夹就行不需要 npm。这个项目既然叫“基于 extjs”重点就在 window / grid / field 的配置式写法而不是手写一堆 DOM。3. 服务端核心模块拆解一条消息从进入到广播的完整链路3.1 ChatEndpoint注解驱动的端点入口先贴完整的端点类然后拆开讲。package com.demo.chat; import java.util.List; import java.util.Map; import javax.websocket.CloseReason; import javax.websocket.OnClose; import javax.websocket.OnError; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; ServerEndpoint(/chat) public class ChatEndpoint { OnOpen public void onOpen(Session session) { MapString, ListString params session.getRequestParameterMap(); String username 游客 session.getId(); if (params.containsKey(username)) { username params.get(username).get(0); } SessionManager.add(session, username); SessionManager.broadcast( MessageBuilder.build(join, username, username 加入聊天室)); } OnMessage public void onMessage(String text, Session session) { String from SessionManager.getUsername(session); if (from null) { return; // 这个 session 没走正常 onOpen拒绝回复 } SessionManager.broadcast(MessageBuilder.build(chat, from, text)); } OnClose public void onClose(Session session, CloseReason reason) { String username SessionManager.getUsername(session); SessionManager.remove(session); if (username ! null) { SessionManager.broadcast( MessageBuilder.build(leave, username, username 离开聊天室)); } } OnError public void onError(Session session, Throwable error) { error.printStackTrace(); SessionManager.remove(session); } }逻辑说明ServerEndpoint(/chat)告诉容器这个类处理ws://主机:端口/项目名/chat的握手请求路径写的是端点相对路径不包含应用上下文。OnOpen在 TCP 握手完成后触发。这里从 URL query string 里取 username存进 SessionManager。session.getId()是容器生成的唯一 ID没传用户名时用它兜底。OnMessage收到的就是前端ws.send()传上来的字符串服务端按协议原样广播。OnClose的第二个参数可以拿到关闭原因正常断线和异常断线能从CloseReason.CloseCodes里区分出来。OnError用来兜底出错时必须把 session 从集合里摘掉否则会变成死连接占着资源。参数说明session.getRequestParameterMap()返回的是MapString, ListString因为 query string 里同一个 key 可以出现多次所以值是 List。取值时先containsKey再get(0)是稳妥做法直接get(username).get(0)在没传参数时会抛 NPE。3.2 SessionManager在线集合与广播package com.demo.chat; import java.io.IOException; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArraySet; import javax.websocket.Session; public class SessionManager { private static final SetSession SESSIONS new CopyOnWriteArraySet(); private static final MapSession, String USER_MAP new ConcurrentHashMap(); public static void add(Session session, String username) { USER_MAP.put(session, username); SESSIONS.add(session); } public static void remove(Session session) { USER_MAP.remove(session); SESSIONS.remove(session); } public static String getUsername(Session session) { return USER_MAP.get(session); } public static Session findByUsername(String username) { for (Map.EntrySession, String entry : USER_MAP.entrySet()) { if (entry.getValue().equals(username)) { return entry.getKey(); } } return null; } public static void broadcast(String json) { for (Session s : SESSIONS) { try { if (s.isOpen()) { s.getBasicRemote().sendText(json); } else { SESSIONS.remove(s); } } catch (IOException e) { e.printStackTrace(); SESSIONS.remove(s); } } } }逻辑说明SESSIONS用CopyOnWriteArraySet它的写操作用复制副本实现非常适合“读多写少”的广播场景遍历时即使有别的线程在增删也不会抛ConcurrentModificationException。USER_MAP用ConcurrentHashMap保证按 session 快速反查用户名读多写少。findByUsername是给后面防重复登录和私聊用的先放在这里后面改造时会直接调用。broadcast发送前检查s.isOpen()避免给已失效的连接发消息被 Tomcat 抛出IllegalStateException。发送失败时顺手把 session 移除防止坏连接长期占用集合这是实测后补的兜底逻辑。注意Java 7 的ConcurrentHashMap没有computeIfAbsent这类方法所以往集合里放数据只走 put / add不玩函数式那套。3.3 消息协议前后端唯一的契约{type:join,from:alice,content:alice 加入聊天室} {type:chat,from:alice,content:大家好} {type:leave,from:alice,content:alice 离开聊天室}参数说明type消息类型join 表示上线通知chat 表示普通群聊leave 表示离开通知后面扩展私聊时还会加 private。from发送者昵称join / leave 里也是那个用户。content正文内容。后端统一用 MessageBuilder 拼这个 JSON而不是每个方法各拼一次package com.demo.chat; public class MessageBuilder { public static String build(String type, String from, String content) { StringBuilder sb new StringBuilder(); sb.append({\type\:\).append(type) .append(\,\from\:\).append(from null ? : from) .append(\,\content\:\).append(content null ? : escape(content)) .append(\}); return sb.toString(); } private static String escape(String s) { if (s null) { return ; } return s.replace(\\, \\\\) .replace(\, \\\) .replace(\r, ) .replace(\n, ); } }手拼 JSON 是 Java 7 小项目里的常见做法比引一个 fastjson 更轻代价是必须自己做转义。escape把聊天内容里的反斜杠、双引号、换行处理掉避免用户输入把整个消息协议打崩。注意顺序先转义反斜杠再转义双引号写反了会出现二次转义的问题——这是一个血泪经验之前有人先替换双引号再替换反斜杠结果反斜杠又被转义成\\对端显示全乱。3.4 前端连接 URL 怎么把参数传过来前端建立连接时把昵称放进 query stringvar username prompt(请输入昵称) || 游客; var wsUrl ws:// location.host /chatroom/chat?username encodeURIComponent(username);服务端在OnOpen里通过session.getRequestParameterMap()解析这个名字。这里有个容易踩的细节encodeURIComponent必须在前端做中文昵称、空格都要编码服务端拿到的才是干净的 UTF-8 字符串。如果前端直接拼原始中文Tomcat 那边解出来的可能是问号这个坑在避坑章节还会再提。4. 前端 ExtJS 实现从窗口布局到 WebSocket 连接管理4.1 ExtJS 窗口组件搭建先说明一个作用域设计username 提到文件顶层因为 WebSocket 客户端模块也要用它拼 URL。var username prompt(输入你的昵称, 游客) || 游客; Ext.onReady(function() { var msgArea Ext.create(Ext.form.field.TextArea, { region: center, readOnly: true, value: , emptyText: 等待消息... }); var userStore Ext.create(Ext.data.Store, { fields: [name], data: [] }); var userGrid Ext.create(Ext.grid.Panel, { region: east, width: 140, title: 在线用户, store: userStore, columns: [{ text: 昵称, dataIndex: name }] }); var inputBox Ext.create(Ext.form.field.Text, { emptyText: 按回车发送..., listeners: { specialkey: function(field, e) { if (e.getKey() e.ENTER) { client.sendMessage(inputBox.getValue()); inputBox.setValue(); } } } }); var win Ext.create(Ext.window.Window, { title: WebSocket 聊天室, width: 640, height: 430, layout: border, items: [msgArea, userGrid], tbar: [inputBox], renderTo: document.body }); win.show(); });逻辑说明border布局把窗口切成三块中央消息区、右侧在线用户列表、顶栏输入框。TextArea设readOnly: true消息只能从服务端来不允许用户在里面乱改。输入框的specialkey事件里判断e.ENTER回车即发送比点按钮更符合聊天场景。userStore是后面在线列表的数据源增删都在这个 store 上操作。代码里的client对象在下一小节定义这里只是绑定回车事件回调真正触发时client必然已经初始化好了。注意在 ExtJS 4.x 里Ext.form.field.Text是文本输入框的标准写法Ext.form.TextField是旧式别名写新代码统一用带field的那个路径查询文档时也更好对应。4.2 WebSocket 客户端封装var client (function() { var ws null; var connected false; var messageHandler null; function connect() { var url ws:// location.host /chatroom/chat?username encodeURIComponent(username); ws new WebSocket(url); ws.onopen function() { connected true; console.log(WebSocket connected); }; ws.onmessage function(evt) { var msg Ext.decode(evt.data); // 服务端保证是合法 JSON if (messageHandler) { messageHandler(msg); } }; ws.onclose function() { connected false; console.log(WebSocket closed); // 断线重连逻辑放在 4.3 之后再补 }; ws.onerror function() { ws.close(); // 主动关闭触发 onclose 进入重连流程 }; } function sendMessage(text) { if (ws connected) { ws.send(text); } else { Ext.Msg.alert(提示, 连接已断开正在重连...); } } return { connect: connect, sendMessage: sendMessage, setMessageHandler: function(fn) { messageHandler fn; } }; })();逻辑说明封装成闭包模块外部只暴露connect、sendMessage、setMessageHandler三个入口内部ws对象不裸露。ws.onerror里调用ws.close()是有意的错误事件不会自动触发onclose主动 close 能保证断线重连逻辑只走一条路径。发送前检查connected标志避免在后端已经断线时ws.send抛出INVALID_STATE_ERR。参数说明Ext.decode是 ExtJS 对JSON.parse的封装拿到的 message 对象直接可用字段名就是协议里的 type / from / content。4.3 消息渲染与在线用户列表更新var appendMsg function(msg) { var from msg.from || 系统; var line [ from ] msg.content; var value msgArea.getValue(); msgArea.setValue(value line \n); var inputEl msgArea.inputEl.dom; inputEl.scrollTop inputEl.scrollHeight; }; var handleMessage function(msg) { if (msg.type chat) { appendMsg(msg); } else if (msg.type join) { appendMsg(msg); if (userStore.findExact(name, msg.from) -1) { userStore.add({ name: msg.from }); } } else if (msg.type leave) { appendMsg(msg); var idx userStore.findExact(name, msg.from); if (idx ! -1) { userStore.removeAt(idx); } } }; client.setMessageHandler(handleMessage); client.connect();逻辑说明appendMsg里直接操作TextArea的 DOM 元素来设置scrollTop这是 ExtJS 文档里没有细讲的习惯做法。msgArea.getValue()每次全量读再全量写消息量小没问题如果做滚动加载要考虑增量插入。userStore.findExact是精确匹配列字段找到索引后removeAt删除避免同名用户重复进入列表。findExact与find的区别在于后者是模糊匹配这里必须精确。从join/leave事件更新列表比在前端自己维护数组更省心所有状态以服务端广播为准。5. 避坑指南从连接 404 到断线重连的五次翻车5.1 连接 404忘了带 context path现象浏览器控制台报WebSocket connection to ws://localhost:8080/chat failed: Error during WebSocket handshake: Unexpected response code: 404。原因你的应用部署在/chatroom这个 context path 下ServerEndpoint(/chat)只写了端点相对路径完整地址是ws://localhost:8080/chatroom/chat。404 说明请求根本没到达应用Tomcat 在应用上下文那一层就把连接挡下了。解决前端拼 URL 时动态取 basePath不要写死var basePath / location.pathname.split(/)[1]; var wsUrl ws:// location.host basePath /chat?username encodeURIComponent(username);如果你把项目部署成 ROOT 应用context path 为空这里拼出来就是ws://localhost:8080/chat。调试时用浏览器开发者工具的 Network 标签看握手请求能直接确认是不是这一层的问题。5.2 metadata-completetrue 让注解扫描失效现象应用启动不报任何错日志里没有ChatEndpoint相关初始化信息连接始终 404。原因web.xml 根元素上带metadata-completetrue容器按 Servlet 规范跳过注解扫描ServerEndpoint相当于没注册。这个问题隐蔽在哪儿呢因为应用本身能正常访问 index.html只有 WebSocket 握手是 404容易让人怀疑是端点路径写错了。解决把 web.xml 根元素上的metadata-complete属性去掉或者显式设为false。改了之后重启 Tomcat日志里能看到ChatEndpoint的扫描记录。5.3 断线重连后出现幽灵在线用户现象用户直接关掉浏览器在线列表里名字还在重新打开页面又出现两个相同的名字。原因浏览器进程被强杀时 TCP 连接不会立刻通知服务端Tomcat 要等心跳超时才会触发onClose。前端刷新重连后旧的 session 还留在集合里同一个用户名被重复登记了。解决服务端在onOpen时做一次同名清理把旧 session 主动关掉String username params.get(username).get(0); Session old SessionManager.findByUsername(username); if (old ! null) { try { old.close(new CloseReason( CloseReason.CloseCodes.NORMAL_CLOSURE, kick old session)); } catch (IOException e) { e.printStackTrace(); } SessionManager.remove(old); }这个方案依赖前面SessionManager.findByUsername的实现把重名用户踢下线新连接就能拿到干净的在线列表。5.4 中文消息变乱码现象输入中文昵称或中文消息对端看到一串问号或乱码。原因Tomcat 连接器默认URIEncoding是 ISO-8859-1query string 里的中文参数按 ISO 解码后字符已经错位WebSocket 消息本身默认 UTF-8但服务端一旦在 query string 环节就乱了后面救不回来。解决在tomcat/conf/server.xml的 Connector 上显式加URIEncodingUTF-8同时在服务端代码里不再做二次转码让 Tomcat 按 UTF-8 解码。前端发送中文昵称时encodeURIComponent也要跟上双管齐下乱码才消失。5.5 ExtJS 的 TextArea 不自动滚动现象消息超过一屏后TextArea 一直停在顶部得手动拖滚动条才能看到新消息。原因ExtJS 的 TextArea 组件没有内置“自动滚到底部”的行为setValue不会改变 scrollTop 位置。这个坑把很多刚接触 ExtJS 的人卡住过看起来是调样式实际是 DOM API 层面的操作。解决每次追加消息后手动设置var el msgArea.inputEl.dom; el.scrollTop el.scrollHeight;放在appendMsg方法的末尾消息一多就能平滑跟随到底部。如果用msgArea.setValue之后立刻执行DOM 可能还没渲染完保险做法是在Ext.defer里延迟 10 毫秒再设一次。6. 二次开发从群聊到私聊、消息落库与重连补发聊天室的骨架跑通后改造最常用的三个方向是私聊、消息落库、断线补发。私聊只需要给消息协议加一个to字段服务端不再无脑 broadcast而是按照to去 SessionManager 里查目标 Session 定向发送。前端点在线列表里的名字时把目标名字塞进消息对象里再发服务端看到 type 是 private 就走单独分支{type:private,from:alice,to:bob,content:在吗}服务端处理的核心就这一段OnMessage public void onMessage(String text, Session session) { String from SessionManager.getUsername(session); if (from null) { return; } if (text.contains(\type\:\private\)) { String to extractField(text, to); Session target SessionManager.findByUsername(to); if (target ! null target.isOpen()) { try { target.getBasicRemote().sendText(text); } catch (IOException e) { e.printStackTrace(); } } } else { SessionManager.broadcast(text); } } private String extractField(String json, String field) { String key \ field \:\; int start json.indexOf(key) key.length(); int end json.indexOf(\, start); return json.substring(start, end); }逻辑说明extractField依赖 MessageBuilder 的转义规则聊天内容里的双引号已经被\转义过所以按:后第一个裸双引号作为结束位置是安全的。如果以后消息体允许嵌套对象就要换成真正的 JSON 解析库这个方法是纯文本时代的轻量办法。消息持久化是另一个高频需求。做法是在onMessage里先把聊天消息广播出去再异步写库。注意不要在 WebSocket 的消息处理线程里直接执行 SQL瞬时消息量大时数据库 I/O 会把 Tomcat 的工作线程拖死。常见做法是扔给一个ExecutorService线程池去写Java 7 没有现成的异步注解用Executors.newCachedThreadPool()就够每条消息提交一个 Runnable 任务。断线重连的完整方案是心跳加定时器。前端每 30 秒发一个{type:ping}服务端回{type:pong}客户端超过 3 个心跳周期没收到 pong 就主动ws.close()并重连。重连前把本地未发送成功的消息暂存在数组里onopen后按顺序批量补发这样弱网环境下用户消息不会静默丢失。我从这套老代码里学到的习惯是所有消息格式改动先在服务端把协议变更写在 MessageBuilder 的注释里再动前端两边各改各的必翻车。后来我做别的实时项目也强制走这个顺序改协议先改服务端、再改页面希望帮到你。本文还有配套的精品资源点击获取

相关新闻

cash-ls 完全指南:纯 ES6 跨平台实现的 Unix ls 命令

cash-ls 完全指南:纯 ES6 跨平台实现的 Unix ls 命令

开发工具CLI 【免费下载链接】cash Cross-platform Linux commands in ES6 项目地址: https://gitcode.com/gh_mirrors/ca/cash 点击查看 免费下载 导读 cash-ls 是 Cash 项目(Cross-platform Linux commands in ES6)中负责目录与文件列表的…

2026/10/15 18:54:42 阅读更多 →
Flutter 无限列表实战:基于 ListView.builder 懒加载机制的 Infinite List 示例深度解析

Flutter 无限列表实战:基于 ListView.builder 懒加载机制的 Infinite List 示例深度解析

示例工程移动开发 【免费下载链接】flutter-examples [Examples] Simple basic isolated apps, for budding flutter devs. 项目地址: https://gitcode.com/gh_mirrors/fl/flutter-examples 点击查看 免费下载 导读 本文以 flutter-examples 仓库中的 infinite_li…

2026/10/15 18:54:42 阅读更多 →
AI EASY获得2026京津冀算力算法大赛一等奖

AI EASY获得2026京津冀算力算法大赛一等奖

近日,京津冀算力算法大赛圆满落幕。AI EASY凭借在安全算法架构、算力资源利用效率、多模态风险识别、私有化部署与产业化应用方面的几大核心优势,获得评审团高度认可,荣获大赛一等奖。本次大赛聚焦算力优化、国产算力适配、产业赋能升级与场景…

2026/10/15 18:54:42 阅读更多 →

最新新闻

AI论文写作要点梳理与前沿研究成果应用实践指南

AI论文写作要点梳理与前沿研究成果应用实践指南

外文文献“白嫖”与高效阅读全攻略 宝子们,是不是一提到下载外文文献就头大? 想找的文献要么全网搜不到,要么下载流程复杂到让人原地爆炸。好不容易在某个小网站找到了资源,又担心版权问题不敢点,更别说有些平台动辄几…

2026/10/15 19:38:32 阅读更多 →
人脸识别开集测试:从闭集幻觉到真实拒识的工程实践

人脸识别开集测试:从闭集幻觉到真实拒识的工程实践

1. 开集与闭集测试:人脸识别系统落地前必须跨过的那道坎 我第一次在某高校实验室参与人脸识别项目验收时,被现场演示结果惊得愣在原地——模型在内部测试集上准确率高达99.2%,可当现场随机邀请三位未录入过人脸的访客走进识别区域&#xff0c…

2026/10/15 19:38:32 阅读更多 →
深耕支付宝收钱码推广领域,拓推客以专业合作服务赋能地推从业者

深耕支付宝收钱码推广领域,拓推客以专业合作服务赋能地推从业者

移动支付已深度融入日常消费与商业经营场景。线下商户对于便捷收款、数字化经营工具及相关支付服务的需求持续受到关注,与之相关的商户拓展和推广服务,也逐渐成为连接支付产品与线下经营场景的重要环节。在这一行业背景下,专业化、规范化的推…

2026/10/15 19:38:32 阅读更多 →
记忆系统演化:从简单缓存到多层认知架构

记忆系统演化:从简单缓存到多层认知架构

在大模型驱动的智能体(AI Agent)系统研发初期,绝大多数团队对“记忆”的理解仅仅停留在会话上下文的传递上。早期的 LangChain 会话记忆组件(如 ConversationBufferMemory 或 ConversationSummaryMemory)通过简单的字符…

2026/10/15 19:38:32 阅读更多 →
观测全景:基于 OpenInference 的可观测体系总结

观测全景:基于 OpenInference 的可观测体系总结

随着大语言模型(LLM)与多智能体(Multi-Agent System)系统从概念验证(POC)全面进入企业生产级核心业务,传统的微服务可观测性体系(Metrics、Logs、Traces)遭遇了前所未有的…

2026/10/15 19:38:32 阅读更多 →
OpenCV+Python轻量物体识别:纯CPU端到端方案

OpenCV+Python轻量物体识别:纯CPU端到端方案

简介:本资源是一套面向Python与OpenCV初学者及计算机视觉入门开发者的自定义物体识别实战教程,聚焦于零基础构建可识别任意目标的检测模型,适用于智能监控、工业质检、教学实验等场景。压缩包共709个文件,含663张标注用JPG图像、2…

2026/10/15 19:37:32 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

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

月新闻

我发现了一个新思路:用 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/15 0:14:36 阅读更多 →
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/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练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/15 9:49:14 阅读更多 →