【SpringBoot实现企业微信会话内容存档】linux部署

注:本文是在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坑的很

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Spring Boot是一种用于开发Java应用程序的开源框架,它简化了基于Spring框架的应用程序的配置和部署企业微信是一款为企业提供即时通讯和协同办公的应用,它能轻松满足企业的沟通和协作需求。 为了在Spring Boot应用程序中引入企业微信会话存档SDK,需要执行以下步骤: 1. 下载企业微信会话存档SDK:你可以从企业微信官方网站或其他可信来源下载适用于Java企业微信会话存档SDK。 2. 引入SDK依赖:将下载的SDK文件导入Spring Boot应用程序的依赖管理系统中。可以使用Maven或Gradle等构建工具来管理依赖,并在配置文件中指定SDK的版本号。 3. 创建企业微信会话存档SDK的配置文件:在Spring Boot的配置文件中,添加企业微信会话存档SDK所需的配置参数,如企业微信的Corpid、Secret和Agentid等。 4. 创建企业微信会话存档SDK的服务类:在Spring Boot应用程序中创建一个服务类,用于调用企业微信会话存档SDK的接口。根据业务需求,可以根据文档提供的接口进行数据的存储、获取等操作。 5. 在应用程序中调用企业微信会话存档服务:在需要使用企业微信会话存档功能的地方,通过注入企业微信会话存档服务的实例,调用相应的方法来实现会话存档的功能。 需要注意的是,引入企业微信会话存档SDK前,要确保企业微信账号已通过认证并开通了相应的权限。 以上是使用Spring Boot引入企业微信会话存档SDK的基本步骤,根据具体的需求,可能还需要对业务逻辑进行进一步的处理和优化。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值