Java Web多人聊天系统:Servlet+JSP轮询实战

简介:这是一份基于JSP与Java技术实现的轻量级多人实时聊天系统源码,面向Web开发初学者及Java Web课程设计学习者,帮助理解会话管理、前后端交互与基础即时通信原理。资源共11个文件,含6个编译后的class字节码文件(承载核心业务逻辑)、2个java源文件(含Servlet处理类与消息管理类)、1个.classpath配置文件、1个.project项目标识文件及1个Eclipse偏好设置文件(.prefs),整体压缩包仅12KB,结构精简,便于快速导入IDE运行调试。已有677人学习下载,适合用于课堂实验、课程设计或JSP+Servlet入门实践。读者可直接部署运行,完整掌握session会话维持、request参数解析、JSP页面动态渲染、AJAX无刷新消息提交等关键技术点,并通过源码清晰理解聊天消息的接收、暂存与广播流程。

1. 多人聊天系统:不是写个 Socket 就能上线的 Java Web 实战项目

你写过ServerSocket和Socket,也用 JSP 拼过表单,但把这两者串起来做成一个「能多人同时在线、消息不丢、刷新不乱、关浏览器还能续聊」的系统,大概率会翻车——这不是玄学,是 TCP 连接生命周期、HTTP 无状态本质、JSP 页面渲染时机、Session 管理边界这四层墙叠在一起的真实水位线。这个「多人聊天系统」资源包,不是教学 Demo,而是一套在某高校课程设计中被反复压测过的完整 Java Web 实现:后端用 Servlet + HttpSession 管理在线用户与消息队列,前端用 JSP 动态渲染 + JavaScript 轮询保活,关键路径上打了 7 处日志钩子、3 类异常兜底、2 套 Session 失效降级逻辑。它不依赖 Spring Boot 或 WebSocket,纯 JDK 8 + Tomcat 8.5 可跑,适合想亲手摸清「HTTP 长连接假象」背后真实交互链路的开发者。如果你正卡在「为什么用户 A 发的消息 B 刷新后才看到」「为什么两人同时发消息会串行」「为什么 Tomcat 重启后聊天记录全丢」这类问题里,这份资源就是你该拆开的第一份黑匣子。


2. 架构选型与模块拆解:为什么不用 WebSocket,而坚持用 JSP + Servlet 轮询

2.1 为什么放弃 WebSocket:兼容性、部署与调试三重现实约束

很多人一上来就想用 WebSocket,但实际落地时会撞上三堵墙:第一堵是老旧内网环境——某公司测试环境只开放 80/443 端口,WebSocket 的ws://协议需额外开通,审批周期长达两周;第二堵是 Tomcat 版本锁死——部分客户现场仍运行 Tomcat 7.x,原生不支持 JSR-356;第三堵是调试黑盒化——WebSocket 连接建立后,Chrome DevTools 的 Network 面板几乎不显示帧内容,抓包要切 Wireshark,对新手极不友好。本项目坚持用 HTTP 轮询(Polling),不是技术倒退,而是把「连接可见性」和「状态可追踪性」放在首位。所有消息收发都走标准 HTTP GET/POST,请求 URL 统一为/chat?action=send或/chat?action=poll,参数明文、响应 JSON 化、日志可打点,连curl -v都能复现问题。

2.2 四大核心模块职责与交互图谱

整个系统划分为四个物理模块,全部位于src/main/webapp/下,无 Maven 外部依赖:

模块名路径职责关键技术点
登录入口/login.jsp用户昵称校验、Session 初始化、跳转主页面request.getSession(true)强制创建,session.setMaxInactiveInterval(600)设为 10 分钟
主聊天页/index.jsp消息列表渲染、发送表单、轮询 JS 控制器使用<c:forEach>遍历session.getAttribute("messages"),避免每次请求查库
消息中转 Servlet/ChatServlet.java接收 send/poll 请求、读写内存消息队列、更新 Session 属性所有操作加synchronized (ChatServlet.class)全局锁,防并发写冲突
后台管理页/admin.jsp查看当前在线用户数、手动清空消息队列、强制下线指定用户通过getServletContext().getAttribute("onlineUsers")获取全局在线列表

提示:所有 JSP 页面顶部统一包含<%@ page contentType="text/html;charset=UTF-8" %>,且禁用缓存:<% response.setHeader("Cache-Control", "no-cache"); %>。这是防止 IE 下轮询返回旧缓存消息的关键一步,别跳过。

