
一个老博主盯着书架上的毕设选题列表给你划划水如果你看见“出租屋管理系统”这个题目心里第一反应是“又老套又没新意”那我劝你把它从弃题堆里捡回来。计算机毕设选Java方向做出租屋管理系统说白了就是用SpringBoot搭一个管房源、管合同、管账单、管报修的全流程后台这题目比商城、博客、图书管理这类“烂大街款”更容易做出完整业务闭环也更容易在答辩时把“业务逻辑复杂度”讲明白。这篇文章我会顺着一个实际能跑通的SpringBoot出租屋管理平台,把选题思路、表结构设计、核心业务实现、难点避坑一条龙拆开顺便给你一套可以直接拿去做毕业设计的落地套路。适合正在选毕设题目的本科生、准备学SpringBoot实战的小白以及想在简历里塞一个真实管理系统的后端初学者。1. 项目为什么值得做选题分析与需求定位1.1 出租屋管理系统在毕设中的适配度分析选毕设题目有个隐形标准业务要够复杂但又不至于做不完。商城系统你得处理订单状态、秒杀并发、支付回调业务链路太深博客系统CRUD写到底也就那样答辩时没什么可深挖的点。出租屋管理系统恰好卡在中间它既有“房东-租客-房源-合同-账单-报修-退租”的多角色业务线又有状态流转、费用计算、时间节点判断这些一看就懂、一拆就多的逻辑点非常利于在论文里画业务流程图和用例图。从学生和导师的双重视角看这套题目有几个实打实的优势数据模型清晰核心实体就那十来个但每个实体之间都有强关联外键关系能说的东西很多。业务闭环完整从房源发布、看房登记、合同签约、入住抄表到每月账单生成、租金收缴、报修处理再到退租清算、押金退款一整条链路能覆盖大部分J2EE课程里的知识点。技术展示面广SpringBoot做接口MyBatis Plus做数据库操作Spring Security做登录鉴权定时任务做账单自动生成Redis做缓存这些技术栈在简历上一摆面试官看完至少知道你不是只会写增删改查。数据可视化好做按月度统计租金收入、房间入住率、水电费消耗趋势ECharts图表一上答辩展示效果直线拉满。我见过太多学生做完这个题目之后论文里画业务流程图的时候才把整个项目逻辑理清楚。这也是好事说明这个题目的复杂度足够撑起一篇论文的框架不会出现“提纲列了三章内容就写完了”的尴尬。1.2 核心需求拆解全流程管理到底管哪些“全流程”这三个字是题目的灵魂。同样是出租屋管理你只做几个CRUD页面和做一个能运转起来的业务系统在答辩得分上完全是两个量级。我习惯把这个项目拆成六大模块来规划系统管理用户登录、权限控制、菜单管理、操作日志。这个模块是标准的配合基数。房源管理楼栋、房间、房间状态空置/已入住/维修/预定、房源配置家具电器、面积、朝向、租金单价。租客管理租客基本信息、身份证件上传、紧急联系人、历史租住记录、租客黑名单。合同管理合同创建、签订、生效、续租、退租、作废租期时间的自动计算押金管理。账务管理每期账单租金水电费管理费、收款记录、欠费提醒、押金收支、财务报表。报修与日常运营租客提交报修、指派维修工、维修进度跟踪、评价反馈、退租验房。如果你看到这里觉得模块有点多、不知道从哪下手那正好说明这题选对了。毕业设计最怕的不是模块多而是模块少导致论文单薄、代码量不够。六大模块拆完之后前后端加起来四十张页面左右代码量大概七八千行工作量适中做起来心里也有底。注意答辩时老师最爱问的一句话是“你这个系统里最难的部分是什么”。如果选这个题目最稳的回答方向就是合同与账单之间的联动逻辑比如租期结束账单怎么算、中途退租怎么清算。这个点必须做扎实后面会详细拆解。2. 技术栈选型的底层逻辑2.1 SpringBoot到底给开发带来了什么现在的Java Web毕设基本绕不开SpringBoot这不是赶时髦而是这东西确实把开发流程变得友好太多。你用原生Servlet写接口配置一个web.xml都够折腾一晚上用SpringMVC加XML配置光“配置文件地狱”就能劝退一批人。SpringBoot的核心价值在于自动配置和管理依赖版本。具体到这个项目里SpringBoot带来的最直接的好处是三个内置Tomcat不用单独部署服务器一个Main方法启动方便快速调试也方便答辩现场演示。starter机制引入spring-boot-starter-web、mybatis-plus-boot-starter等依赖就能直接开写不用管各种jar包版本兼容。配置文件极简application.yml一个文件搞定数据源、Redis、日志、文件上传路径等所有配置。我个人的建议是版本别追求最新。SpringBoot选2.7.x或者3.x早期稳定版就行太高的版本会带来不必要的兼容问题。下面是一份干净的基础pom配置parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-security/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId /dependency /dependencies2.2 为什么选MyBatis Plus而不是JPA同样都是ORM框架MyBatis Plus在这个场景里比Spring Data JPA更合适。原因很实际出租屋管理涉及大量动态条件查询你要按楼层筛、按状态筛、按租金区间筛、按租客姓名模糊搜这种查询如果用JPA写Specification上来就是一堆类逻辑散得一塌糊涂。MyBatis Plus的QueryWrapper和LambdaQueryWrapper直接链式拼接条件代码和SQL直觉都对得上。比如列表页的筛选查询用MyBatis Plus写起来是这样Override public IPageRoomVO queryRoomPage(RoomQueryDTO query) { PageRoom page new Page(query.getCurrent(), query.getSize()); LambdaQueryWrapperRoom wrapper new LambdaQueryWrapper(); wrapper.like(StringUtils.hasText(query.getRoomNo()), Room::getRoomNo, query.getRoomNo()) .eq(query.getBuildingId() ! null, Room::getBuildingId, query.getBuildingId()) .eq(query.getStatus() ! null, Room::getStatus, query.getStatus()) .ge(query.getMinRent() ! null, Room::getRentPrice, query.getMinRent()) .le(query.getMaxRent() ! null, Room::getRentPrice, query.getMaxRent()) .orderByDesc(Room::getCreateTime); IPageRoom roomPage roomMapper.selectPage(page, wrapper); // 手动组装VO把楼栋名称、当前租客信息填进去 return convertToVO(roomPage); }这样的一屏逻辑只要你会写SQL就能看懂查问题也方便。再者MyBatis Plus的代码生成器对毕设简直是救星根据数据库表结构一键生成实体类、Mapper、Service、Controller能省掉至少一天的机械性劳动。2.3 前端方案怎么搭配更稳毕设前端常见的就两条路线一是服务端渲染用Thymeleaf模板二是前后端分离用Vue。我说句实在话如果你前端基础一般建议选Thymeleaf因为它学习成本低、不受跨域问题困扰、写起来就像直接拼HTMLSpringBoot集成Thymeleaf很简单加上Spring Security的会话控制整个项目链路是通的。如果你有Vue基础想做个漂亮点的界面那就上Vue Element Plus。走这条路线要注意处理跨域问题和打包部署问题开发环境配个代理生产环境打成jar包之后想办法把dist文件塞进SpringBoot的static目录里。下面是我的推荐部署方式# 前端项目打包 npm run build # 拷贝dist目录到后端resources/static中手动或maven插件自动做 # 然后直接启动就一个jar包搞定前后端 java -jar rental-house-system.jar这样做的核心原因是答辩现场只需要开一个程序就演示完整系统不会因为“前端npm跑不起来”翻车。这种事我见过不止三次切记找个稳妥的方案。3. 数据库设计与核心表结构3.1 从业务出发设计数据模型数据库设计是整个出租屋管理平台的地基也是你论文里ER图的核心素材。我画表之前会先从一句话业务描述出发房东拥有多栋楼每栋楼有多个房间租客可以租住多个房间但一个房间一个时间段只能有一个有效合同。围绕这个描述主表定下来就是用户表、楼栋表、房间表、租客表、合同表、账目表、报修表、操作日志表。各类表之间的关联用逻辑外键就是存个id不加数据库物理外键。因为毕设项目数据量不大物理外键的约束收益很低反而会因为删除顺序、更新级联这些问题导致线上调试的时候满屏报错。物理外键会让你的删改操作变得很僵硬逻辑外键配代码层校验才是现代开发普遍采用的做法。用户表我建议做成一张表多角色区分用一个字段存角色类型业主/租客/管理员/维修工不搞users和roles多对多那种标准RBAC的五张表。原因很简单这是毕设重点是业务完整权限在Controller层用注解或拦截器控制就够了别把复杂度堆到那种不对答辩有帮助的地方。3.2 核心表的字段设计实战房间表是整个系统的枢纽字段设计直接决定后续业务的难易程度。我按毕设经验建议至少包含这些核心字段字段名类型含义说明idbigint主键building_idbigint所属楼栋room_novarchar房间编号如A栋301room_typevarchar房型单间/一室一厅/两室一厅等areadecimal面积平方米directionvarchar朝向南/北/东/西rent_pricedecimal月租金depositdecimal押金通常为两个月租金statustinyint房间状态0空置1已入住2维修中3预定facilitiesvarchar家具配置如空调,热水器,床,衣柜create_timedatetime创建时间合同表的字段要更讲究一点因为牵涉费用和租期计算。我强烈建议把“约定月租金”“押金金额”“水电表底数”“实付押金”这些业务快照字段冗余到合同表里。为什么因为房间的租金是会调整的合同签的时候是1500一个月三个月后房东改成了1600如果合同里不存租金快照到时候算账你根本说不清该按哪个数收。合同核心字段清单lease_no合同编号格式例如LZ20250615001room_id房间IDtenant_id租客IDstart_date / end_date租赁起止日期rent_amount月租金快照deposit_amount押金快照water_base / electricity_base签约时水电表底数status生效中/已退租/已作废/已到期sign_time / check_in_time / check_out_time这种把业务快照冗余进关联表的设计思路等你以后进公司做订单系统也会遇到提前在毕设里体会一遍很值。3.3 房间状态机设计与流转规则房间状态别看只是一个小小的tinyint它的状态流转逻辑是答辩时最能体现业务理解深度的细节。房间的合法状态流转应该被限定成这样0 空置 → 1 已入住签约且房租首期到账0 空置 → 3 预定租客交了定金但尚未签约1 已入住 → 2 维修中只有退租验收后或租客暂时搬出时才能发生2 维修中 → 0 空置维修完成重新上架这个流转绝对不能乱。比如房间还在维修中系统就不允许新建合同房间有人住着房东想在后台改房间状态直接被代码挡下来。这些校验逻辑看起来简单但很多毕设选手根本没写结果就是数据混乱答辩演示一改状态全崩。状态流转的校验我习惯写个统一的Service方法private void assertRoomStatusChange(Long roomId, RoomStatusEnum from, RoomStatusEnum to) { Room room roomMapper.selectById(roomId); if (!from.getCode().equals(room.getStatus())) { throw new BizException(房间当前状态不允许该操作); } // 比如从空置到已入住必须存在有效合同 if (to RoomStatusEnum.OCCUPIED) { long cnt contractMapper.selectValidContract(roomId, LocalDate.now()); if (cnt 0) { throw new BizException(房间不存在有效合同无法标记为已入住); } } }同时房间状态变更记录我建议单独存一张room_status_log表记录“哪个房间、从什么状态、变成什么状态、操作人、操作时间”。这也是毕业设计里添加“亮点”的好地方写论文时你可以说是为了“保证数据的可追溯性”。4. 核心功能模块开发实录4.1 基于JWT的登录认证与权限控制登录模块用Spring Security加JWT是很常见的组合。思路是用户输入账号密码认证通过后返回一个token前端每次请求在Header里带上后端过滤器解析token后把用户信息放进上下文。对于毕设来说重点是要把“不同角色看到的菜单不一样”这个权限差异化体现出来。技术选型上JWT替代session的好处是服务端无状态前后端分离时不用处理session共享问题。但对于毕设答辩来说最直观的好处是你能现场解释“token过期时间”“Refresh Token”这些概念这是妥妥的加分项。核心代码骨架如下Slf4j Component public class JwtAuthenticationFilter extends OncePerRequestFilter { Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { String header request.getHeader(Authorization); if (StringUtils.hasText(header) header.startsWith(Bearer )) { String token header.substring(7); try { Claims claims Jwts.parser() .setSigningKey(secretKey) .parseClaimsJws(token) .getBody(); LoginUser loginUser new LoginUser(); loginUser.setUserId(Long.valueOf(claims.get(userId).toString())); loginUser.setUsername(claims.get(username).toString()); loginUser.setRole(claims.get(role).toString()); // 放入SpringSecurity上下文 UsernamePasswordAuthenticationToken authentication new UsernamePasswordAuthenticationToken(loginUser, null, null); SecurityContextHolder.getContext().setAuthentication(authentication); } catch (Exception e) { log.warn(token解析失败{}, e.getMessage()); } } chain.doFilter(request, response); } }权限控制可以在Controller加自定义注解比如RequireRole(admin)用拦截器判断当前用户角色。别上Spring Security的PreAuthorize那一套复杂表达式又不是做生产级系统答辩时讲得清楚才是第一位的。4.2 房源管理与筛选查询的核心实现房源管理的核心就是房间列表的多维查询和房间状态的实时展示。前端页面一般是一个表格上面一排筛选条件楼栋下拉框、状态下拉框、质量带宽、租金范围、房型选择、关键词搜索框。后端就是Service层动态拼装条件。“筛选组合”的关键在于空值判断。QueryWrapper提供了很多eq和like的条件重载当参数为null时会自动忽略这个条件逻辑非常清爽。但要注意like查询的字段空值处理像roomNo有时候存的是“A栋301”有时候是“A-B-301”这种不规范数据胸外前台输入的搜索词根本匹配不上这种情况建议在房间号存到表里之前先做一次格式规范化比如统一大写、统一空格处理。另一个要点是VO和Entity分离。Room表里存的是楼层ID、楼栋ID前端要展示的是“楼栋名称”“当前租客姓名”。如果你把关联信息都塞进Room实体不但查询慢代码结构上也乱。简单做法是列表接口返回RoomVO内部手动一对一拆装配。数据量小拼装无压力。RoomVO convertToVO(Room room) { RoomVO vo new RoomVO(); BeanUtils.copyProperties(room, vo); Building building buildingMapper.selectById(room.getBuildingId()); vo.setBuildingName(building ! null ? building.getName() : -); // 查询当前有效合同对应的租客 Contract contract contractMapper.selectValidByRoomId(room.getId(), LocalDate.now()); if (contract ! null) { Tenant tenant tenantMapper.selectById(contract.getTenantId()); vo.setCurrentTenantName(tenant ! null ? tenant.getRealName() : ); } return vo; }4.3 合同续租与退租清算最容易翻车的模块合同管理是整个系统的重头戏其中续租和退租又是真正的业务难点。这里我经历过不少翻车现场仔细说一下。续租的常规操作是原合同到期前一个月内租客或房东发起续租系统生成一份新合同新房期的起止日期从原合同结束的次日开始算租金如果不变就不用交新押金如果租金变了需要补足押金差额。数据库层面新老合同都保留老的标记为“已到期”新的状态为“生效中”。退租清算是更复杂的动作。退租当天系统要做的事情包括生成退租验房单、计算水电费差额、核算未结账单、计算应退押金。公式大概是应退押金 实交押金 - 欠费金额 房间损坏赔偿金额 未到期违约金水电费差额 退租时水表读数 - 合同水表底数 * 单价这些计算逻辑如果没有一张“退租清算表”承接全靠临时算就非常容易出bug。建议设计表结构时加一张checkout_record表把退租时涉及的各项原始数据都存起来比如“退租电表读数”“退租水表读数”“损坏扣款”“违约金金额”“实退押金”等。清算计算是API必须用事务保证数据一致性Transactional(rollbackFor Exception.class) public CheckoutVO handleCheckout(CheckoutRequest request) { Contract contract contractMapper.selectById(request.getContractId()); // 1. 校验合同处于生效状态且当前晚于结束日期或提前退租 // 2. 计算水电费用 BigDecimal meterWaterFee calculateWaterFee(contract, request.getWaterReading()); BigDecimal meterElectricFee calculateElectricFee(contract, request.getElectricReading()); // 3. 汇总未结账单 ListBill unpaidBills billMapper.selectUnpaidByRoomId(contract.getRoomId()); BigDecimal totalDebt unpaidBills.stream() .map(Bill::getAmount) .reduce(BigDecimal.ZERO, BigDecimal::add); // 4. 计算押金退还 BigDecimal depositRefund contract.getDepositAmount() .subtract(meterWaterFee).subtract(meterElectricFee) .subtract(totalDebt).subtract(request.getDamageAmount()); // 5. 更新合同状态、房间状态写入清算记录 // 6. 生成退租记录及退款单 }注意金额计算必须用BigDecimal千万别用double或float。Java里double做减法会出现0.30000000000000004这种惊悚结果。我见过有学生在答辩演示时退租算押金算出一个无限循环小数当场社死引以为戒。4.4 水电费账单自动生成与定时任务每个月的账单生成可以做成手动触发也可以做成定时任务自动跑。毕设项目建议两者都支持系统提供一个“生成本月账单”按钮同时配置一个每月1号凌晨自动执行的定时任务。定时任务用Spring自带的Scheduled就够了不需要上Quartz这种重框架。账单生成逻辑比较复杂的是“按天分摊”。比如租客6月15日入住7月1日系统生成账单他应该交的租金是6月16日到6月30日共15天外加7月整月的预缴租金。这种场景在毕业设计里属于业务细节加分项虽然很多学生懒得做但建议硬着头皮实现一下。Scheduled(cron 0 0 1 1 * ?) public void generateMonthlyBills() { LocalDate today LocalDate.now(); LocalDate currentMonth YearMonth.from(today).atDay(1); // 查询所有生效合同 ListContract activeContracts contractMapper.selectActiveOnDate(today); for (Contract contract : activeContracts) { // 计算本月账单周期 LocalDate billStart contract.getStartDate().after(currentMonth) ? contract.getStartDate() : currentMonth; LocalDate billEnd currentMonth.plusMonths(1).minusDays(1); if (billEnd.isAfter(contract.getEndDate())) { billEnd contract.getEndDate(); } // 根据天数分摊月租金 BigDecimal proportionalRent contract.getRentAmount() .divide(BigDecimal.valueOf(30), 2, RoundingMode.HALF_UP) .multiply(BigDecimal.valueOf(ChronoUnit.DAYS.between(billStart, billEnd) 1)); // 插入账单记录 } }分摊逻辑虽然不复杂但涉及日期边界、大小月、跨年这些问题测试时容易踩坑。建议写几个单元测试覆盖“每月1号生效”“月中生效”“月底生效”“2月”这几种情况确表服务跑得稳答辩时也能跟老师说“我做了单元测试”。4.5 数据可视化让答辩PPT自带亮点数据可视化模块是很多学生最容易忽略、但最出效果的一部分。建议做四个维度的统计每月租金收入趋势图、房源入住率折线图、水电费月度消耗柱状图、逾期未缴费租客列表。这些图用ECharts画后端提供统计接口不用额外技术。统计接口最简单的写法是直接写SQL去group by月份或楼栋映射成DTO返回。比如当月租金收入统计SELECT DATE_FORMAT(bill_month, %Y-%m) AS month, SUM(rent_amount) AS total_rent, COUNT(*) AS bill_cnt FROM bill WHERE bill_month DATE_SUB(CURDATE(), INTERVAL 6 MONTH) AND status 2 GROUP BY DATE_FORMAT(bill_month, %Y-%m) ORDER BY month;这块内容是答辩时展示起来最直观的。老师看到图表第一反应就是“这个系统有运营分析的意识”比光看CRUD表格加分太多。5. 排查避坑毕设过程中容易踩的坑5.1 SpringBoot版本、依赖冲突与启动失败SpringBoot版本太高是个常见隐患。网上很多教程是2.x的你装了个3.x的SpringBoot再引入对应的旧版MyBatis Plus启动时可能直接报ClassNotFoundException。能避开的方案是统一用2.7.x版本MyBatis Plus用3.5.3左右的版本这组合我在多个项目里试过非常稳定。启动失败的另一个常见原因是端口被占用。SpringBoot默认8080你机器上可能跑了其他服务占用了可以在application.yml里改成别的端口server: port: 8088还有数据库连接配置错误导致的启动失败比如MySQL版本8.0连接时必须加上时区参数spring: datasource: url: jdbc:mysql://localhost:3306/rental_house?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: yourpassword driver-class-name: com.mysql.cj.jdbc.Driver5.2 MyBatis Plus自动填充与逻辑删除的配置实体类的create_time和update_time如果是手动每条set既麻烦又容易漏。MyBatis Plus提供了MetaObjectHandler自动填充功能可以实现插入和更新时自动填充时间字段。实现一个Handler就行Component public class MyMetaObjectHandler implements MetaObjectHandler { Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, createTime, LocalDateTime.class, LocalDateTime.now()); this.strictInsertFill(metaObject, updateTime, LocalDateTime.class, LocalDateTime.now()); } Override public void updateFill(MetaObject metaObject) { this.strictUpdateFill(metaObject, updateTime, LocalDateTime.class, LocalDateTime.now()); } }逻辑删除建议也在全局配置里开启防止误删数据。在配置文件加一行mybatis-plus: global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0但要注意合同、账单这类业务表不要直接用逻辑删除因为合同作废是有业务含义的应该用status字段标识不该用deleted锤掉。逻辑删除只用于用户误删这种场景。5.3 日期处理与跨月费用计算这个项目里面日期处理是重灾区。合同起止日期、账单周期、催费提醒日期几乎每个功能都要跟LocalDate打交道。建议全项目统一用LocalDate/LocalDateTime不要用java.util.Date理由是LocalDate的API更友好精度把控也更清晰。两个最经典的坑判断租期是否重叠时用“开始日期 已有合同的结束日期 且 结束日期 已有合同的开始日期”这个条件不能直接用contains判断因为区间是开闭区间边界要考虑清楚。计算“剩余天数”时用ChronoUnit.DAYS.between(start, end)注意是两个日期之间的天数差如果结束日期和开始日期是同一天结果是0但实际租约可能算1天要自己加1。这两个坑我在代码评审时见得太多了。写完了测试用例一跑你就会发现看似简单的日期逻辑到处是边界bug。5.4 分页查询的总数与数据一致性MyBatis Plus自带分页插件配置一个Interceptor就行Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }分页的常见问题是多表关联查询后总数不对。比如查房间列表时你想展示房间的楼栋名称和租客姓名用left join去查然后用Page封装mybatis在count时会把join的表也算进去导致count结果偏大。解决方法是列表页拆成两步先按条件查主表分页数据再根据主表ID批量查关联信息填充。这也是我前面特地强调VO手动组装的原因不只为了代码分离更重要的是避免分页数据错乱。5.5 打包部署的静态资源路径问题前后端分离部署时前端构建完的dist目录塞进SpringBoot静态目录后访问时容易出现刷新页面404因为前端路由用的是history模式。解决办法有两个要么前端改路由模式为hash模式要么后端写一个转发配置把所有非API的GET请求都转发到index.html。对于毕设改hash模式最简单粗暴const router createRouter({ history: createWebHashHistory(), routes })另外文件上传如果涉及到图片记得把配置里的文件保存路径写成绝对路径或者相对路径都行但要注意jar包启动后相对路径的基准目录是jar包所在目录不是classpath目录别把图片存到classpath下的static/images。正确做法是存到一个固定文件夹然后通过映射虚拟路径来访问Configuration public class WebConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceLocations(file: uploadPath); } }工作区固定路径的坑很多答辩前提前检查一遍图片能不能正常显示。6. 扩展思路与答辩实战建议6.1 三个容易做出来的差异化功能如果基础功能做完了还有余力我建议给出租屋管理系统加下面三个功能之一这对答辩分数的提升非常明显。移动端H5适配把租客端查看合同、交租金、报修、投诉单独做成一个H5页面租客扫个码就能用。实际开发就是再加一个移动端页面接口完全复用难度不大但演示效果惊艳。定时催缴提醒定时任务跑一遍把逾期未缴费的租客名单扒出来通过邮件或短信模板推提醒消息。技术栈加个Spring Mail就行或接个发短信的开放API。数据导出到Excel把账单明细、租客列表、合同台账通过EasyExcel导出成Excel文件管理也就是引入一个EasyExcel依赖写三个方法但“报表导出能力”是一个很好的简历词汇。6.2 论文和答辩陈述的技巧论文写作时系统流程图、用例图、ER图这几张图一定得画细致。出租屋管理系统的业务流程图建议把房间状态机画清楚把合同生命周期画清楚这两张图是论文的高光部分。答辩陈述时别从头到尾背开发过程重点讲三个东西你解决了什么业务问题、系统的关键设计决策、你遇到的最大困难是什么以及怎么解决的。用退租清算模块当案例来讲最抓人你可以说“退租时水电费是按照抄表差值乘以单价动态计算的同时考虑未结账单和损坏扣款所有操作在同一事务里执行保证了资金账目的一致”。6.3 这套系统的后续扩展方向这套系统的代码骨架做出来之后往生产级方向扩展的空间很大常见的扩展定位有四个多房东入驻变成平台模式即让多个房东同时在平台上发布房源我们收取服务费。接入在线支付比如支付宝或微信支付房间租金和押金签约电子合同。对接智能水表电表自动读取用量账单全自动生成完全免人工抄表。从出租屋扩展到公寓管理、共享办公工位管理底层表结构改一改就能复用。这些都适合你写在论文的“总结与展望”里。虽然毕设不会真让你上线这些但能跟老师说清楚扩展路径说明你不只是写了代码是真正理解了这个系统的生命周期。7. 实操体验我自己带学生做这个题的几点体会最近带一个学生完整做了这套SpringBoot出租屋管理系统不到两周时间把核心流程跑通了期间几个细节很值得拿出来说说。第一点是第一次生成账单的学生把房租按“30天固定分摊”做了完全没考虑2月只有28天这件事。测试数据一进去2月入住的租客租金账单比实际多算了两天的钱。发现后我在代码里把分摊基数改成了“当月实际天数”又加了一个针对2月边界情况的单元测试。这种细节虽然小但直接影响业务数据的准确性和你对这个项目的掌握深度。第二点权限控制的坑。学生一开始只用前端菜单隐藏来控制按钮权限我让她试着拿一个普通租客的token直接调管理员的删除接口结果畅通无阻。后来在Controller层加了角色注解校验后端彻底挡住越权请求。这一点建议所有做毕设的同学都自查一遍因为答辩老师一定会往深处问“你这个管理端的接口租客能不能直接调”第三点打包部署的坑。她把前端项目打成的dist扔进static目录之后刷新页面全白屏折腾了半天才发现是history路由的问题。换成hash模式后一切正常。这个点我前面已经提过这里再强调一次别在这种小事情上浪费答辩前宝贵的调试时间。如果你正在准备这套出租屋管理系统我建议的推进节奏是第一周搞定数据库设计和系统管理模块第二周搞定房源、租客、合同三个核心模块第三周搞定账单、报表和报修第四周专攻细节打磨和部署演示。这样时间分配均匀不至于最后通宵赶工。系统做出来之后别急着放松。花一天时间把整个流程从头到尾走一遍然后用一台干净机器从零部署确认所有步骤都写得清楚。答辩现场老师最怕的就是“我本地跑得好好的在你电脑上怎么就崩了”这种翻车场景。最后再分享一个小技巧做项目时把每天遇到的问题和解决方案记到一个Markdown文件里答辩前把这些真实调试记录整理成一张“典型问题与解决方案”表放进文档附录。这一页的内容比任何模板化总结都更能打动答辩老师。它证明了你真的亲手写过这套系统。