首页 / 资讯中心 / 文章详情

同城租房系统全栈实战:Spring Boot+MyBatis-Plus+Redis核心设计与实现

同城租房系统全栈实战:Spring Boot+MyBatis-Plus+Redis核心设计与实现 ★ FEATURED ARTICLE
1. 项目概览与核心需求拆解1.1 这个系统到底在解决什么问题先说结论同城租房系统本质上就是一个地理位置 信息撮合 交易流程三合一的业务系统。它跟普通商品商城最大的区别在于——房子是强地域属性商品用户打开系统的第一诉求不是随便看看而是我所在的城市、我所在的区、我附近三公里内有没有合适的房源。所以你在做这类项目时核心关键词不是 CRUD而是同城二字如何落地。我前前后后做过几版租房类项目包括校园短租、写字楼日租、小区长租。最终的通用形态基本一致用户端负责浏览、检索、收藏、预约、下单房东端负责房源发布、上下架、订单确认管理后台负责审核、数据统计、举报处理。再往外扩还可以接地图找房、VR看房、在线签约、租金分期但那是商业化的第二阶段。第一阶段的完整项目把核心闭环跑通就够了。这套系统适合谁两类人。第一类是刚学完 Java 基础、想做课程设计或毕业设计的同学需要一个结构清晰、代码规范、能写进简历的完整项目第二类是打算进入中小型外包公司或自研团队做业务开发的初级工程师想搞清楚一个真实业务系统从前端接口到后端表结构是怎么组织起来的。我用的技术栈是 Java 后端最常见的组合Spring Boot MyBatis-Plus MySQL Redis Vue前后端分离。这套组合不是最炫的但一定是就业市场最认的。它好在哪Spring Boot 让你少写配置MyBatis-Plus 让你少写 SQLRedis 帮你扛住房源详情的热点访问Vue 让前端页面能快速迭代。整套体系学一遍基本能覆盖市面上大部分中小型 Java 业务的日常开发需求。1.2 技术选型背后的真实考量先看一张技术清单都是我实际用过的版本技术组件选型版本用途说明JDK1.8企业级项目的最稳选择Stream 和 Lambda 足够用Spring Boot2.7.x稳定版本第三方组件兼容性最好MyBatis-Plus3.5.x单表 CRUD 免写 SQL复杂查询自己写 XMLMySQL5.7 或 8.0主数据库存储业务数据Redis6.x缓存热点房源、验证码、登录 TokenFastDFS / 本地存储取决于环境房源图片存储Vue 2.x Element UI前端框架管理后台和用户端页面有人会问为什么不用 Spring Cloud 微服务我的答案很直接——没必要。同城租房系统的业务复杂度远没有达到需要微服务的程度单体应用就是最合理的架构。你硬拆成用户服务、房源服务、订单服务三个模块不仅部署麻烦事务一致性也会成为新的痛点。单体架构 模块分包是这类业务最舒服的形态。再解释几个关键选型的为什么MyBatis-Plus 而不是 MyBatisMP 提供的 LambdaQueryWrapper 让条件查询的代码量少了一半以上。比如按价格区间、户型、地铁线筛选房源以前用 MyBatis 要写一堆if标签用 MP 一行lambdaQuery就能解决。Redis 缓存房源详情房源详情页是典型的读多写少场景一个热门小区的房源一天可能被看几百次但房源信息可能一周才改一次。把详情缓存到 RedisQPS 能从几百提升到几千。JWT 而不是 Session前后端分离项目天然适合 Token 认证JWT 无状态、跨域友好、不需要 Redis 存 Session。但要注意过期时间别设太长我一般设 24 小时。这套选型下来整个项目在 2C4G 的云服务器上跑得毫无压力成本也低特别适合学生项目演示或者小团队自用。2. 功能模块划分与数据库设计精讲2.1 三个端的功能边界划分同城租房系统的功能范围我建议严格按用户端、房东端、管理后台三条线来拆。很多初学者容易犯的毛病是把所有功能塞进一张表、一个 Controller 里最后代码乱成一锅粥。功能边界清楚了表结构和代码结构自然就清楚了。用户端C 端手机号验证码登录首页Banner 位 热门区域 精选房源推荐房源搜索按城市、区域、地铁线、价格、户型、租赁方式整租/合租筛选房源详情轮播图、基本信息、配套设施、地图位置、房东信息收藏/取消收藏房源在线预约看房 / 直接下单我的订单查看预约记录、订单状态个人中心修改头像、昵称、手机号房东端B 端手机号登录可以复用用户表用户身份用role字段区分房源发布填写标题、描述、价格、户型、面积、所在区域、详细地址、地图坐标房源图片上传支持多图房源管理上架、下架、编辑、删除订单管理查看用户预约和订单确认或拒绝收益统计简单版本订单数量、累计成交金额管理后台Admin 端管理员登录独立管理表房源审核用户发布的房源需要后台审核通过才能上架用户管理禁用/启用用户举报管理处理违规房源数据看板注册用户数、房源总数、订单总数、成交金额功能范围定下来之后工作量其实就清晰了。用户端是重头约占 50%房东端约 30%管理后台最简单约 20%。如果你是自己写完整项目建议按这个优先级分配时间。2.2 核心数据表设计一张表都不能少数据库设计是整个项目的地基。表结构设计得好后面所有业务开发都是拼积木设计得烂后面每一个查询都在打补丁。我把自己经过多次迭代后比较稳定的表结构分享出来你直接照着建就行。第一张表用户表userCREATE TABLE user ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 用户ID, phone varchar(11) NOT NULL COMMENT 手机号, password varchar(100) DEFAULT NULL COMMENT 密码, nickname varchar(50) DEFAULT NULL COMMENT 昵称, avatar varchar(255) DEFAULT NULL COMMENT 头像地址, role tinyint(1) DEFAULT 1 COMMENT 角色1普通用户 2房东, status tinyint(1) DEFAULT 1 COMMENT 状态1正常 0禁用, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_phone (phone) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;这里有个小细节房东和用户共用一张表靠role字段区分。绝大多数中小项目都这么做省去维护两套登录体系的麻烦。create_time和update_time是每张表都应该有的MyBatis-Plus 的MetaObjectHandler可以自动填充这两个字段不用每次都手动 set。第二张表房源表house这是整个系统的核心CREATE TABLE house ( id bigint(20) NOT NULL AUTO_INCREMENT, owner_id bigint(20) NOT NULL COMMENT 房东用户ID, title varchar(100) NOT NULL COMMENT 房源标题, description text COMMENT 房源描述, price decimal(10,2) NOT NULL COMMENT 月租金, deposit decimal(10,2) DEFAULT 0.00 COMMENT 押金, rent_type tinyint(1) DEFAULT 1 COMMENT 租赁方式1整租 2合租, house_type varchar(20) DEFAULT NULL COMMENT 户型如2室1厅, area int(11) DEFAULT NULL COMMENT 面积平, city varchar(50) NOT NULL COMMENT 城市, district varchar(50) NOT NULL COMMENT 区域/区县, address varchar(255) DEFAULT NULL COMMENT 详细地址, longitude decimal(10,6) DEFAULT NULL COMMENT 经度, latitude decimal(10,6) DEFAULT NULL COMMENT 纬度, cover_image varchar(255) DEFAULT NULL COMMENT 封面图, status tinyint(1) DEFAULT 0 COMMENT 状态0待审核 1上架 2下架 3审核驳回, view_count int(11) DEFAULT 0 COMMENT 浏览量, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_city_district (city, district), KEY idx_owner_id (owner_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT房源表;房源表里最容易忽略的是经纬度。没有经纬度同城范围搜索就只能按行政区划筛选根本实现不了附近房源这种体验。我在后面功能实现部分会详细讲基于经纬度的查询怎么算。cover_image存的是封面图路径房源轮播图单独放一张表因为一个房源对应多张图片不能把多张图塞进一个字段里用逗号拼那样维护起来太痛苦。第三张表房源图片表house_imageCREATE TABLE house_image ( id bigint(20) NOT NULL AUTO_INCREMENT, house_id bigint(20) NOT NULL COMMENT 房源ID, image_url varchar(255) NOT NULL COMMENT 图片地址, sort int(11) DEFAULT 0 COMMENT 排序号, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_house_id (house_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT房源图片表;第四张表订单表rent_orderCREATE TABLE rent_order ( id bigint(20) NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 订单编号, user_id bigint(20) NOT NULL COMMENT 下单用户ID, house_id bigint(20) NOT NULL COMMENT 房源ID, owner_id bigint(20) NOT NULL COMMENT 房东ID, rent_type tinyint(1) DEFAULT 1 COMMENT 租赁方式, month_price decimal(10,2) NOT NULL COMMENT 月租金, deposit decimal(10,2) DEFAULT 0.00 COMMENT 押金, start_date date DEFAULT NULL COMMENT 租期开始日, end_date date DEFAULT NULL COMMENT 租期结束日, status tinyint(1) DEFAULT 0 COMMENT 状态0待确认 1已确认 2已拒绝 3已取消 4已完成, remark varchar(255) DEFAULT NULL COMMENT 备注, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_user_id (user_id), KEY idx_house_id (house_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT租赁订单表;订单表的status字段是一个状态机从待确认到已确认再到已完成中间还有已拒绝和已取消两个终止态。这个状态流转必须前后端严格对齐我在第 3 部分会专门展开讲实现时的坑。第五张表收藏表favorite这个表结构最简单就三个核心字段用户ID、房源ID、创建时间用户ID和房源ID加唯一索引防止重复收藏。第六张表管理员表admin_user字段跟用户表类似但不需要 role单独维护一套管理员账号体系避免和普通用户混在一起导致权限漏洞。第七张表Banner 表banner管理后台投放首页轮播图字段包括标题、图片地址、跳转链接、排序号、状态。这七张表就是整个同城租房系统的全部数据基础。再多加功能可以扩展表白但核心就这么几张。表结构设计完你会发现业务逻辑已经完成了一半——因为所有接口无非就是对这几张表做增删改查组合。2.3 两个最容易翻车的设计细节第一个同城范围搜索的实现方案用户搜索同城房源到底是搜城市还是搜附近我建议两种都支持默认按城市 区域筛选同时提供附近房源功能通过经纬度计算距离。核心 SQL 逻辑是当用户传了经纬度时查询距离用户坐标指定范围内的房源。MySQL 里可以用一种简单的方式实现范围过滤SELECT * FROM house WHERE latitude BETWEEN #{lat} - #{range} AND #{lat} #{range} AND longitude BETWEEN #{lng} - #{range} AND #{lng} #{range} AND status 1其中range是预计算的距离偏移量。简单说纬度每 0.01 度大约对应 1.1 公里所以如果你要搜 3 公里范围range就取3 / 110.574。这个方案虽然不算高精度但胜在简单对于城市级别的租房搜索完全够用。想要精确距离排序的话可以用 Haversine 公式在 SQL 里算距离但那个计算量大数据量过万后性能就会下降。我第一版用 Haversine 做过离我最近排序实测 5000 条数据时响应时间已经明显变慢。后来改成先粗筛再按距离排序性能立刻好转。这也是一个典型的原理正确但工程上要妥协的例子。第二个房源上下架的状态机房源状态从0待审核-1上架-2下架还有3审核驳回。这个流转容易出错的地方在于用户下单前必须校验房源是不是已上架状态。如果你不做校验就会出现用户下一单然后房东把房源下架了订单状态突变两边体验都不好。我的处理方式是电商系统常用的方案下单时校验房源状态下单成功后给房源加一个订单中存在的标记或者直接把房源置为临时锁定状态。更简单的方案是房源详情里加一个是否可租字段有未完成订单时不可再下单。在订单状态流转到已完成或已取消后恢复房源可租状态。3. 关键功能实现与实操细节3.1 项目目录结构与启动流程拿到源码或者自己搭建项目首先要理解目录结构为什么这么组织。我用的还是 Spring Boot 最经典的 mvc 分层但增加了一点业务模块化的味道src/main/java/com/example/rental/ ├── config/ │ ├── WebMvcConfig.java # 拦截器、静态资源配置 │ ├── RedisConfig.java # Redis 序列化配置 │ └── MybatisPlusConfig.java # 分页插件、自动填充 ├── controller/ │ ├── user/ # 用户端接口 │ ├── landlord/ # 房东端接口 │ └── admin/ # 管理后台接口 ├── service/ │ ├── UserService.java │ ├── HouseService.java │ └── OrderService.java ├── mapper/ │ ├── UserMapper.java │ ├── HouseMapper.java │ └── OrderMapper.java ├── entity/ │ ├── User.java │ ├── House.java │ └── Order.java ├── dto/ # 请求参数对象 ├── vo/ # 返回视图对象 ├── common/ │ ├── Result.java # 统一返回结构 │ ├── PageResult.java # 分页返回结构 │ └── BusinessException.java # 业务异常 └── util/ ├── JwtUtil.java # Token 工具 └── DistanceUtil.java # 距离计算工具这个结构好在哪里Controller 按三端拆包后面写权限拦截器的时候可以直接通过包路径匹配管理后台的接口只允许管理员访问。entity对应数据库表dto接收前端参数vo返回给前端展示三者分离避免把数据库字段直接暴露给前端。启动流程我总结为五步第一安装 JDK 1.8、MySQL 5.7、Redis把项目里的application.yml改成本地环境的值主要是数据库账号密码和 Redis 地址。第二新建数据库rental_db导入sql/init.sql初始化脚本所有表结构和基础数据比如管理员账号、Banner 默认数据就都建好了。第三启动 Redis再启动 Spring Boot 项目看到Started RentalApplication日志说明后端启动成功。第四后端启动后接口可以先用 Swagger (springfox 或 knife4j) 调试项目里集成了 knife4j 的话直接访问http://localhost:8080/doc.html看接口文档。第五前端项目是用 Vue CLI 构建的npm install之后npm run serve启动访问前端页面前后端通过 nginx 代理或者后端配置跨域来连接。3.2 登录认证JWT 方案的具体实现I先说登录接口的核心逻辑。用户输入手机号和验证码或者密码后端校验通过后生成 Token 返回给前端前端把 Token 存在本地之后所有请求在请求头加Authorization: Bearer token后端通过拦截器解析并放行。生成 Token 的代码用 jjwt 库核心代码public String generateToken(Long userId, String role) { Date now new Date(); Date expireDate new Date(now.getTime() 24 * 60 * 60 * 1000); // 24小时过期 return Jwts.builder() .setHeaderParam(alg, HS256) .setSubject(String.valueOf(userId)) .claim(role, role) .setIssuedAt(now) .setExpiration(expireDate) .signWith(SignatureAlgorithm.HS256, secretKey) .compact(); }这里有几个细节要注意secretKey要放在配置文件里不要硬编码在代码中也不要存到数据库。Token 过期时间 24 小时比较合理太短用户要频繁登录太长有安全问题。claim(role)是方便后面做权限判断管理员请求需要判断role是否等于admin。拦截器实现只有一个核心方法preHandle从请求头取 Token解析成功就放行解析失败返回 401。放行后还需要把用户 ID 填到ThreadLocal里后面 Controller 要拿当前登录用户时直接UserContext.get()就行。我在出过的问题就是这个ThreadLocal没有清理。因为 web 容器用了线程池线程处理完一个请求后会被复用如果不清除 ThreadLocal下一个请求就有可能取到上一个用户的信息这是严重的安全漏洞。解决方式是在拦截器的afterCompletion方法里显式调用UserContext.clear()。3.3 房源发布与图片上传一套完整闭环房东发布房源是整个系统最复杂的流程。前端表单要传房源基本信息还得多图上传。我实现时把两类信息分开先调图片上传接口拿到图片 URL 列表最后提交表单时把图片列表和房源信息一起传给后端。这样可以避免用户填到一半网络断掉导致数据残缺。图片上传的核心逻辑PostMapping(/upload) public ResultString upload(RequestParam(file) MultipartFile file) { if (file.isEmpty()) { throw new BusinessException(上传文件不能为空); } String originalFilename file.getOriginalFilename(); String suffix originalFilename.substring(originalFilename.lastIndexOf(.)); String fileName UUID.randomUUID().toString().replace(-, ) suffix; // 按日期分目录存储避免单目录文件过多 String datePath new SimpleDateFormat(yyyyMMdd).format(new Date()); File dir new File(uploadPath / datePath); if (!dir.exists()) { dir.mkdirs(); } file.transferTo(new File(dir.getAbsolutePath() / fileName)); return Result.success(/upload/ datePath / fileName); }这里最容易踩的坑有两个文件名必须用 UUID 重命名不能用用户上传的原始文件名。一是防止中文文件名引起的乱码问题二是防止重名覆盖三是有安全因素恶意文件上传攻击。图片格式要校验。只允许 jpg、png、jpeg、webp 这几种后缀大小最好限制在 5MB 以内在后端用file.getSize()判断不要只靠前端校验因为前端校验可以被绕过。上传后的图片访问路径需要配置静态资源映射。Spring Boot 默认的static目录只认 classpath 下的而上传的文件在磁盘上所以必须加一个虚拟路径映射Configuration public class WebMvcConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceLocations(file: uploadPath /); } }这样一个完整的图片上传闭环就通了上传返回 URL前端用 URL 展示图片后端通过映射提供访问。3.4 同城房源检索与列表分页房源列表是流量最大的接口也是性能优化重点。用户端的筛选条件有且不限于这些城市、区域下拉选择租赁方式整租/合租价格区间minPrice/maxPrice户型一居室、两居室...朝向、面积关键词搜索标题模糊搜附近经纬度范围用 MyBatis-Plus 组合这些条件代码比手写 SQL 简洁很多public PageResultHouseVO searchHouse(HouseQueryDTO query) { LambdaQueryWrapperHouse wrapper new LambdaQueryWrapper(); wrapper.eq(StringUtils.isNotBlank(query.getCity()), House::getCity, query.getCity()) .eq(StringUtils.isNotBlank(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()) .eq(House::getStatus, 1) .orderByDesc(House::getCreateTime); PageHouse page houseMapper.selectPage(new Page(query.getPageNum(), query.getPageSize()), wrapper); // 转VO填充封面图、房东昵称等信息 return buildPageResult(page); }如果你做过真实项目就会发现列表查询只是开始真正的坑在于列表的数据组装。比如列表页展示房源卡片除了房源表的基本字段还要显示房东昵称、封面图如果不在 house 表里、是否已收藏前端需要展示红心状态。如果用传统的遍历方式查出 10 条房源后再逐条查房东信息、收藏状态会触发经典的 N1 查询问题。我的处理方式是两个批量查询和一次查完再组装。具体来说拿到 10 条房源 ID 后一次性查房东信息WHERE id IN (房主ID列表)一次性查收藏状态WHERE user_id? AND house_id IN (房源ID列表)然后用 Map 把数据组装起来完全避免循环查库。这个优化能把接口响应从 800ms 压到 100ms 以内属于列表接口必备技巧。3.5 订单状态机的完整实现订单系统是整个项目业务逻辑最重的一环。我在前面表设计时列了status字段的五个值0待确认、1已确认、2已拒绝、3已取消、4已完成。状态流转规则是用户下单 - 初始状态 0房东确认 - 0 变为 1房东拒绝 - 0 变为 2终止状态用户取消 - 0 变为 3只能取消待确认状态的订单确认后不能随便取消租期结束 - 1 变为 4可以定时任务自动处理或者用户手动确认完成实现时最关键的是状态校验。如果用户在下单后调用取消接口必须判断当前状态是不是 0否则报当前订单状态不允许操作。另一个容易忽略的点是下单时要重新查一遍房源的价格。为什么因为前端展示的价格和用户下单之间有时间差房东可能在这期间改过价格。如果你用前端传来的价格入库就存在价格不一致的风险。正确做法是前端只传房源 ID后端查出最新房源价格再用这个价格创建订单。3.6 Redis 缓存房源详情的两种方式房源详情页通过GET /house/{id}访问这个接口在高并发下是最容易被打爆的。我的缓存思路分成两级第一级是查详情时先查 Redis如果命中直接返回如果未命中查数据库然后写 Redis 并设置过期时间。简单版就是用 StringRedisTemplate 存 JSONGetMapping(/{id}) public ResultHouseDetailVO getHouseDetail(PathVariable Long id) { String cacheKey house:detail: id; String cacheValue redisTemplate.opsForValue().get(cacheKey); if (cacheValue ! null) { return Result.success(JSON.parseObject(cacheValue, HouseDetailVO.class)); } // 查数据库、组装详情 VO HouseDetailVO detailVO houseService.getDetail(id); redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(detailVO), 30, TimeUnit.MINUTES); return Result.success(detailVO); }设计细节是缓存过期时间 30 分钟这个时长要权衡太短了缓存命中率低太长则房源信息变更后用户看到的是旧数据。第二级是缓存更新策略。房东修改房源信息后删除对应的缓存 key让下次请求重新加载。这个更新即淘汰模式是最容易实现的比更新后重建缓存Cache Aside 的 Write Through 模式简单也避免了数据不一致的问题。还有一个环节是浏览量view_count的统计。如果每次访问都UPDATE house SET view_count view_count 1 WHERE id ?在高并发下会频繁锁行。简单方案是先把浏览量写 RedisINCR 命令然后定时批量同步到数据库。这个方案的工程量不大但对数据库压力的改善非常明显。4. 部署上线与常见问题排查4.1 从本地到云服务器的完整部署流程项目开发完成只是第一步真正完整项目实现还包括部署上线。我分享一下踩过很多坑后总结的部署流程这套流程对单机小项目完全够用。第一步准备一台云服务器2核4G 配置足够。操作系统选 CentOS 7/8 或 Ubuntu 20.04按自己的熟悉程度来。安装 JDK 和 MySQL# CentOS 环境示例 yum install -y java-1.8.0-openjdk yum install -y mysql-server systemctl start mysqld第二步安装 Redis用默认配置启动即可注意不要忘了设密码。第三步本地项目先打 jar 包。在 IDEA 右侧 Maven 栏执行package如果是多模块项目先 install 公共模块再 package 入口模块。打出来的 jar 包在target/目录下。第四步把数据库初始化脚本sql/init.sql传到服务器执行导入mysql -uroot -p EOF source /root/rental.sql; EOF第五步上传 jar 包用 nohup 方式后台启动nohup java -jar rental-0.0.1-SNAPSHOT.jar --spring.profiles.activeprod /root/logs/rental.log 21 为什么用nohup ... 因为这样 ssh 断开后进程依然在运行。日志文件要单独指定路径后面排查问题全靠它。第六步启动前端。前端项目打包生成dist/目录然后用 Nginx 托管同时配置反向代理把/api开头的请求转发到后端接口。Nginx 配置核心是server { listen 80; server_name your_domain_or_ip; # 前端静态文件 location / { root /usr/share/nginx/html/dist; index index.html; try_files $uri $uri/ /index.html; # 解决 Vue 路由刷新404问题 } # 后端接口反向代理 location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # 上传图片访问 location /upload/ { proxy_pass http://127.0.0.1:8080; } }try_files那一行尤其重要。Vue 如果用 history 路由模式刷新二级页面会报 404没有这行配置就是白屏或者 404。4.2 常见问题速查表我把实际开发中高频遇到、排查起来特别浪费时间的问题整理成一个速查表这些问题在技术群里被反复问过非常典型问题现象根本原因解决方案前端请求接口跨域报错后端未配置跨域在 Spring Boot 增加 CorsFilter 或 WebMvcConfigurer 的 addCorsMappings分页查不到数据但 SQL 没问题MyBatis-Plus 分页插件没注册在 MybatisPlusConfig 中注册 PaginationInnerInterceptor数据库时间比本地时间早 8 小时JDBC 连接未配置 serverTimezone在数据库 URL 加serverTimezoneAsia/Shanghai图片上传后访问 404静态资源映射未配置或路径不对检查 WebMvcConfig 的 addResourceHandlers确认磁盘目录存在列表接口特别慢循环查库导致 N1 问题改为批量查询WHERE IN后内存组装上传大文件失败Tomcat 默认 max-file-size 太小需要在 application.yml 中设置spring.servlet.multipart.max-file-size和max-request-sizeJWT 解析报错或 Token 一直过期服务器时间与客户端时间不一致校时服务器时间和生成/解析 Token 的 secretKey 是否一致这里面最值得说的是 MyBatis-Plus 分页插件。很多人引入了 MP但忘注册分页拦截器结果selectPage方法返回的total是 0 或者数据不分页。这不是 bug是框架机制问题——MP 的分页依赖数据库方言拦截器没注册等于没有分页功能。跨域问题也值得重点说一次。前后端分离开发时前端跑在 8081 端口后端跑在 8080 端口浏览器默认拦截跨域请求。最简单的解决方式是后端加一个全局跨域配置Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }注意allowedOriginPatterns在 Spring Boot 2.4 版本里替代了allowedOrigins(*)如果你用了allowedOrigins(*)配合allowCredentials(true)会报Cannot allow credentials for wildcard origin的错误。4.3 线上环境的性能与安全加固最后聊聊把项目真正跑起来之后必须做的几件事。这些是我在项目直接被外部扫描和攻击之后换来的教训。MySQL 连接配置加参数数据库连接池一般用默认配置但线上流量上来后maximum-pool-size要适当调大。另一个容易忽略的参数是connection-timeout和idle-timeout默认值有时会导致连接池满了后出现获取连接超时异常。我的常用配置spring: datasource: hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 30000 idle-timeout: 600000Redis 必须设密码和绑定内网这个太容易被忽略了。很多人把 Redis 跑在公网上没设密码结果被黑客扫到直接挖矿。Redis 配置里至少要加这两行requirepass your-strong-password bind 127.0.0.1如果 Redis 和应用在同一台服务器绑定回环地址就够了不需要对外暴露。接口防刷租房系统的查询接口和验证码接口容易被脚本刷。至少要在验证码接口上做防刷限流比如同一个手机号一分钟最多发一条验证码。用 Redis 的INCR和过期时间就能实现最简单的限流String phoneKey sms:limit: phone; Long count redisTemplate.opsForValue().increment(phoneKey); if (count 1) { redisTemplate.expire(phoneKey, 60, TimeUnit.SECONDS); // 首次计数时设过期时间 } if (count 1) { throw new BusinessException(发送太频繁请稍后再试); }这里有个细节只有当INCR返回 1 时才设置过期时间否则每次调用都设置过期时间的话60 秒窗口永远滚不完等于没限流。敏感字段加密存储用户的密码在数据库里必须加密。BCrypt 是目前比较推荐的方式Spring Security 自带BCryptPasswordEncoder即使用户设置的密码是弱口令加密后也能防爆力破解。千万别用 MD5 直接存——市面上所有常用密码的 MD5 值都已经能被彩虹表查到这是学生项目最容易暴露的安全漏洞。同城租房系统的密码存储、Token 过期、管理员权限校验这三块是面试官最喜欢深挖的点也是真实线上环境最容易出问题的点。5. 项目后续扩展与我的实践体会到这里一个完整的同城租房系统后端已经成型。但说实话完整项目永远是一个相对概念。我做这类项目的经验是初始版本先把核心闭环跑通——发布房源到用户下单再到订单完成——这个链路通了项目就可以上线演示了。如果你有余力有几个扩展方向非常加分。第一是地图找房接入高德或百度地图 SDK把房源经纬度直接在地图上标注出来用户拖动地图按可视范围筛选房源体验直接上升一个档次。第二是即时通信增加房源详情页和房东在线咨询的入口可以说服房东成交率会明显提升。第三是租金计算根据不同的付款方式付三押一、半年付、年付自动计算总金额。第四是管理后台的数据统计图表用 ECharts 画出房源分布、订单趋势这个在答辩和简历项目里都非常亮眼。我在实际操作中体会最深的一点是这类同城 交易的项目最大的价值不是技术本身而是对业务边界和数据状态的管理能力。房源的上下架、订单的状态流转、用户的身份角色每一条都是业务规则的具象化。技术方案会过时但理解业务建模、把模糊需求翻译成数据结构和接口逻辑的能力才是这类项目真正锻炼你的地方。哪怕将来你不做租房系统换成外卖、二手交易、同城拼车打底的思路一模一样。这套源码的完整实现本质上就是在给你一套同城业务系统的通用解法。
阅读完成 · 觉得有帮助?
咨询建站