拖拽上傳圖片 DropzoneJS+Iris

pardon110發表於2019-10-28

Iris官方其實很厚道,有很多有用的例子,而社群則是文件多於實戰。源於官方示例庫([英文源文])(https://github.com/kataras/iris/tree/master/_examples/tutorial/dropzonejs),本文采用 DropzoneJS and Go實現圖片上傳,後端裁剪,前端呈現,程式碼量少但勝在實用,適合新同學實戰。

涉及的點

  • DropzoneJS 拖拽上傳圖片前端庫,可顯示進度
  • Iris 類(理念相近)PHP 中的 Laravel 框架,但比之輕
  • 相關同步,圖片,檔案,路徑等標準庫
  • 第三方庫 nfnt/resize,用於縮圖等比例裁剪

效果圖

拖拽上傳圖片 DropzoneJS+Iris

前端

引入的DropzoneJS庫本身不依賴於jQeury,表單亦無需 enctype,本文只是用jquery向後端發資料

DOM佈局

表單 file 型別節點, 要攜帶 multiple屬性,以示多檔案上傳

    <form action="/upload" method="post" class="dropzone" id="my-dropzone">
        <div class="fallback">
            <input type="file" name="file" multiple/>
            <input type="submit" value="Upload">
        </div>
    </form>

DropzoneJS

addedfile 事件,使用回撥資料,其新增到對應的檔案物件上去; complete 事件,顯示進度條

    Dropzone.options.myDropzone ={
        paramName:"file",
        init:function(){
            thisDropzone = this;
            $.get('/uploads',function(data){
                if(data==null){
                    return
                }
                $.each(data,function(key,value){
                    var mockFile = {name:value.name,size:value.size}
                    thisDropzone.emit("addedfile", mockFile)
                    thisDropzone.options.thumbnail.call(thisDropzone, mockFile, '/public/uploads/thumbnail_'+value.name)
                    // 進度條
                    thisDropzone.emit("complete", mockFile)
                })
            })
        }
    }

後端

專案目錄結構如下

│  main.go
├─public
│  ├─css
│  │      dropzone.css
│  ├─js
│  │      dropzone.js
│  └─uploads
│          kk048.jpg
│          ll031.jpg
│          thumbnail_kk048.jpg
│          thumbnail_ll031.jpg
│          thumbnail_web_spring.png
│          web_spring.png
└─views
        upload.html

資料

檔案標籤結構體方便前端接收json格式的檔名及大小的資訊

const uploadsDir = "./public/uploads/"
type uploadedFile struct {
    // {name: "", size: } 適應dropzone所需
    Name string `json:"name"`
    Size int64  `json:"size"`
}
type uploadedFiles struct {
    dir   string  // 檔案儲存目錄
    items []uploadedFile // 多檔案資料記錄進切片
    mu    sync.RWMutex // 安全型別的切片鎖
}

操作集

*uploadedFiles型別的建立及其方法

// 獲取多檔案例項
func scanUploads(dir string) *uploadedFiles {
    f := new(uploadedFiles)
    index := dir[len(dir)-1]
    if index != os.PathSeparator && index != '/' {
        dir += string(os.PathSeparator)
    }
    // 遞迴建立資料夾
    if err := os.MkdirAll(dir, os.FileMode(0666)); err != nil {
        return f
    }
    f.scan(dir)
    return f
}

func (f *uploadedFiles) scan(dir string) {
    f.dir = dir
    // 根目錄遞迴分析,得到圖片檔案資訊
    filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
     // 排除目錄及縮圖
        if info.IsDir() || strings.HasPrefix(info.Name(), "thumbnail_") {
            return nil
        }
        // 新增非縮圖檔案
        f.add(info.Name(), info.Size())
        return nil
    })
}
// 新增檔案資訊
func (f *uploadedFiles) add(name string, size int64) uploadedFile {
    uf := uploadedFile{
        Name: name,
        Size: size,
    }
    f.mu.Lock()
    f.items = append(f.items, uf)
    f.mu.Unlock()
    return uf
}
// 建立縮圖
func (f *uploadedFiles) createThumbnail(uf uploadedFile) {
    file, err := os.Open(path.Join(f.dir, uf.Name))
    if err != nil {
        return
    }
    defer file.Close()
    name := strings.ToLower(uf.Name)
    out, err := os.OpenFile(f.dir+"thumbnail_"+uf.Name, os.O_WRONLY|os.O_CREATE, 0666)
    if err != nil {
        return
    }
    defer out.Close()

    // 裁剪縮圖
    if strings.HasSuffix(name, "jpg") {
        img, err := jpeg.Decode(file)
        if err != nil {
            return
        }
        resized := resize.Thumbnail(180, 180, img, resize.Lanczos3)
        // 將影象寫入輸出流
        jpeg.Encode(out, resized, &jpeg.Options{Quality: jpeg.DefaultQuality})
    } else if strings.HasSuffix(name, "png") {
        img, err := png.Decode(file)
        if err != nil {
            return
        }
        // 等比例縮放
        resized := resize.Thumbnail(180, 180, img, resize.Lanczos3)
        png.Encode(out, resized)
    }
}

Iris應用

好的程式碼會說話,上碼

func main() {
    app := iris.New()
    // 註冊檢視引擎
    app.RegisterView(iris.HTML("./views", ".html"))
    // 釋出靜態資源服務
    app.HandleDir("/public", "./public")
    // 註冊首頁路由
    app.Get("/", func(ctx iris.Context) {
        ctx.View("upload.html")
    })

    // 掃描上傳後檔案資訊(縮圖資訊)
    files := scanUploads(uploadsDir)
    // 多圖片資訊api查詢介面,返回json
    app.Get("/uploads", func(ctx iris.Context) {
        ctx.JSON(files.items)
    })

    // 註冊Post上傳
    app.Post("/upload", iris.LimitRequestBodySize(10<<20), func(ctx iris.Context) {
        // 多檔案上傳 ctx是一個集請求與響應的組合體
        file, info, err := ctx.FormFile("file")
        if err != nil {
            ctx.StatusCode(iris.StatusInternalServerError)
            ctx.Application().Logger().Warnf("Error while uploading: %v", err.Error())
            return
        }
        defer file.Close()
        fname := info.Filename

        out, err := os.OpenFile(uploadsDir+fname, os.O_WRONLY|os.O_CREATE, 0666)
        if err != nil {
            ctx.StatusCode(iris.StatusInternalServerError)
            ctx.Application().Logger().Warnf("Error while preparing the new file: %v", err.Error())
            return
        }
        defer out.Close()

        // 將檔案上傳流複製到伺服器本地
        io.Copy(out, file)

        // 可選 順序新增檔案列表
        uploadedFile := files.add(fname, info.Size)
        // goroutine建立縮圖
        go files.createThumbnail(uploadedFile)
    })
    app.Run(iris.Addr("localhost:8080"))
}

參考

原始碼
Iris新手指北

相關文章