easyUi + jquery + common-file-upload +struts2完成文件进度条上传


文章知识点纲要

1:应用struts2框架2.2.1  , 基础框架

2:easyUI 1.2.6, 实现文件进度条 ,progressBar显示进度

3:commons-fileupload-1.2.1 (注意:commons-fileupload-1.2.2老版本上传文件大小有限制<1G,而commons-fileupload-1.3.jar版本对此BUG进行了修正,上传文件大小无限大,具体可见官网说明,目前测试过很大45G), 实现文件读写操作和监听文件写入缓存进度

4:struts2 JSON 配置与前后台交互 ,struts2-json-plugin-2.2.1.1.jar包(注意:目前只测试前台接收后台返回的String类,自定义结构型对象未写)      

大体思路


    引入应用struts2框架,配置相关文件(web.xml, struts.xml)------>配置struts-json,主要修改struts.xml和引入struts2-json-plugin-2.2.1.1.jar包------> 重写继承MultiPartRequest接口struts2类文件JakartaMultiPartRequest,自定义为MyJakartaMultiPartRequest类(注意依旧继承MultiPartRequest接口),并修改protected List<FileItem> parseRequest(HttpServletRequest request, String saveDir) throws FileUploadException(){// 引入开源commons-fileupload-1.2.1.jar包,引用对文件的写入和文件写入时文件进度监听方法}------> 前台easyui 组件展示、并发送AJAX请求   ------>后台继承ActionSupport的Action方法响应并返回JSON ------>前台easyui 组件接收JSON数据,解析展示
------>完成交互展示


效       果

easyUi + jquery + common-file-upload +struts2完成文件进度条上传_第1张图片

easyUi + jquery + common-file-upload +struts2完成文件进度条上传_第2张图片


疑问:为什么要重写、自定义JakartaMultiPartRequest类呢?

原因:

    struts2默认的拦截器中有一个FileUploadInterceptor,它会拦截所有的MultipartRequest,并且将得到的File及相关信息传递给action,因此在action被调用之前,文件上传已经被处理完了,不能引入监听文件写入时文件进度;

    struts2处理文件上传使用的是commons-fileupload,因此我们可以使用ProgressListener。注意我们需要在解析请求的时候加入我们的监听器,我们首先想到的是替换掉FileUploadInterceptor,不幸的是 FileUploadInterceptor并不执行解析的任务,实际在FileUploadInterceptor被调用之前,MultipartRequest已经被解析了,文件上传的工作已经完成。而实际上对于所有的文件上传请求,struts2会为其生成一个MultiPartRequestWrapper进行包装,而它维护着一个 MultiPartRequest接口的实例。MultiPartRequest的实现类只有一个 JakartaMultiPartRequest,JakartaMultiPartRequest有一个方法parseRequest,此方法负责解析 request并生成FileItem,即对文件进行读写操作,因此我们可以重写此方法,添加ProgressListener。不幸的是,JakartaMultiPartRequest的很多方法都是private的,我们不能继承它然后重写parseRequest方法,JakartaMultiPartRequest实现了MultiPartRequest接口,我们可以编写一个类,实现 MultiPartRequest接口,替代JakartaMultiPartRequest类的代码全都拷贝过来,并修改parseRequest方法,完成文件的写入与进度的监听。


具体代码可到 http://download.csdn.net/detail/jun55xiu/7094731  下载


反馈:

       有什么问题或者其它好的实现方法可以提出思路,大家共同学习,谢谢!


测试出问题修正:

1  修改前台文件上传VIEW,即 type="file" 为 s:file 标签,为统一file上传界面,兼容(IE6到9、firefox、google),后台代码方法不变

2  去掉jquery对文件大小的判断,原因:IE7 8 9无法获取文件大小,除非修改IE安全等级和设置IE相关属性,用户使用体验不好

3  前台文件上传页面上设置AJAX全局缓存清空变量,原因:  前台文件上传,在监听进度条上传进度,setTimeout再次发送HTTP GET请求,更新进度时, 请求的HTTP GET请求在设置缓存清空为true情况下,IE浏览器HTTP机制会为提高数据的访问率、效率而当作重复的URL请求,不会再次发次URL请求,而是直接从AJAX全局缓存获取缓存数据;导致HTTP GET请求只发送一次,如图:


设置AJAX全局缓存清空变量,即加入代码:

<script type="text/javascript">
    //设置AJAX全局缓存清空变量cache:false
    $.ajaxSetup({
        cache : false
    });
</script>

4: 在各IE版本上传文件时,在form 表单提交的文件保存HTTP    POST请求正常完成后,会弹出一个已完成安装和文件下载的窗口?如下图:

easyUi + jquery + common-file-upload +struts2完成文件进度条上传_第3张图片

问题原因定位:

  在HTTP POST请求操作后,服务器的响应头类型  Content-Type  application/json;charset=UTF-8

而IE浏览器在接收到Content-Type  application/json时默认为请求下载,故修改 Content-Type  text/html;便可解决问题


附:SSH + JSON 框架此问题解决方案:

     在返回JSON 类型处对  contentType 进行配置为 test/html  ,即可解决问题

easyUi + jquery + common-file-upload +struts2完成文件进度条上传_第4张图片

附:SPRING MVC 框架此问题解决方案:

核心代码

@RequestMapping(value="/entity/headers")
    public ResponseEntity<String> add(HttpServletRequest request, HttpServletResponse response
            ) throws UnsupportedEncodingException {
        String json = "{success: true}";
        HttpHeaders responseHeaders = new HttpHeaders();
        responseHeaders.setContentType(MediaType.TEXT_HTML);

      //  文件保存同上

    return new ResponseEntity<String>(json, responseHeaders, HttpStatus.OK);

  }

前台的HTTP URL 也一样。


5:修改以上4个问题后,此文件上传功能可以兼容IE6到11、firefox、google。



II)  struts2-core版本不同,struts.xml配置不同 

    struts2漏洞爆发,近日修补漏洞,struts2.3.4.1升级到struts2.3.15.1,发现以前用uploadFile能正常上传文件显示进度条现在不能正常工作了,接着进行排错,查看公司项目框架、相关配置文件、struts2包版本的跟踪,依然没有解决,就这样蛋痛了一下午,纠结在同样的代码、同样的配置,文件上传时为什么就是不进行方法parseRequest(重写JakartaMultiPartRequest类方法) ,最终查看了一下struts2.3.15.1的struts-default.xml配置文件,发现<constant name="struts.multipart.handler" value="jakarta" />,见附图,已经变成了parser,而原来的项目(struts2-core版本:struts2-core-2.2.1.jar)中为struts.multipart.handler,立马换为struts.multipart.parser,一切OK,正常了。

