Skip to main content

oss

此方案由 ccfish86 提供,gva团队对其进行优化并适配, pr链接

  • Minio需要自行搭建环境,阿里云需要购买空间,所以不放在 gin-vue-admin

server/config/oss.go#

package config
type Local struct {    Path string `mapstructure:"path" json:"path" yaml:"path" `}
type Qiniu struct {    Zone          string `mapstructure:"zone" json:"zone" yaml:"zone"`    Bucket        string `mapstructure:"bucket" json:"bucket" yaml:"bucket"`    ImgPath       string `mapstructure:"img-path" json:"imgPath" yaml:"img-path"`    UseHTTPS      bool   `mapstructure:"use-https" json:"useHttps" yaml:"use-https"`    AccessKey     string `mapstructure:"access-key" json:"accessKey" yaml:"access-key"`    SecretKey     string `mapstructure:"secret-key" json:"secretKey" yaml:"secret-key"`    UseCdnDomains bool   `mapstructure:"use-cdn-domains" json:"useCdnDomains" yaml:"use-cdn-domains"`}
type Minio struct {    Id       string `mapstructure:"id" json:"id" yaml:"id"`    Path     string `mapstructure:"path" json:"path" yaml:"path"`    Token    string `mapstructure:"token" json:"token" yaml:"token"`    Bucket   string `mapstructure:"bucket" json:"bucket" yaml:"bucket"`    UseSsl   bool   `mapstructure:"use-ssl" json:"useSsl" yaml:"use-ssl"`    Secret   string `mapstructure:"secret" json:"secret" yaml:"secret"`    Endpoint string `mapstructure:"endpoint" json:"endpoint" yaml:"endpoint"`}
type Aliyun struct {    Path             string `mapstructure:"path" json:"path" yaml:"path"`    Bucket           string `mapstructure:"bucket" json:"bucket" yaml:"bucket"`    ACLType          string `mapstructure:"acl-type" json:"aclType" yaml:"acl-type"`    Endpoint         string `mapstructure:"endpoint" json:"endpoint" yaml:"endpoint"`    AccessKeyID      string `mapstructure:"access-key-id" json:"accessKeyId" yaml:"access-key-id"`    SecretAccessKey  string `mapstructure:"secret-access-key" json:"secretAccessKey" yaml:"secret-access-key"`    StorageClassType string `mapstructure:"storage-class-type" json:"storageClassType" yaml:"storage-class-type"`}

server/config/config.go#

type Server struct {    JWT     JWT     `mapstructure:"jwt" json:"jwt" yaml:"jwt"`    Zap     Zap     `mapstructure:"zap" json:"zap" yaml:"zap"`    Redis   Redis   `mapstructure:"redis" json:"redis" yaml:"redis"`    Email   Email   `mapstructure:"email" json:"email" yaml:"email"`    Casbin  Casbin  `mapstructure:"casbin" json:"casbin" yaml:"casbin"`    System  System  `mapstructure:"system" json:"system" yaml:"system"`    Captcha Captcha `mapstructure:"captcha" json:"captcha" yaml:"captcha"`
    Mysql      Mysql      `mapstructure:"mysql" json:"mysql" yaml:"mysql"`    Postgresql Postgresql `mapstructure:"postgresql" json:"postgresql" yaml:"postgresql"`    Sqlite     Sqlite     `mapstructure:"sqlite" json:"sqlite" yaml:"sqlite"`    Sqlserver  Sqlserver  `mapstructure:"sqlserver" json:"sqlserver" yaml:"sqlserver"`
    Local  Local  `mapstructure:"local" json:"local" yaml:"local"`    Qiniu  Qiniu  `mapstructure:"qiniu" json:"qiniu" yaml:"qiniu"`    Minio  Minio  `mapstructure:"minio" json:"minio" yaml:"minio"`    Aliyun Aliyun `mapstructure:"aliyun" json:"aliyun" yaml:"aliyun"`}

server/config.yaml#

