因为公司旗下有好几个微信公众号,常常来回切换登陆很麻烦,粉丝留言咨询的时候经常不能及时回复,致使订单流失。因而咱们团队开发了一个公众号小助手,能够把多个公众号绑定进来,只要有粉丝留言,立刻管理员就收到通知了,而后还能够在手机上进行回复。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); }
用户扫码关注后,服务器完成自动绑定的代码就不贴了,太多了,而且夹杂着咱们系统其余的业务逻辑,不容易理解。