做一个能拿去答辩、能写进简历、还能真正跑起来的前后端分离项目,最怕的就是“功能看着多,实际全是增删改查”。SpringBoot + Vue 的安康旅游网站管理平台不一样的地方在于,它把旅游业务里最常见的用户、景点、线路、订单、评论、统计这些模块串成了一个完整闭环,后端是 Java 生态里最主流的 SpringBoot,前端是 Vue,数据库用 MySQL。对毕设、课设或者想系统学一遍前后端分离开发的人来说,这套源码既能当项目底座,也能当学习教材。
这篇文章我打算换个讲法,不贴一堆项目截图凑字数,而是从项目拆解、技术选型、表结构设计、环境搭建、核心功能实现、常见坑这几个维度,把整个项目从头到尾讲透。里面涉及的代码和步骤都是可以直接复制的,每个关键设计我也会把背后的原因讲清楚,这样你写完报告或者准备答辩的时候,心里是有底的。
1. 项目整体拆解:一个旅游平台到底该有哪些东西
1.1 用户视角:游客在系统里能做什么
很多初学者一看“旅游网站”四个字,第一反应就是景点列表加个详情页,但实际做下来你会发现,一个能称为“平台”的系统,用户侧的体验链路必须是完整的。这个项目里用户端的核心路径是:注册登录 → 浏览景点 → 查看线路 → 下单预订 → 发表评论 → 查看个人订单。这条链路里的每一步都不是孤立的,比如你要下单,前提是景点和线路数据存在;你要评论,前提是订单状态已经是“已完成”。这种业务约束正好是答辩时能讲出来的亮点,也是课程设计评分时最看重的“业务完整性”。
另外,用户端还包含搜索和筛选功能。游客可以按景点名称搜索,按区域、分类、价格区间筛选线路,分页查看结果。这些功能听起来简单,但实现的时候会涉及后端接口的参数设计、MySQL 的模糊查询、前端搜索框的防抖处理,每一个点都能单独展开讲。就算你的课设只需要“能用”,把搜索做规范了,代码质量也会明显高一个档次。
1.2 管理视角:运营人员在后台能做什么
管理端是这个项目另一个重头戏。管理员登录后,需要维护的内容包括景点信息、旅游线路、酒店民宿、资讯公告、订单审核、用户管理、评论管理、数据统计等模块。说白了,用户端展示的每一条数据,都必须在管理端能增删改查;用户产生的每一笔订单,管理端都要能看到状态并且可以处理。这种“前台展示 + 后台管理”的双端结构,是绝大多数企业级 Web 项目的标准形态,也是面试官最熟悉的项目形态。
这里我想特别提醒一点:做毕设的时候,管理端不要做成只有一个“用户列表”的摆设。这个项目里能学到的管理端细节很多,比如分页查询时条件怎么组合、删除景点时关联的线路和订单怎么处理(软删除还是硬删除)、图片上传后 URL 怎么存、统计报表的数据用什么接口聚合。这些细节你在写论文的“系统设计”章节时都能用上。
1.3 为什么业务闭环比功能数量更重要
判断一个项目适不适合当毕设,不是数它有几个页面,而是看业务能不能闭环。举个例子,如果只有景点 CRUD,没有订单,那系统就是一个“数据管理后台”,谈不上“平台”;如果有订单但订单不关联库存、不改变状态,那订单就是一张空表。这个安康旅游平台把“浏览—预订—支付—评价—统计”串到了一起,数据在表之间流动,状态在接口之间流转,这才是“平台”二字的含义。
从答辩的角度讲,业务闭环意味着你可以画出一张完整的数据流图,可以讲清楚订单表为什么要有 status 字段、评论表为什么外键关联订单而不是用户,这些设计理由比“我用了 Vue + SpringBoot”这种话有力得多。从学习的角度讲,你跟着这个闭环走一遍,前后端数据交互的整个套路就通了。
2. 技术选型:为什么这套组合是毕设的“标准答案”
2.1 SpringBoot:后端开发的“默认答案”
SpringBoot 在 Java 后端开发里的地位,基本等同于“默认答案”。它把 Spring 框架里繁琐的 XML 配置变成了自动配置,内嵌 Tomcat 让项目可以用一个 main 方法直接启动,配合 Spring MVC 写 RESTful 接口非常直观。对毕设来说,SpringBoot 还有一个隐藏优势:社区资料多,你遇到任何报错,搜索一下基本都有解决方案,这点在赶工的时候能救命。
这个项目里,SpringBoot 的核心职责是提供 REST API、处理业务逻辑、操作数据库。你会用到 Spring Web、Spring Data JPA 或 MyBatis、Spring Validation 参数校验、JWT 鉴权、跨域配置等模块。用 SpringBoot 还有个好处是它的依赖管理很省心,Maven 引入 starter 之后基本不用管版本兼容问题。
2.2 Vue:前端开发的高效选择
Vue 的核心优势是组件化和响应式数据绑定。组件化意味着你可以把导航栏、景点卡片、分页器、弹窗提示这些 UI 拆成独立组件,改一处全站生效;响应式意味着你不需要像 jQuery 时代那样手动操作 DOM,数据变了页面自动更新。这个项目用 Vue 2 + Element UI 或 Vue 3 + Element Plus 都能跑,具体看你拿到的源码版本,但核心思想一致:前端通过 Axios 调用后端接口,拿到 JSON 数据后渲染页面。
对初学者来说,Vue 的学习曲线比 React 平滑得多,模板语法接近 HTML,上手成本很低。而且 Element UI 这类组件库能直接给你表格、表单、日期选择器、分页、弹窗,写管理端页面基本就是搭积木。这个项目的管理端页面大量使用了这类组件,非常适合用来练手。
2.3 MySQL:业务数据的第一选择
MySQL 不用多说,开源、稳定、资料全,是绝大多数中小型 Web 项目的首选数据库。选择 MySQL 意味着你的表结构设计、SQL 查询、事务处理、索引优化这些知识都能直接迁移到工作场景里。这个项目里的核心表包括用户表、景点表、线路表、订单表、评论表、公告表等,表之间通过外键逻辑关联,比如订单表关联用户和线路,评论表关联用户和订单。
如果让我给初学者一个建议,那就是拿到项目后先不要急着启动,先花半天时间把 SQL 脚本里的建表语句一条条看懂。看懂了表结构,你就知道一个旅游平台的数据是怎么组织的,后面看代码会轻松很多。
2.4 前后端分离架构要解决的关键问题
前后端分离听起来高大上,本质就是前端跑在一个端口,后端跑在另一个端口,前端通过 HTTP 请求调用后端接口。这种架构带来三个必须解决的问题:跨域、鉴权、接口约定。
跨域是因为前端地址和后端地址的协议、域名、端口不一致,浏览器默认会拦截响应,需要在后端配置跨域过滤器或者在前端配置代理。鉴权是因为 HTTP 请求是无状态的,后端需要一种方式识别“你是谁”,这个项目用的方案是 JWT,登录成功后后端签发一个 token,前端存在本地存储里,每次请求带上,后端校验通过才算合法。接口约定是前后端开发时的“合同”,比如列表接口返回什么结构、分页参数叫什么名字、错误码怎么定义,这些在项目中都有统一规范,很值得学习。
3. 数据库设计:先看懂表,才能看懂代码
3.1 核心表结构设计
我在拿到一套源码的时候,习惯先看数据库脚本,因为表结构是业务的骨架子。这个项目里,比较关键的表大概有这些:
| 表名 | 核心字段 | 作用 |
|---|---|---|
| user | id, username, password, nickname, avatar, role | 存储用户和管理员账号,role 区分权限 |
| scenic_spot | id, name, description, address, price, image, category | 景点基础信息 |
| route | id, name, spot_ids, price, days, detail | 旅游线路,可能关联多个景点 |
| hotel | id, name, address, price, image | 酒店民宿信息 |
| orders | id, order_no, user_id, route_id, hotel_id, status, total_price | 核心订单表,状态流转的核心 |
| comment | id, user_id, order_id, content, rating, create_time | 用户评价 |
| notice | id, title, content, create_time | 公告资讯 |
| cart | id, user_id, route_id, quantity | 购物车(部分版本有) |
设计上的几个关键决策值得说一下。第一,用户表里用 role 字段区分管理员和普通用户,这样一套登录接口就能覆盖两个端,而不是建两张用户表。第二,订单表里有 order_no 这种业务编号,看起来和 id 重复,但实际业务中订单号用来给用户看、用来对账,id 只是数据库内部用,两者职责不同。第三,评论表外键关联的是订单而不是直接关联用户和线路,这样能防止“没买过就能评论”这种逻辑漏洞。
3.2 订单状态流转:一张表怎么支撑完整业务
订单表里的 status 字段是整个项目业务逻辑最密集的地方。常见状态可以设计为:待支付、已支付待出行、已完成、已取消。每个状态对应一组用户操作和管理员操作:用户下单生成“待支付”订单,支付后变成“已支付”,出行结束后管理员或系统标记“已完成”,用户此时才能评价。任何状态下用户都可以申请取消,取消后订单进入“已取消”状态。
这个状态机设计在答辩时特别好讲。你可以画一张简单的状态图,说明每个状态之间的迁移条件和触发动作。比如“已完成”状态不是说改就能改,必须校验订单是否已支付、是否在出行日期之后,这些规则写在 Service 层的代码里。这种“状态 + 校验 + 操作”的写法,就是企业里常说的业务逻辑封装。
3.3 索引与查询优化:数据量大了怎么办
毕设项目通常数据量不大,但如果你在论文里写了“系统具有较好的性能”,就得懂点索引知识。这个项目里,最常用的查询场景是:按景点名称模糊搜索、按线路价格区间筛选、按用户查询订单列表、按时间范围统计订单金额。针对这些场景,合理的索引设计是:scenic_spot 表的 name 字段加普通索引(模糊查询 LIKE 开头是通配符时索引会失效,这点可以在论文里讨论),orders 表的 user_id 加索引,orders 表的 create_time 加索引用于统计查询。
另外一个常见优化是分页。前端传 pageNum 和 pageSize,后端用 LIMIT 实现分页,同时返回 total 总数。MySQL 的 COUNT 查询在大表下会变慢,但在毕设数据量下完全没问题,真正需要讲的是分页参数怎么从前端传到后端再传回前端,这个链路本身就是很好的接口设计案例。
4. 从零启动项目:环境配置与踩坑准备
4.1 环境版本对照:先别急着装最新版
启动这个项目之前,先把环境版本对齐,这是我见过初学者最容易卡住的地方。后端建议 JDK 1.8 或 11,SpringBoot 2.x 版本(如果是 3.x 要注意 JDK 版本要升级);前端建议 Node.js 14 以上,npm 6 以上。数据库用 MySQL 5.7 或 8.0 都行,但 8.0 要注意时区和认证插件的问题,下面我会细说。
建议的本地环境清单如下:JDK 8、Maven 3.6+、Node.js 14+、MySQL 5.7+、IDEA(后端)、VSCode 或 WebStorm(前端)。不要一上来就装最新的 JDK 21,很多旧项目在 JDK 17 以上会有兼容问题,SpringBoot 2.x 配合 JDK 8 是最稳的组合。
4.2 数据库初始化:两条命令搞定
项目里一般会提供一个 .sql 文件,比如an_kang_travel.sql。启动数据库后,先创建数据库实例,再导入脚本:
CREATE DATABASE an_kang_travel DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE an_kang_travel; SOURCE /你的路径/an_kang_travel.sql;用 utf8mb4 而不是 utf8 的原因是,utf8mb4 才能完整支持中文和 emoji 字符,避免评论内容带表情符号时插入报错。导入完成后,用 Navicat 或 DataGrip 随便查一张表确认数据存在即可。
4.3 后端启动:改三个配置就够
后端项目用 IDEA 打开,等待 Maven 下载依赖,然后修改配置文件application.yml里的数据库连接信息和 JWT 密钥:
server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/an_kang_travel?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver jwt: secret: your-secret-key expire: 604800这里要提醒两个高频坑。第一,serverTimezone=Asia/Shanghai少写了会直接报时区错误。第二,如果你用的是 MySQL 8.x 而驱动是com.mysql.jdbc.Driver,启动会报警告或直接失败,要改成com.mysql.cj.jdbc.Driver。改完配置直接运行主类里的 main 方法,看到“Started Application”字样就是启动成功。
4.4 前端启动:npm 装依赖要有耐心
前端项目在终端里依次执行:
npm install npm run servenpm install如果速度很慢,可以换成国内镜像源:
npm config set registry https://registry.npmmirror.com启动成功后会显示本地访问地址,一般是http://localhost:8081或http://localhost:3000。打开页面后如果所有接口都报错,不要慌,先看前端配置文件里的代理设置。Vue CLI 项目的vue.config.js通常会有类似这样的代理配置:
devServer: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } }这个代理的意思是,前端把以/api开头的请求转发到后端 8080 端口。如果没配代理,前端直连后端需要后端开启跨域,两种方式任选其一,但最好是都有。
5. 核心功能实现:从接口设计到具体代码
5.1 登录鉴权:JWT 的完整流程
登录功能看起来简单,但前后端分离下的登录和传统 session 登录有本质区别。这个项目的流程是:前端把用户名密码发给后端/api/user/login,后端校验通过后生成一个 JWT token 返回给前端,前端把 token 存到 localStorage,之后每次请求都在请求头里带上Authorization: Bearer <token>,后端通过拦截器统一校验。
一个典型的登录接口代码如下:
@PostMapping("/login") public Result login(@RequestBody LoginDTO dto) { User user = userService.login(dto.getUsername(), dto.getPassword()); if (user == null) { return Result.error("用户名或密码错误"); } String token = JwtUtil.generateToken(user.getId(), user.getUsername(), user.getRole()); return Result.success(new LoginVO(token, user)); }这里的JwtUtil负责生成和解析 token,Interceptor负责拦截需要登录的请求。有一点你要注意:密码千万别明文存数据库,项目里如果是明文,你可以在写论文时顺手改成 MD5 加盐或 BCrypt,这是很好的“个人改进点”。
5.2 景点列表:分页 + 搜索 + 条件筛选
用户端首页最有代表性的接口是景点分页列表。前端传pageNum、pageSize、keyword、category等参数,后端用 MyBatis 或 JPA 查询数据库,返回固定格式的数据结构:
public PageResult<ScenicSpot> page(ScenicSpotQuery query) { PageHelper.startPage(query.getPageNum(), query.getPageSize()); List<ScenicSpot> list = scenicSpotMapper.selectByCondition(query); PageInfo<ScenicSpot> info = new PageInfo<>(list); return new PageResult<>(info.getTotal(), info.getList()); }返回给前端的数据结构建议统一为:
{ "code": 200, "message": "success", "data": { "total": 100, "list": [] } }这种统一返回格式是前后端联调的核心。如果每个接口返回结构都不一样,前端就得每个接口单独写解析逻辑,那代码就没法维护了。这也是写论文时“系统设计”章节里值得大书特书的一点。
5.3 下单与订单状态更新:事务是关键
下单接口是业务逻辑最集中的地方。用户选择线路和日期后,后端要做的动作包括:校验线路是否存在、计算总价、生成订单号、扣减库存(如果线路有库存概念)、插入订单记录。这些动作必须放在一个数据库事务里,任何一个失败都要回滚,否则会出现“订单生成了但库存没减”这种脏数据。
一个精简的下单逻辑是:
@Transactional public Long createOrder(OrderCreateDTO dto) { Route route = routeService.getById(dto.getRouteId()); if (route == null) throw new BusinessException("线路不存在"); Order order = new Order(); order.setOrderNo(generateOrderNo()); order.setUserId(dto.getUserId()); order.setRouteId(route.getId()); order.setTotalPrice(route.getPrice() * dto.getNum()); order.setStatus(OrderStatus.PENDING_PAYMENT); orderMapper.insert(order); return order.getId(); }这里的关键是@Transactional注解。你可能觉得毕设里数据量小,事务无所谓,但面试官一定会问,所以你要能说清楚:事务保证的是“多个操作要么全部成功,要么全部失败”,在下单场景里就是不能出现半成功状态。
5.4 统计报表:管理端的数据看板
管理端首页一般会有统计图,比如近一周订单量趋势、热门景点排行、用户增长曲线。这些图表的数据来自统计接口,典型的实现是用一条带分组的 SQL:
SELECT DATE(create_time) AS day, COUNT(*) AS order_count FROM orders WHERE create_time >= #{startTime} GROUP BY DATE(create_time) ORDER BY day前端拿到数据后,用 ECharts 渲染折线图和柱状图。这个模块是项目里“看起来最厉害”的部分,但实现难度并不高,核心就是 SQL 的聚合分组。建议你重点掌握这一块,答辩演示的时候打开数据看板,比口头讲一百句都有说服力。
6. 常见问题与排查实录
6.1 前端页面能打开但接口全报 404
这个问题我见到的频率最高。页面能打开说明前端代码没问题,接口 404 基本是路径问题。排查步骤是:打开浏览器开发者工具的 Network 面板,看请求的完整 URL。如果请求落在http://localhost:8081/api/xxx但后端接口路径是/api/xxx且后端端口是 8080,那就需要检查前端代理是否生效,或者直接改成完整地址请求。还有一种可能是后端的 context-path 配置,如果 application.yml 里设置了server.servlet.context-path,所有接口会自动加前缀,前后端必须对应。
6.2 MySQL 8.0 连接报时区错误
报错信息一般是The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized。解决方案有两种:一是在 JDBC URL 后面加serverTimezone=Asia/Shanghai,二是执行数据库命令SET GLOBAL time_zone = '+8:00'。第一个方法简单,推荐优先使用。
6.3 npm install 报错或卡住
常见原因是网络问题或 Node 版本不兼容。先切换镜像源,再删除node_modules和package-lock.json重新安装。如果项目用的是 node-sass 这类原生模块,很容易因为 Node 版本太新导致编译失败,建议直接看 package.json 里的版本要求,装对应版本的 Node。
6.4 登录后刷新页面就失效
这个问题的根因通常是没有持久化登录状态。JWT 本身可以设置过期时间,但如果前端只在内存里保存 token,刷新就会丢失。正确做法是把 token 存到 localStorage,在路由守卫里检查本地是否存在 token,不存在则跳转登录页。同时每次请求在 Axios 拦截器里统一加请求头。
axios.interceptors.request.use(config => { const token = localStorage.getItem('token'); if (token) { config.headers.Authorization = 'Bearer ' + token; } return config; });6.5 图片上传成功但访问不到
项目里如果有图片上传功能,上传后数据库保存的路径经常会被人忽略。比如上传到你项目所在的static/upload目录,但访问路径写的是localhost:8080/upload/xxx,那就对不上。排查时先确认图片实际保存在哪里,然后看后端有没有配置静态资源映射。SpringBoot 可以用如下配置把磁盘目录映射为访问路径:
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/upload/**") .addResourceLocations("file:D:/upload/"); } }这类静态资源映射问题特别适合写进论文的“系统实现”部分,因为能体现你对 SpringBoot 配置的掌握程度。
7. 基于个人经验的一些建议
这套项目我跟进过很多次,也帮别人排查过不少问题。如果让我总结一句最重要的话,那就是:拿到源码别急着跑步,先抱着数据库脚本看半天,先理清模块边界,再去看代码会事半功倍。很多同学卡在“不知道从哪看起”这个问题上,我的建议是倒着看:管理员端能操作什么 → 对应的表是什么 → 对应的接口是什么 → 前端哪个页面调用了它。这个链路走通两三个功能之后,整个项目就活了。
另外一个建议是:一定要修改默认的 admin/123456 这类内置账号,至少把密码改成自己的。虽然只是毕设,但这种安全习惯越早养成越好,写在论文里和放在简历里都能加分。再有就是项目跑通之后,不要满足于能启动,试着改一个功能,比如给景点加一个“推荐”标记、给订单加一个“退款”状态,这些小的改动会让你的理解深度完全不一样。
最后想说的是,毕设项目的价值不只是最后那一份代码和论文,而是你在调试报错、搜索资料、修改功能的过程中积累起来的排查能力。哪怕你最后只是把“安康旅游网站管理平台”改成了“自己城市名 + 旅游平台”,只要所有文案、图片、数据都换了,再改一两个页面和接口逻辑,它就是你自己的项目。答辩的时候能清楚讲出每个表为什么这么设计、每个状态为什么这么流转,比什么华丽的辞藻都管用。