feat: 重构消息处理模块,引入统一的消息接口和类型安全的消息解析

This commit is contained in:
lixiangwuxian 2025-03-08 16:10:06 +08:00
parent e0637ab81f
commit 13ea5d7f98
16 changed files with 412 additions and 165 deletions

View File

@ -4,6 +4,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"git.lxtend.com/qqbot/message"
"git.lxtend.com/qqbot/model" "git.lxtend.com/qqbot/model"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
) )
@ -31,7 +32,13 @@ func (am *actionManager) SendAction(action string) error {
func (am *actionManager) SendMsg(reply model.Reply) error { func (am *actionManager) SendMsg(reply model.Reply) error {
if reply.ReferOriginMsg { 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) sendPkg := model.GenSendPkg(reply.FromMsg.UserId, reply.FromMsg.GroupInfo.GroupId, reply.ReplyMsg, false)
sendPkgJson, err := json.Marshal(sendPkg) sendPkgJson, err := json.Marshal(sendPkg)

1
go.mod
View File

@ -13,6 +13,7 @@ require (
github.com/gorilla/websocket v1.5.3 github.com/gorilla/websocket v1.5.3
github.com/jmoiron/sqlx v1.4.0 github.com/jmoiron/sqlx v1.4.0
github.com/mattn/go-sqlite3 v1.14.23 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 github.com/sashabaranov/go-openai v1.36.1
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c
golang.org/x/image v0.21.0 golang.org/x/image v0.21.0

2
go.sum
View File

@ -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/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 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= 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 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= 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= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=

View File

@ -8,6 +8,7 @@ import (
"git.lxtend.com/qqbot/auth" "git.lxtend.com/qqbot/auth"
"git.lxtend.com/qqbot/constants" "git.lxtend.com/qqbot/constants"
"git.lxtend.com/qqbot/handler" "git.lxtend.com/qqbot/handler"
"git.lxtend.com/qqbot/message"
"git.lxtend.com/qqbot/model" "git.lxtend.com/qqbot/model"
) )
@ -36,8 +37,9 @@ func setUserLevel(msg model.Message) (reply model.Reply) {
} }
} }
userText := tokens[1] userText := tokens[1]
if userId, err := model.ParseAtMessage(userText); err == nil { atMsg := message.AtMessage{}
userText = userId.Data.QQ if err := atMsg.ParseMessage(userText); err == nil {
userText = atMsg.Data.QQ
} }
log.Println(userText) log.Println(userText)
user, err := strconv.Atoi(userText) user, err := strconv.Atoi(userText)

View File

