gfast icon indicating copy to clipboard operation
gfast copied to clipboard

新能功能建议: 文件上传可增加七牛或oss

Open dafa168 opened this issue 3 years ago • 3 comments

我在 app/service/admin/upload_service/upload.go 中新增了oss上传代码,可作为参考

后台参数管理新增: 名称:文件上传驱动 键名:sys.uploadFile.driver 键值:oss | local

配置config.toml代码:

#OSS文件存储
[oss]
    accessID = ""
    accessSecret = ""
    bucketName = ""
    endpoint = "oss-cn-hangzhou.aliyuncs.com"
    resUrl = "http://www.xxxx.cn/"

upload.go

package upload_service

import (
	"fmt"
	"gfast/app/model/admin/sys_config"
	"gfast/app/service/admin/params_service"
	"github.com/aliyun/aliyun-oss-go-sdk/oss"
	"github.com/gogf/gf/errors/gerror"
	"github.com/gogf/gf/frame/g"
	"github.com/gogf/gf/net/ghttp"
	"github.com/gogf/gf/os/gfile"
	"github.com/gogf/gf/os/gtime"
	"github.com/gogf/gf/text/gregex"
	"github.com/gogf/gf/text/gstr"
	"github.com/gogf/gf/util/gconv"
	"github.com/gogf/gf/util/grand"
	"strconv"
	"strings"
)

const (
	upPath      = "/pub_upload/"
	driverOSS   = "oss"
	driverLocal = "local"
)

var uploadPath string

//上传得文件信息
type FileInfo struct {
	FileName string `json:"fileName"`
	FileSize int64  `json:"fileSize"`
	FileUrl  string `json:"fileUrl"`
	FileType string `json:"fileType"`
}

func init() {
	uploadPath = g.Cfg().GetString("server.ServerRoot") + upPath
}

//上传图片
func UpImg(file *ghttp.UploadFile) (fileInfo *FileInfo, err error) {
	return upByType(file, "img")
}

//上传文件
func UpFile(file *ghttp.UploadFile) (fileInfo *FileInfo, err error) {
	return upByType(file, "file")
}

//批量上传图片
func UpImgs(files []*ghttp.UploadFile) (fileInfos []*FileInfo, err error) {
	return UpBathByType(files, "img")
}

//批量上传文件
func UpFiles(files []*ghttp.UploadFile) (fileInfos []*FileInfo, err error) {
	return UpBathByType(files, "file")
}

//文件上传 img|file
func upByType(file *ghttp.UploadFile, fType string) (fileInfo *FileInfo, err error) {
	if file == nil {
		err = gerror.New("未上传任何文件")
		return
	}
	var (
		typeKey string
		sizeKey string
	)
	if fType == "img" {
		typeKey = "sys.uploadFile.imageType"
		sizeKey = "sys.uploadFile.imageSize"
	} else if fType == "file" {
		typeKey = "sys.uploadFile.fileType"
		sizeKey = "sys.uploadFile.fileSize"
	}
	//获取上传类型配置
	config, err := getUpConfig(typeKey)
	if err != nil {
		return
	}

	//检测文件类型
	rightType := checkFileType(file.Filename, config.ConfigValue)
	if !rightType {
		err = gerror.New("上传文件类型错误,只能包含后缀为:" + config.ConfigValue + "的文件。")
		return
	}
	//获取上传大小配置
	config, err = getUpConfig(sizeKey)
	if err != nil {
		return
	}
	rightSize, err := checkSize(config.ConfigValue, file.Size)
	if err != nil {
		return
	}
	if !rightSize {
		err = gerror.New("上传文件超过最大尺寸:" + config.ConfigValue)
		return
	}

	//获取文件上传驱动
	config, err = getUpConfig("sys.uploadFile.driver")
	if err != nil {
		return
	}

	fileUrl := ""
	fileName := ""

	switch {
	case config.ConfigValue == driverOSS:
		fileName, err = uploadToOss(file)
		if err != nil {
			return
		}
		fileUrl = getOssUrl(fileName)
	case config.ConfigValue == driverLocal:
		path := getUpPath()
		fileName, err = file.Save(path, true)
		if err != nil {
			return
		}
		fileUrl = getUrl(path, fileName)

	default:
		err = gerror.New("未配置上传驱动,请在后台参数管理中新增配置Key=sys.uploadFile.driver")
		return
	}

	fileInfo = &FileInfo{
		FileName: file.Filename,
		FileSize: file.Size,
		FileUrl:  fileUrl,
		FileType: file.Header.Get("Content-type"),
	}
	return
}

