Gin编写图床后端-上传图片代码实现

日常我们会使用到到图床来存放图片,但是大致流程是什么样子的来??

 

 

 

图床原理

实现一个HTTP服务器,用这个服务器存储图片,针对每个图片提供一个唯一的url,借助这个url就可以将图片展示到其他网页上

后端代码实现

 项目接口

首先,我们需要建立Gin框架 post请求访问,上传图片,判断图片是否符合,在上传图片,返回Url地址,浏览器就可以正常访问

 app.ini

[app]
HttpPort = 8000

RuntimeRootPath = runtime/

ImagePrefixUrl = http://127.0.0.1:8000
ImageSavePath = upload/images/
# MB
ImageMaxSize = 5
ImageAllowExts = .jpg,.jpeg,.png,.gif

LogSavePath = logs/
LogSaveName = log
LogFileExt = log
TimeFormat = 20060102


app/upload.go

package app

import (
	"ImagesUpload/pkg/e"
	"ImagesUpload/pkg/logging"
	"ImagesUpload/pkg/upload"
	"fmt"
	"github.com/gin-gonic/gin"
	"net/http"
	"strconv"
	"time"
)
//获取ip
func GetRequestIP(c *gin.Context) string {
	reqIP := c.ClientIP()
	if reqIP == "::1" {
		reqIP = "127.0.0.1"
	}
	return reqIP
}

func UploadImage(c *gin.Context) {
	code := e.SUCCESS
	data := make(map[string]string)
	t := time.Now()
	year := t.Year()   // type int
	month := t.Month() // type time.Month
	file, image, err := c.Request.FormFile("image")
	if err != nil {
		logging.Warn(err)
		code = e.ERROR
		c.JSON(http.StatusOK, gin.H{
			"code": code,
			"msg":  e.GetMsg(code),
			"data": data,
		})
	}

	if image == nil {
		code = e.INVALID_PARAMS
	} else {
		imageName := upload.GetImageName(image.Filename)
		fullPath := upload.GetImageFullPath() + strconv.Itoa(year) + strconv.Itoa(int(month))
		savePath := upload.GetImagePath() + strconv.Itoa(year) + strconv.Itoa(int(month))
		src := fullPath + "/" + imageName
		fmt.Println(imageName, fullPath, savePath, src)
		if !upload.CheckImageExt(imageName) || !upload.CheckImageSize(file) {
			code = e.ERROR_UPLOAD_CHECK_IMAGE_FORMAT
			fmt.Println(e.GetMsg(code))
		} else {
			err := upload.CheckImage(fullPath)
			if err != nil {
				logging.Warn(err)
				code = e.ERROR_UPLOAD_CHECK_IMAGE_FAIL
			} else if err := c.SaveUploadedFile(image, src); err != nil {
				logging.Warn(err)
				code = e.ERROR_UPLOAD_SAVE_IMAGE_FAIL
			} else {
				logging.Info("访问者IP:",GetRequestIP(c),"上传地址连接:",upload.GetImageFullUrl(imageName))
				data["image_url"] = upload.GetImageFullUrl(imageName)
				data["image_save_url"] = savePath + "/" + imageName
			}
		}
	}
	c.JSON(http.StatusOK, gin.H{
		"code": code,
		"msg":  e.GetMsg(code),
		"data": data,
	})
}

pkg

 pkg/e/code.go

package e

const  (
	SUCCESS        = 200
	ERROR          = 500
	INVALID_PARAMS = 400
	// 保存图片失败
	ERROR_UPLOAD_SAVE_IMAGE_FAIL = 30001
	// 检查图片失败
	ERROR_UPLOAD_CHECK_IMAGE_FAIL = 30002
	// 校验图片错误,图片格式或大小有问题
	ERROR_UPLOAD_CHECK_IMAGE_FORMAT = 30003
)

pkg/e/msg.go

package e

var MsgFlags = map[int]string {
	SUCCESS:                        "ok",
	ERROR:                          "fail",
	INVALID_PARAMS:                 "请求参数错误",
	// 保存图片失败
	ERROR_UPLOAD_SAVE_IMAGE_FAIL: "保存图片失败",
	// 检查图片失败
	ERROR_UPLOAD_CHECK_IMAGE_FAIL: "检查图片失败",
	// 校验图片错误,图片格式或大小有问题
	ERROR_UPLOAD_CHECK_IMAGE_FORMAT: "校验图片错误,图片格式或大小有问题",
}

func GetMsg(code int) string {
	msg ,ok := MsgFlags[code]
	if ok{
		return msg
	}
	return MsgFlags[ERROR]
}

pkg/file/file.go

package file

import (
	"io/ioutil"
	"mime/multipart"
	"os"
	"path"
)

/*
GetSize:获取文件大小
GetExt:获取文件后缀
CheckNotExist:检查文件是否存在
CheckPermission:检查文件权限
IsNotExistMkDir:如果不存在则新建文件夹
MkDir:新建文件夹
Open:打开文件
*/
//GetSize:获取文件大小
func GetSize(f multipart.File) (int, error) {
	content, err := ioutil.ReadAll(f)

	return len(content), err
}

