【轮子狂魔】WeChatAPI 开源系统架构详解

如果使用WeChatAPI,它扮演着什么样的角色?

从图中我们可以看到主要分为3个部分:

1.业务系统

2.WeChatAPI:

  WeChatWebAPI,主要是接收微信服务器请求;

  WeChatAPI Service:主要是提供一些微信基础操作给业务系统使用。(这里有个问题,目前规划是不包含回调业务系统的。当然这并不是无法扩展的。)

3.微信服务器

 

项目结构

1.Test:测试目录

  1.1UnitTest:单元测试目录

    WeChatService.Test:微信服务测试

2.UI:界面目录

  WeChatWebAPI:微信网页接口,接收微信服务器请求

3.Server:服务端目录

  Business:主要是通过触发Event来执行Commands,隔离底层业务逻辑。

  Commands:真正的微信业务指令,在此层与微信服务器交互。

  Contracts:提供微信服务接口契约。

  Events:定义微信业务事件。

  Models:定义数据契约、微信交互对象、枚举等。

4.Insfrastructure:基础设施目录

  4.1Lib:类库目录

    CommonLib:通用辅助类库

    WCFLib:WCF辅助类库

  4.2Plugins:插件目录

    DispatchchingCenter:调度中心负责映射Event和Command的关系,并提供一些通用业务逻辑的处理。

5.Publish:发布目录(目前还没有做,这里将会做成Windows服务,主要是真正发布时用的环境)

6.DevEnvironment:开发环境目录(主要是针对开发,方便调试)

  DevHost:开发主机,可直接针对此项目进行调试。

 

时序图

 

实现一个接口:自定义菜单查询

微信API传送门:http://mp.weixin.qq.com/wiki/16/ff9b7b85220e1396ffa16794a9d95adc.html

  分析微信接口

 1. http请求:GET,地址为:https://api.weixin.qq.com/cgi-bin/menu/get?access_token=ACCESS_TOKEN

 2.请求参数只有一个 access_token

 3.返回参数,这个微信偷懒了,可以看创建菜单接口里很清晰 http://mp.weixin.qq.com/wiki/13/43de8269be54a0a6f64413e4dfa94f39.html 

  创建Model

 根据分析接口得到一个结果,发送请求时是不需要新的实体,而返回结果是需要的。

BaseMenu.cs

 1     /// <summary>
 2     /// 菜单基类
 3     /// </summary>
 4     [DataContract(Name = "menu")]
 5     [KnownType(typeof(ClickMenu))]
 6     [KnownType(typeof(DropDownMenu))]
 7     [KnownType(typeof(ViewMenu))]
 8     public class BaseMenu
 9     {
10         /// <summary>
11         /// 菜单名称
12         /// </summary>
13         [DataMember(Name = "name")]
14         public string Name { get; set; }
15     }
View Code

MenuList.cs

 1     /// <summary>
 2     /// 菜单列表
 3     /// </summary>
 4     [DataContract(Name = "menu_list")]
 5     public class MenuList
 6     {
 7         /// <summary>
 8         /// 菜单集合
 9         /// </summary>
10         [DataMember(Name = "button")]
11         public List<BaseMenu> MenuSet { get; set; }
12     }
View Code

DropDownMenu.cs

 1     /// <summary>
 2     /// 下拉菜单
 3     /// </summary>
 4     [DataContract(Name = "drop_down_menu")]
 5     public class DropDownMenu : BaseMenu
 6     {
 7         /// <summary>
 8         /// 子菜单
 9         /// </summary>
10         [DataMember(Name = "sub_button")]
11         public List<BaseMenu> MenuSet { get; set; }
12     }
View Code

ViewMenu.cs

 1     /// <summary>
 2     /// 跳转URL菜单
 3     /// </summary>
 4     [DataContract(Name = "view_menu")]
 5     public class ViewMenu : BaseMenu
 6     {
 7         /// <summary>
 8         /// 类型
 9         /// </summary>
10         [DataMember(Name = "type")]
11         public string Type { get { return "view"; } set { } }
12 
13         /// <summary>
14         /// 跳转的URL
15         /// </summary>
16         [DataMember(Name = "url")]
17         public string Url { get; set; }
18     }
View Code

ClickMenu.cs

 1     /// <summary>
 2     /// 点击推事件菜单
 3     /// </summary>
 4     [DataContract(Name = "click_menu")]
 5     public class ClickMenu : BaseMenu
 6     {
 7         /// <summary>
 8         /// 类型
 9         /// </summary>
10         [DataMember(Name = "type")]
11         public string Type { get { return "click"; } set { } }
12 
13         /// <summary>
14         /// 事件关键字
15         /// </summary>
16         [DataMember(Name = "key")]
17         public string Key { get; set; }
18     }
View Code

需要注意两点:

1. DataContract(Name=xxx) 和 DataMember(Name=xxx) 其实是告诉序列化器,序列化后的参数名。因为C#的命名规则跟微信的不一样,所以为了方便C#开发,我保持了原有的命名规则,通过这种方式在序列化时改成微信要求的参数名。

2. BaseMenu的[KnowType(typeof(xxx))] ,这个是因为在接口定义上并没有细化到 DorpDownMenu、ViewMenu、ClickMenu,而是抽离出一个基类来操作的,为了引用时可以感知到派生类,需要增加KnowType特性,这是WCF的事情,不详说了。

  创建Event

 GetMenuEvent.cs

1     /// <summary>
2     /// 查询菜单事件
3     /// </summary>
4     public class GetMenuEvent : DispatchEvent, IAccessTokenAuth
5     {
6         public string AccessToken { get; set; }
7 
8         public MenuList MenuList { get; set; }
9     }
View Code

通过上面分析微信接口我们知道,参数应该只有一个AccessToken,为什么会多一个MenuList?

这个问题之前文章有写到,简单提一下,是为了Commands直接操作引用类型,把值从GetMenuEvent类传回来。

 

DispatchEvent:这个是告诉Dispatcher,当前类是一个调度事件。这样Dispatcher就会在你激活这个事件时去找对应的Command。

IAccessTokenAuth:调度器执行DispatchHandler之前会执行BeforeActive,从而激活AccessTokenCommand.FillAccessToken来帮助填充AccessToken。这一块之前也有说过。

  实现Command

 1.添加一个CommandRequest,指定访问的URL

MenuCommandRequest.cs

 1     /// <summary>
 2     /// 查询菜单命令请求
 3     /// </summary>
 4     public class GetMenuCommandRequest : BaseCommandRequest
 5     {
 6         public GetMenuCommandRequest(string accessToken)
 7         {
 8             this.Method = HttpMethod.GET;
 9             this.URL = string.Format("https://api.weixin.qq.com/cgi-bin/menu/get?access_token={0}", accessToken);
10         }
11     }
View Code

2.添加一个Command,实现GetMenuEvent

 1         /// <summary>
 2         /// 查询菜单
 3         /// </summary>
 4         /// <param name="e"></param>
 5         [DispatchHandler(typeof(GetMenuEvent))]
 6         public void GetMenu(GetMenuEvent e)
 7         {
 8             var commandRequest = new GetMenuCommandRequest(e.AccessToken);
 9 
10             e.MenuList = CommandHelper.GetWeChatResponseObject<Models.Menu.MenuList>(commandRequest);
11         }
View Code

 

  创建Business

 MenuOperation.cs

1         public MenuList GetMenu()
2         {
3             var getMenuEvent = new GetMenuEvent();
4 
5             Dispatcher.ActiveEvent(getMenuEvent);
6 
7             return getMenuEvent.MenuList;
8         }
View Code

 

  创建Service Contract

 IMenuService.cs

1         [OperationContract]
2         [FaultContractAttribute(typeof(FaultMessage))]
3         MenuList GetMenu();
View Code

WeChatService.cs

1         public Models.Menu.MenuList GetMenu()
2         {
3             return new Businesses.Menu.MenuOperation().GetMenu();
4         }
View Code

这个类库关系到你的服务是否真正的提供出去。因为这里使用的是WCF,Copy我的类即可,如果研究WCF的话可自行搜索相关教程。

  修改配置文件

因为我并没有新增一个新的服务契约接口类,所以此时并不需要增加下面的代码,只是点明一下是哪里让它生效的而已。

DevHost项目的app.config

1         <endpoint address="http://localhost:15000/WeChatService/Menu"
2                   binding="basicHttpBinding"
3                   bindingConfiguration="WeChatBinding"
4                   contract="Contracts.Interfaces.Menu.IMenuService" >
5         </endpoint>  
View Code

 

PS:这个例子流程上没什么大问题,就是json序列化成menu的时候为null,因为赶着出这篇博客,所以还没修复这个问题。

等我有空把这个修复一下。大概定位到问题是我这里抽象了一个BaseMenu,然后序列化器不识别。

 

你是不是跟我们一样,对编程还充满激情?

 我可以做这些事情是因为我对知识的敬畏,越了解越可怕。

 但我不畏惧,因为这探索知识的路上有我、有你、有他们。

 如果你想加入我们,可以加我们的群:7424099

 最后,源码传送门:http://git.oschina.net/doddgu/WeChatAPI

 最后的最后,再次提示:这并不是一个完整的项目!!!

 

PS:时序图里出现PrintSQL,这个其实不存在的,因为Copy了之前的文档,图片已经上传了就没改。

posted @ 2014-12-25 17:24  寻找和谐  阅读(5113)  评论(15编辑  收藏  举报