feat: 增加刷数据分析接口

This commit is contained in:
cjy 2026-03-10 15:19:09 +08:00
parent fcddb6a732
commit 7f9d069190
5 changed files with 3994 additions and 2993 deletions

File diff suppressed because it is too large Load Diff

View File

@ -11213,6 +11213,268 @@ var _ interface {
ErrorName() string
} = CreateWorkAnalysisRespValidationError{}
// Validate checks the field values on ImportWorkAnalysisReq with the rules
// defined in the proto definition for this message. If any rules are
// violated, the first error encountered is returned, or nil if there are no violations.
func (m *ImportWorkAnalysisReq) Validate() error {
return m.validate(false)
}
// ValidateAll checks the field values on ImportWorkAnalysisReq with the rules
// defined in the proto definition for this message. If any rules are
// violated, the result is a list of violation errors wrapped in
// ImportWorkAnalysisReqMultiError, or nil if none found.
func (m *ImportWorkAnalysisReq) ValidateAll() error {
return m.validate(true)
}
func (m *ImportWorkAnalysisReq) validate(all bool) error {
if m == nil {
return nil
}
var errors []error
// no validation rules for Uuid
// no validation rules for SubNum
// no validation rules for ArtistName
// no validation rules for ArtistID
// no validation rules for ArtistPhone
// no validation rules for Analysis
// no validation rules for Title
// no validation rules for PdfUrl
// no validation rules for MediaAccountCount
// no validation rules for WorkVideoCount
// no validation rules for WorkImageCount
// no validation rules for Views
// no validation rules for Likes
// no validation rules for Comments
// no validation rules for Shares
// no validation rules for FansCount
// no validation rules for TopCities
// no validation rules for MostActiveDay
// no validation rules for BestPostTime
// no validation rules for PeriodTypeFans
// no validation rules for PeriodTypeViews
// no validation rules for PeriodTypeLikes
// no validation rules for PeriodTypeComments
// no validation rules for PeriodTypeShares
// no validation rules for BundleOrderUuid
// no validation rules for Date
// no validation rules for SubmitTime
// no validation rules for IsRefreshData
if len(errors) > 0 {
return ImportWorkAnalysisReqMultiError(errors)
}
return nil
}
// ImportWorkAnalysisReqMultiError is an error wrapping multiple validation
// errors returned by ImportWorkAnalysisReq.ValidateAll() if the designated
// constraints aren't met.
type ImportWorkAnalysisReqMultiError []error
// Error returns a concatenation of all the error messages it wraps.
func (m ImportWorkAnalysisReqMultiError) Error() string {
var msgs []string
for _, err := range m {
msgs = append(msgs, err.Error())
}
return strings.Join(msgs, "; ")
}
// AllErrors returns a list of validation violation errors.
func (m ImportWorkAnalysisReqMultiError) AllErrors() []error { return m }
// ImportWorkAnalysisReqValidationError is the validation error returned by
// ImportWorkAnalysisReq.Validate if the designated constraints aren't met.
type ImportWorkAnalysisReqValidationError struct {
field string
reason string
cause error
key bool
}
// Field function returns field value.
func (e ImportWorkAnalysisReqValidationError) Field() string { return e.field }
// Reason function returns reason value.
func (e ImportWorkAnalysisReqValidationError) Reason() string { return e.reason }
// Cause function returns cause value.
func (e ImportWorkAnalysisReqValidationError) Cause() error { return e.cause }
// Key function returns key value.
func (e ImportWorkAnalysisReqValidationError) Key() bool { return e.key }
// ErrorName returns error name.
func (e ImportWorkAnalysisReqValidationError) ErrorName() string {
return "ImportWorkAnalysisReqValidationError"
}
// Error satisfies the builtin error interface
func (e ImportWorkAnalysisReqValidationError) Error() string {
cause := ""
if e.cause != nil {
cause = fmt.Sprintf(" | caused by: %v", e.cause)
}
key := ""
if e.key {
key = "key for "
}
return fmt.Sprintf(
"invalid %sImportWorkAnalysisReq.%s: %s%s",
key,
e.field,
e.reason,
cause)
}
var _ error = ImportWorkAnalysisReqValidationError{}
var _ interface {
Field() string
Reason() string
Key() bool
Cause() error
ErrorName() string
} = ImportWorkAnalysisReqValidationError{}
// Validate checks the field values on ImportWorkAnalysisResp with the rules
// defined in the proto definition for this message. If any rules are
// violated, the first error encountered is returned, or nil if there are no violations.
func (m *ImportWorkAnalysisResp) Validate() error {
return m.validate(false)
}
// ValidateAll checks the field values on ImportWorkAnalysisResp with the rules
// defined in the proto definition for this message. If any rules are
// violated, the result is a list of violation errors wrapped in
// ImportWorkAnalysisRespMultiError, or nil if none found.
func (m *ImportWorkAnalysisResp) ValidateAll() error {
return m.validate(true)
}
func (m *ImportWorkAnalysisResp) validate(all bool) error {
if m == nil {
return nil
}
var errors []error
// no validation rules for Uuid
if len(errors) > 0 {
return ImportWorkAnalysisRespMultiError(errors)
}
return nil
}
// ImportWorkAnalysisRespMultiError is an error wrapping multiple validation
// errors returned by ImportWorkAnalysisResp.ValidateAll() if the designated
// constraints aren't met.
type ImportWorkAnalysisRespMultiError []error
// Error returns a concatenation of all the error messages it wraps.
func (m ImportWorkAnalysisRespMultiError) Error() string {
var msgs []string
for _, err := range m {
msgs = append(msgs, err.Error())
}
return strings.Join(msgs, "; ")
}
// AllErrors returns a list of validation violation errors.
func (m ImportWorkAnalysisRespMultiError) AllErrors() []error { return m }
// ImportWorkAnalysisRespValidationError is the validation error returned by
// ImportWorkAnalysisResp.Validate if the designated constraints aren't met.
type ImportWorkAnalysisRespValidationError struct {
field string
reason string
cause error
key bool
}
// Field function returns field value.
func (e ImportWorkAnalysisRespValidationError) Field() string { return e.field }
// Reason function returns reason value.
func (e ImportWorkAnalysisRespValidationError) Reason() string { return e.reason }
// Cause function returns cause value.
func (e ImportWorkAnalysisRespValidationError) Cause() error { return e.cause }
// Key function returns key value.
func (e ImportWorkAnalysisRespValidationError) Key() bool { return e.key }
// ErrorName returns error name.
func (e ImportWorkAnalysisRespValidationError) ErrorName() string {
return "ImportWorkAnalysisRespValidationError"
}
// Error satisfies the builtin error interface
func (e ImportWorkAnalysisRespValidationError) Error() string {
cause := ""
if e.cause != nil {
cause = fmt.Sprintf(" | caused by: %v", e.cause)
}
key := ""
if e.key {
key = "key for "
}
return fmt.Sprintf(
"invalid %sImportWorkAnalysisResp.%s: %s%s",
key,
e.field,
e.reason,
cause)
}
var _ error = ImportWorkAnalysisRespValidationError{}
var _ interface {
Field() string
Reason() string
Key() bool
Cause() error
ErrorName() string
} = ImportWorkAnalysisRespValidationError{}
// Validate checks the field values on UpdateWorkAnalysisReq with the rules
// defined in the proto definition for this message. If any rules are
// violated, the first error encountered is returned, or nil if there are no violations.

