Spring Boot租房平台实战:从零构建Java Web业务系统
在实际 Java Web 项目开发中构建一个功能完整、结构清晰的业务系统是检验技术综合运用能力的重要方式。租房平台作为一个典型的业务系统涉及用户管理、房源信息展示、订单处理、支付对接等多个模块非常适合作为 Spring Boot 技术栈的实战项目。它不仅要求开发者掌握 Spring Boot 的快速开发能力还需要整合数据库、前端模板、持久层框架以及处理异步交互等常见问题。本文将围绕一个基于 Spring Boot 的租房平台项目从零开始详细拆解其技术选型、环境搭建、核心模块实现、关键问题排查以及生产环境注意事项旨在为希望深入理解 Spring Boot 项目实战的开发者提供一份可复现、可学习的完整指南。1. 技术栈选型与项目初始化一个典型的 Spring Boot 租房平台项目其技术栈的选择直接决定了开发效率和系统稳定性。基于常见的工程实践我们选择一套成熟、稳定且社区活跃的技术组合。1.1 核心框架与依赖后端以 Spring Boot 2.7.x 作为基础框架这是一个经过长期验证的稳定版本在功能、性能和社区支持上达到了很好的平衡。数据库操作使用 MyBatis-Plus它极大地简化了单表 CRUD 操作同时保留了 MyBatis 的灵活性。前端视图层采用 Thymeleaf 模板引擎它能够与 Spring Boot 无缝集成支持自然模板语法适合快速开发服务端渲染的页面。项目管理工具使用 Maven负责依赖管理和构建流程。以下是项目初始化时pom.xml中的核心依赖配置?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent groupIdcom.example/groupId artifactIdrental-platform/artifactId version1.0.0/version namerental-platform/name description基于Spring Boot的租房平台/description properties java.version11/java.version /properties dependencies !-- Web 支持 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- 模板引擎 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-thymeleaf/artifactId /dependency !-- 数据持久化 -- dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency !-- 数据库驱动 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency !-- 数据校验 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency !-- 常用工具 -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency !-- 测试 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-test/artifactId scopetest/scope /dependency /dependencies build plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId configuration excludes exclude groupIdorg.projectlombok/groupId artifactIdlombok/artifactId /exclude /excludes /configuration /plugin /plugins /build /project关键解释spring-boot-starter-parent统一管理了大量依赖的版本避免了版本冲突。mybatis-plus-boot-starter版本需要与 Spring Boot 2.7.x 兼容3.5.x 是一个稳定选择。lombok设置为optional并在打包插件中排除是为了避免将 Lombok 的编译时依赖打入最终的生产包。Java 版本设置为 11这是一个长期支持版本兼顾了稳定性和现代语言特性。1.2 数据库设计与初始化租房平台的核心数据模型通常包括用户、房源、订单等。这里给出一个简化的数据库表结构设计。用户表 (user)CREATE TABLE user ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键ID, username varchar(50) NOT NULL COMMENT 用户名, password varchar(255) NOT NULL COMMENT 密码加密后, phone varchar(20) DEFAULT NULL COMMENT 手机号, email varchar(100) DEFAULT NULL COMMENT 邮箱, role tinyint(4) NOT NULL DEFAULT 1 COMMENT 角色0-管理员1-普通用户, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;房源表 (house)CREATE TABLE house ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 房源ID, title varchar(200) NOT NULL COMMENT 房源标题, description text COMMENT 房源描述, price decimal(10,2) NOT NULL COMMENT 月租金, address varchar(500) NOT NULL COMMENT 详细地址, city varchar(50) NOT NULL COMMENT 城市, district varchar(50) NOT NULL COMMENT 区域, landlord_id bigint(20) NOT NULL COMMENT 房东用户ID, status tinyint(4) NOT NULL DEFAULT 0 COMMENT 状态0-待审核1-可租2-已租出3-已下架, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id), KEY idx_landlord (landlord_id), KEY idx_city_district (city,district) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT房源信息表;订单表 (order)CREATE TABLE order ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 订单ID, order_no varchar(64) NOT NULL COMMENT 订单编号, house_id bigint(20) NOT NULL COMMENT 房源ID, tenant_id bigint(20) NOT NULL COMMENT 租客用户ID, total_amount decimal(10,2) NOT NULL COMMENT 订单总金额, status tinyint(4) NOT NULL DEFAULT 0 COMMENT 状态0-待支付1-已支付2-已取消3-已完成, start_date date NOT NULL COMMENT 租赁开始日期, end_date date NOT NULL COMMENT 租赁结束日期, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_house (house_id), KEY idx_tenant (tenant_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单表;设计要点主键与索引id使用AUTO_INCREMENT自增主键。为外键字段如landlord_id,house_id,tenant_id和常用查询字段如city,district建立索引以提升查询性能。金额字段price和total_amount使用DECIMAL(10,2)类型避免浮点数精度问题。状态字段使用tinyint表示各种状态并在代码中使用枚举类进行映射提高可读性。时间字段create_time和update_time使用数据库的CURRENT_TIMESTAMP功能自动维护。1.3 项目基础结构使用 IDE如 IntelliJ IDEA或 Spring Initializr 创建项目后建议遵循以下目录结构组织代码这有助于保持清晰的分层架构src/main/java/com/example/rental/ ├── RentalPlatformApplication.java # 启动类 ├── config/ # 配置类 ├── controller/ # 控制层 ├── service/ # 业务逻辑层 │ └── impl/ # 业务逻辑实现层 ├── mapper/ # MyBatis-Plus Mapper接口层 ├── entity/ # 实体类对应数据库表 ├── dto/ # 数据传输对象 ├── vo/ # 视图对象 └── util/ # 工具类 src/main/resources/ ├── application.yml # 主配置文件 ├── static/ # 静态资源CSS, JS, 图片 └── templates/ # Thymeleaf模板文件2. 核心功能模块实现接下来我们将实现用户管理、房源展示和订单处理这三个核心模块。每个模块都遵循 Controller - Service - Mapper 的分层模式。2.1 用户注册与登录用户模块是系统的入口。首先创建用户实体类User并使用 MyBatis-Plus 的注解进行映射。package com.example.rental.entity; import com.baomidou.mybatisplus.annotation.*; import lombok.Data; import java.time.LocalDateTime; Data TableName(user) public class User { TableId(type IdType.AUTO) private Long id; private String username; private String password; // 存储加密后的密码 private String phone; private String email; private Integer role; // 0-管理员1-普通用户 TableField(fill FieldFill.INSERT) private LocalDateTime createTime; TableField(fill FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; }创建对应的 Mapper 接口UserMapper继承 MyBatis-Plus 的BaseMapper即可获得基础的 CRUD 方法。package com.example.rental.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.example.rental.entity.User; import org.apache.ibatis.annotations.Mapper; Mapper public interface UserMapper extends BaseMapperUser { }在 Service 层我们需要处理业务逻辑例如密码加密。这里使用 Spring Security 的BCryptPasswordEncoder它是一个专门用于密码哈希的工具。package com.example.rental.service; import com.example.rental.entity.User; public interface UserService { boolean register(User user); User login(String username, String password); }package com.example.rental.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.example.rental.entity.User; import com.example.rental.mapper.UserMapper; import com.example.rental.service.UserService; import lombok.RequiredArgsConstructor; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; Service RequiredArgsConstructor public class UserServiceImpl implements UserService { private final UserMapper userMapper; private final BCryptPasswordEncoder passwordEncoder new BCryptPasswordEncoder(); Override public boolean register(User user) { // 1. 基础校验 if (user null || !StringUtils.hasText(user.getUsername()) || !StringUtils.hasText(user.getPassword())) { return false; } // 2. 检查用户名是否已存在 LambdaQueryWrapperUser wrapper new LambdaQueryWrapper(); wrapper.eq(User::getUsername, user.getUsername()); if (userMapper.selectCount(wrapper) 0) { return false; } // 3. 密码加密 user.setPassword(passwordEncoder.encode(user.getPassword())); // 4. 设置默认角色普通用户 user.setRole(1); // 5. 插入数据库 return userMapper.insert(user) 0; } Override public User login(String username, String password) { if (!StringUtils.hasText(username) || !StringUtils.hasText(password)) { return null; } // 1. 根据用户名查询用户 LambdaQueryWrapperUser wrapper new LambdaQueryWrapper(); wrapper.eq(User::getUsername, username); User user userMapper.selectOne(wrapper); // 2. 验证用户存在且密码匹配 if (user ! null passwordEncoder.matches(password, user.getPassword())) { // 登录成功注意返回前清空密码 user.setPassword(null); return user; } return null; } }关键点RequiredArgsConstructor是 Lombok 注解为final字段生成构造函数实现依赖注入。BCryptPasswordEncoder每次加密会生成不同的盐值因此必须使用其matches方法进行验证而不是直接比较字符串。查询用户是否存在时使用selectCount比selectOne更轻量。登录成功后返回的用户对象应清除密码字段避免敏感信息泄露。最后在 Controller 层提供 RESTful 接口。为了简化这里使用 Session 来管理登录状态。package com.example.rental.controller; import com.example.rental.entity.User; import com.example.rental.service.UserService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import javax.servlet.http.HttpSession; Controller RequestMapping(/user) RequiredArgsConstructor public class UserController { private final UserService userService; GetMapping(/register) public String registerPage() { return user/register; // 对应 templates/user/register.html } PostMapping(/register) public String doRegister(User user, HttpSession session) { boolean success userService.register(user); if (success) { session.setAttribute(loginUser, userService.login(user.getUsername(), user.getPassword())); return redirect:/; // 注册成功并自动登录跳转到首页 } else { // 可以添加错误信息到Model中这里简化处理 return redirect:/user/register?error1; } } GetMapping(/login) public String loginPage() { return user/login; } PostMapping(/login) public String doLogin(String username, String password, HttpSession session) { User user userService.login(username, password); if (user ! null) { session.setAttribute(loginUser, user); return redirect:/; } else { return redirect:/user/login?error1; } } GetMapping(/logout) public String logout(HttpSession session) { session.invalidate(); return redirect:/; } }2.2 房源信息管理房源管理涉及房东发布房源和管理员审核房源。首先创建房源实体House和对应的HouseMapper。房源服务HouseService需要实现分页查询、条件筛选如按城市、区域、价格区间、发布和状态更新等功能。这里展示分页查询和条件筛选的实现。package com.example.rental.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.example.rental.entity.House; import com.example.rental.mapper.HouseMapper; import com.example.rental.service.HouseService; import com.example.rental.vo.HouseQueryVO; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; Service RequiredArgsConstructor public class HouseServiceImpl implements HouseService { private final HouseMapper houseMapper; Override public IPageHouse queryHousePage(HouseQueryVO queryVO, Long pageNum, Long pageSize) { PageHouse page new Page(pageNum, pageSize); LambdaQueryWrapperHouse wrapper new LambdaQueryWrapper(); // 构建动态查询条件 if (StringUtils.hasText(queryVO.getCity())) { wrapper.eq(House::getCity, queryVO.getCity()); } if (StringUtils.hasText(queryVO.getDistrict())) { wrapper.eq(House::getDistrict, queryVO.getDistrict()); } if (queryVO.getMinPrice() ! null) { wrapper.ge(House::getPrice, queryVO.getMinPrice()); } if (queryVO.getMaxPrice() ! null) { wrapper.le(House::getPrice, queryVO.getMaxPrice()); } // 默认只查询状态为“可租”的房源 wrapper.eq(House::getStatus, 1); // 排序按更新时间倒序 wrapper.orderByDesc(House::getUpdateTime); return houseMapper.selectPage(page, wrapper); } // 其他方法发布房源、更新状态、根据ID查询详情等... }HouseQueryVO是一个视图对象用于接收前端传递的查询条件。package com.example.rental.vo; import lombok.Data; import java.math.BigDecimal; Data public class HouseQueryVO { private String city; private String district; private BigDecimal minPrice; private BigDecimal maxPrice; }在 Controller 中我们将分页查询结果和查询条件传递给 Thymeleaf 模板。package com.example.rental.controller; import com.baomidou.mybatisplus.core.metadata.IPage; import com.example.rental.entity.House; import com.example.rental.service.HouseService; import com.example.rental.vo.HouseQueryVO; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; Controller RequestMapping(/house) RequiredArgsConstructor public class HouseController { private final HouseService houseService; GetMapping(/list) public String list(HouseQueryVO queryVO, RequestParam(defaultValue 1) Long pageNum, RequestParam(defaultValue 10) Long pageSize, Model model) { IPageHouse page houseService.queryHousePage(queryVO, pageNum, pageSize); model.addAttribute(page, page); model.addAttribute(query, queryVO); // 将查询条件回显到页面 return house/list; } }对应的 Thymeleaf 模板templates/house/list.html片段展示如何使用分页数据和条件回显!-- 搜索表单 -- form th:action{/house/list} methodget input typetext namecity th:value${query.city} placeholder城市 input typetext namedistrict th:value${query.district} placeholder区域 input typenumber nameminPrice th:value${query.minPrice} placeholder最低价 input typenumber namemaxPrice th:value${query.maxPrice} placeholder最高价 button typesubmit搜索/button /form !-- 房源列表 -- div th:eachhouse : ${page.records} h3 th:text${house.title}房源标题/h3 p地址span th:text${house.address}/span/p p价格span th:text${#numbers.formatDecimal(house.price, 0, 2)}0/span 元/月/p !-- 更多信息 -- /div !-- 分页控件 -- div span共 span th:text${page.total}0/span 条记录/span a th:href{/house/list(pageNum1, city${query.city}, district${query.district}, minPrice${query.minPrice}, maxPrice${query.maxPrice})}首页/a a th:if${page.hasPrevious()} th:href{/house/list(pageNum${page.current-1}, city${query.city}, district${query.district}, minPrice${query.minPrice}, maxPrice${query.maxPrice})}上一页/a span th:text${page.current}/span/span th:text${page.pages}/span a th:if${page.hasNext()} th:href{/house/list(pageNum${page.current1}, city${query.city}, district${query.district}, minPrice${query.minPrice}, maxPrice${query.maxPrice})}下一页/a a th:href{/house/list(pageNum${page.pages}, city${query.city}, district${query.district}, minPrice${query.minPrice}, maxPrice${query.maxPrice})}末页/a /div2.3 订单创建与状态流转订单模块是业务的核心涉及库存房源状态的锁定、金额计算和状态机管理。首先创建订单实体Order和 Mapper。订单创建是一个事务性操作需要确保房源状态从“可租”变为“已租出”与创建订单这两个步骤的原子性。这里使用 Spring 的Transactional注解来管理事务。package com.example.rental.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.example.rental.entity.House; import com.example.rental.entity.Order; import com.example.rental.entity.User; import com.example.rental.mapper.HouseMapper; import com.example.rental.mapper.OrderMapper; import com.example.rental.service.OrderService; import com.example.rental.vo.OrderCreateVO; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.time.LocalDate; import java.time.temporal.ChronoUnit; import java.util.UUID; Service RequiredArgsConstructor public class OrderServiceImpl implements OrderService { private final OrderMapper orderMapper; private final HouseMapper houseMapper; Override Transactional(rollbackFor Exception.class) public Order createOrder(OrderCreateVO createVO, User tenant) { // 1. 验证房源存在且可租 House house houseMapper.selectById(createVO.getHouseId()); if (house null || house.getStatus() ! 1) { // 状态1代表可租 throw new RuntimeException(房源不存在或不可租); } // 2. 计算租赁天数与总金额 (简化计算按月租金*月数) long months ChronoUnit.MONTHS.between( createVO.getStartDate().withDayOfMonth(1), createVO.getEndDate().withDayOfMonth(1) ) 1; // 至少一个月 BigDecimal totalAmount house.getPrice().multiply(BigDecimal.valueOf(months)); // 3. 生成唯一订单号 String orderNo ORD System.currentTimeMillis() UUID.randomUUID().toString().substring(0, 8).toUpperCase(); // 4. 创建订单对象 Order order new Order(); order.setOrderNo(orderNo); order.setHouseId(createVO.getHouseId()); order.setTenantId(tenant.getId()); order.setTotalAmount(totalAmount); order.setStatus(0); // 待支付 order.setStartDate(createVO.getStartDate()); order.setEndDate(createVO.getEndDate()); // 5. 更新房源状态为“已租出” LambdaUpdateWrapperHouse updateWrapper new LambdaUpdateWrapper(); updateWrapper.eq(House::getId, house.getId()) .eq(House::getStatus, 1) // 乐观锁只有当前状态是可租时才更新 .set(House::getStatus, 2); // 状态2代表已租出 int updateCount houseMapper.update(null, updateWrapper); if (updateCount 0) { // 更新失败可能房源状态已被其他请求修改抛出异常触发事务回滚 throw new RuntimeException(房源状态已变更创建订单失败); } // 6. 插入订单记录 orderMapper.insert(order); return order; } // 其他方法支付成功回调、取消订单、查询订单列表等... }关键点Transactional(rollbackFor Exception.class)确保方法内任何异常包括RuntimeException和Exception都会导致事务回滚保持数据一致性。乐观锁在更新房源状态时使用eq(House::getStatus, 1)作为条件。如果并发请求导致该条件不成立则更新行数为0我们可以感知到并发冲突并回滚订单创建。订单号生成采用“前缀时间戳随机码”的方式生成唯一订单号避免重复和猜测。金额计算这里进行了简化。实际项目中租金计算可能涉及按天计费、服务费、押金等更复杂的逻辑。3. 关键配置与前端交互3.1 应用配置文件application.yml是 Spring Boot 的核心配置文件需要正确配置数据库、MyBatis-Plus 和 Thymeleaf。server: port: 8080 servlet: context-path: /rental spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/rental_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: your_username password: your_password thymeleaf: cache: false # 开发时关闭缓存修改模板立即生效 prefix: classpath:/templates/ suffix: .html mode: HTML encoding: UTF-8 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 控制台打印SQL生产环境应关闭 map-underscore-to-camel-case: true # 自动将下划线命名转为驼峰 global-config: db-config: id-type: auto # 主键自增 logic-delete-field: isDeleted # 全局逻辑删除字段如果表中有此字段 logic-delete-value: 1 # 逻辑已删除值 logic-not-delete-value: 0 # 逻辑未删除值3.2 使用 AJAX 实现异步交互在房源详情页用户点击“立即预订”按钮时通常不希望页面刷新。这时可以使用 AJAX 提交表单数据到后端并根据返回结果动态更新页面。首先在页面中引入 jQuery或其他 JS 库来简化 AJAX 操作。script srchttps://code.jquery.com/jquery-3.6.0.min.js/script然后编写 JavaScript 函数处理预订按钮的点击事件。function submitOrder() { var formData { houseId: $(#houseId).val(), startDate: $(#startDate).val(), endDate: $(#endDate).val() // 其他表单数据... }; $.ajax({ url: /order/create, type: POST, contentType: application/json, data: JSON.stringify(formData), success: function(response) { if (response.code 200) { alert(订单创建成功订单号 response.data.orderNo); // 可以跳转到订单详情页或我的订单列表 window.location.href /order/my; } else { alert(创建失败 response.msg); } }, error: function(xhr) { alert(请求异常请稍后重试); console.error(xhr); } }); }对应的 Controller 方法需要返回统一的 JSON 格式。package com.example.rental.controller; import com.example.rental.entity.Order; import com.example.rental.entity.User; import com.example.rental.service.OrderService; import com.example.rental.vo.OrderCreateVO; import com.example.rental.vo.R; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpSession; RestController RequestMapping(/api/order) RequiredArgsConstructor public class OrderApiController { private final OrderService orderService; PostMapping(/create) public ROrder createOrder(RequestBody OrderCreateVO createVO, HttpSession session) { User loginUser (User) session.getAttribute(loginUser); if (loginUser null) { return R.error(401, 用户未登录); } try { Order order orderService.createOrder(createVO, loginUser); return R.ok(order); } catch (RuntimeException e) { return R.error(500, e.getMessage()); } } }R是一个通用的响应包装类。package com.example.rental.vo; import lombok.Data; Data public class RT { private Integer code; private String msg; private T data; public static T RT ok(T data) { RT r new R(); r.setCode(200); r.setMsg(success); r.setData(data); return r; } public static T RT error(Integer code, String msg) { RT r new R(); r.setCode(code); r.setMsg(msg); return r; } }4. 项目运行、问题排查与生产建议4.1 项目启动与验证启动应用在 IDE 中直接运行RentalPlatformApplication的main方法或在项目根目录下执行mvn spring-boot:run。访问首页打开浏览器访问http://localhost:8080/rental/。如果配置了server.servlet.context-path不要遗漏。验证数据库连接观察控制台日志如果没有报数据库连接错误并且能看到 MyBatis-Plus 的 banner 信息通常表示连接成功。功能验证按顺序测试用户注册、登录、浏览房源、创建订单等核心流程。4.2 常见问题与排查路径在开发过程中你可能会遇到以下典型问题问题现象可能原因检查方式处理建议启动时报Failed to configure a DataSource数据库配置错误或驱动未加载1. 检查application.yml中的url,username,password。2. 检查pom.xml中 MySQL 驱动依赖是否存在。3. 检查数据库服务是否启动。修正配置确保依赖和数据库服务正常。访问页面报Whitelabel Error Page(404)请求路径错误或 Controller 未扫描到1. 检查浏览器地址栏路径是否与RequestMapping匹配。2. 检查启动类是否在 Controller 的上级包中。3. 检查Controller或RestController注解是否添加。使用 IDE 的“Find Usages”功能查看映射路径确保启动类位置正确。Thymeleaf 模板语法不生效页面显示原始表达式模板引擎配置错误或缓存问题1. 检查spring.thymeleaf.prefix和suffix配置。2. 开发环境设置spring.thymeleaf.cachefalse。3. 检查 HTML 文件头是否有xmlns:thhttp://www.thymeleaf.org。确认配置清理浏览器缓存或尝试重启应用。MyBatis-Plus 查询不到数据或字段映射失败实体类字段与数据库列名不匹配1. 检查TableName,TableId,TableField注解是否正确。2. 开启 SQL 日志 (log-impl: StdOutImpl)查看实际执行的 SQL 和参数。3. 确认数据库表中字段是否存在。根据日志调整实体类注解或数据库表结构。事务Transactional不生效方法非 public或异常被捕获未抛出1. 确保方法是public。2. 确保异常传播到了事务管理器默认只回滚RuntimeException和Error。3. 检查是否在同一个类内部调用事务方法代理失效。使用Transactional(rollbackFor Exception.class)或将事务方法放到另一个 Service 中调用。AJAX 请求返回 403 (Forbidden)未处理 CSRF 令牌或 Spring Security 拦截1. 如果引入了 Spring Security默认会开启 CSRF 防护。2. 检查请求头Content-Type是否为application/json。对于纯 API 请求可以在 Security 配置中禁用 CSRF 对特定路径的防护或手动添加 CSRF Token。4.3 生产环境部署与优化建议将学习项目推向生产环境需要考虑更多因素配置外置化将数据库密码、第三方密钥等敏感信息从application.yml移到环境变量或配置中心。可以使用spring.config.import支持外部配置文件。# application-prod.yml spring: datasource: url: ${DB_URL} username: ${DB_USER} password: ${DB_PASS}通过-Dspring.profiles.activeprod激活生产配置。连接池与性能默认的 HikariCP 连接池性能很好但需要根据数据库负载调整参数。spring: datasource: hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000日志与监控使用 Logback 或 Log4j2 替换默认的 Logback配置按级别和大小滚动日志文件。集成 Spring Boot Actuator 暴露健康检查、指标等信息方便接入 Prometheus 和 Grafana。关键业务操作如创建订单、支付回调必须打印业务日志包含唯一流水号便于链路追踪。安全性加固密码加密必须使用BCryptPasswordEncoder等强哈希算法切勿使用 MD5 或 SHA-1。SQL 注入坚持使用 MyBatis-Plus 的 Wrapper 或#{}参数绑定杜绝字符串拼接 SQL。XSS 防护Thymeleaf 默认会对 HTML 内容进行转义。对于富文本内容需要进行安全的 HTML 过滤。越权访问在 Controller 方法中必须校验当前登录用户是否有权操作目标资源例如只能取消自己的订单。打包与部署使用mvn clean package -DskipTests打包生成可执行的 JAR 文件。考虑使用 Docker 容器化部署确保环境一致性。编写DockerfileFROM openjdk:11-jre-slim COPY target/rental-platform-1.0.0.jar app.jar ENTRYPOINT [java, -jar, /app.jar]在容器编排或服务器上配置好 JVM 内存参数如-Xmx512m。这个基于 Spring Boot 的租房平台项目涵盖了从技术选型、数据库设计、分层开发、事务管理到前端交互的完整流程。真正理解一个项目不仅要能跑通 Demo更要清楚每一步背后的设计考量、潜在的问题以及如何让它变得更健壮。建议你在实现基础功能后尝试加入图片上传、在线支付、消息通知、缓存如 Redis等更复杂的模块并思考如何设计更完善的权限系统和后台管理功能这会让你的工程能力得到更全面的锻炼。