栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

企业微信消息存档

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

企业微信消息存档

企业微信消息存档
一、配置动态库

1.企业微信官方文档下载sdk:https://open.work.weixin.qq.com/api/doc/90000/90135/91774#%E6%95%B4%E4%BD%93%E6%B5%81%E7%A8%8B

2.将sdk中的libWeWorkFinanceSdk_Java.so文件上传到服务器/usr/local/lib路径,路径可自定义,爱搁哪搁哪

3.配置环境变量,修改~/.bashrc
增加 "export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib,修改了路径配置的变量路径要相应修改


二、拉取数据

新建包com.tencent.wework
新建包com.tencent.wework
新建包com.tencent.wework
(重要的事说三遍)
把sdk中的Finance文件丢进去

有使用到大佬封装的jar包,github搜一下去琢磨文档


  com.github.binarywang
  weixin-java-cp
  4.1.0

解密的私钥放在工程resources/privateKey目录下
获取到回调之后去拉取存档消息
WxCpMessageArchive类是最终解析出来的数据

import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage;
import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage;
import me.chanjar.weixin.cp.config.WxCpConfigStorage;
import me.chanjar.weixin.cp.message.WxCpMessageHandler;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ResourceLoader;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.*;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

@Component
@Slf4j
public class MsgauditNotifyEventHandle implements WxCpMessageHandler {

    @Autowired
    ResourceLoader resourceLoader;

    public final Map FINANCE_MAP = new HashMap<>();
    private final Map PRIVATE_KEY_MAP = new HashMap<>();
    private final Map MSG_TYPE_MAP = new HashMap<>();

    @PostConstruct
    public void initConvertMap() throws Exception {
        //检查动态库是否存在
        File file = new File("/usr/local/lib/libWeWorkFinanceSdk_Java.so");
        List execute = CmdUtils.execute("cat ~/.bashrc");
        boolean contains = execute.contains("export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib");
        if (!file.exists() || !contains){
            throw new BizException("会话存档所需动态库未配置nn" +
                    "1.企业微信官方文档下载sdk:https://open.work.weixin.qq.com/api/doc/90000/90135/91774#%E6%95%B4%E4%BD%93%E6%B5%81%E7%A8%8Bn" +
                    "2.将sdk中的libWeWorkFinanceSdk_Java.so文件上传到服务器/usr/local/lib路径n" +
                    "3.配置环境变量,修改~/.bashrc,增加 "export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib"n");
        }
        MSG_TYPE_MAP.put("external_redpacket", "redpacket");
        MSG_TYPE_MAP.put("news", "info");
        MSG_TYPE_MAP.put("markdown", "info");
        MSG_TYPE_MAP.put("docmsg", "doc");
    }