View File

@ -85,6 +85,7 @@ type CastClient interface {
GetArtist(ctx context.Context, in *GetArtistReq, opts ...grpc_go.CallOption) (*GetArtistResp, common.ErrorWithAttachment)
// 作品分析相关接口
CreateWorkAnalysis(ctx context.Context, in *CreateWorkAnalysisReq, opts ...grpc_go.CallOption) (*CreateWorkAnalysisResp, common.ErrorWithAttachment)
ImportWorkAnalysis(ctx context.Context, in *ImportWorkAnalysisReq, opts ...grpc_go.CallOption) (*ImportWorkAnalysisResp, common.ErrorWithAttachment)
UpdateWorkAnalysis(ctx context.Context, in *UpdateWorkAnalysisReq, opts ...grpc_go.CallOption) (*emptypb.Empty, common.ErrorWithAttachment)
UpdateWorkAnalysisStatus(ctx context.Context, in *UpdateWorkAnalysisStatusReq, opts ...grpc_go.CallOption) (*emptypb.Empty, common.ErrorWithAttachment)
GetWorkAnalysis(ctx context.Context, in *GetWorkAnalysisDetailReq, opts ...grpc_go.CallOption) (*GetWorkAnalysisDetailResp, common.ErrorWithAttachment)
@ -200,6 +201,7 @@ type CastClientImpl struct {
UpdateArtist func(ctx context.Context, in *UpdateArtistReq) (*UpdateArtistResp, error)
GetArtist func(ctx context.Context, in *GetArtistReq) (*GetArtistResp, error)
CreateWorkAnalysis func(ctx context.Context, in *CreateWorkAnalysisReq) (*CreateWorkAnalysisResp, error)
ImportWorkAnalysis func(ctx context.Context, in *ImportWorkAnalysisReq) (*ImportWorkAnalysisResp, error)
UpdateWorkAnalysis func(ctx context.Context, in *UpdateWorkAnalysisReq) (*emptypb.Empty, error)
UpdateWorkAnalysisStatus func(ctx context.Context, in *UpdateWorkAnalysisStatusReq) (*emptypb.Empty, error)
GetWorkAnalysis func(ctx context.Context, in *GetWorkAnalysisDetailReq) (*GetWorkAnalysisDetailResp, error)
@ -569,6 +571,12 @@ func (c *castClient) CreateWorkAnalysis(ctx context.Context, in *CreateWorkAnaly
return out, c.cc.Invoke(ctx, "/"+interfaceKey+"/CreateWorkAnalysis", in, out)
}
func (c *castClient) ImportWorkAnalysis(ctx context.Context, in *ImportWorkAnalysisReq, opts ...grpc_go.CallOption) (*ImportWorkAnalysisResp, common.ErrorWithAttachment) {
out := new(ImportWorkAnalysisResp)
interfaceKey := ctx.Value(constant.InterfaceKey).(string)
return out, c.cc.Invoke(ctx, "/"+interfaceKey+"/ImportWorkAnalysis", in, out)
}
func (c *castClient) UpdateWorkAnalysis(ctx context.Context, in *UpdateWorkAnalysisReq, opts ...grpc_go.CallOption) (*emptypb.Empty, common.ErrorWithAttachment) {
out := new(emptypb.Empty)
interfaceKey := ctx.Value(constant.InterfaceKey).(string)
@ -887,6 +895,7 @@ type CastServer interface {
GetArtist(context.Context, *GetArtistReq) (*GetArtistResp, error)
// 作品分析相关接口
CreateWorkAnalysis(context.Context, *CreateWorkAnalysisReq) (*CreateWorkAnalysisResp, error)
ImportWorkAnalysis(context.Context, *ImportWorkAnalysisReq) (*ImportWorkAnalysisResp, error)
UpdateWorkAnalysis(context.Context, *UpdateWorkAnalysisReq) (*emptypb.Empty, error)
UpdateWorkAnalysisStatus(context.Context, *UpdateWorkAnalysisStatusReq) (*emptypb.Empty, error)
GetWorkAnalysis(context.Context, *GetWorkAnalysisDetailReq) (*GetWorkAnalysisDetailResp, error)
@ -1107,6 +1116,9 @@ func (UnimplementedCastServer) GetArtist(context.Context, *GetArtistReq) (*GetAr
func (UnimplementedCastServer) CreateWorkAnalysis(context.Context, *CreateWorkAnalysisReq) (*CreateWorkAnalysisResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateWorkAnalysis not implemented")
}
func (UnimplementedCastServer) ImportWorkAnalysis(context.Context, *ImportWorkAnalysisReq) (*ImportWorkAnalysisResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method ImportWorkAnalysis not implemented")
}
func (UnimplementedCastServer) UpdateWorkAnalysis(context.Context, *UpdateWorkAnalysisReq) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateWorkAnalysis not implemented")
}
@ -2772,6 +2784,35 @@ func _Cast_CreateWorkAnalysis_Handler(srv interface{}, ctx context.Context, dec
return interceptor(ctx, in, info, handler)
}
func _Cast_ImportWorkAnalysis_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc_go.UnaryServerInterceptor) (interface{}, error) {
in := new(ImportWorkAnalysisReq)
if err := dec(in); err != nil {
return nil, err
}
base := srv.(dubbo3.Dubbo3GrpcService)
args := []interface{}{}
args = append(args, in)
md, _ := metadata.FromIncomingContext(ctx)
invAttachment := make(map[string]interface{}, len(md))
for k, v := range md {
invAttachment[k] = v
}
invo := invocation.NewRPCInvocation("ImportWorkAnalysis", args, invAttachment)
if interceptor == nil {
result := base.XXX_GetProxyImpl().Invoke(ctx, invo)
return result, result.Error()
}
info := &grpc_go.UnaryServerInfo{
Server: srv,
FullMethod: ctx.Value("XXX_TRIPLE_GO_INTERFACE_NAME").(string),
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
result := base.XXX_GetProxyImpl().Invoke(ctx, invo)
return result, result.Error()
}
return interceptor(ctx, in, info, handler)
}
func _Cast_UpdateWorkAnalysis_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc_go.UnaryServerInterceptor) (interface{}, error) {
in := new(UpdateWorkAnalysisReq)
if err := dec(in); err != nil {
@ -4234,6 +4275,10 @@ var Cast_ServiceDesc = grpc_go.ServiceDesc{
MethodName: "CreateWorkAnalysis",
Handler: _Cast_CreateWorkAnalysis_Handler,
},
{
MethodName: "ImportWorkAnalysis",
Handler: _Cast_ImportWorkAnalysis_Handler,
},
{
MethodName: "UpdateWorkAnalysis",
Handler: _Cast_UpdateWorkAnalysis_Handler,

View File

@ -33,6 +33,9 @@ func AnalysisRouter(r *gin.RouterGroup) {
analysis.POST("update-approval-id", serviceCast.UpdateWorkAnalysisApprovalID) // 更新作品分析审批ID
analysis.POST("update-pdf-url", serviceCast.UpdateWorkAnalysisPdfUrl) // 更新作品分析PDF链接
// 刷数据专用的导入接口
analysis.POST("import-batch", serviceCast.ImportWorkAnalysisBatch) // Excel 批量导入数据分析
analysis.POST("trigger-ayrshare-metrics", serviceCast.TriggerAyrshareMetricsCollector) // 手动触发 Ayrshare 指标采集任务
}

View File

@ -1,6 +1,7 @@
package cast
import (
"bytes"
"context"
"encoding/json"
"errors"
@ -19,14 +20,18 @@ import (
"fonchain-fiee/pkg/service/bundle/common"
"fonchain-fiee/pkg/utils"
"fonchain-fiee/pkg/utils/stime"
"path/filepath"
"strconv"
"strings"
"sync"
"time"
"math/rand"
"dubbo.apache.org/dubbo-go/v3/common/constant"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/xuri/excelize/v2"
"go.uber.org/zap"
)
@ -92,6 +97,286 @@ func CreateWorkAnalysisCore(ctx *gin.Context, req *cast.CreateWorkAnalysisReq) (
return resp, nil
}
// ImportWorkAnalysisBatch 通过 Excel 批量导入数据分析
// Excel 列顺序SubNum | ArtistName | SubmitTime | PeriodTypeFans | PeriodTypeViews | PeriodTypeLikes | PeriodTypeComments | PeriodTypeShares | IsRefreshData(1=false,2=true)
func ImportWorkAnalysisBatch(ctx *gin.Context) {
excelFile, err := ctx.FormFile("file")
if err != nil {
service.Error(ctx, err)
return
}
loginInfo := login.GetUserInfoFromC(ctx)
lockKey := fmt.Sprintf("import_work_analysis_batch:%d", loginInfo.ID)
replay := cache.RedisClient.SetNX(lockKey, time.Now().Format("20060102150405"), 5*time.Minute)
if !replay.Val() {
service.Error(ctx, errors.New("有导入任务正在进行,请稍后再试"))
return
}
defer cache.RedisClient.Del(lockKey)
tempDir := "./runtime/report_pdf"
_, err = utils.CheckDirPath(tempDir, true)
if err != nil {
service.Error(ctx, err)
return
}
fileName := fmt.Sprintf("%d_work_analysis.xlsx", time.Now().UnixMicro())
excelPath := filepath.Join(tempDir, fileName)
if err = ctx.SaveUploadedFile(excelFile, excelPath); err != nil {
service.Error(ctx, err)
return
}
excelData, err := excelize.OpenFile(excelPath)
if err != nil {
service.Error(ctx, err)
return
}
defer excelData.Close()
rows, err := excelData.GetRows("Sheet1")
if err != nil {
service.Error(ctx, err)
return
}
newCtx := NewCtxWithUserInfo(ctx)
// 在 J1 写入表头
_ = excelData.SetCellValue("Sheet1", "J1", "结果")
successCount := 0
for line, row := range rows {
if line == 0 {
continue // 跳过表头
}
if len(row) == 0 {
continue
}
// Excel 行号1-basedline=1 → 行号 2
cellJ := fmt.Sprintf("J%d", line+1)
// 第一列SubNum
subNum := ""
if len(row) > 0 {
subNum = utils.CleanString(row[0])
}
if subNum == "" {
_ = excelData.SetCellValue("Sheet1", cellJ, "SubNum 不能为空")
continue
}
// 第二列ArtistName
artistName := ""
if len(row) > 1 {
artistName = utils.CleanString(row[1])
}
// 第三列SubmitTime
submitTime := ""
if len(row) > 2 {
submitTime = utils.CleanString(row[2])
}
// 第四列PeriodTypeFans
var periodTypeFans uint32
if len(row) > 3 && utils.CleanString(row[3]) != "" {
v, _ := strconv.ParseUint(utils.CleanString(row[3]), 10, 32)
periodTypeFans = uint32(v)
}
// 第五列PeriodTypeViews
var periodTypeViews uint32
if len(row) > 4 && utils.CleanString(row[4]) != "" {
v, _ := strconv.ParseUint(utils.CleanString(row[4]), 10, 32)
periodTypeViews = uint32(v)
}
// 第六列PeriodTypeLikes
var periodTypeLikes uint32
if len(row) > 5 && utils.CleanString(row[5]) != "" {
v, _ := strconv.ParseUint(utils.CleanString(row[5]), 10, 32)
periodTypeLikes = uint32(v)
}
// 第七列PeriodTypeComments
var periodTypeComments uint32
if len(row) > 6 && utils.CleanString(row[6]) != "" {
v, _ := strconv.ParseUint(utils.CleanString(row[6]), 10, 32)
periodTypeComments = uint32(v)
}
// 第八列PeriodTypeShares
var periodTypeShares uint32
if len(row) > 7 && utils.CleanString(row[7]) != "" {
v, _ := strconv.ParseUint(utils.CleanString(row[7]), 10, 32)
periodTypeShares = uint32(v)
}
// 第九列IsRefreshData1 → false, 2 → true
isRefreshData := false
if len(row) > 8 && utils.CleanString(row[8]) == "2" {
isRefreshData = true
}
// 根据 subNum 查询艺人信息
subInfoResp, err := service.AccountFieeProvider.SubNumGetInfo(context.Background(), &accountFiee.SubNumGetInfoRequest{
SubNum: subNum,
Domain: "app",
})
if err != nil {
zap.L().Error("ImportWorkAnalysisBatch SubNumGetInfo", zap.Error(err), zap.String("subNum", subNum))
_ = excelData.SetCellValue("Sheet1", cellJ, fmt.Sprintf("自媒体用户查询失败:%s", err.Error()))
continue
}
if subInfoResp == nil || subInfoResp.Id == 0 {
_ = excelData.SetCellValue("Sheet1", cellJ, "自媒体用户不存在")
continue
}
artistID := uint64(subInfoResp.Id)
// 查询艺人套餐订单
balanceResp, err := service.BundleProvider.GetBundleBalanceByUserId(context.Background(), &bundle.GetBundleBalanceByUserIdReq{
UserId: int32(artistID),
})
if err != nil {
zap.L().Error("ImportWorkAnalysisBatch GetBundleBalanceByUserId", zap.Error(err), zap.Uint64("artistID", artistID))
_ = excelData.SetCellValue("Sheet1", cellJ, fmt.Sprintf("获取套餐订单失败:%s", err.Error()))
continue
}
if balanceResp.OrderUUID == "" {
_ = excelData.SetCellValue("Sheet1", cellJ, "订单不存在")
continue
}
// 若 artistName 为空则使用账号服务中的姓名
if artistName == "" {
artistName = subInfoResp.Name
}
// 将 submitTimeYYYY-MM-DD 00:00:00加随机 9~15 小时,使提交时间落在 09:00~15:00 之间
if submitTime != "" {
if parsedTime, parseErr := time.Parse("2006-01-02 15:04:05", submitTime); parseErr == nil {
randomHours := time.Duration(rand.Intn(7)+9) * time.Hour
submitTime = parsedTime.Add(randomHours).Format("2006-01-02 15:04:05")
}
}
// 将 submitTime 解析为 YYYYMMDD 格式的 int32作为 ArtistMetricsSeries 的 date
var dateInt int32
if submitTime != "" {
if parsedTime, parseErr := time.Parse("2006-01-02 15:04:05", submitTime); parseErr == nil {
dateInt = int32(parsedTime.Year()*10000 + int(parsedTime.Month())*100 + parsedTime.Day())
}
}
// 提升到外部作用域,供 importReq 使用
var aiAnalysis string
var mediaAccountCount int32
var workVideoCount, workImageCount int32
// 调用 ArtistMetricsSeries 获取艺人指标数据并写入 Analysis
metricsReq := &cast.ArtistMetricsSeriesReq{
ArtistUUID: fmt.Sprint(subInfoResp.Id),
Date: dateInt,
PeriodTypeFans: periodTypeFans,
PeriodTypeViews: periodTypeViews,
PeriodTypeLikes: periodTypeLikes,
PeriodTypeComments: periodTypeComments,
PeriodTypeShares: periodTypeShares,
}
// 并行调用 ArtistMetricsSeries 和 GetArtistWorkStats
var metricsResp *cast.ArtistMetricsSeriesResp
var workStatsResp *cast.GetArtistWorkStatsResp
var metricsErr, workStatsErr error
wg := sync.WaitGroup{}
wg.Add(2)
go func() {
defer wg.Done()
metricsResp, metricsErr = service.CastProvider.ArtistMetricsSeries(context.Background(), metricsReq)
}()
go func() {
defer wg.Done()
workStatsResp, workStatsErr = service.CastProvider.GetArtistWorkStats(newCtx, &cast.GetArtistWorkStatsReq{
ArtistUuid: fmt.Sprint(subInfoResp.Id),
StatusUpdateTime: submitTime,
})
}()
wg.Wait()
if workStatsErr == nil && workStatsResp != nil {
mediaAccountCount = int32(workStatsResp.AccountCount)
workVideoCount = int32(workStatsResp.VideoCount)
workImageCount = int32(workStatsResp.ImageCount)
} else if workStatsErr != nil {
zap.L().Warn("ImportWorkAnalysisBatch GetArtistWorkStats failed", zap.Error(workStatsErr), zap.String("subNum", subNum))
}
if metricsErr != nil {
zap.L().Warn("ImportWorkAnalysisBatch ArtistMetricsSeries failed", zap.Error(metricsErr), zap.String("subNum", subNum))
} else if metricsResp != nil {
// 构建与 ArtistMetricsSeries HTTP 接口相同的 respMap
raw, _ := json.Marshal(metricsResp)
respMap := make(map[string]interface{})
_ = json.Unmarshal(raw, &respMap)
respMap["accountConsumptionNumber"] = mediaAccountCount
respMap["videoCount"] = workVideoCount
respMap["imageCount"] = workImageCount
// 调用 AI 生成分析文本
aiAnalysis, _ = generateArtistMetricsAnalysis(metricsResp)
if aiAnalysis == "" {
zap.L().Warn("ImportWorkAnalysisBatch generateArtistMetricsAnalysis returned empty", zap.String("subNum", subNum))
}
respMap["analysis"] = aiAnalysis
}
importReq := &cast.ImportWorkAnalysisReq{
SubNum: subNum,
ArtistID: fmt.Sprint(subInfoResp.Id),
ArtistName: artistName,
ArtistPhone: subInfoResp.TelNum,
BundleOrderUuid: balanceResp.OrderUUID,
SubmitTime: submitTime,
PeriodTypeFans: periodTypeFans,
PeriodTypeViews: periodTypeViews,
PeriodTypeLikes: periodTypeLikes,
PeriodTypeComments: periodTypeComments,
PeriodTypeShares: periodTypeShares,
IsRefreshData: isRefreshData,
Analysis: aiAnalysis,
MediaAccountCount: mediaAccountCount,
WorkVideoCount: workVideoCount,
WorkImageCount: workImageCount,
}
importResp, err := service.CastProvider.ImportWorkAnalysis(newCtx, importReq)
if err != nil {
zap.L().Error("ImportWorkAnalysisBatch ImportWorkAnalysis", zap.Error(err), zap.String("subNum", subNum))
_ = excelData.SetCellValue("Sheet1", cellJ, fmt.Sprintf("导入失败:%s", err.Error()))
continue
}
// 导入成功,将返回的 UUID 写入 J 列
_ = excelData.SetCellValue("Sheet1", cellJ, importResp.Uuid)
successCount++
}
// 将修改后的 Excel 写入 buffer 并返回给客户端下载
buf, err := excelData.WriteToBuffer()
if err != nil {
service.Error(ctx, err)
return
}
utils.ResponseXls(ctx, bytes.NewReader(buf.Bytes()), fmt.Sprintf("数据分析导入结果_%d成功", successCount))
}
// UpdateWorkAnalysis 更新作品分析
func UpdateWorkAnalysis(ctx *gin.Context) {
var req *cast.UpdateWorkAnalysisReq