微信公众平台网址:https://mp.weixin.qq.com/

服务号说明:给企业和组织提供更强大的业务服务与用户管理能力,帮助企业快速实现全新的公众号服务平台。

.NETSDK: Loogn.WeiXinSDK (net2.0源码,下面代码只是大概,不太正确,请自行下载源码)

由于本人用的还是NOKIA-C5,没用过微信,对微信的了解肯定没你多,但公司有需求,只好硬着头皮直接看接口文档了。

看后发现也挺有意思的,一个很有用的作用就是,当用户给公众账号发消息时,程序可以根据用户发的内容自动回复用户,比如给一个物流公司的公众账号发个运单号,

对方自动回复你这个运单号的物流详细,感觉挺酷!为了说明方便,先给出申请好的公众账号信息:

下图为表示上面查看物流详细的消息流程(虚线的编号表示流程的顺序):

微信会向你的URL发送两大类消息:

一是用户的一般消息,如上面用户发的运单号;

二是用户的行为(即文档中说的事件)  ,如用户关注了你的公众账号、扫描了公众账号的二维码、点击了你自定义的菜单等。

你的URL就可以根据收到的消息类型和内容做出回应以实现强大的业务服务,如上面返回的物流详细。消息全部是以XML格式传递,而SDK做的就是把XML转换成.NET对象,以方便你编写业务逻辑。消息的框架类图表示为(点击查看包括子类的全图):

首先有个消息基类,然后是收到的消息(RecEventBaseMsg)和回复的消息(ReplyBaseMsg),上面说了,收到的消息分两大类,即一般消息(RecBaseMsg)和事件消息(EventBaseMsg),收到的消息类型用枚举表示可以是:

其他的类型不说,而当MsgType为Event时,消息便是EventBaseMsg的子类了,所有EventBaseMsg的子类的MsgType都是Event,所以EventBaseMsg类型又有个EventType来区分不同的事件,如果你看过接口文档,你应该知道,它的事件类型对我们判断到底是哪个事件不太友好,扫描二维码事件分了用户已关注和未关注两种情况,已关注时EvenType是scan,未关注时EventType是subscribe,而用户关注事件的EventType也是subscribe,所以SDK里又加了个MyEventType:

现在消息的流程基本清楚了,调用SDK回复消息如下:

using System.Web;
using Loogn.WeiXinSDK;
using Loogn.WeiXinSDK.Message;

namespace WebTest
{
    /// <summary>
    /// 微信->服务器配置URL
    /// </summary>
    public class WeiXinAPI : IHttpHandler
    {
        static string Token = "Token";//这里是Token不是Access_Token
        public void ProcessRequest(HttpContext context)
        {
            context.Response.ContentType = "text/plain";
            var signature = context.Request["signature"];
            var timestamp = context.Request["timestamp"];
            var nonce = context.Request["nonce"];
            if (WeiXin.CheckSignature(signature, timestamp, nonce, Token))//验证是微信给你发的消息
            {
                //根据注册的消息、事件处理程序回复,
                //如果得到没用注册的消息或事件,会返回ReplyEmptyMsg.Instance,即GetXML()为string.Empty,符合微信的要求
                var replyMsg = WeiXin.ReplyMsg();
                var xml = replyMsg.GetXML();
                //WriteLog(xml); //这里可以查看回复的XML消息
                context.Response.Write(xml);
            }
            else
            {
                context.Response.Write("fuck you!");
            }
        }
        static WeiXinAPI()
        {
            WeiXin.ConfigGlobalCredential("appid", "appSecret");
            //注册一个消息处理程序,当用户发"ABC",你回复“你说:ABC”;
            WeiXin.RegisterMsgHandler<RecTextMsg>((msg) =>
            {
                return new ReplyTextMsg
                {
                    Content = "你说:" + msg.Content
                    //FromUserName = msg.ToUserName,  默认就是这样,不用设置!
                    //ToUserName = msg.FromUserName,  默认就是这样,不用设置!
                    //CreateTime = DateTime.Now.Ticks     默认就是这样,不用设置!
                };
            });
            //注册一个用户关注的事件处理程序,当用户关注你的公众账号时,你回复“Hello!”
            WeiXin.RegisterEventHandler<EventAttendMsg>((msg) =>
            {
                return new ReplyTextMsg
                {
                    Content = "Hello !"
                };
            });
            //还可以继续注册你感兴趣的消息、事件处理程序
        }
        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }
}

