☰
Java+Vue房产租赁管理系统:从业务建模到前后端部署全解析
2026/10/12 5:54:44 网站建设 项目流程

做这个东西之前,我其实已经看过不少毕业设计和课设选题,十个人里至少有六七个会选管理系统类。但真正上手去写一个发布出来、能跑通、能提交的完整项目时,很多人卡壳的点根本不是"不会写代码",而是不知道一个像样的系统到底该有哪些模块、数据库怎么设计、前端怎么跟后端配合。这也是我想把这个基于 Java + Vue 的房产租赁管理系统拆开来聊一聊的原因。

这套系统的核心价值很直接:房源信息归集、租客资料管理、合同台账、租金账单、历史租约记录,全部通过一个 Web 系统串起来。后端我用的是 Java 系主流框架 Spring Boot + MyBatis Plus,前端是 Vue 2 + Element UI,数据库选的 MySQL 5.7。整体难度正好卡在"能锻炼工程能力,又不至于写完吐血"的区间。无论你是准备交课程设计、毕业设计,还是想在公司内部搞一套小工具,这套系统的底层逻辑都能直接用。

下面我按实际开发顺序,把从选型、设计到联调、部署的全过程掰开讲,顺带把每次踩坑的细节也写进去,你会看到哪些坑是"人人都会遇到"的。

1. 为什么选 Java + Vue:这套组合到底好在哪

有人选 Python 的 Django,有人选 PHP,但管理系统的课设题里,Java 依然是出镜率最高的那个。原因不是我偏心,而是 Java 的技术栈在"业务管理系统"这个场景下,有着别的组合很难复制的三个特点。

1.1 Spring Boot 让后端开发回归业务本身

如果早几年用 SSH 那套写法,光配置文件就能写半本笔记。但换成 Spring Boot 之后,你只需要一个启动类、一个 application.yml,再加上几个注解,Controller 就出来了。MyBatis Plus 更进一步,连单表的增删改查都不用写 XML,直接用 BaseMapper 里的方法就能完成大部分操作,这让我能把精力集中在业务逻辑上,而不是跟 JDBC 的 try-catch 搏斗。

1.2 Vue 的前后端分离体验比模板引擎好太多

过去用 JSP 写管理系统,最痛苦的是 HTML、Java、SQL 混在一起,改一个表格样式都可能连带碰到后端代码。Vue 这套玩法是彻底分离:前端写页面,通过 Axios 调后端接口,再把拿到的 JSON 数据渲染成表格。你在开发时甚至可以同时开着两个本地端口,前端 8080、后端 8081,互相之间用代理转发,调试体验非常顺畅。

1.3 这套组合的学习成本到底高不高

说句实话,如果你已经有 Java Web 基础,学 Spring Boot 的入门成本并不高。Vue 也一样,只要弄懂"数据绑定""组件通信""路由跳转"这三个概念,初中级别的管理系统前端就能写。真正的难度在于"完整地做出来"这个动作,比如打包、部署、跨域、登录状态保持,这恰恰是很多教程不会细讲的部分,也是这篇文章后续会重点覆盖的内容。

2. 业务模块拆解:一套系统到底在管哪些事

很多同学拿到"房产租赁管理系统"这个题目时,脑子里只有"要能添加房源",但真正到了选题答辩,老师问一句"你系统里租房合同到期怎么提醒",就卡住了。一个合格的租赁管理系统,至少需要覆盖五个核心业务域,我把他们拆开列一下。

2.1 基础数据管理:房源与租客

房源和租客是整个系统的数据地基。房源需要记录的字段包括:房源编号、产权证号、房屋地址、所在小区、户型(几室几厅)、面积、楼层、朝向、装修情况、配置设施(空调、洗衣机、宽带)、当前状态(未出租/已出租/停用)、租金定价等。

租客的表单比房源简单一些,但身份证号、居住证、紧急联系人这类字段必须预留,而且要加入学籍校验——证件号输入错了,后面合同签了,后台数据改起来非常麻烦。我用的是前端做格式校验,后端再用 Hibernate Validator 做一次兜底校验,两边都不能少。

2.2 合同管理:租期、租金、押金一体的核心业务

合同表是整个系统的核心,很多关键业务逻辑都在此体现,常见的字段如合同编号、租客 ID、房源 ID、签约日期、起租日期、到期日期、月租金、押金、付款方式(月付/季付/半年付/年付)、物业费归属、违约金条款、合同状态(执行中/已到期/已退租)等。

