阿里云视频上传视频获取进度条问题(使用session方案,获取进度一直为0的解决方案)补充:前后端分离项目中获取进度解决方案

1.场景描述:
之前用阿里云上传视频,前端反应上传视频经常出现获取视频url失败问题.但是接口我测过很多遍都是没有问题的.后台这边提供了一个视频上传的接口返回一个videoId,还提供了一个根据videoId获取视频url的接口.前端把两个接口的调用都封装在一个方法中,视频上传完成之后直接根据视频id获取视频url.这样会出现一个问题,视频文件如果时间太长,所以前端页面会请求超时,所以会上传失败.商量了一下就是将接口分开来,页面上除了有一个上传的接口,单独提供一个点击查看上传视频的按钮,分别调用两个接口.但是这样问题又来了,如何保证用户上传之后再点击查看按钮调用根据视频id获取视频url的接口,解决方案就是添加一个进度条,显示一下阿里云上传文件的进度.
2.添加上传进度过程中遇到的问题以及解决方案
①阿里云官方的api中有进度回调方法,但是只能是从服务端获取到,如何实时传递到浏览器端.自己想到的方案:第一种是使用redis存储这种随时变化的数据(由于项目中没有使用redis,所以鉴于搭建redis环境考虑就果断放弃此方案);第二种是使用数据库存储(由于对数据库添加以及删除操作过于频繁所有放弃此方案);第三方就是数据存储于服务器端的session解决方案更适合本场景.这也是网上使用很多的方案.具体实现逻辑:除了原有两个接口之外需要添加一个获取上传进度的接口,页面添加一个定时器需要定时的请求此接口刷新进度条,实现上传进度显示.
②技术方案定好之后遇到的问题:每次调用获取进度的接口返回的进度都是0,但是实际上视频都是正常上传的,上传的进度都能通过日志进行打印.阿里云进度监听的类中确实把进度存入session中了.原因:调试发现请求进度接口中的session与上传文件的请求中根据request获取的session不是一个session对象.这就导致每次从session中获取都是0,这里需要说一下session的获取问题.
2.1首先说一下使用request获取session的方式以及区别:
request.getSession(true):若存在会话则返回该会话,否则新建一个会话(request.getSession()默认就是true,通过查看源码可见)。
request.getSession(false):若存在会话则返回该会话,否则返回NULL;
2.2获取session原理:浏览器与服务端交互时,第一次发送请求时会创建session对象,返回JssionId信息并缓存在浏览器中cookie中,下次发送请求会携带该jssionId,使用request获取session对象时会根据是否有jsessionId判断直接获取session对象还是直接创建.
2.3至于两个请求中request如何保证session对象唯一在于:上传文件接口中虽然使用request获取session并往session中添加进度信息,但是此时调用获取进度接口中根据request获取session对象由于不存在JssionId(因为上传接口还未结束,不会响应JssionId信息),所以会重新创建一个session对象.
2.4此问题的解决方案就是保证两个接口通过request获取的session一致,我想到的解决方案就是,先调用获取进度接口,此时返回的进度是0,但是浏览器也缓存了jessionId信息,然后调用文件上传接口,请求中会携带该jessionId,此时文件上传接口中使用request获取的session一定与获取进度接口中的session对象一致.当然要注意在文件上传成功之后请求session信息,否则下次请求进度接口显示的进度是从百分之百开始的了.此处使用的是session自杀方式:session.invalidate();
3.部分主要代码:
文件上传逻辑:

