作为一名从事Java全栈开发10余年的技术老兵,今天想和大家分享一个极具实战价值的毕业设计项目——基于SpringBoot+微信小程序的话剧票务管理系统。这个项目不仅涵盖了前后端主流技术栈的完整应用,更包含了从需求分析到系统测试的全流程开发经验,特别适合计算机相关专业的同学作为毕业设计选题。
在实际开发过程中,我发现很多同学在构建类似系统时容易陷入几个误区:要么过度关注界面美观而忽视业务逻辑,要么后端设计过于复杂导致小程序端调用困难。这个项目通过清晰的MVC分层和合理的API设计,完美解决了这些问题。系统采用SpringBoot作为后端框架,配合微信小程序原生开发,实现了话剧票务从浏览、选座、支付到管理的完整闭环。
选择SpringBoot 2.7作为后端基础框架是经过多方面考虑的。首先,它的自动配置特性大幅减少了XML配置,内嵌Tomcat服务器让部署变得极其简单。我在项目中特别使用了以下依赖:
xml复制<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3</version>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-miniapp</artifactId>
<version>4.5.0</version>
</dependency>
MyBatis-Plus的选择更是神来之笔,它的ActiveRecord模式让数据库操作变得异常简洁。比如用户查询只需:
java复制@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserMapper userMapper;
@GetMapping("/{id}")
public User getById(@PathVariable Long id) {
return userMapper.selectById(id);
}
}
微信小程序端采用原生开发而非uniapp等跨平台方案,主要基于性能考虑。小程序页面主要包含以下几个核心组件:
一个典型的页面结构如下:
javascript复制Page({
data: {
performances: [],
loading: false
},
onLoad() {
this.loadData()
},
loadData() {
wx.request({
url: 'https://yourdomain.com/api/performances',
success: (res) => {
this.setData({performances: res.data})
}
})
}
})
系统严格遵循MVC模式,但做了更符合现代Web开发的调整:
code复制├── controller # 控制器层
│ ├── PerformanceController.java
│ └── OrderController.java
├── service # 业务逻辑层
│ ├── impl
│ └── PerformanceService.java
├── mapper # 数据访问层
│ └── PerformanceMapper.java
└── model # 实体类
├── entity
└── dto
这种结构确保了各层职责单一,我在实际开发中特别强调DTO的使用,避免直接暴露实体类到接口层。
MySQL表设计遵循三范式但不过度设计,核心表包括:
特别值得一提的是座位表的设计,采用位图法存储座位状态:
sql复制CREATE TABLE `seat` (
`id` bigint NOT NULL AUTO_INCREMENT,
`schedule_id` bigint NOT NULL COMMENT '关联场次',
`row_num` int NOT NULL COMMENT '排号',
`col_num` int NOT NULL COMMENT '列号',
`status` tinyint NOT NULL DEFAULT '0' COMMENT '0可用 1已售 2锁定',
PRIMARY KEY (`id`),
KEY `idx_schedule` (`schedule_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
微信登录是系统的入口,我们采用官方推荐的code2session方案:
java复制// 后端登录接口
@PostMapping("/login")
public Result login(@RequestParam String code) {
String url = "https://api.weixin.qq.com/sns/jscode2session?appid={appid}&secret={secret}&js_code={code}&grant_type=authorization_code";
Map<String, String> params = new HashMap<>();
params.put("appid", appId);
params.put("secret", appSecret);
params.put("code", code);
String response = restTemplate.getForObject(url, String.class, params);
JSONObject json = JSON.parseObject(response);
if(json.containsKey("openid")) {
String openid = json.getString("openid");
User user = userService.getByOpenid(openid);
if(user == null) {
user = new User();
user.setOpenid(openid);
userService.save(user);
}
String token = JwtUtil.generateToken(user.getId());
return Result.success(token);
}
return Result.fail("登录失败");
}
选座是系统的核心难点,我们采用双缓存策略:
关键代码片段:
javascript复制// 小程序端选座逻辑
handleSelectSeat(e) {
const {row, col} = e.currentTarget.dataset
this.setData({
[`seats[${row}][${col}]`]: this.data.seats[row][col] === 1 ? 0 : 1
})
}
java复制// 后端座位锁定API
@PostMapping("/lock")
public Result lockSeats(@RequestBody LockRequest request) {
String lockKey = "schedule:" + request.getScheduleId();
try {
for (SeatDTO seat : request.getSeats()) {
String field = seat.getRow() + ":" + seat.getCol();
if (!redisTemplate.opsForHash().putIfAbsent(lockKey, field, "1")) {
throw new BusinessException("座位已被锁定");
}
}
redisTemplate.expire(lockKey, 15, TimeUnit.MINUTES);
return Result.success();
} catch (Exception e) {
redisTemplate.delete(lockKey);
throw e;
}
}
采用Element UI + Vue实现的管理界面支持CRUD操作:
vue复制<template>
<el-table :data="performances">
<el-table-column prop="title" label="剧目名称"></el-table-column>
<el-table-column prop="showTime" label="演出时间"></el-table-column>
<el-table-column label="操作">
<template #default="scope">
<el-button @click="editPerformance(scope.row)">编辑</el-button>
</template>
</el-table-column>
</el-table>
</template>
<script>
export default {
data() {
return {
performances: []
}
},
created() {
this.fetchData()
},
methods: {
async fetchData() {
const res = await this.$http.get('/api/performances')
this.performances = res.data
}
}
}
</script>
使用ECharts实现数据可视化:
java复制@GetMapping("/stats")
public Result getOrderStats(@RequestParam String start,
@RequestParam String end) {
List<OrderStatsDTO> stats = orderMapper.selectStatsByDate(
LocalDate.parse(start),
LocalDate.parse(end)
);
return Result.success(stats);
}
在热门剧目抢票场景下,我们采用Redisson实现分布式锁:
java复制public boolean lockSeats(Long scheduleId, List<SeatDTO> seats) {
RLock lock = redissonClient.getLock("seat_lock:" + scheduleId);
try {
if (lock.tryLock(3, 10, TimeUnit.SECONDS)) {
// 检查座位状态
boolean allAvailable = seatMapper.checkAvailable(
scheduleId,
seats.stream()
.map(s -> new Seat(s.getRow(), s.getCol()))
.collect(Collectors.toList())
);
if (allAvailable) {
// 更新座位状态
seatMapper.batchUpdateStatus(
scheduleId,
seats,
SeatStatus.LOCKED.getCode()
);
return true;
}
}
} finally {
lock.unlock();
}
return false;
}
使用RabbitMQ处理订单创建请求:
java复制@RabbitListener(queues = "order.queue")
public void handleOrderMessage(OrderMessage message) {
try {
orderService.createOrder(message);
} catch (Exception e) {
log.error("订单处理失败", e);
// 加入死信队列
}
}
mermaid复制sequenceDiagram
小程序->>+后端: 发起支付请求
后端->>+微信支付: 创建订单
微信支付-->>-后端: 返回预支付信息
后端-->>-小程序: 返回支付参数
小程序->>+微信支付: 调起支付
微信支付-->>-小程序: 支付结果
微信支付->>+后端: 异步通知
后端-->>-微信支付: 处理结果
java复制@Transactional
public void handlePayNotify(NotifyRequest request) {
// 验证签名
if (!wxPayService.verifyNotify(request)) {
throw new BusinessException("签名验证失败");
}
Order order = orderMapper.selectById(request.getOutTradeNo());
if (order == null) {
throw new BusinessException("订单不存在");
}
if (order.getStatus() != OrderStatus.UNPAID) {
log.warn("订单已处理: {}", order.getId());
return;
}
// 更新订单状态
order.setStatus(OrderStatus.PAID);
order.setPayTime(LocalDateTime.now());
orderMapper.updateById(order);
// 更新座位状态
seatMapper.batchUpdateStatus(
order.getScheduleId(),
order.getSeats(),
SeatStatus.SOLD.getCode()
);
}
application-prod.yml关键配置:
yaml复制server:
tomcat:
max-threads: 200
min-spare-threads: 20
compression:
enabled: true
mime-types: application/json,application/xml,text/html,text/xml,text/plain
spring:
datasource:
hikari:
maximum-pool-size: 20
connection-timeout: 30000
redis:
lettuce:
pool:
max-active: 50
max-idle: 10
nginx复制upstream backend {
server 127.0.0.1:8080;
keepalive 32;
}
server {
listen 80;
server_name yourdomain.com;
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
location /api/ {
proxy_pass http://backend;
proxy_set_header X-Real-IP $remote_addr;
}
}
集成SpringBoot Actuator:
xml复制<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
application.yml配置:
yaml复制management:
endpoints:
web:
exposure:
include: health,info,prometheus
metrics:
tags:
application: ${spring.application.name}
Logback-spring.xml配置示例:
xml复制<appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<destination>logstash:5044</destination>
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<customFields>{"app":"ticket-system","env":"${spring.profiles.active}"}</customFields>
</encoder>
</appender>
在实际开发这个话剧票务系统的过程中,我深刻体会到几个关键点:首先,合理的分层架构是项目可维护性的基础;其次,对于票务这类高并发场景,必须提前考虑分布式锁和消息队列的应用;最后,微信生态的深度集成能显著提升用户体验。
这个系统还有几个值得扩展的方向:
对于正在做毕业设计的同学,我的建议是:不要追求大而全的功能,而是选择一个核心场景做深做透。比如这个项目中,我把选座和支付这两个核心流程打磨得非常顺畅,这比堆砌一堆华而不实的功能要有价值得多。