资讯详情

Vue+SpringBoot房屋租赁系统开发实战

📅 2026/9/12 14:42:04 | 华诺云谱 👁 阅读
Vue+SpringBoot房屋租赁系统开发实战
1. 项目背景与需求分析在当今数字化时代房屋租赁行业正经历着从传统线下模式向线上平台的快速转型。一个高效、易用的房屋租赁系统能够为房东和租客搭建便捷的沟通桥梁解决信息不对称、交易流程繁琐等痛点。这套基于VueSpringBoot的房屋租赁系统主要解决以下几类核心需求房源信息管理房东需要便捷地上传、修改和删除房源信息包括房屋图片、价格、面积、位置等关键信息租客搜索与筛选租客希望能够根据位置、价格区间、房屋类型等条件快速找到合适房源在线预约看房减少电话沟通成本提供标准化的预约流程合同管理电子合同的生成、签署与存档功能支付与账单租金支付、押金管理、水电费记录等财务功能从技术选型角度看Vue.js作为前端框架能够提供流畅的用户体验和响应式界面而SpringBoot则以其快速开发特性和丰富的生态系统成为后端服务的理想选择。这种前后端分离的架构也便于团队协作和系统扩展。2. 技术架构设计2.1 前端技术栈前端采用Vue 3组合式API作为开发基础主要技术组件包括// package.json核心依赖示例 { dependencies: { vue: ^3.2.47, vue-router: ^4.1.6, pinia: ^2.0.33, axios: ^1.3.4, element-plus: ^2.3.3, vue-i18n: ^9.2.2 } }路由设计采用Vue Router实现多页面导航关键路由配置如下const routes [ { path: /, component: HomeView, meta: { title: 首页 } }, { path: /list, component: PropertyListView, meta: { title: 房源列表 } }, { path: /detail/:id, component: PropertyDetailView, meta: { title: 房源详情 }, props: true } ]2.2 后端技术栈后端基于SpringBoot 2.7.x构建主要技术组件包括核心框架Spring Boot 2.7.11 Spring MVC数据持久层MyBatis-Plus 3.5.3.1 PageHelper数据库MySQL 8.0 Redis缓存安全认证Spring Security JWT文件存储阿里云OSS对象存储消息队列RabbitMQ异步处理典型的SpringBoot应用启动类配置SpringBootApplication MapperScan(com.rental.mapper) EnableTransactionManagement public class RentalApplication { public static void main(String[] args) { SpringApplication.run(RentalApplication.class, args); } }2.3 前后端交互设计采用RESTful API规范设计接口使用Swagger生成API文档。关键接口示例RestController RequestMapping(/api/property) Api(tags 房源管理API) public class PropertyController { Autowired private PropertyService propertyService; GetMapping(/list) ApiOperation(分页查询房源列表) public ResultPageInfoPropertyVO listProperties( RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize, PropertyQueryDTO queryDTO) { PageHelper.startPage(pageNum, pageSize); ListPropertyVO list propertyService.queryProperties(queryDTO); return Result.success(PageInfo.of(list)); } }3. 核心功能实现3.1 房源管理模块房源管理是系统的核心功能涉及以下关键实现点数据库表设计CREATE TABLE property ( id bigint NOT NULL AUTO_INCREMENT, title varchar(100) NOT NULL COMMENT 房源标题, address varchar(200) NOT NULL COMMENT 详细地址, price decimal(10,2) NOT NULL COMMENT 月租金, area decimal(6,2) NOT NULL COMMENT 面积(㎡), room_type tinyint NOT NULL COMMENT 户型:1-一室,2-两室,..., status tinyint NOT NULL DEFAULT 0 COMMENT 状态:0-待租,1-已租,2-下架, landlord_id bigint NOT NULL COMMENT 房东ID, 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_landlord (landlord_id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT房源信息表;图片上传处理采用阿里云OSS存储前端实现多图上传组件template el-upload action/api/upload/image list-typepicture-card :file-listfileList :on-successhandleSuccess :before-uploadbeforeUpload el-iconPlus //el-icon /el-upload /template script setup const fileList ref([]) const beforeUpload (file) { const isJPG file.type image/jpeg const isLt5M file.size / 1024 / 1024 5 if (!isJPG) { ElMessage.error(只能上传JPG格式图片!) } if (!isLt5M) { ElMessage.error(图片大小不能超过5MB!) } return isJPG isLt5M } /script3.2 搜索与筛选功能房源搜索采用Elasticsearch实现全文检索结合前端筛选组件template div classfilter-container el-select v-modelfilters.region placeholder选择区域 el-option v-foritem in regionOptions :keyitem.value :labelitem.label :valueitem.value /el-option /el-select el-slider v-modelfilters.priceRange range :min500 :max20000 :step100 /el-slider el-button typeprimary clickhandleSearch搜索/el-button /div /template script setup const filters reactive({ region: , priceRange: [1000, 5000], roomType: [] }) const handleSearch async () { const params { region: filters.region, minPrice: filters.priceRange[0], maxPrice: filters.priceRange[1], roomTypes: filters.roomType.join(,) } const { data } await axios.get(/api/property/search, { params }) propertyList.value data } /script后端搜索接口实现Service public class PropertySearchServiceImpl implements PropertySearchService { Autowired private ElasticsearchRestTemplate elasticsearchTemplate; Override public PagePropertyDocument search(PropertySearchDTO searchDTO) { NativeSearchQueryBuilder queryBuilder new NativeSearchQueryBuilder(); // 构建区域查询 if (StringUtils.isNotBlank(searchDTO.getRegion())) { queryBuilder.withQuery(QueryBuilders.termQuery(region, searchDTO.getRegion())); } // 构建价格范围查询 if (searchDTO.getMinPrice() ! null searchDTO.getMaxPrice() ! null) { queryBuilder.withQuery(QueryBuilders.rangeQuery(price) .gte(searchDTO.getMinPrice()) .lte(searchDTO.getMaxPrice())); } // 分页设置 queryBuilder.withPageable(PageRequest.of( searchDTO.getPageNum() - 1, searchDTO.getPageSize())); return elasticsearchTemplate.search(queryBuilder.build(), PropertyDocument.class); } }4. 系统安全与性能优化4.1 安全防护措施JWT认证实现Component public class JwtTokenProvider { Value(${app.jwt.secret}) private String jwtSecret; Value(${app.jwt.expiration}) private int jwtExpiration; public String generateToken(UserDetails userDetails) { Date now new Date(); Date expiryDate new Date(now.getTime() jwtExpiration * 1000L); return Jwts.builder() .setSubject(userDetails.getUsername()) .setIssuedAt(now) .setExpiration(expiryDate) .signWith(SignatureAlgorithm.HS512, jwtSecret) .compact(); } public String getUsernameFromToken(String token) { return Jwts.parser() .setSigningKey(jwtSecret) .parseClaimsJws(token) .getBody() .getSubject(); } }Spring Security配置Configuration EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { Autowired private JwtTokenProvider tokenProvider; Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers(/api/auth/**).permitAll() .antMatchers(/api/property/list).permitAll() .anyRequest().authenticated() .and() .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); } private JwtAuthenticationFilter jwtAuthenticationFilter() { return new JwtAuthenticationFilter(tokenProvider); } }4.2 性能优化实践Redis缓存应用Service public class PropertyServiceImpl implements PropertyService { Autowired private PropertyMapper propertyMapper; Autowired private RedisTemplateString, Object redisTemplate; Override Cacheable(value property, key #id) public PropertyVO getPropertyById(Long id) { return propertyMapper.selectDetailById(id); } Override CacheEvict(value property, key #property.id) public void updateProperty(Property property) { propertyMapper.updateById(property); } }数据库查询优化使用MyBatis-Plus的QueryWrapper构建类型安全的查询条件对高频查询字段建立合适的索引复杂查询使用SelectProvider实现动态SQL分页查询使用PageHelper插件Override public ListPropertyVO queryProperties(PropertyQueryDTO queryDTO) { QueryWrapperProperty wrapper new QueryWrapper(); if (StringUtils.isNotBlank(queryDTO.getKeyword())) { wrapper.like(title, queryDTO.getKeyword()) .or().like(address, queryDTO.getKeyword()); } if (queryDTO.getMinPrice() ! null) { wrapper.ge(price, queryDTO.getMinPrice()); } if (queryDTO.getMaxPrice() ! null) { wrapper.le(price, queryDTO.getMaxPrice()); } wrapper.orderByDesc(create_time); return propertyMapper.selectList(wrapper).stream() .map(this::convertToVO) .collect(Collectors.toList()); }5. 部署与运维方案5.1 前端部署使用Docker容器化部署Vue应用# 前端Dockerfile FROM node:16-alpine as builder WORKDIR /app COPY package*.json ./ RUN npm install COPY . . RUN npm run build FROM nginx:alpine COPY --frombuilder /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 CMD [nginx, -g, daemon off;]Nginx配置示例server { listen 80; server_name localhost; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } location /api { proxy_pass http://backend:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }5.2 后端部署SpringBoot应用Docker部署方案# 后端Dockerfile FROM openjdk:11-jre-slim VOLUME /tmp ARG JAR_FILEtarget/*.jar COPY ${JAR_FILE} app.jar ENTRYPOINT [java,-jar,/app.jar]使用Docker Compose编排服务version: 3.8 services: frontend: build: ./frontend ports: - 80:80 depends_on: - backend backend: build: ./backend ports: - 8080:8080 environment: - SPRING_PROFILES_ACTIVEprod - SPRING_DATASOURCE_URLjdbc:mysql://mysql:3306/rental?useSSLfalse - SPRING_DATASOURCE_USERNAMEroot - SPRING_DATASOURCE_PASSWORD123456 depends_on: - mysql - redis mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: 123456 MYSQL_DATABASE: rental volumes: - mysql_data:/var/lib/mysql ports: - 3306:3306 redis: image: redis:alpine ports: - 6379:6379 volumes: - redis_data:/data volumes: mysql_data: redis_data:5.3 监控与日志SpringBoot Actuator提供健康检查端点Prometheus Grafana监控系统指标ELK日志收集分析系统Sentry错误追踪平台Actuator配置示例management: endpoints: web: exposure: include: health,info,metrics,prometheus endpoint: health: show-details: always prometheus: enabled: true6. 开发经验与避坑指南6.1 跨域问题解决方案前后端分离开发中常见的跨域问题可通过以下方式解决后端配置Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOrigins(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }前端代理配置vue.config.jsmodule.exports { devServer: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true, pathRewrite: { ^/api: } } } } }6.2 表单验证最佳实践使用Vuelidate进行表单验证template el-form :modelform :rulesrules refformRef el-form-item label手机号 propphone el-input v-modelform.phone/el-input /el-form-item /el-form /template script setup import { reactive, ref } from vue import { useVuelidate } from vuelidate/core import { required, email, minLength } from vuelidate/validators const form reactive({ phone: , email: }) const rules { phone: { required, minLength: minLength(11) }, email: { required, email } } const v$ useVuelidate(rules, form) /script6.3 大文件上传处理采用分片上传方案处理大文件如房源视频// 前端分片上传实现 async function uploadFile(file) { const chunkSize 5 * 1024 * 1024 // 5MB const chunks Math.ceil(file.size / chunkSize) const fileMd5 await calculateFileMD5(file) for (let i 0; i chunks; i) { const start i * chunkSize const end Math.min(file.size, start chunkSize) const chunk file.slice(start, end) const formData new FormData() formData.append(file, chunk) formData.append(chunkNumber, i) formData.append(totalChunks, chunks) formData.append(identifier, fileMd5) await axios.post(/api/upload/chunk, formData, { headers: { Content-Type: multipart/form-data } }) } // 通知服务器合并分片 await axios.post(/api/upload/merge, { filename: file.name, identifier: fileMd5, totalChunks: chunks }) }后端分片上传处理RestController RequestMapping(/api/upload) public class FileUploadController { PostMapping(/chunk) public Result uploadChunk( RequestParam(file) MultipartFile file, RequestParam Integer chunkNumber, RequestParam Integer totalChunks, RequestParam String identifier) { String tempDir System.getProperty(java.io.tmpdir) /upload/ identifier; File dir new File(tempDir); if (!dir.exists()) { dir.mkdirs(); } File chunkFile new File(dir, chunkNumber .part); try { file.transferTo(chunkFile); return Result.success(); } catch (IOException e) { return Result.fail(分片上传失败); } } PostMapping(/merge) public Result mergeChunks( RequestBody MergeFileDTO mergeDTO) throws IOException { String tempDir System.getProperty(java.io.tmpdir) /upload/ mergeDTO.getIdentifier(); File dir new File(tempDir); if (!dir.exists()) { return Result.fail(分片不存在); } File[] chunks dir.listFiles(); if (chunks null || chunks.length ! mergeDTO.getTotalChunks()) { return Result.fail(分片数量不符); } // 合并文件逻辑 String filename mergeDTO.getFilename(); File destFile new File(/data/upload/ filename); try (FileOutputStream fos new FileOutputStream(destFile, true)) { for (int i 0; i mergeDTO.getTotalChunks(); i) { File chunkFile new File(dir, i .part); Files.copy(chunkFile.toPath(), fos); chunkFile.delete(); } } dir.delete(); return Result.success(filename); } }6.4 地图集成方案使用腾讯地图实现房源位置展示template div idmap-container stylewidth:100%; height:400px;/div /template script setup import { onMounted } from vue const initMap () { const center new TMap.LatLng(39.908802, 116.397502) const map new TMap.Map(document.getElementById(map-container), { center: center, zoom: 12 }) const marker new TMap.MultiMarker({ map: map, geometries: [{ position: center, id: 1, properties: { title: 房源位置 } }] }) } onMounted(() { const script document.createElement(script) script.src https://map.qq.com/api/gljs?v1.expkeyYOUR_KEY script.onload initMap document.head.appendChild(script) }) /script7. 扩展功能与未来优化方向7.1 即时通讯功能集成WebSocket实现房东与租客的实时沟通Configuration EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { Override public void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker(/topic); config.setApplicationDestinationPrefixes(/app); } Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint(/ws) .setAllowedOrigins(*) .withSockJS(); } }前端Stomp客户端实现import { Stomp } from stomp/stompjs const client Stomp.client(ws://localhost:8080/ws) client.connect({}, () { client.subscribe(/topic/messages, (message) { console.log(收到消息:, JSON.parse(message.body)) }) }) function sendMessage(content, receiverId) { client.send(/app/chat, {}, JSON.stringify({ content, receiverId, senderId: currentUser.value.id })) }7.2 智能推荐算法基于用户行为的协同过滤推荐Service public class RecommendationService { Autowired private UserBehaviorMapper behaviorMapper; public ListPropertyVO recommendProperties(Long userId) { // 1. 获取用户历史行为数据 ListUserBehavior behaviors behaviorMapper.selectByUserId(userId); // 2. 找出相似用户 SetLong similarUserIds findSimilarUsers(behaviors); // 3. 获取相似用户喜欢的房源 ListLong propertyIds behaviorMapper.selectTopPropertiesByUsers( similarUserIds, 10); // 4. 排除用户已看过的房源 return propertyMapper.selectByIds(propertyIds).stream() .filter(p - !behaviors.stream() .anyMatch(b - b.getPropertyId().equals(p.getId()))) .collect(Collectors.toList()); } private SetLong findSimilarUsers(ListUserBehavior behaviors) { // 实现相似度计算逻辑 return Collections.emptySet(); } }7.3 微服务架构演进随着业务增长可考虑将单体应用拆分为微服务用户服务处理用户注册、登录、个人信息管理房源服务房源CRUD、搜索、推荐订单服务预约、合同、支付流程消息服务站内信、通知、聊天评价服务租客评价、房东回复使用Spring Cloud Alibaba实现服务治理// 房源服务Feign客户端示例 FeignClient(name user-service, path /api/user) public interface UserServiceClient { GetMapping(/{id}) ResultUserVO getUserById(PathVariable Long id); } // 使用Nacos作为注册中心 spring: cloud: nacos: discovery: server-addr: 127.0.0.1:88487.4 移动端适配方案响应式设计使用Element Plus的布局组件实现响应式PWA支持通过Vue PWA插件实现渐进式Web应用Uniapp跨端开发一套代码同时生成小程序和AppPWA配置示例vue.config.jsmodule.exports { pwa: { name: 房屋租赁系统, themeColor: #4DBA87, msTileColor: #000000, appleMobileWebAppCapable: yes, appleMobileWebAppStatusBarStyle: black, workboxPluginMode: GenerateSW, workboxOptions: { skipWaiting: true, clientsClaim: true, exclude: [/\.map$/, /_redirects/] } } }
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。