SDK包含了除(OAuth2.0网页授权)的所有接口的封装,类名及方法名都很明显,这里就不一一演示,有兴趣的朋友可以下载dll自行测试,这是一张付费认证过的接口图:

接下来谈谈实现的几个细节:

一、凭据(access_token)过期

“access_token是公众号的全局唯一票据,公众号调用各接口时都需使用access_token。正常情况下access_token有效期为7200秒,重复获取将导致上次获取的access_token失效。公众号可以使用AppID和AppSecret调用本接口来获取access_token。AppID和AppSecret可在开发模式中获得(需要已经成为开发者,且帐号没有异常状态)。”

根据文档上说的,我们可以想到用缓存(不可能每次用每次取吧!),缓存代码是很简单的,主要是在这种情况下要能想到用缓存,下面是非完整代码:

using System;
using System.Collections.Generic;

namespace Loogn.WeiXinSDK
{
    /// <summary>
    /// 凭据
    /// </summary>
    [Serializable]
    class Credential
    {
        public string access_token { get; set; }
        /// <summary>
        /// 过期秒数
        /// </summary>
        public int expires_in { get; set; }

        [NonSerialized]
        public DateTime add_time;

        static Dictionary<string, Credential> creds = new Dictionary<string, Credential>();
        static string TokenUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={0}&secret={1}";
        internal static Credential GetCredential(string appId, string appSecret)
        {
            Credential cred = null;
            if (creds.TryGetValue(appId, out cred))
            {
                ) < DateTime.Now)
                {
                    creds.Remove(appId);
                    cred = null;
                }
                else
                {
                    return cred;
                }
            }
            var json = Util.HttpGet2(string.Format(TokenUrl, appId, appSecret));
            cred = Util.JsonTo<Credential>(json);            creds[appId] = cred;
            return cred;
        }
    }
}

二、错误码信息

上面说到得到凭据的代码不完整就是因为没有处理可能返回的错误码,微信错误码以json格式返回,如:

{"errcode":40013,"errmsg":"invalid appid"}

大部分由我们主动调用的接口都有可能返回错误码,错误码格式与正常返回的数据格式完全不一样,在SDK里,我是这样处理的,先定义好错误码的模型类,我这里叫ReturnCode,是因为错误码里还包含一个{"errcode":0,"errmsg":"ok"}的请求成功的情况:

    public class ReturnCode
    {
        public int errcode { get; set; }
        public string errmsg { get; set; }        public override string ToString()
        {
            return "{ \"errcode\":" + errcode + ",\"errmsg\":\"" + errmsg + "\"}";
        }
    }

定义有错误码的返回消息类时我们就可以包含一个ReturnCode类型的属性了,如创建二维码接口:

    public class QRCodeTicket
    {
        public string ticket { get; set; }
        public int expire_seconds { get; set; }

        public ReturnCode error { get; set; }
    }

从返回的json到QRCodeTicket对象的代码大概就是这样(其他的也是类似):

            var json = Util.HttpPost2(url, data);
            )
            {
                return Util.JsonTo<QRCodeTicket>(json);
            }
            else
            {
                QRCodeTicket tk = new QRCodeTicket();
                tk.error = Util.JsonTo<ReturnCode>(json);
                return tk;
            }

所以用SDK调用接口后,得到的对象就可轻松判断了:

            );
            if (qrcode.error == null)
            {
                //返回错误,可以用qrcode.error查看错误消息
            }
            else
            {
                //返回正确,可以操作qrcode.ticket
            }

三、反序列化

微信接口返回的json有时候对我们映射到对象并不太直接(json格式太灵活了!),比如创建分组成功后返回的json:

{
    "group": {
        ,
        "name": "test"
    }
}

