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

承接之前的文章企业微信会话存档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,分片上传文档。

至此,问题完美解决。

相关推荐
hookserver3 天前
企业微信hook协议接口,聚合群聊客户管理工具开发
java·c语言·http·微信·企业微信·ipad·企微
欢迎来到祖安!8 天前
企业微信dll,最新版dll
企业微信
hj10438 天前
实战 element-plus 级联选择器(Cascader)+企微部门架构
企业微信
bjshinegood8 天前
企业微信批量发送:高效管理,精准触达的营销新策略
大数据·人工智能·企业微信
bjshinegood11 天前
企业微信自动回复机器人:提升客户服务与工作效率的智能新篇章
机器人·企业微信
MobTech袤博科技14 天前
ShareSDK 企业微信
macos·cocoa·企业微信
金刚钻信息15 天前
怎么管控终端电脑上的移动端口
数据加密·软件加密·文件加密·桌面管理
不知名运维:15 天前
Prometheus Alertmanager告警之邮件、钉钉群、企业微信群机器人报警
钉钉·企业微信·prometheus
weixin_Da冰21 天前
H5接入企微JS-SDK,使用wx.previewFile进行文件预览
开发语言·javascript·企业微信
金刚钻信息21 天前
企业常用的加密软件有哪些
数据加密·加密软件·文件加密