# local configurationlocal:  path: 'uploads/file'
# qiniu configuration (请自行七牛申请对应的 公钥 私钥 bucket 和 域名地址)qiniu:  zone: 'ZoneHuadong'  bucket: 'qm-plus-img'  img-path: 'http://qmplusimg.henrongyi.top'  use-https: false  access-key: '25j8dYBZ2wuiy0yhwShytjZDTX662b8xiFguwxzZ'  secret-key: 'pgdbqEsf7ooZh7W3xokP833h3dZ_VecFXPDeG5JY'  use-cdn-domains: false
# minio configurationminio:  id: 'minio'  path: 'http://localhost:9000'  token: ''  bucket: 'public'  use-ssl: false  secret: 'minio1234'  endpoint: 'localhost:9000'
# aliyun configurationaliyun:  path : 'https://xxx.oss-cn-beijing.aliyuncs.com'  bucket : 'xxx'  acl-type : ''  endpoint : 'https://oss-cn-beijing.aliyuncs.com'  access-key-id : 'xxxxxxx'  secret-access-key : 'xxxxxxx'  storage-class-type : 'Standard' # 存储桶存储类型

server/utils/upload.go#

package upload
import (    "fmt"    "gin-vue-admin/global"    "mime/multipart"    "time")
var Oss OSS
type OSS interface {    UploadFile(file *multipart.FileHeader) (string, string, error)    DeleteFile(key string) error}
func getObjectName(filename string) string {    folder := time.Now().Format("20060102")    return fmt.Sprintf("%s/%d%s", folder, time.Now().Unix(), filename) // 文件名格式 自己可以改 建议保证唯一性}
func NewOss() OSS {    switch global.GVA_CONFIG.System.OssType {    case "local":        Oss = &Local{}    case "qiniu":        Oss = &Qiniu{}    case "minio":        Oss = &Minio{}    case "aliyun":        Oss = &AliYun{}    default:        Oss = &Local{}    }    return Oss}

server/utils/local.go#

package upload
import (    "errors"    "gin-vue-admin/global"    "gin-vue-admin/utils"    "go.uber.org/zap"    "io"    "mime/multipart"    "os"    "path"    "strings"    "time")
type Local struct{}
// Upload 上传文件func (l Local) UploadFile(file *multipart.FileHeader) (string, string, error) {    // 读取文件后缀    ext := path.Ext(file.Filename)    // 读取文件名并加密    name := strings.TrimSuffix(file.Filename, ext)    name = utils.MD5V([]byte(name))    // 拼接新文件名    filename := name + "_" + time.Now().Format("20060102150405") + ext    // 尝试创建此路径    mkdirErr := os.MkdirAll(global.GVA_CONFIG.Local.Path, os.ModePerm)    if mkdirErr != nil {        global.GVA_LOG.Error("function os.MkdirAll() Filed", zap.Any("err", mkdirErr.Error()))        return "", "", errors.New("function os.MkdirAll() Filed, err:" + mkdirErr.Error())    }    // 拼接路径和文件名    p := global.GVA_CONFIG.Local.Path + "/" + filename
    f, openError := file.Open() // 读取文件    if openError != nil {        global.GVA_LOG.Error("function file.Open() Filed", zap.Any("err", openError.Error()))        return "", "", errors.New("function file.Open() Filed, err:" + openError.Error())    }    defer f.Close() // 创建文件 defer 关闭
    out, createErr := os.Create(p)    if createErr != nil {        global.GVA_LOG.Error("function os.Create() Filed", zap.Any("err", createErr.Error()))
        return "", "", errors.New("function os.Create() Filed, err:" + createErr.Error())    }    defer out.Close() // 创建文件 defer 关闭
    _, copyErr := io.Copy(out, f) // 传输(拷贝)文件    if copyErr != nil {        global.GVA_LOG.Error("function io.Copy() Filed", zap.Any("err", copyErr.Error()))        return "", "", errors.New("function io.Copy() Filed, err:" + copyErr.Error())    }    return p, filename, nil}
// DeleteFile 删除文件func (l Local) DeleteFile(key string) error {    p := global.GVA_CONFIG.Local.Path + "/" + key    if strings.Contains(p, global.GVA_CONFIG.Local.Path) {        if err := os.Remove(p); err != nil {            return errors.New("本地文件删除失败, err:" + err.Error())        }    }    return nil}

server/utils/qiniu.go#

package upload
import (    "context"    "errors"    "fmt"    "gin-vue-admin/global"    "github.com/qiniu/api.v7/v7/auth/qbox"    "github.com/qiniu/api.v7/v7/storage"    "go.uber.org/zap"    "mime/multipart"    "time")
type Qiniu struct{}
// UploadFile 上传文件func (*Qiniu) UploadFile(file *multipart.FileHeader) (string, string, error) {    putPolicy := storage.PutPolicy{Scope: global.GVA_CONFIG.Qiniu.Bucket}    mac := qbox.NewMac(global.GVA_CONFIG.Qiniu.AccessKey, global.GVA_CONFIG.Qiniu.SecretKey)    upToken := putPolicy.UploadToken(mac)    cfg := qiniuConfig()    formUploader := storage.NewFormUploader(cfg)    ret := storage.PutRet{}    putExtra := storage.PutExtra{Params: map[string]string{"x:name": "github logo"}}
    f, openError := file.Open()    if openError != nil {        global.GVA_LOG.Error("function file.Open() Filed", zap.Any("err", openError.Error()))
        return "", "", errors.New("function file.Open() Filed, err:" + openError.Error())    }    fileKey := fmt.Sprintf("%d%s", time.Now().Unix(), file.Filename) // 文件名格式 自己可以改 建议保证唯一性    putErr := formUploader.Put(context.Background(), &ret, upToken, fileKey, f, file.Size, &putExtra)    if putErr != nil {        global.GVA_LOG.Error("function formUploader.Put() Filed", zap.Any("err", putErr.Error()))        return "", "", errors.New("function formUploader.Put() Filed, err:" + putErr.Error())    }    return global.GVA_CONFIG.Qiniu.ImgPath + "/" + ret.Key, ret.Key, nil}
// DeleteFile 删除文件func (*Qiniu) DeleteFile(key string) error {    mac := qbox.NewMac(global.GVA_CONFIG.Qiniu.AccessKey, global.GVA_CONFIG.Qiniu.SecretKey)    cfg := qiniuConfig()    bucketManager := storage.NewBucketManager(mac, cfg)    if err := bucketManager.Delete(global.GVA_CONFIG.Qiniu.Bucket, key); err != nil{        global.GVA_LOG.Error("function bucketManager.Delete() Filed", zap.Any("err", err.Error()))        return errors.New("function bucketManager.Delete() Filed, err:" + err.Error())    }    return nil}
// qiniuConfig 根据配置文件进行返回七牛云的配置func qiniuConfig() *storage.Config {    cfg := storage.Config{        UseHTTPS: global.GVA_CONFIG.Qiniu.UseHTTPS,        UseCdnDomains: global.GVA_CONFIG.Qiniu.UseCdnDomains,    }    switch global.GVA_CONFIG.Qiniu.Zone { // 根据配置文件进行初始化空间对应的机房    case "ZoneHuadong":        cfg.Zone = &storage.ZoneHuadong    case "ZoneHuabei":        cfg.Zone = &storage.ZoneHuabei    case "ZoneHuanan":        cfg.Zone = &storage.ZoneHuanan    case "ZoneBeimei":        cfg.Zone = &storage.ZoneBeimei    case "ZoneXinjiapo":        cfg.Zone = &storage.ZoneXinjiapo    }    return &cfg}

server/utils/minio.go#

package upload
import (    "context"    "errors"    "fmt"    "gin-vue-admin/global"    "go.uber.org/zap"    "mime/multipart"
    "github.com/minio/minio-go/v7"    "github.com/minio/minio-go/v7/pkg/credentials")
type Minio struct{}
// UploadFile 上传文件func (*Minio) UploadFile(file *multipart.FileHeader) (string, string, error) {    client, newErr := minio.New(global.GVA_CONFIG.Minio.Endpoint, &minio.Options{ // 初始化minio client对象        Creds: credentials.NewStaticV4(global.GVA_CONFIG.Minio.Id, global.GVA_CONFIG.Minio.Secret, global.GVA_CONFIG.Minio.Token),        Secure: global.GVA_CONFIG.Minio.UseSsl,    })    if newErr != nil {        global.GVA_LOG.Error("function minio.New() Filed", zap.Any("err", newErr.Error()))        return "", "", errors.New("function oss.New() Filed, err:" + newErr.Error())    }    ctx := context.Background()    if bucketErr := client.MakeBucket(ctx, global.GVA_CONFIG.Minio.Bucket, minio.MakeBucketOptions{Region: ""}); bucketErr != nil {        if exists, existsErr := client.BucketExists(ctx, global.GVA_CONFIG.Minio.Bucket); !exists && existsErr != nil { // 检查我们是否已经拥有此存储桶(如果您运行两次,就会发生这种情况)            global.GVA_LOG.Error("function client.BucketExists() Filed", zap.Any("err", existsErr.Error()))            return "", "", errors.New("function client.BucketExists() Filed, err:" + existsErr.Error())        }        global.GVA_LOG.Info(fmt.Sprintf("We already own %s\n", global.GVA_CONFIG.Minio.Bucket))    } else {        global.GVA_LOG.Info(fmt.Sprintf("Successfully created %s\n", global.GVA_CONFIG.Minio.Bucket))    }
    objectName := getObjectName(file.Filename)
    f, openErr := file.Open()    if openErr != nil {        global.GVA_LOG.Error("function file.Open() Filed", zap.Any("err", openErr.Error()))
        return "", "", errors.New("function file.Open() Filed, err:" + openErr.Error())    }
    // 获取文件类型    contentType := file.Header.Get("content-type")
    info, putErr := client.PutObject(ctx, global.GVA_CONFIG.Minio.Bucket, objectName, f, file.Size, minio.PutObjectOptions{ContentType: contentType})    if putErr != nil {        global.GVA_LOG.Error("function client.PutObject() Filed", zap.Any("err", putErr.Error()))        return "", "", errors.New("function client.PutObject() Filed, err:" + putErr.Error())    }
    global.GVA_LOG.Info(fmt.Sprintf("Successfully uploaded %s of size %d\n", objectName, info))    return global.GVA_CONFIG.Minio.Path + "/" + global.GVA_CONFIG.Minio.Bucket + "/" + objectName, objectName, nil}
