1. 项目概述
作为一名有10年Java全栈开发经验的工程师,我最近完成了一个基于SpringBoot的民航网上订票系统。这个项目不仅包含了完整的源码和部署文档,还附带了详细的演示视频和毕业论文指导材料。对于计算机专业的毕业生来说,这是一个非常实用的参考项目。
这个系统采用了当前最流行的技术栈:SpringBoot作为后端框架,Vue.js作为前端框架,MySQL作为数据库,MyBatis Plus作为持久层框架。系统实现了完整的民航机票预订流程,包括用户注册登录、航班查询、机票预订、订单管理、支付对接等核心功能。
2. 系统架构设计
2.1 MVC架构实现
系统采用标准的MVC设计模式,将整个应用划分为以下几个层次:
-
视图层(View):使用Vue.js构建用户界面,包括:
- 用户注册/登录页面
- 航班查询页面
- 机票预订页面
- 订单管理页面
- 后台管理页面
-
控制层(Controller):SpringBoot的RestController处理HTTP请求,包括:
- 用户认证接口
- 航班查询接口
- 订单管理接口
- 支付回调接口
-
服务层(Service):实现核心业务逻辑,包括:
- 用户服务
- 航班服务
- 订单服务
- 支付服务
-
数据访问层(DAO):通过MyBatis Plus与MySQL数据库交互,包括:
- 用户数据访问
- 航班数据访问
- 订单数据访问
2.2 技术选型解析
2.2.1 SpringBoot框架优势
选择SpringBoot作为后端框架主要基于以下考虑:
- 快速开发:自动配置和起步依赖大大减少了配置工作
- 内嵌服务器:可以直接打包成可执行JAR,简化部署
- 微服务友好:便于后续扩展为微服务架构
- 丰富的生态系统:与Spring Cloud等框架无缝集成
2.2.2 Vue.js前端框架
前端选择Vue.js的原因:
- 渐进式框架:可以逐步采用,学习曲线平缓
- 组件化开发:提高代码复用性和可维护性
- 响应式数据绑定:简化DOM操作,提高开发效率
- 丰富的生态系统:Vue Router、Vuex等配套工具完善
2.2.3 MySQL数据库
数据库选择MySQL的考虑:
- 成熟稳定:经过大量生产环境验证
- 性能优异:支持高并发访问
- 开源免费:降低项目成本
- 社区支持:遇到问题容易找到解决方案
3. 核心功能实现
3.1 用户认证模块
3.1.1 注册功能实现
用户注册流程的核心代码:
java复制@RestController
@RequestMapping("/api/auth")
public class AuthController {
@Autowired
private UserService userService;
@PostMapping("/register")
public ResponseEntity<?> registerUser(@Valid @RequestBody RegisterRequest request) {
// 检查用户名是否已存在
if(userService.existsByUsername(request.getUsername())) {
return ResponseEntity.badRequest().body("用户名已存在");
}
// 检查邮箱是否已注册
if(userService.existsByEmail(request.getEmail())) {
return ResponseEntity.badRequest().body("邮箱已注册");
}
// 创建新用户
User user = new User();
user.setUsername(request.getUsername());
user.setEmail(request.getEmail());
user.setPassword(passwordEncoder.encode(request.getPassword()));
user.setRoles(Collections.singleton(Role.USER));
userService.save(user);
return ResponseEntity.ok("注册成功");
}
}
3.1.2 登录功能实现
使用Spring Security实现JWT认证:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Autowired
private AuthEntryPointJwt unauthorizedHandler;
@Bean
public AuthTokenFilter authenticationJwtTokenFilter() {
return new AuthTokenFilter();
}
@Override
public 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/flights/**").permitAll()
.anyRequest().authenticated();
http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
3.2 航班查询模块
3.2.1 航班数据模型
java复制@Entity
@Table(name = "flights")
public class Flight {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String flightNumber;
@Column(nullable = false)
private String departureCity;
@Column(nullable = false)
private String arrivalCity;
@Column(nullable = false)
private LocalDateTime departureTime;
@Column(nullable = false)
private LocalDateTime arrivalTime;
@Column(nullable = false)
private BigDecimal price;
@Column(nullable = false)
private Integer totalSeats;
@Column(nullable = false)
private Integer availableSeats;
// Getters and Setters
}
3.2.2 航班查询接口
java复制@RestController
@RequestMapping("/api/flights")
public class FlightController {
@Autowired
private FlightService flightService;
@GetMapping
public ResponseEntity<List<Flight>> searchFlights(
@RequestParam String departureCity,
@RequestParam String arrivalCity,
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate departureDate) {
LocalDateTime startOfDay = departureDate.atStartOfDay();
LocalDateTime endOfDay = departureDate.plusDays(1).atStartOfDay();
List<Flight> flights = flightService.findAvailableFlights(
departureCity, arrivalCity, startOfDay, endOfDay);
return ResponseEntity.ok(flights);
}
}
3.3 订单管理模块
3.3.1 订单创建流程
java复制@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private FlightRepository flightRepository;
@Autowired
private OrderRepository orderRepository;
@Transactional
public Order createOrder(Long flightId, Long userId, Integer passengerCount) {
// 检查航班是否存在且有足够座位
Flight flight = flightRepository.findById(flightId)
.orElseThrow(() -> new ResourceNotFoundException("航班不存在"));
if(flight.getAvailableSeats() < passengerCount) {
throw new BusinessException("剩余座位不足");
}
// 扣减可用座位
flight.setAvailableSeats(flight.getAvailableSeats() - passengerCount);
flightRepository.save(flight);
// 创建订单
Order order = new Order();
order.setUserId(userId);
order.setFlightId(flightId);
order.setPassengerCount(passengerCount);
order.setTotalAmount(flight.getPrice().multiply(BigDecimal.valueOf(passengerCount)));
order.setStatus(OrderStatus.CREATED);
order.setCreateTime(LocalDateTime.now());
return orderRepository.save(order);
}
}
3.3.2 订单支付处理
java复制@RestController
@RequestMapping("/api/orders")
public class OrderController {
@Autowired
private OrderService orderService;
@Autowired
private PaymentService paymentService;
@PostMapping("/{orderId}/pay")
public ResponseEntity<?> payOrder(@PathVariable Long orderId) {
Order order = orderService.getOrderById(orderId);
if(order.getStatus() != OrderStatus.CREATED) {
return ResponseEntity.badRequest().body("订单状态异常");
}
PaymentResult result = paymentService.processPayment(order);
if(result.isSuccess()) {
order.setStatus(OrderStatus.PAID);
orderService.updateOrder(order);
return ResponseEntity.ok("支付成功");
} else {
return ResponseEntity.badRequest().body(result.getMessage());
}
}
}
4. 系统部署与测试
4.1 环境准备
4.1.1 开发环境要求
- JDK:1.8或以上版本
- Maven:3.6.0或以上版本
- MySQL:5.7或8.0版本
- Node.js:12.x或以上版本(前端开发需要)
4.1.2 数据库配置
在application.properties中配置数据库连接:
properties复制spring.datasource.url=jdbc:mysql://localhost:3306/airline_booking?useSSL=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=yourpassword
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
4.2 系统测试
4.2.1 单元测试示例
java复制@SpringBootTest
public class FlightServiceTest {
@Autowired
private FlightService flightService;
@Test
public void testSearchAvailableFlights() {
// 准备测试数据
String departureCity = "北京";
String arrivalCity = "上海";
LocalDate departureDate = LocalDate.of(2023, 6, 1);
// 执行测试
List<Flight> flights = flightService.searchAvailableFlights(
departureCity, arrivalCity, departureDate);
// 验证结果
assertNotNull(flights);
assertFalse(flights.isEmpty());
for(Flight flight : flights) {
assertEquals(departureCity, flight.getDepartureCity());
assertEquals(arrivalCity, flight.getArrivalCity());
assertTrue(flight.getAvailableSeats() > 0);
}
}
}
4.2.2 集成测试示例
java复制@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class AuthControllerIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Test
public void testRegisterUser() throws Exception {
RegisterRequest request = new RegisterRequest();
request.setUsername("testuser");
request.setEmail("test@example.com");
request.setPassword("Test@1234");
mockMvc.perform(post("/api/auth/register")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isOk())
.andExpect(jsonPath("$").value("注册成功"));
}
}
5. 项目总结与经验分享
在开发这个民航订票系统的过程中,我积累了一些宝贵的经验,分享给大家:
-
数据库设计:在设计航班和订单表时,要特别注意数据一致性问题。我使用了@Transactional注解来确保订票操作的原子性,避免出现座位超卖的情况。
-
性能优化:航班查询是高频操作,我添加了适当的数据库索引,并使用了缓存技术(Redis)来减轻数据库压力。
-
安全性考虑:
- 使用BCrypt加密存储用户密码
- 实现了JWT认证机制
- 对敏感操作添加了权限控制
- 防止SQL注入和XSS攻击
-
异常处理:设计了统一的异常处理机制,返回友好的错误信息给前端,同时记录详细的错误日志便于排查问题。
-
接口设计:遵循RESTful规范,保持接口风格一致,方便前端调用和后期维护。
这个项目完整实现了民航订票的核心业务流程,可以作为学习SpringBoot和Vue.js的很好的实践案例。对于计算机专业的同学来说,理解这个项目的架构设计和实现细节,对完成毕业设计和提升开发能力都有很大帮助。