本功能基于commons fileUpload 组件实现

首先,不能在程序中直接使用 fileUpload.parseRequest(request)的方式来获取 request 请求中的 multipartFile 文件对象,原因是因为在 spring 默认的文件上传处理器 multipartResolver 指向的类CommonsMultipartResolver 中就是通过 commons fileUpload 组件实现的文件获取,因此,在代码中再次使用该方法,是获取不到文件对象的,因为此时的 request 对象是不包含文件的,它已经被CommonsMultipartResolver 类解析处理并转型。

思考:

1、文件上传的过程其处理逻辑是怎样的? 

CommonsMultipartResolver转化请求的一段过程:

/**
	 * Parse the given servlet request, resolving its multipart elements.
	 * @param request the request to parse
	 * @return the parsing result
	 * @throws MultipartException if multipart resolution failed.
	 */
	protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException {
		String encoding = determineEncoding(request);
		FileUpload fileUpload = prepareFileUpload(encoding);
		try {
			List<FileItem> fileItems = ((ServletFileUpload) fileUpload).parseRequest(request);
			return parseFileItems(fileItems, encoding);
		}
		catch (FileUploadBase.SizeLimitExceededException ex) {
			throw new MaxUploadSizeExceededException(fileUpload.getSizeMax(), ex);
		}
		catch (FileUploadBase.FileSizeLimitExceededException ex) {
			throw new MaxUploadSizeExceededException(fileUpload.getFileSizeMax(), ex);
		}
		catch (FileUploadException ex) {
			throw new MultipartException("Failed to parse multipart servlet request", ex);
		}
	}

由于spring 中的 CommonsMultipartResolver 类中并没有加入 processListener 文件上传进度监听器,所以,直接使用 CommonsMultipartResolver 类是无法监听文件上传进度的,如果我们需要获取文件上传进度,就需要继承 CommonsMultipartResolver 类并重写 parseRequest 方法。

添加监听该监听器后,上传组件在上传文件的时会不断回调该方法,回传这些数据。利用这些数字,就可以计算出文件上传的进度,用进度条实时显示出来。

在此之前,我们需要创建一个实现了 processListener 接口的实现类用于监听文件上传进度。

UploadProcessListener接口实现类:

import javax.servlet.http.HttpSession;

import org.apache.commons.fileupload.ProgressListener;
import org.springframework.stereotype.Component;

@Component
public class UploadProgressListener implements ProgressListener {
    private HttpSession session;

    public void setSession(HttpSession session){
        this.session=session;
        ProgressEntity status = new ProgressEntity();
        session.setAttribute("status", status);
    }

    /* pBytesRead  到目前为止读取文件的比特数
     * pContentLength 文件总大小
     * pItems 目前正在读取第几个文件
     */
    public void update(long pBytesRead, long pContentLength, int pItems) {
        ProgressEntity status = (ProgressEntity) session.getAttribute("status");
        status.setPBytesRead(pBytesRead);
        status.setPContentLength(pContentLength);
        status.setPItems(pItems);
        System.out.println("UploadProgressListener update ProgressEntity:  "+status.toString());
    }
}

ProgressEntity 实体类:

@Component
public class ProgressEntity {
    // 读取的文件的比特数
    private long pBytesRead = 0L;
    // 文件的总大小
    private long pContentLength = 0L;
    // 目前正在读取第几个文件
    private int pItems;

    private long startTime = System.currentTimeMillis();

    public ProgressEntity() {
        pBytesRead = 0L;
        pContentLength = 0L;
    }

    public long getPBytesRead() {
        return pBytesRead;
    }

    public void setPBytesRead(long bytesRead) {
        pBytesRead = bytesRead;
    }

    public long getPContentLength() {
        return pContentLength;
    }

    public void setPContentLength(long contentLength) {
        pContentLength = contentLength;
    }

    public int getPItems() {
        return pItems;
    }

    public void setPItems(int items) {
        pItems = items;
    }

    @Override
    public String toString() {
        float tmp = (float) pBytesRead;
        float result = tmp / pContentLength * 100;
        return "ProgressEntity [pBytesRead=" + pBytesRead + ", pContentLength="
                + pContentLength + ", percentage=" + result + "% , pItems=" + pItems + "]";
    }
}

最后,是继承 CommonsMultipartResolver 类的自定义文件上传处理类:

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUpload;
import org.apache.commons.fileupload.FileUploadBase;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;

