尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Spring Boot选课系统实战:从并发控制到部署上线的完整指南
选课系统这东西听起来像是个毕业设计常客但真要把它做扎实从需求梳理到并发控制再到部署上线每一步都是坑。我这些年经手过好几个类似的教务系统项目包括帮某高校改过一套跑了好几年的Spring Boot选课系统今天就把整套从设计到落地的思路、代码要点、还有踩过的坑一次性说清楚。无论你是准备用它做课设还是真要在生产环境扛住全校选课高峰这篇都能给你一套可以直接抄作业的参考。先说清楚这套系统本质是什么Spring Boot选课系统就是一个把传统线下选课流程搬到Web端的管理平台核心要解决三件事——学生能查课、选课、退课老师能开课、改课、录成绩管理员能管理用户、审核课程、监控选课数据。听起来不难但一旦选课时间集中爆发成千上万个请求同时打过来的时候如何保证不超选、不重复选、成绩不错乱才是真正考验系统设计能力的地方。1. 整体设计与需求拆解1.1 三类角色和他们的真实诉求很多人做选课系统上来就建表写接口这是典型的顺序搞反了。我习惯先列角色再列每个角色的核心操作最后推导出数据结构和接口清单这样后面写代码基本不会返工。学生端真实诉求登录后能看到本学期所有可选课程能按课程名、教师名、上课时间筛选选课之前能看课程剩余容量选课动作要快反馈要明确——要么选上要么提示容量已满或时间冲突选上之后能在“我的课表”里看到并且支持在规定时间内退课。注意这里学生最敏感的是“我到底选上没有”所以接口设计上一定要有明确的状态标识不能含糊。教师端诉求登录后能开出自己的课程设定课程名称、学分、上课时间、容量上限能查看哪些学生选了这门课能录入学生成绩并发布。有一个细节容易被忽略教师只能管理自己创建的课程这属于数据权限问题不是所有课程权限都开放给他。管理员端诉求维护学生和教师账号做批量导入导出审核教师提交的开课申请查看全校选课统计数据比如每门课的选课人数、热门课程排行、退课率在极端情况下能强制关闭某门课的选课通道。基于这些诉求我整理的模块划分是这样的认证模块、课程管理模块、选课模块、成绩模块、用户管理模块、统计模块。其中选课模块是最核心也是最复杂的后面单独讲。1.2 选课业务中最容易被忽视的边界情况选课系统业务不算复杂但边界情况极多我列举几个真实发生过的场景你设计的时候一定要覆盖到。第一同一学期同一学生可以选多少门课很多系统没限制结果有人一学期选了二十多门课导致教室容量和教师工作量全乱套。合理做法是加一个每学期选课上限比如本科默认20学分。第二同一门课不同班级、不同教师算不算冲突比如“高等数学A”和“高等数学B”在不同时间上课但课程代码不同学生是否可以同时选这里要定义清楚同代码课程不能重复选择但不同代码的时间冲突要校验。第三退课之后的容量释放是要立即释放还是延迟释放立即释放的话抢课高峰会产生严重的“占座又退座”抖动延迟释放则可能造成容量浪费。我当时采用的是立即释放但加了频率限制同一学生一小时只能退课3次从源头上防住恶意操作。第四学生换了选课时间、教室容量变了已选学生怎么处理这就涉及课程开课状态机草稿、审核中、已发布、已满员、已关闭、已归档。状态机设计好很多问题都能提前拦掉。2. 技术选型与数据库设计2.1 为什么是Spring Boot MyBatis-Plus选这套组合不是因为它有多新恰恰是因为它足够稳、生态足够成熟。Spring Boot负责把整个应用快速搭起来约定大于配置启动一个Web服务几乎零成本MyBatis-Plus在MyBatis基础上做了增强单表CRUD不用手写SQL分页插件、乐观锁插件、逻辑删除这些都是开箱即用非常适合这种以CRUD为主的管理系统。持久层框架的选择上有人会问为什么不直接用JPA。我的观点是选课系统涉及大量动态SQL查询多条件组合筛选、批量插入学生批量导入、复杂的统计报表MyBatis-Plus对SQL的控制力更强排查性能问题时可以直接看SQL执行计划不会像JPA那样在底层帮你生成一堆看不懂的查询。当然如果你团队对JPA很熟用它也没问题但我的经验是MyBatis-Plus更适合这种系统。前端我用的是Vue 3 Element Plus搭配Axios做请求。这个组合算是目前后台管理系统的主流方案组件库齐全表格、表单、弹窗、穿梭框都有现成的开发效率很高。如果你只是想快速跑通后端逻辑前端用服务端渲染的Thymeleaf也行但我更推荐前后端分离因为选课高峰时前端静态资源可以丢到CDN或Nginx上后端只处理API请求抗压能力完全不是一个级别。2.2 数据库表设计六张核心表和关系选课系统的数据库设计核心表我建议至少六张每张表都加上通用字段create_time、update_time、deleted方便后续排查逻辑删除问题。用户表sys_user是最基础的表字段包含id、username、password存BCrypt加密后的值、real_name、roleSTUDENT/TEACHER/ADMIN、student_no或teacher_no、status启用/禁用。这里有个设计经验学生和教师虽然业务属性不同但登录认证逻辑一样所以我倾向于放一张用户表用role字段区分再通过profile表或冗余字段保存各自扩展信息。不要一开始就分成student表和teacher表否则登录接口要写两套非常蠢。课程表course保存课程基本信息course_code课程编号、course_name、credit、teacher_id、course_type必修/选修、max_student容量上限、selected_count当前已选人数、term_id学期、status草稿/审核/发布/关闭。selected_count这个冗余字段很关键后面并发控制会用到。选课记录表course_selection保存选课关系id、student_id、course_id、course_code冗余方便查重、selected_time选课时间、source手动选/退课重选、status正常/退课/已归档。这张表要建联合唯一索引具体是(student_id, course_id, term_id)还是(student_id, course_code)取决于业务定义——我的建议是后者因为同一学生同一学期不能选同一课程代码哪怕开课班级不同。学期表term保存学期信息term_name如“2024-2025学年第一学期”、start_time、end_time、select_start_time、select_end_time、is_current。选课系统一定要有学期概念否则数据会越滚越乱。成绩表course_scoreid、student_id、course_id、score、grade_point、status未录入/已发布。日志表sys_log记录关键操作日志包括登录、选课、退课、成绩修改。选课高峰期这个表的数据量会很大可以考虑按月份分表或者用AOP异步写入避免影响主流程性能。六张表的关系用一句话概括用户表通过选课记录表和课程表关联课程表和成绩表通过学生与课程关联学期表作为全局维度把每个学期的业务数据隔离开。2.3 容量规划与索引设计选课系统的数据量通常不会特别大但并发量很吓人。以某高校为例一届学生8000人平均每个人选6门课选课记录表一年也就5万条左右三年撑死15万条。这个数据量对MySQL来说毫无压力真正的压力在瞬间写入的并发量上——选课开放前10分钟可能涌进来几千个并发请求。索引要设计在最容易成为查询瓶颈的地方选课记录表的(student_id, term_id)联合索引用于查询“我本学期选了什么课”(course_id, selected_time)联合索引用于课程选课人数趋势分析课程表的(term_id, status, course_type)联合索引用于前台课程列表的筛选查询。还有一个容易漏掉的is_current学期字段要优先过滤否则后期数据多了每个查询都扫描全表性能会明显劣化。3. 核心功能实现与前后端对接3.1 登录认证与权限控制登录认证我用的JWTJSON Web Token。Spring Boot里集成JWT的方式很简单引入jjwt依赖登录成功后生成token前端放在请求头Authorization里后端用拦截器统一校验。我建议把JWT的密钥放到Nacos或application.yml外部配置中不要硬编码在代码里防止泄露后所有token都可伪造。权限控制上很多人用Spring Security JWT组合但我觉得对这种角色只有三种的系统用一个简单的拦截器加角色判断就够了Spring Security引入进来学习成本高、配置繁琐收益并没有想象中的大。我在实际项目中是自定义了一个AuthInterceptor先解析token拿到userId和role再把用户信息放入ThreadLocal供后续controller直接获取。同时用RequireRole注解标注在Controller方法上比如RequireRole(TEACHER)拦截器里判断角色不匹配就直接返回403。这套轻量方案在真实项目中跑了三年没有出过权限漏洞。密码存储必须用BCrypt。如果选课系统密码泄露学生群体是典型的弱密码重灾区明文存储绝对不行。Spring Security的BCryptPasswordEncoder是现成的秘码加密后再入库即使数据库被拖走也无法直接还原密码。3.2 学生选课与退课的核心流程选课接口是整个系统中最重要的接口也是我调试时间最长的接口。先描述一下正常选课流程的完整链路学生点击“选课”按钮后前端把courseId和studentId传给后端。后端先校验当前时间是否在选课时段内避免系统开放前的抢跑或者截止后的漏选再校验学生是否还能选课学分上限然后检查这个课程代码是否已经选过再检查上课时间是否和已选课程冲突最后检查课程剩余容量settle后把选课记录插入course_selection表同时将course表的selected_count加1。我把这些校验按“快失败”原则排了序越是简单能拦截的校验越靠前越是耗时的检查越靠后。比如时间校验是内存操作最优先选课查重需要查一次索引排第二时间冲突检查要查询学生已选课程排第三容量检查依赖的select_count字段读到内存后判断排最后。时间冲突检查是一个典型的“烂代码重灾区”逻辑。你不能简单地拿新课程的时间和每门已选课程比较是否重叠要考虑到跨天、跨周的情况。我给一套成熟做法每门课程设置week_day1-7周一到周日和section上课节次用二进制串表示比如section“111110000”表示第1到第5节课有课student已选的所有课程section字段做按位或运算得到一个“占用位图”新课程如果和这个位图的与运算结果不为0就说明时间冲突。这套位图方案简单高效比存多个时间段的方案好维护得多。退课流程相对简单校验学生确实选了这门课校验当前时间在退课截止日期之前然后删除选课记录逻辑删除即可减少course表的selected_count。这里有个经验退课操作也要加日志而且最好把退课原因记录下来方便教务老师后续统计退课数据。3.3 教师端课程管理与成绩录入教师端的功能相对轻量主要是课程CRUD和成绩管理。开课流程我建议做成审核制教师提交课程信息后课程状态变为“待审核”管理员在管理后台审核通过后状态变为“已发布”学生才能看到。这样设计的目的是防止教师随意发布低质量课程也避免开课信息错误直接暴露给学生。这里有一个细节教师编辑已发布的课程时如果课程已经被学生选择了修改上课时间会产生连锁影响必须做提示和二次确认。我在项目里是这样实现的课程表加一个version字段每次课程信息变更都触发版本号递增前端编辑时带上version参数后端更新时先比对version不一致则返回“课程信息已被他人修改请刷新后重试”。成绩录入要支持两种方式单条录入和批量录入。批量录入用Excel上传后端解析Excel文件后批量插入成绩表。解析Excel我用的是EasyExcel它在处理大文件时的性能比Apache POI优秀很多而且API简洁。批量录入必须提供一个“预览模式”——先把数据解析出来校验学生是否存在、学号是否匹配、成绩范围是否合法展示给教师确认后再真正入库这样可以避免一次性写入大量脏数据。3.4 前端接口设计上的取舍我在前后端对接时踩过最大的坑是接口返回格式不统一。前端团队最怕的就是一个接口返回data是对象另一个接口返回data是数组第三个又嵌套一层。我建议在项目中约定一个全局统一返回体{ code: 200, message: success, data: {} }所有接口都走这个格式前端在Axios响应拦截器里统一处理code和message业务代码里只需要关心data。这个统一返回体用Spring的RestControllerAdvice 自定义Result类实现十几行代码搞定但对接效率的提升是巨大的。分页查询的接口参数我也建议统一pageNum从1开始pageSize最大不超过100排序参数用orderBy字段拼接比如“selected_count desc”。不要每个接口自己发明一套分页格式否则前端写起来极其痛苦。4. 选课高峰的并发控制与防超选4.1 为什么会超选从一次线上事故说起我记得有一次真实事故选课系统开放的前30秒某热门课程120个名额瞬间被抢了180个。原因就是多个请求同时读到课程表的selected_count是119都判断“还能选”然后同时写入选课记录导致实际选课人数超过max_student。这其实是非常经典的“丢失更新”问题——多个事务并发读取同一数据各自基于旧值做判断最后各自提交导致数据错乱。要解决这个问题光在代码里写if (selected_count max_student)是不够的因为这个判断和后续的update是两条SQL中间有间隙并发请求就能钻空子。真正的解决方案必须从数据库层面入手而不是在应用层写判断。4.2 乐观锁方案版本号模式最简单可靠的防超选方案就是乐观锁。具体做法是给course表加一个version字段选课更新时执行的SQL是UPDATE course SET selected_count selected_count 1, version version 1 WHERE id #{courseId} AND version #{oldVersion}如果影响行数为0说明version已经不匹配即课程容量数据在本次操作期间被其他事务修改了此时需要重新查询课程状态返回前端“本课程已满员或选课人数有变化请刷新重试”。这个方案不需要引入额外组件纯数据库层面解决并发问题代码侵入也最小是我最推荐的第一版方案。MyBatis-Plus内置了乐观锁插件只需要在实体类字段上加Version注解然后在配置类里注册乐观锁插件它就会自动在update语句上附加version条件非常省事。但乐观锁有一个副作用在极高并发下失败率会很高。因为大量请求同时修改同一行数据真正成功的只有一个其他全部失败重试数据库的行锁竞争其实没有本质缓解。如果热门课程同时有1000人抢乐观锁会把这1000个请求串行化处理系统吞吐量可能不达标。4.3 悲观锁方案SELECT ... FOR UPDATE如果选课人数特别多比如全校公共课我建议对热门课程使用悲观锁。做法是在事务内先执行SELECT selected_count FROM course WHERE id #{courseId} FOR UPDATE这个语句会对course表的对应行加排他锁直到当前事务提交或回滚其他事务的读写都会被阻塞。拿到锁后在事务里做校验和更新最后提交。这样从机制上保证了同一时刻只有一个事务能修改这门课的容量数据不会出现超选。悲观锁的代价是阻塞和死锁风险。阻塞是因为热点行热门课程会成为全系统等待的焦点死锁是因为多个事务以不同的顺序锁定多行数据可能互相持有对方需要的锁。解决办法是保证事务尽量小、锁顺序尽量一致比如所有事务都先锁课程表行再操作选课记录表、设置合理的innodb_lock_wait_timeout超时时间默认50秒太长了我一般设5秒。4.4 Redis缓存与排队设计进阶优化如果前面的方案还是扛不住峰值就要上Redis做削峰。我实践过的思路是把“选课资格校验”和“选课落库”分离选课时先访问Redis预扣减课程容量Redis的DECR命令是原子操作减到负数说明已满进入这个阈值后再把选课请求放入Redis队列比如List或Stream由异步线程池批量消费真正落库时在事务里做最终一致性校验。具体做法课程发布时把课程ID和初始容量写入Rediskey设计为course:stock:{courseId}value是剩余名额。选课请求过来先执行DECR course:stock:1001如果返回值小于0说明已抢完立即返回“已满员”如果大于等于0则把(studentId, courseId)放入队列SELECT_QUEUE_1001同时返回前端“排队中”。异步消费者从队列取出请求执行数据库插入选课记录成功后再把学生信息加入已选集合。如果异步落库失败需要执行INCR course:stock:1001将名额回补。这套方案的优点是把选课高峰的写压力削平了数据库不再直接扛瞬时并发而是用Redis的原子性做前置拦截。但代价是选课结果不是实时的学生看到“排队中”后要等异步落库完成才能确认最终结果这对用户体感来说稍微有点影响。真实项目中我建议先上乐观锁用压测工具模拟1000并发观察指标如果系统仍有余量就没必要上Redis如果频繁超选且接口RT过高再考虑悲观锁或Redis排队方案。不要一上来就追求最复杂的架构这是很多菜鸟项目的通病。我见过的失败案例绝大多数不是技术不行而是过度设计把系统搞复杂了结果一个地方出问题全线崩。4.5 事务边界设计避免长事务选课涉及多个表必然要用事务包裹。但事务不能太大否则锁持有时间过长并发性能极差。我的原则是只把“必要的写操作”放在事务里查询操作尽量放事务外面。比如选课方法先做各种校验无锁操作然后开启事务执行容量扣减和选课记录插入提交事务。整个过程控制在50毫秒以内。另外注意Spring事务的失效场景最容易踩的坑是同类内部方法调用A方法调用B方法B上有Transactional但调用是通过this.method()而不是代理对象调用的事务不生效。解决办法是注入自身代理或者把需要事务的方法放到另一个Service里。这个坑看似基础但在真实项目中经常遇到而且排查起来非常隐蔽。5. 部署、性能调优与问题排查实录5.1 部署架构和服务器参数选课系统的部署架构我建议分两层Nginx负责静态资源、反向代理和负载均衡后端应用运行在Tomcat或Undertow容器中。如果预算充足还可以把后端的课程查询接口做成Redis缓存进一步降低数据库负载。JVM参数调优是我容易忽视但收益明显的环节。假设选课高峰期并发2000Tomcat默认线程数200是不够的要调整为400~800同时要控制最大等待队列防止线程池打满后大量请求堆积。JVM堆内存我一般设置在1GB到2GB之间同时开启G1垃圾回收器减少GC停顿对RT的影响。你如果用的是云服务器至少要2核4G起步。数据库参数里最重要的一个是连接池大小。连接池不是越大越好HikariCP官方文档里给过一个公式connections (core_count * 2) effective_spindle_count单机4核8线程的话连接数16~32就足够了。连接数设太多反而会导致线程上下文切换开销增大数据库连接也会成为瓶颈。再说说一个具体的压测数据参考4核8G的单机部署开启乐观锁选课接口Tomcat线程池400压测结果大概能扛住每秒300~500个选课请求RT平均在200毫秒以内如果上了Redis预扣减选课接口的QPS可以到1000以上RT降到50毫秒以内。真实的高校选课场景瞬时峰值一般在每秒几百的量级单机方案通常够用。5.2 压测工具的选取与压测方法压测工具我推荐JMeter因为它免费、图形化、能模拟并发用户并且可以把测试结果导出成HTML报告。选课系统的关键压测场景有三个登录接口、课程列表查询、选课接口。压测前一定要先造数据不要拿空表压否则测出来的结果没有参考意义。我一般会写一个数据生成脚本造10门课程、2000个学生、若干选课记录模拟接近真实的数据分布。压测时目标线程数分别设为100、300、500、800观察每个梯度下的QPS、RT、错误率画出性能拐点找到系统的临界容量。我踩过的一个坑是压测时数据库连接池被打爆现象是应用日志里疯狂报HikariPool timeout异常。后来排查发现是连接池大小设置成了10压测线程一多每个请求都要排队等连接属于典型的配置不足。把连接池调到20后问题立刻消失。5.3 线上问题排查速查表我整理了一份选课系统高频问题的排查速查表现象查找方向处理建议选课接口RT突增查看数据库慢查询日志检查是否走了全表扫描补索引选课提示系统繁忙查看Tomcat活跃线程数调大线程池检查是否有长事务数据库连接池耗尽查看HikariPool指标调大连接池上限压测找出临界值超选发生查看事务提交日志确认是否加了乐观锁检查锁失效原因课程列表打开很慢查看查询SQL执行计划给course表加(term_id, status)联合索引学生登录偶发失败查看Redis或数据库连接情况检查登录态session是否过期JWT是否被误判成绩录入后学生看不到查看成绩接口权限确认状态字段是否改为“已发布”有一个非常容易忽略的坑是Linux系统文件句柄数限制。Nginx和Tomcat在选课高峰期会创建大量网络连接如果系统的ulimit限制太小会直接报“Too many open files”错误表现为接口时不时失败。解决方法是修改/etc/security/limits.conf文件设置合适的nofile值并重启相关服务。这个坑一般不会出现在开发环境但生产环境几乎必踩。5.4 一套完整的日志追踪方案选课系统出问题时最怕的就是日志里找不到有用的信息。我建议从一开始就做好日志规范每个请求分配一个traceId通过MDC写入日志上下文全链路Nginx、网关、业务代码共享同一个traceId。这样前端反馈一个问题后端拿着traceId一搜就能定位到完整的调用链。具体实现不复杂在拦截器里生成UUID作为traceId放入MDC在Logback的Pattern里加上%X{traceId}日志信息会自动带上。响应头里也返回这个traceId用户在遇到问题时不至于只能截图说“系统错误”而是能把traceId提供给技术人员排查效率翻倍。日志级别要区分环境开发环境用DEBUG测试环境用INFO生产环境用WARN。线上选课高峰期日志量会非常巨大如果打成INFO级别磁盘很快会被写满而且真正要排查的WARN和ERROR日志会被淹没在海量信息里。我在生产环境用WARN级别跑起来就好配合异步appender对性能的影响几乎可以忽略。6. 把自己的项目打磨得更“能打”选课系统作为一类几乎是标配的Web后端项目很多人在简历上写过面试官也早就看腻了。如果只是罗列“用了Spring Boot MyBatis-Plus Vue做了一套选课系统”很难证明你自己的能力。我的经验是要在这个项目里刻意展示几个亮点“选课防超选的并发控制方案”“Redis缓存和队列削峰”和“通过压测发现并解决性能瓶颈”这三点是最容易在面试中打开话题的。具体呈现方式很简单项目README里写清楚压测数据比如“使用JMeter压测200并发下单接口QPS值、RT值、错误率分别是多少优化后变化如何”一个有真实数据支撑的项目比一万字的技术堆砌更有说服力。这个数据来源就是你自己的压测实操没有人能质疑你。还有一个加分项是写测试用例。很多选课系统的毕设和初级项目完全没有单元测试你如果给选课核心接口补上MockMvc集成测试覆盖选课成功、选课已满、重复选课、时间冲突、退课成功这几个关键场景项目质量一下就高出同行一大截而且对你理解代码行为也有很大帮助。最后再分享一个小技巧给选课系统的课程列表做一个Redis缓存key用course:list:term:{termId}:page:{page}缓存5分钟因为课程列表在选课开放前会被学生反复刷新属于典型的读多写少场景缓存效果立竿见影。加上这个点之后我的项目压测数据里课程列表查询的RT从180毫秒降到了15毫秒左右整体接口性能表现直接抬升了一个档次。选课系统看着简单但把并发控制、缓存策略、日志追踪、压测优化这些细节都做到位它就是一套能真正上线用的系统。希望这篇文章能帮你少踩几个坑把项目做扎实。
RELATED

