完整的多文件上传实例(java版)

昨天刚刚做了一个文件列表上传,后端很简单,用

MultipartFile[] files

  获取文件流数组,后端就当IO流操作就可以,似乎好像没啥好写的,但是!!!!!前端是真的糙单.要是自己写一个前端单个文件上传样式是丑了点,不过还是能用的,只是样式是真的丑了....无语了,所有有了这篇.首先来张完成的效果

下面就是实现步骤了,开始对比了Bootstrap fileinput 和jQeury的uploadfile,我使用的功能似乎单一且简单,所以并不需要哪些花狸狐哨的功能,所以选择了这个插件,首先还是感谢大佬,开源这么好的插件

DEMO地址: http://w.twinkling.cn/
官网地址: http://www.twinkling.cn/

  我使用的是SpringBoot,上面的demo是基于基本的servlet写的,现在需要整合到我的项目中.

这个需要注意一点,插件需要写一个

/tk 请求,用于生层上传文件的唯一TOKEN,标识文件,其他的还需要一个配置类,基本配置文件,涉及到文件上传的一些配置,等下一起给出来

  

前端代码:

<head>
    <meta charset="UTF-8">
    <title>上传数据</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <link href="../../static/files/css/stream-v1.css" rel="stylesheet" type="text/css">
</head>
<style>
    body{
        width: 90%;
        padding: 10px;
        margin-left: 5%;
    }
    #i_select_files{
        width: 70%;
        height: 5rem;
    }
    #i_stream_message_container{
        position: absolute !important;
        float: left !important;
    }
    #operate{
        position: absolute;
        width: 50%;
        height: 100%;
    }
    #result{
        position: absolute;
        width: 50%;
        height: 100%;
        margin-left: 50%;
    }
    button{
        background: transparent;
        border: 1px #2a6496 solid;
        width: 3rem;
        margin-left: 2.8rem;
        margin-top: 2rem;
        border-radius: 5px;
        background-color: #0099FF;
        font-weight: bolder;
    }
    button:hover{
        background-color: #4cae4c;
        pointer-events: painted;
    }
    #i_select_files div{
        margin-top: 1rem;
    }
</style>
<body>
<div id="operate">
    <div id="i_select_files"></div>
    <div id="i_stream_files_queue"></div>
    <button onclick="javascript:_t.upload();">开始上传</button>
    <button onclick="javascript:_t.stop();">停止上传</button>
    <button onclick="javascript:_t.cancel();">取消上传</button>
    <button onclick="javascript:_t.destroy();_t=null;_t=new Stream(config);">重新上传</button>
    <button id="import" style="color: #c9302c">导入文件</button>
</div>
<div id="result">
    结果信息:
    <div id="i_stream_message_container" class="stream-main-upload-box" style="overflow: auto;height: 93%;color: #3c763d"></div>
