node.js express koa 學習筆記

node.js 課程

第二章 node.js 介紹

2.1 下載和安裝

nvm(Node.js version manager) 在github下載

幾個指令

+   nvm list
+   nvm install v10.1.0
+   nvm use --delete-prefix 切換版本

2.2 nodejs 和 JavaScript 區(qū)別

ECMAScript  是語法基礎 定義了語法規(guī)范

JavaScript 是  ECMAScript + web API

nodejs 是 ECMAScript + nodeAPI

2.3 commonjs 演示

實現(xiàn)引入導出
module.exports = function add(){}
const add = require("path")
1. 建立兩個文件通過b文件引入a文件中的函數(shù) 實現(xiàn) 求和
2. 導出多個函數(shù) 分別求和 和 求乘(利用解構的語法)
3. 引入npm 包
安裝lodash
1. npm init -y //npm 初始化
2. npm i lodash
3. 引入 lodash
4. 合并數(shù)組 concat([1,2],3)

2.4 debugger

const http=require('http')
const server = http.createServer((req,res)=>{
    res.writeHead(200,{"contetnt-type":"text/html"})
    res.end("<h1>Hello World</h1>")
})
server.listen(3000,()=>{
    console.log("listening on 3000 prot")
})

第三章 項目介紹

3.1 項目需求分析

一笼痛、目標

開發(fā)一個博客系統(tǒng)鹏氧,具有博客的基本功能

只開發(fā)server端锨推,不關心前端

二、需求 
  1. 首頁,作者主頁赔嚎,博客詳情頁
  2. 登錄頁
  3. 管理中心皮假,新建頁,編輯頁

3.2 技術方案

1衩椒、數(shù)據(jù)如何存儲

2蚌父、如何與前端對接,接口設計

第四章 開發(fā)博客項目接口

4.1 http 概述

  1. 從輸入url到顯示頁面的整個過程

    1. DNS解析,建立TCP鏈接毛萌,發(fā)送http請求
    2. server接收到請求 苟弛,處理, 返回
    3. 客戶端接收到返回數(shù)據(jù)阁将,處理數(shù)據(jù)(如執(zhí)行頁面渲染膏秫,執(zhí)行js)

    幾個單詞

    1. request 請求

    2. response 相應

4.2 nodejs 處理get請求

const http = require("http");
const querystring = require("querystring");

const server = http.createServer((req, res) => {
    console.log("method", req.method);
    const url = req.url;
    console.log("url", req.url);
    req.query = querystring.parse(url.split("?")[1]);
    console.log("req.query", req.query);
    res.end(JSON.stringify(req.query));
});

server.listen(8000, () => {
    console.log("OK");
});

4.3 nodejs處理post請求

  1. 安裝postman 發(fā)送數(shù)據(jù)請求
const http = require("http");

const server = http.createServer((req, res) => {
    console.log("獲取到請求");
    if (req.method === "POST") {
        console.log("log content-type:", req.headers["content-type"]);
        let dataString = "";
        req.on("data", (chunk) => {
            dataString += chunk.toString();
        });
        req.on("end", () => {
            console.log("dataString:", dataString);
            res.end("hello world");
        });
    }
});

server.listen(3000, () => {
    console.log("ok");
});

4.4 處理http 請求綜合實例

const http = require("http");
const querystring = require("querystring");

const server = http.createServer((req, res) => {
    const method = req.method;
    const url = req.url;
    const path = url.split("?")[0];
    const query = querystring.parse(url.split("?")[1]);

    res.setHeader("Content-type", "application/json");

    const resData = {
        method,
        url,
        path,
        query,
    };

    if (method === "GET") {
        res.end(JSON.stringify(resData));
    }

    if (method === "POST") {
        let postData = "";
        req.on("data", (chunk) => {
            postData += chunk.toString();
        });
        req.on("end", () => {
            resData.postData = JSON.parse(postData);
            res.end(JSON.stringify(resData));
        });
    }
});

server.listen(3000, () => {
    console.log("ok");
});

4.5 搭建開發(fā)環(huán)境

  1. 新建項目路徑 并搭建基礎代碼

    • 項目目錄

      |-- blog-1
        |-- bin
            |-- www.js  // 項目入口 服務器創(chuàng)建
        |-- res
            |-- router // 處理路由
                |-- blog.js 
                |-- user.js
            |-- model   // 定義數(shù)據(jù)格式
                |-- resModel.js
            |-- controller // 業(yè)務邏輯
                |-- blog.js
        |-- app.js  // 服務器配置等
        |-- package.json
      
    • www.js

      const http = require("http");
      const PORT = 3000;
      const serverHandle = require("../app");
      
      const server = http.createServer(serverHandle);
      
      server.listen(PORT, () => {
          console.log("node server ok");
      });
      
      
      
    • app.js

      const serverHandle = (req, res) => {
          res.setHeader("Content-type", "application/json");
      
          const resData = {
              name: "黑兔",
              age: "23",
          };
      
          res.end(JSON.stringify(resData));
      };
      
      module.exports = serverHandle;
      
      
  1. 使用nodemon 監(jiān)聽文件變化,自動重啟node

  2. 使用 cross-env 設置環(huán)境變量做盅,兼容 mac linux 和 windows

    1. 安裝依賴 npm install nodemon cross-env --save-dev
    2. 配置啟動命令 
    "dev": "cross-env NODE_ENV=dev nodemon ./bin/www.js"
    3. 使用配置的node_env 
    process.env.NODE_ENV
    
  1. 單詞

    • Handle

