Spring Boot与Vue 3实战:智慧停车场管理系统开发指南

发布时间:2026/7/19 8:33:01
Spring Boot与Vue 3实战:智慧停车场管理系统开发指南 在 Java Web 开发领域智慧停车场管理系统是一个典型的全栈实战项目它涵盖了用户管理、车位预约、费用计算、数据统计等核心业务场景。使用 Spring Boot 作为后端框架配合 Vue 3 构建现代化前端界面能够帮助开发者掌握前后端分离架构的设计思路和实现细节。本文将基于 Spring Boot 和 Vue 3 技术栈从零开始搭建一个功能完整的智慧停车场管理系统重点讲解环境准备、项目结构设计、关键模块实现和常见问题排查。1. 项目环境准备与依赖配置在开始编码前需要确保本地开发环境满足项目运行的基本要求。智慧停车场管理系统涉及前后端两个部分需要分别配置相应的开发工具和运行环境。1.1 后端开发环境要求后端基于 Spring Boot 3.x 构建需要 Java 17 或更高版本。建议使用 IntelliJ IDEA 作为开发 IDE同时安装 Lombok 插件以简化实体类编写。!-- pom.xml 中的关键依赖配置 -- dependencies !-- Spring Boot Starter Web -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- Spring Boot Starter Data JPA -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-jpa/artifactId /dependency !-- MySQL 驱动 -- dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency !-- Lombok -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency !-- Spring Boot Starter Validation -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency /dependencies数据库使用 MySQL 8.0需要在 application.yml 中配置数据源连接信息# application.yml spring: datasource: url: jdbc:mysql://localhost:3306/parking_management?useUnicodetruecharacterEncodingutf8zeroDateTimeBehaviorconvertToNulluseSSLtrueserverTimezoneGMT%2B8 username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver jpa: hibernate: ddl-auto: update show-sql: true properties: hibernate: dialect: org.hibernate.dialect.MySQL8Dialect format_sql: true server: port: 80801.2 前端开发环境配置前端使用 Vue 3 组合式 API 风格需要 Node.js 16 和 npm 8 环境。推荐使用 VS Code 作为开发工具并安装 Volar 扩展以提供更好的 Vue 3 开发体验。// package.json 关键依赖 { dependencies: { vue: ^3.3.0, vue-router: ^4.2.0, axios: ^1.4.0, element-plus: ^2.3.0, pinia: ^2.1.0 }, devDependencies: { vitejs/plugin-vue: ^4.2.0, vite: ^4.3.0, typescript: ^5.0.0, sass: ^1.62.0 } }前端项目使用 Vite 作为构建工具vite.config.js 基础配置如下// vite.config.js import { defineConfig } from vite import vue from vitejs/plugin-vue import { resolve } from path export default defineConfig({ plugins: [vue()], resolve: { alias: { : resolve(__dirname, src) } }, server: { port: 3000, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })1.3 数据库表结构设计停车场管理系统的核心数据模型包括用户表、停车场表、车位表、订单表等。以下是关键表的结构设计-- 停车场表 CREATE TABLE parking_lot ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL COMMENT 停车场名称, address VARCHAR(200) NOT NULL COMMENT 停车场地址, total_spaces INT NOT NULL COMMENT 总车位数量, available_spaces INT NOT NULL COMMENT 可用车位数量, price_per_hour DECIMAL(10,2) NOT NULL COMMENT 每小时价格, status TINYINT DEFAULT 1 COMMENT 状态0-关闭1-开放, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); -- 车位表 CREATE TABLE parking_space ( id BIGINT PRIMARY KEY AUTO_INCREMENT, parking_lot_id BIGINT NOT NULL COMMENT 所属停车场ID, space_number VARCHAR(20) NOT NULL COMMENT 车位编号, space_type TINYINT DEFAULT 1 COMMENT 车位类型1-小型车2-大型车, status TINYINT DEFAULT 0 COMMENT 状态0-空闲1-已预约2-使用中, FOREIGN KEY (parking_lot_id) REFERENCES parking_lot(id) ); -- 预约订单表 CREATE TABLE reservation_order ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL COMMENT 用户ID, parking_space_id BIGINT NOT NULL COMMENT 车位ID, start_time DATETIME NOT NULL COMMENT 预约开始时间, end_time DATETIME NOT NULL COMMENT 预约结束时间, total_amount DECIMAL(10,2) NOT NULL COMMENT 总金额, order_status TINYINT DEFAULT 0 COMMENT 订单状态0-待支付1-已支付2-已完成3-已取消, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (parking_space_id) REFERENCES parking_space(id) );2. 后端核心模块实现Spring Boot 后端采用经典的三层架构控制器层(Controller)、服务层(Service)、数据访问层(Repository)。下面重点讲解车位管理和订单处理两个核心模块的实现。2.1 车位管理模块设计车位管理模块负责停车场的车位信息维护、状态更新和查询功能。首先定义实体类和数据访问接口。// ParkingSpace 实体类 Entity Table(name parking_space) Data NoArgsConstructor AllArgsConstructor public class ParkingSpace { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; ManyToOne JoinColumn(name parking_lot_id) private ParkingLot parkingLot; Column(name space_number) private String spaceNumber; Column(name space_type) private Integer spaceType; // 1-小型车2-大型车 Column(name status) private Integer status; // 0-空闲1-已预约2-使用中 CreationTimestamp Column(name create_time) private LocalDateTime createTime; UpdateTimestamp Column(name update_time) private LocalDateTime updateTime; } // ParkingSpaceRepository 数据访问接口 Repository public interface ParkingSpaceRepository extends JpaRepositoryParkingSpace, Long { // 根据停车场ID和状态查询车位 ListParkingSpace findByParkingLotIdAndStatus(Long parkingLotId, Integer status); // 根据车位编号查询 OptionalParkingSpace findBySpaceNumber(String spaceNumber); // 统计某个停车场的空闲车位数量 Query(SELECT COUNT(ps) FROM ParkingSpace ps WHERE ps.parkingLot.id :parkingLotId AND ps.status 0) Long countAvailableSpacesByParkingLotId(Param(parkingLotId) Long parkingLotId); }服务层实现车位状态管理和业务逻辑处理// ParkingSpaceService 接口 public interface ParkingSpaceService { PageParkingSpaceVO getParkingSpacesByPage(ParkingSpaceQuery query); ParkingSpaceVO getParkingSpaceDetail(Long id); boolean updateParkingSpaceStatus(Long spaceId, Integer status); ListParkingSpaceVO getAvailableSpaces(Long parkingLotId); } // ParkingSpaceServiceImpl 实现类 Service Transactional Slf4j public class ParkingSpaceServiceImpl implements ParkingSpaceService { Autowired private ParkingSpaceRepository parkingSpaceRepository; Autowired private ParkingLotRepository parkingLotRepository; Override public PageParkingSpaceVO getParkingSpacesByPage(ParkingSpaceQuery query) { // 构建查询条件 SpecificationParkingSpace spec (root, criteriaQuery, criteriaBuilder) - { ListPredicate predicates new ArrayList(); if (query.getParkingLotId() ! null) { predicates.add(criteriaBuilder.equal(root.get(parkingLot).get(id), query.getParkingLotId())); } if (query.getStatus() ! null) { predicates.add(criteriaBuilder.equal(root.get(status), query.getStatus())); } if (StringUtils.hasText(query.getSpaceNumber())) { predicates.add(criteriaBuilder.like(root.get(spaceNumber), % query.getSpaceNumber() %)); } return criteriaBuilder.and(predicates.toArray(new Predicate[0])); }; Pageable pageable PageRequest.of(query.getPageNum() - 1, query.getPageSize(), Sort.by(Sort.Direction.DESC, createTime)); PageParkingSpace parkingSpacePage parkingSpaceRepository.findAll(spec, pageable); // 转换为VO对象 return parkingSpacePage.map(this::convertToVO); } Override public boolean updateParkingSpaceStatus(Long spaceId, Integer status) { try { ParkingSpace parkingSpace parkingSpaceRepository.findById(spaceId) .orElseThrow(() - new RuntimeException(车位不存在)); parkingSpace.setStatus(status); parkingSpace.setUpdateTime(LocalDateTime.now()); parkingSpaceRepository.save(parkingSpace); // 同步更新停车场的可用车位数量 updateParkingLotAvailableSpaces(parkingSpace.getParkingLot().getId()); return true; } catch (Exception e) { log.error(更新车位状态失败: spaceId{}, status{}, spaceId, status, e); return false; } } private void updateParkingLotAvailableSpaces(Long parkingLotId) { Long availableCount parkingSpaceRepository.countAvailableSpacesByParkingLotId(parkingLotId); ParkingLot parkingLot parkingLotRepository.findById(parkingLotId) .orElseThrow(() - new RuntimeException(停车场不存在)); parkingLot.setAvailableSpaces(availableCount.intValue()); parkingLotRepository.save(parkingLot); } private ParkingSpaceVO convertToVO(ParkingSpace parkingSpace) { ParkingSpaceVO vo new ParkingSpaceVO(); vo.setId(parkingSpace.getId()); vo.setSpaceNumber(parkingSpace.getSpaceNumber()); vo.setSpaceType(parkingSpace.getSpaceType()); vo.setStatus(parkingSpace.getStatus()); vo.setParkingLotName(parkingSpace.getParkingLot().getName()); vo.setCreateTime(parkingSpace.getCreateTime()); return vo; } }2.2 订单处理模块实现订单模块处理用户的预约、支付、取消等业务流程需要特别注意并发场景下的数据一致性。// ReservationOrder 实体类 Entity Table(name reservation_order) Data public class ReservationOrder { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; ManyToOne JoinColumn(name user_id) private User user; ManyToOne JoinColumn(name parking_space_id) private ParkingSpace parkingSpace; Column(name start_time) private LocalDateTime startTime; Column(name end_time) private LocalDateTime endTime; Column(name total_amount) private BigDecimal totalAmount; Column(name order_status) private Integer orderStatus; // 0-待支付1-已支付2-已完成3-已取消 CreationTimestamp Column(name create_time) private LocalDateTime createTime; } // OrderService 订单服务 Service Transactional Slf4j public class OrderService { Autowired private ReservationOrderRepository orderRepository; Autowired private ParkingSpaceService parkingSpaceService; Autowired private RedisTemplateString, Object redisTemplate; // 创建预约订单 public ReservationOrderVO createOrder(OrderCreateDTO createDTO) { // 检查车位是否可用 ParkingSpace parkingSpace checkParkingSpaceAvailable(createDTO.getParkingSpaceId()); // 计算费用 BigDecimal amount calculateAmount(createDTO.getStartTime(), createDTO.getEndTime(), parkingSpace.getParkingLot().getPricePerHour()); // 创建订单 ReservationOrder order new ReservationOrder(); order.setParkingSpace(parkingSpace); order.setStartTime(createDTO.getStartTime()); order.setEndTime(createDTO.getEndTime()); order.setTotalAmount(amount); order.setOrderStatus(0); // 待支付 ReservationOrder savedOrder orderRepository.save(order); // 更新车位状态为已预约 parkingSpaceService.updateParkingSpaceStatus(createDTO.getParkingSpaceId(), 1); return convertToVO(savedOrder); } // 支付订单 public boolean payOrder(Long orderId) { ReservationOrder order orderRepository.findById(orderId) .orElseThrow(() - new RuntimeException(订单不存在)); if (!order.getOrderStatus().equals(0)) { throw new RuntimeException(订单状态异常无法支付); } // 模拟支付流程 order.setOrderStatus(1); // 已支付 orderRepository.save(order); // 发送支付成功消息 sendPaymentSuccessMessage(order); return true; } // 取消订单 public boolean cancelOrder(Long orderId) { ReservationOrder order orderRepository.findById(orderId) .orElseThrow(() - new RuntimeException(订单不存在)); if (order.getOrderStatus().equals(2) || order.getOrderStatus().equals(3)) { throw new RuntimeException(订单无法取消); } order.setOrderStatus(3); // 已取消 orderRepository.save(order); // 释放车位 parkingSpaceService.updateParkingSpaceStatus(order.getParkingSpace().getId(), 0); return true; } private ParkingSpace checkParkingSpaceAvailable(Long spaceId) { ParkingSpace parkingSpace parkingSpaceRepository.findById(spaceId) .orElseThrow(() - new RuntimeException(车位不存在)); if (!parkingSpace.getStatus().equals(0)) { throw new RuntimeException(车位当前不可用); } return parkingSpace; } private BigDecimal calculateAmount(LocalDateTime startTime, LocalDateTime endTime, BigDecimal pricePerHour) { long hours Duration.between(startTime, endTime).toHours(); if (hours 1) { hours 1; // 不足1小时按1小时计算 } return pricePerHour.multiply(BigDecimal.valueOf(hours)); } }3. 前端界面开发与接口对接前端使用 Vue 3 组合式 API 和 Element Plus 组件库构建用户界面通过 Axios 与后端 API 进行数据交互。3.1 前端项目结构设计src/ ├── api/ # API接口封装 ├── components/ # 公共组件 ├── views/ # 页面组件 ├── router/ # 路由配置 ├── store/ # 状态管理 ├── utils/ # 工具函数 └── assets/ # 静态资源3.2 车位管理页面实现车位管理页面展示停车场车位状态支持按条件筛选和分页查询。template div classparking-space-management el-card template #header div classcard-header span车位管理/span el-button typeprimary clickhandleRefresh刷新/el-button /div /template !-- 查询条件 -- el-form :modelqueryParams inline el-form-item label停车场 el-select v-modelqueryParams.parkingLotId placeholder请选择停车场 el-option v-forlot in parkingLots :keylot.id :labellot.name :valuelot.id / /el-select /el-form-item el-form-item label车位状态 el-select v-modelqueryParams.status placeholder请选择状态 el-option label全部 value / el-option label空闲 :value0 / el-option label已预约 :value1 / el-option label使用中 :value2 / /el-select /el-form-item el-form-item el-button typeprimary clickhandleSearch查询/el-button el-button clickhandleReset重置/el-button /el-form-item /el-form !-- 车位列表 -- el-table :datatableData v-loadingloading el-table-column propspaceNumber label车位编号 width120 / el-table-column propparkingLotName label所属停车场 width200 / el-table-column propspaceType label车位类型 width100 template #defaultscope el-tag :typescope.row.spaceType 1 ? : warning {{ scope.row.spaceType 1 ? 小型车 : 大型车 }} /el-tag /template /el-table-column el-table-column propstatus label状态 width100 template #defaultscope el-tag :typegetStatusTagType(scope.row.status) {{ getStatusText(scope.row.status) }} /el-tag /template /el-table-column el-table-column propcreateTime label创建时间 width180 / el-table-column label操作 width150 template #defaultscope el-button sizesmall clickhandleView(scope.row)查看/el-button el-button sizesmall typedanger clickhandleUpdateStatus(scope.row) 更新状态 /el-button /template /el-table-column /el-table !-- 分页 -- div classpagination-container el-pagination v-model:current-pagequeryParams.pageNum v-model:page-sizequeryParams.pageSize :totaltotal :page-sizes[10, 20, 50, 100] layouttotal, sizes, prev, pager, next, jumper size-changehandleSizeChange current-changehandleCurrentChange / /div /el-card /div /template script setup import { ref, reactive, onMounted } from vue import { ElMessage, ElMessageBox } from element-plus import { getParkingSpaces, updateParkingSpaceStatus } from /api/parkingSpace import { getParkingLots } from /api/parkingLot const loading ref(false) const tableData ref([]) const parkingLots ref([]) const total ref(0) const queryParams reactive({ parkingLotId: , status: , spaceNumber: , pageNum: 1, pageSize: 10 }) // 获取车位列表 const fetchParkingSpaces async () { loading.value true try { const response await getParkingSpaces(queryParams) tableData.value response.data.list total.value response.data.total } catch (error) { ElMessage.error(获取车位列表失败) } finally { loading.value false } } // 获取停车场列表 const fetchParkingLots async () { try { const response await getParkingLots() parkingLots.value response.data } catch (error) { ElMessage.error(获取停车场列表失败) } } // 状态标签类型 const getStatusTagType (status) { const map { 0: success, // 空闲 1: warning, // 已预约 2: danger // 使用中 } return map[status] || } // 状态文本 const getStatusText (status) { const map { 0: 空闲, 1: 已预约, 2: 使用中 } return map[status] || 未知 } // 查询 const handleSearch () { queryParams.pageNum 1 fetchParkingSpaces() } // 重置 const handleReset () { Object.assign(queryParams, { parkingLotId: , status: , spaceNumber: , pageNum: 1, pageSize: 10 }) fetchParkingSpaces() } // 分页大小改变 const handleSizeChange (size) { queryParams.pageSize size fetchParkingSpaces() } // 当前页改变 const handleCurrentChange (page) { queryParams.pageNum page fetchParkingSpaces() } // 更新车位状态 const handleUpdateStatus async (row) { try { await ElMessageBox.confirm(确定要更新该车位状态吗, 提示, { confirmButtonText: 确定, cancelButtonText: 取消, type: warning }) const newStatus row.status 0 ? 1 : 0 await updateParkingSpaceStatus(row.id, newStatus) ElMessage.success(状态更新成功) fetchParkingSpaces() } catch (error) { if (error ! cancel) { ElMessage.error(状态更新失败) } } } onMounted(() { fetchParkingLots() fetchParkingSpaces() }) /script3.3 API 接口封装使用 Axios 封装 HTTP 请求统一处理请求拦截、响应处理和错误处理。// api/request.js import axios from axios import { ElMessage } from element-plus import router from /router // 创建axios实例 const service axios.create({ baseURL: /api, timeout: 10000 }) // 请求拦截器 service.interceptors.request.use( config { // 添加token const token localStorage.getItem(token) if (token) { config.headers[Authorization] Bearer ${token} } return config }, error { return Promise.reject(error) } ) // 响应拦截器 service.interceptors.response.use( response { const res response.data if (res.code ! 200) { ElMessage.error(res.message || 请求失败) // token过期处理 if (res.code 401) { localStorage.removeItem(token) router.push(/login) } return Promise.reject(new Error(res.message || Error)) } else { return res } }, error { ElMessage.error(网络错误请检查网络连接) return Promise.reject(error) } ) export default service // api/parkingSpace.js import request from ./request // 获取车位列表 export function getParkingSpaces(params) { return request({ url: /parking-space/page, method: get, params }) } // 更新车位状态 export function updateParkingSpaceStatus(spaceId, status) { return request({ url: /parking-space/${spaceId}/status, method: put, data: { status } }) } // 获取可用车位 export function getAvailableSpaces(parkingLotId) { return request({ url: /parking-space/available/${parkingLotId}, method: get }) }4. 系统部署与性能优化完成开发后需要将系统部署到生产环境并进行必要的性能优化配置。4.1 后端部署配置使用 Docker 容器化部署 Spring Boot 应用确保环境一致性。# Dockerfile FROM openjdk:17-jdk-slim # 设置工作目录 WORKDIR /app # 复制jar包 COPY target/parking-management-0.0.1-SNAPSHOT.jar app.jar # 暴露端口 EXPOSE 8080 # 启动命令 ENTRYPOINT [java, -jar, app.jar, --spring.profiles.activeprod]生产环境配置文件 application-prod.ymlspring: datasource: url: jdbc:mysql://mysql-server:3306/parking_management?useUnicodetruecharacterEncodingutf8 username: ${DB_USERNAME} password: ${DB_PASSWORD} hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 30000 jpa: hibernate: ddl-auto: validate show-sql: false redis: host: redis-server port: 6379 password: ${REDIS_PASSWORD} timeout: 3000 lettuce: pool: max-active: 20 max-wait: -1 max-idle: 10 min-idle: 0 server: port: 8080 tomcat: max-connections: 10000 max-threads: 200 min-spare-threads: 104.2 前端构建与部署使用 Vite 构建生产版本配置 Nginx 作为静态文件服务器。// vite.config.prod.js import { defineConfig } from vite import vue from vitejs/plugin-vue export default defineConfig({ plugins: [vue()], build: { outDir: dist, assetsDir: assets, rollupOptions: { output: { chunkFileNames: assets/js/[name]-[hash].js, entryFileNames: assets/js/[name]-[hash].js, assetFileNames: assets/[ext]/[name]-[hash].[ext] } } }, server: { port: 3000 } })Nginx 配置示例server { listen 80; server_name your-domain.com; # 前端静态文件 location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } # 后端API代理 location /api/ { proxy_pass http://backend-server:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } # 静态资源缓存 location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { expires 1y; add_header Cache-Control public, immutable; } }4.3 数据库性能优化针对停车场管理系统的高并发场景需要对数据库进行性能优化。-- 为常用查询字段添加索引 CREATE INDEX idx_parking_space_status ON parking_space(status); CREATE INDEX idx_parking_space_lot_id ON parking_space(parking_lot_id); CREATE INDEX idx_order_user_id ON reservation_order(user_id); CREATE INDEX idx_order_status ON reservation_order(order_status); CREATE INDEX idx_order_create_time ON reservation_order(create_time); -- 分区表处理历史数据按月份分区 ALTER TABLE reservation_order PARTITION BY RANGE (YEAR(create_time)*100 MONTH(create_time)) ( PARTITION p202401 VALUES LESS THAN (202402), PARTITION p202402 VALUES LESS THAN (202403), PARTITION p202403 VALUES LESS THAN (202404), PARTITION p_max VALUES LESS THAN MAXVALUE );4.4 缓存策略设计使用 Redis 缓存热点数据减少数据库访问压力。// Redis配置类 Configuration public class RedisConfig { Bean public RedisTemplateString, Object redisTemplate(RedisConnectionFactory factory) { RedisTemplateString, Object template new RedisTemplate(); template.setConnectionFactory(factory); // 使用Jackson2JsonRedisSerializer序列化 Jackson2JsonRedisSerializerObject serializer new Jackson2JsonRedisSerializer(Object.class); ObjectMapper mapper new ObjectMapper(); mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); mapper.activateDefaultTyping(Lazy