2013年6月27日 星期四

利用 GAE 與 Gmail 傳送電子郵件

由於 Appfog 似乎無法傳送信件 (老實說是我還不清楚), 而一般的免費 PHP 主機如果傳送太多郵件可能會被 suspend 帳號 (例如累積送出 5000 封信), 只有 GAE 不會搞這樣的不明不白停止人家帳號的小伎倆. 它的收費政策寫得很清楚, 祇要不超過免費界線, 定時寄送郵件的功能非常穩定. 以下說明如何利用 GAE 寄信, 細節請參考 :
GAE 是利用 mail 模組來寄信的, 因此使用前必須匯入 mail :

from google.appengine.api import mail

GAE 的 mail API 提供兩種寄送 E-mail 郵件的方式 :

  1. 直接呼叫 mail.send_mail() 方法 :
    這是最簡單的方式, 只要有寄件者, 收件者, 主旨與內容即可寄信, 當不需要用到副本 cc, 密件副本 bcc , 與附件時適用 :

    sender="abc123@gmail.com"
    subject="mail test from abc123@gmail.com at GAE"
    to="xyz@yahoo.com"
    body="Hello! World!"
    mail.send_mail(sender, to, subject, body)

    如果來自網頁以 POST 方式傳送, 可用例如 self.request.get("subject") 來取得. 由於主旨 subject 與內容 body 可能含有中文, 所以須加上 encode("utf-8") 以免亂碼 :

    body=self.request.get("content").encode("utf-8")
  2. 使用 mail.EmailMessage 物件 :
    此物件可指定副本 (Cc) 與密件副本 (Bcc) 收件人, 也可以傳送附件檔案. 注意, 收件人 to 與 cc, bcc 可以寄給不只一個對象, 只要用逗號或分號隔開即可 :

    msg=mail.EmailMessage()
    msg.sender="abc123@gmail.com"
    msg.subject="mail test from abc123@gmail.com at GAE"
    msg.to="xyz@yahoo.com, tuv@gmail.com"
    msg.cc="abc@yahoo.com, kkk@gmail.com"
    msg.bcc="nono@yahoo.com, ppp@gmail.com"
    msg.html="<b>Hello! World!</b>"

2013年6月22日 星期六

AmyPHP 更新使用者的 BUG

今天又發現了 AmyPHP 一個嚴重 BUG, 中午吃完香 Q 麵回來, 用 iPad 上 stockbot 網站看看執行狀況, 卡好, superuser 竟然無法登入! 這是怎麼回事? 回想了一下出去吃飯前我改了 superuser 的資料, 該不會是 update user 時把密碼也改了吧? 果然, 檢查 sys.php 的 update_users 模組, 嘿嘿, 當初把 add_users 的程式複製過來改時, 忘記把 md5 拿掉, 所以原本的 md5 再 md5 一次存進去, 當然密碼也被改啦! 正確作法應該先檢查使用者是否有更改密碼, 沒改就原來的 md5 密碼存進去, 有改就必須 md5 加密 :

              $id=$_REQUEST["id"];
              //先檢查密碼是否有更改
              $RS=search("users","id",$id);
              if (is_array($RS)) { //有找到
                  if ($_REQUEST["password"]==$RS[0]["password"]) { //沒改
                      $data_array["password"]=$_REQUEST["password"];
                      } //end of if
                  else { //有改
                      $data_array["password"]=md5($_REQUEST["password"]);
                      } //end of else
                  } //end of if

AmyPHP 微框架修改安裝程式列表

今天發現 AmyPHP 管理頁面的應用程式列表向右撐破了 jQuery UI tabs 的框框, 非常的不協調, 這在一般表格也會有此現象, 例如使用 Javascript 的 join() 方法將陣列 tabs 組合成陣列時, 預設是用逗號 "," 串接各元素 :

var table_names=tabs.join(); //預設以 "," 串接
alert table_names;

這樣會得到類似下列這樣毫無空隙的字串 (沒有空格) :

snowball,z0000,z0050,international,inventory,tracking,report,compound_annuity,cron,stock_settings

這個字串如果放在表格中, 就會向右撐破表格的寬度, 破壞整個頁面的規劃. 如果這字串的逗號後面有一個空格, 那就不一樣了 :

snowball, z0000, z0050, international, inventory, tracking, report, compound_annuity, cron, stock_settings

這個字串會被表格的 td 寬度限制, 到右邊界時會折到下一行顯示, 因此不會破壞頁面. 所以 join 時要用 ", " :

var table_names=tabs.join(", ");  //逗號後面加一個空格

同樣的, 在 PHP 用 join() 串接陣列元素時, 也要用這樣的方式為宜 :

$table_name=join(", ", $tabs); //逗號後面加一個空格

而 AmyPHP 需要修改的地方有兩個, 一是 apps/app_install.php 結尾處, 把 tab_names 與 table_names 之串接多加一個空格 :

/*--- 以下是系統作業, 不可修改刪除 ---*/
//更新系統 apps 資料表的 app_name='app' 欄位
$data_array["installed"]="Y";                            //已安裝 "Y"/"N"
$data_array["show_tabs"]="Y";                        //顯示頁籤 "Y"/"N"
$data_array["tab_names"]=join(", ",$tabs);      //空格是為了避免撐破表格
$data_array["table_names"]=join(", ",$tables); //空格是為了避免撐破表格
$data_array["remark"]="OK";                           //安裝結果

二是 sys.php 的 case "remove_app" 模組, 要刪除應用程式 (解除安裝) 時, 須將該應用程式所安裝的 tab 移除, 也要將相關資料表移除, 這時就要從 apps 資料表中讀出這些資料表與頁籤名稱字串, 用 explode 分拆時也要配合以逗號與空格為界 :

//分隔字元為 ", ", 其中空格是為了避免顯示時撐破表格
$tab_names=explode(", ",$RS[0]["tab_names"]); //空格
$table_names=explode(", ",$RS[0]["table_names"]);

判斷 MySQL 是否找到紀錄的方法

在 mysql.php 函式庫中, 可以用 search() 或 run_sql() 函式去資料表撈資料, 例如 :

$SQL="SELECT * FROM `report` WHERE `date_time` LIKE '".$today."%'";
$RS=run_sql($SQL);

或者 :

$RS=search("apps","app_name",$app_name);

