Spring Boot 3+Vue3出行旅游系统实战:从零到答辩全流程解析
2026/9/20 8:20:41 网站建设 项目流程

最近帮一个学弟把一套基于 Spring Boot 3 的出行旅游安排系统完整跑通了,从环境配置到数据库初始化,再到前端联调,前前后后折腾了两天。这个课题在毕业设计里属于比较典型的“业务管理系统”路线,功能边界清晰、技术栈主流、可扩展性强,不管是用来应付答辩还是作为简历上的项目经验,性价比都很高。我干脆把整个项目的拆解思路、核心模块实现、踩坑记录全部整理出来,方便正在做同类课题的同学直接参考。

这套系统本质上解决的是“旅行计划怎么管”的问题。传统方式是用 Excel 或者备忘录记录景点、酒店、交通、行程顺序,信息分散而且没法协作。系统把这些信息统一收口,做成用户注册登录、景点检索、行程线路规划、收藏、评论、预订管理的闭环。技术方案上后端用 Spring Boot 3 + MyBatis Plus,前端用 Vue 3 + Element Plus,数据库 MySQL 8,权限认证采用 JWT + Spring Security 6,这也是当前 Java 生态里非常主流的一套组合。

如果你正准备做这个课题,或者已经在开发过程中被各种报错卡住,这篇文章值得从头到尾看一遍。我会把项目拆成六个部分来写:业务设计、技术选型、数据库设计、核心模块实现、坑点排查、答辩准备,每一部分都给到可以直接落地的细节。

1. 先拆清楚:一个“出行旅游安排系统”到底要做什么

1.1 核心业务场景

很多同学拿到课题后第一反应是打开 IDEA 开始建工程,这是错的。我吃过这个亏,上来就写代码,结果写了半个月发现业务逻辑根本没法闭环。正确的做法是先把用户场景写清楚。

这套系统的用户主要分两类。一类是普通游客,他们的痛点是:去一个陌生城市玩,不知道有哪些景点值得去、不知道路线怎么安排最合理、酒店和景点之间怎么串起来不绕路。另一类是系统管理员,他们需要维护景点信息、审核评论、管理用户状态、发布公告或推荐线路。

围绕这两类用户,系统至少要具备以下能力:游客注册登录、浏览景点列表和详情、按关键词或分类搜索景点、查看系统推荐的旅游线路、收藏感兴趣的景点、对去过的景点进行评价、模拟预订酒店或景点门票。管理员则需要一个独立的后台,能对景点进行增删改查,能管理用户,能查看订单和评论状态。

1.2 角色划分与用例

我建议把角色拆成三种:普通用户(USER)、管理员(ADMIN)、游客(未登录的匿名用户)。匿名用户只能浏览景点和线路,一旦涉及收藏、评论、预订就必须登录。这个设计看起来简单,但它直接影响了后端的权限控制逻辑,Spring Security 的配置全是围绕角色来写的。

核心用例可以整理成表格:

角色用例
匿名用户浏览景点列表、查看景点详情、搜索景点、查看推荐线路
普通用户登录注册、收藏景点、取消收藏、发表评论、预订酒店/门票、查看个人订单
管理员景点管理(CRUD)、用户管理(禁用/启用)、评论审核、订单查看、线路发布

这个用例表就是后面设计接口和数据库表的第一手依据。每一条用例对应一个或几个后端接口,做到后面你会发现,代码基本都是围绕这些用例堆出来的。

1.3 行程规划是怎么实现“智能”的

市面上很多毕设课题名字里带“智能”两个字,但实际做出来的东西并不智能,答辩的时候很容易被老师问住。我在这套系统里做了一个折中的方案:基于景点热度、评分、地理位置做简单的推荐排序,然后按照行政区划和游览路线把景点串成推荐线路。

具体做法是这样的:后台管理员提前维护几条推荐线路,每条线路包含一个有序的景点列表,顺序依据是地理位置的邻近程度和游览时的动线合理性。比如某条线路是“市区文化一日游”,那么景点顺序就是博物馆 → 老街 → 城隍庙,这三个景点在地理位置上连成一条线,游客不需要来回折返。

用户在前端选择一条推荐线路后,系统会自动把这条线路下的景点按顺序展示出来,同时计算每个景点之间的距离和预计游玩时长,生成一个简单的行程时间表。这套逻辑不复杂,但比市面上那些单纯的“景点列表”高级不少,在答辩中也能讲出点东西来。

