微信小程序之文件上傳

文件上傳

文件上傳是一個(gè)常用的功能。同時(shí)也是非常難以掌握的一個(gè)技術(shù)部分。這篇文章從前后端兩個(gè)部分來(lái)分析要完成一個(gè)文件上傳的功能需要編寫的代碼。傾向于前端,后端主要解釋如何獲取文件流并將其寫入文件中。

首先擬定需求:
1、需要可以選取1~n張圖片并展示
2、需要可以對(duì)選取的圖片進(jìn)行預(yù)覽
3、在點(diǎn)擊上傳按鈕后可以將選取的圖片進(jìn)行上傳
4、實(shí)時(shí)監(jiān)控上傳進(jìn)度
5、圖形化表示上傳進(jìn)度

前端

這篇文章前端是由微信小程序完成。所以下面的技術(shù)分析也是針對(duì)于微信小程序中而定。

下面開始功能實(shí)現(xiàn):

1、選取1~n張圖片并展示,小程序規(guī)定一次最多不能超過(guò)9張

微信小程序提供wx.chooseImage接口來(lái)實(shí)現(xiàn)圖片選擇。具體內(nèi)容可參考微信小程序文檔
關(guān)鍵點(diǎn)在于在data中定義一個(gè)數(shù)組用來(lái)存放選擇的圖片信息,并在success方法中對(duì)其進(jìn)行賦值就好。然后使用wx:for來(lái)對(duì)imgList進(jìn)行循環(huán)的列表渲染即可完成這n張圖片的展示。

2、圖片預(yù)覽功能

微信小程序提供wx.previewImage接口可以對(duì)圖片進(jìn)行預(yù)覽,具體功能參考文檔(上面給出)。
這個(gè)方法主要的兩個(gè)參數(shù):

current: '', // 當(dāng)前顯示圖片的http鏈接
urls: [] // 需要預(yù)覽的圖片http鏈接列表

在開發(fā)中,可以把預(yù)覽圖片的方法綁定在每個(gè)圖片上,每個(gè)圖片上需要綁定一個(gè)data-index來(lái)區(qū)分。然后在點(diǎn)擊圖片時(shí)觸發(fā)預(yù)覽圖片的方法,在方法體內(nèi)獲取到該圖片的index。那么 就是

current: imgList[index]
urls: imgList
3、圖片上傳功能

微信小程序提供了wx.uploadFile接口來(lái)實(shí)現(xiàn)上傳文件的功能。不過(guò)需要注意的是這個(gè)方法調(diào)用一次只能上傳一張圖片(一個(gè)文件),那么當(dāng)我們要上傳多張圖片的時(shí)候,就需要循環(huán)調(diào)用該方法。
來(lái)看一下這個(gè)接口的參數(shù):


upload.png

我之前一直不太理解上傳文件的過(guò)程。從這個(gè)接口上看,我們只需要傳遞一個(gè)文件的路徑,然后把后端的上傳圖片的接口填到url上就好了。給我一種向后端傳遞了一個(gè)path的感覺(jué)。

其實(shí),小程序會(huì)將我們要上傳的文件二進(jìn)制化然后傳遞給后端,后端通過(guò)name屬性來(lái)獲取到對(duì)應(yīng)的二進(jìn)制文件內(nèi)容,再將其通過(guò)IO流寫入到服務(wù)器中。

4、實(shí)時(shí)監(jiān)控上傳進(jìn)度

小程序提供的wx.uploadFile接口會(huì)將文件上傳的進(jìn)度返回。返回的是一個(gè)uploadTask對(duì)象。這個(gè)上傳進(jìn)度指的是小程序?qū)D片二進(jìn)制化并傳入到后端接口的進(jìn)度,這個(gè)進(jìn)度無(wú)關(guān)后端是否接收成功,是否成功寫入服務(wù)器。

具體用法為:

const uploadTask = wx.uploadFile({
     // .....
})
uploadTask.onProgressUpdate((res) => {
  console.log('上傳進(jìn)度', res.progress)
  console.log('已經(jīng)上傳的數(shù)據(jù)長(zhǎng)度', res.totalBytesSent)
  console.log('預(yù)期需要上傳的數(shù)據(jù)總長(zhǎng)度', res.totalBytesExpectedToSend)
})
5、圖形化表示上傳

后端

我是搞前端的??墒俏乙彩欠浅O敫忝靼孜募蟼鞯恼麄€(gè)過(guò)程。具體后端是怎么接收到上傳文件的二進(jìn)制流
并將其保存到服務(wù)器。還好java的知識(shí)還沒(méi)忘光

首先將需要用到的jar包引入:

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import com.alibaba.fastjson.JSON;

出現(xiàn)報(bào)錯(cuò)了去pom中寫依賴

