VoiceSideStT RocketMQ 消息对接接口文档
- 文档版本:1.2
- 发布日期:2026-08-02
- 适用对象:下游消费端开发者(订阅 RocketMQ Topic、解析 JSON 用于智能填单/质检落库等业务方)
- 生产端实现:
com.pbxbusiness.sidestt.VoiceSideSttNotifyerRocketMQ - 消息体定义:
com.pbxbusiness.sidestt.json.SideSttMqMessage - 变更记录:
- v1.2 (2026-08-02):新增
voiceFile字段(当次语音识别对应的录音文件路径),并入可选字段统一推送策略 - v1.1 (2026-07-20):补充
markedText字段,明确可选字段统一推送策略 - v1.0 (2026-07-20):首版发布
- v1.2 (2026-08-02):新增
1. 概述
TelCRM 在通话过程中对人工坐席侧进行实时语音识别(SideSTT)。每当识别到一段完整的说话内容,TelCRM 会将结果组装为一条 JSON 消息,推送到 RocketMQ 的指定 Topic。
本文档面向消费端开发者,说明:
- 如何连接并订阅该 Topic;
- 消息的字段结构与取值规则;
- 如何解析 JSON;
- 联调与排错方式。
说明:TelCRM 在此链路中是生产端(Producer),对接方是消费端(Consumer)。
2. 接入前置条件
消费端开始对接前,需向 TelCRM 运维侧获取以下信息:
| 项目 | 说明 |
|---|---|
| NameServer 地址 | RocketMQ NameServer 列表,多个以 ; 分隔 |
| Topic 名称 | 由 TelCRM 服务端配置,消息发往的 Topic |
| Tag | 消息子主题/标签,用于消费端过滤 |
| Consumer Group | 消费端自定义,同一组内集群消费 |
| AccessKey / SecretKey | 仅当服务端开启 ACL 鉴权时需要,由运维分配 |
以上值由 TelCRM 运维侧分配,本文档不写死具体值。
3. 消费连接参数
| 参数 | 取值 / 建议 | 备注 |
|---|---|---|
| NameServer | 由运维提供 | 多个地址以 ; 分隔 |
| Topic | 由运维提供 | 例如业务约定的质检 Topic |
| Tag | 由运维提供 | 订阅时用 Topic:Tag 形式;接收全部 Tag 用 Topic:* |
| Consumer Group | 消费端自定义 | 建议与业务系统命名一致 |
| 消费模式 | CLUSTERING(集群) | 一条消息在同一 Group 内只被一个消费者消费;广播模式按需选择 |
| ACL 鉴权 | 视服务端配置 | 若开启,需在创建 Consumer 时传入 AclClientRPCHook |
| 消息体编码 | UTF-8 | Body 字节流必须按 UTF-8 解码,不要按 GBK 解码(否则中文乱码) |
4. 消息协议
4.1 Message 元信息
| 属性 | 说明 |
|---|---|
| Topic | 由服务端 rocket.mq.topic 配置 |
| Tag | 由服务端 rocket.mq.tags 配置 |
| Keys | 形如 key_1、key_2 ... 的自增序号,便于按 key 查询/去重 |
| Body | UTF-8 编码的 JSON 字符串 |
4.2 Body 固定字段
每条消息的 JSON Body 都包含以下固定字段:
| 字段 | 类型 | 必现 | 含义 |
|---|---|---|---|
uuid | string | 是 | 会话唯一标识(通话 ID),用于关联同一通通话的所有消息 |
call_hash | string | 是 | 主叫号码 |
leg | string | 是 | 通话腿。aleg = 主叫侧说话;bleg = 被叫侧说话 |
offer | string | 是 | 来源标识(对应服务端配置的 Topic 名,作为签名/来源标识) |
content | string | 是 | 本次识别出的语音文本内容;可为空字符串 |
date | string | 是 | 消息产生时间,格式 yyyy-MM-dd HH:mm:ss |
timestamp | string | 是 | 毫秒级时间戳(1970-01-01 至今的毫秒数) |
可选字段(仅在对应能力可用时出现,为空时不输出):
| 字段 | 类型 | 必现 | 含义 |
|---|---|---|---|
emotion | string | 否 | 情绪/态度标签,取值为枚举名(大写),见下方取值表。引擎无情绪能力或识别失败时不输出 |
speechRate | number(float) | 否 | 语速,单位字/秒。无法计算时不输出 |
markedText | string | 否 | 带情绪标记的文本,格式 <EMO>句文(如 <HAPPY>您好)。仅 funasr 多段解析产出时出现,其他引擎不输出。与 content 的关系:content 为纯文本,markedText 在每句前附带情绪标签 |
voiceFile | string | 否 | 当次语音识别对应的录音文件路径。仅当上游识别流程产出了非空录音路径时输出;为空时省略 |
emotion 取值表
实际输出为枚举名(大写字符串):
| 枚举值 | 含义 |
|---|---|
HAPPY | 高兴 |
SAD | 悲伤 |
ANGRY | 愤怒 |
NEUTRAL | 中性 |
FEARFUL | 恐惧 |
DISGUSTED | 厌恶 |
SURPRISED | 惊讶 |
UNKNOWN | 引擎返回了情绪标签但不在标准集内(识别失败兜底) |
可选字段推送策略
emotion / speechRate / markedText / voiceFile 四个字段遵循同一推送策略:
- 有值才推送:仅在对应能力可用且能计算/解析出非空值时才写入消息;
- 为空则省略:当值为
null或空字符串时,整个字段不会出现在 JSON 中(而非输出null); - 消费端处理原则:字段缺失 = 该条消息无对应数据(而非异常),按业务默认值处理即可。
注意:不同引擎能力不同。例如非 funasr 引擎通常既无
emotion/speechRate,也无markedText;voiceFile是否输出取决于上游识别流程是否产出了当次说话的录音路径,与引擎类型无强绑定。
4.3 动态扩展字段
除上述固定字段外,消息体可能额外携带若干由上游业务注入的扩展字段:
- 这些字段的 key 与 value 均为字符串,由上游业务逻辑决定;
- key 与 value 均非空时才会出现;
- 已排除
opt、extnum两个内部字段,不会出现在消息中; - 字段集不固定,本文档不枚举具体 key。
消费端建议:按「未知字段宽容解析」策略处理——反序列化时忽略未识别的字段,不要因出现文档未列出的字段而报错。
5. JSON 示例
示例 1:主叫侧说话,带情绪/语速/带标记文本/录音路径(funasr 引擎)
{
"uuid": "CALL-20260720-0001",
"call_hash": "13800138000",
"leg": "aleg",
"offer": "stt_quality_topic",
"content": "您好,请问需要什么帮助?",
"date": "2026-07-20 10:23:45",
"timestamp": "1779003825000",
"emotion": "NEUTRAL",
"speechRate": 3.8,
"markedText": "<NEUTRAL>您好,请问需要什么帮助?",
"voiceFile": "/record/20260720/CALL-20260720-0001/aleg/1779003825000.wav"
}
示例 2:被叫侧说话,无情绪/语速/带标记文本(非 funasr 引擎)
{
"uuid": "CALL-20260720-0001",
"call_hash": "13800138000",
"leg": "bleg",
"offer": "stt_quality_topic",
"content": "我想咨询一下套餐资费",
"date": "2026-07-20 10:23:48",
"timestamp": "1779003828000"
}
示例 3:含动态扩展字段
{
"uuid": "CALL-20260720-0002",
"call_hash": "13900139000",
"leg": "aleg",
"offer": "stt_quality_topic",
"content": "好的,马上为您办理。",
"date": "2026-07-20 10:25:10",
"timestamp": "1779003910000",
"agentId": "8001",
"deptCode": "sales"
}
上例中的
agentId、deptCode即动态扩展字段,仅作示例,实际 key 以上游业务注入为准。
6. 消费端示例代码(Java)
使用 RocketMQ 官方客户端 rocketmq-client,以集群模式订阅,含 ACL 鉴权与 JSON 解析:
import com.alibaba.fastjson.JSON; // 或 com.google.gson.Gson
import com.google.gson.Gson;
import org.apache.rocketmq.acl.common.AclClientRPCHook;
import org.apache.rocketmq.acl.common.SessionCredentials;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import org.apache.rocketmq.common.message.MessageExt;
public class SideSttConsumer {
public static void main(String[] args) throws Exception {
String namesrvAddr = "127.0.0.1:9876"; // 由运维提供
String topic = "stt_quality_topic"; // 由运维提供
String tag = "sidestt"; // 由运维提供
String consumerGroup = "cid_sidestt_consumer";
// 1. 构建 Consumer(若服务端未开启 ACL,去掉 AclClientRPCHook 参数即可)
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(
consumerGroup,
new AclClientRPCHook(new SessionCredentials(
"YOUR_ACCESS_KEY", // 由运维提供
"YOUR_SECRET_KEY"))); // 由运维提供
consumer.setNamesrvAddr(namesrvAddr);
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
consumer.subscribe(topic, tag); // 订阅指定 Tag;接收全部 Tag 用 "*"
// 2. JSON 解析器(生产端用 Gson 序列化,消费端用 Gson/fastjson 均可)
Gson gson = new Gson();
// 3. 注册监听
consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> {
for (MessageExt msg : msgs) {
// ★ Body 为 UTF-8 字节流,务必按 UTF-8 解码
String body = new String(msg.getBody(), "UTF-8");
// 反序列化到与生产端对称的 POJO(字段名一致即可)
SideSttMessage dto = gson.fromJson(body, SideSttMessage.class);
// 业务处理:落库 / 智能填单 / 实时大屏 ...
handle(dto);
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
});
consumer.start();
System.out.println("SideSTT Consumer started.");
}
private static void handle(SideSttMessage dto) {
// TODO: 落库/转发/填单等业务逻辑
}
/** 与生产端 SideSttMqMessage(com.pbxbusiness.sidestt.json)字段对称的 POJO。 */
static class SideSttMessage {
private String uuid;
private String call_hash; // Gson 默认按字段名映射;若改名 callHash 需加 @SerializedName("call_hash")
private String leg;
private String offer;
private String content;
private String date;
private String timestamp;
private String emotion; // 可空:缺失即无情绪数据
private Float speechRate; // 可空:缺失即无语速数据
private String markedText; // 可空:缺失即无带标记文本
private String voiceFile; // 可空:缺失即无当次录音路径
// getter / setter 省略...
}
}
Maven 依赖(版本按服务端 RocketMQ 对齐):
<dependency> <groupId>org.apache.rocketmq</groupId> <artifactId>rocketmq-client</artifactId> <version>4.9.x</version> </dependency> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.8.5</version> </dependency>
7. 字段稳定性与版本约定
| 类别 | 字段 | 稳定性 |
|---|---|---|
| 稳定契约 | uuid、call_hash、leg、offer、content、date、timestamp | 必现、语义不变,可放心依赖 |
| 可选契约 | emotion、speechRate、markedText、voiceFile | 条件出现;消费端须处理「字段缺失」情况 |
| 开放契约 | 动态扩展字段 | 不保证 key 集合稳定;消费端按未知字段宽容解析 |
演进原则:
- 字段只增不改不删:新增字段不会破坏现有反序列化(Gson/fastjson 默认忽略未知字段);
- 不会改变已有字段的语义与类型;
- 如遇必须的重大变更,将另行通知并提升文档主版本号。
8. 联调与 Mock 调试
8.1 Mock 模式(不发真实 MQ)
TelCRM 服务端配置项 rocket.mq.mock.send:
| 取值 | 行为 |
|---|---|
true | 不真实发送 MQ,仅在 TelCRM 服务端日志中打印消息体(便于对接方先核对格式) |
false(默认) | 正常发送到 RocketMQ |
联调初期可请运维临时置为 true,由 TelCRM 侧在日志中确认消息体格式正确后,再切回 false 实送。
8.2 查看消息体明细
TelCRM 打开 debug 日志后,VoiceSideSttNotifyerRocketMQ 会输出:
[MQ] makeMessage( gbk to utf-8 ):
{"uuid":"...","call_hash":"...","leg":"aleg",...}
可用于核对字段、顺序、编码是否符合预期。
9. 常见问题(FAQ)
Q1:收到消息是乱码?
A:确认消费端按 UTF-8 解码 Body(new String(msg.getBody(), "UTF-8"))。生产端 Body 是 UTF-8 字节流,若按 GBK 解码会乱码。
Q2:有的消息没有 emotion / speechRate / markedText / voiceFile 字段?
A:这四个是可选字段,遵循同一推送策略——仅在引擎具备对应能力且能计算/解析出非空值时输出。字段缺失即代表无该数据,按业务默认值处理,不要将其当作错误。例如非 funasr 引擎通常前三者都不输出;voiceFile 是否输出取决于上游识别流程是否产出了当次说话的录音路径。
Q2.1:content 和 markedText 有什么区别?
A:content 是纯识别文本,始终存在;markedText 是带情绪标签的文本(<EMO>句文 格式),仅 funasr 多段解析产出时出现。两者内容主体一致,markedText 额外携带逐句情绪标记。
Q3:同一通通话的多条消息如何关联?
A:用 uuid 字段。同一通通话内,主叫与被叫说话都会产出消息,uuid 相同,通过 leg 区分 aleg/bleg。
Q4:消息里出现了文档没列出的字段?
A:那是上游业务注入的动态扩展字段(字符串值)。消费端应宽容解析,忽略未识别字段。opt、extnum 不会出现。
Q5:消息顺序有保证吗?
A:同一 Queue 内消息按发送顺序投递。如对顺序敏感,消费端可按 timestamp 字段排序后处理。
Q6:如何判断一条消息对应哪一侧说话?
A:看 leg 字段:aleg = 主叫侧,bleg = 被叫侧。
附录:相关源码索引
| 文件 | 作用 |
|---|---|
PbxBusiness/src/com/pbxbusiness/sidestt/VoiceSideSttNotifyerRocketMQ.java | MQ 生产端,组装并发送消息 |
PbxBusiness/src/com/pbxbusiness/sidestt/json/SideSttMqMessage.java | 消息体 POJO 定义 |
PbxBusiness/src/com/pbxbusiness/sidestt/VoiceSideSttListenerImpl.java | SideSTT 监听实现,注入扩展字段 |
PbxBusiness/src/com/pbxbusiness/config/ConfigConstants.java | rocket.mq.* 配置项定义 |
