2017 年 Ruby on Rails 5 "生成带参数的二维码"

我这里的业务场景是绑定网站账号和微信账号,方便之后发红包。

2017 年 Ruby on Rails 5 "生成带参数的二维码"

"生成带参数的二维码"

https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1443433542

WX20171114-145906@2x

代码部分只是生成二维码,没有夹带任何无关的逻辑。欢迎复制粘贴,希望节省了你的时间。

还有一个重点是,这个二维码扫描之后,会自动要求关注对应的微信公众号。这一点是不在程序员控制范围内的。用户关注后,服务器就可以收到扫二维码的事件推送。如果用户已经关注了,那么服务器就直接收到二维码事件推送。

业务场景

代码是写给寓住(yuzhu.me)的,要解决的问题是绑定 网站用户 <-> 微信用户。最终目的是发红包(见另一篇博客讲了发红包的代码实现)

  1. 用户通过各渠道得知 "填评分领红包" 活动
  2. 用户访问寓住网站 (https://yuzhu.me)
  3. 注册账号 -> 然后给公寓打分+提交价格+写评论。(备注:当前11月14号,寓住网站只有自己的注册登录系统,没有任何第三方登录(QQ/微信/微博等)
  4. 绑定微信号(通过扫二维码的形式)
  5. 评价审核通过(在寓住后台进行人工审核)
  6. 用户在审核通过后立刻收到微信红包。

几个重点:

  1. 微信的逻辑是每个用户一个 OpenID。还可以通过 OpenID 获得用户基础信息,如头像和名字。
    但是微信号和手机是不可能拿到的(给没做过的小白科普一下这一点)
  2. 还有就是二维码在扫描后,服务器会收到一个请求。

那么总体逻辑是:

  1. 生成一个二维码,二维码的数据里带了 user_id。
  2. 用户扫二维码,事件被推送到服务器,服务器收到 open_id + user_id
  3. 根据 user_id 找到用户,把 open_id 写进去,这样就建立起了关联。

代码

注意这里代码只是生成二维码的代码,建立账户关联属于自己的业务逻辑,我感觉很简单,没有必要贴出来,所以就不贴了。

这里的大体逻辑是(微信提供的 API 就是这么要求的):

  1. 获得 access_token
  2. 通过 access_token 获得 ticket
  3. 通过 ticket 获得二维码图片地址

Gemfile

gem 'rest-client'

qr-1

  # https://mp.weixin.qq.com/wiki/11/0e4b294685f817b95cbed85ba5e82b8f.html
  def get_access_token()
    redis = $redis
    return redis.get("weixin_access_key") if redis.get("weixin_access_key")
    # if not exist in redis
    # now we have to send http request to get a access_token from weixin
    appid = Rails.application.config.open_weixin_app_id
    appsecret = Rails.application.config.open_weixin_app_screct
    url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=#{appid}&secret=#{appsecret}"
    r = RestClient::Request.execute({method: :get, url: url})
    r = JSON.parse(r)
    unless r['access_token']
      return false
    end
    redis.set("weixin_access_key", r['access_token'])
    redis.expire("weixin_access_key", r['expires_in'].to_i - 60) # 提早 60 秒过期
    return r['access_token']
  end

  # 微信是先拿 ticket 才能拿二维码
  def get_qr_ticket(data)
    access_token = get_access_token()
    data = {
        "expire_seconds": 100,
        "action_name": "QR_STR_SCENE",
        "action_info": {"scene": {"scene_str": data}}
    }
    url = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=#{access_token}"
    r = RestClient::Request.execute({method: :post, url: url, payload: data.to_json, content_type: :json})
    r = JSON.parse(r)
    return r
  end

  # 返回二维码的 URL
  def get_qr_code(data)
    ticket_array = get_qr_ticket(data)
    ticket = ticket_array['ticket']
    url = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=#{ticket}"
    return url
  end

千万注意现在拿 access_token 是要求 IP 白名单的,如果你调用 get_qr_code 获得空,很大可能就是这样原因。你可以把 get_access_token() 里

    unless r['access_token']
      return false
    end

改成 return r 就知道了。返回信息会写 ip not in whitelist 之类的。

还有用了 redis 做缓存。
Gemfile

gem 'redis-rails'  # https://github.com/redis-store/redis-rails
gem 'redis', '~>3.2'

我也忘记为啥当初引入这俩了,我猜只引入一个也行,反正现在管用,就懒得纠结这些小事情了。

config/initializers/redis.rb

## Added rescue condition if Redis connection is failed
begin
  $redis = Redis.new(:host => Rails.configuration.redis_host, :port => 6379)
rescue Exception => e
  puts e
end

app/weixin/weixin.rb文件的总体结构是这样的:

module Weixin
  module_function
  def get_access_token()
  def get_qr_ticket(data)
  def get_qr_code(data)
end

使用方法

我是写在 app/weixin/weixin.rb 里,同时在 config/application.rb 里写了
config.autoload_paths += Dir[Rails.root.join('app', 'weixin')]
达到自动载入的目的。

用法是在 controller 里调用。
先写条路由 config/routes.rb 写一条 get '/get_qr_code' , to: 'api#get_qr_code'

然后在 app/controllers/api_controller 里写

  def get_qr_code
    data = {
      user_id: current_user.id
    }
    qr = Weixin::get_qr_code(data.to_json)
    render plain: qr
    return
  end

其中 'data' 就是你想带过去的数据。我这里因为只需要绑定用户,所以就给一个 user_id。
这里的 current_user 是来自 devise 。

以上 get_qr_code 会返回一个图片地址。
就是你需要的二维码
api
https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=gQFw8DwAAAAAAAAAAS5odHRwOi8vd2VpeGluLnFxLmNvbS9xLzAyWGNERkVPNkNmZDAxOGtwYXhxY2wAAgSwmApaAwRkAAAA

访问就会看到了:
111

完结

有疑问欢迎邮件 guokrfans#gmail.com