修改文件上传as
This commit is contained in:
parent
55df08b314
commit
188d5c27b2
315
pkg/service/cast/ayrshare.go
Normal file
315
pkg/service/cast/ayrshare.go
Normal file
@ -0,0 +1,315 @@
|
||||
package cast
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"fonchain-fiee/pkg/utils"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const (
|
||||
// Ayrshare API 配置
|
||||
ayrshareAPIBaseURL = "https://api.ayrshare.com/api"
|
||||
maxSmallFileSize = 30 * 1024 * 1024 // 30MB
|
||||
apiKey = "208CBE9F-8E4F426A-990A0184-2C6287B9"
|
||||
)
|
||||
|
||||
// UploadMediaResponse 上传媒体文件响应
|
||||
type UploadMediaResponse struct {
|
||||
ID string `json:"id"`
|
||||
URL string `json:"url"`
|
||||
FileName string `json:"fileName"`
|
||||
Description string `json:"description"`
|
||||
IsAs bool `json:"isAs"`
|
||||
}
|
||||
|
||||
// UploadURLResponse 获取上传URL响应
|
||||
type UploadURLResponse struct {
|
||||
AccessURL string `json:"accessUrl"`
|
||||
ContentType string `json:"contentType"`
|
||||
UploadURL string `json:"uploadUrl"`
|
||||
}
|
||||
|
||||
// VerifyURLResponse 验证URL响应
|
||||
type VerifyURLResponse struct {
|
||||
Status string `json:"status"`
|
||||
StatusCode int `json:"statusCode"`
|
||||
ContentType string `json:"contentType"`
|
||||
}
|
||||
|
||||
// UploadMediaByURL 根据文件大小自动选择上传方式
|
||||
// fileURL: 文件的URL链接
|
||||
// apiKey: Ayrshare API Key
|
||||
// fileName: 文件名(可选)
|
||||
// description: 文件描述(可选)
|
||||
func UploadMediaByURL(ctx context.Context, fileURL, fileName, description string) (*UploadMediaResponse, error) {
|
||||
if fileURL == "" {
|
||||
return nil, errors.New("文件URL不能为空")
|
||||
}
|
||||
ok, err := VerifyMediaURL(ctx, fileURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ok {
|
||||
return &UploadMediaResponse{
|
||||
ID: "",
|
||||
URL: fileURL,
|
||||
FileName: "",
|
||||
Description: "",
|
||||
IsAs: false,
|
||||
}, nil
|
||||
}
|
||||
// 下载文件到临时目录
|
||||
tempFile, fileSize, err := downloadFile(ctx, fileURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("下载文件失败: %v", err)
|
||||
}
|
||||
defer os.Remove(tempFile) // 清理临时文件
|
||||
|
||||
zap.L().Info("文件下载完成", zap.String("tempFile", tempFile), zap.Int64("fileSize", fileSize))
|
||||
|
||||
// 如果没有提供文件名,从URL中提取
|
||||
if fileName == "" {
|
||||
fileName = filepath.Base(fileURL)
|
||||
}
|
||||
|
||||
// 根据文件大小选择上传方式
|
||||
if fileSize < maxSmallFileSize {
|
||||
zap.L().Info("使用小文件上传方式", zap.Int64("fileSize", fileSize))
|
||||
return uploadSmallMedia(ctx, tempFile, apiKey, fileName, description)
|
||||
}
|
||||
zap.L().Info("使用大文件上传方式", zap.Int64("fileSize", fileSize))
|
||||
return uploadLargeMedia(ctx, tempFile, apiKey, fileName, description)
|
||||
}
|
||||
|
||||
// downloadFile 下载文件到临时目录
|
||||
func downloadFile(ctx context.Context, fileURL string) (string, int64, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", fileURL, nil)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
|
||||
client := &http.Client{Timeout: 10 * time.Minute}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", 0, fmt.Errorf("下载文件失败,HTTP状态码: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
// 创建临时文件
|
||||
tempFile, err := os.CreateTemp("", "ayrshare_upload_*")
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
defer tempFile.Close()
|
||||
|
||||
// 复制内容到临时文件
|
||||
fileSize, err := io.Copy(tempFile, resp.Body)
|
||||
if err != nil {
|
||||
os.Remove(tempFile.Name())
|
||||
return "", 0, err
|
||||
}
|
||||
|
||||
return tempFile.Name(), fileSize, nil
|
||||
}
|
||||
|
||||
// uploadSmallMedia 上传小于30MB的文件(使用multipart form-data)
|
||||
func uploadSmallMedia(ctx context.Context, filePath, apiKey, fileName, description string) (*UploadMediaResponse, error) {
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("打开文件失败: %v", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// 创建multipart form-data
|
||||
body := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(body)
|
||||
|
||||
// 添加文件
|
||||
part, err := writer.CreateFormFile("file", fileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := io.Copy(part, file); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 添加其他字段
|
||||
if fileName != "" {
|
||||
_ = writer.WriteField("fileName", fileName)
|
||||
}
|
||||
if description != "" {
|
||||
_ = writer.WriteField("description", description)
|
||||
}
|
||||
|
||||
if err := writer.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 发送请求
|
||||
url := fmt.Sprintf("%s/media/upload", ayrshareAPIBaseURL)
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", url, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey))
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
|
||||
client := &http.Client{Timeout: 10 * time.Minute}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("请求失败: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取响应失败: %v", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
zap.L().Error("上传小文件失败", zap.Int("statusCode", resp.StatusCode), zap.String("response", string(respBody)))
|
||||
return nil, fmt.Errorf("上传失败: HTTP %d, %s", resp.StatusCode, string(respBody))
|
||||
}
|
||||
|
||||
var result UploadMediaResponse
|
||||
if err := json.Unmarshal(respBody, &result); err != nil {
|
||||
return nil, fmt.Errorf("解析响应失败: %v", err)
|
||||
}
|
||||
|
||||
zap.L().Info("小文件上传成功", zap.Any("response", result))
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// uploadLargeMedia 上传大于30MB的文件(使用presigned URL)
|
||||
func uploadLargeMedia(ctx context.Context, filePath, apiKey, fileName, description string) (*UploadMediaResponse, error) {
|
||||
// 获取文件的content type
|
||||
ext := filepath.Ext(fileName)
|
||||
if len(ext) > 0 {
|
||||
ext = ext[1:] // 去掉点号
|
||||
}
|
||||
|
||||
// Step 1: 获取上传URL
|
||||
uploadURLResp, err := getUploadURL(ctx, apiKey, fileName, ext)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取上传URL失败: %v", err)
|
||||
}
|
||||
|
||||
zap.L().Info("获取上传URL成功", zap.Any("uploadURLResp", uploadURLResp))
|
||||
|
||||
// Step 2: 上传文件到presigned URL
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("打开文件失败: %v", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "PUT", uploadURLResp.UploadURL, file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", uploadURLResp.ContentType)
|
||||
|
||||
client := &http.Client{Timeout: 30 * time.Minute}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("上传文件失败: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
respBody, _ := io.ReadAll(resp.Body)
|
||||
zap.L().Error("上传大文件失败", zap.Int("statusCode", resp.StatusCode), zap.String("response", string(respBody)))
|
||||
return nil, fmt.Errorf("上传失败: HTTP %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
zap.L().Info("大文件上传成功")
|
||||
|
||||
// 返回结果
|
||||
return &UploadMediaResponse{
|
||||
URL: uploadURLResp.AccessURL,
|
||||
FileName: fileName,
|
||||
Description: description,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// getUploadURL 获取大文件上传的presigned URL
|
||||
func getUploadURL(ctx context.Context, apiKey, fileName, contentType string) (*UploadURLResponse, error) {
|
||||
url := fmt.Sprintf("%s/media/uploadUrl?fileName=%s", ayrshareAPIBaseURL, fileName)
|
||||
if contentType != "" {
|
||||
url = fmt.Sprintf("%s&contentType=%s", url, contentType)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey))
|
||||
|
||||
client := &http.Client{Timeout: 30 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
zap.L().Error("获取上传URL失败", zap.Int("statusCode", resp.StatusCode), zap.String("response", string(respBody)))
|
||||
return nil, fmt.Errorf("获取上传URL失败: HTTP %d, %s", resp.StatusCode, string(respBody))
|
||||
}
|
||||
|
||||
var result UploadURLResponse
|
||||
if err := json.Unmarshal(respBody, &result); err != nil {
|
||||
return nil, fmt.Errorf("解析响应失败: %v", err)
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// VerifyMediaURL 验证媒体URL是否有效
|
||||
func VerifyMediaURL(ctx context.Context, mediaURL string) (bool, error) {
|
||||
url := fmt.Sprintf("%s/media/urlExists", ayrshareAPIBaseURL)
|
||||
requestBody := map[string]string{
|
||||
"mediaUrl": mediaURL,
|
||||
}
|
||||
jsonData, _ := json.Marshal(requestBody)
|
||||
code, postBody, err := utils.PostBytesHeader(url, map[string]interface{}{
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": "Bearer " + apiKey,
|
||||
}, jsonData)
|
||||
if err != nil {
|
||||
zap.L().Error("VerifyMediaURL 提交异常", zap.Error(err))
|
||||
return false, errors.New("验证链接提交异常")
|
||||
}
|
||||
if code != http.StatusOK {
|
||||
return false, nil
|
||||
}
|
||||
var result *VerifyURLResponse
|
||||
_ = json.Unmarshal(postBody, &result)
|
||||
if result.StatusCode != http.StatusOK || result.Status != "success" {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
@ -99,6 +99,26 @@ func Test(ctx *gin.Context) {
|
||||
service.Success(ctx, resp)
|
||||
return
|
||||
}
|
||||
if action == "upload" {
|
||||
fileUrl := ctx.PostForm("fileUrl")
|
||||
uploadResp, err := UploadMediaByURL(ctx, fileUrl, "", "")
|
||||
if err != nil {
|
||||
service.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
service.Success(ctx, uploadResp)
|
||||
return
|
||||
}
|
||||
if action == "getUrl" {
|
||||
fileUrl := ctx.PostForm("fileUrl")
|
||||
uploadResp, err := VerifyMediaURL(ctx, fileUrl)
|
||||
if err != nil {
|
||||
service.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
service.Success(ctx, uploadResp)
|
||||
return
|
||||
}
|
||||
service.Success(ctx, "unknow")
|
||||
return
|
||||
}
|
||||
|
||||
@ -353,11 +353,37 @@ func PostAS(workUuids []string) (errs []error) {
|
||||
var isVideo bool
|
||||
if workDetail.WorkCategory == 1 {
|
||||
isVideo = false
|
||||
mediaUrls = workDetail.Images
|
||||
for _, imageUrl := range workDetail.Images {
|
||||
urlResp, err := UploadMediaByURL(context.Background(), imageUrl, "", "")
|
||||
if err != nil {
|
||||
zap.L().Error("Publish UploadMediaByURL failed", zap.String("imageUrl", imageUrl), zap.Error(err))
|
||||
continue
|
||||
}
|
||||
mediaUrls = append(mediaUrls, urlResp.URL)
|
||||
}
|
||||
//mediaUrls = workDetail.Images
|
||||
}
|
||||
var coverUrl string
|
||||
if workDetail.WorkCategory == 2 {
|
||||
isVideo = true
|
||||
mediaUrls = []string{workDetail.VideoUrl}
|
||||
//mediaUrls = []string{workDetail.VideoUrl}
|
||||
urlResp, err := UploadMediaByURL(context.Background(), workDetail.VideoUrl, "", "")
|
||||
if err != nil {
|
||||
zap.L().Error("Publish UploadMediaByURL failed", zap.String("VideoUrl", workDetail.VideoUrl), zap.Error(err))
|
||||
continue
|
||||
}
|
||||
mediaUrls = []string{urlResp.URL}
|
||||
if workDetail.CoverUrl != "" {
|
||||
urlResp, err = UploadMediaByURL(context.Background(), workDetail.VideoUrl, "", "")
|
||||
if err != nil {
|
||||
zap.L().Error("Publish UploadMediaByURL failed", zap.String("VideoUrl", workDetail.VideoUrl), zap.Error(err))
|
||||
continue
|
||||
}
|
||||
coverUrl = urlResp.URL
|
||||
}
|
||||
}
|
||||
if len(mediaUrls) == 0 {
|
||||
continue
|
||||
}
|
||||
ArtistInfoResp, _err := service.CastProvider.GetArtist(context.Background(), &cast.GetArtistReq{
|
||||
ArtistUuid: workDetail.ArtistUuid,
|
||||
@ -370,7 +396,6 @@ func PostAS(workUuids []string) (errs []error) {
|
||||
errs = append(errs, errors.New("艺人平台信息未配置"))
|
||||
continue
|
||||
}
|
||||
|
||||
for _, platformID := range needPlatformIDs {
|
||||
var postResp *aryshare.PostResponse = &aryshare.PostResponse{}
|
||||
postReq := &aryshare.PostRequest{
|
||||
@ -389,7 +414,7 @@ func PostAS(workUuids []string) (errs []error) {
|
||||
postReq.InstagramOptions = &aryshare.InstagramOptions{
|
||||
ShareReelsFeed: false,
|
||||
AudioName: "",
|
||||
ThumbNail: workDetail.CoverUrl,
|
||||
ThumbNail: coverUrl,
|
||||
ThumbNailOffset: 0,
|
||||
Stories: false,
|
||||
AltText: nil,
|
||||
|
||||
44
pkg/utils/files.go
Normal file
44
pkg/utils/files.go
Normal file
@ -0,0 +1,44 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"fonchain-fiee/pkg/e"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// SaveUrlFileDisk 保存图片到本地
|
||||
func SaveUrlFileDisk(url string, path string, filename string) (fullPath string, err error) {
|
||||
if err = CreateDirPath(path); err != nil {
|
||||
zap.L().Error("SaveUrlFileDisk err ", zap.Error(err))
|
||||
return
|
||||
}
|
||||
if filename == "" {
|
||||
stepName := strings.Split(url, "/")
|
||||
if len(stepName) > 1 {
|
||||
filename = stepName[len(stepName)-1]
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
err = errors.New(e.GetMsg(e.ERROR_DOWNLOAD_FILE))
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
|
||||
}
|
||||
resp.Body.Close()
|
||||
}()
|
||||
bytes, err := io.ReadAll(resp.Body)
|
||||
fullPath = fmt.Sprintf("%s/%s", path, filename)
|
||||
// 写入数据
|
||||
err = os.WriteFile(fullPath, bytes, 0777)
|
||||
return
|
||||
}
|
||||
@ -133,3 +133,24 @@ func GetUrl(apiUrl string, headerData map[string]string, proxyURL ...string) (st
|
||||
zap.L().Info("Get", zap.Any("url", apiUrl), zap.Any("body", body))
|
||||
return
|
||||
}
|
||||
|
||||
func PostBytesHeader(url string, header map[string]interface{}, data []byte) (int, []byte, error) {
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(data))
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("创建请求失败: %v", "")
|
||||
}
|
||||
for k, v := range header {
|
||||
req.Header.Set(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("请求失败: %v", "")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("读取响应失败: %v", "")
|
||||
}
|
||||
return resp.StatusCode, body, nil
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
@ -163,3 +164,11 @@ func CopyFile(src, dstDir string) (string, error) {
|
||||
}
|
||||
return dst, nil
|
||||
}
|
||||
|
||||
func FileNameFromURL(rawURL string) (string, error) {
|
||||
u, err := url.Parse(rawURL)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return path.Base(u.Path), nil
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user