一.需求:前台通过多线程的方式多次调用后台下载视频接口,在短时间下载一个内存较大的视频。
二.实现原理:
1. 前台在下载视频的时候会创建一个原始视频总大小的临时文件,然后开启多线程,每个线程下载一个视频片段,每个下载完成的视频片段都会填充在临时文件的指定位置。所以该方法需要的参数是:sliceOffset切片视频的位置; sliceSize每个切片视频的大小。
2.输入流:
FileInputStream fis = new FileInputStream(file);
fis.skip(sliceOffset);//从指定位置读取字节
InputStream bis = new BufferedInputStream(fis);//输入流
输出流:
OutputStream toClient = new BufferedOutputStream(response.getOutputStream());//输出流
response.reset();//清除首部空白行
response.setContentType("application/octet-stream");//设置客户端接收的数据类型,这里是 二进制
response.setContentLength(sliceSize);//输出长度
具体代码如下:
1.控制层
@PostMapping(value = "downloadVideoSlice")
public ServerResponse<?> downloadVideoSlice(HttpServletRequest request, HttpServletResponse response) {
Integer stAutoId = Integer.parseInt(request.getParameter("stAutoId"));
Integer iId = Integer.parseInt(request.getParameter("iId"));
Integer sliceOffset = Integer.parseInt(request.getParameter("sliceOffset"));
Integer sliceSize = Integer.parseInt(request.getParameter("sliceSize"));
String path = (String) iImageService.downloadVideoSlice(stAutoId, iId).getData();
// 将文件转化成流输出
FileUtil.getOutputStream(path, response, sliceOffset, sliceSize);
return null;
}
2.实现类(涉及到业务,可以忽略这部分)
@Override
public ServerResponse<?> downloadVideoSlice(int stAutoId, int iId) {
Tstudy tstudy = tstudyMapper.selectByPrimaryKey((long) stAutoId);
Date stDate = tstudy.getStStartdate();
String stDate_str = DateTimeUtil.dateToStr(stDate, "yyyy-MM-dd HH:mm:ss");
VStorageNodeMethod method = CommonConfigCache.getVStorageNodeMethodByDate(stDate, tstudy.getStHospitalcode(),
StorageNodeType.IMAGE, StorageMethodType.DOWNLOAD, Method.HTTP.getType());
String httpHead = method.getSmnHttpVirtualPath();
String rootPath = method.getSnRootpath();
if (method == null) {
return ServerResponse.createByErrorMessage("查无此图");
}
// 获取图片表
String stDate_str1 = DateTimeUtil.dateToStr(stDate, "yyyyMMdd");
Timagecontrol timagecontrol = CommonConfigCache.getTimagecontrol("US", stDate_str1);
TimageExample timageExample = new TimageExample();
timageExample.createCriteria().andIIdEqualTo((long) iId);
timageExample.setOrderByClause("I_Number");
List<Timage> timages = timageMapper.selectByExampleWithTableName(timagecontrol.getcImagetablename(),
timageExample);
Timage timage = null;
if (CollectionUtils.isNotEmpty(timages) && timages.size() > 0) {
timage = timages.get(0);
}
String serverpath = timage.getiServerpath();
String filePath = rootPath + serverpath.replace("/", "\\");
return ServerResponse.createBySuccess(filePath);
}
3.工具类(重点)
/**
* 将文件转化成流输出
*
* @param path
* @param response
* @param sliceOffset
* @param sliceSize
*/
public static void getOutputStream(String path, HttpServletResponse response, Integer sliceOffset,
Integer sliceSize) {
File file = new File(path);
try {
FileInputStream fis = new FileInputStream(file);
fis.skip(sliceOffset);//从指定位置读取字节
InputStream bis = new BufferedInputStream(fis);//输入流
OutputStream toClient = new BufferedOutputStream(response.getOutputStream());//输出流
response.reset();//清除首部空白行
response.setContentType("application/octet-stream");//设置客户端接收的数据类型,这里是 二进制
response.setContentLength(sliceSize);//输出长度
// 数据缓冲区
byte[] arr = new byte[1024];
int len;
long count = 0;
// 数据读写
while ((len = bis.read(arr)) != -1) {
// 分三种情况
if (len + count > sliceSize) {
// 1.当读取的时候操作自己该线程的下载总量的时候,需要改变len
len = (int) (sliceSize - count);
toClient.write(arr, 0, len);
// 证明该线程下载任务已经完毕,结束读写操作
break;
} else if (len + count < sliceSize) {
// 2.证明还没有到下载总量,直接将内容写入
toClient.write(arr, 0, len);
// 并且使计数器任务累加
count += arr.length;
} else {
// 3.证明改好到下载总量
toClient.write(arr, 0, len);
// 结束读写
break;
}
}
toClient.close();
bis.close();
fis.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
} ```