public class CustomMultipartResolver extends CommonsMultipartResolver {

    @Autowired
    private UploadProgressListener uploadProgressListener;

    @Override
    protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException {
        String encoding = determineEncoding(request);
        FileUpload fileUpload = prepareFileUpload(encoding);


        uploadProgressListener.setSession(request.getSession());// 文件上传进度监听器设置session用于存储上传进度
        fileUpload.setProgressListener(uploadProgressListener);// 将文件上传进度监听器加入到 fileUpload 中
        try {
            List<FileItem> fileItems = ((ServletFileUpload) fileUpload).parseRequest(request);
            return parseFileItems(fileItems, encoding);
        } catch (FileUploadBase.SizeLimitExceededException ex) {
            throw new MaxUploadSizeExceededException(fileUpload.getSizeMax(), ex);
        } catch (FileUploadBase.FileSizeLimitExceededException ex) {
            throw new MaxUploadSizeExceededException(fileUpload.getFileSizeMax(), ex);
        } catch (FileUploadException ex) {
            throw new MultipartException("Failed to parse multipart servlet request", ex);
        }
    }
}

此时,所有需要的类已经准备好,接下来我们需要将 spring 默认的文件上传处理类取消自动配置,并将 multipartResolver 指向我们刚刚创建好的继承 CommonsMultipartResolver 类的自定义文件上传处理类。

import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.multipart.MultipartResolver;

import com.baidu.doclabel.biz.listener.CustomMultipartResolver;


/*
 * exclude表示将 spring 默认的MultipartAutoConfiguration文件上传处理类取消自动配置
 * 注意:这里自定义了文件处理类,不加exclude,也会走我们自定义的
 */
// @EnableAutoConfiguration(exclude = {MultipartAutoConfiguration.class})
@EnableAutoConfiguration
@Configuration
public class UploadProgressApplication {
   // 将 multipartResolver 指向我们刚刚创建好的继承 CommonsMultipartResolver 类的 自定义文件上传处理类
    @Bean(name = "multipartResolver")
    public MultipartResolver multipartResolver() {
        CustomMultipartResolver customMultipartResolver = new CustomMultipartResolver();
        return customMultipartResolver;
    }
}

至此,准备工作完成,我们再创建一个测试用的 controller 

@Slf4j
@Controller
@Api(tags = {"文件上传test"})
@RequestMapping("/label/file/upload/test")
public class FileUploadController {

    @RequestMapping(value = "/upload", method = {RequestMethod.POST})
    public GenericBaseResponse useAbility(@Valid AbilityUseFileUpload request) {
        log.info("AbilityController getAbility, request={}", request);
        return new GenericBaseResponse(DoclabelErrorCode.SUCCESS);
    }
}

其中AbilityUseFileUpload类中有个参数是 :private List<MultipartFile> multipartFileList;

项目启动:

       注意下面我把自定义的multipartResolver又写了一遍,如果这个地方有写一遍的话,上面就不用定义UploadProgressApplication这个类了,如果想把启动类和定义的bean分开,只需要把下面这个方法的启动和定义multipartResolver这个bean分开就行。

/*
 * exclude表示将 spring 默认的MultipartAutoConfiguration文件上传处理类取消自动配置
 * 注意:这里自定义了文件处理类,不加exclude,也会走我们自定义的
 */
// @EnableAutoConfiguration(exclude = {MultipartAutoConfiguration.class})
@EnableAutoConfiguration
@Configuration
@ComponentScan("com.example")
@EnableDiscoveryClient
public class ApplicationRunner {
    public static void main(String[] args) {
        SpringApplication.run(ApplicationRunner.class, args);
    }


@Bean(name = "multipartResolver")
public MultipartResolver multipartResolver() {
	CustomMultipartResolver customMultipartResolver = new CustomMultipartResolver();
	return customMultipartResolver;
}

}

我是通过postman做的debug测试:

看到控制台输出的结果:

文件批量上传 获取不到 spring boot springboot文件上传进度_spring

........

文件批量上传 获取不到 spring boot springboot文件上传进度_spring_02

.........

文件批量上传 获取不到 spring boot springboot文件上传进度_自定义_03

 如果这时候前端想要获得当前的进度,可以给前端提供一个controller,里面的逻辑就是查询ProgressEntity里面的值。

或者另外一种方案就是通过webSocket的方式从后端推给前端实时的进度。

无论是哪种方式,都是一个定时的逻辑。