周一早上 9 点 15 分,市场部的需求单直接扔到我桌上:本周四下午两点,新品发布会直播。公司内部要能看,还要转发给一部分客户。前台能创建直播间,主播用 OBS 推流,观众打开网页就能看,最好带一个聊天室。开发排期?没有排期,只有三天。

当时手里的牌是这样的:后端一个主力在休年假,前端的版本迭代排到下个月,外包报价两周一档还只是出方案。要说完全没辙也不至于——我把 Cursor 里的代码模型切到 Grok Bot,心想既然人不够,那就让写代码的 AI 顶上。三天后这个系统真的上线了,市场部用了大概半小时完成推流配置,直播当天 200 多人同时在线观看,聊天室全程没崩。

这篇文章不准备推荐什么"神器",而是把这三天的决策过程、提示词写法和踩过的坑完整摊开。适合两类人:一类是接了紧急需求、需要一个可落地快速方案的开发者;另一类是刚开始用 AI 编程工具、想知道哪些活能交给它、哪些必须自己扛的人。看完你会得到一条可以复用的路径:怎么收敛需求、怎么选型、怎么把任务描述成 Grok Bot 能一次做对的形式。

1. 三天不是口号:先给 Grok Bot 画好边界

1.1 三天真的够吗?先看工作量分布

很多人一听到"三天做一个直播系统"第一反应是扯淡。这个反应没错,前提是"做一个直播系统"指从推流协议到 CDN 分发全部自研。但现实中的公司直播,尤其是对内和面向百来个客户的场景,根本不需要动那层最重的东西。

我这里的判断依据是:直播链路本质是"采集—推流—转分发—播放—互动"五段,每一段都有非常成熟的开源组件。OBS 负责采集和推流,SRS 负责接收 RTMP 流并转成 HTTP-FLV 或 HLS 分发给观众,页面用 flv.js 播放,聊天室用 WebSocket 搞定。真正需要我写的,是连接这些组件的胶水代码:直播间管理接口、推流鉴权、页面、聊天服务。

工作量拆开看就是这样的一张表:

任务 性质 谁来做
需求拆分、技术选型、协议确认 决策 人
项目脚手架、CRUD 接口、页面样式 重复性代码 Grok Bot
推流鉴权、WebSocket 业务逻辑 核心逻辑 人给契约 + Grok Bot 实现
SRS 配置、Nginx 反向代理、部署 运维配置 人主导,AI 辅助
安全审计、异常处理 兜底 人

说得直白一点:三天里真正占用我时间的不是写代码,而是搞清楚"这套系统到底需要什么"。直播系统 80% 的代码是标准件,这部分 Grok Bot 一天能顶我一周;剩下 20% 的协议边界和安全隐患,必须自己盯死。

1.2 Grok Bot 在这套方案里到底负责什么

在 Cursor 里接上 Grok Bot 之后,我发现它最擅长的不是"从零生成一个天才方案",而是"在给定约束下稳定输出代码"。这两者的区别决定了你怎么用它。

比如你说"帮我做一个直播网站",它会给你一套非常宏大但没法落地的架构,光依赖就能列二十个。但如果你说"项目在 ./live-platform,Next.js 13 App Router + TypeScript,数据库用 better-sqlite3,请在 src/app/api/rooms 下新增一个创建直播间的 POST 接口,入参用 zod 校验,返回格式按照项目里已有的响应结构",它基本能一次写对。

所以我的做法是:决策自己做,契约自己定,重复劳动交给 Grok Bot。每次让它动手前,我至少要说清楚四件事——用什么技术栈、在哪个目录、入参出参是什么、返回格式长什么样。它的角色更像一个手速极快的实习工程师,理解力不错,但需要你把需求写到它不会自由发挥的程度。

2. 第一天就定死的直播链路:技术选型不给 AI 留机会

2.1 需求收敛:把"直播"拆成一张能交付的表

