简介:这是一套基于 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、操作前后的值、时间戳。出问题时这些日志就是后悔药,没有它们,排查一个超卖问题能花你一整天。希望帮到你。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

Logo

邀请您加入社区

更多推荐