這些都會呼叫 PHP 原生的 mysql_query() 函式執行 SQL 指令, 然後以 mysql_numrows()  函式取得資料筆數, 作為迴圈的終值, 在每個迴圈內呼叫 mysql_fetch_array() 取得每筆資料的一維關聯式陣列 (用欄位名稱當索引), 再把此陣列存入一個索引陣列中.

         $result=mysql_query($SQL, $conn); //執行 SQL 指令
         for ($i=0; $i<mysql_numrows($result); $i++) {
               $RS[$i]=mysql_fetch_array($result);
               } //end of for
         if (mysql_error($conn)) {return $result;}
         else {return $RS;}

所以如果有找到一筆紀錄, 則最後傳回來的是一個二維陣列, 例如第一筆的 id 欄位為 $RS[0]["id"]. 如果沒找到任何紀錄的話, 預設傳回 FALSE (即 mysql_query 的傳回值), 除錯模式則會傳回錯誤原因字串, 例如 "MySQL SELECT Error : ......".
所以, 要如何判斷有沒有撈到資料呢? 有兩個辦法 :
  1. 用 is_array() :
    if (is_array($RS)) {....}
    else {....}
  2. 用 sizeof() :
    if (sizeof($RS)!=0) {...}
    else {...}
如果有找到紀錄, 則傳回值 $RS 必定是二維陣列, is_array() 就傳回 TRUE, 而 sizeof() 傳回撈到的資料筆數 (所以要計算筆數除了用 count($RS) 外, 也可以用 sizeof($RS)).

2013年6月21日 星期五

PHP 從日期字串求星期幾的方法

從資料庫中的日期時間欄位 (格式是 YYYY-MM-DD HH:mm:SS) 取得資料後, 想要從日期計算當天是星期幾, 這樣顯示在網頁中會比較 user-friendly, 作法是利用 date() 函式的 "w" 參數與該日任意時間之時戳 (time stamp), 詳細如下 :

$week=Array("日","一","二","三","四","五","六");
$date_time="2013-06-21 15:05:24";
list($date)=explode(" ", $date_time); //取出日期部份
list($Y,$M,$D)=explode("-",$date); //分離出年月日以便製作時戳
echo $date."  (星期".$week[date("w", mktime(0,0,0,$M,$D,$Y))].")";

這樣便會顯示 2013-06-21 (星期五) 了.

jQuery 的 val() 設值問題 (setter)

今天在用 jQuery 的 val() 設定 test_date 這個  text input 元素的值時, 發現此函數會計值, 如果要設定文字進去, 務必左右加上引號, 例如 test_date 是一個日期欄位, 所以從資料庫讀出來時是 2013-06-21 這樣的值, 原先我是這樣設值的 :

$("#test_date").val(2013-06-21);

結果在文字欄位中顯示的是 1986, 因為 val() 把 2013-06-21 看成是兩次減法, 所以 2013 減掉 27 後得 1986, 正確用法是用字串 :

$("#test_date").val("2013-06-21");

專案中的實際例子是從資料庫中讀出 test_date 欄位 :

$test_date=$RS[0]["test_date"];

然後在 heredoc 中把此值放入 val() 中 :

$("#test_date").val("{$test_date}"); //設定初始值 (正確方法)

原先錯誤的方式是 :

$("#test_date").val({$test_date}); //設定初始值 (錯誤方法)

我搞清楚為什麼會是 1986 後, 原先以為只要把 test_date 轉成字串就可以, 於是參考 Jollen's PHP 的 "如何做 PHP 的型別轉換?", 用 (string) 去把 $test_date 轉成字串, 但發現這是沒用的 :

$test_date=(string)$RS[0]["test_date"];

$("#test_date").val({$test_date}); //設定初始值 (錯誤方法)

其實只要像上面說的那樣, 左右用引號括起來就可以了.

更新 jqueryui.php 函式庫的 get_datepicker 函式

為 stock_settings 添加 test_date 欄位時發現 get_datepicker() 函式中的 input 元素忘了給 name 屬性, 這樣表單傳送時將無法傳出 test_date 變數, 故加以修改如下 :
function get_datepicker($id, $change_YM=false, $show_week=false, $locale="zh-TW") {
  .....
  $opt="{".join(",", $opt)."}";
  $result="<input id='".$id."' name='".$id."'>\n".
              "<script>$('#".$id."').datepicker(".$opt.");\n</script>\n";
  return $result;
  }

create_report.php 的測試模式

昨天對資料庫做了一項重大變更, stock_settings 資料表加入 test_mode 欄位, 這樣要做測試時只要線上修改, 不需要改程式 :

//讀取 stock_settings (測試用)
$RS=search("stock_settings");
$test_mode=$RS[0]["test_mode"];   //"Y"=測試模式
$test_date=$RS[0]["test_date"];       //"Y"=測試日期
//製作今日日期
$today=date("Y-m-d"); //2011-12-10
if ($test_mode=="Y") { //測試模式時
    $YMD=explode("-", $test_date);
    $test_date=mktime(0,0,0,$YMD[1],$YMD[2],$YMD[0]); //時分秒月日年
    $today=date("Y-m-d",$test_date); //覆蓋 $today, 擷取測試日期之報告
    } //end of if
//讀取 report 資料表, 判斷本日是否已有報告
$SQL="SELECT * FROM `report` WHERE `date_time` LIKE '".$today."%'";
$RS=run_sql($SQL);
if ($test_mode=="Y") {$RS="";} //測試模式時以字串覆蓋陣列,允許重複產生報告
if (is_array($RS)) {echo "本日已有報告";} //工作模式,不允許重複產生報告
else { //本日未有報告, 進行擷取
        ...
       }

2013-06-22 註 :
後來在之前寫的程式發現用 list 更簡潔 :

if ($test_mode=="Y") { //測試模式時
    list($Y,$M,$D)=explode("-", $test_date); //分出年月日
    $test_date=mktime(0,0,0,$M,$D,$Y); //時分秒月日年
    $today=date("Y-m-d",$test_date); //覆蓋 $today, 擷取測試日期之報告
    } //end of if

2013年6月20日 星期四

網頁資料擷取易容術

以網頁程式定期抓取網站資料時, 最忌諱被誤認為是駭客攻擊, 導致被拒絕存取, 而 PHP/cURL 可以使用易容術來避免誤會, 方法是在 cURL 的選項設定 setopt 中, 把 USERAGENT 設為一般常見瀏覽器, 例如以 Chrome 而言 :

$UA="Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36(KHTML, like Gecko) ".
        "Chrome/27.0.1453.110 Safari/537.36";

這樣在 PHP 擷取程式中, 以這個 $UA 作為 cURL 之 CURLOPT_USERAGENT 值, 就可以讓程式看起來比較像是用一般瀏覽器在下載檔案了 :

curl_setopt($ch, CURLOPT_USERAGENT, $UA);

當然, 光是這樣並沒有辦法完全避免誤會你是用程式抓檔案, 固定的來源 IP 與下載週期也很容易被識破. 當然, 來源 IP 是沒辦法改的, 而用 cron table 觸發也會導致固定的下載頻率. 不過這個可以用隨機的 sleep() 來修飾, 亦即在進行擷取前, 先隨機休眠一段時間  :

sleep(rand(5, 50));  //休息 5~50 秒再進行擷取

這樣在伺服端看來就比較不那麼突兀了.

2013年6月19日 星期三

Appfog 的 PHP 以 ini_set() 設定時區無效問題

今天下午把 stockbot 專案部署到 Appfog, 晚上檢查 cron log 發現時間都不對, 差了 8 個小時, 顯然還是主機所在位置 (愛爾蘭) 的時間. 我是用這個指令來指定為台北時間 :

ini_set('date.timezone','ASIA/Taipei')

這在別的 PHP Hosting 似乎沒問題, 但到 Appfog 就有問題, 可見 Appfog 無法更改 php.ini 內容. 還是用 date_default_timezone_set() 比較保險. 所以我把每一個 cron 程式頭的 ini_set 都改成如下 :

#設定時區以使 cron_log 紀錄台灣時間
@ini_set('date.timezone','ASIA/Taipei');  //不一定有效
@ini_set("max_execution_time","120");     //不一定有效
date_default_timezone_set("Asia/Taipei");
echo date_default_timezone_get()."<br>";

重新發佈之後, cron log 資料表所記錄的就是台灣時間了.
另外, 同時也發現一個問題, 也就是若 stock_list 資料表是空的 (也就是還沒執行過 fetch_twse_daily_close.php), 那麼去抓 Yahoo 資料的 cron 程式也會做白工, 因為每一次從 stock_list 抓幾筆 stock_id, 便去擷取相應網頁來分析, 如果 stock_list 是空的, 那麼迴圈也就不會跑. 所以, 相關的 2 個 cron 程式要做些修改 :

$SQL="SELECT `stock_id` FROM `stocks_list` WHERE NOT `stock_id` LIKE '0%'";
$RS=run_sql($SQL);
if (count($RS)==0) {
    header('Location: fetch_twse_daily_close.php');
    } //end of if

這樣一來, 只要是交易日, 只有第一個 cron 程式會失敗, 但它會觸發 twse 的擷取動作, 填滿 stock_list 資料表, 第二個執行的 cron 程式就會成功了. 但若系統啟動時為非交易日, 則要到下一個交易日收盤資料可以抓到時才會真正抓到資料, 在此之前全部都是做白工.

2013年6月16日 星期日

學樂樂考古題測驗網站

今天在找 C 語言資料時看到這個網站, 提供一個平台可以讓註冊使用者上傳考古題測驗卷, 讓大家都可以自我測驗, 我覺得立意相當好, 網址為 :
http://xuelele.com.tw/

2013年6月15日 星期六

張智星老師的實驗室工作進度管理系統

前陣子想起以前在張智星老師的網站上看過其進度管理系統, 看來是用來給研究生每週填進度, 管理各項研究用的, 我覺得很實用, 但是忘了網址, 到實驗室首頁又找不到 ENTRY, 今天搜尋 "張智星 實驗室 進度"一下子就找到了, 在這裡 :

http://neural.cs.nthu.edu.tw/jang/mir/weeklyReport/default.asp

這是用 ASP 寫的, 簡單實用, 還大方把整個系統讓人下載.

以前買過張老師寫的書 "JavaScript 程式設計與應用", 是我看過 Javascript 中文書裡寫得最實用的, 據推薦序說在清大是橫跨各科系非常熱門的課, 而在公司抓系統資料使用的最多的 WSH 也是從這本書學來的.

2013年6月14日 星期五

下載備份 Google App Engine 應用程式檔案的方法

兩年前玩 GAE 時寫的應用程式原始檔案已經不知放到哪兒了, 取得原始碼的唯一辦法便是從 GAE 下載. 參考了 stackoverflow 這篇 "how to download google appengine (uploaded)application files", 原來只要一道指令就搞定了 :

appcfg.py download_app -A yourAppName -V 1 thePath 

其中, yourAppName 是 App 名稱, thePath 是要放下載檔案之路徑. 只要有安裝 Python 與 GAE SDK, 打開 DOS 視窗, 輸入上面指令就 OK 啦. 例如我的 App 名稱為 gaetrader, 下載檔案要放在 D:\gae\gaetrader 下 (不可事先建立該目錄), 那就用 :

D:\>appcfg.py download_app -A gaetrader -V 1 d:\gae\gaetrader
10:57 PM Host: appengine.google.com
10:57 PM Fetching file list...
2013-06-14 22:57:47,530 WARNING appengine_rpc.py:547 ssl module not found.
Without the ssl module, the identity of the remote host cannot be verified, and
connections may NOT be secure. To fix this, please install the ssl module from
http://pypi.python.org/pypi/ssl .
To learn more, see https://developers.google.com/appengine/kb/general#rpcssl
Email: youraccount@gmail.com
Password for youraccount@gmail.com: *****
10:58 PM Fetching files...
10:58 PM [1/28] cron.yaml
10:58 PM [2/28] index.yaml
10:58 PM [3/28] app.yaml
10:58 PM [4/28] main.py
10:58 PM [5/28] css/jquery-ui-1.8.12.custom.css
.....
這表示此 App 有 28 個檔案, 會依序下載, 注意, 存放下載檔案的目錄不要先建立喔, 下載程式會自動建立, 如果該目錄已存在, 就會出現下列錯誤訊息 :

appcfg.py: error: Cannot download to path "d:\gae\gaetrader": directory already
exists and it isn't empty.

