feat(voice): 语音消息落库 + 转码接入(切片 0020)
- 迁移 0020_chat_message_voice:按 design/24 §10 与契约 §7.2 已登记的 voice 形状建列/索引
- 语音回合进编排并落库;voice 引用与统计如实回填(转码/转写没成功就如实标未成功,不假装)
- 复用既有 internal/capability/transcode:非 wav(微信 AMR 等)先转码再送 ASR;失败可分类且不丢消息
⚠️ 部分完成(如实登记):生产端到端未成立(本片只到落库与转码接入),且**未经验真机**。
This commit is contained in:
1 parent
120d926cf8
commit
ede00d9928
17 files changed
+2125
-31
No files matched your search
@@ -20,6 +20,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/capability/transcode"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/channel/adapter"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/channel/session"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/logger"
|
||||
@@ -157,6 +158,14 @@ type Options struct {
|
||||
// VoiceSTTTimeout 是一次 IM 入站语音转写的超时上限(≤0 时取默认 20s)。入站流水线
|
||||
// 会为这次调用举着一个并发槽,因此上游卡住**不能**拖死整条流水线。
|
||||
VoiceSTTTimeout time.Duration
|
||||
// VoiceTranscoder 是**入站语音副本的可播归一化**所用的转码能力(design/25 §7.1 第 ④ 步;
|
||||
// 组合根注入 `a.transcoder`,与工具面用的是同一个实例)。为 nil 时:
|
||||
// 可播格式的语音照旧落副本(不需要转码),**不可播格式**(QQ 的 AMR)走
|
||||
// 「不落副本 + 带分类的 WARN」的诚实路径 —— 绝不落一份播不了的字节并假装能播。
|
||||
VoiceTranscoder transcode.Transcoder
|
||||
// VoiceAudioStore 是入站语音副本的**落盘面**(design/24 §10.9-2)。nil 时按消息构造
|
||||
// 共享文档存储并在它实现了音频分支时直接用它(见 VoiceAudioStore 的说明)。
|
||||
VoiceAudioStore VoiceAudioStore
|
||||
// DocumentStore 是 IM **入站附件**的落盘配置(design/24 §3;切片 M1):
|
||||
// 目录、单文件上限与 `documents` 元数据写入面。它由组合根给出(桥接器不猜
|
||||
// 目录布局),并与 Web 上传共用同一个存储核心。UploadsDir 与 DataDir 都为
|
||||
@@ -174,6 +183,19 @@ type Options struct {
|
||||
EventStore events.Store
|
||||
}
|
||||
|
||||
// VoiceAudioStore 是入站语音**可播副本**的落盘面(design/24 §10.9-2 的「共享存储音频
|
||||
// 分支」)。它与 `documents.Store.Save` 同形:内容寻址 + 去重 + 元数据行,但走另一份
|
||||
// 裁决(音频白名单,见 design/24 §10.6)。
|
||||
//
|
||||
// 生产装配**可以留 nil**:那时桥接器按消息构造共享文档存储,并在它实现了本接口时直接
|
||||
// 用它(`documents.Store.SaveAudio`)—— 也就是说,只要存储侧补上那个方法,无需改组合根。
|
||||
// 注入面优先于上面那条发现路径,供测试与「用别的落盘实现」的部署方使用。
|
||||
//
|
||||
// 两者都没有 ⇒ 走「副本没落成」的诚实路径(带来源 WARN + 不打假 url),绝不假装落成了。
|
||||
type VoiceAudioStore interface {
|
||||
SaveAudio(ctx context.Context, origName string, data []byte) (documents.Ref, error)
|
||||
}
|
||||
|
||||
// Bridge 掌管渠道适配器生命周期与入站消息流水线。它
|
||||
// 可安全并发使用。
|
||||
type Bridge struct {
|
||||
@@ -248,10 +270,16 @@ type Bridge struct {
|
||||
// voiceSTT / voiceSTTTimeout / voiceCircuit 是入站语音转写面(切片 IM-ASR;
|
||||
// design/06 §1.1):注入点、独立短超时,以及按原因分类的**临时熔断**。
|
||||
// voiceLog 是带 `services/channelbridge/voice` 来源的子功能日志器。
|
||||
// voiceTranscoder 是入站语音**副本**的可播归一化所用的转码能力(切片 CODE-VOICE;
|
||||
// design/25 §7.1 第 ④ 步)—— 与转写面**彼此独立**:转写只认原始字节,转码只服务回放。
|
||||
voiceSTT VoiceSTT
|
||||
voiceSTTTimeout time.Duration
|
||||
voiceCircuit voiceBreaker
|
||||
voiceLog *slog.Logger
|
||||
voiceTranscoder transcode.Transcoder
|
||||
// voiceAudioStore 是**显式注入**的音频落盘面(切片 CODE-VOICE;design/24 §10.9-2)。
|
||||
// nil = 退回「共享文档存储的音频分支」发现路径(见 inboundVoiceStore)。
|
||||
voiceAudioStore VoiceAudioStore
|
||||
// proactiveCursor 是主动推送「无信号」兜底时的轮转游标
|
||||
// (channelbridge/proactive.go),使默认渠道不会总是
|
||||
// 白名单中的第一个主人渠道。由 mu 保护。
|
||||
@@ -333,6 +361,8 @@ func New(opts Options) (*Bridge, error) {
|
||||
voiceSTT: opts.VoiceSTT,
|
||||
voiceSTTTimeout: voiceSTTTimeout(opts.VoiceSTTTimeout),
|
||||
voiceLog: logger.WithSource(log, "services/channelbridge/voice"),
|
||||
voiceTranscoder: opts.VoiceTranscoder,
|
||||
voiceAudioStore: opts.VoiceAudioStore,
|
||||
cancel: func() {},
|
||||
sem: make(chan struct{}, maxConcurrent),
|
||||
}, nil
|
||||
|
||||
Reference in new issue
Block a user