Commit 473e0fdcba03f8734063d7aac47d3f470bde7443

Authored by lanrion
2 parents 7494c737 7ebb18e5
Exists in master

Merge pull request #5 from lanrion/develop

#2 添加access_token 存储方案
README.md
1   -开发中...
2   -
3   -部门、成员、标签、自定义菜单、Oauth2 接口均可以在开发环境调试
4   -
5 1 **企业号对应多个管理组,请前往 `设置` => `权限管理` 任意创建一个管理组,在管理组最下角即可获取 CorpID Secret**
6 2  
7 3 **有问题请及时提issue**
... ... @@ -10,9 +6,32 @@
10 6 gem "qy_wechat_api", git: "https://github.com/lanrion/qy_wechat_api.git"
11 7 ```
12 8  
13   -**暂未对access_token做缓存处理,为了确保在开发过程不会出现token过期问题,请不要使用全局变量存储group_client。**
  9 +# token 存储方案
  10 +
  11 +## 对象存储
  12 +如果你是单个企业号,建议使用这个方案,无需任何配置即可使用。
  13 +
  14 +## Redis 存储
  15 +```ruby
  16 +redis = Redis.new(host: "127.0.0.1", port: "6379")
  17 +
  18 +namespace = "qy_wechat_api:redis_storage"
  19 +
  20 +# cleanup keys in the current namespace when restart server everytime.
  21 +exist_keys = redis.keys("#{namespace}:*")
  22 +exist_keys.each{|key|redis.del(key)}
  23 +
  24 +redis_with_ns = Redis::Namespace.new("#{namespace}", redis: redis)
  25 +
  26 +QyWechatApi.configure do |config|
  27 + config.redis = redis_with_ns
  28 +end
  29 +```
  30 +
  31 +## 自定义存储方案
  32 +TODO...
14 33  
15   -# 基本用法
  34 +# API基本用法
16 35  
17 36 请务必结合:http://qydev.weixin.qq.com/wiki/index.php 理解以下API参数使用。
18 37  
... ... @@ -20,6 +39,8 @@ gem "qy_wechat_api", git: "https://github.com/lanrion/qy_wechat_api.git"
20 39  
21 40 ```ruby
22 41 group_client = QyWechatApi::Client.new(corpid, corpsecret)
  42 +# 为了确保用户输入的corpid, corpsecret是准确的,请务必执行:
  43 +group_client.is_valid?
23 44 ```
24 45  
25 46 ## 部门
... ... @@ -97,4 +118,3 @@ group_client.media.upload(image_jpg_file, "image")
97 118 group_client.media.get_media_by_id(media_id)
98 119 ```
99 120  
100   -
... ...
lib/qy_wechat_api.rb
1 1 # encoding: utf-8
2 2  
3 3 require "rest-client"
4   -
5 4 require "carrierwave"
6   -require "qy_wechat_api/carrierwave/qy_wechat_api_uploader"
7   -
8 5 require 'yajl/json_gem'
9 6  
  7 +require "qy_wechat_api/carrierwave/qy_wechat_api_uploader"
  8 +require "qy_wechat_api/config"
10 9 require "qy_wechat_api/client"
11 10 require "qy_wechat_api/handler"
12 11 require "qy_wechat_api/api"
13 12  
14 13 module QyWechatApi
  14 +
  15 + # Storage
  16 + autoload(:Storage, "qy_wechat_api/storage/storage")
  17 + autoload(:ObjectStorage, "qy_wechat_api/storage/object_storage")
  18 + autoload(:RedisStorage, "qy_wechat_api/storage/redis_storage")
  19 +
15 20 ENDPOINT_URL = "https://qyapi.weixin.qq.com/cgi-bin"
16 21 OK_MSG = "ok".freeze
17 22 OK_CODE = 0.freeze
... ...
lib/qy_wechat_api/client.rb
... ... @@ -3,11 +3,23 @@
3 3 module QyWechatApi
4 4 class Client
5 5 attr_accessor :corp_id, :group_secret, :expired_at # Time.now + expires_in
6   - attr_accessor :access_token
  6 + attr_accessor :access_token, :redis_key, :storage