    @Override
    public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map context, WxCpService wxCpService,
                                    WxSessionManager sessionManager) {
        String appId = wxMessage.getToUserName();
        Integer agentId = wxMessage.getAgentId();
        String key = appId + "#" + agentId;
        String privateKey = PRIVATE_KEY_MAP.getOrDefault(key, null);
        if (StringUtils.isBlank(privateKey)) {
            try {
                privateKey = this.readerFile("/privateKey/" + appId + "#" + agentId + "#private.pem");
            } catch (IOException ignored) {
            }
            if (StringUtils.isBlank(privateKey)) {
                log.error("会话存档初始化失败,未获取到解密私钥,appId:{},agentId:{}", appId, agentId);
                return null;
            }
            PRIVATE_KEY_MAP.put(key, privateKey);
        }
        Long sdk = this.getSdk(appId, agentId);
        if (Objects.isNull(sdk)){
            return null;
        }
        //每次使用GetChatData拉取存档前需要调用NewSlice获取一个slice,在使用完slice中数据后,还需要调用FreeSlice释放。
        long slice = Finance.NewSlice();
        long oldSeq = 0;
        WxCpMessageArchive lastSeq = this.getLastSeq();
        if (Objects.nonNull(lastSeq)) {
            oldSeq = lastSeq;
        }
        long ret = Finance.GetChatData(sdk, oldSeq, 1000, "", "", 5, slice);
        if (ret != 0) {
            log.error("会话存档获取失败,appId:{},agentId:{},seq:{}", appId, agentId, oldSeq);
            Finance.FreeSlice(slice);
            return null;
        }
        String content = Finance.GetContentFromSlice(slice);
        MessageResult messageResult = JSONObject.toJavaObject(JSONObject.parseObject(content), MessageResult.class);
        Integer errCode = messageResult.getErrCode();
        if (Objects.isNull(errCode) || errCode != 0) {
            log.error("会话存档获取失败,appId:{}, agentId:{}, seq:{}, errorMsg:{}", appId, agentId, oldSeq,
                    messageResult.getErrMsg());
            return null;
        }
        List chatData = messageResult.getChatData();
        this.dataToQueue(chatData, sdk, slice, key, appId, agentId);
        Finance.FreeSlice(slice);
        return null;
    }

    private void dataToQueue(List chatData, long sdk, long slice, String key,
                             String appId, Integer agentId) {
        if (CollectionUtils.isEmpty(chatData)) {
            return;
        }
        for (MessageResult.Message chatDatum : chatData) {
            String publicKeyVer = chatDatum.getPublicKeyVer();
            try {
                Integer seq = chatDatum.getSeq();
                String encryptRandomKey = chatDatum.getEncryptRandomKey();
                String encryptChatMsg = chatDatum.getEncryptChatMsg();
                String decrypt = RsaUtil.pkcs1decrypt(encryptRandomKey, PRIVATE_KEY_MAP.get(key));
                Finance.DecryptData(sdk, decrypt, encryptChatMsg, slice);
                String message = Finance.GetContentFromSlice(slice);
                JSONObject jsonObject = JSONObject.parseObject(message);
                WxCpMessageArchiveVo wxCpMessageArchiveVo = JSONObject.toJavaObject(jsonObject,
                        WxCpMessageArchiveVo.class);
                wxCpMessageArchiveVo.setContent(this.getContent(wxCpMessageArchiveVo.getMsgType(), jsonObject));
                WxCpMessageArchive wxCpMessageArchive = new WxCpMessageArchive();
                BeanUtils.copyProperties(wxCpMessageArchiveVo, wxCpMessageArchive);
                wxCpMessageArchive.setAppId(appId);
                wxCpMessageArchive.setAgentId(agentId);
                wxCpMessageArchive.setSeq(seq);
                wxCpMessageArchive.setPublicKeyVer(publicKeyVer);
                String to = String.join(",", wxCpMessageArchiveVo.getToList());
                wxCpMessageArchive.setTo(to);
                Long msgTimestamp = wxCpMessageArchiveVo.getMsgTimestamp();
                if (Objects.nonNull(msgTimestamp)) {
                    LocalDateTime msgTime = LocalDateTime.ofEpochSecond(msgTimestamp / 1000, 0, ZoneOffset.ofHours(8));
                    wxCpMessageArchive.setMsgTime(msgTime);
                }
                WxCpMessageArchiveService.MESSAGE_QUEUE.put(wxCpMessageArchive);
            } catch (Exception e) {
                log.error("会话入库失败:{}", chatDatum);
            }
        }
    }

    public Long getSdk(String appId, Integer agentId){
        Long sdk = this.FINANCE_MAP.getOrDefault(appId + "#" + agentId, null);
        if (Objects.nonNull(sdk)){
            return sdk;
        }
        return this.initSdk(appId, agentId);
    }
    
	public Long getLastSeq(){
        //获取最后一次拉取的数据的seq
        return 0L;
    }

    private Long initSdk(String appId, Integer agentId){
        WxCpConfigStorage wxCpConfigStorage = WxCpConfiguration.getCpService(agentId).getWxCpConfigStorage();
        String corpId = wxCpConfigStorage.getCorpId();
        String corpSecret = wxCpConfigStorage.getCorpSecret();
        long sdk = Finance.NewSdk();
        long ret = 0;
        ret = Finance.Init(sdk, corpId, corpSecret);
        if (ret != 0) {
            Finance.DestroySdk(sdk);
            log.error("会话存档sdk初始化失败,appId:{}, agentId:{}", appId, agentId);
            return null;
        }
        FINANCE_MAP.put(appId + "#" + agentId, sdk);
        return sdk;
    }

    private String getContent(String msgType, JSONObject jsonObject) {
        if (!MSG_TYPE_MAP.containsKey(msgType)) {
            return jsonObject.getString(msgType);
        }
        Object object = jsonObject.get(msgType);
        if (Objects.nonNull(object)) {
            return jsonObject.getString(msgType);
        }
        msgType = MSG_TYPE_MAP.get(msgType);
        return jsonObject.getString(msgType);
    }

    private String readerFile(String path) throws IOException {
        org.springframework.core.io.Resource resource = resourceLoader.getResource("classpath:" + path);
        InputStream is = resource.getInputStream();
        InputStreamReader isr = new InputStreamReader(is);
        BufferedReader br = new BufferedReader(isr);
        StringBuilder stringBuilder = new StringBuilder();
        String data;
        while ((data = br.readLine()) != null) {
            stringBuilder.append(data);
        }
        br.close();
        isr.close();
        is.close();
        return stringBuilder.toString();
    }
}