</div>
</body>
<script type="text/javascript" src="../../static/js/jquery-1.11.3.min.js"></script>
<script type="text/javascript" src="../../static/files/js/stream-v1.js"></script>
<script src="https://cdn.bootcss.com/layer/2.3/layer.js"></script>
<script type="text/javascript">
    $("#import").click(function () {
            //询问框
            layer.confirm('确定导入这个月最新上传的数据?', {
                btn: ['确定','我在想想'] //按钮
            }, function(){
                //加载层
                var index = layer.load(0, {shade: false}); //0代表加载的风格,支持0-2
                $.ajax({
                    type: "GET",
                    url: "/xlxs/setData",
                    success: function (data) {
                        if(data != null){
                            layer.close(index);
                                layer.alert('导入成功', {
                                    skin: 'layui-layer-lan'
                                    ,closeBtn: 0
                                    ,anim: 4 //动画类型
                                });
                        }else{
                            layer.msg("导入失败,请重新导入!");
                        }
                    }
                });
            }, function(){
            });
    })
    /**
     * 配置文件(如果没有默认字样,说明默认值就是注释下的值)
     * 但是,on*(onSelect, onMaxSizeExceed...)等函数的默认行为
     * 是在ID为i_stream_message_container的页面元素中写日志
     */
    var config = {
        browseFileId : "i_select_files", /** 选择文件的ID, 默认: i_select_files */
        browseFileBtn : "<div>请把xlxs文件拖到这里</div>", /** 显示选择文件的样式, 默认: <div>请选择文件</div> */
        dragAndDropArea: "i_select_files", /** 拖拽上传区域,Id(字符类型"i_select_files")或者DOM对象, 默认: `i_select_files` */
        dragAndDropTips: "<span>(文件夹)也是可以的</span>", /** 拖拽提示, 默认: <span>把文件(文件夹)拖拽到这里</span> */
        filesQueueId : "i_stream_files_queue", /** 文件上传容器的ID, 默认: i_stream_files_queue */
        filesQueueHeight : 200, /** 文件上传容器的高度(px), 默认: 450 */
        messagerId : "i_stream_message_container", /** 消息显示容器的ID, 默认: i_stream_message_container */
        multipleFiles: true, /** 多个文件一起上传, 默认: false */
        onRepeatedFile: function(f) {
            alert("文件:"+f.name +" 大小:"+f.size + " 已存在于上传队列中。");
            return false;
        },
		autoUploading: false, /** 选择文件后是否自动上传, 默认: true */
		autoRemoveCompleted : true, /** 是否自动删除容器中已上传完毕的文件, 默认: false */
		maxSize: 20480000, /** 单个文件的最大大小,默认:2G */
		retryCount : 3, /** HTML5上传失败的重试次数 */
		// postVarsPerFile : { /** 上传文件时传入的参数,默认: {} */
		// 	param1: "val1",
		// 	param2: "val2"
		// },
		// swfURL : "/swf/FlashUploader.swf", /** SWF文件的位置 */
		// tokenURL : "/tk", /** 根据文件名、大小等信息获取Token的URI(用于生成断点续传、跨域的令牌) */
		// frmUploadURL : "/fd;", /** Flash上传的URI */
		uploadURL : "/upload", /** HTML5上传的URI */
		simLimit: 50, /** 单次最大上传文件个数 */
		extFilters: [".xlsx"], /** 允许的文件扩展名, 默认: [] */
		// onSelect: function(list) {alert('onSelect')}, /** 选择文件后的响应事件 */
		onMaxSizeExceed: function(size, limited, name) {
            alert("上传文件太大了,支持20MB以下")
        }, /** 文件大小超出的响应事件 */
		onFileCountExceed: function(selected, limit) {
            alert("最大上传数量是50个");
        }, /** 文件数量超出的响应事件 */
		onExtNameMismatch: function(name, filters) {
            alert(file.name+' 的文件格式不对,换个试试[xlsx]')
        }, /** 文件的扩展名不匹配的响应事件 */
        // onCancel : function(file) {
        //
        // }, /** 取消上传文件的响应事件 */
		// onComplete: function(file) {alert('onComplete')}, /** 单个文件上传完毕的响应事件 */
		onQueueComplete: function() {
            _t.destroy();_t=null;_t=new Stream(config);
        }, /** 所有文件上传完毕的响应事件 */
		onUploadError: function(status, msg) {
		    alert('上传失败')
        }, /** 文件上传出错的响应事件 */
		onDestroy: function() {
		} /** 文件上传出错的响应事件 */
    };
    var _t = new Stream(config);
</script>

  这里面需要获取两个文件

stream-v1.css
stream-v1.js

其实这个也好获取,要是大家拿不到的话,我就发出来,其他的例子上说明的很详细,按照自己的业务要求修改就可以了.

后台代码:

这里是按照官网给的案例,整合到自己的SpringBoot项目中的,只是稍微修改了下代码,就可以了,只是需要找到修改的地方即可,要是找不到,呵呵,那就又要花费一天的干活.

好啦,开始....基于MVC模式

Controller

    @GetMapping("/upload")
    public void getUpload(HttpServletRequest request,
                            HttpServletResponse response) throws IOException, ServletException {
        StreamServlet streamServlet = new StreamServlet();
        streamServlet.doGet(request,response);
    }

    @PostMapping("/upload")
    public void postUpload(HttpServletRequest request,
                            HttpServletResponse response) throws IOException, ServletException {
        StreamServlet streamServlet = new StreamServlet();
        streamServlet.doPost(request,response);
    }

  

streamServlet:
public class StreamServlet extends HttpServlet {
	private static final long serialVersionUID = -8619685235661387895L;
	/** when the has increased to 10kb, then flush it to the hard-disk. */
	static final int BUFFER_LENGTH = 10240;
	static final String START_FIELD = "start";
	public static final String CONTENT_RANGE_HEADER = "content-range";