public ResultVo uploadVideo(String title,String fileName,HttpServletRequest httpServletRequest) {
    
    

        ResultVo resultVo = new ResultVo();
        // 判空处理
        if(StringUtils.isEmpty(title)||StringUtils.isEmpty(fileName)){
    
    
            resultVo.setSuccess(false);
            resultVo.setMsg("视频上传失败:视频标题或是视频文件路径为空");
            return resultVo;
        }
        // 文件类型校验
        // 对上传的视频格式进行校验: 这里简单校验后缀名
        boolean isLegal = false;
        for (String type : VIDEO_TYPE) {
    
    
            if (StringUtils.endsWithIgnoreCase(fileName, type)) {
    
    
                isLegal = true;
                break;  // 只要与允许上传格式其中一个匹配就可以
            }
        }
        // 格式错误, 返回与前端约定的error
        if (!isLegal) {
    
    
            resultVo.setSuccess(false);
            resultVo.setMsg("视频上传失败:"+"视频格式错误,支持格式:.mp4,.rmvb,.rm,.avi,.flv");
            return resultVo;
        }
        UploadVideoRequest request = new UploadVideoRequest(aliyunConfig.getVideoAccessKeyId(), aliyunConfig.getVideoAccessKeySecret(), title, fileName);
        /* 可指定分片上传时每个分片的大小,默认为1M字节 */
        //request.setPartSize(1 * 1024 * 1024L);
        /* 可指定分片上传时的并发线程数,默认为1,(注:该配置会占用服务器CPU资源,需根据服务器情况指定)*/
        //request.setTaskNum(1);
        /* 是否开启断点续传, 默认断点续传功能关闭。当网络不稳定或者程序崩溃时,再次发起相同上传请求,可以继续未完成的上传任务,适用于超时3000秒仍不能上传完成的大文件。
        注意: 断点续传开启后,会在上传过程中将上传位置写入本地磁盘文件,影响文件上传速度,请您根据实际情况选择是否开启*/
        //request.setEnableCheckpoint(false);
        /* OSS慢请求日志打印超时时间,是指每个分片上传时间超过该阈值时会打印debug日志,如果想屏蔽此日志,请调整该阈值。单位: 毫秒,默认为300000毫秒*/
        //request.setSlowRequestsThreshold(300000L);
        /* 可指定每个分片慢请求时打印日志的时间阈值,默认为300s*/
        //request.setSlowRequestsThreshold(300000L);
        /* 是否使用默认水印(可选),指定模板组ID时,根据模板组配置确定是否使用默认水印*/
        //request.setIsShowWaterMark(true);
        /* 自定义消息回调设置(可选),参数说明参考文档 https://help.aliyun.com/document_detail/86952.html#UserData */
        // request.setUserData("{\"MessageCallback\":{\"CallbackURL\":\"http://hicgu6.natappfree.cc/course/list?pageCurrent=1&pageNum=2\"}}");
        /* 视频分类ID(可选) */
        //request.setCateId(0);
        /* 视频标签,多个用逗号分隔(可选) */
        //request.setTags("标签1,标签2");
        /* 视频描述(可选) */
        //request.setDescription("视频描述");
        /* 封面图片(可选) */
        //request.setCoverURL("http://cover.sample.com/sample.jpg");
        /* 模板组ID(可选) */
        //request.setTemplateGroupId("8c4792cbc8694e7084fd5330e56a33d");
        /* 存储区域(可选) */
        //request.setStorageLocation("https://yujia-shanghai-bucket.oss-cn-shanghai.aliyuncs.com/kawa2/course-image/");
        // request.setStorageLocation("outin-7908e57a68eb11e9a8d800163e1a625e.oss-cn-shanghai.aliyuncs.com");
        request.setStorageLocation(aliyunConfig.getVideoLocation());
        /* 开启默认上传进度回调 */
         request.setPrintProgress(true);
        /* 设置自定义上传进度回调 (必须继承 ProgressListener) */
        HttpSession session = httpServletRequest.getSession();
        request.setProgressListener(new PutObjectProgressListener(session));
        UploadVideoImpl uploader = new UploadVideoImpl();
        UploadVideoResponse response = uploader.uploadVideo(request);
        //System.out.print("RequestId=" + response.getRequestId() + "\n");  //请求视频点播服务的请求ID
        if (response.isSuccess()) {
    
    
            resultVo.setSuccess(true);
            resultVo.setMsg("视频上传成功");
            //resultVo.setData("videoId:"+response.getVideoId()+",requestId:"+response.getRequestId());
            resultVo.setData("videoId:"+response.getVideoId());
            // 视频上传成功之后清除session对象
            session.invalidate();

        } else {
    
    
            /* 如果设置回调URL无效,不影响视频上传,可以返回VideoId同时会返回错误码。其他情况上传失败时,VideoId为空,此时需要根据返回错误码分析具体错误原因 */
            resultVo.setSuccess(false);
            resultVo.setMsg("视频上传失败");
            resultVo.setData("VideoId=" + response.getVideoId()+",ErrorCode=" + response.getCode()+",ErrorMessage=" + response.getMessage());
        }
        return resultVo;
    }

阿里云进度回调逻辑:

public class PutObjectProgressListener implements VoDProgressListener {
    
    
    /**
     * 已成功上传至OSS的字节数
     */
    private long bytesWritten = 0;
    /**
     * 原始文件的总字节数
     */
    private long totalBytes = -1;
    /**
     * 本次上传成功标记
     */
    private boolean succeed = false;
    /**
     * 视频ID
     */
    private String videoId;
    /**
     * 图片ID
     */
    private String imageId;

