weixin_authorize_905 1.6.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +2 -0
  3. data/.gitignore +19 -0
  4. data/.rspec +2 -0
  5. data/.travis.yml +11 -0
  6. data/Gemfile +5 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +72 -0
  9. data/Rakefile +1 -0
  10. data/lib/weixin_authorize.rb +97 -0
  11. data/lib/weixin_authorize/api.rb +3 -0
  12. data/lib/weixin_authorize/api/custom.rb +176 -0
  13. data/lib/weixin_authorize/api/data_cube.rb +8 -0
  14. data/lib/weixin_authorize/api/groups.rb +60 -0
  15. data/lib/weixin_authorize/api/mass.rb +80 -0
  16. data/lib/weixin_authorize/api/media.rb +149 -0
  17. data/lib/weixin_authorize/api/menu.rb +36 -0
  18. data/lib/weixin_authorize/api/oauth.rb +50 -0
  19. data/lib/weixin_authorize/api/qrcode.rb +62 -0
  20. data/lib/weixin_authorize/api/template.rb +34 -0
  21. data/lib/weixin_authorize/api/user.rb +69 -0
  22. data/lib/weixin_authorize/carrierwave/weixin_uploader.rb +4 -0
  23. data/lib/weixin_authorize/client.rb +95 -0
  24. data/lib/weixin_authorize/config.rb +35 -0
  25. data/lib/weixin_authorize/handler.rb +3 -0
  26. data/lib/weixin_authorize/handler/exceptions.rb +5 -0
  27. data/lib/weixin_authorize/handler/global_code.rb +127 -0
  28. data/lib/weixin_authorize/handler/result_handler.rb +52 -0
  29. data/lib/weixin_authorize/js_ticket/object_store.rb +21 -0
  30. data/lib/weixin_authorize/js_ticket/redis_store.rb +41 -0
  31. data/lib/weixin_authorize/js_ticket/store.rb +40 -0
  32. data/lib/weixin_authorize/token/object_store.rb +25 -0
  33. data/lib/weixin_authorize/token/redis_store.rb +38 -0
  34. data/lib/weixin_authorize/token/store.rb +72 -0
  35. data/lib/weixin_authorize/version.rb +3 -0
  36. data/spec/1_fetch_access_token_spec.rb +43 -0
  37. data/spec/2_fetch_jsticket_spec.rb +10 -0
  38. data/spec/api/custom_spec.rb +71 -0
  39. data/spec/api/groups_spec.rb +74 -0
  40. data/spec/api/mass_spec.rb +70 -0
  41. data/spec/api/media_spec.rb +82 -0
  42. data/spec/api/medias/favicon.ico +0 -0
  43. data/spec/api/medias/ruby-logo.jpg +0 -0
  44. data/spec/api/menu_spec.rb +26 -0
  45. data/spec/api/qrcode_spec.rb +22 -0
  46. data/spec/api/template_spec.rb +40 -0
  47. data/spec/api/user_spec.rb +26 -0
  48. data/spec/spec_helper.rb +130 -0
  49. data/weixin_authorize.gemspec +48 -0
  50. metadata +301 -0
