公司自動開始用釘釘後,企業的相關信息化軟體開始使用釘釘上的應用程式。與銷售公司相關的就是CRM系統。 CRM系統中客戶是私人的,如果想多個人同時負責,需要添加客戶負責人。由於公司的特殊性質,客戶特別多,經常會有人要求增加客戶負責人。每天都有幾個小時在做這樣的工作。釘釘消息、添加負責人,太繁瑣了。 學 ...
公司自動開始用釘釘後,企業的相關信息化軟體開始使用釘釘上的應用程式。與銷售公司相關的就是CRM系統。
CRM系統中客戶是私人的,如果想多個人同時負責,需要添加客戶負責人。由於公司的特殊性質,客戶特別多,經常會有人要求增加客戶負責人。每天都有幾個小時在做這樣的工作。釘釘消息、添加負責人,太繁瑣了。
學以致用,開發個工具自動化處理任務。
閑話少說,開乾。
設計思路
- 確認CRM是否有自動添加客戶負責人的介面
- 確認釘釘的審批介面是否可以使用
- 確認介面數據都能拿到後,後臺寫個服務即可。
CRM系統分配用戶
1、採集客戶信息
分配客戶負責人是通過客戶的dataid來處理的,首先需要採集客戶信息。幸好以前有定時服務採集客戶信息。跳過
2、分配客戶負責人介面
客戶負責人分配才是這次的自動化處理的關鍵,通過查看CRM的API介面。可以實現,按照下麵的代碼執行即可。
public static void ReqDistributionCustomer(string datatids,string distributionUserIds, string userid, string corpid, string token, Action<string> SuccessCallback = null, Action<string> FailCallback = null) { string url = "http://127.0.0.1/pro/v1/api/customer/distribution"; StringBuilder data = new StringBuilder(); data.Append($@"{{""corpid"":""{corpid}"",""dataIdList"":[{datatids}],""distributionUserIds"":[""{distributionUserIds}""],""subBusinessType"":101,""userId"":""{userid}""}}"); string sign = Common.sha256($"{data.ToString()}{token}").ToLower(); HttpWebRequest req = WebRequest.Create(url) as HttpWebRequest; req.Method = "POST"; req.KeepAlive = true; req.ContentType = "application/json"; req.Headers.Add("sign", sign); req.ServicePoint.ConnectionLimit = int.MaxValue; req.ServicePoint.Expect100Continue = false; req.Credentials = System.Net.CredentialCache.DefaultCredentials; byte[] buffer = Encoding.UTF8.GetBytes(data.ToString()); using (Stream reqStream = req.GetRequestStream()) { reqStream.Write(buffer, 0, buffer.Length); } req.BeginGetResponse(new AsyncCallback(RspDistributionCustomer), new object[] { req, datatids, userid, corpid, token, distributionUserIds, SuccessCallback, FailCallback }); } private static void RspDistributionCustomer(IAsyncResult result) { object[] parms = result.AsyncState as object[]; string datatids = parms[1].ToString(); string userid = parms[2].ToString(); string corpid = parms[3].ToString(); string token = parms[4].ToString(); string distributionUserIds = parms[5].ToString(); HttpWebRequest req = parms[0] as HttpWebRequest; Action<string> SuccessCallback = parms[6] as Action<string>; Action<string> FailCallback = parms[7] as Action<string>; using (HttpWebResponse rsp = req.EndGetResponse(result) as HttpWebResponse) { using (StreamReader reader = new StreamReader(rsp.GetResponseStream())) { string msg = ""; msg = reader.ReadToEnd(); var jsondata = Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(msg); if (!(bool)(jsondata.success)) { if (FailCallback != null) FailCallback(datatids); } else { if (SuccessCallback != null) SuccessCallback(datatids); } } } }
獲取釘釘的審批信息
- 在釘釘上創建新審批,根據要求設置表單格式,支持多個客戶同時添加相同的負責人。
- 為了防止隨意添加客戶負責人 增加了欄位任意當前客戶負責人,可以在系統中進行校驗。 或者是 定期對所有客戶的負責人進行校驗,如果沒有任何跟進、銷售機會、合同信息可以主動刪除該客戶負責人。
- 關於附件,其實最好的辦法是把客戶放到附件裡面,但是看了一下開放平臺,後臺開發文檔,沒有辦法獲取審批中的附件信息。【難過】,只好允許用戶直接錄入多個客戶了
獲取釘釘的審批記錄
釘釘要求每次獲取只能獲取20條記錄,如果想獲取更多只能翻頁獲取。
其實關於釘釘的開放平臺,講解的還是很詳細的,根據要求傳參數即可。釘釘現在都是Java開發了,.NET還得自己寫。
對於多年的.NET程式員大叔來說,這都不是事,輕鬆搞定.
public static void ReqDingProcess(string token, DateTime dt, Action<string> SuccessCallback, Action<string> FailCallback,int cursor=0) { string url = $"https://oapi.dingtalk.com/topapi/processinstance/listids?access_token={token}"; StringBuilder data = new StringBuilder(); data.Append($@"{{""process_code"":""{FzrProcessCode}"",""start_time"":{Common.ConvertDateTimeLong(dt)},""size"":20,""cursor"":{cursor}}}"); HttpWebRequest req = WebRequest.Create(url) as HttpWebRequest; req.Method = "POST"; req.UserAgent = "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36"; req.Accept = "application/json, text/plain, */*"; req.KeepAlive = true; req.ContentType = "application/json;charset=UTF-8"; req.Headers.Add("X-Requested-With", "XMLHttpRequest"); req.ServicePoint.ConnectionLimit = int.MaxValue; req.ServicePoint.Expect100Continue = false; req.Credentials = System.Net.CredentialCache.DefaultCredentials; byte[] buffer = Encoding.UTF8.GetBytes(data.ToString()); using (Stream reqStream = req.GetRequestStream()) { reqStream.Write(buffer, 0, buffer.Length); } req.BeginGetResponse(new AsyncCallback(RspDingProcess), new object[] { req, token, dt, SuccessCallback, FailCallback }); } public static void RspDingProcess(IAsyncResult result) { object[] parms = result.AsyncState as object[]; string token = parms[1].ToString(); DateTime dt = DateTime.Parse(parms[2].ToString()); HttpWebRequest req = parms[0] as HttpWebRequest; Action<string> SuccessCallback = parms[3] as Action<string>; Action<string> FailCallback = parms[4] as Action<string>; using (HttpWebResponse rsp = req.EndGetResponse(result) as HttpWebResponse) { using (StreamReader reader = new StreamReader(rsp.GetResponseStream())) { string msg = ""; msg = reader.ReadToEnd(); var jsondata = Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(msg); if (jsondata.result.next_cursor != null) { int nextcursor =int.Parse( jsondata.result.next_cursor.ToString()); ReqDingProcess(token, dt, SuccessCallback, FailCallback, nextcursor); } var rows = jsondata.result.list; foreach (var row in rows) { queueFzrProcess.Enqueue(row.ToString()); } } } } public static void ReqDingProcessInfo(string token,string procid, Action<string> SuccessCallback, Action<string> FailCallback) { string url = $"https://oapi.dingtalk.com/topapi/processinstance/get?access_token={token}"; StringBuilder data = new StringBuilder(); data.Append($@"{{""process_instance_id"":""{procid}""}}"); HttpWebRequest req = WebRequest.Create(url) as HttpWebRequest; req.Method = "POST"; req.UserAgent = "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36"; req.Accept = "application/json, text/plain, */*"; req.KeepAlive = true; req.ContentType = "application/json;charset=UTF-8"; req.Headers.Add("X-Requested-With", "XMLHttpRequest"); req.ServicePoint.ConnectionLimit = int.MaxValue; req.ServicePoint.Expect100Continue = false; req.Credentials = System.Net.CredentialCache.DefaultCredentials; byte[] buffer = Encoding.UTF8.GetBytes(data.ToString()); using (Stream reqStream = req.GetRequestStream()) { reqStream.Write(buffer, 0, buffer.Length); } req.BeginGetResponse(new AsyncCallback(RspDingProcessInfo), new object[] { req, token, procid, SuccessCallback, FailCallback }); } public static void RspDingProcessInfo(IAsyncResult result) { object[] parms = result.AsyncState as object[]; string token = parms[1].ToString(); string procid = parms[2].ToString(); HttpWebRequest req = parms[0] as HttpWebRequest; Action<string> SuccessCallback = parms[3] as Action<string>; Action<string> FailCallback = parms[4] as Action<string>; using (HttpWebResponse rsp = req.EndGetResponse(result) as HttpWebResponse) { using (StreamReader reader = new StreamReader(rsp.GetResponseStream())) { string msg = ""; msg = reader.ReadToEnd(); var jsondata = Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(msg); string businessid = jsondata.process_instance.business_id.ToString(); string customers = ""; string fzrNew = ""; StringBuilder sbFzrSource = new StringBuilder(); var fields = jsondata.process_instance.form_component_values; foreach (var field in fields) { switch (field.name.ToString()) { case "客戶名稱": { customers = field.value.ToString().Trim(); break; } case "需添加的負責人": { string userstr= field.ext_value.ToString(); var jsonuser= Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(userstr); foreach (var u in jsonuser) { fzrNew = u.emplId.ToString().Trim(); } break; } case "選擇任意當前客戶負責人": { string userstr = field.ext_value.ToString(); var jsonuser = Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(userstr); foreach (var u in jsonuser) { sbFzrSource.Append($",{u.emplId.ToString().Trim()}"); } break; } } } if (sbFzrSource.Length > 0) sbFzrSource.Remove(0, 1); string[] lstcustomer = customers.Split(new string[] { "\n", ",", ",", "|" }, StringSplitOptions.RemoveEmptyEntries); foreach (string str in lstcustomer) { ConcurrentDictionary<string, string> dic = new ConcurrentDictionary<string, string>(); dic.AddOrUpdate("businessid", businessid, (k, v) => businessid); dic.AddOrUpdate("customer", str, (k, v) => str); dic.AddOrUpdate("fzr", fzrNew, (k, v) => fzrNew); dic.AddOrUpdate("fzrSource", sbFzrSource.ToString(), (k, v) => sbFzrSource.ToString()); dic.AddOrUpdate("procid", procid,(k,v)=>procid); queueProcs.Enqueue(dic); } } } }
定時任務
5分鐘抓取一次審批。
根據抓到的最新的審批記錄,查詢每個審批記錄的詳情,獲取相關的客戶信息、負責人信息。
審批通過後把結果寫入資料庫,已經抓取過的數據,不再進行處理
問題:由於釘釘和CRM系統的介面處理有一定的時間差異,一般來說釘釘的系統比較快,由於CRM系統沒有處理完,釘釘已經把業務處理完了,所以判斷資料庫那可能會出現重覆。處理過的審批最好放到緩存中才完美。
#region 獲取負責人流程信息 Task.Factory.StartNew(() => { DateTime dt = DateTime.Parse(DateTime.Now.AddDays(-1).ToString("yyyy-MM-dd")); //5分鐘一次 while (true) { if (string.IsNullOrWhiteSpace(Business.DingAccessToken)) { Thread.Sleep(1000); continue; } Business.ReqDingProcess(Business.DingAccessToken, dt, new Action<string>(str => { }), new Action<string>(str => { }) ); dt = DateTime.Now; Thread.Sleep(1000 * 60 * 5); } }); //獲取流程信息 Task.Factory.StartNew(() => { string logpath = $"{AppDomain.CurrentDomain.BaseDirectory}/logs/DingProcess"; if (!Directory.Exists(logpath)) Directory.CreateDirectory(logpath); while (true) { if (string.IsNullOrWhiteSpace(Business.DingAccessToken)) { Thread.Sleep(1000); continue; } if (Business.queueFzrProcess.IsEmpty) { Thread.Sleep(1000); continue; } Business.queueFzrProcess.TryDequeue(out string procid); if (string.IsNullOrWhiteSpace(procid)) continue; //如果該條目已經處理 則不再處理 var procids= DbAccess.Query($"select 1 from DingProc_CustomerFZR where procid='{procid}'"); if (procids.Any()) continue; Business.ReqDingProcessInfo(Business.DingAccessToken, procid, new Action<string>(str => { }), new Action<string>(str => { }) ); Thread.Sleep(100); } }); //根據客戶名稱查詢客戶ID 並記錄客戶信息到資料庫 Task.Factory.StartNew(() => { Dictionary<string, ConcurrentDictionary<string, string>> dicTemp = new Dictionary<string, ConcurrentDictionary<string, string>>(); while (true) { if (Business.XbbModel == null) { Thread.Sleep(1000); continue; } if (Business.queueProcs.IsEmpty) { Thread.Sleep(1000); continue; } Business.queueProcs.TryDequeue(out ConcurrentDictionary<string, string> dic); if (dic == null) { Thread.Sleep(1000); continue; } //查詢資料庫中客戶是否存在 var custid= DbAccess.Query($"select dataId from Customer{DateTime.Now.ToString("yyyyMMdd")} where text_1='{dic["customer"]}'"); if (!custid.Any()) { dic.AddOrUpdate("result", "客戶名稱不存在",(k,v)=>v); Business.queueProcsResult.Enqueue(dic); //客戶名稱不存在,保存到資料庫等待處理結果 Thread.Sleep(1000); continue; } string dataid = (custid.FirstOrDefault()).dataId.ToString(); if (dicTemp.ContainsKey(dataid)) { Business.queueProcs.Enqueue(dic); Thread.Sleep(10000); continue; } dicTemp.Add(dataid, dic); Business.ReqDistributionCustomer(dataid, dic["fzr"], Business.XbbModel.UserID, Business.XbbModel.CorpId, Business.XbbModel.token, new Action<string>(success => { dicTemp[success].AddOrUpdate("result", "成功", (k, v) => v); Business.queueProcsResult.Enqueue(dicTemp[success]); dicTemp.Remove(success); }), new Action<string>(fail => { dicTemp[fail].AddOrUpdate("result", "失敗", (k, v) => v); Business.queueProcsResult.Enqueue(dicTemp[fail]); dicTemp.Remove(fail); }) ); } }); Task.Factory.StartNew(() => { List<Dictionary<string, string>> lstdic = new List<Dictionary<string, string>>(); while (true) { if (Business.queueProcsResult.IsEmpty) { Thread.Sleep(1000); continue; } while (!Business.queueProcsResult.IsEmpty) { if (lstdic.Count > 50) break; Business.queueProcsResult.TryDequeue(out ConcurrentDictionary<string, string> dic); if (dic == null) continue; Dictionary<string, string> dicTemp = new Dictionary<string, string>(); foreach (var kv in dic) dicTemp.Add(kv.Key, kv.Value); lstdic.Add(dicTemp); } DbAccess.AddTran(lstdic, "DingProc_CustomerFZR", null); } }); #endregion
歡迎大家點評
學以致用,解放勞動力,把更多時光用在更美好的生活里。