540 lines
16 KiB
Go
540 lines
16 KiB
Go
package imports
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"fonchain-fiee/api/accountFiee"
|
|
"fonchain-fiee/api/bundle"
|
|
apiCast "fonchain-fiee/api/cast"
|
|
"fonchain-fiee/api/files"
|
|
"fonchain-fiee/pkg/config"
|
|
"fonchain-fiee/pkg/model"
|
|
modelCast "fonchain-fiee/pkg/model/cast"
|
|
"fonchain-fiee/pkg/service"
|
|
"fonchain-fiee/pkg/service/cast"
|
|
"fonchain-fiee/pkg/service/upload"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/mholt/archiver"
|
|
"github.com/xuri/excelize/v2"
|
|
)
|
|
|
|
func ImportPublish(c *gin.Context) {
|
|
// 1. 上传画家短视频详情文件
|
|
excelFile, err := c.FormFile("excel")
|
|
if err != nil {
|
|
c.JSON(400, gin.H{"error": "缺少 Excel 文件 excel"})
|
|
return
|
|
}
|
|
zipFile, err := c.FormFile("zip")
|
|
if err != nil {
|
|
c.JSON(400, gin.H{"error": "缺少 ZIP 文件"})
|
|
return
|
|
}
|
|
|
|
// 2. 保存临时文件
|
|
tempDir := "tmp"
|
|
os.MkdirAll(tempDir, 0755)
|
|
excelPath := filepath.Join(tempDir, "artists.xlsx")
|
|
zipPath := filepath.Join(tempDir, "archive.zip")
|
|
|
|
if err = c.SaveUploadedFile(excelFile, excelPath); err != nil {
|
|
c.JSON(500, gin.H{"error": "保存 Excel 失败"})
|
|
return
|
|
}
|
|
if err = c.SaveUploadedFile(zipFile, zipPath); err != nil {
|
|
c.JSON(500, gin.H{"error": "保存 ZIP 文件失败"})
|
|
return
|
|
}
|
|
|
|
// 3. 解压 ZIP
|
|
unzipPath := filepath.Join(tempDir, "unzipped")
|
|
if _, err = os.Stat(unzipPath); err == nil {
|
|
// 路径已存在,删除
|
|
if removeErr := os.RemoveAll(unzipPath); removeErr != nil {
|
|
c.JSON(500, gin.H{"error": "清理已存在解压目录失败: " + removeErr.Error()})
|
|
return
|
|
}
|
|
}
|
|
os.MkdirAll(unzipPath, 0755)
|
|
if err = archiver.Unarchive(zipPath, unzipPath); err != nil {
|
|
c.JSON(500, gin.H{"error": "解压 ZIP 失败: " + err.Error()})
|
|
return
|
|
}
|
|
entries, err := os.ReadDir(unzipPath)
|
|
if err != nil || len(entries) == 0 {
|
|
c.JSON(500, gin.H{"error": "读取解压目录失败或目录为空"})
|
|
return
|
|
}
|
|
|
|
if len(entries) == 1 && entries[0].IsDir() {
|
|
// 说明解压后多了一层目录,把它设为新的 unzipPath
|
|
unzipPath = filepath.Join(unzipPath, entries[0].Name())
|
|
}
|
|
defer os.RemoveAll(tempDir)
|
|
// 4. 读取 Excel 画家名单, 匹配视频和图片
|
|
artists, err := readArtistVideoInfo(excelPath, unzipPath)
|
|
if err != nil {
|
|
c.JSON(500, gin.H{"error": "读取 Excel 失败"})
|
|
return
|
|
}
|
|
// 5.发布视频
|
|
var failedRecords []FailedRecord
|
|
var artistResp []ArtistVideoDetail
|
|
|
|
for _, artist := range artists {
|
|
var infoResp *accountFiee.UserInfoResponse
|
|
var err error
|
|
list, err := service.AccountFieeProvider.UserList(context.Background(), &accountFiee.UserListRequest{
|
|
Name: artist.Name,
|
|
})
|
|
if err != nil {
|
|
failedRecords = append(failedRecords, FailedRecord{
|
|
Name: artist.Name,
|
|
Msg: fmt.Sprintf("获取用户信息失败: %s", err.Error()),
|
|
})
|
|
log.Printf(fmt.Sprintf("获取用户信息失败: %s", err.Error()))
|
|
continue
|
|
}
|
|
if list != nil && len(list.UserList) > 0 {
|
|
infoResp, err = service.AccountFieeProvider.Info(context.Background(), &accountFiee.InfoRequest{
|
|
ID: list.UserList[0].Id,
|
|
Domain: "app",
|
|
})
|
|
if err != nil {
|
|
failedRecords = append(failedRecords, FailedRecord{
|
|
Name: artist.Name,
|
|
Msg: fmt.Sprintf("获取用户信息失败: %s", err.Error()),
|
|
})
|
|
log.Printf(fmt.Sprintf("获取用户信息失败: %s", err.Error()))
|
|
continue
|
|
}
|
|
}
|
|
|
|
if err = cast.CheckUserBundleBalance(int32(list.UserList[0].Id), modelCast.BalanceTypeVideoValue); err != nil {
|
|
failedRecords = append(failedRecords, FailedRecord{
|
|
Name: artist.Name,
|
|
Msg: fmt.Sprintf("检查用户视频可消耗数量: %s", err.Error()),
|
|
})
|
|
log.Printf(fmt.Sprintf("检查用户视频可消耗数量: %s", err.Error()))
|
|
continue
|
|
}
|
|
//自媒体账号
|
|
accountList, err := service.CastProvider.MediaUserList(c, &apiCast.MediaUserListReq{
|
|
//ArtistUuid: strconv.FormatUint(list.UserList[0].Id, 10),
|
|
ArtistVal: artist.Name,
|
|
Page: 1,
|
|
PageSize: 10,
|
|
})
|
|
if err != nil {
|
|
failedRecords = append(failedRecords, FailedRecord{
|
|
Name: artist.Name,
|
|
Msg: fmt.Sprintf("自媒体账号数量获取失败: %s,账号数量:%d", err.Error(), len(accountList.Data)),
|
|
})
|
|
log.Printf(fmt.Sprintf("自媒体账号数量获取失败: %s,账号数量:%d", err.Error(), len(accountList.Data)))
|
|
continue
|
|
}
|
|
if accountList == nil || len(accountList.Data) == 0 {
|
|
failedRecords = append(failedRecords, FailedRecord{
|
|
Name: artist.Name,
|
|
Msg: "自媒体账号数量为0",
|
|
})
|
|
log.Printf(fmt.Sprintf("自媒体账号,账号数量:%d", len(accountList.Data)))
|
|
continue
|
|
}
|
|
mediaAccountUuids := []string{}
|
|
mediaAccountNames := []string{}
|
|
platformIDs := []apiCast.PlatformIDENUM{}
|
|
for _, info := range accountList.Data {
|
|
if info.PlatformID == 2 && ((artist.Id == "31" && info.ArtistName == "荣小松") ||
|
|
(artist.Id == "72" && info.ArtistName == "韩风霞")) {
|
|
continue // 跳过
|
|
}
|
|
mediaAccountUuids = append(mediaAccountUuids, info.MediaAccountUuid)
|
|
mediaAccountNames = append(mediaAccountNames, info.PlatformUserName)
|
|
platformIDs = append(platformIDs, apiCast.PlatformIDENUM(info.PlatformID))
|
|
}
|
|
newCtx := cast.NewCtxWithUserInfo(c)
|
|
resp, err := service.CastProvider.UpdateWorkVideo(newCtx, &apiCast.UpdateWorkVideoReq{
|
|
Title: artist.Title,
|
|
Content: artist.Title,
|
|
VideoUrl: artist.Video,
|
|
CoverUrl: artist.Img,
|
|
MediaAccountUuids: mediaAccountUuids,
|
|
MediaAccountNames: mediaAccountNames,
|
|
PlatformIDs: platformIDs,
|
|
PublishConfig1: &apiCast.PublishConfig{
|
|
CanComment: 1,
|
|
CanJoin: 1,
|
|
CanQuote: 1,
|
|
ForbidComment: 2,
|
|
IsAI: 1,
|
|
PublicType: 1,
|
|
},
|
|
PublishConfig2: &apiCast.PublishConfig{
|
|
CanComment: 1,
|
|
CanJoin: 1,
|
|
CanQuote: 1,
|
|
ForbidComment: 2,
|
|
IsAI: 1,
|
|
PublicType: 1,
|
|
},
|
|
PublishConfig3: &apiCast.PublishConfig{
|
|
CanComment: 1,
|
|
CanJoin: 1,
|
|
CanQuote: 1,
|
|
ForbidComment: 1,
|
|
IsAI: 1,
|
|
PublicType: 1,
|
|
},
|
|
Action: "submit",
|
|
ArtistUuid: strconv.FormatUint(list.UserList[0].Id, 10),
|
|
ArtistName: infoResp.Name,
|
|
ArtistPhone: infoResp.TelNum,
|
|
ArtistPhoneAreaCode: infoResp.TelAreaCode,
|
|
Source: 2,
|
|
})
|
|
if err != nil {
|
|
failedRecords = append(failedRecords, FailedRecord{
|
|
Name: artist.Name,
|
|
Msg: fmt.Sprintf("发布"+artist.Name+"视频"+artist.Title+"失败: %s", err.Error()),
|
|
})
|
|
log.Printf(fmt.Sprintf("发布"+artist.Name+"视频"+artist.Title+"失败: %s", err.Error()))
|
|
continue
|
|
}
|
|
artistResp = append(artistResp, ArtistVideoDetail{
|
|
Id: artist.Id,
|
|
ArtistName: artist.Name,
|
|
Title: artist.Title,
|
|
WorkUuid: resp.WorkUuid,
|
|
Youtube: artist.Youtube,
|
|
Instagram: artist.Instagram,
|
|
TikTok: artist.TikTok,
|
|
})
|
|
}
|
|
excelUrl, err := exportRecordsToExcel(artistResp)
|
|
if err != nil {
|
|
service.Error(c, err)
|
|
return
|
|
}
|
|
|
|
// 6. 返回结果
|
|
service.Success(c, map[string]interface{}{
|
|
"excelUrl": excelUrl,
|
|
"failedRecords": failedRecords,
|
|
})
|
|
}
|
|
|
|
func readArtistVideoInfo(excelPath, unzipPath string) ([]ArtistMedia, error) {
|
|
log.Println(unzipPath)
|
|
f, err := excelize.OpenFile(excelPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer f.Close()
|
|
sheetName := f.GetSheetName(0)
|
|
rows, err := f.GetRows(sheetName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
log.Println("start read excel")
|
|
var artists []ArtistMedia
|
|
for i, row := range rows {
|
|
if i == 0 || i == 1 || len(row) < 2 {
|
|
continue
|
|
}
|
|
if i == 165 {
|
|
break
|
|
}
|
|
id, _ := f.GetCellValue(sheetName, fmt.Sprintf("A%d", i+1))
|
|
if id != "" {
|
|
id = strings.TrimSpace(id)
|
|
}
|
|
artistName, _ := f.GetCellValue(sheetName, fmt.Sprintf("B%d", i+1))
|
|
if artistName != "" {
|
|
artistName = strings.TrimSpace(artistName)
|
|
}
|
|
title, _ := f.GetCellValue(sheetName, fmt.Sprintf("C%d", i+1))
|
|
if title != "" {
|
|
title = strings.TrimSpace(title)
|
|
}
|
|
youtube, _ := f.GetCellValue(sheetName, fmt.Sprintf("D%d", i+1))
|
|
if youtube != "" {
|
|
youtube = strings.TrimSpace(youtube)
|
|
}
|
|
instagram, _ := f.GetCellValue(sheetName, fmt.Sprintf("E%d", i+1))
|
|
if instagram != "" {
|
|
instagram = strings.TrimSpace(instagram)
|
|
}
|
|
tiktok, _ := f.GetCellValue(sheetName, fmt.Sprintf("F%d", i+1))
|
|
if tiktok != "" {
|
|
tiktok = strings.TrimSpace(tiktok)
|
|
}
|
|
artists = append(artists, ArtistMedia{
|
|
Id: id,
|
|
Name: artistName,
|
|
Title: title,
|
|
Youtube: youtube,
|
|
Instagram: instagram,
|
|
TikTok: tiktok,
|
|
})
|
|
}
|
|
artists, err = matchArtistMedia(artists, unzipPath)
|
|
return artists, nil
|
|
}
|
|
func matchArtistMedia(artists []ArtistMedia, unzipPath string) ([]ArtistMedia, error) {
|
|
var err error
|
|
var res []ArtistMedia
|
|
for _, artist := range artists {
|
|
oldImgPath := fmt.Sprintf("%s/%s/%s.jpg", unzipPath, artist.Name, artist.Id)
|
|
oldVideoPath := fmt.Sprintf("%s/%s/%s.mp4", unzipPath, artist.Name, artist.Id)
|
|
// 检查源文件是否存在
|
|
if _, err = os.Stat(oldImgPath); os.IsNotExist(err) {
|
|
continue
|
|
}
|
|
if _, err = os.Stat(oldVideoPath); os.IsNotExist(err) {
|
|
continue
|
|
}
|
|
baseDir := filepath.Join(unzipPath, artist.Name)
|
|
if err = os.MkdirAll(baseDir, 0755); err != nil {
|
|
log.Println("创建目录失败:", err)
|
|
return nil, err
|
|
}
|
|
log.Println("创建目录成功:", baseDir)
|
|
|
|
// 重命名
|
|
now := time.Now().Unix()
|
|
imgPath := fmt.Sprintf("%s/%s/%s_%d.jpg", unzipPath, artist.Name, artist.Id, now)
|
|
videoPath := fmt.Sprintf("%s/%s/%s_%d.mp4", unzipPath, artist.Name, artist.Id, now)
|
|
if err = os.Rename(oldImgPath, imgPath); err != nil {
|
|
log.Println("图片:"+oldImgPath+"重命名失败:", err)
|
|
return nil, err
|
|
}
|
|
if err = os.Rename(oldVideoPath, videoPath); err != nil {
|
|
log.Println("视频:"+oldVideoPath+"重命名失败:", err)
|
|
return nil, err
|
|
}
|
|
//转为url
|
|
content, err := os.ReadFile(videoPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err = UploadToAnotherService(context.Background(), content, filepath.Base(videoPath)); err != nil {
|
|
log.Println("上传视频失败:", err)
|
|
return nil, err
|
|
}
|
|
var httpType string
|
|
if config.AppMode == "prod" {
|
|
url := "saas.fiee.com"
|
|
httpType = fmt.Sprintf("%s%s", model.HttpsType, url)
|
|
} else {
|
|
url := "114.218.158.24:9020"
|
|
httpType = fmt.Sprintf("%s%s", model.HttpType, url)
|
|
}
|
|
baseUrl := fmt.Sprintf("%s/api/fiee/resource/raw/", httpType)
|
|
videoUrl := baseUrl + filepath.Base(videoPath)
|
|
imgUrl, err := upload.PutBos(filepath.ToSlash(imgPath), "image", false)
|
|
if err != nil {
|
|
log.Println("上传图片失败:", err)
|
|
return nil, err
|
|
}
|
|
tmp := artist
|
|
tmp.Id = artist.Id
|
|
tmp.Name = artist.Name
|
|
tmp.Title = artist.Title
|
|
tmp.Img = imgUrl
|
|
//tmp.Video = filepath.ToSlash(videoPath)
|
|
tmp.Video = videoUrl
|
|
res = append(res, tmp)
|
|
}
|
|
return res, nil
|
|
}
|
|
func UploadToAnotherService(ctx context.Context, fileData []byte, path string) error {
|
|
const chunkSize = 4*1024*1024 - 100
|
|
_, err := service.FilesProvider.TusCreate(ctx, &files.TusCreateReq{
|
|
Path: path,
|
|
UserSpacePath: "",
|
|
Override: true,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
log.Println("create success ......**********")
|
|
offset := int64(0)
|
|
totalSize := int64(len(fileData))
|
|
for offset < totalSize {
|
|
end := offset + chunkSize
|
|
if end > totalSize {
|
|
end = totalSize
|
|
}
|
|
chunk := fileData[offset:end]
|
|
_, err = service.FilesProvider.TusUpload(ctx, &files.TusUploadReq{
|
|
Path: path,
|
|
UploadOffset: offset,
|
|
Content: chunk,
|
|
UserSpacePath: "",
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("上传 offset=%d chunk 失败: %w", offset, err)
|
|
}
|
|
log.Printf("upload chunk: %d - %d success\n", offset, end)
|
|
offset = end
|
|
}
|
|
return nil
|
|
}
|
|
func exportRecordsToExcel(artistInfos []ArtistVideoDetail) (string, error) {
|
|
f := excelize.NewFile()
|
|
sheet := "Sheet1"
|
|
f.NewSheet(sheet)
|
|
|
|
// 写表头
|
|
headers := []string{"序号", "画家名", "标题", "uuid", "youtube", "instagram", "tiktok"}
|
|
|
|
for col, h := range headers {
|
|
_ = f.SetCellValue(sheet, string(rune('A'+col))+"1", h)
|
|
}
|
|
|
|
// 写数据
|
|
for i, artistInfo := range artistInfos {
|
|
row := i + 2
|
|
_ = f.SetCellValue(sheet, "A"+strconv.Itoa(row), artistInfo.Id)
|
|
_ = f.SetCellValue(sheet, "B"+strconv.Itoa(row), artistInfo.ArtistName)
|
|
_ = f.SetCellValue(sheet, "C"+strconv.Itoa(row), artistInfo.Title)
|
|
_ = f.SetCellValue(sheet, "D"+strconv.Itoa(row), artistInfo.WorkUuid)
|
|
_ = f.SetCellValue(sheet, "E"+strconv.Itoa(row), artistInfo.Youtube)
|
|
_ = f.SetCellValue(sheet, "F"+strconv.Itoa(row), artistInfo.Instagram)
|
|
_ = f.SetCellValue(sheet, "G"+strconv.Itoa(row), artistInfo.TikTok)
|
|
}
|
|
// 保存文件
|
|
filename := "画家视频详情记录.xlsx"
|
|
fileDir := "./runtime/import/" // 自定义目录
|
|
_ = os.MkdirAll(fileDir, os.ModePerm)
|
|
filePath := filepath.Join(fileDir, filename)
|
|
if err := f.SaveAs(filePath); err != nil {
|
|
return "", err
|
|
}
|
|
excelUrl, err := upload.PutBos(filePath, "excel", true)
|
|
if err != nil {
|
|
return "", err
|
|
|
|
}
|
|
return excelUrl, nil
|
|
}
|
|
func updateApproval(ctx *gin.Context, artistId uint64, workUuid string, accountInfos []*apiCast.MediaUserInfo, videoUrl, imgUrl, title string) error {
|
|
var publishAccounts []PublishAccount
|
|
var publishPlatformIds []int32
|
|
|
|
for _, v := range accountInfos {
|
|
publishAccounts = append(publishAccounts, PublishAccount{
|
|
AccountName: v.PlatformUserName,
|
|
AccountID: v.MediaAccountUuid,
|
|
})
|
|
publishPlatformIds = append(publishPlatformIds, int32(v.PlatformID))
|
|
}
|
|
var req CreateRequest
|
|
var url string
|
|
saasPublishVideo := &SaasPublishVideo{
|
|
Title: title,
|
|
Describe: title,
|
|
IsYoutubeSee: 1,
|
|
IsTiktokScreen: 1,
|
|
IsTiktokComment: 1,
|
|
IsTiktokQuote: 1,
|
|
IsTiktokAiGenerate: 1,
|
|
Cover: imgUrl,
|
|
PlatformIds: publishPlatformIds,
|
|
PublishAccounts: publishAccounts,
|
|
Videos: []Video{
|
|
{
|
|
VideoAddress: videoUrl,
|
|
VideoThumbnail: imgUrl,
|
|
},
|
|
},
|
|
}
|
|
if config.AppMode == "prod" {
|
|
url = "https://erp.fonchain.com/approval/v2/create"
|
|
req = CreateRequest{
|
|
Type: "SaasPublishVideo",
|
|
DepartmentID: 3,
|
|
Domain: "7bfa3942cceb20389822af7b57c5798e",
|
|
MenuType: 2,
|
|
SaasPublishVideo: saasPublishVideo,
|
|
}
|
|
} else {
|
|
url = "http://114.218.158.24:9020/approval/v2/create"
|
|
req = CreateRequest{
|
|
Type: "SaasPublishVideo",
|
|
DepartmentID: 3,
|
|
Domain: "7bfa3942cceb20389822af7b57c5798e",
|
|
MenuType: 2,
|
|
SaasPublishVideo: saasPublishVideo,
|
|
}
|
|
}
|
|
jsonBytes, err := json.Marshal(req)
|
|
if err != nil {
|
|
return err
|
|
|
|
}
|
|
res, err := http.Post(url, "application/json", bytes.NewBuffer(jsonBytes))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer res.Body.Close()
|
|
responseBodyBytes, err := io.ReadAll(res.Body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var apiResp APIResponse
|
|
err = json.Unmarshal(responseBodyBytes, &apiResp)
|
|
if err != nil {
|
|
// 处理错误
|
|
return err
|
|
}
|
|
fmt.Println("拿到审批ID:", apiResp.Data.ID)
|
|
_, err = service.CastProvider.UpdateStatus(ctx, &apiCast.UpdateStatusReq{
|
|
WorkUuid: workUuid,
|
|
WorkAction: apiCast.WorkActionENUM_APPROVAL,
|
|
ApprovalID: strconv.FormatUint(apiResp.Data.ID, 10),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = service.CastProvider.UpdateStatus(ctx, &apiCast.UpdateStatusReq{
|
|
WorkUuid: workUuid,
|
|
WorkAction: apiCast.WorkActionENUM_APPROVAL_PASS,
|
|
ApprovalID: strconv.FormatUint(apiResp.Data.ID, 10),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = service.BundleProvider.AddBundleBalance(ctx, &bundle.AddBundleBalanceReq{
|
|
UserId: int32(artistId),
|
|
VideoConsumptionNumber: 1,
|
|
})
|
|
if err != nil {
|
|
log.Printf(fmt.Sprintf("扣除余额失败: %s", err.Error()))
|
|
return err
|
|
}
|
|
_, err = service.CastProvider.UpdateStatus(ctx, &apiCast.UpdateStatusReq{
|
|
WorkAction: apiCast.WorkActionENUM_CONFIRM,
|
|
WorkUuid: workUuid,
|
|
ConfirmRemark: "",
|
|
ConfirmStatus: 1,
|
|
})
|
|
if err != nil {
|
|
log.Printf(fmt.Sprintf("更新状态失败: %s", err.Error()))
|
|
return err
|
|
}
|
|
return nil
|
|
}
|