昨天剛剛做了一個文件列表上傳,后端很簡單,用
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%的時間...哈哈