1.4 功能模块拆分

完整的功能模块拆出来大概是这样的:用户模块、景点模块、线路推荐模块、收藏模块、评论模块、订单模块、公告模块、后台管理模块。每个模块都不是孤立存在的,模块之间通过外键和业务逻辑关联。比如收藏模块依赖用户和景点,订单模块依赖用户、景点、酒店。

模块拆好以后,开发顺序也很关键。我建议按照“用户模块 → 景点模块 → 收藏/评论 → 线路推荐 → 订单 → 后台管理”的顺序来做,先把地基打好,再往上盖楼。这样每做完一个模块都能跑起来验证,不会出现到最后才集中联调导致修罗场的情况。

2. 技术选型:为什么是 Spring Boot 3 + MyBatis Plus + Vue 3

2.1 后端为什么选 Spring Boot 3

Spring Boot 3 是当前 Java 后端开发的主流版本,和 2.x 相比最大的变化是必须要 JDK 17 及以上,同时底层使用了 Jakarta EE 9 的命名空间。很多同学在这里踩坑,因为网上的老教程全是基于 Spring Boot 2.x + JDK 8 的,照着写一堆代码后发现 javax 包找不到,这就是版本差异导致的。

选 Spring Boot 3 还有一个务实的原因:现在不少高校的毕业设计题目里都明确写了“基于 Spring Boot 3”,或者要求在简历上体现最新的技术栈。相比 2.x,Spring Boot 3 在性能、安全性(比如默认的安全配置调整)方面都有提升,未来一段时间内的生态支持也会更好。

2.2 持久层选型:MyBatis Plus 而不是 JPA

Java 后端操作数据库的主流方案有两个:Spring Data JPA 和 MyBatis Plus。对于这种业务管理系统,我强烈推荐 MyBatis Plus,理由有三点:

第一,MyBatis Plus 的 CRUD 接口是现成的,继承 BaseMapper 之后,单表操作基本不用写 SQL。比如景点表的分页查询、条件过滤,直接调用 selectPage 配合 LambdaQueryWrapper 就能搞定,开发效率比手写 SQL 高很多。

第二,MyBatis Plus 的代码生成器能把 entity、mapper、service、controller 一次性生成出来,对于一个有七八张表的毕设项目来说,能省掉大量重复劳动。

第三,国内公司用 MyBatis 系的比例远高于 JPA,写在简历上更贴合实际就业需求。如果以后去实习或者工作,这一套技能是可以直接迁移的。

2.3 前端方案:Vue 3 + Element Plus

前端选 Vue 3 + Element Plus 基本上是当前中小型管理系统的最优解。Vue 3 的 Composition API 让代码逻辑更清晰,Element Plus 提供了一套完成度很高的 UI 组件库,表格、表单、弹窗、分页这些后台管理常用的组件全是现成的,改改样式就能用。

因为毕设系统需要同时服务于 C 端用户(游客浏览页面)和 B 端用户(后台管理),我会把前端分成两个端来写。用户端负责景点展示、线路浏览、个人中心,界面要偏展示型;管理端负责数据维护,界面就是典型的管理后台布局。两个端共用一套后端 API,通过不同路由来区分。

2.4 工程目录结构

整个项目采用前后端分离架构,后端工程目录如下:

travel-system/ ├── src/main/java/com/example/travel/ │ ├── TravelApplication.java │ ├── config/ │ │ ├── SecurityConfig.java │ │ ├── MybatisPlusConfig.java │ │ └── CorsConfig.java │ ├── controller/ │ │ ├── UserController.java │ │ ├── AttractionController.java │ │ ├── RouteController.java │ │ ├── FavoriteController.java │ │ ├── CommentController.java │ │ └── OrderController.java │ ├── service/ │ │ ├── impl/ │ ├── mapper/ │ ├── entity/ │ ├── dto/ │ ├── common/ │ │ ├── Result.java │ │ ├── ResultCode.java │ │ └── GlobalExceptionHandler.java │ └── utils/ │ └── JwtUtils.java ├── src/main/resources/ │ ├── application.yml │ ├── application-dev.yml │ └── mapper/ └── pom.xml

这个结构尽量遵循“按技术分层、按业务分包”的原则,controller 里不写业务逻辑,service 里不直接操作数据库,保证每层的职责单一。

