1. 项目背景与需求分析
大学生租房一直是个令人头疼的问题。记得我大三那年找房子,跑了十几家中介,看了不下二十套房源,最后签合同时还被坑了一笔"服务费"。正是这段经历让我萌生了开发一个专门服务大学生的房屋租赁系统的想法。
传统租房市场存在几个痛点:
- 信息不对称:虚假房源占比高达40%,学生很难辨别真伪
- 流程繁琐:看房→签约→付款需要多次线下往返
- 安全隐患:约30%的学生租房者遭遇过押金纠纷
我们的系统主要解决三大核心需求:
- 真实性保障:通过学生身份认证+房东资质审核的双重验证机制
- 流程线上化:从找房到签约的全流程电子化操作
- 资金监管:引入第三方支付平台进行租金托管
2. 技术选型与架构设计
2.1 为什么选择Spring Boot
Spring Boot的自动配置特性让我们节省了约60%的初始配置时间。通过spring-boot-starter-web模块,我们快速集成了Tomcat和Spring MVC。特别值得一提的是它的"约定优于配置"理念,比如:
- 默认内嵌Tomcat容器
- 自动扫描@Component注解
- 预置的application.properties配置
java复制@SpringBootApplication
public class RentalApplication {
public static void main(String[] args) {
SpringApplication.run(RentalApplication.class, args);
}
}
2.2 数据库设计要点
我们采用MySQL 5.7,主要考虑其事务支持完善且社区活跃。核心表结构设计:
sql复制CREATE TABLE `house` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`title` varchar(100) NOT NULL,
`price` decimal(10,2) NOT NULL,
`area` int(11) NOT NULL COMMENT '面积',
`address` varchar(200) NOT NULL,
`status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '0-待租 1-已租',
`student_id` bigint(20) DEFAULT NULL COMMENT '租客ID',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
注意:所有金额字段必须使用decimal类型,float/double会出现精度丢失问题
2.3 安全方案设计
采用Spring Security + JWT的组合方案:
- 登录时生成JWT token
- 前端将token存入localStorage
- 每次请求携带token头
- 后端通过Filter校验token有效性
java复制@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilter(new JwtAuthenticationFilter(authenticationManager()))
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
3. 核心功能实现细节
3.1 房源搜索优化
采用Elasticsearch实现全文检索,关键优化点:
- 建立房源索引时对地址进行分词
- 支持价格区间过滤
- 按距离排序(需存储经纬度)
java复制public List<House> search(String keyword, BigDecimal minPrice,
BigDecimal maxPrice, String location) {
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
if(StringUtils.isNotBlank(keyword)){
queryBuilder.withQuery(QueryBuilders.multiMatchQuery(keyword, "title","address"));
}
if(minPrice != null && maxPrice != null){
queryBuilder.withFilter(QueryBuilders.rangeQuery("price")
.gte(minPrice).lte(maxPrice));
}
return elasticsearchTemplate.queryForList(queryBuilder.build(), House.class);
}
3.2 在线签约流程
电子合同签署流程:
- 房东发起合同(模板填充)
- 学生确认条款
- 双方短信验证码确认
- 生成PDF合同并存储到OSS
- 触发租金支付流程
java复制public Contract signContract(Long houseId, Long studentId) {
// 1. 验证房源状态
House house = houseRepository.findById(houseId)
.orElseThrow(() -> new BusinessException("房源不存在"));
if(house.getStatus() != 0){
throw new BusinessException("房源已出租");
}
// 2. 生成合同
Contract contract = new Contract();
contract.setTemplateId(1L);
contract.setLandlordId(house.getUserId());
contract.setStudentId(studentId);
contract.setHouseId(houseId);
contract.setStatus(0);
// 3. 保存到数据库
return contractRepository.save(contract);
}
3.3 支付系统对接
我们对接了支付宝的当面付接口,关键处理逻辑:
- 创建支付订单时冻结房源
- 支付成功回调后修改房源状态
- 15分钟未支付自动取消订单
java复制@Transactional
public String createPayment(Long contractId) {
Contract contract = getValidContract(contractId);
AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest();
request.setBizContent("{" +
"\"out_trade_no\":\"" + contractId + "\"," +
"\"total_amount\":" + contract.getTotalAmount() + "," +
"\"subject\":\"房租支付\"" +
"}");
// 设置支付超时时间
request.setNotifyUrl(config.getNotifyUrl());
AlipayTradePrecreateResponse response = alipayClient.execute(request);
if(response.isSuccess()){
return response.getQrCode();
} else {
throw new BusinessException("支付创建失败");
}
}
4. 性能优化实践
4.1 缓存策略设计
采用多级缓存方案:
- 本地缓存(Caffeine):存储热点房源数据,TTL=5分钟
- Redis缓存:存储全量房源基本信息,TTL=1小时
- 数据库:最终数据源
java复制@Cacheable(value = "house", key = "#id")
public House getHouseById(Long id) {
// 先查Redis
String key = "house:" + id;
House house = redisTemplate.opsForValue().get(key);
if(house == null){
// 查数据库
house = houseRepository.findById(id).orElse(null);
if(house != null){
redisTemplate.opsForValue().set(key, house, 1, TimeUnit.HOURS);
}
}
return house;
}
4.2 数据库分表方案
当房源数据超过50万时,我们实施了分表策略:
- 按城市ID分表(house_1, house_2,...)
- 使用Sharding-JDBC中间件
- 查询时自动路由到对应表
yaml复制spring:
shardingsphere:
datasource:
names: ds0
sharding:
tables:
house:
actual-data-nodes: ds0.house_$->{1..10}
table-strategy:
inline:
sharding-column: city_id
algorithm-expression: house_$->{city_id % 10 + 1}
4.3 接口性能监控
通过Spring Boot Actuator + Prometheus + Grafana搭建监控系统:
- 暴露/metrics端点
- Prometheus定时抓取
- Grafana配置看板
关键监控指标:
- 接口响应时间P99
- 数据库查询耗时
- JVM内存使用率
- 缓存命中率
5. 踩坑经验分享
5.1 事务失效场景
我们遇到过几个典型的事务问题:
- 自调用问题:同类中方法A调用方法B,B的事务注解失效
- 解决:将方法B抽到单独Service
- 异常被捕获:try-catch吞掉了异常
- 解决:catch中throw new RuntimeException
- 非public方法:事务注解不生效
- 解决:改为public方法
java复制// 错误示例
public void createOrder(OrderDTO dto) {
try {
validate(dto); // 内部有@Transactional
saveOrder(dto);
} catch (Exception e) {
log.error("创建订单失败", e);
}
}
// 正确写法
@Transactional
public void createOrder(OrderDTO dto) {
validate(dto);
saveOrder(dto);
}
5.2 分布式锁实践
在房源状态变更时,我们最初用synchronized导致集群环境下失效。最终方案:
- 使用Redis的SETNX命令
- 设置随机value和过期时间
- 用Lua脚本保证原子性
java复制public boolean tryLock(String key, long expireSeconds) {
String value = UUID.randomUUID().toString();
Boolean result = redisTemplate.opsForValue()
.setIfAbsent(key, value, expireSeconds, TimeUnit.SECONDS);
if(Boolean.TRUE.equals(result)){
// 成功获取锁
lockHolder.set(value);
return true;
}
return false;
}
public void unlock(String key) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('del', KEYS[1]) " +
"else return 0 end";
redisTemplate.execute(new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(key),
lockHolder.get());
}
5.3 图片上传优化
初期直接上传到应用服务器导致:
- 磁盘空间不足
- 访问速度慢
- 备份困难
最终方案:
- 前端直传OSS(阿里云对象存储)
- 后端生成临时访问凭证(STS)
- 数据库只存储文件路径
java复制public OssPolicyResult policy() {
OssPolicyResult result = new OssPolicyResult();
// 设置策略过期时间
long expireTime = 30;
long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
Date expiration = new Date(expireEndTime);
// 生成策略
PolicyConditions policyConds = new PolicyConditions();
policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, "house/");
String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
byte[] binaryData = postPolicy.getBytes("utf-8");
String encodedPolicy = BinaryUtil.toBase64String(binaryData);
// 生成签名
String postSignature = ossClient.calculatePostSignature(postPolicy);
result.setAccessKeyId(ossClient.getCredentialsProvider().getCredentials().getAccessKeyId());
result.setPolicy(encodedPolicy);
result.setSignature(postSignature);
result.setDir("house/");
result.setHost("https://" + bucketName + "." + endpoint);
return result;
}
6. 扩展功能展望
虽然基础功能已经完善,但还有几个值得优化的方向:
-
智能推荐系统
- 基于用户浏览历史推荐相似房源
- 使用协同过滤算法
- 需要收集更多用户行为数据
-
虚拟看房功能
- 接入3D全景展示技术
- 支持VR设备浏览
- 需要专业拍摄设备支持
-
信用租房体系
- 对接学信网验证学生身份
- 建立信用评分模型
- 信用良好的学生可免押金
这个项目从最初的想法到最终上线历时8个月,期间经历了3次大的架构调整。最大的收获是:不要过早优化,但一定要为扩展留好接口。比如我们早期预留的分表字段,在数据量增长后确实派上了大用场。