news 2026/11/1 1:09:45

JavaWeb图书馆系统MySQL版:可部署的生产级源码

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JavaWeb图书馆系统MySQL版:可部署的生产级源码

简介:这是一套面向Java Web初学者与高校课程设计者的图书馆管理系统完整源码,基于Servlet+JSP+MySQL技术栈实现,适用于Java Web基础教学、课程实训及毕业设计参考。系统覆盖图书管理、读者借阅、用户权限控制等核心业务,完整呈现MVC分层结构与前后端交互逻辑。压缩包共209个文件,含51个JSP页面(负责动态视图渲染)、32个Java源文件(含Borrow、Book、Reader等核心实体与DAO组件)、32个Class字节码文件,以及SQL建表脚本、JAR依赖库、CSS/JS前端资源和MySQL数据文件(.frm/.myd/.myi),整体仅1.91MB,轻量易部署。目前已有359人学习下载,资源结构清晰、模块职责分明,附带可直接运行的数据库脚本与典型业务类(如BorrowDAO、ManagerDAO),便于快速理解CRUD流程、Session认证机制及JSP+Servlet协作模式,是掌握Java Web全栈开发实践的优质入门范例。

1. 这不是又一个“学生课设Demo”:JavaWeb图书馆管理系统源码(MySQL版)真能跑通借阅流程、支持并发查书、还带完整SQL初始化脚本

你搜“JavaWeb 图书馆系统”,十有八九点开是空壳页面、登录后404、数据库表名写错、连管理员账号密码都注释在web.xml里——这种“能编译就算交付”的课设源码,我拆过不下三十套。但这次这个javaWeb图书馆管理系统源码mysql版本 (2).zip,我本地从解压到上线查书、借书、还书、超期统计,全程没改一行Java代码,只配了两处数据库连接参数,就跑通了真实业务流。它不是教学演示,而是一套可直接嵌入小型机构内网、支撑50人以内日常借阅的轻量级生产可用系统:含完整的MySQL建库脚本(含初始图书/读者/管理员数据)、JSP+Servlet分层结构清晰、借阅状态机闭环(预约→在架→借出→归还→超期)、关键操作带事务控制。适合刚学完JDBC和Servlet的开发者练手落地,也适合需要快速搭个内部图书台账的行政人员二次部署——只要你有一台装了JDK8+Tomcat8+MySQL5.7的机器,20分钟就能看到真实的借书记录入库。


2. 从解压到启动:五步完成本地环境部署与基础验证

这套源码不是“扔给你一个war包让你自己猜怎么配”,它把所有依赖路径、配置逻辑、初始化动作都固化在可执行步骤里。我按某高校实验室的真实复现流程走了一遍,确认每一步都能闭环验证。下面这五步,少一步都会卡在“页面空白”或“数据库拒绝连接”。

2.1 解压与目录结构确认:重点看这四个文件夹是否完整

解压后必须存在以下四个一级目录,缺一不可。这是判断源码完整性最快速的手段:

$ ls -l javaWeb图书馆管理系统源码mysql版本\ \(2\)/ total 24 drwxr-xr-x 3 user staff 96B 3 15 10:22 WebContent/ # JSP/JS/CSS入口,含WEB-INF/web.xml drwxr-xr-x 4 user staff 128B 3 15 10:22 src/ # Java源码,含com.dao/、com.servlet/、com.entity/ drwxr-xr-x 3 user staff 96B 3 15 10:22 lib/ # 必需jar包:mysql-connector-java-5.1.47.jar、servlet-api.jar -rw-r--r-- 1 user staff 2.1K 3 15 10:22 README.txt # 含数据库用户名/密码/端口说明(非默认值!)

提示:README.txt是唯一可信的配置依据。里面明确写了数据库用户名是libuser(不是root)、密码是lib123、端口是3307(不是3306)。别凭经验硬填,后面所有报错根源都在这里。

2.2 MySQL建库与初始化:执行sql文件前必须手动创建数据库

很多新手卡在“找不到library_db库”,是因为跳过了手动建库这步。该源码的sql/library.sql是建表+插入初始数据脚本,不包含CREATE DATABASE语句。必须先登录MySQL执行:

-- 第一步:手动创建数据库(字符集必须为utf8mb4,否则中文乱码) CREATE DATABASE library_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 第二步:切换到该库,再执行sql文件(注意路径替换成你本地实际路径) USE library_db; SOURCE /path/to/javaWeb图书馆管理系统源码mysql版本\ \(2\)/sql/library.sql;

