视频保存

This commit is contained in:
duoaohui
2026-05-19 13:51:34 +08:00
parent 326675a68d
commit 163b541214
6 changed files with 36 additions and 13 deletions

View File

@@ -2,11 +2,11 @@ package controller
import ( import (
"errors" "errors"
"os"
"strconv" "strconv"
"github.com/echochat/backend/app/meeting/model" "github.com/echochat/backend/app/meeting/model"
"github.com/echochat/backend/app/meeting/service" "github.com/echochat/backend/app/meeting/service"
"github.com/echochat/backend/config"
"github.com/echochat/backend/pkg/logs" "github.com/echochat/backend/pkg/logs"
"github.com/echochat/backend/pkg/utils" "github.com/echochat/backend/pkg/utils"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@@ -21,11 +21,17 @@ import (
// - GET /rooms/:code/recordings 列出录制host 或参与者) // - GET /rooms/:code/recordings 列出录制host 或参与者)
type MeetingRecordingController struct { type MeetingRecordingController struct {
recordingSvc *service.MeetingRecordingService recordingSvc *service.MeetingRecordingService
cfg *config.MeetingConfig
} }
// NewMeetingRecordingController Wire Provider // NewMeetingRecordingController Wire Provider
func NewMeetingRecordingController(svc *service.MeetingRecordingService) *MeetingRecordingController { //
return &MeetingRecordingController{recordingSvc: svc} // cfg 提供 InternalWebhookSecret用于校验 media-server 失败回调 X-Internal-Secret header
func NewMeetingRecordingController(
svc *service.MeetingRecordingService,
cfg *config.MeetingConfig,
) *MeetingRecordingController {
return &MeetingRecordingController{recordingSvc: svc, cfg: cfg}
} }
// handleErr 将录制相关领域错误映射成 HTTP 状态码 // handleErr 将录制相关领域错误映射成 HTTP 状态码
@@ -161,9 +167,11 @@ type mediaServerFailurePayload struct {
// - 始终返回 200webhook 语义:吞错避免 media-server 端反复重试) // - 始终返回 200webhook 语义:吞错避免 media-server 端反复重试)
func (ctl *MeetingRecordingController) MediaServerFailureWebhook(c *gin.Context) { func (ctl *MeetingRecordingController) MediaServerFailureWebhook(c *gin.Context) {
funcName := "controller.meeting_recording_controller.MediaServerFailureWebhook" funcName := "controller.meeting_recording_controller.MediaServerFailureWebhook"
secret := os.Getenv("INTERNAL_WEBHOOK_SECRET") // 优先 config.meeting.internal_webhook_secretyaml/viper
// 环境变量覆盖走 ECHOCHAT_MEETING_INTERNAL_WEBHOOK_SECRETLoadConfig 已启用 viper.AutomaticEnv
secret := ctl.cfg.InternalWebhookSecret
if secret == "" { if secret == "" {
logs.Warn(c.Request.Context(), funcName, "INTERNAL_WEBHOOK_SECRET 未配置,拒绝 webhook 请求") logs.Warn(c.Request.Context(), funcName, "meeting.internal_webhook_secret 未配置,拒绝 webhook 请求")
utils.ResponseForbidden(c, "internal webhook not configured") utils.ResponseForbidden(c, "internal webhook not configured")
return return
} }

View File

@@ -185,6 +185,12 @@ func provideLLMSourceConfig(cfg *config.Config) *config.LLMSourceConfig {
return &cfg.LLMSource return &cfg.LLMSource
} }
// provideMeetingConfig 从全局 Config 中提取 MeetingConfig
// Phase B会议模块需要 InternalWebhookSecret 用于校验 media-server 失败回调
func provideMeetingConfig(cfg *config.Config) *config.MeetingConfig {
return &cfg.Meeting
}
// InfraSet 基础设施层 Provider Set // InfraSet 基础设施层 Provider Set
var InfraSet = wire.NewSet( var InfraSet = wire.NewSet(
provideDBConfig, provideDBConfig,
@@ -193,6 +199,7 @@ var InfraSet = wire.NewSet(
provideMinioConfig, provideMinioConfig,
provideServerConfig, provideServerConfig,
provideLLMSourceConfig, provideLLMSourceConfig,
provideMeetingConfig,
db.NewPostgres, db.NewPostgres,
db.NewRedis, db.NewRedis,
db.NewLLMSourceDB, db.NewLLMSourceDB,

View File

@@ -119,7 +119,8 @@ func InitializeApp(cfg *config.Config) (*App, error) {
meetingManageService := service2.NewMeetingManageService(meetingManageDAO, meetingRecordingService) meetingManageService := service2.NewMeetingManageService(meetingManageDAO, meetingRecordingService)
meetingManageController := controller2.NewMeetingManageController(meetingManageService) meetingManageController := controller2.NewMeetingManageController(meetingManageService)
meetingController := controller7.NewMeetingController(meetingService) meetingController := controller7.NewMeetingController(meetingService)
meetingRecordingController := controller7.NewMeetingRecordingController(meetingRecordingService) meetingConfig := provideMeetingConfig(cfg)
meetingRecordingController := controller7.NewMeetingRecordingController(meetingRecordingService, meetingConfig)
meetingWSHandler := controller7.NewMeetingWSHandler(meetingSignalService, hub) meetingWSHandler := controller7.NewMeetingWSHandler(meetingSignalService, hub)
meetingCleanupTask := task2.NewMeetingCleanupTask(meetingLifecycleService, meetingRoomDAO, meetingChatDAO) meetingCleanupTask := task2.NewMeetingCleanupTask(meetingLifecycleService, meetingRoomDAO, meetingChatDAO)
llmSourceConfig := provideLLMSourceConfig(cfg) llmSourceConfig := provideLLMSourceConfig(cfg)

View File

@@ -86,7 +86,8 @@ llm_source:
meeting: meeting:
host_grace_seconds: 120 # 主持人掉线宽限期(秒),期间保留 host 身份 host_grace_seconds: 120 # 主持人掉线宽限期(秒),期间保留 host 身份
empty_room_ttl_seconds: 300 # 空房自动销毁 TTL期间有人加入可复活 empty_room_ttl_seconds: 300 # 空房自动销毁 TTL期间有人加入可复活
cleanup_interval_seconds: 30 # 兜底扫描周期(秒),定时任务检查过期 grace/empty_ttl key cleanup_interval_seconds: 60 # 兜底扫描周期(秒),定时任务检查过期 grace/empty_ttl key
stale_room_hours: 4 # 活跃超过此小时且无活跃成员视为 stale强制结束 stale_room_hours: 4 # 活跃超过此小时且无活跃成员视为 stale强制结束
# webhook 共享密钥:必须与 go-service 端 INTERNAL_WEBHOOK_SECRET 一致,否则 go-service 直接 403 # media-server 失败回调共享密钥:必须与 media-server 端 INTERNAL_WEBHOOK_SECRET 环境变量一致
INTERNAL_WEBHOOK_SECRET: internal-webhook-12xqs # 留空时 /internal/meeting/recordings/failure 全部 403默认安全
internal_webhook_secret: internal-webhook-12xqs

View File

@@ -79,3 +79,6 @@ meeting:
empty_room_ttl_seconds: 300 empty_room_ttl_seconds: 300
cleanup_interval_seconds: 30 cleanup_interval_seconds: 30
stale_room_hours: 4 stale_room_hours: 4
# media-server 失败回调共享密钥:必须与 docker-compose 中 media-server 服务的 INTERNAL_WEBHOOK_SECRET 一致
# 生产部署请通过环境变量 ECHOCHAT_MEETING_INTERNAL_WEBHOOK_SECRET 覆盖viper.AutomaticEnv 已开启)
internal_webhook_secret: internal-webhook-12xqs

View File

@@ -76,6 +76,9 @@ type MeetingConfig struct {
HostGraceSeconds int `mapstructure:"host_grace_seconds"` // host 掉线宽限期秒数,默认 120 HostGraceSeconds int `mapstructure:"host_grace_seconds"` // host 掉线宽限期秒数,默认 120
EmptyRoomTTLSeconds int `mapstructure:"empty_room_ttl_seconds"` // 空房自动销毁 TTL 秒数,默认 300 EmptyRoomTTLSeconds int `mapstructure:"empty_room_ttl_seconds"` // 空房自动销毁 TTL 秒数,默认 300
CleanupIntervalSeconds int `mapstructure:"cleanup_interval_seconds"` // 兜底扫描周期秒数,默认 30 CleanupIntervalSeconds int `mapstructure:"cleanup_interval_seconds"` // 兜底扫描周期秒数,默认 30
// Phase Bmedia-server 失败回调 webhook 共享密钥,必须与 media-server 端 INTERNAL_WEBHOOK_SECRET 一致
// 留空时 /internal/meeting/recordings/failure 全部直接 403默认安全
InternalWebhookSecret string `mapstructure:"internal_webhook_secret"`
StaleRoomHours int `mapstructure:"stale_room_hours"` // 活跃超过此小时且无成员视为 stale默认 4 StaleRoomHours int `mapstructure:"stale_room_hours"` // 活跃超过此小时且无成员视为 stale默认 4
} }