如果想直接用json通过反序列化得到对象,那么这个对象的类的定义有可能会是这样:

    public class GroupInfo
    {
        public Group group { get; set; }
        public class Group
        {
            public int id { get; set; }
            public string name { get; set; }
        }
    }

访问的时候也会是gp.group.name,所以我说不太直接,我们想要的类的定义肯定是只有上面那个子类的样子:

    public class GroupInfo
    {
            public int id { get; set; }
            public string name { get; set; }
    }

如果微信接口返回的是这样:

    {
        ,
        "name": "test"
    }

就再好不过了,但人家的代码,我们修改不了,我们只有自己想办法.

1,要简单类,2不手动分析json(如正则),3,不想多定义一个类,你有想到很好的方法吗?如果有可以回复给我,而我选择用字典来做中间转换。

因为基本所有的json格式都可以反序列化为字典(嵌套字典,嵌套字典集合等),比如上面微信返回的json就可以用以下的类型来表示:

Dictionary<string, Dictionary<string, object>>

json--->dict--->GroupInfo

var dict = Util.JsonTo<Dictionary<string, Dictionary<string, object>>>(json);
var gi = new GroupInfo();
var gpdict = dict["group"];
gi.id = Convert.ToInt32(gpdict["id"]);
gi.name = gpdict["name"].ToString();

四、消息处理的优化

"万物简单为美",我就是一个非常非常喜欢简单的程序员。还记得最开始的那个消息(事件属于消息,这里统称为消息)处理吧,我感觉是很简单的,需要处理哪个消息就注册哪个消息的处理程序。但一开始的时候不是这样的,开始的时候要手动判断消息类型,就像:

using System.Web;
using Loogn.WeiXinSDK;
using Loogn.WeiXinSDK.Message;

namespace WebTest
{
    /// <summary>
    /// 微信->服务器配置URL
    /// </summary>
    public class WeiXinAPI : IHttpHandler
    {
        static string Token = "Token";//这里是Token不是Access_Token
        public void ProcessRequest(HttpContext context)
        {
            context.Response.ContentType = "text/plain";
            var signature = context.Request["signature"];
            var timestamp = context.Request["timestamp"];
            var nonce = context.Request["nonce"];
            if (WeiXin.CheckSignature(signature, timestamp, nonce, Token))//验证是微信给你发的消息
            {
                var replyMsg = WeiXin.ReplyMsg((recEvtMsg) =>
                {
                    switch (recEvtMsg.MsgType)
                    {
                        case MsgType.text:
                            {
                                var msg = recEvtMsg as RecTextMsg; //这里要转型,麻烦
                                return new ReplyTextMsg
                                {
                                    Content = "你说:" + msg.Content
                                };
                            }
                        case MsgType.Event:
                            {
                                var evtMsg = recEvtMsg as EventBaseMsg;//这里要转型到事件消息的基本,麻烦
                                switch (evtMsg.MyEventType)
                                {
                                    case MyEventType.Attend:
                                        var msg = evtMsg as EventAttendMsg;//这个例子不需要这行代码,但其他要用消息内容还是要转型,麻烦
                                        return new ReplyTextMsg
                                        {
                                            Content = "Hello !"
                                        };
                                    default:
                                        break;
                                }
                                break;
                            }
                        default:
                            break;
                    }
                    return ReplyEmptyMsg.Instance;
                    //嵌套switch,而且每个case都有好几个,这也不优雅
                });
                var xml = replyMsg.GetXML();
                //WriteLog(xml); //这里可以查看回复的XML消息
                context.Response.Write(xml);
            }
            else
            {
                context.Response.Write("fuck you!");
            }
        }
        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }
}

做优化的时候,先是试着看能不能在MsgType和MyEventType上做文章,比如注册时传入MsgType和处理程序(lamba)两个参数:

public static void RegisterMsgHandler(MsgType type, Func<RecEventBaseMsg, ReplyBaseMsg> handler)
{
    //add handler
}

