29 December 2014

前言

继Rails之后,为何Node火了,这跟移动互联网有和关系? 想要成功,得审时度势,凡事多问几个为毛。最近,我觉得web开发 存在这样几点变化,移动,响应式,单页面应用程序,API,Node。API开发的需求是由移动互联网带动的,Node适合API的开发。海量的 数据,分布式数据库,NoSQL,云端。

平台割据和诸侯混战已结束,互联网进去个性化时代,依附大平台的生存和创新将是今后的主旋律。所以,我也决定依附想微信这样的 平台。

微信平台简介

特点: 私密性和交流

功能: 群发消息, 自定义回复规则,开发模式(分析消息)

使用类似: 企业移动门户,新闻咨询,娱乐,社交,游戏

订阅号和服务号区别,订阅为个人,服务为公司

自动回复和关键字自动回复。公众号的强大之处: 提供的接口。

云计算,IaaS和PaaS,SAE和BAE属于应用服务器引擎,与IaaS相比,灵活性和可配性不强。要用还是用云服务器比较好。

此外,如果应用为PHP环境的话,使用SAE和BAE都挺不错的,PHP果然还是web开发第一语言,可惜我是学Ruby的。

当前实现

设想,使用weixin_rails_middleware, 从而替换现有的实现。不过,对现有 实现进行总结一下,是个不错的注意。

微信的接口是放置在API项目中的,Api项目中总共存在三个东西:ios的接口,android的接口,以及微信的接口。其中,ios的接口和android的接口 内容完全一样,这么大的重复,我个人是难以忍受的,但前辈说,是为了预备以后的修改和不同,没让我改。

接口这东西,就是一个URL,接受写参数,并返回某些响应(XML亦或是JSON数据)。将三个不同的接口,其实就是三组不同的URL。URL是通过Rails的 路由分发的,所以,我看到了这样的做法:

▾ config/
  ▾ routes/
      android_route.rb
      ios_route.rb
      wx_route.rb
    routes.rb

这里,运用了Ruby开放类的特征,routes/目录下的三个文件,都是对routes.rb中的类的打开补充。ios和android是以命名空间的方式来分隔URL,微信的接口 和常规的rails路由稍微有些不同,具体的内容如下:

TestApi::Application.routes.draw do
  get "/api/winxin/cert"  => "weixin/auths#auth"

  scope "/", via: :post do
    match "api/weixin/cert" => "weixin/auths#info_meals",      constraints: WxRouter.new("event", event: "CLICK", event_key: "info_meals")
    match "api/weixin/cert" => "weixin/users#subscribe_tags",  constraints: WxRouter.new("text",  content: /^tag/i)    
    match "api/weixin/cert" => "weixin/auths#subscribe",       constraints: WxRouter.new("event", event: "subscribe")
    match "api/weixin/cert" => "weixin/auths#user_scan",       constraints: WxRouter.new("event", event: "SCAN",  event_key: /\d+/)
    match "api/weixin/cert" => "weixin/auths#reply_any",       constraints: lambda {|r| r.params} # 接受参代码参数
  end
end
# WxRouter类定义, 用来分析和解析类型
class WxRouter
  def initialize(msg_type="text", options={})
    @message_type = msg_type
    @event_key    = options[:event_key] if options[:event_key]
    @wx_event     = options[:event]     if options[:event]
    @content      = options[:content]   if options[:content]
  end

  def matches?(request)
    xml_data = request.params[:xml]
    if xml_data && xml_data.is_a?(Hash)
      (return false unless @message_type == request.params[:xml][:MsgType])  if @message_type
      (return false unless @event_key    == request.params[:xml][:EventKey]) if @event_key && @event_key.is_a?(String)
      (return false unless @event_key    =~ request.params[:xml][:EventKey]) if @event_key && @event_key.is_a?(Regexp)
      (return false unless @wx_event     == request.params[:xml][:Event])    if @wx_event
      (return false unless @content      == request.params[:xml][:Content])  if @content && @content.is_a?(String)
      (return false unless @content      =~ request.params[:xml][:Content])  if @content && @content.is_a?(Regexp)
    end
    true
  end
