ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

微视频管理系统实战:Spring Boot+Vue+云平台架构与设计

微视频管理系统实战:Spring Boot+Vue+云平台架构与设计

1. 为什么我会去折腾一个微视频管理系统

1.1 微视频管理的真实痛点,远远不只是“存文件”

前几年我参与了一个内部视频素材库的项目,表面需求很简单:把散落在各台电脑、移动硬盘和聊天记录里的短视频素材统一管起来。但真正动手调研后才发现,问题比想象中麻烦得多。团队里的剪辑师需要找一段“去年年会上的开场视频”,往往要在几个人的硬盘里翻半天;运营要上传一批新拍的片段,文件命名乱七八糟,根本不知道谁传的、什么时候传的、有没有版权风险;管理者想看这周的素材上传情况,只能让助理手动拉Excel。说白了,光有“存储”远远不够,微视频管理系统真正要做的是把“文件”变成“资产”,每一段视频都应该有身份、有状态、有去向。

这个系统最后就是用Java + Spring Boot + Vue + 云平台这套组合做出来的。前端使用Vue构建管理界面,后端使用Spring Boot提供RESTful接口,视频文件不落在应用服务器本地,而是直接上传到云平台的对象存储服务,应用服务器只保存元数据。整个项目从需求确认到上线跑通大概花了两个月,期间经历了多次返工,也踩了不少坑。这篇文章不打算讲那种“从零到一教你写代码”的教程,而是从项目决策、数据模型、实现要点、部署排障这条线,把值得借鉴和值得警惕的部分都说清楚。如果你正准备做类似的资源管理类系统,或者想了解前后端分离项目如何结合云平台落地,这篇应该对你有参考价值。

1.2 技术选型:为什么是这套组合,而不是别的

很多人问,为什么选Spring Boot + Vue,而不是更“轻”的Node.js方案,或者更“一体”的服务端渲染方案。说实话,这个选择不是追赶时髦,而是综合考虑团队情况、生态成熟度和后期维护成本。

Spring Boot在Java领域几乎是“标准答案”,我选择它主要是这几层原因:

第一,团队后端主力语言是Java,用Spring Boot的上手成本最低,出了问题也最容易找到人维护。第二,Spring Boot的生态非常完整,权限控制有Spring Security,数据访问有MyBatis-Plus或者Spring Data JPA,缓存有Spring Cache,做微服务拆分也有现成的Spring Cloud支持。第三,Java的强类型和编译期检查,在这种涉及视频状态流转、权限校验的系统里,能减少不少低级错误。

Vue那边的原因更简单:Vue的学习曲线平缓,组件化和响应式数据绑定让管理后台开发效率很高。配合Element UI或者Ant Design Vue这类组件库,几天就能把列表页、表单页、详情页搭出来。再加上Vue Router与Vuex/Pinia的组合,在单页应用里做路由权限和全局状态管理非常顺手。

云平台的选择则遵循一条原则:视频文件肯定不能存应用服务器本地,对象存储是必需品。主流云厂商都能提供S3兼容的对象存储、CDN加速、边缘转码等服务,这些能力可以让一个小团队不用自建存储集群。同时,云服务器用来跑应用,数据库用云数据库或者自建MySQL都可以,部署上尽量容器化,方便后面迁移。项目名里的“基于云平台”,不是简单把代码扔到云服务器上,而是从架构上就把对象存储、CDN、数据库、应用服务拆分开,各用各的云资源。

2. 核心模块与数据模型设计:动工之前必须想清楚的事

2.1 功能边界:先做“管视频”,别急着做“剪视频”

项目最大的风险不是不会写代码,而是需求蔓延。最初需求方提了一堆想法:在线剪辑、自动字幕、智能标签、多人协同批注……如果一上来全做,两个月肯定交付不了。我给出的方案是分优先级,第一版只做好四件事:上传、管理、审核、检索。

  • 上传:支持大文件分片上传,传完后后端抽取出时长、分辨率等信息。
  • 管理:视频列表、详情查看、标签维护、上下架操作。
  • 审核:上传的视频必须经过管理员审核才能公开可见,审核记录留痕。
  • 检索:支持标题模糊搜索、标签筛选、上传时间范围筛选。