最值得做的业务操作是自动算租期:给定起租日期和租期月数,自动算出到期日。还要支持"提前退租"和"续租"两套流程,提前退租要计算违约金,续租要更新合同起止日期而不产生新的房源状态切换。

2.3 账单管理:租金、物业费与水电气台账

账单模块让系统从"台账记录"升级为"业务系统"。我建议拆成两张表:一张是应收账单表,记录每期该收多少钱;另外一张是收款记录表,记录实际收到的金额和收款时间。一对比就能看出哪些租客欠费。应收账单的生成逻辑根据合同里的付款周期,在合同生效的那一刻就自动生成未来 6 个月甚至整个租期的账单,这件事用定时任务最好,也可以做成"点击生成账单"的按钮,优先用按钮,因为课设里演示效果更直观。

2.4 统计报表:让数据在首页说话

管理系统如果没有首页统计,就像账本没有封面,虽然能用但总觉得缺了点什么。我用 ECharts 做了三块统计:每月租金收入折线图、房源状态占比饼图、到期的合同列表。这些数据的后端接口就几个 SQL 聚合查询,并不复杂,但在答辩演示时非常加分。

2.5 权限控制:区分管理员与普通操作员

最基础的做法是用户表里用 role 字段区分 admin 和 operator:admin 能看所有菜单、能删除数据;operator 只能管理租客和房源,不能动合同和财务报表。后端用拦截器拦截请求,判断 session 里的用户角色,权限不足直接返回 403。这部分的重点是讲解"为什么不能只在前端隐藏按钮"——因为只要知道了接口地址就能直接调,所以后端必须做同样的拦截。

3. 数据库设计:7张核心表是怎么规划出来的

数据库设计是整个项目里最考验"预判能力"的部分。表建好了,后面所有业务延伸都会很顺手;表建烂了,后期每个功能都像在补窟窿。我最终按下面这个结构设计,一共 7 张核心表,再加一张系统日志表和一张用户表,一共 9 张。

3.1 表的清单与职责划分

表名中文名主要字段(简写)关键说明
sys_user系统用户id, username, password, real_name, rolepassword 存储 MD5 或 BCrypt 加密后的值
house房屋信息id, house_code, address, unit_type, area, floor, orientation, renovation, facilities, statusstatus 为 0 未出租、1 已出租、2 停用
tenant租客信息id, tenant_no, name, id_card, phone, emergency_name, emergency_phone, remark身份证唯一索引
lease_contract租赁合同id, contract_no, house_id, tenant_id, start_date, end_date, monthly_rent, deposit, pay_cycle, statushouse_id 和 tenant_id 建立外键索引
bill_receivable应收账单id, contract_id, period, amount, due_date, statusstatus:0 未收、1 已收、2 逾期
payment_record收款记录id, bill_id, contract_id, pay_amount, pay_time, pay_method保留 contract_id 便于按合同查历史
house_change_log房屋变更记录id, house_id, old_status, new_status, change_time, reason退租、入住都会产生记录

3.2 设计时反复推敲的几个决策

主键统一用自增 int,不折腾雪花算法,单机课设完全够用。身份证号在 tenant 表上加了唯一索引,避免同一个租客录入两次。合同与房屋是一对一关系,但因为存在"退租——新合同"的切换,所以房屋状态要用独立 status 字段,而不能靠"是否有合同"来判断,否则一旦合同做废就会出逻辑漏洞。

另一个重要的决策是应收账单独立成表。有同学会把租金直接写进合同表,每月收没收到只改一个 SELECT 查询里的值。这样表面省事,但无法追溯"这个月这笔钱到底收没收、哪一天收的"。拆成 bill 和 payment 两张表后,业务上可以做对账,也就有了做数据可视化的基础数据。

3.3 SQL 示例:创建房屋表

下面给出房屋表的创建语句,其他表的思路类似。需要说明的是,抵押字段和默认值要根据你自己的物管产品设计定,我这里只是一个参考。

