node實戰入門總結

搭建流程(按新建順序)層層嵌套引入

系統架構設計的四層抽象:html

  • 第一層: www.js [開啓 Server]
  • 第二層:app.js [通訊設置層]
  • 第三層:router.js [業務邏輯層]
  • 第四層:controller.js [數據層]

整體結構:前端

  • bin/www.js(==第一層==):node構建服務的基本流程配置,和業務無關。package裏面配置好的入口文件
  • app.js(==第二層==)
  • src
    • router(==第三層==:根據不一樣路由返回不一樣數據)
      • blog.js:博客crud接口
      • user.js:用戶相關的接口
    • control(==第四層==:返回數據給到路由這邊,只關心數據及其處理,通常是和數據庫這邊交互)
    • model(數據模型:成功、失敗返回的數據格式等)
    • conf:配置
      • db.js:獲取環境參數對數據庫進行配置,根據開發環境下的和線上環境使用不一樣的數據庫
    • db
      • mysql.js:對數據庫的基本操做(鏈接建立關閉,不涉及業務邏輯)
      • redis.js: 封裝redis的set、get操做
    • utils:工具
      • log.js:專門用來寫日誌的工具
  • logs:日誌
    • access.log:存儲訪問日誌
    • event.log:存儲一些自定義內容
    • error.log:存儲錯誤內容

記錄

session和Redis

1、session不足:node

  • session是存儲在nodeJS進程的內存中的
  • 進程內存有限,session過大,會把進程擠爆
  • 進程與進程之間內存相互獨立,沒法共享,所以session也沒法在多進程之間共享

解決:將session存儲在Redis中mysql

  • Redis 是另一個服務,和web server的nodeJS 進程沒有關係了
  • Redis相似於數據庫,也是一個數據倉庫,用來存儲數據的(能夠叫作內存數據庫吧)
  • 可是普通數據庫的數據是存儲在硬盤上的,訪問速度慢
  • Redis上的數據是存儲在內存中的,訪問速度快,但昂貴

2、爲什麼session適合用Redis存儲nginx

  • session訪問頻繁,對性能要求極高
  • session可不考慮斷點丟失數據問題(內存的硬傷),由於大不了從新登陸
  • session數據量不會太大,通常只是存儲用戶的我的信息(相比於 MySQL 中 存儲的數據)

3、開啓redis服務:web

打開一個 cmd 窗口 使用 cd 命令切換目錄到redis目錄下 運行redis

redis-server.exe redis.windows.conf
複製代碼

接口和前端聯調

  • 登錄功能依賴cookie,必須用瀏覽器來聯調(postman沒法作到)
  • cookie跨域不共享,前端和server端必須同域
  • 所以須要用到Nginx 作代理,讓先後端同域

Nginx介紹

  • 高性能的 web 服務器,開源免費
  • 通常用於作靜態服務(CDN)、負載均衡
  • 反向代理

如何配置反向代理:sql

  1. 要同時開啓對應的服務(佔用不一樣端口):nodeJS server服務8000端口(後端接口)、http-server服務8001端口(前端頁面)
  2. 監聽8080端口
  3. 反向代理配置

nginx.conf配置文件:數據庫

location / {
    # 若是是根目錄(即 http://localhost:8080)則代理到 8001 端口
	proxy_pass http://localhost:8001;
}

location /api/ {
    # 若是是訪問接口,則代理到 8000 端口
	proxy_pass http://localhost:8000;
    proxy_set_header Host $host;
}

複製代碼
  1. 經過訪問http://localhost:8080/index.html便可

ps:express

  • 開啓nginx 服務:start nginx
  • 開啓http-server服務:http-server -p8001
  • 開啓nodeJs server服務:npm run dev

Nginx命令

  • start nginx:啓動nginx服務
  • nginx -t:測試配置文件nginx.conf 語法是否正確

express搭建

構建流程

  1. npm install -g express -generator全局安裝express命令安裝工具
  2. express 項目名
  3. npm install 安裝組件
  4. npm start啓動項目(服務器)
  5. npm i nodemon cross-env

寫本身的業務邏輯:

  1. 新建路由文件,針對不一樣路由進行業務邏輯處理
  2. 將新建的路由文件(好比blog.js)引入app.js文件中,並使用app.use註冊咱們的路由

相對於原生nodejs:

  • 直接經過req.query獲取get傳過來的參數,經過req.body獲取 post傳過來的參數
  • 經過res.json直接返回json數據給客戶端
  • 使用express-sessionconnect-redis、登錄中間件
  • 使用morgan來記錄日誌,根據配置決定將日誌輸出到控制檯仍是文件中

koa2

構建流程

  1. npm install koa-generator -g全局安裝express命令安裝工具
  2. Koa2 項目名
  3. npm install 安裝組件
  4. npm i cross-env
  5. npm run dev啓動項目(服務器)