至于在线剪辑、人脸识别这些,放到二期。这样做的好处是,数据模型不需要为“未确定的需求”预留过于复杂的设计,核心表结构一次到位,扩展字段通过关联表而不是疯狂加列。功能边界的另一个维度是角色权限。第一版只设两种角色:普通成员和管理员。普通成员负责上传和查看自己上传的内容,管理员负责审核所有人的内容、下架违规视频、查看统计仪表盘。权限模型越简单,越容易在后端Spring Security里配置清楚。

2.2 数据库表结构与状态机:状态用整数,别用字符串

微视频管理系统的核心不是技术多炫,而是数据模型要干净。我设计了这么几张核心表:

  • user表:用户信息,包含username、password_hash、role、status、created_at等字段。密码用加密算法,别存明文。
  • video表:视频主表,这是整个系统的中心。字段包括id、title、description、duration、size、format、resolution、cover_url、video_url、md5、status、uploader_id、category_id、created_at、updated_at、published_at。
  • tag表和video_tag关联表:视频和标签是多对多关系,检索时按标签筛选。
  • audit_log表:记录每一次审核动作,包括auditor_id、video_id、action(approve/reject/offline)、reason、created_at。这既是审计需要,也是后续做统计的基础。
  • category表:视频分类,如培训、活动、产品介绍、花絮等。

最关键的是状态字段设计。我强烈建议状态status用整数存储,配合Java枚举做映射,而不是直接用“pending”“approved”这类字符串。原因有二:第一,整数在数据库索引上更高效;第二,避免字符串拼写混乱。比如状态为1表示待审核,2表示审核通过,3表示审核驳回,4表示已下架。

状态流转也要在设计阶段定死:上传成功后初始状态为1;管理员审核通过后变为2;管理员驳回后变为3,上传者可以修改信息后重新提交回到1;已上线的视频可以下架变为4;下架后不能直接改回2,必须重新走审核流程。这个限制是在业务逻辑层硬编码的,不能只靠前端隐藏按钮,因为接口可以被直接调用。

再说视频文件的存储路径。数据库里不要存完整URL,存相对key,比如“videos/2025/03/06/xxxx.mp4”,由后端根据存储桶名称和key拼出访问地址。这样做的原因是对象存储的域名可能变化,如果数据库里写死完整URL,将来换存储桶或加自定义域名会非常痛苦。

2.3 前端路由与页面结构规划

前端页面结构直接影响Vue项目的组织方式。我用Vue Router配置了四个一级路由模块:

  • /upload:上传页,拖拽上传、进度展示、填写视频信息。
  • /library:视频库,列表展示所有当前用户有权限看到的视频,支持筛选和搜索。
  • /review:审核中心,仅管理员可见,展示待审核和已处理的视频列表,可进入详情审核。
  • /dashboard:数据看板,展示上传趋势、审核通过率、分类占比、播放次数Top等统计图表。

这里要特别提醒一点:前端路由的权限控制只是用户体验层面的“隐藏”,真正的权限校验必须以后端接口为准。比如普通用户直接访问/review路由,前端会拦一下跳到403页,但如果他直接调用后端的审核接口,后端必须返回“权限不足”。前后端双校验,才能防住绕过。

3. Spring Boot后端:上传、校验、审核一条链

3.1 大文件上传与分片:别让“传大视频”拖垮服务

微视频的“微”只是相对长视频而言,但日常素材照样有几百兆甚至几个G。如果把整个文件直接交给Spring Boot处理,会遇到两个问题:请求体太大容易被内置限制拦截;上传过程中一旦网络抖动,整个文件就得重传。所以我采用分片上传方案,前后端协同实现。