另外要特別注意, 如果你有兩個 gmail 帳戶, 而且會在兩個帳號之間切換, 那麼使用上面的指令後, 帳號會儲存在 Cookie 中, 你每次打開 DOS 畫面就自動進入該帳號, 這時你要下載另一個帳號的 App 時, 輸入上面的指令時就不會再問你的 email 與 password, 而且會說你的 App 不存在 (帳號不同, 當然找不到 App 啊) :

12:06 AM Host: appengine.google.com
12:06 AM Fetching file list...
Error 403: --- begin server output ---
You do not have permission to modify this app (app_id=u'mybidbot').
--- end server output ---

這時你會想找 appcfg 的 logout 參數吧! 很抱歉, 沒有這樣的參數. 這該怎麼辦? 其實這要用 --no_cookies 參數, 例如現在要改下載第二個帳號下的 mybidbot 應用程式, 要輸入如下指令, 這樣就會詢問 email 與 password 了  :

appcfg.py --no_cookies download_app -A mybidbot -V 1 d:\gae\mybidbot

D:\GAE>appcfg.py --no_cookies download_app -A mybidbot -V 1 d:\gae\mybidbot
12:00 AM Host: appengine.google.com
12:00 AM Fetching file list...
2013-06-15 00:00:26,187 WARNING appengine_rpc.py:547 ssl module not found.
Without the ssl module, the identity of the remote host cannot be verified, and
connections may NOT be secure. To fix this, please install the ssl module from
http://pypi.python.org/pypi/ssl .
To learn more, see https://developers.google.com/appengine/kb/general#rpcssl
Email: your2ndaccount@gmail.com
Password for your2ndaccount@gmail.com: ******
12:01 AM Fetching files...
12:01 AM [1/5] app.yaml
12:01 AM [2/5] main.py
12:01 AM [3/5] index.yaml
12:01 AM [4/5] cron.yaml
12:01 AM [5/5] favicon.ico

以上談的是下載, 如果是上傳呢? 除了用 GAE SDK 的上傳按鈕外, 也可以直接用 appcfg update 指令  :

appcfg.py --no_cookies update mybidbot

二哥畢業典禮

今天早上請假參加二哥小學畢業典禮。好快,六年好像一眨眼啊,牽著他的手入學似乎才不過是昨天的事呢,如今已長得比我高了。菁菁昨晚科見下課時還特地拉我去九乘九買禮物。

2013年6月13日 星期四

好書:簡報改造王

在市圖借了這本 "簡報改造王:8個原則讓你自信滿滿上台去", Robin Williams 著, 周欣欣譯, 旗標出版. 這本是我看過寫得最好的簡報相關書籍, 當然原著內容好, 也要翻譯者文筆精煉才能相得益彰. 作者提出的 8 個原則是 : 明確, 關聯, 動畫, 情節, 對比, 重複, 對齊, 相近. 以下是我的心得摘要 :
  1. 明確 :
    刪除多餘的字句, 只留下關鍵字句. 字越少, 字就可以越大, 設計空間也越多.
    投影片上的關鍵字句要簡潔有力 (要用積極的語氣才會簡潔), 觀眾才能迅速抓到重點.
    不要把講稿放在投影片上, 因為那樣就不需要你來做簡報了.
    不要看著投影片照本宣科念稿, 演講的你才是這場簡報的靈魂人物.
    唯一需要照本念科的是投影片中的引述文句.
    為坐在後方看不清楚投影片字句的聽者朗誦內容是貼心的服務.
    別讓你自己被投影片中的文字取代, 而讓自己在現場顯得多餘.
    英文簡報中要避免使用動名詞, 因為語氣比較被動與弱勢.
    不要在一張投影片上塞進太多圖表, 即使它們相關聯, 最好分成數張投影片 (投影片是免費的).
    簡報內容要針對特定觀眾做取捨, 那些會轉移焦點的東西通通刪掉.
  2. 關聯 :
    不要在每張投影片放上商標, 那只是混亂觀眾的無意義圖形. 商標應該放在講義裡.
    拿掉一切不相關的美工圖案, 線條, 與背景圖, 不相關的圖片只會誤導觀眾的思考, 使簡報失焦. 留白真的沒有關係.
    觀眾的視覺印象大都來自背景圖片, 所以要慎選, 必須與簡報有關聯性, 要對提升主題有幫助, 而非讓人感到困惑.
  3. 動畫 :
    只有在需要引起觀眾注目 (例如轉換主題時) 或釐清某個觀念時, 才需要使用動畫或轉場特效, 而且這時要停止說話, 讓觀眾好好欣賞.
    使用動畫效果要有個好理由, 否則不要畫蛇添足.
  4. 情節 :
    簡報開頭要告訴觀眾接下來你將帶他們到哪裡去, 要有一個概要介紹.
    並非所有的簡報都要加上圖片, 只用文字或只用圖片都行. 但適度搭配文字與圖片, 會有較好的滲透力 (文字是資料, 圖片是情感). 如果圖片可以協助釐清觀點, 當然要加上圖片甚至動畫.
    雖然故事可以吸引觀眾的注意力, 但不要硬塞一個人性化的故事到無關聯的簡報主題上. 故事並非每份簡報的救星, 必須要與主題相契合.
    要讓觀眾知道你的簡報何時會結束, 例如快結尾時說 : "最後我再補充一點", "在結束以前我要再強調" 等等, 讓觀眾有心理準備 (例如發問). 也要有一張 "Thank you" 投影片.
  5. 對比 :
    設計的本質就是要引人注目.
    製造對比效果最簡單的是利用字型與色彩, 特別是標題.
    對比的重點是要能夠明確傳達資訊.
  6. 重複 :
    重複最簡單的形式是一致性, 但這並不是說每張投影片樣式都要一模一樣, 而是要同中求異.
  7. 對齊 :
    投影片上沒有任何一樣東西可以任意放置, 要確定每項元件都是對齊的.
    對齊的目的是要表達關聯性.
  8. 相近 :
    將相關的東西在位置上組合在一起, 可以讓觀眾從視覺上察覺其相關性.
    元件之間的距離越近, 它們就越相關.
    留白不是罪惡, 它也是重要的畫面元素. 但不需要擔心該在哪裡留白, 只要遵循原則, 它會自然出現.
