Node - 从0基础到实战企业官网
Create by jsliang on 2018-11-8 13:42:42
Recently revised in 2018-12-23 21:59:20
Hello 小伙伴們,如果覺得本文還不錯,記得點個贊或者給個 star,你們的贊和 star 是我編寫更多更精彩文章的動力!GitHub 地址
本文重點內容
- Node 基礎 - 通過對 Node 基礎的了解學習,打下 Node 基礎
- Node API - 開啟服務提供 API 給前端調用
- Node 連接 MySQL - 通過 npm 安裝 mysql,從而實現數據庫的鏈接
- Node 實戰 - 企業官網從 0 開始,打造能注冊、登錄以及留言的企業官網
- Node 部署 - 如何通過部署云服務器,讓小伙伴們可以查看到你的網站
本文延伸鏈接
- Node 部署項目、云服務器以及域名的使用:鏈接
- 本文 Node 基礎代碼下載地址:鏈接
- 本文 Node 成品代碼下載地址:鏈接
本文成品演示
- Node 項目演示:jsliang 前端有限公司
一 目錄
?不折騰的前端,和咸魚有什么區別
| 一 目錄 |
| 二 前言 |
| 三 基礎學習 |
| ?3.1 HTTP - 開始 Node 之旅 |
| ?3.2 URL 模塊 |
| ?3.3 CommonJS |
| ?3.4 包與 npm |
| ?3.5 fs 文件管理 |
| ?3.6 fs 案例 |
| ?3.7 fs 流 |
| ?3.8 創建 Web 服務器 |
| ?3.9 非阻塞 I/O 事件驅動 |
| ?3.10 get 與 post |
| ?3.11 Node 連接 MySQL |
| 四 Web 實戰 —— 企業官網 |
| ?4.1 編程環境 |
| ?4.2 后端接口 |
| ?4.3 注冊功能 |
| ?4.4 登錄功能 |
| ?4.5 留言功能 |
| 五 工具整合 |
| ?5.1 supervisor - 監聽 Node 改動 |
| ?5.2 PM2 - Node 進程管理 |
| 六 參考資料 |
| 七 線上部署 |
| 八 歸納總結 |
二 前言
返回目錄
?本文主要目的:
三 基礎
返回目錄
?萬丈高樓平地起,地基還得自己起。
3.1 HTTP - 開始 Node 之旅
返回目錄
?話不多說,先上代碼:
01_http.js
// 1. 引入 http 模塊 var http = require("http");// 2. 用 http 模塊創建服務 /*** req 獲取 url 信息 (request)* res 瀏覽器返回響應信息 (response)*/ http.createServer(function (req, res) {// 設置 HTTP 頭部,狀態碼是 200,文件類型是 html,字符集是 utf8res.writeHead(200, {"Content-Type": "text/html;charset=UTF-8"});// 往頁面打印值res.write('<h1 style="text-align:center">Hello NodeJS</h1>');// 結束響應res.end();}).listen(3000); // 監聽的端口 復制代碼?那么,上面代碼,我們要怎么用呢?
?首先,將上面的代碼復制粘貼到 01_http.js 中。
?然后,啟動 VS Code 終端:Ctrl + ~。
?接著,輸入 node 01_http.js 并回車。
?最后,打開 localhost:3000:
?OK,搞定完事,現在我們一一講解上面代碼:
?至此,小伙伴們是不是也開啟了自己的 Node 之旅?
3.2 URL 模塊
返回目錄
?URL 模塊是什么呢?
?我們在控制臺(終端)開啟 Node 模式,并打印出 url 來看一下:
?好家伙,它有 Url、parse、resolve、resolveObject、format、URL、URLSearchParams、domainToASCII、domainToUnicode 這么多模塊。
?那么,這些模塊都有什么用呢?
?話不多說,先上代碼:
02_url.js
// 1. 引入 url 模塊 var url = require("url");// 2. 引入 http 模塊 var http = require("http");// 3. 用 http 模塊創建服務 /*** req 獲取 url 信息 (request)* res 瀏覽器返回響應信息 (response)*/ http.createServer(function (req, res) {// 4. 獲取服務器請求/*** 訪問地址是:http://localhost:3000/?userName=jsliang&userAge=23* 如果你執行 console.log(req.url),它將執行兩次,分別返回下面的信息:* / ?userName=jsliang&userAge=23* / /favicon.ico* 這里為了防止重復執行,所以排除 req.url == /favicon.ico 的情況*/if(req.url != "/favicon.ico") {// 5. 使用 url 的 parse 方法/*** parse 方法需要兩個參數:* 第一個參數是地址* 第二個參數是 true 的話表示把 get 傳值轉換成對象*/ var result = url.parse(req.url, true);console.log(result);/*** Url {* protocol: null,* slashes: null,* auth: null,* host: null,* port: null,* hostname: null,* hash: null,* search: '?userName=jsliang&userAge=23',* query: { userName: 'jsliang', userAge: '23' },* pathname: '/',* path: '/?userName=jsliang&userAge=23',* href: '/?userName=jsliang&userAge=23' }*/console.log(result.query.userName); // jsliangconsole.log(result.query.userAge); // 23}// 設置 HTTP 頭部,狀態碼是 200,文件類型是 html,字符集是 utf8res.writeHead(200, {"Content-Type": "text/html;charset=UTF-8"});// 往頁面打印值res.write('<h1 style="text-align:center">Hello NodeJS</h1>');// 結束響應res.end();}).listen(3000); 復制代碼?在上面的代碼中:
?首先,我們引入該章節的主角 url 模塊:
// 1. 引入 url 模塊 var url = require("url"); 復制代碼?然后,我們引入 http 模塊:
// 2. 引入 http 模塊 var http = require("http"); 復制代碼?接著,我們創建 http 模塊,因為 url 的監聽,需要 http 模塊的開啟:
// 3. 用 http 模塊創建服務 /*** req 獲取 url 信息 (request)* res 瀏覽器返回響應信息 (response)*/ http.createServer(function (req, res) {// ... 第 4 步、第 5 步代碼// 設置 HTTP 頭部,狀態碼是 200,文件類型是 html,字符集是 utf8res.writeHead(200, {"Content-Type": "text/html;charset=UTF-8"});// 往頁面打印值res.write('<h1 style="text-align:center">Hello NodeJS</h1>');// 結束響應res.end(); }).listen(3000); 復制代碼?最后,我們訪問我們給出的地址:http://localhost:3000/?userName=jsliang&userAge=23,并通過它查看 url 的 parse 模塊怎么用,輸出啥:
// 4. 獲取服務器請求 /*** 訪問地址是:http://localhost:3000/?userName=jsliang&userAge=23* 如果你執行 console.log(req.url),它將執行兩次,分別返回下面的信息:* / ?userName=jsliang&userAge=23* / /favicon.ico* 這里為了防止重復執行,所以排除 req.url == /favicon.ico 的情況*/ if(req.url != "/favicon.ico") {// 5. 使用 url 的 parse 方法/*** parse 方法需要兩個參數:* 第一個參數是地址* 第二個參數是 true 的話表示把 get 傳值轉換成對象*/ var result = url.parse(req.url, true);console.log(result);/*** Url {* protocol: null,* slashes: null,* auth: null,* host: null,* port: null,* hostname: null,* hash: null,* search: '?userName=jsliang&userAge=23',* query: { userName: 'jsliang', userAge: '23' },* pathname: '/',* path: '/?userName=jsliang&userAge=23',* href: '/?userName=jsliang&userAge=23' }*/console.log(result.query.userName); // jsliangconsole.log(result.query.userAge); // 23 } 復制代碼?從中,我們可以看出,我們可以通過 query,獲取到我們想要的路徑字段。
?當然,上面只講解了 parse 的用法,我們可以將上面代碼中 if 語句里面的代碼全部清空。然后,輸入下面的內容,去學習 url 模塊更多的內容:
?當然,url 這里我們只講解了個入門,更多的還請看官網 API:url | Node.js v10.14.1 文檔
3.3 CommonJS
返回目錄
- 什么是 CommonJS?
?CommonJS 就是為 JS 的表現來制定規范,因為 JS 沒有模塊系統、標準庫較少、缺乏包管理工具,所以 CommonJS 應運而生,它希望 JS 可以在任何地方運行,而不只是在瀏覽器中,從而達到 Java、C#、PHP 這些后端語言具備開發大型應用的能力。
- CommonJS 的應用?
- CommonJS 與 Node.js 的關系?
?CommonJS 就是模塊化的標準,Node.js 就是 CommonJS(模塊化)的實現。
- Node.js 中的模塊化?
?現在,我們通過三種使用方式,來講解下 Node 中的模塊化及 exports/require 的使用。
?我們先查看下目錄:
?方法一:
?首先,我們新建 03_CommonJS.js、03_tool-add.js、node_modules/03_tool-multiply.js、node_modules/jsliang-module/tools.js 這 4 個文件/文件夾。
?其中 package.json 我們暫且不理會,稍后會講解它如何自動生成。
?在 03_tool-add.js 中:
03_tool-add.js
// 1. 假設我們文件其中有個工具模塊 var tools = {add: (...numbers) => {let sum = 0;for (let number in numbers) {sum += numbers[number];}return sum;} }/*** 2. 暴露模塊* exports.str = str;* module.exports = str;* 區別:* module.exports 是真正的接口* exports 是一個輔助工具* 如果 module.exports 為空,那么所有的 exports 收集到的屬性和方法,都賦值給了 module.exports* 如果 module.exports 具有任何屬性和方法,則 exports 會被忽略*/// exports 使用方法 // var str = "jsliang is very good!"; // exports.str = str; // { str: 'jsliang is very good!' }// module.exports 使用方法 module.exports = tools; 復制代碼?那么,上面的代碼有啥含義呢?
?第一步,我們定義了個工具庫 tools。
?第二步,我們通過 modules.exports 將 tools 進行了導出。
?所以,我們在 03_CommonJS.js 可以通過 require 導入使用:
?這樣,我們就完成了 exports 與 require 的初次使用。
?方法二:
?當我們模塊文件過多的時候,應該需要有個存放這些模塊的目錄,Node 就很靠譜,它規范我們可以將這些文件都放在 node_modules 目錄中(大家都放在這個目錄上,就不會有其他亂七八糟的命名了)。
?所以,我們在 node_modules 中新建一個 03_tool-multiply.js 文件,其內容如下:
03_tool-multiply.js
var tools = {multiply: (...numbers) => {let sum = numbers[0];for (let number in numbers) {sum = sum * numbers[number];}return sum;} }module.exports = tools; 復制代碼?在引用方面,我們只需要通過:
// 如果 Node 在當前目錄沒找到 tool.js 文件,則會去 node_modules 里面去查找 var tools2 = require('03_tool-multiply');console.log(tools2.multiply(1, 2, 3, 4)); 復制代碼?這樣,就可以成功導入 03_tool-multiply.js 文件了。
?方法三:
?如果全部單個文件丟在 node_modules 上,它會顯得雜亂無章,所以我們應該定義個自己的模塊:jsliang-module,然后將我們的 tools.js 存放在該目錄中:
jsliang-module/tools.js
var tools = {add: (...numbers) => {let sum = 0;for (let number in numbers) {sum += numbers[number];}return sum;},multiply: (...numbers) => {let sum = numbers[0];for (let number in numbers) {sum = sum * numbers[number];}return sum;} }module.exports = tools; 復制代碼?這樣,我們就定義好了自己的工具庫。
?但是,如果我們通過 var tools3 = require('jsliang-module'); 去導入,會發現它報 error 了,所以,我們應該在 jsliang-module 目錄下,通過下面命令行生成一個 package.json
PS E:\MyWeb\node_modules\jsliang-module> npm init --yes
?這樣,在 jsliang-module 中就有了 package.json。
?而我們在 03_CommonJS.js 就可以引用它了:
03_CommonJS.js
var http = require("http");var tools1 = require('./03_tool-add');// 如果 Node 在當前目錄沒找到 tool.js 文件,則會去 node_modules 里面去查找 var tools2 = require('03_tool-multiply');/*** 通過 package.json 來引用文件* 1. 通過在 jsliang-module 中 npm init --yes 來生成 package.json 文件* 2. package.json 文件中告訴了程序入口文件為 :"main": "tools.js",* 3. Node 通過 require 查找 jsliang-module,發現它有個 package.json* 4. Node 執行 tools.js 文件*/ var tools3 = require('jsliang-module');http.createServer(function (req, res) {res.writeHead(200, {"Content-Type": "text/html;charset=UTF-8"});res.write('<h1 style="text-align:center">Hello NodeJS</h1>');console.log(tools1.add(1, 2, 3));console.log(tools2.multiply(1, 2, 3, 4));console.log(tools3.add(4, 5, 6));/*** Console:* 6* 24* 15* 6* 24* 15* 這里要記得 Node 運行過程中,它請求了兩次,* http://localhost:3000/ 為一次,* http://localhost:3000/favicon.ico 為第二次*/res.end();}).listen(3000); 復制代碼?到此,我們就通過三種方法,了解了各種 exports 和 require 的姿勢以及 Node 模塊化的概念啦~
?參考文獻:
- CommonJS 規范 | 博客園 - Little Bird
- js模塊化編程之徹底弄懂CommonJS和AMD/CMD! | 博客園 - 方便以后復習
- [js高手之路] es6系列教程 - 不定參數與展開運算符(...) | 博客園 - ghostwu
3.4 包與 npm
返回目錄
?Node 中除了它自己提供的核心模塊之外,還可以自定義模塊,以及使用 第三方模塊。
?Node 中第三方模塊由包組成,可以通過包來對一組具有相互依賴關系的模塊進行統一管理。
?那么,假如我們需要使用一些第三方模塊,應該去哪找呢?
?那么,npm 是啥?
?npm 是世界上最大的開放源代碼的生態系統。我們可以通過 npm 下載各種各樣的包。
?在我們安裝 Node 的時候,它默認會順帶給你安裝 npm。
- npm -v:查看 npm 版本。
- npm list:查看當前目錄下都安裝了哪些 npm 包。
- npm info 模塊:查看該模塊的版本及內容。
- npm i 模塊@版本號:安裝該模塊的指定版本。
?在平時使用 npm 安裝包的過程中,你可能需要知道一些 npm 基本知識:
- i/install:安裝。使用 install 或者它的簡寫 i,都表明你想要下載這個包。
- uninstall:卸載。如果你發現這個模塊你已經不使用了,那么可以通過 uninstall 卸載它。
- g:全局安裝。表明這個包將安裝到你的計算機中,你可以在計算機任何一個位置使用它。
- --save/-S:通過該種方式安裝的包的名稱及版本號會出現在 package.json 中的 dependencies 中。dependencies 是需要發布在生成環境的。例如:ElementUI 是部署后還需要的,所以通過 -S 形式來安裝。
- --save-dev/-D:通過該種方式安裝的包的名稱及版本號會出現在 package.json 中的 devDependencies 中。devDependencies 只在開發環境使用。例如:gulp 只是用來壓縮代碼、打包的工具,程序運行時并不需要,所以通過 -D 形式來安裝。
?例子:
- cnpm i webpack-cli -D
- npm install element-ui -S
?那么,這么多的 npm 包,我們通過什么管理呢?
?答案是 package.json。
?如果我們需要創建 package.json,那么我們只需要在指定的包管理目錄(例如 node_modules)中通過以下命名進行生成:
- npm init:按步驟創建 package.json。
- npm init --yes:快速創建 package.json
?當然,因為國內網絡環境的原因,有些時候通過 npm 下載包,可能會很慢或者直接卡斷,這時候就要安裝淘寶的 npm 鏡像:cnpm
- npm install -g cnpm --registry=https://registry.npm.taobao.org
3.5 fs 文件管理
返回目錄
?本章節我們講解下 fs 文件管理:
如需快速找到下面某個內容,請使用 Ctrl + F
此章節文件目錄:
?首先,我們通過 fs.stat 檢查一個讀取的是文件還是目錄:
05_fs.js
// 1. fs.stat let fs = require('fs'); fs.stat('index.js', (error, stats) => {if(error) {console.log(error);return false;} else {console.log(stats);/*** Console:* Stats {* dev: 886875,* mode: 33206,* nlink: 1,* uid: 0,* gid: 0,* rdev: 0,* blksize: undefined,* ino: 844424931461390,* size: 284,* blocks: undefined,* atimeMs: 1542847157494,* mtimeMs: 1543887546361.2158,* ctimeMs: 1543887546361.2158,* birthtimeMs: 1542847157493.663,* atime: 2018-11-22T00:39:17.494Z,* mtime: 2018-12-04T01:39:06.361Z,* ctime: 2018-12-04T01:39:06.361Z,* birthtime: 2018-11-22T00:39:17.494Z }*/console.log(`文件:${stats.isFile()}`); // Console:文件:trueconsole.log(`目錄:${stats.isDirectory()}`); // Console:目錄:falsereturn false;} }) 復制代碼?通過 Console 打印出來的信息,我們基礎掌握了 fs.stat 的作用。
?然后,我們嘗試通過 fs.mkdir 創建目錄:
05_fs.js
// 2. fs.mkdir let fs = require('fs');/*** 接收參數* path - 將創建的目錄路徑* mode - 目錄權限(讀寫權限),默認 0777* callback - 回調,傳遞異常參數 err*/ fs.mkdir('css', (err) => {if(err) {console.log(err);return false;} else {console.log("創建目錄成功!");// Console:創建目錄成功!} }) 復制代碼?通過 node 05_fs.js,我們發現目錄中多了一個 css 文件夾。
?那么,有創建就有刪除,創建的目錄如何刪除呢?這里講解下 fs.rmdir:
05_fs.js
// 8. fs.rmdir let fs = require('fs');/*** 接收參數* path - 將創建的目錄路徑* mode - 目錄權限(讀寫權限),默認 0777* callback - 回調,傳遞異常參數 err*/ fs.rmdir('css', (err) => {if(err) {console.log(err);return false;} else {console.log("創建目錄成功!");// Console:創建目錄成功!} }) 復制代碼?通過 node 05_fs.js,我們發現目錄中的 css 文件夾被刪除了。
?接著,我們通過 fs.writeFile 來創建寫入文件:
05_fs.js
// 3. fs.writeFile let fs = require('fs');/*** filename (String) 文件名稱* data (String | Buffer) 將要寫入的內容,可以是字符串或者 buffer 數據。* · encoding (String) 可選。默認 'utf-8',當 data 是 buffer 時,該值應該為 ignored。* · mode (Number) 文件讀寫權限,默認 438。* · flag (String) 默認值 'w'。* callback { Function } 回調,傳遞一個異常參數 err。*/ fs.writeFile('index.js', 'Hello jsliang', (err) => {if(err) {console.log(err);return false;} else {console.log('寫入成功!');} }) 復制代碼?值得注意的是,這樣的寫入,是清空原文件中的所有數據,然后添加 Hello jsliang 這句話。即:存在即覆蓋,不存在即創建。
?有創建就有刪除,感興趣的可以使用 fs.unlink 進行文件的刪除,再次不做過多講解。
?既然,上面的是覆蓋文件,那么有沒有追加文件呢?有的,使用 fs.appendFile 吧:
05_fs.js
// 4. fs.appendFile let fs = require('fs');fs.appendFile('index.js', '這段文本是要追加的內容', (err) => {if(err) {console.log(err);return false;} else {console.log("追加成功");} }) 復制代碼?這樣,我們就成功往里面追加了一段話,從而使 index.js 變成了:
index.js
Hello jsliang這段文本是要追加的內容 復制代碼?在上面,我們已經做了:新增、修改、刪除操作。那么小伙伴一定很熟悉下一步驟是做什么了:
- fs.readFile 讀取文件
- fs.readdir 讀取目錄
05_fs.js
let fs = require('fs');// 5. fs.readFile fs.readFile('index.js', (err, data) => {if(err) {console.log(err);return false;} else {console.log("讀取文件成功!");console.log(data);// Console:// 讀取文件成功!// <Buffer 48 65 6c 6c 6f 20 6a 73 6c 69 61 6e 67 e8 bf 99 e6 ae b5 e6 96 87 e6 9c ac e6 98 af e8 a6 81 e8 bf bd e5 8a a0 e7 9a 84 e5 86 85 e5 ae b9>} })// 6. fs.readdir 讀取目錄 fs.readdir('node_modules', (err, data) => {if(err) {console.log(err);return false;} else {console.log("讀取目錄成功!");console.log(data);// Console:// 讀取目錄成功!// [ '03_tool-multiply.js', 'jsliang-module' ]} }) 復制代碼?如上,我們成功做到了讀取文件和讀取目錄。
?最后,我們再回顧一開始的目標:
1. fs.stat 檢測是文件還是目錄
2. fs.mkdir 創建目錄
3. fs.writeFile 創建寫入文件
4. fs.appendFile 追加文件
5. fs.readFile 讀取文件
6. fs.readdir 讀取目錄
7. fs.rename 重命名
8. fs.rmdir 刪除目錄
9. fs.unlink 刪除文件
?很好,我們就剩下重命名了:
05_fs.js
let fs = require('fs');// 7. fs.rename 重命名 fs.rename('index.js', 'jsliang.js', (err) => {if(err) {console.log(err);return false;} else {console.log("重命名成功!");} }) 復制代碼?當然,如果 fs.rename 還有更勁爆的功能:剪切
05_fs.js
let fs = require('fs');// 7. fs.rename 重命名 fs.rename('jsliang.js', 'node_modules/jsliang.js', (err) => {if(err) {console.log(err);return false;} else {console.log("剪切成功!");} }) 復制代碼?OK,通通搞定,現在目錄變成了:
3.6 fs 案例
返回目錄
?在上一章節中,我們了解了 fs 的文件管理。
?那么,在這里,我們嘗試使用 fs 做點小事情:
06_fsDemo.js
/*** 1. fs.stat 檢測是文件還是目錄* 2. fs.mkdir 創建目錄* 3. fs.writeFile 創建寫入文件* 4. fs.appendFile 追加文件* 5. fs.readFile 讀取文件* 6. fs.readdir 讀取目錄* 7. fs.rename 重命名* 8. fs.rmdir 刪除目錄* 9. fs.unlink 刪除文件*/// 1. 判斷服務器上面有沒有 upload 目錄,沒有就創建這個目錄 // 2. 找出 html 目錄下面的所有的目錄,然后打印出來let fs = require('fs');// 圖片上傳 fs.stat('upload', (err, stats) => {// 判斷有沒有 upload 目錄if(err) {// 如果沒有fs.mkdir('upload', (error) => {if(error) {console.log(error);return false;} else {console.log("創建 upload 目錄成功!");}})} else {// 如果有console.log(stats.isDirectory());console.log("有 upload 目錄,你可以做更多操作!");} })// 讀取目錄全部文件 fs.readdir('node_modules', (err, files) => {if(err) {console.log(err);return false;} else {// 判斷是目錄還是文件夾console.log(files);let filesArr = [];(function getFile(i) {// 循環結束if(i == files.length) {// 打印出所有目錄console.log("目錄:");console.log(filesArr);return false;}// 判斷目錄是文件還是文件夾fs.stat('node_modules/' + files[i], (error, stats) => {if(stats.isDirectory()) {filesArr.push(files[i]);}// 遞歸調用getFile(i+1);})})(0)} }) 復制代碼3.7 fs 流
返回目錄
?話不多說,我們了解下 fs 流及其讀取:
// 新建 fs const fs = require('fs'); // 流的方式讀取文件 let fileReadStream = fs.createReadStream('index.js'); // 讀取次數 let count = 0; // 保存數據 let str = ''; // 開始讀取 fileReadStream.on('data', (chunk) => {console.log(`${++count} 接收到:${chunk.length}`);// Console:1 接收到:30str += chunk; }) // 讀取完成 fileReadStream.on('end', () => {console.log("——結束——");console.log(count);console.log(str);// Console:——結束——// 1// console.log("Hello World!"); }) // 讀取失敗 fileReadStream.on('error', (error) => {console.log(error); }) 復制代碼?在這里,我們通過 fs 模塊的 createReadStream 創建了讀取流,然后讀取文件 index.js,從而最后在控制臺輸出了:
1 接收到:259 ——結束—— 1 console.log("盡信書,不如無書;盡看代碼,不如刪掉這些文件。"); console.log("盡信書,不如無書;盡看代碼,不如刪掉這些文件。"); console.log("盡信書,不如無書;盡看代碼,不如刪掉這些文件。"); 復制代碼?其中 console.log() 那三行就是 index.js 的文本內容。
?然后,我們試下流的存入:
let fs = require('fs'); let data = 'console.log("Hello World! 我要存入數據!")';// 創建一個可以寫入的流,寫入到文件 index.js 中 let writeStream = fs.createWriteStream('index.js'); // 開始寫入 writeStream.write(data, 'utf8'); // 寫入完成 writeStream.end(); writeStream.on('finish', () => {console.log('寫入完成!');// Console:寫入完成 }); 復制代碼?我們打開 index.js,會發現里面的內容變成了 console.log("Hello World! 我要存入數據!"),依次,我們通過流的形式進行了讀取和寫入的操作。
3.8 創建 Web 服務器
返回目錄
?在這里,我們利用 http 模塊、url 模塊、path 模塊、fs 模塊創建一個 Web 服務器。
?什么是 Web 服務器?
?Web 服務器一般指網站服務器,是指駐留于因特網上某種類型計算機的程序,可以像瀏覽器等 Web 客戶端提供文檔,也可以放置網站文件,讓全世界瀏覽;可以放置數據文件,讓全世界下載。目前最主流的三個 Web 服務器是 Apache、Nginx、IIS。
?下面,我們使用 Node 來創建一個 Web 服務:
08_WebService.js
// 引入 http 模塊 let http = require("http");// 引入 fs 模塊 let fs = require("fs");http.createServer((req, res) => {// 獲取響應路徑let pathName = req.url;// 默認加載路徑if (pathName == "/") {// 默認加載的首頁pathName = "index.html";}// 過濾 /favicon.ico 的請求if (pathName != "/favicon.ico") {// 獲取 08_WebService 下的 index.htmlfs.readFile("./08_WebService/" + pathName, (err, data) => {if (err) {// 如果不存在這個文件console.log("404 Not Found!");fs.readFile('./08_WebService/404.html', (errorNotFound, dataNotFound) => {if(errorNotFound) {console.log(errorNotFound);} else {res.writeHead(200, {"Content-Type": "text/html; charset='utf-8'"});// 讀取寫入文件res.write(dataNotFound);// 結束響應res.end();}})return;} else {// 返回這個文件// 設置請求頭res.writeHead(200, {"Content-Type": "text/html; charset='utf-8'"});// 讀取寫入文件res.write(data);// 結束響應res.end();}});} }).listen(8080); 復制代碼?這樣,我們在瀏覽器輸入 localhost:8080 即可以看到:
?好家伙,感情它就加載了整個 index.html 文件,連 CSS 這些沒引入么?
?所以,下一步,我們要動態加載 html、css 以及 js:
08_WebService.js
// 引入 http 模塊 let http = require("http");// 引入 fs 模塊 let fs = require("fs");// 引入 url 模塊 let url = require("url");// 引入 path 模塊 let path = require("path");http.createServer((req, res) => {// 獲取響應路徑let pathName = url.parse(req.url).pathname;// 默認加載路徑if (pathName == "/") {// 默認加載的首頁pathName = "index.html";}// 獲取文件的后綴名let extName = path.extname(pathName);// 過濾 /favicon.ico 的請求if (pathName != "/favicon.ico") {// 獲取 08_WebService 下的 index.htmlfs.readFile("./08_WebService/" + pathName, (err, data) => {// 如果不存在這個文件if (err) {console.log("404 Not Found!");fs.readFile("./08_WebService/404.html",(errorNotFound, dataNotFound) => {if (errorNotFound) {console.log(errorNotFound);} else {res.writeHead(200, {"Content-Type": "text/html; charset='utf-8'"});// 讀取寫入文件res.write(dataNotFound);// 結束響應res.end();}});return;}// 返回這個文件else {// 獲取文件類型let ext = getExt(extName);// 設置請求頭res.writeHead(200, {"Content-Type": ext + "; charset='utf-8'"});// 讀取寫入文件res.write(data);// 結束響應res.end();}});} }).listen(8080);// 獲取后綴名 getExt = (extName) => {switch(extName) {case '.html': return 'text/html';case '.css': return 'text/css';case '.js': return 'text/js';default: return 'text/html';} } 復制代碼?這樣,當我們再次請求的時候,瀏覽器就變成了:
?當然,在上面,我們僅僅模擬了 html、css、js 這三種文件類型而已,我們需要模擬更多的文件類型:
08_ext.json
代碼詳情請點擊上面的鏈接 復制代碼?在上面的 json 文件中,我們定義了各種的文件類型,此刻文件目錄如下所示:
?這時候,我們需要修改下我們的 js 文件,讓它適應多種請求響應了:
08_WebService.js
// 引入 http 模塊 let http = require("http");// 引入 fs 模塊 let fs = require("fs");// 引入 url 模塊 let url = require("url");// 引入 path 模塊 let path = require("path");http.createServer((req, res) => {// 獲取響應路徑let pathName = url.parse(req.url).pathname;// 默認加載路徑if (pathName == "/") {// 默認加載的首頁pathName = "index.html";}// 獲取文件的后綴名let extName = path.extname(pathName);// 過濾 /favicon.ico 的請求if (pathName != "/favicon.ico") {// 獲取 08_WebService 下的 index.htmlfs.readFile("./08_WebService/" + pathName, (err, data) => {// 如果不存在這個文件if (err) {console.log("404 Not Found!");fs.readFile("./08_WebService/404.html",(errorNotFound, dataNotFound) => {if (errorNotFound) {console.log(errorNotFound);} else {res.writeHead(200, {"Content-Type": "text/html; charset='utf-8'"});// 讀取寫入文件res.write(dataNotFound);// 結束響應res.end();}});return;}// 返回這個文件else {// 獲取文件類型let ext = getExt(extName);console.log(ext);// 設置請求頭res.writeHead(200, {"Content-Type": ext + "; charset='utf-8'"});// 讀取寫入文件res.write(data);// 結束響應res.end();}});} }).listen(8080);// 獲取后綴名 getExt = (extName) => {// readFile 是異步操作,所以需要使用 readFileSynclet data = fs.readFileSync('./08_ext.json');let ext = JSON.parse(data.toString());return ext[extName]; } 復制代碼?如此,我們做了個簡單的 Web 服務器。
3.9 非阻塞 I/O 事件驅動
返回目錄
?Java、PHP 或者 .NET 等服務端語言,會為每一個客戶端的連接創建一個新的線程。
?Node 不會為每一個客戶連接創建一個新的線程,而僅僅使用一個線程。
?當有用戶連接了,就會觸發一個內部事件,通過非租塞 I/O、事件驅動機制,讓 Node 程序宏觀上也是并行的。
?使用 Node,一個 8GB 內存的服務器,可以同時處理超過 4 萬用戶的連接。
?在這一章節中,主要解決:
?首先,在我們正常編程中,我們是希望程序能夠一行一行按照我們的意愿編寫的:
09_io.js
console.log("1");console.log("2");console.log("3");/*** Console:* 1* 2* 3*/ 復制代碼?但是,事與愿違。
?我們有時候,會執行一些異步方法(函數):
09_io.js
console.log("1");// console.log("2"); let fs = require('fs'); getExt = () => {fs.readFile('08_ext.json', (err, data) => {console.log("2");}) } getExt();console.log("3");/*** Console:* 1* 3* 2*/ 復制代碼?在上面代碼中,由于 fs.readFile 是 Node 的異步函數。所以,程序先執行了 1 和 3,最后才執行 fs.readFile 的 2 部分。
在這里,可以看出 Node 不會因為一段代碼的邏輯錯誤,從而導致其他代碼無法運行。
?這樣子,就導致了一個問題:步驟 3 可能拿不到步驟 2 的執行結果了!這就是 Node 的非租塞性 I/O 驅動。
?那么,我們有沒有辦法解決這個問題呢?
?有的!
?首先,我們通過回調函數來解決這個異步問題:
09_io.js
let fs = require("fs");getExt = (callback) => {fs.readFile('08_ext.json', (err, data) => {callback(data);}) }getExt( (result) => {console.log(result.toString()); }) 復制代碼?通過回調,我們可以將 getExt 的數據提取出來。
?然后,我們通過 Node 的 events 模塊來解決這個異步問題:
// 引入 fs 模塊 let fs = require("fs");/*** Node 事件循環:* 1. Node 是單進程單線程應用程序,但是通過事件和回調支持并發,所以性能非常高。* 2. Node 的每一個 API 都是異步的,并作為一個獨立線程運行,使用異步函數調用,并處理并發。* 3. Node 有多個內置的事件,我們可以通過引入 events 模塊,并通過實例化 EventEmitter 類來綁定和監聽事件。*/// 引入 events 模塊 let events = require("events"); // 實例化事件對象 let EventEmitter = new events.EventEmitter();getExt = () => {fs.readFile('08_ext.json', (err, data) => {// 將 data 廣播出去EventEmitter.emit('data', data.toString());}) };getExt();// 監聽 data EventEmitter.on('data', (ext) => {console.log(ext); }); 復制代碼?在這里,EventEmitter.on 通過監聽 data 的形式,獲取了 getExt 內部的執行結果。
?如此,我們就了解了 Node 的 I/O 事件及 events 模塊
3.10 get 與 post
返回目錄
?話不多說,先上代碼:
index.js
// 加載 http 模塊 var http = require('http');// 虛擬 SQL 讀取出來的數據 var items = [];// 創建 http 服務 http.createServer(function (req, res) {// 設置跨域的域名,* 代表允許任意域名跨域res.setHeader('Access-Control-Allow-Origin', '*');// 設置 header 類型res.setHeader('Access-Control-Allow-Headers', 'Content-Type');// 跨域允許的請求方式res.setHeader('Content-Type', 'application/json');// 判斷請求switch (req.method) {// post 請求時,瀏覽器會先發一次 options 請求,如果請求通過,則繼續發送正式的 post 請求case 'OPTIONS':res.statusCode = 200;res.end();break;// 如果是 get 請求,則直接返回 items 數組case 'GET':let data = JSON.stringify(items);res.write(data);res.end();break;// 如果是 post 請求case 'POST':let item = '';// 讀取每次發送的數據req.on('data', function (chunk) {item += chunk;});// 數據發送完成req.on('end', function () {// 存入item = JSON.parse(item);items.push(item.item);// 將數據返回到客戶端let data = JSON.stringify(items);res.write(data);res.end();});break;} }).listen(3000)console.log('http server is start...'); 復制代碼?首先,我們加載了 http 模塊,并創建了服務。
?然后,我們設置了跨域的處理方式,允許進行跨域。
?接著,我們進行了請求的判斷處理,由于只做簡單演練,故只判斷是 get 請求還是 post 請求。
?最后,我們將請求的結果返回給客戶端。
?在上面,我們進行了后端 Node 的部署,那么前端頁面要怎么做呢?
index.html
<!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no"><meta http-equiv="X-?-Compatible" content="ie=edge"><title>Node Web</title></head><body><div id="app"><h1>Todo List</h1><ul><li v-for="(item, index) in items" :key="index">{{ item }}</li></ul><input type="text" v-model="item"><button @click="postApi">添加</button></div><!-- cdn 引用:Vue 和 Node --><script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script><script src="https://unpkg.com/axios/dist/axios.min.js"></script><script>new Vue({el: document.getElementById('app'),data: function () {return {items: [],item: '',}},created() {// 進入頁面請求數據axios.get('http://localhost:3000/').then(res => {console.log("\n【API - get 數據】");console.log(res);this.items = res.data;}).catch(function (err) {console.log(err)})},methods: {// 點擊按鈕提交數據postApi() {axios.post('http://localhost:3000/', {item: this.item}).then(res => {console.log("\n【API - post 數據】")console.log(res);this.items = res.data;}).catch(function (err) {console.log(err)})}}})</script> </body></html> 復制代碼?我們通過 Vue 進行了布局,通過 Axios 進行了接口的請求。從而完成了對數據的操作。
3.11 Node 連接 MySQL
返回目錄
關于 MySQL 的安裝,可以查看 jsliang 寫的:MySQL 安裝及圖形化工具
?首先,我們通過可視化工具進行表的設計:
| id | int | 11 | 主鍵 |
| name | varchar | 255 | |
| age | varchar | 255 |
?然后,我們進行表的填充:
| 1 | jslliang | 23 |
| 2 | 梁峻榮 | 23 |
?接著,我們安裝 Node 連接 MySQL 的包:
npm i mysql -D 復制代碼?再來,我們編寫 Node 的 index.js:
index.js
var mysql = require('mysql'); var connection = mysql.createConnection({host: 'localhost',user: 'root',password: '123456',database: 'node' });connection.connect();connection.query('SELECT * FROM user', function (error, results, fields) {if (error) throw error;console.log(results); });connection.end(); 復制代碼?最后,我們通過 node index.js,打開該服務:
[ RowDataPacket { id: 1, name: 'jsliang', age: '23' },RowDataPacket { id: 2, name: '梁峻榮', age: '23' } ] 復制代碼?如此,我們便完成了 Node 連接 MySQL。
?———————華麗分割線———————
?當然,增刪改查是后端的基本操作,所以在這里,我們可以補全基本的增刪改查功能。
?先看目錄:
- 新增表字段
add.js
var mysql = require('mysql'); var connection = mysql.createConnection({host: 'localhost',user: 'root',password: '123456',database: 'node' });connection.connect();let addSql = "INSERT INTO user(id,name,age) VALUES(0,?,?)"; let addSqlParams = ["jsliang", "23"];connection.query(addSql, addSqlParams, function (err, res) {if (err) {console.log("新增錯誤:");console.log(err);return;} else {console.log("新增成功:");console.log(res);} });connection.end(); 復制代碼?我們只需要直接 node add.js,就能往數據庫中新增數據了。
- 刪除表字段
delete.js
// 連接 MySQL var mysql = require('mysql'); // MySQL 的連接信息 var connection = mysql.createConnection({host: 'localhost',user: 'root',password: '123456',database: 'node' });// 開始連接 connection.connect();// 新增的 SQL 語句及新增的字段信息 var delSql = 'DELETE FROM user where id = 2';// 連接 SQL 并實施語句 connection.query(delSql, function (err, res) {if (err) {console.log("刪除錯誤:");console.log(err);return;} else {console.log("刪除成功:");console.log(res);} });// 終止連接 connection.end(); 復制代碼- 修改表字段
update.js
// 連接 MySQL var mysql = require('mysql'); // MySQL 的連接信息 var connection = mysql.createConnection({host: 'localhost',user: 'root',password: '123456',database: 'node' });// 開始連接 connection.connect();// 新增的 SQL 語句及新增的字段信息 let updateSql = "UPDATE user SET name = ?,age = ? WHERE Id = ?"; let updateSqlParams = ["LiangJunrong", "23", 1];// 連接 SQL 并實施語句 connection.query(updateSql, updateSqlParams, function (err, res) {if (err) {console.log("修改錯誤:");console.log(err);return;} else {console.log("修改成功:");console.log(res);} });// 終止連接 connection.end(); 復制代碼- 查詢表字段
read.js
// 連接 MySQL var mysql = require('mysql'); // MySQL 的連接信息 var connection = mysql.createConnection({host: 'localhost',user: 'root',password: '123456',database: 'node' });// 開始連接 connection.connect();// 新增的 SQL 語句及新增的字段信息 let readSql = "SELECT * FROM user";// 連接 SQL 并實施語句 connection.query(readSql, function (err, res) {if (err) throw err;console.log(res); });// 終止連接 connection.end(); 復制代碼?以上,我們打通了 Node 與 MySQL 的壁壘,實現了數據的增刪改查。
四 Web 實戰 —— 企業官網
返回目錄
?在進行代碼實戰的時候,我們很多時候會遇到一些小事兒,例如:logo 制作、ico 制作、icon 挑選等……
?下面這些都是 jsliang 平時碰到的,小伙伴有需要的可以 mark 啦~
- logo 制作
- ico 制作
- icon 挑選
?另外,由于 HTML 與 CSS 沒什么好講的,所以本章節的前提靜態頁面 jsliang 已經寫好了,小伙伴們在學習前可以預先下載:
- 本文靜態頁面代碼地址
4.1 編程環境
返回目錄
?首先,我們查看下我們的前端基本代碼:地址
?如上,我們僅需要了解 FrontEndCode 目錄以及 NodeWeb 目錄即可,其他目錄為上面章節練習參考。
?然后,我們進行后端功能分析:
?在 留言板頁面 中,存在兩個接口:
- 獲取留言內容:調取 getMessage 接口,返回全部留言信息,由于預計信息不多,故這里不做分頁功能,有需要的小伙伴在實現完這個功能后,可以進行分頁接口的設計。
- 提交留言內容:調取 sendMessage 接口,將用戶名、用戶 id、留言內容發送給后端。
- 登錄:調取 login 接口,提交用戶填寫的姓名和密碼。
- 注冊:調取 register 接口,提交用戶填寫的姓名和密碼。
?由此,我們可以設計下前后端的接口結合:
接口文檔
| getMessage:獲取留言信息 | get | 無參 | n 條記錄:id(用戶 id)、user_name(用戶名)、user_message(用戶留言內容)、time(留言時間) |
| sendMessage:提交留言信息 | post | id(用戶 id)、user_name(用戶名)、user_message(用戶留言內容) | status 狀態 |
| login:登錄 | post | id(用戶 id)、user_name(用戶名)、user_password(用戶密碼) | status 狀態 |
| register:注冊 | post | id(用戶 id)、user_name(用戶名)、user_password(用戶密碼) | status 狀態 |
?最后,我們進行 MySQL 數據庫的表設計:
user 表
| id | int | 11 | 主鍵 |
| user_name | varchar | 255 | |
| user_password | varchar | 255 | |
| time | datetime |
message 表
| id | int | 11 | 主鍵 |
| user_message | varchar | 255 | |
| user_id | varchar | 255 | 外鍵 |
| user_name | varchar | 255 | |
| time | datetime |
4.2 后端接口
返回目錄
?在我們進行實操之前,先確認我們是否能寫接口,所以我們可以新建一個 test 文件夾,里面放一個 index.html 以及一個 index.js 來測試一下。
- text- index.html- index.js 復制代碼?首先,我們就 4.1 提到的接口,提前進行后端接口的設置:
index.js
// 連接 MySQL:先安裝 npm i mysql -D var mysql = require('mysql'); // MySQL 的連接信息 var connection = mysql.createConnection({host: 'localhost',user: 'root',password: '123456',database: 'nodebase' }); // 開始連接 connection.connect();// 引入 http 模塊:http 是提供 Web 服務的基礎 const http = require("http");// 引入 url 模塊:url 是對用戶提交的路徑進行解析 const url = require("url");// 引入 qs 模塊:qs 是對路徑進行 json 化或者將 json 轉換為 string 路徑 const qs = require("querystring");// 用 http 模塊創建服務 /*** req 獲取 url 信息 (request)* res 瀏覽器返回響應信息 (response)*/ http.createServer(function (req, res) {// 設置 cors 跨域res.setHeader("Access-Control-Allow-Origin", "*");// 設置 header 類型res.setHeader('Access-Control-Allow-Headers', 'Content-Type');// 跨域允許的請求方式res.setHeader('Content-Type', 'application/json');if (req.method == "POST") { // 接口 POST 形式console.log("\n【POST 形式】");// 獲取前端發來的路由地址let pathName = req.url;console.log("\n接口為:" + pathName);// 接收發送過來的參數let tempResult = "";// 數據接入中req.addListener("data", function (chunk) {tempResult += chunk;});// 數據接收完成req.addListener("end", function () {var result = JSON.stringify(qs.parse(tempResult));console.log("\n參數為:");console.log(result);if (pathName == "/sendMessage") { // 提交留言信息console.log("\n【API - 提交留言信息】");} else if (pathName == "/login") { // 登錄console.log("\n【API - 登錄】");} else if (pathName == "/register") { // 注冊console.log("\n【API - 注冊】");}// 接口信息處理完畢})// 數據接收完畢} else if (req.method == "GET") { // 接口 GET 形式console.log("\n【GET 形式】");// 解析 url 接口let pathName = url.parse(req.url).pathname;console.log("\n接口為:" + pathName);if (pathName == "/getMessage") { // 獲取留言信息console.log("\n【API - 獲取留言信息】");} else if(pathName == "/") { // 首頁res.writeHead(200, {"Content-Type": "text/html;charset=UTF-8"});res.write('<h1 style="text-align:center">jsliang 前端有限公司服務已開啟!</h1><h2 style="text-align:center">詳情可見:<a href="https://github.com/LiangJunrong/document-library/blob/master/other-library/Node/NodeBase.md" target="_blank">Node 基礎</a></h2>');res.end();}}}).listen(8888); // 監聽的端口// 獲取當前時間 function getNowFormatDate() {var date = new Date();var year = date.getFullYear(); // 年var month = date.getMonth() + 1; // 月var strDate = date.getDate(); // 日var hour = date.getHours(); // 時var minute = date.getMinutes(); // 分var second = date.getMinutes(); // 秒if (month >= 1 && month <= 9) {month = "0" + month;}if (strDate >= 0 && strDate <= 9) {strDate = "0" + strDate;}// 返回 yyyy-mm-dd hh:mm:ss 形式var currentdate = year + "-" + month + "-" + strDate + " " + hour + ":" + minute + ":" + second;return currentdate; } 復制代碼?通過判斷 req.method 屬于 GET 還是 POST 形式,從而確定加載的接口:
- 在 POST 中,判斷是屬于 提交留言信息、登錄 還是 注冊;
- 在 GET 中,判斷是不是 獲取留言信息。
?同時,我們在其中定義了 MySQL 的連接以及一個 getNowFormatDate 用來獲取當前時間,格式為:2018-12-21 10:03:59
?然后,我們通過一個前端頁面來演示我們的接口是否能使用:
index.html
<!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>演示代碼</title> </head><body><div><label for="user">用戶名</label><input type="text" id="user"></div><div><label for="password">密 碼</label><input type="password" id="password"></div><div><button id="register">注冊</button></div><script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script><script>$(function () {// 測試 get 接口$.ajax({url: "http://localhost:8888/getMessage",type: "POST",data: {username: "jsliang"},success: function (res) {console.log(res);},error: function (err) {console.log(err);}})$("#register").click(function () {// 測試 post 接口$.ajax({url: "http://localhost:8888/login",type: "POST",data: {username: $("#user").val(),password: $("#password").val()},success: function (res) {console.log(res);},error: function (err) {console.log(err);}})})});</script> </body></html> 復制代碼?最后,我們通過 node index.js,并打開 index.html,通過 F12 控制臺查看我們的接口是否正常:
?可以看到我們的接口能正常調通,這樣我們就可以連接數據庫,進行這 4 個接口的設計了。
如果小伙伴們覺得每次更新 Node 代碼后,又要重啟一遍 node index.js 覺得麻煩,可以通過 supervisor 來監聽 Node 代碼的改動,supervisor 的安裝使用:supervisor
4.3 注冊功能
返回目錄
?很好,我們回到仿企業網站的頁面上,準備編寫接口以及豐富 Node 的接口。
?首先,我們開啟前端和 Node 服務:
打開命令行/終端
開啟前端
- cd FrontEndCode
- live-server
安裝 live-server:npm i live-server -g
- cd NodeWeb
- supervisor index.js
安裝 supervisor:npm i supervisor -g
?然后,我們在注冊頁面通過點擊事件來觸發調接口:
register.html
<!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8"><meta http-equiv="keywords" content="前端,jsliang,bootstrap,企業建站"><meta http-equiv="description" content="jsliang 為你打造最好的企業服務"><link rel="shortcut icon" href="./images/favicon.ico" type="image/x-icon" /><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>注冊-jsliang 前端有限公司</title><link rel="stylesheet" href="./css/index.css"><link rel="stylesheet" href="./css/bootstrap.min.css"> </head><body><!-- 省略 body 中代碼,有需要的請前往第四章開頭下載查看全部代碼 --><script src="./js/jquery-3.3.1.min.js"></script><script src="./js/bootstrap.min.js"></script><script src="./js/islogin.js"></script><script>$(function () {$("#register-submit").click(function () {let userName = $("#userName").val();let userPassword = $("#userPassword").val();if (!userName) {alert("請輸入用戶名");$("#userName").focus();} else if (!userPassword) {alert("請輸入密碼");$("#userPassword").focus();} else if (userName.length > 10) {alert("請輸入少于 10 位的用戶名");$("#userName").focus();} else if (userPassword.length > 20) {alert("請輸入少于 20 位的密碼");$("#userPassword").focus();} else {// 如果用戶輸入的沒毛病,那就加載接口$.ajax({url: "http://localhost:8888/register",type: 'post',dataType: 'json',data: {username: userName,password: userPassword},success: function (res) {console.log(res);if (res.code == "0") {alert("注冊成功,前往登錄!");window.location.href = "./login.html";}},error: function (err) {console.log(err.responseText);if (err.responseText == "注冊失敗,姓名重復!") {alert("用戶名已被注冊!");} else if (err.responseText == "注冊失敗,名額已滿!") {alert("注冊失敗,名額已滿!");} else if (err.responseText == "注冊失敗,密碼為空!") {alert("注冊失敗,密碼為空!");} else if (err.responseText == "注冊失敗,姓名過長!") {alert("注冊失敗,姓名過長!");} else if (err.responseText == "注冊失敗,密碼過長!") {alert("注冊失敗,密碼過長!");} else {alert("未知錯誤!");}}})}})})</script> </body></html> 復制代碼?如此,我們在用戶點擊 注冊 按鈕的時候,進行接口的調用,發送數據到了后端,如果成功了,那就彈窗,并跳轉到登錄頁;如果沒成功,就彈窗提示。
?接著,我們編寫 Node,前端調用接口后,Node 判斷這兩個參數是否為空,如果不為空,則將數據存儲到數據庫。
index.js
// ... 其他代碼省略,請自行前往章節 4.2 后端接口 獲取其他代碼if (pathName == "/sendMessage") { // 提交留言信息console.log("\n【API - 提交留言信息】");} else if (pathName == "/login") { // 登錄console.log("\n【API - 登錄】");} else if (pathName == "/register") { // 注冊console.log("\n【API - 注冊】");result = JSON.parse(result);let username = result.username; // 用戶名let password = result.password; // 密碼let time = getNowFormatDate(); // 時間if (!username) { // 用戶名為空res.end("注冊失敗,用戶名為空。");return;} else if (!password) { // 密碼為空res.end("注冊失敗,密碼為空!");return;} else if(username.length > 10) { // 姓名過長res.end("注冊失敗,姓名過長!");return;} else if(password.length > 20) { // 密碼過長res.end("注冊失敗,密碼過長!");return;} else {// 查詢 user 表// 使用 Promise 的原因是因為中間調用了兩次數據庫,而數據庫查詢是異步的,所以需要用 Promise。new Promise( (resolve, reject) => {// 新增的 SQL 語句及新增的字段信息let readSql = "SELECT * FROM user";// 連接 SQL 并實施語句connection.query(readSql, function (error1, response1) {if (error1) { // 如果 SQL 語句錯誤throw error1;} else {console.log("\nSQL 查詢結果:");// 將結果先去掉 RowDataPacket,再轉換為 json 對象let newRes = JSON.parse(JSON.stringify(response1));console.log(newRes);// 判斷姓名重復與否let userNameRepeat = false;for(let item in newRes) {if(newRes[item].user_name == username) {userNameRepeat = true;}}// 如果姓名重復if(userNameRepeat) {res.end("注冊失敗,姓名重復!");return;} else if(newRes.length > 300) { // 如果注冊名額已滿res.end("注冊失敗,名額已滿!");return;} else { // 可以注冊resolve();}}});}).then( () => {console.log("\n第二步:");// 新增的 SQL 語句及新增的字段信息let addSql = "INSERT INTO user(user_name,user_password, time) VALUES(?,?,?)";let addSqlParams = [result.username, result.password, time];// 連接 SQL 并實施語句connection.query(addSql, addSqlParams, function (error2, response2) {if (error2) { // 如果 SQL 語句錯誤console.log("新增錯誤:");console.log(error2);return;} else {console.log("\nSQL 查詢結果:");console.log(response2);console.log("\n注冊成功!");// 返回數據res.write(JSON.stringify({code: "0",message: "注冊成功!"}));// 結束響應res.end();}});})// Promise 結束}// 注冊流程結束 } 復制代碼?最后,我們在查看下該功能是否成功:
4.4 登錄功能
返回目錄
?在上面,我們完成了注冊功能,那么相對來說,登錄功能就容易通了,因為查詢部分我們已經試過了一次。
login.html
<!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8"><meta http-equiv="keywords" content="前端,jsliang,bootstrap,企業建站"><meta http-equiv="description" content="jsliang 為你打造最好的企業服務"><link rel="shortcut icon" href="./images/favicon.ico" type="image/x-icon" /><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>登錄-jsliang 前端有限公司</title><link rel="stylesheet" href="./css/index.css"><link rel="stylesheet" href="./css/bootstrap.min.css"> </head><body><!-- 代碼省略,有需要的小伙伴請在第四章前言部分下載代碼 --><script src="./js/jquery-3.3.1.min.js"></script><script src="./js/bootstrap.min.js"></script><script src="./js/islogin.js"></script><script>$(function () {$("#login-submit").click(function () {let userName = $("#userName").val(); // 用戶名let userPassword = $("#userPassword").val(); // 密碼if (!userName) {alert("請輸入用戶名");$("#userName").focus();} else if (!userPassword) {alert("請輸入密碼");$("#userPassword").focus();} else if (userName.length > 10) {alert("請輸入少于 10 位的用戶名");$("#userName").focus();} else if (userPassword.length > 20) {alert("請輸入少于 20 位的密碼");$("#userPassword").focus();} else {$.ajax({url: "http://localhost:8888/login",type: 'post',dataType: 'json',data: {username: userName,password: userPassword},success: function (res) {console.log(res);if (res.code == "0") {sessionStorage.setItem("id", res.data.id);sessionStorage.setItem("userName", res.data.userName);alert("登錄成功!");window.location.href = "./messageBoard.html";} else if (res.code == "1") {alert("登錄失敗,密碼錯誤!");}},error: function (err) {console.log(err.responseText);if (err.responseText == "不存在該用戶!") {alert("不存在該用戶!");} else if (err.responseText == "登錄失敗,用戶名為空!") {alert("登錄失敗,用戶名為空!");} else if (err.responseText == "登錄失敗,密碼為空!") {alert("登錄失敗,密碼為空!");} else if (err.responseText == "登錄失敗,姓名過長!") {alert("登錄失敗,姓名過長!");} else if (err.responseText == "登錄失敗,密碼過長!") {alert("登錄失敗,密碼過長!");} else {alert("未知錯誤!");}}})}})})</script> </body></html> 復制代碼?編寫完前端的代碼后,我們進行 Node 代碼的編輯:
index.js
// ... 其他代碼省略,請自行前往章節 4.2 后端接口 獲取其他代碼if (pathName == "/sendMessage") { // 提交留言信息console.log("\n【API - 提交留言信息】");} else if (pathName == "/login") { // 登錄console.log("\n【API - 登錄】");result = JSON.parse(result);let username = result.username; // 用戶名let password = result.password; // 密碼if (!username) { // 用戶名為空res.end("登錄失敗,用戶名為空!");return;} else if (!password) { // 密碼為空res.end("登錄失敗,密碼為空!");return;} else if(username.length > 10) {res.end("登錄失敗,姓名過長!");return;} else if(password.length > 20) {res.end("登錄失敗,密碼過長!");return;} else { // 新增的 SQL 語句及新增的字段信息let readSql = "SELECT * FROM user WHERE user_name = '" + username + "'";// 連接 SQL 并實施語句connection.query(readSql, function (error1, response1) {if (error1) {throw error1;} else {if(response1 == undefined || response1.length == 0) { // 不存在用戶res.end("\n不存在該用戶!");return;} else { // 存在用戶console.log("\n存在該用戶!");let newRes = JSON.parse(JSON.stringify(response1));console.log(newRes);if(newRes[0].user_password == password) { // 密碼正確// 返回數據res.write(JSON.stringify({code: "0",message: "登錄成功!",data: {id: newRes[0].id,userName: newRes[0].user_name}}));res.end();} else { // 密碼錯誤// 返回數據res.write(JSON.stringify({code: "1",message: "登錄失敗,密碼錯誤!"}));res.end();}// 判斷密碼正確與否完畢}// 存在用戶處理結束}});}// 登錄步驟結束 } else if (pathName == "/register") { // 注冊console.log("\n【API - 注冊】");} 復制代碼?很好,前端和后端都編寫完畢,是時候查驗下功能是否實現了:
4.5 留言功能
返回目錄
?現在,我們就剩下留言功能了,一鼓作氣做好它吧!
messageBoard.html
<!-- 留言板 --> <!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8"><meta http-equiv="keywords" content="前端,jsliang,bootstrap,企業建站"><meta http-equiv="description" content="jsliang 為你打造最好的企業服務"><link rel="shortcut icon" href="./images/favicon.ico" type="image/x-icon" /><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>留言板-jsliang 前端有限公司</title><link rel="stylesheet" href="./css/index.css"><link rel="stylesheet" href="./css/bootstrap.min.css"> </head><body><!-- 代碼省略,基礎代碼請前往本章節前言下載 --><script src="./js/jquery-3.3.1.min.js"></script><script src="./js/bootstrap.min.js"></script><script src="./js/islogin.js"></script><script>$(function() {let userName = sessionStorage.getItem("userName");let userId = sessionStorage.getItem("id");// 查詢留言板if(userName && userId) { // 如果有存儲$.ajax({url: "http://localhost:8888/getMessage",type: 'get',dataType: 'json',success: function (res) {console.log(res);let li = ``;for(let item in res.data) {li = li + `<li><span class="text-warning font-bold">☆ </span><span class="user-message">${res.data[item].user_message}</span><span>—— </span><span class="user-name">${res.data[item].user_name} [${res.data[item].user_id}]</span><span class="message-time">${res.data[item].time}</span></li>`;}$("#message-board-ul").append(li);},error: function (err) {console.log(err);}})} else { // 如果沒有存儲window.location.href = "../login.html";}// 提交留言$("#message-submit").click(function() {let messageText = $("#message").val()if(!messageText) {alert("留言內容不能為空");} else if(messageText.length > 140) {alert("留言長度不能超過 140 位!");} else {$.ajax({url: "http://localhost:8888/sendMessage",type: 'post',dataType: 'json',data: {userid: userId,username: userName,message: messageText},success: function (res) {console.log(res);if(res.code == "0") {alert("新增成功!");window.location.reload();}},error: function (err) {console.log(err);console.log(err.responseText);if (err.responseText == "登錄失敗,留言內容為空!") {alert("登錄失敗,留言內容為空!");} else if (err.responseText == "登錄失敗,字數超過限制!") {alert("登錄失敗,字數超過限制!");} else {alert("未知錯誤!");}}})}})})</script> </body></html> 復制代碼?接著編寫下 Node 后端:
index.js
// ... 其他代碼省略,請自行前往章節 4.2 后端接口 獲取其他代碼if (pathName == "/sendMessage") { // 提交留言信息console.log("\n【API - 提交留言信息】");result = JSON.parse(result);let id = result.userid; // idlet userName = result.username; // 用戶名let messageText = result.message; // 留言內容let time = getNowFormatDate(); // 時間if(!messageText) {res.end("登錄失敗,留言內容為空!");return;} else if(messageText.length > 140) {res.end("登錄失敗,字數超過限制!");return;} else {// 新增的 SQL 語句及新增的字段信息let addSql = "INSERT INTO message(user_message, user_id, user_name, time) VALUES(?, ?, ?, ?)";let addSqlParams = [messageText, id, userName, time];// 連接 SQL 并實施語句connection.query(addSql, addSqlParams, function (error1, response1) {if (error1) { // 如果 SQL 語句錯誤throw error1;} else {console.log("\n新增成功!");// 返回數據res.write(JSON.stringify({code: "0",message: "新增成功!"}));// 結束響應res.end();}})}} else if (pathName == "/login") { // 登錄console.log("\n【API - 登錄】");} else if (pathName == "/register") { // 注冊console.log("\n【API - 注冊】");}// ... 其他代碼省略,請自行前往章節 4.2 后端接口 獲取其他代碼if (pathName == "/getMessage") { // 獲取留言信息console.log("\n【API - 獲取留言信息】");// 解析 url 參數部分let params = url.parse(req.url, true).query;console.log("\n參數為:");console.log(params);// 新增的 SQL 語句及新增的字段信息let readSql = "SELECT * FROM message";// 連接 SQL 并實施語句connection.query(readSql, function (error1, response1) {if (error1) {throw error1; } else {let newRes = JSON.parse(JSON.stringify(response1));console.log(newRes);// 返回數據res.write(JSON.stringify({code: "1",message: "查詢成功!",data: newRes}));// 結束響應res.end();}});// 查詢完畢 } else if(pathName == "/") { // 首頁res.writeHead(200, {"Content-Type": "text/html;charset=UTF-8"});res.write('<h1 style="text-align:center">jsliang 前端有限公司服務已開啟!</h1><h2 style="text-align:center">詳情可見:<a href="https://github.com/LiangJunrong/document-library/blob/master/other-library/Node/NodeBase.md" target="_blank">Node 基礎</a></h2>');res.end(); } 復制代碼?敲完代碼再看下功能是否實現:
?綜上,我們完成了所有的功能模塊:注冊、登錄以及留言。
五 工具整合
返回目錄
?工欲善其事,必先利其器。
?掌控好了工具,可以方便你更快地進行開發。
5.1 supervisor - 監聽 Node 改動
返回目錄
- supervisor 官網
?正如其官網所說,它是一個進行控制系統:
?平時,我們 node app.js 后,當我們修改了 app.js 的內容,就需要關閉 node 命令行再執行 node app.js。
?而我們使用 supervisor 后,我們修改了 app.js 中的內容,只要點擊保存,即可生效保存后的代碼,實現實時監聽 node 代碼的變動。
?關于這個工具,網上更詳細的攻略有:
- 詳細版:用Supervisor守護你的Node.js進程 | 簡書 - Mike的讀書季
5.2 PM2 - Node 進程管理
返回目錄
- PM2 - npm
?PM2 是 Node 進程管理工具,可以利用它來簡化很多 Node 應用管理的繁瑣任務,如性能監控、自動重啟、負載均衡等,而且使用非常簡單。
?下面就對 PM2 進行入門性的介紹,基本涵蓋了 PM2 的常用的功能和配置:
先通過 pm2 list 查看:
| index | 0 | online |
?只需要執行 pm2 stop index 或者 pm2 stop 0 即可。
?如上,如果說我們的 supervisor 是監聽單個進程的話,那么 PM2 就是監聽多個進程。
?更多攻略:
- PM2 官網
- PM2 用法簡介 | 簡書 - LeavesLife
- PM2實用入門指南 | 博客園 - 程序猿小卡
六 參考資料
返回目錄
?在編寫這篇文章的過程中,有一些參考資料是值得保留閱讀的:
經典,就是隨著時間流逝,它還是那么有參考價值。
- API 文檔 | Node.js 中文網
- Node.js 教程 | 菜鳥教程
- Express 文檔 | Express 中文網
Node 基礎模塊
- nodejs之querystring模塊 | 博客園 - whiteMu
Node 編寫接口
- 用Node編寫RESTful API接口 | php 中文網 - 不言
MySQL 學習
- MySQL 教程 | 菜鳥教程
Node 連接數據庫
- node.js前后臺交互示例 -- 使用node.js實現用戶注冊功能 | 博客園 - 返回主頁 黨興明
- node.js實現簡單的登錄注冊頁面 - 博客園 - 返回主頁 bestjarvan
Node 仿 Express
- nodejs模塊:簡單http請求路由,仿express | CSDN - TTUZ
- 初學nodejs一:別被Express的API搞暈了 | 前端亂燉 - 飛天小黑神豬
- NodeJs 實戰——原生 NodeJS 輕仿 Express 框架從需求到實現(一) | 倔強的石頭 - 掘金
- NodeJs 實戰——原生 NodeJS 輕仿 Express 框架從需求到實現(二) | 倔強的石頭 - 掘金
- 仿 Express | Github - wallaceyuan
- Node.js 封裝仿照 express 的路由 | CSDN - c.
- 學習node中express框架中間件的相關知識及實踐 | Github - BadWaka
七 線上部署
返回目錄
?關于線上部署及域名、服務器相關的配置,jsliang 在另外一篇文章有所交代:云服務器建站。
?如果小伙伴需要訂購云服務器來存放像 jsliang 個人網站類的靜態或者有 Node 后端的網頁,但卻不知道怎么選擇,可以加 jsliang QQ:1741020489 咨詢,下面是一些優惠推廣:
?騰訊云推廣:
?新用戶點這里:
- 新客戶無門檻 2775 元代金券
?購買云服務器:
- 12 月優惠低至 168 元/年
?阿里云推廣:
?新用戶點這里:
- 新用戶云產品 1888 通用代金券
?購買云服務器:
- 高性能云服務器 - 低至 293元/年
?購買企業級云服務器:
- 企業級高性能云服務器
八 歸納總結
返回目錄
?綜上,搞定一切!
?興許在前面代碼的摧殘下,能看到這里的小伙伴已經寥寥無幾了,但我堅信我該交代的基本都交代了,不該交代的也交代了~
?所以,如果小伙伴看完真覺得不錯,那就點個贊或者給個 star 吧!你們的贊和 star 是我編寫更多更精彩文章的動力!GitHub 地址
?如果小伙伴看完這里要評論的話,可以加個暗語:Node 基礎,***,這樣 jsliang 看到必回,哈哈~
- Node 基礎,我完成了!
- Node 基礎,我想說 jsliang 肯定還偷懶了,沒寫成最完美的,我不管我打賞了你趕緊給我完善下!
- ……
?so, that's all, thanks~
-----------------------
后記
撰文不易,如果文章對小伙伴有幫助,希望小伙伴們給勤勞敲代碼、辛苦撰文的 jsliang 進行微信/支付寶打賞,你們的每一次打賞都是最好的鼓勵,謝謝~
jsliang 的文檔庫 由 梁峻榮 采用 知識共享 署名-非商業性使用-相同方式共享 4.0 國際 許可協議進行許可。
基于github.om/LiangJunron…上的作品創作。
本許可協議授權之外的使用權限可以從 creativecommons.org/licenses/by… 處獲得。
總結
以上是生活随笔為你收集整理的Node - 从0基础到实战企业官网的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 苹果手机测距离_iPhone 12 Pr
- 下一篇: 为什么程序员是吃青春饭的但还是这么多想当