//GetExt:获取文件后缀
func GetExt(fileName string) string {
	return path.Ext(fileName)
}

//CheckNotExist:检查文件是否存在
func CheckNotExist(src string) bool {
	_, err := os.Stat(src)

	return os.IsNotExist(err)
}

//CheckPermission:检查文件权限
func CheckPermission(src string) bool {
	_, err := os.Stat(src)

	return os.IsPermission(err)
}

//IsNotExistMkDir:如果不存在则新建文件夹
func IsNotExistMkDir(src string) error {
	if notExist := CheckNotExist(src); notExist == true {
		if err := MkDir(src); err != nil {
			return err
		}
	}

	return nil
}

//MkDir:新建文件夹
func MkDir(src string) error {
	err := os.MkdirAll(src, os.ModePerm)
	if err != nil {
		return err
	}

	return nil
}

//Open:打开文件
func Open(name string, flag int, perm os.FileMode) (*os.File, error) {
	f, err := os.OpenFile(name, flag, perm)
	if err != nil {
		return nil, err
	}

	return f, nil
}

pkg/logging/file.go

package logging

import (
	"ImagesUpload/pkg/file"
	"ImagesUpload/setting"
	"fmt"
	"os"
	"time"
)

func getLogFilePath() string {
	return fmt.Sprintf("%s%s", setting.AppSetting.RuntimeRootPath, setting.AppSetting.LogSavePath)
}

func getLogFileName() string {
	return fmt.Sprintf("%s%s.%s",
		setting.AppSetting.LogSaveName,
		time.Now().Format(setting.AppSetting.TimeFormat),
		setting.AppSetting.LogFileExt,
	)
}

func openLogFile(fileName, filePath string) (*os.File, error) {
	dir, err := os.Getwd()
	if err != nil {
		return nil, fmt.Errorf("os.Getwd err: %v", err)
	}

	src := dir + "/" + filePath
	perm := file.CheckPermission(src)
	if perm == true {
		return nil, fmt.Errorf("file.CheckPermission Permission denied src: %s", src)
	}

	err = file.IsNotExistMkDir(src)
	if err != nil {
		return nil, fmt.Errorf("file.IsNotExistMkDir src: %s, err: %v", src, err)
	}

	f, err := file.Open(src+fileName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		return nil, fmt.Errorf("Fail to OpenFile :%v", err)
	}

	return f, nil
}

pkg/logging/log.go

package logging

import (
	"fmt"
	"log"
	"os"
	"path/filepath"
	"runtime"
)


type Level int

var (
	F *os.File

	DefaultPrefix      = ""
	DefaultCallerDepth = 2

	logger     *log.Logger
	logPrefix  = ""
	levelFlags = []string{"DEBUG", "INFO", "WARN", "ERROR", "FATAL"}
)

const (
	DEBUG Level = iota
	INFO
	WARNING
	ERROR
	FATAL
)

func Setup() {
	var err error
	filePath := getLogFilePath()
	fileName := getLogFileName()
	F, err = openLogFile(fileName, filePath)
	if err != nil {
		log.Fatalln(err)
	}

	logger = log.New(F, DefaultPrefix, log.LstdFlags)
}

func Debug(v ...interface{}) {
	setPrefix(DEBUG)
	logger.Println(v)
}

func Info(v ...interface{}) {
	setPrefix(INFO)
	logger.Println(v)
}

func Warn(v ...interface{}) {
	setPrefix(WARNING)
	logger.Println(v)
}

func Error(v ...interface{}) {
	setPrefix(ERROR)
	logger.Println(v)
}

func Fatal(v ...interface{}) {
	setPrefix(FATAL)
	logger.Fatalln(v)
}

func setPrefix(level Level) {
	_, file, line, ok := runtime.Caller(DefaultCallerDepth)
	if ok {
		logPrefix = fmt.Sprintf("[%s][%s:%d]", levelFlags[level], filepath.Base(file), line)
	} else {
		logPrefix = fmt.Sprintf("[%s]", levelFlags[level])
	}

	logger.SetPrefix(logPrefix)
}

pkg/logging/logrus.go

package logging

import (
	"fmt"
	"github.com/sirupsen/logrus"
)

func Logger() *logrus.Logger {
	//now := time.Now()
	//logFilePath := getLogFilePath()
	//logFileName :=  getLogFileFullPath()
	//日志文件
	//fileName := path.Join(logFilePath, logFileName)
	//if _, err := os.Stat(fileName); err != nil {
	//	if _, err := os.Create(fileName); err != nil {
	//		fmt.Println(err.Error())
	//	}
	//}
	//写入文件
	filePath := getLogFilePath()
	fileName := getLogFileName()
	F, err := openLogFile(fileName, filePath)
	if err != nil {
		fmt.Println("日志写入失败,请检查")
	}
	//实例化
	logger := logrus.New()

	//设置输出
	logger.Out = F

	//设置日志级别
	logger.SetLevel(logrus.DebugLevel)

	//设置日志格式
	logger.SetFormatter(&logrus.TextFormatter{
		TimestampFormat: "2006-01-02 15:04:05",
	})
	return logger
}

