Spring Boot+Vue+小程序:视频点播直播社区抢购拍卖系统实战
简介:这是一套基于 Spring Boot 与 Vue.js 构建的小程序全栈项目源码,面向具备一定 Java 与前端基础、希望深入小程序音视频与电商互动场景的开发者。项目将视频点播、直播推流、社区交流、评论互动、限时抢购、拍卖竞价与短视频浏览等功能整合于一体,可作为毕业设计、课程实训或二次开发的参考方案。压缩包共约 2000 个文件,整体 8.49MB,其中 621 个 Java 文件承载后端业务逻辑,534 个 JavaScript 与 86 个 Vue 文件构成前端交互层,另有 415 个 XML、289 个 JSON 及 3 个 SQL 脚本用于配置与数据建模,并附带少量 Markdown 说明与部署脚本。已有 667 人学习下载。读者可借此梳理多模块项目的目录组织方式,理解点播与直播的接口划分、社区评论的数据流转以及抢购拍卖的并发处理思路,适合对照源码进行功能拆解与本地调试。
1. 从零搭一套小程序视频点播+直播+社区+抢购+拍卖+短视频系统,难在哪
真正做过这类项目的人都知道,难点从来不是"能不能播视频",而是六个业务模块塞进一个小程序里之后,后端接口、鉴权、订单、并发、缓存全搅在一起。标题里这套组合——Spring Boot + Vue + 小程序,覆盖视频点播、直播、社区交流、评论、抢购、拍卖、短视频——本质是一个"内容 + 交易 + 实时"三合一的中型系统。它适合两类人:一类是想拿一个完整项目练手全链路开发的学生或转行者,另一类是接私活、需要快速交付一套可运营小程序的独立开发者。这篇文章不讲空泛架构,只讲我实际落地时怎么分层、怎么选型、哪些参数必须调、哪些坑一定会踩。看完你应该能自己搭出一套跑得起来、扛得住抢购、直播不卡顿的骨架。
2. 技术选型与整体分层:为什么是 Spring Boot + Vue + 小程序
2.1 三端职责怎么切
这套系统天然是三端结构,先把职责划清楚,后面才不会乱。
- 小程序端 :用户交互层。视频播放、直播拉流、社区发帖、评论、下单抢购、出价拍卖全在这里。用原生小程序或 uniapp 都行,我一般用原生,因为视频和直播组件对原生支持最稳。
- Vue 管理后台 :运营层。内容审核、商品管理、拍卖场次配置、直播推流地址管理、订单处理。Vue3 + Element Plus 是当前最省事的组合。
- Spring Boot 服务端 :业务与数据层。所有鉴权、订单、库存、评论、消息都走这里,对外统一 REST 接口,直播和短视频的实时信令走 WebSocket。
选 Spring Boot 的理由很直接:生态全,Redis、MySQL、WebSocket、对象存储 SDK 都有成熟 starter,一个人也能维护。选 Vue 是因为后台表单和表格多,Vue 的响应式和组件化写管理端效率最高。小程序不用多说,视频点播和直播的官方组件开箱即用。
2.2 模块拆解与依赖关系
把标题里的功能拆成可独立开发的模块,是控制复杂度的关键:
| 模块 | 核心依赖 | 是否强实时 |
|---|---|---|
| 视频点播 | 对象存储 + CDN + 转码 | 否 |
| 直播 | 推流服务 + 拉流地址 + WebSocket | 是 |
| 社区交流 | MySQL + Redis 缓存 | 否 |
| 评论 | MySQL + 敏感词过滤 | 否 |
| 抢购 | Redis 预扣库存 + 消息队列 | 是 |
| 拍卖 | Redis 原子出价 + 定时结算 | 是 |
| 短视频 | 对象存储 + 封面截帧 | 否 |
依赖关系上,点播和短视频共用一套存储与转码;直播和 WebSocket 共用长连接通道;抢购和拍卖共用库存与订单模型。先把共用部分抽成基础服务,后面每个模块只写业务逻辑。
2.3 后端工程骨架与最小可运行接口
先建一个能跑起来的 Spring Boot 工程,把依赖和分层定下来。下面是核心 pom.xml 依赖片段:
<!-- Spring Boot 基础 + Web + WebSocket + Redis + MySQL -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 直播信令、评论推送走 WebSocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- 抢购库存预扣、拍卖出价原子操作 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
逻辑说明: spring-boot-starter-web 提供 REST 接口能力,所有模块的 HTTP 接口都靠它; spring-boot-starter-websocket 负责直播弹幕、评论实时推送和拍卖出价广播; spring-boot-starter-data-redis 是抢购和拍卖的命脉,库存和出价必须走 Redis 原子操作,不能直接压 MySQL。
参数说明:Redis 连接池建议 max-active 设 50 以上,抢购场景连接数不够会直接超时;MySQL 连接池 maximum-pool-size 设 20 起步,配合 Redis 削峰后数据库压力其实不大。
一个最小可运行的接口,用来验证工程通了:
@RestController
@RequestMapping("/api/video")
public class VideoController {
// 返回点播视频的播放地址,实际项目里地址由转码服务生成
@GetMapping("/play/{id}")
public Result<String> play(@PathVariable Long id) {
// 这里省略查库逻辑,真实场景要校验用户是否有权限观看
String playUrl = "https://your-cdn-domain/video/" + id + ".m3u8";
return Result.success(playUrl);
}
}
逻辑说明:这个接口演示了点播模块最核心的一步——把视频 ID 换成可播放的 m3u8 地址。真实项目里地址不是拼出来的,而是转码完成后写进数据库的字段,这里只是让你先跑通链路。
参数说明: {id} 是视频主键,返回的 playUrl 必须是 HTTPS,小程序对非 HTTPS 资源会直接拦截,这是新手最常翻车的地方。
2.4 前端与管理后台的初始化
Vue 后台用 Vite 起项目,装好路由和请求库:
# 创建 Vue3 管理后台
npm create vite@latest admin -- --template vue
cd admin
npm install
# 路由和状态管理
npm install vue-router pinia
# 请求库,统一拦截 token
npm install axios
逻辑说明:Vite 启动快,Vue3 组合式 API 写管理端逻辑清晰。 vue-router 管页面跳转, pinia 存登录态和用户信息, axios 统一加请求头 token 和错误处理。
参数说明: axios 的 baseURL 指向 Spring Boot 服务地址, timeout 建议设 10000 毫秒,直播相关接口可以单独放宽到 30000,因为拉流地址获取偶尔会慢。
小程序端在 app.json 里配好页面和权限,视频和直播组件需要声明:
{
"pages": ["pages/index/index", "pages/live/live", "pages/community/community"],
"permission": {
"scope.userLocation": { "desc": "用于同城社区内容推荐" }
},
"requiredPrivateInfos": []
}
逻辑说明: pages 列出所有页面路径,直播页和社区页必须在这里注册,否则跳转报错。 permission 按需申请,社区做同城推荐才需要位置权限,不需要就别加,否则审核会被问。
参数说明:小程序对视频组件有域名白名单要求,播放域名和直播拉流域名都要在后台配置,漏配一个就是黑屏。
3. 视频点播与短视频:转码、播放地址与 m3u8 落地
3.1 点播链路为什么必须走转码
用户上传一个 MP4,直接丢给小程序播放,大概率会遇到两个问题:一是文件太大加载慢,二是不同机型编码支持不一致。所以点播的标准链路是:上传到对象存储 → 触发转码 → 输出 m3u8(HLS)切片 → 播放地址写库。
m3u8 的好处是自适应码率,弱网自动降清晰度,这是短视频和点播体验的底线。转码可以用云厂商的媒体处理服务,也可以自建 FFmpeg 服务。自建的话核心命令是:
# 把上传的 mp4 转成 HLS,输出 index.m3u8 和切片
ffmpeg -i input.mp4 \
-c:v libx264 -c:a aac \
-hls_time 6 -hls_list_size 0 \
-hls_segment_filename "seg_%03d.ts" \
index.m3u8
逻辑说明: -c:v libx264 指定视频编码,兼容性最好; -c:a aac 音频编码,小程序全支持; -hls_time 6 表示每个切片 6 秒,切片太短请求多,太长首屏慢,6 秒是经验值; -hls_list_size 0 表示保留全部切片,点播场景需要完整列表。
参数说明:如果做短视频,切片可以缩到 2 到 4 秒,首屏更快;点播长视频保持 6 到 10 秒。转码是 CPU 密集型,别和业务服务挤一台机器,否则抢购高峰时转码会把 CPU 吃满。
3.2 小程序端播放 m3u8 的正确姿势
小程序播放 m3u8 用 video 组件,关键是 type 和地址格式:
// 小程序页面里播放点播视频
Page({
data: { playUrl: '' },
onLoad(options) {
// 从后端拿播放地址,不要在前端拼
wx.request({
url: 'https://your-api/api/video/play/' + options.id,
success: (res) => {
// 后端返回的必须是 https 的 m3u8 地址
this.setData({ playUrl: res.data.data });
}
});
}
});
<!-- wxml 里绑定播放地址 -->
<video
src="{{playUrl}}"
controls
autoplay="{{false}}"
show-center-play-btn
object-fit="contain"
/>
逻辑说明:播放地址一定从后端拿,前端拼地址等于把存储结构暴露出去,也不利于做权限控制。 autoplay 设 false,小程序自动播放有策略限制,用户没交互就播会被拦。
参数说明: object-fit="contain" 保证视频完整显示不裁切,短视频列表页可以用 cover 做封面填充。 show-center-play-btn 让用户明确知道能点,提升完播率。
3.3 短视频的封面与列表优化
短视频和点播的区别在于列表页要快速出封面。做法是转码时同步截一帧封面:
# 截取视频第 1 秒作为封面
ffmpeg -i input.mp4 -ss 00:00:01 -vframes 1 cover.jpg
逻辑说明: -ss 指定截帧时间点,取第 1 秒避免黑帧; -vframes 1 只输出一帧。封面图上传对象存储后,列表接口只返回封面和标题,用户点进去才加载 m3u8,这样列表滑动才流畅。
参数说明:封面建议压到 200KB 以内,宽度 750px 适配小程序。列表接口一定要分页,每页 10 到 20 条,一次拉太多小程序渲染会卡。
4. 直播与 WebSocket:拉流、弹幕和低延迟取舍
4.1 直播链路与拉流地址
直播和点播最大的区别是实时性。链路是:主播推流 → 直播服务 → 观众拉流。小程序端只负责拉流播放,推流一般在 PC 或专业设备上完成。
小程序直播用 live-player 组件,注意它需要特定类目权限,个人开发者可能申请不下来,这是很多人做到一半才发现的血泪经验。拉流地址格式通常是:
<!-- 小程序直播播放 -->
<live-player
src="{{liveUrl}}"
mode="live"
autoplay
bindstatechange="onStateChange"
/>
逻辑说明: mode="live" 表示直播模式, autoplay 直播场景可以自动播放。 bindstatechange 监听播放状态,网络断开时能及时提示用户重连。
参数说明: live-player 的 min-cache 和 max-cache 控制缓冲,默认值延迟偏高。追求低延迟可以把 min-cache 调到 1, max-cache 调到 3,但弱网会更容易卡,需要权衡。
4.2 WebSocket 承载弹幕与评论推送
直播的弹幕、社区评论的实时提醒、拍卖出价广播,都走 WebSocket。Spring Boot 里配置很简单:
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// 直播弹幕通道,按房间号区分
registry.addHandler(new DanmakuHandler(), "/ws/danmaku")
.setAllowedOrigins("*");
// 拍卖出价广播通道
registry.addHandler(new AuctionHandler(), "/ws/auction")
.setAllowedOrigins("*");
}
}
逻辑说明: /ws/danmaku 处理直播弹幕, /ws/auction 处理拍卖出价。按业务分通道,避免一个通道消息类型太多难维护。 setAllowedOrigins("*") 开发期放开,上线要收紧到具体域名。
参数说明:WebSocket 连接数受服务器文件描述符限制,单机几千连接没问题,上万要考虑加机器或用网关。心跳间隔建议 30 秒,太短浪费,太长容易被中间层断开。
4.3 直播数据统计与在线人数
直播运营离不开在线人数和互动数据。在线人数不要每次查询都遍历连接,用 Redis 的集合存房间成员:
// 用户进入直播间,加入 Redis 集合
public void joinRoom(Long roomId, Long userId) {
String key = "live:room:" + roomId + ":users";
redisTemplate.opsForSet().add(key, userId);
// 设置过期,防止异常退出后数据残留
redisTemplate.expire(key, 2, TimeUnit.HOURS);
}
// 获取在线人数
public Long onlineCount(Long roomId) {
String key = "live:room:" + roomId + ":users";
return redisTemplate.opsForSet().size(key);
}
逻辑说明:用 Redis Set 存用户 ID,天然去重,同一用户重复进入不会重复计数。设置 2 小时过期是后悔药,防止用户异常断线后集合里一直留着脏数据。
参数说明: expire 时间要大于最长直播时长,否则直播中途数据被清空,在线人数会突然归零。真实项目还要在用户断开连接时主动移除,双保险。
5. 社区、评论、抢购与拍卖:并发与数据一致性
5.1 社区与评论的存储设计
社区帖子、评论是典型的读多写少。表设计上,帖子和评论分表,评论用 parent_id 支持二级回复:
-- 社区帖子表
CREATE TABLE post (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
content TEXT,
images JSON,
like_count INT DEFAULT 0,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_user (user_id),
INDEX idx_create (create_time)
);
-- 评论表,parent_id 为 0 表示一级评论
CREATE TABLE comment (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
post_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
parent_id BIGINT DEFAULT 0,
content VARCHAR(500),
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_post (post_id)
);
逻辑说明: images 用 JSON 字段存图片数组,省一张关联表。 like_count 冗余在帖子上,避免每次查点赞数都 count。评论的 parent_id 支持盖楼,查询时按 post_id 一次拉出再在内存组装树。
参数说明: content 评论限制 500 字,帖子用 TEXT。索引只建必要的, idx_create 支撑按时间倒序的列表查询,这是社区首页最常用的。
5.2 抢购的 Redis 预扣库存
抢购的核心是别让请求直接打到数据库。用 Redis 预扣库存,扣成功再异步落库:
public boolean seckill(Long goodsId, Long userId) {
String stockKey = "seckill:stock:" + goodsId;
String userKey = "seckill:user:" + goodsId + ":" + userId;
// 一人一单,先判断是否已抢
Boolean hasBought = redisTemplate.opsForValue().setIfAbsent(userKey, "1", 1, TimeUnit.HOURS);
if (Boolean.FALSE.equals(hasBought)) {
return false; // 重复抢购
}
// Lua 脚本保证判断库存和扣减的原子性
Long remain = redisTemplate.execute(SECKILL_SCRIPT,
Collections.singletonList(stockKey));
if (remain != null && remain >= 0) {
// 扣减成功,发消息队列异步创建订单
mqProducer.send("seckill.order", new OrderMsg(goodsId, userId));
return true;
}
// 库存不足,回滚用户标记
redisTemplate.delete(userKey);
return false;
}
逻辑说明: setIfAbsent 实现一人一单,key 带过期时间防止长期占用。库存扣减必须用 Lua 脚本,因为"判断库存大于 0"和"扣减"两步分开会有并发问题,这是抢购最经典的坑。
参数说明:用户标记过期时间设 1 小时,覆盖抢购活动时长即可。消息队列用 RocketMQ 或 RabbitMQ 都行,消费端要幂等,防止重复创建订单。
5.3 拍卖的原子出价与定时结算
拍卖比抢购更复杂,因为出价要比较当前最高价。同样用 Redis 保证原子性:
public boolean bid(Long auctionId, Long userId, BigDecimal price) {
String priceKey = "auction:price:" + auctionId;
String current = redisTemplate.opsForValue().get(priceKey);
BigDecimal currentPrice = current == null ? BigDecimal.ZERO : new BigDecimal(current);
// 出价必须高于当前价
if (price.compareTo(currentPrice) <= 0) {
return false;
}
// 用 set 覆盖,配合分布式锁保证并发安全
redisTemplate.opsForValue().set(priceKey, price.toString());
// 记录出价人,结算时用
redisTemplate.opsForValue().set("auction:winner:" + auctionId, userId.toString());
return true;
}
逻辑说明:出价先读当前价再比较,这段在高并发下需要分布式锁或 Lua 脚本保护,否则两个用户同时出价会互相覆盖。这里演示逻辑,生产环境要把比较和写入合并成 Lua。
参数说明:拍卖结束用定时任务扫描到期场次,结算时读 auction:winner 生成订单。定时任务间隔 1 秒足够,太密浪费资源。
6. 避坑与排查:这套系统最容易翻车的 5 个地方
6.1 小程序视频黑屏,控制台无报错
现象:视频组件加载后一直黑屏,不报错也不播放。原因:播放域名没加进小程序后台的合法域名白名单,或者用了 HTTP 地址。解决:登录小程序后台,把点播和直播的 HTTPS 域名都加进 request 和 downloadFile 白名单,地址统一用 HTTPS。
6.2 抢购超卖,库存扣成负数
现象:活动结束后订单数大于库存数。原因:库存判断和扣减没做成原子操作,或者扣了 Redis 没扣数据库。解决:库存扣减必须用 Lua 脚本,Redis 扣减成功后异步落库,落库失败要补偿回滚,别只扣一边。
6.3 直播延迟越来越高
现象:直播看着看着延迟从几秒涨到几十秒。原因:播放器缓冲区越积越多, max-cache 设太大。解决:把 min-cache 和 max-cache 调小,或者定期重连拉流地址刷新缓冲。低延迟和流畅度是取舍,别指望两者都拉满。
6.4 WebSocket 连接频繁断开
现象:弹幕时有时无,用户反馈消息不实时。原因:心跳间隔太长被中间层断开,或者服务器连接数到上限。解决:心跳设 30 秒,服务端加连接数监控,接近上限就扩容。Nginx 反代 WebSocket 要配 proxy_read_timeout 调大。
6.5 评论和社区内容出现敏感词
现象:社区帖子或评论出现违规内容,导致小程序被警告。原因:发布时没做敏感词过滤。解决:发布接口接入敏感词库,命中直接拦截或转人工审核。别等出事再补,这是上线前的必做项。
7. 进阶技巧:用压测数据决定你的扩容阈值
这套系统上线前,我最看重的不是功能全不全,而是知道它在什么量级会崩。分享一个我常用的压测方法,用 JMeter 或 wrk 对抢购接口打流量,观察 Redis 和 MySQL 的指标。
# 用 wrk 对抢购接口压测,10 个线程 100 连接持续 30 秒
wrk -t10 -c100 -d30s --latency \
-s post.lua \
https://your-api/api/seckill/1
逻辑说明: -t10 是 10 个压测线程, -c100 是 100 个并发连接, -d30s 持续 30 秒, --latency 输出延迟分布。 post.lua 脚本里带上用户 token,模拟真实抢购。
参数说明:重点看 P99 延迟,如果超过 500 毫秒,说明 Redis 或消息队列成了瓶颈。这时候先加 Redis 连接池,再考虑消息队列分区扩容。数据库在抢购链路里应该是异步的,如果压测时 MySQL CPU 飙升,说明有请求绕过 Redis 直接打库了,回去查代码。
我自己的习惯是:每个模块上线前都跑一遍压测,把 QPS 和延迟记在文档里。这样线上出问题时,能立刻判断是流量超了还是代码有 bug,而不是靠猜。这套系统模块多,最怕的就是出问题时不知道从哪查起,压测数据就是你的黑匣子。
拍卖模块的结算定时任务也要压测,尤其是同时结束多个场次时,订单创建会不会把数据库打满。我一般把结算任务分批执行,每批 100 个场次,批间 sleep 100 毫秒,给数据库喘息时间。
最后说个习惯:所有涉及库存、金额、出价的操作,日志一定要打全,包括用户 ID、操作前后的值、时间戳。出问题时这些日志就是后悔药,没有它们,排查一个超卖问题能花你一整天。希望帮到你。
更多推荐
所有评论(0)