☰
基于SSM与微信小程序的房屋租赁系统实战:从后端搭建到小程序联调
2026/10/4 7:13:18 网站建设 项目流程

简介:这份资源是基于SSM框架的微信小程序房屋租赁系统完整项目源码,面向计算机相关专业的毕业设计、课程设计学生以及Java Web初学者。项目将Spring、SpringMVC、MyBatis与微信小程序结合,覆盖房源发布、租房需求、在线预约看房、支付与订单管理等核心业务,帮助读者理解前后端分离的轻量级应用开发流程。压缩包共1343个文件,约4.05MB,包含59个Java源文件、53个JSP页面、30个WXSS与29个WXML小程序页面,以及大量HTML、JS、CSS、PNG等前端资源,另有1个SQL数据库脚本和若干配置文件,结构清晰、模块分明。已有48人学习下载。读者可从中获得完整的赛题级实现方案,包括控制器分层设计、数据库表结构、小程序页面布局与接口调用思路,适合直接参考或二次开发,快速搭建自己的房屋租赁系统。

1. 从一份 SSM 微信小程序房屋租赁系统说起:谁需要它,能解决什么

如果你手上有「基于SSM的微信小程序房屋租赁系统设计.zip」这类标题的项目,大概率是三种人之一:正在做毕设的学生、想快速搭一套房源管理后台的独立开发者、或者被中介业务方催着要一个「能看房、能预约、能管房源」的小团队。这个标题拆开看其实就三块:SSM(Spring + SpringMVC + MyBatis)做后端,微信小程序做用户端,房屋租赁系统是业务场景。它要解决的核心问题很朴素——房东或运营方在后台录房源,租客在小程序里按区域、租金、户型筛选,点进去看详情、预约看房、提交租赁意向,管理员再在后台处理这些线索。

我见过太多人一上来就纠结「用不用 SpringBoot」,其实 SSM 和 SpringBoot 不是对立的,SSM 是骨架,SpringBoot 只是帮你省掉一堆 XML 配置的脚手架。这个项目真正值得投入的点在于:微信小程序天然适合租房这种「低频、重决策、需要随时翻看」的场景,用户不用装 App,扫码或搜一下就能进,分享给合租室友也方便。而 SSM 这套组合在国内中小型管理系统里沉淀了快十年,资料多、坑位明确、招人也好招。所以它不是一个炫技项目,是一个能跑通、能交付、能二次开发的务实方案。适合谁?适合需要一套「房源 CRUD + 小程序端展示 + 预约流程」最小闭环的人,不适合想直接拿去做高并发 SaaS 的人。

2. SSM 后端骨架怎么搭:从依赖到第一个房源接口

2.1 为什么这个场景下 SSM 仍然够用

先讲选型理由,不然你搭到一半会怀疑自己。房屋租赁系统的后端压力其实很小:房源列表是读多写少,预约记录是低频写入,真正的瓶颈往往在图片存储和列表分页上,而不是在框架本身。SSM 的 MyBatis 在处理「多条件动态筛选房源」这种需求时特别顺手,因为租房筛选条件经常变——今天按租金区间,明天加个「是否近地铁」,用 MyBatis 的动态 SQL 改起来比 JPA 的 Criteria 直观得多。SpringMVC 负责把小程序发来的 JSON 请求接住,返回统一格式,这套流程非常成熟。

常见做法是:Maven 多模块或者单模块都行,我一般单模块起步,包结构按 controller / service / mapper / entity / vo 分。数据库用 MySQL 5.7 或 8.0 都可以,字符集统一 utf8mb4,因为房源描述里可能有 emoji 或者生僻字。连接池用 Druid,监控页面在调试阶段能帮你看清慢 SQL。

2.2 最小可运行的依赖与配置

下面这份 pom 依赖是这个项目的最小集合,多一个都别加,加多了启动慢还容易冲突。

<!-- pom.xml 关键依赖,版本按你本地仓库已有的稳定版来 --> <dependencies> <!-- Spring 核心 + MVC,SSM 的 S --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.30</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.3.30</version> </dependency> <!-- MyBatis 与 Spring 整合包,别只引 mybatis 本体 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.1.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency> <!-- Druid 连接池,自带监控 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.20</version> </dependency> <!-- JSON 转换,小程序端全靠它 --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.15.3</version> </dependency> </dependencies>

