Node - 從0基礎到實戰企業官網

jsliang發表於2018-12-23

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 - 從0基礎到實戰企業官網

一 目錄

不折騰的前端,和鹹魚有什麼區別

目錄
一 目錄
二 前言
三 基礎學習
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 程式管理
六 參考資料
七 線上部署
八 歸納總結

二 前言

返回目錄

 本文主要目的:

  1. 整合 Node 基礎,加深 jsliang 對 Node 的學習瞭解,並且方便日後複習。
  2. 整合 Node 工具,方便查詢在 Node 開發中,有哪些工具比較有利於開發。
  3. 給初學 Node 的小夥伴做一個參考,如有疑問還請在 QQ 群:798961601 中諮詢。

三 基礎

返回目錄

萬丈高樓平地起,地基還得自己起。

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,字符集是 utf8
  res.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

Node - 從0基礎到實戰企業官網

 OK,搞定完事,現在我們一一講解上面程式碼:

  1. 首先,我們需要先開啟仙人模式。哦,不是,是 HTTP 模式。我們都知道,像 PHP 這類老牌子的後端語言,需要 Apache 或者 Nginx 開啟 HTTP 服務。然而我們的 Node 不需要:
var http = require("http");
複製程式碼
  1. 然後,開啟 HTTP 服務,並設定開啟的埠:
/**
 * req 獲取 url 資訊 (request)
 * res 瀏覽器返回響應資訊 (response)
 */
http.createServer(function (req, res) {
  // ... 步驟 3 程式碼
}).listen(3000); // 監聽的埠
複製程式碼
  1. 接著,我們設定 HTTP 頭部,並往頁面列印值,最後結束響應:
// 設定 HTTP 頭部,狀態碼是 200,檔案型別是 html,字符集是 utf8
res.writeHead(200, {
  "Content-Type": "text/html;charset=UTF-8"
});

// 往頁面列印值
res.write('<h1 style="text-align:center">Hello NodeJS</h1>');

// 結束響應 
res.end();
複製程式碼
  1. 最後,我們往瀏覽器輸入 http://localhost:3000/,將訪問到我們開啟的 Node 服務,從而往頁面渲染頁面。

 至此,小夥伴們是不是也開啟了自己的 Node 之旅?

3.2 URL 模組

返回目錄

 URL 模組是什麼呢?
 我們在控制檯(終端)開啟 Node 模式,並列印出 url 來看一下:

Node - 從0基礎到實戰企業官網

 好傢伙,它有 UrlparseresolveresolveObjectformatURLURLSearchParamsdomainToASCIIdomainToUnicode 這麼多模組。
 那麼,這些模組都有什麼用呢?

 話不多說,先上程式碼:

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); // jsliang

    console.log(result.query.userAge); // 23
  }

  // 設定 HTTP 頭部,狀態碼是 200,檔案型別是 html,字符集是 utf8
  res.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,字符集是 utf8
  res.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,並通過它檢視 urlparse 模組怎麼用,輸出啥:

// 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); // jsliang

  console.log(result.query.userAge); // 23
}
複製程式碼

 從中,我們可以看出,我們可以通過 query,獲取到我們想要的路徑欄位。

 當然,上面只講解了 parse 的用法,我們可以將上面程式碼中 if 語句裡面的程式碼全部清空。然後,輸入下面的內容,去學習 url 模組更多的內容:

  1. url 模組所有內容:
console.log(url);

/**
 * Console:
 { 
   Url: [Function: Url],
    parse: [Function: urlParse], // 獲取地址資訊
    resolve: [Function: urlResolve], // 追加或者替換地址
    resolveObject: [Function: urlResolveObject],
    format: [Function: urlFormat], // 逆向 parse,根據地址資訊獲取原 url 資訊
    URL: [Function: URL],
    URLSearchParams: [Function: URLSearchParams],
    domainToASCII: [Function: domainToASCII],
    domainToUnicode: [Function: domainToUnicode] 
  }
 */
複製程式碼
  1. parse 如何使用
console.log(url.parse("http://www.baidu.com"));
/**
 * Console:
  Url {
    protocol: 'http:',
    slashes: true,
    auth: null,
    host: 'www.baidu.com',
    port: null,
    hostname: 'www.baidu.com',
    hash: null,
    search: null,
    query: null,
    pathname: '/',
    path: '/',
    href: 'http://www.baidu.com/' 
  }
 */
複製程式碼
  1. parse 帶引數:
console.log(url.parse("http://www.baidu.com/new?name=zhangsan"));