前端把文件切成固定大小(默认5MB一块),每块上传时带上总文件标识fileId、当前块序号chunkIndex、总分片数totalChunks和文件md5(用于合并后校验)。后端收到分片后先把二进制写入临时目录,并将“该分片是否已上传”的状态记到Redis里,键名类似video:upload:{fileId}:chunk:{index}。当所有分片都上传完成,前端调用一个合并接口,后端把临时目录里的分片按顺序拼接成完整文件。

合并完成后,后端计算整个文件的md5,与Redis里记录的期望值比对,不一致就返回“文件损坏”并清理临时文件。这里有两个很关键的细节:

第一,每个分片上传接口必须幂等。也就是说,同一个分片上传两次,不能产生两份数据。处理方式很简单:在写入临时分片之前先检查Redis中是否存在对应分片完成的标记,如果已完成则直接返回成功。这样前端断点续传时重发已经传过的分片,不会造成数据重复。

第二,临时目录必须有清理机制。如果用户传了一半放弃,或者合并接口一直没被调用,临时目录会越堆越大。我写了一个定时任务,每隔一小时扫描临时目录,删除超过两小时未完成的分片文件。这个清理任务在生产环境非常重要,否则存储空间会悄悄被占满。

3.2 视频元数据抽取与合规校验:FFmpeg是不可或缺的后端依赖

视频不只是一个文件,时长、分辨率、编码格式、是否有声音,这些信息对管理来说很重要。文件上传完后,后端不能直接入库,需要先做元数据抽取。我调用FFmpeg的ffprobe工具,命令行大致是这样的:

ffprobe -v error -show_entries format=duration,size:stream=codec_type,codec_name,width,height -of json /tmp/video.mp4

解析返回的JSON,把duration、width、height、codec_name保存到video表。同时用FFmpeg从视频中间帧截取一张封面图:

ffmpeg -i /tmp/video.mp4 -ss 00:00:02 -vframes 1 -q:v 2 /tmp/cover.jpg

封面图本身也是一张图片文件,我会把封面和视频一起上传到对象存储。这一步看似不起眼,但对列表页的加载体验影响巨大——没有封面,列表就只有一排排播放按钮,显得非常粗糙。

合规校验同样放在这个阶段。上传者填写的标题、描述、标签,后端要做敏感词过滤;视频本身如果涉及未授权的素材,则需要管理员审核时人工判断。技术上能做的自动化校验包括:文件扩展名白名单、文件大小上限、时长上限、md5去重。如果发现md5已经存在于库中,直接提示“该视频已存在,请勿重复上传”,可以省下一份存储。

3.3 审核状态流转与角色权限:Spring Security + JWT的配置心得

身份认证我选了JWT方案,实现起来比较直白。登录成功后后端签发一个JWT,里面带上userId和role,前端把token存到localStorage,每次请求在请求头里带上。Spring Security这边我需要配置好三件事:哪些接口放行(比如登录、刷新token),哪些接口需要认证,哪些接口需要特定角色。

