Servlet+JSP学生选课系统:从部署到改造的JavaWeb项目实战

发布时间:2026/10/7 6:15:21

Servlet+JSP学生选课系统:从部署到改造的JavaWeb项目实战 简介这是一套基于JavaWeb技术栈实现的学生选课系统面向计算机相关专业毕业设计学生及需要项目实战的Java学习者可解决课程管理、选课、成绩录入等常见业务场景的完整开发需求。系统采用Servlet与JSP及MySQL架构前端结合Bootstrap和jQuery支持管理员、教师、学生三种角色覆盖学生与教师信息管理、课程信息管理、课程查看与选课、成绩录入与查询、个人信息查看等功能。资源包共有一百一十二个文件以Java源码、JSP页面、class编译文件为主同时包含十个jar依赖库、SQL数据库脚本、CSS与JS前端样式及少量图片字体资源整包仅二点三九MB已通过严格调试可直接运行并作为毕业设计项目使用。目前已有四千二百七十三人学习下载适合用于毕设选题参考、项目实战练习以及JavaWeb开发流程的完整学习。1. 还在用 Servlet JSP 的选课系统为什么值得你把它跑起来说到 JavaWeb 项目很多人第一反应是 Spring Boot。但这套学生选课系统用的是最经典的 Servlet JSP MySQL Bootstrap 组合JDK 1.8配一个 Tomcat 就能跑。不是它过时而是它正好覆盖了两类诉求正在做毕设、需要一套能直接答辩的完整案例的在校生以及学完 JavaWeb 基础、想看看项目从登录过滤器到 DAO 层怎么串起来的自学者。系统把管理员、教师、学生三种角色拆成三条权限线选课、成绩录入、课程管理全是能点能跑的界面不是那种堆了一堆类名却起不来的半成品。2. 技术选型与服务端分层为什么坚持用 Servlet源码该从哪看起2.1 Servlet JSP JDK 1.8组合的边界与选型理由先交代一个常被忽略的事实Spring Boot 的 DispatcherServlet 本身也是一个 Servlet。Servlet 规范是 JavaWeb 的地基这套系统直接在地基上盖楼没有框架黑匣子。对选课这种并发量不大、业务规则清晰的场景Servlet JSP 反而比 Spring Boot 更直白请求进来走哪个 ServletServlet 转发到哪个 JSP全程一眼能看穿。那为什么不选 SSM 或 Spring BootSSM 要维护 XML 映射文件三张表的小系统写 mapper 比写业务还费劲Spring Boot 自动配置把数据库连接、视图解析全部藏起来了答辩时被追问一句「Tomcat 是怎么启动的」很容易卡壳。Servlet JSP 的每一步都是显式的适合用来证明你理解 JavaWeb 而不是只会用框架。另外两个配套选择也值得说JDK 1.8 是这套技术栈最稳定的版本lambda 能用又不会引入 module 系统的麻烦前端用 Bootstrap 而不是 Vue/React是为了让 JSP 后端渲染和静态页面样式各司其职列表、表单、弹窗全是现成组件改样式几行 CSS 的事。对这些管理系统前端复杂度越低越能把答辩时间留给业务逻辑。选型代价也很清楚没有声明式事务、没有 ORMJDBC 连接要手动开手动关。所以这套技术栈适合单体、校园网规模、几千条数据以内的管理系统。如果目标是高并发抢课那该上 MQ、Redis这不是这套源码的定位。2.2 三种角色一个登录口权限与表结构怎么落登录相关逻辑集中在 UserServlet 和 LoginFilter。这类项目最常见的表结构是一张 user 表存账号、密码、角色admin / teacher / studentstudent、teacher 表存各自资料course 表存课程student_course 表存选课关系和成绩。成绩录入、成绩查询都是围绕 student_course 的 score 字段展开的这一条能对上教师和学生两个角色的核心功能也解释了为什么源码里会单独放一个 StudentCourse 实体类。角色不同操作入口不同但一般不会写死多个主页而是复用一套页面按角色渲染菜单。管理员能看到学生信息管理、教师信息管理、课程信息管理教师能看到自己教的课程、选课学生名单、成绩录入入口学生只能看到课程列表、选课按钮和自己的成绩。权限粒度控制在「菜单级」对毕设来说刚好做细反而增加调试负担。成绩录入这条线在 TeacherServlet 里教师进课程后看到学生名单提交分数时更新 student_course.score。这里有个权限边界要注意TeacherServlet 里通常会判断当前课程是否属于登录教师本人否则教师可以改任何课程的成绩这是常见越权漏洞也是答辩时能主动讲出来的加分点。还有一点容易被新手绕晕LoginFilter 只回答「有没有登录」不回答「能不能做这件事」。具体操作是否越权由 Servlet 内部拿 session 里的角色判断。两层各管一头职责分明改起来也快。2.3 核心文件调用链压缩包打开后先看哪几个先看类文件分布。这套资源的核心是六个 Servlet 和四个数据类把层次和职责摆开是这样的层次类文件对应职责控制层UserServlet登录登出、个人资料控制层StudentServlet学生资料维护管理员操作控制层TeacherServlet教师资料维护、成绩录入控制层CourseServlet课程列表、选课入口数据层StudentDao / TeacherDao学生、教师表 CRUD数据层CourseDao课程表 CRUD数据层StudentCourseDao选课关系、成绩读写横切LoginFilter登录状态拦截实体StudentCourse选课关系实体拿到项目我一般不建议先开 index.jsp按这个顺序读更快先看 SQL 脚本里的表和初始化数据确认有几个角色、几门课再看 LoginFilter 怎么拦截然后挑一条完整链路比如学生选课从 CourseServlet 追到 StudentCourseDao最后打开对应 JSP 看渲染。两小时能把整套系统讲明白比漫无目的翻代码高效得多。顺带说一句读代码的节奏。第一遍只需要把「谁调用谁」画在脑子里不用深挖每行 JDBC第二遍再回到 StudentCourseDao把连接管理、结果集映射看细。两遍下来这套系统的并发边界、可扩展点在哪里心里基本有数。提示压缩包里的 .class 是编译产物真正要改的是 src 下对应的 .java 源文件两者在工程目录里是对应存在的别拿反编译工具去折腾 class。3. 核心代码拆解登录拦截、选课链路与 Bootstrap 页面的串法3.1 LoginFilter 与 UserServlet登录态怎么产生、怎么拦截先看登录态怎么来。UserServlet 接收用户名密码校验通过后把 User 对象放进 session。常见写法是WebServlet(/login) public class UserServlet extends HttpServlet { Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); String username req.getParameter(username); String password req.getParameter(password); // 查 user 表校验账号密码成功返回 User失败返回 null User user userDao.findByUsernameAndPassword(username, password); if (user null) { req.setAttribute(error, 账号或密码错误); req.getRequestDispatcher(/login.jsp).forward(req, resp); return; } req.getSession().setAttribute(loginUser, user); resp.sendRedirect(req.getContextPath() /index.jsp); } }逻辑说明登录失败用 forward 转发回登录页地址栏不变方便在页面上回显 error 提示登录成功用 sendRedirect 重定向到主页避免刷新页面时表单重复提交。具体查表这段如果压缩包里没有独立的 UserDao那多半是把校验写在 UserServlet 内部或复用了现有 Dao不影响整条链路理解。有了登录态LoginFilter 就负责把没登录的请求挡在门外WebFilter(/*) public class LoginFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; HttpServletResponse resp (HttpServletResponse) response; String uri req.getRequestURI(); if (uri.endsWith(login.jsp) || uri.contains(/login) || uri.endsWith(.css) || uri.endsWith(.js)) { chain.doFilter(request, response); return; } Object user req.getSession().getAttribute(loginUser); if (user null) { resp.sendRedirect(req.getContextPath() /login.jsp); return; } chain.doFilter(request, response); } }逻辑说明过滤器先放行登录页、登录接口和静态资源再检查 session。注意静态资源必须放行否则登录页 CSS/JS 全被拦截样式全丢出现「能登录但页面丑得没法看」的翻车现场。参数说明WebFilter(/*) 拦截所有路径也可以用 web.xml 里的filter/filter-mapping声明老项目两种写法都有效果一致。判断登录态时建议用 getSession(false)没登录时返回 null而 getSession() 会当场创建一个空 session既浪费内存还绕不过拦截登录成功后主动 setAttribute 存 loginUser这个 key 要和过滤器里取的一致否则永远认为你没登录。3.2 三层调用链CourseServlet 与 StudentCourseDao 的配合以学生选课为例完整链路是「课程列表点按钮 → CourseServlet → StudentCourseDao → 结果页」。CourseServlet 里有两个关键动作加载课程列表、处理选课请求。列表加载常见写法WebServlet(/course/list) public class CourseServlet extends HttpServlet { private CourseDao courseDao new CourseDao(); Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { ListCourse courseList courseDao.findAll(); req.setAttribute(courseList, courseList); req.getRequestDispatcher(/course_list.jsp).forward(req, resp); } }逻辑说明Dao 查出来的 List 放进 requestforward 给 JSP 渲染。这里用 forward 而不是重定向因为数据在 request 作用域里重定向会丢。这也是 JSP 后端渲染和前后端分离最大的区别页面是在服务端拼好再发出去的。选课请求的处理是同一类 Servlet 里的另一个动作。不少项目把 /course/list 和 /course/select 配在同一个 Servlet 上在 doGet 里根据路径分流也有项目拆成两个 Servlet逻辑是一样的// 地址形如 /course/select?courseId5 String path req.getServletPath(); if (/course/select.equals(path)) { Integer courseId Integer.valueOf(req.getParameter(courseId)); Integer studentId getLoginUserId(req); // 从 session 取当前学生的 id boolean ok studentCourseDao.addStudentCourse(studentId, courseId); if (ok) { resp.sendRedirect(req.getContextPath() /course/list); } else { req.setAttribute(msg, 选课失败可能已选过或课程人数已满); req.getRequestDispatcher(/course_list.jsp).forward(req, resp); } }逻辑说明courseId 来自前端按钮studentId 必须从 session 拿而不是从 URL 参数拿这是防止越权的关键——用户改 URL 里的数字没用身份以服务端 session 为准。getServletPath() 分流是 Servlet 时代最常用的多动作单类写法等价于 Spring MVC 里的方法映射只是要自己写分支。对应的 StudentCourseDao 里是经典 JDBCpublic boolean addStudentCourse(int studentId, int courseId) { String selectSql SELECT COUNT(*) FROM student_course WHERE student_id? AND course_id?; String insertSql INSERT INTO student_course(student_id, course_id, create_time) VALUES(?,?,NOW()); // 先查是否已选count 0 直接返回 false // 未选则 PreparedStatement 执行 insert // finally 里依次关闭 rs、ps、conn }逻辑说明先查后插是选课系统最基本的幂等保护防止同一学生在列表页连点两次按钮产生重复记录。PreparedStatement 用 ? 占位符传参SQL 和参数分离既防注入又可读。create_time 交给数据库 NOW() 生成不在 Java 里拼时间字符串是 JDBC 时代的标准做法。3.3 JSP Bootstrap列表渲染、选课按钮与提示消息前端是 Bootstrap jQueryJSP 负责把 Dao 查出来的数据渲染成表格。页头引入 JSTL 标签% taglib prefixc urihttp://java.sun.com/jsp/jstl/core %课程列表主体用 Bootstrap 表格按角色控制按钮显示table classtable table-hover thead trth课程名/thth教师/thth学分/thth操作/th/tr /thead tbody c:forEach items${courseList} varcourse tr td${course.courseName}/td td${course.teacherName}/td td${course.credit}/td td c:if test${sessionScope.loginUser.role student} a href${pageContext.request.contextPath}/course/select?courseId${course.id} classbtn btn-primary btn-sm onclickreturn confirm(确定选这门课)选这门课/a /c:if /td /tr /c:forEach /tbody /table逻辑说明c:forEach 循环渲染表格行c:if 按角色决定是否显示选课按钮。confirm() 是 jQuery 时代最常用的防误点方案点取消返回 false链接不会跳转。参数说明${course.xxx} 取值依赖实体类属性名Servlet setAttribute 的 key 和 JSP 里写的 key 必须完全一致否则页面空白且不报错。URL 里用 ${pageContext.request.contextPath} 拼绝对路径而不是写死 /student_course这样以后改 context 不用全局替换。这类渲染问题要用浏览器查看源码定位比盯控制台有效。到这一步登录、课程列表、选课、结果反馈的闭环就通了。成绩录入、学生管理逻辑完全同构Dao 多写 updateServlet 多一个路由JSP 多一个表单半小时内能摸清全部套路。4. 在 IDEA 里跑起这套 JavaWeb 项目Tomcat、Navicat 与连接参数一次对齐4.1 数据库脚本导入先建空库再跑 SQL压缩包里的 SQL 脚本就是整套系统的数据基础。我习惯先在 Navicat 建一个空库再导入脚本避免脚本自带的建库语句和本地已有库冲突Navicat 新建连接字符集选 utf8mb4创建数据库名字和脚本里一致比如 student_course右键数据库 → 运行 SQL 文件选择 .sql等待执行完成重点核对三张核心表user、course、student_course。导入后顺手看一眼脚本里的 INSERT 语句确认三种角色的账号密码都在。有些精简版脚本只初始化了管理员教师和学生要靠自己手动加这个差异会影响你后面验证三角色流程。建议在 Navicat 里执行一条 SELECT * FROM user 确认账号在再进 IDEA 折腾数据库这层通了后面问题少一半。4.2 JDBC 连接参数driver、url 和时区三个坑位连接配置在 jdbc.properties 或 DBUtil.java 里核心四参数jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/student_course?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8 jdbc.usernameroot jdbc.password你的数据库密码参数说明driver 类名由 MySQL 版本决定MySQL 5.x 用 com.mysql.jdbc.DriverMySQL 8.x 要换成 com.mysql.cj.jdbc.Driver否则启动报 ClassNotFoundExceptionurl 里 serverTimezone 是 MySQL 8 的强制要求不配直接报时区异常characterEncodingutf8 管中文写入useSSLfalse 关掉本地连接的 SSL 握手告警。密码写错是最常见的连不上原因报错信息里一般会直接提示 Access denied。参数典型取值常见误配drivercom.mysql.cj.jdbc.Driver8.x 还在用旧类名urljdbc:mysql://localhost:3306/student_course端口不对、库名不对serverTimezoneAsia/Shanghai缺失导致时区异常characterEncodingutf8配置了但库表是 latin1 仍乱码4.3 IDEA 配置 TomcatJDK、Artifact、上下文路径在 IDEA 里跑 JavaWeb 项目配置点就三个JDK、Tomcat、Artifact。按下面顺序走能避开大部分启动翻车File → Project Structure → ProjectSDK 选 1.8Language level 选 8检查依赖lib 目录下的 mysql-connector、jstl 等 jar 是否已被识别没有就手动 Add as Library打开 Run/Debug Configurations新增 Tomcat Server → LocalDeployment 标签页添加 ArtifactApplication context 填 /student_course先编译一次确认 target 里有 classes再点启动。启动后访问 http://localhost:8080/student_course/login.jsp 打开登录页。这里最容易犯的错是 Application context 填的和 URL 对不上context 是 /student_courseURL 就必须带这个前缀前后不一致就是一片 404。启动时先看 IDEA 的 console。常见三种输出要会识别Tomcat started 表示容器起来了Deployment of web application has finished 表示项目部署完成Artifact 相关报错说明 target 里没编译出 classes。8080 端口被占会直接提示 Port was already in use改 Tomcat 配置里的 HTTP port或者把占用端口的进程清掉。控制台里的堆栈要看 Cause by 那一行真正原因通常写在最后面前面的框架日志大多是噪音。4.4 三种账号各过一遍主流程数据库脚本初始化了管理员、教师、学生三种账号具体用户名密码看 SQL 里的 INSERT 语句。登录后按角色各验证一条链路角色验证动作预期结果管理员新增一门课程、改一名学生资料列表刷新后有变化教师进课程 → 学生名单 → 录入成绩保存后提示成功学生选一门课 → 查成绩选课成功、成绩显示我验证时有个习惯学生端选课后去 Navicat 里查 student_course 表确认插入的 student_id、course_id 是对的。数据库层验证通过代码基本就稳了。还有一个小坑JSP 里所有静态资源引用、链接跳转最好都写成 ${pageContext.request.contextPath} 开头的绝对路径。如果源码里看到资源引用没带 contextPath跑通后建议顺手改掉不然以后改 context 时图片、CSS、跳转链接全乱。5. 避坑实录JavaWeb 项目跑不起来的五个高频问题5.1 Tomcat 10 部署老项目404 或 ClassNotFound现象项目在别人机器上能跑自己用 Tomcat 10 启动访问就 404控制台报 Servlet 相关类找不到。原因JDK 1.8 时代的项目用的是 javax.servlet 包Tomcat 10 起全面切到 jakarta.servlet包名不兼容Servlet 注解和 API 全部失效。代码没变容器版本变了。解决换 Tomcat 8.5 或 9.0.x不要用 10 及以上。我接手任意一个 JavaWeb 老项目第一步就是确认容器版本只要源码里出现 javax.servlet默认配 Tomcat 9。5.2 MySQL 8 连不上驱动名和时区双报错现象登录页正常打开一提交账号密码就报 ClassNotFoundException: com.mysql.jdbc.Driver或 The server time zone value 异常。原因本地装的是 MySQL 8驱动旧类名不再兼容同时 JDBC 要求 url 显式指定时区。两个坑经常一起出现。解决driver 换成 com.mysql.cj.jdbc.Driverurl 末尾加 serverTimezoneAsia/Shanghai。两条同时改改完重启 Tomcat。只改一半还会报另一个错这也是为什么我建议直接按第 4.2 节的参数表逐项核对。5.3 中文乱码JSP、请求、库表三处各自为政现象中文用户名存进数据库变问号或页面显示一堆乱字符。原因三处编码不一致。JSP 页面声明 UTF-8但请求没设置编码或数据库连接没带 characterEncodingutf8或库表字符集是 latin1。解决统一成 UTF-8。Navicat 里把库和表字符集改成 utf8mb4jdbc.url 加 characterEncodingutf8Servlet 读参数前执行 request.setCharacterEncoding(UTF-8)。三处缺一处乱码就可能换个位置复发。5.4 JSTL 标签原样输出或 $ 取不到值现象页面打开显示c:forEach原样代码或者 ${course.courseName} 位置空白控制台毫无报错。原因不识别 JSTL 标签说明缺 jstl 依赖取不到值说明 Servlet setAttribute 的 key 和 JSP 里的 key 拼写不一致。EL 表达式对空值默认输出空串所以静默失败。解决确认 WEB-INF/lib 或依赖里有 jstl.jar 和 standard.jar再用浏览器查看源码对比渲染结果逐个核对 key。这类问题盯控制台没用要看渲染后的 HTML。5.5 选课重复提交刷新页面插了两条记录现象学生选课后手动刷新页面student_course 表里出现两条相同记录。原因选课走的是 GET 链接浏览器刷新会重放上一次请求。如果 Dao 层没做先查后插的幂等校验就会插两条。解决StudentCourseDao 的 insert 前先查 count已存在直接返回 false更稳妥是把选课改成 POST 表单提交避免 GET 被无意识刷新。答辩演示时网络一卡手一抖就多一条记录这个坑最容易在现场翻车。老项目的问题集中在环境和编码两头真正的业务逻辑报错反而少见。遇到问题先按「容器版本 → 数据库连接 → 编码 → 依赖」四步排查比反复看业务代码效率高得多。6. 把它改造成答辩加分项限选人数校验与验收自查清单6.1 加一个「限选人数」校验原版选课只有幂等校验。答辩时老师常问「怎么防止课程选爆」加一个人数上限校验就能回应这个问题。在 StudentCourseDao 里插入前先查目标课程的已选人数和上限String countSql SELECT COUNT(*) FROM student_course WHERE course_id?; String maxSql SELECT max_num FROM course WHERE id?; // 已选人数 max_num 时返回 false提示课程已满两条查询在同一个连接里执行避免并发时读到不一致的数据。这个改动不碰 JSP只在 Dao 层加判断、Servlet 改一下提示文字十分钟能改完。6.2 答辩前按这张清单自查检查项操作通过标准登录拦截未登录直接访问 course/list被踢回 login.jsp角色隔离三种账号分别登录菜单与数据范围匹配成绩闭环教师录入学生查询分数一致重复选课同一学生连点两次选课数据库只有一条中文完整性新增带中文的课程列表与库表不乱码我从那以后每次拿到一套 JavaWeb 项目都强制先走一遍「数据库脚本 → 容器版本 → 登录链路」再动手改代码。版本对不上、编码不统一的项目十个里有九个是环境问题地基摆正了再谈扩展会少走很多弯路。这套 Servlet JSP 的选课系统复杂度刚好够完整走一遍从部署到改造的过程结构不花哨适合反复拆开重装。希望帮到你跑通之后你会发现JavaWeb 老项目远没有想象中难啃。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/10/7 6:15:21

