提交 640fb53f 编写于 作者: Mr.奇淼('s avatar Mr.奇淼(

断点续传 开发中

上级 20ab5a8d
package api
import (
"fmt"
"gin-vue-admin/controller/servers"
"gin-vue-admin/model/dbModel"
"github.com/gin-gonic/gin"
"io/ioutil"
"strconv"
)
// @Tags ExaFileUploadAndDownload
// @Summary 断点续传到服务器
// @Security ApiKeyAuth
// @accept multipart/form-data
// @Produce application/json
// @Param file formData file true "断点续传示例"
// @Success 200 {string} string "{"success":true,"data":{},"msg":"上传成功"}"
// @Router /fileUploadAndDownload/breakpointContinue [post]
func BreakpointContinue(c *gin.Context) {
fileMd5 := c.Request.FormValue("fileMd5")
fileName := c.Request.FormValue("fileName")
chunkMd5 := c.Request.FormValue("chunkMd5")
chunkNumber, _ := strconv.Atoi(c.Request.FormValue("chunkNumber"))
chunkTotal, _ := strconv.Atoi(c.Request.FormValue("chunkTotal"))
_, FileHeader, err := c.Request.FormFile("file")
if err != nil {
servers.ReportFormat(c, false, fmt.Sprintf("%v", err), gin.H{})
} else {
f, err := FileHeader.Open()
if err != nil {
servers.ReportFormat(c, false, fmt.Sprintf("%v", err), gin.H{})
} else {
cen, _ := ioutil.ReadAll(f)
defer f.Close()
if flag := servers.CheckMd5(cen, chunkMd5); flag {
err, file := new(dbModel.ExaFile).FindOrCreateFile(fileMd5, fileName, chunkTotal)
if err != nil {
servers.ReportFormat(c, false, fmt.Sprintf("%v", err), gin.H{})
} else {
err, pathc := servers.BreakPointContinue(cen, fileName, chunkNumber, chunkTotal, fileMd5)
if err != nil {
servers.ReportFormat(c, false, fmt.Sprintf("%v", err), gin.H{})
} else {
err = file.CreateFileChunk(pathc, chunkNumber)
if err != nil {
servers.ReportFormat(c, false, fmt.Sprintf("%v", err), gin.H{})
} else {
servers.ReportFormat(c, true, "切片创建成功", gin.H{})
}
}
}
} else {
}
}
}
}
// @Tags ExaFileUploadAndDownload
// @Summary 查找文件
// @Security ApiKeyAuth
// @accept multipart/form-data
// @Produce application/json
// @Param file params file true "查找文件"
// @Success 200 {string} string "{"success":true,"data":{},"msg":"查找成功"}"
// @Router /fileUploadAndDownload/findFile [post]
func FindFile(c *gin.Context) {
fileMd5 := c.Query("fileMd5")
fileName := c.Query("fileName")
chunkTotal, _ := strconv.Atoi(c.Query("chunkTotal"))
err, file := new(dbModel.ExaFile).FindOrCreateFile(fileMd5, fileName, chunkTotal)
if err != nil {
servers.ReportFormat(c, false, fmt.Sprintf("查找失败:%v", err), gin.H{})
} else {
servers.ReportFormat(c, true, "查找成功", gin.H{"file": file})
}
}
// @Tags ExaFileUploadAndDownload
// @Summary 查找文件
// @Security ApiKeyAuth
// @accept multipart/form-data
// @Produce application/json
// @Param file params file true "查找文件"
// @Success 200 {string} string "{"success":true,"data":{},"msg":"查找成功"}"
// @Router /fileUploadAndDownload/findFile [post]
func BreakpointContinueFinish(c *gin.Context) {
fileMd5 := c.Query("fileMd5")
fileName := c.Query("fileName")
err, filePath := servers.MakeFile(fileName, fileMd5)
if err != nil {
servers.ReportFormat(c, true, fmt.Sprintf("文件创建失败:%v", err), gin.H{})
} else {
servers.ReportFormat(c, true, "文件创建成功", gin.H{"filePath": filePath})
}
}
// @Tags ExaFileUploadAndDownload
// @Summary 删除切片
// @Security ApiKeyAuth
// @accept multipart/form-data
// @Produce application/json
// @Param file params file true "查找文件"
// @Success 200 {string} string "{"success":true,"data":{},"msg":"查找成功"}"
// @Router /fileUploadAndDownload/removeChunk [post]
func RemoveChunk(c *gin.Context) {
fileMd5 := c.Query("fileMd5")
fileName := c.Query("fileName")
filePath := c.Query("filePath")
err := servers.RemoveChunk(fileMd5)
err = new(dbModel.ExaFile).DeleteFileChunk(fileMd5, fileName, filePath)
if err != nil {
servers.ReportFormat(c, true, fmt.Sprintf("缓存切片删除失败:%v", err), gin.H{})
} else {
servers.ReportFormat(c, true, "缓存切片删除成功", gin.H{})
}
}
package servers
import (
"gin-vue-admin/tools"
"io/ioutil"
"os"
"strconv"
)
// 前端传来文件片与当前片为什么文件的第几片
// 后端拿到以后比较次分片是否上传 或者是否为不完全片
// 前端发送每片多大
// 前端告知是否为最后一片且是否完成
const breakpointDir = "./breakpointDir/"
const finishDir = "./fileDir/"
func BreakPointContinue(content []byte, fileName string, contentNumber int, contentTotal int, fileMd5 string) (error, string) {
path := breakpointDir + fileMd5 + "/"
err := os.MkdirAll(path, os.ModePerm)
if err != nil {
return err, path
}
err, pathc := makeFileContent(content, fileName, path, contentNumber)
return err, pathc
}
func CheckMd5(content []byte, chunkMd5 string) (CanUpload bool) {
fileMd5 := tools.MD5V(content)
if fileMd5 == chunkMd5 {
return true // "可以继续上传"
} else {
return false // "切片不完整,废弃"
}
}
func makeFileContent(content []byte, fileName string, FileDir string, contentNumber int) (error, string) {
path := FileDir + fileName + "_" + strconv.Itoa(contentNumber)
f, err := os.Create(path)
defer f.Close()
if err != nil {
return err, path
} else {
_, err = f.Write(content)
if err != nil {
return err, path
}
}
return nil, path
}
func MakeFile(fileName string, FileMd5 string) (error, string) {
rd, err := ioutil.ReadDir(breakpointDir + FileMd5)
if err != nil {
return err, finishDir + fileName
}
_ = os.MkdirAll(finishDir, os.ModePerm)
fd, _ := os.OpenFile(finishDir+fileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
for k, _ := range rd {
content, _ := ioutil.ReadFile(breakpointDir + FileMd5 + "/" + fileName + "_" + strconv.Itoa(k))
_, err = fd.Write(content)
if err != nil {
_ = os.Remove(finishDir + fileName)
return err, finishDir + fileName
}
}
defer fd.Close()
return nil, finishDir + fileName
}
func RemoveChunk(FileMd5 string) error {
err := os.RemoveAll(breakpointDir + FileMd5)
return err
}
......@@ -14,6 +14,7 @@ func InitRouter() *gin.Engine {
var Router = gin.Default()
//Router.Use(middleware.LoadTls()) // 打开就能玩https了
Router.Use(middleware.Logger()) // 如果不需要日志 请关闭这里
Router.Use(middleware.Cors()) // 跨域
Router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
ApiGroup := Router.Group("") // 方便统一添加路由组前缀 多服务器上线使用
//Router.Use(middleware.Logger())
......@@ -26,6 +27,6 @@ func InitRouter() *gin.Engine {
router.InitWorkflowRouter(ApiGroup) // 工作流相关路由
router.InitCasbinRouter(ApiGroup) // 权限相关路由
router.InitJwtRouter(ApiGroup) // jwt相关路由
router.InitSystemRouter(ApiGroup) // system相关路由
router.InitSystemRouter(ApiGroup) // system相关路由
return Router
}
......@@ -14,8 +14,10 @@ func RegistTable(db *gorm.DB) {
sysModel.SysApi{},
sysModel.SysBaseMenu{},
sysModel.JwtBlacklist{},
dbModel.ExaFileUploadAndDownload{},
sysModel.SysWorkflow{},
sysModel.SysWorkflowStepInfo{},
dbModel.ExaFileUploadAndDownload{},
dbModel.ExaFile{},
dbModel.ExaFileChunk{},
)
}
package middleware
import (
"github.com/gin-gonic/gin"
"net/http"
)
// 处理跨域请求,支持options访问
func Cors() gin.HandlerFunc {
return func(c *gin.Context) {
method := c.Request.Method
c.Header("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token")
c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS")
c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type")
c.Header("Access-Control-Allow-Credentials", "true")
//放行所有OPTIONS方法
if method == "OPTIONS" {
c.AbortWithStatus(http.StatusNoContent)
}
// 处理请求
c.Next()
}
}
package dbModel
import (
"gin-vue-admin/init/qmsql"
"github.com/jinzhu/gorm"
)
type ExaFile struct {
gorm.Model
FileName string
FileMd5 string
FilePath string
ExaFileChunk []ExaFileChunk
ChunkTotal int
IsFinish bool
}
type ExaFileChunk struct {
gorm.Model
ExaFileId uint
FileChunkNumber int
FileChunkPath string
}
func (f *ExaFile) FileCreateComplete(FileMd5 string, FileName string, FilePath string) error {
var file ExaFile
upDateFile := make(map[string]interface{})
upDateFile["FilePath"] = FilePath
upDateFile["IsFinish"] = true
err := qmsql.DEFAULTDB.Where("file_md5 = ? AND file_name = ?", FileMd5, FileName).First(&file).Updates(upDateFile).Error
return err
}
func (f *ExaFile) FindOrCreateFile(FileMd5 string, FileName string, ChunkTotal int) (err error, file ExaFile) {
var cfile ExaFile
cfile.FileMd5 = FileMd5
cfile.FileName = FileName
cfile.ChunkTotal = ChunkTotal
notHaveSameMd5Finish := qmsql.DEFAULTDB.Where("file_md5 = ? AND is_finish = ?", FileMd5, true).First(&file).RecordNotFound()
if notHaveSameMd5Finish {
err = qmsql.DEFAULTDB.Where("file_md5 = ? AND file_name = ?", FileMd5, FileName).Preload("ExaFileChunk").FirstOrCreate(&file, cfile).Error
return err, file
} else {
cfile.IsFinish = true
cfile.FilePath = file.FilePath
err = qmsql.DEFAULTDB.Create(&cfile).Error
return err, cfile
}
}
// 创建文件切片记录
func (f *ExaFile) CreateFileChunk(FileChunkPath string, FileChunkNumber int) error {
var chunk ExaFileChunk
chunk.FileChunkPath = FileChunkPath
chunk.ExaFileId = f.ID
chunk.FileChunkNumber = FileChunkNumber
err := qmsql.DEFAULTDB.Create(&chunk).Error
return err
}
// 删除文件切片记录
func (f *ExaFile) DeleteFileChunk(fileMd5 string, fileName string, filePath string) error {
var chunks []ExaFileChunk
var file ExaFile
err := qmsql.DEFAULTDB.Where("file_md5 = ? AND file_name = ?", fileMd5, fileName).First(&file).Update("IsFinish", true).Update("file_path", filePath).Error
err = qmsql.DEFAULTDB.Where("exa_file_id = ?", file.ID).Delete(&chunks).Unscoped().Error
return err
}
......@@ -35,7 +35,7 @@ func (u *SysUser) Regist() (err error, userInter *SysUser) {
return errors.New("用户名已注册"), nil
} else {
// 否则 附加uuid 密码md5简单加密 注册
u.Password = tools.MD5V(u.Password)
u.Password = tools.MD5V([]byte(u.Password))
u.UUID = uuid.NewV4()
err = qmsql.DEFAULTDB.Create(u).Error
}
......@@ -46,8 +46,8 @@ func (u *SysUser) Regist() (err error, userInter *SysUser) {
func (u *SysUser) ChangePassword(newPassword string) (err error, userInter *SysUser) {
var user SysUser
//后期修改jwt+password模式
u.Password = tools.MD5V(u.Password)
err = qmsql.DEFAULTDB.Where("username = ? AND password = ?", u.Username, u.Password).First(&user).Update("password", tools.MD5V(newPassword)).Error
u.Password = tools.MD5V([]byte(u.Password))
err = qmsql.DEFAULTDB.Where("username = ? AND password = ?", u.Username, u.Password).First(&user).Update("password", tools.MD5V([]byte(newPassword))).Error
return err, u
}
......@@ -60,7 +60,7 @@ func (u *SysUser) SetUserAuthority(uuid uuid.UUID, AuthorityId string) (err erro
//用户登录
func (u *SysUser) Login() (err error, userInter *SysUser) {
var user SysUser
u.Password = tools.MD5V(u.Password)
u.Password = tools.MD5V([]byte(u.Password))
err = qmsql.DEFAULTDB.Where("username = ? AND password = ?", u.Username, u.Password).First(&user).Error
if err != nil {
return err, &user
......
......@@ -2,16 +2,19 @@ package router
import (
"gin-vue-admin/controller/api"
"gin-vue-admin/middleware"
"github.com/gin-gonic/gin"
)
func InitFileUploadAndDownloadRouter(Router *gin.RouterGroup) {
FileUploadAndDownloadGroup := Router.Group("fileUploadAndDownload").Use(middleware.JWTAuth()).Use(middleware.CasbinHandler())
//.Use(middleware.JWTAuth())
FileUploadAndDownloadGroup := Router.Group("fileUploadAndDownload")
//.Use(middleware.JWTAuth()).Use(middleware.CasbinHandler())
{
FileUploadAndDownloadGroup.POST("/upload", api.UploadFile) // 上传文件
FileUploadAndDownloadGroup.POST("/getFileList", api.GetFileList) // 获取上传文件列表
FileUploadAndDownloadGroup.POST("/deleteFile", api.DeleteFile) // 删除指定文件
FileUploadAndDownloadGroup.POST("/upload", api.UploadFile) // 上传文件
FileUploadAndDownloadGroup.POST("/getFileList", api.GetFileList) // 获取上传文件列表
FileUploadAndDownloadGroup.POST("/deleteFile", api.DeleteFile) // 删除指定文件
FileUploadAndDownloadGroup.POST("/breakpointContinue", api.BreakpointContinue) // 断点续传
FileUploadAndDownloadGroup.GET("/findFile", api.FindFile) // 查询当前文件成功的切片
FileUploadAndDownloadGroup.POST("/breakpointContinueFinish", api.BreakpointContinueFinish) // 查询当前文件成功的切片
FileUploadAndDownloadGroup.POST("/removeChunk", api.RemoveChunk) // 查询当前文件成功的切片
}
}
......@@ -5,8 +5,8 @@ import (
"encoding/hex"
)
func MD5V(str string) string {
func MD5V(str []byte) string {
h := md5.New()
h.Write([]byte(str))
h.Write(str)
return hex.EncodeToString(h.Sum(nil))
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册