add: upload function
This commit is contained in:
parent
3b1b9938d3
commit
8640d56ce2
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
*.out
|
*.out
|
||||||
tmp/log/*
|
tmp/*
|
||||||
.vscode/*
|
.vscode/*
|
||||||
conf/app.ini
|
conf/app.ini
|
||||||
.idea/*
|
.idea/*
|
||||||
|
|
|
@ -4,6 +4,10 @@ app:
|
||||||
jwt_expire: '7200'
|
jwt_expire: '7200'
|
||||||
run_mode: 'debug'
|
run_mode: 'debug'
|
||||||
page_size: 10
|
page_size: 10
|
||||||
|
upload_save_path: 'tmp/uploads'
|
||||||
|
upload_server_path: '/static'
|
||||||
|
upload_image_max_size: 5 # MB
|
||||||
|
upload_image_allow_exts: ['.jpg','.jpeg','.png']
|
||||||
server:
|
server:
|
||||||
http_port: 8000
|
http_port: 8000
|
||||||
read_timeout: 60
|
read_timeout: 60
|
||||||
|
|
|
@ -1,15 +1,19 @@
|
||||||
/*
|
/*
|
||||||
* @Date: 2021-03-22 09:46:19
|
* @Date: 2021-03-22 09:46:19
|
||||||
* @LastEditors: viletyy
|
* @LastEditors: viletyy
|
||||||
* @LastEditTime: 2021-06-10 18:24:42
|
* @LastEditTime: 2021-06-11 16:40:34
|
||||||
* @FilePath: /potato/config/app.go
|
* @FilePath: /potato/config/app.go
|
||||||
*/
|
*/
|
||||||
package config
|
package config
|
||||||
|
|
||||||
type App struct {
|
type App struct {
|
||||||
PageSize int64 `mapstructure:"page_size" json:"page_size" yaml:"page_size"`
|
PageSize int64 `mapstructure:"page_size" json:"page_size" yaml:"page_size"`
|
||||||
JwtSecret string `mapstructure:"jwt_secret" json:"jwt_secret" yaml:"jwt_secret"`
|
JwtSecret string `mapstructure:"jwt_secret" json:"jwt_secret" yaml:"jwt_secret"`
|
||||||
JwtIssuser string `mapstructure:"jwt_issuser" json:"jwt_issuser" yaml:"jwt_issuser"`
|
JwtIssuser string `mapstructure:"jwt_issuser" json:"jwt_issuser" yaml:"jwt_issuser"`
|
||||||
JwtExpire int64 `mapstructure:"jwt_expire" json:"jwt_expire" yaml:"jwt_expire"`
|
JwtExpire int64 `mapstructure:"jwt_expire" json:"jwt_expire" yaml:"jwt_expire"`
|
||||||
RunMode string `mapstructure:"run_mode" json:"run_mode" yaml:"run_mode"`
|
RunMode string `mapstructure:"run_mode" json:"run_mode" yaml:"run_mode"`
|
||||||
|
UploadSavePath string `mapstructure:"upload_save_path" json:"upload_save_path" yaml:"upload_save_path"`
|
||||||
|
UploadServerPath string `mapstructure:"upload_server_path" json:"upload_server_path" yaml:"upload_server_path"`
|
||||||
|
UploadImageMaxSize int64 `mapstructure:"upload_image_max_size" json:"upload_image_max_size" yaml:"upload_image_max_size"`
|
||||||
|
UploadImageAllowExts []string `mapstructure:"upload_image_allow_exts" json:"upload_image_allow_exts" yaml:"upload_image_allow_exts"`
|
||||||
}
|
}
|
||||||
|
|
42
docs/docs.go
42
docs/docs.go
|
@ -59,6 +59,48 @@ var doc = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/v1/upload": {
|
||||||
|
"post": {
|
||||||
|
"consumes": [
|
||||||
|
"multipart/form-data"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"summary": "上传文件",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "auth by /auth",
|
||||||
|
"name": "token",
|
||||||
|
"in": "header",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "file",
|
||||||
|
"description": "文件",
|
||||||
|
"name": "file",
|
||||||
|
"in": "formData",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "文件类型",
|
||||||
|
"name": "type",
|
||||||
|
"in": "formData",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "请求成功",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/errcode.Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/v1/vendors": {
|
"/v1/vendors": {
|
||||||
"get": {
|
"get": {
|
||||||
"consumes": [
|
"consumes": [
|
||||||
|
|
|
@ -43,6 +43,48 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/v1/upload": {
|
||||||
|
"post": {
|
||||||
|
"consumes": [
|
||||||
|
"multipart/form-data"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"summary": "上传文件",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "auth by /auth",
|
||||||
|
"name": "token",
|
||||||
|
"in": "header",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "file",
|
||||||
|
"description": "文件",
|
||||||
|
"name": "file",
|
||||||
|
"in": "formData",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "文件类型",
|
||||||
|
"name": "type",
|
||||||
|
"in": "formData",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "请求成功",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/errcode.Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/v1/vendors": {
|
"/v1/vendors": {
|
||||||
"get": {
|
"get": {
|
||||||
"consumes": [
|
"consumes": [
|
||||||
|
|
|
@ -40,6 +40,34 @@ paths:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/errcode.Error'
|
$ref: '#/definitions/errcode.Error'
|
||||||
summary: 鉴权验证
|
summary: 鉴权验证
|
||||||
|
/v1/upload:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- multipart/form-data
|
||||||
|
parameters:
|
||||||
|
- description: auth by /auth
|
||||||
|
in: header
|
||||||
|
name: token
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- description: 文件
|
||||||
|
in: formData
|
||||||
|
name: file
|
||||||
|
required: true
|
||||||
|
type: file
|
||||||
|
- description: 文件类型
|
||||||
|
in: formData
|
||||||
|
name: type
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: 请求成功
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/errcode.Error'
|
||||||
|
summary: 上传文件
|
||||||
/v1/vendors:
|
/v1/vendors:
|
||||||
get:
|
get:
|
||||||
consumes:
|
consumes:
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
/*
|
||||||
|
* @Date: 2021-06-11 17:27:25
|
||||||
|
* @LastEditors: viletyy
|
||||||
|
* @LastEditTime: 2021-06-11 17:54:34
|
||||||
|
* @FilePath: /potato/internal/controller/api/v1/upload.go
|
||||||
|
*/
|
||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/viletyy/potato/global"
|
||||||
|
"github.com/viletyy/potato/internal/service"
|
||||||
|
"github.com/viletyy/potato/pkg/app"
|
||||||
|
"github.com/viletyy/potato/pkg/errcode"
|
||||||
|
"github.com/viletyy/potato/pkg/upload"
|
||||||
|
"github.com/viletyy/yolk/convert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Upload struct{}
|
||||||
|
|
||||||
|
func NewUpload() Upload {
|
||||||
|
return Upload{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Summary 上传文件
|
||||||
|
// @Description
|
||||||
|
// @Accept mpfd
|
||||||
|
// @Produce json
|
||||||
|
// @Param token header string true "auth by /auth"
|
||||||
|
// @Param file formData file true "文件"
|
||||||
|
// @Param type formData int true "文件类型"
|
||||||
|
// @Success 200 {object} errcode.Error "请求成功"
|
||||||
|
// @Router /v1/upload [post]
|
||||||
|
func (u Upload) Create(c *gin.Context) {
|
||||||
|
response := app.NewResponse(c)
|
||||||
|
file, fileHeader, err := c.Request.FormFile("file")
|
||||||
|
if err != nil {
|
||||||
|
global.GO_LOG.Sugar().Errorf("c.Request.FormFile err: %v", err)
|
||||||
|
errorCode := errcode.InvalidParams
|
||||||
|
errorCode.WithData(err)
|
||||||
|
response.ToErrorResponse(errorCode)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fileType, err := convert.StrTo(c.PostForm("type")).Int()
|
||||||
|
if err != nil {
|
||||||
|
global.GO_LOG.Sugar().Errorf("convert.StrTo err: %v", err)
|
||||||
|
response.ToErrorResponse(errcode.InvalidParams)
|
||||||
|
}
|
||||||
|
|
||||||
|
svc := service.New(c.Request.Context())
|
||||||
|
fileInfo, err := svc.UploadFile(upload.FileType(fileType), file, fileHeader)
|
||||||
|
if err != nil {
|
||||||
|
global.GO_LOG.Sugar().Errorf("svc.UploadFile err :v", err)
|
||||||
|
errorCode := errcode.ErrorUploadFileFail
|
||||||
|
errorCode.WithData(err)
|
||||||
|
response.ToErrorResponse(errorCode)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.ToResponse(gin.H{
|
||||||
|
"file_access_url": fileInfo.AccessUrl,
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,12 +1,14 @@
|
||||||
/*
|
/*
|
||||||
* @Date: 2021-03-21 19:54:57
|
* @Date: 2021-03-21 19:54:57
|
||||||
* @LastEditors: viletyy
|
* @LastEditors: viletyy
|
||||||
* @LastEditTime: 2021-06-10 19:02:31
|
* @LastEditTime: 2021-06-11 17:57:21
|
||||||
* @FilePath: /potato/internal/routers/router.go
|
* @FilePath: /potato/internal/routers/router.go
|
||||||
*/
|
*/
|
||||||
package routers
|
package routers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
_ "github.com/swaggo/gin-swagger"
|
_ "github.com/swaggo/gin-swagger"
|
||||||
ginSwagger "github.com/swaggo/gin-swagger"
|
ginSwagger "github.com/swaggo/gin-swagger"
|
||||||
|
@ -32,6 +34,7 @@ func InitRouter() *gin.Engine {
|
||||||
gin.SetMode(global.GO_CONFIG.App.RunMode)
|
gin.SetMode(global.GO_CONFIG.App.RunMode)
|
||||||
|
|
||||||
Engine.Use(middleware.CORS())
|
Engine.Use(middleware.CORS())
|
||||||
|
Engine.StaticFS("/static", http.Dir(global.GO_CONFIG.App.UploadSavePath))
|
||||||
|
|
||||||
Engine.POST("/api/v1/auth", v1.GetAuth)
|
Engine.POST("/api/v1/auth", v1.GetAuth)
|
||||||
Engine.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
|
Engine.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
|
||||||
|
@ -43,5 +46,7 @@ func InitRouter() *gin.Engine {
|
||||||
|
|
||||||
func V1InitModule() {
|
func V1InitModule() {
|
||||||
V1RouterGroup.Use(middleware.JWT())
|
V1RouterGroup.Use(middleware.JWT())
|
||||||
|
upload := v1.NewUpload()
|
||||||
|
V1RouterGroup.POST("/upload", upload.Create)
|
||||||
V1InitBasicRouter()
|
V1InitBasicRouter()
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* @Date: 2021-06-11 17:07:48
|
||||||
|
* @LastEditors: viletyy
|
||||||
|
* @LastEditTime: 2021-06-11 17:24:45
|
||||||
|
* @FilePath: /potato/internal/service/upload.go
|
||||||
|
*/
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"mime/multipart"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/viletyy/potato/global"
|
||||||
|
"github.com/viletyy/potato/pkg/upload"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FileInfo struct {
|
||||||
|
Name string
|
||||||
|
AccessUrl string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (svc *Service) UploadFile(fileType upload.FileType, file multipart.File, fileHeader *multipart.FileHeader) (*FileInfo, error) {
|
||||||
|
fileName := upload.GetFileName(fileHeader.Filename)
|
||||||
|
if !upload.CheckContainExt(fileType, fileName) {
|
||||||
|
return nil, errors.New("file suffix is not supported.")
|
||||||
|
}
|
||||||
|
if !upload.CheckMaxSize(fileType, file) {
|
||||||
|
return nil, errors.New("exceeded maximum file limit.")
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadSavePath := upload.GetSavePath()
|
||||||
|
if upload.CheckSavePath(uploadSavePath) {
|
||||||
|
if err := upload.CreateSavePath(uploadSavePath, os.ModePerm); err != nil {
|
||||||
|
return nil, errors.New("failed to create save directory.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if upload.CheckPermission(uploadSavePath) {
|
||||||
|
return nil, errors.New("insufficient file permissions.")
|
||||||
|
}
|
||||||
|
|
||||||
|
dst := uploadSavePath + "/" + fileName
|
||||||
|
if err := upload.SaveFile(fileHeader, dst); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
accessUrl := global.GO_CONFIG.App.UploadServerPath + "/" + fileName
|
||||||
|
return &FileInfo{Name: fileName, AccessUrl: accessUrl}, nil
|
||||||
|
}
|
|
@ -1,12 +1,13 @@
|
||||||
/*
|
/*
|
||||||
* @Date: 2021-06-10 23:09:09
|
* @Date: 2021-06-10 23:09:09
|
||||||
* @LastEditors: viletyy
|
* @LastEditors: viletyy
|
||||||
* @LastEditTime: 2021-06-11 15:16:59
|
* @LastEditTime: 2021-06-11 17:25:58
|
||||||
* @FilePath: /potato/pkg/errcode/module_code.go
|
* @FilePath: /potato/pkg/errcode/module_code.go
|
||||||
*/
|
*/
|
||||||
package errcode
|
package errcode
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
ErrorUploadFileFail = NewError(20001, "上传文件失败")
|
||||||
ErrorGetVendorListFail = NewError(20101, "获取系统厂商列表失败")
|
ErrorGetVendorListFail = NewError(20101, "获取系统厂商列表失败")
|
||||||
ErrorGetVendorFail = NewError(20102, "获取系统厂商失败")
|
ErrorGetVendorFail = NewError(20102, "获取系统厂商失败")
|
||||||
ErrorCreateVendorFail = NewError(20103, "创建系统厂商失败")
|
ErrorCreateVendorFail = NewError(20103, "创建系统厂商失败")
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
/*
|
||||||
|
* @Date: 2021-06-11 16:01:37
|
||||||
|
* @LastEditors: viletyy
|
||||||
|
* @LastEditTime: 2021-06-11 17:07:43
|
||||||
|
* @FilePath: /potato/pkg/upload/file.go
|
||||||
|
*/
|
||||||
|
package upload
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"mime/multipart"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/viletyy/potato/global"
|
||||||
|
"github.com/viletyy/yolk/crypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FileType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
TypeImage FileType = iota + 1
|
||||||
|
TypeExcel
|
||||||
|
TypeTxt
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetFileName(name string) string {
|
||||||
|
ext := GetFileExt(name)
|
||||||
|
fileName := strings.TrimSuffix(name, ext)
|
||||||
|
fileName = crypt.Md5Encode(fileName)
|
||||||
|
|
||||||
|
return fileName + ext
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetFileExt(name string) string {
|
||||||
|
return path.Ext(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSavePath() string {
|
||||||
|
return global.GO_CONFIG.App.UploadSavePath
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckSavePath(dst string) bool {
|
||||||
|
_, err := os.Stat(dst)
|
||||||
|
return os.IsNotExist(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckContainExt(t FileType, name string) bool {
|
||||||
|
ext := GetFileExt(name)
|
||||||
|
ext = strings.ToUpper(ext)
|
||||||
|
switch t {
|
||||||
|
case TypeImage:
|
||||||
|
for _, allowExt := range global.GO_CONFIG.App.UploadImageAllowExts {
|
||||||
|
if strings.ToUpper(allowExt) == ext {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckMaxSize(t FileType, f multipart.File) bool {
|
||||||
|
content, _ := ioutil.ReadAll(f)
|
||||||
|
size := len(content)
|
||||||
|
switch t {
|
||||||
|
case TypeImage:
|
||||||
|
if size <= int(global.GO_CONFIG.App.UploadImageMaxSize*1024*1024) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckPermission(dst string) bool {
|
||||||
|
_, err := os.Stat(dst)
|
||||||
|
return os.IsPermission(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateSavePath(dst string, perm os.FileMode) error {
|
||||||
|
err := os.MkdirAll(dst, perm)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SaveFile(file *multipart.FileHeader, dst string) error {
|
||||||
|
src, err := file.Open()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer src.Close()
|
||||||
|
|
||||||
|
out, err := os.Create(dst)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(out, src)
|
||||||
|
return err
|
||||||
|
}
|
Loading…
Reference in New Issue