Merge branch 'dev' of http://gitea.tools.fontree.cn:3000/fiee/fonchain-fiee into dev
This commit is contained in:
commit
c6cf13465c
Binary file not shown.
@ -55,7 +55,7 @@ func AnalysisRouter(r *gin.RouterGroup) {
|
||||
analysisAppRoute := r.Group("app/analysis")
|
||||
analysisAppRoute.Use(middleware.CheckLogin(service.AccountFieeProvider))
|
||||
{
|
||||
analysisAppRoute.POST("list", serviceCast.ListWorkAnalysis) // 作品列表
|
||||
analysisAppRoute.POST("list", serviceCast.ListWorkAnalysisForApp) // 作品列表
|
||||
analysisAppRoute.POST("detail", serviceCast.GetWorkAnalysis) // 作品分析详情
|
||||
analysisAppRoute.POST("update-status", serviceCast.UpdateWorkAnalysisStatus) // 用户确认
|
||||
analysisAppRoute.POST("check-balance", serviceCast.CheckBundleBalance) // 检查套餐余量
|
||||
@ -67,7 +67,7 @@ func AnalysisRouter(r *gin.RouterGroup) {
|
||||
competitiveReportAppRoute.Use(middleware.CheckLogin(service.AccountFieeProvider))
|
||||
{
|
||||
competitiveReportAppRoute.POST("detail", serviceCast.GetCompetitiveReportForApp) // 获取竞品报告详情(App端)
|
||||
competitiveReportAppRoute.POST("list", serviceCast.ListCompetitiveReportByArtistUuid) // 根据艺人UUID获取竞品报告列表(App端)
|
||||
competitiveReportAppRoute.POST("list", serviceCast.ListReportByArtistUuidForApp) // 根据艺人UUID获取竞品报告列表(App端)
|
||||
competitiveReportAppRoute.POST("update-status", serviceCast.UpdateCompetitiveReportStatus) // 更新竞品报告状态(App端)
|
||||
}
|
||||
}
|
||||
|
||||
@ -101,7 +101,7 @@ func AICompetitorReport(ctx *gin.Context) {
|
||||
}
|
||||
|
||||
// 第一步:调用AI理解视频/图片内容
|
||||
vlPrompt := "请你详细描述视频和图片中的内容分别是什么"
|
||||
vlPrompt := "请你详细描述这些视频或者这些图片中的内容分别是什么,请详细描述,不要遗漏任何细节"
|
||||
vlResult, err := qwen.VL(req.Videos, req.Images, vlPrompt, req.Model)
|
||||
if err != nil {
|
||||
// 检查是否是文件下载超时错误(内容过大)
|
||||
@ -144,7 +144,7 @@ func AICompetitorReport(ctx *gin.Context) {
|
||||
textChan = make(chan textResult, 1)
|
||||
go func() {
|
||||
// 构建文本生成提示词:理解内容 + 用户要求
|
||||
textPrompt := fmt.Sprintf("基于以下视频和图片的内容描述:\n%s\n\n请根据以下要求生成竞品报告:注意不要输出markdown格式来进行排版,请直接输出纯文本\n我的要求是:\n%s", vlContent, req.TextPrompt)
|
||||
textPrompt := fmt.Sprintf("基于以下视频和图片的内容描述:\n%s\n\n请根据以下要求生成竞品报告:注意不要输出markdown格式来进行排版,请直接输出纯文本。只需要回复竞品报告的内容,其他无关的内容不要输出,输出的内容第一行不要标题,直接输出竞品报告的正文即可\n我的要求是:\n%s", vlContent, req.TextPrompt)
|
||||
|
||||
chatReq, err := buildChatRequest(textPrompt, nil)
|
||||
if err != nil {
|
||||
|
||||
@ -302,7 +302,7 @@ func WorkAnalysisConfirm(c *gin.Context) { // 确认数据分析并扣除余量
|
||||
fmt.Println("res:", res)
|
||||
fmt.Println("err:", err)
|
||||
if err != nil {
|
||||
service.Error(c, errors.New(common.UpdateWorkStatusFailed))
|
||||
service.Error(c, errors.New("驳回失败"))
|
||||
return
|
||||
}
|
||||
service.Success(c, res)
|
||||
@ -317,6 +317,11 @@ func WorkAnalysisConfirm(c *gin.Context) { // 确认数据分析并扣除余量
|
||||
return
|
||||
}
|
||||
|
||||
if balanceInfoRes.BundleStatus == common.BundleExpired {
|
||||
service.Error(c, errors.New("套餐已过期"))
|
||||
return
|
||||
}
|
||||
|
||||
analysisInfoRes, err := service.CastProvider.GetWorkAnalysis(c, &cast.GetWorkAnalysisDetailReq{
|
||||
Uuid: req.Uuid,
|
||||
})
|
||||
@ -328,6 +333,11 @@ func WorkAnalysisConfirm(c *gin.Context) { // 确认数据分析并扣除余量
|
||||
service.Error(c, errors.New("数据分析不是待确认状态"))
|
||||
return
|
||||
}
|
||||
artistID, _ := strconv.ParseUint(analysisInfoRes.ArtistID, 10, 64)
|
||||
if artistID != uint64(userInfo.ID) {
|
||||
service.Error(c, errors.New("非本人数据分析,无法操作"))
|
||||
return
|
||||
}
|
||||
|
||||
var addBalanceReq bundle.AddBundleBalanceReq
|
||||
addBalanceReq.UserId = int32(userInfo.ID)
|
||||
@ -353,7 +363,7 @@ func WorkAnalysisConfirm(c *gin.Context) { // 确认数据分析并扣除余量
|
||||
fmt.Println("res:", res)
|
||||
fmt.Println("err:", err)
|
||||
if err != nil {
|
||||
service.Error(c, errors.New(common.UpdateWorkStatusFailed))
|
||||
service.Error(c, errors.New("确认失败"))
|
||||
return
|
||||
}
|
||||
// 如果是艺人手动确认,确认操作后,自动标记为待阅读状态
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package cast
|
||||
package cast
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -13,6 +13,7 @@ import (
|
||||
modelCast "fonchain-fiee/pkg/model/cast"
|
||||
"fonchain-fiee/pkg/model/login"
|
||||
"fonchain-fiee/pkg/service"
|
||||
"fonchain-fiee/pkg/service/bundle/common"
|
||||
"fonchain-fiee/pkg/utils"
|
||||
"strconv"
|
||||
"sync"
|
||||
@ -143,6 +144,41 @@ func ListWorkAnalysis(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// ListWorkAnalysis 获取作品分析列表
|
||||
func ListWorkAnalysisForApp(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)
|
||||
loginInfo := login.GetUserInfoFromC(ctx)
|
||||
// 查询用户套餐有没有过期
|
||||
balanceInfoRes, err := service.BundleProvider.GetBundleBalanceByUserId(context.Background(), &bundle.GetBundleBalanceByUserIdReq{
|
||||
UserId: int32(loginInfo.ID),
|
||||
})
|
||||
if err != nil {
|
||||
zap.L().Error("ListWorkAnalysisForApp GetBundleBalanceByUserId", zap.Any("err", err))
|
||||
service.Error(ctx, errors.New(common.GetUserBalanceFailed))
|
||||
return
|
||||
}
|
||||
// 套餐未过期的话,传入 subNum ,只获取该套餐的有效期内数据
|
||||
if balanceInfoRes.BundleStatus == common.BundleNotExpired {
|
||||
zap.L().Info("ListWorkAnalysisForApp BundleNotExpired", zap.Any("loginInfo", loginInfo))
|
||||
req.SubNum = loginInfo.SubNum
|
||||
}
|
||||
resp, err := service.CastProvider.ListWorkAnalysis(newCtx, req)
|
||||
if err != nil {
|
||||
zap.L().Error("ListWorkAnalysisForApp ListWorkAnalysis", zap.Any("err", err))
|
||||
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 {
|
||||
@ -609,6 +645,48 @@ func autoConfirmAnalysis(ctx context.Context, analysisUuid string) (err error) {
|
||||
isFailed = true
|
||||
}
|
||||
|
||||
if balanceInfoRes.BundleStatus == common.BundleExpired {
|
||||
confirmRemark = "套餐已过期"
|
||||
// 直接提交
|
||||
_, err = service.CastProvider.UpdateWorkAnalysisStatus(context.Background(), &cast.UpdateWorkAnalysisStatusReq{
|
||||
WorkAction: cast.WorkActionENUM_CONFIRM,
|
||||
Uuid: analysisUuid,
|
||||
ConfirmRemark: confirmRemark,
|
||||
ConfirmStatus: 3,
|
||||
})
|
||||
if err != nil {
|
||||
zap.L().Error("autoConfirmAnalysis UpdateWorkAnalysisStatus", zap.Any("err", err))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
// 判断数据分析的提交时间是否在现在的套餐期间范围之内
|
||||
submitTime, err := time.Parse("2006-01-02 15:04:05", infoResp.SubmitTime)
|
||||
if err != nil {
|
||||
zap.L().Error("autoConfirmAnalysis ParseSubmitTime", zap.Any("err", err))
|
||||
return
|
||||
}
|
||||
if submitTime.Before(time.Unix(balanceInfoRes.PayTime, 0)) {
|
||||
// todo 暂时先这样
|
||||
// confirmRemark = "该报告提交时间不在该套餐期间范围之内"
|
||||
// 直接提交
|
||||
confirmRemark = "系统自动确认"
|
||||
usedType = 0
|
||||
_, err = service.CastProvider.UpdateWorkAnalysisStatus(context.Background(), &cast.UpdateWorkAnalysisStatusReq{
|
||||
WorkAction: cast.WorkActionENUM_CONFIRM,
|
||||
Uuid: analysisUuid,
|
||||
ConfirmRemark: confirmRemark,
|
||||
CostType: usedType,
|
||||
ConfirmStatus: 1,
|
||||
ConfirmType: 2,
|
||||
})
|
||||
if err != nil {
|
||||
zap.L().Error("autoConfirmAnalysis UpdateWorkAnalysisStatus", zap.Any("err", err))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var addBalanceReq bundle.AddBundleBalanceReq
|
||||
addBalanceReq.UserId = int32(userID)
|
||||
// 检查数据分析余量
|
||||
|
||||
@ -89,7 +89,7 @@ func CreateCompetitiveReport(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if req.ReportContent == "" || req.ImageUrl == "" {
|
||||
if req.ReportContent == "" && req.ImageUrl == "" {
|
||||
err = errors.New("报告内容和图片不能同时为空")
|
||||
service.Error(ctx, err)
|
||||
return
|
||||
@ -109,7 +109,9 @@ func CreateCompetitiveReport(ctx *gin.Context) {
|
||||
// 如果提供了报告内容和图片URL,则生成PDF并上传
|
||||
if req.ReportContent != "" {
|
||||
// 生成临时PDF文件路径
|
||||
pdfFileName := fmt.Sprintf("competitive_report_%s_%d.pdf", req.ArtistID, time.Now().UnixMicro())
|
||||
today := time.Now().Format("20060102")
|
||||
timestamp := time.Now().UnixMicro()
|
||||
pdfFileName := fmt.Sprintf("%s%s老师的竞品报告%d.pdf", today, req.ArtistName, timestamp)
|
||||
pdfFilePath := "./runtime/report_pdf/" + pdfFileName
|
||||
|
||||
// 确保目录存在
|
||||
@ -285,8 +287,11 @@ func ImportCompetitiveReportBatch(ctx *gin.Context) {
|
||||
temp.Remark = "该艺人竞品报告可用次数为0"
|
||||
req.Reports = append(req.Reports, temp)
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
} else if err != nil && err.Error() == "该艺人竞品数可用次数为0" {
|
||||
temp.Remark = "该艺人竞品报告可用次数为0"
|
||||
req.Reports = append(req.Reports, temp)
|
||||
continue
|
||||
} else {
|
||||
temp.Remark = errors.New("查询竞品报告余额失败").Error()
|
||||
req.Reports = append(req.Reports, temp)
|
||||
continue
|
||||
@ -296,7 +301,15 @@ func ImportCompetitiveReportBatch(ctx *gin.Context) {
|
||||
|
||||
// 解析标题(C列,row[2])
|
||||
if len(row) > 2 {
|
||||
temp.Title = utils.CleanString(row[2])
|
||||
if temp.Title == "" {
|
||||
// 获取中国时区时间
|
||||
loc, _ := time.LoadLocation("Asia/Shanghai")
|
||||
now := time.Now().In(loc)
|
||||
nowDate := now.Format("2006-01-02")
|
||||
temp.Title = nowDate + temp.ArtistName + "老师竞品报告"
|
||||
} else {
|
||||
temp.Title = utils.CleanString(row[2])
|
||||
}
|
||||
}
|
||||
|
||||
// 解析报告内容(D列,row[3])
|
||||
@ -309,11 +322,6 @@ func ImportCompetitiveReportBatch(ctx *gin.Context) {
|
||||
temp.ImageUrl = utils.CleanString(row[4])
|
||||
}
|
||||
|
||||
// 解析PDF URL(F列,row[5]),可选
|
||||
if len(row) > 5 && utils.CleanString(row[5]) != "" {
|
||||
temp.PdfUrl = utils.CleanString(row[5])
|
||||
}
|
||||
|
||||
// 验证必填字段
|
||||
if artistNum == "" {
|
||||
temp.Remark = "艺人编号不能为空"
|
||||
@ -321,12 +329,84 @@ func ImportCompetitiveReportBatch(ctx *gin.Context) {
|
||||
continue
|
||||
}
|
||||
|
||||
if temp.PdfUrl == "" {
|
||||
temp.Remark = "PDF URL不能为空"
|
||||
// 验证报告内容和图片不能同时为空
|
||||
if temp.ReportContent == "" && temp.ImageUrl == "" {
|
||||
temp.Remark = "报告内容和图片不能同时为空"
|
||||
req.Reports = append(req.Reports, temp)
|
||||
continue
|
||||
}
|
||||
|
||||
// 如果已经有错误信息,跳过PDF生成
|
||||
if temp.Remark != "" {
|
||||
req.Reports = append(req.Reports, temp)
|
||||
continue
|
||||
}
|
||||
|
||||
// 检查图片URL是否包含阿里云,如果包含则下载并重新上传到OSS
|
||||
if temp.ImageUrl != "" {
|
||||
newImageUrl, err := checkAndReuploadImageForReport(temp.ImageUrl)
|
||||
if err != nil {
|
||||
temp.Remark = fmt.Sprintf("图片处理失败: %v", err)
|
||||
zap.L().Error("图片重新上传失败", zap.String("imageUrl", temp.ImageUrl), zap.Error(err))
|
||||
req.Reports = append(req.Reports, temp)
|
||||
continue
|
||||
}
|
||||
temp.ImageUrl = newImageUrl
|
||||
}
|
||||
|
||||
// 如果提供了报告内容,则生成PDF并上传
|
||||
if temp.ReportContent != "" {
|
||||
// 生成临时PDF文件路径
|
||||
today := time.Now().Format("20060102")
|
||||
timestamp := time.Now().UnixMicro()
|
||||
pdfFileName := fmt.Sprintf("%s%s老师的竞品报告%d.pdf", today, temp.ArtistName, timestamp)
|
||||
pdfFilePath := "./runtime/report_pdf/" + pdfFileName
|
||||
|
||||
// 确保目录存在
|
||||
_, err = utils.CheckDirPath("./runtime/report_pdf/", true)
|
||||
if err != nil {
|
||||
temp.Remark = fmt.Sprintf("创建PDF目录失败: %v", err)
|
||||
req.Reports = append(req.Reports, temp)
|
||||
continue
|
||||
}
|
||||
|
||||
// 生成PDF文件
|
||||
fontPath := "./data/simfang.ttf"
|
||||
err = utils.GeneratePDF(temp.ReportContent, temp.ImageUrl, pdfFilePath, fontPath)
|
||||
if err != nil {
|
||||
zap.L().Error("生成PDF失败", zap.Error(err))
|
||||
temp.Remark = "生成PDF失败"
|
||||
req.Reports = append(req.Reports, temp)
|
||||
continue
|
||||
}
|
||||
|
||||
// 上传PDF到OSS
|
||||
pdfUrl, uploadErr := upload.PutBos(pdfFilePath, upload.PdfType, true)
|
||||
if uploadErr != nil {
|
||||
zap.L().Error("上传PDF失败", zap.Error(uploadErr))
|
||||
temp.Remark = "上传PDF失败"
|
||||
req.Reports = append(req.Reports, temp)
|
||||
// 清理临时PDF文件
|
||||
if _, err := os.Stat(pdfFilePath); err == nil {
|
||||
os.Remove(pdfFilePath)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// 将上传后的PDF链接设置到请求中
|
||||
temp.PdfUrl = pdfUrl
|
||||
|
||||
// 清理临时PDF文件
|
||||
if _, err := os.Stat(pdfFilePath); err == nil {
|
||||
if err := os.Remove(pdfFilePath); err != nil {
|
||||
zap.L().Warn("删除临时PDF文件失败", zap.String("path", pdfFilePath), zap.Error(err))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 如果没有报告内容,则将图片URL设置为PDF URL
|
||||
temp.PdfUrl = temp.ImageUrl
|
||||
}
|
||||
|
||||
req.Reports = append(req.Reports, temp)
|
||||
}
|
||||
|
||||
@ -356,8 +436,8 @@ func ImportCompetitiveReportBatch(ctx *gin.Context) {
|
||||
reqReport := req.Reports[i]
|
||||
// 通过请求对象找到对应的Excel行号
|
||||
if excelRowNum, ok := reportRowMap[reqReport]; ok {
|
||||
// 将错误信息写入最后一列(G列,可根据实际模板调整)
|
||||
excelData.SetCellValue("Sheet1", fmt.Sprintf("G%d", excelRowNum), v.Remark)
|
||||
// 将错误信息写入最后一列(F列)
|
||||
excelData.SetCellValue("Sheet1", fmt.Sprintf("F%d", excelRowNum), v.Remark)
|
||||
hasValueRows[excelRowNum] = true
|
||||
}
|
||||
}
|
||||
@ -397,10 +477,102 @@ func UpdateCompetitiveReportStatus(ctx *gin.Context) {
|
||||
service.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
userInfo := login.GetUserInfoFromC(ctx)
|
||||
newCtx := NewCtxWithUserInfo(ctx)
|
||||
|
||||
// 当 WorkAction = 1(确认)时,ConfirmStatus 必须为 1(通过)、2(驳回)或 3(失败)
|
||||
// 当 WorkAction = 6(阅读)时,不需要 ConfirmStatus
|
||||
if req.WorkAction == 1 {
|
||||
if req.ConfirmStatus != 1 && req.ConfirmStatus != 2 && req.ConfirmStatus != 3 {
|
||||
service.Error(ctx, errors.New("非法操作"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 只有当 WorkAction = 1 或 6 时,才获取竞品报告详情并验证权限
|
||||
var resp *cast.GetCompetitiveReportDetailResp
|
||||
if req.WorkAction == 1 || req.WorkAction == 6 {
|
||||
var err error
|
||||
resp, err = service.CastProvider.GetCompetitiveReport(context.Background(), &cast.GetCompetitiveReportDetailReq{
|
||||
Uuid: req.Uuid,
|
||||
})
|
||||
if err != nil {
|
||||
service.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
if resp == nil {
|
||||
service.Error(ctx, errors.New("竞品报告不存在"))
|
||||
return
|
||||
}
|
||||
artistID, _ := strconv.ParseUint(resp.ArtistID, 10, 64)
|
||||
if artistID != uint64(userInfo.ID) {
|
||||
service.Error(ctx, errors.New("非本人竞品报告,无法操作"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 当 WorkAction == 1(确认)且 ConfirmStatus == 1(通过)时,需要检查并扣减余额
|
||||
if req.WorkAction == 1 && req.ConfirmStatus == 1 {
|
||||
// 检查竞品报告状态是否为待确认(状态4)
|
||||
if resp.WorkReportStatus != 4 {
|
||||
service.Error(ctx, errors.New("非法状态"))
|
||||
return
|
||||
}
|
||||
|
||||
// 查询用户余额
|
||||
balanceInfoRes, err := service.BundleProvider.GetBundleBalanceByUserId(context.Background(), &bundle.GetBundleBalanceByUserIdReq{
|
||||
UserId: int32(userInfo.ID),
|
||||
})
|
||||
if err != nil {
|
||||
service.Error(ctx, errors.New(common.GetUserBalanceFailed))
|
||||
return
|
||||
}
|
||||
|
||||
if balanceInfoRes.BundleStatus == common.BundleExpired {
|
||||
service.Error(ctx, errors.New("套餐已过期"))
|
||||
return
|
||||
}
|
||||
|
||||
if balanceInfoRes.CompetitiveConsumptionNumber >= balanceInfoRes.CompetitiveNumber {
|
||||
service.Error(ctx, errors.New("竞品报告余额不足"))
|
||||
return
|
||||
}
|
||||
|
||||
// 扣减竞品余额
|
||||
var addBalanceReq bundle.AddBundleBalanceReq
|
||||
addBalanceReq.UserId = int32(userInfo.ID)
|
||||
addBalanceReq.CompetitiveConsumptionNumber = 1
|
||||
balanceResp, err := service.BundleProvider.AddBundleBalance(ctx, &addBalanceReq)
|
||||
if err != nil {
|
||||
service.Error(ctx, errors.New("扣除余额失败"))
|
||||
return
|
||||
}
|
||||
|
||||
// 更新竞品报告状态,记录扣费类型
|
||||
req.CostType = balanceResp.UsedType
|
||||
_, err = service.CastProvider.UpdateCompetitiveReportStatus(newCtx, req)
|
||||
if err != nil {
|
||||
service.Error(ctx, errors.New("确认失败"))
|
||||
return
|
||||
}
|
||||
|
||||
// 确认操作后,自动标记为已阅读状态
|
||||
_, err = service.CastProvider.UpdateCompetitiveReportStatus(newCtx, &cast.UpdateCompetitiveReportStatusReq{
|
||||
WorkAction: cast.WorkActionENUM_READ,
|
||||
Uuid: req.Uuid,
|
||||
})
|
||||
if err != nil {
|
||||
service.Error(ctx, errors.New("确认失败"))
|
||||
return
|
||||
}
|
||||
service.Success(ctx, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 驳回或其他操作,直接更新状态
|
||||
_, err = service.CastProvider.UpdateCompetitiveReportStatus(newCtx, req)
|
||||
if err != nil {
|
||||
service.Error(ctx, err)
|
||||
service.Error(ctx, errors.New("操作失败"))
|
||||
return
|
||||
}
|
||||
service.Success(ctx, nil)
|
||||
@ -487,6 +659,46 @@ func ListCompetitiveReportByArtistUuid(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// ListReportByArtistUuidForApp 根据艺人UUID获取竞品报告列表(必须传ArtistUuid)
|
||||
func ListReportByArtistUuidForApp(ctx *gin.Context) {
|
||||
var req *cast.ListCompetitiveReportReq
|
||||
var err error
|
||||
if err = ctx.ShouldBind(&req); err != nil {
|
||||
service.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
// 验证ArtistUuid必须存在
|
||||
if req.ArtistUuid == "" {
|
||||
service.Error(ctx, errors.New("ArtistUuid不能为空"))
|
||||
return
|
||||
}
|
||||
|
||||
newCtx := NewCtxWithUserInfo(ctx)
|
||||
loginInfo := login.GetUserInfoFromC(ctx)
|
||||
// 查询用户套餐有没有过期
|
||||
balanceInfoRes, err := service.BundleProvider.GetBundleBalanceByUserId(context.Background(), &bundle.GetBundleBalanceByUserIdReq{
|
||||
UserId: int32(loginInfo.ID),
|
||||
})
|
||||
if err != nil {
|
||||
service.Error(ctx, errors.New(common.GetUserBalanceFailed))
|
||||
return
|
||||
}
|
||||
// 套餐未过期的话,传入 subNum ,只获取该套餐的有效期内数据
|
||||
if balanceInfoRes.BundleStatus == common.BundleNotExpired {
|
||||
zap.L().Info("ListReportByArtistUuidForApp BundleNotExpired", zap.Any("loginInfo", loginInfo))
|
||||
req.SubNum = loginInfo.SubNum
|
||||
}
|
||||
|
||||
resp, err := service.CastProvider.ListCompetitiveReport(newCtx, req)
|
||||
if err != nil {
|
||||
service.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
// RefreshCompetitiveReportApproval(ctx, resp.Data)
|
||||
service.Success(ctx, resp)
|
||||
return
|
||||
}
|
||||
|
||||
// DeleteCompetitiveReport 删除竞品报告
|
||||
func DeleteCompetitiveReport(ctx *gin.Context) {
|
||||
var req *cast.DeleteCompetitiveReportReq
|
||||
@ -787,6 +999,47 @@ func autoConfirmReport(ctx context.Context, reportUuid string) (err error) {
|
||||
isFailed = true
|
||||
}
|
||||
|
||||
if balanceInfoRes.BundleStatus == common.BundleExpired {
|
||||
confirmRemark = "套餐已过期"
|
||||
// 直接提交
|
||||
_, err = service.CastProvider.UpdateCompetitiveReportStatus(context.Background(), &cast.UpdateCompetitiveReportStatusReq{
|
||||
WorkAction: cast.WorkActionENUM_CONFIRM,
|
||||
Uuid: reportUuid,
|
||||
ConfirmRemark: confirmRemark,
|
||||
ConfirmStatus: 3,
|
||||
})
|
||||
if err != nil {
|
||||
zap.L().Error("autoConfirmReport UpdateCompetitiveReportStatus", zap.Any("err", err))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
// 判断竞品报告的提交时间是否在现在的套餐期间范围之内
|
||||
submitTime, err := time.Parse("2006-01-02 15:04:05", infoResp.SubmitTime)
|
||||
if err != nil {
|
||||
zap.L().Error("autoConfirmReport ParseSubmitTime", zap.Any("err", err))
|
||||
return
|
||||
}
|
||||
if submitTime.Before(time.Unix(balanceInfoRes.PayTime, 0)) {
|
||||
// confirmRemark = "该报告提交时间不在该套餐期间范围之内"
|
||||
confirmRemark = "系统自动确认"
|
||||
usedType = 0
|
||||
// 直接提交
|
||||
_, err = service.CastProvider.UpdateCompetitiveReportStatus(context.Background(), &cast.UpdateCompetitiveReportStatusReq{
|
||||
WorkAction: cast.WorkActionENUM_CONFIRM,
|
||||
Uuid: reportUuid,
|
||||
ConfirmRemark: confirmRemark,
|
||||
CostType: usedType,
|
||||
ConfirmStatus: 1,
|
||||
ConfirmType: 2,
|
||||
})
|
||||
if err != nil {
|
||||
zap.L().Error("autoConfirmReport UpdateCompetitiveReportStatus", zap.Any("err", err))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var addBalanceReq bundle.AddBundleBalanceReq
|
||||
addBalanceReq.UserId = int32(userID)
|
||||
// 检查竞品报告余量
|
||||
|
||||
132
pkg/utils/pdf.go
132
pkg/utils/pdf.go
@ -48,6 +48,9 @@ func loadChineseFont(pdf *gofpdf.Fpdf, fontPath string) error {
|
||||
|
||||
// GeneratePDF 生成PDF文件
|
||||
func GeneratePDF(text, imageURL, outputPath, fontPath string) error {
|
||||
if text == "" {
|
||||
return errors.New("文本不能为空")
|
||||
}
|
||||
// 创建PDF实例,P=纵向,mm=毫米单位,A4=页面大小
|
||||
pdf := gofpdf.New("P", "mm", "A4", "")
|
||||
|
||||
@ -80,59 +83,86 @@ func GeneratePDF(text, imageURL, outputPath, fontPath string) error {
|
||||
lineHeight := 7.0
|
||||
pdf.MultiCell(textWidth, lineHeight, cleanedText, "", "L", false)
|
||||
|
||||
// 添加一些间距
|
||||
pdf.Ln(5)
|
||||
// 如果提供了图片URL,则添加图片
|
||||
if imageURL != "" {
|
||||
// 添加一些间距
|
||||
pdf.Ln(5)
|
||||
|
||||
// 解析URL获取文件扩展名
|
||||
u, err := url.Parse(imageURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("图片链接解析错误: %v", err)
|
||||
// 解析URL获取文件扩展名
|
||||
u, err := url.Parse(imageURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("图片链接解析错误: %v", err)
|
||||
}
|
||||
fileExt := filepath.Ext(u.Path)
|
||||
// 如果没有扩展名,默认使用.jpg
|
||||
if fileExt == "" {
|
||||
fileExt = ".jpg"
|
||||
}
|
||||
|
||||
// 下载图片
|
||||
resp, err := http.Get(imageURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("下载图片失败: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 读取图片数据
|
||||
imageData, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("读取图片数据失败: %v", err)
|
||||
}
|
||||
|
||||
// 将图片数据保存到临时文件(gofpdf需要文件路径)
|
||||
tmpFile, err := os.CreateTemp("", "pdf_image_*"+fileExt)
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建临时文件失败: %v", err)
|
||||
}
|
||||
defer os.Remove(tmpFile.Name()) // 使用完后删除临时文件
|
||||
defer tmpFile.Close()
|
||||
|
||||
// 写入图片数据到临时文件
|
||||
_, err = tmpFile.Write(imageData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("写入临时文件失败: %v", err)
|
||||
}
|
||||
tmpFile.Close()
|
||||
|
||||
// A4纵向页面宽度210mm,减去左右边距40mm,可用宽度170mm
|
||||
// 图片宽度设为可用宽度的70%
|
||||
imageWidth := textWidth * 0.7
|
||||
// 计算居中位置:页面宽度210mm,图片居中
|
||||
imageX := (210.0 - imageWidth) / 2
|
||||
currentY := pdf.GetY()
|
||||
|
||||
// 注册图片并获取原始尺寸,用于计算缩放后的高度
|
||||
imgInfo := pdf.RegisterImageOptions(tmpFile.Name(), gofpdf.ImageOptions{})
|
||||
if imgInfo == nil {
|
||||
return fmt.Errorf("注册图片失败")
|
||||
}
|
||||
|
||||
// 计算缩放后的图片高度(按比例缩放)
|
||||
// 原始宽度:原始高度 = 缩放后宽度:缩放后高度
|
||||
originalWidth, originalHeight := imgInfo.Extent()
|
||||
imageHeight := (imageWidth / originalWidth) * originalHeight
|
||||
|
||||
// A4页面高度297mm,底部边距10mm,计算可用的最大Y坐标
|
||||
pageHeight := 297.0
|
||||
bottomMargin := 10.0
|
||||
maxY := pageHeight - bottomMargin
|
||||
|
||||
// 检查当前页面剩余空间是否足够放下图片
|
||||
// 如果图片底部会超出页面可用区域,则添加新页面
|
||||
if currentY+imageHeight > maxY {
|
||||
pdf.AddPage()
|
||||
// 新页面从顶部边距开始
|
||||
currentY = 10.0
|
||||
}
|
||||
|
||||
// 添加图片
|
||||
// ImageOptions参数:图片路径、x坐标、y坐标、宽度、高度、是否流式布局、选项、链接
|
||||
// 高度设为0表示按比例自动计算
|
||||
pdf.ImageOptions(tmpFile.Name(), imageX, currentY, imageWidth, 0, false, gofpdf.ImageOptions{}, 0, "")
|
||||
}
|
||||
fileExt := filepath.Ext(u.Path)
|
||||
// 如果没有扩展名,默认使用.jpg
|
||||
if fileExt == "" {
|
||||
fileExt = ".jpg"
|
||||
}
|
||||
|
||||
// 下载图片
|
||||
resp, err := http.Get(imageURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("下载图片失败: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 读取图片数据
|
||||
imageData, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("读取图片数据失败: %v", err)
|
||||
}
|
||||
|
||||
// 将图片数据保存到临时文件(gofpdf需要文件路径)
|
||||
tmpFile, err := os.CreateTemp("", "pdf_image_*"+fileExt)
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建临时文件失败: %v", err)
|
||||
}
|
||||
defer os.Remove(tmpFile.Name()) // 使用完后删除临时文件
|
||||
defer tmpFile.Close()
|
||||
|
||||
// 写入图片数据到临时文件
|
||||
_, err = tmpFile.Write(imageData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("写入临时文件失败: %v", err)
|
||||
}
|
||||
tmpFile.Close()
|
||||
|
||||
// A4纵向页面宽度210mm,减去左右边距40mm,可用宽度170mm
|
||||
// 图片宽度设为可用宽度的70%
|
||||
imageWidth := textWidth * 0.7
|
||||
// 计算居中位置:页面宽度210mm,图片居中
|
||||
imageX := (210.0 - imageWidth) / 2
|
||||
currentY := pdf.GetY()
|
||||
|
||||
// 添加图片
|
||||
// ImageOptions参数:图片路径、x坐标、y坐标、宽度、高度、是否流式布局、选项、链接
|
||||
// 高度设为0表示按比例自动计算
|
||||
pdf.ImageOptions(tmpFile.Name(), imageX, currentY, imageWidth, 0, false, gofpdf.ImageOptions{}, 0, "")
|
||||
|
||||
// 生成并保存PDF文件
|
||||
err = pdf.OutputFileAndClose(outputPath)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user