
做毕设这几年凡是题目里带监控的十个有九个最后都做成了播放器登录进去拉一路视频流能看能截图然后就没有然后了。但你把这个题目完整读一遍——基于SpringBoot和Vue框架的分布式商业智能安防监控系统面向商业场景的微服务架构智能安防视频监管平台——它要的不是一个播放器而是一整套面向多门店、多区域、多角色的安防监管闭环。这套系统的核心可以概括成一句话把分散在不同门店、不同楼层的摄像头统一接入到一个平台里让总部能看、店长能管、安保能查、告警能联动。技术形态上是用微服务架构拆分业务域用SpringBoot做后端服务用Vue做前端界面视频流走HLS切片录像和图片存MinIO对象存储再用Redis扛分布式锁和缓存。整套链路走下来内容量非常大对毕设来说是个典型的业务看着不大、链路非常杂的项目。这篇文章我会按我实际做这类项目的顺序来写需求怎么拆、架构怎么选、视频链路怎么做、微服务落地有哪些坑、前端工程化要注意什么、最后是演示和答辩前一定要做哪些自测。内容全部是实操向的代码片段和配置都能直接往项目里套适合准备做这个题目的同学也适合想从单体转到微服务流媒体方向的开发者参考。1. 商业安防平台到底在解决什么问题从需求倒推系统功能很多同学拿到这个题目第一反应是先找个能播放视频的组件。这是典型的切入点错误。你仔细想想商业安防和家用监控、机房监控完全是三类东西。家用监控关注的是家里有没有人、有没有异常一般一部手机管两三个摄像头就够了。机房监控关注的是服务器区域的物理安全摄像头数量少、位置固定。而商业安防面对的是连锁门店、商场、写字楼、园区这类场景摄像头数量动辄几十上百路并且分散在不同地理位置。总部需要统一管理所有门店的设备区域经理只管自己片区门店店长只管自己店铺安保人员要能快速处理告警出了纠纷事件还要能翻录像取证。这就意味着系统本质上是多租户 多级权限 集中管控 流媒体 告警联动的综合业务平台而不是一个视频播放页。如果你按播放器去做功能撑不起来答辩的时候也经不住一句那告警怎么处理权限怎么隔离的追问。1.1 商业场景和家用/园区监控的本质区别先看一个真实的连锁门店场景。假设一家连锁便利店品牌有50家门店每家门店装4个摄像头分别对着收银台、货架、门口和仓库。以前查一个客流纠纷要从各个门店的DVR上导录像费时费力。装了这套平台之后总部运营人员登录系统可以看到全部门店的设备在线状态、今日告警数量点进某个门店就能直接看实时画面投诉来了直接把对应时段的录像下载出来。这中间有几个普通播放器给不了的硬需求多级组织架构。超管能看到全部区域经理只能看自己区域店长只能看自己门店。权限不能只是前端按钮隐藏后端接口也要做数据隔离。设备状态感知。摄像头离线了要能及时发现设备告警、录像丢失、硬盘异常都要有记录。录像的连续性和可回溯性。不是简单录下来就行要按时间轴回放精确到某一天某一小时某一分钟。告警闭环。视频分析发现夜间有人闯入要生成告警记录、推送给值守人员、处理结果要留痕。这些需求摆出来系统的功能地图就很清楚了。核心模块包括设备管理、通道管理、实时预览、电子地图、录像计划、告警规则、告警处理、用户权限、操作日志、系统配置。1.2 核心角色、业务闭环与功能地图我习惯先把角色和流程画出来再倒推功能这样后面写代码的时候不会东一榔头西一棒子。角色主要诉求数据范围典型操作超级管理员全局配置、分配权限全部机构与门店添加设备、配置角色、查看全局报表区域经理监管自己片区的门店指定区域内的门店巡店预览、查看区域告警、导出录像门店店长管理自己门店的日常本门店实时预览、简单回放、确认告警安保值守响应告警事件被分配的门店处理告警、标记误报、上报事件业务闭环是这样的用户在左侧机构树选择某个门店进入设备列表点击一根通道发起实时预览预览画面旁边显示设备在线状态如果有告警规则分析服务发现异常后落一条告警记录前端轮询或者通过WebSocket推送值守人员打开告警详情关联回放录像确认是真实事件还是误报然后填写处理结果。功能模块上我建议按这样分设备管理负责摄像头和录像机的注册、修改、删除通道管理负责一根摄像头通道的编码、名称、码流地址实时预览负责播放和抓图电子地图负责把设备标在门店位置图上录像计划负责哪天哪个时段录告警规则负责定义什么东西算告警告警处理负责闭环。另外用户权限、操作日志、字典参数这些属于公共模块。这样拆出来微服务划分也就顺理成章了。1.3 存储与带宽先把账算明白再动手视频项目最怕的就是跑到一半发现硬盘不够了。存储和带宽是安防系统的物理约束我建议动手前先算一笔粗账。录像存储量的估算公式很简单单路摄像头每天录像量(GB) ≈ 码率(Mbps) × 86400 ÷ 8 ÷ 1024按1Mbps码率计算一路摄像头全天录像大约10.5GB。如果是50路、每路1Mbps、保存7天就是50 × 10.5 × 7 3675GB约3.6TB。如果码率提到2Mbps直接翻倍到7.2TB。很多同学最后败就败在没算这个账代码写完了硬盘塞不下录像回放全是空洞。所以我在项目中设了两个策略。第一录像计划支持全天录像和告警录像两种模式告警录像只保存触发事件前后各30秒的视频存储量可以砍掉一大半。第二存储层直接用MinIO对象存储因为它是分布式的单机磁盘不够了可以加节点扩容。MinIO兼容S3协议在SpringBoot里集成也不复杂。带宽也要算。实时预览每路按2-4Mbps算如果有20个人同时看下行带宽至少要40-80Mbps这个在部署的时候心里要有数。2. 选型逻辑为什么是SpringBootVue以及为什么敢上微服务技术选型这部分答辩被问得最多。很多同学做完项目都会遇到一个灵魂拷问你这个系统单体就能实现为什么要用微服务答不好真的会很尴尬。我先说结论做这个题目微服务不是为了赶时髦而是题目本身把分布式、微服务架构写进了名字你必须有一个合理拆分服务、并在分布式环境下解决协作问题的完整方案。但我的建议很明确——先想清楚服务边界再决定技术栈。如果只是把所有业务塞进一个SpringBoot工程然后跟老师说我用的是SpringCloud那是过不了关的。真正的微服务项目每个服务都要能独立启动、独立演进、独立部署服务之间通过接口或消息通信。2.1 单体也能做但题目既然写了分布式就要有理由实话说从纯开发速度角度单体确实更快。一个SpringBoot工程mapper、service、controller全塞进去前端Vue一个工程调接口跑通功能不需要两周。但代价是耦合。设备接入、流媒体转码、告警分析、用户权限这四个域的负载特征完全不同设备接入是高频但轻量的通信流媒体是重I/O操作告警分析是CPU密集用户权限则是标准CRUD。把它们分开互相不影响才能各自扩容。而且微服务拆分的本质是业务边界清晰。设备管理和告警分析都需要读设备表但它们对数据的操作目的不同设备管理管增删改查告警分析只管读设备和写告警。拆开之后通过接口传递反而强制你规范了数据模型。答辩时我就这么讲单体能做出同样的功能但微服务让每个域的故障隔离、水平扩展、独立维护成为可能设备接入服务挂了不会拖垮视频回放这就是分布式架构在这个场景里的价值。2.2 服务边界怎么切一张表格说清楚我建议拆分四个核心服务加一个网关服务名职责关键技术点核心数据表device-service设备/通道/门店管理设备状态心跳SpringBoot、MyBatis-Plus、Redisdevice、channel、device_statusstream-service拉流、转码、切片、录像索引FFmpeg、MinIO、HLSstream_record、record_segmentalert-service告警规则、视频分析触发、告警闭环Redis分布式锁、MQalert_rule、alert_eventauth-service用户、角色、菜单、权限JWT、Spring Securityuser、role、menu、user_rolegateway统一入口、路由转发、鉴权过滤Spring Cloud Gateway-再加上通用依赖Nacos做服务注册与配置中心OpenFeign做服务间调用MySQL存业务数据Redis做缓存和分布式锁MinIO存录像和图片RabbitMQ做异步解耦。这里有个小提醒毕设项目千万不要把服务拆得太碎。有人会把文件上传单独拆一个file-service把短信通知单独拆一个notice-service这样带来的服务间调用复杂度和部署成本远超收益。我的原则是核心业务域4个左右就够了最多加一个报警消费服务。拆得太多演示的时候光是启动服务就能把你心态搞崩。2.3 技术栈版本别一上来就踩SpringBoot版本太高的坑热搜词里有一条非常扎心springboot版本太高。这是真实存在的坑。很多同学新建项目直接拉到SpringBoot 3.x结果发现Spring Cloud版本对不上各种starter找不到javax和jakarta包名迁移导致老教程全失效光解决问题就花了一周。做毕设我强烈建议用这套稳定组合组件推荐版本说明JDK1.8兼容性最好教程最多SpringBoot2.7.182.x系列的收官版本踩坑资料丰富Spring Cloud2021.0.9与SpringBoot 2.7配套Spring Cloud Alibaba2021.0.5.0提供Nacos集成Vue3.4.x Vite快速、生态好UI组件库Element Plus适合管理后台MinIO8.5.xJDK8兼容的客户端版本MySQL/Redis8.0 / 6.x稳定即可这段话写给所有准备动手的同学先查版本兼容矩阵再写代码。SpringBoot、SpringCloud、SpringCloudAlibaba三者的版本必须配套哪怕用2021.0.9这种老版本它比新版本跑不起来强一百倍。3. 视频链路全流程接入、存储、切片、播放这个项目最大的技术难点其实不在微服务而在视频链路。很多没做过流媒体的同学第一次被m3u8、HLS、RTSP这些词砸晕然后就卡住了。我用最简单的方式把这条链路讲明白。总链路是摄像头产生流 → FFmpeg拉流并切成m3u8切片 → 切片落到MinIO → 前端用播放器请求m3u8 → 播放器按列表顺序加载ts切片播放。一句话概括就是**把一整个视频流剪成一小段一小段让浏览器边拉边播**。3.1 摄像头侧的事实标准RTSP/RTMP/GB28181商业场景下的摄像头海康、大华、宇视这些品牌占了绝大多数。它们的取流协议往往同时支持RTSP、RTMP、GB28181。RTSP是最普遍的直接给一个取流地址就行。比如海康设备的RTSP地址一般长这样rtsp://admin:password192.168.1.64:554/Streaming/Channels/101这个地址的101表示通道1的主码流一般主码流分辨率高适合存储子码流102分辨率低适合预览。很多场景我会用子码流做实时预览墙用主码流做录像存储这样能省大量带宽。但问题来了很多学校的实验室根本没有真实摄像头。这时候不要慌有两个现成方案。第一用FFmpeg把本地视频文件推成RTSP流模拟一个摄像头先喂给采集端比如执行下面这个命令把视频文件循环推成RTMP流ffmpeg -re -stream_loop -1 -i test.mp4 -c copy -f flv rtmp://127.0.0.1:1935/live/cam001如果你的实验室有网上找的公共测试流地址也可以直接用。第二直接用开源流媒体服务比如ZLMediaKit它自带拉流代理和切片能力省去自己处理FFmpeg进程的麻烦。毕设的话我推荐先跑通FFmpeg推流 公开流地址这个低门槛方案功能验证没问题后再接真实摄像头。3.2 用FFmpeg把实时流转成m3u8HLS的玩法是一个m3u8文件其实是个播放列表里面记录了一串.ts切片文件的地址。播放器拿到m3u8之后按顺序请求ts文件边下载边播放。对这个项目来说切片环节的核心命令长这样ffmpeg -rtsp_transport tcp \ -i rtsp://admin:password192.168.1.64:554/Streaming/Channels/102 \ -c:v copy -c:a aac -f hls \ -hls_time 5 -hls_list_size 7 -hls_flags delete_segments \ /data/hls/device_001/live.m3u8可以看到我用了-hls_time 5把每段切片控制在5秒左右-hls_list_size 7表示播放列表只保留最近7个切片这样在实时预览模式下每个m3u8文件不会越积越大。-c:v copy是流复制模式不做转码CPU占用很低但前提是输入流的编码格式和切片要求匹配。如果摄像头输出H.264直接copy没问题如果是H.265很多浏览器播放器支持不好就需要用libx264转码命令里改成-c:v libx264 -preset veryfast。还有一个关键点切片必须能对齐到视频关键帧也就是GOP结构。你可以把视频流想象成一本只标了章节的书只有关键帧才有章节标记播放器从任意位置跳转时一定落到关键帧上。所以FFmpeg切片时要么用-c:v copy配合摄像头的GOP设置要么转码时强制-g 50之类的参数让关键帧间隔和切片时长匹配。否则容易出现播放时跳帧、拖动时间轴后画面花掉的问题。3.3 MinIO在SpringBoot里怎么接切片文件如果写在本地磁盘用几天就会碰到两个问题磁盘空间不够、没法横向扩容。MinIO就是干这个用的。它是开源的S3兼容对象存储单机部署简单Web控制台直观接口成熟。在SpringBoot里接入MinIO也非常顺手。首先在application.yml里配置MinIO连接信息minio: endpoint: http://127.0.0.1:9000 access-key: minioadmin secret-key: minioadmin bucket: security-video然后封装一个工具类至少要提供三个方法上传文件、生成预签名URL、删除文件。预签名URL非常重要因为MinIO的桶默认是私有的你不能直接把http://minio:9000/xxx丢给前端播放必须生成一个带签名和过期时间的临时访问地址。核心代码参考如下public String upload(String objectName, InputStream inputStream, String contentType) { try { minioClient.putObject(PutObjectArgs.builder() .bucket(bucket) .object(objectName) .contentType(contentType) .stream(inputStream, -1, 10485760) .build()); return objectName; } catch (Exception e) { throw new RuntimeException(上传对象失败, e); } } public String getPresignedUrl(String objectName, int expiresSeconds) { try { return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder() .method(Method.GET) .bucket(bucket) .object(objectName) .expiry(expiresSeconds) .build()); } catch (Exception e) { throw new RuntimeException(生成预签名URL失败, e); } }切片上传的设计有三种思路。最笨的是每一个ts切片都调一次上传接口这个小文件会非常多几万几十万个MinIO虽然能处理但日志和性能都很痛苦。更好一点的做法是先让FFmpeg把切片写到本地临时目录然后由定时任务把整个设备日期小时目录打包上传或者干脆只把m3u8索引和录像元数据登记到数据库真正的文件按对象路径规则直接落库。我最终用的是切片写本地 异步传MinIO DB登记对象路径的组合方案。FFmpeg写入本地目录的好处是即时预览不依赖网络异步任务再把历史切片传上去做长期存储。3.4 前端播放m3u8hls.js接入实战浏览器原生video标签是不能直接播放m3u8的只有Safari有原生HLS支持。所以第一步就要引入播放器库最常用的是hls.js它是JS实现的HLS客户端把m3u8列表拉下来再通过MediaSource Extensions喂给video元素。Vue 3项目里安装依赖就一行命令npm install hls.js然后在组件里写一个最简单的播放逻辑。核心代码如下template video refvideoRef classvideo-player controls muted playsinline/video /template script setup import { ref, onMounted, onBeforeUnmount } from vue import Hls from hls.js const videoRef ref(null) let hls null function playHls(url) { if (hls) { hls.destroy() hls null } if (Hls.isSupported()) { hls new Hls({ enableWorker: true }) hls.loadSource(url) hls.attachMedia(videoRef.value) } else if (videoRef.value.canPlayType(application/vnd.apple.mpegurl)) { videoRef.value.src url } } onMounted(() { playHls(/api/stream/live?deviceId1) }) onBeforeUnmount(() { if (hls) hls.destroy() }) /script这里面的坑很集中。第一m3u8和ts切片必须允许跨域访问MinIO桶的CORS配置要打开否则hls.js拉取ts会报跨域错误。第二浏览器自动播放策略限制带声音的视频默认不允许自动播放所以video要加muted属性或者让用户手动点击播放按钮。第三播放的是实时流时如果网络抖动画面会卡在一个位置不前进解决方法是监听hls.js的MANIFEST_PARSED和ERROR事件在异常时手动调用hls.startLoad()或者直接重新加载整个播放器实例。录像回放的时间轴也一样后端返回该时间段内的m3u8地址列表比如2025-06-01/14-15/record.m3u8指向一个小时的切片合集用户拖动时间轴到某个点前端把地址切换过去重新开始播放。这里注意录像回放的m3u8列表要和实时预览的区分开一个是长期切片索引一个是短时的直播播放列表别混着用。4. 微服务之后分布式协作才是真难点视频链路跑通之后项目算是完成了一大半。但既然叫微服务架构就一定会遇到单体时代没有的问题。我挑三个最典型的都是在答辩时会问到、实际开发时也必须处理的服务间调用的一致性、分布式锁、分布式缓存。4.1 服务间通信与数据一致性拆了服务之后一个业务动作常常要跨多个服务。举一个最典型的例子——一条告警的产生。视频分析服务检测到某个设备区域出现移动物体干的事情包括写入告警事件、更新设备状态、把事件推送给前端。如果这几个操作全在一个服务里用本地事务包一下就完事了。微服务模式下告警事件落在alert-service设备状态更新在device-service前端推送又要经过gateway。这时候就要考虑设备状态更新失败怎么办告警记录写进去了但推送没发出去怎么办我的方案是引入RabbitMQ做异步解耦。alert-service负责把告警记录落库然后发一条消息到交换机device-service消费消息后更新设备状态gateway通过WebSocket把告警推给前端。消息队列的好处是削峰、解耦、失败重试。如果消费失败消息会进死信队列后台可以定时补偿。如果非要在若干服务之间保证强一致完整方案是引入Seata通过AT模式在全局事务里注册各分支事务。但我不推荐毕设一上来就上Seata学习成本和调试成本都比较高。这里的核心思路是区分业务场景强一致场景可以用Seata弱一致场景就用MQ加最终一致性把一个链路上的总成功放宽为最终成功再配合定时任务做对账补偿。答辩时主动讲清楚这个取舍反而比无脑堆技术更有说服力。4.2 Redis分布式锁的两个典型场景分布式锁在这个项目里不是摆设至少有两个场景必须用。第一个场景是视频分析任务的控制。一个设备通道在同一时间只能有一个分析任务在处理。如果告警规则配置了并发触发或者前端重复发了开启指令就可能同时启动两个FFmpeg分析进程导致资源耗尽。这时候用Redis的SETNX实现互斥锁。第二个场景是定时任务的防重复执行。比如每5分钟检测一次设备离线状态这个任务在单体里没问题但部署了多个实例之后每个实例都会执行一遍产生重复告警。解决办法就是让定时任务先尝试获取分布式锁只有拿到锁的实例才执行。代码核心很简单用RedisTemplate做String lockKey alert:task:device_001; String token UUID.randomUUID().toString(); Boolean locked redisTemplate.opsForValue() .setIfAbsent(lockKey, token, Duration.ofSeconds(30)); if (Boolean.TRUE.equals(locked)) { try { // 执行任务 alertService.processDeviceTask(deviceId); } finally { String current redisTemplate.opsForValue().get(lockKey); if (token.equals(current)) { redisTemplate.delete(lockKey); } } }这里面有几个常识必须写进注释里锁要设置过期时间防止进程崩溃后死锁释放锁时要校验token防止误删了其他实例刚获取的锁如果要阻塞等待锁就用Redisson的tryLockRedisson还支持看门狗续期比手写setIfAbsent更省心。答辩时能说出网络抖动导致锁过期、两个任务同时执行怎么兜底这类问题就说明你真理解了分布式锁。4.3 分布式缓存与设备在线状态微服务模式下设备状态是高频读取的数据。设备列表页要显示几百个设备的在线状态如果每次都去查数据库压力很大。正确姿势是把在线状态缓存到Redis。设备心跳的处理流程是设备端定期上报心跳 → device-service收到心跳 → 更新Redis里的device:online:{id}字段值为1同时设置TTL为心跳周期的3倍。比如设备每分钟心跳一次TTL就设180秒。前端查设备状态时先读Redis如果key不存在说明设备一段时间没上报心跳认为离线。这个设计顺带解决了另一个问题原来的device-service挂了前端依然能从Redis拿到设备状态的快照不至于整个页面白屏。这本身就是分布式架构带来的容错优势。缓存一致性也要注意。当管理员修改了设备名称或IP时要先把更新写入MySQL然后删除或刷新Redis里的对应缓存字段。业务上我们一般用先更新数据库再删缓存的策略配合缓存的过期时间兜底避免出现更新失败导致缓存里长期挂着旧数据。5. 前端工程化与动态权限的那些坑很多同学后端写得挺顺一到前端就踩坑踩得怀疑人生。其实这套项目的复杂度后端和前端对半分。这部分我把最容易卡住人的几个点单独拿出来说都属于抄作业级别的经验。5.1 Vue环境配置与跨域代理Vue 3的工程化开发现在默认是Vite。先确保Node版本在18以上然后创建项目npm create vuelatest npm install依赖安装有些细节值得注意。项目里组件多的时候安装依赖尽量用npm install生成的package-lock.json锁定版本避免团队开发时别人装到不同版本的包导致运行结果不一致。开发环境下前端和后端是两个端口会有跨域问题。Vite里配置代理把以/api开头的请求转发到后端网关是最省事的方案// vite.config.js export default defineConfig({ server: { host: 0.0.0.0, port: 5173, proxy: { /api: { target: http://127.0.0.1:8080, changeOrigin: true, rewrite: (path) path.replace(/^\/api/, ) } } } })这样前端代码里请求地址写成/api/device/list后端网关监听8080端口只认/device/list一个rewrite就处理完了。开发的时候完全不用管跨域部署的时候把前端dist丢到Nginx里再配一层location /api反向代理到网关整个链路就通了。5.2 动态路由与菜单权限商业安防系统天然需要多角色权限。超级管理员能看到系统管理菜单区域经理看到机构管理和设备管理门店店长可能只有实时预览和告警处理。这种场景下前端路由不能写死必须按当前登录用户的权限动态生成。我的做法是登录成功后后端根据用户角色返回菜单树包含路由路径、组件名、菜单名称、图标这些信息。前端拿到菜单树后在路由配置里用router.addRoute()动态注册。这个方案里有个特别容易踩的坑直接addRoute注册的组件刷新页面后路由会丢失。因为刷新时Vue是新的内存实例动态注册的路由没了就会404。解决办法是在路由守卫里做一层权限路由缓存每次刷新先判断store里有没有菜单数据如果没有就重新请求权限接口然后addRoute同时把第一次进入的路径重新放行。核心伪代码是router.beforeEach(async (to, from, next) { if (!userStore.loaded) { const menuTree await authApi.getMenuTree() userStore.setMenus(menuTree) initDynamicRoutes(menuTree) next({ ...to, replace: true }) } else { next() } })另外按钮级权限别只用v-ifhasPermission(device:delete)控制显示后端接口同样要在PreAuthorize里校验权限防止有人绕过前端直接调接口。权限这块是答辩高频区能说出前端控制展示、后端控制访问两层逻辑比只会说我用JWT要加分得多。5.3 多路视频页面的性能与播放细节实时预览墙是视频监控系统的门面技术点却往往被忽视。一个9宫格页面如果一次创建9个video元素每个都挂一个hls.js实例在性能一般的电脑上会非常卡。优化思路有两个。第一懒加载。页面滚动到哪个区域再去加载对应视频流用IntersectionObserver监听。第二切换策略。预览墙上显示多个子码流点击某个画面放大时才切成主码流。前端可以维护一个当前激活的视频实例池只保留可见的几个播放器不可见的销毁掉避免内存里堆着几十个hls.js实例。还有一个容易忽略的问题时间显示。录像回放时后端返回的时间戳要统一用UTC存储前端用本地时区渲染否则会出现明明录了8点的视频界面上显示的是7点。我用的是后端统一返回时间戳前端通过dayjs转成本地字符串把所有格式化都收敛到一个工具函数里。6. 验收演示前的排坑清单这些坑我替你们踩过到这里系统功能该做的都做完了但离能上台演示、能通过答辩还有一段距离。这一节完全是我的实操经验总结每一条都是真金白银换来的。6.1 版本兼容是第一道坎开头说的版本问题这里再强调一次。SpringBoot 3.x和SpringCloud 3.x配套不是你想的那么轻松。我见过太多同学新建项目默认选最新的SpringBoot结果SpringCloud的starter一拉下来注册中心版本对不上启动直接报错而且网上的解决方案大部分是针对2.x的。如果实在想用SpringBoot 3至少得用Spring Cloud 2022.0.x和对应的Spring Cloud Alibaba 2022.0.0.0同时JDK必须是17。但我仍然建议毕设使用JDK8 SpringBoot 2.7这条线因为在答辩演示现场稳定性比版本新更重要。中间件方面MinIO客户端版本和JDK版本也有对应关系8.5.x版本是兼容JDK8的别随手拉最新的客户端到JDK8项目里一样会踩NoSuchMethodError。6.2 演示现场容易翻车的点演示环境绝对不是开发环境。以下几点每一条我都亲眼见过翻车现场摄像头地址问题。答辩教室的网络和你实验室的网络不是同一张网写死的RTSP地址大概率不通。强烈建议准备1-2段本地测试视频演示时用FFmpeg循环推流模拟摄像头。就算真实设备在现场也要保证有一键切换模拟流的开关。FFmpeg进程管理。拉流转码是通过SpringBoot启动的进程命令有时候进程崩了但没有正常回收前端看到的m3u8地址还在播放器却一直转圈。一定要在服务里加拉起进程 心跳监控 异常重启的逻辑最好做一个StreamService的独立接口可以主动查看每个设备的转码进程状态。端口和防火墙。MinIO的9000端口、管理台的9001端口、Nacos的8848端口、MySQL的3306、Redis的6379部署时要确保防火墙放行。机房或者教室的网络安全策略很可能限制了这些端口提前用telnet 127.0.0.1 9000测一遍最保险。启动脚本。不要手工一个个启服务。写一个start-all.sh按顺序启动数据库、Redis、MinIO、Nacos、各微服务再启动前端Nginx。演示前点一下脚本五分钟全部就绪显得专业得多。6.3 答辩追问怎么接答辩时老师未必会深入看代码但大概率会围绕题目里最显眼的几个词追问。提前想好答案比临场编要靠谱很多。为什么用微服务不要只说因为题目要求。回答要落到具体收益业务域边界清晰、独立扩容、故障隔离。可以举例子比如设备接入服务集群部署能扛大量并发注册而告警分析服务可以针对计算需求单独加机器。分布式事务怎么保证答MQ最终一致性 本地消息表 定时补偿说明这个方案在告警推送这种场景下能接受几秒延迟但保证了不丢数据。如果老师追问强一致场景补充Seata的适用条件。系统安全性怎么样分两层回答接口是JWT鉴权每个请求经过网关做token校验业务数据按用户角色做数据权限过滤后端有PreAuthorize不只是前端藏菜单。数据量大了怎么办录像走MinIO集群扩容节点即可业务数据库的告警记录按月分表历史表归档Redis只缓存热数据设备音频和录像索引都落MySQL。这些问题其实都不难难的是你心里有没有真的想清楚。平时写完代码闭上眼睛把这条链路在脑子里走一遍从登录、权限、设备、预览、告警到回放每一步调用哪个服务、查哪些表、经过哪些组件能讲出来答辩就稳了。最后再分享一个我个人的体会做这个题目的正确顺序永远不要是先搭微服务再写功能而是先把单体跑通把所有业务闭环做完然后再按服务边界把一个单体拆成微服务。第一次做的时候我直接上了Nacos加五个服务结果每个服务都在改表改字段联调成本巨大。第二次我把所有代码先写进一个工程分成module跑通后再拆成独立服务整个流程反而快得多。这个顺序看起来多了一次重构实际上帮你省掉了大量跨服务调试的时间。你们做的时候可以试一试。