news 2026/10/24 10:12:34

Java校园博客系统:Spring Boot 3.2教学实践方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java校园博客系统:Spring Boot 3.2教学实践方案

简介:本资源是一份面向计算机专业本科生与Java初学者的毕业设计类实践文档,聚焦校园博客系统的设计与实现全过程,解决高校师生知识分享、学习记录与互动交流的实际需求。文档以Word格式(.docx)完整呈现,共1个文件,大小4.91MB,内容涵盖技术选型(Java+Spring Boot+MySQL+Tomcat)、系统可行性分析、功能与业务流程梳理、模块化功能结构设计、数据库E-R图及数据表字段设计、前后台核心功能实现细节(含管理员、博主、前台首页)以及系统测试方案与结论,结构严谨、章节完整,具备直接参考与复用价值。目前已有62人学习下载,适合用于课程设计参考、毕设开题与技术方案借鉴,尤其利于理解Web应用从需求分析到部署落地的全周期开发逻辑。

1. 为什么一个“校园博客系统”值得用 Java 重做一遍?——不是为了炫技,而是解决真实教学场景里的三类断层

你见过学生交的博客作业:Word 文档里贴截图、手写 URL、用邮箱附件传.zip;老师批改靠 Excel 打分表,评论写在 Word 批注里,反馈周期动辄一周;课程群每天刷屏“老师我的博客打不开”“数据库连不上”“登录后跳回首页”,没人知道是前端路由错、Session 失效,还是 MySQL 8.0 默认认证插件不兼容。这不是开发能力问题,是教学系统与工程实践之间存在三道断层:需求断层(学生要交作业,不是建 SaaS)、部署断层(机房只装 JDK 17 + Tomcat 9,不配 Docker)、维护断层(毕设答辩完代码就进回收站,没人管半年后的 Spring Boot 版本升级)。这个“基于 Java 的校园博客系统”不是又一个 CRUD 演示项目,它是把 Java 工程能力压进教学闭环的最小可行载体:用Spring Boot 3.2做骨架,MyBatis-Plus 4.3自动生成建表 SQL 和基础 CRUD,Thymeleaf 3.1渲染纯 HTML 页面(避开 Vue/React 学习成本),所有功能跑在单台 Windows Server 上,学生 clone 后改两行配置就能本地启动,老师导出 WAR 包扔进机房 Tomcat 就能开课。它不追求高并发,但必须扛住 200 人同时提交作业的瞬时压力;它不玩微服务,但每个模块(用户、文章、评论、权限)都按真实项目分包;它甚至预留了“教师后台一键清空学生数据”的按钮——因为每学期初,真有老师需要重置整个班级的博客空间。如果你正带 Java 课程设计、指导毕业设计,或想给实训平台加个可交付的练手项目,这个系统就是那块“垫脚石”:不高,但刚好够到工程落地的门槛。


2. 从零搭起骨架:用 Spring Boot 3.2 + MyBatis-Plus 4.3 初始化项目并生成基础表结构

2.1 创建项目并确认 JDK 与依赖版本的硬性约束

校园环境对运行时版本极其敏感。机房服务器普遍为 Windows Server 2016,预装 JDK 17(LTS),而部分老教师电脑仍用 JDK 11。Spring Boot 3.x 要求最低 JDK 17,因此必须放弃 JDK 11 兼容性,这是第一个技术取舍。创建项目时,使用 start.spring.io (注意:选 Spring Boot 3.2.x,非 3.3+),勾选以下依赖:

  • Spring Web
  • Spring Data JDBC(非 JPA,因 MyBatis-Plus 更适配教学场景的 SQL 可视化)
  • Lombok(减少样板代码,学生易理解)
  • Thymeleaf(服务端渲染,避免前端框架学习负担)
  • Validation(校验学生注册邮箱、密码格式)

提示:不要勾选 Spring Security。校园系统需“教师账号拥有全站权限,学生仅能管理自己博客”,用自定义拦截器比 Security 配置更直观,也方便学生 debug 权限逻辑。

生成 ZIP 后解压,用 IDEA 打开。关键一步:检查pom.xml中 MyBatis-Plus 版本是否为4.3.0(2023 年底发布,支持 Spring Boot 3.2,且@TableName注解默认开启驼峰转下划线,省去手动配置)。若版本不符,强制覆盖:

<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-spring-boot3-starter</artifactId> <version>4.3.0</version> </dependency>

2.2 用 MyBatis-Plus 逆向工程生成实体类与建表 SQL:三步完成数据库初始化

