使用 Parse.com Cloud Code Hosting 進行 Facebook 登入存取 2

延續上一篇,成功使用 Facebook 登入 Parse.com 的使用者資訊後,接著就是怎麼從使用者資訊中取得 Facebook 的資料了。

User
以上圖的 Facebook Test Users 為例,建立完的使用者可以由 Parse.com 的 Data 中看到,不過 authData 卻只顯示了 Facebook 的 ID,因此我們可以先透過 API Console 來對 users 作存取,這裡要注意的是 Use Master Key 記得要選 Yes,否則會沒有權限看 authData 的內容;users 後面的參數為 objectId,若不放置則會列出全部符合的資料。
API console
在 Response 中的內容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"authData": {
"facebook": {
// 不要想對這組 access_token 亂來,因為是 test user XD
"access_token": "CAAMCk3Pv7SkBAEjfvRaG4SrC8k3CXak1843iisuUJiIK9gYV9PNFRraXi9gxYVBJO83zsvzFO91dcACevKwinxAVPNCUeEv0UPWsmv7DZBlqPjtZCCnEBcMBKpU7ikoj9OKo1ZCwzi3wmTycsB2avHT1SiBxLUF5ZAHTaT9XDNtz1phGZCk0lltOY5agj0JGQ9ezNGmOsvUmdpKFASx5K",
"expiration_date": "2015-08-15T17:52:46.495Z",
"id": "118563748477765"
}

},

"createdAt": "2015-06-16T17:52:48.623Z",
"name": "Super Mario",
"objectId": "wmVm7Qb1Fc",
"sessionToken": "jEKKzfbDcIN0CmBIvsdZR9Aoc",
"updatedAt": "2015-06-17T07:57:08.934Z",
"username": "3GwDpgmuqmfnyGYINNI27W9fO"
}

已經知道存放在哪裡之後,接下來就是利用 cloud code function 建立資料存取了!在這裡我用名字與圖片作範例,參考了 stackoverflow 這篇的回答稍微修改了一下:
當然,別忘記 userMasterKey…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Parse.Cloud.define("facebook", function(request, response) {
Parse.Cloud.useMasterKey();

new Parse.Query(Parse.User).get(request.params.user_id).then(function(user) {
var authData = user.get("authData");

// Quit early for users who aren't linked with Facebook
if (authData === undefined || authData.facebook === undefined) {
response.success(null);
return;
}

return Parse.Cloud.httpRequest({
method: "GET",
url: "https://graph.facebook.com/me",
params: {
access_token: authData.facebook.access_token,
fields: "name, friends",
},
});

}).then(function(json) {
response.success(json.data);

// Promises will let you bubble up any error, similar to a catch statement
}, function(error) {
response.error(error);
});
});

在 Express 中 call 建立好的 cloud host function:

1
2
3
4
5
6
7
8
9
10
11
app.get('/test', fbLogin, function(req, res) {
var user = Parse.User.current();
Parse.Cloud.run('facebook', { user_id: user.id }, {
success: function(results) {
res.send('Congrats, you are logged in, ' + results.name + '!' + '<img src="https://graph.facebook.com/'+ results.id +'/picture?type=normal">');
},
error: function(error) {
console.log("error :" + error);
}
});
});

若是成功的話就能在自己的 URL 中看到如下圖的結果了!
Facebook success

使用 Parse.com Cloud Code Hosting 進行 Facebook 登入存取 1

Parse.comjavascript SDK 中提供了使用者的存取,其中包含 Facebook 的整合,但 javascript 終究是前端,有些不想讓 client end知道的還是放在後端處理比較好。然而 Parse.com 所提供的 cloud code 所使用的目前雖是 node.js,會讓開發者很想直接把 javascript SDK 的 Facebook 部分塞進去看能不能跑,乍看之下很合理,但實際上就是不行,因為以 Parse.com Javascript SDK在要求登入時會跳出另一個瀏覽器視窗以要求登入 Facebook 及權限給予,而這個視窗當然沒有辦法在 server end 中呈現並要求 client end 進行認證。

在官方論壇上也有人發表過此問題:Interacting with the Facebook API in Cloud Code
所得到的回答是:
Unfortunately, the Facebook JavaScript SDK is not made to work outside of a browser, so using it directly from Cloud Code is not supported at the moment.

You can, however, get the authData from the current user in cloud code and use that to make a REST call to Facebook’s graph API manually.

因此我轉而研究使用OAuth方式來登入Facebook,除了跳轉出的頁面比較美觀外(不會產生另一個瀏覽器視窗),也不用擔心暴露資訊給clent end,參考資料有這兩篇:
網站利用 Facebook 帳號登入 (使用 OAuth)
10分鐘理解OAuth和facebook登入原理
這篇以python Django framework實作,其實看code好像也蠻容易理解的:Ghetto Facebook Registration with Django

後來正當我開始打算實作時,我找到了 parse-facebook-user-session ,原來 Parse.com 早就把這個寫好了,根本不用自己去寫了,只需要按照他的說明一步一步來就行了(吧)!

