房产中介小程序的功能清单不长:房源列表、地图找房、房源详情、预约看房、经纪人联系。真正难的是底下的数据问题——同一个小区、同一套房,被三个经纪人各发了一遍,用户看到重复房源,体验直接崩。
本文围绕房源建模、地图找房、去重、带看预约四条线展开。示例基于 MySQL 8 + Redis GEO。
房产数据天然是两层:楼盘(小区) 和 房源(具体一套)。把两者合成一张表,会重复存储小区的位置、配套、物业信息,而且小区信息一改就要批量更新。
-- 楼盘/小区
CREATE TABLE community (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
district VARCHAR(40) NOT NULL COMMENT '行政区',
address VARCHAR(200) NOT NULL,
lat DECIMAL(10,7) NOT NULL,
lng DECIMAL(10,7) NOT NULL,
build_year SMALLINT NULL,
property_fee DECIMAL(6,2) NULL COMMENT '物业费 元/㎡',
developer VARCHAR(100) NULL,
UNIQUE KEY uk_name_district (name, district),
KEY idx_district (district)
);
-- 房源
CREATE TABLE house (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
community_id BIGINT NOT NULL,
agent_id BIGINT NOT NULL,
biz_type TINYINT NOT NULL COMMENT '1出售 2出租',
layout VARCHAR(20) NOT NULL COMMENT '如 3室2厅',
room_count TINYINT NOT NULL,
hall_count TINYINT NOT NULL,
area DECIMAL(7,2) NOT NULL COMMENT '建筑面积 ㎡',
floor SMALLINT NOT NULL,
total_floor SMALLINT NOT NULL,
orientation TINYINT NULL COMMENT '1东2南3西4北',
decoration TINYINT NULL COMMENT '1毛坯2简装3精装4豪装',
price_total INT NULL COMMENT '总价,万元(出售)',
price_rent INT NULL COMMENT '月租,元(出租)',
title VARCHAR(120) NOT NULL,
description TEXT NULL,
cover_url VARCHAR(255) NULL,
status TINYINT NOT NULL DEFAULT 1 COMMENT '1在售 2已成交 3已下架',
fingerprint CHAR(32) NOT NULL COMMENT '去重指纹',
published_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
refreshed_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
KEY idx_comm_biz (community_id, biz_type, status),
KEY idx_price (biz_type, price_total),
KEY idx_agent (agent_id, status),
KEY idx_fingerprint (fingerprint)
);fingerprint 是解决重复房源的关键字段,下一节展开。
中介行业有个现实:一套房源会被多个经纪人录入,甚至同一个经纪人重复录。如果不做去重,用户搜索"城西区 三居 100-150 万",会看到同一套房出现四次,价格还不一样。
去重的核心是给每套房算一个"业务指纹",让物理上同一个东西得到同一个值。
可用的字段组合是:小区 + 楼栋 + 单元 + 房号。这四个字段能唯一定位一套房。但实际数据里这些字段经常缺失或不规范("3号楼"写成"3#","1单元"写成"一单元")。
工程上的做法是做归一化,再算指纹:
const crypto = require('crypto');
function normalize(s) {
if (!s) return '';
return String(s)
.replace(/[##号栋幢单元座]/g, '') // 去掉单位和符号
.replace(/[一二三四五六七八九十]/g, c => '一二三四五六七八九十'.indexOf(c) + 1)
.replace(/\s+/g, '')
.toUpperCase();
}
function fingerprint(house) {
const raw = [
house.communityId,
normalize(house.building),
normalize(house.unit),
normalize(house.roomNo),
].join('|');
return crypto.createHash('md5').update(raw).digest('hex');
}指纹不唯一时怎么办:如果楼栋房号缺失,退化为"小区 + 户型 + 面积 + 楼层"的近似指纹,置信度低,只用于提示"疑似重复",不自动合并。
合并策略要谨慎:不能简单地删掉重复的。同一套房由两个经纪人维护,其中一个可能价格更准、照片更新。合理的做法是保留一条"主房源",其他作为"同房源报价"挂在下面,详情页展示"3 位经纪人发布,最低报价 148 万"。这样既消除了重复感,又保留了竞争信息——这也是用户真正想要的。
CREATE TABLE house_duplicate (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
master_id BIGINT NOT NULL COMMENT '主房源',
duplicate_id BIGINT NOT NULL COMMENT '被合并的房源',
confidence TINYINT NOT NULL COMMENT '1高 2中 3低',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_dup (duplicate_id),
KEY idx_master (master_id)
);uk_dup 保证一套房只能被合并一次,避免形成链条(A 合并到 B,B 又合并到 C)。
地图找房是房产小程序的标配,也是最容易做卡的功能。用户在地图上缩放,视野内可能有几千套房源,全部渲染会直接卡死。
方案:按缩放级别做网格聚合。
Redis 的 GEO 命令适合做"附近搜索",但做"视野内聚合"要自己算网格。做法是把地图按当前缩放级别切成网格,统计每个网格内的房源数和最低价:
// 网格边长(度),随缩放级别变化
function gridSize(zoom) {
const table = { 10: 0.5, 11: 0.25, 12: 0.125, 13: 0.0625, 14: 0.03125, 15: 0.015625 };
return table[zoom] || 0.015625;
}
async function mapCluster(sw, ne, zoom, filters) {
const size = gridSize(zoom);
// 缩放级别足够大(比如 16+),直接返回具体房源
if (zoom >= 16) {
return db.query(
`SELECT h.id, h.title, h.price_total, c.lat, c.lng
FROM house h JOIN community c ON c.id = h.community_id
WHERE h.status = 1 AND h.biz_type = ?
AND c.lat BETWEEN ? AND ? AND c.lng BETWEEN ? AND ?
LIMIT 200`,
[filters.bizType, sw.lat, ne.lat, sw.lng, ne.lng]
);
}
// 低缩放级别,按网格聚合
return db.query(
`SELECT
FLOOR(c.lat / ?) AS grid_lat,
FLOOR(c.lng / ?) AS grid_lng,
COUNT(*) AS cnt,
MIN(h.price_total) AS min_price,
AVG(c.lat) AS lat,
AVG(c.lng) AS lng
FROM house h JOIN community c ON c.id = h.community_id
WHERE h.status = 1 AND h.biz_type = ?
AND c.lat BETWEEN ? AND ? AND c.lng BETWEEN ? AND ?
GROUP BY grid_lat, grid_lng
HAVING cnt > 0
LIMIT 500`,
[size, size, filters.bizType, sw.lat, ne.lat, sw.lng, ne.lng]
);
}LIMIT 200 / LIMIT 500 是必须的。 地图视野是不可控的——用户把地图缩到全国,视野内可能有几十万条数据。没有上限,一次请求就能把数据库拖垮。
lat/lng 存在 community 上而不是 house 上,这一点很重要:同一小区的所有房源共享坐标,避免了"同一小区的房源在地图上散成一片"的问题,也省了大量存储。
索引:community 表的 (lat, lng) 要建联合索引。MySQL 8 也支持 SPATIAL 索引配合 POINT 类型,但实测在矩形范围查询上,普通 B-Tree 联合索引配合 BETWEEN 的性能已经够用,且更容易维护。
房产带看预约看着像普通预约,实际有三个差异。
一是"看房"往往连带多套。 用户约一次,经纪人会安排"这条街看三套"。所以预约单要能关联多个 house_id,而不是一对一。
CREATE TABLE viewing_appointment (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
agent_id BIGINT NOT NULL,
house_ids JSON NOT NULL COMMENT '[123, 456, 789]',
appoint_date DATE NOT NULL,
time_slot VARCHAR(20) NOT NULL COMMENT '如 14:00-15:00',
contact_name VARCHAR(40) NOT NULL,
contact_phone VARCHAR(20) NOT NULL,
status TINYINT NOT NULL DEFAULT 1 COMMENT '1待确认 2已确认 3已完成 4已取消 5爽约',
remark VARCHAR(200) NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
KEY idx_agent_date (agent_id, appoint_date, status),
KEY idx_user (user_id)
);二是经纪人的时间要防冲突。 同一个经纪人同一时段不能接两个客户。这个校验不能用简单的唯一索引(因为时段是字符串),要在应用层加锁或者用条件更新。
三是爽约要记录。 用户约了不来,经纪人的时间就浪费了。要记录爽约次数,超过阈值(比如 3 次)限制预约,或者要求提前付小额定金。
虚假房源是房产平台的顽疾:用低价好房吸引用户,实际带看时说"这套刚卖掉,我带你看看别的"。
技术上能做的有三件事:
一是图片查重。 同一组图片被用在多个房源上,基本可以判定是虚假或重复。用感知哈希(pHash)比对,相似度超过阈值就标记。
二是价格异常检测。 同小区同户型的均价,如果某套房源低于均价 30% 以上,标记为"待核实",在列表上不优先展示,或者加提示。
三是房源有效期。 房源要定期"刷新确认",超过 N 天未确认的自动下架。这条对清理"僵尸房源"很有效——很多虚假房源是半年前发的,早就成交了。
-- 每日任务:超过 30 天未确认的房源自动下架 UPDATE house SET status = 3 WHERE status = 1 AND refreshed_at < DATE_SUB(NOW(), INTERVAL 30 DAY);
中介公司的管理难点不在房源,在"这个客户算谁的"。同一个客户可能被三个经纪人先后带看,成交后佣金怎么分,是每天都在吵的事。
系统要能回答两个问题:客户第一次是谁接待的、最后一次是谁带看的。
常见规则有几种:首接保护(第一个录入客户的经纪人享有 N 天保护期)、末次带看(成交前最后一次带看的人拿主要佣金)、按带看次数分配(多人参与时按次数拆分)。
规则是业务定的,系统要做的是把过程记全:
CREATE TABLE customer_visit (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
customer_id BIGINT NOT NULL,
agent_id BIGINT NOT NULL,
house_id BIGINT NULL,
visit_type TINYINT NOT NULL COMMENT '1首次接待 2带看 3复看',
visited_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
KEY idx_customer (customer_id, visited_at),
KEY idx_agent (agent_id, visited_at)
);有了这张表,无论公司采用哪种分佣规则,都能从历史记录里算出结果。反过来,如果只记"当前负责人",一旦换人,前面的贡献就查不到了,分佣只能靠回忆。
客户手机号要防私单。 经纪人私下成交、绕开公司,是行业普遍问题。技术上能做的是给客户号做虚拟中转,通话记录留痕,成交必须走系统合同。这些手段不能根治,但能提高绕过的成本。
房产中介小程序的重点在数据层,不在界面层:
- 楼盘与房源分表,坐标挂在楼盘上,避免同小区房源散点;- 指纹去重 + 主房源合并,用"多经纪人报价"的方式保留竞争信息,而不是硬删;- 地图按缩放级别聚合,必须加 LIMIT,否则视野一放大就拖垮数据库;- 带看预约是一对多,要处理经纪人时段冲突和爽约记录;- 房源真实性 靠图片查重、价格异常、定期刷新三条防线。
青海青帝信息科技有限公司在本地房产类项目里,会把"房源去重"和"地图聚合上限"作为两个必测项——前者决定用户看到的信息质量,后者决定高峰期系统活不活。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。