# Node.js ## 目錄 [TOC] ## 1、Node.js 介紹 ### <i class="fa fa-arrow-circle-right"></i> V8 JavaScript引擎 1. Google團隊所開發 2. Chrome內含V8 3. 解讀執行JavaScript程式 #### 【補充】deno v.s. node.js {%youtube M3BM9TB-8yA %} [Node.js 開發之父:「十個Node.js 的設計錯誤」](https://m.oursky.com/node-js-%E9%96%8B%E7%99%BC%E4%B9%8B%E7%88%B6-%E5%8D%81%E5%80%8Bnode-js-%E7%9A%84%E8%A8%AD%E8%A8%88%E9%8C%AF%E8%AA%A4-%E4%BB%A5%E5%8F%8A%E5%85%B6%E7%B5%82%E6%A5%B5%E8%A7%A3%E6%B1%BA%E8%BE%A6%E6%B3%95-f0db0afb496e) ### <i class="fa fa-arrow-circle-right"></i> [Node.js](https://github.com/nodejs/node)架構介紹 * Node.js主要是來做後端的 * 後端開發 * php * Java * Python * ASP.NET * Node.js優勢 1. 性能 2. 與前端JavaScript配合方便 3. Node.js方便前端學習 * [node原始碼](https://github.com/nodejs/node) * deps→Node.js用到的插件 * src→Node.js仰賴V8寫的C++程式 (可以當作Node.js的核心程式碼) * lib→Node.js的API ### <i class="fa fa-arrow-circle-right"></i> 命令提示字元操作 #### 電腦裡的終端機(命令提示字元) * Mac: terminal.app (ctrl+空白 搜尋 terminal) * Win: cmd.exe (開始→尋找 cmd) * ConEmu、Cmder * win 10 / win 11 Microsoft Store - [terminal](https://www.microsoft.com/store/productId/9N0DX20HK701) #### 熟悉指令 1. 移動路徑 ```bash cd 移動路徑 ``` 2. 回到上一層 ```bash cd .. ``` 3. 瀏覽所在目錄 * Win ```bash dir ``` * Mac ```bash ls ``` ### <i class="fa fa-arrow-circle-right"></i> 安裝[Node.js](https://nodejs.org/en/) * node起手式指令 1. 查詢node.js版本 ```bash node --version ``` ### <i class="fa fa-arrow-circle-right"></i> 使用Node.js開啟編譯核心 ```bash node ``` 進入到node.js編譯核心,可以在指令列中直接撰寫JavaScript (就像是打開Chrome瀏覽器的console功能寫JavaScript) * 離開此模式 * `Ctrl+C`兩次 ### <i class="fa fa-arrow-circle-right"></i> 透過Node.js執行js檔案 ```bash node js檔案名稱(含副檔名) ``` ### <i class="fa fa-arrow-circle-right"></i> 使用VS Code執行與除錯Node.js ## 2、Node模組原理 ### <i class="fa fa-arrow-circle-right"></i> Global全域物件 #### Window * 開啟網頁就會有一個Window的全域物件 #### Global (與Window物件概念相同) * 對應開js檔案所繼承的資料 * 變數要被Global繼承必須要寫成 ```javascript Global.a = 1; //以下寫法並不能被Global繼承 //此變數僅在該js裡使用 //原因是Node.js設計上就是一個js檔案就是一個模組,所以不允許模組汙染到Global var a = 1; ``` ### <i class="fa fa-arrow-circle-right"></i> VS Code執行Node.js應用程式 * VS Code有整合終端機功能 * 檢視→整合終端機 ### <i class="fa fa-arrow-circle-right"></i> require、module exports模組設計 * app.js ```javascript var a = 1; console.log(a); console.log(data); ``` * data.js ```javascript var data = 2; ``` 如果在`app.js`中`console.log(data)`是不會印出2 * 因為node.js==模組化規則==很嚴格 --- #### 修改1 * app.js ```javascript var content = require('./data'); var a = 1; console.log(a); console.log(content); ``` * data.js ```javascript var data = 2; ``` **此時會印出** ```bash 1 {} ``` --- #### 修改2 * app.js ```javascript var content = require('./data'); var a = 1; console.log(a); console.log(content); ``` * data.js ```javascript var data = 2; module.exports = data;// ``` **此時會印出** ```bash 1 2 ``` #### module.exports導出模組資料 ### <i class="fa fa-arrow-circle-right"></i> exports模組設計 ```javascript exports.data = 2; //等同以下寫法 module.exports = { data = 2 } ``` * 也可以是一個`function` ```javascript exports.data = 2; exports.bark = function(){ return 'bark!!!'; } ``` :::info `exports`與`module.exports`不能混用會互相覆蓋 ::: ### <i class="fa fa-arrow-circle-right"></i> Node核心模組http - createServer ```javascript var http = require('http');//載入(引用)node.js http模組 http.createServer(function (request,response) { //request 當使用者讀取到網站時,就會發送瀏覽請求並且提供相關資料 //response 收到資料,回傳資料 response.writeHead(200,{"Content-Type":"text/plain"}); response.write('Hello'); response.end(); }).listen(8080); //ES6寫法 const http = require('http'); http.createServer((req,res) =>{ console.log('有人來訪問我了'); }); ``` #### 監聽 - 等著 ```javascript const http = require('http'); var server = http.createServer(function(request,response) { console.log('有人來訪問我了'); }); //監聽-等著 //端口-數字 server.listen(8080); ``` #### request 請求 (輸入-請求的訊息) > request.url #### response 響應 (輸出-輸出給瀏覽器的東西) ```javascript response.write(); response.end(); ``` #### port(通訊埠) * `127.0.0.1` or `localhost`用自己電腦所開啟的web server * 開啟自己電腦內部的伺服器 ### <i class="fa fa-arrow-circle-right"></i> __dirname、__filename #### __dirname #### __filename ### <i class="fa fa-arrow-circle-right"></i> Node模組-path ```javascript var path = require('path'); // 抓目錄路徑 console.log(path.dirname('/xx/yy/zz.js')); // 回傳 /xx/yy // 路徑合併 console.log(path.join(__dirname,'/xx')); // 回傳 前後路徑合併 // 抓檔名 console.log(path.basename('/xx/yy/zz.js')); // 回傳 zz.js // 抓副檔名 console.log(path.extname('/xx/yy/zz.js')); // 回傳 js // 解(分)析路徑 console.log(path.parse('/xx/yy/zz.js')); // 回傳 上述綜合物件 ``` ## 3、NPM (Node Package Manager) ### <i class="fa fa-arrow-circle-right"></i> 什麼是[NPM (Node Package Manager)](https://www.npmjs.com) * 查詢npm版本 ```bash npm -v ``` ### <i class="fa fa-arrow-circle-right"></i> npm init開發自己的package.json #### package.json檔 * 新增package.json ```bash npm init ``` ### <i class="fa fa-arrow-circle-right"></i> npm安裝流程 * 記得要先移動到**相對應的專案資料夾**中 ```bash npm install express --save ``` * Mac版本需要前面加`sudo` ### <i class="fa fa-arrow-circle-right"></i> npm版本號介紹 說明範例:1.12.0 * 1: 主要版本號 * 12:次要版本號 * 0: bug修正 * ^: 安裝次要、bug修正的版本 (1.x.x) * ~: 安裝bug修正的版本 (1.12.x) * 沒有任何符號:指定對應版本 (1.12.0) * latest: 永遠都會載入最新的版本 ### <i class="fa fa-arrow-circle-right"></i> npm install的妙用 * node_modules不會進入到版本控制 (忽略) * 透過指令把dependencies中相對應的套件安裝回來 ```bash npm install ``` ### <i class="fa fa-arrow-circle-right"></i> --save、--save-dev、-g差異 #### --save `node`應用程式上線會用到的`npm`套件 * 能夠有效紀錄開發過程中所用到的套件 #### --save-dev 只是在開發過程中用來測試或除錯的套件(非主要模組),不會影響你的應用程式上線功能 * devDependencies #### -g (全域) 安裝路徑 > Win: C:\Users[使用者名稱]\AppData\Roming\npm\node_modules > Mac: usr/local/lib/node_modules ### <i class="fa fa-arrow-circle-right"></i> 執行NPM內容流程 #### nodemon套件 ```bash nodemon 檔案名稱 ``` 在開發過程中可以不用一直輸入指令執行修改後的js檔 ### <i class="fa fa-arrow-circle-right"></i> NPM常用指令小抄 * npm -v :觀看 NPM 版本 * npm init :新增 package.json * npm install [模組名稱][安裝位置] :安裝 NPM 模組,安裝位置常用屬性如下: * -g 全域安裝 * --save 安裝模組並寫入 package.json 的 "dependencies" * --save-dev 安裝模組並寫入 package.json 的 "devDependencies" * npm list :顯示安裝的 NPM 列表 * npm uninstall [模組名稱] :刪除專案裡的 NPM ## 4、Node除錯 ### <i class="fa fa-arrow-circle-right"></i> log探索 ### <i class="fa fa-arrow-circle-right"></i> Node內建除錯模組 ```javascript var a = 1; var b = 1; var c = 1; ``` ```bash node debug js檔案名稱 ``` * n (next的意思) * repl (在debug模式下觀察上下文環境內容) * ctrl+c結束 ```javascript var a = 1; var b = 1; var c = 1; debugger a = 3; a = 4; debugger ``` * c (cont)跳到debugger那行去 ### <i class="fa fa-arrow-circle-right"></i> node內建chrome dev tools ```bash node --inspect --debug-brk js檔案名稱 ``` * --inspect 開啟chrome去看當下js的狀態 * --debug-brk 可以中斷在程式第一行的位置上 * 複製chrome-devtools:...,貼到chrome瀏覽器中 ### <i class="fa fa-arrow-circle-right"></i> VS Code進階除錯 ### <i class="fa fa-arrow-circle-right"></i> VS Code定義瀏覽 * 移至定義 * 預覽定義 ## 5、LINE Bot機器人開發 ### 參考資料 - [建立 LINE Channel](https://steam.oxxostudio.tw/category/python/example/line-developer.html) - [什麼是 Webhook?](https://steam.oxxostudio.tw/category/python/example/line-webhook.html) ### 開發準備 - [LINE Developers](https://developers.line.biz/zh-hant/) - [LINE 官方帳號](https://tw.linebiz.com/login/) - 開發套件 [Bottender](https://bottender.js.org/) - 讓外網連接伺服器[ngrok](https://ngrok.com) - API 工具 [Postman](https://www.postman.com/) ### 1. 登入LINE Developers建立Provider ![image](https://hackmd.io/_uploads/SJWlq-VN0.png) - 第一次登入的話應該會是空的 ![image](https://hackmd.io/_uploads/S1R49ZN4A.png) - 點擊Create New Provider - 輸入Provider name ![image](https://hackmd.io/_uploads/H10Da8NVC.png) - 選擇要建立的Channel (選擇 `Create a Messaging API channel`) - LINE Login - Messaging API - Blockchain Service - LINE MINI App ![image](https://hackmd.io/_uploads/Sk75ALE4A.png) - 輸入相關必要資訊 - 建立完成後可以再對應的Provider看到Channel ![image](https://hackmd.io/_uploads/ry9RRIEER.png) :::warning 補充說明: 建立完Messaging API後,LINE現在會自動使用該Channel的名稱建立一個官方帳號,所以不用自己建。 如果發現官方帳號的地方,沒有出現跟Messaging API這個Channel名稱一樣的官方帳號,請同學執行底下第2個步驟 ::: ### 2. 登入[LINE Official Account](https://tw.linebiz.com/login/)建立官方帳號 - 選擇**LINE官方帳號管理頁面**→**登入管理頁面** ![2024_05_29_15_55_58_LINE_官方帳號管理頁面_LINE_Biz_Solutions](https://hackmd.io/_uploads/r19YxvN40.jpg) - 建立官方帳號 (填寫相關資訊) - 官方帳號建立完成後,可以在【帳號一覽】中看到建立好的官方帳號 ![image](https://hackmd.io/_uploads/H1CzbPVN0.png) - 點擊進入建立好的官方帳號 ![image](https://hackmd.io/_uploads/rkkKZwEVC.png) - 選擇【自動回應訊息】,【關閉】自動回應訊息的狀態,避免每次跟 LINE BOT 聊天時,都會跳出自動回應的訊息。 ![2024_05_29_16_06_13_LINE_Official_Account_Manager](https://hackmd.io/_uploads/rk2QGP44R.jpg) - 點選右上角【設定】進入設定頁面 → 選擇左邊選單中的 【Messaging API】 ![image](https://hackmd.io/_uploads/SyitzvNN0.png) - 點選【啟用Messaging API】 → 選擇第一步驟建立好的服務提供者 (Provider) ![image](https://hackmd.io/_uploads/SyMZmwNEC.png) ### LINE BOT 與 Webhook 的關係 - 參考圖 ![image](https://hackmd.io/_uploads/rJkWIv4ER.png) - 當使用者在 LINE 聊天室裡跟 LINE BOT 聊天,會發生下列的步驟: - Step 1:向使用 Message API 所建立的 LINE BOT 發送訊息。 - Step 2:訊息透過 Webhook 傳遞到使用者部署 Python 程式的伺服器。 - Step 3:根據 後端伺服器程式的邏輯,處理訊息。 - Step 4:透過 Webhook 回傳結果到 LINE BOT。 - Step 5:LINE BOT 發送訊息到 LINE 聊天室裡。 ### 建立後端程式 (Webhook) - 複製bottender-express程式道專案資料夾 - 執行`npm install` - 修改bottender.config.js ```javascript= module.exports = { channels: { messenger: { enabled: false, path: '/webhooks/messenger', pageId: process.env.MESSENGER_PAGE_ID, accessToken: process.env.MESSENGER_ACCESS_TOKEN, appId: process.env.MESSENGER_APP_ID, appSecret: process.env.MESSENGER_APP_SECRET, verifyToken: process.env.MESSENGER_VERIFY_TOKEN, }, line: { enabled: true, path: '/webhooks/line', accessToken: process.env.LINE_ACCESS_TOKEN, channelSecret: process.env.LINE_CHANNEL_SECRET, }, telegram: { enabled: false, path: '/webhooks/telegram', accessToken: process.env.TELEGRAM_ACCESS_TOKEN, }, slack: { enabled: false, path: '/webhooks/slack', accessToken: process.env.SLACK_ACCESS_TOKEN, verificationToken: process.env.SLACK_VERIFICATION_TOKEN, }, viber: { enabled: false, path: '/webhooks/viber', accessToken: process.env.VIBER_ACCESS_TOKEN, sender: { name: 'xxxx', }, }, }, }; ``` - 設定.env檔案中的`LINE_ACCESS_TOKEN`以及`LINE_CHANNEL_SECRET` - LINE_ACCESS_TOKEN - LINE Developers → 選擇對應的Channel → 選擇Messagning API頁籤拉到最下面有一個`Channel access token`,點選Issue產生出一串文字金鑰 - 將那串文字複製起來貼到.env的LINE_ACCESS_TOKEN後面 - LINE_CHANNEL_SECRET - LINE Developers → 選擇對應的Channel → 選擇Basic setting頁籤 - 找到Channel secret將金鑰文字複製起來貼到.env檔案的LINE_CHANNEL_SECRET後面 ```env= MESSENGER_PAGE_ID= MESSENGER_ACCESS_TOKEN= MESSENGER_APP_ID= MESSENGER_APP_SECRET= MESSENGER_VERIFY_TOKEN= LINE_ACCESS_TOKEN=h5XPHUQTn5Tz9/F4+WC1dzXE0r/ObCRIyUYd+T79FigMII4vtZyd2VvPukqq5+JWt2U7fSKCGZoOGndx14XiuBje7vmQWrONBxWrwr6bV0Kq1KtQH3tW/rUKZy7sL3sypPlnrJwf+spKeR5/C7jGizdvdsvszcfbsfvbv LINE_CHANNEL_SECRET=69b32c7e8636c2vadvadvadvf325989c2 TELEGRAM_ACCESS_TOKEN= SLACK_ACCESS_TOKEN= SLACK_VERIFICATION_TOKEN= VIBER_ACCESS_TOKEN= ``` - 執行程式 ```bash= npm run dev ``` ### ngrok - [註冊帳號](https://dashboard.ngrok.com/signup) - 完成註冊後登入,進入到帳號主控台頁面,左側選擇【Your Authtoken】 ![2024_05_30_11_37_02_](https://hackmd.io/_uploads/HkuiVdrV0.jpg) - 執行ngrok.exe將紅色框起來地方的程式複製貼進去執行 ![2024_05_30_11_38_54_](https://hackmd.io/_uploads/HJ8-BdH4A.jpg) - 執行完畢後輸入以下程式 ```bash= ngrok http 5000 ``` ### 設定Webhook 1. 請同學到LINE Developers選擇建立好的Provider的Messaging API Channel 2. 將ngrok所產生出來的網址加上`/webhooks/line`,例如`https://xxx.xxx.xxx/webhooks/line` 3. 在Webhook URL的地方把上述網址貼上 4. Use webhook記得要打開 ![2024_06_06_09_16_42_](https://hackmd.io/_uploads/HkTZycRNR.jpg) ### 3. 程式串接與撰寫(使用Bottender、ngrok) #### index.js ```javascript= module.exports = async function App(context) { let userInfo = await context.getUserProfile(); const userName = userInfo.displayName; if (context.event.isText) { // handling the text message event // console.log(`user info: ${JSON.stringify(userInfo)}`); let jsonObj = { "type": "carousel", "contents": [ { "type": "bubble", "size": "micro", "hero": { "type": "image", "url": "https://cycu-me.org/upload/fac_member_list_pic/twL_fac_member_21B27_sl9rv9Hs1G.jpg", "size": "full", "aspectMode": "cover", "aspectRatio": "320:213" }, "body": { "type": "box", "layout": "vertical", "contents": [ { "type": "text", "text": "陳夏宗 教授", "weight": "bold", "size": "sm", "wrap": true }, { "type": "text", "text": "shiachun@cycu.edu.tw", "weight": "bold", "size": "sm", "wrap": true } ], "spacing": "sm", "paddingAll": "13px" } }, { "type": "bubble", "size": "micro", "hero": { "type": "image", "url": "https://cycu-me.org/upload/fac_member_list_pic/twL_fac_member_21C11_j9Me4vCfFU.jpg", "size": "full", "aspectMode": "cover", "aspectRatio": "320:213" }, "body": { "type": "box", "layout": "vertical", "contents": [ { "type": "text", "text": "許政行 教授", "weight": "bold", "size": "sm", "wrap": true }, { "type": "text", "text": "chhsu@cycu.edu.tw", "weight": "bold", "size": "sm", "wrap": true } ], "spacing": "sm", "paddingAll": "13px" } }, { "type": "bubble", "size": "micro", "hero": { "type": "image", "url": "https://cycu-me.org/upload/fac_member_list_pic/twL_fac_member_21B24_baCL7oICh3.jpg", "size": "full", "aspectMode": "cover", "aspectRatio": "320:213" }, "body": { "type": "box", "layout": "vertical", "contents": [ { "type": "text", "text": "康淵 教授", "weight": "bold", "size": "sm", "wrap": true }, { "type": "text", "text": "yk@cycu.edu.tw", "weight": "bold", "size": "sm", "wrap": true } ], "spacing": "sm", "paddingAll": "13px" } }, { "type": "bubble", "size": "micro", "hero": { "type": "image", "url": "https://cycu-me.org/upload/fac_member_list_pic/twL_fac_member_21B24_6F6jP39wom.jpg", "size": "full", "aspectMode": "cover", "aspectRatio": "320:213" }, "body": { "type": "box", "layout": "vertical", "contents": [ { "type": "text", "text": "鍾文仁 教授", "weight": "bold", "size": "sm", "wrap": true }, { "type": "text", "text": "wenren@cycu.edu.tw", "weight": "bold", "size": "sm", "wrap": true } ], "spacing": "sm", "paddingAll": "13px" } }, { "type": "bubble", "size": "micro", "hero": { "type": "image", "url": "https://cycu-me.org/upload/fac_member_list_pic/twL_fac_member_21B24_4ZHU29sjXu.jpg", "size": "full", "aspectMode": "cover", "aspectRatio": "320:213" }, "body": { "type": "box", "layout": "vertical", "contents": [ { "type": "text", "text": "丁鏞 教授", "weight": "bold", "size": "sm", "wrap": true }, { "type": "text", "text": "yung@cycu.edu.tw", "weight": "bold", "size": "sm", "wrap": true } ], "spacing": "sm", "paddingAll": "13px" } }, { "type": "bubble", "size": "micro", "hero": { "type": "image", "url": "https://cycu-me.org/upload/fac_member_list_pic/twL_fac_member_21B24_v12kVehuaS.jpg", "size": "full", "aspectMode": "cover", "aspectRatio": "320:213" }, "body": { "type": "box", "layout": "vertical", "contents": [ { "type": "text", "text": "張耀仁 教授", "weight": "bold", "size": "sm", "wrap": true }, { "type": "text", "text": "justin@cycu.edu.tw", "weight": "bold", "size": "sm", "wrap": true } ], "spacing": "sm", "paddingAll": "13px" } }, { "type": "bubble", "size": "micro", "hero": { "type": "image", "url": "https://cycu-me.org/upload/fac_member_list_pic/twL_fac_member_21B24_jjL041ZS8e.jpg", "size": "full", "aspectMode": "cover", "aspectRatio": "320:213" }, "body": { "type": "box", "layout": "vertical", "contents": [ { "type": "text", "text": "陳冠宇 教授", "weight": "bold", "size": "sm", "wrap": true }, { "type": "text", "text": "gychen@cycu.edu.tw", "weight": "bold", "size": "sm", "wrap": true } ], "spacing": "sm", "paddingAll": "13px" } }, { "type": "bubble", "size": "micro", "hero": { "type": "image", "url": "https://cycu-me.org/upload/fac_member_list_pic/twL_fac_member_21B24_6RKPS0lwmo.jpg", "size": "full", "aspectMode": "cover", "aspectRatio": "320:213" }, "body": { "type": "box", "layout": "vertical", "contents": [ { "type": "text", "text": "范憶華 教授", "weight": "bold", "size": "sm", "wrap": true }, { "type": "text", "text": "yihuafan@cycu.edu.tw", "weight": "bold", "size": "sm", "wrap": true } ], "spacing": "sm", "paddingAll": "13px" } } ] }; let jsonObj2 = { "type": "carousel", "contents": [ { "type": "bubble", "size": "micro", "hero": { "type": "image", "url": "https://cycu-me.org/upload/fac_member_list_pic/twL_fac_member_21B24_WjkRCaWpY7.jpg", "size": "full", "aspectMode": "cover", "aspectRatio": "320:213" }, "body": { "type": "box", "layout": "vertical", "contents": [ { "type": "text", "text": "李有璋 教授", "weight": "bold", "size": "sm", "wrap": true }, { "type": "text", "text": "yclee@cycu.edu.tw", "weight": "bold", "size": "sm", "wrap": true } ], "spacing": "sm", "paddingAll": "13px" } }, { "type": "bubble", "size": "micro", "hero": { "type": "image", "url": "https://cycu-me.org/upload/fac_member_list_pic/twL_fac_member_21B25_eiQgDNp8CQ.jpg", "size": "full", "aspectMode": "cover", "aspectRatio": "320:213" }, "body": { "type": "box", "layout": "vertical", "contents": [ { "type": "text", "text": "翁輝竹 教授", "weight": "bold", "size": "sm", "wrap": true }, { "type": "text", "text": "hcweng@cycu.edu.tw", "weight": "bold", "size": "sm", "wrap": true } ], "spacing": "sm", "paddingAll": "13px" } }, { "type": "bubble", "size": "micro", "hero": { "type": "image", "url": "https://cycu-me.org/upload/fac_member_list_pic/twL_fac_member_21B24_XeccGWymN5.jpg", "size": "full", "aspectMode": "cover", "aspectRatio": "320:213" }, "body": { "type": "box", "layout": "vertical", "contents": [ { "type": "text", "text": "黃信行 教授", "weight": "bold", "size": "sm", "wrap": true }, { "type": "text", "text": "hhh@cycu.edu.tw", "weight": "bold", "size": "sm", "wrap": true } ], "spacing": "sm", "paddingAll": "13px" } }, { "type": "bubble", "size": "micro", "hero": { "type": "image", "url": "https://cycu-me.org/upload/fac_member_list_pic/twL_fac_member_21B24_kieyumNnqO.jpg", "size": "full", "aspectMode": "cover", "aspectRatio": "320:213" }, "body": { "type": "box", "layout": "vertical", "contents": [ { "type": "text", "text": "吳政達 教授", "weight": "bold", "size": "sm", "wrap": true }, { "type": "text", "text": "nanowu@cycu.edu.tw", "weight": "bold", "size": "sm", "wrap": true } ], "spacing": "sm", "paddingAll": "13px" } }, { "type": "bubble", "size": "micro", "hero": { "type": "image", "url": "https://cycu-me.org/upload/fac_member_list_pic/twL_fac_member_21B24_8BHUeGF7iL.jpg", "size": "full", "aspectMode": "cover", "aspectRatio": "320:213" }, "body": { "type": "box", "layout": "vertical", "contents": [ { "type": "text", "text": "廖川傑 教授", "weight": "bold", "size": "sm", "wrap": true }, { "type": "text", "text": "chuanchiehliao@cycu.edu.tw", "weight": "bold", "size": "sm", "wrap": true } ], "spacing": "sm", "paddingAll": "13px" } }, { "type": "bubble", "size": "micro", "hero": { "type": "image", "url": "https://cycu-me.org/upload/fac_member_list_pic/twL_fac_member_21B24_wEqdEN87mx.jpg", "size": "full", "aspectMode": "cover", "aspectRatio": "320:213" }, "body": { "type": "box", "layout": "vertical", "contents": [ { "type": "text", "text": "林明璋 副教授", "weight": "bold", "size": "sm", "wrap": true }, { "type": "text", "text": "mclin@cycu.edu.tw", "weight": "bold", "size": "sm", "wrap": true } ], "spacing": "sm", "paddingAll": "13px" } }, { "type": "bubble", "size": "micro", "hero": { "type": "image", "url": "https://cycu-me.org/upload/fac_member_list_pic/twL_fac_member_21B24_ekYkM8gaEH.jpg", "size": "full", "aspectMode": "cover", "aspectRatio": "320:213" }, "body": { "type": "box", "layout": "vertical", "contents": [ { "type": "text", "text": "胡聖彥 助理教授", "weight": "bold", "size": "sm", "wrap": true }, { "type": "text", "text": "shengyenhu@cycu.edu.tw", "weight": "bold", "size": "sm", "wrap": true } ], "spacing": "sm", "paddingAll": "13px" } }, { "type": "bubble", "size": "micro", "hero": { "type": "image", "url": "https://cycu-me.org/upload/fac_member_list_pic/twL_fac_member_21B24_BCNYxN2mEQ.jpg", "size": "full", "aspectMode": "cover", "aspectRatio": "320:213" }, "body": { "type": "box", "layout": "vertical", "contents": [ { "type": "text", "text": "杜哲怡 助理教授", "weight": "bold", "size": "sm", "wrap": true }, { "type": "text", "text": "ann@cycu.edu.tw", "weight": "bold", "size": "sm", "wrap": true } ], "spacing": "sm", "paddingAll": "13px" } } ] }; let jsonObj3 = { "type": "carousel", "contents": [ { "type": "bubble", "size": "micro", "hero": { "type": "image", "url": "https://cycu-me.org/upload/fac_member_list_pic/twL_fac_member_21B24_tKoL6Ybsdc.jpg", "size": "full", "aspectMode": "cover", "aspectRatio": "320:213" }, "body": { "type": "box", "layout": "vertical", "contents": [ { "type": "text", "text": "魏福勝 助理教授", "weight": "bold", "size": "sm", "wrap": true }, { "type": "text", "text": "harrywey@cycu.edu.tw", "weight": "bold", "size": "sm", "wrap": true } ], "spacing": "sm", "paddingAll": "13px" } }, { "type": "bubble", "size": "micro", "hero": { "type": "image", "url": "https://cycu-me.org/upload/fac_member_list_pic/twL_fac_member_21B24_d7gtYv2UhK.jpg", "size": "full", "aspectMode": "cover", "aspectRatio": "320:213" }, "body": { "type": "box", "layout": "vertical", "contents": [ { "type": "text", "text": "李汶墾 助理教授", "weight": "bold", "size": "sm", "wrap": true }, { "type": "text", "text": "wenkenli@cycu.edu.tw", "weight": "bold", "size": "sm", "wrap": true } ], "spacing": "sm", "paddingAll": "13px" } }, { "type": "bubble", "size": "micro", "hero": { "type": "image", "url": "https://cycu-me.org/upload/fac_member_list_pic/twL_fac_member_21B24_lzO72hXEED.jpg", "size": "full", "aspectMode": "cover", "aspectRatio": "320:213" }, "body": { "type": "box", "layout": "vertical", "contents": [ { "type": "text", "text": "黃建勝 助理教授", "weight": "bold", "size": "sm", "wrap": true }, { "type": "text", "text": "jshuang@cycu.edu.tw", "weight": "bold", "size": "sm", "wrap": true } ], "spacing": "sm", "paddingAll": "13px" } }, { "type": "bubble", "size": "micro", "hero": { "type": "image", "url": "https://cycu-me.org/upload/fac_member_list_pic/twL_fac_member_21H02_SeeP0NgnN8.jpg", "size": "full", "aspectMode": "cover", "aspectRatio": "320:213" }, "body": { "type": "box", "layout": "vertical", "contents": [ { "type": "text", "text": "丁郁宏 助理教授", "weight": "bold", "size": "sm", "wrap": true }, { "type": "text", "text": "august@cycu.edu.tw", "weight": "bold", "size": "sm", "wrap": true } ], "spacing": "sm", "paddingAll": "13px" } }, { "type": "bubble", "size": "micro", "hero": { "type": "image", "url": "https://cycu-me.org/upload/fac_member_list_pic/twL_fac_member_23A18_pmpQS8XCNb.jpg", "size": "full", "aspectMode": "cover", "aspectRatio": "320:213" }, "body": { "type": "box", "layout": "vertical", "contents": [ { "type": "text", "text": "鄭年添 助理教授", "weight": "bold", "size": "sm", "wrap": true }, { "type": "text", "text": "tonycheng@cycu.edu.tw", "weight": "bold", "size": "sm", "wrap": true } ], "spacing": "sm", "paddingAll": "13px" } }, { "type": "bubble", "size": "micro", "hero": { "type": "image", "url": "https://cycu-me.org/upload/fac_member_list_pic/twL_fac_member_24A29_RqNx223iiD.jpg", "size": "full", "aspectMode": "cover", "aspectRatio": "320:213" }, "body": { "type": "box", "layout": "vertical", "contents": [ { "type": "text", "text": "簡育欽 助理教授", "weight": "bold", "size": "sm", "wrap": true }, { "type": "text", "text": "ycchien@cycu.edu.tw", "weight": "bold", "size": "sm", "wrap": true } ], "spacing": "sm", "paddingAll": "13px" } } ] }; if(context.event.text ==`機械系老師`) { await context.sendFlex('中原機械教師1', jsonObj); await context.sendFlex('中原機械教師2', jsonObj2); await context.sendFlex('中原機械教師3', jsonObj3); } else { await context.sendText(`${userName}您好!我收到你的文字【${context.event.text}】`); } } else { await context.sendText('我不吃文字以外的資料!'); } }; ``` #### Flex Message -[Flex Message Simulator](https://developers.line.biz/flex-simulator/) ## 6、Google Firebase ### <i class="fa fa-arrow-circle-right"></i> Firebase服務介紹 * [Firebase官方網站](https://firebase.google.com/) * [Firebase資料庫官方文件](https://firebase.google.com/docs/reference/js/firebase.database.Reference?hl=zh-tw) ### <i class="fa fa-arrow-circle-right"></i> 環境介紹 ### <i class="fa fa-arrow-circle-right"></i> 資料庫環境設定 ### <i class="fa fa-arrow-circle-right"></i> ref(路徑)、set(新增) * firebase全部都是物件格式,不能陣列內容 ```javascript var data = null; data = { student1:{ name: 'Tom', num:'1' }, student2:{ name: 'John', num:'2' } }; ``` #### set新增 寫入資料庫的內容可以是**文字**也可以是**物件** #### ref路徑 (類似mongoDB的collection或是SQL的資料表) * ref()沒有內容即為根目錄 * ref('第一層/第二層/...')可以修改對應層級資料 #### 資料庫的設計 (以餐廳為例) * 盡量扁平一點不要寫太多層級 ```javascript var data = null; data = { food:{ coke:{ price:30, num:1 }, fries:{ price:50, num:20 } }, order:{ 1:{ coke:2 }, 2:{ fries:5, coke:2 } } }; ``` ### <i class="fa fa-arrow-circle-right"></i> 顯示資料 * firebase的程式要放到body中 #### once 讀取一次資料庫的資料 ```javascript var myName = firebase.database().ref('myName'); // once 讀取一次資料庫的資料 // on 隨時監聽 myName.once('value',function(snapshot){ console.log(snapshot.val()); var str = snapshot.val(); document.getElementById('title').textContent = str; }); ``` #### on 隨時監聽(資料即時呈現) ### <i class="fa fa-arrow-circle-right"></i> firebase非同步觀念 ### <i class="fa fa-arrow-circle-right"></i> push - 新增資料 ```javascript var todos = firebase.database().ref('todos'); todos.push({content:'今天要記得刷牙'}); ``` ### <i class="fa fa-arrow-circle-right"></i> remove、child移除資料 #### child找出當下的子路徑 ```javascript var todos = firebase.database().ref().child('todos'); //找到根目錄底下的子目錄 ``` #### remove 刪除資料 ### <i class="fa fa-arrow-circle-right"></i> for-in語法 ```javascript var colors = ['red','black','yellow']; // 一般for迴圈寫法 for (var i = 0; color.length > i; i++) { console.log(color[i]) } // for in 寫法 // item會依序撈出陣列索引值 // for (variable in [ object | array]) // { // statements // } for(var item in colors){ console.log(item); } //結果會是0 1 2 ``` * 陣列物件 ```javascript var kaohsiung = [ { father:'Tom', mon:'Mary' }, { father:'John', mom:'Jane' } ]; for (var item in kaohsiung){ console.log(kaohsiung[item].father); } ``` * 物件 * item會是物件屬性 ```javascript var todos = { num1:{ content:'要記得刷牙' }, num2:{ content: '要記得洗澡' } }; for (var item in todos){ console.log(item); } // 結果會是 num1、num2 // 要撈出content // todos[item].content ``` ### <i class="fa fa-arrow-circle-right"></i> 網頁上即時瀏覽firebase資料 ```javascript var ref = firebase.database().ref(); ref.on('value',function(snapshot){ var results = JSON.stringify(snapshot.val(), null, 3); }); ``` ### <i class="fa fa-arrow-circle-right"></i> 排序 * orderByChild * 需搭配forEach語法 ```javascript var peopleRef = firebase.database().ref('people'); peopleRef.orderByChild('weight').once('value',function(snapshot){ snapshot.forEach(function(item){ console.log(item.key); // item.key可以得到父屬性值 console.log(item.val()); }); }); ``` > 抓路徑→排序('屬性')→讀取內容→forEach 依序撈出資料 #### 排序規則 1. 指定子鍵為null的子項目排在最前面 2. 指定子鍵值為false的子項目 3. 指定子鍵值為true的子項目 4. 指定子鍵值為數值,按升序排序 5. 指定子鍵值為字串 6. 指定子鍵值為物件 ### <i class="fa fa-arrow-circle-right"></i> 過濾(搜尋規則) > 抓路徑→排序('屬性')→過濾→讀取內容→forEach 依序撈出資料 * startAt() 多少以上 * endAt() 多少以下 * equalTo() 相等 ### <i class="fa fa-arrow-circle-right"></i> 限制筆數 limit > 抓路徑→排序('屬性')→過濾→限制筆數→讀取內容→forEach 依序撈出資料 * limitToFirst() 從最前面開始撈出資料 * limitToLast() 從最後面開始撈出資料 ### <i class="fa fa-arrow-circle-right"></i> 時間篇 ```javascript var time = new Date(); consolelog(time); consolelog(time.getFullYear()); consolelog(time.getMonth()); //在JavaScript中,0 = 1月 consolelog(time.getDay()); //在JavaScript中,禮拜天 = 0 consolelog(time.getHours()); consolelog(time.getMinutes()); consolelog(time.getSeconds()); consolelog(time.Milliseconds()); // 1000毫秒 = 1秒 ``` * timestamp (時間戳記,時戳) * UNIX時間 - 格林威治時間(GTM)1970年1月1日00:00:00到目前經過的秒數 ```javascript var time = new Date(); time.getTime(); //將帶有日期格式的時間轉換成UNIX時間 var now = new Date(time); //將UNIX時間轉換回帶有日期的格式 ``` ### <i class="fa fa-arrow-circle-right"></i> reverse資料翻轉調整 ```javascript var data = [1, 2, 3, 4, 5]; data.reverse();// 將資料反轉 // 將物件加入陣列後進行reverse反轉排序 var data = []; var todos = { 1234: { content: 'hello' }, 456: { content: 'hi' } }; ``` ## 7、Node.js後端 - Express框架 ### <i class="fa fa-arrow-circle-right"></i> Express框架介紹 * Node.js Web應用框架 * 輕量型 Web 應用框架 * 後端邏輯 * AJAX post * EJS template、jade(pug) ### <i class="fa fa-arrow-circle-right"></i> Express環境安裝 * Win ```bash npm install express --save ``` * Mac ```bash sudo npm install express --save ``` ### <i class="fa fa-arrow-circle-right"></i> 開啟Web伺服器 * 透過Express建立Web伺服器 * app.js ```javascript var express = require('express'); var app = express(); app.get('/', function(request, response){ res.send(''); }); //監聽 port var port = process.env.PORT || 3000; app.listen(port); ``` * process.env.PORT * 環境預設port ### <i class="fa fa-arrow-circle-right"></i> 網址規則 #### Router路由 * https、http協定 * https有加密 * domian網址 * www.google.com.tw * 127.0.0.1 →本地端 * www.cycu.edu.tw * 路徑 * 參數(query) * ?參數1=&參數2=&.... ### <i class="fa fa-arrow-circle-right"></i> Router路由設計 ### <i class="fa fa-arrow-circle-right"></i> 取得指定路徑(params) > 網址 https://www.xxx.xxx/**:路徑參數名稱** > request.params.參數名稱 ```javascript app.get('/user/:name', function(req,res){ var myName = req.params.name; }); ``` ### <i class="fa fa-arrow-circle-right"></i> 取得網址參數(query) > 網址 https://www.xxx.xxx/參數名稱**?網址參數1&網址參數2** > request.query.網址參數名稱 ```javascript //範例:某某人的音樂列表,抓前10筆 app.get('/user/:name', function(req,res){ var myName = req.params.name; var limit = req.query.limit; }); ``` ### <i class="fa fa-arrow-circle-right"></i> Middleware - 中介軟體 ```javascript var express = require('express'); var app = express(); //app.use像是個守門員,要顧及網站安全,必須在前面 app.use(function(req,res,next){ console.log('有人進來了'); //確保沒問題next()進入到下一個關卡(程式) next(); }); ``` #### 404路由設定 * 頁面不存在 ```javascript app.use(function(req,res,next){ res.status(404),send('抱歉,您的頁面找不到'); }); ``` #### 500路由設定 * 程式錯誤 ```javascript app.use(function(err,req,res,next){ console.log(err.stack); res.status(500).send('程式有些問題,請稍後嘗試'); }); ``` #### 不同寫法 ```javascript // Way 1 var login = function(req,res,next){ console.log('你是登入狀態'); next(); }; app.use(login); // Way2 var login = function(req,res,next){ console.log('你是登入狀態'); next(); }; // 給單獨router使用 app.get('/',login,function(req,res,next){ }) ``` ### <i class="fa fa-arrow-circle-right"></i> 載入靜態檔案 (static) > app.use(express.static('資料夾名稱')); * 專案中必須要有對應的資料夾名稱 * 必須寫在最前面,這樣才可以抓到檔案 * 檔案的位置會以該**資料夾為根目錄**往下抓 ```javascript var express = require('express'); var app = express(); // 增加靜態檔案的路徑 // 我要把所有靜態檔案增加到public資料夾上 // 專案中要有public資料夾 // 必須寫在最前面的位置 app.use(express.static('public')); ``` ### <i class="fa fa-arrow-circle-right"></i> Template - EJS語言介紹 #### 樣板語言 #### 環境安裝 ```bash npm install ejs-locals --save ``` ```javascript var engine = require('ejs-locals'); app.engine('ejs',engine); // Express載入ejs作為樣板控制的 app.set('views','./views'); // app.set()設定Express各種設定,這裡是設定ejs檔案路徑位置 app.set('view engine','ejs'); // 指定Expess要用哪一個樣板引擎去跑 ``` * xxx.ejs * res.render('ejs檔案名稱(不用帶附檔名)'); ### <i class="fa fa-arrow-circle-right"></i> 參數導入 * app.js ```javascript app.get('/',function(req,res){ res.render('index', { 'title':'六角學院', 'boss': 'AT' }); }); ``` * index.ejs ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> <%= title %> <!-- =是要轉成字串 --> <h2><%= boss %></h2> </body> </html> ``` ### <i class="fa fa-arrow-circle-right"></i> EJS載入內容種類 * <%= %>渲染成字串 * <%- %>渲染成html格式 * <% %> 程式邏輯 ```html <% if() { %> <span></span> <% } %> <% else { %> <span></span> <% } %> ``` ### <i class="fa fa-arrow-circle-right"></i> EJS載入陣列 ```html <% for (var i=0; course.length;i++) { %> <li><%- course[i] %></li> <% } %> ``` ### <i class="fa fa-arrow-circle-right"></i> EJS設定Layout * layout.ejs ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> <%- body %> </body> </html> ``` * index.ejs ```html <% layout('layout') %> <%= title %> <!-- =是要轉成字串 --> <h2><%= boss %></h2> ``` ### <i class="fa fa-arrow-circle-right"></i> API管理工具 #### API設計 - AJAX get、post向後端傳遞資料 * [postman](https://www.getpostman.com/) #### Postman取得get資料、基礎操作 ##### 使用postman傳送表單資訊 * 選擇POST * body * 類型選x-www-form-urlencoded * key→控制項name * value→控制項中的值 ### <i class="fa fa-arrow-circle-right"></i> body-parser取得表單資料 * body-parser套件 * 可以把前端表單的資料傳送到後端 ```bash npm install body-parser --save ``` ```javascript var bodyParser = require('body-parser'); //增加body解析 app.use(bodyParser.json()); app.use(bodyParser,urlencoded({ extended:false })); ``` ```html <form action="路徑" method="post"> <!-- name屬性很重要!!!資料傳送到後端去,會以name作為名稱 --> <input type="text" name="" value="" > <input type="submit" name="" value="Button"> <form> ``` * req.body //解析內容 ### <i class="fa fa-arrow-circle-right"></i> Redirect跳轉網頁設定 ```javascript // 轉址到某頁面去 res.redirect('頁面名稱'); // 會把該頁面名稱渲染到當前的router上 res.render('頁面名稱'); ``` ### <i class="fa fa-arrow-circle-right"></i> POST AJAX前後端介接原理 ### <i class="fa fa-arrow-circle-right"></i> POST AJAX JSON格式 ```javascript var data = JSON.stringify({"content":str}); // 必須將JSON字串化 ``` * 使用Postman測試 * body選raw * 選JSON(application/json) ### <i class="fa fa-arrow-circle-right"></i> Router 進階設定 * 將各個邏輯放到各個route js檔中 * app.js僅需要load相關模組 ```javascript // app.js app.use('/user',user); // routes/user.js // 路徑就會為 http://domain/user/edit-profile router.get('/edit-profile', (req,res) =>{}); ``` ### <i class="fa fa-arrow-circle-right"></i> [express-generator](http://expressjs.com/zh-tw/starter/generator.html) #### Express應用程式產生器 * 可以快速建立應用程式架構 ```bash npm install express-generator -g ``` * 建立應用程式 ```bash express [options][dir] ``` #### 產生的結構 * bin - www * public - javascript - images - stylesheets - style.css * routes - index.js - users.js * views - error.ejs - index.ejs * app.js * package.json ## 7、 ### <i class="fa fa-arrow-circle-right"></i> 後端RESTful API ### <i class="fa fa-arrow-circle-right"></i> 前端設計 SPA (Single Page Application) ## 8、 ### <i class="fa fa-arrow-circle-right"></i> cookie、session講解 #### cookie ##### cookie簡介 * 能夠儲存資料在瀏覽器上的小型資料庫 * 能夠在client、server進行讀取、寫入 * 是由key/value方式組成,並由**分號**跟**空格**來隔開 * 可以設定失效時間,讓cookie在指定時間內消失 * Chrome: F12 → application可以看到cookie ##### cookie欄位介紹 * Name: 鍵(key) * Value: 值(Value) * Domain: 可存取該cookie的網域 * expires: 限制cookie有效期間 * path: 設定可以存取該cookie的路徑 * secure: 設定cookie是否要https網址才可以進行傳送 ##### cookie client(瀏覽器)端寫法 * 寫入cookie ```javascript document.cookie = "myName=tom"; ``` * 寫入cookie,並加入過期時間 ```javascript document.cookie = "username=bob;expires=Mon, 04 Dec 2017 08:18:32 GMT;path=/"; ``` * GMT時間 ```javascript new Date().toGMTString(); ``` * 寫入cookie,設定10秒後失效 ```javascript document.cookie = "username=bob; max-age=10; path=/"; ``` ##### cookie server(伺服器)端寫法 * 安裝解析cookie NPM ```bash npm install cookie-parser ``` * express寫入cookie,並加入相關設定(過期時間、httponly、path) ```javascript res.cookie(name, value[,options]); // 範例 res.cookie('name','Mary',{ maxAge: 1000, httponly: true }); ``` * express讀取Client端cookie ```javascript req.cookie.yourCookieName; ``` #### session ##### session簡介 * 儲存在伺服器的暫存資料,此暫存資料可放在記憶體或資料庫上 * session可在cookie上儲存一筆辨識你是誰的session id ##### session id * 用到[express session](https://github.com/expressjs/session)套件 ```bash npm install express-session --save ``` ```javascript const session = require('express-session'); ```