//批量上传 img|file
func UpBathByType(files []*ghttp.UploadFile, fType string) (fileInfos []*FileInfo, err error) {
	if len(files) == 0 {
		err = gerror.New("未上传任何文件")
		return
	}
	var (
		typeKey string
		sizeKey string
	)
	if fType == "img" {
		typeKey = "sys.uploadFile.imageType"
		sizeKey = "sys.uploadFile.imageSize"
	} else if fType == "file" {
		typeKey = "sys.uploadFile.fileType"
		sizeKey = "sys.uploadFile.fileSize"
	}
	//获取上传类型配置
	configType, err := getUpConfig(typeKey)
	if err != nil {
		return
	}
	//获取上传大小配置
	configSize, err := getUpConfig(sizeKey)
	if err != nil {
		return
	}
	for _, file := range files {
		//检测文件类型
		rightType := checkFileType(file.Filename, configType.ConfigValue)
		if !rightType {
			err = gerror.New("上传文件类型错误,只能包含后缀为:" + configType.ConfigValue + "的文件。")
			return
		}
		var rightSize bool
		rightSize, err = checkSize(configSize.ConfigValue, file.Size)
		if err != nil {
			return
		}
		if !rightSize {
			err = gerror.New("上传文件超过最大尺寸:" + configSize.ConfigValue)
			return
		}
	}
	path := getUpPath()
	for _, file := range files {
		var fileName string
		fileName, err = file.Save(path, true)
		if err != nil {
			return
		}
		fileInfo := &FileInfo{
			FileName: file.Filename,
			FileSize: file.Size,
			FileUrl:  getUrl(path, fileName),
			FileType: file.Header.Get("Content-type"),
		}
		fileInfos = append(fileInfos, fileInfo)
	}
	return
}

//检查文件大小是否合法
func checkSize(configSize string, fileSize int64) (bool, error) {
	match, err := gregex.MatchString(`^([0-9]+)(?i:([a-z]*))$`, configSize)
	if err != nil {
		return false, err
	}
	if len(match) == 0 {
		err = gerror.New("上传文件大小未设置,请在后台配置,格式为(30M,30k,30MB)")
		return false, err
	}
	var cfSize int64
	switch gstr.ToUpper(match[2]) {
	case "MB", "M":
		cfSize = gconv.Int64(match[1]) * 1024 * 1024
	case "KB", "K":
		cfSize = gconv.Int64(match[1]) * 1024
	case "":
		cfSize = gconv.Int64(match[1])
	}
	if cfSize == 0 {
		err = gerror.New("上传文件大小未设置,请在后台配置,格式为(30M,30k,30MB),最大单位为MB")
		return false, err
	}
	return cfSize >= fileSize, nil
}

//获取上传配置
func getUpConfig(key string) (config *sys_config.Entity, err error) {
	config, err = params_service.GetConfigByKey(key)
	if err != nil {
		return
	}
	if config == nil {
		err = gerror.New("上传文件类型未设置,请在后台配置")
		return
	}
	return
}

//判断上传文件类型是否合法
func checkFileType(fileName, typeString string) bool {
	suffix := gstr.SubStrRune(fileName, gstr.PosRRune(fileName, ".")+1, gstr.LenRune(fileName)-1)
	imageType := gstr.Split(typeString, ",")
	rightType := false
	for _, v := range imageType {
		if gstr.Equal(suffix, v) {
			rightType = true
			break
		}
	}
	return rightType
}

func getUpPath() (upPath string) {
	upPath = uploadPath + gtime.Date() + "/"
	return
}

func getUrl(path, fileName string) string {

	url := gstr.SubStr(path, gstr.Pos(path, upPath)+1) + fileName
	return url
}

//--------------------------------OSS Upload-----------------------------------
const objPrefix = "admin/%s"

func getOssUrl(fileName string) string {
	resUrl := g.Cfg().GetString("oss.resUrl")
	objKey := fmt.Sprintf(objPrefix, fileName)
	return resUrl + objKey
}

func uploadToOss(file *ghttp.UploadFile) (string, error) {

	f, err := file.Open()
	if err != nil {
		g.Log().Error(err)
		return "", err
	}

	accessID := g.Cfg().GetString("oss.accessID")
	accessSecret := g.Cfg().GetString("oss.accessSecret")
	endpoint := g.Cfg().GetString("oss.endpoint")
	bucketName := g.Cfg().GetString("oss.bucketName")

	client, err := oss.New(endpoint, accessID, accessSecret)
	if err != nil {
		g.Log().Error(err)
		return "", err
	}

	//获取存储空间
	bucket, err := client.Bucket(bucketName)
	if err != nil {
		g.Log().Error(err)
		return "", err
	}

	//生成随机文件名
	name := gfile.Basename(file.Filename)
	name = strings.ToLower(strconv.FormatInt(gtime.TimestampNano(), 36) + grand.S(10))
	name = name + gfile.Ext(file.Filename)

	objKey := fmt.Sprintf(objPrefix, name)

	//上传Byte数组
	if err := bucket.PutObject(objKey, f); err != nil {
		g.Log().Error(err)
		return "", err
	}

	if err := bucket.SetObjectACL(objKey, oss.ACLPublicRead); err != nil {
		g.Log().Error(err)
	}

	return name, nil
}

dafa168 avatar Apr 15 '21 06:04 dafa168