7 7  
8 8 def initialize(corp_id, group_secret, redis_key=nil)
9   - @corp_id = corp_id
  9 + @corp_id = corp_id
10 10 @group_secret = group_secret
  11 + @redis_key = security_redis_key((redis_key || "qy_" + group_secret))
  12 + @storage = Storage.init_with(self)
  13 + end
  14 +
  15 + # return token
  16 + def get_access_token
  17 + @storage.access_token
  18 + end
  19 +
  20 + # 检查appid和app_secret是否有效。
  21 + def is_valid?
  22 + @storage.valid?
11 23 end
12 24  
13 25 # 管理部门API
... ... @@ -43,14 +55,9 @@ module QyWechatApi
43 55 end
44 56  
45 57 private
46   - def get_access_token
47   - self.access_token ||= get_token.result["access_token"]
48   - end
49 58  
50   - # 获取token
51   - def get_token
52   - params = {corpid: corp_id, corpsecret: group_secret}
53   - QyWechatApi.http_get_without_token("/gettoken", params)
  59 + def security_redis_key(key)
  60 + Digest::MD5.hexdigest(key.to_s).upcase
54 61 end
55 62  
56 63 end
... ...
lib/qy_wechat_api/config.rb 0 → 100644
... ... @@ -0,0 +1,20 @@
  1 +module QyWechatApi
  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 QyWechatApi.config.nil?
  13 + @redis ||= QyWechatApi.config.redis
  14 + end
  15 + end
  16 +
  17 + class Config
  18 + attr_accessor :redis
  19 + end
  20 +end
... ...
lib/qy_wechat_api/handler.rb
  1 +require "qy_wechat_api/handler/errors"
1 2 require "qy_wechat_api/handler/global_code"
2 3 require "qy_wechat_api/handler/result_handler"
... ...
lib/qy_wechat_api/handler/errors.rb 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +module QyWechatApi
  2 + module Errors
  3 + class ValidAccessTokenException < RuntimeError;end
  4 + end
  5 +end
... ...
lib/qy_wechat_api/storage/object_storage.rb 0 → 100644
... ... @@ -0,0 +1,23 @@
  1 +# encoding: utf-8
  2 +module QyWechatApi
  3 + class ObjectStorage < Storage
  4 + def valid?
  5 + super
  6 + end
  7 +
  8 + def token_expired?
  9 + # 如果当前token过期时间小于现在的时间,则重新获取一次
  10 + client.expired_at <= Time.now.to_i
  11 + end
  12 +
  13 + def refresh_token
  14 + super
  15 + end
  16 +
  17 + def access_token
  18 + super
  19 + client.access_token
  20 + end
  21 + end
  22 +
  23 +end
... ...
lib/qy_wechat_api/storage/redis_storage.rb 0 → 100644
... ... @@ -0,0 +1,28 @@
  1 +# encoding: utf-8
  2 +module QyWechatApi
  3 + class RedisStorage < Storage
  4 + def valid?
  5 + weixin_redis.del(client.redis_key)
  6 + super
  7 + end
  8 +
  9 + def token_expired?
  10 + weixin_redis.hvals(client.redis_key).empty?
  11 + end
  12 +
  13 + def refresh_token
  14 + super
  15 + weixin_redis.hmset(client.redis_key, "access_token", client.access_token,
  16 + "expired_at", client.expired_at)
  17 + weixin_redis.expireat(client.redis_key, client.expired_at.to_i-10) # 提前10秒超时
  18 + end
  19 +
  20 + def access_token
  21 + super
  22 + client.access_token = weixin_redis.hget(client.redis_key, "access_token")
  23 + client.expired_at = weixin_redis.hget(client.redis_key, "expired_at")
  24 + client.access_token
  25 + end
  26 + end
  27 +
  28 +end
