news 2026/10/1 18:27:49

基于Spring Boot的大学生房屋租赁系统全解析:从JavaWeb到Docker部署

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于Spring Boot的大学生房屋租赁系统全解析:从JavaWeb到Docker部署

Spring Boot 的大学生房屋租赁系统,前前后后我做过好几个版本,有给毕设做的,也有给实际业务改造的。这个“基于 JavaWeb 的大学生房屋租赁系统的设计与实现”,属于非常典型的 Java 全栈入门偏进阶项目。放到今天来看,它不算新潮,但恰恰是因为它不新潮,反而特别适合用来把 Spring Boot 的核心链路吃透——从 Maven 构建、MyBatis 持久层、拦截器鉴权、文件上传,到前后端联调、Docker 部署,整条线走完,你对“一个 JavaWeb 项目到底是怎么跑起来的”会有非常踏实的理解,而不是停留在“跟着视频敲了一遍”的层面。

这篇文章我就按我实际做这类项目的顺序来写,从整体设计思路、技术选型、核心模块的代码落地,到数据库表设计、部署配置、常见坑的排查方法,尽量把“为什么这样做”也讲清楚。内容会比较多,建议收藏着慢慢看。

1. 项目整体设计与思路拆解

1.1 这个系统的定位与核心需求

大学生房屋租赁系统,说白了就是一个“房源信息发布 + 租客找房 + 在线沟通/预订/合同管理”的内容管理类系统。目标用户很明确:在校大学生和学校周边的房东(或者二房东)。这类用户群体的特点决定了系统功能和传统租房平台差异很大:

  • 价格敏感、信息透明:大学生预算有限,对价格、距离学校的位置、合租室友情况非常在意。
  • 租期灵活:寒暑假、实习、考研二战,租期往往不是标准的一年,而是几个月甚至按周计算。
  • 流程相对简单:不要求像链家那样复杂的电子签约、押金托管,核心是“看到房源 → 联系房东 / 发起预订 → 线下看房 → 成交确认”。
  • 管理员干预要少:系统需要一个后台做基本审核(比如房东身份验证、房源信息合规性),但不需要太重的审批流。

基于这些分析,系统可以拆成三大端:

  1. 租客端(前台):注册登录、浏览房源列表、按区域/租金/户型筛选、查看房源详情、收藏房源、发起租房申请(或在线预订)、管理自己的订单/合同、个人信息维护、浏览公告。
  2. 房东端(前台角色之一):房源发布、房源上下架、编辑房源信息(标题、图片、租金、户型、配套设施、地址)、处理租客的租房申请(同意/拒绝)、查看自己的合同和收款记录。
  3. 管理员端(后台):用户管理(禁用/启用)、房东资质审核、房源审核、公告管理、投诉与反馈处理、数据概览(比如房源数、注册用户数、订单数)。

注意:毕设或者课程设计里,很容易把结构做成“普通用户 + 管理员”两端。但“大学生房屋租赁”这个场景下,把“房东”独立成一个有业务操作权限的角色,会让项目的业务逻辑丰富很多,答辩时也更好讲。用户表里用一个 role 字段区分(0 普通用户 / 1 房东 / 2 管理员)即可,没必要拆成三套用户体系。

1.2 为什么用 Spring Boot + JavaWeb 这套组合

这个问题我经常被问。标题里“基于 JavaWeb”和“Spring Boot”同时出现,很多同学会觉得矛盾——JavaWeb 不是 Servlet + JSP 的老技术吗?Spring Boot 不是新框架吗?其实不矛盾。

  • JavaWeb 是范畴:广义的 JavaWeb 指“用 Java 语言开发 Web 应用”的整个技术体系,包括 Servlet、HTTP 协议、Session/Cookie、Filter/Interceptor、MVC 分层模型这些基础概念。这些是面试常问、也是理解框架底层绕不开的东西。
  • Spring Boot 是工具:它把传统的 Spring MVC + Spring + MyBatis 整合过程简化了,内嵌 Tomcat,自动装配,让你从“配置地狱”里解放出来。但它底层跑的还是 Servlet 规范那一套——DispatcherServlet 还是继承自 HttpServlet,你写的 Controller 最终还是被 Servlet 容器调用。

所以用 Spring Boot 做这个项目,等于“站在 JavaWeb 基础之上,用现代的开发效率去实现业务”。这也就是为什么绝大多数高校毕设题目都是“基于 Spring Boot 的 XX 系统”——既有理论深度(JavaWeb 基础),又有工程实践价值(Spring Boot 全家桶)。

技术栈选型方面,我的建议是不要贪新,稳为主:

技术组件推荐选型说明
后端框架Spring Boot 2.7.x用 2.7.x 而非 3.x,原因后面专门讲
持久层MyBatis(或 MyBatis-Plus)比 JPA 好讲好懂,SQL 可控性强
数据库MySQL 5.7 / 8.0免费、资料多、面试常问
前端Thymeleaf + Bootstrap / Layui不推荐 Vue,理由见下文
构建工具Maven别用 Gradle,毕设答辩老师不一定熟
权限控制Spring Boot Interceptor + Session简单直接,够用
文件上传本地存储(后面可升级 MinIO)减少部署复杂度

不推荐上 Vue 前后端分离的原因很简单:大学生的房屋租赁系统,业务复杂度撑不起前后端分离的架构优势,却要承担双倍的开发量和部署成本。你用 Thymeleaf 模板引擎,后端渲染页面,一套 Spring Boot 应用直接跑完,本地开发、服务器部署都简单。前后端分离适合多人协作、接口复用要求高的大型项目,单人或双人做的课程设计,老老实实服务端渲染最省事。

1.3 为什么 Spring Boot 版本不能乱选

这其实是个直接影响“能不能跑起来”的问题。做毕设/课设最常见的悲剧:创建项目时选了 Spring Boot 3.x,然后怎么也跑不起来,报各种依赖冲突。原因很简单:

  • Spring Boot 3.x 最低要求 JDK 17,很多同学的机器还是 JDK 8,直接跑不了。
  • Spring Boot 3.x 用的是 Jakarta EE(javax 改成了 jakarta 包名),很多网上的老教程里的代码直接粘贴过来会报“程序包 javax.servlet 不存在”。
  • 国内大量教学资料、毕业设计论文模板、博客代码都是基于 Spring Boot 2.x 写的,你选 3.x 意味着几乎所有的参考代码都要自己改。

所以,老老实实用 Spring Boot 2.7.18(2.x 最后的一个版本),JDK 1.8,MyBatis 用 mybatis-spring-boot-starter 2.2.0 或 2.3.0。这个组合经过了无数人的验证,网上资料最全,出问题最好搜。等这个项目做完了,想体验新特性再单独去学 3.x 不迟。

2. 核心功能模块设计与编码要点

2.1 用户注册、登录与权限拦截

这个模块是整个系统的地基。注意几个关键点:

密码不能明文存。我见过太多课程设计代码是明文密码,答辩老师一问“你这个密码安全吗”就卡壳。用 Spring Security Crypto 或者自己写个 MD5 加盐都行。最简单靠谱的姿势:

// 引入 spring-security-crypto 依赖,用 BCrypt 加密 // 依赖坐标:org.springframework.security:spring-security-crypto:5.7.11 String encodedPwd = new BCryptPasswordEncoder().encode(rawPassword); // 校验时: boolean matches = new BCryptPasswordEncoder().matches(rawPassword, encodedPwd);

BCrypt 的好处是每次加密结果都不一样(内置随机盐),而且计算速度慢,能抗暴力破解。比自己手写 MD5 + 固定盐要安全得多。虽然毕设项目对安全要求没那么高,但用上这个,答辩时算是一个亮点。

登录状态用 Session 还是 JWT?对于服务端渲染的项目,直接用 HttpSession 即可,不需要引入 JWT 增加复杂度。登录成功后把用户对象放进 Session:

@PostMapping("/login") public String login(String username, String password, HttpSession session, Model model) { User user = userService.login(username, password); if (user == null) { model.addAttribute("error", "用户名或密码错误"); return "login"; } session.setAttribute("loginUser", user); // 根据角色跳转到不同首页 if (user.getRole() == 2) { return "redirect:/admin/index"; } return "redirect:/index"; }

权限拦截器是必须的。否则用户直接访问http://localhost:8080/admin/index就绕过了登录,系统等于裸奔。用一个 HandlerInterceptor 实现:

@Component public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行登录注册相关的 URL String uri = request.getRequestURI(); if (uri.startsWith("/login") || uri.startsWith("/register") || uri.startsWith("/css") || uri.startsWith("/js") || uri.startsWith("/images") || uri.startsWith("/fonts")) { return true; } HttpSession session = request.getSession(); User user = (User) session.getAttribute("loginUser"); if (user == null) { // 没登录,跳回登录页 response.sendRedirect("/login"); return false; } // 已登录,进一步做角色校验 if (uri.startsWith("/admin") && user.getRole() != 2) { response.sendRedirect("/index"); return false; } return true; } }

注册到 WebMvcConfigurer 里:

@Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private LoginInterceptor loginInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginInterceptor) .addPathPatterns("/**") .excludePathPatterns("/login", "/register", "/static/**", "/error"); } }