	/**
	 * Lookup where's the position of this file?
	 */
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		doOptions(req, resp);

		final String token = req.getParameter(UploadController.TOKEN_FIELD);
		final String size = req.getParameter(UploadController.FILE_SIZE_FIELD);
		final String fileName = req.getParameter(UploadController.FILE_NAME_FIELD);
		final PrintWriter writer = resp.getWriter();
		/** TODO: validate your token. */
		JSONObject json = new JSONObject();
		long start = 0;
		boolean success = true;
		String message = "";
		try {
			File f = IoUtil.getTokenedFile(token);
			start = f.length();
		} finally {
			try {
				if (success)
					json.put(START_FIELD, start);
				json.put(UploadController.SUCCESS, success);
				json.put(UploadController.MESSAGE, message);
			} catch (JSONException e) {}
			writer.write(json.toString());
			IoUtil.close(writer);
		}
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		doOptions(req, resp);
		
		final String token = req.getParameter(UploadController.TOKEN_FIELD);
		final String fileName = req.getParameter(UploadController.FILE_NAME_FIELD);
		Range range = IoUtil.parseRange(req);
		OutputStream out = null;
		InputStream content = null;
		final PrintWriter writer = resp.getWriter();

		//清除旧的文件
		if(!IoUtil.deleteFile(fileName)){
			writer.write("上传失败");
		}

		/** TODO: validate your token. */
		
		JSONObject json = new JSONObject();
		long start = 0;
		boolean success = true;
		String message = "";

		File f = IoUtil.getTokenedFile(token);

		try {
			if (f.length() != range.getFrom()) {
				/** drop this uploaded data */
				throw new StreamException(StreamException.ERROR_FILE_RANGE_START);
			}
			
			out = new FileOutputStream(f, true);
			content = req.getInputStream();
			int read = 0;
			final byte[] bytes = new byte[BUFFER_LENGTH];
			while ((read = content.read(bytes)) != -1)
				out.write(bytes, 0, read);
			start = f.length();
		}catch (StreamException se) {
			success = StreamException.ERROR_FILE_RANGE_START == se.getCode();
			message = "Code: " + se.getCode();
		}catch (FileNotFoundException fne) {
			message = "Code: " + StreamException.ERROR_FILE_NOT_EXIST;
			success = false;
		} catch (IOException io) {
			message = "IO Error: " + io.getMessage();
			success = false;
		} finally {
			IoUtil.close(out);
			IoUtil.close(content);

			/** rename the file */
			if (range.getSize() == start) {
				/** fix the `renameTo` bug */
//				File dst = IoUtil.getFile(fileName);
//				dst.delete();
				// TODO: f.renameTo(dst); 重命名在Windows平台下可能会失败,stackoverflow建议使用下面这句
				try {
					// 先删除
					IoUtil.getFile(fileName).delete();
					
					Files.move(f.toPath(), f.toPath().resolveSibling(fileName));
					System.out.println("TK: `" + token + "`, NE: `" + fileName + "`");
					
					/** if `STREAM_DELETE_FINISH`, then delete it. */
					if (Configurations.isDeleteFinished()) {
						IoUtil.getFile(fileName).delete();
					}
				} catch (IOException e) {
					success = false;
					message = "Rename file error: " + e.getMessage();
				}
				
			}
			try {
				if (success) {
					json.put(START_FIELD, start);
				}
				json.put(UploadController.SUCCESS, success);
				json.put(UploadController.MESSAGE, message);
			} catch (JSONException e) {}
			
			writer.write(json.toString());
			IoUtil.close(writer);
		}
	}
	
	@Override
	protected void doOptions(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		resp.setContentType("application/json;charset=utf-8");
		resp.setHeader("Access-Control-Allow-Headers", "Content-Range,Content-Type");
		resp.setHeader("Access-Control-Allow-Origin", Configurations.getCrossOrigins());
		resp.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
	}

	@Override
	public void destroy() {
		super.destroy();
	}

}

  IoUtils:

/**
 * IO--closing, getting file name ... main function method
 */
public class IoUtil {
    static final Pattern RANGE_PATTERN = Pattern.compile("bytes \\d+-\\d+/\\d+");

