feat:批量导入竞品报告

This commit is contained in:
cjy 2026-01-13 16:15:06 +08:00
parent e6ca737fb1
commit 8fa9f89db9

View File

@ -4,18 +4,23 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"fonchain-fiee/api/accountFiee"
"fonchain-fiee/api/bundle" "fonchain-fiee/api/bundle"
"fonchain-fiee/api/cast" "fonchain-fiee/api/cast"
"fonchain-fiee/cmd/config"
"fonchain-fiee/pkg/cache" "fonchain-fiee/pkg/cache"
"fonchain-fiee/pkg/e" "fonchain-fiee/pkg/e"
modelCast "fonchain-fiee/pkg/model/cast" modelCast "fonchain-fiee/pkg/model/cast"
"fonchain-fiee/pkg/model/login"
"fonchain-fiee/pkg/service" "fonchain-fiee/pkg/service"
"fonchain-fiee/pkg/utils" "fonchain-fiee/pkg/utils"
"path/filepath"
"strconv" "strconv"
"time" "time"
"dubbo.apache.org/dubbo-go/v3/common/constant" "dubbo.apache.org/dubbo-go/v3/common/constant"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/xuri/excelize/v2"
"go.uber.org/zap" "go.uber.org/zap"
) )
@ -48,19 +53,211 @@ func CreateCompetitiveReport(ctx *gin.Context) {
// ImportCompetitiveReportBatch 批量导入竞品报告 // ImportCompetitiveReportBatch 批量导入竞品报告
func ImportCompetitiveReportBatch(ctx *gin.Context) { func ImportCompetitiveReportBatch(ctx *gin.Context) {
var req *cast.ImportCompetitiveReportBatchReq // 获取上传的Excel文件
var err error excelFile, err := ctx.FormFile("file")
if err = ctx.ShouldBind(&req); err != nil {
service.Error(ctx, err)
return
}
newCtx := NewCtxWithUserInfo(ctx)
resp, err := service.CastProvider.ImportCompetitiveReportBatch(newCtx, req)
if err != nil { if err != nil {
service.Error(ctx, err) service.Error(ctx, err)
return return
} }
service.Success(ctx, resp)
loginInfo := login.GetUserInfoFromC(ctx)
lockKey := fmt.Sprintf("import_competitive_report_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"
_, err = utils.CheckDirPath(tempDir, true)
if err != nil {
service.Error(ctx, err)
return
}
// 生成文件名并保存文件
fileName := fmt.Sprintf("%d_competitive_report.xlsx", time.Now().UnixMicro())
excelPath := filepath.Join(tempDir, fileName)
if err = ctx.SaveUploadedFile(excelFile, excelPath); err != nil {
service.Error(ctx, err)
return
}
// 打开Excel文件
excelData, err := excelize.OpenFile(excelPath)
if err != nil {
service.Error(ctx, err)
return
}
defer excelData.Close()
// 解析Excel中的数据
rows, err := excelData.GetRows("Sheet1")
if err != nil {
service.Error(ctx, err)
return
}
// 初始化请求结构
req := cast.ImportCompetitiveReportBatchReq{
Reports: make([]*cast.CreateCompetitiveReportReq, 0),
}
// 生成结果文件URL
urlHost := config.AppConfig.System.FieeHost
urlResult := fmt.Sprintf("%s/api/fiee/static/report/%s", urlHost, fileName)
// 记录每行数据对应的Excel行号用于后续匹配失败记录
reportRowMap := make(map[*cast.CreateCompetitiveReportReq]int)
for line, row := range rows {
// 跳过表头
if line == 0 {
continue
}
// 跳过空行
if len(row) == 0 {
continue
}
// 创建报告请求对象
temp := &cast.CreateCompetitiveReportReq{
Source: 2, // 来源2 导入
}
// 记录Excel行号line+1是因为Excel行号从1开始且跳过表头
excelRowNum := line + 1
reportRowMap[temp] = excelRowNum
// 解析艺人编号B列row[1]
var artistNum string
if len(row) > 1 && utils.CleanString(row[1]) != "" {
artistNum = utils.CleanString(row[1])
artistSubNum := utils.CleanString(row[1])
if artistSubNum == "" {
temp.Remark = "艺人编号不能为空"
req.Reports = append(req.Reports, temp)
continue
}
var subInfoResp *accountFiee.UserInfoResponse
subInfoResp, err := service.AccountFieeProvider.SubNumGetInfo(context.Background(), &accountFiee.SubNumGetInfoRequest{
SubNum: artistSubNum,
Domain: "app",
})
if err != nil {
temp.Remark = fmt.Sprintf("自媒体用户查询失败:%s", err.Error())
zap.L().Error("AccountFieeProvider.SubNumGetInfo", zap.Error(err))
req.Reports = append(req.Reports, temp)
continue
}
if subInfoResp == nil || subInfoResp.Id == 0 {
temp.Remark = "自媒体用户不存在"
zap.L().Error("AccountFieeProvider.SubNumGetInfo user not found", zap.String("subNum", artistSubNum))
req.Reports = append(req.Reports, temp)
continue
}
// 设置艺人信息
temp.SubNum = artistSubNum
temp.ArtistID = fmt.Sprint(subInfoResp.Id)
temp.ArtistName = subInfoResp.Name
temp.ArtistPhone = subInfoResp.TelNum
}
// 解析标题C列row[2]
if len(row) > 2 {
temp.Title = utils.CleanString(row[2])
}
// 解析报告内容D列row[3]
if len(row) > 3 {
temp.ReportContent = row[3]
}
// 解析图片URLE列row[4]
if len(row) > 4 && utils.CleanString(row[4]) != "" {
temp.ImageUrl = utils.CleanString(row[4])
}
// 解析PDF URLF列row[5]),可选
if len(row) > 5 && utils.CleanString(row[5]) != "" {
temp.PdfUrl = utils.CleanString(row[5])
}
// 验证必填字段
if artistNum == "" {
temp.Remark = "艺人编号不能为空"
req.Reports = append(req.Reports, temp)
continue
}
if temp.PdfUrl == "" {
temp.Remark = "PDF URL不能为空"
req.Reports = append(req.Reports, temp)
continue
}
req.Reports = append(req.Reports, temp)
}
// 检查是否有数据
if len(req.Reports) == 0 {
service.Error(ctx, errors.New(e.ErrNoData))
return
}
// 调用批量导入接口
newCtx := NewCtxWithUserInfo(ctx)
resp, err := service.CastProvider.ImportCompetitiveReportBatch(newCtx, &req)
if err != nil {
service.Error(ctx, err)
return
}
// 如果有失败的数据,生成结果文件
if resp.FailCount > 0 {
hasValueRows := make(map[int]bool, resp.FailCount)
// 遍历响应结果,标记失败的行
// resp.Reports的顺序应该与req.Reports的顺序一致
for i, v := range resp.Reports {
if !v.Success {
// 根据索引找到对应的请求对象
if i < len(req.Reports) {
reqReport := req.Reports[i]
// 通过请求对象找到对应的Excel行号
if excelRowNum, ok := reportRowMap[reqReport]; ok {
// 将错误信息写入最后一列G列可根据实际模板调整
excelData.SetCellValue("Sheet1", fmt.Sprintf("G%d", excelRowNum), v.Remark)
hasValueRows[excelRowNum] = true
}
}
}
}
// 删除成功的行(从后往前删除,避免行号变化)
for i := len(rows) - 1; i >= 1; i-- {
if !hasValueRows[i+1] {
if err = excelData.RemoveRow("Sheet1", i+1); err != nil {
continue
}
}
}
// 保存结果文件
resultPath := fmt.Sprintf("./runtime/report/%s", fileName)
if err = excelData.SaveAs(resultPath); err != nil {
service.Error(ctx, err)
return
}
}
// 返回结果
service.Success(ctx, map[string]interface{}{
"successCount": resp.SuccessCount,
"failCount": resp.FailCount,
"resultUrl": urlResult,
})
return return
} }