
做了几年 Java 后端也带过不少刚入行的同事我发现一个特别典型的现象很多人会写单接口、会搭单个页面的 Demo但一碰到“完整系统”这种要求脑子里立刻一团浆糊——表怎么建、模块怎么拆、前后端怎么接、环境怎么配每一步都在踩坑。今天想拿这个“基于 SpringBoot Vue 的安康旅游网站管理系统”做例子完整拆一遍从零到部署上线的全过程项目不大不小刚好覆盖全栈开发里最核心的技术点非常适合拿来练手、做课设或者作为简历上的实战项目。整个系统的核心链路不复杂前端用 Vue 做单页面应用后端用 SpringBoot 提供 RESTful APIMySQL 存业务数据MyBatis 负责数据库访问层最终打成 jar 包和静态资源包部署到一台服务器上就能跑。这套技术栈是现在中小型 Web 项目里最主流的搭配之一学会了它你再看别的 Java Web 项目会轻松很多。下面我按自己实际开发的顺序来写尽量把每一步背后“为什么这么做”也讲清楚。1. 从选题到落地旅游网站系统的需求边界与功能地图很多新手拿到“旅游网站管理系统”这种题目第一反应就是把旅游网站往大了做——要在线支付、要地图导航、要智能推荐结果被复杂度活埋。我建议立项时先把边界划清楚一个管理系统最核心的价值就两个方向对外展示和信息管理。安康旅游网站管理系统也是如此拆开来看其实是两个端。1.1 前台展示端游客能看到什么前端对应的是游客访问的网站页面也就是一个旅游门户。游客进来之后重点体验路径是浏览景点列表 → 查看景点详情 → 搜索和筛选自己感兴趣的景点 → 顺手看看相关酒店、旅游线路和攻略文章。所以在设计页面时我划分了这几个核心页面模块首页展示 banner、热门景点推荐、最新攻略等聚合信息是整个门户的信息门面景点列表/详情按城市、区域、分类筛选景点详情页展示景点图片、简介、开放时间、门票价格等酒店频道酒店列表、房型介绍支持按价格、星级排序线路频道展示一日游、多日游线路线路详情里挂关联景点攻略文章图文形式的游玩攻略按时间倒序排列用户登录/注册前台用户注册登录之后才能发表评论、收藏景点前台的重心是“浏览体验”所以数据查询要快页面要结构清晰尤其是景点和线路的关联数据要设计好查询口径。1.2 后台管理端管理员怎么维护数据后台是给系统管理员用的功能上有明确的 CRUD 特征这也是毕设和课设考察的核心。后台包含这些模块管理员登录与权限控制基于 Session 或 Token 都可以本项目可以先用拦截器做登录校验景点管理对景点数据做增删改查、上架/下架支持图片上传酒店管理维护酒店和房型数据操作逻辑和景点类似线路管理维护线路基本信息并关联多个景点攻略管理发布、编辑、删除攻略文章用户管理查看注册用户列表禁用/启用用户评论管理查看、删除用户提交的评论数据统计可选按月份统计景点访问量之类的基础图表旅游系统说到底就是内容管理系统的一个行业变种后台就是在做模型的标准操作。把后台做扎实了项目的技术说服力就有了。1.3 用例视角下的角色与权限从系统角色上看整个系统只涉两类角色管理员和普通用户。管理员拥有后台全部权限普通用户只能在前台浏览登录后可评论、收藏。这样设计的好处是权限模型极其清晰适合用小规模的拦截器和 Session/Redis 做控制而不必引入 Spring Security 这种体量的框架。如果后续想往简历上写亮点可以把权限这块升级成基于角色的访问控制模型加入 JWT 认证、Redis 存储 Token那就完全是另一档次的复杂度了。但刚开始千万别一上来就上全家桶先跑通主链路最重要。2. 技术选型的账要算清楚SpringBoot Vue MySQL MyBatis 是不是最优解这套技术栈经常被人说是“毕设标配”话里带点嫌弃但真拿去做中小型系统它依然是性价比极高的组合。我从业多年见过不少项目用更“高大上”的框架做得一塌糊涂反而这种主流稳妥的组合更容易出活、更容易维护。下面把四个核心技术的分工和选型理由逐个讲清楚。2.1 SpringBoot为什么用它而不是传统 SpringSpringBoot 最大的价值不是新发明了什么而是把 Spring 的配置复杂度给收编了。传统 Spring 项目要写大量 XML 配置——数据源、事务、组件扫描、视图解析器每一样都要手动声明对新手很不友好。SpringBoot 用“约定大于配置”和“自动配置”这两招把常见场景的默认配置全部提前打好包你只需要在 application.yml 里写上少量个性化配置就能启动。用个生活化的比喻Spring 是买了毛坯房所有硬装软装都要自己搞SpringBoot 是精装房水电管线全部预埋好了你搬进来只需要买点家具家电就能住。对于业务系统开发我们需要的是快速把业务跑起来而不是花时间调框架配置。另外一个特别实用的点是 SpringBoot 内嵌了 Tomcat 服务器打包成可执行的 jar 文件后服务器上只需要装 JDK 就能跑这对部署来说简直是降维打击。传统 Spring 项目打包成 war 还要装 Tomcat、还要配 context path麻烦得多。2.2 Vue前后端分离下的页面构建方案选择 Vue 不止是因为它热度和文档友好而是它的渐进式架构非常适合这个项目。Vue 的模板语法上手快v-model 双向绑定能轻松处理表单数据流组件化开发可以把头部导航、景点卡片、轮播图这些重复出现的 UI 块抽成独立组件复用。前后端分离模式下Vue 项目本身是一个独立的工程通过 axios 发起 HTTP 请求调用后端的 JSON API和后端完全解耦。开发时可用 Vite 或 Vue CLI 起一个本地开发服务器接口代理到后端端口生产环境则由 Nginx 来接管静态文件和反向代理。这种架构带来的直接好处是后端接口只要写好文档前端可以并行开发不会互相卡住。如果你对这块还不熟悉可以先用 Vue 2 Vue CLI 快速搭项目很多公开的教程和插件都基于这个组合碰到问题也容易搜索到解决方案。使用 Vue 3 Vite 也没有问题本质开发路径是一致的。2.3 MySQL MyBatis数据持久层的最稳妥搭档MySQL 的量级和复杂度正好匹配这种旅游网站——单机部署、数据量几万到几十万级别、需要事务支持、需要灵活的查询。景区、酒店、用户、评论这些数据之间天然存在外键关联用关系型数据库存储是直觉正确的选择。MyBatis 的核心优势是“SQL 在你的掌控之中”。与 JPA/Hibernate 这种全自动 ORM 相比MyBatis 不会替你生成神奇的全表查询也不会在复杂关联查询上拐弯抹角。你可以手写 SQL、理解 SQL这对于学习数据库原理很有帮助也方便后期针对慢查询做优化。另外MyBatis 在处理动态 SQL 时提供了 where、if、foreach 等标签酒店条件筛选这种多条件组合的查询写起来非常舒服。用 MyBatis 的时候我建议配合代码生成插件如 MyBatis Generator 或 MyBatis-Plus看团队规范把基础的单表操作生成出来复杂查询再手写 XML。这样既能避免大量重复的样板代码又保留手写 SQL 的灵活性兼顾开发效率和查询可控性。3. 数据库建模旅游业务的核心表与字段设计逻辑表结构设计决定了整个系统写起来顺不顺手。我在做这个系统时没有急着写代码而是先把核心表构造好因为后端业务逻辑只是对表数据做操作表设计烂了后面每一步都难受。旅游系统的核心数据模型可以圈定为管理员表、用户表、景点表、酒店表、线路表、攻略表、评论表再加上关联表和常用辅助表。3.1 核心表的核心字段设计以景点表为例我给出一份实际可用的表结构CREATE TABLE scenic_spot ( id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT 主键, name VARCHAR(100) NOT NULL COMMENT 景点名称, cover_image VARCHAR(255) COMMENT 封面图URL, images TEXT COMMENT 轮播图逗号分隔, summary VARCHAR(500) COMMENT 一句话简介, content TEXT COMMENT 详细介绍, category VARCHAR(50) COMMENT 分类自然/人文/乐园, location VARCHAR(255) COMMENT 具体地址, region VARCHAR(50) COMMENT 区域汉滨/旬阳/岚皋等, open_time VARCHAR(100) COMMENT 开放时间, ticket_price DECIMAL(10,2) DEFAULT 0 COMMENT 门票价格, recommend TINYINT DEFAULT 0 COMMENT 是否推荐 0否 1是, view_count INT DEFAULT 0 COMMENT 访问次数, status TINYINT DEFAULT 1 COMMENT 状态 0下架 1上架, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_region (region), KEY idx_category (category), KEY idx_recommend (recommend) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT景点信息表;几个值得留意的设计决策图片字段要存 URL 而不是二进制大数据文件上传后返回一个资源链接数据库中只存路径。前端展示时直接拼静态资源地址即可这是最常见的实践方案所有时间字段建议用 datetime 而非 timestamp因为 timestamp 有 2038 年问题而且 datetime 的语义更直观状态字段用 tinyint 而不是字符串既节省空间查询也快索引不是越多越好但 region 和 category 这种高频查询条件字段加上索引很有必要所有表的字符集统一为 utf8mb4否则存储生僻字和表情符号会出问题3.2 表间关系设计旅游系统的表关系指数比较简单但有几个关系要提前规划明白用户-评论-景点或酒店/线路用户表与景点表通过评论表产生多对多联系。评论表里的 target_type 字段可以用来区分评论的对象类型1-景点2-酒店3-线路这就是一个简单的“多态关联”设计线路-景点一条旅游线路会包含多个景点一个景点也可能出现在多条线路中所以需要一张关联表 line_scenic线路ID 景点ID而不是在线路表里放一个景点的 JSON 数组酒店-房型一个酒店有多个房型房型的 id 可以放进 hotel_room 表中酒店表不存重复集合这种关联表设计的好处是数据规范化程度高不会产生冗余和更新异常。要知道很多新人最容易犯的毛病就是在主表里塞一个字段存 1,2,3,4 这种逗号分隔的 ID 串看起来查询方便但统计和一致性维护会非常别扭。3.3 数据库初始化与版本管理开发过程中我强烈建议引入数据库脚本管理也就是把建库建表语句放到项目根目录的 sql/ 文件夹里每个版本存一个脚本文件。这样做有三个明显好处一是团队成员可以随时重建本地库二是部署生产环境时有据可查三是避免大家在共享开发库上互相覆盖表结构。开发时安装 MySQL 也很关键遇到过不少安装了最新版却连不上控制台或者默认认证插件不兼容的坑。这里不建议用过新的版本8.0 的社区版是一个相对稳妥的选择。下载完成后要注意设置字符集为 utf8mb4并选对认证方式否则后面程序连接数据库的时候容易报错。4. 后端从空项目到跑通SpringBoot MyBatis 的配置与方法落地后端工程的搭建虽然主要靠 IDEA 的初始化工具但里面的每一步都需要知其所以然。这里我把最核心的配置和代码路径过一遍全部是实际生产在用的写法。4.1 项目初始化与依赖管理用 IDEA 创建 Spring Initializr 项目建议使用 Java JDK 1.8 或 11、SpringBoot 2.7.x 版本。不推荐用 SpringBoot 3.x 起步因为 3.x 基于 Jakarta EE 规范对 JDK 版本要求更高部分老教程中的依赖写法也不适用。对新手来说先用成熟稳定版本把项目跑通再考虑升级这是最现实的路径。pom.xml 中核心依赖如下dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-jdbc/artifactId /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.1/version /dependency dependency groupIdcom.github.pagehelper/groupId artifactIdpagehelper-spring-boot-starter/artifactId version1.4.7/version /dependency /dependenciesPageHelper 是我额外加上的分页插件做后台列表分页非常好用避免了手写 LIMIT 的重复劳动也保证了分页 SQL 的统一性。Maven 里面这类的版本选择建议跟着启动器的版本来避免自身版本冲突。4.2 application.yml 中的关键配置项SpringBoot 的配置文件是项目启动的基础。以下是我日常在用的健康配置样式server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/ankang_travel?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: 123456 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8 mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.ankang.travel.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl pagehelper: helper-dialect: mysql reasonable: true有几个点需要重点解释一下allowPublicKeyRetrievaltrue 这个参数在 MySQL 8.0 下经常需要加上否则可能报公钥检索错误map-underscore-to-camel-case 这个配置特别有用可以让数据库的 create_time 自动映射成实体的 createTime减少大量手动映射serverTimezoneAsia/Shanghai 指定时区否则连接数据库会报时差错误这是中国开发者最常遇到的经典坑mapper-locations 指定 XML 文件位置所有手写 SQL 放在这个目录下统一管理4.3 Controller-Service-Mapper 三层结构的职责边界后端代码的分层本质上是为了把“接收请求、处理业务、访问数据”这三件事拆开让代码好维护、好测试。我见过一些人为了图省事把 SQL 写在 Controller 里短期开发快后期项目稍微扩展一点就崩溃。推荐的分层方式是Controller 层接收参数、校验参数格式、调用 Service、包装返回结果。这一层不写 SQL不写复杂业务逻辑Service 层承载业务逻辑比如用户注册时要检查用户名是否存在、创建评论时要更新景点评论数这些流程编排都在这一层Mapper 层DAO只负责数据库的增删改查方法名和参数要与 XML 中的 SQL 对应以景点列表接口为例RestController RequestMapping(/api/scenic) public class ScenicController { Resource private ScenicService scenicService; GetMapping(/list) public Result list(RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize, ScenicQuery query) { PageInfoScenicSpot page scenicService.queryPage(pageNum, pageSize, query); return Result.success(page); } }Service 层再调用 Mapper 层Mapper 在 XML 中编写动态 SQLselect idselectPage resultTypecom.ankang.travel.entity.ScenicSpot SELECT * FROM scenic_spot where if testname ! null and name ! AND name LIKE CONCAT(%, #{name}, %) /if if testregion ! null and region ! AND region #{region} /if if testcategory ! null and category ! AND category #{category} /if AND status 1 /where ORDER BY create_time DESC /select动态 SQL 的 where 标签自动处理了 AND 前缀问题这是 MyBatis 中高频使用的技巧酒店筛选、线路筛选都可以按这个模式扩展。4.4 文件上传与静态资源映射旅游系统的后台涉及图片上传实际开发中一个相对独立的做法是本地磁盘保存 虚拟路径映射。实现上定义一个上传目录接收 MultipartFile生成 UUID 文件名保存后返回 /uploads/xxx.jpg 这个 URL。然后写一个 WebMvcConfigurer 把 /uploads/** 映射到本地磁盘目录。这个方案最大的优势是部署简单不需要引入 MinIO 或云存储。但要注意两点一是服务器上上传目录要提前创建好并给足写权限二是 Nginx 部署时要把 /uploads/ 这个路径也放进静态资源访问规则这样前端才能在页面上真正显示图片。不要踩这种最常见的前端图片 404 的坑。4.5 登录校验与拦截器实现登录校验用一个拦截器来实现就够了不需要上 Spring Security 这种重型框架。在 interceptor 内排除掉 /api/user/login、/api/scenic/, /api/hotel/等游客可访问的接口管理员的 /api/admin/** 则必须登录后才能访问。public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Object user request.getSession().getAttribute(loginUser); if (user null) { response.setStatus(401); response.getWriter().write({\code\:401,\msg\:\未登录\}); return false; } return true; } }Session 方案在单机部署下足够用。如果以后系统要横向扩展、部署多个实例再考虑把 Session 换成 Redis配合 Token 传递身份信息。5. 前端 Vue 接入从环境配置到管理后台页面搭建前端部分很多做后端的人会在这一步卡壳原因不是说 Vue 有多难而是对前端的工程流程不熟。环境配置、依赖安装、代理转发、打包部署这一整套链路从一开始就要跑通。5.1 Node.js 环境与项目初始化前端工程依赖 Node.js 环境。安装时我建议选用 LTS 长期支持版本具体版本号没有严格要求但不要太新否则某些依赖包还没跟上会报兼容错误。安装完成后用 Node 自带的 npm 工具来管理依赖。这里有一个新手必踩的坑直接下载依赖时npm 速度极慢很容易卡在某个大包上这是因为默认的源服务器在国外。解决办法是切换到国内镜像源npm config set registry https://registry.npmmirror.com执行之后再运行 npm install 就会发现速度有了质的提升。如果项目创建时使用的是 Vue CLI还可以用对应的图形界面工具查看依赖状态。我自己的建议是使用 Vite 创建 Vue 3 项目。Vite 的启动速度比 Webpack 快不少开发时热更新非常爽配置也简洁。如果你特别依赖老教程选 Vue 2 Vue CLI 也没问题原理是相通的。5.2 接口请求封装与代理转发前端调用后端接口时统一封装一个 axios 实例把 baseURL 和拦截器配置好import axios from axios const service axios.create({ baseURL: /api, timeout: 10000 }) service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] token } return config }) service.interceptors.response.use( response { const res response.data if (res.code ! 200) { ElMessage.error(res.msg || 请求失败) return Promise.reject(new Error(res.msg)) } return res }, error { ElMessage.error(error.message || 网络异常) return Promise.reject(error) } ) export default servicebaseURL 设置为相对路径 /api开发环境通过 Vite 的 proxy 配置把请求转发到后端端口生产环境由 Nginx 统一转发这样前后端联调和部署都不用改前端代码里的接口地址非常方便。以下是 Vite 的代理配置export default defineConfig({ server: { port: 3000, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })5.3 后台管理页面的表单与表格实现后台管理的本质就是“表格表单”的循环。列表页用 Element Plus 的 el-table 展示数据配合 el-pagination 分页组件新增/编辑页用 el-dialog 弹窗嵌套 el-form 表单搜索区域用 el-form inline 模式。路由配置方面后台建议做成独立的 layout包含侧边栏菜单和顶部导航栏子页面通过 router-view 切换。你不需要给每个页面写重复的头部和侧边栏代码只需要在根路由下嵌套子路由即可。在这个过程中要注意的是表单校验规则、日期格式化、状态枚举转换比如 1 转成“上架”、0 转成“下架”这些是后台系统最容易出细节问题的地方。状态转换写好过滤器之后整个后台的观感会专业很多。5.4 前台门户的视觉与交互要点前台面向游客视觉呈现直接影响体验也直接影响老师或面试官对项目的初印象。首页的轮播图可以用 Element Plus 的 el-carousel 或 Swiper 实现景点卡片展示图片和名称点击跳转详情。列表页的筛选器放在顶栏或侧边栏下拉选项的数据由后端接口动态返回不要写死。如果你希望在项目中增加一点技术亮点可以考虑在景点详情页接入视频播放模块比如播放景区的宣传视频。视频转码后生成 m3u8 格式的切片文件前端用 vue 配套的视频播放器插件加载 m3u8 地址这样画面更现代。不过要注意这个模块不是核心功能属于加分项放在项目迭代的后期再来做。前端打包后有一个常见坑打包后的静态资源路径不对导致图片、CSS 加载失败。解决方法是修改 Vite 或 Vue CLI 的 publicPath 配置将其设置为相对路径或者 Nginx 中以子路径部署的路径。Nginx 部署时需要注意这里。6. 前后端联调阶段最容易翻车的几类问题到了联调阶段真正的排障工作才开始。前后端各自开发的时候一切正常一联调各种问题就冒出来了。这里我列出几类高频问题每一条都是项目里真实踩过的坑。6.1 跨域请求怎么处理跨域问题出现的原因很直接前端页面运行在 localhost:3000后端接口在 localhost:8080浏览器基于同源策略会拦截跨域请求。解决办法有几类做开发阶段最推荐在 SpringBoot 中配置 CORS 映射Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }注意允许凭证请求时allowedOriginPatterns 推荐用模式而不是直接用 allowedOrigins(*)否则浏览器可能报错。还有一种更彻底的办法是走 Nginx 反代把 /api 路径代理到后端地址从浏览器视角看所有请求都指向同一个源压根没有跨域问题。这正好说明前后端分离项目用 Nginx 部署是顺理成章的方案。6.2 日期与时间格式不一致后端返回给前端的日期默认可能是时间戳或者带 T 的 ISO 字符串前端展示的时候要么不对要么干脆显示成 NaN。规范的解决办法是后端统一日期序列化格式也就是前面 application.yml 里配置的spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8这样后端返回的 JSON 字符串就是“2025-01-15 14:30:00”这种格式前端直接展示即可。如果某些字段前端想显示成 yyyy-MM-dd那就在前端做格式化处理封装一个全局过滤器。这就是分层的意义。6.3 分页插件的返回值包装问题PageHelper 使用后返回的是 Page 对象它继承自 ArrayList不建议直接把这个 Page 对象作为接口返回值返回给前端因为 JSON 序列化时会把分页信息带出来但格式很丑。更加规范的做法是 Service 层直接返回 PageInfo 对象前端通过 pageInfo.list 取数据、通过 pageInfo.total 取总数PageHelper.startPage(pageNum, pageSize); ListScenicSpot list scenicSpotMapper.selectPage(query); PageInfoScenicSpot pageInfo new PageInfo(list); return Result.success(pageInfo);前端拿到数据后分页组件的 current 和 total 直接对应上。如果你用 MyBatis-Plus分页方式略有不同但概念是相通的。联调的时候碰到数据与总数对不上的情况十有八九是分页插件没生效或 PageHelper 与多数据源有冲突。6.4 参数校验与空值防御接口联调时另一个高发问题是参数校验不严格。前端传了一个空字符串或者 null后端没有做处理SQL 查询就异常或者数据库插入了脏数据。这里我推荐两个层面的处理后端层面对必填参数使用 JSR-303 注解比如 NotNull、NotBlank或者在业务逻辑里显式判断并返回友好提示。数据库层面给关键字段加上 NOT NULL 约束和 DEFAULT 默认值。不要只依赖前端校验前端校验只是为了体验后端校验才是安全底线。比如注册接口的密码字段不仅要判空还要按业务规则校验长度。这些代码写起来不复杂但体现的是工程素养面试时也很容易被问到。7. 打包部署从开发机到服务器的最后一公里项目开发到了尾声最后一步就是部署上线。这一部分在许多教学视频里是被忽略的但我认为恰恰是完整项目中含金量比较高的环节经历过的人会对整个系统有更全面的认知。7.1 前端构建与 Nginx 配置前端构建很直接在 Vue 工程根目录执行 npm run build就会生成一个 dist 静态资源目录。将这个目录里的内容上传到服务器的指定目录比如 /usr/share/nginx/ankang然后配置 Nginxserver { listen 80; server_name your_domain; root /usr/share/nginx/ankang; index index.html; location / { try_files $uri $uri/ /index.html; } location /api { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location /uploads { alias /usr/local/ankang/uploads; } }try_files 配置是 Vue 路由在 history 模式下刷新页面 404 问题的最优解。它的作用是当请求的路径不是真实文件时自动回退到 index.html让前端路由接管。这个配置不加你会发现首页能打开但一旦刷新子页面就白屏了。7.2 后端打包与启动后端项目在 IDEA 中执行 mvn clean package 即可构建一个可执行的 jar 包。构建时如果遇到测试用例失败导致打包中断的问题可以加参数跳过测试mvn clean package -DskipTests上传 jar 包到服务器用如下命令启动nohup java -jar ankang-travel-0.0.1-SNAPSHOT.jar --server.port8080 app.log 21 nohup 和 的组合让进程在 SSH 断开后依然运行日志输出到 app.log。如果需要查看日志tail -f app.log 即可。要注意的是服务器上的 MySQL 需要在启动前初始化好并导入项目根目录 sql/ 下的脚本文件。数据库连接参数通过启动命令覆盖是运维时比较方便的方式也可以写入 application-prod.yml 再用 --spring.profiles.activeprod 指定。7.3 负载问题与持续优化方向项目如果只做演示到这一步就已经完成了。但如果想继续优化并积累经验可以从这几个方向入手数据量变大后景点的热门列表接口可以做 Redis 缓存key 设计成 scenic:recommend:top10缓存时间 10 分钟减少数据库压力图片上传后的体积优化前端做图片压缩后端做尺寸裁剪或引入 CDN给后台接口加操作日志记录管理员对景点、酒店、攻略的每次修改这是管理系统常见的进阶需求数据库连接池参数调优连接池初始大小、最大连接数要根据服务器的硬件配置来设这些扩展点没有一样是花哨的全是真实项目中会碰到的实际问题。如果你在毕业设计或者面试中能把其中一两个点讲明白会明显提升整个项目的技术含金量。我个人在实际开发中的体会是这类管理系统最大的价值不在于技术有多新而在于你是否真的把每一个环节跑通了。关于 Vue 打包后布局异常的问题我曾排查了很久最后发现是 index.html 的资源引用路径少了一个点这种小问题在部署阶段最容易出现遇到了一定要耐心看控制台和网络面板逐一排查。整个系统从表设计到前端页面再到打包部署所有流程都走顺之后你会对整个全栈项目有一种豁然开朗的感觉。SpringBoot 负责把复杂配置留给自己、Vue 负责把交互体验交给浏览器、MySQL 和 MyBatis 负责把数据管得明明白白。以后不管碰到什么业务类型的管理系统拿到手的第一反应都能快速拆出“数据模型 接口设计 页面构建”这三条线这才是这类练习项目真正留给你的能力。