Java分片上传全解决方案,真香

165 阅读3分钟

image.png

“持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第20天,点击查看活动详情

因为我们是做语音识别的,在离线的情况下,需要上传语音,但是因为语音很大,

所以需要拆分进行上传,要不然如果网络波动就前功尽弃了,今天就来复述下这个方案

1、解决方案

我们的方案是

第一步需要创建转写任务,返回给前段一个taskId。

第二步就是分片上传语音,每次只能上传一片语音,并且是顺序上传。

在最后一片上传完成后进行合成并且执行任务。如果失败了下次继续上传就可以了

2、show code

看一下我们分片上传的时候一些请求参数。

主要的参数有两个。

一个是sliceIndex,表示当前是第几个分片

一个是sliceTotal,表示总共有多少个分片,在最后一个的时候触发合并任务。

/**
 * 产品id 必传
 */
private String productId;
/**
 * 任务Id
 */
private String taskId;
/**
 * 文件名
 */
private String fileName;
/**
 * 当前分片的索引 从 1 开始
 */
private Integer sliceIndex;
/**
 * 总共多少片 最后一个会触发合并任务
 */
private Integer sliceTotal;
下面是合并文件的代码:

/**
 * 合并文件夹下的文件
 *
 * @param fold 所在文件夹
 * @param fileName 原文件的名字
 * @return
 * @throws Exception
 */
public static String mergeFiles(File fold, String fileName) throws Exception {
    if (fold == null) {
        throw new BadRequestException("参数错误");
    }
    File[] files = fold.listFiles();
    if (files == null || files.length == 0) {
        throw new BadRequestException("参数错误");
    }
    File resultFile = new File(fold, fileName);
    if (files.length == 1) {
        files[0].renameTo(resultFile);
        return resultFile.getAbsolutePath();
    }
    List<File> collect = Arrays.stream(files)
            .filter(f -> f.getName().lastIndexOf("_") != -1)
            .sorted((o1, o2) -> {
                int idx1 = Integer.parseInt(o1.getName().substring(o1.getName().lastIndexOf("_") + 1));
                int idx2 = Integer.parseInt(o2.getName().substring(o2.getName().lastIndexOf("_") + 1));
                return idx1 - idx2;
            }).collect(Collectors.toList());
    FileChannel resultFileChannel = new FileOutputStream(resultFile, true).getChannel();
    for (File f : collect) {
        FileChannel blk = new FileInputStream(f).getChannel();
        resultFileChannel.transferFrom(blk, resultFileChannel.size(), blk.size());
        blk.close();
    }
    resultFileChannel.close();
    for (int i = 0; i < files.length; i++) {
        files[i].delete();
    }
    return resultFile.getAbsolutePath();
}

3、技术解析

知识点一:

如果上传的文件只有一个,直接进行改名就可以了。不需要合成

知识点二:

因为在上传的时候,每个分天的保存在后缀加索引,是为了在合成的时候进行顺序合成。

所以在合成之前需要先对文件进行排序

知识点三:

这里直接使用了FileChannel,比原来的stream效率更高

知识点四:

FileChannel要及时关闭。原来的分片任务这里进行了删除。

4、测试

一般情况下,分片上传是由前端调用。但是在开发的过程中,需要我们自己去测试接口。所以需要自己去切分文件。

方案一:

在最初的时候,我拿了一个小文件。大概2M,直接使用sublime打开。从其中拷贝字节。存到另外一个文件里。相当于进行了切分。

方案二:

自己写程序进行切换,这里将文件切分成1M大小。

public static void splitFile() throws IOException {
    FileInputStream fileInputStream = new FileInputStream(new File("C:\\Users\\xin.chong\\Desktop\\aispeech-aichat\\明月几时有2.wav"));
    byte[] _1m = new byte[1024*1024];
    int index = 0;
    while (fileInputStream.read(_1m) != -1){
        String path  = "C:\\Users\\xin.chong\\Desktop\\aispeech-aichat\\明月几时有2.wav" +"__"+ index ++;
        new FileOutputStream(new File(path)).write(_1m);
    }
    fileInputStream.close();
}

总结:

文件的合并注意点比较多,但是切分没太多注意事项,因为那是前端的事情,只要不影响测试就好