
1. 项目定位与技术选型解析1.1 为什么大学生租房平台适合作为管理系统实战项目每年毕业设计和实训季总有一大批同学在选题上纠结既要业务不复杂、工作量可控又希望技术栈能覆盖现阶段的主流框架最好做出来以后还能往简历上写。基于SpringBootVue的大学生在线租房平台管理系统源码正好卡在这个需求点上。租房平台这个业务非常典型。它本质上是一个“信息发布交易撮合”系统包含用户注册登录、房源发布、条件检索、收藏预约、订单管理、后台运营审核等一整条业务流程。相比单纯的增删改查Demo它的业务逻辑更有深度比如房源筛选涉及多条件组合查询、登录鉴权涉及Token设计、图片上传涉及静态资源处理——这些都是真实项目中每天都会遇到的需求。相比电商、社交这类大而全的系统它又不会让工作量失控单人开发完全能hold住。技术选型也刚好踩在最主流的组合上后端SpringBoot负责业务接口Vue负责前端页面交互MyBatis作为数据访问层MySQL存储业务数据。这套技术栈在中小型管理系统中的市场占有率非常高学完以后无论是继续做毕设、进公司实习还是参考简历上的项目经验都有直接的复用价值。1.2 SpringBoot MyBatis MySQL这一组合的核心优势先聊聊后端这套组合为什么能成为“国民级配置”。SpringBoot的核心贡献是解决了Spring框架长期以来的配置地狱问题。以前写一个SSM项目要配一堆XML、要部署到外部Tomcat、要手动管理依赖版本光是环境搭建就能劝退一批新手。SpringBoot用自动配置约定大于配置的机制把大部分重复劳动包办掉让我们把精力集中在业务代码上。对于租房平台这种业务规模SpringBoot的默认配置完全够用不需要过度设计。MyBatis作为持久层框架最大特点是灵活。它的SQL由自己控制方便针对复杂查询做手工优化。比如租房平台的房源列表页往往需要根据关键词、价格区间、户型、区域等多个条件动态拼接查询用MyBatis的动态SQL处理这类场景得心应手。如果换成某些封闭性较强的ORM框架遇到这种复杂查询反而不容易写清楚。对学习而言在XML里手动写SQL也能加深对SQL本身的理解这是MyBatis长期活跃在教学和中小型项目里的重要原因。MySQL则是关系型数据库中最稳妥的选择。租房平台的数据量级——几千个用户、几百条房源、几万条日志——在MySQL面前完全没有压力。它的部署运维成本低、资料丰富、有问题一搜就有答案适合学生项目也适合作为面试聊项目时的数据库底牌。1.3 前端为什么选择Vue再单独说说Vue。早期的课程设计大多用Thymeleaf做服务端渲染所有页面逻辑都混在后端里改个样式都要重启服务前后端职责搅在一起工作效率很低。现在Vue配合Vite/RESTful接口做前后端分离后端只管出JSON前端负责渲染与交互职责清晰联调体验也顺畅。Vue对新手友好这个评价不是空话。它的模板语法直观、响应式机制屏蔽了繁琐的DOM操作Element Plus这类组件库又能快速搭建出看着像样的后台管理界面。对于租房平台来说用户端的房源列表、搜索筛选、房源详情管理端的表格、弹窗、表单这些界面用Vue Element Plus做出来既快又规范。简历上写“熟悉Vue”也已经是Java开发岗位的基本要求了——后端工程师懂点Vue在职场上属于加分项。2. 业务模块与数据库表设计2.1 核心业务流转与模块划分拿到一个租房平台项目先不要急着写代码第一件事是把业务角色和流程摸清楚。这套系统的角色分三类普通用户找房者、房东发布房源的人、管理员平台运营方。用户端核心流程是注册登录 → 浏览房源列表 → 按条件筛选 → 查看房源详情 → 收藏心仪房源 → 提交预约/租房申请 → 在个人中心查看申请状态。房东端流程是登录后发布房源 → 管理自己发布的房源 → 查看收到的租房申请 → 上下架房源。管理端流程是登录后台 → 审核房源是否合规 → 管理注册用户禁用/启用 → 处理投诉反馈 → 查看平台数据概览。模块划分清楚了数据库表设计也就有了依据。我建议按“用户、房源、订单/预约、收藏、反馈、公告”这几个核心维度来建表不要一开始就贪多把业务跑通以后再考虑扩展。2.2 数据表设计详解下面给出一个经过实践检验的核心表结构字段命名和类型可以直接抄作业。用户表userCREATE TABLE user ( id int NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL COMMENT 用户名, password varchar(100) NOT NULL COMMENT 密码建议BCrypt加密存储, phone varchar(20) DEFAULT NULL COMMENT 手机号, avatar varchar(255) DEFAULT NULL COMMENT 头像URL, role tinyint NOT NULL DEFAULT 1 COMMENT 角色0管理员 1普通用户 2房东, status tinyint NOT NULL DEFAULT 1 COMMENT 状态1正常 0禁用, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;房源表houseCREATE TABLE house ( id int NOT NULL AUTO_INCREMENT, landlord_id int NOT NULL COMMENT 房东ID关联user表, title varchar(100) NOT NULL COMMENT 房源标题, description text COMMENT 详细描述, address varchar(255) NOT NULL COMMENT 具体地址, area decimal(10,2) DEFAULT NULL COMMENT 面积(㎡), price decimal(10,2) NOT NULL COMMENT 月租金, room_num tinyint DEFAULT 1 COMMENT 室, hall_num tinyint DEFAULT 1 COMMENT 厅, toilet_num tinyint DEFAULT 1 COMMENT 卫, orientation varchar(20) DEFAULT NULL COMMENT 朝向, floor varchar(20) DEFAULT NULL COMMENT 楼层, tags varchar(255) DEFAULT NULL COMMENT 标签近地铁/朝南/拎包入住逗号分隔, cover_image varchar(255) DEFAULT NULL COMMENT 封面图URL, images text COMMENT 详情图URL逗号分隔, status tinyint NOT NULL DEFAULT 0 COMMENT 状态0待审核 1已上架 2已下架 3审核拒绝, browse_count int NOT NULL DEFAULT 0 COMMENT 浏览量, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_landlord_id (landlord_id), KEY idx_price (price), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;预约/订单表house_reserveCREATE TABLE house_reserve ( id int NOT NULL AUTO_INCREMENT, house_id int NOT NULL, user_id int NOT NULL, contact_name varchar(50) DEFAULT NULL COMMENT 联系人, contact_phone varchar(20) DEFAULT NULL COMMENT 联系电话, remark varchar(500) DEFAULT NULL COMMENT 备注, status tinyint NOT NULL DEFAULT 0 COMMENT 状态0待处理 1已同意 2已拒绝 3已完成 4已取消, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_house_id (house_id), KEY idx_user_id (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;收藏表favorite、反馈表feedback、公告表notice结构相对简单不再赘述。收藏表唯一约束用(user_id, house_id)联合去重反馈表就是用户ID内容回复公告表就是标题内容发布时间。几个容易被忽视的设计细节用户表不要和房东表分两套用role字段区分即可后续扩展角色也方便。房源状态一定要设计成多状态不能只是简单的上架/下架“待审核”是管理端的核心动作。价格、面积用decimal不要用float避免精度问题。所有字符串字段尽量用utf8mb4不然emoji和一些生僻字会报错。索引不用建太多但where条件里高频出现的字段要建比如price、status。2.3 表关系梳理与查询思路表关系理清楚以后写SQL就有了方向。用户和房源是一对多关系一个房东可以发布多条房源house表的landlord_id指向user表id。用户和收藏是多对多关系一个用户可以收藏多套房源一套房源也可以被多个用户收藏通过favorite表中转。用户和预约也是一对多house_reserve表同时连house和user。房源列表页的查询是这套系统最核心的SQL场景。既要分页又要根据关键词、价格区间、朝向、户型、状态做动态筛选还要关联房东名称用于展示。典型的XML写法我放在第三章来讲这里先说思路优先用status1作为基础条件过滤掉未上架的数据再按用户勾选的条件动态追加WHERE子句排序用create_time倒序让新发布的房源排在前面。这个数据规模下不需要复杂的SQL优化技巧但有一个经验值得提前说列表查询尽量只返回列表页需要的字段不要把description这种text大字段查出来等进入详情页再单独查一次。这样做的好处是响应体量小、页面加载快在真实项目中这是一个非常常见的性能优化点。3. 后端核心实现解析3.1 后端分层结构与统一响应体SpringBoot项目的分层基本是写死的controller接收前端参数、service处理业务逻辑、mapper操作数据库、entity对应表结构。这套分层规则看起来很“套路”但确实能保证代码不出乱子。我自己写项目时会在此基础上加一层DTO用于接口参数的接收和响应字段的控制避免直接把entity暴露给前端。统一响应体属于那种“一开始觉得麻烦、后面真香”的设计。我的习惯是写一个Result类public class ResultT { private Integer code; // 200成功其他为失败 private String message; // 提示信息 private T data; // 数据 public static T ResultT success(T data) { ResultT r new Result(); r.code 200; r.message 操作成功; r.data data; return r; } public static T ResultT error(String message) { ResultT r new Result(); r.code 500; r.message message; return r; } }这样前端只需要根据code判断请求是否成功不用每个接口单独商量返回结构。尤其在后端接口数量超过20个以后统一响应体可以省掉大量联调上的废话。3.2 登录鉴权的实现思路登录鉴权是这个项目里学生最容易偷工减料、面试官最喜欢追问的部分。常见的做法有Session、Token、JWT三种。Session是传统Web项目的标配但前后端分离以后Session默认依赖Cookie传递跨域场景下处理起来比较麻烦。JWT是目前主流的选择用户登录成功后后端用密钥签发一个包含用户信息的Token返回给前端前端每次请求在请求头里带上这个Token后端通过拦截器验证Token的合法性和有效期。我推荐用JWTjwt工具类。核心代码如下// 生成Token String token Jwts.builder() .setSubject(String.valueOf(userId)) .claim(role, user.getRole()) .setExpiration(new Date(System.currentTimeMillis() 7 * 24 * 3600 * 1000)) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact();拦截器里校验Tokenpublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (token null || token.isEmpty()) { response.setStatus(401); return false; } try { Claims claims Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody(); request.setAttribute(userId, Integer.valueOf(claims.getSubject())); request.setAttribute(role, claims.get(role)); return true; } catch (Exception e) { response.setStatus(401); return false; } }密码存储一定不能明文。最稳妥的是用BCrypt加密// 加密 String encoded BCrypt.hashpw(rawPassword, BCrypt.gensalt()); // 校验 boolean ok BCrypt.checkpw(rawPassword, encodedPassword);这里有一个很多人会踩的坑用户表里存的password是BCrypt加密后的字符串但某些同学的注册接口不小心把加密逻辑写漏了导致登录时永远校验不过。排查这个问题的方法很简单注册后去数据库看一眼password字段如果是明文那肯定是加密逻辑没生效。鉴权还有一层容易忽略的地方——权限区分。普通用户、房东、管理员能访问的接口不一样。我现在习惯用自定义注解RequireRole加上拦截器判断管理员接口加RequireRole(0)房东接口加RequireRole(2)比在controller里手动写if判断干净得多。3.3 房源分页检索SQL实战用MyBatis做条件查询最重要的一项技能是动态SQL。房源列表页的筛选条件可能同时有多个关键词匹配标题、价格区间、户型、朝向、排序方式。如果每个组合都写一种SQL那SQL数量会爆炸。MyBatis的where和if标签就是解决这个问题的select idselectHousePage resultTypecom.demo.entity.House SELECT id, landlord_id, title, address, area, price, room_num, hall_num, toilet_num, orientation, floor, tags, cover_image, status, browse_count, create_time FROM house where status 1 if testkeyword ! null and keyword ! AND (title LIKE CONCAT(%, #{keyword}, %) OR address LIKE CONCAT(%, #{keyword}, %)) /if if testminPrice ! null AND price gt; #{minPrice} /if if testmaxPrice ! null AND price lt; #{maxPrice} /if if testroomNum ! null and roomNum ! 0 AND room_num #{roomNum} /if if testorientation ! null and orientation ! AND orientation #{orientation} /if /where ORDER BY create_time DESC LIMIT #{offset}, #{pageSize} /select注意两个细节gt;和lt;是XML里的转义写法直接写会报XML解析错误。还有分页用的offset需要在前端传回来更优雅的做法是封装一个PageQuery对象里面包含pageNum和pageSize然后在service层手动计算offset。配套的统计总数SQL同样使用相同的条件select idcountHouse resultTypeint SELECT COUNT(*) FROM house where status 1 if testkeyword ! null and keyword ! AND (title LIKE CONCAT(%, #{keyword}, %) OR address LIKE CONCAT(%, #{keyword}, %)) /if if testminPrice ! null AND price gt; #{minPrice} /if if testmaxPrice ! null AND price lt; #{maxPrice} /if ... /where /select把列表查询和总数查询各自写一套动态SQL虽然看着有点重复但这是最清晰最可控的做法。不要图省事去搞什么自动分页插件除非封装得足够成熟尤其在教学项目里手动写明LIMIT反而能让你真正理解分页原理。3.4 图片上传与静态资源映射档案、房源图片上传是这个项目里最容易被忽略、却又最容易出问题的功能。SpringBoot默认静态资源路径是classpath:/static/但用户上传的图片不应该打进jar包。正确的做法是在配置文件里指定一个本地上传目录然后通过自定义资源映射暴露出去。application.yml配置spring: servlet: multipart: max-file-size: 5MB max-request-size: 20MB file: upload-dir: ./upload/定义一个配置类来做资源映射Configuration public class WebConfig implements WebMvcConfigurer { Value(${file.upload-dir}) private String uploadDir; Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceLocations(file: uploadDir); } }上传接口里保存文件时文件名一定要用UUID重命名不能直接用用户上传的原始文件名。原因有两个一是防止文件名重复导致互相覆盖二是防止某些特殊字符或路径穿越攻击。我的写法是String originalFilename file.getOriginalFilename(); String ext originalFilename.substring(originalFilename.lastIndexOf(.)); String newFileName UUID.randomUUID().toString() ext; file.transferTo(new File(uploadDir newFileName));返回给前端的URL一般是/upload/{newFileName}前端直接拼在图片地址上就能回显。这里要留意一下Vite开发服务器的代理如果没把/upload路径转发到后端图片就会404这个问题放在后面章节详细说。4. 前端Vue页面与联调要点4.1 项目初始化与整体结构如果源码给的是2025最新版本前端大概率是Vue3 Vite Element Plus的组合。这个组合相比Vue2 Vue CLI的版本创建项目更快、开发时热更新更快组件库的API风格也稍有区别但底层思路一致。创建一个Vue3项目npm create vitelatest house-frontend -- --template vue cd house-frontend npm install npm install axios vue-router4 element-plus目录结构建议按功能而不是按类型划分src/ api/ // 接口请求封装 assets/ // 静态资源 components/ // 公共组件 router/ // 路由配置 stores/ // 状态管理Pinia views/ user/ // 用户端房源列表、详情、个人中心 landlord/ // 房东端发布房源、管理房源 admin/ // 管理端审核、用户管理、反馈路由设计核心是权限控制。用户端页面和管理后台的页面必须分开可以用路由meta标记角色再配合前置守卫做拦截const router createRouter({ routes: [ { path: /, component: Home }, { path: /house/:id, component: HouseDetail }, { path: /admin, component: AdminLayout, meta: { role: 0 } } ] }) router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.meta.role) { const role localStorage.getItem(role) if (!token || role ! to.meta.role) { router.push(/login) return } } next() })4.2 房源列表、详情与发布页核心思路列表页是整个前端工作量最大的页面。整体布局一般是一个筛选栏价格区间、户型、朝向、关键词加上一个卡片列表。筛选条件变化就重新调用接口拉数据用Loading控制状态避免重复点击。分页我习惯用el-pagination组件每次页码变化时带上pageNum和pageSize重新请求。详情页有几个细节要注意房源的多张图片用el-carousel轮播组件展示标签通过tags字段按逗号拆分渲染成一个个小的标签右侧放联系房东/预约看房的表单。预约按钮点击后需要先确认用户是否登录如果没登录就跳登录页这属于交互上的基本体验。发布房源页面在房东端。表单字段多建议直接用el-form配合校验规则。字段校验规则里价格、面积、手机号这些必须有格式校验。图片上传用el-upload组件重点是处理好上传成功的URL收集逻辑把所有图片URL拼成一个逗号分隔的字符串再提交给后端。这里如果图省事不做回显和删除后续编辑房源时会很难受。管理端的表格页面通用套路是el-table 搜索表单 分页 弹窗。房源审核页是最重要的管理员在表格里看到待审核房源点详情查看完整信息然后选择通过或驳回。驳回时可以填一下驳回理由这个理由会展示给房东是比较标准的产品设计。4.3 前后端联调中的跨域与代理问题联调时第一个拦路虎就是跨域。前端口在5173Vite默认后端接口在8080浏览器直接访问会上演经典的CORS错误。开发阶段最省事的方案是在Vite配置代理把请求转发到后端// vite.config.js export default defineConfig({ server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })这样前端请求/api/user/loginVite开发服务器会转发到http://localhost:8080/api/user/login。后端Controller的RequestMapping就要统一以/api开头两边对齐不需要后端额外配置CORS。很多同学喜欢在后端写CrossOrigin来解决问题开发阶段可行但真实项目中这种方式往往不够规范——更通用的做法是后端通过CORS配置类统一放行或者生产环境通过Nginx反向代理到一个域名下彻底规避跨域。如果接口路径不小心写错了联调时最大的排查工具就是浏览器Network面板。状态码304和404要分清楚304代表缓存、404代表接口不存在。前端代理配好后接口仍然404就去后端控制台看有没有请求进来有请求没响应是后端逻辑问题没请求到达就是代理配置问题。这个排查思路能解决掉80%的联调问题。5. 从零部署跑通整个系统5.1 环境准备与源码导入拿到源码以后想直接跑起来环境准备工作要按顺序来。我的建议清单如下软件版本建议用途JDK1.8 或 11运行SpringBootMaven3.6后端依赖管理MySQL5.7 或 8.0数据库Node.js16前端构建IDEIDEA 2022开发/导入工程注意JDK版本要和pom.xml里设置的java.version一致。SpringBoot 2.x用JDK8就行如果源码是SpringBoot 3.x就需要JDK17。版本不匹配会报各类奇怪的编译错误这是新手环境问题里排第一的坑。后端导入IDEA后等待Maven把依赖下载完。如果下载速度慢或者失败去Maven的settings.xml里配阿里云镜像。前端在项目目录下执行npm install如果网络不稳定就换淘宝镜像源。5.2 数据库初始化与配置文件修改打开项目里的SQL脚本文件通常是init.sql或database.sql也可能有多个分库脚本在Navicat或命令行中执行。执行完以后确认一下表是否建齐特别是user表和house表这种核心表里是否有初始数据。如果有初始管理员账号要到脚本里查一下——它一般写在INSERT语句里把用户名和BCrypt加密后的密码复制出来看过。然后修改后端的application.ymlspring: datasource: url: jdbc:mysql://localhost:3306/house_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 你的数据库密码这里有几个关键点。serverTimezone不设置的话高版本MySQL驱动会报时区错误characterEncoding不设置的话往数据库写入中文就可能乱码。如果你是MySQL 8.0驱动类名要写成com.mysql.cj.jdbc.Driver8.0以下则是com.mysql.jdbc.Driver。5.3 前后端启动顺序与真实记录启动顺序建议是先启动MySQL再启动后端最后启动前端。后端在IDEA里直接运行主类看到类似Tomcat started on port(s): 8080的日志就说明启动成功。第一次启动可能会因为数据库账号密码不对、端口被占用等原因失败看日志排查即可。前端启动命令npm run dev看到VITE v5.x ready in xxx ms以及Local: http://localhost:5173/就说明前端起来了。浏览器打开地址用初始管理员账号登录后台再注册一个普通用户测试房源浏览流程全链路通一遍以后项目就算真正跑通了。这里有一个容易误判的场景后端已经正常启动但前端页面登录一直失败。问题不一定在前端也不一定在后端而可能在于数据库里的MD5/BCrypt初始化数据格式和后端校验逻辑不一致——比如源码里登录接口用的是BCrypt校验但初始化SQL里的管理员密码却是明文这种情况验签必然失败。解决办法要么更新SQL数据要么重新注册一个新账号来测试别在错误的初始化数据上反复纠结。6. 常见问题排查实录6.1 登录接口一直返回401或者500先分清401和500的本质区别。401一般是前端没带Token、Token过期、或者拦截器校验失败500通常是后端代码或数据库层面异常。实际项目中我发现一个高频原因前端虽然把Token存了localStorage但Axios请求拦截器里没配置请求头Authorization: Bearer xxx导致每一次请求都匿名访问。检查顺序请求头有没有Token → Token有没有过期 → 拦截器校验逻辑有没有写错。另一个隐蔽的坑/user/login这类接口不需要鉴权但如果拦截器注册时写了全部路径拦截登录接口自己也被拦截了就会造成“登录接口401”的诡异现象。解决办法是注册拦截器时用excludePathPatterns把登录、注册、房源列表等公开接口排除掉。6.2 图片上传成功但回显404上传接口返回200、URL看起来正常但浏览器就是显示不出来这个问题我在多个项目里都见过。原因基本就是一个后端静态资源映射没生效。检查顺序也简单。先手动在浏览器访问http://localhost:8080/upload/xxx.jpg如果404说明/upload/**映射到的本地目录路径不对或者映射配置没注册成功。如果直接访问本地路径能打开但URL访问不了就去看WebConfig里的addResourceLocations(file: uploadDir)确认uploadDir是绝对路径还是相对路径、路径末尾有没有斜杠。file:./upload/和file:upload/写法不同、容错性也不同直接用绝对路径最保险。前端还有一层代理问题Vite代理只转发了/api前缀/upload路径没转发所以浏览器拿到的还是前端静态服务器地址。只需要在proxy里加一条规则/upload: { target: http://localhost:8080, changeOrigin: true }6.3 中文乱码问题乱码的出现场景通常是两类一类是数据库里的中文变成问号另一类是接口返回的JSON中文乱码。数据库层面建库时指定utf8mb4字符集是关键。已经建完库的执行ALTER DATABASE house_db CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;连接URL里也要带characterEncodingutf8。如果还乱码检查表级别的字符集ALTER TABLE house CONVERT TO CHARACTER SET utf8mb4;接口返回乱码检查SpringBoot的Jackson配置一般不需要特殊处理只要确保pom依赖没被反复折腾就行。一个隐藏的坑是如果后端代码里手写了response.getWriter().write()返回字符串这时需要手动设置response.setContentType(application/json;charsetUTF-8)否则可能以ISO-8859-1输出。6.4 前端刷新后404这个问题的标准场景是在页面内跳转一切正常但一旦手动刷新/house/3或者/admin页面浏览器就报404。原因在于前端用的是history模式路由刷新时浏览器带着前端路径去请求后端服务器后端没有对应的路由自然返回404。解决办法是在后端加一个SPA fallback让所有非API路径都转发到index.html页面。简单做法是用路由configuration把未知路径指向网站的根目录Controller public class SPAForwardController { RequestMapping(value {/, /house/**, /admin/**, /login}) public String forward() { return forward:/index.html; } }不过这个方案要注意类路径下必须有/static/index.html。更工程化的方案是部署时用Nginx的try_files $uri $uri/ /index.html;这个在生产环境里是最优雅的面试时能说出来会加分。6.5 时间字段格式化不对数据库里的时间是2025-01-01 10:30:00接口返回却变成1703997000000这种时间戳前端拿到数字直接显示出来观感很差。原因是SpringBoot默认会用Jackson把Date/LocalDateTime序列化成时间戳。解决办法有两个推荐直接写在配置文件里spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai对于LocalDateTime字段还需要在字段上添加JsonFormat(pattern yyyy-MM-dd HH:mm:ss)。这两步做完以后前端拿到的就是人类可读的字符串。6.6 MyBatis的XML映射文件没生效后端启动不报错但一调用Mapper方法就提示Invalid bound statement往往是XML文件没有被扫描到。检查点有三个XML文件的namespace是否对应Mapper接口的全限定名Mapper接口的id是否和XML里的select/update的id一致XML文件是否放在了resources目录下并被构建产物包含。最后一点最隐蔽有的人把XML放在了java源码目录下IDEA默认不会把它复制到target里运行时就找不到了。解决办法是把XML放到src/main/resources/mapper/下并在application.yml里配置mybatis: mapper-locations: classpath:mapper/*.xml写在最后的一些体会这个项目我前后接过几次不同的版本也帮不少正在准备毕设或找工作的朋友排查过问题。最大的感受是一个好的毕设项目不在于功能花哨而在于“链路完整”从数据库设计、后端接口、前端页面到部署上线每一步都要踏踏实实跑通。如果你考研复试或面试时能把这个租房平台的表设计逻辑、JWT鉴权、动态SQL这几块讲清楚说服力比网上随便下载的千篇一律商城项目强很多。上手这个源码的时候建议先按我的步骤把环境跑通然后不要急着改功能先把“用户发房源 → 管理员审核 → 用户预约看房”这条主链路完整走一遍。主链路通了再考虑加需求比如给房源加地图定位、加站内聊天、接入支付做在线签约这些扩展都能在现有表结构和技术栈上自然生长。最后再分享一个小技巧很多报错信息看着吓人其实核心原因就在前几行。养成先看控制台最下面或最上面几行日志的习惯大多数问题自己就能定位。把这个问题排查的耐心练出来比你多背几个框架API有用得多。