1.准备
1.从企业微信官方文档下载demo
官方文档地址
1.工具类Finance
package com.tencent.wework;
/**
* @author byChen
* @date 2022/1/26
*/
/* sdk返回数据
typedef struct Slice_t {
char* buf;
int len;
} Slice_t;
typedef struct MediaData {
char* outindexbuf;
int out_len;
char* data;
int data_len;
int is_finish;
} MediaData_t;
*/
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);
/**
* @param [in] encrypt_key, getchatdata返回的encrypt_key
* @param [in] encrypt_msg, getchatdata返回的content
* @param [out] msg, 解密的消息明文
* @return 返回是否调用成功
* 0 - 成功
* !=0 - 失败
* @brief 解析密文
*/
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();
/**
* @return
* @brief 释放slice,和NewSlice成对使用
*/
public native static void FreeSlice(long slice);
/**
* @return 内容
* @brief 获取slice内容
*/
public native static String GetContentFromSlice(long slice);
/**
* @return 内容
* @brief 获取slice内容长度
*/
public native static int GetSliceLen(long slice);
public native static long NewMediaData();
public native static void FreeMediaData(long mediaData);
/**
* @return outindex
* @brief 获取mediadata outindex
*/
public native static String GetOutIndexBuf(long mediaData);
/**
* @return data
* @brief 获取mediadata data数据
*/
public native static byte[] GetData(long mediaData);
public native static int GetIndexLen(long mediaData);
public native static int GetDataLen(long mediaData);
/**
* @return 1完成、0未完成
* @brief 判断mediadata是否结束
*/
public native static int IsMediaDataFinish(long mediaData);
static {
// System.loadLibrary("WeWorkFinanceSdk");
/**
* dll文件需要放在c:windows/system32文件夹下一份
*/
String path = Finance.class.getResource("").getPath().replaceAll("%20", " ").replaceFirst("/", "").replace("/", "\\\\");
System.load(path.concat("libcrypto-1_1-x64.dll"));
System.load(path.concat("libcurl-x64.dll"));
System.load(path.concat("libssl-1_1-x64.dll"));
System.load(path.concat("WeWorkFinanceSdk.dll"));
}
}
2.几个dll文件
①dll文件下载后放入电脑C盘C:\Windows\System32目录下一份
②项目中也同样需要放入一份
3.项目结构必须是这样的:
4.pom文件里面还要配置,配置打包时,将dll文件一块打包
<!--指定打包文件-->
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.dll</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
2.开始编码,提炼出工具类
/**
* 企业微信会话存档工具类
* @author byChen
* @date 2022/1/26
*/
@Component
public class MsgFileUtils {
/**
* 企业微信里面的私钥内容
*/
private static final String privKeyPEM = "-----BEGIN RSA PRIVATE KEY-----" +
"你的私钥" +
"-----END RSA PRIVATE KEY-----";
/**
* 根据资源名称,获取资源类型
* 只处理图片、视频、语音、文件
*
* @param dto
* @return
*/
public static String getFileType(MsgConDto dto) {
String fileType = "";
JSONObject jsonObject = JSONObject.parseObject(dto.getStrData());
String msgType = dto.getMsgType();
switch (msgType) {
case "image":
fileType = ".jpg";
break;
case "voice":
fileType = ".mp3";
break;
case "video":
fileType = ".mp4";
break;
case "file":
fileType = "." + jsonObject.getString("fileext");
break;
default:
fileType = null;
break;
}
return fileType;
}
/**
* rsa私钥(企业微信的privKeyPEM)解密encrypt_random_key
* (利用私钥解密公钥)
*
* @return
*/
public static String decryptMsg(String encrypt_random_key) throws Exception {
//64位解密后的字符串(需首先对每条消息的encrypt_random_key内容进行base64 decode,得到字符串str1.)
byte[] decode = Base64.getDecoder().decode(encrypt_random_key);
//获取私钥
PrivateKey privateKey = RSAUtils.getPrivateKey(privKeyPEM);
//RSA解密(使用RSA PKCS1算法对str1进行解密)
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] bytes = cipher.doFinal(decode);
//(得到解密内容)
String encrypt_key = new String(bytes);
return encrypt_key;
}
/**
* 拉取会话存档
*
* @param properties
* @param seq
* @throws Exception
*/
public static List<MsgContent> PullChatStorage(QyWeChatConfigProperties properties, Integer seq) throws Exception {
/**
* 获取sdk对象,然后初始化函数
* 使用sdk前需要初始化,初始化成功后的sdk可以一直使用
* 如需并发调用sdk,建议每个线程持有一个sdk实例
*/
long sdk = Finance.NewSdk();
Finance.Init(sdk, properties.getCorpId(), properties.getSecret4());
/**
* 拉取会话内容前,参数准备
*/
int limit = 100;//一次拉取多少条信息,最大1000
String proxy = null;
String passwd = null;
int timeout = 2000;//超时时间
/**
* 每次使用GetChatData拉取存档前需要调用NewSlice获取一个slice用来接受产出值,
* 在使用完slice中数据后,还需要调用FreeSlice释放。
*/
long slice = Finance.NewSlice();
int i = Finance.GetChatData(sdk, seq, limit, proxy, passwd, timeout, slice);
//根据返回结果判断是不是成功
if (i != 0) {
System.out.println("getchatdata ret " + i);
//释放
Finance.FreeSlice(slice);
System.out.println("拉取会话存档失败");
return null;
}
/**
* 将返回的slice对象转成JSON字符串
* slice结构体:
* "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"
* },
* {...},
* {...},
* ...拉取多条
* ]
*/
String sliceStr = Finance.GetContentFromSlice(slice);
// System.out.println("返回本次拉取消息的数据" + sliceStr);
//转化成实体
SliceDate sliceDate = JSONObject.parseObject(sliceStr, SliceDate.class);
//释放
Finance.FreeSlice(slice);
/**
* 遍历每一条未解密会话,进行解密、判断等操作
*/
List<MsgContent> msgContentList=new ArrayList<>();
for (ChatData chatData : sliceDate.getChatdata()) {
String encrypt_random_key = chatData.getEncrypt_random_key();
//此处需要先用rsa私钥解密encrypt_random_key后,作为encrypt_key参数传入sdk
String encrypt_key = MsgFileUtils.decryptMsg(encrypt_random_key);
long msg = Finance.NewSlice();
//进行会话的解密
Finance.DecryptData(sdk, encrypt_key, chatData.getEncrypt_chat_msg(), msg);
//将解密的消息明文转化为字符串
String s = Finance.GetContentFromSlice(msg);
// System.out.println("解密后的消息明文为:" + s);
//释放
Finance.FreeSlice(msg);
/**
* 将消息明文,压制为实体
*/
JSONObject content = JSONObject.parseObject(s);
MsgContent msgContent = new MsgContent();
if ("send".equals(content.get("action"))) {//发送消息
//消息的seq值,标识消息的序号
msgContent.setSeq(chatData.getSeq());
//消息id
msgContent.setMsgid(content.getString("msgid"));
//消息动作
msgContent.setMesAction(content.getString("action"));
//存入发送人id(可用此id去表中获取到名字)
msgContent.setMesFrom(content.getString("from"));
//接收人id可以是列表,因此需要处理
String tolist = content.getString("tolist");
String replace = tolist.replace("[", "").replace("]", "").replace("\"", "");
msgContent.setTolist(replace);
msgContent.setRoomid(content.getString("roomid"));
//存入创建时间
String msgtime = content.getString("msgtime");
Long time = Long.valueOf(msgtime);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = new Date(time);
String format = simpleDateFormat.format(date);
msgContent.setMsgtime(format);
//消息类型
msgContent.setMsgtype(content.getString("msgtype"));
switch (msgContent.getMsgtype()) {
case "text":
//处理文本消息,对文本消息进行获取,其他类型消息直接返回全部字符串
String text = content.getString("text");
if (text != null) {
JSONObject jsonObject1 = JSONObject.parseObject(text);
String content1 = jsonObject1.getString("content");
if (content1 != null) {
//存入文本消息的文本内容
msgContent.setText(content1);
}
}
break;
case "image":
msgContent.setStrData(content.getString("image"));
break;
case "weapp":
msgContent.setStrData(content.getString("weapp"));
break;
case "file":
msgContent.setStrData(content.getString("file"));
break;
case "video":
msgContent.setStrData(content.getString("video"));
break;
case "voice":
msgContent.setStrData(content.getString("voice"));
break;
case "chatrecord":
msgContent.setText(content.getString("chatrecord"));
case "link":
msgContent.setLink(content.getString("link"));
default:
}
msgContentList.add(msgContent);
}
}
//关闭SDK
Finance.DestroySdk(sdk);
System.out.println("----------------------scheduled tasks qywx data success-----------------------");
return msgContentList;
}
}
/**
* 下载会话存档中的资源
* @param sdkfileid
* @param fileName
* @param properties
*/
public static void downLodaFile(String sdkfileid,String fileName,QyWeChatConfigProperties properties){
int ret = 0;
long sdk = Finance.NewSdk();
// 初始化
Finance.Init(sdk, properties.getCorpId(), properties.getSecret4());
String indexbuf = "";
while (true) {
long media_data = Finance.NewMediaData();
ret = Finance.GetMediaData(sdk, indexbuf, sdkfileid, null, null, 3, media_data);
if (ret != 0) {
return;
}
System.out.printf("getmediadata outindex len:%d, data_len:%d, is_finis:%d\n",
Finance.GetIndexLen(media_data), Finance.GetDataLen(media_data),
Finance.IsMediaDataFinish(media_data));
try {
FileOutputStream outputStream = new FileOutputStream(new File("H:\\scrm-chat-file\\" + fileName), true);
outputStream.write(Finance.GetData(media_data));
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
if (Finance.IsMediaDataFinish(media_data) == 1) {
Finance.FreeMediaData(media_data);
break;
} else {
indexbuf = Finance.GetOutIndexBuf(media_data);
Finance.FreeMediaData(media_data);
}
}
Finance.DestroySdk(sdk);
}
直接复制,替换必要参数就行
3.调用工具类
1.调用拉取存档
/**
* 拉取会话记录
* 从上次拉取最新开始算起,每次访问拉取100条
* @return
* @throws WxErrorException
* @throws IOException
*/
@RequestMapping(value = "/uploadImg", method = RequestMethod.GET)
public R uploadImg(Page<MsgContent> page) throws Exception {
//获取本次拉取开始的位置
int seq = msgContentService.getLastSeq() == null ? 0 : msgContentService.getLastSeq();// 从第几条开始拉取
//工具类进行拉取
List<MsgContent> msgContents = MsgFileUtils.PullChatStorage(properties, seq);
if (msgContents==null){
return R.ok("拉取存档失败");
}
//拉取的存入数据库
for (MsgContent msgContent : msgContents) {
msgContentService.saveCheckIfHave(msgContent);
}
return R.ok("拉取完成");
}
getLastSeq:获取应该从那一条开始拉取
@Select("SELECT seq FROM msg_content ORDER BY id DESC limit 1")
Integer getLastSeq();
saveCheckIfHave:判断是否存在,插入
/**
* 判断本次拉取的数据是否以及存在
* 不存在才插入
* @param msgContent
*/
@Override
public void saveCheckIfHave(MsgContent msgContent) {
MsgContent msgContent1 = baseMapper.selectOne(Wrappers.<MsgContent>lambdaQuery()
.eq(MsgContent::getMsgid, msgContent.getMsgid()));
if (msgContent1==null){
baseMapper.insert(msgContent);
}
}
properties:存在nacos上的企业微信参数映射类
/**
* 企业微信参数,对应nacos配置映射
* @author byChen
* @date 2022/1/18
*/
@Data
@RefreshScope
@Configuration
@ConfigurationProperties(prefix = "qychat")
public class QyWeChatConfigProperties {
/**
* 企业id
*/
private String corpId;
/**
* 通讯录应用id
*/
private Integer agentId1;
/**
* 外部联系人应用id
*/
private Integer agentId2;
/**
* 消息推送应用id
*/
private Integer agentId3;
/**
* 通讯录应用密钥
*/
private String secret1;
/**
* 外部联系人应用密钥
*/
private String secret2;
/**
* 消息推送应用密钥
*/
private String secret3;
/**
* 会话存档密钥
*/
private String secret4;
/**
* 通讯录回调应用密钥
*/
private String EncodingAESKey1;
/**
* 外部联系人回调应用密钥
*/
private String EncodingAESKey2;
/**
* 通讯录回调应用密钥
*/
private String token1;
/**
* 外部联系人回调应用密钥
*/
private String token2;
}
2.调用下载存档文件资源
/**
* 下载会话中存在的文件
* fileName 会话返回的媒体图片资源的md5值+格式
* sdkfileid 会话返回的媒体资源的id信息
*
* @param
* @param
*/
@GetMapping("/downFile")
public void downLodaFile(@RequestBody MsgConDto dto) {
//获取必要参数
JSONObject content = JSONObject.parseObject(dto.getStrData());
//会话返回的媒体资源的id信息
String sdkfileid = content.getString("sdkfileid");
//拼接成的文件名
String fileName = content.getString("md5sum") + MsgFileUtils.getFileType(dto);
//进行下载 下载目录为 H:\scrm-chat-file\
MsgFileUtils.downLodaFile(sdkfileid, fileName, properties);
}
MsgConDto
/**
* @author byChen
* @date 2022/1/26
*/
@Data
public class MsgConDto {
/**
* 资源数据
*/
private String strData;
/**
* 消息类型
*/
private String msgType;
}
完成