2.5 依赖清单与版本对照

pom.xml 里的核心依赖要注意版本对齐,这部分我实测过很多次,直接照抄没问题:

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.2.5</version> <relativePath/> </parent> <properties> <java.version>17</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-spring-boot3-starter</artifactId> <version>3.5.7</version> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.5</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies>

注意两个细节。第一,MyBatis Plus 从 3.5.7 开始提供了专门的 spring-boot3-starter,不要再引入旧的mybatis-plus-boot-starter,否则在 Spring Boot 3 下会直接启动失败。第二,JDK 17 的--add-opens参数可能导致某些反射相关告警,但一般不影响运行,可以在启动参数里加上--add-opens java.base/java.lang=ALL-UNNAMED来消除。

3. 数据库设计与核心配置

3.1 表结构设计

表设计是这套系统能不能跑顺的关键。我建议最少建八张表:用户表、景点分类表、景点表、评论表、收藏表、线路表、线路景点关联表、订单表。在此基础上,如果还要做公告功能,再加一张公告表。

用户表的核心字段:

CREATE TABLE `t_user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL COMMENT '用户名', `password` varchar(100) NOT NULL COMMENT '加密后的密码', `nickname` varchar(50) DEFAULT NULL COMMENT '昵称', `avatar` varchar(255) DEFAULT NULL COMMENT '头像URL', `phone` varchar(20) DEFAULT NULL COMMENT '手机号', `email` varchar(100) DEFAULT NULL COMMENT '邮箱', `role` tinyint(1) NOT NULL DEFAULT '0' COMMENT '角色 0-普通用户 1-管理员', `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '状态 1-正常 0-禁用', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

景点表设计时要注意几个细节:景点图片用 JSON 数组存多张图,方便前端轮播;经纬度字段用 decimal(10, 6) 来存,为后续“附近景点”功能预留;门票价格用 decimal(10, 2);热度字段可以用一个 int 类型的 score 来表示,数值越高推荐优先级越高。

线路表和景点之间是多对多关系,所以需要一张关联表t_route_attraction,字段就是线路 ID 和景点 ID,再加上一个 sort_order 字段表示景点在线路中的顺序。这是路线推荐模块的核心数据结构。

3.2 订单表和收藏表的设计细节

订单表是系统中业务闭环的关键。字段包括订单号、用户 ID、订单类型(门票/酒店)、关联的商品 ID、商品名称、单价、数量、总价、状态(待支付/已支付/已取消)、创建时间。最佳实践是引入一个冗余的goods_name字段,这样查询订单列表的时候就不需要再 join 景点表或酒店表,减少联表压力。

收藏表相对简单,唯一需要注意的就是加一个联合唯一索引,防止用户对同一个景点重复收藏:UNIQUE KEY uk_user_attraction (user_id, attraction_id)。这比在代码里先查后插的防重方式更可靠,数据库层面就把脏数据拦截住了。

3.3 application.yml 配置要点

Spring Boot 3 + MyBatis Plus 的核心配置如下:

server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/travel_system?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: root jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl map-underscore-to-camel-case: true global-config: db-config: id-type: auto logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0 jwt: secret: your-secret-key-your-secret-key-your-secret-key expire: 604800

这里要特别注意两点。第一,数据库连接串里一定要加serverTimezone=Asia/Shanghai,否则时间字段会在数据库和 Java 之间转换时差问题。第二,MyBatis Plus 的逻辑删除配置要开启,这样删除景点时走的是逻辑删除而不是物理删除,用户在前端看到的已删除数据会自动过滤,管理端还能恢复,这个机制在答辩时很加分。

3.4 统一返回结构和异常处理

前后端分离项目必须有一个统一的接口返回格式,否则前端处理后端数据时空值、报错信息都没法统一处理。我定义了一个 Result 类:

@Data public class Result<T> { private Integer code; private String message; private T data; public static <T> Result<T> success(T data) { Result<T> result = new Result<>(); result.setCode(200); result.setMessage("success"); result.setData(data); return result; } public static <T> Result<T> error(Integer code, String message) { Result<T> result = new Result<>(); result.setCode(code); result.setMessage(message); return result; } }

