1. 项目概述
这个基于SpringBoot的租房中介系统是一个面向现代租房市场的全流程解决方案。作为一名经历过多次租房和出租房屋的开发者,我深知传统租房模式中的痛点:虚假房源、信息不对称、流程繁琐等问题长期困扰着租客和房东。这个系统正是为了解决这些问题而设计的。
系统采用前后端分离架构,后端使用SpringBoot框架,前端基于Vue.js和Element UI组件库。核心功能包括房源管理、租赁流程、信用评价和数据分析四大模块,支持房东、租客和管理员三种角色。通过这个系统,房东可以高效管理房源,租客能够快速找到合适房屋,管理员则能规范市场秩序。
2. 系统架构设计
2.1 技术选型解析
选择SpringBoot作为后端框架主要基于以下考虑:
- 快速开发:SpringBoot的自动配置和起步依赖大大减少了样板代码
- 微服务友好:便于后期扩展为微服务架构
- 生态丰富:Spring生态中有大量成熟解决方案
前端选择Vue.js+Element UI组合是因为:
- 学习曲线平缓:相比React和Angular更易上手
- 组件丰富:Element UI提供了大量现成组件
- 性能优秀:虚拟DOM和响应式系统保证了良好性能
数据库方面采用MySQL 8.0作为主数据库,主要考虑:
- 事务支持完善:ACID特性保障数据一致性
- 性能稳定:经过大量生产环境验证
- 成本低廉:相比商业数据库节省成本
2.2 系统架构图
系统采用典型的三层架构:
code复制表现层(Web/App) → 业务逻辑层(SpringBoot) → 数据访问层(MySQL)
关键设计决策:
- 前后端分离:便于独立开发和部署
- RESTful API:统一接口规范
- JWT认证:无状态认证机制减轻服务器压力
3. 核心功能实现
3.1 房源管理模块
房源是系统的核心实体,其数据库设计如下:
sql复制CREATE TABLE `house` (
`id` bigint NOT NULL AUTO_INCREMENT,
`title` varchar(100) NOT NULL,
`address` varchar(200) NOT NULL,
`price` decimal(10,2) NOT NULL,
`area` decimal(6,2) NOT NULL,
`room_count` int NOT NULL,
`hall_count` int NOT NULL,
`bathroom_count` int NOT NULL,
`floor` int NOT NULL,
`total_floor` int NOT NULL,
`description` text,
`cover_image` varchar(255) NOT NULL,
`status` tinyint NOT NULL DEFAULT '1' COMMENT '1-待租 2-已租 3-下架',
`landlord_id` bigint NOT NULL,
`create_time` datetime NOT NULL,
`update_time` datetime NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_landlord` (`landlord_id`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
房源搜索功能使用Elasticsearch实现,核心查询逻辑:
java复制public Page<House> search(String keyword, Integer minPrice, Integer maxPrice,
String district, int page, int size) {
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
// 构建多条件查询
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
if (StringUtils.isNotBlank(keyword)) {
boolQuery.must(QueryBuilders.multiMatchQuery(keyword, "title", "address", "description"));
}
if (minPrice != null && maxPrice != null) {
boolQuery.must(QueryBuilders.rangeQuery("price").gte(minPrice).lte(maxPrice));
}
if (StringUtils.isNotBlank(district)) {
boolQuery.must(QueryBuilders.termQuery("district", district));
}
queryBuilder.withQuery(boolQuery)
.withPageable(PageRequest.of(page, size))
.withSort(SortBuilders.fieldSort("create_time").order(SortOrder.DESC));
return elasticsearchRestTemplate.search(queryBuilder.build(), House.class);
}
3.2 租赁流程模块
租赁流程状态机设计:
code复制待支付 → 已支付 → 已签约 → 已入住 → 已完成
↘
已取消
电子合同生成使用Thymeleaf模板引擎:
java复制public String generateContract(RentalOrder order) {
Context context = new Context();
context.setVariable("order", order);
context.setVariable("landlord", order.getLandlord());
context.setVariable("tenant", order.getTenant());
context.setVariable("house", order.getHouse());
return templateEngine.process("contract_template", context);
}
支付集成支付宝SDK的关键配置:
properties复制# 支付宝配置
alipay.app-id=your_app_id
alipay.private-key=your_private_key
alipay.public-key=your_public_key
alipay.notify-url=/api/payment/notify
alipay.return-url=/payment/success
4. 数据库设计与优化
4.1 核心表结构
- 用户表(users)
sql复制CREATE TABLE `users` (
`id` bigint NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL,
`password` varchar(100) NOT NULL,
`real_name` varchar(50) DEFAULT NULL,
`phone` varchar(20) NOT NULL,
`email` varchar(100) DEFAULT NULL,
`id_card` varchar(20) DEFAULT NULL,
`avatar` varchar(255) DEFAULT NULL,
`role` tinyint NOT NULL COMMENT '1-租客 2-房东 3-管理员',
`status` tinyint NOT NULL DEFAULT '1' COMMENT '1-正常 2-冻结',
`credit_score` int DEFAULT '100',
`create_time` datetime NOT NULL,
`update_time` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_username` (`username`),
UNIQUE KEY `uk_phone` (`phone`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
- 租房订单表(rental_order)
sql复制CREATE TABLE `rental_order` (
`id` bigint NOT NULL AUTO_INCREMENT,
`order_no` varchar(32) NOT NULL,
`house_id` bigint NOT NULL,
`tenant_id` bigint NOT NULL,
`landlord_id` bigint NOT NULL,
`monthly_rent` decimal(10,2) NOT NULL,
`deposit` decimal(10,2) NOT NULL,
`total_amount` decimal(10,2) NOT NULL,
`lease_start` date NOT NULL,
`lease_end` date NOT NULL,
`status` tinyint NOT NULL COMMENT '1-待支付 2-已支付 3-已签约 4-已入住 5-已完成 6-已取消',
`payment_time` datetime DEFAULT NULL,
`sign_time` datetime DEFAULT NULL,
`check_in_time` datetime DEFAULT NULL,
`complete_time` datetime DEFAULT NULL,
`cancel_time` datetime DEFAULT NULL,
`cancel_reason` varchar(255) DEFAULT NULL,
`create_time` datetime NOT NULL,
`update_time` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_order_no` (`order_no`),
KEY `idx_house` (`house_id`),
KEY `idx_tenant` (`tenant_id`),
KEY `idx_landlord` (`landlord_id`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
4.2 索引优化策略
- 复合索引设计
sql复制-- 房源搜索常用条件组合索引
ALTER TABLE `house` ADD INDEX `idx_search` (`district`, `price`, `room_count`);
-- 订单查询常用条件组合索引
ALTER TABLE `rental_order` ADD INDEX `idx_query` (`tenant_id`, `status`, `create_time`);
- 慢查询优化案例
sql复制-- 优化前
SELECT * FROM house WHERE status = 1 ORDER BY create_time DESC;
-- 优化后
SELECT * FROM house FORCE INDEX(idx_status) WHERE status = 1 ORDER BY create_time DESC LIMIT 20;
5. 系统安全设计
5.1 认证与授权
JWT认证流程实现:
java复制public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
claims.put("username", userDetails.getUsername());
claims.put("role", userDetails.getAuthorities());
return Jwts.builder()
.setClaims(claims)
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS512, SECRET_KEY)
.compact();
}
public boolean validateToken(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
5.2 数据安全
敏感数据加密处理:
java复制// 身份证号加密存储
public String encryptIdCard(String idCard) {
return AESUtil.encrypt(idCard, SECRET_KEY);
}
// 数据库字段脱敏显示
public String maskPhone(String phone) {
if (StringUtils.isBlank(phone) || phone.length() < 7) {
return phone;
}
return phone.substring(0, 3) + "****" + phone.substring(7);
}
SQL注入防护:
java复制// 使用预编译语句
@Query(value = "SELECT * FROM users WHERE username = ?1 AND status = 1", nativeQuery = true)
User findByUsername(String username);
// MyBatis参数绑定
@Select("SELECT * FROM house WHERE id = #{id} AND status = 1")
House selectById(@Param("id") Long id);
6. 部署与运维
6.1 生产环境部署
推荐服务器配置:
- 开发环境:4核CPU/8GB内存/100GB SSD
- 测试环境:4核CPU/16GB内存/200GB SSD
- 生产环境:8核CPU/32GB内存/500GB SSD(可扩展)
Nginx配置示例:
nginx复制server {
listen 80;
server_name yourdomain.com;
location / {
root /var/www/html;
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
6.2 监控与日志
SpringBoot Actuator配置:
properties复制management.endpoints.web.exposure.include=health,info,metrics
management.endpoint.health.show-details=always
management.metrics.tags.application=${spring.application.name}
ELK日志收集方案:
- Filebeat收集日志
- Logstash解析处理
- Elasticsearch存储索引
- Kibana可视化展示
7. 常见问题与解决方案
7.1 开发阶段问题
问题1:MyBatis查询结果映射失败
- 症状:返回的实体类部分字段为null
- 原因:数据库字段名与实体类属性名不一致
- 解决:添加@Results注解或配置mapUnderscoreToCamelCase
java复制@Results({
@Result(property = "createTime", column = "create_time"),
@Result(property = "updateTime", column = "update_time")
})
@Select("SELECT * FROM users WHERE id = #{id}")
User selectById(Long id);
问题2:事务不回滚
- 症状:抛出异常后数据库操作未回滚
- 原因:异常类型非RuntimeException或未开启事务
- 解决:添加@Transactional注解并指定rollbackFor
java复制@Transactional(rollbackFor = Exception.class)
public void createOrder(OrderDTO dto) throws BusinessException {
// 业务逻辑
}
7.2 生产环境问题
问题1:数据库连接池耗尽
- 症状:系统变慢并出现"Timeout waiting for connection"错误
- 解决方案:
- 调整连接池参数
properties复制spring.datasource.hikari.maximum-pool-size=20 spring.datasource.hikari.connection-timeout=30000- 添加连接泄露检测
properties复制spring.datasource.hikari.leak-detection-threshold=60000
问题2:缓存雪崩
- 症状:大量缓存同时失效导致数据库压力激增
- 解决方案:
- 设置差异化过期时间
java复制@Cacheable(value = "houses", key = "#id", unless = "#result == null") public House getById(Long id) { return houseMapper.selectById(id); } @CacheEvict(value = "houses", key = "#id") public void updateHouse(House house) { houseMapper.updateById(house); }- 使用多级缓存策略
8. 性能优化实践
8.1 数据库优化
- 查询优化案例
sql复制-- 优化前
SELECT * FROM house WHERE status = 1 AND price > 3000 ORDER BY create_time DESC;
-- 优化后
SELECT id, title, price, area, cover_image FROM house
WHERE status = 1 AND price > 3000
ORDER BY create_time DESC
LIMIT 20;
- 批量插入优化
java复制@Transactional
public void batchInsertHouses(List<House> houses) {
SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);
HouseMapper mapper = session.getMapper(HouseMapper.class);
for (House house : houses) {
mapper.insert(house);
}
session.commit();
session.close();
}
8.2 缓存策略
Redis缓存配置:
java复制@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 使用Jackson2JsonRedisSerializer序列化
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(om);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
}
缓存使用示例:
java复制@Service
public class HouseServiceImpl implements HouseService {
@Cacheable(value = "house", key = "#id", unless = "#result == null")
@Override
public House getById(Long id) {
return houseMapper.selectById(id);
}
@CacheEvict(value = "house", key = "#house.id")
@Override
public void update(House house) {
houseMapper.updateById(house);
}
}
9. 扩展功能设计
9.1 智能推荐系统
基于用户行为的协同过滤算法实现:
java复制public List<House> recommendHouses(Long userId) {
// 1. 获取用户历史行为
List<UserBehavior> behaviors = behaviorMapper.selectByUser(userId);
// 2. 计算相似用户
Map<Long, Double> similarUsers = findSimilarUsers(userId);
// 3. 获取推荐房源ID列表
Set<Long> recommendIds = calculateRecommendations(similarUsers);
// 4. 查询房源详情
return houseMapper.selectBatchIds(recommendIds);
}
private Map<Long, Double> findSimilarUsers(Long userId) {
// 使用余弦相似度计算用户相似度
// 实现略...
}
9.2 虚拟看房功能
基于Three.js的3D看房实现要点:
- 使用全景相机拍摄房源照片
- 使用PTGui等软件合成全景图
- 前端使用Three.js加载展示
核心代码结构:
javascript复制// 初始化场景
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
// 加载全景纹理
const textureLoader = new THREE.TextureLoader();
const panoramaTexture = textureLoader.load('panorama.jpg');
// 创建球体几何体
const geometry = new THREE.SphereGeometry(500, 60, 40);
geometry.scale(-1, 1, 1); // 反转几何体
// 创建材质并应用到网格
const material = new THREE.MeshBasicMaterial({ map: panoramaTexture });
const mesh = new THREE.OrbitControls(camera, renderer.domElement);
10. 项目总结与展望
在开发这个租房系统的过程中,我积累了一些值得分享的经验:
-
领域建模要准确:初期对租赁流程的状态设计不够完善,导致后期频繁修改。建议在需求分析阶段就绘制完整的状态转换图。
-
缓存策略要灵活:不同类型的业务数据需要不同的缓存过期策略,例如房源信息可以缓存较长时间,而订单状态应该实时性更高。
-
监控要尽早接入:等到系统上线后再加监控往往为时已晚,应该在开发阶段就集成APM工具。
对于未来的改进方向,我认为可以在以下方面继续优化:
-
引入机器学习算法,实现更精准的房源推荐和租金预测
-
增加区块链技术应用,确保合同不可篡改
-
开发移动端APP,提升用户体验
这个项目从开题到实现历时三个月,期间遇到了各种技术挑战,但最终都找到了解决方案。希望这个系统能够真正帮助到有租房需求的人们,让租房过程变得更加简单透明。