struts2上传文件显示进度条实例---有图有代码,一看就会
来源:互联网 发布:耶鲁大学法学院知乎 编辑:程序博客网 时间:2024/06/06 07:18
啰嗦两句:显示进度条其实没那么困难
有时候,不知道是环境问题还是人品问题,遇到的bug总是莫名其妙的。可是说到底还是某个知识点不太熟练。这次吐槽一下前端jquery的submit方法。下午的时候测试提交能顺利完成任务,可是到了十一点多的时候就不行了。我知道,程序员十大谎言之一就是:我真的没改这部分代码。好吧,既然不太清除bug到底是怎么出现的,那么就换一种提交方法吧。
所以直接把button类型改成submit,再设置它的click事件,让表单自己提交好了。知识点的盲区就在这时候出现了。由于要轮询后台文件的上传进度信息,不得不一边提交file一遍ajax获取进度。实在是不太清楚setinterval这个方法是异步还是同步,会不会阻塞submit(又和上一段中提到的文件submit不成功混在一起,就测试了好久)。在这个地方也停留了好久。。。
最后,项目还残存的问题就是:明明是每隔500ms向后台请求的进度信息,但是实际上后台并不能够在这段时间内返回对应的信息。亟待优化!
效果图如下:
获取文件上传信息的基本步骤:经测试有效
上传文件时如何获取上传进度信息?
从网上查一下资料就可以知道struts2对文件上传的request请求做了封装,也就是说在调用上传文件的action时就已经上传temp完成了。
那么既然这样,我们就可以重写struts2封装request的类:org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest;
由于这个类的许多方法都是private的,那么也就是说我们是无法通过extends来重写父类的一些方法的。怎么办呢?
暴力一点!
直接自定义一个JakartaMultiPartRequest类型的类,把父类的方法全部copy过来,然后在struts.xml中配置:
<struts> <bean type="org.apache.struts2.dispatcher.multipart.MultiPartRequest" name="myRequestParser" class="listener.MultiPartRequest" scope="default" optional="true" /> <!-- 注意struts2.3.15.1以前版本这里为struts.multipart.handler, struts2.3.15.1(包含2.3.15.1)这里为struts.multipart.parser--> <constant name="struts.multipart.handler" value="myRequestParser"/> <!--action--> .... </struts>
接下来我们实现上述配置文件中的MultiPartRequest类:
通过intellij的反编译插件,我们decode JakartaMultiPartRequest这个类:源码如下—-大量代码来袭,直接copy这部分吧 不用细看
/* * $Id: JakartaMultiPartRequest.java 1384107 2012-09-12 20:14:23Z lukaszlenart $ * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */package org.apache.struts2.dispatcher.multipart;import com.opensymphony.xwork2.LocaleProvider;import com.opensymphony.xwork2.inject.Inject;import com.opensymphony.xwork2.util.LocalizedTextUtil;import com.opensymphony.xwork2.util.logging.Logger;import com.opensymphony.xwork2.util.logging.LoggerFactory;import org.apache.commons.fileupload.FileItem;import org.apache.commons.fileupload.FileUploadBase;import org.apache.commons.fileupload.FileUploadException;import org.apache.commons.fileupload.RequestContext;import org.apache.commons.fileupload.disk.DiskFileItem;import org.apache.commons.fileupload.disk.DiskFileItemFactory;import org.apache.commons.fileupload.servlet.ServletFileUpload;import org.apache.struts2.StrutsConstants;import javax.servlet.http.HttpServletRequest;import java.io.File;import java.io.IOException;import java.io.InputStream;import java.io.UnsupportedEncodingException;import java.util.ArrayList;import java.util.Collections;import java.util.Enumeration;import java.util.HashMap;import java.util.List;import java.util.Locale;import java.util.Map;import java.util.Set;/** * Multipart form data request adapter for Jakarta Commons Fileupload package. */public class JakartaMultiPartRequest implements MultiPartRequest { static final Logger LOG = LoggerFactory.getLogger(JakartaMultiPartRequest.class); // maps parameter name -> List of FileItem objects protected Map<String, List<FileItem>> files = new HashMap<String, List<FileItem>>(); // maps parameter name -> List of param values protected Map<String, List<String>> params = new HashMap<String, List<String>>(); // any errors while processing this request protected List<String> errors = new ArrayList<String>(); protected long maxSize; private Locale defaultLocale = Locale.ENGLISH; @Inject(StrutsConstants.STRUTS_MULTIPART_MAXSIZE) public void setMaxSize(String maxSize) { this.maxSize = Long.parseLong(maxSize); } @Inject public void setLocaleProvider(LocaleProvider provider) { defaultLocale = provider.getLocale(); } /** * Creates a new request wrapper to handle multi-part data using methods adapted from Jason Pell's * multipart classes (see class description). * * @param saveDir the directory to save off the file * @param request the request containing the multipart * @throws java.io.IOException is thrown if encoding fails. */ public void parse(HttpServletRequest request, String saveDir) throws IOException { try { setLocale(request); processUpload(request, saveDir); } catch (FileUploadBase.SizeLimitExceededException e) { if (LOG.isWarnEnabled()) { LOG.warn("Request exceeded size limit!", e); } String errorMessage = buildErrorMessage(e, new Object[]{e.getPermittedSize(), e.getActualSize()}); if (!errors.contains(errorMessage)) { errors.add(errorMessage); } } catch (Exception e) { if (LOG.isWarnEnabled()) { LOG.warn("Unable to parse request", e); } String errorMessage = buildErrorMessage(e, new Object[]{}); if (!errors.contains(errorMessage)) { errors.add(errorMessage); } } } protected void setLocale(HttpServletRequest request) { if (defaultLocale == null) { defaultLocale = request.getLocale(); } } protected String buildErrorMessage(Throwable e, Object[] args) { String errorKey = "struts.messages.upload.error." + e.getClass().getSimpleName(); if (LOG.isDebugEnabled()) { LOG.debug("Preparing error message for key: [#0]", errorKey); } return LocalizedTextUtil.findText(this.getClass(), errorKey, defaultLocale, e.getMessage(), args); } private void processUpload(HttpServletRequest request, String saveDir) throws FileUploadException, UnsupportedEncodingException { for (FileItem item : parseRequest(request, saveDir)) { if (LOG.isDebugEnabled()) { LOG.debug("Found item " + item.getFieldName()); } if (item.isFormField()) { processNormalFormField(item, request.getCharacterEncoding()); } else { processFileField(item); } } } private void processFileField(FileItem item) { if (LOG.isDebugEnabled()) { LOG.debug("Item is a file upload"); } // Skip file uploads that don't have a file name - meaning that no file was selected. if (item.getName() == null || item.getName().trim().length() < 1) { LOG.debug("No file has been uploaded for the field: " + item.getFieldName()); return; } List<FileItem> values; if (files.get(item.getFieldName()) != null) { values = files.get(item.getFieldName()); } else { values = new ArrayList<FileItem>(); } values.add(item); files.put(item.getFieldName(), values); } private void processNormalFormField(FileItem item, String charset) throws UnsupportedEncodingException { if (LOG.isDebugEnabled()) { LOG.debug("Item is a normal form field"); } List<String> values; if (params.get(item.getFieldName()) != null) { values = params.get(item.getFieldName()); } else { values = new ArrayList<String>(); } // note: see http://jira.opensymphony.com/browse/WW-633 // basically, in some cases the charset may be null, so // we're just going to try to "other" method (no idea if this // will work) if (charset != null) { values.add(item.getString(charset)); } else { values.add(item.getString()); } params.put(item.getFieldName(), values); item.delete(); } private List<FileItem> parseRequest(HttpServletRequest servletRequest, String saveDir) throws FileUploadException { DiskFileItemFactory fac = createDiskFileItemFactory(saveDir); ServletFileUpload upload = new ServletFileUpload(fac); upload.setSizeMax(maxSize); return upload.parseRequest(createRequestContext(servletRequest)); } private DiskFileItemFactory createDiskFileItemFactory(String saveDir) { DiskFileItemFactory fac = new DiskFileItemFactory(); // Make sure that the data is written to file fac.setSizeThreshold(0); if (saveDir != null) { fac.setRepository(new File(saveDir)); } return fac; } /* (non-Javadoc) * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getFileParameterNames() */ public Enumeration<String> getFileParameterNames() { return Collections.enumeration(files.keySet()); } /* (non-Javadoc) * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getContentType(java.lang.String) */ public String[] getContentType(String fieldName) { List<FileItem> items = files.get(fieldName); if (items == null) { return null; } List<String> contentTypes = new ArrayList<String>(items.size()); for (FileItem fileItem : items) { contentTypes.add(fileItem.getContentType()); } return contentTypes.toArray(new String[contentTypes.size()]); } /* (non-Javadoc) * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getFile(java.lang.String) */ public File[] getFile(String fieldName) { List<FileItem> items = files.get(fieldName); if (items == null) { return null; } List<File> fileList = new ArrayList<File>(items.size()); for (FileItem fileItem : items) { File storeLocation = ((DiskFileItem) fileItem).getStoreLocation(); if (fileItem.isInMemory() && storeLocation != null && !storeLocation.exists()) { try { storeLocation.createNewFile(); } catch (IOException e) { if (LOG.isErrorEnabled()) { LOG.error("Cannot write uploaded empty file to disk: " + storeLocation.getAbsolutePath(), e); } } } fileList.add(storeLocation); } return fileList.toArray(new File[fileList.size()]); } /* (non-Javadoc) * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getFileNames(java.lang.String) */ public String[] getFileNames(String fieldName) { List<FileItem> items = files.get(fieldName); if (items == null) { return null; } List<String> fileNames = new ArrayList<String>(items.size()); for (FileItem fileItem : items) { fileNames.add(getCanonicalName(fileItem.getName())); } return fileNames.toArray(new String[fileNames.size()]); } /* (non-Javadoc) * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getFilesystemName(java.lang.String) */ public String[] getFilesystemName(String fieldName) { List<FileItem> items = files.get(fieldName); if (items == null) { return null; } List<String> fileNames = new ArrayList<String>(items.size()); for (FileItem fileItem : items) { fileNames.add(((DiskFileItem) fileItem).getStoreLocation().getName()); } return fileNames.toArray(new String[fileNames.size()]); } /* (non-Javadoc) * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getParameter(java.lang.String) */ public String getParameter(String name) { List<String> v = params.get(name); if (v != null && v.size() > 0) { return v.get(0); } return null; } /* (non-Javadoc) * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getParameterNames() */ public Enumeration<String> getParameterNames() { return Collections.enumeration(params.keySet()); } /* (non-Javadoc) * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getParameterValues(java.lang.String) */ public String[] getParameterValues(String name) { List<String> v = params.get(name); if (v != null && v.size() > 0) { return v.toArray(new String[v.size()]); } return null; } /* (non-Javadoc) * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getErrors() */ public List<String> getErrors() { return errors; } /** * Returns the canonical name of the given file. * * @param filename the given file * @return the canonical name of the given file */ private String getCanonicalName(String filename) { int forwardSlash = filename.lastIndexOf("/"); int backwardSlash = filename.lastIndexOf("\\"); if (forwardSlash != -1 && forwardSlash > backwardSlash) { filename = filename.substring(forwardSlash + 1, filename.length()); } else if (backwardSlash != -1 && backwardSlash >= forwardSlash) { filename = filename.substring(backwardSlash + 1, filename.length()); } return filename; } /** * Creates a RequestContext needed by Jakarta Commons Upload. * * @param req the request. * @return a new request context. */ private RequestContext createRequestContext(final HttpServletRequest req) { return new RequestContext() { public String getCharacterEncoding() { return req.getCharacterEncoding(); } public String getContentType() { return req.getContentType(); } public int getContentLength() { return req.getContentLength(); } public InputStream getInputStream() throws IOException { InputStream in = req.getInputStream(); if (in == null) { throw new IOException("Missing content in the request"); } return req.getInputStream(); } }; } /* (non-Javadoc) * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#cleanUp() */ public void cleanUp() { Set<String> names = files.keySet(); for (String name : names) { List<FileItem> items = files.get(name); for (FileItem item : items) { if (LOG.isDebugEnabled()) { String msg = LocalizedTextUtil.findText(this.getClass(), "struts.messages.removing.file", Locale.ENGLISH, "no.message.found", new Object[]{name, item}); LOG.debug(msg); } if (!item.isInMemory()) { item.delete(); } } } }}
好的,我们把上面这部分代码copy到我们自定义的public class MultiPartRequest extends JakartaMultiPartRequest {}中
复制粘贴完了之后,我们要ctrl f找到parseRequest方法。在这里,我们要设置文件上传进度监听器
/** * 自定义的parseRequest方法 * @param servletRequest * @param saveDir * @return * @throws FileUploadException */ private List<FileItem> parseRequest(HttpServletRequest servletRequest, String saveDir) throws FileUploadException { System.out.println("调用parseRequest方法"); UploadProgressListener listener = new UploadProgressListener(servletRequest); DiskFileItemFactory fac = createDiskFileItemFactory(saveDir); ServletFileUpload upload = new ServletFileUpload(fac); upload.setSizeMax(maxSize); upload.setProgressListener(listener); System.out.println("设置监听器成功"); return upload.parseRequest(createRequestContext(servletRequest)); }
在上面这个代码片段中可以看到:我们new了一个监听器UploadProgressListener 的实例,然后调用了ServletFileUpload .setProgressListener(listener)方法。也就是说:这里的监听器与我们以前写过的request,session等等监听器不同,不需要在web.xml中配置。这里只需要一行代码即可解决。
那么我们就来实现这个监听器吧!
这个监听器需要实org.apache.commons.fileupload.ProgressListener这个接口:我们直接implement一下。
在update方法中我们可以获取到已上传的文件长度,上传的文件的总长度,当前上传第几个文件。(当你上传一个文件的时候,打印日志就会发现这里会输出2。个人猜测是先上传到temp然后再上传到指定路径…)
我们在update方法中设置了文件上传状态实体类的实例,并且保存到session。这一步就是能让前端使用ajax访问到文件当前的上传进度信息。
package listener;import entity.FileUploadProgress;import org.apache.commons.fileupload.ProgressListener;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpSession;/** * Created by zipple on 2017/10/13. * 监听文件上传情况 * 实现org.apache.commons.fileupload.ProgressListener接口 */public class UploadProgressListener implements ProgressListener { private HttpSession session;//创建监听器实例的时候获取session //在自定义的MultiPartRequest 类中 创建此监听器的实例 public UploadProgressListener(HttpServletRequest request) { session = request.getSession(); FileUploadProgress fileUploadProgress = new FileUploadProgress(); fileUploadProgress.setFlag(false); session.setAttribute("fileUploadProgress", fileUploadProgress); } @Override public void update(long readBytes, long totalBytes, int currentItem) { //实现文件上传的核心方法 Object attribute = session.getAttribute("fileUploadProgress");// System.out.println("当前已读取:"+readBytes+" 总长度:"+totalBytes+" 正在保存:"+currentItem); FileUploadProgress fileUploadProgress; if(null == attribute){ fileUploadProgress = new FileUploadProgress(); fileUploadProgress.setFlag(false); System.out.println("uploadListener文件上传的开始时间:"+fileUploadProgress.getStartTime()); session.setAttribute("fileUploadProgress", fileUploadProgress); }else{ fileUploadProgress = (FileUploadProgress)attribute; } fileUploadProgress.setCurrentLength(readBytes); fileUploadProgress.setTotalLength(totalBytes); if(readBytes==totalBytes){ fileUploadProgress.setFlag(true); }else{ fileUploadProgress.setFlag(false); } session.setAttribute("fileUploadProgress", fileUploadProgress); }}
文件上传状态实体类代码:
package entity;/** * Created by zipple on 2017/10/14. * 上传文件进度信息实体类 */public class FileUploadProgress { private long startTime = System.currentTimeMillis();//开始时间 private long totalLength=1;//文件上传的总长度 private long currentLength=0;//当前文件上传的长度 private boolean flag;//是否上传完成 public long getTotalLength() { return totalLength; } public void setTotalLength(long totalLength) { this.totalLength = totalLength; } public long getCurrentLength() { return currentLength; } public void setCurrentLength(long currentLength) { this.currentLength = currentLength; } public boolean isFlag() { return flag; } public void setFlag(boolean flag) { this.flag = flag; } public long getStartTime() { return startTime; } public void setStartTime(long startTime) { this.startTime = startTime; } public FileUploadProgress(){ super(); }}
前端获取后台文件上传的进度信息
好的,前面已经做完了所有的基本工作了。万事俱备,我们最后只需要实现对应的查询action即可。在这个地方我也要记录一个bug。在使用ajax访问这个action的时候,一直报错。当然,我在前面的博客中也吐槽过了,这个报错信息一点用没有。我又找了好久。后来发现jsonData只是单纯的定义了map对象,并没有new出它的实例,所以空指针异常了!!!
这里我们使用的是返回json数据。(需要struts2-json-plugin-2.3.16.jar)
package action;import com.opensymphony.xwork2.Action;import entity.FileUploadProgress;import org.apache.struts2.ServletActionContext;import util.DataUtil;import java.util.HashMap;import java.util.Map;/** * Created by zipple on 2017/10/15. * 获取文件进度信息 */public class ProgressAction implements Action { private Map<String,Object> jsonData; @Override public String execute() throws Exception { System.out.println("获取上传文件进度信息"); jsonData = new HashMap<>();//初始化jsonData!!!! FileUploadProgress p; Object attribute = ServletActionContext.getRequest().getSession().getAttribute("fileUploadProgress"); if(null == attribute){ System.out.println("session中没有fileupload信息"); jsonData.put("completed", false); jsonData.put("isStarted", false); setJsonData(jsonData); return SUCCESS; }else{ System.out.println("session中有fileupload信息"); p = (FileUploadProgress)attribute; } System.out.println("action中获取到的文件上传的开始时间:"+p.getStartTime()); System.out.println("action运行到这里的时间:"+System.currentTimeMillis()); long time = (System.currentTimeMillis() - p.getStartTime())/ 1000 + 1; //已传输的时间 单位:s System.out.println("截至目前上传时间:"+time); double v = ((double)p.getCurrentLength()) / (double)time; // b/s System.out.println("传输速度:"+v); System.out.println("pAction:当前上传--"+p.getCurrentLength()); jsonData.put("percent", DataUtil.percent(p.getCurrentLength(),p.getTotalLength())); jsonData.put("remain", (int)(p.getTotalLength()/v-time)); jsonData.put("isStarted", true); jsonData.put("completed", p.isFlag()); if (p.isFlag()){ System.out.println("已完成,重置fileUploadProgress"); ServletActionContext.getRequest().getSession().removeAttribute("fileUploadProgress"); } setJsonData(jsonData); return SUCCESS; } public Map<String,Object> getJsonData() { return jsonData; } public void setJsonData(Map<String,Object> jsonData) { this.jsonData = jsonData; }}
返回json数据的struts.xml配置如下:
<package name="json" extends="struts-default,json-default"> <action name="getProgress" class="action.ProgressAction" method="execute"> <result name="success" type="json"> <param name="root">jsonData</param><!-- action中的要返回的属性 --> </result> </action> </package>
项目做到这里,后端的事情已经做完了。接下来就是前端显示进度条的事情。需要的同学可以继续往下看咯。
前端那些事儿——css实现进度条控件
css:可以直接copy,无需细看(firefox下测试无问题)
.progress{ height: 20px; background: #ebebeb; border-left: 1px solid transparent; border-right: 1px solid transparent; border-radius: 10px;}.progress .blue { background: #5aaadb; border-color: #459fd6 #3094d2 #277db2; background-image: -webkit-linear-gradient(top, #aed5ed 0%, #7bbbe2 70%, #5aaadb 100%); background-image: -moz-linear-gradient(top, #aed5ed 0%, #7bbbe2 70%, #5aaadb 100%); background-image: -o-linear-gradient(top, #aed5ed 0%, #7bbbe2 70%, #5aaadb 100%); background-image: linear-gradient(to bottom, #aed5ed 0%, #7bbbe2 70%, #5aaadb 100%);}.progress > span { position: relative; float: left; margin: 0 -1px; min-width: 30px; height: 18px; line-height: 16px; text-align: right; background: #cccccc; border: 1px solid; border-color: #bfbfbf #b3b3b3 #9e9e9e; border-radius: 10px; background-image: -webkit-linear-gradient(top, #f0f0f0 0%, #dbdbdb 70%, #cccccc 100%); background-image: -moz-linear-gradient(top, #f0f0f0 0%, #dbdbdb 70%, #cccccc 100%); background-image: -o-linear-gradient(top, #f0f0f0 0%, #dbdbdb 70%, #cccccc 100%); background-image: linear-gradient(to bottom, #f0f0f0 0%, #dbdbdb 70%, #cccccc 100%); -webkit-box-shadow: inset 0 1px rgba(255, 255, 255, 0.3), 0 1px 2px rgba(0, 0, 0, 0.2); box-shadow: inset 0 1px rgba(255, 255, 255, 0.3), 0 1px 2px rgba(0, 0, 0, 0.2);}.progress > span > span { padding: 0 8px; font-size: 11px; font-weight: bold; color: #404040; color: rgba(0, 0, 0, 0.7); text-shadow: 0 1px rgba(255, 255, 255, 0.4);}.progress > span:before { content: ''; position: absolute; top: 0; bottom: 0; left: 0; right: 0; z-index: 1; height: 18px; background: url("http://js.itivy.com/5-progress-bars/img/progress.png") 0 0 repeat-x; border-radius: 10px;}
html调用progress代码很简单,就一行:这里设置隐藏,上传文件时再显示
<div class="progress" style="width: 400px;" hidden="hidden"><span class="blue" style="width: 1%;"><span>1%</span></span></div>
控制进度条代码:在提交文件时同步调用此计时器即可。
setInterval(progressBar,500); /** * 展示上传进度条 */function progressBar () { console.log("询问后台进度..."); $.ajax({ type : "GET", //提交方式 url : "getProgress.action",//路径 success : function(result) {//返回数据根据结果进行相应的处理 console.log(result); console.log("文件是否上传完成"+result.completed); if (result.isStarted){ if (result.completed){ $(".progress").hide(); clearInterval(progressBar);//清除计时器 }else{ $(".progress >span").css({"width":result.percent}); $(".progress >span>span").text(result.percent); $(".remain").text(result.remain); } }else{ console.log("文件还没有提交"); } }, //异常处理 error:function (XMLHttpRequest, textStatus, errorThrown) { console.log(XMLHttpRequest+"---"+textStatus+"---"+errorThrown); } }); }
看到这里,上传文件显示进度条的相关内容基本上是结束了。
有问题的,可以留言交流~
生活有许多的不如意,如果一不开心,就寄希望于“如果当初”,那你永远都不会开心。
- struts2上传文件显示进度条实例---有图有代码,一看就会
- struts2上传文件,显示进度条
- struts2上传文件,显示进度条
- struts2上传文件,显示进度条
- struts2上传文件,显示进度条
- Struts2 简单的文件上传例子,一看就懂。
- struts2上传文件,显示进度条 (2)
- 上传文件时显示进度条的代码
- struts2上传文件进度条
- sed 一看就会
- 文件上传进度条显示
- 用Struts2实现文件上传时显示进度条功能
- 【转】用Struts2实现文件上传时显示进度条功能
- 用Struts2实现文件上传时显示进度条功能
- eclipse 基于struts2 + json 实现文件异步上传, 显示进度条
- 使用struts2和AJAX实现文件上传并显示进度条
- 使用struts2和AJAX实现文件上传并显示进度条
- struts2上传文件(进度条)
- 从零一起学react(6)---props属性验证v16.0.0
- PostgreSQL在非默认的路径下创建表空间及数据库后,删除数据库目录后,还需要删除pg_tblspc下的链接文件,否则尝试启动其他实例时会报错
- iOS面试题
- 用Vue.js实现全选与全不选删除功能
- nodejs zip压缩版安装与配置
- struts2上传文件显示进度条实例---有图有代码,一看就会
- 字符串转json数组
- 51nod 1076 2条不相交的路径(边双连通分量)
- 使用Qt插件在Qt中进行ROS开发
- 设计模式学习总结
- 【产品经理三节课】第3章 需求挖掘入门
- 如何实现一个分布式RPC框架
- java 静态成员变量或方法
- 给文件按序编号(多用于大数据处理)