全局异常处理用@RestControllerAdvice来统一处理业务异常和参数校验异常。这里有一个很容易踩的坑:业务异常如果直接在 Controller 里手动 try-catch,代码会非常冗余,而且异常信息难以统一。我用全局异常处理器之后,Service 层只需要抛出自定义的业务异常,前端就能收到结构化的错误信息。

4. 核心功能实现详解:从登录到行程规划

4.1 JWT 登录认证与 Spring Security 6 的整合

Spring Security 6 相比 5.x 变化很大,最大的坑就是WebSecurityConfigurerAdapter被废弃了,现在必须用SecurityFilterChain的 Bean 方式来配置。我最初照着老教程写,发现方法找不到,查了半天才发现是版本兼容问题。

核心配置类是 SecurityConfig,关键代码:

@Configuration @EnableWebSecurity public class SecurityConfig { @Resource private JwtAuthenticationFilter jwtAuthenticationFilter; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.csrf(AbstractHttpConfigurer::disable) .cors(Customizer.withDefaults()) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth .requestMatchers("/api/user/login", "/api/user/register").permitAll() .requestMatchers("/api/attraction/list", "/api/attraction/detail/**").permitAll() .requestMatchers("/api/route/list", "/api/route/detail/**").permitAll() .requestMatchers("/api/admin/**").hasRole("ADMIN") .anyRequest().authenticated() ) .exceptionHandling(ex -> ex.authenticationEntryPoint(unauthorizedHandler)) .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } }

JWT 认证过滤器的作用是拦截每个请求,从 Header 中取出 token,解析成功后把用户信息放入 SecurityContext。这里最关键的实现要点是:token 解析必须在过滤器里完成,然后在 SecurityContextHolder 里设置 Authentication 对象,后面的接口才能通过@AuthenticationPrincipal或者从 SecurityContext 中拿到当前登录用户。

密码加密使用 BCryptPasswordEncoder,注册时加密存储,登录时用 matches 方法校验,千万不要自己写 MD5 加盐方案,BCrypt 是行业标准,答辩时老师问到密码安全方案可以解释得比较从容。

4.2 景点模块:列表、搜索、分页

景点列表是用户端最核心的接口。使用 MyBatis Plus 的分页插件实现,配合 LambdaQueryWrapper 做条件过滤:

public Page<Attraction> getAttractionPage(int page, int size, String keyword, Long categoryId) { Page<Attraction> p = new Page<>(page, size); LambdaQueryWrapper<Attraction> wrapper = new LambdaQueryWrapper<>(); wrapper.like(StringUtils.hasText(keyword), Attraction::getName, keyword) .eq(categoryId != null, Attraction::getCategoryId, categoryId) .orderByDesc(Attraction::getScore); return attractionMapper.selectPage(p, wrapper); }

分页插件在 MyBatis Plus 配置类里通过MybatisPlusInterceptor注册,代码网上很多,但要注意版本对应。搜索功能建议用 MySQL 的 LIKE 即可,不需要上 Elasticsearch,毕设项目用 ES 反而会让复杂度失控,重点是讲清楚业务逻辑。

4.3 收藏和评论模块的实现

收藏功能的核心点是幂等。也就是说用户连续点击两次收藏,第二次应该提示“已经收藏过了”而不是插入一条重复数据。后端处理方案是数据库联合唯一索引 + 先查再插/直接捕获唯一索引冲突。

评论模块相对复杂一点,涉及用户表联表查询昵称头像。这里我会建议用 MyBatis Plus 的注解 SQL 来写:

@Select("SELECT c.*, u.nickname, u.avatar FROM t_comment c " + "LEFT JOIN t_user u ON c.user_id = u.id " + "WHERE c.attraction_id = #{attractionId} " + "ORDER BY c.create_time DESC") List<CommentVO> listCommentByAttractionId(@Param("attractionId") Long attractionId);

评论接口需要做敏感词过滤的铺垫吗?如果你的论文里写了“内容安全”相关的模块,可以在评论提交时做简单过滤,用拦截器检查内容中是否包含预先配置的敏感词库。不过这属于加分项,时间不够可以只做长度校验。

4.4 行程推荐线路模块

线路推荐模块是我在这套系统里最想强调的亮点。它不仅是简单的 CRUD,而是涉及一个相对有业务逻辑的算法:如何把景点串成一条合理的游览线路。

