fonchain-fiee/pkg/service/cast/analysis.go

1187 lines
39 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.DataAnalysisNumber-resp.DataAnalysisConsumptionNumber <= 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.DataAnalysisConsumptionNumber >= balanceInfoRes.DataAnalysisNumber {
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("metricsList", metricsList)
return metricsList, nil
}
// parseSocialAnalyticsToMediaMetricsForAyrshare 解析社交分析数据并转换为媒体指标
func parseSocialAnalyticsToMediaMetricsForAyrshare(socialResp *aryshare.GetSocialAnalyticsResponse, artistInfo *cast.ArtistAyrShareInfo, dateCN int) []*cast.MediaMetricsDailyItem {
items := make([]*cast.MediaMetricsDailyItem, 0)
// 解析 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 {
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 {
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)
// 根据平台映射平台ID1 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 使用 followsCountTikTok 可能没有直接对应字段
FansCount: extractInt64ForAyrshare(analyticsData, "followsCount", "followersCount", "followers", "followerCount", "fans", "fanCount"),
// 观看量Instagram 使用 viewsCountTikTok 使用 viewCountTotal
ViewsCount: extractInt64ForAyrshare(analyticsData, "viewCountTotal", "viewsCount", "views", "viewCount", "viewCountPeriod"),
// 点赞数Instagram 使用 likeCount
LikesCount: extractInt64ForAyrshare(analyticsData, "likeCount", "likes", "likesCount", "likeCountTotal"),
// 评论数Instagram 使用 commentsCountTikTok 使用 commentCountTotal
CommentsCount: extractInt64ForAyrshare(analyticsData, "commentCountTotal", "commentsCount", "comments", "commentCount", "commentCountPeriod"),
// 分享数Instagram 使用 reachCountTikTok 使用 shareCountTotal
SharesCount: extractInt64ForAyrshare(analyticsData, "shareCountTotal", "shares", "shareCount", "sharesCount", "shareCountPeriod"),
// 视频数Instagram 使用 mediaCountTikTok 使用 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"))
// 调用 ListWorkPlatformInfoPlatformIDs=[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
}