移动端演示 http://8.146.211.120:8081/#/

管理端演示 http://8.146.211.120:8088/#/

项目整体介绍及演示

前言

商城解决「买」,IM 解决「聊」,直播则把两者拉到同一现场:主播边讲边卖、观众边看边互动。早期小红书教程系列通常停在内容与购物,直播是本章相对原教程全新加入的能力

本章基于腾讯云 TRTC,实现:

  • 视频直播 / 语音直播
  • 公屏弹幕、在线人数、点赞
  • 主播心跳与超时自动关播
  • 轻量直播货架(讲解商品浮卡)
  • 体验版时长限制 + 页面中部倒计时

线上演示首页

开播入口:视频 / 语音

直播间:画面 + 公屏 + 体验倒计时

直播业务闭环

直播结束页:时长 / 峰值 / 获赞

直播邀请可走私信卡片

管理端仪表盘(线上截图)

一、业务架构

直播挂在「笔记」之上:一条直播笔记同时承载内容属性与房间属性。

创作者中心 / 发布入口
        ↓
   创建直播笔记(liveStatus=直播中)
        ↓
   GET /member/note/live/credentials
   (sdkAppId / userId / userSig / roomId / maxMinutes)
        ↓
   主播推流 / 观众拉流 + WebSocket 公屏
        ↓
   主动结束 / 心跳超时 / 体验到期 → 结束页

为什么选 TRTC

  • 音视频质量稳定,Web / App 都有成熟 SDK
  • UserSig 必须后端签发,密钥不进前端
  • 与下一章「聊天室」共用同一套 TRTC 配置,运维成本低

二、进房凭证(核心代码)

MemberNoteLiveServiceImpl#getCredentials:校验直播中 → 签发 UserSig → 返回体验到期时间。

public AppMemberNoteLiveCredentialsRespVO getCredentials(Long loginUserId, Long noteId) {
    MemberNoteDO note = requireLiveNote(noteId);
    boolean anchor = Objects.equals(note.getUserId(), loginUserId);
    if (!Objects.equals(note.getLiveStatus(), MemberNoteLiveStatusEnum.LIVING.getStatus())) {
        throw exception(NOTE_LIVE_NOT_LIVING);
    }
    // 体验时长到期:先关播再拒绝进房
    if (endIfExperienceExpired(note)) {
        throw exception(NOTE_LIVE_NOT_LIVING);
    }

    long sdkAppId = requireSdkAppId();
    String secretKey = requireSecretKey();
    String trtcUserId = toTrtcUserId(loginUserId);
    String userSig = new TLSSigAPIv2(sdkAppId, secretKey)
            .genUserSig(trtcUserId, USER_SIG_EXPIRE_SECONDS);

    AppMemberNoteLiveCredentialsRespVO resp = new AppMemberNoteLiveCredentialsRespVO();
    resp.setSdkAppId(sdkAppId);
    resp.setUserId(trtcUserId);
    resp.setUserSig(userSig);
    resp.setRoomId(StrUtil.blankToDefault(note.getLiveRoomId(), buildRoomId(note.getId())));
    resp.setAnchor(anchor);
    // ...
    int maxMinutes = getLiveMaxMinutes();
    resp.setMaxMinutes(maxMinutes);
    if (maxMinutes > 0 && note.getLiveStartTime() != null) {
        resp.setLiveExpireTime(note.getLiveStartTime().plusMinutes(maxMinutes));
    }
    return resp;
}

主播心跳(杀进程后靠 Job 扫超时关播):

public void heartbeat(Long loginUserId, Long noteId) {
    MemberNoteDO note = requireLiveNote(noteId);
    if (!Objects.equals(note.getUserId(), loginUserId)) {
        throw exception(NOTE_LIVE_ONLY_OWNER);
    }
    touchOwnerHeartbeat(note.getId());
    presenceRedisDAO.add(BIZ_LIVE, String.valueOf(noteId), loginUserId);
}

对应 HTTP:

  • GET /member/note/live/credentials
  • POST /member/note/live/heartbeat
  • POST /member/note/live/end

三、公屏走业务 WebSocket(不走私信)

