Gem wechat-rails 微信 DSL for ruby

skinnyworm · 2014年04月02日 · 最后由 Thomastar 回复于 2017年11月08日 · 20504 次阅读
本帖已被管理员设置为精华贴

Wechat Rails

Build Status Code Climate Code Coverage Gem Version

看了 Github 上几个 ruby 的微信的 Gems, 感觉大多有以下几个问题

  • 实现不够完整,
  • 代码质量有待提高
  • 设计上没有从开发者的 Use case 去考虑,大多数以实现微信的 API 为目标,但实际使用的话不顺手,很多使用场景需要写很多代码
  • 不是很 Ruby way

由于找不到好的方案,决定自己动手写一个。我的目的比较明确,这个 Gem 是帮助开发者方便地在已有的 Rails app 中集成微信的消息机制,同时提供命令行程序帮助日常的维护,比如更新菜单等。目前还没有在正式的生产环境中使用过,欢迎大家吐槽,参与,让这个项目可以成为每个希望集成微信公众平台的开发者的好工具。

以下是项目文档,正在完善中 :P

Wechat-rails 可以帮助开发者方便地在 Rails 环境中集成微信公众平台提供的所有服务,目前微信公众平台提供了以下几种类型的服务。

  • 微信公众平台基本 API, 无需 Web 环境。
  • 消息处理机制,需运行在 Web 环境中。
  • OAuth 2.0 认证机制

Wechat-rails gem 包含了一个命令行程序可以调用各种无需 web 环境的 API。同时它也提供了 Rails Controller 的 responder DSL, 可以帮助开发者方便地在 Rails 应用中集成微信的消息处理机制。如果你的 App 还需要集成微信 OAuth2.0, 你可以考虑omniauth-wechat-oauth2, 这个 gem 可以方便地和 devise 集成提供完整的用户认证。

在使用这个 Gem 前,你需要获得微信 API 的 appid, secret, token。具体情况可以参见http://mp.weixin.qq.com

安装

Using gem install or add to your app's Gemfile:

gem install "wechat-rails"
gem "wechat-rails", git:"https://github.com/skinnyworm/wechat-rails"

配置

命令行程序的配置

要使用命令行程序,你需要在你的 home 目录中创建一个~/.wechat.yml,包含以下内容。其中access_token是存放 access_token 的文件位置。

appid: "my_appid"
secret: "my_secret"
access_token: "/var/tmp/wechat_access_token"

Rails 全局配置

Rails 环境中,你可以在 config 中创建 wechat.yml, 为每个 rails environment 创建不同的配置。

default: &default
  appid: "app_id"
  secret: "app_secret"
  token:  "app_token"
  access_token: "/var/tmp/wechat_access_token"

production: 
  appid: <%= ENV['WECHAT_APPID'] %>
  secret: <%= ENV['WECHAT_APP_SECRET'] %>
  token:   <%= ENV['WECHAT_TOKEN'] %>
  access_token:  <%= ENV['WECHAT_ACCESS_TOKEN'] %>

staging: 
  <<: *default

development: 
  <<: *default

test: 
  <<: *default

Rails 为每个 Responder 配置不同的 appid 和 secret

在个别情况下,你的 app 可能需要处理来自多个公众账号的消息,这时你可以配置多个 responder controller。

class WechatFirstController < ApplicationController
   wechat_responder appid: "app1", secret: "secret1", token: "token1", access_token: Rails.root.join("tmp/access_token1")

   on :text, with:"help", respond: "help content"
end

使用命令行

$ wechat
Wechat commands:
  wechat custom_image [OPENID, IMAGE_PATH]                 # 发送图片客服消息
  wechat custom_music [OPENID, THUMBNAIL_PATH, MUSIC_URL]  # 发送音乐客服消息
  wechat custom_news [OPENID, NEWS_YAML_FILE]              # 发送图文客服消息
  wechat custom_text [OPENID, TEXT_MESSAGE]                # 发送文字客服消息
  wechat custom_video [OPENID, VIDEO_PATH]                 # 发送视频客服消息
  wechat custom_voice [OPENID, VOICE_PATH]                 # 发送语音客服消息
  wechat help [COMMAND]                                    # Describe available commands or one specific command
  wechat media [MEDIA_ID, PATH]                            # 媒体下载
  wechat media_create [MEDIA_TYPE, PATH]                     # 媒体上传
  wechat menu                                              # 当前菜单
  wechat menu_create [MENU_YAML]                           # 创建菜单
  wechat menu_delete                                       # 删除菜单
  wechat user [OPEN_ID]                                    # 查找关注者
  wechat users                                             # 关注者列表

