# node.js練習筆記 - window.setTimeout 設定定時器 ```javascript= # 參數1是要執行的function 參數2是delay的時間點 setTimeout(function(){},3000); ``` - 箭頭函示 ```javascript= # 函示1跟2是同等意思 只是簡寫 1. () => { statements } 2. function(){ statements } ``` - new運算符號 ```javascript= # 創建一個 "具備後者相同屬性" 的新對象 function Car(make, model, year) { this.make = make; this.model = model; this.year = year; } var mycar = new Car("Eagle", "Talon TSi", 1993); ``` - cb()用法 問老師 - 通常,第一個參數是一個error對象(通常為 false - 如果一切按計劃進行),隨後的參數是某種形式的數據。 - 讚美要公開,批評指教在私下。 - 時間、品質、成本、範疇 需要做取捨。 - 站立會議要點 Today、Yesterday、Need Help。 - 問題越早曝光,解決成本越低。 promise 是非同步的函式 ```bash= # 建立檔案夾 $ mkdir git-workshop # 進入該檔案夾 $ cd git-workshop # 初始化 git 專案 $ git init # 檢查看看是否有建立 .git 檔案夾 $ ls -al ``` ### 在電腦端連結github ```bash= # 增加一個遠端repo 的連結,這個遠端的名稱叫做 origin $ git remote add origin {url} $ git branch -M main # 以下指令二選一做,設定 main 這個分支跟 origin 遠端的main 分支做連結且 push 上去 $ git push --set-upstream origin main $ git push -u origin main # 設定 set-upstream 過後,就可以不用指定 origin {分支名稱} $ git push ``` ### 在github連結電腦 1. 到 github 建立 node-workshop 專案 2. clone 下來 ```bash= $ git clone {url} ``` ### 刪除遠端檔案夾 ```bash= # 在 cache 中指定你要刪除的資料夾名稱 $ git rm -r --cached {檔案夾} ``` ### 在 push 分支之前,該如何讓分支同步 master? https://hackmd.io/@Heidi-Liu/git-workflow 第二種:繼續在舊的分支開發,需要先同步分支 較推薦這個方法,直接在舊分支同步遠端,就不須再另外新開分支 ```bash= $ git pull origin main // 同步遠端 main $ git commit -am 'new_commit' $ git push origin <old_branch> // push 分支 ``` 依照上述其中一種方式,之後就可以在 GitHub 頁面發 PR 進行 merge,解決衝突問題。 --- Promise 是一個表示"非同步"運算的"最終完成"或"失敗"的"物件"。 - 非同步 - 物件 - 最終完成 - 最終失敗 new promise 這句本身是同步函式, ![](https://i.imgur.com/NeP8SO2.png) gitignore 放忽略的檔案,但也要一起push到github上面 async function 可以用來定義一個"非同步"函式,讓這個函式本體是屬於非同步,但其內部以 "==同步的方式運行非同步==" 程式碼。 await一個promise物件 node-modual 是package.json下的產物 --- ### 爬蟲資料練習 [安裝MySql](https://www.npmjs.com/package/mysql) -> 用途使用mysql資料庫 ```javascript= // 下載myaql $ npm install mysql // 照文件建立連線 const mysql = require("mysql"); const connection = mysql.createConnection({ host:, user:, port:, password:, database:, }); connection.connect((err) => { if (err) { console.error("資料庫連不上"); } }); // 連線後再用sql語法撈資料 // 不關閉連線,認為程式一直在執行 connection.end(); ``` SQL_injection -> 惡意攻擊 [安裝dotenv](https://www.npmjs.com/package/dotenv) -> 設定環境變數,讓重要的帳密資料不要流出去github (!避免陷阱) ```javascript= // 下載dotenv $ npm install dotenv // 引用dotenv $ require('dotenv').config() //建立.env檔案並修改設定名稱 //修改引用.env的名稱 $ process.env.{設定名稱} const connection = mysql.createConnection({ host: process.env.DB_host, user: process.env.DB_user, port: process.env.DB_port, password: process.env.DB_password, database: process.env.DB_database, }); // 為避免git clone的同事不清楚要建立什麼設定 // 會再建立一個 .env.example 推上github 並在README.md中告知 (!不要再把.env推上去!資安問題!) (!.gitignore 要隔行放需要忽略的檔案) ``` forEach .map 高階函式盡量不要用async await 預期結果可能不一樣 高階函式-> 吃一個函式的函式,只要是可以接收函式作為參數,或是回傳函式作為輸出的函式,我們就稱為高階函式。 .map -> pending (有處理未處理完) forEach -> 忽略await (印完才處理) forloop -> 比較保險 --- - IP & Port IP adress -> 網路連線地址 Port -> 瀏覽器程式 - Cookies 和 Session 的關係 *瀏覽器上的 Session Storage !== Session* - Cookies 和 local Storage 都存在client端 [使用 localStorage 的缺點](https://medium.com/%E9%BA%A5%E5%85%8B%E7%9A%84%E5%8D%8A%E8%B7%AF%E5%87%BA%E5%AE%B6%E7%AD%86%E8%A8%98/javascript-localstorage-%E7%9A%84%E4%BD%BF%E7%94%A8-e0da6f402453) > Cookies 和 Session 存在的目的都是幫助 Server 記住 Client 的狀態,差別在 Cookies 是將狀態資料存在 Client 端(i.e Browser),而 Session 則是將資料存在 Server 端 ( e.g Redis )。 Cookies 只能儲存結構簡單的資訊,為了彌補 Cookies 的不足,Server 端會產生一組 Session key 放入 Cookie 中。 - client端和sever端的呼叫 client端 -> 發出 Request -> sever端 sever端 -> 回覆 Response -> client端 ![](https://i.imgur.com/v3TWcyM.png) [Github建立token](https://docs.github.com/en/github/authenticating-to-github/keeping-your-account-and-data-secure/creating-a-personal-access-token) - Token 主要的用途驗證權限 - 修改電腦中的 ==keychain密碼==改成==token== ![](https://i.imgur.com/CTSXcsj.png) - 兩種登入方式 1. cookie / session based 2. token 登入 - 產生一個 token => reponse 回給前端 - 前端需要自己儲存這個 token --> localstorage, session storage - 每一次前端發送需要身份驗正的 request 時,都必須自己帶著這個 token - 因為 cookie "預設"是無法跨網域讀取的 - 登出就會 delete session JS 有兩種資料型別: 1. 基本資料型別 `number、boolean、null、undefined、string` 2. 物件 `物件、陣列、函式` - 基本資料型別 -> 傳值 (容量小 複製的) - 物件 -> 傳址 (容量大 共用的) ![](https://i.imgur.com/zmmD4y2.png) ={...} 會創建一個新物件,不會指向同一個物件 ![](https://i.imgur.com/PSEvu7g.png) ### Module 模組化 - 優點:職責切割、好維護管理、設定公開不公開屬性 ```javascript= // (隱形的底層設定 無法改) exports = module.exports = {}; // 看到模組會設定空物件 module.exports = {}; // // exports = 又創建一個新物件! 不等於 module.exports! 不能用 // 不要為 exports 重新宣告一個物件! // exports = { // showBrand, // showColor, // showOwner // } //因為最後是回傳module.exports 所以如果要改要用module.exports = module.exports = { setOwner, showBrand, showColor } // (隱形的底層設定 無法改) return module.exports; // 雖然前面改到 exports 但最後回傳是 module.exports; 所以會變成空物件 ``` - (隱形的底層設定 無法改) `exports = module.exports = {};` - (隱形的底層設定 無法改) `return module.exports;` - `CJS` 不是JS內建,但NodeJS可以實現,*目前較多使用* - 匯出用`module.exports = { function1,function2,function3... };` - 匯入用`require("./car1")` - `ESM` JS內建的模組 - 匯出用 `export` - 匯入用 `import` *p.s.如果module已經被引用過就不會再重複引用了* ### bluebird 套件 - 幫助包成promisw物件 ```bash= # 下載bluebird $ npm i bluebird ``` Promise.all -> 回傳 全部promise都做完的時候 Promise.race -> 回傳最早執行完成的 ==(!面試必考題)== - 靜態網站 ![](https://i.imgur.com/dXDvWwu.png) - 動態網站 ![](https://i.imgur.com/5m4Vcpj.png) --- 問六個需球 不看表面的問題!人會轉譯問題! 為什麼需要椅子? -> 為什麼要站著? -> 原本怎麼做的? -> 真正的需求 思考別人講的話 給人看: http://www.gooogle.com 電腦看的: IP address 172.217.160.99 DNS: domain name server 階層式 cache 用途:有效率、比較快 設定 cache 的暫存時效,看需求。 - 1分鐘就失效:每一分鐘就得去問一次、資料更新比較快 - 10小時失效: 10小時才需要去問一次、資料更新比較慢 --- # Express (node.js Web應用框架) ```bash= # 初始化專案 $ npm init # 下載express $ npm i express ``` ```bash= # 引用express const express = require("express"); # 利用 express 建立了一個 express application let app = express(); # HTTP Method: get, post, put, patch, delete app.get("/", function (request, response, next) { response.send("Hello"); }); # 啟動.listen() app.listen(3000, function () { console.log("我的 web server 啟動了~"); }) ``` #### nodemon套件 - node sever.js 開啟websever - control+C ->每次更新都要重啟快捷鍵 - nodemon類似live sever,不用一直crtl+c關掉再重新啟動(取代node功能) ```bash= $ npm i -g nodemon ``` #### 自訂linux指令 - package.json >> "script"區塊 >> 增加指令行 ex: `"dev":"nodemon sever.js"` #### 中間鍵 middleware - 照程式碼由上往下走 ```javascript= // 要造訪頁面才會經過流程 要有請求才會觸發 類似生命週期 // app.use包著中間鍵 (request,response,next)=>{} app.use((request,response,next)=>{ let time = new Date(); console.log(`我是第一名 在 ${time.toISOString()}`); // 要往下走 就給一個next(); 沒寫next就不會往下跑 next(); }); ``` - 遇到 response.send() 就結束 ```javascript= // 遇到 response.send() 之後就近到sever回覆response app.get("/", function (request, response, next) { response.send("Hello nodemon"); }); ``` - .json() 讀取json檔案 app.get()用在router上面(特殊中間件) ```bash= app.get("/stock", async (req,res,next)=>{ let result = await connection.queryAsync("SELECT * FROM stock"); res.json(result); }) ``` - 中間鍵每個都會經過,最後再進到路由(router) - router是特殊中間鍵(如果不呼叫response就沒有終點 會一直pending) ![](https://i.imgur.com/m3ydIw6.png) - router針對各支股票撈取資料(後面用:帶變數) ```javascript= // 後面用:帶變數 搭配固定用法 req.params.{stockCode} //網址後面有股票代碼就抓取對應的資料 app.get("/stock/:stockCode",async (req,res,next)=>{ //規定用法 req.params.stockCode let result = await connection.queryAsync("SELECT * FROM stock_price where stock_id=?",[req.params.stockCode]); res.json(result); }) ``` - mysql.createConnection() -> 只有一個連線 - mysql.createPool() -> 可以有多個連線 - 用中間鍵處理404 (按順序需要放在最下面) ```javascript= // 處理 404 的中間件 app.use((req,res,next)=>{ res.status(404).send("Sorry 查無資料"); }) ``` - 處理cors問題 ```bash= # 一樣先下載cors 並放在所有"路由"和"中間件"前面 # 使用 cors const cors = require("cors"); app.use(cors()); ``` --- 8/28 偶合性 -> function內呼叫function 會有連動關係 用next();減少耦合性問題。 [HTTP狀態碼 status code](https://developer.mozilla.org/zh-TW/docs/Web/HTTP/Status) 200 >> 成功 401 >> 沒登入 403 >> 沒授權 404 >> 客戶端錯誤 500 >> 伺服器錯誤 - next()沒帶參數,正常前往下一個 - next()中帶參數,呼叫express有錯誤,用捕捉錯誤的middleware去接 ```bash= # 也可以寫判斷式進到next錯誤訊息 let isLogin = false; if(isLogin){ next(); }else{ next({ status:401, message:"沒登入", }); } # next()沒帶參數,正常前往下一個 # next()中帶參數,呼叫express有錯誤,用捕捉錯誤的middleware去接 app.get("/about", function (request, response, next) { console.log("我是about"); # 將next中放入錯誤訊息物件 統一丟給特殊middleware處理 next({ code:"1001", status:500, message:"測試錯誤" }); }); # 超級特殊的middleware有"四個參數" 用來捕捉錯誤的 通常放在最下面 app.use((err,req,res,next)=>{ # err接的是next()中的東西 console.error(err); res.status(err.status).json(err.message); }); ``` react中加入`REACT_APP_`類似於.env功能,自定義環境變量 https://create-react-app.dev/docs/adding-custom-environment-variables/ ### 前後端整合 localhost ### 做分頁 - 注意localhost port 要統一(可以開啟network檢查工具檢查連線) (十年的)資料多>>後端分頁 (3天的)資料少>>前端分頁 ```sql= -- sql計算比數的語法 用count SELECT COUNT(*) AS total FROM stock_price where stock_id=? ``` ```javascript= // 無條件進位 Math.ceil() ``` 1. react 做了一個 config.js --> .env failed??? 2. 在用到設定的地方都 import config.js 的變數 3. 後端分頁: 取得總筆數、計算總頁數、取得該頁資料、回覆 pagination 給前端 4. 前端: 先取得正確格式 res.data --> res.data.result 先驗證原本的東西沒有壞 5. 開始做前端頁碼: 渲染頁碼、setTotalPage、修改 useEffect 跟呼叫的 API 網址 ```javascript= app.get("/stock/:stockCode",async (req,res,next)=>{ //做分頁 let page = req.query.page || 1; // 目前在第幾頁,預設是第一頁 const perPage = 10 ; // 每一頁的資料10筆 let count = await connection.queryAsync("SELECT COUNT(*) AS total FROM stock_price where stock_id=?",[req.params.stockCode]); console.log(count); // 是一個物件 RowDataPacket { total: 17 } const total = count[0].total // 就可以抓到總筆數了 const lastPage = Math.ceil(total/perPage); // Math.ceil無條件進位 (總筆數/一頁10筆)的無條件進位=總頁數 console.log(total,lastPage); // LIMIT 要取幾筆資料 // OFFSET 要跳過多少 let offset = (page-1) * perPage; let result = await connection.queryAsync("SELECT * FROM stock_price where stock_id=? ORDER BY date LIMIT ? OFFSET ?",[req.params.stockCode,perPage,offset]); res.json(result); }) ``` - react 做了一個 config.js --> .env failed??? - .env 柏元測試出來了,要放在 src 的外面,也就是跟 src 同一層才可以! https://create-react-app.dev/docs/adding-custom-environment-variables/ req.params.stockCoode req.query.page API 對模組來說就是`開一個網址給人使用` ### 註冊 先建資料庫 再切前端表格頁面 再做後端api `<label></label>的 for跟id要一樣才會有功用` API_url 做法( 不用每次都要重寫 http://localhost:3002 ) 可以再額外建立一個utils>config檔 裡面export API_url ```bash= #記得export export const API_URL = process.env.REACT_APP_API_URL ``` 再在src“同層”建立.env檔 並在config.js中引用 ```bash= # 記得加上REACT_APP_ (react用法) REACT_APP_API_URL=http://localhost:3002 ``` 連線 axios 後面代要傳過去的資料,{name,email,password,confirmPassword} ```bash= let response = await axios.post(`${API_URL}/auth/register`, { name, email, password, confirmPassword, }); ``` RESTFUL API??? - HTTP Method: get, post, put, patch, delete https://developer.mozilla.org/zh-TW/docs/Web/HTTP/Methods 後端寫register的網址 - 要抓取前端來的資料因為是發送到後端 所以要用req去接 - 資料放在body裡面 所以是`req.body` ```bash= # 做register的網址 app.post("/auth/register",(req,res,next)=>{ # 要抓取前端來的資料因為是發送到後端 所以要用req去接 資料放在body裡面 console.log(req.body); res.json({}); }) ``` - express規定要讀取req.body 需要引入這兩個中間件 ```javascript= // 要使用這兩個中間件,不然req.body會是undefined // 使用這個中間件才可以讀到body的茲料 app.use(express.urlencoded({extended:true})); // 使用這個中間件才可以讀json格式 app.use(express.json()); ``` - 存進資料庫 ```javascript= // 存進資料庫 let result = await connection.queryAsync("INSERT INTO members (email, password, name) VALUE (?);" , [[ req.body.email, req.body.password, req.body.name, ]]); ``` - 加入驗證 - [使用 express-validator 套件](https://express-validator.github.io/docs/) ```javascript= // 引入express-validator部分功能 body, validationResult const { body, validationResult } = require('express-validator'); // 引入express-validator部分功能 body, validationResult const { body, validationResult } = require('express-validator'); // 建立驗證規則 const registerRules =[ body("email").isEmail().withMessage("Email 欄位請填寫正確格式"), body("password").isLength({min:6}).withMessage("密碼長度至少為6"), // .custom() express-validator客製化規則語法 body("confirmPassword").custom((value,{req})=>{ // 客製化規則值要等於req.body.password return value === req.body.password; }).withMessage("密碼驗證不一致"), ]; // 路由中間件可以再放其他中間件 // app.post("path","中間鍵1","中間鍵2",....) // 將registerRules放到指定路由下 只讓特定路由使用 app.post("/auth/register",registerRules,async(req,res,next)=>{ // 用validateResult韓式接住req const validateResult = validationResult(req); console.log(validateResult); // .... ``` - 驗證 方法1.app.post("path","中間鍵1","中間鍵2",....) 方法2.app.post("path","中間鍵1(真正的處理函式)") ```javascript= try{ let response = await axios.post(`${API_URL}/auth/register`, { name, email, password, confirmPassword, }); } catch (e) { // 前端可以透過 e.response 拿到 axios 的 response console.error(e.response); ``` - 密碼加密 - 使用bcrypt套件 https://www.npmjs.com/package/bcrypt ```javascript= // 密碼加密 bcrypt.hash(明文,salt) // 因為是非同步記得加await let hassPassword = await bcrypt.hash(req.body.password,10); ``` - 要上傳檔案(圖片)的版本,需要透過 FormData ```javascript= // 要上傳檔案的版本,需要透過 FormData let formData = new FormData(); formData.append("name", name); formData.append("email", email); formData.append("password", password); formData.append("confirmPassword", confirmPassword); formData.append("photo", photo); // 後免傳遞的格式改成formData let response = await axios.post(`${API_URL}/auth/register`, formData); ``` - 原本json格式 ==Content-Type: application/json== - 使用formData之後header格式變成-> ==Content-Type: multipart/form-data== - 所以要再使用 [multer 套件處理]( https://www.npmjs.com/package/multer) - 其他類似的套件: multiparty, busyboy, formidable,… - 再建立 public>uploads 放上傳圖片檔案 ```javascript= // !為了處理 multipart/form-data 所以要再使用 [multer 套件處理] const multer = require("multer"); // 先使用diskStorage設定儲存方式 // 通常儲存在硬碟中用 .diskStorage() const storage = multer.diskStorage({ // 設定儲存目的地 destination: function (req, file, cb) { cb(null,path.join(__dirname,"../","public","uploads")); }, // 設定儲存名稱 filename: function (req, file, cb) { console.log(file); cb(null, file.originalname); }, }); // 再開始使用multer處理上傳檔案 const uploader = multer({ // 檔案儲存位置 storage: storage, // 檔案格式驗證!很重要! fileFilter: function (req, file, cb) { if( file.mimetype !== "image/jpeg" && file.mimetype !== "image/jpg" && file.mimetype !== "image/png" ){ cb(new Error("不接受的檔案型態"), false); } cb(null,true); }, // 檔案大小限制 limits: { fileSize: 1024 * 1024, }, }); ``` - 再將multer中間件 `uploader.single("photo")` 放進去 ```javascript= router.post( "/register", // multer 中間件需要放在 validation驗證前面 因為驗證規則要用到解譯過的資料 uploader.single("photo") , registerRules, async (req, res, next) => { ..... ``` - 引入 node.js內建物件 path 使用路徑 ```javascript= // 引入 node.js內建物件 path 使用路徑 const path = require("path"); // 路徑用法 cb(null,path.join(__dirname,"..","public","uploads")); }, // -> __dirname/../public/uploads ``` - 讀檔案 要引用fs套件 ```bash= const fs = require("fs/promises"); fs.reaadFile("檔案路徑"); ``` ```bash= __dirname dirname ├── first.js ├── stock.txt └── sub └── second.js ``` 我人在 dirname/ 的時候,執行 node sub/second.js 是可以讀得到 stock.txt 我人已經在 dirname/sub 裡了,node second.js,請問讀得到嗎? A 讀得到 B 讀不到 V - readFile 時,不是從這個程式本身的位置來出發,他是從你下執行指令 node 的位置開始找。 從頭到尾,second.js 跟 stock.txt 的相對位置不重要 重要的是你在哪裡下 node second.js 指令 解法1: 寫絕對路徑 -> 就跟你從哪裡下 node 沒有關係了 太長了,而且沒有彈性、檔案都不能搬動 /Users/xxx/ooo/node-workshop/dirname/stock.txt 解法2: 加上 …/ ==> 相對路徑 => 又跟你人在哪裡下 node 有關了 當我在 node-workshop/dirname/ 下 node 的時候,變成要去 node-workshop/stock.txt 但不會有… ==解法3(最佳解法):== 當我在 /dirname `執行 node sub/second.js` 的時候 __dirname = /Users/azole/Sites/node-workshop/dirname/sub 當我在 /dirname/sub 裡,`執行 node second.js` 的時候 __dirname = /Users/azole/Sites/node-workshop/dirname/sub ==__dirname => 其實就是 second.js 所在的檔案夾位置==,跟你人在哪裡下指令有沒有關係?沒有! 所以我們就可以利用 __dirname 來組合路徑,那這樣就跟你在哪裡執行 node 沒有關係了 - 三種組合路徑的方式 ```bash= let filepath = __dirname + "/../" + "stock.txt"; let filepath = [__dirname, "..", "stock.txt"].join("/"); let filepath = path.join(__dirname, "..", "stock.txt") ``` - 程式碼太長要改成module - 建立 routers檔案夾 > stock.js ==router的模組固定寫法== ```javascript= // router的模組固定寫法 // 這裡是stock router的模組 const express = require("express"); const router = express.Router(); // router就是在app底下得一個小型app // 中間app.use等都改成router.use.... module.exports = router; ``` 並在sever.js引用進這個router ```javascript= let stockRouter = require("./routers/stock"); // 使用router中間鍵 // /stock // /stock/:stockCode app.use("/stock",stockRouter); ``` ==!記得sever改為/stock之後 stockrouter的路徑就要刪除/stock 不然會重複== MVC model-view-controller - 在更改mysql中日期的格式! ```bash= let connection = mysql.createPool({ host: process.env.DB_host, user: process.env.DB_user, port: process.env.DB_port, password: process.env.DB_password, database: process.env.DB_database, # 使用預設值或10 connectionLimit : process.env.CONNECTION_LIMIT || 10 , # 更改mysql中日期的格式! # 原本格式 "date": "2021-08-02T16:00:00.000Z", # 修改後格式 "date": "2021-08-02", dateStrings: true, }); ``` --- 8/29 - Multer丟出來的錯誤是MulterError 不符合我們自訂格式 要做特別處理 - [instanceof](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/instanceof) instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。 ```javascript= app.use((err,req,res,next)=>{ console.error("來自四個參數的錯誤處理",err); // Multer丟出來的錯誤是MulterError 不符合我們自訂格式 if(err instanceof MulterError){ if(err.code === "LIMIT_FILE_SIZE"){ return res.status(400).json({message:"檔案太大拉"}); } return res.status(400).json({message:err.message}); } res.status(err.status).json(err.message); }); ``` - [javascript-prototype-chain 面試必考題](https://azole.medium.com/javascript-prototype-chain-ee5a90f6fa5e) - 將上傳檔案取新檔名 兩個常用方法 - 根據 uuid 或 datetime - [1.uuid 使用uuidv4套件](https://www.npmjs.com/package/uuidv4) - [2.datetime 使用Date.now()](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Date/now) ```javascript= // 設定儲存名稱 filename: function (req, file, cb) { console.log(file); // 將上傳檔案取新檔名 兩個常用方法 uuid 或 datetime // uuidv4 or Date.now() // 建議使用命名 -> member-75442486-0878-440c-9db1-a7006c25a39f.jpg // 處理.jpg等格式 const ext = file.originalname.split(".").pop(); // cb(null, 新名字) cb(null, `member-${Date.now()}.${ext}`); }, ``` - 使用中間件 express.static 來設定靜態檔案的位置 ```bash= # 使用中間件來設定靜態檔案的位置 讓後端伺服器讀得到上傳後的靜態檔案 app.use(express.static(path.join(__dirname,"public"))); ``` ### 正式環境部署 - 在前端檔案夾使用yarn build 編譯 - 會出現build檔案夾 ```bash= $ yarn build ``` - 再在後端sever.js中設置靜態檔案位置 ```bash= app.use(express.static(path.join(__dirname,"react"))); ``` - 再將前端檔案夾中得build檔案夾內容複製放到react檔案夾中 ==這樣就可以將前端東西放到後端伺服器上了!!== - react底層好像是webpack-dev-server - https://www.npmjs.com/package/webpack-dev-server 開發時會這樣做: 啟動 react => webpack-dev-server 跑在 3000 啟動 epxress => 3002 前端 http://localhost:3000 -> 測試前端 axios 是對 http://localhost:3002 打 API 正式環境部署的時候「可以」這樣做:(還有其他不同的做法) 當我們把 react build 完、且複製這個 build 檔案夾到 express 去 (express 有指定這個檔案夾是靜態資源目錄 express.static() ) 因為 build 裡有一個 index.html http://localhost:3002 (express) 也可以開啟這個編譯過後的 index.html (react) 開發的時候會這樣部署嗎? 部署是一種風險高的,特別是人為動作很多的時候 - 設定圖片存進mysql資料庫的檔案位置 ```javascript= // 設定圖片存進mysql資料庫的檔案位置 要儲存 uploads/member-1631240809142.jpg let filename = req.file ? "/uploads/" + req.file.filename : ""; // http://localhost:3002/uploads/member-1631240809142.jpg // 再存入mysql let result = await connection.queryAsync( "INSERT INTO members (email, password, name, photo) VALUE (?);", [[req.body.email, hassPassword, req.body.name, filename]] ); ``` - 檢查帳號是否重複 ```javascript= // 檢查帳號是否重複 let member =await connection.queryAsync("SELECT * FROM members WHERE email = ?",[req.body.email]); if(member.length > 0){ return next({ code:330001, status:400, message:"已經註冊過了", }) } ``` --- 0911 跨來源請求「預設」是不帶 cookie 要開放的話: 後端的 cors 要設定 ```bash= const cors = require("cors"); app.use( cors({ origin: ["http://localhost:3000"], // 跨源送 cookie // 如果要把 credentials 設成 true, 那 origin 就不能是 * // 不然太恐怖,誰都可以跨源送 cookie credentials: true, }) ); ``` 前端的 axios 也要設定 login 也要加 需要驗證身份的 api 呼叫也要加 ```bash= let result = await axios.post( `${API_URL}/auth/login`, // 設定可以跨源存取 cookie { email, password }, { withCredentials: true, } ); cross-origin vs cross-site 這兩個東西是不一樣的,然後都會影響 cookie 能否讀取 ``` 45:30 ![](https://i.imgur.com/pDUofxz.png) ![](https://i.imgur.com/3sYU1tq.png) - session&cookie -> cretidation (較新專案) 身份驗證完,將資料記錄在session中,例如session.member,此後靠是否有資料來判斷登入。 - JWT(token 較舊專案) ### JWT 同源預設會帶cookie所以JWT通常運用在跨源上, 身份驗證完,後端會產生一個token,將token回傳給前端,前端需要自己把token存起來,存在localstorage(前端)。 當前端發出任何需要身份驗證的API,需要自己帶著這個token。 後端如何處理token 1. 把token存在資料庫 - 當有token請求來時,就去資料庫看是否有這個token - 登出就是把資料庫的這個token刪除 2. 後端完全不存token - 當有token請求來時,他會解這個token然後取出資料 - 這個方法完全不需要存取資料庫 - 無法做token登出(可以但很麻煩) - jwt - refresh token: 24hr - access token: 10min - 每次資料要用access token - 如果access token過期了,就用refresh token再去return一個新的 access token ```javascript= // 下載jwt $ npm install jsonwebtoken const jwt = require('jsonwebtoken'); // 後端完全不存token const token = jwt.sign(returnMember,process.env.JWT_SECERT,{expiresIn:"24h"}); // 將tokie回覆給前端 res.json({ .... token:token }) ``` - passport - passport-facebook - react-facebook-login --- https://developer.mozilla.org/en-US/docs/Web/API/Element ### 尋找元素節點 - nextElementSibling 下一個 - previousElementSibling 上一個 - Event.currentTarget - Event.target - Spread syntax (...) 展開運算子(...) 允許可迭代的陣列或字串展開成0到多個參數(如果是function的話)或是0到多個元素(如果是array或字組的話),或如果是物件的話則展開成0到多個key-value pair。 https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Operators/Spread_syntax - FileReader 藉由 FileReader 物件,Web 應用程式能以非同步(asynchronously)方式讀取儲存在用戶端的檔案(或原始資料暫存)內容,可以使用 File 或 Blob 物件指定要讀取的資料。 https://developer.mozilla.org/zh-TW/docs/Web/API/FileReader - FileReader.onload 於讀取完成時觸發 - FileReader.result 讀入的資料內容。只有在讀取完成之後此屬性才有效,而資料的格式則取決於是由哪一個方法進行讀取。 - FileReader.readAsDataURL() 開始讀取指定的 Blob,讀取完成後屬性 result 將以 data: URL 格式(base64 編碼)的字串來表示讀入的資料內容。 - bootstraap 垂直置中 d-flex align-items-center --- ==react面試前記得要練習用redux== ORM(面試考題) [Sequelize ORM](https://sequelize.org/) 資料庫協同作業工具 MVC 就是組織程式結構跟職責切割 重構 不改變功能的情況下,改寫程式 - github金鑰過期要重新申請 https://docs.github.com/en/github/authenticating-to-github/keeping-your-account-and-data-secure/creating-a-personal-access-token
{"metaMigratedAt":"2023-06-16T05:45:15.993Z","metaMigratedFrom":"Content","title":"node.js練習筆記","breaks":true,"contributors":"[{\"id\":\"3bf0a96b-7210-4533-8698-51c161dcf339\",\"add\":25804,\"del\":3317}]"}
    1031 views