简介这份资源是一套基于JSPJavaScriptAjaxServletTomcatMySQL实现的一对一网页聊天系统源码面向正在学习JavaWeb、需要课程设计或练手项目的学生与初级开发者重点解决前后端消息收发与定时刷新这一核心交互问题。压缩包共54个文件约2.88MB包含12个Java类与12个class文件承载Servlet与业务逻辑8个JSP页面负责登录、注册、好友列表与聊天界面另有7个jar依赖、4个xml配置及js脚本等结构完整可直接导入Eclipse运行。已有348人学习下载。项目将聊天拆分为发送消息与轮询拉取消息两条Servlet链路前端每秒请求一次获取更新配合c3p0连接池与sql脚本可快速搭建数据库环境适合借此理解Ajax轮询机制、Servlet请求分发与JSP参数传递并在此基础上自行完善UI与扩展功能。1. 从一次“消息发出去对方没收到”说起去年帮一个做职业培训的朋友搭内部答疑工具需求很朴素两个学员和一位助教之间能私聊不要群、不要花哨的社交功能浏览器打开就能用。我第一反应是找现成的 IM 服务结果对接成本比预期高得多最后干脆用 JavaWeb 那套老底子自己写了一个一对一网页聊天系统。它做的事情很聚焦用户登录后看到在线联系人列表点开某个人就能建立一条独立的会话通道消息实时到达对方浏览器历史记录落库可查。技术栈是典型的 Servlet JSP JDBC 前端原生 JavaScript会话层用 WebSocket 或长轮询都能落地数据库存用户、会话、消息三张核心表。适合谁适合正在学 JavaWeb 想找一个“能跑通、能改、逻辑闭环”的练手项目的人也适合需要给内部系统加一个轻量私聊模块的开发者。它不追求高并发架构但把“一对一”这条链路讲透了你拿去改群聊、改客服坐席都有清晰的切入点。2. 项目骨架与技术选型为什么是 Servlet WebSocket 这套组合2.1 整体分层与请求走向这个项目的结构不复杂但分层是清楚的照着目录就能判断一个改动该落在哪一层。常见做法是标准的 Maven Web 工程src/main/java下按包划分src/main/webapp放页面和静态资源。下面是我一般会采用的目录形态你可以对照自己拿到的源码看是否一致chat-one-to-one/ ├── pom.xml ├── src/main/java/com/example/chat/ │ ├── controller/ # Servlet登录、联系人、历史消息接口 │ ├── websocket/ # 会话端点负责消息收发 │ ├── service/ # 业务逻辑会话建立、消息落库 │ ├── dao/ # JDBC 数据访问 │ ├── model/ # User / Conversation / Message 实体 │ └── util/ # 连接池、JSON 工具、会话管理 ├── src/main/resources/ │ ├── db.properties # 数据库连接配置 │ └── schema.sql # 建表脚本 └── src/main/webapp/ ├── login.jsp ├── chat.jsp # 主聊天界面 ├── js/chat.js # WebSocket 客户端逻辑 └── WEB-INF/web.xml一次完整的消息链路是这样的浏览器在chat.jsp里通过chat.js建立 WebSocket 连接握手阶段带上登录时写入 HttpSession 的用户标识服务端的端点收到连接后把userId - session注册进一个全局的在线会话表发送方提交消息时端点先做参数校验再交给 Service 落库然后从在线表里找到接收方的会话把消息推过去接收方浏览器收到帧后渲染气泡。HTTP 那部分 Servlet 只负责登录鉴权和历史消息分页查询实时通道完全走 WebSocket职责切得很干净。为什么不用纯轮询一对一场景下消息频率不高轮询也能用但延迟和无效请求都难看。WebSocket 在 Tomcat 7 以后就是容器原生支持javax.websocket的注解式端点写起来比想象中轻不需要额外引入 Netty 这类重框架。这就是选型的核心逻辑用容器能力解决实时性用 Servlet 解决鉴权和查询两边各干各的。2.2 三张核心表与实体映射数据模型是一对一聊天的地基表设计错了后面全是补丁。这个项目最少需要三张表用户表、会话表、消息表。会话表的存在很关键它把“谁和谁”抽象成一个稳定的conversation_id消息只挂会话不挂人后续要加已读状态、会话置顶都好扩展。-- 用户表登录与在线状态的基础 CREATE TABLE t_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(128) NOT NULL, -- 存加盐哈希别存明文 nickname VARCHAR(50), avatar VARCHAR(255), created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 会话表一对一关系的唯一锚点 CREATE TABLE t_conversation ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_a BIGINT NOT NULL, -- 约定 user_a user_b避免重复建会话 user_b BIGINT NOT NULL, last_msg_at DATETIME, UNIQUE KEY uk_pair (user_a, user_b) ); -- 消息表所有聊天内容落这里 CREATE TABLE t_message ( id BIGINT PRIMARY KEY AUTO_INCREMENT, conversation_id BIGINT NOT NULL, sender_id BIGINT NOT NULL, content TEXT NOT NULL, msg_type TINYINT DEFAULT 1, -- 1文本 2图片预留扩展 created_at DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_conv_time (conversation_id, created_at) );参数说明几个容易忽略的点。uk_pair唯一索引配合“小 id 在前”的写入约定能保证同一对用户永远只有一个会话避免并发下建出两条会话导致消息分裂。idx_conv_time是给历史消息分页用的按会话加时间排序走这个索引翻页不会全表扫。msg_type现在只用文本但字段先留着后面加图片消息不用改表结构。密码字段长度给到 128是因为加盐哈希后的字符串比明文长得多用 MD5 那种 32 位反而是血泪教训别省这个空间。2.3 在线会话表内存里的“通讯录”WebSocket 端点是无状态的容器为每个连接创建一个端点实例所以“谁在线、对应哪个会话”必须放在一个共享的地方。这个项目里最典型的是一个线程安全的静态 Map键是用户 id值是Session对象。public final class OnlineSessionRegistry { // ConcurrentHashMap 保证多连接并发注册时的线程安全 private static final MapLong, Session ONLINE new ConcurrentHashMap(); public static void register(Long userId, Session session) { ONLINE.put(userId, session); } public static void remove(Long userId) { ONLINE.remove(userId); } public static Session get(Long userId) { return ONLINE.get(userId); } public static SetLong onlineUserIds() { return ONLINE.keySet(); } }这段代码短但它是整个实时链路的中枢。ConcurrentHashMap而不是HashMap是因为多个 WebSocket 线程会同时注册和移除普通 Map 在高并发下会出问题。注册时机在OnOpen里移除在OnClose和异常回调里都要做只写一处的话客户端异常断线会留下“幽灵在线”记录联系人列表里显示在线但消息发不过去这是这个项目最常见的翻车点之一。另外要注意单机内存表在多实例部署下不成立如果后面要横向扩展得换成 Redis 的发布订阅这一点在改造章节会展开。3. 核心链路拆解一条消息从发送到渲染的完整实现3.1 服务端端点握手、注册与消息分发WebSocket 端点承担了实时通道的全部职责写法上用注解式端点最省事。下面这段是核心骨架包含连接建立、消息接收、连接关闭三个回调ServerEndpoint(/ws/chat/{userId}) public class ChatEndpoint { OnOpen public void onOpen(Session session, PathParam(userId) Long userId) { // 把当前连接登记到在线表供其他用户定向推送 OnlineSessionRegistry.register(userId, session); session.getUserProperties().put(userId, userId); } OnMessage public void onMessage(String payload, Session session) { Long senderId (Long) session.getUserProperties().get(userId); // payload 是 JSON{to:2,content:在吗} ChatMessage msg JsonUtil.parse(payload, ChatMessage.class); if (msg.getTo() null || msg.getContent() null || msg.getContent().trim().isEmpty()) { return; // 空消息直接丢弃别让它进库 } // 1. 找到或创建会话2. 落库3. 推给接收方 Long convId ConversationService.getOrCreate(senderId, msg.getTo()); Message saved MessageService.save(convId, senderId, msg.getContent()); Session target OnlineSessionRegistry.get(msg.getTo()); if (target ! null target.isOpen()) { target.getAsyncRemote().sendText(JsonUtil.toJson(saved)); } // 发送方自己也回显一份保证本地气泡和服务端 id 一致 session.getAsyncRemote().sendText(JsonUtil.toJson(saved)); } OnClose public void onClose(Session session) { Long userId (Long) session.getUserProperties().get(userId); OnlineSessionRegistry.remove(userId); } OnError public void onError(Session session, Throwable t) { // 异常也要清理否则在线表会残留失效连接 Long userId (Long) session.getUserProperties().get(userId); OnlineSessionRegistry.remove(userId); } }逐段说逻辑。OnOpen里的PathParam(userId)让连接地址自带用户标识省去握手后再发一条认证消息的麻烦但生产环境不能只信路径参数常见做法是在握手前用ServerEndpointConfig.Configurator校验 HttpSession或者用一次性 token 换连接防止有人伪造 id 连上来偷消息。OnMessage里先校验再落库空内容和超长内容都要拦TEXT字段虽然能存很大但一条几兆的消息推给浏览器会卡死页面。getAsyncRemote()而不是getBasicRemote()是因为同步发送在推给多个目标时会阻塞当前线程异步发送更适合这种“一发多收”的场景。OnError里重复清理在线表是踩过坑之后的习惯客户端网络抖动断开时OnClose不一定被调用异常回调是最后一道保险。3.2 会话与消息的 Service 层幂等建会话与分页查询Service 层要做两件容易写错的事并发下保证会话唯一以及历史消息按时间倒序分页。先看建会话public class ConversationService { public static Long getOrCreate(Long u1, Long u2) { // 约定小 id 在前保证同一对用户只对应一条记录 long a Math.min(u1, u2); long b Math.max(u1, u2); Long id ConversationDao.findByPair(a, b); if (id ! null) { return id; } try { return ConversationDao.insert(a, b); } catch (DuplicateKeyException e) { // 并发下另一个线程先插入了回查一次即可 return ConversationDao.findByPair(a, b); } } }这里的核心是“先查后插 唯一索引兜底 捕获重复键回查”。只写先查后插两个用户同时发第一条消息时会各插一条消息就分裂到两个会话里历史记录看起来像丢了一半。只靠唯一索引不捕获异常第二个线程直接 500。两者结合才是幂等的正确姿势。Math.min/max的排序约定必须和表上的uk_pair索引方向一致否则唯一约束形同虚设。历史消息查询走分页别一次性把整个会话拉出来public static ListMessage page(Long convId, Long beforeId, int size) { // 用 id 游标而不是 offset翻页越深也不会变慢 String sql SELECT id, conversation_id, sender_id, content, created_at FROM t_message WHERE conversation_id ? (beforeId ! null ? AND id ? : ) ORDER BY id DESC LIMIT ?; // ... JDBC 参数绑定返回后由前端反转顺序渲染 }参数说明beforeId是上一页最旧那条消息的 id首次加载传 nullsize一般给 20 到 30太大首屏渲染慢。用 id 游标而不是LIMIT offset, size是因为 offset 在数据量大时会扫描并丢弃前面所有行聊天记录越聊越多这个差异会越来越明显。返回结果按 id 倒序前端渲染前反转成时间正序这样“加载更多”是往顶部插符合聊天界面的直觉。3.3 前端 chat.js连接、重连与消息渲染前端这块没有框架原生 JavaScript 就够关键是连接管理和重连策略。很多人第一次写 WebSocket 只写一个new WebSocket()断线之后页面就静默失效了用户以为对方没回其实是连接早断了。let ws null; let retry 0; function connect() { const userId document.body.dataset.userId; ws new WebSocket(ws://${location.host}/ws/chat/${userId}); ws.onopen () { retry 0; // 连上就重置重试计数 setStatus(在线); }; ws.onmessage (evt) { const msg JSON.parse(evt.data); appendBubble(msg); // 渲染气泡自己发的靠右 }; ws.onclose () { setStatus(已断开重连中); // 指数退避避免服务端刚重启就被大量重连打满 const delay Math.min(1000 * Math.pow(2, retry), 30000); setTimeout(connect, delay); }; } function send(to, content) { if (!ws || ws.readyState ! WebSocket.OPEN) { alert(连接未就绪请稍候); return; } ws.send(JSON.stringify({ to, content })); } connect();逻辑说明onopen里重置retry否则一次断线后重试计数只增不减退避时间会一路涨到 30 秒上限。onclose里的指数退避是必须的服务端重启的瞬间所有客户端同时重连没有退避就是自己打自己。send前检查readyState是因为用户可能在断线期间点发送直接ws.send会抛异常。appendBubble里要做 XSS 防护消息内容用textContent而不是innerHTML这一点在避坑章节还会提。另外ws://在 HTTPS 页面下会被浏览器拦截部署到线上要换成wss://本地开发用ws://没问题。4. 跑起来环境准备、建库与启动验证4.1 环境清单与依赖版本这个项目对环境的胃口不大但版本对不上会浪费很多时间。下面是我验证过能跑通的组合你按这个对齐基本不会卡在启动阶段组件建议版本说明JDK8 或 11注解式 WebSocket 在 8 上就可用Tomcat8.5 / 9必须支持javax.websocketTomcat 10 换成了 jakarta 包名直接跑会报类找不到MySQL5.7 / 8.08.0 注意驱动包和时区参数Maven3.6用来拉依赖和打 war浏览器任意现代浏览器需要支持 WebSocket最容易翻车的是 Tomcat 版本。项目里import javax.websocket.*如果部署到 Tomcat 10容器提供的是jakarta.websocket启动时端点注册失败日志里是一串ClassNotFoundException。要么换 Tomcat 9要么把源码里的包名整体迁移二选一别混着来。MySQL 8.0 还要注意连接串加serverTimezoneAsia/Shanghai否则时间字段可能差 8 小时聊天记录的时间戳看起来会很怪。4.2 建库、改配置、打 war 包第一步先把库建好schema.sql直接执行然后按你的环境改db.propertiesjdbc.urljdbc:mysql://127.0.0.1:3306/chat_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password你的密码 jdbc.drivercom.mysql.cj.jdbc.Driver第二步打包部署。用 Maven 的话一条命令出 war# 在项目根目录执行跳过测试加快打包 mvn clean package -DskipTests # 产物在 target/ 下复制到 Tomcat 的 webapps 目录 cp target/chat-one-to-one.war $TOMCAT_HOME/webapps/ # 启动 Tomcat $TOMCAT_HOME/bin/startup.sh参数说明-DskipTests只是加快本地打包正式构建别加。war 包名决定访问路径chat-one-to-one.war部署后访问http://localhost:8080/chat-one-to-one/login.jsp。如果你用 IDEA 直接配 Tomcat 跑注意 Application context 要设成和 war 包名一致否则页面里的相对路径会 404。数据库连接池如果用的是 Druid 或 HikariCPdb.properties里的池参数按默认值先跑通别一上来就调最大连接数本地开发用不到。4.3 双开验证两个浏览器窗口的完整走查跑起来之后怎么确认它真的通了别只开一个窗口自己发给自己那样测不出定向推送。正确姿势是用两个不同浏览器或者一个正常窗口加一个无痕窗口分别登录两个账号然后按这个顺序走两个窗口都登录观察联系人列表里对方是否显示在线。如果显示离线先查在线表注册逻辑和OnOpen是否被触发。A 向 B 发一条消息B 的页面应当即时出现气泡A 的页面也应有回显。B 没收到但 A 有回显说明落库和回显正常问题在定向推送重点看OnlineSessionRegistry.get拿到的 session 是否为 null。刷新 B 的页面历史消息应当从库里加载出来。刷新后丢失说明查询接口或分页参数有问题。关掉 B 的窗口A 再发消息服务端不应报错消息仍应落库。这一步验证的是接收方离线时的降级处理。重启 Tomcat两个窗口都应自动重连状态从“已断开”回到“在线”。这五步走完基本能覆盖连接、推送、持久化、离线、重连五条主链路。哪一步断了日志里通常有明确线索Tomcat 的catalina.out里搜ChatEndpoint就能定位到具体回调。5. 避坑与排查五个真实踩过的坑5.1 消息发出去对方收不到但自己能看到回显现象发送方气泡正常出现接收方页面毫无反应服务端日志无异常。原因通常是接收方不在线表里或者表里存的是失效连接。常见于客户端异常断线后OnClose没触发ONLINE里残留了旧的SessionisOpen()返回 false 但代码没判断。解决推送前必须判target ! null target.isOpen()同时在OnError里也执行移除排查时可以在onMessage里打印在线表的 key 集合一眼就能看出接收方在不在。5.2 部署到 Tomcat 10 启动即报类找不到现象war 包放进去启动日志里一堆java.lang.ClassNotFoundException: javax.websocket.server.ServerEndpoint。原因Tomcat 10 起把 Java EE 迁移到 Jakarta EE包名从javax.*变成jakarta.*源码里的注解和容器提供的 API 对不上。解决最省事是换回 Tomcat 9如果必须用 10就把所有javax.websocket、javax.servlet的 import 改成jakarta对应包并同步调整pom.xml里的依赖坐标改完重新打包。5.3 聊天记录时间差 8 小时现象数据库里created_at看着正常页面上显示的时间却比实际早或晚 8 小时。原因MySQL 8.0 驱动默认按 UTC 解析时间而服务器和数据库都在东八区连接串没指定时区。解决JDBC URL 加serverTimezoneAsia/Shanghai同时确认 MySQL 服务端的time_zone设置如果历史数据已经写歪了别急着批量改先确认是展示层转换问题还是存储层问题展示层用前端本地时区格式化往往更稳。5.4 同一条消息在会话里出现两次现象快速连点发送或者网络抖动后重发接收方看到两条一样的内容。原因前端没有防重复提交服务端也没有幂等键两次请求各自落库。解决前端发送后短暂禁用按钮更彻底的做法是客户端为每条消息生成一个client_msg_id消息表加唯一索引服务端插入时捕获重复键直接返回已有记录。这个改动不大但对体验提升明显尤其是移动网络不稳的场景。5.5 消息内容里的 HTML 被当成标签执行现象有人发了一段带script的文本接收方页面弹窗或布局错乱。原因前端渲染用了innerHTML直接拼接消息内容。解决统一用textContent赋值需要富文本时走白名单过滤别自己写正则去“清洗”正则漏网的概率比想象中高。服务端落库前也可以做一次长度和字符校验但根本防线在前端渲染方式。6. 二次开发从单机私聊到可扩展的会话服务拿到这个项目之后最值得先动的地方不是加功能而是把“在线表”这个单机内存结构抽象掉。现在OnlineSessionRegistry是静态 Map单实例跑没问题一旦部署两个节点A 连在节点一、B 连在节点二消息就推不过去。我的改法是引入一个MessageRouter接口单机实现走内存表集群实现走 Redis 发布订阅每个节点订阅自己的频道发送方把消息投到接收方所在节点的频道节点收到后再从本地在线表找 session 推送。接口先定好实现可替换这样本地开发不用起 Redis线上再切集群实现。public interface MessageRouter { // 把消息投递给目标用户具体走内存还是走消息中间件由实现决定 void route(Long targetUserId, Message msg); } // 单机实现直接查本地在线表 public class LocalRouter implements MessageRouter { Override public void route(Long targetUserId, Message msg) { Session s OnlineSessionRegistry.get(targetUserId); if (s ! null s.isOpen()) { s.getAsyncRemote().sendText(JsonUtil.toJson(msg)); } } }第二个改造点是消息可靠性的验证方法。现在消息落库和推送是顺序执行的推送失败消息仍在库里接收方下次拉历史能看到这算一种兜底。但如果你要做“已送达”状态就得在客户端收到消息后回一个 ack服务端更新消息状态。验证这套逻辑别靠肉眼写一个简单的压测脚本模拟两个客户端互发一千条统计落库条数、推送条数、ack 条数三者是否一致不一致就说明某条链路有丢消息的窗口。这个习惯救过我很多次尤其是改完推送逻辑自以为没问题的时候。第三个技巧是关于会话列表的。现在联系人列表是查全量用户再标在线状态用户一多就慢。改成“最近会话优先”更符合真实使用按t_conversation.last_msg_at倒序取前 20 个再补上在线但没聊过的人。last_msg_at在每次发消息时更新这个字段现在表里就有只是很多人没把它用起来。改完之后首屏查询从全表扫变成走索引体验和性能一起改善。最后说个我自己的教训。早些年改这类实时项目我总习惯先动前端看效果结果后端会话状态一乱前端怎么调都是玄学。后来我定了个规矩任何涉及连接和推送的改动先在服务端日志里把“谁连上、谁断开、消息投给了谁”打全用两个窗口走一遍第 4 章那五步验证确认链路干净了再碰界面。从那以后我每次改 WebSocket 相关代码都强制先把在线表的增删打点跑一遍再谈功能。希望这个项目和你手上的改造也能少走点弯路帮到你。本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
ASP三级菜单源码:三级联动实现、部署排错与缓存优化 简介:ASP三级菜单源码是一份面向Web开发者的导航实现参考代码,适合正在用ASP构建站点、需要多层级栏目或产品分类的开发者,尤其贴合电子商务网站的产品目录场景。压缩包为rar格式,约1.81MB,内含核心ASP源码、HTML示例页… · 2026/11/2 21:32:20
Windows下从OSGeo4W提取PDAL:点云处理链实战指南 简介:这份资源是面向点云数据处理开发者与GIS从业者的OSGeo4W离线安装包,内含PDAL点云抽象库及配套工具链,主要解决官网下载速度慢、网络条件不佳时难以获取安装程序的问题。压缩包共4159个文件,约233.75MB,以py脚本、… · 2026/11/2 21:32:20
随机波浪模拟与波浪力计算:JONSWAP谱到Morison方程 简介:面向海洋工程方向学习者与结构设计人员的随机波浪计算代码包,围绕JONSWAP谱随机波浪模拟、小振幅理论下的波浪速度场求解,以及Morison方程计算小尺寸结构物所受波浪力三条主线展开,可服务于海洋平台、立管等结构的耐波性分析… · 2026/11/2 21:32:20
YOLOv5跨平台验证:PC端沙盒构建与RK3588部署基准线 1. 为什么先在PC端跑通YOLOv5,而不是直接烧录到香橙派RK3588?你拿到一块崭新的香橙派RK3588开发板,拆开包装、接上电源、插好HDMI线,满心期待地想立刻跑起YOLOv5做目标检测——结果卡在第一步:连Ubuntu系统都装不进去&… · 2026/11/2 22:15:42
Apipost接口压测实战指南:从调试到性能治理 1. 为什么选Apipost做压测,而不是JMeter或k6?很多人第一次接触接口压力测试时,下意识会去搜“JMeter教程”——毕竟它名气大、资料多、社区全。但我在某跨平台系统上线前的性能验证阶段,用JMeter跑了三天,卡在三个地方… · 2026/11/2 22:15:42
UniApp 打包 Windows exe 实战:Electron 套壳与原生能力 1. 方案选型:UniApp 为什么不能直接吐出一个 exe先说结论,省得有人翻到一半才发现方向错了:UniApp 打包项目为桌面端 exe 文件,官方并没有一条一键命令。你在 HBuilderX 的"发行"菜单里翻遍,能看到 App、小程… · 2026/11/2 22:15:42
DeepSeek知识平台:智能知识管理的技术实践与优化 1. DeepSeek知识平台解析:下一代智能知识管理的技术实践上周在调试一个RAG系统时,偶然发现DeepSeek团队开源的知识处理工具链在效果评估中表现突出。这个发现让我开始系统性研究他们的技术方案,结果挖出了一整套值得推荐的知识管理新范式。不… · 2026/11/2 22:15:42
无线传感器网络多路径选择协议与Matlab实现 1. 项目背景与核心挑战无线传感器网络(WSNs)作为物联网的基础设施,在环境监测、工业控制等领域发挥着关键作用。多跳传输是其典型特征,但面临两大核心挑战:一是硬件噪声导致的信号衰减,二是窃听者引发的数据安全问题。传统单路径传… · 2026/11/2 22:15:42
Agent静默截断:上下文链路中的隐性数据丢失与工程防御 1. 这不是模型“看错了”,是输入链路上的静默失血你刚跑完一轮 Agent 测试,日志里清清楚楚写着“源端输入 50 条结构化数据”,可模型推理时的 prompt 里只塞进了前 20 条——中间那 30 条呢?没报错、没警告、没超时,连… · 2026/11/2 22:14:42
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 4:42:45
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 5:17:18
雷达信号调制技术解析:从LFM到伪线性调频原理与应用 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 1:01:15
基于MAX14431与TM4C123的工业SPI隔离电路设计全攻略 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 1:01:15
PLFM_RADAR:相位线性调频雷达信号处理框架 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 1:01:15
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 4:42:45
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 5:17:18