zoukankan      html  css  js  c++  java
  • 完整的多文件上传实例(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{
             90%;
            padding: 10px;
            margin-left: 5%;
        }
        #i_select_files{
             70%;
            height: 5rem;
        }
        #i_stream_message_container{
            position: absolute !important;
            float: left !important;
        }
        #operate{
            position: absolute;
             50%;
            height: 100%;
        }
        #result{
            position: absolute;
             50%;
            height: 100%;
            margin-left: 50%;
        }
        button{
            background: transparent;
            border: 1px #2a6496 solid;
             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%的时间...哈哈

    平凡是我的一个标签
  • 相关阅读:
    以定位为核心的互联网产品思维
    互联网服务有两种,1是靠软件卖产品,2是靠卖软件盈利
    以用户为核心的互联网思维
    xpath定位详解
    jmeter性能测试的经验总结
    IT男的”幸福”生活"续2
    MySql索引数据结构
    机器学习第四章学习记录和心得
    《机器学习》第一次作业——第一至三章学习记录和心得
    华为云使用初探
  • 原文地址:https://www.cnblogs.com/guyanzy/p/10494323.html
Copyright © 2011-2022 走看看