package cast import ( "context" "encoding/json" "errors" "fmt" "fonchain-fiee/api/aryshare" "fonchain-fiee/api/bundle" "fonchain-fiee/api/cast" "fonchain-fiee/pkg/cache" "fonchain-fiee/pkg/e" modelCast "fonchain-fiee/pkg/model/cast" "fonchain-fiee/pkg/model/login" "fonchain-fiee/pkg/service" "fonchain-fiee/pkg/utils" "strconv" "time" "dubbo.apache.org/dubbo-go/v3/common/constant" "github.com/gin-gonic/gin" "github.com/google/uuid" "go.uber.org/zap" ) // CreateWorkAnalysis 创建作品分析 func CreateWorkAnalysis(ctx *gin.Context) { var req *cast.CreateWorkAnalysisReq var err error if err = ctx.ShouldBind(&req); err != nil { service.Error(ctx, err) return } newCtx := NewCtxWithUserInfo(ctx) artistID, _ := strconv.ParseUint(req.ArtistID, 10, 64) if err = CheckUserBundleBalance(int32(artistID), modelCast.BalanceTypeDataValue); err != nil { service.Error(ctx, err) return } resp, err := service.CastProvider.CreateWorkAnalysis(newCtx, req) if err != nil { service.Error(ctx, err) return } service.Success(ctx, resp) return } // UpdateWorkAnalysis 更新作品分析 func UpdateWorkAnalysis(ctx *gin.Context) { var req *cast.UpdateWorkAnalysisReq var err error if err = ctx.ShouldBind(&req); err != nil { service.Error(ctx, err) return } newCtx := NewCtxWithUserInfo(ctx) _, err = service.CastProvider.UpdateWorkAnalysis(newCtx, req) if err != nil { service.Error(ctx, err) return } service.Success(ctx, nil) return } // UpdateWorkAnalysisStatus 更新作品分析状态 func UpdateWorkAnalysisStatus(ctx *gin.Context) { var req *cast.UpdateWorkAnalysisStatusReq var err error if err = ctx.ShouldBind(&req); err != nil { service.Error(ctx, err) return } newCtx := NewCtxWithUserInfo(ctx) _, err = service.CastProvider.UpdateWorkAnalysisStatus(newCtx, req) if err != nil { service.Error(ctx, err) return } service.Success(ctx, nil) return } // GetWorkAnalysis 获取作品分析详情 func GetWorkAnalysis(ctx *gin.Context) { var req *cast.GetWorkAnalysisDetailReq var err error if err = ctx.ShouldBind(&req); err != nil { service.Error(ctx, err) return } newCtx := NewCtxWithUserInfo(ctx) resp, err := service.CastProvider.GetWorkAnalysis(newCtx, req) if err != nil { service.Error(ctx, err) return } service.Success(ctx, resp) return } // GetLatestWorkAnalysis 获取最新作品分析 func GetLatestWorkAnalysis(ctx *gin.Context) { var req *cast.GetLatestWorkAnalysisReq var err error if err = ctx.ShouldBind(&req); err != nil { service.Error(ctx, err) return } newCtx := NewCtxWithUserInfo(ctx) resp, err := service.CastProvider.GetLatestWorkAnalysis(newCtx, req) if err != nil { service.Error(ctx, err) return } service.Success(ctx, resp) return } // ListWorkAnalysis 获取作品分析列表 func ListWorkAnalysis(ctx *gin.Context) { var req *cast.ListWorkAnalysisReq var err error if err = ctx.ShouldBind(&req); err != nil { service.Error(ctx, err) return } newCtx := NewCtxWithUserInfo(ctx) resp, err := service.CastProvider.ListWorkAnalysis(newCtx, req) if err != nil { service.Error(ctx, err) return } RefreshWorkAnalysisApproval(ctx, resp.Data) service.Success(ctx, resp) return } // RefreshWorkAnalysisApproval 刷新作品分析审批状态 func RefreshWorkAnalysisApproval(ctx *gin.Context, data []*cast.WorkAnalysisInfo) { if len(data) > 0 { var analysisUuidApprovalIDMap = make(map[int]string) for _, v := range data { // 状态为2表示待审批,需要获取详情来获取ApprovalID if v.WorkAnalysisStatus == 2 && v.ApprovalID != "" { zap.L().Info("RefreshWorkAnalysisApproval", zap.Any("approvalID", v.ApprovalID)) approvalID, _ := strconv.ParseUint(v.ApprovalID, 10, 64) analysisUuidApprovalIDMap[int(approvalID)] = v.Uuid } } if len(analysisUuidApprovalIDMap) > 0 { _ = RefreshWorkAnalysisApprovalStatus(ctx, analysisUuidApprovalIDMap) } } } // RefreshWorkAnalysisApprovalStatus 刷新作品分析审批状态详情 func RefreshWorkAnalysisApprovalStatus(ctx *gin.Context, approvalIDAnalysisUuidMap map[int]string) (err error) { var castS = new(CastService) var data = make(map[int]modelCast.Item) var approvalIDs []int for approvalId := range approvalIDAnalysisUuidMap { approvalIDs = append(approvalIDs, approvalId) } if len(approvalIDs) == 0 { return } data, err = castS.ApprovalDetail(approvalIDs) if err != nil { return } // status: 1待审批 2审批通过 3审批不通过 6撤销发其中 7撤销完成 var newData = make(map[int]modelCast.Item, len(approvalIDs)) for _, v := range approvalIDs { newData[v] = data[v] } newCtx := NewCtxWithUserInfo(ctx) if len(newData) > 0 { for approvalId, v := range newData { if v.ID == 0 { _, _ = service.CastProvider.UpdateWorkAnalysisStatus(newCtx, &cast.UpdateWorkAnalysisStatusReq{ WorkAction: cast.WorkActionENUM_APPROVAL_DELETE, Uuid: approvalIDAnalysisUuidMap[approvalId], ApprovalID: fmt.Sprint(approvalId), ApprovalReply: "", }) continue } var workAction cast.WorkActionENUM if v.Status == 2 { workAction = cast.WorkActionENUM_APPROVAL_PASS } else if v.Status == 3 { workAction = cast.WorkActionENUM_APPROVAL_REJECT } else { continue } _, _ = service.CastProvider.UpdateWorkAnalysisStatus(newCtx, &cast.UpdateWorkAnalysisStatusReq{ WorkAction: workAction, Uuid: approvalIDAnalysisUuidMap[approvalId], ApprovalID: fmt.Sprint(approvalId), ApprovalReply: v.Reply, }) } } return } // DeleteWorkAnalysis 删除作品分析 func DeleteWorkAnalysis(ctx *gin.Context) { var req *cast.DeleteWorkAnalysisReq var err error if err = ctx.ShouldBind(&req); err != nil { service.Error(ctx, err) return } newCtx := NewCtxWithUserInfo(ctx) _, err = service.CastProvider.DeleteWorkAnalysis(newCtx, req) if err != nil { service.Error(ctx, err) return } service.Success(ctx, nil) return } // getWorkAnalysisStatusText 获取作品分析状态文本 func getWorkAnalysisStatusText(status uint32) string { switch status { case 1: return "待提交" case 2: return "审批中" case 3: return "审批驳回" case 4: return "待艺人验收" case 5: return "验收失败" case 6: return "待阅读" case 7: return "已阅读" default: return "" } } // getComfirmTypeText 获取确认类型文本 func getComfirmTypeText(comfirmType int32) string { switch comfirmType { case 1: return "艺人确认" case 2: return "系统确认" default: return "" } } // ListWorkAnalysisExport 获取作品分析列表并导出Excel func ListWorkAnalysisExport(ctx *gin.Context) { var req *cast.ListWorkAnalysisReq var err error if err = ctx.ShouldBind(&req); err != nil { service.Error(ctx, err) return } req.Page = 1 req.PageSize = 999999999 newCtx := NewCtxWithUserInfo(ctx) resp, err := service.CastProvider.ListWorkAnalysis(newCtx, req) if err != nil { service.Error(ctx, err) return } titleList := []string{ "艺人", "用户编号", "手机号", "状态", "验收确认类型", "原因", "状态变更时间", "提交时间", "操作人", "pdf链接", } var dataList []interface{} for _, i := range resp.Data { data := []any{ i.ArtistName, i.SubNum, i.ArtistPhone, getWorkAnalysisStatusText(i.WorkAnalysisStatus), getComfirmTypeText(i.ComfirmType), i.Reason, i.StatusUpdateTime, i.SubmitTime, i.OperatorName, i.PdfUrl, } dataList = append(dataList, &data) } content, err := utils.ToExcelByType(titleList, dataList, "slice", "") if err != nil { service.Error(ctx, err) return } utils.ResponseXls(ctx, content, "数据分析列表") return } // ListWorkAnalysisSingleExport 获取作品分析列表并导出Excel func ListWorkAnalysisSingleExport(ctx *gin.Context) { var req *cast.ListWorkAnalysisReq var err error if err = ctx.ShouldBind(&req); err != nil { service.Error(ctx, err) return } req.Page = 1 req.PageSize = 999999999 newCtx := NewCtxWithUserInfo(ctx) resp, err := service.CastProvider.ListWorkAnalysis(newCtx, req) if err != nil { service.Error(ctx, err) return } titleList := []string{ "状态", "验收确认类型", "原因", "状态变更时间", "操作人", "提交时间", "pdf链接", } var dataList []interface{} for _, i := range resp.Data { data := []any{ getWorkAnalysisStatusText(i.WorkAnalysisStatus), getComfirmTypeText(i.ComfirmType), i.Reason, i.StatusUpdateTime, i.OperatorName, i.SubmitTime, i.PdfUrl, } dataList = append(dataList, &data) } content, err := utils.ToExcelByType(titleList, dataList, "slice", "") if err != nil { service.Error(ctx, err) return } utils.ResponseXls(ctx, content, "数据分析列表") return } // ArtistDataList 艺人数据列表 func ArtistDataList(ctx *gin.Context) { var req *cast.ArtistDataListReq var err error if err = ctx.ShouldBind(&req); err != nil { service.Error(ctx, err) return } newCtx := NewCtxWithUserInfo(ctx) resp, err := service.CastProvider.ArtistDataList(newCtx, req) if err != nil { service.Error(ctx, err) return } service.Success(ctx, resp) return } // MediaDataList 自媒体数据列表 func MediaDataList(ctx *gin.Context) { var req *cast.MediaDataListReq var err error if err = ctx.ShouldBind(&req); err != nil { service.Error(ctx, err) return } newCtx := NewCtxWithUserInfo(ctx) resp, err := service.CastProvider.MediaDataList(newCtx, req) if err != nil { service.Error(ctx, err) return } service.Success(ctx, resp) return } // DataOverview 数据概览 func DataOverview(ctx *gin.Context) { var req *cast.DataOverviewReq var err error if err = ctx.ShouldBind(&req); err != nil { service.Error(ctx, err) return } newCtx := NewCtxWithUserInfo(ctx) resp, err := service.CastProvider.DataOverview(newCtx, req) if err != nil { service.Error(ctx, err) return } service.Success(ctx, resp) return } // ArtistMetricsSeries 艺人指标系列 func ArtistMetricsSeries(ctx *gin.Context) { var req *cast.ArtistMetricsSeriesReq var err error if err = ctx.ShouldBind(&req); err != nil { service.Error(ctx, err) return } newCtx := NewCtxWithUserInfo(ctx) resp, err := service.CastProvider.ArtistMetricsSeries(newCtx, req) if err != nil { service.Error(ctx, err) return } service.Success(ctx, resp) return } // ArtistMetricsDailyWindow 艺人指标日窗口 func ArtistMetricsDailyWindow(ctx *gin.Context) { var req *cast.ArtistMetricsDailyWindowReq var err error if err = ctx.ShouldBind(&req); err != nil { service.Error(ctx, err) return } newCtx := NewCtxWithUserInfo(ctx) resp, err := service.CastProvider.ArtistMetricsDailyWindow(newCtx, req) if err != nil { service.Error(ctx, err) return } service.Success(ctx, resp) return } // TobeConfirmedList 待确认数据列表 func TobeConfirmedList(ctx *gin.Context) { var req *cast.TobeConfirmedListReq var err error if err = ctx.ShouldBind(&req); err != nil { service.Error(ctx, err) return } userInfo := login.GetUserInfoFromC(ctx) req.ArtistUuid = strconv.Itoa(int(userInfo.ID)) newCtx := NewCtxWithUserInfo(ctx) resp, err := service.CastProvider.TobeConfirmedList(newCtx, req) if err != nil { service.Error(ctx, err) return } service.Success(ctx, resp) return } // UpdateWorkAnalysisApprovalID 更新作品分析审批ID func UpdateWorkAnalysisApprovalID(ctx *gin.Context) { var req *cast.UpdateWorkAnalysisApprovalIDReq var err error if err = ctx.ShouldBind(&req); err != nil { service.Error(ctx, err) return } newCtx := NewCtxWithUserInfo(ctx) _, err = service.CastProvider.UpdateWorkAnalysisApprovalID(newCtx, req) if err != nil { service.Error(ctx, err) return } service.Success(ctx, nil) return } type CheckBundleBalanceReq struct { ArtistID string `protobuf:"bytes,4,opt,name=artistID,proto3" json:"artistID"` // 艺人ID BalanceType modelCast.BalanceTypeEnum `json:"balanceType"` // 套餐类型 } // CheckBundleBalance 检查套餐余量 func CheckBundleBalance(ctx *gin.Context) { var req *CheckBundleBalanceReq var err error if err = ctx.ShouldBind(&req); err != nil { service.Error(ctx, err) return } artistID, err := strconv.ParseUint(req.ArtistID, 10, 64) if err != nil { service.Error(ctx, err) return } zap.L().Info("CheckUserBundleBalance", zap.Any("userID", artistID), zap.Any("balanceType", req.BalanceType)) newCtx := NewCtxWithUserInfo(ctx) resp, err := service.BundleProvider.GetBundleBalanceByUserId(newCtx, &bundle.GetBundleBalanceByUserIdReq{UserId: int32(artistID)}) if err != nil { zap.L().Error("CheckUserBundleBalance", zap.Any("err", err)) service.Error(ctx, err) return } zap.L().Info("CheckUserBundleBalance", zap.Any("resp", resp)) switch req.BalanceType { case modelCast.BalanceTypeAccountValue: if resp.AccountNumber-resp.AccountConsumptionNumber <= 0 { err = errors.New(e.ErrorBalanceInsufficient) service.Error(ctx, err) return } case modelCast.BalanceTypeImageValue: if resp.ImageNumber-resp.ImageConsumptionNumber <= 0 { err = errors.New(e.ErrorBalanceInsufficient) service.Error(ctx, err) return } case modelCast.BalanceTypeVideoValue: if resp.VideoNumber-resp.VideoConsumptionNumber <= 0 { err = errors.New(e.ErrorBalanceInsufficient) service.Error(ctx, err) return } case modelCast.BalanceTypeDataValue: if resp.DataAnalysisExtendNumber-resp.DataAnalysisExtendConsumptionNumber <= 0 { err = errors.New(e.ErrorBalanceInsufficient) service.Error(ctx, err) return } } service.Success(ctx, resp) return } // ProcessAnalysisTask 处理数据分析自动确认任务 func ProcessAnalysisTask(ctx context.Context, analysisUuid string) { lockKey := fmt.Sprintf(modelCast.AutoConfirmAnalysisLockKey, analysisUuid) reply := cache.RedisClient.SetNX(lockKey, "1", 5*time.Minute) if !reply.Val() { zap.L().Warn("数据分析任务正在被其他实例处理", zap.String("analysisUuid", analysisUuid)) return } defer func() { cache.RedisClient.Del(lockKey) }() err := autoConfirmAnalysis(ctx, analysisUuid) if err != nil { zap.L().Error("数据分析自动确认失败", zap.String("analysisUuid", analysisUuid), zap.Error(err)) return } // 从队列中移除 args := make([]interface{}, len(analysisUuid)) for i, m := range analysisUuid { args[i] = m } err = cache.RedisClient.ZRem(modelCast.AutoConfirmAnalysisQueueKey, args...).Err() if err != nil { zap.L().Error("从队列移除数据分析任务失败", zap.String("analysisUuid", analysisUuid), zap.Error(err)) } zap.L().Info("数据分析自动确认成功", zap.String("analysisUuid", analysisUuid)) } // autoConfirmAnalysis 自动确认数据分析 func autoConfirmAnalysis(ctx context.Context, analysisUuid string) (err error) { var confirmRemark string var isFailed bool var usedType uint32 infoResp, err := service.CastProvider.GetWorkAnalysis(context.Background(), &cast.GetWorkAnalysisDetailReq{ Uuid: analysisUuid, }) if err != nil { zap.L().Error("autoConfirmAnalysis GetWorkAnalysis", zap.Any("err", err)) confirmRemark = "获取数据分析详情失败:" + err.Error() isFailed = true } if infoResp.WorkAnalysisStatus != 4 { // 4是待确认状态,需要根据实际情况调整 return } userID, _ := strconv.ParseInt(infoResp.ArtistID, 10, 64) balanceInfoRes, err := service.BundleProvider.GetBundleBalanceByUserId(context.Background(), &bundle.GetBundleBalanceByUserIdReq{ UserId: int32(userID), }) if err != nil { zap.L().Error("autoConfirmAnalysis GetBundleBalanceByUserId", zap.Any("err", err)) confirmRemark = "获取余额失败:" + err.Error() isFailed = true } var addBalanceReq bundle.AddBundleBalanceReq addBalanceReq.UserId = int32(userID) // 检查数据分析余量 if balanceInfoRes.DataAnalysisExtendConsumptionNumber >= balanceInfoRes.DataAnalysisExtendNumber { confirmRemark = "数据分析余量不足" isFailed = true } addBalanceReq.DataAnalysisConsumptionNumber = 1 zap.L().Info("autoConfirmAnalysis AddBundleBalanceReq", zap.Any("addBalanceReq", &addBalanceReq)) resp, err := service.BundleProvider.AddBundleBalance(context.Background(), &addBalanceReq) if err != nil { zap.L().Error("autoConfirmAnalysis AddBundleBalance", zap.Any("err", err)) confirmRemark = "扣除失败:" + err.Error() isFailed = true } zap.L().Info("autoConfirmAnalysis AddBundleBalanceResp", zap.Any("resp", resp)) var confirmStatus uint32 = 1 if isFailed { usedType = 0 confirmStatus = 3 } else { usedType = resp.UsedType confirmRemark = "系统自动确认" confirmStatus = 1 } var mm = make(map[string]interface{}, 3) mm["userid"] = 0 mm["name"] = "系统自动确定" mm["phone"] = "" newCtx := context.WithValue(context.Background(), constant.DubboCtxKey("attachment"), mm) _, err = service.CastProvider.UpdateWorkAnalysisStatus(newCtx, &cast.UpdateWorkAnalysisStatusReq{ WorkAction: cast.WorkActionENUM_CONFIRM, Uuid: analysisUuid, ConfirmRemark: confirmRemark, CostType: usedType, ConfirmStatus: confirmStatus, ConfirmType: 2, }) if err != nil { return } return } // TriggerAyrshareMetricsCollector 手动触发 Ayrshare 指标采集任务 func TriggerAyrshareMetricsCollector(ctx *gin.Context) { // 在后台 goroutine 中执行任务,避免阻塞 HTTP 请求 go func() { // 同时使用 zap 和 fmt.Printf 确保日志输出到终端 zap.L().Info("手动触发 Ayrshare 指标采集任务") fmt.Printf("[%s] 手动触发 Ayrshare 指标采集任务\n", time.Now().Format("2006-01-02 15:04:05")) executeAyrshareMetricsCollector() }() service.Success(ctx, map[string]string{ "message": "任务已触发,正在后台执行", }) return } // ExecuteAyrshareMetricsCollector 提供给定时任务调用的 Ayrshare 指标采集入口 func ExecuteAyrshareMetricsCollector() { executeAyrshareMetricsCollector() } // executeAyrshareMetricsCollector 执行 Ayrshare 指标采集任务 func executeAyrshareMetricsCollector() { ctx := context.Background() zap.L().Info("开始执行 Ayrshare 指标采集任务") fmt.Printf("[%s] [INFO] 开始执行 Ayrshare 指标采集任务\n", time.Now().Format("2006-01-02 15:04:05")) // 获取当前日期(中国时区,格式:YYYYMMDD) loc, _ := time.LoadLocation("Asia/Shanghai") now := time.Now().In(loc) dateCN := now.Year()*10000 + int(now.Month())*100 + now.Day() // 第一步:获取所有状态为1的艺人AyrShare信息 mediaMetricsList, err := collectMediaMetricsForAyrshare(ctx, dateCN) if err != nil { zap.L().Error("采集账号指标失败", zap.Error(err)) fmt.Printf("[%s] [ERROR] 采集账号指标失败: %v\n", time.Now().Format("2006-01-02 15:04:05"), err) return } // 批量插入账号指标 if len(mediaMetricsList) > 0 { req := &cast.UpsertMediaMetricsDailyBatchReq{ Data: mediaMetricsList, } resp, err := service.CastProvider.UpsertMediaMetricsDailyBatch(ctx, req) if err != nil { zap.L().Error("批量插入账号指标失败", zap.Error(err)) fmt.Printf("[%s] [ERROR] 批量插入账号指标失败: %v\n", time.Now().Format("2006-01-02 15:04:05"), err) } else if resp != nil && resp.Success { zap.L().Info("批量插入账号指标成功", zap.Int("count", len(mediaMetricsList))) fmt.Printf("[%s] [INFO] 批量插入账号指标成功,数量: %d\n", time.Now().Format("2006-01-02 15:04:05"), len(mediaMetricsList)) } } // 第二步:获取作品指标 workMetricsList, err := collectWorkMetricsForAyrshare(ctx, dateCN) if err != nil { zap.L().Error("采集作品指标失败", zap.Error(err)) fmt.Printf("[%s] [ERROR] 采集作品指标失败: %v\n", time.Now().Format("2006-01-02 15:04:05"), err) return } // 批量插入作品指标 if len(workMetricsList) > 0 { req := &cast.UpsertWorkMetricsDailyBatchReq{ Data: workMetricsList, } resp, err := service.CastProvider.UpsertWorkMetricsDailyBatch(ctx, req) if err != nil { zap.L().Error("批量插入作品指标失败", zap.Error(err)) fmt.Printf("[%s] [ERROR] 批量插入作品指标失败: %v\n", time.Now().Format("2006-01-02 15:04:05"), err) } else if resp != nil && resp.Success { zap.L().Info("批量插入作品指标成功", zap.Int("count", len(workMetricsList))) fmt.Printf("[%s] [INFO] 批量插入作品指标成功,数量: %d\n", time.Now().Format("2006-01-02 15:04:05"), len(workMetricsList)) } } zap.L().Info("Ayrshare 指标采集任务执行完成") fmt.Printf("[%s] [INFO] Ayrshare 指标采集任务执行完成\n", time.Now().Format("2006-01-02 15:04:05")) } // collectMediaMetricsForAyrshare 采集账号指标 func collectMediaMetricsForAyrshare(ctx context.Context, dateCN int) ([]*cast.MediaMetricsDailyItem, error) { metricsList := make([]*cast.MediaMetricsDailyItem, 0) page := int32(1) pageSize := int32(500) fmt.Printf("[%s] [INFO] 开始采集账号指标\n", time.Now().Format("2006-01-02 15:04:05")) for { // 获取状态为1的艺人AyrShare信息 req := &cast.GetArtistAyrShareInfoReq{ Status: 1, // 状态为1表示有效 Page: page, PageSize: pageSize, } resp, err := service.CastProvider.GetArtistAyrShareInfo(ctx, req) if err != nil { zap.L().Error("获取艺人AyrShare信息失败", zap.Error(err), zap.Int32("page", page)) fmt.Printf("[%s] [ERROR] 获取艺人AyrShare信息失败,页码: %d, 错误: %v\n", time.Now().Format("2006-01-02 15:04:05"), page, err) return metricsList, err } if resp == nil || resp.Data == nil || len(resp.Data) == 0 { break } fmt.Printf("[%s] [INFO] 获取到第 %d 页艺人信息,数量: %d\n", time.Now().Format("2006-01-02 15:04:05"), page, len(resp.Data)) // 对每个艺人调用 GetSocialAnalytics for _, artistInfo := range resp.Data { if artistInfo.ProfileKey == "" { zap.L().Warn("艺人ProfileKey为空,跳过", zap.String("artistUuid", artistInfo.ArtistUuid)) fmt.Printf("[%s] [WARN] 艺人ProfileKey为空,跳过,artistUuid: %s\n", time.Now().Format("2006-01-02 15:04:05"), artistInfo.ArtistUuid) continue } // 调用 GetSocialAnalytics,平台为 ["instagram", "tiktok"] socialReq := &aryshare.GetSocialAnalyticsRequest{ Platforms: []string{"instagram", "tiktok"}, ProfileKey: artistInfo.ProfileKey, } socialResp, err := service.AyrshareProvider.GetSocialAnalytics(ctx, socialReq) if err != nil { zap.L().Warn("获取社交分析数据失败", zap.Error(err), zap.String("profileKey", artistInfo.ProfileKey)) fmt.Printf("[%s] [WARN] 获取社交分析数据失败,profileKey: %s, 错误: %v\n", time.Now().Format("2006-01-02 15:04:05"), artistInfo.ProfileKey, err) continue } fmt.Println("socialResp", socialResp) // 解析 JSON 数据并构建指标 items := parseSocialAnalyticsToMediaMetricsForAyrshare(socialResp, artistInfo, dateCN) metricsList = append(metricsList, items...) // 避免请求过于频繁 time.Sleep(200 * time.Millisecond) } // 如果返回的数据少于 pageSize,说明已经是最后一页 if len(resp.Data) < int(pageSize) { break } page++ } fmt.Printf("[%s] [INFO] 账号指标采集完成,共采集 %d 条\n", time.Now().Format("2006-01-02 15:04:05"), len(metricsList)) fmt.Println("--------------------------------") fmt.Println("账号的指标数据") fmt.Println("metricsList", metricsList) fmt.Println("--------------------------------") return metricsList, nil } // parseSocialAnalyticsToMediaMetricsForAyrshare 解析社交分析数据并转换为媒体指标 func parseSocialAnalyticsToMediaMetricsForAyrshare(socialResp *aryshare.GetSocialAnalyticsResponse, artistInfo *cast.ArtistAyrShareInfo, dateCN int) []*cast.MediaMetricsDailyItem { items := make([]*cast.MediaMetricsDailyItem, 0) ctx := context.Background() // 解析 Instagram 数据 if socialResp.Instagram != "" { // 调试:打印 Instagram JSON 字符串的前500字符 instaPreview := socialResp.Instagram if len(instaPreview) > 500 { instaPreview = instaPreview[:500] + "..." } fmt.Printf("[%s] [DEBUG] Instagram JSON 字符串预览: %s\n", time.Now().Format("2006-01-02 15:04:05"), instaPreview) item := parsePlatformDataForAyrshare(socialResp.Instagram, "instagram", artistInfo, dateCN) if item != nil { // 获取 Instagram 平台的用户信息 mediaInfoReq := &cast.MediaInfoByPlatformReq{ ArtistUuid: artistInfo.ArtistUuid, PlatformID: cast.PlatformIDENUM_INS, // Instagram 平台 ID 为 3 } mediaInfoResp, err := service.CastProvider.MediaInfoByPlatform(ctx, mediaInfoReq) if err != nil { zap.L().Warn("获取Instagram媒体账号信息失败", zap.Error(err), zap.String("artistUuid", artistInfo.ArtistUuid)) fmt.Printf("[%s] [WARN] 获取Instagram媒体账号信息失败,artistUuid: %s, 错误: %v\n", time.Now().Format("2006-01-02 15:04:05"), artistInfo.ArtistUuid, err) } else if mediaInfoResp != nil && mediaInfoResp.Info != nil { // 填充媒体账号信息 item.MediaName = mediaInfoResp.Info.PlatformUserName item.ArtistName = mediaInfoResp.Info.ArtistName item.ArtistPhone = mediaInfoResp.Info.ArtistPhone item.MediaAccUserID = mediaInfoResp.Info.MediaAccountUuid fmt.Printf("[%s] [INFO] 成功获取Instagram账号信息,账号名: %s\n", time.Now().Format("2006-01-02 15:04:05"), item.MediaName) } items = append(items, item) fmt.Printf("[%s] [INFO] 解析 Instagram 账号指标成功,艺人: %s\n", time.Now().Format("2006-01-02 15:04:05"), artistInfo.ArtistUuid) } } // 解析 TikTok 数据 if socialResp.Tiktok != "" { // 调试:打印 TikTok JSON 字符串的前500字符 tiktokPreview := socialResp.Tiktok if len(tiktokPreview) > 500 { tiktokPreview = tiktokPreview[:500] + "..." } fmt.Printf("[%s] [DEBUG] TikTok JSON 字符串预览: %s\n", time.Now().Format("2006-01-02 15:04:05"), tiktokPreview) item := parsePlatformDataForAyrshare(socialResp.Tiktok, "tiktok", artistInfo, dateCN) if item != nil { // 获取 TikTok 平台的用户信息 mediaInfoReq := &cast.MediaInfoByPlatformReq{ ArtistUuid: artistInfo.ArtistUuid, PlatformID: cast.PlatformIDENUM_TIKTOK, // TikTok 平台 ID 为 1 } mediaInfoResp, err := service.CastProvider.MediaInfoByPlatform(ctx, mediaInfoReq) if err != nil { zap.L().Warn("获取TikTok媒体账号信息失败", zap.Error(err), zap.String("artistUuid", artistInfo.ArtistUuid)) fmt.Printf("[%s] [WARN] 获取TikTok媒体账号信息失败,artistUuid: %s, 错误: %v\n", time.Now().Format("2006-01-02 15:04:05"), artistInfo.ArtistUuid, err) } else if mediaInfoResp != nil && mediaInfoResp.Info != nil { // 填充媒体账号信息 item.MediaName = mediaInfoResp.Info.PlatformUserName item.ArtistName = mediaInfoResp.Info.ArtistName item.ArtistPhone = mediaInfoResp.Info.ArtistPhone item.MediaAccUserID = mediaInfoResp.Info.MediaAccountUuid fmt.Printf("[%s] [INFO] 成功获取TikTok账号信息,账号名: %s\n", time.Now().Format("2006-01-02 15:04:05"), item.MediaName) } items = append(items, item) fmt.Printf("[%s] [INFO] 解析 TikTok 账号指标成功,艺人: %s\n", time.Now().Format("2006-01-02 15:04:05"), artistInfo.ArtistUuid) } } fmt.Println("items", items) fmt.Println("items length", len(items)) return items } // parsePlatformDataForAyrshare 解析平台数据(JSON格式) func parsePlatformDataForAyrshare(jsonData, platform string, artistInfo *cast.ArtistAyrShareInfo, dateCN int) *cast.MediaMetricsDailyItem { // 调试:打印原始 JSON 数据(截取前500字符避免日志过长) jsonPreview := jsonData fmt.Printf("parsePlatformDataForAyrshare 原始 JSON 数据: %s\n", jsonPreview) var rootData map[string]interface{} if err := json.Unmarshal([]byte(jsonData), &rootData); err != nil { zap.L().Warn("解析平台数据失败", zap.Error(err), zap.String("platform", platform)) fmt.Printf("parsePlatformDataForAyrshare 解析平台数据失败,平台: %s, 错误: %v\n", platform, err) return nil } // 调试:打印根数据的所有 key rootKeys := make([]string, 0, len(rootData)) for k := range rootData { rootKeys = append(rootKeys, k) } fmt.Printf("parsePlatformDataForAyrshare 根数据字段: %v\n", rootKeys) // 根据平台映射平台ID:1 TikTok, 3 Instagram var platformID uint32 if platform == "tiktok" { platformID = 1 } else if platform == "instagram" { platformID = 3 } else { return nil } // 从根数据中提取 analytics 对象,实际数据在 analytics 字段下 var analyticsData map[string]interface{} if analyticsVal, ok := rootData["analytics"]; ok { if analyticsMap, ok := analyticsVal.(map[string]interface{}); ok { analyticsData = analyticsMap } else { fmt.Printf("parsePlatformDataForAyrshare analytics 字段类型不正确: %T\n", analyticsVal) } } else { fmt.Printf("[%s] [WARN] %s 根数据中没有找到 analytics 字段\n", time.Now().Format("2006-01-02 15:04:05"), platform) } // 如果没有 analytics 字段,尝试直接从根数据提取(兼容旧格式) if analyticsData == nil { analyticsData = rootData fmt.Printf("parsePlatformDataForAyrshare 使用根数据作为 analytics 数据\n") } // 调试:打印 analytics 数据的所有 key,帮助诊断字段名 if len(analyticsData) > 0 { keys := make([]string, 0, len(analyticsData)) for k := range analyticsData { keys = append(keys, k) } fmt.Printf("parsePlatformDataForAyrshare analytics 数据字段: %v\n", keys) } else { fmt.Printf("parsePlatformDataForAyrshare analytics 数据为空\n") } // 提取指标数据(根据 Ayrshare API 的实际返回结构) // Instagram 字段:followersCount, likeCount, mediaCount, viewsCount, commentsCount // TikTok 字段:videoCountTotal, viewCountTotal, commentCountTotal, shareCountTotal item := &cast.MediaMetricsDailyItem{ Uuid: uuid.NewString(), ArtistUuid: artistInfo.ArtistUuid, MediaAccUserID: "", // MediaAccUserID 需要通过其他接口获取,暂时留空 MediaName: "", // MediaName 需要通过其他接口获取,暂时留空 ArtistName: "", // ArtistName 需要通过其他接口获取,暂时留空 ArtistPhone: "", // ArtistPhone 需要通过其他接口获取,暂时留空 PlatformID: platformID, Date: int32(dateCN), // 粉丝数:Instagram 使用 followsCount,TikTok 可能没有直接对应字段 FansCount: extractInt64ForAyrshare(analyticsData, "followsCount", "followersCount", "followers", "followerCount", "fans", "fanCount"), // 观看量:Instagram 使用 viewsCount,TikTok 使用 viewCountTotal ViewsCount: extractInt64ForAyrshare(analyticsData, "viewCountTotal", "viewsCount", "views", "viewCount", "viewCountPeriod"), // 点赞数:Instagram 使用 likeCount LikesCount: extractInt64ForAyrshare(analyticsData, "likeCount", "likes", "likesCount", "likeCountTotal"), // 评论数:Instagram 使用 commentsCount,TikTok 使用 commentCountTotal CommentsCount: extractInt64ForAyrshare(analyticsData, "commentCountTotal", "commentsCount", "comments", "commentCount", "commentCountPeriod"), // 分享数:Instagram 使用 reachCount,TikTok 使用 shareCountTotal SharesCount: extractInt64ForAyrshare(analyticsData, "shareCountTotal", "shares", "shareCount", "sharesCount", "shareCountPeriod"), // 视频数:Instagram 使用 mediaCount,TikTok 使用 videoCountTotal VideoCount: extractInt64ForAyrshare(analyticsData, "mediaCount", "videoCountTotal", "videos", "videoCount", "videosCount"), // 图片/媒体数:Instagram 可能没有直接对应字段,使用 posts 等 ImageCount: extractInt64ForAyrshare(analyticsData, "posts", "postCount", "postsCount", "images", "imageCount", "imagesCount"), } // 调试:打印提取到的指标值 fmt.Printf("parsePlatformDataForAyrshare 提取的指标 - 粉丝数: %d, 观看量: %d, 点赞数: %d, 评论数: %d, 分享数: %d, 视频数: %d, 图片数: %d\n", item.FansCount, item.ViewsCount, item.LikesCount, item.CommentsCount, item.SharesCount, item.VideoCount, item.ImageCount) return item } // extractInt64ForAyrshare 从 map 中提取 int64 值,尝试多个可能的 key // getMapKeys 获取 map 的所有键,用于调试 func getMapKeys(m map[string]interface{}) []string { keys := make([]string, 0, len(m)) for k := range m { keys = append(keys, k) } return keys } func extractInt64ForAyrshare(data map[string]interface{}, keys ...string) int64 { for _, key := range keys { if val, ok := data[key]; ok { // 调试:打印找到的字段和值 fmt.Printf("extractInt64ForAyrshare 找到字段 %s, 值: %v, 类型: %T\n", key, val, val) switch v := val.(type) { case float64: return int64(v) case int64: return v case int: return int64(v) case int32: return int64(v) case string: // 如果是字符串,尝试解析为数字 if key == "viewCountPeriod" || key == "commentCountPeriod" || key == "shareCountPeriod" { // 这些字段是字符串,跳过 continue } // 尝试将字符串解析为数字 if num, err := strconv.ParseInt(v, 10, 64); err == nil { return num } if num, err := strconv.ParseFloat(v, 64); err == nil { return int64(num) } } } } return 0 } // collectWorkMetricsForAyrshare 采集作品指标 func collectWorkMetricsForAyrshare(ctx context.Context, dateCN int) ([]*cast.WorkMetricsDailyItem, error) { metricsList := make([]*cast.WorkMetricsDailyItem, 0) fmt.Printf("[%s] [INFO] 开始采集作品指标\n", time.Now().Format("2006-01-02 15:04:05")) // 调用 ListWorkPlatformInfo,PlatformIDs=[1,3], PublishMediaStatus=2, PageSize=99999999 req := &cast.ListWorkPlatformInfoReq{ PlatformIDs: []uint32{1, 3}, // 1 TikTok, 3 Instagram PublishMediaStatus: 2, // 状态为2 Page: 1, PageSize: 99999999, } resp, err := service.CastProvider.ListWorkPlatformInfo(ctx, req) if err != nil { return metricsList, err } fmt.Println("--------------------------------") fmt.Println("66666666666666666666666666666") fmt.Println("resp", resp) fmt.Println("66666666666666666666666666666") fmt.Println("--------------------------------") if resp == nil || resp.Data == nil || len(resp.Data) == 0 { zap.L().Info("没有作品平台信息") return metricsList, nil } // 对每个作品调用 GetPostAnalytics for _, platformInfo := range resp.Data { if platformInfo.PublishMediaID == "" || platformInfo.WorkUuid == "" { continue } // 只处理 TikTok(1) 和 Instagram(3) 平台 if platformInfo.PlatformID != 1 && platformInfo.PlatformID != 3 { continue } // 通过 ArtistUuid 获取艺人的 AyrShare 信息,获取 profileKey artistAyrShareReq := &cast.GetArtistAyrShareInfoReq{ ArtistUuid: platformInfo.ArtistUuid, Status: 1, // 状态为1表示有效 Page: 1, PageSize: 1, } artistAyrShareResp, err := service.CastProvider.GetArtistAyrShareInfo(ctx, artistAyrShareReq) if err != nil || artistAyrShareResp == nil || len(artistAyrShareResp.Data) == 0 { zap.L().Warn("获取艺人AyrShare信息失败", zap.Error(err), zap.String("artistUuid", platformInfo.ArtistUuid)) fmt.Printf("collectWorkMetricsForAyrshare 获取艺人AyrShare信息失败,artistUuid: %s, 错误: %v\n", platformInfo.ArtistUuid, err) continue } profileKey := artistAyrShareResp.Data[0].ProfileKey if profileKey == "" { zap.L().Warn("作品ProfileKey为空,跳过", zap.String("workUuid", platformInfo.WorkUuid)) fmt.Printf("collectWorkMetricsForAyrshare 作品ProfileKey为空,跳过,workUuid: %s\n", platformInfo.WorkUuid) continue } // 调用 GetPostAnalytics 接口 var postReq *aryshare.GetPostAnalyticsRequest if platformInfo.PlatformID == 1 { // TikTok 平台 postReq = &aryshare.GetPostAnalyticsRequest{ Id: platformInfo.PublishMediaID, Platforms: []string{"tiktok"}, ProfileKey: profileKey, } } else if platformInfo.PlatformID == 3 { // Instagram 平台 postReq = &aryshare.GetPostAnalyticsRequest{ Id: platformInfo.PublishMediaID, Platforms: []string{"instagram"}, ProfileKey: profileKey, } } postResp, err := service.AyrshareProvider.GetPostAnalytics(ctx, postReq) fmt.Println("--------------------------------") fmt.Println("postResp", postResp) fmt.Println("--------------------------------") if err != nil { zap.L().Warn("获取作品分析数据失败", zap.Error(err), zap.String("publishMediaID", platformInfo.PublishMediaID)) fmt.Printf("collectWorkMetricsForAyrshare 获取作品分析数据失败,publishMediaID: %s, 错误: %v\n", platformInfo.PublishMediaID, err) continue } // 解析作品分析数据并构建指标 item := parsePostAnalyticsToWorkMetricsForAyrshare(postResp, platformInfo, dateCN) if item != nil { fmt.Println("--------------------------------") fmt.Println("test") fmt.Println("item", item) fmt.Println("--------------------------------") metricsList = append(metricsList, item) fmt.Printf("collectWorkMetricsForAyrshare 解析作品指标成功,workUuid: %s, platformID: %d\n", platformInfo.WorkUuid, platformInfo.PlatformID) } // 避免请求过于频繁 time.Sleep(200 * time.Millisecond) } fmt.Println("--------------------------------") fmt.Println("metricsList", metricsList) fmt.Println("--------------------------------") fmt.Printf("collectWorkMetricsForAyrshare 作品指标采集完成,共采集 %d 条\n", len(metricsList)) return metricsList, nil } // parsePostAnalyticsToWorkMetricsForAyrshare 解析作品分析数据并转换为作品指标 func parsePostAnalyticsToWorkMetricsForAyrshare(postResp *aryshare.GetPostAnalyticsResponse, platformInfo *cast.WorkPlatformInfo, dateCN int) *cast.WorkMetricsDailyItem { fmt.Println("--------------------------------") fmt.Println("44444444444444444444444444444") fmt.Println("platformInfo", platformInfo) fmt.Println("44444444444444444444444444444") fmt.Println("--------------------------------") // 根据平台ID选择对应的 JSON 数据 var jsonData string if platformInfo.PlatformID == 1 { // TikTok jsonData = postResp.Tiktok } else if platformInfo.PlatformID == 3 { // Instagram jsonData = postResp.Instagram } else { return nil } if jsonData == "" { return nil } // 解析 JSON 数据 var rootData map[string]interface{} if err := json.Unmarshal([]byte(jsonData), &rootData); err != nil { zap.L().Warn("解析作品分析数据失败", zap.Error(err)) fmt.Printf("parsePostAnalyticsToWorkMetricsForAyrshare 解析作品分析数据失败,workUuid: %s, 错误: %v\n", platformInfo.WorkUuid, err) return nil } // 调试:打印根数据的键 fmt.Printf("[DEBUG] 根数据包含的键: %v\n", getMapKeys(rootData)) // 从根数据中提取 analytics 对象,实际数据可能在 analytics 字段下 var analyticsData map[string]interface{} if analyticsVal, ok := rootData["analytics"]; ok { if analyticsMap, ok := analyticsVal.(map[string]interface{}); ok { analyticsData = analyticsMap fmt.Printf("analytics 字段提取数据,包含的键: %v\n", getMapKeys(analyticsData)) } } // 如果没有 analytics 字段,尝试直接从根数据提取(兼容旧格式) if analyticsData == nil { analyticsData = rootData fmt.Printf("parsePostAnalyticsToWorkMetricsForAyrshare 使用根数据作为 analyticsData,包含的键: %v\n", getMapKeys(analyticsData)) } // 构建作品指标项,使用 ListWorkPlatformInfo 返回的字段信息 item := &cast.WorkMetricsDailyItem{ Uuid: uuid.NewString(), WorkUuid: platformInfo.WorkUuid, ArtistUuid: platformInfo.ArtistUuid, MediaAccUserID: platformInfo.PlatformUserID, // 使用平台用户ID MediaName: platformInfo.PlatformUserName, // 平台用户名 ArtistName: platformInfo.ArtistName, // 艺人名字 ArtistPhone: platformInfo.ArtistPhone, // 艺人手机号 PlatformID: platformInfo.PlatformID, Date: int32(dateCN), } // 根据平台ID使用不同的字段提取逻辑 if platformInfo.PlatformID == 3 { // Instagram 平台 // 访问量:使用 viewsCount item.ViewsCount = extractInt64ForAyrshare(analyticsData, "viewsCount", "viewCount", "views") // 点赞数:使用 likeCount item.LikesCount = extractInt64ForAyrshare(analyticsData, "likeCount", "likes", "likesCount") // 评论数:Instagram 可能没有评论数字段,尝试多种可能 item.CommentsCount = extractInt64ForAyrshare(analyticsData, "commentsCount", "commentCount", "comments") // 分享数:使用 sharesCount(注意是复数形式) item.SharesCount = extractInt64ForAyrshare(analyticsData, "sharesCount", "shareCount", "shares") // 打印解析结果 fmt.Printf("parsePostAnalyticsToWorkMetricsForAyrshare Instagram 作品指标解析完成 - workUuid: %s, 访问量: %d, 点赞数: %d, 评论数: %d, 分享数: %d\n", platformInfo.WorkUuid, item.ViewsCount, item.LikesCount, item.CommentsCount, item.SharesCount) } else if platformInfo.PlatformID == 1 { // TikTok 平台 // 访问量:使用 viewCountTotal item.ViewsCount = extractInt64ForAyrshare(analyticsData, "viewCountTotal", "viewCount", "views", "videoViews") // 点赞数:使用 likeCount item.LikesCount = extractInt64ForAyrshare(analyticsData, "likeCount", "likes", "likesCount") // 评论数:使用 commentCountTotal item.CommentsCount = extractInt64ForAyrshare(analyticsData, "commentCountTotal", "commentCount", "comments", "commentsCount") // 分享数:使用 shareCountTotal item.SharesCount = extractInt64ForAyrshare(analyticsData, "shareCountTotal", "shareCount", "shares", "sharesCount") // 打印解析结果 fmt.Printf("parsePostAnalyticsToWorkMetricsForAyrshare TikTok 作品指标解析完成 - workUuid: %s, 访问量: %d, 点赞数: %d, 评论数: %d, 分享数: %d\n", platformInfo.WorkUuid, item.ViewsCount, item.LikesCount, item.CommentsCount, item.SharesCount) } fmt.Println("--------------------------------") fmt.Println("5555555555555555555555") fmt.Println("item", item) fmt.Println("5555555555555555555555") fmt.Println("--------------------------------") return item }