# 架設後端環境&API撰寫&網路知識 ## 開發工具 1. Node.js 2. Visual Studio Code (vscode) - ++加裝SFTP(安全文件傳輸程序)、Material Icon Theme(資料圖形化)工具++ 3. Postman 4. MySQL-workbench 5. Microsoft Visual C++ 6. GCP (Google Cloud Platform) ## 一、GCP設定 1. 新增一台虛擬機器(注意金額!) 建立新的專案->Compute Engine->VM執行個體->建立。 2. 基本資訊更改: 修改名稱、區域設定成台灣、asia-east1-a。 3. 選擇虛擬機器: 系列: E2 (CPU的核心) 、機器類型:2GB(核心大小) 4. 選擇開機磁碟(硬碟大小、作業系統): 作業系統:Ubuntu 、版本:Ubuntu 18.04 LTS 、GB:50 5. 防火牆:允許HTTP、HTTPS。 6. ++建立完成後,連接SSH確認虛擬機器OK,並且第一次連線會將安全殼層金鑰生成兩把用於GCP Web Terminal上的++ 7. 將動態IP設成靜態:VPC網路->外部IP位址->將臨時外部外址改成靜態->名稱自取。 8. 設定ssh鑰匙資訊:中繼資料->利用PuTTY Key Generator(可以用sourceTree開) 製作公私鑰, 將Key comment改成跟GCP的使用者名稱相同(這樣ssh跟使用者才會對上),私鑰自行留存(建議放在.ssh資料夾內) ->將公鑰新增至安全殼層金鑰裡,確認使用者名稱與GCP Web Terminal一樣,後續GCP才可以驗證身份。 9. 至vscode 測試是否可成功連線上。 ![](https://i.imgur.com/gXS3sgC.png) 紅底 : 空白鍵; ip : GCP的外部IP; -p 22: port 22; -i(Identity) + 私鑰位置(ex: ~/.ssh/testkey):驗證私鑰 ~/ : user的第一層資料夾 補充資料:[ssh-基礎概論](https://jennycodes.me/posts/security-ssh)、[ssh-進階應用](https://www.digitalocean.com/community/tutorials/ssh-essentials-working-with-ssh-servers-clients-and-keys#generating-and-working-with-ssh-keys) ## 二、創立Node.js專案 1. 利用vscode的terminal 或 cmp 輸入指令。 2. 安裝express-generator(以利快速啟動express專案使用): `npm install -g express-generator`:(利用npm來安裝 express-generator) * -g是全域安裝的意思(安裝在系統裡),如果不帶-g則是安裝在當前資料夾 * 執行全域安裝的時候可會需要管理者權限,所以要用系統管理/sudo方式執行,或右鍵以管理者身分執行cmd * npm → 管理nodejs套件的一個管理工具( node plugins manger) 3. 建立專案資料夾及相關檔案: terminal先到放專案的node_proj資料夾-> 利用 express-generator 創建專案-> `express --view=pug proj_name` * `express`代表我們使用的應用程式(也就是expressgenerator) * `--view=pug` 是表示我們產生的view使用pug語法(pug是替代html用的一種格式) * `proj_name` 自己取名即可 4. 利用vscode把資料夾打開,開啟後可以看到資料夾裡面的結構package.json是管理所有專案組態的地方, 5. 在package.json -> scripts 中加入連線私鑰: `"ssh" = "ssh GCP使用者名稱@外部IP -p 22 -i 私鑰位置" ` * scripts是快速執行的腳本,可以放你想放的任何terminal指令(終端機) EX: npm run ssh ,ssh就是快速執行的腳本。 6. terminal進到專案資料夾內,使用npm i 安裝套件: * npm i 插件名稱:表示在當前資料夾安裝該插件 * npm i : 安裝package.json內所有的dependencies 在當前資料夾 * dependencies是要安裝的套件,以及該套件的版本編號。 -> 安裝成功後vsCode的左邊會出現node_modules裡面會包含所有已經安裝的插件(檔案會超多的),因此我們通常不會整包複製,而是利用package.json標記,這樣到不同環境中只需要npm i 就可以快速安裝。 7. 安裝完成後,執行npm run start,如果要停止執行狀態,用ctrl + C (當結束使用時要記得關閉),打開後在google打127.0.0.1:3000 即可連線上去。 * run → 執行腳本 * start -> **"Node ./bin/www"** →使用node啟動www :**開啟伺服器**。 * ssh → 腳本執行連線及驗證私鑰。 ## 三、將專案上傳至伺服器 1. 至GCP網路設定防火牆->建立防火牆規則->自取名稱及++目標標記++ ->設定來源IP範圍(0.0.0.0/0 代表全開; 192.168.2.0/24 代表區網連線) ->指定的通訊協定和通訊埠設3000(可自訂) ->建立後至VM執行個體將剛設定的目標標記,加入網路標記已讓防火牆通過。 2. 建立SFTP連線並將專案上傳到伺服器:在vscode按F1->SFTP:Config->建立.vscode->++修改.vscode資料++->右鍵Sync Local -> Remote 上傳至伺服器 ->登錄ssh確認是否正確。 ```json "name": "My Server", "host": "外部IP", "protocol": "sftp", "port": 22, "username": "使用者名稱", "privateKeyPath": "私鑰位置", "remotePath": "/home/使用者名稱/創建伺服器資料夾名稱", "uploadOnSave": false, "ignore":[ "node_modules", ".vscode", ".git", ".DS_Store" ] //"uploadOnSave" : 是否要有存檔就上傳 //"ignore":要屏蔽掉不上傳至伺服器資料夾的資料。 ``` 3. 幫伺服器(VM)安裝node.js: terminal進入VM(`npm run ssh`)->進到使用者-> `sudo apt-get update`:更新VM裡的應用程式清單-> `curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -`:利用curl(讀取網站)下載nodejs-> `sudo apt-get install -y nodejs`:安裝nodejs (-y :通通同意的意思) 4. 幫專案安裝node_modules: 進到server(專案)->npm i 5. ==幫VM安裝nginx(網頁伺服器)及設定==:退回到使用者 1. 更新app列表:`sudo apt-get update` 2. 安裝nginx(部屬服務器工具):`sudo apt-get install nginx` 3. 進入nginx裡:`cd /etc/nginx/` 4. 編輯nginx(服務器設定):★`sudo vi nginx.conf`:因為要調整系統內容,一定要用管理者權限進入,進入後只能只用鍵盤控制 5. 將預設的nginx網頁關掉: `include /etc/nginx/sites-enabled/*`註解掉 6. 加入一個指向路徑server:在include /etc/nginx/conf.d/*.conf;的上方加入server資訊: ```javascript server{ listen 80; // 將port設定為80(HTTP) server_name 127.0.0.1; //對VM來說是本地,所以打localhost index index.html; // 當沒有指向時(也就是只到下面設定的路徑,沒進去任何檔案時)的默認頁面,如果沒檔案(index.html)-可以自行增加,都沒有時會是nginx的預設頁 root /home/username/test_server/uploads; //連線的位址路徑,當連線到時,會到這層,因此只要在後面加/即可讀到uploads的檔案。 //如果有多一層資料夾,要在username後+ /myapp } include /etc/nginx/conf.d/*.conf; #include /etc/nginx/sites-enabled/*; ``` 6. 測試是否正常,沒問題即完成環境建置: 1. 新增資料夾uploads並放測試圖片,並上傳STFP local->Remote, 重新開啟nginx `sudo service nginx restart`:因為nginx.conf是靜態的,雖然有存檔但沒有重新建置。 2. 網頁輸入外部IP/圖片名稱.png (如果有再放一層資料夾,就是/資料夾/圖片名稱.png) 3. 如果有看到圖片即為正確 7. 測試異常排除: 1. 如果只打外部IP,出現404,代表檔案沒上傳找不到,如果有上傳,會出現403。 2. 如果進不去起始頁或user,請檢查是否是HTTP連線,或是沒輸入:3000,因為port3000還是一樣是index.js的畫面。 3. 通常都會利用`套件` `-v` or `--version`查詢版本號確認是否有安裝成功。 ### *nginx vs apache (網頁伺服器) | 內容 | nginx | apache | | -------------- | ----- | ------ | | 效能(靜態內容) | 超快 | 一般 | | 效能(動態內容) | 一般 | 一般 | | 操作系統支援 | 支持大部分類似Unix的操作系統、部分支持Windows | 支持所有Unix系統(Linux.BSD...)、全支持MS-Windows | | 配置權限 | 不允許其他配置(因此效能會較好) | 允許 .htaccess文件配置(因此速度較慢) | | 請求的方式 | 文件 | URL | | 安全性 | 比apache高一些 | 非常高 | | 較常用情況 | 高流量、靜態網站 | java | 補充資料:[nginx vs apache](https://serverguy.com/comparison/apache-vs-nginx/)、[靜態網頁vs動態網頁](https://wiki.mbalib.com/zh-tw/%E9%9D%99%E6%80%81%E7%BD%91%E9%A1%B5)、[靜態vs動態-簡述版](https://www.newscan.com.tw/all-faq/faq-detail-15.htm) --- ## 四、postMan ### 1.設定連線 1. 將本地及server(外部IP),加入參數,以快速切換本地或外地: 1. 右上角Manage Environments->add 2. 建立兩個參數(Local、Remote): 1. variable名稱:Local&Remote相同 ex: url, 2. Initial value值:。 Local:`http://127.0.0.1:3000` Remote:`http://外部IP:3000` 3. Current value同Initial value。 2. 使用方式: {{url}}/檔案索引 url = 會依照環境選擇Local or Remote 轉變成對應的current value,因此只需要/索引即可url。 3. 通常會在本地測試,沒問題後才上傳Remote。 補充資料:[Initital&Current value](https://learning.postman.com/docs/sending-requests/variables/) --- ### 2.測試API 1. postman是代替前端做測試使用的,因此會撰寫要測試的參數(要傳回伺服器處理的資訊-也就是正常前端在做的事情),按下send = 前端做了動作(get.post...),伺服器(vscode)會處理這些資訊後,再回傳給postman資訊。 2. get的方法(postman),從url傳參數進去,方法有兩種: 1. 直接在網址的後面加上?代表要傳的參數。 ex:`{{url}}/test?key1=value1` * 也可以在postman的Params直接撰寫key&value,可以多個,會自動用&做串接。 2. 使用RESTful API方式傳入參數(vscode要先設定好key),只需要/參數即可,值不能有?因為有?會直接抓成query的方式。 ex:`{{url}}/test/value1` * 因為postman走的是query params所以用RESTful API的寫法要先寫/:key1,Params會出現Path Variables,可以在這撰寫值。 * 如果有兩個key以上,就是用/分開 ex:`/value1/value2` 3. 可以同時用兩種方式傳參數,但需要先寫params參數,寫完後再用?隔開寫query的參數,因為query的值會吃掉符號。 ex:`/value1/value2?key3=value3&key4=value4` value1&2是存在params,value3&4是存在query。 3. post方法(postman),從body傳參數進去: 1.至Body->raw->選JSON格式。 2.==JSON格式就像JavaScript的Object格式寫法,大括號裡面寫Key&value。==,要注意的是何時該用keyPair,何時用陣列,看拿取物件時的需求性。 ```javascript { "key1": 1, "key2": false, "key3": "test", "key4": [1, 2, 3, 4, 5], "key5": { "key5-1":2, "key5-2":"object" } } ``` 4. 但其實get、post,都還是可以傳body/url,只是通常不會這樣。 補充資料:[JSON撰寫練習](http://json.parser.online.fr/)、[JSON介紹](http://miniaspreading.github.io/guide-to-json/1-what-is-json.html) ## 五、撰寫API(vscode) API:應用程序編程接口 1. vscode 是server後端,因此要在後端處理資料,就將處理的邏輯寫在vscode,在res.send回postman。(請配合postMan的測試API觀看) 2. 記得如果是伺服器連線的話,改完檔案內容要先sftp傳過,才會更新,所以盡量先在local測完沒問題才上傳sftp,用伺服器測試。 3. routes-> `router.get('/test', function(req,res,next)` /test: url的路徑 ==req==:(request) 伺服器收到postman請求時的資料(進來的資料),因此所要的資訊就在這包資料中(很大一包)`console.log(req)`。 ==res==:(response) 伺服器直接回傳回去前端(postman)的資料,要send的資料(出去的資料)`res.send()`。 ==next==: 中介層,丟給下一個人處理(如果資料還需要先傳給下個地方做處裡的話,才會使用到) 4. req資料較會用到的(可以將其印出檢查資訊): 1. Url : baseUrl, originalUrl(現在的路徑) 2. query:用get方式所接收到使用者傳入的參數。 ex: req.query = 'key1=value1'。 3. params:用get方式且是用RESTful API傳入的參數。ex: req.params = 'key1=value1' 4. body: 用post方式所接收到使用者傳入的參數。 5. get有另一種方式,用RESTful API傳參數,需要先在vscode做設定,要把router.get裡面的路徑改成`/test/:key1`,(路徑+`/:`+key名稱),會讓test(路徑)後面的網址自動變成key1的值,也就是轉變為路徑+一個參數的API,因此一定要有參數,並且使用者傳入的參數會變成在params裡。 * 如果要有兩把key以上,就是在加上`/:key2`無限下去。 * 因為postman是Query Params,所以只要是用?傳的都資料都在query,沒問號的在params;可以同時混用。 6. 因為拿到的資料,是一個object,裡面是用keyPair存,取資料的方式是 `body["keyname"]`,或`body.keyname`,++資料的型態都是String。++ ==ex: `req.body["key1"] => 1` or `req.body.key1 => 1`== 7. res.send(),括號內是放一個JSON,也就是要用object的格式撰寫,也可以利用({Test: req.body}),也可以直接放一個參數進去ex:res.send(a) 8. API撰寫範例: ```javascript //兩數相乘,傳回postman router.post('/test', function(req, res, next) { var a = (req.body["key1"]) * (req.body["key5"]["key5-1"]) res.send({Mul: a}); // 可以只寫a,但不能只回傳數值,因為這樣不吻合格式(沒有key)。 }); postman畫面: {"Mul": 2} 只傳a時(res.send(a)):{"a":2} //get的方法 postman傳入:{{url}}/test/5/45?key3=10&key4=23 router.get('/test/:key1/:key2', function(req, res, next) { let a = parseInt(req.query["key3"]) + parseInt(req.query["key4"]) //利用parseInt()將字串轉成數字 let b = req.params["key1"] - req.params["key2"] res.send({Test: [a,b]}); }); postman畫面:{"Test": [33,-40]} ``` 補充資料:[RESTful API](https://medium.com/itsems-frontend/api-%E6%98%AF%E4%BB%80%E9%BA%BC-restful-api-%E5%8F%88%E6%98%AF%E4%BB%80%E9%BA%BC-a001a85ab638) --- ## 六、MongoDB & mongoose(SQL資料庫建立) ### 一、小知識: 1. 每個資料夾中只要有index.js,該資料夾的就預設的index資料。 2. 導出(export):把常數有可能會被其他檔案用到的部分導出,其他檔案在收到你這份檔案時,就會收到你的導出內容。 3. JS一旦import就會把import的資料跑一次,然後把要的內容導出。 4. 可以直接裝一個mongodb在GCP上面,正常來說都會這樣子操作,但就不會有圖形化界面,因為限制比較少。 5. 因為Nodejs是以JS物件為基礎,資料儲存格式通常是JSON,建議搭配MongoDB(No-SQL)一起使用。 --- ### 二、資料庫小知識: 1. 資料庫管理系統ex:MsSQL Oracle MySQL... 2. API服務通常都會需要存資料起來,但仍有少數特例EX:車牌辨識,或算法太複雜時,因為寫檔太慢,因此資料庫就是**專門用來存資料的系統**。 3. 關聯式資料庫核心關鍵:表和表之間建立關係,及用最理想的大小存資料,最難的是設計表格(正規化-基本上做到第三正規化),需避免過度正規化(過度耗費效能)。 4. 關聯式的原則:ACID (非關聯沒有 1. A 原子性(atomicity,或稱不可分割性):一次跟資料庫的溝通為基礎做成最小單位(原子),EX:要新增一筆資料(也就是一次需要全部寫完的資料)當作一個原子。 也就是說中間中斷就不會儲存,會整個丟掉。 2. C 一致性(consistency):EX: A匯款給B 50元 A一定會被扣款50元,B一定會被加50元。 3. I 隔離性(isolation,又稱獨立性):每個人操作時,都是獨立的,整併時會是正確。 4. D 永續性(持久性)(durability) 只要一次操作完成,資料就不會遺失,跟原子性有關。 5. 目前大型專案,都是混用SQL跟NoSQL。 | 名稱| 特點 | 使用時機 | 備註 | | ----- | --- | ---- | -- | | [關聯式資料庫(SQL)](https://zh.wikipedia.org/wiki/%E5%85%B3%E7%B3%BB%E6%95%B0%E6%8D%AE%E5%BA%93) | 1. ACID 原則<br>2.效能較慢,因為要向陣列那樣搜尋<br>3.彈性低(資料欄位必須相同、新增耗時因為每筆都要跑一次)<br> | 資料量大、資料欄位不常變動的專案 | 目前大部分專案使用<br>Ex:excel、ATM | | [非關聯式資料庫(NoSQL)](https://zh.wikipedia.org/wiki/NoSQL) |1.無ACID原則<br>2.利用keyPair拿資料,所以效率快<br>3.彈性高(資料欄位可不相同、新增容易) | 新創公司、小規模專案或變動性高的專案 |不好建索引,容易有資料不一致的問題 <br>Ex:JSON、臉書按讚 | | | [關係資料庫(NewSQL)](https://zh.wikipedia.org/wiki/NewSQL)| 結合SQL跟NoSQL的特性 |線上交易處理工作(OLTP) | | --- ### 三、建置流程: 1. 組織(隨便設)->新專案(選免費Der)->建叢集(Cluster-選GCP,免費的,選台灣)。 2. 等Cluster跑時,先至SECURITY->Databass Access->add new database user ->Password Authentication 填寫帳號密碼->權限記得開可以讀檔跟寫檔。 3. 再至Network Access-> add ip address->Access List Entry:0.0.0.0/0 4. 建好後選CONNECT->Connect your applicatioin->Node js 3.0 or later->至vscode創建一個名為config的資料夾->在資料夾中創建一個index.js檔,並把code放入。 ```javascript const config = { //沒用到,可以不打 urlRoot: "http://外部IP/", //沒用到,可以不打 port: 3000, mongodb: "mongodb+srv://admin:<password>@cluster0.hn38m.mongodb.net/<dbname>?retryWrites=true&w=majority" //mongodb 的code = mongodb+srv://帳號:<password>@cluster0.hn38m.mongodb.net/<dbname>?retryWrites=true&w=majority //<password> = 步驟二的user的密碼 //<dbname> = 自己取名 } //設一個常數config,裡面是一個object module.exports = config; ``` 5. 安裝mongoose:terminal進到專案資料夾->`npm i -S mongoose`->至package.json的dependencies確認是否安裝成功。 6. 至bin->www檔案,在一開始加上`const config = require(../config);`、`const mongoose = require('mongoose');`-> ```javascript mongoose.connect(config.mongodb,{useNewUrlParser: true ,useUnifiedTopology: true}).then(() =>{ //mongodb逗點後的資料,在第一次完成時按會跑出兩個錯誤訊息(意思:有一些資料即將被棄用,要把設定打開) //因此將那兩個資訊放到mongodb後方即可-如code。 server.listen(port); server.on('error', onError); server.on('listening', onListening); //在原本放這三個server的地方,修改成這樣->意思是用mongoose連結,如果有錯誤會丟錯誤訊息 }).catch((err) => { console.log(err) }) ``` 7. ★mongoose.connect,就代表著現在這份資料不論是本地還是GCP的專案,都跟目前建的mongodb(資料庫 -資料不超過500MB免費)連在一起了-也就是資料庫代管。 8. 至mongoose官網將sample code 複製下來做測試:至rootes->index.js->先導入mongoose `const{ resource }= require('../app');`、 `const mongoose = require('mongoose');`-> 在起始頁(res.render()前),加上sample code ex: ```javascript= const Cat = mongoose.model('Cat', { name: String }); //Cat的表格有個name的String const kitty = new Cat({ name: 'Zildjian' }); //欄位的資料(Zildjian),創建出來->kitty kitty.save().then(() => console.log('meow')); //呼叫save()將kitty跟資料庫連線存入 //then:如果save動作成功 ,就console 'meow' ``` 9. 完成後,連線到127.0.0.1:3000,vscode會出現meow -> 到mongodb->Cluster->Collections,有看到cats的表就成功了(看到的就是資料)。 10. 資料的id是資料庫給的,是唯一id,_v則是model的版本號。 --- ### 四、Mongoose 1. mongoose 是第三方程式,並非mongodb,因為他的文件資料比較多,所以才使用他。 2. mongoose是ODM模組。 * ORM(Object Relational Mapping):針對SQL-Based資料庫。 * ODM(Object Document Mapping):針對NoSQL-Based資料庫。 * 使用ODM / ORM 通常可以降低開發和維護成本!除非非常熟悉原生 SQL,或者效能遇到瓶頸,否則應該建議優先考慮 ODM/ORM。 3. 會利用mongoose連接到MongoDB數據庫(數據庫是MongoDB,mongoose只是一個套件,快速方便的處理這過程):導入`require('mongoose')`並使用`mongoose.connect(mongoDB...)` 4. `mongoose.model()` & 綱要Schema:需先model會創建一個模型,而這個模型是透過先前自定義的Schema的格式創建的(就像選單模組的按鈕,自己先設定好後,套用該格式)。 ++Schema是用JS物件撰寫,可以有function、find()。++ 5. `save()`:創建和修改文檔至資料庫; `update()`:更新文檔資料至資料庫; `create()`:定義模型並保存。 6. 如果沒指定callback,則會返回Query類型 。 7. `findById()`:用指定的id 查找文檔(每個文檔都有一個唯一的id) `count()`:符合條件的數量 補充資料: | 資料 | 資料 | | ------- | ----- | | [mongoose中文站](https://developer.mozilla.org/zh-TW/docs/Learn/Server-side/Express_Nodejs/mongoose) | [mongoose官方文件](https://mongoosejs.com/docs/guide.html) | | [增改刪減-1](https://codertw.com/%E8%B3%87%E6%96%99%E5%BA%AB/18224/#outline__4_3) | [增改刪減-2](https://pjchender.github.io/2018/12/09/mongo-mongoose-%E6%93%8D%E4%BD%9C/) | | [`$set`運算子](https://docs.mongodb.com/manual/reference/operator/update/set/) | [比較查詢運算符號](https://docs.mongodb.com/manual/reference/operator/query/lte/) | | [mongodb大全-各種運算符](https://docs.mongodb.com/manual/reference/operator/query/) | [ORM & ODM簡介](https://ithelp.ithome.com.tw/articles/10185085) | | [正規表示式](https://larry850806.github.io/2016/06/23/regex/) | [正規表示式-進階](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Guide/Regular_Expressions) | |[模糊搜尋`$regex`-文件](https://docs.mongodb.com/manual/reference/operator/query/regex/#op._S_regex) | | --- ## 七、基礎使用 ### [請參閱Notion範例Code](https://www.notion.so/code-53c2062638e74aeabe5d008ad4389d98) 1. routes 路徑:就像path,所以邏輯要獨立出來,app.js應該只要指到主路由,再由主路由連到子路由,子路由在各自判斷資訊要呼叫什麼方式。(路徑會設/api是為了要符合實際開發情形) 2. 基本上架構都會是: 1. 導入、定義需要的資訊,ex: `var express = require('express');`:先導入express-這個express也是別的地方的exports `var router = express.Router();`:用一個變數儲存express的Router方法。 2. 開始撰寫要的邏輯,有點像是builder,將該資料設定好。 3. 在結束時,利用`module.exports = ?`,將這些資訊用物件或變數的方式包起來導出,其他地方導入時就會拿到這個物件,在看需要什麼資訊或裡面的方法使用。 ex:controllers 在裡面撰寫完邏輯,將撰寫的function名稱放入導出的物件中,router先用變數存取導出的物件後,呼叫裡面的function。 3. 通常都會在res.json放入errorCode(HTTP狀態碼),以方便自己確認錯誤或正確資訊,ex:`res.json({errorCode: xxx, msg:{})` 4. find出來會是model。 5. next()只是會讓目前擁有的資訊,跳出router繼續往下層跑,通常會加個return;避免後面的事情又作動。 6. 驗證的中介層其實可以放在各個router中,會更有彈性,或是另外建router去控管。 7. err 會送出錯誤的資訊,最終的資訊會放在message,因此錯誤訊息的data放err.message即可,這樣就可以讓不符合資料格式的錯誤顯示出來。 補充資料:[errorCode-狀態碼大全](https://developer.mozilla.org/zh-TW/docs/Web/HTTP/Status) ### Promise & async & await & new 1. promise:每個方法結束後,會回傳一個promise,所以才可以用裡面的then跟catch。(then跟catch,都是promise的方法).then resolve = console.log(..) 2. promise:是為了要解決callback Hell的問題。 3. .then() 裡面的方法會被resolve存起來; .catch() 裡面的方法會被reject存起來。 .finally() 一定會等到then.catch執行完後才做。 * then() 跟 catch() 會是異步是因為是promise裡面的方法,而promise裡大多是異步的方法,所以是異步方法。 4. 只要是async 跟promise,都一定是異步、非同步 、非同腳- 平行交互蹲跳。 所以在後面的有可能會是非異步、同步、同腳。 5. await 如果後面不是接異步的方法(promise或async)一定會報錯(因為主執行緒會停止)。 6. 匿名內部類別跟function的差異,會有個 .invoke()執行這個function。 7. new 接一個function ,就會像一個建構子,會回傳一個object,裡面的this.a,this不能省略,他的意思是再這個物件(this)建立一個a的key 然後再放值進去。 8. 所以其實在new 時,會做function的呼叫,因為再new的最後會把function 執行。 9. ++要注意function不能用lambda寫,因為lambda會幫他做一個包裝會做invoke()-因此會變成一個執行的方法而非一個變數。++ --- ### 模糊搜尋、populate&select、型態創建、兩表相關聯、update 1. 兩表相關聯: 1. ObjectId:每個model(表)的唯一數值,可以利用他跟其他的表產生關連,加上 `ref:'某一個model的名稱'`:代表只能是特定的某個表的ID。 * 一個model = 一個表。 * ★要特別注意,這個ref裡的名稱,要跟models裡面export創建的model名稱一樣,不然會跑出錯誤訊息是Schema hasn't register。 2. 利用ID找到該表格資料用,可以再利用populate替換該格資料,`.populate('要替換的屬性名稱','要選擇或剔除的資訊-可選')`:是對該物件內的屬性作用。 ex: `Order.findOne({serialNum}).populate('customer')` ,也就是把customer把所有資料(或選擇過的資料替換掉) 3. `.select('-__v -id')`:是對目前被指的物件,選擇要替除掉的資訊用'-',多個資料中間用空格隔開即可,也可以在裡面放要顯示的資料,不加'-',這是對原本的資料,讓send json的資料選擇的,資料庫不會變。 4. `.lean()`:會讓其喪失model的權利,因此可以變更裡面的key值。 5. `.exec()`:讓目前動作強制同步執行,但通常不會用,不然請求多時會卡。 6. 可以用key.key (name.last) 來去找巢狀的找法,不論find. populate select都可以。 7. ★populate、select只有搜尋時可以用,因為他是在搜尋時把資料過濾掉不抓去來。 2. 進階搜尋 1. `$gte`:大於等於 `$lte`:小於等於,EX: `this.find({birth:{$gte: start, $lte: end}});` 2. `$regex`:有包含的文字就算。 ex:`let reg = new RegExp(author,"i")`:=先用一個變數存取要搜尋的欄位及是否有特殊條件(ex:'i'大小寫都算),在放入搜尋中。 `let author = await Author.find({name:{$regex:reg}});` 或是`this.where({ name: new RegExp(name, 'i') })` 3. model在設定Schema時,欄位可以是一個物件,利用type:型態,後面可在加設定,ex: 1. required:一定要有這個欄位,必填的概念,如果使用者沒填就會錯誤。 2. default,如果該欄位沒有值傳入,就會是這個預設值的資訊。 3. unique(關鍵字:indexes) 唯一值,設成true,這樣就讓他在資料庫中只能有一次,有做的話mongoDB 的Indexes 就可以看到了。 4. update裡面如果是放物件,如果該值是undefined,該欄就不會更新,要嘛用`$set:Obj`,不然就是要在option加上`{omitUndefined:true}`。 --- ## 八、登入功能 1. ★json web token:是一種格式,分為三個部分,裡面都是JSON格式,用.做分隔,組合成一個字串,這個字串就是token, 1. 第一部分-簽名資訊:這次簽章用的演算法(HASH的方法)、過期的時間(秒數)等等。 2. 第二部分-要儲存的資訊(payload)-自由填寫:ex:user.id、權限(管理者、使用者-identlytype)、token的創建時間、等等的資訊-自定義,依照服務需求填寫。 3. 第三部分-把第一、二部分的資料+salt做Hash,並將結果存入第三部分。 4. 第一、二部分是明碼,因此要注意不要把重要資料放在外面。 5. Hash的方法只有後端server才有,因此驗證就是把第一、二部分的資料在做一次hash跟第三部分比對,就知道資料有沒有被竄改過/錯誤了。 6. 要驗證是因為會在做1.2的HASH跟3比對。 7. 驗證另外還會比對token的過期時間,如果token過期就不吻合。 8. 第二部分會有個iat = token創建時間,可以設定有效的時間有多長,因此過期就會讓該token無效;另外也會讓修改密碼的地方不同來做預防。 ![](https://i.imgur.com/JdKSAPP.png) 2. ★Hash是一個編碼的過程,也是不可逆的;Rsa是非對稱的,一組公鑰對一組私鑰。 3. 網路很常用到編碼器,是為了縮短字串長度,或是避免ㄧ些特殊的符號,這些編碼器的格式是公開的,因此並非為了加密,JWT的編碼器是用(base64)。 4. sha-256已經被破解完了,所以現在都會再加個salt(會改變資料的值),因此每台server的salt設定的值都不同,所以每個的結果都不一樣,所以簡單說,如果算法及salt都被盜,就真的沒救,如果要更高規格的防止,就可以用sha-1024或sha-2048。 5. 目前網路登錄通常都是client端紀錄登錄資料,server是做驗證而已,後續都是驗證token然後做動作。 6. 建置流程: 1. 在專案中安裝jsonwebtoken `npm i -S jsonwebtoken`` 2. 在config 中加入 `salt:"隨意打"`。 3. 製作authController.js ->加入login及verify方法。 4. ++驗證成功時會將第二部分(payload)的資訊回傳出來(next())。++ 5. 加入中介層(不要帶路徑,就代表是中介層)-> 導入authController,並加上`router.use(authController.verify);`。 6. 在routes/index 裡,要注意程式執行的順序,要把要驗證後才能做的事情,放在驗證之後,index的順序是 ->main ->不需登入就可做的服務... ->登入`'/auth,authRouter`:(此時不能做verify,因為絕對不會過) ->中介層(verify驗證)`'authController.verify'` : postman要手動將登入產生的code放至headers(通常會用環境變數做-改右上角的資料),實務應該會在前端做處裡。 ->登入後可以做的服務... 7. 可以自己實現,也可以用套件(ex: jsonwebtoken) 8. 使用方法-登陸:`jwt.sign(payload,secretOrPrivateKey,[option])`: * payload:要放在第二部分的資料ex:user.id。 * secretOrPrivateKey: salt(每個server不同,設在config.js。 * option: 可以放要再設定的資訊,例如加入token的有效時長,時間到就無效,expiresIn:字串,裡面的數字是毫秒(1000毫秒=1秒)。 * ex:`let token = jwt.sign({ userId: user._id }, config.salt, {expiresIn: '10000'});` `res.json({ errorCode: 200, data: { token, user } });` * 有效時長在這設定就會自己跑幾秒後過期,但如果要刷新這件時間,通常是在前端做。 9. postman: 1. token、較隱密的資訊會放在 Headers傳(get.post都可),key: 'x-access-token' value: {{token}} 2. 可以把token放到環境的預設值中,變數名為token,這樣只要改token的currery值即可改變所有的token 3. token就是在login時印出來的,將此串token拿去JWT網站可以轉碼得到內容。 登錄流程示意圖: ![](https://i.imgur.com/5NsV3z3.png) 補充資料:[JWT-token轉碼器](https://jwt.io/)、[node-jsonwebtoken套件](https://github.com/auth0/node-jsonwebtoken) --- ## 九、網路知識&相關重要資訊 ### 一、科普資訊 1. nodejs是基於javaScript的框架,其實只需要package.json就可以是一個nodejs的專案了,其他檔案都可以自己建立。 可以用node 檔案,以node.js開啟檔案。 Spring Boot後端,但要用比較好的版本可以用Intellj 但要付費 還有javaScript 2. 語言:java c/c++ javascript ruby php... ,和作業系統做窗口的 3. 框架:Android Nodejs Vue.js React.js Spring ,基於原本語言特性,包裝一層生命週期要把剩的資料補全。 EX: GameKernal,把update paint補好即可。 4. Node npm : Node Package Manager 套件管理工具, 其他還有: maven gradle (andriod是用gradle); 套件就像是我們在做的鏡頭、地圖模組,然後再讓其他人可以安裝這個模組直接使用他。 * package.json → depemdencies 裡面的數字是版本號。 * 因此每次更新時,就會把node_modules裡的更新。 * 可以直接更新套件的方法是因為會自動連到npm的官網,並且連到github將作者的套件軟體直接抓下來更新, 因此要更新時就將最新的版本號打到depemdencies,然後`sudo npm adp-get update`更新即可。 5. express 是基於nodejs的一種框架,可以拿來做api的服務。 6. 網路有網際網路協議 TCP/IP,因為 IP v4位置快用完了,所以之後會改成v6,IP是電腦唯一的位置。 * 網路的連線方式: 1. **TCP** 三向交握:可靠連線,來回確認一次後才送出,共跑三次,能夠確保兩邊請求都會成功,穩定,但消耗較多資源,不會漏封包。 2. **UDP** 一直送資訊,不管對方有沒有收成功,速度超快,但容易漏封包,非常常用在直播。 7. HTTP 是基於TCP的協議80的port,HTTPS代表是有經過ssl加密的協議,443的port HTTP1.0 跟1.1的最大差別是,1.0每次都會斷開,1.1會暫存一段時間的連線(TCP) TELNET (PTT)。 8. 後端要了解OSI模組,前端淺了解就行。 9. 只要是一個應用程式的接口/介面,就是一個API ,因此如果說是 WEP API服務,才是指RESTAPI。 10. REST API→ 將API **規範**分成四個部分,取得get、建立post、修改put、刪除delete,會對應到http協議,目前大家都是走這個。 ==GET 傳資料是用url(網址)傳的==,get過去,給整個網頁內容,ex:游覽器就是一直在做get跟網路請求內容。 ==POST 傳資料是用body傳的==,POST過去,給token,ex: 會員登入。 所以要送出比較私密的資訊,會用POST。 11. npm run start 是打開應用程式的概念,要ctrl + C 做關閉,會不能run是因為打開應用程式,就不太會去編輯。 12. OSI模型七層:從底部(發生實際數據傳輸的地方)到頂部(最接近最終用戶)是物理層→數據鏈路層→ 網絡層→==傳輸層==→會話層→表示層→==應用層==。 補充資料:[TCP/IP網路協議](https://jennycodes.me/posts/the-tcpip-model)、[OSI模型](https://jennycodes.me/posts/networking-the-osi-model)、[超文本傳輸安全協定(HTTP/HTTPS)](https://zh.wikipedia.org/wiki/%E8%B6%85%E6%96%87%E6%9C%AC%E4%BC%A0%E8%BE%93%E5%AE%89%E5%85%A8%E5%8D%8F%E8%AE%AE)、[REST](https://zh.wikipedia.org/wiki/%E8%A1%A8%E7%8E%B0%E5%B1%82%E7%8A%B6%E6%80%81%E8%BD%AC%E6%8D%A2) ### 二、網路IP小知識 1. 網路裡面本地位置永遠是127.0.0.1,只要是127.0.0.1就是本地IP 2. IP位址分5個等級,通常依照網路的區域範圍有多大決定等級。 | 網路等級 | IP分佈範圍|私有IP(區域網路) | 可連結主機數目| | -------- | -------- | -------- |--------| | A | 0~127 | 10.x.x.x |65536*256| | B |128~191 | 172.16.x.x - 172.31.x.x | 256*256 | | C | 192~223 | 192.168.x.x | 256 | | D | 224-239 | 通常無私有IP | 群播用途(多點傳送位址)| | E | 240-255 | 通常無私有IP | 研究用途(保留使用)| 補充資料:[IP分級](http://kevin.hwai.edu.tw/~kevin/material/EAssistant/IP_Class.htm)、[靜態動態IP](https://ithelp.ithome.com.tw/questions/10000796) ### 三、常用port 1. 一個ip對應到某個電腦,再利用port對應到特定的服務,port最多可以到65536個。 2. 0~1023 為知名port,固定分給一些特定服務使用, 1024~65535 則是動態port,不固定的服務,因此病毒程式很多都是用這邊的port。 | 項次 | Port | 用途說明 | | -------- | -------- | -------- | |1. | 80 | HTTP(未經加密的連線) | |2. | 443 | HTTPS(經加密的連線) | |3. | 22 | SSH(連線加密機制) | |4. | 21 | FTP(檔案傳輸協定)| |5. | 23 | Telnet(遠端登錄) | |6. | 25 |SMTP(簡單郵件傳輸協定* | |7. | 53 | DNS(功能變數名稱伺服器)| |8. | 135 | PRC(遠端程序呼叫) | |9. | 161 | SNMP(簡單網路管理協定)| 補充資料:[常用port](https://yun1450.pixnet.net/blog/post/47494172)、[port大全-1](https://zh.wikipedia.org/wiki/TCP/UDP%E7%AB%AF%E5%8F%A3%E5%88%97%E8%A1%A8)、[port大全-2](https://david740204.pixnet.net/blog/post/458902670-%E5%B8%B8%E8%A6%8B%E7%B3%BB%E7%B5%B1%E7%9A%84%E5%90%84%E7%A8%AEport%E7%94%A8%E9%80%94) --- ### 四、Linux常用指令 |指令 |說明 | 指令| 說明 | | -- | --| -------- | -- | | vi | 打開文件 | i | 進入編輯模式(文件中) | | # | 註解| esc | 進入編輯模式(文件中) | | wq| 存檔並離開| shift+ : | 叫出指令輸入(工具列)| | w|存檔(write)| q | 離開(quit) | | -v | 查詢版本號| `--version` | 查詢版本號| | sudo | 使用管理者權限|rm -r|刪除資料夾 | | ls | 看全部的資料| cd .. | 上一層資料夾 | | cd | 進入資料夾| tab | 自動補齊名稱 | |[curl](https://blog.techbridge.cc/2019/02/01/linux-curl-command-tutorial/)|網路相關的操作| [apt-get](https://b9532026.wordpress.com/2010/03/30/apt-get-%E6%8C%87%E4%BB%A4%E4%B8%80%E8%A6%BD-2) | 對軟體做事情(詳見連結) | 補充資料:[Linux基礎指令大全](https://www.itread01.com/content/1547935401.html) --- ### 五、JS執行緒 ★javaScript 是單一執行緒,是內部自己分割,像是把input→update→paint拆分一點點,然後迴圈跑完,才達成偽執行緒。 ``` 執行緒 A方法{ B方法 C方法 }//如果是異步,就會兩邊各自跑,也就是 //計算機組織 Program:尚未執行的程式。 System:大概就是我把時間分給再執行的程式 =>多個Process:執行中的程式,將被分到的時間,再分給Thread, =>Main Thread:每個程式都一定會有一個主執行緒,理論上主執行序(Main Thread)停止,子執行序應該都會停止。。 =>多個Thread:根據分到的時間做對應的事情,EX:被分到可以讀60行code,就先走60步,等下一次給予是繼續做。 js是單一執行緒,利用每個程式都只跑一點點Input->Update->Paint(EX:Timer()),來做到執行緒的分工。 ``` 補充資料:[java的執行緒](https://programming.im.ncnu.edu.tw/J_Chapter9.htm)、[java的執行緒創建-Callable](https://dotblogs.com.tw/grayyin/2016/07/04/113501) --- ### 六、專案結構 1. 專案結構很重要,尤其對於前端,降低程式的耦合,讓彈性提高,目前有三種方式MVC、MVVM、MVP,是一種開發模式的思維。 1. MVP:Model View Presenter :MVC之後出來,畫面跟邏輯拆開,邏輯可單獨測試。 * VIEW 跟MODEL 沒有關聯關係,都是對Presenter-主持 3. MVVM:Model View ViewModel:基本上不處理邏輯的程式才會用,指交換view跟model的資訊,都是透過viewModel交換。 * 如果加上邏輯處理,就會像Controller 4. MVC:Model View Controller :畫面、處理邏輯、物件本身分開處理。一開始是給網頁使用,使用者點了view,呼救controller,用model實體做運算,實體再通知畫面更新。 * VIEW跟倆者的關聯較深 2. View:畫面。 3. Model:最小單位的元件,最小的儲存單位,EX: student、 book、 game object 4. Controller:整體的運算-邏輯。 補充資料:[專案結構介紹-1](https://ithelp.ithome.com.tw/articles/10218263)、[專案結構介紹-2](https://kknews.cc/zh-tw/code/l3n8z59.html)、[觀察者模式-1](https://medium.com/enjoy-life-enjoy-coding/design-pattern-%E5%8F%AA%E8%A6%81%E4%BD%A0%E6%83%B3%E7%9F%A5%E9%81%93-%E6%88%91%E5%B0%B1%E5%91%8A%E8%A8%B4%E4%BD%A0-%E8%A7%80%E5%AF%9F%E8%80%85%E6%A8%A1%E5%BC%8F-observer-pattern-feat-typescript-8c15dcb21622)、[觀察者模式-2](https://notfalse.net/10/observer-pattern) --- ### 七、應用程式服務結構 1. 以前都是前後端連在一起,但在智慧型手機出來後,就變成前後端分離為主了因為有網頁版跟手機板,藉由網路連接到SERVER後在跟資料庫索取資料。 2. 基本上會把網頁端的UI、服務、MySQL綁在一起=區網,速度才快, 3. 電腦->網頁及app UI→服務都是用internet 示意圖: ![](https://i.imgur.com/Q2n6CDO.png) 連在一起的時候: ![](https://i.imgur.com/AImNLJC.png) ###### tags: `CMoney7th-戰鬥營學習筆記`