生成视频并且上传到minio
This commit is contained in:
@@ -93,16 +93,10 @@ type ListTaskRes struct {
|
|||||||
|
|
||||||
// ---------- 回调通知结构 ----------
|
// ---------- 回调通知结构 ----------
|
||||||
|
|
||||||
// CallbackPayload 回调通知内容
|
// CallbackPayload 回调通知内容(与 GetTaskRes 出参一致)
|
||||||
type CallbackPayload struct {
|
type CallbackPayload struct {
|
||||||
TaskID string `json:"taskId" dc:"任务ID"`
|
TaskInfo TranscribeTaskItem `json:"taskInfo" dc:"任务信息"`
|
||||||
Status string `json:"status" dc:"任务状态"`
|
DetailList []TranscribeTaskDetailItem `json:"detailList" dc:"明细列表(每视频一条)"`
|
||||||
TotalFiles int `json:"totalFiles" dc:"文件总数"`
|
|
||||||
SuccessFiles int `json:"successFiles" dc:"成功文件数"`
|
|
||||||
FailFiles int `json:"failFiles" dc:"失败文件数"`
|
|
||||||
Result string `json:"result,omitempty" dc:"完整的处理结果JSON"`
|
|
||||||
ErrorMessage string `json:"errorMessage,omitempty" dc:"错误信息"`
|
|
||||||
DetailList []TranscribeTaskDetailItem `json:"detailList" dc:"明细列表"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- 任务处理结果结构(用于result JSONB) ----------
|
// ---------- 任务处理结果结构(用于result JSONB) ----------
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ func (s *audioTaskService) processTask(taskID string, urls []string, model, lang
|
|||||||
g.Log().Infof(ctx, "[任务 %s] 全部处理流程结束", taskID)
|
g.Log().Infof(ctx, "[任务 %s] 全部处理流程结束", taskID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// callback 向回调地址 POST 任务结果
|
// callback 向回调地址 POST 任务结果(与查询接口 GetTaskRes 出参一致)
|
||||||
func (s *audioTaskService) callback(ctx context.Context, taskID, status, errMsg, callbackURL string) {
|
func (s *audioTaskService) callback(ctx context.Context, taskID, status, errMsg, callbackURL string) {
|
||||||
if callbackURL == "" {
|
if callbackURL == "" {
|
||||||
return
|
return
|
||||||
@@ -214,27 +214,29 @@ func (s *audioTaskService) callback(ctx context.Context, taskID, status, errMsg,
|
|||||||
detailItems = append(detailItems, dao.DetailEntityToItem(&detailList[i]))
|
detailItems = append(detailItems, dao.DetailEntityToItem(&detailList[i]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 构建与查询接口一致的 taskInfo
|
||||||
|
taskInfo := dao.EntityToItem(task)
|
||||||
|
|
||||||
|
// 与查询接口一致:从 result 中补全 scenes 等字段
|
||||||
|
detailItems = enrichDetailsFromResult(task.Result, detailItems)
|
||||||
|
|
||||||
payload := dto.CallbackPayload{
|
payload := dto.CallbackPayload{
|
||||||
TaskID: taskID,
|
TaskInfo: taskInfo,
|
||||||
Status: status,
|
DetailList: detailItems,
|
||||||
TotalFiles: task.TotalFiles,
|
|
||||||
SuccessFiles: task.SuccessFiles,
|
|
||||||
FailFiles: task.FailFiles,
|
|
||||||
ErrorMessage: errMsg,
|
|
||||||
Result: task.Result,
|
|
||||||
DetailList: detailItems,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body, _ := json.Marshal(payload)
|
body, _ := json.Marshal(payload)
|
||||||
g.Log().Infof(ctx, "[回调 %s] 触发回调, 状态=%s, 成功=%d 失败=%d, 错误=%s, 目标=%s",
|
g.Log().Infof(ctx, "[回调 %s] 触发回调, 状态=%s, 成功=%d 失败=%d, 错误=%s, 目标=%s",
|
||||||
taskID, status, payload.SuccessFiles, payload.FailFiles, errMsg, callbackURL)
|
taskID, taskInfo.Status, taskInfo.SuccessFiles, taskInfo.FailFiles, errMsg, callbackURL)
|
||||||
g.Log().Debugf(ctx, "[回调 %s] 回调载荷长度=%d字节, 明细条数=%d",
|
g.Log().Debugf(ctx, "[回调 %s] 回调载荷长度=%d字节, 明细条数=%d",
|
||||||
taskID, len(body), len(detailItems))
|
taskID, len(body), len(detailItems))
|
||||||
|
// 透传调用方的用户信息,供回调方 GetUserInfo 从 X-User-Info 头获取
|
||||||
|
userJSON, _ := json.Marshal(beans.User{UserName: "admin", TenantId: 1})
|
||||||
|
g.Log().Infof(ctx, "[回调 %s] curl -X POST '%s' -H 'Content-Type: application/json' -H 'X-User-Info: %s' -d '%s'",
|
||||||
|
taskID, callbackURL, string(userJSON), strings.ReplaceAll(string(body), "'", "'\\''"))
|
||||||
|
|
||||||
req, _ := http.NewRequest("POST", callbackURL, bytes.NewReader(body))
|
req, _ := http.NewRequest("POST", callbackURL, bytes.NewReader(body))
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
// 透传调用方的用户信息,供回调方 GetUserInfo 从 X-User-Info 头获取
|
|
||||||
userJSON, _ := json.Marshal(beans.User{UserName: "admin", TenantId: 1})
|
|
||||||
req.Header.Set("X-User-Info", string(userJSON))
|
req.Header.Set("X-User-Info", string(userJSON))
|
||||||
|
|
||||||
resp, reqErr := http.DefaultClient.Do(req)
|
resp, reqErr := http.DefaultClient.Do(req)
|
||||||
|
|||||||
@@ -7,11 +7,14 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitea.com/red-future/common/beans"
|
||||||
commonHttp "gitea.com/red-future/common/http"
|
commonHttp "gitea.com/red-future/common/http"
|
||||||
|
|
||||||
"github.com/gogf/gf/v2/frame/g"
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
@@ -64,7 +67,16 @@ func (s *concatService) Concat(ctx context.Context, req *ConcatReq) (res *Concat
|
|||||||
outputPath := req.OutputPath
|
outputPath := req.OutputPath
|
||||||
if outputPath == "" {
|
if outputPath == "" {
|
||||||
outputDir := filepath.Dir(req.VideoPaths[0])
|
outputDir := filepath.Dir(req.VideoPaths[0])
|
||||||
outputPath = filepath.Join(outputDir, "concat_output.mp4")
|
// 用第一个输入文件名 + 拼接数 + 时间戳,溯源更清晰
|
||||||
|
baseName := filepath.Base(req.VideoPaths[0])
|
||||||
|
ext := filepath.Ext(baseName)
|
||||||
|
stem := strings.TrimSuffix(baseName, ext)
|
||||||
|
stemRunes := []rune(stem)
|
||||||
|
if len(stemRunes) > 20 {
|
||||||
|
stemRunes = stemRunes[:20]
|
||||||
|
}
|
||||||
|
outputPath = filepath.Join(outputDir,
|
||||||
|
fmt.Sprintf("concat_%s_x%d_%s%s", string(stemRunes), len(req.VideoPaths), time.Now().Format("150405"), ext))
|
||||||
}
|
}
|
||||||
|
|
||||||
method := req.Method
|
method := req.Method
|
||||||
@@ -304,9 +316,9 @@ type uploadFileRes struct {
|
|||||||
FileAddressPrefix string `json:"fileAddressPrefix"`
|
FileAddressPrefix string `json:"fileAddressPrefix"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UploadToMinIO 通过 OSS 微服务的 multipart 文件上传接口上传到 MinIO
|
// UploadToMinIO 通过 OSS 微服务的 uploadFile 接口上传到 MinIO(multipart/form-data)
|
||||||
func (s *concatService) UploadToMinIO(ctx context.Context, localFilePath string) (*uploadFileRes, error) {
|
func (s *concatService) UploadToMinIO(ctx context.Context, localFilePath string) (*uploadFileRes, error) {
|
||||||
// 手动构建 multipart/form-data 表单
|
// 构建 multipart/form-data 表单
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
mw := multipart.NewWriter(&buf)
|
mw := multipart.NewWriter(&buf)
|
||||||
|
|
||||||
@@ -325,25 +337,39 @@ func (s *concatService) UploadToMinIO(ctx context.Context, localFilePath string)
|
|||||||
}
|
}
|
||||||
mw.Close()
|
mw.Close()
|
||||||
|
|
||||||
|
// 使用 commonHttp 的客户端(含 Consul 服务发现),大文件上传设置长超时
|
||||||
client := commonHttp.Httpclient.Clone()
|
client := commonHttp.Httpclient.Clone()
|
||||||
|
// 必须单独设置 Transport.ResponseHeaderTimeout,SetTimeout 只设 Client.Timeout
|
||||||
|
newTransport := http.DefaultTransport.(*http.Transport).Clone()
|
||||||
|
newTransport.ResponseHeaderTimeout = 5 * time.Minute
|
||||||
|
client.Transport = newTransport
|
||||||
|
client.SetTimeout(10 * time.Minute)
|
||||||
|
|
||||||
// 透传认证 headers
|
// 透传认证 headers
|
||||||
|
hasAuthHeader := false
|
||||||
if r := g.RequestFromCtx(ctx); r != nil {
|
if r := g.RequestFromCtx(ctx); r != nil {
|
||||||
for k, v := range r.Header {
|
for k, v := range r.Header {
|
||||||
client.SetHeader(k, v[0])
|
client.SetHeader(k, v[0])
|
||||||
|
if strings.EqualFold(k, "Authorization") || strings.EqualFold(k, "X-User-Info") {
|
||||||
|
hasAuthHeader = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 原始请求无认证信息时,注入默认用户上下文
|
||||||
|
if !hasAuthHeader {
|
||||||
|
userJSON, _ := json.Marshal(beans.User{UserName: "admin", TenantId: 1})
|
||||||
|
client.SetHeader("X-User-Info", string(userJSON))
|
||||||
|
}
|
||||||
|
|
||||||
// 设置 multipart Content-Type(含 boundary)
|
// 设置 multipart Content-Type(含 boundary)
|
||||||
contentType := mw.FormDataContentType()
|
contentType := mw.FormDataContentType()
|
||||||
g.Log().Debugf(ctx, "[UploadToMinIO] Content-Type: %s", contentType)
|
|
||||||
client.SetHeader("Content-Type", contentType)
|
client.SetHeader("Content-Type", contentType)
|
||||||
|
|
||||||
// 打印请求信息
|
g.Log().Debugf(ctx, "[UploadToMinIO] 请求URL: oss/file/uploadFile, 文件: %s, Body大小: %d bytes",
|
||||||
postBytes := buf.Bytes()
|
localFilePath, buf.Len())
|
||||||
g.Log().Debugf(ctx, "[UploadToMinIO] 请求URL: oss/file/uploadFile, 文件: %s, Body大小: %d bytes, Boundary: %s",
|
|
||||||
localFilePath, len(postBytes), mw.Boundary())
|
|
||||||
|
|
||||||
response, err := client.Post(ctx, "oss/file/uploadFile", postBytes)
|
// 发送 multipart 请求(原始字节流)
|
||||||
|
response, err := client.Post(ctx, "oss/file/uploadFile", buf.Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Error(ctx, err)
|
glog.Error(ctx, err)
|
||||||
return nil, fmt.Errorf("调用OSS上传服务失败: %v", err)
|
return nil, fmt.Errorf("调用OSS上传服务失败: %v", err)
|
||||||
|
|||||||
Reference in New Issue
Block a user