注:本文档是在Linux环境下进行测试的。

今天终于有空来聊聊企业微信“会话内容存档”,虽然官方有给出开发文档,但确实是有点晦涩难懂啊,对于我这种菜鸟来说。
在网上翻阅许多教程,也有点摸不着头脑,直至后面在CSDN上看到2位大神的文档,才整出个所以然。
下面就说一下我的整个开发流程:
一、申请会话内容存档接口,有1个月的试用期可申请,然后配置相关的属性。

这里需要注意的是“消息加密公钥”,这是用于加密和解密聊天记录的,相当重要。那个“版本号”,没更新一次,版本号就会+1,个人建议没啥必要就不要经常更换,若要更换也要把历史秘钥对保存起来。因为更新了秘钥对,之前的信息就无法解密了。
秘钥对可以通过此网站生成:http://web.chacuo.net/netrsakeypair

定义类RSAEncrypt做加解密处理,代码如下:

package com.tencent.wework;import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import javax.crypto.Cipher;
import java.io.Reader;
import java.io.StringReader;
import java.security.*;public class RSAEncrypt {public static String decryptRSA(String str, String privateKey) throws Exception {Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());Cipher rsa = Cipher.getInstance("RSA/ECB/PKCS1Padding", "BC");rsa.init(Cipher.DECRYPT_MODE, getPrivateKey(privateKey));byte[] utf8 = rsa.doFinal(Base64.decodeBase64(str));String result = new String(utf8,"UTF-8");return result;}public static PrivateKey getPrivateKey (String privateKey) throws Exception {Reader privateKeyReader = new StringReader(privateKey);PEMParser privatePemParser = new PEMParser(privateKeyReader);Object privateObject = privatePemParser.readObject();if (privateObject instanceof PEMKeyPair) {PEMKeyPair pemKeyPair = (PEMKeyPair) privateObject;JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");PrivateKey privKey = converter.getPrivateKey(pemKeyPair.getPrivateKeyInfo());return privKey;}return null;}}

需要添加的Maven依赖:

<!--<dependency><groupId>org.bouncycastle</groupId><artifactId>bcprov-jdk15on</artifactId><version>1.64</version>
</dependency>(这个好像可以不要)-->
<dependency><groupId>org.bouncycastle</groupId><artifactId>bcpg-jdk16</artifactId><version>1.46</version>
</dependency>
<dependency><groupId>org.bouncycastle</groupId><artifactId>bcpkix-jdk15on</artifactId><version>1.64</version>
</dependency>

二、大致看一下官方给出的整个业务流程:

三、下载官方提供的SDK(小编用的是Linux环境的SDK,至于Windows的至今还没搞懂为啥报错,所以没有使用),主要是使用到libWeWorkFinanceSdk_Java.so文件,把该文件放到某目录下,可以让程序加载到就行(小编就直接放到/root/workwx/目录下了)。
项目目录结构:

注意:Finance类必须放在com.tencent.wework目录下,不然会报错(虽然没验证过,但很多都这样说,你们可以测试一下)

四、将官方提供的Finance类进行稍微修改:

package com.tencent.wework;public class Finance {public native static long NewSdk();/*** 初始化函数* Return值=0表示该API调用成功* * @param [in]  sdk            NewSdk返回的sdk指针* @param [in]  corpid      调用企业的企业id,例如:wwd08c8exxxx5ab44d,可以在企业微信管理端--我的企业--企业信息查看* @param [in]  secret       聊天内容存档的Secret,可以在企业微信管理端--管理工具--聊天内容存档查看** @return 返回是否初始化成功*      0   - 成功*      !=0 - 失败*/public native static int Init(long sdk, String corpid, String secret);/*** 拉取聊天记录函数* Return值=0表示该API调用成功* ** @param [in]  sdk                NewSdk返回的sdk指针* @param [in]  seq               从指定的seq开始拉取消息,注意的是返回的消息从seq+1开始返回,seq为之前接口返回的最大seq值。首次使用请使用seq:0* @param [in]  limit            一次拉取的消息条数,最大值1000条,超过1000条会返回错误* @param [in]  proxy          使用代理的请求,需要传入代理的链接。如:socks5://10.0.0.1:8081 或者 http://10.0.0.1:8081* @param [in]  passwd          代理账号密码,需要传入代理的账号密码。如 user_name:passwd_123* @param [out] chatDatas       返回本次拉取消息的数据,slice结构体.内容包括errcode/errmsg,以及每条消息内容。** @return 返回是否调用成功*      0   - 成功*      !=0 - 失败  */      public native static int GetChatData(long sdk, long seq, long limit, String proxy, String passwd, long timeout, long chatData);/*** 拉取媒体消息函数* Return值=0表示该API调用成功* ** @param [in]  sdk                NewSdk返回的sdk指针* @param [in]  sdkFileid     从GetChatData返回的聊天消息中,媒体消息包括的sdkfileid* @param [in]  proxy           使用代理的请求,需要传入代理的链接。如:socks5://10.0.0.1:8081 或者 http://10.0.0.1:8081* @param [in]  passwd          代理账号密码,需要传入代理的账号密码。如 user_name:passwd_123* @param [in]  indexbuf        媒体消息分片拉取,需要填入每次拉取的索引信息。首次不需要填写,默认拉取512k,后续每次调用只需要将上次调用返回的outindexbuf填入即可。* @param [out] media_data        返回本次拉取的媒体数据.MediaData结构体.内容包括data(数据内容)/outindexbuf(下次索引)/is_finish(拉取完成标记)** @return 返回是否调用成功*      0   - 成功*      !=0 - 失败*/public native static int GetMediaData(long sdk, String indexbuf, String sdkField, String proxy, String passwd, long timeout, long mediaData);/*** @brief 解析密文* @param [in]  encrypt_key, getchatdata返回的encrypt_key* @param [in]  encrypt_msg, getchatdata返回的content* @param [out] msg, 解密的消息明文* @return 返回是否调用成功*      0   - 成功*      !=0 - 失败*/public native static int DecryptData(long sdk, String encrypt_key, String encrypt_msg, long msg);public native static void DestroySdk(long sdk);public native static long NewSlice();/*** @brief 释放slice,和NewSlice成对使用* @return */public native static void FreeSlice(long slice);/*** @brief 获取slice内容* @return 内容*/public native static String GetContentFromSlice(long slice);/*** @brief 获取slice内容长度* @return 内容*/public native static int GetSliceLen(long slice);public native static long NewMediaData();public native static void FreeMediaData(long mediaData);/*** @brief 获取mediadata outindex* @return outindex*/public native static String GetOutIndexBuf(long mediaData);/*** @brief 获取mediadata data数据* @return data*/public native static byte[] GetData(long mediaData);public native static int GetIndexLen(long mediaData);public native static int GetDataLen(long mediaData);/*** @brief 判断mediadata是否结束* @return 1完成、0未完成*/public native static int IsMediaDataFinish(long mediaData);static {System.load("/root/workwx/libWeWorkFinanceSdk_Java.so");}
}

五、主要业务代码:

package com.tencent.wework;
import java.io.File;
import java.io.FileOutputStream;
import java.util.Arrays;
import java.util.List;import org.json.JSONArray;
import org.json.JSONObject;public class FinanceDemo {private static String priKey = "-----BEGIN RSA PRIVATE KEY-----\n"+ "..."+ "-----END RSA PRIVATE KEY-----";public void demo() {long sdk = Finance.NewSdk();Finance.Init(sdk, "corpid", "secret"); // 初始化long ret = 0;int seq = 0; // 从指定的seq开始拉取消息,注意的是返回的消息从seq+1开始返回,seq为之前接口返回的最大seq值。首次使用请使用seq:0(这个值需要记录下来,以便下一次的拉去)int limit = 60;long slice = Finance.NewSlice();ret = Finance.GetChatData(sdk, seq, limit, null, null, 3, slice);if (ret != 0) {System.out.println("getchatdata ret " + ret);return;}String getchatdata = Finance.GetContentFromSlice(slice);System.out.println(seq + ",拉去的聊天记录密文结果:" + getchatdata);JSONObject jo = new JSONObject(getchatdata);JSONArray chatdata = jo.getJSONArray("chatdata");System.out.println("消息数:" + chatdata.length());for (int i = 0; i < chatdata.length(); i++) {JSONObject data = new JSONObject(chatdata.get(i).toString());String encryptRandomKey = data.getString("encrypt_random_key");String encryptChatMsg   = data.getString("encrypt_chat_msg");long msg = Finance.NewSlice();try {// 聊天记录密文解密String message = RSAEncrypt.decryptRSA(encryptRandomKey, priKey);ret = Finance.DecryptData(sdk, message, encryptChatMsg, msg);if (ret != 0) {System.out.println("getchatdata ret " + ret);return;}String plaintext = Finance.GetContentFromSlice(msg);System.out.println("decrypt ret:" + ret + " msg:" + plaintext);Finance.FreeSlice(msg);JSONObject plaintextJson = new JSONObject(plaintext);// 拉去媒体文件解密String msgtype = plaintextJson.getString("msgtype");if ("mixed".equals(msgtype)) {// 混合消息JSONArray array = new JSONArray();JSONObject mixed = new JSONObject(plaintextJson.get("mixed").toString());JSONArray items = mixed.getJSONArray("item");for (int j = 0; j < items.length(); j++) {JSONObject item = new JSONObject(items.get(j).toString());JSONObject content = new JSONObject(item.getString("content"));String type = item.getString("type");if ("text".equals(type)) {item.put("content", content.getString("content"));} else {String url = pullMediaFiles(sdk, type, content);item.put("content", url);}array.put(item);}JSONObject content = new JSONObject();content.put(msgtype, array.toString());plaintextJson.put(msgtype, content.toString());} else {pullMediaFiles(sdk, msgtype, plaintextJson);}// 会话内容写入数据库System.out.println(plaintextJson);// save(plaintextJson);} catch (Exception e) {e.printStackTrace();return;}}}// 拉去媒体信息private String pullMediaFiles(long sdk, String msgtype, JSONObject plaintextJson) {String[] msgtypeStr = {"image", "voice", "video", "emotion", "file"};List<String> msgtypeList = Arrays.asList(msgtypeStr);if (msgtypeList.contains(msgtype)) {String savefileName = "";JSONObject file = new JSONObject();if (!plaintextJson.isNull("msgid")) {file = plaintextJson.getJSONObject(msgtype);savefileName = plaintextJson.getString("msgid");} else {// 混合消息file = plaintextJson;savefileName = file.getString("md5sum");}System.out.println("媒体文件信息:" + file);/* ============ 文件存储目录及文件名 Start ============ */String suffix = "";switch (msgtype) {case "image" : suffix = ".jpg"; break;case "voice" : suffix = ".amr"; break;case "video" : suffix = ".mp4"; break;case "emotion" : int type = (int) file.get("type");if (type == 1) suffix = ".gif";else if (type == 2) suffix = ".png";break;case "file" : suffix = "." + file.getString("fileext");break;}savefileName += suffix;String path = "/var/data/workwx/";String savefile = path + savefileName;File targetFile = new File(savefile);if (!targetFile.getParentFile().exists()) //创建父级文件路径targetFile.getParentFile().mkdirs();/* ============ 文件存储目录及文件名 End ============ *//* ============ 拉去文件 Start ============ */int i = 0; boolean isSave = true;String indexbuf = "", sdkfileid = file.getString("sdkfileid");while (true) {long mediaData = Finance.NewMediaData();int ret = Finance.GetMediaData(sdk, indexbuf, sdkfileid, null, null, 3, mediaData);if (ret != 0) {System.out.println("getmediadata ret:" + ret);Finance.FreeMediaData(mediaData);return null;}System.out.printf("getmediadata outindex len:%d, data_len:%d, is_finis:%d\n",Finance.GetIndexLen(mediaData), Finance.GetDataLen(mediaData),Finance.IsMediaDataFinish(mediaData));try {// 大于512k的文件会分片拉取,此处需要使用追加写,避免后面的分片覆盖之前的数据。FileOutputStream outputStream = new FileOutputStream(new File(savefile), true);outputStream.write(Finance.GetData(mediaData));outputStream.close();} catch (Exception e) {e.printStackTrace();}if (Finance.IsMediaDataFinish(mediaData) == 1) {// 已经拉取完成最后一个分片Finance.FreeMediaData(mediaData);break;} else {// 获取下次拉取需要使用的indexbufindexbuf = Finance.GetOutIndexBuf(mediaData);Finance.FreeMediaData(mediaData);}// 若文件大于50M则不保存if (++i > 100) {isSave = false;break;}}/* ============ 拉去文件 End ============ */if (isSave) {file.put("sdkfileid", savefile);return savefile;}}return null;}
}

此时,可以拉取到聊天记录并入库了,要将这堆聊天记录对应的展示出来,还需做很多工作,如:获取内部成员、客户列表、客户群列表等等(后续如果大家有需要再分享出来吧)。

总之,开发完整个功能,小编真的是脱了一层皮,所以开发时跟自己说,开发完一定要把教程分享出来,让大家少走点弯路。

本教程主要参考了:
https://blog.csdn.net/weixin_42932323/article/details/118326236
https://blog.csdn.net/u011056339/article/details/105704995

更多文章可查看小编的个人博客网站:www.lrfun.com

Java 企业微信会话内容(聊天记录)存档功能实现,并获取媒体文件相关推荐

  1. 企业微信-会话内容存档 实时拉取企业微信聊天记录java版SDK

    企业微信-会话内容存档 实时拉取企业微信聊天记录java版SDK git传送门 https://gitee.com/flash127/wework-msgaudit wework-msgaudit 企 ...

  2. python对接企业微信_Python对接企业微信会话内容存档功能的实践

    背景 企业微信会话内容存档,是一项面对金融行业的开放的监管功能,其他行业可以找企业微信官方服务商[1]向腾讯申请审批开通.会话存档的推出是企业微信为了让企业可以了解员工与客户的互动情况,也为了避免员工 ...

  3. 企业微信会话内容存档功能说明

    为保障客户服务质量.提高内部协作效率和监管合规等原因,企业微信提供会话内容存档功能. 会话内容存档是什么? 企业在遇到以下情况时,是不是非常想获取员工与客户的聊天记录: 考察员工和客户的沟通过程是否有 ...

  4. 服务器微信接口返回乱码,企业微信会话内容存档调用DecryptData返回中文出现乱码...

    企业微信会话内容存档调用DecryptData返回中文出现乱码 问题类型 API/组件名称 终端类型 微信版本 基础库版本 Bug 企业微信会话存档sdk 工具 无 无 我用c#开发会话存档功能,调用 ...

  5. Java企业微信会话存档开发(从跳坑到爬坑)

    Java企业微信会话存档开发(从跳坑到爬坑) 本文仅作为方便首次开发企业微信使用 文章目录 Java企业微信会话存档开发(从跳坑到爬坑) 前言 一.开发准备 1.企业微信后台配置 2.sdk下载 3. ...

  6. PHP企业微信会话内容存档sdk扩展

    前言 由于企业微信官方提供的会话内容存档sdk只支持C和java,给用php开发的小伙伴带来了障碍,网上搜了一圈,实际操作了一番,踩了不少坑,这里总结一下 1. 环境依赖 仅Linux环境(估计是后面 ...

  7. 企业微信会话内容存档PHP版SDK编译详细步骤

    感谢这位老哥编写的SDK: https://github.com/pangdahua/php7-wxwork-finance-sdkhttps://github.com/pangdahua/php7- ...

  8. 企业微信会话存档功能开启

    企业微信会话存档功能是企业微信所开发的,可以获取存档员工的工作聊天记录,包括文字.图片.语音.撤回消息等内容,用来保障客户服务质量.提高内部协作效率和监管合规. 企业如果想使用会话存档功能,首先需要管 ...

  9. 企业微信会话存档在哪里看员工聊天记录?

    开通企业微信会话存档功能后,在哪里能看到员工聊天记录呢?其实会话存档是通过企业微信提供的API接口而开发的将聊天数据可视化的软件,开通会话存档后,我们有三种方法可以查看员工的聊天记录,一起来看看吧: ...

最新文章

  1. 用两种方法判断男性女性的存储过程
  2. 基于cobbler实现自动安装系统
  3. Oracle学习:序列
  4. Shader 坐标转换
  5. 使用Python往Elasticsearch插入数据
  6. redis debug命令详解
  7. Linux终端复用神器-tmux初探
  8. Linux中last的用法及参数,查看登陆系统用户的信息
  9. Visual C++ 时尚编程百例013(CRect类)
  10. 简述mysql完全备份过程_【SQL】MySQL之使用mysqldump全备份及恢复过程详解_MySQL
  11. Atitit.计算机图形图像图片处理原理与概论attilax总结
  12. 过年啦!什么是你的春节专属年味儿?
  13. 火车采集器V2010免费版下载
  14. 计算机科学和热力学,相图热力学数据库及其计算软件: 过去、现在和将来
  15. CPU 手机CPU 显示 天梯图
  16. AutoCAD2012安装失败解决办法,Failed Installation aborted, Res
  17. MATLAB编写用户登陆界面小结——更改界面左上角图标、输入用户名提醒和输入密码隐藏
  18. IAR生成文件链接过程解析
  19. 微信蓝牙设备服务器,微信又更新了 支持连接蓝牙设备
  20. C#参数详解一(形参和实参)

热门文章

  1. All In One - 第4章 通信与网络安全
  2. Yii2 创建定时任务
  3. Linux free查看内存使用说明
  4. VBS 计算汉字笔画数
  5. android 8 呼吸灯,小米8se呼吸灯可以设置颜色吗 小米8se呼吸灯颜色在哪设置
  6. C++的浅拷贝与深拷贝
  7. 2007版本AutoCAD关于定数等分的应用
  8. matplotlib_03_柱状图(条形图)
  9. Python设置画布大小_Python Tkinter Canvas画布
  10. 用NuGet管理好你的包包