@ -2,12 +2,16 @@ package beatleader
import ( import (
"log" "log"
"os"
"strconv" "strconv"
"sync"
"git.lxtend.com/qqbot/constants" "git.lxtend.com/qqbot/constants"
"git.lxtend.com/qqbot/handler" "git.lxtend.com/qqbot/handler"
"git.lxtend.com/qqbot/message"
"git.lxtend.com/qqbot/model" "git.lxtend.com/qqbot/model"
"git.lxtend.com/qqbot/service/beatleader" "git.lxtend.com/qqbot/service/beatleader"
"git.lxtend.com/qqbot/util"
) )
func init() { func init() {
@ -135,9 +139,31 @@ func getMyRecentScore(msg model.Message) (reply model.Reply) {
FromMsg: msg, FromMsg: msg,
} }
} }
for _, v := range records { // 单独线程下载封面图片
scoreMsg += v.ToString() + "\n\n" coverImageMap := make(map[string]string)
userName = v.Name 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++ recordCount++
} }
if len(scoreMsg) > 0 { if len(scoreMsg) > 0 {
@ -157,8 +183,14 @@ func getMyRecentScore(msg model.Message) (reply model.Reply) {
} }
func screenShotBL(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{ return model.Reply{
ReplyMsg: "[CQ:image,file=file:///tmp/qqbot/" + beatleader.GetBLPicture(strconv.Itoa(int(msg.UserId))) + "]", ReplyMsg: imageMsg.ToCQString(),
ReferOriginMsg: true, ReferOriginMsg: true,
FromMsg: msg, FromMsg: msg,
} }

View File

@ -1,11 +1,10 @@
package drawback package drawback
import ( import (
"log"
"git.lxtend.com/qqbot/action" "git.lxtend.com/qqbot/action"
"git.lxtend.com/qqbot/constants" "git.lxtend.com/qqbot/constants"
"git.lxtend.com/qqbot/handler" "git.lxtend.com/qqbot/handler"
"git.lxtend.com/qqbot/message"
"git.lxtend.com/qqbot/model" "git.lxtend.com/qqbot/model"
) )
@ -15,9 +14,8 @@ func init() {
} }
func drawback(msg model.Message) model.Reply { func drawback(msg model.Message) model.Reply {
msgIdToDrawback, err := model.ParseReplyData(msg.RawMsg) msgIdToDrawback := message.ReplyMessage{}
log.Printf("Drawback message %d", msgIdToDrawback.Data.ID) if err := msgIdToDrawback.ParseMessage(msg.RawMsg); err != nil {
if err != nil {
return model.Reply{ return model.Reply{
ReplyMsg: "", ReplyMsg: "",
ReferOriginMsg: false, ReferOriginMsg: false,

View File

@ -1,6 +1,7 @@
package getweb package getweb
import ( import (
"git.lxtend.com/qqbot/message"
"git.lxtend.com/qqbot/model" "git.lxtend.com/qqbot/model"
"git.lxtend.com/qqbot/util" "git.lxtend.com/qqbot/util"
) )
@ -24,8 +25,14 @@ func getweb(msg model.Message) (reply model.Reply) {
FromMsg: msg, FromMsg: msg,
} }
} }
imageMsg := message.ImageMessage{
Type: "image",
Data: message.ImageMessageData{
File: "file:///tmp/qqbot/getweb/url.png",
},
}
return model.Reply{ return model.Reply{
ReplyMsg: "[CQ:image,file=file:///tmp/qqbot/getweb/url.png]", ReplyMsg: imageMsg.ToCQString(),
ReferOriginMsg: true, ReferOriginMsg: true,
FromMsg: msg, FromMsg: msg,
} }

View File

@ -6,6 +6,7 @@ import (
"git.lxtend.com/qqbot/constants" "git.lxtend.com/qqbot/constants"
"git.lxtend.com/qqbot/handler" "git.lxtend.com/qqbot/handler"
"git.lxtend.com/qqbot/message"
"git.lxtend.com/qqbot/model" "git.lxtend.com/qqbot/model"
"git.lxtend.com/qqbot/service/xibao" "git.lxtend.com/qqbot/service/xibao"
"git.lxtend.com/qqbot/util" "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) 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{ return model.Reply{
ReplyMsg: fmt.Sprintf("[CQ:image,file=file:///tmp/qqbot/%s]", fileName+".png"), ReplyMsg: imageMsg.ToCQString(),
ReferOriginMsg: true, ReferOriginMsg: true,
FromMsg: msg, FromMsg: msg,
} }
@ -45,8 +52,14 @@ func xiBaoTemp(msg model.Message) (reply model.Reply, isTrigger bool) {
fileName := uuid.New().String() fileName := uuid.New().String()
filePath := util.GenTempFilePath(fmt.Sprintf("%s.png", fileName)) filePath := util.GenTempFilePath(fmt.Sprintf("%s.png", fileName))
xibao.GenerateCongratulationImage(msg.RawMsg, "./resource/xibao_background.png", filePath, true) 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{ return model.Reply{
ReplyMsg: fmt.Sprintf("[CQ:image,file=file:///tmp/qqbot/%s]", fileName+".png"), ReplyMsg: imageMsg.ToCQString(),
ReferOriginMsg: true, ReferOriginMsg: true,
FromMsg: msg, FromMsg: msg,
}, true }, true
@ -66,8 +79,14 @@ func beiBao(msg model.Message) (reply model.Reply) {
} }
} }
xibao.GenerateCongratulationImage(tokens[1], "./resource/beibao_background.png", filePath, false) 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{ return model.Reply{
ReplyMsg: fmt.Sprintf("[CQ:image,file=file:///tmp/qqbot/%s]", fileName+".png"), ReplyMsg: imageMsg.ToCQString(),
ReferOriginMsg: true, ReferOriginMsg: true,
FromMsg: msg, FromMsg: msg,
} }
@ -78,8 +97,14 @@ func beiBaoTemp(msg model.Message) (reply model.Reply, isTrigger bool) {
fileName := uuid.New().String() fileName := uuid.New().String()
filePath := util.GenTempFilePath(fmt.Sprintf("%s.png", fileName)) filePath := util.GenTempFilePath(fmt.Sprintf("%s.png", fileName))
xibao.GenerateCongratulationImage(msg.RawMsg, "./resource/beibao_background.png", filePath, false) 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{ return model.Reply{
ReplyMsg: fmt.Sprintf("[CQ:image,file=file:///tmp/qqbot/%s]", fileName+".png"), ReplyMsg: imageMsg.ToCQString(),
ReferOriginMsg: true, ReferOriginMsg: true,
FromMsg: msg, FromMsg: msg,
}, true }, true

36
message/at.go Normal file
View File

@ -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
}

6
message/cq_message.go Normal file
View File

@ -0,0 +1,6 @@
package message
type CQMessage interface {
ToCQString() string
ParseMessage(data string) error
}

69
message/image.go Normal file
View File

@ -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, "&#44;", ",")
decodedURL = strings.ReplaceAll(decodedURL, "&#91;", "[")
decodedURL = strings.ReplaceAll(decodedURL, "&#93;", "]")
decodedURL = strings.ReplaceAll(decodedURL, "&amp;", "&")
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
}