逻辑说明:spring-webmvc 提供 DispatcherServlet 和注解驱动,mybatis-spring 是把 SqlSession 交给 Spring 管理的关键,少了它你就得自己写 SqlSessionFactory 的样板代码。参数上注意 mybatis-spring 2.x 对应 MyBatis 3.5+,如果你本地是 1.x 版本,和 Spring 5 搭配会报NoClassDefFoundError,这是血泪经验,别问我怎么知道的。

2.3 房源列表接口:动态筛选与分页

房源列表是整个系统被调用最频繁的接口,小程序首页、搜索页、筛选页都打它。核心是 MyBatis 动态 SQL 加物理分页。

<!-- HouseMapper.xml 里的列表查询,用 <where> 自动处理 and 前缀 --> <select id="selectHouseList" resultType="com.rent.entity.House"> SELECT id, title, district, rent, room_type, area, cover_img, status FROM house <where> <if test="district != null and district != ''"> AND district = #{district} </if> <if test="minRent != null"> AND rent &gt;= #{minRent} </if> <if test="maxRent != null"> AND rent &lt;= #{maxRent} </if> <if test="roomType != null and roomType != ''"> AND room_type = #{roomType} </if> <!-- 只查上架的,下架房源不暴露给小程序 --> AND status = 1 </where> ORDER BY create_time DESC LIMIT #{offset}, #{pageSize} </select>

逻辑说明:<where>标签会自动去掉第一个多余的 AND,这是 MyBatis 最实用的特性之一。参数里 offset 由(pageNum - 1) * pageSize算出,pageSize 我一般限制在 10 到 20 之间,小程序一屏放不下太多,加载更多体验反而更好。注意rent字段如果是 decimal 类型,前端传参要做一次校验,别让用户传个负数进来。status 硬编码在 SQL 里是故意的,避免有人忘了加过滤条件把下架房源查出来,这种翻车在联调时特别尴尬。

3. 微信小程序端怎么接:列表加载、登录与图片处理

3.1 页面列表加载更多的正确姿势

微信小程序的列表加载更多,热搜里问得很多,但很多人写出来要么重复请求,要么卡在最后一页死循环。核心是三个状态:pageNum、hasMore、loading。

// pages/house/list.js Page({ data: { houseList: [], pageNum: 1, pageSize: 10, hasMore: true, loading: false }, onLoad() { this.loadHouseList(); }, // 触底事件,由页面 onReachBottom 触发 loadHouseList() { // 正在加载或没有更多了,直接返回,防止重复请求 if (this.data.loading || !this.data.hasMore) return; this.setData({ loading: true }); wx.request({ url: 'https://your-domain/api/house/list', data: { pageNum: this.data.pageNum, pageSize: this.data.pageSize }, success: (res) => { const list = res.data.data.list || []; this.setData({ houseList: this.data.houseList.concat(list), pageNum: this.data.pageNum + 1, // 返回条数小于 pageSize,说明到底了 hasMore: list.length === this.data.pageSize, loading: false }); }, fail: () => { // 失败也要复位 loading,否则永远卡住 this.setData({ loading: false }); wx.showToast({ title: '加载失败', icon: 'none' }); } }); }, onReachBottom() { this.loadHouseList(); } });

逻辑说明:hasMore的判断依据是「本次返回条数是否等于 pageSize」,这是最稳的写法,比让后端返回 total 再算更省一次查询。参数上 pageSize 别设太大,小程序 setData 有性能开销,一次 concat 太多数据会掉帧。注意 fail 回调里必须复位 loading,我见过有人漏了这行,结果网络抖一下列表就再也加载不出来了,用户只能杀掉小程序重进。

3.2 微信登录与后端会话打通

小程序端的登录不能直接用账号密码,标准流程是wx.login()拿 code,后端换 openid,再签发自己的 token。

// 小程序端登录 wx.login({ success: (res) => { if (res.code) { wx.request({ url: 'https://your-domain/api/user/login', method: 'POST', data: { code: res.code }, success: (r) => { // 后端返回自定义 token,存本地 wx.setStorageSync('token', r.data.data.token); } }); } } });

后端拿到 code 后,用 appid + secret + code 去微信接口换 openid,然后查用户表,没有就注册一条,最后生成一个 UUID 或 JWT 作为 token 返回。参数上注意 code 只能用一次,五分钟过期,所以别在前端缓存 code。token 存 storage 后,后续每个请求在 header 里带上,后端用拦截器校验。这里有个坑:小程序请求的 header 名建议用Authorization,别用中文或特殊字符,某些安卓机型会丢 header。

3.3 房源图片的存储与展示

