ARTICLE DETAIL

资讯详情

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

Spring Boot大学生租房平台毕业设计:从CRUD到全栈工程实践

Spring Boot大学生租房平台毕业设计:从CRUD到全栈工程实践 1. 选题拆解大学生租房平台表面是CRUD实际考的是工程化能力看到“springboot007大学生租房平台的设计与实现”这个题目时你大概率和我当年一样这不就是一套增删改查吗把房源表、用户表、订单表建好后端写几个接口前端再做几个列表页和表单页收工。但真正把毕业设计做完之后我才明白这类题目之所以被选了这么多年恰恰是因为它把软件工程里最典型、最容易踩坑的问题全揉在了一个场景里多角色权限、图片上传、复杂状态流转、检索排序、事务一致性、文件存储、部署上线。你只要认真做一遍Spring Boot后端、Vue前端、数据库设计、文档写作这些能力基本都能串起来。所以这篇不适合只想拿个及格分的人更适合那种想通过一个完整项目把全栈流程走通、并且在答辩时能讲清楚“为什么这么设计”的同学。1.1 一道题里藏着的技术考点大学生租房平台的核心业务并不复杂租客找房、收藏房源、预约看房、提交订单房东发布房源、管理房源、处理订单管理员审核房源、管理用户和公告。但每一个小的业务点展开都能对应一个后端开发的常见考点。比如“多角色”对应权限设计“房源图片”对应文件上传与静态资源映射“订单”对应状态机设计“搜索筛选”对应多条件动态SQL与分页“看房预约”对应时间冲突校验“合同”对应PDF生成或富文本展示“数据统计”对应聚合查询。把这些点一个个列出来你会发现这个题目其实是在模拟一个真实的小型信息管理平台而不是单纯的教学案例。从实现方式来看这个题目最标准的技术栈就是Spring Boot 2.x MyBatis Plus MySQL Vue 2/3 Element UI。Spring Boot负责后端接口和自动配置MyBatis Plus简化单表CRUDRedis可以做缓存或验证码存储JWT处理无状态登录。前端用Vue Axios Element UI既能快速搭建后台页面也能在答辩时演示响应式交互。技术栈不需要太花哨但必须每一个点都能讲出用途。1.2 为什么是Spring Boot而不是SSH或JSP很多同学会问学校教的是SSHStruts Spring Hibernate或者纯JSP Servlet为什么毕设要用Spring Boot我个人的建议是如果你的毕业设计没有强制规定老技术直接选Spring Boot。Spring Boot最大的价值不是“替代Spring”而是把配置简化到了可以忽略不计的程度。你只需要一个spring-boot-starter-web依赖就能跑起Web服务只需要加一个spring-boot-starter-data-redis就能拿到RedisTemplate。这对学生来说能把更多精力放到业务逻辑本身而不是在XML配置文件里折腾。另一个理由是生态成熟。你随便搜一个博客或视频都能找到Spring Boot Vue的项目脚手架遇到问题也有大量解决方案。相比之下SSH的资料已经越来越少报错后连搜索都费劲。Spring Boot的自动配置虽然“黑盒”但只要我们理解了它的 Starter 机制——启动时加载META-INF/spring.factories里的配置类按条件装配Bean就可以在答辩时主动讲出“为什么引入一个依赖就能直接用”这本身就比背诵SSH配置高一个段位。1.3 文档源码的组合意味着什么题目里专门强调“文档源码”说明评阅老师看重的不只是能跑的程序还有设计文档中对需求、技术选型、数据库设计、核心流程的描述。文档和源码是互相印证的关系源码里每一个关键模块都应该是文档中“详细设计”章节的落地。我在写文档时吃过一个亏一开始把文档写成纯流水账粘贴了一大堆代码结果老师一眼就看出来是凑字数。后来我调整成“功能需求 - 表结构设计 - 核心接口时序 - 关键代码片段 - 测试结果”的结构每一段都和源码对应反而好写很多。这部分我会在第6节详细说先继续往下聊技术实现。2. 架构设计与数据库模型先画清楚边界再动代码大学生租房平台如果一开始就埋头写接口后面大概率会陷入“加一个字段就改一遍SQL”的泥潭。我的经验是先做两件事一是把角色边界画清楚二是把每张表的字段和状态定下来。这两件事做扎实了开发速度反而最快。2.1 从“人”和“房”两条主线梳理业务整个平台可以抽象成“人找人、人找房、房找人”三条路径。人指的是用户分三种角色租客、房东、管理员。房指的是房源从发布、审核、上架、出租到下架的整个生命周期。业务上租客和房东是交易双方管理员是审核与监管方。角色边界一旦清楚功能列表就很好列了租客端注册登录、浏览房源、条件搜索、收藏房源、预约看房、提交租赁订单、查看订单状态、个人中心、投诉建议。房东端注册登录、发布房源、管理房源上下架、修改、处理预约、确认订单、查看租客信息、合同确认。管理端用户管理、房源审核、公告管理、订单监管、数据统计。这里有一个很容易被忽略的点用户表不能只用一个role字段区分角色因为真实场景中一个手机号可能既是租客又是房东。虽然毕设可以先用单角色简化但更合理的设计是建一张用户表再加一张用户角色关联表或者至少在注册页面让用户选择“租客”还是“房东”。我在源码里用的是user表存基础信息然后通过role字段区分但文档中会注明“实际生产环境建议使用RBAC”。这个细节答辩时提出来老师会觉得你考虑过权限模型。2.2 核心表结构设计先定义好状态机租房平台的数据库表大概包括这些用户表、角色表、房源表、房源图片表、收藏表、预约看房表、订单表、合同表、公告表、反馈表。其中最关键的是房源表和订单表。房源表要区分“待审核、已上架、已下架、已出租”状态。订单表要区分“待支付、已支付待确认、看房完成、已签约、已退租、已取消”状态。不要小看这些状态字段它们是后面所有业务判断的依据。比如“预约看房”不是简简单单插入一条记录而是要判断该房源、该时间段是否已经被其他租客预约。这个逻辑放在后端Service层就好用SQL查询时间区间是否有交集即可SELECT COUNT(*) FROM view_appointment WHERE house_id #{houseId} AND status IN (0, 1) AND (start_time lt; #{endTime} AND end_time gt; #{startTime})这段SQL的意思很直白只要已有预约的开始时间早于你预约的结束时间并且结束时间晚于你预约的开始时间就说明时间段重叠不能预约。这种交叉时间判断比单纯用比较日期可靠得多。订单状态不建议散落着写死字符串建议用常量类或枚举统一管理。如果用枚举后续在代码里维护状态流转会非常清晰public enum OrderStatus { PENDING_PAYMENT(0, 待支付), PAID(1, 已支付), VIEWED(2, 已看房), SIGNED(3, 已签约), FINISHED(4, 已退租), CANCELLED(5, 已取消); private final int code; private final String desc; OrderStatus(int code, String desc) { this.code code; this.desc desc; } public int getCode() { return code; } public String getDesc() { return desc; } }表设计还有一个容易踩的坑house表的租金字段建议用BigDecimal而不是double。金额计算一旦用浮点型后面算押金、租金、水电费就会冒出0.1 0.2不等于0.3的问题。虽然毕设里金额计算不多但用BigDecimal会让答辩时的“为什么这么设计”显得专业。2.3 数据库索引与冗余字段怎么做房源列表页最常见的是按区域、价格区间、面积、户型、朝向筛选。如果表数据量大了全表扫描会非常慢所以需要给高频查询字段建索引。我的习惯是给house表的district_id、rent_price、status建联合索引并把“区域”这种东西冗余到house表里而不是每次联表查。举个例CREATE INDEX idx_house_query ON house (district_id, status, rent_price);不过索引也不是越多越好插入更新时索引会拖慢速度。毕设规模小两三个联合索引足够。我见过有的同学给每张表每个字段都加了索引反而得不偿失。3. Spring Boot 后端实现把业务闭环落到实处这一节讲的是真正的编码实现。我会按照一个标准的Spring Boot项目结构把登录、权限、房源接口、文件上传这几个核心模块的写法和思路串起来。3.1 项目结构分包方式决定后续维护体验Spring Boot项目分包看似简单但顺序错了后面很乱。我个人习惯用“controller / service / mapper / entity / dto / common / config”这种职责分包而不是按“user包、house包、order包”平铺。这样公共的返回结果、异常处理、配置类能放在一起后续查错方便。一个可参考的目录结构如下src/main/java/com/example/rent ├── common │ ├── Result.java │ ├── ResultCode.java │ └── exception │ ├── BizException.java │ └── GlobalExceptionHandler.java ├── config │ ├── WebMvcConfig.java │ ├── JwtInterceptor.java │ └── MybatisPlusConfig.java ├── controller │ ├── AuthController.java │ ├── HouseController.java │ └── OrderController.java ├── service │ ├── UserService.java │ ├── HouseService.java │ └── OrderService.java ├── mapper │ ├── UserMapper.java │ └── HouseMapper.java └── entity ├── User.java ├── House.java └── Order.javaResult类是统一响应体所有接口都返回同样的结构前端只需要解析一次。我一般是这么写的Data public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(操作成功); result.setData(data); return result; } public static T ResultT error(String message) { ResultT result new Result(); result.setCode(500); result.setMessage(message); return result; } }这样前端判断code 200就能放行否则弹错误消息不用每个接口都去处理网络异常和业务异常。3.2 JWT登录与登录拦截租房平台有三种角色登录方案我建议用JWT而不是Session。理由是Spring Boot做成前后端分离后后端不再关心Session存储前端可以把Token放在请求头里无状态扩展部署也方便。至于安全性JWT本身不加密敏感数据只是签名票据用户名、角色放在Payload里可以但不要放密码。登录接口的流程是前端传手机号和密码后端用BCryptPasswordEncoder加密密码再与数据库比对。密码永远不要明文存储。这个在我当年做的第一个项目里就踩过坑后来才补上的。生成Token后前端每次请求在Header里带Authorization: Bearer xxx后端用拦截器统一校验。Spring Boot里可以用HandlerInterceptor实现登录拦截Component public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行登录接口和静态资源 if (request.getRequestURI().contains(/auth/login) || request.getRequestURI().contains(/house/list)) { return true; } String token request.getHeader(Authorization); if (token ! null token.startsWith(Bearer )) { token token.substring(7); try { // 使用JWT解析校验签名和有效期 Claims claims JwtUtil.parseToken(token); request.setAttribute(userId, claims.get(userId)); request.setAttribute(role, claims.get(role)); return true; } catch (Exception e) { response.setStatus(401); response.getWriter().write({\code\:401,\message\:\登录已过期\}); return false; } } response.setStatus(401); response.getWriter().write({\code\:401,\message\:\未登录\}); return false; } }这里要注意放行接口不要图省事全部放行至少把需要登录的“我的收藏”“发布房源”“订单操作”都拦住。否则前端只能靠隐藏按钮来防越权后端等于裸奔。3.3 房源列表查询MyBatis Plus 分页与多条件组合房源列表是整个项目里最需要打磨的接口。租客可能按价格范围、区域、面积、房型来筛选还可能叠加关键词搜索。如果用MyBatis Plus可以借助LambdaQueryWrapper做动态条件。我的写法一般是这样的public IPageHouse queryHousePage(PageHouse page, HouseQueryDTO query) { LambdaQueryWrapperHouse wrapper new LambdaQueryWrapper(); // 关键词搜索 wrapper.like(StringUtils.isNotBlank(query.getKeyword()), House::getTitle, query.getKeyword()); // 区域 wrapper.eq(query.getDistrictId() ! null, House::getDistrictId, query.getDistrictId()); // 价格区间 wrapper.between(query.getMinPrice() ! null query.getMaxPrice() ! null, House::getRentPrice, query.getMinPrice(), query.getMaxPrice()); // 状态只查上架 wrapper.eq(House::getStatus, 1); // 排序最新发布优先 wrapper.orderByDesc(House::getCreateTime); return houseMapper.selectPage(page, wrapper); }这里有一个常见的坑Page对象需要传入当前页码和每页条数如果前端不传一定要给默认值。另外wrapper.like的第一个参数是condition如果条件为false就不拼这个SQL这个特性用好了能让接口非常干净。如果是更复杂一点的“附近房源”或“按距离排序”需要引入经纬度计算毕设里一般不用。但你可以把它写在文档“扩展设计”里说明用MySQL的ST_Distance_Sphere或者Redis Geo也能实现。这个属于加分项。3.4 房源图片上传别把图片塞进数据库图片上传是大学生租房平台必有的功能。房东在发布房源时要传多张室内图、户型图。最合理的做法是后端接收MultipartFile保存到服务器指定目录并返回可访问的URL。不要把图片转成Base64塞进数据库除非你做的是一次性允许几KB的图片否则数据库会被撑爆。我的实现思路配置文件里设置上传根路径例如upload.path/data/upload。用UUID.randomUUID()生成文件名防止重名和中文名乱码。保存后把相对路径/upload/xxx.jpg存入house_image表。在WebMvcConfig中把/upload/**映射到本地磁盘路径。Configuration public class WebMvcConfig implements WebMvcConfigurer { Value(${upload.path}) private String uploadPath; Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceLocations(file: uploadPath /); } }这样前端可以直接通过img src/upload/xxx.jpg访问图片。如果你用了本地开发环境上传路径最好用System.getProperty(user.dir)拼一个upload目录避免写成绝对路径后换电脑就崩。3.5 订单与事务一个订单流程要涉及几张表以“发起租赁订单”为例不只是插入order表中一条记录。租客下单时要做这几件事校验房源状态必须是“已上架”。校验租客不是房主本人自己不能租自己的房。创建订单状态为“待支付”。将房源状态改为“已锁定”或“待确认”避免被其他人重复下单。给房东生成一条站内通知。这五个操作必须放在同一个事务里。只要有一个失败整个下单都要回滚。Spring Boot里用Transactional注解就能解决Transactional(rollbackFor Exception.class) public Long createOrder(CreateOrderDTO dto) { House house houseMapper.selectById(dto.getHouseId()); if (house null || house.getStatus() ! HouseStatus.ON_SALE) { throw new BizException(房源不存在或已下架); } if (house.getLandlordId().equals(dto.getTenantId())) { throw new BizException(不能租赁自己的房源); } Order order new Order(); // ... 填充订单字段 orderMapper.insert(order); house.setStatus(HouseStatus.LOCKED); houseMapper.updateById(house); // 写入通知 noticeMapper.insert(new Notice(...)); return order.getId(); }事务这种东西平时项目里感觉不到但在并发场景下特别重要。答辩时只要讲一句“我用Transactional保证下单环节的数据一致性”老师就会觉得你懂事务的作用而不只是会用注解。4. Vue前端联动页面交互不是堆组件后端接口写好之后前端页面看似简单实际上也有很多容易忽略的点。尤其是权限路由、Token拦截、日期回显、文件回显这几块处理不好后面联调全是泪。4.1 路由守卫与角色菜单Vue项目里我建议用vue-router的路由元信息来判断角色。比如路由配置可以这样写{ path: /house/publish, name: HousePublish, component: () import(../views/house/Publish.vue), meta: { roles: [landlord] } }然后在全局前置守卫中判断角色router.beforeEach((to, from, next) { const token localStorage.getItem(token); if (!token to.path ! /login) { next(/login); return; } const role localStorage.getItem(role); if (to.meta.roles !to.meta.roles.includes(role)) { next(/403); return; } next(); });有的同学把按钮权限也写在页面里管理员才能看到“审核”按钮房东才能看到“发布房源”。这样就够了真正的权限控制还是要放在后端接口的拦截器里前端只是做体验优化。4.2 Axios 请求封装前端调接口最忌讳的是每个页面都写一遍axios.get然后自己处理错误。我用一个request.js统一封装核心功能是自动带Token、统一处理code非200的异常、后端返回401时跳到登录页import axios from axios; import { ElMessage } from element-plus; const request axios.create({ baseURL: /api, timeout: 10000 }); request.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers.Authorization Bearer ${token}; } return config; }); request.interceptors.response.use( response { const res response.data; if (res.code ! 200) { ElMessage.error(res.message); return Promise.reject(new Error(res.message)); } return res.data; }, error { if (error.response error.response.status 401) { localStorage.clear(); router.push(/login); } ElMessage.error(网络异常); return Promise.reject(error); } ); export default request;这里有个小细节如果后端的分页响应是IPage对象会和统一返回体再包一层。建议在文档中写明分页返回结构是{ total, records, current, size }前端可以直接用。4.3 日期处理和图片回显预约看房需要选择时间后端返回的LocalDateTime格式一般是2025-03-01T10:00:00前端如果不处理Element UI的el-date-picker可能显示不出时间。最简单的做法是在后端实体类字段上用JsonFormat指定格式JsonFormat(pattern yyyy-MM-dd HH:mm:ss) private LocalDateTime createTime;前端再用dayjs格式化双保险。图片回显更简单接口返回相对路径后拼上后端地址即可。但如果后端部署在服务器前端本地联调时直接把/upload/xxx.jpg拼到localhost:8080部署后要拼服务器域名。这个统一用一个全局变量管理不要到处写死。5. 部署上线与问题排查实录项目能本地跑起来只是第一步。我见过太多项目直到答辩前一天才发现打包后启动报错或者前端打包后访问不到后端接口。这里分享一套标准的全流程部署经验。5.1 Maven打包与配置文件分离Spring Boot项目用Maven打包非常简单mvn clean package -DskipTests打包后在target/目录下得到.jar文件。我强烈建议把配置根据环境分开例如application-dev.yml和application-prod.yml启动时指定java -jar target/rent-0.0.1-SNAPSHOT.jar --spring.profiles.activeprod如果数据库、上传路径这些配置不想打进包可以放在application-prod.yml里也可以支持外部化配置。上线环境我一般用宝塔面板或者直接systemd托管。如果你会Docker写一个Dockerfile也很快FROM openjdk:8-jre WORKDIR /app COPY target/rent-0.0.1-SNAPSHOT.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, app.jar]容器化部署在答辩时可以加分但需要你对Linux和Docker基本操作有把握不要只背书。前端打包后是静态文件可以用Nginx托管然后通过反向代理把/api转发到后端服务。Nginx配置片段server { listen 80; server_name your-domain.com; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }注意proxy_pass后面的斜杠/api/会被替换成后端根路径如果不加斜杠则会把/api也带上容易踩坑。5.2 高频报错与排查速查表做毕设的过程中几乎每个人都会遇到下面这些报错。我把高频问题和解决办法整理成一张表按这个挨个排查能省一晚上的时间。现象可能原因解决思路启动时报Port 8080 was already in use端口被占用改用server.port8081或杀掉占用进程数据库连接失败MySQL没启动、账号密码错、时区配置不对检查url加上useSSLfalseserverTimezoneAsia/Shanghai实体类映射不到数据库表类名和表名不一致使用TableName(house)指定表名前端访问接口跨域vue端口和springboot端口不一致在SpringBoot配置跨域过滤器或通过Nginx代理上传图片访问404静态资源映射没配置检查WebMvcConfig的映射路径打包后因为JDK版本报错本地用JDK 11打包环境用JDK 8统一pom.xml中的java.version使用Spring Boot 3.x时javax不存在新版本改成了jakarta换成Spring Boot 2.7.x或改用jakarta.servlet其中最常见的就是Spring Boot版本太高导致代码全部报红。很多教程用的还是2.x版本如果你的环境和教程不一致建议直接固定版本别盲目追新。我建议用Spring Boot 2.7.x配合JDK 8/11资源最多踩坑最少。5.3 数据一致性排查订单提交后房态没变、收藏数量没变、通知没发出去这类问题大概率是事务没有生效。排查时看两件事第一方法是不是public第二是不是被同类里另一个方法调用。Spring的事务是基于AOP的如果同类内部调用事务注解会失效。另一个隐蔽问题是自增主键用完。MyBatis Plus默认使用雪花算法生成ID如果实体类主键没有加TableId(type IdType.AUTO)插入数据时主键会生成一个很大的雪花ID和数据库自增ID对不上导致查询异常。建议每个实体类明确主键类型。6. 文档写作与答辩准备项目做完事情才做了一半很多同学的误区是程序写完了文档最后几天随便拼一拼。但实事求是地讲老师看毕业设计时会花相当多时间翻阅文档和源码结构文档的质量直接影响最终分数。所以我把文档写作的方法也放在这篇里讲清楚。6.1 毕业论文结构按“瀑布模型”组织最安全网上模板很多但一般逃不出这几个章节绪论、相关技术介绍、需求分析、系统设计、系统实现、系统测试、总结与展望。这个结构本身就是经典软件工程的流程照着写不容易出大问题。需求分析部分要画用例图至少包括租客用例、房东用例、管理员用例。不会画也不要紧用Word画图就够了但一定要分清每个角色能做什么。系统设计部分重点写数据库设计把ER图放上去再列出核心表的结构关键字段注释写全。实现部分不要贴大段源码而是贴核心代码片段并解释流程。测试部分用表格写测试用例比如“输入预约时间重叠数据期望提示冲突”。写文档时我还建议加一个“系统部署”小节把本地开发环境和上线环境的启动步骤写清楚方便老师部署检查。不要小看这一步很多老师真的会按文档一步步重现项目如果文档里缺依赖说明印象分会降不少。6.2 答辩高频问题与应对思路答辩时老师问的问题往往不是单纯问“这段代码怎么写”而是问“你为什么这么设计”。这里整理几个我实际遇到过的高频问题老师可能会问建议回答思路为什么选Spring Boot自动配置减少开发成本生态成熟适合前后端分离登录如何保持状态JWT无状态登录服务端不存Session前端存Token密码怎么存的BCrypt加密不解析为明文即使泄露也不能直接登录房源搜索SQL怎么优化联合索引 MyBatis Plus动态条件分页查询订单取消后房源状态怎么变取消订单后把房源状态从锁定改为已上架并更新操作时间如果两个租客同时租同一间房怎么办下单时用事务状态判空必要时加数据库行锁回答问题的核心是“自圆其说”。哪怕你的设计不是最完美的但只要你能讲出当初为什么这样选、有什么局限、如果要改进会怎么做老师基本都会认可。最怕的是背了一堆术语但项目里的代码对不上那样反而容易被追问到崩溃。6.3 从毕设到简历这个项目还能怎么延伸如果时间允许给这个项目加两个加分点一是用Redis缓存热门房源列表展示缓存穿透的解决方案二是给订单模块增加定时任务比如“超过30分钟未支付自动取消订单”。这两个功能代码量不大但能体现你对并发、缓存、定时任务的理解。另外我强烈建议你在README里写一个完整的上手指南把数据库初始化脚本、后端启动步骤、前端启动步骤、测试账号都列出来。这不仅是为了老师也是为了让未来的你或者看这个项目的人能快速跑起来。源码只有在能跑起来的前提下才有价值。回到开头那个问题大学生租房平台真的只是增删改查吗做完之后我有了更明确的答案——增删改查只是外壳里面装的其实是完整的Web开发流程。把这条流程走通比背一百道面试题都管用。如果你正在做这个项目我的建议只有一句不要急着复制别人的源码先把表结构设计好再一个模块一个模块地实现。过程中遇到的所有问题最后都会变成你答辩时的底气。
返回列表