39
message/reply.go Normal file
View File

@ -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
}

View File

@ -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, "&#44;", ",")
decodedURL = strings.ReplaceAll(decodedURL, "&#91;", "[")
decodedURL = strings.ReplaceAll(decodedURL, "&#93;", "]")
decodedURL = strings.ReplaceAll(decodedURL, "&amp;", "&")
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
}

View File

@ -5,7 +5,7 @@ import (
"os" "os"
"strings" "strings"
"git.lxtend.com/qqbot/model" "git.lxtend.com/qqbot/message"
"git.lxtend.com/qqbot/util" "git.lxtend.com/qqbot/util"
"github.com/fogleman/gg" "github.com/fogleman/gg"
"github.com/google/uuid" "github.com/google/uuid"
@ -157,8 +157,9 @@ func GenerateCongratulationImage(text string, inputFile, outputFile string, isGo
} }
func isImageCQ(text string) (string, bool) { func isImageCQ(text string) (string, bool) {
if img, err := model.ParseCQImageMessage(text); err == nil { imgMsg := message.ImageMessage{}
return img.Data.URL, true if err := imgMsg.ParseMessage(text); err == nil {
return imgMsg.Data.URL, true
} }
return "", false return "", false
} }

166
util/picture.go Normal file
View File

@ -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
}

View File

@ -36,11 +36,10 @@ func normalizeURL(rawURL string) string {
return u.String() return u.String()
} }
func DownloadFile(url string, filepath string) error { func DownloadFile(url string, filepath string) (err error) {
// 发送 HTTP GET 请求 // 发送 HTTP GET 请求
// resp, err := http.Get(url) // resp, err := http.Get(url)
var resp *http.Response var resp *http.Response
var err error
var maxRetry = 100 var maxRetry = 100
var retry = 0 var retry = 0
for resp, err = http.Get(url); err != nil && retry < maxRetry; resp, err = http.Get(url) { for resp, err = http.Get(url); err != nil && retry < maxRetry; resp, err = http.Get(url) {