使用场景

以下是几种典型场景的使用方法

#####获取所有用户的 OPENID

$ wechat users

{"total"=>4, "count"=>4, "data"=>{"openid"=>["oCfEht9***********", "oCfEhtwqa***********", "oCfEht9oMCqGo***********", "oCfEht_81H5o2***********"]}, "next_openid"=>"oCfEht_81H5o2***********"}

#####获取用户的信息

$ wechat user "oCfEht9***********"

{"subscribe"=>1, "openid"=>"oCfEht9***********", "nickname"=>"Nickname", "sex"=>1, "language"=>"zh_CN", "city"=>"徐汇", "province"=>"上海", "country"=>"中国", "headimgurl"=>"http://wx.qlogo.cn/mmopen/ajNVdqHZLLBd0SG8NjV3UpXZuiaGGPDcaKHebTKiaTyof*********/0", "subscribe_time"=>1395715239}
获取当前菜单
$ wechat menu

{"menu"=>{"button"=>[{"type"=>"view", "name"=>"保护的", "url"=>"http://***/protected", "sub_button"=>[]}, {"type"=>"view", "name"=>"公开的", "url"=>"http://***", "sub_button"=>[]}]}}

创建菜单

创建菜单需要一个定义菜单内容的 yaml 文件,比如 menu.yaml

button:
  -
    type: "view"
    name: "保护的"
    url: "http://***/protected"
  -
    type: "view"
    name: "公开的"
    url: "http://***"

然后执行命令行

$ wechat menu_create menu.yaml

发送客服图文消息

需定义一个图文消息内容的 yaml 文件,比如 articles.yaml

articles:
 -
  title: "习近平在布鲁日欧洲学院演讲"
  description: "新华网比利时布鲁日4月1日电 国家主席习近平1日在比利时布鲁日欧洲学院发表重要演讲"
  url: "http://news.sina.com.cn/c/2014-04-01/232629843387.shtml"
  pic_url: "http://i3.sinaimg.cn/dy/c/2014-04-01/1396366518_bYays1.jpg"

然后执行命令行

$ wechat custom_news oCfEht9oM*********** articles.yml 

Rails Responder Controller DSL

为了在 Rails app 中响应用户的消息,开发者需要创建一个 wechat responder controller. 首先在 router 中定义

resource :wechat, only:[:show, :create]

然后创建 Controller class, 例如


class WechatsController < ApplicationController
  wechat_responder

  # 默认的文字信息responder
  on :text do |request, content|
    request.reply.text "echo: #{content}" #Just echo
  end

  # 当请求的文字信息内容为'help'时, 使用这个responder处理
  on :text, with:"help" do |request, help|
    request.reply.text "help content" #回复帮助信息
  end

  # 当请求的文字信息内容为'<n>条新闻'时, 使用这个responder处理, 并将n作为第二个参数
  on :text, with: /^(\d+)条新闻$/ do |request, count|
    articles_range = (0... [count.to_i, 10].min)
    request.reply.news(articles_range) do |article, i| #回复"articles"
      article.item title: "标题#{i}", description:"内容描述#{i}", pic_url: "http://www.baidu.com/img/bdlogo.gif", url:"http://www.baidu.com/"
    end
  end

  # 处理图片信息
  on :image do |request|
    request.reply.image(request[:MediaId]) #直接将图片返回给用户
  end

  # 处理语音信息
  on :voice do |request|
    request.reply.voice(request[:MediaId]) #直接语音音返回给用户
  end

  # 处理视频信息
  on :video do |request|
    request.reply.video(request[:MediaId], title: "回声", description: "发来的视频请求") #直接视频返回给用户
  end

  # 处理地理位置信息
  on :location do |request|
    nickname = wechat.user(request[:FromUserName])["nickname"] #呼叫 api 获得发送者的nickname
    request.reply.text("#{nickname}#{request[:Location_X]}, #{request[:Location_Y]}") #回复地理位置
  end

  # 当无任何responder处理用户信息时,使用这个responder处理
  on :fallback, respond: "fallback message"  