另外, 軟體使用方面, 作者認為必須關掉 Power Point 的自動調整功能 (在 "格式化圖案/文字方塊/自動調整" 勾選 "不自動調整"), 因為它會造成不一致的外觀. 其次, 簡報時除了投影片外應該準備講義, 而且要在演講前發給觀眾. 簡報一方面是提供與投影片內容相關之詳細資料, 另外也可以讓觀眾做筆記. 而 SOP 之類的程序, 聯絡資訊, 網址, 資料來源等應該放在講義中, 因為觀眾不太可能抄寫完整. 作者也提醒, 簡報者應該看自己筆電上的投影片, 而非螢幕上的, 因為這樣就會背對著觀眾.
以上只是理論概念, 書上有舉出實際簡報範例, 書末還有一個實例小測驗, 非常值得一看.

MySQL 模擬 SELECT TOP 20 PERCENT * 指令的方法

ACCESS 或 SQL Server 資料庫提供的 TOP n 與 TOP n PERCENT 功能非常方便 (雖然他們都不是標準 SQL 指令) :

  1. 選取資料表的前 100 筆紀錄 : SELECT TOP 100 * FROM users
  2. 選取資料表的前 20% 筆紀錄 : SELECT TOP 20 PERCENT * FROM users

但是 MySQL 卻沒有此功能, 那該怎麼做呢?

TOP n 功能在 MySQL 要改用 LIMIT n, 例如 :

$SQL="SELECT * FROM users WHERE gender='F' ORDER BY age LIMIT 100"

但是 TOP n PERCENT 就稍微麻煩一點, 需要兩個步驟利用 LIMIT n 來模擬, 首先用 SELECT COUNT 求出全部紀錄列數, 再乘以百分比得到筆數, 然後就可以用 LIMIT n 去抓出所需的紀錄 :

$SQL="SELECT COUNT(*) FROM users"; //查詢全部記錄筆數
$RS=run_sql($SQL); //執行 SQL 操作
$percent=20; //列出前百分之 20 紀錄
if (is_array($RS)) {
    $total=$RS[0][0]; //總筆數
    $limit=round($percent*$total/100); //將百分比換算成筆數
    $SQL="SELECT * FROM users LIMIT ".$limit;
    $RS=run_sql($SQL);
    ...
    }
上面的自訂函式 run_sql 會呼叫 PHP 的 mysql_query(), 然後用迴圈以 mysql_fetch_array() 讀取後放入陣列中傳回, 如果是執行 SELECT COUNT 指令, 那麼總記錄筆數會放在 [0][0] 中.

參考資料 :

  1. http://stackoverflow.com/questions/1309137/mysql-limit-by-percentage
  2. http://dba.stackexchange.com/questions/20260/select-top-in-mysql



2013年6月11日 星期二

用 PHP 計算複利與年金終值

複利的威力連愛因斯坦都認為比原子彈還大, 說實在的, 我認為整個數學裡面就只有這個對人生最有用了 (以前學了一大堆高等微積分, 線性代數, 複變函數, 有鳥用?). 單純的複利是本金放進去後, 所得利息不領出來滾入本金繼續生息, 這就是複利的滾雪球效應. 但還有一個比複利的滾雪球效應還大的, 就是年金+複利, 也就是說不但不領息, 反而每年添柴火, 以後每年還另外再投入本金, 使得原始本金年年倍增, 產生的複利效應更大, 好比是滾雪球時, 每滾一圈又加入另一顆雪球, 滾到山腳下的時候, 會是怎樣? 很像原子彈的連鎖反應, 原子核被中子轟擊後, 釋放出更多中子, 這些中子又繼續轟擊其他原子核, ... 這就是為什麼愛因斯坦把複利拿來跟原子彈相提並論的緣故了. 為了對複利與年金有一個清楚的, 具體的數字概念, 我在網路上找到關於計算複利與年金的參考文章如下 :
  1. 年金終值表 &複利終值表 
  2. 什麼是年金 
  3. 1元的複利終值
我根據第三篇 "一元的複利終值" 計算式, 以 PHP 寫了一個可以產生終值表程式如下  :

複利與年金http://mybidrobot.allalla.com/jquerytest/compound_annuity.php [看原始碼]

我把這兩張表的一部分抓出來做個比較如下, 有沒有很驚訝, 如果現在 20 歲, 只要傻傻地利用年金法, 每年投資一萬元到殖利率 6% 的投資工具 (每個月 833 元), 40 年後退休時你就擁有 154 萬的退休金了, 總投入金額 40 萬元. 但如果投資標的殖利率提升到 15%, 那就非常不一樣了, 是將近 1800 萬! 也就是說, 殖利率增加為 2.5 倍 (15/6=2.5), 終值增加為 11.5 倍 (1779/154), 很恐怖吧!  可見年金法要發威的關鍵是在殖利率 (因為從公式可知, 終值是利率的指數啊), 殖利率越高, 要達到預定投資目標所需的時間就可以大幅縮短, 例如 15% 的殖利率, 投資 20 年為 102 萬, 就遠超過 6% 投資 30 年的 79 萬了. 更恐怖的是, 如果是 30% 以年金法每年 1 萬投資 40 年, 終值超過 12 億! 不過, 要每年穩定收益 30% 確實不容易啊!
另一方面, 增加本金也有不錯的滾雪球效果 (呈倍數增長, 不像殖利率是呈指數增長), 以保守的 6% 收益率來說, 以年金法每個月投資 1 萬元 (比每月 833 元增為 12 倍), 每年投資 12 萬,  40 年後退休時的終值是 12*154=1848 萬 (總投入成本 480 萬), 假定退休後還可活 20 年, 這樣的退休準備是每個月 7.7 萬的月退俸啊! (1848/240=7.7). 當然, 以現今的薪水倒退慘況, 每月投資 1 萬元可能不容易, 打對折吧, 每個月投資 5000 元, 每年為 6 萬元, 這樣 40 年後的終值也有 924 萬啊 (相當於 20 年月退俸 3.85 萬)! 請問哪一家公司可能給你 924 萬退休金? 同胞們, 醒醒吧, 要靠自己啊! 但是要特別注意喔, 雪球要滾得大有兩個要件, 第一是滾的方法要對, 也就是上面說的年金+殖利率, 第二是要找一個夠長的山坡, 說白點, 就是要趁早啦! 如果是像我 40 多歲, 滾 40 年, 難不成要 80 多歲退休? 所以 20 歲出頭就要開始囉!

