Commit 5eafcfbc by lanrion

Merge branch 'master' into broadcast-apis

parents 2dfb2050 c051e036
--format documentation --color spec --drb --format documentation --color spec --drb
--require spec_helper
source 'https://rubygems.org' source 'https://rubygems.org'
group :test, :development do group :test, :development do
gem "rspec", "~> 3.0.0.beta1" gem "rspec"
gem 'redis-namespace', '~> 1.5.1' gem 'redis-namespace'
gem "rake", "~> 0.9.6"
gem 'simplecov', '~> 0.7.1', :require => false gem 'simplecov', '~> 0.7.1', :require => false
gem "codeclimate-test-reporter", require: nil gem "codeclimate-test-reporter", require: nil
gem 'coveralls', require: false gem 'coveralls', require: false
# For debugger # For debugger
gem "pry-rails", "~> 0.3.2" gem "pry-rails"
gem "pry-debugger", "~> 0.2.2" gem "pry-byebug"
end end
# Specify your gem's dependencies in weixin_authorize.gemspec # Specify your gem's dependencies in weixin_authorize.gemspec
......
...@@ -11,6 +11,14 @@ Support using [Redis](http://redis.io) to store `access_token` ...@@ -11,6 +11,14 @@ Support using [Redis](http://redis.io) to store `access_token`
[Getting-Started](https://github.com/lanrion/weixin_authorize/wiki/Getting-Started) [Getting-Started](https://github.com/lanrion/weixin_authorize/wiki/Getting-Started)
## V2.0开发中:
https://github.com/lanrion/weixin_authorize/milestones/v2.0-dev
1. 重构API实现,调用方式
2. 对token,ticket的管理,提供第三方开发灵活者自助化
3. 尝试RestClient的弃用,选择更高效的HTTP client包
4. 支持更多的异常处理机制
注意:查看Wiki或者源代码时,请切换对应的版本来查看。Master处于不断更新完善分支。 注意:查看Wiki或者源代码时,请切换对应的版本来查看。Master处于不断更新完善分支。
## How to test ## How to test
......
require "rest-client" require "rest-client"
require "carrierwave" require "carrierwave"
require "weixin_authorize/carrierwave/weixin_uploader"
require 'yajl/json_gem' require 'yajl/json_gem'
require "weixin_authorize/carrierwave/weixin_uploader"
require "weixin_authorize/config" require "weixin_authorize/config"
require "weixin_authorize/handler" require "weixin_authorize/handler"
require "weixin_authorize/api" require "weixin_authorize/api"
...@@ -12,26 +10,38 @@ require "weixin_authorize/client" ...@@ -12,26 +10,38 @@ require "weixin_authorize/client"
module WeixinAuthorize module WeixinAuthorize
# Storage # token store
autoload(:Storage, "weixin_authorize/adapter/storage") module Token
autoload(:ClientStorage, "weixin_authorize/adapter/client_storage") autoload(:Store, "weixin_authorize/token/store")
autoload(:RedisStorage, "weixin_authorize/adapter/redis_storage") autoload(:ObjectStore, "weixin_authorize/token/object_store")
autoload(:RedisStore, "weixin_authorize/token/redis_store")
end
OK_MSG = "ok".freeze module JsTicket
OK_CODE = 0.freeze autoload(:Store, "weixin_authorize/js_ticket/store")
GRANT_TYPE = "client_credential".freeze autoload(:ObjectStore, "weixin_authorize/js_ticket/object_store")
autoload(:RedisStore, "weixin_authorize/js_ticket/redis_store")
end
OK_MSG = "ok"
OK_CODE = 0
GRANT_TYPE = "client_credential"
class << self class << self
def http_get_without_token(url, headers={}, endpoint="plain") def http_get_without_token(url, headers={}, endpoint="plain")
get_api_url = endpoint_url(endpoint, url) get_api_url = endpoint_url(endpoint, url)
load_json(RestClient.get(get_api_url, :params => headers)) load_json(resource(get_api_url).get(params: headers))
end end
def http_post_without_token(url, payload={}, headers={}, endpoint="plain") def http_post_without_token(url, payload={}, headers={}, endpoint="plain")
post_api_url = endpoint_url(endpoint, url) post_api_url = endpoint_url(endpoint, url)
payload = JSON.dump(payload) if endpoint == "plain" # to json if invoke "plain" payload = JSON.dump(payload) if endpoint == "plain" # to json if invoke "plain"
load_json(RestClient.post(post_api_url, payload, :params => headers)) load_json(resource(post_api_url).post(payload, params: headers))
end
def resource(url)
RestClient::Resource.new(url, rest_client_options)
end end
# return hash # return hash
......
...@@ -6,3 +6,4 @@ require "weixin_authorize/api/qrcode" ...@@ -6,3 +6,4 @@ require "weixin_authorize/api/qrcode"
require "weixin_authorize/api/media" require "weixin_authorize/api/media"
require "weixin_authorize/api/mass" require "weixin_authorize/api/mass"
require "weixin_authorize/api/oauth" require "weixin_authorize/api/oauth"
require "weixin_authorize/api/template"
# encoding: utf-8
module WeixinAuthorize
module Api
module DataCube
end
end
end
...@@ -33,9 +33,6 @@ module WeixinAuthorize ...@@ -33,9 +33,6 @@ module WeixinAuthorize
WeixinAuthorize.http_get_without_token("/sns/userinfo?access_token=#{oauth_token}&openid=#{openid}&lang=#{lang}", {}, "api") WeixinAuthorize.http_get_without_token("/sns/userinfo?access_token=#{oauth_token}&openid=#{openid}&lang=#{lang}", {}, "api")
end end
private
end end
end end
end end
# encoding: utf-8
module WeixinAuthorize
module Api
module Template
# 设置所属行业
# 需要选择公众账号服务所处的2个行业,每月可更改1次所选行业;
# 初始化行业时,传入两个,每月更改时,传入一个即可。
def set_template_industry(industry_id1, industry_id2="")
industries = {industry_id1: industry_id1}
if industry_id2 != ""
industries.merge!({industry_id2: industry_id2})
end
http_post("/template/api_set_industry", industries)
end
# 获得模板ID
# code: 模板库中模板的编号,有“TM**”和“OPENTMTM**”等形式
def add_template(code)
http_post("/template/api_add_template", template_id_short: code)
end
# 发送模板消息
def send_template_msg(touser, template_id, url, topcolor, data)
msg = {
touser: touser, template_id: template_id,
url: url, topcolor: topcolor, data: data
}
http_post("/message/template/send", msg)
end
end
end
end
...@@ -18,6 +18,24 @@ module WeixinAuthorize ...@@ -18,6 +18,24 @@ module WeixinAuthorize
http_get(followers_url, {next_openid: next_openid}) http_get(followers_url, {next_openid: next_openid})
end end
# 设置备注名
# http请求方式: POST(请使用https协议)
# https://api.weixin.qq.com/cgi-bin/user/info/updateremark?access_token=ACCESS_TOKEN
# POST数据格式:JSON
# POST数据例子:
# {
# "openid":"oDF3iY9ffA-hqb2vVvbr7qxf6A0Q",
# "remark":"pangzi"
# }
def update_remark(openid, remark)
update_url = "/user/info/updateremark"
payload = {
openid: openid,
remark: remark
}
http_post(update_url, payload)
end
private private
def user_base_url def user_base_url
......
...@@ -12,35 +12,56 @@ module WeixinAuthorize ...@@ -12,35 +12,56 @@ module WeixinAuthorize
include Api::Media include Api::Media
include Api::Mass include Api::Mass
include Api::Oauth include Api::Oauth
include Api::Template
attr_accessor :app_id, :app_secret, :expired_at # Time.now + expires_in attr_accessor :app_id, :app_secret, :expired_at # Time.now + expires_in
attr_accessor :access_token, :redis_key attr_accessor :access_token, :redis_key
attr_accessor :storage attr_accessor :jsticket, :jsticket_expired_at, :jsticket_redis_key
def initialize(app_id, app_secret, redis_key=nil) def initialize(app_id, app_secret, redis_key=nil)
@app_id = app_id @app_id = app_id
@app_secret = app_secret @app_secret = app_secret
@expired_at = Time.now.to_i @jsticket_expired_at = @expired_at = Time.now.to_i
@redis_key = security_redis_key((redis_key || "weixin_" + app_id)) @redis_key = security_redis_key(redis_key || "weixin_#{app_id}")
@storage = Storage.init_with(self) @jsticket_redis_key = security_redis_key("js_sdk_#{app_id}")
end end
# return token # return token
def get_access_token def get_access_token
@storage.access_token token_store.access_token
end end
# 检查appid和app_secret是否有效。 # 检查appid和app_secret是否有效。
def is_valid? def is_valid?
@storage.valid? token_store.valid?
end end
private def token_store
Token::Store.init_with(self)
end
def access_token_param def jsticket_store
{access_token: get_access_token} JsTicket::Store.init_with(self)
end
def get_jsticket
jsticket_store.jsticket
end
# 获取js sdk 签名包
def get_jssign_package(url)
timestamp = Time.now.to_i
noncestr = SecureRandom.hex(16)
str = "jsapi_ticket=#{get_jsticket}&noncestr=#{noncestr}&timestamp=#{timestamp}&url=#{url}";
signature = Digest::SHA1.hexdigest(str)
{
"appId" => app_id, "nonceStr" => noncestr,
"timestamp" => timestamp, "url" => url,
"signature" => signature, "rawString" => str
}
end end
# 暴露出:http_get,http_post两个方法,方便第三方开发者扩展未开发的微信API。
def http_get(url, headers={}, endpoint="plain") def http_get(url, headers={}, endpoint="plain")
headers = headers.merge(access_token_param) headers = headers.merge(access_token_param)
WeixinAuthorize.http_get_without_token(url, headers, endpoint) WeixinAuthorize.http_get_without_token(url, headers, endpoint)
...@@ -51,6 +72,12 @@ module WeixinAuthorize ...@@ -51,6 +72,12 @@ module WeixinAuthorize
WeixinAuthorize.http_post_without_token(url, payload, headers, endpoint) WeixinAuthorize.http_post_without_token(url, payload, headers, endpoint)
end end
private
def access_token_param
{access_token: get_access_token}
end
def security_redis_key(key) def security_redis_key(key)
Digest::MD5.hexdigest(key.to_s).upcase Digest::MD5.hexdigest(key.to_s).upcase
end end
......
...@@ -9,12 +9,21 @@ module WeixinAuthorize ...@@ -9,12 +9,21 @@ module WeixinAuthorize
end end
def weixin_redis def weixin_redis
return nil if WeixinAuthorize.config.nil? return nil if config.nil?
@redis ||= WeixinAuthorize.config.redis @redis ||= config.redis
end
# 可选配置: RestClient timeout, etc.
# key 必须是符号
def rest_client_options
if config.nil?
return {timeout: 2, open_timeout: 3, verify_ssl: true}
end
config.rest_client_options
end end
end end
class Config class Config
attr_accessor :redis attr_accessor :redis, :rest_client_options
end end
end end
...@@ -39,6 +39,7 @@ module WeixinAuthorize ...@@ -39,6 +39,7 @@ module WeixinAuthorize
# result.result[:ok] #=> true # result.result[:ok] #=> true
# result.result['ok'] #=> true # result.result['ok'] #=> true
def package_result(result) def package_result(result)
return result if !result.is_a?(Hash)
if defined?(Rails) if defined?(Rails)
ActiveSupport::HashWithIndifferentAccess.new(result) ActiveSupport::HashWithIndifferentAccess.new(result)
else else
......
module WeixinAuthorize
module JsTicket
class ObjectStore < Store
def jsticket_expired?
# 如果当前token过期时间小于现在的时间,则重新获取一次
client.jsticket_expired_at <= Time.now.to_i
end
def jsticket
super
client.jsticket
end
def refresh_jsticket
super
end
end
end
end
module WeixinAuthorize
module JsTicket
class RedisStore < Store
JSTICKET = "jsticket"
EXPIRED_AT = "expired_at"
def jsticket_expired?
weixin_redis.hvals(client.jsticket_redis_key).empty?
end
def refresh_jsticket
super
weixin_redis.hmset(
client.jsticket_redis_key,
JSTICKET,
client.jsticket,
EXPIRED_AT,
client.jsticket_expired_at
)
weixin_redis.expireat(
client.jsticket_redis_key,
client.jsticket_expired_at.to_i
)
end
def jsticket
super
client.jsticket = weixin_redis.hget(client.jsticket_redis_key, JSTICKET)
client.jsticket_expired_at = weixin_redis.hget(
client.jsticket_redis_key,
EXPIRED_AT
)
client.jsticket
end
def weixin_redis
WeixinAuthorize.weixin_redis
end
end
end
end
# encoding: utf-8
module WeixinAuthorize
module JsTicket
class Store
attr_accessor :client
def initialize(client)
@client = client
end
def self.init_with(client)
if WeixinAuthorize.weixin_redis.nil?
ObjectStore.new(client)
else
RedisStore.new(client)
end
end
def jsticket_expired?
raise NotImplementedError, "Subclasses must implement a jsticket_expired? method"
end
def refresh_jsticket
set_jsticket
end
def jsticket
refresh_jsticket if jsticket_expired?
end
def set_jsticket
result = client.http_get("/ticket/getticket", {type: 1}).result
client.jsticket = result["ticket"]
client.jsticket_expired_at = result["expires_in"] + Time.now.to_i
end
end
end
end
# encoding: utf-8 # encoding: utf-8
module WeixinAuthorize module WeixinAuthorize
class ClientStorage < Storage module Token
class ObjectStore < Store
def valid? def valid?
super super
...@@ -20,4 +21,5 @@ module WeixinAuthorize ...@@ -20,4 +21,5 @@ module WeixinAuthorize
client.access_token client.access_token
end end
end end
end
end end
# encoding: utf-8 # encoding: utf-8
module WeixinAuthorize module WeixinAuthorize
module Token
class RedisStorage < Storage class RedisStore < Store
def valid? def valid?
weixin_redis.del(client.redis_key) weixin_redis.del(client.redis_key)
...@@ -29,6 +29,7 @@ module WeixinAuthorize ...@@ -29,6 +29,7 @@ module WeixinAuthorize
def weixin_redis def weixin_redis
WeixinAuthorize.weixin_redis WeixinAuthorize.weixin_redis
end end
end end
end
end end
# encoding: utf-8 # encoding: utf-8
module WeixinAuthorize module WeixinAuthorize
module Token
class Storage class Store
attr_accessor :client attr_accessor :client
...@@ -11,9 +11,9 @@ module WeixinAuthorize ...@@ -11,9 +11,9 @@ module WeixinAuthorize
def self.init_with(client) def self.init_with(client)
if WeixinAuthorize.weixin_redis.nil? if WeixinAuthorize.weixin_redis.nil?
ClientStorage.new(client) ObjectStore.new(client)
else else
RedisStorage.new(client) RedisStore.new(client)
end end
end end
...@@ -25,7 +25,7 @@ module WeixinAuthorize ...@@ -25,7 +25,7 @@ module WeixinAuthorize
auth_result = http_get_access_token auth_result = http_get_access_token
auth = false auth = false
if auth_result.is_ok? if auth_result.is_ok?
set_access_token_for_client(auth_result.result) set_access_token(auth_result.result)
auth = true auth = true
end end
{"valid" => auth, "handler" => auth_result} {"valid" => auth, "handler" => auth_result}
...@@ -33,7 +33,7 @@ module WeixinAuthorize ...@@ -33,7 +33,7 @@ module WeixinAuthorize
def refresh_token def refresh_token
handle_valid_exception handle_valid_exception
set_access_token_for_client set_access_token
end end
def access_token def access_token
...@@ -44,7 +44,7 @@ module WeixinAuthorize ...@@ -44,7 +44,7 @@ module WeixinAuthorize
raise NotImplementedError, "Subclasses must implement a token_expired? method" raise NotImplementedError, "Subclasses must implement a token_expired? method"
end end
def set_access_token_for_client(access_token_infos=nil) def set_access_token(access_token_infos=nil)
token_infos = access_token_infos || http_get_access_token.result token_infos = access_token_infos || http_get_access_token.result
client.access_token = token_infos["access_token"] client.access_token = token_infos["access_token"]
client.expired_at = Time.now.to_i + token_infos["expires_in"].to_i client.expired_at = Time.now.to_i + token_infos["expires_in"].to_i
...@@ -67,7 +67,6 @@ module WeixinAuthorize ...@@ -67,7 +67,6 @@ module WeixinAuthorize
raise ValidAccessTokenException, result_handler.full_error_message raise ValidAccessTokenException, result_handler.full_error_message
end end
end end
end end
end
end end
# NOTE: the rspec should be test alonely. # NOTE: the rspec should be test alonely.
require "spec_helper"
describe WeixinAuthorize::Client do describe WeixinAuthorize::Client do
describe "#get access_token" do describe "#get access_token" do
it "return a access_token nil value before authenticate" do it "return a access_token nil value before authenticate" do
......
describe WeixinAuthorize::Client do
describe "#get jsticket" do
it "return the same jsticket in the same thing twice" do
js_ticket_1 = $client.get_jsticket
sleep 5
js_ticket_2 = $client.get_jsticket
expect(js_ticket_1).to eq(js_ticket_2)
end
end
end
require "spec_helper"
describe WeixinAuthorize::Api::Custom do describe WeixinAuthorize::Api::Custom do
let(:text_message) do let(:text_message) do
"text Custom message" "text Custom message"
......
require "spec_helper"
describe WeixinAuthorize::Api::Groups do describe WeixinAuthorize::Api::Groups do
let(:group_name) do let(:group_name) do
......
require "spec_helper"
describe WeixinAuthorize::Api::Media do describe WeixinAuthorize::Api::Media do
let(:image_jpg_path) do let(:image_jpg_path) do
......
# encoding: utf-8 # encoding: utf-8
require "spec_helper"
describe WeixinAuthorize::Api::Menu do describe WeixinAuthorize::Api::Menu do
it "can create a menu" do it "can create a menu" do
......
require "spec_helper"
describe WeixinAuthorize::Api::Qrcode do describe WeixinAuthorize::Api::Qrcode do
it "#create_qr_scene" do it "#create_qr_scene" do
response = $client.create_qr_scene("123") response = $client.create_qr_scene("123")
expect(response.code).to eq(WeixinAuthorize::OK_CODE) expect(response.code).to eq(WeixinAuthorize::OK_CODE)
expect(response.result.keys).to eq(["ticket", "expire_seconds"]) expect(response.result.keys).to eq(["ticket", "expire_seconds", "url"])
expect(response.result["expire_seconds"]).to eq(1800) expect(response.result["expire_seconds"]).to eq(1800)
end end
it "#create_qr_limit_scene" do it "#create_qr_limit_scene" do
response = $client.create_qr_limit_scene("1234") response = $client.create_qr_limit_scene("1234")
expect(response.code).to eq(WeixinAuthorize::OK_CODE) expect(response.code).to eq(WeixinAuthorize::OK_CODE)
expect(response.result.keys).to eq(["ticket"]) expect(response.result.keys).to eq(["ticket", "url"])
end end
it "#return_qr_url" do it "#return_qr_url" do
......
require "spec_helper"
describe WeixinAuthorize::Api::User do describe WeixinAuthorize::Api::User do
it "can get a weixin User info" do it "can get a weixin User info" do
user_info = $client.user(ENV["OPENID"]) user_info = $client.user(ENV["OPENID"])
...@@ -20,4 +18,9 @@ describe WeixinAuthorize::Api::User do ...@@ -20,4 +18,9 @@ describe WeixinAuthorize::Api::User do
expect(followers.code).to eq(WeixinAuthorize::OK_CODE) expect(followers.code).to eq(WeixinAuthorize::OK_CODE)
expect(followers.result.keys).to eq(["total", "count", "data", "next_openid"]) expect(followers.result.keys).to eq(["total", "count", "data", "next_openid"])
end end
it "can update user remark" do
user_info = $client.update_remark(ENV["OPENID"], "dylan")
expect(user_info.code).to eq(WeixinAuthorize::OK_CODE)
end
end end
...@@ -43,7 +43,7 @@ ENV["APPSECRET"]="1a941cd88cb4579ba98ec06b6813af03" ...@@ -43,7 +43,7 @@ ENV["APPSECRET"]="1a941cd88cb4579ba98ec06b6813af03"
ENV["OPENID"]="o9k6BuB0kydAcPTc7sPxppB1GQqA" ENV["OPENID"]="o9k6BuB0kydAcPTc7sPxppB1GQqA"
# Comment to test for ClientStorage # Comment to test for ClientStorage
redis = Redis.new(:host => "127.0.0.1",:port => "6379") redis = Redis.new(host: "127.0.0.1", port: "6379", db: 15)
namespace = "weixin_test:weixin_authorize" namespace = "weixin_test:weixin_authorize"
...@@ -55,9 +55,11 @@ redis_with_ns = Redis::Namespace.new("#{namespace}", :redis => redis) ...@@ -55,9 +55,11 @@ redis_with_ns = Redis::Namespace.new("#{namespace}", :redis => redis)
WeixinAuthorize.configure do |config| WeixinAuthorize.configure do |config|
config.redis = redis_with_ns config.redis = redis_with_ns
config.rest_client_options = {timeout: 1, open_timeout: 1, verify_ssl: true}
end end
$client = WeixinAuthorize::Client.new(ENV["APPID"], ENV["APPSECRET"]) $client = WeixinAuthorize::Client.new(ENV["APPID"], ENV["APPSECRET"])
RSpec.configure do |config| RSpec.configure do |config|
# The settings below are suggested to provide a good initial experience # The settings below are suggested to provide a good initial experience
# with RSpec, but feel free to customize to your heart's content. # with RSpec, but feel free to customize to your heart's content.
......
...@@ -19,16 +19,16 @@ Gem::Specification.new do |spec| ...@@ -19,16 +19,16 @@ Gem::Specification.new do |spec|
spec.require_paths = ["lib"] spec.require_paths = ["lib"]
spec.add_dependency "rest-client", ">= 1.6.7" spec.add_dependency "rest-client", ">= 1.6.7"
spec.add_dependency "redis", "~> 3.1.0" spec.add_dependency "redis", ">= 3.1.0"
spec.add_dependency "carrierwave", "~> 0.10.0" spec.add_dependency "carrierwave", ">= 0.10.0"
spec.add_dependency 'mini_magick', '~> 3.7.0' spec.add_dependency 'mini_magick', '>= 3.7.0'
# A streaming JSON parsing and encoding library for Ruby (C bindings to yajl) # A streaming JSON parsing and encoding library for Ruby (C bindings to yajl)
# https://github.com/brianmario/yajl-ruby # https://github.com/brianmario/yajl-ruby
spec.add_dependency "yajl-ruby", "~> 1.2.0" spec.add_dependency "yajl-ruby", ">= 1.2.0"
spec.add_development_dependency "bundler", "~> 1.3" spec.add_development_dependency "bundler"
spec.add_development_dependency "rake" spec.add_development_dependency "rake"
end end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment