1. 房屋租赁系统概述:为什么选择SpringBoot+Vue?
房屋租赁管理系统是连接房东与租客的数字化桥梁,核心要解决房源信息管理、租约跟踪、费用结算三大痛点。传统PHP或ASP.NET方案在应对高并发查询和复杂业务逻辑时往往力不从心,这正是我们选择SpringBoot+Vue技术栈的根本原因。
去年我为某长租公寓平台做架构升级时,原系统在日均5000次房源搜索请求下平均响应时间超过3秒。改用SpringBoot重构后,配合MyBatis二级缓存,同样硬件环境下响应时间降至300毫秒内。Vue前端采用懒加载和组件化设计,首屏加载速度提升40%,这就是现代化技术栈的实战价值。
2. 系统架构设计解析
2.1 技术栈选型依据
后端选择SpringBoot 2.7 + MyBatis-Plus组合,主要考虑:
- 自动配置:通过spring-boot-starter-data-jpa快速集成JPA
- 性能优化:MyBatis-Plus的Lambda查询比传统XML方式减少30%代码量
- 监控保障:Actuator端点暴露配合Prometheus实现实时监控
前端采用Vue3 + Element Plus方案,其优势在于:
- 组合式API使代码复用率提升50%以上
- Vite构建速度比Webpack快5-8倍
- TypeScript支持完善,大型项目维护成本更低
2.2 数据库设计要点
MySQL表设计遵循第三范式的同时做了适当反范式优化:
sql复制CREATE TABLE `house` (
`id` bigint NOT NULL AUTO_INCREMENT,
`title` varchar(100) COLLATE utf8mb4_bin NOT NULL COMMENT '房源标题',
`cover_img` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '封面图URL',
`monthly_rent` decimal(10,2) NOT NULL COMMENT '月租金',
`status` tinyint NOT NULL DEFAULT '1' COMMENT '1-待租 2-已租',
`address_json` json DEFAULT NULL COMMENT '结构化地址信息',
PRIMARY KEY (`id`),
KEY `idx_rent_status` (`monthly_rent`,`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
特别注意:
- 地址信息使用JSON类型存储省市区结构化数据
- 建立联合索引加速租金范围+状态查询
- 所有金额字段使用DECIMAL(10,2)避免精度丢失
3. 核心功能实现细节
3.1 房源多条件搜索实现
后端采用Specification动态查询构造器:
java复制public Page<House> searchHouses(SearchParams params) {
return houseRepository.findAll((root, query, cb) -> {
List<Predicate> predicates = new ArrayList<>();
if (StringUtils.isNotBlank(params.getKeyword())) {
predicates.add(cb.like(root.get("title"), "%"+params.getKeyword()+"%"));
}
if (params.getMinPrice() != null) {
predicates.add(cb.ge(root.get("monthlyRent"), params.getMinPrice()));
}
return cb.and(predicates.toArray(new Predicate[0]));
}, PageRequest.of(params.getPage(), params.getSize()));
}
前端使用axios拦截器实现防抖查询:
javascript复制const searchHouses = _.debounce(async (params) => {
try {
const res = await api.post('/houses/search', params)
houseList.value = res.data.content
} catch (err) {
ElMessage.error('搜索失败')
}
}, 500)
3.2 租约状态机设计
采用Spring StateMachine实现租约状态流转:
java复制@Configuration
@EnableStateMachineFactory
public class LeaseStateMachineConfig extends EnumStateMachineConfigurerAdapter<LeaseState, LeaseEvent> {
@Override
public void configure(StateMachineStateConfigurer<LeaseState, LeaseEvent> states) throws Exception {
states.withStates()
.initial(LeaseState.PENDING)
.states(EnumSet.allOf(LeaseState.class));
}
@Override
public void configure(StateMachineTransitionConfigurer<LeaseState, LeaseEvent> transitions) throws Exception {
transitions
.withExternal()
.source(LeaseState.PENDING)
.target(LeaseState.ACTIVE)
.event(LeaseEvent.SIGN)
.and()
.withExternal()
.source(LeaseState.ACTIVE)
.target(LeaseState.TERMINATED)
.event(LeaseEvent.TERMINATE);
}
}
4. 性能优化实战技巧
4.1 缓存策略设计
采用多级缓存架构:
- 本地Caffeine缓存高频访问的房源详情(TTL 5分钟)
- Redis缓存分页查询结果(TTL 1小时)
- MySQL查询使用覆盖索引避免回表
缓存更新策略:
java复制@CacheEvict(value = "house", key = "#houseId")
public void updateHouse(Long houseId, HouseUpdateDTO dto) {
// 先更新数据库
houseRepository.updateById(dto);
// 异步刷新Redis缓存
redisTemplate.convertAndSend("house.update", houseId);
}
4.2 前端性能优化方案
- 路由懒加载:
javascript复制const HouseList = () => import('./views/HouseList.vue')
- 图片懒加载:
html复制<img v-lazy="imgUrl" alt="房源图片">
- Web Worker处理大数据量导出:
javascript复制const worker = new Worker('./exportWorker.js')
worker.postMessage(exportData)
worker.onmessage = (e) => {
saveAs(e.data, 'houses.xlsx')
}
5. 安全防护措施
5.1 接口安全设计
- JWT令牌双校验机制:
java复制public class JwtFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) {
String token = request.getHeader("Authorization");
// 1. 校验签名有效性
if (!jwtUtil.validateToken(token)) {
throw new AccessDeniedException("Invalid token");
}
// 2. 校验Redis中的令牌状态
if (redisTemplate.opsForValue().get("token:"+token) == null) {
throw new AccessDeniedException("Token expired");
}
chain.doFilter(request, response);
}
}
- 敏感数据脱敏处理:
java复制@JsonSerialize(using = PhoneSerializer.class)
public String getLandlordPhone() {
return this.landlordPhone;
}
public class PhoneSerializer extends JsonSerializer<String> {
@Override
public void serialize(String value, JsonGenerator gen,
SerializerProvider provider) {
gen.writeString(value.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2"));
}
}
6. 部署与监控方案
6.1 Docker-Compose部署
yaml复制version: '3'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql
redis:
image: redis:6-alpine
ports:
- "6379:6379"
backend:
build: ./backend
ports:
- "8080:8080"
depends_on:
- mysql
- redis
frontend:
build: ./frontend
ports:
- "80:80"
6.2 Prometheus监控配置
application.yml片段:
yaml复制management:
endpoints:
web:
exposure:
include: health,info,prometheus
metrics:
export:
prometheus:
enabled: true
tags:
application: ${spring.application.name}
Grafana监控看板需重点关注:
- JVM内存使用率(堆/非堆)
- MySQL连接池活跃连接数
- API接口99线响应时间
7. 典型问题排查实录
7.1 N+1查询问题
错误现象:房源列表页产生100+SQL查询
解决方案:
- 使用@EntityGraph配置抓取策略
java复制@EntityGraph(attributePaths = {"landlord"})
Page<House> findAll(Pageable pageable);
- 或者使用JOIN FETCH语法:
xml复制<select id="findWithLandlord" resultMap="houseResultMap">
SELECT h.*, l.name as landlord_name
FROM house h LEFT JOIN landlord l ON h.landlord_id = l.id
</select>
7.2 Vue内存泄漏
常见于动态组件场景:
javascript复制// 错误写法
components: {
dynamicComp: () => import('./DynamicComp.vue')
}
// 正确写法
const DynamicComp = defineAsyncComponent(() =>
import('./DynamicComp.vue')
)
8. 开发环境配置建议
8.1 后端开发环境
- IDEA必备插件:
- Lombok Plugin
- MyBatisX
- Grep Console
- JPA Buddy
- 推荐配置:
properties复制# application-dev.yml
spring:
datasource:
hikari:
maximum-pool-size: 5
connection-timeout: 3000
jpa:
show-sql: true
properties:
hibernate:
format_sql: true
8.2 前端开发环境
- VSCode推荐扩展:
- Volar
- ESLint
- Prettier
- Vue VSCode Snippets
- 调试技巧:
javascript复制// vite.config.js
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
}
})
9. 测试策略设计
9.1 后端测试方案
- 单元测试覆盖Service层:
java复制@Test
void shouldUpdateHouseStatus() {
// given
House house = new House().setStatus(1);
when(houseRepository.findById(any())).thenReturn(Optional.of(house));
// when
houseService.updateStatus(1L, 2);
// then
assertThat(house.getStatus()).isEqualTo(2);
verify(houseRepository).save(house);
}
- 集成测试使用Testcontainers:
java复制@Testcontainers
@SpringBootTest
class HouseSearchIT {
@Container
static MySQLContainer<?> mysql = new MySQLContainer<>("mysql:8.0");
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", mysql::getJdbcUrl);
}
}
9.2 前端E2E测试
使用Cypress实现:
javascript复制describe('House Search', () => {
it('should filter by price range', () => {
cy.visit('/houses')
cy.get('[data-test="min-price"]').type('3000')
cy.get('[data-test="search-btn"]').click()
cy.get('[data-test="house-item"]').should('have.length.gt', 0)
})
})
10. 项目演进方向
- 智能化升级:
- 使用Elasticsearch实现语义化搜索
- 集成机器学习模型预测房源热度
- 微服务化改造:
- 拆分为房源服务、合约服务、支付服务
- 采用Spring Cloud Alibaba体系
- 移动端适配:
- 使用Uniapp开发跨平台应用
- 集成微信小程序SDK
关键建议:初期版本不要过度设计,但数据库Schema和API接口版本控制要做好扩展准备。我在实际项目中遇到的最多问题就是早期版本没有预留足够的扩展字段,导致后期需要频繁做数据迁移。
