一,演示项目的信息
1,项目地址:
GitHub - liuhongdi/digv13: gin框架实现图片文件上传
2,功能说明:
演示了通过gin框架上传图片文件,包括单张上传和多张上传
3, 项目结构:如图:
说明:刘宏缔的go森林是一个专注golang的博客,
网站:https://blog.imgtouch.com
原文: go语言web开发系列之十三:gin框架实现图片文件上传 – 架构森林
说明:作者:刘宏缔 邮箱: 371125307@qq.com
二,配置文件说明:
1,config/config.yaml
Database:
DBType: mysql
UserName: root
Password: password
Host: 127.0.0.1:3306
DBName: dig
Charset: utf8
ParseTime: True
MaxIdleConns: 10
MaxOpenConns: 30
Server:
RunMode: debug
HttpPort: 8000
ReadTimeout: 60
WriteTimeout: 60
Log:
LogFilePath: /data/gologs/logs
LogInfoFileName: info
LogWarnFileName: warn
LogFileExt: log
AccessLog:
LogFilePath: /data/gologs/logs
LogFileName: access
LogFileExt: log
Static:
StaticDir: /data/liuhongdi/digv13/static
ArticleImage:
UploadDir: /data/liuhongdi/digv13/static/ware/article
ImageHost: http://127.0.0.1:8000
说明:StaticDir:静态文件的保存目录
UploadDir:文章配图的上传后保存目录
ImageHost:访问文章配图url的host
三,go代码说明
1,global/setting.go
package global
import (
"fmt"
"github.com/liuhongdi/digv13/pkg/setting"
"time"
)
//服务器配置
type ServerSettingS struct {
RunMode string
HttpPort string
ReadTimeout time.Duration
WriteTimeout time.Duration
}
//数据库配置
type DatabaseSettingS struct {
DBType string
UserName string
Password string
Host string
DBName string
Charset string
ParseTime bool
MaxIdleConns int
MaxOpenConns int
}
//日志配置
type LogSettingS struct {
LogFilePath string //保存到的目录
LogInfoFileName string //info级日志文件的名字
LogWarnFileName string //warn级日志文件的名字
LogAccessFileName string //Access日志文件的名字
LogFileExt string //文件的扩展名
}
//访问日志配置
type AccessLogSettingS struct {
LogFilePath string //保存到的目录
LogFileName string //Access日志文件的名字
LogFileExt string //文件的扩展名
}
//静态目录配置
type StaticSettingS struct {
StaticDir string //静态文件目录
}
//配图的文件路径和host
type ArticleImageSettings struct {
UploadDir string //文章图片文件目录
ImageHost string //访问文章图片文件的host
}
//定义全局变量
var (
ServerSetting *ServerSettingS
DatabaseSetting *DatabaseSettingS
LogSetting *LogSettingS
AccessLogSetting *AccessLogSettingS
StaticSetting *StaticSettingS
ArticleImageSetting *ArticleImageSettings
)
//读取配置到全局变量
func SetupSetting() error {
s, err := setting.NewSetting()
if err != nil {
return err
}
err = s.ReadSection("Database", &DatabaseSetting)
if err != nil {
return err
}
err = s.ReadSection("Server", &ServerSetting)
if err != nil {
return err
}
err = s.ReadSection("Log", &LogSetting)
if err != nil {
return err
}
err = s.ReadSection("Static", &StaticSetting)
if err != nil {
return err
}
err = s.ReadSection("ArticleImage", &ArticleImageSetting)
if err != nil {
return err
}
err = s.ReadSection("AccessLog", &AccessLogSetting)
if err != nil {
return err
}
fmt.Println("setting:")
fmt.Println(ServerSetting)
fmt.Println(DatabaseSetting)
fmt.Println(LogSetting)
fmt.Println(AccessLogSetting)
fmt.Println(StaticSetting)
fmt.Println(ArticleImageSetting)
return nil
}
2,router/touter.go
package router
import (
"github.com/gin-gonic/gin"
"github.com/liuhongdi/digv13/controller"
"github.com/liuhongdi/digv13/global"
"github.com/liuhongdi/digv13/middleware"
"github.com/liuhongdi/digv13/pkg/result"
"net/http"
"runtime/debug"
)
func Router() *gin.Engine {
router := gin.Default()
//处理异常
router.NoRoute(HandleNotFound)
router.NoMethod(HandleNotFound)
router.Use(middleware.AccessLog())
router.Use(Recover)
//static
router.StaticFS("/static", http.Dir(global.StaticSetting.StaticDir))
// 路径映射
articlec:=controller.NewArticleController()
router.GET("/article/getone/:id", articlec.GetOne);
router.GET("/article/list", articlec.GetList);
//图片上传
imagec:=controller.NewImageController()
router.POST("/image/uploadone", imagec.UploadOne);
router.POST("/image/uploadmore", imagec.UploadMore);
return router
}
//404
func HandleNotFound(c *gin.Context) {
global.Logger.Errorf("handle not found: %v", c.Request.RequestURI)
//global.Logger.Errorf("stack: %v",string(debug.Stack()))
result.NewResult(c).Error(404,"资源未找到")
return
}
//500
func Recover(c *gin.Context) {
defer func() {
if r := recover(); r != nil {
//打印错误堆栈信息
//log.Printf("panic: %v\n", r)
global.Logger.Errorf("panic: %v", r)
//log stack
global.Logger.Errorf("stack: %v",string(debug.Stack()))
//print stack
debug.PrintStack()
//return
result.NewResult(c).Error(500,"服务器内部错误")
}
}()
//继续后续接口调用
c.Next()
}
3,controller/imageController.go
package controller
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/liuhongdi/digv13/global"
"github.com/liuhongdi/digv13/pkg/result"
"github.com/liuhongdi/digv13/pkg/validCheck"
"github.com/liuhongdi/digv13/request"
"strconv"
)
type ImageController struct{}
func NewImageController() ImageController {
return ImageController{}
}
//上传单张图片
func (a *ImageController) UploadOne(c *gin.Context) {
resultRes := result.NewResult(c)
param := request.ArticleRequest{ID: validCheck.StrTo(c.Param("id")).MustUInt64()}
valid, errs := validCheck.BindAndValid(c, ¶m)
if !valid {
resultRes.Error(400,errs.Error())
return
}
//save image
//得到图片文件
f, err := c.FormFile("f1s")
//错误处理
if err != nil {
fmt.Println(err.Error())
resultRes.Error(1,"图片上传失败")
} else {
//将文件保存至本项目根目录中
idstr:=strconv.FormatUint(param.ID, 10)
destImage := global.ArticleImageSetting.UploadDir+"/"+idstr+".jpg"
err := c.SaveUploadedFile(f, destImage)
if (err != nil){
fmt.Println("save err:")
fmt.Println(err)
resultRes.Error(1,"图片保存失败")
} else {
imageUrl := global.ArticleImageSetting.ImageHost+"/static/ware/article/"+idstr+".jpg"
resultRes.Success(gin.H{"url":imageUrl})
}
}
return
}
//上传多张图片
func (a *ImageController) UploadMore(c *gin.Context) {
resultRes := result.NewResult(c)
param := request.ArticleRequest{ID: validCheck.StrTo(c.Param("id")).MustUInt64()}
valid, errs := validCheck.BindAndValid(c, ¶m)
if !valid {
resultRes.Error(400,errs.Error())
return
}
//save image,
//得到form
form,err:=c.MultipartForm()
//得到文件列表
files:=form.File["f1m"]
//错误处理
if err != nil {
resultRes.Error(1,"图片上传失败")
return
}
idstr:=strconv.FormatUint(param.ID, 10)
var images []string
for i,f:=range files{
//fmt.Println(f.Filename)
istr := strconv.Itoa(i)
destImage := global.ArticleImageSetting.UploadDir+"/"+idstr+"_"+istr+".jpg"
c.SaveUploadedFile(f,destImage)
//return image url
imageUrl := global.ArticleImageSetting.ImageHost+"/static/ware/article/"+idstr+"_"+istr+".jpg"
images = append(images, imageUrl)
}
resultRes.Success(gin.H{"imagesurls":images})
return
}
说明:代码没有放到service中,主要功能是保存图片文件后返回url地址
4,static/upload.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>上传文件示例</title>
</head>
<body>
单文件上传:<br/>
<form action="/image/uploadone" method="post" enctype="multipart/form-data">
<input type="text" name="id" id="id" placeholder="请输入id" /> <br/>
<input type="file" name="f1s" /><br/>
<input type="submit" value="上传">
</form>
<br/><br/><br/>
多文件上传(可多选):<br/>
<form action="/image/uploadmore" method="post" enctype="multipart/form-data">
<input type="text" name="id" id="id" placeholder="请输入id" /> <br/>
<input type="file" name="f1m" multiple /><br/>
<input type="submit" value="上传">
</form>
</body>
</html>
5,其他相关代码可访问github
四,测试效果
1,查看上传页面:
访问:
http://127.0.0.1:8000/static/upload.html
返回:
2, 上传单张
返回:
3, 上传多张
返回:
五,todo:
1,文件的扩展名要记录下来,通常要写入到数据库
我们这里因为没有记录就只支持jpg一种格式了
2,一个记录下有多张图片,通常会把图片的id记录到数据库中
3,生产环境中不会直接展示用户上传的图片,避免文件太大等问题,
而是会用工具处理缩小为指定大小
这些大家可以自己去实现
六,查看库的版本:
module github.com/liuhongdi/digv13
go 1.15
require (
github.com/gin-gonic/gin v1.6.3
github.com/go-playground/universal-translator v0.17.0
github.com/go-playground/validator/v10 v10.2.0
github.com/jinzhu/gorm v1.9.16
github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f
github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 // indirect
github.com/magiconair/properties v1.8.4 // indirect
github.com/mitchellh/mapstructure v1.3.3 // indirect
github.com/pelletier/go-toml v1.8.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/spf13/afero v1.4.1 // indirect
github.com/spf13/cast v1.3.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.7.1
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.16.0
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 // indirect
golang.org/x/text v0.3.4 // indirect
gopkg.in/ini.v1 v1.62.0 // indirect
gopkg.in/yaml.v2 v2.3.0 // indirect
)