由于公司旗下有好幾個微信公眾號,經(jīng)常來回切換登錄很麻煩,粉絲留言咨詢的時候常常不能及時回復(fù),導(dǎo)致訂單流失。于是我們團(tuán)隊開發(fā)了一個公眾號小助手,可以把多個公眾號綁定進(jìn)來,只要有粉絲留言,馬上管理員就收到通知了,然后還可以在手機(jī)上進(jìn)行回復(fù)。 實(shí)現(xiàn)的功能如下:
雖然這個小助手很小,但是里面用到的技術(shù)我覺得還是有一定分享價值。本文就向大家分享一下這個小助手中核心的技術(shù)方案,包括:公眾號綁定、粉絲信息獲取、給粉絲發(fā)送消息、微信圖片上傳與下載、公眾號自定義菜單接口、公眾號臨時二維碼的妙用等等。 免費(fèi)在線體驗(yàn): 核心技術(shù)1:多個公眾號的綁定想要調(diào)用微信公眾號的API,首先要通過AppId和AppSecret獲取AccessToken,而AccessToken過一段時間就會過期。為了提高AccessToken的利用率并且實(shí)現(xiàn)自動刷新,我們專門寫了一個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,就可以調(diào)用絕大部分接口了。 核心技術(shù)2:拉取粉絲基本信息不同的微信公眾號下面的粉絲擁有不同的OpenId,而OpenId是微信對于用于的唯一標(biāo)識。 微信提供了幾個事件發(fā)生的時候,程序可以獲取用戶的OpenId,而用OpenId就可以跟用戶互動。我們僅用了2個事件獲取OpenId:粉絲關(guān)注時和粉絲留言時。 下面的代碼展示了如何通過OpenId和AccessToken獲取粉絲基本信息。 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;
}
} 核心技術(shù)3:給粉絲發(fā)送消息注意這里僅僅是發(fā)送的客服消息,也就是粉絲與公眾號互動之后的48小時內(nèi)可以隨意給粉絲發(fā)送的消息,包括文字和圖片。 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 } 如果推送的是圖片消息,則需要先上傳圖片到微信服務(wù)器拿到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 } 核心技術(shù)4:微信圖片上傳與下載微信圖片的上傳與下載都是通過media_id進(jìn)行的。上傳一個圖片文件之后,微信服務(wù)器返回media_id;如果要下載某張圖片,也需要提供media_id。 關(guān)于圖片上傳這塊,我們封裝了一個非常方便的微信圖片上傳控件,等以后有時間再給大家詳解這個控件,絕對超cool的,現(xiàn)在你可以先體驗(yàn)下。 圖片上傳之前,需要先將用戶上傳的圖片保存到服務(wù)器,然后再將服務(wù)器的圖片上傳到微信服務(wù)器: 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}';
} 核心技術(shù)5:自定義菜單的實(shí)現(xiàn)由于微信定義的接口可以接受JSON格式的自定義菜單項,所以我們就用JS在瀏覽器中編輯菜單,然后最終提交的時候,將整個菜單序列化成JSON,一起提交到微信服務(wù)器。 先看下我們的自定義菜單編輯器吧,純JS打造的,有機(jī)會也給大家分享下: 調(diào)用這個JS的菜單編輯器非常簡單,只需要傳入一個容器和accesstoken即可: (function {
$(document).ready(function {
var manager = new WeixinMenuAppManager($('#hfAccessToken').val, $('.content'));
manager.init;
$('#hfAccessToken').remove;
});
}); 而傳到我們服務(wù)器之后,調(diào)用微信接口的代碼就非常簡單了: 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 } 核心技術(shù)6:帶參數(shù)的臨時二維碼首先說一下這個臨時二維碼是由微信服務(wù)器生成的。用戶掃碼之后,首先是關(guān)注公眾號,同時我們服務(wù)器還接收到了這個二維碼額外的一個參數(shù)(系統(tǒng)唯一標(biāo)識),我們利用這個參數(shù)就可以很方便的完成多管理員掃碼自動綁定的功能了。 業(yè)務(wù)流程:公眾號所有者點(diǎn)擊【添加管理員】,我們系統(tǒng)就彈出一個有效期5分鐘的臨時二維碼,另一個管理員掃碼關(guān)注公眾號之后,自動將他綁定到該公眾號,再給用戶推送一條客服消息,告訴他綁定成功。 這個體驗(yàn)是相當(dāng)?shù)膸洶。?/p> 生成臨時二維碼的代碼: 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);
} 用戶掃碼關(guān)注后,服務(wù)器完成自動綁定的代碼就不貼了,太多了,并且夾雜著我們系統(tǒng)其他的業(yè)務(wù)邏輯,不容易理解。 結(jié)語這個公眾號小助手雖然很小,但幾乎完全包含了我們團(tuán)隊在微信公眾號開發(fā)方面積累的經(jīng)驗(yàn),以及我們自己封裝的很多控件、類庫。如果大家感興趣的話,后面我再專門把這些積累開源。 |
|