    /**
     * According the key, generate a file (if not exist, then create
     * a new file).
     *
     * @param filename
     * @return
     * @throws IOException
     */
    public static File getFile(String filename) throws IOException {
        if (filename == null || filename.isEmpty())
            return null;
        String name = filename.replaceAll("/", Matcher.quoteReplacement(File.separator));
        File f = new File(Configurations.getFileRepository() + File.separator + name);
        if (!f.getParentFile().exists())
            f.getParentFile().mkdirs();
        if (!f.exists())
            f.createNewFile();

        return f;
    }


    /**
     * 清楚旧的文件
     *
     * @param fileName
     * @return
     */
    public static boolean deleteFile(String fileName){
        boolean flag = false;
        File file = new File(new UploadServiceImpl().filePathForNowDay() + File.separator + fileName);
        if(file.exists()){
            return file.delete();
        }
        return flag;
    }

    /**
     * Acquired the file.
     *
     * @param key
     * @return
     * @throws IOException
     */
    public static File getTokenedFile(String key) throws IOException {
        if (key == null || key.isEmpty())
            return null;
        // 文件保存在服务器上的路径
        File f = new File(new UploadServiceImpl().filePathForNowDay().toString()+key);
        if (!f.getParentFile().exists())
            f.getParentFile().mkdirs();
        if (!f.exists())
            f.createNewFile();

        return f;
    }

    public static void storeToken(String key) throws IOException {
        if (key == null || key.isEmpty())
            return;

        File f = new File(Configurations.getFileRepository() + File.separator + key);
        if (!f.getParentFile().exists())
            f.getParentFile().mkdirs();
        if (!f.exists())
            f.createNewFile();
    }

    /**
     * close the IO stream.
     *
     * @param stream
     */
    public static void close(Closeable stream) {
        try {
            if (stream != null)
                stream.close();
        } catch (IOException e) {
        }
    }

    /**
     * 获取Range参数
     *
     * @param req
     * @return
     * @throws IOException
     */
    public static Range parseRange(HttpServletRequest req) throws IOException {
        String range = req.getHeader(StreamServlet.CONTENT_RANGE_HEADER);
        Matcher m = RANGE_PATTERN.matcher(range);
        if (m.find()) {
            range = m.group().replace("bytes ", "");
            String[] rangeSize = range.split("/");
            String[] fromTo = rangeSize[0].split("-");

            long from = Long.parseLong(fromTo[0]);
            long to = Long.parseLong(fromTo[1]);
            long size = Long.parseLong(rangeSize[1]);

            return new Range(from, to, size);
        }
        throw new IOException("Illegal Access!");
    }

    /**
     * From the InputStream, write its data to the given file.
     */
    public static long streaming(InputStream in, String key, String fileName) throws IOException {
        OutputStream out = null;
        File f = getTokenedFile(key);
        try {
            out = new FileOutputStream(f);

            int read = 0;
            final byte[] bytes = new byte[FormDataServlet.BUFFER_LENGTH];
            while ((read = in.read(bytes)) != -1) {
                out.write(bytes, 0, read);
            }
            out.flush();
        } finally {
            close(out);
        }
        /** rename the file * fix the `renameTo` bug */
        File dst = IoUtil.getFile(fileName);
        dst.delete();
        f.renameTo(dst);

        long length = getFile(fileName).length();
        /** if `STREAM_DELETE_FINISH`, then delete it. */
        if (Configurations.isDeleteFinished()) {
            dst.delete();
        }

        return length;
    }
}

TokenUtil:

/**
 * Key Util: 1> according file name|size ..., generate a key;
 * 			 2> the key should be unique.
 */
public class TokenUtil {

	/**
	 * 生成Token, A(hashcode>0)|B + |name的Hash值| +_+size的值
	 * @param name
	 * @param size
	 * @return
	 * @throws Exception
	 */
	public static String generateToken(String name, String size)
			throws IOException {
		if (name == null || size == null)
			return "";
		int code = name.hashCode();
		try {
			String token = (code > 0 ? "A" : "B") + Math.abs(code) + "_" + size.trim();
			/** TODO: store your token, here just create a file */
			IoUtil.storeToken(token);
			
			return token;
		} catch (Exception e) {
			throw new IOException(e);
		}
	}
}

.............................直接看仓库吧.

官方仓库:https://gitee.com/jiangdx/stream

基本上,看完这些可以缩短40%的时间...哈哈

 

posted @ 2019-03-08 10:32  孤燕南飞  阅读(14398)  评论(2编辑  收藏  举报