end

在 controller 中使用wechat_responder引入 Responder DSL, 之后可以用

on <message_type> do |message|
 message.reply.text "some text"
end

来响应用户信息。

目前支持的 message_type 有如下几种

  • :text 响应文字消息,可以用:with参数来匹配文本内容 on(:text, with:'help'){|message, content| ...}
  • :image 响应图片消息
  • :voice 响应语音消息
  • :video 响应视频消息
  • :location 响应地理位置消息
  • :link 响应链接消息
  • :event 响应事件消息,可以用:with参数来匹配事件类型
  • :fallback 默认响应,当收到的消息无法被其他 responder 响应时,会使用这个 responder.

Message DSL

Wechat-rails 的核心是一个 Message DSL,帮助开发者构建各种类型的消息,包括主动推送的和被动响应的。 ....

很不错!果断 star.

果断点赞啊 我之前做了一个 估计就是被你说的代码质量不行的那个

总有一天会用到,star!!!

我估计也中枪了

大师之作啊。。。

看来 DSL 是 ruby way 元素之一啊。。。

果断尝试一下。

果断收藏,以备不时之需

感谢楼主分享

感谢分享 问一下 微信 api 能主动给特定的用户或者组发消息么?

#13 楼 @liuxingfeiyu 订阅号能在特定用户发送过消息的 48 小时内,给他主动发送所谓的客服消息。由于目前的开发账号貌似只能是订阅号,所以服务号的情况我不太了解了。如果楼中有服务号的朋友,可以帮忙解答。

非常喜欢!正 fork 下来研究

#14 楼 @Skinnyworm 服务号也是在 48 小时内可以发客服消息给用户,发客服消息只能单个单个的发

所以可以写个移动 app 来重复发明轮子,企业在服务客户群的时候需要,可以在企业内部 crm 里直接调 api 实现数据同步,最简单的消息推送等。

好稀饭呐,API 很舒服

恩。多谢啊

感谢楼主,貌似我以后的工作会变得简单了

NB 啊,果断采用。

提几个问题吧 1、access_token 如果存文件的话 如果 rails 部署在 2 个物理机器上就会出现互相刷新 token 的情况 导致当天 2000 次次数用完 2、发送消息前 没对消息内容做验证,比方说 news 类型的消息 Articles 的数量不能大于 10 个 这个应该本地检测再发送 否则白白消耗了接口调用次数 3、响应类消息的 xml 结构,如果用 to_xml 的话 是没有 CDATA 的吧(我没跑起来验证 代码只是粗略过了一次抱歉)没有 CDATA 会不会导致回复的消息 content 里带有 a 标签破坏 xml 的结构 因为回复的消息是可以带 a 标签自动转成链接的 4、缺少了二维码的接口

不支持 rails4 版本?

这真是个好项目,现在这个活动http://railsgirlssummerofcode.org/about/roles/ 在寻找好的开源项目,我想把这个项目推荐给他们。然后如果在中国招募女孩子参与 RG RoC 项目的话,可以邀请你做 mentor 吗?

大概的职责如下:

Mentors are experts on the Open Source project the team is working on. Ideally they are decision makers, or work closely with decision makers, so they can set general goals, give directions and provide feedback.

Since students will be supported by coaches in their day-to-day work, the workload for mentors is limited. This said, mentors can also assume the role of a coach themselves.

#24 楼 @zj0713001

  • 为了不依赖于 ActiveModel, access_token 是存放在文件中的,不过完全可以考虑提供基于其他 Store 的 access_token。
  • 好的,会加上对 news 数量的限制
  • 响应消息的结果只要 respond_to? :to_xml 就可以了,不必须是 message object. 如果是 message object 的话,它的 to_xml 会把 text content escape 了,所以不必使用 CDATA section 了,除非微信那边的 xml parser 不符合 xml 规范。
  • 谢谢提醒二维码的接口,会在下个版本中补上。

另外大家可以在 Github 中直接创建 issue, 谢谢。

