gfd_wechat 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +321 -0
  3. data/LICENSE +21 -0
  4. data/README-CN.md +815 -0
  5. data/README.md +844 -0
  6. data/bin/wechat +520 -0
  7. data/lib/action_controller/wechat_responder.rb +72 -0
  8. data/lib/generators/wechat/config_generator.rb +36 -0
  9. data/lib/generators/wechat/install_generator.rb +20 -0
  10. data/lib/generators/wechat/menu_generator.rb +21 -0
  11. data/lib/generators/wechat/redis_store_generator.rb +16 -0
  12. data/lib/generators/wechat/session_generator.rb +36 -0
  13. data/lib/generators/wechat/templates/MENU_README +3 -0
  14. data/lib/generators/wechat/templates/app/controllers/wechats_controller.rb +12 -0
  15. data/lib/generators/wechat/templates/app/models/wechat_config.rb +46 -0
  16. data/lib/generators/wechat/templates/app/models/wechat_session.rb +17 -0
  17. data/lib/generators/wechat/templates/config/initializers/wechat_redis_store.rb +42 -0
  18. data/lib/generators/wechat/templates/config/wechat.yml +72 -0
  19. data/lib/generators/wechat/templates/config/wechat_menu.yml +6 -0
  20. data/lib/generators/wechat/templates/config/wechat_menu_android.yml +15 -0
  21. data/lib/generators/wechat/templates/db/config_migration.rb.erb +40 -0
  22. data/lib/generators/wechat/templates/db/session_migration.rb.erb +10 -0
  23. data/lib/wechat/api.rb +54 -0
  24. data/lib/wechat/api_base.rb +63 -0
  25. data/lib/wechat/api_loader.rb +145 -0
  26. data/lib/wechat/cipher.rb +66 -0
  27. data/lib/wechat/concern/common.rb +217 -0
  28. data/lib/wechat/controller_api.rb +96 -0
  29. data/lib/wechat/corp_api.rb +168 -0
  30. data/lib/wechat/helpers.rb +47 -0
  31. data/lib/wechat/http_client.rb +112 -0
  32. data/lib/wechat/message.rb +265 -0
  33. data/lib/wechat/mp_api.rb +46 -0
  34. data/lib/wechat/responder.rb +308 -0
  35. data/lib/wechat/signature.rb +10 -0
  36. data/lib/wechat/ticket/corp_jsapi_ticket.rb +14 -0
  37. data/lib/wechat/ticket/jsapi_base.rb +84 -0
  38. data/lib/wechat/ticket/public_jsapi_ticket.rb +14 -0
  39. data/lib/wechat/token/access_token_base.rb +53 -0
  40. data/lib/wechat/token/corp_access_token.rb +13 -0
  41. data/lib/wechat/token/public_access_token.rb +13 -0
  42. data/lib/wechat.rb +52 -0
  43. metadata +195 -0
