package tool import ( "bufio" "bytes" "encoding/base64" "encoding/json" "fmt" "image" "image/jpeg" "image/png" "io" "net/http" "os" "path" "path/filepath" "planA/planB/initialization/golabl" modulesImage "planA/planB/modules/image" "planA/planB/service" planBType "planA/planB/type" planBTypeKfz "planA/planB/type/kfz" planBTypeModules "planA/planB/type/modules" planAType "planA/type" "strconv" "strings" "time" "github.com/nfnt/resize" ) // BuildPrice 价格处理 // @param priceMods 价格处理列表 // @param price 价格 // @return int64 处理后的价格 func BuildPrice(priceMods []planAType.PriceMod, price int64) int64 { for _, mod := range priceMods { if price >= mod.Min && price <= mod.Max { newPrice := price * (100 + mod.MarkupRate) / 100 newPrice += mod.MarkupValue return newPrice } } return 0 // 没有匹配到价格模版,直接返回0 } // ReturnErr 接口返回错误处理 func ReturnErr(logUuid string, taskMsg planAType.TaskBody, typeStr string, err error) (string, error) { dataRetBaty, marshalErr := json.Marshal(taskMsg) if marshalErr != nil { return string(dataRetBaty), fmt.Errorf("[%s] json.Marshal错误: %v", logUuid, marshalErr) } return string(dataRetBaty), fmt.Errorf("[%s] %v错误: %v", logUuid, typeStr, err) } // BuildGoodsName 构建商品名称 // @param goodsNamePrefix 商品名称前缀 // @param goodsNameSuffix 商品名称后缀 // @param titleConsistOf 标题组成 // @param spaceCharacter 间隔符 1=空格 // @param bookInfo 图书信息 // @return string 商品名称 func BuildGoodsName(goodsNamePrefix string, goodsNameSuffix string, titleConsistOf string, spaceCharacter string, bookInfo planAType.BookInfo) string { // 解析标题组成 if titleConsistOf == "" { titleConsistOf = "1:true" // 默认使用书名 } // 解析标题组成 titleOfArr := strings.Split(titleConsistOf, ",") // 间隔符 separator := "" if spaceCharacter == "1" { separator = " " } // 构建标题 title := goodsNamePrefix + separator // 遍历标题组成 for _, item := range titleOfArr { // 解析标题组成 parts := strings.Split(item, ":") // 判断是否需要添加标题 if len(parts) == 2 && parts[1] == "true" { switch parts[0] { case "0": // ISBN title += separator + bookInfo.Isbn case "1": // 书名 title += separator + bookInfo.BookName case "2": // 作者 title += separator + bookInfo.Author case "3": // 出版社 title += separator + bookInfo.Publishing case "4": // 出版时间 title += separator + bookInfo.PublicationDate case "5": // 装帧 title += separator + bookInfo.Binding case "6": // 开本 title += separator + strconv.FormatInt(bookInfo.Format, 10) } } } // 添加后缀 title += separator + goodsNameSuffix // 如果标题超过60个字符,截取前60个字符 if StringLength(title) > 60 { title = SubstringByWidth(title, 60) } //去掉首尾双引号 title = strings.Trim(title, "\"") return title } // BuildCarouselGallery 构建轮播图 // @param carouseLastImgUrlArray 最后一张图 // @param oldCarouselUrlArray 旧轮播图 // @param carouselUrlArray 轮播图组 // @param watermarkPosition 水印位置 0 全部 1第一张 // @return []string 轮播图组 func BuildCarouselGallery(carouseLastImgUrlArray []string, oldCarouselUrlArray []string, carouselUrlArray []string, watermarkPosition string) []string { // 查看轮播图组长度 if len(carouselUrlArray)+len(carouseLastImgUrlArray) < 10 { length := 10 - (len(carouselUrlArray) + len(carouseLastImgUrlArray)) // 向轮播图组中添加图片 添加最后一张图片 if len(carouselUrlArray) > 0 { for i := 0; i < length; i++ { if carouselUrlArray[len(carouselUrlArray)-1] != "" { if watermarkPosition == "1" { // 使用不打水印的图片补充 carouselUrlArray = append(carouselUrlArray, oldCarouselUrlArray[len(oldCarouselUrlArray)-1]) } else { // 使用打水印的图片补充 carouselUrlArray = append(carouselUrlArray, carouselUrlArray[len(carouselUrlArray)-1]) } } } } } // 合并数组 carouselUrlArray = append(carouselUrlArray, carouseLastImgUrlArray...) return carouselUrlArray } // BuildCarouselGalleryOld 构建轮播图 // @param carouseLastImgUrlArray 最后一张图 // @param carouselUrlArray 轮播图组 // @return []string 轮播图组 func BuildCarouselGalleryOld(carouseLastImgUrlArray []string, carouselUrlArray []string) []string { // 查看轮播图组长度 if len(carouselUrlArray)+len(carouseLastImgUrlArray) < 10 { length := 10 - (len(carouselUrlArray) + len(carouseLastImgUrlArray)) // 向轮播图组中添加图片 添加最后一张图片 if len(carouselUrlArray) > 0 { for i := 0; i < length; i++ { if carouselUrlArray[len(carouselUrlArray)-1] != "" { carouselUrlArray = append(carouselUrlArray, carouselUrlArray[len(carouselUrlArray)-1]) } } } } // 合并数组 carouselUrlArray = append(carouselUrlArray, carouseLastImgUrlArray...) return carouselUrlArray } // BuildDetailGallery 构建详情图 // @param goodsDetailFirstImgUrlArray 商详头图 // @param goodsDetailLastImgUrlArray 商详尾图 // @param detailUrlObject 商详图片 // @param mainImage 主图 // @return []string 详情图组 func BuildDetailGallery(goodsDetailFirstImgUrlArray []string, goodsDetailLastImgUrlArray []string, detailUrlObject planAType.DetailImageObject, mainImage string) []string { // 合并数组 简介图+目录图 imgArr := append(detailUrlObject.IntroductionUrl, detailUrlObject.CatalogueUrl...) // 合并数组 简介图+目录图+实拍图 //imgArr = append(imgArr, detailUrlObject.LiveShootingUrl...) // 合并数组 简介图+目录图+实拍图+主图 imgArr = append(imgArr, mainImage) // 合并数组 简介图+目录图+实拍图+主图+其他图 imgArr = append(imgArr, detailUrlObject.OtherUrl...) // 合并数组 商详头图+简介图+目录图+实拍图+主图+其他图 imgArr = append(goodsDetailFirstImgUrlArray, imgArr...) // 合并数组 商详头图+简介图+目录图+实拍图+主图+其他图+商详尾图 imgArr = append(imgArr, goodsDetailLastImgUrlArray...) return imgArr } // BuildGoodsPrice 构建商品价格 // @param bookInfoPrice 图书价格 // @return int64 商品价格 func BuildGoodsPrice(price int64) int64 { return price * 4 } // ReturnSuccess 添加商品返回成功处理 func ReturnSuccess(taskMsg planAType.TaskBody) (string, error) { dataRetBaty, marshalErr := json.Marshal(taskMsg) if marshalErr != nil { return string(dataRetBaty), fmt.Errorf("json.Marshal错误: %w", marshalErr) } return string(dataRetBaty), nil } // StringLength 计算字符串显示长度 // @param s 字符串 // @return int 字符串显示长度 func StringLength(s string) int { length := 0 for _, r := range s { if r > 255 { // 非ASCII字符(如中文) length += 2 } else { // ASCII字符(如英文、数字) length += 1 } } return length } // SubstringByWidth 按显示宽度截取字符串 func SubstringByWidth(s string, maxWidth int) string { width := 0 for i, r := range s { if r > 255 { width += 2 } else { width += 1 } if width > maxWidth { return s[:i] // 返回截取的部分 } } return s // 如果整个字符串都不超过maxWidth,返回原字符串 } // FilterWord 违规词处理 // @param taskMsg 任务信息 func FilterWord(taskMsg *planAType.TaskBody) error { substitution, httpBannedWordSubstitutionErr := HttpFilterWord(taskMsg.BookInfo.Isbn, taskMsg.BookInfo.BookName, taskMsg.BookInfo.Author, taskMsg.BookInfo.Publishing) if httpBannedWordSubstitutionErr != nil { return fmt.Errorf("HttpFilterWord 违禁词处理失败-原因来自:%v", httpBannedWordSubstitutionErr) } if golabl.Config.Server.ReplaceMark == "0" && len(substitution.Data) > 0 { errMsg := "违规词命中 " for _, v := range substitution.Data { errMsg = errMsg + " " + v.AddTxt + "(" + v.MatchType + ") " } return fmt.Errorf(errMsg) } if golabl.Config.Server.ReplaceMark == "1" && len(substitution.Data) > 0 { //替换违禁词 taskMsg.BookInfo.BookName = substitution.BookName taskMsg.BookInfo.Author = substitution.Author taskMsg.BookInfo.Publishing = substitution.Publisher taskMsg.BookInfo.Isbn = substitution.Isbn } return nil } // AddWatermarkFromURLExs 打水印 // @param imgUrl 轮播图组 // @param watermarkImgUrl 水印图片 // @param watermarkPosition 水印位置 0 全部 1第一张 // @return []string 轮播图组 // @return error 错误信息 func AddWatermarkFromURLExs(imgUrl []string, watermarkImgUrl string, watermarkPosition string) ([]planBTypeModules.ImageResult, error) { var watermarkFromURLExsBase64Arr []planBTypeModules.ImageResult // 循环轮播图组给图片打水印 for i := 0; i < len(imgUrl); i++ { var newImgJson string var addWatermarkFromURLExsErr error // 给图片打水印,带重试机制,最大重试次数为3 maxRetries := 3 for retryCount := 0; retryCount <= maxRetries; retryCount++ { newImgJson, addWatermarkFromURLExsErr = modulesImage.AddWatermarkFromURLExs(imgUrl[i], watermarkImgUrl) // 判断是否包含超时错误 if addWatermarkFromURLExsErr != nil && strings.Contains(addWatermarkFromURLExsErr.Error(), "dialing to the given TCP address timed out") { if retryCount < maxRetries { // 重试前等待一段时间(可选) time.Sleep(time.Duration(retryCount+1) * time.Second) continue } } // 如果没有错误或者不是超时错误,跳出重试循环 break } if addWatermarkFromURLExsErr != nil { return watermarkFromURLExsBase64Arr, fmt.Errorf("给图片打水印错误 %w", addWatermarkFromURLExsErr) } // 将 newImg 转为结构体 var newImg planBTypeModules.ImageResult unmarshalErr := json.Unmarshal([]byte(newImgJson), &newImg) if unmarshalErr != nil { return nil, fmt.Errorf("解析失败 %w 原始数据 %v", unmarshalErr, newImgJson) } watermarkFromURLExsBase64Arr = append(watermarkFromURLExsBase64Arr, newImg) if watermarkPosition == "1" { break } } return watermarkFromURLExsBase64Arr, nil } // UploadImageToPdd 将图片上传到拼多多 // @param watermarkFromURLExsBase64Arr 待上传的base64图片列表 // @return []string 图片列表 // @return error 错误信息 func UploadImageToPdd(watermarkFromURLExsBase64Arr []planBTypeModules.ImageResult) ([]string, error) { var imageUrlArr []string for _, watermarkFromURLExsBase64 := range watermarkFromURLExsBase64Arr { var pddImg planBTypeModules.GoodsImageUploadResponse imageUrl, pddGoodsImageUploadErr := golabl.PddDll.PddGoodsImageUpload(golabl.Config.PddConfig.ClientId, golabl.Config.PddConfig.ClientSecret, golabl.Task.Header.ShopMsg.Token, watermarkFromURLExsBase64.Data) if pddGoodsImageUploadErr != nil { return imageUrlArr, pddGoodsImageUploadErr } // 解析 JSON字符串 unmarshalErr := json.Unmarshal([]byte(imageUrl), &pddImg) if unmarshalErr != nil { return imageUrlArr, fmt.Errorf("解析拼多多 PddGoodsImageUpload 错误: %v [拼多多数据:%v]", unmarshalErr, imageUrl) } imageUrlArr = append(imageUrlArr, pddImg.GoodsImageUploadResponse.ImageURL) } return imageUrlArr, nil } // GetPlatformName 获取平台名称 func GetPlatformName() string { title := "" switch golabl.Task.Header.ShopType { //case 2: // return kongFuZi.NewKongfuzi(), nil case "1": title = title + "拼多多" case "5": title = title + "闲鱼" default: title = title + "其他平台 " + golabl.Task.Header.ShopType } return title } // GetTaskType 获取店铺类型 func GetTaskType() string { switch golabl.Task.Header.TaskType { case 1: //核价发布 return "核价发布" case 2: //表格发布 return "表格发布" case 3: //获取商品 return "获取商品" default: return "错误!" } } // GetWatermarkImg 获取水印图片 // @return string 水印图片 base64 func GetWatermarkImg() (string, error) { // 1. 获取日期 t := time.Unix(golabl.Task.Header.TaskCreateAt, 0) yearMonthDay := t.Format("2006-01-02") // 2. 获取文件后缀(去除前面的.) extWithDot := path.Ext(golabl.Task.Header.ShopMsg.WatermarkImgUrl) // 拼接本地存储路径 imgUrl := "img/watermark/" + yearMonthDay + "/" + golabl.Task.TaskId + extWithDot // 3. 判断本地文件是否存在 if _, err := os.Stat(imgUrl); err == nil { // 文件存在 → 直接读取并转base64返回 imgBytes, err := os.ReadFile(imgUrl) if err != nil { return "", err } return base64.StdEncoding.EncodeToString(imgBytes), nil } // 4. 文件不存在 → 创建目录、下载图片、保存本地、转base64 // 创建多级目录 dirPath := filepath.Dir(imgUrl) if err := os.MkdirAll(dirPath, 0755); err != nil { return "", err } // 下载远程图片 resp, err := http.Get(golabl.Task.Header.ShopMsg.WatermarkImgUrl) if err != nil { return "", err } defer resp.Body.Close() // 判断下载响应是否成功 if resp.StatusCode != http.StatusOK { fmt.Printf("下载水印图片响应异常: %s\n", resp.Status) return "", fmt.Errorf("下载水印图片失败: %s", resp.Status) } // 读取图片二进制数据 imgBytes, err := io.ReadAll(resp.Body) if err != nil { fmt.Printf("读取下载的图片数据失败: %v\n", err) return "", err } // 保存图片到本地 if err := os.WriteFile(imgUrl, imgBytes, 0644); err != nil { fmt.Printf("保存水印图片到本地失败: %v\n", err) return "", err } // 5. 转base64返回 return base64.StdEncoding.EncodeToString(imgBytes), nil } // GetSkuWatermarkImg 获取sku水印图片 // @return string 水印图片 base64 func GetSkuWatermarkImg() (string, error) { // 1. 获取日期 t := time.Unix(golabl.Task.Header.TaskCreateAt, 0) yearMonthDay := t.Format("2006-01-02") // 2. 获取文件后缀(去除前面的.) extWithDot := path.Ext(golabl.Task.Header.ShopMsg.SkuWatermarkImgUrl) // 拼接本地存储路径 imgUrl := "img/skuwatermark/" + yearMonthDay + "/" + golabl.Task.TaskId + extWithDot // 3. 判断本地文件是否存在 if _, err := os.Stat(imgUrl); err == nil { // 文件存在 → 直接读取并转base64返回 imgBytes, err := os.ReadFile(imgUrl) if err != nil { return "", err } return base64.StdEncoding.EncodeToString(imgBytes), nil } // 4. 文件不存在 → 创建目录、下载图片、保存本地、转base64 // 创建多级目录 dirPath := filepath.Dir(imgUrl) if err := os.MkdirAll(dirPath, 0755); err != nil { return "", err } // 下载远程图片 resp, err := http.Get(golabl.Task.Header.ShopMsg.SkuWatermarkImgUrl) if err != nil { return "", err } defer resp.Body.Close() // 判断下载响应是否成功 if resp.StatusCode != http.StatusOK { fmt.Printf("下载水印图片响应异常: %s\n", resp.Status) return "", fmt.Errorf("下载水印图片失败: %s", resp.Status) } // 读取图片二进制数据 imgBytes, err := io.ReadAll(resp.Body) if err != nil { fmt.Printf("读取下载的图片数据失败: %v\n", err) return "", err } // 保存图片到本地 if err := os.WriteFile(imgUrl, imgBytes, 0644); err != nil { fmt.Printf("保存水印图片到本地失败: %v\n", err) return "", err } // 5. 转base64返回 return base64.StdEncoding.EncodeToString(imgBytes), nil } // UpdateTaskHeader 更新头部信息 // @return error 错误信息 func UpdateTaskHeader() error { //通过 footer 来更新 header 的计数 golabl.Task.Header.TaskCountWait = golabl.Task.Footer.TaskCountWait.Load() golabl.Task.Header.TaskCountOver = golabl.Task.Footer.TaskCountOver.Load() golabl.Task.Header.TaskCountSuccess = golabl.Task.Footer.TaskCountSuccess.Load() golabl.Task.Header.TaskCountError = golabl.Task.Footer.TaskCountError.Load() golabl.Task.Header.LastIndex = golabl.Logic.LastIndex return service.UpdateTaskHeaderCount() } // UpdateTaskProgress 更新拉取商品进度 // @param con int64 更新进度数 // @return error 错误信息 func UpdateTaskProgress(con int64) error { // 更新 进度 if updateTaskFooterErr := service.UpdateTaskFooter(1, con); updateTaskFooterErr != nil { return updateTaskFooterErr } // 重新获取 footer信息 if getTaskFooterErr := service.GetTaskFooter(); getTaskFooterErr != nil { return getTaskFooterErr } if updateTaskHeaderCountErr := UpdateTaskHeader(); updateTaskHeaderCountErr != nil { return updateTaskHeaderCountErr } return nil } // FenToYuan 将金额从分转换为元 // 参数:fen - 金额(分),int64类型 // 返回值:金额(元),string类型 func FenToYuan(fen int64) string { yuan := float64(fen) / 100.0 return fmt.Sprintf("%.2f", yuan) } // FenToYuanFloat64 将金额从分转换为元 // 参数:fen - 金额(分),int64类型 // 返回值:金额(元),float64类型 func FenToYuanFloat64(fen int64) float64 { return float64(fen) / 100.0 } // IsShopIDExists 判断店铺ID是否存在于shopId.txt文件中 func IsShopIDExists(targetShopID string) (bool, error) { // 打开文件 file, err := os.Open("shopId.txt") if err != nil { return false, fmt.Errorf("无法打开文件: %w", err) } defer file.Close() // 逐行扫描 scanner := bufio.NewScanner(file) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) // 去除空格和换行符 // 忽略空行 if line == "" { continue } if line == targetShopID { return true, nil } } if err := scanner.Err(); err != nil { return false, fmt.Errorf("读取文件出错: %w", err) } return false, nil } // AppendTextToFile 在文件中追加文本 // @param filePath 文件路径 // @param text 要追加的文本 // @return error 错误信息 func AppendTextToFile(filePath string, text string) error { // 获取文件所在的目录 dir := filepath.Dir(filePath) // 创建目录(如果不存在) if err := os.MkdirAll(dir, 0755); err != nil { return fmt.Errorf("创建目录失败: %v", err) } // 以追加模式打开文件,如果文件不存在则创建 file, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return fmt.Errorf("打开文件失败: %v", err) } defer file.Close() // 写入文本并添加换行符 if _, err := file.WriteString(text + "\n"); err != nil { return fmt.Errorf("写入文件失败: %v", err) } return nil } // UploadImageToKfz 将图片上传到孔夫子 // @param watermarkFromURLExsBase64Arr 待上传的base64图片列表 // @return []string 图片列表 // @return error 错误信息 func UploadImageToKfz(watermarkFromURLExsBase64Arr []planBTypeModules.ImageResult) ([]string, error) { var imageUrlArr []string for _, watermarkFromURLExsBase64 := range watermarkFromURLExsBase64Arr { //将图片保存到本地 imgTempUrl, saveBase64ImageByDateErr := SaveBase64ImageByDate(watermarkFromURLExsBase64.Data, golabl.Config.FileUrl.KfzImgTempUrl) if saveBase64ImageByDateErr != nil { return nil, saveBase64ImageByDateErr } //将图片上传到孔夫子 upload, kfzGoodsImageUploadErr := golabl.KfzDll.KfzGoodsImageUpload(golabl.Config.KfzConfig.AppId, golabl.Config.KfzConfig.AppSecret, golabl.Task.Header.ShopMsg.Token, imgTempUrl) if kfzGoodsImageUploadErr != nil { return nil, kfzGoodsImageUploadErr } // 解析数据 var uploadData planBTypeKfz.UploadImgRet unmarshalErr := json.Unmarshal([]byte(upload), &uploadData) if unmarshalErr != nil { return nil, fmt.Errorf("解析上传图片数据失败 %v", unmarshalErr) } if uploadData.ErrorResponse != nil && uploadData.ErrorResponse.Code != 0 { return nil, fmt.Errorf("上传图片失败 错误码 %v 错误描述 %v", uploadData.ErrorResponse.Code, uploadData.ErrorResponse.SubMsg) } if uploadData.SuccessResponse == nil { return nil, fmt.Errorf("上传图片成功但返回数据为空") } imageUrlArr = append(imageUrlArr, uploadData.SuccessResponse.Image.Url) } return imageUrlArr, nil } // SaveBase64ImageByDate 保存base64图片或下载URL图片到按年月日组织的文件夹中 // 参数1: input - base64编码的图片字符串 或 网络图片地址 // 参数2: basePath - 基础路径(如 D:\\file\\kfzImg) // 返回: 保存的完整图片地址和错误信息 func SaveBase64ImageByDate(input string, url string) (string, error) { // 去除首尾空格 input = strings.TrimSpace(input) var imageData []byte var err error // 判断是否为URL(以http://或https://开头) if strings.HasPrefix(strings.ToLower(input), "http://") || strings.HasPrefix(strings.ToLower(input), "https://") { // 处理URL情况:下载图片 imageData, err = downloadImage(input) if err != nil { return "", fmt.Errorf("下载图片失败: %v", err) } } else { // 处理base64情况 imageData, err = decodeBase64Image(input) if err != nil { return "", fmt.Errorf("base64解码失败: %v", err) } } // 生成按年月日的文件夹路径 now := time.Now() dateFolder := now.Format("2006-01-02") // 格式: 2026-05-11 saveDir := filepath.Join(url, dateFolder) // 创建目录(如果不存在) if err := os.MkdirAll(saveDir, 0755); err != nil { return "", fmt.Errorf("创建目录失败: %v", err) } // 生成唯一文件名(使用时间戳+随机数避免重名) timestamp := now.UnixNano() filename := fmt.Sprintf("%d.png", timestamp) savePath := filepath.Join(saveDir, filename) // 写入文件 err = os.WriteFile(savePath, imageData, 0644) if err != nil { return "", fmt.Errorf("保存图片失败: %v", err) } return savePath, nil } // decodeBase64Image 解码base64图片 func decodeBase64Image(base64Str string) ([]byte, error) { // 去除可能存在的base64头部信息 if idx := strings.Index(base64Str, ","); idx != -1 { base64Str = base64Str[idx+1:] } // 解码base64字符串 imageData, err := base64.StdEncoding.DecodeString(base64Str) if err != nil { return nil, err } return imageData, nil } // downloadImage 下载网络图片 func downloadImage(url string) ([]byte, error) { // 创建HTTP客户端(设置超时时间) client := &http.Client{ Timeout: 30 * time.Second, } // 发送GET请求 resp, err := client.Get(url) if err != nil { return nil, fmt.Errorf("HTTP请求失败: %v", err) } defer resp.Body.Close() // 检查HTTP状态码 if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("HTTP状态码异常: %d", resp.StatusCode) } // 检查Content-Type是否为图片 contentType := resp.Header.Get("Content-Type") if !strings.HasPrefix(contentType, "image/") { return nil, fmt.Errorf("非图片资源: Content-Type=%s", contentType) } // 读取图片数据 imageData, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("读取图片数据失败: %v", err) } return imageData, nil } // ProcessImage 处理图片(支持本地文件路径和HTTP URL) func ProcessImage(imageURL string) (string, string, error) { // 判断是本地文件还是HTTP URL if strings.HasPrefix(imageURL, "http://") || strings.HasPrefix(imageURL, "https://") { // HTTP URL:从网络获取图片 img, format, err := GetImageFromURL(imageURL) if err != nil { return "", "", err } return processImageAndConvert(img, format) } else { // 本地文件路径:从本地读取图片 img, format, err := GetImageFromLocalFile(imageURL) if err != nil { return "", "", err } return processImageAndConvert(img, format) } } // processImageAndConvert 处理图片并转换为base64 func processImageAndConvert(img image.Image, format string) (string, string, error) { bounds := img.Bounds() fmt.Printf("原始尺寸: %dx%d\n", bounds.Dx(), bounds.Dy()) var processedImg image.Image if bounds.Dx() == 800 && bounds.Dy() == 800 { processedImg = img } else { processedImg = ResizeImageHighQuality(img, 800, 800) } // 转换为base64 base64Str, err := ImageToBase64(processedImg, format) if err != nil { return "", "", fmt.Errorf("转换为base64失败: %v", err) } return base64Str, format, nil } // GetImageFromLocalFile 从本地文件获取图片 func GetImageFromLocalFile(filePath string) (image.Image, string, error) { // 打开本地文件 file, err := os.Open(filePath) if err != nil { return nil, "", fmt.Errorf("打开本地文件失败: %v", err) } defer file.Close() // 读取文件数据 data, err := io.ReadAll(file) if err != nil { return nil, "", fmt.Errorf("读取本地文件失败: %v", err) } // 尝试解码PNG img, err := png.Decode(bytes.NewReader(data)) if err == nil { return img, "png", nil } // 尝试解码JPEG img, err = jpeg.Decode(bytes.NewReader(data)) if err == nil { return img, "jpg", nil } return nil, "", fmt.Errorf("不支持的图片格式") } // ResizeImageHighQuality 高质量缩放图片 func ResizeImageHighQuality(img image.Image, width, height uint) image.Image { return resize.Resize(width, height, img, resize.Lanczos3) } // GetImageFromURL 从URL获取图片 func GetImageFromURL(url string) (image.Image, string, error) { resp, err := http.Get(url) if err != nil { return nil, "", err } defer resp.Body.Close() data, err := io.ReadAll(resp.Body) if err != nil { return nil, "", err } // 尝试解码PNG img, err := png.Decode(strings.NewReader(string(data))) if err == nil { return img, "png", nil } // 尝试解码JPEG img, err = jpeg.Decode(strings.NewReader(string(data))) if err == nil { return img, "jpg", nil } return nil, "", fmt.Errorf("不支持的图片格式") } // ImageToBase64 将图片转换为base64 func ImageToBase64(img image.Image, format string) (string, error) { buf := new(strings.Builder) if format == "png" { err := png.Encode(buf, img) if err != nil { return "", err } } else { err := jpeg.Encode(buf, img, &jpeg.Options{Quality: 95}) if err != nil { return "", err } } base64Str := base64.StdEncoding.EncodeToString([]byte(buf.String())) return fmt.Sprintf("data:image/%s;base64,%s", format, base64Str), nil } // GetGoodsByShopIdAndIsbn 根据店铺id与isbn获取商品 func GetGoodsByShopIdAndIsbn(shopId, isbn string) (planBType.GetShopGoodsByShopIdAndIsbn, error) { var getShopGoodsByShopIdAndIsbn planBType.GetShopGoodsByShopIdAndIsbn params := map[string]string{ "shopId": shopId, "isbn": isbn, } withParams, buildURLWithParamsErr := BuildURLWithParams(golabl.Config.FileUrl.GetPddGoodsShopIdIsbnUrl, params) if buildURLWithParamsErr != nil { return getShopGoodsByShopIdAndIsbn, buildURLWithParamsErr } _, resStr, httpGetRequestErr := HttpGetRequest(withParams) if httpGetRequestErr != nil { return getShopGoodsByShopIdAndIsbn, httpGetRequestErr } unmarshalErr := json.Unmarshal([]byte(resStr), &getShopGoodsByShopIdAndIsbn) if unmarshalErr != nil { return getShopGoodsByShopIdAndIsbn, unmarshalErr } return getShopGoodsByShopIdAndIsbn, nil }