/**
 * Console:
  Url {
    protocol: 'http:',
    slashes: true,
    auth: null,
    host: 'www.baidu.com',
    port: null,
    hostname: 'www.baidu.com',
    hash: null,
    search: '?name=zhangsan',
    query: 'name=zhangsan',
    pathname: '/new',
    path: '/new?name=zhangsan',
    href: 'http://www.baidu.com/new?name=zhangsan' 
  }
 */
複製程式碼
  1. format 的使用:
console.log(url.format({
  protocol: 'http:',
  slashes: true,
  auth: null,
  host: 'www.baidu.com',
  port: null,
  hostname: 'www.baidu.com',
  hash: null,
  search: '?name=zhangsan',
  query: 'name=zhangsan',
  pathname: '/new',
  path: '/new?name=zhangsan',
  href: 'http://www.baidu.com/new?name=zhangsan' 
}))

// Console:
// http://www.baidu.com/new?name=zhangsan
複製程式碼
  1. resolve 的使用:
console.log(url.resolve("http://www.baidu.com/jsliang", "樑峻榮"));

// Console:
// http://www.baidu.com/樑峻榮
複製程式碼

 當然,url 這裡我們只講解了個入門,更多的還請看官網 API:url | Node.js v10.14.1 文件

3.3 CommonJS

返回目錄

  • 什麼是 CommonJS?

 CommonJS 就是為 JS 的表現來制定規範,因為 JS 沒有模組系統、標準庫較少、缺乏包管理工具,所以 CommonJS 應運而生,它希望 JS 可以在任何地方執行,而不只是在瀏覽器中,從而達到 Java、C#、PHP 這些後端語言具備開發大型應用的能力。

  • CommonJS 的應用?
  1. 伺服器端 JavaScript 應用程式。(Node.js)
  2. 命令列工具
  3. 桌面圖形介面應用程式。
  • CommonJS 與 Node.js 的關係?

 CommonJS 就是模組化的標準,Node.js 就是 CommonJS(模組化)的實現。

  • Node.js 中的模組化?
  1. 在 Node 中,模組分為兩類:一是 Node 提供的模組,稱為核心模組;二是使用者編寫的模組,成為檔案模組。核心模組在 Node 原始碼的編譯過程中,編譯進了二進位制執行檔案,所以它的載入速度是最快的,例如:HTTP 模組、URL 模組、FS 模組;檔案模組是在執行時動態載入的,需要完整的路勁分析、檔案定位、編譯執行過程等……所以它的速度相對核心模組來說會更慢一些。
  2. 我們可以將公共的功能抽離出一個單獨的 JS 檔案存放,然後在需要的情況下,通過 exports 或者 module.exports 將模組匯出,並通過 require 引入這些模組。

 現在,我們通過三種使用方式,來講解下 Node 中的模組化及 exports/require 的使用。

 我們先檢視下目錄:

Node - 從0基礎到實戰企業官網

方法一

 首先,我們新建 03_CommonJS.js03_tool-add.jsnode_modules/03_tool-multiply.jsnode_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.exportstools 進行了匯出。
 所以,我們在 03_CommonJS.js 可以通過 require 匯入使用:

var http = require("http");

var tools1 = require('./03_tool-add');

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:
   * 6
   * 6
   * 這裡要記得 Node 執行過程中,它請求了兩次,
   * http://localhost:3000/ 為一次,
   * http://localhost:3000/favicon.ico 為第二次
   */
  
  res.end();

}).listen(3000);
複製程式碼

 這樣,我們就完成了 exportsrequire 的初次使用。

方法二

 當我們模組檔案過多的時候,應該需要有個存放這些模組的目錄,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);
複製程式碼

 到此,我們就通過三種方法,瞭解了各種 exportsrequire 的姿勢以及 Node 模組化的概念啦~

 參考文獻:

3.4 包與 npm

返回目錄

 Node 中除了它自己提供的核心模組之外,還可以自定義模組,以及使用 第三方模組
 Node 中第三方模組由包組成,可以通過包來對一組具有相互依賴關係的模組進行統一管理。

Node - 從0基礎到實戰企業官網

 那麼,假如我們需要使用一些第三方模組,應該去哪找呢?

  1. 百度。百度查詢你需要安裝的第三方模組的對應內容。
  2. npm 官網。如果你已經知道包的名字或者包的作用。那麼,直接在 npm 官網上搜尋,想必會更快找到想要安裝的包。

 那麼,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

  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 刪除檔案

此章節檔案目錄:

Node - 從0基礎到實戰企業官網

首先,我們通過 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:檔案:true
    
    console.log(`目錄:${stats.isDirectory()}`); 
    // Console:目錄:false

    return 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,通通搞定,現在目錄變成了:

Node - 從0基礎到實戰企業官網

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 接收到:30
  str += 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 服務:

Node - 從0基礎到實戰企業官網

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.html
    fs.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 即可以看到:

Node - 從0基礎到實戰企業官網

 好傢伙,感情它就載入了整個 index.html 檔案,連 CSS 這些沒引入麼?
 所以,下一步,我們要動態載入 htmlcss 以及 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.html
    fs.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';
  }
}
複製程式碼

 這樣,當我們再次請求的時候,瀏覽器就變成了:

Node - 從0基礎到實戰企業官網

 當然,在上面,我們僅僅模擬了 htmlcssjs 這三種檔案型別而已,我們需要模擬更多的檔案型別:

08_ext.json

 程式碼詳情請點選上面的連結
複製程式碼

 在上面的 json 檔案中,我們定義了各種的檔案型別,此刻檔案目錄如下所示:

Node - 從0基礎到實戰企業官網

 這時候,我們需要修改下我們的 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.html
    fs.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 是非同步操作,所以需要使用 readFileSync
  let 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 萬使用者的連線。

 在這一章節中,主要解決:

  1. Node 的非阻塞 I/O 是什麼?
  2. Node events 模組是什麼?

 首先,在我們正常程式設計中,我們是希望程式能夠一行一行按照我們的意願編寫的:

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 驅動。
 那麼,我們有沒有辦法解決這個問題呢?
 有的!

  1. 通過回撥函式
  2. 通過 Node 的 events 模組

 首先,我們通過回撥函式來解決這個非同步問題:

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

返回目錄

Node - 從0基礎到實戰企業官網

 話不多說,先上程式碼:

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

然後,我們進行表的填充:

id name age
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。

 ———————華麗分割線———————

 當然,增刪改查是後端的基本操作,所以在這裡,我們可以補全基本的增刪改查功能。

 先看目錄:

Node - 從0基礎到實戰企業官網

  • 新增表欄位

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 啦~

 另外,由於 HTML 與 CSS 沒什麼好講的,所以本章節的前提靜態頁面 jsliang 已經寫好了,小夥伴們在學習前可以預先下載:

4.1 程式設計環境

返回目錄

首先,我們檢視下我們的前端基本程式碼:地址

Node - 從0基礎到實戰企業官網

 如上,我們僅需要了解 FrontEndCode 目錄以及 NodeWeb 目錄即可,其他目錄為上面章節練習參考。

然後,我們進行後端功能分析:

  1. 留言板。使用者點選 留言板 的時候,需要先判斷使用者是否登入。如果使用者尚未登入,則直接跳轉到 登入頁;如果使用者登入了,則顯示 留言板頁面

 在 留言板頁面 中,存在兩個介面:

  • 獲取留言內容:調取 getMessage 介面,返回全部留言資訊,由於預計資訊不多,故這裡不做分頁功能,有需要的小夥伴在實現完這個功能後,可以進行分頁介面的設計。
  • 提交留言內容:調取 sendMessage 介面,將使用者名稱、使用者 id、留言內容傳送給後端。

Node - 從0基礎到實戰企業官網

  1. 登入頁面 中,存在一個介面:
  • 登入:調取 login 介面,提交使用者填寫的姓名和密碼。

Node - 從0基礎到實戰企業官網

  1. 註冊頁面 中,存在一個介面:
  • 註冊:調取 register 介面,提交使用者填寫的姓名和密碼。

Node - 從0基礎到實戰企業官網

 由此,我們可以設計下前後端的介面結合:

介面文件

介面 型別 引數 返回資訊
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">密&nbsp;&nbsp;&nbsp;碼</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 控制檯檢視我們的介面是否正常:

Node - 從0基礎到實戰企業官網

 可以看到我們的介面能正常調通,這樣我們就可以連線資料庫,進行這 4 個介面的設計了。

如果小夥伴們覺得每次更新 Node 程式碼後,又要重啟一遍 node index.js 覺得麻煩,可以通過 supervisor 來監聽 Node 程式碼的改動,supervisor 的安裝使用:supervisor

4.3 註冊功能

返回目錄

 很好,我們回到仿企業網站的頁面上,準備編寫介面以及豐富 Node 的介面。

首先,我們開啟前端和 Node 服務:

  1. 開啟命令列/終端

  2. 開啟前端

  • cd FrontEndCode
  • live-server

安裝 live-servernpm i live-server -g

  1. 開啟後端
  • cd NodeWeb
  • supervisor index.js

安裝 supervisornpm 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 結束
  }
  // 註冊流程結束
}
複製程式碼

最後,我們在檢視下該功能是否成功:

