监听一个接口文件上传的进度

public R<String> uploadDeviceLogFile(@RequestParam(value = "playCode", required = true) String playCode,
                                      @RequestParam(value = "taskId", required = false) String taskId,
                                      //taskId  唯一标识  作为key存放进度至redis
                                      @RequestPart(value = "file") MultipartFile file){
        // 确认日志
        String url = logFileService.uploadDeviceLogFile(playCode, taskId, file);
        return R.success(url);
		
    }

本功能基于commons fileUpload 组件实现

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

2.由于spring 中的 CommonsMultipartResolver 类中并没有加入 processListener 文件上传进度监听器,所以,直接使用 CommonsMultipartResolver 类是无法监听文件上传进度的,如果我们需要获取文件上传进度,就需要继承 CommonsMultipartResolver 类并重写 parseRequest 方法,在此之前,我们需要创建一个实现了 processListener 接口的实现类用于监听文件上传进度。

/**
 * 用于监听文件上传进度
 * @author X22020
 */
@Slf4j
@Component
public class UploadProgressListener implements ProgressListener {
	
	@Autowired
    private  HttpServletRequest request;

    @Resource
    private RedisUtil redisUtil;

    /**
     * pBytesRead  到目前为止读取文件的比特数
     * pContentLength 文件总大小
     * pItems 目前正在读取第几个文件
     */
    public void update(long pBytesRead, long pContentLength, int pItems) {
        BigDecimal bg = BigDecimal.valueOf((double) pBytesRead  / (double) pContentLength * Constants.MAX_PERCENTAGE);
        double percentage = bg.setScale(2, RoundingMode.HALF_UP).doubleValue();
        //从request中获取唯一标识
        String taskId = request.getParameter("taskId");
       
        //将taskId作为key,上传进度同步至redis
        String advertLockKey = Constants.LOG_UPLOAD_PROGRESS_LOCK_KEY.format(new String[]{taskId});
        redisUtil.set(advertLockKey,String.valueOf(percentage));
    }

最后,是继承 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);
        // 文件上传进度监听器设置session用于存储上传进度
        //改进  不需要
   //     uploadProgressListener.setRequest(request);
        // 将文件上传进度监听器加入到 fileUpload 中
        fileUpload.setProgressListener(uploadProgressListener);
        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 类的自定义文件上传处理类。

/**
 * exclude表示将 spring 默认的MultipartAutoConfiguration文件上传处理类取消自动配置
 * 注意:这里自定义了文件处理类,不加exclude,也会走我们自定义的
 * @author X22020
 */

@EnableAutoConfiguration
@Configuration
public class UploadProgressApplication {
    /**
     *  将 multipartResolver 指向我们刚刚创建好的继承 CommonsMultipartResolver 类的 自定义文件上传处理类
     * @return
     */
    @Bean(name = "multipartResolver")
    public MultipartResolver multipartResolver() {
        return new CustomMultipartResolver();
    }
}

获取进度条接口

@GetMapping("/getDeviceLogPercentage")
    @ApiOperation(value = "查询设备端上报日志进度")
    public R<DeviceLogPercentageVO> queryDeviceLogPercentage(@RequestParam(value = "taskId", required = true) String taskId){
        return R.success(logService.queryDeviceLogPercentage(taskId));
    }
@Override
    public DeviceLogPercentageVO queryDeviceLogPercentage(String taskId){
        DeviceLogPercentageVO vo = new DeviceLogPercentageVO();

        //判断日志获取是否超时
        String requestTimeKey = Constants.REQUEST_TIME_KEY.format(new String[]{taskId});
        Object requestTime = redisUtil.get(requestTimeKey);
        if (requestTime == null){
            throw new UnprocessableException(AdvertEnum.DATABASE_DATA_TRUNCATION);
        }

        if (System.currentTimeMillis() - (long)requestTime > Constants.HALF_HOUR ){
            int update = deviceLogMapper.updateStatus(taskId);
            if (update == 1) {
                vo.setStatus(3);
                vo.setSchedule(Constants.LOG_GET_FAILED);
                return vo;
            }
        }

        //从redis获取进度
        String advertLockKey = Constants.LOG_UPLOAD_PROGRESS_LOCK_KEY.format(new String[]{taskId});
        Object percentages = redisUtil.get(advertLockKey);
        if (percentages == null){
            throw new UnprocessableException(AdvertEnum.DATABASE_DATA_TRUNCATION);
        }

        String percentage = String.valueOf(percentages);

        //日志已上报S3
        if (percentage.equals(Constants.LOG_GET_SUCCESS)){
            //查询日志信息
            QueryWrapper<DeviceLogEntity> query = new QueryWrapper<DeviceLogEntity>()
                    .eq("task_id", taskId);
            DeviceLogEntity deviceLogEntity = deviceLogMapper.selectOne(query);
            if (deviceLogEntity == null){
                throw new UnprocessableException(AdvertEnum.DATABASE_DATA_TRUNCATION);
            }
            log.info("queryDeviceLogPercentage.deviceLogEntity :{}", JSON.toJSONString(deviceLogEntity));

            BeanUtils.copyProperties(deviceLogEntity,vo);
            String logUrl = deviceLogEntity.getLogUrl();
            if (logUrl != null){
                int index = logUrl.indexOf("_");
                vo.setLogName(logUrl.substring(index + 1));
            }
            return vo;
        }
        //服务器上传S3
        double parseDouble = Double.parseDouble(percentage);
        if (parseDouble < Constants.LOG_MAX_PERCENTAGE) {
                vo.setSchedule(percentage);
        } else{
                vo.setSchedule(Constants.LOG_PERCENTAGE);
        }
        return vo;