ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于Spring Boot的演出售票管理系统设计与部署全解析

基于Spring Boot的演出售票管理系统设计与部署全解析 毕设季的日子我比谁都懂那种“框架学了又好像没学、项目启动报错三小时”的崩溃。我这次做的题目是“基于Java Web的线下演出售票管理系统Spring Boot版”从选题、建表、写后端到写论文、录演示视频、做答辩PPT整整一个月的时间。如果你也正在为这类“管理系统类毕设”发愁或者想搞清楚Spring Boot到底怎么支撑一个完整的售票业务这篇文章就是为你准备的。这套系统到底解决什么问题线下演出话剧、音乐会、体育赛事的票务管理核心就是三件事用户能在网页上看到演出、选座位、下单买票管理员能维护演出场次、管理订单、统计销售数据系统能扛住同一场演出的并发锁票。听起来不复杂但把这三件事串起来就覆盖了Java Web开发里最常见的技术点Maven多模块、Spring Boot自动配置、MyBatis数据访问、MySQL事务、Redis缓存或锁、JWT登录、RBAC权限、ECharts报表每一块都是毕设答辩时老师爱追问的。谁能用得上这篇文章准备做Java后端方向毕业设计的在校生、想快速搭建一个“管理系统”作为求职项目的应届生还有不太清楚Spring Boot项目如何从零部署到服务器的人。我会从题目怎么拆、表怎么建一直讲到代码怎么组织、项目怎么部署、论文怎么写全程给可直接抄作业的配置和步骤也会把我在实际开发中踩过的坑指出来。1. 毕设题目拆解售票管理系统的“边界”到底划在哪里很多人的毕设翻车不是代码写不出来而是题目范围没控制住。我先说说为什么“线下演出售票管理系统”是个好题目以及你应该把功能边界划到哪里。1.1 为什么选“线下演出售票”而不是“电影票”或“火车票”做售票类系统很容易陷入一个误区试图建模“所有种类的票”。一旦你开始兼容“选座一样吗退票规则一样吗票价浮动一样吗”项目复杂度就会瞬间爆炸。线下演出售票和电影票、火车票最大的区别在于演出有“场次座位”的概念同一个演出在不同时间、不同场馆会有不同场次每个场次有独立的座位图而且座位的票价还可能分区域比如内场、看台。这就让数据模型一下子立体起来但又不至于像火车票那样有复杂的余票池和行程规划。另外线下演出售票非常贴合“管理系统”的定位管理员需要维护演出信息、场馆信息、场次安排、座位价格用户则在一个相对简单的界面上完成浏览、选座、下单。前后台分离很清晰做毕业设计展示的时候评委很容易看懂“我到底写了什么”。1.2 功能清单把“全栈功能”砍到“够用且完整”我的建议是功能清单一定要包含用户端和管理端但不要贪多。实际的系统我分成这几个模块用户端注册登录、首页演出列表、演出详情、场次选择、座位选择、订单提交、我的订单、订单支付模拟、个人资料。管理端管理员登录、演出管理增删改查、上架下架、场馆管理、场次管理包含座位图初始化、订单管理查看、退款处理、销售统计按场次、按日期的图表报表。基础支撑用户与权限分离普通用户、管理员两种角色、数据初始化脚本、统一异常处理、接口统一返回格式。砍掉什么砍掉在线支付对接毕设演示用模拟支付点一下“支付成功”即可、砍掉复杂退款流程只做申请和同意、砍掉真实短信通知日志代替、砍掉秒杀级的高并发优化只要做到事务和乐观锁或Redis锁演示。这样设计既完整又有深度而且你完全有精力把每个点做扎实。1.3 技术栈选型Spring Boot 2.7 MyBatis-Plus MySQL Redis Vue如果你的毕设要求是“Java Web”那么前后端不分离也能过但考虑到现在主流开发习惯和答辩观感我强烈建议采用前后端分离前端用Vue 3 Element Plus后端用Spring Boot。后端最终采用的技术组合如下技术版本用途Spring Boot2.7.x基础框架简化配置MyBatis-Plus3.5.xORM减少SQL量MySQL8.0数据存储Redis5.x以上验证码缓存、锁票JWTjjwt0.9.1用户认证Maven3.6项目构建Vue 3框架前端页面Element PlusUI库管理后台组件选择这些不是因为“流行”而是因为MyBatis-Plus能把单表CRUD的代码量减少70%让你把精力放在选座和订单事务上Redis锁正好可以拿来解释“并发锁票”的机制这是答辩亮点JWT避免了Session跨域问题前端携带Token调用接口也符合当前企业里常用的做法。2. 从零搭建Spring Boot骨架目录结构、依赖配置与启动流程这一节带你从空目录开始搭出一个能运行的后端工程。如果你已经用IDEA的Spring Initializr创建过项目可以直接跳到2.3。2.1 Maven依赖一份可直接用的pom.xml核心片段创建Spring Boot项目时最关键的是选择依赖。我踩过的坑是版本兼容Spring Boot 2.7和MyBatis-Plus 3.5.3加上MySQL 8.0驱动这一组组合我实测很稳定。下面是我pom.xml中的核心依赖parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent dependencies !-- Web启动器 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- MyBatis-Plus -- dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency !-- MySQL驱动 -- dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency !-- Redis -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency !-- JWT -- dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt/artifactId version0.9.1/version /dependency !-- Lombok -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency !-- 参数校验 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency !-- Swagger接口文档可选但建议加 -- dependency groupIdio.springfox/groupId artifactIdspringfox-boot-starter/artifactId version3.0.0/version /dependency /dependencies提醒一点当你看到“spring-boot-starter-parent”的版本是2.7.x时对应JDK建议用1.8或11不要一上来就装JDK 17跑Spring Boot 2.7的旧项目容易在CGLIB代理或内嵌容器初始化时报奇怪错误。2.2 项目目录结构按模块分包而不是按层分包很多教材习惯把Controller、Service、Mapper各建一个包再把所有业务类堆进去。项目小的时候还行但毕设系统一旦涉及用户、演出、订单三个域这种结构会让你后期改一个订单功能时同时动四五个地方的代码。我推荐按功能模块分包com.stage.ticket ├── common // 通用类、统一返回、异常处理、工具类 │ ├── Result.java │ ├── BusinessException.java │ └── GlobalExceptionHandler.java ├── config // 配置类RedisConfig、WebMvcConfig、JWT拦截器 ├── controller // 控制层按模块建子包 │ ├── user │ ├── show │ ├── order │ └── admin ├── service // 业务层接口 impl实现 ├── mapper // MyBatis-Plus的Mapper接口 ├── entity // 数据库实体 ├── dto // 请求/响应参数对象 └── interceptor // JWT拦截器按模块分包看起来是“乱一点”但对个人项目非常友好比如你要加“退票”功能直接从Controller层的order包进去顺着service/order包找到对应服务Mapper里的订单SQL也集中放在一起。答辩时老师问你“订单模块在哪些文件里”你可以用三句话讲清楚不会手忙脚乱。2.3 application.yml配置与启动验证我见过太多人因为配置文件踩坑MySQL时区不对、Redis密码没配、Jackson序列化循环引用。下面是我的参考配置server: port: 8080 spring: application: name: ticketing-system datasource: url: jdbc:mysql://localhost:3306/stage_ticket?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver redis: host: localhost port: 6379 # 如果没密码就注释掉下面这行 password: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: id-type: auto jwt: secret: your-secret-key-for-jwt-token expire-minutes: 120启动类如果放在com.stage.ticket包下直接运行main方法。启动成功后访问http://localhost:8080/doc.htmlSpringfox 3.0的接口页面地址能看到接口分组说明骨架通了。如果启动时遇到“Failed to configure a DataSource”要么是MySQL没建库要么是依赖版本冲突导致DataSource自动配置失败先把数据库建好CREATE DATABASE stage_ticket DEFAULT CHARACTER SET utf8mb4;3. 数据模型与建表演出、场次、座位、订单的关系设计数据模型是整个系统的灵魂。我第一版设计踩了一个大坑把“座位”直接挂在“演出”下面结果同一演出在不同城市不同场次时座位完全错乱。后来才发现必须拆成“场馆-座位”和“场次-座位状态”两层。3.1 五张核心表一张辅助表核心表设计如下表名字段要点作用userid, username, password, nickname, role, phone用户与管理员共用role区分venueid, name, address, seat_rows, seat_cols演出场馆矩形座位图行列数showid, title, category, description, cover_url, start_time, venue_id演出基本信息和所属场馆sessionid, show_id, venue_id, session_time, status场次同一演出可以有多场orderid, order_no, user_id, session_id, total_amount, status, create_time订单主表order_itemid, order_id, seat_row, seat_col, price订单明细记录具体坐了哪一排哪一座其中order_item不是独立下单入口而是把订单和座位连接起来的关键。用户选中多个座位生成一条订单主记录和N条明细记录。3.2 座位状态模型用0/1/2表示可售、锁定、已售座位图是二维的我用简单方式处理把“场馆-场次-座位”关联表单独建了一张seat_status表字段包含session_id, row_no, col_no, status。每次管理员创建一个场次时系统自动根据场馆的行列数初始化所有座位的status为0可售。用户选座提交订单时把选中的座位status改为1锁定支付成功后改为2已售。为什么要“锁定”因为如果用户下单时直接改成已售订单超时取消后座位的状态很难恢复。锁定状态的意义是你可以写一个定时任务把超过30分钟仍未支付的订单取消同时把对应的座位status改回0。这个逻辑虽然简单但在答辩时能讲出实际业务中的“库存保护”意识。3.3 建表SQL关键片段注意索引与唯一约束CREATE TABLE show ( id bigint NOT NULL AUTO_INCREMENT, title varchar(100) NOT NULL, category varchar(20) DEFAULT NULL, description text, cover_url varchar(255) DEFAULT NULL, venue_id bigint NOT NULL, start_time datetime NOT NULL, status tinyint DEFAULT 1, PRIMARY KEY (id), KEY idx_venue_id (venue_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE seat_status ( id bigint NOT NULL AUTO_INCREMENT, session_id bigint NOT NULL, row_no int NOT NULL, col_no int NOT NULL, status tinyint NOT NULL DEFAULT 0, PRIMARY KEY (id), UNIQUE KEY uk_session_row_col (session_id, row_no, col_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;seat_status表上的唯一约束很关键它保证同一场次同一座位不会因为并发请求产生两行数据。数据库层面的唯一约束是防并发锁票的最后一道防线。如果你的毕设只讲了Java层的锁答辩老师多半会追问“数据库层面怎么防重”这张表的唯一键就是答案。3.4 模拟数据与数据初始化我还写了一组data.sql里面包含3个场馆、5场演出、2场音乐会、3场话剧以及每场演出的座位初始化。注意Spring Boot默认不会自动执行data.sql除非配置spring.sql.init.modealways我更推荐直接在Navicat里导入SQL文件或者在系统启动时通过ApplicationRunner执行一个初始化方法。启动时初始化的好处是演示环境换电脑后拉下来就能跑不用手动导库。4. 售票核心流程选座、锁票、下单的事务与并发控制如果整个毕设只能挑一段代码讲给答辩老师我建议挑“提交订单”这个方法。它囊括了参数校验、库存检查、座位锁定、订单生成、金额计算、状态流转还涉及事务与并发。4.1 提交订单的完整时序从前端传过来的数据结构是sessionId、selectedSeats座位列表[1_2, 3_4]、userId。后端拿到按以下顺序处理校验用户是否登录、场次是否存在且未取消检查选中的座位是否都在“可售”状态计算金额座位的价格来自seat_status对应的票价区域字段或者来自场次统一的票价生成唯一订单号建议用时间戳随机数或数据库序列在事务中插入order、order_item同时更新seat_status的status为1返回订单号提示用户去“支付”。4.2 事务失效的三个经典坑我第一版写完发现插入订单成功但座位状态总是不变排查后是三个问题没有加Transactional注解或者加了注解但方法被同类内部调用导致事务不生效异常被try-catch吞掉事务感知不到RuntimeException使用了非InnoDB引擎的表。MySQL 5.7默认是InnoDB没问题但如果之前有人误导你建表时用了MyISAM事务会静默失效。我的做法是给OrderServiceImpl.createOrder()加Transactional(rollbackFor Exception.class)并在方法内部不要自己去捕获异常统一交给全局异常处理器。提交订单代码如下Transactional(rollbackFor Exception.class) public OrderVO createOrder(Long userId, OrderCreateRequest request) { Session session sessionMapper.selectById(request.getSessionId()); if (session null || session.getStatus() 0) { throw new BusinessException(场次不存在或已取消); } ListSeatVO seats request.getSelectedSeats(); for (SeatVO seat : seats) { SeatStatus seatStatus seatStatusMapper.selectOne( new LambdaQueryWrapperSeatStatus() .eq(SeatStatus::getSessionId, request.getSessionId()) .eq(SeatStatus::getRowNo, seat.getRowNo()) .eq(SeatStatus::getColNo, seat.getColNo())); if (seatStatus null || seatStatus.getStatus() ! 0) { throw new BusinessException(座位 seat.getRowNo() 排 seat.getColNo() 列已不可选); } } BigDecimal totalAmount calculateAmount(session, seats); String orderNo generateOrderNo(); Order order new Order(); order.setOrderNo(orderNo); order.setUserId(userId); order.setSessionId(request.getSessionId()); order.setTotalAmount(totalAmount); order.setStatus(0); // 待支付 orderMapper.insert(order); for (SeatVO seat : seats) { OrderItem item new OrderItem(); item.setOrderId(order.getId()); item.setSeatRow(seat.getRowNo()); item.setSeatCol(seat.getColNo()); orderItemMapper.insert(item); // 锁定座位 seatStatusMapper.updateSeatLocked(request.getSessionId(), seat.getRowNo(), seat.getColNo()); } return buildOrderVO(order); }上面的updateSeatLockedSQL用一条UPDATE语句实现条件更新UPDATE seat_status SET status 1 WHERE session_id #{sessionId} AND row_no #{rowNo} AND col_no #{colNo} AND status 0这条SQL能影响的行数只有0或1。如果返回值是0说明座位已经被别人抢走了后续可以主动抛出异常回滚事务。这个“先查再改”和“以UPDATE影响行数为准”的写法比单纯select再update更稳妥因为数据库帮助你完成了原子性比较。4.3 并发场景Redis锁与数据库行锁怎么选毕设系统实际并发量不高但老师很爱问“如果一万个人同时买同一场演出的同一个座位你的系统会怎样”我的回答分两层第一层数据库行锁已经保证不会超卖因为UPDATE ... WHERE status0在InnoDB下会对命中行加锁另一个事务的相同UPDATE会等待第二层为了减少数据库压力我引入了Redis分布式锁在createOrder方法前加锁key设计为lock:seat:{sessionId}:{row}_{col}锁过期时间60秒。加了Redis锁后同一座位的重复请求会先被拦截在Redis这一层只有拿到锁的请求才会去执行数据库操作。这里必须强调Redis锁不是必须的但写了它就是你的加分项。我实测压测200个线程并发选同一个座位不锁的时候偶发“座位重复”提示加了Redis锁和数据库条件更新后即使200个请求全部进来最终也只有一个人能成功锁定座位其他请求全部返回“该座位已被锁定”。4.4 支付模拟与订单状态的流转不做真实支付但状态的流转一定要完整。我设计了三个状态0待支付、1已支付、2已取消。用户点“立即支付”时后端把订单状态从0改成1同时把对应座位状态从1改成2。如果超过30分钟未支付订单状态改成2座位释放回0。这个定时任务我直接用Spring自带的Scheduled实现每30秒扫一次Scheduled(fixedDelay 30000) public void autoCancelExpiredOrders() { ListOrder orders orderMapper.selectList( new LambdaQueryWrapperOrder() .eq(Order::getStatus, 0) .lt(Order::getCreateTime, LocalDateTime.now().minusMinutes(30))); for (Order order : orders) { expireOrder(order.getId()); } }expireOrder方法内部先判断订单状态是否仍为0再更新订单状态并释放该订单对应的所有座位。这步也必须有事务否则座位释放和订单状态更新不同步会出现票已经取消了但座位还锁着的情况。5. 管理后台的关键模块演出排期、订单处理与销售统计管理端比用户端更体现“管理”特性评委也喜欢看这里。5.1 项目启动时自动初始化座位图管理员新建“场次”时前端选择所属演出、所属场馆、开始时间之后后端需要给该场次生成一张全可售的座位图。如果前端有几千个座位后端循环插入几千条记录会很慢吗我的做法是批量插入MyBatis-Plus的saveBatch并不能直接传入海量数据所以我手动写了一个批量insert SQLINSERT INTO seat_status(session_id, row_no, col_no, status) VALUES foreach collectionlist itemitem separator, (#{item.sessionId}, #{item.rowNo}, #{item.colNo}, 0) /foreach注意MySQL单次insert的包大小有限制如果场馆很大比如50行x50列2500个座位一次insert没问题如果更大的场馆至少分批每批1000条。另外管理员删除场次时要同时把seat_status的对应数据删除否则会留下孤儿数据。5.2 订单管理列表、详情、退款处理管理端的订单列表需要关联查出场次和用户信息不能只查order表。我用MyBatis-Plus的自定义SQL写一个PageOrderVO联表查询这里给出核心SQLSELECT o.id, o.order_no, o.total_amount, o.status, o.create_time, s.title AS show_title, sess.session_time, u.username FROM order o LEFT JOIN session sess ON o.session_id sess.id LEFT JOIN show s ON sess.show_id s.id LEFT JOIN user u ON o.user_id u.id WHERE o.user_id #{userId} ORDER BY o.create_time DESC建议Status字段不要只给数字前端映射成中文标签即可。退款处理逻辑也不复杂管理员点击“同意退款”后端校验订单已支付且未过期再把订单状态改成2同时把座位状态恢复到0。退款这里要注意不要直接删除订单而是保留完整记录便于对账和审计这在答辩中是“业务完整性”的体现。5.3 销售统计用ECharts画折线图和饼图统计模块是很多毕设容易忽略但很出彩的部分。我做了两个维度按日期的销售额折线图查询最近7天按日期分组统计已支付订单的金额总和按演出类别的饼图统计每个类别演出的售出票数展示各品类占比。后端接口返回很简单就是返回ListMapString, Object。比如日期销售额SELECT DATE(create_time) AS day, SUM(total_amount) AS amount FROM order WHERE status 1 AND create_time DATE_SUB(CURDATE(), INTERVAL 6 DAY) GROUP BY DATE(create_time) ORDER BY day前端ECharts拿到数据后我直接渲染。这段虽然不算难但视觉效果好答辩时直接在演示视频里展示图表比你口述数据库里有多少条数据震撼得多。6. JWT认证与角色权限别再只用Session判断登录了传统的Java Web毕设常用Session但如果你做前后端分离Session的跨域处理会比较麻烦。我用JWT实现登录并且用拦截器区分“用户接口”和“管理员接口”。6.1 JWT的生成与拦截器设计登录成功时后端根据用户id和角色生成Token返回给前端前端每次请求在Header里带Authorization: Bearer token。我写了一个JwtInterceptor在WebMvcConfig中注册并配置拦截路径public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException { String auth request.getHeader(Authorization); String token ; if (auth ! null auth.startsWith(Bearer )) { token auth.substring(7); } try { Claims claims JwtUtil.parseToken(token); request.setAttribute(userId, claims.get(userId)); request.setAttribute(role, claims.get(role)); return true; } catch (Exception e) { response.setStatus(401); response.getWriter().write({\code\:401,\msg\:\未登录或登录过期\}); return false; } } }路径拦截配置如下/api/user/**需要登录但任何角色都能访问/api/admin/**需要登录且角色为ADMIN所以我又在拦截器里对/api/admin/**额外判断了role。这里有个细节JWT Token如果生成时不加过期时间或者过期时间设置太长答辩容易被问。我的方案是2小时过期并且前端拿到Token后把它存入localStorage请求拦截器统一加Header响应401时跳转登录页。6.2 为什么我不单独建一张“管理员表”有些同学设计时把admin单独建表导致登录逻辑要查两张表。我统一用一张user表用role字段区分默认USER管理员账号通过SQL脚本预先插入roleADMIN。这样做的好处是登录接口不需要判别身份来源只要查user表即可后续如果要加“超级管理员”也只要加一个role值。坏处是普通用户表里混入了管理员但毕设这种量级完全不敏感。6.3 密码存储一定要加盐哈希哪怕这是个毕设我也不建议明文存密码。我用了Spring Security的BCryptPasswordEncoder单独引入加密工具类而不引入整个Spring Security因为整个Security框架会带来复杂的机制配置容易把你劝退。注册时加密存储登录时用matches方法校验。答辩时提到BCrypt比直接写MD5有说服力得多。7. LW论文/文档写作要点从“代码实现”反向推导章节逻辑很多人的毕设论文是把代码贴一遍这大错特错。论文的重点是“为什么这样设计”不是“代码怎么写的”。我按这个逻辑组织论文顺利过审7.1 论文目录结构参考绪论背景与意义、国内外现状、主要工作相关技术Spring Boot、MyBatis-Plus、Vue、JWT需求分析可行性分析、功能需求、角色分析系统设计架构设计、模块设计、数据库设计这部分用ER图和表结构系统实现分用户端、管理端按“业务目标→实现思路→关键代码→界面展示”小节讲述系统测试功能测试、并发测试、结果分析总结与展望注意“系统实现”不要写成一堆代码堆砌每个功能只用关键片段文字要描述“该方法调用了哪些服务、事务是怎么控制的、异常如何抛出”这些句子才是论文的血肉。7.2 论文中必放两张核心图第一张是系统架构图从前端Vue、后端Controller、Service、Mapper到MySQL和Redis的分层图。第二张是业务流程图用户浏览演出→选择场次→选座→提交订单→支付→生成票。这两张图几乎必考而且把整个系统讲清楚了。7.3 查重与降重建议毕设查重一般是查“文字部分”代码通常不参与查重。我的经验是关键概念自己复述不要整段复制博客或官方文档表格、截图、流程图的占比越高重复率越容易控制。数据库设计那章每次我写字段说明时都用表格列举大量减少了连续文字的重复概率。8. 部署上线的完整流程与演示视频录制技巧毕设的“部署说明”往往是打包成一个文本文档里面写清楚怎么跑起来就行。但很多人在这一步翻车因为本机能跑换台电脑就起不来。我总结一套稳的流程。8.1 后端打包要不要打成jar包我建议打成jar包而不是war包。Spring Boot内置Tomcat直接mvn clean package -DskipTests生成target/ticketing-system-0.0.1-SNAPSHOT.jar。用java -jar运行。如果你的环境没有安装Maven也可以用IDEA右侧的Maven面板双击package。这一步经常遇到打包报错“找不到主类”或“程序包不存在”多半是没先执行mvn clean或者子模块依赖没install。8.2 Linux服务器部署从零到跑起来我是在阿里云服务器上部署的系统是Ubuntu 22.04步骤如下# 更新包管理器 sudo apt update # 安装JDK 8或11 sudo apt install openjdk-11-jdk # 安装MySQL 8.0 sudo apt install mysql-server # Redis sudo apt install redis-server # 将jar包上传到服务器 scp ticketing-system-0.0.1-SNAPSHOT.jar rootyour_server_ip:/opt/ticket/ # 启动后端 java -jar /opt/ticket/ticketing-system-0.0.1-SNAPSHOT.jar --spring.profiles.activeprod 注意服务器上数据库字符集必须utf8mb4否则中文乱码。启动后先看日志是否出现Started TicketingApplication in X seconds再测试接口是否能访问。我还会用nohup的方式启动同时把日志输出到文件nohup java -jar /opt/ticket/app.jar /opt/ticket/log.log 21 如果网页无法访问先排查安全组是否放行8080端口再排查server.address是否配置成0.0.0.0默认就是。8.3 演示视频的录制别照本宣科按场景讲演示视频是给老师看的“剧透”录的时候不要静态地念界面。我的录制脚本是这样的从管理员界面登录演示新建一个场馆、创建一个演出、排一个场次并展示系统自动生成的座位图切到用户端注册登录购买一张票展示选座、提交订单、模拟支付切回管理端查看订单确认支付状态打开销售统计图表最后提到并发场景打开两个页面同时买同一个座位展示只有一个成功。视频总时长控制在8-12分钟不用剪辑得很花哨但一定要保证画质清晰、鼠标不飘忽。很多同学的演示视频是拿手机录屏这种不建议直接用OBS或IDEA自带的录屏工具1080P分辨率。9. 作为过来人的踩坑清单与最终检查我最后想给你一份自己排查过的清单每一项都是我在实际开发中真实遇到的对照检查能省你很多时间。端口占用启动时报Port 8080 was already in use用netstat -tunlp | grep 8080找到占用进程并kill或者修改server.port。MySQL连接失败连接URL里serverTimezoneAsia/Shanghai一定要加上另外MySQL 8.0的驱动类是com.mysql.cj.jdbc.Driver不是com.mysql.jdbc.Driver。Redis连接超时本机启动Redis后默认只监听127.0.0.1不需要密码如果远程连不上修改bind 0.0.0.0和requirepass。前端跨域如果在Vue项目里访问后端http://localhost:8080要么在后端加CORS配置要么在Vue的vue.config.js里设置proxy我个人更推荐proxy因为部署时前端和后端同域名时不用改代码。时间字段显示不对MySQL时区和Jackson时区都改为Asia/Shanghai否则你明明存了14:00打印出来却是06:00。MyBatis-Plus分页失效3.5.x版本需要手动配置MybatisPlusInterceptor分页插件不配置的话Page查询会返回全部数据。一定要在Config里加Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; }临时文件混乱项目中的上传图片比如演出封面不要存到数据库的BLOB字段最好存到服务器的/upload目录然后在数据库中存访问路径。这样部署和备份都方便得多。最后再聊一点个人体会。毕设的意义不在于你用了多少高深技术而在于把一个完整的业务闭环讲清楚。线下演出售票系统这个题目足够你用Spring Boot把CRUD、事务、缓存、权限、报表这些核心技能全部串起来而且每一块你都能说清楚“为什么这么设计”。我做完这个项目后最明显的变化是看别人写的开源项目能在心里快速映射出表结构、判断某个功能是放在Service还是Controller。如果你也能达到这个状态答辩现场你就不会再慌了。你自己动手做的时候如果卡在哪个环节记住八成是依赖版本问题两成是业务逻辑问题。把Maven依赖统一把数据库字符集统一把时间时区统一80%的报错都会消失。剩下的就一个一个debug日志看下去。祝你也把系统完整跑起来答辩顺利。
返回列表