🆕 #1596 【企业微信】新增会话存档相关接口

This commit is contained in:
Wong
2022-01-24 16:09:16 +00:00
committed by Binary Wang
parent b8dd04b89f
commit c0535f87fb
19 changed files with 2308 additions and 13 deletions

View File

@@ -0,0 +1,207 @@
package com.tencent.wework;
import lombok.extern.slf4j.Slf4j;
/**
* 注意:
* 此类必须配置在com.tencent.wework路径底下否则会报错
* java.lang.UnsatisfiedLinkError: com.xxx.Finance.NewSdk()
* <p>
* QJAVA版本的sdk报错UnsatisfiedLinkError
* A请检查是否修改了sdk的包名。
* <p>
* 官方文档:
* https://developer.work.weixin.qq.com/document/path/91552
*
* @author Wang_Wong
* @date 2022-01-17
*/
@Slf4j
public class Finance {
private static volatile long sdk = -1L;
private static Finance finance = null;
private static final String SO_FILE = "so";
private static final String DLL_FILE = "dll";
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);
/**
* 判断Windows环境
*
* @return
*/
public static boolean isWindows() {
String osName = System.getProperties().getProperty("os.name");
log.info("Loading System Libraries, Current OS Version Is: {}", osName);
return osName.toUpperCase().contains("WINDOWS");
}
/**
* 加载系统类库
*
* @param libFiles 类库配置文件
* @param prefixPath 类库文件的前缀路径
*/
public Finance(String[] libFiles, String prefixPath) {
boolean isWindows = Finance.isWindows();
for (String file : libFiles) {
String suffix = file.substring(file.lastIndexOf(".") + 1);
if (isWindows) {
// 加载dll文件
if (suffix.equalsIgnoreCase(DLL_FILE)) {
System.load(prefixPath + file);
}
} else {
// 加载so文件
if (suffix.equalsIgnoreCase(SO_FILE)) {
System.load(prefixPath + file);
}
}
}
}
/**
* 初始化类库文件
*
* @param libFiles
* @param prefixPath
* @return
*/
public synchronized static Finance loadingLibraries(String[] libFiles, String prefixPath) {
if (finance != null) {
return finance;
}
finance = new Finance(libFiles, prefixPath);
return finance;
}
/**
* 单例sdk
*
* @return
*/
public synchronized static long SingletonSDK() {
if (sdk > 0) {
return sdk;
}
sdk = Finance.NewSdk();
return sdk;
}
/**
* 销毁sdk,保证线程可见性
*
* @return
*/
public synchronized static void DestroySingletonSDK(long destroySDK) {
sdk = 0L;
Finance.DestroySdk(destroySDK);
}
}

View File

@@ -0,0 +1,108 @@
package me.chanjar.weixin.cp.api;
import lombok.NonNull;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.cp.bean.msgaudit.*;
import java.util.List;
/**
* 会话内容存档接口.
* 官方文档https://developer.work.weixin.qq.com/document/path/91360
* <p>
* 如需自行实现亦可调用Finance类库函数进行实现
* com.tencent.wework.Finance
*
* @author Wang_Wong
* @date 2022-01-14
*/
public interface WxCpMsgAuditService {
/**
* 拉取聊天记录函数
*
* @param seq 从指定的seq开始拉取消息注意的是返回的消息从seq+1开始返回seq为之前接口返回的最大seq值。首次使用请使用seq:0
* @param limit 一次拉取的消息条数最大值1000条超过1000条会返回错误
* @param proxy 使用代理的请求需要传入代理的链接。如socks5://10.0.0.1:8081 或者 http://10.0.0.1:8081如果没有传null
* @param passwd 代理账号密码,需要传入代理的账号密码。如 user_name:passwd_123如果没有传null
* @param timeout 超时时间,根据实际需要填写
* @return 返回是否调用成功
*/
WxCpChatDatas getChatDatas(long seq, @NonNull long limit, String proxy, String passwd, @NonNull long timeout) throws Exception;
/**
* 获取解密的聊天数据Model
*
* @param chatData getChatDatas()获取到的聊天数据
* @return 解密后的聊天数据
* @throws Exception
*/
WxCpChatModel getDecryptData(@NonNull WxCpChatDatas.WxCpChatData chatData) throws Exception;
/**
* 获取解密的聊天数据明文
*
* @param chatData getChatDatas()获取到的聊天数据
* @return 解密后的明文
* @throws Exception
*/
String getChatPlainText(@NonNull WxCpChatDatas.WxCpChatData chatData) throws Exception;
/**
* 获取媒体文件
* 针对图片、文件等媒体数据提供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 targetFilePath 目标文件绝对路径+实际文件名,比如:/usr/local/file/20220114/474f866b39d10718810d55262af82662.gif
* @throws WxErrorException
*/
void getMediaFile(@NonNull String sdkfileid, String proxy, String passwd, @NonNull long timeout, @NonNull String targetFilePath) throws WxErrorException;
/**
* 获取会话内容存档开启成员列表
* 企业可通过此接口,获取企业开启会话内容存档的成员列表
* <p>
* 请求方式POSTHTTPS
* 请求地址https://qyapi.weixin.qq.com/cgi-bin/msgaudit/get_permit_user_list?access_token=ACCESS_TOKEN
*
* @param type 拉取对应版本的开启成员列表。1表示办公版2表示服务版3表示企业版。非必填不填写的时候返回全量成员列表。
* @return
* @throws WxErrorException
*/
List<String> getPermitUserList(Integer type) throws WxErrorException;
/**
* 获取会话内容存档内部群信息
* 企业可通过此接口获取会话内容存档本企业的内部群信息包括群名称、群主id、公告、群创建时间以及所有群成员的id与加入时间。
* <p>
* 请求方式POSTHTTPS
* 请求地址https://qyapi.weixin.qq.com/cgi-bin/msgaudit/groupchat/get?access_token=ACCESS_TOKEN
*
* @param roomid 待查询的群id
* @return
* @throws WxErrorException
*/
WxCpGroupChat getGroupChat(@NonNull String roomid) throws WxErrorException;
/**
* 获取会话同意情况
* 企业可通过下述接口,获取会话中外部成员的同意情况
* <p>
* 单聊请求地址https://qyapi.weixin.qq.com/cgi-bin/msgaudit/check_single_agree?access_token=ACCESS_TOKEN
* <p>
* 请求方式POSTHTTPS
*
* @param checkAgreeRequest 待查询的会话信息
* @return
* @throws WxErrorException
*/
WxCpAgreeInfo checkSingleAgree(@NonNull WxCpCheckAgreeRequest checkAgreeRequest) throws WxErrorException;
}