// DeleteFile 删除文件func (*Minio) DeleteFile(key string) error {    client, newErr := minio.New(global.GVA_CONFIG.Minio.Endpoint, &minio.Options{        Creds: credentials.NewStaticV4(global.GVA_CONFIG.Minio.Id, global.GVA_CONFIG.Minio.Secret, global.GVA_CONFIG.Minio.Token),        Secure: global.GVA_CONFIG.Minio.UseSsl,    }) // Initialize minio client object.    if newErr != nil {        global.GVA_LOG.Error("function minio.New() Filed", zap.Any("err", newErr.Error()))        return errors.New("function minio.New() Filed, err:" + newErr.Error())    }    opts := minio.RemoveObjectOptions{GovernanceBypass: true}    removeErr := client.RemoveObject(context.Background(), global.GVA_CONFIG.Minio.Bucket, key, opts)    if removeErr != nil {        global.GVA_LOG.Error("function client.RemoveObject() Filed", zap.Any("err", removeErr.Error()))        return errors.New("function client.RemoveObject() Filed, err:" + removeErr.Error())    }    return nil}

server/utils/aliyun.go#

package upload
import (    "errors"    "gin-vue-admin/global"    "go.uber.org/zap"    "mime/multipart"
    "github.com/aliyun/aliyun-oss-go-sdk/oss")

