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
|
||||
*.out
|
||||
tmp/log/*
|
||||
tmp/*
|
||||
.vscode/*
|
||||
conf/app.ini
|
||||
.idea/*
|
||||
|
|
|
@ -4,6 +4,10 @@ app:
|
|||
jwt_expire: '7200'
|
||||
run_mode: 'debug'
|
||||
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:
|
||||
http_port: 8000
|
||||
read_timeout: 60
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
* @Date: 2021-03-22 09:46:19
|
||||
* @LastEditors: viletyy
|
||||
* @LastEditTime: 2021-06-10 18:24:42
|
||||
* @LastEditTime: 2021-06-11 16:40:34
|
||||
* @FilePath: /potato/config/app.go
|
||||
*/
|
||||
package config
|
||||
|
@ -12,4 +12,8 @@ type App struct {
|
|||
JwtIssuser string `mapstructure:"jwt_issuser" json:"jwt_issuser" yaml:"jwt_issuser"`
|
||||
JwtExpire int64 `mapstructure:"jwt_expire" json:"jwt_expire" yaml:"jwt_expire"`
|
||||
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": {
|
||||
"get": {
|
||||
"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": {
|
||||
"get": {
|
||||
"consumes": [
|
||||
|
|
|
@ -40,6 +40,34 @@ paths:
|
|||
schema:
|
||||
$ref: '#/definitions/errcode.Error'
|
||||
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:
|
||||
get:
|
||||
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
|
||||
* @LastEditors: viletyy
|
||||
* @LastEditTime: 2021-06-10 19:02:31
|
||||
* @LastEditTime: 2021-06-11 17:57:21
|
||||
* @FilePath: /potato/internal/routers/router.go
|
||||
*/
|
||||
package routers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
_ "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)
|
||||
|
||||
Engine.Use(middleware.CORS())
|
||||
Engine.StaticFS("/static", http.Dir(global.GO_CONFIG.App.UploadSavePath))
|
||||
|
||||
Engine.POST("/api/v1/auth", v1.GetAuth)
|
||||
Engine.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
|
||||
|
@ -43,5 +46,7 @@ func InitRouter() *gin.Engine {
|
|||
|
||||
func V1InitModule() {
|
||||
V1RouterGroup.Use(middleware.JWT())
|
||||
upload := v1.NewUpload()
|
||||
V1RouterGroup.POST("/upload", upload.Create)
|
||||
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
|
||||
* @LastEditors: viletyy
|
||||
* @LastEditTime: 2021-06-11 15:16:59
|
||||
* @LastEditTime: 2021-06-11 17:25:58
|
||||
* @FilePath: /potato/pkg/errcode/module_code.go
|
||||
*/
|
||||
package errcode
|
||||
|
||||
var (
|
||||
ErrorUploadFileFail = NewError(20001, "上传文件失败")
|
||||
ErrorGetVendorListFail = NewError(20101, "获取系统厂商列表失败")
|
||||
ErrorGetVendorFail = NewError(20102, "获取系统厂商失败")
|
||||
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