一元複利終值
 年/利率6% 10%  15%30% 
 101.791 2.594 4.046 13.786 
 20 3.207 6.72716.367  190.05
ˇ30 5.743  17.449 66.212 2619.996
 4010.286  45.259 267.86436118.865 

一元年金終值
 年/利率6% 10%  15%30% 
 10 13.18115.937 20.304  42.619
 2036.786  57.275102.444 630.165 
 30 79.058 164.494434.745 8729.985 
 40154.762  442.593 1779.09120392.883

以收現金寡佔市場的電信三雄為例, 其過去十年來都能穩定配出平均 6% 以上的現金股息殖利率, 加上財務穩健, 所以我認為是不錯的投資標的 :


現金殖利率 %
公司  200320042005 2006 2007  20082009 2010 2011 2012 平均% 
 中華電 7.7 7.45.8  6.16.5 6.1 5.7 5.9 5.7 6.49 
台灣大  7.3 7.98.4 6.8  4.88.8  5.35.3 5.2 6.78 
遠傳 4.3 7.7  8.37.9 7.1 7.7 6.9  5.34.5 4.9 6.46 

最後說明複利年金表產生程式, 此程式的核心是用兩層迴圈來產生終值表, 複利表與年金表程式結構完全一樣, 只有計算式不同而已, 最後會將終值表陣列組合成字串, 用來灌進 jQuery Datatables 外掛的 aaData 選項中呈現 :


//製作複利表
$r=Array(); //列陣列
$c=Array(); //行陣列
$title=Array();
for ($i=1; $i<=50; $i++) { //列=年
     $c[]=$i;
     for ($j=1; $j<=20; $j++) { //行=利息
          if ($i==1) {$title[]='{"sTitle":"'.$j.'%"}';} //製作標題
          $c[]=round(pow(1 + $j/100,$i),3); 
          } //end of for
     $r[]="[".join(",",$c)."]";
     $c=null; //歸零以免串接
     } //end of for
$sTitle=join(",",$title);
$compound="[".join(",",$r)."]";
//製作年金表
$r=Array(); //列陣列
$c=Array(); //行陣列
$title=Array();
for ($i=1; $i<=50; $i++) { //列=年
     $c[]=$i;
     for ($j=1; $j<=20; $j++) { //行=利息
          if ($i==1) {$title[]='{"sTitle":"'.$j.'%"}';} //製作標題
          $c[]=round((pow(1 + $j/100,$i)-1)/($j/100),3); 
          } //end of for
     $r[]="[".join(",",$c)."]";
     $c=null; //歸零以免串接
     } //end of for
$sTitle=join(",",$title);
$annuity="[".join(",",$r)."]";


龍保羅的中國回憶錄

今天看到這篇葉家興教授的 "雞排博士的貴人與罪人",  提到  :
"為了強制實施價格管制,蔣介石在上海的公共廣場射殺商人以殺雞儆猴,因此把最後的支持者推向共產勢力的一方。"
其實指的是1949 年蔣介石為了推行金圓券以挽救經濟 (我看是要搜刮民間金銀以支援不斷增加的剿共軍費吧), 派太子蔣經國前往上海打老虎的事情. 號稱鐵面無私的蔣經國在槍斃幾個囤積商人後, 本來鎖定大發國難財的表弟孔令侃當標靶, 結果被後娘宋美齡倒打一耙, 給蔣介石叫到南京夾蛋蛋, 叫他放了孔令侃. 老虎既然打不成, 蔣經國又鎖定上海灘黑幫的杜月笙之子, 開玩笑, 杜月笙是蔣介石的拜把耶, 最後也只落個虎頭蛇尾, 緩刑了事. 關於 金圓券 這個讓國民黨垮台的最後一根稻草發行始末, 詳見維基百科. 英國人龍保羅的回憶錄 " 中的 "上海別離" 這篇對當時上海經濟管制與蔣經國打老虎也有很真實的描述. 龍保羅據云是英國外交官之子, 在 1949 年之前長期居留中國, 自述親身見證了國共內戰時期的許多秘辛, 由於附上其當年所拍攝的珍貴照片, 我認為可信度非常高.

2013年6月10日 星期一

微框架 AmyPHP 的安裝程式調整

為了能讓我的微框架 AmyPHP 能夠同時在 Appfog 雲端主機以及一般虛擬主機上均能順利安裝, 我把 index.php 上的安裝部分做了如下修改, 若是 Appfog 主機, 那麼就可以讀到環境變數CAP_SERVICES 解碼出來的陣列, 這樣就從該陣列中讀取 MySQL 資料庫的連線參數. 如果是一般虛擬主機, 就直接從 dbsettings.php 檔讀取. 程式碼如下 :

if (!$installed) { //系統尚未安裝, 先安裝系統
    //先判斷是否為 Appfog 雲端虛擬主機以讀取 MySQL 連線參數
    $services_json=json_decode(getenv("VCAP_SERVICES"),true);
    if (is_array($services_json)) { //appfog 雲端虛擬主機
        $mysql_config=$services_json["mysql-5.1"][0]["credentials"];
        $username=$mysql_config["username"];
        $password=$mysql_config["password"];
        $hostname=$mysql_config["hostname"];
        $port=$mysql_config["port"];
        $db=$mysql_config["name"];
        $address="$hostname:$port";
        } //end of if
    else { //一般 PHP+MySQL 虛擬主機 (從 dbsettings.php 檔讀取)
        $str=read_file("dbsettings.php");
        $username=return_between($str,"'MYSQL_USERNAME','","')","EXCL");
        $password=return_between($str,"'MYSQL_PASSWORD','","')","EXCL");
        $address=return_between($str,"'MYSQL_ADDRESS','","')","EXCL");
        $db=return_between($str,"'DATABASE','","')","EXCL");
        } //end of else
    } //end of if

Appfog 在 AWS-Asia in Singapore 上配置 MySQL 服務失敗問題

前天為了試一下 Appfog 上的 af delete 指令, 順便把舊的上傳檔案清除乾淨, 就把已申請好, 可以正常使用的 snowball 應用程式刪除掉了, 然後重新佈署新的程式, 但這回沒這麼順利了, 進行到 creating service 時就發生錯誤, 訊息如下 : 

