ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

短租日租房源管理系统:数据库设计与Spring Boot实现

2026/9/3 6:17:37 拓冰建站 浏览量
短租日租房源管理系统:数据库设计与Spring Boot实现 在实际的小区房源运营场景里业主想把自己的一套空置房同时做日租、周租、月租最常见的动作是在业主群或社区平台上发一条类似“碧桂园山湖城观澜1街10座22022室1厅1厨1卫2阳台2床1.8m1.5m可日租/周租/月租”的房源消息。这条消息看似只是几行文字但从系统角度看里面每一段都是可结构化的核心字段小区、楼栋门牌、户型、阳台数量、床宽、可租周期。如果只有一两套房用表格和手工登记还能维持一旦房源数量增加或者同一个时间有多个咨询就会出现房源状态不透明、价格算错、日期重叠、押金和订单状态混乱等问题。这篇文章围绕“短租日租房源管理”这一场景从数据库设计、Spring Boot 接口实现、可用性判断、价格计算到生产环境落地讲清楚一套轻量短租管理系统应该怎么做。整个方案不涉及复杂框架重点在于字段建模、日期边界约定、订单状态设计和并发冲突处理。对于正在做社区房源运营、民宿后台、短租房源管理系统的开发人员可以直接参考其中的表结构和代码思路。1. 先把房源信息拆成结构化字段再谈系统设计最早接触短租需求时最容易犯的错是直接做订单表。还没有把“一套房子”本身的结构定义清楚就开始写预订接口结果后面扩展房间、床型、价格策略都很痛苦。正确顺序是先理解一条房源消息到底包含哪些业务要素再把它们映射到表结构里。1.1 一条房源消息里包含哪些核心字段以“碧桂园山湖城观澜1街10座22022室1厅1厨1卫2阳台2床1.8m1.5m可日租/周租/月租”为例可以拆成下面这些字段。信息点示例值类型用途小区名称碧桂园山湖城字符串用于聚合同一个小区房源街区/门牌观澜1街10座2202字符串标识唯一物理地址户型2室1厅1厨1卫2阳台字符串或枚举用于展示和筛选卧室数量2数值方便按“几室”筛选床配置1.8m 1.5m子表对应两张床影响可住人数可租方式日租/周租/月租枚举决定计价逻辑状态上架/下架/已预订状态字段控制房源是否可展示如果只用一列存“2室1厅1厨1卫2阳台”展示没问题但以后要统计“几室房源数量”“哪些房源有两张床”就会非常难写 SQL。因此在系统设计时要把展示型文本和结构化字段分开。展示型字段可以保存原始文本但还必须同时保存结构化字段。1.2 短租业务的完整流程房源发布上线后业务会经历以下几个阶段咨询阶段租客看到房源询问日期和价格。预订阶段租客确认日期系统锁定房源防止重复预订。入住阶段确认租客身份交付钥匙或门锁密码。在住阶段可能产生保洁、维修、加床等增值服务。退房阶段检查房间退还押金生成账单。结算阶段统计总收入、入住率、每套房产出。每个阶段都要有对应的状态字段和操作记录。尤其是“预订到锁定”这个过程必须考虑并发问题否则两个租客同时看中同一个晚上系统可能同时放行最后导致超卖。1.3 为什么不能只靠一个订单表如果一个订单表同时保存房源信息、房型、床型、价格和租客信息开发早期确实很快但后期会变得很难维护。问题主要体现在三方面房源信息被复制到订单里后修改房源描述不会自动同步。无法查询某套房在某天是否可用因为可用性散落在订单里。价格策略变化后历史订单和未来订单难以区分。推荐做法是独立维护房源、房间、价格、订单四张表。订单只关联房源 ID 和房间 ID不冗余整段房源文本。2. 数据库设计短租系统的核心表结构本章以 MySQL 8.0 为例设计一套适合短租日租房源场景的表结构。实际项目中表名、字段名可以根据团队习惯调整但核心关系不建议省略。2.1 技术选型和环境准备后端建议使用 Java 8 或 Java 11 都可以框架使用 Spring Boot 2.7 或 3.x但 3.x 对 JDK 版本有要求落地前要先确认。数据库使用 MySQL 8.0JDBC 驱动使用mysql-connector-j。如果项目需要使用缓存可以接入 Redis 缓存报价和当日房态如果只是学习环境先不接 Redis用数据库查询即可。需要准备的环境如下组件建议版本用途JDK8 或 11运行 Spring BootMaven3.6 以上管理依赖MySQL8.0持久化数据Spring Boot2.7.x提供 Web 接口MyBatis-Plus 或 Spring Data JPA按团队习惯操作数据库说明本文的代码示例主要用于说明核心逻辑实际项目要结合自己的包名、表名和持久层框架进行调整。2.2 房源表 apartment房源表保存一套房子的基本信息。这个表的粒度是“一套物理房屋”不是“一个房间”。CREATE TABLE apartment ( id BIGINT PRIMARY KEY AUTO_INCREMENT, community_name VARCHAR(64) NOT NULL COMMENT 小区名称, block_address VARCHAR(128) NOT NULL COMMENT 街区或楼栋, room_no VARCHAR(64) NOT NULL COMMENT 门牌号, house_type_code VARCHAR(32) NOT NULL COMMENT 户型代码如 TWO_BEDROOM, house_type_text VARCHAR(128) NOT NULL COMMENT 户型展示文本如 2室1厅1厨1卫2阳台, bedroom_count INT NOT NULL DEFAULT 1 COMMENT 卧室数量, living_room_count INT NOT NULL DEFAULT 1, bathroom_count INT NOT NULL DEFAULT 1, balcony_count INT NOT NULL DEFAULT 0, area_sqm DECIMAL(6,2) DEFAULT NULL COMMENT 建筑面积单位平方米, status TINYINT NOT NULL DEFAULT 0 COMMENT 0-下架 1-上架, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_community_address (community_name, block_address, room_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT房源基本信息表;这里把“小区名称”“街区”“门牌号”拆开是为了后续按小区或街区筛选时更方便。house_type_code和house_type_text分别保存代码和展示文本避免以后排序列举型字段时只能依赖字符串模糊匹配。2.3 床位表 room_bed“2床1.8m1.5m”这句话在数据库里不能存成一列而应该拆成两行。一张 1.8m 床一张 1.5m 床分别记录在哪个房间、可睡几人。CREATE TABLE room_bed ( id BIGINT PRIMARY KEY AUTO_INCREMENT, apartment_id BIGINT NOT NULL, room_name VARCHAR(32) NOT NULL COMMENT 卧室名称如主卧、次卧, bed_size DECIMAL(4,1) NOT NULL COMMENT 床宽单位米如1.8、1.5, sleep_count INT NOT NULL DEFAULT 1 COMMENT 该床可住人数, status TINYINT NOT NULL DEFAULT 1 COMMENT 1-启用 0-停用, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_apartment_id (apartment_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT房间床位表;需要注意这里没有单独建“房间表”而是直接在床位表里用room_name表达“主卧”“次卧”。对于小型短租系统这种设计够用。如果以后需要按房间维度管理空调、窗帘、保洁记录就应该再加一张apartment_room表让room_bed挂在具体房间下。2.4 价格表 price_config日租、周租、月租是三种不同的计价模式。价格表的核心设计点是价格必须关联到房源并且要支持历史价格变化。CREATE TABLE price_config ( id BIGINT PRIMARY KEY AUTO_INCREMENT, apartment_id BIGINT NOT NULL, lease_mode VARCHAR(16) NOT NULL COMMENT DAILY/WEEKLY/MONTHLY, price DECIMAL(10,2) NOT NULL COMMENT 价格, unit VARCHAR(8) NOT NULL COMMENT 计价单位如元/晚、元/周、元/月, effective_date DATE NOT NULL COMMENT 生效日期, expire_date DATE NOT NULL DEFAULT 2099-12-31 COMMENT 失效日期, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_apartment_date (apartment_id, effective_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT价格配置表;价格表采用“有效期”模式而不是只保存当前价格这样才能支持旺季调价、淡季促销。查询时需要找到满足effective_date 入住日期且expire_date 入住日期的价格。2.5 订单表 stay_order订单表是短租系统最核心的一张表。它记录哪个租客、在哪段时间、以什么模式租了哪套房。CREATE TABLE stay_order ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(64) NOT NULL COMMENT 订单号, apartment_id BIGINT NOT NULL, guest_name VARCHAR(64) NOT NULL COMMENT 租客姓名, guest_phone VARCHAR(32) NOT NULL COMMENT 联系电话, id_card_no VARCHAR(32) DEFAULT NULL COMMENT 身份证号按当地法规要求采集, start_date DATE NOT NULL COMMENT 入住日期, end_date DATE NOT NULL COMMENT 退房日期业务上不包含当天, lease_mode VARCHAR(16) NOT NULL COMMENT DAILY/WEEKLY/MONTHLY, amount DECIMAL(10,2) NOT NULL COMMENT 订单总金额, status TINYINT NOT NULL COMMENT 0-待支付 1-已支付 2-已入住 3-已完成 4-已取消, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_order_no (order_no), KEY idx_apartment_date (apartment_id, start_date, end_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT入住订单表;代码里要明确一个日期约定start_date表示入住当天end_date表示退房当天且退房当天不占用房源。也就是说一个订单的占用区间是“左闭右开”包含开始日期不包含结束日期。后面所有冲突查询都基于这个约定。3. 核心代码实现房源、床型与租赁模式表结构确定后再用 Java 实现基础类型和业务逻辑。这里重点看三种核心代码枚举、价格计算和可用性判断。3.1 用枚举管理租赁模式不要用字符串传递租赁模式否则容易出现“DAILY”“daily”“日租”不一致的情况。用枚举可以统一。public enum LeaseMode { DAILY(DAILY, 日租), WEEKLY(WEEKLY, 周租), MONTHLY(MONTHLY, 月租); private final String code; private final String desc; LeaseMode(String code, String desc) { this.code code; this.desc desc; } public String getCode() { return code; } public String getDesc() { return desc; } public static LeaseMode fromCode(String code) { for (LeaseMode mode : values()) { if (mode.code.equals(code)) { return mode; } } throw new IllegalArgumentException(Unsupported lease mode: code); } }在接口入参校验时直接调用LeaseMode.fromCode就能拦截无效值。如果前端传了“周租”后端可以先做映射也可以要求前端传标准代码WEEKLY。3.2 户型与床位建模apartment表里已经有bedroom_count、house_type_text但床的明细在room_bed表里。查询一个房源时需要把床位列表一起返回供前端展示“1.8m 1.5m”这样的文案。一个简单的 DTO 如下public class ApartmentDetailVO { private Long apartmentId; private String communityName; private String blockAddress; private String roomNo; private String houseTypeText; private ListBedInfo bedList; public static class BedInfo { private String roomName; private BigDecimal bedSize; private Integer sleepCount; // getter/setter 省略 } }查询时先查apartment主表再根据apartmentId查room_bed。如果房源固定是 2 室 1 厅 1 卫 2 阳台这套查询逻辑足够如果要支持真实房间维度就对apartment_room做同样操作。3.3 价格计算要区分不同租赁模式价格计算是最容易出 bug 的地方。日租按晚数乘单价周租按周数乘周价月租按月数乘月价。问题是“不足完整周期”怎么处理必须提前定义清楚。下面这段代码用于说明核心思路import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDate; import java.time.temporal.ChronoUnit; public class PriceCalculator { public BigDecimal calculateAmount(LeaseMode mode, LocalDate startDate, LocalDate endDate, BigDecimal dailyPrice, BigDecimal weeklyPrice, BigDecimal monthlyPrice) { long days ChronoUnit.DAYS.between(startDate, endDate); if (days 0) { throw new IllegalArgumentException(endDate must be after startDate); } if (mode LeaseMode.DAILY) { return dailyPrice.multiply(BigDecimal.valueOf(days)) .setScale(2, RoundingMode.HALF_UP); } if (mode LeaseMode.WEEKLY) { long weeks days / 7; long remainDays days % 7; BigDecimal amount weeklyPrice.multiply(BigDecimal.valueOf(weeks)); amount amount.add(dailyPrice.multiply(BigDecimal.valueOf(remainDays))); return amount.setScale(2, RoundingMode.HALF_UP); } if (mode LeaseMode.MONTHLY) { long months days / 30; long remainDays days % 30; BigDecimal amount monthlyPrice.multiply(BigDecimal.valueOf(months)); amount amount.add(dailyPrice.multiply(BigDecimal.valueOf(remainDays))); return amount.setScale(2, RoundingMode.HALF_UP); } throw new IllegalArgumentException(Unsupported lease mode: mode); } }这段代码采用了一个简便规则不足一周或不足一月的天数按日租价补齐。实际项目里月租通常按自然月计算比如“6 月 1 日入住7 月 1 日退房”是整月。如果从 6 月 15 日住到 7 月 14 日按其实天数折算还是按自然月折算需要在价格规则里提前确定。另一个关键点是金额计算一律使用BigDecimal。不要使用double否则金额会出现精度问题尤其在累计多天价格时0.1 的误差可能被放大。4. 可用性判断与预订冲突检测短租系统的核心难点之一是如何判断一个房源在某天是否可用。有了订单表之后可用性判断本质上就是“是否存在时间重叠的有效订单”。4.1 时间区间冲突 SQL按照“左闭右开”的规则两个订单冲突的条件是新订单开始日期 已有订单结束日期新订单结束日期 已有订单开始日期对应 SQL 如下SELECT COUNT(*) FROM stay_order WHERE apartment_id #{apartmentId} AND status IN (1, 2) AND start_date #{newEndDate} AND end_date #{newStartDate};这里status IN (1, 2)表示只排除“已支付”和“已入住”的订单。“待支付”的订单要不要占房源需要在业务上选择。如果待支付订单也占可以防止用户恶意占单如果待支付订单不占则需要引入“锁定时间”机制比如锁定 15 分钟后自动释放。4.2 用 Java 做一次二次校验接口层只相信前端传参是很危险的。创建订单时后端必须再次检查可用性不能只靠前端把不可预订的日期隐藏掉。Service public class BookingService { Autowired private StayOrderRepository stayOrderRepository; public void checkAvailable(Long apartmentId, LocalDate startDate, LocalDate endDate) { if (!startDate.isBefore(endDate)) { throw new IllegalArgumentException(入住日期必须早于退房日期); } int overlapCount stayOrderRepository.countOverlap( apartmentId, startDate, endDate, BookingStatus.PAID.getCode(), BookingStatus.CHECKED_IN.getCode()); if (overlapCount 0) { throw new IllegalStateException(该日期区间已被占用请更换日期); } } }实际项目中这个方法还要考虑数据库事务隔离级别。默认的READ_COMMITTED或REPEATABLE_READ下两个并发请求可能同时查到overlapCount 0然后同时插入订单导致超卖。解决办法有几种给stay_order表加唯一约束比如apartment_id start_date end_date。但短租订单时间区间不固定唯一约束不好设计。使用SELECT ... FOR UPDATE锁住房源记录。引入 Redis 分布式锁以apartmentId:startDate:endDate作为锁 key。增加一个occupancy_calendar日表每天对应一条记录预订时更新并加锁。对于中小型短租系统推荐先用数据库锁或日表方案逻辑简单且不容易出错。4.3 订单状态机订单状态不能随意跳转。合理的状态至少包括状态数值含义可跳转到待支付0订单已创建但未付款已支付、已取消已支付1租客已付款已入住、已取消已入住2租客已办理入住已完成已完成3订单正常结束无已取消4订单被取消无在代码里可以写一个简单的状态校验方法禁止非法流转。比如“已完成”的订单不能重新跳回“已支付”否则房源时间轴会被破坏。5. 接口示例与运行验证为了让整个方案形成闭环这里设计三个最小接口发布房源、查询可用日期、创建订单。用这三个接口就能跑通短租业务的主流程。5.1 发布房源接口请求示例POST /api/apartments { communityName: 碧桂园山湖城, blockAddress: 观澜1街10座, roomNo: 2202, houseTypeText: 2室1厅1厨1卫2阳台, bedroomCount: 2, livingRoomCount: 1, bathroomCount: 1, balconyCount: 2, areaSqm: 89.50, bedList: [ { roomName: 主卧, bedSize: 1.8, sleepCount: 2 }, { roomName: 次卧, bedSize: 1.5, sleepCount: 1 } ] }后端把apartment和room_bed分别插入两处操作要放在同一个事务里。否则可能出现“房源插入成功但床位插入失败”的脏数据。5.2 查询可用日期接口请求示例GET /api/apartments/{apartmentId}/available?startDate2025-06-01endDate2025-06-10返回示例{ apartmentId: 1, startDate: 2025-06-01, endDate: 2025-06-10, available: true, occupiedDates: [], dailyPrice: 268.00, weeklyPrice: 1680.00, monthlyPrice: 5800.00 }这里只返回了“是否可用”没有返回每一天的可预订情况。如果要做日历控件还需要查询每一天的占用状态这可以依靠日表或临时计算实现。小规模场景下可用性判断直接查询订单表就够了不需要预先生成全年日历。5.3 创建订单接口请求示例POST /api/bookings { apartmentId: 1, startDate: 2025-06-05, endDate: 2025-06-09, leaseMode: DAILY, guestName: 张三, guestPhone: 13800000000 }后端执行流程校验参数确认日期合法。查询价格配置。检查该区间是否已被占用。生成订单号并插入订单表。返回订单金额和订单号。返回示例{ orderNo: 20250601000001, apartmentId: 1, startDate: 2025-06-05, endDate: 2025-06-09, leaseMode: DAILY, amount: 1072.00, status: 0 }这个接口考虑的是单套房源。如果同一个租客要一次性预订多个房源需要再加“订单主表 订单明细表”的结构本文不再展开。5.4 预期验证结果操作输入预期结果发布房源上述 JSON返回房源 ID床位记录 2 条查询可用日期6 月 1 日至 6 月 10 日available true创建订单6 月 5 日至 6 月 9 日生成订单金额 1072 元再次查询可用日期6 月 5 日至 6 月 9 日available false再次创建订单重叠日期 6 月 8 日至 6 月 10 日抛出“该日期区间已被占用”6. 常见问题排查短租系统上线后问题通常集中在日期边界、金额精度、并发重复预订和状态不同步四类。下面按现象、原因、检查方式和解决方案整理。6.1 订单重叠但没有被拦截现象同一个房源同一个晚上被订了两次系统没有报错。可能原因SQL 冲突条件写反比如使用了start_date newEndDate但方向理解错了。检查状态时没有排除已取消订单。两个请求并发执行都通过了检查再插入订单。排查方式先检查传入参数是否满足“结束日期大于开始日期”。打印最终执行的 SQL带入真实日期手工执行。查看数据库里是否存在合法状态下时间重叠的订单。处理建议统一使用“左闭右开”规则即start_date endDate AND end_date startDate。对并发要求高的场景增加日表或分布式锁。6.2 金额出现 1.099999999 这样的结果现象订单金额有时正常有时多出很多位小数。原因用double或float做乘法累加浮点数本身存在精度误差。排查方式检查金额计算代码是否使用了BigDecimal。检查数据库字段是否是DECIMAL而不是DOUBLE。检查前端展示时是否做了格式化。处理建议Java 代码统一使用BigDecimal并在最终结果调用setScale(2, RoundingMode.HALF_UP)。数据库金额字段使用DECIMAL(10,2)或DECIMAL(12,2)。6.3 周租或月租的费用偏差现象同一个 10 天的周租订单不同入口计算出来的价格不同。原因关于“不足一周部分如何计价”的规则没有统一。排查方式查看订单明细里记录的是“日价周价”的混合计算还是单独周价。检查价格表是否查到了不同生效日期的配置。处理建议在代码里把计算规则写清楚并增加单元测试。对于月租尽量按自然月计算如果跨月需要约定天数的统计方式。6.4 修改房源信息后旧订单展示异常现象用户把 2 室改成 1 室历史订单详情也跟着变了导致对账出现麻烦。原因订单表只关联了公寓 ID下单后没有快照房源信息。处理建议订单里保存下单时的关键快照比如户型、价格、床配置。如果要追溯历史可以增加order_snapshot字段或单独的快照表。7. 生产落地建议与扩展方向学习环境里跑通一个接口就算完成生产环境则要考虑更多因素包括租客实名、门锁交接、保洁排期、支付对账和物业合规。这些内容虽然不属于“数据库和接口”范畴但直接决定系统能否长期用下去。7.1 生产环境必须补齐的能力能力说明实名登记按当地法规采集租客身份证信息确保人证一致智能门锁使用动态密码入住时下发一次性密码退房后失效支付对接接入支付宝/微信支付生成支付流水自动更新订单状态后台权限区分业主、管家、保洁角色避免越权操作日志与审计记录谁在什么时间修改了房价、取消了什么订单数据备份每日备份数据库至少保留最近 7 天监控告警对重复预订、支付失败、接口异常设置告警7.2 可复用检查清单项目上线前可以按下面这份清单逐项确认房源表和床位表是否在同一事务中写入。订单表是否建立了apartment_id start_date end_date联合索引。日期边界是否统一为“入住包含、退房不包含”。价格计算是否全部使用BigDecimal。创建订单前是否做了后端二次可用性校验。并发场景是否引入了锁或唯一约束。订单状态是否只能按状态机流转。租客个人敏感信息是否加密存储。是否保留了历史价格配置而不是覆盖。是否记录了支付流水和订单变更日志。7.3 下一步扩展方向完成一套基础短租管理系统后可以继续扩展以下方向日历房态管理以日表维度展示某套房未来 90 天的入住情况。动态定价根据节假日、入住率、周边活动调整日租价格。房源对比功能同小区多套房源按价格、面积、床数对比。收益统计按小区、房源、月份统计出租率和总收入。智能保洁排期根据退房时间自动生成保洁任务。对于开发者来说最有价值的不是把接口写得多么复杂而是把数据模型和业务边界定义清楚。一个短租系统能不能稳定运行往往取决于最初对“房源、床位、日期、价格、订单状态”这五个概念是否理解到位。先把这些核心字段和规则落到表结构和代码里后面的功能扩展也就有了稳定的底座。