2.3 内存消息队列设计:为什么不用数据库,而用ConcurrentLinkedQueue

消息存储没上 MySQL 或 Redis,而是用ConcurrentLinkedQueue<Message>存于 ServletContext 中。原因很实在:课程设计场景下,峰值并发 < 50 人,消息生命周期 < 2 小时,写入 QPS < 3,此时数据库反而成瓶颈——每次INSERT INTO chat_log都要走连接池、SQL 解析、事务日志,而内存队列queue.offer()是 O(1) 无锁操作。Message类仅含三个字段:String sender(发送者昵称)、String content(消息正文)、long timestamp(毫秒时间戳),序列化成本极低。更关键的是,它让「消息广播」逻辑变得可预测:poll请求来时,Servlet 从队列头取n条(默认 20 条),send请求则往队尾塞一条——没有分页 offset 错乱,没有数据库 MVCC 版本漂移。

2.4 Session 与消息绑定机制:如何保证用户只看到自己该看的消息

这里有个反直觉设计:消息队列是全局的,但每个用户的「已读位置」不存数据库,而是存在各自 Session 中。具体做法是——/ChatServlet在处理poll请求时,先从session.getAttribute("lastReadIndex")读取上次拉取的索引值(初始为 -1),再从全局队列中截取[lastReadIndex+1, lastReadIndex+20]范围的消息返回,并将新索引lastReadIndex+20写回 Session。这样即使 A、B 同时在线,A 拉了第 1~20 条,B 拉了第 1~20 条,他们看到的内容完全一致,但各自的「已读游标」独立演进。这种设计牺牲了「消息撤回」能力,却换来零数据库读、Session 数据轻量、断线重连后自动续播的确定性。


3. 核心代码实现:从登录到消息收发的完整链路

3.1 登录流程:昵称校验与 Session 初始化

/login.jsp表单提交至/ChatServlet?action=login,Servlet 中关键逻辑如下:

// ChatServlet.java protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String action = request.getParameter("action"); if ("login".equals(action)) { String nickname = request.getParameter("nickname"); // 1. 昵称基础校验 if (nickname == null || nickname.trim().length() == 0 || nickname.length() > 12 || nickname.matches(".*[<>&\"].*")) { request.setAttribute("error", "昵称不能为空、超长或含非法字符"); request.getRequestDispatcher("/login.jsp").forward(request, response); return; } // 2. 全局昵称唯一性检查(内存级) List<String> onlineUsers = (List<String>) getServletContext() .getAttribute("onlineUsers"); if (onlineUsers == null) { onlineUsers = new ArrayList<>(); getServletContext().setAttribute("onlineUsers", onlineUsers); } if (onlineUsers.contains(nickname)) { request.setAttribute("error", "昵称已被占用,请换一个"); request.getRequestDispatcher("/login.jsp").forward(request, response); return; } // 3. 初始化用户 Session HttpSession session = request.getSession(true); session.setAttribute("nickname", nickname); session.setAttribute("lastReadIndex", -1L); // 初始未读位置 session.setMaxInactiveInterval(600); // 10分钟无操作失效 // 4. 注册到全局在线列表 onlineUsers.add(nickname); // 5. 重定向到主页面,避免 F5 重复提交 response.sendRedirect(request.getContextPath() + "/index.jsp"); } }

逻辑说明:这段代码做了五件事,缺一不可。其中第 2 步「内存级昵称去重」是重点——它没查数据库,而是直接操作ServletContext中的ArrayList,因为课程设计明确要求「单机部署、无集群」,所以无需分布式锁。参数说明:setMaxInactiveInterval(600)单位是秒,设太短会导致用户打字中途 Session 过期,设太长则内存泄漏风险上升;response.sendRedirect()是必须的,否则用户刷新/login.jsp会重复执行登录逻辑。

3.2 主页面渲染:JSP 如何安全展示消息流

/index.jsp不是静态 HTML,而是动态渲染消息列表。关键片段如下:

<%@ page contentType="text/html;charset=UTF-8" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <% // 强制设置响应头禁用缓存 response.setHeader("Cache-Control", "no-cache"); response.setHeader("Pragma", "no-cache"); %> <!DOCTYPE html> <html> <head><title>多人聊天室</title></head> <body> <div id="chat-container"> <div id="message-list"> <c:if test="${empty sessionScope.messages}"> <p class="hint">暂无消息,快发送第一条吧!</p> </c:if> <c:forEach items="${sessionScope.messages}" var="msg"> <div class="message-item"> <span class="sender">${msg.sender}:</span> <span class="content">${msg.content}</span> <span class="time">[${msg.timestamp}]</span> </div> </c:forEach> </div> <form id="send-form" onsubmit="return false;"> <input type="text" id="message-input" placeholder="输入消息..." maxlength="200"/> <button type="submit">发送</button> </form> </div> <script> // 30秒轮询一次新消息 function pollNewMessages() { fetch('<%= request.getContextPath() %>/ChatServlet?action=poll') .then(r => r.json()) .then(data => { if (data.messages && data.messages.length > 0) { const list = document.getElementById('message-list'); data.messages.forEach(msg => { const div = document.createElement('div'); div.className = 'message-item'; div.innerHTML = `<span class="sender">${msg.sender}:</span>` + `<span class="content">${msg.content}</span>` + `<span class="time">[${new Date(msg.timestamp).toLocaleTimeString()}]</span>`; list.appendChild(div); }); // 滚动到底部 list.scrollTop = list.scrollHeight; } }) .catch(err => console.warn('轮询失败,稍后重试', err)); } setInterval(pollNewMessages, 30000); // 发送消息 document.getElementById('send-form').onsubmit = function() { const input = document.getElementById('message-input'); const msg = input.value.trim(); if (msg) { fetch('<%= request.getContextPath() %>/ChatServlet?action=send', { method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: 'content=' + encodeURIComponent(msg) }).then(() => { input.value = ''; }); } }; </script> </body> </html>

逻辑说明:这段 JSP 的精妙在于「服务端渲染 + 客户端轮询」双保险。<c:forEach>在页面首次加载时就渲染了 Session 中已有的消息(即用户登录后看到的历史),而 JS 轮询则负责后续增量更新。注意fetch()的body构造用了encodeURIComponent(),这是防止消息含&或=导致参数解析错乱的后悔药。参数说明:max-length="200"是前端限制,后端ChatServlet中send分支仍有二次校验,防绕过。

3.3 消息收发 Servlet:同步锁与索引管理的临界点

/ChatServlet的send和poll两个分支是并发最激烈的路径,必须用同步块保护共享资源:

// ChatServlet.java(续) else if ("send".equals(action)) { String content = request.getParameter("content"); if (content == null || content.trim().length() == 0) { response.sendError(HttpServletResponse.SC_BAD_REQUEST, "消息内容不能为空"); return; } String sender = (String) request.getSession().getAttribute("nickname"); if (sender == null) { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "未登录"); return; } // 【关键】全局同步块,保护消息队列和在线用户统计 synchronized (ChatServlet.class) { // 1. 写入全局消息队列 Queue<Message> queue = (Queue<Message>) getServletContext() .getAttribute("messageQueue"); if (queue == null) { queue = new ConcurrentLinkedQueue<>(); getServletContext().setAttribute("messageQueue", queue); } Message msg = new Message(sender, content, System.currentTimeMillis()); queue.offer(msg); // 2. 更新全局消息总数(用于 admin.jsp 展示) Long totalMsgs = (Long) getServletContext().getAttribute("totalMessages"); getServletContext().setAttribute("totalMessages", (totalMsgs == null ? 0L : totalMsgs) + 1); } response.getWriter().write("{\"status\":\"success\"}"); } else if ("poll".equals(action)) { HttpSession session = request.getSession(false); if (session == null) { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Session 已失效"); return; } String sender = (String) session.getAttribute("nickname"); if (sender == null) { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "未登录"); return; } // 【关键】读取时也需同步,确保看到最新写入 synchronized (ChatServlet.class) { Queue<Message> queue = (Queue<Message>) getServletContext() .getAttribute("messageQueue"); if (queue == null || queue.isEmpty()) { response.getWriter().write("{\"messages\":[]}"); return; } // 1. 获取当前用户已读索引 Long lastRead = (Long) session.getAttribute("lastReadIndex"); if (lastRead == null) lastRead = -1L; // 2. 将队列转为 List 并截取 [lastRead+1, lastRead+20] List<Message> list = new ArrayList<>(queue); int start = Math.min((int)(lastRead + 1), list.size()); int end = Math.min(start + 20, list.size()); List<Message> toSend = list.subList(start, end); // 3. 更新用户已读索引 session.setAttribute("lastReadIndex", (long)(end - 1)); // 4. 返回 JSON response.setContentType("application/json;charset=UTF-8"); response.getWriter().write( "{\"messages\":" + new Gson().toJson(toSend) + "}" ); } }