这样的确是可以行的通的,但是在调用SDK注册的时候还是要手动转换类型:

 WeiXin.RegisterMsgHandler(MsgType.text, (recEvtMsg) =>
 {
     var msg = recEvtMsg as RecTextMsg;
     return new ReplyTextMsg { Content = "你说:" + msg.Content };
 });

那么能不能每个子类型写一个呢?

    public static void RegisterMsgHandler(MsgType type, Func<RecTextMsg, ReplyBaseMsg> handler)
    {
        //add handler
    }
    public static void RegisterMsgHandler(MsgType type, Func<RecImageMsg, ReplyBaseMsg> handler)
    {
        //add handler
    }
    //.............

定义是可以的,来看看调用:

//可以
RegisterMsgHandler(MsgType.text, new Func<RecTextMsg, ReplyBaseMsg>((msg) =>
{
    return new ReplyTextMsg { Content = "你说:" + msg.Content };
}));
//可以
RegisterMsgHandler(MsgType.text, new Func<RecImageMsg, ReplyBaseMsg>((msg) =>
{
    return new ReplyTextMsg { Content = "你发的图片:" + msg.PicUrl };
}));
//可以,注意这里msg的智能提示是RecTextMsg类型
RegisterMsgHandler(MsgType.text, (msg) =>
{
    return new ReplyTextMsg { Content = "你说:" +msg.Content};
});
//可以,注意这里msg的智能提示还是RecTextMsg类型,但用了类型推断,运行时可以确定是RecImageMsg,所以可以
RegisterMsgHandler(MsgType.text, (msg) =>
{
    return new ReplyTextMsg { Content = "你发的图片:" + msg.PicUrl };
});
//不可以,注意这里msg的智能提示还是RecTextMsg类型,但lamba body里没有用msg的特定子类的属性,类型推断不了,所以调用不明
RegisterMsgHandler(MsgType.text, (msg) =>
{
    return new ReplyTextMsg { Content = "你发了个消息" };
});

从上面调用可知,想用这种方法调用,就不能随意的用lamba表达式,我所不欲也!最后,终于用泛型搞定了

public static void RegisterMsgHandler<TMsg>(Func<TMsg, ReplyBaseMsg> handler) where TMsg : RecBaseMsg
        {
            var type = typeof(TMsg);
            var key = string.Empty;
            if (type == typeof(RecTextMsg))
            {
                key = MsgType.text.ToString();
            }
            else if (type == typeof(RecImageMsg))
            {
                key = MsgType.image.ToString();
            }
            else if (type == typeof(RecLinkMsg))
            {
                key = MsgType.link.ToString();
            }
            else if (type == typeof(RecLocationMsg))
            {
                key = MsgType.location.ToString();
            }
            else if (type == typeof(RecVideoMsg))
            {
                key = MsgType.video.ToString();
            }
            else if (type == typeof(RecVoiceMsg))
            {
                key = MsgType.voice.ToString();
            }
            else
            {
                return;
            }
            m_msgHandlers[key] = (Func<RecEventBaseMsg, ReplyBaseMsg>)handler;
        }

经过这样的变换,我们才可以像开始那样用简洁的lamba表达式注册。