注意一个细节:Thymeleaf 的静态资源路径如果不做放行,CSS/JS 全部 404,页面难看得没法看。在 application.properties 里配置 Spring Boot 2.x 默认静态资源路径是classpath:/static/,所以资源引用路径写/css/xxx.css,拦截器放行/css/**、/js/**、/images/**即可。

2.2 房源信息的发布、展示与图片上传

房源模块是整个系统的核心业务。几个关键设计:

图片上传不能存 Base64 到数据库。数据库只存图片的访问路径(URL),文件本身落到本地磁盘目录(或后续换成 MinIO 对象存储)。做法:

@PostMapping("/house/add") public String addHouse(House house, MultipartFile[] files, HttpSession session) throws IOException { User loginUser = (User) session.getAttribute("loginUser"); house.setUserId(loginUser.getId()); // 归属于当前房东账号 // 上传主图 + 户型图等 List<String> urls = new ArrayList<>(); String uploadDir = "D:/upload/"; // 生产环境换成服务器绝对路径,不要用相对路径 for (MultipartFile file : files) { if (file.isEmpty()) continue; // 生成不重复的文件名:时间戳 + 随机数 + 扩展名 String originalFilename = file.getOriginalFilename(); String ext = originalFilename.substring(originalFilename.lastIndexOf(".")); String filename = System.currentTimeMillis() + "_" + new Random().nextInt(1000) + ext; file.transferTo(new File(uploadDir + filename)); urls.add("/upload/" + filename); } house.setImages(String.join(",", urls)); // 多张图逗号分隔存储 houseService.save(house); return "redirect:/house/list"; }

丰富房源筛选条件是个加分项。列表页除了分页,按区域、价格区间、户型、出租方式(整租/合租)、朝向筛选,对应的就是后端一个带动态 SQL 的查询。这正好能体现 MyBatis 的最强场景——动态 SQL:

<select id="searchHouses" resultType="com.example.entity.House"> SELECT * FROM house WHERE status = 1 <!-- 只查已上架 --> <if test="district != null and district != ''"> AND district = #{district} </if> <if test="minPrice != null"> AND price &gt;= #{minPrice} </if> <if test="maxPrice != null"> AND price &lt;= #{maxPrice} </if> <if test="type != null and type != ''"> AND house_type = #{type} </if> <if test="rentType != null and rentType != ''"> AND rent_type = #{rentType} </if> ORDER BY create_time DESC </select>

注意 XML 中小于号&lt;不能直接写<,否则 XML 解析直接报错。这个坑几乎每个人都要踩一次。

为什么图片地址要返回/upload/xxx.jpg而不是D:/upload/xxx.jpg?因为前端页面渲染时,浏览器拿D:/upload/xxx.jpg是访问不了的——那是服务器磁盘路径,不是 URL。你需要把本地的 upload 目录映射成一个可访问的静态资源路径。Spring Boot 里一行配置:

@Configuration public class UploadConfig implements WebMvcConfigurer { @Value("${file.upload-path}") private String uploadPath; @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { // 把 /upload/** 映射到磁盘路径,浏览器就能直接访问图片了 registry.addResourceHandler("/upload/**") .addResourceLocations("file:" + uploadPath); } }

2.3 租房申请、订单状态流转与合同生成

这个模块是业务深度的体现。一个完整的租房流程,状态要闭环:

状态机设计(用整数存状态,扩展性好):

待看房(0) → 已看房/待确认(1) → 已签约(2) / 已取消(3) 从已签约 → 已到期(4) / 已退租(5)

租客在房源详情页点“申请看房”/“立即预定”,生成一条订单记录,状态为待确认,同时通知房东(可以在房东端的“我的订单”里看到待处理列表)。房东点击“同意”,状态变为已看房/待签约;房东点击“拒绝”,直接流转到已取消。

订单状态变更的 Update 语句必须带状态条件:

// 防止并发下状态错乱:只允许从当前状态流转到目标状态 // SQL: UPDATE orders SET status = #{newStatus} WHERE id = #{orderId} AND status = #{oldStatus} // 返回值 int 为 0 说明状态已经变了,前端提示“操作失败,请刷新后重试” int rows = orderMapper.updateStatus(orderId, fromStatus, toStatus); if (rows == 0) { throw new BusinessException("操作失败,订单状态已更新,请刷新查看最新状态"); }

合同生成有两种做法:一种是在线预览/打印 HTML 合同(Thymeleaf 模板渲染),另一种是直接用 Java 生成 PDF 文件。毕设建议选 HTML 合同预览,简单得多——你只要在合同页面把订单信息(双方姓名、房屋地址、租金、租期、押金)渲染出来,再提供一个“打印”按钮让用户自己打印。用 PDF 的话要引入 iText 或者 OpenPDF,字体问题(中文不显示、文字乱码)能折腾一整天,性价比太低。

合同的核心字段:合同编号(可以用时间戳+随机数生成)、出租方(房东)、承租方(租客)、房屋信息、租金、押金、起止日期、签订日期、双方同意条款。存一张 contract 表即可,状态跟订单绑定。

2.4 管理后台的数据概览与审核操作

管理后台不要做得太复杂,但一定要有“数据统计”+“审核操作”两块。

数据概览页:用一个简单的 Dashboard,展示房源总数、用户总数、今日新增订单、待审核房源数。Mapper 里写几个 count 查询即可:

@Select("SELECT COUNT(*) FROM house") int countHouse(); @Select("SELECT COUNT(*) FROM user") int countUser(); @Select("SELECT COUNT(*) FROM orders WHERE DATE(create_time) = CURDATE()") int countTodayOrders();

房源审核:房东发布的房源默认状态为待审核(status=0),管理员审核通过后变为已上架(status=1),驳回则填入驳回原因(status=2),租客前台只能看到已上架房源。这个流程简单,但在业务上非常合理——避免垃圾房源直接出现在列表里。

用户禁用:admin 后台可以直接把某个用户状态改为 0(禁用),被禁用的用户登录时提示账户已被禁用。登录逻辑加一个判断:

if (user.getStatus() == 0) { model.addAttribute("error", "该账户已被禁用,请联系管理员"); return "login"; }

3. 数据库设计:这 6 张核心表怎么建才合理

3.1 用户表、房源表、订单表的核心字段

数据库设计是答辩时老师必问的环节,也是后续写代码的地基。建表语句不要用逆向生成的,自己手工设计能讲清楚每张表为什么这么建。

用户表(t_user)

CREATE TABLE `t_user` ( `id` INT NOT NULL AUTO_INCREMENT COMMENT '用户ID', `username` VARCHAR(50) NOT NULL COMMENT '登录用户名', `password` VARCHAR(100) NOT NULL COMMENT 'BCrypt加密后的密码', `real_name` VARCHAR(30) DEFAULT NULL COMMENT '真实姓名', `phone` VARCHAR(20) DEFAULT NULL COMMENT '手机号', `email` VARCHAR(50) DEFAULT NULL COMMENT '邮箱', `role` TINYINT DEFAULT 0 COMMENT '角色:0普通用户/学生,1房东,2管理员', `status` TINYINT DEFAULT 1 COMMENT '状态:1正常,0禁用', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '注册时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

注意:chatset 必须用 utf8mb4 而不是 utf8。utf8mb4 才能存 emoji 表情和冷僻字,而且这是 MySQL 5.7+ 的主流配置。用老 utf8 后面插入特殊字符直接报错。

房源表(t_house)

CREATE TABLE `t_house` ( `id` INT NOT NULL AUTO_INCREMENT, `user_id` INT NOT NULL COMMENT '发布者ID(房东)', `title` VARCHAR(100) NOT NULL COMMENT '房源标题', `description` TEXT COMMENT '房源描述', `images` VARCHAR(1000) DEFAULT NULL COMMENT '图片地址,逗号分隔', `district` VARCHAR(30) DEFAULT NULL COMMENT '区域(如:海淀区)', `address` VARCHAR(200) DEFAULT NULL COMMENT '详细地址', `price` DECIMAL(10,2) NOT NULL COMMENT '月租金(元)', `house_type` VARCHAR(20) DEFAULT NULL COMMENT '户型(如:一室一厅、两室一厅)', `area` INT DEFAULT NULL COMMENT '面积(平方米)', `rent_type` TINYINT DEFAULT 0 COMMENT '出租方式:0整租 1合租', `orientation` VARCHAR(10) DEFAULT NULL COMMENT '朝向:南/北/东/西', `status` TINYINT DEFAULT 0 COMMENT '0待审核 1已上架 2已驳回 3已下架', `view_count` INT DEFAULT 0 COMMENT '浏览量', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_user_id` (`user_id`), KEY `idx_status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='房源表';

订单表(t_orders)

CREATE TABLE `t_orders` ( `id` INT NOT NULL AUTO_INCREMENT, `order_no` VARCHAR(32) NOT NULL COMMENT '订单编号', `house_id` INT NOT NULL COMMENT '房源ID', `tenant_id` INT NOT NULL COMMENT '租客ID', `landlord_id` INT NOT NULL COMMENT '房东ID', `start_date` DATE DEFAULT NULL COMMENT '租期开始日期', `end_date` DATE DEFAULT NULL COMMENT '租期结束日期', `monthly_rent` DECIMAL(10,2) DEFAULT NULL COMMENT '成交月租金', `deposit` DECIMAL(10,2) DEFAULT NULL COMMENT '押金', `status` TINYINT DEFAULT 0 COMMENT '0待确认 1待签约 2已签约 3已取消 4已到期', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_order_no` (`order_no`), KEY `idx_house_id` (`house_id`), KEY `idx_tenant_id` (`tenant_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='租房订单表';

其它表:收藏表(t_favorite,user_id + house_id 联合唯一)、公告表(t_notice,标题+内容+时间)、合同表(t_contract,关联订单)、反馈表(t_feedback)。这些表结构相对简单,按业务字段填充即可。我个人习惯是每张表都要有 id 主键和 create_time 字段,这是基本素养。

3.2 表关系怎么讲才清楚

答辩时老师爱问“你讲讲表与表之间的关系”。涉及到的关系:

  • 用户(房东)→ 房源:一对多,t_house.user_id 关联 t_user.id。
  • 用户(租客)→ 订单:一对多,t_orders.tenant_id 关联 t_user.id。
  • 房源 → 订单:一对多,一个房源可以被多个租客先后预订(但同一时间只能有一个有效订单,这个可以在业务层判断:查询该房源是否存在 status in (0,1,2) 的订单)。
  • 用户 → 收藏 → 房源:多对多,通过 t_favorite 中间表关联。

为什么要存冗余的 landlord_id 在订单表里?因为订单列表页要频繁按房东查订单,如果不冗余,每次都要先查房源再反查房东,SQL 写得啰嗦,性能也差。适度冗余在业务表设计里是常见做法,能讲出来说明你有实战经验。

4. 实操过程:从 IDEA 创建项目到跑通第一个页面

4.1 IDEA 创建 Spring Boot 项目时的正确配置

用 IDEA 的 Spring Initializr 创建项目,注意几个容易踩的选择项:

  • Project SDK 选Java 8(1.8),Language 选 Java。
  • 如果初始化时让你选 Spring Boot 版本,一定要选2.x 系列的最高版本(2.7.18),不要默认选 3.x。
  • Dependencies 里勾选:Spring Web、Thymeleaf、MyBatis Framework(注意来自 mybatis.org 那个,不是 MyBatis Spring Boot Starter)、MySQL Driver、Lombok(可选,减少样板代码)。
  • 如果初始化时没有 MyBatis 选项,就在 pom.xml 手动加:
<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.3.0</version> </dependency>

为什么要 MyBatis Framework 又要 mybatis-spring-boot-starter?IDEA 里默认给的 “MyBatis Framework” 其实是早期版本的 starter,坐标会写成org.mybatis:mybatis,这个只是 MyBatis 核心库,不会被 Spring Boot 自动配置。你需要的是org.mybatis.spring.boot:mybatis-spring-boot-starter,它把 SqlSessionFactory、DataSource 的装配都搞定了。如果不小心只加了核心库,一运行就会报Invalid bound statement (not found)之类的问题。

4.2 IDEA 运行 JavaWeb 项目的配置细节

IDE 配置这块,很多新手卡在“明明代码没问题,但跑不起来”。几个关键配置:

1. Maven 配置

  • Settings → Maven → Maven home path 选你自己解压的 Maven(3.6.x 或 3.8.x),不要用 IDEA 自带的也行,但建议手动配。
  • User settings file 指向conf/settings.xml,Local repository 指向你自定义的仓库目录。
  • settings.xml 里加阿里云镜像:
<mirror> <id>aliyunmaven</id> <mirrorOf>*</mirrorOf> <name>阿里云公共仓库</name> <url>https://maven.aliyun.com/repository/public</url> </mirror>

不加镜像的话,Maven 中央仓库的下载速度会让人怀疑人生。

2. 配置 Application 启动类

Edit Configurations → 点 + → Spring Boot → 选择主类(带上@SpringBootApplication的那个类)。Working directory 建议选$MODULE_WORKING_DIR$(模块目录),否则如果配错工作目录,读不到相对路径的配置文件。

3. 端口配置

application.properties里:

server.port=8080 spring.datasource.url=jdbc:mysql://localhost:3306/house_rent?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false spring.datasource.username=root spring.datasource.password=你的密码 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver mybatis.mapper-locations=classpath:mapper/*.xml mybatis.type-aliases-package=com.example.entity logging.level.com.example.mapper=debug

serverTimezone=Asia/Shanghai必须加。MySQL 8.x 的驱动默认时区是美国,不加这个,数据库连接直接报 CST 时区错误,页面数据全是乱的。

4. 一个把 Java 源文件直接放 classpath 的坑

MyBatis 的 Mapper 接口和 XML 文件分开两个目录时(接口在src/main/java,XML 在src/main/resources/mapper),如果不把src/main/java下的.xml文件也打进 classpath,运行时会报Invalid bound statement (not found)。最省心的做法是 XML 统一放 resources,并且 pom.xml 加配置:

<resources> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.xml</include> <include>**/*.properties</include> <include>**/*.yml</include> </includes> </resource> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> </resource> </resources>

4.3 跑通“注册 → 登录 → 发布房源 → 前端展示”全链路

不要等所有代码写完再一次性测试。我建议按下面这个顺序,每完成一个环节就跑通一次:

  1. 创建 Spring Boot 项目,写一个HelloController,访问http://localhost:8080/hello能看到字符串,证明环境是好的。
  2. 引入 MyBatis,配置 datasource,创建 user 表,完成注册接口:浏览器提交用户名密码 → 后台写入数据库 → 用 Navicat 能看到记录。
  3. 写登录接口:查数据库 → 比对密码 → Session 存入用户 → 跳转首页。
  4. 加拦截器,验证未登录访问/house/add会被重定向到登录页。
  5. 创建 house 表,完成房源发布表单提交(包含图片上传),再写一个/house/list页面展示数据库里的房源记录。
  6. 最后再完善订单、收藏、后台审核、公告等模块。

这样每个环节的问题都被限制在很小的范围内,排查起来极快。如果一口气写完所有代码再启动,报错之后你根本分不清是配置问题还是代码问题。

5. 常见问题与排查技巧实录

5.1 启动失败的几类典型原因

现象一:Failed to configure a DataSource: 'url' attribute is not specified

启动类上多了@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)但你没配数据源——这是最常见的误操作。或者你根本没有引入 JDBC/MyBatis 的依赖,但 Spring Boot 还是会自动装配数据源。排查思路:确认 application.properties 里 datasource 三件套(url/username/password)都配了,且驱动依赖已经在 pom 里。

现象二:Table 'house_rent.xxx' doesn't exist

数据库建了,但表不存在。检查方式:确认你连接的库名是不是真的叫 house_rent;确认表名、字段名和实体类/mapper XML 里完全一致(注意大小写和下划线);MySQL 在 Linux 下区分表名大小写,Windows 下不区分,所以本地跑得好好的代码部署到 Linux 服务器就报这个错。避免方式:建表全部用统一小写+下划线。

现象三:Whitelabel Error Page404

三种可能:Controller 的@RequestMapping路径没写对;静态资源和模板页没放对位置(Thymeleaf 模板必须在templates目录下);Controller 类没有被 Spring 扫描到(启动类不在com.example包的上级,或者 Controller 没加@Controller)。

现象四:MyBatis 的Invalid bound statement (not found)

Mapper 接口方法和 XML 里的 id 名称不一致;XML 文件没有在mapper-locations指定路径下;接口没有加@Mapper注解或没在启动类加@MapperScan。我一般直接用@MapperScan("com.example.mapper")一次性扫描,省得每个接口加注解。

5.2 前端页面调试的几个技巧

Thymeleaf 页面热更新:Spring Boot 2.x 里,修改 HTML 模板后默认要重启才能生效。本地开发想省事,在 pom.xml 加:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency>

并且在application.properties里配:

spring.thymeleaf.cache=false

这样改完 HTML、Controller,IDEA 里按下 Ctrl+F9(Build Project)就能热更新,不用反复重启。

模板路径引用不要写错:src/main/resources/templates/login.html对应的 Controller 返回值是"login",返回路径不能带templates/前缀,也不能写/login.html。Thymeleaf 的 ViewResolver 会自动拼 prefix 和 suffix。

页面静态资源用th:href:普通 HTML 里写死/css/style.css没问题,但如果项目配了 context-path(比如server.servlet.context-path=/house),所有静态资源路径都要加前缀。我建议本地开发不配 context-path,部署时用 Nginx 做二级路径转发,省去这个麻烦。

5.3 部署到服务器时最容易踩的 3 个坑

坑一:上传图片路径问题

本地是D:/upload/,部署到 Linux 之后不存在这个路径。建议把上传路径做成外部配置:

file.upload-path=/data/house-rent/upload/

并且在 Linux 上执行mkdir -p /data/house-rent/upload,给权限。绝对不要把上传目录放在项目包里面,因为重新打包部署时目录会被覆盖,图片全丢。

坑二:打包成 jar 还是 war

Spring Boot 默认打成 jar 包直接运行,这是最简单的方式。不要折腾 war 包丢到 Tomcat 的 webapps 下,除非你的部署环境强制要求外部 Tomcat。打 jar 包命令:

mvn clean package -DskipTests java -jar house-rent-0.0.1-SNAPSHOT.jar --server.port=8080

用nohup java -jar xxx.jar &后台运行。生产环境建议加--spring.profiles.active=prod区分不同环境配置。

坑三:数据库时区再报错

本地配了serverTimezone=Asia/Shanghai,服务器上也一定要配,否则 MySQL 连接串里的时区问题会再次出现。另外 MySQL 8.x 需要在 my.cnf 里加上:

[mysqld] lower_case_table_names=1

这样表名大小写不敏感,Windows 开发、Linux 部署切换才不会有“表不存在”的诡异问题。

5.4 版本和依赖冲突的排查套路

如果项目里某些依赖版本异常,Spring Boot 启动时会给BeanCreationException或者奇怪的 NoSuchMethodError。排查流程按顺序来:

  1. mvn dependency:tree看依赖树,找有没有同一个包出现两个版本(比如 mybatis 3.5 和 4.0 共存)。
  2. 检查是否混用 Spring Boot 2.x 和 3.x 的 starter。某些网站下载的 pom 可能自动带了 3.x 的传递依赖,和 2.x 冲突。
  3. 确认 JDK 版本:Spring Boot 2.7 支持到 JDK 17,但你本地是 JDK 8 的话,编译时用-source 1.8 -target 1.8即可;Spring Boot 3 直接要求 JDK 17+,装个 JDK 17 就得全套换。
  4. 依赖冲突的终极方案:删C:\Users\你的用户名\.m2\repository下对应的包目录,重新 mvn 依赖拉取(或者 mvn clean + reimport)。很多时候 IDEA 的 Maven 索引没刷新,你以为依赖有问题,其实本地仓库里是脏的。

6. 进阶优化:让这个项目从“能跑”到“像样”

6.1 MinIO 做文件存储:从本地磁盘走向对象存储

房源图片放本地磁盘,好处是简单,坏处是:应用多实例部署时图片不一致、磁盘满了不好管理、服务器迁移麻烦。如果想让项目在答辩上更出彩,可以引入 MinIO(开源对象存储,兼容 S3 协议)。

核心思想:图片上传时直接传到 MinIO 服务器,返回一个 URL 存数据库。本地开发用一个 Docker 起的 MinIO:

docker run -p 9000:9000 -p 9001:9001 \ -e "MINIO_ROOT_USER=admin" \ -e "MINIO_ROOT_PASSWORD=admin123456" \ -v /data/minio/data:/data \ minio/minio server /data --console-address ":9001"

Spring Boot 引入依赖:

<dependency> <groupId>io.minio</groupId> <artifactId>minio</artifactId> <version>8.5.7</version> </dependency>

上传代码核心:

@Autowired private MinioClient minioClient; public String upload(MultipartFile file, String bucket) throws Exception { // 检查 bucket 是否存在,不存在则创建 boolean found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucket).build()); if (!found) { minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucket).build()); } // 生成文件名(时间戳+随机数),避免重名 String filename = System.currentTimeMillis() + "_" + file.getOriginalFilename(); // 上传 minioClient.putObject(PutObjectArgs.builder() .bucket(bucket) .object(filename) .stream(file.getInputStream(), file.getSize(), -1) .contentType(file.getContentType()) .build()); return "/minio/" + bucket + "/" + filename; }

这就解释了为什么热词里有“minio加入到springboot”——因为这个内容确实踩过坑:MinIO 的 SDK 版本新旧 API 差异大,网上搜出来的代码可能互相矛盾。建议直接用官方文档,别用博客里粘贴的老代码。

6.2 Linux 下 Docker 部署 Spring Boot 应用的完整流程

Docker 部署对这个项目而言,最大的价值是“环境一致性”——本地跑的和线上跑的完全一样。

写一个 Dockerfile,放在项目根目录:

FROM openjdk:8-jdk-alpine MAINTAINER yourname VOLUME /tmp COPY house-rent-0.0.1-SNAPSHOT.jar app.jar ENV TZ=Asia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]

打包部署命令:

mvn clean package -DskipTests docker build -t house-rent:1.0 . docker run -d --name house-rent \ -p 8080:8080 \ -v /data/house-rent/upload:/data/upload \ --restart=always \ house-rent:1.0

这里-v挂载目录特别重要:上传的图片必须放在宿主机持久化目录,否则容器删了图片就丢了。

6.3 撰论文时的模块结构建议

如果这个项目是毕设,论文的目录结构(不是代码结构)可以参考:

  • 第1章 绪论(背景、国内外研究现状、研究内容)
  • 第2章 相关技术介绍(Spring Boot、MyBatis、MySQL、Thymeleaf)
  • 第3章 系统分析(可行性分析、需求分析、用例图、功能模块分析)
  • 第4章 系统设计(总体架构、功能模块设计、数据库设计、ER图、表结构)
  • 第5章 系统实现(每个功能模块的界面和核心代码)
  • 第6章 系统测试(测试用例、功能测试、兼容性测试)

论文里的图表比文字重要。ER 图用 MySQL Workbench 的逆向功能生成即可,流程图和用例图可以用 Process On 或者 draw.io 画,别手画。核心代码贴最有代表性的(拦截器、动态 SQL、订单状态流转),不要贴整段 Controller。

一点过来人的经验

整个系统从零开始,一个人做,节奏控制好的话两个礼拜能出完整的第一版。最容易拖慢进度的地方不是代码,而是“不知道怎么改才能跑起来”的调试环节。我的建议是,遇到报错先冷静读最后几行日志,把异常信息原文复制到搜索引擎搜,大部分坑都有人踩过。不要自己瞎改配置,改了三处之后你根本不知道到底是哪里生效的。

另外强烈建议用 Git 做版本管理,每完成一个功能点就 commit 一次。我见过太多人做毕设,代码写崩了之后没法回退,只能全部重来。Git 不是加分项,是这个规模项目的生存刚需。

最后再多说一句:做一个完整系统,真正值钱的不是你用了多新的技术,而是你清楚每张表为什么这么设计、每个拦截器的时间复杂度是什么、每个状态的流转为什么会走到那一步。把这些想明白了,写出来的代码自己心里有底,答辩老师一问一个准,因为你真的做过。

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

Linux远程挂载原理与NFS/CIFS/SSHFS实战指南

1. 这不是“远程桌面”&#xff0c;而是让远程存储变成你电脑里的一个真实文件夹很多人第一次听说“用 mount 命令远程挂载”&#xff0c;第一反应是&#xff1a;“这不就是远程桌面或者网盘同步吗&#xff1f;”——完全不是。mount 的本质&#xff0c;是让远端的文件系统&…

作者头像 李华
网站建设 2026/10/1 18:26:39

Mamba模型环境配置完全指南:从零跑通mamba_ssm与视觉任务集成

mamba这个模型&#xff0c;最近可以说是红得发紫。但很多人第一步就卡住了——“环境配置”四个字&#xff0c;劝退了一大批想复现、想上手用它做实验的人。我去年第一次在项目里引入mamba_ssm&#xff0c;光是环境就折腾了整整两天&#xff0c;编译报错、版本不兼容、显存爆掉…

作者头像 李华
网站建设 2026/10/1 18:24:06

PyQt实时显示海康MV相机画面:GetImageBuffer零拷贝取帧完整实战

做机器视觉项目、需要把海康MV相机画面接到PyQt界面里的人&#xff0c;应该都体会过一种尴尬&#xff1a;海康官方的MVS文档和C示例很全&#xff0c;但Python示例往往只有最基础的枚举设备和单帧采集&#xff0c;真正到了“在PyQt界面里实时预览画面”这一步&#xff0c;就只能…

作者头像 李华
网站建设 2026/10/1 18:23:30

小米万亿参数全模态MoE模型实战解析

1. 这不是又一个“大模型发布”&#xff0c;而是全模态推理范式的分水岭最近刷到“小米开源万亿参数全模态模型”这个标题&#xff0c;很多人第一反应是&#xff1a;又一个蹭热度的营销稿&#xff1f;参数堆到万亿&#xff0c;是不是又在玩数字游戏&#xff1f;MoE、全模态、MI…

作者头像 李华
网站建设 2026/10/1 18:23:23

Agent 算力底座实战:从有效算力、精度取舍到资源配置建模

这一两年&#xff0c;凡是跑过 Agent 生产环境的人&#xff0c;大概都有一种感觉&#xff1a;模型能力越来越强&#xff0c;但真正卡住你的往往不是模型本身&#xff0c;而是底座。我在华为全联接大会 2026 期间跟几个做 Agent 基础设施的同行聊了一整天&#xff0c;大家的共识…

作者头像 李华