具体的规则大概长这样:

  • /api/auth/login:放行
  • /api/videos/upload、/api/videos/list、/api/videos/search:登录用户可访问
  • /api/review/**:仅ADMIN角色可访问
  • /api/admin/**:仅ADMIN角色可访问

这里我踩过一个坑,就是在Spring Security里配置了放行静态资源,结果把某些后端接口路径也写进了“忽略列表”,导致接口未登录也能访问。后来排查发现是路径匹配写法的问题,比如使用了“/api/**”排除,而没有细分。建议把接口按业务模块分组,然后用不同的前缀(如/api/open/、/api/app/、/api/admin/),这样在SecurityConfig里写规则时一目了然,不容易误放行。

审核操作的接口设计成POST /api/review/{videoId},请求体里携带action和reason。后端在审核前先判断当前视频状态是否合法——比如一个已下架的视频不能直接审核通过,必须先从下架变为“重新提交”状态。这种状态机的校验逻辑放在Service层,用枚举和常量替代散落的if-else,具体做法是创建一个VideoStatus枚举,每个枚举里定义允许的下一状态集合,由统一的changeStatus方法做校验和流转。这样后续加状态时只需要改枚举,不会漏掉某一条校验路径。

4. Vue前端:让视频管理像刷信息流一样顺手

4.1 视频库列表:懒加载、缩略图占位与搜索防抖

视频库页面是用户使用频率最高的页面。我的做法不是一次把所有数据请求回来,而是采用“滚动加载”的方式,接口参数带上page和size,每次返回20条。Vue这边通过IntersectionObserver监听列表底部的占位元素,进入视口后自动加载下一页。

但滚动加载有个体验问题:视频封面图是图片文件,一次加载太多也会卡。我的处理方案是:列表接口只返回视频的基本信息和封面地址,播放按钮点击后再加载真实播放地址。封面图地址在对象存储上都有CDN加速,图片加载很快;真正需要使用播放地址时,后端生成一个带签名和有效期的临时访问URL,避免直接暴露永久地址。

搜索功能一定要做防抖。最开始没有防抖,用户输入一个字就会发一次请求,短时间打出的关键字能触发十几甚至几十次请求,后端压力不小,前端列表也会频繁刷新。后来用了一个简单的方式,在输入框的change事件上做300毫秒的debounce,确保用户停下输入后才请求,实测对接口压力降低非常明显。

4.2 上传交互:进度条、并发控制与断点续传

上传页面算是整个前端里最复杂的一块。我用第三方分片库做底层,自己封装了上传组件。分片大小对齐后端要求的5MB,同时控制并发数,默认同时最多上传3个分片。如果同时把几十个分片一次性发出,后端合并时会因为网络连接过多出现文件块错乱的风险,控制并发反而更稳定。

进度条的计算公式是:

整体进度 = 已成功上传的分片数 / 总分片数 * 100

同时记录每个分片的上传状态,已经上传成功的分片在高亮区域打勾。如果中间断网,重新选择同一文件后会先请求后端接口查询哪些分片已存在,然后只上传缺失的部分,这就是断点续传的核心逻辑。这个功能看着不起眼,但对经常要传大文件的用户来说,体验差异非常大。

上传完成后,前端跳转到视频信息填写页面,用户需要补充标题、描述、分类和标签。这里的表单校验我做得比较严格:标题最少5个字,分类必选,标签至少填一个。毕竟后端审核时,信息残缺的视频很容易被驳回,一来二去用户体验更差。

4.3 数据看板:统计不是花架子,要能指导运营动作

管理后台的数据看板,我使用ECharts做了四个图表:上传趋势折线图、状态分布饼图、分类占比柱状图、近七天审核通过率趋势图。最有价值的其实是审核通过率趋势图——如果某一周通过率突然下降,多半是上传内容质量出现问题,可以快速反馈给团队。

这个看板的数据来源是后端单独的统计接口,比如/statistics/publish-trend?range=7,后端利用SQL中的date函数对created_at进行分组。需要注意时区问题,默认情况下MySQL使用系统时区,如果云服务器时区不对,统计出来的“今天”会偏差很大。我在数据库连接串里强制设置了serverTimezone=Asia/Shanghai,才把这个坑填掉。

5. 云平台部署:从本地跑通到线上稳定运行

5.1 服务划分:对象存储、CDN是比虚拟机更重要的“云资源”

很多人以为“上云”就是把Java代码打成Jar包,放到一台云服务器上跑。我的经验是,微视频管理系统上云的重点在于把存储和计算能力都交给云平台。视频、封面、临时分片目录本身涉及海量存储和高带宽,如果放在云服务器的系统盘里,很快就满,而且带宽出口会成为瓶颈。

部署时云服务器实例只承担应用运行和数据库,视频和封面全部上传到对象存储。我在对象存储的bucket里按目录做了区分:

  • /videos/原始视频
  • /covers/封面图片
  • /temp/未合并的分片临时文件(定期清理)

同时开启CDN加速,将视频和封面目录都接入CDN。这样用户访问列表页时,图片和视频都从CDN边缘节点加载,压力不会到源站。实测效果很明显,原本列表页加载要一两秒,加了CDN后普遍在200毫秒内。

5.2 Docker Compose编排:一套命令部署整个系统

为了方便部署和迁移,我用Docker Compose把后端、前端、MySQL、Redis编排到一起。前端Vue项目在CI阶段先构建出dist静态文件,然后打进Nginx镜像里,由Nginx直接托管静态资源,并将/api的请求反向代理到后端容器。后端Spring Boot项目打成Jar包,构建成Java镜像,在容器里跑。MySQL使用官方镜像,数据目录挂载到宿主机持久化。Redis负责缓存和上传分片状态。

docker-compose.yml的核心结构大致是:

version: "3.8" services: nginx: image: nginx ports: ["80:80"] volumes: - ./nginx.conf:/etc/nginx/conf.d/default.conf depends_on: [backend] backend: image: video-manager-backend environment: SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/video_db SPRING_REDIS_HOST: redis depends_on: [mysql, redis] mysql: image: mysql volumes: ["mysql-data:/var/lib/mysql"] redis: image: redis

这里有个关键配置:后端应用的数据库地址不能写localhost,因为在Docker网络里,localhost指向容器自身而不是宿主机;必须写服务名mysql。第一次部署时我用localhost连接数据库,结果启动直接报错,这个细节很容易踩。另一个经验是,Nginx的client_max_body_size一定要调大一点,我设置为2048m,否则上传大视频文件时Nginx会直接返回413错误。

5.3 监控、备份与成本控制:上云后的日常功课

线上系统跑起来之后,我最担心的是三件事:磁盘爆了、数据库数据丢了、服务不经意的挂了。为此做了几个基础措施:

  • 定时任务每月清理对象存储的临时文件目录,防止分片文件占满存储。
  • 每天凌晨对MySQL做全量备份,备份文件压缩后上传到对象存储另一个bucket,保留30天。
  • 使用云平台的监控告警功能,对CPU、内存、磁盘使用率和带宽设置阈值,超过80%就发送告警到手机。

成本控制方面,第一版为了省钱只买了一台云服务器跑应用和数据库,对象存储按量付费。高峰期如果同时有几十个人上传大文件,带宽压力会比较大,后续如果遇到这种情况,建议把上传运营商线路换成按流量计费的带宽包。钱要花在刀刃上,视频下载和播放尽量走CDN,源站带宽主要用于API接口。

6. 上线后遇到的几个大坑与排查思路

6.1 跨域、上传超时与内存溢出:一次典型的“三连击”

系统刚上线那周,我就被拉去做线上问题排查,第一个问题就是前端上传文件时,浏览器控制台报跨域。原以为是Nginx没加跨域头,检查发现Nginx其实已经配置了Access-Control-Allow-Origin,但还是报错。后来看了请求细节才知道,分片上传走的是XMLHttpRequest,而且请求带了自定义请求头(比如文件标识),属于非简单请求,浏览器会先发一个OPTIONS预检请求。而当时后端接口只处理了POST请求,没有显式返回预检成功状态,预检失败后续上传肯定通过不了。

解决方式是在Spring Boot全局配置里专门处理OPTIONS请求,返回成功状态并带上允许的请求头和方法列表。在SecurityConfig里也要把OPTIONS请求设置为放行,否则预检请求会被拦截器挡住。这一步排查花了小半天,但搞明白后,前后端分离项目的跨域问题其实就这一个关键知识点。

接着遇到的是上传超时。一个小伙伴传一个200多兆的短视频,前端进度条走到一半直接报网络错误,后端日志显示SocketException: Connection reset。检查发现有两层超时设置:Nginx的proxy_read_timeout默认60秒,后端Spring Boot的server.tomcat.connection-timeout默认也不长。而分片上传5MB需要的时间在弱网下可能超过60秒,连接就会被断开。我把Nginx的proxy_read_timeout、proxy_send_timeout都调大到300秒,后端相关超时配置也同步调整,问题解决。

内存溢出则是紧接着出现的。上传完成后合并分片时,我用了一个字节数组来读取所有分片,一个大文件合并时占用了几百兆内存,并发稍高就触发OutOfMemoryError。这里我做了两处优化:合并时不把所有分片一次性读进内存,而是用FileChannel分块复制;同时把JVM堆内存调大,并设置合理的Xms和Xmx。对于微视频系统,2G到4G堆内存就足够,但要防止代码写出“一次性加载整个文件”的坏味道。

6.2 数据库连接池被打满:明明量不大,为何连接耗尽

上线第三天,应用突然开始大面积超时,排查发现数据库连接池满了。当时第一反应是并发太高,但看监控发现请求量并不大。这是一个非常经典的“连接泄漏”问题,真正原因是某个Service方法里使用了数据库事务,但是方法内部调用了第三方对象存储的上传接口,对象存储上传耗时可能好几秒甚至十几秒。这个过程一直占着数据库连接不释放,并发一来,连接池就被耗尽。

解决方式非常直接:事务里不要做远程调用。我把文件上传操作拆到事务外面,先把视频元数据保存到库里,提交事务后再上传文件到对象存储;如果上传失败,再回调接口把元数据标记为“上传失败”,并允许用户重新上传。这比一开始就在事务里做长耗时操作要合理得多。这个经验后来我在很多项目里都用上了,处理外部依赖时,事务边界越短越好。

6.3 播放体验优化:首屏黑屏时间太长,原来是没做转码

所有视频都能正常上传和审核后,另一个体验问题浮现出来了:播放器点击播放后,黑屏时间往往长达两三秒,甚至有的视频播放一段时间后卡一下。一开始以为是CDN的锅,后来排查发现是视频格式兼容性造成的。有些手机拍摄的视频编码格式可能是H.265,编码器在部分浏览器里无法直接播放,浏览器需要请求能解码的格式,转码过程就会黑屏卡顿。

解决方案是在上传后增加一步“转码”流程:如果检测到视频编码不是H.264,或者分辨率高于1080p,就调用云平台自带的媒体转码服务,将视频转为H.264 + AAC并压缩到合适码率。转码完成后再把转码后的视频地址更新到对象存储和数据库。经过这步处理后,播放器默认播放统一格式的视频,黑屏时间大幅缩短。转码需要时间,所以我把状态机里加了一个“处理中”的中间状态,审核通过前先完成转码。

7. 项目复盘:如果再做一次,我会保留与优化的地方

整个项目做下来,我个人最得意的是数据模型和状态机设计。用整数状态 + 严格状态流转 + 审计日志,这套组合在任何资源管理类场景里都适用,后来我把同样的设计模式套用到图片管理、文档管理项目,都收到了不错的效果。但让我重新做一次,有些决策我会调整。

第一个调整是前端架构。第一版直接用Vue 3 + JavaScript开发,后期代码量增加以后,类型检查成了痛点。如果再给我一次机会,我会直接用TypeScript,尤其对于复杂的表单状态、视频状态流转这些场景,类型定义能帮我们提前发现很多低级错误。

第二个调整是上传模块的并发参数。默认并发3个分片显得过于保守,在内部网络环境良好的情况下其实可以提升到5甚至8个;并发数应该根据实际网络质量动态调整,而不是写死。后期我做的版本里加入了“网络测速”功能,根据当前上传速度动态调整并发数,上传大文件的速度提升明显。

第三个调整是增加“操作审计”到更细的粒度。第一版只记录了审核动作,但后续运营希望看到谁在什么时间修改了视频描述、谁把视频下架了,这些操作也值得记录。建议从一开始就设计一个通用的操作日志表,里面至少包含operate_type、operator_id、target_type、target_id、detail字段,之后任何关键业务操作都往里面写一行,成本很低但价值很大。

最后想说的是,微视频管理系统这类项目,真正的难点从来不是视频上传和播放这些技术组件,而是怎样把一个“看不全、理不清、管不住”的素材库,沉淀成一套有规范、有状态、有反馈的流程。Spring Boot、Vue和云平台只是工具,模型设计和业务理解才是系统能不能真正用起来的关键。如果你也在做类似系统,希望这篇文章能帮你少走一些弯路。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进