1. 项目概述
作为一名长期从事Java全栈开发的工程师,我最近完成了一个基于SpringBoot的预约打车系统毕业设计项目。这个系统采用了当前主流的技术栈,包括SpringBoot、MySQL和Spring Security,旨在解决传统打车软件中存在的效率低下、用户体验差和安全性不足等问题。
在开发过程中,我特别注重系统的实用性和安全性。系统实现了完整的预约打车业务流程,包括车辆信息管理、预约下单、订单确认、支付和服务评价等功能模块。同时,通过多种安全措施保障用户数据和交易安全,如数据加密、HTTPS传输和严格的权限控制。
提示:本系统采用前后端分离架构,后端使用SpringBoot框架开发RESTful API,前端使用Vue.js构建用户界面,数据库选用MySQL 8.0版本。
2. 系统架构设计
2.1 技术选型与架构
系统采用典型的三层架构设计,分为表现层、业务逻辑层和数据访问层。这种分层设计使得系统各模块职责明确,耦合度低,便于维护和扩展。
后端技术栈:
- 基础框架:SpringBoot 2.7.0
- 安全框架:Spring Security 5.7.0
- 数据库:MySQL 8.0
- ORM框架:Spring Data JPA
- 构建工具:Maven
前端技术栈:
- 框架:Vue.js 3.0
- UI组件库:Element Plus
- 状态管理:Vuex
- 路由:Vue Router
2.2 数据库设计
系统数据库包含多个核心表,以下是主要表结构设计:
用户表(user)
sql复制CREATE TABLE `user` (
`id` bigint NOT NULL AUTO_INCREMENT,
`username` varchar(64) NOT NULL,
`password` varchar(256) NOT NULL,
`email` varchar(128) NOT NULL,
`phone` varchar(20) DEFAULT NULL,
`role` varchar(20) NOT NULL,
`create_time` datetime NOT NULL,
`update_time` timestamp NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_username` (`username`),
UNIQUE KEY `idx_email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
车辆信息表(vehicle_information)
sql复制CREATE TABLE `vehicle_information` (
`id` bigint NOT NULL AUTO_INCREMENT,
`driver_id` bigint NOT NULL,
`license_plate` varchar(20) NOT NULL,
`vehicle_model` varchar(50) NOT NULL,
`vehicle_color` varchar(20) DEFAULT NULL,
`available_status` varchar(20) DEFAULT 'AVAILABLE',
`fee_standard` decimal(10,2) NOT NULL,
`examine_state` varchar(20) DEFAULT 'PENDING',
`create_time` datetime NOT NULL,
`update_time` timestamp NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_driver` (`driver_id`),
KEY `idx_status` (`available_status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
预约信息表(appointment)
sql复制CREATE TABLE `appointment` (
`id` bigint NOT NULL AUTO_INCREMENT,
`appointment_number` varchar(32) NOT NULL,
`user_id` bigint NOT NULL,
`driver_id` bigint NOT NULL,
`vehicle_id` bigint NOT NULL,
`departure` varchar(255) NOT NULL,
`destination` varchar(255) NOT NULL,
`departure_time` datetime NOT NULL,
`status` varchar(20) DEFAULT 'PENDING',
`total_fee` decimal(10,2) DEFAULT NULL,
`create_time` datetime NOT NULL,
`update_time` timestamp NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_appointment_no` (`appointment_number`),
KEY `idx_user` (`user_id`),
KEY `idx_driver` (`driver_id`),
KEY `idx_vehicle` (`vehicle_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3. 核心功能实现
3.1 车辆信息管理模块
车辆信息管理模块允许司机用户添加、编辑和删除自己的车辆信息,管理员负责审核这些信息。以下是核心代码实现:
VehicleController.java
java复制@RestController
@RequestMapping("/api/vehicles")
public class VehicleController {
@Autowired
private VehicleService vehicleService;
@PostMapping
public ResponseEntity<Vehicle> addVehicle(@RequestBody VehicleDTO vehicleDTO,
@AuthenticationPrincipal User user) {
Vehicle vehicle = vehicleService.addVehicle(vehicleDTO, user.getId());
return ResponseEntity.ok(vehicle);
}
@PutMapping("/{id}")
public ResponseEntity<Vehicle> updateVehicle(@PathVariable Long id,
@RequestBody VehicleDTO vehicleDTO,
@AuthenticationPrincipal User user) {
Vehicle vehicle = vehicleService.updateVehicle(id, vehicleDTO, user.getId());
return ResponseEntity.ok(vehicle);
}
@GetMapping("/{id}")
public ResponseEntity<Vehicle> getVehicle(@PathVariable Long id) {
Vehicle vehicle = vehicleService.getVehicle(id);
return ResponseEntity.ok(vehicle);
}
@GetMapping
public ResponseEntity<Page<Vehicle>> listVehicles(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size) {
Page<Vehicle> vehicles = vehicleService.listVehicles(page, size);
return ResponseEntity.ok(vehicles);
}
}
VehicleServiceImpl.java
java复制@Service
@Transactional
public class VehicleServiceImpl implements VehicleService {
@Autowired
private VehicleRepository vehicleRepository;
@Override
public Vehicle addVehicle(VehicleDTO vehicleDTO, Long driverId) {
// 验证车牌号是否已存在
if (vehicleRepository.existsByLicensePlate(vehicleDTO.getLicensePlate())) {
throw new BusinessException("车牌号已存在");
}
Vehicle vehicle = new Vehicle();
BeanUtils.copyProperties(vehicleDTO, vehicle);
vehicle.setDriverId(driverId);
vehicle.setExamineState("PENDING");
vehicle.setAvailableStatus("AVAILABLE");
return vehicleRepository.save(vehicle);
}
@Override
public Vehicle updateVehicle(Long id, VehicleDTO vehicleDTO, Long driverId) {
Vehicle vehicle = vehicleRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("车辆不存在"));
if (!vehicle.getDriverId().equals(driverId)) {
throw new UnauthorizedException("无权修改此车辆信息");
}
BeanUtils.copyProperties(vehicleDTO, vehicle);
return vehicleRepository.save(vehicle);
}
}
注意事项:在实现车辆信息管理功能时,需要注意以下几点:
- 车牌号需要做唯一性校验,避免重复
- 只有车辆所有者才能修改自己的车辆信息
- 新添加的车辆默认状态为"待审核"
- 被预约中的车辆不能直接删除,应先改为"不可用"状态
3.2 预约打车模块
预约打车是系统的核心功能,涉及多个状态转换和业务规则校验。以下是关键实现:
AppointmentController.java
java复制@RestController
@RequestMapping("/api/appointments")
public class AppointmentController {
@Autowired
private AppointmentService appointmentService;
@PostMapping
public ResponseEntity<Appointment> createAppointment(
@RequestBody AppointmentDTO appointmentDTO,
@AuthenticationPrincipal User user) {
Appointment appointment = appointmentService.createAppointment(appointmentDTO, user);
return ResponseEntity.ok(appointment);
}
@PutMapping("/{id}/confirm")
public ResponseEntity<Appointment> confirmAppointment(
@PathVariable Long id,
@AuthenticationPrincipal User user) {
Appointment appointment = appointmentService.confirmAppointment(id, user);
return ResponseEntity.ok(appointment);
}
@PutMapping("/{id}/cancel")
public ResponseEntity<Appointment> cancelAppointment(
@PathVariable Long id,
@AuthenticationPrincipal User user) {
Appointment appointment = appointmentService.cancelAppointment(id, user);
return ResponseEntity.ok(appointment);
}
}
AppointmentServiceImpl.java
java复制@Service
@Transactional
public class AppointmentServiceImpl implements AppointmentService {
@Autowired
private AppointmentRepository appointmentRepository;
@Autowired
private VehicleRepository vehicleRepository;
@Autowired
private NotificationService notificationService;
@Override
public Appointment createAppointment(AppointmentDTO appointmentDTO, User user) {
// 检查车辆是否可用
Vehicle vehicle = vehicleRepository.findById(appointmentDTO.getVehicleId())
.orElseThrow(() -> new ResourceNotFoundException("车辆不存在"));
if (!"AVAILABLE".equals(vehicle.getAvailableStatus())) {
throw new BusinessException("该车辆当前不可预约");
}
// 计算费用
BigDecimal distance = calculateDistance(appointmentDTO.getDeparture(),
appointmentDTO.getDestination());
BigDecimal totalFee = distance.multiply(vehicle.getFeeStandard());
// 创建预约
Appointment appointment = new Appointment();
BeanUtils.copyProperties(appointmentDTO, appointment);
appointment.setAppointmentNumber(generateAppointmentNumber());
appointment.setUserId(user.getId());
appointment.setDriverId(vehicle.getDriverId());
appointment.setStatus("PENDING");
appointment.setTotalFee(totalFee);
Appointment savedAppointment = appointmentRepository.save(appointment);
// 发送通知
notificationService.sendAppointmentNotification(savedAppointment);
return savedAppointment;
}
private String generateAppointmentNumber() {
return "APP" + System.currentTimeMillis();
}
private BigDecimal calculateDistance(String departure, String destination) {
// 实际项目中应调用地图API计算实际距离
// 这里简化为固定值
return new BigDecimal("50.00");
}
}
常见问题处理:
- 并发预约问题:使用数据库乐观锁或悲观锁防止同一车辆被重复预约
- 费用计算:实际项目中应集成地图API计算实际距离和预估费用
- 状态管理:预约状态应严格管理,避免非法状态转换
3.3 支付模块实现
支付模块集成第三方支付平台,以下是简化实现:
PaymentController.java
java复制@RestController
@RequestMapping("/api/payments")
public class PaymentController {
@Autowired
private PaymentService paymentService;
@PostMapping
public ResponseEntity<PaymentResponse> createPayment(
@RequestBody PaymentRequest paymentRequest,
@AuthenticationPrincipal User user) {
PaymentResponse response = paymentService.createPayment(paymentRequest, user);
return ResponseEntity.ok(response);
}
@PostMapping("/callback")
public ResponseEntity<String> paymentCallback(
@RequestBody PaymentCallbackRequest callbackRequest) {
boolean result = paymentService.handlePaymentCallback(callbackRequest);
return result ? ResponseEntity.ok("SUCCESS") : ResponseEntity.badRequest().build();
}
}
PaymentServiceImpl.java
java复制@Service
@Transactional
public class PaymentServiceImpl implements PaymentService {
@Autowired
private AppointmentRepository appointmentRepository;
@Override
public PaymentResponse createPayment(PaymentRequest paymentRequest, User user) {
Appointment appointment = appointmentRepository.findById(paymentRequest.getAppointmentId())
.orElseThrow(() -> new ResourceNotFoundException("预约不存在"));
if (!appointment.getUserId().equals(user.getId())) {
throw new UnauthorizedException("无权支付此订单");
}
if (!"CONFIRMED".equals(appointment.getStatus())) {
throw new BusinessException("订单状态异常,无法支付");
}
// 调用第三方支付平台API
PaymentResponse response = callPaymentGateway(appointment);
// 更新订单状态
appointment.setPaymentStatus("PROCESSING");
appointmentRepository.save(appointment);
return response;
}
private PaymentResponse callPaymentGateway(Appointment appointment) {
// 实际项目中应调用真实支付接口
PaymentResponse response = new PaymentResponse();
response.setPaymentUrl("https://payment-gateway.com/pay?order=" + appointment.getAppointmentNumber());
return response;
}
}
实操心得:支付模块开发时需要注意以下几点:
- 支付状态与订单状态需要严格同步
- 支付回调接口要做好签名验证,防止伪造请求
- 支付超时和失败情况要有完善的处理机制
- 敏感支付信息不能明文存储
4. 安全设计与实现
4.1 Spring Security配置
系统使用Spring Security实现认证和授权,核心配置如下:
SecurityConfig.java
java复制@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.antMatchers("/api/payments/callback").permitAll()
.anyRequest().authenticated();
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
4.2 JWT认证实现
JwtTokenProvider.java
java复制@Component
public class JwtTokenProvider {
@Value("${app.jwtSecret}")
private String jwtSecret;
@Value("${app.jwtExpirationInMs}")
private int jwtExpirationInMs;
public String generateToken(Authentication authentication) {
UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();
Date now = new Date();
Date expiryDate = new Date(now.getTime() + jwtExpirationInMs);
return Jwts.builder()
.setSubject(Long.toString(userPrincipal.getId()))
.setIssuedAt(new Date())
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS512, jwtSecret)
.compact();
}
public Long getUserIdFromJWT(String token) {
Claims claims = Jwts.parser()
.setSigningKey(jwtSecret)
.parseClaimsJws(token)
.getBody();
return Long.parseLong(claims.getSubject());
}
public boolean validateToken(String authToken) {
try {
Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);
return true;
} catch (SignatureException ex) {
logger.error("Invalid JWT signature");
} catch (MalformedJwtException ex) {
logger.error("Invalid JWT token");
} catch (ExpiredJwtException ex) {
logger.error("Expired JWT token");
} catch (UnsupportedJwtException ex) {
logger.error("Unsupported JWT token");
} catch (IllegalArgumentException ex) {
logger.error("JWT claims string is empty");
}
return false;
}
}
4.3 数据安全措施
-
敏感数据加密:
- 用户密码使用BCrypt加密存储
- 敏感个人信息如手机号在数据库中使用AES加密
-
HTTPS传输:
- 全站启用HTTPS
- 配置HSTS防止SSL剥离攻击
-
防SQL注入:
- 使用JPA参数化查询
- 对用户输入进行严格过滤
-
XSS防护:
- 前端使用Vue的文本插值自动转义HTML
- 后端对输出内容进行HTML编码
5. 系统测试与部署
5.1 测试策略
系统采用分层测试策略,包括单元测试、集成测试和端到端测试:
- 单元测试:使用JUnit + Mockito测试单个类和方法
- 集成测试:使用SpringBootTest测试模块集成
- API测试:使用Postman测试REST API
- UI测试:使用Cypress进行前端自动化测试
5.2 性能测试
使用JMeter进行压力测试,主要测试指标:
| 测试场景 | 并发用户数 | 平均响应时间 | 错误率 | 吞吐量 |
|---|---|---|---|---|
| 用户登录 | 100 | 230ms | 0% | 420/s |
| 车辆查询 | 200 | 150ms | 0% | 850/s |
| 创建预约 | 50 | 350ms | 0% | 120/s |
| 支付回调 | 30 | 200ms | 0% | 80/s |
5.3 部署方案
系统采用Docker容器化部署,部署架构如下:
- 前端服务:Nginx容器托管Vue静态资源
- 后端服务:SpringBoot应用容器
- 数据库:MySQL容器,配置主从复制
- 缓存:Redis容器缓存热点数据
- 监控:Prometheus + Grafana监控系统状态
docker-compose.yml示例
yaml复制version: '3'
services:
backend:
image: taxi-booking-backend:latest
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
- DB_URL=jdbc:mysql://mysql:3306/taxi_booking
- DB_USER=root
- DB_PASSWORD=secret
depends_on:
- mysql
- redis
frontend:
image: taxi-booking-frontend:latest
ports:
- "80:80"
depends_on:
- backend
mysql:
image: mysql:8.0
environment:
- MYSQL_ROOT_PASSWORD=secret
- MYSQL_DATABASE=taxi_booking
volumes:
- mysql_data:/var/lib/mysql
redis:
image: redis:6.0
ports:
- "6379:6379"
prometheus:
image: prom/prometheus:latest
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
volumes:
mysql_data:
6. 项目总结与展望
在开发这个预约打车系统的过程中,我遇到了许多挑战也积累了不少经验。最大的收获是理解了如何将一个复杂的业务流程分解为多个可管理的模块,并通过适当的技术方案实现它们。
主要技术收获:
- 深入理解了Spring Security的工作原理和定制方法
- 掌握了JWT在分布式系统中的应用
- 学习了如何设计安全的RESTful API
- 实践了前后端分离的开发模式
未来改进方向:
- 引入消息队列处理高并发预约请求
- 集成实时地图服务提供更准确的费用计算
- 实现司机和乘客的实时通信功能
- 增加智能调度算法优化车辆分配
这个项目虽然作为毕业设计完成,但采用了企业级的开发标准和流程。从需求分析、系统设计到编码实现和测试部署,完整地实践了软件开发的整个生命周期。希望这个项目经验能为其他开发者提供参考,也欢迎同行交流改进意见。