简介这是一套面向高校数据库与Java课程设计实践的完整CS架构学生选课信息管理系统适用于计算机相关专业本科生开展数据库建模、JDBC编程与多角色权限管理综合实训。系统严格遵循三层用户逻辑学生可完成选课退课、成绩查询与成绩单打印教师支持课程管理与成绩录入管理员覆盖院系、课程、师生、奖惩等全维度后台操作配套SQL脚本建表规范设计报告详述需求分析与ER模型。资源包共98个文件含21个核心Java源码、66个编译后class文件、1个educate.sql数据库脚本、1个本地SQLite数据库文件educateSystem.db、1个设计报告.doc及必要配置文件整体仅1.98MB轻量易部署。目前已有11834人学习下载读者可直接导入Eclipse运行调试快速掌握MySQLJava桌面应用开发全流程包括连接池配置、事务控制、GUI界面布局与角色权限分层实现。1. 这不是又一个“增删改查”Demo它真能跑通学生选课全流程的 MySQLJava 实战系统某高校数据库课程设计里90% 的学生交上来的是带登录框的 Swing 界面 四张表硬编码 CRUD——点“添加学生”就弹窗输学号姓名点“选课”就手动填学号课号学期后台连事务都不开。但真实教务系统里“学生选课”从来不是原子操作它要校验学分上限、检查课程容量、判断先修课是否已通过、冲突课表自动拦截、选课成功后还要触发成绩初始化……这个「学生选课信息管理系统」源码包是某导师带学生用一整个学期打磨出的闭环实现MySQL 8.0 建模含 7 张核心表学生、课程、教师、选课记录、开课计划、先修关系、课表冲突规则Java 后端用 JDBC 原生封装事务控制Swing 客户端所有按钮行为都对应真实业务约束。它不炫技但每处报错提示都带着业务语义比如“您已修满本学期 24 学分上限无法再选《编译原理》”它不抽象但所有 SQL 都带注释说明为什么用SELECT ... FOR UPDATE而非普通查询。适合正在啃《数据库系统概念》第六章、被隔离级别和死锁搞晕的新手也适合想把课程设计答辩讲出技术深度的高年级同学——你拿到的不是脚手架是能对着 ER 图逐行调试的业务逻辑黑匣子。2. 从 ER 图到可运行 jar建库、建表、初始化数据三步落地2.1 为什么这 7 张表结构是经过业务推演的而不是拍脑袋定的很多同学建完“学生表”“课程表”“选课表”就以为完工了结果在实现“查看某学生本学期课表”时卡住课表需要显示上课时间、地点、教师姓名、是否已结课……这些字段散落在不同实体里。本系统采用显式建模业务事实的方式拆解出关键中间表course_offering开课计划记录某门课在某学期由哪位教师在哪间教室几点上解决“同一门课多个班次”的问题prerequisite先修关系用(course_id, prerequisite_id)复合主键支持一门课有多个先修课如《操作系统》需先修《C语言》和《数据结构》schedule_conflict_rule课表冲突规则存time_slot_id和room_id组合用于实时校验——不是简单比对“星期几第几节”而是抽象成时间槽 ID方便后期扩展在线预约场景。提示prerequisite表未设外键级联因为先修课删除时业务要求人工审核不能自动解除依赖所以 Java 层用SELECT COUNT(*) FROM prerequisite WHERE course_id ?主动拦截。2.2 MySQL 8.0 建库脚本字符集、排序规则与严格模式必须显式声明课程设计常被忽略的细节本地 MySQL 默认是latin1而学生姓名含中文会乱码更隐蔽的是sql_mode缺失导致INSERT INTO student (name) VALUES ()竟然成功——空字符串在业务中应视为非法输入。本系统建库脚本强制声明CREATE DATABASE IF NOT EXISTS course_selection CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_as_cs; USE course_selection; SET sql_mode STRICT_TRANS_TABLES,NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION;utf8mb4_0900_as_cs是 MySQL 8.0 推荐的大小写敏感排序规则确保SELECT * FROM student WHERE name 张三不会误匹配张三 带空格STRICT_TRANS_TABLES让插入空值或超长字段直接报错逼你在 Java 层做校验而不是让数据库默默截断。2.3 初始化数据用 INSERT SELECT 替代硬编码保证测试数据自洽系统附带init_data.sql但关键不在“插了多少条”而在如何保证数据逻辑自洽。例如student表插入 50 条后course_offering表必须确保每门课至少有 1 个开班且该班容量 ≥ 30enrollment选课记录表的数据必须满足“每个学生选课数 ≤ 6 门”。脚本用子查询生成-- 插入 30 条选课记录确保每条记录的 student_id 和 course_offering_id 真实存在 INSERT INTO enrollment (student_id, course_offering_id, enrollment_time, status) SELECT s.student_id, co.course_offering_id, NOW(), CONFIRMED FROM student s CROSS JOIN course_offering co WHERE s.student_id 30 AND co.course_offering_id 30 LIMIT 30;CROSS JOIN生成笛卡尔积再LIMIT比INSERT ... VALUES (1,1), (1,2)...更易维护所有INSERT后紧跟SELECT COUNT(*)校验避免因主键冲突导致部分数据未插入却无报错。2.4 编译打包为什么用 javac jar 命令而非 IDE 一键构建IDE如 IntelliJ默认将src下所有.java文件编译进out目录但本系统要求明确分离业务逻辑与 UI 层com.example.dao包下是纯 JDBC 操作com.example.ui包下是 Swing 组件。若用 IDE 自动编译容易把ui包里的测试类如TestLoginFrame.java也打进最终 jar增大体积且暴露内部结构。我们坚持命令行构建# 1. 创建编译输出目录 mkdir -p build/classes # 2. 编译所有 .java 文件指定输出路径和依赖jar javac -d build/classes \ -cp lib/mysql-connector-java-8.0.33.jar:. \ src/com/example/dao/*.java \ src/com/example/service/*.java \ src/com/example/ui/*.java # 3. 打包MANIFEST.MF 指定主类和Class-Path echo -e Manifest-Version: 1.0\nMain-Class: com.example.ui.LoginFrame\nClass-Path: lib/mysql-connector-java-8.0.33.jar build/MANIFEST.MF jar cfm course_selection.jar build/MANIFEST.MF -C build/classes .-cp参数显式声明 MySQL 驱动路径避免运行时报ClassNotFoundExceptionMANIFEST.MF中Class-Path用相对路径lib/xxx.jar这样双击 jar 或java -jar course_selection.jar都能加载驱动src/com/example/ui/*.java编译时若报package com.example.dao does not exist说明未加-cp build/classes这是新手最常翻车的环节。3. JDBC 事务怎么控看懂 enrollCourse() 方法里的四层防御3.1 业务入口enrollCourse() 方法签名暴露了什么约束EnrollmentService.enrollCourse(int studentId, int offeringId)方法看似简单但参数类型暴露了关键设计int studentId不接受String因为学号在数据库是INT UNSIGNED前端输入框做了typenumber限制返回值是EnrollmentResult枚举SUCCESS,CAPACITY_FULL,PREREQ_NOT_MET,TIME_CONFLICT而非boolean——业务错误必须分类不能只用false模糊掩盖。注意方法未声明throws SQLException所有异常被封装为RuntimeException并在 UI 层捕获显示友好提示符合课程设计“不暴露技术细节给用户”的要求。3.2 第一层防御SELECT ... FOR UPDATE 锁定开课计划行选课前必须检查余量但普通SELECT会导致超卖。本系统在checkCapacityAndLock()方法中// 使用 FOR UPDATE 显式加行锁防止并发选课时余量判断失效 String sql SELECT capacity, enrolled_count FROM course_offering WHERE course_offering_id ? FOR UPDATE; try (PreparedStatement ps connection.prepareStatement(sql)) { ps.setInt(1, offeringId); try (ResultSet rs ps.executeQuery()) { if (!rs.next()) throw new IllegalArgumentException(开课计划不存在); int capacity rs.getInt(capacity); int enrolledCount rs.getInt(enrolled_count); if (enrolledCount capacity) { throw new BusinessRuleException(CAPACITY_FULL); } // 余量充足继续后续校验 } }FOR UPDATE在 RR可重复读隔离级别下会对course_offering_id ?这一行加写锁其他事务对该行的SELECT ... FOR UPDATE或UPDATE会被阻塞关键点enrolled_count字段在course_offering表中而非在enrollment表里COUNT(*)——前者是冗余字段但换来了 O(1) 查询这是课程设计允许的合理妥协。3.3 第二层防御用 EXISTS 子查询校验先修课先修课校验不能只查“学生是否修过该课”而要查“是否修过且成绩 ≥ 60”。本系统用关联子查询String prereqSql SELECT 1 FROM prerequisite p WHERE p.course_id ? AND NOT EXISTS ( SELECT 1 FROM enrollment e WHERE e.student_id ? AND e.course_offering_id IN ( SELECT co.course_offering_id FROM course_offering co WHERE co.course_id p.prerequisite_id ) AND e.status COMPLETED AND e.final_score 60 ) ; // 若返回结果集非空说明存在未满足的先修课NOT EXISTS比LEFT JOIN ... IS NULL更易读且 MySQL 对EXISTS有优化找到一条即停子查询里e.status COMPLETED是硬性业务规则未完成的选课记录如ENROLLING状态不计入先修课完成。3.4 第三层防御课表冲突检测用时间槽 ID 而非字符串拼接冲突检测最易出错有人用CONCAT(day_of_week, start_time)拼字符串比较结果Monday08:00和Monday08:00:00不等。本系统在schedule_conflict_rule表中预定义时间槽time_slot_idday_of_weekstart_timeend_time101108:00:0009:40:00102110:00:0011:40:00检测逻辑// 获取当前选课的时间槽ID String getSlotSql SELECT time_slot_id FROM course_offering WHERE course_offering_id ?; // 获取学生已选课程的时间槽ID列表 String getStudentSlotsSql SELECT DISTINCT co.time_slot_id FROM enrollment e JOIN course_offering co ON e.course_offering_id co.course_offering_id WHERE e.student_id ? AND e.status CONFIRMED ; // 若两个集合取交集非空则冲突时间槽 ID 是整数比较效率远高于字符串DISTINCT防止同一时间槽多门课重复计算。3.5 第四层防御事务提交前的最终一致性快照校验即使前三层都通过仍可能因极短时间内其他事务修改导致状态漂移。本系统在commit()前执行快照校验// 在 connection.commit() 前重新查询关键字段 String finalCheckSql SELECT capacity, enrolled_count, status FROM course_offering WHERE course_offering_id ? AND status OPEN -- 开课状态未变 ; // 若 capacity 或 enrolled_count 变化说明并发冲突回滚并重试这是典型的“乐观锁”思想虽增加一次查询但避免了死锁课程设计中不实现重试逻辑太复杂而是抛出ConcurrentModificationException提示“请重试”。4. Swing 界面不是摆设按钮状态、输入校验、错误反馈全链路闭环4.1 登录界面为什么用 JPasswordField 而非 JTextFieldJPasswordField返回char[]而非String这是安全基本功。本系统在LoginFrame.loginButtonActionPerformed()中char[] passwordChars passwordField.getPassword(); String password new String(passwordChars); // ...校验逻辑 Arrays.fill(passwordChars, \0); // 立即清空内存中的密码明文Arrays.fill()清空char[]是必须步骤否则密码可能留在 JVM 堆内存中被 dump 出来JTextField会把密码存为String而String在 Java 中不可变无法主动清空。4.2 选课界面JTable 的动态列宽与右对齐数字列JTable显示选课列表时学分列需右对齐符合阅读习惯课程名列需自动调整宽度。本系统重写getColumnClass()public Class? getColumnClass(int columnIndex) { if (columnIndex 3) return Integer.class; // 学分列 return super.getColumnClass(columnIndex); }Integer.class触发DefaultTableCellRenderer自动右对齐配合table.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN)让最后一列撑满剩余空间。4.3 输入校验DocumentFilter 拦截非法字符而非 onblur 时弹窗学生学号只能是 8 位数字课程号是字母数字组合如 CS101。本系统用DocumentFilter实时过滤((AbstractDocument) studentIdField.getDocument()).setDocumentFilter( new DocumentFilter() { Override public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr) throws BadLocationException { if (string ! null !string.matches(\\d{0,8})) return; // 只允许最多8位数字 super.insertString(fb, offset, string, attr); } Override public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException { if (text ! null !text.matches(\\d{0,8})) return; super.replace(fb, offset, length, text, attrs); } } );insertString和replace双重覆盖防止粘贴非法内容正则\\d{0,8}允许空输入便于删除但阻止输入第9位。4.4 错误反馈JOptionPane 不是万能的要用 JDialog 嵌套组件JOptionPane.showMessageDialog()只能显示纯文本但业务错误需带操作按钮如“去查看已选课程”。本系统用JDialogJDialog errorDialog new JDialog(this, 选课失败, true); errorDialog.setLayout(new BorderLayout()); JLabel msgLabel new JLabel(htmlb时间冲突/b您已选《数据结构》其上课时间为周一 08:00-09:40与《算法分析》冲突/html); JButton viewBtn new JButton(查看我的课表); viewBtn.addActionListener(e - showMySchedule(studentId)); errorDialog.add(msgLabel, BorderLayout.CENTER); errorDialog.add(viewBtn, BorderLayout.SOUTH); errorDialog.pack(); errorDialog.setLocationRelativeTo(this); errorDialog.setVisible(true);html标签支持粗体和换行比纯文本更清晰setLocationRelativeTo(this)让对话框居中于父窗口而非屏幕中心。5. 避坑 / 常见问题 / 排查血泪经验总结的 4 个真实翻车现场5.1 现象启动 jar 报java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver原因MANIFEST.MF中Class-Path路径写错或mysql-connector-java-8.0.33.jar未放在lib/目录下。常见错误是把驱动 jar 放在lib同级目录或Class-Path写成lib/mysql-connector-java-8.0.33.jar缺了开头的空格。解决检查jar tf course_selection.jar | grep MANIFEST确认MANIFEST.MF内容运行java -cp lib/mysql-connector-java-8.0.33.jar:course_selection.jar com.example.ui.LoginFrame测试类路径。5.2 现象选课成功后course_offering.enrolled_count未增加原因UPDATE course_offering SET enrolled_count enrolled_count 1语句未在同一个事务中执行或enrolled_count字段类型是TINYINT最大值 127导致溢出。解决在enrollCourse()方法末尾添加日志System.out.println(Updated enrolled_count to: newCount)检查表结构DESCRIBE course_offering将enrolled_count改为SMALLINT UNSIGNED。5.3 现象输入学号00123456查询不到学生原因MySQL 中student_id是INT类型00123456被转为123456存储前端传参时未补零。但业务要求学号固定 8 位含前导零。解决将student_id字段改为CHAR(8)并在INSERT时用LPAD(?, 8, 0)补零Java 层String.format(%08d, id)格式化后再传参。5.4 现象Swing 界面在高 DPI 屏幕上字体模糊、按钮错位原因Java 8 默认不启用高 DPI 支持JFrame未设置setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)导致关闭后进程仍在。解决启动 jar 前加 JVM 参数-Dsun.java2d.uiScale1.25根据缩放比例调整在LoginFrame构造函数末尾添加setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)。6. 验证你的系统是否真闭环用这 3 个边界测试用例压测核心流程6.1 测试用例 1并发选同一门满员课验证事务隔离准备用course_offering表将某门课capacity1当前enrolled_count0。操作开两个终端同时运行以下命令模拟两个学生点击“选课”# 终端1 java -cp lib/mysql-connector-java-8.0.33.jar:. com.example.test.ConcurrentEnrollTest 101 201 # 终端2几乎同时执行 java -cp lib/mysql-connector-java-8.0.33.jar:. com.example.test.ConcurrentEnrollTest 101 202预期结果一个终端输出SUCCESS另一个输出CAPACITY_FULL检查数据库enrolled_count应为1且两条enrollment记录中仅一条statusCONFIRMED。关键观察点若两条记录都是CONFIRMED说明FOR UPDATE未生效检查 MySQL 隔离级别是否为REPEATABLE-READ默认。6.2 测试用例 2先修课链式依赖验证 EXISTS 子查询建模课程 AID1001依赖课程 BID1002课程 B 依赖课程 CID1003。学生只修过课程 C。操作调用enrollCourse(studentId, 1001)。预期结果抛出PREREQ_NOT_MET且错误消息明确指出“未修《数据结构》课程B”。验证方法在PrerequisiteService.checkPrerequisites()方法中打断点观察prereqSql执行的ResultSet是否为空——若为空说明EXISTS逻辑正确若不为空检查enrollment表中course_offering_id是否关联到正确的course_id易错点course_offering.course_id与prerequisite.prerequisite_id比对。6.3 测试用例 3课表冲突的“隐形”时间重叠验证时间槽模型场景课程 X 时间槽 ID101周一 08:00-09:40课程 Y 时间槽 ID102周一 10:00-11:40表面不冲突。但课程 Z 时间槽 ID103周一 09:00-10:30——它与 X 重叠 10 分钟与 Y 重叠 30 分钟。操作学生先选 X再选 Z应失败先选 Z再选 X也应失败。验证表格学生已选尝试选预期结果数据库schedule_conflict_rule应含X (101)Z (103)TIME_CONFLICT(101,103)和(103,101)两条记录Z (103)X (101)TIME_CONFLICT同上从那以后我每次重构schedule_conflict_rule表都强制走一遍这个三元组测试——哪怕只是改了个字段注释。因为课表冲突是学生最敏感的体验点任何漏判都会在答辩时被导师当场揪出。希望帮到你本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
单片机嵌入式面试120题:硬件与C语言核心考点解析 单片机嵌入式岗位的面试有个很现实的特点:范围看着不大,实际问起来能从寄存器位操作一路聊到电源纹波,从volatile的语义扯到中断服务函数里该不该调用printf。很多人复习的时候抱着厚厚一本教材从头翻,翻到定时器那一章就困了&… · 2026/11/4 10:53:29
Model-Optimizer:大模型推理的硬件-运行时-模型协同优化方法论 1. 什么是Model-Optimizer:不是“一键加速器”,而是模型推理效能的系统性重构工程 你搜“Model-Optimizer”,十有八九会撞上一堆TensorRT、vLLM、NVIDIA驱动安装失败的报错截图,还有人问“vLLM部署DeepSeek卡在scheduler初始化”—… · 2026/11/4 10:53:29
Superpowers技能框架:让AI编程助手稳定输出工程级代码 1. 从“superpowers”说起:这套技能框架到底在解决什么问题第一次看到“superpowers”这个词,很多人会以为是某个超级英雄题材的游戏或者插件合集。但如果你最近在折腾 Claude Code、Codex CLI 这类终端里的 AI 编程助手,大概率已经在各种社区… · 2026/11/4 10:52:28
维智 JS-API 定位插件:H5 开发必备的全域定位解决方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 11:34:25
Komikku:免费开源的Android漫画阅读器终极指南 Komikku:免费开源的Android漫画阅读器终极指南
如果你正在寻找一款功能强大、完全免费的Android漫画阅读器,Komikku绝对是你的不二之选。这款开源应用汇集了200多个漫画源,让你在一个应用中就能畅读海量漫画内容。今天,我将为你详… · 2026/11/4 11:33:25
SleekXMPP 生产环境部署指南:日志、断线重连与长期稳定运行的 6 个实用技巧 【免费下载链接】SleekXMPP Python 2.6/3.1 XMPP Library 项目地址: https://gitcode.com/gh_mirrors/sl/SleekXMPP 点击查看 免费下载 SleekXMPP 是一个功能完整的 Python XMPP Library(聊天协议库),广泛用于构建机器人、消息网… · 2026/11/4 11:32:24
Bilibili-Evolved 使用指南:3 步把 B 站改成你的样子 Bilibili-Evolved 使用指南:3 步把 B 站改成你的样子 【免费下载链接】Bilibili-Evolved 强大的哔哩哔哩增强脚本 项目地址: https://gitcode.com/gh_mirrors/bi/Bilibili-Evolved
B 站页面长什么样、下载什么编码,官方说了算,你插不上… · 2026/11/4 11:32:24
Windows 操作系统基础命令详解 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 11:32:24
双极步进电机控制实战: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/4 3:16:49
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/4 6:59:29
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/4 7:02:38
AI辅助技术栈评估:提升研发决策效率60% 1. 项目背景与核心价值技术决策一直是研发团队面临的重大挑战。随着技术生态的快速演进,技术栈的选择、维护和迭代变得愈发复杂。我们团队在过去两年里,先后经历了三次重大技术栈迁移,每次都需要投入大量人力进行技术评估和风险分析。直到去年… · 2026/11/4 0:00:17
从生成Token到直接决策:Jev如何重塑AI Agent工程实践 1. 从"生成文字"到"拍板决策":Jev 到底在解决什么问题大多数人第一次接触大模型,脑子里建立的模型是"输入问题、输出文字"。你问它今天天气怎么样,它给你一段描述;你让它写个周报,它给你… · 2026/11/4 0:00:17
32G统一内存跑大模型:内存账、TPS实测与端云分流 想在一台 32G 统一内存的小主机上把大模型跑成"日常可用",这件事我前后折腾了小半年。标题里说的这台 Mac mini M6,是我们这次讨论的目标平台,具体到手规格以实物为准;但下面这套推算方法和实测思路,对任何统… · 2026/11/4 0:00:17
双极步进电机控制实战: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/4 3:16:49
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/4 6:59:29
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/4 7:02:38