C#开发微信门户及应用教程
作者:伍华聪
C#开发微信门户及应用(1)--开始使用微信接口 6
1、微信账号 6
2、微信菜单定义 7
3、接入微信的链接处理 8
4、使用开发方式创建菜单 14
5、我创建的菜单案例 17
C#开发微信门户及应用(2)--微信消息的处理和应答 18
1、微信的消息应答交互 18
2、微信的管理接口 25
C#开发微信门户及应用(3)--文本消息和图文消息的应答 29
1、实体信息关系及定义 30
2、消息的回复处理 37
C#开发微信门户及应用(4)--关注用户列表及详细信息管理 41
1、关注用户列表及用户分组信息 41
2、获取AIP调用者的的Token 47
3、获取关注用户列表 50
4、获取用户详细信息 59
C#开发微信门户及应用(5)--用户分组信息管理 62
1、用户分组管理内容 62
2、用户分组管理接口的实现 67
3、用户分组接口的调用 79
C#开发微信门户及应用(6)--微信门户菜单的管理操作 82
1、菜单的基础信息 82
2、菜单的实体类定义 85
3、菜单管理操作的接口实现 91
C#开发微信门户及应用(7)-微信多客服功能及开发集成 100
1、多客服准备工作 101
2、使用多客服客户端或助手操作 102
3、微信多客服的开发使用 103
C#开发微信门户及应用(8)-微信门户应用管理系统功能介绍 108
1、微信菜单管理 109
2、菜单事件的处理 112
3、微信消息内容管理 116
4、应答指令的维护 121
5、订阅用户管理 129
6、用户分组管理 134
7、多媒体管理 136
8、图文消息处理 139
9、会话消息管理 145
10、群发消息管理 147
C#开发微信门户及应用(9)-微信门户菜单管理及提交到微信服务器 149
1、微信菜单的要求及相关界面设计 150
2、提交菜单到微信服务器的操作 153
C#开发微信门户及应用(10)--在管理系统中同步微信用户分组信息 160
1、用户分组,在管理系统中的界面设计 161
2、分组同步操作代码展示 163
C#开发微信门户及应用(11)--微信菜单的多种表现方式介绍 172
1、微信自定义菜单的分类 172
2、重定向类型菜单的URL 174
3、重定向链接菜单的用途 182
C#开发微信门户及应用(12)-使用语音处理 182
1、微信语音接口的定义0 183
2、语音的处理操作 186
C#开发微信门户及应用(13)-使用地理位置扩展相关应用 197
1、微信的地理位置信息 198
2、地址位置的应用处理 205
3、地址位置应用扩展 208
C#开发微信门户及应用(14)-在微信菜单中采用重定向获取用户数据 223
1、微信重定向菜单的配置 224
2、脚本转换操作的实现代码 227
3、重定向页面的设计及处理 230
C#开发微信门户及应用(15)-微信菜单增加扫一扫、发图片、发地理位置功能 233
1、微信几个功能的官方介绍 234
2、微信新菜单功能的测试公众号 236
3、改进菜单对象和提交菜单 238
4、微信扫一扫功能集成 245
5、新菜单功能测试发现的问题 250
C#开发微信门户及应用(16)-微信企业号的配置和使用 251
1、微信企业号的注册和登陆 251
2、设置开发回调模式 256
3、实现回调页面的功能开发 259
C#开发微信门户及应用(17)-微信企业号的通讯录管理开发之部门管理 266
1、企业组织的创建和配置 266
2、API访问的全局唯一票据AccessToken的获取 270
2、通讯录管理之部门信息的维护 272
3、部门管理的API调用 278
C#开发微信门户及应用(18)-微信企业号的通讯录管理开发之成员管理 281
1、成员的创建操作 281
2、成员的更新操作 287
3、成员的删除、成员的获取、部门成员的获取操作 290
7、综合例子调用代码 295
C#开发微信门户及应用(19)-微信企业号的消息发送(文本、图片、文件、语音、视频、图文消息等) 299
1、企业号特点 299
2、企业号的管理接口内容 300
3、企业号消息和事件的处理 302
4、企业号消息管理 304
5、消息接口的定义和实现 310
6、消息的发送操作和实际效果 313
C#开发微信门户及应用(20)-微信企业号的菜单管理 317
1、菜单的总体介绍 318
2、菜单的实体类定义和接口定义处理 319
3、企业号菜单管理接口的调用和处理效果 324
微信应用如火如荼,很多公司都希望搭上信息快车,这个是一个商机,也是一个技术的方向,因此,有空研究下、学习下微信的相关开发,也就成为日常计划的重要事情之一了。本系列文章希望从一个循序渐进的角度上,全面介绍微信的相关开发过程和相关经验总结,希望给大家了解一下相关的开发历程。本随笔主要针对微信开发过程的前期准备和一些初始的工作的介绍。
在写下本文的之前一周时间里,我主要就是参考一些介绍文章以及微信公众平台的相关接口说明,并结合C#的代码开发,整理了自己公司的门 户界面,实现了微信工作号的初步用户交互和信息展示工作,随着工作的进一步开展,越来越多的功能可能加入,并希望从应用角度上扩展微信的接口,从而实现我 对微信接口的技术探秘和了解过程。
要开发使用微信的平台API,就需要到微信的公众平台(https://mp.weixin.qq.com/)去注册,拥有一个服务号或者订阅号,服务号主要面对企业和组织,订阅号主要面向组织和个人,他们之间有一定的差异,根据不同的需要自己申请对应的账号即可。
为了使用一些高级的接口,你可能需要拥有服务号和高级的认证。账号注册过程,需要下载一个申请表格,打印并盖公章,另外还需要申请人拿着身份证拍照(有点怪异,呵呵),然后上传到服务器进行审核,一般很快就能获取批复。
我以公司名义申请了服务号,账号注册后,会在主界面上显示你的相关信息,另外给你申请一个二维码的东西,扫描二维码即可进入公司的微信关注确认对话框,非常方便。如下就是我申请后的公司账号二维码,可以直接使用扫描。
微信有两种方式的菜单定义,一种是编辑模式,一种是开发模式,两者互斥,也就是说,一旦我们采用了开发模式,就不能使用编辑模式了,反过来也一样。编辑下的菜单,其实也是可以管理的,但是微信不支持,觉得很不爽。
一般情况下,如果我们刚刚申请了微信号码,可以使用编辑菜单测试一下,根据说明编辑一些菜单试试。虽然微信说24小时内更新,不过一般很快,最快可能一两分钟就更新了,感觉还是不错的。
使用开发者模式,你需要根据微信的要求,在服务器上放置一个页面链接,使用C#开发的,可以采用***.ashx的命名方式,使用Asp.NET的一般处理程序即可,不需要使用普通的页面。
使用开发模式的菜单,也就是可以调用微信API进行菜单创建的工作,对于调用微信的API(微信有很多API可以调用),我们需要知道,有几个参数的重要性,所以在开发模式打开的时候,会给你列出这些参数,如下所示。
上面说了,你申请开发模式对菜单或者对其他API的调用,你需要顺利通过接入微信的测试,也就是确认你填写的链接存在并能顺利经过微信的回调测试。微信提供了一个PHP的页面处理例子,如果我们是C#开发的呢,可以搜一下就会得到答案,我的处理方式如下所示。
创建一个一般处理程序,然后在其处理页面里面增加一个处理逻辑,如果是非POST方式的内容,就是表示微信进行的Get测试,你需要增加一些处理逻辑,把它给你的内容传回去即可,如果是POST方式的,就是微信服务器对接口消息的请求操作了,后面介绍。
///
/// 微信接口。统一接收并处理信息的入口。
///
public class wxapi : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
string postString = string.Empty;
if (HttpContext.Current.Request.HttpMethod.ToUpper() =="POST")
{
using (Stream stream = HttpContext.Current.Request.InputStream)
{
Byte[] postBytes = new Byte[stream.Length];
stream.Read(postBytes, 0, (Int32)stream.Length);
postString = Encoding.UTF8.GetString(postBytes);
}
if (!string.IsNullOrEmpty(postString))
{
Execute(postString);
}
}
else
{
Auth(); //微信接入的测试
}
}
一般来说,Auth函数里面,就是要对相关的参数进行获取,然后进行处理返回给微信服务器。
string token = "****";//你申请的时候填写的Token
string echoString = HttpContext.Current.Request.QueryString["echoStr"];
string signature = HttpContext.Current.Request.QueryString["signature"];
string timestamp = HttpContext.Current.Request.QueryString["timestamp"];
string nonce = HttpContext.Current.Request.QueryString["nonce"];
完整的Author函数代码如下所示,其中我把业务逻辑进行进一步抽取到了一个新的类里面,方便业务逻辑的管理。
///
/// 成为开发者的第一步,验证并相应服务器的数据
///
private void Auth()
{
string token = ConfigurationManager.AppSettings["WeixinToken"];//从配置文件获取Token
if (string.IsNullOrEmpty(token))
{
LogTextHelper.Error(string.Format("WeixinToken 配置项没有配置!"));
}
string echoString = HttpContext.Current.Request.QueryString["echoStr"];
string signature = HttpContext.Current.Request.QueryString["signature"];
string timestamp = HttpContext.Current.Request.QueryString["timestamp"];
string nonce = HttpContext.Current.Request.QueryString["nonce"];
if (new BasicApi().CheckSignature(token, signature, timestamp, nonce))
{
if (!string.IsNullOrEmpty(echoString))
{
HttpContext.Current.Response.Write(echoString);
HttpContext.Current.Response.End();
}
}
}
而对微信参数的签名并返回的操作CheckSignature,代码如下所示。
///
/// 验证微信签名
///
public bool CheckSignature(string token,string signature, string timestamp, string nonce)
{
string[] ArrTmp = { token, timestamp, nonce };
Array.Sort(ArrTmp);
string tmpStr = string.Join("", ArrTmp);
tmpStr = FormsAuthentication.HashPasswordForStoringInConfigFile(tmpStr,"SHA1");
tmpStr = tmpStr.ToLower();
if (tmpStr == signature)
{
return true;
}
else
{
return false;
}
}
一旦你顺利通过微信的认证,那么它就让你以开发方式调用它的API,并且可以随意创建你的菜单了。
创建菜单的方式,你可以通过下面的位置进入到他的API处理界面里面。
进入后,你会发现微信把很多消息的处理,分门别类放到不同的分类里面了。
其实我们现在初步要做的就是如何看看,使用代码方式调用创建菜单,进入菜单的API调试界面里面。
你会发现里面还需要输入一个Access_Token的东西,这个是一个会话身份认证,因此你还需要到接口里面去找这个如何创建的。下面图中的两个红色部分,就是我们开始的时候,微信提示我们“开发者凭据”的两个关键参数。
弄完这些,你就可以根据获得的Access_Token进行菜单的创建工作了,根据菜单的定义,它分为几类,可以分为URL方式(View),事件方式(Click)。
click:用户点击click类型按钮后,微信服务器会通过消息接口推送消息类型为event 的结构给开发者(参考消息接口指南),并且带上按钮中开发者填写的key值,开发者可以通过自定义的key值与用户进行交互;
view:用户点击view类型按钮后,微信客户端将会打开开发者在按钮中填写的url值 (即网页链接),达到打开网页的目的,建议与网页授权获取用户基本信息接口结合,获得用户的登入个人信息。
在随笔的开始,我公布了一个二维码,一旦使用微信扫一扫,进行关注服务号后,那么就可以看到我自己创建的菜单了。主菜单一般最多三列,每个主菜单还可以有子菜单,他们的文字都有所限制的。
我们来看看我公司的微信门户菜单,看起来是不是很酷呢。
微信应用如火如荼,很多公司都希望搭上信息快车,这个是一个商机,也是一个技术的方向,因此,有空研究下、学习下微信的相关开发,也就成为计划的安排事情之一了。本系列文章希望从一个循序渐进的角度上,全面介绍微信的相关开发过程和相关经验总结,希望给大家了解一下相关的开发历程。本篇随笔主要基于上一篇《C#开发微信门户及应用(1)--开始使用微信接口》的基础上进行深入的介绍,介绍微信消息的处理和应答的过程。
我们知道,微信的服务器架起了客户手机和开发者服务器的一个桥梁,通过消息的传递和响应,实现了与用户的交互操作,下面是它的消息流程图。
微信向开发者服务器请求的消息包含了多种类型,不过基本来说,分为了文本消息处理、事件消息处理、语音消息的识别,以及成为开发者之前的那个消息认证操作基本分类,下面是我绘制的一个消息分类图,其中介绍了这几种关系,以及各自的消息细化分类。
对于这些消息的请求,我们在开发服务器端,需要编写相关的逻辑进行对应给的处理,然后给微信服务器平台回应消息即可。
在前一篇的随笔里面我贴过代码,介绍微信消息处理的入口操作,代码如下所示。
public void ProcessRequest(HttpContext context)
{
//WHC.Framework.Commons.LogTextHelper.Info("测试记录");
string postString = string.Empty;
if (HttpContext.Current.Request.HttpMethod.ToUpper() =="POST")
{
using (Stream stream = HttpContext.Current.Request.InputStream)
{
Byte[] postBytes = new Byte[stream.Length];
stream.Read(postBytes, 0, (Int32)stream.Length);
postString = Encoding.UTF8.GetString(postBytes);
}
if (!string.IsNullOrEmpty(postString))
{
Execute(postString);
}
}
else
{
Auth();
}
}
其中的Execute(postString);就是对消息的处理函数,它实现了对不同消息的分发处理过程。‘
///
/// 处理各种请求信息并应答(通过POST的请求)
///
/// POST方式提交的数据
private void Execute(string postStr)
{
WeixinApiDispatch dispatch = new WeixinApiDispatch();
string responseContent = dispatch.Execute(postStr);
HttpContext.Current.Response.ContentEncoding = Encoding.UTF8;
HttpContext.Current.Response.Write(responseContent);
}
里面的WeixinApiDispatch就是一个分发的管理类,它提取请求消息的内容,并构建不同类型的消息参数,传递给不同的响应函数进行处理,然后返回封装好的XML内容,作为响应。
具体的代码处理逻辑如下图所示。
这个消息处理接口,其实就是定义好一系列的对请求消息的处理操作,参数是不同给的消息对象,具体的代码定义如下所示(由于篇幅原因,省略部分接口,具体可以参考上图)。
///
/// 客户端请求的数据接口
///
public interface IWeixinAction
{
///
/// 对文本请求信息进行处理
///
/// 文本信息实体
///
string HandleText(RequestText info);
///
/// 对图片请求信息进行处理
///
/// 图片信息实体
///
string HandleImage(RequestImage info);
...........................
///
/// 对订阅请求事件进行处理
///
/// 订阅请求事件信息实体
///
string HandleEventSubscribe(RequestEventSubscribe info);
///
/// 对菜单单击请求事件进行处理
///
/// 菜单单击请求事件信息实体
///
string HandleEventClick(RequestEventClick info);
..............................
}
从上面的代码可以看出,不同的消息,到处理函数这里,就以不同的消息实体类的方式传递过来了(注意:实体类是我根据程序开发需要自己定义的,非微信本身的实体类),这样非常方便我们处理操作,否则每次需要解析不同的消息内容,很容易出现问题,这样强类型的数据类型,提高了我们开发微信应用的强壮型和高效性。这些实体类的对象有一定的继承关系的,他们的继承关系如下所示。
上面的消息分类是微信服务器向开发者服务器发送的消息请求操作,还有一种消息,是我们开发者服务器向微信服务器进行的消息请求或者响应,这种这里暂且称之为微信的管理接口,它表明了我们可以通过这些接口进行相关的消息回复或者数据管理操作。它的分类图如下所示。
微信的回复消息处理,它也和上面小节的信息一样,它也是继承自BaseMessage实体类的(同样,下图的实体类及其继承关系也是自定义的,方便程序开发),它的关系如下所示
回复的消息,一般用的最多的是文本消息和图文消息。
文本消息的效果如下所示。
图文消息,可以增加图片,还可以增加详细的链接页面,是非常好看的一种效果,对于一些内容比较多,希望展现更好效果的,一般采用这种,效果如下所示。
微信应用如火如荼,很多公司都希望搭上信息快车,这个是一个商机,也是一个技术的方向,因此,有空研究下、学习下微信的相关开发,也就成为计划的安排事情之一了。本系列文章希望从一个循序渐进的角度上,全面介绍微信的相关开发过程和相关经验总结,希望给大家了解一下相关的开发历程。
在前面两篇两篇随笔《C#开发微信门户及应用(1)--开始使用微信接口》和《C#开发微信门户及应用(2)--微信消息的处理和应答》里面,大致介绍了我微信应用的框架构建,本随笔继续介绍这一主题,介绍消息应答里面的文本应答和图文应答的过程。
我们知道,给手机用户发送响应消息,它可以分为好多种方式,如回复文本消息、回复图片消息、回复语音消息、回复视频消息、回复音乐消息、回复图文消息等,如下所示。
而其中图片、视频、语音这三种方式,是需要开通微信认证才可以向用户发送存在微信服务器上的媒体信息,一般没有认证的公众号或者服务号,是不能发送这几种内容的。
在上一篇微信开发的随笔中,我展示了对接收消息和回复消息的应用实体类,这些实体类是我根据需要,根据开发需要,在应用层面对它们进行了封装,如回复的消息关系如下所示。
消息基类BaseMessage的实体类定义如下所示,它对日期构造了一个整形数值,并具备了一些常规的属性,并且还有一个重要的ToXML方法,用来给方法传递这些XML数据的。
///
/// 基础消息内容
///
[XmlRoot(ElementName = "xml")]
public class BaseMessage
{
///
/// 初始化一些内容,如创建时间为整形,
///
public BaseMessage()
{
this.CreateTime = DateTime.Now.DateTimeToInt();
}
///
/// 开发者微信号
///
public string ToUserName {get; set; }
///
/// 发送方帐号(一个OpenID)
///
public string FromUserName {get; set; }
///
/// 消息创建时间(整型)
///
public int CreateTime {get; set; }
///
/// 消息类型
///
public string MsgType {get; set; }
public virtual string ToXml()
{
this.CreateTime = DateTime.Now.DateTimeToInt();//重新更新
return MyXmlHelper.ObjectToXml(this);
}
}
回复的文本消息实体类代码如下所示,我们可以看到,它继承了很多通用的实体属性,并且还具备了一个ToXml的通用方法,我们需要把它转换为响应的XML的时候,就使用这个方法就可以了。
///
/// 回复文本消息
///
[System.Xml.Serialization.XmlRoot(ElementName = "xml")]
public class ResponseText : BaseMessage
{
public ResponseText()
{
this.MsgType = ResponseMsgType.Text.ToString().ToLower();
}
public ResponseText(BaseMessage info) :this()
{
this.FromUserName = info.ToUserName;
this.ToUserName = info.FromUserName;
}
///
/// 内容
///
public string Content {get; set; }
}
而图文消息对象类ResponseNews,它包含更多的信息定义
///
/// 回复图文消息
///
[System.Xml.Serialization.XmlRoot(ElementName = "xml")]
public class ResponseNews : BaseMessage
{
public ResponseNews()
{
this.MsgType = ResponseMsgType.News.ToString().ToLower();
this.Articles = new List
}
public ResponseNews(BaseMessage info) :this()
{
this.FromUserName = info.ToUserName;
this.ToUserName = info.FromUserName;
}
///
/// 图文消息个数,限制为10条以内
///
public int ArticleCount
{
get
{
return this.Articles.Count;
}
set
{
;//增加这个步骤才出来XML内容
}
}
///
/// 图文列表。
/// 多条图文消息信息,默认第一个item为大图,注意,如果图文数超过10,则将会无响应
///
[System.Xml.Serialization.XmlArrayItem("item")]
public List
}
而其中的图文列表集合中的对象,它也是一个实体类型,包含了一些图文的链接,标题等信息,不在赘述。
如对于文本消息,我们可以用以下的方式进行处理。
ResponseText response = new ResponseText(info);
response.Content = "抱歉,此功能暂未开通。";
result = response.ToXml();
对于图文消息,我们可能需要录入更多的消息才能返回更好的效果。
注意图文的消息,图片的尺寸最好按照官方的标准,否则在手机上看起来不好看,官方的标准好像是宽高是(360,200)像素
///
/// 订阅或者显示公司信息
///
///
///
private string ShowCompanyInfo(BaseMessage info)
{
string result = "";
//使用在微信平台上的图文信息(单图文信息)
ResponseNews response = new ResponseNews(info);
ArticleEntity entity = new ArticleEntity();
entity.Title = "广州爱奇迪软件科技有限公司";
entity.Description = "欢迎关注广州爱奇迪软件--专业的单位信息化软件和软件开发框架提供商,我们立志于为客户提供最好的软件及服务。\r\n";
entity.Description += "我们是一家极富创新性的软件科技公司,从事研究、开发并销售最可靠的、安全易用的技术产品及优质专业的服务,帮助全球客户和合作伙伴取得成功。\r\n......(此处省略1000字,哈哈)";
entity.PicUrl = "http://www.iqidi.com/WeixinImage/company.png";
entity.Url = "http://www.iqidi.com";
response.Articles.Add(entity);
result = response.ToXml();
return result;
}
我们来看看我公司的微信门户菜单,看起来是不是很酷呢。
对于这两种(文本消息、图文消息)用的地方是最多,很多微信门户,都主要是使用这两种方式进行响应。当然,我们还可以根据客户手机提交上来的各种消息进行不同的处理,请求消息的类型我在上一篇的随笔有介绍,如下所示。
需要关注了解整体效果,可以使用微信直接扫描二维码即可。
在上个月的对C#开发微信门户及应用做了介绍,写过了几篇的随笔进行分享,由于时间关系,间隔了一段时间没有继续写这个系列的博客了,并不是对这个方面停止了研究,而是继续深入探索这方面的技术,为了更好的应用起来,专心做好底层的技术开发。
微信的很重要的一个特点就是能够利用其平台庞大的用户群体,因此很容易整合在CRM(客户关系管理)系统里面,服务号和订阅好都能够向关注者推送相 关的产品消息,还能和48小时内响应消息和事件的活跃用户进行交互对话,因此用户信息是微信API里面非常重要的一环,本随笔主要介绍获取关注用户、查看 用户信息、分组管理等方面的开发应用。
在微信的管理平台上,我们可以看到自己账号的关注者用户,以及用户分组信息,如下所示。
上面的管理界面,能看到关注者用户的基础信息,但是使用微信API获取到的是一个称之为OpenID的列表,我们先了解这个东西是什么?微信API的说明给出下面的解析:
关注者列表由一串OpenID(加密后的微信号,每个用户对每个公众号的OpenID是唯一的。对于不同公众号,同一用户的openid不同)组成。公众号可通过本接口来根据OpenID获取用户基本信息,包括昵称、头像、性别、所在城市、语言和关注时间。
上面的解析意思很清楚了,就是一个用户关注我们的公众号,那么不管他是第几次关注,对我们公众号来说,都是一个确定的值;但是,一个用户对其他公众号,却有着其他不同的OpenID。
微信提供了为数不多的几个关键字信息,用来记录用户的相关内容,根据用户的相关定义,我们定义一个实体类,用来放置获取回来的用户信息。
///
/// 高级接口获取的用户信息。
/// 在关注者与公众号产生消息交互后,公众号可获得关注者的OpenID
/// (加密后的微信号,每个用户对每个公众号的OpenID是唯一的。对于不同公众号,同一用户的openid不同)。
/// 公众号可通过本接口来根据OpenID获取用户基本信息,包括昵称、头像、性别、所在城市、语言和关注时间。
///
public class UserJson : BaseJsonResult
{
///
/// 用户是否订阅该公众号标识,值为0时,代表此用户没有关注该公众号,拉取不到其余信息。
///
public int subscribe {get; set; }
///
/// 用户的标识,对当前公众号唯一
///
public string openid {get; set; }
///
/// 用户的昵称
///
public string nickname {get; set; }
///
/// 用户的性别,值为1时是男性,值为2时是女性,值为0时是未知
///
public int sex {get; set; }
///
/// 用户的语言,简体中文为zh_CN
///
public string language {get; set; }
///
/// 用户所在城市
///
public string city {get; set; }
///
/// 用户所在省份
///
public string province {get; set; }
///
/// 用户所在国家
///
public string country {get; set; }
///
/// 用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空
///
public string headimgurl {get; set; }
///
/// 用户关注时间,为时间戳。如果用户曾多次关注,则取最后关注时间
///
public long subscribe_time {get; set; }
}
根据分组信息定义,我们定义一个分组的实体类信息。
///
/// 分组信息
///
public class GroupJson : BaseJsonResult
{
///
/// 分组id,由微信分配
///
public int id {get; set; }
///
/// 分组名字,UTF8编码
///
public string name {get; set; }
}
在做微信API的开发,很多时候,我们都需要传入一个AccessToken,这个就是区分调用者和记录会话信息的字符串,因此,在学习所有API开发之前,我们需要很好理解这个访问控制参数。
这个对象的定义,我们可以从微信的API说明中了解。
access_token是公众号的全局唯一票据,公众号调用各接口时都需使用access_token。正常情况下access_token有效期为7200秒,重复获取将导致上次获取的access_token失效。由于获取access_token的api调用次数非常有限,建议开发者全局存储与更新access_token,频繁刷新access_token会导致api调用受限,影响自身业务。
根据上面的说明定义,我们可以看到,它是一个和身份,以及会话时间有关的一个参数,而且它的产生次数有限制,因此要求我们需要对它进行缓存并重复利用,在会话到期之前,我们应该尽可能重用这个参数,避免反复请求,增加服务器压力,以及调用的时间。
我定义了一个方法,用来构造生成相关的Access Token,而且它具有缓存的功能,但具体如何缓存及使用,对我API的调用是透明的,我们只要用的时候,就对它调用就是了。
/// 获取凭证接口
///
/// 第三方用户唯一凭证
/// 第三方用户唯一凭证密钥,既appsecret
string GetAccessToken(string appid,string secret);
缓存主要是基于.NET4增加的类库MemoryCache,这个是一个非常不错的缓存类。
我的获取AccessToken的操作实现代码如下所示。
///
/// 获取每次操作微信API的Token访问令牌
///
/// 应用ID
/// 开发者凭据
///
public string GetAccessToken(string appid,string secret)
{
//正常情况下access_token有效期为7200秒,这里使用缓存设置短于这个时间即可
string access_token = MemoryCacheHelper.GetCacheItem<string>("access_token",delegate()
{
string grant_type ="client_credential";
var url = string.Format("https://api.weixin.qq.com/cgi-bin/token?grant_type={0}&appid={1}&secret={2}",
grant_type, appid, secret);
HttpHelper helper = new HttpHelper();
string result = helper.GetHtml(url);
string regex = "\"access_token\":\"(?
string token = CRegex.GetText(result, regex,"token");
return token;
},
new TimeSpan(0,0, 7000)//7000秒过期
);
return access_token;
}
由于我们知道,AccessToken默认是7200秒过期,因此在这个时间段里面,我们尽可能使用缓存来记录它的值,如果超过了这个时间,我们调用这个方法的时候,它会自动重新获取一个新的值给我们了。
获取关注用户列表,一次拉取API调用,最多拉取10000个关注者的OpenID,可以通过多次拉取的方式来满足需求。微信的接口定义如下所示。
http请求方式: GET(请使用https协议)
https://api.weixin.qq.com/cgi-bin/user/get?access_token=ACCESS_TOKEN&next_openid=NEXT_OPENID
这个接口返回的数据是
{"total":2,"count":2,"data":{"openid":["","OPENID1","OPENID2"]},"next_openid":"NEXT_OPENID"}
根据返回的Json数据定义,我们还需要定义两个实体类,用来存放返回的结果。
///
/// 获取关注用户列表的Json结果
///
public class UserListJsonResult : BaseJsonResult
{
///
/// 关注该公众账号的总用户数
///
public int total {get; set; }
///
/// 拉取的OPENID个数,最大值为10000
///
public int count {get; set; }
///
/// 列表数据,OPENID的列表
///
public OpenIdListData data { get; set; }
///
/// 拉取列表的后一个用户的OPENID
///
public string next_openid {get; set; }
}
///
/// 列表数据,OPENID的列表
///
public class OpenIdListData
{
///
/// OPENID的列表
///
public List<string> openid {get; set; }
}
为了获取相关的用户信息,我定义了一个接口,用来获取用户的信息,接口定义如下所示。
///
/// 微信用户管理的API接口
///
public interface IUserApi
{
///
/// 获取关注用户列表
///
/// 调用接口凭证
/// 第一个拉取的OPENID,不填默认从头开始拉取
///
List<string> GetUserList(string accessToken,string nextOpenId = null);
///
/// 获取用户基本信息
///
/// 调用接口凭证
/// 普通用户的标识,对当前公众号唯一
/// 返回国家地区语言版本,zh_CN 简体,zh_TW 繁体,en 英语
UserJson GetUserDetail(string accessToken,string openId, Language lang = Language.zh_CN);
然后在实现类里面,我们分别对上面两个接口进行实现,获取用户列表信息如下所示。
///
/// 获取关注用户列表
///
/// 调用接口凭证
/// 第一个拉取的OPENID,不填默认从头开始拉取
///
public List<string> GetUserList(string accessToken,string nextOpenId = null)
{
List<string> list = new List<string>();
string url = string.Format("https://api.weixin.qq.com/cgi-bin/user/get?access_token={0}", accessToken);
if (!string.IsNullOrEmpty(nextOpenId))
{
url += "&next_openid=" + nextOpenId;
}
UserListJsonResult result = JsonHelper
if (result != null && result.data != null)
{
list.AddRange(result.data.openid);
}
return list;
}
我们看到,转换的逻辑已经放到了JsonHelper里面去了,这个辅助类里面分别对数值进行了获取内容,验证返回值,然后转换正确实体类几个部分的操作。
获取内容,通过辅助类HttpHelper进行,这个在我的公用类库里面,里面的逻辑主要就是通过HttpRequest进行数据的获取操作,不在赘述。
HttpHelper helper = new HttpHelper();
string content = helper.GetHtml(url);
由于返回的内容,我们需要判断它是否正确返回所需的结果,如果没有,抛出自定义的相关异常,方便处理,具体如下所示。
///
/// 检查返回的记录,如果返回没有错误,或者结果提示成功,则不抛出异常
///
/// 返回的结果
///
private static bool VerifyErrorCode(string content)
{
if (content.Contains("errcode"))
{
ErrorJsonResult errorResult = JsonConvert.DeserializeObject
//非成功操作才记录异常,因为有些操作是返回正常的结果({"errcode": 0, "errmsg": "ok"})
if (errorResult != null && errorResult.errcode != ReturnCode.请求成功)
{
string error = string.Format("微信请求发生错误!错误代码:{0},说明:{1}", (int)errorResult.errcode, errorResult.errmsg);
LogTextHelper.Error(errorResult);
throw new WeixinException(error);//抛出错误
}
}
return true;
}
然后转换为相应的格式,就是通过Json.NET的类库进行转换。
T result = JsonConvert.DeserializeObject
return result;
这样我们就可以在ConvertJson函数实体里面,完整的进行处理和转换了,转换完整的函数代码如下所示。
///
/// Json字符串操作辅助类
///
public class JsonHelper
{
///
/// 检查返回的记录,如果返回没有错误,或者结果提示成功,则不抛出异常
///
/// 返回的结果
///
private static bool VerifyErrorCode(string content)
{
if (content.Contains("errcode"))
{
ErrorJsonResult errorResult = JsonConvert.DeserializeObject
//非成功操作才记录异常,因为有些操作是返回正常的结果({"errcode": 0, "errmsg": "ok"})
if (errorResult != null && errorResult.errcode != ReturnCode.请求成功)
{
string error = string.Format("微信请求发生错误!错误代码:{0},说明:{1}", (int)errorResult.errcode, errorResult.errmsg);
LogTextHelper.Error(errorResult);
throw new WeixinException(error);//抛出错误
}
}
return true;
}
///
/// 转换Json字符串到具体的对象
///
/// 返回Json数据的链接地址
///
public static T ConvertJson(string url)
{
HttpHelper helper = new HttpHelper();
string content = helper.GetHtml(url);
VerifyErrorCode(content);
T result = JsonConvert.DeserializeObject
return result;
}
}
调用这个API的界面层代码如下所示(测试代码)
IUserApi userBLL = new UserApi();
List<string> userList = userBLL.GetUserList(token)
上面的获取列表操作,相对比较简单,而且不用POST任何数据,因此通过Get协议就能获取到所需的数据。
本小节继续介绍获取用户详细信息的操作,这个操作也是通过GET协议就可以完成的。
这个API的调用定义如下所示:
http请求方式: GET
https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
通过传入一个OpenId,我们就能很好获取到用户的相关信息了。
前面小节我们已经定义了它的接口,说明了传入及返回值,根据定义,它的实现函数如下所示。
///
/// 获取用户基本信息
///
/// 调用接口凭证
/// 普通用户的标识,对当前公众号唯一
/// 返回国家地区语言版本,zh_CN 简体,zh_TW 繁体,en 英语
public UserJson GetUserDetail(string accessToken,string openId, Language lang = Language.zh_CN)
{
string url = string.Format("https://api.weixin.qq.com/cgi-bin/user/info?access_token={0}&openid={1}&lang={2}",
accessToken, openId, lang.ToString());
UserJson result = JsonHelper
return result;
}
最后,我们结合获取用户列表和获取用户详细信息的两个API,我们看看调用的代码(测试代码)。
private void btnGetUsers_Click(object sender, EventArgs e)
{
IUserApi userBLL = new UserApi();
List<string> userList = userBLL.GetUserList(token);
foreach (string openIdin userList)
{
UserJson userInfo = userBLL.GetUserDetail(token, openId);
if (userInfo != null)
{
string tips = string.Format("{0}:{1}", userInfo.nickname, userInfo.openid);
Console.WriteLine(tips);
}
}
}
在上个月的对C#开发微信门户及应用做了介绍,写过了几篇的随笔进行分享,由于时间关系,间隔了一段时间没有继续写这个系列的博客了,并不是对这个 方面停止了研究,而是继续深入探索这方面的技术,为了更好的应用起来,专心做好底层的技术开发。本篇继续上一篇的介绍,主要介绍分组管理方面的开发应用, 这篇的内容和上一篇,作为一个完整的用户信息和分组信息管理的组合。
用户分组的引入,主要是方便管理关注者列表,以及方便向不同的组别发送消息的操作的,一个公众账号,最多支持创建500个分组。
用户分组管理,包含下面几个方面的内容:
1 创建分组
2 查询所有分组
3 查询用户所在分组
4 修改分组名
5 移动用户分组
微信对于创建分组的定义如下所示。
http请求方式: POST(请使用https协议)
https://api.weixin.qq.com/cgi-bin/groups/create?access_token=ACCESS_TOKEN
POST数据格式:json
POST数据例子:{"group":{"name":"test"}}
正常返回的结果如下所示。
{
"group": {
"id": 107,
"name": "test"
}
}
其他接口,也是类似的方式,通过POST一些参数进去URL里面,获取返回的Json数据。
前面随笔定义了GroupJson的实体类信息如下所示。
///
/// 分组信息
///
public class GroupJson : BaseJsonResult
{
///
/// 分组id,由微信分配
///
public int id { get; set; }
///
/// 分组名字,UTF8编码
///
public string name { get; set; }
}
根据以上几个接口的定义,我定义了几个接口,并把它们归纳到用户管理的API接口里面。
///
/// 查询所有分组
///
/// 调用接口凭证
///
List
///
/// 创建分组
///
/// 调用接口凭证
/// 分组名称
///
GroupJson CreateGroup(string accessToken,string name);
///
/// 查询用户所在分组
///
/// 调用接口凭证
/// 用户的OpenID
///
int GetUserGroupId(string accessToken,string openid);
///
/// 修改分组名
///
/// 调用接口凭证
/// 分组id,由微信分配
/// 分组名字(30个字符以内)
///
CommonResult UpdateGroupName(string accessToken,int id, string name);
///
/// 移动用户分组
///
/// 调用接口凭证
/// 用户的OpenID
/// 分组id
///
CommonResult MoveUserToGroup(string accessToken,string openid, int to_groupid);
2.1 创建用户分组
为了解析如何实现创建用户分组的POST数据操作,我们来一步步了解创建用户的具体过程。
首先需要创建一个动态定义的实体类信息,它包含几个需要提及的属性,如下所示。
string url = string.Format("https://api.weixin.qq.com/cgi-bin/groups/create?access_token={0}", accessToken);
var data = new
{
group = new
{
name = name
}
};
string postData = data.ToJson();
其中我们把对象转换为合适的Json数据操作,放到了扩展方法ToJson里面了,这个主要就是方便把动态定义的实体类转换Json内容,主要就是调用Json.NET的序列号操作。
///
/// 把对象为json字符串
///
/// 待序列号对象
///
public static string ToJson(this object obj)
{
return JsonConvert.SerializeObject(obj, Formatting.Indented);
}
准备好Post的数据后,我们就进一步看看获取数据并转换为合适格式的操作代码。
GroupJson group = null;
CreateGroupResult result = JsonHelper
if (result != null)
{
group = result.group;
}
其中POST数据并转换为合适格式实体类的操作,放在了ConvertJson方法里面,这个方法的定义如下所示,里面的HttpHelper是我公用类库的辅助类,主要就是调用底层的httpWebRequest对象方法,进行数据的提交,并获取返回结果。
///
/// 转换Json字符串到具体的对象
///
/// 返回Json数据的链接地址
/// POST提交的数据
///
public static T ConvertJson(string url,string postData)
{
HttpHelper helper = new HttpHelper();
string content = helper.GetHtml(url, postData,true);
VerifyErrorCode(content);
T result = JsonConvert.DeserializeObject
return result;
}
这样,完整的创建用户分组的操作函数如下所示。
///
/// 创建分组
///
/// 调用接口凭证
/// 分组名称
///
public GroupJson CreateGroup(string accessToken,string name)
{
string url = string.Format("https://api.weixin.qq.com/cgi-bin/groups/create?access_token={0}", accessToken);
var data = new
{
group = new
{
name = name
}
};
string postData = data.ToJson();
GroupJson group = null;
CreateGroupResult result = JsonHelper
if (result != null)
{
group = result.group;
}
return group;
}
2.2 查询所有分组
查询所有分组,可以把服务器上的分组全部获取下来,也就是每个分组的ID和名称。
///
/// 查询所有分组
///
/// 调用接口凭证
///
public List
{
string url = string.Format("https://api.weixin.qq.com/cgi-bin/groups/get?access_token={0}", accessToken);
List
GroupListJsonResult result = JsonHelper
if (result != null && result.groups != null)
{
list.AddRange(result.groups);
}
return list;
}
2.3 查询用户所在分组
每个用户都属于一个分组,默认在 未分组 这个分组里面,我们可以通过API获取用户的分组信息,也就是获取所在用户分组的ID。
///
/// 查询用户所在分组
///
/// 调用接口凭证
/// 用户的OpenID
///
public int GetUserGroupId(string accessToken,string openid)
{
string url = string.Format("https://api.weixin.qq.com/cgi-bin/groups/getid?access_token={0}", accessToken);
var data = new
{
openid = openid
};
string postData = data.ToJson();
int groupId = -1;
GroupIdJsonResult result = JsonHelper
if (result != null)
{
groupId = result.groupid;
}
return groupId;
}
2.4 修改分组名称
也可以在实际中,调整用户所在的分组,操作代码如下。
///
/// 修改分组名
///
/// 调用接口凭证
/// 分组id,由微信分配
/// 分组名字(30个字符以内)
///
public CommonResult UpdateGroupName(string accessToken,int id, string name)
{
string url = string.Format("https://api.weixin.qq.com/cgi-bin/groups/update?access_token={0}", accessToken);
var data = new
{
group = new
{
id = id,
name = name
}
};
string postData = data.ToJson();
return Helper.GetExecuteResult(url, postData);
}
这里的返回值CommonResult是,一个实体类,包含了bool的成功与否的标志,以及String类型的错误信息(如果有的话)。
对于这个GetExecuteResult函数体,里面主要就是提交数据,然后获取结果,并根据结果进行处理的函数。
///
/// 通用的操作结果
///
/// 网页地址
/// 提交的数据内容
///
public static CommonResult GetExecuteResult(string url,string postData = null)
{
CommonResult success = new CommonResult();
try
{
ErrorJsonResult result;
if (postData != null)
{
result = JsonHelper
}
else
{
result = JsonHelper
}
if (result != null)
{
success.Success = (result.errcode == ReturnCode.请求成功);
success.ErrorMessage = result.errmsg;
}
}
catch (WeixinException ex)
{
success.ErrorMessage = ex.Message;
}
return success;
}
}
上面红色部分的意思,就是转换为实体类的时候,如果错误是微信里面定义的,那么记录错误信息,其他异常我不处理(也就是抛出去)。
2.5 移动用户到新的分组
移动用户到新的分组的操作和上面小节的差不多,具体看代码。
///
/// 移动用户分组
///
/// 调用接口凭证
/// 用户的OpenID
/// 分组id
///
public CommonResult MoveUserToGroup(string accessToken,string openid, int to_groupid)
{
string url = string.Format("https://api.weixin.qq.com/cgi-bin/groups/members/update?access_token={0}", accessToken);
var data = new
{
openid = openid,
to_groupid = to_groupid
};
string postData = data.ToJson();
return Helper.GetExecuteResult(url, postData);
}
上面小节,定义并实现了用户分组的各类接口,所有的用户相关的都已经毫无保留贴出代码,它的调用操作如下代码所示(测试代码)。
private void btnGetGroupList_Click(object sender, EventArgs e)
{
IUserApi userBLL = new UserApi();
List
foreach (GroupJson info in list)
{
string tips = string.Format("{0}:{1}", info.name, info.id);
Console.WriteLine(tips);
}
}
private void btnFindUserGroup_Click(object sender, EventArgs e)
{
IUserApi userBLL = new UserApi();
int groupId = userBLL.GetUserGroupId(token, openId);
string tips = string.Format("GroupId:{0}", groupId);
Console.WriteLine(tips);
}
private void btnCreateGroup_Click(object sender, EventArgs e)
{
IUserApi userBLL = new UserApi();
GroupJson info = userBLL.CreateGroup(token, "创建测试分组");
if (info != null)
{
string tips = string.Format("GroupId:{0} GroupName:{1}", info.id, info.name);
Console.WriteLine(tips);
string newName = "创建测试修改";
CommonResult result = userBLL.UpdateGroupName(token, info.id, newName);
Console.WriteLine("修改分组名称:" + (result.Success ?"成功" : "失败:" + result.ErrorMessage));
}
}
private void btnUpdateGroup_Click(object sender, EventArgs e)
{
int groupId = 111;
string newName = "创建测试修改";
IUserApi userBLL = new UserApi();
CommonResult result = userBLL.UpdateGroupName(token, groupId, newName);
Console.WriteLine("修改分组名称:" + (result.Success ?"成功" : "失败:" + result.ErrorMessage));
}
private void btnMoveToGroup_Click(object sender, EventArgs e)
{
int togroup_id = 111;//输入分组ID
if (togroup_id > 0)
{
IUserApi userBLL = new UserApi();
CommonResult result = userBLL.MoveUserToGroup(token, openId, togroup_id);
Console.WriteLine("移动用户分组名称:" + (result.Success ?"成功" : "失败:" + result.ErrorMessage));
}
}
了解了上面的代码和调用规则,我们就能通过API进行用户分组信息的管理了。通过在应用程序中集成相关的接口代码,我们就能够很好的控制我们的关注用户列表和用户分组信息。从而为我们下一步用户的信息推送打好基础。
前面几篇继续了我自己对于C#开发微信门户及应用的技术探索和相关的经验总结,继续探索微信API并分享相关的技术,一方面是为了和大家对这方面进行互动沟通,另一方面也是专心做好微信应用的底层技术开发,把基础模块夯实,在未来的应用中派上用途。本随笔继续介绍微信门户菜单的管理操作。
微信门户的菜单,一般服务号和订阅号都可以拥有这个模块的开发,但是订阅号好像需要认证后才能拥有,而服务号则不需要认证就可以拥有了。这个菜单可以有编辑模式和开发模式,编辑模式主要就是在微信门户的平台上,对菜单进行编辑;而开发模式,就是用户可以通过调用微信的API对菜单进行 定制开发,通过POST数据到微信服务器,从而生成对应的菜单内容。本文主要介绍基于开发模式的菜单管理操作。
自定义菜单能够帮助公众号丰富界面,让用户更好更快地理解公众号的功能。目前自定义菜单最多包括3个一级菜单,每个一级菜单最多包含5个二级菜单。一级菜单最多4个汉字,二级菜单最多7个汉字,多出来的部分将会以“...”代替。目前自定义菜单接口可实现两种类型按钮,如下:
click:
用户点击click类型按钮后,微信服务器会通过消息接口推送消息类型为event 的结构给开发者(参考消息接口指南),并且带上按钮中开发者填写的key值,开发者可以通过自定义的key值与用户进行交互;
view:
用户点击view类型按钮后,微信客户端将会打开开发者在按钮中填写的url值 (即网页链接),达到打开网页的目的,建议与网页授权获取用户基本信息接口结合,获得用户的登入个人信息。
菜单提交的数据,本身是一个Json的数据字符串,它的官方例子数据如下所示。
{
"button":[
{
"type":"click",
"name":"今日歌曲",
"key":"V1001_TODAY_MUSIC"
},
{
"type":"click",
"name":"歌手简介",
"key":"V1001_TODAY_SINGER"
},
{
"name":"菜单",
"sub_button":[
{
"type":"view",
"name":"搜索",
"url":"http://www.soso.com/"
},
{
"type":"view",
"name":"视频",
"url":"http://v.qq.com/"
},
{
"type":"click",
"name":"赞一下我们",
"key":"V1001_GOOD"
}]
}]
}
从上面我们可以看到,菜单不同的type类型,有不同的字段内容,如type为view的有url属性,而type为click的,则有key属性。而菜单可以有子菜单sub_button属性,总得来说,为了构造好对应的菜单实体类信息,不是一下就能分析的出来。
我看过一些微信接口的开发代码,把菜单的分为了好多个实体类,指定了继承关系,然后分别对他们进行属性的配置,大概的关系如下所示。
这种多层关系的继承方式能解决问题,不过我觉得并不是优雅的解决方案。其实结合Json.NET自身的Attribute属性配置,可以指定那些为空的内容在序列号为Json字符串的时候,不显示出来的。
[JsonProperty( NullValueHandling = NullValueHandling.Ignore)]
有了这个属性,我们就可以统一定义菜单的实体类信息更多的属性了,可以把View类型和Click类型的菜单属性的url和key合并在一起。
///
/// 菜单基本信息
///
public class MenuInfo
{
///
/// 按钮描述,既按钮名字,不超过16个字节,子菜单不超过40个字节
///
public string name {get; set; }
///
/// 按钮类型(click或view)
///
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string type {get; set; }
///
/// 按钮KEY值,用于消息接口(event类型)推送,不超过128字节
///
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string key {get; set; }
///
/// 网页链接,用户点击按钮可打开链接,不超过256字节
///
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string url {get; set; }
///
/// 子按钮数组,按钮个数应为2~5个
///
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public List
.......
但是,这么多信息,不同的类型我需要指定不同的属性类型,那不是挺麻烦,万一我在View类型的菜单里面,把key属性设置了,那怎么办?
解决方法就是我们定义几个构造函数,分别用来构造不同的菜单信息,如下所示是对菜单不同的类型,赋值给不同的属性的构造函数。
///
/// 参数化构造函数
///
/// 按钮名称
/// 菜单按钮类型
/// 按钮的键值(Click),或者连接URL(View)
public MenuInfo(string name, ButtonType buttonType,string value)
{
this.name = name;
this.type = buttonType.ToString();
if (buttonType == ButtonType.click)
{
this.key = value;
}
else if(buttonType == ButtonType.view)
{
this.url = value;
}
}
好了,还有另外一个问题,子菜单也就是属性sub_button是可有可无的东西,有的话,需要指定Name属性,并添加它的sub_button集合对象就可以了,那么我们在增加一个构造子菜单的对象信息的构造函数。
///
/// 参数化构造函数,用于构造子菜单
///
/// 按钮名称
/// 子菜单集合
public MenuInfo(string name, IEnumerable
{
this.name = name;
this.sub_button = new List
this.sub_button.AddRange(sub_button);
}
由于只指定Name和sub_button的属性内容,其他内容为null的话,自然构造出来的Json就没有包含它们,非常完美!
为了获取菜单的信息,我们还需要定义两个实体对象,如下所示。
///
/// 菜单的Json字符串对象
///
public class MenuJson
{
public List
public MenuJson()
{
button = new List
}
}
///
/// 菜单列表的Json对象
///
public class MenuListJson
{
public MenuJson menu { get; set; }
}
我们从微信的定义里面,可以看到,我们通过API可以获取菜单信息、创建菜单、删除菜单,那么我们来定义它们的接口如下。
///
/// 菜单的相关操作
///
public interface IMenuApi
{
///
/// 获取菜单数据
///
/// 调用接口凭证
///
MenuJson GetMenu(string accessToken);
///
/// 创建菜单
///
/// 调用接口凭证
/// 菜单对象
///
CommonResult CreateMenu(string accessToken, MenuJson menuJson);
///
/// 删除菜单
///
/// 调用接口凭证
///
CommonResult DeleteMenu(string accessToken);
}
具体的获取菜单信息的实现如下。
///
/// 获取菜单数据
///
/// 调用接口凭证
///
public MenuJson GetMenu(string accessToken)
{
MenuJson menu = null;
var url = string.Format("https://api.weixin.qq.com/cgi-bin/menu/get?access_token={0}", accessToken);
MenuListJson list = JsonHelper
if (list != null)
{
menu = list.menu;
}
return menu;
}
这里就是把返回的Json数据,统一转换为我们需要的实体信息了,一步到位。
调用代码如下所示。
private void btnGetMenuJson_Click(object sender, EventArgs e)
{
IMenuApi menuBLL = new MenuApi();
MenuJson menu = menuBLL.GetMenu(token);
if (menu != null)
{
Console.WriteLine(menu.ToJson());
}
}
创建和删除菜单对象的操作实现如下所示。
///
/// 创建菜单
///
/// 调用接口凭证
/// 菜单对象
///
public CommonResult CreateMenu(string accessToken, MenuJson menuJson)
{
var url = string.Format("https://api.weixin.qq.com/cgi-bin/menu/create?access_token={0}", accessToken);
string postData = menuJson.ToJson();
return Helper.GetExecuteResult(url, postData);
}
///
/// 删除菜单
///
/// 调用接口凭证
///
public CommonResult DeleteMenu(string accessToken)
{
var url = string.Format("https://api.weixin.qq.com/cgi-bin/menu/delete?access_token={0}", accessToken);
return Helper.GetExecuteResult(url);
}
看到这里,有些人可能会问,实体类你简化了,那么创建菜单是不是挺麻烦的,特别是构造对应的信息应该如何操作呢?前面不是介绍了不同的构造函数了吗,通过他们简单就搞定了,不用记下太多的实体类及它们的继承关系来处理菜单信息。
private void btnCreateMenu_Click(object sender, EventArgs e)
{
MenuInfo productInfo = new MenuInfo("软件产品",new MenuInfo[] {
new MenuInfo("病人资料管理系统", ButtonType.click,"patient"),
new MenuInfo("客户关系管理系统", ButtonType.click,"crm"),
new MenuInfo("酒店管理系统", ButtonType.click,"hotel"),
new MenuInfo("送水管理系统", ButtonType.click,"water")
});
MenuInfo frameworkInfo = new MenuInfo("框架产品",new MenuInfo[] {
new MenuInfo("Win开发框架", ButtonType.click,"win"),
new MenuInfo("WCF开发框架", ButtonType.click,"wcf"),
new MenuInfo("混合式框架", ButtonType.click,"mix"),
new MenuInfo("Web开发框架", ButtonType.click,"web"),
new MenuInfo("代码生成工具", ButtonType.click,"database2sharp")
});
MenuInfo relatedInfo = new MenuInfo("相关链接",new MenuInfo[] {
new MenuInfo("公司介绍", ButtonType.click,"Event_Company"),
new MenuInfo("官方网站", ButtonType.view,"http://www.iqidi.com"),
new MenuInfo("提点建议", ButtonType.click,"Event_Suggestion"),
new MenuInfo("联系客服", ButtonType.click,"Event_Contact"),
new MenuInfo("发邮件", ButtonType.view,"http://mail.qq.com/cgi-bin/qm_share?t=qm_mailme&email=S31yfX15fn8LOjplKCQm")
});
MenuJson menuJson = new MenuJson();
menuJson.button.AddRange(new MenuInfo[] { productInfo, frameworkInfo, relatedInfo });
//Console.WriteLine(menuJson.ToJson());
if (MessageUtil.ShowYesNoAndWarning("您确认要创建菜单吗") == System.Windows.Forms.DialogResult.Yes)
{
IMenuApi menuBLL = new MenuApi();
CommonResult result = menuBLL.CreateMenu(token, menuJson);
Console.WriteLine("创建菜单:" + (result.Success ?"成功" : "失败:" + result.ErrorMessage));
}
}
这个就是我微信门户里面的菜单操作了,具体效果可以关注我的微信门户:广州爱奇迪,也可以扫描下面二维码进行关注了解。
菜单的效果如下:
最近一直在弄微信的集成功能开发,发现微信给认证账户开通了一个多客服的功能,对于客户的咨询,可以切换至客服处理的方式,而且可以添加多个客服进行处理,这个在客户咨询比较多的时候,是一个不错的营销功能。微信多客服的功能,能够在很大程度上利用客服员工资源,及时迅速对客户咨询信息进行处理,为企业带来更多的机会和市场。
默认这个多客服的功能,需要在微信公众平台中的服务中心进行主动开通,默认是不开通的,为了体验这个功能,我这里把多客服功能进行开通。
微信的多客服功能,对于客服的响应操作,既可以在电脑的客户端上进行操作,也可以在微信多客服助手进行信息处理,两者都能对客户的信息进行回应、结束会话等操作。
开通微信多客服功能后,就需要添加一些处理客户信息的客服工号了。
多客服账号采用“工号@微信号”的形式进行登录,请您在登录窗口依照下图形式输入帐号信息。
在电脑客户端上使用
在手机客户端上进行多客服的使用,就是关注一个账号,信息通过转发到这里进行处理。关注公众号”多客服助手“就搞定了。
通过上面两种途径,能够很好处理客户的相关信息,其实也就是类似电话坐席的方式,让不同的客服员工,对来访的客户进行处理。
在微信的多客服开发介绍中,内容介绍的比较少,如下所示。
在新的微信协议中,开发模式也可以接入客服系统。 开发者如果需要使用客服系统,需要在接收到用户发送的消息时,返回一个MsgType为transfer_customer_service的消息,微信 服务器在收到这条消息时,会把用户这次发送的和以后一段时间内发送的消息转发客服系统。返回的消息举例如下。
<xml>
<ToUserName>touser]]>ToUserName>
<FromUserName>fromuser]]>FromUserName>
<CreateTime>1399197672CreateTime>
<MsgType>transfer_customer_service]]>MsgType>
xml>
而在开发的时候,我们一般把它封装为一个实体类信息,如下所示。主要就是指定消息类型,和翻转传入传出对象就可以了。
///
/// 客服消息
///
[System.Xml.Serialization.XmlRoot(ElementName = "xml")]
public class ResponseCustomer : BaseMessage
{
public ResponseCustomer()
{
this.MsgType = ResponseMsgType.transfer_customer_service.ToString().ToLower();
}
public ResponseCustomer(BaseMessage info) :this()
{
this.FromUserName = info.ToUserName;
this.ToUserName = info.FromUserName;
}
}
然后调用处理的时候,代码如下所示。
ResponseCustomer customInfo = new ResponseCustomer(info);
xml = customInfo.ToXml();
如我在客户应答处理里面,客户回应0,我就切换进入客服模式,这样客户后续所有的输入内容,均不会触发微信门户里面的解析,而转发到客服模式,让客服的工号可以和客户进行交谈了。
//处理 0 指令, 人工客服
if (string.IsNullOrEmpty(xml) && eventKey.Trim() =="0")
{
xml = base.DealEvent(eventInfo,"event_customservice");
}
而在DealEvent里面,根据这个条件进行处理就可以了。
//人工客服
if (eventKey == "event_customservice")
{
ResponseCustomer customInfo = new ResponseCustomer(info);
xml = customInfo.ToXml();
}
通过使用多客服的客户端,这样处理消息交互起来非常方便,能获得客户的对话信息了,在电脑客户端上,看到的界面如下所示。
手机上的谈话截图如下所示。
这样就能够通过多途径,及时响应客户的信息了。
如果感兴趣或者体验相关的客服应答功能,可以关注我的微信了解下。具体效果可以关注我的微信门户:广州爱奇迪,也可以扫描下面二维码进行关注了解。
最近对微信接口进行深入的研究,通过把底层接口一步步进行封装后,逐步升级到自动化配置、自动化应答,以及后台处理界面的优化和完善上,力求搭建一个较为完善、适用的微信门户应用管理系统。
微信门户应用管理系统,采用基于MVC+EasyUI的路线,由于多数域名服务器上都只能支持.NET4.0,所以以MVC3,C#4.0作为开发基础,基本上能够部署在任何.NET服务器上。
在微信门户系统里面,实现下面这些功能操作:
1)实现菜单的动态配置及更新到服务器上;
2)动态定义事件和响应消息,实现对不同行业,不同需求的菜单动作响应;
3)动态的应答指令配置处理,实现整套应答链的消息处理;
4)获取订阅用户和用户分组信息,并可以实现用户分组信息的维护等操作;
5)管理并更新多媒体文件、图文消息等内容,方便为客户推送消息做准备。
6)使用向选定订阅用户或者分组进行消息的群发功能。
在系统中管理菜单,并通过把菜单提交到服务器上,实现菜单的动态配置和生成,能够为我们系统适应各种的需要,实现灵活的处理。
微信菜单的添加界面如下所示。
微信菜单的修改界面如下所示
微信菜单定义是存储在数据库里面,如果需要提交到微信服务器上并生效,则需要调用微信API接口进行处理,我在页面的Controller控制器里增加一个提交到服务器的处理方法。
在微信服务账号的门户上,菜单的表现效果如下所示。
对于动态生成的菜单,大多数情况下是用作Click的方式,也就是需要定义每个菜单的事件响应操作,我们使用微信的话,可以了解到,微信的处理事件,一般可以响应用户文本消息、图片消息、图文消息等内容,常规下,一般使用文本消息或者图文消息居多。
为了进一步实现响应内容的重用,我们把菜单的事件定义和内容定义进行分开管理,事件定义可以使用多个文本消息,也可以使用多个图文消息进行组合,这样可以实现更加灵活的使用环境。
添加事件定义如下所示
事件的响应内容编码,可以选择输入或者从“编辑”按钮中选择,当选择“编辑”按钮进行选择的时候,系统弹出一个对话框供用户对事件的响应内容编码选择。
完成选择后,回到原来的新增界面,将会看到返回的记录就是我们选择的记录。
微信事件的编辑界面如下所示,类似新增界面的内容。
上面说到,菜单的事件通过关联事件编码进行处理,而事件本身可以组合多个消息内容,因此消息内容是响应客户操作的最小单元,它们可以是一条文本消息、图文消息,也可以是多条消息的组合(同类型的话)。
为了方便管理,我把消息分为了图文、指令、文本类型,如果需要,还可以根据需要把它细化为其他类型的消息。
消息内容的添加界面如下所示。
文本消息的手机上界面效果如下所示。
这里不管是文本消息还是图文消息,我们统一以图文消息的定义来定义消息,如果是文本消息,我们只需要获取描述内容作为消息的主体即可。
图文消息的编辑界面如下所示,主要就是填写完整的内容和图片,以及页面详细的链接即可。
上面的这个客户关系管理系统的消息,在手机上显示的界面效果如下所示,单击链接,可以切换到消息跳转链接地址的。
应答指令的维护,有点类似于事件的管理,主要就是定义一些用到的指令,方便构建应答系统的响应链,从而实现一步步的操作指令。
在后台设置好应答指令后,系统就能根据应答指令链进行处理了。首先我们需要提供一个进入应答链的提示界面,如下所示。
但我们在菜单选择应答系统后,系统返回一个文本提示界面,如下所示。
这个界面里面提示了一些按键,包括几个固定的按键和一些业务按键,输入简单的1~6可以对选择进行响应。
我们看到上面的界面,输入指令1后,系统进入下一层的应答指令,然后又列出几个可供输入的按键和内容提示。
当我们继续输入业务按键1后,响应的是一个图文消息,也是关于按键的详细说明。
这个时候,我们也还可以输入*号按键,返回上一级菜单的。
输入0则转入了客服对话模式,后续您发的任何消息,将会转发到多客服系统里面了。
当用户发送消息后,客服助手就能及时收到消息并处理和客户的应答了。
为了更有效管理订阅用户以及分组信息,我们可以从微信服务器上获取相关的信息,供我们了解关注的用户信息,也可以为后续的群发消息做准备。
订阅用户的管理如下所示,默认可以通过用户的地区进行查看,地区根据:国家-省份-城市这样的级别进行展开。单击同步数据,可以把服务器上的用户数据下载到本地进行更新或者写入。
订阅用户,还可以根据分组进行查看
双击可以查看订阅用户信息,查看订阅用户的详细信息界面如下所示。
创建分组的界面如下所示。
编辑分组信息界面如下所示。
当对分组进行编辑保存后,系统会记住那些修改过的,同步的时候,把本地新增的内容,在服务器上创建分组;把修改的的分组名称,在服务器上进行修改,然后进行同步列表处理。
多媒体管理是指把本地文件上传到微信服务器上进行保存,方便信息的发送等操作。微信要求,某些信息,必须是先上传到服务器上,然后才能使用它的媒体ID进行发送的。
文件成功上传到服务器后,在列表里面的“文件上传标识,就是一串BASE64的编码数据,同时有一个上传的时间戳(因为微信服务器只保留了3天的媒体数据,超过期限的数据会被自动删除。
同时,在列表的上面,有两个重要的功能:上传选定的记录,重新上传过期的记录。方便我们对自己多媒体文件的重新更新操作。
添加界面操作如下所示,其中引入了附件上传的控件进行文件的操作,非常方便。同时上传成功的文件,会在列表中列出。
多媒体文件可以是下面几种方式:图片、语音、视频、缩略图。
保存后的数据记录,文件上传标识和时间戳都是空的,我们如果要使用,必须把他们上传到微信的服务器上,然后根据它的MediaId进行信息的发送,上传选定的记录操作界面如下所示。
多媒体文件顺利上传后,记录的信息如下所示。
图文消息分为单图文消息和多图文消息两种,单图文消息如下所示。
多图文消息如下所示:
和多媒体数据管理一样,图文消息也是通过同样的方式进行管理,先上传到服务器,然后在进行消息的发送操作,多媒体消息一样有时间方面的限制要求,具体在我们的微信门户平台里面管理界面如下所示。
添加图文消息界面如下所示,保存后,可以在编辑界面中的“其他图文列表”里面,继续添加多图文的消息内容。
在添加界面中,选择图文消息的缩略图,都是通过选定指定的,已经上传到服务器上图片或者缩略图资源才可以的。
添加后的多图文列表,可以进行查看管理。
保存记录后,然后继续上传,上传后的记录界面如下所示,成功后返回一个上传后的服务器标识和时间戳,否则提示错误。
为了方便记录客户的输入和发送信息,我们在微信门户管理平台里面记录用户的输入数据,具体会话消息管理界面如下所示。
我们可以双击最近48小时内的任何一条记录,可以给关注的客户进行消息的发送操作,如果消息发送成功,用户在手机的微信账号里面就能收到相关的发送消息了。
为了对客户进行相应的营销操作,有时候我们需要对指定的群主或者人员进行消息的群发,让客户经常性的了解我们产品的信息和活动。
由于群发消息,除了文本消息,可以直接编辑发送外,其他数据,必须要求是上传到服务器的多媒体文件或者图文消息内容,因此前面的多媒体管理和图文消息管理,就是主要为了群发消息的目的引入的。有了上面的多媒体和多图文信息,我们从平台里面选择记录即可进行发送,从而省却麻烦的连带工作,实现高效的信息群发操作。
群发的消息,可以按群发分组进行查看,也可以按照消息类型进行查看,使得我们管理起来根据方便。
添加图文消息,可以选择文本消息、图文消息、图片消息等内容,根据不同的内容,界面提供不同的选择操作。
消息的群发类型分为两种,一种是根据分组,那么从平台里面选择对应的分组即可;一种是根据用户的OpenID进行发送,提供给用户输入。主要的操作界面如下所示。
微信公众号(包括服务号和订阅号)都可以对菜单进行自定义设置,我们为了方便管理,一般先把菜单数据在本地管理维护,需要更新的时候,把它们更新到微信服务器上就可以了。本文基于这个方式,介绍我的微信门户平台管理系统中菜单提交到微信服务器上的操作。微信门户应用管理系统,采用基于 MVC+EasyUI的路线,由于多数域名服务器上都只能支持.NET4.0,所以以MVC3,C#4.0作为开发基础,基本上能够部署在任何.NET服 务器上。
微信公众号的菜单我们可以通过网站进行本地的管理,维护好它们之间的层级关系,由于微信对自定义的菜单要求比较严格,以下是微信对自定义菜单的要求:
目前自定义菜单最多包括3个一级菜单,每个一级菜单最多包含5个二级菜单。一级菜单最多4个汉字,二级菜单最多7个汉字,多出来的部分将会以“...”代替。
因此我们自己根据约定,不要越界即可,否则提交菜单到服务器,可能会返回一些错误,这些细节,我们在创建本地菜单管理的时候,注意一下就可以了。我在早期的一篇文章也介绍了自定义菜单的一些内容,需要可以进行回顾一下《C#开发微信门户及应用(6)--微信门户菜单的管理操作》,本篇主要是介绍在我的平台管理系统里面,调用前面介绍的菜单接口API,实现菜单提交到服务器的操作。
根据微信的自定义菜单要求,我在管理系统里面,对微信的菜单几个基础性的界面设计如下。
主菜单管理界面如下所示。
添加菜单的界面设计如下所示
微信菜单的修改界面如下所示
微信菜单定义是存储在数据库里面,如果需要提交到微信服务器上并生效,则需要调用微信API接口进行处理,我在页面的Controller控制器里增加一个提交到服务器的处理方法。
上面几个界面,主要就是根据微信菜单的属性,对菜单进行维护管理,我们最终的目的是把它们放到服务器上去,供我们处理客户的相关事件操作的。
提交菜单的操作,我们在MVC的View页面里面,使用JQuery的Ajax提交即可(前提是我们在控制器里面添加相应的处理,后面介绍),界面脚本代码如下所示。
//绑定提交按钮的的点击事件
function BindSubmitEvent() {
$("#btnSubmit").click(function () {
$.messager.confirm("提交菜单确认", "您确认需要提交菜单到微信服务器吗?", function (action) {
if (action) {
//提交数据
$.ajax({
url: '/Menu/UpdateWeixinMenu',
type: 'post',
dataType: 'json',
success: function (data) {
if (data.Success) {
$.messager.alert("提示", "提交微信菜单成功");
}
else {
$.messager.alert("提示", "提交微信菜单失败:" + data.ErrorMessage);
}
},
data: ''
});
}
});
});
}
上面红色的代码,就是我们在MVC的控制器里面定义的方法,我们只需要通过POST方法,对控制器方法调用,就能实现菜单提交到微信服务器上,至于具体里面的细节,我们可以把它挪到控制器或者更底层进行处理就是了,页面不需要涉及太多的逻辑就是了。
上面那个Menu控制器的UpdateWeixinMenu的方法代码如下所示(主要就是根据我前面介绍过的开发模型进行处理就是了)。
///
///更新微信菜单
///
///
public ActionResult UpdateWeixinMenu()
{
string token = base.GetAccessToken();
MenuListJson menuJson = GetWeixinMenu();
IMenuApi menuApi = new MenuApi();
CommonResult result = menuApi.CreateMenu(token, menuJson);
return ToJsonContent(result);
}
上面的几个方法这里逐一介绍一下。GetAccessToken主要就是获得当前操作的访问令牌,这里的操作可以用缓存进行缓存,否则频繁的获取AccessToken,达到每天指定的次数后,当天就不能再用了。
GetWeixinMenu方法,主要就是为了方便,对获取构造微信的自定义菜单数据进行了一个函数封装,具体代码如下所示。
///
/// 生成微信菜单的Json数据
///
///
private MenuListJson GetWeixinMenu()
{
MenuListJson menuJson = new MenuListJson();
List
foreach (MenuNodeInfo infoin menuList)
{
ButtonType type = (info.Type == "click") ? ButtonType.click : ButtonType.view;
string value = (type == ButtonType.click) ? info.Key : info.Url;
MenuJson weiInfo = new MenuJson(info.Name, type, value);
AddSubMenuButton(weiInfo, info.Children);
menuJson.button.Add(weiInfo);
}
return menuJson;
}
private void AddSubMenuButton(MenuJson menu, List
{
if (menuList.Count > 0)
{
menu.sub_button = new List
}
foreach (MenuNodeInfo infoin menuList)
{
ButtonType type = (info.Type == "click") ? ButtonType.click : ButtonType.view;
string value = (type == ButtonType.click) ? info.Key : info.Url;
MenuJson weiInfo = new MenuJson(info.Name, type, value);
menu.sub_button.Add(weiInfo);
AddSubMenuButton(weiInfo, info.Children);
}
}
上面的代码,就是把本地存储的MenuNodeInfo数据,通过递归遍历的方 式,转换为微信的自定义菜单实体MenuJson,这样我们调用API就非常方便了,这个函数主要负责构造对应的实体信息就是了。至于调用微信API提交 菜单的事情,还是让API自己亲自处理为好,他们的代码如下所示(也就是上面函数的部分代码)。
IMenuApi menuApi = new MenuApi();
CommonResult result = menuApi.CreateMenu(token, menuJson);
return ToJsonContent(result);
最终的结果是返回一个通用的结果CommonResult,这个结果对象,非常方便脚本的处理,如果有错误,则提示错误,否则也方便判断布尔值,也就是上面的页面代码脚本。
success: function (data) {
if (data.Success) {
$.messager.alert("提示", "提交微信菜单成功");
}
else {
$.messager.alert("提示", "提交微信菜单失败:" + data.ErrorMessage);
}
},
通过以上几部分的代码,我们就可以实现前台MVC的视图界面,调用后台封装好的微信API,实现菜单的提交处理了。
如果感兴趣或者体验相关的客服应答功能,可以关注我的微信了解下。具体效果可以关注我的微信门户:广州爱奇迪,也可以扫描下面二维码进行关注了解。
在前面几篇文章中,逐步从原有微信的API封装的基础上过渡到微信应用平台管理系统里面,逐步介绍管理系统中的微信数据的界面设计,以及相关的处理 操作过程的逻辑和代码,希望从更高一个层次,向大家介绍微信的应用开发过程。本篇主要介绍在管理系统中,如何实现微信用户分组信息的同步操作。
其实微信能够风风火火的原因,主要就是因为有用户信息,所以同步并管理好微信账号的关注用户数据是非常重要的。有了微信用户的数据,你可以和你任何应用系统对接,实现系统-手机客户端的数据整合,还可以对用户进行营销管理,如发送用户感兴趣的产品消息、服务消息等,能够很好扩大企业的影 响力和市场行为。
在较早之前的一篇随笔《C#开发微信门户及应用(5)--用户分组信息管理》,我曾经介绍了微信分组的各种底层的API封装操作,里面主要就是对微信提供API的.NET高级分组,对所有的信息交换,通过实体性进行数据交换,使得我 们调用API来处理微信的各种事务更加方便,从而为微信应用平台的管理奠定基础。其中这篇文章介绍了所有微信分组管理的API封装过程,用户分组管理,包 含下面几个方面的内容:
1)创建分组
2) 查询所有分组
3) 查询用户所在分组
4) 修改分组名
5) 移动用户分组
针对以上微信分组的操作,我们可以在微信的应用管理系统里面,设计一个模块,用来管理微信的分组数据,在这个模块里面,可以创建分组,修改分组,查看分组等基础操作,还可以实现同步微信分组的操作,同步操作,主要就是把新增的分组信息添加到微信里面,修改的分组也在微信中实现修改功能,删除目前微信不支持,所以不用管了。最后,我们可以在此从微信服务器上,把修改后的数据同步下来,同步的时候为了避免对我们提交不成功的数据,我们需要对修改过的记录做好标识,这个就是我对整个同步操作的逻辑处理了。
在管理系统里面,对微信分组的列表管理界面设计如下所示。
创建分组的时候,我们只需要添加一个分组名称就可以了,界面设计也简单,但是我们把创建的ID统一设计为-1,作为未同步的新增标识。
编辑分组信息界面如下所示。当对分组进行编辑保存后,系统会记住那些修改过的分组就是了。
为了更好实现分组同步的管理,我把分组的操作代码,封装在一个MVC的控制器的方法里面,页面代码通过Ajax调用就可以实现同步操作了,同步成功,或者失败,都会提示用户,让我们对其结果进行了解。
同步的时候,把本地新增的内容,在服务器上创建分组;把修改的的分组名称,在服务器上进行修改,然后进行同步列表处理,同步操作前,列表界面可能如下所示,有新增记录ID=-1的,也有修改后,记录修改标志的。
用户分组的同步按钮操作,是调用一个脚本代码就可以了,具体代码如下所示。
//绑定提交按钮的的点击事件
function BindSyncDataEvent() {
$("#btnSyncData").click(function () {
$.messager.confirm("提交确认", "您确认需要和微信服务器同步分组信息吗?", function (action) {
if (action) {
//提交数据
$("#loading").show();
$.ajax({
url: '/Group/SyncGroup',
type: 'post',
dataType: 'json',
success: function (data) {
if (data.Success) {
$("#grid").datagrid("reload");
$.messager.alert("提示", "同步成功");
}
else {
$.messager.alert("提示", "同步失败:" + data.ErrorMessage);
}
},
data: ''
});
$("#loading").fadeOut(500);
}
});
});
}
其中上面红色部分就是通过Jquery调用的MVC的控制器方法,具体函数代码如下所示。
///
/// 同步服务器的分组信息
///
///
public ActionResult SyncGroup()
{
string accessToken = GetAccessToken();
CommonResult result = BLLFactory
return ToJsonContent(result);
}
从上面,我们没有看到太多的逻辑,为了方便我对他们进行了进一步的封装,把它放到了业务逻辑层进行处理了。具体我们看看它的代码逻辑吧,这里为了所有的数据库操作更加快捷和完整,使用了事务的操作,我把相关的代码贴出来,方便大家了解逻辑。
///
/// 同步服务器的分组信息
///
///
public CommonResult SyncGroup(string accessToken)
{
CommonResult result = new CommonResult();
try
{
IUserApi api = new UserApi();
using (DbTransaction trans = baseDal.CreateTransaction())
{
//先把本地标志groupId = -1未上传的记录上传到服务器,然后进行本地更新
string condition =string.Format("GroupID = '-1' ");
List
foreach (GroupInfo infoin unSubmitList)
{
GroupJson groupJson = api.CreateGroup(accessToken, info.Name);
if (groupJson !=null)
{
info.GroupID = groupJson.id;
baseDal.Update(info, info.ID, trans);
}
}
//把标志为修改状态的记录,在服务器上修改
condition = string.Format("GroupID >=0 and Modified =1 ");
List
foreach (GroupInfo infoin unModifyList)
{
CommonResult modifyed = api.UpdateGroupName(accessToken, info.GroupID, info.Name);
if (modifyed !=null && modifyed.Success)
{
info.Modified = 0;//重置标志
baseDal.Update(info, info.ID, trans);
}
}
//删除具有删除标志的分组
//condition = string.Format("GroupID >=100 and Deleted=1 ");
//List
//foreach (GroupInfo info in unDeletedList)
//{
// CommonResult deleted = api.DeleteGroup(accessToken, info.GroupID, info.Name);
// if (deleted != null && deleted.Success)
// {
// baseDal.Delete(info.ID, trans);
// }
//}
List
foreach (GroupJson infoin list)
{
UpdateGroup(info, trans);
}
try
{
trans.Commit();
result.Success = true;
}
catch
{
trans.Rollback();
throw;
}
}
}
catch (Exception ex)
{
result.ErrorMessage = ex.Message;
}
return result;
}
在Jquery同步的时候,我们为了避免等待时间过久而无法判断程序是否正常在工作,最好增加一个忙碌的提示操作,因为我们使用了Ajax调用,所以我们可以统一设置Ajax的忙碌和完成状态,具体设置代码如下所示。
//用来统一请求忙碌显示的设置
$.ajaxSetup({
beforeSend: function () {
$("#loading").show();
},
complete: function () {
$("#loading").hide();
}
});
如果感兴趣或者体验相关的微信功能,可以关注我的微信了解下。具体效果可以关注我的微信门户:广州爱奇迪,也可以扫描下面二维码进行关注了解。
在前面一系列文章中,我们可以看到微信自定义菜单的重要性,可以说微信公众号账号中,菜单是用户的第一印象,我们要规划好这些菜单的内容,布局等信息。根据微信菜单的定义,我们可以看到,一般菜单主要分为两种,一种是普通的Url菜单(类型为View的菜单),一种是事件菜单(类型为Click的菜 单),一般情况下,微信的Url菜单,是无法获得用户的任何信息的,但微信用户信息非常重要,因此也提供了另外一种方式(类似重定向的方式)来给我们使 用,本篇主要介绍这种重新定向的方式菜单的使用,以使我们能够尽可能和用户进行交互。
微信对自定义菜单的要求:目前自定义菜单最多包括3个一级菜单,每个一级菜单最多包含5个二级菜单。一级菜单最多4个汉字,二级菜单最多7个汉字,多出来的部分将会以“...”代替。
根据菜单的分类,我们可以把它通过图形进行分类展示:
我对各种微信公众号进行了解,发现多数账号采用的都是普通的View类型的菜单链接方式,通过它们链接到自己的微网站上,但也有一些做的好的,如省 立中山图书馆,就能通过重定向的方式,提供一个绑定图书馆用户和微信OpenID的入口,绑定后,用户就可以查看借阅的书籍,然后可以通过一键续借功能实 现图书的快速续借功能。
对于这种重定向类型的Url菜单事件,微信的说明如下:
如果用户在微信中(Web微信除外)访问公众号的第三方网页,公众号开发者可以通过此接口获取当前用户基本信息(包括昵称、性别、城市、国家)。利用用户信息,可以实现体验优化、用户来源统计、帐号绑定、用户身份鉴权等功能。请注意,“获取用户基本信息接口是在用户和公众号产生消息交互时,才能根据用户OpenID获取用户基本信息,而网页授权的方式获取用户基本信息,则无需消 息交互,只是用户进入到公众号的网页,就可弹出请求用户授权的界面,用户授权后,就可获得其基本信息(此过程甚至不需要用户已经关注公众号。)”
上面说了,重定向类型的菜单分为了两种,其实他们也仅仅是参数Scope类型的不同,其他部分也还是一样的。
为了展示,我们在假设用户单击菜单的时候,切换到http://www.iqidi.com/testwx.ashx这个页面,并带过来当前用户的OpenID等参数信息
对于scope=snsapi_base方式的链接如下:
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx3d81fc2886d86526&redirect_uri=http%3A%2F%2Fwww.iqidi.com%2Ftestwx.ashx&response_type=code&scope=snsapi_base&state=123#wechat_redirect
而对于scope=snsapi_userinfo方式的链接如下:
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx3d81fc2886d86526&redirect_uri=http%3A%2F%2Fwww.iqidi.com%2Ftestwx.ashx&response_type=code&scope=snsapi_userinfo&state=123#wechat_redirect
不过他们给手机客户端的体验是不同的,第一种可以平滑切换,但是第二种会弹出一个对话框供用户确认才能继续。
为了演示上面两种获取数据的不同,我把他们传过来的code的值,用户换取OpenID后进行用户信息的解析,他们两者的结果都是一样了。具体测试界面如下所示。
其中TestWX.ashx的页面后台代码如下所示:
///
/// TestWX 的摘要说明
///
public class TestWX : IHttpHandler
{
string appId = ""; //换成你的信息
string appSecret = ""; //换成你的信息
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "text/plain";
string content = "";
if (context.Request != null && context.Request.Url != null)
{
NameValueCollection list = HttpUtility.ParseQueryString(context.Request.Url.Query);
foreach (string keyin list.AllKeys)
{
content += string.Format("{0}:{1} \r\n", key, list[key]);
}
}
string code = context.Request.QueryString["code"] ??"";
if (!string.IsNullOrEmpty(code))
{
IBasicApi api = new BasicApi();
try
{
AppConfig config = new AppConfig();
appId = config.AppConfigGet("AppId");//从配置中获取微信程序ID
appSecret = config.AppConfigGet("AppSecret");//从配置中获取微信程序秘钥
AccessTokenResult result = api.GetAccessToken(appId, appSecret, code);
if (result != null)
{
content += string.Format("openid:{0}\r\n", result.openid);
string token = api.GetAccessToken(appId, appSecret);
IUserApi userApi = new UserApi();
UserJson userDetail = userApi.GetUserDetail(token, result.openid);
if (userDetail !=null)
{
content += string.Format("nickname:{0} sex:{1}\r\n", userDetail.nickname, userDetail.sex);
content += string.Format("Location:{0} {1} {2} {3}\r\n", userDetail.country, userDetail.province, userDetail.city, userDetail.language);
content += string.Format("HeadUrl:{0} \r\n", userDetail.headimgurl);
content += string.Format("subscribe:{0},{1}\r\n", (userDetail.subscribe ==1) ? "已订阅" :"未订阅", userDetail.subscribe_time.GetDateTime());
}
}
}
catch { }
}
context.Response.Write(content);
}
在上面的代码中,我主要分为几步,一个是打印当前用户重定向过来的链接的参数信息,代码如下。
NameValueCollection list = HttpUtility.ParseQueryString(context.Request.Url.Query);
foreach (string keyin list.AllKeys)
{
content += string.Format("{0}:{1} \r\n", key, list[key]);
}
然后获取到Code参数后,通过API接口,获取AccessTokenResult的数据,这里面有用户的OpenID
AccessTokenResult result = api.GetAccessToken(appId, appSecret, code);
当正常调用后,我们把用户标识的OpenID进一步进行解析,调用API获取用户的详细信息,具体代码如下所示。
UserJson userDetail = userApi.GetUserDetail(token, result.openid);
当我们把用户的相关信息获取到了,就可以做各种用户信息的展示了,如下代码所示。
if (userDetail !=null)
{
content += string.Format("nickname:{0} sex:{1}\r\n", userDetail.nickname, userDetail.sex);
content += string.Format("Location:{0} {1} {2} {3}\r\n", userDetail.country, userDetail.province, userDetail.city, userDetail.language);
content += string.Format("HeadUrl:{0} \r\n", userDetail.headimgurl);
content += string.Format("subscribe:{0},{1}\r\n", (userDetail.subscribe ==1) ? "已订阅" :"未订阅", userDetail.subscribe_time.GetDateTime());
}
这种菜单就是需要指定域名,在微信后台中进行设置,重定向的链接必须属于这个域名之中,否则不会转到你希望的链接。
这个方式,让我们的微信应用程序后台可以获得用户的标识、用户详细信息等,我们就可以用来绑定和用户相关的业务信息了,如上面提到的图书馆借阅信息,送水客户的信息,客户的积分信息,或者可以和后台账号进行关联实现更加复杂的应用等。用户的身份信息如此重要,如果结合到我们的CRM系统、业务管理 系统,就可以发挥用户信息应用的作用了。
以上就是我对这个类型菜单链接的应用了解,具体还需要进一步深化其应用,希望和大家共同探讨这方面的应用场景。
我们知道,微信最开始就是做语音聊天而使得其更加流行的,因此语音的识别处理自然也就成为微信交流的一个重要途径,微信的开发接口,也提供了对语音的消息请求处理。本文主要介绍如何利用语音的识别,对C#开发的微信门户应用的整个事件链的处理操作,使得在我们的微信账号里面,更加方便和多元化对用户 的输入进行处理。
微信的API这么定义语音的识别的:开通语音识别功能,用户每次发送语音给公众号时,微信会在推送的语音消息XML数据包中,增加一个Recongnition字段。
语音的消息格式如下所示。
<xml>
<ToUserName>toUser]]>ToUserName>
<FromUserName>fromUser]]>FromUserName>
<CreateTime>1357290913CreateTime>
<MsgType>voice]]>MsgType>
<MediaId>media_id]]>MediaId>
<Format>Format]]>Format>
<MsgId>1234567890123456MsgId>
xml>
参数 |
描述 |
ToUserName |
开发者微信号 |
FromUserName |
发送方帐号(一个OpenID) |
CreateTime |
消息创建时间 (整型) |
MsgType |
语音为voice |
MediaId |
语音消息媒体id,可以调用多媒体文件下载接口拉取数据。 |
Format |
语音格式,如amr,speex等 |
MsgID |
消息id,64位整型 |
根据以上微信接口的定义,我们可以定义一个实体类来对消息的传递进行处理,如下所示。
///
/// 接收的语音消息
///
[System.Xml.Serialization.XmlRoot(ElementName = "xml")]
public class RequestVoice : BaseMessage
{
public RequestVoice()
{
this.MsgType = RequestMsgType.Voice.ToString().ToLower();
}
///
/// 语音格式,如amr,speex等
///
public string Format {get; set; }
///
/// 语音消息媒体id,可以调用多媒体文件下载接口拉取数据。
///
public string MediaId {get; set; }
///
/// 消息ID
///
public Int64 MsgId { get; set; }
///
/// 语音识别结果,UTF8编码
///
public string Recognition {get; set; }
}
我们看到,这里我们最感兴趣的是语音的识别结果,也就是Recognition的字段,这个就是微信服务器自动根据用户的语音转换过来的内容,我测试过,识别率还是非常高的。
这个实体类,在整个微信应用的消息传递中的关系如下所示:
明确了上面的语音对象实体,我们就可以看看它们之间是如何处理的。
微信消息的处理逻辑如下图所示。
其中我们来看看语音的处理操作,我的代码处理逻辑如下所示。
///
/// 对语音请求信息进行处理
///
/// 语音请求信息实体
///
public string HandleVoice(Entity.RequestVoice info)
{
string xml = "";
// 开通语音识别功能,用户每次发送语音给公众号时,
// 微信会在推送的语音消息XML数据包中,增加一个Recongnition字段。
if (!string.IsNullOrEmpty(info.Recognition))
{
TextDispatch dispatch = new TextDispatch();
xml = dispatch.HandleVoiceText(info, info.Recognition);
}
else
{
xml = "";
}
return xml;
}
在这里,我先看看,是否获得了微信的语音识别结果,如果获得,那么这个时候,就是和处理用户文本输入的操作差不多了,因此把它转给TextDispatch的处理类进行处理。
其中这里面的处理逻辑如下所示。
首先我根据识别结果,寻找是否用户读出了微信门户的菜单名称,如果根据语音结果找到对应的菜单记录,那么我们执行菜单事件(如果是URL的View 类型菜单,我们没办法重定向到指定的链接,因此给出一个链接文本提示,给用户单击进入;如果没有找到菜单记录,那么我们就把语音识别结果作为一般的事件进 行处理,如果事件逻辑没有处理,那么我们最后给出一个默认的语音应答提示结果就可以了。
具体的处理代码如下所示。
///
/// 如果用户用语音读出菜单的内容,那么我们应该先根据菜单对应的事件触发,最后再交给普通事件处理
///
///
///
public string HandleVoiceText(BaseMessage info,string voiceText)
{
string xml = "";
MenuInfo menuInfo = BLLFactory