4.6 初始化路由

  1. blog.js

    const handleBlogRouter = (req, res) => {
        const method = req.method;
        const url = req.url;
        const path = url.split("?")[0];
    
        console.log("獲取到的路徑:", path);
        // 獲取博客列表
        if (method == "GET" && path == "/api/blog/list") {
            return {
                msg: "調用博客列表",
            };
        }
    
        // 獲取博客詳情
        if (method == "GET" && path == "/api/blog/detailed") {
            return {
                msg: "獲取博客詳情",
            };
        }
        // 創(chuàng)建博客
        if (method == "POST" && path == "/api/blog/create") {
            return {
                msg: "創(chuàng)建博客",
            };
        }
        // 刪除博客
        if (method == "POST" && path == "/api/blog/delete") {
            return {
                msg: "刪除博客",
            };
        }
        // 修改博客
        if (method == "POST" && path == "/api/blog/update") {
            return {
                msg: "修改博客",
            };
        }
    };
    
    module.exports = handleBlogRouter;
    
    
  1. user.js

    const handleUserRouer = (req, res) => {
        const method = req.method;
        const url = req.url;
        const path = url.split("?")[0];
    
        // 用戶登錄
        if (method == "POST" && path == "/api/user/login") {
            return {
                msg: "調用用戶登錄aip",
            };
        }
    };
    
    module.exports = handleUserRouer;
    
    
  1. app.js

    const handleBlogRouter = require("./src/router/blog");
    const handleUserRouer = require("./src/router/user");
    
    const serverHandle = (req, res) => {
        res.setHeader("Content-type", "application/json");
    
        const blogData = handleBlogRouter(req, res);
        if (blogData) {
            res.end(JSON.stringify(blogData));
            return;
        }
    
        const userData = handleUserRouer(req, res);
        if (userData) {
            res.end(JSON.stringify(userData));
            return;
        }
    
        res.writeHeader(404, { "Content-type": "text/plan" });
        res.write("404 Not");
        res.end();
    };
    
    module.exports = serverHandle;
    
    

4.7 開發(fā)路由(博客列表路由)

  1. 定義數(shù)據(jù)格式

    class BaseModel {
       constructor(data, message) {
           if (typeof data == "string") {
               this.message = data;
               data = null;
               message = null;
           }
           if (data) {
               this.data = data;
           }
           if (message) {
               this.data = data;
           }
       }
    }
    
    class SuccessModel extends BaseModel {
       constructor(data, message) {
           super(data, message);
           this.errno = 0;
       }
    }
    
    class ErrorModel extends BaseModel {
       constructor(data, message) {
           super(data, message);
           this.erron = -1;
       }
    }
    
    module.exports = {
       SuccessModel,
       ErrorModel,
    };
    
    
  2. 創(chuàng)建controller(控制器)文件夾

    const getList = (author, keyword) => {
        return [
            {
                id: 1,
                title: "標題A",
                contentText: "內容eAAAnei",
                createTime: "1635941708713",
                author: "張三",
            },
            {
                id: 1,
                title: "標題A",
                contentText: "內容eAAAnei",
                createTime: "1635941708713",
                author: "張三",
            },
        ];
    };
    
    module.exports = {
        getList
    };
    
  1. 返回數(shù)據(jù)

           ~~~javascript
        if (method == "GET" && req.path == "/api/blog/list") {
            const author = req.query.author || "";
            const keyword = req.query.keyword || "";
            const listData = getList(author, keyword);
            return new SuccessModel(listData);
        }
        ~~~
    

4.8博客詳情頁開發(fā)

  1. Promise 簡單講解callback 方式

    const fs = require("fs");
    const path = require("path");
    
    // callback 方式獲取一個文件內容
    
    function getFileContent(fileName, callback) {
        const fullFileName = path.resolve(__dirname, "files", fileName);
        fs.readFile(fullFileName, (err, data) => {
            if (err) {
                console.log(err);
                return;
            }
            callback(JSON.parse(data.toString()));
        });
    }
    
    getFileContent("a.json", (adata) => {
        console.log("a data:", adata);
        getFileContent(adata.next, (bdata) => {
            console.log("b data:", bdata);
            getFileContent(bdata.next, (cdata) => {
                console.log("c data:", cdata);
            });
        });
    });
    
  2. promise 方式

~~~javascript
function getFileContent(fileName) {
    const promise = new Promise((resolve, reject) => {
        const filePath = path.resolve(__dirname, "files", fileName);
        fs.readFile(filePath, (err, data) => {
            if (err) {
                reject(err);
                return;
            }
            return resolve(JSON.parse(data.toString()));
        });
    });
    return promise;
}

getFileContent("a.json")
    .then((data) => {
    console.log("a data:", data);
    return getFileContent(data.next);
})
    .then((bdata) => {
    console.log("b data:", bdata);
    return getFileContent(bdata.next);
})
    .then((cdata) => {
    console.log("cdata", cdata);
});

~~~

4.9 處理post 請求

  1. 通過promise 解析post 數(shù)據(jù)

    const getPostData = (req) => {
        const promise = new Promise((resolve, reject) => {
            if (method !== "POST") {
                resolve({});
                return;
            }
    
            if (req.headers["Content-type"] !== "application/json") {
                resolve({});
                return;
            }
    
            let postData = "";
    
            req.on("data", (chunk) => {
                postData += chunk.toString();
            });
    
            req.on("end", () => {
                if (postData) {
                    resolve({});
                    return;
                } else {
                    resolve(JSON.parse(postData));
                }
            });
        });
        return promise;
    };
    
  2. 使用 處理數(shù)據(jù)

getPostData(req).then((postData) => {
    req.body = postData;

    // 創(chuàng)建博客
    if (method == "POST" && req.path == "/api/blog/create") {
        return {
            msg: "創(chuàng)建博客",
        };
    }
    // 刪除博客
    if (method == "POST" && req.path == "/api/blog/delete") {
        return {
            msg: "刪除博客",
        };
    }
    // 修改博客
    if (method == "POST" && req.path == "/api/blog/update") {
        return {
            msg: "修改博客",
        };
    }
});

