☰
SpringBoot+Vue旅游网站管理平台实战:从毕设选题到完整开发指南
2026/10/16 7:09:04 网站建设 项目流程

1. 这个平台到底解决什么问题:毕设选题前的需求拆解

先说结论:如果你正在为毕业设计或课程设计找题目,SpringBoot+Vue这套组合做旅游网站管理平台,几乎是性价比最高的选择之一。原因很简单——它同时覆盖了后端开发、前端开发、数据库设计三大块,技术栈主流、需求清晰、演示效果好,而且有大量可扩展的空间。

这里的"安康旅游网站管理平台",核心业务非常典型:围绕某个旅游目的地的资源展示、线路推荐、资讯发布、用户互动、订单管理来构建一套完整的管理系统。它不是一个简单的静态网页,而是"游客端展示 + 管理端维护"的双端结构。游客能看到旅游景点、路线、攻略、新闻动态,管理员能在后台维护这些内容、处理订单、管理用户。

从学习的角度看,这个项目有非常完整的业务闭环:

  • 游客端:注册登录、浏览景点、查看攻略、收藏线路、在线预订
  • 管理端:景点管理、线路管理、资讯管理、用户管理、订单管理
  • 基础设施:数据库设计、权限控制、文件上传、接口鉴权

更关键的是,这套东西不是空中楼阁。安康作为一个旅游城市,它的业务场景有真实的内涵——自然风光、人文景点、美食特产、节庆活动都可以成为系统中的数据分类。这比做一个"虚拟书店""假想商城"要自然得多,答辩的时候也好讲,因为业务逻辑和现实世界对得上。

我见过太多学生做完一个项目,答辩时被问"为什么这个表要这样设计"就卡壳,核心原因就是项目的业务背景是硬编的。而旅游平台的好处恰恰在于:每一个功能都能对应到真实的使用场景,每一个字段都有存在的理由,这让你有东西可讲。

如果你已经有了一定的Java基础,但还没系统性做过一个完整的"前后端分离"项目,这个选题也特别适合练手。它能一次性让你把Maven依赖管理、MyBatis-Plus操作数据库、SpringSecurity做权限控制、Vue组件化开发、Axios异步请求、ECharts数据可视化这些点全部串起来,而不是像教程那样一个个孤立地学。

2. 技术栈选型的逻辑:SpringBoot 3 + Vue 3 + MySQL 8 的搭配思路

在开始写代码之前,先聊聊这套技术栈的选型逻辑。很多同学抄项目的时候只关心"能跑起来",不关心"为什么用这个"。其实当你理解了每个组件在整个系统里的定位,遇到问题的时候自然知道该查哪里。

2.1 SpringBoot 为什么是后端的标准答案

SpringBoot在后端项目里的地位已经不需要过多解释了,但你要知道它到底解决了什么问题。传统的JavaWeb开发要配置一堆XML文件、手动管理Tomcat、处理各种依赖版本冲突,而SpringBoot把这些都做掉了:内嵌的Tomcat容器、自动化的依赖管理、起步依赖(starter)机制、自动配置,让开发者专注于写业务代码。

在这个项目里,几个关键的依赖选型是这样的:

依赖版本用途选型理由
spring-boot-starter-web3.xMVC框架、REST接口内嵌Tomcat,快速开发接口
mybatis-plus-boot-starter3.5.xORM框架不需要写重复的CRUD SQL
mysql-connector-j8.xJDBC驱动支持MySQL 8新特性
lombok最新版简化实体类代码少写getter/setter
spring-boot-starter-validation3.x参数校验统一接口入参的合法性校验
spring-boot-starter-test3.x单元测试接口调试和测试

MyBatis-Plus在这里是个重点加分项,因为它比原生MyBatis写SQL的情况要少得多。你只需要定义实体类,继承BaseMapper<T>,基础的增删改查方法就直接有了,不需要写大量的XML映射文件。对于单表操作频繁的管理系统来说,效率提升非常明显。

2.2 Vue 3 + Element-Plus:管理端界面的生产力组合

前端的选型逻辑同样需要理解。如果你用纯HTML+JavaScript写管理端页面,工作量庞大且不优雅;如果用Vue2,生态虽然成熟但已经趋于维护状态。Vue3才是当前的主流方向,配合Element-Plus组件库可以快速搭建出风格统一的界面。