@Data
@NoArgsConstructor
public class MessageResult {
    @JSONField(name = "errcode")
    private Integer errCode;
    @JSONField(name = "errmsg")
    private String errMsg;
    @JSONField(name = "chatdata")
    private List chatData;

    @Data
    @NoArgsConstructor
    public static class Message {
        @JSONField(name = "seq")
        private Integer seq;
        @JSONField(name = "msgid")
        private String msgId;
        @JSONField(name = "publickey_ver")
        private String publicKeyVer;
        @JSONField(name = "encrypt_random_key")
        private String encryptRandomKey;
        @JSONField(name = "encrypt_chat_msg")
        private String encryptChatMsg;
    }
}
@Data
@NoArgsConstructor
public class WxCpMessageArchiveVo {
    @JSONField(name = "msgid")
    private String msgId;
    private String action;
    private String from;
    @JSONField(name = "tolist")
    private List toList;
    @JSONField(name = "roomid")
    private String roomId;
    @JSONField(name = "msgtime")
    private Long msgTimestamp;
    @JSONField(name = "msgtype")
    private String msgType;
    @JSONField(name = "voiceid")
    private String voiceId;
    @JSONField(name = "voipid")
    private String voipId;
    private String content;
}
@Data
@NoArgsConstructor
public class WxCpMessageArchive {
    private Long id;

    
    private String appId;

    
    private Integer agentId;

    
    private Integer seq;

    
    private String msgId;

    
    private String publicKeyVer;

    
    private String action;

    
    private String from;

    
    private String to;

    
    private String roomId;

    
    private String msgType;

    
    private String voiceId;

    
    private String voipId;

    
    private String content;

    
    private LocalDateTime msgTime;

    
    private String filePath;
}   
import lombok.extern.slf4j.Slf4j;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.SequenceInputStream;
import java.util.ArrayList;
import java.util.List;

@Slf4j
public class CmdUtils {

    public static List execute(String command) throws IOException {
        List processList = new ArrayList();
        BufferedReader input = null;
        try {
            Process process = Runtime.getRuntime().exec(new String[]{"/bin/sh", "-c", command});
            input = new BufferedReader(new InputStreamReader(process.getInputStream()));
            process.getErrorStream();
            String line;
            while ((line = input.readLine()) != null) {
                processList.add(line);
            }
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        } finally {
            if (input != null) {
                input.close();
            }
        }
        return processList;
    }
}
import org.apache.tomcat.util.codec.binary.base64;
import sun.security.util.DerInputStream;
import sun.security.util.DerValue;

import javax.crypto.Cipher;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.RSAPrivateCrtKeySpec;

