ST广播系统 — 开发说明文档(对外版)
API接口文档
最后更新: 2026-08-11 · 适用版本: v2.28.0 (Electron) / v2.0.33 (Web/Android 应用内)
本文档面向开发者,涵盖:
- 整体架构与平台差异
- HTTP API 接口完整清单(认证、设备、分组、任务、媒体、报警、播放日志、TTS、电台、计费等)
- 实时喊话 WebSocket 协议(含握手、音频帧格式、关闭流程)
- 客户端本地存储与平台检测
- 调试与常见问题
一、整体架构
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Web 浏览器 │ │ Electron 客户端 │ │ Android APK │
│ (Vue3 + Vite) │ │ (Vue3 + Vite + │ │ (Vue3 + Vite + │
│ │ │ Capacitor) │ │ Capacitor) │
└────────┬────────┘ └────────┬────────┘ └────────┬────────┘
│ │ │
HTTP / WS HTTP / WS HTTP / WS
(via /api 代理) (直连) (直连, Capacitor HTTP)
│ │ │
└───────────────────┼───────────────────┘
│
┌──────────▼──────────┐
│ 后端 Node.js 服务 │
│ 127.0.0.1:12080 │
│ (仓库不包含) │
└─────────────────────┘平台差异
| 能力 | Web | Electron | Android |
|---|---|---|---|
| HTTP API | ✅ | ✅ | ✅ |
| WebSocket 喊话 | ✅ | ✅ | ✅ |
| UDP 设备局域网扫描 | ❌ 浏览器无 UDP | ✅ Electron IPC | ❌ |
| 本地存储 | localStorage | localStorage + 主进程 JSON | Capacitor Preferences |
| 客户端登录(输入服务器地址) | ❌ | ✅ | ✅ |
| 单声道/立体声协议 | 单声道 | 单声道 | 单声道 |
| 麦克风采集 | Web Audio + ScriptProcessor | 同左(WebView 内核) | 同左 |
音频参数(3 个构建版本,3 端通用)
| 版本 | 采样率 | 码率 | 声道 |
|---|---|---|---|
web-16k-stereo-16k / 16K 16K |
16kHz | 16 kbps | 单声道 |
web-16k-stereo-32k / 16K 32K |
16kHz | 32 kbps | 单声道 |
web-44.1k-stereo-64k / 44.1K 64K |
44.1kHz | 64 kbps | 单声道 |
重要:协议必须单声道。服务器、原始 demo、uni-app2 老项目都是单声道。立体声编码服务器不认,会导致推空数据。
二、HTTP API
2.1 基础约定
- 后端地址:
http://127.0.0.1:12080(开发)/http://192.168.0.222:12080(仓库示例) - 方法:基本全为 POST
- 请求格式:
multipart/form-data(FormData) - 数据格式:UTF-8
- Token 双重传递(必须同时):
- Header:
Authorization: Bearer {token} - FormData:
token={token}
- Header:
- 自动注入:请求拦截器自动追加
id(用户ID)和token到 FormData - 基础 URL 切换(客户端模式):
- Web:Vite 代理
/user和/api→ 后端 - Electron/Android:先调用
localStorage.getItem('serverHost')作为基础 URL,空则走相对路径
- Web:Vite 代理
2.2 通用返回格式
// 通用
{ res: boolean, info?: string, msg?: string }
// 列表型(带 data 包装)
{ res: boolean, data: [...], list: [...], results: [...] }
// 登录型
{ res: true, data: { id, user_name, nickname, token, ... } }
res: true 成功;res: false 失败,查看 info 或 msg。
2.3 认证模块
| 接口 | 方法 | 备注 |
|---|---|---|
/user/fnkukei/gtoken |
POST | 登录(username + passwd) |
/user/checkin |
POST | 登录验证 |
/user/logout |
POST | 登出 |
/user/info |
GET | 当前用户信息 |
登录接口(/user/fnkukei/gtoken)特殊:返回 data 字段包含 id、token、user_name、nickname、mask、master_id、play_level、user_level 等,前端存到 Pinia useUserStore + localStorage。
2.4 设备管理
| 接口 | 方法 | 参数 | 用途 |
|---|---|---|---|
/user/listdev |
POST | — | 设备列表(字段:id, devname, device_seed, status, vol, voltage, debug, updatetime, changetime) |
/user/fnckukei/listmydevice |
POST | — | 我的设备列表(results 字段) |
/user/olist |
POST | — | 设备列表(list 字段) |
/user/deviceadd |
POST | name, sn | 添加设备(sn = 序列号|密钥) |
/user/deldevs |
POST | snid | 删除设备 |
/user/redevname |
POST | devid, newname | 重命名 |
/user/resn |
POST | devid, newsn | 修改 SN(32 位) |
/user/sndebug |
POST | sn, dsv (1=开/2=关) | 设备调试/提示音 |
/user/editvols |
POST | vol(1-63), snlist(` | `分隔) |
/user/fnkukei/batchsetvolume |
POST | vol, snlist | 批量调音量 |
关键返回字段(/user/fnckukei/listmydevice):
{
"id": 35,
"devname": "123123",
"device_name": "MTIzMTIz", // base64 编码(注意)
"device_seed": "b14a70bf...", // 设备 SN/MAC
"status": 1, // 1=在线 2=播放中 0=离线 -1=从未上线 -2=过期
"vol": 30,
"voltage": 85,
"debug": 0,
"updatetime": "...",
"changetime": "...", // ★ 最后更新时间(注意是 changetime)
"kind": 0,
"device_type": 0,
"iswarning": 0
}
device_name可能是 base64 编码(decodeURIComponent(escape(atob(s))解码),前端已封装decodeDeviceName。
2.5 分组管理
| 接口 | 方法 | 参数 | 用途 |
|---|---|---|---|
/user/addgname |
POST | gname, snlist | 添加分组 |
/user/listdetailgroups |
POST | — | 获取分组列表 |
/user/fnckukei/getonegdevs |
POST | gname | 获取分组设备 |
/user/editgdevs |
POST | gname, snlist | 编辑分组设备 |
/user/delgroup |
POST | gname | 删除分组 |
2.6 任务/定时播放
| 接口 | 方法 | 参数 | 用途 |
|---|---|---|---|
/user/tmpstartpl |
POST | taskary | 开始播放(任务数组) |
/user/tmpstoppl |
POST | taskid | 停止任务 |
/user/tmplistpl |
POST | — | 获取播放列表 |
/user/fnckukei/addziuser |
POST | ziusername, zipasswd, nickname, beizhu, level, mask | 添加子账号(mask 40 位) |
/user/fnckukei/listziuser |
POST | — | 子账号列表 |
/user/fnckukei/info_edit_listziuser |
POST | — | 子账号详情/编辑 |
/user/fnckukei/delziuser |
POST | id | 删除子账号 |
/user/fnckukei/changezimask |
POST | userid, zdex, zcase | 修改子账号权限位 |
/user/fnckukei/changezidevices |
POST | userid, snlist | 修改子账号可见设备 |
/user/fnckukei/newpasswdnick |
POST | id, newpass | 子账号改密码 |
mask 权限位(40 位字符串,每位 0/1):
- 位 0 = 可创建子账户
- 位 1 = 可添加/删除设备
- 位 2 = 可上传文件
2.7 媒体管理
| 接口 | 方法 | 参数 | 用途 |
|---|---|---|---|
/user/listfile |
POST | — | 文件列表 |
/user/fnckukei/listmydevice (复用) |
/ | / | 设备列表 |
/user/swapupload |
POST | file, sn | 交换/上传文件 |
| TTS 购买 | POST | — | 文字转语音购买支付(5次/2元) |
2.8 报警系统
| 接口 | 方法 | 参数 | 用途 |
|---|---|---|---|
/user/fnckukei/listmydevice |
POST | — | 报警设备列表 |
/user/fnkukei/listzrdevs |
POST | — | 报警注册设备列表 |
/warningsetauto.php |
POST | — | 自动报警设置 |
/Warningeableauto.php |
POST | enable (1/0) | 启用/禁用自动报警 |
/user/fnckukei/wfilelistrg |
POST | — | 报警文件分组列表 |
/user/fnckukei/wplantadd |
POST | — | 报警方案新增 |
/user/fnckukei/getwplantlist |
POST | — | 报警方案列表 |
/user/fnckukei/rmwplant |
POST | id | 删除报警方案 |
/user/fnckukei/wpdevicehistory |
POST | sn | 报警设备历史 |
2.9 播放日志
| 接口 | 方法 | 参数 | 用途 |
|---|---|---|---|
/user/fnckukei/queryLogs |
POST | index, count, findex/bindex | 分页查询日志 |
/user/fnckukei/clearLogs |
POST | — | 清空日志 |
2.10 计费资料
| 接口 | 方法 | 参数 | 用途 |
|---|---|---|---|
/user/fnckukei/getBilling |
POST | — | 获取计费资料 |
/user/fnckukei/saveBilling |
POST | 表单数据 | 保存计费资料 |
2.11 电台/TTS
| 接口 | 方法 | 参数 | 用途 |
|---|---|---|---|
/user/fnckukei/tts |
POST | text | 文字转语音 |
/user/fnckukei/radio |
POST | url | 电台广播 |
2.12 客户端登录(Electron/Android 特有)
Electron/Android 不和 Web 一样有 Vite 代理,需要用户手动输入服务器地址:
- 用户在
ClientLogin页输入serverHost(如http://192.168.0.222:12080) - 存入
localStorage.serverHost axiosbaseURL 优先读serverHost,空则走相对路径
三、实时喊话 WebSocket 协议
协议基于原始 demo(
WEBsocketDemo/4GYX/1.html+recordmp3.js),单声道 PCM → lame.js MP3 → base64 → WebSocket。
3.1 流程图
┌──────────┐ ┌──────────┐
│ 浏览器 │ │ 服务器 │
│ Web Audio│ │ WS 23020 │
└────┬─────┘ └────┬─────┘
│ 1. WS connect ws://server:23020 │
│─────────────────────────────────────────>│
│ 2. WS open (连接成功) │
│<─────────────────────────────────────────│
│ 3. 发送握手报文 (JSON, 长度前缀) │
│─────────────────────────────────────────>│
│ 4. 等待服务器返回 'WELL' (校验成功) │
│<─────────────────────────────────────────│
│ 5. 麦克风采集 → PCM → Worker → MP3 │
│ → base64 → ws.send() (循环) │
│─────────────────────────────────────────>│
│ 6. 停止推流 → ws.send('CLOSE') │
│─────────────────────────────────────────>│
│ 7. ws.close() │
│─────────────────────────────────────────>│3.2 握手协议(步骤 3)
格式:{json长度}\n{json内容}
示例:208\n{"Meport":6002,...}
{
"Meport": 6002, // 跨服端口号
"Umagic": 89686, // 快服句柄随机数(10-99999)
"userid": 6, // 用户 ID(也作为 "id" 字段)
"id": 6, // 同 userid
"token": "aece294b...", // 用户登录 token
"Umask": "84d3def4...", // 随机 32 字符串(快服填)
"plevel": 9, // 播放等级(1~9,9 最高)
"ulevel": 600, // 用户间等级(1~600),冲突时高优先级切低
"snlist": ["7f31ad..."], // 目标设备 SN 数组
"cmd": "PLAYLIST", // 固定值
"Meip": "127.0.0.1" // 节点服务器 IP
}
前端发送实现(useBroadcast.ts:183):
const headerStr = JSON.stringify(headerObj)
ws.send(headerStr.length + '\n' + headerStr)
3.3 服务器响应(步骤 4)
校验成功返回 WELL(4 字节文本),前端 ws.onmessage 监听:
if (data.trim() === 'WELL') {
// 握手成功,开始推流
} else {
// 握手失败
}
10 秒超时未收到 WELL 则判定失败。
3.4 音频帧协议(步骤 5)
3.4.1 音频参数
| 参数 | 16K 16K 版本 | 16K 32K 版本 | 44.1K 64K 版本 |
|---|---|---|---|
| 采样率 | 16000 Hz | 16000 Hz | 44100 Hz |
| 码率 | 16 kbps | 32 kbps | 64 kbps |
| 声道 | 1(单声道) | 1(单声道) | 1(单声道) |
| MP3 编码器 | Mp3Encoder(1, ...) |
Mp3Encoder(1, ...) |
Mp3Encoder(1, ...) |
3.4.2 采集 → 编码 → 发送管线
麦克风 → MediaStream
→ AudioContext (sampleRate = audioContext.sampleRate, 自适应)
→ MediaStreamSource
→ GainNode (采集音量 0~200)
→ ScriptProcessor(16384, 1, 1) ← 单声道输入 1 通道
→ Worker (lame.js Mp3Encoder(1, sampleRate, bitRate))
→ Int16Array PCM → 编码为 Uint8Array MP3 帧
→ base64 编码
→ ws.send(base64)3.4.3 Worker 协议
| 消息方向 | cmd | 数据 |
|---|---|---|
| 主 → Worker | init |
{ config: { inSampleRate, bitRate } } 初始化编码器 |
| 主 → Worker | open |
{ buf: Float32Array } 一段单声道 PCM |
| 主 → Worker | close |
刷编码器剩余数据 |
| Worker → 主 | init |
初始化完成 |
| Worker → 主 | chunk |
{ buf: Uint8Array[] } 编码好的 MP3 帧列表 |
| Worker → 主 | end |
flush 完成 |
前端实现(useBroadcast.ts:277):
scriptProcessor = audioContext.createScriptProcessor(16384, 1, 1)
scriptProcessor.onaudioprocess = (event) => {
const inputData = event.inputBuffer.getChannelData(0) // 单声道
worker.postMessage({ cmd: 'open', buf: inputData })
}
worker 实现(public/js/worker-realtime.js):
mp3Encoder = new lame.Mp3Encoder(1, inSampleRate, bitRate) // 单声道
// 每 1152 样本编码一个 MP3 帧,立即 postMessage
3.5 客户端音频图(关键)
Web Audio 是 destination 驱动渲染的,音频图必须连到 destination,否则 onaudioprocess 的 inputBuffer 拿到的全是静音:
Microphone → MediaStreamSource → Gain → ScriptProcessor
│
┌───────────────────────┴────────────────┐
│ ① ScriptProcessor → AnalyserNode (音量分析)
│ ② ScriptProcessor → monitorGain(0) → destination
│ (0 增益静音节点连接 destination,避免扬声器回声)⚠️ 常见错误:以为”连 destination 就会从扬声器播出来”。错!只要中间节点
gain=0,最终到 destination 听不到任何声音,但渲染触发了,onaudioprocess正常触发并获取真实音频。
3.6 关闭协议(步骤 6-7)
- 客户端
ws.send('CLOSE')(4 字节字符串) - 客户端
ws.close()断开连接
3.7 完整代码位置
| 文件 | 职责 |
|---|---|
src-new/composables/useBroadcast.ts |
采集 + 编码 + WebSocket 客户端(430 行) |
public/js/worker-realtime.js |
lame.js MP3 编码 worker(81 行) |
src-new/pages/Broadcast/index.vue |
UI 和调用入口 |
四、本地存储与平台检测
4.1 localStorage 键
| 键名 | 用途 | 值 |
|---|---|---|
token |
用户登录 token | string |
userInfo |
用户信息 JSON | JSON |
userId |
用户 ID | string |
theme |
主题模式 | 'diablo' / 'light' |
serverHost |
客户端模式服务器地址 | URL string |
serverPort |
客户端模式端口 | string |
device_display_order |
设备自定义排序 | JSON array |
device_view_mode |
设备列表视图模式 | 'list' / 'card' |
broadcast_selected_sn |
喊话选中的设备 SN | array |
4.2 平台检测
const isNativeApp = !!(window as any).Capacitor?.isNativePlatform?.()
const isElectron = !!(window as any).electronAPI?.isElectron
const isClient = isElectron || isNativeApp
- Web:都 false
- Electron:
isElectron = true(由 preload.js 通过contextBridge注入window.electronAPI) - Android (Capacitor):
isNativeApp = true
4.3 Capacitor 配置
// capacitor.config.ts
{
appId: 'com.st.broadcast',
appName: 'ST广播系统',
webDir: 'dist',
server: { android:Uri: 'http', cleartext: true },
plugins: { CapacitorHttp: { enabled: true } }
}
4.4 Electron preload 注入
electron/preload.js 通过 contextBridge.exposeInMainWorld('electronAPI', {...}) 暴露:
| 方法 | 用途 |
|---|---|
storeGet/storeSet/storeDelete |
主进程 JSON 文件存储 |
udpStartListener/udpStopListener/udpScan/udpConfigure/udpClear |
UDP 设备扫描 |
udpVolume/udpAmixerGet/udpAmixer |
音量/amixer 控制 |
五、调试与常见问题
5.1 开发调试
# 启动开发服务器(带热更新)
npm run dev
# 访问 http://localhost:8080
Vite 自动代理 /user 和 /api 到 http://127.0.0.1:12080。
5.2 一键打包三端
npm run build:web # Web 3 份 → release-main/
npm run build:electron # Electron 3 份 → release/
npm run build:android # Android 3 份 → release/
5.3 常见问题
Q: 喊话推流推出去是空数据/假数据
原因:协议必须是单声道,不是立体声。
检查:
useBroadcast.ts的createScriptProcessor(16384, 1, 1)—— 必须是(16384, 1, 1)worker-realtime.js的Mp3Encoder(1, ...)—— 必须是Mp3Encoder(1, ...)onaudioprocess用getChannelData(0)单声道
Q: 喊话声音采不到(inputBuffer 全 0)
原因:音频图没有连接到 destination,Web Audio 不渲染。
修复:用 0 增益 静音节点把图连到 destination:
const monitorGain = audioContext.createGain()
monitorGain.gain.value = 0
scriptProcessor.connect(monitorGain)
monitorGain.connect(audioContext.destination)
Q: 表格在手机上操作列被裁掉
修复:用 .scroll-x 类包表格,加 min-width:
<div class="scroll-x">
<table class="data-table" style="min-width: 900px">...</table>
</div>
Q: 接口拼写 fnckukei vs fnkukei
仓库里两种都有(uni-app2 用无 c 的 fnkukei)。建议跟后端确认统一为一种。
Q: Android 上扫不到 UDP 设备
正常,浏览器无 UDP socket,UDP 扫描只支持 Electron。
Q: Capacitor 后端请求发不出去
原因:相对路径 /user/swapupload 在 Capacitor 下 origin 是本地(capacitor://localhost),请求不会发到 serverHost。
修复:使用 getStaticBase() 函数从 localStorage.serverHost 取服务器地址前缀:
function getStaticBase() {
return (localStorage.getItem('serverHost') || '').replace(/\/+$/, '')
}
fetch(`${getStaticBase()}/user/swapupload`, { ... })
六、版本与构建
6.1 版本号管理
package.jsonversion:Electron 安装包命名(当前2.28.0,必须是合法 semver)src-new/version.json:应用内版本(Web/Android 使用,当前2.0.32,每次 vite 构建自动 +1)
6.2 3 个音频参数版本
3 个版本通过修改 src-new/composables/useBroadcast.ts 的:
new AudioContext({ sampleRate })—— 采样率bitRate—— 码率
实现自动化(scripts/build-*-all.mjs)。一键构建自动切换参数、构建、恢复源码。
6.3 平台差异总结
| 平台 | 入口 | 打包 |
|---|---|---|
| Web | index.html → src-new/main.ts |
npm run build → dist/ |
| Electron | electron/main.js + src-new/main.ts |
npm run electron:build:win → release/Setup.exe |
| Android | android/ + src-new/ |
npm run build + npx cap sync android + gradlew assembleDebug → release/*.apk |
七、相关文件清单
src-new/
├── main.ts # Web 入口
├── pages/
│ ├── Login/ # 管理员登录
│ ├── ClientLogin/ # 客户端登录(Electron/Android)
│ ├── Dashboard/ # 仪表盘
│ ├── DeviceManage/ # 设备管理
│ ├── GroupManage/ # 分组管理
│ ├── TaskManage/ # 定时任务
│ ├── MediaManage/ # 媒体管理
│ ├── Broadcast/ # 实时喊话(WS)
│ ├── Alarm/ # 报警系统
│ ├── Account/ # 账户管理
│ ├── Log/ # 播放日志
│ └── Admin/ # 后台管理(管理员)—— 不在本对外版文档范围内
├── composables/
│ └── useBroadcast.ts # 喊话核心(WS + 采集 + 编码)
├── stores/
│ ├── user.ts # 用户状态
│ ├── device.ts # 设备状态
│ └── app.ts # 应用状态(主题等)
├── api/
│ ├── request.ts # axios 实例 + 拦截器
│ ├── auth.ts # 认证 API
│ └── device.ts # 设备 API
└── assets/styles/
└── diablo-theme.css # 主题变量(玻璃 / 暗黑 / 亮色)
public/js/
├── worker-realtime.js # 喊话 MP3 编码 worker(lame.js)
└── lame.min.js # MP3 编码库
electron/
├── main.js # Electron 主进程
└── preload.js # 上下文桥(注入 electronAPI)
android/
├── app/src/main/AndroidManifest.xml
├── app/build.gradle # 应用包配置
└── app/src/main/assets/ # Capacitor 同步的 web 资源
scripts/
├── build-web-all.mjs # 一键构建 Web 三版本
├── build-electron-all.mjs # 一键构建 Electron 三版本
└── build-android-all.mjs # 一键构建 Android 三版本文档版本:v1.0(2026-08-11)
适用代码版本:main 分支(包含单声道推流修复、Android 移动端布局修复、一键构建脚本)
最后编辑:oxiaom 更新时间:2026-08-30 18:51