直播互动类型:

String MEMBER_NOTE_LIVE_SUBSCRIBE = "member_note_live_subscribe_type";
String MEMBER_NOTE_LIVE_CHAT = "member_note_live_chat_type";
String MEMBER_NOTE_LIVE_ONLINE = "member_note_live_online_type";
String MEMBER_NOTE_LIVE_ENDED = "member_note_live_ended_type";
String MEMBER_NOTE_LIVE_GOODS = "member_note_live_goods_type";
String MEMBER_NOTE_LIVE_BUY = "member_note_live_buy_type";

流程:发送聊天 → 敏感词过滤 → 写入 live_chat_json → 推房间内在线用户。在线人数用 Redis 统计,主播可禁言。


四、体验倒计时(前端)

配置:member.live.max-minutes(默认 30,0=不限制)。

export function createExperienceCountdown(options) {
  const expireAtMs = Number(options?.expireAtMs) || 0;
  const onTick = options?.onTick;
  const onExpire = options?.onExpire;
  let timer = null;

  const tick = () => {
    const remain = Math.max(0, Math.ceil((expireAtMs - Date.now()) / 1000));
    if (typeof onTick === 'function') onTick(remain, formatRemain(remain));
    if (remain <= 0) {
      clearInterval(timer);
      if (typeof onExpire === 'function') onExpire();
    }
  };

  return {
    start() {
      tick();
      timer = setInterval(tick, 1000);
    },
    stop() {
      if (timer) clearInterval(timer);
    },
  };
}

// 进房后
experienceCountdown = createExperienceCountdown({
  expireAtMs: parseTimeMs(data.liveExpireTime),
  onTick: (remain, text) => {
    state.remainSec = remain;
    state.countdownText = text;
  },
  onExpire: () => onForceEnded('体验时长已到,自动关闭。如需体验请联系管理员', true),
});
experienceCountdown.start();

页面中部浮层显示「体验剩余 MM:SS」,最后 1 分钟标红。


五、前端信息架构

区域内容
顶栏返回、主播胶囊、关注、在线人数
中部视频画面 / 语音头像舞台;体验倒计时浮层
左侧货架短轨(最多 3 个)
下部公屏滚动 + 输入框
底部操作静音、摄像头、翻转、耳返、扬声器、分享、更多
  • H5:TRTC Web SDK 直连
  • App:web-view 加载 hybrid/html/trtc-live.html

分享可复制链接,或发给互关好友(直播邀请卡片,详见(19))。


六、数据库与配置

member_note 直播字段(节选):

  • live_room_id / live_mode / live_status
  • live_start_time / live_end_time / live_peak_online
  • live_heartbeat_time / live_chat_json
  • live_goods_json / live_explain_spu_id
config_key含义
member.trtc.sdk-app-idTRTC SDKAppID
member.trtc.secret-keyTRTC 密钥(仅后端)
member.live.max-minutes单场最长分钟
member.live.chat-sensitive-words公屏敏感词

一键部署:sql/mysql/xhs-init.sql


关键代码位置

  • 后端:MemberNoteLiveServiceImpl.javaTLSSigAPIv2.javaMemberLiveHeartbeatTimeoutJob.java
  • 移动端:pages/note/live.vuepages/note/live-end.vue
  • Hybrid:hybrid/html/trtc-live.html
  • 倒计时:sheep/helper/experienceCountdown.js

总结

  1. UserSig 后端签发,前端零密钥
  2. 「媒体用 TRTC + 互动用 WebSocket」分层清晰
  3. 体验倒计时把运营策略产品化,而不是只做硬关房
下一章预告

直播是「一人对多人」。下一章实现 音视频聊天室:8 麦位、上麦治理、视频/语音双模式。


演示地址
移动端:http://8.146.211.120:8081/#/
管理端:http://8.146.211.120:8088/#/

代码地址
https://gitee.com/ddeatrr/springboot_vue_xhs

Logo

电商企业物流数字化转型必备!快递鸟 API 接口,72 小时快速完成物流系统集成。全流程实战1V1指导,营造开放的API技术生态圈。

更多推荐