Vue 3最重要的两个特性是Composition API和响应式系统。Composition API把逻辑按功能组织,而不是按选项类型组织,这在你写一个景点管理页面(需要同时处理列表查询、搜索过滤、分页、弹窗编辑、删除确认)时特别好用。所有的状态和操作逻辑可以组织在setup()里,比Vue2的data+methods+watch三块分离清晰得多。

在这个项目里,前端MVP技术栈只需要这几个:

  • Vue 3 + VueRouter 4:单页应用路由
  • Pinia:状态管理(登录态、用户信息)
  • Axios:HTTP请求封装
  • Element-Plus:UI组件库
  • ECharts:数据可视化图表(后台统计)
  • Vite:开发服务器与构建工具

2.3 MySQL 8 数据库设计:表结构就是业务的骨架

数据库设计是整个项目的地基。很多人建表的时候想到什么加什么,最后表之间关系混乱、数据冗余严重。一个合格的旅游管理平台,表结构应该围绕几个核心实体展开。

以这个项目为例,核心表至少包含:

  • 用户表(user):用户ID、用户名、密码、手机号、头像、角色(游客/管理员)
  • 景点表(scenic):景点ID、名称、图片、简介、详细描述、分类、地址、开放时间、门票参考价、状态
  • 旅游线路表(route):线路ID、名称、封面图、出发地、目的地、天数、价格、行程亮点、状态
  • 攻略/资讯表(strategy):资讯ID、标题、封面图、摘要、正文、浏览量、发布时间
  • 酒店表(hotel):可选,如果要做住宿预订模块
  • 订单表(orders):订单号、用户ID、关联线路ID、下单时间、出行时间、人数、总价、状态
  • 收藏表(collection):用户ID、收藏对象类型、对象ID、收藏时间
  • 分类表(category):景点分类、线路分类等

字段设计的核心原则是:每个字段都要有明确用途,能关联的用外键逻辑关联,能用枚举的不要用字符串散存。比如订单状态,建议用0待支付/1已支付/2进行中/3已完成/4已取消这样的数字枚举,而不是直接存"待支付"三个字——因为你在前端展示时做状态标签映射更方便,统计时也方便。

数据库连接方面建议使用连接池。默认的HikariCP是SpringBoot内置的,配置一下连接地址、账号密码就行。MySQL 8注意时区问题,连接URL里要加上serverTimezone=Asia/Shanghai。

3. 核心模块的代码实现:从登录鉴权到景点管理的完整链路

说了这么多选型的理由,是时候看代码了。我把整个项目里最容易出问题、也最核心的几个模块拆出来讲,每个模块都附上关键代码和思路。

3.1 JWT + SpringSecurity 的权限控制实现

旅游平台有游客端和管理端两个角色。游客需要登录才能下单和收藏,管理员则需要验证身份才能进后台。用SpringSecurity+JWT的方式是目前主流且简洁的方案。

流程是这样:用户提交用户名密码到登录接口,校验通过后签发一个JWT令牌返回给前端;前端把令牌存在本地(localStorage或Pinia),每次请求在请求头带上Authorization: Bearer <token>;后端通过过滤器校验JWT有效性,把用户身份放到SecurityContext中。

核心代码——JWT工具类:

@Component public class JwtUtil { @Value("${jwt.secret}") private String secret; @Value("${jwt.expire}") private Long expire; // 毫秒数 public String generateToken(Integer userId, String username) { Date now = new Date(); Date exp = new Date(now.getTime() + expire); return Jwts.builder() .setSubject(username) .claim("userId", userId) .setIssuedAt(now) .setExpiration(exp) .signWith(SignatureAlgorithm.HS256, secret) .compact(); } public Claims parseToken(String token) { return Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody(); } }

注意版本问题。如果你用的是新版JJWT库(0.11+),API有变化,setSigningKey等过时方法建议迁移到Keys.hmacShaKeyFor(secret.getBytes())。我最初写的时候用的是老版本API,换了依赖版本后跑出了WeakKeyException,查了好久才发现是密钥长度不够256位。

再写一个过滤器校验请求头:

@Component public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { @Autowired private JwtUtil jwtUtil; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String header = request.getHeader("Authorization"); if (StringUtils.hasText(header) && header.startsWith("Bearer ")) { String token = header.substring(7); try { Claims claims = jwtUtil.parseToken(token); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(claims.getSubject(), null, null); SecurityContextHolder.getContext().setAuthentication(authentication); } catch (Exception e) { // 令牌无效/过期,不设置认证信息 } } filterChain.doFilter(request, response); } }

配置SecurityConfig时,关键在于放开登录注册和景点浏览接口,其余接口统一要求认证。

@Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeHttpRequests() .requestMatchers("/api/user/login", "/api/user/register").permitAll() .requestMatchers("/api/scenic/list", "/api/scenic/detail").permitAll() .anyRequest().authenticated() .and() .addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); } }

这一步是最容易卡住的地方。很多时候登录接口能通,但一访问业务接口就403或401,八成是Security的过滤链配置问题,或者没有把自定义过滤器加到正确的链路上。

3.2 景点模块的CRUD+分页+条件查询实现

景点模块是最典型的管理端功能。列表页需要支持:按名称模糊搜索、按分类筛选、按状态筛选、分页展示。我们用MyBatis-Plus的Page对象配合条件构造器就能非常优雅地实现。

@RestController @RequestMapping("/api/scenic") public class ScenicController { @Autowired private ScenicService scenicService; @PostMapping("/list") public Result pageQuery(@RequestBody ScenicQueryDTO dto) { // 分页查询,默认当前页1,每页10 Page<Scenic> page = new Page<>(dto.getPageNum(), dto.getPageSize()); LambdaQueryWrapper<Scenic> wrapper = new LambdaQueryWrapper<>(); if (StringUtils.hasText(dto.getName())) { wrapper.like(Scenic::getName, dto.getName()); } if (dto.getCategoryId() != null) { wrapper.eq(Scenic::getCategoryId, dto.getCategoryId()); } if (dto.getStatus() != null) { wrapper.eq(Scenic::getStatus, dto.getStatus()); } wrapper.orderByDesc(Scenic::getCreateTime); Page<Scenic> result = scenicService.page(page, wrapper); return Result.success(result); } }

这里有个小经验:查询条件尽量用DTO对象封装,而不是直接把前端参数散着传。原因有两个——参数多了之后接口签名不优雅,而且将来新增筛选条件时不需要改方法签名。

MyBatis-Plus的条件构造器有几种,LambdaQueryWrapper是我最推荐的,因为它在编译期就能检查字段名是否正确,避免字符串手写错了查不出来。虽然代码看起来比QueryWrapper多几个字母,但值得。

3.3 批量删除与逻辑删除的取舍

景点管理里有个高频需求是删除。这个项目我建议使用逻辑删除——就是在表中加一个deleted字段,删除时执行UPDATE而不是DELETE。原因很好理解:管理系统的数据是有历史价值的,用户收藏过的景点如果被物理删除了,收藏关联表就会产生悬空引用;而且逻辑删除让你可以轻松恢复误删数据。

MyBatis-Plus对逻辑删除支持得非常简便:

mybatis-plus: global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0

这样配置之后,所有查询会自动追加WHERE deleted = 0,删除操作会自动变成UPDATE ... SET deleted = 1,不需要你自己操心。

但注意一个坑:设置了这个全局配置后,如果你在某个查询里需要查询包括已删除的数据(比如后台的回收站功能),就得手动重写SQL了。常规场景用逻辑删除就好,不用过度设计。

3.4 文件上传功能的二选一方案

景点图片、资讯封面、用户头像,这些都是图片上传需求。实现方案有简单有复杂,我建议毕设阶段用最简单可靠的方案。

方案一:上传到本地服务器指定目录,保存相对路径到数据库,前端拼接域名访问。这个方案不需要额外依赖,适合学习。

@PostMapping("/upload") public Result upload(@RequestParam("file") MultipartFile file) { String originalFilename = file.getOriginalFilename(); String ext = originalFilename.substring(originalFilename.lastIndexOf(".")); String fileName = System.currentTimeMillis() + UUID.randomUUID().toString().substring(0, 6) + ext; String datePath = new SimpleDateFormat("yyyy/MM/dd").format(new Date()); String dir = uploadDir + datePath; File dirFile = new File(dir); if (!dirFile.exists()) { dirFile.mkdirs(); } file.transferTo(new File(dir, fileName)); String url = "/upload/" + datePath + "/" + fileName; return Result.success(url); }

关键点:文件名一定要重新生成,不要用前端传来的原名,不然相同文件名会互相覆盖,而且中文文件名会引发乱码和跨平台问题。这里我用了时间戳+随机字符串的方式,保证了唯一性。如果你担心撞名,直接上UUID就行。

另外,需要配置一个静态资源映射,把/upload/**映射到本地磁盘目录,否则浏览器访问不到上传的图片。

@Configuration public class WebConfig implements WebMvcConfigurer { @Value("${file.upload-dir}") private String uploadDir; @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/upload/**") .addResourceLocations("file:" + uploadDir + "/"); } }

方案二:接入云存储服务。如果是项目展示,这个方案会显得更专业,但考虑到你可能没有现成的存储资源,我就不展开了。简单说一句:思路是先拿后端签发的临时凭证,前端直传存储平台,然后把返回的URL存到数据库。

3.5 数据统计模块:ECharts图表的后端接口设计

管理端首页通常会放几个统计图表,比如近7日订单趋势、景点分类占比、用户增长趋势。后端接口的设计思路是返回聚合好的数据,前端直接喂给ECharts就好。

订单趋势的SQL写法:

// 假设记录时间段和订单表结构 SELECT DATE_FORMAT(create_time, '%Y-%m-%d') AS day, COUNT(*) AS count FROM orders WHERE create_time >= #{startDate} AND create_time <= #{endDate} GROUP BY DATE_FORMAT(create_time, '%Y-%m-%d') ORDER BY day

返回的数据结构可以设计成这样:

{ "categories": ["2025-01-01", "2025-01-02", "2025-01-03"], "series": [ {"name": "订单量", "data": [12, 24, 15]} ] }

这个接口没什么复杂的地方,但需要注意时区和日期格式。数据库里时间的存储格式,建议统一用datetime,Java实体类用LocalDateTime接收,不要用Date,因为Date默认格式化和时区配置太容易出问题。

4. 前端Vue3页面的组织方式:从登录页到管理面板

后端接口写完了,前端怎么组织,直接影响你这套系统的可用性和可维护性。

4.1 路由结构与权限守卫

前端路由分两块:游客端界面和管理端界面。游客端主要有首页、景点列表、景点详情、攻略列表、个人中心;管理端有仪表盘、景点管理、线路管理、资讯管理、订单管理、用户管理。

Vue Router 4配置路由时,特别注意管理端的路由要用meta标注需要权限,然后在全局路由守卫里判断登录状态。

// router/index.js const routes = [ { path: '/', component: Home }, { path: '/scenic/:id', component: ScenicDetail }, { path: '/login', component: Login }, { path: '/admin', component: AdminLayout, meta: { requiresAuth: true }, children: [ { path: '', component: Dashboard }, { path: 'scenic', component: ScenicManage }, { path: 'route', component: RouteManage } ] } ] router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (to.meta.requiresAuth && !token) { next('/login') } else { next() } })

4.2 Axios拦截器的统一封装

每个页面都要调接口,重复写axios.get(url, { headers: { Authorization: ... } })肯定不行。用拦截器统一处理才是正确姿势。

// utils/request.js import axios from 'axios' import { ElMessage } from 'element-plus' import router from '../router' const request = axios.create({ baseURL: '/api', timeout: 10000 }) request.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }) request.interceptors.response.use( response => { if (response.data.code === 401) { localStorage.removeItem('token') router.push('/login') ElMessage.warning('登录已过期,请重新登录') } return response.data }, error => { ElMessage.error(error.response?.data?.message || '请求失败') return Promise.reject(error) } ) export default request

这里有个设计细节:后端返回的统一响应体是{ code, message, data },拦截器里把code === 401(未授权)的统一拦下来跳转登录页,这样每个页面都不需要写"登录过期"的处理逻辑。

4.3 景点管理页面的完整逻辑

管理端景点管理页面是典型的三段式组件结构:搜索表单区、表格展示区、弹窗编辑区。我直接贴一个核心逻辑片段,注意看Composition API的组织方式。

<template> <div class="scenic-manage"> <el-card> <!-- 搜索表单 --> <el-form :inline="true" :model="queryParams"> <el-form-item label="景点名称"> <el-input v-model="queryParams.name" placeholder="请输入" clearable /> </el-form-item> <el-form-item label="分类"> <el-select v-model="queryParams.categoryId" placeholder="全部" clearable> <el-option v-for="item in categoryList" :key="item.id" :label="item.name" :value="item.id" /> </el-select> </el-form-item> <el-form-item> <el-button type="primary" @click="handleSearch">查询</el-button> <el-button @click="handleReset">重置</el-button> </el-form-item> </el-form> <!-- 表格 --> <el-table :data="tableData" border> <el-table-column prop="id" label="ID" width="80" /> <el-table-column prop="name" label="景点名称" /> <el-table-column prop="address" label="地址" show-overflow-tooltip /> <el-table-column label="状态"> <template #default="{ row }"> <el-tag :type="row.status === 1 ? 'success' : 'info'"> {{ row.status === 1 ? '上架' : '下架' }} </el-tag> </template> </el-table-column> <el-table-column label="操作" width="200"> <template #default="{ row }"> <el-button size="small" @click="handleEdit(row)">编辑</el-button> <el-popconfirm title="确定删除吗?" @confirm="handleDelete(row.id)"> <template #reference> <el-button size="small" type="danger">删除</el-button> </template> </el-popconfirm> </template> </el-table-column> </el-table> <!-- 分页 --> <el-pagination v-model:current-page="queryParams.pageNum" v-model:page-size="queryParams.pageSize" :total="total" layout="total, prev, pager, next, sizes" background @change="fetchList" /> <!-- 编辑弹窗 --> <el-dialog v-model="dialogVisible" title="编辑景点" width="600px"> <el-form :model="form" label-width="100px"> <el-form-item label="景点名称"> <el-input v-model="form.name" /> </el-form-item> <el-form-item label="景点简介"> <el-input v-model="form.description" type="textarea" :rows="3" /> </el-form-item> <el-form-item label="封面图片"> <el-upload action="/api/file/upload" :headers="uploadHeaders" list-type="picture-card" @success="handleUploadSuccess" > <el-icon><Plus /></el-icon> </el-upload> </el-form-item> <el-form-item label="分类"> <el-select v-model="form.categoryId"> <el-option v-for="item in categoryList" :key="item.id" :label="item.name" :value="item.id" /> </el-select> </el-form-item> </el-form> <template #footer> <el-button @click="dialogVisible = false">取消</el-button> <el-button type="primary" @click="handleSubmit">确定</el-button> </template> </el-dialog> </el-card> </div> </template>

脚本部分的核心逻辑是:查询列表方法、删除方法、提交方法、以及弹窗的打开和关闭逻辑。这里就不贴全代码了,最关键的是封装好fetchList方法,任何状态变化之后统一调用它刷新数据,避免到处手动维护表格数据。

4.4 富文本编辑器的选择

资讯管理页面需要录入较长的图文内容,这时候需要一个富文本编辑器。我推荐用wangeditor,它轻量、易用、对Vue3支持好。要注意的是,富文本编辑器的内容里如果有<img>标签,图片地址通常是你上传后返回的相对路径,需要在显示时加上你的服务器前缀,这个细节比较隐蔽,我见过好几个人在富文本图片显示不出来时卡很久。

5. 部署与运行的注意事项:从本地环境到项目答辩演示

代码写完了,部署运行这一关很多同学会栽跟头。项目跑不起来或者演示的时候出bug,本事再大也白搭。

5.1 环境版本搭配

我这里给一个经过验证的版本组合,可以少踩很多坑:

组件建议版本说明
JDK17SpringBoot 3.x强制要求JDK17+
MySQL8.0+5.7也能跑,但8更推荐
Node.js18+Vite要求高版本Node
Maven3.6.3+构建后端项目
前端包管理器npm或pnpm建议pnpm,安装更快

SpringBoot 2.x和3.x有不少区别,最大的一个是javax包改为jakarta。如果你下载的源码是SpringBoot3版本,导入项目后遇到import javax相关报错,把javax.servlet替换成jakarta.servlet即可。

5.2 后端启动步骤

  1. 创建数据库travel_platform,执行项目里的init.sql脚本,把表单和数据都建好。
  2. 修改application.yml里的数据库连接信息(用户名、密码、URL)。
  3. 使用IDEA打开后端项目,等待Maven自动下载依赖。
  4. 运行主启动类。
  5. 访问http://localhost:8080/api/ping确认后端启动成功。

5.3 前端启动步骤

  1. 把前端项目放到任意目录,用命令行进入项目根目录。
  2. 执行npm install安装依赖。如果网络不行,用国内镜像源加速。
  3. 修改vite.config.js里的代理配置,将/api代理到后端地址。
  4. 执行npm run dev启动开发服务器。
  5. 浏览器访问http://localhost:5173。

这里有个关键配置——前端开发服务器的代理。如果你不配置代理,前端直接请求http://localhost:8080/api/xxx也可以,但会遇到跨域问题。用Vite代理就简单了:

// vite.config.js export default defineConfig({ plugins: [vue()], server: { port: 5173, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } })

配置之后,前端请求/api/scenic/list会被自动转发到http://localhost:8080/api/scenic/list,浏览器不存在跨域。这样一个代理配置解决了开发环境的跨域问题。

5.4 答辩演示的几个建议

项目能跑起来只是第一步,答辩的时候怎么演示组织语言也很重要。我建议按这个顺序演示:

  1. 先打开游客端首页,展示景点列表、攻略文章,说明这是什么场景。
  2. 注册一个新账号登录,演示收藏和下单流程。
  3. 切换到管理端,展示新增景点、编辑资讯、处理订单操作。
  4. 最后打开数据统计页,结合ECharts图表说明业务数据的变化。

整个演示过程控制在5分钟内。注意一定提前准备好演示数据,图片不要有失真或加载失败的情况。有些同学答辩时现场注册账号、现场上传图片,网络一卡或者输入法出问题,整个节奏就乱了。提前把账号注册好、图片路径配好,是演示顺序里最容易被忽略但其实最重要的一环。

6. 常见BUG与排查经验:我实际踩过的坑

最后分享几个我没有在文档里见过、但真的很常见的运行时问题。每个都是我在实际开发中卡了很久才找到原因的。

6.1 JWT密钥太短导致启动即报错

JJWT库对HS256算法有安全性检查,会校验密钥长度至少为256位。如果你随便写了个字符串比如"secret",启动项目或者第一次登录时会抛异常。正确做法是生成一个足够长的字符串,比如32个以上字符,或者直接用一个Base64编码后的随机串。

jwt: secret: 9b0a8d51e9c2f54d30b7a16567efac90e45c8f62a6a632d9d91c06e916040a75 expire: 86400000

6.2 图片上传成功但访问404

这个90%是静态资源映射配置没生效。检查三个地方:配置文件里的file.upload-dir路径是否存在;WebMvcConfigurer中的映射是否写对;路径末尾的斜杠是否缺失(file:后面拼的路径如果没以斜杠结尾,映射经常失效)。另外,如果你把项目打包成JAR在服务器上运行,上传路径不要用项目内部的相对路径,要使用服务器上独立的绝对路径,否则解压后的临时目录会被清掉。

6.3 Redis未配置或端口占用

如果项目里使用了Redis做缓存,请先确保本地Redis服务已启动。我遇到过一次比较烦的情况是Redis默认端口6379被别的程序占用了,项目能启动但一访问就报连接超时。排查手段很简单:检查端口占用情况,改一个不冲突的端口就行。

6.4 前端白屏但控制台无报错

这个问题很迷惑。有一次前端打开是白屏,F12也没有任何报错,最后发现是路由配置里的history模式在本地开发时没问题,但刷新后静态资源路径找不到。解决方法是改成createWebHashHistory,或者在后端加一个转发规则,把所有非API请求转发到前端入口。毕设演示场景下,直接用createWebHashHistory最省事,虽然URL上会多个#,但完全不影响演示。

6.5 MyBatis-Plus分页不生效

分页查询返回的总数一直是0或全部数据,这是因为没有配置分页插件。MyBatis-Plus的分页功能不是默认开启的,你需要配置一个拦截器:

@Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }

别小看这个配置,我见过好几个项目看起来"一切正常",但一翻页就发现数据不对,几乎所有原因都是这个插件没加。

写在最后:这个项目做完你能带走什么

对我来说,做完一个完整的SpringBoot+Vue项目,比刷三个月八股文算法题有用得多。它把零散的知识点串成了一条线——你知道Maven怎么管依赖、数据库表怎么设计、接口怎么定义、前端怎么对接、部署要注意什么,这些是背几百道面试题都换不来的"项目感"。

如果你把这个平台作为毕设,我建议在跑通基础功能后,挑一个方向做深一点。数据统计模块加个导出Excel功能、资讯模块加个点赞评论功能、景点模块加个地图展示功能,都会让答辩的深度上一个台阶。不需要多,一个亮点就够。

最后再提醒一句:拿到任何源码先不要急着改,先按照文档把项目跑通,再对照数据库表结构和接口文档把每个模块的调用关系理清楚,然后才动代码。按这个顺序走,你才真正"拥有"了这个项目,而不是只当了个运行脚本的搬运工。

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

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

立即咨询