我采用了一个简单的贪心策略:从起点(通常是用户所在酒店或市区中心点)出发,每次选择距离当前点最近且未被访问的景点,逐步访问完所有景点。这个策略基于“最近邻算法”,在旅游线路规划中常用,虽然不一定是最优解,但实现简单、效果直观,而且答辩时能讲明白算法思想。

核心实现思路:

public List<Attraction> planRoute(List<Attraction> attractions, Coordinate startPoint) { List<Attraction> result = new ArrayList<>(); Coordinate current = startPoint; List<Attraction> remaining = new ArrayList<>(attractions); while (!remaining.isEmpty()) { Attraction nearest = null; double minDist = Double.MAX_VALUE; for (Attraction a : remaining) { double dist = calculateDistance(current, a.getCoordinate()); if (dist < minDist) { minDist = dist; nearest = a; } } result.add(nearest); current = nearest.getCoordinate(); remaining.remove(nearest); } return result; }

距离计算采用 Haversine 公式,考虑地球球面弧度,得到的结果误差在可接受范围内。这个模块做完之后,我建议在前端用 ECharts 把路线画出来,节点连接成线,可视化效果一下子就能在答辩时抓住老师的注意力。

4.5 模拟支付与订单状态流转

订单模块要做一个完整的支付状态机:待支付 → 已支付 → 已完成 → 已取消。毕设不需要对接真实支付平台,但状态流转的逻辑必须写清楚。我建议做一个模拟支付的接口,只要用户调用该接口,订单状态从“待支付”变成“已支付”,这样就能完整走通业务流程。

订单过期未支付自动取消这个功能可以作为加分项。用 Spring 的@Scheduled定时任务,每分钟扫描一次超过 15 分钟未支付的订单,将其置为已取消。这个逻辑不复杂,但能体现你对真实业务场景的思考。

5. 实际操作中容易踩的坑:从报错到解决

5.1 Spring Boot 3 启动失败的常见原因

最常见的报错是NoSuchMethodError或者ClassNotFoundException: javax.servlet.*,这基本是依赖引入不对导致。记住 Spring Boot 3 系已经全面切换到 Jakarta EE,凡是教程里让你引入javax.servlet的,都要改成jakarta.servlet。同理,Spring Security 6 里所有javax开头的引入也需要替换。

另一个高频报错是 MyBatis Plus 找不到 mapper 接口,报Invalid bound statement (not found)。这个问题的根源通常是 mapper 接口没有扫描到,解决方案是在启动类上加上@MapperScan("com.example.travel.mapper")注解,或者在每个 Mapper 接口上标注@Mapper

5.2 前后端联调时的跨域问题

前后端分离项目必然遇到 CORS 跨域。我在开发时前端跑在 5173 端口(Vite 默认),后端跑在 8080,直接发请求会被浏览器拦截。解决方案是在后端配置跨域过滤器:

@Configuration public class CorsConfig { @Bean public CorsFilter corsFilter() { CorsConfiguration config = new CorsConfiguration(); config.addAllowedOriginPattern("*"); config.addAllowedMethod("*"); config.addAllowedHeader("*"); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return new CorsFilter(source); } }

要注意的是,如果项目里已经整合了 Spring Security,跨域配置只加这个还不够,必须在 SecurityConfig 里也调用http.cors(),否则请求走到过滤器链的时候会被安全拦截。

5.3 数据库时间字段变成了数组

这个坑我印象很深。前端拿到的时间字段是"createTime": [2024, 5, 20, 14, 30, 25]这种数组结构,而不是字符串。原因很简单,Jackson 将 LocalDateTime 默认序列化成了数组。解决办法有两种:要么在字段上加上@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss"),要么在 yml 里全局配置:

spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8 serialization: write-dates-as-timestamps: false

推荐第二种全局方案,因为不用每次写注解。

5.4 部署时端口占用和静态资源路径问题

本地跑得好好的,部署到服务器上一重启端口就被占了。排查命令:lsof -i:8080(Mac/Linux)或netstat -ano | findstr 8080(Windows),找到进程后kill -9结束。如果用的是宝塔面板部署,注意检查是否已经启动了一个 Java 进程,尽量不要重复启动。

前端打包后的 dist 目录里,静态资源的相对路径和绝对路径也需要关注,Vite 默认 base 是/,如果部署到子目录下要把 base 改成./,否则页面白屏找不到 JS。

5.5 常见问题速查表