房源图片是这个项目里最容易拖慢速度的部分。常见做法是图片存 OSS 或本地静态目录,数据库只存 URL。小程序端展示时用image组件的mode="aspectFill",列表页一定要用缩略图,别直接加载原图。

<!-- 列表页图片,加 lazy-load 懒加载 --> <image src="{{item.coverImg}}" mode="aspectFill" lazy-load />

参数说明:lazy-load在列表滚动时只加载可视区域图片,能明显降低流量和内存。如果后端没做缩略图,至少在上传时压缩到 200KB 以内,宽度 750px 足够。我一般会在上传接口里用 Thumbnails 库压一道,别指望前端压,小程序端压缩质量参差不齐。

4. 房源筛选与预约流程:把业务闭环补完整

4.1 多条件筛选的参数设计

筛选是租房系统的灵魂。小程序端一般用顶部下拉或抽屉式筛选面板,参数传到后端就是 district、minRent、maxRent、roomType 这几个。设计上有两个选择:一是每个条件变化就重新请求,二是点「确定」再请求。我推荐后者,因为租房用户经常一次调好几个条件,实时请求会打出一堆废请求。

// 筛选面板确认后触发 onFilterConfirm(e) { const { district, minRent, maxRent, roomType } = e.detail; // 重置分页,否则会接着上次的页码查 this.setData({ pageNum: 1, hasMore: true, houseList: [], filter: { district, minRent, maxRent, roomType } }); this.loadHouseList(); }

逻辑说明:筛选条件变化时必须重置 pageNum 和 houseList,否则会出现「筛选后列表里混着上次的数据」这种玄学问题。参数上 minRent 和 maxRent 要做前后端双重校验,前端限制输入框只能输数字,后端再判一次 min <= max,不然 SQL 查出来是空还找不到原因。

4.2 预约看房的表结构与状态机

预约流程看着简单,但状态一多就容易乱。我一般用一张 appointment 表,字段包括 id、house_id、user_id、appoint_time、remark、status、create_time。status 用数字:0 待确认、1 已确认、2 已完成、3 已取消。