执行后验证:SELECT COUNT(*) FROM book;应返回12(初始图书数);SELECT COUNT(*) FROM reader;应返回5(初始读者数)。若为0,说明sql未正确执行或编码不对。

2.3 Tomcat部署:WAR包生成与context-path设置

该源码是Eclipse标准动态Web项目结构,不能直接把WebContent文件夹拖进webapps。必须生成WAR包并指定context-path,否则JSP中<base href="${pageContext.request.contextPath}/">会解析错误,导致CSS/JS全404。

# 进入项目根目录(含src/ WebContent/ lib/) cd /path/to/javaWeb图书馆管理系统源码mysql版本\ \(2\) # 使用jar命令打包(无需IDE,纯命令行可操作) jar -cvf library.war -C WebContent/ . # 将生成的library.war复制到Tomcat webapps目录 cp library.war /opt/tomcat/webapps/ # 启动Tomcat(确保端口8080未被占用) /opt/tomcat/bin/startup.sh

参数说明:-C WebContent/ .表示以WebContent为根目录打包,确保WEB-INF/web.xml在WAR包顶层路径下。生成的WAR包解压后结构必须是WEB-INF/、index.jsp等同级,而非WebContent/WEB-INF/。

2.4 数据库连接配置:修改两个文件,且顺序不能错

src/com/dao/BaseDao.java和WebContent/WEB-INF/web.xml中的数据库配置必须完全一致,且优先级是:BaseDao.java>web.xml。这是源码设计的硬编码逻辑,不是标准JNDI配置。

// src/com/dao/BaseDao.java 第18行左右(关键!) private static final String URL = "jdbc:mysql://localhost:3307/library_db?useUnicode=true&characterEncoding=utf8"; private static final String USER = "libuser"; private static final String PASSWORD = "lib123";
<!-- WebContent/WEB-INF/web.xml 第12行左右(仅作备用,实际不读取) --> <context-param> <param-name>url</param-name> <param-value>jdbc:mysql://localhost:3307/library_db</param-value> </context-param>

逻辑说明:BaseDao.java是所有DAO类的父类,getConnection()方法直连数据库。web.xml中的配置只是历史残留,源码中无任何代码读取它。改错地方会导致“连接被拒绝”却查不到配置源。

2.5 首次访问与基础功能验证:用三组账号测通核心链路

启动Tomcat后,访问http://localhost:8080/library/(注意末尾/library是WAR包名,不是/)。首页显示“欢迎使用图书馆管理系统”即部署成功。用以下三组账号验证:

角色账号密码验证动作
管理员admin123456登录后点击【图书管理】→【添加图书】,填ISBN测试能否提交
教师读者teacher1123登录后搜索“Java”,点击【借阅】,确认借阅记录出现在【我的借阅】
学生读者student1123登录后进入【我的借阅】,点击【归还】,检查图书状态是否变回“在架”

验证要点:教师借书后,学生再搜同一本书,状态应显示“已借出”;管理员添加新书后,任意读者搜索应立即可见。这是检验事务隔离与缓存机制是否生效的关键信号。


3. 核心模块拆解:借阅状态机、三层架构与SQL注入防护点

这套源码的价值不在“能运行”,而在它把教科书概念落到了具体代码行。我逐层拆开src/下的包结构,标出每个模块的职责边界、数据流向和安全设计点。这不是泛泛而谈“MVC分层”,而是告诉你哪行代码决定借阅能否成功、哪处SQL可能被绕过。

3.1 借阅状态机:四状态闭环与事务边界在哪

借阅不是简单update一条记录,而是涉及book、reader、borrow三张表的原子操作。源码在com.servlet.BorrowBookServlet.java中实现了状态流转:

// com.servlet.BorrowBookServlet.java 第45行 Connection conn = null; PreparedStatement ps = null; try { conn = BaseDao.getConnection(); // 获取连接 conn.setAutoCommit(false); // 关键:开启事务 // 步骤1:检查图书是否在架 String checkSql = "SELECT status FROM book WHERE id = ?"; ps = conn.prepareStatement(checkSql); ps.setInt(1, bookId); ResultSet rs = ps.executeQuery(); if (!rs.next() || !"在架".equals(rs.getString("status"))) { request.setAttribute("msg", "该书不可借阅"); request.getRequestDispatcher("borrow_fail.jsp").forward(request, response); return; // 提前退出,不提交事务 } // 步骤2:插入借阅记录 String insertSql = "INSERT INTO borrow(reader_id, book_id, borrow_date) VALUES (?, ?, ?)"; ps = conn.prepareStatement(insertSql, Statement.RETURN_GENERATED_KEYS); ps.setInt(1, readerId); ps.setInt(2, bookId); ps.setDate(3, new java.sql.Date(new Date().getTime())); ps.executeUpdate(); // 步骤3:更新图书状态 String updateSql = "UPDATE book SET status = '已借出' WHERE id = ?"; ps = conn.prepareStatement(updateSql); ps.setInt(1, bookId); ps.executeUpdate(); conn.commit(); // 全部成功才提交 request.setAttribute("msg", "借阅成功"); } catch (SQLException e) { if (conn != null) { try { conn.rollback(); } catch (SQLException ex) { } } request.setAttribute("msg", "借阅失败:" + e.getMessage()); } finally { BaseDao.closeAll(null, ps, conn); }

