utils-rails 1.2.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 9b338086b803512c2d6ecadb85d0e420758ffc4368b08927c680a38ed6ef389f
4
+ data.tar.gz: 307553cbeeca15b2561a7d5dcf7d716064fe95091c80c546bc5437c04628522a
5
+ SHA512:
6
+ metadata.gz: 5b940d4e5403145aec99c05fff67636dfd09dc3b0d35fbc8ecf5965cdf121e6ed98768412b388ee49ff1d18e8e21dd219646d697a49b508ddb61d2f933051f05
7
+ data.tar.gz: 3b8ab3c1268d7a3515d64aa0ec34443153241e0daaac22acaa7fbc6d3dfc453b775310f7c4829f8eebbfc3965e14ae644c6f66a277b4dc409a2efd72f1383b21
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2013 Wangfuhai
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.rdoc ADDED
@@ -0,0 +1,3 @@
1
+ = Ums
2
+
3
+ This project rocks and uses MIT-LICENSE.
data/Rakefile ADDED
@@ -0,0 +1,34 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Utils'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+
21
+
22
+ Bundler::GemHelper.install_tasks
23
+
24
+ require 'rake/testtask'
25
+
26
+ Rake::TestTask.new(:test) do |t|
27
+ t.libs << 'lib'
28
+ t.libs << 'test'
29
+ t.pattern = 'test/**/*_test.rb'
30
+ t.verbose = false
31
+ end
32
+
33
+
34
+ task default: :test
@@ -0,0 +1,19 @@
1
+ module Utils
2
+ class Common
3
+
4
+ def self.convert_bool(val)
5
+ ret = val
6
+ if ActiveRecord::Base.connection.adapter_name.downcase.starts_with? 'sqlite'
7
+ ret = "'t'" if val.to_i > 0
8
+ ret = "'f'" if val.to_i <= 0
9
+ end
10
+ if ActiveRecord::Base.connection.adapter_name.downcase.starts_with? 'mysql'
11
+ ret = 1 if val == 't'
12
+ ret = 0 if val == 'f'
13
+ end
14
+
15
+ ret
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,213 @@
1
+ module Utils
2
+
3
+ class FileUtil
4
+
5
+ #定义logger
6
+ def self.logger
7
+ Rails.logger
8
+ end
9
+
10
+ def self.get_full_path(path,file_name="")
11
+ if Rails.configuration.respond_to?('upload_root') && !Rails.configuration.upload_root.blank?
12
+ full_path = File.join(Rails.configuration.upload_root,path).to_s
13
+ else
14
+ full_path = Rails.root.join("public",path).to_s
15
+ end
16
+ full_path = File.join(full_path,file_name) unless file_name.blank?
17
+ full_path
18
+ end
19
+
20
+ #获取预览图文件名
21
+ def self.get_thumb_file(file_name,extname="")
22
+ extname = File.extname(file_name) if extname.blank?
23
+ extname = "." + extname if extname.index('.').nil?
24
+ file_name[0..file_name.index('.')-1] + "_thumb" + extname unless file_name.index('.').nil?
25
+ end
26
+
27
+ #获取移动图片文件名
28
+ def self.get_mobile_file(file_name,extname="")
29
+ extname = File.extname(file_name) if extname.blank?
30
+ extname = "." + extname if extname.index('.').nil?
31
+ file_name[0..file_name.index('.')-1] + "_mobile" + extname unless file_name.index('.').nil?
32
+ end
33
+
34
+ #删除文件
35
+ def self.delete_file(file_name)
36
+ unless file_name.blank?
37
+ full_name = get_full_path(file_name)
38
+ #logger.debug("delete file:"+full_name.to_s)
39
+ File.delete(full_name) if File.exist?(full_name)
40
+
41
+ thumb_name = get_thumb_file(file_name)
42
+ full_name = get_full_path(thumb_name) if thumb_name
43
+ File.delete(full_name) if File.file?(full_name)
44
+
45
+ thumb_name = get_thumb_file(file_name,'jpg')
46
+ full_name = get_full_path(thumb_name) if thumb_name
47
+ File.delete(full_name) if File.file?(full_name)
48
+
49
+ mobile_name = get_mobile_file(file_name)
50
+ full_name = get_full_path(mobile_name) if mobile_name
51
+ File.delete(full_name) if File.file?(full_name)
52
+
53
+ mobile_name = get_mobile_file(file_name,'jpg')
54
+ full_name = get_full_path(mobile_name) if mobile_name
55
+ File.delete(full_name) if File.file?(full_name)
56
+
57
+ end
58
+ end
59
+
60
+
61
+ #检查文件上传许可
62
+ def self.check_ext(res_file)
63
+ if res_file
64
+ extname = File.extname(res_file.original_filename)
65
+
66
+ is_allowed_ext = false
67
+ Rails.configuration.upload_extname.split(';').each do |ext|
68
+ if ext.to_s.upcase == extname.to_s.upcase
69
+ is_allowed_ext = true
70
+ break
71
+ end
72
+ end
73
+ #logger.debug("res_file: "+ is_allowed_ext.to_s)
74
+ is_allowed_ext
75
+ else
76
+ true #无文件返回true
77
+ end
78
+ end
79
+
80
+ #上传文件
81
+ def self.upload (res_file,to_jpg=true,width=0) # res_file为 ActionController::UploadedFile 对象
82
+ if res_file
83
+ upload_path = get_upload_save_path
84
+ file_name = get_upload_save_name(res_file.original_filename,to_jpg)
85
+
86
+ abs_file_name = get_full_path(upload_path,file_name)
87
+ logger.debug("res_file:" + res_file.original_filename + ",abs_file_name:" + abs_file_name)
88
+
89
+ max_width = 0
90
+ max_width = Rails.configuration.image_max_width.to_i if Rails.configuration.respond_to?('image_max_width')
91
+ max_width = width unless width == 0
92
+ #只配置了image_max_width,才做图片缩小处理,jpg图质量统一使用80
93
+ if image_file?(abs_file_name) && (to_jpg || max_width > 0)
94
+ resize_image_file(res_file.path,abs_file_name,max_width)
95
+ else
96
+ File.open(abs_file_name, 'wb') do |file|
97
+ file.write(res_file.read)
98
+ end
99
+ end
100
+
101
+ upload_path + "/" + file_name
102
+ end
103
+ end
104
+
105
+ #从URL保存文件
106
+ def self.save_from_url (url,to_jpg=false)
107
+ save_path = get_upload_save_path + "/" + get_upload_save_name(url,to_jpg)
108
+ save_path += ".jpg" if to_jpg && File.extname(save_path).blank?
109
+ conn = Faraday.new(:url => url)
110
+ File.open(get_full_path(save_path).to_s, 'wb') { |f| f.write(conn.get.body) }
111
+ return save_path
112
+ end
113
+
114
+ #获取上传文件保存路径
115
+ def self.get_upload_save_path
116
+ upload_path = "upload"
117
+ if Rails.configuration.respond_to?('upload_path') && !Rails.configuration.upload_path.blank?
118
+ upload_path = Rails.configuration.upload_path
119
+ end
120
+ upload_path += "/"+ Time.now.strftime("%Y%m/%d")
121
+ unless Dir.exist?(get_full_path(upload_path))
122
+ FileUtils.mkdir_p(get_full_path(upload_path))
123
+ end
124
+ upload_path
125
+ end
126
+
127
+ #获取上传文件保存名称
128
+ def self.get_upload_save_name(ori_filename,to_jpg=true)
129
+ file_name_main = (Time.now.to_f * 1000000).to_i.to_s(16) + Digest::SHA2.hexdigest(rand.to_s)[0,8]
130
+ file_name_ext = File.extname(ori_filename).downcase #扩展名统一小写
131
+ file_name_ext = ".jpg" if image_file?(ori_filename) && to_jpg
132
+ file_name = file_name_main + file_name_ext
133
+ end
134
+
135
+ #缩小图片尺寸
136
+ def self.resize_image_file(src_file,dst_file="",max_width=0)
137
+ return unless File.exist?(src_file)
138
+ image = MiniMagick::Image.open(src_file)
139
+ dst_file = src_file if dst_file.blank?
140
+ if image[:width] > max_width && max_width > 0
141
+ image.resize max_width.to_s + "x"
142
+ end
143
+ if File.extname(dst_file) == '.jpg'
144
+ image.format "jpg"
145
+ image.quality "80"
146
+ end
147
+ image.auto_orient
148
+ image.write dst_file
149
+ File.chmod(0644,dst_file) # MiniMagick没有处理图片(resize或format)而直接写文件时,默认把文件权限设为600
150
+ end
151
+
152
+ #检查是否图片文件名
153
+ def self.image_file?(file_name)
154
+ return !file_name.blank? && !!file_name.downcase.match("\\.png|\\.bmp|\\.jpeg|\\.jpg|\\.gif")
155
+ end
156
+
157
+ #生成缩略图
158
+ def self.thumb_image(file_name,format="jpg",size="0")
159
+ thumb_size = "300x"
160
+ thumb_size = Rails.configuration.image_thumb_size if Rails.configuration.respond_to?('image_thumb_size')
161
+ thumb_size = size.to_s unless size == "0"
162
+
163
+ resize_image(file_name,get_thumb_file(file_name),thumb_size)
164
+ resize_image(file_name,get_thumb_file(file_name,format),thumb_size) if File.extname(file_name).downcase.sub(".","") != format
165
+ end
166
+
167
+ #生成手机图
168
+ def self.mobile_image(file_name,format="jpg",size="0")
169
+ mobile_size = "720x"
170
+ mobile_size = Rails.configuration.image_thumb_size if Rails.configuration.respond_to?('image_mobile_size')
171
+ mobile_size = size.to_s unless size == "0"
172
+
173
+ resize_image(file_name,get_mobile_file(file_name),thumb_size)
174
+ resize_image(file_name,get_mobile_file(file_name,format),thumb_size) if File.extname(file_name).downcase.sub(".","") != format
175
+ end
176
+
177
+ #内部方法,不对外
178
+ def self.resize_image(src_file,dst_file,size)
179
+ #logger.debug(src_file + "," + dst_file + "," + size)
180
+ src_file = get_full_path(src_file) if !src_file.start_with?("/")
181
+ dst_file = get_full_path(dst_file) if !dst_file.start_with?("/")
182
+ if image_file?(src_file) && File.exist?(src_file)
183
+ image = MiniMagick::Image.open(src_file)
184
+ size += "x" if !!(size =~ /\A[0-9]+\z/) # 如果只设置一个数字,则默认为宽
185
+ size += ">" if size.index(">").nil? && size.index("<").nil? && size.index("^").nil? && size.index("!").nil? # 默认不放大,只缩小
186
+ #logger.debug(src_file + "," + dst_file + "," + size)
187
+ image.resize size
188
+ image.auto_orient
189
+ src_ext = File.extname(src_file).downcase.sub(".","")
190
+ dst_ext = File.extname(dst_file).downcase.sub(".","")
191
+ if src_ext != dst_ext
192
+ image.format dst_ext
193
+ image.quality "80" if dst_ext == 'jpg'
194
+ end
195
+
196
+ #image.combine_options do |i|
197
+ # i.resize "150x150^"
198
+ # i.gravity "center"
199
+ # i.crop "150x150+0+0"
200
+ # end
201
+ image.write dst_file
202
+ end
203
+ end
204
+
205
+ #获取图片文件信息
206
+ def self.get_image_info(file_name)
207
+ file_name = get_full_path(file_name) if !file_name.start_with?("/")
208
+ image = MiniMagick::Image.open(file_name)
209
+ return image
210
+ end
211
+
212
+ end
213
+ end
@@ -0,0 +1,8 @@
1
+ require 'utils/view_helper'
2
+ module Utils
3
+ class Railtie < Rails::Railtie
4
+ initializer "utils.view_helper" do
5
+ ActionView::Base.send :include, ViewHelper
6
+ end
7
+ end
8
+ end
data/lib/utils/sms.rb ADDED
@@ -0,0 +1,58 @@
1
+ module Utils
2
+
3
+ class Sms
4
+
5
+ #定义logger
6
+ def self.logger
7
+ Rails.logger
8
+ end
9
+
10
+ def self.send(mobile,message,provider='')
11
+ provider = Rails.configuration.sms_provider if provider.blank? && Rails.configuration.respond_to?('sms_provider')
12
+ result = false
13
+ case provider
14
+ when 'yunpian'
15
+ result = send_by_yunpian(mobile,message)
16
+ end
17
+ result
18
+ end
19
+ def self.send_by_yunpian(mobile,message)
20
+ result = false
21
+ apikey = Rails.configuration.sms_apikey if Rails.configuration.respond_to?('sms_apikey')
22
+ uri = URI.parse("http://yunpian.com/v1/sms/send.json")
23
+ response = Net::HTTP.post_form(uri, {"apikey" => apikey,"mobile"=>mobile,"text"=>message})
24
+ ret = JSON.parse(response.body)
25
+ if ret["code"] == 0
26
+ result = true
27
+ end
28
+ result
29
+ end
30
+
31
+ def self.send_verify_code(mobile,code,provider='')
32
+ provider = Rails.configuration.sms_provider if provider.blank? && Rails.configuration.respond_to?('sms_provider')
33
+ result = false
34
+ case provider
35
+ when 'yunpian'
36
+ result = send_verify_code_by_yunpian(mobile,code)
37
+ end
38
+ result
39
+ end
40
+ def self.send_verify_code_by_yunpian(mobile,code)
41
+ result = false
42
+ apikey = Rails.configuration.sms_apikey if Rails.configuration.respond_to?('sms_apikey')
43
+ company = Rails.configuration.sms_company if Rails.configuration.respond_to?('sms_company')
44
+ tpl_id = 1
45
+ tpl_id = Rails.configuration.sms_tpl_id if Rails.configuration.respond_to?('tpl_id')
46
+ tpl_value = "#code#=#{code}&#company#=#{company}"
47
+ uri = URI.parse("http://yunpian.com/v1/sms/tpl_send.json")
48
+ response = Net::HTTP.post_form(uri, {"apikey" => apikey,"mobile"=>mobile,
49
+ "tpl_id"=>tpl_id,"tpl_value"=>tpl_value})
50
+ ret = JSON.parse(response.body)
51
+ logger.debug("send_verify_code_by_yunpian:" + ret.to_s)
52
+ if ret["code"] == 0
53
+ result = true
54
+ end
55
+ result
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,66 @@
1
+ module Utils
2
+ module ViewHelper
3
+
4
+ #输出布尔值标签
5
+ def bool_label(val)
6
+ output = "<span class=\"label " + (val ? 'label-success' : 'label-default') +
7
+ "\">" + (val ? '是' : '否') +"</span>"
8
+ output.html_safe
9
+ end
10
+
11
+ #输出预览图
12
+ def thumbnail(file,width=150,use_thumb=true)
13
+ output = '暂无图片'
14
+ unless file.blank?
15
+ if use_thumb
16
+ thumb_file = Utils::FileUtil.get_thumb_file(file)
17
+ full_name = Rails.root.join("public",thumb_file.to_s)
18
+ file = thumb_file if File.file?(full_name)
19
+ end
20
+ file = "/" + file if !file.start_with?("/") && !file.start_with?("http://")
21
+ output = "<div class=\"img-thumbnail\">" + image_tag(file,:width=>width) +"</div>"
22
+ end
23
+
24
+ output.html_safe
25
+ end
26
+
27
+ #字符串截取
28
+ def str_trim(str,length,postfix='...')
29
+ str[0,length]+(str.length > length ? postfix : "") if str
30
+ end
31
+
32
+ #分页显示标签
33
+ def info_paginate(collection, options = {})
34
+ pre_page = collection.current_page - 1
35
+ next_page = collection.current_page + 1
36
+ html = ""
37
+ html += "<div id=\"" + options[:container] = "\">" unless options[:container].nil?
38
+
39
+ html += "<span>共<span>" + collection.total_entries.to_s + "</span>条记录<span>" +
40
+ collection.total_pages.to_s + "</span>页</span>" if options[:show_total]
41
+
42
+ html += "<span><a id=\"first\" href=\"" + url_for(params.merge(:page=> "1")) +
43
+ "\">第一页</a></span>" if options[:full_link] && collection.current_page > 1
44
+
45
+ if pre_page > 0
46
+ html += "<span><a id=\"prev\" href=\"" + url_for(params.merge(:page=> pre_page)) + "\""
47
+ html += " class=\"" + options[:class] + "\"" unless options[:class].nil?
48
+ html += ">上一页</a></span>"
49
+ end
50
+
51
+ html += "<span>当前第<span>" + collection.current_page.to_s + "</span>页</span>" if options[:full_link]
52
+
53
+ if next_page <= collection.total_pages
54
+ html += "<span><a id=\"next\" href=\"" + url_for(params.merge(:page=> next_page)) + "\""
55
+ html += " class=\"" + options[:class] + "\"" unless options[:class].nil?
56
+ html += ">下一页</a></span>"
57
+ end
58
+
59
+ html += "<span><a id=\"last\" href=\"" + url_for(params.merge(:page=> collection.total_pages)) +
60
+ "\">最后一页</a></span>" if options[:full_link] && collection.current_page < collection.total_pages
61
+ html += "</div>" unless options[:container].nil?
62
+ html.html_safe
63
+ end
64
+
65
+ end
66
+ end
@@ -0,0 +1,260 @@
1
+ module Utils
2
+ class Weixin
3
+ @@ticket_list = {}
4
+ @@access_token_list = {}
5
+ @@oauth2_access_token_list = {}
6
+
7
+ class << self
8
+ attr_accessor :app_id,:app_secret,:mch_id,:pay_sign_key,:oauth2_state
9
+ end
10
+
11
+ #定义logger
12
+ def self.logger
13
+ Rails.logger
14
+ end
15
+
16
+ #提交数据
17
+ def self.post_data(data,path)
18
+ logger.debug("data:"+ JSON.parse(data).to_s)
19
+ uri = URI.parse("https://api.weixin.qq.com/")
20
+ http = Net::HTTP.new(uri.host, uri.port)
21
+ http.use_ssl = true
22
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
23
+ request = Net::HTTP::Post.new(path)
24
+ request.add_field('Content-Type', 'application/json')
25
+ request.body = data
26
+ response = http.request(request)
27
+
28
+ result = JSON.parse(response.body)
29
+ logger.debug("result:"+result.to_s)
30
+ return result
31
+ end
32
+
33
+ #获取oauth2 url
34
+ def self.get_oauth2_url(redirect_uri,auth_type="snsapi_userinfo",app_id=nil)
35
+ app_id = Utils::Weixin.app_id if app_id.nil?
36
+ redirect_uri = CGI::escape(redirect_uri)
37
+
38
+ state = 'oauth2'
39
+ state = Utils::Weixin.oauth2_state if Utils::Weixin.respond_to?('oauth2_state')
40
+ url = "https://open.weixin.qq.com/connect/oauth2/authorize?" +
41
+ "appid=" + app_id + "&redirect_uri=" + redirect_uri + "&response_type=code" +
42
+ "&scope=" + auth_type + "&state=" + state + "#wechat_redirect"
43
+ return url
44
+ end
45
+
46
+ def self.get_oauth2_access_token(code,app_id=nil,app_secret=nil)
47
+ app_id = Utils::Weixin.app_id if app_id.nil?
48
+ app_secret = Utils::Weixin.app_secret if app_secret.nil?
49
+ cache = @@oauth2_access_token_list[app_id]
50
+
51
+ token = nil
52
+ logger.debug("Utils::Weixin.get_access_token,cache access_token:"+cache.to_s)
53
+ if cache.nil? || cache[:expire_time].nil? ||
54
+ cache[:expire_time] - 600 < Time.now.to_i
55
+ #提前十分钟过期,避免时间不同步导致时间差异
56
+
57
+ return if code.blank? # 首次调用code为空时直接返回
58
+
59
+ url = "https://api.weixin.qq.com/sns/oauth2/access_token"
60
+ url += "?appid=" + app_id
61
+ url += "&secret=" + app_secret
62
+ url += "&code=" + code
63
+ url += "&grant_type=authorization_code"
64
+
65
+ conn = Faraday.new(:url => url,headers: { accept_encoding: 'none' })
66
+ result = JSON.parse conn.get.body
67
+ logger.debug("get oauth2_access_token result :"+result.to_s)
68
+ if result['access_token']
69
+ cache = {} if cache.nil?
70
+ cache[:access_token] = result['access_token']
71
+ cache[:expire_time] = Time.now.to_i + result['expires_in'].to_i
72
+ token = cache[:access_token]
73
+ @@oauth2_access_token_list[app_id] = cache # 更新缓存
74
+ logger.debug("Utils::Weixin.get_oauth2_access_token,access_token is update:"+cache[:access_token])
75
+ else
76
+ logger.error('Utils::Weixin.get_oauth2_access_token,获取access_token出错:' + result['errmsg'].to_s)
77
+ end
78
+ else
79
+ token = cache[:access_token]
80
+ end
81
+ return token
82
+ end
83
+
84
+ #获取opendid,用于snsapi_base授权
85
+ def self.get_openid(code,app_id=nil,app_secret=nil)
86
+ openid,access_token = Utils::Weixin.get_openid_and_access_token(code,app_id,app_secret)
87
+ return openid
88
+ end
89
+ #同时获取openid和access_token,用于snsapi_userinfo授权
90
+ def self.get_openid_and_access_token(code,app_id=nil,app_secret=nil)
91
+ app_id = Utils::Weixin.app_id if app_id.nil?
92
+ app_secret = Utils::Weixin.app_secret if app_secret.nil?
93
+ path = "/sns/oauth2/access_token?appid=" + app_id + "&secret=" + app_secret +
94
+ "&code=" + code + "&grant_type=authorization_code"
95
+ uri = URI.parse("https://api.weixin.qq.com/")
96
+ http = Net::HTTP.new(uri.host, uri.port)
97
+ http.use_ssl = true
98
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
99
+ request = Net::HTTP::Get.new(path)
100
+ request.add_field('Content-Type', 'application/json')
101
+ response = http.request(request)
102
+ result = JSON.parse(response.body)
103
+ logger.error("Utils::Weixin.get_openid fail,result:"+result.to_s) if result["openid"].blank?
104
+ openid = access_token = nil
105
+ openid = result["openid"] if result["openid"]
106
+ access_token = result["access_token"] if result["access_token"]
107
+ return openid,access_token
108
+ end
109
+
110
+ #通过网页授权获取用户信息(无须关注公众号)
111
+ def self.get_userinfo_by_authcode(code,app_id=nil,app_secret=nil)
112
+ openid,access_token = get_openid_and_access_token(code,app_id,app_secret)
113
+ return get_userinfo_by_auth(access_token,openid)
114
+ end
115
+ def self.get_userinfo_by_auth(access_token,openid)
116
+ url = "https://api.weixin.qq.com/sns/userinfo?access_token=" + access_token.to_s +
117
+ "&openid=" + openid.to_s + "&lang=zh_CN"
118
+ conn = Faraday.new(:url => url)
119
+ result = JSON.parse conn.get.body
120
+ if result["errmsg"]
121
+ logger.error("Utils::Weixin.get_userinfo_by_auth of openid: "+ openid.to_s +
122
+ ",access_token:" + access_token.to_s + ", error:" + result["errmsg"])
123
+ end
124
+ result
125
+ end
126
+
127
+ #获取用户信息(仅对关注者有效)
128
+ def self.get_userinfo(openid,app_id=nil,app_secret=nil)
129
+ app_id = Utils::Weixin.app_id if app_id.nil?
130
+ app_secret = Utils::Weixin.app_secret if app_secret.nil?
131
+ access_token = get_access_token(app_id,app_secret)
132
+ url = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=" + access_token +
133
+ "&openid=" + openid + "&lang=zh_CN"
134
+ conn = Faraday.new(:url => url)
135
+ result = JSON.parse conn.get.body
136
+ if result["errmsg"]
137
+ logger.error("Utils::Weixin.get_userinfo of "+ openid.to_s + " error:" + result["errmsg"])
138
+ end
139
+ result
140
+ end
141
+
142
+ #获取基础access_token
143
+ def self.get_access_token(app_id=nil,app_secret=nil)
144
+ app_id = Utils::Weixin.app_id if app_id.nil?
145
+ app_secret = Utils::Weixin.app_secret if app_secret.nil?
146
+ cache = @@access_token_list[app_id]
147
+
148
+ token = nil
149
+ logger.debug("Utils::Weixin.get_access_token,cache access_token:"+cache.to_s)
150
+ if cache.nil? || cache[:expire_time].nil? ||
151
+ cache[:expire_time] - 600 < Time.now.to_i
152
+ #提前十分钟过期,避免时间不同步导致时间差异
153
+
154
+ url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="+
155
+ app_id.to_s + "&secret=" + app_secret.to_s
156
+ result =JSON.parse(Net::HTTP.get(URI.parse(url)))
157
+ logger.debug("get access_token result :"+result.to_s)
158
+ if result['access_token']
159
+ cache = {} if cache.nil?
160
+ cache[:access_token] = result['access_token']
161
+ cache[:expire_time] = Time.now.to_i + result['expires_in'].to_i
162
+ token = cache[:access_token]
163
+ @@access_token_list[app_id] = cache # 更新缓存
164
+ logger.debug("Utils::Weixin.get_access_token,access_token is update:"+cache[:access_token])
165
+ else
166
+ logger.error('Utils::Weixin.get_access_token,获取access_token出错:' + result['errmsg'].to_s)
167
+ end
168
+ else
169
+ token = cache[:access_token]
170
+ end
171
+ return token
172
+ end
173
+
174
+ def self.get_ticket(type="jsapi",access_token="",app_id=nil,app_secret=nil)
175
+ access_token = get_access_token(app_id,app_secret) if access_token.blank?
176
+ key = access_token.to_s + "_" + type
177
+ cache = @@ticket_list[key]
178
+ ticket = nil
179
+ logger.debug("Utils::Weixin.get_ticket,cache ticket:"+cache.to_s)
180
+ if cache.nil? || cache[:expire_time].nil? ||
181
+ cache[:expire_time] - 600 < Time.now.to_i
182
+ #提前十分钟过期,避免时间不同步导致时间差异
183
+ url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=" + access_token.to_s +
184
+ "&type=" + type.to_s
185
+ result =JSON.parse(Net::HTTP.get(URI.parse(url)))
186
+ logger.debug("get get_ticket result :"+result.to_s)
187
+ if result['ticket']
188
+ cache = {} if cache.nil?
189
+ cache[:ticket] = result['ticket']
190
+ cache[:expire_time] = Time.now.to_i + result['expires_in'].to_i
191
+ ticket = cache[:ticket]
192
+ @@ticket_list[key] = cache # 更新缓存
193
+ logger.debug("Utils::Weixin.get_ticket,ticket is update:" + cache[:ticket].to_s)
194
+ else
195
+ logger.error('Utils::Weixin.get_ticket,获取ticket出错:' + result['errmsg'].to_s)
196
+ end
197
+ else
198
+ ticket = cache[:ticket]
199
+ end
200
+ return ticket
201
+ end
202
+
203
+ #发送客服消息
204
+ def self.send_customer_message(to_openid,message,app_id=nil,app_secret=nil)
205
+ app_id = Utils::Weixin.app_id if app_id.nil?
206
+ app_secret = Utils::Weixin.app_secret if app_secret.nil?
207
+ access_token = get_access_token(app_id,app_secret)
208
+ data = '{"touser":"'+ to_openid +'" , "msgtype": "text", "text": {"content": "' +
209
+ message + '"}}'
210
+ path = '/cgi-bin/message/custom/send?access_token=' + access_token
211
+ result = post_data(data,path)
212
+ if result["errcode"] != 0
213
+ logger.error("Utils::Weixin.send_customer_message to :"+to_openid + " result:" + result["errmsg"])
214
+ end
215
+ end
216
+
217
+ #发送模板消息
218
+ def self.send_template_message(to_openid,template_id,message,url='',top_color='#FF0000',value_color='#173177',
219
+ app_id=nil,app_secret=nil)
220
+ app_id = Utils::Weixin.app_id if app_id.nil?
221
+ app_secret = Utils::Weixin.app_secret if app_secret.nil?
222
+ access_token = get_access_token(app_id,app_secret)
223
+
224
+ data = '{"touser":"'+ to_openid +'","template_id":"' + template_id +'","url":"' + url + '",' +
225
+ '"topcolor":"' + top_color + '","data":{'
226
+ message.each do |m_k,m_v|
227
+ data += '"' + m_k + '":{"value":"' + m_v + '","color":"' + value_color + '"},'
228
+ end
229
+ data.chop! if data.end_with?(',')
230
+ data += '}}'
231
+ logger.debug("send_template_message data:" + data)
232
+ path = '/cgi-bin/message/template/send?access_token=' + access_token
233
+ result = post_data(data,path)
234
+ if result["errcode"] != 0
235
+ logger.error("Utils::Weixin.send_template_message to :"+to_openid + " result:" + result["errmsg"])
236
+ end
237
+ end
238
+
239
+
240
+ #sign_string :appid, :appkey, :noncestr, :package, :timestamp
241
+ def self.api_sign(sign_params = {},sign_type = 'SHA1')
242
+ #logger.debug(sign_params)
243
+ result_string = ''
244
+ sign_params = sign_params.sort
245
+ sign_params.each{|key,value|
246
+ result_string += (key.to_s + '=' + value.to_s + '&')
247
+ }
248
+ result_string = result_string[0, result_string.length - 1]#去掉多余的&号
249
+ logger.debug(result_string)
250
+ sign = Digest::MD5.hexdigest(result_string).upcase if sign_type == 'MD5'
251
+ sign = Digest::SHA1.hexdigest(result_string) if sign_type == 'SHA1'
252
+ sign
253
+ end
254
+ #兼容老接口
255
+ def self.pay_sign(sign_params = {},sign_type = 'SHA1')
256
+ api_sign(sign_params,sign_type)
257
+ end
258
+
259
+ end
260
+ end
@@ -0,0 +1,56 @@
1
+ module Utils
2
+ module WeixinHelper
3
+
4
+ #调用微信接口获取openid,并设置在session中,如果指定scope为snsapi_userinfo,将返回access_token
5
+ def set_session_openid(session_name='openid',app_id=nil,app_secret=nil,scope='snsapi_base')
6
+ #测试 start o6hyyjlRoyQelo6YgWstsRJjSBb8
7
+ unless params[:openid].blank?
8
+ #TODO 校验是从微信传过来的参数才写入session;或者使用加密参数,用于没有网页授权接口权限的公众号
9
+ #开发模式下才采用
10
+ session[session_name] = params[:openid] if Rails.env == 'development'
11
+ end
12
+ #logger.debug("session[:openid]:" + session[:openid] )
13
+ #测试 end
14
+
15
+ #已设置openid且不用获取用户信息,直接返回
16
+ return if !session[session_name].blank? && scope == 'snsapi_base'
17
+
18
+ state = 'oauth2'
19
+ state = Utils::Weixin.oauth2_state if Utils::Weixin.respond_to?('oauth2_state')
20
+ if !params[:state].nil? && params[:state] == state && params[:code] #从授权接口返回
21
+ openid = Utils::Weixin.get_openid(params[:code],app_id,app_secret) if scope == 'snsapi_base'
22
+ openid,access_token = Utils::Weixin.get_openid_and_access_token(params[:code],app_id,
23
+ app_secret) if scope == 'snsapi_userinfo'
24
+ if openid
25
+ session[session_name] = openid
26
+ return access_token if scope == 'snsapi_userinfo'
27
+ else
28
+ render text: "获取微信用户标识openid失败"
29
+ end
30
+ else
31
+ render text: "微信接口返回参数错误(state,code)或用户未授权访问" if !params[:state].nil?
32
+ end
33
+ if params[:state].nil?
34
+ #如果有state参数,表示从接口返回,不需再跳转
35
+ redirect_to Utils::Weixin.get_oauth2_url(request.original_url,scope,app_id)
36
+ end
37
+ end
38
+
39
+ #获取oauth2 access_token,并设置实例变量
40
+ def set_oauth2_access_token(app_id=nil,app_secret=nil)
41
+ state = 'oauth2'
42
+ state = Utils::Weixin.oauth2_state if Utils::Weixin.respond_to?('oauth2_state')
43
+ if !params[:state].nil? && params[:state] == state && params[:code] #从授权接口返回
44
+ @oauth2_access_token = Utils::Weixin.get_oauth2_access_token(params[:code],app_id,app_secret)
45
+ return
46
+ end
47
+ token = Utils::Weixin.get_oauth2_access_token("",app_id,app_secret)
48
+ if token.nil?
49
+ redirect_to Utils::Weixin.get_oauth2_url(request.original_url,'snsapi_base',app_id)
50
+ else
51
+ @oauth2_access_token = token
52
+ end
53
+ end
54
+
55
+ end
56
+ end
@@ -0,0 +1,120 @@
1
+ require 'xmlsimple'
2
+
3
+ module Utils
4
+ class Wxpay
5
+
6
+ #定义logger
7
+ def self.logger
8
+ Rails.logger
9
+ end
10
+
11
+ #提交数据
12
+ def self.post_xml_data(data,path)
13
+ logger.debug("data:"+ data.to_s)
14
+ uri = URI.parse("https://api.mch.weixin.qq.com/")
15
+ http = Net::HTTP.new(uri.host, uri.port)
16
+ http.use_ssl = true
17
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
18
+ request = Net::HTTP::Post.new(path)
19
+ request.add_field('Content-Type', 'application/json')
20
+ request.body = data
21
+ response = http.request(request)
22
+ logger.debug("body:"+ response.body)
23
+ result = XmlSimple.xml_in(response.body)
24
+ logger.debug("result:"+result.to_s)
25
+ return result
26
+ end
27
+
28
+ #微信支付处理(NATIVE)
29
+ def self.native(out_trade_no,total_fee,body,notify_url,openid,ip,return_code=true,app_id=nil,mch_id=nil,pay_sign_key=nil)
30
+ result = Utils::Wxpay.unifiedorder('NATIVE',out_trade_no,total_fee,body,notify_url,openid,ip,app_id,mch_id,pay_sign_key)
31
+ return nil if result.nil?
32
+
33
+ if return_code
34
+ return result["code_url"][0] #模式二返回结果
35
+ else
36
+ package_params = {
37
+ :appId => app_id,
38
+ :timeStamp => Time.now.to_i.to_s,#必须是字符串,否则iPhone下报错
39
+ :nonceStr => Digest::MD5.hexdigest(Time.now.to_s).to_s,
40
+ :package => "prepay_id=" + result["prepay_id"][0],
41
+ :signType => "MD5"
42
+ }
43
+ package_params[:paySign] = Utils::Wxpay.pay_sign(package_params,pay_sign_key)
44
+ return package_params #模式一返回结果
45
+ end
46
+ end
47
+
48
+ #微信支付处理(JSAPI v2)
49
+ def self.jsapi2(out_trade_no,total_fee,body,notify_url,openid,ip,app_id=nil,mch_id=nil,pay_sign_key=nil)
50
+ return Utils::Wxpay.unifiedorder('JSAPI',out_trade_no,total_fee,body,notify_url,openid,ip,app_id,mch_id,pay_sign_key)
51
+ end
52
+
53
+ #微信支付统一调用接口
54
+ def self.unifiedorder(trade_type,out_trade_no,total_fee,body,notify_url,openid,ip,app_id,mch_id,pay_sign_key)
55
+ app_id = Utils::Weixin.app_id if app_id.nil?
56
+ pay_sign_key= Utils::Weixin.pay_sign_key if pay_sign_key.nil?
57
+ mch_id= Utils::Weixin.mch_id if mch_id.nil?
58
+
59
+ #构造支付订单接口参数
60
+ pay_params = {
61
+ :appid => app_id,
62
+ :mch_id => mch_id,
63
+ :body => body,
64
+ :nonce_str => Digest::MD5.hexdigest(Time.now.to_s).to_s,
65
+ :notify_url => notify_url, #'http://szework.com/weixin/pay/notify',#get_notify_url(),
66
+ :out_trade_no => out_trade_no, #out_trade_no, #'2014041311110001'
67
+ :total_fee => total_fee,
68
+ :trade_type => trade_type,
69
+ :openid => openid,
70
+ :spbill_create_ip => ip #TODO 支持反向代理
71
+ }
72
+
73
+ path = "/pay/unifiedorder"
74
+ data = "<xml>"
75
+ pay_params.each do |k,v|
76
+ data += "<" + k.to_s + "><![CDATA[" + v.to_s + "]]></" + k.to_s + ">"
77
+ end
78
+ data += "<sign><![CDATA[" + pay_sign(pay_params,pay_sign_key) + "]]></sign>"
79
+ data += "</xml>"
80
+ result = post_xml_data(data,path)
81
+ if result["return_code"][0] == 'SUCCESS' && result["result_code"][0] == 'SUCCESS'
82
+ #JSAPI调用返回
83
+ if result["trade_type"][0] == 'JSAPI'
84
+ package_params = {
85
+ :appId => app_id,
86
+ :timeStamp => Time.now.to_i.to_s,#必须是字符串,否则iPhone下报错
87
+ :nonceStr => Digest::MD5.hexdigest(Time.now.to_s).to_s,
88
+ :package => "prepay_id=" + result["prepay_id"][0],
89
+ :signType => "MD5"
90
+ }
91
+ package_params[:paySign] = Utils::Wxpay.pay_sign(package_params,pay_sign_key)
92
+ return package_params
93
+ end
94
+
95
+ #NATIVE调用返回
96
+ return result
97
+ else
98
+ #调用失败
99
+ logger.info("weixin_pay.error:" + result["return_msg"].to_s + ";" +
100
+ result["err_code_des"].to_s )
101
+ return nil
102
+ end
103
+ end
104
+
105
+
106
+ #sign_string :appid, :appkey, :noncestr, :package, :timestamp
107
+ def self.pay_sign(sign_params = {},key)
108
+ #logger.debug(sign_params)
109
+ result_string = ''
110
+ sign_params = sign_params.sort
111
+ sign_params.each{|key,value|
112
+ result_string += (key.to_s + '=' + value.to_s + '&') unless value.blank?
113
+ }
114
+ result_string +="key=" + key
115
+ sign = Digest::MD5.hexdigest(result_string).upcase
116
+ sign
117
+ end
118
+
119
+ end
120
+ end
data/lib/utils.rb ADDED
@@ -0,0 +1,61 @@
1
+ require 'utils/common'
2
+ require 'utils/file_util'
3
+ require 'utils/sms'
4
+ require 'utils/weixin'
5
+ require 'utils/weixin_helper'
6
+ require 'utils/wxpay'
7
+ require 'utils/railtie' if defined?(Rails)
8
+
9
+ module Utils
10
+
11
+ end
12
+
13
+ class String
14
+ def to_bool
15
+ return true if self == true || self =~ (/^(true|t|yes|y|1)$/i)
16
+ return false if self == false || self.blank? || self =~ (/^(false|f|no|n|0)$/i)
17
+ raise ArgumentError.new("invalid value for Boolean: \"#{self}\"")
18
+ end
19
+ end
20
+
21
+ class Fixnum
22
+ def to_bool
23
+ return true if self == 1
24
+ return false if self == 0
25
+ raise ArgumentError.new("invalid value for Boolean: \"#{self}\"")
26
+ end
27
+ end
28
+
29
+ class TrueClass
30
+ def to_i; 1; end
31
+ def to_bool; self; end
32
+ end
33
+
34
+ class FalseClass
35
+ def to_i; 0; end
36
+ def to_bool; self; end
37
+ end
38
+
39
+ class NilClass
40
+ def to_bool; false; end
41
+ end
42
+
43
+ module HTTPResponseDecodeContentOverride
44
+ def initialize(h,c,m)
45
+ super(h,c,m)
46
+ @decode_content = true
47
+ end
48
+ def body
49
+ res = super
50
+ if self['content-length']
51
+ self['content-length']= res.bytesize
52
+ end
53
+ res
54
+ end
55
+ end
56
+
57
+ module Net
58
+ class HTTPResponse
59
+ prepend HTTPResponseDecodeContentOverride
60
+ end
61
+ end
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: utils-rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.2.13
5
+ platform: ruby
6
+ authors:
7
+ - wangfuhai
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-12-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: mini_magick
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
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: faraday
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: xml-simple
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
+ description: utils with rails
56
+ email:
57
+ - wangfuhai@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - MIT-LICENSE
63
+ - README.rdoc
64
+ - Rakefile
65
+ - lib/utils.rb
66
+ - lib/utils/common.rb
67
+ - lib/utils/file_util.rb
68
+ - lib/utils/railtie.rb
69
+ - lib/utils/sms.rb
70
+ - lib/utils/view_helper.rb
71
+ - lib/utils/weixin.rb
72
+ - lib/utils/weixin_helper.rb
73
+ - lib/utils/wxpay.rb
74
+ homepage: https://github.com/wangfuhai2013/utils-rails
75
+ licenses:
76
+ - MIT
77
+ metadata: {}
78
+ post_install_message:
79
+ rdoc_options: []
80
+ require_paths:
81
+ - lib
82
+ required_ruby_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ required_rubygems_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ requirements: []
93
+ rubygems_version: 3.3.7
94
+ signing_key:
95
+ specification_version: 4
96
+ summary: utils with rails
97
+ test_files: []