毕业设计实战:基于SpringBoot+MySQL的房屋租赁管理系统设计与实现避坑指南
在开发“房屋租赁管理系统”毕业设计时,曾因“房源与租赁合同未建立有效关联”踩过关键坑——初期仅在两表设置独立字段,未建立外键约束与业务逻辑关联,导致管理员查询某房源的历史租赁记录时,需手动匹配房源编号与合同数据,耗费2天重构数据库与业务层代码才解决问题🏠。本文基于实战经验,系统拆解房屋租赁系统的设计要点与避坑技巧。
一、需求分析:聚焦房屋租赁核心业务流
房屋租赁系统易陷入“功能堆砌”误区。笔者曾花费3天开发“房源3D展示模块”,最终因偏离核心需求被要求删减。明确“房东-租客-管理员”三方协作流程是关键。
1. 核心用户角色与功能矩阵
| 角色 | 核心功能 | 易忽略要点 |
|---|---|---|
| 管理员 | 房源审核、合同管理、投诉处理、数据统计 | 需设置房源状态机(待审核/已上架/已出租/已下架) |
| 房东 | 发布房源、管理合同、收租提醒、维修申报 | 需验证房产证明,防止虚假房源 |
| 租客 | 房源搜索、在线预约、合同签署、维修申报 | 需集成实名认证,保障双方权益 |
2. 核心业务流程梳理
房源上架流程:
房东提交房源 → 管理员审核(验证产权) → 上架展示 → 租客浏览预约 → 线下看房 → 签署电子合同 → 支付押金租金 → 生成租赁记录
维修处理流程:
租客提交报修 → 系统通知房东 → 房东确认/委托物业 → 维修完成 → 租客确认 → 费用结算(从押金扣除或单独支付)
3. 需求分析避坑要点
- 数据一致性:租金支付记录、合同状态、房源状态必须保持实时同步
- 权限隔离:租客只能查看已租房源,不能查看他人合同
- 时间冲突检测:同一房源在同一时间段内不能签署两份合同
- 押金管理:需明确退还规则(何时退、退多少、扣除依据)
二、技术选型:稳中求胜的技术栈组合
| 技术 | 选型理由 | 避坑提醒 |
|---|---|---|
| Spring Boot 2.7.x | 快速搭建REST API,集成MyBatis Plus简化数据库操作 | 避免3.x版本,与部分中间件兼容性不足 |
| MySQL 8.0 | 支持JSON字段存储房源特色标签,事务保障支付数据一致性 | 启用utf8mb4字符集,支持emoji和生僻字 |
| Redis 6.x | 缓存热门房源数据,减少数据库压力 | 设置合适过期时间,避免缓存雪崩 |
| Vue 2.x + ElementUI | 快速构建管理后台,组件丰富 | 注意按需引入,避免打包体积过大 |
| 支付宝/微信支付SDK | 集成在线支付,提升用户体验 | 使用沙箱环境测试,处理好回调验证 |
三、数据库设计:关系型数据模型的关键关联
1. 核心表结构设计(精简版)
-- 房源表(核心表)
CREATE TABLE house (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '房源ID',
house_number VARCHAR(50) UNIQUE NOT NULL COMMENT '房源编号(如HS202312001)',
landlord_id BIGINT NOT NULL COMMENT '房东ID',
title VARCHAR(100) NOT NULL COMMENT '房源标题',
address VARCHAR(200) NOT NULL COMMENT '详细地址',
area DECIMAL(10,2) NOT NULL COMMENT '面积(㎡)',
price DECIMAL(10,2) NOT NULL COMMENT '月租金',
deposit DECIMAL(10,2) NOT NULL COMMENT '押金(通常为月租金倍数)',
house_type VARCHAR(20) COMMENT '户型:一室一厅等',
status TINYINT DEFAULT 1 COMMENT '状态:1待审核 2已上架 3已出租 4已下架',
tags JSON COMMENT '房源标签:["近地铁","带阳台","可养宠物"]',
images TEXT COMMENT '图片URL列表,JSON数组',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_landlord (landlord_id),
INDEX idx_status_price (status, price)
) COMMENT='房源信息表';
-- 租赁合同表(核心业务表)
CREATE TABLE contract (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
contract_number VARCHAR(50) UNIQUE NOT NULL COMMENT '合同编号(如HT202312001)',
house_id BIGINT NOT NULL COMMENT '房源ID',
landlord_id BIGINT NOT NULL COMMENT '房东ID',
tenant_id BIGINT NOT NULL COMMENT '租客ID',
start_date DATE NOT NULL COMMENT '租赁开始日期',
end_date DATE NOT NULL COMMENT '租赁结束日期',
monthly_rent DECIMAL(10,2) NOT NULL COMMENT '月租金',
deposit_amount DECIMAL(10,2) NOT NULL COMMENT '押金金额',
payment_cycle TINYINT DEFAULT 1 COMMENT '支付周期:1月付 2季付 3半年付 4年付',
status TINYINT DEFAULT 1 COMMENT '状态:1待签署 2生效中 3已到期 4已解约',
sign_time DATETIME COMMENT '签署时间',
electronic_copy_url VARCHAR(500) COMMENT '电子合同存储路径',
FOREIGN KEY (house_id) REFERENCES house(id) ON DELETE RESTRICT,
FOREIGN KEY (tenant_id) REFERENCES user(id) ON DELETE RESTRICT,
INDEX idx_tenant (tenant_id),
INDEX idx_house_dates (house_id, start_date, end_date)
) COMMENT='租赁合同表';
-- 租金支付记录表
CREATE TABLE payment (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
contract_id BIGINT NOT NULL,
payment_number VARCHAR(50) UNIQUE NOT NULL COMMENT '支付单号',
period VARCHAR(20) NOT NULL COMMENT '所属账期:2024-01',
amount DECIMAL(10,2) NOT NULL COMMENT '支付金额',
payment_type TINYINT COMMENT '支付类型:1租金 2押金 3水电费',
status TINYINT DEFAULT 1 COMMENT '状态:1待支付 2支付成功 3支付失败',
pay_time DATETIME COMMENT '支付时间',
transaction_id VARCHAR(100) COMMENT '第三方支付流水号',
FOREIGN KEY (contract_id) REFERENCES contract(id)
) COMMENT='支付记录表';
-- 维修申报表
CREATE TABLE repair (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
house_id BIGINT NOT NULL,
tenant_id BIGINT NOT NULL COMMENT '报修人',
title VARCHAR(100) NOT NULL COMMENT '报修标题',
description TEXT NOT NULL COMMENT '问题描述',
images TEXT COMMENT '现场照片',
status TINYINT DEFAULT 1 COMMENT '状态:1待处理 2处理中 3已完成 4已取消',
repair_cost DECIMAL(10,2) DEFAULT 0 COMMENT '维修费用',
cost_bearer TINYINT COMMENT '费用承担方:1房东 2租客 3AA',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (house_id) REFERENCES house(id)
) COMMENT='维修申报表';
2. 关键关联查询验证
-- 查询房源及其租赁历史
SELECT
h.house_number,
h.title,
h.address,
h.price,
COUNT(c.id) as rental_count,
MAX(c.end_date) as last_rental_end,
GROUP_CONCAT(DISTINCT t.real_name) as previous_tenants
FROM house h
LEFT JOIN contract c ON h.id = c.house_id
LEFT JOIN user t ON c.tenant_id = t.id
WHERE h.id = 1
GROUP BY h.id;
-- 检查房源时间冲突(重要!)
SELECT EXISTS(
SELECT 1 FROM contract c
WHERE c.house_id = 1
AND c.status = 2 -- 生效中的合同
AND (
(c.start_date <= '2024-06-01' AND c.end_date >= '2024-05-01')
OR (c.start_date BETWEEN '2024-05-01' AND '2024-06-01')
)
) as has_conflict;
四、核心业务模块实现
1. 房源管理模块(含状态机控制)
@Service
@Transactional
public class HouseService {
@Autowired
private HouseMapper houseMapper;
@Autowired
private ContractMapper contractMapper;
/**
* 房源上架(包含状态校验)
*/
public ApiResult publishHouse(Long houseId) {
House house = houseMapper.selectById(houseId);
// 状态机校验
if (house.getStatus() != HouseStatus.PENDING_REVIEW.getCode()) {
return ApiResult.error("房源状态不符,无法上架");
}
// 校验必要信息
if (StringUtils.isEmpty(house.getImages())) {
return ApiResult.error("请上传房源图片");
}
// 更新状态
house.setStatus(HouseStatus.LISTED.getCode());
house.setPublishTime(new Date());
houseMapper.updateById(house);
// 记录操作日志
logService.addLog("房源上架", "房源ID:" + houseId);
return ApiResult.success("上架成功");
}
/**
* 房源下架(检查是否有进行中的合同)
*/
public ApiResult offlineHouse(Long houseId, Long landlordId) {
// 检查是否有生效中的合同
Long activeContract = contractMapper.countActiveByHouseId(houseId);
if (activeContract > 0) {
return ApiResult.error("该房源存在生效中的合同,无法下架");
}
House house = new House();
house.setId(houseId);
house.setStatus(HouseStatus.OFFLINE.getCode());
house.setUpdateTime(new Date());
return houseMapper.updateById(house) > 0
? ApiResult.success("下架成功")
: ApiResult.error("下架失败");
}
}
2. 合同管理模块(含时间冲突检测)
@Service
public class ContractService {
/**
* 创建租赁合同(核心业务)
*/
@Transactional
public ApiResult createContract(ContractDTO dto) {
// 1. 校验房源状态
House house = houseMapper.selectById(dto.getHouseId());
if (house.getStatus() != HouseStatus.LISTED.getCode()) {
return ApiResult.error("房源不可租");
}
// 2. 校验时间冲突(关键!)
if (hasTimeConflict(dto.getHouseId(), dto.getStartDate(), dto.getEndDate())) {
return ApiResult.error("该时间段房源已被预定");
}
// 3. 校验租客身份
User tenant = userMapper.selectById(dto.getTenantId());
if (!UserRole.TENANT.equals(tenant.getRole())) {
return ApiResult.error("用户不是租客身份");
}
// 4. 生成合同
Contract contract = new Contract();
BeanUtils.copyProperties(dto, contract);
contract.setContractNumber(generateContractNumber());
contract.setStatus(ContractStatus.PENDING_SIGN.getCode());
contractMapper.insert(contract);
// 5. 更新房源状态
houseMapper.updateStatus(dto.getHouseId(), HouseStatus.RENTED.getCode());
return ApiResult.success("合同创建成功", contract.getId());
}
/**
* 时间冲突检测
*/
private boolean hasTimeConflict(Long houseId, Date startDate, Date endDate) {
List<Contract> activeContracts = contractMapper.selectActiveByHouseId(houseId);
for (Contract contract : activeContracts) {
// 时间重叠检测逻辑
if (!(endDate.before(contract.getStartDate()) ||
startDate.after(contract.getEndDate()))) {
return true;
}
}
return false;
}
}
3. 租金支付模块
@Service
public class PaymentService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 生成租金账单
*/
public List<PaymentBill> generateBills(Long contractId) {
Contract contract = contractMapper.selectById(contractId);
List<PaymentBill> bills = new ArrayList<>();
Calendar calendar = Calendar.getInstance();
calendar.setTime(contract.getStartDate());
// 根据合同周期生成账单
while (!calendar.getTime().after(contract.getEndDate())) {
PaymentBill bill = new PaymentBill();
bill.setContractId(contractId);
bill.setPeriod(formatPeriod(calendar.getTime()));
bill.setAmount(contract.getMonthlyRent());
bill.setDueDate(calculateDueDate(calendar.getTime()));
bill.setStatus(BillStatus.PENDING.getCode());
bills.add(bill);
// 按支付周期增加
calendar.add(Calendar.MONTH, contract.getPaymentCycle());
}
return bills;
}
/**
* 处理支付回调(需幂等性处理)
*/
@Transactional
public ApiResult handlePaymentCallback(PaymentCallbackDTO callback) {
String lockKey = "payment_lock:" + callback.getPaymentNumber();
// 分布式锁防止重复处理
Boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", Duration.ofMinutes(5));
if (!Boolean.TRUE.equals(locked)) {
return ApiResult.error("支付正在处理中");
}
try {
// 验证支付签名
if (!verifyPaymentSignature(callback)) {
return ApiResult.error("签名验证失败");
}
// 查询支付记录
Payment payment = paymentMapper.selectByPaymentNumber(callback.getPaymentNumber());
if (payment == null) {
return ApiResult.error("支付记录不存在");
}
// 幂等性检查:已成功的不再处理
if (payment.getStatus() == PaymentStatus.SUCCESS.getCode()) {
return ApiResult.success("支付已处理");
}
// 更新支付状态
payment.setStatus(PaymentStatus.SUCCESS.getCode());
payment.setPayTime(new Date());
payment.setTransactionId(callback.getTransactionId());
paymentMapper.updateById(payment);
// 发送通知
notificationService.sendPaymentSuccess(payment);
return ApiResult.success("支付成功");
} finally {
redisTemplate.delete(lockKey);
}
}
}
五、前端页面设计要点
1. 房源列表页设计
<template>
<div class="house-list">
<!-- 筛选条件 -->
<el-card class="filter-card">
<el-row :gutter="20">
<el-col :span="6">
<el-input v-model="filters.priceRange" placeholder="价格范围" />
</el-col>
<el-col :span="6">
<el-select v-model="filters.houseType" placeholder="户型">
<el-option v-for="type in houseTypes" :key="type" :label="type" :value="type" />
</el-select>
</el-col>
<el-col :span="6">
<el-button type="primary" @click="search">搜索</el-button>
</el-col>
</el-row>
</el-card>
<!-- 房源卡片 -->
<el-row :gutter="20">
<el-col v-for="house in houseList" :key="house.id" :span="8">
<house-card :house="house" @click="viewDetail(house.id)" />
</el-col>
</el-row>
<!-- 分页 -->
<el-pagination
@current-change="handlePageChange"
:current-page="pagination.current"
:page-size="pagination.size"
:total="pagination.total"
layout="total, prev, pager, next, jumper"
/>
</div>
</template>
2. 合同签署页设计
<template>
<div class="contract-sign">
<el-steps :active="currentStep" simple>
<el-step title="确认信息" />
<el-step title="在线签署" />
<el-step title="支付押金" />
<el-step title="完成" />
</el-steps>
<!-- 合同预览 -->
<div v-if="currentStep === 0">
<contract-preview :contract="contractData" />
<el-button type="primary" @click="nextStep">同意并签署</el-button>
</div>
<!-- 签署区域 -->
<div v-if="currentStep === 1">
<el-form ref="signForm" :model="signData">
<el-form-item label="手写签名" prop="signature">
<signature-pad @change="handleSignatureChange" />
</el-form-item>
<el-button @click="prevStep">上一步</el-button>
<el-button type="primary" @click="submitSign">提交签署</el-button>
</el-form>
</div>
</div>
</template>
六、系统测试要点
1. 核心功能测试用例
| 测试场景 | 测试步骤 | 预期结果 |
|---|---|---|
| 房源时间冲突 | 为同一房源创建时间重叠的两个合同 | 第二个合同创建失败,提示"时间冲突" |
| 租金支付回调 | 模拟支付平台回调,重复发送相同支付单号 | 仅第一次处理成功,后续返回"已处理" |
| 房源状态流转 | 对已出租的房源尝试下架 | 下架失败,提示"存在生效中的合同" |
| 押金退还计算 | 租期结束,计算应退押金(扣除维修费) | 正确计算退还金额,生成退款记录 |
2. 性能测试关注点
- 房源搜索性能:添加索引,测试万级数据下的响应时间
- 合同生成并发:测试多人同时签署同一房源的合同
- 支付回调处理:测试高并发支付回调的幂等性保障
七、答辩准备要点
-
演示流程设计:
- 房东发布房源 → 管理员审核 → 租客浏览 → 在线预约 → 签署合同 → 支付租金 → 申报维修
- 重点展示:时间冲突检测、电子合同生成、支付状态同步
-
问题预判与回答:
-
Q: 如何防止一房多租? A: 通过合同时间冲突检测 + 房源状态机控制
-
Q: 电子合同的法律效力如何保障? A: 集成CA认证 + 时间戳 + 双方实名认证 + 签署过程存证
-
Q: 系统如何处理押金纠纷? A: 维修记录留证 + 费用明细公示 + 双方确认机制
-
-
创新点展示:
- 房源状态自动机设计
- 基于Redis的分布式锁防止重复支付
- 合同时间冲突智能检测算法
结语
房屋租赁管理系统的核心在于业务流程的闭环管理和数据的一致性保障。开发时需重点关注:
- 状态一致性:房源、合同、支付状态需实时同步
- 时间冲突检测:这是租赁系统的核心算法
- 资金安全:支付回调的幂等性和对账机制
- 法律合规:电子合同流程符合法律规定
避坑总结:
- 前期必须理清状态流转图
- 时间冲突检测要放在业务层最前面
- 支付模块一定要做幂等性处理
- 重要操作都要留痕(操作日志)
按照这个指南开发,你的房屋租赁管理系统毕设一定能顺利通过!需要完整源码或遇到具体问题,欢迎留言交流。