4.12 補充路由和Api

路由 和 api
  • API: 前段和后端缤削,不同端(子系統(tǒng))直接對接的一個術語

  • 路由: API 的一部分 后端系統(tǒng)內部的一個定義

第五章 數(shù)據(jù)存儲

5.1 MySql 介紹

1.  下載mysql
1.  下載workBench
1.  查看全部的表 show  databases

5.2 數(shù)據(jù)庫操作

  • 數(shù)據(jù)庫縮寫
    1. PK primary key 主鍵
    2. NN not null 是否為空
    3. UQ unique 外鍵
    4. BIN binary 二進制
    5. UN unsigned 無符號
    6. ZF zero fill 補零
    7. AI auto increment 自動增加

簡單的指令
1. show databases // 查看數(shù)據(jù)庫
2. use // 數(shù)據(jù)名稱
3. show tables // 顯示數(shù)據(jù)庫
4. SET SQL_SAFF_UPDATAS = 0 // 關閉數(shù)據(jù)庫安全檢查
5. <> // 設置不等于零

  • 指令:

      1. insert into 表名 (參數(shù)一,參數(shù)二,.....) values(參數(shù)一,參數(shù)二,..)
      1. delete from users where username="lisi"
      1. update users set realname = "測試"
      2. update users set realname = "李四" where username="lisi"
      1. select * from 表名; //查詢全部
      2. select 列名,列名 from 表名 // 查詢某幾列
      3. select * from 表名 where 列名=值 // 具有篩選條件的
      4. select * from 表名 where 列名=值 and 列名=值 // 多重與查詢
      5. select * from 表名 where 列名=值 or 列名=值 // 多重或查詢
      6. select * from 表名 where 列名 like %值% // 模糊查詢
      7. select * from 表名 order by 列名 // 排序 desc

5.4 nodejs 操作數(shù)據(jù)庫

1. 新建一個項目 (初始化)
2. 安裝mysql 依賴
- npm install mysql
3. 引入mysql
    1. const con = require('mysql')
4. 創(chuàng)建鏈接對象
+ con = mysql.createConnection({})
  +  host:"localhost" //域地址
  + user:"root"
  + password:"root"
  + port:"3306"
  + database:"myblog"
5. 開始鏈接數(shù)據(jù)庫
    1. con.connect()
6. 執(zhí)行sql語句
    1.  const sql = `'select * from users'`
    2.  con.query(sql,(err,result)=>{})
7. 關閉鏈接
    1.  con.end()

5.5 nodejs 鏈接mysql 做成工具

  1. 獲取環(huán)境變量
  • 鏈接 const env = process.env.NODE_ENV
  1. 新建兩個文件

  2. config

    const env = process.env.NODE_ENV; // 獲取環(huán)境參數(shù)
    
    let MYSQL_CONF;
    
    if (env === "dev") {
        console.log("鏈接dev:");
        MYSQL_CONF = {
            host: "localhost",
            user: "root",
            password: "root",
            port: "3306",
            database: "myblog",
        };
    }
    
    if (env === "production") {
        console.log("鏈接production:");
        MYSQL_CONF = {
            host: "localhost",
            user: "root",
            password: "root",
            port: "3306",
            database: "myblog",
        };
    }
    
    module.exports = {
        MYSQL_CONF,
    };
    
    
  3. mysql.js

  ~~~ javascript
  const mysql = require("mysql");
  
  const { MYSQL_CONF } = require("../conf/db.js");
  
  // 創(chuàng)建鏈接對象
  const con = mysql.createConnection(MYSQL_CONF);
  
  // 開始鏈接
  con.connect();
  
  // 執(zhí)行sql的函數(shù)
  function exec(sql) {
    const promise = new Promise((resolve, reject) => {
      con.query(sql, (err, result) => {
        console.log("數(shù)據(jù)庫獲取回來的數(shù)據(jù):", sql, err, result);
        if (err) {
          reject(err);
          return;
        }
        resolve(result);
      });
    });
  
    return promise;
  }
  
  module.exports = {
    exec,
    escape: mysql.escape,
  };
  
  ~~~

第六章 登錄

6.1 cook介紹

  1. 什么是cookie

    1. 存儲在瀏覽器中的一段字符串(最大5kb)
    2. 跨越不共享
    3. 格式如 k1=v1;k2=v2; k3=v3
    4. 沒次發(fā)送http請求,會將請求的cookie 一起發(fā)送給server
    5. server 可以修改cookie 并返回給瀏覽器
    6. 瀏覽器中也可以通過JavaScript修改cookie(有限制)
  2. JavaScript 操作 cookie 瀏覽器中查看cookie

    1. 查看cookie 的三種方式
    2. 直接在請求中查看
    3. 在application 中查看
    4. document.cookie
    5. 操作cookie的方式
    6. document.cookie = "k1=100"
    7. 只能以累加的方式修改
  3. server 端操作 cookie, 實現(xiàn)登錄驗證

    1. 查看cookie

      1. const cookieStr = req.headers.cookie
    2. 修改cookie

      1. res.serHeader("Set-Cookie",username = zhangshan; path=/; httpOnly)
      2. 注意設置 path
      3. 設置httpOnly 實現(xiàn)禁止前端修改
      4. 設置expires過期時間
        • expires = ${getCookieExpires()}
    3. 設置cookie過期時間

      const getCookieExpires = ()=>{
          const d = new Date()
          d.setTime(d.getTime() + (24 * 60 * 60 * 1000))
          return d.toGMTString()
      } 
      