data/README-CN.md ADDED
@@ -0,0 +1,815 @@
1
+ WeChat [![Gem Version](https://badge.fury.io/rb/wechat.svg)](https://rubygems.org/gems/wechat) [![Build Status](https://travis-ci.org/Eric-Guo/wechat.svg)](https://travis-ci.org/Eric-Guo/wechat) [![Maintainability](https://api.codeclimate.com/v1/badges/12885358487c13e91e00/maintainability)](https://codeclimate.com/github/Eric-Guo/wechat/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/12885358487c13e91e00/test_coverage)](https://codeclimate.com/github/Eric-Guo/wechat/test_coverage)
2
+ ======
3
+
4
+ [![Join the chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Eric-Guo/wechat?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
5
+
6
+ WeChat gem 可以帮助开发者方便地在Rails环境中集成微信[公众平台](https://mp.weixin.qq.com/)和[企业平台](https://qy.weixin.qq.com)提供的服务,包括:
7
+
8
+ - 微信公众/企业平台[主动消息](http://qydev.weixin.qq.com/wiki/index.php?title=%E5%8F%91%E9%80%81%E6%B6%88%E6%81%AF)API(命令行和Web环境都可以使用)
9
+ - [回调消息](http://qydev.weixin.qq.com/wiki/index.php?title=%E6%8E%A5%E6%94%B6%E6%B6%88%E6%81%AF%E4%B8%8E%E4%BA%8B%E4%BB%B6)(必须运行Web服务器)
10
+ - [微信JS-SDK](http://qydev.weixin.qq.com/wiki/index.php?title=%E5%BE%AE%E4%BF%A1JS%E6%8E%A5%E5%8F%A3) config接口注入权限验证
11
+ - OAuth 2.0认证机制
12
+ - 回调消息会话(session)记录机制(可选)
13
+
14
+ 命令行工具`wechat`可以调用各种无需web环境的API。同时也提供了Rails Controller的responder DSL, 可以帮助开发者方便地在Rails应用中集成微信的消息处理,包括主动推送的和被动响应的消息。
15
+
16
+ 如果您的App还需要集成微信OAuth2.0, 除了简便的`wechat_oauth2`指令,也可以考虑[omniauth-wechat-oauth2](https://github.com/skinnyworm/omniauth-wechat-oauth2), 以便和devise集成,提供完整的用户认证。
17
+
18
+ 如果您对如何制作微信网页UI没有灵感,可以参考官方的[weui](https://github.com/weui/weui),针对Rails的Gem是[weui-rails](https://github.com/Eric-Guo/weui-rails)。
19
+
20
+ 主页型应用请使用[`wechat_api`](#wechat_api---rails-controller-wechat-api),传统消息型应用请使用[`wechat_responder`](#wechat_responder---rails-responder-controller-dsl)。
21
+
22
+ 如果您想从一个稍微完整一些的示例开始微信开发,可以参考[wechat-starter](https://github.com/goofansu/wechat-starter),这个示例甚至包括了微信支付的内容。
23
+
24
+ ## 安装
25
+
26
+ 使用 `gem install`
27
+
28
+ ```
29
+ gem install "wechat"
30
+ ```
31
+
32
+ 或者添加下面这行到 `Gemfile`:
33
+
34
+ ```
35
+ gem 'wechat'
36
+ ```
37
+
38
+ 运行下面这行代码来安装:
39
+
40
+ ```console
41
+ bundle install
42
+ ```
43
+
44
+ 运行下面这行代码来生成必要文件:
45
+
46
+ ```console
47
+ rails generate wechat:install
48
+ ```
49
+
50
+ 运行`rails g wechat:install`后会自动生成wechat.yml配置,还有wechat controller及相关路由配置到当前Rails项目。
51
+
52
+ 启用session会话记录:
53
+
54
+ ```console
55
+ rails g wechat:session
56
+ rake db:migrate
57
+ ```
58
+
59
+ 运行后会自动启用回调消息会话(session)记录,wechat gem会在Rails项目中生成两个文件,用户可以在*wechat_session*表中添加更多字段或者声明一些关联关系。使用已有的**hash_store**直接保存也是可以的,但对于PostgreSQL用户,使用[hstore](http://guides.rubyonrails.org/active_record_postgresql.html#hstore)或者json格式可能更佳,当然,最佳方案仍然是添加新字段记录数据。
60
+
61
+ 启用Redis存贮token和ticket:
62
+
63
+ ```console
64
+ rails g wechat:redis_store
65
+ ```
66
+
67
+ Redis存贮相比默认的文件存贮,可以允许Rails应用运行在多台服务器中,如果只有一台服务器,仍然推荐使用默认的文件存贮,另外命令行不会读取Redis存贮的Token或者Ticket。
68
+
69
+ 启用数据库配置微信账户:
70
+
71
+ ```console
72
+ rails g wechat:config
73
+ rake db:migrate
74
+ ```
75
+
76
+ 运行后会在数据库中创建 `wechat_configs` 表,用来记录不同微信账户的配置。
77
+
78
+ ## 配置
79
+
80
+ #### 微信的第一次配置
81
+
82
+ 请先确保在服务器上配置成功,再到微信官网提交链接。否则微信会提示错误。
83
+
84
+ 默认通过`rails g wechat:install`生成的URL是: `http://your-server.com/wechat`
85
+
86
+ appid/corpid,以及secret的配置请阅读下一节
87
+
88
+ #### 命令行程序的配置
89
+
90
+ 要使用命令行程序,需要在home目录中创建一个`~/.wechat.yml`,包含以下内容。其中`access_token`是存放access_token的文件位置。
91
+
92
+ ```
93
+ appid: "my_appid"
94
+ secret: "my_secret"
95
+ access_token: "/var/tmp/wechat_access_token"
96
+ ```
97
+
98
+ Windows或者使用企业号,需要存放在`C:/Users/[user_name]/`下,其中corpid和corpsecret可以从企业号管理界面的设置->权限管理,通过新建任意一个管理组后获取。
99
+
100
+ ```
101
+ corpid: "my_appid"
102
+ corpsecret: "my_secret"
103
+ agentid: 1 # 企业应用的id,整型。可在应用的设置页面查看
104
+ access_token: "C:/Users/[user_name]/wechat_access_token"
105
+ ```
106
+
107
+ #### Rails 全局配置
108
+ Rails应用程序中,需要将配置文件放在`config/wechat.yml`,可以为不同environment创建不同的配置。
109
+
110
+ 公众号配置示例:
111
+
112
+ ```
113
+ default: &default
114
+ appid: "app_id"
115
+ secret: "app_secret"
116
+ token: "app_token"
117
+ access_token: "/var/tmp/wechat_access_token"
118
+ jsapi_ticket: "/var/tmp/wechat_jsapi_ticket"
119
+
120
+ production:
121
+ appid: <%= ENV['WECHAT_APPID'] %>
122
+ secret: <%= ENV['WECHAT_APP_SECRET'] %>
123
+ token: <%= ENV['WECHAT_TOKEN'] %>
124
+ access_token: <%= ENV['WECHAT_ACCESS_TOKEN'] %>
125
+ jsapi_ticket: <%= ENV['WECHAT_JSAPI_TICKET'] %>
126
+ oauth2_cookie_duration: <%= ENV['WECHAT_OAUTH2_COOKIE_DURATION'] %> # seconds
127
+
128
+ development:
129
+ <<: *default
130
+ trusted_domain_fullname: "http://your_dev.proxy.qqbrowser.cc"
131
+
132
+ test:
133
+ <<: *default
134
+ ```
135
+
136
+ 公众号可选安全模式(加密模式),通过添加如下配置可开启加密模式。
137
+
138
+ ```
139
+ default: &default
140
+ encrypt_mode: true
141
+ encoding_aes_key: "my_encoding_aes_key"
142
+ ```
143
+
144
+ 企业号配置下必须使用加密模式,其中token和encoding_aes_key可以从企业号管理界面的应用中心->某个应用->模式选择,选择回调模式后获得。
145
+
146
+ ```
147
+ default: &default
148
+ corpid: "corpid"
149
+ corpsecret: "corpsecret"
150
+ agentid: 1
151
+ access_token: "C:/Users/[user_name]/wechat_access_token"
152
+ token: ""
153
+ encoding_aes_key: ""
154
+ jsapi_ticket: "C:/Users/[user_name]/wechat_jsapi_ticket"
155
+
156
+ production:
157
+ corpid: <%= ENV['WECHAT_CORPID'] %>
158
+ corpsecret: <%= ENV['WECHAT_CORPSECRET'] %>
159
+ agentid: <%= ENV['WECHAT_AGENTID'] %>
160
+ access_token: <%= ENV['WECHAT_ACCESS_TOKEN'] %>
161
+ token: <%= ENV['WECHAT_TOKEN'] %>
162
+ timeout: 30,
163
+ skip_verify_ssl: true
164
+ encoding_aes_key: <%= ENV['WECHAT_ENCODING_AES_KEY'] %>
165
+ jsapi_ticket: <%= ENV['WECHAT_JSAPI_TICKET'] %>
166
+ oauth2_cookie_duration: <%= ENV['WECHAT_OAUTH2_COOKIE_DURATION'] %>
167
+
168
+ development:
169
+ <<: *default
170
+ trusted_domain_fullname: "http://your_dev.proxy.qqbrowser.cc"
171
+
172
+ test:
173
+ <<: *default
174
+
175
+ # Multiple Accounts
176
+ #
177
+ # wx2_development:
178
+ # <<: *default
179
+ # appid: "my_appid"
180
+ # secret: "my_secret"
181
+ # access_token: "tmp/wechat_access_token2"
182
+ # jsapi_ticket: "tmp/wechat_jsapi_ticket2"
183
+ #
184
+ # wx2_test:
185
+ # <<: *default
186
+ # appid: "my_appid"
187
+ # secret: "my_secret"
188
+ #
189
+ # wx2_production:
190
+ # <<: *default
191
+ # appid: "my_appid"
192
+ # secret: "my_secret"
193
+ ```
194
+
195
+ 进一步的多账号支持参见[PR 150](https://github.com/Eric-Guo/wechat/pull/150)。
196
+
197
+ #### 数据库微信账户配置
198
+ 启用数据库微信配置之后,会生成如下数据表:
199
+
200
+ 属性 | 类型 | 备注
201
+ ---- | ---- | ----
202
+ environment | 字串 | 必填。配置对应的运行环境,一般有:`production`、`development`、`test`。比如 `production` 配置仅在生产环境有效。默认为 `development`。
203
+ account | 字串 | 必填。自定义的微信账户名称。同一 `environment` 下,账户名称不允许重复。
204
+ enabled | 布尔 | 必填。配置是否生效。默认 `true`。
205
+ appid | 字串 | 公众号 id。此字段和 `corpid` 两者必填其一。
206
+ secret | 字串 | 公众号相关配置。当公众号 `appid` 存在时必填。
207
+ corpid | 字串 | 企业号 id。此字段和 `appid` 两者必填其一。
208
+ corpsecret | 字串 | 企业号相关配置。当企业号 `corpid` 存在时必填。
209
+ agentid | 整数 | 企业号相关配置。当企业号 `corpid` 存在时必填。
210
+ encrypt_mode | 布尔 |
211
+ encoding_aes_key | 字串 | 当 `encrypt_mode` 为 `true` 时必填。
212
+ token | 字串 | 必填。
213
+ access_token | 字串 | 必填。存储 `access token` 文件的路径。
214
+ jsapi_ticket | 字串 | 必填。存储 `jsapi ticket` 文件的路径。
215
+ skip_verify_ssl | 布尔
216
+ timeout | 整数 | 默认值是 20。
217
+ trusted_domain_fullname | 字串 |
218
+
219
+ 数据库配置更新后,需要重启服务器或者调用 `Wechat.reload_config!` 载入更新,否则更新不会生效。
220
+
221
+ ##### 配置优先级
222
+
223
+ 注意在Rails项目根目录下运行`wechat`命令行工具会优先使用`config/wechat.yml`中的`default`配置,如果失败则使用`~\.wechat.yml`中的配置,以便于在生产环境下管理多个微信账号应用。
224
+
225
+ 如果启用数据库账户配置,数据库中的账户信息在读入 `wechat.yml` 或环境变量之后被载入。当存在同名账户时,数据库中的配置会覆盖前两者。
226
+
227
+ ##### 配置微信服务器超时
228
+
229
+ 微信服务器有时请求会花很长时间,如果不配置,默认为20秒,可视情况配置。
230
+
231
+ ##### 配置跳过SSL认证
232
+
233
+ Wechat服务器有报道曾出现[RestClient::SSLCertificateNotVerified](http://qydev.weixin.qq.com/qa/index.php?qa=11037)错误,此时可以选择关闭SSL验证。`skip_verify_ssl: true`
234
+
235
+ #### 为每个Responder配置不同的appid和secret
236
+
237
+ 有些情况下,单个Rails应用可能需要处理来自多个微信公众号的消息,您可以通过在`wechat_responder`和`wechat_api`后配置多个相关参数来支持多账号。
238
+
239
+ ```ruby
240
+ class WechatFirstController < ActionController::Base
241
+ wechat_responder account: :new_account, account_from_request: Proc.new{ |request| request.params[:wechat] }
242
+
243
+ on :text, with:"help", respond: "help content"
244
+ end
245
+ ```
246
+
247
+ 或者直接完整配置
248
+
249
+ ```ruby
250
+ class WechatFirstController < ActionController::Base
251
+ wechat_responder appid: "app1", secret: "secret1", token: "token1", access_token: Rails.root.join("tmp/access_token1"),
252
+ account_from_request: Proc.new{ |request| request.params[:wechat] }
253
+
254
+ on :text, with:"help", respond: "help content"
255
+ end
256
+ ```
257
+
258
+ 其中 `account_from_request` 是一个 `Proc`,接受 `request` 作为唯一参数,返回相应的微信账户名称。以上示例中,`controller` 会根据 `request` 中传入的 `wechat` 参数选择微信账户。如果没有提供 `account_from_request` 或者 `Proc` 的结果是 `nil`,则使用 `account` 或者完整配置。
259
+
260
+ #### JS-SDK 支持
261
+
262
+ 通过JS-SDK可以在HTML网页中控制微信客户端的行为,但必须先注入配置信息,wechat gems提供了帮助方法`wechat_config_js`使这个过程更简单:
263
+
264
+ 注意wechat_config_js指令依赖于[`wechat_api`](#wechat_api---rails-controller-wechat-api) 或 [`wechat_responder`](#wechat_responder---rails-responder-controller-dsl) ,需要先在controller里面添加。
265
+
266
+ ```erb
267
+ <body>
268
+ <%= wechat_config_js debug: false, api: %w(hideMenuItems closeWindow) -%>
269
+ <script type="application/javascript">
270
+ wx.ready(function() {
271
+ wx.hideOptionMenu();
272
+ });
273
+ </script>
274
+ <a href="javascript:wx.closeWindow();">Close</a>
275
+ </body>
276
+ ```
277
+
278
+ 在开发模式下,由于程序往往通过微信调试工具的服务器端调试工具反向代理被访问,此时需要配置`trusted_domain_fullname`以便wechat gem可以使用正确的域名做JS-SDK的权限签名。
279
+
280
+ #### OAuth2.0验证接口支持
281
+
282
+ 公众号可使用如下代码取得关注用户的相关信息。
283
+
284
+ ```ruby
285
+ class CartController < ActionController::Base
286
+ wechat_api
287
+ def index
288
+ wechat_oauth2 do |openid|
289
+ @current_user = User.find_by(wechat_openid: openid)
290
+ @articles = @current_user.articles
291
+ end
292
+
293
+ # 指定 account_name,可以使用任意微信账户
294
+ # wechat_oauth2('snsapi_base', nil, account_name) do |openid|
295
+ # ...
296
+ # end
297
+ end
298
+ end
299
+ ```
300
+
301
+ 企业号可使用如下代码取得企业用户的相关信息。
302
+
303
+ ```ruby
304
+ class WechatsController < ActionController::Base
305
+ layout 'wechat'
306
+ wechat_responder
307
+ def apply_new
308
+ wechat_oauth2 do |userid|
309
+ @current_user = User.find_by(wechat_userid: userid)
310
+ @apply = Apply.new
311
+ @apply.user_id = @current_user.id
312
+ end
313
+ end
314
+ end
315
+ ```
316
+
317
+ `wechat_oauth2`封装了OAuth2.0验证接口和cookie处理逻辑,用户仅需提供业务代码块即可。userid指的是微信企业成员UserID,openid是关注该公众号的用户openid。
318
+
319
+ 注意:
320
+ * 如果使用 `wechat_responder`, 请不要在 Controller 里定义 `show` 和 `create` 方法, 否则会报错。
321
+ * 如果遇到“redirect_uri参数错误”的错误信息,请登录服务号管理后台,查看“开发者中心/网页服务/网页授权获取用户基本信息”的授权回调页面域名已正确配置。
322
+
323
+ ## 关于接口权限
324
+
325
+ wechat gems 内部不会检查权限。但因公众号类型不同,和微信服务器端通讯时,可能会被拒绝,详细权限控制可参考[官方文档](https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1433401084)。
326
+
327
+ ## 使用命令行
328
+
329
+ 根据企业号和公众号配置不同,wechat提供了的命令行命令。
330
+
331
+ #### 公众号命令行
332
+
333
+ ```
334
+ $ wechat
335
+ Wechat Public Account commands:
336
+ wechat callbackip # 获取微信服务器IP地址
337
+ wechat custom_image [OPENID, IMAGE_PATH] # 发送图片客服消息
338
+ wechat custom_music [OPENID, THUMBNAIL_PATH, MUSIC_URL] # 发送音乐客服消息
339
+ wechat custom_news [OPENID, NEWS_YAML_PATH] # 发送图文客服消息
340
+ wechat custom_text [OPENID, TEXT_MESSAGE] # 发送文字客服消息
341
+ wechat custom_video [OPENID, VIDEO_PATH] # 发送视频客服消息
342
+ wechat custom_voice [OPENID, VOICE_PATH] # 发送语音客服消息
343
+ wechat customservice_getonlinekflist # 获取在线客服接待信息
344
+ wechat group_create [GROUP_NAME] # 创建分组
345
+ wechat group_delete [GROUP_ID] # 删除分组
346
+ wechat group_update [GROUP_ID, NEW_GROUP_NAME] # 修改分组名
347
+ wechat groups # 查询所有分组
348
+ wechat material [MEDIA_ID, PATH] # 永久媒体下载
349
+ wechat material_add [MEDIA_TYPE, PATH] # 永久媒体上传
350
+ wechat material_count # 获取永久素材总数
351
+ wechat material_delete [MEDIA_ID] # 删除永久素材
352
+ wechat material_list [TYPE, OFFSET, COUNT] # 获取永久素材列表
353
+ wechat media [MEDIA_ID, PATH] # 媒体下载
354
+ wechat media_hq [MEDIA_ID, PATH] # 高清音频下载
355
+ wechat media_create [MEDIA_TYPE, PATH] # 媒体上传
356
+ wechat media_uploadimg [IMAGE_PATH] # 上传图文消息内的图片
357
+ wechat media_uploadnews [MPNEWS_YAML_PATH] # 上传图文消息素材
358
+ wechat menu # 当前菜单
359
+ wechat menu_addconditional [CONDITIONAL_MENU_YAML_PATH] # 创建个性化菜单
360
+ wechat menu_create [MENU_YAML_PATH] # 创建菜单
361
+ wechat menu_delconditional [MENU_ID] # 删除个性化菜单
362
+ wechat menu_delete # 删除菜单
363
+ wechat menu_trymatch [USER_ID] # 测试个性化菜单匹配结果
364
+ wechat message_mass_delete [MSG_ID] # 删除群发消息
365
+ wechat message_mass_get [MSG_ID] # 查询群发消息发送状态
366
+ wechat message_mass_preview [WX_NAME, MPNEWS_MEDIA_ID] # 预览图文消息素材
367
+ wechat qrcode_create_limit_scene [SCENE_ID_OR_STR] # 请求永久二维码
368
+ wechat qrcode_create_scene [SCENE_ID_OR_STR, EXPIRE_SECONDS] # 请求临时二维码
369
+ wechat qrcode_download [TICKET, QR_CODE_PIC_PATH] # 通过ticket下载二维码
370
+ wechat short_url [LONG_URL] # 长链接转短链接
371
+ wechat tag [TAGID] # 获取标签下粉丝列表
372
+ wechat tag_add_user [TAG_ID, OPEN_IDS] # 批量为用户打标签
373
+ wechat tag_create [TAGNAME, TAG_ID] # 创建标签
374
+ wechat tag_del_user [TAG_ID, OPEN_IDS] # 批量为用户取消标签
375
+ wechat tag_delete [TAG_ID] # 删除标签
376
+ wechat tag_update [TAG_ID, TAGNAME] # 更新标签名字
377
+ wechat tags # 获取所有标签
378
+ wechat template_message [OPENID, TEMPLATE_YAML_PATH] # 模板消息接口
379
+ wechat user [OPEN_ID] # 获取用户基本信息
380
+ wechat user_batchget [OPEN_ID_LIST] # 批量获取用户基本信息
381
+ wechat user_change_group [OPEN_ID, TO_GROUP_ID] # 移动用户分组
382
+ wechat user_group [OPEN_ID] # 查询用户所在分组
383
+ wechat user_update_remark [OPEN_ID, REMARK] # 设置备注名
384
+ wechat users # 关注者列表
385
+ wechat wxacode_download [WXA_CODE_PIC_PATH, PATH, WIDTH] # 下载小程序码
386
+ ```
387
+
388
+ #### 企业号命令行
389
+ ```
390
+ $ wechat
391
+ Wechat Enterprise Account commands:
392
+ wechat agent [AGENT_ID] # 获取企业号应用详情
393
+ wechat agent_list # 获取应用概况列表
394
+ wechat batch_job_result [JOB_ID] # 获取异步任务结果
395
+ wechat batch_replaceparty [BATCH_PARTY_CSV_MEDIA_ID] # 全量覆盖部门
396
+ wechat batch_replaceuser [BATCH_USER_CSV_MEDIA_ID] # 全量覆盖成员
397
+ wechat batch_syncuser [SYNC_USER_CSV_MEDIA_ID] # 增量更新成员
398
+ wechat callbackip # 获取微信服务器IP地址
399
+ wechat convert_to_openid [USER_ID] # userid转换成openid
400
+ wechat custom_image [OPENID, IMAGE_PATH] # 发送图片客服消息
401
+ wechat custom_music [OPENID, THUMBNAIL_PATH, MUSIC_URL] # 发送音乐客服消息
402
+ wechat custom_news [OPENID, NEWS_YAML_PATH] # 发送图文客服消息
403
+ wechat custom_text [OPENID, TEXT_MESSAGE] # 发送文字客服消息
404
+ wechat custom_video [OPENID, VIDEO_PATH] # 发送视频客服消息
405
+ wechat custom_voice [OPENID, VOICE_PATH] # 发送语音客服消息
406
+ wechat department [DEPARTMENT_ID] # 获取部门列表
407
+ wechat department_create [NAME, PARENT_ID] # 创建部门
408
+ wechat department_delete [DEPARTMENT_ID] # 删除部门
409
+ wechat department_update [DEPARTMENT_ID, NAME] # 更新部门
410
+ wechat invite_user [USER_ID] # 邀请成员关注
411
+ wechat material [MEDIA_ID, PATH] # 永久媒体下载
412
+ wechat material_add [MEDIA_TYPE, PATH] # 永久媒体上传
413
+ wechat material_count # 获取永久素材总数
414
+ wechat material_delete [MEDIA_ID] # 删除永久素材
415
+ wechat material_list [TYPE, OFFSET, COUNT] # 获取永久素材列表
416
+ wechat media [MEDIA_ID, PATH] # 媒体下载
417
+ wechat media_create [MEDIA_TYPE, PATH] # 媒体上传
418
+ wechat media_uploadimg [IMAGE_PATH] # 上传图文消息内的图片
419
+ wechat menu # 当前菜单
420
+ wechat menu_addconditional [CONDITIONAL_MENU_YAML_PATH] # 创建个性化菜单
421
+ wechat menu_create [MENU_YAML_PATH] # 创建菜单
422
+ wechat menu_delconditional [MENU_ID] # 删除个性化菜单
423
+ wechat menu_delete # 删除菜单
424
+ wechat menu_trymatch [USER_ID] # 测试个性化菜单匹配结果
425
+ wechat message_send [OPENID, TEXT_MESSAGE] # 发送文字消息
426
+ wechat qrcode_download [TICKET, QR_CODE_PIC_PATH] # 通过ticket下载二维码
427
+ wechat tag [TAG_ID] # 获取标签成员
428
+ wechat tag_add_department [TAG_ID, PARTY_IDS] # 增加标签部门
429
+ wechat tag_add_user [TAG_ID, USER_IDS] # 增加标签成员
430
+ wechat tag_create [TAGNAME, TAG_ID] # 创建标签
431
+ wechat tag_del_department [TAG_ID, PARTY_IDS] # 删除标签部门
432
+ wechat tag_del_user [TAG_ID, USER_IDS] # 删除标签成员
433
+ wechat tag_delete [TAG_ID] # 删除标签
434
+ wechat tag_update [TAG_ID, TAGNAME] # 更新标签名字
435
+ wechat tags # 获取所有标签
436
+ wechat template_message [OPENID, TEMPLATE_YAML_PATH] # 模板消息接口
437
+ wechat upload_replaceparty [BATCH_PARTY_CSV_PATH] # 上传文件方式全量覆盖部门
438
+ wechat upload_replaceuser [BATCH_USER_CSV_PATH] # 上传文件方式全量覆盖成员
439
+ wechat user [OPEN_ID] # 获取用户基本信息
440
+ wechat user_batchdelete [USER_ID_LIST] # 批量删除成员
441
+ wechat user_create [USER_ID, NAME] # 创建成员
442
+ wechat user_delete [USER_ID] # 删除成员
443
+ wechat user_list [DEPARTMENT_ID] # 获取部门成员详情
444
+ wechat user_simplelist [DEPARTMENT_ID] # 获取部门成员
445
+ wechat user_update_remark [OPEN_ID, REMARK] # 设置备注名
446
+ ```
447
+
448
+ ### 使用场景
449
+ 以下是几种典型场景的使用方法
450
+
451
+ #####获取所有用户的OPENID
452
+
453
+ ```
454
+ $ wechat users
455
+
456
+ {"total"=>4, "count"=>4, "data"=>{"openid"=>["oCfEht9***********", "oCfEhtwqa***********", "oCfEht9oMCqGo***********", "oCfEht_81H5o2***********"]}, "next_openid"=>"oCfEht_81H5o2***********"}
457
+
458
+ ```
459
+
460
+ #####获取用户的信息
461
+
462
+ ```
463
+ $ wechat user "oCfEht9***********"
464
+
465
+ {"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}
466
+
467
+ ```
468
+
469
+ ##### 获取当前菜单
470
+ ```
471
+ $ wechat menu
472
+
473
+ {"menu"=>{"button"=>[{"type"=>"view", "name"=>"保护的", "url"=>"http://***/protected", "sub_button"=>[]}, {"type"=>"view", "name"=>"公开的", "url"=>"http://***", "sub_button"=>[]}]}}
474
+
475
+ ```
476
+
477
+ ##### 创建菜单
478
+
479
+
480
+ 通过运行`rails g wechat:menu`可以生成一个定义菜单内容的yaml文件,菜单可以包含下列内容:
481
+
482
+ ```
483
+ button:
484
+ -
485
+ name: "我要"
486
+ sub_button:
487
+ -
488
+ type: "scancode_waitmsg"
489
+ name: "绑定用餐二维码"
490
+ key: "BINDING_QR_CODE"
491
+ -
492
+ type: "click"
493
+ name: "预订午餐"
494
+ key: "BOOK_LUNCH"
495
+ -
496
+ type: "miniprogram"
497
+ name: "小程序示例"
498
+ url: "http://ericguo.com/"
499
+ appid: "wx1234567890"
500
+ pagepath: "pages/index"
501
+ -
502
+ name: "查询"
503
+ sub_button:
504
+ -
505
+ type: "click"
506
+ name: "进出记录"
507
+ key: "BADGE_IN_OUT"
508
+ -
509
+ type: "click"
510
+ name: "年假余额"
511
+ key: "ANNUAL_LEAVE"
512
+ -
513
+ type: "view"
514
+ name: "关于"
515
+ url: "http://blog.cloud-mes.com/"
516
+ ```
517
+
518
+ 下列命令行将上传自定义菜单:
519
+
520
+ ```
521
+ $ wechat menu_create menu.yaml
522
+ ```
523
+
524
+ 需确保设置,权限管理中有对此应用的管理权限,否则会报[60011](http://qydev.weixin.qq.com/wiki/index.php?title=%E5%85%A8%E5%B1%80%E8%BF%94%E5%9B%9E%E7%A0%81%E8%AF%B4%E6%98%8E)错。
525
+
526
+ ##### 发送客服图文消息
527
+ 需定义一个图文消息内容的yaml文件,比如
528
+ articles.yaml
529
+
530
+ ```
531
+ articles:
532
+ -
533
+ title: "习近平在布鲁日欧洲学院演讲"
534
+ description: "新华网比利时布鲁日4月1日电 国家主席习近平1日在比利时布鲁日欧洲学院发表重要演讲"
535
+ url: "http://news.sina.com.cn/c/2014-04-01/232629843387.shtml"
536
+ pic_url: "http://i3.sinaimg.cn/dy/c/2014-04-01/1396366518_bYays1.jpg"
537
+ ```
538
+
539
+ 然后执行命令行
540
+
541
+ ```
542
+ $ wechat custom_news oCfEht9oM*********** articles.yml
543
+
544
+ ```
545
+
546
+ ##### 发送模板消息
547
+ 需定义一个模板消息内容的yaml文件,比如
548
+ template.yml
549
+
550
+ ```
551
+ template:
552
+ template_id: "o64KQ62_xxxxxxxxxxxxxxx-Qz-MlNcRKteq8"
553
+ url: "http://weixin.qq.com/download"
554
+ topcolor: "#FF0000"
555
+ data:
556
+ first:
557
+ value: "您好,您已报名成功"
558
+ color: "#0A0A0A"
559
+ keynote1:
560
+ value: "XX活动"
561
+ color: "#CCCCCC"
562
+ keynote2:
563
+ value: "2014年9月16日"
564
+ color: "#CCCCCC"
565
+ keynote3:
566
+ value: "上海徐家汇xxx城"
567
+ color: "#CCCCCC"
568
+ remark:
569
+ value: "欢迎再次使用。"
570
+ color: "#173177"
571
+
572
+ ```
573
+
574
+ 然后执行命令行
575
+
576
+ ```
577
+ $ wechat template_message oCfEht9oM*********** template.yml
578
+ ```
579
+
580
+ 在代码中可以这样使用:
581
+
582
+ ```ruby
583
+ template = YAML.load(File.read(template_yaml_path))
584
+ Wechat.api.template_message_send Wechat::Message.to(openid).template(template['template'])
585
+ ```
586
+
587
+ 若在Controller中使用wechat_api或者wechat_responder,可以使用wechat:
588
+
589
+ ```ruby
590
+ template = YAML.load(File.read(template_yaml_path))
591
+ wechat.template_message_send Wechat::Message.to(openid).template(template['template'])
592
+ ```
593
+
594
+ ## wechat_api - Rails Controller Wechat API
595
+
596
+ 虽然用户可以随时通过`Wechat.api`在任意代码中访问wechat的API功能,但是更推荐的做法是仅在controller中,通过引入`wechat_api`,使用`wechat`调用API功能,不仅因为这样是支持多个微信公众号的必然要求,而且也避免了在模型层内过多引入微信相关代码。
597
+
598
+ ```ruby
599
+ class WechatReportsController < ApplicationController
600
+ wechat_api
601
+ layout 'wechat'
602
+
603
+ def index
604
+ @lots = Lot.with_preloading.wip_lot
605
+ end
606
+ end
607
+ ```
608
+
609
+ ## 在ActiveJob/Rake tasks中调用有wechat api
610
+
611
+ 可以通过`Wechat.api`在任意地方使用wechat api的功能。
612
+
613
+ ## wechat_responder - Rails Responder Controller DSL
614
+
615
+ 为了在Rails app中响应用户的消息,开发者需要创建一个wechat responder controller. 首先在router中定义
616
+
617
+ ```ruby
618
+ resource :wechat, only:[:show, :create]
619
+ ```
620
+
621
+ 然后创建Controller class, 例如
622
+
623
+ ```ruby
624
+ class WechatsController < ActionController::Base
625
+ wechat_responder
626
+
627
+ # 默认文字信息responder
628
+ on :text do |request, content|
629
+ request.reply.text "echo: #{content}" #Just echo
630
+ end
631
+
632
+ # 当请求的文字信息内容为'help'时, 使用这个responder处理
633
+ on :text, with: 'help' do |request|
634
+ request.reply.text 'help content' #回复帮助信息
635
+ end
636
+
637
+ # 当请求的文字信息内容为'<n>条新闻'时, 使用这个responder处理, 并将n作为第二个参数
638
+ on :text, with: /^(\d+)条新闻$/ do |request, count|
639
+ # 微信最多显示8条新闻,大于8条将只取前8条
640
+ news = (1..count.to_i).each_with_object([]) { |n, memo| memo << { title: '新闻标题', content: "第#{n}条新闻的内容#{n.hash}" } }
641
+ request.reply.news(news) do |article, n, index| # 回复"articles"
642
+ article.item title: "#{index} #{n[:title]}", description: n[:content], pic_url: 'http://www.baidu.com/img/bdlogo.gif', url: 'http://www.baidu.com/'
643
+ end
644
+ end
645
+
646
+ # 当用户加关注
647
+ on :event, with: 'subscribe' do |request|
648
+ request.reply.text "User #{request[:FromUserName]} subscribe now"
649
+ end
650
+
651
+ # 公众号收到未关注用户扫描qrscene_xxxxxx二维码时。注意此次扫描事件将不再引发上条的用户加关注事件
652
+ on :scan, with: 'qrscene_xxxxxx' do |request, ticket|
653
+ request.reply.text "Unsubscribe user #{request[:FromUserName]} Ticket #{ticket}"
654
+ end
655
+
656
+ # 公众号收到已关注用户扫描创建二维码的scene_id事件时
657
+ on :scan, with: 'scene_id' do |request, ticket|
658
+ request.reply.text "Subscribe user #{request[:FromUserName]} Ticket #{ticket}"
659
+ end
660
+
661
+ # 当没有任何on :scan事件处理已关注用户扫描的scene_id时
662
+ on :event, with: 'scan' do |request|
663
+ if request[:EventKey].present?
664
+ request.reply.text "event scan got EventKey #{request[:EventKey]} Ticket #{request[:Ticket]}"
665
+ end
666
+ end
667
+
668
+ # 企业号收到EventKey 为二维码扫描结果事件时
669
+ on :scan, with: 'BINDING_QR_CODE' do |request, scan_result, scan_type|
670
+ request.reply.text "User #{request[:FromUserName]} ScanResult #{scan_result} ScanType #{scan_type}"
671
+ end
672
+
673
+ # 企业号收到EventKey 为CODE 39码扫描结果事件时
674
+ on :scan, with: 'BINDING_BARCODE' do |message, scan_result|
675
+ if scan_result.start_with? 'CODE_39,'
676
+ message.reply.text "User: #{message[:FromUserName]} scan barcode, result is #{scan_result.split(',')[1]}"
677
+ end
678
+ end
679
+
680
+ # 当用户点击菜单时
681
+ on :click, with: 'BOOK_LUNCH' do |request, key|
682
+ request.reply.text "User: #{request[:FromUserName]} click #{key}"
683
+ end
684
+
685
+ # 当用户点击菜单时
686
+ on :view, with: 'http://wechat.somewhere.com/view_url' do |request, view|
687
+ request.reply.text "#{request[:FromUserName]} view #{view}"
688
+ end
689
+
690
+ # 处理图片信息
691
+ on :image do |request|
692
+ request.reply.image(request[:MediaId]) #直接将图片返回给用户
693
+ end
694
+
695
+ # 处理语音信息
696
+ on :voice do |request|
697
+ request.reply.voice(request[:MediaId]) #直接语音音返回给用户
698
+ end
699
+
700
+ # 处理视频信息
701
+ on :video do |request|
702
+ nickname = wechat.user(request[:FromUserName])['nickname'] #调用 api 获得发送者的nickname
703
+ request.reply.video(request[:MediaId], title: '回声', description: "#{nickname}发来的视频请求") #直接视频返回给用户
704
+ end
705
+
706
+ # 处理地理位置消息
707
+ on :label_location do |request|
708
+ request.reply.text("Label: #{request[:Label]} Location_X: #{request[:Location_X]} Location_Y: #{request[:Location_Y]} Scale: #{request[:Scale]}")
709
+ end
710
+
711
+ # 处理上报地理位置事件
712
+ on :location do |request|
713
+ request.reply.text("Latitude: #{request[:Latitude]} Longitude: #{request[:Longitude]} Precision: #{request[:Precision]}")
714
+ end
715
+
716
+ # 当用户取消关注订阅
717
+ on :event, with: 'unsubscribe' do |request|
718
+ request.reply.success # user can not receive this message
719
+ end
720
+
721
+ # 成员进入应用的事件推送
722
+ on :event, with: 'enter_agent' do |request|
723
+ request.reply.text "#{request[:FromUserName]} enter agent app now"
724
+ end
725
+
726
+ # 当异步任务增量更新成员完成时推送
727
+ on :batch_job, with: 'sync_user' do |request, batch_job|
728
+ request.reply.text "job #{batch_job[:JobId]} finished, return code #{batch_job[:ErrCode]}, return message #{batch_job[:ErrMsg]}"
729
+ end
730
+
731
+ # 当异步任务全量覆盖成员完成时推送
732
+ on :batch_job, with: 'replace_user' do |request, batch_job|
733
+ request.reply.text "job #{batch_job[:JobId]} finished, return code #{batch_job[:ErrCode]}, return message #{batch_job[:ErrMsg]}"
734
+ end
735
+
736
+ # 当异步任务邀请成员关注完成时推送
737
+ on :batch_job, with: 'invite_user' do |request, batch_job|
738
+ request.reply.text "job #{batch_job[:JobId]} finished, return code #{batch_job[:ErrCode]}, return message #{batch_job[:ErrMsg]}"
739
+ end
740
+
741
+ # 当异步任务全量覆盖部门完成时推送
742
+ on :batch_job, with: 'replace_party' do |request, batch_job|
743
+ request.reply.text "job #{batch_job[:JobId]} finished, return code #{batch_job[:ErrCode]}, return message #{batch_job[:ErrMsg]}"
744
+ end
745
+
746
+ # 事件推送群发结果
747
+ on :event, with: 'masssendjobfinish' do |request|
748
+ # https://mp.weixin.qq.com/wiki?action=doc&id=mp1481187827_i0l21&t=0.03571905015619936#8
749
+ request.reply.success # request is XML result hash.
750
+ end
751
+
752
+ # 当无任何responder处理用户信息时,使用这个responder处理
753
+ on :fallback, respond: 'fallback message'
754
+ end
755
+ ```
756
+
757
+ 在controller中使用`wechat_responder`引入Responder DSL, 之后可以用
758
+
759
+ ```
760
+ on <message_type> do |message|
761
+ message.reply.text "some text"
762
+ end
763
+ ```
764
+
765
+ 来响应用户信息。
766
+
767
+ 目前支持的message_type有如下几种
768
+
769
+ - :text 响应文字消息,可以用`:with`参数来匹配文本内容 `on(:text, with:'help'){|message, content| ...}`
770
+ - :image 响应图片消息
771
+ - :voice 响应语音消息
772
+ - :shortvideo 响应短视频消息
773
+ - :video 响应视频消息
774
+ - :label_location 响应地理位置消息
775
+ - :link 响应链接消息
776
+ - :event 响应事件消息, 可以用`:with`参数来匹配事件类型,同文字消息类似,支持正则表达式匹配
777
+ - :click 虚拟响应事件消息, 微信传入:event,但gem内部会单独处理
778
+ - :view 虚拟响应事件消息, 微信传入:event,但gem内部会单独处理
779
+ - :scan 虚拟响应事件消息
780
+ - :batch_job 虚拟响应事件消息
781
+ - :location 虚拟响应上报地理位置事件消息
782
+ - :fallback 默认响应,当收到的消息无法被其他responder响应时,会使用这个responder.
783
+
784
+ ### 多客服消息转发
785
+
786
+ ```ruby
787
+ class WechatsController < ActionController::Base
788
+ # 当无任何responder处理用户信息时,转发至客服处理。
789
+ on :fallback do |message|
790
+ message.reply.transfer_customer_service
791
+ end
792
+ end
793
+ ```
794
+
795
+ 注意设置了[多客服消息转发](http://dkf.qq.com/)后,不能再添加`默认文字信息responder`,否则文字消息将得不到转发。
796
+
797
+ ### 通知
798
+
799
+ 现支持以下通知:
800
+
801
+ * `wechat.responder.after_create` data 包含 request<Wechat::Message> 和 response<Wechat::Message>
802
+
803
+ 使用示例:
804
+
805
+ ```ruby
806
+ ActiveSupport::Notifications.subscribe('wechat.responder.after_create') do |name, started, finished, unique_id, data|
807
+ WechatLog.create request: data[:request], response: data[:response]
808
+ end
809
+ ```
810
+
811
+ ## 已知问题
812
+
813
+ * 企业号接受菜单消息时,Wechat腾讯服务器无法解析部分域名,请使用IP绑定回调URL,用户的普通消息目前不受影响。
814
+ * 企业号全量覆盖成员使用的csv通讯录格式,直接将下载的模板导入[是不工作的](http://qydev.weixin.qq.com/qa/index.php?qa=13978),必须使用Excel打开,然后另存为csv格式才会变成合法格式。
815
+ * 如果使用nginx+unicron部署方案,并且使用了https,必须设置`trusted_domain_fullname`为https,否则会导致JS-SDK签名失效。