From 13ea5d7f9841fe4651a8a5c1afc4d79bfb969e14 Mon Sep 17 00:00:00 2001 From: lixiangwuxian Date: Sat, 8 Mar 2025 16:10:06 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=87=8D=E6=9E=84=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E5=A4=84=E7=90=86=E6=A8=A1=E5=9D=97=EF=BC=8C=E5=BC=95=E5=85=A5?= =?UTF-8?q?=E7=BB=9F=E4=B8=80=E7=9A=84=E6=B6=88=E6=81=AF=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E5=92=8C=E7=B1=BB=E5=9E=8B=E5=AE=89=E5=85=A8=E7=9A=84=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- action/action.go | 9 +- go.mod | 1 + go.sum | 2 + handler/auth/auth.go | 6 +- handler/beatleader/beatleader.go | 40 +++++++- handler/drawback/drawback.go | 8 +- handler/getweb/getweb.go | 9 +- handler/xibao/xibao.go | 33 +++++- message/at.go | 36 +++++++ message/cq_message.go | 6 ++ message/image.go | 69 +++++++++++++ message/reply.go | 39 ++++++++ model/cq_message.go | 143 -------------------------- service/xibao/image_gen.go | 7 +- util/picture.go | 166 +++++++++++++++++++++++++++++++ util/url.go | 3 +- 16 files changed, 412 insertions(+), 165 deletions(-) create mode 100644 message/at.go create mode 100644 message/cq_message.go create mode 100644 message/image.go create mode 100644 message/reply.go delete mode 100644 model/cq_message.go create mode 100644 util/picture.go diff --git a/action/action.go b/action/action.go index 6d31461..1fe3e93 100644 --- a/action/action.go +++ b/action/action.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" + "git.lxtend.com/qqbot/message" "git.lxtend.com/qqbot/model" "github.com/gorilla/websocket" ) @@ -31,7 +32,13 @@ func (am *actionManager) SendAction(action string) error { func (am *actionManager) SendMsg(reply model.Reply) error { if reply.ReferOriginMsg { - reply.ReplyMsg = fmt.Sprintf("%s%s", GenReplyCQ(reply.FromMsg.OriginMsgId, "", 0, 0, 0), reply.ReplyMsg) + replyMsg := message.ReplyMessage{ + Type: "reply", + Data: message.ReplyData{ + ID: int(reply.FromMsg.OriginMsgId), + }, + } + reply.ReplyMsg = fmt.Sprintf("%s%s", replyMsg.ToCQString(), reply.ReplyMsg) } sendPkg := model.GenSendPkg(reply.FromMsg.UserId, reply.FromMsg.GroupInfo.GroupId, reply.ReplyMsg, false) sendPkgJson, err := json.Marshal(sendPkg) diff --git a/go.mod b/go.mod index 8937bb6..fb6db2b 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/gorilla/websocket v1.5.3 github.com/jmoiron/sqlx v1.4.0 github.com/mattn/go-sqlite3 v1.14.23 + github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/sashabaranov/go-openai v1.36.1 golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c golang.org/x/image v0.21.0 diff --git a/go.sum b/go.sum index 67a4cf6..880f491 100644 --- a/go.sum +++ b/go.sum @@ -123,6 +123,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= diff --git a/handler/auth/auth.go b/handler/auth/auth.go index 2c1e68f..f1f23c2 100644 --- a/handler/auth/auth.go +++ b/handler/auth/auth.go @@ -8,6 +8,7 @@ import ( "git.lxtend.com/qqbot/auth" "git.lxtend.com/qqbot/constants" "git.lxtend.com/qqbot/handler" + "git.lxtend.com/qqbot/message" "git.lxtend.com/qqbot/model" ) @@ -36,8 +37,9 @@ func setUserLevel(msg model.Message) (reply model.Reply) { } } userText := tokens[1] - if userId, err := model.ParseAtMessage(userText); err == nil { - userText = userId.Data.QQ + atMsg := message.AtMessage{} + if err := atMsg.ParseMessage(userText); err == nil { + userText = atMsg.Data.QQ } log.Println(userText) user, err := strconv.Atoi(userText) diff --git a/handler/beatleader/beatleader.go b/handler/beatleader/beatleader.go index d104407..34663ff 100644 --- a/handler/beatleader/beatleader.go +++ b/handler/beatleader/beatleader.go @@ -2,12 +2,16 @@ package beatleader import ( "log" + "os" "strconv" + "sync" "git.lxtend.com/qqbot/constants" "git.lxtend.com/qqbot/handler" + "git.lxtend.com/qqbot/message" "git.lxtend.com/qqbot/model" "git.lxtend.com/qqbot/service/beatleader" + "git.lxtend.com/qqbot/util" ) func init() { @@ -135,9 +139,31 @@ func getMyRecentScore(msg model.Message) (reply model.Reply) { FromMsg: msg, } } - for _, v := range records { - scoreMsg += v.ToString() + "\n\n" - userName = v.Name + // 单独线程下载封面图片 + coverImageMap := make(map[string]string) + wg := sync.WaitGroup{} + for _, record := range records { + coverImageMap[record.SongHash] = record.CoverImage + wg.Add(1) + go func(songHash string) { + defer wg.Done() + //文件存在则跳过 + if _, err := os.Stat(util.GetResizedIamgePathByOrgPath(util.GenTempFilePath(songHash + ".png"))); err == nil { + return + } + util.DownloadFile(coverImageMap[songHash], util.GenTempFilePath(songHash+".png")) + newPath, err := util.ResizeImageByMaxHeight(util.GenTempFilePath(songHash+".png"), 20) + os.Remove(util.GenTempFilePath(songHash + ".png")) + if err != nil { + log.Printf("缩放图片失败: %v", err) + } + coverImageMap[songHash] = newPath + }(record.SongHash) + } + wg.Wait() + for _, record := range records { + scoreMsg += record.ToString() + "\n\n" + userName = record.Name recordCount++ } if len(scoreMsg) > 0 { @@ -157,8 +183,14 @@ func getMyRecentScore(msg model.Message) (reply model.Reply) { } func screenShotBL(msg model.Message) (reply model.Reply) { + imageMsg := message.ImageMessage{ + Type: "image", + Data: message.ImageMessageData{ + File: "file:///tmp/qqbot/" + beatleader.GetBLPicture(strconv.Itoa(int(msg.UserId))), + }, + } return model.Reply{ - ReplyMsg: "[CQ:image,file=file:///tmp/qqbot/" + beatleader.GetBLPicture(strconv.Itoa(int(msg.UserId))) + "]", + ReplyMsg: imageMsg.ToCQString(), ReferOriginMsg: true, FromMsg: msg, } diff --git a/handler/drawback/drawback.go b/handler/drawback/drawback.go index fa00695..e0bb806 100644 --- a/handler/drawback/drawback.go +++ b/handler/drawback/drawback.go @@ -1,11 +1,10 @@ package drawback import ( - "log" - "git.lxtend.com/qqbot/action" "git.lxtend.com/qqbot/constants" "git.lxtend.com/qqbot/handler" + "git.lxtend.com/qqbot/message" "git.lxtend.com/qqbot/model" ) @@ -15,9 +14,8 @@ func init() { } func drawback(msg model.Message) model.Reply { - msgIdToDrawback, err := model.ParseReplyData(msg.RawMsg) - log.Printf("Drawback message %d", msgIdToDrawback.Data.ID) - if err != nil { + msgIdToDrawback := message.ReplyMessage{} + if err := msgIdToDrawback.ParseMessage(msg.RawMsg); err != nil { return model.Reply{ ReplyMsg: "", ReferOriginMsg: false, diff --git a/handler/getweb/getweb.go b/handler/getweb/getweb.go index b4ad33f..6e34bf6 100644 --- a/handler/getweb/getweb.go +++ b/handler/getweb/getweb.go @@ -1,6 +1,7 @@ package getweb import ( + "git.lxtend.com/qqbot/message" "git.lxtend.com/qqbot/model" "git.lxtend.com/qqbot/util" ) @@ -24,8 +25,14 @@ func getweb(msg model.Message) (reply model.Reply) { FromMsg: msg, } } + imageMsg := message.ImageMessage{ + Type: "image", + Data: message.ImageMessageData{ + File: "file:///tmp/qqbot/getweb/url.png", + }, + } return model.Reply{ - ReplyMsg: "[CQ:image,file=file:///tmp/qqbot/getweb/url.png]", + ReplyMsg: imageMsg.ToCQString(), ReferOriginMsg: true, FromMsg: msg, } diff --git a/handler/xibao/xibao.go b/handler/xibao/xibao.go index c95a7fa..36274cb 100644 --- a/handler/xibao/xibao.go +++ b/handler/xibao/xibao.go @@ -6,6 +6,7 @@ import ( "git.lxtend.com/qqbot/constants" "git.lxtend.com/qqbot/handler" + "git.lxtend.com/qqbot/message" "git.lxtend.com/qqbot/model" "git.lxtend.com/qqbot/service/xibao" "git.lxtend.com/qqbot/util" @@ -33,8 +34,14 @@ func xiBao(msg model.Message) (reply model.Reply) { } } xibao.GenerateCongratulationImage(tokens[1], "./resource/xibao_background.png", filePath, true) + imageMsg := message.ImageMessage{ + Type: "image", + Data: message.ImageMessageData{ + File: "file:///tmp/qqbot/" + fileName + ".png", + }, + } return model.Reply{ - ReplyMsg: fmt.Sprintf("[CQ:image,file=file:///tmp/qqbot/%s]", fileName+".png"), + ReplyMsg: imageMsg.ToCQString(), ReferOriginMsg: true, FromMsg: msg, } @@ -45,8 +52,14 @@ func xiBaoTemp(msg model.Message) (reply model.Reply, isTrigger bool) { fileName := uuid.New().String() filePath := util.GenTempFilePath(fmt.Sprintf("%s.png", fileName)) xibao.GenerateCongratulationImage(msg.RawMsg, "./resource/xibao_background.png", filePath, true) + imageMsg := message.ImageMessage{ + Type: "image", + Data: message.ImageMessageData{ + File: "file:///tmp/qqbot/" + fileName + ".png", + }, + } return model.Reply{ - ReplyMsg: fmt.Sprintf("[CQ:image,file=file:///tmp/qqbot/%s]", fileName+".png"), + ReplyMsg: imageMsg.ToCQString(), ReferOriginMsg: true, FromMsg: msg, }, true @@ -66,8 +79,14 @@ func beiBao(msg model.Message) (reply model.Reply) { } } xibao.GenerateCongratulationImage(tokens[1], "./resource/beibao_background.png", filePath, false) + imageMsg := message.ImageMessage{ + Type: "image", + Data: message.ImageMessageData{ + File: "file:///tmp/qqbot/" + fileName + ".png", + }, + } return model.Reply{ - ReplyMsg: fmt.Sprintf("[CQ:image,file=file:///tmp/qqbot/%s]", fileName+".png"), + ReplyMsg: imageMsg.ToCQString(), ReferOriginMsg: true, FromMsg: msg, } @@ -78,8 +97,14 @@ func beiBaoTemp(msg model.Message) (reply model.Reply, isTrigger bool) { fileName := uuid.New().String() filePath := util.GenTempFilePath(fmt.Sprintf("%s.png", fileName)) xibao.GenerateCongratulationImage(msg.RawMsg, "./resource/beibao_background.png", filePath, false) + imageMsg := message.ImageMessage{ + Type: "image", + Data: message.ImageMessageData{ + File: "file:///tmp/qqbot/" + fileName + ".png", + }, + } return model.Reply{ - ReplyMsg: fmt.Sprintf("[CQ:image,file=file:///tmp/qqbot/%s]", fileName+".png"), + ReplyMsg: imageMsg.ToCQString(), ReferOriginMsg: true, FromMsg: msg, }, true diff --git a/message/at.go b/message/at.go new file mode 100644 index 0000000..e6c3ba5 --- /dev/null +++ b/message/at.go @@ -0,0 +1,36 @@ +package message + +import ( + "fmt" + "regexp" +) + +// @某个QQ用户的结构体 +type AtMessage struct { + Type string `json:"type"` + Data AtData `json:"data"` +} + +// @消息数据结构体 +type AtData struct { + QQ string `json:"qq"` +} + +func (msg *AtMessage) ToCQString() string { + return fmt.Sprintf(`\[CQ:at,qq=%s\]`, msg.Data.QQ) +} + +func (msg *AtMessage) ParseMessage(data string) error { + // 使用正则表达式提取QQ号 + re := regexp.MustCompile(`\[CQ:at,qq=(\d+)\]`) + matches := re.FindStringSubmatch(data) + if len(matches) < 2 { + return fmt.Errorf("数据格式不正确") + } + + // 返回解析后的结构体 + msg.Data = AtData{ + QQ: matches[1], + } + return nil +} diff --git a/message/cq_message.go b/message/cq_message.go new file mode 100644 index 0000000..4bf747f --- /dev/null +++ b/message/cq_message.go @@ -0,0 +1,6 @@ +package message + +type CQMessage interface { + ToCQString() string + ParseMessage(data string) error +} diff --git a/message/image.go b/message/image.go new file mode 100644 index 0000000..159c544 --- /dev/null +++ b/message/image.go @@ -0,0 +1,69 @@ +package message + +import ( + "fmt" + "net/url" + "regexp" + "strconv" + "strings" +) + +type ImageMessage struct { + Type string `json:"type"` + Data ImageMessageData `json:"data"` +} + +type ImageMessageData struct { + File string + SubType string + FileID string + URL string + FileSize int + FileUnique string +} + +func (msg *ImageMessage) ToCQString() string { + // URL 转义 + escapedURL := url.QueryEscape(msg.Data.URL) + + // 构造 CQ:image 字符串 + cqString := fmt.Sprintf("[CQ:image,file=%s,sub_type=%s,file_id=%s,url=%s,file_size=%d,file_unique=%s]", + msg.Data.File, msg.Data.SubType, msg.Data.FileID, escapedURL, msg.Data.FileSize, msg.Data.FileUnique) + + return cqString +} + +func (msg *ImageMessage) ParseMessage(data string) error { + // 使用正则表达式提取各个字段 + re := regexp.MustCompile(`\[CQ:image,file=(.*?),sub_type=(.*?),file_id=(.*?),url=(.*?),file_size=(\d+),file_unique=(.*?)\]`) + matches := re.FindStringSubmatch(data) + if len(matches) < 7 { + return fmt.Errorf("数据格式不正确") + } + + // 转换file_size为整数 + fileSize, err := strconv.Atoi(matches[5]) + if err != nil { + return fmt.Errorf("解析 file_size 出错: %v", err) + } + + // 处理URL转义 + decodedURL, err := url.QueryUnescape(matches[4]) + + decodedURL = strings.ReplaceAll(decodedURL, ",", ",") + decodedURL = strings.ReplaceAll(decodedURL, "[", "[") + decodedURL = strings.ReplaceAll(decodedURL, "]", "]") + decodedURL = strings.ReplaceAll(decodedURL, "&", "&") + if err != nil { + return fmt.Errorf("URL 转义失败: %v", err) + } + msg.Data = ImageMessageData{ + File: matches[1], + SubType: matches[2], + FileID: matches[3], + URL: decodedURL, + FileSize: fileSize, + FileUnique: matches[6], + } + return nil +} diff --git a/message/reply.go b/message/reply.go new file mode 100644 index 0000000..67de762 --- /dev/null +++ b/message/reply.go @@ -0,0 +1,39 @@ +package message + +import ( + "fmt" + "regexp" + "strconv" +) + +// 回复消息结构体 +type ReplyMessage struct { + Type string `json:"type"` + Data ReplyData `json:"data"` +} + +// 回复消息数据结构体 +type ReplyData struct { + ID int `json:"id"` +} + +func (msg *ReplyMessage) ToCQString() string { + return fmt.Sprintf(`\[CQ:reply,id=%d\]`, msg.Data.ID) +} + +func (msg *ReplyMessage) ParseMessage(data string) error { + // 使用正则表达式提取ID + re := regexp.MustCompile(`\[CQ:reply,id=(\d+)\]`) + matches := re.FindStringSubmatch(data) + if len(matches) < 2 { + return fmt.Errorf("数据格式不正确") + } + + id, _ := strconv.Atoi(matches[1]) + + // 返回解析后的结构体 + msg.Data = ReplyData{ + ID: id, + } + return nil +} diff --git a/model/cq_message.go b/model/cq_message.go deleted file mode 100644 index 32473bd..0000000 --- a/model/cq_message.go +++ /dev/null @@ -1,143 +0,0 @@ -package model - -import ( - "fmt" - "net/url" - "regexp" - "strconv" - "strings" -) - -// @某个QQ用户的结构体 -type AtMessage struct { - Type string `json:"type"` - Data AtData `json:"data"` -} - -// @消息数据结构体 -type AtData struct { - QQ string `json:"qq"` -} - -func (msg *AtMessage) ToCQString() string { - return fmt.Sprintf(`\[CQ:at,qq=%s\]`, msg.Data.QQ) -} - -func ParseAtMessage(data string) (*AtMessage, error) { - // 使用正则表达式提取QQ号 - re := regexp.MustCompile(`\[CQ:at,qq=(\d+)\]`) - matches := re.FindStringSubmatch(data) - if len(matches) < 2 { - return nil, fmt.Errorf("数据格式不正确") - } - - // 返回解析后的结构体 - return &AtMessage{ - Type: "at", - Data: AtData{ - QQ: matches[1], - }, - }, nil -} - -// 回复消息结构体 -type ReplyMessage struct { - Type string `json:"type"` - Data ReplyData `json:"data"` -} - -// 回复消息数据结构体 -type ReplyData struct { - ID int `json:"id"` -} - -func ParseReplyData(data string) (*ReplyMessage, error) { - // 使用正则表达式提取ID - re := regexp.MustCompile(`\[CQ:reply,id=(\d+)\]`) - matches := re.FindStringSubmatch(data) - if len(matches) < 2 { - return nil, fmt.Errorf("数据格式不正确") - } - - id, _ := strconv.Atoi(matches[1]) - - // 返回解析后的结构体 - return &ReplyMessage{ - Type: "reply", - Data: ReplyData{ - ID: id, - }, - }, nil -} - -type ImageMessage struct { - Type string `json:"type"` - Data ImageMessageData `json:"data"` -} - -type ImageMessageData struct { - File string - SubType string - FileID string - URL string - FileSize int - FileUnique string -} - -func (msg *ImageMessage) ToCQString() (string, error) { - // URL 转义 - escapedURL := url.QueryEscape(msg.Data.URL) - - // 构造 CQ:image 字符串 - cqString := fmt.Sprintf("[CQ:image,file=%s,sub_type=%s,file_id=%s,url=%s,file_size=%d,file_unique=%s]", - msg.Data.File, msg.Data.SubType, msg.Data.FileID, escapedURL, msg.Data.FileSize, msg.Data.FileUnique) - - return cqString, nil -} - -func ParseCQImageMessage(data string) (*ImageMessage, error) { - // 使用正则表达式提取各个字段 - re := regexp.MustCompile(`\[CQ:image,file=(.*?),sub_type=(.*?),file_id=(.*?),url=(.*?),file_size=(\d+),file_unique=(.*?)\]`) - matches := re.FindStringSubmatch(data) - if len(matches) < 7 { - return nil, fmt.Errorf("数据格式不正确") - } - - // 转换file_size为整数 - fileSize, err := strconv.Atoi(matches[5]) - if err != nil { - return nil, fmt.Errorf("解析 file_size 出错: %v", err) - } - - // 处理URL转义 - decodedURL, err := url.QueryUnescape(matches[4]) - - decodedURL = strings.ReplaceAll(decodedURL, ",", ",") - decodedURL = strings.ReplaceAll(decodedURL, "[", "[") - decodedURL = strings.ReplaceAll(decodedURL, "]", "]") - decodedURL = strings.ReplaceAll(decodedURL, "&", "&") - if err != nil { - return nil, fmt.Errorf("URL 转义失败: %v", err) - } - - // 返回解析后的结构体 - // return &ImageMessageData{ - // File: matches[1], - // SubType: matches[2], - // FileID: matches[3], - // URL: decodedURL, - // FileSize: fileSize, - // FileUnique: matches[6], - // }, nil - return &ImageMessage{ - Type: "image", - Data: ImageMessageData{ - File: matches[1], - SubType: matches[2], - FileID: matches[3], - URL: decodedURL, - FileSize: fileSize, - FileUnique: matches[6], - }, - }, nil -} diff --git a/service/xibao/image_gen.go b/service/xibao/image_gen.go index f3482cf..bdfab2c 100644 --- a/service/xibao/image_gen.go +++ b/service/xibao/image_gen.go @@ -5,7 +5,7 @@ import ( "os" "strings" - "git.lxtend.com/qqbot/model" + "git.lxtend.com/qqbot/message" "git.lxtend.com/qqbot/util" "github.com/fogleman/gg" "github.com/google/uuid" @@ -157,8 +157,9 @@ func GenerateCongratulationImage(text string, inputFile, outputFile string, isGo } func isImageCQ(text string) (string, bool) { - if img, err := model.ParseCQImageMessage(text); err == nil { - return img.Data.URL, true + imgMsg := message.ImageMessage{} + if err := imgMsg.ParseMessage(text); err == nil { + return imgMsg.Data.URL, true } return "", false } diff --git a/util/picture.go b/util/picture.go new file mode 100644 index 0000000..75498be --- /dev/null +++ b/util/picture.go @@ -0,0 +1,166 @@ +package util + +import ( + "errors" + "image" + "image/jpeg" + "image/png" + "os" + "path/filepath" + "strings" + + "github.com/nfnt/resize" +) + +func ResizeImage(imagePath string, width int, height int) (outputPath string, err error) { + // 打开源图片文件 + file, err := os.Open(imagePath) + if err != nil { + return "", err + } + defer file.Close() + + // 解码图片 + var img image.Image + var decodeErr error + + ext := strings.ToLower(filepath.Ext(imagePath)) + switch ext { + case ".jpg", ".jpeg": + img, decodeErr = jpeg.Decode(file) + case ".png": + img, decodeErr = png.Decode(file) + default: + return "", errors.New("unsupported image format") + } + + if decodeErr != nil { + return "", decodeErr + } + + // 调整图片大小 + resized := resize.Resize(uint(width), uint(height), img, resize.Lanczos3) + + // 创建输出文件 + outputPath = strings.TrimSuffix(imagePath, ext) + "_resized" + ext + out, err := os.Create(outputPath) + if err != nil { + return "", err + } + defer out.Close() + + // 根据文件扩展名选择编码方式 + switch ext { + case ".jpg", ".jpeg": + return outputPath, jpeg.Encode(out, resized, nil) + case ".png": + return outputPath, png.Encode(out, resized) + default: + return "", errors.New("unsupported image format") + } +} + +// ResizeImageByMaxWidth 按最大宽度缩放图片,保持宽高比 +func ResizeImageByMaxWidth(imagePath string, maxWidth uint) (outputPath string, err error) { + // 打开源图片文件 + file, err := os.Open(imagePath) + if err != nil { + return "", err + } + defer file.Close() + + // 解码图片 + var img image.Image + var decodeErr error + + ext := strings.ToLower(filepath.Ext(imagePath)) + switch ext { + case ".jpg", ".jpeg": + img, decodeErr = jpeg.Decode(file) + case ".png": + img, decodeErr = png.Decode(file) + default: + return "", errors.New("unsupported image format") + } + + if decodeErr != nil { + return "", decodeErr + } + + // 计算缩放后的尺寸,保持宽高比 + // 传入0作为高度,resize包会自动计算等比例的高度 + resized := resize.Resize(maxWidth, 0, img, resize.Lanczos3) + + // 创建输出文件 + outputPath = strings.TrimSuffix(imagePath, ext) + "_resized" + ext + out, err := os.Create(outputPath) + if err != nil { + return "", err + } + defer out.Close() + + // 根据文件扩展名选择编码方式 + switch ext { + case ".jpg", ".jpeg": + return outputPath, jpeg.Encode(out, resized, nil) + case ".png": + return outputPath, png.Encode(out, resized) + default: + return "", errors.New("unsupported image format") + } +} + +// ResizeImageByMaxHeight 按最大高度缩放图片,保持宽高比 +func ResizeImageByMaxHeight(imagePath string, maxHeight uint) (outputPath string, err error) { + // 打开源图片文件 + file, err := os.Open(imagePath) + if err != nil { + return "", err + } + defer file.Close() + + // 解码图片 + var img image.Image + var decodeErr error + + ext := strings.ToLower(filepath.Ext(imagePath)) + switch ext { + case ".jpg", ".jpeg": + img, decodeErr = jpeg.Decode(file) + case ".png": + img, decodeErr = png.Decode(file) + default: + return "", errors.New("unsupported image format") + } + + if decodeErr != nil { + return "", decodeErr + } + + // 计算缩放后的尺寸,保持宽高比 + // 传入0作为宽度,resize包会自动计算等比例的宽度 + resized := resize.Resize(0, maxHeight, img, resize.Lanczos3) + + // 创建输出文件 + outputPath = strings.TrimSuffix(imagePath, ext) + "_resized" + ext + out, err := os.Create(outputPath) + if err != nil { + return "", err + } + defer out.Close() + + // 根据文件扩展名选择编码方式 + switch ext { + case ".jpg", ".jpeg": + return outputPath, jpeg.Encode(out, resized, nil) + case ".png": + return outputPath, png.Encode(out, resized) + default: + return "", errors.New("unsupported image format") + } +} + +func GetResizedIamgePathByOrgPath(orgPath string) string { + ext := strings.ToLower(filepath.Ext(orgPath)) + return strings.TrimSuffix(orgPath, ext) + "_resized" + ext +} diff --git a/util/url.go b/util/url.go index 28c07d4..73220e5 100644 --- a/util/url.go +++ b/util/url.go @@ -36,11 +36,10 @@ func normalizeURL(rawURL string) string { return u.String() } -func DownloadFile(url string, filepath string) error { +func DownloadFile(url string, filepath string) (err error) { // 发送 HTTP GET 请求 // resp, err := http.Get(url) var resp *http.Response - var err error var maxRetry = 100 var retry = 0 for resp, err = http.Get(url); err != nil && retry < maxRetry; resp, err = http.Get(url) {