ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SpringBoot+Vue前后端分离项目:桂林旅游导游平台源码全解析

SpringBoot+Vue前后端分离项目:桂林旅游导游平台源码全解析 SpringBootVue前后端分离、JavaMySQL这几个词组合在一起基本就是当下毕业设计、课程设计项目里的标准配置。这次要聊的这套桂林旅游景点导游平台管理平台源码正好是一个把后端接口、前端页面、数据库表全部串起来的完整项目适合拿来当毕设、课设的底子也适合刚接触全栈开发的同学逐行研究。这套项目承载的业务很明确景点信息展示、导游讲解预约、后台管理、用户管理一整套流程。听起来不复杂但麻雀虽小五脏俱全从前端的 Vue 路由到后端的 JWT 登录认证再到 MySQL 的表设计几乎覆盖了一个 Web 应用要经历的各个环节。下面我按自己平时拆项目的习惯把它的架构思路、技术选型、表设计、运行步骤和可复现的实操经验一条一条写清楚。1. 项目整体思路与功能拆解1.1 为什么选 SpringBoot Vue 这套组合先说结论这套组合不是唯一选择却是当前 Java 全栈项目里性价比最高、最容易被答辩老师认可的组合之一。SpringBoot 解决的是后端开发体验问题。传统 SSM 项目要写大量 XML 配置一个数据源配半小时SpringBoot 用自动配置把这些杂事全接管了写一个 Controller 就能直接跑起来。Vue 解决的是前端开发效率问题组件化开发让页面复用变得很简单配合 Element UI 这种现成组件库后台管理界面半天就能搭出框架。两端通过 JSON 交互职责清晰代码结构一目了然。从学习角度讲这套组合还有一个隐性好处市面上教程极多无论是 B 站视频、博客文章还是 Gitee 上的开源项目遇到问题基本都能搜到答案。相比之下如果选一些冷门框架光环境配置就够折腾好几天的。对于毕设这种有明确时间节点的任务选成熟技术栈本身就是一种风险管理。1.2 平台功能模块全景我把这套平台按使用者拆成三块来看游客端前台页面注册登录、景点分类浏览、景点关键词搜索、景点详情查看、导游列表查看、在线预约导游讲解、发表评论、个人中心查看我的预约记录。这部分面向普通用户重点在界面友好和操作顺畅。管理端后台页面景点管理新增、编辑、删除、上架下架、景点分类管理、导游信息管理、预约订单审核、用户管理、系统数据统计。这部分面向管理员重点在信息维护效率和数据结构完整性。导游侧简化后台查看自己被预约的订单、确认或拒绝预约、维护个人简介和擅长语言。部分项目还会把导游侧并入管理端用角色权限区分具体看实现粒度。这三块业务全部通过 RESTful 接口打通前端只负责渲染数据后端只负责提供数据和校验权限。所以你在源码里会看到很明显的分层Vue 的 views 目录对应着页面的粒度Java 的 controller/service/mapper 目录对应着业务逻辑的粒度。1.3 角色划分与权限边界角色设计是这类平台的核心也是答辩时的加分项。通常分为三种角色ROLE_USER 普通游客只能访问前台页面可以预约导游、发表评论、维护自己的个人信息。ROLE_ADMIN 管理员拥有全部后台权限可以管理景点、导游、预约审核和用户。ROLE_GUIDE 导游可以查看与自己相关的预约记录对预约进行确认操作。权限控制必须做两层。前端用 Vue Router 的路由守卫控制页面访问未登录用户跳转登录页非管理员访问后台时拦截并提示后端再通过拦截器校验每个请求携带的 Token并在 Controller 里做角色判断。前端控制只是体验后端控制才是安全底线这一点是很多课设项目容易忽略的。2. 技术栈选型与核心原理2.1 后端选型SpringBoot、MyBatis-Plus、MySQL后端框架我建议直接用 SpringBoot 2.7.x配 JDK 1.8 或 8。有人会问为什么不上 Spring Boot 3因为 3.x 强制要求 JDK 17很多教学环境和企业生产环境还在用 JDK 8选 2.7.x 兼容性最好部署时也不用折腾。数据库访问层首选 MyBatis-Plus。它把单表 CRUD 操作封装好了Service 里写一行list(new LambdaQueryWrapper(Spot.class).like(Spot::getName, keyword))就能完成模糊查询不用手写 XML SQL。遇到需要手写 SQL 的复杂统计场景又可以直接在 Mapper 接口上写Select注解灵活度完全保留。相比之下Spring Data JPA 虽然也能做但碰到分页和动态查询时HQL 和 JPA Specification 的学习成本比 MyBatis 的 XML 高不少答辩时也容易被追问细节。数据库用 MySQL 5.7 或 8.0 都可以。需要注意版本差异MySQL 8.0 的驱动类名是com.mysql.cj.jdbc.Driver而且连接串里必须加时区参数serverTimezoneAsia/Shanghai否则会报时区错误。5.7 虽然不用加但加上也不影响。依赖清单里还会看到 Lombok省去 getter/setter 模板代码、Hutool封装了常用工具类如加密、日期处理、JWT 相关库用于登录令牌。这三件套基本是这类项目的标配。2.2 前端选型Vue 2 Element UI Axios前端框架选 Vue 2.x 还是 Vue 3.x要分情况讨论。如果是从零开始写新项目我会推荐 Vue 3 Vite Element Plus性能更好、组合式 API 更现代但如果是参照现成源码改且源码本身是 Vue 2 Element UI那就没必要强行升级因为升级不是简单改版本而是连带改组件用法、生命周期和路由写法工作量很大。这套源码沿用的是 Vue 2 生态。Vue 2 配 Element UI 的优势是稳定、资料多、组件封装完整后台管理系统的表格、表单、弹窗、分页组件拿来即用。Axios 负责 HTTP 请求通过拦截器统一注入 Token 和处理返回值不用在每个页面里重复写请求逻辑。Vue Router 做页面跳转模式建议选hash模式因为history模式刷新页面时若不配置服务器重定向会 404有时候部署环境不支持配置直接 404 卡在登录页。虽然 hash 模式地址栏多个#不太好看但胜在省心对毕设演示项目是最稳的选择。2.3 前后端交互的规范与认证机制前后端交互靠的是一套约定俗成的 JSON 格式。后端统一返回结构大致是public class ResultT { private Integer code; // 200 成功500 失败401 未登录 private String msg; private T data; public static T ResultT success(T data) { ResultT r new Result(); r.code 200; r.msg 操作成功; r.data data; return r; } public static T ResultT error(String msg) { ResultT r new Result(); r.code 500; r.msg msg; return r; } }前端 Axios 拦截器这样写axios.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] Bearer token } return config }) axios.interceptors.response.use( res { const data res.data if (data.code 401) { localStorage.removeItem(token) router.push(/login) return Promise.reject(未登录或登录过期) } return data }, err Promise.reject(err) )登录成功后后端签发一个 JWT 字符串前端存到localStorage之后所有请求都自动带上。后端的拦截器拿到 Token 后做两件事先校验签名和有效期再解析出用户 ID 和角色放到当前请求的上下文里供 Controller 取用。这样一个无状态认证流程就闭环了。3. 数据库设计与核心表结构3.1 表结构设计的基本思路数据库设计直接决定了业务代码好不好写。我的习惯是先画业务关系图再转成表结构一个用户可以对多个景点发表多条评论一个用户可以提交多个预约单一个景点可以对应多个导游一个景点属于一个分类。其中预约表是核心桥接表把用户、景点、导游三者的关系串起来。表命名统一用下划线风格Java 实体类用驼峰命名配置 MyBatis-Plus 的驼峰映射后自动转换。主键统一用bigint的自增 ID业务字段尽量用varchar和datetime金额字段用decimal(10,2)。每个业务表都带create_time和update_time两个通用字段插入和更新时交给公共字段自动填充省去每个方法里手动赋值。3.2 核心业务表逐表拆解这里列六张核心表基本能覆盖项目全部业务用户表 sys_user字段类型说明idbigint主键usernamevarchar(50)登录账号唯一索引passwordvarchar(100)BCrypt 加密后的密文nicknamevarchar(50)昵称phonevarchar(20)手机号avatarvarchar(255)头像地址rolevarchar(20)角色编码 user/admin/guidestatustinyint状态 1正常 0禁用create_timedatetime创建时间密码字段这一条要特别强调不要明文存储。项目里用 Spring Security 的BCryptPasswordEncoder或 Hutool 的 BCrypt 工具加密登录时再比对密文既安全也是答辩加分项。景点分类表 spot_category字段包括 id、name、sort、status。分类一般就做两层比如“自然风光”“人文历史”“休闲度假”前端列表页用分类筛选景点。景点表 scenic_spot字段类型说明idbigint主键category_idbigint分类ID外键逻辑关联namevarchar(100)景点名称covervarchar(255)封面图imagestext轮播图逗号分隔多个URLaddressvarchar(255)地址descriptiontext详细介绍pricedecimal(10,2)门票参考价heatint热度用于排序statustinyint上架状态create_timedatetime创建时间导游表 guide字段包括 id、name、avatar、introduce、language擅长语种、level星级、status。有些项目会把导游和用户表打通用user_id字段关联导游信息挂在用户账号下这样导游登录时能直接看到自己的订单。推荐后一种设计逻辑更合理。预约表 reservation字段类型说明idbigint主键user_idbigint预约人spot_idbigint关联景点guide_idbigint导游tour_datedate讲解日期statustinyint0待确认 1已确认 2已完成 3已取消remarkvarchar(255)备注create_timedatetime预约时间评论表 comment字段包括 id、user_id、spot_id、content、score、create_time。评分可以做成星标组件前端富文本和评分器直接绑定后端只存整数或小数点后一位。3.3 容易踩坑的字段设计细节第一状态字段的语义一定要统一。比如预约的 status 用 0/1/2/3那我建议在前后端约定成常量字典前端用this.$dict或枚举映射显示中文而不是散布在代码各处的魔法数字否则后期维护时根本分不清 2 到底是“已完成”还是“已取消”。第二金额不能存 float。浮点数有精度问题票务类数据一旦涉及金额务必用 decimal。第三图片存储不要直接存二进制。本地开发时把图片放到项目静态目录存相对路径部署后建议用对象存储表里存 URL。这样既简单又不会拖慢数据库。第四给查询频繁的字段加索引。景点表的 name 加普通索引预约表在 user_id 和 status 上加联合索引评论表在 spot_id 上加普通索引。数据量小的时候不明显但如果答辩时被问到性能优化这个回答比“我加了缓存”更有说服力。4. 环境搭建与项目初始化实操4.1 后端环境准备与依赖后端运行需要三样东西JDK 1.8 或 8、Maven 3.6、IDEA 或 Eclipse。安装时注意JAVA_HOME环境变量要配到 JDK 的根目录Maven 的settings.xml里最好配置国内镜像源不然下载依赖能慢到怀疑人生。核心 pom.xml 依赖片段如下dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt/artifactId version0.9.1/version /dependency dependency groupIdcn.hutool/groupId artifactIdhutool-all/artifactId version5.8.22/version /dependency /dependenciesspring-boot-starter-web是基础中的基础负责内嵌 Tomcat 和 MVC 相关能力MyBatis-Plus 负责操作数据库Lombok 减少样板代码jjwt 和 hutool 负责 Token 生成和常用工具。4.2 前端环境准备与依赖前端需要 Node.js版本 14 或 16 都可以不建议直接上最新的 20/22部分旧依赖可能和 Node 20 有兼容问题。装完 Node 后自带 npm建议再配一个淘宝镜像源npm config set registry https://registry.npmmirror.com然后进入源码的 frontend 目录执行npm install。如果安装过程报错优先检查 Node 版本和镜像源其次删掉node_modules重新安装大多数问题都能这样解决。4.3 从零到一把整套源码跑起来拿到源码后按这个顺序操作创建数据库比如guilin_tour导入项目根目录下的sql文件初始化表结构和基础数据。用 IDEA 导入后端代码等待 Maven 下载完依赖。修改application.yml里的数据库连接信息server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/guilin_tour?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 你的数据库密码 driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl map-underscore-to-camel-case: true运行后端主类看到 Tomcat started 说明后端起来了。打开前端目录执行npm install完成后执行npm run serve默认端口是 8081浏览器访问http://localhost:8081。前端起在 8081后端在 8080前端通过vue.config.js里的 devServer 代理把/api开头的请求转发到后端从而绕过跨域问题。这个代理配置是前后端联调的关键我见过不少同学前端请求直接写http://localhost:8080虽然也能通但一部署就残废不如一开始就走代理。4.4 二次开发的几种切入方式拿到源码后不建议上来就改功能先按三条线熟悉项目第一条线是“接口线”打开后端 Controller逐个看每个接口的 URL、入参、返回值对应前端src/api目录下的请求方法。第二条线是“数据库线”对照 3.2 节的表结构理解每张表的数据流向。第三条线是“页面线”打开前端 views 目录从登录页开始走一遍看每个页面调用了哪些接口。如果要新增功能比如给景点加一个“推荐指数”字段按这个顺序改先给scenic_spot表加字段再在实体类加属性再用 MyBatis-Plus 的代码生成器或者手写 Service 方法最后在前端列表页和详情页补上展示。全程不用大改框架这也是这类项目适合二次开发的重要原因。5. 核心业务实现关键模块走一遍5.1 景点模块列表分页、搜索、详情景点模块是最基础也最完整的 CRUD 示例。前端列表页通过pageNum、pageSize、keyword、categoryId四个参数请求接口后端 Controller 接收后传给 ServiceGetMapping(/spot/list) public ResultIPageScenicSpot list(RequestParam Integer pageNum, RequestParam Integer pageSize, RequestParam(required false) String keyword, RequestParam(required false) Long categoryId) { LambdaQueryWrapperScenicSpot wrapper new LambdaQueryWrapper(); wrapper.like(StringUtils.hasText(keyword), ScenicSpot::getName, keyword) .eq(categoryId ! null, ScenicSpot::getCategoryId, categoryId) .orderByDesc(ScenicSpot::getHeat); IPageScenicSpot page spotService.page(new Page(pageNum, pageSize), wrapper); return Result.success(page); }这段代码演示了 MyBatis-Plus 最常用的两点LambdaQueryWrapper链式条件构造以及like和eq配合布尔参数实现“条件动态生效”。关键词搜索用LIKE分类筛选用EQ排序按热度倒序前台展示天然带热门排序效果。详情页就简单了直接按 ID 查一条同时查该景点的评论列表和可选导游列表。三个接口并行返回前端用Promise.all请求减少白屏时间。5.2 导游预约一张表串起完整业务闭环预约是整个平台最有业务含量的模块。游客在景点详情页选择导游、日期、填写备注提交后生成reservation记录状态为 0待确认。管理员或导游在后台看到待确认列表点击确认后状态变为 1已确认游客端个人中心就能看到“已确认”标识。到约定日期后管理员在后台标记“已完成”完成整个闭环。创建预约时要处理几个边界情况同一个导游同一日期不能重复预约同一个用户对同一个景点不能同时存在未完成预约这两个校验一个写在 SQL 层一个写在 Service 层long count reservationService.count(new LambdaQueryWrapperReservation() .eq(Reservation::getGuideId, guideId) .eq(Reservation::getTourDate, tourDate) .ne(Reservation::getStatus, 3)); if (count 0) { return Result.error(该导游当天已有预约安排); }这段写在哪里很重要。写在 Controller 里是“面向过程”写在 Service 里才对因为这属于业务规则不属于接口适配逻辑。5.3 登录鉴权JWT 是核心登录接口的流程是前端提交用户名和密码后端查出用户用BCrypt校验密码通过后生成 JWT 返回前端。JWT 生成的核心逻辑public String generateToken(Long userId, String role) { return Jwts.builder() .setSubject(String.valueOf(userId)) .claim(role, role) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() 7 * 24 * 60 * 60 * 1000L)) .signWith(SignatureAlgorithm.HS256, secretKey) .compact(); }我设置的是 7 天有效期毕设演示足够。如果希望更严谨可以引入 Redis 存 Token 并做单点登录过期时间是半小时。答辩时如果你能说出“无状态认证”这几个字老师通常会觉得你是真理解了而不是只会调登录接口。注意真正实现时 Controller 层代码不要自己解析 Token统一在拦截器里解析然后通过ThreadLocal或方法参数传给业务层这样业务代码不用关心认证细节职责更清晰。5.4 管理端的统计数据怎么来后台首页通常要展示平台概况总用户数、景点数、预约总数、近七天新增预约。这类数据适合在 MySQL 里直接聚合SELECT DATE_FORMAT(create_time, %Y-%m-%d) AS day, COUNT(*) AS cnt FROM reservation WHERE create_time DATE_SUB(CURDATE(), INTERVAL 7 DAY) GROUP BY DATE_FORMAT(create_time, %Y-%m-%d) ORDER BY day在 MyBatis 的 Mapper 中写Select注解返回结果用一个包含day和cnt的视图对象接收前端用 ECharts 折线图展示。整个过程不难但写 SQL 时注意DATE_FORMAT和GROUP BY的字段要对齐否则分组结果不对。6. 常见问题与排查技巧实录6.1 高频报错速查表我整理了一份这套项目运行时最常见的报错和排查方向照着查基本能解决 80% 的问题。现象常见原因排查方法启动报 Access denied for user数据库账号密码错误或 root 不支持远程检查application.yml的用户名密码本地用 root/密码连一下 navicat连接 MySQL 报时区错误MySQL 8 连接串缺少时区参数连接串加serverTimezoneAsia/Shanghai前端请求 404代理没生效或接口路径不对先直接访问后端接口确认存在再检查vue.config.js的 devServer 代理前端跨域报错前后端端口域名不一致且没走代理用 devServer 代理不要在前端写死后端地址8080 端口被占用其他程序占用了端口命令行执行netstat -ano | findstr 8080结束对应进程或改 server.portMaven 依赖下载失败仓库源太慢或依赖冲突配置阿里云镜像源删除本地.m2/repository下对应目录重新拉取npm install 一直报错Node 版本过新或网络问题切换 Node 14/16配置 npmmirror 源页面白屏控制台报 favicon 404前端资源路径问题检查publicPath配置一般是/或相对路径中文乱码数据库连接串或表编码问题建库时指定 utf8mb4连接串加characterEncodingutf86.2 容易丢分的细节清单项目能跑只是及格线能不能高分看细节。我评项目时最常看到这几个问题第一没有任何异常处理。后端代码到处 try-catch 但都空着或者没捕获时直接把堆栈抛给前端页面上一大段英文报错。正确做法是用RestControllerAdvice全局异常通知统一记录日志并转换为友好提示。这是生产级和玩具项目的分水岭。第二密码明文存库。这个问题严重到可以直接影响成绩。记得用 BCrypt 加密实在不想用 Spring Security 全家桶用 Hutool 的 BCrypt 工具也行关键是库里的密文不能一眼看出原密码。第三删除数据直接DELETE。管理端删除景点时物理删除会把关联的预约和评论一起删掉数据完整性被破坏。用逻辑删除字段deleted配合 MyBatis-Plus 的TableLogic注解删除只是打个标记数据还在随时能恢复。第四分页查询写死LIMIT 页码。手写 SQL 分页时不要自己拼 offset用 MyBatis-Plus 的Page对象或者 MyBatis 的 PageHelper既安全又省事。第五上传图片的路径写死。比如把图片保存到本地磁盘某目录页面用C:\xxx绝对路径访问换台电脑就裂开。要么存项目静态目录相对路径要么存对象存储 URL。6.3 被答辩老师问住的几个技术问题我模拟一下答辩现场这几个问题被问概率极高为什么用 MyBatis-Plus 不用 JPA答本系统涉及较多动态条件查询和聚合统计MyBatis-Plus 既保留了手写 SQL 的灵活性又对单表 CRUD 做了封装开发效率高JPA 在简单 CRUD 上更方便但多表动态查询的学习成本和书写成本偏高。前端路由守卫是干嘛的答在进入每个页面之前做校验比如未登录跳转登录页非管理员访问后台提示无权限。它是权限提示层真正防越权靠后端的 Token 校验和角色判断。Token 被别人盗用了怎么办答Token 存在前端 localStorage 里理论上存在被 XSS 窃取的风险。可以在密码修改时让 Token 立即失效在服务端维护一个黑名单列表或者把有效期设置短一些再配合刷新令牌机制。毕设项目做到 7 天有效期和统一过期处理已经能满足需求。如果景点数据量过百万当前表设计有什么问题答热点查询景点名和分类可以用覆盖索引减少回表评论和预约按景点 ID 分库分表景点详情这种大文本字段单独拆表或走缓存。这题没有标准答案重点是展示你有扩展意识而不是只会写 CRUD。项目中哪个模块是你独立完成的答提前准备一个完整的模块讲清楚比如导游预约模块从建表、查询、状态流转到前端交互能把细节说的越具体越好。老师问这个问题是判断你对自己的项目到底了解多深。最后再分享一个实操技巧这套项目我前后带人跑过好几遍最深的体会是不要一上手就摊开所有代码先只启动后端用 Apifox 或 Postman 把登录、景点列表、预约这三个核心接口调通再去开前端页面。这样做的好处是一旦前端报错你能立刻判断问题出在前端还是接口层而不是被一个报错信息带着满项目乱翻。还有一个小诀窍如果你时间紧张加需求不要硬造模块优先找现有模块的“毕业缺陷”补上。比如给景点加多图轮播、给评论加回复、给预约加导出 Excel。这些功能看似零碎但每一个都能讲出完整业务闭环在课设和毕设报告里特别好写改造成本也低。最后提醒一句网上很多源码下载下来根本跑不通大概率不是代码问题而是环境问题。按我上面 4.3 节的顺序一步一步走本地能跑通再谈改功能。祝你的毕设顺利。
返回列表