點燈坊

失くすものさえない今が強くなるチャンスよ

如何計算檔案的 MD5 ?

Sam Xiao's Avatar 2021-04-26

上傳檔案時,API 會要求先計算出檔案的 MD5 連同檔案一起上傳,後端再根據 MD5 確認檔案是否傳輸正確,JavaScript 該如何計算出檔案的 MD5 呢 ?

Version

Vue 3.0.11
SparkMD5 3.0.1

Add JavaScript MD5

$ yarn add spark-md5

使用 Yarn 安裝 JavaScript MD5。

md5000

顯示 String 的 MD5。

<template lang='pub'>
div {{ hash }}
</template>

<script setup>
import SparkMD5 from 'spark-md5'

let spark = new SparkMD5
spark.append('Hello World')
let hash = spark.end()
</script>

第 6 行

import SparkMD5 from 'spark-md5'

spark-md5 引用 SparkMD5 class。

第 8 行

let spark = new SparkMD5
spark.append('Hello World')
let hash = spark.end()
  • 將 String 傳入 spark.append()
  • spark.end() 讀取 MD5

這是最簡單的將 String 轉成 MD5,可先確認 JavaScript MD5 環境是否設定成功

FileReader

md5001

上傳檔案計算出檔案的 MD5。

<template lang='pug'>
input(@input='onInput', ref='fileUpload', type='file', accept='images/*', multiple, hidden)
button(@click='onUpload').bg-blue-500.hover_bg-blue-700.text-white.font-bold.py-2.px-4.rounded Upload
span {{ hash }}
</template>

<script setup>
import SparkMD5 from 'spark-md5'

ref: hash = ''
ref: fileUpload = null

let onUpload = () => fileUpload.click()

let onInput = () => {
  let fileReader = new FileReader
  fileReader.onload = () => {
    let spark = new SparkMD5
    spark.appendBinary(fileReader.result)
    hash = spark.end()
  }
  fileReader.onerror = console.error

  fileReader.readAsBinaryString(fileUpload.files[0])
}
</script>

第 2 行

input(@input='onInput', ref='fileUpload', type='file', accept='images/*', multiple, hidden)
button(@click='onUpload').bg-blue-500.hover_bg-blue-700.text-white.font-bold.py-2.px-4.rounded Upload

使用 Tailwind CSS 實現 File Upload。

13 行

let onUpload = () => fileUpload.click()

當 button click 時,執行 fileUpload.click()

15 行

let onInput = () => {}

fileUpload.click() 執行時是會執行 onInput()

17 行

let fileReader = new FileReader
fileReader.onload = () => {
  let spark = new SparkMD5
  spark.appendBinary(fileReader.result)
  hash = spark.end()
}
fileReader.onerror = console.error
  • JavaScript MD5 雖然可以計算出 MD5,但首先必須使用 FileReader 將檔案轉成 Binary String 之後再透過 JavaScript MD5 計算

  • 不過 FileReader 以 event-based API 實現 asynchronous,必須在 onload event 內讀取 fileReader.result 才能得到 Binary String

24 行

fileReader.readAsBinaryString(fileUpload.files[0])

將所選擇檔案傳入 fileReader.readAsBinaryString() 即可得到 MD5。

Promise

md5001

結果不變,但使用 Promise 改寫。

<template lang='pug'>
input(@input='onInput', ref='fileUpload', type='file', accept='images/*', multiple, hidden)
button(@click='onUpload').bg-blue-500.hover_bg-blue-700.text-white.font-bold.py-2.px-4.rounded Upload
span {{ hash }}
</template>

<script setup>
import SparkMD5 from 'spark-md5'

ref: hash = ''
ref: fileUpload = null

let toMD5 = file => new Promise((resolve, reject) => {
  let fileReader = new FileReader
  fileReader.onload = () => {
    let spark = new SparkMD5
    spark.appendBinary(fileReader.result)
    resolve(spark.end())
  }
  fileReader.onerror = reject
  fileReader.readAsBinaryString(file)
})

let onUpload = () => fileUpload.click()

let onInput = () =>
  toMD5(fileUpload.files[0])
    .then(x => hash = x)
    .catch(console.error)
</script>

FileReader 雖然可用,但畢竟屬於較早期 Web API,asynchronous 仍使用 event 處理,比較好的方式是改用 Promise。

13 行

let toMD5 = file => new Promise((resolve, reject) => {
  let fileReader = new FileReader
  fileReader.onload = () => {
    let spark = new SparkMD5
    spark.appendBinary(fileReader.result)
    resolve(spark.end())
  }
  fileReader.onerror = reject
  fileReader.readAsBinaryString(file)
})

自行建立 toMD5(),使用 new Promise((resolve, reject) => {}) 建立 Promise,在 onload使用 resolve() 回傳 MD5,且在 onerror 使用 reject() 回傳 error。

最後將 file 傳入 fileReader.readAsBinaryString() 轉成 Binary String。

26 行

let onInput = () =>
  toMD5(fileUpload.files[0])
    .then(x => hash = x)
    .catch(console.error)

toMD5() 回傳 Promise 後,就可使用 Promise Chain 處理 MD5。

Point-free

md5001

結果不變,但使用 Point-free 改寫。

<template lang='pug'>
input(@input='onInput', ref='fileUpload', type='file', accept='images/*', multiple, hidden)
button(@click='onUpload').bg-blue-500.hover_bg-blue-700.text-white.font-bold.py-2.px-4.rounded Upload
span {{ hash }}
</template>

<script setup>
import { ref, unref } from 'vue'
import { read, write } from 'vue3-fp'
import { pipe, prop, andThen as then, otherwise, head } from 'ramda'
import { error } from 'wink-fp'
import SparkMD5 from 'spark-md5'

let hash = ref('')
let fileUpload = ref(null)

let toMD5 = file => new Promise((resolve, reject) => {
  let fileReader = new FileReader
  fileReader.onload = () => {
    let spark = new SparkMD5
    spark.appendBinary(fileReader.result)
    resolve(spark.end())
  }
  fileReader.onerror = reject
  fileReader.readAsBinaryString(file)
})

let onUpload = () => unref(fileUpload).click()

let onInput = pipe(
  read(fileUpload),
  prop('files'),
  head,
  toMD5,
  then(write(hash)),
  otherwise(error)
)
</script>

30 行

let onInput = pipe(
  read(fileUpload),
  prop('files'),
  head,
  toMD5,
  then(write(hash)),
  otherwise(error)
)

使用 pipe() 組合出 onInput()

  • read(fileUpload):讀取 fileUpload ref
  • prop('files'):讀取 files prop
  • headfiles 為 Array,取其第一筆
  • toMD5:將 file 轉成 MD5
  • then(write(hash))toMD5() 回傳為 Promise,使用 then() 拆解後寫入 hash state
  • otherwise(error):處理 Rejected Promise

Conclusion

  • 實務上較少使用 new Promise((resolve, reject) => {}) 建立 Promise,但遇到 FileReader這類 event-based API,只能使用這種方式回傳 Promise
  • JavaScript 預設無法計算出 MD5,必須另外安裝 Package;若要計算檔案的 MD5,先要透過內建 FilerReader 將檔案轉成 Binary String 後,再計算其 MD5

Reference

Andre Cruz, SparkMD5