LSTM时间序列预测实战:实例代码解析与避坑指南

简介:面向机器学习初学者与时间序列预测开发者的LSTM入门实例,以房地产价格预测为场景,完整演示长短期记忆网络从数据预处理到权重更新的实现过程。LSTM通过输入门、遗忘门、输出门与细胞状态协同解决传统RNN的梯度消失问题,该实例…

2026/10/7 6:15:21

上下文工程与Agent Harness:AI编码代理10x效率实践指南

这两年AI编码代理的讨论热度一直在涨,但绝大多数人的用法还停留在“开个对话窗口、把报错贴进去”的阶段。真正拉开差距的,其实不是模型选谁、参数多大,而是两件常常被忽略的事:Context Engineering(上下文工程&#x…

2026/10/7 6:10:21

AI编程工作流实战:三个可立刻复用的高效开发流程

1. 为什么“能立刻复用”比“功能强大”更重要我见过太多人收藏了几百个AI编程工具,从代码补全到Agent框架,硬盘里塞满了各种教程和配置,但真正每天在用的工作流,掰着手指头数不超过三个。问题出在哪?不是工具不够好&a…

2026/10/7 6:55:23

Agent-Reach:AI Agent与外部工具统一触达层设计与实践

提到 Agent 类项目时,很多人第一反应是“换个更强的模型”或者“把提示词再调一调”。但真正把智能体接到生产环境里跑过一阵子之后,你会意识到一个挺残酷的事实:模型决定的是“想不想得到”,而真正卡住系统的往往是“够不够得着”…

2026/10/5 6:32:56

Jev+Agent接管浏览器:browser-use实战与jev-ultrafast性能优化

1. 从“Jev”说起:为什么我要把Agent接进浏览器“Jev”这个词最近在圈子里出现的频率越来越高,很多人第一次听到会以为是某个新模型的名字,其实它更像是一种思路——把Jev模型的能力当作底座,通过Agent的方式去接管浏览器&#xf…

2026/10/6 4:01:51

多智能体集群实战:DeepAgents编排、MCP与A2A协议及Skills体系

1. 从"单兵作战"到"集群协同":多智能体编排到底在解决什么问题如果你最近在折腾 Agent 相关的东西,大概率会有一种感觉:单个 Agent 能做的事情,其实很快就摸到天花板了。你给它一个提示词,挂几个工…

2026/10/6 17:46:51

无源低通滤波器设计实战:从RC到LC,手把手教你避开那些坑

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

2026/10/7 1:05:03

ESP32免重刷固件:浏览器直接修改NVS键值实现WiFi配置更新

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

2026/10/7 1:05:03

SAP HANA查询结果导出CSV:避开乱码、性能与权限的实用指南

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

2026/10/7 1:05:03

数字后端Placement阶段Density与Congestion控制实战

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

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