wechat-gate-updater 0.1.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +49 -0
- data/LICENSE.txt +21 -0
- data/README.md +193 -0
- data/RELEASE.md +11 -0
- data/Rakefile +1 -0
- data/bin/console +14 -0
- data/bin/refresh_token_and_ticket +13 -0
- data/bin/setup +7 -0
- data/config/wechat.yml +18 -0
- data/lib/tasks/wechat_gate.rake +22 -0
- data/lib/wechat-gate.rb +8 -0
- data/lib/wechat_gate/config.rb +64 -0
- data/lib/wechat_gate/controller.rb +73 -0
- data/lib/wechat_gate/exception.rb +7 -0
- data/lib/wechat_gate/media.rb +24 -0
- data/lib/wechat_gate/menu.rb +21 -0
- data/lib/wechat_gate/message.rb +53 -0
- data/lib/wechat_gate/oauth.rb +80 -0
- data/lib/wechat_gate/railtie.rb +8 -0
- data/lib/wechat_gate/request.rb +34 -0
- data/lib/wechat_gate/send_message.rb +138 -0
- data/lib/wechat_gate/tokens/access_token.rb +30 -0
- data/lib/wechat_gate/tokens/base.rb +66 -0
- data/lib/wechat_gate/tokens/ext.rb +50 -0
- data/lib/wechat_gate/tokens/jsapi_ticket.rb +31 -0
- data/lib/wechat_gate/user.rb +16 -0
- data/lib/wechat_gate/version.rb +3 -0
- data/wechat-gate.gemspec +28 -0
- metadata +130 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: '05826f541ea10d64326fdf81f707288381a50bbc07c9c5ff33aa9bccc91eea12'
|
4
|
+
data.tar.gz: e87102aa758d4c6f7e55dac92ca31f18a0231e7320613186c1e6f9be83eac747
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6031f7e8729571efcfde0c7814c896c9193626d25bb3d8704588b2c7967f80bb0fd91fb1759bf48d2eb03f2e1db313d533113d77e57b75a713b7112dc2303823
|
7
|
+
data.tar.gz: 62a136dd45c5cdfcc4140c40c6f5d3d850f3d55c4689a28fab398544afd2e6007313df530089fb8d164c1c26e16f741ec919e7b26d58a1824c5744363c624a88
|
data/.gitignore
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# Contributor Code of Conduct
|
2
|
+
|
3
|
+
As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
|
4
|
+
|
5
|
+
We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
|
6
|
+
|
7
|
+
Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
|
8
|
+
|
9
|
+
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
|
10
|
+
|
11
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
|
12
|
+
|
13
|
+
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
wechat-gate (0.1.6)
|
5
|
+
activesupport (>= 5.0.1)
|
6
|
+
rest-client (>= 1.8)
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
activesupport (5.1.4)
|
12
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
13
|
+
i18n (~> 0.7)
|
14
|
+
minitest (~> 5.1)
|
15
|
+
tzinfo (~> 1.1)
|
16
|
+
concurrent-ruby (1.0.5)
|
17
|
+
domain_name (0.5.20170404)
|
18
|
+
unf (>= 0.0.5, < 1.0.0)
|
19
|
+
http-cookie (1.0.3)
|
20
|
+
domain_name (~> 0.5)
|
21
|
+
i18n (0.9.1)
|
22
|
+
concurrent-ruby (~> 1.0)
|
23
|
+
mime-types (3.1)
|
24
|
+
mime-types-data (~> 3.2015)
|
25
|
+
mime-types-data (3.2016.0521)
|
26
|
+
minitest (5.10.3)
|
27
|
+
netrc (0.11.0)
|
28
|
+
rake (10.5.0)
|
29
|
+
rest-client (2.0.2)
|
30
|
+
http-cookie (>= 1.0.2, < 2.0)
|
31
|
+
mime-types (>= 1.16, < 4.0)
|
32
|
+
netrc (~> 0.8)
|
33
|
+
thread_safe (0.3.6)
|
34
|
+
tzinfo (1.2.4)
|
35
|
+
thread_safe (~> 0.1)
|
36
|
+
unf (0.1.4)
|
37
|
+
unf_ext
|
38
|
+
unf_ext (0.0.7.4)
|
39
|
+
|
40
|
+
PLATFORMS
|
41
|
+
ruby
|
42
|
+
|
43
|
+
DEPENDENCIES
|
44
|
+
bundler (>= 1.10)
|
45
|
+
rake (>= 10.0)
|
46
|
+
wechat-gate!
|
47
|
+
|
48
|
+
BUNDLED WITH
|
49
|
+
1.15.3
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Lei Lee
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,193 @@
|
|
1
|
+
# WechatGate
|
2
|
+
|
3
|
+
> 搬运 from [隔壁老李的微信开发Ruby Gem](https://github.com/eggmantv/wechat_gate),原著未支持 Application::API,这里添加
|
4
|
+
|
5
|
+
**微信公众平台开发库**
|
6
|
+
|
7
|
+
支持的接口:
|
8
|
+
|
9
|
+
- access_token(后端API使用)
|
10
|
+
- 用户授权信息获取(OAuth2)
|
11
|
+
- JS-SDK
|
12
|
+
- 回复消息封装
|
13
|
+
- 菜单接口
|
14
|
+
- 素材接口
|
15
|
+
|
16
|
+
功能特点:
|
17
|
+
|
18
|
+
- 自动管理access_token和JS-SDK的ticket刷新和过期
|
19
|
+
- 多微信公众号支持
|
20
|
+
- 多环境支持(development, production),方便本地测试
|
21
|
+
- Controler和helper方法(微信session管理等等)
|
22
|
+
- 接口简单,方便定制
|
23
|
+
|
24
|
+
使用视频教程: [微信公众号开发](https://eggman.tv/c/s-wechat-development-using-ruby-on-rails)
|
25
|
+
|
26
|
+
## Installation
|
27
|
+
|
28
|
+
Add this line to your application's Gemfile:
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
gem 'wechat-gate'
|
32
|
+
```
|
33
|
+
|
34
|
+
And then execute:
|
35
|
+
|
36
|
+
$ bundle
|
37
|
+
|
38
|
+
Or install it yourself as:
|
39
|
+
|
40
|
+
$ gem install wechat-gate
|
41
|
+
|
42
|
+
## 公众号
|
43
|
+
|
44
|
+
在开工之前你需要在微信公众账号平台做以下配置:
|
45
|
+
|
46
|
+
1. 开通你的公众号(服务号),并开通微信认证(300元认证服务费)
|
47
|
+
2. 在公众号后台“公众号设置” - “功能设置”中设置你的JS接口安全域名,就是你的公众号调用的网站的域名
|
48
|
+
3. 在“接口权限” - “网页授权获取用户基本信息”中设置你的授权回调页面域名,这个用于OAuth2的回调域名认证
|
49
|
+
4. 在“基本配置”中查看并配置你的AppID和AppSecret
|
50
|
+
|
51
|
+
## 配置
|
52
|
+
|
53
|
+
在Rails项目config目录下建立文件wechat.yml,并配置你的公众号信息.
|
54
|
+
|
55
|
+
```
|
56
|
+
# 区分不同的环境
|
57
|
+
eggman:
|
58
|
+
development:
|
59
|
+
host: http://wechat-test1.eggman.tv
|
60
|
+
|
61
|
+
wechat_id: xxxxxxxxxx
|
62
|
+
app_id: xxxxxxxxxx
|
63
|
+
app_secret: xxxxxxxxxx
|
64
|
+
|
65
|
+
oauth2_redirect_uri: "http://wechat-test1.eggman.tv/wechat/users/callback"
|
66
|
+
|
67
|
+
push_url: "http://wechat-test1.eggman.tv/wechat/push"
|
68
|
+
push_token: xxxxxxxxxxxxxxxxxxxx
|
69
|
+
production:
|
70
|
+
host: https://eggman.tv
|
71
|
+
|
72
|
+
wechat_id: xxxxxxxxxx
|
73
|
+
app_id: xxxxxxxxxxxxxxxxxxxx
|
74
|
+
app_secret: xxxxxxxxxxxxxxxxxxxxxxxxxx
|
75
|
+
|
76
|
+
# 如果不需要多环境支持,也可以这样
|
77
|
+
app_name:
|
78
|
+
app_id: <%= ENV['WECHAT_APP_NAME_APP_ID'] %>
|
79
|
+
app_secret: <%= ENV['WECHAT_APP_NAME_APP_SECRET'] %>
|
80
|
+
oauth2_redirect_uri: <%= ENV['WECHAT_APP_NAME_OAUTH2_REDIRECT_URI'] %>
|
81
|
+
```
|
82
|
+
|
83
|
+
然后在ApplicationController中指定当前要读取的公众号名称:
|
84
|
+
|
85
|
+
```
|
86
|
+
self.wechat_gate_app_name = 'eggman'
|
87
|
+
```
|
88
|
+
|
89
|
+
## 后端调用
|
90
|
+
|
91
|
+
后台API操作(比如微信用户信息获取等等操作)。
|
92
|
+
|
93
|
+
默认情况下在controller中已经初始化了配置,方法为**wechat_gate_config**,直接使用就行。
|
94
|
+
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
wechat_gate_config.users # 获取用户列表
|
98
|
+
wechat_gate_config.user('ONE_OPEN_ID') # 获取一个用户的详细信息
|
99
|
+
wechat_gate_config.access_token # 获取当前access_token
|
100
|
+
|
101
|
+
# OAuth 2
|
102
|
+
wechat_gate_config.oauth2_entrance_url(scope: "snsapi_userinfo", state: "CURENT_STATE") # 获取当前OAuth2授权入口URL
|
103
|
+
wechat_gate_config.oauth2_access_token("TOKEN") # 根据OAuth2返回的TOKEN获取access_token
|
104
|
+
wechat_gate_config.oauth2_user("ACCESS_TOKEN", "ONE_OPEN_ID") # 获取一个用户的信息
|
105
|
+
|
106
|
+
wechat_gate_config.medias # 获取素材列表, 参数type: image | video | voice | news (图文)
|
107
|
+
|
108
|
+
wechat_gate_config.menu_get # 获取菜单
|
109
|
+
wechat_gate_config.menu_create(MENU_HASH) # 创建菜单
|
110
|
+
|
111
|
+
wechat_gate_config.generate_js_request_params(REFERER_URL) # 返回JS-SDK的验证参数,供前端JS-SDK使用
|
112
|
+
```
|
113
|
+
|
114
|
+
当然你也可以手工来初始化配置,甚至指定配置文件的路径:
|
115
|
+
|
116
|
+
```
|
117
|
+
config = WechatGate::Config.new('eggman', '/path/to/what/ever/you/want.yml')
|
118
|
+
```
|
119
|
+
|
120
|
+
access_token和JS_SDK中ticket都有过期时间和刷新次数限制,这里已经考虑了,你可以不用管,如果你想手工刷新,可以这样:
|
121
|
+
|
122
|
+
```
|
123
|
+
config.refresh_access_token
|
124
|
+
config.refresh_jsapi_ticket
|
125
|
+
```
|
126
|
+
|
127
|
+
**配置文件支持erb**
|
128
|
+
|
129
|
+
> 更多接口和文档请直接看源码,写的很详细
|
130
|
+
|
131
|
+
## JS-SDK
|
132
|
+
|
133
|
+
```ruby
|
134
|
+
def ticket
|
135
|
+
url = CGI.unescape(params[:url]) # 微信中用户访问的页面
|
136
|
+
@data = wechat_gate_config.generate_js_request_params(url) # 生成微信JS-SDK所需的jsapi_ticket,signature等等参数供前段js使用
|
137
|
+
render content_type: "application/javascript"
|
138
|
+
end
|
139
|
+
```
|
140
|
+
|
141
|
+
ticket.js.erb:
|
142
|
+
|
143
|
+
```
|
144
|
+
var wxServerConfig = <%= @data.to_json.html_safe %>;
|
145
|
+
<%= params[:callback] %>();
|
146
|
+
```
|
147
|
+
|
148
|
+
然后在微信端页面引入以下代码:
|
149
|
+
|
150
|
+
```js
|
151
|
+
(function() {
|
152
|
+
var ticket = document.createElement("script");
|
153
|
+
ticket.src = "http://localhost/api/wechat_ticket/ticket.js?url=" + encodeURIComponent(window.location.href.split('#')[0]) + "&callback=wxCallback";
|
154
|
+
var s = document.getElementsByTagName("script")[0];
|
155
|
+
s.parentNode.insertBefore(ticket, s);
|
156
|
+
})();
|
157
|
+
```
|
158
|
+
|
159
|
+
## 其他功能
|
160
|
+
|
161
|
+
### 自定义菜单
|
162
|
+
|
163
|
+
首先设置菜单配置文件,config/wechat_menu.yml,支持erb,格式请参考[微信自定义菜单文档](https://mp.weixin.qq.com/wiki):
|
164
|
+
|
165
|
+
```
|
166
|
+
button:
|
167
|
+
- type: view
|
168
|
+
name: 我的2
|
169
|
+
url: <%= @config.oauth2_entrance_url(scope: 'snsapi_userinfo', state: 'profile') %>
|
170
|
+
- type: view
|
171
|
+
name: 课程
|
172
|
+
sub_button:
|
173
|
+
- type: view
|
174
|
+
name: 免费课程
|
175
|
+
url: <%= @config.oauth2_entrance_url(scope: 'snsapi_userinfo', state: 'free') %>
|
176
|
+
- type: view
|
177
|
+
name: 付费课程
|
178
|
+
url: <%= @config.oauth2_entrance_url(scope: 'snsapi_userinfo', state: 'paid') %>
|
179
|
+
```
|
180
|
+
|
181
|
+
> 其中的**@config**变量为当前微信公众号实例,请不要修改,直接使用
|
182
|
+
|
183
|
+
然后执行rake任务:
|
184
|
+
|
185
|
+
```shell
|
186
|
+
$rails wechat_gate:create_menu APP_NAME=eggman CONFIG=/path/to/wechat.yml MENU=/path/to/wechat_menu.yml
|
187
|
+
```
|
188
|
+
|
189
|
+
其中,CONFIG默认为config/wechat.yml,MENU默认为config/wechat_menu.yml,APP_NAME必须指定
|
190
|
+
|
191
|
+
## TODO
|
192
|
+
|
193
|
+
添加测试
|
data/RELEASE.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "wechat-gate"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "wechat-gate"
|
5
|
+
|
6
|
+
# usage:
|
7
|
+
# output_type: "/file/to/write/js/variable" | ruby | js
|
8
|
+
# refresh_token_and_ticket app_name page_url output_type
|
9
|
+
#
|
10
|
+
|
11
|
+
WechatGate::Config.new(ARGV[0]) do |config|
|
12
|
+
config.write_token_to_file ARGV[1], ARGV[2]
|
13
|
+
end
|
data/bin/setup
ADDED
data/config/wechat.yml
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# 请参考这个配置文件
|
2
|
+
#
|
3
|
+
app_name:
|
4
|
+
development:
|
5
|
+
app_id: <%= ENV['WECHAT_APP_NAME_APP_ID'] %>
|
6
|
+
app_secret: <%= ENV['WECHAT_APP_NAME_APP_SECRET'] %>
|
7
|
+
oauth2_redirect_uri: <%= ENV['WECHAT_APP_NAME_OAUTH2_REDIRECT_URI'] %>
|
8
|
+
|
9
|
+
production:
|
10
|
+
app_id: <%= ENV['WECHAT_APP_NAME_APP_ID'] %>
|
11
|
+
app_secret: <%= ENV['WECHAT_APP_NAME_APP_SECRET'] %>
|
12
|
+
oauth2_redirect_uri: <%= ENV['WECHAT_APP_NAME_OAUTH2_REDIRECT_URI'] %>
|
13
|
+
|
14
|
+
# 如果不需要多环境支持,也可以这样
|
15
|
+
app_name:
|
16
|
+
app_id: <%= ENV['WECHAT_APP_NAME_APP_ID'] %>
|
17
|
+
app_secret: <%= ENV['WECHAT_APP_NAME_APP_SECRET'] %>
|
18
|
+
oauth2_redirect_uri: <%= ENV['WECHAT_APP_NAME_OAUTH2_REDIRECT_URI'] %>
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'wechat_gate/exception'
|
2
|
+
|
3
|
+
namespace :wechat_gate do
|
4
|
+
|
5
|
+
def validate_envs
|
6
|
+
raise WechatGate::Exception::ConfigException, 'need specify APP_NAME!' unless ENV['APP_NAME']
|
7
|
+
end
|
8
|
+
|
9
|
+
desc "create menu, APP_NAME=app_name, CONFIG=/path/to/config/file.yml, MENU=/path/to/menu/config/file.yml"
|
10
|
+
task :create_menu => :environment do
|
11
|
+
validate_envs
|
12
|
+
|
13
|
+
@config = WechatGate::Config.new(ENV['APP_NAME'], ENV['CONFIG'])
|
14
|
+
|
15
|
+
menu_file = ENV['MENU']
|
16
|
+
menu_file = "#{Dir.pwd}/config/wechat_menu.yml" unless menu_file
|
17
|
+
raise WechatGate::Exception::ConfigException, "MENU #{menu_file} not found!" unless File.exists?(menu_file)
|
18
|
+
|
19
|
+
menu = YAML.load(ERB.new(File.read(menu_file)).result(binding))
|
20
|
+
@config.menu_create(JSON.generate(menu))
|
21
|
+
end
|
22
|
+
end
|
data/lib/wechat-gate.rb
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
require "wechat_gate/version"
|
2
|
+
require "wechat_gate/config"
|
3
|
+
require "wechat_gate/railtie" if defined?(Rails)
|
4
|
+
|
5
|
+
if defined?(ActionController)
|
6
|
+
ActionController::Base.send(:include, WechatGate::Controller)
|
7
|
+
ActionController::API.send(:include, WechatGate::Controller)
|
8
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'erb'
|
3
|
+
require 'wechat_gate/tokens/access_token'
|
4
|
+
require 'wechat_gate/tokens/jsapi_ticket'
|
5
|
+
require 'wechat_gate/tokens/ext'
|
6
|
+
require 'wechat_gate/oauth'
|
7
|
+
require 'wechat_gate/user'
|
8
|
+
require 'wechat_gate/menu'
|
9
|
+
require 'wechat_gate/media'
|
10
|
+
require 'wechat_gate/message'
|
11
|
+
require 'wechat_gate/send_message'
|
12
|
+
require 'wechat_gate/exception'
|
13
|
+
require 'wechat_gate/controller'
|
14
|
+
|
15
|
+
module WechatGate
|
16
|
+
class Config
|
17
|
+
|
18
|
+
attr_reader :app_name
|
19
|
+
attr_reader :config
|
20
|
+
attr_reader :output_type
|
21
|
+
|
22
|
+
include WechatGate::Tokens::AccessToken
|
23
|
+
include WechatGate::Tokens::JsapiTicket
|
24
|
+
include WechatGate::Tokens::Ext
|
25
|
+
include WechatGate::Oauth
|
26
|
+
include WechatGate::User
|
27
|
+
include WechatGate::Menu
|
28
|
+
include WechatGate::Media
|
29
|
+
include WechatGate::Message
|
30
|
+
include WechatGate::SendMessage
|
31
|
+
|
32
|
+
def initialize app_name, config_file = nil
|
33
|
+
unless config_file
|
34
|
+
if defined?(Rails)
|
35
|
+
config_file = "#{Rails.root}/config/wechat.yml"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
raise Exception::ConfigException, "no wechat configuration file found!" unless config_file
|
40
|
+
unless File.exists?(config_file)
|
41
|
+
raise Exception::ConfigException, "configuration file does not exist!"
|
42
|
+
end
|
43
|
+
|
44
|
+
config_text = ERB.new(File.read(config_file)).result
|
45
|
+
configs = YAML.load(config_text)
|
46
|
+
unless configs[app_name]
|
47
|
+
raise Exception::ConfigException, "no configuration found for app: #{app_name}!"
|
48
|
+
end
|
49
|
+
|
50
|
+
@config = if defined?(Rails)
|
51
|
+
configs[app_name][Rails.env] || configs[app_name]
|
52
|
+
else
|
53
|
+
configs[app_name]
|
54
|
+
end
|
55
|
+
|
56
|
+
@app_name = app_name
|
57
|
+
|
58
|
+
yield(self) if block_given?
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'wechat_gate/exception'
|
2
|
+
|
3
|
+
module WechatGate
|
4
|
+
module Controller
|
5
|
+
|
6
|
+
def self.included base
|
7
|
+
base.send :include, InstanceMethods
|
8
|
+
|
9
|
+
base.class_eval do
|
10
|
+
helper_method :is_wechat_logged_in?
|
11
|
+
helper_method :current_open_id
|
12
|
+
|
13
|
+
class_attribute :wechat_gate_app_name
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module InstanceMethods
|
18
|
+
protected
|
19
|
+
def wechat_gate_setup
|
20
|
+
unless self.class.wechat_gate_app_name
|
21
|
+
raise Exception::ConfigException, "please specify wechat_gate_app_name!"
|
22
|
+
end
|
23
|
+
|
24
|
+
@wechat_gate_config = WechatGate::Config.new(self.class.wechat_gate_app_name)
|
25
|
+
end
|
26
|
+
|
27
|
+
def wechat_gate_config
|
28
|
+
@wechat_gate_config ||= wechat_gate_setup
|
29
|
+
end
|
30
|
+
|
31
|
+
def is_wechat_logged_in?
|
32
|
+
!!session[:user_open_id]
|
33
|
+
end
|
34
|
+
|
35
|
+
def current_open_id
|
36
|
+
session[:user_open_id]
|
37
|
+
end
|
38
|
+
|
39
|
+
def wechat_user_signin(user_open_id)
|
40
|
+
session[:user_open_id] = user_open_id
|
41
|
+
end
|
42
|
+
|
43
|
+
def wechat_user_auth
|
44
|
+
unless is_wechat_logged_in?
|
45
|
+
redirect_to wechat_gate_config.oauth2_entrance_url(scope: "snsapi_base")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def bind_user_with_open_id user_model
|
50
|
+
if is_wechat_logged_in? and user_model.open_id.blank?
|
51
|
+
user_model.update_attribute :open_id, current_open_id
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def is_legal_from_wechat_server?
|
56
|
+
data = [
|
57
|
+
wechat_gate_config.config["push_token"],
|
58
|
+
params[:timestamp],
|
59
|
+
params[:nonce]
|
60
|
+
]
|
61
|
+
|
62
|
+
Digest::SHA1.hexdigest(data.sort.join('')) == params[:signature]
|
63
|
+
end
|
64
|
+
|
65
|
+
def check_wechat_server
|
66
|
+
unless is_legal_from_wechat_server?
|
67
|
+
render text: "illegal signature!"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'wechat_gate/request'
|
2
|
+
|
3
|
+
module WechatGate
|
4
|
+
module Media
|
5
|
+
#
|
6
|
+
# http://mp.weixin.qq.com/wiki/15/8386c11b7bc4cdd1499c572bfe2e95b3.html
|
7
|
+
#
|
8
|
+
|
9
|
+
# type: image | video | voice | news (图文)
|
10
|
+
def medias(type = 'news', offset = 0, count = 20)
|
11
|
+
WechatGate::Request.send(
|
12
|
+
"https://api.weixin.qq.com/cgi-bin/material/batchget_material?access_token=#{self.access_token}",
|
13
|
+
:post,
|
14
|
+
{
|
15
|
+
"type": type,
|
16
|
+
"offset": offset,
|
17
|
+
"count": count
|
18
|
+
}.to_json
|
19
|
+
)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'wechat_gate/request'
|
2
|
+
|
3
|
+
module WechatGate
|
4
|
+
module Menu
|
5
|
+
def menu_get
|
6
|
+
WechatGate::Request.send(
|
7
|
+
"https://api.weixin.qq.com/cgi-bin/menu/get?access_token=#{self.access_token}"
|
8
|
+
)
|
9
|
+
end
|
10
|
+
|
11
|
+
def menu_create(menu_hash)
|
12
|
+
WechatGate::Request.send(
|
13
|
+
"https://api.weixin.qq.com/cgi-bin/menu/create?access_token=#{self.access_token}",
|
14
|
+
:post,
|
15
|
+
menu_hash
|
16
|
+
)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module WechatGate
|
2
|
+
module Message
|
3
|
+
#
|
4
|
+
# http://mp.weixin.qq.com/wiki/17/f298879f8fb29ab98b2f2971d42552fd.html
|
5
|
+
#
|
6
|
+
# 消息发送只能是被动的,就是微信会把用户的聊天数据推送到服务器端,然后服务器利用返回值作出相应
|
7
|
+
#
|
8
|
+
|
9
|
+
def message_body(type, to, body)
|
10
|
+
content = case type.to_sym
|
11
|
+
when :text
|
12
|
+
%Q{
|
13
|
+
<Content><![CDATA[#{body}]]></Content>
|
14
|
+
}
|
15
|
+
when :image
|
16
|
+
# body: media_id
|
17
|
+
%Q{
|
18
|
+
<Image>
|
19
|
+
<MediaId><![CDATA[#{body}]]></MediaId>
|
20
|
+
</Image>
|
21
|
+
}
|
22
|
+
when :voice
|
23
|
+
%Q{
|
24
|
+
<Voice>
|
25
|
+
<MediaId><![CDATA[#{body}]]></MediaId>
|
26
|
+
</Voice>
|
27
|
+
}
|
28
|
+
when :video
|
29
|
+
# body: { media_id: MEDIA_ID, title: TITLE, description: DESCRIPTION }
|
30
|
+
%Q{
|
31
|
+
<Video>
|
32
|
+
<MediaId><![CDATA[#{body[:media_id]}]]></MediaId>
|
33
|
+
<Title><![CDATA[#{body[:title]}]]></Title>
|
34
|
+
<Description><![CDATA[#{body[:description]}]]></Description>
|
35
|
+
</Video>
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
%Q{
|
40
|
+
<xml>
|
41
|
+
<ToUserName><![CDATA[#{to}]]></ToUserName>
|
42
|
+
<FromUserName><![CDATA[#{self.config['wechat_id']}]]></FromUserName>
|
43
|
+
<CreateTime>#{Time.now.to_i}</CreateTime>
|
44
|
+
<MsgType><![CDATA[#{type}]]></MsgType>
|
45
|
+
#{content}
|
46
|
+
</xml>
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'rest-client'
|
2
|
+
require 'wechat_gate/request'
|
3
|
+
|
4
|
+
module WechatGate
|
5
|
+
module Oauth
|
6
|
+
#
|
7
|
+
#
|
8
|
+
# 此module中的access_token是微信网页授权OAuth2.0的专用access_token,和其他modules中所需需要的access_token
|
9
|
+
# 不是一个概念,这里的access_token主要就是用户非公众号的关注用户来进行网页授权访问的,其中:
|
10
|
+
# scope:
|
11
|
+
# snsapi_base 用于公众号已关注用户,已关注的用户默认已经得到了授权,这里只是为了取得当前用户的openid,
|
12
|
+
# 此时对用户的所有操作 (ex: WechatGate::User模块) 可以直接利用系统基本的access_token (WechatGate::Tokens::AccessToken模块) 来进行。
|
13
|
+
# snsapi_userinfo 用户非关注用户取得授权,这里遵循标准的OAuth2.0授权流程,取得用户信息需要利用本模块中的方法来进行。
|
14
|
+
# 这里的access_token是和用户绑定的。
|
15
|
+
#
|
16
|
+
#
|
17
|
+
|
18
|
+
#
|
19
|
+
# 用户点击授权入口页面
|
20
|
+
#
|
21
|
+
def oauth2_entrance_url(ops = {})
|
22
|
+
ops = {
|
23
|
+
state: 'empty', # 自定义参数值
|
24
|
+
redirect_uri: self.config["oauth2_redirect_uri"],
|
25
|
+
scope: 'snsapi_base' # snsapi_base | snsapi_userinfo
|
26
|
+
}.merge(ops)
|
27
|
+
|
28
|
+
"https://open.weixin.qq.com/connect/oauth2/authorize?appid=#{self.config['app_id']}&redirect_uri=#{CGI.escape(ops[:redirect_uri])}&response_type=code&scope=#{ops[:scope]}&state=#{ops[:state]}#wechat_redirect"
|
29
|
+
end
|
30
|
+
|
31
|
+
# TODO
|
32
|
+
# 这里目前需要调用该gem的应用自身来保存用户的access_token, refresh_token, openid等参数以及判断有效期
|
33
|
+
# 不过这个地方也可以不做缓存,微信在这个地方没有限制API的调用次数。
|
34
|
+
#
|
35
|
+
# code:
|
36
|
+
# code来源于网页端redirect_uri页面得到的微信端返回的参数,专门用户取得下一步的access_token
|
37
|
+
# 该接口会返回:
|
38
|
+
# {
|
39
|
+
# "access_token" => "access_token",
|
40
|
+
# "expires_in"=>7200,
|
41
|
+
# "refresh_token"=>"refresh_token",
|
42
|
+
# "openid"=>"MZkwG5sAx-d4PMQ6Lq1xisE",
|
43
|
+
# "scope"=>"snsapi_base"
|
44
|
+
# }
|
45
|
+
# 此时已经获得了用户的openid,如果用户为公众号的订阅用户,就可以直接利用Tokens::AccessToken的token来对改用户调用业务接口了,
|
46
|
+
# 此时这里的access_token意义就不大了,这里的access_token和Tokens::AccessToken的token是完全不一样的。
|
47
|
+
#
|
48
|
+
def oauth2_access_token(code)
|
49
|
+
WechatGate::Request.send("https://api.weixin.qq.com/sns/oauth2/access_token?appid=#{self.config['app_id']}&secret=#{self.config['app_secret']}&code=#{code}&grant_type=authorization_code")
|
50
|
+
end
|
51
|
+
|
52
|
+
# access_token拥有较短的有效期,当access_token超时后,可以使用refresh_token进行刷新,
|
53
|
+
# refresh_token拥有较长的有效期(7天、30天、60天、90天),当refresh_token失效的后,需要用户重新授权。
|
54
|
+
#
|
55
|
+
def oauth2_access_token_valid?(access_token, openid)
|
56
|
+
WechatGate::Request.send("https://api.weixin.qq.com/sns/auth?access_token=#{access_token}&openid=#{openid}")
|
57
|
+
end
|
58
|
+
|
59
|
+
# 利用refresh_token刷新access_token
|
60
|
+
#
|
61
|
+
# response:
|
62
|
+
# {
|
63
|
+
# "access_token":"ACCESS_TOKEN",
|
64
|
+
# "expires_in":7200,
|
65
|
+
# "refresh_token":"REFRESH_TOKEN",
|
66
|
+
# "openid":"OPENID",
|
67
|
+
# "scope":"SCOPE"
|
68
|
+
# }
|
69
|
+
def oauth2_refresh_access_token(refresh_token)
|
70
|
+
WechatGate::Request.send("https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=#{self.config['app_id']}&grant_type=refresh_token&refresh_token=#{refresh_token}")
|
71
|
+
end
|
72
|
+
|
73
|
+
# 获取用户信息
|
74
|
+
#
|
75
|
+
def oauth2_user(access_token, openid)
|
76
|
+
WechatGate::Request.send("https://api.weixin.qq.com/sns/userinfo?access_token=#{access_token}&openid=#{openid}&lang=zh_CN")
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'rest-client'
|
2
|
+
|
3
|
+
module WechatGate
|
4
|
+
module Request
|
5
|
+
def self.send(url, method = :get, payload = nil, headers = nil, &block)
|
6
|
+
method = method.to_sym
|
7
|
+
|
8
|
+
opts = {
|
9
|
+
method: method,
|
10
|
+
url: url,
|
11
|
+
verify_ssl: false
|
12
|
+
}
|
13
|
+
if method == :post and payload
|
14
|
+
opts.merge! payload: payload
|
15
|
+
end
|
16
|
+
|
17
|
+
if headers
|
18
|
+
opts.merge! headers: headers
|
19
|
+
end
|
20
|
+
|
21
|
+
response = RestClient::Request.execute(opts)
|
22
|
+
response = JSON.parse(response)
|
23
|
+
raise response.to_s if response['errmsg'] and response['errmsg'] != 'ok'
|
24
|
+
|
25
|
+
if block_given?
|
26
|
+
yield(response)
|
27
|
+
else
|
28
|
+
response
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require 'active_support/core_ext/module/attribute_accessors'
|
2
|
+
require 'wechat_gate/request'
|
3
|
+
|
4
|
+
module WechatGate
|
5
|
+
module SendMessage
|
6
|
+
|
7
|
+
#
|
8
|
+
# https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN
|
9
|
+
# 这个接口有发送限制,服务号每月只能发送4次,订阅号每天一次
|
10
|
+
#
|
11
|
+
def mass_send open_ids, msg_options = {}
|
12
|
+
payload = {
|
13
|
+
"touser": [open_ids].flatten,
|
14
|
+
"msgtype": "text",
|
15
|
+
"text": { "content": "hello from boxer."}
|
16
|
+
}.merge(msg_options)
|
17
|
+
|
18
|
+
WechatGate::Request.send(
|
19
|
+
"https://api.weixin.qq.com/cgi-bin/message/mass/send?access_token=#{self.access_token}",
|
20
|
+
:post,
|
21
|
+
payload.to_json
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
#
|
26
|
+
# 这个是微信的私有接口,在微信网页端才能使用,给单个用户发消息,而且没有发送数量限制,
|
27
|
+
# 这里是模拟公众号登录,然后给用户发送消息(在“用户管理”页面点击单个用户)
|
28
|
+
#
|
29
|
+
# **这个接口要求48小时内只能发送20条**
|
30
|
+
# **用户在48小时内与公众账号只要发生互动(点击公众号菜单或者发过消息),阀值就会刷新**
|
31
|
+
#
|
32
|
+
# Request URL:https://mp.weixin.qq.com/cgi-bin/singlesend?t=ajax-response&f=json&token=1927434739&lang=zh_CN
|
33
|
+
# Request Method:POST
|
34
|
+
# Query String:
|
35
|
+
# t:ajax-response
|
36
|
+
# f:json
|
37
|
+
# token:1927434739
|
38
|
+
# lang:zh_CN
|
39
|
+
# Form Data
|
40
|
+
# token:1927434739
|
41
|
+
# lang:zh_CN
|
42
|
+
# f:json
|
43
|
+
# ajax:1
|
44
|
+
# random:0.9569772763087352
|
45
|
+
# type:1
|
46
|
+
# content:ll
|
47
|
+
# tofakeid:oMZZkwziWkGiXq3-RjGHcgXn6v70
|
48
|
+
# imgcode:
|
49
|
+
#
|
50
|
+
mattr_accessor :single_send_cookies
|
51
|
+
mattr_accessor :single_send_token
|
52
|
+
mattr_accessor :single_send_cookies_refreshed_at
|
53
|
+
mattr_accessor :single_send_cookies_expired_in
|
54
|
+
|
55
|
+
mattr_accessor :single_send_ua
|
56
|
+
|
57
|
+
#
|
58
|
+
# 在公众号端没有cookie过期的概念,这里把登录后的cookie和token缓存起来,为了不频繁的登录公众号
|
59
|
+
#
|
60
|
+
# **这个功能只能在生产环境的真实公众账号中测试,不支持在沙盒中测试**
|
61
|
+
#
|
62
|
+
self.single_send_cookies_refreshed_at = Time.now.to_i
|
63
|
+
self.single_send_cookies_expired_in = 3600
|
64
|
+
|
65
|
+
self.single_send_ua = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36"
|
66
|
+
|
67
|
+
def single_send open_id, content
|
68
|
+
single_send_if_need_refresh_cookie
|
69
|
+
|
70
|
+
opts = {
|
71
|
+
method: :post,
|
72
|
+
url: "https://mp.weixin.qq.com/cgi-bin/singlesend?t=ajax-response&f=json&token=#{self.single_send_token}&lang=zh_CN",
|
73
|
+
verify_ssl: false,
|
74
|
+
payload: {
|
75
|
+
token: self.single_send_token,
|
76
|
+
lang: "zh_CN",
|
77
|
+
f: "json",
|
78
|
+
ajax: 1,
|
79
|
+
random: Random.rand,
|
80
|
+
type: 1, # 文本消息
|
81
|
+
content: content,
|
82
|
+
tofakeid: open_id,
|
83
|
+
imgcode: ""
|
84
|
+
}.to_query,
|
85
|
+
headers: {
|
86
|
+
'User-Agent': self.single_send_ua,
|
87
|
+
'Cookie': self.single_send_cookies,
|
88
|
+
'Referer': "https://mp.weixin.qq.com/cgi-bin/singlesendpage?t=message/send&action=index&tofakeid=#{open_id}&token=#{self.single_send_token}&lang=zh_CN"
|
89
|
+
}
|
90
|
+
}
|
91
|
+
|
92
|
+
response = RestClient::Request.execute(opts)
|
93
|
+
data = JSON.parse(response)
|
94
|
+
raise response.to_s if data['errmsg'] and data['errmsg'] != 'ok'
|
95
|
+
data
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
def single_send_if_need_refresh_cookie
|
100
|
+
if self.single_send_cookies.nil? ||
|
101
|
+
(Time.now.to_i - self.single_send_cookies_refreshed_at > self.single_send_cookies_expired_in)
|
102
|
+
single_send_login_and_refresh_cookies_w_token
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
#
|
107
|
+
# 登录公众号,缓存登录后的cookie和token
|
108
|
+
#
|
109
|
+
def single_send_login_and_refresh_cookies_w_token
|
110
|
+
opts = {
|
111
|
+
method: :post,
|
112
|
+
url: "https://mp.weixin.qq.com/cgi-bin/login?lang=zh_CN",
|
113
|
+
verify_ssl: false,
|
114
|
+
payload: {
|
115
|
+
username: self.config['wechat_login_username'],
|
116
|
+
pwd: Digest::MD5.hexdigest(self.config['wechat_login_password']),
|
117
|
+
imgcode: "",
|
118
|
+
f: "json"
|
119
|
+
}.to_query,
|
120
|
+
headers: {
|
121
|
+
'User-Agent': self.single_send_ua,
|
122
|
+
'Referer': 'https://mp.weixin.qq.com/'
|
123
|
+
}
|
124
|
+
}
|
125
|
+
|
126
|
+
response = RestClient::Request.execute(opts)
|
127
|
+
data = JSON.parse(response)
|
128
|
+
raise response.to_s if data['errmsg'] and data['errmsg'] != 'ok'
|
129
|
+
|
130
|
+
self.single_send_cookies = response.cookies.map {|k, v| k + "=" + v}.join("; ")
|
131
|
+
data["redirect_url"].gsub(/token=(\d+)/) { |x| self.single_send_token = $1 }
|
132
|
+
self.single_send_cookies_refreshed_at = Time.now.to_i
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'wechat_gate/tokens/base'
|
2
|
+
|
3
|
+
module WechatGate
|
4
|
+
module Tokens
|
5
|
+
module AccessToken
|
6
|
+
def self.included base
|
7
|
+
base.send :include, InstanceMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
module InstanceMethods
|
11
|
+
def access_token
|
12
|
+
token = WechatGate::Tokens::AccessToken::Get.refresh(self)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Get < WechatGate::Tokens::Base
|
17
|
+
def url
|
18
|
+
"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=#{@config.config['app_id']}&secret=#{@config.config['app_secret']}"
|
19
|
+
end
|
20
|
+
|
21
|
+
def save response
|
22
|
+
File.open(saved_file, 'w') do |f|
|
23
|
+
f.puts "#{Time.now.to_i} #{response['access_token']}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'rest-client'
|
2
|
+
require 'wechat_gate/request'
|
3
|
+
|
4
|
+
module WechatGate
|
5
|
+
module Tokens
|
6
|
+
class Base
|
7
|
+
|
8
|
+
def self.refresh config
|
9
|
+
handler = new(config)
|
10
|
+
handler.run
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize config
|
14
|
+
@config = config
|
15
|
+
end
|
16
|
+
|
17
|
+
def run
|
18
|
+
fetch if is_expired?
|
19
|
+
File.readlines(saved_file).first.chomp.split(' ').last
|
20
|
+
end
|
21
|
+
|
22
|
+
def expired_in
|
23
|
+
7100
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
def url
|
28
|
+
raise "need to implement #ur method in sub-class"
|
29
|
+
end
|
30
|
+
|
31
|
+
def save response
|
32
|
+
raise "need to implement #save method in sub-class"
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
def fetch
|
37
|
+
WechatGate::Request.send(url) do |response|
|
38
|
+
save response
|
39
|
+
response
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def saved_file
|
44
|
+
# File.expand_path("../../../../data/APP-#{@config.app_name}-#{self.class.name}", __FILE__)
|
45
|
+
"/tmp/APP-#{@config.app_name}-#{self.class.name}-#{@config.config['app_id']}"
|
46
|
+
end
|
47
|
+
|
48
|
+
def is_expired?
|
49
|
+
if File.exists?(saved_file)
|
50
|
+
line = File.readlines(saved_file).first
|
51
|
+
unless line
|
52
|
+
return true
|
53
|
+
else
|
54
|
+
line = line.chomp
|
55
|
+
return Time.now.to_i - line.split(' ').first.to_i >= expired_in
|
56
|
+
end
|
57
|
+
else
|
58
|
+
FileUtils.touch(saved_file)
|
59
|
+
return true
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module WechatGate
|
2
|
+
module Tokens
|
3
|
+
module Ext
|
4
|
+
# please refer
|
5
|
+
# http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html
|
6
|
+
#
|
7
|
+
def generate_js_request_params current_page_url
|
8
|
+
current_page_url = current_page_url.gsub(/#.*/, '')
|
9
|
+
|
10
|
+
letters = ('a'..'z').to_a + (0..9).to_a
|
11
|
+
word_creator = proc { letters.sample }
|
12
|
+
noncestr = []
|
13
|
+
16.times { noncestr << word_creator.call }
|
14
|
+
|
15
|
+
params = {
|
16
|
+
"jsapi_ticket" => self.jsapi_ticket,
|
17
|
+
"noncestr" => noncestr.join,
|
18
|
+
"timestamp" => Time.now.to_i,
|
19
|
+
"url" => current_page_url
|
20
|
+
}
|
21
|
+
|
22
|
+
sign_string = params.keys.sort.inject([]) { |m, n| m << "#{n}=#{params[n]}" }.join('&')
|
23
|
+
sign = Digest::SHA1.hexdigest(sign_string)
|
24
|
+
params["signature"] = sign
|
25
|
+
params["app_id"] = self.config['app_id']
|
26
|
+
|
27
|
+
params
|
28
|
+
end
|
29
|
+
|
30
|
+
def write_token_to_file current_page_url, output_type
|
31
|
+
params = generate_js_request_params(current_page_url)
|
32
|
+
case output_type
|
33
|
+
when /\// # write to file
|
34
|
+
f = File.open(output_type, 'w')
|
35
|
+
f.write %Q{<script>var wxServerConfig = #{params.to_json}</script>}
|
36
|
+
f.close
|
37
|
+
when 'ruby'
|
38
|
+
params
|
39
|
+
when 'js'
|
40
|
+
params.to_json
|
41
|
+
else
|
42
|
+
params
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'wechat_gate/tokens/base'
|
2
|
+
|
3
|
+
module WechatGate
|
4
|
+
module Tokens
|
5
|
+
module JsapiTicket
|
6
|
+
def self.included base
|
7
|
+
base.send :include, InstanceMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
module InstanceMethods
|
11
|
+
def jsapi_ticket
|
12
|
+
token = WechatGate::Tokens::JsapiTicket::Get.refresh(self)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Get < WechatGate::Tokens::Base
|
17
|
+
def url
|
18
|
+
token = WechatGate::Tokens::AccessToken::Get.refresh(@config)
|
19
|
+
"https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=#{token}&type=jsapi"
|
20
|
+
end
|
21
|
+
|
22
|
+
def save response
|
23
|
+
File.open(saved_file, 'w') do |f|
|
24
|
+
f.puts "#{Time.now.to_i} #{response['ticket']}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'wechat_gate/request'
|
2
|
+
|
3
|
+
module WechatGate
|
4
|
+
module User
|
5
|
+
def users(next_openid = nil)
|
6
|
+
WechatGate::Request.send("https://api.weixin.qq.com/cgi-bin/user/get?access_token=#{self.access_token}&next_openid=#{next_openid}")
|
7
|
+
end
|
8
|
+
|
9
|
+
def user(openid)
|
10
|
+
WechatGate::Request.send("https://api.weixin.qq.com/cgi-bin/user/info?access_token=#{self.access_token}&openid=#{openid}&lang=zh_CN")
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
end
|
data/wechat-gate.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'wechat_gate/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "wechat-gate-updater"
|
8
|
+
spec.version = WechatGate::VERSION
|
9
|
+
spec.authors = ["Lei Lee"]
|
10
|
+
spec.email = ["qinyuanmao.live@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{隔壁家老李的微信开发Ruby Gem}
|
13
|
+
spec.description = %q{接口简单易用,实在是微信开发必备之好Gem}
|
14
|
+
spec.homepage = "https://github.com/qinyuanmao/wechat_gate"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_dependency "rest-client", '>= 1.8'
|
23
|
+
spec.add_dependency "activesupport", '>= 5.0.1'
|
24
|
+
|
25
|
+
spec.add_development_dependency "bundler", ">= 1.10"
|
26
|
+
spec.add_development_dependency "rake", ">= 10.0"
|
27
|
+
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: wechat-gate-updater
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.6
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Lei Lee
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-02-18 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rest-client
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.8'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.8'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activesupport
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 5.0.1
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 5.0.1
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.10'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.10'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10.0'
|
69
|
+
description: 接口简单易用,实在是微信开发必备之好Gem
|
70
|
+
email:
|
71
|
+
- qinyuanmao.live@gmail.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- ".gitignore"
|
77
|
+
- CODE_OF_CONDUCT.md
|
78
|
+
- Gemfile
|
79
|
+
- Gemfile.lock
|
80
|
+
- LICENSE.txt
|
81
|
+
- README.md
|
82
|
+
- RELEASE.md
|
83
|
+
- Rakefile
|
84
|
+
- bin/console
|
85
|
+
- bin/refresh_token_and_ticket
|
86
|
+
- bin/setup
|
87
|
+
- config/wechat.yml
|
88
|
+
- lib/tasks/wechat_gate.rake
|
89
|
+
- lib/wechat-gate.rb
|
90
|
+
- lib/wechat_gate/config.rb
|
91
|
+
- lib/wechat_gate/controller.rb
|
92
|
+
- lib/wechat_gate/exception.rb
|
93
|
+
- lib/wechat_gate/media.rb
|
94
|
+
- lib/wechat_gate/menu.rb
|
95
|
+
- lib/wechat_gate/message.rb
|
96
|
+
- lib/wechat_gate/oauth.rb
|
97
|
+
- lib/wechat_gate/railtie.rb
|
98
|
+
- lib/wechat_gate/request.rb
|
99
|
+
- lib/wechat_gate/send_message.rb
|
100
|
+
- lib/wechat_gate/tokens/access_token.rb
|
101
|
+
- lib/wechat_gate/tokens/base.rb
|
102
|
+
- lib/wechat_gate/tokens/ext.rb
|
103
|
+
- lib/wechat_gate/tokens/jsapi_ticket.rb
|
104
|
+
- lib/wechat_gate/user.rb
|
105
|
+
- lib/wechat_gate/version.rb
|
106
|
+
- wechat-gate.gemspec
|
107
|
+
homepage: https://github.com/qinyuanmao/wechat_gate
|
108
|
+
licenses:
|
109
|
+
- MIT
|
110
|
+
metadata: {}
|
111
|
+
post_install_message:
|
112
|
+
rdoc_options: []
|
113
|
+
require_paths:
|
114
|
+
- lib
|
115
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
116
|
+
requirements:
|
117
|
+
- - ">="
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: '0'
|
120
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
requirements: []
|
126
|
+
rubygems_version: 3.1.4
|
127
|
+
signing_key:
|
128
|
+
specification_version: 4
|
129
|
+
summary: 隔壁家老李的微信开发Ruby Gem
|
130
|
+
test_files: []
|