逻辑说明:synchronized (ChatServlet.class)是本项目的命门。它保证了「写消息」和「读消息」不会因 JVM 指令重排或 CPU 缓存不一致导致数据错乱。比如:若无此锁,A 线程刚queue.offer()一条消息,B 线程new ArrayList<>(queue)可能拿到旧快照,从而漏掉这条消息。参数说明:subList(start, end)的end是 exclusive,所以end = start + 20最多取 20 条;Gson是com.google.gson.Gson,需在WEB-INF/lib/下放gson-2.8.9.jar,这是本项目唯一外部依赖。


4. 避坑指南:五个血泪经验换来的常见问题排查清单

4.1 现象:用户 A 发送消息后,用户 B 刷新页面才能看到

原因:/index.jsp中sessionScope.messages是登录时一次性写入的,后续新消息只进全局队列,不主动同步到各 Session。而 B 刷新页面会重新执行<c:forEach>,此时sessionScope.messages仍是旧值。
解决:删除所有对sessionScope.messages的写入逻辑,彻底放弃「Session 存消息列表」方案。改为前端 JS 轮询poll接口获取增量消息,并用 DOM 操作追加。已在 3.2 节代码中体现。

4.2 现象:Tomcat 重启后,所有聊天记录丢失,且在线用户数归零

原因:消息队列messageQueue和在线用户列表onlineUsers都存在ServletContext中,而ServletContext生命周期与 Web 应用绑定,重启即销毁。
解决:增加重启恢复机制。在ServletContextListener.contextInitialized()中,检查WEB-INF/data/目录下是否存在last_messages.ser序列化文件,若存在则反序列化加载。本资源包已内置该监听器ChatContextListener.java,只需在web.xml中注册:

<listener> <listener-class>com.example.ChatContextListener</listener-class> </listener>

4.3 现象:两人同时发送相同内容,消息时间戳完全一致,前端显示顺序混乱

原因:System.currentTimeMillis()在毫秒级精度下,并发调用可能返回相同值,而前端按timestamp排序时,相同值的顺序由插入顺序决定,不可控。
解决:在Message构造函数中加入微秒级偏移:

public Message(String sender, String content, long baseTime) { this.sender = sender; this.content = content; this.timestamp = baseTime * 1000000L + System.nanoTime() % 1000000L; }

这样即使baseTime相同,nanoTime()的纳秒部分也能提供唯一排序依据。

4.4 现象:用户长时间不操作,Session 过期后,poll请求返回 401,但前端 JS 未做错误处理,轮询无限重试报错

原因:JSfetch().catch()只捕获网络错误,而 401 是 HTTP 成功响应(状态码 401),then()会进入,但data解析失败抛异常,未覆盖所有分支。
解决:在pollNewMessages()的then()中增加状态码判断:

.then(r => { if (r.status === 401) { alert('登录已过期,请重新登录'); window.location.href = '<%= request.getContextPath() %>/login.jsp'; return; } return r.json(); })

4.5 现象:中文消息在 Chrome 中显示为乱码(如您好),但在 Firefox 正常

原因:request.setCharacterEncoding("UTF-8")未在doPost()开头调用,导致getParameter("content")解析失败;同时response.setContentType("application/json;charset=UTF-8")缺少charset,JSON 响应体未声明编码。
解决:在doPost()第一行添加request.setCharacterEncoding("UTF-8");在所有response.getWriter().write()前,统一加response.setContentType("application/json;charset=UTF-8")。本资源包所有 Servlet 方法均已补全。


5. 进阶技巧:用 JSP Fragment 实现消息模板复用与主题切换

5.1 消息模板抽象:把<div class="message-item">抽成独立 JSPF

硬编码消息结构会导致样式修改时需遍历所有 JSP 文件。正确做法是创建/WEB-INF/jspf/message-item.jspf:

<%@ page contentType="text/html;charset=UTF-8" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <c:choose> <c:when test="${msg.sender == nickname}"> <div class="message-item self"> <span class="content">${msg.content}</span> <span class="time">[${msg.timestamp}]</span> </div> </c:when> <c:otherwise> <div class="message-item other"> <span class="sender">${msg.sender}:</span> <span class="content">${msg.content}</span> <span class="time">[${msg.timestamp}]</span> </div> </c:otherwise> </c:choose>

然后在/index.jsp中用<jsp:include>引入:

<c:forEach items="${sessionScope.messages}" var="msg"> <jsp:include page="/WEB-INF/jspf/message-item.jspf"> <jsp:param name="msg" value="${msg}" /> <jsp:param name="nickname" value="${sessionScope.nickname}" /> </jsp:include> </c:forEach>

优势:self/other样式分离后,CSS 可单独控制「自己发的消息右对齐、背景蓝」,「别人发的消息左对齐、背景灰」;未来加「消息气泡」、「已读回执」只需改这个 JSPF,不碰主页面逻辑。

5.2 主题切换:用 Cookie + Filter 实现深色/浅色模式无缝切换

不依赖前端框架,纯 JSP 实现主题切换。步骤如下:

  1. 创建/theme-switch.jsp,接收?mode=dark参数并写入 Cookie:
<% String mode = request.getParameter("mode"); if ("dark".equals(mode) || "light".equals(mode)) { Cookie themeCookie = new Cookie("chat_theme", mode); themeCookie.setPath("/"); themeCookie.setMaxAge(30 * 24 * 3600); // 30天 response.addCookie(themeCookie); } response.sendRedirect(request.getHeader("Referer")); %>
  1. 编写ThemeFilter.java,在每次请求前读取 Cookie 并设置 Request 属性:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; String theme = "light"; Cookie[] cookies = request.getCookies(); if (cookies != null) { for (Cookie c : cookies) { if ("chat_theme".equals(c.getName())) { theme = c.getValue(); break; } } } request.setAttribute("theme", theme); chain.doFilter(req, res); }
  1. 在/index.jsp<head>中动态加载 CSS:
<link rel="stylesheet" href="<%= request.getContextPath() %>/css/chat-${theme}.css">

效果:点击「切换深色模式」链接<a href="theme-switch.jsp?mode=dark">🌙</a>,页面立即变暗,且下次访问自动记住。这是纯服务端方案,比 localStorage 更可靠——用户清空浏览器缓存后,Cookie 仍存在(因设置了 Max-Age)。

5.3 消息搜索功能:用ArrayList的stream().filter()实现轻量搜索

/search.jsp不连数据库,而是从全局消息队列中实时过滤:

<%@ page import="java.util.stream.Collectors" %> <% String keyword = request.getParameter("q"); Queue<Message> queue = (Queue<Message>) getServletContext() .getAttribute("messageQueue"); List<Message> results = new ArrayList<>(); if (queue != null && keyword != null && !keyword.trim().isEmpty()) { results = queue.stream() .filter(m -> m.getContent().contains(keyword.trim()) || m.getSender().contains(keyword.trim())) .limit(50) // 限制最多50条,防 OOM .collect(Collectors.toList()); } request.setAttribute("results", results); %> <!-- 渲染 results 列表,复用 message-item.jspf -->

注意事项:stream().filter()是内存操作,当消息队列超 10 万条时会明显卡顿。本资源包已预设保护:getServletContext().setAttribute("maxQueueSize", 10000),并在send分支中检查队列大小,超限时queue.poll()弹出最老消息。这是用空间换时间的务实选择。

从那以后我每次做 Java Web 课程设计,都会先搭一个最小可行的ChatServlet骨架,把synchronized锁、ServletContext共享、Session游标这三根柱子立稳,再往上堆功能。因为只要这三根柱子歪了,后面加 WebSocket、加 Redis、加 JWT,全是给危房贴金箔。希望帮到你。

本文还有配套的精品资源,点击获取

相关新闻

ICEEMDAN-CMBE与Bayes-TCN-BiLSTM故障诊断SHAP解读

ICEEMDAN-CMBE与Bayes-TCN-BiLSTM故障诊断SHAP解读

搞过旋转机械故障诊断的朋友应该都有体会&#xff0c;最难的不是把某个单一算法跑通&#xff0c;而是把“信号进来—特征出来—故障判出来—结果让人信服”这条链路完整串起来。这套项目标题里写的ICEEMDAN-CMBE特征提取、Bayes-TCN-BiLSTM故障诊断、SHAP可解释分析&#xff0c…

2026/10/20 7:33:04 阅读更多 →
财务分析报告自动生成方案:Python从数据清洗到报告输出全解析

财务分析报告自动生成方案:Python从数据清洗到报告输出全解析

环境&#xff1a;我需要完全遵守设定的角色和任务要求。让我仔细规划这篇关于“自动生成财务分析报告”的博文内容。博文题目是“自动生成财务分析报告”&#xff0c;相关内容必须安全、合规&#xff0c;不涉及任何禁止的话题。我要按照要求生成一篇独立完整的高质量博文&#…

2026/10/20 7:33:04 阅读更多 →
Agent上下文压缩全攻略:五条技术路线与工程落地实践

Agent上下文压缩全攻略:五条技术路线与工程落地实践

上一篇文章里我们聊了上下文工程的基础边界和常见误区&#xff0c;这次把焦点收缩到Agent场景里最刚需的一环&#xff1a;上下文压缩。为什么单独拎出来写一篇&#xff1f;因为我在实际调试一个数据采集Agent的时候&#xff0c;真真切切被"上下文爆掉"这件事坑过——…

2026/10/20 7:32:04 阅读更多 →

最新新闻

Agent上线就崩?从状态管理到可观测性,拆解工程化落地的五大核心难题

Agent上线就崩?从状态管理到可观测性,拆解工程化落地的五大核心难题

1. 演示与上线之间的鸿沟到底在哪做 Agent 项目的人大概都经历过这种场面&#xff1a;内部演示的时候&#xff0c;Agent 流畅地理解需求、调用工具、多步推理、最后给出一个漂亮的答案&#xff0c;会议室里一片掌声。然后老板说“下周上线吧”&#xff0c;结果一上线&#xff0…

2026/10/20 8:13:30 阅读更多 →
查IP地址不用装App:一条命令分清内网IP与公网IP

查IP地址不用装App:一条命令分清内网IP与公网IP

查IP地址这件事&#xff0c;听着特简单&#xff0c;真做起来却有一堆让人上火的小状况。我经常被朋友问“你帮我看看我电脑IP是多少”&#xff0c;结果他说的是宽带师傅问他“你家庭网关的公网IP变没变”&#xff0c;俩人鸡同鸭讲。实际上&#xff0c;“查IP地址”至少分成三个…

2026/10/20 8:13:30 阅读更多 →
大模型跨文化推理评估全指南:从方法到避坑实践

大模型跨文化推理评估全指南:从方法到避坑实践

去年我们团队接了个挺特别的评估任务&#xff1a;给一个大语言模型的推理能力做跨文化适应性测试。最初大家都觉得这活儿不复杂——把现成的英文推理评测集翻译成中文、日文、阿拉伯文&#xff0c;跑一遍看分数不就完了&#xff1f;结果第一次试跑就翻车了。同一道逻辑推理题&a…

2026/10/20 8:13:30 阅读更多 →
AGI核心突破已实现?从理论到智能体落地的工程实践指南

AGI核心突破已实现?从理论到智能体落地的工程实践指南

在 AI 圈子里待久了&#xff0c;你会发现一个规律&#xff1a;每隔几个月&#xff0c;就会有一句“石破天惊”的话炸出来&#xff0c;让行业内外都跟着兴奋或焦虑一回。最近这两天&#xff0c;某前沿AI机构前首席研究官的一番表态&#xff0c;就精准踩中了这个节奏——他直接放…

2026/10/20 8:13:30 阅读更多 →
MySQL 8.0 WITH子句详解:从基础语法到递归CTE优化

MySQL 8.0 WITH子句详解:从基础语法到递归CTE优化

你回想一下&#xff0c;早几年写稍微复杂一点的分析SQL&#xff0c;是不是每次都得先建临时表、再join几层子查询&#xff0c;到了第三层嵌套自己都看不下去&#xff1f;MySQL 8.0的通用表达式&#xff08;WITH&#xff09;出来后&#xff0c;这个问题基本算是翻篇了。我最早接…

2026/10/20 8:13:30 阅读更多 →
S7-1200与博途V16在生物发酵控制系统中的实施经验

S7-1200与博途V16在生物发酵控制系统中的实施经验

做自动化这些年&#xff0c;液体、流量、温控项目接过不少&#xff0c;但真正让人睡不踏实的&#xff0c;还是生物发酵这一类。罐子里养的是活物&#xff0c;微生物不会等我把PID调完再繁殖&#xff0c;迟到一小时&#xff0c;整罐料可能就废了。制药厂对批次记录、灭菌温度曲线…

2026/10/20 8:12:29 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

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

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

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

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

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

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/19 10:02:04 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 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/19 2:54:50 阅读更多 →
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/19 9:10:22 阅读更多 →
黑夜航拍船只数据集训练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/18 12:16:16 阅读更多 →