E:\snowball>af push
[WARNING] DL is deprecated, please use Fiddle
Would you like to deploy from the current directory? [Yn]: y
Pushing application 'snowball'...
Creating Application: OK
Creating Service [mysql-c1e30]: Error 503: Unexpected response from service gateway

即使跳過自動產生 MySQL 服務程序先完成 App 部署, 然後再用 create-service 指令來手動產生 MySQL 服務也不行 :

E:\snowball>af create-service mysql snowball

都會出現 Error 503 錯誤. 換個方式到 Appfog 網站登入後, 用網頁方式產生 MySQL 服務也是一樣失敗, 出現如下一樣的錯誤 :

name: VmcError, endpoint: POST:/services, statusCode: 502, code: 503, description: Unexpected response from service gateway

寫了一封信給 support@appfog.com, 但是好幾天了都沒人理我 (是因為 Free 用戶嗎? 這讓我對 Appfog 的好印象已經開始打折 ...). 好吧, 或許是這帳號有甚麼問題, 重新申請一個新帳號試試看, 結果還是一樣. 後來在 doc 中發現 create-service 指令可以指定主機位置 (--infra 選項), 我試著用下列指令, 指定把 MySQL 服務建在其他基礎上, 例如 AWS US East - Virginia 就可以順利建立, 但是唯獨在 AWS Asia SE - Singapore (代號 ap-aws) 就會失敗, 可見新加坡的主機有問題. 指令如下 :

E:\snowball>af create-service mysql snowball --infra aws

此指令是在 AWS US East - Virginia 主機 (代號 aws) 上建立一個 MySQL 資料庫 snowball, 各主機代號如下 (參考 How to use AppFog’s multi-infrastructure architecture in the command line) :

1: AWS US East - Virginia (aws)
2: AWS EU West - Ireland (eu-aws)
3: AWS Asia SE - Singapore (ap-aws)
4: HP AZ 2 - Las Vegas (hp)

建好 MySQL 資料庫後, 就可以用 af bind-service 指令把應用程式聯繫到 MySQL 資料庫 :

E:\snowball>af bind-service snowball snowball

其中第一個 snowball 是指資料庫名稱, 第二個 snowball 是 App 名稱. 經過這樣處理就 OK 了. 但是要注意的是, App 與 Service 必須都位於同一個 infrastructture 才能 bind 起來, 否則會出現]如下訊息 :

E:\snowball>af bind-service snowball snowball
[WARNING] DL is deprecated, please use Fiddle
Binding Service [twstockbot]: Service snowball and App snowball are not on the same infra

所以我只好先把建在新加坡的 App 刪除, 重新 push 時選擇美國東部維吉尼亞的主機來佈署 App 以與 MySQL 配合.

但是我發現網頁速度似乎有比新加坡主機要慢一些, 不過至少可以用啦.
參考網站 :
How To Manage Your AppFog Database from your Desktop on the Other Side of the World
Getting Started with AppFog’s Command Line

2013年6月9日 星期日

Internet 元素週期表

今天問二哥週期表英文要怎麼說, 結果在網路上找到這張奇怪的週期表 :




很奇怪是吧! 乍看之下很像週期表沒錯, 但感覺怪怪的, 第一元素應該是氫 H 不是嗎? 怎麼會是 Y! 呢? 這是什麼元素? 再看看標題, 呵呵, 是 "Periodic Table of the Internet", 是把常用網站排列成週期表的樣子啦!


網站首頁出現檔案列表 Index of / 的問題

最近用剛寫好的微框架 AmyPHP (用菁菁的英文名字命名) 陸續更新所申請的幾個免費 PHP 主機, 測試一下相容性與執行效率, 赫然發現連上首頁竟然出現根目錄的檔案列表如下 :


哇咧, 這不是赤裸裸地暴露網站的結構嗎? 套句公司 MIS 的說法-"嚴重違反資安控管", 對伺服器管理與資安一竅不通的我, 這下可迷惑了, 怎會這樣啦?
稍微做了一下功課 (參考永遠的真田幸村寫的 : Apache設定限制目錄瀏覽的方法), 原來是要修改 Apache 伺服器的 httpd.conf 檔案, 我安裝的是 AppServ, 所以位置在 C:\AppServ\Apache2.2\conf 下,

<Directory "C:/AppServ/www">
    Options Indexes FollowSymLinks MultiViews ExecCGI
    AllowOverride All
    Order allow,deny
    Allow from all
</Directory>

造成此問題的是 "Options Indexes" 這兩個字, 意思是, 當找不到 index.php/index.htm/index.html 時, 就顯示根目錄檔案列表. 請將 "Options Indexes" 這兩個字刪除後重新啟動伺服器即可, 這樣一來伺服器找不到 index 檔時就會顯示 403 Error (找不到網頁). 但是問題還沒有真正解決, 因為在 Local host 我當然可以辦到, 但是在免費的虛擬主機呢? 我根本沒法改呀! 真正的原因是我把網站首頁檔取名為 default.php, 而非 index.php 啊! 所以根本之道除了上述的 httpd.conf 設定外, 還要修改網站之首頁檔為 index.php/index.htm/index.html 才行.

2013年6月6日 星期四

飛機修護

星期天在鄉下收拾桌面,發現了一包塑膠袋,打開一看,呵呵,原來是上學期菁菁四上戶外教學時做的美勞作品 (去哪裡啊? 真糟糕,忘了),是一架陶瓷飛機,主要是給小朋友上色,不是捏陶啦。回家後她把作品放在客廳茶几展示,結果被哥哥一不 小心,手一揮,呵呵,摔個稀巴爛,變成一堆破片。賞味期只有十分鐘不到,她當然生氣啦,菁菁說下個禮拜還要再去做一個,我只能答應她會儘量幫她組合回去,所以把碎片都收起來帶回鄉下,假日有空再組,結果一放就過了快一年。擇日不如撞日, 這週就來修飛機吧,用白膠仔細黏合後如下圖,少了兩小片,所以左前側下方機鼻與左引擎外側各有一個破洞。


我家的咖啡樹

這週回鄉下時,特意到雞舍旁查看上個月開過花的咖啡樹,發現幾週不見,已經結成咖啡豆了。這三棵咖啡樹有兩棵是左營阿姨爬山時帶回來的,一棵則是小舅拿來種的。等成熟時再來看看是否變紅色,因為高雄樓下新開的咖啡屋外牆圖片上就是紅色的。



