拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Spring Boot 3 + Vue 3 + TypeScript 全栈开发驾校预约管理系统实战

在驾校业务数字化转型的浪潮中传统的人工电话预约、手动排课模式已难以应对学员激增、教练资源调配复杂、财务对账繁琐等痛点。学员抱怨约不到心仪的教练和时间驾校管理者则为排班混乱、数据统计滞后而头疼。本文将分享一套基于 Spring Boot 3、Vue 3 和 TypeScript 构建的现代化驾校预约管理系统从零开始手把手带你完成一个功能完整、前后端分离的企业级项目。无论你是想深入学习全栈开发还是为毕业设计寻找灵感或是为实际业务寻找解决方案本文提供的完整代码、配置详解和避坑指南都能让你直接上手。1. 系统核心概念与业务背景1.1 什么是驾校预约管理系统驾校预约管理系统是一个旨在优化驾校核心业务流程的信息化平台。它通过数字化手段将学员、教练、车辆、课程、场地、财务等要素进行高效连接与智能调度。其核心目标是解决传统驾校运营中的信息不对称、资源浪费和管理低效问题为学员提供便捷透明的服务为管理者提供精准的数据决策支持。1.2 系统核心业务流程与价值一个典型的预约流程包含以下环节学员端注册/登录 - 查看教练/车辆/时间资源 - 提交预约申请 - 在线支付可选 - 查看预约状态 - 完成培训后评价。教练端查看个人课表 - 确认或拒绝预约 - 记录学员培训进度。管理端管理学员、教练、车辆信息 - 设置课程套餐与价格 - 审核预约订单 - 排班调度 - 查看各类统计报表收入、预约率、教练负荷等。系统的价值在于提升效率自动化排班与预约减少人工沟通成本。优化体验学员可随时随地自助预约状态实时可查。精细化管理数据驱动决策实现资源教练、车辆、场地的利用率最大化。规范财务集成支付与对账资金流清晰可追溯。1.3 技术选型为什么是 Spring Boot 3 Vue 3 TypeScriptSpring Boot 3 (后端)作为 Java 生态中最主流的快速开发框架其“约定大于配置”的理念能极大提升后端服务开发效率。Spring Boot 3 基于 Spring Framework 6 和 Java 17提供了更好的性能、更现代的编程模型如响应式编程支持以及对 GraalVM 原生镜像的初步支持是构建稳健、可扩展后端 API 的理想选择。Vue 3 (前端框架)以其轻量、渐进式和出色的性能著称。Vue 3 的 Composition API 提供了更灵活的逻辑复用方式搭配script setup语法糖使得组件代码更简洁、更易于维护。其活跃的生态如 Element Plus、Vite能快速构建出美观、交互丰富的前端界面。TypeScript (前端语言)是 JavaScript 的超集为 JavaScript 添加了静态类型系统。在 Vue 3 项目中使用 TypeScript可以在编码阶段就捕获许多潜在的错误如拼写错误、类型不匹配提供更好的 IDE 智能提示和代码补全显著提升大型前端项目的可维护性和开发体验。这正是构建复杂业务系统如本管理系统所必需的。这三者结合构成了一个技术栈现代、生态成熟、前后端职责清晰的全栈开发方案。2. 环境准备与项目初始化在开始编码前请确保你的开发环境已就绪。2.1 后端环境准备 (Spring Boot 3)JDK确保已安装JDK 17 或更高版本。Spring Boot 3 要求最低 JDK 17。java -version构建工具推荐使用Maven 3.6或Gradle 7.x。本文示例使用 Maven。IDEIntelliJ IDEA推荐、Eclipse 或 VS Code需安装 Java 插件。数据库MySQL 5.7 或 8.0。我们将使用 MySQL 作为持久化存储。初始化项目访问 Spring Initializr 生成项目骨架。Project: MavenLanguage: JavaSpring Boot: 3.2.x (选择稳定版本)Project Metadata:Group:com.drivingschoolArtifact:booking-systemName:booking-systemPackage name:com.drivingschool.bookingsystemPackaging: JarJava: 17Dependencies: 添加Spring Web,Spring Data JPA,MySQL Driver,Lombok。下载生成的项目压缩包并解压用 IDE 打开。2.2 前端环境准备 (Vue 3 TypeScript)Node.js: 安装Node.js 18和 npm。node -v npm -v包管理工具可以使用 npm但更推荐pnpm或yarn速度更快。本文使用 pnpm。npm install -g pnpm创建 Vue 项目使用官方脚手架create-vue它天然支持 TypeScript。pnpm create vuelatest根据提示进行选择Project name:driving-school-frontendAdd TypeScript?YesAdd JSX Support? No (按需)Add Vue Router for Single Page Application?YesAdd Pinia for state management?Yes(推荐)Add Vitest for Unit Testing? No (可选)Add an End-to-End Testing Solution? No (可选)Add ESLint for code quality?YesAdd Prettier for code formatting?Yes创建完成后进入项目目录并安装依赖cd driving-school-frontend pnpm installUI 组件库我们将使用Element Plus它是对 Vue 3 支持良好的流行组件库。pnpm add element-plus element-plus/icons-vue同时需要安装并配置按需导入以优化打包体积具体配置可参考 Element Plus 官方文档。2.3 项目整体结构预览项目完成后整体目录结构大致如下driving-school-project/ ├── booking-system/ # Spring Boot 后端项目 │ ├── src/main/java/com/drivingschool/bookingsystem/ │ │ ├── controller/ # 控制器处理HTTP请求 │ │ ├── service/ # 业务逻辑层 │ │ ├── repository/ # 数据访问层 (JPA) │ │ ├── entity/ # 实体类对应数据库表 │ │ ├── dto/ # 数据传输对象 │ │ ├── config/ # 配置类 (如跨域、安全) │ │ └── BookingSystemApplication.java │ ├── src/main/resources/ │ │ ├── application.yml # 主配置文件 │ │ └── ... │ └── pom.xml └── driving-school-frontend/ # Vue 3 前端项目 ├── src/ │ ├── api/ # 封装所有后端API请求 │ ├── router/ # 路由配置 │ ├── stores/ # Pinia 状态管理 │ ├── views/ # 页面组件 │ ├── components/ # 可复用组件 │ ├── utils/ # 工具函数 │ ├── App.vue │ └── main.ts ├── index.html ├── vite.config.ts # Vite 配置 ├── tsconfig.json # TypeScript 配置 ├── package.json └── ...3. 后端核心功能实现 (Spring Boot 3)我们将从数据库设计开始逐步实现后端的核心业务逻辑。3.1 数据库设计与实体类映射首先在 MySQL 中创建数据库driving_school_db。然后我们设计几个核心实体。1. 用户实体 (User)涵盖学员、教练、管理员。通过userType字段区分。// 文件路径src/main/java/com/drivingschool/bookingsystem/entity/User.java package com.drivingschool.bookingsystem.entity; import jakarta.persistence.*; import lombok.Data; import java.time.LocalDateTime; Entity Table(name sys_user) Data public class User { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; Column(unique true, nullable false) private String username; // 登录名 private String password; private String realName; // 真实姓名 private String phone; private String email; Enumerated(EnumType.STRING) private UserType userType; // 枚举STUDENT, COACH, ADMIN private LocalDateTime createTime; public enum UserType { STUDENT, COACH, ADMIN } }2. 教练实体 (Coach)扩展用户信息包含教练特有属性。// 文件路径src/main/java/com/drivingschool/bookingsystem/entity/Coach.java package com.drivingschool.bookingsystem.entity; import jakarta.persistence.*; import lombok.Data; Entity Table(name biz_coach) Data public class Coach { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; OneToOne JoinColumn(name user_id, referencedColumnName id, unique true) private User user; // 关联用户 private String coachNumber; // 教练编号 private String idCard; // 身份证号 private String drivingLicense; // 驾驶证号 private Integer teachingYears; // 教龄 private String description; // 简介 private Boolean isAvailable true; // 是否可用 }3. 预约订单实体 (BookingOrder)系统的核心。// 文件路径src/main/java/com/drivingschool/bookingsystem/entity/BookingOrder.java package com.drivingschool.bookingsystem.entity; import jakarta.persistence.*; import lombok.Data; import java.math.BigDecimal; import java.time.LocalDateTime; Entity Table(name biz_booking_order) Data public class BookingOrder { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; private String orderNumber; // 订单号可自定义生成规则 ManyToOne JoinColumn(name student_id, nullable false) private User student; // 学员 ManyToOne JoinColumn(name coach_id, nullable false) private Coach coach; // 教练 private LocalDateTime bookingTime; // 预约的培训时间 private Integer duration; // 课时长单位小时 private String location; // 训练场地 Enumerated(EnumType.STRING) private OrderStatus status; // 状态PENDING, CONFIRMED, COMPLETED, CANCELLED private BigDecimal amount; // 订单金额 private LocalDateTime createTime; private LocalDateTime updateTime; public enum OrderStatus { PENDING, // 待确认 CONFIRMED, // 已确认 COMPLETED, // 已完成 CANCELLED // 已取消 } }3.2 数据访问层与业务逻辑层使用 Spring Data JPA 可以极大简化数据库操作。1. 创建 Repository 接口// 文件路径src/main/java/com/drivingschool/bookingsystem/repository/BookingOrderRepository.java package com.drivingschool.bookingsystem.repository; import com.drivingschool.bookingsystem.entity.BookingOrder; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.stereotype.Repository; import java.time.LocalDateTime; import java.util.List; Repository public interface BookingOrderRepository extends JpaRepositoryBookingOrder, Long, JpaSpecificationExecutorBookingOrder { // 查询某个教练在特定时间段的预约 ListBookingOrder findByCoachIdAndBookingTimeBetween(Long coachId, LocalDateTime start, LocalDateTime end); // 查询某个学员的预约历史 ListBookingOrder findByStudentIdOrderByBookingTimeDesc(Long studentId); }2. 实现 Service 业务逻辑// 文件路径src/main/java/com/drivingschool/bookingsystem/service/BookingService.java package com.drivingschool.bookingsystem.service; import com.drivingschool.bookingsystem.dto.BookingRequestDTO; import com.drivingschool.bookingsystem.entity.BookingOrder; import com.drivingschool.bookingsystem.entity.Coach; import com.drivingschool.bookingsystem.entity.User; import com.drivingschool.bookingsystem.repository.BookingOrderRepository; import com.drivingschool.bookingsystem.repository.CoachRepository; import com.drivingschool.bookingsystem.repository.UserRepository; import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.time.LocalDateTime; Service RequiredArgsConstructor // Lombok 自动生成构造函数注入 public class BookingService { private final BookingOrderRepository bookingOrderRepository; private final CoachRepository coachRepository; private final UserRepository userRepository; /** * 创建预约订单 */ Transactional public BookingOrder createBooking(BookingRequestDTO bookingRequest) { // 1. 参数校验 User student userRepository.findById(bookingRequest.getStudentId()) .orElseThrow(() - new EntityNotFoundException(学员不存在)); Coach coach coachRepository.findById(bookingRequest.getCoachId()) .orElseThrow(() - new EntityNotFoundException(教练不存在)); // 2. 业务校验教练是否可用、时间是否冲突等 if (!coach.getIsAvailable()) { throw new RuntimeException(该教练暂不可预约); } // 检查时间冲突简化示例实际需更复杂校验 LocalDateTime startTime bookingRequest.getBookingTime(); LocalDateTime endTime startTime.plusHours(bookingRequest.getDuration()); boolean hasConflict bookingOrderRepository .findByCoachIdAndBookingTimeBetween(coach.getId(), startTime, endTime) .stream() .anyMatch(order - order.getStatus() ! BookingOrder.OrderStatus.CANCELLED); if (hasConflict) { throw new RuntimeException(该时间段已被预约请选择其他时间); } // 3. 构建订单实体 BookingOrder order new BookingOrder(); order.setOrderNumber(generateOrderNumber()); order.setStudent(student); order.setCoach(coach); order.setBookingTime(bookingRequest.getBookingTime()); order.setDuration(bookingRequest.getDuration()); order.setLocation(bookingRequest.getLocation()); order.setStatus(BookingOrder.OrderStatus.PENDING); // 简单计算金额假设每小时单价固定 BigDecimal hourlyRate new BigDecimal(100.00); order.setAmount(hourlyRate.multiply(BigDecimal.valueOf(bookingRequest.getDuration()))); order.setCreateTime(LocalDateTime.now()); // 4. 保存到数据库 return bookingOrderRepository.save(order); } private String generateOrderNumber() { // 生成订单号逻辑例如DS202405210001 return DS LocalDateTime.now().format(java.time.format.DateTimeFormatter.ofPattern(yyyyMMddHHmmss)); } }3.3 控制器层与 RESTful API 设计控制器负责接收 HTTP 请求调用 Service并返回响应。// 文件路径src/main/java/com/drivingschool/bookingsystem/controller/BookingController.java package com.drivingschool.bookingsystem.controller; import com.drivingschool.bookingsystem.dto.BookingRequestDTO; import com.drivingschool.bookingsystem.entity.BookingOrder; import com.drivingschool.bookingsystem.service.BookingService; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; RestController RequestMapping(/api/bookings) RequiredArgsConstructor public class BookingController { private final BookingService bookingService; PostMapping public ResponseEntityBookingOrder createBooking(Valid RequestBody BookingRequestDTO bookingRequest) { BookingOrder newOrder bookingService.createBooking(bookingRequest); return ResponseEntity.status(HttpStatus.CREATED).body(newOrder); } GetMapping(/{id}) public ResponseEntityBookingOrder getBookingById(PathVariable Long id) { // 这里省略了Service层的查询逻辑实际需要实现 return ResponseEntity.ok().build(); } // 其他接口取消预约、确认预约、查询列表等 }对应的 DTO数据传输对象用于封装请求和响应数据// 文件路径src/main/java/com/drivingschool/bookingsystem/dto/BookingRequestDTO.java package com.drivingschool.bookingsystem.dto; import jakarta.validation.constraints.Future; import jakarta.validation.constraints.NotNull; import lombok.Data; import java.time.LocalDateTime; Data public class BookingRequestDTO { NotNull(message 学员ID不能为空) private Long studentId; NotNull(message 教练ID不能为空) private Long coachId; NotNull(message 预约时间不能为空) Future(message 预约时间必须是将来的时间) private LocalDateTime bookingTime; NotNull(message 课时长不能为空) private Integer duration; // 小时 private String location; }3.4 关键配置数据库与跨域1. 数据库配置 (application.yml)# 文件路径src/main/resources/application.yml spring: datasource: url: jdbc:mysql://localhost:3306/driving_school_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver jpa: hibernate: ddl-auto: update # 开发环境可用update生产环境用validate或none show-sql: true properties: hibernate: format_sql: true database-platform: org.hibernate.dialect.MySQL8Dialect server: port: 80802. 跨域配置允许前端项目访问后端 API。// 文件路径src/main/java/com/drivingschool/bookingsystem/config/WebConfig.java package com.drivingschool.bookingsystem.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; Configuration public class WebConfig { Bean public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurer() { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/api/**) // 允许跨域的路径 .allowedOrigins(http://localhost:5173) // 前端开发服务器地址 .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true); } }; } }4. 前端核心功能实现 (Vue 3 TypeScript Element Plus)前端将使用 Vue 3 的 Composition API 和script setup语法配合 TypeScript 和 Pinia 进行开发。4.1 项目配置与依赖确保vite.config.ts和tsconfig.json配置正确。安装必要的依赖pnpm add axios pinia vue-router pnpm add -D types/node # 解决 path 模块类型问题创建src/utils/request.ts文件使用 axios 封装统一的 HTTP 请求// 文件路径src/utils/request.ts import axios from axios; import { ElMessage } from element-plus; import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from axios; const service: AxiosInstance axios.create({ baseURL: import.meta.env.VITE_APP_BASE_API || http://localhost:8080/api, timeout: 10000, }); // 请求拦截器 service.interceptors.request.use( (config) { // 从 Pinia store 或 localStorage 获取 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: AxiosResponse) { const res response.data; // 假设后端统一返回格式 { code: number, data: any, message: string } if (res.code ! 200) { ElMessage.error(res.message || Error); return Promise.reject(new Error(res.message || Error)); } else { return res; } }, (error) { ElMessage.error(error.message || 请求失败); return Promise.reject(error); } ); export default service;4.2 状态管理使用 Pinia 管理用户状态创建用户状态 Store// 文件路径src/stores/user.ts import { defineStore } from pinia; import { ref, computed } from vue; import type { UserInfo } from /types/user; export const useUserStore defineStore(user, () { // 状态 const token refstring(); const userInfo refUserInfo | null(null); // Getter const isLoggedIn computed(() !!token.value); // Action const setToken (newToken: string) { token.value newToken; localStorage.setItem(token, newToken); }; const setUserInfo (info: UserInfo) { userInfo.value info; }; const logout () { token.value ; userInfo.value null; localStorage.removeItem(token); }; return { token, userInfo, isLoggedIn, setToken, setUserInfo, logout, }; });4.3 核心页面学员预约页面这是一个典型的 Vue 3 单文件组件展示了如何组合使用 Composition API、TypeScript、Element Plus 组件和 Pinia。!-- 文件路径src/views/student/BookingPage.vue -- template div classbooking-container el-card template #header span classcard-header预约练车/span /template el-form :modelbookingForm :rulesrules refformRef label-width100px el-row :gutter20 el-col :span12 el-form-item label选择教练 propcoachId el-select v-modelbookingForm.coachId placeholder请选择教练 filterable changehandleCoachChange el-option v-forcoach in coachList :keycoach.id :label${coach.user.realName} (${coach.coachNumber}) :valuecoach.id / /el-select /el-form-item /el-col el-col :span12 el-form-item label预约时间 propbookingTime el-date-picker v-modelbookingForm.bookingTime typedatetime placeholder选择日期和时间 :disabled-datedisabledDate :shortcutsshortcuts value-formatYYYY-MM-DD HH:mm:ss / /el-form-item /el-col /el-row el-row :gutter20 el-col :span12 el-form-item label课时长(小时) propduration el-input-number v-modelbookingForm.duration :min1 :max4 / /el-form-item /el-col el-col :span12 el-form-item label训练场地 proplocation el-input v-modelbookingForm.location placeholder请输入训练场地 / /el-form-item /el-col /el-row el-form-item el-button typeprimary clicksubmitBooking :loadingsubmitting提交预约/el-button el-button clickresetForm重置/el-button /el-form-item /el-form !-- 教练信息卡片 -- el-card v-ifselectedCoach classcoach-info-card template #header span教练信息/span /template div classcoach-detail el-avatar :size60 :srcselectedCoach.avatar / div classcoach-text h4{{ selectedCoach.user.realName }}/h4 p编号{{ selectedCoach.coachNumber }}/p p教龄{{ selectedCoach.teachingYears }} 年/p p{{ selectedCoach.description }}/p /div /div /el-card /el-card /div /template script setup langts import { ref, reactive, onMounted } from vue; import type { FormInstance, FormRules } from element-plus; import { ElMessage } from element-plus; import { useUserStore } from /stores/user; import { getCoaches, createBooking } from /api/booking; import type { Coach, BookingRequest } from /types/booking; // 类型定义 interface BookingForm { coachId: number | null; bookingTime: string; duration: number; location: string; } // 响应式数据 const formRef refFormInstance(); const coachList refCoach[]([]); const selectedCoach refCoach | null(null); const submitting ref(false); const bookingForm reactiveBookingForm({ coachId: null, bookingTime: , duration: 2, location: 驾校一号训练场, }); // 表单验证规则 const rules reactiveFormRulesBookingForm({ coachId: [{ required: true, message: 请选择教练, trigger: change }], bookingTime: [{ required: true, message: 请选择预约时间, trigger: change }], duration: [{ required: true, message: 请输入课时长, trigger: blur }], }); // 日期选择器快捷选项 const shortcuts [ { text: 明天上午, value: () { const date new Date(); date.setDate(date.getDate() 1); date.setHours(9, 0, 0, 0); return date; }, }, // ... 其他快捷选项 ]; // 禁用过去的日期 const disabledDate (time: Date) { return time.getTime() Date.now() - 24 * 60 * 60 * 1000; }; // 方法 const handleCoachChange (coachId: number) { const coach coachList.value.find(c c.id coachId); selectedCoach.value coach || null; }; const loadCoaches async () { try { const res await getCoaches(); coachList.value res.data; } catch (error) { ElMessage.error(加载教练列表失败); } }; const submitBooking async () { if (!formRef.value) return; const valid await formRef.value.validate(); if (!valid) return; submitting.value true; const userStore useUserStore(); const requestData: BookingRequest { studentId: userStore.userInfo?.id!, // 实际应从store获取 coachId: bookingForm.coachId!, bookingTime: bookingForm.bookingTime, duration: bookingForm.duration, location: bookingForm.location, }; try { await createBooking(requestData); ElMessage.success(预约成功); resetForm(); } catch (error) { ElMessage.error(预约失败请重试); } finally { submitting.value false; } }; const resetForm () { if (!formRef.value) return; formRef.value.resetFields(); selectedCoach.value null; }; // 生命周期 onMounted(() { loadCoaches(); }); /script style scoped .booking-container { padding: 20px; } .coach-info-card { margin-top: 20px; } .coach-detail { display: flex; align-items: center; gap: 20px; } .coach-text h4 { margin: 0 0 8px 0; } /style对应的 API 请求封装// 文件路径src/api/booking.ts import request from /utils/request; import type { AxiosPromise } from axios; import type { Coach, BookingRequest, BookingOrder } from /types/booking; export function getCoaches(): AxiosPromise{ data: Coach[] } { return request({ url: /coaches, method: get, }); } export function createBooking(data: BookingRequest): AxiosPromiseBookingOrder { return request({ url: /bookings, method: post, data, }); }4.4 路由与权限控制使用 Vue Router 定义路由并结合导航守卫实现简单的权限控制。// 文件路径src/router/index.ts import { createRouter, createWebHistory } from vue-router; import { useUserStore } from /stores/user; const routes [ { path: /, redirect: /login, }, { path: /login, name: Login, component: () import(/views/LoginPage.vue), meta: { requiresAuth: false }, }, { path: /student, name: StudentLayout, component: () import(/layouts/StudentLayout.vue), meta: { requiresAuth: true, role: STUDENT }, children: [ { path: booking, name: StudentBooking, component: () import(/views/student/BookingPage.vue), }, // ... 其他学生路由 ], }, // ... 教练和管理员路由 ]; const router createRouter({ history: createWebHistory(), routes, }); // 全局前置守卫 router.beforeEach((to, from, next) { const userStore useUserStore(); const isAuthenticated userStore.isLoggedIn; if (to.meta.requiresAuth !isAuthenticated) { // 需要登录但未登录重定向到登录页 next({ name: Login, query: { redirect: to.fullPath } }); } else if (to.meta.role) { // 需要特定角色这里简化处理实际应对比用户角色 // if (userStore.userInfo?.userType ! to.meta.role) { // next({ name: Forbidden }); // } next(); } else { next(); } }); export default router;5. 前后端联调与部署注意事项5.1 联调常见问题与解决思路跨域问题 (CORS)现象前端控制台报错Access-Control-Allow-Origin。解决确保后端已正确配置跨域如本文 3.4 节所示且allowedOrigins包含前端运行的地址如http://localhost:5173。接口 404 或 405现象请求后端接口返回 404 (Not Found) 或 405 (Method Not Allowed)。解决检查后端 Controller 的RequestMapping或GetMapping/PostMapping路径是否正确。检查前端请求的 URL 是否拼接正确baseURL api path。使用浏览器开发者工具的 Network 面板查看请求的详细 URL 和方法。数据类型不匹配现象后端接收到的 JSON 字段为null或类型转换错误。解决前后端统一使用相同的命名规范如驼峰。检查后端 DTO 的字段类型如LocalDateTime与前端的传值格式字符串是否匹配可能需要配置全局的 JSON 序列化/反序列化格式。身份认证失败现象请求需要认证的接口返回 401 (Unauthorized)。解决确认登录流程正确后端成功返回了 token如 JWT。确认前端在请求拦截器中正确地将 token 添加到了请求头如Authorization: Bearer token。检查 token 是否已过期。5.2 生产环境部署要点后端部署配置文件分离使用application-prod.yml并通过spring.profiles.activeprod激活。将数据库密码、密钥等敏感信息放入环境变量或配置中心。关闭开发特性设置spring.jpa.hibernate.ddl-autovalidate或none禁止自动更新表结构。打包与运行使用mvn clean package -DskipTests打包生成可执行的 JAR 文件。使用java -jar booking-system.jar --spring.profiles.activeprod运行。使用进程管理推荐使用systemd(Linux) 或 Docker 容器化部署实现服务自启和监控。前端部署构建优化运行pnpm run build生成静态文件位于dist目录。确保vite.config.ts中配置了正确的base如果部署在子路径下和生产环境 API 地址。静态资源服务将dist目录下的文件部署到 Nginx、Apache 或对象存储如 AWS S3、阿里云 OSS中。Nginx 配置示例server { listen 80; server_name your-domain.com; root /path/to/your/dist; index index.html; location / { try_files $uri $uri/ /index.html; # 支持 Vue Router 的 history 模式 } # 代理后端 API 请求 location /api/ { proxy_pass http://localhost:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }6. 系统扩展与最佳实践建议6.1 功能扩展方向支付集成接入支付宝、微信支付 SDK实现预约费用的在线支付与退款流程。消息通知集成短信如阿里云 SMS或微信模板消息在预约成功、教练确认、课程提醒等节点自动通知学员和教练。排班算法优化实现更智能的自动排班考虑教练专长科目二、科目三、学员进度、车辆可用性等多重约束。数据报表与分析使用 ECharts 等图表库为管理员提供可视化的数据看板展示营收趋势、教练负荷、学员转化率等。移动端适配基于同一套后端 API使用 Uni-app 或 Taro 等跨端框架开发微信小程序或 App覆盖更多使用场景。6.2 工程最佳实践后端统一响应封装定义如ResultT的通用响应体包含code、data、message字段。全局异常处理使用ControllerAdvice和ExceptionHandler捕获并统一处理异常返回友好的错误信息。接口文档使用Spring Doc OpenAPI自动生成 API 文档替代旧的 Swagger 2方便前后端协作。数据校验在 DTO 上使用Valid和 Jakarta Validation 注解如NotNull,Size进行入参校验。单元测试对 Service 层核心业务逻辑编写 JUnit 单元测试保证代码质量。前端TypeScript 严格模式开启tsconfig.json中的strict: true利用类型系统减少运行时错误。组件化与复用将通用的 UI 元素如日期选择器、教练卡片抽离成可复用组件。状态管理规范明确 Pinia Store 的职责避免在组件中直接修改全局状态通过 Actions 进行变更。API 层抽象将所有的后端请求统一放在src/api目录下管理便于维护和 Mock。环境变量使用.env.development和.env.production管理不同环境的前端配置如 API 地址。安全与性能后端安全引入 Spring Security 实现完整的认证如 JWT和授权基于角色的访问控制 RBAC。对敏感操作如取消预约、确认订单进行权限校验。防止 SQL 注入JPA 已提供一定防护、XSS 攻击等。前端安全对用户输入进行校验和转义防止 XSS。敏感信息如 token存储在localStorage或sessionStorage中需权衡利弊考虑使用 HttpOnly Cookie。性能优化后端可使用 Redis 缓存热点数据如教练列表、场地信息。前端可使用路由懒加载、组件异步加载、图片懒加载等技术优化首屏加载速度。通过以上步骤你已经完成了一个具备核心功能的驾校预约管理系统。从数据库设计、后端 API 开发到前端页面交互涵盖了现代 Web 应用开发的主要环节。这个项目不仅是一个可运行的系统更是一个学习 Spring Boot 3、Vue 3 和 TypeScript 全栈开发的优秀实践案例。在实际开发中你可以根据具体业务需求在此基础上不断迭代和完善。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门