diff --git a/pkg/service/cast/report.go b/pkg/service/cast/report.go index 990cd0f..11706ae 100644 --- a/pkg/service/cast/report.go +++ b/pkg/service/cast/report.go @@ -13,13 +13,18 @@ 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/service/upload" "fonchain-fiee/pkg/utils" + "net/url" "os" "path/filepath" "strconv" + "strings" "time" + "github.com/google/uuid" + "dubbo.apache.org/dubbo-go/v3/common/constant" "github.com/gin-gonic/gin" "github.com/xuri/excelize/v2" @@ -90,6 +95,17 @@ func CreateCompetitiveReport(ctx *gin.Context) { return } + // 检查图片URL是否包含阿里云,如果包含则下载并重新上传到OSS + if req.ImageUrl != "" { + newImageUrl, err := checkAndReuploadImageForReport(req.ImageUrl) + if err != nil { + zap.L().Error("图片重新上传失败", zap.String("imageUrl", req.ImageUrl), zap.Error(err)) + service.Error(ctx, fmt.Errorf("图片处理失败: %v", err)) + return + } + req.ImageUrl = newImageUrl + } + // 如果提供了报告内容和图片URL,则生成PDF并上传 if req.ReportContent != "" { // 生成临时PDF文件路径 @@ -815,3 +831,48 @@ func autoConfirmReport(ctx context.Context, reportUuid string) (err error) { } return } + +// checkAndReuploadImageForReport 检查图片链接是否包含aliyuncs.com,如果包含则下载并重新上传到OSS +func checkAndReuploadImageForReport(imageUrl string) (string, error) { + // 如果不包含阿里云域名,直接返回原URL + if !strings.Contains(imageUrl, "aliyuncs.com") { + return imageUrl, nil + } + + // 解析URL获取文件扩展名 + u, err := url.Parse(imageUrl) + if err != nil { + return "", errors.New("图片链接解析错误") + } + fileExt := filepath.Ext(u.Path) + + // 确保目录存在 + _, err = utils.CheckDirPath("./runtime/report_pdf/", true) + if err != nil { + return "", fmt.Errorf("创建目录失败: %v", err) + } + + // 下载图片到本地 + fullPath, err := utils.SaveUrlFileDisk(imageUrl, "runtime/report_pdf/", uuid.New().String()+fileExt) + if err != nil { + zap.L().Error("SaveUrlFileDisk err", zap.Error(err)) + return "", errors.New("保存图片失败") + } + + // 上传到OSS + compressUrl, err := upload.PutBos(fullPath, "image", true) + if err != nil { + return "", errors.New(common.ErrorUploadFile) + } + + // 清理临时文件 + defer func() { + if fullPath != "" { + if _, err := os.Stat(fullPath); err == nil { + os.Remove(fullPath) + } + } + }() + + return compressUrl, nil +} diff --git a/pkg/utils/pdf.go b/pkg/utils/pdf.go index 60d8688..a89ff51 100644 --- a/pkg/utils/pdf.go +++ b/pkg/utils/pdf.go @@ -5,11 +5,28 @@ import ( "fmt" "io" "net/http" + "net/url" "os" + "path/filepath" + "unicode" "github.com/phpdave11/gofpdf" ) +// cleanTextForPDF 清理文本,移除PDF不支持的字符(如emoji) +// gofpdf库不支持某些特殊字符 +func cleanTextForPDF(text string) string { + var result []rune + for _, r := range text { + // 保留基本多文种平面(BMP)内的字符(码点 <= 0xFFFF) + // 这样可以保留中文、英文、数字等常用字符,但过滤掉emoji等特殊字符 + if r <= 0xFFFF && (unicode.IsPrint(r) || unicode.IsSpace(r)) { + result = append(result, r) + } + } + return string(result) +} + // loadChineseFont 加载中文字体 func loadChineseFont(pdf *gofpdf.Fpdf, fontPath string) error { var fontData []byte @@ -52,17 +69,31 @@ func GeneratePDF(text, imageURL, outputPath, fontPath string) error { // 设置当前位置(x, y),从左上角开始 pdf.SetXY(20, 10) + // 清理文本,移除PDF不支持的字符(如emoji) + cleanedText := cleanTextForPDF(text) + // 添加文本内容 // 使用MultiCell方法处理多行文本,支持自动换行 // 参数:宽度、行高、文本内容、边框、对齐方式、是否填充 // A4页面宽度210mm,减去左右边距40mm,可用宽度170mm textWidth := 170.0 lineHeight := 7.0 - pdf.MultiCell(textWidth, lineHeight, text, "", "L", false) + pdf.MultiCell(textWidth, lineHeight, cleanedText, "", "L", false) // 添加一些间距 pdf.Ln(5) + // 解析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 { @@ -77,7 +108,7 @@ func GeneratePDF(text, imageURL, outputPath, fontPath string) error { } // 将图片数据保存到临时文件(gofpdf需要文件路径) - tmpFile, err := os.CreateTemp("", "pdf_image_*.jpg") + tmpFile, err := os.CreateTemp("", "pdf_image_*"+fileExt) if err != nil { return fmt.Errorf("创建临时文件失败: %v", err) }