1. 项目概述与核心技术栈解析
这个基于SpringBoot2+Vue3+MyBatis-Plus+MySQL8.0的汽车票网上预订系统,是一个典型的全栈式Java Web应用开发案例。作为一名长期从事企业级应用开发的工程师,我认为这个技术组合在当前Java Web开发领域具有标杆意义。
核心架构分层清晰:后端采用SpringBoot2作为基础框架,配合MyBatis-Plus实现数据持久层;前端使用Vue3构建响应式用户界面;数据库选用MySQL8.0提供稳定可靠的数据存储。这种分层架构既保证了开发效率,又能满足中型票务系统的性能需求。
提示:选择SpringBoot2而非SpringBoot3,主要考虑企业环境的版本兼容性。SpringBoot2对JDK8的完美支持,使其在企业级应用中仍占据主流地位。
2. 开发环境搭建与项目初始化
2.1 后端环境配置
首先需要配置Java开发环境。推荐使用JDK8或JDK11(LTS版本),配置环境变量时需特别注意:
bash复制# 检查Java版本
java -version
# 设置JAVA_HOME(示例为Mac/Linux)
export JAVA_HOME=$(/usr/libexec/java_home -v 1.8)
创建SpringBoot项目时,除常规依赖外,需要特别添加:
xml复制<!-- MyBatis-Plus 依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<!-- MySQL Connector -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
2.2 前端环境准备
Vue3开发需要Node.js环境,建议安装LTS版本(如18.x)。安装完成后:
bash复制# 检查Node版本
node -v
# 安装Vue CLI
npm install -g @vue/cli
# 创建Vue3项目
vue create ticket-frontend
在Vue3项目中,需要额外安装以下常用库:
bash复制npm install axios vue-router@4 pinia element-plus
2.3 MySQL8.0安装与配置
MySQL8.0的安装有几个关键注意点:
- 初始化时务必设置强密码策略
- 需要显式设置默认认证插件为
caching_sha2_password - 建议调整以下参数:
sql复制-- 创建专用数据库
CREATE DATABASE ticket_system
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
-- 创建应用账号
CREATE USER 'ticket_app'@'%' IDENTIFIED WITH caching_sha2_password BY 'ComplexP@ssw0rd';
GRANT ALL PRIVILEGES ON ticket_system.* TO 'ticket_app'@'%';
3. 核心业务模块实现
3.1 车票管理模块设计
车票实体设计需要考虑多个维度:
java复制@Data
@TableName("t_ticket")
public class Ticket {
@TableId(type = IdType.AUTO)
private Long id;
private String busNumber; // 车次编号
private Integer seatType; // 座位类型
private LocalDateTime departureTime; // 发车时间
private String startStation; // 始发站
private String endStation; // 终点站
private BigDecimal price; // 票价
private Integer stock; // 余票数量
// 其他字段...
}
对应的MyBatis-Plus Mapper接口:
java复制public interface TicketMapper extends BaseMapper<Ticket> {
// 自定义复杂查询
@Select("SELECT * FROM t_ticket WHERE start_station = #{start} AND end_station = #{end} " +
"AND departure_time BETWEEN #{startTime} AND #{endTime}")
List<Ticket> queryByCondition(@Param("start") String start,
@Param("end") String end,
@Param("startTime") LocalDateTime startTime,
@Param("endTime") LocalDateTime endTime);
}
3.2 订单处理流程实现
订单创建是系统的核心业务,需要考虑并发控制:
java复制@Service
@Transactional
public class OrderServiceImpl implements OrderService {
@Autowired
private TicketMapper ticketMapper;
@Override
public Order createOrder(Long ticketId, Integer quantity, Long userId) {
// 1. 查询车票并锁定
Ticket ticket = ticketMapper.selectByIdForUpdate(ticketId);
// 2. 检查余票
if (ticket.getStock() < quantity) {
throw new BusinessException("余票不足");
}
// 3. 扣减库存
ticket.setStock(ticket.getStock() - quantity);
ticketMapper.updateById(ticket);
// 4. 创建订单
Order order = new Order();
// 设置订单属性...
return order;
}
}
注意:这里使用了
selectForUpdate实现悲观锁,防止超卖。在高并发场景下,也可以考虑使用乐观锁或Redis分布式锁。
3.3 Vue3前端页面开发
车票查询页面示例:
vue复制<template>
<div class="query-container">
<el-form :model="queryForm" @submit.prevent="handleQuery">
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="出发地">
<el-input v-model="queryForm.startStation" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="目的地">
<el-input v-model="queryForm.endStation" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="出发日期">
<el-date-picker
v-model="queryForm.departureDate"
type="date"
value-format="YYYY-MM-DD" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-button type="primary" native-type="submit">查询</el-button>
</el-col>
</el-row>
</el-form>
<el-table :data="ticketList" v-loading="loading">
<el-table-column prop="busNumber" label="车次" />
<el-table-column prop="departureTime" label="发车时间" />
<el-table-column prop="price" label="票价" />
<el-table-column label="操作">
<template #default="scope">
<el-button @click="handleBook(scope.row)">预订</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script setup>
import { ref } from 'vue'
import axios from 'axios'
const queryForm = ref({
startStation: '',
endStation: '',
departureDate: ''
})
const ticketList = ref([])
const loading = ref(false)
const handleQuery = async () => {
loading.value = true
try {
const res = await axios.get('/api/ticket/query', {
params: queryForm.value
})
ticketList.value = res.data
} finally {
loading.value = false
}
}
</script>
4. 系统安全与性能优化
4.1 安全防护措施
-
SQL注入防护:
- 使用MyBatis-Plus的Wrapper构建查询条件
- 禁止直接拼接SQL语句
-
XSS防护:
- 前端使用vue-sanitize处理富文本
- 后端添加全局过滤器
java复制@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable() // 根据实际情况决定是否禁用
.authorizeRequests()
.antMatchers("/api/**").authenticated()
.anyRequest().permitAll()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
}
4.2 缓存策略优化
使用Redis缓存热门线路数据:
java复制@Service
public class TicketServiceImpl implements TicketService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final String CACHE_PREFIX = "ticket:hot:";
@Override
@Cacheable(value = "ticketCache", key = "#start+'-'+#end")
public List<Ticket> getHotTickets(String start, String end) {
// 数据库查询逻辑...
}
@Override
@CacheEvict(value = "ticketCache", key = "#ticket.startStation+'-'+#ticket.endStation")
public void updateTicket(Ticket ticket) {
// 更新逻辑...
}
}
4.3 数据库性能调优
针对票务系统特点优化MySQL8.0:
sql复制-- 创建合适的索引
CREATE INDEX idx_ticket_route ON t_ticket(start_station, end_station);
CREATE INDEX idx_ticket_time ON t_ticket(departure_time);
-- 优化InnoDB参数
SET GLOBAL innodb_buffer_pool_size = 2G; -- 根据服务器内存调整
SET GLOBAL innodb_io_capacity = 2000;
SET GLOBAL innodb_io_capacity_max = 4000;
5. 常见问题与解决方案
5.1 MyBatis-Plus分页查询问题
实现自定义分页查询时,常见配置错误:
java复制@Configuration
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
// 使用示例
Page<Ticket> page = new Page<>(1, 10);
Page<Ticket> result = ticketMapper.selectPage(page,
Wrappers.<Ticket>lambdaQuery()
.eq(Ticket::getStartStation, "北京")
.eq(Ticket::getEndStation, "上海"));
5.2 Vue3路由缓存失效问题
三级嵌套路由缓存问题的解决方案:
javascript复制// router.js
const routes = [
{
path: '/',
component: Layout,
children: [
{
path: 'ticket',
component: TicketContainer,
children: [
{
path: 'list',
component: TicketList,
meta: { keepAlive: true } // 启用缓存
},
{
path: 'detail/:id',
component: TicketDetail
}
]
}
]
}
]
// App.vue
<template>
<router-view v-slot="{ Component }">
<keep-alive>
<component :is="Component" v-if="$route.meta.keepAlive" />
</keep-alive>
<component :is="Component" v-if="!$route.meta.keepAlive" />
</router-view>
</template>
5.3 高并发下的库存超卖问题
除了数据库锁,还可以采用Redis原子操作:
java复制public boolean reduceStock(Long ticketId, int quantity) {
String key = "ticket:stock:" + ticketId;
return redisTemplate.execute(new RedisCallback<Boolean>() {
@Override
public Boolean doInRedis(RedisConnection connection) {
// Lua脚本保证原子性
String script = "if tonumber(redis.call('get', KEYS[1])) >= tonumber(ARGV[1]) then " +
"return redis.call('decrby', KEYS[1], ARGV[1]) " +
"else return -1 end";
Long result = connection.eval(
script.getBytes(),
ReturnType.INTEGER,
1,
key.getBytes(),
String.valueOf(quantity).getBytes());
return result != null && result >= 0;
}
});
}
6. 项目部署与监控
6.1 后端打包与部署
SpringBoot应用打包注意事项:
bash复制# 打包命令
mvn clean package -DskipTests
# 生产环境启动建议
java -server -Xms2g -Xmx2g -XX:+UseG1GC -jar ticket-system.jar \
--spring.profiles.active=prod \
--spring.datasource.url=jdbc:mysql://db-host:3306/ticket_system \
--spring.datasource.username=ticket_app
6.2 前端项目构建
Vue3生产环境构建优化:
bash复制# 安装依赖
npm install
# 构建生产版本
npm run build
# 构建完成后,dist目录可直接部署到Nginx
Nginx配置示例:
nginx复制server {
listen 80;
server_name ticket.example.com;
location / {
root /path/to/dist;
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass http://backend-server:8080;
proxy_set_header Host $host;
}
}
6.3 系统监控配置
使用SpringBoot Actuator暴露监控端点:
yaml复制# application-prod.yml
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
metrics:
export:
prometheus:
enabled: true
配合Grafana监控面板,可以实时监控:
- JVM内存使用情况
- 数据库连接池状态
- 接口响应时间
- 系统吞吐量
我在实际部署中发现,对于票务系统特别需要关注以下几个指标:
- 订单创建接口的99线响应时间
- 数据库活跃连接数
- Redis缓存命中率
- 车票查询接口的QPS
这些指标异常往往能提前预警系统瓶颈。建议设置当订单创建响应时间超过500ms时触发告警,这通常意味着数据库压力过大或缓存失效。
