springBoot企业微信引入会话存档SDK

一:准备工作

业务需求,需要用企业微信实现消息通信,一对一通信有接口可以直接实现,下面就说一下这群聊,
企业微信并没有给出群聊获取消息的接口,就需要引入会话存档SDK来获取群聊消息,下面说一下前期的准备工作。
先就是填一个申请表,开通会话存档权限,这个功能是收费的,但是先一个月免费,足够开发调试了,
能用到这个功能的,说明那些基础的已经知道了,那就直接开始了
生成公钥和密钥对,地址:http://web.chacuo.net/netrsakeypair
springBoot企业微信引入会话存档SDK_第1张图片
将公钥填写在客户端,注意版本,修改一个公钥,对应的版本就需要+1

二:引入依赖

需要这样引入依赖,不然的话有2个依赖有冲突,无法解密

  <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>

三:下载SDK,先说windows版本

将sdk中的.dll文件直接放入C:\Windows\System32目录下,然后在环境变量的Path中添加该目录。
注意:sdk中的Finace.java文件必须放入项目的com.tencent.wework目录下
springBoot企业微信引入会话存档SDK_第2张图片

四:linux版本引入sdk,docker中部署

将下载的.so文件放在改项目的lib文件目录下面,方便寻找,尽量不要放在系统的lib目录下面
springBoot企业微信引入会话存档SDK_第3张图片
需要在dockerfile文件中配置动态链路库,找到改.so文件
第一句是copy文件到lib下面 第二局在启动时加入改环境变量
COPY ./lib/libWeWorkFinanceSdk_Java.so /usr/lib/libWeWorkFinanceSdk_Java.so
ENV LD_LIBRARY_PATH=/disk2/docker/qywx/lib:$LD_LIBRARY_PATH

五:初始化加载静态代码块

    static {
        if(isWindows()){
            System.out.println(System.getProperty("java.library.path"));
            System.loadLibrary("WeWorkFinanceSdk");
        }else {
            System.out.println(System.getProperty("java.library.path"));
            System.loadLibrary("WeWorkFinanceSdk_Java");
        }
    }

    public static boolean isWindows() {
        String osName = System.getProperties().getProperty("os.name");
        System.out.println("current system is " + osName);
        return osName.toUpperCase().indexOf("WINDOWS") != -1;
    }

注意区别:加载windows和linux的文件是不一样的,仔细琢磨以下,这里可以把path打印出来看一下
如果部署到线上linux服务器,引入SDK无报错也无反应,大概率就是这里文件没有加载上

六:配置回调url

这里插一下如何配置回调url,需要封装get和post两种请求,get只用于验证,post用于业务,这一点要仔细看文档,通过返回的消息体,就可以解析出里面的消息内容,通过mq返回给前端

  @GetMapping("/message/callback")
    @ResponseBody()
    public String callback(HttpServletRequest request, @RequestParam("msg_signature") String sVerifyMsgSig, @RequestParam("timestamp") String sVerifyTimeStamp, @RequestParam("nonce") String sVerifyNonce, @RequestParam("echostr") String sVerifyEchoStr) throws Exception {

        String sToken = "";
        String sCorpID = "";
        String sEncodingAESKey = "";

        WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(sToken, sEncodingAESKey, sCorpID);
        String sEchoStr; //需要返回的明文
        try {
            sEchoStr = wxcpt.VerifyURL(sVerifyMsgSig, sVerifyTimeStamp,
                    sVerifyNonce, sVerifyEchoStr);
            return sEchoStr;
            // 验证URL成功,将sEchoStr返回
            // HttpUtils.SetResponse(sEchoStr);
        } catch (Exception e) {
            //验证URL失败,错误原因请查看异常
            e.printStackTrace();
        }
        return "error";
    }
@PostMapping("/message/callback")
    @ResponseBody()
    public void callbackData(HttpServletRequest request, @RequestBody() String sRespData, @RequestParam("msg_signature") String sVerifyMsgSig, @RequestParam("timestamp") String sReqTimeStamp, @RequestParam("nonce") String sReqNonce) throws Exception {
        String sToken = "";
        String sCorpID = "";
        String sEncodingAESKey = "";
        WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(sToken, sEncodingAESKey, sCorpID);
        String sMsg = wxcpt.DecryptMsg(sVerifyMsgSig, sReqTimeStamp, sReqNonce, sRespData);
        System.out.println("解密decrypt企业微信推送的消息->sMsg:" + sMsg);
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        DocumentBuilder db = dbf.newDocumentBuilder();
        StringReader sr = new StringReader(sMsg);
        InputSource is = new InputSource(sr);
        Document document = db.parse(is);
        //获取整个XML消息体,进行解析
        Element root = document.getDocumentElement();
        //返回消息的公共部分,内容,创建时间,消息ID,来源,去处

        NodeList nodelistTime = root.getElementsByTagName("CreateTime");
        String CreateTime = nodelistTime.item(0).getTextContent();
        NodeList nodelistFrom = root.getElementsByTagName("FromUserName");
        String fromUserName = nodelistFrom.item(0).getTextContent();
        NodeList nodelistTo = root.getElementsByTagName("ToUserName");
        String toUserName = nodelistTo.item(0).getTextContent();
        Map<String, String> msgMap = new HashMap<String, String>();
        msgMap.put("CreateTime", CreateTime);

        msgMap.put("fromUser", fromUserName);
        msgMap.put("to", toUserName);
        //消息类型,不同消息返回不同消息体
        NodeList typeNodelist = root.getElementsByTagName("MsgType");
        String MsgType = typeNodelist.item(0).getTextContent();
        msgMap.put("msgType", MsgType);
        //文本直接返回
        if (StringUtils.equals(MsgType, "text")) {
            NodeList nodelistMsgId = root.getElementsByTagName("MsgId");
            String MsgId = nodelistMsgId.item(0).getTextContent();
            msgMap.put("msgId", MsgId);
            NodeList nodelist = root.getElementsByTagName("Content");
            String Content = nodelist.item(0).getTextContent();
            msgMap.put("text", Content);
            //自定义返回消息类型,只支持字符串类型
            Map<String, Object> getMsg = new HashMap<String, Object>();
            getMsg.put("msg", msgMap);
            String message = JSONArray.toJSON(getMsg).toString();
            rabbitTemplate.convertAndSend("qywx_exange", null, message);
            //图片返回图片需要增加url和mediaId
        }
        }