6.2 session介紹學習

  1. 設置session的作用
    1. cookie不夠安全 seesion 用于存儲用戶數(shù)據(jù) 前端存儲虛擬的報文

    2. 加密數(shù)據(jù) 解決cooki不安全的問題

    3. 設置 session data 存儲 數(shù)據(jù)

    4. session 的問題
      1. 進程內存有限 訪問量過大 內存暴增
      2. 正式上線運行是多進程吹榴,進程之間內存無法共享

6.3 redis(內存數(shù)據(jù)庫) 學習

web server 最常用的緩存數(shù)據(jù)庫亭敢,數(shù)據(jù)放在內存中

優(yōu)點讀寫特別快 缺點 內存很貴 存儲空間小

  1. session 訪問頻繁 對性能要求極高
  2. session 可不考慮斷電數(shù)據(jù)丟失的問題(內存的硬傷)也可以斷電不丟失 要配置
  3. session 數(shù)據(jù)量不會太大(相比于MySQL中的數(shù)據(jù))

安裝redis

windows https://www.runoob.com/redis/redis-install.html 安裝教程

redis 命令

啟動命令 
  1. redis-server.exe redis.windows.conf
  2. redis-cli.exe -h 127.0.0.1 -p 6379
  3. set myKey abc
  4. get myKey
  5. keys *

6.4 nodejs 鏈接 redis 的demo

  1. 安裝 redis
    1. npm i redis@3.1.2 --save

    2. const redis = require("redis");
      
      // 創(chuàng)建redis客戶端
      const redisClient = redis.createClient(6379, "127.0.0.1");
      // 監(jiān)聽錯誤
      redisClient.on("err", (err) => {
        console.error(err);
      });
      
      // 測試
      redisClient.set("myname", "zhangshan1", redis.print);
      redisClient.get("myname", (err, val) => {
        if (err) {
          console.log("err:", err);
          return;
        }
        console.log("val:", val);
        redisClient.quit();
      });
      
      

6.5 redis封裝成組件

const redis = require("redis");
const { REDIS_CONF } = require("../conf/db");

// 創(chuàng)建客戶端
const redisClient = redis.createClient(REDIS_CONF.port, REDIS_CONF.host);

redisClient.on("error", (err) => {
  console.log("error:", err);
});

// set value

function set(key, val) {
  if (typeof val === "object") {
    val = JSON.stringify(val);
  }
  redisClient.set(key, val, redis.print);
}

// get value

function get(key) {
  const promise = new Promise((resolve, reject) => {
    redisClient.get(key, (err, val) => {
      if (err) {
        reject(err);
        return;
      }
      if (val == null) {
        resolve(null);
        return;
      }
      // 兼容json 操作
      try {
        resolve(JSON.parse(val));
      } catch {
        resolve(val);
      }
    });
  });
  return promise;
}

module.exports = {
  set,
  get,
};

6.6 session+cookie 聯(lián)調

  1. 判斷用戶是否通過cookie傳入userid 如果沒有傳入則隨機生成 并設置到用戶瀏覽器和 redis中
  2. 如果傳入cookie 中有userid 則去redis中查詢userid 如何返回非空對象則登錄 如果返回空對象 則未登錄
  3. 添加登錄驗證 在調用接口前判斷是否登錄成功(有 username)

6.7 前端聯(lián)調配置nginx

問題:

1. 登錄功能依賴cookie 必須用瀏覽器聯(lián)調
2. cookie,跨越不共享图筹,前端和server端必須同域
3. 需要用到nignx做代理帅刀,讓前后端端同域

6.8 nginx 介紹

  1. 高性能的web服務器让腹,開源免費
  2. 一般用于做靜態(tài)服務,負載均衡
  3. 反向代理

[圖片上傳失敗...(image-6e6580-1642776253504)]

  1. Nginx下載地址 https://nginx.org/en/download.html

    1. nginx 的幾個命令

      1. 查看配置是否錯誤 nginx -t
      2. 啟動nginx nginx
      3. 重啟nginx nginx -s reload
      4. 停止nginx -s stop
      
      #user  nobody;
      # 啟動幾何;
      worker_processes  1;
      
      #error_log  logs/error.log;
      #error_log  logs/error.log  notice;
      #error_log  logs/error.log  info;
      
      #pid        logs/nginx.pid;
      
      
      events {
          worker_connections  1024;
      }
      
      
      http {
          include       mime.types;
          default_type  application/octet-stream;
      
          #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
          #                  '$status $body_bytes_sent "$http_referer" '
          #                  '"$http_user_agent" "$http_x_forwarded_for"';
      
          #access_log  logs/access.log  main;
      
          sendfile        on;
          #tcp_nopush     on;
      
          #keepalive_timeout  0;
          keepalive_timeout  65;
      
          #gzip  on;
      
          server {
              listen       8080;
              server_name  localhost;
      
              #charset koi8-r;
      
              #access_log  logs/host.access.log  main;
      
              #        location / {
              #            root   html;
              #           index  index.html index.htm;
              #      }
        
        location / {
            proxy_pass http://localhost:8001;
        }
        
        location /api/ {
            proxy_pass http://localhost:8000;
            proxy_set_header Host $host;
        }
      
              #error_page  404              /404.html;
      
              # redirect server error pages to the static page /50x.html
              #
              error_page   500 502 503 504  /50x.html;
              location = /50x.html {
                  root   html;
              }
      
              # proxy the PHP scripts to Apache listening on 127.0.0.1:80
              #
              #location ~ \.php$ {
              #    proxy_pass   http://127.0.0.1;
              #}
      
              # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
              #
              #location ~ \.php$ {
              #    root           html;
              #    fastcgi_pass   127.0.0.1:9000;
              #    fastcgi_index  index.php;
              #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
              #    include        fastcgi_params;
              #}
      
              # deny access to .htaccess files, if Apache's document root
              # concurs with nginx's one
              #
              #location ~ /\.ht {
              #    deny  all;
              #}
          }
      
      
          # another virtual host using mix of IP-, name-, and port-based configuration
          #
          #server {
          #    listen       8000;
          #    listen       somename:8080;
          #    server_name  somename  alias  another.alias;
      
          #    location / {
          #        root   html;
          #        index  index.html index.htm;
          #    }
          #}
      
      
          # HTTPS server
          #
          #server {
          #    listen       443 ssl;
          #    server_name  localhost;
      
          #    ssl_certificate      cert.pem;
          #    ssl_certificate_key  cert.key;
      
          #    ssl_session_cache    shared:SSL:1m;
          #    ssl_session_timeout  5m;
      
          #    ssl_ciphers  HIGH:!aNULL:!MD5;
          #    ssl_prefer_server_ciphers  on;
      
          #    location / {
          #        root   html;
          #        index  index.html index.htm;
          #    }
          #}
      
      }
      
      

