做同城租房系统这活儿说实话看着简单真正要把房源、订单、用户、支付这几条线拧在一起还是挺考验人的。尤其是用 Java 这套技术栈从零到一个能跑起来的完整项目里面的坑和细节不亲手走一遍根本意识不到。这篇文章我就把自己折腾这个“Java 同城服务同城租房系统”完整源码项目的思路、核心模块拆解、数据库设计、部署过程还有踩过的坑从头到尾捋一遍。不管你是准备做毕业设计、课程设计还是想在公司内部快速搭一个租房业务的原型系统这篇文章都能给你一条可以直接落地的参考路径。1. 项目背景与需求拆解1.1 同城租房系统到底在解决什么问题先说点实在的。同城租房这个场景本质上就是“本地化”的房源信息撮合。跟链家、贝壳这种全国性平台不一样同城系统更强调“本地服务闭环”——房东发房源、租客找房子、双方在线沟通、预约看房、签约下单最好还能把水电费、合同管理这些细碎的事情一并处理掉。这种系统做出来之后可以直接被本地中介公司、公寓运营商或者校园周边的房东群体拿去用也可以作为智慧社区项目里的一个子模块嵌进去。我之前在做一个本地生活服务类项目的时候客户就是做长租公寓的手底下百来套房源之前全靠Excel和微信群管理后来实在撑不住了才决定上系统。需求其实就一句话让租客能在线看房下单让管家能在后台管房源管合同让老板能一眼看到每个月有多少单子在走、多少房源空着。落到技术上这就是一个典型的信息管理系统加上交易流程。1.2 核心角色与功能需求梳理任何系统先捋角色角色定了权限和功能边界也就清楚了。同城租房系统里我一般分成三类角色租客端C端用户注册登录、实名认证可选按区域、价格、户型、朝向等条件搜索房源查看房源详情、图片、配套设施、地图位置在线预约看房、收藏房源提交租房订单、在线签约、支付押金和租金查看合同、报修、退租申请房东/管家端B端用户房源发布、编辑、上下架管理房态管理已租、待租、维护中查看预约、处理订单、确认签约合同生成与管理租金账单生成与催收记录房源数据统计管理后台Admin端用户管理、房东资质审核房源审核图片、信息合规性订单监管、纠纷处理系统配置城市区域、地铁线路、租金规则数据报表房源量、订单量、成交率、区域热度这三块功能全部做出来确实工作量大所以实际做项目的时候最好的方式是先做核心链路用户能发房源、能搜房源、能下订单后台能审核能统计。先把这条主链路跑通再谈其他锦上添花的功能。1.3 这个项目适合谁来看如果说你正在准备 Java 相关的面试或者学校里的课程设计、毕业设计这个项目是非常好的练手素材。原因有三第一它覆盖了 CRUD 之外的真实业务逻辑尤其是订单状态流转和并发控制第二它包含了文件上传、地图定位、权限管理等 Java Web 开发里的高频考点第三它属于“看得见、摸得着”的应用系统讲起来比纯管理系统有画面感。如果你有 Java 基础但一直停留在跟着教程写 demo 的阶段我强烈建议用这个项目作为你的第一个完整实战。它能帮你把 Spring Boot、MyBatis、Redis、MySQL 这些东西真正串起来而不是每个框架单独学一遍。2. 核心技术选型与架构设计2.1 为什么选 Java 这套技术栈选 Java 做同城租房系统不是因为它是最时髦的而是因为它最稳。同城租房这种业务涉及资金交易、合同数据、用户隐私稳定性要求比一般的展示类网站高得多。Java 的生态成熟Spring Boot 让开发效率大幅提升MyBatis 灵活控制 SQLMySQL 承载核心业务数据Redis 扛住热点数据和并发流量这一套组合在中小型项目中几乎是无脑选。具体版本上我个人习惯用 Spring Boot 2.7.x 系列稳定而且兼容性好。JDK 就用 1.8 或者 11别一上来追新用 JDK 17很多老一点的依赖会出兼容性问题。后面部署的时候踩过的坑我会细说。2.2 前后端分离还是传统单体这个问题早几年还会纠结一下现在基本不用想。如果前端人手够直接前后端分离Vue 3 Element Plus 或者 React Ant Design后端纯出接口。如果项目工期紧、人手少那就用 Thymeleaf 服务端渲染一套代码全搞定。我自己这个项目采用的是前后端分离前端用 Vue 3 Vite Element Plus后端 Spring Boot 提供 RESTful API。这么做的好处是后期做小程序端或者 App 端的时候后端接口可以直接复用不需要重新写业务逻辑。当然前后端分离也意味着你要多处理一些事跨域配置、接口文档维护、前端打包部署、Token 鉴权。这些都跑通之后整个开发流程还是很舒服的。2.3 完整技术栈清单与对比为了让你看得更清楚我把这个项目里用到的技术栈整理成一张表层级技术选型主要用途前端框架Vue 3 Vite Element Plus用户端和管理后台界面后端框架Spring Boot 2.7业务接口、自动配置、依赖管理ORM 框架MyBatis-Plus数据库操作、分页查询、逻辑删除数据库MySQL 8.0核心业务数据存储缓存Redis Spring Cache房源热点数据缓存、验证码存储鉴权JWT Spring Security用户身份认证与权限控制文件存储本地磁盘 Nginx 静态映射房源图片、合同文件上传接口文档Knife4j接口调试与文档展示构建工具Maven依赖管理与项目打包这套技术栈的好处就是全部都是当前 Java 后端的主流方案网上资料多出问题容易找到解决方案。我踩过的很多坑其实都是别人踩烂了的一搜就有答案。2.4 项目目录结构规划项目拿到手第一步不是写代码而是先规划好目录结构。一个好的分包结构能让你后续维护起来神清气爽。我习惯这么分house-rental/ ├── house-admin/ # 后台管理端前端Vue3 ├── house-web/ # 用户端前端Vue3 ├── house-server/ # 后端主服务Spring Boot │ ├── common/ # 通用工具、常量、异常处理 │ ├── config/ # 配置类跨域、Redis、MyBatis等 │ ├── controller/ # 接口层 │ ├── service/ # 业务逻辑层 │ ├── mapper/ # 数据访问层 │ ├── entity/ # 数据库实体类 │ ├── dto/ # 前后端交互的数据对象 │ └── utils/ # 工具类JWT、日期等 └── sql/ # 数据库初始化脚本分包的原则就是按技术层次分而不是按功能模块分。这样代码结构稳定不管未来加什么业务功能都能在对应的包里找到位置。3. 数据库设计与核心表结构3.1 业务表设计思路数据库设计是这种系统最见功力的部分。同城租房系统的数据模型核心就是“人-房-单”三角关系。用户围绕这三个核心对象延伸出各种子表。我第一版设计的时候犯过一个错误就是把所有字段塞进一张大表结果后期加需求的时候改表结构改到怀疑人生。后来重构的时候老老实实按业务拆分每个业务域独立成表。核心表清单如下表名说明核心字段t_user用户表id, username, password, phone, role, statust_house房源表id, landlord_id, title, cover_img, price, area, address, statust_house_image房源图片表id, house_id, img_url, sortt_subway地铁线路表id, line_name, station_name, lat, lngt_appointment看房预约表id, user_id, house_id, appoint_time, statust_order订单表id, order_no, user_id, house_id, amount, status, create_timet_contract合同表id, order_id, rent_month, start_date, end_date, file_urlt_favorite收藏表id, user_id, house_id, create_timet_house_audit房源审核记录表id, house_id, admin_id, audit_status, reason3.2 房源表与订单表关键设计细节房源表是整个系统的信息中枢字段设计直接决定搜索筛选能不能做好。我的建议是房源的基础属性字段比如标题、描述、价格、面积、户型、朝向这些直接放在房源主表里查询效率高不用关联查询。而动态属性比如配套设施家电、宽带、电梯可以用一个 JSON 字段处理MyBatis-Plus 支持 JacksonTypeHandler直接映射成 Java 对象。CREATE TABLE t_house ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 房源ID, landlord_id bigint(20) NOT NULL COMMENT 房东用户ID, title varchar(100) NOT NULL COMMENT 房源标题, cover_img varchar(255) DEFAULT NULL COMMENT 封面图URL, price decimal(10,2) NOT NULL COMMENT 月租金(元), deposit decimal(10,2) DEFAULT NULL COMMENT 押金(元), area decimal(10,2) DEFAULT NULL COMMENT 面积(㎡), room_num tinyint(4) DEFAULT NULL COMMENT 几室, hall_num tinyint(4) DEFAULT NULL COMMENT 几厅, toilet_num tinyint(4) DEFAULT NULL COMMENT 几卫, orientation varchar(10) DEFAULT NULL COMMENT 朝向东/南/西/北等, floor varchar(20) DEFAULT NULL COMMENT 所在楼层如5/18, decoration varchar(20) DEFAULT NULL COMMENT 装修情况精装/简装/毛坯, rent_type varchar(20) DEFAULT NULL COMMENT 出租方式整租/合租, province varchar(50) DEFAULT NULL COMMENT 省, city varchar(50) DEFAULT NULL COMMENT 市, district varchar(50) DEFAULT NULL COMMENT 区/县, address varchar(255) DEFAULT NULL COMMENT 详细地址, longitude decimal(10,7) DEFAULT NULL COMMENT 经度, latitude decimal(10,7) DEFAULT NULL COMMENT 纬度, facilities json DEFAULT NULL COMMENT 配套设施JSON, description text COMMENT 房源描述, status tinyint(4) NOT NULL DEFAULT 0 COMMENT 状态0待审核 1已上架 2已下架 3已出租, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_city_district (city, district), KEY idx_price (price), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT房源信息表;订单表的核心是状态机。我常用的状态设计是1 待支付 - 2 已支付/待签约 - 3 已签约/生效中 - 4 已到期 - 5 已退租 - 6 已取消。每一步流转都有对应的触发条件。订单号一定要用业务单号别直接用自增 ID我一般会生成类似HO 日期 随机数 的格式。3.3 索引设计与查询优化同城租房系统最核心的查询场景就是“按城市区域价格范围搜房源”。这种查询条件一旦数据量上来全表扫描必挂。所以索引设计要围绕搜索条件来建。上面 SQL 里我建了三个索引城市和区的联合索引、价格索引、状态索引。实际项目里如果搜索条件更多还可以考虑把商圈、朝向、出租方式加进去做联合索引。另外要注意一个细节MySQL 8.0 的 JSON 字段不要直接建索引否则查询效率极低。我的做法是如果设施配套需要作为筛选条件就拆成独立的关联表比如房屋配置表一个房源对应多条配置记录查询的时候用 JOIN 或者 EXISTS。4. 核心功能模块实现与实战操作4.1 用户注册登录与 JWT 鉴权用户这块看似简单但安全细节不能马虎。密码存储一定用 BCrypt 加密别用 MD5MD5 彩虹表一撞就出来了。Spring Security 加 JWT 是 Java 后端做鉴权的标配组合流程是这样的用户登录成功后后端生成一个携带用户信息的 JWT Token前端存到本地之后每次请求都带上 Authorization 请求头后端通过过滤器校验 Token 有效性。Service public class LoginService { Autowired private UserMapper userMapper; Autowired private StringRedisTemplate stringRedisTemplate; public LoginResponse login(LoginRequest request) { // 1. 校验验证码 String code stringRedisTemplate.opsForValue().get(captcha: request.getPhone()); if (!request.getCaptcha().equalsIgnoreCase(code)) { throw new BizException(验证码错误或已过期); } // 2. 查询用户校验密码 User user userMapper.selectByPhone(request.getPhone()); if (user null || !BCrypt.checkpw(request.getPassword(), user.getPassword())) { throw new BizException(手机号或密码错误); } // 3. 生成 JWT把用户ID和角色放进去 String token JwtUtil.createToken(user.getId(), user.getRole()); return new LoginResponse(token, user); } }这里有个坑要提醒一下JWT 是无状态的后端没法主动让它失效。所以如果遇到用户修改密码或者被封号的情况就需要引入 Redis 黑名单机制。我一般会在 Redis 里维护一个token:blacklist:{jti}的键退出登录或者封号的时候把这个键加进去过滤器里先判断是否在黑名单内。4.2 房源发布与图片上传房源发布是 B 端用户用得最多、也最容易出问题的模块。图片上传看似简单实际上有各种小坑。我用的方案是前端把图片传给后端后端把图片文件保存到本地磁盘然后通过 Nginx 做静态资源映射对外暴露一个可访问的图片地址。这样有一个好处就是前端能直接通过 URL 访问图片不需要后端再走一次流式读取。PostMapping(/api/upload) public ResultString upload(MultipartFile file) { // 1. 校验文件类型和大小 String originalFilename file.getOriginalFilename(); String suffix originalFilename.substring(originalFilename.lastIndexOf(.)); if (!Arrays.asList(.jpg, .jpeg, .png, .gif).contains(suffix.toLowerCase())) { return Result.fail(仅支持jpg、png、gif格式图片); } if (file.getSize() 5 * 1024 * 1024) { return Result.fail(图片大小不能超过5MB); } // 2. 生成唯一文件名避免重名覆盖 String newFileName UUID.randomUUID().toString().replace(-, ) suffix; // 3. 按日期分目录存储 String datePath new SimpleDateFormat(yyyyMMdd).format(new Date()); File dir new File(UPLOAD_PATH datePath); if (!dir.exists()) { dir.mkdirs(); } file.transferTo(new File(dir.getAbsolutePath() File.separator newFileName)); // 4. 返回可访问的URL地址 String fileUrl /upload/ datePath / newFileName; return Result.success(fileUrl); }图片上传这里有几个细节值得注意文件名一定要用 UUID 重新生成不要直接用用户上传的原始文件名随意命名的文件会因为重名互相覆盖甚至存在路径穿越的安全隐患。图片目录按日期分文件夹方便后续做定时清理避免量大了之后单目录文件数过多。如果用的是前后端分离开发模式本地开发环境访问上传的文件需要配置虚拟路径映射不然前端跨域拿不到图片。4.3 房源搜索与筛选逻辑搜索功能做得好不好直接决定用户体验。同城租房的筛选条件基本固定区域、地铁线、价格区间、户型、朝向、出租方式。用 MyBatis-Plus 写动态 SQL条件拼接用 LambdaQueryWrapper配合 Page 分页实现很简单。public PageResultHouseVO searchHouse(HouseQuery query) { PageHouse page new Page(query.getPageNum(), query.getPageSize()); LambdaQueryWrapperHouse 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()) .eq(query.getOrientation() ! null, House::getOrientation, query.getOrientation()) .eq(House::getStatus, 1) // 只看已上架房源 .orderByDesc(House::getCreateTime); PageHouse result houseMapper.selectPage(page, wrapper); // 转换为VO对象填充房东信息和图片列表 return convertToVO(result); }如果数据量很大这种简单查询扛不住就需要引入 Elasticsearch 做全文检索和复杂筛选。中小型项目初期用 MySQL 完全够等到房源量超过十万条再考虑引入 ES 也不迟。避免过度设计。4.4 下单流程与库存扣减租房下单和电商下单有一个很重要的区别一个房源在同一时间段内只能被成功下单一次。这就涉及并发控制的问题了是很多面试官喜欢问的点。如果两个用户同时看中同一套房源同时提交订单都成功了那这个系统就是有问题的。常规的做法有三种数据库乐观锁在房源表加一个 version 字段下单时先查版本号更新的时候校验版本号是否一致。Redis 分布式锁对房源 ID 加锁同一时间只有一个线程能处理这间房源的订单。用 SETNX 命令实现注意设置过期时间防止死锁。数据库唯一约束在订单表添加(house_id, status, rent_period)唯一约束保证同一时间只有一条生效中的订单。我实际项目里用的是“Redis 分布式锁 数据库状态校验”双保险。先拿到锁再查一次房源状态确认是“待出租”才创建订单然后立刻把房源状态改为“已被预定”。这套流程下来并发问题基本不存在。public Order createOrder(CreateOrderRequest request) { // 使用Redis分布式锁防止并发下单 String lockKey house:lock: request.getHouseId(); boolean locked redisLock.tryLock(lockKey, 5, TimeUnit.SECONDS); if (!locked) { throw new BizException(系统繁忙请稍后重试); } try { // 二次校验房源状态 House house houseMapper.selectById(request.getHouseId()); if (house null || house.getStatus() ! 1) { throw new BizException(房源不存在或已被预定); } // 创建订单手动生成订单号 Order order new Order(); order.setOrderNo(generateOrderNo()); order.setUserId(request.getUserId()); order.setHouseId(request.getHouseId()); order.setAmount(house.getPrice().multiply(new BigDecimal(request.getRentMonths()))); order.setStatus(1); // 待支付 orderMapper.insert(order); // 更新房源状态为已预定 houseMapper.updateStatus(house.getId(), 4); return order; } finally { redisLock.unlock(lockKey); } }这里还要提一个细节生成订单号的时候如果用时间戳加随机数在高并发下还是可能重复。我习惯用 Redis 自增序列拼接上业务前缀比如HO yyyyMMddHHmmss 5位自增序列这样既优雅又可以保证唯一性。4.5 验证码与短信服务对接同城租房系统注册登录基本都依赖手机号验证码。真实生产环境里你要去对接阿里云短信或者腾讯云短信但本地开发或者做演示的时候最方便的做法是弄一个通用的短信服务接口在配置里切换“真实发送”和“本地模拟”两种模式。本地模拟模式就是在后端把验证码打到日志里同时存到 Redis有效期 5 分钟。前端输入验证码后直接发送给后端校验。这样你不需要买短信包也能把完整流程跑通。等要上线了再去短信服务商那边申请签名和模板把实现类切换一下就行。另外验证码还有两个容易被忽略的限制一分钟内不能重复发送同一个手机号一天内单个手机号最多只能发 5 条。这些限制用 Redis 过期时间就能轻松实现。5. 项目部署与环境搭建5.1 开发环境配置清单整个项目需要准备的环境如下照着装就行组件版本要求安装说明JDK1.8 或 11配置 JAVA_HOME 环境变量Maven3.6配置阿里云镜像加速依赖下载MySQL8.0创建数据库并执行初始化脚本Redis5.0本地开发直接默认配置Node.js16用于前端项目安装依赖和打包Nginx1.20部署时做静态资源代理和数据转发如果你准备用宝塔面板部署在云服务器上那 LNMP 环境一键装好比自己编译省事多了。但有一点要特别注意MySQL 默认的字符集是 utf8不支持中文 emoji 表情你在新建数据库的时候要手动指定 utf8mb4否则用户昵称带着表情符写入数据库直接报错。5.2 后端打包与配置详解后端项目开发完成后部署第一步就是打包。用 Maven 打包跳过测试mvn clean package -DskipTests打包完成后target 目录下会生成一个house-server.jar文件这个就是可以直接运行的产物。上传到服务器执行java -jar house-server.jar默认情况下Spring Boot 是用内置 Tomcat 的端口是 8080。生产环境里我建议你用--spring.profiles.activeprod来加载不同环境的配置文件。我的application-prod.yml配置风格是这样的server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/house_rental?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/Shanghai username: root password: 你的密码 redis: host: localhost port: 6379 servlet: multipart: max-file-size: 5MB max-request-size: 20MB # 自定义配置文件上传路径 file: upload-path: /data/www/house-rental/upload/有一个配置项我要单独拎出来说max-file-size和max-request-size。Spring Boot 默认上传文件大小限制是 1MB你如果不改前端一传稍微大一点的图片就会报 500 错误。这是我当年第一次部署的时候栽过跟头的地方排查了半天才发现是默认限制。5.3 前端打包与 Nginx 配置前端项目打包之前先要确认接口地址配对了。Vue 项目里我习惯使用环境变量文件。开发环境用.env.development生产环境用.env.production。# .env.production VITE_API_BASE_URL/api打包命令很简单npm install npm run build打包完成后 dist 目录就是纯静态文件上传到服务器后用 Nginx 指向这个目录就行。Nginx 配置可以参考下面这种写法server { listen 80; server_name your-domain.com; # 前端静态资源 location / { root /data/www/house-rental/dist; try_files $uri $uri/ /index.html; } # 后端接口反向代理 location /api/ { proxy_pass http://localhost:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # 上传的图片访问映射 location /upload/ { alias /data/www/house-rental/upload/; } }这里try_files指令是前端路由的核心所有非静态文件的请求都指向index.html这样 Vue Router 的 history 模式就不会出现刷新页面 404 的问题了。6. 常见问题与排查技巧实录6.1 数据库连接报错Public Key Retrieval is not allowed这个错误我在本地开发时遇到过MySQL 8.0 默认使用 caching_sha2_password 认证插件JDBC URL 里需要加上allowPublicKeyRetrievaltrueuseSSLfalse才能正常连接。解决办法很简单数据库连接串改成jdbc:mysql://localhost:3306/house_rental?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrueuseSSLfalse6.2 前后端分离跨域问题前端访问后端接口一定会遇到跨域问题。解决方式有两种后端加 CORS 配置或者前端用 Vite 的 proxy 代理。开发阶段用 Vite proxy 更方便// vite.config.js export default defineConfig({ server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true, } } } })生产环境就不存在跨域问题了因为都是走同一个域名的 NginxNginx 已经把/api开头的请求转发到后端了。所以生产环境你只需要保证 Nginx 配置没问题就行。6.3 MyBatis-Plus 分页查询不生效如果你发现分页查询返回的 total 一直是 0或者分页根本没生效大概率是你忘记配置 MyBatis-Plus 的分页插件了。分页插件不是自动装配的必须手动注册这是 MyBatis-Plus 的经典大坑Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }6.4 JWT Token 过期与刷新机制用户登录后 Token 默认有效期我设为 24 小时但是用户不可能隔一天就重新登录一次。更合理的做法是使用双 Token 机制Access Token 有效期 2 小时Refresh Token 有效期 7 天。Access Token 过期后前端拿到 401 状态码自动携带 Refresh Token 请求刷新接口获取新的 Access Token。这样用户基本无感知。我一开始偷懒只做了单 Token结果运营同学反馈“为什么每天早上都要重新登录”后来才老老实实加了刷新机制。做这种系统体验细节很重要。6.5 支付回调与订单状态不一致严格来说同城租房系统的支付环节是接微信支付或支付宝支付的。本地开发联调的时候没有真实支付回调最容易出现的问题就是“用户已经支付了但订单状态还是待支付”。我的建议是开发阶段做一个支付模拟接口对接假的支付网关后端收到模拟回调后更新订单状态。这样业务主链路是完整的等上线再切真实支付只换实现不影响业务代码。7. 从项目到简历的项目亮点提炼7.1 技术亮点怎么说如果你打算拿这个项目去面试不要上来就说“我做了个租房系统”那太普通了。你要学会提炼技术亮点基于 Redis 分布式锁解决并发抢单问题保证房源订单数据一致性使用 JWT Spring Security 实现无状态认证结合 Redis 黑名单实现 Token 的主动失效通过 MyBatis-Plus 动态查询条件实现多维度房源筛选并通过索引优化提升查询性能采用前后端分离开发模式接口通过 Nginx 反向代理部署支持高并发静态资源分发使用 Knife4j 自动生成接口文档规范前后端联调流程7.2 业务细节怎么体现面试官更关心的是你对业务的理解。比如他会问你“下单时如果两个人同时抢同一间房怎么办”这时候你把你用 Redis 锁的实现思路讲清楚比背一堆八股文有用得多。再比如他会问你“房源搜索很慢怎么办”你从 SQL 索引、缓存、分库分表、引入 ES 这几个层次逐步拆解他能看到你是真的思考过问题。我个人面过不少人很多人项目写一堆但一问细节就答不出来。像“订单状态怎么流转的”“如果你是架构师会怎么优化”这类问题能流利说清楚的人很少。这套租房系统如果你自己从零写一遍这些问题你都能回答得很有底气。8. 写在最后的项目心得做这个项目前前后后大概花了三周左右的时间中间经历了数据库设计改版、并发问题排查、部署环境踩坑整个过程下来最大的体会就是完整的项目跟教程里的 demo 完全是两回事教程只会教你正常流程怎么走但真实系统要处理的都是那些“边缘情况”和异常分支。我最想强调的一点是如果你准备拿这个项目练手或者交作业一定要亲手把数据库表建一遍、把核心代码敲一遍、把部署流程走一遍不要只是把源码 clone 下来跑通就算完事。遇到问题去查、去解决的过程才是真正长本事的过程。项目源码只是一个结果背后的思考逻辑和解决问题的方法才是这个项目能给你带来的最大价值。最后再分享一个小技巧这个项目的代码里不管写哪一块功能都顺手把关键注释写清楚接口的入参出参也列出示例。等到需求变更或者隔了几个月再回来看代码的时候你会发现这些当时看起来“浪费时间”的操作其实帮你省了太多力气。代码是写给人看的顺手让下一个接手的人少骂你几句也算是程序员之间的善意吧。