pkg/upload/image.go

package upload

import (
	"ImagesUpload/pkg/file"
	"ImagesUpload/pkg/logging"
	"ImagesUpload/pkg/util"
	"ImagesUpload/setting"
	"fmt"
	"log"
	"mime/multipart"
	"os"
	"path"
	"strconv"
	"strings"
	"time"
)

/*
GetImageFullUrl:获取图片完整访问 URL
GetImageName:获取图片名称
GetImagePath:获取图片路径
GetImageFullPath:获取图片完整路径
CheckImageExt:检查图片后缀
CheckImageSize:检查图片大小
CheckImage:检查图片
*/

//GetImageFullUrl:获取图片完整访问 URL
func GetImageFullUrl(name string) string {
	t := time.Now()
	year := t.Year()   // type int
	month := t.Month() // type time.Month
	return setting.AppSetting.ImagePrefixUrl + "/" + GetImagePath() + strconv.Itoa(year) + strconv.Itoa(int(month)) + "/" + name
}

//GetImageName:获取图片名称
func GetImageName(name string) string {
	ext := path.Ext(name)
	fileName := strings.TrimSuffix(name, ext)
	fileName = util.EncodeMD5(fileName)

	return fileName + ext
}

//GetImagePath:获取图片路径
func GetImagePath() string {
	return setting.AppSetting.ImageSavePath
}

//GetImageFullPath:获取图片完整路径
func GetImageFullPath() string {
	return setting.AppSetting.RuntimeRootPath + GetImagePath()
}

//CheckImageExt:检查图片后缀
func CheckImageExt(fileName string) bool {
	ext := file.GetExt(fileName)
	for _, allowExt := range setting.AppSetting.ImageAllowExts {
		if strings.ToUpper(allowExt) == strings.ToUpper(ext) {
			return true
		}
	}

	return false
}

//CheckImageSize:检查图片大小
func CheckImageSize(f multipart.File) bool {
	size, err := file.GetSize(f)
	if err != nil {
		log.Println(err)
		logging.Warn(err)
		return false
	}

	return size <= setting.AppSetting.ImageMaxSize
}

//CheckImage:检查图片
func CheckImage(src string) error {
	dir, err := os.Getwd()
	if err != nil {
		return fmt.Errorf("os.Getwd err: %v", err)
	}

	err = file.IsNotExistMkDir(dir + "/" + src)
	if err != nil {
		return fmt.Errorf("file.IsNotExistMkDir err: %v", err)
	}

	perm := file.CheckPermission(src)
	if perm == true {
		return fmt.Errorf("file.CheckPermission Permission denied src: %s", src)
	}

	return nil
}

pkg/util/md5.go

package util

import (
	"crypto/md5"
	"encoding/hex"
)

func EncodeMD5(value string) string {
	m := md5.New()
	m.Write([]byte(value))
	return hex.EncodeToString(m.Sum(nil))
}

routers/router.go

package routers

import (
	"ImagesUpload/app"
	"ImagesUpload/pkg/upload"
	"github.com/gin-gonic/gin"
	"net/http"
)

func InitRouter() *gin.Engine {
	r := gin.New()

	r.Use(gin.Logger())

	r.Use(gin.Recovery())
	r.StaticFS("/upload/images", http.Dir(upload.GetImageFullPath()))
	r.POST("upload", app.UploadImage)
	return r
}

setting/setting.go

package setting

import (
	"github.com/go-ini/ini"
	"log"
)

type App struct {
	HttpPort int
	RuntimeRootPath string

	ImagePrefixUrl string
	ImageSavePath  string
	ImageMaxSize   int
	ImageAllowExts []string

	LogSavePath string
	LogSaveName string
	LogFileExt  string
	TimeFormat  string
}

var AppSetting = &App{}

func Setup() {
	Cfg, err := ini.Load("conf/app.ini")
	if err != nil {
		log.Fatalf("Fail to parse 'conf/app.ini': %v", err)
	}

	err = Cfg.Section("app").MapTo(AppSetting)
	if err != nil {
		log.Fatalf("Cfg.MapTo AppSetting err: %v", err)
	}
	AppSetting.ImageMaxSize = AppSetting.ImageMaxSize * 1024 * 1024
}

main.go

package main

import (
	"ImagesUpload/pkg/logging"
	"ImagesUpload/routers"
	"ImagesUpload/setting"
	"fmt"
	"log"
	"net/http"
)

func main() {
	setting.Setup()
	logging.Setup()
	router := routers.InitRouter()
	fmt.Println("启动端口:", setting.AppSetting.HttpPort)
	s := &http.Server{
		Addr:           fmt.Sprintf(":%d", setting.AppSetting.HttpPort),
		Handler:        router,
	}
	if err := s.ListenAndServe(); err != nil {
		log.Printf("Listen: %s\n", err)
	}
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南宫乘风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值