type AliYun struct{}
// UploadFile 上传文件func (*AliYun) UploadFile(file *multipart.FileHeader) (string, string, error) {    var storageType oss.Option    client, newErr := oss.New(global.GVA_CONFIG.Aliyun.Endpoint, global.GVA_CONFIG.Aliyun.AccessKeyID, global.GVA_CONFIG.Aliyun.SecretAccessKey, oss.Timeout(10, 120))    if newErr != nil {        global.GVA_LOG.Error("function oss.New() Filed", zap.Any("err", newErr.Error()))        return "", "", errors.New("function oss.New() Filed, err:" + newErr.Error())    }
    bucket, bucketErr := client.Bucket(global.GVA_CONFIG.Aliyun.Bucket) // 获取存储空间    if bucketErr != nil {        global.GVA_LOG.Error("function client.Bucket() Filed", zap.Any("err", bucketErr.Error()))        return "", "", errors.New("function client.Bucket() Filed, err:" + bucketErr.Error())    }
    switch global.GVA_CONFIG.Aliyun.StorageClassType { // 根据配置文件进行指定存储类型    case "Standard": // 指定存储类型为标准存储        storageType = oss.ObjectStorageClass(oss.StorageStandard)    case "IA": // 指定存储类型为很少访问存储        storageType = oss.ObjectStorageClass(oss.StorageIA)    case "Archive": // 指定存储类型为归档存储。        storageType = oss.ObjectStorageClass(oss.StorageArchive)    case "ColdArchive": // 指定存储类型为归档存储。        storageType = oss.ObjectStorageClass(oss.StorageColdArchive)    default: // 无匹配结果就是标准存储        storageType = oss.ObjectStorageClass(oss.StorageStandard)    }
    f, openError := file.Open() // 读取文件    if openError != nil {        global.GVA_LOG.Error("function file.Open() Filed", zap.Any("err", openError.Error()))        return "", "", errors.New("function file.Open() Filed, err:" + openError.Error())    }    contentType := file.Header.Get("content-type") // 获取文件类型    objectType := oss.ContentType(contentType)    var objectAcl oss.Option    switch global.GVA_CONFIG.Aliyun.ACLType { // 根据配置文件进行指定访问权限    case "private": // 指定访问权限为私有读写        objectAcl = oss.ObjectACL(oss.ACLPrivate) // 指定访问权限为公共读    case "public-read":        objectAcl = oss.ObjectACL(oss.ACLPublicRead) // 指定访问权限为公共读    case "public-read-write":        objectAcl = oss.ObjectACL(oss.ACLPublicReadWrite) // 指定访问权限为公共读写    case "default":        objectAcl = oss.ObjectACL(oss.ACLDefault) // 指定访问权限为公共读    default:        objectAcl = oss.ObjectACL(oss.ACLPrivate) // 默认为访问权限为公共读    }    objectName := getObjectName(file.Filename) // 文件对象名    putErr := bucket.PutObject(objectName, f, storageType, objectType, objectAcl) // 上传    if putErr != nil {        global.GVA_LOG.Error("function bucket.PutObject() Filed", zap.Any("err", putErr.Error()))        return "", "", errors.New("function bucket.PutObject() Filed, err:" + putErr.Error())    }
    return global.GVA_CONFIG.Aliyun.Path + "/" + objectName, objectName, nil}
