前阵子帮某高校的学弟梳理他的毕业设计项目,正好是“基于SpringBoot+Vue的大学生在线租房平台管理系统”。这个项目做完之后我最大的感受是:它几乎覆盖了前后端分离开发里最典型的一整套流程——从用户体系、权限控制,到房源这种多状态业务数据的流转,再到预约、合同这类带有状态机特征的业务模块。技术栈是Java + SpringBoot + Vue + MySQL + MyBatis,源码完整,非常适合拿来当课设、毕设的基线项目,也适合刚入门前后端分离开发的读者用来理解整个项目的骨架和业务逻辑。
这篇文章我会把整个系统的需求拆解、数据库设计、后端实现、前端页面组织、部署运行过程,以及我在实际调试中踩过的坑,按开发顺序完整地过一遍。部分代码和配置为了方便展示会做简化,但核心思路不变,照着复现不是一个困难的事。
1. 为什么大学生租房需要一套独立的管理系统——需求分析与功能拆解
1.1 大学生租房的痛点在哪里
大学生在外租房,和普通社会租客不太一样。他们大多是第一次接触租房,对市场行情不熟,对合同条款不敏感,预算也相对有限。市面上那些通用租房平台虽然房源多,但信息噪音大,中介植入多,学生用户很容易被虚假房源误导,又不容易判断房东的真实身份。
我当时给学弟做需求梳理时,最先做的不是画页面原型,而是把所有可能的用户角色和他们的痛点列出来。这套系统涉及的核心角色有四类:
- 学生(求租者):要找靠谱房源、收藏对比、预约看房、签合同、入住后有问题能反馈。
- 房东(房源提供者):要发布房源、管理自己名下的房源状态、处理学生的看房预约、在线确认合同。
- 系统管理员:要审核房源是否真实合规、管理注册用户、发布平台公告、处理用户反馈。
- 访客:可以浏览房源列表和详情,但只有注册登录后才能执行收藏、预约等操作。
这些角色一旦理清楚,功能边界就清楚了。这个系统不是简单做一个“房源信息展示页”,而是一个带有审核机制、预约流程、合同生命周期的管理平台。这也是它区别于普通CRUD演示项目的地方——业务状态复杂,角色权限分明,适合用来展示完整开发能力。
1.2 从痛点推导出功能矩阵
基于上面四类角色,我把功能拆成四大模块。
- 用户模块:注册、登录、角色区分(学生/房东/管理员)、个人资料维护。
- 房源模块:房东发布房源、编辑房源、上下架管理;访客和学生浏览房源、按价格/区域/户型筛选;管理员审核房源。
- 交易模块:学生预约看房、房东确认或拒绝预约、双方在线确认合同、合同状态流转。
- 管理与辅助模块:管理员管理用户和审核房源、发布公告;学生收藏房源和评价;用户提交反馈。
这块设计做完,我第一个体会是:不要一上来就写代码,需求拆解做到位,后面所有表结构和接口都是顺着这个功能矩阵长出来的。很多同学做毕设卡壳,不是卡在编码,而是卡在需求模糊,做一步想一步,最后代码逻辑前后矛盾。
2. 技术选型的账本:SpringBoot、Vue、MyBatis、MySQL的组合逻辑
2.1 后端为什么选SpringBoot而不是SSH或者别的
在做技术选型时,学弟问过我:现在学校还在教SSH(Struts + Spring + Hibernate),能不能用那个做?我跟他说:能用,但没必要。SSH的技术体系在现在的企业开发里已经几乎看不到了,配置繁琐,社区资料也少,遇到问题很难查。SpringBoot的核心优势是“约定大于配置”,内置Tomcat,一个main方法就能启动,自动配置省掉了大量XML配置。它的生态极其成熟,和MyBatis、Spring Security、JWT这些组件的整合方案都很成熟,出问题能搜到的资料也最多。
另外一点很实际:毕设答辩时,SpringBoot是当前绝大多数评审老师熟悉的技术框架,你在答辩时不需要花大量时间解释“为什么用这个技术”,技术认可度天然高。
2.2 Vue在前后端分离架构里的定位
前端选Vue,理由也很直接——学习曲线平缓,中文资料极多,模板语法直观。对于需要在一个学期内完成项目并写论文的同学来说,Vue 2 + Element UI的组合可以让界面开发效率提高很多。Element UI直接提供表格、表单、对话框、分页这些成套组件,不需要从零写样式。
前后端分离是一个重点表达项。后端只提供RESTful接口,返回JSON数据;前端通过Axios请求接口渲染页面。这种架构和传统JSP页面最大的区别是:前端和后端可以完全独立开发、独立部署。在写论文时,“前后端分离架构设计”可以单独作为一章来展开,内容非常充实。
2.3 MyBatis与MySQL:持久层和数据库的配合
MyBatis在这个项目里的角色是持久层框架。我的选择理由可以简单概括为三点:
- 手写SQL灵活可控,多表联查、条件筛选这类复杂查询,写SQL比ORM自动生成的语句更直观、更易优化。
- 动态SQL能力很强,房源列表的筛选条件可能动态组合,
<if>、<where>这些标签正好能解决这个问题。 - 和SpringBoot整合非常成熟,
mybatis-spring-boot-starter一个依赖就搞定。
数据库选MySQL就不用多说了,开源免费、稳定可靠,学校机房、个人电脑都能跑。5.7或8.0都可以,需要注意连接驱动和时区配置的差异,这点我后面在部署章节会专门讲。
2.4 这套技术栈对学习者意味着什么
把这四个技术组合在一起,背后是一个完整的现代Web开发链路:SpringBoot负责后端接口和业务逻辑,MyBatis负责数据持久化,MySQL负责数据存储,Vue负责前端交互和界面渲染。这套链路和当前中小型企业的实际技术栈非常接近。我见过很多学生项目用的是"SpringBoot + Thymeleaf"老一套模板渲染,也不是不行,但前后端分离的经验在简历上明显更好写。
3. 数据地基:核心表结构设计与字段规划
3.1 用户体系设计——学生与房东的双角色模型
数据库是整个系统的地基,表结构设计得好不好,直接决定后面代码写起来顺不顺畅。我用的是最常见的单表用户设计:一张用户表,用role字段区分角色。虽然也可以设计成学生表、房东表、管理员表分开存储,但对于这个体量的项目,单表加角色字段更简洁,查询也更方便。
CREATE TABLE `t_user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL COMMENT '登录用户名', `password` varchar(255) NOT NULL COMMENT '密码(BCrypt加密)', `phone` varchar(20) DEFAULT NULL COMMENT '手机号', `real_name` varchar(50) DEFAULT NULL COMMENT '真实姓名', `avatar` varchar(255) DEFAULT NULL COMMENT '头像地址', `role` tinyint(4) NOT NULL DEFAULT '0' COMMENT '角色:0-学生,1-房东,2-管理员', `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态:1-正常,0-禁用', `create_time` datetime NOT NULL COMMENT '注册时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;这里有两个设计细节值得注意。第一,密码必须加密存储,我建议使用BCrypt加密,而不是MD5。MD5虽然也能用,但彩虹表攻击下几乎形同虚设,用spring-security-crypto里的BCryptPasswordEncoder几行代码就能实现,答辩时这也是一个很好的技术亮点。第二,user表里加一个status字段用于禁用异常账号,管理员封号时不需要删数据,只改状态。
3.2 房源信息表:房源状态的生命周期管理
房源表是整个业务的核心,字段设计上需要平衡“信息展示完整”和“查询效率”。我的做法是把房源基础信息拆成两部分:一部分是文本类信息,比如标题、描述、地址;另一部分是价格、面积、户型这类结构化信息。图片可以JSON格式存多个地址,方便前端循环渲染。
CREATE TABLE `t_house` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `landlord_id` bigint(20) NOT NULL COMMENT '房东用户ID', `title` varchar(100) NOT NULL COMMENT '房源标题', `cover` varchar(255) DEFAULT NULL COMMENT '封面图', `images` text COMMENT '轮播图列表(JSON数组)', `price` decimal(10,2) NOT NULL COMMENT '月租金', `deposit` decimal(10,2) DEFAULT NULL COMMENT '押金', `area` decimal(10,2) DEFAULT NULL COMMENT '面积(㎡)', `room_type` varchar(20) DEFAULT NULL COMMENT '户型,如:一室一厅', `floor_info` varchar(50) DEFAULT NULL COMMENT '楼层信息,如:5/18层', `community` varchar(100) DEFAULT NULL COMMENT '小区名称', `address` varchar(255) DEFAULT NULL COMMENT '详细地址', `city` varchar(50) DEFAULT NULL COMMENT '城市', `district` varchar(50) DEFAULT NULL COMMENT '区域', `description` text COMMENT '房源描述', `status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '状态:0-待审核,1-已上架,2-已出租,3-已下架', `views` int(11) NOT NULL DEFAULT '0' COMMENT '浏览次数', `create_time` datetime NOT NULL COMMENT '发布时间', `update_time` datetime DEFAULT NULL COMMENT '更新时间', PRIMARY KEY (`id`), KEY `idx_landlord` (`landlord_id`), KEY `idx_city_district` (`city`, `district`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;房源状态我设计成四个:待审核、已上架、已出租、已下架。这个状态设计贯穿了平台的审核机制和交易闭环。新发布的房源不能直接上架,必须管理员审核通过后才对外展示,这是平台信息质量的保证。房源一旦签订合同,就自动变为“已出租”,防止同一个房源被重复签约。查询列表时,普通用户只能看到status=1的房源。
设计索引时我额外加了idx_city_district组合索引,因为前端列表最常见的筛选就是“城市 + 区域”,查询频率极高。
3.3 预约与合同流程的数据建模
预约和合同是交易模块的核心,也是最容易体现“系统设计能力”的地方。预约表的核心是记录一次看房申请的状态流转;合同表则记录了最终的租赁关系。
CREATE TABLE `t_appointment` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `house_id` bigint(20) NOT NULL, `student_id` bigint(20) NOT NULL COMMENT '预约学生ID', `landlord_id` bigint(20) NOT NULL COMMENT '房东ID(冗余存储,便于查询)', `visit_time` datetime DEFAULT NULL COMMENT '期望看房时间', `student_phone` varchar(20) DEFAULT NULL COMMENT '联系电话', `message` varchar(255) DEFAULT NULL COMMENT '留言', `status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '状态:0-待确认,1-已接受,2-已拒绝,3-已完成,4-已取消', `create_time` datetime NOT NULL, PRIMARY KEY (`id`), KEY `idx_house` (`house_id`), KEY `idx_student` (`student_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `t_contract` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `house_id` bigint(20) NOT NULL, `student_id` bigint(20) NOT NULL, `landlord_id` bigint(20) NOT NULL, `start_date` date NOT NULL COMMENT '合同开始日期', `end_date` date NOT NULL COMMENT '合同结束日期', `rent` decimal(10,2) NOT NULL COMMENT '合同租金', `deposit` decimal(10,2) NOT NULL COMMENT '押金', `payment_cycle` tinyint(4) NOT NULL DEFAULT '1' COMMENT '支付周期:1-月付,2-季付', `content` text COMMENT '合同条款', `status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '状态:0-待签署,1-进行中,2-已到期,3-已终止', `create_time` datetime NOT NULL, `sign_time` datetime DEFAULT NULL COMMENT '签署时间', PRIMARY KEY (`id`), KEY `idx_student` (`student_id`), KEY `idx_house` (`house_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;这里有一个我自己比较看重的设计细节:t_appointment表里冗余存储了landlord_id。从表结构上这似乎违反了“减少冗余”的设计原则,但实际使用中,房东需要快速查出“所有关于我房源的预约”,如果每次都通过house_id去关联查询,列表页会多一次联表。冗余一个房东ID,可以让“查我的预约列表”变成单表条件查询,性能好,代码也简单。数据一致性完全可由业务代码保证——创建预约时同时写入两个ID。
合同表的content字段可以直接存一份合同条款文本,真实项目中合同内容通常来自模板动态生成,在这个系统里可以简化为房东填写的条款或系统预置模板。
3.4 辅助功能表:收藏、评价、公告与反馈
这几个表结构相对简单,但也不建议省略。收藏表解决“我关注的房源”这个需求;评价表记录学生对已入住房源的评分和文字评价;公告表给管理员发布平台通知;反馈表给学生提交问题。
CREATE TABLE `t_favorite` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) NOT NULL, `house_id` bigint(20) NOT NULL, `create_time` datetime NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_user_house` (`user_id`, `house_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;收藏表加唯一约束uk_user_house很重要,防止同一用户重复收藏同一房源。这个约束在接口层也要再做一遍判断,数据库约束是兜底,业务判断是用户体验。
评价表我用rating字段保存1-5的整数评分,前端用星级组件渲染。有些同学会把评分做成浮点数,比如4.5分,这样后期统计平均分确实更精细,但会引入更多校验逻辑。对课设/毕设项目来说,整数评分完全够用,反而能让数据库和代码更简洁。
4. 后端核心实现:接口设计与业务状态流转
4.1 登录鉴权与JWT的使用
这套系统的鉴权我建议用JWT,而不是传统的Session。原因是前后端分离架构下,后端接口是纯API,天然无状态,JWT把用户身份信息加密保存在token里,前端每次请求在请求头带上Authorization字段即可。SpringBoot整合JWT非常成熟,核心依赖就两个:java-jwt或jjwt,再配合一个拦截器实现接口保护。
登录接口的核心逻辑:先按用户名查用户,再用BCryptPasswordEncoder.matches校验密码,校验通过后生成token返回给前端。
@Service public class AuthService { @Autowired private UserMapper userMapper; public LoginResult login(String username, String password) { User user = userMapper.findByUsername(username); if (user == null) { throw new BusinessException("用户不存在"); } if (user.getStatus() == 0) { throw new BusinessException("账号已被禁用,请联系管理员"); } BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); if (!encoder.matches(password, user.getPassword())) { throw new BusinessException("密码错误"); } String token = JwtUtil.generateToken(user.getId(), user.getUsername(), user.getRole()); return new LoginResult(token, user.getId(), user.getUsername(), user.getRole()); } }拦截器负责两件事:校验token是否合法、判断用户角色是否允许访问某个接口。我倾向于把角色校验也放在拦截器里统一做,接口层就可以写得很干净。
@Component public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (OPTIONS.equals(request.getMethod())) { return true; } String token = request.getHeader("Authorization"); if (token == null || token.isEmpty()) { throw new BusinessException("未登录或登录已过期"); } UserContext user = JwtUtil.parseToken(token); if (user == null) { throw new BusinessException("token无效"); } request.setAttribute("currentUser", user); return true; } }实际开发时,不同接口的角色控制精度不同。比如发布房源只需要“已登录”拦截,而审核房源则必须“管理员角色”才能访问。解决办法是在拦截器里结合@RequiresRole注解做精细控制,或者在后端Controller上手动判断UserContext中的角色值。对课设项目来说,后一种更直观。
4.2 房源发布与审核的接口逻辑
房源模块的接口是整个系统数量和复杂度最高的部分。列表查询、详情、发布、编辑、上下架、审核,每个接口都要考虑状态与权限。我挑两个最容易出错的点讲。
第一个是发布接口。房东提交房源表单后,系统不是直接把它设为“已上架”,而是设为“待审核”。insert之后还要做一件事:给管理员的待审核列表能看到这条新数据。这里不需要额外的“通知表”,管理员查询时直接按status=0过滤即可。
第二个是列表查询。房源列表要支持关键词搜索、城市/区域筛选、价格区间、户型筛选等多重条件,如果用固定SQL写死,条件一多就得写大量拼接逻辑。我用MyBatis动态SQL解决:
<select id="searchHouses" resultType="com.example.entity.House"> SELECT * FROM t_house <where> <if test="status != null"> AND status = #{status} </if> <if test="city != null and city != ''"> AND city = #{city} </if> <if test="district != null and district != ''"> AND district = #{district} </if> <if test="minPrice != null"> AND price >= #{minPrice} </if> <if test="maxPrice != null"> AND price <= #{maxPrice} </if> <if test="roomType != null and roomType != ''"> AND room_type = #{roomType} </if> <if test="keyword != null and keyword != ''"> AND (title LIKE CONCAT('%', #{keyword}, '%') OR community LIKE CONCAT('%', #{keyword}, '%') OR address LIKE CONCAT('%', #{keyword}, '%')) </if> </where> ORDER BY create_time DESC </select>这里要注意MyBatis的XML写法:>、<符号必须转义为>、<,不然后端启动时会报XML解析错误。这个问题我见过很多同学踩,卡了一下午才发现是没转义。
4.3 预约看房与合同签订的状态流转
预约和合同这两个模块的核心不是增删改查,而是状态机。我在代码层面不是用什么状态机框架,而是自己用if-else严格约束状态流转路径。
预约状态的流转路径是这样的:
- 学生创建预约 →
待确认(0) - 房东接受 →
已接受(1);房东拒绝 →已拒绝(2) - 双方完成看房,学生确定要租并进入签约流程 →
已完成(3) - 学生在任何前置状态下取消 →
已取消(4)
在“房东接受预约”的接口里,必须判断当前状态是否为待确认,只有这个状态才能被修改为已接受或已拒绝。如果预约已经是已取消状态,房东再操作就应该提示“该预约已取消”。这个判断就是状态机控制,能有效防止接口在并发或重复请求下产生脏数据。
public void acceptAppointment(Long appointmentId, Long landlordId) { Appointment appointment = appointmentMapper.findById(appointmentId); if (appointment == null) { throw new BusinessException("预约不存在"); } if (!appointment.getLandlordId().equals(landlordId)) { throw new BusinessException("无权操作该预约"); } if (appointment.getStatus() != 0) { throw new BusinessException("当前状态不允许该操作"); } appointment.setStatus(1); appointmentMapper.updateStatus(appointment); }合同签订的逻辑更复杂一些。房东接受预约后,由房东发起创建合同,此时合同状态为“待签署”。学生端看到待签署的合同,确认无误后点击“签署”,合同状态变为“进行中”。合同生效的同时,系统要把对应房源状态更新为“已出租”,防止房源继续被其他用户预约。
这个“跨表事务”必须加@Transactional注解,保证合同状态和房源状态要么同时成功,要么同时失败。少了这个注解,就会出现“合同签了但房源还是已上架状态”的严重bug。
4.4 MyBatis的多表联查与VO设计
业务中还有大量“查列表但数据来自多张表”的场景。比如管理员的任务中心要同时看到“用户头像 + 房源标题 + 房源状态”,预约列表要显示“房源标题 + 封面 + 学生姓名 + 手机号”。
MyBatis处理这种场景有两种思路:一种是直接写ResultMap做联查映射;另一种是在Mapper里写JOIN查询,把结果映射到一个自定义的VO类。
我倾向于用后者,更直白也更好维护。比如预约列表,先定义一个AppointmentVO,里面包含预约本身字段加上房源标题、封面,以及学生名字。
<select id="findAppointmentVOByLandlord" resultType="com.example.vo.AppointmentVO"> SELECT a.*, h.title AS houseTitle, h.cover AS houseCover, h.address AS houseAddress, u.real_name AS studentName FROM t_appointment a LEFT JOIN t_house h ON a.house_id = h.id LEFT JOIN t_user u ON a.student_id = u.id WHERE a.landlord_id = #{landlordId} ORDER BY a.create_time DESC </select>LEFT JOIN在这里很重要,如果用INNER JOIN,万一房源被删除,预约记录也会跟着查不出来。虽然系统里房源默认不做物理删除,但这种容错意识还是得有。
5. 前端实现思路:Vue页面组织与交互设计
5.1 页面结构与路由设计
前端我按“门户页面 + 业务管理端”两大部分来划分。门户页面包括首页、房源列表、房源详情、登录注册;业务管理端则根据登录角色动态渲染不同的后台界面。
路由设计上,我会在router/index.js里做路由表和守卫。守卫的逻辑是:进入任何需要登录的页面之前,检查本地存储是否有token;进入管理员页面之前,检查本地存储里的角色值是否为管理员。
router.beforeEach((to, from, next) => { const token = localStorage.getItem('token'); if (to.meta.requiresAuth && !token) { next({ path: '/login', query: { redirect: to.fullPath } }); } else if (to.meta.requiresAdmin) { const role = localStorage.getItem('role'); if (role !== '2') { next({ path: '/' }); } else { next(); } } else { next(); } });这里有个提效的技巧:不同角色登录后虽然看到的功能菜单不同,但很多页面组件是可以复用的。比如“学生端-我的预约”和“房东端-预约管理”,表格列不一样,但操作按钮的逻辑都差不多,我会用v-if按角色渲染不同操作列,而不是写两套完全独立的组件。
5.2 房源展示与筛选的交互细节
首页和房源列表页是访客和学生看到的第一张脸,体验好不好,直接影响印象分。我用Element UI的el-card做房源卡片,展示封面、标题、价格、小区名称、户型、面积这些核心字段。价格用醒目红色标注,其他信息用小字号排布。
筛选区域做成表单形式,包含城市、区域、价格区间、户型四个下拉框,配合一个“搜索”按钮。筛选逻辑的整体封装也非常简单:
async loadHouses() { const params = { page: this.currentPage, size: this.pageSize, city: this.filters.city || null, district: this.filters.district || null, minPrice: this.filters.minPrice || null, maxPrice: this.filters.maxPrice || null, roomType: this.filters.roomType || null, keyword: this.keyword || null }; const res = await request.get('/api/house/list', { params }); this.houseList = res.data.records; this.total = res.data.total; }后端接口已经用MyBatis动态SQL处理了条件可选,前端只需要把空值传成null就行,不需要判断空字符串。
详情页的交互要注意三个点:轮播图展示、房源信息分区展示、操作按钮按状态展示。一手房源已经“已出租”,按钮就要置灰并显示“已租”,不能再允许预约。这个状态判断放在前端更直观,同时后端接口也要做兜底校验——前端只是体验优化,后端才是安全边界。
5.3 角色权限控制与动态菜单
权限这块是前端最容易做得繁琐的部分。我用的方案比较简单直接:登录成功后将用户信息和角色写入localStorage,在侧边栏菜单里用v-if按角色控制哪一个菜单项显示。
<el-menu> <el-menu-item v-if="role === '2'" index="/admin/user">用户管理</el-menu-item> <el-menu-item v-if="role === '2'" index="/admin/house">房源审核</el-menu-item> <el-menu-item v-if="role === '1'" index="/landlord/house">我的房源</el-menu-item> <el-menu-item v-if="role === '1'" index="/landlord/appointment">预约管理</el-menu-item> <el-menu-item v-if="role !== '2'" index="/user/favorite">我的收藏</el-menu-item> </el-menu>这种方案足以应对这个项目的需求,而且思路清晰,答辩时也好解释。如果要做得更工程化,可以用动态路由方案,后端根据角色返回菜单权限,前端用router.addRoutes动态注入,但会对项目复杂度带来不小的上升。课设项目做到“路由守卫 + 菜单按角色显示”这一层,已经能体现完整的权限设计意识。
前端和本地后端的联调,还要处理跨域问题。我用的方案是在vue.config.js里配置开发服务器的代理,而不是在后端开启全局CORS——开发环境下代理不仅解决跨域,还能让前端请求路径保持简洁。
module.exports = { devServer: { port: 3000, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } };这样前端所有请求都直接写成/api/xxx,后端接口统一以/api开头。部署时前端构建后的静态文件也可以由Nginx反向代理同一个/api前缀到后端服务,这套方案可以无缝从开发环境迁移到生产环境。
6. 从源码到运行:环境配置与部署实操记录
6.1 完全的环境准备清单
拿到完整源码后,第一件事不是急着打开IDE,而是确认环境齐不齐。我列一个最低版本清单,照着准备即可:
| 工具 | 版本建议 | 说明 |
|---|---|---|
| JDK | 1.8 | 项目基于Java 8语法,版本过高可能需要调整编译参数 |
| MySQL | 5.7 或 8.0 | 5.7和8.0驱动依赖不同,下文有说明 |
| Maven | 3.6+ | 管理后端依赖 |
| Node.js | 14+ | 前端构建和包管理需要 |
| IDE | IDEA(后端)、VSCode或IDEA(前端) | 前后端都可以在IDEA里开发 |
数据库初始化时,一般源码包里会附带一个sql文件。执行方式是命令行或Navicat,把整个sql文件导入即可。导入完成后检查一下表数量,比如上面设计的7到8张表是否都在,有些项目的sql文件里还顺带插入了一个管理员账号、几个测试学生账号和若干演示房源,这类初始数据对调试很有帮助。
6.2 数据库初始化和项目启动步骤
后端启动的流程我总结为四步:
- 修改
application.yml里的数据库连接信息,把url、username、password换成自己本机的参数。 - 确认
pom.xml里的MySQL驱动版本和本机MySQL版本对应。MySQL 8.0要使用com.mysql.cj.jdbc.Driver,并且URL里带上serverTimezone=Asia/Shanghai。 - 在IDEA里导入项目,等待Maven下载依赖,执行
SpringbootApplication主类。 - 看到“Started Application”日志,说明后端启动成功。
前端启动流程是:
npm install npm run servenpm install npm run serve这里多提一句,npm install在部分网络环境下会很慢,可以把npm源切到国内镜像,比如https://registry.npmmirror.com,速度会明显提升。
6.3 首次运行时最常遇到的三个问题
我帮学弟调试时遇到过几个高频问题,这里把排查思路写透。
第一个是MySQL连接失败。报错多为Communications link failure或者Public Key Retrieval is not allowed。前者一般是URL里的serverTimezone没配置或者端口不对;后者是MySQL 8.0的特有问题,在URL末尾加allowPublicKeyRetrieval=true即可解决。
第二个是后端启动成功后,前端登录接口请求报错“跨域”。如果你已经在vue.config.js里配了代理,那大概率是请求没有走代理,比如写成了http://localhost:8080/api/login,绕过代理直接请求后端,后端又没有开启CORS。统一改用相对路径/api/login即可。
第三个是前端页面能开,但验证码或图片不显示。测试阶段最常见的做法是图片暂时用外链或本地占位图,但外链可能被防盗链拦截。比较稳妥的做法是把图片放到前端public目录下,用相对路径引用;或者后端做一个静态资源映射,把上传目录映射为/images/**。
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/images/**") .addResourceHandler("file:" + System.getProperty("user.dir") + "/upload/"); } }顺带说一句,很多源码项目会有一个file:上传路径写死的配置,如果你换电脑运行,记得把路径改成实际存在的目录,否则图片接口会404。
7. 写给后来者:开发周期、优化方向与我的体会
7.1 一个合理的时间安排参考
如果你是从零开始做这个项目而不是直接用现成源码,我建议把时间分成四段。第一周做需求分析和数据库设计,把角色、功能、表关系画清楚。第二周到第三周做后端,建议顺序是用户认证、房源模块、预约合同、管理后台。第四周到第五周做前端页面,同样按模块推进。最后留一周联调、写论文和准备答辩PPT。
实际经验是:数据库设计占用的时间应该占总周期的20%以上,这块基础打不好,后面返工成本极高。我在第三章节花大量篇幅讲表结构,就是因为我见过太多人因为t_house表少了个状态字段,最后写代码时不得不一遍遍补逻辑。
7.2 可以进一步提升的方向
如果你手里这套源码已经能完整跑通,想要在答辩或面试时多讲出一些亮点,可以从这几个方向做小成本优化。
- 分页优化:接入
PageHelper分页插件,把列表接口里的LIMIT ?参数封装掉;或者干脆自己写一个通用分页对象,体现你对分页原理的理解。 - Redis缓存热点房源:把详情页的房源信息缓存到Redis,设置过期时间,并在房源被编辑或状态变更时主动清除缓存。可以很小地改造,但能讲出“缓存穿透/缓存雪崩”这些面试高频概念。
- 图片上传改造:把房源图片的本地存储改成云存储或对象存储,前端地址改为CDN,这个改动在答辩展示里很加分。
- 消息通知:预约被接受、合同待签署这类事件,可以用简单的站内信表或WebSocket推送实现通知功能,用户体验好很多。
我的意见是只选一两个方向做深,不要全做。课设项目的重点是把一个点做扎实,而不是功能堆砌。
7.3 我的心得体会
帮学弟把这个项目完整梳理一遍之后,我最大的感触是:课设和毕设项目的核心价值,不在于功能多复杂,而在于你有没有把“设计”的思维路径完整走一遍。从需求拆解到表结构,从状态机到接口权限,从前端交互到部署运行,每个环节都有合理的取舍和明确的理由。答辩时老师问你“为什么这个表要冗余一个字段”“为什么这个接口要加状态判断”,你能立刻答出背后逻辑,比堆砌一堆高级框架却一问三不知要有效得多。
如果你正准备动手,我的建议是:先别急着打开IDE,拿张白纸把用户角色、功能矩阵、核心表结构画出来,一遍不对就画第二遍。等图纸清晰了,代码只是水到渠成的事。按照这篇文章的顺序,配合完整源码,两到三周把系统跑通、理顺、学会讲解,是完全可行的。
最后再分享一个小技巧:项目跑通后,把后端的application.yml里的密码和连接信息改成环境变量占位方式,比如${DB_PASSWORD},让配置和代码解耦。这个细节可能在评分标准里不会明说,但能在代码审查环节给人留下“这个学生有工程意识”的印象,不亏。