... ...
lib/qy_wechat_api/storage/storage.rb 0 → 100644
... ... @@ -0,0 +1,75 @@
  1 +# encoding: utf-8
  2 +
  3 +module QyWechatApi
  4 + class Storage
  5 +
  6 + attr_accessor :client
  7 +
  8 + def initialize(client)
  9 + @client = client
  10 + end
  11 +
  12 + def self.init_with(client)
  13 + if QyWechatApi.weixin_redis.nil?
  14 + ObjectStorage.new(client)
  15 + else
  16 + RedisStorage.new(client)
  17 + end
  18 + end
  19 +
  20 + def valid?
  21 + authenticate["valid"]
  22 + end
  23 +
  24 + def authenticate
  25 + auth_result = http_get_access_token
  26 + auth = false
  27 + if auth_result.is_ok?
  28 + set_access_token_for_client(auth_result.result)
  29 + auth = true
  30 + end
  31 + {"valid" => auth, "handler" => auth_result}
  32 + end
  33 +
  34 + def refresh_token
  35 + handle_valid_exception
  36 + set_access_token_for_client
  37 + end
  38 +
  39 + def access_token
  40 + refresh_token if token_expired?
  41 + end
  42 +
  43 + def token_expired?
  44 + raise NotImplementedError, "Subclasses must implement a token_expired? method"
  45 + end
  46 +
  47 + def set_access_token_for_client(access_token_infos=nil)
  48 + token_infos = access_token_infos || http_get_access_token.result
  49 + client.access_token = token_infos["access_token"]
  50 + client.expired_at = Time.now.to_i + token_infos["expires_in"].to_i
  51 + end
  52 +
  53 + def http_get_access_token
  54 + QyWechatApi.http_get_without_token("/gettoken", authenticate_headers)
  55 + end
  56 +
  57 + def authenticate_headers
  58 + {corpid: client.corp_id, corpsecret: client.group_secret}
  59 + end
  60 +
  61 + private
  62 +
  63 + def handle_valid_exception
  64 + auth_result = authenticate
  65 + if !auth_result["valid"]
  66 + result_handler = auth_result["handler"]
  67 + raise Errors::ValidAccessTokenException, result_handler.full_error_message
  68 + end
  69 + end
  70 +
  71 + def weixin_redis
  72 + QyWechatApi.weixin_redis
  73 + end
  74 + end
  75 +end
... ...
spec/spec_helper.rb
... ... @@ -14,13 +14,30 @@
14 14 # users commonly want.
15 15 #
16 16 # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
  17 +require "redis-namespace"
  18 +require "redis"
  19 +
17 20 require "qy_wechat_api"
18 21 require "pry-rails"
19   -
20 22 corpid = "wxb9ce1d023fe6eb69"
21 23 corpsecret = "UOofFIah4PVLmkG8xMH3lpDxj6NTnQSKMrFt-HubiPB4kjB09EmTVcUjgNeermps"
22 24  
23 25  
  26 +# Comment to test for ClientStorage
  27 +redis = Redis.new(:host => "127.0.0.1",:port => "6379")
  28 +
  29 +namespace = "qy_wechat_api:redis_storage"
  30 +
  31 +# cleanup keys in the current namespace when restart server everytime.
  32 +exist_keys = redis.keys("#{namespace}:*")
  33 +exist_keys.each{|key|redis.del(key)}
  34 +
  35 +redis_with_ns = Redis::Namespace.new("#{namespace}", :redis => redis)
  36 +
  37 +QyWechatApi.configure do |config|
  38 + config.redis = redis_with_ns
  39 +end
  40 +
24 41 $client = QyWechatApi::Client.new(corpid, corpsecret)
25 42  
26 43 RSpec.configure do |config|
... ...