本文同步發布在 www.inside.com.tw

看到Robert Shedd的這篇部落格,提到這個問題,整理一下分享給大家,相關的code也是引用自該文章。
基本問題 – 因為facebook沒給安裝你應用的用戶清單,沒辦法精準掌握現有用戶
這裡假設你已經用RoR+Facebooker開發了幾個應用,如果還沒,可以閱讀:
那麼,用facebooker開發好了一個ROR在facebook上的應用後,如果想知道目前某人現在是否安裝了你的程式,當他連上有建立Session的時候,我們用User#has_added_app可以知道他是否安裝了程式。如果把他的user id 存在local的資料庫裡,那麼你就可以知道目前所有"安裝了"你應用程式的使用者了。
不過,如果使用者在某個時間刪除掉了你的應用程式,也就是說他退出了,由於退出時沒有建立與ap間的session,我們不能用has_added_app來了解狀況,並刪除local資料庫的資料。使用者有進有出,時間久了,你的local資料庫上的資料就不準確了。若有做排名的列表,就可能出現幽靈人,明明已經沒裝你的應用,卻仍高掛第一名。
解決方法 – 使用 post_authorize與 post_deauthorize
針對這樣的需求,facebook提供了post_authorize與post_deauthorize兩個callback,註冊了應用跟移除應用都可以通知我們的ap一聲,在這個地方做些事情,那麼我們就能確保數據的一致性了。
首先,Robert建議另開一個Controller,另外就是要注意要確認authenticity token,程式碼範例如下:
class CallbacksController < ApplicationController
skip_before_filter :ensure_app_installed, :except => :post_authorize
skip_before_filter :verify_authenticity_token
def post_authorize
if request.post?
#做些事情,例如存資料庫
render :nothing => true
end
end
def post_deauthorize
if request.post?
#做些事情,例如刪除資料
render :nothing => true
end
end
end
進階注意-進一步確認加入/退出請求實際來自Facebook平台
以上程式碼已經可以了,可以解決使用者新增與退出應用時,我們能精準掌握他的狀態的問題了。
不過,我們仍得注意另一個問題,就是這個動作是否來自facebook平台,免得被其他人透過其他方式觸發了。在上述的程式碼裡加上這段檢查,就變成了:
class CallbacksController < ApplicationController
skip_before_filter :ensure_app_installed, :except => :post_authorize
skip_before_filter :verify_authenticity_token
def post_authorize
if request.post?
if verify_uninstall_signature
#將使用者的has_app設成1
end
end
render :nothing => true
end
def post_deauthorize
if request.post?
if verify_uninstall_signature
#把使用者的has_app設成0
end
end
render :nothing => true
end
private
#參考了 http://wiki.developers.facebook.com/index.php/Post-Remove_URL
def verify_uninstall_signature
signature = ''
keys = params.keys.sort
keys.each do |key|
next if key == 'fb_sig'
next unless key.include?('fb_sig')
key_name = key.gsub('fb_sig_', '')
signature += key_name
signature += '='
signature += params[key]
end
signature += FACEBOOK_YAML['secret_key']
calculated_sig = Digest::MD5.hexdigest(signature)
if calculated_sig != params[:fb_sig]
logger.warn "\nWARNING :: potential spoofing :: expected signatures did not match"
logger.info "\nSignature (fb_sig param from facebook) :: #{params[:fb_sig]}"
logger.info "\nSignature String (pre-hash) :: #{signature}"
logger.info "\nMD5 Hashed Sig :: #{calculated_sig}"
#check to see if ip variables are nil
if not request.env['HTTP_X_FORWARDED_FOR'].nil? and not request.env['HTTP_X_REAL_IP'].nil?
ip = request.env['HTTP_X_FORWARDED_FOR'] || request.env['HTTP_X_REAL_IP']
else
ip = request.remote_ip
end
logger.info "\nRemote IP :: #{ip}"
return false
else
#logger.warn "\n\nSUCCESS!! Signatures matched.\n"
end
return true
end
end
其中要注意的是關於FACEBOOK_YAML[‘secret_key’]的這行,密碼金鑰要定義在environment.rb:
#load facebooker configuration for usage
facebook_config = File.join(RAILS_ROOT, 'config', 'facebooker.yml')
FACEBOOK_YAML = YAML::load(ERB.new(File.read(facebook_config)).result)[RAILS_ENV]
這樣,就可以準確掌握你的應用程式中真正的活躍者了!Happy Coding!