diff --git a/api/cast/cast.pb.go b/api/cast/cast.pb.go index 763f2d9..0431ea9 100644 --- a/api/cast/cast.pb.go +++ b/api/cast/cast.pb.go @@ -3016,6 +3016,7 @@ type WorkListResp_Info struct { ManagerUserNames []string `protobuf:"bytes,12,rep,name=managerUserNames,proto3" json:"managerUserNames"` ManagerUuids []string `protobuf:"bytes,13,rep,name=managerUuids,proto3" json:"managerUuids"` ApprovalID string `protobuf:"bytes,14,opt,name=approvalID,proto3" json:"approvalID"` + VideoCostType uint32 `protobuf:"varint,15,opt,name=videoCostType,proto3" json:"videoCostType"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -3148,6 +3149,13 @@ func (x *WorkListResp_Info) GetApprovalID() string { return "" } +func (x *WorkListResp_Info) GetVideoCostType() uint32 { + if x != nil { + return x.VideoCostType + } + return 0 +} + type MediaAccountsResp_Info struct { state protoimpl.MessageState `protogen:"open.v1"` PlatformID uint32 `protobuf:"varint,1,opt,name=platformID,proto3" json:"platformID"` @@ -3506,10 +3514,10 @@ const file_pb_fiee_cast_proto_rawDesc = "" + "\x0emanagerUserVal\x18\v \x01(\tR\x0emanagerUserVal\x12\x12\n" + "\x04page\x18\f \x01(\x05R\x04page\x12\x1a\n" + "\bpageSize\x18\r \x01(\x05R\bpageSize\x12,\n" + - "\x11mediaAccountUuids\x18\x0e \x03(\tR\x11mediaAccountUuids\"\xcc\x04\n" + + "\x11mediaAccountUuids\x18\x0e \x03(\tR\x11mediaAccountUuids\"\xf2\x04\n" + "\fWorkListResp\x12+\n" + "\x04data\x18\x01 \x03(\v2\x17.Cast.WorkListResp.InfoR\x04data\x12\x14\n" + - "\x05count\x18\x02 \x01(\x03R\x05count\x1a\xf8\x03\n" + + "\x05count\x18\x02 \x01(\x03R\x05count\x1a\x9e\x04\n" + "\x04Info\x12\x1a\n" + "\bworkUuid\x18\x01 \x01(\tR\bworkUuid\x12\x1e\n" + "\n" + @@ -3533,7 +3541,8 @@ const file_pb_fiee_cast_proto_rawDesc = "" + "\fmanagerUuids\x18\r \x03(\tR\fmanagerUuids\x12\x1e\n" + "\n" + "approvalID\x18\x0e \x01(\tR\n" + - "approvalID\"+\n" + + "approvalID\x12$\n" + + "\rvideoCostType\x18\x0f \x01(\rR\rvideoCostType\"+\n" + "\rWorkDetailReq\x12\x1a\n" + "\bworkUuid\x18\x01 \x01(\tR\bworkUuid\"\xfb\x01\n" + "\vWorkLogInfo\x12\x1a\n" + diff --git a/api/cast/cast.pb.validate.go b/api/cast/cast.pb.validate.go index b5289de..8e5c8e7 100644 --- a/api/cast/cast.pb.validate.go +++ b/api/cast/cast.pb.validate.go @@ -4692,6 +4692,8 @@ func (m *WorkListResp_Info) validate(all bool) error { // no validation rules for ApprovalID + // no validation rules for VideoCostType + if len(errors) > 0 { return WorkListResp_InfoMultiError(errors) } diff --git a/pkg/router/router.go b/pkg/router/router.go index f0de128..6951861 100644 --- a/pkg/router/router.go +++ b/pkg/router/router.go @@ -235,6 +235,8 @@ func NewRouter() *gin.Engine { importRoute.Use(middleware.CheckWebLogin(service.AccountProvider)) importRoute.POST("data/bind", imports.ImportBind) importRoute.POST("data/publish", imports.ImportPublish) + importRoute.POST("data/publish2", imports.ImportPublishV2) + importRoute.POST("data/publish3", imports.ImportPublishV3) importRoute.POST("data/confirm", imports.WorkConfirm) } //静态文件 diff --git a/pkg/service/import/confirm.go b/pkg/service/import/confirm.go index a01d113..48f2341 100644 --- a/pkg/service/import/confirm.go +++ b/pkg/service/import/confirm.go @@ -1,15 +1,14 @@ package imports import ( - "context" "fmt" - account "fonchain-fiee/api/accountFiee" "fonchain-fiee/api/bundle" apiCast "fonchain-fiee/api/cast" "fonchain-fiee/pkg/service" "log" "os" "path/filepath" + "strconv" "strings" "github.com/gin-gonic/gin" @@ -40,22 +39,15 @@ func WorkConfirm(c *gin.Context) { // 确认作品并扣除余量 //遍历更新状态 var failedRecords []FailedRecord for _, v := range artists { - res, err := service.AccountFieeProvider.UserList(context.Background(), &account.UserListRequest{ - Name: v.ArtistName, - SubNum: v.SubNum, - }) + artistId, err := strconv.ParseUint(v.ArtistId, 10, 32) if err != nil { failedRecords = append(failedRecords, FailedRecord{ Name: v.ArtistName, - Msg: fmt.Sprintf("获取用户信息失败: %s", err.Error()), + Msg: fmt.Sprintf("解析用户ID失败: %s", err.Error()), }) - log.Printf(fmt.Sprintf("获取用户信息失败: %s", err.Error())) + log.Printf(fmt.Sprintf("解析用户ID失败: %s", err.Error())) continue } - var artistId uint64 - if res != nil && len(res.UserList) > 0 { - artistId = res.UserList[0].Id - } _, err = service.BundleProvider.AddBundleBalance(c, &bundle.AddBundleBalanceReq{ UserId: int32(artistId), VideoConsumptionNumber: 1, @@ -73,6 +65,7 @@ func WorkConfirm(c *gin.Context) { // 确认作品并扣除余量 WorkUuid: v.WorkUuid, ConfirmRemark: "", ConfirmStatus: 1, + AutoPublish: apiCast.AutoPublishENUM_AutoPublish_FALSE, }) if err != nil { failedRecords = append(failedRecords, FailedRecord{ @@ -104,9 +97,9 @@ func readCastWorkList(excelPath string) ([]ArtistVideoDetail, error) { continue } tmp := ArtistVideoDetail{ + ArtistId: strings.TrimSpace(row[0]), ArtistName: strings.TrimSpace(row[1]), - WorkUuid: strings.TrimSpace(row[3]), - SubNum: strings.TrimSpace(row[7]), + WorkUuid: strings.TrimSpace(row[2]), } artistVideos = append(artistVideos, tmp) } diff --git a/pkg/service/import/publish.go b/pkg/service/import/publish.go index a2f4748..4a9f0a9 100644 --- a/pkg/service/import/publish.go +++ b/pkg/service/import/publish.go @@ -42,16 +42,18 @@ func ImportPublish(c *gin.Context) { os.MkdirAll(tempDir, 0755) excelPath := filepath.Join(tempDir, "artists.xlsx") zipPath := filepath.Join(tempDir, "archive.zip") - + fmt.Println("before save excel...") + now := time.Now() if err = c.SaveUploadedFile(excelFile, excelPath); err != nil { c.JSON(500, gin.H{"error": "保存 Excel 失败"}) return } + fmt.Println("save excel success", time.Since(now)) if err = c.SaveUploadedFile(zipFile, zipPath); err != nil { c.JSON(500, gin.H{"error": "保存 ZIP 文件失败"}) return } - fmt.Println("解压前...") + fmt.Println("save zip success", time.Since(now)) // 3. 解压 ZIP unzipPath := filepath.Join(tempDir, "unzipped") if _, err = os.Stat(unzipPath); err == nil { @@ -67,13 +69,11 @@ func ImportPublish(c *gin.Context) { c.JSON(500, gin.H{"error": "解压 ZIP 失败: " + err.Error()}) return } - fmt.Println("解压11111111") entries, err := os.ReadDir(unzipPath) if err != nil || len(entries) == 0 { c.JSON(500, gin.H{"error": "读取解压目录失败或目录为空"}) return } - fmt.Println("jieya后...") if len(entries) == 1 && entries[0].IsDir() { // 说明解压后多了一层目录,把它设为新的 unzipPath unzipPath = filepath.Join(unzipPath, entries[0].Name()) @@ -89,10 +89,9 @@ func ImportPublish(c *gin.Context) { // 5.发布视频 var failedRecords []FailedRecord var artistResp []ArtistVideoDetail - fmt.Println("artists: ", artists) + fmt.Println("artists num: ", len(artists)) for _, artist := range artists { var infoResp *accountFiee.UserInfoResponse - var err error list, err := service.AccountFieeProvider.UserList(context.Background(), &accountFiee.UserListRequest{ Name: artist.Name, SubNum: artist.SubNum, @@ -105,6 +104,14 @@ func ImportPublish(c *gin.Context) { log.Printf(fmt.Sprintf("获取用户信息失败: %s", err.Error())) continue } + if len(list.UserList) == 0 { + failedRecords = append(failedRecords, FailedRecord{ + Name: artist.Name, + Msg: fmt.Sprintf("未找到用户信息: %s", artist.Name), + }) + log.Printf(fmt.Sprintf("未找到用户信息: %s", artist.Name)) + continue + } if list != nil && len(list.UserList) > 0 { infoResp, err = service.AccountFieeProvider.Info(context.Background(), &accountFiee.InfoRequest{ ID: list.UserList[0].Id, @@ -130,10 +137,229 @@ func ImportPublish(c *gin.Context) { } //自媒体账号 accountList, err := service.CastProvider.MediaUserList(c, &apiCast.MediaUserListReq{ - //ArtistUuid: strconv.FormatUint(list.UserList[0].Id, 10), - ArtistVal: artist.Name, - Page: 1, - PageSize: 10, + 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, + AutoPublish: apiCast.AutoPublishENUM_AutoPublish_FALSE, + 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, + SubNum: artist.SubNum, + Title: artist.Title, + WorkUuid: resp.WorkUuid, + Youtube: artist.Youtube, + Instagram: artist.Instagram, + TikTok: artist.TikTok, + }) + } + // excelUrl, err := exportPublishRecordsToExcel(artistResp) + // if err != nil { + // service.Error(c, err) + // return + // } + + // 6. 返回结果 + service.Success(c, map[string]interface{}{ + //"excelUrl": excelUrl, + "failedRecords": failedRecords, + }) +} +func ImportPublishV2(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") + fmt.Println("before save excel...") + now := time.Now() + if err = c.SaveUploadedFile(excelFile, excelPath); err != nil { + c.JSON(500, gin.H{"error": "保存 Excel 失败"}) + return + } + fmt.Println("save excel success", time.Since(now)) + if err = c.SaveUploadedFile(zipFile, zipPath); err != nil { + c.JSON(500, gin.H{"error": "保存 ZIP 文件失败"}) + return + } + fmt.Println("save zip success", time.Since(now)) + // 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 + } + } + fmt.Println("开始解压...") + 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 + } + fmt.Println("jieya后...") + if len(entries) == 1 && entries[0].IsDir() { + // 说明解压后多了一层目录,把它设为新的 unzipPath + unzipPath = filepath.Join(unzipPath, entries[0].Name()) + } + fmt.Println("开始读取excel...") + 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 + fmt.Println("artists num: ", len(artists)) + for _, artist := range artists { + var infoResp *accountFiee.UserInfoResponse + list, err := service.AccountFieeProvider.UserList(context.Background(), &accountFiee.UserListRequest{ + Name: artist.Name, + SubNum: artist.SubNum, + }) + 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 len(list.UserList) == 0 { + failedRecords = append(failedRecords, FailedRecord{ + Name: artist.Name, + Msg: fmt.Sprintf("未找到用户信息: %s", artist.Name), + }) + log.Printf(fmt.Sprintf("未找到用户信息: %s", artist.Name)) + 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{ @@ -222,19 +448,18 @@ func ImportPublish(c *gin.Context) { TikTok: artist.TikTok, }) } - excelUrl, err := exportPublishRecordsToExcel(artistResp) - if err != nil { - service.Error(c, err) - return - } + // excelUrl, err := exportPublishRecordsToExcel(artistResp) + // if err != nil { + // service.Error(c, err) + // return + // } // 6. 返回结果 service.Success(c, map[string]interface{}{ - "excelUrl": excelUrl, + //"excelUrl": excelUrl, "failedRecords": failedRecords, }) } - func readArtistVideoInfo(excelPath, unzipPath string) ([]ArtistMedia, error) { log.Println(unzipPath) f, err := excelize.OpenFile(excelPath) @@ -298,12 +523,19 @@ func matchArtistMedia(artists []ArtistMedia, unzipPath string) ([]ArtistMedia, e var err error var res []ArtistMedia for _, artist := range artists { - oldImgPath := fmt.Sprintf("%s/%s/%s.jpg", unzipPath, artist.Name, artist.Id) + var oldVideoPath, oldImgPath string + for _, ext := range []string{".jpg", ".png", ".jpeg"} { + p := fmt.Sprintf("%s/%s/%s%s", unzipPath, artist.Name, artist.Id, ext) + if _, err = os.Stat(p); err == nil { + oldImgPath = p + break + } + } // 检查源文件是否存在 if _, err = os.Stat(oldImgPath); os.IsNotExist(err) { + fmt.Println("图片不存在: ", artist.Id, artist.Name, oldImgPath) continue } - var oldVideoPath string for _, ext := range []string{".mp4", ".mov"} { p := fmt.Sprintf("%s/%s/%s%s", unzipPath, artist.Name, artist.Id, ext) if _, err = os.Stat(p); err == nil { @@ -312,6 +544,7 @@ func matchArtistMedia(artists []ArtistMedia, unzipPath string) ([]ArtistMedia, e } } if oldVideoPath == "" { + fmt.Println("视频不存在: ", artist.Id, artist.Name, oldVideoPath) continue } baseDir := filepath.Join(unzipPath, artist.Name) @@ -369,6 +602,7 @@ func matchArtistMedia(artists []ArtistMedia, unzipPath string) ([]ArtistMedia, e } 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{ @@ -402,71 +636,348 @@ func UploadToAnotherService(ctx context.Context, fileData []byte, path string) e } return nil } -func exportPublishRecordsToExcel(artistInfos []ArtistVideoDetail) (string, error) { - fileDir := "./runtime/import/" - filename := "画家视频详情记录0922.xlsx" - filePath := filepath.Join(fileDir, filename) - _ = os.MkdirAll(fileDir, os.ModePerm) +// func exportPublishRecordsToExcel(artistInfos []ArtistVideoDetail) (string, error) { +// fileDir := "./runtime/import/" +// filename := "画家视频详情记录0922.xlsx" +// filePath := filepath.Join(fileDir, filename) - var f *excelize.File - sheet := "Sheet1" +// _ = os.MkdirAll(fileDir, os.ModePerm) - // 判断文件是否存在 - if _, err := os.Stat(filePath); os.IsNotExist(err) { - // 文件不存在,新建文件和Sheet - f = excelize.NewFile() - f.SetSheetName(f.GetSheetName(0), sheet) +// var f *excelize.File +// sheet := "Sheet1" - // 写表头 - headers := []string{"序号", "画家名", "标题", "uuid", "youtube", "instagram", "tiktok", "用户编号"} - for col, h := range headers { - _ = f.SetCellValue(sheet, string('A'+col)+"1", h) - } - } else { - // 文件存在,打开 - var err error - f, err = excelize.OpenFile(filePath) +// // 判断文件是否存在 +// if _, err := os.Stat(filePath); os.IsNotExist(err) { +// // 文件不存在,新建文件和Sheet +// f = excelize.NewFile() +// f.SetSheetName(f.GetSheetName(0), sheet) + +// // 写表头 +// headers := []string{"序号", "画家名", "标题", "uuid", "youtube", "instagram", "tiktok", "用户编号"} +// for col, h := range headers { +// _ = f.SetCellValue(sheet, string('A'+col)+"1", h) +// } +// } else { +// // 文件存在,打开 +// var err error +// f, err = excelize.OpenFile(filePath) +// if err != nil { +// return "", err +// } +// } + +// // 找到最后一行,追加数据 +// rows, err := f.GetRows(sheet) +// if err != nil { +// return "", err +// } + +// // 计算下一行,从表头之后开始 +// startRow := len(rows) + 1 +// if startRow == 1 { +// startRow = 2 // 文件新建或没有数据,从第2行开始 +// } + +// // 写数据 +// for i, artistInfo := range artistInfos { +// row := startRow + i +// _ = f.SetCellValue(sheet, "A"+strconv.Itoa(row), row-1) // 序号连续 +// _ = 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) +// _ = f.SetCellValue(sheet, "H"+strconv.Itoa(row), artistInfo.SubNum) +// } + +// // 保存文件 +// if err = f.SaveAs(filePath); err != nil { +// fmt.Println("saveAs err: ", err) +// return "", err +// } + +// // 上传 +// excelUrl, err := upload.PutBos(filePath, "excel", false) +// if err != nil { +// return "", err +// } +// return excelUrl, nil +// } + +func ImportPublishV3(c *gin.Context) { + // 1. 上传画家短视频详情文件 + excelFile, err := c.FormFile("excel") + if err != nil { + c.JSON(400, gin.H{"error": "缺少 Excel 文件 excel"}) + return + } + // 2. 保存临时文件 + tempDir := "tmp" + os.MkdirAll(tempDir, 0755) + excelPath := filepath.Join(tempDir, "artists.xlsx") + + if err = c.SaveUploadedFile(excelFile, excelPath); err != nil { + c.JSON(500, gin.H{"error": "保存 Excel 失败"}) + return + } + fmt.Println("save excel...") + defer os.RemoveAll(tempDir) + + // 3. 读取 Excel 画家名单, 匹配视频和图片 + artists, err := readArtistVideoInfoV2(c, excelPath) + if err != nil { + c.JSON(500, gin.H{"error": "读取 Excel 失败"}) + return + } + // 4.发布视频 + var failedRecords []FailedRecord + //var artistResp []ArtistVideoDetail + for _, artist := range artists { + var infoResp *accountFiee.UserInfoResponse + list, err := service.AccountFieeProvider.UserList(context.Background(), &accountFiee.UserListRequest{ + Name: artist.Name, + SubNum: artist.SubNum, + }) if err != nil { - return "", err + failedRecords = append(failedRecords, FailedRecord{ + Name: artist.Name, + Msg: fmt.Sprintf("获取用户信息失败: %s", err.Error()), + }) + log.Printf(fmt.Sprintf("获取用户信息失败: %s", err.Error())) + continue + } + if len(list.UserList) == 0 { + failedRecords = append(failedRecords, FailedRecord{ + Name: artist.Name, + Msg: fmt.Sprintf("未找到用户信息: %s", artist.Name), + }) + log.Printf(fmt.Sprintf("未找到用户信息: %s", artist.Name)) + 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 + } } - } - // 找到最后一行,追加数据 - rows, err := f.GetRows(sheet) - if err != nil { - return "", err + 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 { + mediaAccountUuids = append(mediaAccountUuids, info.MediaAccountUuid) + mediaAccountNames = append(mediaAccountNames, info.PlatformUserName) + platformIDs = append(platformIDs, apiCast.PlatformIDENUM(info.PlatformID)) + } + newCtx := cast.NewCtxWithUserInfo(c) + _, 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, + // SubNum: artist.SubNum, + // Title: artist.Title, + // WorkUuid: resp.WorkUuid, + // Youtube: artist.Youtube, + // Instagram: artist.Instagram, + // TikTok: artist.TikTok, + // }) } + // excelUrl, err := exportPublishRecordsToExcel(artistResp) + // if err != nil { + // service.Error(c, err) + // return + // } - // 计算下一行,从表头之后开始 - startRow := len(rows) + 1 - if startRow == 1 { - startRow = 2 // 文件新建或没有数据,从第2行开始 - } - - // 写数据 - for i, artistInfo := range artistInfos { - row := startRow + i - _ = f.SetCellValue(sheet, "A"+strconv.Itoa(row), row-1) // 序号连续 - _ = 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) - _ = f.SetCellValue(sheet, "H"+strconv.Itoa(row), artistInfo.SubNum) - } - - // 保存文件 - if err = f.SaveAs(filePath); err != nil { - fmt.Println("saveAs err: ", err) - return "", err - } - - // 上传 - excelUrl, err := upload.PutBos(filePath, "excel", false) - if err != nil { - return "", err - } - return excelUrl, nil + // 6. 返回结果 + service.Success(c, map[string]interface{}{ + //"excelUrl": excelUrl, + "failedRecords": failedRecords, + }) +} +func readArtistVideoInfoV2(ctx *gin.Context, excelPath string) ([]ArtistMedia, error) { + 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 + } + 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) + } + subNum, _ := f.GetCellValue(sheetName, fmt.Sprintf("G%d", i+1)) + if subNum != "" { + subNum = strings.TrimSpace(subNum) + } + artists = append(artists, ArtistMedia{ + Id: id, + Name: artistName, + Title: title, + SubNum: subNum, + }) + } + artists, err = matchArtistMediaV3(ctx, artists) + return artists, nil +} + +func matchArtistMediaV3(ctx *gin.Context, artists []ArtistMedia) ([]ArtistMedia, error) { + var medias = make(map[string][]*files.Items) + var res []ArtistMedia + for _, artist := range artists { + mediaPath := "fiee2/" + artist.Name + imgPath := fmt.Sprintf("%s/%s/%s.jpg", mediaPath, artist.Name, artist.Title) + videoPath := fmt.Sprintf("%s/%s/%s.mp4", mediaPath, artist.Name, artist.Title) + //判断文件是否存在 + if _, ok := medias[artist.Name]; !ok { + sortBy := ctx.DefaultQuery("sortBy", "name") + sortAsc, _ := strconv.ParseBool(ctx.DefaultQuery("sortAsc", "true")) + resp, err := service.FilesProvider.List(ctx, &files.FileListReq{ + Path: mediaPath, + UserSpacePath: "", + Sorting: &files.Sorting{ + By: sortBy, + Asc: sortAsc, + }, + }) + if err != nil { + return nil, err + } + medias[artist.Name] = resp.Items + } + isExist := false + for _, media := range medias[artist.Name] { + if media.Name == artist.Title+".jpg" || media.Name == artist.Title+".mp4" || media.Name == artist.Title+".mov" { + isExist = true + break + } + } + if !isExist { + continue + } + var httpType string + if config.AppConfig.System.AppMode == "dev" { + url := "114.218.158.24:9020" + httpType = fmt.Sprintf("%s%s", model.HttpType, url) + } else { + url := "saas.fiee.com" + httpType = fmt.Sprintf("%s%s", model.HttpsType, url) + } + baseUrl := fmt.Sprintf("%s/api/fiee/resource/raw/", httpType) + videoUrl := baseUrl + filepath.Base(videoPath) + imgUrl := baseUrl + filepath.Base(imgPath) + tmp := artist + tmp.Id = artist.Id + tmp.Name = artist.Name + tmp.Title = artist.Title + tmp.Img = imgUrl + tmp.Video = videoUrl + tmp.SubNum = artist.SubNum + res = append(res, tmp) + } + return res, nil }