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 来解决
相关文章
相关标签/搜索