拿到 springboot007 这套大学生租房平台源码后,我第一反应不是急着跑起来,而是先拉开数据库字段表看了一遍。很常见的毕设题目,但只要你把“学生要租房、房东要发房、管理员要审房”三条线同时放进一个系统,最简单的列表增删改查就不太够用了。这篇准备以这套源码为例,讲讲这类校园租房项目从建库到跑通的全过程,重点说清楚哪些地方是设计上的分水岭、哪些地方容易在演示或答辩时被问倒,也给你一份可以直接对照使用的落地清单。不管你是准备拿它改造成自己的毕设,还是想通过一个完整项目把 Spring Boot 的业务建模、接口开发、权限控制串起来,这篇应该都能帮上忙。
1. 三个角色一出来,需求就绕不开“状态流转”
1.1 学生、房东、管理员,三方看的其实是同一条业务链的不同阶段
这类平台乍一看和二手交易网站有点像,都是“有人发布、有人浏览、有人成交”,但租房场景有一个明显差异:它不是下单即结束,而是需要走“发房 → 审核 → 看房 → 申请 → 签约/拒绝”这样一条更长的链路。
先说学生这一侧。学生的核心诉求是“快速找到符合自己预算和位置要求的房子”,所以检索条件一定要做得具体:城市、区域、价格区间、租赁方式、几室几厅,最好还能按图片多少、发布时间筛选。学生在找到房子之后不会直接下单,通常会先发起咨询或者提交一个“租房申请”,等房东确认后双方才进入线下看房环节。
房东这一侧要处理的事更杂。他要维护自己名下的多套房源,能发布、编辑、上架、下架,还要查看每个租房申请来自哪个学生、状态是什么。很多新手在做房东端时只做了“房源 CRUD”,把最关键的“申请处理”给漏了,这会导致业务流程根本闭环不了。只要有学生提交租房申请,房东端就必须有对应的待处理入口,否则这条路断了,整个项目就只能算一个“带登录功能的房源展示站”。
管理员则是平台的守门员。因为房源信息涉及线下真实交易,管理员至少要负责用户状态管理和房源审核。学生发布虚假房源、房东上传违规图片,这些在毕设答辩里不一定要真实做到,但表结构和接口设计时必须留下“审核”这个维度,否则别人一看就知道需求想得太浅。
1.2 把业务语言翻译成可以验收的功能清单
我在复盘这套源码的时候,会习惯先把用例拆成一张功能清单,再对照代码看哪个模块是完整的、哪个模块只是摆设。对于大学生租房平台,核心模块至少应该是下面这些:
| 模块 | 面向角色 | 关键功能点 | 是否容易被忽略 |
|---|---|---|---|
| 登录注册 | 全部角色 | 用户名/手机号登录、注册、角色区分 | 管理员账号需要系统内置或授权 |
| 房源检索 | 学生 | 分页、条件筛选、关键词搜索、收藏 | 分页和排序最容易被忽略 |
| 房源详情 | 学生 | 轮播图、设施标签、基本信息、房东信息 | 图片上传和回显是常见坑 |
| 租房申请 | 学生 | 提交申请、查看我发起的申请、取消申请 | 状态流转必须设计清楚 |
| 申请处理 | 房东 | 查看收到的申请、同意/拒绝 | 部分源码缺这个就成大问题 |
| 房源管理 | 房东 | 发布、编辑、上下架、查看租出状态 | 编辑后是否需要重新审核要注意 |
| 用户与审核管理 | 管理员 | 用户列表、房源审核、数据概览 | 审核状态位必须贯通房源表 |
| 公告/通知 | 全部角色 | 站内公告、消息通知 | 属于加分项,非核心 |
对照这个清单,你拿到源码后可以先做一个“检查动作”:学生提交一笔租房申请,房东端能不能看到?房东同意后,学生端的状态会不会变成“待签约”或“已签约”?如果你的源码在申请链路这里断了,那它大概率是不完整的,不建议直接拿去交作业。
1.3 容易被新手忽略的三个非功能性需求
第一个是密码安全。很多毕业设计源码里用户表密码直接明文存储,演示时没问题,但只要问一句“你怎么保证用户密码安全”,如果答不上来,整个项目的技术深度都会被打问号。这里至少要会用 BCrypt 或 MD5+盐,前者是更常见的做法。
第二个是文件上传目录的处理。房源图片、合同文件不能只存在项目根目录里,否则重打包、重启都可能丢。源码如果用的是本地磁盘存储,一定要把路径单独抽到配置文件里,并配置静态资源映射。
第三个是操作幂等和防重复。学生连续点两次“提交租房申请”,数据库里不应出现两条相同订单。这个在源码里一般靠前端按钮置灰处理,但后端也要做一层简单的去重判断。能做到这一步,项目的严谨程度会明显高出一截。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 骨架选型:为什么 Spring Boot 是这种题目最不费力的底座
2.1 技术栈清单与版本搭配的关键点
无论你拿到的源码是 JSP 版、Thymeleaf 模板版,还是前后端分离版,Spring Boot 作为后端主框架几乎没争议。它最大的优势不是“代码生成快”,而是把 Spring 全家桶里最常用的能力做成了开箱即用的 starter,你不需要去理解一堆 XML 配置就能把 Web 服务跑起来。
这套项目比较稳妥的技术栈搭配如下:
- 后端:Spring Boot 2.7.x,搭配 JDK 1.8 或 JDK 17
- ORM:MyBatis-Plus 3.5.x
- 数据库:MySQL 5.7 或 8.0
- 权限方案:JWT + 拦截器
- 前端:Vue 3 + Vite + Element Plus(如果源码给的是 Vue 2 也能用,但新项目建议直接 Vue 3)
- 构建工具:Maven 3.6+
这里有一个很容易踩的版本雷区:Spring Boot 3.x 要求 JDK 17 起步,而很多基础课和毕设环境还是 JDK 1.8。如果你拿到的是 Spring Boot 2.x 源码,就别强行升级到 Spring Boot 3.x,否则不仅 MyBatis-Plus 要用专门的 mybatis-plus-spring-boot3-starter,很多第三方工具类也会因为 javax 到 jakarta 的命名空间切换而报一堆错。反过来,如果你新开项目且本机只有 JDK 17,直接用 Spring Boot 3.x 就好,没必要为了“兼容旧项目”而把自己绑在 JDK 1.8 上。
2.2 源码包目录长什么样,要怎么改
这套源码拿到后,先看工程目录是否清晰。一个合理的结构大致是这样的:
code复制student-rental
├── sql
│ └── init.sql
├── src/main/java/com/example/rental
│ ├── RentalApplication.java
│ ├── common
│ │ ├── Result.java
│ │ ├── BizException.java
│ │ └── GlobalExceptionHandler.java
│ ├── config
│ │ ├── WebMvcConfig.java
│ │ └── JwtInterceptor.java
│ ├── controller
│ │ ├── AuthController.java
│ │ ├── HouseController.java
│ │ ├── RentOrderController.java
│ │ └── admin
│ │ ├── AdminHouseController.java
│ │ └── AdminUserController.java
│ ├── service
│ ├── mapper
│ ├── entity
│ └── dto
├── src/main/resources
│ ├── application.yml
│ └── mapper
│ └── HouseMapper.xml
└── web
├── package.json
└── src
我个人比较在意 common 目录是否存在。它里面通常放统一返回体 Result、全局异常处理和自定义业务异常,这几样东西决定了接口抛错时前端拿到的 JSON 长什么样。如果没有统一返回体,每个 controller 各写各的,联调时前端要兼容无数种返回结构,非常痛苦。源码缺少这个目录时,建议自己补上,改动成本不高,但对整个项目的规范性提升非常明显。
2.3 “版本太高”为什么也成了常见坑
很多同学在导入 Spring Boot 项目时会遇到类似的报错:“java: 错误: 无效的源发行版 17”或者“程序包 lombok不存在”。这通常不是代码问题,而是 IDEA 里 Project Structure 的 Java 版本、Maven 的 JDK 版本、pom.xml 里的 java.version 三者不一致。
我遇到过有人拿一个基于 Spring Boot 2.7 的老项目,非要把 JDK 换成 19,结果 Lombok 版本不支持,编译直接失败。处理办法很简单:要么把 JDK 降到 1.8 并保持 pom 里的 <java.version>1.8</java.version>,要么把整套依赖升级到兼容 JDK 17+ 的版本。别在版本这件事上将就,环境不一致会浪费大量时间。
3. 建表之前想清楚这三件事,业务才不会在联调阶段来回改
3.1 用户、房源、租房申请,这三张核心表的关系要先画出来
数据模型设计是这个项目里最值得花时间的部分。很多源码能跑通,但扩展性很差,是因为建表时没有把核心关系想明白。
先说用户表 user。它至少要包含:id、用户名、密码、手机号、头像、角色、状态、注册时间。这里的角色字段是业务分层的起点,我建议用 1/2/3 分别代表学生、房东、管理员,而不要用字符串存中文,既省空间又方便代码里定义常量。这里有一个细节:一个人可能既是学生又是房东吗?在真实场景中是可能的。但毕设为了简单,通常每次登录只能选一种角色,或者用 user_role 关联表做多角色。源码如果只用一个 role 字段,答辩时可以主动提一句“这是为了简化模型”,而不是等老师来问。
用户表和房源表之间的关系是“一对多”:一个房东可以发布多套房源。房源表和租房申请表之间的关系也是“一对多”:一套房源可以被多个学生申请,但最终只会和其中一个人签约。所以正常的做法是申请单里记录 house_id 和 student_id,而不要在房源表里直接加一个 student_id 字段,否则同一套房被第二个人申请时,之前的申请记录就被覆盖了。
3.2 房源表 DDL 解析:关注检索字段和状态字段
我整理一个可参考的房源表结构,实际使用中可在这个基础上增删字段:
sql复制CREATE TABLE `house` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '房源ID',
`landlord_id` int(11) NOT NULL COMMENT '房东ID',
`title` varchar(100) NOT NULL COMMENT '房源标题',
`cover` varchar(255) DEFAULT NULL COMMENT '封面图URL',
`images` text COMMENT '轮播图,多个URL用逗号分隔',
`province` varchar(30) DEFAULT NULL,
`city` varchar(30) DEFAULT NULL,
`district` varchar(30) DEFAULT NULL,
`address` varchar(255) DEFAULT NULL COMMENT '详细地址',
`price` decimal(10,2) DEFAULT NULL COMMENT '月租金',
`rent_type` tinyint(1) DEFAULT '1' COMMENT '出租方式:1整租 2合租',
`area_size` decimal(8,2) DEFAULT NULL COMMENT '面积,单位平米',
`bedroom_num` tinyint(2) DEFAULT NULL COMMENT '卧室数',
`living_room_num` tinyint(2) DEFAULT NULL COMMENT '客厅数',
`bathroom_num` tinyint(2) DEFAULT NULL COMMENT '卫生间数',
`orientation` varchar(10) DEFAULT NULL COMMENT '朝向:南/北/东/西/南北',
`facilities` varchar(255) DEFAULT NULL COMMENT '设施,逗号分隔,如wifi,air_condition,washer',
`description` text COMMENT '房源描述',
`status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '状态:0待审核 1已上架 2已出租 3已下架 4审核拒绝',
`view_count` int(11) DEFAULT '0' COMMENT '浏览量',
`is_recommend` tinyint(1) DEFAULT '0' COMMENT '是否推荐',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_landlord` (`landlord_id`),
KEY `idx_area_price` (`city`, `district`, `price`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='房源表';
这张表有几个字段可能是从简单源码里看不到的,但建议保留。第一个是 status,把“待审核”“已上架”“已出租”“已下架”“审核拒绝”统一用一个数字表达,前端再根据数字映射成对应标签。这样管理员审核、房东下架、前端展示过滤都能用同一个字段,不用到处拼条件。第二个是 is_recommend,这个字段的价值在“首页不冷场”。如果没有推荐位,刚上线的平台房源少、没有排序逻辑,首页看起来会很空。有了它,管理员可以把几套优质房源置顶,演示效果会好很多。第三个是地理位置的冗余字段,比如 province/city/district 分开存,而不是只用一串 address。虽然冗余,但检索时可以快速用等值条件过滤,不用在 address 里做模糊匹配。
3.3 租房申请单的状态机设计
租房申请单是整个平台的“心脏”,它记录了一笔租房意向从开始到结束的所有状态变化。我见过最离谱的源码是给申请表只设计了一个 status 字段,功能上用“删除记录”来代表拒绝,这会让历史记录丢失。
一张合格的租房申请表至少是这样的:
sql复制CREATE TABLE `rent_order` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`order_no` varchar(32) DEFAULT NULL COMMENT '业务编号,展示给用户看的单号',
`house_id` int(11) NOT NULL,
`house_title` varchar(100) DEFAULT NULL COMMENT '冗余房源标题,订单列表不用再联表',
`cover` varchar(255) DEFAULT NULL COMMENT '冗余封面图',
`student_id` int(11) NOT NULL COMMENT '申请人ID',
`student_name` varchar(30) DEFAULT NULL COMMENT '冗余申请人姓名',
`student_phone` varchar(20) DEFAULT NULL,
`landlord_id` int(11) NOT NULL COMMENT '房东ID',
`start_date` date DEFAULT NULL COMMENT '期望入住时间',
`duration_months` int(11) DEFAULT NULL COMMENT '租期,单位月',
`message` varchar(500) DEFAULT NULL COMMENT '备注',
`status` tinyint(2) NOT NULL DEFAULT '0' COMMENT '0待房东处理 1待签约 2已签约 3已拒绝 4已取消 5已结束',
`apply_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '申请时间',
`handle_time` datetime DEFAULT NULL COMMENT '房东处理时间',
`sign_time` datetime DEFAULT NULL COMMENT '签约时间',
`reject_reason` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_house` (`house_id`),
KEY `idx_student` (`student_id`),
KEY `idx_landlord` (`landlord_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='租房申请表';
状态流转建议这样设计:学生提交申请后状态为 0,房东可以同意(变成 1,表示可以签约)或拒绝(变成 3)。状态为 1 时,学生可以“确认签约”,把它推到 2;房东或者学生也可以取消,变成 4。最后租期结束由房东或管理员手动设置为 5。注意这套流程里没有“待看房”状态,简化处理了。如果想把看房流程也做进去,可以单独建一个 visit_reservation 表,把看房预约和最终的租房申请解耦,这样逻辑更清晰。
3.4 表设计里的两个实用选择:适度冗余与逻辑删除
前面在租单表里我写了很多冗余字段,比如冗余了房源的标题和封面、学生的姓名和手机号。标准化学过的读者可能会问,这违反第三范式了吧?但在真实项目里,适度冗余是为了减少高频查询时的联表。比如学生端“我发起的申请”列表,如果每次都要 JOIN 房源表和用户表去取标题和头像,看起来不复杂,但遇到条件筛选和分页的时候 SQL 会越写越长。把业务列表页面展示需要的几个核心字段直接冗余到订单表里,查询体验会好很多。当然冗余带来的问题是,如果房源标题被修改了,订单里的旧标题不会自动变。在租房这个场景里,订单一旦生成,通常需要保留“申请当时看到的标题和信息”,所以这个冗余反而更合理。
另一个值得说的是逻辑删除。用户注销、房源删除这类场景,不建议用 DELETE 语句把记录直接物理删掉。原因是后续如果需要统计历史数据、或者恢复误删房源,物理删除就全没了。MyBatis-Plus 支持全局逻辑删除配置,在实体字段上标 @TableLogic,执行 delete 时它会自动改成 UPDATE ... SET deleted = 1。数据库层面加一个 deleted 字段,默认 0,删除后置 1,查询时框架会自动过滤。这个方案很适合毕设项目,既能体现你对业务数据完整性的理解,实现成本又很低。
4. 我把检索、鉴权、租房申请三条链路各写一遍,避免只靠 List 凑数
4.1 房源分页检索:最容易被写成“全表查询”的地方
房源检索模块是所有页面里调用最频繁的接口。刚入门的人很容易写成一个查询所有记录再程序里过滤的方法,或者只按一个关键词模糊搜。这样应付一两条数据没问题,一旦数据量到几千条,每次响应都会明显变慢。
用 MyBatis-Plus 时,比较推荐把查询条件封装成一个 DTO,然后在 Service 层用 LambdaQueryWrapper 动态拼条件。示例代码大致是这样:
java复制public IPage<HouseVO> searchHouse(HouseQueryDTO query) {
Page<House> page = new Page<>(query.getPageNum(), query.getPageSize());
LambdaQueryWrapper<House> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(StringUtils.hasText(query.getCity()), House::getCity, query.getCity())
.eq(StringUtils.hasText(query.getDistrict()), House::getDistrict, query.getDistrict())
.eq(query.getRentType() != null, House::getRentType, query.getRentType())
.ge(query.getMinPrice() != null, House::getPrice, query.getMinPrice())
.le(query.getMaxPrice() != null, House::getPrice, query.getMaxPrice())
.ge(query.getBedroomNum() != null, House::getBedroomNum, query.getBedroomNum());
// keyword 同时匹配标题和地址,注意要用 and 包起来,避免破坏其他条件
if (StringUtils.hasText(query.getKeyword())) {
wrapper.and(w -> w.like(House::getTitle, query.getKeyword())
.or()
.like(House::getAddress, query.getKeyword()));
}
// 学生/游客只能看到已上架房源
wrapper.eq(House::getStatus, 1)
.eq(query.getSortType() != null && query.getSortType() == 2,
House::getIsRecommend, 1)
.orderByDesc(House::getIsRecommend)
.orderByDesc(House::getViewCount);
return houseMapper.selectPage(page, wrapper);
}
这套代码的核心优势是“条件有无都安全”。前端没传价格上限时,le 方法根本不会生效,避免了在 XML 里写一堆 <if> 的繁琐。需要注意一个细节:多字段 OR 的模糊查询一定要用 and(...) 包起来,否则 WHERE city = ? AND title LIKE ? OR address LIKE ? 会因为运算符优先级导致结果错乱。这也是面试官经常挖坑的一个点。
4.2 JWT 登录和角色权限:简单直接但不要做成到处散落
登录模块用 JWT 已经是这类项目的主流做法。流程并不复杂:用户输入账号密码,后端校验通过后,生成一个包含 userId 和角色的 token 返回给前端。前端之后每次请求都在请求头带上 Authorization: Bearer <token>,后端用一个拦截器统一解析。
拦截器负责两件事:一是判断 token 是否存在并且能解析出 userId;二是把当前登录用户的信息放进 ThreadLocal,方便后续 controller 或 service 直接获取。核心代码大致是:
java复制public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 跨域预检请求直接放行
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
return true;
}
String token = request.getHeader("Authorization");
if (StringUtils.hasText(token) && token.startsWith("Bearer ")) {
token = token.substring(7);
}
Long userId = JwtUtils.parseToken(token);
if (userId == null) {
throw new BizException(401, "登录已过期,请重新登录");
}
UserContext.setUserId(userId);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
// 请求结束后必须清理,否则线程池复用会串号
UserContext.clear();
}
}
然后在配置类里注册拦截器,并放行登录、注册、房源分页浏览、房源详情这些不需要登录也能访问的接口。
角色权限怎么控制呢?我见过在 controller 每个方法里先 UserContext.getUser() 再判断角色,代码里到处是 if,容易漏。更清晰一点的做法是,把“登录后获取当前用户角色”封装成一个工具方法,需要管理员身份的接口就直接注入 AdminUserService 或在方法前调用一个权限校验工具。如果项目想更正规,可以引入 Spring Security 或 Sa-Token,但对一个以 Spring Boot 为主的毕设项目来说,JWT + 自定义拦截器 + 角色字段已经能覆盖绝大部分需求。需要注意的是,后端在做“查询我的房源”这类接口时,除了从 token 获取 userId,还要在 SQL 里带上 landlord_id = userId,防止普通学生通过改 URL 上的 id 看到别人的房源数据。
4.3 提交租房申请与状态变更:用乐观更新避免“一房多签”
提交租房申请的接口看起来只是 insert 一条订单记录,实际上需要保证两点:一是同一学生不能对同一套房源重复提交未处理的申请;二是房源不能被两个学生同时签走。
重复提交的判断可以在 insert 前先 select 一下。更好的做法是在申请表上建一个唯一索引,比如 uk_student_house_active,但这在有多个历史状态时并不合适,因为同一个人可能租完一次再租第二次。一般建议在 service 层先查询是否存在“学生等于当前用户、房源等于目标房源、状态在 0/1/2 之间”的记录:
java复制public Long submitApply(RentApplyDTO dto, Long studentId) {
// 1. 校验学生和房源状态
House house = houseMapper.selectById(dto.getHouseId());
if (house == null || !house.getStatus().equals(1)) {
throw new BizException("该房源不存在或已下架");
}
if (house.getLandlordId().equals(studentId)) {
throw new BizException("不能申请自己发布的房源");
}
// 2. 查重复申请
Long count = rentOrderMapper.selectCount(
new LambdaQueryWrapper<RentOrder>()
.eq(RentOrder::getStudentId, studentId)
.eq(RentOrder::getHouseId, dto.getHouseId())
.in(RentOrder::getStatus, Arrays.asList(0, 1, 2))
);
if (count != null && count > 0) {
throw new BizException("你已申请过该房源,请等待房东处理");
}
// 3. 创建申请单
...
}
房东处理申请时,更关键的是状态更新的写法。不要先 select 当前状态,再在代码里 if 一下然后 update,这类写法在并发场景下可能出现两个人同时看到“待处理”,同时执行同意操作的情况。正确做法是把“当前状态”作为 update 条件,并利用数据库受影响行数来判断是否更新成功:
java复制public boolean handleOrder(Long orderId, Long landlordId, Integer fromStatus, Integer toStatus) {
UpdateWrapper<RentOrder> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("id", orderId)
.eq("landlord_id", landlordId)
.eq("status", fromStatus)
.set("status", toStatus)
.set("handle_time", new Date());
return rentOrderMapper.update(null, updateWrapper) == 1;
}
这里没有先查后改,而是直接把 “status = 0” 作为条件。如果房东 A 和房东 B 同时处理同一个订单,数据库行锁只会让其中一个 update 成功,另一个因为条件不满足影响行数为 0,接口就返回“操作失败,请刷新”。这套思路在答辩里非常加分,因为它展示了你对并发安全的理解,而不是只会写 CRUD。
4.4 图片上传与回显:部署后 404 的大概率在这个环节
这个项目里图片主要涉及房源封面、房源轮播图和用户头像。Spring Boot 接收上传文件并不复杂,但有一个问题经常让初学者卡住:上传到本地磁盘的文件,默认情况下无法通过 URL 直接访问,因为 Spring Boot 不会把项目外部的目录当作静态资源暴露出来。
解决办法是配置一个本地磁盘路径到 URL 的映射。在 WebMvcConfig 里写下这段:
java复制@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Value("${custom.upload.path}")
private String uploadPath;
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/upload/**")
.addResourceLocations("file:" + uploadPath);
}
}
上传文件的代码则建议用 UUID 重命名,避免用户上传的图片文件名和别人的冲突,也防路径穿越:
java复制public String uploadFile(MultipartFile file) {
if (file.isEmpty()) {
throw new BizException("上传文件不能为空");
}
String originalFilename = file.getOriginalFilename();
String ext = "";
if (originalFilename != null && originalFilename.contains(".")) {
ext = originalFilename.substring(originalFilename.lastIndexOf("."));
}
String filename = UUID.randomUUID().toString().replace("-", "") + ext;
File dest = new File(uploadPath + filename);
try {
file.transferTo(dest);
} catch (IOException e) {
throw new BizException("文件上传失败");
}
// 返回给前端的访问路径
return "/upload/" + filename;
}
配置里记得把上传路径放到配置文件而不是写死在代码里。Windows 上写 D:/student-rental/upload/,Linux 上写 /usr/local/student-rental/upload/,同时要确保目录有写入权限,否则启动时不报错,真正传文件时才报 FileNotFoundException。
5. 从下载源码到跑通页面的完整操作清单(含坑位提醒)
5.1 启动前的三步准备工作
不管你从哪个渠道拿到源码,请先对照检查三件事,能帮你省掉很多启动报错。
第一,确认包内是否包含 SQL 文件。很多源码分享只会给后端代码和前端代码,但没给数据库建表脚本,你只能凭实体类手动建表,十分痛苦。正常的源码包至少应有一个 init.sql,或者文档里写明建表语句在哪里。如果没有,就先看实体类,把所有字段整理成 DDL,再核对关联关系。
第二,检查 pom.xml 里的依赖版本和本机 JDK 是否匹配。如果 pom 里写着 <java.version>1.8</java.version>,本机却是 JDK 17,大概率会出现编译错误。这时可以优先下载 JDK 1.8 并切换,而不是去改整个项目的依赖。
第三,确认没有缺少前端目录。如果这套源码是前后端分离的,需要同时有后端工程和 web 或 frontend 目录。如果只有后端,那你只能通过 Swagger 或 Postman 调接口,页面看不到。
5.2 后端和前端的启动顺序
启动后端的步骤基本固定。第一步用 Navicat 或命令行创建数据库:
bash复制mysql -uroot -p -e "create database student_rental default character set utf8mb4"
第二步导入数据:
bash复制mysql -uroot -p student_rental < sql/init.sql
第三步修改 application.yml 里的数据库账号密码。如果你的 MySQL 是 8.0,驱动要写 `com.mysql.cj.jdbc.Driver
