ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于SSM与微信小程序的房屋租赁系统:核心业务与避坑指南

基于SSM与微信小程序的房屋租赁系统:核心业务与避坑指南 简介这份资源是基于SSM框架的微信小程序房屋租赁系统完整项目源码面向计算机相关专业的毕业设计、课程设计学生以及需要Java全栈实战练习的开发者。项目将Spring、SpringMVC与MyBatis整合应用于服务端配合微信小程序客户端实现房源发布与搜索、在线预约看房、即时通讯、在线支付及订单管理等核心业务并包含实名认证与敏感数据加密等安全设计可作为一套结构清晰、职责分明的完整赛题方案参考。压缩包共1343个文件约4.05MB涵盖59个Java源文件、53个JSP页面、232个JavaScript脚本、218个CSS样式、253个HTML页面以及小程序端的29个WXML与30个WXSS文件另含1个SQL数据库脚本和若干图片资源前后端与数据库脚本齐备。目前已有48人学习下载适合用来理解SSM分层架构、小程序页面组织与数据库表结构设计快速搭建可运行环境并对照源码梳理业务逻辑。1. 从一份 SSM 微信小程序的房屋租赁系统说起它到底解决什么问题如果你手头正躺着一个「基于SSM的微信小程序房屋租赁系统设计」的题目或者你正打算用 SSM 框架给一个微信小程序做后端那这篇就是写给你的。房屋租赁这个场景本质上是把线下中介的那套流程搬到线上房东发房源、租客看房、双方约看、签合同、交押金。SSMSpring SpringMVC MyBatis负责把这些业务逻辑和数据落库微信小程序负责给用户一个不用装 App 就能看房、预约、支付的入口。它适合谁适合做课程设计、毕业设计、小型中介自用系统或者你想练手一个「前后端分离 小程序端」的完整闭环。但我要先说一个反直觉的结论这个系统里最难的不是 SSM 的增删改查而是房源状态流转和微信登录态这两件事很多人翻车都翻在这里。2. 技术选型与整体架构为什么是 SSM 而不是 Spring Boot2.1 SSM 在这个项目里的角色划分SSM 是三个框架的合称Spring 管 Bean 和事务SpringMVC 管 HTTP 请求路由MyBatis 管 SQL 映射。放到房屋租赁系统里典型的分层是这样的Controller 层接收小程序发来的 JSON 请求比如/api/house/list、/api/order/createService 层处理业务规则比如「同一房源在租期内不能被重复下单」DAO 层用 MyBatis 的 Mapper 接口操作 MySQL。小程序端不直接连数据库所有数据都走 HTTP 接口这一点和传统 JSP 项目最大的区别就是后端只返回 JSON不返回页面。为什么很多课程设计指定 SSM 而不是 Spring Boot因为 SSM 的 XML 配置能让你看清 Spring 的依赖注入和 AOP 到底怎么配的答辩时老师也认这套。但你要清楚代价web.xml、applicationContext.xml、spring-mvc.xml、mybatis-config.xml 这一堆配置文件少一个context:component-scan就会导致 Controller 注入失败启动直接 404。我一般会先把四个 XML 的职责列清楚再动手不然后面排错全靠猜。2.2 小程序端与后端的接口约定小程序端用wx.request发请求后端统一返回一个固定结构这是最省事的做法{ code: 200, msg: success, data: {} }code用 200 表示业务成功401 表示未登录500 表示服务端异常。为什么不用 HTTP 状态码直接区分因为小程序端wx.request的success回调对 4xx、5xx 也会进很多新手在这里踩坑统一在 body 里放 code 反而更好判断。接口路径建议按模块划分/api/user/*、/api/house/*、/api/order/*、/api/contract/*。每个接口在 Controller 里对应一个方法方法上打ResponseBody返回 JSON。2.3 数据库表的最小集合房屋租赁系统不需要一上来就设计几十张表先把核心五张表跑通用户表t_user、房源表t_house、预约看房表t_appointment、订单表t_order、合同表t_contract。房源表里最关键的是status字段用 0 表示待租、1 表示已租、2 表示下架。订单表里要有house_id、tenant_id、start_date、end_date、amount。这里有个血泪经验金额字段一定用DECIMAL(10,2)别用FLOAT否则押金算出来会出现1999.9999999这种鬼东西对账时能把你逼疯。3. 后端落地用 SSM 把房源和订单接口跑通3.1 MyBatis 映射文件与房源查询先看房源列表查询的 Mapper XML这是整个后端最常被调用的 SQL!-- HouseMapper.xml -- select idselectHouseList resultMapHouseResultMap SELECT h.id, h.title, h.price, h.area, h.status, h.cover_img, u.nickname AS landlordName FROM t_house h LEFT JOIN t_user u ON h.landlord_id u.id where if teststatus ! null AND h.status #{status} /if if testkeyword ! null and keyword ! AND h.title LIKE CONCAT(%, #{keyword}, %) /if /where ORDER BY h.create_time DESC LIMIT #{offset}, #{pageSize} /select这段 SQL 做了三件事关联房东昵称、按状态和关键词动态过滤、分页。where标签会自动处理第一个AND这是 MyBatis 的细节手写 SQL 拼接时容易多一个AND导致语法错误。LIMIT #{offset}, #{pageSize}里的 offset 要在 Service 层算好公式是(pageNum - 1) * pageSize。参数说明status传 null 表示查全部传 0 查待租房源keyword用于小程序搜索框pageSize建议默认 10小程序一屏放不下太多。3.2 微信登录与用户态维护小程序登录不能直接用账号密码标准流程是wx.login拿 code后端拿 code 去换 openid。这里给出后端处理的核心代码// UserController.java PostMapping(/api/user/login) ResponseBody public Result login(RequestBody LoginDTO dto) { // dto.code 来自小程序 wx.login 的返回值 String url https://api.weixin.qq.com/sns/jscode2session ?appid appId secret appSecret js_code dto.getCode() grant_typeauthorization_code; String resp restTemplate.getForObject(url, String.class); JSONObject json JSON.parseObject(resp); String openid json.getString(openid); if (openid null) { return Result.fail(401, 登录失败); } // 查库没有就注册 User user userService.findByOpenid(openid); if (user null) { user userService.registerByOpenid(openid); } // 生成 token 返回给小程序后续请求带在 header 里 String token jwtUtil.generateToken(user.getId()); return Result.success(token); }逻辑说明jscode2session是微信官方接口用 code 换 openid 和 session_key。openid 是用户在这个小程序里的唯一标识拿它当账号主键最稳。参数说明appId和appSecret放在配置文件里不要硬编码在 Java 里答辩时老师会问这个。token 用 JWT 生成里面塞 userId有效期设 7 天。小程序端把 token 存到wx.setStorageSync(token, token)之后每次请求在 header 里带Authorization。注意session_key 不要返回给前端它只用来解密手机号等敏感数据。3.3 订单创建与房源状态流转订单创建是业务核心必须保证「同一房源同一时间段只能有一个有效订单」。做法是在 Service 层加事务和状态校验// OrderServiceImpl.java Transactional(rollbackFor Exception.class) public Result createOrder(Long houseId, Long tenantId, Date start, Date end) { House house houseMapper.selectById(houseId); if (house null || house.getStatus() ! 0) { return Result.fail(500, 房源不可租); } // 查是否有时间重叠的有效订单 int conflict orderMapper.countConflict(houseId, start, end); if (conflict 0) { return Result.fail(500, 该时间段已被预订); } Order order new Order(); order.setHouseId(houseId); order.setTenantId(tenantId); order.setStartDate(start); order.setEndDate(end); order.setStatus(0); // 0 待支付 orderMapper.insert(order); // 更新房源状态为已租 houseMapper.updateStatus(houseId, 1); return Result.success(order.getId()); }Transactional保证插入订单和更新房源状态要么都成功要么都回滚。countConflict的 SQL 用start_date #{end} AND end_date #{start}判断重叠这是区间重叠的标准写法别写成BETWEEN会漏掉边界情况。参数说明start和end是租期起止日期前端传时间戳或yyyy-MM-dd字符串都行后端统一转Date。房源状态从 0 改成 1 后列表页就查不到它了这是最简单的防重复下单手段。4. 小程序端落地请求封装与页面交互4.1 请求封装与 token 自动携带小程序端不要在每个页面里裸写wx.request封装一层是标准做法// utils/request.js const BASE_URL https://your-domain.com; function request(options) { const token wx.getStorageSync(token); return new Promise((resolve, reject) { wx.request({ url: BASE_URL options.url, method: options.method || GET, data: options.data || {}, header: { Content-Type: application/json, Authorization: token || }, success(res) { if (res.data.code 200) { resolve(res.data.data); } else if (res.data.code 401) { // token 失效重新登录 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); } }); }); } module.exports { request };逻辑说明把 token 从缓存里取出来塞进 header后端拦截器统一校验。401 时清 token 并跳登录页这是最常见的处理。参数说明BASE_URL换成你自己的域名小程序正式环境必须用 HTTPS 且域名要在后台配置白名单否则真机请求直接失败开发者工具里却正常这个坑很多人踩过。4.2 房源列表页与下拉刷新列表页用onLoad拉第一页onReachBottom加载下一页// pages/house/list.js const { request } require(../../utils/request); Page({ data: { list: [], pageNum: 1, hasMore: true }, onLoad() { this.loadData(); }, loadData() { if (!this.data.hasMore) return; request({ url: /api/house/list, data: { pageNum: this.data.pageNum, pageSize: 10, status: 0 } }).then(data { this.setData({ list: this.data.list.concat(data.list), pageNum: this.data.pageNum 1, hasMore: data.list.length 10 }); }); }, onReachBottom() { this.loadData(); }, onPullDownRefresh() { this.setData({ list: [], pageNum: 1, hasMore: true }); this.loadData(); wx.stopPullDownRefresh(); } });hasMore用返回条数是否等于 pageSize 判断简单有效。下拉刷新时重置页码和列表再重新拉。参数说明status: 0只查待租房源已租的不展示。注意onReachBottom要配合页面配置onReachBottomDistance: 50不然触发太晚用户以为到底了。4.3 预约看房与表单校验预约表单提交前必须做前端校验否则后端要处理一堆脏数据submitAppointment() { const { name, phone, date } this.data.form; if (!name || name.length 2) { return wx.showToast({ title: 请填写姓名, icon: none }); } if (!/^1[3-9]\d{9}$/.test(phone)) { return wx.showToast({ title: 手机号格式不对, icon: none }); } if (!date) { return wx.showToast({ title: 请选择看房日期, icon: none }); } request({ url: /api/appointment/create, method: POST, data: { houseId: this.data.houseId, name, phone, date } }).then(() { wx.showToast({ title: 预约成功 }); setTimeout(() wx.navigateBack(), 1500); }); }手机号正则^1[3-9]\d{9}$覆盖了当前号段。日期用小程序picker组件的modedate拿。参数说明houseId从页面参数传进来date格式是yyyy-MM-dd。提交成功后延迟返回上一页让用户看到 toast。5. 避坑与排查那些让你加班到凌晨的细节5.1 小程序真机请求失败但开发者工具正常现象开发者工具里接口全通真机预览时所有请求 fail。原因小程序正式环境要求 HTTPS且域名必须在微信公众平台「开发设置」里配置 request 合法域名开发者工具默认勾选了「不校验合法域名」所以本地看着正常。解决把后端部署到有 HTTPS 证书的服务器域名加白名单调试阶段可以在开发者工具详情里勾选不校验但上线前必须配好。5.2 SSM 启动报 404Controller 不生效现象Tomcat 启动没报错但访问任何接口都是 404。原因spring-mvc.xml里的context:component-scan base-packagecom.xxx.controller/包路径写错或者web.xml里 DispatcherServlet 的contextConfigLocation指向了错误的文件。解决先确认web.xml里 servlet 映射是/再检查 component-scan 的包名和实际 Controller 所在包一致最后看 Tomcat 启动日志里有没有「Mapped URL path」的输出。5.3 订单重复创建现象用户快速点两次提交生成两条订单。原因前端没做防抖后端没做幂等。解决前端提交按钮点击后立即disabled后端在createOrder里先查该用户对该房源是否有未支付订单有就直接返回已有订单号。更稳的做法是用 Redis 加分布式锁但课程设计级别用数据库唯一索引UNIQUE(house_id, tenant_id, status)配合状态判断就够了。5.4 图片上传后小程序不显示现象后台上传房源图片成功小程序列表里图片裂开。原因图片存的是服务器本地路径/upload/xxx.jpg小程序端直接拿这个相对路径请求域名不对。解决后端返回图片时拼完整 URL比如https://your-domain.com/upload/xxx.jpg或者在小程序端BASE_URL data.coverImg。另外检查服务器目录权限tomcat用户要有读权限。5.5 日期格式前后端不一致现象小程序传2024-06-01后端Date接收变成2024-05-31T16:00:00.000Z差一天。原因时区问题Jackson 默认按 UTC 解析。解决在实体类的日期字段上加JsonFormat(pattern yyyy-MM-dd, timezone GMT8)或者在spring-mvc.xml里配置全局的ObjectMapper时区。6. 进阶技巧用状态机管住房源生命周期房源状态如果只用 0/1/2 三个数字硬编码后面加「已预订」「维修中」就会乱。我一般会引入一个简单的状态机把状态和允许的流转写在一张表里代码里只做校验。下面是一个轻量实现public enum HouseStatus { PENDING(0, 待租), RENTED(1, 已租), OFFLINE(2, 下架), BOOKED(3, 已预订); private final int code; private final String desc; HouseStatus(int code, String desc) { this.code code; this.desc desc; } public int getCode() { return code; } } // 状态流转校验 public boolean canTransfer(int from, int to) { MapInteger, ListInteger rules new HashMap(); rules.put(0, Arrays.asList(1, 3, 2)); // 待租 - 已租/已预订/下架 rules.put(3, Arrays.asList(1, 0)); // 已预订 - 已租/回退待租 rules.put(1, Arrays.asList(0)); // 已租 - 回退待租 ListInteger allowed rules.get(from); return allowed ! null allowed.contains(to); }这样每次改状态前调canTransfer不合法就抛异常。好处是加新状态只改rules这一处不用满项目搜status 1。参数说明from是当前状态码to是目标状态码。配合数据库的status字段和订单表整个租赁流程就闭环了。验证方法写一个 JUnit 测试把canTransfer的所有组合跑一遍断言非法流转返回 false。我习惯在每次改状态逻辑后跑一遍这个测试比手动点页面靠谱得多。最后说个我自己的教训别在 Controller 里直接改状态所有状态变更必须走 Service否则事务和校验都会漏。这个习惯帮我省了至少两次通宵排查。希望帮到你。本文还有配套的精品资源点击获取
返回列表