第一天上午,我拉着市场部负责直播的同事坐下来,把"直播"这个东西拆成了具体问题。这一步非常关键,因为"直播"这个词在不同人脑里的想象完全不一样。产品经理想的可能是抖音式的弹幕大屏,客户想的是能扫码就看的网页,运维想的是一堆服务器。

我列了一张问题清单,让市场部逐条回答:

问题 市场部的期望 落地方案
同时几个直播间? 每次发布会一个,偶尔并行 先支持多房间,UI 只暴露单房间入口
观看人数规模? 内部 + 部分客户,预计 200 人 自建足够,不需要上 CDN
主播用什么推流? 同事用笔记本直播 OBS,统一配置文档
是否要看回放? 本次不需要 预留录制开关,不做点播页
聊天要不要审核? 能踢人就行 管理员后台提供禁言接口
是否需要登录? 不需要,拿链接就能看 匿名观看,聊天用昵称

这个问题清单的价值在于,它把"做一个直播系统"变成了"开六个明确的小需求"。更重要的是,它帮我挡住了很多 Grok Bot 容易自作主张的地方——比如它很可能在没被要求的情况下引入用户注册登录系统、加上支付功能、内置一套会员体系。需求越模糊,AI 的自由发挥空间就越大,返工概率也越高。

2.2 技术选型:为什么是 SRS + Node.js + WebSocket

确认需求之后,技术选型大概用了一个小时就敲定了,原则只有一个:全部选用我"闭着眼也能排错"的成熟方案,不给三天工期埋雷。

直播服务器用 SRS 6,而不是 Nginx-RTMP 或者自研分发。 原因很简单,SRS 支持 RTMP 推流、HTTP-FLV / HLS 播放、HTTP 回调鉴权,一个容器全搞定。Nginx-RTMP 是另一个选项,但它的 HTTP-FLV 支持和回调能力不如 SRS 顺手。至于自研分发,不在三天工期的讨论范围内。

应用层用 Next.js + TypeScript,数据库先用 SQLite。 选 Next.js 是因为它一个项目同时搞定服务端接口和前端页面,少维护一套代码库,Grok Bot 对这个框架的训练数据也足够充足。SQLite 在 200 人观看、几十个直播间的规模下性能绰绰有余,而且零运维。这里我特意不选 Prisma 这类 ORM,不是为了炫技,而是因为紧急项目里少一层抽象就少一层出错的可能。

播放协议用 HTTP-FLV,延迟低,flv.js 成熟。 HLS 的延迟普遍在 5 秒以上,发布会互动环节观众反馈会明显慢半拍;HTTP-FLV 内网环境下能做到 2 秒左右。代价是不支持原生 video 标签直接播放,但一个 flv.js 就解决了。

聊天用 Socket.IO,不用裸 WebSocket。 直播间的聊天需要断线重连和房间管理,Socket.IO 自带这些能力。裸 WebSocket 要自己处理心跳、重连、房间广播,三天工期里不值得。

2.3 半天搭出骨架:第一轮提示词进场

选型结束已经过了午饭时间,下午开始正式动手。Cursor 里我用了一个比较长的首轮提示词来搭建项目骨架,这个提示词后面几乎成了我所有 AI 辅助项目的模板:

我们的新项目在 ./live-platform,技术栈是 Next.js(App Router)+ TypeScript。
请帮我初始化项目,并遵守以下规则:
1. 只安装这些依赖:better-sqlite3、socket.io、socket.io-client、flv.js,不要安装其他无关包
2. 页面暂时用普通 CSS,不引入 Tailwind 或 UI 组件库
3. 目录结构按功能组织:src/app/api(接口)、src/components(前端组件)、src/lib(工具和服务)
4. 初始化完成后,用 300 字说明目录结构和你打算怎么组织代码

注意最后一条:让 AI 先解释方案再动手。这个习惯帮我避免了很多次"它自作主张改结构"的麻烦。Grok Bot 很快生成了项目骨架,我扫了一眼发现它额外装了一个 axios,直接用命令 npm uninstall axios 反手卸掉。这算是后面所有坑的一个预兆: AI 默认会引入它认为"标准"的东西,但这个"标准"不一定是你定义的。

