企业微信会话存档:大文件拉取、加密、上传

承接之前的文章企业微信会话存档sdk报错:A fatal error has been detected by the Java Runtime Environment

在之前的那篇文章结尾,我说发现了系统另外一个隐含的bug:企业微信会话存档拉取媒体文件是分片拉取,我的处理方式有问题导致,保存的永远是最后一个分片。

这个问题之所以没有在测试环境复现,是因为测试环境都是拿的一些小文件测试,结果在线上运行一段时间后,就发现,下载下来的文件显然比真实文件小很多。仔细排查了一下才发现是这个问题。

怎么解决呢?既然企业微信的sdk是分片拉取媒体文件的,那么我们就必需对内容进行汇总。但是汇总之前我们得判断文件的大小,如果文件不大,那么直接用ByteArrayOutputStream接收即可;但是如果文件很大,直接用ByteArrayOutputStream可能导致OOM,这里需要使用临时文件接收。

我使用的是weixin-java-cp这个库,所以代码用这个库的方法演示。

对于小文件,使用ByteArrayOutputStream接收:

java 复制代码
ByteArrayOutputStreamByteConsumer byteArrayOutputStreamByteConsumer = new ByteArrayOutputStreamByteConsumer(bos);
msgAuditService.getMediaFile(sdk, sdkFileId, null, null, 180L, byteArrayOutputStreamByteConsumer);

这里我们抽象了一个ByteArrayOutputStreamByteConsumer类,每拉一次分片都会调用这个类,往bos中写分片数据。

java 复制代码
public class ByteArrayOutputStreamByteConsumer implements Consumer<byte[]> {
    private final ByteArrayOutputStream bos;

    public ByteArrayOutputStreamByteConsumer(ByteArrayOutputStream bos) {
        this.bos = bos;
    }

    @Override
    public void accept(byte[] bytes) {
        try {
            bos.write(bytes);
        } catch (IOException e) {
            throw new MsgFileException("写入ByteArrayOutputStream失败", e);
        }
    }
}

如果文件比较大,我们需要写入临时文件,使用hutool的工具类创建临时文件。

java 复制代码
File tempFile = FileUtil.createTempFile("audit_media_", "." + StringUtils.defaultString(ext, "bin"), false);
msgAuditService.getMediaFile(sdk, sdkFileId, null, null, 180L, tempFile.getAbsolutePath());

至此,我们已经解决文件下载的问题,我们总结一下:

  • 小文件:用ByteArrayOutputStream接收
  • 大文件:用临时文件接收

下载问题只是第一个问题,还有两个问题等着我们。

第二个问题就是文件加密的问题。由于我们的项目媒体文件存在云存储,所以要求上传前必须加密,防止文件泄露。如果你们没有这个需要,那就忽略这个问题。

我之前的加密方式使用的是AES/ECB/PKCS5Padding这种模式,因为使用的是hutool的工具类,默认AES就是这个模式。

之前的做法是拿到文件的byte[],然后对它加密。如果文件很大,比如几个G,那很容易就OOM,为啥之前没有OOM呢,因为第一个问题导致文件内容只有最后一个分片的大小,所以这个问题被隐藏了。

所以现在就不能这么干了,可以使用AES/CTR/NoPadding这种模式代替,这种模式非常适合大文件的加解密。不用一次性传入整个文件的内容,可以流式的进行处理。算法的原理可以自行搜索,这里就不赘述了。

jdk提供的CipherOutputStream可以简化操作:

java 复制代码
try (is; CipherOutputStream cos = new CipherOutputStream(os, encryptCipher)) {
     while (true) {
         byte[] buffer = new byte[8192];

         int read = is.read(buffer, 0, 8192);
         if (read == -1) {
             break;
         }
         cos.write(buffer, 0, read); // 这里write的时候就会同时进行加密
     }
 } catch (IOException e) {
     throw new FileUploadException("文件加密过程失败", e);
 }

这里的encryptCipherCipher的实例:

java 复制代码
private Cipher getCipher(String key, byte[] ivBytes) {
    SecureRandom secureRandom = new SecureRandom();
    secureRandom.nextBytes(ivBytes);
    IvParameterSpec ivParameterSpec = new IvParameterSpec(ivBytes);

    SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
    Cipher encryptCipher;
    try {
        encryptCipher = Cipher.getInstance("AES/CTR/NoPadding");
        encryptCipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
    } catch (Exception e) {
        throw new FileUploadException("加密套件初始化失败", e);
    }
    return encryptCipher;
}

对于一个大文件,你可以创建一个临时文件来接收加密后的文件,以免OOM。

现在我们解决了第二个问题,大文件加密问题。接下来解决第三个问题,当前文件比较大时,上传非常慢。解决的办法,就是使用分片上传,这个要看各家云存储对分片上传的支持了。阿里的OSS,分片上传文档。

至此,问题完美解决。

相关推荐
私域实战笔记18 小时前
企业微信SCRM怎么选?工具适配与落地实操指南
人工智能·数据挖掘·企业微信·scrm·企业微信scrm
私域实战笔记18 小时前
企业微信SCRM工具该如何选择?从需求匹配出发的筛选思路
大数据·人工智能·企业微信·scrm·企业微信scrm
微盛企微增长小知识18 小时前
SCRM工具测评:助力企业微信私域运营的核心功能解析
大数据·人工智能·企业微信
hj104318 小时前
php上传企业微信附件的方法
开发语言·php·企业微信
重回19811 天前
企业微信可信IP配置的Python完美解决方案
网络协议·tcp/ip·企业微信
IT小哥哥呀2 天前
Node.js 实现企业内部消息通知系统(钉钉/企业微信机器人)
node.js·钉钉·企业微信·webhook·后端开发·自动化通知·mysql实战
自然 醒3 天前
企业微信自建应用开发详细教程,如何获取授权链接?如何使用js-sdk?
javascript·vue.js·企业微信
私域实战笔记5 天前
SCRM平台对比推荐:以企业微信私域运营需求为核心的参考
大数据·人工智能·企业微信·scrm·企业微信scrm
私域实战笔记7 天前
选企业微信服务商哪家好?从工具适配与行业案例看选型逻辑
大数据·人工智能·企业微信
AI企微观察7 天前
企业微信SCRM系统有什么作用,满足哪些功能?从获客到提效的功能适配逻辑
大数据·企业微信·scrm·企业微信scrm