java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的 Java 程序设计语言和 Java 平台(即 JavaEE(j2ee), JavaME(j2me), JavaSE(j2se))的总称。
在 Web 应用系统开发中,文件上传功能是非常常用的功能,今天来主要讲讲 JavaWeb 中的文件上传功能的相关技术实现,本文给大家介绍的非常详细,具有参考借鉴价值,感兴趣的朋友一起看看吧
一、课程概述
在 Web 应用系统开发中,文件上传功能是非常常用的功能,今天来主要讲讲 JavaWeb 中的文件上传功能的相关技术实现,并且随着互联网技术的飞速发展,用户对网站的体验要求越来越高,在文件上传功能的技术上也出现许多创新点,例如异步上传文件,拖拽式上传,黏贴上传,上传进度监控,文件缩略图,大文件断点续传,大文件秒传等等。
本课程需要的基础知识:
了解基本的 Http 协议内容
基本 IO 流操作技术
Servlet 基础知识
javascript/jQuery 技术基础知识
二、文件上传的基础
对于文件上传,浏览器在上传的过程中是将文件以流的形式提交到服务器端的,并且所有流数据都会随着 Http 请求携带到服务器端。所以,文件上传时的请求内容格式要能够基本看懂。
文件上传页面:
- <form action="/itheimaUpload/UploadServlet" method="post" enctype="multipart/form-data">
- 请选择上传的文件:<input type="file" name="attach"/><br/>
- <input type="submit" value="提交"/>
- </form>
Http 请求内容:
三、Java 后台使用 Servlet 接收文件
如果使用 Servlet 获取上传文件的输入流然后再解析里面的请求参数是比较麻烦,所以一般后台选择采用 Apache 的开源工具 common-fileupload 这个文件上传组件。
- //Java后台代码:Commons-fileUpload组件上传文件
- public class UploadServlet extends HttpServlet {
- public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,
- IOException {
- //1.配置缓存
- DiskFileItemFactory factory = new DiskFileItemFactory(1 * 1024 * 1024, new File("c:/tempFiles/"));
- //2.创建ServleFileUpload对象
- ServletFileUpload sfu = new ServletFileUpload(factory);
- //解决文件名称中文问题
- sfu.setHeaderEncoding("utf-8");
- //3.解析
- try {
- List < FileItem > list = sfu.parseRequest(request);
- //解析所有内容
- if (list != null) {
- for (FileItem item: list) {
- //判断是否为普通表单参数
- if (item.isFormField()) {
- //普通表单参数
- //获取表单的name属性名称
- String fieldName = item.getFieldName();
- //获取表单参数值
- String value = item.getString("utf-8");
- } else {
- //文件
- if (item.getName() != null && !item.getName().equals("")) {
- //保存到服务器硬盘
- FileUtils.copyInputStreamToFile(item.getInputStream(), new File("c:/targetFiles/" + item.getName()));
- item.delete();
- }
- }
- }
- }
- } catch(FileUploadException e) {
- e.printStackTrace();
- }
- }
- public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException,
- IOException {
- doGet(request, response);
- }
- }
四、使用 WebUploader 上传组件
文件上传页面的前端我们可以选择使用一些比较好用的上传组件,例如百度的开源组件 WebUploader,这个组件基本能满足文件上传的一些日常所需功能,如异步上传文件,拖拽式上传,黏贴上传,上传进度监控,文件缩略图,甚至是大文件断点续传,大文件秒传。
下载 WebUpload 组件
http://fex.baidu.com/webuploader/ 到 WebUpload 官网下载 WebUpload 包
WebUpload 目录结构:
基本文件上传 Demo(包含上传进度)
前端
1.1 在页面导入所需 css,js
- <link rel="stylesheet" type="text/css"
- href="${pageContext.request.contextPath}/css/webuploader.css">
- <script type="text/javascript"
- src="${pageContext.request.contextPath }/js/jquery-1.10.2.min.js"></script>
- <script type="text/javascript"
- src="${pageContext.request.contextPath }/js/webuploader.js"></script>
1.2 编写上传页面标签
- <!-- 上传div -->
- <div id="uploader">
- <!-- 显示文件列表信息 -->
- <ul id="fileList"></ul>
- <!-- 选择文件区域 -->
- <div id="filePicker">点击选择文件</div>
- </div>
1.3 编写 webupload 代码
- <script type="text/javascript">
- //1.初始化WebUpload,以及配置全局的参数
- var uploader = WebUploader.create({
- //flashk控件的地址
- swf: "${pageContext.request.contextPath}/js/Uploader.swf",
- //后台提交地址
- server: "${pageContext.request.contextPath}/UploadServlet",
- //选择文件控件的标签
- pick: "#filePicker",
- //自动上传文件
- auto: true,
- });
- //2.选择文件后,文件信息队列展示
- // 注册fileQueued事件:当文件加入队列后触发
- // file: 代表当前选择的文件
- uploader.on("fileQueued",
- function(file) {
- //追加文件信息div
- $("#fileList").append("<div id='" + file.id + "' class='fileInfo'><span>" + file.name + "</span><div class='state'>等待上传...</div><span class='text'></span></div>");
- });
- //3.注册上传进度监听
- //file: 正在上传的文件
- //percentage: 当前进度的比例。最大为1.例如:0.2
- uploader.on("uploadProgress",
- function(file, percentage) {
- var id = $("#" + file.id);
- //更新状态信息
- id.find("div.state").text("上传中...");
- //更新上传百分比
- id.find("span.text").text(Math.round(percentage * 100) + "%");
- });
- //4.注册上传完毕监听
- //file:上传完毕的文件
- //response:后台回送的数据,以json格式返回
- uploader.on("uploadSuccess",
- function(file, response) {
- //更新状态信息
- $("#" + file.id).find("div.state").text("上传完毕");
- });
2)后端 Servlet 代码
- DiskFileItemFactory factory = new DiskFileItemFactory();
- ServletFileUpload sfu = new ServletFileUpload(factory);
- sfu.setHeaderEncoding("utf-8");
- try {
- List < FileItem > items = sfu.parseRequest(request);
- for (FileItem item: items) {
- if (item.isFormField()) {
- //普通信息
- } else {
- //文件信息
- //判断只有文件才需要进行保存处理
- System.out.println("接收的文件名称:" + item.getName());
- //拷贝文件到后台的硬盘
- FileUtils.copyInputStreamToFile(item.getInputStream(), new File(serverPath + "/" + item.getName()));
- System.out.println("文件保存成功");
- }
- }
- } catch(FileUploadException e) {
- e.printStackTrace();
- }
生成图片缩略图
关键点:调用 uploader.makeThumb() 方法生成缩略图
- uploader.on("fileQueued",function(file){
- //追加文件信息div
- $("#fileList").append("<div id='"+file.id+"' class='fileInfo'><img/><span>"+file.name+"</span><div class='state'>等待上传...</div><span class='text'></span></div>");
- //制造图片缩略图:调用makeThumb()方法
- //error: 制造缩略图失败
- //src: 缩略图的路径
- uploader.makeThumb(file,function(error,src){
- var id = $("#"+file.id);
- //如果失败,则显示"不能预览"
- if(error){
- id.find("img").replaceWith("不能预览");
- }
- //成功,则显示缩略图到指定位置
- id.find("img").attr("src",src);
- });
- });
拖拽,黏贴上传
1)页面添加拖拽区域的 div
- <!-- 上传div -->
- <div id="uploader">
- <!-- 文件拖拽区域 -->
- <div id="dndArea">
- <p>将文件直接拖拽到这里即可自动上传</p>
- </div>
- <!-- 显示文件列表信息 -->
- <ul id="fileList"></ul>
- <!-- 选择文件区域 -->
- <div id="filePicker">点击选择文件</div>
- </div>
2)在 webuploader 的全局配置参数添加拖拽功能的参数
- //1.初始化WebUpload,以及配置全局的参数
- var uploader = WebUploader.create(
- {
- //flashk控件的地址
- swf: "${pageContext.request.contextPath}/js/Uploader.swf",
- //后台提交地址
- server:"${pageContext.request.contextPath}/UploadServlet",
- //选择文件控件的标签
- pick:"#filePicker",
- //自动上传文件
- auto:true,
- //开启拖拽功能,指定拖拽区域
- dnd:"#dndArea",
- //禁用页面其他地方的拖拽功能,防止页面直接打开文件
- disableGlobalDnd:true
- //开启黏贴功能
- paste:"#uploader"
- }
- );
大文件分块上传
1)在 webuploader 全局参数中添加分块上传参数
- //1.初始化WebUpload,以及配置全局的参数
- var uploader = WebUploader.create({
- //flashk控件的地址
- swf: "${pageContext.request.contextPath}/js/Uploader.swf",
- //后台提交地址
- server: "${pageContext.request.contextPath}/UploadServlet",
- //选择文件控件的标签
- pick: "#filePicker",
- //自动上传文件
- auto: true,
- //开启拖拽功能,指定拖拽区域
- dnd: "#dndArea",
- //禁用页面其他地方的拖拽功能,防止页面直接打开文件
- disableGlobalDnd: true,
- //开启黏贴功能
- paste: "#uploader",
- //分块上传设置
- //是否分块上传
- chunked: true,
- //每块文件大小(默认5M)
- chunkSize: 5 * 1024 * 1024,
- //开启几个并发线程(默认3个)
- threads: 3,
- //在上传当前文件时,准备好下一个文件
- prepareNextFile: true
- });
2)监控上传文件的三个时间点
添加以上三个配置后,会发现当文件超过 5M 时,webuploader 自动把文件会分几个请求发送给后台
每个分块请求,包含的信息:
可以监听文件分块上传的三个重要的时间点。
- before - send - file: 在所有分块发送之前调用before - send: 如果有分块,在每个分块发送之前调用after - send - file: 在所有分块发送完成之后调用
- //5.监控文件上传的三个时间点(注意:该段代码必须放在WebUploader.create之前)
- //时间点1::所有分块进行上传之前(1.可以计算文件的唯一标记;2.可以判断是否秒传)
- //时间点2: 如果分块上传,每个分块上传之前(1.询问后台该分块是否已经保存成功,用于断点续传)
- //时间点3:所有分块上传成功之后(1.通知后台进行分块文件的合并工作)
- WebUploader.Uploader.register({
- "before-send-file": "beforeSendFile",
- "before-send": "beforeSend",
- "after-send-file": "afterSendFile"
- },
- {
- //时间点1::所有分块进行上传之前调用此函数
- beforeSendFile: function() {
- //1.计算文件的唯一标记,用于断点续传和秒传
- //2.请求后台是否保存过该文件,如果存在,则跳过该文件,实现秒传功能
- },
- //时间点2:如果有分块上传,则 每个分块上传之前调用此函数
- beforeSend: function() {
- //1.请求后台是否保存过当前分块,如果存在,则跳过该分块文件,实现断点续传功能
- },
- //时间点3:所有分块上传成功之后调用此函数
- afterSendFile: function() {
- //1.如果分块上传,则通过后台合并所有分块文件
- }
- });
before-send-file 逻辑:
- //利用md5File()方法计算文件的唯一标记符
- //该函数接收一个deferred
- beforeSendFile:function(file){
- //创建一个deffered
- var deferred = WebUploader.Deferred();
- //1.计算文件的唯一标记,用于断点续传和秒传
- (new WebUploader.Uploader()).md5File(file,0,5*1024*1024)
- .progress(function(percentage){
- $("#"+file.id).find("div.state").text("正在获取文件信息...");
- })
- .then(function(val){
- uniqueFileTag = val;
- $("#"+file.id).find("div.state").text("成功获取文件信息");
- //只有文件信息获取成功,才进行下一步操作
- deferred.resolve();
- });
- //alert(uniqueFileTag);
- //2.请求后台是否保存过该文件,如果存在,则跳过该文件,实现秒传功能
- //返回deffered
- return deferred.promise();
- }
before-send 逻辑:
- //向后台发送当前文件的唯一标记,用于后台创建保存分块文件的目录
- beforeSend: function() {
- //携带当前文件的唯一标记到后台,用于让后台创建保存该文件分块的目录
- this.owner.options.formData.fileMd5 = fileMd5;
- }
3)后台需要保存所有分块文件
- //为每个文件创建一个目录,并保存这个文件的所有分块文件
- //判断是否已经分块上传
- if (chunks != null) {
- System.out.println("分块处理...");
- //进行分块上传了
- //建立一个临时目录,用于保存所有分块文件
- File chunksDir = new File(serverPath + "/" + fileMd5);
- if (!chunksDir.exists()) {
- chunksDir.mkdir();
- }
- if (chunk != null) {
- //保存分块文件
- File chunkFile = new File(chunksDir.getPath() + "/" + chunk);
- FileUtils.copyInputStreamToFile(item.getInputStream(), chunkFile);
- }
4)前台通知后台合并所有分块文件
- //前台通知后台合并文件
- after - send - file逻辑:afterSendFile: function(file) {
- //1.如果分块上传,则通过后台合并所有分块文件
- //请求后台合并文件
- $.ajax({
- type: "POST",
- url: "${pageContext.request.contextPath}/UploadCheckServlet?action=mergeChunks",
- data: {
- //文件唯一标记
- fileMd5: fileMd5,
- //文件名称
- fileName: file.name
- },
- dataType: "json",
- success: function(response) {
- alert(response.msg);
- }
- });
- }
- //后台合并所有分块文件
- if ("mergeChunks".equals(action)) {
- System.out.println("开始合并文件...");
- //合并文件
- String fileMd5 = request.getParameter("fileMd5");
- String fileName = request.getParameter("fileName");
- //读取目录里面的所有文件
- File f = new File(serverPath + "/" + fileMd5);
- File[] fileArray = f.listFiles(new FileFilter() {
- //排除目录,只要文件
- public boolean accept(File pathname) {
- if (pathname.isDirectory()) {
- return false;
- }
- return true;
- }
- });
- //转成集合,便于排序
- List < File > fileList = new ArrayList < File > (Arrays.asList(fileArray));
- //从小到大排序
- Collections.sort(fileList, new Comparator < File > () {
- public int compare(File o1, File o2) {
- if (Integer.parseInt(o1.getName()) < Integer.parseInt(o2.getName())) {
- return - 1;
- }
- return 1;
- }
- });
- File outputFile = new File(serverPath + "/" + fileName);
- //创建文件
- outputFile.createNewFile();
- //输出流
- FileChannel outChannel = new FileOutputStream(outputFile).getChannel();
- //合并
- FileChannel inChannel;
- for (File file: fileList) {
- inChannel = new FileInputStream(file).getChannel();
- inChannel.transferTo(0, inChannel.size(), outChannel);
- inChannel.close();
- //删除分片
- file.delete();
- }
- //清除文件夹
- File tempFile = new File(serverPath + "/" + fileMd5);
- if (tempFile.isDirectory() && tempFile.exists()) {
- tempFile.delete();
- }
- //关闭流
- outChannel.close();
- response.setContentType("text/html;charset=utf-8");
- response.getWriter().write("{\"msg\":\"合并成功\"}");
- }
大文件断点续传
在实现了分块上传的基础上,实现断点续传就非常简单了!!!
前端:
- //时间点2:如果有分块上传,则 每个分块上传之前调用此函数
- //block:代表当前分块对象
- beforeSend: function(block) {
- //1.请求后台是否保存过当前分块,如果存在,则跳过该分块文件,实现断点续传功能
- var deferred = WebUploader.Deferred();
- //请求后台是否保存完成该文件信息,如果保存过,则跳过,如果没有,则发送该分块内容
- $.ajax({
- type: "POST",
- url: "${pageContext.request.contextPath}/UploadCheckServlet?action=checkChunk",
- data: {
- //文件唯一标记
- fileMd5: fileMd5,
- //当前分块下标
- chunk: block.chunk,
- //当前分块大小
- chunkSize: block.end - block.start
- },
- dataType: "json",
- success: function(response) {
- if (response.ifExist) {
- //分块存在,跳过该分块
- deferred.reject();
- } else {
- //分块不存在或者不完整,重新发送该分块内容
- deferred.resolve();
- }
- }
- });
- //携带当前文件的唯一标记到后台,用于让后台创建保存该文件分块的目录
- this.owner.options.formData.fileMd5 = fileMd5;
- return deferred.promise();
- },
后台:
- //检查该分块是否存在或者完整保存
- private void checkChunk(HttpServletRequest request, HttpServletResponse response) throws IOException,
- FileNotFoundException {
- System.out.println("checkChunk...");
- String fileMd5 = request.getParameter("fileMd5");
- String chunk = request.getParameter("chunk");
- String chunkSize = request.getParameter("chunkSize");
- File checkFile = new File(serverPath + "/" + fileMd5 + "/" + chunk);
- response.setContentType("text/html;charset=utf-8");
- //检查文件是否存在,且大小是否一致
- if (checkFile.exists() && checkFile.length() == Integer.parseInt(chunkSize)) {
- response.getWriter().write("{\"ifExist\":1}");
- } else {
- response.getWriter().write("{\"ifExist\":0}");
- }
- }
文件秒传
在所有分块请求之前,就已经可以进行实现秒传功能!!!
前端:
- beforeSendFile: function(file) {
- //创建一个deffered
- var deferred = WebUploader.Deferred();
- //1.计算文件的唯一标记,用于断点续传和秒传
- (new WebUploader.Uploader()).md5File(file, 0, 5 * 1024 * 1024).progress(function(percentage) {
- $("#" + file.id).find("div.state").text("正在获取文件信息...");
- }).then(function(val) {
- fileMd5 = val;
- $("#" + file.id).find("div.state").text("成功获取文件信息");
- //2.请求后台是否保存过该文件,如果存在,则跳过该文件,实现秒传功能
- $.ajax({
- type: "POST",
- url: "${pageContext.request.contextPath}/UploadCheckServlet?action=fileCheck",
- data: {
- //文件唯一标记
- fileMd5: fileMd5
- },
- dataType: "json",
- success: function(response) {
- if (response.ifExist) {
- $("#" + file.id).find("div.state").text("秒传成功");
- //如果存在,则跳过该文件,秒传成功
- deferred.reject();
- } else {
- //继续上传
- deferred.resolve();
- }
- }
- });
- });
- //返回deffered
- return deferred.promise();
- },
后台:
- //检查文件的md5数据是否跟在数据库存在
- private void fileCheck(HttpServletRequest request, HttpServletResponse response) throws IOException,
- FileNotFoundException {
- String fileMd5 = request.getParameter("fileMd5");
- //模拟数据库
- Map < String,
- String > database = new HashMap < String,
- String > ();
- database.put("576018603f4091782b68b78af85704a1", "01.课程回顾.itcast");
- response.setContentType("text/html;charset=utf-8");
- if (database.containsKey(fileMd5)) {
- response.getWriter().write("{\"ifExist\":1}");
- } else {
- response.getWriter().write("{\"ifExist\":0}");
- }
- }
以上所述是小编给大家介绍的 JavaWeb 文件上传下载实例讲解 (酷炫的文件上传技术),希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 PHPERZ 网站的支持!
来源: http://www.phperz.com/article/17/1128/359382.html