骨架完成后,我再让它按我给的契约写了创建直播间的接口、SQLite 的建表脚本、SRS 的 Docker Compose 配置。第一天收工的时候,仓库已经能跑起来, POST /api/rooms 能往 SQLite 里插入一条直播间记录。当天晚上我用 OBS 往本地 SRS 推了一路测试流,发现播放页还没写,只能拿 VLC 验证一下,确认这条链路本身是通的。

3. 第二天的主战场:把需求翻译成提示词

3.1 提示词的第一原则:先给上下文,再给任务

第二天的效率高低,完全取决于提示词质量。我第一天晚上复盘时发现,凡是给 Grok Bot 足够上下文的请求,一次写对的概率在八成以上;凡是上来就甩一句话需求的,基本都要来回改三轮。

什么叫"足够的上下文"?我总结为四个要素:

  • 项目现状 :它必须知道代码在哪个目录、已经有什么模块、风格是怎样的
  • 技术约束 :用什么库、不引入什么、接口出入参的契约
  • 协议细节 :比如 SRS 回调的字段格式、返回码含义
  • 验收标准 :什么样的运行结果算"做完了"

举一个对比。直接说"帮我写一个推流鉴权的接口",Grok Bot 大概率会给你一个看起来完整但根本对不上 SRS 回调格式的实现。我实际用的提示词是:

项目在 ./live-platform,直播服务器 SRS 6 会调用我们的 HTTP 回调来做推流鉴权。
请求会 POST 到 /api/hooks/publish,请求体里有 stream 字段,格式类似:
stream123?e=1735600000&sign=ab12cd34
校验逻辑:
1. 用 env 里的 PUSH_SECRET 作为 HMAC-SHA256 密钥,对 /stream123?e=1735600000 签名,比较结果和 sign
2. e 是过期时间,小于当前时间戳则拒绝
3. 通过返回 JSON { code: 0 },拒绝返回 { code: 1 }
先读一下项目现有的路由写法,保持风格一致,用 zod 校验请求体。先告诉我你的实现思路,我确认后再写。

这段提示词里包含了一个很关键的技巧: 把协议文档翻译成 AI 能理解的具体格式 。SRS 的 on_publish 回调字段在不同版本表现有细微差别,我没有让 AI 去猜,而是直接把它的请求字段格式写在提示词里。这等于把排错成本前置,把"AI 猜错了然后我来查"变成了"我在提示词里就确认了"。

3.2 推流鉴权:让 AI 实现 SRS 的 on_publish 校验

推流鉴权是整个系统里最"脏"的一环,因为涉及 SRS 配置、HTTP 回调、签名算法三个层面配合。我的做法是让 Grok Bot 先写应用层的校验逻辑,再自己动手配 SRS 和看回调日志。

SRS 侧的配置大概长这样,重点在 http_hooks 这一段:

listen              1935;
max_connections     1000;
daemon              off;

rtmp_server {
    enabled         on;
    listen          1935;
}

http_server {
    enabled         on;
    listen          8080;
}

vhost __defaultVhost__ {
    http_hooks {
        enabled         on;
        on_publish      http://app:3000/api/hooks/publish;
        on_unpublish    http://app:3000/api/hooks/unpublish;
    }
}

当主播用 OBS 推流时,SRS 会先向 http://app:3000/api/hooks/publish 发起一次 POST,你的接口返回 { code: 0 } 才允许推流。这个机制本质上是把"谁有资格推流"的判断交还给你自己的业务服务。

我让 Grok Bot 生成了对应的 Next.js 接口,核心逻辑如下:

import crypto from "crypto";