相关推荐

Spring Boot会员制医疗预约系统开发实战:从需求到并发控制

Spring Boot会员制医疗预约系统开发实战:从需求到并发控制

做毕业设计或课程项目选课题的时候,“基于Spring Boot的会员制医疗预约服务管理信息系统”这个标题确实很常见,但你千万别被“常见”两个字误导了。这个课题能一直挂在热门选题清单上,是因为它覆盖了Java Web开发几乎所有核心环节&#xff1a…

📅 2026/10/10 9:40:04
Agent可观测性实战:用trace_id和结构化日志看清AI行为

Agent可观测性实战:用trace_id和结构化日志看清AI行为

调试过 AI Agent 的朋友,大概率经历过这种场景:代码跑通了,Agent 也执行完了,但你完全不知道它中间做了什么事。它调用了哪个工具?为什么走这条路而不是那条路?哪一步耗掉了 70% 的 token?失败发…

📅 2026/10/10 9:35:02
代码布局指南:主函数与功能函数的摆放艺术与工程实践

代码布局指南:主函数与功能函数的摆放艺术与工程实践

写代码这事,入门的时候最容易忽略的就是“布局”两个字。刚学会函数那阵子,我也觉得代码能跑就行,管什么先后顺序?直到有一次,代码过了几天自己都看不懂了,改一个功能找了半天,才明白那句“代码…