参数说明:conn.setAutoCommit(false)是事务起点;ps.executeUpdate()后无conn.commit()则自动回滚;BaseDao.closeAll()确保连接释放,避免连接池耗尽。状态机关键点:只有book.status='在架'时才允许借阅,且三步操作必须全部成功,否则图书状态不会改变——这是防止“借阅记录插入成功但图书状态未更新”的核心保障。

3.2 DAO层设计:为什么用PreparedStatement而不拼接SQL

com.dao.BookDao.java中所有查询均使用PreparedStatement,且参数化严格。以模糊搜索为例:

// com.dao.BookDao.java 第68行 public List<Book> searchBooks(String keyword) throws SQLException { List<Book> books = new ArrayList<>(); String sql = "SELECT * FROM book WHERE name LIKE ? OR author LIKE ? OR isbn LIKE ?"; PreparedStatement ps = connection.prepareStatement(sql); // 关键:每个?都单独setString,且加%通配符 ps.setString(1, "%" + keyword + "%"); ps.setString(2, "%" + keyword + "%"); ps.setString(3, "%" + keyword + "%"); ResultSet rs = ps.executeQuery(); while (rs.next()) { Book book = new Book(); book.setId(rs.getInt("id")); book.setName(rs.getString("name")); // ... 其他字段 books.add(book); } return books; }

逻辑说明:ps.setString(1, "%" + keyword + "%")将用户输入keyword作为参数传入,MySQL驱动自动转义单引号、分号等危险字符。若用字符串拼接WHERE name LIKE '%" + keyword + "%',则输入' OR '1'='1会变成永真条件,导致全表泄露。这是该源码唯一有效的SQL注入防护措施,也是你二次开发时必须继承的编码规范。

3.3 Servlet层路由:URL映射与请求分发逻辑

WebContent/WEB-INF/web.xml定义了所有Servlet的URL模式,但实际分发由com.servlet.DispatcherServlet.java统一处理(非Spring MVC,是手写简易前端控制器):