CREATE TABLE `house` ( `id` int(11) NOT NULL AUTO_INCREMENT, `house_code` varchar(32) NOT NULL COMMENT '房源编号', `address` varchar(255) NOT NULL COMMENT '详细地址', `unit_type` varchar(32) DEFAULT '1室1厅' COMMENT '户型', `area` decimal(10,2) DEFAULT NULL COMMENT '面积', `floor` varchar(16) DEFAULT NULL COMMENT '楼层', `orientation` varchar(16) DEFAULT NULL COMMENT '朝向', `renovation` varchar(32) DEFAULT NULL COMMENT '装修情况', `facilities` varchar(255) DEFAULT NULL COMMENT '配置设施,逗号分隔', `rent_price` decimal(10,2) DEFAULT NULL COMMENT '参考月租价', `status` tinyint(1) DEFAULT '0' COMMENT '0未出租 1已出租 2停用', `create_time` datetime DEFAULT NULL, `update_time` datetime DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_house_code` (`house_code`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='房屋信息表';

注意rent_price代表的是"参考售价",实际成交租金应该以合同里的 monthly_rent 为准。一开始我把两个字段放混了,导致改价后合同里也自动变,逻辑上很危险。后来改成房源表只存参考价,合同表存成交价,两者不再互相影响。

4. 后端实现中的关键逻辑:登录、合同、租金计算

后端代码里最容易丢分的点往往不是 CRUD,而是那些"不直接对应一张表"的业务逻辑。这里我挑三个重点讲,每个都附上实现思路,你可以直接抄作业。

4.1 登录鉴权与会话保持

单机管理系统不需要引入 JWT 这种复杂的方案,用 Servlet 的 Session 就够了。用户登录成功后把 user 对象放进 session,前端每次请求携带 cookie,后端用一个拦截器统一校验。

拦截器里需要注意的是放行名单:登录接口、静态资源、前端首页的路由接口不需要鉴权,其余 API 全部拦截。举例来说,如果没登录就请求/api/house/list,后端直接返回 401 状态码。前端在 axios 的响应拦截器里统一判断 401,然后强制跳回登录页。

public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session = request.getSession(); Object user = session.getAttribute("loginUser"); if (user == null) { response.setStatus(401); return false; } return true; } }

要额外处理的一点是前端路由鉴权:你可以在 Vue 的路由配置里写 meta 字段,比如{ path: '/house', component: HouseList, meta: { requiresAuth: true } },在 beforeEach 导航守卫里检查 token 或自己的登录状态。

4.2 自动计算租期与续租/退租逻辑

这是最容易被人问"你这里是怎么处理的"的地方。租期计算用 Java 的LocalDate,非常方便且不易踩坑。比如合同起租日是 2023-07-01,租期 12 个月,到期日就是startDate.plusMonths(12).minusDays(1),注意一定要减一天,因为租到 2024-07-01 的 0 点结束,实际最后一天是 2024-06-30。

续租操作:把旧合同状态改为"已续租",然后创建一个新合同,起租日期为旧合同到期日加一天,租期重新填。房屋状态在旧合同退租和新合同生效中间要维护好。

退租操作:首先校验是不是有未缴纳的应收账单,有的话提示先结清账单;然后清空租客与房源之间的绑定,house.status设为 0(未出租),收押金退回,记录 house_change_log。这里必须用一个事务方法包起来,任何一步抛异常都整体回滚,避免出现"房子解绑了但账单还挂着"这种脏数据。

@Transactional(rollbackFor = Exception.class) public void terminateContract(Contract contract) { // 1. 校验账单 int unpaid = billReceivableMapper.countUnpaid(contract.getId()); if (unpaid > 0) throw new BizException("该合同存在未结清账单,请先收款处理"); // 2. 更新合同状态 contract.setStatus("3"); // 已退租 contractMapper.updateById(contract); // 3. 房屋释放 houseMapper.updateStatus(contract.getHouseId(), 0); // 4. 记录日志 houseChangeLogMapper.insert(new HouseChangeLog(...)); }

4.3 账单自动生成的算法

应收账单生成规则:按合同里的付款周期把租期拆成多个期数。如果租期是 12 个月、月付,那就生成 12 条 bill。如果租期是 12 个月、季付,那就生成 4 条 bill。生成时每期金额相同,到期日按起租日期逐期增加。

这里要特别注意跨年或中途价格调整的情况,所以账单里的 amount 字段应该在生成时冻结下来,不要关联合同金额做实时计算。合同签的是月租 1800,首月到期日前系统自动账单,哪怕后面改合同金额,之前已生成的账单也不能随之变化,不然对账就对不上了。

5. 前端页面的组织方式与 API 对接案例

前端我用的 Vue CLI 2.x 版本(对应 Vue 2),配 Element UI 组件库。页面结构上不怎么需要精细到布局,重点是把按钮、表格、弹窗、表单校验串起来。

5.1 页面结构:分包与复用

项目目录我会分成views、components、api和router四块。页面按业务模块建目录:views/house/index.vue房源列表、views/tenant/index.vue租客列表、views/contract/index.vue合同列表、views/bill/index.vue账单列表。每个页面里如果有"新增/编辑"弹窗,单独拆一个 dialog 组件放在同目录下,不要全部堆在一个文件里,改起来会心态爆炸。

5.2 请求封装与跨域处理

axios 的封装是前端工程化的第一步,核心是统一 baseURL、统一携带 cookie、统一处理 401。我一般在api/request.js里创建一个实例,设置baseURL: '/api',并配置withCredentials: true,这样会话 cookie 才能被后端识别。

本地开发时的跨域问题用 Vue CLI 里的 devServer 代理解决。修改vue.config.js:

module.exports = { devServer: { port: 8080, proxy: { '/api': { target: 'http://localhost:8081', changeOrigin: true } } } }

在这套配置下,前端请求/api/house/list会被转发到后端的/api/house/list,后端不会再出现跨域报错。生产环境部署时,把后端接口通过 Nginx 反向代理到同一域名下,也基本是同理。

5.3 列表展示的实战代码片段

下面这段代码是从合同列表页面抽出来的核心思路。重点是翻页数据回填和操作按钮的事件绑定。

<template> <div> <el-table :data="tableData" border stripe v-loading="loading"> <el-table-column prop="contractNo" label="合同编号" /> <el-table-column prop="tenantName" label="租客" /> <el-table-column prop="houseAddress" label="房源地址" /> <el-table-column prop="monthlyRent" label="月租金" /> <el-table-column prop="endDate" label="到期日" /> <el-table-column label="操作"> <template slot-scope="{ row }"> <el-button type="text" @click="openDetail(row)">详情</el-button> <el-button type="text" @click="doTerminate(row)">退租</el-button> </template> </el-table-column> </el-table> <el-pagination layout="total, prev, pager, next" :total="total" :current-page="queryParams.pageNum" :page-size="queryParams.pageSize" @current-change="handlePageChange" /> </div> </template> <script> import { listContract } from '@/api/contract' export default { data() { return { tableData: [], total: 0, loading: false, queryParams: { pageNum: 1, pageSize: 10 } } }, created() { this.fetchList() }, methods: { async fetchList() { this.loading = true const res = await listContract(this.queryParams) this.tableData = res.data.rows this.total = res.data.total this.loading = false }, handlePageChange(page) { this.queryParams.pageNum = page this.fetchList() } } } </script>

值得提的一个小坑:表格里的tenantName和houseAddress在合同表里其实并不存在,是后端在做列表查询时通过 left join 查出并封装到 VO 里的。前端直接绑 VO 的字段名,省了每行去调一个详情接口。这个设计在课设里是加分项。

5.4 Element UI 表单校验收不住手

租客身份证号校验、手机号校验、金额输入范围,这些都是 EL-Form 自带能力。我在租客表单里用rules对象配置了required: true和pattern,其中身份证的pattern是从网上搜到的通用正则。关键并不是这条正则多准确,而是在提交按钮里不要忘了加上this.$refs.form.validate()的判断,否则校验不出声也照样提交,很影响体验。

6. 运行部署全流程:从代码到本地跑起来

项目交付时"能跑起来"是第一印象。很多人源码写了,最后却因为环境搭建问题在老师面前翻车。我整理了一套近乎保姆级别的步骤,配好环境后十分钟内就能把整站跑起来。

6.1 本地环境要求

软件版本建议说明
JDK8 或 11太新的版本如 17 可能遇到兼容性问题
Maven3.6+用来拉依赖和打包
MySQL5.7 或 8.0注意 8.0 的驱动差异
Node.js14 或 16Vue 2 项目尽量避免 Node 18 以上
浏览器Chrome/Edge仅调试用

6.2 数据库初始化的步骤

项目目录下会有sql/rent_manage.sql文件,里面包含建表和示例数据。如果你拿到的源码里没有,那就自己导一份。执行顺序很重要:先建库,再选库,再导入 SQL。

mysql -uroot -p create database rent_manage default character set utf8mb4; use rent_manage; source /你的绝对路径/rent_manage.sql;

导入后检查一下表数量,我这次是 9 张表,并且sys_user里已经有admin用户。如果 SQL 里没有初始用户,你要在启动后端前手动插入一条记录,否则会登录不了。

6.3 后端启动三步走

第一步,用 IDEA 打开后端工程,等 Maven 下载完依赖,时间可能较长。第二步,修改application.yml里的数据库连接参数:

spring: datasource: url: jdbc:mysql://localhost:3306/rent_manage?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: yourpassword

第三步,运行RentManageApplication.java的主方法,看到控制台输出 "Started RentManageApplication" 就算启动成功。默认端口 8081,简单验证:浏览器访问http://localhost:8081/api/house/list,未登录时应该返回 401 JSON,说明接口已经活着。

6.4 前端启动与打包

前端在另一个终端窗口中执行:

npm install npm run serve

如果npm install太慢,可以用npm config set registry https://registry.npmmirror.com换源。启动后访问http://localhost:8080。开发环境接口代理已经在vue.config.js里配好,直接登录即可。

要打包上传到服务器的话,执行npm run build,把生成的dist/目录用 Nginx 托管,并把接口路径/api反向代理到后端端口,类似:

location /api/ { proxy_pass http://127.0.0.1:8081/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }

如果不做代理,前端打包后请求会 404,这是最常见的一个部署误区。

7. 排错实录与扩展建议:我踩过的坑和后续玩法

这是本篇最想让你仔细看的部分,全是实际开发里冒出来的问题,有些甚至教材里根本不会提。

7.1 "列表接口能通,但是跨域报错了"

开发模式下只要出现跨域报错,十次里有九次是后端没把数据正确返回,或者代理没配置生效。先确认前端请求地址是/api/house/list而不是http://localhost:8081/api/house/list。前者经过代理是"同源"请求,后者是"跨域"请求,请避免直接在 axios 里写死端口号。

7.2 合同日期计算的经典翻车现场

我最早写租期计算时endDate = startDate.plusMonths(month),没有减一天,结果合同显示 2024-07-01 到期,当天系统判定为"逾期"。后来改为减一天,逻辑才通顺。如果你拿到任何源码里有类似的日期 bug,不要犹豫,直接用 LocalDate 重写一遍。

7.3 数据库字符集导致中文乱码

这几乎是必现的坑。创建表时如果是默认 latin1,全部中文都会变成问号。解决办法是确保建库语句带default character set utf8mb4,并且 Spring 的配置里加上characterEncoding=utf8。两个地方都做对,才能彻底避免乱码。

7.4 扩展成多角色、多租户甚至小程序端

现在这套系统的用户表只有 admin 和 operator 两种角色,但稍微扩展一下就能变成多房东多门店的系统:给house表加owner_id,给contract表加branch_id,再用一个数据权限拦截器,让不同门店只能看自己的数据。前端如果想做小程序或移动端,可以直接复用这套 Java 后端接口,用 uni-app 或原生小程序框架再写一个壳就行。

7.5 把文档写清楚,比功能全还重要

你的交付物里包含"文档",那就一定要在 README 里写清楚环境依赖、数据库导入命令、默认账号、后端启动入口、前端启动入口、项目结构说明。很多评阅老师不看源码,只看你能不能按文档把东西跑起来。我建议你在写完项目后,从零开始按 README 走一遍流程,会发现"我以为自己写清楚了,结果还是有地方漏了"。

写到最后想说的

这套系统的开发过程,本质上是一场"从零开始把一个业务域变成数据模型"的练习。你会遇到接口设计时的纠结、遇到前端表格渲染不出数据时的沮丧、也会遇到部署成功后眼睁睁看着系统跑通的满足感。我在实际开发里最大的体会是:别想着一步到位,先把最核心的房源、合同、账单闭环跑通,再加入统计、权限这类增值模块。而且一定要记住,源码只是初始材料,数据库里每一条样例数据、文档里每一句使用说明,都体现了你对这个业务域的理解程度。做完之后强烈建议你再回头把house_change_log和应收账单的对账功能完善一下,这两块很多人会忽略,但它们恰恰是现实中租赁业务真正离不开的底子。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询