ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

房产租赁管理系统实战:SpringBoot+Vue全栈开发与数据库设计详解

房产租赁管理系统实战:SpringBoot+Vue全栈开发与数据库设计详解 毕业设计、课程设计、或者单纯想练手全栈开发的人一定绕不开一类项目xxx管理系统。而“房产租赁管理系统”这个题目在我接触过的众多Java Web课设题目里属于非常有代表性、很值得拿来深入讲一讲的项目。它不是一个简单的CRUD堆砌里面涉及房源信息的状态流转、租约合同的时间计算、房租账单的生成与核销、租客与房东的多角色权限控制。这些业务逻辑一旦展开能学到的远不止是“怎么调通一个接口”这么简单。这篇内容我打算换个角度不给你贴一堆让人眼花缭乱的完整代码而是把整个项目的骨架、数据库设计思路、后端接口的核心实现逻辑、前端Vue部分的交互设计以及我在实际运行这个项目时踩过的一些坑全部掰开揉碎讲清楚。文章覆盖的技术栈非常主流SpringBoot负责后端接口Vue 2 Element UI负责管理端界面Vue 3的写法后面我会提配合MySQL数据库外加一套完整的前后端分离部署方案。源码、数据库脚本、配套的文档这些基础资料现在大多能在网上找到但很多同学下载下来之后不知道从哪里开始看或者在运行的时候被各种环境问题卡住。所以这篇博文我默认你已经拥有了一套这样的项目资源开源社区和各类课程资源库都有我的重点不是帮你重复罗列这些文件而是帮你把这份资源真正吃透让你能在答辩的时候、或者面试聊项目的时候有自己的理解。1. 内容整体设计与思路拆解我见过很多同学的“房产租赁管理系统”第一版数据库表居然只有一个room表和一个user表。这不能怪他们因为很多网上的“精简版教程”就是这么教的。但真正的租房业务里房东不可能直接把房子挂出去就完事租客也不只是看看房源列表。这里面有带看记录、有定金意向、有合同起止日期、有水电费抄表数、有退租时的押金扣除。如果一个系统连租房合同都只能用一个remark字段存那它其实只是个“房源展示网站”不是“租赁管理系统”。所以在整体设计思路上我强烈建议按照“状态机驱动”的方式来拆解核心业务。所谓状态机就是我们给房源、租约、账单这些核心实体都设计一个明确的字段专门记录它的生命周期状态。比如房源状态正常应该包括空闲、已预订、已出租、维修中、已下架这几种。租约状态包括生效中、已到期、已退租、已违约。账单状态包括待支付、已支付、已逾期、已核销。任何操作都是状态的流转而不是直接对数据进行物理删除。这样做最大的好处是后续做统计报表的时候你的数据都是可追溯的每一个历史状态都有记录。另一个核心设计思路是“角色权限驱动菜单”。这个系统里存在两类完全不同的用户前台租客和后勤管理员在很多场景下房产中介或房东就是管理员。用户的需求截然不同。租客需要的是浏览房源、申请预约看房、在线签约、查看账单和缴纳房租管理员则需要发布房源、处理预约、起草并审核合同、抄录水电表、生成账单、办理退租。因此如果仅仅用一个status字段区分管理员和普通用户前端再根据这个字段显示不同菜单逻辑上是可以的但随着功能迭代代码里会到处是if (user.role admin)非常难维护。实践当中更优雅的做法是使用Spring Security结合JWT做认证再配合数据库里的role字段做简单的权限判断前端则使用Vue Router的导航守卫配合动态菜单渲染。这里不一定要上Spring Security OAuth2那种重量级的权限框架对于课设和中小型项目Spring Security的基础过滤链加JWT工具类就足够用了。后面我会把这部分的实现思路单独拿出来讲。1.1 核心需求解析先来梳理一下一个合格的房产租赁管理系统到底应该包含哪些功能模块。你在网上下的那些源码可能有的模块多有的模块少但核心链路应该是完整的。租房的核心链路是录入房源 - 展示房源 - 预约看房 - 签订合同 - 生成账单 - 收租对账 - 合同到期 - 退租结算。这套链路对应到后台管理功能大致就是房源管理楼栋、单元、房间号、户型、面积、朝向、月租金、押金、出租状态、可看房时间。这里要注意一个房源必须关联到某个“楼栋”或“小区”所以设计的时候house表要有一个building_id或者community_id。租客管理租客个人信息、证件号码、联系电话、紧急联系人、租住历史记录。部分系统还会要求上传身份证照片。合同管理这就是一个完整的租赁合同包含合同编号、出租方、承租方、房屋信息、租赁期限、租金、押金、支付方式、违约责任。这里我想强调一点很多课设系统的合同只是一个简单的表单弹窗但正规的做法是在保存合同的同时要联动修改房源状态为“已出租”并自动在账单管理里生成一条首次付款记录。账单管理生成每月租金账单、水电费账单、滞纳金记录以及处理租客的在线支付。支付这一块很多课设项目直接对接支付宝沙箱或者PayPal但我建议如果只是想展示业务闭环做一个“模拟支付”的按钮就可以把支付状态置为已支付即可。面试时你再补充说“实际上线会对接微信支付/支付宝”即可不必在毕设阶段因为商户资质问题卡住。报修管理租客发起报修管理员收到工单指派维修师傅回填维修进度。这个过程比较简单但很能体现系统是否“完整”。统计报表月租金收入图、房源出租率、待收款项统计等。这需要后端写聚合查询前端用ECharts展示图表。1.2 为什么选择SpringBoot Vue组合这个组合在四五年前开始就是国内中小型管理系统开发的主流配置了到现在依然是毕业生做项目最稳妥的选择。SpringBoot的生态太成熟了。它不需要像早期的SSH那样写大量的XML配置默认的自动配置已经覆盖了绝大多数场景。对做课设的同学来说spring-boot-starter-web、mybatis-plus-boot-starter、mysql-connector-java这三个依赖引入进来一个能跑通CRUD的后端就基本成型了。MyBatis-Plus尤其对新人友好它内置的分页插件和ServiceImpl、IService通用方法能省掉大量手写BaseMapper的时间。Vue在管理后台方面几乎就是统治级的存在。特别是Element UI组件库表格、表单、弹窗、分页、步骤条这些后台管理最常用的组件全都做得很成熟不需要自己再去折腾复杂的CSS。前后端分离的开发模式下前端开发环境用Vue CLI起一个8080端口的服务后端用SpringBoot跑一个8080端口两边通过Axios发请求和接收JSON数据。开发起来互不阻塞改前端不用重启后端反过来也一样。我还要说说为什么不用JSP或者Thymeleaf。虽然那套服务端渲染的技术学起来更简单但现在的就业市场和技术社区已经几乎全面倒向前后端分离架构了。如果你是冲着巩固技术去的直接上手Vue SpringBoot这种组合你不会后悔。2. 数据库设计与表结构解析一个项目的质量一半看数据库设计。很多网上流传的“源码”为了避免复杂关联查询把能塞进一张表的字段全部塞进去查起来确实是方便了但后端的业务逻辑尤其是统计那一块写起来会痛不欲生。我这里给出的设计是在方便查询和规范化之间取了一个很好的平衡点。2.1 核心数据表清单我习惯把表分成三类基础数据表用户、角色、楼房、业务数据表房源、合同、账单、报修、关联数据表用户角色关联、房源图片关联。数据表名存储内容设计要点sys_user用户账号信息主键、用户名、密码BCrypt加密、姓名、手机号、角色标识、头像、创建时间building楼栋信息楼栋编号、楼栋名称、地址、楼层数house房源信息所属楼栋ID、门牌号、户型、面积、月租金、押金、出租状态、房源描述customer租客信息关联用户ID、真实姓名、证件号码、紧急联系人contract租赁合同合同编号、关联房源ID、租客ID、起止时间、租金、押金、状态bill账单关联合同ID、账单类型租金/水费/电费/物业费、金额、计费周期、状态repair报修工单关联房源ID、报修人、报修内容、状态、维修师傅house_image房源图片关联房源ID、图片URL、排序号我不建议把租客信息直接写在sys_user表里再添一个is_tenant字段因为租客会有很多跟用户登录无关的属性比如紧急联系人、证件号、工作单位等拆出一张customer表来更清晰。另外如果你做的系统里有房东这个概念那大可以再拆一张landlord表本质逻辑是一样的。2.2 关键表的字段设计与关联关系我重点讲三张表house、contract、bill因为这三张表是整个系统的核心链路。house表最需要注意的是“冗余冗余”和“状态状态”。门牌号、户型、面积这些是静态信息。月租金和押金是金额字段用DECIMAL(10,2)存储这里强调一下金额一律不用float或double否则后续计算对账会出现满地打滚的精度错误面试官也喜欢揪着这点问。房源状态字段建议使用tinyint类型0表示空闲1表示已预订2表示已出租3表示维修中。不要用字符串来存状态因为用数字枚举既可以做索引优化也能减少因为大小写不一致导致的查询bug。contract表的关联关系是整个数据库设计的核心。表中必须冗余一份房源快照信息即合同里的“房屋坐落”要记录当时的房源地址和租金标准。这意味着如果几个月后房东给房子涨价了已经生效的合同金额不能跟着变。有些课设系统直接在合同里只关联一个house_id前端查询时再去关联查询house表里的最新租金这是不对的。正确的做法是在生成合同时把租金、押金、房源地址都复制一份存进contract表这就是所谓的“快照”思想。contract表里还需要有起租日期start_date和退租日期end_date这两个日期是计算账单周期的依据。bill表我加了一个period_start和period_end字段用来记录这笔账单是几月份到几月份的。很多初学者的账单表里只有一个create_time到了月底做统计的时候想查清楚“这个月到底应该收多少钱”的时候就傻眼了。有了计费周期后面写SQL做月统计就会变得非常轻松。bill表还有一个bill_type字段1代表房租2代表水费3代表电费4代表物业费。为什么要用1、2、3、4而不是直接写汉字因为后续要写按月分组的group by聚合查询用数字代表枚举比在SQL里匹配字符串要优雅得多。2.3 状态字段的枚举约定这一部分很值得展开说因为很多同学把后端代码跑通了却发现前端显示的状态乱套了比如列表里出现数字0、状态1前端却没法对应显示文字。这就是因为没有统一定义枚举值。我一般会新建一个枚举类或者至少在一个常量类里把所有状态定义好。举个例子public class HouseStatus { public static final int FREE 0; // 空闲 public static final int ORDERED 1; // 已预订 public static final int RENTED 2; // 已出租 public static final int REPAIRING 3; // 维修中 }在Vue前端则用一个对象映射{0: 空闲, 1: 已预订, 2: 已出租, 3: 维修中}并通过Element UI的el-tag组件渲染不同颜色这样展示就一目了然了。这种做法统一了前后端的状态认知也方便写单元测试断言。2.4 数据库初始化脚本心态建设你自己下载的源码里可能会有一个扩展名为.sql的数据库脚本文件。运行这个脚本或者让项目自动执行它建库建表都对。我曾经遇到有同学PowerShell运行脚本时因为MySQL的character_set_server没有设置成utf8mb4表结构建出来之后在控制台查询中文全变成了???号这类问题先别急着怀疑脚本很大概率是数据库字符集设置的问题。这里我顺便区分一下两种建表方式一种是用MyBatis-Plus的代码生成器根据我们建好的表结构自动生成实体类、Mapper接口、Service和Controller这个能大大提高开发速度。另一种是手动写SQL建表然后再手写Java实体类和Mapper适合你想彻底搞懂映射关系的情况。我更推荐前者尤其是在你已经有了一份比较完善的数据库脚本的前提下直接用代码生成器生成基础CRUD再把时间花在复杂的业务逻辑上效率会翻好几倍。3. 后端核心模块的实现思路与关键代码拆解先泼一盆冷水网上流传的很多“房产租赁管理系统源码”后端部分其实是SSHSpring Struts Hibernate老项目改过来的虽然包结构叫com.example.rent但里面全是老的HibernateTemplate用法一旦你换了JDK版本跑都跑不起来。所以你拿到源码第一件事是检查pom.xml里的依赖版本是否符合你当前的Java环境。一般来说SpringBoot 2.x系列对应JDK 8或11SpringBoot 3.x系列对应JDK 17及以上。版本不对后面的所有折腾都会变得特别没意义。3.1 项目初始化与依赖引入假设你现在拿到的是一个基础骨架代码但还没有实现业务模块那么我建议你从零开始搭一个干净的后端工程。创建一个SpringBoot项目非常简单用IDEA的Spring Initializr直接生成即可。核心依赖就这么几个dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId /dependency配置文件里我踩过的坑要提一句。MySQL 8以上版本的驱动名变成了com.mysql.cj.jdbc.Driver并且时区建议显式设置为serverTimezoneAsia/Shanghai不然你连数据库的时候大概率会碰见一个无意义的“The server time zone value”报错。再加上一个characterEncodingutf8基本就稳了。server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/house_rent?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 1234563.2 登录认证与JWT权限控制登录是任何管理系统的第一道门这个模块也是面试官最爱问的。我的做法是用户输入用户名密码后后端用BCryptPasswordEncoder校验密码验证通过后生成一个JWT令牌返回给前端。令牌包含用户的ID、用户名、角色信息并设置一个过期时间一般是2小时或者24小时看你的业务紧张程度。生成JWT的常用库是io.jsonwebtoken的jjwt用法不算复杂核心就是一个生成和解析的过程。为了不把篇幅拖太长我讲一个实战中容易忽略的点JWT的无状态特性。服务端不保存登录状态所以修改密码、管理员禁用某用户后旧的JWT依然有效直到它过期。这对于课设项目足够了但面试官如果追问你可以主动提一下解决方案“可以通过维护一个token黑名单或者缩短token有效期来缓解”这样回答会显得你有思考。接着要配置Spring Security的过滤链。通常的思路是在SecurityConfig里放行登录接口、放行静态资源然后所有/api/**请求都走JwtAuthenticationTokenFilter在这个过滤器里解析Headers里的Authorization如果token合法就手动把用户信息放到SecurityContextHolder里放行请求。Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String authHeader request.getHeader(Authorization); if (authHeader ! null authHeader.startsWith(Bearer )) { String token authHeader.substring(7); String username JwtUtil.parseToken(token); if (username ! null SecurityContextHolder.getContext().getAuthentication() null) { UserDetails userDetails userDetailsService.loadUserByUsername(username); UsernamePasswordAuthenticationToken authentication new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); } } filterChain.doFilter(request, response); }这个过滤器是前后端分离项目的重点。很多同学搞不懂为什么前端明明登录成功了带着token来请求后端还是返回401。问题基本都出在过滤器没有成功把authentication对象塞进上下文里。排查的时候你先在doFilterInternal里打印一下parseToken返回的值是不是正常的用户名然后再看SecurityContextHolder里的内容是否被正确设置链路就会清晰。3.3 分页查询与条件检索实现房源列表页、合同列表页、账单列表页全都是需要分页的。MyBatis-Plus内置的分页插件用起来非常简单。在配置类里加一个MybatisPlusInterceptor并注册PaginationInnerInterceptor然后在Service层直接调用Page对象即可。千万别傻傻的自己写LIMIT (page - 1) * size, page * size虽然那样也能查但MyBatis-Plus做分页的时候还能自动帮我们做统计total少写一行SQL。条件检索是另一个高频需求。比如房源列表会有“区域”“户型”“租金范围”“状态”这些筛选条件。常规做法是构造一个LambdaQueryWrapper动态添加条件LambdaQueryWrapperHouse wrapper new LambdaQueryWrapper(); wrapper.eq(StringUtils.hasText(query.getCommunity()), House::getCommunity, query.getCommunity()); wrapper.eq(query.getStatus() ! null, House::getStatus, query.getStatus()); wrapper.between(query.getMinPrice() ! null query.getMaxPrice() ! null, House::getMonthlyRent, query.getMinPrice(), query.getMaxPrice()); PageHouse page houseMapper.selectPage(new Page(query.getPageNum(), query.getPageSize()), wrapper);这里最容易被忽略的就是对空值的判断。很多同学写eq(House::getStatus, query.getStatus())的时候不判断空用户在界面没有选状态传过来一个nullMyBatis-Plus就会生成WHERE status null结果查不出任何数据还很不好排查。加上StringUtils.hasText()或者ObjectUtils.isEmpty()的判断这个坑就避开了。3.4 核心业务时序从签约到账单生成这可以说是整个系统最重要的业务流。我之前见到的很多源码合同保存了房源状态却是“空闲”账单也一塌糊涂。所以我会把它们串成一个事务方法要么全部成功要么全部回滚。这个方法的大致逻辑是校验合同参数是否合法起止日期不能倒挂、押金和租金不能为负。查询house表确认该房源状态是“空闲”或“已预订”。保存合同合同状态设为“生效中”。更新房源状态为“已出租”。生成首期账单截至到当前月底的租金账单。若该房源有物业费也顺手生成一条物业费账单。代码层面需要给这个方法添加Transactional(rollbackFor Exception.class)注解。这个注解是Spring声明式事务的核心。不用它的话假设第4步成功更新了房源状态但第5步生成账单失败房源就被“已出租”了合同却不存在系统就产生了脏数据。首期账单的租金计算可以按天按比例算。举个例子合同起租日是2025年1月15日月租金3000元那么1月份的账单金额就是3000 / 31 * (31 - 15 1) 1645.16元。实现这个计算时我会用java.time.LocalDate提供的lengthOfMonth()方法自动获取当月天数避免自己维护天数数组。这个业务链路的代码在面试答辩时可以重点讲讲事务边界和控制逻辑这是明显区别于普通CRUD项目的加分项。3.5 统计报表的SQL技巧统计数据是很多同学觉得难的地方但其实掌握了聚合函数的用法剩下的就是套模板。我拿最常问的“过去六个月每月租金收入”举例。账单表bill有create_time和amount字段而且支付状态为“已支付”。SELECT DATE_FORMAT(pay_time, %Y-%m) AS month, SUM(amount) AS total_income FROM bill WHERE pay_status 1 AND pay_time DATE_SUB(CURDATE(), INTERVAL 6 MONTH) GROUP BY DATE_FORMAT(pay_time, %Y-%m) ORDER BY month;这个SQL配合MyBatis的Select注解或者Mapper XML文件直接返回一个Map列表即可前端ECharts拿到数据就能画柱状图。需要注意的是在Mapper接口里返回多个字段时用MapString, Object承接最省事。如果查询结果是单个字段则可以用ListString或者ListBigDecimal看情况选择即可。4. 前端Vue核心模块解析前端这一块我从一个“如何审阅和修改他人源码”的角度来展开因为这个场景对刚接触项目的同学来说更真实。你拿到一个现成的npm项目第一件事不是双击index.html而是先在终端里执行npm install装依赖然后npm run serve把前端服务跑起来默认它监听在8080端口。SpringBoot后端的端口我在配置里改成了8081或者在Vue的.env文件里配置VUE_APP_BASE_URL http://localhost:8081开发环境下利用Vue CLI的proxy代理把/api前缀转发到后端的8081端口从而规避跨域问题。4.1 路由与权限守卫Vue Router是前端项目的主心骨。管理后台的路由通常写成嵌套路由形式比如/layout下面挂载/layout/house、/layout/contract、/layout/bill这些子页面。路由要想清楚用户没登录时访问/layout/house应该被导航守卫拦下来跳回到登录页。经典的导航守卫写法router.beforeEach((to, from, next) { const token localStorage.getItem(token); if (to.path /login) { next(); } else { if (!token) { next(/login); } else { next(); } } });这样写对普通用户够用但有个问题如果管理员和租客权限不一样页面菜单还需要动态渲染。我只用过一个简单有效的方法后端登录接口会把用户角色返回给前端前端存到Vuex里。然后根据不同的角色在渲染侧边栏的时候过滤路由表。不需要太复杂的动态路由注册只需要维护两套不同的菜单配置数组权限不是特别精细的课设项目完全够。4.2 与后端接口的交互与状态管理前端和后端交互全靠Axios。我这里建议做一层简单的封装统一设置baseURL和请求头。业务代码里只要引入自己封装好的request模块就行不用在每个组件里重复写axios.create()。拦截器里有一个容易遗漏的细节响应拦截器里如果发现HTTP状态码是401或者后端返回的业务码是401就应该把本地缓存的token清掉并跳转到登录页否则用户登录过期后页面会一直停留在假死状态。数据请求这块很多同学会问要不要用Vuex。我的回答是全局唯一的数据比如用户信息、角色权限、系统配置放在Vuex里管理是合适的。但像房源列表、账单列表这种页面级的数据直接放在组件的data()里即可没必要塞进Vuex否则会平白增加调试成本。不要为了用Vuex而用Vuex一切以“是否会被多个组件共享”为判断标准。4.3 核心页面拆解房源管理列表房源管理页是后台系统里最典型也最复杂的列表页面。一个合格的管理页面应该包含搜索区、表格区、分页器和操作按钮组。搜索区一般是几个筛选组件比如关键字输入框、状态下拉框、价格范围InputNumber。点击“查询”按钮时把筛选条件赋值给searchParams然后重新加载列表数据。表格区用el-table渲染列包括房源编码、门牌号、户型、面积、月租金、状态、操作栏。状态列我一般会用el-tag配合状态映射对象来展示状态不同颜色不同。操作栏里的按钮根据状态做显隐控制比如“空闲”状态下显示“出租”按钮“已出租”状态下显示“退租”按钮。这一页最核心的交互是点击“出租”之后弹出一个表单要求填写租客信息和合同信息。这个逻辑从后端来看就是3.4节讲的签约事务接口。所以前端页面和后端接口一定要配合着看你才不会觉得代码是东拼西凑的。4.4 表单校验的细节Element UI的表单校验我见过太多人都只是把required属性设置为true就完事了但这里有两个高频坑。第一个是数字类型校验。月租金和押金用的是el-input-number返回的值是数字类型如果校验规则里写了type: number但输入框里初始值是空字符串校验就会一直失败。解决方法是初始化数据时直接把字段设为undefined。第二个坑是自定义校验函数的手写逻辑。比如校验身份证号长度是18位或者校验手机号是否符合/^1[3-9]\d{9}$/。Element UI的表单校验支持写validator函数但很多同学不熟悉怎么写就直接放弃校验了。其实写法很简单const validateMobile (rule, value, callback) { if (!value) { callback(new Error(手机号不能为空)); } else if (!/^1[3-9]\d{9}$/.test(value)) { callback(new Error(手机号格式不正确)); } else { callback(); } };这种校验规则写好后提交表单时表单组件会自动执行校验。当校验不通过时不会请求后端这可以拦截掉大部分无效数据也给后端减轻了压力。交互体验上明显更专业。4.5 ECharts统计页的实现建议统计页是给项目增色的地方。使用ECharts的柱状图展示近6个月收入用饼图展示房源租赁状态分布。前端实现起来不复杂先安装echarts依赖然后在mounted生命周期中请求后端统计接口拿到数据后调用echarts.init()初始化图表并设置option。需要提醒的是用v-if控制统计图表的DOM渲染否则在数据还没回来的时候图表容器高度为0或隐藏状态ECharts初始化就会失败图表显示不出来。还有一个常见问题是窗口大小改变后图表不会自适应需要在window.addEventListener(resize)里调用chart.resize()并且在组件销毁前移除监听以免内存泄漏。5. 常见问题与排查技巧实录这部分是我最想跟你分享的。很多代码你自己都能写出来但跑不起来的时候能不能靠自己找到问题才真正体现工程能力。我整理了运行这种前后端分离项目时遇到频率最高的五个问题以及对应的定位思路。5.1 前端请求后端接口报跨域错误这个问题在开发环境里几乎人人都会碰到。浏览器控制台报Access-Control-Allow-Origin错误或者请求的Request URL是http://localhost:8080/api/...但端口和前端不一致。解决办法有两种。第一种利用Vue CLI的代理功能。在vue.config.js里写module.exports { devServer: { proxy: { /api: { target: http://localhost:8081, changeOrigin: true } } } };这样开发环境/api开头的请求会被代理到后端浏览器里看到的接口地址是前端自己的域名跨域就自然消失了。第二种方法在后端配置一个CORS过滤器允许指定来源跨域。虽然开发时能用但不太建议用作最终解决方案因为上线后前后端大概率部署在同一个域下代理方式更简洁。5.2 数据库连接失败或中文乱码连接失败的原因无外乎三类MySQL服务没启动application.yml里账号密码和本机不一致驱动类和URL模板不对。排查的时候先去服务管理器或者命令行确认MySQL在运行再检查配置文件是否用了正确的连接串。中文乱码绝大部分是因为数据库或表的字符集不是utf8mb4。检查SQL脚本里有没有DEFAULT CHARSET utf8mb4。如果表已经建出来了可以用ALTER TABLE table_name CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;补救一下。SpringBoot的连接URL里的characterEncodingutf8不要省略双保险。5.3 分页插件不生效我的一个朋友遇到过特别邪门的问题前端的每页条数和页码都传得好好的但MyBatis-Plus的selectPage返回的对象里records却是一整张表的所有数据。最后发现是他的配置类里没有注入分页插件。MyBatis-Plus的分页功能已经不是默认的内置行为了必须显式声明Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }没有这个配置SQL里不会自动拼接LIMIT所以看起来就是“分页失效”。这个问题的排查思路很有代表性因为MyBatis-Plus的报错不会很明显你得对框架的运行机制有了解才能想到。5.4 上传的房源图片无法访问很多课设系统都会实现图片上传。但如果你把图片直接存在了前端项目的src/assets里跑npm run build打包之后图片路径就会被修改或者因为前端路由的history模式导致资源访问不到。我的建议是图片上传接口在后端接收MultipartFile之后保存到后端服务器一个独立的upload目录下例如/usr/local/upload然后通过后端的静态资源映射对外提供访问。在WebMvcConfigurer里配置Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceHandler(file: uploadPath /); }这样一个房源图片的URL就是http://localhost:8081/upload/xxx.jpg前端直接拿这个地址即可展示完全绕开前端打包的路径问题。如果你只是做课设本地替代方案也简单把图片存到前端项目的public/uploads目录下打包后public里的文件会被复制到dist根目录路径也能访问。但这只限于小项目真要上线还是后端存储靠谱。5.5 定时任务生成账单的实现每月1号自动生成当月房租账单这个功能发生频率高逻辑重复性也强非常适合用SpringBoot的定时任务。在主启动类上加EnableScheduling注解再写一个Component类里面用Scheduled(cron 0 0 0 1 * ?)标识一个方法这样每月1号0点0分0秒就会执行一次。方法逻辑是查询所有状态为“生效中”的合同过滤掉那些在当月月初已经退租的然后检查当月账单是否已经生成过防止重复生成。这里要特别注意幂等性用bill表的period_start和contract_id做唯一索引即可。如果忘了做幂等定时任务一旦手动执行两次租客就会收到两笔一模一样的房租账单这个bug在答辩时被老师发现是非常尴尬的。定时任务这块很多网上源码根本没实现算是一个加分功能。你可以根据自己的情况决定要不要自己加一下毕竟实现成本并不高。6. 部署上线与部署文档的编写思路项目做完了最终总得能跑起来给别人看。很多同学到这一步才开始头疼因为平时开发都用IDEA一键启动换到服务器上或者在别人电脑上却跑不起来了。部署虽然不难但也值得系统说一下。6.1 本地双端启动本地环境下后端就是IDEA里点Run前端就是终端npm run serve。但有些同学会发现把IDEA项目分享给室友后室友那边跑不起来报错说“程序包com.baomidou.mybatisplus不存在”。这是因为SpringBoot项目用Maven构建依赖没有下载全或者本地Maven仓库缓存损坏。解决办法很简单把本地仓库~/.m2/repository里的相关目录删除重新导入项目让Maven重新拉取依赖即可。如果前后端是同一个机器注意端口不要冲突。后端我习惯用8081前端用8080。如果在同一个端口上那就只能跑一个另一个必然启动失败这是很常见的新手问题。6.2 服务器部署真正的生产部署核心是后端打包成Jar包前端打包成静态文件。后端的打包命令是mvn clean package -DskipTests打包出来的Jar包用简单的命令即可运行nohup java -jar house-rent.jar --spring.profiles.activeprod app.log 21 前端打包是npm run build会在dist目录下生成静态文件使用Nginx托管即可。Nginx配置里需要两个关键点一是把location /指到dist目录二是把/api开头的请求反向代理到后端接口地址。server { listen 80; server_name your-domain.com; root /usr/share/nginx/html; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://localhost:8081; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }我这里提一句try_files $uri $uri/ /index.html;非常重要。因为Vue Router如果启用了history模式前端路由/contract在服务器上并没有对应的物理文件如果不加这条配置刷新页面就会404。加上之后所有请求都会回退到index.html前端路由兜底接管。6.3 项目文档的写法一个完整的课程设计或毕业设计除了代码还要有配套的文档。文档一般包含需求分析、系统设计、数据库设计、核心代码说明、系统测试、运行环境说明这几个部分。如果你下载的源码里自带了文档你也不能直接交上去这既是学术规范问题也会在答辩时露出破绽。我的建议是自己真实地跑一遍项目把每一步过程和结果截图替换到文档中再结合自己的理解重新组织语言。文档里特别值得花时间写的一个是数据库设计说明书一个是你自己实现的难点解决部分。前者用PowerDesigner或者Navicat的模型图直接导出简单高效。后者把你遇到的最棘手的bug或业务逻辑写清楚这样答辩时既展示了自己的工作量又让评审老师觉得你有独立解决问题的能力。7. 从项目源码中学习简历与答辩准备我知道很多人做这个项目终极目标还是简历上能写一笔、答辩能顺利过。那这个项目到底能不能写进简历怎么写才有亮点7.1 避免“简历只看CRUD”的评价很多人的简历里写着“熟悉SpringBoot、Vue”但提到项目就是“实现用户管理、房源管理、合同管理”这种描述在面试官眼里跟没写一样。要写出亮点你必须在项目描述里突出技术和业务的结合点。比如“设计并实现了基于状态机的房源状态管理覆盖预约、签约、退租完整流程保证数据一致性”再比如“基于Spring定时任务实现每月账单自动生成并使用唯一索引保证幂等”或者“针对前后端数据交互设计了基于JWT的双端认证机制”。这些描述显得你在思考技术背后的原理而不是单纯在调用框架。7.2 答辩常问的问题准备答辩和面试时围绕这个项目的问题翻来覆去就那么几个我提前把答案的要点列出来你心中有数即可。第一题你这个系统解决了什么痛点你要回答传统纸质合同管理混乱、收租周期不透明、房源状态不清晰而系统通过线上化、数据化和自动化来解决。第二题为什么选MyBatis-Plus你要说它减少了手写SQL的工作量提供单表CRUD的通用方法并且内置分页插件但对复杂统计查询仍然保留了XML自定义SQL的能力兼顾效率与灵活。第三题如果并发量大你的系统有哪些瓶颈你可以说如果未来用户量增长可以考虑引入Redis缓存房源热门数据和用户Token以及把数据库读写分离或者引入消息队列来处理账单生成然后补充说当前课程设计版本主要专注业务闭环的实现。第四题数据库为什么要加逻辑外键而不是物理外键你要说物理外键在删除数据时会带来巨大约束开销不利于分库分表日常通过Java代码控制关联和数据一致性因此设计中以逻辑外键为主。这个问题比较深能答上来会加分。7.3 功能扩展的方向项目如果时间充裕可以自行扩展两个方向。一个是“对接地图组件”比如接入高德地图API展示房源位置这在小程序或移动端场景里非常实用。另一个是“消息通知”租客提交报修后通过WebSocket给管理端推送一个实时通知气泡这种细节放在答辩演示现场很容易出效果。再有就是“移动端适配”。虽然管理后台是PC端的但很多租赁平台已经开始强调移动端体验。后续可以把租客端抽出来用Vue 3 Vant组件库做一个H5版让租客在手机上就能看房、签约、付租金。这会让你的项目在架构上更完整也能体现出你有一定的产品思维。写在最后的个人体会做这个房产租赁管理系统我的最大感受是它不是一个单纯的编码练习更像是一场“如何把现实业务搬到线上”的完整训练。从第一行建表SQL开始你就在一次又一次地做权衡。房源状态冗余到合同里是为了历史可追溯账单里记录计费周期是为了统计不头疼后端接口返回统一JSON结构是为了前端少出bug。这些决定课本上不会直接教你但在真实开发中都会遇到。如果你手上已经有一套可以运行的源码我特别建议你花一个下午干一件事顺着“新租客注册 - 查看房源 - 签约 - 支付首期账单”完整走一遍然后去数据库里看看每一张表里的数据变化。你可能会发现哦原来签订合同那一步后端一口气更新了房源状态、生成了账单、写入了合同记录这三个动作是绑定在一个事务里的。这个发现会比你看十篇教程都有用。最后再分享一个小技巧项目里所有的日期处理无论是合同的起止日期还是账单的周期计算都统一用LocalDate和LocalDateTime不要用老的java.util.Date。这样你在做日期加减、比较、格式化时会轻松得离谱。这个习惯值得从现在开始养成。
返回列表