miki艾比利的博客

愿居于一城,与卿所见美好......

您现在的位置是:首页>漫生活>详细内容

struts2结合common.fileUpload实现文件带进度条上传

发布时间:2019-10-09 11:34:28 编辑:miki浏览(713)评论(3)


    最后,项目还残存的问题就是:明明是每隔500ms向后台请求的进度信息,但是实际上后台并不能够在这段时间内返回对应的信息。亟待优化! 
    效果图如下: 

    获取文件上传信息的基本步骤:经测试有效

    上传文件时如何获取上传进度信息?

    从网上查一下资料就可以知道struts2对文件上传的request请求做了封装,也就是说在调用上传文件的action时就已经上传temp完成了。 
    那么既然这样,我们就可以重写struts2封装request的类:org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest; 
    由于这个类的许多方法都是private的,那么也就是说我们是无法通过extends来重写父类的一些方法的。怎么办呢? 
    暴力一点! 
    直接自定义一个JakartaMultiPartRequest类型的类,把父类的方法全部copy过来,然后在struts.xml中配置:

    
        
        
        
        
        ....
        

    接下来我们实现上述配置文件中的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> files = new HashMap>();
    
        // maps parameter name -> List of param values
        protected Map> params = new HashMap>();
    
        // any errors while processing this request
        protected List errors = new ArrayList();
    
        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 values;
            if (files.get(item.getFieldName()) != null) {
                values = files.get(item.getFieldName());
            } else {
                values = new ArrayList();
            }
    
            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 values;
            if (params.get(item.getFieldName()) != null) {
                values = params.get(item.getFieldName());
            } else {
                values = new ArrayList();
            }
    
            // 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 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 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 items = files.get(fieldName);
    
            if (items == null) {
                return null;
            }
    
            List contentTypes = new ArrayList(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 items = files.get(fieldName);
    
            if (items == null) {
                return null;
            }
    
            List fileList = new ArrayList(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 items = files.get(fieldName);
    
            if (items == null) {
                return null;
            }
    
            List fileNames = new ArrayList(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 items = files.get(fieldName);
    
            if (items == null) {
                return null;
            }
    
            List fileNames = new ArrayList(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 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 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 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 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 names = files.keySet();
            for (String name : names) {
                List 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 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接口
     */
    
    
    /**
     * 2018.11.27	miki	监听文件上传进度的监听器
     * 
     */
    
    public class UploadProgress implements ProgressListener {
    	HttpSession session;
    	/*
    	 * 实时更新上传进度
    	 * arg0:当前上传文件的大小
    	 * arg1:总共需要上传的大小
    	 * arg2:剩余上传文件的大小
    	 */
    	public void update(long arg0, long arg1, int arg2) {
    		// TODO Auto-generated method stub
    		if(arg1==0L){
    			return;
    		}
    		
    		//创造json格式参数
    		String value="var info={read:"+arg0+",total:"+arg1+",items:"+1+"}";		//items:"+arg2+"	d多文件上传的时候,使用arg2
    		
    //		System.out.println(value);
    		session.setAttribute("progress", value);	//上传完毕后,记得清除上传过后的进度条session
    	}
    	
    	public UploadProgress(HttpSession session){
    		this.session=session;
    	}
    
    }
    

     

    前端获取后台文件上传的进度信息

    上传文件可大致分为两个阶段:1. 上传到服务器上,在临时目录中 2.从临时目录中把文件移到指定目录(由自己写的action处理),而struts2.的监听器只监听

    public String upload()throws Exception{
    		
    		//2017.11.16 21:58 miki 加载网页静态化缓存路径
    		Properties prop = new Properties();
    		try {
    			prop.load(this.getClass().getClassLoader().getResourceAsStream("resource.properties"));
    		} catch (IOException e1) {
    			throw new RuntimeException(e1);
    		}
    		
    		//2017.11.16 20:37 取静态资源中常量
    //		String HTML_GEN_PATH = prop.getProperty("HTML_GEN_PATH");//静态资源存储路径
    
    		HttpServletResponse response=ServletActionContext.getResponse();
    		HttpSession session=request.getSession();
    			user=(User) session.getAttribute("currentUser");
    			if(user==null){
    				request.getRequestDispatcher("error.jsp").forward(request, response);
    			}
    			System.out.println("上传文件名为:"+uploadFileName);
    			shop=shopService.getShopByuserId(user.getId());
    			// 2017.08.05	miki 文件二次命名,作为下载url链接,采用uuid加密命名,并截取2-18位字符,二次加密,增加安全性
    			String imageName = "file_"+RadomUtil.getUUID().substring(2, 18)+uploadFileName.substring(uploadFileName.lastIndexOf("."));
    			File newfile = new File(ServletActionContext.getServletContext().getRealPath("/image_upload/source_upload/file")+"\\"+imageName);
    						
    			IOUtils.cp(upload, newfile);		//因为要设置表单里没有的属性(setImage(),)所以重新创造了一个photo对象
    			
    			//清除上传过后的进度条session
    			session.setAttribute("progress", null);
    			
    			source.setPath(imageName);
    			source.setUser(user);
    			source.setShop(shop);
    			source.setUpload_time(new Date());
    			sourceService.save(source);
    			user.setScore(user.getScore()+5);
    			shop.setSourceNum(shop.getSourceNum()+1);
    
    			shopService.save(shop);
    			userService.saveUser(user);
    						
    //			/*
    //			 * 2017.11.16	20:31 网页静态化实例
    //			 */
    //			try {
    //				User vistor=(User) session.getAttribute("currentUser");
    //				if (StringUtil.isEmpty(page)) {
    //					page="1";
    //				}
    //				PageBean pageBean=new PageBean(Integer.parseInt(page), 6);
    //				sourceList=sourceService.getSourcesByShopId(shop.getId(), pageBean);
    //				long total=sourceService.getSourceCountByuserId(shop.getUser().getId());
    //				StringBuffer param=new StringBuffer();	
    //				param.append("shopId="+shop.getId());
    //				pageCode=PageUtil.genPagination(request.getContextPath()+"/shop/Shop_view.action", total, Integer.parseInt(page), 6,param.toString());
    //				
    //				
    //				//创建一个数据集,Map将data封装进去
    //				Map data=new HashMap<>();
    //				data.put("vistor", vistor);	
    //				data.put("user", user);	
    //				data.put("shop", shop);
    //				data.put("sourceList", sourceList);
    //				data.put("pageCode", pageCode);
    //				//加载模板对象
    //				Configuration configuration=freeMarkerConfigurer.getConfiguration();
    //				Template template = configuration.getTemplate("shop.ftl");				
    //				//创建一个输出流,指定目录及文件名
    //				Writer out=new FileWriter(HTML_GEN_PATH +shop.getId()+ ".html");				
    //				//生成静态页面
    //				template.process(data, out);				
    //				//关闭流
    //				out.close();
    //				
    //			} catch (Exception e) {
    //				// TODO: handle exception
    //				e.printStackTrace();
    //			}
    								
    		return "su";
    	}

    返回json数据的struts.xml配置如下:

     
            
               shop/Shop_upload.action
            
            

    项目做到这里,后端的事情已经做完了。接下来就是前端显示进度条的事情。需要的同学可以继续往下看咯。

    前端那些事儿——css实现进度条控件

    css:可以直接copy,无需细看(firefox下测试无问题)

     

     

    控制进度条代码:在提交文件时同步调用此计时器即可。

    ...			
    if('${shop.sourceNum}'=='${shop.maxNum}'){
    				$("#error").html("*您已达到资源上传上限,请升级权限再来*");
    				return false;
    			}
    			$("#error").html("");
    			createProgress();
    			//getProgress();	//定时刷新
    			setTimeout("getProgressBar()",1000);
    			return true;
    
        /**
          * 展示上传进度条
          */
    
    
    
    
    

     

关键字词:struts2;文件上传;进度条