#25 楼 @redvoilin Rails4 还没有试过,可能 strong parameters 会对 xml.to_hash 有影响,看看能否在 responder controller 中关掉 strong parameter。我去试试看。

#27 楼 @Skinnyworm 不能 escape 消息回复是支持 a 标签的 escape 了标签就失败了 你可以试试~

补个图:

然后这几天网非常慢 github 已打不开 issues 只能这里提了 抱歉~

问个小白的问题啊,看到代码里有这样的写法:

def item title: "title", description: nil, pic_url: nil, url: nil
  items << {:Title=> title, :Description=> description, :PicUrl=> pic_url, :Url=> url}
end

是 rails/ruby 的新特性么?函数定义能直接用 Hash 当 arguments 了?

#29 楼 @zj0713001 sorry, 我是指 escape 后,如果微信那边的 xml parser 符合规范,应该正确转译&lt, &gt 这些 xml entities 的,你例子中的 A tag 会被作为链接显示的。

#30 楼 @zisasign 是的,ruby 2.x 中的 Keyword arguments 写法,这个方法写的不好,主要因为微信 api 中 XML 和 Json message 用 key 的方法不统一,同一个 key 在 xml 时候是 camel case, 在 Json 的时侯确实 low case 或者是 underscore. 搞得 code 不得不做很多特殊处理。

#31 楼 @Skinnyworm 嗯 转义后是不会的 这是一个坑 所以只能选择适应微信而不是和他掰扯规范... 没办法 唉

#26 楼 @sundevilyang mentor 真不敢当,这只是个 Productive lib,称不上开源项目,不过如果能有更多的女性能对编程感兴趣,我一定乐意帮忙。

37 楼 已删除

很多东东还待学习,不过很是不一般呀,支持分享!

好东西啊!!!

问个问题:可以让公众号关注者以回复方式提交新闻吗?提交的新闻即时推送给所有订阅者。

尝试在 Rails4.0.1 的 app 中加入 gem,bundle 的时候报错,ruby 2.0.0p353 和 ruby 1.9.3p385 都不行。

undler could not find compatible versions for gem "rails":
  In Gemfile:
    wechat-rails (>= 0) ruby depends on
      rails (~> 3.2.14) ruby

    rails (4.0.1)

#41 楼 @dotcomXY 楼主现在这个 gem 只支持 3.2.X 版本的,希望楼主尽快支持 rails4 哈

#41 楼 @dotcomXY 由于还未在 Rails4 中测试过,0.11 版暂时把 Rails 限制 3.2.14。rails 4 的支持将在 0.12 中提供,ruby 版本需 2.0 或以上。如果有需要,可以考虑 fork 一个版本,在 gemspec 中修改对 rails 版本的限制,但我不确定 rails4 中会不会有问题。

另外大家可以考虑在 GitHub 中直接 submit issue

#40 楼 @jazzi 这个是 App 的问题,可以做到

#43 楼 @Skinnyworm 我会 fork 一个,尝试在 Rails 4 的 app 中使用,如果发现 issue 会提交给原 repo

我在 4 里边自己写着 controller 用; #14 楼 @Skinnyworm 服务号是 48 小时之内可以回复,刚申请的公众号好像可以主动发消息,至于升级为订阅号之后,我就不清楚了。订阅号可以每天群发一条,公众号每个月群发一条。

mark,日后学习。

赞,有机会用用

赞一个,我也重复造了个轮子。适用于 rack base 的框架,回来完善了也放 github 上

赞·····

太赞,之前项目需要自己写了部分代码,但是应需要而写,能有这样专业的 Gem 必然会给后来者提供很多方便

匿名 #52 2014年04月26日

👍

赞......

问个问题,微信开发怎么本地测试啊?必须放到服务器上吗?

#55 楼 @lionzixuanyuan 我在 window 上怎么安装不上?双击 exe 文件没反应

#56 楼 @michael_roshen window 上不清楚啊,你要自己研究了,我的是 mac

默默的鄙视自己。。。。😭

#57 楼 @lionzixuanyuan 好吧,我生气了,买了台 mac,今天在公司 ubuntu 下搞定那个问题了,不过想问一下,mac 下如何安装 sublime 呢,那个官网打不开

