SpringBoot+Vue体育新闻网站全栈开发实战与避坑指南 每年毕业设计季我总能碰到好几个选体育新闻网站的同学。题目通常是“基于SpringBoot的WEB体育新闻网站”或者长一点变成“基于SpringBootVue的在线体育赛事与新闻发布系统”听起来覆盖面很广但很多人做着做着就变成了一个“新闻CRUD”——登录、加个富文本编辑器、发文章、列表展示完事交差。这其实挺可惜的因为体育网站真正难的从来不是发新闻而是赛事数据组织、用户互动和实时看球体验这三件事。这篇文章我就把做这类全栈项目的完整思路写下来从需求拆解、技术选型、数据库建模、核心功能实现到联调部署和踩坑记录全流程复盘一遍。内容既适合正在做这个毕设课题的同学也适合想系统学习SpringBootVue全栈开发的朋友。1. 看懂需求再动手体育资讯平台到底在解决什么问题题目里“体育新闻网站”“全端体育资讯互动平台”“在线体育赛事与新闻发布系统”这几个说法本质上是同一套系统的三个侧面。你首先要明白它想干什么它不只是承载文章的CMS还要覆盖赛事信息的结构化展示和用户互动甚至要兼顾管理后台的发布效率。把这个搞清楚了后面的设计才不会跑偏。1.1 体育资讯场景的三个特性体育新闻内容有很强的“时效性”“结构化”和“互动性”。时效性意味着比赛结束几分钟内要有快讯发布这就要求后台发布流程足够简洁不能为了发一篇战报还要填十个表单字段。结构化意味着比分、排名、射手榜、赛程这些数据不能往富文本正文里一塞完事必须拆成独立的数据库表和字段。互动性则更直接球迷看完一场比赛情绪需要一个出口评论、点赞、收藏这些功能不是锦上添花而是这类站点的刚需。这三点决定了很多通用博客系统或新闻网站的代码不能直接拿过来改改就用。比如你做一个普通的个人博客文章表可能只需要标题、正文、发布时间就够了但体育网站不行单是一个赛事详情页就需要关联两支球队、一个联赛、一个比分、一个比赛时间还得算小组排名。数据模型一开始设计得不对后面写接口和前端页面的时候会非常痛苦。1.2 从使用者角度拆解需求拿真实场景来看这个平台至少有三种使用者需求完全不一样使用者核心诉求对应的功能模块游客浏览资讯、查看比分和赛程、搜索感兴趣的内容首页新闻流、赛事列表、赛事详情、搜索注册用户参与讨论、收藏内容、关注关注度高的球队或赛事、管理个人资料登录注册、评论、点赞、收藏、个人中心管理员/编辑快速发布新闻、维护赛事数据、管理分类和评论、查看访问数据后台管理、新闻管理、赛事管理、评论审核、统计面板你做完这套分析就会发现光是一个“登录”就能带出一串问题游客可以看什么登录之后能做什么管理员和普通用户看到的接口是不是同一套评论是否需要审核这些都得在写代码前有一个明确结论。我的建议是游客拥有所有查询接口的权限注册用户可以执行评论、点赞、收藏等写操作后台接口则统一走独立的前缀并做角色校验。1.3 功能清单按优先级切一刀毕设项目最忌讳的就是需求无限膨胀。我给一个合理的优先级划分基础闭环必须做用户注册、登录、注销统一JWT鉴权新闻分类、列表、详情后台新闻发布与修改赛事列表、赛事详情、比分展示评论、点赞、收藏进阶加分有余力做热门新闻排行榜、标签推荐资讯内容中的图片上传与富文本编辑后台数据统计新闻量、用户量、访问量亮点功能答辩加分项基于WebSocket的比分实时推送视频集锦播放M3U8关键词搜索与搜索高亮先保证基础闭环真的能用再考虑亮点。见过太多同学一上来就想做数据大屏结果核心功能都没跑通答辩现场演示到一半页面报错得不偿失。2. 技术选型的底气为什么SpringBootVue是当前最稳的组合“SpringBoot Vue”这个组合在Web开发里已经是近乎标准答案的存在但你要能在答辩时说得清楚它到底好在哪而不是只会说“因为大家都用”。2.1 SpringBoot到底帮你省了哪些事SpringBoot的核心价值是自动配置和生态整合。传统SSM项目要写一堆XML配置文件SpringBoot用starter机制把这些都收敛成了依赖和约定。内嵌Tomcat解决了外置容器的部署问题一个java -jar就能跑起来。对毕设来说这意味着你可以把精力集中在业务代码而不是配置反复横跳上。同时SpringBoot的周边生态非常成熟MyBatis-Plus操作数据库、Spring Security做安全控制、Validation做参数校验、Mail发邮件通知几乎每个功能都能找到对应starter。出了任何问题搜索引擎上都有海量现成案例这对独立开发的学习者来说价值极高。2.2 Vue为什么适合做这类互动型站点体育资讯互动平台的前端有典型的“多状态、多交互”特点页面组件多、新闻列表要滚动加载、赛事状态要动态刷新、用户登录信息要全局共享。Vue的组件化开发把这些都管理得很舒服——每个页面拆成若干组件组件内部只管自己的状态跨页面的共享数据交给Vuex或Pinia。另一个好处是Vue对中文开发者非常友好。Element Plus这类组件库提供现成的表格、表单、弹窗组件后台管理系统几十分钟就能搭出基础界面。相比React庞大的生态链Vue的学习曲线要平缓很多特别适合毕设周期内边学边做。2.3 技术方案对比有多大必要非得前后端分离很多同学会纠结“我是不是用JSPSpringBoot更简单”我直接给结论对这个题目来说前后端分离明显更合适。方案优点缺点适合场景JSP SpringBoot开发链路短不用考虑跨域前后端耦合严重页面逻辑和Java代码混在一起维护困难老项目维护若依/RuoYi脚手架功能齐全直接生成大量代码代码复杂答辩提问容易被难住很难说清是自己的设计快速原型SpringBoot Vue 前后端分离职责清晰前端交互体验好接口可复用答辩有讲头需要处理跨域、联调、部署等额外问题本项目的正解我个人不推荐零基础同学直接拿若依这类脚手架改。毕业设计答辩的核心是“你设计了什么、你解决了什么问题”如果用脚手架改了改界面老师追问RBAC设计、权限拦截、接口鉴权的时候你很难讲明白。自己从零搭一套哪怕简单一点也完全经得起细问。2.4 工程目录怎么组织才不混乱我习惯把项目拆成两个独立目录各自拥有独立的Git仓库后端结构backend/ ├── src/main/java/com/example/sports/ │ ├── controller/ # 接口层 │ ├── service/ # 业务逻辑层 │ ├── mapper/ # 数据访问层 │ ├── entity/ # 数据库实体 │ ├── dto/ # 接口传输对象 │ ├── config/ # 全局配置 │ └── common/ # 统一返回、异常处理、工具类前端结构frontend/ ├── src/ │ ├── views/ # 页面组件 │ ├── components/ # 公共组件 │ ├── api/ # 接口请求封装 │ ├── router/ # 路由配置 │ ├── store/ # 全局状态 │ └── utils/ # 工具函数前后端分离开发的第一步就是定好这个结构不然随着代码变多找文件都会变成一种折磨。3. 数据库设计是关键别把赛事数据当普通新闻存数据库设计是整个项目的地基。我发现很多同学做这类系统最大的问题是“所有内容都往一张新闻表里放”球队、比分、赛季、联赛全部用文本字段拼最后写查询的时候一团乱麻。3.1 用户与权限模型用户模块我建议按轻量RBAC来设计至少四张表sys_user用户表字段包括id、username、password、nickname、avatar、status、create_timesys_role角色表id、role_name、role_code比如ADMIN、EDITOR、USERsys_user_role用户角色关联表sys_menu菜单/权限表如果不想做太复杂可以改成permission表记录权限标识如果你觉得五张表太重也可以用最简单方案user表加一个role字段存USER或ADMIN字符串。这个方案在答辩时也能说清楚但“为什么不做标准RBAC”这个问题你得很合理不然容易被动。我建议正统一点用标准的RBAC模型后面如果做后台菜单权限直接复用。3.2 新闻模块的数据表新闻不能只做一张表至少要拆出分类表和新闻表news_categoryid、name、sort、status newsid、category_id、title、summary、content、cover_image、author_id、status、view_count、comment_count、like_count、is_top、publish_time、create_time、update_time几个容易被忽略的字段说明status0草稿、1已发布、2已下线。发布和下线是后台最基本操作必须有独立状态管理。summary列表页展示用的摘要不能把整个正文拿过去截取性能和排版都难看。is_top置顶标识体育网站头部需要展示重要赛事快讯。view_count和like_count冗余计数读多写少的场景直接查冗余字段不需要实时count。content字段类型用LONGTEXT富文本编辑出的HTML体积不小TEXT类型可能不够用。图片不要存相对零碎的路径建议统一存/uploads/2025/06/xxx.jpg这样带日期目录的格式方便后续迁移和静态资源映射。3.3 赛事模块的建模思路赛事模块是这类网站和普通新闻网站最大的区别。比赛信息本身就具备结构化特征必须拆表league联赛表id、name、logo、country、season team球队表id、name、logo、league_id、founded_year、home_court match_info赛事表id、league_id、home_team_id、away_team_id、home_score、away_score、match_time、match_round、status、venue拆表的核心原因是射手榜、积分榜、球队详情页都需要从多个维度查询数据。比如一个球队详情页要展示“该队所有比赛”和“该队所在联赛的积分排名”如果这些信息全塞在一张新闻表里查询逻辑根本无法写。这里还可以衍生一张standings积分榜表每轮比赛更新后重新计算排名或者比赛更新时实时计算。毕设建议独立一个standings表因为计算逻辑清晰答辩好讲standingsid、league_id、team_id、matches_played、wins、draws、losses、goals_for、goals_against、points、rank3.4 互动数据评论、点赞、收藏怎么设计互动数据有一个常见选择点赞和收藏是分两张表还是用一张表加type区分。我建议一张表加type字段理由是点赞和收藏的行为结构高度一致都是“用户user_id”对“内容business_id”做一次操作无非是业务类型不同user_interactionid、user_id、business_id、business_type1新闻、2赛事、type1点赞、2收藏、create_time这样查询一个人的点赞列表和收藏列表都只需要一条SQL加条件筛选。评论表单独设计commentid、user_id、business_id、business_type、content、parent_id、status、create_timeparent_id用于楼中楼回复为0表示一级评论。status字段在体育社区非常重要因为评论体量大、内容参差不齐设置待审核/通过/删除三个状态后台才能做内容管理。3.5 核心建表SQL参考我贴一段赛事模块的核心建表语句照着这个风格扩展其他表即可CREATE TABLE league ( id bigint NOT NULL AUTO_INCREMENT, name varchar(64) NOT NULL COMMENT 联赛名称, logo varchar(255) DEFAULT NULL COMMENT 联赛logo, country varchar(64) DEFAULT NULL COMMENT 国家/地区, season varchar(32) DEFAULT NULL COMMENT 赛季如2024-2025, status tinyint DEFAULT 1 COMMENT 1启用 0停用, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT联赛表; CREATE TABLE match_info ( id bigint NOT NULL AUTO_INCREMENT, league_id bigint NOT NULL COMMENT 所属联赛, home_team_id bigint NOT NULL COMMENT 主队ID, away_team_id bigint NOT NULL COMMENT 客队ID, home_score int DEFAULT 0 COMMENT 主队比分, away_score int DEFAULT 0 COMMENT 客队比分, match_time datetime DEFAULT NULL COMMENT 比赛时间, match_round varchar(32) DEFAULT NULL COMMENT 轮次, venue varchar(128) DEFAULT NULL COMMENT 比赛场地, status tinyint DEFAULT 0 COMMENT 0未开始 1进行中 2已结束, PRIMARY KEY (id), KEY idx_league_time (league_id, match_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT赛事表;我看到很多初学者会纠结到底加不加外键约束。演示项目加外键会自动建立索引、保证关联数据完整性但对后期的数据更新和删除会造成麻烦比如删除一个联赛所有赛事都要级联处理。我的建议是表结构设计保留逻辑关联关系不强制加物理外键但一定要在常用查询字段上建索引比如match_info的league_id和match_time。4. 核心功能落地从登录认证到视频播放的实现思路需求想清楚了表结构定好了接下来就是最耗时的编码阶段。这一节我把几个关键功能的实现方案和代码骨架全部写出来你可以直接照着推演。4.1 JWT认证简单方案还是Spring Security安全认证是答辩一定会被问到的问题。对于这个项目我建议你用手写拦截器 JWT的方案而不是直接上Spring Security。理由有三点代码量小、逻辑可控、答辩时你可以清晰地从头讲到尾。Spring Security虽然功能强大但概念太多一旦配置不当排查问题的成本可能比写整个业务还要高。流程是这样的用户注册密码使用BCrypt加密存储。用户登录成功后后端生成JWT令牌返回给前端。前端把Token存放在本地存储每次请求放在请求头Authorization: Bearer token。后端写一个拦截器拦截除登录注册外的所有请求解析Token并校验有效期。JWT生成的核心代码Component public class JwtUtil { Value(${jwt.secret}) private String secret; Value(${jwt.expire}) private Long expire; public String createToken(Long userId, String username, String role) { return Jwts.builder() .setSubject(username) .claim(userId, userId) .claim(role, role) .setExpiration(new Date(System.currentTimeMillis() expire)) .signWith(SignatureAlgorithm.HS512, secret) .compact(); } public Claims parseToken(String token) { return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); } }拦截器里把解析出的userId和role存入ThreadLocal这样在Controller中就能随时获取当前登录用户的信息而不需要每个接口都手动从Token里拆字段。记住在请求结束后要清理ThreadLocal避免线程池复用时数据串号。4.2 权限控制怎么做权限控制在SpringBoot中有几种常见方式一是上面说的拦截器判断请求路径前缀二是使用Spring Security的PreAuthorize注解三是自定义注解AOP。毕设推荐方案一简单直观。比如后台管理接口统一以/api/admin/**开头拦截器里判断登录用户角色是否为ADMIN或EDITOR不是就直接返回403if (request.getRequestURI().startsWith(/api/admin/)) { String role (String) claims.get(role); if (!ADMIN.equals(role) !EDITOR.equals(role)) { response.setStatus(403); return false; } }普通用户只能访问自己id下的资源这种细粒度校验尽量放在Service层做而不是全部丢给拦截器因为拦截器拿不到业务上下文硬搞只会让代码越来越绕。4.3 富文本编辑与图片上传新闻发布功能绕不开富文本编辑器。国内比较省心的选择是wangEditor中文文档完整UI简洁直接用npm引入即可不需要额外的复杂配置。Quill也很优秀但是分页、上传图片这些扩展需要自己调对新手来说有门槛。富文本编辑器里的图片上传是个关键细节。编辑器只会把图片以base64形式嵌入如果你没有配置自定义上传一篇带大图的文章可能直接把后端接口打爆。正确的做法是前端配置编辑器使用自定义上传接口把图片传到后端后端返回图片的访问URL编辑器再把URL插入内容里。后端上传接口需要注意三点文件类型白名单只允许jpg、png、gif、webp文件大小限制单张图片建议不超过5M随机文件名避免用户传一个中文名或特殊字符图片存储路径建议配置一个upload目录然后通过配置静态资源映射让SpringBoot直接暴露这个目录Configuration public class WebMvcConfig implements WebMvcConfigurer { Value(${file.upload-dir}) private String uploadDir; Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/uploads/**) .addResourceLocations(file: uploadDir /); } }这样上传成功后返回的URL就是http://localhost:8080/uploads/2025/06/xxx.jpg图片既不需要打进前端代码包也不需要额外引入OSS。4.4 赛事信息展示与比分更新赛事列表页建议直接按时间倒序查询“今天和未来一周”的比赛前端用卡片展示主队、比分、客队、联赛logo和比赛状态。比分更新入口放在管理后台编辑比赛结束后保存比分同时把状态改为“已结束”。这里要做一个判断比分更新之后前端怎么拿到最新数据两种方案**方案A前端定时轮询。**前端每隔30秒请求一次赛事列表接口看数据有没有变化。实现简单缺点是不够实时。**方案BWebSocket广播。**后端在比分更新时向所有连接的客户端推送一条消息。前端收到消息后重新拉取数据或者直接更新本地状态。我推荐你优先实现方案A把系统跑通然后花半天时间额外加一个WebSocket比分推送作为答辩亮点。这个功能的实现思路是前端连上/ws/live后端在比分更新接口中通过广播对象推送新比分前端页面监听消息后刷新对应卡片。WebSocket和轮询结合的方式也是大多数体育网站的真实做法。4.5 M3U8视频播放防晕指南体育资讯网站经常要挂视频集锦而目前视频流最常见的格式是M3U8。这个和热词里的“vue播放m3u8免安装”“web端实时视频”直接相关。前端播放M3U8流我推荐使用video.js结合videojs-contrib-hls插件或者单独使用hls.js。以hls.js为例在Vue组件里播放一个M3U8地址template video refvideoEl controls classvideo-player/video /template script import Hls from hls.js export default { name: M3U8Player, props: { src: { type: String, required: true } }, mounted() { if (Hls.isSupported()) { const hls new Hls() hls.loadSource(this.src) hls.attachMedia(this.$refs.videoEl) hls.on(Hls.Events.MANIFEST_PARSED, () { this.$refs.videoEl.play() }) } else if (this.$refs.videoEl.canPlayType(application/vnd.apple.mpegurl)) { // Safari原生支持 this.$refs.videoEl.src this.src } } } /script播放M3U8最常见的问题是跨域导致请求失败。M3U8的本质是HTTP请求一个多段的m3u8索引文件然后在播放过程中不断请求ts分片文件。服务器要正确响应Range请求头代理配置也要允许Range头透传。如果你后端用Nginx做代理需要确认proxy_set_header Range $http_range;这类配置没有丢掉。4.6 搜索与热门推荐搜索功能毕设阶段没必要上ElasticsearchMySQL本身就够用。最简单的方案是SELECT * FROM news WHERE status 1 AND (title LIKE CONCAT(%, #{keyword}, %) OR summary LIKE CONCAT(%, #{keyword}, %)) ORDER BY publish_time DESC这个写法能跑但只有一两万数据没问题如果数据量大建议加个MySQL全文索引。推荐热门内容可以设计一个加权排序SELECT id, title, view_count, like_count, comment_count, publish_time, (view_count 2 * like_count 3 * comment_count) / (TIMESTAMPDIFF(HOUR, publish_time, NOW()) 2) AS hot_score FROM news WHERE status 1 ORDER BY hot_score DESC LIMIT 10;这个公式的含义是浏览数、点赞数、评论数越多越热同时距离发布时间越近时效权重越高防止旧闻霸榜。答辩时你把这个公式推导讲清楚比任何花里胡哨的算法都更有说服力。5. 前后端联调与部署中的硬核细节前后端分离开发前期各自跑得很欢一到联调阶段就各种“诡异问题”。这一节我把最容易卡住人的几个点全部过一遍。5.1 解决跨域代理优先CORS兜底开发环境最省心的方式是前端代理。在Vite项目中配置// vite.config.js export default defineConfig({ server: { port: 3000, proxy: { /api: { target: http://localhost:8080, changeOrigin: true }, /uploads: { target: http://localhost:8080, changeOrigin: true } } } })这样前端页面里请求/api/news/list会被Vite开发服务器转发到http://localhost:8080/api/news/list浏览器里看不到任何跨域报错。如果某些大型网络环境不好用代理必须后端开CORS那么可以配置一个全局CORS。注意开了跨域之后处理预检请求OPTIONS时必须放行否则AJAX请求会一直失败。曾经帮人排查过一个诡异问题前端Post请求一直报405最后发现是Controller没处理OPTIONS请求踩了一天坑。5.2 Vue打包后如何放进SpringBoot这个操作被问过无数遍。前端项目执行npm run build后会生成一个dist目录里面包含index.html、js、css等静态资源。把dist目录下的内容直接复制到SpringBoot项目的src/main/resources/static目录SpringBoot会自动作为静态资源托管。但有一个关键配置不改就会出大问题。Vue默认的构建配置会生成绝对路径的静态资源引用比如/assets/index-xxx.js而你的项目可能部署在服务器根路径下这样没问题一旦你不想用根路径或者端口后面还要加前缀就必须改// vite.config.js export default defineConfig({ base: ./ })base: ./让打包后的index.html里的资源引用变成相对路径不管部署在哪一层路径下都能找得到静态文件。5.3 路由刷新404这个坑几乎人人踩Vue采用history路由模式时地址是http://xxx.com/news/123这种形式。本地开发没问题因为Vite开发服务器会做fallback处理。但打包放进SpringBoot之后你直接访问http://xxx.com/news/123SpringBoot会拿着/news/123去找静态资源找不到就返回404。这就是热词里“vue路由”“vue打包放进springboot中”最常见的问题。解决办法是让SpringBoot把所有不是/api、不是静态资源的路径都转发到index.html。前端路由再由Vue接管。在SpringBoot中写一个转发ControllerController public class ForwardController { GetMapping(value {/, /news/**, /match/**, /user/**}) public String forward() { return forward:/index.html; } }更好的做法是不在代码里枚举路径而是通过重写资源处理器实现先尝试找静态资源找不到就转发到/index.html。不管哪种思路核心都是把“未知路径”交给前端路由而不是交给404页面。5.4 生产环境配置三件套本地跑通只是第一步部署到服务器还经常有人翻车。最关键的三件事第一配置文件要区分环境。application.yml放公共配置application-dev.yml放本地数据库application-prod.yml放服务器配置启动时用--spring.profiles.activeprod选择环境避免本地密码不小心提交到仓库。第二数据库时区问题。MySQL连接串一定要加serverTimezoneAsia/Shanghai否则插入时间和真实时间会差8个小时。useSSLfalse也要显式声明避免开发环境因为没有SSL证书狂打警告日志。第三文件上传大小限制。SpringBoot默认上传限制是1MB富文本里传几张高清图就报错了在配置里放开spring: servlet: multipart: max-file-size: 10MB max-request-size: 50MB启动脚本我习惯写成这样#!/bin/bash nohup java -jar sports-news-1.0.0.jar \ --spring.profiles.activeprod \ --server.port8080 \ logs/app.log 21 日志重定向到文件前端页面导致的问题至少能通过日志定位而不是跑到服务器上抓瞎。6. 实测踩坑记录这几个问题几乎见一个踩一个开发过程中踩坑是常态。这里直接列出我在类似项目中实际遇到过、也最有代表性的几个问题每一个都是花过时间查资料、翻源码总结出来的。6.1 SpringBoot版本与Java版本不匹配这个真的非常经典。现在很多人新建项目习惯直接选最新的SpringBoot版本而SpringBoot 3.x要求Java 17以上。如果你本机装的是Java 8创建完项目之后就会一直报编译错误。出现“SpringBoot版本太高”类似问题的绝大多数都是版本对不上。我的建议是看本机JDK版本定SpringBoot版本本机JDKSpringBoot版本MyBatis-Plus版本推荐JDK 82.7.x3.5.3及以上兼容即可JDK 112.7.x 或 3.x3.5.5JDK 173.x3.5.5注意需要适配项启动类报红第一反应不是怀疑代码而是先看项目SDK版本和Maven依赖的版本对不对。6.2 hamcrest与JUnit版本冲突如果你引入MyBatis-Plus或者做单元测试时碰到“No tests found”或者测试类报java.lang.NoSuchMethodError八成是JUnit 4和JUnit 5的hamcrest版本冲突。SpringBoot用JUnit 5做单元测试默认依赖的hamcrest是2.2版本。如果你手动引了旧版hamcrest就会在运行测试时各种诡异报错。解决方式是注释掉自己额外引入的hamcrest依赖让SpringBoot统一管理版本。6.3 MyBatis-Plus分页不生效没配置分页插件MyBatis-Plus的selectPage方法如果没有配置分页拦截器表面上不报错但传入的Page对象根本不会执行分页会把全表数据都查出来。这个坑非常隐蔽尤其是列表数据量小的时候根本看不出问题。正确配置分页插件Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }6.4 M3U8视频流跨域与Range请求前端能正常访问接口但M3U8视频就是播不出来控制台报NETWORK_ERROR或MEDIA_ERR_SRC_NOT_SUPPORTED。这种情况八成是服务器没有正确处理视频文件的Range请求或者后端代理没有把Range头传递给视频存储服务器。排查步骤很简单用curl请求视频地址看返回头里有没有Accept-Ranges: bytes和Content-Length。没有这个头浏览器就不会按分片方式播放。如果你用Nginx托管的视频文件检查Nginx配置里是不是被CDN缓存或者压缩模块改掉了相关头。6.5 给接口写一份文档省下大量返工时间这个是我个人最想强调的一点。做完整套项目后给自己写一份接口文档把每个接口的请求方式、参数、返回值字段列清楚。这不是额外工作而是联调和答辩的保命符。前后端分离开发时你经常为一两个字段来回沟通没有文档就只能一次次翻代码。答辩时老师如果让你说明某个接口怎么设计你直接掏出文档马上就显得专业。最后再分享一个我自己做体育类项目的小技巧新闻系统和赛事系统不要做成两套独立的代码让赛事模块直接复用新闻模块的评论、点赞、收藏逻辑互动数据用business_type字段区分业务。这样核心代码集中在几个Controller里后期加新的互动对象比如直播、球员评分就像加一个枚举值一样简单。代码量最少结构也最干净答辩时也很容易讲清楚设计思路。