package uploadser import ( "bytes" "encoding/json" "fmt" "io" "mime/multipart" "net/http" "net/textproto" "strconv" "videoweb/pub/common/log" "videoweb/pub/pubdef" "videoweb/pub/storage/proxy" ) const ( defaultUploadUserName = "questionnaires" // 分片上传路径 videoBodyPath = "/media/web/autopublish/videoBody" // 分片完成路径 videoOkPath = "/media/web/autopublish/videoOk" // 图片上传路径 imagePath = "/media/web/autopublish/image" ) // VideoBodyResp 视频分片上传响应 type VideoBodyResp struct { Code int `json:"code"` Data VideoBodyData `json:"data"` Hash bool `json:"hash"` Msg string `json:"msg"` Time string `json:"time"` Tip string `json:"tip"` } // VideoBodyData 视频分片上传响应数据 type VideoBodyData struct { Id string `json:"id"` TaskId string `json:"taskId"` SourceNameFs string `json:"sourceNameFs"` } // VideoOkResp 视频上传完成响应 type VideoOkResp struct { Code int `json:"code"` Msg string `json:"msg"` Tip string `json:"tip"` } // ImageUploadResp 图片上传响应 type ImageUploadResp struct { Code int `json:"code"` Data ImageUploadData `json:"data"` Hash bool `json:"hash"` Msg string `json:"msg"` Time string `json:"time"` Tip string `json:"tip"` } type ImageUploadData struct { Id string `json:"id"` SourceNameFs string `json:"sourceNameFs"` } // VideoBodyParam 视频分片上传入参 type VideoBodyParam struct { TaskId string Pos string AppId string UploadUserName string FileHeader *multipart.FileHeader // 分片文件 } // ImageParam 图片上传入参 type ImageParam struct { AppId string FileHeader *multipart.FileHeader } // targetDomain 获取转发目标域名 func targetDomain() string { return proxy.DomainAddressRepair("", proxy.BucketVideoAddressV2) } // ForwardVideoBody 转发视频分片上传 func ForwardVideoBody(param *VideoBodyParam) ([]byte, error) { if param.AppId == "" { param.AppId = strconv.Itoa(pubdef.AppId) } if param.UploadUserName == "" { param.UploadUserName = defaultUploadUserName } var buf bytes.Buffer mw := multipart.NewWriter(&buf) fields := map[string]string{ "taskId": param.TaskId, "pos": param.Pos, "appId": param.AppId, "uploadUserName": param.UploadUserName, } for k, v := range fields { if err := mw.WriteField(k, v); err != nil { return nil, fmt.Errorf("write field %s: %w", k, err) } } if param.FileHeader != nil { if err := writeFileField(mw, "file", param.FileHeader); err != nil { return nil, err } } if err := mw.Close(); err != nil { return nil, err } url := targetDomain() + videoBodyPath return doMultipartPost(url, mw.FormDataContentType(), &buf) } // ForwardVideoOk 转发视频分片完成通知 func ForwardVideoOk(taskId string) ([]byte, error) { body := map[string]string{"taskId": taskId} jsonBytes, err := json.Marshal(body) if err != nil { return nil, err } url := targetDomain() + videoOkPath resp, err := http.Post(url, "application/json", bytes.NewReader(jsonBytes)) //nolint:noctx if err != nil { log.Error("uploadser:ForwardVideoOk failed", log.E(err)) return nil, err } defer resp.Body.Close() return io.ReadAll(resp.Body) } // ForwardImage 转发图片上传 func ForwardImage(param *ImageParam) ([]byte, error) { if param.AppId == "" { param.AppId = strconv.Itoa(pubdef.AppId) } var buf bytes.Buffer mw := multipart.NewWriter(&buf) if err := mw.WriteField("appId", param.AppId); err != nil { return nil, err } if param.FileHeader != nil { if err := writeFileField(mw, "file", param.FileHeader); err != nil { return nil, err } } if err := mw.Close(); err != nil { return nil, err } url := targetDomain() + imagePath return doMultipartPost(url, mw.FormDataContentType(), &buf) } // writeFileField 将 multipart.FileHeader 写入目标 multipart.Writer func writeFileField(mw *multipart.Writer, fieldName string, fh *multipart.FileHeader) error { src, err := fh.Open() if err != nil { return fmt.Errorf("open file: %w", err) } defer src.Close() // 保留原始文件名和 Content-Type h := make(textproto.MIMEHeader) contentType := fh.Header.Get("Content-Type") if contentType == "" { contentType = "application/octet-stream" } h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`, fieldName, fh.Filename)) h.Set("Content-Type", contentType) part, err := mw.CreatePart(h) if err != nil { return fmt.Errorf("create part: %w", err) } if _, err = io.Copy(part, src); err != nil { return fmt.Errorf("copy file data: %w", err) } return nil } // doMultipartPost 发送 multipart/form-data 请求并返回响应 body func doMultipartPost(url, contentType string, body io.Reader) ([]byte, error) { req, err := http.NewRequest(http.MethodPost, url, body) if err != nil { return nil, err } req.Header.Set("Content-Type", contentType) cli := &http.Client{} resp, err := cli.Do(req) if err != nil { log.Error("uploadser:doMultipartPost failed", log.Any("url", url), log.E(err)) return nil, err } defer resp.Body.Close() return io.ReadAll(resp.Body) }