public class RsaUtil {

    
    public static String pkcs1decrypt(String str, String privateKey) throws Exception{
        //64位解码加密后的字符串
        byte[] inputByte = base64.decodebase64(str.getBytes(StandardCharsets.UTF_8));
        //base64编码的私钥
        PrivateKey priKey = getPkcs1PrivateKey(privateKey);
        //RSA解密
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.DECRYPT_MODE, priKey);
        return new String(cipher.doFinal(inputByte));
    }

    
    public static PrivateKey getPkcs1PrivateKey(String privateKey) throws Exception{
        privateKey = privateKey.replaceAll("\n", "").replace("-----BEGIN RSA PRIVATE KEY-----", "").replace("-----END RSA PRIVATE KEY-----", "");
        byte[] bytes = base64.decodebase64(privateKey);
        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 crt = seq[8].getBigInteger();
        RSAPrivateCrtKeySpec keySpec = new RSAPrivateCrtKeySpec(modulus, publicExp, privateExp, prime1, prime2, exp1, exp2, crt);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return keyFactory.generatePrivate(keySpec);
    }
}

三、拉取媒体文件
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.File;
import java.io.FileOutputStream;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.linkedBlockingQueue;

@Service
@Slf4j
public class WxCpMessageArchiveService {

    @Resource
    private MsgauditNotifyEventHandle msgauditNotifyEventHandle;
	//queue保存解析出来的数据,完了消费queue里的数据入库
    public static final linkedBlockingQueue MESSAGE_QUEUE = new linkedBlockingQueue<>(10000);
    private final List mediaTypeList = new ArrayList<>();

    @PostConstruct
    public void initMediaTypeList(){
        mediaTypeList.add("image");
        mediaTypeList.add("voice");
        mediaTypeList.add("video");
        mediaTypeList.add("file");
    }

    
    public void getMediaDataFromWechat(){
        List archiveList = this.getNeedMediaDataList();
        for (WxCpMessageArchive archive : archiveList) {
            String appId = archive.getAppId();
            Integer agentId = archive.getAgentId();
            String msgId = archive.getMsgId();
            String msgType = archive.getMsgType();
            String content = archive.getContent();
            JSONObject jsonObject = JSONObject.parseObject(content);
            String sdkFileId = jsonObject.getString("sdkfileid");
            if (StringUtils.isBlank(sdkFileId)){
                continue;
            }
            String indexBuf = "";
            Long sdk = this.msgauditNotifyEventHandle.getSdk(appId, agentId);
            if (Objects.isNull(sdk)){
                continue;
            }
            String fileName = "";
            if (Objects.equals(msgType, "file")){
                fileName = jsonObject.getString("filename");
            }
            if (StringUtils.isBlank(fileName)){
                fileName = msgId;
            }
            String filePath = "/opt/mediaFile/" + fileName;
            File file = new File(filePath);
            while(true){
                //每次使用GetMediaData拉取存档前需要调用NewMediaData获取一个media_data,在使用完media_data中数据后,还需要调用FreeMediaData释放。
                long media_data = Finance.NewMediaData();
                long ret = Finance.GetMediaData(sdk, indexBuf, sdkFileId, "", "", 5, media_data);
                if(ret!=0){
                    log.error("会话存档, 获取媒体文件失败, msgId:{}, msgType:{}", msgId, msgType);
                    Finance.FreeMediaData(media_data);
                    //删除已拉取的媒体数据
                    FileUtils.deleteQuietly(file);
                    break;
                }
                try (FileOutputStream outputStream  = new FileOutputStream(filePath, true);){
                    //大于512k的文件会分片拉取,此处需要使用追加写,避免后面的分片覆盖之前的数据。
                    outputStream.write(Finance.GetData(media_data));
                } catch (Exception e) {
                    log.error("会话存档, 写入媒体文件失败, msgId:{}, msgType:{}", msgId, msgType);
                    Finance.FreeMediaData(media_data);
                    //删除已拉取的媒体数据
                    FileUtils.deleteQuietly(file);
                    break;
                }
                if(Finance.IsMediaDataFinish(media_data) == 1) {
                    //已经拉取完成最后一个分片
                    Finance.FreeMediaData(media_data);
                    break;
                } else {
                    //获取下次拉取需要使用的indexBuf
                    indexBuf = Finance.GetOutIndexBuf(media_data);
                    Finance.FreeMediaData(media_data);
                }
            }
            if (file.exists()){
                //媒体文件获取成功
            }
        }
    }
    
     public List getNeedMediaDataList(){
        //获取需要拉取媒体文件的数据集
        return new ArrayList<>();
    }
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/678728.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号