基于SpringBoot前后端分离的校园失物招领系统实战:MySQL脚本内置与接口开发
简介:这份资源是面向高校计算机相关专业学生与Java初学者的一套校园失物招领系统完整源码,采用前后端分离架构,可作为毕业设计、课程设计或SpringBoot实战练手项目。后端基于Spring Boot 2.1.3.RELEASE与Spring Data JPA搭建,运行于JDK 1.8环境,数据库使用MySQL 5.7,脚本文件已随后端程序一并提供,开发工具推荐IntelliJ IDEA。压缩包共220个文件,约2.11MB,其中103个java文件承载核心业务逻辑,37个js与19个html、12个css构成前端交互与页面样式,另有properties、xml等配置文件及png、jpg等图片素材,结构完整、层次清晰。目前已有1651人学习下载,读者可从中获取一套可直接运行的失物招领业务方案,涵盖信息发布、查询与后台管理等模块,便于理解前后端分离的接口设计与JPA数据持久化思路,也能作为二次开发与功能扩展的参考基础。
1. 校园失物招领系统为什么值得用 SpringBoot 前后端分离重写一遍
在高校信息化项目里,校园失物招领系统是个典型的“看起来简单、做起来全是细节”的题目。很多同学第一次做课程设计或毕设,直接拿 JSP 或者 Thymeleaf 把页面和逻辑揉在一起,结果一改需求就牵一发动全身,前端想换个样式都得动 Java 代码。而基于 SpringBoot 的前后端分离方案,恰好能把这件事拆干净:后端只负责数据与接口,前端只管渲染与交互,MySQL 脚本直接放在后端程序里随项目启动初始化。这套结构对新手友好,对熟手也留足了扩展空间——你可以把它当成一个标准的 RESTful 接口练习场,也可以在此基础上加图片上传、消息通知、认领审核流。适合谁?正在找毕设题目的学生、想练手 SpringBoot 整合 MyBatis 的初级开发者、以及需要快速搭一个校内工具站点的运维同学。下面我就按实际落地的顺序,把选型理由、建表、接口、联调和踩坑一条条讲清楚。
2. 前后端分离的骨架怎么搭:从 SpringBoot 初始化到 MySQL 脚本内置
2.1 为什么选 SpringBoot 而不是传统 SSM
传统 SSM 要配 web.xml、applicationContext.xml、spring-mvc.xml,光 XML 就能写满三屏,而且 Tomcat 要单独装。SpringBoot 把内嵌 Tomcat 和自动配置做掉了,一个 main 方法就能跑起来。对于校园失物招领这种中小型系统,SpringBoot 的起步依赖能让你少写 70% 的配置文件。更关键的是,前后端分离后,后端只需要暴露 JSON 接口,前端用 Vue 或 React 单独跑在 8080/5173 端口,通过 axios 调后端 8081 的接口。这样前端同学不用装 JDK,后端同学不用管 CSS,协作效率直接翻倍。
我一般会选 SpringBoot 2.7.x 或 3.x 的稳定版。注意热词里提到的“springboot版本太高”是个真实痛点:SpringBoot 3.x 要求 JDK 17,而且把 javax.servlet 换成了 jakarta.servlet,很多老教程里的代码直接报错。如果你学校机房还在用 JDK 8,就老老实实选 2.7.x,别追新。
2.2 用 Spring Initializr 生成项目骨架
最省事的起步方式是走 start.spring.io,选 Maven、Java 8 或 17、SpringBoot 2.7.18,依赖勾 Spring Web、MyBatis Framework、MySQL Driver、Lombok。生成后解压,用 IDEA 打开。如果你习惯命令行,也可以用 curl 拉取:
curl https://start.spring.io/starter.zip \ -d type=maven-project \ -d language=java \ -d bootVersion=2.7.18 \ -d javaVersion=8 \ -d groupId=com.campus \ -d artifactId=lostfound \ -d name=lostfound \ -d dependencies=web,mybatis,mysql,lombok \ -o lostfound.zip这段命令的含义:type=maven-project指定 Maven 构建;bootVersion=2.7.18锁定版本避免自动选到 3.x;dependencies里 web 提供 REST 能力,mybatis 做 ORM,mysql 是驱动,lombok 省 getter/setter。下载完解压,目录结构里src/main/resources/application.properties就是你要改的配置文件。
2.3 把 MySQL 脚本放进后端程序的 resources 目录
标题里明确写了“mysql脚本在后端程序中”,这意味着建表语句和初始数据不是让你手动去 Navicat 里跑,而是随项目启动自动执行。SpringBoot 支持schema.sql和data.sql两个约定文件。你只需要在src/main/resources下建这两个文件,然后在配置里开启初始化。
先看schema.sql的核心表结构:
-- 用户表:区分学生和管理员 CREATE TABLE IF NOT EXISTS `user` ( `id` INT NOT NULL AUTO_INCREMENT, `username` VARCHAR(50) NOT NULL UNIQUE, `password` VARCHAR(100) NOT NULL, `role` TINYINT DEFAULT 0 COMMENT '0学生 1管理员', `phone` VARCHAR(20), PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 失物招领信息表 CREATE TABLE IF NOT EXISTS `item` ( `id` INT NOT NULL AUTO_INCREMENT, `title` VARCHAR(100) NOT NULL, `description` TEXT, `type` TINYINT NOT NULL COMMENT '0失物 1招领', `status` TINYINT DEFAULT 0 COMMENT '0待认领 1已认领', `user_id` INT NOT NULL, `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_user` (`user_id`), KEY `idx_status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;参数说明:utf8mb4是为了支持 emoji 和生僻字,校园场景里学生输入的表情符号不会乱码;idx_status索引让“只看待认领”的查询走索引而不是全表扫;create_time默认当前时间,省去代码里手动 set。
然后在application.properties里加:
spring.datasource.url=jdbc:mysql://localhost:3306/lostfound?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai spring.datasource.username=root spring.datasource.password=你的密码 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.sql.init.mode=always spring.sql.init.schema-locations=classpath:schema.sql spring.sql.init.data-locations=classpath:data.sql注意spring.sql.init.mode=always表示每次启动都执行。生产环境要改成never,否则重复插入会报主键冲突。data.sql里放管理员账号和几条测试数据即可。
提示:MySQL 8.0 的驱动类名是
com.mysql.cj.jdbc.Driver,5.7 用com.mysql.jdbc.Driver。如果你从 5.7 升到 8.0 没改这个,启动直接报Loading class 'com.mysql.jdbc.Driver'警告甚至连接失败。
3. 接口层怎么写:失物招领的增删改查与分页
3.1 用 MyBatis 注解还是 XML
校园失物招领的查询条件不复杂,无非是按类型、状态、关键字过滤。我一般用 MyBatis 注解做简单 CRUD,用 XML 做动态 SQL 分页。这样既不用维护大量 XML,又能在列表页灵活拼条件。实体类用 Lombok 的@Data省掉 getter/setter,Mapper 接口直接写@Select、@Insert。
@Mapper public interface ItemMapper { @Insert("INSERT INTO item(title,description,type,user_id) VALUES(#{title},#{description},#{type},#{userId})") int insert(Item item); @Select("SELECT * FROM item WHERE status = #{status} ORDER BY create_time DESC LIMIT #{offset},#{size}") List<Item> pageByStatus(@Param("status") int status, @Param("offset") int offset, @Param("size") int size); }逻辑说明:#{offset}和#{size}对应 MySQL 的LIMIT分页,前端传页码和每页条数,后端换算成 offset。参数说明:status为 0 查待认领,为 1 查已认领;create_time DESC保证最新发布的排前面,符合失物招领的时效性。
3.2 Controller 层统一返回格式
前后端分离最怕接口返回格式不统一,前端一会儿拿数组一会儿拿对象。我习惯定义一个Result<T>包装类,包含code、msg、data三个字段。Controller 里所有方法都返回Result.success(data)或Result.error("原因")。
@RestController @RequestMapping("/api/item") public class ItemController { @Autowired private ItemMapper itemMapper; @GetMapping("/list") public Result<List<Item>> list(@RequestParam(defaultValue = "0") int status, @RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "10") int size) { int offset = (page - 1) * size; List<Item> list = itemMapper.pageByStatus(status, offset, size); return Result.success(list); } }参数说明:page从 1 开始,size默认 10 条,offset计算公式是(page-1)*size。这里有个容易翻车的点:如果前端传page=0,offset 会变成负数,MySQL 直接报错。所以我在方法开头加个判断,page = Math.max(page, 1)。
3.3 跨域配置别再用 @CrossOrigin 一个个加
前后端分离开发时,前端跑在 5173 端口,后端 8081,浏览器同源策略会拦请求。很多教程让你在每个 Controller 上加@CrossOrigin,但项目一大就漏。正确做法是写一个全局配置类:
@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE") .allowCredentials(true) .maxAge(3600); } }注意allowedOriginPatterns是 SpringBoot 2.4 之后的新写法,老版本用allowedOrigins。如果你用了allowCredentials(true),就不能把 origins 写成"*",否则启动报错。这是热词里“springboot配置”高频踩的坑。
4. 避坑与排查:校园失物招领系统落地时最容易翻车的 5 个地方
4.1 现象:启动报错 “Table 'lostfound.user' doesn't exist”
原因:spring.sql.init.mode没开,或者schema.sql放错了目录。SpringBoot 默认只认classpath:schema.sql,你放到src/main/resources/sql/下就不生效。 解决:确认文件在resources根目录,且配置里写了spring.sql.init.schema-locations=classpath:schema.sql。如果用了 MyBatis 的@MapperScan,不影响初始化,两者不冲突。
4.2 现象:中文存入数据库变成问号
原因:连接 URL 没加characterEncoding=utf8,或者数据库建表时用了latin1。 解决:URL 里补上useUnicode=true&characterEncoding=utf8,建表语句统一DEFAULT CHARSET=utf8mb4。已经建好的表用ALTER TABLE item CONVERT TO CHARACTER SET utf8mb4;补救。
4.3 现象:前端拿到的 JSON 里日期是时间戳数组
原因:Jackson 默认把Date序列化成[2024,5,20,10,30,0]这种格式,前端没法直接显示。 解决:在application.properties加spring.jackson.date-format=yyyy-MM-dd HH:mm:ss和spring.jackson.time-zone=GMT+8。或者直接在实体类字段上加@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")。
4.4 现象:分页查询第二页数据重复或丢失
原因:ORDER BY create_time DESC时,如果两条记录时间完全相同,MySQL 排序不稳定,分页边界会乱。 解决:排序条件加一个唯一字段兜底,改成ORDER BY create_time DESC, id DESC。这是热词里“mysql排序”相关的经典坑。
4.5 现象:图片上传后刷新页面图片没了
原因:你把图片存到了项目临时目录,重新部署后目录被清空。 解决:配置spring.servlet.multipart.location指向一个固定磁盘路径,比如D:/campus/upload/,并在数据库里只存相对路径。前端通过后端提供的静态资源映射访问。
5. 进阶技巧:用 MySQL 存储过程做认领审核的原子操作
5.1 为什么认领审核需要原子性
失物招领里有个典型场景:一个物品被多人点击“认领”,系统需要判断当前状态是否为“待认领”,如果是则更新为“已认领”并记录认领人。如果两个请求同时进来,先查后改的写法会导致两个人都认领成功。用 MySQL 存储过程把判断和更新包在一个原子操作里,能避免这种并发问题。
DELIMITER // CREATE PROCEDURE claim_item(IN p_item_id INT, IN p_user_id INT, OUT p_result INT) BEGIN DECLARE v_status TINYINT; START TRANSACTION; SELECT status INTO v_status FROM item WHERE id = p_item_id FOR UPDATE; IF v_status = 0 THEN UPDATE item SET status = 1, user_id = p_user_id WHERE id = p_item_id; SET p_result = 1; ELSE SET p_result = 0; END IF; COMMIT; END // DELIMITER ;逻辑说明:FOR UPDATE加行锁,保证同一物品的并发认领请求串行执行;p_result输出 1 表示认领成功,0 表示已被抢。参数说明:p_item_id是物品 ID,p_user_id是认领人 ID。调用时用CALL claim_item(1, 2, @res); SELECT @res;。
5.2 在 SpringBoot 里调用存储过程
MyBatis 调存储过程用@Select配合statementType = StatementType.CALLABLE:
@Select("{call claim_item(#{itemId}, #{userId}, #{result, mode=OUT, jdbcType=INTEGER})}") @Options(statementType = StatementType.CALLABLE) void claimItem(@Param("itemId") int itemId, @Param("userId") int userId, @Param("result") Map<String, Object> result);调用后从result.get("result")取值判断。注意@Options(statementType = StatementType.CALLABLE)不能省,否则 MyBatis 会当成普通查询,OUT 参数拿不到。
5.3 验证存储过程是否生效
启动项目后,用两个浏览器窗口同时登录不同账号,对同一个物品点认领。观察数据库item表的status和user_id字段,应该只有一个账号成功。如果两个都成功,检查存储过程里有没有加FOR UPDATE,以及 MySQL 引擎是不是 InnoDB——MyISAM 不支持行锁,这个坑我当年踩过,查了半天才发现建表时引擎写错了。
注意:存储过程在 MySQL 5.7 和 8.0 里语法基本一致,但 8.0 默认开启了
binlog,创建存储过程需要SUPER权限或者log_bin_trust_function_creators=1。如果你用宝塔面板装的 MySQL,去配置文件里加这一行再重启。
这套方案我从建表到接口联调跑通大概花了一个周末,最难的不是写代码,而是环境版本对齐和字符集统一。如果你也在做校园失物招领系统,建议先把 MySQL 脚本内置这条链路走通,再往上叠前端页面,不然后面查数据问题会怀疑人生。希望帮到你。
本文还有配套的精品资源,点击获取