相對於express和原生nodejs:

  • 直接經過ctx.query獲取get傳過來的參數,經過ctx.request.body獲取 post傳過來的參數
  • 經過ctx.body直接返回json數據給客戶端
  • 經過async/await來實現中間件
  • 經過await next()來執行下一個中間件
  • 相比express,koa2在記錄日誌時要手動安裝koa-morgan插件

中間件

1、express中間件

  • 其實中間件就是一個這樣格式的函數,三個參數req, res, next
function loginCheck(req, res, next) {
    console.log('模擬登錄成功')
    next()
}
複製代碼
  • 能夠經過app.use()app.get()app.post()註冊中間件
  • 能夠註冊多箇中間件,依次執行
  • 經過next()的執行一個一個的往下串聯下一個中間件

實現原理思路:

  1. app.use用來註冊中間件,先收集起來
  2. 遇到http請求,根據pathmethod判斷觸發哪些中間件
  3. 實現next()機制,即上一個經過next()觸發下一個
// 實現相似 express 的中間件
const http = require('http')
const slice = Array.prototype.slice

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

    register(path) {
        const info = {}
        if (typeof path === 'string') {
            info.path = path
            // 從第二個參數開始,轉換爲數組,存入 stack
            info.stack = slice.call(arguments, 1)
        } else {
            info.path = '/'
            // 從第一個參數開始,轉換爲數組,存入 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)
    }

    // 經過當前 method 和 url 來匹配當前路由可執行的中間件
    match(method, url) {
        let stack = []
        if (url === '/favicon.ico') {
            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) {
                // url === '/api/get-cookie' 且 routeInfo.path === '/'
                // url === '/api/get-cookie' 且 routeInfo.path === '/api'
                // url === '/api/get-cookie' 且 routeInfo.path === '/api/get-cookie'
                stack = stack.concat(routeInfo.stack)
            }
        })
        return stack
    }

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

    callback() {
        return (req, res) => {
            res.json = (data) => {
                res.setHeader('Content-type', 'application/json')
                res.end(
                    JSON.stringify(data)
                )
            }
            const url = req.url
            const method = req.method.toLowerCase()

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

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

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

複製代碼

2、koa2中間件

  • koa2中間件其實就是一個async函數,參數爲(ctx, next)
app.use(async (ctx, next) => {
    await next();
    ctx.body = 'Hello World';
});
複製代碼

實現思路:

  • 也是使用app.use來註冊中間件,先收集起來
  • 實現next機制,即上一個經過await next()觸發下一個中間件
  • 不涉及methodpath的判斷
<!--實現相似 靠中間件-->
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))  // promise
                )
            } catch (err) {
                return Promise.reject(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

複製代碼

node線上環境

PM2

  • 進程守護,系統奔潰自動重啓restart,而不是說系統出錯以後其餘用戶就沒法使用了
  • 啓動多進程,充分利用cpu 和內存
  • 自帶日誌記錄功能

下載安裝

cnpm i pm2 -g
複製代碼

配置命令

"prd": "cross-env NODE_ENV=production pm2 start app.js"
複製代碼

啓動

npm run prd
複製代碼

經常使用命令

  • pm2 start ... 啓動進程
  • pm2 list: 能夠查看pm2進程列表
  • pm2 restart appNme/id : 重啓進程
  • pm2 stop appName/id : 中止
  • pm2 delete appName/id : 刪除
  • pm2 info appName/id :查看基本信息
  • pm2 log appName/id :查看進程日誌
  • pm2 monit appName/id:監控進程的cpu和內存信息

pm2配置文件

  1. 包括進程數量、日誌文件目錄等
  2. 修改pm2 啓動命令,重啓
  3. 訪問 server,檢查日誌文件的內容(日誌記錄是否生效)
{
    "apps": {
        "name": "pm2-test-server", // 進程名
        "script": "app.js", //用框架就是'bin/www'
        "watch": true,  // 監聽文件變化,是否自動重啓
        "ignore_watch": [ // 哪些文件不須要重啓
            "node_modules",
            "logs"
        ],
        "instances": 4, // 進程個數,這裏
        "error_file": "logs/err.log", // 錯誤日誌存放位置
        "out_file": "logs/out.log", // 原本打印在控制檯的console自定義存放在文件裏
        "log_date_format": "YYYY-MM-DD HH:mm:ss" // 日誌的時間戳
    }
}
複製代碼

多進程

爲什麼使用多進程

  • 操做系統會限制一個進程的最大可用內存。由於按照軟件設計拆分這種模式來講,若是一個進程作不少不少的事情,該進程萬一奔潰,這但是天災後果。所以須要分爲多個進程,進程之間相互獨立,提升了服務器穩定性
  • 所以多進程能夠充分利用機器的內存,充分發揮多核CPU的優點(同時處理多個進程)

多進程和redis

  • 多進程之間的session沒法共享
  • 採用共享redis 來解決
相關文章
相關標籤/搜索