资讯动态

SpringCloud+Mysql房产销售平台:微服务拆分与数据库设计实战

发布时间:2026/10/10 16:23:20 来源:尧图企业网站定制
简介这是一套面向高校计算机专业学生与Java开发学习者的房产销售平台完整项目源码采用Spring Cloud微服务架构搭建数据存储选用MySQL适合用作课程设计、毕业设计或微服务入门实战参考。资源包共811个文件约20.15MB涵盖130个Java后端源码、48个Vue前端组件、44个HTML页面与44个CSS样式文件并配有SQL建表脚本、yml配置、xml映射及bat启动脚本前后端与数据库结构一应俱全。平台区分管理员与客户两类角色管理员负责用户与房源信息维护客户登录后可浏览房源并完成在线签约业务链路完整。项目从可行性分析、需求梳理到功能设计与代码落地均有对应实现读者可借此理解微服务拆分、接口调用与前后端联调思路也能直接运行调试、二次开发或作为论文素材。目前已有70人学习下载适合需要完整项目案例的开发者参考。1. 从一张房源表说起SpringCloudMysql 房产销售平台到底在解决什么挂出一套房背后至少牵扯四件事房源信息要能被多端同时看到、客户带看记录要实时同步、成交状态一变所有相关方都得知道、经纪人之间的业绩归属不能算错。单机应用扛不住这种并发和一致性要求于是就有了 SpringCloudMysql 房产销售平台这套组合。它用 SpringCloud 把房源、客源、交易、用户几个模块拆成独立服务各自连自己的 Mysql 库通过注册中心和网关串起来对外提供接口。适合谁适合正在做 Java 课程设计、毕业设计或者想拿一个真实业务场景练微服务拆分和数据库设计的人。源码加论文这种交付形态核心价值不在代码本身而在于你能顺着它把「一个业务系统怎么从单体走向服务化」这条路走一遍。下面我按实际搭建顺序把选型理由、建库建表、服务拆分、联调排错逐层拆开。2. 服务怎么拆、库怎么分房产销售平台的领域建模与 SpringCloud 组件选型2.1 先画业务边界再决定拆几个服务很多人一上来就照着 SpringCloud 教程把 Eureka、Gateway、Feign 全搭一遍结果服务拆完了发现业务对不上。正确顺序是先做领域建模。房产销售平台的核心实体就那么几个房源House、小区Community、客户Customer、带看记录Visit、成交订单Deal、经纪人Agent、用户账号User。围绕这些实体业务动作分三类房源维护录入、修改、上下架、客户跟进登记、分配、带看、回访、交易管理认购、签约、佣金结算。按业务动作聚类我一般会拆成四个服务房源服务house-service管房源和小区客户服务customer-service管客户和带看交易服务deal-service管订单和佣金用户服务user-service管账号、角色和权限。每个服务独立一个 Mysql schema服务之间只通过接口拿数据不跨库 join。这样拆的好处是房源上下架的高频写操作不会拖累交易结算客户服务的查询压力也不会传导到房源库。提示拆服务的粒度以「一个服务能不能由一个人独立维护」为准不要为了微服务而微服务。课程设计场景下四个服务足够拆到七八个反而增加联调成本。2.2 SpringCloud 组件选型注册中心、网关、远程调用各选什么组件选型直接决定你后面踩多少坑。以当前主流稳定组合为例组件作用常见选择选型理由注册中心服务注册与发现Nacos自带配置中心省一套 Apollo网关统一入口、鉴权、限流Spring Cloud Gateway基于 WebFlux性能比 Zuul 好远程调用服务间通信OpenFeign声明式接口代码量少负载均衡请求分发Spring Cloud LoadBalancer替代已停更的 Ribbon熔断降级防止雪崩Sentinel控制台可视化规则动态推Nacos 同时做注册中心和配置中心对课程设计来说少维护一个组件。Gateway 负责把/api/house/**路由到房源服务/api/deal/**路由到交易服务同时在网关层做 JWT 校验避免每个服务重复写鉴权。OpenFeign 让服务间调用像调本地方法但要注意它默认不带熔断得手动配 Sentinel 或 Resilience4j。2.3 用 Docker 起一个 Mysql 8 并建好房产库环境搭建是第一个翻车高发区。Mysql 8 的认证插件和时区配置跟 5.7 不一样直接装容易连不上。我一般用 Docker 起干净且可复现# 拉取 mysql 8.0 镜像并启动映射 3306设置 root 密码和时区 docker run -d \ --name estate-mysql \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDRoot1234 \ -e TZAsia/Shanghai \ -v /data/mysql/estate:/var/lib/mysql \ mysql:8.0 \ --character-set-serverutf8mb4 \ --collation-serverutf8mb4_unicode_ci \ --default-authentication-pluginmysql_native_password--default-authentication-pluginmysql_native_password是关键Mysql 8 默认用caching_sha2_password老版本 JDBC 驱动连不上会报Public Key Retrieval is not allowed。-v把数据挂到宿主机容器删了数据还在。TZ设成上海否则create_time存进去差 8 小时。启动后进容器建库-- 建四个库对应四个服务字符集统一 utf8mb4 CREATE DATABASE estate_house DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE DATABASE estate_customer DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE DATABASE estate_deal DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE DATABASE estate_user DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;2.4 房源表设计字段类型和索引怎么定房源表是整个平台被查最多的表字段类型和索引设计直接影响后面接口的响应速度。核心表结构CREATE TABLE house ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 主键, title VARCHAR(128) NOT NULL COMMENT 房源标题, community_id BIGINT UNSIGNED NOT NULL COMMENT 小区ID, price DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT 总价(万), area DECIMAL(8,2) NOT NULL DEFAULT 0.00 COMMENT 面积(平米), room TINYINT NOT NULL DEFAULT 0 COMMENT 室, hall TINYINT NOT NULL DEFAULT 0 COMMENT 厅, floor SMALLINT NOT NULL DEFAULT 0 COMMENT 楼层, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待售 1已售 2下架, agent_id BIGINT UNSIGNED NOT NULL COMMENT 负责经纪人, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_community (community_id), KEY idx_status_price (status, price), KEY idx_agent (agent_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT房源表;价格用DECIMAL不用FLOAT浮点算佣金会出精度问题。status和price建联合索引因为列表页最常见的查询是「按状态筛、按价格排序」。update_time用ON UPDATE CURRENT_TIMESTAMP自动维护省得业务代码每次手动 set。注意Mysql 8 的utf8mb4_unicode_ci和utf8mb4_0900_ai_ci排序规则不同前者兼容 5.7 迁移后者是 8.0 默认。如果源码里 JDBC 连接串指定了collation两边要对齐否则中文排序结果不一致。3. 从零跑通第一个接口房源服务的注册、网关路由与 Feign 调用3.1 房源服务的依赖和 bootstrap 配置房源服务是第一个要跑起来的服务它的配置模式后面三个服务照抄即可。pom.xml核心依赖!-- Nacos 服务发现 -- dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-nacos-discovery/artifactId /dependency !-- Nacos 配置中心 -- dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-nacos-config/artifactId /dependency !-- OpenFeign 远程调用 -- dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-starter-openfeign/artifactId /dependency !-- Mysql 驱动 -- dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId /dependencybootstrap.yml里配 Nacos 地址和服务名spring: application: name: house-service cloud: nacos: discovery: server-addr: 127.0.0.1:8848 config: server-addr: 127.0.0.1:8848 file-extension: yaml datasource: url: jdbc:mysql://127.0.0.1:3306/estate_house?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue username: root password: Root1234 driver-class-name: com.mysql.cj.jdbc.Driver连接串里serverTimezoneAsia/Shanghai必须加否则DATETIME字段读写会报时区错误。allowPublicKeyRetrievaltrue配合前面 Mysql 8 的认证插件设置解决连接被拒。3.2 写一个房源分页查询接口并注册到 NacosController 层用 MyBatis-Plus 做分页避免手写 count 语句RestController RequestMapping(/house) public class HouseController { Autowired private HouseService houseService; // 分页查询房源支持按状态和价格区间过滤 GetMapping(/page) public ResultIPageHouse page( RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize, RequestParam(required false) Integer status, RequestParam(required false) BigDecimal minPrice, RequestParam(required false) BigDecimal maxPrice) { LambdaQueryWrapperHouse wrapper new LambdaQueryWrapper(); wrapper.eq(status ! null, House::getStatus, status) .ge(minPrice ! null, House::getPrice, minPrice) .le(maxPrice ! null, House::getPrice, maxPrice) .orderByDesc(House::getCreateTime); return Result.ok(houseService.page(new Page(pageNum, pageSize), wrapper)); } }LambdaQueryWrapper的条件方法第一个参数是布尔值为 false 时该条件不拼进 SQL这样动态查询不用写一堆 if。启动类上加EnableDiscoveryClient和MapperScan服务起来后去 Nacos 控制台的服务列表能看到house-service就说明注册成功。3.3 网关路由配置把 /api/house 转发到房源服务Gateway 服务单独一个模块application.ymlspring: cloud: gateway: routes: - id: house-route uri: lb://house-service predicates: - Path/api/house/** filters: - StripPrefix1 - id: deal-route uri: lb://deal-service predicates: - Path/api/deal/** filters: - StripPrefix1lb://表示走负载均衡从注册中心找实例StripPrefix1去掉路径第一段/api这样转发到房源服务的请求就是/house/page跟 Controller 的RequestMapping对上。网关启动后访问http://localhost:8080/api/house/page能拿到数据说明路由通了。3.4 交易服务用 Feign 调房源服务查房源详情交易服务创建订单前要校验房源是否存在、是否可售这就要跨服务调用。定义 Feign 客户端FeignClient(name house-service, fallback HouseClientFallback.class) public interface HouseClient { // 调用房源服务的详情接口 GetMapping(/house/{id}) ResultHouse getById(PathVariable(id) Long id); }name写 Nacos 里的服务名不用写 IP。fallback指定降级类房源服务挂了返回兜底数据而不是直接抛异常。启动类加EnableFeignClients。这里有个血泪经验PathVariable必须显式写 value否则 Feign 拼 URL 时参数名丢失报 400。4. 数据一致性和性能Mysql 锁、事务边界与缓存穿透的排查4.1 成交扣减库存为什么要用悲观锁而不是乐观锁房产交易里「一套房只能卖一次」是硬约束。两个经纪人同时给同一套房创建订单如果只用update house set status1 where id?两个请求都可能查到 status0 然后都更新成功出现一房两卖。常见做法是在事务里用select ... for update锁住行START TRANSACTION; -- 悲观锁锁住房源行其他事务阻塞等待 SELECT status FROM house WHERE id 1001 FOR UPDATE; -- 校验 status0 后更新 UPDATE house SET status 1 WHERE id 1001 AND status 0; INSERT INTO deal (house_id, customer_id, amount) VALUES (1001, 2001, 350.00); COMMIT;FOR UPDATE加在id主键上锁的是行锁不是表锁。但要注意如果WHERE条件没走索引InnoDB 会退化成表锁整个房源表被锁住并发直接崩。所以id或status上必须有索引。乐观锁版本号方案在秒杀场景更合适但房产交易并发量不高悲观锁逻辑更直观不容易写错。4.2 事务边界Feign 调用不能放在本地事务里这是微服务下最容易翻车的地方。交易服务创建订单时如果先开本地事务再通过 Feign 调房源服务改状态会出现「本地事务回滚了但远程调用已经生效」的不一致。正确做法是把远程调用放在本地事务提交之后或者用 Seata 做分布式事务。课程设计场景下我一般建议// 先本地落订单状态为待确认 dealService.savePendingDeal(deal); // 事务提交后再调房源服务锁定房源 try { houseClient.lockHouse(deal.getHouseId()); dealService.confirmDeal(deal.getId()); } catch (Exception e) { // 远程失败补偿取消订单 dealService.cancelDeal(deal.getId()); }这种「本地消息表 补偿」的思路比硬上 Seata 简单也更容易讲清楚。Seata 的 AT 模式对新手来说配置量大出问题不好排查。4.3 房源列表缓存穿透空结果也要缓存房源列表接口 QPS 高一般会加 Redis 缓存。但有个坑查一个不存在的房源 ID缓存里没有每次都打到 Mysql恶意请求能把库压垮。解决办法是空结果也缓存设短过期时间public House getById(Long id) { String key house: id; String cached redisTemplate.opsForValue().get(key); if (cached ! null) { // 缓存的是空标记直接返回 null return EMPTY.equals(cached) ? null : JSON.parseObject(cached, House.class); } House house houseMapper.selectById(id); if (house null) { // 空结果缓存 60 秒防止穿透 redisTemplate.opsForValue().set(key, EMPTY, 60, TimeUnit.SECONDS); return null; } redisTemplate.opsForValue().set(key, JSON.toJSONString(house), 30, TimeUnit.MINUTES); return house; }缓存空值的时间要短否则房源刚录入也查不到。房源更新时要主动删缓存别只依赖过期时间。5. 避坑与排查Mysql 连接、Nacos 注册、Feign 超时的 5 个真实故障5.1 服务启动报 Public Key Retrieval is not allowed现象SpringBoot 启动时连 Mysql 8 报java.sql.SQLException: Public Key Retrieval is not allowed。原因Mysql 8 默认认证插件是caching_sha2_passwordJDBC 驱动需要公钥交换但默认禁止。解决连接串加allowPublicKeyRetrievaltrueuseSSLfalse或者建库时把用户认证插件改成mysql_native_password。两个都做最稳。5.2 Nacos 里服务时有时无Feign 调用报 No instances available现象服务明明启动了Nacos 控制台偶尔能看到偶尔看不到Feign 调用间歇性报找不到实例。原因Nacos 心跳间隔和临时实例剔除时间配置不匹配或者服务所在机器时间不同步。解决检查spring.cloud.nacos.discovery.heart-beat-interval和heart-beat-timeout默认 5 秒和 15 秒别改太小。机器时间用ntpdate同步时间差超过心跳超时会被误剔除。5.3 Feign 第一次调用超时后面正常现象服务刚启动后第一次 Feign 调用必超时重试就好。原因OpenFeign 默认不开启 Ribbon 的饥饿加载第一次调用时才去拉服务列表和建立连接。解决配置ribbon.eager-load.enabledtrue并指定客户端或者调大feign.client.config.default.connectTimeout。更彻底的办法是加spring.cloud.openfeign.lazy-attributes-resolutionfalse。5.4 分页查询 total 为 0 但列表有数据现象MyBatis-Plus 分页查出来records有数据但total是 0。原因分页插件没配或者配错count 语句没执行。解决确认配置类里注册了MybatisPlusInterceptor并添加了PaginationInnerInterceptor且指定了数据库类型DbType.MYSQL。漏了这步分页只做 limit 不做 count。5.5 房源状态更新后列表还是旧数据现象改了房源状态列表页刷新还是旧状态。原因缓存没删或者删的 key 和查的 key 不一致。解决更新和删除操作里缓存 key 的拼法必须和查询时完全一致建议抽一个CacheKeyUtil统一生成。另外注意CacheEvict在同类内部方法调用时不生效因为不走代理。6. 进阶用配置中心动态调参和接口压测验证微服务拆分效果服务跑通只是起点真正体现 SpringCloud 价值的是动态配置和可观测性。Nacos 配置中心可以让房源服务的分页大小、缓存过期时间不重启就生效。在 Nacos 控制台建house-service.yaml内容写house.page.max-size: 50代码里用RefreshScope注解的 Bean 读取RestController RefreshScope public class HouseController { Value(${house.page.max-size:20}) private Integer maxPageSize; GetMapping(/page) public ResultIPageHouse page(RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize) { // 用配置中心的值兜底防止前端传超大 pageSize 拖垮数据库 if (pageSize maxPageSize) { pageSize maxPageSize; } // ... 分页逻辑 } }改完 Nacos 里的值不用重启服务下次请求就生效。这个能力在压测调参时特别有用你可以边压边调分页上限和缓存时间找到吞吐和内存的平衡点。验证微服务拆分是否合理最直接的办法是压测。用 JMeter 或 wrk 对网关的房源列表接口打 200 并发观察三个指标网关的 P99 延迟、房源服务的 CPU、Mysql 的 QPS。如果房源服务 CPU 先到瓶颈而 Mysql 很闲说明缓存命中率不够该加 Redis如果 Mysql QPS 先爆说明索引没走对或者缓存没生效。我一般会先压出单服务的极限再压网关对比两者差值就是网关和网络的开销。最后说个习惯每次改完配置或代码先看 Nacos 服务列表是否健康再看网关日志有没有 503最后才去查业务数据。这个顺序能帮你快速定位是注册问题、路由问题还是业务问题。微服务排查最怕一上来就翻业务代码其实八成故障在基础设施层。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