Node.js 網頁 (Old)

v0.29.7 - by 晴☆

Slides.com

就是你現在正在看的簡報網站,按        吧

使用方法

  右下角的箭頭可以換頁,但簡報頁面的排列方式不是網格狀的,而是像很多串投影片,而它會記住使用者在每一串投影片的垂直位置(預設是最上方),所以先往下滑到底看完一個章節,再往右滑至另一個章節吧!

Title A
Title B
Title C
Title D
A1
B1
C1
D1
A2
C2

為什麼要用 

  • 線上方便共用
  • 編輯介面簡單
  • 支援數學嵌入
  • 支援程式嵌入
  • 很容易分章節
  • 看起來就很酷

小提示:

  • 也可以用鍵盤的方向鍵換頁

  • 按 [Esc] 可以看縮圖

  • 回到標題頁可以重置簡報順序

什麼是 Node.js

嗯 我知道它是一個綠色六角形

JavaScript

一種程式語言,通常用在網頁互動程式

  • 感覺就很隨便
  • 其實很好用
  • 大部分人覺得很毒瘤
  • 寫起來蠻漂亮的(至少我覺得

它不是 Java! 它不是 Java!

它不是 Java! 它不是 Java!

它不是 Java! 它不是 Java!

Node.js

可以單獨執行 JavaScript 的平台!

還可以裝模組包、分成很多檔案,很像在做專案

一般網頁:

執行 Node 程式:

> node ./MyFile.js
x: 10
y: █

實作一下

好像有個做漢堡的說法

開始實作之前

要確定 Node.js 有裝好欸

node -v

還有 NPM(Node Package Manager)

npm -v

然後記得開個終端機!

Hello World!

學程式肯定要從 Hello World 開始

console.log("Hello World!");
  • console 就是你的控制台
    可以用 .log() 輸出內容 .error() 報錯 .warn() 警告,都會換行
    甚至還有 .time() 等奇怪功能
  • "Hello World!" 就是個普通字串
    在 JavaScript 中單引號 ' 和雙引號 " 是等價的
  • 分號
    其實可以不要加,但我喜歡加一下,比較保險
node <your file name>

Hello, AaW!

AaW 是誰?

超強,一三學術長 🛐

那我們的主題是輸出 Hello, <名字>!
const NAME = "AaW";

console.log("Hello, " + NAME + "!");
  • const 宣告常數,不能改,相對地 var 則是宣告一般變數
    變數不用宣告型別!
  • 「+」可以組合字串

輸入……?

是不是有 console.input() 呢?
沒有!
npm install stdio
然後理論上就可以輸入了!

理論上……

但內建有 readline,超難用

這邊推薦 stdio 模組包

改一下 code

為了避免以後麻煩

先把 code 改成這樣:

function main() {
    const NAME = "AaW";
    console.log("Hello, " + NAME + "!");
}

main();

宣告 main() 然後呼叫它

  • function 宣告一個函數,不用標記回傳值型別

接著改成這樣,加上輸入:

const std = require("stdio");

function main() {
    const NAME = std.ask("Your name");
    console.log("Hello, " + NAME + "!");
}

main();
Your name: Hello, [object Promise]!

結果:

好像不太對耶
來不及輸入,就輸出了奇怪的東西

因為 std.ask() 是非同步的函數!

怎麼忽然就變難了?

漢堡

我剛才講了非同步

本來就不太好理解

何況只有一堆程式碼

所以我要舉個例子

比方說我們來做個漢堡 🍔

Overcooked...

做漢堡

切菜

煮肉

做漢堡

切菜

煮肉

做漢堡

切菜

煮肉

做漢堡

切菜

煮肉

等肉和菜做好才結束!

回來看 code

一個人做漢堡,同步的做法:

function cut(thing) {
    // 略
    return thing;
}

function fry(thing) {
    // 略
    return thing;
}

function makeHamburger(vegetable, meat, bread) {
    var cutVegetable = cut(vegetable);
    var friedMeat    = fry(meat);
    
    return bread + cutVegetable + friedMeat;
}

makeHamburger();

回來看 code

三個人做漢堡,非同步的做法:

async function cut(thing) {
    // 略
    return thing;
}

async function fry(thing) {
    // 略
    return thing;
}

async function makeHamburger(vegetable, meat, bread) {
    var cutVegetable = await cut(vegetable);
    var friedMeat    = await fry(meat);
    
    return bread + cutVegetable + friedMeat;
}

makeHamburger();

回來看 code

好像不太對!

這樣寫的話應該是:

    var cutVegetable = await cut(vegetable);
    var friedMeat    = await fry(meat);

(等待)

(等待)

煮肉

切菜

做漢堡

好像跟同步的一樣?

Promise
非同步的東西會回傳一個 Promise
……承諾?
回去看一下一開始的輸入:
const std = require("stdio");

function main() {
    const NAME = std.ask("Your name");
    console.log("Hello, " + NAME + "!");
}

main();
Your name: Hello, [object Promise]!

正解:

const std = require("stdio");

async function main() {
    const NAME = await std.ask("Your name");
    console.log("Hello, " + NAME + "!");
}

main();
Your name: Huey☆   
Hello, Huey☆!

結果:

(上面那行的 Huey☆ 是我輸入的)

Promise 帶有它的狀態,比方說:
當還沒完成處理時是 Promise { <pending> }
完成時可能是 Promise { 結果 }
若使用 await 等待它,就會直接回傳值
var a = myAsyncFunction()
// a 現在是 Promise { <pending> }

// 過了 10 秒以後
// a 是 Promise { 0 }
假設今天我有一個 10 秒後會回傳 0 的函數:
var b = await myAsyncFunction()
// 等待十秒後,b 是 0

同時做,一起等?

剛才那樣是先發派去切菜,等菜切完,再發派去煮肉

var cutVegetable = await cut(vegetable);
var friedMeat    = await fry(meat);
var promise1 = cut(vegetable);
var promise2 = fry(meat);

var cutVegetable = await promise1;
var friedMeat    = await promise2;

應該要兩邊都先法派去工作,然後等待兩個人做完

Promise
Value

同時做,一起等?

剛才那樣是先發派去切菜,等菜切完,再發派去煮肉

var cutVegetable = await cut(vegetable);
var friedMeat    = await fry(meat);
var promise1 = cut(vegetable);
var promise2 = fry(meat);

var cutVegetable = await promise1;
var friedMeat    = await promise2;

應該要兩邊都先法派去工作,然後等待兩個人做完

Value
Promise

FS

Friendly Smile

檔案系統

內建的 File System 可以處理本機檔案

右邊是我的資料夾結構,下圖是檔案內容:

然後就得到了下列這一串奇怪的東西:

<Buffer 5b 20 22 48 65 6c 6c 6f 22 2c 20 22 57 6f 72 6c 64 22 2c 20 22 41 70 70 6c 65 22 20 5d>
因為它是 Buffer, 不能顯示 加上 .toString() 方法:
const fs = require("node:fs");

async function main() {
    console.log(
        fs.readFileSync("./Data/Messages.json").toString()
    );
}

main();
[ "Hello", "World", "Apple" ]

但它還是字串!

我們需要 JSON 資料

加上 JSON.parse(...) 解譯:
const fs = require("node:fs");

async function main() {
    const arr = JSON.parse(
        fs.readFileSync("./Data/Messages.json")
    );
    console.log(arr[0]);
}

main();
Hello

Express

幾乎無關剛才講了半天的東西

檔案配置

我的檔案 Tree 如下:

C:.
│  Main.js
│  
└─Data
        Index.html
        Script.js
        Style.css
Tree /f
C:.
│  Main.js
│  README.md
│  RUN.sh
│
├─.vscode
│      launch.json
│      settings.json
│
├─Build
│  │  Client.js
│  │  ReloadCommands.js
│  │  SystemData.json
│  │  UpdateFileSystem.js
│  │
│  └─Commands
│          CustomResponse.js
│          Economy.js
│          Essentials.js
│          Games.js
│          Music.js
│          Quiz.js
│          Remineder.js
│          Scoreboard.js
│          Tools.js
│
├─ConstantListeners
│  │  Button.js
│  │  ClientReady.js
│  │  InteractionCommand.js
│  │  JoinGuild.js
│  │  MessageCommand.js
│  │  UpdateMember.js
│  │
│  └─Disabled
│          btn.old.js
│          general.template.js
│          itr_cmd.old.js
│          msg_cmd.old.js
│          normal_msg.old.js
│
├─Functions
│  ├─CustomResponse
│  │  ├─Buttons
│  │  │      List.js
│  │  │
│  │  ├─Commands
│  │  │      AddReact.js
│  │  │      AddReply.js
│  │  │      ForgetKeyword.js
│  │  │      ForgetSpecific.js
│  │  │      List.js
│  │  │
│  │  └─Handlers
│  │          Message.js
│  │
│  ├─EconomyBasic
│  │  └─Commands
│  │          Coins.js
│  │          Daily.js
│  │          Give.js
│  │
│  ├─Encryption
│  │  ├─Buttons
│  │  │      Publish.js
│  │  │
│  │  └─Commands
│  │          Decrypt.js
│  │          Encrypt.js
│  │
│  ├─ImageProcessing
│  │  │  MainHandler.js
│  │  │
│  │  └─Commands
│  │          Autocrop.js
│  │          Blur.js
│  │          Circle.js
│  │          Dither.js
│  │          Fisheye.js
│  │          Flip.js
│  │          Mirror.js
│  │          Resize.js
│  │          Rotate.js
│  │          Scale.js
│  │          Separate.js
│  │
│  ├─Mail
│  │  ├─Buttons
│  │  │      Check.js
│  │  │
│  │  └─Commands
│  ├─Music
│  │  └─Commands
│  │          Join.js
│  │          Leave.js
│  │          Pause.js
│  │          Play.js
│  │          Resume.js
│  │          Stop.js
│  │          Volume.js
│  │
│  ├─Reminder
│  │  └─Commands
│  │          Add.js
│  │
│  ├─RoleSystem
│  ├─Scoreboard
│  │  ├─Buttons
│  │  │      Add.js
│  │  │      Remove.js
│  │  │      Value.js
│  │  │
│  │  ├─Commands
│  │  │      Create.js
│  │  │
│  │  └─Tools
│  │          CircleEmoji.js
│  │
│  └─Various
│      ├─Buttons
│      │      ChooseAgain.js
│      │
│      └─Commands
│              Calculate.js
│              Choose.js
│              Impersonate.js
│              Ping.js
│
├─Hacks
│  │  General.js
│  │  Listener.js
│  │
│  ├─Commands
│  │      Logger.js
│  │      Transfer.js
│  │
│  ├─Data
│  │      Logger.json
│  │      Transfer.json
│  │
│  ├─Handlers
│  │      Logger.js
│  │      Transfer.js
│  │
│  └─Modules
│          Stamen.js
│
├─Modules
│      EmbedList.js
│      Logger.js
│      Pistil.js
│      Timer.js
│
└─ServersData
    ├─1014155633110556763
    │  │  Data.json
    │  │
    │  └─Users
    ├─1016628892997517322
    │  │  Data.json
    │  │
    │  └─Users
    ├─1074132253568938044
    │  │  Data.json
    │  │
    │  └─Users
    │          845923211127947274.json
    │          880102161881632869.json
    │
    ├─941613122358239273
    │  │  Data.json
    │  │
    │  └─Users
    │          1091618094339854406.json
    │          845923211127947274.json
    │          947651644219359273.json
    │
    ├─981914320415891536
    │  │  Data.json
    │  │
    │  └─Users
    │          1091618094339854406.json
    │          845923211127947274.json
    │
    └─Template
        │  Data.json
        │
        └─Users
                Template.json

Express

簡易的網頁伺服器架設工具

參見 Mdn Docs

npm i express
// Main.js

const PORT = 80;

const express = require("express"),
      fs      = require("node:fs");

var app = express();

app.get("/", async (req, res) => {
    res.write(fs.readFileSync("./Source/Index.html"));
    res.end();
});

app.post("/", async (req, res, next) => {
    res.write("<body>This is a blank page!</body>");
    res.end();
});
 
var server = app.listen(PORT, "0.0.0.0", async () => {
    console.log(`Starting web server at port ${PORT}`);
});

IP 與 Port

IP 就是區分每一台電腦在網路上的位置
而可以申請網域,由好記的文字名稱連接到難記的 IP 位置
注意 IP 只是一個標記,外網 IP 和內網 IP 等等都不同 
例如:127.0.0.1 或 localhost 可以連線到本機
Port 就是區分連線到這台主機是要幹什麼
比方說 80 和 443 是網站的預設,Minecraft 伺服器則是 25565
總共有 0 ~ 65535 不過有一些是保留,不可使用
可以用 my.cool.ip.address:port 指定 port

Request & Response

Get & Post

Get 我要看網頁
Post 我要傳資料

連線到 Google

→ Get Request

搜尋關鍵字

→ Get Request

輸入帳密,登入

→ Post Request

HTML

<!DOCTYPE html>
<html lang="zh-TW">
    <head>
        <!-- Bootstrap  -->
        <link 
            rel="stylesheet" 
            href="https://cdn.jsdelivr.net/npm/bootstrap@latest/dist/css/bootstrap.min.css"
        >
        <script 
            src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"
        ></script>
        
        <!-- jQuery -->
        <script 
            src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.4/jquery.min.js"
        ></script>

        <meta charset="utf-8">

        <title> Homepage - Huey☆ </title>

        <!-- Custom -->
        <link rel="stylesheet" href="./Style.css">
        <script src="./Script.js"></script>

    </head>
    <body class="bg-dark text-white">
        <div class="container-fluid py-3 bd-navbar bg-secondary sticky-top">
            <ul class="nav justify-content-end">
                <li class="nav-item">
                    <a class="nav-link text-white" href="#">One</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link text-white" href="#">Two</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link text-white" href="#">Three</a>
                </li>
            </ul>
        </div>

        <div class="container py-5 scrollbar scrollbar-primary">
            <div class="h2"> Test Page! </div>

            <p> So, here is my test page. What is this for? I don't know, either.</p>

            <form method="POST" action="/">
                <input class="btn btn-primary" type="submit" value="Send Request" />
            </form>
        </div>
    </body>
</html>

參見 Mdn Docs

補充知識

登入系統

登入,然後就沒了

檔案 Tree

用 FS 來儲存使用者的資料

這樣就算重新啟動資料也不會消失

C:.
│  Main.js
│
├─Data
│      Users.json
│
└─Source
    │  Index.html
    │
    └─Login
            Index.html

HTML

<!DOCTYPE html>
<html lang="zh-TW" class="w-100 h-100">
    <head>
        <!-- Bootstrap  -->
        <link 
            href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css" 
            rel="stylesheet" 
            integrity="sha384-KK94CHFLLe+nY2dmCWGMq91rCGa5gtU4mk92HdvYe+M/SXH301p5ILy+dN9+nJOZ" 
            crossorigin="anonymous"
        />
        <script 
            src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js" 
            integrity="sha384-ENjdO4Dr2bkBIFxQpeoTz1HIcje39Wm4jDKdf19U8gI4ddQ3GYNS7NTKfAdVQSZe" 
            crossorigin="anonymous"
        ></script>
        
        <!-- jQuery -->
        <script 
            src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.4/jquery.min.js"
        ></script>

        <meta charset="utf-8">

        <title> Login - Huey☆ </title>
    </head>
    <body data-bs-theme="dark" class="w-100 h-100 bg-dark text-white">
        <div class="w-100 h-100 p-0 m-0 d-flex flex-column">
            <div class="container-fluid py-3 bd-navbar bg-secondary sticky-top">
                <ul class="nav justify-content-end">
                    <li class="nav-item">
                        <a class="nav-link text-white" href="#">One</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link text-white" href="#">Two</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link text-white" href="#">Three</a>
                    </li>
                </ul>
            </div>

            <form class="container p-5 h-100 d-flex justify-content-center" method="POST" action="/account">
                <div class="login-box container p-0 bg-body-tertiary rounded d-flex flex-column" style="width: 40em;">
                    <div 
                        class="rounded-top w-100 p-4 bg-light-subtle d-flex" 
                        style="height: 5em;"
                    > 
                        <h3>Login</h3>
                    </div>
                    <div class="w-100 p-4 flex-grow-1 d-flex flex-column align-content-center">
                        <p>User ID:</p>
                        <input type="text" name="id" />  
                        <br />
                        <p>Password:</p>
                        <input type="password" name="password" />  
                    </div>
                    <div 
                        class="rounded-bottom w-100 p-4 bg-light-subtle d-flex justify-content-end" 
                        style="height: 5em;"
                    >
                        <input 
                            class="btn btn-secondary ms-2" 
                            type="button"
                            name="register"
                            value="Register" 
                        />
                        <input 
                            class="btn btn-primary ms-2" 
                            type="submit" 
                            name="login"
                            value="Login" 
                        />
                    </div>
                </div>
            </form>
        </div>
    </body>
</html>

HTML

Body

Form (POST)

Input...

Input...

處理 Request

app.get("/login", async (req, res, next) => {
    res.write(fs.readFileSync("./Source/Login/Index.html"));
    res.end();
});

app.post("/account", async (req, res, next) => {
    let body = "";

    req.on("data", function(chunk) {
      body += chunk.toString();
    });
  
    req.on("end", function() {
      console.log(body);
      res.send("Received POST request body: " + body);
    });
});

結果

長得很怪欸

Received POST request body: id=I+just+wanna+tell+you+how+I%27m+feeling...
https://www.google.com.tw/search?q=i+just+wanna+tell+you+how+i%27m+feeling

是百分號編碼 aka. 網址的格式 (url encoding)

翻譯一下……?

Body Parser

const bodyParser = require("body-parser");

// ...


app.use(bodyParser.urlencoded({ extended: false }));
app.post("/account", async (req, res, next) => {
    console.log(req.body);
});
npm i body-parser
[Object: null prototype] {
  name: "I just wanna tell you how I'm feeling..."
}

存到檔案中

目前我們會收到的資料:

Starting web server at port 80
[Object: null prototype] { id: '', password: '', register: 'Register' }
[Object: null prototype] { id: '', password: '', login: 'Login' }
[Object: null prototype] { id: '', password: '', register: 'Register' }
[Object: null prototype] { id: '', password: '', login: 'Login' }
if (req.body["register"]) {
    // ...
}
else if (req.body["login"]) {
    // ...
}

處理它:

const PORT = 80;

const express    = require("express"),
      bodyParser = require("body-parser"),
      fs         = require("node:fs"),
      path       = require("node:path");

var app = express();

app.use(bodyParser.urlencoded({ extended: false }));

app.get("/", async (req, res) => {
    res.write(fs.readFileSync("./Source/Index.html"));
    res.end();
});

app.post("/", async (req, res) => {
    res.send("This is a blank page!");
});

app.get("/login", async (req, res) => {
    res.write(fs.readFileSync("./Source/Login/Index.html"));
    res.end();
});

app.post("/account", async (req, res) => {
    var users = JSON.parse(fs.readFileSync("./Data/Users.json"));

    var { id, password } = req.body;

    if (!id) {
        res.send("Please enter your id!");
        return;
    }

    if (req.body["register"]) {
        if (users[id] !== undefined) {
            res.send("An account with the given ID already exists!");
            return;
        }
        users[id] = password;
        res.send(`Successfully registered as ${id}!`);
        return;
    }
    else if (req.body["login"]) {
        if (users[id] == password) {
            res.send(`Hello, ${id}! You have logged in!`);
            return;
        }
        else if (users[id]) {
            res.send("Wrong password!");
            return;
        }
        else {
            res.send("Account not found!");
            return;
        }
    }

    fs.writeFileSync("./Data/Users.json", JSON.stringify(users, null, 4));
});

var server = app.listen(PORT, "0.0.0.0", async () => {
    console.log(`Starting web server at port ${PORT}`);
});

檔案內容

{
    "username-1": "password-1",
    "star_huey": "960817"
}

不過這樣不太好

如果之後會加上其它資料的話

最好是寫成這樣:

{
    "star_huey": {
        "password": "960817",
        "height": 173,
        "weight": 47.5,
        "favorite-color": "orange"
    }
}

我們剛才儲存了使用者的資料

那我們來看看檔案內容

大概會長這樣:

其它功能

這個登入系統真的非常簡易

沒有規定密碼字元限制

沒辦法刪除帳號

也沒有「記住我」的功能

不過「記住我」的功能會用到 Cookies

像是這樣 →

下課

慘了 沒什麼內容

我是燒雞

對不起嘛

Node.js 筆記

By 晴☆

Node.js 筆記

  • 123