CREATE TABLE `appointment` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `house_id` BIGINT NOT NULL COMMENT '房源ID', `user_id` BIGINT NOT NULL COMMENT '预约用户', `appoint_time` DATETIME NOT NULL COMMENT '预约看房时间', `remark` VARCHAR(255) DEFAULT NULL COMMENT '用户备注', `status` TINYINT DEFAULT 0 COMMENT '0待确认 1已确认 2已完成 3已取消', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_house` (`house_id`), KEY `idx_user` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

参数说明:appoint_time 用 DATETIME 而不是时间戳,方便后台直接看。索引加在 house_id 和 user_id 上,因为「查某房源的所有预约」和「查某用户的所有预约」是两个高频查询。状态流转要在 service 层控制,比如已取消的不能改成已完成,这种校验别只靠前端按钮置灰,后端必须再拦一道。

4.3 后台管理端的房源上下架

后台管理端通常是另一个 Web 页面,用同一套 SSM 后端。房源上下架就是改 status 字段,但要注意:下架时如果有未完成的预约,要么提示管理员,要么自动把预约置为取消并通知用户。我一般选前者,因为自动取消容易引发纠纷。

// HouseService 里的上下架方法 public void updateStatus(Long houseId, Integer status) { House house = houseMapper.selectById(houseId); if (house == null) { throw new RuntimeException("房源不存在"); } // 下架前检查是否有进行中的预约 if (status == 0) { int count = appointmentMapper.countActiveByHouse(houseId); if (count > 0) { throw new RuntimeException("该房源还有 " + count + " 条进行中的预约,请先处理"); } } houseMapper.updateStatus(houseId, status); }

逻辑说明:这段代码的价值在于把业务规则收在 service 层,controller 只负责接参和返回。参数上 status 用 0/1 表示下架/上架,别用 true/false,数据库里 tinyint 更省空间。异常直接抛 RuntimeException 在小型项目里够用,但如果你要统一异常处理,配一个@ControllerAdvice会更规范。

5. 避坑与排查:那些联调时让人抓狂的问题

5.1 小程序请求后端报「不在以下 request 合法域名列表中」

现象:开发者工具里勾了「不校验合法域名」能跑,真机预览就报这个错。原因:微信小程序正式环境要求所有请求域名在后台配置且必须是 HTTPS。解决:开发阶段在开发者工具「详情-本地设置」勾选不校验,真机调试时用「预览」并打开调试模式;上线前必须把域名备案、配 HTTPS 证书、在小程序后台「开发-开发设置-服务器域名」里加上。注意域名不能带端口,必须是 443。

5.2 MyBatis 查询返回字段为 null,但数据库里明明有值

现象:SQL 在客户端跑有结果,Java 里查出来对象字段全是 null。原因:九成是实体类字段名和数据库列名没对上,比如数据库是cover_img,实体类写的是coverImg,但没开驼峰映射。解决:在 MyBatis 配置里加mapUnderscoreToCamelCase=true,或者在 SQL 里用别名。我一般直接开驼峰映射,一劳永逸。

5.3 小程序 setData 后列表不刷新

现象:数据明明 concat 进去了,页面还是旧的。原因:直接对 data 里的数组 push,没有走 setData,或者 setData 的 key 写错了层级。解决:永远用this.setData({ houseList: newList })整体赋值,别用this.data.houseList.push()。另外注意 setData 是异步的,如果你在 setData 回调外立刻读 data,拿到的还是旧值。

5.4 预约时间存进去差 8 小时

现象:用户选的是下午 3 点,数据库里存的是早上 7 点。原因:小程序端new Date()拿到的是本地时间,传到后端 JSON 序列化时如果没配时区,或者 JDBC 连接串没加serverTimezone,就会偏。解决:JDBC URL 里加serverTimezone=Asia/Shanghai,后端统一用java.util.Date或LocalDateTime,别混用。前端传时间建议直接传格式化字符串yyyy-MM-dd HH:mm:ss,别传时间戳,省得来回算。

5.5 图片上传后小程序显示裂图

现象:后台上传成功,数据库也有 URL,小程序里就是裂的。原因:URL 是相对路径,或者后端静态资源没映射,或者 HTTPS 页面加载了 HTTP 图片被拦截。解决:数据库存完整 URL,后端配WebMvcConfigurer映射静态目录,上线后图片也必须走 HTTPS。另外检查小程序image组件的 src 有没有多余空格,这种低级错误联调时特别常见。

6. 进阶技巧:把房源搜索从「能用」做到「好用」

到这一步系统基本能跑了,但如果你想让它在答辩或交付时更拿得出手,可以加一个关键词搜索的优化。最土的做法是LIKE '%关键词%',数据量一上来就全表扫描。我的习惯是给 title 和 district 建全文索引,或者至少建普通索引配合前缀匹配。

-- 给房源标题加全文索引,MySQL 5.7+ 支持中文需要 ngram 解析器 ALTER TABLE house ADD FULLTEXT INDEX ft_title (title) WITH PARSER ngram; -- 查询时用 MATCH AGAINST,比 LIKE 快一个量级 SELECT id, title, rent FROM house WHERE MATCH(title) AGAINST(#{keyword} IN BOOLEAN MODE) AND status = 1 LIMIT 20;

参数说明:ngram 解析器默认 token size 是 2,也就是按两个字切词,适合中文。BOOLEAN MODE 支持+必须包含、-排除,比如用户搜「朝阳 -合租」就能排除合租。注意全文索引对短词和停用词有过滤,如果搜不到,先看innodb_ft_min_token_size配置。

另一个技巧是给列表接口加一层本地缓存。房源列表变化不频繁,用 Guava Cache 或 Caffeine 缓存 30 秒,能挡掉大量重复请求。但要注意上下架和新增房源时主动失效缓存,否则用户会看到已经下架的房源,这种后悔药可没地方买。

// 用 Caffeine 做 30 秒本地缓存 private final Cache<String, List<House>> listCache = Caffeine.newBuilder() .expireAfterWrite(30, TimeUnit.SECONDS) .maximumSize(200) .build(); public List<House> getList(String key, Supplier<List<House>> loader) { return listCache.get(key, k -> loader.get()); } // 房源变更时手动失效 public void evictListCache() { listCache.invalidateAll(); }

逻辑说明:expireAfterWrite保证数据最多旧 30 秒,maximumSize防止内存无限涨。参数上 30 秒是个经验值,太短没效果,太长用户感知明显。失效方法要在增删改的 service 里调用,别漏了,漏了就是玄学 bug——后台改了价格,小程序半天不更新。

最后说个验证方法:拿 Postman 或 Apifox 把列表、详情、预约三个接口各跑 50 次,看平均响应时间。如果列表接口超过 200ms,先查慢 SQL 日志,八成是没走索引。我自己的习惯是每加一个查询条件,就 EXPLAIN 一次,看 type 是不是 ALL,是 ALL 就回去加索引。这套系统不大,但把索引和缓存这两件事做扎实,体验能甩开一大半同类项目。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询