微信小程序之文件上传

文件上传

文件上传是一个常用的功能。同时也是非常难以掌握的一个技术部分。这篇文章从前后端两个部分来分析要完成一个文件上传的功能需要编写的代码。倾向于前端,后端主要解释如何获取文件流并将其写入文件中。

首先拟定需求:
1、需要可以选取1~n张图片并展示
2、需要可以对选取的图片进行预览
3、在点击上传按钮后可以将选取的图片进行上传
4、实时监控上传进度
5、图形化表示上传进度

前端

这篇文章前端是由微信小程序完成。所以下面的技术分析也是针对于微信小程序中而定。

下面开始功能实现:

1、选取1~n张图片并展示,小程序规定一次最多不能超过9张

微信小程序提供wx.chooseImage接口来实现图片选择。具体内容可参考微信小程序文档
关键点在于在data中定义一个数组用来存放选择的图片信息,并在success方法中对其进行赋值就好。然后使用wx:for来对imgList进行循环的列表渲染即可完成这n张图片的展示。

2、图片预览功能

微信小程序提供wx.previewImage接口可以对图片进行预览,具体功能参考文档(上面给出)。
这个方法主要的两个参数:

current: '', // 当前显示图片的http链接
urls: [] // 需要预览的图片http链接列表

在开发中,可以把预览图片的方法绑定在每个图片上,每个图片上需要绑定一个data-index来区分。然后在点击图片时触发预览图片的方法,在方法体内获取到该图片的index。那么 就是

current: imgList[index]
urls: imgList
3、图片上传功能

微信小程序提供了wx.uploadFile接口来实现上传文件的功能。不过需要注意的是这个方法调用一次只能上传一张图片(一个文件),那么当我们要上传多张图片的时候,就需要循环调用该方法。
来看一下这个接口的参数:


微信小程序之文件上传_第1张图片
upload.png

我之前一直不太理解上传文件的过程。从这个接口上看,我们只需要传递一个文件的路径,然后把后端的上传图片的接口填到url上就好了。给我一种向后端传递了一个path的感觉。

其实,小程序会将我们要上传的文件二进制化然后传递给后端,后端通过name属性来获取到对应的二进制文件内容,再将其通过IO流写入到服务器中。

4、实时监控上传进度

小程序提供的wx.uploadFile接口会将文件上传的进度返回。返回的是一个uploadTask对象。这个上传进度指的是小程序将图片二进制化并传入到后端接口的进度,这个进度无关后端是否接收成功,是否成功写入服务器。

具体用法为:

const uploadTask = wx.uploadFile({
     // .....
})
uploadTask.onProgressUpdate((res) => {
  console.log('上传进度', res.progress)
  console.log('已经上传的数据长度', res.totalBytesSent)
  console.log('预期需要上传的数据总长度', res.totalBytesExpectedToSend)
})
5、图形化表示上传

后端

我是搞前端的。可是我也是非常想搞明白文件上传的整个过程。具体后端是怎么接收到上传文件的二进制流
并将其保存到服务器。还好java的知识还没忘光

首先将需要用到的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;

出现报错了去pom中写依赖

定义文件上传的路径(这个并非是文件存储的位置,而是DiskFileItemFactory)
String path ="D:/upload/test";
// 用上面传入的这个路径创建一个文件并检查文件是否存在,不存在就创建一个
File dir = new File(path);
if (!dir.exists()) {
    dir.mkdir();
}
DiskFileItemFactory类

DiskFileItemFactory类是文件上传的核心API。一个文件上传可以分为以下几个步骤:

1、创建DiskFileItemFactory对象,设置缓冲区大小和临时文件目录

DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setRepository(dir);   // 设置临时文件目录。也就是上面的那个目录,不太理解的是这个目录为什么是临时文件目录?因为这个目录就是让DiskFileItemFactory 对象来操作数据流的,这个过程还没有进行文件写入
factory.setSizeThreshold(1024 * 1024); // 设置缓冲区大小

2、使用DiskFileItemFactory 对象创建ServletFileUpload对象,并设置上传文件的大小限制。

ServletFileUpload upload = new ServletFileUpload(factory); // 创建ServletFileUpload对象,暂时不做大小限制

3、调用ServletFileUpload.parseRequest方法解析request对象,得到一个保存了所有上传内容的List对象。

List list = upload.parseRequest(request);

4、对list进行迭代,每迭代一个FileItem对象,调用其isFormField方法判断是否是上传文件(false是上传文件的类型,true是普通表单类型)

FileItem picture = null; // 定义图片对象
for (FileItem item : list) {
    //获取表单的属性名字
     String name = item.getFieldName(); // 这个获取属性名对应的就是wx.uploadFile接口中的name和formData中的内容
     System.out.print(name); // 每一个前端定义的属性都是一个单独的对象(item)存在。
     if (item.isFormField()) {
      //获取用户具体输入的字符串
      String value = item.getString();
      request.setAttribute(name, value); // 这一步是为了后面给图片命名方便
     logger.debug("name=" + name + ",value=" + value);
    } else {
       picture = item; // 如果这一项不是表单输入数据,那么就是上传的图片啦。赋值给picture就好了。
      }
 }

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

// 通过传入的formdata中的id属性来作为唯一标示给图片命名,这样其实很不好。最好可以根据时间戳什么的生成一个完全唯一的名字
String fileName = request.getAttribute("id") + ".jpg";
 
// 这个是真正存放图片的地址。这里我在操作DiskFileItemFactory 的地址上进行改动
String destPath = path +"/"+ fileName;          
            
File file = new File(destPath);  // 创建一个文件
OutputStream out = new FileOutputStream(file);// 定义输出流
InputStream in = picture.getInputStream(); // 根据图片对象定义输入流
            
int length = 0;           
byte[] buf = new byte[1024]; // 每次读到的数据存放在buf 数组中
while ((length = in.read(buf)) != -1) {
      //在buf数组中取出数据写到(输出流)磁盘上
      out.write(buf, 0, length); 
}
in.close();
out.close();  // 关闭输入输出流

ok,现在讲上面的代码片段整理后,暴露出接口给前端调用,你就会发现,通过小程序以及可以上传图片了。

完整代码戳这里

残留问题:
我在手机上测试的时候,上传时生成的.tem文件并未自动删除。怎么回事

总结

这个功能我从头到尾自己搞了一遍。才明白其中是怎么回事,相信下次再有类似的功能也不会犯迷了。有些东西一知半解还不如不会,不如静下心来好好研习一下。

你可能感兴趣的:(微信小程序之文件上传)