学生最怕“建表语句写错导致整个项目崩”。MyBatis-Plus 的AutoGenerator可根据 Java 实体类反向生成建表 SQL,这才是教学友好型方案。我们先定义核心实体类(以BlogPost为例):

// src/main/java/com/example/campusblog/entity/BlogPost.java import com.baomidou.mybatisplus.annotation.*; @TableName("blog_post") // 显式指定表名,避免自动转 blog_post public class BlogPost { @TableId(type = IdType.AUTO) // 主键自增,MySQL 用 AUTO_INCREMENT private Long id; @TableField("user_id") private Long userId; // 关联学生 ID @TableField("title") private String title; @TableField("content") private String content; @TableField("create_time") private LocalDateTime createTime; // 使用 LocalDateTime,非 Date @TableField("status") // 0-草稿, 1-已发布, 2-已删除 private Integer status; // getter/setter 省略,Lombok @Data 已覆盖 }

关键点在于@TableField显式标注字段名,禁止依赖驼峰自动转换——因为学生常把createTime写成create_time或createtime,显式声明能杜绝歧义。

接下来,编写代码生成器(放在src/test/java下,仅用于初始化):

// src/test/java/com/example/campusblog/generator/CodeGenerator.java import com.baomidou.mybatisplus.generator.FastAutoGenerator; import com.baomidou.mybatisplus.generator.config.OutputFile; import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine; import java.util.Collections; public class CodeGenerator { public static void main(String[] args) { FastAutoGenerator.create("jdbc:mysql://localhost:3306/campus_blog?serverTimezone=Asia/Shanghai&useSSL=false", "root", "123456") .globalConfig(builder -> { builder.author("teacher") // 作者名,生成代码时写入注释 .outputDir("src/main/java"); // 生成到 src/main/java }) .packageConfig(builder -> { builder.parent("com.example.campusblog") // 包名前缀 .moduleName("entity") // 模块名,影响子包路径 .pathInfo(Collections.singletonMap(OutputFile.xml, "src/main/resources/mapper")); // XML 映射文件位置 }) .strategyConfig(builder -> { builder.addInclude("blog_user", "blog_post", "blog_comment") // 只生成这三张表 .entityBuilder() .enableLombok() // 启用 Lombok .naming(com.baomidou.mybatisplus.generator.config.NamingStrategy.underline_to_camel) // 数据库下划线转驼峰 .controllerBuilder() .enableRestStyle(); // 生成 RESTful 风格 Controller }) .templateEngine(new FreemarkerTemplateEngine()) // 使用 Freemarker 模板 .execute(); } }

执行main()方法后,自动生成:

  • BlogUser.java,BlogPost.java,BlogComment.java实体类
  • BlogUserMapper.java,BlogPostMapper.java接口(含insert,selectById等方法)
  • BlogUserMapper.xml,BlogPostMapper.xml(含<insert><select>标签)
  • BlogUserController.java,BlogPostController.java(带@RestController的空壳)

参数说明:serverTimezone=Asia/Shanghai是必须项,否则LocalDateTime字段插入 MySQL 会报时区错误;useSSL=false因校园内网 MySQL 通常未配 SSL,强行开启会导致连接失败;addInclude明确限定表名,避免生成无关表(如flyway_schema_history)。

2.3 运行生成器后,获取建表 SQL 并手动执行:为什么不能直接schema.sql?

MyBatis-Plus 不提供“一键建库建表”功能,但生成器输出的 XML 文件中,<insert>标签内的 SQL 是可读的。更可靠的做法是:用 HikariCP 连接池 +spring.sql.init.mode=always加载初始化脚本。在src/main/resources/application.yml中添加:

spring: sql: init: mode: always schema-locations: classpath:schema.sql >-- src/main/resources/schema.sql CREATE TABLE IF NOT EXISTS blog_user ( id BIGINT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, email VARCHAR(100) NOT NULL, role TINYINT DEFAULT 0 COMMENT '0-学生, 1-教师', create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS blog_post ( id BIGINT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT NOT NULL, title VARCHAR(200) NOT NULL, content TEXT NOT NULL, status TINYINT DEFAULT 1 COMMENT '0-草稿,1-已发布,2-已删除', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_user_id (user_id) ); CREATE TABLE IF NOT EXISTS blog_comment ( id BIGINT AUTO_INCREMENT PRIMARY KEY, post_id BIGINT NOT NULL, user_id BIGINT NOT NULL, content VARCHAR(500) NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_post_id (post_id) );

为什么不用@MapperScan自动建表?
因为@MapperScan只扫描 Mapper 接口,不执行 DDL。而schema.sql在应用启动时由 Spring JDBC 自动执行,且IF NOT EXISTS保证重复启动不报错——这对学生反复重启项目至关重要。


3. 让学生真正“交作业”:实现博客发布流程与教师后台审核链路

3.1 学生端:从登录到发布,三步完成一篇博客(含富文本与图片上传)

校园博客不需复杂编辑器。用summernote(轻量级 jQuery 富文本)即可,它支持图片拖拽上传,且 JS 文件仅 300KB。在src/main/resources/templates/user/dashboard.html中引入:

<!-- src/main/resources/templates/user/dashboard.html --> <link href="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote.min.css" rel="stylesheet"> <script src="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote.min.js"></script> <div class="form-group"> <label>博客内容</label> <textarea id="content-editor" name="content" class="form-control"></textarea> </div> <script> $(document).ready(function() { $('#content-editor').summernote({ height: 300, toolbar: [ ['style', ['bold', 'italic', 'underline']], ['insert', ['picture', 'link']] ], callbacks: { onImageUpload: function(files) { sendFile(files[0]); // 调用自定义上传函数 } } }); }); function sendFile(file) { var formData = new FormData(); formData.append("file", file); $.ajax({ url: "/api/upload/image", type: "POST", data: formData, processData: false, contentType: false, success: function(url) { $('#content-editor').summernote('insertImage', url); // 插入图片 URL } }); } </script>

后端接收图片的 Controller 必须处理MultipartFile,并保存到src/main/resources/static/upload/目录(静态资源目录,图片可直接通过 HTTP 访问):

// src/main/java/com/example/campusblog/controller/UploadController.java import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import org.springframework.core.io.Resource; import org.springframework.core.io.UrlResource; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; @RestController @RequestMapping("/api/upload") public class UploadController { private final Path rootLocation = Paths.get("src/main/resources/static/upload"); @PostMapping("/image") public String handleFileUpload(@RequestParam("file") MultipartFile file) { try { if (file.isEmpty()) { return ""; } // 生成唯一文件名:时间戳 + 原文件名 String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); String filename = timestamp + "_" + file.getOriginalFilename(); Path destinationFile = this.rootLocation.resolve( Paths.get(filename)) .normalize().toAbsolutePath(); // 确保目录存在 if (!this.rootLocation.toFile().exists()) { Files.createDirectories(this.rootLocation); } Files.copy(file.getInputStream(), destinationFile); // 返回可访问的 URL(注意:static 目录下文件路径为 /upload/xxx.jpg) return "/upload/" + filename; } catch (Exception e) { e.printStackTrace(); return ""; } } }

关键细节:return "/upload/" + filename是硬编码路径,因为src/main/resources/static/是 Spring Boot 默认静态资源目录,无需额外配置即可通过http://localhost:8080/upload/xxx.jpg访问。学生写博客时插入的图片 URL 就是这个地址,避免使用file://协议(浏览器安全限制)或绝对路径(部署后失效)。

3.2 教师端:审核列表与一键操作,用 Thymeleaf 渲染动态表格

教师后台首页/admin/posts展示待审核博客列表。MyBatis-Plus 分页查询 + Thymeleaf 表格渲染,代码简洁且易调试:

// src/main/java/com/example/campusblog/controller/AdminController.java @GetMapping("/admin/posts") public String listPendingPosts(Model model) { Page<BlogPost> page = new Page<>(1, 10); // 第 1 页,每页 10 条 QueryWrapper<BlogPost> wrapper = new QueryWrapper<>(); wrapper.eq("status", 0); // 只查草稿状态 wrapper.orderByDesc("create_time"); Page<BlogPost> resultPage = blogPostService.page(page, wrapper); model.addAttribute("posts", resultPage.getRecords()); model.addAttribute("page", resultPage); return "admin/post-list"; // 对应 templates/admin/post-list.html }

post-list.html中用 Thymeleaf 渲染表格:

<!-- src/main/resources/templates/admin/post-list.html --> <table class="table table-striped"> <thead> <tr> <th>标题</th> <th>学生</th> <th>提交时间</th> <th>操作</th> </tr> </thead> <tbody> <tr th:each="post : ${posts}"> <td th:text="${post.title}">示例标题</td> <td th:text="${post.user.username}">张三</td> <td th:text="${#dates.format(post.createTime, 'yyyy-MM-dd HH:mm')}">2024-05-01 10:20</td> <td> <a th:href="@{/admin/post/{id}/publish(id=${post.id})}" class="btn btn-success btn-sm">发布</a> <a th:href="@{/admin/post/{id}/reject(id=${post.id})}" class="btn btn-danger btn-sm">退回</a> </td> </tr> </tbody> </table>

发布操作只需更新status字段:

// src/main/java/com/example/campusblog/controller/AdminController.java @PostMapping("/admin/post/{id}/publish") public String publishPost(@PathVariable Long id) { BlogPost post = blogPostService.getById(id); if (post != null && post.getStatus() == 0) { post.setStatus(1); // 1-已发布 blogPostService.updateById(post); } return "redirect:/admin/posts"; }

为什么不用 REST API + AJAX?
因为教师可能用老旧 IE 浏览器(机房常见),AJAX 兼容性差;而redirect是最稳妥的页面跳转方式,且 Thymeleaf 模板天然支持服务端渲染,学生 debug 时直接看 HTML 源码就能理解数据流向。

3.3 权限控制:用拦截器实现“学生只能看自己的博客,教师看全部”

不引入 Spring Security,改用HandlerInterceptor实现细粒度控制。创建AuthInterceptor:

// src/main/java/com/example/campusblog/interceptor/AuthInterceptor.java import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session = request.getSession(false); if (session == null || session.getAttribute("user") == null) { response.sendRedirect(request.getContextPath() + "/login"); return false; } String uri = request.getRequestURI(); BlogUser user = (BlogUser) session.getAttribute("user"); // 学生不能访问 /admin/ 开头的路径 if (uri.startsWith("/admin/") && user.getRole() == 0) { response.sendError(HttpServletResponse.SC_FORBIDDEN, "无权限访问管理员页面"); return false; } // 学生访问 /post/{id} 时,校验该博客是否属于本人 if (uri.matches("/post/\\d+") && user.getRole() == 0) { Long postId = Long.parseLong(uri.substring(uri.lastIndexOf("/") + 1)); BlogPost post = blogPostService.getById(postId); if (post == null || !post.getUserId().equals(user.getId())) { response.sendError(HttpServletResponse.SC_FORBIDDEN, "无权查看他人博客"); return false; } } return true; } }

在WebMvcConfigurer中注册:

// src/main/java/com/example/campusblog/config/WebConfig.java import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new AuthInterceptor()) .excludePathPatterns("/login", "/register", "/css/**", "/js/**", "/images/**"); } }

拦截器 vs Filter:Filter 在 Servlet 容器层面,无法直接获取 Spring Bean(如blogPostService);而HandlerInterceptor在 Spring MVC 拦截链中,可注入 Service,适合业务逻辑判断。这是教学场景下的合理降级——用更易懂的机制替代复杂框架。


4. 避坑指南:学生部署时最常遇到的 4 个致命问题与血泪解决方案

4.1 现象:本地启动成功,但部署到机房 Tomcat 后页面空白,控制台无报错

原因:Spring Boot 3.x 默认打包为jar,而机房 Tomcat 要求war包;且jar包内嵌 Tomcat 与外部 Tomcat 冲突。
解决:

  1. 修改pom.xml,将打包方式改为war:
    <packaging>war</packaging>
  2. 排除内嵌 Tomcat:
    <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <scope>provided</scope> <!-- 交给外部 Tomcat 提供 --> </dependency>
  3. 主类继承SpringBootServletInitializer:
    @SpringBootApplication public class CampusBlogApplication extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { return application.sources(CampusBlogApplication.class); } public static void main(String[] args) { SpringApplication.run(CampusBlogApplication.class, args); } }
  4. 用mvn clean package -DskipTests生成target/campus-blog-0.0.1-SNAPSHOT.war,丢进Tomcat/webapps/即可。

4.2 现象:学生注册时提示“邮箱格式错误”,但输入student@school.edu.cn仍失败

原因:@Email注解默认正则过于严格,不支持中文域名或长后缀(如.edu.cn)。
解决:自定义校验注解,或改用@Pattern:

// 在 BlogUser 实体类中 @Pattern(regexp = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", message = "邮箱格式不正确,请输入如 student@school.edu.cn") private String email;

玄学经验:正则末尾{2,}允许任意长度顶级域名,比@Email的硬编码列表更包容校园邮箱。

4.3 现象:教师后台点击“发布”后,博客状态没变,刷新页面仍是草稿

原因:blogPostService.updateById(post)更新时,MyBatis-Plus 默认只更新非null字段;而post对象中userId,title等字段为null(因只设置了id和status),导致 SQL 中SET status=?被忽略。
解决:两种方案任选其一:

  • 方案 A(推荐):用LambdaUpdateWrapper显式指定更新字段:
    LambdaUpdateWrapper<BlogPost> wrapper = new LambdaUpdateWrapper<>(); wrapper.eq(BlogPost::getId, id).set(BlogPost::getStatus, 1); blogPostService.update(wrapper);
  • 方案 B:在BlogPost实体类中,为所有字段添加@TableField(fill = FieldFill.UPDATE),并在MetaObjectHandler中填充,但教学场景过于复杂,不推荐。

4.4 现象:图片上传后显示 404,URL 是/upload/20240501102030_xxx.jpg,但浏览器打不开

原因:src/main/resources/static/upload/目录在打包成war后,路径变为WEB-INF/classes/static/upload/,而 Tomcat 默认不将WEB-INF下的资源设为静态可访问。
解决:在application.yml中显式配置静态资源路径:

spring: web: resources: static-locations: classpath:/static/,file:./upload/

然后修改上传代码,将图片保存到项目根目录的upload/文件夹(而非src/main/resources/static/upload/):

private final Path rootLocation = Paths.get("./upload"); // 改为相对路径

这样./upload/位于 Tomcat 的webapps/ROOT/同级,天然可被 HTTP 访问。


5. 让系统“活”过一个学期:教师后台的批量操作与数据快照技巧

5.1 教师一键清空全班博客:用 MyBatis-Plus 的remove方法安全擦除

每学期初,教师需要重置学生博客空间。与其手动删库,不如在后台加个按钮。在AdminController中添加:

@PostMapping("/admin/clear-all-posts") public String clearAllPosts() { // 删除所有博客(软删除:status=2) UpdateWrapper<BlogPost> wrapper = new UpdateWrapper<>(); wrapper.set("status", 2) // 标记为已删除 .ne("status", 2); // 避免重复操作 blogPostService.update(wrapper); // 清空评论 UpdateWrapper<BlogComment> commentWrapper = new UpdateWrapper<>(); commentWrapper.set("status", 2) .ne("status", 2); blogCommentService.update(commentWrapper); return "redirect:/admin/dashboard"; }

对应前端按钮:

<!-- 在 admin/dashboard.html 中 --> <form action="/admin/clear-all-posts" method="post"> <button type="submit" class="btn btn-danger" onclick="return confirm('确定清空所有博客和评论?此操作不可撤销!')"> 清空全班博客 </button> </form>

为什么用软删除(status=2)而非物理删除?
因为教师可能误点,物理删除后无法恢复;而软删除只是标记状态,SQL 查询时加WHERE status != 2即可过滤,且保留数据用于期末统计(如“本学期共提交博客 1200 篇”)。

5.2 生成学期数据快照:用SELECT ... INTO OUTFILE导出学生博客为 CSV

期末归档时,教师需要导出所有已发布博客为 Excel。MySQL 原生命令SELECT ... INTO OUTFILE可直接生成 CSV,比 Java 导出更高效:

-- 在 MySQL 客户端执行(需 FILE 权限) SELECT u.username AS '学生姓名', p.title AS '博客标题', p.content AS '博客内容', p.create_time AS '发布时间' INTO OUTFILE '/var/lib/mysql-files/campus_blog_export.csv' FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' LINES TERMINATED BY '\n' FROM blog_post p JOIN blog_user u ON p.user_id = u.id WHERE p.status = 1;

注意:/var/lib/mysql-files/是 MySQLsecure_file_priv参数指定的安全目录,不能随意改路径;导出后,教师用scp或远程桌面复制该文件到本地,用 Excel 打开即为标准 CSV。

5.3 防止学生“刷分”:用 Redis 记录每日发布上限

有学生为拿高分,一天发 20 篇博客。在BlogPostController的发布接口中加入限流:

// src/main/java/com/example/campusblog/controller/BlogPostController.java @Autowired private RedisTemplate<String, Object> redisTemplate; @PostMapping("/post/publish") public String publishPost(@RequestBody BlogPost post, HttpSession session) { BlogUser user = (BlogUser) session.getAttribute("user"); // 限流:每天最多 3 篇 String key = "post_limit:" + user.getId() + ":" + LocalDate.now(); Long count = redisTemplate.opsForValue().increment(key, 1); if (count > 3) { model.addAttribute("error", "今日发布次数已达上限(3 篇)"); return "user/dashboard"; } redisTemplate.expire(key, 1, TimeUnit.DAYS); // 设置 1 天过期 // 正常发布逻辑... post.setUserId(user.getId()); post.setStatus(0); // 先存草稿 blogPostService.save(post); return "redirect:/user/dashboard"; }

Redis 部署建议:校园机房可安装 Windows 版 Redis(微软维护的redis-windows),内存占用仅 10MB,比数据库计数更轻量。若无 Redis,退化为数据库计数(SELECT COUNT(*) FROM blog_post WHERE user_id=? AND DATE(create_time)=CURDATE()),但性能稍差。

5.4 我的习惯:每次课程更新前,用 Git Tag 锁定教学版本

我带的 Java 课程每年迭代,但不同届学生用的系统版本必须一致。我的做法是:

  • 每学期初,在 GitHub 仓库打 Tag,如v2024-spring;
  • 给学生文档明确写:“请 clonegit clone -b v2024-spring https://github.com/yourname/campus-blog.git”;
  • 所有application.yml中的数据库密码、Redis 地址等敏感配置,用application-dev.yml单独存放,并加入.gitignore;
  • 最后,把v2024-springTag 对应的 ZIP 包上传到学校教学平台,供学生直接下载。

这看似多一步,却避免了“学生用最新版代码,结果 Spring Boot 升级到 3.3,JDK 17 不兼容”的翻车现场。工程能力的第一课,不是写多炫的算法,而是让代码在别人机器上稳稳跑起来。

希望帮到你。

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

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

AI技术博客翻译工作流:从选题到协同交付的完整实践

我理解您的要求&#xff0c;但需要说明&#xff1a;您提供的输入内容中&#xff0c;项目标题为“TowardsArtificialIntelligence 博客中文翻译&#xff08;二百二十六&#xff09;”&#xff0c;其余字段&#xff08;项目正文、关键词、摘要描述&#xff09;全部为空&#xff0…

作者头像 李华
网站建设 2026/10/17 17:40:31

Win2003集成阵列卡驱动:nLite与手工方法解决安装找不到硬盘

简介&#xff1a;这份资源面向需要在无软驱环境下为 Windows 2003 Server 安装 RAID 卡驱动的运维人员与装机爱好者&#xff0c;解决原版安装盘无法识别阵列硬盘、导致安装中断或蓝屏的问题。资源以光盘集成思路为核心&#xff0c;围绕 RAID 控制器驱动的手工整合展开&#xff…

作者头像 李华
网站建设 2026/10/24 10:12:19

WebSocket五子棋对战游戏开发:协议、心跳与重连实战

简介&#xff1a;基于WebSocket的在线五子棋对战游戏设计源码&#xff0c;完整实现了用户注册、登录、对战匹配、实时对战与实时聊天五大功能模块&#xff0c;适合正在学习C、CSS、HTML和JavaScript的开发者用于个人实践与项目复盘。资源共33个文件&#xff0c;包体仅6.13MB&am…

作者头像 李华
网站建设 2026/10/22 1:02:52

AI智能体开发平台落地实战:选型、架构与避坑指南

简介&#xff1a;一份聚焦大模型应用与AI智能体开发平台的PPT&#xff0c;适合AI开发者、产品经理及高校师生入门智能体开发。内容以女娲智能体平台为主线&#xff0c;系统讲解插件、工作流、触发器如何拓展智能体能力&#xff0c;文档/表格/照片三类知识库的应用场景&#xff…

作者头像 李华
网站建设 2026/10/23 12:41:29

Ubuntu远程桌面下Firefox打不开闪退?完整排查指南

如果你用VNC或xrdp远程登录Ubuntu&#xff0c;点了Firefox图标后界面闪一下就没了&#xff0c;或者进程起来一会儿又自己退出&#xff0c;别急着卸载重装。这类问题在远程桌面场景下非常典型&#xff0c;根因通常不在Firefox本身&#xff0c;而在会话环境。我前前后后接手过不下…

作者头像 李华
网站建设 2026/10/17 16:49:26

VS Code中Claude Code命令行界面详解:安装配置与实战排查

1. 先别慌&#xff0c;看到命令行是正常现象 很多朋友在 VS Code 里安装 Claude Code 之后&#xff0c;第一次点击打开&#xff0c;发现弹出的不是想象中的聊天窗口、侧边栏对话面板&#xff0c;而是一个黑底白字的终端界面&#xff0c;心里多少会咯噔一下&#xff1a;“是不是…

作者头像 李华