每隔一段期間,同溫層又會彼此檢視著近況,近來越來越有強烈的孤僻感,或許就是「道不同,不相為謀」吧!
前陣子得知一些強者同事離職,細細品味後,稱得上在錯的時間遇上對的人,出現高頻的內耗,不得已只好分別。畢竟大多數的公司只需要完美執行老闆的策略,而非不斷跳出來 challenge 老闆的人,想著想著也感慨了起來。
除此之外,漸漸進入 35 歲的人生,偶爾哈拉起同好,倒不是每個人有強烈的求生意識的,不少如溫水煮青蛙,停滯了進展。但,沒有什麼是絕對的好或絕對的壞,永遠的事後話!在對岸發展的強者學弟,隨著公司角色的轉換,開始不 coding 了,卻也開始感到內心空虛寂寞覺得弱 XD 彼此寒暄後,人生就是一場場賭注罷了!只待小公司,若沒起色,那段就變成空白人生,若成了肯定比在大公司獲得更多;只待大公司,CP值、未來好推算,但往上的位置早已被卡位,容易成了永遠的螺絲釘。所以,只能彼此期許著未來是賭對的,繼續走下去了。
回到標題,我想,漸漸地眼不見為淨吧,為那些不同道的人心煩也挺浪費光陰的,給予什麼回應也無法清醒,那就都別說了,大家找著自己的同溫層,繼續黑皮下去,人生也沒什麼不好囉
2017年3月23日 星期四
2017年3月7日 星期二
使用 Facebook Graph API 取得使用者資料:性別、年紀、地區、教育來做問卷調查
幾年前開發 sign in via Facebook,然後就停擺了好一陣子,最近市場研究想做個問卷,才發現當年收集的資料不怎齊全 XD 再加上 Facebook Graph api 也改版幾次,並且越來越重視個資保護,有很多權限必須額外取得才行。
請參考 https://developers.facebook.com/docs/graph-api/reference/user 定義最準。
而一般問卷常用的年紀,在 Facebook 只有 13/18/21 這三種數值可用,要再更仔細去請用 FB ads 去發了;而教育資訊跟地區資訊也得額外要求額外的權限 user_education_history 跟 user_location 才能得到
最後,在用 graph api 來取得這些資訊吧:
預計先這般儲存吧:
請參考 https://developers.facebook.com/docs/graph-api/reference/user 定義最準。
而一般問卷常用的年紀,在 Facebook 只有 13/18/21 這三種數值可用,要再更仔細去請用 FB ads 去發了;而教育資訊跟地區資訊也得額外要求額外的權限 user_education_history 跟 user_location 才能得到
最後,在用 graph api 來取得這些資訊吧:
/me?fields=id,name,email,education,gender,location,age_range
{
"id": "##",
"name": "Yuan-Yi Chang",
"email": "@gmail.com",
"education": [
{
...
"type": "High School",
...
},
{
...
"type": "College",
...
},
{
...
"type": "Graduate School",
...
}
],
"gender": "male",
"location": {
"id": "110765362279102",
"name": "Taipei, Taiwan"
},
"age_range": {
"min": 21
}
}
預計先這般儲存吧:
age_range => 13/18/21
gender => None/Male/Female/Unisex
education => None/HighSchool/College/GraduateSchool
2017年3月2日 星期四
[PHP] Facebook Graph API v2.2 升級提醒 - CodeIgniter 2.x 與 Facebook PHP SDK v5
這幾天一堆 fb app 被這種信轟炸:YourFBApp 的新開發人員重要通知,簡言之就是 facebook graph 舊版 api 即將在 2017/03/25 失效,請立即更新。
Facebook 開放平台變更紀錄 - https://developers.facebook.com/docs/apps/changelog
追了一下,主因是很多手上的服務是在 2014 年底開發,當時就是用 v2.2 graph api 沒錯,關鍵之處可以用搜尋:
非常精準的地發現自己就是這個族群,也感謝 Facebook 賜予工作機會(誤),這樣每兩年就有新工作可以做,非常開心!接下來就是痛苦的開始,還是一口氣從 v4 升級到 v5 吧!
前提:
由於使用 PHP CodeIgniter 2.x 架構,再加上 HA 架構,因此需要處理 SESSION 的儲存機制,在此就用 DB 處理,而對 v4 SDK 中,是直接在 FacebookRedirectLoginHelper 改寫一份對於認證所需資料儲存的東西,但在 v5 架構就更漂亮了,可以把負責資料儲存的東西傳入,不需再改成 FacebookRedirectLoginHelper。若服務仍在單一機器上運行,可以略過此設計。
初始化:
取得登入網址:
登入完成取得 access_token:
取得 longlived access_token:
查詢個人資料:
從字串初始化並檢查 token 是否過期:
api 查詢架構:
取得使用者權限清單:
查詢朋友清單:
收工!
Facebook 開放平台變更紀錄 - https://developers.facebook.com/docs/apps/changelog
追了一下,主因是很多手上的服務是在 2014 年底開發,當時就是用 v2.2 graph api 沒錯,關鍵之處可以用搜尋:
facebook-php-sdk-v4 $ grep -r "v2.2" *
src/Facebook/FacebookRequest.php: const GRAPH_API_VERSION = 'v2.2';
非常精準的地發現自己就是這個族群,也感謝 Facebook 賜予工作機會(誤),這樣每兩年就有新工作可以做,非常開心!接下來就是痛苦的開始,還是一口氣從 v4 升級到 v5 吧!
前提:
由於使用 PHP CodeIgniter 2.x 架構,再加上 HA 架構,因此需要處理 SESSION 的儲存機制,在此就用 DB 處理,而對 v4 SDK 中,是直接在 FacebookRedirectLoginHelper 改寫一份對於認證所需資料儲存的東西,但在 v5 架構就更漂亮了,可以把負責資料儲存的東西傳入,不需再改成 FacebookRedirectLoginHelper。若服務仍在單一機器上運行,可以略過此設計。
v4:
<?php
// FacebookRedirectLoginCIHelper
namespace Facebook;
use Facebook\FacebookRedirectLoginHelper;
class FacebookRedirectLoginCIHelper extends \Facebook\FacebookRedirectLoginHelper {
private $sessionPrefix = 'FBRLH_';
public function __construct($redirectUrl, $appId = null, $appSecret = null) {
parent::__construct($redirectUrl, $appId, $appSecret);
$this->ci = & get_instance();
}
protected function storeState($state) {
if ($this->ci->session->set_userdata($this->sessionPrefix . 'state', $state)) {
$this->state = $this->ci->session->set_userdata($this->sessionPrefix . 'state', $state);
return $this->state;
}
return NULL;
}
protected function loadState() {
return $this->state = $this->ci->session->userdata($this->sessionPrefix . 'state');
}
}
v5:
<?php
// FacebookSessionPersistentDataCIHandler
namespace Facebook\PersistentData;
use Facebook\Exceptions\FacebookSDKException;
class FacebookSessionPersistentDataCIHandler implements PersistentDataInterface {
protected $sessionPrefix = 'FBRLH_';
public function __construct($enableSessionCheck = true) {
$this->ci = & get_instance();
}
public function get($key) {
if ($this->ci)
return $this->ci->session->userdata($this->sessionPrefix . $key);
return null;
}
public function set($key, $value) {
if ($this->ci)
$this->ci->session->set_userdata($this->sessionPrefix . $key, $value);
}
}
初始化:
v4:
require 'path/facebook-php-sdk-v4/autoload.php';
use Facebook\FacebookRedirectLoginCIHelper;
use Facebook\FacebookRequest;
use Facebook\FacebookSession;
use Facebook\GraphUser;
FacebookSession::setDefaultApplication($this->config->item("api_key"), $this->config->item("secret_key"));
v5:
require 'path/php-graph-sdk-5.4.4/src/Facebook/autoload.php';
use Facebook\Facebook;
use Facebook\Authentication\AccessToken;
use Facebook\PersistentData\FacebookSessionPersistentDataCIHandler;
use Facebook\FacebookRequest;
$fb = new Facebook([
'app_id' => $this->config->item("api_key"),
'app_secret' => $this->config->item("secret_key"),
'persistent_data_handler' => new FacebookSessionPersistentDataCIHandler(),
//'default_graph_version' => 'v2.8',
]);
取得登入網址:
v4:
$helper = new FacebookRedirectLoginCIHelper($callback_url);
$login_url = $helper->getLoginUrl($this->config->item('fb_scope'));
v5:
$helper = $fb->getRedirectLoginHelper();
$login_url = $helper->getLoginUrl($callback_url, $this->config->item('fb_scope'));
登入完成取得 access_token:
v4:
$fb_session = $helper->getSessionFromRedirect();
$access_token = $fb_session->getAccessToken();
v5:
$accessToken = $helper->getAccessToken();
取得 longlived access_token:
v4:
$long_lived_token = $fb_session->getLongLivedSession()->getToken();
v5:
$long_lived_token = (string) $fb->getOAuth2Client()->getLongLivedAccessToken($access_token);
查詢個人資料:
v4:
$user_profile = (new FacebookRequest($fb_session, 'GET', '/me'))->execute()->getGraphObject(GraphUser::className());
$uid = $user_profile->getProperty('id');
$name = $user_profile->getProperty('name');
$email = $user_profile->getProperty('email');
$profile_url = $user_profile->getProperty('link');
if (empty($profile_url))
$profile_url = https://www.facebook.com/$uid";
$profile_image_link = "https://graph.facebook.com/$uid/picture";
v5:
$user_profile = $fb->get('/me', $accessToken)->getGraphNode();
$uid = $user_profile->getField('id');
$name = $user_profile->getField('name');
$email = $user_profile->getField('email');
$profile_url = $user_profile->getField('link');
if (empty($profile_url))
$profile_url = https://www.facebook.com/$uid";
$profile_image_link = "https://graph.facebook.com/$uid/picture";
從字串初始化並檢查 token 是否過期:
v4:
use Facebook\FacebookSession;
$session = new FacebookSession( $token );
return $session->validate();
v5:
return $fb->getOAuth2Client()->debugToken( $token )->getIsValid() == true;
api 查詢架構:
v4:
function query($token, $method, $api, $array_mode = true) {
if (!empty($token)) {
$session = new FacebookSession($token);
if (!$session->validate())
return false;
$response = ( new FacebookRequest( $session, $method, $api ) )->execute();
if ($array_mode)
return $response->getGraphObject()->asArray();
return $response->getGraphObject();
}
return false;
}
v5:
function _node_query($token, $method, $api, $array_mode = true) {
if (!empty($token)) {
try {
if (!strcasecmp($method, 'POST'))
return $array_mode ? $this->fb->post($api, $token)->getGraphNode()->asArray() : $this->fb->post($api, $token)->getGraphNode();
return $array_mode ? $this->fb->get($api, $token)->getGraphNode()->asArray() : $this->fb->get($api, $token)->getGraphNode();
} catch (Exception $e) {
var_dump($e);
}
}
return false;
}
function _edge_query($token, $method, $api, $array_mode = false) {
if (!empty($token)) {
try {
if (!strcasecmp($method, 'POST'))
return $array_mode ? $this->fb->post($api, $token)->getGraphEdge()->asArray() : $this->fb->post($api, $token)->getGraphEdge();
return $array_mode ? $this->fb->get($api, $token)->getGraphEdge()->asArray() : $this->fb->get($api, $token)->getGraphEdge();
} catch (Exception $e) {
var_dump($e);
}
}
return false;
}
取得使用者權限清單:
v4:
query($token, 'GET', '/me/permissions');
v5:
_edge_query($token, 'GET', '/me/permissions');
查詢朋友清單:
v4:
function get_friend_installed_app_list($token, &$have_next_page, $page = 1, $item_per_page = 25) {
$have_next_page = false;
$data = $this->query($token, 'GET', '/me/friends?fields=installed&limit='.($item_per_page).'&offset='.(($page - 1) * $item_per_page));
if(isset($data['data']) && ($cnt = count($data['data'])) > 0) {
if (isset($data['paging']) && is_object($data['paging']) && property_exists($data['paging'], 'next')) {
if ($cnt >= $item_per_page)
$have_next_page = true;
}
$output = array();
foreach($data['data'] as $user)
if (is_object($user) && property_exists($user, 'id'))
array_push($output, $user->id);
return $output;
}
return false;
}
v5:
function get_friend_installed_app_list($token, &$have_next_page, $page = 1, $item_per_page = 25) {
$have_next_page = false;
$data = $this->_edge_query($token, 'GET', '/me/friends?fields=installed&limit='.($item_per_page).'&offset='.(($page - 1) * $item_per_page), false);
$items = $data->asArray();
if(is_array($items) && ($cnt = count($items)) > 0) {
$paging = $data->getMetaData();
if (isset($paging['paging']) && isset($paging['paging']['next']))
if ($cnt >= $item_per_page)
$have_next_page = true;
$output = array();
foreach($items as $user)
if (isset($user['id']))
array_push($output, $user['id']);
return $output;
}
return false;
}
收工!
2017年3月1日 星期三
一打一的世界
趁著連假,扛著一個小孩返鄉,體驗了走路、公車、捷運和高鐵。挑戰最大的是高鐵,在一個壅擠的座位上哄著小孩,也算是個磨練了。還好我不用選台鐵,那肯定是瘋了!
這陣子咀嚼著又似看透一些世俗話題,從黃毛小子、強說愁,總算累積了一點社會經歷,只是如同雷軍創業的初始心態:財務自由了,再來談創業、再來談不為錢做事的方向。很多事的起頭,都是有前提的,也認清自己不夠本去做哪些事。
返鄉看著兩老和周邊的考驗,漸漸浮現出這類現實的感觸,不再像學生時代的低機會成本,不再是年少輕狂自傲能扛著天,我想,漸漸地就像練武的人,什麼一次打十個,還是逃命吧 XD 一次打一個吧!
我想,我還是會積極地把握機會,但需要更沉得住氣、需要更多耐心和命運磨合磨合。
這陣子咀嚼著又似看透一些世俗話題,從黃毛小子、強說愁,總算累積了一點社會經歷,只是如同雷軍創業的初始心態:財務自由了,再來談創業、再來談不為錢做事的方向。很多事的起頭,都是有前提的,也認清自己不夠本去做哪些事。
返鄉看著兩老和周邊的考驗,漸漸浮現出這類現實的感觸,不再像學生時代的低機會成本,不再是年少輕狂自傲能扛著天,我想,漸漸地就像練武的人,什麼一次打十個,還是逃命吧 XD 一次打一個吧!
我想,我還是會積極地把握機會,但需要更沉得住氣、需要更多耐心和命運磨合磨合。
2017年2月24日 星期五
Captive portal 研究心得,以 iptables 實作
之前略知使用 Taipei Free 或 CHT Wi-Fi時,會彈跳出認證(登入)的網頁,但卻不知其所以然,直到最近研究了 Captive portal 的東西。一開始用 Captive portal 關鍵字找了一些 RFC 文件,殊不知那些都是煙霧彈 XD 如:
至於 router 上的實作,簡單的說,就是透過防火牆機制:
以 NoCatSplash 產出的 iptables 為筆記:
其他 iptabes 簡介:
- Captive-Portal Identification Using DHCP or Router Advertisements (RAs) https://tools.ietf.org/html/rfc7710
- Additional HTTP Status Codes - The 511 Status Code and Captive Portals https://tools.ietf.org/html/rfc6585
- 用戶連上 router 時,可以進行 DNS lookup
- 未通過認證的用戶,無法透過 router 進行網路連線請求,如連上 facebook.com
- 通過認證的用戶,透過 router 可以正常使用網路
至於 router 上的實作,簡單的說,就是透過防火牆機制:
- 防火牆設計各種狀態,例如未認證,已認證的 flag (mark)
- 當用戶完成認證時,透過 CGI 執行防火牆指令,給予標記 (mark)
- 允許已標記的 client 封包通行
- http://dev.wifidog.org/
- https://www.dd-wrt.com/wiki/index.php/NoCatSplash
- https://github.com/arjan/NoCatSplash
- https://github.com/mhaas/fbwlan
-P INPUT ACCEPT
-P FORWARD ACCEPT
-P OUTPUT ACCEPT
-N WD_wlan0_AuthServs
-N WD_wlan0_Global
-N WD_wlan0_Internet
-N WD_wlan0_Known
-N WD_wlan0_Locked
-N WD_wlan0_Unknown
-N WD_wlan0_Validate
-A FORWARD -i wlan0 -j WD_wlan0_Internet
-A FORWARD -i eth0 -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -i wlan0 -o eth0 -j ACCEPT
-A WD_wlan0_AuthServs -d SERVER_IP/32 -j ACCEPT
-A WD_wlan0_Internet -m state --state INVALID -j DROP
-A WD_wlan0_Internet -o eth0 -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
-A WD_wlan0_Internet -j WD_wlan0_AuthServs
-A WD_wlan0_Internet -m mark --mark 0x254 -j WD_wlan0_Locked
-A WD_wlan0_Internet -j WD_wlan0_Global
-A WD_wlan0_Internet -m mark --mark 0x1 -j WD_wlan0_Validate
-A WD_wlan0_Internet -m mark --mark 0x2 -j WD_wlan0_Known
-A WD_wlan0_Internet -j WD_wlan0_Unknown
-A WD_wlan0_Known -j ACCEPT
-A WD_wlan0_Locked -j REJECT --reject-with icmp-port-unreachable
-A WD_wlan0_Unknown -p udp -m udp --dport 53 -j ACCEPT
-A WD_wlan0_Unknown -p tcp -m tcp --dport 53 -j ACCEPT
-A WD_wlan0_Unknown -p udp -m udp --dport 67 -j ACCEPT
-A WD_wlan0_Unknown -p tcp -m tcp --dport 67 -j ACCEPT
-A WD_wlan0_Unknown -j REJECT --reject-with icmp-port-unreachable
-A WD_wlan0_Validate -j ACCEPT
以 NoCatSplash 產出的 iptables 為筆記:
$ iptables -S
-P INPUT ACCEPT
-P FORWARD ACCEPT
-P OUTPUT ACCEPT
-N NoCat
-N NoCat_Inbound
-N NoCat_Ports
-A FORWARD -j NoCat
-A NoCat -j NoCat_Ports
-A NoCat -j NoCat_Inbound
-A NoCat -s SERVER_IP/24 -i wlan0 -m mark --mark 0x1 -j ACCEPT
-A NoCat -s SERVER_IP/24 -i wlan0 -m mark --mark 0x2 -j ACCEPT
-A NoCat -s SERVER_IP/24 -i wlan0 -m mark --mark 0x3 -j ACCEPT
-A NoCat -j DROP
-A NoCat_Ports -i wlan0 -p tcp -m tcp --dport 25 -m mark --mark 0x3 -j DROP
-A NoCat_Ports -i wlan0 -p udp -m udp --dport 25 -m mark --mark 0x3 -j DROP
$ iptables -t nat -S
-P PREROUTING ACCEPT
-P INPUT ACCEPT
-P OUTPUT ACCEPT
-P POSTROUTING ACCEPT
-N NoCat_Capture
-N NoCat_NAT
-A PREROUTING -j NoCat_Capture
-A POSTROUTING -j NoCat_NAT
-A NoCat_Capture -p tcp -m mark --mark 0x4 -m tcp --dport 80 -j REDIRECT --to-ports 5280
-A NoCat_Capture -p tcp -m mark --mark 0x4 -m tcp --dport 443 -j REDIRECT --to-ports 5280
-A NoCat_NAT -s SERVER_IP/24 -o eth0 -m mark --mark 0x1 -j MASQUERADE
-A NoCat_NAT -s SERVER_IP/24 -o eth0 -m mark --mark 0x2 -j MASQUERADE
-A NoCat_NAT -s SERVER_IP/24 -o eth0 -m mark --mark 0x3 -j MASQUERADE
其他 iptabes 簡介:
訂閱:
文章 (Atom)