    // 使用session存储文件上传进度
    private HttpSession session;
    private int uploadPercent = 0;

    //构造方法中加入session
    public PutObjectProgressListener(){
    
    }
    public PutObjectProgressListener(HttpSession mSession) {
    
    
        this.session = mSession;
        session.setAttribute("upload_percent", uploadPercent);
    }


    public void progressChanged(ProgressEvent progressEvent)  {
    
    
        long bytes = progressEvent.getBytes();
        ProgressEventType eventType = progressEvent.getEventType();
        switch (eventType) {
    
    
            // 开始上传事件
            case TRANSFER_STARTED_EVENT:
                if (videoId != null) {
    
    
                    System.out.println("Start to upload videoId " + videoId + "......");
                }
                if (imageId != null) {
    
    
                    System.out.println("Start to upload imageId " + imageId + "......");
                }
                break;
            // 计算待上传文件总大小事件通知,只有调用本地文件方式上传时支持该事件
            case REQUEST_CONTENT_LENGTH_EVENT:
                this.totalBytes = bytes;
                System.out.println(this.totalBytes + "bytes in total will be uploaded to OSS.");
                break;
            // 已经上传成功文件大小事件通知
            case REQUEST_BYTE_TRANSFER_EVENT:
                this.bytesWritten += bytes;
                if (this.totalBytes != -1) {
    
    
                    int percent = (int) (this.bytesWritten * 100.0 / this.totalBytes);
                    // 将进度添加到session中
                    //将进度percent放入session中
                    session.setAttribute("upload_percent", percent);
                    System.out.println(bytes + " bytes have been written at this time, upload progress: " +
                            percent + "%(" + this.bytesWritten + "/" + this.totalBytes + ")");
                } else {
    
    
                    System.out.println(bytes + " bytes have been written at this time, upload sub total : " +
                            "(" + this.bytesWritten + ")");
                }
                break;
            // 文件全部上传成功事件通知
            case TRANSFER_COMPLETED_EVENT:
                this.succeed = true;
                if (videoId != null) {
    
    
                    System.out.println("Succeed to upload videoId " + videoId + " , " + this.bytesWritten + " bytes have been transferred in total.");
                }
                if (imageId != null) {
    
    
                    System.out.println("Succeed to upload imageId " + imageId + " , " + this.bytesWritten + " bytes have been transferred in total.");
                }
                break;
            // 文件上传失败事件通知
            case TRANSFER_FAILED_EVENT:
                if (videoId != null) {
    
    
                    System.out.println("Failed to upload videoId " + videoId + " , " + this.bytesWritten + " bytes have been transferred.");
                }
                if (imageId != null) {
    
    
                    System.out.println("Failed to upload imageId " + imageId + " , " + this.bytesWritten + " bytes have been transferred.");
                }
                break;
            default:
                break;
        }
    }
    public boolean isSucceed() {
    
    
        return succeed;
    }
    public void onVidReady(String videoId) {
    
    
        setVideoId(videoId);
    }
    public void onImageIdReady(String imageId) {
    
    
        setImageId(imageId);
    }
    public String getVideoId() {
    
    
        return videoId;
    }
    public void setVideoId(String videoId) {
    
    
        this.videoId = videoId;
    }
    public String getImageId() {
    
    
        return imageId;
    }
    public void setImageId(String imageId) {
    
    
        this.imageId = imageId;
    }
}
请求上传进度接口:
  @RequestMapping ("/percent")
    @ResponseBody
    public int getUploadPercent(HttpServletRequest request){
    
    
        HttpSession session = request.getSession();
        int percent = session.getAttribute("upload_percent") == null ? 0:  (Integer)session.getAttribute("upload_percent");
        return percent;
    }

根据文件上传成功返回的视频id获取视频url接口暂不提供,可参考阿里云api;
4.调用逻辑说明:
调用请求进度接口(确保session对象唯一)–>前端页面调用文件上传接口–>定时请求请求进度接口,实时显示上传进度.–>根据视频id获取视频url接口(视频上传完成之后查看上传视频内容)
5.补充一种使用场景(前后端分离项目)
上面说的解决方案适用于前后端一体项目,如果前后端分离的项目介绍一下我的解决方案:采用redis(此处使用jedis客户端进行存取).直接上代码
文件上传代码:

public ResultVo uploadVideo(String title,String fileName,HttpServletRequest httpServletRequest) {
    
    

        ResultVo resultVo = new ResultVo();
        // 判空处理
        if(StringUtils.isEmpty(title)||StringUtils.isEmpty(fileName)){
    
    
            resultVo.setSuccess(false);
            resultVo.setMsg("视频上传失败:视频标题或是视频文件路径为空");
            return resultVo;
        }
        // 文件类型校验
        // 对上传的视频格式进行校验: 这里简单校验后缀名
        boolean isLegal = false;
        for (String type : VIDEO_TYPE) {
    
    
            if (StringUtils.endsWithIgnoreCase(fileName, type)) {
    
    
                isLegal = true;
                break;  // 只要与允许上传格式其中一个匹配就可以
            }
        }
        // 格式错误, 返回与前端约定的error
        if (!isLegal) {
    
    
            resultVo.setSuccess(false);
            resultVo.setMsg("视频上传失败:"+"视频格式错误,支持格式:.mp4,.rmvb,.rm,.avi,.flv");
            return resultVo;
        }
        UploadVideoRequest request = new UploadVideoRequest(aliyunConfig.getVideoAccessKeyId(), aliyunConfig.getVideoAccessKeySecret(), title, fileName);
        /* 可指定分片上传时每个分片的大小,默认为1M字节 */
        //request.setPartSize(1 * 1024 * 1024L);
        /* 可指定分片上传时的并发线程数,默认为1,(注:该配置会占用服务器CPU资源,需根据服务器情况指定)*/
        //request.setTaskNum(1);
        /* 是否开启断点续传, 默认断点续传功能关闭。当网络不稳定或者程序崩溃时,再次发起相同上传请求,可以继续未完成的上传任务,适用于超时3000秒仍不能上传完成的大文件。
        注意: 断点续传开启后,会在上传过程中将上传位置写入本地磁盘文件,影响文件上传速度,请您根据实际情况选择是否开启*/
        //request.setEnableCheckpoint(false);
        /* OSS慢请求日志打印超时时间,是指每个分片上传时间超过该阈值时会打印debug日志,如果想屏蔽此日志,请调整该阈值。单位: 毫秒,默认为300000毫秒*/
        //request.setSlowRequestsThreshold(300000L);
        /* 可指定每个分片慢请求时打印日志的时间阈值,默认为300s*/
        //request.setSlowRequestsThreshold(300000L);
        /* 是否使用默认水印(可选),指定模板组ID时,根据模板组配置确定是否使用默认水印*/
        //request.setIsShowWaterMark(true);
        /* 自定义消息回调设置(可选),参数说明参考文档 https://help.aliyun.com/document_detail/86952.html#UserData */
        // request.setUserData("{\"MessageCallback\":{\"CallbackURL\":\"http://hicgu6.natappfree.cc/course/list?pageCurrent=1&pageNum=2\"}}");
        /* 视频分类ID(可选) */
        //request.setCateId(0);
        /* 视频标签,多个用逗号分隔(可选) */
        //request.setTags("标签1,标签2");
        /* 视频描述(可选) */
        //request.setDescription("视频描述");
        /* 封面图片(可选) */
        //request.setCoverURL("http://cover.sample.com/sample.jpg");
        /* 模板组ID(可选) */
        //request.setTemplateGroupId("8c4792cbc8694e7084fd5330e56a33d");
        /* 存储区域(可选) */
        //request.setStorageLocation("https://yujia-shanghai-bucket.oss-cn-shanghai.aliyuncs.com/kawa2/course-image/");
        // request.setStorageLocation("outin-7908e57a68eb11e9a8d800163e1a625e.oss-cn-shanghai.aliyuncs.com");
        request.setStorageLocation(aliyunConfig.getVideoLocation());
        /* 开启默认上传进度回调 */
         request.setPrintProgress(true);
        /* 设置自定义上传进度回调 (必须继承 ProgressListener) */
        request.setProgressListener(new PutObjectProgressListener());
        UploadVideoImpl uploader = new UploadVideoImpl();
        UploadVideoResponse response = uploader.uploadVideo(request);
        //System.out.print("RequestId=" + response.getRequestId() + "\n");  //请求视频点播服务的请求ID
        if (response.isSuccess()) {
    
    
            resultVo.setSuccess(true);
            resultVo.setMsg("视频上传成功");
            //resultVo.setData("videoId:"+response.getVideoId()+",requestId:"+response.getRequestId());
            resultVo.setData("videoId:"+response.getVideoId());

        } else {
    
    
            /* 如果设置回调URL无效,不影响视频上传,可以返回VideoId同时会返回错误码。其他情况上传失败时,VideoId为空,此时需要根据返回错误码分析具体错误原因 */
            resultVo.setSuccess(false);
            resultVo.setMsg("视频上传失败");
            resultVo.setData("VideoId=" + response.getVideoId()+",ErrorCode=" + response.getCode()+",ErrorMessage=" + response.getMessage());
        }
        return resultVo;
    }