結果證明事情果然不是我想的這麼簡單,不管怎麼弄就是跳出 209 invalid session token ,花了一段時間後找到 Parse.com 自己發的文章 Session Migration Tutorial ,才知道把這個選項關掉就行了,我花了這麼久到底在幹麻!

User sessions
總之,關閉這個選項後就成功了, Parse.com 的資料庫也會成功紀錄登入過的使用者,事情完成一半,其餘的就是登入的使用者資料該怎麼讀取出來了~

UPDATE:在GitHub上新增了 demoGitHub

PHPWord 中文字顯示與下載問題

UPDATE 2016-06-24:不曉得當初自己怎麼沒將這篇從 Blogger 轉過來,總之現在是轉過來了,只將內文格式改為 Markdown,不確定內文是否還能使用,請閱讀者再自行斟酌一下(畢竟也是2012時發的文了)。


好久沒有更新了,看到上次更新的日期,我覺得我的文章產出可能會是一年一篇至兩篇(崩潰)

這次碰上的問題主要是有個網頁委託內容包含需動態產生word檔以供使用者下載,因此我從先前接觸過的 PHPExcel 而找到 PHPWord 這套 library。先前使用 PHPExcel 時多半是為了利用簡單的方式來讀取 excel 檔中的內容,而並未自行產出一個獨立的 excel 檔,而這次算是剛好反過來了,要利用已有的內容來產生一個 word 檔。

起初利用 PHPWord 0.6.2-1 Beta 所提供的 examples 成功建立 word 檔後覺得還蠻放心的,後來隨即也想到若是包含中文字時會不會有編碼問題,因此測了一下,果然變成亂碼了。後來隨即在官方討論區找到此篇文章:how to properly handle UTF-8 ? (UTF8),原來不只我遇到了這個問題~而根據底下的回覆表示以簡體中文編碼為例,須將 source code 中的 $givenText = utf8_encode($text); 註解並換成 $text = iconv('gbk','utf-8','福建省泉州市惠南工业区北一路'); 即可解決問題,但這個方法只適用於範例中的 Template.php 才行,用在 Basic Table.php 則會出現亂碼。

1
2
// 原始寫法
$givenText = utf8_encode($text);
1
2
3
// 修改後
//$givenText = utf8_encode($text);
$text = iconv('gbk','utf-8','福建省泉州市惠南工业区北一路');

後來我將 source code 中所有包含 utf8_encode 的文件都以 iconv 的方式去取代,結果算是成功沒出現亂碼了,但是 word 會在開啟時跳出錯誤警告,表示此檔案已損毀是否要進行修復(修復後沒大礙就是了)。憑著吹毛求疵斤斤計較的精神,也擔心使用者若看到損毀時會有疑慮,因此我又開始找其他方式去做,而最後找到的方法就是直接將有包含 utf8_encode 的文件都註解掉,改成直接賦予對應值,如下範例:

1
2
// 原始寫法
$givenText = utf8_encode($text);
1
2
3
// 修改後
//$givenText = utf8_encode($text);
$givenText = $text;

另外範例中用來產生 word 的 PHP 也記得先改成 utf-8,之後即可輕鬆產生包含中文內容的 docx 檔~


至於該如何直接產生word供使用者一點擊就下載,方法我也都找到了,只是 Template 在 PHPWord 中的寫法與其他比較不一樣,因此方法也有被侷限住,一般的如 Basic Table 可以用以下方法來達到不存檔而下載,只要在最後 Save File 部分動一點手腳即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Save File
$objWriter = PHPWord_IOFactory::createWriter($PHPWord, 'Word2007');
// 原本是儲存到當前目錄下,名為BasicTable.docx
// $objWriter->save('BasicTable.docx');

// 先寫入header,再利用$objWriter->save('php://output');
// 將結果直接show出來,即可成功進行下載動作
$filename = 'abc.docx';
header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-Disposition: attachment; filename=$filename");
header("Content-Type: application/octet-stream; ");
header("Content-Transfer-Encoding: binary");
$objWriter->save('php://output');

而 Template 無法使用上述方法,須使用下面範例才行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Save File
// 原本是儲存到當前目錄下,名為Solarsystem.docx
// $document->save('Solarsystem.docx');

// 先將檔案暫存起來
$temp_file = tempnam(sys_get_temp_dir(), 'PHPWord');
$document->save($temp_file);

// 一樣是寫入header,但最後以不同指令呈現出來,最後再刪掉伺服器上的暫存檔
$objWriter = PHPWord_IOFactory::createWriter($PHPWord, 'Word2007');
$filename = 'abc.docx';
header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-Disposition: attachment; filename=$filename");
header("Content-Type: application/octet-stream; ");
header("Content-Transfer-Encoding: binary");
readfile($temp_file); // or echo file_get_contents($temp_file);
unlink($temp_file); // remove temp file

到這裡我所需要的功能大致上齊全了,只剩下 PHPWord 沒辦法產生 word2003(*.doc) 的檔案,也是一個令人頭痛的問題。