SpringBoot+Vue前后端分离项目实战:文旅网部署联调与优化
简介一套基于SpringBootVue实现前后端分离的山西文旅网项目源码围绕文旅网站常见的信息展示等功能后端采用Java与SpringBoot框架前端使用Vue.js技术栈并搭配MySQL 5.7数据库适合正在做毕业设计、课程设计、大作业的Java学习者也适合想入门前后端分离开发的小白或进阶开发者拿来练手与二次开发。压缩包共430个文件大小约20.35MB以124个Java后台源码、102个Vue前端组件、41个JS脚本为主体同时包含SQL数据库脚本、XML配置、前端样式、项目文档等目录结构清晰便于按模块学习。目前已有830人学习/浏览。通过这套资源可完整看到从数据库设计到接口开发再到页面渲染的实现链路既能掌握SpringBoot与Vue的整合方式也能学习Maven项目配置、前后端数据交互等关键技能源码注释与文档能够帮助理解核心逻辑遇到问题还可与博主直接沟通实用性和可扩展性都不错。1. 拿到“山西文旅网”压缩包之后先别急着解压这个识别的核心不是“解读某个特定项目”而是一个最真实的任务场景你手里有一个名为基于SpringBootVue的山西文旅网的压缩包极可能是毕业设计、课程设计或外包项目源码。停。先验证一件事这个压缩包里到底有没有pom.xml和package.json同时在根部。如果只有其中一种说明这是个前后端分离但一方缺失的单机版你后面要花大量时间做接口假数据。反过来两个文件都在且分别出现在backend/或server/和frontend/或web/目录下那你面对的就是一个经典的前后端分离项目SpringBoot 3.x或 2.7.x提供 RESTful APIVue 3 Vite 负责页面渲染和交互。这类“XX网”项目文旅网、景区网、电商网的骨架高度相似差异只在业务表和页面数量上。下面以“本地跑通到接口联调再到功能增强”为顺序把 SpringBoot 和 Vue 这两条线都拆开讲重点是怎么改、怎么调、怎么排错而不是泛泛说“这是用 SpringBoot 写的”。2. SpringBoot 后端从简化到分页把所有“文旅网”后端骨架点透2.1 项目结构里必然存在的“骨架”Controller-Service-Mapper无论压缩包里的包名是com.shanxi还是com.culture底层一定是三层架构Controller 接收前端请求、 Service 处理业务、 Mapper或 Repository访问数据库。打开pom.xml先确认依赖里有没有spring-boot-starter-web、mybatis-plus或mybatis-spring-boot-starter、mysql-connector-j也可能叫mysql-connector-java以及spring-boot-starter-validation。如果是个人做毕设尤其喜欢用 MyBatis-Plus因为它自带分页插件和QueryWrapper能省掉大量手写 CRUD。举个例子在ScenicSpotServiceImpl里最常见的文旅网后端写法是这样// ScenicSpotController.java RestController RequestMapping(/api/scenic) public class ScenicSpotController { Autowired private ScenicSpotService scenicSpotService; GetMapping(/page) public ResultIPageScenicSpot getPage( RequestParam(defaultValue 1) int current, RequestParam(defaultValue 10) int size, RequestParam(required false) String name) { PageScenicSpot page new Page(current, size); LambdaQueryWrapperScenicSpot wrapper new LambdaQueryWrapper(); wrapper.like(StringUtils.isNotBlank(name), ScenicSpot::getName, name) .orderByDesc(ScenicSpot::getCreateTime); return Result.success(scenicSpotService.page(page, wrapper)); } }逻辑拆解RequestParam(defaultValue 1)保证前端没传current时不会抛出“缺少参数”的 400 错误LambdaQueryWrapper是 MyBatis-Plus 3.5 的推荐写法列名变化不会影响 SQLResult.success()是把数据包成{ code: 200, data: {...}, msg: success }的统一结构Vue 前端基本都会按这个结构去取data。如果你拿到项目后发现没有分页而是ListScenicSpot一把梭全部返回那要小心数据量超过几百条时文旅网这种图片多、正文多的表一次性全量返回会让首页首屏变慢而且内存占用高。2.2 只改一个yml不够application-dev.yml与application-prod.yml双配置分离很多SpringBoot Vue的毕设项目会把数据库密码和本机端口直接写死在application.properties。接手后第一件事是新建多环境文件。最常见的做法是保留一个主配置application.yml用于选环境把差异放到环境文件里# application.yml spring: profiles: active: dev # application-dev.yml自己机器跑 server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/shanxi_travel?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl注意几个参数characterEncodingutf8是中文乱码的第一道闸少了它标题里的“山西”就会变成?serverTimezoneAsia/Shanghai解决 MySQL 8.x 时区报错问题map-underscore-to-camel-case必须开启否则数据库字段scenic_name无法映射到实体类scenicName查询结果全是 null——这是新手最容易摸不着头脑的地方。StdOutImpl是 MyBatis 的日志实现会在控制台打印完整 SQL 和参数这个在前期联调时千万别关。另外一个常被忽略的点是spring.jackson时间格式。如果实体里有LocalDateTime前端拿到的可能是2025-01-02T10:00:00并不符合展示习惯。建议在application-dev.yml里加一行全局格式化spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8不过要注意date-format只对java.util.Date生效对LocalDateTime无效。处理LocalDateTime更规范的做法是在实体字段上添加JsonFormat(pattern yyyy-MM-dd HH:mm:ss)。所以当你调接口看到时间变成T分隔时别怀疑直接给字段加注解别去动全局配置。2.3 文旅数据表设计的共通套路景点、线路、资讯、用户、评论一个合格的“山西文旅网”项目在数据库里起码要包含以下五类表景点表scenic_spot字段至少包含id、name、province/city山西 11 个地级市、intro景点简介富文本或纯文本、open_time、ticket_price、cover_image、gallery图集常用 JSON 字符串。旅游线路表travel_routeroute_name、scenic_ids用逗号分隔或 JSON、days、price、description。资讯表news/ articletitle、content、author、cover、publish_time。这一张和景点表一样关键——搜索“山西文旅网”觉得太电商化会补充游记攻略需求。用户表userusername、password必须 BCrypt 加密、avatar、phone。评论表commentuser_id、content、create_time、target_id和target_type区分评论的是景点还是资讯这个字段多数毕设都没有建议补上。如果压缩包里没有建表 SQL只有 entity 类那你得靠实体反向建表把TableName(scenic_spot)里的值摘出来把字段类型映射过去——String对应varchar(255)Integer对应intBigDecimal对应decimalLocalDateTime对应datetime。建议把实体类的类名如ScenicSpot拆成小写下划线来建表和默认驼峰映射匹配省得每条查询都手写别名。2.4 接口返回固定结构前端才好写拦截器有一个极常见但很多人不当回事的坑Controller 直接返回 HashMap 或干脆String。没有ResultT统一包装Vue 端axios拦截器就无从写if (res.code 200)。如果你手里的项目是杂乱的返回格式建议在common包里补一个Result类Data public class ResultT implements Serializable { private Integer code; private String msg; private T data; public static T ResultT success(T data) { ResultT r new Result(); r.code 200; r.msg success; r.data data; return r; } public static T ResultT error(String msg) { ResultT r new Result(); r.code 500; r.msg msg; return r; } }前端response.interceptors.response.use里res.data.code ! 200时ElMessage.error统一弹窗错误后端就不用每个方法都 try-catch 了。注意Result必须实现Serializable否则在一部分自定义序列化场景下会告警。这看起来不是文旅网专属但 Web 项目接手后第一件事就是检查返回结构这个习惯能在后期测通前后端时减少大量无用 Debug。3. Vue 前端从复现到跑通路由与页面联调怎么做3.1 初始化前端环境依赖安装与版本不兼容的避坑前端是不是用了 Vite 还是 Vue CLI直接看frontend/package.json里的devDependencies。当前多数“SpringBoot Vue”项目以 Vue 3 为主vue ^3.4.x、vite ^5.x或^6.x、element-plus ^2.x、pinia ^2.x、vue-router ^4.3.x、axios ^1.6.x。如果项目依赖里出现node-sass这一类包安装时会很痛苦Python 环境、node 版本兼容、编译工具链都可能抛错。遇到这种情况一个可行策略是把node-sass剥离出去用sassdart-sass替代删掉package.json里的node-sass执行npm install sass -D再跑npm install。这步不是改业务代码只是把纯 JS 实现的 sass 换成更现代的编译器绝大多数.scss样式不会因此失效。Vue 端安装依赖时Node 版本要特别注意Vite 5 需要 Node 18Vite 6 更是要求 Node 18.17 或 20。node -v如果显示 16.x大概率装完后跑npm run dev会报error:0308010C:digital envelope routines::unsupported这就是 OpenSSL 版本与 Vite 不兼容导致的配置文件加载失败。一个治标不治本的办法是NODE_OPTIONS--openssl-legacy-provider不过更推荐直接升级 Node 到 LTS 20。3.2 路由拆分与动态标题文旅网“页面多而不乱”的典型做法一个文旅网前端最少有这几个一级路由首页/、景点列表/scenic、景点详情/scenic/:id、线路/route、资讯/news、登录/login、后台管理/admin。用 Vue Router 4 的createWebHistory模式时注意后端application.yml里没有干预history模式刷新会出现 404SpringBoot 把/scenic/1交给 controller 匹配匹配不到就返回 404。比较快的解法是后端加一层WebMvcConfigurer把history路由 fallback 到index.html但前端正式部署的还是应配置 nginxlocation / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; }try_files这一行是核心找不到物理文件时把请求返回给index.html由 Vue Router 自行匹配路径。除此之外路由前置守卫也是接手必做的改造尤其是管理员后台。手写一个全局守卫// router/index.js import { createRouter, createWebHistory } from vue-router import { useUserStore } from /stores/user const router createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ { path: /, name: home, component: () import(/views/Home.vue) }, { path: /scenic, name: scenic, component: () import(/views/scenic/List.vue) }, { path: /scenic/:id, name: scenicDetail, component: () import(/views/scenic/Detail.vue) }, { path: /admin, name: admin, component: () import(/views/admin/Admin.vue), meta: { requiresAuth: true } }, ], }) router.beforeEach((to, from, next) { const userStore useUserStore() if (to.meta.requiresAuth !userStore.isLoggedIn) { next({ path: /login, query: { redirect: to.fullPath } }) } else { next() } }) export default routermeta.requiresAuth是一个非常好用的参数化写法在路由配置里标上一个标记守卫里读出来判断比写在跳转函数里逐个判断干净得多。redirect参数解决“登录后跳回首访页面”的问题——比如用户没登录时点了“收藏景点”就该在登录完后回到原页面。很多毕设代码不做这一步用户一收藏就被踹到首页体验很差。3.3 axios 封装统一 BaseURL 避免每次改接口地址在src/utils/request.js里封装 axios 实例是最常见的做法用途不只是加baseURL还包含 token 注入、错误提示和 401 跳转// 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 }, (error) Promise.reject(error) ) request.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) { if (error.response?.status 401) { localStorage.removeItem(token) router.push(/login) } ElMessage.error(error.message || 服务器错误) return Promise.reject(error) } ) export default request接口地址默认写成/api的时候开发环境还需要在 Vite 配置代理到后端 8080// vite.config.js export default defineConfig({ plugins: [vue()], server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true, }, }, }, })changeOrigin: true是必须开的否则后端看到的 Host 还是localhost:5173部分 SpringBoot 项目里如果做了 CorsFilter 域名校验就可能被拦截。用代理还能避开跨域浏览器看到是相对路径/api请求被 Vite 服务转发前端代码里不需要写http://localhost:8080/api这种写死后端地址的写法。如果改掉baseURL后还有 404先看 SpringBoot 的 Controller 类上的RequestMapping是不是已经包含/api前缀——如果已经加了前端baseURL就不能再带/api否则路径会变成/api/api/scenic。3.4 文旅网首页的常用组件轮播图、地图与 m3u8 视频首页轮播图用 Element Plus 的el-carousel就很够用把从后端拿到的coverImage数组直接绑定。视频方面如果景区上传的是.mp4用video标签就行但文旅平台经常需要挂直播流或高清回放最常见的是m3u8格式。Vue 里放 m3u8 的标准方案是用hls.jsnpm install hls.js!-- VideoPlayer.vue -- template video refvideoRef controls autoplay muted stylewidth:100%/video /template script setup import { ref, onMounted, watch } from vue import Hls from hls.js const props defineProps({ src: { type: String, required: true }, }) const videoRef ref(null) const playVideo (url) { if (!url) return const video videoRef.value if (Hls.isSupported()) { const hls new Hls() hls.loadSource(url) hls.attachMedia(video) } else if (video.canPlayType(application/vnd.apple.mpegurl)) { video.src url // Safari 原生支持 } } onMounted(() playVideo(props.src)) watch(() props.src, (newVal) playVideo(newVal)) /scripthls.js的原理是把 m3u8 播放列表拉下来再按列表里的.ts分片顺序请求、拼接播放。注意在onMounted后还要watch一下src因为详情页里切换视频比如从“景区宣传片”切换到“滑雪场实时画面”时不会重新挂载组件onMounted不会再触发。4. 前后端联调的关键路径从首页数据到控制台登录4.1 登录鉴权从“假登录”到 JWT 后端校验文旅网后台的登录如果不做权限控制就只是个摆设。SpringBoot 项目里最常用的是 JWT用户输入用户名密码后端校验通过后返回一个带签名的 token前端存到localStorage之后每次请求在 header 里带上。重点在后端拦截器需要写一个HandlerInterceptor放行/api/login和静态资源其余接口校验 token。这个“放行哪些路径”的配置是很多项目跑不通的根源// WebConfig.java Configuration public class WebConfig implements WebMvcConfigurer { Autowired private JwtInterceptor jwtInterceptor; Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(jwtInterceptor) .addPathPatterns(/api/**) .excludePathPatterns(/api/login, /api/register); } Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }addPathPatterns(/api/**)会拦截所有/api下的请求如果用户上传图片走/files/**那图片上传就不能被拦截所以要么在excludePathPatterns里加/api/upload要么单独建一个不落在/api前缀下的文件接口。很多“前端能打开页面但图片加载不出来”的场景并不是图片真的坏了而是 token 过期后拦截器把/api/file/*也拦了前端又没在图片地址上拼 token导致 401 后整张图挂掉。JWT 校验的拦截器逻辑里通常要读 token 并解析用户但加上后要特别留意跨域预检请求。浏览器发POST请求时如果带自定义 header比如Authorization会先发一个OPTIONS预检。拦截器如果对OPTIONS也执行 token 校验前端就会得到 401登录接口直接不通。所以拦截器中要写一行if (OPTIONS.equalsIgnoreCase(request.getMethod())) { return true; }这一行排在 token 解析逻辑之前。这类“拦截器拦截掉了预检请求”的问题在 Vue 工程中特别高频凡是接到带 JWT 的项目第一件事就应该检查这里。4.2 景区搜索与分页前端参数怎么拼才算和IPage匹配在后端已经按IPage接收参数时前端要这样写请求参数// api/scenic.js import request from /utils/request export const fetchScenicList ({ current 1, size 10, name } {}) { return request.get(/scenic/page, { params: { current, size, name } }) }对应页面里const query ref({ current: 1, size: 10, name: }) const list ref([]) const total ref(0) const loadData async () { const res await fetchScenicList(query.value) list.value res.data.records total.value res.data.total }res.data.records和res.data.total对应 MyBatis-PlusIPage的 JSON 序列化结果。如果拿回来后列表是undefined十有八九是后端直接把Page对象返回了而不是包进Result或者Result里的泛型没有被正确序列化检查返回类有没有getRecords()和getTotal()的 getterLombok 的Data没加就会导致没这些字段。另一个容易踩的点是IPage里records是第一个字段搜索maven依赖时如果发现mybatis-plus-extension版本和mybatis-plus-boot-starter不一致序列化的字段名也可能变成list而不是records目前较为少见但只要出现就直接统一版本解决。4.3 评论表与反向关联一对多在前端的呈现文旅网里“景点评论区”是体现项目完整度的一块。后端如果只在Comment实体里存了userId前端展示时只能显示一串数字。有两种常见处理方式一是CommentVO里冗余一个username字段查出父表后手动 set 进去二是用TableField(exist false)加一个非表字段搭配 XML 自定义 SQL 联查。代码结构里万不得已才用for循环逐条调用查询用户这是典型的 N1 问题// 不推荐的写法数据量一大就完蛋 for (Comment c : comments) { User u userMapper.selectById(c.getUserId()); c.setUsername(u.getUsername()); }改成批量联查// 推荐一次性查出所有 userId ListLong userIds comments.stream() .map(Comment::getUserId) .distinct() .collect(Collectors.toList()); MapLong, String userMap userMapper.selectBatchIds(userIds) .stream() .collect(Collectors.toMap(User::getId, User::getUsername)); comments.forEach(c - c.setUsername(userMap.getOrDefault(c.getUserId(), 游客)));这段代码在数据量为 10 条时看不出差别但到了 500 条就能明显感到接口变慢。接手此类项目后一个检查点就是列表相关的循环里有没有“查库”动作。有就改掉性能收益立竿见影。4.4 图片上传一个 File 接口和 Vue 组件的对齐景点后台通常要上传封面图和图集。前端用 Element Plus 的el-upload关键是action属性直接写上绝对或代理地址el-upload action/api/file/upload :headers{ Authorization: Bearer ${localStorage.getItem(token)} } :on-successhandleUploadSuccess el-button上传封面/el-button /el-uploadel-upload默认用 XMLHttpRequest 上传不走自定义 axios 实例所以headers必须显式传 token否则后端拦截器会因没有 token 把上传接口拦下而不响应成功事件。后端接收MultipartFilePostMapping(/api/file/upload) public ResultString upload(RequestParam(file) MultipartFile file) { if (file.isEmpty()) { return Result.error(文件为空); } String originalFilename file.getOriginalFilename(); String ext originalFilename.substring(originalFilename.lastIndexOf(.)); String fileName UUID.randomUUID().toString().replace(-, ) ext; String savePath D:/shangxiTravel/upload/ fileName; try { file.transferTo(new File(savePath)); return Result.success(/images/ fileName); } catch (IOException e) { return Result.error(上传失败); } }后端的savePath一般不要放在项目工程目录内部否则mvn clean会顺手删掉用户传的图片。如果不写本地路径也可以用云存储但毕设场景下本地transferTo就够了。还要补一个静态映射否则浏览器用/images/xxx.jpg会 404spring: web: resources: static-locations: file:D:/shanxiTravel/upload/,classpath:/static/这样http://localhost:8080/images/xxx.jpg就能直接访问。如果后端在 8080前端在 5173前端页面里的img src/images/xxx.jpg则会指向 5173所以上传后回显图片时需要根据环境拼后端地址const imgUrl import.meta.env.DEV ? http://localhost:8080${path} : path这种判断写在一个工具函数formatImg里即可不必每个组件都写。5. 从打包到部署若依系 / 非若依项目的区分处理5.1 判断是否是若依RuoYi底座决定你要不要改代码每次提到SpringBoot Vue的毕设绕不开“若依”这个名字。打开压缩包后快速辨别如果前端界面风格是左侧菜单、顶栏面包屑、深色 Sidebar而且包名里有com.ruoyi那你手里是一个基于若依二次开发的文旅网。若依体系与普通 SpringBoot 项目的区别在于后端application.yml里会有ruoyi.profile配置指向文件上传路径。所有返回结构是AjaxResult类继承自HashMap。前端封装好了request.js响应拦截器在处理code 401后会在控制台打印getRequestToken校验。数据库层使用通用BaseEntity因此 Controller 里传参可以接收一个继承了BaseEntity的实体。二次开发若依类文旅网时最该注意的是分页对象不同若依使用PageUtils.startPage()开启线程变量分页调用前必须紧跟一条IService.page中间不能隔其他数据库操作。如果手动在原项目里复制了List返回前端Table会显示Cannot read properties of undefined (reading records)或rows具体看封装。在若依里分页返回是getData()拿到rows字段而不是 MyBatis-Plus 的records。接手后先识别自己属于哪一类别套错分页格式。5.2 Maven 打包与前端产物合并SpringBoot 直接托管静态资源生产模式最简单的方式是把 Vue 构建产物放进 SpringBoot 的static目录让 Tomcat 同时托管 API 和页面。步骤是cd frontend npm run build构建完成后会生成frontend/dist目录把里面所有内容复制到backend/src/main/resources/static/。后端再执行mvn clean package -DskipTests启动后访问http://localhost:8080/就是完整站点。这个方案的优点是单端口部署不需要额外配置 Nginx适合毕设演示和低并发场景。缺点是后续前端每次改动都要重新构建一次且static目录会被clean清理——所以建议把static排除出 Maven 的clean范围或只打包不 cleanmvn package -DskipTests-DskipTests的意思是跳过测试代码的编译执行打包更快同时避免测试环境连不上数据库导致打包失败。如果打包报“找不到符号”或“程序包不存在”先执行mvn clean install -DskipTests把依赖装进本地仓库再打包。5.3 数据库脚本迁移SQL 文件里时间字段的坑压缩包里通常附shanxi.sql。用 Navicat 或命令行导入后检查一下scenic_spot表里有没有create_time或update_time字段的默认值。不少手写建表 SQL 不使用DEFAULT CURRENT_TIMESTAMP导致插入数据时需要后端显式设置创建时间。SpringBoot 插入时没赋值这条记录在数据库里的create_time就是NULL前端分页按create_time排序时就会出现时序错乱或 NullPointerException。给表补一个稳妥的默认值ALTER TABLE scenic_spot MODIFY COLUMN create_time datetime DEFAULT CURRENT_TIMESTAMP, MODIFY COLUMN update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;ON UPDATE CURRENT_TIMESTAMP是 MySQL 的自动更新时间写法只要记录有更新这一列自动改成当前时间不用在 Java 代码里手动setUpdateTime。这属于典型的“一行 SQL 省十天维护”的细节值得在导入数据库后立即执行。6. 收尾不装完留几个能继续优化的硬核点文旅网做出来后通常不会只有基础 CRUD还要能讲得出“我做了哪些设计上的选择”。下面是可以在答辩或 Code Review 时直接讲的三个增强点。6.1 景点缓存SpringCache 或 Redis 二选一景点列表是读多写少的典型场景每次进入首页都从 MySQL 查全表压力没意义。使用spring-boot-starter-data-redisCacheable是改动最小的方案Cacheable(value scenic:top, key hot_list, unless #result null) public ListScenicSpot getHotScenicSpots() { return scenicSpotMapper.selectHotList(); }对应配置spring: data: redis: host: localhost port: 6379 spring: cache: type: redis redis: cache-null-values: falsecache-null-values: false表示空结果不缓存避免恶意请求拿不存在的 key 打穿缓存。注意引入 Redis 后如果本机没有 Redis 服务项目启动会报连接失败。不想装 Redis 的话也可以把缓存切到caffeine配置更轻单机足够用。6.2 全文检索资讯和攻略模块的标题搜索文旅网的搜索框如果只做数据库LIKE %keyword%数据量上千条以后会慢。纯基于 SpringBoot 的实现一个轻量方案是对title和tags字段建全文索引用 MySQL 的MATCH ... AGAINSTSELECT * FROM news WHERE MATCH(title, tags) AGAINST (山西 春节 IN BOOLEAN MODE);需要在建表时给title和tags加上FULLTEXT索引ALTER TABLE news ADD FULLTEXT INDEX ft_title_tags (title, tags);但要注意MySQL 全文索引对中文分词支持有限默认的最小 token 长度是 3 个英文字符或一个中文词组。所以“山西”“旅游”这类短词可能匹配不到。替换方案是引入 HanLP 或直接查资讯表时用LIKE兜底。在 SpringBoot 里可以使用org.apache.lucene之类的库但对一个文旅平台来说复杂度偏高。6.3 视频点播m3u8 流媒体处理与播放完成状态上报前面提到的hls.js能播放 m3u8但管理员的维护成本在流媒体切片生成。如果一个景区视频是.mp4格式且希望生成多码率 m3u8使用ffmpeg是最常见的服务器处理方案ffmpeg -i scenic_720.mp4 -codec copy -start_number 0 -hls_time 10 -hls_list_size 0 -f hls scenic_hls/720p.m3u8-hls_time 10表示每 10 秒切片一个.ts文件-hls_list_size 0表示保留所有分片而不只保留最近的若干个不然播放中拖动时间轴到后段会出现 404。后端在启动类里预留一个ffmpeg可执行路径配置前端根据浏览器分辨率切换index.m3u8的不同分辨率版本即可。这块不要求所有毕设都做但如果你手里的项目涉及景区直播流或大视频展示写进项目介绍里会明显提升技术评分。实际上拿到这种压缩包后最值钱的技能是把骨架“提出来”先保证本地能跑通再逐页对照后端接口把联调理顺最后把上传、缓存、搜索三个点做掉。这时候你就不是在“复现一份别人的毕设”而是真正拥有了一套能改、能维护、能部署的文旅平台代码基础。之后无论接到的任务是“加一个线路推荐接口”还是“把首页地图换成高德”都是在同一套骨架上的正常扩展。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →