注:本文是在windows环境下开发完成的。linux环境还没进行测试。
前言
为保障客户服务质量、提高内部协作效率和监管合规等原因,企业微信提供会话内容存档功能。企业可以统一设置存档的员工范围,并通过API获取开启存档员工的工作沟通内容,满足企业的外部监管合规和内部管理需求。
一、使用前帮助
1、管理后台开启会话内容存档,开启前需设置开启范围、IP地址及消息加密公钥(注:员工的企业微信需升级到2.8.9版本及以上),开启后:
①开启存档的员工登录客户端,进入企业后会经过告知页面,获知后可继续使用
②企业可以获取开启存档的员工和未开启员工之间的会话内容,但不能获取未开启员工之间的会话内容
2、会话内容存档功能由企业管理员进行设置。出于安全考虑,每次操作前需要通过声纹验证管理员身份(需保证已在微信内设置声纹),验证通过后才能进行操作。每次身份验证有效期为30分钟。
3、设置公钥,需要在管理端配置消息加密公钥,这是用于加密和解密聊天记录的,非常重要。密钥的版本号,每更新一次,版本号就会+1,版本号变更,之前的消息就解密不了了。个人建议不要经常更换,若要更换也要把历史秘钥对保存起来,切记切记!!!。本人就在这里吃了亏。
密钥对可以通过在线网址生成:在线生成RSA私钥对
公钥配置在管理端,私钥配置在代码里。
二、获取会话内容
1.下载SDK
先下载企业微信提供的sdk
linux环境 SDK:
下载 SDK v1.2 [更新时间:2020-11-16 更新特性:更新sdk示例项目]
下载 SDK v1.1 [更新时间:2020-04-01 更新特性:支持并发调用]
下载 SDK v1.0
windows环境 SDK:
下载 SDK v1.1[更新时间:2020-12-23 更新特性:支持并发调用、更新sdk示例项目]
下载 SDK v1.0
2.需要引入的jar包
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpg-jdk16</artifactId>
<version>1.46</version>
<exclusions>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk16</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.64</version>
</dependency>
3.开发环境说明
使用SpringBoot开发时,我是使用Windows系统,将sdk中的.dll文件直接放入C:\Windows\System32目录下,然后在环境变量的Path中添加该目录。
如果是linux系统,先将 libWeWorkFinanceSdk_Java.so文件上传到服务器上自己创建的lib目录下,也可以使用系统的lib,但是不推荐(容易找不着),然后再linux环境启动项目时增加启动命令:
-Djava.library.path=这里填写文件保存的目录
(如果配置到全局环境变量中也可以不增加启动命令)
这种办法可以参考链接https://www.pudn.com/news/625e86708cbeb85d5722c955.html
也可以直接配置LD_LIBRARY_PATH环境变量
export LD_LIBRARY_PATH=这里填写文件保存的目录 :$LD_LIBRARY_PATH
export LIBRARY_PATH=这里填写文件保存的目录 :$LIBRARY_PATH
注意:sdk中的Finace.java文件必须放入项目的com.tencent.wework目录下,切记
4.linux部署
Linux部署时,项目打包切记要改Finance类的静态加载代码块
static {
System.loadLibrary("WeWorkFinanceSdk_Java");
}
之前windows的是没有后缀_java的,linux的有。我在这找了好久的问题。
5.开发,代码实现
第一步:初始化SDK,拿到的sdkid放入了缓存当中
/**
* 第一步:初始化sdk,获取sdk对象,首次使用初始化
* 第二步:初始化函数,return 返回是否初始化成功 0:-成功,!=0:-失败
* @param sdkId sdkid
* @param corpid 应用id
* @param secrectkey 密钥
* @return
*/
@Override
public Result initSdk(String sdkId, String corpid, String secrectkey) {
long ret;
Long sdk = Finance.NewSdk();
ret = Finance.Init(sdk, corpid, secrectkey);
if(ret != 0){
Finance.DestroySdk(sdk);
sdkMap.remove(sdkId);
return new Result(ret,"init sdk err",null);
}
if(StrUtil.isNotBlank(sdkId)){
sdkMap.put(sdkId,sdk);
return Result.success("init sdk success",sdkId);
}else{
sdkMap.put(String.valueOf(sdk),sdk);
return Result.success("init sdk success",String.valueOf(sdk));
}
}
第二部:拉取会话数据,这里是没有解密的
/**
* 拉取会话数据
* @param sdkId sdkId
* @param seq 会话seq
* @param limit 会话条数
* @param proxy 代理名
* @param passwd 代理密码
* @param timeout 超时时间
* @return
*/
@Override
public Result getChatData(String sdkId, int seq, int limit, String proxy, String passwd, int timeout){
Long sdk = sdkMap.get(sdkId);
if(ObjectUtil.isNull(sdk)){
return Result.error("init sdk err");
}
log.info("获取GetChatData参数:sdk:{},seq:{},limit:{},proxy:{}",sdk,seq,limit,proxy);
//每次使用GetChatData拉取存档前需要调用NewSlice获取一个slice,在使用完slice中数据后,还需要调用FreeSlice释放。
long slice = Finance.NewSlice();
long ret = Finance.GetChatData(sdk, seq, limit, proxy, passwd, timeout, slice);
if (ret != 0) {
log.error("获取GetChatData失败:{}",ret);
Finance.FreeSlice(slice);
return new Result(ret,"获取GetChatData失败",null);
}
String result = Finance.GetContentFromSlice(slice);
log.info("获取GetChatData成功:{}",result);
Finance.FreeSlice(slice);
return Result.success("获取GetChatData成功",result);
}
第三步:解密会话数据
/**
* 解密会话存档内容
* @param sdkId
* @param pkv 私有-加密此条消息使用的公钥版本号。Uint32类型
* @param encryptRandomKey 使用publickey_ver指定版本的公钥进行非对称加密后base64加密的内容,需要业务方先base64 decode处理后,再使用指定版本的私钥进行解密,得出内容。String类型
* @param encryptChatMsg-消息密文。需要业务方使用将encrypt_random_key解密得到的内容,与encrypt_chat_msg,传入sdk接口DecryptData,得到消息明文。String类型
* @return
*/
@Override
public Result decryptData(String sdkId, String pkv,String encryptRandomKey, String encryptChatMsg) {
Long sdk = sdkMap.get(sdkId);
if(ObjectUtil.isNull(sdk)){
return Result.error("init sdk err");
}
String encryptKey = RSAUtil.decrypt(encryptRandomKey,pkv);
return decryptData(sdkId,encryptKey,encryptChatMsg);
}
RSAUtil.decrypt(encryptRandomKey,pkv);方法
/**
* RSA私钥解密
* @param str 加密字符串
* @param privateKey 私钥
* @return 铭文
* @throws Exception
* 解密过程中的异常信息
*/
public static String decrypt(String str, String privateKey){
log.info("密文:{}",str);
String outStr = null;
try {
outStr = RSAUtil.getPrivateKeyByPKCS1(str,privateKey);
} catch (Exception e) {
e.printStackTrace();
}
return outStr;
}
/**
* 用此方法先获取秘钥
* RSAUtil_PKCS1
* @param encrypt_random_key
* @param privKeyPEM
* @return
* @throws Exception
*/
public static String getPrivateKeyByPKCS1(String encrypt_random_key,String privKeyPEM) throws Exception {
byte[] bytes = Base64.decodeBase64(privKeyPEM);
DerInputStream derReader = new DerInputStream(bytes);
DerValue[] seq = derReader.getSequence(0);
BigInteger modulus = seq[1].getBigInteger();
BigInteger publicExp = seq[2].getBigInteger();
BigInteger privateExp = seq[3].getBigInteger();
BigInteger prime1 = seq[4].getBigInteger();
BigInteger prime2 = seq[5].getBigInteger();
BigInteger exp1 = seq[6].getBigInteger();
BigInteger exp2 = seq[7].getBigInteger();
BigInteger crtCoef = seq[8].getBigInteger();
RSAPrivateCrtKeySpec keySpec = new RSAPrivateCrtKeySpec(modulus, publicExp, privateExp, prime1, prime2, exp1, exp2, crtCoef);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
// 64位解码加密后的字符串
byte[] inputByte = Base64.decodeBase64(encrypt_random_key.getBytes("UTF-8"));
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
//RSA解密
//当长度过长的时候,需要分割后解密 128个字节
String outStr = new String(getMaxResultDecrypt(encrypt_random_key, cipher));
log.info("RSA私钥解密后的数据|outStr:{}", outStr);
return outStr;
}
/**
*
* @param str
* @param cipher
* @return
*/
private static byte[] getMaxResultDecrypt(String str, Cipher cipher) throws Exception {
byte[] inputArray = Base64.decodeBase64(str.getBytes("UTF-8"));
int inputLength = inputArray.length;
log.info("解密字节数|inputLength:{}", inputLength);
// 最大解密字节数,超出最大字节数需要分组加密
int MAX_ENCRYPT_BLOCK = 128;
// 标识
int offSet = 0;
byte[] resultBytes = {};
byte[] cache = {};
while (inputLength - offSet > 0) {
if (inputLength - offSet > MAX_ENCRYPT_BLOCK) {
cache = cipher.doFinal(inputArray, offSet, MAX_ENCRYPT_BLOCK);
offSet += MAX_ENCRYPT_BLOCK;
} else {
cache = cipher.doFinal(inputArray, offSet, inputLength - offSet);
offSet = inputLength;
}
resultBytes = Arrays.copyOf(resultBytes, resultBytes.length + cache.length);
System.arraycopy(cache, 0, resultBytes, resultBytes.length - cache.length, cache.length);
}
return resultBytes;
}
decryptData(sdkId,encryptKey,encryptChatMsg);方法
/**
* 解密会话存档内容
* @param sdkId
* @param encryptKey
* @param encryptChatMsg
* @return
*/
@Override
public Result decryptData(String sdkId, String encryptKey, String encryptChatMsg) {
Long sdk = sdkMap.get(sdkId);
if(ObjectUtil.isNull(sdk)){
return Result.error("init sdk err");
}
long msg = Finance.NewSlice();
long ret = Finance.DecryptData(sdk, encryptKey, encryptChatMsg, msg);
if (ret != 0) {
System.out.println("decryptData ret " + ret);
Finance.FreeSlice(msg);
return new Result(ret,"decryptData err",null);
}
String result = Finance.GetContentFromSlice(msg);
System.out.println("decryptData ret:" + ret + " msg:" + result);
Finance.FreeSlice(msg);
return Result.success("decryptData success",result);
}
我的Result是自定义的一个返回范类
package com.util;
import cn.hutool.core.util.ObjectUtil;
import java.util.HashMap;
public class Result extends HashMap<String, Object>
{
private static final long serialVersionUID = 1L;
public static final String CODE_TAG = "code";
public static final String MSG_TAG = "msg";
public static final String DATA_TAG = "data";
/**
* 状态类型
*/
public enum Type
{
/** 成功 */
SUCCESS(0L),
/** 警告 */
WARN(301L),
/** 错误 */
ERROR(500L);
private final long value;
Type(long value)
{
this.value = value;
}
public long value()
{
return this.value;
}
}
/** 状态类型 */
private Type type;
/** 状态码 */
private long code;
/** 返回内容 */
private String msg;
/** 数据对象 */
private Object data;
/**
* 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。
*/
public Result()
{
}
/**
* 初始化一个新创建的 Result 对象
*
* @param type 状态类型
* @param msg 返回内容
*/
public Result(Type type, String msg)
{
super.put(CODE_TAG, type.value);
super.put(MSG_TAG, msg);
}
/**
* 初始化一个新创建的 Result 对象
*
* @param type 状态类型
* @param msg 返回内容
* @param data 数据对象
*/
public Result(Type type, String msg, Object data)
{
super.put(CODE_TAG, type.value);
super.put(MSG_TAG, msg);
if (ObjectUtil.isNotEmpty(data))
{
super.put(DATA_TAG, data);
}
}
/**
* 初始化一个新创建的 Result 对象
*
* @param code 状态码
* @param msg 返回内容
* @param data 数据对象
*/
public Result(long code, String msg, Object data)
{
super.put(CODE_TAG, code);
super.put(MSG_TAG, msg);
if (ObjectUtil.isNotEmpty(data))
{
super.put(DATA_TAG, data);
}
}
/**
* 返回成功消息
*
* @return 成功消息
*/
public static Result success()
{
return Result.success("操作成功");
}
/**
* 返回成功数据
*
* @return 成功消息
*/
public static Result success(Object data)
{
return Result.success("操作成功", data);
}
/**
* 返回成功消息
*
* @param msg 返回内容
* @return 成功消息
*/
public static Result success(String msg)
{
return Result.success(msg, null);
}
/**
* 返回成功消息
*
* @param msg 返回内容
* @param data 数据对象
* @return 成功消息
*/
public static Result success(String msg, Object data)
{
return new Result(Type.SUCCESS, msg, data);
}
/**
* 返回警告消息
*
* @param msg 返回内容
* @return 警告消息
*/
public static Result warn(String msg)
{
return Result.warn(msg, null);
}
/**
* 返回警告消息
*
* @param msg 返回内容
* @param data 数据对象
* @return 警告消息
*/
public static Result warn(String msg, Object data)
{
return new Result(Type.WARN, msg, data);
}
/**
* 返回错误消息
*
* @return
*/
public static Result error()
{
return Result.error("操作失败");
}
/**
* 返回错误消息
*
* @param msg 返回内容
* @return 警告消息
*/
public static Result error(String msg)
{
return Result.error(msg, null);
}
/**
* 返回错误消息
*
* @param msg 返回内容
* @param data 数据对象
* @return 警告消息
*/
public static Result error(String msg, Object data)
{
return new Result(Type.ERROR, msg, data);
}
public Type getType()
{
return type;
}
public void setType(Type type)
{
this.type = type;
}
public long getCode()
{
return (long) this.get("code");
}
public void setCode(long code)
{
this.code = code;
}
public String getMsg()
{
return (String) this.get("msg");
}
public void setMsg(String msg)
{
this.msg = msg;
}
public Object getData()
{
return this.get("data");
}
public void setData(Object data)
{
this.data = data;
}
}
第四个:获取媒体文件
/**
* 拉取媒体文件
* @return
*/
@Override
public void getMediaDataAndUpload(String sdkId, String msgtype, JSONObject jsonObject,String proxy,String passwd,int timeout) {
Map<String,Object> map = new HashMap<>();
Long sdk = sdkMap.get(sdkId);
if(ObjectUtil.isNull(sdk)){
throw new RuntimeException("init sdk err");
}
try {
String savefileName = "";
JSONObject file = new JSONObject();
if (!jsonObject.isNull("msgid")) {
file = jsonObject.getJSONObject(msgtype);
savefileName = jsonObject.getStr("msgid");
} else {
// 混合消息
file = jsonObject;
savefileName = file.getStr("md5sum");
}
log.info("媒体文件消息:{}",file.toString());
/* ============ 文件存储目录及文件名 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.getStr("fileext");
break;
}
savefileName += suffix;
String path = basePath;
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 = "";
String sdkfileid = file.getStr("sdkfileid");
while (true) {
long mediaData = Finance.NewMediaData();
int ret = Finance.GetMediaData(sdk, indexbuf, sdkfileid, proxy, passwd, timeout, mediaData);
if (ret != 0) {
log.info("getmediadata ret:{}",ret);
Finance.FreeMediaData(mediaData);
return null;
}
log.info("getmediadata outindex len:{}, data_len:{}, is_finis:{}", 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 {
// 获取下次拉取需要使用的indexbuf
indexbuf = Finance.GetOutIndexBuf(mediaData);
Finance.FreeMediaData(mediaData);
}
// 若文件大于50M则不保存
if (++i > 100) {
isSave = false;
break;
}
}
/* ============ 拉去文件 End ============ */
if (isSave) {
file.set("sdkfileid", savefile);
File file1 = new File(savefile);
}
}catch (Exception e){
e.printStackTrace();
}
}
第五步:释放sdk,当本次拉取结束时,可以选择调用释放sdk
/**
* 释放sdk
* @param sdkId sdkid
* @return
*/
@Override
public Result destroySdk(String sdkId) {
Long sdk = sdkMap.get(sdkId);
if(ObjectUtil.isNull(sdk)){
return Result.error("init sdk err");
}
Finance.DestroySdk(sdk);
sdkMap.remove(sdkId);
return Result.success("destroySdk success");
}
以上都是加密解密调用的方法,拉取存库实现的逻辑还得自己来写,直接上代码
private String[] msgtypeStr = {"image", "voice", "video", "emotion", "file"};
@Override
public Result getMsgSave() {
//初始化sdk
iTxChatService.initSdk(SDKID,corpid,secrectkey);
//获取会话数据,这里我是把seq的值存入了数据库,这里你们自行存库存redis存内存当中都行
//代表消息的seq值,标识消息的序号。再次拉取需要带上上次回包中最大的seq。
String sqlV = tWxSysconfigMapper.getTWxSysconfig(AppConstant.RECORD_SEQ);
int seq = Integer.valueOf(sqlV);
int limit = 1000;
String proxy = "http://" + httpProxyHost + ":" + httpProxyPort;
List<String> msgTypeList = Arrays.asList(msgtypeStr);
int a = 1;
while(true){
log.info("******第{}次拉取开始*****",a);
Result data = iTxChatService.getChatData(SDKID, seq, limit, proxy, null, 10);
if(data.getCode() == Result.Type.SUCCESS.value()){
//拿到了会话数据,此时还没有解密
//样例{"errcode":0,"errmsg":"ok","chatdata":[{"seq":196,"msgid":"CAQQ2fbb4QUY0On2rYSAgAMgip/yzgs=","publickey_ver":3,"encrypt_random_key":"ftJ+uz3n/z1DsxlkwxNgE+mL38H42/KCvN8T60gbbtPD+Rta1hKTuQPzUzO6Hzne97MgKs7FfdDxDck/v8cDT6gUVjA2tZ/M7euSD0L66opJ/IUeBtpAtvgVSD5qhlaQjvfKJc/zPMGNK2xCLFYqwmQBZXbNT7uA69Fflm512nZKW/piK2RKdYJhRyvQnA1ISxK097sp9WlEgDg250fM5tgwMjujdzr7ehK6gtVBUFldNSJS7ndtIf6aSBfaLktZgwHZ57ONewWq8GJe7WwQf1hwcDbCh7YMG8nsweEwhDfUz+u8rz9an+0lgrYMZFRHnmzjgmLwrR7B/32Qxqd79A==","encrypt_chat_msg":"898WSfGMnIeytTsea7Rc0WsOocs0bIAerF6de0v2cFwqo9uOxrW9wYe5rCjCHHH5bDrNvLxBE/xOoFfcwOTYX0HQxTJaH0ES9OHDZ61p8gcbfGdJKnq2UU4tAEgGb8H+Q9n8syRXIjaI3KuVCqGIi4QGHFmxWenPFfjF/vRuPd0EpzUNwmqfUxLBWLpGhv+dLnqiEOBW41Zdc0OO0St6E+JeIeHlRZAR+E13Isv9eS09xNbF0qQXWIyNUi+ucLr5VuZnPGXBrSfvwX8f0QebTwpy1tT2zvQiMM2MBugKH6NuMzzuvEsXeD+6+3VRqL"}]}
JSONObject chatData = JSONUtil.parseObj(data.getData());
JSONArray jsonArray = chatData.getJSONArray("chatdata");
if(!jsonArray.isEmpty()){
//遍历
for (int i = 0; i < jsonArray.size() ; i++) {
seq = jsonArray.getJSONObject(i).getInt("seq");
//这里记录着seq
seqMap.put("seq",seq);
JSONObject chatOne = jsonArray.getJSONObject(i);
log.info("密钥信息:{}",chatOne.toString());
try {
//公钥版本号,我这里是到了第四个版本,之前的版本不记得了,所以之前版本的消息都不解密了
if(chatOne.getInt("publickey_ver") != 4) {
continue;
}
//解密,pk是放在代码当中的私钥,
Result dcRes = iTxChatService.decryptData(SDKID,pk,chatOne.getStr("encrypt_random_key"),chatOne.getStr("encrypt_chat_msg"));
if(dcRes.getCode() == Result.Type.SUCCESS.value()){
log.info("解密数据:{}",dcRes.getData());
//解密数据
JSONObject res = JSONUtil.parseObj(dcRes.getData());
//res就是解密之后的数据,各位可以自行判断消息类型来处理想要的消息
//res.getStr("msgtype")
//文本消息
if("text".equals(res.getStr("msgtype"))){
System.out.println(res.getJSONObject("text").getStr("content"));
}else if(msgTypeList.contains(res.getStr("msgtype"))){
//处理媒体消息,上面有拉取媒体消息的方法,自行处理
Map<String,Object> map = iTxChatService.getMediaDataAndUpload(SDKID,res.getStr("msgtype"),res,proxy,null,10);
System.out.println(com.alibaba.fastjson.JSONObject.toJSONString(map));
}else if("revoke".equals(res.getStr("msgtype"))){
//撤回消息
System.out.println(res.getJSONObject("revoke").getStr("pre_msgid"));
}else if("agree".equals(res.getStr("msgtype"))){
//同意会话聊天内容
System.out.println(res.getJSONObject("agree").toString());
}else if("disagree".equals(res.getStr("msgtype"))){
//不同意消息
System.out.println(res.getJSONObject("disagree").toString());
}else if("card".equals(res.getStr("msgtype"))){
//名片
System.out.println(res.getJSONObject("card").toString());
}else if("location".equals(res.getStr("msgtype"))){
//位置
System.out.println(res.getJSONObject("location").toString());
}else if("link".equals(res.getStr("msgtype"))){
//链接
System.out.println(res.getJSONObject("link").toString());
}else if("weapp".equals(res.getStr("msgtype"))){
//小程序
System.out.println(res.getJSONObject("weapp").toString());
}else if("chatrecord".equals(res.getStr("msgtype"))){
//会话记录消息
System.out.println(res.getJSONObject("chatrecord").toString());
}else if("todo".equals(res.getStr("msgtype"))){
//代办消息
System.out.println(res.getJSONObject("todo").toString());
}else if("vote".equals(res.getStr("msgtype"))){
//投票消息
System.out.println(res.getJSONObject("vote").toString());
}else if("collect".equals(res.getStr("msgtype"))){
//填表消息
System.out.println(res.getJSONObject("collect").toString());
}else if("redpacket".equals(res.getStr("msgtype"))){
//红包消息
System.out.println(res.getJSONObject("redpacket").toString());
}else if("meeting".equals(res.getStr("msgtype"))){
//会议邀请消息
System.out.println(res.getJSONObject("meeting").toString());
}else if("docmsg".equals(res.getStr("msgtype"))){
//在线文档消息
System.out.println(res.getJSONObject("doc").toString());
}else if("markdown".equals(res.getStr("msgtype"))){
//MarkDown格式消息
System.out.println(res.getJSONObject("info").toString());
}else if("news".equals(res.getStr("msgtype"))){
//图文消息
System.out.println(res.getJSONObject("info").toString());
}else if("calendar".equals(res.getStr("msgtype"))){
//图文消息
System.out.println(res.getJSONObject("calendar").toString());
}else if("mixed".equals(res.getStr("msgtype"))){
//混合消息
JSONObject mixed = res.getJSONObject("mixed");
JSONArray jsonArray1 = mixed.getJSONArray("item");
if(!jsonArray1.isEmpty()){
for (int j = 0; j < jsonArray1.size(); j++) {
JSONObject object = jsonArray1.getJSONObject(j);
if(msgTypeList.contains(object.getStr("type"))){
//处理媒体消息
JSONObject object1 = JSONUtil.parseObj(object.getStr("content"));
Map<String,Object> map = iTxChatService.getMediaDataAndUpload(SDKID,object.getStr("type"),object1,proxy,null,10);
object.set("content",map);
jsonArray1.set(j,object);
}
}
}
mixed.set("item",jsonArray1);
System.out.println(mixed.toString());
}
}
}catch (Exception e){
e.printStackTrace();
}
}
//更新SEQ
tWxSysconfigMapper.updateTWxSysconfig(seqMap.get("seq").toString(),AppConstant.RECORD_SEQ);
}else{
//当前消息已拉完
break;
}
}else{
break;
}
log.info("******第{}次拉取完毕*****",a);
a++;
}
log.info("******会话拉取结束******");
return Result.success("会话拉取结束");
}
开发就已经基本完成了,部署的话我现在还没有测试,等到时候测试好了如果还有坑到时候我会补上。
以上我本地运行是可以获取到解密消息的。有兴趣的小伙伴可以去试试。
总结
以下是开发时参考的文章:
https://blog.csdn.net/weixin_48908035/article/details/124119947
个人感觉微信为什么不直接出api接口来调用,整个sdk坑的很