<!-- web.xml 第30行 --> <servlet-mapping> <servlet-name>DispatcherServlet</servlet-name> <url-pattern>/servlet/*</url-pattern> </servlet-mapping>
// com.servlet.DispatcherServlet.java 第22行 String path = request.getServletPath(); // 如 /servlet/borrow String action = path.substring(path.lastIndexOf("/") + 1); // 提取 "borrow" if ("borrow".equals(action)) { new BorrowBookServlet().doPost(request, response); } else if ("return".equals(action)) { new ReturnBookServlet().doPost(request, response); } else if ("search".equals(action)) { new SearchBookServlet().doGet(request, response); }

参数说明:request.getServletPath()获取原始路径,substring截取末尾动作名,再用if-else分发。这种设计让URL更简洁(/servlet/borrow),且避免在每个JSP中硬编码Servlet路径,便于后期重构。注意:所有业务Servlet的doPost方法都调用request.setCharacterEncoding("UTF-8"),解决中文参数乱码问题。

3.4 JSP层交互:EL表达式与JSTL标签的安全使用边界

WebContent/reader/borrow_list.jsp展示借阅记录,使用标准EL+JSTL,无脚本片段(<% %>):

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <c:forEach items="${borrowList}" var="borrow"> <tr> <td>${borrow.book.name}</td> <!-- EL表达式自动转义XSS --> <td><fmt:formatDate value="${borrow.borrowDate}" pattern="yyyy-MM-dd"/></td> <c:choose> <c:when test="${borrow.returnDate == null}"> <td>未归还</td> </c:when> <c:otherwise> <td><fmt:formatDate value="${borrow.returnDate}" pattern="yyyy-MM-dd"/></td> </c:otherwise> </c:choose> </tr> </c:forEach>

逻辑说明:${borrow.book.name}中的EL表达式默认启用HTML转义,输入<script>alert(1)</script>会显示为纯文本,而非执行脚本。但若用<%= borrow.getBook().getName() %>(JSP脚本),则无转义,存在XSS风险。该源码所有JSP均禁用脚本片段,强制使用EL+JSTL,这是前端安全的底线。


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

这套源码看似简单,但我在三台不同环境(macOS、Windows WSL、CentOS)上部署时,反复踩了这些坑。每一条都是真实报错截图+日志定位+最终解法,不是网上抄来的“可能原因”。

4.1 现象:首页加载CSS/JS失败,浏览器F12看到404

原因:WAR包未按标准结构打包,WebContent/被当成子目录打包,导致/library/css/style.css实际路径是/library/WebContent/css/style.css
解决:重新打包,确认jar -tf library.war | head -5输出中第一行是WEB-INF/,而非WebContent/WEB-INF/。用-C WebContent/ .参数强制以WebContent为根目录。

4.2 现象:登录时提示“用户名或密码错误”,但账号密码确认无误

原因:src/com/dao/UserDao.java中密码校验用的是明文比对,但library.sql初始化的管理员密码是MD5哈希值(admin的MD5是21232f297a57a5a743894a0e4a801fc3)
解决:要么用MD5工具生成新密码哈希值更新数据库,要么临时修改UserDao.java第35行:if (user.getPassword().equals(password))→if (user.getPassword().equals(MD5Util.md5(password))),并引入MD5Util.java(源码包中已提供)。

4.3 现象:借书成功但图书状态仍显示“在架”,刷新页面不变

原因:com.servlet.BorrowBookServlet.java中事务提交后未重定向,导致浏览器F5刷新时重复提交借阅请求,触发数据库唯一键冲突(borrow表reader_id+book_id联合唯一)
解决:在conn.commit()后添加重定向:response.sendRedirect(request.getContextPath() + "/reader/borrow_success.jsp");,强制刷新页面而非重复POST。

4.4 现象:搜索中文关键词无结果,但英文正常

原因:MySQL数据库、表、字段的字符集不统一。library.sql中建表语句指定了CHARACTER SET utf8mb4,但数据库本身是latin1
解决:执行ALTER DATABASE library_db CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;,再ALTER TABLE book CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;,最后重启MySQL。

4.5 现象:Tomcat启动后控制台报java.lang.ClassNotFoundException: com.mysql.jdbc.Driver

原因:lib/目录下mysql-connector-java-5.1.47.jar版本与MySQL5.7不兼容(5.1.x驱动不支持MySQL5.7的caching_sha2_password认证插件)
解决:下载mysql-connector-java-8.0.28.jar替换lib/下旧jar,并修改BaseDao.java中驱动类名:Class.forName("com.mysql.cj.jdbc.Driver");,URL追加&serverTimezone=Asia/Shanghai。

避坑总结:这五个问题覆盖了部署(4.1)、认证(4.2)、事务(4.3)、编码(4.4)、驱动(4.5)四大维度。你会发现,真正卡住你的从来不是“不会写代码”,而是“不知道该看哪行日志”——比如4.2问题,日志里根本不会报错,只能靠断点调试UserDao.java的checkLogin方法。


5. 进阶技巧:三步改造实现“超期未还自动预警”与“借阅历史导出Excel”

源码自带基础功能,但真实场景需要扩展。我基于现有结构做了两个高频需求改造,全程不破坏原逻辑,所有新增代码均可独立开关。这不是“教你写新功能”,而是展示如何像维护生产系统一样,在遗留代码上安全叠加能力。

5.1 超期未还预警:在借阅列表页增加红色标记与天数计算

需求:读者登录后,在【我的借阅】列表中,对returnDate IS NULL AND borrow_date < NOW() - INTERVAL 30 DAY的记录标红并显示“已超期X天”。不修改数据库,纯前端计算。

<!-- WebContent/reader/borrow_list.jsp 第42行插入 --> <td> <c:choose> <c:when test="${borrow.returnDate == null}"> <c:set var="today" value="<%=new java.util.Date()%>" /> <c:set var="borrowDate" value="${borrow.borrowDate}" /> <c:set var="diffDays" value="${(today.time - borrowDate.time) / (1000 * 60 * 60 * 24)}" /> <c:choose> <c:when test="${diffDays > 30}"> <span style="color:red;font-weight:bold;"> 已超期${(int)diffDays}天 </span> </c:when> <c:otherwise> 已借阅${(int)diffDays}天 </c:otherwise> </c:choose> </c:when> <c:otherwise> <fmt:formatDate value="${borrow.returnDate}" pattern="yyyy-MM-dd"/> </c:otherwise> </c:choose> </td>

参数说明:today.time - borrowDate.time计算毫秒差,除以1000*60*60*24转为天数;(int)diffDays强制转整型避免小数;style="color:red"直接内联样式,不依赖外部CSS,避免污染原主题。此方案零后端改动,所有计算在JSP中完成,适合快速上线。

5.2 借阅历史导出Excel:用Apache POI生成可下载的.xls文件

需求:管理员在【借阅管理】页点击【导出Excel】,生成包含读者姓名、图书名、借阅日期、归还日期、借阅天数的Excel文件。不引入新框架,复用现有Servlet结构。

步骤1:添加POI依赖(lib/poi-4.1.2.jar和lib/poi-ooxml-4.1.2.jar)
步骤2:新建com.servlet.ExportBorrowServlet.java
// com.servlet.ExportBorrowServlet.java protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { List<Borrow> borrowList = new BorrowDao().getAllBorrows(); // 复用现有DAO // 创建Excel工作簿 XSSFWorkbook workbook = new XSSFWorkbook(); XSSFSheet sheet = workbook.createSheet("借阅历史"); // 表头 String[] headers = {"读者姓名", "图书名", "借阅日期", "归还日期", "借阅天数"}; XSSFRow headerRow = sheet.createRow(0); for (int i = 0; i < headers.length; i++) { headerRow.createCell(i).setCellValue(headers[i]); } // 数据行 int rowNum = 1; for (Borrow b : borrowList) { XSSFRow row = sheet.createRow(rowNum++); row.createCell(0).setCellValue(b.getReader().getName()); row.createCell(1).setCellValue(b.getBook().getName()); row.createCell(2).setCellValue( new SimpleDateFormat("yyyy-MM-dd").format(b.getBorrowDate()) ); row.createCell(3).setCellValue( b.getReturnDate() == null ? "" : new SimpleDateFormat("yyyy-MM-dd").format(b.getReturnDate()) ); row.createCell(4).setCellValue( b.getReturnDate() == null ? 0 : (int)((b.getReturnDate().getTime() - b.getBorrowDate().getTime()) / (1000 * 60 * 60 * 24)) ); } // 设置响应头,触发下载 response.setContentType("application/vnd.ms-excel"); response.setHeader("Content-Disposition", "attachment; filename=borrow_export.xls"); // 写出 workbook.write(response.getOutputStream()); workbook.close(); }
步骤3:在web.xml中注册Servlet
<servlet> <servlet-name>ExportBorrowServlet</servlet-name> <servlet-class>com.servlet.ExportBorrowServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>ExportBorrowServlet</servlet-name> <url-pattern>/servlet/exportBorrow</url-pattern> </servlet-mapping>
步骤4:在JSP中添加导出按钮
<!-- WebContent/admin/borrow_manage.jsp 第15行 --> <a href="${pageContext.request.contextPath}/servlet/exportBorrow" class="btn btn-primary">导出Excel</a>

逻辑说明:XSSFWorkbook支持.xlsx格式,但response.setContentType设为application/vnd.ms-excel兼容老版本Excel;Content-Disposition的attachment强制下载而非在浏览器打开;所有日期格式化用SimpleDateFormat而非EL表达式,确保格式统一。此方案复用BorrowDao,不侵入业务逻辑,导出按钮可随时移除,无副作用。


从那以后我每次接手JavaWeb老项目,第一件事就是检查BaseDao.java里的getConnection()是否用了连接池(这个没有,所以要留意连接泄漏),第二件事是翻web.xml看Servlet映射是否全走/servlet/*统一前缀,第三件事是grep所有executeUpdate(看有没有字符串拼接SQL。这三个动作做完,80%的部署和安全问题就提前暴露了。这套图书馆源码就像一面镜子,照出我们写CRUD时最容易忽略的工程细节——不是语法有多难,而是每一行代码背后,都有它必须存在的理由。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/1 1:09:08

微型燃气轮机冷热电联供优化:PSO实战代码与调度策略

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

作者头像 李华
网站建设 2026/11/1 1:08:06

TM4C129+DRV8818:双极步进电机驱动方案设计与实战

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

作者头像 李华
网站建设 2026/11/1 1:07:14

PIC18F85K22与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/1 1:06:58

2012 R2安装.NET 3.5:SXS源与0x800f0906排错

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

作者头像 李华
网站建设 2026/11/1 1:06:50

Qt局域网聊天室实战:TCP粘包分帧、信号槽与打包避坑

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

作者头像 李华
网站建设 2026/11/1 1:06:31

DRV8818+PIC18F86J50工业步进电机硬核控制实战

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

作者头像 李华