jQuery 原始碼解析

我有訂閱 Tsung's blog (因為他寫的 PHP 心得非常實用, 還有上回寫去小琉球吃喜酒記事讓我印象非常深刻), 今天收到這篇 "一行一行說明 jQuery Source Code", 對於我這種想一窺 jQuery 怎麼寫出來的素人程式員來說, 真是一大福音. 馬上連過去, 發現這是一本 HTML 的電子書 "jQuery Annotated Source", 解析對象是 jQuery 1.6.2 版 (現在已經是 1.10.1 版), 分成 14 個章節, 真的是左右對稱一行一行解說 (英文的啦, 所以當年去念英文系是有回報的), 哇咧, 太棒了, 呵呵. 另外原作者在 Git 有 zip 檔可以下載離線閱讀. 以上補充說明.

2013年6月5日 星期三

Appfog 雲端虛擬主機

今天在找 PHP 函式的參數預設值要如何自動判別時, 看到小惡魔這篇 "PHP function 參數 default value", 找到解答同時也看到他另外一篇 "PHP 免費雲端主機 PHPFog vs Pagoda vs AppFog", 才知道有這家 Appfog 雲端虛擬主機商, 昨天不是才罵完 1freehosting 嗎? 所以趕緊去看看 Appfog 是啥東東. 研究了一下, 原來它跟 GAE 一樣是 PaaS 雲端服務, 但不同的是, 它自己沒有主機, 而是向 Amazon 或 HP 等 IDC 租用, 而且跟 GAE 一樣, 有免費額度 (註 : Appfog 自 2014 年已經取消免費帳號, 改為付費服務), 對於一般應用綽綽有餘. 試用了一下, 感覺只有六個字 : 簡單穩定快速, 非常滿意. 以下是我蒐集的 Appfog 教學 :
  1. 官網 : appfog af cli tools
  2. 安裝教學 教學二 教學三 離線安裝 
  3. Appfog 的小試用
  4. appfog 中怎样獲得的MYSQL數據庫的連接信息
只要按照安裝教學一步步做, 很快就可以將自己原先的 PHP 應用部署到 Appfog 上. 比起 GAE 還要學習 Java 或 Python, 我想這是最經濟的雲端方案. 整個過程摘要如下 :
  1. 先到 Appfog 申請帳號 : https://console.appfog.com/signup
    完畢後會寄 verification code 到您的登錄信箱, 回到註冊頁填入驗證碼馬上就可以登錄了.
  2. 再到 Ruby 網站下載 Rubyinstallers, 目前是 Ruby 2.0.0 版 (建議安裝 1.9.3 版才不會有錯誤訊息), 約 16MB. 但這並不是要你學習 Ruby 語言, 而是它要用 Ruby 來管理套件以及處理應用程式部署安裝等工作. 安裝 Ruby 時最好勾選將 Ruby 加入 path 中 :



    如果忘了勾選也沒關係, 可以在安裝完後, 到控制台/系統/進階/環境變數裡, 把 C:\Ruby200\bin 加到 path 中, 這樣才能在任意目錄下執行 Ruby 指令.
  3. 接著打開 DOS 視窗, 切到你的 PHP 應用程式目錄, 例如 D:\snowball, 輸入下列指令安裝 af 套件, 用來管理應用程式部署, 更新, 刪除等等).
    gem instal af

    順利的話, 應該看到如下訊息  (公司防火牆若有擋, 那就會報出 unreachable error 了) :


  4. 檢查 af 是否安裝成功 :
    af -v

    如果輸出 af 版本就對了 :
    D:\snowball>af -v
    [WARNING] DL is deprecated, please use Fiddle
    af 0.3.18.12
  5. 接下來就可以用註冊之 email 與密碼登入 appfog 了 :
    af login

    D:\snowball>af login
    [WARNING] DL is deprecated, please use Fiddle
    Attempting login to [https://api.appfog.com]
    Email: yourname@ms15.hinet.net
    Password: ********
    Successfully logged into [https://api.appfog.com]
  6. 最後一步, 就是上傳檔案, 部署應用程式, 只要在 app 所在目錄下 af push 指令, 就會把該目錄下的所有檔案上傳 appfog 了 :
    af push 



  7. 這樣就大功告成, 只要連線 http://test.ap01.aws.af.cm 就可以看到網站了. 如果有部份檔案更新, 不用再下 af push 指令重新部署, 只要用 update 即可 :
    af update snowball



  8. 檢視應用程式部署情形 :
    af apps
  9. 停止與啟動應用程式 :
    af start
    af stop
  10. 下載 app 原始碼
    af pull <appname>
  11. 刪除應用程式
    af delete <appname>

    當此應用程式不要了, 就用 af delete app_name 刪除.
以上, 是不是跟 GAE 很像呢? 我個人覺得是比 GAE 還要簡單一些. 但最重要的, Appfog 也跟 GAE 一樣提供 cron job!  詳見 :
http://blog.appfog.com/task-scheduling-support-on-appfog-with-standalone-apps/



2013年6月4日 星期二

mybidrobot.allalla.com 測試網站掛點

最近忙著寫雪球股網站, 沒時間進行 jQuery 測試; 前幾天要找一下以前的測試範例來套用, 連到 http://mybidrobot.allalla.com/jquerytest/datatable_22.php 才發現網站已掛點, 登入後台赫然看到下列訊息 :

mybidrobot.allalla.com1FreeHostingSuspendedAbuseAutomatically generated website or malicious script / software was uploaded to mybidrobot.allalla.com  

卡好, 已經被 1FreeHosting 停權了, 說我濫用免費方案 (abuse), 網頁疑似為自動產生, 但這都是我一個字一個字敲出來的程式碼, 竟然說是自動產生的, 而且直接停權, 無法再登入取回檔案, 我猜是要逼我升級買它的付費方案吧! 還好吃過幾次苦頭的我, 早已料到會有這一手, 上回告一段落後就整個網站下載存檔.

今天再連一次 http://mybidrobot.allalla.com/jquerytest/datatable_22.php, 發現更恐怖了 :


竟然變成釣魚網站了. 而且還是有人檢舉哩. 算了, 昨天開始找一些付費主機, 有找到幾個口碑不錯的, 有空再整理. 

修正 : 經我寫信要求恢復後, mybidbot 測試網站已經恢復正常, 因此註銷原先之 complains.