有个问题不太明白 我在 gemfile 加了 gem "wechat-rails", git:"https://github.com/skinnyworm/wechat-rails" 执行 bundle 以后,为什么在本地的 gems 中找不到这个 gem 包 但是在 gemfile.lock 中有,难道调用的时候去 github 吗 如果是远程,那手动执行 gem install wechat-rails,然后 gemfile 中这样写 gem "wechat-rails", git:"https://github.com/skinnyworm/wechat-raills 还是调用的 github 上的吗?

跪求 0.12 版本

希望能加入新的消息体签名及加解密功能,微信不怎么提倡明文消息了

#61 楼 @hxljustdoit #62 楼 @embbnux 已经加入了企业号支持(包括消息体签名和加解密功能),而且我拿到了wechat这个主域名,可以关注一下,目前只有beta1 版本,应该很快会发布 0.2.0。

赞... 希望在下周的聚会上能看到你。

非常赞的 gem!👍 请教个小问题,ruby commands 部分很完整,在 dsl responder 里面怎么对应呢?比如我在某个 on :text 事件想先看一下 image 素材的列表?

#65 楼 @hlltc 那就需要调用material_list接口,得到 image 素材的命令是wechat material_list image 0 10,详细问题还是在wechat issues里面讨论吧。

很強的 gem, 不過有個問題 運行 rails generate wechat:session 時,系統說找不到 wechat:session Could not find generator wechat:session.

這是什麽原因啊?

$ bundle show wechat /home/app/.gem/ruby/2.1/gems/wechat-0.6.10

#67 楼 @zdantz 不好意思,这个 rails generate wechat:session 是 0.7.2 的新功能,还没有发布,我过一个小时会发布。

#68 楼 @ericguo wow, 回覆好快啊!太感謝了!

#68 楼 @ericguo 希望你继续维护更新自己创造的 gem,加油!同时也谢谢!

#70 楼 @ruby_xiaojie 这个是@skinnyworm 创造的,俺只是维护一下。

#71 楼 @ericguo 都很不错了,辛苦了,谢谢!

我一直以为我一定已经回过这个帖子了。。。。。

@skinnyworm 开始我就已经用这个 gem 了,最早发现这个 gem 是因为小伙伴当时开了个坑,造了一个 python 版的微信 SDK,问我要不要来一个 ruby 的,我一搜就发现了这个,然后神气的告诉他,我大 ruby 社区早就已经有很成熟的了 后来在工作中维护公司的微信服务,开始重度使用这个 gem。但是良心的说,skinnyworm 维护到后期的版本,用起来是有一点不辣么优雅的,我一直对于它的加载方式有所介怀,before_* 的触发器都用不了,那样总感觉写出来的代码会冗余,而且当时也没有转接多客服的功能,token 文件的刷新机制也有点小问题。 等一段时间后我再回来维护微信项目时,发现 gem 大更新了,换了一个作者,短短一个多月 100+ 的 commits,之前的所有痛点都解决了。本来之前是 fork 了一份,自己改了一些再放入项目中用的,当时就直接删了 fork,使用官方版本了,哈哈哈~~~ 感谢 @ericguo 的无私奉献

再来点建议:多客服转发其实可以指定客服的;发送模板消息感觉也可以封装到 reply 里面去,使用场景挺多的。(有机会我也想贡献点代码~)

#73 楼 @xworm 0.7.3 版本现在多客服转发可以指定客服了,也支持获取在线客服列表,模板消息我不怎么用,实际上我只是用用企业号。。。

用的到的功能就提 PR 啦,现在连 session 这样的可加可不加的功能都加了,还有啥功能不能加呢。。。

chairy11 谁来个微信开发学习资源大全? 提及了此话题。 07月06日 23:19
ericguo 回复

截图是在 shell 执行 wechat material_delete [MEDIA_ID] 时出现的问题 可以解答一下吗?🙏 版本 0.8.8

alice_ 回复

wechat material_delete实现是有问题,在 0.8.10 版本里面已经修正了。

ericguo 回复

好的,谢谢

用测试验证不通过,出现 Forbidden ..公众号提示验证失败

需要 登录 后方可回复, 如果你还没有账号请 注册新账号