定義文件上傳的路徑(這個(gè)并非是文件存儲(chǔ)的位置,而是DiskFileItemFactory)
String path ="D:/upload/test";
// 用上面?zhèn)魅氲倪@個(gè)路徑創(chuàng)建一個(gè)文件并檢查文件是否存在,不存在就創(chuàng)建一個(gè)
File dir = new File(path);
if (!dir.exists()) {
    dir.mkdir();
}
DiskFileItemFactory類

DiskFileItemFactory類是文件上傳的核心API。一個(gè)文件上傳可以分為以下幾個(gè)步驟:

1、創(chuàng)建DiskFileItemFactory對(duì)象,設(shè)置緩沖區(qū)大小和臨時(shí)文件目錄

DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setRepository(dir);   // 設(shè)置臨時(shí)文件目錄。也就是上面的那個(gè)目錄,不太理解的是這個(gè)目錄為什么是臨時(shí)文件目錄?因?yàn)檫@個(gè)目錄就是讓DiskFileItemFactory 對(duì)象來(lái)操作數(shù)據(jù)流的,這個(gè)過(guò)程還沒(méi)有進(jìn)行文件寫入
factory.setSizeThreshold(1024 * 1024); // 設(shè)置緩沖區(qū)大小

2、使用DiskFileItemFactory 對(duì)象創(chuàng)建ServletFileUpload對(duì)象,并設(shè)置上傳文件的大小限制。

ServletFileUpload upload = new ServletFileUpload(factory); // 創(chuàng)建ServletFileUpload對(duì)象,暫時(shí)不做大小限制

3、調(diào)用ServletFileUpload.parseRequest方法解析request對(duì)象,得到一個(gè)保存了所有上傳內(nèi)容的List對(duì)象。

List<FileItem> list = upload.parseRequest(request);

4、對(duì)list進(jìn)行迭代,每迭代一個(gè)FileItem對(duì)象,調(diào)用其isFormField方法判斷是否是上傳文件(false是上傳文件的類型,true是普通表單類型)

FileItem picture = null; // 定義圖片對(duì)象
for (FileItem item : list) {
    //獲取表單的屬性名字
     String name = item.getFieldName(); // 這個(gè)獲取屬性名對(duì)應(yīng)的就是wx.uploadFile接口中的name和formData中的內(nèi)容
     System.out.print(name); // 每一個(gè)前端定義的屬性都是一個(gè)單獨(dú)的對(duì)象(item)存在。
     if (item.isFormField()) {
      //獲取用戶具體輸入的字符串
      String value = item.getString();
      request.setAttribute(name, value); // 這一步是為了后面給圖片命名方便
     logger.debug("name=" + name + ",value=" + value);
    } else {
       picture = item; // 如果這一項(xiàng)不是表單輸入數(shù)據(jù),那么就是上傳的圖片啦。賦值給picture就好了。
      }
 }

5、使用IO流,給文件命名,指定存放文件地址

// 通過(guò)傳入的formdata中的id屬性來(lái)作為唯一標(biāo)示給圖片命名,這樣其實(shí)很不好。最好可以根據(jù)時(shí)間戳什么的生成一個(gè)完全唯一的名字
String fileName = request.getAttribute("id") + ".jpg";
 
// 這個(gè)是真正存放圖片的地址。這里我在操作DiskFileItemFactory 的地址上進(jìn)行改動(dòng)
String destPath = path +"/"+ fileName;          
            
File file = new File(destPath);  // 創(chuàng)建一個(gè)文件
OutputStream out = new FileOutputStream(file);// 定義輸出流
InputStream in = picture.getInputStream(); // 根據(jù)圖片對(duì)象定義輸入流
            
int length = 0;           
byte[] buf = new byte[1024]; // 每次讀到的數(shù)據(jù)存放在buf 數(shù)組中
while ((length = in.read(buf)) != -1) {
      //在buf數(shù)組中取出數(shù)據(jù)寫到(輸出流)磁盤上
      out.write(buf, 0, length); 
}
in.close();
out.close();  // 關(guān)閉輸入輸出流

ok,現(xiàn)在講上面的代碼片段整理后,暴露出接口給前端調(diào)用,你就會(huì)發(fā)現(xiàn),通過(guò)小程序以及可以上傳圖片了。

完整代碼戳這里

殘留問(wèn)題:
我在手機(jī)上測(cè)試的時(shí)候,上傳時(shí)生成的.tem文件并未自動(dòng)刪除。怎么回事

總結(jié)

這個(gè)功能我從頭到尾自己搞了一遍。才明白其中是怎么回事,相信下次再有類似的功能也不會(huì)犯迷了。有些東西一知半解還不如不會(huì),不如靜下心來(lái)好好研習(xí)一下。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容