需求:公司想要拉取外部群客户发送的相关信息,用以进行跟进。

分析可行性方案:1.原来想用企业微信自带的机器人,但机器人目前不支持外部群。

                             2.然后使用了一个免费的第三方,不过只能拉取到文本信息也pass了

                             3.使用付费的第三方,但担心对方过两年跑路,并且有客户信息泄露的风险,故也放弃

                             4.故最后敲定使用企业微信提供的会话存档来实现(这个也是收费的,服务版450/年)

实现过程:首先你需要到公司对应的企业微信账号去开通会话存档功能,一开始有1个月的免费试用期(之后服务版的是450/年),需要配置响应的ip地址,信息接收回调接口(这里需要注意的是该接口需要get请求)等,企业微信有对应的官方文档,根据文档一步一步来,申请好后,我这里下载的C版的sdk(这里可以根据需要选择32,或者64位的,我选择的是64位的,这个只有一系列的环境也都需要相匹配哦)。

  接下来准备工作都做好了,开始进入正题。

  这里引用借鉴了大喵网站的相关文章,并加以改进,已适用自身业务。期间遇到的问题以及解决方案如下:

1.因为所给的libcurl-x64.dll等文件不用直接被vs直接引用,只需要放到项目的根路径就好,在使用DllImport来调用实现,这里FinanceAdapter.cs有封装好,可以自行查看。这里会遇到一个问题:

引用dll“找不到指定模块"

解决方案:方案1,1.鼠标右键属性(或者选中该DLL按下F4)—— 复制到输入目录 ——始终复制(默认情况下是不复制,但是不能选择不复制),2.并添加C++的相关环境(同理发布到服务器上时也需要)vc_redist.x64.exe

这里可能会遇到安装vc_redist.x64.exe不成功的可能,大多是因为已经安装了别的版本的redist原因,例如已经安装了32位的,64位就会弹窗提示,这里可以使用cmd强制安装或者卸载32位的,再安装64位。如果还是找不到,调用路径可以直接写成绝对路径,或者在 (x86系统)C:\Windows\System32或(x64系统)C:\Windows\SysWOW64目录下中放入WeWorkFinanceSdk相关的dll

2.引用成功后,代码跑起来,因为兼容性由出现了问题:

试图加载格式不正确的程序

解决方案:方案1(方案里的是32位的,64位的改为对应的即可),

1)允许的话把C#客户端项目平台修改为64位

2)客户端平台不允许修改,则选择AnyCPU,勾选首选64位,如图(在.netFramework4.5上,勾选64位才可以进行选择)

设置完这些,如果依旧报错,就需要修改VS的工具=>选项=>项目和解决方案=>Web项目=>对网站和项目使用IIS Express 的 64位版 打钩

未找到框架.net framework 未找到指定模块_未找到框架.net framework

3.RSAKEY密钥需要由string类型转换成xml才可以被FinanceAdapter.DecryptData调用

/// <summary> 
        /// /// 私钥转XML 
        /// /// </summary> 
        /// /// <param name="privateJavaKey"></param> 
        /// /// <returns></returns> 
        public static string ConvertToXmlPrivateKey(string privateJavaKey)
        {
            RsaPrivateCrtKeyParameters privateKeyParam = (RsaPrivateCrtKeyParameters)PrivateKeyFactory.CreateKey(Convert.FromBase64String(privateJavaKey));
            return string.Format("<RSAKeyValue><Modulus>{0}</Modulus><Exponent>{1}</Exponent><P>{2}</P><Q>{3}</Q><DP>{4}</DP><DQ>{5}</DQ><InverseQ>{6}</InverseQ><D>{7}</D></RSAKeyValue>",
                Convert.ToBase64String(privateKeyParam.Modulus.ToByteArrayUnsigned()),
                Convert.ToBase64String(privateKeyParam.PublicExponent.ToByteArrayUnsigned()),
                Convert.ToBase64String(privateKeyParam.P.ToByteArrayUnsigned()),
                Convert.ToBase64String(privateKeyParam.Q.ToByteArrayUnsigned()),
                Convert.ToBase64String(privateKeyParam.DP.ToByteArrayUnsigned()),
                Convert.ToBase64String(privateKeyParam.DQ.ToByteArrayUnsigned()),
                Convert.ToBase64String(privateKeyParam.QInv.ToByteArrayUnsigned()),
                Convert.ToBase64String(privateKeyParam.Exponent.ToByteArrayUnsigned()));
        }