七:开发

几个注意的点:
1.seq是传入的每次拉取消息索引,可以根据返回消息的长度获取该值

  Integer newSeq = (Integer) chatdata.getJSONObject(chatdata.length()-1).get("seq");
   System.out.println(newSeq);

2.消息解密和消息加密、
3.linux服务器上消息拉取会乱码,用下面那种方式转一下,不行的话,还是乱码就是Linxu服务器不支持GBK编码,目前我也没解决

  public static Map pullMsg(Integer seq) throws Exception {
        long sdk = Finance.NewSdk();
        Finance.Init(sdk, corpid, secret); // 初始化
        long ret = 0;
        //int seq = 0;
        int limit = 1000;
        long slice = Finance.NewSlice();
        List msgList = new ArrayList();
        Map resultMap = new HashMap();
        //拉取聊天记录
        ret = Finance.GetChatData(sdk, seq, limit, null, null, 15, slice);
        if (ret != 0) {
            return resultMap;
        }
        //获取切片中的内容
        String getchatdata = Finance.GetContentFromSlice(slice);
        JSONObject jo = new JSONObject(getchatdata);
        //聊天内容
        JSONArray chatdata = jo.getJSONArray("chatdata");
        if(chatdata.length() > 1){
            Integer newSeq = (Integer) chatdata.getJSONObject(chatdata.length()-1).get("seq");
            resultMap.put("lastSeq",newSeq);
        }else{
            resultMap.put("lastSeq",0);
        }
        System.out.println("消息数:" + chatdata.length());
        String msgContent = null;
        for (int i = 0; i < chatdata.length(); i++) {
            String item = chatdata.get(i).toString();
            JSONObject data = new JSONObject(item);
            //加密密钥
            String encrypt_random_key = data.getString("encrypt_random_key");
            //加密聊天消息
            String encrypt_chat_msg = data.getString("encrypt_chat_msg");
            long msg = Finance.NewSlice();
            try {
                //解密
                String message = RSAEncrypt.decryptRSA(encrypt_random_key, priKey);
                //使用SDK获取msg明文
                ret = Finance.DecryptData(sdk, message, encrypt_chat_msg, msg);
                System.out.println(ret);
                if (ret != 0) {
                    return resultMap;
                }
                msgContent =new String(Finance.GetContentFromSlice(msg).getBytes("GBK"),"UTF-8");
                msgList.add(msgContent);
                Finance.FreeSlice(msg);
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
        Finance.FreeSlice(slice);
        resultMap.put("data",msgList);
        return resultMap;
    }

八:消息加密和消息解密

这个方法拿过去就不要动了,直接就能用

public class RSAEncrypt {
    public static String decryptRSA(String str, String privateKey) throws Exception {
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
        //此处的"RSA/ECB/PKCS1Padding", "BC"不可以改变,改变会导致解密乱码
        Cipher rsa = Cipher.getInstance("RSA/ECB/PKCS1Padding", "BC");
        rsa.init(Cipher.DECRYPT_MODE, getPrivateKey(privateKey));
        byte[] utf8 = rsa.doFinal(Base64.decodeBase64(str));
        String result = new String(utf8, "UTF-8");
        return result;

    }

    public static PrivateKey getPrivateKey (String privateKey) throws Exception {
        Reader privateKeyReader = new StringReader(privateKey);
        PEMParser privatePemParser = new PEMParser(privateKeyReader);
        Object privateObject = privatePemParser.readObject();
        if (privateObject instanceof PEMKeyPair) {
            PEMKeyPair pemKeyPair = (PEMKeyPair) privateObject;
            JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
            PrivateKey privKey = converter.getPrivateKey(pemKeyPair.getPrivateKeyInfo());
            return privKey;
        }
        return null;
    }
}

9,总结

最后,总结一下吧,因为这个功能是要实时获取群聊的消息,但这个会话存档不能做到,有点鸡肋,主要是做不到实时,会话存档嘛,有点延迟,大概6-7s吧,体验不好,看了企微那边的介绍,也问了客服找了案例,大多数用这个会话存档做scrm系统

你可能感兴趣的:(spring,boot,企业微信,java)