Node - 從0基礎到實戰企業官網

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 - 註冊】");

}
複製程式碼

 很好,前端和後端都編寫完畢,是時候查驗下功能是否實現了:

Node - 從0基礎到實戰企業官網

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; // id
  let 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();
}
複製程式碼

 敲完程式碼再看下功能是否實現:

Node - 從0基礎到實戰企業官網

 綜上,我們完成了所有的功能模組:註冊、登入以及留言。

五 工具整合

返回目錄

工欲善其事,必先利其器。
 掌控好了工具,可以方便你更快地進行開發。

5.1 supervisor - 監聽 Node 改動

返回目錄

 正如其官網所說,它是一個進行控制系統:

  1. 安裝外掛:npm i supervisor -g
  2. 執行檔案:supervisor app.js
  3. 檢視執行:localhost:3000

 平時,我們 node app.js 後,當我們修改了 app.js 的內容,就需要關閉 node 命令列再執行 node app.js
 而我們使用 supervisor 後,我們修改了 app.js 中的內容,只要點選儲存,即可生效儲存後的程式碼,實現實時監聽 node 程式碼的變動。

 關於這個工具,網上更詳細的攻略有:

5.2 PM2 - Node 程式管理

返回目錄

 PM2 是 Node 程式管理工具,可以利用它來簡化很多 Node 應用管理的繁瑣任務,如效能監控、自動重啟、負載均衡等,而且使用非常簡單。

 下面就對 PM2 進行入門性的介紹,基本涵蓋了 PM2 的常用的功能和配置:

  1. 全域性安裝 PM2:npm i pm2 -g
  2. 監聽應用:pm2 start index.js
  3. 檢視所有程式:pm2 list
  4. 檢視某個程式:pm2 describe App name/id
  5. 停止某個程式:pm2 stop App name/id。例如:

先通過 pm2 list 檢視:

App name id status
index 0 online

 只需要執行 pm2 stop index 或者 pm2 stop 0 即可。

  1. 停止所有程式:pm2 stop all
  2. 重啟某個程式:pm2 restart App name/id
  3. 刪除某個程式:pm2 delete App name/id

 如上,如果說我們的 supervisor 是監聽單個程式的話,那麼 PM2 就是監聽多個程式。

 更多攻略:

六 參考資料

返回目錄

 在編寫這篇文章的過程中,有一些參考資料是值得保留閱讀的:

  1. 經典:該類值得我們研讀

經典,就是隨著時間流逝,它還是那麼有參考價值。

  1. 嘗試:該類值得我們參考借鑑

Node 基礎模組

Node 編寫介面

MySQL 學習

Node 連線資料庫

Node 仿 Express

七 線上部署

返回目錄

 關於線上部署及域名、伺服器相關的配置,jsliang 在另外一篇文章有所交代:雲伺服器建站

 如果小夥伴需要訂購雲伺服器來存放像 jsliang 個人網站類的靜態或者有 Node 後端的網頁,但卻不知道怎麼選擇,可以加 jsliang QQ:1741020489 諮詢,下面是一些優惠推廣:

騰訊雲推廣

 新使用者點這裡:

 購買雲伺服器:


阿里雲推廣

 新使用者點這裡:

 購買雲伺服器:

 購買企業級雲伺服器:

八 歸納總結

返回目錄

 綜上,搞定一切!
 興許在前面程式碼的摧殘下,能看到這裡的小夥伴已經寥寥無幾了,但我堅信我該交代的基本都交代了,不該交代的也交代了~
 所以,如果小夥伴看完真覺得不錯,那就點個贊或者給個 star 吧!你們的贊和 star 是我編寫更多更精彩文章的動力!GitHub 地址

 如果小夥伴看完這裡要評論的話,可以加個暗語:Node 基礎,***,這樣 jsliang 看到必回,哈哈~

  • Node 基礎,我完成了!
  • Node 基礎,我想說 jsliang 肯定還偷懶了,沒寫成最完美的,我不管我打賞了你趕緊給我完善下!
  • ……

so, that's all, thanks~

-----------------------

後記

撰文不易,如果文章對小夥伴有幫助,希望小夥伴們給勤勞敲程式碼、辛苦撰文的 jsliang 進行微信/支付寶打賞,你們的每一次打賞都是最好的鼓勵,謝謝~

Node - 從0基礎到實戰企業官網

Node - 從0基礎到實戰企業官網

知識共享許可協議
jsliang 的文件庫樑峻榮 採用 知識共享 署名-非商業性使用-相同方式共享 4.0 國際 許可協議進行許可。
基於github.om/LiangJunron…上的作品創作。
本許可協議授權之外的使用許可權可以從 creativecommons.org/licenses/by… 處獲得。

相關文章