{"id":21178698,"url":"https://github.com/poabob/nodejs","last_synced_at":"2025-08-24T02:10:47.640Z","repository":{"id":36684466,"uuid":"211254483","full_name":"POABOB/nodejs","owner":"POABOB","description":null,"archived":false,"fork":false,"pushed_at":"2023-07-17T19:02:09.000Z","size":1970,"stargazers_count":1,"open_issues_count":2,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-14T18:49:04.145Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/POABOB.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-09-27T06:49:20.000Z","updated_at":"2023-06-22T04:24:01.000Z","dependencies_parsed_at":"2025-01-21T11:45:39.163Z","dependency_job_id":"fcb92610-d68a-4b50-9d08-361ddc9cc68b","html_url":"https://github.com/POABOB/nodejs","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/POABOB/nodejs","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/POABOB%2Fnodejs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/POABOB%2Fnodejs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/POABOB%2Fnodejs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/POABOB%2Fnodejs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/POABOB","download_url":"https://codeload.github.com/POABOB/nodejs/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/POABOB%2Fnodejs/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271780401,"owners_count":24819292,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-24T02:00:11.135Z","response_time":111,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-20T17:23:27.629Z","updated_at":"2025-08-24T02:10:47.590Z","avatar_url":"https://github.com/POABOB.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Nodejs\r\n\r\n## 前言\r\n\r\n### Nodejs 的真正用途\r\n* Nodejs，一個JS的執行環境\r\n* 執行在Server =\u003e Web server\r\n* 執行在本地 =\u003e 打包、構件工具\r\n\r\n### Nodejs 困惑\r\n* 執行於Server端，非瀏覽器環境\r\n* 開發思維與前端完全不一樣\r\n\r\n## Nodejs 介紹\r\n\r\n### 下載\u0026安裝\r\n\r\n* 一般安裝\r\n\r\n\u003e https://nodejs.org/en/ ，下載並安裝\r\n\u003e 打開終端機，執行node -v和npm -v\r\n\r\n* NVM安裝\r\n\r\n\u003e NVM，nodejs版本管理工具，可切換多個nodejs版本\r\n\u003e MacOS，使用brew install nvm\r\n\u003e Windows，github搜尋nvm-windows，https://github.com/coreybutler/nvm-windows 下載\r\n\r\n* 使用NVM\r\n\r\n\u003e nvm list 查看目前所有node版本\r\n\u003e nvm install v10.13.0 安裝指定版本\r\n\u003e nvm use --delete-prefix 10.13.0 切換版本\r\n\r\n\r\n* 總結\r\n\r\n\u003e 如果使用多版本的node，建議用NVM\r\n\u003e 無論如何安裝，版本要\u003e=8.0\r\n\r\n### nodejs和前端JS區別\r\n* ECMAScript(ES)\r\n\r\n\u003e 定義了語法，寫js和nodejs須遵守\r\n\u003e 變數定義、循環、判斷、函數\r\n\u003e 原型和原型鏈、作用域和閉包、異步\\\r\n\u003e 可參考http://es6.ruanyifeng.com/\r\n\u003e 不能操作DOM、監聽click事件、ajax請求\r\n\u003e 不能處理http請求、操作文件\r\n\r\n\r\n* javascript\r\n\r\n\u003e 使用ECMAScript(ES)，外加Web API，缺一不可\r\n\u003e DOM、BOM、事件綁定、Ajax...等\r\n\u003e 兩者結合，即可完成瀏覽器操作\r\n\r\n* nodejs\r\n\r\n\u003e 使用ECMAScript(ES)，外加Web API，缺一不可\r\n\u003e 處理http請求、處理文件，可參考https://nodejs.org/dist/latest-v10.x/docs/api/\r\n\u003e 兩者結合，即可完成Server端操作\r\n\r\n* 總結\r\n\r\n\u003e ECMAScript(ES)是語法規範\r\n\u003e nodejs = ECMAScript(ES) + nodejs API\r\n\r\n* 補充ˇ\r\n\r\n\u003e commonjs 模塊化\r\n\t\u003e require \u0026 exports\r\n\t\u003e ex. a.js =\u003e module.exports = { add, mul }\r\n\t\u003e ex. b.js =\u003e const { add, mul } = require('./a')\r\n\u003e nodejs debugger\r\n\t\u003e JsDebuggr(sublime)的Ctrl+F10可以設定斷點，並在瀏覽器執行\r\n\r\n### Server開發和前端開發區別\r\n\r\n* 服務穩定性\r\n\r\n\u003e Server端可能遭受各種惡意攻擊和錯誤操作\r\n\u003e 單個Client端可以惡意掛掉，但Server端不行\r\n\u003e PM2進程守護\r\n\r\n* 考慮記憶體和CPU(優化、擴充)\r\n\r\n\u003e Client 獨佔一個瀏覽器，記憶體和CPU不是問題\r\n\u003e Server端要承載很多請求，CPU和記憶體都是稀缺資源\r\n\u003e 使用stream寫日誌，redis存session\r\n\r\n* 日誌紀錄\r\n\r\n\u003e 前端也有日誌紀錄，但只是日誌的發起方，不關心後續\r\n\u003e Server端要記錄日至、存儲日誌、分析日誌\r\n\r\n* 安全\r\n\r\n\u003e Server端要隨時準備接收各種惡意攻擊，前端多則少很多\r\n\u003e 例如:越權操作、SQL Injection...\r\n\r\n* 集群和服務拆分\r\n\r\n\u003e 產品開發效率快，流量可能會迅速增加\r\n\u003e 透過擴展機器和服務拆分來承載大流量\r\n\r\n* 總結\r\n\r\n\u003e 列出前後端區別\r\n\u003e 將如何在nodejs中解決\r\n\r\n### 總結\r\n\r\n* nodejs 下載安裝\r\n* nodejs和前端js的區別、commonjs和debugger\r\n* Server開發和前端開發區別，重點在於切換思路\r\n\r\n## 案例分析和設計\r\n\r\n### 目標\r\n\r\n* 開發一個Blog，具有Blog基本功能\r\n* 只開發Server端，不關心前端\r\n\r\n### 需求\r\n\r\n* 首頁、作者主頁、詳細頁\r\n* 登入頁\r\n* 管理中心、新建頁、編輯頁\r\n\r\n### 總結\r\n* 需求一定要明確，需求指導開發\r\n* 不要糾結於簡單的頁面樣式，並不影響Server端的複雜度\r\n\r\n## API和資料存儲\r\n\r\n### 資料如何存儲\r\n\r\n* 貼文\r\n\r\n| id | title | content | created_at | author |\r\n| -- | -- | -- | -- | -- |\r\n| 1 | title1 | content1 | 123 | Bob |\r\n| 2 | title2 | content1 | 123 | Bob |\r\n\r\n* 用戶\r\n\r\n| id | name | password | created_at |\r\n| -- | -- | -- | -- |\r\n| 1 | Bob | qqq | 123 |\r\n| 2 | Dick | qqq | 123 |\r\n\r\n### 如何與前端對接，即API設計\r\n\r\n* 接口設計\r\n\r\n| 描述 | 接口 | 方法 | url參數 | 備註 |\r\n| -- | -- | -- | -- | -- |\r\n| 獲取貼文列表 | /api/blog/list | get | author, keyword | 參數為空，則不進行查詢過濾 |\r\n| 獲取一篇貼文的內容 | /api/blog/detail | get | id |  |\r\n| 新增一篇貼文 | /api/blog/new | post | 123 | post中有新增的訊息 |\r\n| 更新一篇貼文 | /api/blog/update | post | id | postData中有更新的內容 |\r\n| 刪除一篇貼文 | /api/blog/del | post | id |  |\r\n| 登入 | /api/blog/login | post | 123 | postData中有name和password |\r\n\r\n### 登入\r\n\r\n* 有統一解決方案，一般不用重新設計\r\n* 實現起來比較複雜\r\n\r\n### 總結\r\n\r\n* 細節不懂可以不用深究\r\n* 只要明白存儲和API的概念的用途\r\n\r\n## 開發API(不使用任何框架)\r\n\r\n### nodejs 處理http請求\r\n\r\n#### http請求概述\r\n\r\n* 範例在tag v0.1之中\r\n* DNS解析，建立TCP連線，發送http請求\r\n* Server接受http請求，處理並返回\r\n* Client端接受到返回數據，處理數據(頁面渲染、js執行)\r\n* 簡單範例\r\n\r\n`nodejs\\example\\http.js`\r\n```gherkin=\r\nconst http = require('http');\r\n\r\nconst server = http.createServer((req, res) =\u003e {\r\n\tres.end('Hello World!');\r\n});\r\n\r\nserver.listen(8000);\r\n```\r\n\r\n#### nodejs 處理get請求\r\n\r\n* get請求，即Client要項Server端獲取數據，如查詢貼文列表\r\n* 通過querystring來傳遞數據，如a.html?a=100\u0026b=200\r\n* 瀏覽器直接訪問，就發送get請求\r\n* 簡單範例\r\n\r\n`nodejs\\example\\get.js`\r\n```gherkin=\r\nconst http = require('http');\r\nconst querystring = require('querystring');\r\n\r\nconst server = http.createServer((req, res) =\u003e {\r\n\t//GET\r\n\tconsole.log('Method: ', req.method);\r\n\t//獲取完整URL\r\n\tconst url = req.url;\r\n\tconsole.log('Url: ', url);\r\n\t//解析querystring\r\n\treq.query = querystring.parse(url.split('?')[1]);\r\n\tconsole.log('Query: ', req.query);\r\n\t//querystring返回\r\n\tres.end(JSON.stringify(req.query));\r\n\r\n});\r\n\r\nserver.listen(8000);\r\nconsole.log('Listening on port 8000');\r\n```\r\n\r\n#### nodejs 處理post請求\r\n\r\n* post請求，即Client要項Server端傳遞數據，如新增貼文\r\n* 通過post data來傳遞數據\r\n* 瀏覽器無法直接訪問，需要寫JS或是使用postman\r\n* 簡單範例\r\n`nodejs\\example\\post.js`\r\n```gherkin=\r\nconst http = require('http');\r\n\r\nconst server = http.createServer((req, res) =\u003e {\r\n\r\n\t//POST\r\n\tif(req.method == 'POST') {\r\n\t\t//數據格式\r\n\t\tconsole.log('Content-type: ', req.headers['content-type']);\r\n\t\t//接收數據\r\n\t\tlet postData = \"\";\r\n\t\treq.on('data', chunk =\u003e{\r\n\t\t\tpostData += chunk.toString();\r\n\t\t});\r\n\t\treq.on('end', () =\u003e {\r\n\t\t\tconsole.log(postData);\r\n\t\t\tres.end('Hello World!');\r\n\t\t});\r\n\t}\r\n});\r\n\r\nserver.listen(8000);\r\nconsole.log('Listening on port 8000');\r\n```\r\n\r\n#### nodejs 處理路由\r\n\r\n* 簡單範例\r\n`nodejs\\example\\url.js`\r\n```gherkin=\r\nconst http = require('http');\r\n\r\nconst server = http.createServer((req, res) =\u003e {\r\n\t//獲取完整URL\r\n\tconst url = req.url;\r\n\tconst path = url.split('?')[0];\r\n\t//返回路由\r\n\tres.end(path);\r\n});\r\n\r\nserver.listen(8000);\r\nconsole.log('Listening on port 8000');\r\n```\r\n\r\n#### nodejs 綜合範例\r\n\r\n* 簡單範例\r\n`nodejs\\example\\index.js`\r\n```gherkin=\r\nconst http = require('http');\r\nconst querystring = require('querystring');\r\n\r\nconst server = http.createServer((req, res) =\u003e {\r\n\t//獲取完整URL\r\n\tconst method = req.method;\r\n\tconst url = req.url;\r\n\tconst path = url.split('?')[0];\r\n\tconst query = querystring.parse(url.split('?')[1]);\r\n\r\n\t//設定返回格式為JSON\r\n\tres.setHeader('Content-type', 'application/json');\r\n\tconst resData = {\r\n\t\tmethod,\r\n\t\turl,\r\n\t\tpath,\r\n\t\tquery\r\n\t};\r\n\r\n\t//POST\r\n\tif(method === 'GET') {\r\n\t\tres.end(JSON.stringify(resData));\r\n\t}\r\n\r\n\tif(method === 'POST') {\r\n\t\t//接收數據\r\n\t\tlet postData = \"\";\r\n\t\treq.on('data', chunk =\u003e {\r\n\t\t\tpostData += chunk.toString();\r\n\t\t});\r\n\t\treq.on('end', () =\u003e {\r\n\t\t\tresData.postData = postData;\r\n\t\t\tres.end(JSON.stringify(resData));\r\n\t\t});\r\n\t}\r\n});\r\n\r\nserver.listen(8000);\r\nconsole.log('Listening on port 8000');\r\n```\r\n\r\n### 搭建開發環境\r\n\r\n* 範例在tag v0.2之中\r\n* 從0開始，不使用任何框架\r\n* 使用nodemon監測文件變化，自動重啟node\r\n* 使用cross-env設置環境變量，兼容mac、linux和windows\r\n* 在terminal 輸入，初始化並安裝\r\n```gherkin=\r\nnpm init\r\nnpm install nodemon cross-env --save-dev\r\n```\r\n* 並配置運行模式\r\n`nodejs\\package.json`\r\n```gherkin=\r\n  \"scripts\": {\r\n    \"test\": \"echo \\\"Error: no test specified\\\" \u0026\u0026 exit 1\",\r\n    \"dev\": \"cross-env NODE_ENV=dev nodemon ./bin/www.js\",\r\n    \"prd\": \"cross-env NODE_ENV=production pm2 ./bin/www.js\"\r\n  },\r\n```\r\n* 運行指令\r\n```gherkin=\r\nnpm run dev/prd...\r\n```\r\n* 簡單範例\r\n`nodejs\\bin\\www.js`\r\n```gherkin=\r\nconst http = require('http');\r\n\r\nconst PORT = 8000;\r\nconst serverHandler = require('../app');\r\n\r\nconst server = http.createServer(serverHandler);\r\n\r\nserver.listen(PORT);\r\nconsole.log('Listening on port 8000');\r\n```\r\n\r\n`nodejs\\app.js`\r\n```gherkin=\r\nconst serverHandler = (req, res) =\u003e {\r\n\t//設定返回格式為JSON\r\n\tres.setHeader('Content-type', 'application/json');\r\n\r\n\tconst resData = {\r\n\t\tmethod,\r\n\t\turl,\r\n\t\tpath,\r\n\t\tquery,\r\n\t\tenv: process.env.NODE_ENV\r\n\t};\r\n\tres.end(JSON.stringify(resData));\r\n};\r\n\r\nmodule.exports = serverHandler;\r\n```\r\n\r\n### 開發API(暫時不連接資料庫和登入)\r\n\r\n* 初始化路由: 根據之前方案設計，做出路由\r\n* 返回假數據: 將路由和數據處理分離，以符合設計原則\r\n\r\n#### 路由簡構\r\n\r\n* 範例\r\n`nodejs\\app.js`\r\n```gherkin=\r\nconst handleIndexRouter = require('./src/router/index');\r\nconst handleUserRouter = require('./src/router/user');\r\n\r\nconst serverHandler = (req, res) =\u003e {\r\n\t//設定返回格式為JSON\r\n\tres.setHeader('Content-type', 'application/json');\r\n\r\n\tconst url = req.url;\r\n\treq.path = url.split('?')[0];\r\n\r\n\t//處理index路由\r\n\tconst indexData = handleIndexRouter(req, res);\r\n\tif(indexData) {\r\n\t\tres.end(\r\n\t\t\tJSON.stringify(indexData)\r\n\t\t);\r\n\t\treturn;\r\n\t}\r\n\r\n\t//處理user路由\r\n\tconst userData = handleUserRouter(req, res);\r\n\tif(userData) {\r\n\t\tres.end(\r\n\t\t\tJSON.stringify(userData)\r\n\t\t);\r\n\t\treturn;\r\n\t}\r\n\r\n\t//不符合路由，返回404\r\n\tres.writeHead(404, {\"Content-type\": \"text/plain\"});\r\n\tres.write(\"404 Not Found\\n\");\r\n\tres.end();\r\n};\r\n\r\nmodule.exports = serverHandler;\r\n```\r\n* 處理Blog的路由簡構\r\n`nodejs\\src\\router\\index.js`\r\n```gherkin=\r\nconst handleIndexRouter = (req, res) =\u003e {\r\n\tconst method = req.method;\r\n\r\n\t//GET\r\n\t//獲取Blog貼文\r\n\tif(method === 'GET' \u0026\u0026 req.path === '/api/blog/list') {\r\n\t\treturn {\r\n\t\t\tmsg: '獲取Blog貼文'\r\n\t\t};\r\n\t\t// res.end(JSON.stringify(resData));\r\n\t}\r\n\r\n\t//獲取貼文詳情\r\n\tif(method === 'GET' \u0026\u0026 req.path === '/api/blog/detail') {\r\n\t\treturn {\r\n\t\t\tmsg: '獲取貼文詳情'\r\n\t\t};\r\n\t}\r\n\r\n\t//POST\r\n\t//新增貼文\r\n\tif(method === 'POST' \u0026\u0026 req.path === '/api/blog/new') {\r\n\t\treturn {\r\n\t\t\tmsg: '獲取貼文詳情'\r\n\t\t};\r\n\t}\r\n\r\n\t//更新貼文\r\n\tif(method === 'POST' \u0026\u0026 req.path === '/api/blog/update') {\r\n\t\treturn {\r\n\t\t\tmsg: '更新貼文'\r\n\t\t};\r\n\t}\r\n\r\n\t//刪除貼文\r\n\tif(method === 'POST' \u0026\u0026 req.path === '/api/blog/del') {\r\n\t\treturn {\r\n\t\t\tmsg: '刪除貼文'\r\n\t\t};\r\n\t}\r\n};\r\n\r\nmodule.exports = handleIndexRouter;\r\n```\r\n* 處理登入的路由簡構\r\n`nodejs\\src\\router\\user.js`\r\n```gherkin=\r\nconst handleUserRouter = (req, res) =\u003e {\r\n\tconst method = req.method;\r\n\r\n\t//POST\r\n\t//登入\r\n\tif(method === 'POST' \u0026\u0026 req.path === '/api/user/login') {\r\n\t\treturn {\r\n\t\t\tmsg: '登入'\r\n\t\t};\r\n\t}\r\n};\r\n\r\nmodule.exports = handleUserRouter;\r\n```\r\n\r\n#### 路由延伸(model, controller, promise)\r\n\r\n* 範例在tag v0.3之中\r\n* promise 處理範例\r\n`nodejs\\nodejs\\example\\promise.js`\r\n```gherkin=\r\nconst fs = require('fs');\r\nconst path = require('path');\r\n\r\n// // callback方式獲取一個文件內容\r\n// function getFileContent(fileName, callback) {\r\n// \tconst fullFileName = path.resolve(__dirname, 'file', fileName);\r\n// \tfs.readFile(fullFileName, (err, data) =\u003e {\r\n// \t\tif(err) {\r\n// \t\t\tconsole.error(err);\r\n// \t\t\treturn;\r\n// \t\t}\r\n// \t\tcallback(\r\n// \t\t\tJSON.parse(data.toString())\r\n// \t\t);\r\n// \t});\r\n// }\r\n\r\n// // 測試 callback-hell\r\n// getFileContent('a.json', aData =\u003e {\r\n// \tconsole.log('a data', aData);\r\n// \tgetFileContent(aData.next, bData =\u003e {\r\n// \t\tconsole.log('b data', bData);\r\n// \t});\r\n// });\r\n\r\n// promise方式獲取文件內容\r\nfunction getFileContent(fileName) {\r\n\tconst promise = new Promise((resolve, reject) =\u003e {\r\n\t\tconst fullFileName = path.resolve(__dirname, 'file', fileName);\r\n\t\tfs.readFile(fullFileName, (err, data) =\u003e {\r\n\t\t\tif(err) {\r\n\t\t\t\treject(err);\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\t\t\tresolve(\r\n\t\t\t\tJSON.parse(data.toString())\r\n\t\t\t);\r\n\t\t});\r\n\t});\r\n\treturn promise;\r\n}\r\n\r\ngetFileContent('a.json').then(aData =\u003e {\r\n\tconsole.log('a data', aData);\r\n\treturn getFileContent(aData.next);\r\n}).then(bData =\u003e {\r\n\tconsole.log('b data', bData);\r\n\treturn getFileContent(bData.next);\r\n});\r\n```\r\n\r\n#### 總結\r\n\r\n* nodejs處理http請求的常用技能，ARC(google plugin)\r\n* nodejs開發Blog的API(未連接mysql，未使用登入)\r\n* router和controller分開用意\r\n\r\n## Mysql、登入(cookie)和redis\r\n\r\n### mysql介紹、安裝和使用\r\n\r\n#### 介紹\r\n\r\n* web server中最流行的關係行資料庫\r\n* 官方網站可以免費下載，可用於學習\r\n* 輕量級，易學易用\r\n* 下載地址 https://dev.mysql.com/downloads/mysql/\r\n\r\n#### 安裝\r\n\r\n* 執行安裝\r\n* 如果需要輸入root用戶的密碼，要務必記住\r\n* 安裝mysql workbench(Client)，圖形化操作\r\n* 下載地址 https://dev.mysql.com/downloads/workbench/\r\n\r\n#### 使用\r\n\r\n* 開啟並輸入帳密\r\n* 查看現有資料庫\r\n\t*　show databases;\r\n* 建資料庫\r\n\t*　create schema `myDB`;\r\n* 建資料表\r\n\t* users\r\n\t\t* create table `myDB`.`users`(`id` int not null auto_increment, `name` varchar(20) not null, `password` varchar(126) not null, `create_at` datetime, primary key (`id`));\r\n\t* blogs\r\n\t\t* create table `myDB`.`blogs`(`id` int not null auto_increment, `title` varchar(52) not null, `content` longtext not null, `create_at` datetime, `author` varchar(20) not null, primary key (`id`));\r\n* 操作資料表\r\n\t* 增\r\n\t\t* INSERT INTO `users`( `name`, `password`) VALUES (`Bob`, `123456`)\r\n\t\t* INSERT INTO `blogs`(`title`, `content`, `author`) VALUES (`我`, `吃變變`, `Bob`)\r\n\r\n\t* 刪\r\n\t\t* DELETE FROM `users` WHERE `id` = 1\r\n\t\t* DELETE FROM `blogs` WHERE `id` = 1\r\n\r\n\t* 改\r\n\t\t* UPDATE `users` SET `id`=2,`name`=`POABOB` WHERE `id` = 1\r\n\t\t* UPDATE `blogs` SET `id`=2,`title`=`ㄎㄎ`,`content`=`變遍布建ㄌ` WHERE `id` = 1\r\n\r\n\t* 查\r\n\t\t* SELECT * FROM `users` WHERE `id` = 1\r\n\t\t* SELECT * FROM `blogs` WHERE `id` = 1\r\n\r\n#### 總結\r\n\r\n* 如何建資料庫，如何建資料表\r\n* 建資料表時常用資料類型(int bigint varchar longtext)\r\n* SQL語法實現增刪改查\r\n\r\n### nodejs連接mysql\r\n\r\n* 範例在tag v0.4之中\r\n* 範例: 用demo演示，不考慮使用\r\n* 封裝: 將其封裝成系統可用工具\r\n* 使用: 讓API直接操作資料庫，不再使用假資料\r\n\r\n#### 配置\r\n\r\n* 在'nodejs\\\\nodejs\\\\mysql-demo'輸入指令\r\n\t* npm init -y\r\n\t* npm install mysql\r\n\r\n### API連接mysql\r\n\r\n* 範例在tag v0.5之中\r\n* 範例\r\n`nodejs/nodejs/src/config/db.js`\r\n```gherkin=\r\n//環境參數\r\nconst env = process.env.NODE_ENV;\r\n\r\nlet MYSQL_CONF;\r\n\r\nif(env === 'dev') {\r\n\tMYSQL_CONF = {\r\n\t\t\thost: 'localhost',\r\n\t\t\tuser: 'root',\r\n\t\t\tpassword: 'root',\r\n\t\t\tport: '3306',\r\n\t\t\tdatabase: 'myDB'\r\n\t\t};\r\n}\r\n\r\nif(env === 'production') {\r\n\tMYSQL_CONF = {\r\n\t\t\thost: 'localhost',\r\n\t\t\tuser: 'root',\r\n\t\t\tpassword: 'root',\r\n\t\t\tport: '3306',\r\n\t\t\tdatabase: 'myDB'\r\n\t\t};\r\n}\r\n\r\nmodule.exports = {\r\n\tMYSQL_CONF\r\n};\r\n\r\n```\r\n\r\n`nodejs/nodejs/src/db/mysql.js`\r\n```gherkin=\r\nconst { MYSQL_CONF } = require('../config/db');\r\nconst mysql = require('mysql');\r\n\r\nconst conn = mysql.createConnection(MYSQL_CONF);\r\n\r\nconn.connect();\r\n\r\nfunction exec(sql) {\r\n\tconst promise = new Promise((resolve, reject) =\u003e {\r\n\t\tconn.query(sql, (err, result) =\u003e {\r\n\t\t\tif(err) {\r\n\t\t\t\treject(err);\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\t\t\tresolve(result);\r\n\t\t});\r\n\t});\r\n\r\n\treturn promise;\r\n}\r\n// conn.end();\r\n\r\nmodule.exports = {\r\n\texec\r\n}\r\n```\r\n\r\n#### 總結\r\n\r\n* nodejs 連接mysql，如何執行sq語法\r\n* 根據NODE_ENV區分配置\r\n* 封裝exec函數，API使用exec操作資料庫\r\n\r\n### 總結\r\n\r\n* 安裝Mysql和workbench\r\n* 創建資料庫、資料表、SQL語法和使用\r\n* nodejs連接Mysql，應用到API\r\n\r\n### 登入\r\n\r\n* cookie和session\r\n* session寫入redis\r\n* 開發登入功能，和前端聯調(nginx反向代理)\r\n\r\n#### cookie和session\r\n\r\n* 範例在tag v0.5之中\r\n* 什麼是cookie\r\n\t* 儲存於瀏覽器的一段字符串(最大5kb)\r\n\t* 跨域不共享(每個網站cookie不相同)\r\n\t* 格式如k1=v1;k2=v2;k3=v3，因此可以儲存結構化資料\r\n\t* 每次翻送http請求，會將請求域的cookie一起發送給server\r\n\t* server可以修改cookie並返回給瀏覽器\r\n\t* 瀏覽器可以通過js修改cookie(有限制)\r\n* session\r\n\t* 儲存於Server的字符串\r\n\t* 因為cookie會暴露重要的值，非常危險\r\n\t* 如何解決，cookie儲存userid，server對應userid並給其值的session\r\n\r\n#### session寫入redis\r\n\r\n* 範例在v0.7之中\r\n* session問題\r\n\t* 目前session直接為js變量，放在nodejs進程內存中\r\n\t* 一、進程內存有限，訪問量大，內存暴增會使系統崩饋潰\r\n\t* 二、正式上線為多進程，進程間的內存無法共享\r\n* 解決方案redis\r\n\t* web server最常用的緩存資料庫，資料將存放於內存中\r\n\t* 相對於mysql，訪問速度更快(內存與硬碟處理效能差別極大)\r\n\t* 相對成本較高，可儲存資料量更小(硬傷)\r\n\t* 將web server和redis拆分成兩個單獨的服務\r\n\t* 雙方皆為獨立，都是可以擴展(可擴展成集群)\r\n\t* mysql，亦是一個單獨的服務，也可礦展\r\n* 為什麼session適合用redis?\r\n\t* session訪問頻繁，對性能要求極大\r\n\t* session可不考慮斷電丟失數據問題\r\n\t* session資料量部會太大(相對於mysql)\r\n* 安裝redis\r\n\t* 下載 https://github.com/MSOpenTech/redis/releases\r\n\t* Mac使用brew install redis\r\n\t* Linux參考 http://www.runoob.com/redis/redis-install.html\r\n* redis-server使用\r\n\t* 開啟(已配置好環境變數)\r\n\t\t* redis-server.exe redis.windows.conf\r\n* redis-cli使用\r\n\t* 連線\r\n\t\t* redis-cli.exe -h 127.0.0.1 -p 6379\r\n\t* 插入session值\r\n\t\t* set {key} {value}\r\n\t* 獲取session值\r\n\t\t* get {key}\r\n\t* 獲取全部\r\n\t\t* keys *\r\n\t* 刪除session值\r\n\t\t* del {key}\r\n* 用redis儲存session\r\n\t* 範例在v0.6之中\r\n\r\n#### 前端聯調\r\n\r\n* 登入功能依賴cookie，必須用瀏覽器來聯調\r\n* cookie跨域不共享，前端和server必須同域\r\n* nginx\r\n\t* 修改/usr/local/etc/nginx/nginx.conf\r\n\t```gherkin=\r\n\t# 根據幾核心填寫\r\n\tworker_processes 2;\r\n\t# 在server{}裡面修改location\r\n\tlocation /nodejs/html/ {\r\n\t\tproxy_pass http://localhost:80/nodejs/html/;\r\n\t}\r\n\tlocation /api/ {\r\n\t\tproxy_pass http://localhost:8000;\r\n\t\tproxy_set_header Host $host;\r\n\t}\r\n\t```\r\n* apache2\r\n\t* 修改conf/httpd.conf，這些要註解掉\r\n\t```gherkin=\r\n\tLoadModule proxy_module modules/mod_proxy.so\r\n\tLoadModule proxy_connect_modulemodules/mod_proxy_connect.so\r\n\tLoadModule proxy_ftp_modulemodules/mod_proxy_ftp.so\r\n\tLoadModuleproxy_http_modulemodules/mod_proxy_http.so\r\n\t```\r\n\t* balancer如果沒有額外配置就不要開啟，apache會開不起來\r\n\t* 然後到conf/httpd.conf最下面輸入\r\n\t```gherkin=\r\n\t#反向代理\r\n\tProxyRequests Off\r\n\tProxyPass /api/ http://127.0.0.1:8000/api/\r\n\tProxyPassReverse /nodejs/html/ http://127.0.0.1:80/nodejs/html/\r\n\t#80為apache的監聽埠\r\n\t\u003cproxy http://127.0.0.1:80\u003e\r\n\t  AllowOverride None\r\n\t  Order Deny,Allow\r\n\t  Allow from all\r\n\t\u003c/proxy\u003e\r\n\t```\r\n\r\n#### 總結\r\n\r\n* cookie和session是什麼?如何實現登入\r\n* redis在這裡扮演甚麼角色，其核心價值是甚麼?\r\n* nginx的反向代理配置，聯調過程中的作用\r\n\r\n## 安全和日誌\r\n\r\n### 日誌\r\n\r\n* 系統沒有日誌 = 沒有眼睛\r\n* 一、訪問日誌 access log (server最重要的)\r\n* 二、自定義日誌(自定義事件、錯誤紀錄...等)\r\n\r\n#### nodejs文件操作\r\n\r\n* 範例\r\n`nodejs\\nodejs\\example\\file.js`\r\n```gherkin=\r\nconst fs = require('fs');\r\nconst path = require('path');\r\n\r\nconst fileName = path.resolve(__dirname, './file/data.txt');\r\n\r\n// //讀取文件\r\n// fs.readFile(fileName, (err, data) =\u003e {\r\n// \tif(err) {\r\n// \t\tconsole.log(err);\r\n// \t\treturn;\r\n// \t}\r\n// \t//轉換為字符串\r\n// \tconsole.log(data.toString());\r\n// });\r\n\r\n// //寫入文件\r\n// const content = 'WRITTED';\r\n// const opt = {\r\n// \tflag: 'a'\t//追加寫入，覆蓋用w\r\n// }\r\n// fs.writeFile(fileName, content, opt, (err) =\u003e {\r\n// \tif(err) {\r\n// \t\tconsole.log(err);\r\n// \t}\r\n\r\n// });\r\n\r\n// //判斷文件是否存在\r\n// fs.exists(fileName, (exists) =\u003e {\r\n// \tconsole.log(exists);\r\n// });\r\n```\r\n* I/O操作的性能瓶頸\r\n\t* I/O，包括\"網路\"I/O和\"文件\"I/O\r\n\t* 想比於CPU計算和內存讀寫，I/O特點就是\"慢\"\r\n\t* 如何在有限情況下提高I/O操作效率\r\n* stream\r\n\t* 標準輸入輸出，pipe就是管道(符合水流管道的模型圖)\r\n\t* process.stdin 獲取資料，直接通過管道傳遞給process.stdout\r\n\t\t* process.stdin.pipe(process.stdout)\r\n\t* 範例\r\n`nodejs\\nodejs\\example\\stream.js`\r\n```gherkin=\r\n//標準輸入輸出\r\n// process.stdin.pipe(process.stdout)\r\n\r\n// const http = require('http')\r\n\r\n// const server = http.createServer((req, res) =\u003e {\r\n//     if(method === 'POST') {\r\n// \t\treq.pipe(res)\r\n//     }\r\n// })\r\n\r\n// server.listen(8000)\r\n\r\n\r\n\r\n// const fs = require('fs')\r\n// const path = require('path')\r\n\r\n// //兩文件名\r\n// const file1 =  path.resolve(__dirname, 'file/data.txt')\r\n// const file2 =  path.resolve(__dirname, 'file/data-bak.txt')\r\n\r\n// //讀取文件的stream物件\r\n// const readStream = fs.createReadStream(file1)\r\n// const writeStream = fs.createWriteStream(file2)\r\n\r\n// //copy，通過pipe\r\n// readStream.pipe(writeStream)\r\n\r\n// readStream.on('data', chunk =\u003e {\r\n//     console.log(chunk.toString())\r\n// })\r\n\r\n// //資料讀取完成，即copy完成\r\n// readStream.on('end', function () {\r\n//     console.log('copyed!')\r\n// })\r\n\r\n\r\n\r\nconst http = require('http')\r\nconst fs = require('fs')\r\nconst path = require('path')\r\n\r\nconst file1 =  path.resolve(__dirname, 'file/data.txt')\r\nconst server = http.createServer((req, res) =\u003e {\r\n    if(req.method === 'GET') {\r\n        const readStream = fs.createReadStream(file1)\r\n\t\treadStream.pipe(res)\r\n    }\r\n})\r\n\r\nserver.listen(8000)\r\n\r\n```\r\n#### 日誌功能開發和使用\r\n\r\n* 範例\r\n`nodejs/nodejs/src/utils/log.js`\r\n```gherkin=\r\nconst fs = require('fs')\r\nconst path = require('path')\r\nconst { LOG_CONF } = require('../config/db');\r\n\r\n//寫日誌\r\nfunction writeLog(writeStream, log) {\r\n    writeStream.write(log + '\\n')\r\n}\r\n\r\n//write stream\r\nfunction createWriteStream(fileName) {\r\n    const fullFileName= path.join(__dirname, '../', '../', 'logs', fileName)\r\n    const writeStream = fs.createWriteStream(fullFileName, {\r\n        flags: 'a'\r\n    })\r\n    return writeStream\r\n}\r\n\r\n//寫訪問日誌\r\nconst accessWriteStream = createWriteStream('access.log')\r\nfunction access(log) {\r\n    if(LOG_CONF) {\r\n        console.log(log)\r\n        return\r\n    }\r\n    writeLog(accessWriteStream, log)\r\n}\r\n\r\n// //寫錯誤日誌\r\n// const errorWriteStream = createWriteStream('errors.log')\r\n// function error(log) {\r\n//     writeLog(errorWriteStream, log)\r\n// }\r\n\r\n// //寫事件日誌\r\n// const eventWriteStream = createWriteStream('event.log')\r\n// function event(log) {\r\n//     writeLog(eventWriteStream, log)\r\n// }\r\n\r\nmodule.exports = {\r\n    access,\r\n    // error,\r\n    // event\r\n}\r\n```\r\n\r\n`nodejs/nodejs/app.js`\r\n```gherkin=\r\n//寫access log\r\n\taccess(`${req.method} -- ${req.url} -- ${req.headers['user-agent']} -- ${Date.now()}`)\r\n```\r\n\r\n#### 日誌文件拆分，日誌內容分析\r\n\r\n##### 日誌拆分\r\n* 日誌內容會慢慢累積，放在一個文件中不好處理\r\n* 按照時間劃分日誌文件，如：2019-10-27\r\n* 實現方式：linux的crontab命令，即定時任務\r\n* crontab\r\n\t* 設定定時任務，格式：***** command\r\n\t* 將access.log cpoy成2019-10-27.access.log\r\n\t* 清空access.log，繼續累積日誌\r\n\t* 範例\r\n\t`nodejs/nodejs/utils/copy.sh`\r\n\t```gherkin=\r\n\t#!/bin/sh\r\n\tcd /opt/lampp/htdocs/nodejs/nodejs/logs\r\n\tcp access.log $(data +%Y-%m-%d).access.log\r\n\techo \"\" \u003e access.log\r\n\t```\r\n\t* terminal 輸入crontab -e，然後增加以下一行(每天0點執行)\r\n\t\u003e \\* 0 * * * sh /opt/lampp/htdocs/nodejs/nodejs/src/utils/copy.sh\r\n\r\n##### 日誌分析\r\n\r\n* 如何針對access.log日誌，分析chrome的佔比\r\n* 日誌是按行儲存，一行為一條\r\n* 使用nodejs readline(基於stream，效率高)\r\n* 範例\r\n`nodejs/nodejs/src/utils/readline.js`\r\n```gherkin=\r\nconst fs = require('fs')\r\nconst path = require('path')\r\nconst readline = require('readline')\r\nconst { LOG_CONF } = require('../config/db');\r\n\r\n//文件名\r\nconst fileName= path.join(__dirname, '../', '../', 'logs', 'access.log')\r\n//read Stream\r\nconst readStream = fs.createReadStream(fileName)\r\n//readline\r\nconst readline = fs.createInterface({\r\n    input: readStream\r\n})\r\n\r\nlet chromeNum = 0\r\nlet sum = 0\r\n\r\nreadline.on('line',  (lineData) =\u003e {\r\n    if(!lineData) {\r\n        return\r\n    }\r\n\r\n    //總行數\r\n    sum++\r\n\r\n    const arr = lineData.split(' -- ')\r\n    if(arr[2] \u0026\u0026 arr[2].indexOf('Chrome') \u003e 0) {\r\n        //累加chrome數量\r\n        chromeNum++\r\n    }\r\n})\r\n\r\nreadline.on('close', () =\u003e {\r\n    console.log('chrome 佔比：'+sum/chromeNum)\r\n})\r\n\r\n```\r\n#### 日誌- 總結\r\n* 日誌對server端的重要性，相當於人的眼睛\r\n* I/O性能瓶頸，使用stream提高性能\r\n* 使用crontab拆分日誌文件，使用readline分析日誌內容\r\n\r\n### 安全\r\n\r\n* sql注入：竊取資料庫內容\r\n* XSS攻擊：竊取cookie內容\r\n* 密碼加密：保障使用者資料安全\r\n\r\n#### sql注入\r\n\r\n* 最原始，最簡單的攻擊，從有了web2.0就有sql注入\r\n* 攻擊方式：輸入一個sql片段，最終拼成一段攻擊code\r\n* 預防措施：使用mysql escape函數處理輸入內容\r\n\r\n#### XSS攻擊\r\n*  前端最熟悉的攻擊方式，但後端更應該掌握\r\n* 攻擊方式：在頁面中參雜js code，以獲取網頁訊息\r\n* 預防措施：轉換產生js的特殊字符\r\n\r\n| 轉換前 | 轉換後 |\r\n| -- | -- | \r\n| \u0026 | \\\u0026amp; |\r\n| \u003c | \\\u0026lt; |\r\n| \u003e | \\\u0026gt; |\r\n| \" | \\\u0026quot; |\r\n| ' | \\\u0026#x27; |\r\n| / | \\\u0026#x2F; |\r\n\r\n#### 密碼加密\r\n\r\n* 萬一資料庫資料外洩，應把重要訊息加密\r\n* 攻擊方式：獲取帳號和密碼，再去登入其他系統\r\n* 預防措施：將密碼加密，即便拿到也不知道是明文\r\n* 範例\r\n`nodejs/nodejs/src/utils/crypto.js`\r\n```gherkin=\r\nconst crypto = require('crypto')\r\n\r\n//key\r\nconst SERECT_KEY = '2K3NFFdf49290548523sJIOfdFr455fd'\r\n\r\n//md5加密\r\nfunction md5(content) {\r\n    let md5 = crypto.createHash('md5')\r\n    return md5.update(content).digest('hex')\r\n}\r\n\r\nfunction getPassword(passwd) {\r\n    const str = `password=${passwd}\u0026key=${SERECT_KEY}`\r\n    return md5(str)\r\n}\r\n\r\nmodule.exports = {\r\n    getPassword,\r\n    md5\r\n}\r\n```\r\n\r\n### 總結\r\n\r\n* 如何預防sql注入\r\n* 如何預防xss攻擊\r\n* 如何加密密碼\r\n\r\n## express和koa2\r\n\r\n## 中間件和插件\r\n\r\n## 中間件原理\r\n\r\n## PM2介紹和配置\r\n\r\n## PM2多進程模型\r\n\r\n## Server運維\r\n\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpoabob%2Fnodejs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpoabob%2Fnodejs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpoabob%2Fnodejs/lists"}