@@ -0,0 +1,4 @@
1
+ module WeixinAuthorize
2
+ class WeixinUploader < CarrierWave::Uploader::Base
3
+ end
4
+ end
@@ -0,0 +1,95 @@
1
+ # encoding: utf-8
2
+ require "monitor"
3
+ require "redis"
4
+ require 'digest/md5'
5
+ module WeixinAuthorize
6
+
7
+ class Client
8
+
9
+ include MonitorMixin
10
+
11
+ include Api::User
12
+ include Api::Menu
13
+ include Api::Custom
14
+ include Api::Groups
15
+ include Api::Qrcode
16
+ include Api::Media
17
+ include Api::Mass
18
+ include Api::Oauth
19
+ include Api::Template
20
+
21
+ attr_accessor :app_id, :app_secret, :expired_at # Time.now + expires_in
22
+ attr_accessor :access_token, :redis_key, :custom_access_token
23
+ attr_accessor :jsticket, :jsticket_expired_at, :jsticket_redis_key
24
+
25
+ # options: redis_key, custom_access_token
26
+ def initialize(app_id, app_secret, options={})
27
+ @app_id = app_id
28
+ @app_secret = app_secret
29
+ @jsticket_expired_at = @expired_at = Time.now.to_i
30
+ @redis_key = security_redis_key(options[:redis_key] || "weixin_#{app_id}")
31
+ @jsticket_redis_key = security_redis_key("js_sdk_#{app_id}")
32
+ @custom_access_token = options[:custom_access_token]
33
+ super() # Monitor#initialize
34
+ end
35
+
36
+ # return token
37
+ def get_access_token
38
+ return custom_access_token if !custom_access_token.nil?
39
+ synchronize{ token_store.access_token }
40
+ end
41
+
42
+ # 检查appid和app_secret是否有效。
43
+ def is_valid?
44
+ return true if !custom_access_token.nil?
45
+ token_store.valid?
46
+ end
47
+
48
+ def token_store
49
+ Token::Store.init_with(self)
50
+ end
51
+
52
+ def jsticket_store
53
+ JsTicket::Store.init_with(self)
54
+ end
55
+
56
+ def get_jsticket
57
+ jsticket_store.jsticket
58
+ end
59
+
60
+ # 获取js sdk 签名包
61
+ def get_jssign_package(url)
62
+ timestamp = Time.now.to_i
63
+ noncestr = SecureRandom.hex(16)
64
+ str = "jsapi_ticket=#{get_jsticket}&noncestr=#{noncestr}&timestamp=#{timestamp}&url=#{url}";
65
+ signature = Digest::SHA1.hexdigest(str)
66
+ {
67
+ "appId" => app_id, "nonceStr" => noncestr,
68
+ "timestamp" => timestamp, "url" => url,
69
+ "signature" => signature, "rawString" => str
70
+ }
71
+ end
72
+
73
+ # 暴露出:http_get,http_post两个方法,方便第三方开发者扩展未开发的微信API。
74
+ def http_get(url, url_params={}, endpoint="plain")
75
+ url_params = url_params.merge(access_token_param)
76
+ WeixinAuthorize.http_get_without_token(url, url_params, endpoint)
77
+ end
78
+
79
+ def http_post(url, post_body={}, url_params={}, endpoint="plain")
80
+ url_params = access_token_param.merge(url_params)
81
+ WeixinAuthorize.http_post_without_token(url, post_body, url_params, endpoint)
82
+ end
83
+
84
+ private
85
+
86
+ def access_token_param
87
+ {access_token: get_access_token}
88
+ end
89
+
90
+ def security_redis_key(key)
91
+ Digest::MD5.hexdigest(key.to_s).upcase
92
+ end
93
+
94
+ end
95
+ end
@@ -0,0 +1,35 @@
1
+ module WeixinAuthorize
2
+
3
+ class << self
4
+
5
+ attr_accessor :config
6
+
7
+ def configure
8
+ yield self.config ||= Config.new
9
+ end
10
+
11
+ def weixin_redis
12
+ return nil if config.nil?
13
+ @redis ||= config.redis
14
+ end
15
+
16
+ def key_expired
17
+ config.key_expired || 100
18
+ end
19
+
20
+ # 可选配置: RestClient timeout, etc.
21
+ # key 必须是符号
22
+ # 如果出现 RestClient::SSLCertificateNotVerified Exception: SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed
23
+ # 这个错,除了改 verify_ssl: true,请参考:http://www.extendi.it/blog/2015/5/23/47-sslv3-read-server-certificate-b-certificate-verify-failed
24
+ def rest_client_options
25
+ if config.nil?
26
+ return {timeout: 5, open_timeout: 5, verify_ssl: true}
27
+ end
28
+ config.rest_client_options
29
+ end
30
+ end
31
+
32
+ class Config
33
+ attr_accessor :redis, :rest_client_options, :key_expired
34
+ end
35
+ end
@@ -0,0 +1,3 @@
1
+ require "weixin_authorize/handler/global_code"
2
+ require "weixin_authorize/handler/result_handler"
3
+ require "weixin_authorize/handler/exceptions"
@@ -0,0 +1,5 @@
1
+ # encoding: utf-8
2
+ module WeixinAuthorize
3
+ class ValidAccessTokenException < RuntimeError;end
4
+ class MediaTypeException < RuntimeError;end
5
+ end
@@ -0,0 +1,127 @@
1
+ # encoding: utf-8
2
+ module WeixinAuthorize
3
+
4
+ GLOBAL_CODES = {
5
+ -1 => "系统繁忙",
6
+ 0 => "请求成功",
7
+ 40001 => "获取access_token时AppSecret错误,或者access_token无效",
8
+ 40002 => "不合法的凭证类型",
9
+ 40003 => "不合法的OpenID",
10
+ 40004 => "不合法的媒体文件类型",
11
+ 40005 => "不合法的文件类型",
12
+ 40006 => "不合法的文件大小",
13
+ 40007 => "不合法的媒体文件id",
14
+ 40008 => "不合法的消息类型",
15
+ 40009 => "不合法的图片文件大小",
16
+ 40010 => "不合法的语音文件大小",
17
+ 40011 => "不合法的视频文件大小",
18
+ 40012 => "不合法的缩略图文件大小",
19
+ 40013 => "不合法的APPID",
20
+ 40014 => "不合法的access_token",
21
+ 40015 => "不合法的菜单类型",
22
+ 40016 => "不合法的按钮个数",
23
+ 40017 => "不合法的按钮个数",
24
+ 40018 => "不合法的按钮名字长度",
25
+ 40019 => "不合法的按钮KEY长度",
26
+ 40020 => "不合法的按钮URL长度",
27
+ 40021 => "不合法的菜单版本号",
28
+ 40022 => "不合法的子菜单级数",
29
+ 40023 => "不合法的子菜单按钮个数",
30
+ 40024 => "不合法的子菜单按钮类型",
31
+ 40025 => "不合法的子菜单按钮名字长度",
32
+ 40026 => "不合法的子菜单按钮KEY长度",
33
+ 40027 => "不合法的子菜单按钮URL长度",
34
+ 40028 => "不合法的自定义菜单使用用户",
35
+ 40029 => "不合法的oauth_code",
36
+ 40030 => "不合法的refresh_token",
37
+ 40031 => "不合法的openid列表",
38
+ 40032 => "不合法的openid列表长度",
39
+ 40033 => "不合法的请求字符,不能包含xxxx格式的字符",
40
+ 40035 => "不合法的参数",
41
+ 40038 => "不合法的请求格式",
42
+ 40039 => "不合法的URL长度",
43
+ 40050 => "不合法的分组id",
44
+ 40051 => "分组名字不合法",
45
+ 41001 => "缺少access_token参数",
46
+ 41002 => "缺少appid参数",
47
+ 41003 => "缺少refresh_token参数",
48
+ 41004 => "缺少secret参数",
49
+ 41005 => "缺少多媒体文件数据",
50
+ 41006 => "缺少media_id参数",
51
+ 41007 => "缺少子菜单数据",
52
+ 41008 => "缺少oauth code",
53
+ 41009 => "缺少openid",
54
+ 42001 => "access_token超时",
55
+ 42002 => "refresh_token超时",
56
+ 42003 => "oauth_code超时",
57
+ 43001 => "需要GET请求",
58
+ 43002 => "需要POST请求",
59
+ 43003 => "需要HTTPS请求",
60
+ 43004 => "需要接收者关注",
61
+ 43005 => "需要好友关系",
62
+ 44001 => "多媒体文件为空",
63
+ 44002 => "POST的数据包为空",
64
+ 44003 => "图文消息内容为空",
65
+ 44004 => "文本消息内容为空",
66
+ 45001 => "多媒体文件大小超过限制",
67
+ 45002 => "消息内容超过限制",
68
+ 45003 => "标题字段超过限制",
69
+ 45004 => "描述字段超过限制",
70
+ 45005 => "链接字段超过限制",
71
+ 45006 => "图片链接字段超过限制",
72
+ 45007 => "语音播放时间超过限制",
73
+ 45008 => "图文消息超过限制",
74
+ 45009 => "接口调用超过限制",
75
+ 45010 => "创建菜单个数超过限制",
76
+ 45015 => "回复时间超过限制",
77
+ 45016 => "系统分组,不允许修改",
78
+ 45017 => "分组名字过长",
79
+ 45018 => "分组数量超过上限",
80
+ 46001 => "不存在媒体数据",
81
+ 46002 => "不存在的菜单版本",
82
+ 46003 => "不存在的菜单数据",
83
+ 46004 => "不存在的用户",
84
+ 47001 => "解析JSON/XML内容错误",
85
+ 48001 => "api功能未授权",
86
+ 50001 => "用户未授权该api",
87
+ 50002 => "用户受限,可能是违规后接口被封禁",
88
+ 61451 => "参数错误(invalid parameter)",
89
+ 61452 => "无效客服账号(invalid kf_account)",
90
+ 61453 => "客服帐号已存在(kf_account exsited)",
91
+ 61454 => "客服帐号名长度超过限制(仅允许10个英文字符,不包括@及@后的公众号的微信号)(invalid kf_acount length)",
92
+ 61455 => "客服帐号名包含非法字符(仅允许英文+数字)(illegal character in kf_account)",
93
+ 61456 => "客服帐号个数超过限制(10个客服账号)(kf_account count exceeded)",
94
+ 61457 => "无效头像文件类型(invalid file type)",
95
+ 61450 => "系统错误(system error)",
96
+ 61500 => "日期格式错误",
97
+ 61501 => "日期范围错误",
98
+ 9001001 => "POST数据参数不合法",
99
+ 9001002 => "远端服务不可用",
100
+ 9001003 => "Ticket不合法",
101
+ 9001004 => "获取摇周边用户信息失败",
102
+ 9001005 => "获取商户信息失败",
103
+ 9001006 => "获取OpenID失败",
104
+ 9001007 => "上传文件缺失",
105
+ 9001008 => "上传素材的文件类型不合法",
106
+ 9001009 => "上传素材的文件尺寸不合法",
107
+ 9001010 => "上传失败",
108
+ 9001020 => "帐号不合法",
109
+ 9001021 => "已有设备激活率低于50%,不能新增设备",
110
+ 9001022 => "设备申请数不合法,必须为大于0的数字",
111
+ 9001023 => "已存在审核中的设备ID申请",
112
+ 9001024 => "一次查询设备ID数量不能超过50",
113
+ 9001025 => "设备ID不合法",
114
+ 9001026 => "页面ID不合法",
115
+ 9001027 => "页面参数不合法",
116
+ 9001028 => "一次删除页面ID数量不能超过10",
117
+ 9001029 => "页面已应用在设备中,请先解除应用关系再删除",
118
+ 9001030 => "一次查询页面ID数量不能超过50",
119
+ 9001031 => "时间区间不合法",
120
+ 9001032 => "保存设备与页面的绑定关系参数错误",
121
+ 9001033 => "门店ID不合法",
122
+ 9001034 => "设备备注信息过长",
123
+ 9001035 => "设备申请参数不合法",
124
+ 9001036 => "查询起始值begin不合法"
125
+ }unless defined?(GLOBAL_CODES)
126
+
127
+ end
@@ -0,0 +1,52 @@
1
+ # encoding: utf-8
2
+ module WeixinAuthorize
3
+
4
+ class ResultHandler
5
+
6
+ attr_accessor :code, :cn_msg, :en_msg, :result
7
+
8
+ def initialize(code, en_msg, result={})
9
+ @code = code || OK_CODE
10
+ @en_msg = en_msg || OK_MSG
11
+ @cn_msg = GLOBAL_CODES[@code.to_i]
12
+ @result = package_result(result)
13
+ end
14
+
15
+ # This method is to valid the current request if is true or is false
16
+ def is_ok?
17
+ code == OK_CODE
18
+ end
19
+ alias_method :ok?, :is_ok?
20
+
21
+ # e.g.:
22
+ # 45009: api freq out of limit(接口调用超过限制)
23
+ def full_message
24
+ "#{code}: #{en_msg}(#{cn_msg})."
25
+ end
26
+ alias_method :full_messages, :full_message
27
+
28
+ def full_error_message
29
+ full_message if !is_ok?
30
+ end
31
+ alias_method :full_error_messages, :full_error_message
32
+ alias_method :errors, :full_error_message
33
+
34
+ private
35
+
36
+ # if define Rails constant
37
+ # result = WeixinAuthorize::ResultHandler.new("0", "success", {:ok => "true"})
38
+ # result.result["ok"] #=> true
39
+ # result.result[:ok] #=> true
40
+ # result.result['ok'] #=> true
41
+ def package_result(result)
42
+ return result if !result.is_a?(Hash)
43
+ if defined?(Rails)
44
+ ActiveSupport::HashWithIndifferentAccess.new(result)
45
+ else
46
+ result
47
+ end
48
+ end
49
+
50
+ end
51
+
52
+ end
@@ -0,0 +1,21 @@
1
+ module WeixinAuthorize
2
+ module JsTicket
3
+ class ObjectStore < Store
4
+
5
+ def jsticket_expired?
6
+ # 如果当前token过期时间小于现在的时间,则重新获取一次
7
+ client.jsticket_expired_at <= Time.now.to_i
8
+ end
9
+
10
+ def jsticket
11
+ super
12
+ client.jsticket
13
+ end
14
+
15
+ def refresh_jsticket
16
+ super
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,41 @@
1
+ module WeixinAuthorize
2
+ module JsTicket
3
+ class RedisStore < Store
4
+ JSTICKET = "jsticket"
5
+ EXPIRED_AT = "expired_at"
6
+
7
+ def jsticket_expired?
8
+ weixin_redis.hvals(client.jsticket_redis_key).empty?
9
+ end
10
+
11
+ def refresh_jsticket
12
+ super
13
+ weixin_redis.hmset(
14
+ client.jsticket_redis_key,
15
+ JSTICKET,
16
+ client.jsticket,
17
+ EXPIRED_AT,
18
+ client.jsticket_expired_at
19
+ )
20
+ weixin_redis.expireat(
21
+ client.jsticket_redis_key,
22
+ client.jsticket_expired_at.to_i
23
+ )
24
+ end
25
+
26
+ def jsticket
27
+ super
28
+ client.jsticket = weixin_redis.hget(client.jsticket_redis_key, JSTICKET)
29
+ client.jsticket_expired_at = weixin_redis.hget(
30
+ client.jsticket_redis_key,
31
+ EXPIRED_AT
32
+ )
33
+ client.jsticket
34
+ end
35
+
36
+ def weixin_redis
37
+ WeixinAuthorize.weixin_redis
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,40 @@
1
+ # encoding: utf-8
2
+ module WeixinAuthorize
3
+ module JsTicket
4
+ class Store
5
+
6
+ attr_accessor :client
7
+
8
+ def initialize(client)
9
+ @client = client
10
+ end
11
+
12
+ def self.init_with(client)
13
+ if WeixinAuthorize.weixin_redis.nil?
14
+ ObjectStore.new(client)
15
+ else
16
+ RedisStore.new(client)
17
+ end
18
+ end
19
+
20
+ def jsticket_expired?
21
+ raise NotImplementedError, "Subclasses must implement a jsticket_expired? method"
22
+ end
23
+
24
+ def refresh_jsticket
25
+ set_jsticket
26
+ end
27
+
28
+ def jsticket
29
+ refresh_jsticket if jsticket_expired?
30
+ end
31
+
32
+ def set_jsticket
33
+ result = client.http_get("/ticket/getticket", {type: 1}).result
34
+ client.jsticket = result["ticket"]
35
+ client.jsticket_expired_at = WeixinAuthorize.calculate_expire(result["expires_in"])
36
+ end
37
+
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,25 @@
1
+ # encoding: utf-8
2
+ module WeixinAuthorize
3
+ module Token
4
+ class ObjectStore < Store
5
+
6
+ def valid?
7
+ super
8
+ end
9
+
10
+ def token_expired?
11
+ # 如果当前token过期时间小于现在的时间,则重新获取一次
12
+ client.expired_at <= Time.now.to_i
13
+ end
14
+
15
+ def refresh_token
16
+ super
17
+ end
18
+
19
+ def access_token
20
+ super
21
+ client.access_token
22
+ end
23
+ end
24
+ end
25
+ end