export async function POST(req: Request) {
  const body = await req.json();
  const stream = String(body.stream || "");

  // stream 形如:stream123?e=1735600000&sign=ab12cd34
  const [streamKey, query = ""] = stream.split("?");
  const params = new URLSearchParams(query);
  const expire = Number(params.get("e"));
  const sign = params.get("sign");

  const expected = crypto
    .createHmac("sha256", process.env.PUSH_SECRET!)
    .update(`/${streamKey}?e=${expire}`)
    .digest("hex");

  const valid =
    sign === expected && expire > Math.floor(Date.now() / 1000);

  return Response.json({ code: valid ? 0 : 1 });
}

这一段生成的代码基本没有改。原因是我在提示词里把字段格式、签名算法、返回结构全部定义好了,它只需要做翻译。真正出问题的是另一处:SRS 实际回调时 stream 字段到底带不带 query 参数。我在本地用真实推流测了一次,发现带 stream?e=xxx&sign=xxx 连在一起,才确认写法没问题。 这类"协议文档和实际行为不一致"的问题,再怎么调提示词都没用,必须自己看日志。

3.3 播放页与聊天室:一次成型的两个模块

鉴权通过之后,推流链路就通了。接下来是观众侧的两个模块:播放页和聊天室。

播放页的核心是 flv.js,代码很短,我直接把需求丢给 Grok Bot,它一次就写对了:

"use client";

import { useEffect, useRef } from "react";
import flvjs from "flv.js";

export default function LivePlayer({ streamKey }: { streamKey: string }) {
  const videoRef = useRef<HTMLVideoElement>(null);

  useEffect(() => {
    if (!videoRef.current || !flvjs.isSupported()) return;

    const player = flvjs.createPlayer({
      type: "flv",
      isLive: true,
      url: `/live/${streamKey}.flv`,
    });

    player.attachMediaElement(videoRef.current);
    player.load();
    player.play();

    return () => player.destroy();
  }, [streamKey]);

  return <video ref={videoRef} controls autoPlay muted />;
}

注意这里的 URL 是 /live/${streamKey}.flv ,走的是同域路径,由 Nginx 代理到 SRS。这个设计不是随意定的,而是为了避免混合内容问题——如果页面是 HTTPS,直接请求 HTTP 的流地址会被浏览器拦截。同域反向代理同时解决跨域和混合内容两个问题,比在 SRS 上硬开 HTTPS 简单得多。

聊天室的提示词我也用了类似"契约先行"的方式,把 Socket.IO 的事件名和消息格式直接规定清楚:

聊天模块用 Socket.IO,事件约定:
- 客户端连接后发送 join_room,参数 roomId
- 发送消息:send_message,参数 { roomId, nickname, text }
- 服务端广播:new_message,参数 { nickname, text, time }
- 管理员踢人:调用 POST /api/rooms/{roomId}/ban,参数 userId
请实现服务端和页面两端的逻辑,消息存到 SQLite 的 messages 表。

这个模块大概是全场返工最少的部分,半天里面就写完了。Socket.IO 本身是个非常标准的库,Grok Bot 对它的 API 掌握得相当准确。

3.4 当 AI 给出的方案不对时,怎么把它拉回来

第二天下午我遇到一个典型场景:Grok Bot 在实现"管理员踢人"时,自作主张加了一个用户注册体系,理由是"没有用户 ID 无法记录谁被踢了"。它说得有道理,但完全超出这个 MVP 的范围。

我的处理方法不是直接否定它,而是把它的方案简化:既然观众匿名进入,聊天消息里本来就有昵称和 socket ID,那就直接用 socket ID 作为临时身份。管理员踢人时记录 socket ID,在服务端维护一个被禁名单,聊天室收到被禁 socket ID 的消息直接丢弃。

这一轮的提示词只需要追加一句:"不引入用户体系,使用 socket.id 作为身份标识。"问题就解决了。

这里想强调的是: AI 给出超范围方案时别急着重新生成,很多时候只需在对话里追加约束。 重新生成意味着丢掉之前所有上下文,成本更高。

4. 第三天动真格:部署、推流、延迟实测

4.1 Nginx 一层解决 HTTPS、CORS、WebSocket

第三天上午是部署日。因为直播服务涉及 SRS 和 Next.js 两个服务,浏览器只能访问 443/80 端口,所以 Nginx 在这一层做了三件很关键的事:

server {
    listen 443 ssl;
    server_name live.example.com;

    ssl_certificate     /etc/nginx/ssl/live.example.com.pem;
    ssl_certificate_key /etc/nginx/ssl/live.example.com.key;

    # Next.js 应用
    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    # HTTP-FLV 流,代理到 SRS
    location /live/ {
        proxy_pass http://127.0.0.1:8080/live/;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }

    # Socket.IO 需要 Upgrade 头
    location /socket.io/ {
        proxy_pass http://127.0.0.1:3000/socket.io/;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

第一件事,把所有流量统一收进 443 端口,统一使用 HTTPS,避免混合内容和明文推流的问题。第二件事, /live/ 路径代理到 SRS 的 HTTP-FLV 端口,观众拿到的播放地址和页面是同域的,浏览器不会因为跨域或混杂内容报错。第三件事,Socket.IO 的长连接必须显式带上 Upgrade 和 Connection: upgrade 头,否则 WebSocket 握手会失败。这个配置我一开始漏了 proxy_set_header Connection "" 那行(用于解决 flv.js 请求时浏览器复用连接导致的问题),直播画面一直黑屏,花了一个小时才定位到。

4.2 OBS 推流设置与第一次真实开播

部署完成后,最关键的一步:让市场部同事的 OBS 真正推上流。这里我不想让他们研究什么叫"服务器地址"和"串流密钥",而是直接给一份填空文档:

  • 服务器: rtmp://live.example.com/live/
  • 串流密钥:创建直播间后,后台点击"推流信息"复制,里面已经带好 streamKey?e=...&sign=... 签名参数
  • 视频设置:分辨率 1920x1080,帧率 30,码率 4500 Kbps
  • 关键帧间隔:2 秒(这是为了控制延迟,关键帧间隔太长会显著增加首帧等待)

推流密钥带签名这个设计有个隐藏好处:就算这份配置被截图发出去,密钥有过期时间,默认 24 小时后自动失效,安全性比裸的流名高一个量级。

第一次真实开播是在公司内网测试,我当时很紧张地盯着一块秒表。OBS 点下"开始推流"后大约 2 秒,播放页出现画面。那一刻心里的石头落地了。

4.3 延迟实测与参数微调

直播系统最怕的不是画面模糊,而是延迟。观众看到主播说话,声音画面不齐,或者互动环节问了个问题,屏幕里 3 秒后才回应,体验会非常糟糕。

我用一个土办法测延迟:手机打开秒表放在摄像头前,屏幕里拍秒表,然后对比实际时间和画面里的时间差。实测数据如下:

播放方式 首帧耗时 端到端延迟 备注
flv.js 播放 HTTP-FLV 约 0.8 秒 1.5~2.5 秒 最终采用
HLS 播放 2~4 秒 5~10 秒 备选,延迟偏高
http-flv 且关闭缓冲 约 0.5 秒 1~1.5 秒 有卡顿风险

2 秒左右的延迟对于发布会场景完全够用。我没有继续往下优化延迟,因为它会带来播放卡顿的风险,得不偿失。这里也提醒一句: 延迟和流畅度是反比关系,没有特殊需求别盲目追低延迟。 我们做的是发布会,不是直播打游戏。

延迟调完,整个系统就算成型了。市场部当天下午用这个系统做了两轮彩排,创建直播间、复制推流信息、OBS 开播、观众打开链接、聊天室互动,全流程走通。第三天晚上我写了半页操作说明,加上一份"如果 OBS 连不上,重启一下直播服务"的应急预案,然后下班。

5. 回看三天:AI 生成代码的坑与补法

5.1 AI 幻觉依赖:版本号和 API 不能全信

整个项目里 Grok Bot 生成的代码大概有一千多行,其中真正需要我动手改的大概三分之一。最大的坑集中在"AI 幻觉依赖"上——它会生成一些看起来合理、但实际项目里根本不存在的依赖或 API。

具体发生过两次。第一次是它生成 flv.js 播放代码时,用了 mpegts.js 的 API 风格,两家的初始化参数并不完全兼容,直接跑会报错。第二次是创建直播间接口时,它没跟我说一声就"顺手"引入一个 date-fns 来格式化时间,而项目里已经有现成的格式化工具函数。

这两次给了我一个很实在的教训: 每轮让 Grok Bot 完成任务后,第一件事是看 package.json 的 diff,而不是看业务代码。 如果它往项目里塞了新依赖,先问一句"为什么需要这个依赖,能不能用现有代码替代"。大多数时候答案是可以替代,依赖数量保持越少越好,三天工期里少一个依赖就少一个潜在的版本冲突。

5.2 安全底线不能交给 AI

Grok Bot 写的接口有个习惯:只管功能,不管权限。推流鉴权的接口它给我加了签名校验,但直播间管理接口它默认对所有人开放。这直接导致一个问题——任何人拿到后台地址后,都能创建直播间、看到推流密钥,这在发布会场景里等于把直播间的控制权交给了外部。

我的处理分三层:

  • 接口层 :管理类接口都加了一个简单的 X-Admin-Token 请求头校验,令牌配置在服务端环境变量里
  • 数据层 :推流签名在创建直播间时生成,观众端接口永远不返回完整的推流地址,只返回播放地址
  • 协议层 :SRS 的 on_publish 回调严格校验签名,即使密钥泄露,过期时间也能兜底

这一层是我花时间最多的部分,也是我认为 AI 辅助开发最需要人盯的地方。 AI 在"功能正确"和"权限安全"之间天然偏向前者,它的训练数据里绝大多数 demo 项目都没有鉴权。 你可以要求它补上鉴权,但前提是你自己先得知道哪些接口需要保护。

5.3 代码审查口诀:AI 写逻辑,人看边界

三天的经验最后沉淀成一句话: AI 负责写逻辑,人负责看边界。 这里说的边界包括入参边界、权限边界、异常边界和部署边界。

举个例子,Grok Bot 写的聊天室消息接口,最初没有限制消息长度,也没有过滤空白消息。如果直播聊天室混入一个脚本,往里面发几十 MB 的字符串,Socket.IO 的广播会直接把服务拖垮。我把这些边界条件列给 Grok Bot 让它补上,一分钟就改完了,但它不会主动想到这些。

所以我的审查清单很固定,每条都对着边界问一遍:

  • 这个接口的入参有没有上限?
  • 这个操作用户有没有权限?
  • 如果对方断开连接、重复请求、传错格式,服务会怎样?
  • 这个配置在部署环境里和本地有什么区别?

按这个清单过一轮,AI 生成的代码基本能安全落地。它不是不会写这些保护逻辑,而是你如果不问,它就当这个需求不存在。

5.4 这套流程复用到其他项目

项目结束后的第二个星期,我又被拉去帮一个兄弟部门做了个"活动报名 + 现场签到"的小系统,这次只用了两天。复用的大致是同一套方法:先拿需求清单把业务拆清楚,再定好技术约束,然后让 Grok Bot 按契约生成代码,最后人工审查边界。

方法本身不复杂,真正值钱的是一种思维方式: 和 AI 协作时,你的角色从"写代码的人"变成了"定契约的人"。 你越能把一句话需求翻译成带上下文、带约束、带验收标准的任务描述,AI 产出的质量就越高,你花在纠错上的时间就越少。这不是 Grok Bot 特有的用法,任何代码模型都适用。

最后再分享一个小技巧:这两天在 Cursor 里我养成了一个习惯,所有提示词都保留在项目根目录的一个 PROMPTS.md 文件里,每条记录后面标注"一次通过"还是"返工三次"。返工多次的提示词就是你的知识盲区,回头一分析,往往能发现自己之前没想清楚的业务规则。三天速建公司直播的这套东西,下一次再遇到类似紧急需求,我打开这个文件就能原地起飞,不用再从零开始。

Logo

火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。

更多推荐