第七章 博客項目日志

7.1 開始

1. 訪問日志 access log(server 端最重要的日志)
1. 自定義日志(包括自定義事件扣溺,錯誤記錄等)

nodejs 文件操作 nodejs stream

日志功能開發(fā)和使用

日志文件拆分骇窍,日志內容分析

7.2 nodeJs 文件操作

  1. 引入fs 和 path 模塊

  2. path 模塊用于統(tǒng)一文件目錄 Linux mac windows

  3. 讀取文件

    1. const fs = require("fs");
      const path = require("path");
      
      const filePath = path.relative(__dirname, "data.txt");
      
      fs.readFile(filePath, (err, data) => {
        if (err) {
          console.error(err);
          return;
        }
        // data 是二進制類型 需要轉換為字符串
        console.log("data.toString", data.toString());
      });
      
      
      
  4. 寫入文件

    1. const fs = require("fs");
      const path = require("path");
      
      const filePath = path.relative(__dirname, "data.txt");
      
      const content = "這里是要寫入的內容\n";
      const opt = {
        flag: "a", // 追加內容 覆蓋用w
      };
      fs.writeFile(filePath, content, opt, (err) => {
        if (err) {
          console.log("err", err);
        }
      });
      
      
  5. 判斷文件是否存在

    1. fs.exists(filePath, (exists) => {
        console.log("exists", exists);
      });
      

7.3 stream 介紹

  1. IO操作的性能瓶頸
    1. IO 包括 網(wǎng)絡IO 和文件 IO
    2. 相比于 CPU 計算和內存讀寫 IO 突出的特殊的就是:慢!
    3. 如何在有限的硬件資源下提供IO 的操作效率

7.4 stream 實例

  1. 復制文件

    1. const fs = require("fs");
      const path = require("path");
      
      // 復制文件
      let index = 0;
      const filePath1 = path.relative(__dirname, "test01.txt");
      const filePath2 = path.relative(__dirname, "text03.txt");
      
      const streamFile1 = fs.createReadStream(filePath1);
      const streamFile2 = fs.createWriteStream(filePath2);
      
      streamFile1.pipe(streamFile2);
      streamFile1.on("data", (chunk) => {
        console.log("index:", index++);
      });
      streamFile1.on("end", () => {
        console.log("file ok");
      });
      
      
  2. server 利用stream

  3. const fs = require("fs");
    const path = require("path");
    const http = require("http");
    
    const filePath = path.relative(__dirname, "test01.txt");
    const server = http.createServer((req, res) => {
      if (req.method == "GET") {
        const readStreamFile = fs.createReadStream(filePath);
        readStreamFile.pipe(res);
      }
    });
    
    server.listen(4401);
    
    

7.5 stream封裝組件

const fs = require("fs");
const path = require("path");

// 寫入文件
function writeLog(writeStream, log) {
  writeStream.write(log + "\n");
}

// 創(chuàng)建讀stream
function createWriteStream(fileName) {
  const falePath = path.join(__dirname, "../", "../", "logs", fileName);
  const streamFile = fs.createWriteStream(falePath, { flags: "a" });
  return streamFile;
}

const accessWriteStream = createWriteStream("access.log");
accessWriteStream.on("err", (err) => {
  console.log("err", err);
});
function access(log) {
  writeLog(accessWriteStream, log);
}

module.exports = {
  access,
};

7.6 日志拆分(運維處理)

1. 日志內容會慢慢累積,放在一個文件中不好處理
2. 按時間劃分日志文件锥余,如 2019-02-10.access.log
3. 實現(xiàn)方式: linux 的 crontab 命令像鸡,定時任務
  1. 設置定時任務

     > **** command
    
     1. 分鐘   
     2. 小時
     3. 日期
     4. 月份
     5. 星期
    
  2. 將 access.log 拷貝并重命名為 2019-02-10.access.log

  3. 清空access.log 文件 ,繼續(xù)積累日志

  4. 配置

    #!/bin/sh
    
    cd 目錄文件下面
    cp access.lgo $(date +%Y-%m-%d).access.log
    echo '' > access.log
    
    >> 運行 sh copy.sh 可以用于測試
    
    >> 編輯 crontab 
      | crontab -e
    >> 在文件中編輯
      | * 0 * * * sh 目錄
    
    成功的話顯示 installing new crontab
    查看任務 crontab -l
    

7.7 日志分析 readline

const fs = require("fs");
const path = require("path");
const readline = require("readline");

// 文件路徑
const filePath = path.join(__dirname, "../", "../", "logs", "access.log");

