yol_qy_weixin 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 492bc402556844ff268a6cc561decc01273d6c625cfbe3d6fead5cd990a3c543
4
+ data.tar.gz: f245159f2eb8175688c586950c1ddc4e0ef20f2a1837f430ceab8898b6bf3581
5
+ SHA512:
6
+ metadata.gz: '04889e7bf6966e97dd0b9bb3e5d68cd686216a8cd9878f616388f12915d3789ac1de792452b97fabd968a6673dd2364b1acb30fbd8dfe8893cbd28bbe2de80a0'
7
+ data.tar.gz: e8eb257736eff86043bfe00a2a5e98fd2135d106d825dbe472eec07b9e82d0da0a4ac0a8add84cc84a72fffebe68a4de3cb7f659bff6a03bb93817c06fcee519
data/.DS_Store ADDED
Binary file
data/.gitignore ADDED
@@ -0,0 +1,56 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /spec/examples.txt
9
+ /test/tmp/
10
+ /test/version_tmp/
11
+ /tmp/
12
+
13
+ # Used by dotenv library to load environment variables.
14
+ # .env
15
+
16
+ # Ignore Byebug command history file.
17
+ .byebug_history
18
+
19
+ ## Specific to RubyMotion:
20
+ .dat*
21
+ .repl_history
22
+ build/
23
+ *.bridgesupport
24
+ build-iPhoneOS/
25
+ build-iPhoneSimulator/
26
+
27
+ ## Specific to RubyMotion (use of CocoaPods):
28
+ #
29
+ # We recommend against adding the Pods directory to your .gitignore. However
30
+ # you should judge for yourself, the pros and cons are mentioned at:
31
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
32
+ #
33
+ # vendor/Pods/
34
+
35
+ ## Documentation cache and generated files:
36
+ /.yardoc/
37
+ /_yardoc/
38
+ /doc/
39
+ /rdoc/
40
+
41
+ ## Environment normalization:
42
+ /.bundle/
43
+ /vendor/bundle
44
+ /lib/bundler/man/
45
+
46
+ # for a library or gem, you might want to ignore these files since the code is
47
+ # intended to run in multiple environments; otherwise, check them in:
48
+ # Gemfile.lock
49
+ # .ruby-version
50
+ # .ruby-gemset
51
+
52
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
53
+ .rvmrc
54
+
55
+ # Used by RuboCop. Remote config files pulled in from inherit_from directive.
56
+ # .rubocop-https?--*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in yol_qy_weixin.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,44 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ yol_qy_weixin (0.0.3)
5
+ multi_xml
6
+ nokogiri
7
+ roxml
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ activesupport (6.0.3.2)
13
+ concurrent-ruby (~> 1.0, >= 1.0.2)
14
+ i18n (>= 0.7, < 2)
15
+ minitest (~> 5.1)
16
+ tzinfo (~> 1.1)
17
+ zeitwerk (~> 2.2, >= 2.2.2)
18
+ concurrent-ruby (1.1.8)
19
+ i18n (1.8.9)
20
+ concurrent-ruby (~> 1.0)
21
+ minitest (5.14.4)
22
+ multi_xml (0.6.0)
23
+ nokogiri (1.11.1-x86_64-darwin)
24
+ racc (~> 1.4)
25
+ racc (1.5.2)
26
+ rake (13.0.1)
27
+ roxml (4.1.1)
28
+ activesupport (>= 4.0)
29
+ nokogiri (>= 1.3.3)
30
+ thread_safe (0.3.6)
31
+ tzinfo (1.2.9)
32
+ thread_safe (~> 0.1)
33
+ zeitwerk (2.4.0)
34
+
35
+ PLATFORMS
36
+ ruby
37
+
38
+ DEPENDENCIES
39
+ bundler
40
+ rake
41
+ yol_qy_weixin!
42
+
43
+ BUNDLED WITH
44
+ 2.1.4
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 TODO: Write your name
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,370 @@
1
+ # YolQyWeixin
2
+
3
+ This project rocks and uses MIT-LICENSE.
4
+
5
+ https://rubygems.org/gems/yol_qy_weixin
6
+
7
+ [![Gem Version](https://badge.fury.io/rb/yol_qy_weixin.svg)](http://badge.fury.io/rb/yol_qy_weixin)
8
+
9
+ **企业号对应多个管理组,请前往 `设置` => `权限管理` 任意创建一个管理组,在管理组最下角即可获取 CorpID Secret**
10
+
11
+ **有问题请及时提issue**
12
+
13
+ ```ruby
14
+ gem "qy_wechat_api", git: "https://github.com/lanrion/qy_wechat_api.git"
15
+ ```
16
+
17
+ # 配置
18
+
19
+ ## jsticket、suite_token存储方案
20
+ 如果你是在Rails框架下,默认情况下直接使用Rails.cache,如果你想独立cache,请配置如下:
21
+ ```ruby
22
+ QyWechatApi.configure do |config|
23
+ config.cache_store = YourCustomCacheStore
24
+ end
25
+ ```
26
+ cache_store按照Rails.cache的接口实现,强烈建议使用ActiveSupport::Cache
27
+
28
+ ## 日志配置
29
+ 如果你是在Rails框架下,默认情况下直接使用Rails.logger,如果你想独立cache,请配置如下:
30
+ ```ruby
31
+ QyWechatApi.configure do |config|
32
+ config.logger = YourCustomLogger
33
+ end
34
+ ```
35
+ logger按照Rails.logger的接口实现,强烈建议使用ActiveSupport::Logger
36
+
37
+ ## Token 存储方案(TODO: 待重构直接使用cache_store)
38
+
39
+ ### 对象存储
40
+ 如果你是单个企业号,建议使用这个方案,无需任何配置即可使用。
41
+
42
+ ### Redis 存储
43
+ ```ruby
44
+ redis = Redis.new(host: "127.0.0.1", port: "6379")
45
+
46
+ namespace = "qy_wechat_api:redis_storage"
47
+
48
+ # cleanup keys in the current namespace when restart server everytime.
49
+ exist_keys = redis.keys("#{namespace}:*")
50
+ exist_keys.each{|key|redis.del(key)}
51
+
52
+ redis_with_ns = Redis::Namespace.new("#{namespace}", redis: redis)
53
+
54
+ QyWechatApi.configure do |config|
55
+ config.redis = redis_with_ns
56
+ end
57
+ ```
58
+
59
+ # API基本用法
60
+
61
+ 请务必结合:http://qydev.weixin.qq.com/wiki/index.php 理解以下API参数使用。
62
+
63
+ ## 初始化
64
+
65
+ ```ruby
66
+ group_client = QyWechatApi::Client.new(corpid, corpsecret)
67
+
68
+ # 为了确保用户输入的corpid, corpsecret是准确的,请务必执行:
69
+ group_client.is_valid?
70
+ ```
71
+
72
+ 如果需要使用通过第三方应用 **获取企业号access_token** API 获取的 access_token
73
+ 做如下处理:
74
+
75
+ ```ruby
76
+ options = {access_token: "access_token"}
77
+ # redis_key 也可定制
78
+ group_client = QyWechatApi::Client.new(corpid, corpsecret, options)
79
+ ```
80
+
81
+ ## 部门
82
+
83
+ ```ruby
84
+ group_client.department.create(name, parent_id, order=nil, id=nil)
85
+ group_client.department.update(id, name, parent_id, order=nil)
86
+ group_client.department.delete(id)
87
+ group_client.department.list
88
+ ```
89
+
90
+ ## 成员
91
+
92
+ ```ruby
93
+ # 创建成员
94
+ group_client.user.create(user_id, name, options={})
95
+
96
+ # 更新成员
97
+ group_client.user.update(user_id, options={})
98
+
99
+ # 删除成员
100
+ group_client.user.delete(user_id)
101
+
102
+ # 批量删除成员
103
+ group_client.user.batch_delete(user_ids)
104
+
105
+ # 获取成员
106
+ group_client.user.get(user_id)
107
+
108
+ # 获取部门成员
109
+ group_client.user.simple_list(department_id, fetch_child=nil, status=nil)
110
+
111
+ # 获取部门成员(详情)
112
+ group_client.user.full_list(department_id, fetch_child=nil, status=nil)
113
+
114
+ # 邀请成员关注
115
+ group_client.user.send_invitation(user_id, tips=nil)
116
+
117
+ # userid转换成openid接口(企业支付需要使用到)
118
+ group_client.covert_to_open_id(user_id, agent_id="")
119
+
120
+ # openid转换成userid接口
121
+ group_client.covert_to_user_id(open_id)
122
+ ```
123
+
124
+ ## 标签
125
+
126
+ ```ruby
127
+ group_client.tag.create(name)
128
+ group_client.tag.update(id, name)
129
+ group_client.tag.delete(id)
130
+ group_client.tag.get(id)
131
+ group_client.tag.add_tag_users(id, user_ids)
132
+ group_client.tag.delete_tag_users(id, user_ids)
133
+ group_client.tag.list
134
+ ```
135
+
136
+ ## 自定义菜单
137
+
138
+ menu_json的生成方法请参考:
139
+ https://github.com/lanrion/weixin_rails_middleware/wiki/DIY-menu
140
+
141
+ ```ruby
142
+ group_client.menu.create(menu_json, app_id)
143
+ group_client.menu.delete(app_id)
144
+ group_client.menu.get(app_id)
145
+ ```
146
+
147
+ ## Oauth2用法
148
+
149
+ 先要配置你应用的 可信域名 `2458023e.ngrok.com`
150
+ state 为开发者自定义参数,可选
151
+
152
+ ```ruby
153
+ # 生成授权url
154
+ group_client.oauth.authorize_url("http://2458023e.ngrok.com", "state")
155
+
156
+ # 获取code后,获取用户信息
157
+ # app_id: 跳转链接时所在的企业应用ID
158
+ group_client.oauth.get_user_info("code", "app_id")
159
+ ```
160
+
161
+ ## 发送消息
162
+
163
+ ```ruby
164
+ # params: (users, parties, tags, agent_id, content, safe=0)
165
+ # users, parties, tags 如果是多个用户,传数组,如果是全部,则直接传 "@all"
166
+ group_client.message.send_text("@all", "@all", "@all", app_id, text_message)
167
+ ```
168
+ **其他发送消息方法请查看 api/message.rb**
169
+
170
+ ## 上传多媒体文件
171
+ ```ruby
172
+ # params: media, media_type
173
+ group_client.media.upload(image_jpg_file, "image")
174
+
175
+ # 获取下载链接
176
+ # 返回一个URL,请开发者自行使用此url下载
177
+ group_client.media.get_media_by_id(media_id)
178
+
179
+ # 上传永久图文素材
180
+ # articles 为图文列表:
181
+ {
182
+ "title": "Title01",
183
+ "thumb_media_id": "2-G6nrLmr5EC3MMb_-zK1dDdzmd0p7cNliYu9V5w7o8K0",
184
+ "author": "zs",
185
+ "content_source_url": "",
186
+ "content": "Content001",
187
+ "digest": "airticle01",
188
+ "show_cover_pic": "0"
189
+ }
190
+ group_client.material.add_mpnews(agent_id, articles)
191
+
192
+ # 更新图文素材
193
+ group_client.material.update_mpnews(agent_id, media_id, articles=[])
194
+
195
+ # 上传其他类型永久素材
196
+ # type: "image", "voice", "video", "file"
197
+ # file: File
198
+ group_client.material.add_material(agent_id, type, file)
199
+
200
+ # 删除永久素材
201
+ group_client.material.del(agent_id, media_id)
202
+
203
+ # 获取素材总数
204
+ group_client.material.get_count(agent_id)
205
+
206
+ # 获取素材列表
207
+ group_client.material.list(agent_id, type, offset, count=20)
208
+ ```
209
+
210
+ ## 第三方应用
211
+
212
+ 这里特别注意:保留 suite_access_token的cache是直接利用了前文配置的cache_store缓存。
213
+
214
+ ### api 使用介绍
215
+
216
+ ```ruby
217
+ suite_api = QyWechatApi::Suite.service(suite_id, suite_secret, suite_ticket)
218
+
219
+ # 获取预授权码
220
+ suite_api.get_pre_auth_code(appid=[])
221
+
222
+ # 获取企业号的永久授权码
223
+ suite_api.get_permanent_code(auth_code)
224
+
225
+ # 获取企业号的授权信息
226
+ suite_api.get_auth_info(auth_corpid, code)
227
+
228
+ # 获取企业号应用
229
+ suite_api.get_agent(auth_corpid, code, agent_id)
230
+
231
+ # 设置授权方的企业应用的选项设置信息
232
+ suite_api.set_agent(auth_corpid, permanent_code, agent_info)
233
+
234
+ # 调用企业接口所需的access_token
235
+ suite_api.get_corp_token(auth_corpid, permanent_code)
236
+
237
+ # 生成授权URL
238
+ suite_api.auth_url(code, uri, state="suite")
239
+
240
+ ```
241
+
242
+ ## 企业号登录授权
243
+
244
+ ```ruby
245
+ # 获取登录授权URL
246
+ # state default 'qy_wechat', option
247
+ # 此处授权回调时会传递auth_code、expires_in,auth_code用于get_login_info(获取企业号管理员登录信息)接口使用
248
+ group_client.auth_login.auth_login_url("redirect_uri", "state")
249
+
250
+ # 获取应用提供商凭证
251
+ # provider_secret:提供商的secret,在提供商管理页面可见
252
+ # 此处会返回:provider_access_token(已通过QyWechatApi.cache缓存7100s)
253
+ group_client.auth_login.get_provider_token(provider_secret)
254
+
255
+ # 通过传递provider_access_token,获取企业号管理员登录信息
256
+ group_client.auth_login.get_login_info(auth_code, provider_access_token)
257
+
258
+ # 通过传递provider_secret,获取企业号管理员登录信息
259
+ group_client.auth_login.get_login_info_by_secret(auth_code, provider_secret)
260
+
261
+ # 获取登录企业号官网的url
262
+ group_client.auth_login.get_login_url(ticket, provider_token, target, agentid=nil)
263
+ ```
264
+
265
+ ## 异步任务接口
266
+
267
+ ```ruby
268
+ # 邀请成员关注
269
+ group_client.async_task.invite_user(callback, invite_info={})
270
+ # 增量更新成员
271
+ group_client.async_task.sync_user(callback, media_id)
272
+ # 全量覆盖成员
273
+ group_client.async_task.replace_user(callback, media_id)
274
+ # 全量覆盖部门
275
+ group_client.async_task.replace_party(callback, media_id)
276
+ # 获取异步任务结果
277
+ group_client.async_task.get_result(job_id)
278
+ ```
279
+
280
+ ## 获取js api签名包
281
+ ```ruby
282
+ group_client.sign_package(request.url)
283
+ ```
284
+
285
+ ## 管理企业号应用
286
+
287
+ ```ruby
288
+ # 获取应用概况列表
289
+ group_client.agent.list
290
+
291
+ # 设置企业号应用
292
+ # agentid 企业应用的id
293
+ # report_location_flag 企业应用是否打开地理位置上报 0:不上报;1:进入会话上报;2:持续上报
294
+ # logo_mediaid 企业应用头像的mediaid,通过多媒体接口上传图片获得mediaid,上传后会自动裁剪成方形和圆形两个头像
295
+ # name 企业应用名称
296
+ # description 企业应用详情
297
+ # redirect_domain 企业应用可信域名
298
+ # isreportuser 是否接收用户变更通知。0:不接收;1:接收
299
+ # isreportenter 是否上报用户进入应用事件。0:不接收;1:接收
300
+ group_client.agent.set()
301
+
302
+ ## 获取企业号应用
303
+ group_client.agent.get(agent_id)
304
+ ```
305
+
306
+ ### 应用套件的回调通知处理
307
+
308
+ Wiki: http://qydev.weixin.qq.com/wiki/index.php?title=%E7%AC%AC%E4%B8%89%E6%96%B9%E5%9B%9E%E8%B0%83%E5%8D%8F%E8%AE%AE
309
+
310
+ ```ruby
311
+ class QyServicesController < ApplicationController
312
+ skip_before_filter :verify_authenticity_token, only: :receive_ticket
313
+
314
+ # TODO: 需要创建表: suites
315
+ def receive_ticket
316
+ param_xml = request.body.read
317
+ aes_key = "NJgquXf6vnYlGpD5APBqlndAq7Nx8fToiEz5Wbaka47"
318
+ aes_key = Base64.decode64("#{aes_key}=")
319
+ hash = MultiXml.parse(param_xml)['xml']
320
+ @body_xml = OpenStruct.new(hash)
321
+ suite_id = "tj86cd0f5b8f7ce20d"
322
+ content = QyWechat::Prpcrypt.decrypt(aes_key, @body_xml.Encrypt, suite_id)[0]
323
+ hash = MultiXml.parse(content)["xml"]
324
+ Rails.logger.info hash
325
+ render text: "success"
326
+ # {"SuiteId"=>"tj86cd0f5b8f7ce20d",
327
+ # "SuiteTicket"=>"Pb5M0PEQFZSNondlK1K_atu2EoobY9piMcQCdE3URiCG3aTwX5WBTQaSsqCzaD-0",
328
+ # "InfoType"=>"suite_ticket",
329
+ # "TimeStamp"=>"1426988061"}
330
+ end
331
+ end
332
+ ```
333
+
334
+ ### 企业号消息接口
335
+
336
+ Wiki: http://qydev.weixin.qq.com/wiki/index.php?title=%E4%BC%81%E4%B8%9A%E5%8F%B7%E6%B6%88%E6%81%AF%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E
337
+
338
+ ```ruby
339
+ group_client.chat.send_single_text(sender, user_id, msg)
340
+ group_client.chat.send_single_image(sender, user_id, media_id)
341
+ group_client.chat.send_single_file(sender, user_id, media_id)
342
+ group_client.chat.send_group_text(sender, chat_id, msg)
343
+ group_client.chat.send_group_image(sender, chat_id, media_id)
344
+ group_client.chat.send_group_file(sender, chat_id, media_id)
345
+ ```
346
+
347
+ ### 企业客服服务
348
+
349
+ Wiki: http://qydev.weixin.qq.com/wiki/index.php?title=企业客服接口说明
350
+
351
+ ```ruby
352
+ # msg_struct请根据文档结构拼接传入
353
+ group_client.kf.send(msg_struct)
354
+ ```
355
+
356
+ ### 企业号摇一摇周边
357
+
358
+ Wiki: http://qydev.weixin.qq.com/wiki/index.php?title=获取设备及用户信息
359
+
360
+ ```ruby
361
+ # 获取设备及用户信息
362
+ # 摇周边业务的ticket,可在摇到的URL中得到,ticket生效时间为30分钟,每一次摇都会重新生成新的ticket
363
+ group_client.get_shake_info(ticket)
364
+ ```
365
+
366
+ ## 捐赠支持
367
+
368
+ 如果你觉得我的gem对你有帮助,欢迎打赏支持,:smile:
369
+
370
+ ![](https://raw.githubusercontent.com/lanrion/my_config/master/imagex/donation_me_wx.jpg)
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/lib/.DS_Store ADDED
Binary file
@@ -0,0 +1,20 @@
1
+ require "monitor"
2
+ require "redis"
3
+ require 'digest/md5'
4
+ module YolQyWeixin
5
+ class Client
6
+
7
+ include Connection::Base
8
+ include Connection::Qrcode
9
+ include Connection::Template
10
+ include Connection::User
11
+
12
+ attr_accessor :corpid, :secret, :redis
13
+
14
+ def initialize(options = {})
15
+ @corpid = options[:corpid] || YolQyWeixin.configuration.corpid
16
+ @secret = options[:secret] || YolQyWeixin.configuration.secret
17
+ @redis = options[:redis] || YolQyWeixin.configuration.redis
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,3 @@
1
+ Dir["#{File.dirname(__FILE__)}/connections/*.rb"].each do |path|
2
+ require path
3
+ end
@@ -0,0 +1,63 @@
1
+ module YolQyWeixin
2
+ module Connection
3
+ module Base
4
+ def http_post(url, params)
5
+ uri = URI(url)
6
+ req = Net::HTTP.new(uri.host, uri.port)
7
+ req.use_ssl = true
8
+ res = req.post("#{uri.path}?#{uri.query}", params.to_json)
9
+ handle_res(res)
10
+ end
11
+
12
+ def http_get(url)
13
+ uri = URI(url)
14
+ req = Net::HTTP.new(uri.host, uri.port)
15
+ req.use_ssl = true
16
+ res = req.get("#{uri.path}?#{uri.query}")
17
+ handle_res(res)
18
+ end
19
+
20
+ def get_access_token
21
+ # access_token = redis.find("access_token_#{appid}")
22
+
23
+ # if access_token.nil?
24
+ # access_token_res = get_token(corpid, secret)
25
+
26
+ # # TODO:
27
+ # access_token = JSON.parse(access_token_res)["access_token"] rescue nil
28
+
29
+ # if access_token.nil?
30
+ # raise Exception.new("Weixin access token authorize false, appid: #{appid},
31
+ # appsecret: #{appsecret}, access_token_res: #{access_token_res.to_s}")
32
+ # else
33
+ # redis.save("access_token_#{appid}", access_token)
34
+ # redis.expire("access_token_#{appid}", 7200)
35
+ # end
36
+ # end
37
+
38
+ # access_token
39
+ access_token_res = get_token(corpid, secret)
40
+ access_token = access_token_res["access_token"] rescue nil
41
+ access_token
42
+ end
43
+
44
+ private
45
+
46
+ def get_token(app_id, app_secret)
47
+ http_get(token_url(app_id, app_secret))
48
+ end
49
+
50
+ def token_url(corpid, secret)
51
+ "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=#{corpid}&corpsecret=#{secret}"
52
+ end
53
+
54
+ def handle_res(res)
55
+ if res.code == '200'
56
+ return JSON.parse(res.body)
57
+ else
58
+ return {:code => res.code}.to_json
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,17 @@
1
+ module YolQyWeixin
2
+ module Connection
3
+ module Qrcode
4
+ def get_limited_qr(scene_id)
5
+ post_params = {"expire_seconds": 3600, "action_name": "QR_SCENE", "action_info": {"scene": {"scene_id": scene_id}}}
6
+ http_post(qrcode_create_url, post_params)
7
+ end
8
+
9
+ private
10
+
11
+ def qrcode_create_url
12
+ "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=#{get_access_token}"
13
+ end
14
+ end
15
+ end
16
+ end
17
+
@@ -0,0 +1,15 @@
1
+ module YolQyWeixin
2
+ module Connection
3
+ module Template
4
+ def send_template_message(template_params)
5
+ http_post(message_template_url, template_params)
6
+ end
7
+
8
+ private
9
+
10
+ def message_template_url
11
+ "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=#{get_access_token}"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module YolQyWeixin
2
+ module Connection
3
+ module User
4
+ def get_user_info(open_id)
5
+ http_get(user_info_url(open_id))
6
+ end
7
+
8
+ private
9
+
10
+ def user_info_url(open_id)
11
+ "https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token=#{get_access_token}&userid=#{open_id}"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,28 @@
1
+ # encoding: utf-8
2
+
3
+ module YolQyWeixin
4
+ module PKCS7Encoder
5
+ extend self
6
+
7
+ BLOCK_SIZE = 32
8
+
9
+ def decode(text)
10
+ pad = text[-1].ord
11
+ pad = 0 if (pad < 1 || pad > BLOCK_SIZE)
12
+ size = text.size - pad
13
+ text[0...size]
14
+ end
15
+
16
+ # 对需要加密的明文进行填充补位
17
+ # 返回补齐明文字符串
18
+ def encode(text)
19
+ # 计算需要填充的位数
20
+ amount_to_pad = BLOCK_SIZE - (text.length % BLOCK_SIZE)
21
+ amount_to_pad = BLOCK_SIZE if amount_to_pad == 0
22
+ # 获得补位所用的字符
23
+ pad_chr = amount_to_pad.chr
24
+ "#{text}#{pad_chr * amount_to_pad}"
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,48 @@
1
+ # encoding: utf-8
2
+ require "yol_qy_weixin/helpers/pkcs7_encoder"
3
+ module YolQyWeixin
4
+ module Prpcrypt
5
+ extend self
6
+
7
+ # 对密文进行解密.
8
+ # text 需要解密的密文
9
+ def decrypt(aes_key, text, app_id)
10
+ status = 200
11
+ text = Base64.decode64(text)
12
+ text = handle_cipher(:decrypt, aes_key, text)
13
+ result = PKCS7Encoder.decode(text)
14
+ content = result[16...result.length]
15
+ len_list = content[0...4].unpack("N")
16
+ xml_len = len_list[0]
17
+ xml_content = content[4...4 + xml_len]
18
+ from_app_id = content[xml_len + 4...content.size]
19
+ # TODO: refactor
20
+ if app_id != from_app_id
21
+ # raise BizErr, "#{__FILE__}:#{__LINE__} Failure because app_id != from_app_id"
22
+ status = 500
23
+ end
24
+ [xml_content, status]
25
+ end
26
+
27
+ # 加密
28
+ def encrypt(aes_key, text, app_id)
29
+ text = text.force_encoding("ASCII-8BIT")
30
+ random = SecureRandom.hex(8)
31
+ msg_len = [text.length].pack("N")
32
+ text = "#{random}#{msg_len}#{text}#{app_id}"
33
+ text = PKCS7Encoder.encode(text)
34
+ text = handle_cipher(:encrypt, aes_key, text)
35
+ Base64.encode64(text)
36
+ end
37
+
38
+ private
39
+ def handle_cipher(action, aes_key, text)
40
+ cipher = OpenSSL::Cipher.new('AES-256-CBC')
41
+ cipher.send(action)
42
+ cipher.padding = 0
43
+ cipher.key = aes_key
44
+ cipher.iv = aes_key[0...16]
45
+ cipher.update(text) + cipher.final
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,13 @@
1
+ module YolQyWeixin
2
+ class Configuration
3
+
4
+ OPTIONS = [:corpid, :secret, :redis].freeze
5
+
6
+ attr_accessor :corpid
7
+
8
+ attr_accessor :secret
9
+
10
+ attr_accessor :redis
11
+
12
+ end
13
+ end
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+ # 标准的回包
3
+ # <xml>
4
+ # <Encrypt><![CDATA[msg_encrypt]]></Encrypt>
5
+ # <MsgSignature><![CDATA[msg_signature]]></MsgSignature>
6
+ # <TimeStamp>timestamp</TimeStamp>
7
+ # <Nonce><![CDATA[nonce]]></Nonce>
8
+ # </xml>
9
+
10
+ module YolQyWeixin
11
+ class EncryptMessage
12
+ include ROXML
13
+ xml_name :xml
14
+
15
+ xml_accessor :Encrypt, :cdata => true
16
+ xml_accessor :Nonce, :cdata => true
17
+ xml_accessor :TimeStamp, :as => Integer
18
+ xml_accessor :MsgSignature, :cdata => true
19
+
20
+ def to_xml
21
+ super.to_xml(:encoding => 'UTF-8', :indent => 0, :save_with => 0)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,187 @@
1
+ # encoding: utf-8
2
+ # multi_xml will use Nokogiri if it is available
3
+ MultiXml.parser = :nokogiri
4
+
5
+ module YolQyWeixin
6
+
7
+ class Message
8
+
9
+ def initialize(hash)
10
+ @source = OpenStruct.new(hash)
11
+ end
12
+
13
+ def method_missing(method, *args, &block)
14
+ @source.send(method, *args, &block)
15
+ end
16
+
17
+ def CreateTime
18
+ @source.CreateTime.to_i
19
+ end
20
+
21
+ def MsgId
22
+ @source.MsgId.to_i rescue nil
23
+ end
24
+
25
+ def self.factory(xml)
26
+ hash = MultiXml.parse(xml)['xml']
27
+
28
+ # 优先检测服务器信息
29
+ unless hash['InfoType'].to_s.empty?
30
+ case hash['InfoType']
31
+ when 'component_verify_ticket'
32
+ TicketMessage.new(hash)
33
+ when 'unauthorized'
34
+ AuthorizeMessage.new(hash)
35
+ else
36
+ raise ArgumentError, 'Unknown Weixin Message' + hash.to_s
37
+ end
38
+ else
39
+ case hash['MsgType']
40
+ when 'text'
41
+ TextMessage.new(hash)
42
+ when 'image'
43
+ ImageMessage.new(hash)
44
+ when 'location'
45
+ LocationMessage.new(hash)
46
+ when 'link'
47
+ LinkMessage.new(hash)
48
+ when 'event'
49
+ EventMessage.new(hash)
50
+ when 'voice'
51
+ VoiceMessage.new(hash)
52
+ when 'video'
53
+ VideoMessage.new(hash)
54
+ else
55
+ raise ArgumentError, 'Unknown Weixin Message' + hash.to_s
56
+ end
57
+ end
58
+ end
59
+
60
+ end
61
+
62
+ # <xml>
63
+ # <ToUserName><![CDATA[toUser]]></ToUserName>
64
+ # <FromUserName><![CDATA[fromUser]]></FromUserName>
65
+ # <CreateTime>1348831860</CreateTime>
66
+ # <MsgType><![CDATA[text]]></MsgType>
67
+ # <Content><![CDATA[this is a test]]></Content>
68
+ # <MsgId>1234567890123456</MsgId>
69
+ # </xml>
70
+ TextMessage = Class.new(Message)
71
+
72
+ # <xml>
73
+ # <ToUserName><![CDATA[toUser]]></ToUserName>
74
+ # <FromUserName><![CDATA[fromUser]]></FromUserName>
75
+ # <CreateTime>1348831860</CreateTime>
76
+ # <MsgType><![CDATA[image]]></MsgType>
77
+ # <PicUrl><![CDATA[this is a url]]></PicUrl>
78
+ # <MsgId>1234567890123456</MsgId>
79
+ # </xml>
80
+ ImageMessage = Class.new(Message)
81
+
82
+ # <xml>
83
+ # <ToUserName><![CDATA[toUser]]></ToUserName>
84
+ # <FromUserName><![CDATA[fromUser]]></FromUserName>
85
+ # <CreateTime>1351776360</CreateTime>
86
+ # <MsgType><![CDATA[link]]></MsgType>
87
+ # <Title><![CDATA[公众平台官网链接]]></Title>
88
+ # <Description><![CDATA[公众平台官网链接]]></Description>
89
+ # <Url><![CDATA[url]]></Url>
90
+ # <MsgId>1234567890123456</MsgId>
91
+ # </xml>
92
+ LinkMessage = Class.new(Message)
93
+
94
+ # <xml>
95
+ # <ToUserName><![CDATA[toUser]]></ToUserName>
96
+ # <FromUserName><![CDATA[FromUser]]></FromUserName>
97
+ # <CreateTime>123456789</CreateTime>
98
+ # <MsgType><![CDATA[event]]></MsgType>
99
+ # <Event><![CDATA[EVENT]]></Event>
100
+ # <EventKey><![CDATA[EVENTKEY]]></EventKey>
101
+ # </xml>
102
+ EventMessage = Class.new(Message)
103
+
104
+ # <xml>
105
+ # <ToUserName><![CDATA[toUser]]></ToUserName>
106
+ # <FromUserName><![CDATA[fromUser]]></FromUserName>
107
+ # <CreateTime>1351776360</CreateTime>
108
+ # <MsgType><![CDATA[location]]></MsgType>
109
+ # <Location_X>23.134521</Location_X>
110
+ # <Location_Y>113.358803</Location_Y>
111
+ # <Scale>20</Scale>
112
+ # <Label><![CDATA[位置信息]]></Label>
113
+ # <MsgId>1234567890123456</MsgId>
114
+ # </xml>
115
+ class LocationMessage < Message
116
+
117
+ def Location_X
118
+ @source.Location_X.to_f
119
+ end
120
+
121
+ def Location_Y
122
+ @source.Location_Y.to_f
123
+ end
124
+
125
+ def Scale
126
+ @source.Scale.to_i
127
+ end
128
+ end
129
+
130
+ # <xml>
131
+ # <ToUserName><![CDATA[toUser]]></ToUserName>
132
+ # <FromUserName><![CDATA[fromUser]]></FromUserName>
133
+ # <CreateTime>1376632760</CreateTime>
134
+ # <MsgType><![CDATA[voice]]></MsgType>
135
+ # <MediaId><![CDATA[Qyb0tgux6QLjhL6ipvFZJ-kUt2tcQtkn0BU365Vt3wUAtqfGam4QpZU35RXVhv6G]]></MediaId>
136
+ # <Format><![CDATA[amr]]></Format>
137
+ # <MsgId>5912592682802219078</MsgId>
138
+ # <Recognition><![CDATA[]]></Recognition>
139
+ # </xml>
140
+ class VoiceMessage < Message
141
+
142
+ def MediaId
143
+ @source.MediaId
144
+ end
145
+
146
+ def Format
147
+ @source.Format
148
+ end
149
+ end
150
+
151
+ # <xml>
152
+ # <ToUserName><![CDATA[toUser]]></ToUserName>
153
+ # <FromUserName><![CDATA[fromUser]]></FromUserName>
154
+ # <CreateTime>1376632994</CreateTime>
155
+ # <MsgType><![CDATA[video]]></MsgType>
156
+ # <MediaId><![CDATA[TAAGb6iS5LcZR1d5ICiZTWGWi6-Upic9tlWDpAKcNJA]]></MediaId>
157
+ # <ThumbMediaId><![CDATA[U-xulPW4kq6KKMWFNaBSPc65Bcgr7Qopwex0DfCeyQs]]></ThumbMediaId>
158
+ # <MsgId>5912593687824566343</MsgId>
159
+ # </xml>
160
+ class VideoMessage < Message
161
+
162
+ def MediaId
163
+ @source.MediaId
164
+ end
165
+
166
+ def ThumbMediaId
167
+ @source.ThumbMediaId
168
+ end
169
+ end
170
+
171
+ # <xml>
172
+ # <AppId> </AppId>
173
+ # <CreateTime>1413192605 </CreateTime>
174
+ # <InfoType> </InfoType>
175
+ # <ComponentVerifyTicket> </ComponentVerifyTicket>
176
+ # </xml>
177
+ TicketMessage = Class.new(Message)
178
+
179
+ # <xml>
180
+ # <AppId></AppId>
181
+ # <CreateTime>1413192760</CreateTime>
182
+ # <InfoType> </InfoType>
183
+ # <AuthorizerAppid></AuthorizerAppid>
184
+ # </xml>
185
+ AuthorizeMessage = Class.new(Message)
186
+
187
+ end
@@ -0,0 +1,3 @@
1
+ module YolQyWeixin
2
+ VERSION = "0.0.3"
3
+ end
@@ -0,0 +1,31 @@
1
+ require 'roxml'
2
+ require 'multi_xml'
3
+ require 'ostruct'
4
+
5
+ require "yol_qy_weixin/version"
6
+
7
+ require "yol_qy_weixin/models/configuration"
8
+ require "yol_qy_weixin/models/encrypt_message"
9
+ require "yol_qy_weixin/models/message"
10
+ require "yol_qy_weixin/helpers/prpcrypt"
11
+
12
+ require "yol_qy_weixin/connection"
13
+ require "yol_qy_weixin/client"
14
+
15
+ module YolQyWeixin
16
+
17
+ class << self
18
+
19
+ # A YolQyWeixin configuration object. See YolQyWeixin::Configuration.
20
+ attr_writer :configuration
21
+
22
+ def configure
23
+ yield(configuration)
24
+ end
25
+
26
+ def configuration
27
+ @configuration ||= Configuration.new
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'yol_qy_weixin/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "yol_qy_weixin"
8
+ spec.version = YolQyWeixin::VERSION
9
+ spec.authors = ["cjl"]
10
+ spec.email = ["chenjialiang@yeezon.com"]
11
+ spec.summary = %q{Shop middleware for Weixin.}
12
+ spec.description = ""
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler"
22
+ spec.add_development_dependency "rake"
23
+
24
+ spec.add_dependency 'nokogiri'
25
+ spec.add_dependency 'multi_xml'
26
+ spec.add_dependency 'roxml'
27
+ end
metadata ADDED
@@ -0,0 +1,136 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: yol_qy_weixin
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ platform: ruby
6
+ authors:
7
+ - cjl
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-03-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: nokogiri
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: multi_xml
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: roxml
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: ''
84
+ email:
85
+ - chenjialiang@yeezon.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".DS_Store"
91
+ - ".gitignore"
92
+ - Gemfile
93
+ - Gemfile.lock
94
+ - LICENSE.txt
95
+ - README.md
96
+ - Rakefile
97
+ - lib/.DS_Store
98
+ - lib/yol_qy_weixin.rb
99
+ - lib/yol_qy_weixin/client.rb
100
+ - lib/yol_qy_weixin/connection.rb
101
+ - lib/yol_qy_weixin/connections/base.rb
102
+ - lib/yol_qy_weixin/connections/qrcode.rb
103
+ - lib/yol_qy_weixin/connections/template.rb
104
+ - lib/yol_qy_weixin/connections/user.rb
105
+ - lib/yol_qy_weixin/helpers/pkcs7_encoder.rb
106
+ - lib/yol_qy_weixin/helpers/prpcrypt.rb
107
+ - lib/yol_qy_weixin/models/configuration.rb
108
+ - lib/yol_qy_weixin/models/encrypt_message.rb
109
+ - lib/yol_qy_weixin/models/message.rb
110
+ - lib/yol_qy_weixin/version.rb
111
+ - yol_qy_weixin.gemspec
112
+ homepage: ''
113
+ licenses:
114
+ - MIT
115
+ metadata: {}
116
+ post_install_message:
117
+ rdoc_options: []
118
+ require_paths:
119
+ - lib
120
+ required_ruby_version: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ required_rubygems_version: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ version: '0'
130
+ requirements: []
131
+ rubyforge_project:
132
+ rubygems_version: 2.7.9
133
+ signing_key:
134
+ specification_version: 4
135
+ summary: Shop middleware for Weixin.
136
+ test_files: []