From 44c75c9c6420ecaa625f88c5534540413eb22673 Mon Sep 17 00:00:00 2001 From: duoaohui <928970622@qq.com> Date: Tue, 19 May 2026 18:08:52 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A7=86=E9=A2=91=E4=BF=9D=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/file/service/file_service.go | 10 +---- .../service/meeting_recording_service.go | 42 +++++++++++++------ backend/go-service/config/config.docker.yaml | 2 +- backend/go-service/pkg/storage/minio.go | 26 ++++++++++++ 4 files changed, 58 insertions(+), 22 deletions(-) diff --git a/backend/go-service/app/file/service/file_service.go b/backend/go-service/app/file/service/file_service.go index a2e0533..ef4d41e 100644 --- a/backend/go-service/app/file/service/file_service.go +++ b/backend/go-service/app/file/service/file_service.go @@ -19,6 +19,7 @@ import ( "github.com/disintegration/imaging" "github.com/echochat/backend/config" "github.com/echochat/backend/pkg/logs" + "github.com/echochat/backend/pkg/storage" "github.com/google/uuid" "github.com/minio/minio-go/v7" "go.uber.org/zap" @@ -259,12 +260,5 @@ func (s *FileService) UploadVoice(ctx context.Context, fileHeader *multipart.Fil // buildURL 根据 MinIO 配置构建文件访问 URL func (s *FileService) buildURL(objectName string) string { - if base := strings.TrimRight(strings.TrimSpace(s.minioCfg.PublicBaseURL), "/"); base != "" { - return fmt.Sprintf("%s/%s", base, objectName) - } - scheme := "http" - if s.minioCfg.UseSSL { - scheme = "https" - } - return fmt.Sprintf("%s://%s/%s/%s", scheme, s.minioCfg.Endpoint, s.minioCfg.Bucket, objectName) + return storage.BuildObjectURL(s.minioCfg, objectName) } diff --git a/backend/go-service/app/meeting/service/meeting_recording_service.go b/backend/go-service/app/meeting/service/meeting_recording_service.go index 28a8758..5833556 100644 --- a/backend/go-service/app/meeting/service/meeting_recording_service.go +++ b/backend/go-service/app/meeting/service/meeting_recording_service.go @@ -16,6 +16,7 @@ import ( "github.com/echochat/backend/app/meeting/model" "github.com/echochat/backend/config" "github.com/echochat/backend/pkg/logs" + "github.com/echochat/backend/pkg/storage" "github.com/minio/minio-go/v7" "github.com/redis/go-redis/v9" "go.uber.org/zap" @@ -503,14 +504,7 @@ func (s *MeetingRecordingService) uploadRecording(ctx context.Context, localPath } func (s *MeetingRecordingService) buildRecordingURL(objectKey string) string { - if base := strings.TrimRight(strings.TrimSpace(s.minioCfg.PublicBaseURL), "/"); base != "" { - return fmt.Sprintf("%s/%s", base, objectKey) - } - scheme := "http" - if s.minioCfg.UseSSL { - scheme = "https" - } - return fmt.Sprintf("%s://%s/%s/%s", scheme, s.minioCfg.Endpoint, s.minioCfg.Bucket, objectKey) + return storage.BuildObjectURL(s.minioCfg, objectKey) } // prepareUploadArtifact 决定最终上传到 MinIO 的产物 @@ -542,8 +536,30 @@ func (s *MeetingRecordingService) prepareUploadArtifact(ctx context.Context, web zap.Int64("recording_id", recordingID), zap.String("webm_path", webmPath), zap.Error(err)) return webmPath, ".webm", "video/webm", "" } + webmStat, webmStatErr := os.Stat(webmPath) + mp4Stat, mp4StatErr := os.Stat(mp4Path) + if webmStatErr == nil && mp4StatErr == nil { + if mp4Stat.Size() >= webmStat.Size() { + logs.Warn(ctx, funcName, "MP4 转码产物未变小,回退上传原始 webm", + zap.Int64("recording_id", recordingID), + zap.String("webm_path", webmPath), + zap.Int64("webm_size_bytes", webmStat.Size()), + zap.String("mp4_path", mp4Path), + zap.Int64("mp4_size_bytes", mp4Stat.Size())) + return webmPath, ".webm", "video/webm", mp4Path + } + logs.Info(ctx, funcName, "WebM→MP4 转码完成", + zap.Int64("recording_id", recordingID), + zap.String("mp4_path", mp4Path), + zap.Int64("webm_size_bytes", webmStat.Size()), + zap.Int64("mp4_size_bytes", mp4Stat.Size())) + return mp4Path, ".mp4", "video/mp4", mp4Path + } logs.Info(ctx, funcName, "WebM→MP4 转码完成", - zap.Int64("recording_id", recordingID), zap.String("mp4_path", mp4Path)) + zap.Int64("recording_id", recordingID), + zap.String("mp4_path", mp4Path), + zap.NamedError("webm_stat_error", webmStatErr), + zap.NamedError("mp4_stat_error", mp4StatErr)) return mp4Path, ".mp4", "video/mp4", mp4Path } @@ -555,8 +571,8 @@ func (s *MeetingRecordingService) prepareUploadArtifact(ctx context.Context, web // - 转码产物不存在或大小为 0 // // 参数选型: -// - -c:v libx264 -preset veryfast -crf 23:常见 1080p 短会议清晰度/CPU 折中 -// - -c:a aac -b:a 128k:覆盖绝大多数浏览器原生播放 +// - -c:v libx264 -preset medium -crf 30:压缩优先,避免 WebM 转 MP4 后体积变大 +// - -c:a aac -b:a 64k:会议语音场景的体积/清晰度折中 // - -movflags +faststart:moov 前置,HTTP Range 即点即播 // - -y:覆盖目标文件,避免重复 stop 时 EEXIST func (s *MeetingRecordingService) transcodeWebmToMp4(ctx context.Context, webmPath string) (string, error) { @@ -587,8 +603,8 @@ func (s *MeetingRecordingService) transcodeWebmToMp4(ctx context.Context, webmPa cmd := exec.CommandContext(tctx, ffmpegPath, "-y", "-i", webmPath, - "-c:v", "libx264", "-preset", "veryfast", "-crf", "23", - "-c:a", "aac", "-b:a", "128k", + "-c:v", "libx264", "-preset", "medium", "-crf", "30", + "-c:a", "aac", "-b:a", "64k", "-movflags", "+faststart", mp4Path, ) diff --git a/backend/go-service/config/config.docker.yaml b/backend/go-service/config/config.docker.yaml index 0294740..6df85b9 100644 --- a/backend/go-service/config/config.docker.yaml +++ b/backend/go-service/config/config.docker.yaml @@ -44,7 +44,7 @@ log: minio: endpoint: "minio:9000" access_key: "echochat" - secret_key: "echochat123456" + secret_key: "echochat_2026-1s32dswW@#" bucket: "echochat" use_ssl: false public_base_url: "http://yl.z64.cn/echochat" diff --git a/backend/go-service/pkg/storage/minio.go b/backend/go-service/pkg/storage/minio.go index f2ed060..bd20a95 100644 --- a/backend/go-service/pkg/storage/minio.go +++ b/backend/go-service/pkg/storage/minio.go @@ -5,6 +5,7 @@ package storage import ( "context" "fmt" + "strings" "github.com/echochat/backend/config" "github.com/minio/minio-go/v7" @@ -61,3 +62,28 @@ func ensureBucketPublicRead(ctx context.Context, client *minio.Client, bucket st }`, bucket) return client.SetBucketPolicy(ctx, bucket, policy) } + +func BuildObjectURL(cfg *config.MinioConfig, objectName string) string { + objectName = strings.TrimLeft(strings.TrimSpace(objectName), "/") + if cfg == nil { + return objectName + } + bucket := strings.Trim(strings.TrimSpace(cfg.Bucket), "/") + if base := strings.TrimRight(strings.TrimSpace(cfg.PublicBaseURL), "/"); base != "" { + if bucket != "" && !strings.HasSuffix(base, "/"+bucket) { + base = fmt.Sprintf("%s/%s", base, bucket) + } + if objectName == "" { + return base + } + return fmt.Sprintf("%s/%s", base, objectName) + } + scheme := "http" + if cfg.UseSSL { + scheme = "https" + } + if objectName == "" { + return fmt.Sprintf("%s://%s/%s", scheme, cfg.Endpoint, bucket) + } + return fmt.Sprintf("%s://%s/%s/%s", scheme, cfg.Endpoint, bucket, objectName) +}