🆕 添加MinIO配置和初始化功能,新增下载图片和视频消息并保存到MinIO
This commit is contained in:
parent
65d245ddaf
commit
cc629bd8b7
@ -53,3 +53,12 @@ auth:
|
||||
logger:
|
||||
level: "info"
|
||||
file: "./logs/app.log"
|
||||
|
||||
# MinIO配置示例
|
||||
minio:
|
||||
endpoint: http://10.0.0.5:9000
|
||||
host: http://10.0.0.5:9000
|
||||
accessKey: xxx
|
||||
secretKey: xxx
|
||||
bucket: wechat
|
||||
useSSL: false
|
||||
|
11
go.mod
11
go.mod
@ -10,9 +10,12 @@ require (
|
||||
github.com/goccy/go-json v0.10.5
|
||||
github.com/gofiber/fiber/v2 v2.52.6
|
||||
github.com/gofiber/template/html/v2 v2.1.3
|
||||
github.com/gofiber/websocket/v2 v2.2.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/logto-io/go/v2 v2.0.0
|
||||
github.com/minio/minio-go/v7 v7.0.91
|
||||
github.com/spf13/viper v1.20.1
|
||||
github.com/valyala/fasthttp v1.60.0
|
||||
gorm.io/driver/mysql v1.5.7
|
||||
gorm.io/driver/postgres v1.5.11
|
||||
gorm.io/driver/sqlite v1.5.7
|
||||
@ -27,9 +30,11 @@ require (
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/distribution/reference v0.5.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/fasthttp/websocket v1.5.3 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/go-ini/ini v1.67.0 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.0.4 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
@ -38,7 +43,6 @@ require (
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
|
||||
github.com/gofiber/template v1.8.3 // indirect
|
||||
github.com/gofiber/utils v1.1.0 // indirect
|
||||
github.com/gofiber/websocket/v2 v2.2.1 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
@ -48,10 +52,13 @@ require (
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/jonboulle/clockwork v0.5.0 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.28 // indirect
|
||||
github.com/minio/crc64nvme v1.0.1 // indirect
|
||||
github.com/minio/md5-simd v1.1.2 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/sys/atomicwriter v0.1.0 // indirect
|
||||
github.com/moby/term v0.5.0 // indirect
|
||||
@ -62,6 +69,7 @@ require (
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||
github.com/rs/xid v1.6.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.9.0 // indirect
|
||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
@ -70,7 +78,6 @@ require (
|
||||
github.com/spf13/pflag v1.0.6 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.60.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect
|
||||
go.opentelemetry.io/otel v1.29.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 // indirect
|
||||
|
15
go.sum
15
go.sum
@ -25,6 +25,8 @@ github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj
|
||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/fasthttp/websocket v1.5.3 h1:TPpQuLwJYfd4LJPXvHDYPMFWbLjsT91n3GpWtCQtdek=
|
||||
github.com/fasthttp/websocket v1.5.3/go.mod h1:46gg/UBmTU1kUaTcwQXpUxtRwG2PvIZYeA8oL6vF3Fs=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
@ -35,6 +37,8 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/go-co-op/gocron/v2 v2.16.1 h1:ux/5zxVRveCaCuTtNI3DiOk581KC1KpJbpJFYUEVYwo=
|
||||
github.com/go-co-op/gocron/v2 v2.16.1/go.mod h1:opexeOFy5BplhsKdA7bzY9zeYih8I8/WNJ4arTIFPVc=
|
||||
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
||||
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E=
|
||||
github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
@ -93,6 +97,9 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
@ -108,6 +115,12 @@ github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh
|
||||
github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
|
||||
github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
|
||||
github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/minio/crc64nvme v1.0.1 h1:DHQPrYPdqK7jQG/Ls5CTBZWeex/2FMS3G5XGkycuFrY=
|
||||
github.com/minio/crc64nvme v1.0.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
|
||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||
github.com/minio/minio-go/v7 v7.0.91 h1:tWLZnEfo3OZl5PoXQwcwTAPNNrjyWwOh6cbZitW5JQc=
|
||||
github.com/minio/minio-go/v7 v7.0.91/go.mod h1:uvMUcGrpgeSAAI6+sD3818508nUyMULw94j2Nxku/Go=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
|
||||
@ -135,6 +148,8 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k=
|
||||
github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk=
|
||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
|
||||
|
@ -1,6 +1,7 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
@ -14,6 +15,7 @@ type Config struct {
|
||||
Docker DockerConfig `mapstructure:"docker"`
|
||||
Auth AuthConfig `mapstructure:"auth"`
|
||||
Logger LoggerConfig `mapstructure:"logger"`
|
||||
Minio MinioConfig `mapstructure:"minio"`
|
||||
}
|
||||
|
||||
// Load 加载配置文件和环境变量
|
||||
@ -36,7 +38,7 @@ func Load() (*Config, error) {
|
||||
// 读取配置文件
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
// 配置文件不存在时不返回错误
|
||||
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
|
||||
if !errors.As(err, &viper.ConfigFileNotFoundError{}) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@ -66,5 +68,8 @@ func (c *Config) Validate() error {
|
||||
if err := c.Logger.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.Minio.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
37
internal/config/minio.go
Normal file
37
internal/config/minio.go
Normal file
@ -0,0 +1,37 @@
|
||||
package config
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Minio
|
||||
// @description: Minio配置
|
||||
type MinioConfig struct {
|
||||
Endpoint string `mapstructure:"endpoint"` // 接口地址
|
||||
Host string `mapstructure:"host"` // 自定义域名
|
||||
AccessKeyID string `mapstructure:"accessKey"` // 账号
|
||||
SecretAccessKey string `mapstructure:"secretKey"` // 密码
|
||||
BucketName string `mapstructure:"bucket"` // 桶名称
|
||||
UseSsl bool `mapstructure:"useSSL"` // 是否使用SSL
|
||||
}
|
||||
|
||||
// Validate
|
||||
// @description: 验证参数
|
||||
// @receiver c
|
||||
// @return error
|
||||
func (c *MinioConfig) Validate() error {
|
||||
if c.Endpoint == "" {
|
||||
return fmt.Errorf("endpoint is required")
|
||||
}
|
||||
if c.Host == "" {
|
||||
return fmt.Errorf("host is required")
|
||||
}
|
||||
if c.AccessKeyID == "" {
|
||||
return fmt.Errorf("accessKeyId is required")
|
||||
}
|
||||
if c.SecretAccessKey == "" {
|
||||
return fmt.Errorf("secretAccessKey is required")
|
||||
}
|
||||
if c.BucketName == "" {
|
||||
c.BucketName = "wechat"
|
||||
}
|
||||
return nil
|
||||
}
|
@ -3,7 +3,7 @@ package docker
|
||||
import (
|
||||
"context"
|
||||
"gitee.ltd/lxh/wechat-robot/internal/config"
|
||||
"log"
|
||||
"github.com/gofiber/fiber/v2/log"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -43,7 +43,7 @@ func CreateRobotContainer(ctx context.Context, cfg *config.DockerConfig, robotNa
|
||||
// 获取容器访问地址
|
||||
containerHost, err := GetContainerHost(ctx, containerID, cfg)
|
||||
if err != nil {
|
||||
log.Printf("警告: 无法获取容器访问地址: %v", err)
|
||||
log.Warnf("警告: 无法获取容器访问地址: %v", err)
|
||||
containerHost = "localhost:9000" // 使用默认值
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"gitee.ltd/lxh/xybot"
|
||||
"log"
|
||||
"github.com/gofiber/fiber/v2/log"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@ -173,19 +173,19 @@ func DeleteRobot(c *fiber.Ctx) error {
|
||||
|
||||
robotCli, err := xybot.NewClient(robot.WechatID, robot.ContainerHost, false)
|
||||
if err != nil {
|
||||
log.Printf("创建微信客户端失败: %v", err)
|
||||
log.Errorf("创建微信客户端失败: %v", err)
|
||||
}
|
||||
|
||||
if robot.Status == model.RobotStatusOnline {
|
||||
if err = robotCli.Login.Logout(); err != nil {
|
||||
log.Printf("登出机器人失败: %v", err)
|
||||
log.Errorf("登出机器人失败: %v", err)
|
||||
// 继续删除流程,不因登出失败而中断
|
||||
}
|
||||
}
|
||||
|
||||
// 删除容器
|
||||
if err = docker.RemoveContainer(ctx, robot.ContainerID, true); err != nil {
|
||||
log.Printf("删除容器失败: %v", err)
|
||||
log.Errorf("删除容器失败: %v", err)
|
||||
// 继续删除流程,不因容器删除失败而中断
|
||||
}
|
||||
|
||||
|
@ -1,43 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
// DebugMiddleware 用于记录更详细的请求信息以便排查问题
|
||||
func DebugMiddleware() fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
// 记录开始时间
|
||||
start := time.Now()
|
||||
|
||||
// 输出请求详情
|
||||
log.Printf("[DEBUG] 收到请求: %s %s", c.Method(), c.Path())
|
||||
log.Printf("[DEBUG] 查询参数: %s", c.Request().URI().QueryArgs().String())
|
||||
log.Printf("[DEBUG] 请求头: %v", c.GetReqHeaders())
|
||||
|
||||
// 处理请求
|
||||
err := c.Next()
|
||||
|
||||
// 请求结束,计算耗时
|
||||
duration := time.Since(start)
|
||||
status := c.Response().StatusCode()
|
||||
|
||||
if err != nil {
|
||||
log.Printf("[DEBUG] 请求错误: %s %s - %d - %v - %s",
|
||||
c.Method(), c.Path(), status, err, duration)
|
||||
} else {
|
||||
log.Printf("[DEBUG] 请求完成: %s %s - %d - %s",
|
||||
c.Method(), c.Path(), status, duration)
|
||||
}
|
||||
|
||||
if status == 500 {
|
||||
log.Printf("[ERROR] 内部服务器错误: %s %s - %v",
|
||||
c.Method(), c.Path(), err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
98
internal/minio/funcs.go
Normal file
98
internal/minio/funcs.go
Normal file
@ -0,0 +1,98 @@
|
||||
package minio
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"gitee.ltd/lxh/wechat-robot/internal/config"
|
||||
"github.com/gofiber/fiber/v2/log"
|
||||
"github.com/minio/minio-go/v7"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SaveBytes
|
||||
// @description: 保存文件到OSS
|
||||
// @param b
|
||||
// @param md5
|
||||
// @param suffix
|
||||
// @return url
|
||||
// @return err
|
||||
func SaveBytes(b []byte, md5 string, suffix ...string) (url string, err error) {
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
log.Errorf("加载配置失败: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
contentType := http.DetectContentType(b)
|
||||
if strings.Contains(contentType, ";") {
|
||||
contentType = contentType[:strings.Index(contentType, ";")]
|
||||
}
|
||||
// 重新设置文件名
|
||||
suf := ""
|
||||
if len(suffix) == 0 {
|
||||
suf = contentType[strings.Index(contentType, "/")+1:]
|
||||
} else {
|
||||
suf = suffix[0]
|
||||
}
|
||||
today := time.Now().Local().Format("2006/01/02")
|
||||
fileName := fmt.Sprintf("%s/%s/%s", contentType, today, md5)
|
||||
if suf != "" {
|
||||
fileName = fmt.Sprintf("%s.%s", fileName, suf)
|
||||
}
|
||||
log.Debugf("开始上传文件: %v", fileName)
|
||||
reader := bytes.NewBuffer(b)
|
||||
_, err = minioClient.PutObject(ctx, cfg.Minio.BucketName, fileName, reader, -1, minio.PutObjectOptions{ContentType: contentType})
|
||||
if err != nil {
|
||||
log.Errorf("文件上传错误: %v", err)
|
||||
return "", err
|
||||
}
|
||||
log.Debugf("文件上传完毕: %v", fileName)
|
||||
protocol := "http"
|
||||
if cfg.Minio.UseSsl {
|
||||
protocol = "https"
|
||||
}
|
||||
url = fmt.Sprintf("%s://%s/%s/%s", protocol, cfg.Minio.Host, cfg.Minio.BucketName, fileName)
|
||||
// 异步数据入库
|
||||
//go func() {
|
||||
// wf := db.WeChatFileEntity{Url: fileUrl, HashCode: md5, FileType: contentType}
|
||||
// if err = wf.Save(); err != nil {
|
||||
// log.Errorf("文件资源信息保存入库失败: %v", err)
|
||||
// }
|
||||
//}()
|
||||
return
|
||||
}
|
||||
|
||||
// SaveBase64
|
||||
// @description: 保存Base64字符串到OSS
|
||||
// @param bs53Str
|
||||
// @param md5Str
|
||||
// @param suffix
|
||||
// @return url
|
||||
// @return err
|
||||
func SaveBase64(bs64Str string, md5Str string, suffix ...string) (url string, err error) {
|
||||
bs64Bytes, err := base64.StdEncoding.DecodeString(bs64Str) // 解密base64字符串
|
||||
if err != nil {
|
||||
log.Errorf("解密失败: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 如果md5为空,计算一下
|
||||
if md5Str == "" {
|
||||
hasher := md5.New()
|
||||
if _, err = hasher.Write(bs64Bytes); err != nil {
|
||||
log.Errorf("计算md5失败: %v", err)
|
||||
return
|
||||
}
|
||||
md5Str = hex.EncodeToString(hasher.Sum(nil))
|
||||
//log.Debugf("计算出的md5: %v", md5Str)
|
||||
}
|
||||
|
||||
return SaveBytes(bs64Bytes, md5Str, suffix...)
|
||||
}
|
50
internal/minio/minio.go
Normal file
50
internal/minio/minio.go
Normal file
@ -0,0 +1,50 @@
|
||||
package minio
|
||||
|
||||
import (
|
||||
"context"
|
||||
"gitee.ltd/lxh/wechat-robot/internal/config"
|
||||
"github.com/gofiber/fiber/v2/log"
|
||||
"github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||
)
|
||||
|
||||
var minioClient *minio.Client
|
||||
|
||||
// Init
|
||||
// @description: 初始化Minio连接
|
||||
func Init() {
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
log.Panicf("加载配置失败: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
// 初使化 minio client对象。
|
||||
minioClient, err = minio.New(cfg.Minio.Endpoint, &minio.Options{
|
||||
Creds: credentials.NewStaticV4(cfg.Minio.AccessKeyID, cfg.Minio.SecretAccessKey, ""),
|
||||
Secure: cfg.Minio.UseSsl,
|
||||
})
|
||||
if err != nil {
|
||||
log.Panicf("OSS初始化失败: %v", err.Error())
|
||||
}
|
||||
log.Debug("OSS连接成功,开始判断桶是否存在")
|
||||
// 判断捅是否存在,不存在就创建
|
||||
var exists bool
|
||||
exists, err = minioClient.BucketExists(ctx, cfg.Minio.BucketName)
|
||||
if err != nil {
|
||||
log.Panicf("判断桶失败: %v", err)
|
||||
}
|
||||
if !exists {
|
||||
log.Debug("桶不存在,开始创建")
|
||||
// 创建桶
|
||||
err = minioClient.MakeBucket(ctx, cfg.Minio.BucketName, minio.MakeBucketOptions{Region: "us-east-1"})
|
||||
if err != nil {
|
||||
log.Panicf("OSS桶创建失败: %v", err.Error())
|
||||
}
|
||||
log.Debug("桶创建成功")
|
||||
} else {
|
||||
log.Debug("桶已存在")
|
||||
}
|
||||
log.Info("OSS初始化成功")
|
||||
}
|
@ -1,8 +1,9 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"github.com/gofiber/fiber/v2/log"
|
||||
"sync"
|
||||
|
||||
"gorm.io/driver/mysql"
|
||||
@ -57,14 +58,15 @@ func InitDB(cfg *config.DatabaseConfig) error {
|
||||
|
||||
// 对于SQLite,执行一些特定的优化
|
||||
if cfg.Type == config.SQLite {
|
||||
sqlDB, err := db.DB()
|
||||
var sqlDB *sql.DB
|
||||
sqlDB, err = db.DB()
|
||||
if err != nil {
|
||||
log.Printf("Warning: Could not get underlying SQL DB: %v", err)
|
||||
log.Errorf("Warning: Could not get underlying SQL DB: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 启用外键约束
|
||||
sqlDB.Exec("PRAGMA foreign_keys = ON")
|
||||
_, _ = sqlDB.Exec("PRAGMA foreign_keys = ON")
|
||||
// 设置连接池大小
|
||||
sqlDB.SetMaxOpenConns(1) // SQLite建议使用单连接
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ package server
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"github.com/gofiber/fiber/v2/log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
@ -69,7 +69,7 @@ func New(cfg *config.Config) *Server {
|
||||
JSONDecoder: json.Unmarshal,
|
||||
ErrorHandler: func(c *fiber.Ctx, err error) error {
|
||||
// 详细错误日志
|
||||
log.Printf("错误: %+v\n请求路径: %s\n", err, c.Path())
|
||||
log.Errorf("错误: %+v\n请求路径: %s\n", err, c.Path())
|
||||
|
||||
code := fiber.StatusInternalServerError
|
||||
var e *fiber.Error
|
||||
|
@ -1,9 +1,13 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"gitee.ltd/lxh/wechat-robot/internal/minio"
|
||||
"gitee.ltd/lxh/wechat-robot/internal/model"
|
||||
"gitee.ltd/lxh/wechat-robot/internal/types"
|
||||
"gitee.ltd/lxh/wechat-robot/internal/wechat"
|
||||
"gitee.ltd/lxh/xybot"
|
||||
"github.com/gofiber/fiber/v2/log"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@ -66,6 +70,48 @@ func syncMessage(client *xybot.Client, robotId uint) {
|
||||
m.Content = strings.Join(strings.Split(m.Content, "\n")[1:], "\n")
|
||||
}
|
||||
|
||||
// 处理一下,如果是图片、文件、表情包等信息,直接下载下来存到OSS
|
||||
if m.Type == types.MsgTypeImage || m.Type == types.MsgTypeVideo {
|
||||
// 解析消息xml文件
|
||||
// 解析为结构体
|
||||
var media wechat.MediaMessage
|
||||
if err = xml.Unmarshal([]byte(m.Content), &media); err != nil {
|
||||
log.Errorf("%v解析消息失败: %v", m.Type, err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
var md5Str, bs64Str string
|
||||
switch m.Type {
|
||||
case types.MsgTypeImage:
|
||||
fileUrl := ""
|
||||
md5Str = media.Img.Md5
|
||||
if media.Img.CdnBigImgUrl != "" {
|
||||
fileUrl = media.Img.CdnBigImgUrl
|
||||
} else if media.Img.CdnMidImgUrl != "" {
|
||||
fileUrl = media.Img.CdnMidImgUrl
|
||||
} else if media.Img.CdnThumbUrl != "" {
|
||||
fileUrl = media.Img.CdnThumbUrl
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
if bs64Str, err = client.Tool.CdnDownloadImg(media.Img.AesKey, fileUrl); err != nil {
|
||||
log.Errorf("图片文件下载失败: %s", err.Error())
|
||||
continue
|
||||
}
|
||||
case types.MsgTypeVideo:
|
||||
if bs64Str, err = client.Tool.DownloadVideo(m.ClientMsgId); err != nil {
|
||||
log.Errorf("视频文件下载失败: %s", err.Error())
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// 下载完成,保存到OSS
|
||||
if m.FileUrl, err = minio.SaveBase64(bs64Str, md5Str); err != nil {
|
||||
log.Errorf("文件保存到Minio失败: %s", err.Error())
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
msg = append(msg, m)
|
||||
}
|
||||
// 保存入库
|
||||
|
@ -4,8 +4,8 @@ import (
|
||||
"gitee.ltd/lxh/wechat-robot/internal/model"
|
||||
"gitee.ltd/lxh/xybot"
|
||||
"github.com/go-co-op/gocron/v2"
|
||||
"github.com/gofiber/fiber/v2/log"
|
||||
"github.com/google/uuid"
|
||||
"log"
|
||||
"sync"
|
||||
)
|
||||
|
||||
@ -39,13 +39,13 @@ func Start() {
|
||||
|
||||
// 启动定时任务
|
||||
scheduler.Start()
|
||||
log.Println("定时任务已启动")
|
||||
log.Info("定时任务已启动")
|
||||
}
|
||||
|
||||
// AddJob
|
||||
// @description: 添加任务
|
||||
func AddJob(robot model.Robot) {
|
||||
log.Printf("开始添加【%s[%s]】的定时任务", robot.Nickname, robot.WechatID)
|
||||
log.Debugf("开始添加【%s[%s]】的定时任务", robot.Nickname, robot.WechatID)
|
||||
// 初始化微信客户端
|
||||
robotCli, err := xybot.NewClient(robot.WechatID, robot.ContainerHost, false)
|
||||
if err != nil {
|
||||
@ -57,7 +57,7 @@ func AddJob(robot model.Robot) {
|
||||
gocron.NewTask(syncMessage, robotCli, robot.ID),
|
||||
)
|
||||
if err != nil {
|
||||
log.Printf("添加定时任务失败: %v", err)
|
||||
log.Errorf("添加定时任务失败: %v", err)
|
||||
return
|
||||
}
|
||||
// 添加到已启动的任务列表
|
||||
@ -82,11 +82,11 @@ func DeleteJob(robotId uint) {
|
||||
// 先取出任务Id
|
||||
jobId, ok := enabledSyncMessageMap.Load(robotId)
|
||||
if !ok {
|
||||
log.Printf("定时任务不存在,robotId: %d", robotId)
|
||||
log.Errorf("定时任务不存在,robotId: %d", robotId)
|
||||
return
|
||||
}
|
||||
if err := scheduler.RemoveJob(jobId.(uuid.UUID)); err != nil {
|
||||
log.Printf("删除定时任务失败: %v", err)
|
||||
log.Errorf("删除定时任务失败: %v", err)
|
||||
return
|
||||
}
|
||||
// 删除已启动的任务列表
|
||||
@ -95,11 +95,11 @@ func DeleteJob(robotId uint) {
|
||||
// 删除联系人同步任务
|
||||
jobId, ok = enabledSyncContactMap.Load(robotId)
|
||||
if !ok {
|
||||
log.Printf("联系人同步任务不存在,robotId: %d", robotId)
|
||||
log.Errorf("联系人同步任务不存在,robotId: %d", robotId)
|
||||
return
|
||||
}
|
||||
if err := scheduler.RemoveJob(jobId.(uuid.UUID)); err != nil {
|
||||
log.Printf("删除联系人同步任务失败: %v", err)
|
||||
log.Errorf("删除联系人同步任务失败: %v", err)
|
||||
return
|
||||
}
|
||||
// 删除已启动的任务列表
|
||||
|
25
internal/types/appmessage.go
Normal file
25
internal/types/appmessage.go
Normal file
@ -0,0 +1,25 @@
|
||||
package types
|
||||
|
||||
// AppMessageType 以Go惯用形式定义了微信所有的官方App消息类型。
|
||||
type AppMessageType int
|
||||
|
||||
const (
|
||||
AppMsgTypeText AppMessageType = 1 // 文本消息
|
||||
AppMsgTypeImg AppMessageType = 2 // 图片消息
|
||||
AppMsgTypeAudio AppMessageType = 3 // 语音消息
|
||||
AppMsgTypeVideo AppMessageType = 4 // 视频消息
|
||||
AppMsgTypeUrl AppMessageType = 5 // 文章消息
|
||||
AppMsgTypeAttach AppMessageType = 6 // 附件消息
|
||||
AppMsgTypeOpen AppMessageType = 7 // Open
|
||||
AppMsgTypeEmoji AppMessageType = 8 // 表情消息
|
||||
AppMsgTypeVoiceRemind AppMessageType = 9 // VoiceRemind
|
||||
AppMsgTypeScanGood AppMessageType = 10 // ScanGood
|
||||
AppMsgTypeGood AppMessageType = 13 // Good
|
||||
AppMsgTypeEmotion AppMessageType = 15 // Emotion
|
||||
AppMsgTypeCardTicket AppMessageType = 16 // 名片消息
|
||||
AppMsgTypeRealtimeShareLocation AppMessageType = 17 // 地理位置消息
|
||||
AppMsgTypeReferences AppMessageType = 57 // 引用回复
|
||||
AppMsgTypeTransfers AppMessageType = 2000 // 转账消息
|
||||
AppMsgTypeRedEnvelopes AppMessageType = 2001 // 红包消息
|
||||
AppMsgTypeReaderType AppMessageType = 100001 //自定义的消息
|
||||
)
|
79
internal/wechat/app_message.go
Normal file
79
internal/wechat/app_message.go
Normal file
@ -0,0 +1,79 @@
|
||||
package wechat
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"gitee.ltd/lxh/wechat-robot/internal/types"
|
||||
)
|
||||
|
||||
// AppMessage APP消息
|
||||
type AppMessage struct {
|
||||
XMLName xml.Name `xml:"msg"`
|
||||
Text string `xml:",chardata"`
|
||||
AppMsg struct {
|
||||
Text string `xml:",chardata"`
|
||||
Appid string `xml:"appid,attr"`
|
||||
SdkVer string `xml:"sdkver,attr"`
|
||||
Title string `xml:"title"`
|
||||
Des string `xml:"des"`
|
||||
Action string `xml:"action"`
|
||||
Type types.AppMessageType `xml:"type"`
|
||||
ShowType string `xml:"showtype"`
|
||||
SoundType string `xml:"soundtype"`
|
||||
MediaTagName string `xml:"mediatagname"`
|
||||
MessageExt string `xml:"messageext"`
|
||||
MessageAction string `xml:"messageaction"`
|
||||
Content string `xml:"content"`
|
||||
ContentAttr string `xml:"contentattr"`
|
||||
URL string `xml:"url"`
|
||||
LowUrl string `xml:"lowurl"`
|
||||
DataUrl string `xml:"dataurl"`
|
||||
LowDataUrl string `xml:"lowdataurl"`
|
||||
SongAlbumUrl string `xml:"songalbumurl"`
|
||||
SongLyric string `xml:"songlyric"`
|
||||
AppAttach struct {
|
||||
Text string `xml:",chardata"`
|
||||
TotalLen string `xml:"totallen"`
|
||||
AttachId string `xml:"attachid"`
|
||||
EmoticonMd5 string `xml:"emoticonmd5"`
|
||||
FileExt string `xml:"fileext"`
|
||||
CdnThumbAeskey string `xml:"cdnthumbaeskey"`
|
||||
CdnAttachUrl string `xml:"cdnattachurl"`
|
||||
FileKey string `xml:"filekey"`
|
||||
AesKey string `xml:"aeskey"`
|
||||
} `xml:"appattach"`
|
||||
ExtInfo string `xml:"extinfo"`
|
||||
SourceUsername string `xml:"sourceusername"`
|
||||
SourceDisplayName string `xml:"sourcedisplayname"`
|
||||
ThumbUrl string `xml:"thumburl"`
|
||||
Md5 string `xml:"md5"`
|
||||
StaTextStr string `xml:"statextstr"`
|
||||
DirectShare string `xml:"directshare"`
|
||||
ReferMsg struct {
|
||||
Text string `xml:",chardata"`
|
||||
Type string `xml:"type"`
|
||||
SvrId int64 `xml:"svrid"`
|
||||
FromUsr string `xml:"fromusr"`
|
||||
ChatUsr string `xml:"chatusr"`
|
||||
DisplayName string `xml:"displayname"`
|
||||
Content string `xml:"content"`
|
||||
MsgSource struct {
|
||||
Text string `xml:",chardata"`
|
||||
MsgSource struct {
|
||||
Text string `xml:",chardata"`
|
||||
SequenceID string `xml:"sequence_id"`
|
||||
Silence string `xml:"silence"`
|
||||
MemberCount string `xml:"membercount"`
|
||||
Signature string `xml:"signature"`
|
||||
} `xml:"msgsource"`
|
||||
} `xml:"msgsource"`
|
||||
} `xml:"refermsg"`
|
||||
} `xml:"appmsg"`
|
||||
FromUsername string `xml:"fromusername"`
|
||||
Scene string `xml:"scene"`
|
||||
AppInfo struct {
|
||||
Text string `xml:",chardata"`
|
||||
Version string `xml:"version"`
|
||||
AppName string `xml:"appname"`
|
||||
} `xml:"appinfo"`
|
||||
CommentUrl string `xml:"commenturl"`
|
||||
}
|
56
internal/wechat/media_message.go
Normal file
56
internal/wechat/media_message.go
Normal file
@ -0,0 +1,56 @@
|
||||
package wechat
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// MediaMessage 多媒体消息(视频、图片)
|
||||
type MediaMessage struct {
|
||||
XMLName xml.Name `xml:"msg" json:"msg"`
|
||||
Text string `xml:",chardata" json:"-"`
|
||||
VideoMsg *VideoMessage `xml:"videomsg" json:"videoMsg,omitempty"`
|
||||
Img *ImgMessage `xml:"img" json:"img,omitempty"`
|
||||
}
|
||||
|
||||
// ImgMessage 图片消息
|
||||
type ImgMessage struct {
|
||||
Text string `xml:",chardata"`
|
||||
AesKey string `xml:"aeskey,attr"`
|
||||
EnCryVer string `xml:"encryver,attr"`
|
||||
CdnThumbAesKey string `xml:"cdnthumbaeskey,attr"`
|
||||
CdnThumbUrl string `xml:"cdnthumburl,attr"`
|
||||
CdnThumbLength string `xml:"cdnthumblength,attr"`
|
||||
CdnThumbHeight string `xml:"cdnthumbheight,attr"`
|
||||
CdnThumbWidth string `xml:"cdnthumbwidth,attr"`
|
||||
CdnMidHeight string `xml:"cdnmidheight,attr"`
|
||||
CdnMidWidth string `xml:"cdnmidwidth,attr"`
|
||||
CdnMidImgUrl string `xml:"cdnmidimgurl,attr"`
|
||||
CdnHdHeight string `xml:"cdnhdheight,attr"`
|
||||
CdnHdWidth string `xml:"cdnhdwidth,attr"`
|
||||
CdnBigImgUrl string `xml:"cdnbigimgurl,attr"`
|
||||
Length string `xml:"length,attr"`
|
||||
Md5 string `xml:"md5,attr"`
|
||||
HevcMidSize string `xml:"hevc_mid_size,attr"`
|
||||
}
|
||||
|
||||
// VideoMessage 视频消息
|
||||
type VideoMessage struct {
|
||||
Text string `xml:",chardata"`
|
||||
AesKey string `xml:"aeskey,attr"`
|
||||
CdnVideoUrl string `xml:"cdnvideourl,attr"`
|
||||
CdnThumbAesKey string `xml:"cdnthumbaeskey,attr"`
|
||||
CdnThumbUrl string `xml:"cdnthumburl,attr"`
|
||||
Length string `xml:"length,attr"`
|
||||
PlayLength string `xml:"playlength,attr"`
|
||||
CdnThumbLength string `xml:"cdnthumblength,attr"`
|
||||
CdnThumbWidth string `xml:"cdnthumbwidth,attr"`
|
||||
CdnThumbHeight string `xml:"cdnthumbheight,attr"`
|
||||
FromUsername string `xml:"fromusername,attr"`
|
||||
Md5 string `xml:"md5,attr"`
|
||||
NewMd5 string `xml:"newmd5,attr"`
|
||||
IsPlaceholder string `xml:"isplaceholder,attr"`
|
||||
RawMd5 string `xml:"rawmd5,attr"`
|
||||
RawLength string `xml:"rawlength,attr"`
|
||||
CdnRawVideoUrl string `xml:"cdnrawvideourl,attr"`
|
||||
CdnRawVideoAesKey string `xml:"cdnrawvideoaeskey,attr"`
|
||||
OverWriteNewMsgId string `xml:"overwritenewmsgid,attr"`
|
||||
IsAd string `xml:"isad,attr"`
|
||||
}
|
16
main.go
16
main.go
@ -2,8 +2,9 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"gitee.ltd/lxh/wechat-robot/internal/minio"
|
||||
"gitee.ltd/lxh/wechat-robot/internal/tasks"
|
||||
"log"
|
||||
"github.com/gofiber/fiber/v2/log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
@ -26,6 +27,9 @@ func main() {
|
||||
log.Fatalf("配置无效: %v", err)
|
||||
}
|
||||
|
||||
// 初始化Minio
|
||||
minio.Init()
|
||||
|
||||
// 初始化数据库
|
||||
err = model.InitDB(&cfg.Database)
|
||||
if err != nil {
|
||||
@ -36,7 +40,7 @@ func main() {
|
||||
// 初始化Docker客户端
|
||||
err = docker.InitClient(&cfg.Docker)
|
||||
if err != nil {
|
||||
log.Printf("初始化Docker客户端失败: %v", err)
|
||||
log.Errorf("初始化Docker客户端失败: %v", err)
|
||||
}
|
||||
defer docker.CloseClient()
|
||||
|
||||
@ -55,11 +59,11 @@ func main() {
|
||||
// 启动HTTP服务器
|
||||
go func() {
|
||||
if err = srv.Start(); err != nil {
|
||||
log.Printf("Server error: %v", err)
|
||||
log.Errorf("Server error: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
log.Println("Server started successfully")
|
||||
log.Debug("Server started successfully")
|
||||
|
||||
// 启动定时任务
|
||||
tasks.Start()
|
||||
@ -69,7 +73,7 @@ func main() {
|
||||
signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
|
||||
<-quit
|
||||
|
||||
log.Println("Shutting down...")
|
||||
log.Warn("Shutting down...")
|
||||
cancel() // 停止容器监控
|
||||
|
||||
// 关闭HTTP服务器
|
||||
@ -77,5 +81,5 @@ func main() {
|
||||
log.Fatalf("Server shutdown failed: %v", err)
|
||||
}
|
||||
|
||||
log.Println("Server exited properly")
|
||||
log.Warn("Server exited properly")
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user