# coding: utf-8 require 'cgi' require 'rails' require 'openssl' require 'open-uri' require 'net/https' require 'active_model' require 'active_record' require 'active_support' require 'blsm-mp-wx/version' require 'blsm-mp-wx/model/active_record' require 'blsm-mp-wx/model/vd_mp_wx' require 'blsm-mp-wx/model/vd_mp_msg' require 'blsm-mp-wx/model/vd_wx_user' require 'json' require 'faraday' module BlsmMpWx ERROR_CODES = { 404 => {code: 404, msg: '未知的公众号,如有疑问,请联系xi.liu@abcomb.com'} } class << self attr_accessor :APP_ID attr_accessor :APP_ID_2 # MP_MSG_TYPES = %w(new_order balance score_change account_change distribute custom split_order order todo) #微信公众号消息类型 #根据app_id获取access_token #系统会根据当前的access_token即过期时间,自动更新并返回 #开发者无需关心这里的access_token怎么更新 #===Parameters # * +app_id+ -公众号的appid def access_token(app_id=nil) app_id ||= self.APP_ID app = VdMpWx.where(app_id: app_id).first return nil unless app return app.access_token unless app.access_token_expired? #没有过期,直接返回 response = Faraday.new(:url => 'https://api.weixin.qq.com').get do |req| req.url '/cgi-bin/token' req.params[:appid] = app.app_id req.params[:secret] = app.app_secret req.params[:grant_type] = 'client_credential' end json_obj = parse_json(response.body) return nil unless json_obj return nil unless json_obj.has_key?('access_token') access_token = json_obj['access_token'] app.save_access_token(access_token, json_obj['expires_in']) access_token end #获取jsapi全局唯一票据 def js_api_ticket(app_id=nil) app_id ||= self.APP_ID access_token = access_token(app_id) app = VdMpWx.where(app_id: app_id).first return nil unless access_token return nil unless app return app.js_api_ticket unless app.js_api_ticket_expired? #没有过期,直接返回 response = Faraday.new(:url => 'https://api.weixin.qq.com').get do |req| req.url '/cgi-bin/ticket/getticket' req.params[:access_token] = access_token req.params[:type] = 'jsapi' end json_obj = parse_json(response.body) return nil unless json_obj return nil unless json_obj.has_key?('ticket') ticket = json_obj['ticket'] app.save_js_api_ticket(ticket, json_obj['expires_in']) ticket end #发送普通消息 #文本消息: # { # "touser":"OPENID", # "msgtype":"text", # "text": # { # "content":"Hello World" # } # } #图文消息: # { # "touser":"OPENID", # "msgtype":"news", # "news": # { # "articles": [ # { # "title":"Happy Day", # "description":"Is Really A Happy Day", # "url":"URL", # "picurl":"PIC_URL" # }, # { # "title":"Happy Day", # "description":"Is Really A Happy Day", # "url":"URL", # "picurl":"PIC_URL" # } # ] # } # } def send_msg(app_id=nil, openid, msg) app_id ||= self.APP_ID access_token = access_token(app_id) return nil unless access_token return nil unless openid return nil unless msg[:touser]==openid response = Faraday.new(:url => 'https://api.weixin.qq.com').post do |req| req.url '/cgi-bin/message/custom/send' req.params[:access_token] = access_token req.headers['Content-Type'] = 'application/json' req.body = msg.to_json end json_obj = parse_json(response.body) return nil unless json_obj json_obj['errcode']==0 end #JSAPI签名package def js_api_sign_package(app_id=nil, url) app_id ||= self.APP_ID ticket = js_api_ticket(app_id) timestamp = Time.new.to_i nonce_str = Digest::MD5.hexdigest(Time.new.to_i.to_s + Random.rand(99999).to_s) string = "jsapi_ticket=#{ticket}&noncestr=#{nonce_str}×tamp=#{timestamp}&url=#{url}" signature = Digest::SHA1.hexdigest(string) { appId: app_id, nonceStr: nonce_str, timestamp: timestamp, url: url, signature: signature, rawString: string } end #获取用户的基本信息(包括UnionID机制) def gen_wx_user_info(app_id=nil, openid) app_id ||= self.APP_ID access_token = access_token(app_id) return nil unless access_token response = Faraday.new(:url => 'https://api.weixin.qq.com').get do |req| req.url '/cgi-bin/user/info' req.params[:openid] = openid req.params[:access_token] = access_token end json_obj = parse_json(response.body) puts "#{json_obj}" return nil unless json_obj return json_obj.inject({}) { |memo, (key, v)| memo[key.to_s.to_sym]=v; memo } if json_obj.has_key?('openid') nil end #生成永久二维码ticket def gen_forever_qr_ticket(app_id=nil, scene_id) app_id ||= self.APP_ID access_token = access_token(app_id) return nil unless access_token response = Faraday.new(:url => 'https://api.weixin.qq.com').post do |req| req.url '/cgi-bin/qrcode/create' req.params[:access_token] = access_token req.body="{\"action_name\": \"QR_LIMIT_SCENE\", \"action_info\": {\"scene\": {\"scene_id\": #{scene_id}}}}" end json_obj = parse_json(response.body) return nil unless json_obj return nil unless json_obj.has_key?('ticket') json_obj['ticket'] end #长链接转短连接 def gen_short_url(app_id=nil, long_url) app_id ||= self.APP_ID access_token = access_token(app_id) return nil unless access_token response = Faraday.new(:url => 'https://api.weixin.qq.com').post do |req| req.url '/cgi-bin/shorturl' req.headers['Content-Type'] = 'application/json' req.params[:long_url] = long_url req.params[:access_token] = access_token req.body = "{\"action\":\"long2short\",\"long_url\":\"#{long_url}\"}" end json_obj = parse_json(response.body) return nil unless json_obj return nil unless json_obj.has_key?('short_url') json_obj['short_url'] end #创建消息,存储到消息队列中 #====Parameters # * +app_id+ - 公众号的唯一标识 # * +openid+ - 发送给用户的openid # * +content+ - 消息内容 # * +msg_name+ - 消息种类:new_order(新订单通知)、balance(结算通知)、score_change(积分变动通知)、order(订单通知)、todo(待处理)、split_order(分单)、custom(普通消息) # new_order => content: {touser:'openid',order_id:''} 系统自动优先使用模板消息发送通知 # balance => content: {touser:'openid',clearing_id} 系统自动优先使用模板消息发送通知 # score_change => content: {touser:'',change:5,total:25,content:'邀请他人。。。。'} def create_msg(app_id, openid, content, msg_name, template_msg=true) app_id ||= self.APP_ID return nil unless openid return nil unless content[:touser]==openid # return nil unless MP_MSG_TYPES.include?(msg_name) VdMpMsg.create({ app_id: app_id, openid: openid, content: content.to_json, msg_name: msg_name, template_msg: template_msg, status: 'none' }) end def create_msg!(app_id, openid, content, msg_name, template_msg=true) app_id ||= self.APP_ID VdMpMsg.create!({ app_id: app_id, openid: openid, content: content.to_json, msg_name: msg_name, template_msg: template_msg, status: 'none' }) end #批量获取公众号的素材列表 #=====Parameters # * +app_id+ # * +type+ 素材类型:voice、video、image、news def get_material_count(app_id, type) app_id ||= self.APP_ID access_token = access_token(app_id) return nil unless access_token response = Faraday.new(:url => 'https://api.weixin.qq.com').post do |req| req.url '/cgi-bin/material/get_materialcount' req.headers['Content-Type'] = 'application/json' req.params[:access_token] = access_token end json_obj = parse_json(response.body) return nil unless json_obj return nil unless json_obj.has_key?("#{type}_count") json_obj["#{type}_count"].to_i end #根据media_id来获取永久素材 #=====Parameters # * +app_id+ # * +media_id+ 要获取的素材的media_id def get_material(app_id, media_id) app_id ||= self.APP_ID access_token = access_token(app_id) return nil unless access_token response = Faraday.new(:url => 'https://api.weixin.qq.com').post do |req| req.url '/cgi-bin/material/get_material' req.headers['Content-Type'] = 'application/json' req.params[:access_token] = access_token req.body= {media_id: media_id}.to_json end body = response.body json_obj = parse_json(body) return body unless json_obj json_obj.inject({}) { |memo, (key, v)| memo[key.to_s.to_sym]=v; memo } end #批量获取公众号的素材列表 #=====Parameters # * +app_id+ # * +type+ 素材类型:voice、video、image、news # * +offset+ # * +count+ def batchget_material(app_id, type, offset, count=20) app_id ||= self.APP_ID access_token = access_token(app_id) return nil unless access_token response = Faraday.new(:url => 'https://api.weixin.qq.com').post do |req| req.url '/cgi-bin/material/batchget_material' req.headers['Content-Type'] = 'application/json' data = { type: type, offset: offset, count: count } req.params[:access_token] = access_token req.body = data.to_json end json_obj = parse_json(response.body) return nil unless json_obj return nil unless json_obj.has_key?('total_count') json_obj.inject({}) { |memo, (key, v)| memo[key.to_s.to_sym]=v; memo } end def all_openid(app_id=nil, next_openid='', returned_openids = []) app_id ||= self.APP_ID p "getting openid... next_openid:#{next_openid}" begin conn = Faraday.new(:url => 'https://api.weixin.qq.com') response = conn.get do |req| req.url '/cgi-bin/user/get' req.headers['Content-Type'] = 'application/json' req.params[:access_token] = access_token(app_id) req.params[:next_openid] = next_openid end response_body = response.body p response_body jobject = JSON.parse(response_body) if jobject if jobject['data'] && jobject['data']['openid'] p "total:#{jobject['total']},count:#{jobject['count']}" returned_openids += jobject['data']['openid'] end if jobject['next_openid'].to_s != '' return all_openid(jobject['next_openid'], returned_openids) end end if !jobject || jobject['next_openid'].to_s =='' return returned_openids end rescue Exception => e puts e end returned_openids end #sns登录获取用户信息 def sns_login_userinfo(code) app_id = 'wx143306afc47c63a6' app = VdMpWx.where(app_id: app_id).first conn = Faraday.new(:url => 'https://api.weixin.qq.com') response = conn.post do |req| req.url '/sns/oauth2/access_token' req.headers['Content-Type'] = 'application/xml' req.params[:appid] = app_id req.params[:secret] =app.app_secret req.params[:code] = code req.params[:grant_type] = 'authorization_code' end if response.body.encoding != 'UTF-8' response_body = response.body.force_encoding('UTF-8') else response_body = response.body end res = parse_json(response_body) conn = Faraday.new(:url => 'https://api.weixin.qq.com') response = conn.post do |req| req.url '/sns/userinfo' req.headers['Content-Type'] = 'application/xml' req.params[:access_token] = res['access_token'] req.params[:openid] = res['openid'] end if response.body.encoding != 'UTF-8' response_body = response.body.force_encoding('UTF-8') else response_body = response.body end res = parse_json(response_body) VdWxUser.find_or_create_by({app_id: app_id, openid: res['openid']}) do |u| u.update_attributes({nickname: res['nickname'], sex: res['sex'], unionid: res['unionid'], language: res['language'], city: res['city'], province: res['province'], country: res['country'], headimgurl: res['headimgurl'], }) end end private #解析json类型的数据,并将最后的数据转为hash或array # +json_data_str+ 要转换的json数据 def parse_json(json_data_str) begin return JSON.parse(json_data_str) rescue end nil end end end