报错现象原因解决方式
Failed to configure a DataSource未正确配置数据库连接检查 application.yml 中 datasource URL、账号密码
Invalid bound statementMapper 接口未扫描启动类加 @MapperScan
401 UnauthorizedJWT 未传入或过期前端请求头加 Authorization: Bearer token
503 Service Unavailable后端未启动或启动失败查看后端日志,重新注册 Bean
ClassNotFoundException: javax.servlet依赖版本错误统一替换为 jakarta.servlet
LocalDateTime 序列化为数组Jackson 配置缺失全局配置 date-format

6. 毕业设计答辩怎么讲:演示顺序和高频问题

6.1 演示稿顺序设计

答辩演示有讲究,建议按照“业务痛点 → 整体演示 → 核心亮点 → 技术难点 → 总结”来组织,总时长控制在 8 到 10 分钟。前两分钟讲清楚系统解决的问题,中间四分钟做全流程演示,之后重点讲 1 到 2 个核心亮点,最后提解决问题的过程。

演示流程推荐这样走:从用户注册开始,然后登录,接着浏览景点列表,进入详情页,收藏一个景点,查看推荐线路,选中一条线路查看行程安排,然后下单预订,最后切换管理员账号演示景点管理功能。这个流程把系统的所有核心功能串了一遍,逻辑环环相扣,给老师的印象是“系统完成度高、链路完整”。

演示之前一定要把环境准备好,数据库里预置好数据,浏览器提前登录两个账号(用户和管理员)。我见过太多人答辩现场现注册账号、现输入景点信息,又慢又容易出bug,结果时间一到还没讲到重点。

6.2 老师最爱问的几个问题

第一个高频问题:Spring Boot 3 相比 2.x 有什么变化?你要能说出 JDK 17 基线、Jakarta EE 命名空间迁移、Spring Security 6 的配置方式变化、Spring Native 支持等。不用背特别多,但要能讲出两三点,证明你是自己学过的。

第二个高频问题:你的 JWT 登录是怎么实现的?认证流程是什么?这个问题一定要能画着流程图讲出来,从用户登录→发 token→前端存储→后续请求携带→过滤器验证→放行/拒绝。不能只说“用了 JWT”,要说出过滤器链的处理流程。

第三个高频问题:数据库表之间是什么关系?外键怎么设计的?这里就要把你设计的表结构讲清楚,说明为什么订单表要冗余商品名称,为什么收藏表要加联合唯一索引。这些问题都在展示你的工程判断力,而不仅仅是“照着教程做”。

第四个问题:系统有什么缺陷或者可以改进的地方?千万别回答“没有”。可以说真实的不足,比如“目前搜索功能只支持按名称模糊匹配,后续可以引入更完善的搜索方案”“行程规划采用的最近邻算法不保证全局最优,后续可以换成动态规划或引入地图API做更精确的时间估算”。这种回答既坦诚又能体现你有深入思考。

6.3 个人实操中的几点体会

最后再分享几个我做这套项目过程中特别深的体会。

第一,目录结构和命名规范一定要从一开始就统一好。我自己早期写代码时类名、字段名混乱,后面改起来极其痛苦。尤其在中国开发的课程设计里,会用到拼音命名变量,到后期非常难维护。尽量从一开始就用规范的英文命名,哪怕慢一点。

第二,做项目时一定要用 Git 做版本管理,每完成一个功能就提交一次。毕设开发周期长,中间会改很多次,提交记录本身也是工作量的证明。答辩时老师如果看到你 GitHub 上的提交记录,印象分会好很多。

第三,功能宁可少一点,也要把已有的功能做深。比如做一个完整的“线路规划”,比做五个半吊子的模块要靠谱。带队老师一般看重的不是功能数量,而是你对某个核心模块的理解深度和完成度。

第四,前端样式不要用过于花哨的模板,简单干净的 Element Plus 默认风格就很好。内容比皮囊重要,但别让简陋的 UI 掩盖了你后端的努力。花两天时间调一调间距、对齐、颜色,整个项目的观感会完全不一样。

这套系统从零到一跑通以后,我对 Spring Boot 3 生态和前后端分离开发的整体理解会提升一个档次。如果你也正在做这个方向的课题,照着这个思路走,省下来折腾环境的时间拿去做功能优化和答辩准备,比什么都值。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询