微信公众平台SDK的更多相关文章

  1. 微信公众平台SDK Python

    微信公众平台SDK 项目背景 从2014年开始玩微信公众平台,试用过其中大多数的功能,如:消息回复.自定义菜单.公众号中的支付,页面授权等.之前的程序中都是直接调用公众平台的接口,这样复用功能无法实现 ...

  2. 国内流行的两大开源.net微信公众平台SDK对比分析

    最近忙于微信周边的开发 难免手痒去搜索一下有没有相关的sdk直接拿来使 还真发现了不少 这里总结两个看起来比较不错的.net平台下基于C#语言开发的SDK 一个强大一个小巧 (1) Senparc.W ...

  3. 国内流行的开源.net微信公众平台SDK对比分析

    一.引言 目前微信公众平台正如火如荼的进行中,微信虽然在海外市场不敌WhatsApp,但是已经俘获了国内绝大部分用户的心.作为国内最大的,超级"app",微信已算是成功问鼎了.公众 ...

  4. 【微信公众平台SDK(链式调用)】经过半个月的迭代,今天抽空写了个Demo

    这个项目是在实际开发中逐渐完善的,开发过程基于ASP.Net Core 1.1,实际生成会兼容Net4.5. 写有完善的代码提示,怎么用就不多做解释了,引用好实例中的命名空间基本上就可以通过智能提示了 ...

  5. 微信公众平台SDK for node

    实现了下面特性: 1.开启开发人员模式 2.解析微信请求參数 3.验证消息来源 4.被动回复文字消息 5.被动回复图文消息 6.获取access_token 7.创建自己定义菜单 地址:wechat ...

  6. Senparc.Weixin.MP SDK 微信公众平台开发教程(十三):地图相关接口说明

    为了方便大家开发LBS应用,SDK对常用计算公式,以及百度和谷歌的地图接口做了封装. 常用计算: 用于计算2个坐标点之间的直线距离:Senparc.Weixin.MP.Helpers.Distance ...

  7. ASP.NET MVC5+EF6+EasyUI 后台管理系统(70)-微信公众平台开发-成为开发者

    系列目录 前言: 一.阅读这段系列之前,你必须花半天时间大致阅读微信公众平台的API文档,我尽量以简短快速的语言与大家分享一个过程 二.借助微信公众平台SDK Senparc.Weixin for C ...

  8. Senparc.Weixin.MP SDK 微信公众平台开发教程(十八):Web代理功能

    在Senparc.Weixin.dll v4.5.7版本开始,我们提供了Web代理功能,以方便在受限制的局域网内的应用可以顺利调用接口. 有关的修改都在Senparc.Weixin/Utilities ...

  9. Senparc.Weixin.MP SDK 微信公众平台开发教程(十七):个性化菜单接口说明

    前不久微信上线了个性化菜单接口,Senparc.Weixin SDK也已经同步更新. 本次更新升级Senparc.Weixin.MP版本到v13.5.2,依赖Senparc.Weixin版本4.5.4 ...

随机推荐

  1. RTP、RTCP协议学习-2015.04.15

    最近做视频编解码部分,传输采用RTP协议.对学习做个记录 1.简介 实时传输协议(Real-time Transport Protocol或简写RTP)是一个网络传输协议,它是由IETF的多媒体传输工 ...

  2. hibernate.xml文件详解

    <!--标准的XML文件的起始行,version='1.0'表明XML的版本,encoding='gb2312'表明XML文件的编码方式--> <?xml version='1.0' ...

  3. 十一、Android学习第十天——项目开始(转)

    (转自:http://wenku.baidu.com/view/af39b3164431b90d6c85c72f.html) 十一.Android学习第十天——项目开始 Android知识点的学习告一 ...

  4. [转]jQuery: how to get which button was clicked upon form submission?

    本文转自:http://stackoverflow.com/questions/5721724/jquery-how-to-get-which-button-was-clicked-upon-form ...

  5. JAVA中关于并发的一些理解

    一,JAVA线程是如何实现的? 同步,涉及到多线程操作,那在JAVA中线程是如何实现的呢? 操作系统中讲到,线程的实现(线程模型)主要有三种方式: ①使用内核线程实现 ②使用用户线程实现 ③使用用户线 ...

  6. Github 下载单个文件

    前言 通常我们对Github上的项目都是完整的clone下来,但对于某些大型项目,或者某些时候只需要其中一两个文件,那该怎么办呢? 本文就是教你如何在github上下载单个文件. 方法 1.找到需要下 ...

  7. 【Unity】矩阵运算

    http://www.cnblogs.com/wywnet/p/3585075.html Vector3:  Unity3D中Vector3类定义(只写有用的):  属性:  x,y,z       ...

  8. sublime text2安装package control的方法

    Package Control 方法一:在线安装,首先打开 Ctrl + ~,输入如下的代码: import urllib2,os; pf='Package Control.sublime-packa ...

  9. NPM 如何升级?

  10. 一个screen的简单配置。。

    # Start message startup_message off defencoding utf- encoding utf- utf- shell bash hardstatus always ...