我們可以通過變更 `state` 或 `props` 的內容來更新 render 的結果。但有些情況如果當 Component 又可以通過 `state` 來更新,又同時可以通過 `props` 來更新的話,那麼需要怎樣做呢? ### 使用 `state` 更新 render 先看看下面的 Code,我們可以通過把 `props` 傳的的資料 assign 到 `state`,然後在 render 時讀取 state 便可了。這樣傳入的 `props` 可以從 `state` 反映出來。 ```js /** * my component */ class MyComponent extends React.Component { constructor(props) { super(props); this.state = { text: this.props.text }; } render() { return this.state.text; } } ``` 不過上面有一個缺點,就是 `constructor` 只會在建立 Component 時運行一次,在往後的 `props` 更新時,Component 並不會把 `props` 的內容再一次 assign 到 `state`。這讓便不能反映出日後 `props`變更的值。 ### 解決方法 我們可以加入一個方法,來把 `props` 的變更重新 assign 到 `state`,使 render 出來的結果合乎預期。 ```js /** * my component */ class MyComponent extends React.Component { constructor(props) { super(props); this.state = { text: this.props.text }; } componentDidUpdate(oldProps) { if( oldProps.text !== this.props.text ) { this.setState(state => ({ ...state, text: this.props.text })); } } render() { return this.state.text; } } ``` 我們可以通過在 `componentDidUpdate` 中檢查需要的 `props` 內容有沒有變更,從而決定是否發動 `setState`,這樣就可以把 `props` 的更新反映出來。 在 React 的官方網站對這個動作也有特別說明 : https://zh-hant.reactjs.org/docs/react-component.html#constructor
要驗證使用者自行輸入的日期時間,使用 Regular Expression 來驗證是最方便的方法。 ### 什麼是 Regular Expression Regular Expression 能夠描述出字串的出現模式,其中一個用途是可以用來驗證字串是否符合某個特定的 Pattern。例如 : ```js // define a string var str1= 'Hello World'; var str2= 'World Hello'; // define regex pattern var regex = /^Hello World$/; console.log(regex.test(str1)); // true console.log(regex.test(str2)); // false ``` ### 驗證日期字串 例如我們要限制使用者輸入的日期格式為 `YYYY-MM-DD` 的話,那麼以下的日期格式是能通過的。 ```js // valid dates var validDates = [ '2020-01-02', '2020-02-29', '2020-12-31' ]; // invalid dates var invalidDates = [ '02-02-2020, '31-01-2020', '04-30-2020' ]; ``` 要使用 Regular Expression 來表達這個格式的話,可以使用以下語法 : ```js // regex for checking date string var regex = /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/; ``` ### 解說 下面會為上面的 Regular Expression 進行解說。 |符號|解釋| |---| |`^`|字串的開始| |`[0-9]`|字符為 0 到 9 之間的數字| |`{4}`|上一個字符重覆 4 次| |-|出現 "-" 字符 1 次| |`[0-9]`|字符為 0 到 9 之間的數字| |`{2}`|上一個字符重覆 2 次| |-|出現 "-" 字符 1 次| |`[0-9]`|字符為 0 到 9 之間的數字| |`{2}`|上一個字符重覆 2 次| |`$`|字串的結尾| 用這樣的 Regular Expression 便能測試出字串是否符合特定的日期格式。 ```js // regex for checking date string var regex = /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/; console.log(regex.test('2020-01-02')); // true console.log(regex.test('2020-11-22')); // true console.log(regex.test('2020-33-99')); // true ``` 從上面的結果可以看到,雖然可以正常檢查到日期格式,但是會連 `2020-33-99` 這種不合法的日期都會能通過。如果要加強度合法日期的檢查,則需要再修改一下 Regular Expression 內容。 ### 驗查日期字串加強版 我們使用上面的 Regular Expression 再加以修改一下。 ```js // regex for checking date string var regex = /^[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$/; ``` 經過修改後的 Regular Expression 就可以檢查出月份在 1 到 12 之間及日期在 1 到 31 之間,但是如果要能驗證出 31 日及 30 日的月份甚至是潤年的話,就需要更多的邏輯檢查了。
如果要在 MySQL 中的 `DATETIME` 欄位中抽出 DATE 的話,可以使用 `DATE()` function 來達成。 ### 日期時間 在 MySQL 中 `DATETIME` 欄位會儲存著日期時間。格式如下 : ```sql # select query select created_at from table; # 2020-01-22 12:11:23 ``` ### 使用 `DATE()` Function 透過使用 `DATE()` function,可以只抽出當中的日期值 : ```sql # select query select DATE(created_at) as created_at from table; # 2020-01-22 ```
雖然每個月的日子都差不多是固定的,但是如果能使用 Program 計算出日期相關的數值,就更為方便易用。 ### TL;DR 我們可以使用 Date Object 來快速計算出每個月份的最後一日。 ```js // year var year = 2020; // month var month = 0; // get date var date = new Date(year, month + 1, 0); ``` ### 潤年 Leap Year 在小學時都會有教過 : 一月大、二月小、三月大、四月小、五月大、六月小、七月大、八月大、九月小、十月大、十一月小、十二月大。 實質上上面的意思是 : 1 月有 31 日;2 月有 28 或 29 日;3 月有 31 日;4 月有 30 日;5 月有 31 日;6 月有 30 日;7 月有 31 日;8 月有 31 日;9 月有 30 日;10 月有 31 日;11 月有 30 日;12 月有 31 日。 潤年是指二月有 29 日的年份,而潤年是有一條公式可以計算的。 ### 潤年定義方法 年份是 4 的倍數及年份不是 100 的倍數但年份是 400 的倍數。 以上的規則可以使用以下的邏輯判別出來。 ```js /** * check year is leap year */ function isLeapYear(year) { return ((year % 4) === 0 && (year % 100) !== 0) || (year % 400) === 0; } ``` ### 找出每個月份的最後一日 由以上的資料,我們可以總結出下面的邏輯用來計算每個月份的最後一日。 ```js /** * get last date of month */ function getDaysOfMonth(year, month) { // get date by month switch (month) { case 2: return isLeapYear(year) ? 29 : 28; case 4: case 6: case 9: case 11: return 30; default: return 31; } } ``` 但是... 其實 Javascript 入面的 Date Object 已經可以快速計算出來。 ```js // year var year = 2020; // month var month = 0; // get date var date = new Date(year, month + 1, 0); ``` 上面的 `new Date()` 方法是把目前的月份加上 1,然後再把日子設定為 0,這樣 Date Object 便會設定為下一個月的 0 日,但因為 0 日是一個不合法的日期,所以 Date Object 會把 0 設定為 1 的前一日,也即是設定月份的最後一日。 不過有人回報過使用 `new Date()` 的速度很慢,所以用那一個方法來取得每月最後一,可以因應情況自行決定。
今日收到個問題,是關於把顏色變成一段數線。這個問題令筆者想起其實光也是波段,不同的波段可以反映出不同的顏色。 在網上找了一會,原來已經有科學人實作了出來。筆者把內裏的 Javascript 代碼節錄出來。 ```js /** * takes wavelength in nm and returns an rgba value */ function wavelengthToColor(wavelength) { var r, g, b, alpha, colorSpace, wl = wavelength, gamma = 1; if (wl >= 380 && wl < 440) { r = -1 * (wl - 440) / (440 - 380); g = 0; b = 1; } else if (wl >= 440 && wl < 490) { r = 0; g = (wl - 440) / (490 - 440); b = 1; } else if (wl >= 490 && wl < 510) { r = 0; g = 1; b = -1 * (wl - 510) / (510 - 490); } else if (wl >= 510 && wl < 580) { r = (wl - 510) / (580 - 510); g = 1; b = 0; } else if (wl >= 580 && wl < 645) { r = 1; g = -1 * (wl - 645) / (645 - 580); b = 0.0; } else if (wl >= 645 && wl <= 780) { r = 1; g = 0; b = 0; } else { r = 0; g = 0; b = 0; } // intensty is lower at the edges of the visible spectrum. if (wl > 780 || wl < 380) { alpha = 0; } else if (wl > 700) { alpha = (780 - wl) / (780 - 700); } else if (wl < 420) { alpha = (wl - 380) / (420 - 380); } else { alpha = 1; } colorSpace = ['rgba(' + (r * 100) + '%,' + (g * 100) + '%,' + (b * 100) + '%, ' + alpha + ')', r, g, b, alpha] // colorSpace is an array with 5 elements. // The first element is the complete code as a string. // Use colorSpace[0] as is to display the desired color. // use the last four elements alone or together to access each of the individual r, g, b and a channels. return colorSpace; } ``` 上面 function 是把光的波長數值變換成 HTML 格式的顏色數值。另外還會獨立輸出 RGB 及 Alpha 值。 由上面的 Code 可見,如果把 380 - 780 的數值拉一條線用 0 - 100% 包裝起來,就可以用 0 - 100 中任一數值來表示每一個不同的顏色。 ```js /** * get color 1 to 100 */ function getColor(value) { // get the weight var weight = Math.floor(380 + ((780 - 380) / 100 * value)); // return color return wavelengthToColor(weight); } ``` 另外在 GitHub 上有其他的實作版本,不過筆者就未有嘗試。 https://gist.github.com/mlocati/7210513 > 參考網址 : https://scienceprimer.com/javascript-code-convert-light-wavelength-color
2019 年的冬天來曼谷的 ICON SIAM !!! 這個地點是先前來曼谷時沒有到過的,因為在 Youtube 上看到 "冲遊泰國" 系列的其介一隻,介紹了一家好吃的 Harbour 自助餐,所以專程來這邊試試。 筆者這次是 GRAB 車來的,所以遇上了很塞的交通情況。如果下次有機會再來的話,一定會試試坐船來,可以看到更多的水上風景。和在河上看到 ICON SIAM 建築特色。 ### 交通 和去河濱夜市一樣,可以先坐 BTS Saphan Taksin 2號出口,再行去 Sathorn Pier 碼頭轉乘船隻抵達 ICON Siam。  ### 時間 早上 09:00 到晚上 23:00,大約 10 分鐘一班船。 ### 商場特色 商場的定位是高級商場,有各國名店進駐,但是最大的特色是底層是一個水上市場,模擬了泰國傳統船家的生活及交易方式。 除了水上市場外,商場內還有很多特色的手作小店,售賣當地的手工藝品及傳統食物,而且價格相宜,能把高級名店和特色小店包裝在一起,又不會有違和感。   
Harbour 自助餐位於 ICON Siam 商場的 6 樓,是一家專門服務自助餐的餐廳。 ### 交通 和去河濱夜市一樣,可以先坐 BTS Saphan Taksin 2號出口,再行去 Sathorn Pier 碼頭轉乘船隻抵達 ICON Siam。  ### 時間 早上 09:00 到晚上 23:00,大約 10 分鐘一班船。 ### Harbour 位於 ICON Siam 6 樓的 Harbour。       ### 筆者點評 食物種類很多,以中西日較為主道,台資公司。餐廳的地方很闊,不過有逼的感覺。食物質數 OK,熱食為佳,達到水準。海鮮冷食相對較差,新鮮程度一般。甜品超多選擇,飲品也很多款。總體來說性價比高,值得一試再試。
曼谷遊的第七天,行程來到最後一天了,今天下午 5:00 就要到達機場坐 7:20 的機回去了 !!! 今天會去一個曼谷相對比較新的地方,ICON Siam。雖然地方是叫 ICON Siam,但是一點都不近 Siam BTS 站的,它是位處河濱夜市的對岸。 ### ICON Siam ICON Siam 附近的交通真是十分之差,塞了非非非常之久才到達 !  ICON Siam 的特色是它的最下層是一個大型的水上市集,內裏有很多小店,吃的穿的都有,價格也平民。   而上層的就是普通的大型商場,生活百貨。   ### Harbour 還沒有吃早午餐的我們就得先解決一下腸胃。所以選擇去了頂樓的 Harbour 自助餐吃個飽 !!   因為當日為 12 月31日,所以這個格目不適用,入坐 2 小時 1,300 BAHT 一位。價錢也不算貴啊 !       ### 水上市場 吃完後我們又回到地下再逛逛水上市場。     因為時間關係不能逛太久,交通情況很難預計得到,所以就提早了叫車回酒店再叫車去機場。  這次泰國之旅圓滿結束 !!
2019 年冬天,來到泰國吃了一餐自助午餐。 ### 曼谷洲際酒店 這家酒店每次出 Chit Lom BTS 站都會看到,每次都說下次一定要來試一試,而這次終於都合時機 BOOK 來試試 !! ### 地點 由 Chit Lom BTS 站行三分鐘就到達。     ### Espresso Espresso 位處酒店二樓,在大堂走上一層便可以到達,星期日的自助午餐大約 1,000 BATH 一位。   ### 餐廳內部 餐廳內部坐位十分之多,地方很多,不會有很逼的感覺。   不過食物的種類不是很多,主要是以熱食為主,在午市自助餐沒有鮮等食物提供。 當時目測光顧的人不是很多,大約有 10 枱客左右。 ### 筆者點評 總的來說筆者覺得相同的價格可以在曼谷找到性價比更高的自助餐,整體感覺一般。食物味道還可以,沒有超水準的表現,也沒有特別差的。環境感覺起來普通,可能因為日光滲入不足,所以覺得暗暗的。有個服務員很利害的能說一點點廣東話,在他國能聽到當地人說廣東話感覺很親切。
曼谷遊的第六天,這是今次旅程的最後一個一整日了。但是因為體力關係,所以今天的行程也是一樣簡簡單單行逛為主。 這天訓到日上三竿,不過今天會去一家大酒店吃自助午餐 !! 就是曼谷的洲際酒店 (Intercontinental Hotel)。 ### 曼谷洲際酒店 曼谷洲際酒店位於 Chit Lom BTS 站,每次出站必定會見到身影。  在天橋右手邊的樓梯下去,就可以到達。    ### Espresso 進入酒店後上二樓,就可以到達自助餐的餐廳 Espresso。       吃了大約兩個小時,四個人埋單 5,200 BAHT 左右。食完後又分開各自行,筆者和太太去了 Siam 站附近周圍走走。 > 在另一個 POST 會有影片介紹 : https://19site.net/posts/85 ### Siam 貴為曼谷中心地帶的中心區,Siam 商場名店臨立,是旅人必到的地方之一。筆者雖然來了數次,但是也必定要來一來,為的是一家海南雞飯。  Siam Paragon,這個名字自從 2013 年第一次和女朋友 (當是太太還是女朋友) 來泰國時,聽到導遊介紹時就被洗腦了。導遊哥哥 (他自稱叫阿譚) 介紹說 Paragon 是解作 Mall 的意思,並說大家如果失散了可以在 Siam Paragon 的正門口等。 不過這次要先去找筆者想吃那家海南雞飯,所以目標為本,立即進發。   我們穿過了 Siam Square ONE 商場。      很難想像休假的第 1 日貼的出 A4 紙可以 "巢" 成這樣 !!! 唯有返 Siam Square ONE 行下。    ### Siam Center Siam Center 位於 Siam Paragon 的側邊,行 1 分鐘就到。入面的格區不像 Siam Paragon 走貴要路線,而是較為中級的商場,有吃的也有買的,比起 Siam Paragon 更易吸引大眾。   ### 河濱夜市 走了一整個下午都有點累了,筆者和太太先回飯店休息一會,再一行四人 GRAB 車到河濱夜市 (先前都是坐船去的,今次試一下叫車去)。  由於塞車的關係,在上車一邊塞時日以落了 !!  由於實在太多車的關係,所以司機停了在夜市很遠的地方讓我們走進去 .... 還真是很遠呢,走了差不多十五分鐘才到。 到達碼頭時,日已落 ..  行進去一點,就會看到地標性的摩天輪。     夜市的另外一邊有像貨食一樣的市集區。   > 在另一個 POST 會有影片介紹 : https://19site.net/posts/78 ### 晚飯時間 雖然夜市內有很多餐廳,不過好像也沒有合心意的,所以最後還是回去 Asok 附近,吃飯店樓下一家的餐廳 (平常晚上回去酒店時超多人在吃)。          總共九個菜,食到好飽 !! 吃飽後就回酒店休息去 !! 明天就是最後一天的行程,今天晚上要執 GIP 了。
很多時我們也會在程式用上 Random String,用來作為暫時的 ID 或者用來生產 TOKEN。 ### TL;DR 我們可以使用隨機數字來生產隨機的字串。以下 Script 可以產生 8 個位的隨機字串 : ```js // create random string Math.random().toString(36).slice(2, 10); ``` ### 原理 以上的 Script 其實是把使用 `Math.ramdom()` 生產出來的隨機數字變成 36 進位的字串。由此推理出,也可以生成其他進位的字串 : ```js // hexadecimal Math.random().toString(16).slice(2); // octadecimal Math.random().toString(8).slice(2); // binary Math.random().toString(2).slice(2); ``` ### 實作 不過以上都只可以當作為快速用途,生成的字符都是在 a-z 及 0-9 之間,如果要加入更多的字符,就要用較為複雜的邏輯了。 ```js /** * generate random string */ function randStr(length) { // result container var result = []; // characters pool var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; // create random string for (var i = 0; i < length; i++) { // get random position character result.push(chars.charAt(Math.floor(Math.random() * chars.length))); } // return result return result.join(''); } // print random string with 8 characters console.log(randStr(8)); ``` > 參考資料 : https://stackoverflow.com/questions/1349404/generate-random-string-characters-in-javascript
製作網站時小不免會使用到 ICON 用來標示功能,但是如果每一個 ICON 都使用一幅圖片來處理的話,在載入網站時便會使用大量時間。後來為了減少載入圖片的時間,使用了 ICON MAP 來解決。即是把所有的 ICON 都合併在一張圖片,然後使用 background-image 及 background-position 來決定顯示圖片的那一個位置。  ### 文字 ICON 在未開始前先看看這個網站 : https://fontawesome.com 這個網站提供了字型 ICON,供開發者免費或者付費使用。只需要匯入文字到項目,然後在相應的地方加入 CSS 就可以顯示文字 ICON 了。 ### 用法 只需要在 HTML 中插入特定元素並設定好 `class` 就可以使用。 ```html <!-- use i element to reference to icon --> <i class='fa fa-star'></i> <!-- use span element to reference to icon --> <span class='fa fa-star'></span> ``` 詳細的情況可以查看這裏 : https://fontawesome.com/how-to-use/on-the-web/referencing-icons/basic-use ### 在 React 上使用 要在 React 上使用先要使用 npm 安裝套件。 ```sh npm install @fortawesome/fontawesome-free ``` 然後在 JS 或 JSX 檔案內載入套件。 ```js import '@fortawesome/fontawesome-free/css/all.css'; ``` 最後就可以使用上面的語法正常使用。 ```html <!-- use i element to reference to icon --> <i className='fa fa-star' /> <!-- use span element to reference to icon --> <span className='fa fa-star' /> ```
很多時我們使用 Http 回傳 JSON 到客戶應用程式時都會忽視了一點,就是告訴客戶應用程式 (多數是指 Browser 或 Http Agent) 你回傳的是 JSON 字串。 得多比較新的 Http Agent 都會有 Auto-detect 回傳的內容是什麽而去決定為回傳的結果 "加工"。例如回傳的是 JSON String,則會進行 `JSON.stringify()` 事前處理,然後才會回傳到使用者。所以如果伺服器可以告訴客戶要傳送的內容種類,就可以省去客戶分析內容的時間了 ! ### Header 伺服器可以透過設定回應檔頭 (Response Header),來告訴客戶端回傳的內容是什麼格式。在 PHP 上我們可以使用 `header()` 功能來達成。 ```php // send json string header('Content-Type: application/json'); // send json string with charset header('Content-Type: application/json; charset=utf-8'); ``` 以上就是設定回應檔頭的語法,而 `Content-Type: application/json` 就是要告訴客戶程式,將會收到的東西是 `application/json`。以下是其他檔頭的例子 : ```php // send html header('Content-Type: text/html'); // send plain text header('Content-Type: text/plain'); // send xml header('Content-Type: text/xml'); ``` ### Chrome Developer Toolbar 以下是使用 Chrome 開發人員工具,可以看到回應檔頭的內容。  還可以設定內容的編碼 (charset encoding)。
在先前有篇文章是提及使用 Javascript 把 JSON 的格式對齊好 : https://19site.net/posts/76 其實相類似的功能在 PHP 下也是有的 !! ### PHP 上的 JSON 在 PHP 上我們常常使用到 `json_encode()` 及 `json_decode()` 把資料和 JSON 之間進行互換。 自從 PHP 5.4 後更提供了 `JSON_PRETTY_PRINT` 參數,來把 JSON 對齊好 ! http://php.net/manual/en/function.json-encode.php ``` <?php // data $data = [ 'name' => 'Peter', 'age' => 21 ]; // $data to json $json_string = json_encode($data, JSON_PRETTY_PRINT); ```
在開發網站時 (特別是 ReactJS 時),網站主機 (Web Hosting Server) 和應用程式主機 (Application Server) 很多時都不會是同一台主機。如果在沒有設定 proxy 的情況下,在網站使用 Ajax 載入外部資料時便會出現 `Access-Control-Allow-Origin` 例外。 > Access to XMLHttpRequest at 'https://19site.net/files/7d/79/7d790b71-7af2-4b0b-890d-65c513f4077e.jpeg' from origin http://192.168.1.147:8000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. ### 源因 這是因為安全性的因素,瀏覽器預設不允許 JavaScript 任意存取其他伺服器的資源。以免因為 Session 而能夠載入先前登入過的網站,然後把資料經過惡意網站上載到第三方地址,達到偷取個人資料的功能。  ### 解決方法 解決方法有二 : - 修改 Application Server 的應用程式,使回應的檔頭 (Response Header) 加入 `Access-Control-Allow-Origin` 並設定為相應的 IP 地址。 - 關閉 Chrome 的網站安全設定。 (本文提及的方法) 注意 : 關閉 Chrome 的網站安全設定隻能生效在本機上,如果正式 Production 的話記得到設定好 Server Application 才對啊 ! 另外要留意是關閉了網站安全設定代表你的 Chrome 並不是在安全環境下運行著,不要用這個 Session 的 Chrome 來作為日常使用 ! 不然便會有機會被惡意網站偷取個人資料了 ! ### 進入正題 實際的操作是在啟動 Chrome 時加入參數 `--disable-web-security`。 在 Window 上,我們可以建立以下 Shortcut : > "C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --user-data-dir="C:/Chrome-dev-session" --disable-web-security  然後我們可以使用新建立的連結來啟動 Chrome。 會看到在啟動時會有警告的字句,提醒使用者使用 `--disable-web-security` 會危害穩定及安全性。  再次提醒一次,不要用這個 Session 的 Chrome 來作為日常使用 ! 不然便會有機會被惡意網站偷取個人資料了 ! > 參考資料 : https://stackoverflow.com/questions/20035101/why-does-my-javascript-code-get-a-no-access-control-allow-origin-header-is-pr
泰國河濱夜市是一個位於曼谷的夜市,在 2013 年第一次來泰國時 19 筆者到過這裏,那是還是一個初開發地方,只有室內的小店鋪,還沒有發展到碼頭的位置。到2016 年再來時已經發展到有摩天輪的及完善飲食的大型市集了。 這次 2019 再來,拍了一段小影片遊走在市集之間。 ### 交通資訊 乘坐 BTS 坐到 Saphan Taksin 站,然後到沙吞碼頭轉搭免費接駁船。  到達碼頭後在左邊有一條專門是去 Aisatique 或是 Icon Siam 的隊,小心不要上錯船,這裏有些船是到沿岸酒店的。 ### 營業時間 夜市的營業時間為每天 16:00pm 到 00:00am ### 場內介紹 如果是乘坐船來的話,下船後最先可以在碼頭走走,時間剛好的話還可以觀看日落呢 !   走過了碼頭,行進入一點就會看到像倉庫一樣的商店。  當然不少得地標性的摩天輪。  再走進一點是餐廳及市隻了啊 !!   文字介紹先到這裏,可以看看影片啊 !
今天在工作時找到了關於 NodeJS DES-ECB 的例子代碼,在這裏先記錄下來。 ### NodeJS 中的 DES 加解密 Node.js 自帶強大的加密功能 Crypto,它是基於 OpenSSL 庫實現的加密技術。 DES 是一種對稱加密算法,密匙長度必須是 8 的整數倍,在一些簡單的應用場景經常被使用。 為了網絡上信息傳輸的安全(防止第三方竊取信息看到明文),發送發和接收方分別進行加密和解密,這樣信息在網絡上傳輸的時候就是相對安全的。 DES 加密模式有: Electronic Codebook (ECB) , Cipher Block Chaining (CBC) , Cipher Feedback (CFB) , Output Feedback (OFB)。這裏以密文分組鏈接模式 CBC 為例,使用了相同的 key 和 iv (Initialization Vector)。 ```js const crypto = require('crypto') // DES 加密 function desEncrypt (message, key) { key = key.length >= 8 ? key.slice(0, 8) : key.concat('0'.repeat(8 - key.length)); const keyHex = new Buffer(key); const cipher = crypto.createCipheriv('des-cbc', keyHex, keyHex); let c = cipher.update(message, 'utf8', 'base64'); c += cipher.final('base64'); return c; } // DES 解密 function desDecrypt (text, key) { key = key.length >= 8 ? key.slice(0, 8) : key.concat('0'.repeat(8 - key.length)); const keyHex = new Buffer(key); const cipher = crypto.createDecipheriv('des-cbc', keyHex, keyHex); let c = cipher.update(text, 'base64', 'utf8'); c += cipher.final('utf8'); return c; } ``` ### NodeJS DES-ECB 上面的代碼情況是使用 DES-CBC 的,如果是要改為使用 DES-ECB 的話,則要把 `keyHex` 換為 `null` 值。 還要注意讀入的字串是什麼格式,即是 `base64` > `utf8` 等等,不然搞半天也隻會搞出亂碼來。 以下代碼是已經把上面的改為使用 DES-ECB Algorithm : ```js // import const crypto = require('crypto'); // DES-CBC Encrypt function desEncrypt (text, key) { key = key.length >= 8 ? key.slice(0, 8) : key.concat('0'.repeat(8 - key.length)); const keyHex = new Buffer(key); const cipher = crypto.createCipheriv('des-ecb', keyHex, null); let c = cipher.update(text, 'utf8', 'base64'); c += cipher.final('base64'); return c; } // DES-CBC Decrypt function desDecrypt (text, key) { key = key.length >= 8 ? key.slice(0, 8) : key.concat('0'.repeat(8 - key.length)); const keyHex = new Buffer(key); const cipher = crypto.createDecipheriv('des-ecb', keyHex, null); let c = cipher.update(text, 'base64', 'utf8'); c += cipher.final('utf8'); return c; } ``` 這段 Code 省掉了 19 筆者半日時間。 > 參考網址 : https://blog.niceue.com/front-end-development/des-encryption-and-decryption-in-nodejs.html
要美觀的 JSON 排列,大概是因為看 API 或者是 Debug 時才會用上。 ### JSON JSON 全名是 JavaScript Object Notation,也就是我們可以使用 JSON 的格式來使用文字的方式定義一個 Object 有什麽。 以下是一條隨機生成的 JSON 字串 : ```json [{"id":"5e18484f4e25d29c4c67233f","index":0,"guid":"29c564e9-138c-4cfe-9504-b0e84c9561f3","isActive":false,"balance":"$1,900.03","picture":"http://placehold.it/32x32","age":32,"eyeColor":"brown","name":"Erma Butler","gender":"female","company":"KNOWLYSIS","email":"ermabutler@knowlysis.com","phone":"+1 (959) 489-2209","address":"566 Gold Street, Cherokee, District Of Columbia, 3647","about":"Quis enim sunt cupidatat et fugiat enim id incididunt consectetur in. Consectetur dolore pariatur elit ullamco veniam. Exercitation fugiat elit ex fugiat. Aute ad nisi aute cupidatat.\r\n","registered":"2016-02-07T01:22:26 -08:00","latitude":79.400117,"longitude":81.521948,"tags":["consectetur","laboris","irure","in","sit","ad","nisi"],"friends":[{"id":0,"name":"Golden Small"},{"id":1,"name":"Felicia Gilmore"},{"id":2,"name":"Frankie Curry"}],"greeting":"Hello, Erma Butler! You have 5 unread messages.","favoriteFruit":"apple"}] ``` 下面這條 JSON 內容和上面是完全一樣的,不過就使用特定方法排列好 : ```json [ { "id": "5e18484f4e25d29c4c67233f", "index": 0, "guid": "29c564e9-138c-4cfe-9504-b0e84c9561f3", "isActive": false, "balance": "$1,900.03", "picture": "http://placehold.it/32x32", "age": 32, "eyeColor": "brown", "name": "Erma Butler", "gender": "female", "company": "KNOWLYSIS", "email": "ermabutler@knowlysis.com", "phone": "+1 (959) 489-2209", "address": "566 Gold Street, Cherokee, District Of Columbia, 3647", "about": "Quis enim sunt cupidatat et fugiat enim id incididunt consectetur in. Consectetur dolore pariatur elit ullamco veniam. Exercitation fugiat elit ex fugiat. Aute ad nisi aute cupidatat.\r\n", "registered": "2016-02-07T01:22:26 -08:00", "latitude": 79.400117, "longitude": 81.521948, "tags": [ "consectetur", "laboris", "irure", "in", "sit", "ad", "nisi" ], "friends": [ { "id": 0, "name": "Golden Small" }, { "id": 1, "name": "Felicia Gilmore" }, { "id": 2, "name": "Frankie Curry" } ], "greeting": "Hello, Erma Butler! You have 5 unread messages.", "favoriteFruit": "apple" } ] ``` 可讀性立即增加上 N 倍了 !!!! ### JSON.stringify() 如果是 JS 的関發者相信對 `JSON.stringify()` 也應該不會佰生,要把 Object 或是 Array 變成為 JSON 字串最正統就是用這個方法。原來我們只要在後面加入多兩個參數就可以把 JSON 輸出為已 Format 版本 !! ```js // data var data = { name: 'foo', pass: 'bar' }; // data to json string var jsonString = JSON.stringify(data, null, 4); // print json string console.log(jsonString); ``` 就會有以下輸出 : ```js // output { name: 'foo', pass: 'bar' } ``` > 在 PHP 上也有類似的動作可以把 JSON 對齊好 : https://19site.net/posts/80
我們會常常看到 HTML Form 要填上 `enctype` 才可以上傳檔案,究竟 HTML Form 的 `enctype` 是什麼來的呢? ### TL;DR 如果需要文件上傳,你只可以使用 `form-data`。 。 與 `x-www-form-urlencoded` 相比,`form-data` 是一種更高級的數據編碼方式。 您可以將 `x-www-form-urlencode` 視為 `txt` 文件,並將 `form-data` 視為 `html` 文件。最終,它們都提供了一些 `http` 的 payload。 ### Content Type ||content-type| |---| |x-www-form-urlencoded|application/x-www-form-urlencoded| |form-data|multipart/form-data; boundary={boundary string}| 瀏灠器通常會自己生成一個分隔的字串,用來分隔每筆傳動的資料,格式通常會是 : ```txt ----WebKitFormBoundaryFasdfWEfawEF3 ``` 這個字串使用者是可以自訂的。 ### Request Payload 假如有以下登入資料 : |fields|values| |---| |username|foo| |password|bar| 如果以上的 payload 使用 `x-www-form-urlencoded` 方法去表示的話,資料會使用 `encodeURIComponent()` 編碼並變成以下這樣 : ```text username=foo&password=bar ``` 而如果使用 `form-data` 的話,每一組資料 (key, value) 都會有自己的一個 section,並會使用 `{boundary string}` 為作分隔。以下會以 `form-data` 方法再把上面的資料整理一次 : ```text --{boundary string} Content-Disposition: form-data; name="username", foo --{boundary string} Content-Disposition: form-data; name="password", bar --{boundary string}-- ``` 如果 `form` 中有上傳檔案的話,內容會像以下 : ```text --{boundary string} Content-Disposition: form-data; name="username", foo --{boundary string} Content-Disposition: form-data; name="password", bar --{boundary string} Content-Disposition: form-data; name="file"; filename="image.jpg" Content-Type: image/jpeg, binary data... --{boundary string}-- ``` 我們把上面的 Body 分成為 4 部份,以下會為每一部份講解 : ### 第 1 部份 這裏儲存著資料 `username` 為 `foo`。 ```text --{boundary string} Content-Disposition: form-data; name="username", foo ``` ### 第 2 部份 這裏儲存著資料 `password` 為 `bar`。 ```text --{boundary string} Content-Disposition: form-data; name="password", bar ``` ### 第 3 部份 這裏儲存著上傳檔案的資料,欄位名為 `file`,檔案名為 `image.jpg`。 ```text --{boundary string} Content-Disposition: form-data; name="file"; filename="image.jpg" Content-Type: image/jpeg, binary data... ``` ### 第 4 部份 Body 的完結。 ```text --{boundary string}-- ```
曼谷遊的第五天,經過了昨天非常消耗體力的行程,這天我們決定會輕鬆一點的。早上在床上過了很久也沒法起床因為腳太痛了,到出門口時已經差不多12時。 我們決定再到 Terminal 21 找吃的,但是不會再吃 Pier 21 了 ... 好像已經吃了很多次。到達 Terminal 21 乘坐電梯到 4 樓時,看到 MK 火鍋的指示廣告板,就決定今天的午餐在 MK 火鍋解決。 ### MK火鍋 對 MK 火鍋的認識,是因為在2018年冬天來泰國的時候,參加了一個去華欣的本地旅行團。中午的時候汽車駛到路程的一半,好導遊帶我們到一個商場,說今天的午飯大家在這裡自費安排。並說這裏有一家MK火鍋是非常出名的,但是如果中午吃火鍋的話你們可能會趕不上車哦。 那時候我和太太邊行邊走,就看到導遊說那間 MK火鍋。在火鍋的門口那排隊的凳子上,竟然看到有兩個團友正在坐著。看來他們真的有自信可以一個小時內吃完火鍋呢 ! 然後我和太太便去了另一家吃泰菜的餐廳。吃完泰菜之後看時間也差不多,想預留少少時間去樓上的超市逛逛。當再次經過 MK 火鍋的時候那兩位團友還在裏面努力中。當然我們逛完超市後那兩位團友剛好吃完了,真是厲害,真的可以在一個小時內完成火鍋午餐。 那次之後便想,如果有機會必然會到 MK火鍋吃一餐。終於機會來了 !!!  在科技的加持下,點菜不再是難度了 !!!     MK 火鍋係有燒鴨叫的,不過比起港式的燒鴨,這裏的口味可能不太岩港人胃口,因為那個汁是好像用花生醬弄成的。味道不是不好,就是有怪不相襯。   四個人埋單大約 2,300 BAHT 左右,比起當地物價唔算是平,不過也是一個值得一試的地方,湯低弄得不錯。 ### Platinum Fashion Mall 在下午的時間我們決定分頭事,筆者會和老婆到 Platinum Fashion Mall 逛逛街。這是女士來曼谷必到的地方之一,在那裏筆者認為是曼谷的 "旺中"。衣服款式多,價格也相宜。   筆者在上年來的時間,Platinum 商場還只是個 "Platinum 商場",一年之差已經變為了 "Platinum Fashion Mall",其實這個商場一直也是在賣女裝衣服為主,早在第一次來泰國時已經是。不過經過重新命名後,更為專業。   逛完後再由原路走回去。   ### 晚餐 由於行得太累,所以先回飯店休息一會,然後到 Terminal 21 的 Foot Court 隨使吃了點東西就回去休息了。
2019 泰國遊這次跟了 KKDAY 的一日團遊泰國大城,探索一下泰國的歷史古都 ! 如果想看看筆者的大城遊記,可以到下面這篇文章 : 2019 曼谷遊 - Day4 https://19site.net/posts/66 ### 大成府 面積約為 2,556.6 平方公里,距首都曼谷約76公里。其疆域北連紅統府及華富里府;東瀕沙拉武里府,南毗巴吞他尼府,西臨素攀府及暖武里府。 大城府為廣闊平原,河道縱橫,是三條河流的匯合處。農業發達,為全泰國最大產米區。另外,水產豐富,且有不少大、中、小型工廠。 ### 歷史資料 1563年,緬甸東吁王朝攻滅阿瑜陀耶王朝,及後復國並延續30多年。 1767年,緬甸貢榜王朝軍隊攻陷大城,阿瑜陀耶王朝正式滅亡。後鄭昭重建王國,將首都南遷至吞武裏。 原王城遺址現為阿瑜陀耶遺蹟公園,被列為聯合國教科文組織世界遺產。該府作為大城王國時代的首都時,其文化、藝術、國際貿易均非常發達,很可惜,此古城遭入侵緬甸軍人縱火徹底破壞,現只剩下部份宮殿遺跡、珍貴佛像和精美雕刻等供人憑弔。 ### 參觀景點 ##### 邦芭茵夏宮   建於 17 世紀的泰皇夏日行宮,鄭王將首都遷至曼谷吞武里區後,邦芭茵夏宮漸漸被荒廢,隨著時間的流逝,年久失修的木製宮殿逐漸腐爛,後在19世紀開始重建,因當時西方文化的傳入,從邦芭茵夏宮的建築可以看出多種元素的融合,包含維多莉亞式、哥德式、中國式,當然還有泰國本身特有的建築風格。在此可以欣賞精心打理的園藝,享受宛如貴族的悠閒氣氛。 ##### 大城水上市場   乾淨整齊的大城水上市場,不同於丹能莎朵的喧鬧,是許多泰國本地人的景點名單之一。避開炎熱的太陽,沿著河岸兩旁建立的商家,探索濃厚古城特色的玩物,品嚐各樣經典泰式小點。若逛到腳酸,不妨體驗道地的泰式按摩。 ##### 崖差蒙空寺  保留完整的崖差蒙空寺,14世紀為紀念戰勝緬甸軍隊而修建,以戶外臥佛跟寶塔最為知名。高聳雄偉的錫蘭式高塔、連綿壯觀的佛像,你可以爬上塔頂,將硬幣投入許願井中,或是把硬幣貼在臥佛的腳底,許下心願、祈求好運。 ##### 瑪哈泰寺   位在遺址中心的瑪哈泰寺是城內最古老的廟宇,「樹中佛陀」更為大城必看景點之一。相傳當時緬甸軍人砍下這尊佛像時,落下的佛首滾到菩提樹旁,被樹根團團包圍保護,緬甸軍因此奇景被嚇退,才得以保有現在所看到的佛寺景象。 ##### 帕席桑碧寺 & 帕蒙空博大皇宮&涅槃寺   主要用來舉行皇家儀式和典禮的帕席桑碧寺,分別存放國王及皇室成員的骨灰在三座鐘型佛塔。即使遭遇戰亂的洗劫,這些佛塔至今仍昂然聳立在寺廟內, 一排排主殿屋簷的圓柱上仍保留姿態千萬的蓮花雕刻,向世人展示大城王朝歷久不衰的傲氣。 > 參考資料 : Wikipedia - https://zh.wikipedia.org/wiki/%E5%A4%A7%E5%9F%8E%E5%BA%9C > 參考資料 : KKDAY - https://www.kkday.com/zh-hk/product/18532
19Site 的 Youtube Channel 正式啟動 !! 連結網址 : https://www.youtube.com/channel/UCM96oVSbCazZfGwhl2uqbsA 或者可以點擊網站右上角的 Youtube Logo 也可以進入 19Site Youtube Channel !! Youtube Channel 目的在於記錄遊記剪輯出來的影片 !! 分享 19 筆者的足跡 !!  日後可能會以影片的方式分享更多不同的資訊 !!
我們會常常使用到 Youtube 影片的縮圖作為影片的簡介,這裏會介紹如果得到 Youtube 影片的縮圖。 ### 縮圖 我們可以通過 `i1.ytimg.com` 取得影片的縮圖檔案,URL 的格式如下 : https://i1.ytimg.com/vi/<youtube-video-id>/0.jpg 如果大家有過 Youtube 都會知道其實每一條的影片也會有一組 ID 用來識別。這組 ID 通常都可以在 URL 上找得到,只要我們把這組 ID 貼到上面 `<youtube-video-id>` 的位置,我們便可以得到影片的縮圖了。 ### 縮圖大小 以下的縮圖大小是保證會出現的: | Thumbnail Name| Size (px) | URL| |---| | Player Background| 480x360 | https://i1.ytimg.com/vi/<VIDEO ID>/0.jpg| | Start| 120x90 | https://i1.ytimg.com/vi/<VIDEO ID>/1.jpg| | Middle| 120x90 | https://i1.ytimg.com/vi/<VIDEO ID>/2.jpg| | End| 120x90 | https://i1.ytimg.com/vi/<VIDEO ID>/3.jpg| | High Quality| 480x360 | https://i1.ytimg.com/vi/<VIDEO ID>/hqdefault.jpg | | Medium Quality| 320x180 | https://i1.ytimg.com/vi/<VIDEO ID>/mqdefault.jpg | | Normal Quality| 120x90 | https://i1.ytimg.com/vi/<VIDEO ID>/default.jpg| 以下的縮圖大小是不保證會出現的: | Thumbnail Name| Size (px) | URL| |---| | Standard Definition | 640x480 | https://i1.ytimg.com/vi/<VIDEO ID>/sddefault.jpg| | Maximum Resolution | 1920x1080 | https://i1.ytimg.com/vi/<VIDEO ID>/maxresdefault.jpg | 在實際使用起來時必需要小心啊 ! > 參考資料 : https://stackoverflow.com/questions/2068344/how-do-i-get-a-youtube-video-thumbnail-from-the-youtube-api
最新因為 Project 需要的關係,所以要為原本的 Server Project 加入一個 OAuth2 的功能。 ### OAuth2 RFC6749 詳細說明了 OAuth2 的概念 : https://tools.ietf.org/html/rfc6749 如果上面太難睇得明,可以睇下面這篇 : https://www.digitalocean.com/community/tutorials/an-introduction-to-oauth-2 ### OAuth2 角色 OAuth定義了四個角色 : - 資源所有者 (Resource Owner) - 客戶 (Client) - 資源服務器 (Resource Server) - 授權服務器 (Authorization Server) ##### 資源所有者:用戶 資源所有者是授權應用程序訪問其帳戶的用戶。 該應用程序對用戶帳戶的訪問僅限於所授予授權的“範圍”(例如,讀取或寫入訪問權限)。 ##### 資源/授權服務器:API 資源服務器託管受保護的用戶帳戶,授權服務器驗證用戶的身份,然後向應用程序頒發訪問令牌。 從應用程序開發人員的角度來看,服務的API既擔當資源角色又擔當授權服務器角色。 我們將把這兩個角色結合在一起,稱為服務或API角色。 ##### 客戶:申請 客戶端是想要訪問用戶帳戶的應用程序。 在此之前,必須先由用戶授權,並且必須由API驗證授權。 ### OAuth2 流程 1. 應用程序請求用戶授權訪問服務資源 2. 如果用戶授權了該請求,則應用程序將獲得授權 3. 應用程序通過提供其自身身份的身份驗證和授權授權,從授權服務器(API)請求訪問令牌 (access token) 4. 如果應用程序身份經過驗證並且授權授予有效,那麼授權服務器(API)會向該應用程序發出訪問令牌。 授權完成。 5. 該應用程序從資源服務器(API)請求資源,並提供用於身份驗證的訪問令牌 6. 如果訪問令牌有效,則資源服務器(API)將資源提供給應用程序 該過程的實際流程將根據所使用的授權授予類型而有所不同,但這是一般性的想法。 ### 申請註冊 在對應用程序使用 OAuth 之前,您必須在服務中註冊應用程序。這是通過服務網站 “開發人員” 或 “ API” 部分中的註冊表格完成的,您將在其中提供以下信息(可能還包括有關您的應用程序的詳細信息): - 應用名稱 - 申請網站 - 重定向 URI 或回調 URL 重定向 URI 是服務在用戶授權(或拒絕)您的應用程序後重定向用戶的位置,因此,應用程序中將處理授權代碼或訪問令牌的部分。 ##### 客戶編號和客戶密碼 註冊您的應用程序後,該服務將以客戶端標識符和客戶端機密的形式發布“客戶端憑據”。客戶端 ID 是服務API用於標識應用程序的公開字符串,還用於構建提供給用戶的授權 URL。客戶密碼用於在應用程序請求訪問用戶帳戶時向服務 API 驗證應用程序的身份,並且必須在應用程序和 API 之間保持私密狀態。 ### 授權 在上面的流程中,前四個步驟涉及獲得授權授權和訪問令牌。 授權授予類型取決於應用程序用於請求授權的方法以及 API 支持的授予類型。 OAuth2 定義了四種授權類型,每種類型在不同情況下都很有用: - 授權代碼:與服務器端應用程序一起使用 - 隱式:與移動應用程序或Web應用程序(在用戶設備上運行的應用程序)一起使用 - 資源所有者密碼憑證:與受信任的應用程序一起使用,例如服務本身擁有的那些 - 客戶端憑據:與應用程序 API 訪問一起使用 ### 實作 由於筆者 Project 現在需要的只是第三方 Server 部份要讀取現時 System 的使用者資料,所以這次只需要使用 "授權代碼" 方式授權就可解決問題了。 關於要如何實作 "授權代碼",可以直接參考 GOOGLE 自家的 OAuth2 方法 GOOGLE: [Using OAuth 2.0 for Web Server Applications](https://developers.google.com/identity/protocols/OAuth2WebServer) 這絶對是可以完全照著流程實作就可以 !
曼谷遊的第四天,今天訂了 Local Tour 跟團遊大城。 如果想了解更多大城的歷史和 KKDAY 的相關行程,在另一篇文章會有更多資料 : 泰國大城府 https://19site.net/posts/70 ### Ayutthaya 大城是泰國舊有的首都,因為經過戰事的洗禮後,變成文化遺產。那裏有著舊有的泰國文化氣色,是一個十分值得到的地方。 ### 出發 走出去飯店的行道,發覺今天天氣非常好 !! 由於出發地點,是距離在一個地鐵站外的小紅屋,所以我們先到地鐵站坐地鐵去。  然後在 Asoke 坐地鐵到 Phetchaburi 站去集合地點。  出地鐵站後會看到一間賣咖啡的店鋪,然後轉左便會看到長長的電梯。上天梯後便會看到長長的天橋,當走到長長天橋的中間時便會看到遠處有一所紅色小屋。  就會看到遠處的 "紅色小屋",那裏就是今天的集合點。  那裏的人超多的,站著都是等待本地旅行團的遊客。然後就坐車出發 !!! ### 第一站 - 邦芭茵夏宮 這個地方是第五世國皇時建立的皇宮,當中有很多歐式建築風格。    ### 第二站 - 河邊市集午餐 這個市集是在一條公路的側邊,旅遊巴入去時我還以為是進去吃 "超值餐"。後來發現原來是進去一個市集 !!! 裏面有得多本地製作的手工藝品及當地手作的小食 !! 是日的午餐也是在這裏解決 !      ### 第三站 - 帕蒙空博碧大皇宮 吃過午飯後,就要去最主要的目的地 "帕蒙空博碧大皇宮" 了 !         ### 第四站 - 世界文化遺產石碑銘 來的時候這裏的時候,導遊姐姐說這裏有個特別的廁所,這個廁所要收 10 元的。是因為廁所來回播放音樂還有冷氣 ... 而正正在這個音樂廁所的後面,有一個免費的廁所。      ### 第五站 - 涅槃寺 在停車場下車後,行出過一個很舊的市場,便來到這個寺廟。    ### 最後一站 - 拉查達火車夜市 當進入到火車夜市時,真的太可怕了 !!! 門外竟然企了大約 500 人 ... 原來火車夜市已經變成了中國旅行團必到的景點 ... 那邊的人真的多得有點可怕,筆者快快的吃點東西就立即走人 ... 走時還看到有三到四團旅行團在地鐵站上來 ... 每團也有 30 - 40 人 ...
曼谷遊的第三天,因為昨天的行程在太過高強度了,所以今天要去一點舒服的地方。 每天出發前都一定要食個靚早餐,今天也是不會例外的,所以這天到船面一條街,食泰國地道的美食,船面 ! ### 船面 來到泰國都必定要吃一吃船面 !!  由於出門口時已經時大約早上 11 時多了,為免太過晏食飯,所以決定 GRAB 車去,130 BAHT 大約 20 分鐘就到,非常方便。 話說今次來泰國是沒有想過再去食船面的,因為上次食完一次後的經驗不是太好。第一次食船面是在 2016 年,那時和太太來泰國,剛好在行程上遇到朋友 POLO 哥,我們行完 JJ Market 後,他帶我們去勝利紀念碑站食船面。那一家是在商場內的,湯好飲,面好食,食了很多碗 !!! 但係食食下看到餐廳好多甲由行來行去,之後去比錢時,更加有甲由在收錢枱面走來走去,店員好像很熟的把甲由用手弄走.... 吃完後我和太太先回酒店休息,事後痾了數次... 畢生難忘。 然後上次去是 2018 年,因為船面的味道仲係好難忘,但係唔想再去食甲由面... 所以去了第二家比較出名的 !!! 就是船面一條街最出面那一家了 !! 可是味道真的是差太多... 沒有當年食甲由面那種味道。 今次決定行入一點,吃裏面一點那家,這次好像有驚喜 !!! 因為有很多人在等呢,還要是泰國當地人 !! 人很多但是地方也很大,等了一會就有位置了 !! 先立即點吃的 !!!  第三次食船面也算是有點心得,知道什麼比較岩胃口,所以主力都是叫岩口味的 !   這次十分滿足 !!! 食完之後有另一個景點 !!! 都是在勝利紀念碑站的 !!! ### Kay's Cafe Kay's Cafe 是太太找的一間 Cafe,有什麼特別 ? 是因為店內一個位置放了個花牆作為影相位 !! 女仔一定會為此變心心眼 !       左邊的是 Thai Latte,右邊是 Chococino。   很多當地的年輕男女也來拍拍照片 !!! 我們也拍了很多照片 !! 大約休息一個小時左右,因為日光實在太強的關係,所以決定先回酒店休息一會,晚上再去轉戰第三目的地,霓虹夜市。 ### 霓虹夜市 在 2018 年來的那一次,看到有廣告片在介紹水門夜市 !! 但是行了半天也在水門找不到夜市,後來在一次 GRAB 車時經過一個地方,問問司機原來那裏就是水門夜市 !! 那時知道只會在星期四晚上才會開的。 時至今日,那個夜市命為霓虹夜市,天天開門 !!  不過因為肚子餓的關係,所以決定先去醫醫肚 !!  這裏有個文化是其他夜市沒有的,就是這裡的餐廳都會以 8 折作招來,吸引客人入來消費。如果你的定力足夠的話,他更會開個 75 折給你。所以不用急著進一家餐廳。 最後找了一家 25% off 的吃 !     這個冬陰功湯很好味,少有地在泰國能夠吃到這個水準。  吃飽後因為我們需要乘坐 BTS 的關係,所以也要穿過那條車水馬龍的馬路,向水門西飯那個方向進發 !    在路過水門海南雞飯時,買了兩合回來當宵夜 !
曼谷遊的第二天,起身當然要食返個早餐補充下能量,因為今次住近 Asoke Terminal 21,所以就去返 Pier 21 食個靚早。  食完個靚早,就出發去今日的目的地,大皇宮。 ### 出發大皇宮 Grap 車去大皇宮,話近唔近,話遠都唔遠。  ### 到達 今日天氣晴朗,得好少雲。特別的熱啊 !! 先去買門票 !     前一次來的時間因為泰王離世的關係,所以大皇宮內很多地方也沒有開放出來。事隔兩年,現在開放能進入了。   天氣實在太熱,離開大皇宮後到了一所 cafe 喝喝坐坐回一下體力。  ### 鄭王廟 行回出去大碼頭,只需要比 4 BAHT 就可以坐船到鄭王廟,鄭王廟的日落可是很出名的。另一出名的,是鄭王廟的樓梯很斜的。     ### 回去 行完大皇宮和鄭王廟已經超級累 ... 就拿拿淋 GRAP 車回去 Asoke Terminal 21 按摩去 !!! 然後又去 Pier 21 食晚飯 !!! 因為太累了,相也沒有影。
又到了年尾的時間,當然又是遊泰國的時間。因為 12 月的泰國相對較為涼快,所以差不多每年的 12 月也會去泰國玩玩。今年也是一樣 !! ### 曼谷遊 因為筆者上年和太太遊完泰國之後,發現曼谷實在太多野玩 (及好方便)。於是就計劃今年可以和外父外母一齊來泰國自由行。 在出發之前去了買泰國用的電話卡,AIS 7 日卡只係要 $45 蚊,真係抵到爛 !!! (電話卡個哥仔無介紹錯),下載埋個 GRAP APP,真係 1 GRAP 在手,叫車無難度 ! ### 出發     ### 到達 到達後回到飯店已經是當地時間大約 4:30,時間尚早可以去一轉 Big C 買呢幾日用的日用品。 ### Chit Lom 站  上年去泰國時,新商場已經起好了並已經通行,以後去 Big C 不用再由 Central World 轉個大圈才能過去。   在未去 Big C 前先去水門食個飯醫下肚仔先,點知下橋後又見到新商場 !!   吃飽後決定到新的商場走走。  商場的格區主要也是小店居多,有吃的也有買衣服的。 走過這間 AIIZ (連鎖服裝店),被排得非常整齊的衣服吸引住了 !    不知道是否因為節日關係,Central World 對出的空地現在變成了夜市 !!  行完夜市就回去 Big C 購物 !!  ### 回飯店 回到飯店前到了 7 仔買了個三文字吃 ! 這個超好吃,每天也要必吃一個 !  開電視看看泰國新聞,看到有報道有關香港的 "和你 SHOP" 活動,驚方又出來濫捕。  
每次要裝新機時,都會有機會做一次這個動作。 ### 檢查主機時間 可以透過使用以下指令取機主機現時時間: ```sh $ timedatectl ``` 應該會有類似以下的結果 : ```sh Local time: Thu 2020-01-02 04:32:15 UTC Universal time: Thu 2020-01-02 04:32:15 UTC RTC time: Thu 2020-01-02 04:32:15 Time zone: Etc/UTC (UTC, +0000) System clock synchronized: yes systemd-timesyncd.service active: yes RTC in local TZ: no ``` 預設應該是使用 UTC 的設定。 其實系統的時間是在 `/etc/localtime` 設定的,我們可以查看一下它: ```sh $ ls -l /etc/localtime ``` 會看到它是一個 Symbolic Link 指向一個 zoneinfo 檔案。 ```sh lrwxrwxrwx 1 root root 27 Oct 3 06:18 /etc/localtime -> /usr/share/zoneinfo/Etc/UTC ``` ### 使用 timedatectl 來變更 timezone 設定 首先我們可以透過以下指令取得可以使用的 timezone : ```sh $ timedatectl list-timezones ``` 會列出大量的結果: ```sh Africa/Banjul Africa/Bissau Africa/Blantyre Africa/Brazzaville Africa/Bujumbura Africa/Cairo Africa/Casablanca Africa/Ceuta Africa/Conakry Africa/Dakar Africa/Dar_es_Salaam ... ``` 然後使用 `timedatectl` 變更時區: ```sh $ sudo timedatectl set-timezone Asia/Hong_Kong ``` 上面的指令是變更時區到香港,如要變更其他地方時區則只要填上其他地方便可以。 然後可以再次使用 `timedatectl` 查檢變更後的時區 : ```sh $ timedatectl ``` 現在變更為 Asia/Hong_Kong 了。 ```sh Local time: Thu 2020-01-02 12:49:30 HKT Universal time: Thu 2020-01-02 04:49:30 UTC RTC time: Thu 2020-01-02 04:49:30 Time zone: Asia/Hong_Kong (HKT, +0800) System clock synchronized: yes systemd-timesyncd.service active: yes RTC in local TZ: no ```