end

仔细阅读和理解这段路由和代码,可以看到,包含了如下的信息:

  • 就开发微信接口而言,微信服务器是本地服务器的客户端,get和post请求都是来自微信服务器。
  • 微信服务器首先通过get验证授权,然后通过post请求,转发用户的消息,且其消息体为XML
  • 消息类型存在: 事件(如菜单点击)和文本

自定义菜单

微信的菜单为 3 × 5的列表,菜单存在多种类型,常见的为click和view(点击的URL之类),最新的微信版本又支持一些 更加高级的菜单。现有实现为如下的类:

class WxMenu
  def self.build
    menu = {
      button: [{
          name: "xxxx",
          sub_button: [{
              type: "click",
              name: "yyyy",
              key:  "info_hot"
            },{
              type: "click",
              name: "zzzz",
              key:  "info_meals"
          }]
        }] #button
    }
    response = JSON.parse  RestClient.post((Settings.wx_create_menu_url % WxAccessToken.token),  menu.to_json, content_type: "application/json")
    STDOUT.puts response.inspect
  end

  def self.clear
    response = JSON.parse  RestClient.get(Settings.wx_delete_menu_url % WxAccessToken.token)
    STDOUT.puts response.inspect
  end
end
# 访问token的实现
class WxAccessToken

  def self.token is_force=false
    Rails.cache.fetch "wx_token", expires_in: 6000, force: is_force, race_condition_ttl: 10, raw: true do
      # 发现access_token每次都会不一样,每次访问,都会使得之前的失效
      JSON.parse(RestClient.get "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=#{Settings.wx_appid}&secret=#{Settings.wx_secret}")["access_token"]
    end
  end

end

这里,该类实现了微信的自定菜单接口的创建和删除,Settings.wx_create_menu_url"https://api.weixin.qq.com/cgi-bin/menu/create?access_token=%s", Settings.wx_delete_menu_url"https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=%s"。post请求是通过RestClient类实现的。

注: 由于个人对接口的理解不到位,导致对如何在测试号中使用自定义菜单的接口不知所措。看了前辈的做法之后,知道,原来是需要自己手动调用方法发post请求过去的啊。

访问token的问题,访问token是每次生成均不相同,没

本地测试

如何测试,是一个很重要的问题,如何利用本地环境测试,是一个极其严肃的问题。

微信官方提供的测试帐号地址: http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login, 本地开发配合ngrok,监听特定的端口,生成http://xxx.ngrok.com的URL。 意外的发现,ngrok生成的随机的URL其实是固定的,也就说对外的接口层是固定的,本地的Rails开发环境和本地的各种项目来回切换,无缝切换。一个字: “真爽”。

利用测试的appID和appsecret,验证URL以及Token,就可以进行本地测试了,这是一个相当不错的测试方案。

问题: 微信的xxx.xml.erb中,数据模型不能使用ActiveRecord原生的对象,仅支持Ruby的数组对象,所以,需要将ActiveRecord的对象转换成数组对象。此外,微信对消息的格式具有严格的要求, 解析不通过,就说无服务。

大事件: Ngrok被强了,成为又一个GFW的牺牲者。然后,我的微信菜单开发的任务就算到头了,可以正大光明的去干其他事情了。

weixin_rails_middleware

以下,是利用weixin_rails_middleware进行相关的探索。

环境准备

从Github上,下载其Rails 3的示例项目,修改Rails的版本,删除Gemfile.lock, 添加.ruby-gemset.ruby-version,实现安装环境隔离,bundle。

备注: 意外的发现Ruby的bundle程序极耗CPU,尤其是在解析包关系依赖时,某CPU一直在正弦曲线(振幅为20% - 100%), 还有,在安装某gem包时,CPU轨迹图非常的杂乱无章。

好不容易配置好了,结果,在配置上appid和token上,有点不知所措,权限验证的URL地址,以及文档中的描述也不太明朗,搞的很不愉快。算了,留个标签,以后再说:

参考文献

  1. 用 Rails 搭建微信公众平台 API
  2. Rails折腾微信 API 的一点小技巧



傲娇的使用Disqus