// DeleteFile 删除文件func (*AliYun) DeleteFile(key string) error {    client, newErr := oss.New(global.GVA_CONFIG.Aliyun.Endpoint, global.GVA_CONFIG.Aliyun.AccessKeyID, global.GVA_CONFIG.Aliyun.SecretAccessKey, oss.Timeout(10, 120))    if newErr != nil {        global.GVA_LOG.Error("function oss.New() Filed", zap.Any("err", newErr.Error()))        return errors.New("function oss.New() Filed, err:" + newErr.Error())    }
    // 获取存储空间。    bucket, bucketErr := client.Bucket(global.GVA_CONFIG.Aliyun.Bucket)    if bucketErr != nil {        global.GVA_LOG.Error("function client.Bucket() Filed", zap.Any("err", bucketErr.Error()))        return errors.New("function client.Bucket() Filed, err:" + bucketErr.Error())    }
    // 删除单个文件。objectName表示删除OSS文件时需要指定包含文件后缀在内的完整路径,例如abc/efg/123.jpg。    // 如需删除文件夹,请将objectName设置为对应的文件夹名称。如果文件夹非空,则需要将文件夹下的所有object删除后才能删除该文件夹。    if err := bucket.DeleteObject(key); err != nil {        global.GVA_LOG.Error("function bucket.DeleteObject() Filed!", zap.Any("err", err.Error()))        return errors.New("function bucket.DeleteObject() Filed, err:" + err.Error())    }    return nil}

使用代码

// 上传文件oss := upload.NewOss()filePath, key, uploadErr := oss.UploadFile(header)// 删除文件oss := upload.NewOss()err := oss.DeleteFile(key)