进度回调类中将进度添加到缓存中:

public class PutObjectProgressListener implements VoDProgressListener {
    
    
    /**
     * 已成功上传至OSS的字节数
     */
    private long bytesWritten = 0;
    /**
     * 原始文件的总字节数
     */
    private long totalBytes = -1;
    /**
     * 本次上传成功标记
     */
    private boolean succeed = false;
    /**
     * 视频ID
     */
    private String videoId;
    /**
     * 图片ID
     */
    private String imageId;

    // add by txm 2020/11/19 视频上传进度使用redis方案解决 
    private Jedis jedis = new Jedis("redisIP", redis端口);


    public void progressChanged(ProgressEvent progressEvent)  {
    
    
        long bytes = progressEvent.getBytes();
        ProgressEventType eventType = progressEvent.getEventType();
        switch (eventType) {
    
    
            // 开始上传事件
            case TRANSFER_STARTED_EVENT:
                if (videoId != null) {
    
    
                    System.out.println("Start to upload videoId " + videoId + "......");
                }
                if (imageId != null) {
    
    
                    System.out.println("Start to upload imageId " + imageId + "......");
                }
                break;
            // 计算待上传文件总大小事件通知,只有调用本地文件方式上传时支持该事件
            case REQUEST_CONTENT_LENGTH_EVENT:
                this.totalBytes = bytes;
                System.out.println(this.totalBytes + "bytes in total will be uploaded to OSS.");
                break;
            // 已经上传成功文件大小事件通知
            case REQUEST_BYTE_TRANSFER_EVENT:
                this.bytesWritten += bytes;
                if (this.totalBytes != -1) {
    
    
                    int percent = (int) (this.bytesWritten * 100.0 / this.totalBytes);
                    System.out.println(bytes + " bytes have been written at this time, upload progress: " +
                            percent + "%(" + this.bytesWritten + "/" + this.totalBytes + ")");
                    // add by txm 2020/11/19 redis解决视频上传进度问题
                    jedis.set("progressData",String.valueOf(percent));
                } else {
    
    
                    System.out.println(bytes + " bytes have been written at this time, upload sub total : " +
                            "(" + this.bytesWritten + ")");
                }
                break;
            // 文件全部上传成功事件通知
            case TRANSFER_COMPLETED_EVENT:
                this.succeed = true;
                if (videoId != null) {
    
    
                    System.out.println("Succeed to upload videoId " + videoId + " , " + this.bytesWritten + " bytes have been transferred in total.");
                }
                if (imageId != null) {
    
    
                    System.out.println("Succeed to upload imageId " + imageId + " , " + this.bytesWritten + " bytes have been transferred in total.");
                }
                break;
            // 文件上传失败事件通知
            case TRANSFER_FAILED_EVENT:
                if (videoId != null) {
    
    
                    System.out.println("Failed to upload videoId " + videoId + " , " + this.bytesWritten + " bytes have been transferred.");
                }
                if (imageId != null) {
    
    
                    System.out.println("Failed to upload imageId " + imageId + " , " + this.bytesWritten + " bytes have been transferred.");
                }
                break;
            default:
                break;
        }
    }
    public boolean isSucceed() {
    
    
        return succeed;
    }
    public void onVidReady(String videoId) {
    
    
        setVideoId(videoId);
    }
    public void onImageIdReady(String imageId) {
    
    
        setImageId(imageId);
    }
    public String getVideoId() {
    
    
        return videoId;
    }
    public void setVideoId(String videoId) {
    
    
        this.videoId = videoId;
    }
    public String getImageId() {
    
    
        return imageId;
    }
    public void setImageId(String imageId) {
    
    
        this.imageId = imageId;
    }
}

控制层获取缓存中进度的逻辑:

private Jedis jedis = new Jedis("redis服务器IP", redis服务器端口);
@RequestMapping ("/percent")
    @ResponseBody
    public ResultVo getUploadPercent(HttpServletRequest request){
    
    
        String progressData = jedis.get("progressData");
        return ResultVoUtil.success(progressData,"获取上传进度成功");
    }

前后端分离的项目进行数据共享的方式还有很多,有时间会进行更新说明.

猜你喜欢

转载自blog.csdn.net/weixin_43401380/article/details/109517230