package main import ( "io/ioutil" "net" "net/http" _ "net/http/pprof" "os" "path/filepath" "sort" "strconv" "strings" "webuploader/static" "github.com/gin-gonic/gin" "github.com/quillaja/logrus-systemd-formatter/systemdfmt" log "github.com/sirupsen/logrus" "github.com/urfave/cli/v2" ) var ( dir string = getCurrentAbPathByExecutable() + string(filepath.Separator) port string group1 *gin.RouterGroup ) func init() { gin.SetMode(gin.ReleaseMode) log.SetFormatter(&systemdfmt.Formatter{}) log.SetOutput(os.Stdout) //log.SetLevel(log.WarnLevel) //pprof go func() { _ = http.ListenAndServe(":6066", nil) }() } // 获取当前执行文件绝对路径 func getCurrentAbPathByExecutable() string { exePath, err := os.Executable() if err != nil { log.Fatal(err) } res, _ := filepath.EvalSymlinks(filepath.Dir(exePath)) return res } func getClientIp() { addrs, err := net.InterfaceAddrs() if err != nil { log.Errorf("Error getClientIp %v", err) return } for _, address := range addrs { if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { if ipnet.IP.To4() != nil { //return ipnet.IP.String(), nil log.Infof("HTTP://" + ipnet.IP.String() + ":" + port) } } } } func walkDir(filestr string) ([]string, error) { files, err := ioutil.ReadDir(filestr) // files为当前目录下的所有文件名称【包括文件夹】 if err != nil { return nil, err } var allfile []string for _, v := range files { if v.Name() == ".DS_Store" { continue } fullPath := filestr + string(filepath.Separator) + v.Name() // 全路径 + 文件名称 if v.IsDir() { // 如果是目录 a, _ := walkDir(fullPath) // 遍历改路径下的所有文件 allfile = append(allfile, a...) } else { allfile = append(allfile, fullPath) // 如果不是文件夹,就直接追加到路径下 } } return allfile, nil } // 验证是否上传过该文件 func check(c *gin.Context) { fileMd5 := c.PostForm("md5File") path := dir + "wisemodel" + string(filepath.Separator) + "merge" + string(filepath.Separator) + fileMd5 if _, err := os.Stat(path); os.IsNotExist(err) { //判断是否已经上传过该文件,设置返回code 为 -1 :秒传 log.Println("mkDir " + path) _ = os.MkdirAll(path, os.ModePerm) c.JSON(http.StatusOK, gin.H{ "resultCode": 0, // }) return } result := 0 // 获取目录里上传文件数量 files, err := ioutil.ReadDir(path) if err != nil { log.Infof("Error ioutil.ReadDir %v", err) } else if len(files) == 0 { //文件没有上传过,下标为零 result = 0 } else { result = 1 } c.JSON(http.StatusOK, gin.H{ "resultCode": result, }) } // 验证是否上传过该文件 func checkChunk(c *gin.Context) { fileMd5 := c.PostForm("md5File") chunk := c.PostForm("chunk") log.Println(fileMd5) path := dir + "wisemodel" + string(filepath.Separator) + fileMd5 + string(filepath.Separator) + strings.Trim(chunk, "") log.Println(path) if _, err := os.Stat(path); err == nil { //判断是否已经上传过该文件,设置返回code 为 -1 :秒传 c.JSON(http.StatusOK, gin.H{ "resultCode": 1, // }) return } else { c.JSON(http.StatusOK, gin.H{ "resultCode": 0, }) } } // 上传文件分片 func upload(c *gin.Context) { fileName := c.PostForm("name") // 获取文件名 chunkIndex := strings.Trim(c.PostForm("chunk"), "") // 获取分片下标 fileMd5 := c.PostForm("md5File") //获取文件md5 dst := dir + "wisemodel" + string(filepath.Separator) + fileMd5 if chunkIndex == "" { chunkIndex = "0" } result := 0 defer func(subResult *int) { c.JSON(http.StatusOK, gin.H{ "resultCode": *subResult, // }) }(&result) if _, err := os.Stat(dst); os.IsNotExist(err) { //判断是否存在 _ = os.MkdirAll(dst, os.ModePerm) } if file, err := c.FormFile("file"); err == nil { //获取分片文件 if chunkIndex == "" { err = c.SaveUploadedFile(file, dst+string(filepath.Separator)+fileName) } else { err = c.SaveUploadedFile(file, dst+string(filepath.Separator)+chunkIndex) } if err != nil { result = 0 log.Errorf("Error c.SaveUploadedFile %v", err) } else { result = 1 } } else { result = 0 log.Errorf("Error c.FormFile %v", err) } } // 合并分片 func merge(c *gin.Context) { fileName := c.PostForm("name") fileMd5 := c.PostForm("md5File") chunks := c.PostForm("chunks") path := dir + "wisemodel" + string(filepath.Separator) + fileMd5 //分片存储位置 result := 0 defer func(subResult *int) { c.JSON(http.StatusOK, gin.H{ "resultCode": *subResult, // }) }(&result) // 获取目录里上传文件分片数量 files, err := ioutil.ReadDir(path) if err != nil || len(files) == 0 || chunks != strconv.Itoa(len(files)) { log.Errorf("Error merge ioutil.ReadDir %v", err) result = 0 return } //按名字排序 sort.SliceStable(files, func(i, j int) bool { numA, _ := strconv.Atoi(files[i].Name()) numB, _ := strconv.Atoi(files[j].Name()) return numA < numB //return files[i].Name() < files[j].Name() }) realFilePath := dir + "wisemodel" + string(filepath.Separator) + "merge" + string(filepath.Separator) + fileMd5 _ = os.MkdirAll(realFilePath, os.ModePerm) //创建目录 // 创建一个需要合并的文件 realFile, err := os.OpenFile(realFilePath+string(filepath.Separator)+fileName, os.O_CREATE|os.O_TRUNC|os.O_RDWR, os.ModePerm) defer realFile.Close() if err != nil { result = 0 log.Errorf("Error merge os.OpenFile %v", err) return } for _, v := range files { // f, err := os.OpenFile(path+string(filepath.Separator)+v.Name(), os.O_RDONLY, os.ModePerm) if err != nil { result = 0 log.Errorf("Error for os.OpenFile %v", err) break } b, err := ioutil.ReadAll(f) if err != nil { result = 0 log.Errorf("Error for ioutil.ReadAll %v", err) break } realFile.Write(b) // 关闭分片 f.Close() os.Remove(f.Name()) //合并后,删除分片 result = 1 } } // 文件list func getFiles(c *gin.Context) { pathMerge := dir + "wisemodel" + string(filepath.Separator) + "merge" //上传文件存储位置 pathLocal := dir + "wisemodel" + string(filepath.Separator) + "local" //本地文件存储位置 if _, err := os.Stat(pathLocal); os.IsNotExist(err) { _ = os.MkdirAll(pathLocal, os.ModePerm) } mergeFiles, err := walkDir(pathMerge) if err != nil { log.Errorf("Error walkDir(pathMerge) %v", err) } localFiles, err := walkDir(pathLocal) if err != nil { log.Errorf("Error walkDir(pathMerge) %v", err) } c.JSON(http.StatusOK, gin.H{ "mergeFiles": mergeFiles, "localFiles": localFiles, }) } func doWork() { getClientIp() r := gin.Default() //加载静态文件 r.StaticFS("/static", http.FS(static.FS)) // 最大文件大小M r.MaxMultipartMemory = 8 << 20 // 跳转上传页面 r.GET("/", func(c *gin.Context) { c.Redirect(http.StatusMovedPermanently, "/static/upload.html") }) //路由分组 group1 = r.Group("/bigfile") group1.POST("/check", check) group1.POST("/checkChunk", checkChunk) group1.POST("/upload", upload) group1.POST("/merge", merge) group1.GET("/getFiles", getFiles) group1.GET("/down", func(c *gin.Context) { pathName := c.DefaultQuery("path", "") fileName := filepath.Base(pathName) if pathName != "" { c.Header("Content-Type", "application/octet-stream") c.Header("Content-Disposition", "attachment; filename="+fileName) c.Header("Content-Disposition", "inline;filename="+fileName) c.Header("Content-Transfer-Encoding", "binary") c.Header("Cache-Control", "no-cache") c.File(pathName) } }) _ = r.Run(":" + port) } func main() { app := &cli.App{ Name: "webuploader", //app名字 Usage: "文件服务", //详细描述该app的用途 // 设置参数列表 Flags: []cli.Flag{ &cli.StringFlag{ Name: "port", Value: "8888", Aliases: []string{"p"}, Usage: "port", Destination: &port, }, }, Action: func(context *cli.Context) error { doWork() return nil }, } if err := app.Run(os.Args); err != nil { log.Errorln("app run error: ", err.Error()) os.Exit(1) } }