mini_program 0.1.0

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: 04f5a8d75c16656473df0c93306b9d83833c272037d7fb60e894756eed9f4b1c
4
+ data.tar.gz: '0868870f615a9e08047465024b8293be7af4080d64fa4b1d7313ea7b56926e4a'
5
+ SHA512:
6
+ metadata.gz: ee4c68f56483b9acdbee602db221033ea503738ddd63409288b864d130ea37ca23a291fc2392a3f146ea0c5991236e8acd0f1f48a4f05ae8f817ef4f5792e100
7
+ data.tar.gz: e1ec32db171b7e4fe2168ac715668e9f6ed9e3704e276c7a61b7e6dbc30a6b30858bec73acf83c406459ef32c85382954c89e6e2efdf29ce40c86fae733d3a86
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2021 ian
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,143 @@
1
+ # MiniProgram
2
+ Short description and motivation.
3
+
4
+ ## 开始
5
+
6
+ 1. 添加`gem`
7
+ ```ruby
8
+ gem 'mini_program'
9
+ ```
10
+
11
+ 2. 执行 bundle
12
+ ```bash
13
+ $ bundle
14
+ ```
15
+
16
+ 3. 生成`initializer`文件,需要將里边的内容修改成你自己的小程序的配置
17
+ ```bash
18
+ $ rails g mini_program:install
19
+ ```
20
+
21
+ 4. 如果需要订阅消息,使用如下命令生成订阅消息的配置
22
+ ```bash
23
+ $ rails g mini_program:msg_config
24
+ ```
25
+
26
+ ## 使用
27
+
28
+ #### 授权登录(获取openid)
29
+ ```ruby
30
+ mp = MiniProgram::Client.new
31
+
32
+ # code 是在小程序端调用 wx.login 拿到的
33
+ result = mp.login(code)
34
+ if result.success?
35
+ open_id = result.data[:openid]
36
+ session_key = result.data[:session_key]
37
+ end
38
+ ```
39
+
40
+ #### 获取手机号
41
+ ```ruby
42
+ mp = MiniProgram::Client.new
43
+
44
+ # 小程序端 wx.login 拿到的 code
45
+ code = "041PuvGa1rysrB0noDJa1n7RBv2PuvGe"
46
+
47
+ # encrypted_data 和 iv 都是小程序端 getPhoneNum 获取到的参数
48
+ encrypted_data = "3G/+Fh6kCBaQszXFTxz3h3HFSbu0UuVW/4aLbz8WGzrKfmbGpvnxYHAa4QrKXJvHpB++3ogOYoU6iiG+1HW18Lkt9qEJE9GyRw5OnuXSjTnUIPSRROT3sxeAYnT1kf4ngTAfrD3f4TFtLXkRIrrc1MzSqx/LV8iXA8Lu5Y+7kZx26eulz3yVrlXDH3BOIX6zcGOeprsK5XzDx2ltmf3j5w=="
49
+ iv = "5tiyfVEHNVgHN4n8lzDrUA=="
50
+
51
+ result = mp.get_phone_num(code: code, encrypted_data: encrypted_data, iv: iv)
52
+
53
+ if result.success?
54
+ phone_num = result[:phone_num]
55
+ end
56
+
57
+ ```
58
+
59
+ #### 发送订阅消息
60
+ ```ruby
61
+
62
+ # 订阅消息配置 ,具体模板配置请参考小程序后台模板配置
63
+ #
64
+ # config/subscribe_msg.yml
65
+ #
66
+ # progress:
67
+ # template_id: YQ0cmL_ugsXwPaA2Pl75IMo_qtoc1n6CtT1orIeX4_o
68
+ # page: "/pages/index/index"
69
+ # data:
70
+ # thing2:
71
+ # value: "%{title}"
72
+ # phrase4:
73
+ # value: "%{state}"
74
+ # thing1:
75
+ # value: "%{detail}"
76
+
77
+ msg = MiniProgram::Msg.new(:progress, {
78
+ title: "测试",
79
+ state: "已完成",
80
+ detail: "测试已完成"
81
+ })
82
+
83
+ # send_to 的参数可以是 openid(字符串),或者是带有 open_id 方法的对象,返回 MiniProgram::ServiceResult
84
+ result msg.send_to("ogT7J5YddGnll-ippRvJq62Nv5W0")
85
+
86
+ # or
87
+ user = User.first
88
+ user.open_id # => ogT7J5YddGnll-ippRvJq62Nv5W0
89
+ result = msg.send_to(user)
90
+
91
+ if result.success?
92
+ puts "发送成功"
93
+ end
94
+
95
+ ```
96
+
97
+ #### 获取`access-token`
98
+ ```ruby
99
+ mp = MiniProgram::Client.new
100
+
101
+ # 缓存在 redis 中,缓存时间为 1.5 小时,微信官方`access-token`过期时间是 2小时
102
+ result = mp.get_access_token
103
+ if result.success?
104
+ access_token = result[:access_token]
105
+ end
106
+
107
+ # 传入 fresh: true 可以不使用缓存,直接调用微信api获取到 token,默认为 false
108
+ result = mp.access_token fresh:true
109
+ ```
110
+
111
+ #### MiniProgram::ServiceResult 类
112
+ ```ruby
113
+ # success 默认为 false
114
+ result = MiniProgram::ServiceResult.new(success: true, data: {data1: 1})
115
+
116
+ ## 处理结果(成功)
117
+ if result.success?
118
+ # do something...
119
+ end
120
+
121
+ # or
122
+ result.on_success do |result|
123
+ # do something...
124
+ end
125
+
126
+ ## 处理结果(失败)
127
+ if result.failure?
128
+ # do something...
129
+ end
130
+
131
+ result.on_failure do |result|
132
+ # do something...
133
+ end
134
+
135
+
136
+
137
+ ```
138
+
139
+ ## Contributing
140
+ Contribution directions go here.
141
+
142
+ ## License
143
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ require "bundler/setup"
2
+
3
+ require "bundler/gem_tasks"
4
+
5
+ require "rake/testtask"
6
+
7
+ Rake::TestTask.new(:test) do |t|
8
+ t.libs << 'test'
9
+ t.pattern = 'test/**/*_test.rb'
10
+ t.verbose = false
11
+ end
12
+
13
+ task default: :test
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Explain the generator
3
+
4
+ Example:
5
+ bin/rails generate install Thing
6
+
7
+ This will create:
8
+ what/will/it/create
@@ -0,0 +1,24 @@
1
+ class MiniProgram::InstallGenerator < Rails::Generators::Base
2
+ source_root File.expand_path('templates', __dir__)
3
+ def create_initializer_file
4
+ # copy_file "initializer.rb", "config/initializers/mini_program.rb"
5
+ appid = if Object.const_defined? "WechatPayment"
6
+ WechatPayment.sub_appid || WechatPayment.appid || "your appid"
7
+ else
8
+ "your appid"
9
+ end
10
+
11
+ app_secret = if Object.const_defined? "WechatPayment"
12
+ WechatPayment.sub_app_secret || WechatPayment.app_secret || "your app secret"
13
+ else
14
+ "your app_secret"
15
+ end
16
+ create_file "config/initializers/mini_program.rb", <<~INITIALIZER
17
+
18
+ MiniProgram.setup do |config|
19
+ config.appid = "#{appid}"
20
+ config.app_secret = "#{app_secret}"
21
+ end
22
+ INITIALIZER
23
+ end
24
+ end
@@ -0,0 +1,5 @@
1
+
2
+ MiniProgram.setup do |config|
3
+ config.appid = "your appid"
4
+ config.app_secret = "your appsecret"
5
+ end
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Explain the generator
3
+
4
+ Example:
5
+ bin/rails generate msg_config Thing
6
+
7
+ This will create:
8
+ what/will/it/create
@@ -0,0 +1,7 @@
1
+ class MiniProgram::MsgConfigGenerator < Rails::Generators::Base
2
+ source_root File.expand_path('templates', __dir__)
3
+
4
+ def create_subscribe_msg_config_file
5
+ copy_file "subscribe_msg.yml", "config/subscribe_msg.yml"
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ ---
2
+ progress:
3
+ template_id: YQ0cmL_ugsXwPaA2Pl75IMo_qtoc1n6CtT1orIeX4_o
4
+ page: "/pages/index/index"
5
+ data:
6
+ thing2:
7
+ value: "%{title}"
8
+ phrase4:
9
+ value: "%{state}"
10
+ thing1:
11
+ value: "%{detail}"
@@ -0,0 +1,16 @@
1
+ require "mini_program/version"
2
+ require "mini_program/railtie"
3
+ require "mini_program/client"
4
+ require "mini_program/msg"
5
+ require "r_logger"
6
+ require "service_result"
7
+ require "redis"
8
+
9
+ module MiniProgram
10
+ # Your code goes here...
11
+ mattr_accessor :appid, :app_secret
12
+
13
+ def self.setup
14
+ yield self if block_given?
15
+ end
16
+ end
@@ -0,0 +1,201 @@
1
+ module MiniProgram
2
+ class Client
3
+ attr_reader :appid, :app_secret
4
+
5
+ def initialize(appid: config.appid, app_secret: config.app_secret)
6
+ @appid = appid
7
+ @app_secret = app_secret
8
+
9
+ if appid == 'your appid'
10
+ warn "\e[33m" + "*" * 80 + "\e[0m"
11
+ warn "\e[33m警告: 请将 config/initializer/mini_program.rb 中的 appid 修改成你实际的appid\e[0m"
12
+ warn "\e[33m" + "*" * 80 + "\e[0m"
13
+ end
14
+
15
+ if app_secret == 'your app_secret'
16
+ warn "\e[33m" + "*" * 80 + "\e[0m"
17
+ warn "\e[33m警告: 请将 config/initializer/mini_program.rb 中的 app_secret 修改成你实际的app_secret\e[0m"
18
+ warn "\e[33m" + "*" * 80 + "\e[0m"
19
+ end
20
+ end
21
+
22
+ def get_access_token(fresh: false)
23
+ access_token = redis.get("mp-#{appid}-access-token")
24
+ if access_token.present? && !fresh
25
+ return MiniProgram::ServiceResult.new(success: true, data: {access_token: access_token})
26
+ end
27
+
28
+ result = request_access_token
29
+ if result.success?
30
+ redis.setex "mp-#{appid}-access-token", 1.5.hours.to_i, access_token
31
+ end
32
+
33
+ yield result if block_given?
34
+
35
+ result
36
+ end
37
+
38
+ # 调用微信 api 获取 access_token
39
+ #
40
+ # @return MiniProgram::ServiceResult
41
+ def request_access_token
42
+ api = "https://api.weixin.qq.com/cgi-bin/token"
43
+ params = {
44
+ appid: appid,
45
+ secret: app_secret,
46
+ grant_type: :client_credential
47
+ }
48
+
49
+ response = get(api, params)
50
+
51
+ result = JSON.parse(response)
52
+
53
+ if result["errcode"].present?
54
+ logger.error <<~ERROR
55
+ Get access token failed.
56
+ api: #{api}
57
+ error: #{result}
58
+ ERROR
59
+ return MiniProgram::ServiceResult.new(success: false, errors: result, message: result["errmsg"])
60
+ end
61
+
62
+ MiniProgram::ServiceResult.new(success: true, data: result)
63
+ end
64
+
65
+ # 调用微信授权登录 api
66
+ #
67
+ #
68
+ def login(code)
69
+ api = "https://api.weixin.qq.com/sns/jscode2session"
70
+ params = {
71
+ appid: appid,
72
+ secret: app_secret,
73
+ js_code: code,
74
+ grant_type: :authorization_code
75
+ }
76
+
77
+ response = get(api, params)
78
+
79
+ result = JSON.parse(response)
80
+
81
+ if result["errcode"]
82
+ logger.error <<~ERROR
83
+ Get session key failed.
84
+ api: #{api}
85
+ result: #{result}
86
+ ERROR
87
+ return MiniProgram::ServiceResult.new(errors: result, message: result["errmsg"])
88
+ end
89
+
90
+ MiniProgram::ServiceResult.new(success: true, data: result)
91
+ end
92
+
93
+ # 发送订阅消息
94
+ # @param [MiniProgram::Msg] msg
95
+ # @param [String] to 用户的open id
96
+ def send_msg(msg, to: )
97
+ open_id = to.try(:open_id) || to
98
+
99
+ payload = msg.as_json.merge!(touser: open_id)
100
+
101
+ # 获取 access_token
102
+ get_token_result = get_access_token
103
+ if get_token_result.failure?
104
+ return get_token_result
105
+ end
106
+
107
+ api = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=#{get_token_result["access_token"]}"
108
+ result = post(api, payload)
109
+
110
+ msg_logger.info {"{params: #{payload}, response: #{result}}"}
111
+ MiniProgram::ServiceResult.new(success: true, data: result)
112
+ end
113
+
114
+ # 获取用户手机号
115
+ def get_phone_num(code:, encrypted_data:, iv:)
116
+ login_result = login(code)
117
+ return login_result if login_result.failure?
118
+
119
+ open_id = login_result.data[:openid]
120
+ session_key = login_result.data[:session_key]
121
+
122
+ data = decrypt_phone_data(encrypted_data, iv, session_key)
123
+
124
+ phone_num = JSON.parse(data)["phoneNumber"]
125
+
126
+ MiniProgram::ServiceResult.new(
127
+ success: true,
128
+ data: {
129
+ open_id: open_id,
130
+ phone_num: phone_num
131
+ })
132
+ end
133
+
134
+ def config
135
+ appid, app_secret = if MiniProgram.appid && MiniProgram.app_secret
136
+ [MiniProgram.appid, MiniProgram.app_secret]
137
+
138
+ # 如果有挂载 WechatPayment 的 engine 时,使用里边的小程序配置
139
+ elsif Object.const_defined? "WechatPayment"
140
+ [WechatPayment.sub_appid || WechatPayment.appid, WechatPayment.sub_app_secret || WechatPayment.app_secret]
141
+ else
142
+ [nil, nil]
143
+ end
144
+
145
+ Struct.new(:appid, :app_secret).new(appid, app_secret)
146
+ end
147
+
148
+ private
149
+
150
+ def get(api, payload = {})
151
+ uri = URI(api)
152
+
153
+ if payload.present?
154
+ uri.query = URI.encode_www_form(payload)
155
+ end
156
+
157
+ Net::HTTP.get(uri)
158
+ end
159
+
160
+ def post(api, payload = {}, options = {})
161
+ uri = URI(api)
162
+
163
+ req = Net::HTTP::Post.new(uri)
164
+ req["Content-Type"] = "application/json"
165
+ options = {
166
+ use_ssl: true
167
+ }.merge(options)
168
+
169
+ res = Net::HTTP.start(uri.host, uri.port, **options) do |http|
170
+ http.request(req, payload.to_json)
171
+ end
172
+
173
+ JSON.parse(res.body)
174
+ end
175
+
176
+ def decrypt_phone_data(encrypted_data, iv, session_key)
177
+ aes = OpenSSL::Cipher::AES.new "128-CBC"
178
+ aes.decrypt
179
+ aes.key = Base64::decode64(session_key)
180
+ aes.iv = Base64.decode64(iv)
181
+ aes.update(Base64::decode64(encrypted_data)) + aes.final
182
+ end
183
+
184
+ def logger
185
+ @logger ||= MiniProgram::RLogger.make("mini_program")
186
+ end
187
+
188
+ def redis
189
+ @redis ||= Redis.current
190
+ end
191
+
192
+ def access_token_store_key
193
+ "mp-#{appid}-access-token"
194
+ end
195
+
196
+ def msg_logger
197
+ @msg_logger ||= MiniProgram::RLogger.make("wx_msg")
198
+ end
199
+
200
+ end
201
+ end
@@ -0,0 +1,30 @@
1
+
2
+ module MiniProgram
3
+ class Msg
4
+ attr_reader :msg_config, :type, :data
5
+ def initialize(type, data)
6
+ @type = type
7
+ @data = data
8
+ end
9
+
10
+ def as_json
11
+ {
12
+ template_id: config[:template_id],
13
+ data: JSON.parse(config[:data].to_json % data),
14
+ page: config[:page],
15
+ }
16
+ end
17
+
18
+ def send_to(open_id)
19
+ mini_program.send_msg(self, to: open_id)
20
+ end
21
+
22
+ def mini_program
23
+ @mini_program ||= MiniProgram::Client.new
24
+ end
25
+
26
+ def config
27
+ @config ||= YAML.load_file(Rails.root.join("config/subscribe_msg.yml")).with_indifferent_access[type]
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,4 @@
1
+ module MiniProgram
2
+ class Railtie < ::Rails::Railtie
3
+ end
4
+ end
@@ -0,0 +1,3 @@
1
+ module MiniProgram
2
+ VERSION = '0.1.0'
3
+ end
data/lib/r_logger.rb ADDED
@@ -0,0 +1,36 @@
1
+ module MiniProgram
2
+ class RLogger
3
+ def initialize
4
+ raise Exceptions::InitializeDenied.new("please use 'ILogger.make' instead of 'ILogger.new'")
5
+ end
6
+
7
+ class << self
8
+ def make(log_file)
9
+ @logger ||= {}
10
+
11
+ log_file_name = if log_file.class.in? [String, Symbol]
12
+ log_file_name = log_file.to_sym
13
+
14
+ unless log_file_name.to_s.end_with? ".log"
15
+ log_file_name = "#{log_file_name}.log"
16
+ end
17
+
18
+ "#{root_path}/#{log_file_name}"
19
+ elsif log_file.respond_to? :to_path
20
+ log_file.to_path
21
+ else
22
+ raise Exceptions::UnsupportdParamType.new("log file parameter only support 'File' or 'String' Type.")
23
+ end
24
+
25
+ # 如果已经存在日志对象,则返回已有的日志对象
26
+ @logger[log_file_name] ||= ::Logger.new(log_file_name)
27
+ end
28
+
29
+ def root_path
30
+ @root ||= "#{Rails.root}/log"
31
+ end
32
+ end
33
+
34
+
35
+ end
36
+ end
@@ -0,0 +1,49 @@
1
+ module MiniProgram
2
+ class ServiceResult
3
+ attr_accessor :success,
4
+ :errors,
5
+ :data,
6
+ :message,
7
+ :message_type
8
+
9
+ delegate :[], :[]=, to: :data
10
+
11
+ def initialize(success: false,
12
+ errors: nil,
13
+ message: nil,
14
+ message_type: nil,
15
+ data: {})
16
+ self.success = success
17
+ self.data = (data.presence || {}).with_indifferent_access
18
+ self.errors = errors.is_a?(Enumerable) ? errors : [errors]
19
+ self.message = message
20
+ self.message_type = message_type
21
+ end
22
+
23
+ alias success? :success
24
+
25
+ def failure?
26
+ !success?
27
+ end
28
+
29
+ def on_success
30
+ yield(self) if success?
31
+ self
32
+ end
33
+
34
+ def on_failure
35
+ yield(self) if failure?
36
+ self
37
+ end
38
+
39
+ def get_message_type
40
+ if message_type.present?
41
+ message_type.to_sym
42
+ elsif success?
43
+ :notice
44
+ else
45
+ :error
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :mini_program do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mini_program
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - ian
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-08-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 6.1.4
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 6.1.4
27
+ - !ruby/object:Gem::Dependency
28
+ name: redis
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
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: pry-rails
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: 登录授权,发送订阅消息
56
+ email:
57
+ - ianlynxk@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - MIT-LICENSE
63
+ - README.md
64
+ - Rakefile
65
+ - lib/generators/mini_program/install/USAGE
66
+ - lib/generators/mini_program/install/install_generator.rb
67
+ - lib/generators/mini_program/install/templates/initializer.rb
68
+ - lib/generators/mini_program/msg_config/USAGE
69
+ - lib/generators/mini_program/msg_config/msg_config_generator.rb
70
+ - lib/generators/mini_program/msg_config/templates/subscribe_msg.yml
71
+ - lib/mini_program.rb
72
+ - lib/mini_program/client.rb
73
+ - lib/mini_program/msg.rb
74
+ - lib/mini_program/railtie.rb
75
+ - lib/mini_program/version.rb
76
+ - lib/r_logger.rb
77
+ - lib/service_result.rb
78
+ - lib/tasks/mini_program_tasks.rake
79
+ homepage: https://github.com/otorain
80
+ licenses:
81
+ - MIT
82
+ metadata:
83
+ homepage_uri: https://github.com/otorain
84
+ source_code_uri: https://github.com/otorain/mini_program
85
+ changelog_uri: https://github.com/otorain/mini_program/CHANGELOG.md
86
+ post_install_message:
87
+ rdoc_options: []
88
+ require_paths:
89
+ - lib
90
+ required_ruby_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ requirements: []
101
+ rubygems_version: 3.2.15
102
+ signing_key:
103
+ specification_version: 4
104
+ summary: 小程序api开发工具
105
+ test_files: []