基于Spring Boot的计算机网络课程在线学习系统开发实战

发布时间:2026/10/20 8:51:55

基于Spring Boot的计算机网络课程在线学习系统开发实战 拿到一套基于Spring Boot的计算机网络课程在线学习系统源码我第一反应不是急着点启动按钮而是先把项目结构和数据库脚本翻了一遍。很多人觉得这种课程设计项目简单但真正让它在班级甚至全系稳定跑起来要处理的事情比想象中多。这套系统的核心场景其实很明确把计算机网络课程的视频、课件、章节练习、在线测试和成绩汇总放到同一入口教师统一管理教学资源学生按章节学习并保留进度管理员查看整体数据。它解决的是传统教学中资源散落在网盘和聊天记录里、学习进度靠自觉、考试判分靠手工的问题。适合正在做毕业设计、课程设计或者想学Spring Boot全栈实战的人拿来当参考。1. 项目概述与整体设计思路1.1 这门在线学习系统到底在解决什么计算机网络课程的信息化最麻烦的不是写代码而是想清楚边界。早期我见过不少团队把在线学习系统做成视频网站一上来就考虑转码、切片、CDN结果交作业时连课程章节都还没做完。其实对于高校内部或培训机构的场景用户量级通常只有几百到几千核心任务就是把课程资源结构化管理。一套标准的计算机网络课程在线学习系统至少要覆盖这几条链路教师创建课程维护课程简介、封面、章节信息。教师在章节下上传视频、课件、扩展阅读材料。学生选课之后在课程中心按章节顺序学习系统记录每条视频看到第几秒。教师发布章节练习和测试学生在线作答系统自动计算客观题成绩。管理员维护用户、课程分类、公告并查看基础学习数据。这个系统不能解决教学水平问题但能解决教学组织问题。学生不用再问“PPT在哪发过”教师不用在群里反复发文件期末统计成绩也有一份相对规范的数据来源。基于Spring Boot来做更多是考虑到部署和维护成本低一台普通服务器或者个人电脑就能跑起来。1.2 技术选型为什么是Spring Boot而不是其他方案如果只看课程设计可以用Python Flask也可以直接用Node.js。但Spring Boot在校园项目里使用率最高原因不只是“网上资料多”。Spring Boot的自动配置能力让数据源、Web容器、事务管理开箱即用对一个需要快速交付的项目来说能省掉大量配置时间。同时它和Spring生态天然绑定以后想加Spring Security、Spring Data Redis、Spring Cloud组件都很顺手。我拿到这套源码时关注的几个技术点后端基础框架Spring Boot内嵌Tomcat打包后直接 java -jar 启动。ORM层MyBatis-Plus分页和条件查询比较方便适合中小型管理类系统。鉴权方案登录后返回JWT前端保存在本地请求时放到Authorization头里。前端页面采用服务端渲染配合部分Ajax交互整体难度适中拿到源码后不用额外启动Node服务。数据库MySQL通过脚本初始化表和基础数据。对比来看有的源码把权限做得极其复杂角色表、菜单表、权限点表全套RBAC十几张表最后实际用到的只有“教师/学生/管理员”三个入口。这个项目没有过度设计用角色的方式做了区分后续想扩展可以再升级。对于学习性质的项目简单清晰比宏大完整更重要。2. 系统架构与核心模块设计2.1 前后端交互与模块划分整个系统在架构上属于典型的单体应用前端页面由后端渲染局部动态操作走Ajax接口。选单体的原因很实际并发量不高部署简单学生和教师只需浏览器打开一个地址。把服务拆成前端项目和后端项目反而给课程设计的部署增加麻烦。从后端接口来看模块可以分成四层用户层登录、注册、个人信息维护、修改密码。内容层课程分类、课程信息、章节管理、资源文件管理。学习层选课退课、章节浏览、学习进度上报、收藏/笔记。测试层题库维护、考试发布、答题记录、自动判分、成绩查询。模块之间通过统一返回结构交互。比如所有接口返回{ code: 200, message: success, data: ... }前端拿到 code 再决定提示什么。这样比直接返回Map或者裸对象更容易集中处理异常。目录结构上源码通常会把Controller、Service、Mapper分层放好。拿到项目先看包名如果类是随便堆在一个文件夹里后期会非常痛苦。好的项目即使功能简单分层也应该清晰。这个系统的层级划分基本接近标准Spring Boot工程结构controller只接收参数和返回结果service处理业务逻辑mapper负责SQL。2.2 核心数据模型课程、章节、学习进度只要把课程、章节、学习进度这三张表关系设计清楚系统的骨架就稳了。课程表course的核心字段字段名类型说明idbigint主键course_namevarchar课程名称teacher_idbigint授课教师用户IDcover_urlvarchar封面图片地址category_idbigint所属分类statustinyint状态1上架0下架create_timedatetime创建时间章节表chapter记录课程下的学习单元比如“物理层”“数据链路层”“网络层”就是典型的计算机网络章节划分。字段主要包括字段名类型说明idbigint主键course_idbigint所属课程chapter_namevarchar章节名sort_orderint排序号video_urlvarchar视频文件地址attach_urlvarchar课件附件地址contenttext章节文字说明学习进度表learning_progress是系统的核心特色字段字段名类型说明idbigint主键user_idbigint学生IDchapter_idbigint章节IDprogressint观看进度单位秒finishedtinyint是否完成update_timedatetime最后更新时间上课场景里学生看视频可能中途关掉下次进来需要继续播放。如果没有进度表每次都要从片头开始非常影响体验。这个表的设计要点是user_id chapter_id应该做联合唯一索引避免一个学生对同一章节产生多条记录。以后在代码里使用“存在则更新不存在则插入”的方式维护。2.3 权限模型简单角色字段够用还是上RBAC权限设计是课程系统容易失控的地方。有的项目参考企业级后台设计了用户表、角色表、菜单表、角色菜单关联表最后发现真正的入口只有教师后台和学生后台。这个项目直接用role字段区分0 管理员1 教师2 学生登录后把角色信息写进JWT后端在Controller上用注解或者拦截器判断。这种方式在用户量不超过几千、且权限点不会频繁变化时完全够用。缺点是角色一旦变多代码里散落的if(admin.equals(role))会越来越难维护。所以如果要扩展成校级平台可以考虑把角色表提炼出来但初学阶段没必要。在权限上需要特别注意一点菜单隐藏不等于接口安全。很多系统前端把某个按钮隐藏了但接口没有校验学生直接构造请求就能访问教师接口。这类问题在评审时非常致命。实现时应该通过Spring Security或自定义拦截器统一校验路径权限而不是靠页面上“看不见”来兜底。3. 关键功能源码解析3.1 登录鉴权JWT认证与拦截器组合我拿到的这套源码登录流程是标准的JWT方式。用户提交用户名和密码后端校验通过后生成一个token前端每次请求都带着。Spring Security在其中承担了过滤器链的部分职责。生成JWT的工具类核心逻辑可以这样理解String token Jwts.builder() .setSubject(userId.toString()) .claim(username, username) .claim(role, role) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() 7 * 24 * 3600 * 1000L)) .signWith(SignatureAlgorithm.HS256, secretKey) .compact();token有效期建议不设置太长。有的源码直接设置30天看起来方便但一旦泄漏别人能持续冒充用户。我在实际使用中一般设成7天前端在过期前用刷新接口更换新token或者直接让用户重新登录。对校园项目来说让学生重新登录一次并不算坏事反而能减少被盗用的窗口。密码存储不能使用明文。源码里用的是BCrypt加密注册时对密码加密登录时调用matches方法校验。如果你拿到别的项目发现密码字段直接可以读出来需要马上改掉。课堂项目演示时容易被忽略但安全评审一票否决。登录成功后接口还需要一个拦截器来解析token。通常的实现是写一个JwtInterceptor重写preHandle从请求头里取token解析成功就放行失败则返回401。排除路径包括登录接口、注册接口、以及前端静态资源路径。3.2 课程资源上传本地存储与访问映射视频和PPT是课程系统的核心资源。这套源码没有接入云存储而是把文件保存到服务器某个目录。实现逻辑比较直接PostMapping(/upload) public Result upload(RequestParam(file) MultipartFile file, RequestParam(chapterId) Long chapterId) { String originalFilename file.getOriginalFilename(); String ext originalFilename.substring(originalFilename.lastIndexOf(.)); String fileName System.currentTimeMillis() _ UUID.randomUUID().toString().substring(0, 8) ext; String datePath new SimpleDateFormat(yyyy/MM/dd).format(new Date()); String fullDir uploadBasePath / datePath; File dir new File(fullDir); if (!dir.exists()) { dir.mkdirs(); } file.transferTo(new File(fullDir / fileName)); // 保存文件相对路径到资源表 String fileUrl /uploads/ datePath / fileName; return Result.success(fileUrl); }文件名必须重新生成这个细节很关键。直接用用户上传的文件名可能出现重名覆盖也可能出现中文乱码或特殊字符问题。用时间戳加随机串可以避免大部分麻烦同时保留原始后缀用于判断类型。上传目录不能放在项目根目录的target下面因为每次重新打包可能被清掉。我建议在启动配置里设置一个绝对路径比如/data/online-course/uploads然后把该目录通过Spring Boot的静态资源映射暴露出来。如果源码里用的是相对路径部署时改成绝对路径即可。视频格式建议在前后端都要校验。后端检查扩展名和白名单同时限制文件大小。前端可以先校验后缀缩短用户等待时间。真正的校验必须放在后端因为接口可以被绕过直接调用。3.3 学习进度断点续看数据库更新的正确姿势在线学习的体验核心在于进度保存。学生看了20秒视频关闭页面下次进来还能从20秒继续。前端可以在播放器的事件里定时上报比如每5秒调一次接口。player.on(timeupdate, function () { var currentTime player.currentTime(); if (currentTime - lastReportTime 5) { reportProgress(chapterId, Math.floor(currentTime)); lastReportTime currentTime; } });后端接收后处理逻辑要有约束。第一检查进度值不能大于视频总时长否则会出现“学习进度超过100%”的脏数据。第二用事务或者唯一索引防止同用户同章节产生重复记录。第三不能每次都先查再更新更推荐“存在即更新否则插入”的方式减少一次数据库往返。Service层可以参考下面的实现LearningProgress existing progressMapper.selectOne( new LambdaQueryWrapperLearningProgress() .eq(LearningProgress::getUserId, userId) .eq(LearningProgress::getChapterId, chapterId)); if (existing ! null) { existing.setProgress(progress); existing.setFinished(progress totalDuration); progressMapper.updateById(existing); } else { LearningProgress np new LearningProgress(); np.setUserId(userId); np.setChapterId(chapterId); np.setProgress(progress); progressMapper.insert(np); }这里有个并发问题如果学生开了两个标签页同时观看同一课程两个请求同时查到没有记录然后同时插入可能产生两条记录。解决办法是给user_id和chapter_id加联合唯一索引插入时捕获唯一键冲突再转换为更新操作。或者直接用MySQL的INSERT ... ON DUPLICATE KEY UPDATE一条SQL解决问题。在课程设计的体量下这属于“可以不处理但最好知道”的问题。4. 本地部署从源码到跑通4.1 环境准备与版本对应关系拿到源码后想跑起来先不要急着用最新的环境。Spring Boot项目对版本比较敏感JDK版本、Maven版本、MySQL版本都可能影响启动。我习惯先看pom.xml里的父依赖版本再决定本地环境。环境推荐版本说明JDK1.8 或 11大多数Spring Boot 2.x项目要求1.8Maven3.6用IDEA内置Maven也可以MySQL5.7 或 8.05.7更稳8.0需要注意驱动配置IDEIDEA或Eclipse建议直接用IDEA社区版很多同学在这里踩的第一个坑是本机只装了JDK 17然后强行打开一个基于JDK 8的Spring Boot 2.x项目结果报模块错误或者字节码版本问题。最简单的办法是装一个JDK 8在IDEA里配置好项目SDK和Maven的Java版本就能避免大多数启动失败。4.2 配置文件和数据库初始化运行前需要改两类配置。第一是数据源打开application.yml或application.properties把数据库地址、用户名、密码改成自己的本地值。spring: datasource: url: jdbc:mysql://localhost:3306/online_course?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: root driver-class-name: com.mysql.cj.jdbc.DriverMySQL 8.0的驱动是com.mysql.cj.jdbc.DriverMySQL 5.7用com.mysql.jdbc.Driver也可能没问题。serverTimezone必须配置否则连接时经常报中国时区相关的错误。数据库名称如果不存在需要手动先在MySQL里创建源码自带的建库语句一般不会自动建库。导入SQL脚本时要留意执行顺序。先把user、course、chapter这些不依赖其他表的基础表建好再导入关联表和数据。有的源码SQL脚本里包含drop语句执行时会清掉相同名称的表所以不要在已有重要数据的库上直接跑。第二是文件上传路径。把源码里的upload.path配置改成你本机的一个目录比如D:/online-course-uploads。如果不改默认路径在服务器上可能没有写权限上传文件时报FileNotFoundException。4.3 启动流程与访问入口配置完成后在IDEA里找到主启动类类名通常是OnlineCourseApplication或LearningSystemApplication右键直接运行。看到类似下面的日志说明启动成功Tomcat started on port(s): 8080 (http)默认访问地址是http://localhost:8080。如果前端模板放在templates目录下根路径可能直接跳转到登录页。建议先用管理员账号登录查看后台再用学生账号走一遍选课学习流程。源码包里通常会有src/main/resources/sql目录里面的init.sql会预置测试账号。测试账号一般类似 admin/123456学生或教师账号要查看SQL里的insert语句确认。如果源码没有内置账号需要自己通过注册接口创建用户再手动修改role字段。5. 问题排查与避坑实录5.1 数据库连接失败的三类原因在线学习系统跑不起来八成问题出在数据库连接上。常见提示包括Access denied for user、Unknown database、Unable to load authentication plugin。Access denied用户名密码错误或者用户权限不足。检查application.yml里账号密码是否和本地MySQL一致。Unknown database数据库没有创建或者URL里的库名拼错。注意Linux下库名严格区分大小写。Unable to load authentication plugin caching_sha2_password这是MySQL 8.0默认认证插件与旧驱动不兼容。解决办法是更换成com.mysql.cj.jdbc.Driver或者在MySQL里把用户认证改成mysql_native_password。我排查这个问题的习惯是先在Navicat或命令行里测试能否用同样的账号连接数据库能连上再让Spring Boot去连这样可以快速定位问题出在应用配置还是数据库本身。5.2 文件上传大小受限和前端播放异常上传视频时最常见的是报错MaxUploadSizeExceededException。Spring Boot默认上传文件限制是1MB视频很容易超过。需要修改配置spring: servlet: multipart: max-file-size: 500MB max-request-size: 500MB注意如果系统部署时前面有NginxNginx的client_max_body_size也要同步修改否则文件在到达Spring Boot之前就被Nginx拦截了。这个坑我在对接实际部署时踩过前端报500后端日志里什么都没有最后发现是Nginx配置没改。视频正常上传后播放时出现只有声音没有画面或者点击播放没有任何反应大概率是浏览器不支持这种编码格式。计算机网络课程的录屏视频经常是MP4编码不统一。建议统一转成H.264编码的MP4兼容性最好。也可以考虑前端集成一个支持HLS播放的播放器但需要把视频切片对课程设计项目来说成本偏高。5.3 登录态失效与用户串号问题使用JWT的系统中用户反馈“刚登录就退出”或者“A同学的账号能看到B同学的数据”主要需要排查三点。第一token过期时间设置太短。有的项目为了演示方便把过期时间设为30分钟学生看一节视频中途去吃饭回来就失效了。建议有效期设在7天左右。第二UUID或者用户ID没有放到token里后端Controller方法里从token取用户ID时取到空值再走了默认逻辑导致查询条件缺失返回了所有数据。这种情况正是“串号”的常见来源。我建议每个涉及用户数据的接口都从当前登录上下文获取用户ID而不要信任前端传入的userId参数。第三登出功能没有真正使token失效。JWT无状态的特性决定了服务端不保存登录状态登出只是前端删掉本地token。如果token被截获在有效期内仍然可以访问。对课程系统来说可以接受如果要做严格一点可以在Redis里维护一个token黑名单登出时把当前token加进去接口解析前先查一下黑名单。5.4 接口跨域与前端请求404开发时如果前端单独启动比如用了Vue脚手架和后端不在一台服务上就会出现跨域。解决方式有两种一种是在Controller上加CrossOrigin另一种是写一个全局CORS配置类。课程项目最简单的是写全局配置Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(*) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }如果前端请求接口返回404先看控制台访问的后端地址是不是http://localhost:8080/api/...再看后端Controller的类上有没有加上RequestMapping(/api)。很多源码习惯加/api前缀前端调用时容易漏掉新手排查半天发现是路径拼写问题。6. 二次开发与性能优化建议6.1 数据库索引与查询优化课程系统运行一段时间后最常出现的性能瓶颈是选课记录和学习进度查询。以learning_progress表为例查询条件一般是user_id和chapter_id那就应该建立联合索引如果还经常按课程维度统计学习时长可以再加一个course_id到冗余字段避免每次通过章节反查课程。在选择课功能里学生选课后会创建选课记录。如果同时有几百个学生抢同一门课需要对课程选课人数做防超卖处理。最稳妥的做法是在事务里先对课程行加锁再插入选课记录。不要使用先查再判断人数的方式并发场景下两个请求可能同时查到同一个剩余名额。对于视频文件如果服务器磁盘空间有限可以定时清理未关联到任何章节的孤儿文件。视频资源消耗大但代码层面清理逻辑要小心不能误删真实数据。建议设计一个“待清理资源”状态管理员确认后再删除文件。6.2 在线实验模块可以怎么加计算机网络是一门实践性很强的课只靠看视频和做选择题还不够。这个系统后续可以扩展一个在线实验模块思路是利用服务端模拟常用网络命令和抓包过程比如设置虚拟的拓扑环境让学生通过网页提交配置命令后端调用网络模拟工具执行并返回结果。这项功能可以作为独立的模块与现有系统并行不需要大规模改造。也可以加入抓包数据文件解析功能把抓包文件上传后解析出协议层次跟章节测试关联。比如学生在学习HTTP章节时上传一个HTTP请求的抓包文件系统自动识别请求头并生成题目增强课堂参与感。如果往企业级方向走再考虑引入消息队列处理视频转码、用对象存储替换本地存储、将前端项目独立部署。但这些都是后话对于一个以课程设计为起点的项目先把基础流程跑通把课程内容管理好已经能覆盖绝大多数使用场景。6.3 源码阅读与二次开发的三条建议第一不要从Controller层开始读代码。先读SQL脚本建立数据模型认知再读Service层了解业务规则最后看Controller确认接口路径。数据模型是整个系统的底层地基。第二做二次开发前先在本地用测试账号完整走一遍“管理员建课、教师传资源、学生选课学习、在线测试”的主流程。只有把原始流程走通才能知道改动会影响哪些地方。直接改代码很容易改到一半发现某个功能依赖了没注意的隐藏逻辑。第三不要为了“看起来高级”盲目引入新技术。在线学习系统的核心体验是稳定访问和进度可靠与其加一堆缓存中间件不如先保证视频上传成功率高、播放路径可用、数据不丢。最后说一点个人体会。像这种基于Spring Boot的在线学习系统代码本身不算难真正难的是把一门课程的资源组织逻辑落到数据表里。课程、章节、进度、测验环环相扣。如果让我重新做一遍我会把数据库设计时间再拉长一点先在纸上画出学生和教师的操作路径确认哪些是核心流程哪些是辅助功能再动手写Controller。很多崩溃现场都是因为一开始把表设计得太随意后期改字段、补索引、修数据远比写几行业务代码更痛苦。源码只是提供了一个起点真正让它成为自己能维护的系统躲不开那些本地部署、字段调整和数据修复的琐碎工作。这个过程没有捷径但每踩一次坑后面就会少踩一次。
延伸阅读

更多相关文章

2026/10/20 8:51:55

超高清可编程Smith圆图生成系统:阻抗与导音双坐标矢量可视化

简介:本资源是一份专为射频与微波工程师、通信专业师生及硬件调试人员设计的彩色超高清Smith圆图PDF文档,聚焦阻抗与导纳联合分析,高效支撑射频匹配网络设计、S参数解读、驻波比(SWR)估算及馈线长度补偿等核心任务。文…

2026/10/20 8:51:55

EasyDarwin Windows部署实战:搭建RTSP流媒体服务与避坑指南

简介:EasyDarwin是一款开源实时流媒体服务器,基于HTTP/RTSP标准协议,支持H.264/MPEG-4/MJPEG等编码格式,适合监控、教育、医疗等场景。面向Windows 64位系统,适合快速搭建视频推流、拉流与转发服务的开发者和运维人员。…

2026/10/20 8:46:54

std::ranges性能剖析:从迭代器到硬件友好的现代C++写法

看到这个标题,我猜你至少写过一段时间 C。std::ranges 在 C20 里正式落地,很多人第一反应只是“替换循环的语法糖”,甚至觉得 transform、filter 这套声明式写法会拖慢程序。但我在自己的项目里把一段老代码改成 ranges 管道之后,…

2026/10/20 9:57:25

天工Skywork桌面版部署指南:从源码包到可运行环境

简介:这份资源面向希望零代码上手国产桌面AI代理工具的办公人士与知识管理者,提供天工Skywork桌面版的完整部署指南与可运行源码,帮助非技术用户绕开WSL2,在Windows环境下完成原生部署。压缩包共3个文件,约4KB&#xf…

2026/10/20 9:57:25

10亿美元融资与三顾茅庐:AI泰斗押注世界模型与自监督学习

这事刚出的时候,我的几个技术交流群一下午都没消停。一位在卷积神经网络时代就封神的泰斗级学者A,从老东家那边转身离场,又跑到一位视觉表征领域青年学者B的门下反复游说,最终把这位长年在自监督学习一线深耕的华人学者拉进了自己…

2026/10/20 9:57:25

C#抓包实战:SharpPcap与PacketDotNet解析数据包

简介:面向C#网络编程学习者与开发者的数据包捕获解析完整项目,基于Socket编程实现指定端口的IP、TCP、UDP报文监听与解析,可辅助网络排错、协议学习和安全分析。压缩包共82个文件、约1.12MB,以24个C#源代码和3个可执行程序为主体&…

2026/10/20 9:57:25

基于SSM+Vue的天气查询毕设:从选题架构到部署上线的完整实战

又一个毕设季,后台经常有人问我:“不想做商城系统,也不想做博客了,有没有一个题目既不像玩具、又不会把自己写崩?”我的回答通常是同一个:天气查询。别小看这个小题目,它几乎没有支付、权限、并…

2026/10/20 9:52:24

Android USB串口打印热敏小票的底层通信与ESC/POS实战

1. 这不是“接个打印机”那么简单:Android USB串口打印的本质挑战很多人看到“Android USB串口打印结账单小票”这个标题,第一反应是:“不就是连个热敏打印机,发几行文本过去吗?网上一搜全是教程。”我最初也是这么想的…

2026/10/19 18:06:35

金融AI落地指南:从调研报告到技术选型与工程实践

简介:这份PDF报告聚焦2025年金融服务业中国AI现状与趋势,面向金融科技从业者、AI战略规划人员及关注行业数字化转型的研究者,帮助读者把握生成式AI在银行、支付、交易等场景中的落地路径与投资方向。资源包内含1个PDF文件,大小约1…

2026/10/20 1:05:37

单片机C运行时从启动到多任务:中断安全与共享资源冲突

/* 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:05:37

GCC编译四步详解:预处理、编译、汇编、链接实战

/* 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:05:37

FIW线是什么?FEP注塑线与普通氟塑料线的区别及应用指南

/* 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
免费获取方案
☎咨询二维码 ☎ ↑