從某一個應用系統 (或資料庫系統)要把資料轉移到另一個應用系統 (或資料庫系統),這個轉移的過程稱之為 “Migration” (相對地,若是同一系統的版本更新,則稱之為 Upgrade)。
上一個月,要從原來 b2evolution Blog 系統轉移 (migrate)到 wordpress 系統時,就如同當初我從 MT 轉移到 b2evolution 一樣,官方的系統均有提供 import (載入) 的 script 檔。 只是, MT 轉 b2evolution 時,當時因為資料結構比較單純,所以完全不用作任何修正,透過 import 工具程式一下子就轉好了;但是,這兩三年來, b2evolution 系統相當積極,版本更新極快,而每一次大版本的更新,必然會變動到 DB Schema,這使得 wordpress 所提供可以 import from b2evolution 的 script 檔,不可能不作修改就可以順利完成轉移作業。
wordpress 官方所提供對各大 Blog 系統的 import script 檔在此:Importing Content。 不過,我真的挺懶,想說若能找到無痛地可以從我現在 b2 1.10.3 版本 → wordpress 2.71 的網友加持修改版,那就更省事了。 結果呢,花了大半天的時間,是有找到啦,像這篇: Import From B2evolution(2.4.6) to WordPress (2.7)。 只是,要收費耶!! 含售後客服要價 50 USD。 其實啊,這價錢是算便宜的啦,當初我擔任某家公司的 Oracle DBA 時,幫企業客戶作轉檔的話,起碼是 10 數倍以上收費起跳的呢。
不過,想想還是乾脆又自己來動手 DIY 好了。 其實轉檔的原理相當簡單,就是把 Source Schema 對應 (mapping) 到 Target Schema 就對了。 b2evolution 有 31 個表格;wordpress 則只有 10 個 (非常欣賞它的結構簡潔)! 怎麼會差這麼多? 主因就是前者是被設計來服務多個部落格用戶的,而後者僅設定單一的部落格,所以前者儲存了非常多的統計數據,而這些,其實是不用轉移的。
轉移的核心會是什麼? 當然就是本文內容了。 本文就是包含 Post 內文 (content)與迴響 (comment),一篇內文會包含 0 到 多篇迴響,非常典型的 “Master-Detail” 表格結構,一般就是會有兩個表格個別儲存並以 Key (primary-key to foreign key)來關聯了。 所以只要找出含有 “_post” 字串的表格,大部份就是儲存 Post 內文的主表了。 再以此為中心,把一些如類別 (category)、帳號等額外資訊,給一一的加進來即可。
轉檔的寫法百百種,可以利用 stored-procedure、利用 C#/VB.NET、PHP、Perl 等機制來轉寫轉檔程序。 事實上,對資料結構與 SQL 語法精通的真正高手來說,只要寫個 SQL 陳述 (statement)甚至就可以轉移了。 嗯,我那個 partner Ringle 就是精通此道,他對資料庫的掌握度,我仍是沒有看到一個比他更強悍的。 可惜,他可不幫我作這種小事,而他收費可更是不便宜。 所以呢,我還是又繞回來,下載回來 wordpress 官方的 import script 程式,當然是利用 php 撰寫的,再以此作為轉移程式的 “骨架 (skeleton)”,然後慢慢地調整 SQL 陳述,修改表格與欄位、資料型態等。 呵呵,比想像得簡單,我採用漸進調整的方式作業,大概反覆轉移了有四、五次之多,至最終轉到我滿意正確的結果,大約也才耗上半天的時間而已。
其實,我並沒有把類別轉過來,原因是我覺得原來我規劃的分類太過繁雜了,所以我想乾脆重新針對每一篇文章來指定類別,同時,也來設定 wordpress 相當流行的 標籤 (Tag)。 所以,每一篇轉移過來的文章都是 “未分類”,然後呢,我又再花上了有近兩天的時間,共有近1000 篇,然後一篇一篇地重新指定類別與標籤,順便再對較舊的文章重新排版。 這些可都是 “硬功夫”,沒有訣竅可以幫你的。
我 “客製化” 化過的轉檔 PHP script,這裡就同時作個保留紀念,如果有興趣的讀者們也可以下載參考 (下載回來後把後面的 .txt 改為 .php 即可): migrate_b2-to-wp.txt。
上述所提真正的核心程式碼,其實是下面這一段程式碼。 有其它 Blog 系統想轉移至 wordpress 的讀者們,多少可以作個參考。
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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | // get entries for blog echo "Importing Entry records ... <BR />"; $sql = "SELECT * FROM evo_posts"; $result = mysql_query($sql,$resB2) or die("Invalid query: " . mysql_error() . "<BR /> SQL : " . $sql); if ($result) { $cnt = 0; $cntCom = 0; $cntCat = 0; while ($row = mysql_fetch_array($result, MYSQL_ASSOC)) { // author ID must be switched to new author ID //$aid = $arUser[$row['post_author']]; $aid = 1; //Set to Admin User. // category ID must be switched to new category ID //$cid = $arCat[$row['post_category']]; //if (!$cid) { // $cid = '1'; //} $cid = 0; //設定為 "未分類" // status mapping $stat = $row['post_status']; if ($stat == 'published') { $stat = 'publish'; } else if ($stat == 'deprecated') { $stat = 'publish'; } else if ($stat == 'protected') { $stat = 'private'; } else if ($stat == 'private') { $stat = 'private'; } else if ($stat == 'draft') { $stat = 'draft'; } // update urls in the post content $post_content = $row['post_content']; $post_content = str_replace($filepath_b2, $filepath_wp, $post_content); $sql = trim(str_replace("\n","","INSERT INTO `". $wp_pref ."posts` ". "(`post_author`,`post_date`,`post_content`,`post_title`,`post_category`,`post_status`)" . " VALUES ('".$aid."','".$row['post_issue_date']."','".mysql_escape_string($post_content)."','" . mysql_escape_string($row['post_title'])."','".$cid."','".$stat."');")); echo "$sql <br />"; $q = mysql_query($sql, $resWP) or die("Invalid query: " . mysql_error() . "<BR /> SQL : " . $sql); $id = mysql_insert_id($resWP); $eid = $row['ID']; $cnt = $cnt + 1; // get comments for entry $sql = "SELECT * FROM evo_comments WHERE comment_post_ID=" . $eid; $res = mysql_query($sql, $resB2) or die("Invalid query: " . mysql_error() . "<BR /> SQL : " . $sql); if ($res) { while ($row = mysql_fetch_array($res, MYSQL_ASSOC)) { $sql = trim(str_replace("\n","","INSERT INTO `". $wp_pref ."comments` ". "(`comment_post_ID`,`comment_author`,`comment_author_email`," . "`comment_author_url`,`comment_author_IP`,`comment_date`," . "`comment_content`,`comment_karma`)" . " VALUES ('".$id."','" . mysql_escape_string($row['comment_author']) . "','" . mysql_escape_string($row['comment_author_email']) . "','" . mysql_escape_string($row['comment_author_url']) . "','" . $row['comment_author_IP'] . "','" . $row['comment_date'] . "','" . mysql_escape_string($row['comment_content'])."','". $row['comment_karma'] ."');")); $q = mysql_query($sql, $resWP) or die("Invalid query: " . mysql_error() . "<BR /> SQL : " . $sql); $cntCom = $cntCom + 1; } } // get categories for entry $cntTmp = 0; $sql = "SELECT * FROM evo_postcats WHERE postcat_post_ID=" . $eid; $res = mysql_query($sql, $resB2) or die("Invalid query: " . mysql_error() . "<BR /> SQL : " . $sql); if ($res) { while ($row = mysql_fetch_array($res, MYSQL_ASSOC)) { $cid = $arCat[$row['postcat_cat_ID']]; $sql = trim(str_replace("\n","","INSERT INTO `". $wp_pref ."post2cat` ". "(`post_id`,`category_id`)" . " VALUES ('" . $id . "','" . $cid . "');")); $q = mysql_query($sql, $resWP) or die("Invalid query: " . mysql_error() . "<BR /> SQL : " . $sql); $cntCat = $cntCat + 1; $cntTmp = $cntTmp + 1; } } if ($cntTmp == 0) { // No categories defined in b2evo - put it in the default category $sql = trim(str_replace("\n","","INSERT INTO `". $wp_pref ."post2cat` ". "(`post_id`,`category_id`)" . " VALUES ('" . $id . "','1');")); $q = mysql_query($sql, $resWP) or die("Invalid query: " . mysql_error() . "<BR /> SQL : " . $sql); $cntCat = $cntCat + 1; } } echo $cnt . " Entry record(s) imported! <BR />"; echo " " . $cntCom . "Comment record(s) imported! <BR />"; echo " " . $cntCat . "Entry Category record(s) imported! <BR />"; } else { echo "No Entry records found!<BR />"; } mysql_close($resB2); mysql_close($resWP); echo "That's all folks!"; break; |