4.会话存档成功搭建后,拉取信息时可能会遇到信息还在3天内,但依旧过期的情况,询问社区平台,还是待处理的情况,我这边的处理方式是,使用会话存档提供的会话回调,进行同步跟进,现将信息拉取到本地服务器,待之后进行处理使用。

 

 

以下是会用到的一些方法:

/// <summary>
        /// 获取文本
        /// </summary>
        /// <param name="slice"></param>
        /// <returns></returns>
        public string GetContentFromSlice(long slice)
        {
            var length = FinanceAdapter.GetSliceLen(slice);
            var bytes = new byte[length];
            var ptr = FinanceAdapter.GetContentFromSlice(slice);
            Marshal.Copy(ptr, bytes, 0, bytes.Length);
            return Encoding.UTF8.GetString(bytes);
        }
      /// <summary>
        /// 解密
        /// </summary>
        /// <param name="privateKey"></param>
        /// <param name="text"></param>
        /// <returns></returns>
        public string Decrypt(string text)
        {
            string xml = ConvertToXmlPrivateKey(privateKeys);//Json字符串转xml字符串
            var rsa = new RSACryptoServiceProvider();
            var bytes = Convert.FromBase64String(text);
            rsa.FromXmlString(xml);
            var result = rsa.Decrypt(bytes, false);
            return Encoding.UTF8.GetString(result);
        }
        /// <summary>
        /// 拉取会话存档
        /// </summary>
        /// <param name="iSeq">从指定的seq开始拉取消息,注意的是返回的消息从seq+1开始返回,seq为之前接口返回的最大seq值。首次使用请使用seq:0</param>
        /// <param name="iLimit">一次拉取的消息条数,最大值1000条,超过1000条会返回错误</param>
        /// <param name="proxy">使用代理的请求,需要传入代理的链接。如:socks5://10.0.0.1:8081 或者 http://10.0.0.1:8081</param>
        /// <param name="paswd">代理账号密码,需要传入代理的账号密码。如 user_name:passwd_123</param>
        /// <param name="timeout">超时时间,单位秒</param>
        /// <param name="echostr">返回本次拉取消息的数据,slice结构体.内容包括errcode/errmsg,以及每条消息内容。</param>
        /// <returns></returns>
        public int GetChatData(long iSeq, long iLimit, string proxy, string paswd, long timeout,   ref long echostr)
        {
            try
            {
                DateTime timeTo = new DateTime();
                DateTime timeFo = new DateTime();
                string[] reser = new string[] { };


                var seq = Convert.ToInt32(iSeq);

                ///NewSdk返回的sdk指针
                var sdk = FinanceAdapter.NewSdk();
                
                var ret = FinanceAdapter.Init(sdk, 企业微信corpId, 企业微信secret);

                if (ret != 0)
                {
                    //sdk需要主动释放
                    FinanceAdapter.DestroySdk(sdk);
                    return -1;
                }
                //拉取会话存档

                //每次使用GetChatData拉取存档前需要调用NewSlice获取一个chatDatas,在使用完chatDatas中数据后,还需要调用FreeSlice释放。
                var chatDatas = FinanceAdapter.NewSlice();
                ret = FinanceAdapter.GetChatData(sdk, iSeq, iLimit, proxy, paswd, timeout, chatDatas);
                echostr = chatDatas;

                if (ret != 0)
                {
                    FinanceAdapter.FreeSlice(chatDatas);
                    return -1;
                }



                FinanceAdapter.FreeSlice(chatDatas);

                FinanceAdapter.DestroySdk(sdk);

                return seq;
            }
            catch (Exception ex)
            {
                Logs.logSave("回滚报错"+ex);
                throw;
            }

        }
        /// <summary>
        /// SDK解密会话存档内容
        /// </summary>
        /// <param name="encrypt_key">getchatdata返回的encrypt_random_key,使用企业自持对应版本秘钥RSA解密后的内容</param>
        /// <param name="encrypt_msg">getchatdata返回的encrypt_chat_msg</param>
        /// <param name="MsgsStr">消息明文,json格式</param>
        /// <returns></returns>
        public int GetDecryptData(string encrypt_key, string encrypt_msg, ref string MsgsStr)
        {
            var sdk = FinanceAdapter.NewSdk();
            var ret = FinanceAdapter.Init(sdk, coupId, secret);
            if (ret != 0)
            {
                //sdk需要主动释放
                FinanceAdapter.DestroySdk(sdk);
                return -1;
            }

            //解密会话存档内容
            //sdk不会要求用户传入rsa私钥,保证用户会话存档数据只有自己能够解密。
            //此处需要用户先用rsa私钥解密encrypt_random_key后,作为encrypt_key参数传入sdk来解密encrypt_chat_msg获取会话存档明文。
            //每次使用DecryptData解密会话存档前需要调用NewSlice获取一个Msgs,在使用完Msgs中数据后,还需要调用FreeSlice释放。

            var Msgs = FinanceAdapter.NewSlice();
            ret = FinanceAdapter.DecryptData(encrypt_key, encrypt_msg, Msgs);
            MsgsStr = GetContentFromSlice(Msgs);
            FinanceAdapter.FreeSlice(Msgs);

            FinanceAdapter.DestroySdk(sdk);
            return ret;
        }
        /// <summary>
        /// 获取媒体文件
        /// </summary>
        /// <param name="sdkFileid"></param>
        /// <param name="proxy"></param>
        /// <param name="passwd"></param>
        /// <param name="timeout"></param>
        /// <param name="echostr"></param>
        /// <returns></returns>
        public int GetMediaData(string sdkFileid, string proxy, string passwd, long timeout,string Title, ref string filepath)
        {
            var sdk = FinanceAdapter.NewSdk();
            var ret = FinanceAdapter.Init(sdk, coupId, secret);
            if (ret != 0)
            {
                //sdk需要主动释放
                FinanceAdapter.DestroySdk(sdk);
                return -1;
            }

            //拉取媒体文件
            string index = "";
            int isfinish = 0;

            //媒体文件每次拉取的最大size为512k,因此超过512k的文件需要分片拉取。若该文件未拉取完整,mediaData中的is_finish会返回0,同时mediaData中的outindexbuf会返回下次拉取需要传入GetMediaData的indexbuf。
            //indexbuf一般格式如右侧所示,”Range:bytes=524288-1048575“,表示这次拉取的是从524288到1048575的分片。单个文件首次拉取填写的indexbuf为空字符串,拉取后续分片时直接填入上次返回的indexbuf即可。
            while (isfinish == 0)
            {
                //每次使用GetMediaData拉取存档前需要调用NewMediaData获取一个mediaData,在使用完mediaData中数据后,还需要调用FreeMediaData释放。
                var mediaData = FinanceAdapter.NewMediaData();
                ret = FinanceAdapter.GetMediaData(sdk, index, sdkFileid, proxy, passwd, timeout, mediaData);
                if (ret != 0)
                {
                    return -1;
                }
                string endpath = @"Excel\" + DateTime.Now.ToString("yyyyMMdd"); //打包的根路径
                string uploadPath = HttpRuntime.AppDomainAppPath + endpath+"\\";
                //var title = DateTime.Now.ToString("yyyyMMddHHmmss") + ".xlsx";
                if (!Directory.Exists(uploadPath))
                    Directory.CreateDirectory(uploadPath);
                filepath = uploadPath + Title;
                byte[] bytes = new byte[FinanceAdapter.GetDataLen(mediaData)];
                Marshal.Copy(FinanceAdapter.GetData(mediaData), bytes, 0, FinanceAdapter.GetDataLen(mediaData));
                FileStream file = new FileStream(filepath, FileMode.Create, FileAccess.Write);
                file.Write(bytes, 0, FinanceAdapter.GetDataLen(mediaData));
                file.Close();

                if (FinanceAdapter.IsMediaDataFinish(mediaData) == 1)
                {
                    // need free media_data
                    FinanceAdapter.FreeMediaData(mediaData);
                    break;
                }
                else
                {
                    index = FinanceAdapter.GetOutIndexBuf(mediaData);

                    // need free media_data
                    FinanceAdapter.FreeMediaData(mediaData);
                }
            }


            FinanceAdapter.DestroySdk(sdk);
            return ret;
        }

最后,git上有一个使用.net对会话存档进行封装的源码项目