easyUi + jquery + common-file-upload +struts2完成文件进度条上传_第5张图片


最终原因为struts2-core版本不同,对应org.apache.struts2.dispatcher.multipart.MultiPartRequest的配置和 constant name不一致。

以下为我附上的不同struts-core版本 struts.xml 配置文件和核心代码

struts2.3.15.1版本:

<struts>
    <!-- 1配置自定义文件类myRequestParser,继承MultiPartRequest重写 -->
    <bean type="org.apache.struts2.dispatcher.multipart.MultiPartRequest"
        name="myRequestParser" class="com.vrv.paw.action.MyJakartaMultiPartRequest"
        scope="default" optional="true" />
    <constant name="struts.multipart.parser" value="myRequestParser" />

...................

</struts>

=============================友情分隔===========================================

com.vrv.paw.action.MyJakartaMultiPartRequest类:

/*
 * $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 com.vrv.paw.action;
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 com.vrv.paw.utils.FileUploadConstant;
import com.vrv.paw.domain.User;
import com.vrv.paw.utils.FileOperatorUtil;
import com.vrv.paw.utils.ReadConfigFileUtil;
import com.vrv.paw.utils.SessionUtil;

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.ServletActionContext;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.dispatcher.multipart.MultiPartRequest;

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 MyJakartaMultiPartRequest implements MultiPartRequest {

    static final Logger LOG = LoggerFactory.getLogger(MyJakartaMultiPartRequest.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>();
    private Locale defaultLocale = Locale.ENGLISH;

    private long lastfileLen = 0L;
    protected long maxSize = 100 * 1024 * 1024l; //in bytes  = 100M
    @Inject(StrutsConstants.STRUTS_MULTIPART_MAXSIZE)
    public void setMaxSize(String maxSize) {
        this.maxSize = Long.parseLong(maxSize);
    }

    @Inject
    public void setLocaleProvider(LocaleProvider provider) {
        defaultLocale = provider.getLocale();
    }

    private List<FileItem> parseRequest(HttpServletRequest servletRequest, String saveDir) throws FileUploadException {
        long st = System.currentTimeMillis();
        String filePath = ServletActionContext.getServletContext().getRealPath(ReadConfigFileUtil.getValue("downDir"));
        File file = new File(filePath);
        if (!file.exists())
            file.mkdirs();
        DiskFileItemFactory factory = new DiskFileItemFactory();
        ServletFileUpload upload = new ServletFileUpload(factory);
        // param sizeMax The maximum allowed size, in bytes. The default value of -1 indicates, that there is no limit.
        upload.setFileSizeMax(-1L);
        // Sets the maximum allowed size of a single uploaded file, in bytes
        upload.setSizeMax(maxSize);
        upload.setHeaderEncoding("UTF-8");
        // 设置进度监听器
        upload.setProgressListener(new FileListener(servletRequest));
        try {
            String fileName = null;
            List<?> items = upload.parseRequest(servletRequest);
            FileItem item = null;
            for (int i = 0; i < items.size(); i++) {
                item = (FileItem) items.get(i);
                String uploadFileNameStr = "";
                if( null != item.getName() && (!"".equals(item.getName())) ){
                    Integer lastChar = item.getName().trim().lastIndexOf("\\") ;
                    if( -1 != lastChar ){
                        //IE 6 C:\demo\新fdsf本文档.exe
                        uploadFileNameStr = item.getName().trim().substring(lastChar+1, item.getName().trim().length());
                    }
                    else{
                        uploadFileNameStr = item.getName().trim();
                    }
                    FileOperatorUtil.isDoubleFileNameAndNew(filePath,uploadFileNameStr);
                    fileName = filePath + uploadFileNameStr.replaceAll(" ", "");
                    lastfileLen = item.getSize();//Byte
                    if (item.isInMemory()) {
                        // 处理小文件,数据直接放入内存中
                        item.write(new File(fileName));
                    } else {
                        // 保存文件
                        if (!item.isFormField() && uploadFileNameStr.length() > 0) {
                            // item.write(new File(fileName));
                            ((DiskFileItem) item).getStoreLocation().renameTo(new File(fileName));
                        }
                    }
                    //记录文件上传的MAP对象
                    User currentUser  = SessionUtil.getInstance().getUserFromSession(servletRequest);
                    final String name = currentUser.getAccount().trim();//加入实际项目后,获取文件上传的系统用户名称,即登录用记名称,唯一性
                    FileUploadConstant.fileOperator.put(name, uploadFileNameStr);
                }
            }
        } catch (FileUploadException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        long et = System.currentTimeMillis();
        System.out.println("文件上传速度为:" + (Double.valueOf(lastfileLen)/(1024*1024))/(Double.valueOf(String.valueOf(et - st))/1000) + "兆/秒,即mb/s");
        /** 本机测试80M兆/秒速度情况
         * 文件上传速度为:113.34725895553785兆/秒,即mb/s
         * 文件上传速度为:118.41494564505518兆/秒,即mb/s
         * 文件上传速度为:117.71322744864004兆/秒,即mb/s
         * 文件上传速度为:120.38852807247277兆/秒,即mb/s
         * 文件上传速度为:118.76895146163233兆/秒,即mb/s
         */
        return upload.parseRequest(createRequestContext(servletRequest));
    }
    
    /**
     * 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 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();
                }
            }
        }
    }

}

=============================友情分隔===========================================

struts2.3.15.1以前版本:

<struts>
    <!-- 1配置自定义文件类myRequestParser,继承MultiPartRequest重写 -->
    <bean type="org.apache.struts2.dispatcher.multipart.MultiPartRequest"
        name="myRequestParser" class="com.vrv.paw.action.MyJakartaMultiPartRequest"
        scope="default" optional="true" />
    <constant name="struts.multipart.handler" value="myRequestParser" />

...................

</struts>

=============================友情分隔===========================================

com.vrv.paw.action.MyJakartaMultiPartRequest类:可到 http://download.csdn.net/detail/jun55xiu/7094731  下载



友情提示:具体项目代码可以见上面提到的地址去下载





你可能感兴趣的:(带进度条的文件上传)