1. 项目概述
作为一名长期从事Java开发的工程师,我最近完成了一个地铁购票App的开发项目。这个项目不仅让我深入理解了现代移动应用开发的完整流程,也让我积累了宝贵的全栈开发经验。这个App采用Spring Boot作为后端框架,Uni-App实现跨平台支持,MySQL作为数据库,为用户提供了便捷的地铁购票体验。
在开发过程中,我遇到了不少挑战,比如如何设计高效的票务系统架构、如何处理高并发购票请求、如何确保支付系统的安全性等。通过这个项目,我不仅巩固了Java开发技能,还深入学习了前端开发、数据库优化和系统安全等方面的知识。
2. 技术选型与架构设计
2.1 后端技术栈
我们选择Java作为后端开发语言,主要基于以下几个考虑:
- Java具有优秀的跨平台特性,通过JVM可以在不同操作系统上运行
- Java拥有丰富的生态系统和成熟的开发工具链
- Java在企业级应用开发中有着广泛的应用和验证
Spring Boot框架是我们的核心选择,它提供了:
- 自动配置:简化了Spring应用的初始搭建和开发过程
- 内嵌服务器:无需部署WAR文件,可以直接运行
- Starter依赖:简化构建配置,快速集成常用功能
- 生产就绪特性:如健康检查、指标收集等
java复制@SpringBootApplication
public class MetroTicketApplication {
public static void main(String[] args) {
SpringApplication.run(MetroTicketApplication.class, args);
}
}
2.2 前端技术选型
考虑到需要支持iOS和Android双平台,我们选择了Uni-App作为前端框架:
- 基于Vue.js开发,学习曲线平缓
- 一套代码可以编译到多个平台
- 丰富的组件库和插件生态
- 良好的性能表现
javascript复制// Uni-App页面示例
<template>
<view class="container">
<text>{{message}}</text>
</view>
</template>
<script>
export default {
data() {
return {
message: 'Hello Metro Ticket App'
}
}
}
</script>
2.3 数据库设计
我们使用MySQL作为关系型数据库,主要表结构包括:
- 用户表(ordinary_user):存储用户基本信息
- 车次信息表(train_information):存储地铁线路和车次信息
- 车票订单表(ticket_order):记录用户购票信息
- 退票改签表(refund_and_change):管理退票改签业务
- 用户评价表(user_evaluation):存储用户对服务的评价
sql复制CREATE TABLE `ticket_order` (
`ticket_order_id` int NOT NULL AUTO_INCREMENT,
`departure_site` varchar(64) DEFAULT NULL,
`arriving_at_the_site` varchar(64) DEFAULT NULL,
`train_number` varchar(64) DEFAULT NULL,
`train_type` varchar(64) DEFAULT NULL,
`departure_time` datetime DEFAULT NULL,
`time_of_arrival` datetime DEFAULT NULL,
`seat_fare` double DEFAULT NULL,
`ordinary_user` int DEFAULT NULL,
`user_name` varchar(64) DEFAULT NULL,
`user_phone` varchar(64) DEFAULT NULL,
`id_number` varchar(64) DEFAULT NULL,
`ticket_purchase_date` date DEFAULT NULL,
`number_of_tickets_purchased` double DEFAULT NULL,
`total_total_price` varchar(64) DEFAULT NULL,
`pay_state` varchar(16) NOT NULL,
`pay_type` varchar(16) DEFAULT NULL,
`seat` varchar(64) NOT NULL,
`refund_and_change_limit_times` int NOT NULL,
`user_evaluation_limit_times` int NOT NULL,
`create_time` datetime NOT NULL,
`update_time` timestamp NOT NULL,
`source_table` varchar(255) DEFAULT NULL,
`source_id` int DEFAULT NULL,
`source_user_id` int DEFAULT NULL,
PRIMARY KEY (`ticket_order_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3. 核心功能实现
3.1 用户认证模块
用户认证是系统的安全基础,我们实现了完整的注册登录流程:
-
注册流程:
- 用户填写基本信息(用户名、密码、手机号等)
- 系统验证信息合法性
- 密码加密存储(使用BCrypt加密)
- 发送验证码确认手机号有效性
-
登录流程:
- 用户输入用户名和密码
- 系统验证凭证
- 生成JWT令牌返回给客户端
- 后续请求携带令牌进行认证
java复制// 用户认证服务实现
@Service
public class AuthServiceImpl implements AuthService {
@Autowired
private UserRepository userRepository;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private JwtTokenProvider tokenProvider;
@Override
public String authenticate(String username, String password) {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("用户不存在"));
if (!passwordEncoder.matches(password, user.getPassword())) {
throw new BadCredentialsException("密码错误");
}
return tokenProvider.generateToken(user);
}
}
3.2 车次查询功能
车次查询是App的核心功能之一,我们实现了:
- 基础查询:根据出发站、到达站和日期查询车次
- 高级筛选:按列车类型、出发时间范围等条件筛选
- 实时更新:车次状态实时同步
- 缓存优化:热门线路查询结果缓存
java复制// 车次查询服务实现
@Service
public class TrainServiceImpl implements TrainService {
@Autowired
private TrainRepository trainRepository;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Override
public List<TrainInfo> searchTrains(String departure, String arrival, LocalDate date) {
String cacheKey = "trains:" + departure + "-" + arrival + ":" + date;
// 尝试从缓存获取
List<TrainInfo> cached = (List<TrainInfo>) redisTemplate.opsForValue().get(cacheKey);
if (cached != null) {
return cached;
}
// 查询数据库
List<TrainInfo> trains = trainRepository.findByDepartureAndArrivalAndDate(
departure, arrival, date);
// 存入缓存,有效期1小时
redisTemplate.opsForValue().set(cacheKey, trains, 1, TimeUnit.HOURS);
return trains;
}
}
3.3 购票流程实现
购票流程是系统最复杂的业务之一,需要考虑:
- 并发控制:防止超卖
- 事务管理:确保数据一致性
- 支付集成:对接第三方支付平台
- 座位分配:智能分配最优座位
java复制// 购票服务实现
@Service
@Transactional
public class TicketServiceImpl implements TicketService {
@Autowired
private TrainRepository trainRepository;
@Autowired
private TicketOrderRepository orderRepository;
@Autowired
private PaymentService paymentService;
@Override
public TicketOrder purchaseTicket(Long trainId, Long userId, int count) {
// 1. 查询车次信息
Train train = trainRepository.findById(trainId)
.orElseThrow(() -> new ResourceNotFoundException("车次不存在"));
// 2. 检查余票
if (train.getAvailableSeats() < count) {
throw new BusinessException("余票不足");
}
// 3. 扣减库存(使用乐观锁防止超卖)
int updated = trainRepository.reduceSeats(trainId, count, train.getVersion());
if (updated == 0) {
throw new ConcurrentModificationException("购票冲突,请重试");
}
// 4. 创建订单
TicketOrder order = new TicketOrder();
order.setTrainId(trainId);
order.setUserId(userId);
order.setCount(count);
order.setTotalPrice(train.getPrice() * count);
order.setStatus(OrderStatus.CREATED);
orderRepository.save(order);
// 5. 发起支付
Payment payment = paymentService.createPayment(order);
order.setPaymentId(payment.getId());
orderRepository.save(order);
return order;
}
}
4. 系统优化与安全
4.1 性能优化措施
-
数据库优化:
- 合理设计索引
- 查询优化
- 读写分离
-
缓存策略:
- Redis缓存热门数据
- 多级缓存设计
- 缓存失效策略
-
异步处理:
- 非核心流程异步化
- 消息队列削峰填谷
- 定时任务分离
java复制// 使用Spring Cache简化缓存操作
@Service
@CacheConfig(cacheNames = "trains")
public class TrainServiceImpl implements TrainService {
@Cacheable(key = "#departure + '-' + #arrival + ':' + #date.format(@dateFormatter)")
public List<TrainInfo> searchTrains(String departure, String arrival, LocalDate date) {
// 数据库查询逻辑
}
@CacheEvict(key = "#train.departure + '-' + #train.arrival + ':' + #train.departureTime.format(@dateFormatter)")
public void updateTrain(Train train) {
// 更新逻辑
}
}
4.2 安全防护方案
-
认证安全:
- JWT令牌认证
- 密码加密存储
- 多因素认证支持
-
数据安全:
- SQL注入防护
- XSS防护
- CSRF防护
-
支付安全:
- 支付数据加密
- 支付结果校验
- 敏感信息脱敏
java复制// 安全配置示例
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.antMatchers("/api/trains/search").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint());
}
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter();
}
}
5. 测试与部署
5.1 测试策略
我们采用了多层次的测试策略确保系统质量:
- 单元测试:使用JUnit和Mockito测试单个方法
- 集成测试:测试组件间交互
- API测试:使用Postman测试接口
- UI测试:使用Appium进行移动端测试
- 性能测试:使用JMeter模拟高并发场景
java复制// 单元测试示例
@SpringBootTest
public class TrainServiceTest {
@Autowired
private TrainService trainService;
@MockBean
private TrainRepository trainRepository;
@Test
public void testSearchTrains() {
// 准备测试数据
Train train = new Train();
train.setDeparture("北京");
train.setArrival("上海");
train.setDepartureTime(LocalDateTime.now());
// 模拟Repository行为
when(trainRepository.findByDepartureAndArrivalAndDate(
anyString(), anyString(), any(LocalDate.class)))
.thenReturn(Arrays.asList(train));
// 调用测试方法
List<TrainInfo> result = trainService.searchTrains("北京", "上海", LocalDate.now());
// 验证结果
assertEquals(1, result.size());
assertEquals("北京", result.get(0).getDeparture());
}
}
5.2 部署方案
我们采用Docker容器化部署方案:
- 后端服务:Spring Boot应用打包为Docker镜像
- 数据库:MySQL容器,数据卷持久化
- 缓存:Redis容器
- 前端:Uni-App编译后部署到Nginx容器
- 编排:使用Docker Compose管理多容器
dockerfile复制# 后端Dockerfile示例
FROM openjdk:11-jre-slim
WORKDIR /app
COPY target/metro-ticket-app.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
yaml复制# docker-compose.yml示例
version: '3'
services:
backend:
build: ./backend
ports:
- "8080:8080"
depends_on:
- mysql
- redis
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: metro_ticket
volumes:
- mysql_data:/var/lib/mysql
redis:
image: redis:alpine
frontend:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./frontend/dist:/usr/share/nginx/html
volumes:
mysql_data:
6. 开发经验与心得
在开发这个地铁购票App的过程中,我积累了一些宝贵的经验:
- 技术选型要权衡:不要盲目追求新技术,要考虑团队熟悉度和项目需求
- 并发控制是关键:票务系统必须处理好并发购票场景
- 测试要全面:特别是支付等关键流程,要覆盖各种边界情况
- 监控不能少:上线后要有完善的监控系统,及时发现和解决问题
一个特别值得分享的经验是关于并发控制的实现。最初我们使用简单的数据库锁机制,但在高并发场景下性能不佳。后来我们改用了Redis分布式锁结合数据库乐观锁的方案,既保证了数据一致性,又提高了系统吞吐量。
java复制// 改进后的购票方法
public TicketOrder purchaseTicket(Long trainId, Long userId, int count) {
String lockKey = "ticket_lock:" + trainId;
String lockValue = UUID.randomUUID().toString();
try {
// 获取分布式锁
boolean locked = redisTemplate.opsForValue().setIfAbsent(
lockKey, lockValue, 10, TimeUnit.SECONDS);
if (!locked) {
throw new BusinessException("系统繁忙,请稍后再试");
}
// 业务逻辑...
} finally {
// 释放锁
if (lockValue.equals(redisTemplate.opsForValue().get(lockKey))) {
redisTemplate.delete(lockKey);
}
}
}
这个项目让我深刻理解了全栈开发的挑战和乐趣。从数据库设计到API开发,从前端界面到部署运维,每个环节都需要认真思考和不断优化。特别是在处理高并发场景时,需要考虑的细节非常多,这让我对分布式系统有了更深的理解。