📅 2026/10/10 9:35:02
MORE NEWS

更多资讯

📰

Codeforces 946G Almost Increasing Array:删除位置与树状数组优化解析

1. 先搞清楚题目到底在问什么CodeForces 946G 这道 Almost Increasing Array,我第一次做的时候栽在了一个很容易忽略的地方:题目里的操作是“修改数组中元素的值”,而 Almost Increasing 的定义是“存在一个位置,删掉它之后剩余部…

📰

odbcji32.dll丢失修复指南:从SFC扫描到官方数据库驱动完整方案

如果你曾在一台刚迁移完系统、或者刚重装完的电脑上跑一个老业务软件,大概率见过这种弹窗:“由于找不到odbcji32.dll,无法继续执行代码。重新安装程序可能会解决此问题。”当时第一反应多半是上网搜“odbcji32.dll 免费下载”,从某…

📰

【一人公司】2026 独立开发新范式:从 v0 到 Cursor,用 TaoToken 统一 Key 打通全链路 AI 提效

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

📰

一天连开七个仓库对标 Adobe:本周 GitHub 上最猛的个人开发者是他

一天连开七个仓库对标 Adobe:本周 GitHub 上最猛的个人开发者是他 【免费下载链接】artcraft ArtCraft is an intentional crafting engine for artists, designers, and filmmakers 项目地址: https://gitcode.com/GitHub_Trending/ar/artcraft 2026 年 9 月…

📰

Zotero Better BibTeX 导入偏好配置指南:花括号大小写保护、AUX 扫描回填与句例化处理

科研 【免费下载链接】zotero-better-bibtex Make Zotero effective for us LaTeX holdouts 项目地址: https://gitcode.com/gh_mirrors/zo/zotero-better-bibtex 点击查看 免费下载 本篇技术指南围绕 Zotero Better BibTeX(BBT)插件「偏好设…

📰

WAMP环境下的网络考试系统设计与实现:从数据库到PHP的完整指南

简介:这是一篇基于WAMP(Windows、Apache、MySQL、PHP)环境开发网络考试系统的毕业论文,面向计算机相关专业毕业生及需要设计在线考试系统的开发者。论文覆盖从可行性分析、需求分析到系统设计、数据库设计、界面设计与测试的全流程…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