View File

@@ -399,6 +399,13 @@ public interface WxCpService extends WxService {
*/
WxCpLivingService getLivingService();
/**
* 获取会话存档相关接口的服务类对象
*
* @return
*/
WxCpMsgAuditService getMsgAuditService();
/**
* 获取日历相关接口的服务类对象
*

View File

@@ -50,6 +50,7 @@ public abstract class BaseWxCpServiceImpl<H, P> implements WxCpService, RequestH
private WxCpAgentService agentService = new WxCpAgentServiceImpl(this);
private WxCpOaService oaService = new WxCpOaServiceImpl(this);
private WxCpLivingService livingService = new WxCpLivingServiceImpl(this);
private WxCpMsgAuditService msgAuditService = new WxCpMsgAuditServiceImpl(this);
private WxCpTaskCardService taskCardService = new WxCpTaskCardServiceImpl(this);
private WxCpExternalContactService externalContactService = new WxCpExternalContactServiceImpl(this);
private WxCpGroupRobotService groupRobotService = new WxCpGroupRobotServiceImpl(this);
@@ -484,6 +485,11 @@ public abstract class BaseWxCpServiceImpl<H, P> implements WxCpService, RequestH
return livingService;
}
@Override
public WxCpMsgAuditService getMsgAuditService() {
return msgAuditService;
}
@Override
public WxCpOaCalendarService getOaCalendarService() {
return this.oaCalendarService;

View File

@@ -0,0 +1,196 @@
package me.chanjar.weixin.cp.api.impl;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import com.tencent.wework.Finance;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.util.json.GsonParser;
import me.chanjar.weixin.cp.api.WxCpMsgAuditService;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.bean.msgaudit.*;
import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil;
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
import org.apache.commons.lang3.StringUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.util.List;
import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.MsgAudit.*;
/**
* 会话内容存档接口实现类.
*
* @author Wang_Wong
* @date 2022-01-17
*/
@Slf4j
@RequiredArgsConstructor
public class WxCpMsgAuditServiceImpl implements WxCpMsgAuditService {
private final WxCpService cpService;
@Override
public WxCpChatDatas getChatDatas(long seq, @NonNull long limit, String proxy, String passwd, @NonNull long timeout) throws Exception {
String configPath = cpService.getWxCpConfigStorage().getMsgAuditLibPath();
if (StringUtils.isEmpty(configPath)) {
throw new WxErrorException("请配置会话存档sdk文件的路径不要配错了");
}
// 替换斜杠
String replacePath = configPath.replace("\\", "/");
// 所有的后缀文件
String suffixFiles = replacePath.substring(replacePath.lastIndexOf("/") + 1);
// 获取的前缀路径
String prefixPath = replacePath.substring(0, replacePath.lastIndexOf("/") + 1);
// 包含so文件
String[] libFiles = suffixFiles.split(",");
if (libFiles.length <= 0) {
throw new WxErrorException("请仔细配置会话存档文件路径!!");
}
Finance.loadingLibraries(libFiles, prefixPath);
long sdk = Finance.SingletonSDK();
long ret = Finance.Init(sdk, cpService.getWxCpConfigStorage().getCorpId(), cpService.getWxCpConfigStorage().getCorpSecret());
if (ret != 0) {
Finance.DestroySingletonSDK(sdk);
throw new WxErrorException("init sdk err ret " + ret);
}
long slice = Finance.NewSlice();
ret = Finance.GetChatData(sdk, seq, limit, proxy, passwd, timeout, slice);
if (ret != 0) {
Finance.FreeSlice(slice);
Finance.DestroySingletonSDK(sdk);
throw new WxErrorException("getchatdata err ret " + ret);
}
// 拉取会话存档
String content = Finance.GetContentFromSlice(slice);
Finance.FreeSlice(slice);
WxCpChatDatas chatDatas = WxCpChatDatas.fromJson(content);
if (chatDatas.getErrCode().intValue() != 0) {
throw new WxErrorException(chatDatas.toJson());
}
return chatDatas;
}
@Override
public WxCpChatModel getDecryptData(@NonNull WxCpChatDatas.WxCpChatData chatData) throws Exception {
String plainText = this.decryptChatData(chatData);
return WxCpChatModel.fromJson(plainText);
}
public String decryptChatData(WxCpChatDatas.WxCpChatData chatData) throws Exception {
// 企业获取的会话内容将用公钥加密企业可用自行保存的私钥解开会话内容数据aeskey不能为空
String priKey = cpService.getWxCpConfigStorage().getAesKey();
if (StringUtils.isEmpty(priKey)) {
throw new WxErrorException("请配置会话存档私钥【aesKey】");
}
String decryptByPriKey = WxCpCryptUtil.decryptByPriKey(chatData.getEncryptRandomKey(), priKey);
// 每次使用DecryptData解密会话存档前需要调用NewSlice获取一个slice在使用完slice中数据后还需要调用FreeSlice释放。
long sdk = Finance.SingletonSDK();
long msg = Finance.NewSlice();
int ret = Finance.DecryptData(sdk, decryptByPriKey, chatData.getEncryptChatMsg(), msg);
if (ret != 0) {
Finance.FreeSlice(msg);
Finance.DestroySingletonSDK(sdk);
throw new WxErrorException("msg err ret " + ret);
}
// 明文
String plainText = Finance.GetContentFromSlice(msg);
Finance.FreeSlice(msg);
return plainText;
}
@Override
public String getChatPlainText(WxCpChatDatas.@NonNull WxCpChatData chatData) throws Exception {
return this.decryptChatData(chatData);
}
@Override
public void getMediaFile(@NonNull String sdkfileid, String proxy, String passwd, @NonNull long timeout, @NonNull String targetFilePath) throws WxErrorException {
/**
* 1、媒体文件每次拉取的最大size为512k因此超过512k的文件需要分片拉取。
* 2、若该文件未拉取完整sdk的IsMediaDataFinish接口会返回0同时通过GetOutIndexBuf接口返回下次拉取需要传入GetMediaData的indexbuf。
* 3、indexbuf一般格式如右侧所示”Range:bytes=524288-1048575“:表示这次拉取的是从524288到1048575的分片。单个文件首次拉取填写的indexbuf为空字符串拉取后续分片时直接填入上次返回的indexbuf即可。
*/
File targetFile = new File(targetFilePath);
if (!targetFile.getParentFile().exists()) {
targetFile.getParentFile().mkdirs();
}
String indexbuf = "";
int ret, data_len = 0;
while (true) {
long mediaData = Finance.NewMediaData();
long sdk = Finance.SingletonSDK();
ret = Finance.GetMediaData(sdk, indexbuf, sdkfileid, proxy, passwd, timeout, mediaData);
if (ret != 0) {
Finance.FreeMediaData(mediaData);
Finance.DestroySingletonSDK(sdk);
throw new WxErrorException("getmediadata err ret " + ret);
}
data_len += Finance.GetDataLen(mediaData);
log.info("正在分片拉取媒体文件 len:{}, data_len:{}, is_finis:{} \n", Finance.GetIndexLen(mediaData), data_len, Finance.IsMediaDataFinish(mediaData));
try {
// 大于512k的文件会分片拉取此处需要使用追加写避免后面的分片覆盖之前的数据。
FileOutputStream outputStream = new FileOutputStream(new File(targetFilePath), 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);
}
}
}
@Override
public List<String> getPermitUserList(Integer type) throws WxErrorException {
final String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(GET_PERMIT_USER_LIST);
JsonObject jsonObject = new JsonObject();
if (type != null) {
jsonObject.addProperty("type", type);
}
String responseContent = this.cpService.post(apiUrl, jsonObject.toString());
return WxCpGsonBuilder.create().fromJson(GsonParser.parse(responseContent).getAsJsonArray("ids"),
new TypeToken<List<String>>() {
}.getType());
}
@Override
public WxCpGroupChat getGroupChat(@NonNull String roomid) throws WxErrorException {
final String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(GET_GROUP_CHAT);
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("roomid", roomid);
String responseContent = this.cpService.post(apiUrl, jsonObject.toString());
return WxCpGroupChat.fromJson(responseContent);
}
@Override
public WxCpAgreeInfo checkSingleAgree(@NonNull WxCpCheckAgreeRequest checkAgreeRequest) throws WxErrorException {
String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(CHECK_SINGLE_AGREE);
String responseContent = this.cpService.post(apiUrl, checkAgreeRequest.toJson());
return WxCpAgreeInfo.fromJson(responseContent);
}
}

View File

@@ -0,0 +1,65 @@
package me.chanjar.weixin.cp.bean.msgaudit;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
import java.io.Serializable;
import java.util.List;
/**
* 获取会话同意情况返回对象.
*
* @author Wang_Wong
*/
@Data
public class WxCpAgreeInfo implements Serializable {
private static final long serialVersionUID = -5028321625140879571L;
@SerializedName("errcode")
private Integer errcode;
@SerializedName("errmsg")
private String errmsg;
@SerializedName("agreeinfo")
private List<AgreeInfo> agreeInfo;
@Getter
@Setter
public static class AgreeInfo implements Serializable {
private static final long serialVersionUID = -5696099236344075582L;
@SerializedName("status_change_time")
private Long statusChangeTime;
@SerializedName("userid")
private String userid;
@SerializedName("exteranalopenid")
private String exteranalOpenId;
@SerializedName("agree_status")
private String agreeStatus;
public static AgreeInfo fromJson(String json) {
return WxCpGsonBuilder.create().fromJson(json, AgreeInfo.class);
}
public String toJson() {
return WxCpGsonBuilder.create().toJson(this);
}
}
public static WxCpAgreeInfo fromJson(String json) {
return WxCpGsonBuilder.create().fromJson(json, WxCpAgreeInfo.class);
}
public String toJson() {
return WxCpGsonBuilder.create().toJson(this);
}
}

View File

@@ -0,0 +1,68 @@
package me.chanjar.weixin.cp.bean.msgaudit;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
import java.io.Serializable;
import java.util.List;
/**
* 聊天记录数据内容.
*
* @author Wang_Wong
*/
@Data
public class WxCpChatDatas implements Serializable {
private static final long serialVersionUID = -5028321625140879571L;
@SerializedName("errcode")
private Integer errCode;
@SerializedName("errmsg")
private String errMsg;
@SerializedName("chatdata")
private List<WxCpChatData> chatData;
@Getter
@Setter
public static class WxCpChatData implements Serializable {
private static final long serialVersionUID = -5028321625140879571L;
@SerializedName("seq")
private Long seq;
@SerializedName("msgid")
private String msgId;
@SerializedName("publickey_ver")
private Integer publickeyVer;
@SerializedName("encrypt_random_key")
private String encryptRandomKey;
@SerializedName("encrypt_chat_msg")
private String encryptChatMsg;
public static WxCpChatData fromJson(String json) {
return WxCpGsonBuilder.create().fromJson(json, WxCpChatData.class);
}
public String toJson() {
return WxCpGsonBuilder.create().toJson(this);
}
}
public static WxCpChatDatas fromJson(String json) {
return WxCpGsonBuilder.create().fromJson(json, WxCpChatDatas.class);
}
public String toJson() {
return WxCpGsonBuilder.create().toJson(this);
}
}

View File

@@ -0,0 +1,987 @@
package me.chanjar.weixin.cp.bean.msgaudit;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
import java.io.Serializable;
import java.util.List;
/**
* 聊天记录数据内容.
*
* @author Wang_Wong
*/
@Data
public class WxCpChatModel implements Serializable {
private static final long serialVersionUID = -5028321625140879571L;
@SerializedName("msgid")
private String msgId;
@SerializedName("action")
private String action;
@SerializedName("send")
private String send;
@SerializedName("from")
private String from;
@SerializedName("tolist")
private String[] tolist;
@SerializedName("roomid")
private String roomId;
@SerializedName("msgtime")
private Long msgTime;
@SerializedName("msgtype")
private String msgType;
/**
* 文本
*/
@SerializedName("text")
private Text text;
/**
* 图片
*/
@SerializedName("image")
private Image image;
/**
* 撤回消息
*/
@SerializedName("revoke")
private Revoke revoke;
/**
* 同意会话聊天内容
*/
@SerializedName(value = "agree")
private Agree agree;
@SerializedName(value = "disagree")
private Agree disagree;
/**
* 语音
*/
@SerializedName(value = "voice")
private Voice voice;
/**
* 视频
*/
@SerializedName(value = "video")
private Video video;
/**
* 名片
*/
@SerializedName(value = "card")
private Card card;
/**
* 位置
*/
@SerializedName(value = "location")
private Location location;
/**
* 表情
*/
@SerializedName(value = "emotion")
private Emotion emotion;
/**
* 文件
*/
@SerializedName(value = "file")
private File file;
/**
* 链接
*/
@SerializedName(value = "link")
private Link link;
/**
* 小程序消息
*/
@SerializedName(value = "weapp")
private Weapp weapp;
/**
* 会话记录消息
*/
@SerializedName(value = "chatrecord")
private ChatRecord chatRecord;
/**
* 待办消息 官网暂无
*/
/**
* 投票消息 官网暂无
*/
/**
* 填表消息
*/
@SerializedName(value = "collect")
private Collect collect;
/**
* 红包消息
* 互通红包消息
*/
@SerializedName("redpacket")
private Redpacket redPacket;
/**
* 会议邀请消息
*/
@SerializedName("meeting")
private Meeting meeting;
/**
* 切换企业日志
*/
@SerializedName("time")
private Long time;
@SerializedName("user")
private String user;
/**
* 在线文档消息
*/
@SerializedName("doc")
private Doc doc;
@SerializedName("info")
private Info info;
/**
* 日程消息
*/
@SerializedName("calendar")
private Calendar calendar;
/**
* 混合消息
*/
@SerializedName("mixed")
private Mixed mixed;
/**
* 音频存档消息
*/
@SerializedName("voiceid")
private String voiceId;
@SerializedName("meeting_voice_call")
private MeetingVoiceCall meetingVoiceCall;
/**
* 音频共享文档消息
*/
@SerializedName("voipid")
private String voipId;
@SerializedName("voip_doc_share")
private WxCpFileItem voipDocShare;
/**
* 视频号消息
*/
@SerializedName("sphfeed")
private SphFeed sphFeed;
public static WxCpChatModel fromJson(String json) {
return WxCpGsonBuilder.create().fromJson(json, WxCpChatModel.class);
}
public String toJson() {
return WxCpGsonBuilder.create().toJson(this);
}
@Getter
@Setter
public static class Text implements Serializable {
private static final long serialVersionUID = -5028321625140879571L;
@SerializedName("content")
private String content;
public static Text fromJson(String json) {
return WxCpGsonBuilder.create().fromJson(json, Text.class);
}
public String toJson() {
return WxCpGsonBuilder.create().toJson(this);
}
}
@Getter
@Setter
public static class Image implements Serializable {
private static final long serialVersionUID = -5028321625140879571L;
@SerializedName("md5sum")
private String md5Sum;
@SerializedName("sdkfileid")
private String sdkFileId;
@SerializedName("filesize")
private Long fileSize;
public static Image fromJson(String json) {
return WxCpGsonBuilder.create().fromJson(json, Image.class);
}
public String toJson() {
return WxCpGsonBuilder.create().toJson(this);
}
}
@Getter
@Setter
public static class Revoke implements Serializable {
private static final long serialVersionUID = -5028321625140879571L;
@SerializedName("pre_msgid")
private String preMsgId;
public static Revoke fromJson(String json) {
return WxCpGsonBuilder.create().fromJson(json, Revoke.class);
}
public String toJson() {
return WxCpGsonBuilder.create().toJson(this);
}
}
@Getter
@Setter
public static class Agree implements Serializable {
private static final long serialVersionUID = -5028321625140879571L;
@SerializedName("userid")
private String userId;
@SerializedName(value = "agree_time")
private Long agreeTime;
@SerializedName(value = "disagree_time")
private Long disagreeTime;
public static Agree fromJson(String json) {
return WxCpGsonBuilder.create().fromJson(json, Agree.class);
}
public String toJson() {
return WxCpGsonBuilder.create().toJson(this);
}
}
@Getter
@Setter
public static class Voice implements Serializable {
private static final long serialVersionUID = -5028321625140879571L;
@SerializedName("md5sum")
private String md5Sum;
@SerializedName("sdkfileid")
private String sdkFileId;
@SerializedName("voice_size")
private Long voiceSize;
@SerializedName("play_length")
private Long playLength;
public static Voice fromJson(String json) {
return WxCpGsonBuilder.create().fromJson(json, Voice.class);
}
public String toJson() {
return WxCpGsonBuilder.create().toJson(this);
}
}
@Getter
@Setter
public static class Video implements Serializable {
private static final long serialVersionUID = -5028321625140879571L;
@SerializedName("md5sum")
private String md5Sum;
@SerializedName("sdkfileid")
private String sdkFileId;
@SerializedName("filesize")
private Long fileSize;
@SerializedName("play_length")
private Long playLength;
public static Video fromJson(String json) {
return WxCpGsonBuilder.create().fromJson(json, Video.class);
}
public String toJson() {
return WxCpGsonBuilder.create().toJson(this);
}
}
@Getter
@Setter
public static class Card implements Serializable {
private static final long serialVersionUID = -5028321625140879571L;
@SerializedName("corpname")
private String corpName;
@SerializedName("userid")
private String userId;
public static Card fromJson(String json) {
return WxCpGsonBuilder.create().fromJson(json, Card.class);
}
public String toJson() {
return WxCpGsonBuilder.create().toJson(this);
}
}
@Getter
@Setter
public static class Location implements Serializable {
private static final long serialVersionUID = -5028321625140879571L;
@SerializedName("longitude")
private Double longitude;
@SerializedName("latitude")
private Double latitude;
@SerializedName("address")
private String address;
@SerializedName("title")
private String title;
@SerializedName("zoom")
private Integer zoom;
public static Location fromJson(String json) {
return WxCpGsonBuilder.create().fromJson(json, Location.class);
}
public String toJson() {
return WxCpGsonBuilder.create().toJson(this);
}
}
@Getter
@Setter
public static class Emotion implements Serializable {
private static final long serialVersionUID = -5028321625140879571L;
@SerializedName("type")
private Integer type;
@SerializedName("width")
private Integer width;
@SerializedName("height")
private Integer height;
@SerializedName("title")
private String title;
@SerializedName("imagesize")
private Integer imageSize;
@SerializedName("md5sum")
private String md5Sum;
@SerializedName("sdkfileid")
private String sdkFileId;
public static Emotion fromJson(String json) {
return WxCpGsonBuilder.create().fromJson(json, Emotion.class);
}
public String toJson() {
return WxCpGsonBuilder.create().toJson(this);
}
}
@Getter
@Setter
public static class File implements Serializable {
private static final long serialVersionUID = -5028321625140879571L;
@SerializedName("md5sum")
private String md5Sum;
@SerializedName("filename")
private String fileName;
@SerializedName("fileext")
private String fileExt;
@SerializedName("sdkfileid")
private String sdkFileId;
@SerializedName("filesize")
private Integer fileSize;
public static File fromJson(String json) {
return WxCpGsonBuilder.create().fromJson(json, File.class);
}
public String toJson() {
return WxCpGsonBuilder.create().toJson(this);
}
}
@Getter
@Setter
public static class Link implements Serializable {
private static final long serialVersionUID = -5028321625140879571L;
@SerializedName("title")
private String title;
@SerializedName("description")
private String description;
@SerializedName("link_url")
private String linkUrl;
@SerializedName("image_url")
private String imageUrl;
public static Link fromJson(String json) {
return WxCpGsonBuilder.create().fromJson(json, Link.class);
}
public String toJson() {
return WxCpGsonBuilder.create().toJson(this);
}
}
/**
* 小程序消息
*/
@Getter
@Setter
public static class Weapp implements Serializable {
private static final long serialVersionUID = -5028321625140879571L;
@SerializedName("title")
private String title;
@SerializedName("description")
private String description;
@SerializedName("username")
private String userName;
@SerializedName("displayname")
private String displayName;
public static Weapp fromJson(String json) {
return WxCpGsonBuilder.create().fromJson(json, Weapp.class);
}
public String toJson() {
return WxCpGsonBuilder.create().toJson(this);
}
}
/**
* 会话记录消息
*/
@Getter
@Setter
public static class ChatRecord implements Serializable {
private static final long serialVersionUID = -5028321625140879571L;
@SerializedName(value = "item")
private List<ChatRecordItem> item;
@SerializedName("title")
private String title;
public static ChatRecord fromJson(String json) {
return WxCpGsonBuilder.create().fromJson(json, ChatRecord.class);
}
public String toJson() {
return WxCpGsonBuilder.create().toJson(this);
}
}
@Getter
@Setter
public static class ChatRecordItem implements Serializable {
private static final long serialVersionUID = -5028321625140879571L;
@SerializedName("type")
private String type;
@SerializedName("msgtime")
private Long msgTime;
@SerializedName("content")
private String content;
@SerializedName("from_chatroom")
private Boolean fromChatRoom;
public static ChatRecordItem fromJson(String json) {
return WxCpGsonBuilder.create().fromJson(json, ChatRecordItem.class);
}
public String toJson() {
return WxCpGsonBuilder.create().toJson(this);
}
}
/**
* 填表消息
*/
@Getter
@Setter
public static class Collect implements Serializable {
private static final long serialVersionUID = -5028321625140879571L;
@SerializedName("room_name")
private String roomName;
@SerializedName("creator")
private String creator;
@SerializedName("create_time")
private String createTime;
@SerializedName("title")
private String title;
@SerializedName("details")
private List<Details> details;
public static Collect fromJson(String json) {
return WxCpGsonBuilder.create().fromJson(json, Collect.class);
}
public String toJson() {
return WxCpGsonBuilder.create().toJson(this);
}
}
@Getter
@Setter
public static class Details implements Serializable {
private static final long serialVersionUID = -5028321625140879571L;
@SerializedName("id")
private Long id;
@SerializedName("ques")
private String ques;
@SerializedName("type")
private String type;
public static Details fromJson(String json) {
return WxCpGsonBuilder.create().fromJson(json, Details.class);
}
public String toJson() {
return WxCpGsonBuilder.create().toJson(this);
}
}
/**
* 红包消息
*/
@Getter
@Setter
public static class Redpacket implements Serializable {
private static final long serialVersionUID = -5028321625140879571L;
@SerializedName("type")
private Integer type;
@SerializedName("totalcnt")
private Integer totalCnt;
@SerializedName("totalamount")
private Integer totalAmount;
@SerializedName("wish")
private String wish;
public static Redpacket fromJson(String json) {
return WxCpGsonBuilder.create().fromJson(json, Redpacket.class);
}
public String toJson() {
return WxCpGsonBuilder.create().toJson(this);
}
}
/**
* 会议邀请消息
*/
@Getter
@Setter
public static class Meeting implements Serializable {
private static final long serialVersionUID = -5028321625140879571L;
@SerializedName("topic")
private String topic;
@SerializedName("starttime")
private Long startTime;
@SerializedName("endtime")
private Long endTime;
@SerializedName("address")
private String address;
@SerializedName("remarks")
private String remarks;
@SerializedName("meetingtype")
private Integer meetingType;
@SerializedName("meetingid")
private Long meetingId;
@SerializedName("status")
private Integer status;
public static Meeting fromJson(String json) {
return WxCpGsonBuilder.create().fromJson(json, Meeting.class);
}
public String toJson() {
return WxCpGsonBuilder.create().toJson(this);
}
}
/**
* 在线文档消息
*/
@Getter
@Setter
public static class Doc implements Serializable {
private static final long serialVersionUID = -5028321625140879571L;
@SerializedName("title")
private String title;
@SerializedName("doc_creator")
private String docCreator;
@SerializedName("link_url")
private String linkUrl;
public static Doc fromJson(String json) {
return WxCpGsonBuilder.create().fromJson(json, Doc.class);
}
public String toJson() {
return WxCpGsonBuilder.create().toJson(this);
}
}
/**
* MarkDown格式消息
*/
@Getter
@Setter
public static class Info implements Serializable {
private static final long serialVersionUID = -5028321625140879571L;
@SerializedName("content")
private String content;
@SerializedName("item")
private List<NewsItem> newsItem;
public static Info fromJson(String json) {
return WxCpGsonBuilder.create().fromJson(json, Info.class);
}
public String toJson() {
return WxCpGsonBuilder.create().toJson(this);
}
}
/**
* 图文消息
*/
@Getter
@Setter
public static class NewsItem implements Serializable {
private static final long serialVersionUID = -5028321625140879571L;
@SerializedName("title")
private String title;
@SerializedName("description")
private String description;
@SerializedName("url")
private String url;
@SerializedName("picurl")
private String picUrl;
public static NewsItem fromJson(String json) {
return WxCpGsonBuilder.create().fromJson(json, NewsItem.class);
}
public String toJson() {
return WxCpGsonBuilder.create().toJson(this);
}
}
/**
* 日程消息
*/
@Getter
@Setter
public static class Calendar implements Serializable {
private static final long serialVersionUID = -5028321625140879571L;
@SerializedName("title")
private String title;
@SerializedName("creatorname")
private String creatorName;
@SerializedName("attendeename")
private String[] attendeeName;
@SerializedName("starttime")
private Long startTime;
@SerializedName("endtime")
private Long endTime;
@SerializedName("place")
private String place;
@SerializedName("remarks")
private String remarks;
public static Calendar fromJson(String json) {
return WxCpGsonBuilder.create().fromJson(json, Calendar.class);
}
public String toJson() {
return WxCpGsonBuilder.create().toJson(this);
}
}
/**
* 混合消息
*/
@Getter
@Setter
public static class Mixed implements Serializable {
private static final long serialVersionUID = -5028321625140879571L;
@SerializedName("item")
private List<Item> item;
@Getter
@Setter
public static class Item implements Serializable {
private static final long serialVersionUID = -5028321625140879571L;
@SerializedName("type")
private String type;
@SerializedName("content")
private String content;
}
}
/**
* 音频存档消息
*/
@Getter
@Setter
public static class MeetingVoiceCall implements Serializable {
private static final long serialVersionUID = -5028321625140879571L;
@SerializedName("endtime")
private Long endTime;
@SerializedName("sdkfileid")
private String sdkFileId;
@SerializedName("demofiledata")
private List<DemoFileData> demoFileData;
@SerializedName("sharescreendata")
private List<ShareScreenData> shareScreenData;
public static MeetingVoiceCall fromJson(String json) {
return WxCpGsonBuilder.create().fromJson(json, MeetingVoiceCall.class);
}
public String toJson() {
return WxCpGsonBuilder.create().toJson(this);
}
@Getter
@Setter
public static class DemoFileData implements Serializable {
private static final long serialVersionUID = -5028321625140879571L;
@SerializedName("filename")
private String fileName;
@SerializedName("demooperator")
private String demoOperator;
@SerializedName("starttime")
private Long startTime;
@SerializedName("endtime")
private Long endTime;
public static DemoFileData fromJson(String json) {
return WxCpGsonBuilder.create().fromJson(json, DemoFileData.class);
}
public String toJson() {
return WxCpGsonBuilder.create().toJson(this);
}
}
@Getter
@Setter
public static class ShareScreenData implements Serializable {
private static final long serialVersionUID = -5028321625140879571L;
@SerializedName("share")
private String share;
@SerializedName("starttime")
private Long startTime;
@SerializedName("endtime")
private Long endTime;
public static ShareScreenData fromJson(String json) {
return WxCpGsonBuilder.create().fromJson(json, ShareScreenData.class);
}
public String toJson() {
return WxCpGsonBuilder.create().toJson(this);
}
}
}
/**
* 视频号消息
*/
@Getter
@Setter
public static class SphFeed implements Serializable {
private static final long serialVersionUID = -5028321625140879571L;
@SerializedName("feed_type")
private Integer feedType;
@SerializedName("sph_name")
private String sphName;
@SerializedName("feed_desc")
private String feedDesc;
public static SphFeed fromJson(String json) {
return WxCpGsonBuilder.create().fromJson(json, SphFeed.class);
}
public String toJson() {
return WxCpGsonBuilder.create().toJson(this);
}
}
}

View File

@@ -0,0 +1,57 @@
package me.chanjar.weixin.cp.bean.msgaudit;
import com.google.gson.annotations.SerializedName;
import lombok.*;
import lombok.experimental.Accessors;
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
import java.io.Serializable;
import java.util.List;
/**
* 获取会话同意情况请求参数.
*
* @author Wang_Wong
* @date 2022-01-21
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class WxCpCheckAgreeRequest implements Serializable {
private static final long serialVersionUID = -4960239393895754138L;
@SerializedName("info")
private List<Info> info;
public static WxCpCheckAgreeRequest fromJson(String json) {
return WxCpGsonBuilder.create().fromJson(json, WxCpCheckAgreeRequest.class);
}
public String toJson() {
return WxCpGsonBuilder.create().toJson(this);
}
@Getter
@Setter
public static class Info implements Serializable {
private static final long serialVersionUID = -4960239393895754138L;
@SerializedName("userid")
private String userid;
@SerializedName("exteranalopenid")
private String exteranalOpenId;
public static Info fromJson(String json) {
return WxCpGsonBuilder.create().fromJson(json, Info.class);
}
public String toJson() {
return WxCpGsonBuilder.create().toJson(this);
}
}
}

View File

@@ -0,0 +1,39 @@
package me.chanjar.weixin.cp.bean.msgaudit;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
import java.io.Serializable;
/**
* 会话存档 文档信息对象
*
* @author Wang_Wong
*/
@Data
public class WxCpFileItem implements Serializable {
private static final long serialVersionUID = -5028321625140879571L;
@SerializedName("filename")
private String fileName;
@SerializedName("md5sum")
private String md5Sum;
@SerializedName("sdkfileid")
private String sdkFileId;
@SerializedName("filesize")
private Long fileSize;
public static WxCpFileItem fromJson(String json) {
return WxCpGsonBuilder.create().fromJson(json, WxCpFileItem.class);
}
public String toJson() {
return WxCpGsonBuilder.create().toJson(this);
}
}

View File

@@ -0,0 +1,61 @@
package me.chanjar.weixin.cp.bean.msgaudit;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
import java.io.Serializable;
import java.util.List;
/**
* 内部群信息
*
* @author Wang_Wong
*/
@Data
public class WxCpGroupChat implements Serializable {
private static final long serialVersionUID = -5028321625140879571L;
@SerializedName("roomname")
private String roomName;
@SerializedName("creator")
private String creator;
@SerializedName("room_create_time")
private Long roomCreateTime;
@SerializedName("notice")
private String notice;
private List<Member> members;
@Getter
@Setter
public class Member implements Serializable {
private static final long serialVersionUID = -5028321625140879571L;
@SerializedName("memberid")
private String memberId;
@SerializedName("jointime")
private Long joinTime;
public Member fromJson(String json) {
return WxCpGsonBuilder.create().fromJson(json, Member.class);
}
}
public static WxCpGroupChat fromJson(String json) {
return WxCpGsonBuilder.create().fromJson(json, WxCpGroupChat.class);
}
public String toJson() {
return WxCpGsonBuilder.create().toJson(this);
}
}

View File

@@ -174,6 +174,13 @@ public interface WxCpConfigStorage {
*/
String getAesKey();
/**
* 获取企微会话存档系统库 绝对路径
*
* @return
*/
String getMsgAuditLibPath();
/**
* Gets expires time.
*

View File

@@ -43,6 +43,7 @@ public class WxCpDefaultConfigImpl implements WxCpConfigStorage, Serializable {
private volatile String token;
private volatile String aesKey;
private volatile long expiresTime;
private volatile String msgAuditLibPath;
private volatile String oauth2redirectUri;
private volatile String httpProxyHost;
private volatile int httpProxyPort;
@@ -256,6 +257,11 @@ public class WxCpDefaultConfigImpl implements WxCpConfigStorage, Serializable {
return this.aesKey;
}
@Override
public String getMsgAuditLibPath() {
return this.msgAuditLibPath;
}
/**
* Sets aes key.
*

View File

@@ -40,6 +40,7 @@ public class WxCpRedisConfigImpl implements WxCpConfigStorage {
private volatile String token;
private volatile String aesKey;
private volatile Integer agentId;
private volatile String msgAuditLibPath;
private volatile String oauth2redirectUri;
private volatile String httpProxyHost;
private volatile int httpProxyPort;
@@ -320,6 +321,11 @@ public class WxCpRedisConfigImpl implements WxCpConfigStorage {
return this.aesKey;
}
@Override
public String getMsgAuditLibPath() {
return this.msgAuditLibPath;
}
/**
* Sets aes key.
*

View File

@@ -129,6 +129,12 @@ public interface WxCpApiPathConsts {
String DELETE_REPLAY_DATA = "/cgi-bin/living/delete_replay_data";
}
interface MsgAudit {
String GET_PERMIT_USER_LIST = "/cgi-bin/msgaudit/get_permit_user_list";
String GET_GROUP_CHAT = "/cgi-bin/msgaudit/groupchat/get";
String CHECK_SINGLE_AGREE = "/cgi-bin/msgaudit/check_single_agree";
}
interface Tag {
String TAG_CREATE = "/cgi-bin/tag/create";
String TAG_UPDATE = "/cgi-bin/tag/update";

View File

@@ -4,6 +4,12 @@ import com.google.common.base.CharMatcher;
import com.google.common.io.BaseEncoding;
import me.chanjar.weixin.common.util.crypto.WxCryptUtil;
import me.chanjar.weixin.cp.config.WxCpConfigStorage;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
public class WxCpCryptUtil extends WxCryptUtil {
public WxCpCryptUtil(WxCpConfigStorage wxCpConfigStorage) {
@@ -21,4 +27,31 @@ public class WxCpCryptUtil extends WxCryptUtil {
this.aesKey = BaseEncoding.base64().decode(CharMatcher.whitespace().removeFrom(encodingAesKey));
}
/**
* 会话存档接口解密私钥
* 企业获取的会话内容将用公钥加密,企业用自行保存的私钥解开会话内容数据
*
* @param encryptRandomKey
* @param msgAuditPriKey
* @return
* @throws Exception
*/
public static String decryptByPriKey(String encryptRandomKey, String msgAuditPriKey) throws Exception {
String privateKey = msgAuditPriKey.replaceAll("\\n", "")
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll(" ", "");
byte[] keyByte = Base64.decodeBase64(privateKey);
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyByte);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey priKey = keyFactory.generatePrivate(pkcs8KeySpec);
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, priKey);
byte[] utf8 = cipher.doFinal(Base64.decodeBase64(encryptRandomKey));
return new String(utf8, "UTF-8");
}
}