Java Spring Boot + 微信小程序:一套房地产销售管理系统的完整复盘
做毕业设计或者自己接外包项目,只要是涉及“房源管理 + 客户预约 + 销售跟进”这类场景,基本绕不开一套房销系统。最近刚好把我手头这一套基于微信小程序 + Spring Boot 的房地产销售管理系统从需求梳理到落地部署完整跑了一遍,实现了后台管理员对房源、户型、销售数据的统一管理,也实现了购房客户在微信小程序端浏览房源、在线预约看房、填写购房意向的功能。整体技术栈就是 Java + Spring Boot + MyBatis Plus + 微信小程序原生前端,是我认为目前做毕设项目或者中小型房销系统性价比最高的一套组合。
这篇文章我就把这套系统的完整思路、核心功能、数据库设计、关键代码逻辑以及我在实际部署和运行过程中踩过的坑,一次性全部梳理出来。不管是正在做这个课题的学生,还是想快速搭一套房销系统雏形的开发者,应该都能从里面拿到可以直接用的东西。
1. 项目整体设计与思路拆解
1.1 需求定位:这套系统到底在解决什么问题
先说业务背景。传统的房地产销售管理大多依赖销售顾问各自用 Excel 记录客户、用纸质台账登记房源信息,客户想看房得打电话问、约时间、再跑售楼处。一旦楼盘体量上来,几十套甚至上百套房源在各个销售手里流转,很容易出现这些问题:同一套房被两个销售重复登记、预约看房时间撞车、客户信息散落丢失、销售业绩月底统计对不上账。
这套系统的核心目标就是解决上面这些基层痛点。它面向三类角色:来浏览房源、提交预约的普通客户(小程序端);负责管理房源、审核预约、处理订单的销售人员或运营人员(后台管理端);以及拥有最高权限、看数据报表的系统管理员(后台管理端)。业务链路我梳理下来是这样的:管理员先在后台录入楼盘信息与楼栋户型数据,小程序端按区域、户型、价格等维度展示在售房源,客户看到感兴趣的房源后可提交“预约看房申请”,后台收到申请后分配给对应的销售顾问跟进,销售在线更新跟进状态(待联系、已联系、已成交、已失效),最终成交的数据沉淀成可统计的销售报表。
这么一拆,系统边界就很清晰了:小程序端不用管复杂的业务逻辑,只负责信息展示与自助预约;真正的业务流转全在后台管理端完成。这种前后端分离、职责清晰的架构,是最适合这个体量项目的设计方式。
1.2 技术选型:为什么是 Spring Boot + 微信小程序
技术选型从来不是越新越好,而是越合适越好。这套系统在后端选择了 Spring Boot 2.x,持久层框架用 MyBatis Plus,数据库用 MySQL 5.7,前端小程序则使用微信原生小程序框架开发。这套组合在选择时有几点明确考量。
第一,Spring Boot 极大降低了后端搭建成本。它内置 Tomcat,简化了依赖管理和配置方式。一个规范的 Java 后端项目,如果用传统的 SSM 框架手动整合,光 spring-context、spring-mvc、mybatis 这些依赖的版本匹配就够折腾半天,而 Spring Boot 通过 spring-boot-starter-parent 统一管理版本,起步就是 Web 项目跑通 Hello World。对于需要快速交付毕设或外包项目的场景,这是最大的效率优势。
第二,MyBatis Plus 让数据库操作回归简单。它内置了通用的 insert、delete、update、select 方法,单表 CRUD 基本不用手写 SQL。业务代码里常见的分页查询,只需要调用 selectPage 并传入一个 Page 对象即可。不过需要说明的是,一旦涉及到多表关联查询,比如查询房源列表时同时带出所属楼栋名称和户型图,我仍然会手写 SQL,而不是把全部逻辑堆在 Java 代码里做内存关联。
第三,微信小程序作为 C 端载体,最大的优势是零安装成本和天然的微信生态流量。客户在微信里搜索小程序或扫码即可打开,无需下载 App,对不熟悉智能手机操作的中老年购房群体非常友好。小程序提供的登录能力(wx.login 获取 code,后端再通过 code 换取 openid)也免去了自己手机号验证码注册登录的开发量。只要在微信公众平台注册一个小程序账号,下载微信开发者工具,就可以直接开发与调试。
1.3 模块划分与整体架构
系统按功能域划分为两层:
后端管理端(Web 管理后台 + RESTful API)包含以下模块:
- 管理员登录与权限拦截模块
- 房产信息管理模块(楼盘、楼栋、房源)
- 预约看房管理模块
- 客户信息管理模块
- 销售数据统计模块
小程序用户端包含以下页面:
- 首页(楼盘推荐、最新房源)
- 房源列表页(条件筛选)
- 房源详情页(户型图、价格、预约入口)
- 在线预约页(选择看房时间、填写联系方式)
- 个人中心(我的预约、收藏)
为了把这两个端串联起来,后端对外提供一套统一的 RESTful API,所有接口都返回统一的 JSON 结构:{ code: 200, message: "success", data: {...} }。小程序端通过 wx.request 与后端进行数据交互。架构上大致是“小程序客户端 → Nginx/直接访问 → Spring Boot API 服务 → MySQL 数据库”,开发阶段可以直接用 IP + 端口访问,生产部署再加一层 Nginx 转发。
这样设计的价值在于:小程序端只关心页面渲染和用户交互,所有权限校验、数据校验、业务状态流转都在后端完成,避免把业务规则散落到前端导致同一个规则在多端出现不一致的情况。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据库设计:一张表一张表地讲清楚
2.1 核心业务表关系
数据库设计是这类管理系统的地基。我把它拆成了 8 张核心表:管理员表、用户表(小程序端客户)、楼盘表、房源表、户型表、预约看房表、收藏表、留言反馈表。其中楼盘与房源是一对多关系,房源与户型是多对一关系,用户与预约是一对多关系,用户与收藏是多对多关系(通过收藏表关联)。
我建表的思路遵循几个原则:
- 凡是业务上有归属关系的数据,用外键字段关联而不是直接存冗余文本。例如房源表存 building_id(所属楼盘 ID),这样后台可按楼盘维度统计。
- 状态字段用 int 或 tinyint 存储,而不是字符串。因为字符串状态容易写得不一致(比如“已联系”和“已联系 ”带空格),用数字常量在代码里做枚举映射更可控。
- 创建时间、更新时间统一用 datetime 类型,并且由后端代码统一填充,不依赖数据库触发器,便于以后迁移或做数据同步。
2.2 房源表与预约表的设计细节
房源表是整个系统信息量最大的表。我设计的字段包括:
- id、house_name(房源名称,如“A栋1单元302”)
- building_id(关联所属楼栋)
- house_type_id(关联户型)
- cover_image(封面图)
- area(面积,单位平方米)
- price(总价,单位元)
- unit_price(单价,单位元/平方米)
- decoration(装修情况:毛坯/简装/精装)
- orientation(朝向)
- floor(所在楼层)
- status(销售状态:在售/已预定/已售)
- detail(房源描述)
- create_time、update_time
在设计时,我把 price 和 unit_price 都设置成 decimal(12,2) 而不是 int。原因是房产交易中的金额没有“整数化”的硬需求,保留两位小数便于后续按折扣或分期方案计算。千万不能把金额存成 float,否则在做累计比较时可能出现精度问题。
预约表 house_appointment 的字段包括:id、user_id、house_id、appointment_time(计划看房时间)、contact_name、contact_phone、status(待确认/已联系/已完成/已取消)、remark(备注)、create_time。这张表是连接 C 端客户和 B 端销售的核心表。销售在后台看到一条新的预约记录后,即可电话联系客户,并在系统中把状态推进到下一阶段。通过这张表,我可以在后台很自然地实现“每位销售的跟进列表”,后续统计销售转化率时,只要按预约的最终成交状态汇总即可。
2.3 我踩过的设计坑:为什么最后加了户型表
第一次开发这类系统时,我把户型信息(几室几厅、建筑面积、参考总价)直接放在房源表里,结果录入数据时苦不堪言:同一个楼盘的同一个户型,在不同楼层、不同房号上重复录入十几条,而且一旦户型面积需要调整,就要逐条更新房源数据,容易漏改,还容易出现同一楼盘同户型价格不一致的脏数据。
后来我单独抽了一张户型表 house_type,记录 id、building_id、name(如“三室两厅两卫”)、area、layout_img(户型图)、description;房源表只保留 house_type_id 外键。这样带来的最直接好处是房源录入效率大幅提升——销售团队新建房源时只需要选择已有户型,再补充楼层、房号、朝向等个性化信息,数据一致性也更有保障。
如果让我再给一个建议:在建表时顺手把逻辑删除字段 deleted(0/1)也加上。实际运营中,房源下架往往是暂时性的(比如客户反悔、贷款未通过),物理删除会丢失历史关联数据,逻辑删除则可以把下架和恢复都变成一次 update 操作。
3. 核心功能模块与 API 实现解析
3.1 后端接口设计整体风格
后端 API 统一以 /api/ 前缀区分模块。用户端接口与后台管理端接口从路径上分开,例如小程序端调用的接口是 /api/wx/house/list、/api/wx/appointment/add,后台管理端接口则是 /api/admin/house/page、/api/admin/appointment/updateStatus。这样分层的设计让权限控制非常好做:在 Spring Boot 里增加一个拦截器或过滤器,只对 /api/admin/** 做登录态校验,小程序端接口则通过自定义 token 参数校验用户身份。
所有接口的响应结构我统一封装成 Result 类,代码如下:
java复制@Data
public class Result<T> {
private Integer code;
private String message;
private T data;
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(200);
result.setMessage("success");
result.setData(data);
return result;
}
public static <T> Result<T> error(String message) {
Result<T> result = new Result<>();
result.setCode(500);
result.setMessage(message);
return result;
}
}
3.2 后端核心接口清单
| 模块 | 请求方式 | 接口路径 | 说明 |
|---|---|---|---|
| 管理员 | POST | /api/admin/login | 管理员登录,校验账号密码并返回 token |
| 楼盘 | GET | /api/wx/building/list | 小程序端获取楼盘列表 |
| 房源 | GET | /api/wx/house/list | 小程序端分页条件筛选房源 |
| 房源 | GET | /api/wx/house/detail/ | 获取房源详情(含户型信息) |
| 预约 | POST | /api/wx/appointment/add | 用户提交看房预约 |
| 预约 | GET | /api/wx/appointment/myList | 用户查看自己的预约记录 |
| 预约 | GET | /api/admin/appointment/page | 后台分页查询所有预约(可按状态筛选) |
| 预约 | PUT | /api/admin/appointment/updateStatus | 后台更新预约跟进状态 |
| 房源管理 | POST | /api/admin/house/saveOrUpdate | 后台新增/编辑房源 |
| 统计 | GET | /api/admin/stats/summary | 首页统计:总房源、在售量、本月预约量、本月成交量 |
以最核心的“小程序端房源条件查询”为例。这里的难点在于,前端希望通过一个接口同时按关键字(楼盘名称/房源名称)、户型、朝向、价格区间、销售状态等多个维度组合筛选。我在 Controller 层接收一个 HouseQueryDTO 对象,然后把查询条件传给 MyBatis Plus 的 LambdaQueryWrapper 动态拼接 SQL:
java复制@GetMapping("/list")
public Result<IPage<HouseVO>> page(@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size,
HouseQueryDTO query) {
Page<House> pageParam = new Page<>(page, size);
LambdaQueryWrapper<House> wrapper = new LambdaQueryWrapper<>();
// 商品名称模糊查询
if (StringUtils.hasText(query.getKeyword())) {
wrapper.and(w -> w.like(House::getHouseName, query.getKeyword())
.or().like(House::getDetail, query.getKeyword()));
}
// 面积区间
if (query.getMinArea() != null) {
wrapper.ge(House::getArea, query.getMinArea());
}
if (query.getMaxArea() != null) {
wrapper.le(House::getArea, query.getMaxArea());
}
// 价格区间
if (query.getMinPrice() != null) {
wrapper.ge(House::getPrice, query.getMinPrice());
}
if (query.getMaxPrice() != null) {
wrapper.le(House::getPrice, query.getMaxPrice());
}
// 朝向
if (StringUtils.hasText(query.getOrientation())) {
wrapper.eq(House::getOrientation, query.getOrientation());
}
// 销售状态
if (StringUtils.hasText(query.getStatus())) {
wrapper.eq(House::getStatus, query.getStatus());
}
wrapper.orderByDesc(House::getCreateTime);
IPage<House> housePage = houseMapper.selectPage(pageParam, wrapper);
// 转换为包含楼盘名称的 VO
return Result.success(convertToVO(housePage));
}
这套动态条件拼接的写法,业务逻辑清晰且完全可控,避免了自己手动拼 SQL 字符串带来的注入风险。
3.3 小程序端核心页面设计
微信小程序端采用的是原生 WXML + WXSS + JavaScript 开发,没有额外引入 uni-app 或 Taro 框架。原生的好处是调试方便、运行时依赖少,对毕设或中小型项目足够。
首页设计上我用了两层结构。顶部是一个 banner 轮播图组件,展示楼盘最新活动;下面是一个“热门楼盘”的横向滑动列表,再往下是“最新房源”卡片流。进入首页后通过 onLoad 发起请求,加载首页数据后调用 setData 更新展示。这里有个性能细节值得注意:setData 传输的数据不宜过大,因此后端返回房源列表时,我只返回卡片需要的字段(id、图片、名称、面积、总价、状态),房源详情这类大文本通过点击进入详情页后单独请求,而不是和列表一起返回。
房源列表页是这个小程序里最复杂的页面,因为要处理筛选条件。页面顶部用 picker 组件做朝向筛选、价格排序开关,下面是使用 scroll-view 的房源卡片列表。核心是每次筛选条件变化时重新调用房源列表接口并替换列表数据。
javascript复制onFilterChange() {
const params = {
page: 1,
size: 10,
keyword: this.data.keyword,
orientation: this.data.orientation
};
request.get('/api/wx/house/list', params)
.then(res => {
this.setData({
houseList: res.data.records,
total: res.data.total
});
});
}
在上拉加载更多的处理里,我用了一个 isLoading 开关来防止重复请求:当用户快速上拉时,只有上一次请求完成才会发起第二次请求,避免出现页码错乱数据重叠的问题。这个设计虽然简单,但在真实场景中非常关键,稍微复杂一点的分页列表都应该做这个保护。
3.4 用户登录态处理:用 openid 作为天然账号
小程序端没有独立的注册登录页面。用户首次进入时会通过微信提供的 wx.login 拿到一个临时 code,然后把 code 传给后端,后端调用微信接口换取 openid。openid 是用户在当前小程序下的唯一标识,我用它作为用户表的逻辑主键。
换取 openid 的调用方式并不复杂,后端主要逻辑如下:
java复制@PostMapping("/wx/login")
public Result<User> login(@RequestBody LoginDTO dto) {
// 使用 code 调用微信接口换取 openid
String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + appid
+ "&secret=" + secret
+ "&js_code=" + dto.getCode()
+ "&grant_type=authorization_code";
String result = restTemplate.getForObject(url, String.class);
JSONObject json = JSON.parseObject(result);
String openid = json.getString("openid");
// 如果用户不存在则自动注册
User user = userService.getOne(new LambdaQueryWrapper<User>()
.eq(User::getOpenid, openid));
if (user == null) {
user = new User();
user.setOpenid(openid);
user.setNickname("微信用户" + openid.substring(0, 6));
userService.save(user);
}
// 将用户标识存入小程序缓存由前端自行管理即可
return Result.success(user);
}
在微信开发者工具中调试时,可以通过工具栏的“清缓存 → 清除数据缓存”来模拟新用户首次进入的情况。需要特别提醒的是,调用微信接口必须使用后端中转,绝对不可以把 appSecret 写在微信小程序前端代码里,因为小程序代码包可以在某些工具下被反编译,密钥一旦泄露,任何人都可以冒充你的小程序获取用户信息。
4. 实战实操:从零到一部署运行这个项目
很多同学买的源码到手后第一反应是打不开、运行不起来,最后发现 80% 的问题不是代码问题,而是环境与配置问题。下面我以这套系统为例,把部署运行的完整流程按顺序写一遍,包括版本选择、配置修改和验证办法。
4.1 环境准备清单
- JDK:1.8 或 11 均可。Spring Boot 2.x 与 JDK 8 是绝配,不建议上来就用 JDK 17 跑老项目,会遇到 javax 包名不兼容的问题。
- Maven:3.6 及以上,用于自动下载依赖。
- MySQL:5.7 或 8.0,注意数据库编码统一设置为 utf8mb4,因为小程序用户昵称可能包含生僻字或表情符号,utf8 字符集在插入时会出现“Incorrect string value”错误。
- 微信开发者工具:最新稳定版即可,使用前需要用自己的微信扫码登录。
- IDEA 或 Eclipse:推荐 IDEA,社区版就够用,其对 Spring Boot 项目的支持非常完善。
- 一个微信小程序 AppID:如果没有正式 AppID,也可以使用测试号,但测试号无法调用部分能力,生产环境还是建议注册一个个人或企业主体的小程序账号。
4.2 后端配置与启动步骤
第一步,导入源码。用 IDEA 打开后端的项目根目录,IDEA 会自动识别 Maven 工程并开始下载依赖,这一步需要等待较长时间,取决于网络环境。如果网络不稳定导致依赖一直下载失败,建议在 Maven 的 settings.xml 中配置阿里云镜像。
第二步,创建数据库。在本地 MySQL 中执行项目提供的 init.sql 脚本,脚本会创建数据库、所有表结构和默认管理员账号数据。执行完成后用 Navicat 或命令行检查一下,保证所有表都已经生成。
第三步,修改配置文件。在 application.yml 或 application.properties 中修改数据库连接信息,核心配置形如下:
yaml复制server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/house_sale?useUnicode=true&characterEncoding=utf8mb4&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
第四步,启动项目。运行启动类中带 @SpringBootApplication 注解的 main 方法,观察控制台日志,出现 “Started Application in xx seconds” 表示启动成功。为了验证接口是否正常,浏览器直接访问 http://localhost:8080/api/wx/building/list,如果能返回 JSON 数据,说明后端服务已经正常。
4.3 小程序端导入与调试步骤
第一步,下载微信开发者工具,导入小程序端源码目录。导入时需要填写自己的 AppID,如果没有可以将 AppID 选择为测试号,但要注意部分接口可能受限制。
第二步,修改小程序端的全局配置文件 app.js 或 config.js 中的后端接口地址。开发调试阶段,后端跑在本地电脑,小程序需要访问局域网 IP。假设电脑的 IP 是 192.168.1.100,那么接口地址就改成 http://192.168.1.100:8080/api。这个地址不可以在真机上用 http://localhost:8080,因为这里 localhost 指向的是手机自身,而不是你的电脑。
第三步,在微信公众平台配置合法域名或开启不校验。在开发者工具的详情面板中勾选“不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书”——这一步只是开发阶段跳过域名校验的权宜之计,上线发布时必须把后端 API 配到 HTTPS 域名下并在小程序后台配置 request 合法域名。
第四步,编译预览。点击开发者工具顶部的“编译”按钮,模拟器中即可看到小程序首页。如果数据正常展示,说明小程序端与后端的数据链路已经打通。接下来可以把手机和电脑连到同一 WiFi 下,点击“预览”按钮,用微信扫码在真机上体验完整流程。
4.4 上线前必须要改的几个配置
开发环境能跑通,和部署到公网环境完全是两回事。如果这个项目要正式发布上线,至少还有这几个配置要处理:
- HTTPS 证书。小程序生产环境强制要求所有 request 请求必须是 HTTPS,不能使用明文 HTTP。可以通过 Nginx 配置 SSL 证书,并将请求反向代理到后端的 8080 端口。
- 数据库账号隔离。开发环境直接使用 root 账号是没问题的,生产环境务必要新建一个专用数据库账号,只授权本项目所需数据库,降低数据泄露风险。
- 定时备份。房销系统的核心资产是预约数据和客户数据,建议每天凌晨通过 mysqldump 自动备份数据库,保留最近 7 天的备份文件。
- 后端接口日志。在 Spring Boot 中配置 logback,将关键业务操作(如预约状态变更、房源上下架)写入操作日志,出现纠纷时可追溯。
5. 核心实现难点与解决方案
5.1 图片上传与管理:本机存储还是对象存储
房源系统必然涉及大量图片,包括楼盘效果图、户型图、室内实拍图。最直接的方案是把图片上传到服务器本地,在数据库存图片的相对路径或全路径。开发环境下我把上传接口统一为 /api/admin/upload,后端将 MultipartFile 保存到项目配置的上传目录,同时把可访问的 URL 返回给前端。
利用 Spring Boot 实现图片上传的方法比较直接的写法:
java复制@PostMapping("/upload")
public Result<String> upload(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
return Result.error("上传文件不能为空");
}
// 原始文件名
String originalFilename = file.getOriginalFilename();
// 文件后缀 例如 .jpg
String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
// 生成新的不重复文件名
String newFileName = System.currentTimeMillis() + UUID.randomUUID().toString().replace("-", "") + suffix;
// 保存目录
String dirPath = ResourceUtils.getURL("classpath:").getPath() + "static/upload/";
File dir = new File(dirPath);
if (!dir.exists()) {
dir.mkdirs();
}
File newFile = new File(dirPath + newFileName);
file.transferTo(newFile);
return Result.success("/upload/" + newFileName);
}
需要注意,如果把图片保存在 Spring Boot 项目内部路径下,重启项目时如果执行了 clean 操作,上传的图片会被清空。这算是最容易踩的坑之一。实际开发中我会把上传目录配置为服务器固定路径(如 /home/upload),再在 WebMvcConfig 里配置静态资源映射,让 /upload/** 指向这个外部目录。这样无论项目怎么重新部署,图片都不会丢失。
当图片量较大时,更合适的做法是接入云对象存储。上传时后端生成预签名 URL 给小程序,小程序直接向云存储上传图片,上传完成后回传 URL 给后端做记录。这种方案能避免文件存储占用应用服务器磁盘,读取时走 CDN,加载速度也更好。
5.2 预约看房的并发冲突处理:防止同一时段重复预约
“预约看房”是这个系统最核心的一个写操作。在真实场景中,一个热销户型可能同时有多个客户在看,如果同一套房源在同一时间段被预约给两个不同客户,后续销售跟进就会非常尴尬。数据库层怎么避免这个问题?
最直接的方案是给预约表加上唯一约束:同一房源、同一天的预约时间只能有一条非取消状态的记录。具体实现可以先查询后插入,但仍然存在并发窗口。更稳的做法是利用数据库唯一索引:uk_house_appoint_time (house_id, appointment_time, status)——当然这个唯一索引会因为取消和新建的状态区分而变得复杂,实际中我更倾向于在插入前加一个带 for update 的行锁查询,或者直接用 Redis 的分布式锁保证同一房源同一时间只有一个预约请求在处理。
考虑到这套系统的并发量不会特别高,我在实现时先查询再插入,结合 MySQL 默认的可重复读隔离级别,并不会产生严重问题。不过对于后续扩展,我建议把“预约看房”做成一个预占状态机:客户提交预约后先进入“待确认”,销售顾问确认后锁定时段;如果超时未确认,系统自动释放可预约名额。
5.3 微信小程序与后端交互时的 Session 管理
微信小程序是一种特殊的“无 Cookie” HTTP 客户端,传统的 Servlet Session 机制在这里并不适用。小程序原生并没有 Cookie 的概念,不能像网页浏览器一样自动携带 JSESSIONID。因此,我采用 token 方案处理会话状态:用户登录成功(用 wx.login 的 code 换 openid)后,后端生成一个随机 UUID 作为 token,以 openid 为 value 存入 Redis 并设置过期时间,然后将 token 返回给前端。前端在后续请求头中以 Authorization: token 的形式携带该参数。
javascript复制// 小程序端请求封装
const request = (url, method, data) => {
return new Promise((resolve, reject) => {
wx.request({
url: baseUrl + url,
method: method || 'GET',
data: data || {},
header: {
'Authorization': wx.getStorageSync('token')
},
success: (res) => {
if (res.data.code === 200) {
resolve(res.data);
} else if (res.data.code === 401) {
// token 过期,跳转登录页
wx.navigateTo({ url: '/pages/login/login' });
} else {
reject(res.data);
}
},
fail: (err) => reject(err)
});
});
};
对应地,在后端实现一个登录拦截器(HandlerInterceptor),对需要登录的接口进行 token 有效性校验。之所以不依赖 Spring Session 把 Session 同步到 Redis,是因为那套方案需要额外的依赖和配置,而当前的 token 方案足够轻量且直观。
5.4 房源“上下架”状态管理与用户端实时展示
房源数据不是静态的。一套房被客户交定金之后,应当从前端列表中消失或置灰;一套房由于业主反悔重新释放,应当重新出现在库存中。我设计了一个 status 字典:
- 0:下架(不可见)
- 1:在售(小程序端可见)
- 2:已预定(小程序端可见但不可预约)
- 3:已售(小程序端样式置灰或直接过滤)
在用户端房源列表查询中,默认只查 status = 1 的房源;在房源详情页,如果房源状态为“已预定”,预约按钮disabled 显示为“已被预定”;状态为“已售”则隐藏预约入口。后台管理中,销售可以把房源状态从“在售”改为“预定/下架”,这种状态流转在每次变更后都需要写入操作日志,实现运营上的可追溯。
6. 常见问题与排查技巧实录
6.1 小程序提示“不在以下 request 合法域名列表中”
这句话应该排在“小程序开发最常见报错”第一名。出现这个问题的原因是开发者在微信公众平台没有配置后端 API 的域名白名单,而小程序出于安全策略只允许向白名单中的域名发起网络请求。
开发阶段有两个解决方案:一是在微信开发者工具的“详情 → 本地设置”中勾选“不校验合法域名…”,二是在微信公众平台后台的“开发管理 → 开发设置 → 服务器域名”中添加 request 合法域名。方案二要求域名必须是 HTTPS,而且需要在小程序发布前完成。如果没有自己的域名,且只是本地测试,使用方案一就够了。
6.2 后端启动时报数据库连接失败
这类问题的表现形态非常多:Access denied for user、Unknown database、Public Key Retrieval is not allowed、Communications link failure。排查时从前往后逐一确认三层:数据库服务是否启动、数据库账号密码是否正确、数据库名是否存在。
另外 MySQL 8.0 与 5.7 的 JDBC 驱动配置有差异。如果在 pom.xml 里使用的是 com.mysql.cj.jdbc.Driver,则对应 MySQL 8.x;如果是 com.mysql.jdbc.Driver,则对应 MySQL 5.x。两者混用会导致启动失败。本地如果安装了 MySQL 8.0,驱动应该使用前者,而且连接串最好加上 allowPublicKeyRetrieval=true&useSSL=false,否则会出现“Public Key Retrieval is not allowed”的异常。
6.3 小程序真机预览时请求不到后端接口
模拟器里运行得好好的,一扫码到真机上就请求失败,这基本是网络环境问题。这时请按顺序检查:
- 手机和电脑是否在同一个局域网中?不在同一网络则无法通过内网 IP 访问。
- 电脑防火墙是否拦截了外网对 8080 端口的访问?最简单的方法是临时关闭防火墙测试,如果能通就把 8080 端口加入入站规则。
- 后端服务是否绑定了 127.0.0.1?如果在 application.yml 中把 server.address 配置成了 127.0.0.1,外部设备自然无法访问。删除该配置或改为 0.0.0.0。
- 接口地址配置的是不是 IP?http://localhost:8080 这种地址重定向到了手机自身,必须改为局域网 IP。
还有一个很容易被忽略的坑:Windows 笔记本连接手机热点做调试时,电脑的 IP 可能是 192.168.137.x 网段,而手机又是另一个网段。这种情况下更稳妥的方案是用内网穿透工具生成一个临时公网地址,让真机通过公网地址访问后端接口。
6.4 小程序端页面数据不显示或报 500
在开发者工具中打开调试器的 Network 面板,查看请求的 URL 和响应体。如果响应是 HTTP 500,注意后端控制台的异常堆栈信息,多半是 SQL 语句错误或空指针异常。这里有一个建议:后端开发环境开启 MyBatis Plus 的 SQL 日志输出,每次请求都能在控制台看到实际执行的 SQL,这样可以快速定位是 SQL 问题还是 Java 代码问题。
code复制mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
6.5 微信小程序图片不显示
这个问题的原因通常有三类:图片 URL 是 HTTP 而不是 HTTPS,小程序对生产环境有安全限制;图片 URL 是相对路径没有拼全;图片被防盗链政策拦截。开发环境如果确认 URL 没问题但还是显示空白,可以把图片 URL 复制到浏览器中直接访问,若浏览器能打开而小程序不行,就要检查是否因为未配置 downloadFile 合法域名。在小程序后台配置 downloadFile 合法域名后,图片即可正常加载。
7. 前后端联调与数据一致性经验
很多初学者把前后端开发当成两个独立的事,先把后端写好再用 Postman 测一遍,然后把前端写好再用 mock 数据跑一遍,最后联调时才发现两者对不上。前后端是否约定清楚了接口字段,往往决定联调是否能顺利推进。
我的建议是,项目启动前就写好一份接口文档,并确定以下规范:状态码语义、分页参数和返回结构、字段命名风格(统一使用 camelCase)、时间格式统一传字符串等。后端接口调试完成之前,小程序端可以先用固定 mock 数据开发页面;等后端接口稳定了再把页面请求切换为真实调用。联调时优先打通“房源列表 → 房源详情 → 提交预约”这条核心链路,这条链路通了,说明数据通路正常。
我在这次项目中总结出的一个经验是:小程序的 setData 数据层与后端 JSON 字段尽量保持一致。例如后端给的是 houseName,前端在小程序里也用 houseName 接收,而不是转成 name,这样可以大大降低多层数据映射带来的维护成本。如果确实需要做字段精简,可以在后端专门组装 VO(视图对象),而不是让前端在拿到数据后做二次加工。频繁的前端数据处理容易引入 bug。
8. 这套系统还能怎么扩展
基础版本功能做完之后,可以考虑从以下几个方向扩展:
- 电子合同在线签署。将“预约看房 → 确认购买 → 在线签署认购书”流程线上化,订单可关联微信支付定金接口。
- 经纪人分销系统。每个销售顾问生成专属分享海报,客户通过海报进入小程序后自动绑定该销售,成交后按比例计算佣金。
- 地图找房。在首页提供地图模式,房源在地图上以 marker 展示,点击 marker 弹出房源卡片,提升筛选效率。
- 推送通知。用户预约成功后,通过微信小程序订阅消息能力给用户发送确认提醒,减少爽约率。
- 数据分析看板。销售周期趋势、热销户型排行榜、客户来源分析,用 ECharts 在后台管理端以图表形式展示。
- 多角色权限控制。引入 Spring Security 或 Sa-Token,做到不同销售只能查看和维护自己管理的房源数据,而管理员可以看到全量数据。
在这些扩展方向里,我个人最推荐加入的是“订阅消息”功能。它对代码改动量不大,但直接提升了系统的实用性——用户的看房预约有了即时回执,销售也不用再打电话催客户确认时间。微信小程序订阅消息的实现思路是:用户在小程序端点击“允许”订阅,后端拿到用户的 openid 与模板 ID 后,在业务事件发生后调用微信的发送接口。第一次做的时候要注意,用户授权订阅是一次性的,每次发送前都需要用户重新授权,不能像微信公众号模板消息那样无限次推送。
9. 部署上线之后,系统运营层面的几条建议
技术层面的部署只是第一步,真正让这套系统跑出价值的是日常运营。根据我在房产行业项目里的实践经验,有几点建议可以分享:
预约看房数据要每日跟进。很多房产项目上线系统后,销售顾问不习惯每天登录后台查看预约记录,导致客户在线上约了看房,售楼处却没人对接。给销售团队留出固定的“线索处理时间”,比如每天上班后第一时间处理前一天的预约请求,这是运营制度对系统价值的保障。
房源状态更新要有时效。如果一位置业顾问没有及时把已售房源标记为“已售”,前端就会继续展示这套房,客户打电话来询问时就会产生糟糕体验。应当在团队内部约定:签署认购书后 10 分钟内必须在系统中完成状态变更。
定期清理无效数据。小程序端会收到大量无效咨询,比如错误电话号码、测试留言。如果不定期清理,后台的有效预约会被无效数据淹没。可以每周执行一次数据质量检查,把电话格式不正确、连续多周未跟进的预约记录标记为失效。
上线之前做一些基础的数据初始化工作。楼盘数据、户型图、价格信息必须完整,宁可先上少量真实的精品房源,也不要直接空着列表上线。前端空状态的设计同样重要,没有图片的房源卡片应默认展示统一的“暂无图片”,而不是破图。
10. 写在最后的一些心得
从我个人的实际开发经验来看,这类“Java + Spring Boot + 微信小程序”的管理系统项目,形式上看是一个技术展示,实质上更多考查的是对业务场景的理解。如果仅仅把 CRUD 写完,项目就是一个空壳;但如果你能想清楚每个字段为什么这样设计、每一步状态为什么这样流转、每个接口为什么需要做这样的权限校验,这个项目的含金量就完全不一样了。
代码量其实不大,全项目大概在 3000 到 5000 行之间,真正值钱的地方在于你对房产销售业务的抽象能力:房源生命周期怎么建模、预约状态怎么流转、销售数据怎么统计。这些需要靠项目积累,不是单纯背八股文能拿到的。
最后再分享一个小技巧。拿到这类带“源码 + 文档 + 运行视频”的项目资料时,不要先急着导代码,先把项目提供的数据库脚本从头到尾读一遍。表结构设计得好不好,直接决定了后面所有代码的复杂度。我见过太多把图片路径放在字典表里、把业务状态用中文字符串存储的设计,表结构混乱,后面每写一个查询都是一种折磨。先读库表,再读代码,最后再运行,这个顺序能帮你省下大量排查时间。
