做课程设计或者毕业设计,我见过太多人一上来就选“XX管理系统”,结果做着做着发现功能堆了一堆,代码却乱成一锅粥。如果你拿到的是“校友录管理系统”这个题目,并且已经确定用 Spring Boot,那这篇文章基本就是为你准备的。我按自己实际做项目的习惯,把这个系统的技术选型、数据库设计、核心功能实现、常见坑点全部过一遍,做成一篇直接能参考的实操笔记。
这套系统本身解决的问题很明确:把校友信息从纸质档案和Excel表里解放出来,做成一个可登录、可维护、可查询的Web应用。管理员可以管理班级和校友信息,普通用户(或者同样管理员)可以按班级、专业、入学年份去检索校友,也能维护工作单位、联系方式、近况动态这些字段。别小看这个题目,它几乎覆盖了 Spring Boot 入门阶段该学的所有东西:实体建模、ORM映射、分层开发、接口设计、异常处理、拦截器鉴权,认真做完一遍,你对 Java Web 开发的理解会扎实很多。
适合参考这套方案的读者有三类:一是正在做课程设计的学生,二是拿这个题目练手、想补全 Spring Boot 知识体系的开发者,三是需要快速交付一个可演示项目的同学。下面我按自己的实操顺序来讲,从设计思路一路说到部署,每个环节都会标注我踩过的坑。
1. 项目定位与设计思路拆解
1.1 校友录管理系统到底解决什么问题
很多同学拿到这种题目,第一反应是“把增删改查做出来就行”,这个想法没错,但容易做成低分项目。校友录管理系统的核心不是“能录入数据”,而是“让数据管理变得有意义”。我习惯先问三个问题:谁在用这个系统?他们有什么痛点?系统要做到什么程度才算好用?
在这个场景里,使用者通常是学校或院系的校友工作办公室,可能还包括班级联络人。他们的日常工作往往是维护历届毕业生名单、按班级统计校友去向、活动前发通知、整理校友捐赠信息。没有系统之前,这些数据分散在Excel、纸质登记表甚至微信群接龙里,整理起来非常痛苦。所以系统至少应该具备:统一的校友信息登记入口、分类检索能力、班级维度归组、导出或统计能力。
想清楚这一点之后,功能边界就不会跑偏。我这里把功能拆成几个模块:用户登录注册、班级管理(年级、专业、班级编号)、校友信息管理(基本信息、学历信息、工作单位、联系方式)、校友动态/留言、数据统计看板。如果你做的版本还想要加分项,可以加上管理员审核、图片上传头像、导出Excel。但是有一个原则:宁可核心功能做得精,不要所有功能都做得半吊子。
1.2 为什么选 Spring Boot 而不是其他框架
这是我在博客评论区和私信里被问得最多的问题。选 Spring Boot 的原因其实很实在:第一,生态成熟,网上参考资料极多,遇到问题基本能搜到答案;第二,内嵌 Tomcat,省去单独部署服务器的麻烦;第三,Spring 自带的 IOC/AOP 容器、Spring Data JPA 或者 MyBatis 的整合方式都很标准化;第四,这个方向在就业市场上有真实需求,学会 Sprng Boot 之后往微服务方向走也是一条顺路。
有人会问,那为什么不选 SSM(Spring + SpringMVC + MyBatis)?如果你的教学大纲或老师要求使用 SSM,那没问题。但我个人建议:除非有硬性要求,否则直接 Spring Boot。因为 Spring Boot 本质上是 Spring 生态的“自动配置”封装,你用 Spring Boot 写出的代码,底层仍然是 Spring MVC 那套机制,学 Spring Boot 并不耽误你理解 SSM。相反,SSM 里大量的 XML 配置会耗费很多时间,对课设来说性价比太低。
还有人会问:前端要不要前后端分离?我的建议是:课程设计级别不要强行上用 Vue + 分离式接口。原因很简单:前后端分离意味着你要同时维护两套工程、处理跨域、考虑 Token 认证,这些对初学者来说很容易卡住。用 Thymeleaf 或者直接把静态页面放到 src/main/resources/static 下,Spring Boot 默认托管,部署时打成一个 Jar 包就能跑,演示和答辩都方便。如果你是技术栈里已经会 Vue 的同学,也可以做成前后端分离,但这篇文章我按更稳妥的传统页面开发来讲。
1.3 源码、数据库、文档三个交付物意味着什么
拿到这个项目资源包的时候,你肯定会看到三个东西:源码、数据库(SQL脚本)、文档。这三个不是随便放进去的,它们正好对应了课程设计和毕设的三大评分维度:代码能力、数据库设计能力、文档表达能力。
源码部分要注意的是:你不应该只追求“能跑”,而是要让代码结构清晰、命名规范、注释适度。很多同学答辩时被问“这个功能怎么实现的”,答不上来,就是因为代码全是复制粘贴的,自己根本不熟。我后面会专门讲怎么逐层拆解源码。
数据库部分,通常是一份 .sql 文件,内含建库建表语句和基础测试数据。有这份脚本,意味着任何人在新环境里都能快速初始化数据库。我特别建议你把初始化数据写得丰富一点,比如每个班级放三五条校友记录,演示的时候随便搜一个关键词就有结果,视觉效果完全不一样。
文档部分一般包括需求说明、概要设计、数据库设计、核心代码说明、测试报告。写文档时不要大段抄代码,重点放在“为什么这么设计”和“核心流程怎么走”。比如你设计了一张 alumni 表,文档里要说清楚每个字段的业务含义、字段类型为什么这么选、哪个字段是外键、数据量大了之后怎么优化索引。这些内容才是阅卷老师真正想看的东西。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术选型与版本搭配的讲究
2.1 Spring Boot 版本选择:2.x还是3.x
关于版本,我这些年被问过无数遍,尤其是“springboot版本太高”这个问题。很多人从网上下载的项目模板是 2.x 的,结果自己建项目时 IDEA 默认给你选了 3.x,然后发现 JDK 版本不匹配、javax 包变成 jakarta、各种配置失效,心态直接炸。
我的建议很直接:如果 JDK 是 1.8,那就老老实实用 Spring Boot 2.7.x;如果机器上装的是 JDK 17,那可以考虑 Spring Boot 3.0+。Spring Boot 3.x 强制要求 JDK 17,同时把 javax.servlet 等包迁移到了 jakarta.* 命名空间,如果你用的是旧教程,很多代码直接报红。对于课程设计这个场景,我通常推荐 Spring Boot 2.7.18,这个版本是 2.x 系列的最终维护版本,稳定、教程多,和 JDK 1.8 完美配合。
这里我用表格整理一下,方便你对照自己的环境选择:
| 环境组合 | Spring Boot版本 | JDK | 说明 |
|---|---|---|---|
| 老电脑/实验室 | 2.7.18 | JDK 1.8 | 最稳,网上资料最多 |
| 新电脑/最新IDEA | 3.2.x | JDK 17+ | 可以用,但遇到问题搜答案时记得加版本号 |
| 练习微服务方向 | 2.7.18 或 3.x | 视JDK而定 | 建议先不纠结版本,把基础做通 |
这里的核心原则是:学校要求什么用什么,优先兼容环境。不要为了追新版本而选 3.x,也不要在 JDK 17 的机器上硬装着 JDK 1.8 去跑 2.x,编译和启动时容易出现一些诡异问题。
2.2 数据库与连接方式选型
数据库方面,校友录管理系统最合适的还是 MySQL 5.7 或 8.0。MySQL 免费、资料多、图形化工具多,老师也熟悉。如果你机器的磁盘或内存比较紧张,SQLite 也可以,但考虑到后期扩展和管理方便,我还是建议 MySQL。
连接方式上,推荐用 MyBatis 或者 MyBatis-Plus。个人经验是:MyBatis-Plus 更省事,它内置了通用 Mapper,单表增删改查几乎不用写 SQL。但有些学校的教学大纲里要求“使用 MyBatis 手写 SQL”,那你就用纯 MyBatis。如果要体现自己的水平,可以混合使用:简单单表操作用 MP,复杂多表查询用自定义 SQL。
JDBC 驱动依赖版本也要注意。如果你用 MySQL 8.0,驱动类名是 com.mysql.cj.jdbc.Driver,依赖坐标是 mysql:mysql-connector-java,推荐用 8.0.x 版本。如果用 5.x 的驱动连接 MySQL 8.0 的数据库,也能连,但可能遇到时区报错或者认证插件不兼容的问题。
2.3 开发工具与前端方案
开发工具就用 IntelliJ IDEA,社区版够用,如果你有教育免费授权就用 Ultimate。前端方案我分两种情况:
一是用 Thymeleaf 服务端渲染。Spring Boot 对 Thymeleaf 支持是原生的,页面里通过 ${} 获取后端数据,写起来有些像 JSP,但比 JSP 优雅。适合你不太想折腾前端工程的情况。
二是纯静态页面 + AJAX。把 HTML、CSS、JS 放进 static 目录,页面用 $.ajax 或 fetch 调用后端接口,后端只返回 JSON。这种方案代码上前后端分离,但又不用单独启动前端服务,部署依然是一个 Jar 包。我比较推荐这种方式,因为它能让你提前适应接口思维,后面学前后端分离会非常快。
我自己做的话,通常会先用 Bootstrap 或 Layui 做后台管理界面。Layui 的表格组件 table 自带分页、搜索、工具栏,做管理后台非常快,非常适合课程设计的演示效果。模板方面,网上很容易找到免费的后台管理模板,二开的时候注意保留版权说明就行。
3. 数据库设计与核心功能建模
3.1 数据表结构设计
数据库是整个系统的地基,表设计错了,后面写代码全是补丁。我一般先画一个简单的 ER 图(不用太规范,自己能看懂就行),然后落成表结构。校友录管理系统我建议至少要这几张表:
user(用户表)
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键,自增 |
| username | varchar(50) | 登录名,唯一 |
| password | varchar(100) | 登录密码,MD5/BCrypt加密 |
| real_name | varchar(50) | 真实姓名 |
| role | tinyint | 角色:1管理员 / 0普通用户 |
| create_time | datetime | 创建时间 |
class_info(班级表)
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| grade | varchar(20) | 年级,如2020级 |
| major | varchar(50) | 专业 |
| class_name | varchar(50) | 班级名称,如2020级计科1班 |
| head_teacher | varchar(50) | 班主任/辅导员 |
alumni(校友表)
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| class_id | bigint | 所属班级,外键关联 class_info |
| student_no | varchar(20) | 学号 |
| name | varchar(50) | 姓名 |
| gender | tinyint | 性别 |
| birthday | date | 出生日期 |
| phone | varchar(20) | 联系电话 |
| varchar(100) | 邮箱 | |
| company | varchar(100) | 工作单位 |
| job_title | varchar(100) | 职位 |
| address | varchar(200) | 现居住地 |
| photo | varchar(255) | 照片URL |
| remark | varchar(500) | 备注 |
| create_time | datetime | 录入时间 |
| update_time | datetime | 更新时间 |
activity(校友活动表,可选加分项)
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| title | varchar(100) | 活动标题 |
| content | text | 活动内容 |
| start_time | datetime | 开始时间 |
| end_time | datetime | 结束时间 |
| create_by | bigint | 创建人 |
还可以加一张 message(留言表)或者 alumni_education(教育经历表),具体看你的功能。
3.2 核心业务流程梳理
带过不少新人的经验告诉我,数据库表设计好之后,不要急着写代码,先梳理流程。校友录系统的核心流程有这几条:
- 管理员登录:输入账号密码 → 校验 → 成功后进入管理首页 → 操作班级和校友数据。
- 校友信息登记:填写表单 → 前端校验必填项 → 提交后端 → 后端再次校验 → 写入数据库 → 列表页面刷新。
- 按条件检索校友:选择年级/专业/班级 → 输入姓名或学号关键字 → 组合条件查询 → 返回列表。
- 编辑和删除:针对某条记录执行操作 → 权限验证(管理员才能删) → 更新数据库。
这些流程如果只在脑子里留着,代码就会写得东一块西一块。我建议你用文字把流程写下来,或者画一个简单的状态图,然后在每个流程的节点上标注“这里要做什么校验”“这里要操作哪张表”。后面写代码就是照图施工。
3.3 权限模型设计
校友录这种系统一般不需要特别复杂的权限模型,RBAC(基于角色的访问控制)最小实现就够了。
我把角色分成两种:管理员和普通用户。普通用户登录后可以查看校友信息、修改自己的资料;管理员除查看外,还能新增、修改、删除校友信息,管理班级和用户账号。
最简单的实现方式就是在 user 表里加一个 role 字段,然后在后端写一个拦截器(HandlerInterceptor)或者过滤器,拦截 /admin/** 之类的路径,判断当前登录用户的 role 是否为管理员,不是就跳转到无权限页面。如果你非要用 Spring Security,也不是不行,但课设阶段往往把简单问题复杂化,容易在配置文件里爬坑。用拦截器两小时就能搞定,核心代码也容易在答辩时讲清楚。
4. 从零搭建项目:实操全过程
4.1 项目初始化与依赖配置
先用 IDEA 创建 Spring Initializr 项目,注意勾选依赖:
- Spring Web
- Thymeleaf(如果用服务端渲染)
- MyBatis Framework 或 MyBatis-Plus
- MySQL Driver
- Lombok(可选,能省不少样板代码)
如果你创建的是 Maven 项目,pom.xml 大致长这样:
xml复制<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
注意:如果 MyBatis-Plus 版本和 Spring Boot 版本不匹配,可能会启动失败。3.5.x 配 2.x 基本没问题,配 3.x 也兼容,但为了保险,你先去看一下对应的 release notes。
4.2 配置文件与通用返回结构
application.yml 是我每次必先写好的文件。下面这个配置可以当模板用:
yaml复制server:
port: 8080
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/alumni_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false
username: root
password: 123456
thymeleaf:
cache: false
mybatis-plus:
mapper-locations: classpath:mapper/*.xml
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
这里有几个关键点。map-underscore-to-camel-case 设为 true 后,数据库的 class_id 能自动映射为 Java 属性的 classId,省去大量手写映射。serverTimezone 必须设置,否则 MySQL 8.0 连接时多半会报时区错误。useSSL=false 是避免本地环境 SSL 证书警告。
为了统一返回格式,我还习惯写一个通用的响应类:
java复制@Data
public class Result<T> {
private Integer code; // 200成功,500失败
private String message;
private T data;
public static <T> Result<T> success(T data) {
Result<T> r = new Result<>();
r.setCode(200);
r.setMessage("操作成功");
r.setData(data);
return r;
}
public static <T> Result<T> error(String msg) {
Result<T> r = new Result<>();
r.setCode(500);
r.setMessage(msg);
return r;
}
}
这个类的好处是:前端拿到接口结果后,先看 code,再取 data,不用每次面对乱七八糟的格式。写前端联调的时候非常省心。
4.3 登录鉴权与拦截器实现
登录功能很多人第一反应就是写个接口验证用户名密码,但其实有个重要环节容易漏掉:会话保持和拦截器。
我的做法是:用户登录成功后,把用户对象放进 session,同时设置一个 session 的过期时间。然后写一个拦截器,对需要登录的路径进行检查。
Controller 的登录方法核心逻辑像这样:
java复制@PostMapping("/login")
public Result<String> login(String username, String password, HttpSession session) {
User user = userService.login(username, password);
if (user != null) {
session.setAttribute("loginUser", user);
return Result.success("登录成功");
}
return Result.error("用户名或密码错误");
}
拦截器:
java复制public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession();
User user = (User) session.getAttribute("loginUser");
if (user != null) {
return true;
}
// 未登录,重定向到登录页
response.sendRedirect(request.getContextPath() + "/login.html");
return false;
}
}
注册拦截器:
java复制@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/login", "/register", "/css/**", "/js/**", "/img/**");
}
}
这里注意:静态资源路径一定要排除,否则登录页的 CSS/JS 全被拦截,页面直接白板。我见过太多次这种低级错误。
密码存储上,课程设计里很多人用 MD5,但我建议至少加盐,甚至直接用 BCrypt。MD5 加盐安全系数也不够高,但应付课设够了。如果你是给真实场景用,推荐 spring-security-crypto 里的 BCryptPasswordEncoder。
4.4 校友信息管理的增删改查实现
校友信息的增删改查是整个系统的核心,也是你演示时的重头戏。以 MyBatis-Plus 为例,实体类可以这样写:
java复制@Data
@TableName("alumni")
public class Alumni {
@TableId(type = IdType.AUTO)
private Long id;
private Long classId;
private String studentNo;
private String name;
private Integer gender;
private Date birthday;
private String phone;
private String email;
private String company;
private String jobTitle;
private String address;
private String photo;
private String remark;
private Date createTime;
private Date updateTime;
}
Mapper 接口:
java复制public interface AlumniMapper extends BaseMapper<Alumni> {
// 直接用 MyBatis-Plus 的通用方法即可
}
Service 层:
java复制@Service
public class AlumniService {
@Resource
private AlumniMapper alumniMapper;
public Page<Alumni> pageAlumni(int current, int size, String keyword) {
Page<Alumni> page = new Page<>(current, size);
LambdaQueryWrapper<Alumni> wrapper = new LambdaQueryWrapper<>();
if (StringUtils.hasText(keyword)) {
wrapper.like(Alumni::getName, keyword)
.or().like(Alumni::getStudentNo, keyword);
}
wrapper.orderByDesc(Alumni::getCreateTime);
return alumniMapper.selectPage(page, wrapper);
}
public boolean addAlumni(Alumni alumni) {
alumni.setCreateTime(new Date());
alumni.setUpdateTime(new Date());
return alumniMapper.insert(alumni) > 0;
}
public boolean updateAlumni(Alumni alumni) {
alumni.setUpdateTime(new Date());
return alumniMapper.updateById(alumni) > 0;
}
public boolean deleteAlumni(Long id) {
return alumniMapper.deleteById(id) > 0;
}
}
Controller:
java复制@RestController
@RequestMapping("/api/alumni")
public class AlumniController {
@Resource
private AlumniService alumniService;
@GetMapping("/page")
public Result<Page<Alumni>> page(@RequestParam(defaultValue = "1") int current,
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String keyword) {
return Result.success(alumniService.pageAlumni(current, size, keyword));
}
@PostMapping("/save")
public Result<String> save(@RequestBody Alumni alumni) {
boolean flag = alumni.getId() == null ? alumniService.addAlumni(alumni) : alumniService.updateAlumni(alumni);
return flag ? Result.success("保存成功") : Result.error("保存失败");
}
@DeleteMapping("/delete/{id}")
public Result<String> delete(@PathVariable Long id) {
return alumniService.deleteAlumni(id) ? Result.success("删除成功") : Result.error("删除失败");
}
}
思路就是“一个接口干一件事”,前后端通过 JSON 交互。前端页面里,列表用 Layui 表格渲染,表单弹窗收集数据,保存时调 /api/alumni/save,删除时调 /api/alumni/delete/{id}。
下面是我实际写前端页面时比较顺手的一个 AJAX 调用片段:
javascript复制$.ajax({
url: '/api/alumni/save',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(formData),
success: function (res) {
if (res.code === 200) {
layer.msg('保存成功');
table.reload('alumniTable');
} else {
layer.msg(res.message);
}
}
});
注意:发送请求时一定要加 contentType: 'application/json',并且把对象序列化成 JSON 字符串。如果你后端用 @RequestBody 接收,前端却用表单默认的 application/x-www-form-urlencoded,大概率会报“Required request body is missing”之类的错误。
4.5 班级管理与数据统计的简易实现
班级管理本质上跟校友信息管理一样,也是围绕 class_info 表做增删改查。这里我提醒一句:班级和校友是“一对多”关系。有班级才能录校友,所以前端页面里,录校友时“所属班级”这个下拉框的数据,应该从班级表实时查询。
数据统计可以做得很简单:首页放三张统计卡片,显示“校友总数”、“班级总数”、“活动总数”。SQL 就是 select count(*) from alumni 这种。想稍微高级一点,可以画个柱状图,按年级统计校友人数。前端可以用 ECharts,后端接口返回每个年级的人数列表,然后 ECharts 直接渲染。
下面这段是统计按年级人数的 Service 代码:
java复制public List<Map<String, Object>> countByGrade() {
return alumniMapper.selectMaps(
new QueryWrapper<Alumni>()
.select("class_id", "count(*) as total")
.groupBy("class_id")
);
}
实际展示时,可能要关联班级表拿到年级名称。我这里直接写一个自定义 SQL:
xml复制<select id="countByGrade" resultType="map">
SELECT ci.grade AS grade, COUNT(a.id) AS total
FROM alumni a
LEFT JOIN class_info ci ON a.class_id = ci.id
GROUP BY ci.grade
ORDER BY ci.grade
</select>
这种跨表统计场景,手写 SQL 比 MyBatis-Plus 的 Wrapper 更直观。课程设计里学会“什么时候用框架、什么时候手写 SQL”,也是加分点。
5. 常见问题排查与避坑实录
5.1 Spring Boot 版本过高导致的兼容问题
我在前面反复强调版本问题,因为这是很多初学者翻车的第一现场。最常见的报错是启动类直接失败,提示 java.lang.NoClassDefFoundError: javax/servlet/...,或者 Maven 依赖导入后各种爆红。
如果是 Spring Boot 3.x 配 JDK 17,而你项目里还在用 javax.servlet.http.HttpSession 这种旧包,那就得全部改成 jakarta.servlet.http.HttpSession。我把常见替换列在下面:
| 旧包 | 新包(Spring Boot 3.x) |
|---|---|
| javax.servlet.* | jakarta.servlet.* |
| javax.validation.* | jakarta.validation.* |
| javax.annotation.* | jakarta.annotation.* |
如果你的老师或者项目物料明确要求 JDK 1.8,请直接在创建项目时就选 2.7.18。不要想着先建 3.x 再降级,Maven 依赖关系会乱到你怀疑人生。
另外,Maven 私服下载依赖慢的,可以配置阿里云镜像。在 ~/.m2/settings.xml 里加:
xml复制<mirror>
<id>aliyunmaven</id>
<mirrorOf>central</mirrorOf>
<name>阿里云公共仓库</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
5.2 数据库连接失败类问题
数据库连不上是最高频的报错,没有之一。我把常见情况整理成一张速查表:
| 报错信息 | 原因 | 解决办法 |
|---|---|---|
Access denied for user 'root'@'localhost' |
用户名或密码错误 | 核对 yml 里的账号密码 |
The server time zone value ... is unrecognized |
时区未设置 | url 加 serverTimezone=Asia/Shanghai |
Public Key Retrieval is not allowed |
MySQL 8.0 认证方式问题 | url 加 allowPublicKeyRetrieval=true |
Could not create connection to database server |
驱动版本不匹配或数据库没开 | 换 8.0 驱动,确认 MySQL 服务已启动 |
Unknown database 'alumni_db' |
数据库不存在 | 先执行 SQL 脚本建库 |
尤其是时区问题,新装 MySQL 8.0 时非常常见。我一般会在建库脚本里直接设置数据库字符集:
sql复制CREATE DATABASE IF NOT EXISTS alumni_db
DEFAULT CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
utf8mb4 能存 emoji 和生僻字,比如学生姓名里有“䶮”这类字,用旧版 utf8 会报错或乱码。课程设计数据库一定用 utf8mb4,这是不会错的选择。
还有一点:如果你用 Navicat 或 DBeaver 建表,导出的 SQL 脚本里可能带有库名,比如 use alumni_db;。执行前看清楚,别在错误的库下建了表,导致后端查不到。
5.3 会话失效、前端联调与数据刷新问题
登录状态一会儿有,一会儿没有,这种情况通常有两个原因:一是浏览器禁用 Cookie,二是拦截器把静态资源拦截了。
排查方法是:在浏览器开发者工具里看发的请求的响应头,有没有 Set-Cookie。如果没设置成功,就检查前端的 AJAX 是否带上 xhrFields: { withCredentials: true }。不过同域部署一般不会遇到这个问题,因为 Session Cookie 会自动带。
前端页面另一个常见问题是:列表操作完之后,表格数据不刷新。解决办法很简单,在删除或保存成功的回调里调用表格的 reload 方法。但要注意,如果你的分页参数里有搜索关键词,reload 的时候要把当时的搜索条件一起传进去,否则表格会回到第一页且丢失筛选条件。
javascript复制table.reload('alumniTable', {
where: {
keyword: $('#keyword').val()
},
page: {
curr: 1
}
});
这里有个体验细节:点击删除后,为了防误删,建议先弹一个确认框。用 Layui 的 layer.confirm 也就几行代码,但演示效果会好很多。
6. 打包部署与项目扩展思路
6.1 项目打包与部署
到这一步,你的项目已经能在 IDEA 里顺利运行了。但课程设计通常需要交付一个“能独立运行”的成品。在理想情况下,我希望你在自己电脑上,通过命令行启动你的项目,而不是永远依赖 IDEA 的绿色小三角。
如果用的是 Maven,在项目根目录执行:
bash复制mvn clean package -DskipTests
打包完成后,target 目录下会生成一个可执行 Jar,名称类似 alumni-system-0.0.1-SNAPSHOT.jar。然后运行:
bash复制java -jar alumni-system-0.0.1-SNAPSHOT.jar
只要数据库是启动状态,且 application.yml 里的数据库密码正确,项目就能在 8080 端口跑起来。这种部署方式在答辩现场非常加分,因为老师会看到你是一个能“交付”的人,而不是只会点 IDE 按钮。
注意事项:打包前确认 pom.xml 里包含 spring-boot-maven-plugin,否则打出来的 Jar 可能“没有主清单属性”,运行直接报错。
如果你要把项目部署到服务器,可以使用 Docker,但对课程设计而言不是必须。如果一定要上 Docker,我建议先把数据库和 Spring Boot 分成两个容器来编排,数据库容器用命名卷保存数据,这样容器重建后数据还在。这里不做展开,项目背景毕竟以本地交付为主。
6.2 从课设走向生产的扩展建议
很多人项目交完就扔了,但我觉得既然花时间做了,不如顺手多往前走几步。以下几点是我觉得性价比很高的扩展方向:
- 增加导入导出功能:用 Apache POI 把校友列表导出成 Excel,也可以从 Excel 模板批量导入。这个功能在很多真实业务场景中非常实用,简历上写“导出报表功能”也比“实现了增删改查”有说服力得多。
- 引入 Spring Security 或 Sa-Token 做登录认证:学习阶段用拦截器够了,但如果你想拿这个项目去面试,建议至少了解 Sa-Token 或 JWT 的认证流程,能说出差异就是你比同龄人强的地方。
- 使用 Redis 缓存热点数据:比如首页统计数字、高频搜索的班级列表,可以放入 Redis 缓存,减轻数据库压力。课设阶段没有高性能压力,但你能主动用上缓存,也是一个合理的加分项。
- 补充单元测试:很多人不写测试,但课程的文档部分如果包含几个关键接口的测试用例,会显得专业不少。Spring Boot 里写一个
@SpringBootTest的测试类,调几个 Mapper 方法,并不难。
另外文档部分,我特别建议你在完成代码后,自己打开项目,对照着文档里的模块图,把每一个 Controller、Service、Mapper 的职责捋一遍。很多同学答辩翻车,就是因为代码是别人的,文档是复制改的,自己根本没有完整读过。你哪怕只是把项目里每一个方法的功能读一遍,写一份两页纸的“接口清单”,答辩时可讲的东西都会多出不少。
还有一个小技巧:演示的时候不要只演示“能跑”,要设计几个问题场景。比如“请找出 2019 级计算机专业所有在杭州工作的校友”,你可以提前在数据里备好几条数据,演示时输入查询条件,顺利展示筛选结果,老师对你的印象一下子就会不一样。这是我带过多个课程设计项目后,最想说的一点经验。
最后再分享一个我在实际开发里养成的习惯:勤备份数据库脚本。每次改动表结构,就导出一份新的 SQL 脚本,命名带上日期,比如 alumni_db_20240601.sql。项目做到后期,这个习惯会避免很多次“数据库崩了重头再来”的悲剧。开发过程中你会感谢当初多点了这几下导出按钮。
