欢迎各位兄弟 发布技术文章
这里的技术是共享的
1)首先需要邮箱注册:
2)邮箱激活。邮箱将会收到激活邮件,点击激活链接即可。
3)需要登记个人信息。这里需要提供一些个人信息或者单位信息。
现在个人只允许注册订阅号,公司单位才能注册服务号,服务号比订阅号,功能更多,可以实现自定义菜单。
需要提供的信息,主要有身份证号码、本人手持身份证的照片,如果是单位注册,还需要单位注册号、营业执照及法人身份证及照片,感觉这个比较变态。哪个老总愿意让你给他和他的身份证合张影呢?
注意:
同一个手机号或同一个身份证号只允许注册两个微信公众平台账号。
个人信息登记:
企业用户
企业用户上面的个人信息也要填写的,只是运营者的个人信息。
政府媒体
4)输入公众账号相关信息了。
在输入公众号的名称和描述时要注意,公众账号的名称是不能编辑的,一旦提交,再不能改,一定要慎重。
注册需要7天内审核。具体的注册步骤这里不再赘述。
如果审核通过,可以进行下一步的工作了。如何将我们的服务绑定到公众账号呢?
主要是上传头像,也可以修改描述信息。不过注意,一个月只能修改一次。 所以,在做一个新的应用时,一定要等到,需求已定,UE也设计好了图标再处理。
如有修改,只能等一个月了。
3、成为开发者:
首先关闭编辑模式,开启开发模式
这里真正可以绑定我们自己的服务器了。
绑定服务地址和token,服务器地址必须是公网IP,其端口要使用80
token值尽量复杂一点,一旦被人破解,很可能被人利用。
因为,在初次访问服务器的时候,需要一次身份验证,这时需要token。而且一经验证成功,今后不再验证。
公众平台消息接口为开发者提供了一种新的消息处理方式。
最新地址:
http://kf.qq.com/faq/120322fu63YV130422AJbaI3.html
http://www.cnblogs.com/yank/p/3406308.html
来自 http://www.cnblogs.com/yank/p/3364827.html
在开始做之前,大家可能对这个很感兴趣,但是又比较茫然。是不是很复杂?很难学啊?
其实恰恰相反,很简单。为了打消大家的顾虑,先简单介绍了微信公众平台的基本原理。
微信服务器就相当于一个转发服务器,终端(手机、Pad等)发起请求至微信服务器,微信服务器,然后将请求转发给自定义服务(这就里就是我们的具体实现)。
服务处理完毕,然后挥发给微信服务器,微信服务器再将具体响应回复到终端。
通信协议为:HTTP
数据格式为:XML
具体的流程如下图所示:
其实,我们需要做的事情,就是对HTTP请求,做出响应。
具体的请求内容,我们按照特定的XML格式去解析,处理完毕后,也要按照特定的XML格式返回。
我们只需要一个简单的实现HttpHandler即可。
当然,微信平台还能实现更加复杂的业务,比如微信可以作为内嵌的浏览器,我们可以通过微信的链接,打开htm界面,然后实现自己的逻辑。
点击申请,填写网址url和token,其中token可由开发者可以任意填写,用作生成签名。
公众平台用户提交信息后,微信服务器将发送GET请求到填写的URL上,并且带上四个参数:
参数 | 描述 |
---|---|
signature | 微信加密签名 |
timestamp | 时间戳 |
nonce | 随机数 |
echostr | 随机字符串 |
开发者通过检验signature对请求进行校验(下面有校验方式)。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,否则接入失败。
signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
加密/校验流程: 1. 将token、timestamp、nonce三个参数进行字典序排序 2. 将三个参数字符串拼接成一个字符串进行sha1加密 3. 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
当普通微信用户向公众账号发消息时,微信服务器将POST该消息到填写的URL上。结构如下:
<xml> <ToUserName><![CDATA[toUser]]></ToUserName> <FromUserName><![CDATA[fromUser]]></FromUserName> <CreateTime>1348831860</CreateTime> <MsgType><![CDATA[text]]></MsgType> <Content><![CDATA[this is a test]]></Content> <MsgId>1234567890123456</MsgId> </xml>
参数 | 描述 |
---|---|
ToUserName | 开发者微信号 |
FromUserName | 发送方帐号(一个OpenID) |
CreateTime | 消息创建时间 (整型) |
MsgType | text |
Content | 文本消息内容 |
MsgId | 消息id,64位整型 |
<xml> <ToUserName><![CDATA[toUser]]></ToUserName> <FromUserName><![CDATA[fromUser]]></FromUserName> <CreateTime>1348831860</CreateTime> <MsgType><![CDATA[image]]></MsgType> <PicUrl><![CDATA[this is a url]]></PicUrl> <MsgId>1234567890123456</MsgId> </xml>
参数 | 描述 |
---|---|
ToUserName | 开发者微信号 |
FromUserName | 发送方帐号(一个OpenID) |
CreateTime | 消息创建时间 (整型) |
MsgType | image |
PicUrl | 图片链接 |
MsgId | 消息id,64位整型 |
<xml> <ToUserName><![CDATA[toUser]]></ToUserName> <FromUserName><![CDATA[fromUser]]></FromUserName> <CreateTime>1351776360</CreateTime> <MsgType><![CDATA[location]]></MsgType> <Location_X>23.134521</Location_X> <Location_Y>113.358803</Location_Y> <Scale>20</Scale> <Label><![CDATA[位置信息]]></Label> <MsgId>1234567890123456</MsgId> </xml>
参数 | 描述 |
---|---|
ToUserName | 开发者微信号 |
FromUserName | 发送方帐号(一个OpenID) |
CreateTime | 消息创建时间 (整型) |
MsgType | location |
Location_X | 地理位置纬度 |
Location_Y | 地理位置经度 |
Scale | 地图缩放大小 |
Label | 地理位置信息 |
MsgId | 消息id,64位整型 |
<xml> <ToUserName><![CDATA[toUser]]></ToUserName> <FromUserName><![CDATA[fromUser]]></FromUserName> <CreateTime>1351776360</CreateTime> <MsgType><![CDATA[link]]></MsgType> <Title><![CDATA[公众平台官网链接]]></Title> <Description><![CDATA[公众平台官网链接]]></Description> <Url><![CDATA[url]]></Url> <MsgId>1234567890123456</MsgId> </xml>
参数 | 描述 |
---|---|
ToUserName | 接收方微信号 |
FromUserName | 发送方微信号,若为普通用户,则是一个OpenID |
CreateTime | 消息创建时间 |
MsgType | 消息类型,link |
Title | 消息标题 |
Description | 消息描述 |
Url | 消息链接 |
MsgId | 消息id,64位整型 |
事件推送只支持微信4.5版本,目前开启自定义菜单接口事件推送、关注与取消关注事件推送。其余功能即将开放,敬请期待。
<xml><ToUserName><![CDATA[toUser]]></ToUserName> <FromUserName><![CDATA[FromUser]]></FromUserName> <CreateTime>123456789</CreateTime> <MsgType><![CDATA[event]]></MsgType> <Event><![CDATA[EVENT]]></Event> <EventKey><![CDATA[EVENTKEY]]></EventKey> </xml>
参数 | 描述 |
---|---|
ToUserName | 接收方微信号 |
FromUserName | 发送方微信号,若为普通用户,则是一个OpenID |
CreateTime | 消息创建时间 |
MsgType | 消息类型,event |
Event | 事件类型,subscribe(订阅)、unsubscribe(取消订阅)、CLICK(自定义菜单点击事件) |
EventKey | 事件KEY值,与自定义菜单接口中KEY值对应 |
对于每一个POST请求,开发者在响应包中返回特定xml结构,对该消息进行响应(现支持回复文本、图文、语音、视频、音乐)。
微信服务器在五秒内收不到响应会断掉连接。
回复xml结构如下:
<xml> <ToUserName><![CDATA[toUser]]></ToUserName> <FromUserName><![CDATA[fromUser]]></FromUserName> <CreateTime>12345678</CreateTime> <MsgType><![CDATA[text]]></MsgType> <Content><![CDATA[content]]></Content> </xml>
参数 | 描述 |
---|---|
ToUserName | 接收方帐号(收到的OpenID) |
FromUserName | 开发者微信号 |
CreateTime | 消息创建时间 |
MsgType | text |
Content | 回复的消息内容,长度不超过2048字节 |
<xml> <ToUserName><![CDATA[toUser]]></ToUserName> <FromUserName><![CDATA[fromUser]]></FromUserName> <CreateTime>12345678</CreateTime> <MsgType><![CDATA[music]]></MsgType> <Music> <Title><![CDATA[TITLE]]></Title> <Description><![CDATA[DESCRIPTION]]></Description> <MusicUrl><![CDATA[MUSIC_Url]]></MusicUrl> <HQMusicUrl><![CDATA[HQ_MUSIC_Url]]></HQMusicUrl> </Music> </xml>
参数 | 描述 |
---|---|
ToUserName | 接收方帐号(收到的OpenID) |
FromUserName | 开发者微信号 |
CreateTime | 消息创建时间 |
MsgType | music |
MusicUrl | 音乐链接 |
HQMusicUrl | 高质量音乐链接,WIFI环境优先使用该链接播放音乐 |
<xml> <ToUserName><![CDATA[toUser]]></ToUserName> <FromUserName><![CDATA[fromUser]]></FromUserName> <CreateTime>12345678</CreateTime> <MsgType><![CDATA[news]]></MsgType> <ArticleCount>2</ArticleCount> <Articles> <item> <Title><![CDATA[title1]]></Title> <Description><![CDATA[description1]]></Description> <PicUrl><![CDATA[picurl]]></PicUrl> <Url><![CDATA[url]]></Url> </item> <item> <Title><![CDATA[title]]></Title> <Description><![CDATA[description]]></Description> <PicUrl><![CDATA[picurl]]></PicUrl> <Url><![CDATA[url]]></Url> </item> </Articles> </xml>
参数 | 描述 |
---|---|
ToUserName | 接收方帐号(收到的OpenID) |
FromUserName | 开发者微信号 |
CreateTime | 消息创建时间 |
MsgType | news |
ArticleCount | 图文消息个数,限制为10条以内 |
Articles | 多条图文消息信息,默认第一个item为大图 |
Title | 图文消息标题 |
Description | 图文消息描述 |
PicUrl | 图片链接,支持JPG、PNG格式,较好的效果为大图640*320,小图80*80。 |
Url | 点击图文消息跳转链接 |
1.用户OpenID对一个公众号是固定唯一的串
2.请使用80端口
来自 http://www.cnblogs.com/yank/p/3507326.html
上一章,我们已经初步讲解了微信公众账号开发的基本原理,今天我们来探索设计实现。
首先我们设计了模块层次图,当然图中只是给出一种实现方式,不局限于此。具体见下图。
主要功能介绍如下:
1)请求接口层。处理HTTP请求,及响应
2)分发层。由接口层传入请求,然后具体分析请求类型,分发至不同的处理器
3)业务逻辑层。这里是我们的具体业务逻辑了,根据请求,实现具体的业务逻辑。
4)数据层。我们在实现某个应用时可能需要访问数据,可以是数据库或者是文件。如果是简单应用,可能没有这一层。
其实,具体的应用可以在这个结构上去扩展,可以扩展消息对象层、业务对象层、数据访问层、功能管理层等。这里只是提供一种思路,不局限于此。
根据层次图,设计流程图,具体讲述实现的各个过程。以便了解整个处理过程。如下图所示:
根据流程图,我们能够清晰的了解整个流程,消息处理的具体实现步骤。
下面我们针对每个流程进行代码实现。
我们需要一个HttpHandler或者一个网页,来处理微信服务端HTTP请求。
这里我们使用了HttpHandler。因为其灵活性高,性能好。
具体实现如下。
public class WeiXinHttpHandler:IHttpHandler { /// <summary> /// /// </summary> public bool IsReusable { get { return true; } } /// <summary> /// 处理请求 /// </summary> /// <param name="context"></param> public void ProcessRequest(HttpContext context) { //由微信服务接收请求,具体处理请求 WeiXinService wxService = new WeiXinService(context.Request); string responseMsg = wxService.Response(); context.Response.Clear(); context.Response.Charset = "UTF-8"; context.Response.Write(responseMsg); context.Response.End(); } }
如果是HTTPHandler,需要在配置文件中,配置具体的应用。具体的节点配置,我们不作说明。直接给出例子,配置HttpHandler节点如下
<httpHandlers> <add verb="*" path="WXService.ashx" type="namespace.WeiXinHttpHandler,WXWeb" validate="true"/> </httpHandlers>
为了能功能封装,我们也将此封装在了处理组件中。其实可以放置在HttpHandler中的。
1)验证签名
如果是首次请求,需要验证签名。就相当于一次HTTP握手。之前在上一章中,设置的服务器URL以及token值,这个功能就是检验是否链接成功。
这个请求是GET请求。以下具体说明(官方):
业务逻辑:
加密/校验流程:
<1> 将token、timestamp、nonce三个参数进行字典序排序
<2> 将三个参数字符串拼接成一个字符串进行SHA1加密
<3> 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
而官方只提供了PHP的代码示例,很多东西在C#中并非直译既得。所以这里面也有一些具体处理。先看官方的代码:
private function checkSignature() { $signature = $_GET["signature"]; $timestamp = $_GET["timestamp"]; $nonce = $_GET["nonce"]; $token = TOKEN; $tmpArr = array($token, $timestamp, $nonce); sort($tmpArr); $tmpStr = implode( $tmpArr ); $tmpStr = sha1( $tmpStr ); if( $tmpStr == $signature ){ return true; }else{ return false; } }
我们将其翻译成C#版本:
/// <summary> /// 检查签名 /// </summary> /// <param name="request"></param> /// <returns></returns> private bool CheckSignature() { string signature = Request.QueryString[SIGNATURE]; string timestamp = Request.QueryString[TIMESTAMP]; string nonce = Request.QueryString[NONCE]; List<string> list = new List<string>(); list.Add(TOKEN); list.Add(timestamp); list.Add(nonce); //排序 list.Sort(); //拼串 string input = string.Empty; foreach (var item in list) { input += item; } //加密 string new_signature = SecurityUtility.SHA1Encrypt(input); //验证 if (new_signature == signature) { return true; } else { return false; } }
这里需要SHA1加密,具体的算法如下:
/// <summary> /// SHA1加密 /// </summary> /// <param name="intput">输入字符串</param> /// <returns>加密后的字符串</returns> public static string SHA1Encrypt(string intput) { byte[] StrRes = Encoding.Default.GetBytes(intput); HashAlgorithm mySHA = new SHA1CryptoServiceProvider(); StrRes = mySHA.ComputeHash(StrRes); StringBuilder EnText = new StringBuilder(); foreach (byte Byte in StrRes) { EnText.AppendFormat("{0:x2}", Byte); } return EnText.ToString(); }
2)分发请求
接下来就是具体的消息请求了,这里都是POST请求。
因为有多种消息类型,我们通过工厂类来进行封装,然后每种消息都有专门的处理器来进行处理。具体实现逻辑:
/// <summary> /// 处理请求 /// </summary> /// <returns></returns> private string ResponseMsg() { string requestXml = Common.ReadRequest(this.Request); IHandler handler = HandlerFactory.CreateHandler(requestXml); if (handler != null) { return handler.HandleRequest(); } return string.Empty; }
处理请求的对外方法(HttpHandler调用的方法就是这个了),即:
/// <summary> /// 处理请求,产生响应 /// </summary> /// <returns></returns> public string Response() { string method = Request.HttpMethod.ToUpper(); //验证签名 if (method == "GET") { if (CheckSignature()) { return Request.QueryString[ECHOSTR]; } else { return "error"; } } //处理消息 if (method == "POST") { return ResponseMsg(); } else { return "无法处理"; } }
1)消息类型
首先我们来看下,具体的消息类型,其实上一张中已经明确给了消息的接口。
这里再看具体看一下,请求的消息类型有哪些,回复的消息类型有哪些等。
千万要注意,请求的消息是文本类型,回复的消息,不一定也是文本哦,可以是图文、音乐等任意一种可回复的消息。具体见下表所示。
2)根据具体的消息接口,设计消息类。
这里给出类图,供参考。
3)针对不同的消息,会有不同的处理器,来看下具体的类图。
4)具体业务处理
每个handler里面就是可以处理具体请求。输入的什么消息,访问那些数据,调用服务等,都在这里处理。
还是建议大家对具体的业务进行单独封装,在Handler中,只提供调用的接口。
因为随着业务的增加,一个Handler可能要处理很多业务,如果所有的操作逻辑都写在这里,势必影响阅读,也不易于维护与扩展。
5)产生回复消息
在处理完请求后,需要生成回复消息,响应到终端。消息格式,就是我们介绍那些消息类型,但必须是可用于回复的,当前支持的有:文本、图文、音乐等。
一定要明确:回复的消息类型不一定要与请求的消息类型一样,比如,请求是文本,回复的可以是图文、音乐。
产生回复消息的过程,其实,就是特定的消息对象格式化为对应的XML的过程,然后将XML响应至微信服务器。
6)实例
这里以微信用户关注公众账号,然后服务端处理处理事件请求,登记用户,并提示欢迎信息。
class EventHandler : IHandler { /// <summary> /// 请求的xml /// </summary> private string RequestXml { get; set; } /// <summary> /// 构造函数 /// </summary> /// <param name="requestXml"></param> public EventHandler(string requestXml) { this.RequestXml = requestXml; } /// <summary> /// 处理请求 /// </summary> /// <returns></returns> public string HandleRequest() { string response = string.Empty; EventMessage em = EventMessage.LoadFromXml(RequestXml); if (em.Event == EventType.Subscribe) { //注册用户 User user = new User(); user.OpenID = em.FromUserName; UserManager.Regester(user); //回复欢迎消息 TextMessage tm = new TextMessage(); tm.ToUserName = em.FromUserName; tm.FromUserName = em.ToUserName; tm.CreateTime = Common.GetNowTime(); tm.Content = "欢迎您关注xxx,我是小微。有什么我能帮助您的吗?"; response = tm.GenerateContent(); } return response; } }
最后将处理结果返回至最初HttpHandler,响应给微信服务器,直接Response处理。这也是在最开始设计的HttpHandler中实现的。
下面是代码片段,具体可见一、Http请求
context.Response.Clear(); context.Response.Charset = "UTF-8"; context.Response.Write(responseMsg); context.Response.End();
来自 http://www.cnblogs.com/yank/p/3392394.html
上一篇文章,写了基本框架,可能很多人会觉得晕头转向,这里提供一个简单的例子来予以说明,希望能帮你解开谜团。
通过微信公众平台实现在线客服机器人功能。主要的功能包括:简单对话、查询天气等服务。
这里只是提供比较简单的功能,重在通过此实例来说明公众平台的具体研发过程。只是一个简单DEMO,如果需要的话可以在此基础上进行扩展。
当然后续我们还会推出比较复杂的应用实例。
这里不再赘述,参照上一章,微信公众账号开发教程(二) 基础框架搭建
http://www.cnblogs.com/yank/p/3392394.html
这里不再赘述,参照上一章,微信公众账号开发教程(二) 基础框架搭建
http://www.cnblogs.com/yank/p/3392394.html
当微信用户关注公众账号时,可以给其适当的提示。可以是欢迎词,可以是帮助提示。
直接上代码:
class EventHandler : IHandler { /// <summary> /// 请求的xml /// </summary> private string RequestXml { get; set; } /// <summary> /// 构造函数 /// </summary> /// <param name="requestXml"></param> public EventHandler(string requestXml) { this.RequestXml = requestXml; } /// <summary> /// 处理请求 /// </summary> /// <returns></returns> public string HandleRequest() { string response = string.Empty; EventMessage em = EventMessage.LoadFromXml(RequestXml); if (em.Event.Equals("subscribe",StringComparison.OrdinalIgnoreCase)) { //回复欢迎消息 TextMessage tm = new TextMessage(); tm.ToUserName = em.FromUserName; tm.FromUserName = em.ToUserName; tm.CreateTime = Common.GetNowTime(); tm.Content = "欢迎您关注***,我是大哥大,有事就问我,呵呵!\n\n"; response = tm.GenerateContent(); } return response; } }
简单的交流问候,比如你好、帮助等等,跟我们使用微信聊天一样,不过回应是由我们的程序响应。具体功能,可以根据自己的需要进行添加。
微信本来就是沟通的平台。这个案例,可以用于在线服务机器人,类似于淘宝的客服机器人,可是我们这个是微信版的。呵呵
其实,很简单,获取请求消息,根据关键字来匹配回应。当然这里可能要做的工作很多,如何支持智能匹配,如何支持模糊匹配等。
代码如下:
/// <summary> /// 文本信息处理类 /// </summary> public class TextHandler : IHandler { /// <summary> /// 请求的XML /// </summary> private string RequestXml { get; set; } /// <summary> /// 构造函数 /// </summary> /// <param name="requestXml">请求的xml</param> public TextHandler(string requestXml) { this.RequestXml = requestXml; } /// <summary> /// 处理请求 /// </summary> /// <returns></returns> public string HandleRequest() { string response = string.Empty; TextMessage tm = TextMessage.LoadFromXml(RequestXml); string content = tm.Content.Trim(); if (string.IsNullOrEmpty(content)) { response = "您什么都没输入,没法帮您啊,%>_<%。"; } else { if (content.StartsWith("tq", StringComparison.OrdinalIgnoreCase)) { string cityName = content.Substring(2).Trim(); response = WeatherHelper.GetWeather(cityName); } else { response = HandleOther(content); } } tm.Content = response; //进行发送者、接收者转换 string temp = tm.ToUserName; tm.ToUserName = tm.FromUserName; tm.FromUserName = temp; response = tm.GenerateContent(); return response; } /// <summary> /// 处理其他消息 /// </summary> /// <param name="tm"></param> /// <returns></returns> private string HandleOther(string requestContent) { string response = string.Empty; if (requestContent.Contains("你好") || requestContent.Contains("您好")) { response = "您也好~"; } else if (requestContent.Contains("傻")) { response = "我不傻!哼~ "; } else if (requestContent.Contains("逼") || requestContent.Contains("操")) { response = "哼,你说脏话! "; } else if (requestContent.Contains("是谁")) { response = "我是大哥大,有什么能帮您的吗?~"; } else if (requestContent.Contains("再见")) { response = "再见!"; } else if (requestContent.Contains("bye")) { response = "Bye!"; } else if (requestContent.Contains("谢谢")) { response = "不客气!嘿嘿"; } else if (requestContent == "h" || requestContent == "H" || requestContent.Contains("帮助")) { response = @"查询天气,输入tq 城市名称\拼音\首字母"; } else { response = "您说的,可惜,我没明白啊,试试其他关键字吧。"; } return response; } }
这个功能需要请求实时查询的,请求官方的天气发布网站,然后解析其返回值,按照我们需要的格式,组织天气信息,最后发送给微信客户。
采用文本消息方式处理。
用户请求,只需输入:tq 城市名称/拼音/首字母,即可获取消息。
回复的消息:(以北京为例)
北京
2013年11月6日 星期三
今天:(17℃~4℃)晴北风4-5级转3-4级4-5级转3-4级
24小时穿衣指数:天气冷,建议着棉服、羽绒服、皮夹克加羊毛衫等冬季服装。年老体弱者宜着厚棉衣、冬大衣或厚羽绒服。
明天:(14℃~3℃)晴转多云微风小于3级
48小时穿衣指数:天气冷,建议着棉服、羽绒服、皮夹克加羊毛衫等冬季服装。年老体弱者宜着厚棉衣、冬大衣或厚羽绒服。
来看源码吧:
class WeatherHelper { /// <summary> /// 城市集合字段 /// </summary> private static Dictionary<string, City> mCities; /// <summary> /// 城市集合 /// </summary> public static Dictionary<string, City> Cities { get { if (mCities == null) { LoadCities(); } return mCities; } } /// <summary> /// 加载城市 /// </summary> private static void LoadCities() { mCities = new Dictionary<string, City>(); mCities.Clear(); mCities.Add("101010100", new City() { Code = "101010100", Name = "北京", PinYin = "beijing", FristLetter = "bj" }); mCities.Add("101020100", new City() { Code = "101020100", Name = "上海", PinYin = "shanghai", FristLetter = "sh" }); mCities.Add("101200101", new City() { Code = "101200101", Name = "武汉", PinYin = "wuhai", FristLetter = "wh" }); } /// <summary> /// 获取城市的天气 /// </summary> /// <param name="name">城市名称、拼音或首字母</param> /// <returns></returns> public static string GetWeather(string name) { string result = string.Empty; string cityCode = string.Empty; //获取城市编码 IEnumerable<string> codes = from item in Cities where item.Value != null && (item.Value.Name.Equals(name, StringComparison.OrdinalIgnoreCase) || item.Value.PinYin.Equals(name, StringComparison.OrdinalIgnoreCase) || item.Value.FristLetter.Equals(name, StringComparison.OrdinalIgnoreCase)) select item.Value.Code; if (codes != null && codes.Count() > 0) { cityCode = codes.First<string>(); } //http请求,获取天气 if (!string.IsNullOrEmpty(cityCode)) { string url = "http://m.weather.com.cn/data/{0}.html"; url = string.Format(url, cityCode); WebRequest request = HttpWebRequest.Create(url); //超时时间为:2秒 request.Timeout = 2000; request.Credentials = CredentialCache.DefaultCredentials; WebResponse response = request.GetResponse(); StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8); string weahterInfo = reader.ReadToEnd(); if (string.IsNullOrEmpty(weahterInfo)) { result = "暂时没有取到天气数据,请稍后再试"; } else { XmlDocument doc = JsonConvert.DeserializeXmlNode(weahterInfo); if (doc != null) { XmlNode node = doc.DocumentElement; if (node != null) { StringBuilder builder = new StringBuilder(); builder.Append(node["city"].InnerText).Append("\n"); builder.Append(node["date_y"].InnerText).Append(" ").Append(node["week"].InnerText).Append("\n"); builder.Append("今天:").Append("(").Append(node["temp1"].InnerText).Append(")").Append(node["weather1"].InnerText).Append(node["wind1"].InnerText).Append(node["fl1"].InnerText).Append("\n"); builder.Append("24小时穿衣指数:").Append(node["index_d"].InnerText).Append("\n"); builder.Append("明天:").Append("(").Append(node["temp2"].InnerText).Append(")").Append(node["weather2"].InnerText).Append(node["wind2"].InnerText).Append(node["fl2"].InnerText).Append("\n"); builder.Append("48小时穿衣指数:").Append(node["index48_d"].InnerText).Append("\n"); result = builder.ToString(); } } #region 天气json数据格式 /* { "weatherinfo": { "city": "北京", "city_en": "beijing", "date_y": "2013年11月4日", "date": "", "week": "星期一", "fchh": "11", "cityid": "101010100", "temp1": "17℃~5℃", "temp2": "16℃~5℃", "temp3": "18℃~4℃", "temp4": "17℃~5℃", "temp5": "14℃~6℃", "temp6": "14℃~2℃", "tempF1": "62.6℉~41℉", "tempF2": "60.8℉~41℉", "tempF3": "64.4℉~39.2℉", "tempF4": "62.6℉~41℉", "tempF5": "57.2℉~42.8℉", "tempF6": "57.2℉~35.6℉", "weather1": "晴转多云", "weather2": "多云", "weather3": "多云转晴", "weather4": "晴转多云", "weather5": "多云转阴", "weather6": "阴转晴", "img1": "0", "img2": "1", "img3": "1", "img4": "99", "img5": "1", "img6": "0", "img7": "0", "img8": "1", "img9": "1", "img10": "2", "img11": "2", "img12": "0", "img_single": "0", "img_title1": "晴", "img_title2": "多云", "img_title3": "多云", "img_title4": "多云", "img_title5": "多云", "img_title6": "晴", "img_title7": "晴", "img_title8": "多云", "img_title9": "多云", "img_title10": "阴", "img_title11": "阴", "img_title12": "晴", "img_title_single": "晴", "wind1": "微风", "wind2": "微风", "wind3": "微风", "wind4": "微风", "wind5": "微风", "wind6": "北风4-5级", "fx1": "微风", "fx2": "微风", "fl1": "小于3级", "fl2": "小于3级", "fl3": "小于3级", "fl4": "小于3级", "fl5": "小于3级", "fl6": "4-5级", "index": "较冷", "index_d": "建议着大衣、呢外套加毛衣、卫衣等服装。体弱者宜着厚外套、厚毛衣。因昼夜温差较大,注意增减衣服。", "index48": "冷", "index48_d": "天气冷,建议着棉服、羽绒服、皮夹克加羊毛衫等冬季服装。年老体弱者宜着厚棉衣、冬大衣或厚羽绒服。", "index_uv": "中等", "index48_uv": "弱", "index_xc": "适宜", "index_tr": "适宜", "index_co": "舒适", "st1": "17", "st2": "5", "st3": "17", "st4": "5", "st5": "18", "st6": "6", "index_cl": "适宜", "index_ls": "适宜", "index_ag": "极不易发" } } */ #endregion } } else { result = "没有获取到该城市的天气,请确定输入了正确的城市名称,如\'北京\'或者\'beijing\'或者\'bj\'"; } //返回 return result; } /// <summary> /// 内部类:城市 /// </summary> internal class City { /// <summary> /// 编码 /// </summary> public string Code { get; set; } /// <summary> /// 名称 /// </summary> public string Name { get; set; } /// <summary> /// 拼音 /// </summary> public string PinYin { get; set; } /// <summary> /// 拼音首字母 /// </summary> public string FristLetter { get; set; } } }
这里可是可执行的代码哦。应大家的需求,这里提供全部的源代码。
http://files.cnblogs.com/yank/Yank.WeiXin.Robot.zip
来自 http://www.cnblogs.com/yank/p/3409308.html
请尊重作者版权,如需转载,请标明出处。
应大家强烈要求,将自定义菜单功能课程提前。
如果只有输入框,可能太简单,感觉像命令行。自定义菜单,给我们提供了很大的灵活性,更符合用户的操作习惯。在一个小小的微信对话页面,可以实现更多的功能。菜单直观明了,不仅能提供事件响应,还支持URL跳转,如果需要的功能比较复杂,我们大可以使用URL跳转,跳转至我们的网页即可。
注意:自定义菜单,只有服务号才有此功能
如何注册,见第一章:微信公众账号开发教程(一) 基本原理及微信公众账号注册
效果如下,
接着我们详细介绍,如何实现自定义菜单?
access_token是公众号的全局唯一票据,公众号调用各接口时都需使用access_token。正常情况下access_token有效期为7200秒,重复获取将导致上次获取的access_token失效。
公众号可以使用AppID和AppSecret调用本接口来获取access_token。AppID和AppSecret可在开发模式中获得(需要已经成为开发者,且帐号没有异常状态)。注意调用所有微信接口时均需使用https协议。
接口调用请求说明
http请求方式: GET https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
参数说明
参数 | 是否必须 | 说明 |
---|---|---|
grant_type | 是 | 获取access_token填写client_credential |
appid | 是 | 第三方用户唯一凭证 |
secret | 是 | 第三方用户唯一凭证密钥,既appsecret |
返回说明
正常情况下,微信会返回下述JSON数据包给公众号:
{"access_token":"ACCESS_TOKEN","expires_in":7200}
参数 | 说明 |
---|---|
access_token | 获取到的凭证 |
expires_in | 凭证有效时间,单位:秒 |
错误时微信会返回错误码等信息,JSON数据包示例如下(该示例为AppID无效错误):
{"errcode":40013,"errmsg":"invalid appid"}
自定义菜单能够帮助公众号丰富界面,让用户更好更快地理解公众号的功能。开启自定义菜单后,公众号界面如图所示:
目前自定义菜单最多包括3个一级菜单,每个一级菜单最多包含5个二级菜单。一级菜单最多4个汉字,二级菜单最多7个汉字,多出来的部分将会以“...”代替。请注意,创建自定义菜单后,由于微信客户端缓存,需要24小时微信客户端才会展现出来。建议测试时可以尝试取消关注公众账号后再次关注,则可以看到创建后的效果。
目前自定义菜单接口可实现两种类型按钮,如下:
click: 用户点击click类型按钮后,微信服务器会通过消息接口推送消息类型为event 的结构给开发者(参考消息接口指南),并且带上按钮中开发者填写的key值,开发者可以通过自定义的key值与用户进行交互; view: 用户点击view类型按钮后,微信客户端将会打开开发者在按钮中填写的url值 (即网页链接),达到打开网页的目的,建议与网页授权获取用户基本信息接口结合,获得用户的登入个人信息。
接口调用请求说明
http请求方式:POST(请使用https协议) https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN
请求示例
{ "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" }] }] }
参数说明
参数 | 是否必须 | 说明 |
---|---|---|
button | 是 | 一级菜单数组,个数应为1~3个 |
sub_button | 否 | 二级菜单数组,个数应为1~5个 |
type | 是 | 菜单的响应动作类型,目前有click、view两种类型 |
name | 是 | 菜单标题,不超过16个字节,子菜单不超过40个字节 |
key | click类型必须 | 菜单KEY值,用于消息接口推送,不超过128字节 |
url | view类型必须 | 网页链接,用户点击菜单可打开链接,不超过256字节 |
返回结果
正确时的返回JSON数据包如下:
{"errcode":0,"errmsg":"ok"}
错误时的返回JSON数据包如下(示例为无效菜单名长度):
{"errcode":40018,"errmsg":"invalid button name size"}
使用接口创建自定义菜单后,开发者还可使用接口查询自定义菜单的结构。
请求说明
http请求方式:GET https://api.weixin.qq.com/cgi-bin/menu/get?access_token=ACCESS_TOKEN
返回说明
对应创建接口,正确的Json返回结果: {"menu":{"button":[{"type":"click","name":"今日歌曲","key":"V1001_TODAY_MUSIC","sub_button":[]},{"type":"click","name":"歌手简介","key":"V1001_TODAY_SINGER","sub_button":[]},{"name":"菜单","sub_button":[{"type":"view","name":"搜索","url":"http://www.soso.com/","sub_button":[]},{"type":"view","name":"视频","url":"http://v.qq.com/","sub_button":[]},{"type":"click","name":"赞一下我们","key":"V1001_GOOD","sub_button":[]}]}]}}
使用接口创建自定义菜单后,开发者还可使用接口删除当前使用的自定义菜单。
请求说明
http请求方式:GET https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=ACCESS_TOKEN
返回说明
对应创建接口,正确的Json返回结果: {"errcode":0,"errmsg":"ok"}
用户点击自定义菜单后,如果菜单按钮设置为click类型,则微信会把此次点击事件推送给开发者,注意view类型(跳转到URL)的菜单点击不会上报。
推送XML数据包示例:
<xml> <ToUserName><![CDATA[toUser]]></ToUserName> <FromUserName><![CDATA[FromUser]]></FromUserName> <CreateTime>123456789</CreateTime> <MsgType><![CDATA[event]]></MsgType> <Event><![CDATA[CLICK]]></Event> <EventKey><![CDATA[EVENTKEY]]></EventKey> </xml>
参数说明:
参数 | 描述 |
---|---|
ToUserName | 开发者微信号 |
FromUserName | 发送方帐号(一个OpenID) |
CreateTime | 消息创建时间 (整型) |
MsgType | 消息类型,event |
Event | 事件类型,CLICK |
EventKey | 事件KEY值,与自定义菜单接口中KEY值对应 |
还接着上一篇文章讲。微信公众账号开发教程(三) 实例入门:机器人(附源码)
我们将在上一篇文章基础上,添加自定义菜单功能。
首先需要得到AppId和AppSecret
当你成为开发者后,自然能够在,开发者模式,便可看到这两个值,可以重置。
然后调用按照二.1中所示,进行操作。
注意:access_token有过期时间,如果过期,需要重新获取。
代码如下:
private static DateTime GetAccessToken_Time; /// <summary> /// 过期时间为7200秒 /// </summary> private static int Expires_Period = 7200; /// <summary> /// /// </summary> private static string mAccessToken; /// <summary> /// /// </summary> public static string AccessToken { get { //如果为空,或者过期,需要重新获取 if (string.IsNullOrEmpty(mAccessToken) || HasExpired()) { //获取 mAccessToken = GetAccessToken(AppID, AppSecret); } return mAccessToken; } } /// <summary> /// /// </summary> /// <param name="appId"></param> /// <param name="appSecret"></param> /// <returns></returns> private static string GetAccessToken(string appId, string appSecret) { string url = string.Format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={0}&secret={1}", appId, appSecret); string result = HttpUtility.GetData(url); XDocument doc = XmlUtility.ParseJson(result, "root"); XElement root = doc.Root; if (root != null) { XElement access_token = root.Element("access_token"); if (access_token != null) { GetAccessToken_Time = DateTime.Now; if (root.Element("expires_in")!=null) { Expires_Period = int.Parse(root.Element("expires_in").Value); } return access_token.Value; } else { GetAccessToken_Time = DateTime.MinValue; } } return null; } /// <summary> /// 判断Access_token是否过期 /// </summary> /// <returns>bool</returns> private static bool HasExpired() { if (GetAccessToken_Time != null) { //过期时间,允许有一定的误差,一分钟。获取时间消耗 if (DateTime.Now > GetAccessToken_Time.AddSeconds(Expires_Period).AddSeconds(-60)) { return true; } } return false; }
菜单需根据需要,按照实际要求进行设定。
这里我们提供天气查询功能,将常用的城市列出来,点击即可查询。
然后还提供了友情链接,这里提供了view类型的菜单,直接可以跳转至URL页面,为跳转做个好的演示。
具体菜单如下:
{ "button": [ { "name": "链接", "sub_button": [ { "type": "view", "name": "搜索", "url": "http://www.baidu.com/" }, { "type": "view", "name": "视频", "url": "http://v.qq.com/" }, { "type": "click", "name": "赞一下我们", "key": "BTN_GOOD" } ] }, { "name": "查询天气", "sub_button": [ { "type": "click", "name": "武汉", "key": "BTN_TQ_WUHAN" }, { "type": "click", "name": "上海", "key": "BTN_TQ_SHANGHAI" }, { "type": "click", "name": "北京", "key": "BTN_TQ_BEIJING" } ] }, { "type": "click", "name": "帮助", "key": "BTN_HELP" } ] }
因为菜单的变更没有那么频繁,因此通过txt文件来设置菜单,并通过管理界面来管理菜单。
主要的管理功能:
1)从文件加载菜单
2)创建菜单。即将菜单通知微信服务端,并更新至微信客户端
3)查询菜单。获取当前系统的菜单。
4)删除菜单。从微信服务器删除菜单,也可以删除后再创建。
实现代码如下:
public class MenuManager { /// <summary> /// 菜单文件路径 /// </summary> private static readonly string Menu_Data_Path = System.AppDomain.CurrentDomain.BaseDirectory + "/Data/menu.txt"; /// <summary> /// 获取菜单 /// </summary> public static string GetMenu() { string url = string.Format("https://api.weixin.qq.com/cgi-bin/menu/get?access_token={0}", Context.AccessToken); return HttpUtility.GetData(url); } /// <summary> /// 创建菜单 /// </summary> public static void CreateMenu(string menu) { string url = string.Format("https://api.weixin.qq.com/cgi-bin/menu/create?access_token={0}", Context.AccessToken); //string menu = FileUtility.Read(Menu_Data_Path); HttpUtility.SendHttpRequest(url, menu); } /// <summary> /// 删除菜单 /// </summary> public static void DeleteMenu() { string url = string.Format("https://api.weixin.qq.com/cgi-bin/menu/delete?access_token={0}", Context.AccessToken); HttpUtility.GetData(url); } /// <summary> /// 加载菜单 /// </summary> /// <returns></returns> public static string LoadMenu() { return FileUtility.Read(Menu_Data_Path); } }
上面的代码,其实我们对一些公共功能做了封装。如进行get请求、POST提交等操作,读取文件等。
这里我们提供进行get、Post提交的方法案例代码,如果使用,建议优化。
using System; using System.IO; using System.Net; using System.Text; using System.Web; namespace Yank.WeiXin.Robot.Utility { /// <summary> /// Http帮助类 /// </summary> class HttpUtility { /// <summary> /// 发送请求 /// </summary> /// <param name="url">Url地址</param> /// <param name="data">数据</param> public static string SendHttpRequest(string url, string data) { return SendPostHttpRequest(url,"application/x-www-form-urlencoded",data); } /// <summary> /// /// </summary> /// <param name="url"></param> /// <returns></returns> public static string GetData(string url) { return SendGetHttpRequest(url,"application/x-www-form-urlencoded"); } /// <summary> /// 发送请求 /// </summary> /// <param name="url">Url地址</param> /// <param name="method">方法(post或get)</param> /// <param name="method">数据类型</param> /// <param name="requestData">数据</param> public static string SendPostHttpRequest(string url,string contentType,string requestData) { WebRequest request = (WebRequest)HttpWebRequest.Create(url); request.Method = "POST"; byte[] postBytes = null; request.ContentType = contentType; postBytes = Encoding.UTF8.GetBytes(requestData); request.ContentLength = postBytes.Length; using (Stream outstream = request.GetRequestStream()) { outstream.Write(postBytes, 0, postBytes.Length); } string result = string.Empty; using (WebResponse response = request.GetResponse()) { if (response != null) { using (Stream stream = response.GetResponseStream()) { using (StreamReader reader = new StreamReader(stream, Encoding.UTF8)) { result = reader.ReadToEnd(); } } } } return result; } /// <summary> /// 发送请求 /// </summary> /// <param name="url">Url地址</param> /// <param name="method">方法(post或get)</param> /// <param name="method">数据类型</param> /// <param name="requestData">数据</param> public static string SendGetHttpRequest(string url, string contentType) { WebRequest request = (WebRequest)HttpWebRequest.Create(url); request.Method = "GET"; request.ContentType = contentType; string result = string.Empty; using (WebResponse response = request.GetResponse()) { if (response != null) { using (Stream stream = response.GetResponseStream()) { using (StreamReader reader = new StreamReader(stream, Encoding.UTF8)) { result = reader.ReadToEnd(); } } } } return result; } } }
using System; using System.Xml.Linq; using Newtonsoft.Json; namespace Yank.WeiXin.Robot.Utility { class XmlUtility { /// <summary> /// /// </summary> /// <param name="json"></param> /// <param name="rootName"></param> /// <returns></returns> public static XDocument ParseJson(string json,string rootName) { return JsonConvert.DeserializeXNode(json, rootName); } } }
5、事件处理
设置了菜单,这下需要处理事件了。跟我们之前设计ASPX或者WinForm一样,都要绑定按钮的事件。这里只是通过XML消息将请求传递过来。
通过“2、设置菜单"中具体的菜单内容,我们便已经知道需要进行哪些事件处理了。对于按钮类型为view的,无须处理,它会自动跳转至指定url.
需要处理的点击事件:
1)赞一下
2)查询某城市的天气,北京、上海、武汉
3)帮助
这个还要沿用上章中的事件处理器EventHandler来扩展处理。
具体的实现代码吧:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Yank.WeiXin.Robot.Messages; namespace Yank.WeiXin.Robot.Handlers { class EventHandler : IHandler { /// <summary> /// 请求的xml /// </summary> private string RequestXml { get; set; } /// <summary> /// 构造函数 /// </summary> /// <param name="requestXml"></param> public EventHandler(string requestXml) { this.RequestXml = requestXml; } /// <summary> /// 处理请求 /// </summary> /// <returns></returns> public string HandleRequest() { string response = string.Empty; EventMessage em = EventMessage.LoadFromXml(RequestXml); if (em != null) { switch (em.Event.ToLower()) { case ("subscribe"): response = SubscribeEventHandler(em); break; case "click": response = ClickEventHandler(em); break; } } return response; } /// <summary> /// 关注 /// </summary> /// <param name="em"></param> /// <returns></returns> private string SubscribeEventHandler(EventMessage em) { //回复欢迎消息 TextMessage tm = new TextMessage(); tm.ToUserName = em.FromUserName; tm.FromUserName = em.ToUserName; tm.CreateTime = Common.GetNowTime(); tm.Content = "欢迎您关注***,我是大哥大,有事就问我,呵呵!\n\n"; return tm.GenerateContent(); } /// <summary> /// 处理点击事件 /// </summary> /// <param name="em"></param> /// <returns></returns> private string ClickEventHandler(EventMessage em) { string result = string.Empty; if (em != null && em.EventKey != null) { switch (em.EventKey.ToUpper()) { case "BTN_GOOD": result = btnGoodClick(em); break; case "BTN_TQ_BEIJING": result = searchWeather("beijing", em); break; case "BTN_TQ_SHANGHAI": result = searchWeather("shanghai", em); break; case "BTN_TQ_WUHAN": result = searchWeather("wuhai", em); break; case "BTN_HELP": result = btnHelpClick(em); break; } } return result; } /// <summary> /// 赞一下 /// </summary> /// <param name="em"></param> /// <returns></returns> private string btnGoodClick(EventMessage em) { //回复欢迎消息 TextMessage tm = new TextMessage(); tm.ToUserName = em.FromUserName; tm.FromUserName = em.ToUserName; tm.CreateTime = Common.GetNowTime(); tm.Content = @"谢谢您的支持!"; return tm.GenerateContent(); } /// <summary> /// 帮助 /// </summary> /// <param name="em"></param> /// <returns></returns> private string btnHelpClick(EventMessage em) { //回复欢迎消息 TextMessage tm = new TextMessage(); tm.ToUserName = em.FromUserName; tm.FromUserName = em.ToUserName; tm.CreateTime = Common.GetNowTime(); tm.Content = @"查询天气,输入tq 城市名称\拼音\首字母"; return tm.GenerateContent(); } /// <summary> /// 查询天气 /// </summary> /// <param name="cityName"></param> /// <param name="em"></param> /// <returns></returns> private string searchWeather(string cityName, EventMessage em) { TextMessage tm = new TextMessage(); tm.Content = WeatherHelper.GetWeather(cityName); //进行发送者、接收者转换 tm.ToUserName = em.FromUserName; tm.FromUserName = em.ToUserName; tm.CreateTime = Common.GetNowTime(); return tm.GenerateContent(); } } }
6、效果图
终于大工告成,最后来看下效果图吧
注意:
请尊重作者版权,如需转载,请标明出处。
下一章计划:
微信公众平台开发教程(六)获取个性二维码
来自 http://www.cnblogs.com/yank/p/3418194.html
在进行推广时,我们可以告诉对方,我们的微信公众账号是什么,客户可以去搜索,然后关注。二维码给我们提供了极大的便捷,只要简单一扫描,即可关注。
如果已经关注过,立刻跳入对话画面。在我们进行推广时,不再是简陋的文字,可以是一个有个性的二维码,想必会很生动。
微信对二维码提供了很好的支持,而且还可以根据需要生成不同场景的二维码。下面我们将介绍如何获取和使用二维码。
注意:限服务号,且进行了微信认证,费用300
为了满足用户渠道推广分析的需要,公众平台提供了生成带参数二维码的接口。使用该接口可以获得多个带不同场景值的二维码,用户扫描后,公众号可以接收到事件推送。
目前有2种类型的二维码,分别是临时二维码和永久二维码,前者有过期时间,最大为1800秒,但能够生成较多数量,后者无过期时间,数量较少(目前参数只支持1--1000)。两种二维码分别适用于帐号绑定、用户来源统计等场景。
用户扫描带场景值二维码时,可能推送以下两种事件:
如果用户还未关注公众号,则用户可以关注公众号,关注后微信会将带场景值关注事件推送给开发者。
如果用户已经关注公众号,在用户扫描后会自动进入会话,微信也会将带场景值扫描事件推送给开发者。
获取带参数的二维码的过程包括两步,首先创建二维码ticket,然后凭借ticket到指定URL换取二维码。
每次创建二维码ticket需要提供一个开发者自行设定的参数(scene_id),分别介绍临时二维码和永久二维码的创建二维码ticket过程。
临时二维码请求说明
http请求方式: POST URL: https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=TOKEN POST数据格式:json POST数据例子:{"expire_seconds": 1800, "action_name": "QR_SCENE", "action_info": {"scene": {"scene_id": 123}}}
永久二维码请求说明
http请求方式: POST URL: https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=TOKEN POST数据格式:json POST数据例子:{"action_name": "QR_LIMIT_SCENE", "action_info": {"scene": {"scene_id": 123}}}
参数说明
参数 | 说明 |
---|---|
expire_seconds | 该二维码有效时间,以秒为单位。 最大不超过1800。 |
action_name | 二维码类型,QR_SCENE为临时,QR_LIMIT_SCENE为永久 |
action_info | 二维码详细信息 |
scene_id | 场景值ID,临时二维码时为32位整型,永久二维码时最大值为1000 |
返回说明
正确的Json返回结果:
{"ticket":"gQG28DoAAAAAAAAAASxodHRwOi8vd2VpeGluLnFxLmNvbS9xL0FuWC1DNmZuVEhvMVp4NDNMRnNRAAIEesLvUQMECAcAAA==","expire_seconds":1800}
参数 | 说明 |
---|---|
ticket | 获取的二维码ticket,凭借此ticket可以在有效时间内换取二维码。 |
expire_seconds | 二维码的有效时间,以秒为单位。最大不超过1800。 |
错误的Json返回示例:
{"errcode":40013,"errmsg":"invalid appid"}
获取二维码ticket后,开发者可用ticket换取二维码图片。请注意,本接口无须登录态即可调用。
请求说明
HTTP GET请求(请使用https协议) https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=TICKET
返回说明
ticket正确情况下,http 返回码是200,是一张图片,可以直接展示或者下载。
HTTP头(示例)如下:
Accept-Ranges:bytes Cache-control:max-age=604800 Connection:keep-alive Content-Length:28026 Content-Type:image/jpg Date:Wed, 16 Oct 2013 06:37:10 GMT Expires:Wed, 23 Oct 2013 14:37:10 +0800 Server:nginx/1.4.1
错误情况下(如ticket非法)返回HTTP错误码404。
依然基于之前的机器人案例进行功能添加,直接看代码。
/// <summary> /// 二维码管理者 /// </summary> public class DimensionalCodeManager { /// <summary> /// 临时二维码地址 /// </summary> /// 使用string.format时,报:字符串格式错误,因为其中有{ //private const string TEMP_URL = "{\"expire_seconds\": 1800, \"action_name\": \"QR_SCENE\", \"action_info\": {\"scene\": {\"scene_id\": {0}}}}"; /// <summary> /// 解决办法,将原有字符串中的一个{用两个{代替 /// </summary> private const string TEMP_JSON_DATA = "{{\"expire_seconds\": 1800, \"action_name\": \"QR_SCENE\", \"action_info\": {{\"scene\": {{\"scene_id\": {0}}}}}}}"; /// <summary> /// 永久二维码地址 /// </summary> private const string PERMANENT_URL = "{{\"action_name\": \"QR_LIMIT_SCENE\", \"action_info\": {{\"scene\": {{\"scene_id\": {0}}}}}}}"; /// <summary> /// 获取ticket的URL /// </summary> private const string GET_TICKET_URL = " https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token={0}"; /// <summary> /// 获取二维码URL /// </summary> private const string GET_CODE_URL = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket={0}"; /// <summary> /// 根据场景ID获取ticket /// </summary> /// <param name="sceneID">场景ID</param> /// <param name="isTemp">是否是临时二维码</param> /// <returns></returns> private static string GetTicket(int sceneID, bool isTemp) { string result = null; string data = string.Empty; if (isTemp) { data = string.Format(TEMP_JSON_DATA, sceneID.ToString()); } else { if (sceneID > 0 && sceneID <= 1000) { data = string.Format(PERMANENT_URL, sceneID); } else { //scene_id不合法 return null; } } string ticketJson = HttpUtility.GetData(string.Format(GET_TICKET_URL,Context.AccessToken)); XDocument doc = XmlUtility.ParseJson(ticketJson, "root"); XElement root = doc.Root; if (root != null) { XElement ticket = root.Element("ticket"); if (ticket != null) { result = ticket.Value; } } return result; } /// <summary> /// 创建临时二维码 /// </summary> /// <param name="sceneID">场景id,int类型</param> /// <returns></returns> public static string GenerateTemp(int sceneID) { string ticket = GetTicket(sceneID,true); if (ticket == null) { return null; } return HttpUtility.GetData(string.Format(GET_CODE_URL, ticket)); } /// <summary> /// 创建临时二维码 /// </summary> /// <param name="sceneID">场景id,int类型</param> /// <returns></returns> public static string GeneratePermanent(int sceneID) { string ticket = GetTicket(sceneID, false); if (ticket == null) { return null; } return HttpUtility.GetData(string.Format(GET_CODE_URL, ticket)); } }
来自 http://www.cnblogs.com/yank/p/3449690.html
尽管处理微信请求的服务器,处于微信服务器的后端,但是安全问题依然不可小觑。
大概总结以下几个方面,希望引起注意。
URL:即为处理微信请求的链接地址
Token:用户身份凭证
申请成为开发者或者修改URL\Token时,微信会通过Get请求访问URL,验证签名,其中需要Token。
过程相当于一次握手,如果握手成功,可进行后续的通信。
成为开发者后,我们也可以进行修改
面临的危险:
1、如URL和Token被破解,直接链接到其他公众账号,直接可以盗用服务。当然对于一些广告类型账号而言,这样无利可图。但是,如果是提供某种应用或者服务的公众账号,免费给其他账号提供服务,势必增加服务端压力,带来一定的风险。
2、如果URL被破解,即使token没被破解。一些不法分子,可能对该URL进行攻击,当然枪打出头鸟,想被黑客盯上也不没那么容易。呵呵
建议:
1、尽量保证服务的URL,与提供消息或者网页没有直接关系。以防止,根据URL推算得出服务URL。
2、可以使用URL重定向,将一些路径信息进行隐藏。
3、在服务中判定请求的来源,是否是微信服务器来的请求。这个可以根据请求的URL来进行判定,对于其他请求不予处理。
4、Token值,尽量复杂一些。
在设置URL或token后,微信都会提交get请求,来访问我们后端服务。验证通过之后,微信其他请求都是通过POST方式提交。
所以在代码中,我们常常会根据请求的方式来判断是否进行签名验证。在之前的例子中,也曾这么用:
/// <summary> /// 处理请求,产生响应 /// </summary> /// <returns></returns> public string Response() { string method = Request.HttpMethod.ToUpper(); //验证签名 if (method == "GET") { if (CheckSignature()) { return Request.QueryString[ECHOSTR]; } else { return "error"; } } //处理消息 if (method == "POST") { return ResponseMsg(); } return "无法处理"; }
尽管微信其他请求是以POST提交的,但是其URL中同样携带了签名信息,我们同样需要进行签名认证。所以为了安全起见,建议每次请求都进行签名认证。
根据这个原理,我们将代码修改如下:
/// <summary> /// 处理请求,产生响应 /// </summary> /// <returns></returns> public string Response() { string method = Request.HttpMethod.ToUpper(); //验证签名 if (method == "GET") { if (CheckSignature()) { return Request.QueryString[ECHOSTR]; } else { return "error"; } } //处理消息 if (method == "POST") { //验证签名 if (CheckSignature()) { return ResponseMsg(); } } return "无法处理"; }
签名算法CheckSignature(),这里不再赘述,具体可见:微信公众账号开发教程(二) 基础框架搭建
通常我们的公众账号都对应一个openId,在处理消息时可以获得。这个openId是固定的,可以根据其判定发送者的身份信息。这种方式,可以很好的过滤无效消息或者欺骗,只有发给我的消息,我才处理。即使URL和Token被人破解,也同样能够保证后端服务,只为我们的公众账号提供服务。
/// <summary> /// 是否是发给我的呢 /// </summary> /// <param name="toUserName">接受者</param> /// <returns>bool</returns> private bool IsSentToMe(string toUserName) { return string.Equals(toUserName,Context.OpenID,StringComparison.OrdinalIgnoreCase); }
如果是服务号,还有一些高级功能,而这些高级功能需要开发者凭据:AppId和AppSecret。
根据AppId和AppSecret可以获得ACCESS_TOKEN,根据ACCESS_TOKEN就可以管理高级功能了,比如:自定义菜单。
ACESS_TOKEN有过期时间,通常为7200S。但是AppId和AppSecret是系统随机生成的,无过期时间,如果需要修改,需要登录微信公众账号管理平台进行重置。
获取Access_Token方式,通过Get请求如下URL
https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=xxxx&secret=xxxx.
获取Access_Token后,就可以操作一些高级接口
比如:
创建自定义菜单,是通过http请求方式:POST(请使用https协议)
https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN
具体实现,见:微信公众账号开发教程(四)自定义菜单
ACCESS_TOKEN是通过get方法获得的,其实不太安全,如果被人窃取,其可以修改自定义菜单的链接,可以将其改为一些广告链接,或者更邪恶的链接,你这服务器直接成了人家的肉机。所以一定要保证服务器的安全。为了安全起见,建议隔一段时间重置AppId和AppSecret(微信公众平台的后台服务页面)。重要的还是要保证允许服务器的安全,具体可以见五。
服务器安全要素很多,比如:保证网络安全、设置防火墙、安装杀毒软件、限制一些端口等等,这跟我们平时服务器安全要求一样,这方面资料很多,这里不再赘述。
来自 http://www.cnblogs.com/yank/p/3449690.html
在微信窗口,输入的信息有限,我们需要将一些信息分多次请求。
比如:在进行用户绑定时,我们需要输入用户的相关信息,比如:用户名、密码,或者姓名、电话号码,服务端验证通过,即可将系统用户与微信用户绑定。
然后,此微信账户就有一定的功能权限了,可以查积分,消费记录等。服务号:招商银行信用卡,就有很多功能。
微信客户端无法缓存信息,而且输入信息有限,需要进行多次请求,在服务端保存当前会话状态。这就需要Session。
本文以用户认证,绑定账号为例,来说明具体处理。
为了更好的说明原理,便于扩展,我们来自己设计Session。当然,这里也可以使用System.Web.SessionState.HttpSessionState,这是Web常用的Session机制。
用于存储会话片段以及相关数据。
class Session { /// <summary> /// 缓存hashtable /// </summary> private static Hashtable mDic = new Hashtable(); /// <summary> /// 添加 /// </summary> /// <param name="key">key</param> /// <param name="value">value</param> public static void Add(string key, object value) { mDic[key] = value; } /// <summary> /// 移除 /// </summary> /// <param name="key">key</param> public static void Remove(string key) { if (Contains(key)) { mDic.Remove(key); } } /// <summary> /// 设置值 /// </summary> /// <param name="key"></param> /// <param name="value"></param> public static void Set(string key, object value) { mDic[key] = value; } /// <summary> /// 获取值 /// </summary> /// <param name="key"></param> /// <returns></returns> public static object Get(string key) { return mDic[key]; } /// <summary> /// 是否含有 /// </summary> /// <param name="key">key</param> /// <returns>bool</returns> public static bool Contains(string key) { return mDic.ContainsKey(key); } /// <summary> /// 清空所有项 /// </summary> public static void Clear() { mDic.Clear(); } }
记录具体的操作类型,标识当前会话的具体操作
/// <summary> /// 操作类型 /// </summary> enum Operation { /// <summary> /// 认证 /// </summary> Auth, /// <summary> /// 添加用户 /// </summary> CreateUser }
用于标识当前操作,处于哪一个阶段,不同阶段做不同的处理。
/// <summary> /// 操作过程 /// </summary> enum OperationStage { /// <summary> /// 默认 /// </summary> Default, /// <summary> /// 第一步 /// </summary> First, /// <summary> /// 第二步 /// </summary> Second, /// <summary> /// 第三步 /// </summary> Third }
缓存记录的项,这里面记录了操作类型、操作步骤以及会话对象。为了便于进行Session管理,还增加了最后访问时间,是否自动清除标识。
class SessionItem { /// <summary> /// 操作类型 /// </summary> public Operation Oper { get; set; } /// <summary> /// 当前步骤 /// </summary> public OperationStage Stage { get; set; } /// <summary> /// 数据对象 /// </summary> public object Data { get; set; } /// <summary> /// 是否自动删除 /// </summary> public bool AutoRemove { get; set; } /// <summary> /// 最后更新时间 /// </summary> public DateTime UpdateTime { get; set; } }
这个对象,记录用户在会话过程中,录入的相关信息。也是作为业务处理数据提供对象。
class AuthSessionItem { /// <summary> /// 用户名 /// </summary> public string FromUserName { get; set; } /// <summary> /// 账号 /// </summary> public string Code { get; set; } /// <summary> /// 唯一标识 /// </summary> public string ID { get; set; } }
1)开始进入认证,根据认证关键字进行标识,启动会话,并缓存相关数据
2)提示录入个人账号信息
3)微信用户录入个人账号,服务端记录账号信息,并提示录入员工卡号
4)微信用户录入卡号信息,服务端记录卡号信息,并调用具体的认证逻辑
5)用户认证通过,绑定微信OpenId,提示成功绑定信息,并清除会话。
在认证过程中,需要对用户录入信息进行合法性验证,而且在会话过程中,支持用户退出当前操作。
/// <summary> /// 认证用户信息 /// </summary> /// <param name="tm"></param> /// <returns></returns> private bool Auth(TextMessage tm, ref string response) { SessionItem sessionItem = null; if (string.Equals(tm.Content, "Auth", StringComparison.OrdinalIgnoreCase)) { //检查是否已经认证,业务组件验证 if (UserManager.IsAuth(tm.FromUserName)) { //如果已经认证,提示 tm.Content = "您已经认证过了,无需再次认证!"; } else { AuthSessionItem authSessionItem = new AuthSessionItem(); authSessionItem.FromUserName = tm.FromUserName; sessionItem.Oper = Operation.Auth; sessionItem.Stage = OperationStage.First; sessionItem.Data = authSessionItem; Session.Set(tm.FromUserName, sessionItem); //输入账号,并将数据和步骤,写入缓存 tm.Content = "请输入您的个人账号"; } response = ResponseText(tm); return false; } //从Session获取用户信息 sessionItem = Session.Get(tm.FromUserName) as SessionItem; //如果会话存在,且当前操作为用户认证 if (sessionItem != null && sessionItem.Oper == Operation.Auth) { if (sessionItem.Stage == OperationStage.First) { tm.Content = tm.Content.Trim(); if (string.IsNullOrEmpty(tm.Content) || tm.Content.Length > 20) { tm.Content = "输入的个人账号不合法,请重新输入。"; response = ResponseText(tm); return false; } AuthSessionItem authSessionItem = sessionItem.Data as AuthSessionItem; if (authSessionItem != null) { authSessionItem.Code = tm.Content; } //更新缓存 sessionItem.Stage = OperationStage.Second; Session.Set(tm.FromUserName, sessionItem); tm.Content = "请输入您的员工卡号!\n退出认证请输入Exit。"; response = ResponseText(tm); } else if (sessionItem.Stage == OperationStage.Second) { string cardNum = null; if (!Common.TryConvertToCardNum(tm.Content, out cardNum)) { tm.Content = "员工卡号不合法,请重新输入。\n退出认证请输入Exit。"; response = ResponseText(tm); return false; } AuthSessionItem authSessionItem = sessionItem.Data as AuthSessionItem; if (authSessionItem != null) { authSessionItem.ID = cardNum; } //认证 string message; if (UserManager.Authenticate(authSessionItem, out message)) { tm.Content = "祝贺您,已经认证成功,可以使用通讯录的查询功能呢。"; //清理缓存 Session.Remove(tm.FromUserName); response = ResponseText(tm); return true; } else if (!string.IsNullOrEmpty(message)) { tm.Content = message; } else { tm.Content = "您输入的信息有误。\n重新认证请输入:Auth!"; } //过程结束:清理Session Session.Remove(tm.FromUserName); response = ResponseText(tm); return false; } } return false; }
在认证过程中,用户可以通过命令,强制退出当前操作,在退出当前操作时,需要清理会话信息。
/// <summary> /// 退出,并清理Session /// </summary> /// <param name="tm"></param> /// <param name="response"></param> /// <returns></returns> private bool Exit(TextMessage tm, ref string response) { //退出 if (string.Equals(tm.Content, "Exit", StringComparison.OrdinalIgnoreCase)) { //清除Session Session.Remove(tm.FromUserName); tm.Content = "您已退出当前操作,请执行其他操作。"; response = ResponseText(tm); return true; } return false; }
用户认证通过,并绑定微信OpenId,通过OpenId即可查询通讯录、查询个人积分以及消费记录等操作了。用户认证是一个身份认证过程,也是一个用户绑定过程。用户身份认证通过,即可通过微信账号查询具体信息了。这时候业务层可以根据微信分配的OpenId直接查询用户相关信息。
通过这种方法,公众账号,可以通过小小的文本输入框,实现更多、更复杂的业务应用。当然,还是通过提供网页来进行信息录入,更直观便捷。
来自 http://www.cnblogs.com/yank/p/3449690.html
开发了几个微信项目,一直在思考:
如何将微信相关的处理与业务系统联系在一起?
如何做到彼此分离,且易于扩展?
能否开发一套独立的微信服务框架,支持各种业务应用?
支持多种业务应用,我们通过分层的方式来实现。将复杂的系统进行分层,将一些功能或者特有的逻辑进行封装,封装为不同的基础服务或中间件。业务层无需关心底层具体实现,只需进行简单调用、组装,即可支撑强大的业务应用。这样保证了层级独立,也使得系统易于维护和扩展。在一个平台基础上,可以构建多种业务应用。就像建楼房,地基打好了,楼房样式可以多种多样。也好比做菜,各种食材准备好了,可以进行不同搭配组合,就能做出不同的美味。常用的框架结构:MVC、MVVM等。以下就是通常的MVC架构。
但是微信公众平台与以往的项目有所不同。其实现不再基于计算机底层实现。从应用层面讲,需要对业务的上层,即显示层和前端逻辑层、通信层,进行封装,下面才是真正的业务系统。从整体考虑,系统通过设计通用微信服务框架,支持所有业务。微信服务框架的改动,只与微信接口的调整做相关。多个业务系统,公用一套微信服务框架。这里介绍一种实现架构,如有欠缺,欢迎批评指正。
1、由微信服务框架,负责与微信服务器进行交互,包括验证签名、消息处理、消息分发、安全策略、日志处理等。
2、通过服务接口,将微信服务框架与业务逻辑进行分离,通过服务注册,将业务服务注册到微信服务框架。
3、通过微信服务框架的服务分发器,调用具体的业务应用。
4、业务应用,可以全新开发,也可以在已有的业务逻辑基础上,封装相关服务,并提供对应Provider,对微信接口予以支持。
具体框架图如下所示:
仅仅是个人的一些看法。
从一个业务系统长远发展来看,一套业务系统,UI端会很多。特别是移动互联网的发展,许多功能需要移动化,之前的PC端、Web端,已经有一点的局限。
还有云计算的发展和深入,我们的业务服务也可以部署在云端。这些都对传统的信息系统提出了挑战。
如何将业务系统与前端展现相分离?
如何支持多终端?
能否实现一套业务逻辑,多端展现?
无论是开发新系统新应用,还是在已有的系统上扩展,这些问题必须认真考虑。业务逻辑需要更加细化、更加独立,业务逻辑需要从前端抽取出来,为了适应各种终端应用,可能需要加入一些适配层、代理层。尽管难度很大,但是在这个信息化高速发展的时期,如果不顺应潮流,势必被时代所淘汰。所以,变革不可避免。未来信息系统开发框架可能会变为下图所示。这里只是一个简单提纲,以后会就这一点进行专题介绍。
来自 http://www.cnblogs.com/yank/p/3449690.html
为了消除大家对订阅号与服务号的疑问,特总结如下:
功能点 | 介绍 | 订阅号 | 服务号 |
注册 | 注册账号 | 个人信息 | 个人信息和企业相关信息 |
展示 | 在手机端展现方式 | 显示在订阅号文件夹中 | 跟微信好友一样显示 |
收发消息 | 接受和发送消息,包括: 1 文本消息 2 图片消息 3 语音消息 4 视频消息 5 音乐消息 6 图文消息 | 有 | 有 |
事件响应 | 获取关注、取消关注、自定义菜单点击事件,并产生响应 | 有(自定义年菜单点击事件,取决于自定义菜单权限) | 有 |
群发消息 | 向全部客户或指定客户发送消息 | 每天一条 | 每月四条 |
自定义菜单 | 通信画面下端的菜单列表 | 认证后拥有,认证需要企业相关信息 | 无需认证 |
高级接口功能 | 具体功能见后面具体说明 | 无 | 认证后具有此功能 |
官方对别图:
高级接口:
高级接口 | |
订阅号功能(未认证):
认证后的服务号具有的功能:
详细的接口文档说明:
http://mp.weixin.qq.com/wiki/index.php
订阅号微信认证说明:
https://mp.weixin.qq.com/cgi-bin/readtemplate?t=wxverify/faq_tmpl&lang=zh_CN&token=1458699179
来自 http://www.cnblogs.com/yank/p/3651592.html
企业号 | 服务号 | 订阅号 | |
---|---|---|---|
面向人群 | 面向企业,政府、事业单位和非政府组织,实现生产管理、协作运营的移动化。 | 面向企业,政府或组织,用以对用户进行服务。 | 面向媒体和个人提供一种信息传播方式。 |
消息显示方式 | 出现在好友会话列表首层。 | 出现在好友会话列表首层。 | 折叠在订阅号目录中。 |
消息次数限制 | 最高每分钟可群发200次。 | 每月主动发送消息不超过4条。 | 每天群发一条。 |
验证关注者身份 | 通讯录成员可关注。 | 任何微信用户扫码即可关注。 | 任何微信用户扫码即可关注。 |
消息保密 | 消息可转发、分享。 支持保密消息,防成员转发。 | 消息可转发、分享。 | 消息可转发、分享。 |
高级接口权限 | 支持 | 支持 | 不支持 |
定制应用 | 可根据需要定制应用,多个应用聚合成一个企业号 | 不支持,新增服务号需要重新关注。 | 不支持,新增服务号需要重新关注。 |
微信企业号能帮助企业、政府机关、学校、医院等事业单位和非政府组织建立与员工、上下游供应链及内部IT系统间的连接,并能有效地简化管理流程、提高信息的沟通和协同效率、提升对一线员工的服务及管理能力。
基于微信的数亿活跃用户及充分开放的接口,第三方开发者可以为不同企业客户,提供多样且个性化的产品和服务,并能有效提升开发、部署效率、确保应用活跃度。不断增长的企业客户,将持续提升开发者价值。
微信全平台客户端的覆盖和方便灵活的企业号后台管理及连接能力,使得员工无论是在办公室还是奔波于一线,企业信息的传递在时间和空间上将不再有任何的阻碍。
确保信息安全是连接企业的基础。腾讯及微信已建立的业界一流的安全保障体系、高可靠的系统实现机制,以及企业号完善的安全特性,为企业信息安全提供了全方位的安全保障。
企业号提供了丰富的功能帮助企业便捷地实现基本的沟通与协同管理。同时不断完善开放的接口,也帮助第三方开发商快速高效地为企业提供更多的个性化应用。