diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMsgAuditService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMsgAuditService.java index 69ecfa23d4..221caf2e70 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMsgAuditService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMsgAuditService.java @@ -5,6 +5,7 @@ import me.chanjar.weixin.cp.bean.msgaudit.*; import java.util.List; +import java.util.function.Consumer; /** * 会话内容存档接口. @@ -72,6 +73,22 @@ WxCpChatModel getDecryptData(@NonNull long sdk, @NonNull WxCpChatDatas.WxCpChatD void getMediaFile(@NonNull long sdk, @NonNull String sdkfileid, String proxy, String passwd, @NonNull long timeout, @NonNull String targetFilePath) throws WxErrorException; + /** + * 获取媒体文件 传入一个lambda,each所有的数据分片byte[],更加灵活 + * 针对图片、文件等媒体数据,提供sdk接口拉取数据内容。 + * 详情可以看官方文档,亦可阅读此接口源码。 + * + * @param sdk getChatDatas()获取到的sdk,注意,每次获取的sdk会不一样 + * @param sdkfileid 消息体内容中的sdkfileid信息 + * @param proxy 使用代理的请求,需要传入代理的链接。如:socks5://10.0.0.1:8081 或者 http://10.0.0.1:8081,如果没有传null + * @param passwd 代理账号密码,需要传入代理的账号密码。如 user_name:passwd_123,如果没有传null + * @param timeout 超时时间,分片数据需累加到文件存储。单次最大返回512K字节,如果文件比较大,自行设置长一点,比如timeout=10000 + * @param action 传入一个lambda,each所有的数据分片 + * @throws WxErrorException the wx error exception + */ + void getMediaFile(@NonNull long sdk, @NonNull String sdkfileid, String proxy, String passwd, @NonNull long timeout, + @NonNull Consumer action) throws WxErrorException; + /** * 获取会话内容存档开启成员列表 * 企业可通过此接口,获取企业开启会话内容存档的成员列表 diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMsgAuditServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMsgAuditServiceImpl.java index d6cadbda7b..fc4f7cef64 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMsgAuditServiceImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMsgAuditServiceImpl.java @@ -21,6 +21,7 @@ import java.util.Arrays; import java.util.LinkedList; import java.util.List; +import java.util.function.Consumer; import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.MsgAudit.*; @@ -80,10 +81,10 @@ public WxCpChatDatas getChatDatas(long seq, @NonNull long limit, String proxy, S long sdk = Finance.NewSdk(); //因为会话存档单独有个secret,优先使用会话存档的secret String msgAuditSecret = cpService.getWxCpConfigStorage().getMsgAuditSecret(); - if(StringUtils.isEmpty(msgAuditSecret)) { + if (StringUtils.isEmpty(msgAuditSecret)) { msgAuditSecret = cpService.getWxCpConfigStorage().getCorpSecret(); } - long ret = Finance.Init(sdk, cpService.getWxCpConfigStorage().getCorpId(),msgAuditSecret); + long ret = Finance.Init(sdk, cpService.getWxCpConfigStorage().getCorpId(), msgAuditSecret); if (ret != 0) { Finance.DestroySdk(sdk); throw new WxErrorException("init sdk err ret " + ret); @@ -181,7 +182,26 @@ public void getMediaFile(@NonNull long sdk, @NonNull String sdkfileid, String pr if (!targetFile.getParentFile().exists()) { targetFile.getParentFile().mkdirs(); } + this.getMediaFile(sdk, sdkfileid, proxy, passwd, timeout, i -> { + try { + // 大于512k的文件会分片拉取,此处需要使用追加写,避免后面的分片覆盖之前的数据。 + FileOutputStream outputStream = new FileOutputStream(targetFile, true); + outputStream.write(i); + outputStream.close(); + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + @Override + public void getMediaFile(@NonNull long sdk, @NonNull String sdkfileid, String proxy, String passwd, @NonNull long timeout, @NonNull Consumer action) throws WxErrorException { +/** + * 1、媒体文件每次拉取的最大size为512k,因此超过512k的文件需要分片拉取。 + * 2、若该文件未拉取完整,sdk的IsMediaDataFinish接口会返回0,同时通过GetOutIndexBuf接口返回下次拉取需要传入GetMediaData的indexbuf。 + * 3、indexbuf一般格式如右侧所示,”Range:bytes=524288-1048575“:表示这次拉取的是从524288到1048575的分片。单个文件首次拉取填写的indexbuf + * 为空字符串,拉取后续分片时直接填入上次返回的indexbuf即可。 + */ String indexbuf = ""; int ret, data_len = 0; log.debug("正在分片拉取媒体文件 sdkFileId为{}", sdkfileid); @@ -200,9 +220,7 @@ public void getMediaFile(@NonNull long sdk, @NonNull String sdkfileid, String pr try { // 大于512k的文件会分片拉取,此处需要使用追加写,避免后面的分片覆盖之前的数据。 - FileOutputStream outputStream = new FileOutputStream(new File(targetFilePath), true); - outputStream.write(Finance.GetData(mediaData)); - outputStream.close(); + action.accept(Finance.GetData(mediaData)); } catch (Exception e) { e.printStackTrace(); } diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMsgAuditTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMsgAuditTest.java index 91927046f5..0822bfb493 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMsgAuditTest.java +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMsgAuditTest.java @@ -12,8 +12,11 @@ import me.chanjar.weixin.cp.demo.WxCpDemoInMemoryConfigStorage; import me.chanjar.weixin.cp.util.xml.XStreamTransformer; import org.eclipse.jetty.util.ajax.JSON; +import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; +import java.io.File; +import java.io.FileOutputStream; import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; @@ -39,17 +42,21 @@ public class WxCpMsgAuditTest { */ // com.binarywang.spring.starter.wxjava.cp.config.WxCpServiceAutoConfiguration // WxCpServiceImpl.getAccessToken() - @Test - public void test() throws Exception { - - InputStream inputStream = ClassLoader.getSystemResourceAsStream("test-config.xml"); - WxCpDemoInMemoryConfigStorage config = WxCpDemoInMemoryConfigStorage.fromXml(inputStream); - - wxCpConfigStorage = config; - cpService = new WxCpServiceImpl(); - cpService.setWxCpConfigStorage(config); + @BeforeTest + private void initCpservice() { + if(cpService == null) { + InputStream inputStream = ClassLoader.getSystemResourceAsStream("test-config.xml"); + WxCpDemoInMemoryConfigStorage config = WxCpDemoInMemoryConfigStorage.fromXml(inputStream); + config.setMsgAuditLibPath("/E:/IDEA_WORKSPACE/saisc/crs-member-java/target/classes/wework/libcrypto-1_1-x64.dll,libssl-1_1-x64.dll,libcurl-x64.dll,WeWorkFinanceSdk.dll"); + wxCpConfigStorage = config; + cpService = new WxCpServiceImpl(); + cpService.setWxCpConfigStorage(config); + } + } + @Test + public void test() throws Exception { /** * 客户同意进行聊天内容存档事件回调 * 配置了客户联系功能的成员添加外部联系人同意进行聊天内容存档时,回调该事件。 @@ -644,9 +651,109 @@ public void test() throws Exception { * https://www.jianshu.com/p/dde171887d63 */ String getUrl = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s"; - String data = cpService.get(String.format(getUrl, config.getCorpId(), config.getCorpSecret()), null); + String data = cpService.get(String.format(getUrl, wxCpConfigStorage.getCorpId(), wxCpConfigStorage.getCorpSecret()), null); } + @Test + public void testGetMediaFile() throws Exception { + WxCpMsgAuditService msgAuditService = cpService.getMsgAuditService(); + WxCpChatDatas chatDatas = msgAuditService.getChatDatas(0L, 100L, null, null, 10); + for (WxCpChatDatas.WxCpChatData chatDatum : chatDatas.getChatData()) { + WxCpChatModel decryptData = msgAuditService.getDecryptData(chatDatas.getSdk(), chatDatum, 2); + // 文件后缀 + String suffix = ""; + // 文件名md5 + String md5Sum = ""; + // sdkFileId + String sdkFileId = ""; + String msgType = decryptData.getMsgType(); + if(msgType == null ) msgType = ""; + switch (msgType) { + case "image": + suffix = ".jpg"; + md5Sum = decryptData.getImage().getMd5Sum(); + sdkFileId = decryptData.getImage().getSdkFileId(); + break; + case "voice": + suffix = ".amr"; + md5Sum = decryptData.getVoice().getMd5Sum(); + sdkFileId = decryptData.getVoice().getSdkFileId(); + break; + case "video": + suffix = ".mp4"; + md5Sum = decryptData.getVideo().getMd5Sum(); + sdkFileId = decryptData.getVideo().getSdkFileId(); + break; + case "emotion": + md5Sum = decryptData.getEmotion().getMd5Sum(); + sdkFileId = decryptData.getEmotion().getSdkFileId(); + int type = decryptData.getEmotion().getType(); + switch (type) { + case 1: + suffix = ".gif"; + break; + case 2: + suffix = ".png"; + break; + default: + return; + } + break; + case "file": + md5Sum = decryptData.getFile().getMd5Sum(); + suffix = "." + decryptData.getFile().getFileExt(); + sdkFileId = decryptData.getFile().getSdkFileId(); + break; + // 音频存档消息 + case "meeting_voice_call": + + md5Sum = decryptData.getVoiceId(); + sdkFileId = decryptData.getMeetingVoiceCall().getSdkFileId(); + for (WxCpChatModel.MeetingVoiceCall.DemoFileData demofiledata : + decryptData.getMeetingVoiceCall().getDemoFileData()) { + String demoFileDataFileName = demofiledata.getFileName(); + suffix = demoFileDataFileName.substring(demoFileDataFileName.lastIndexOf(".") + 1); + } + + break; + // 音频共享文档消息 + case "voip_doc_share": + + md5Sum = decryptData.getVoipId(); + WxCpFileItem docShare = decryptData.getVoipDocShare(); + String fileName = docShare.getFileName(); + suffix = fileName.substring(fileName.lastIndexOf(".") + 1); + break; + default: + continue; + } + + /** + * 拉取媒体文件 + * + * 传入一个each函数,用于遍历每个分片的数据 + */ + String path = Thread.currentThread().getContextClassLoader().getResource("").getPath(); + String targetPath = path + "testfile/" + md5Sum + suffix; + File file = new File(targetPath); + if (!file.getParentFile().exists()) { + file.getParentFile().mkdirs(); + }else{ + file.delete(); + } + cpService.getMsgAuditService().getMediaFile(chatDatas.getSdk(), sdkFileId, null, null, 1000L, data -> { + try { + // 大于512k的文件会分片拉取,此处需要使用追加写,避免后面的分片覆盖之前的数据。 + FileOutputStream outputStream = new FileOutputStream(targetPath, true); + outputStream.write(data); + outputStream.close(); + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + Finance.DestroySdk(chatDatas.getSdk()); + } } diff --git a/weixin-java-cp/src/test/resources/test-config.sample.xml b/weixin-java-cp/src/test/resources/test-config.sample.xml index b55597dd84..05b112850a 100644 --- a/weixin-java-cp/src/test/resources/test-config.sample.xml +++ b/weixin-java-cp/src/test/resources/test-config.sample.xml @@ -12,6 +12,7 @@ 网页授权获取用户信息回调地址 webhook链接地址的key值 + 会话存档的secret -----BEGIN RSA PRIVATE KEY----- MIICXAIBAAKBgQCTfm5cxqfglfOV7b/Z7OtTZZoZpk2EPTvVhn/ngsfKR899xRdR 25s4h8HkK0XhxqYdOGoAdG3Gms+DvCSY/vu3UtImf0eZSNXpKZJBUnvUVjX4ivnr