// 創(chuàng)建 read stream
const readStreamFile = fs.createReadStream(filePath);

// 創(chuàng)建一個 readline 對象
const rl = readline.createInterface({ input: readStreamFile });

let chromeNum = 0;
let sum = 0;

rl.on("line", (lineData) => {
  if (lineData == "") {
    return;
  }
  sum++;
  const str = lineData.split("--")[2];
  if (str && str.indexOf("Chrome") > 0) {
    chromeNum++;
  }
});

rl.on("close", () => {
  console.log("chrome 占比:" + chromeNum / sum);
});

第八章 node安全

8.1 開始和sql注入

1. sql注入: 竊取數(shù)據(jù)庫內容
1. XSS 攻擊: 竊取前端cookie內容
1. 密碼加密: 保障用戶信息安全( 重要! )

一 哈恰、 sql 注入

1. 最原始最簡單的攻擊
2. 攻擊方式 輸入一個sql片段只估,最終拼接成一段攻擊代碼
3.  使用mysql的 escape 函數(shù)處理輸入內容即可
const mysql = require("mysql");

const login = (username, password) => {
  const username1 = mysql.escape(username);
  const password1 = mysql.escape(password);

8.2 XSS攻擊

攻擊范式:在頁面展示內容中摻雜js代碼,以獲取網(wǎng)頁信息

預防措施:轉換生成js 的特殊字符

  1. 使用專業(yè)的工具 xss
    1. npm install xss --save
  2. 引入后直接使用即可
    1. const title = escape(xss(blogData.title));

8.3 密碼加密

  1. 萬一數(shù)據(jù)庫被用戶攻破着绷,最不應該泄露的就是用戶信息
  2. 攻擊方式: 獲取用戶名和密碼蛔钙,在去嘗試登錄其他系統(tǒng)
  3. 預防措施: 將密碼加密,即便拿到密碼也不知道明文

總結

1. 服務端穩(wěn)定性
2. 內存CPU 優(yōu)化 擴展
3. 日志記錄
4. 安全
5. 集群 和 服務拆分
const crypto = require("crypto");

// 密匙
const SERET_KEY = "BLACKITRABBIT";

// md5 加密

function md5(content) {
  let md5 = crypto.createHash("md5");
  return md5.update(content).digest("hex");
}

// 加密函數(shù)
function genPassword(password) {
  const str = `password=${password}&key=${SERET_KEY}`;
  return md5(str);
}

console.log(genPassword(123));

第九章 express 重構項目

9.1 開始

  1. express 是最常用的 web server 框架
  2. express 下載荠医、安裝吁脱、和使用,express 中間件機制
  3. 開發(fā)接口彬向,鏈接數(shù)據(jù)庫兼贡,實現(xiàn)登錄,日志記錄
  4. 分解 express 中間件原理

9.2 express 安裝

  1. 安裝(使用腳手架 express-generator)

    npm install express-generator -g

  2. 利用工具生成項目

    express express-test

  3. npm install && npm start

9.3 express 中間件機制

  1. 有很多 app.use
  2. 代碼中的 next 參數(shù)是什么

9.4 環(huán)境初始化

  1. 使用express-session
  2. connect-redis

9.5 鏈接mysql

"mysql": "^2.18.1",

"xss": "^1.0.10"

9.6 鏈接redis

  1. redis
  2. connect-redis
const RedisStore = require("connect-redis")(session);

const { redisClient } = require("./db/redis");
const sessionStore = new RedisStore({
  client: redisClient,
});

app.use(
  session({
    secret: "asdwerd_sad",
    cookie: {
      path: "/", // 默認配置
      httpOnly: true, // 默認配置
      maxAge: 24 * 60 * 60 * 1000,
    },
    store: sessionStore,
  })
);

9.7 登錄中間件

const { ErrorModel } = require("../model/resModel");

module.exports = (req, res, next) => {
  if (req.session.username) {
    next();
    return;
  }
  res.json(new ErrorModel("未登錄"));
};

9.8 介紹morgan日志

  1. 引入 > const logger = require("morgan");
  2. 配置 app.use(logger("dev"));
const filePath = path.join(__dirname, "logs", "access.log");

const writeStream = fs.createWriteStream(filePath, { flags: "a" });

 app.use(

  logger("combined", {

   stream: writeStream,

  })

 );

9.9express 中間件原理

  1. 回顧中間件使用
  2. 分析如何實現(xiàn)
  3. 代碼演示
const http = require("http");
const slice = Array.prototype.slice;

class LikeExpress {
  constructor() {
    // 存放中間件的列表
    this.routes = {
      all: [],
      get: [],
      post: [],
    };
  }

  register(path) {
    const info = {};
    if (typeof path == "string") {
      info.path = path;
      // 從第二個參數(shù)開始, 轉換為數(shù)組 stack
      info.stack = slice.call(arguments, 1);
    } else {
      info.path = "/";
      // 從第一個參數(shù)開始, 轉換為數(shù)組 存入 stack
      info.stack = slice.call(arguments, 0);
    }
    return info;
  }

  use() {
    const info = this.register.apply(this, arguments);
    this.routes.all.push(info);
  }

  get() {
    const info = this.register.apply(this, arguments);
    this.routes.get.push(info);
  }

  post() {
    const info = this.register.apply(this, arguments);
    this.routes.post.push(info);
  }

  // 核心想next 函數(shù)
  handle(req, res, stack) {
    const next = () => {
      // 拿到第一個匹配的中間件
      const middleware = stack.shift();
      if (middleware) {
        // 執(zhí)行中間件函數(shù)
        middleware(req, res, next);
      } 
    };
    next();
  }

  match(method, url) {
    let stack = {};
    if (url == "/favicon.icon") {
      return stack;
    }

    // 獲取  routes
    let curRoutes = [];
    curRoutes = curRoutes.concat(this.routes.all);
    curRoutes = curRoutes.concat(this.routes[method]);

    curRoutes.forEach((routeInfo) => {
      if (url.indexOf(routeInfo.path) == 0) {
        stack = stack.concat(routeInfo.stack);
      }
    });
  }

  callback() {
    return (req, res) => {
      res.json = (data) => {
        res.setHeader("Content-tyoe", "application/json");
        res.end(JSON.stringify(data));
      };

      const url = req.url;
      const method = req.method.toLowerCase();

      const resulList = this.match(method, url);
      this.handle(req, res, resulList);
    };
  }

  listen(...args) {
    const server = http.createServer(this.callback());
  }
}

// 工廠函數(shù)
module.exports = () => {
  return new LikeExpress();
};

第十章 Ko2 重構博客項目

10.1 開始

  1. express 中間件是異步回調娃胆,koa2 原生支持 async/await(異步函數(shù))
  2. 新開發(fā)的框架和系統(tǒng)遍希,都開始基于koa2, 例如 egg.js
  3. express 雖然未過時里烦,但是koa2 肯定是未來的趨勢

10.2 koa2 介紹

  1. 安裝

    npm install koa-generator -g

  2. koa2 環(huán)境初始化

    Koa2 koa2-test

  3. 啟動項目

> npm install & npm run dev

10.3 介紹koa2 中間件機制

  1. 有很多 app.use
  2. 代碼中的next參數(shù)是什么

10.4 koa2實現(xiàn)session

  1. 基于 koa-generic-session 和 koa-redis

    npm i koa-generic-session koa-redis redis --save

  2. 引用

    const session = require('koa-generic-session')
    const reidsStore = require('koa-redis')
    
  3. 配置session 設置key

    app.keys = ["key1"]
    app.use(session({
     // 配置cookie
     cookie: {
         path: '/',
         httpOnlay: true,
         maxAge: 24 * 60 * 60 * 1000
     }
     // 配置 redis
     store: redisStore({
         all: '127.0.0.1:6379' // 寫死本地的 redis server
     })
    }))
    
  4. 配置驗證接口

    router.get('/session-test',async function (ctx, next) {
     if(ctx.session.viewCount == null){
            ctx.session.viewCount = 0;
        }
        ctx.session.viewCount ++;
        ctx.body = {
            errno: 0,
            viewCount:ctx.session.viewCount
        }
    })
    

10.5 開發(fā)路由-準備工作

  1. 安裝mysql 和 xss

    npm i mysql xss --save

  2. 拷貝module凿蒜、config 文件夾

  3. 配置 redis

  4. 拷貝db,controller 文件夾加

10.6 日志

access log 記錄 胁黑,使用 morgan

  1. 安裝koa-morgan

    npm i koa-morgan --save

  2. 新建logs文件夾 創(chuàng)建access.log 文件

  3. 引入morgan

    const fs = require('fs')
    const path = require('path')
    const morgan = require('koa-morgan')
    
    const ENV = process.env.NODE_ENV
    if(ENV !== 'production'){
        app.use(morgan('dev'))
    }else{
        const logFilePath = path.join(__dirname,'logs','access.log')
        const writeStream = fs.createWriteStream(logFilePath,{flags:'a'})
        app.user(morgan("combined",{
            stream:writeStram
        }))
    }
    

10.7 Koa2 中間件原理分析

洋蔥圈模型 從最外層依次進入 從內層向外走出

const Koa = require('koa');
const app = new Koa();

// logger

app.use(async (ctx, next) => {
  await next();
  const rt = ctx.response.get('X-Response-Time');
  console.log(`${ctx.method} ${ctx.url} - ${rt}`);
});

// x-response-time

app.use(async (ctx, next) => {
  const start = Date.now();
  await next();
  const ms = Date.now() - start;
  ctx.set('X-Response-Time', `${ms}ms`);
});

// response

app.use(async ctx => {
  ctx.body = 'Hello World';
});

app.listen(3000);

10.8

const http = require("http");

// 組合中間件
function compose(middlewareList) {
  return function (ctx) {
    // 中間件調用的邏輯
    function dispatch(i) {
      const fn = middlewareList[i];
      try {
        return Promise.resolve(fn(ctx, dispatch.bind(null, i + 1)));
      } catch (err) {
        Promise.resolve(err);
      }
    }
    return dispatch(0);
  };
}

class LikeKoa2 {
  constructor() {
    this.middlewareList = [];
  }

  use(fn) {
    this.middlewareList.push(fn);
    return this;
  }

  createContext = (req, res) => {
    const ctx = {
      req,
      res,
    };
    ctx.query = req.query;
    return ctx;
  };

  handleRequest(ctx, fn) {
    return fn(ctx);
  }

  callback() {
    const fn = compose(this.middlewareList);

    return (req, res) => {
      const ctx = this.createContext(req, res);
      return this.handleRequest(ctx, fn);
    };
  }

  listen(...args) {
    const server = http.createServer(this.callback());
    server.listen(...args);
  }
}

module.exports = LikeKoa2;

第十一章 上線與配置

11.1 開始和PM2介紹

一废封、pm2做什么
  1. 進程守護,系統(tǒng)崩潰自動重啟丧蘸。
  2. 啟動多進程漂洋,充分利用CPU和內存
  3. 自帶日志記錄功能
二 、pm2學什么
  1. pm2 介紹

  2. pm2 進程守護

  3. pm2 配置和日志記錄

  4. pm2 多進程

  5. pm2 關于服務運維

11.2 pm2 下載和安裝

  1. 下載和安裝

    npm install pm2 -g

    pm2 -version

  2. 配置pm啟動

    "prd": "cross-env NODE_ENV=production pm2 start app.js"

  3. 查看啟動列表

    pm2 ls

11.3 pm2 常用命令

  1. pm2 start ...
  2. pm2 list // 獲取當前運行的列表
  3. pm2 restart <appName>/<id> // 重啟服務器
  4. pm2 stop <appName>/<id> // 停止
  5. pm2 delete <appName>/<id> // 刪除
  6. pm2 info <appName>/<id> // 獲取某一個進程的基本信息
  7. pm2 log <appName>/<id> // 打印log信息
  8. pm2 monit <appName>/<id> // 監(jiān)聽服務器狀態(tài)

11.4 pm2 常用配置

新建pm2 配置文件(包括進程數(shù)量力喷,日志文件目錄等)

修改pm2 啟動命令刽漂,重啟

訪問server,檢查日志文件的內容(日志記錄是否生效)

  1. 新建配置文件

    pm2.conf.json

  2. 配置內容

    {
      "apps": {
        "name": "pm2-test-server",
        "script": "app.js",
        "watch": true,
        "ignore_watch": ["node_modules", "logs"],
        "error_file": "logs/err.log",
        "out_file": "logs/out.log",
        "log_date_format": "YYYY-MM-DD HH:mm:ss"
      }
    }
    
    
  3. 配置啟動項

    "prd": "cross-env NODE_ENV=production pm2 start pm2.conf.json"

11.5 pm2 多進程

  1. 為什么使用多進程

    1. 操作系統(tǒng)會限制進程的最大可用內存
    2. 無法充分利用多核cpu的優(yōu)勢
    3. 某一個進程程序崩潰 其他的進程不受影響
  2. 配置多進程

    "instances": 4

擴展項目上線發(fā)發(fā)布

CentOs nodejs搭建

  1. 安裝nvm

    wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash

  2. 查看配置

    vim .bashrc

  3. 退出文件

> :w file 將修改另外保存到file中冗懦,不退出vi
>
> :w!  強制保存爽冕,不推出vi
>
> :wq 保存文件并退出vi
>
> :wq! 強制保存文件,并退出vi
>
> :q 不保存文件披蕉,退出vi
>
> :q! 不保存文件,強制退出vi
>
> :e! 放棄所有修改,從上次保存文件開始再編輯
  1. 執(zhí)行source 使環(huán)境變量生效

    source .bashrc

CentOs Nginx環(huán)境搭建

  1. 安裝nginx服務器需要配置 yum CentOs 服務自帶

yum -y install pcre*

  1. 安裝 openssl *

    yum -y install openssl*

  2. 新建文件夾

    mkdir nginx

  3. 切換到nginx中安裝nginx

    wget http://nginx.org/download/nginx-1.12.2.tar.gz

  4. 解壓

    tar -zxvf 解壓的文件

  5. 進入到解壓好的文件目錄下 執(zhí)行 configure

    ./configure 或者 sh configure --profix = 安裝路徑

  6. 利用make指令進行安裝

    make install

  7. 查看是否安裝成功

/user/local/nginx/sbin/nginx -t

  1. 路徑進行軟連接到 usr/bin 路徑下 -snf 可以修改

ln -s /root/nginx/ninxtstart nginx

  1. 查看是否創(chuàng)建成功

    ll nginx

  2. 查看進程

    ps -ef|grep nginx

  3. 關閉nginx進程

    nginx -s stop

nginx配置

  1. 配置權限

  2. 配置引入其他配置

    include /root/nginx/*.conf;y

  3. 在其他文件中配置

    server{
       listen 80;
       server_name localhost;
       root /root/nginx/upload;
       autoindex on;
       add_header Cache-Control "no-cache,must-revalidate";
       location / {
           add_header Access-Control-Allow-Origin *;
       }
    }
    
  4. 配置 vim 顯示行數(shù)

    :set nu

  5. 配置好對應展示的文件即可

CentOs git 部署+免密更新

  1. 安裝git

    npm install -y git

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子氨距,更是在濱河造成了極大的恐慌刊橘,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件帅涂,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機于样,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來潘靖,“玉大人穿剖,你說我怎么就攤上這事∝砸纾” “怎么了糊余?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長单寂。 經(jīng)常有香客問我贬芥,道長,這世上最難降的妖魔是什么宣决? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任蘸劈,我火速辦了婚禮,結果婚禮上尊沸,老公的妹妹穿的比我還像新娘昵时。我一直安慰自己,他們只是感情好椒丧,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布壹甥。 她就那樣靜靜地躺著,像睡著了一般壶熏。 火紅的嫁衣襯著肌膚如雪句柠。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天棒假,我揣著相機與錄音溯职,去河邊找鬼。 笑死帽哑,一個胖子當著我的面吹牛谜酒,可吹牛的內容都是我干的。 我是一名探鬼主播妻枕,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼僻族,長吁一口氣:“原來是場噩夢啊……” “哼粘驰!你這毒婦竟也來了?” 一聲冷哼從身側響起述么,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤蝌数,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后度秘,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體顶伞,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年剑梳,在試婚紗的時候發(fā)現(xiàn)自己被綠了唆貌。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡垢乙,死狀恐怖锨咙,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情侨赡,我是刑警寧澤蓖租,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站羊壹,受9級特大地震影響蓖宦,放射性物質發(fā)生泄漏。R本人自食惡果不足惜油猫,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一稠茂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧情妖,春花似錦睬关、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至料睛,卻和暖如春丐箩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背恤煞。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工屎勘, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人居扒。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓概漱,卻偏偏與公主長得像,于是被迫代替她去往敵國和親喜喂。 傳聞我的和親對象是個殘疾皇子瓤摧,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內容