简介:这份资源是基于SSM框架的微信小程序房屋租赁系统完整项目包,面向计算机相关专业的毕业设计、课程设计学生以及需要Java全栈实战练手的开发者。项目将Spring、SpringMVC、MyBatis与微信小程序结合,覆盖房源发布、租房需求、在线预约看房、支付与订单管理等核心业务,帮助读者理解前后端分离的工程结构与业务落地思路。压缩包共1343个文件,约4.05MB,包含59个Java源文件、53个JSP页面、30个WXSS与29个WXML小程序页面,以及大量html、js、css、png等前端资源,另有1个SQL数据库脚本和若干配置文件,完整呈现客户端、服务端与数据库三层结构。已有48人学习下载。读者可据此获得一套可直接参考的赛题级方案,涵盖控制器分层、持久层CRUD、数据库表设计与小程序页面组织,便于快速搭建环境、对照调试并完成二次开发。
1. 基于SSM的微信小程序房屋租赁系统:从选型到跑通的第一道坎
打开招聘软件搜“Java 实习”,十个岗位有八个写着“熟悉 SSM 框架”。但真到动手做一套能演示、能讲清楚、能写进简历的项目时,很多人卡在第一步:后端用 SSM 搭好了,前端到底用 Vue 还是微信小程序?如果你瞄准的是本地生活服务类场景,比如房屋租赁,微信小程序几乎是绕不开的选择——用户扫码即用,房东发房源不用装 App,中介带看时直接甩个小程序卡片。这套“基于 SSM 的微信小程序房屋租赁系统”要解决的核心问题就三个:房源信息的增删改查、租客与房东之间的角色隔离、以及租赁状态从“待租”到“已租”的流转。它适合正在做课程设计、毕业设计,或者想用一个完整项目把 SSM 和微信小程序串起来的一线开发者。别被“系统”两个字吓到,拆开看就是几张表、几个接口、几个页面的事,但坑恰恰藏在那些看起来“应该没问题”的地方。
2. SSM 后端骨架:从建表到接口能返回 JSON
2.1 房屋租赁场景下的四张核心表与字段设计
房屋租赁系统的数据模型比电商简单,但比博客复杂。我一般会先落四张表:user(用户)、house(房源)、order(租赁订单)、contract(合同,可选)。user表里必须有一个role字段区分租客和房东,用tinyint存,0 是租客,1 是房东,2 是管理员。house表的关键字段包括title、address、rent、area、room_type、status(0 待租、1 已租、2 下架)、landlord_id外键指向user.id。order表记录tenant_id、house_id、start_date、end_date、total_amount、order_status。这里有个血泪经验:rent字段用decimal(10,2),别用float,否则算总价时会出现0.1 + 0.2 = 0.30000000000000004的玄学问题。建表语句里给address加普通索引,因为租客最常用的筛选就是按区域搜房。
CREATE TABLE `house` ( `id` int(11) NOT NULL AUTO_INCREMENT, `title` varchar(100) NOT NULL COMMENT '房源标题', `address` varchar(200) NOT NULL COMMENT '详细地址', `rent` decimal(10,2) NOT NULL COMMENT '月租金', `area` decimal(8,2) DEFAULT NULL COMMENT '面积', `room_type` varchar(20) DEFAULT NULL COMMENT '户型,如两室一厅', `status` tinyint(4) DEFAULT '0' COMMENT '0待租 1已租 2下架', `landlord_id` int(11) NOT NULL COMMENT '房东用户ID', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_address` (`address`), KEY `idx_landlord` (`landlord_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;字段设计阶段最容易翻车的是status的枚举值定义。我见过有人把“待租”设成 1,“已租”设成 0,结果前端判断逻辑全反了。统一用 0 表示“正常/待租”这个默认态,后面加状态只往后追加,别插队。
2.2 Spring + SpringMVC + MyBatis 的最小可用配置
SSM 的配置在 2025 年看确实有点“古典”,但面试和课设就认这个。最小可用配置分三块:applicationContext.xml管 Spring 容器和 MyBatis 的SqlSessionFactory,spring-mvc.xml管 Controller 扫描和 JSON 消息转换,web.xml注册DispatcherServlet和ContextLoaderListener。MyBatis 的mapper接口用@MapperScan或者 XML 里配MapperScannerConfigurer都行,我习惯用注解扫描,少写一个 XML。数据源用 Druid,连接池参数里initialSize设 5,maxActive设 20,课设环境够用了。spring-mvc.xml里必须加<mvc:annotation-driven/>,否则@ResponseBody返回的对象不会自动转成 JSON,前端拿到的是 406 错误。
<!-- spring-mvc.xml 关键片段 --> <context:component-scan base-package="com.rent.controller"/> <mvc:annotation-driven> <mvc:message-converters> <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"> <property name="supportedMediaTypes"> <list> <value>application/json;charset=UTF-8</value> </list> </property> </bean> </mvc:message-converters> </mvc:annotation-driven>MappingJackson2HttpMessageConverter里指定charset=UTF-8是为了防止中文房源标题返回时变成问号。这个坑在 Tomcat 8 以后默认 UTF-8 的环境下不明显,但一旦部署到某些老版本容器或者用了过滤器没配对,就会复现。参数说明:supportedMediaTypes只写application/json就够了,别加text/html,否则浏览器直接访问接口会触发下载行为。
2.3 房源列表接口:分页、条件筛选与 JSON 返回
房源列表是使用频率最高的接口。Controller 层接收page、size、keyword、minRent、maxRent五个参数,Service 层用 PageHelper 做分页。PageHelper 的startPage必须紧挨着 Mapper 查询方法,中间不能插别的数据库操作,否则分页会作用到错误的 SQL 上。返回给前端的 JSON 结构我一般包一层:{code: 200, msg: "success", data: {list: [], total: 100}}。code用 200 表示成功,401 表示未登录,403 表示角色不对,500 表示服务端异常。别用 HTTP 状态码直接当业务码,微信小程序端wx.request对非 200 的 HTTP 状态码处理起来很别扭。
@RequestMapping("/list") @ResponseBody public Result list(@RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer size, String keyword, BigDecimal minRent, BigDecimal maxRent) { PageHelper.startPage(page, size); List<House> list = houseService.selectByCondition(keyword, minRent, maxRent); PageInfo<House> pageInfo = new PageInfo<>(list); return Result.success(pageInfo); }PageInfo里的total是总记录数,list是当前页数据。前端做“加载更多”时,判断page * size >= total就停止加载。这里有个细节:keyword模糊查询要用CONCAT('%', #{keyword}, '%'),别用'%${keyword}%',后者有 SQL 注入风险,而且当keyword为空时会查出全表。minRent和maxRent传 null 时,MyBatis 的动态 SQL<if test="minRent != null">会自动跳过条件,不用在 Java 里拼字符串。
3. 微信小程序端:页面结构、请求封装与加载更多
3.1 房源列表页的 wxml 结构与 wx:for 渲染
微信小程序的列表页用scroll-view或者普通view加onReachBottom都行。我倾向用页面级的onReachBottom,因为scroll-view在 iOS 上有回弹和滚动穿透的玄学问题。wxml里用wx:for渲染houseList,每个卡片展示title、address、rent、room_type和一张封面图。图片用mode="aspectFill"裁切,防止不同比例的房源图把卡片撑变形。wx:key必须写,用house.id,不写的话控制台会警告,而且列表更新时可能渲染错位。
<view class="house-card" wx:for="{{houseList}}" wx:key="id"> <image src="{{item.coverUrl}}" mode="aspectFill" class="cover"/> <view class="info"> <text class="title">{{item.title}}</text> <text class="address">{{item.address}}</text> <text class="rent">¥{{item.rent}}/月</text> </view> </view> <view wx:if="{{loading}}" class="loading">加载中...</view> <view wx:if="{{noMore}}" class="no-more">没有更多了</view>loading和noMore两个状态变量控制底部提示。onReachBottom触发时先判断noMore是否为 true,是就直接 return,否则把page加 1 再请求。这里有个容易忽略的点:快速上拉会连续触发onReachBottom,导致同一页数据请求两次。加一个loading标志位,请求开始前设为 true,complete回调里设回 false,就能挡住重复请求。
3.2 封装 wx.request:统一处理 token 与错误码
小程序的wx.request回调风格写多了会嵌套成地狱。我一般封装一个request.js,返回 Promise,统一拼接baseUrl,统一在 header 里带token,统一处理code非 200 的情况。token存在wx.getStorageSync('token')里,登录接口返回后写入。如果后端返回 401,就清掉本地 token 并跳转到登录页。注意wx.request的success回调里,只要 HTTP 状态码是 200,无论业务code是多少都会进success,所以业务错误判断必须放在success内部做。
const request = (url, method = 'GET', data = {}) => { return new Promise((resolve, reject) => { wx.request({ url: 'https://your-domain.com/api' + url, method, data, header: { 'content-type': 'application/json', 'token': wx.getStorageSync('token') || '' }, success(res) { if (res.data.code === 200) { resolve(res.data.data); } else if (res.data.code === 401) { wx.removeStorageSync('token'); wx.navigateTo({ url: '/pages/login/login' }); reject(res.data); } else { wx.showToast({ title: res.data.msg, icon: 'none' }); reject(res.data); } }, fail(err) { wx.showToast({ title: '网络异常', icon: 'none' }); reject(err); } }); }); };header里的token字段名要和后端拦截器里取的一致,我见过有人前端写Authorization,后端读token,调了半天以为是跨域问题。baseUrl在开发阶段用本地 IP,真机调试时手机和电脑必须在同一局域网,且微信开发者工具里要勾选“不校验合法域名”。上线前换成备案过的 HTTPS 域名,否则wx.request直接报错。
3.3 加载更多的页码管理与“没有更多”判断
加载更多的逻辑核心是三个变量:page(当前页码)、pageSize(每页条数)、total(总条数)。首次加载page = 1,onReachBottom时page + 1。每次请求成功后,把新数据concat到houseList后面,同时更新total。判断是否还有更多:page * pageSize >= total时设noMore = true。这里有个边界情况:如果后端返回的total是 0,首次加载后就应该直接显示“暂无房源”,而不是“没有更多”。我一般用wx:if="{{houseList.length === 0 && !loading}}"单独渲染空状态。
onReachBottom() { if (this.data.noMore || this.data.loading) return; this.setData({ loading: true, page: this.data.page + 1 }); request('/house/list', 'GET', { page: this.data.page, size: this.data.pageSize }).then(res => { const list = this.data.houseList.concat(res.list); this.setData({ houseList: list, total: res.total, noMore: this.data.page * this.data.pageSize >= res.total, loading: false }); }).catch(() => { this.setData({ loading: false }); }); }setData里同时更新多个字段时,微信小程序会合并成一次渲染,比分开写性能好。concat不要用push循环,数据量大时concat更直观。如果房源列表有筛选条件,切换筛选时要重置page = 1、houseList = []、noMore = false,否则会把不同条件的数据混在一起。
4. 避坑与排查:那些让课设卡三天的真实问题
4.1 跨域问题:开发者工具不报错,真机请求失败
现象:微信开发者工具里请求正常返回数据,用手机预览时所有接口都失败,控制台提示“不在以下 request 合法域名列表中”。原因:开发者工具默认开启了“不校验合法域名”选项,真机环境强制校验。解决:开发阶段在开发者工具右上角“详情”里勾选“不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书”,真机预览时在手机微信里打开调试模式。上线前必须把后端域名配置到微信公众平台的“request 合法域名”里,且必须是 HTTPS。
4.2 图片上传后回显 404:文件路径与静态资源映射
现象:房东发布房源时上传图片成功,但列表页图片显示裂图,控制台 404。原因:SpringMVC 没有配置静态资源映射,上传的图片存在服务器磁盘目录,但通过 URL 访问不到。解决:在spring-mvc.xml里加<mvc:resources mapping="/upload/**" location="file:/data/upload/"/>,同时确保DispatcherServlet的url-pattern是/而不是/*。/*会拦截所有请求包括静态资源,导致mvc:resources失效。
4.3 分页插件失效:PageHelper 返回了全量数据
现象:接口返回的list里是全部房源,total也对,但page和size参数没起作用。原因:PageHelper.startPage()后面没有紧跟着 Mapper 查询,中间插了别的 Service 方法或者缓存读取。解决:把startPage挪到 Mapper 调用前一行,确保之间没有其他数据库操作。另一个可能是 MyBatis 配置里plugins没注册PageInterceptor,检查mybatis-config.xml或 Spring 配置里的plugins标签。
4.4 微信登录态过期:token 失效后页面白屏
现象:用户登录后放置一段时间,再操作时接口返回 401,但页面没有跳转登录,而是卡在当前页。原因:request.js里 401 处理用了wx.navigateTo,但当前页面已经是登录页或者页面栈已满,跳转失败。解决:用wx.reLaunch替代wx.navigateTo,清空页面栈直接重启到登录页。同时wx.removeStorageSync('token')要在跳转前执行,防止登录页读取到旧 token 又自动跳回。
4.5 真机调试时 setData 数据量过大导致卡顿
现象:房源列表加载到第 10 页以后,上拉明显掉帧,setData回调变慢。原因:houseList数组累积了几百条数据,每次setData都全量传输。解决:分页加载时只concat新数据,但setData仍然传输整个数组。优化方案是用this.setData({ ['houseList[' + index + ']']: item })按索引更新,或者限制最大加载页数,超过后提示“请使用筛选缩小范围”。课设场景下,限制到 5 页就够演示了。
5. 从能跑到能演示:房源状态流转与角色权限的收尾技巧
房源状态流转是这套系统里最能体现业务逻辑的部分,也是答辩时老师最爱问的地方。我一般把状态机画在 Service 层:0 待租只能由房东操作变成2 下架,或者由租客下单后变成1 已租;1 已租不能直接回到0 待租,必须先由管理员或系统在订单完成后置回。这个规则用 Java 枚举写清楚,别在 Controller 里散落一堆if-else。角色权限用 Spring 拦截器做,拦截/api/landlord/**路径,从 token 里解析出role,不是 1 就直接返回 403。微信小程序端在app.js的onLaunch里调一次/user/info拿到角色,存到globalData,页面里用wx:if控制“发布房源”按钮的显示。
验证方法很简单:用两个微信号,一个注册房东,一个注册租客。房东发布房源,租客搜索到并下单,房东在“我的订单”里确认,房源状态自动变成“已租”。整个过程走通,这套系统就立住了。最后分享一个我自己的习惯:每次改完后端接口,先用 Postman 或者浏览器直接访问 URL 确认返回 JSON 正确,再去小程序里调页面。这样能把“是接口问题还是前端问题”快速隔离开,省下大量在开发者工具里反复编译的时间。希望帮到你。
本文还有配套的精品资源,点击获取