因爲公司旗下有好幾個微信公衆號,常常來回切換登陸很麻煩,粉絲留言諮詢的時候經常不能及時回覆,致使訂單流失。因而咱們團隊開發了一個公衆號小助手,能夠把多個公衆號綁定進來,只要有粉絲留言,立刻管理員就收到通知了,而後還能夠在手機上進行回覆。html
實現的功能以下:json
雖然這個小助手很小,可是裏面用到的技術我以爲仍是有必定分享價值。本文就向你們分享一下這個小助手中核心的技術方案,包括:公衆號綁定、粉絲信息獲取、給粉絲髮送消息、微信圖片上傳與下載、公衆號自定義菜單接口、公衆號臨時二維碼的妙用等等。api
免費在線體驗:瀏覽器
想要調用微信公衆號的API,首先要經過AppId和AppSecret獲取AccessToken,而AccessToken過一段時間就會過時。爲了提升AccessToken的利用率而且實現自動刷新,咱們專門寫了一個AccessTokenContext來管理多個公衆號的AccessToken,這個類也是完成多個公衆號綁定最重要的一步。服務器
請看源碼:微信
1 public class AccessTokenContext 2 { 3 public static AccessTokenContext Instance { get; } 4 5 static AccessTokenContext() 6 { 7 Instance = new AccessTokenContext(); 8 } 9 10 private readonly Dictionary<Guid, AccountAccessTokenDto> _keyValues; 11 12 public AccessTokenContext() 13 { 14 _keyValues = new Dictionary<Guid, AccountAccessTokenDto>(); 15 } 16 17 public string GetDabenAccessToken() 18 { 19 return GetAccessToken(AppContext.DabenMpAccountId); 20 } 21 22 public string GetAccessToken(Guid accountId) 23 { 24 if (_keyValues.ContainsKey(accountId)) 25 { 26 var dto = _keyValues[accountId]; 27 if (dto.IsExpired() == false) 28 { 29 return dto.AccessToken; 30 } 31 } 32 var account = Ioc.Get<IAccountService>().Get(accountId); 33 var apidto = GetByApi(account); 34 _keyValues[account.Id] = apidto; 35 return apidto.AccessToken; 36 } 37 38 public string GetAccessToken(MpAccount account) 39 { 40 if (_keyValues.ContainsKey(account.Id)) 41 { 42 var dto = _keyValues[account.Id]; 43 if (dto.IsExpired()) 44 { 45 dto = GetByApi(account); 46 } 47 return dto.AccessToken; 48 } 49 else 50 { 51 var dto = GetByApi(account); 52 _keyValues[account.Id] = dto; 53 return dto.AccessToken; 54 } 55 } 56 57 private AccountAccessTokenDto GetByApi(MpAccount account) 58 { 59 var token = WeixinApi.GetAccessToken(account.AppId, account.AppSecret); 60 if (token == null || token.IsSuccess() == false) 61 { 62 throw new KnownException("Mp.GetAccessToken:" + account.Name); 63 } 64 return new AccountAccessTokenDto(account.Id, token); 65 } 66 }
一旦拿到了某個公衆號的AccessToken,就能夠調用絕大部分接口了。編輯器
不一樣的微信公衆號下面的粉絲擁有不一樣的OpenId,而OpenId是微信對於用於的惟一標識。ide
微信提供了幾個事件發生的時候,程序能夠獲取用戶的OpenId,而用OpenId就能夠跟用戶互動。咱們僅用了2個事件獲取OpenId:粉絲關注時和粉絲留言時。ui
下面的代碼展現瞭如何經過OpenId和AccessToken獲取粉絲基本信息。this
public class WeixinApi { public static UserDto GetUserInfo(string openId, string accessToken = null) { return HttpHelper.GetApiDto<UserDto>(WeixinConfigs.Urls.GetUserInfo(openId, accessToken)); } }
internal class HttpHelper { public static T GetApiDto<T>(string url) where T : ApiDtoBase { var html = DownloadString(url); try { var dto = Serializer.FromJson<T>(html); if (dto.IsSuccess() == false) { Logger.Error("GetApiDto." + typeof(T).FullName + ".NotSuccess", dto.GetFullError()); } return dto; } catch (Exception ex) { Logger.Error("GetApiDto." + typeof(T).FullName + ".Exception", ex); Logger.Error("GetApiDto." + typeof(T).FullName + ".Exception", html); } return null; } }
注意這裏僅僅是發送的客服消息,也就是粉絲與公衆號互動以後的48小時內能夠隨意給粉絲髮送的消息,包括文字和圖片。
public class WeixinApi { public static ApiDtoBase TrySendMessage(MessageBase message, string accessToken = null) { try { return HttpHelper.PostApiDto<ApiDtoBase>(WeixinConfigs.Urls.SendMessage(message is TemplateMessageBase, accessToken), message.ToJson()); } catch (Exception ex) { Logger.Error("WeixinApi TrySendMessage", ex); } return null; } }
internal class HttpHelper { public static T PostApiDto<T>(string url, string json) where T : ApiDtoBase { string html; using (var client = new WebClient()) { var result = client.UploadData(url, "POST", Encoding.UTF8.GetBytes(json ?? string.Empty)); html = Encoding.UTF8.GetString(result); } try { var dto = Serializer.FromJson<T>(html); if (dto.IsSuccess() == false) { Logger.Error("PostApiDto." + typeof(T).FullName + ".NotSuccess", dto.GetFullError()); } return dto; } catch (Exception ex) { Logger.Error("PostApiDto." + typeof(T).FullName + ".Exception", ex); return null; } } }
若是推送的是文本消息:
1 public class TextMessage : MessageBase 2 { 3 public string Text { get; set; } 4 5 public TextMessage(string openId, string text) 6 { 7 this.ToUserOpenId = openId; 8 this.Text = text; 9 } 10 11 public override string ToJson() 12 { 13 return Serializer.ToJson( 14 new 15 { 16 touser = this.ToUserOpenId, 17 msgtype = "text", 18 text = new 19 { 20 content = this.Text 21 } 22 }); 23 } 24 }
若是推送的是圖片消息,則須要先上傳圖片到微信服務器拿到media_Id(本文後面會展現):
1 public class ImageMessage : MessageBase 2 { 3 public string MediaId { get; set; } 4 5 public ImageMessage(string openId, string mediaId) 6 { 7 this.ToUserOpenId = openId; 8 this.MediaId = mediaId; 9 } 10 11 public override string ToJson() 12 { 13 return Serializer.ToJson( 14 new 15 { 16 touser = this.ToUserOpenId, 17 msgtype = "image", 18 image = new 19 { 20 media_id = this.MediaId 21 } 22 }); 23 } 24 }
微信圖片的上傳與下載都是經過media_id進行的。上傳一個圖片文件以後,微信服務器返回media_id;若是要下載某張圖片,也須要提供media_id。
關於圖片上傳這塊,咱們封裝了一個很是方便的微信圖片上傳控件,等之後有時間再給你們詳解這個控件,絕對超cool的,如今你能夠先體驗下。
圖片上傳以前,須要先將用戶上傳的圖片保存到服務器,而後再將服務器的圖片上傳到微信服務器:
public class WeixinApi { public static UploadFileDto UploadFile(string localFilePath, ResourceType type, string accessToken = null) { return HttpHelper.PostFile<UploadFileDto>(WeixinConfigs.Urls.UploadFile(type, accessToken), localFilePath); } }
1 internal class HttpHelper 2 { 3 public static T PostFile<T>(string url, string filePath) where T : ApiDtoBase 4 { 5 string html; 6 using (var client = new WebClient()) 7 { 8 var result = client.UploadFile(url, "POST", filePath); 9 html = Encoding.UTF8.GetString(result); 10 } 11 try 12 { 13 var dto = Serializer.FromJson<T>(html); 14 if (dto.IsSuccess() == false) 15 { 16 Logger.Error("PostFile." + typeof(T).FullName + ".NotSuccess", dto.GetFullError()); 17 } 18 return dto; 19 } 20 catch (Exception ex) 21 { 22 Logger.Error("PostFile." + typeof(T).FullName + ".Exception", ex); 23 return null; 24 } 25 } 26 }
下載圖片就很是簡單了,只須要經過media_id獲取下載圖片的URL便可:
public static string GetMediaDownloadUrl(string mediaId, string accessToken = null) { return "http://file.api.weixin.qq.com/cgi-bin/media/get" + $"?access_token={accessToken ?? WeixinKeyManager.Instance.GetAccessToken()}&media_id={mediaId}"; }
因爲微信定義的接口能夠接受JSON格式的自定義菜單項,因此咱們就用JS在瀏覽器中編輯菜單,而後最終提交的時候,將整個菜單序列化成JSON,一塊兒提交到微信服務器。
先看下咱們的自定義菜單編輯器吧,純JS打造的,有機會也給你們分享下:
調用這個JS的菜單編輯器很是簡單,只須要傳入一個容器和accesstoken便可:
(function() { $(document).ready(function () { var manager = new WeixinMenuAppManager($("#hfAccessToken").val(), $(".content")); manager.init(); $("#hfAccessToken").remove(); }); })();
而傳到咱們服務器以後,調用微信接口的代碼就很是簡單了:
1 public class WeixinApi 2 { 3 public static string GetMenuJson(string accessToken = null) 4 { 5 return HttpHelper.DownloadString(WeixinConfigs.Urls.GetMenu(accessToken)); 6 } 7 8 public static ApiDtoBase SaveMenu(string json, string accessToken = null) 9 { 10 return HttpHelper.PostApiDto<ApiDtoBase>(WeixinConfigs.Urls.SaveMenu(accessToken), json); 11 } 12 13 public static ApiDtoBase DeleteMenu(string accessToken = null) 14 { 15 return HttpHelper.PostApiDto<ApiDtoBase>(WeixinConfigs.Urls.DeleteMenu(accessToken), null); 16 } 17 }
首先說一下這個臨時二維碼是由微信服務器生成的。用戶掃碼以後,首先是關注公衆號,同時咱們服務器還接收到了這個二維碼額外的一個參數(系統惟一標識),咱們利用這個參數就能夠很方便的完成多管理員掃碼自動綁定的功能了。
業務流程:公衆號全部者點擊【添加管理員】,咱們系統就彈出一個有效期5分鐘的臨時二維碼,另外一個管理員掃碼關注公衆號以後,自動將他綁定到該公衆號,再給用戶推送一條客服消息,告訴他綁定成功。
這個體驗是至關的帥啊!
生成臨時二維碼的代碼:
public static string GetTempQrCodeUrl(int autoId, int expireMinutes, string accessToken = null) { var data = Serializer.ToJson(new { expire_seconds = expireMinutes*60, action_name = "QR_SCENE", action_info = new { scene = new { scene_id = autoId } } }); var result = HttpHelper.PostApiDto<GetQrCodeDto>(WeixinConfigs.Urls.GenerateQrCode(accessToken), data); if (result.IsSuccess() == false) { throw new KnownException(result.GetFullError()); } return WeixinConfigs.Urls.ShowQrCode(result.Ticket); }
用戶掃碼關注後,服務器完成自動綁定的代碼就不貼了,太多了,而且夾雜着咱們系統其餘的業務邏輯,不容易理解。