美文网首页
Node.js从零搭建

Node.js从零搭建

作者: 小猪佩奇丶 | 来源:发表于2019-06-02 12:03 被阅读0次

    主要是学习了Node.js从零开发Web Server博客,而将学习内容做个总结。

    1.nodejs介绍

    nodejs的安装就不说了,最主要的是安装nodemon和cross-env。

    1)nodemon

    nodemon主要是用来当服务器启动之后,监听文件的变化,当有文件发生变化的时候,就会自动重启服务。

    2)cross-env

    cross-env主要是在package.json里面设置环境变量。可以让程序内通过process.env来进行获取变量。
    例如:在package.json的script中写上了
    cross-env NODE_ENV=dev nodemon ./bin/www.js
    意思就是启动www.js这个主文件,然后可以在process.env.NODE_ENV获取到dev这个值。

    2.主体框架搭建

    1)创建主入口文件

    首先在项目文件夹内创建bin文件夹,然后在里面创建www.js,该文件主要是用来启动服务的,是项目的主要入口。

    const http = require('http')
    const PORT = 8000
    
    const serverHandle = require('../app')
    
    const server = http.createServer(serverHandle)
    
    server.listen(PORT, () => {
      console.log('server listen on localhost:8000')
    })
    

    利用nodejs自带的http创建一个服务器实例,并传入一个函数,当用户访问的时候会走这一个函数。由于这只是主入口,应做到代码分离,这样改起来比较清晰一点。所以将函数放在了另一个文件当中,最后server.listen进行监听8000端口就可以了。
    入口函数可以什么都不写,然后用node ./bin/www.js也可以进行启动。

    2)创建入口函数

    首先我们在根目录下创建app.js作为处理用户访问时候的函数。
    该函数主要接受两个参数req,res,即请求和响应。
    主体内容为:

    const serverHandle = async (req, res) => {
    }
    module.exports = serverHandle
    

    这样其实就已经可以进行输出了,而程序主要做的就是往里面填写东西,对用户访问进行的请求进行处理,并添加处理结果到响应中,返回给用户。

    3)解析url地址

    通过req.url我们可以获取到用户访问的路径,然后使用split(‘?’)来分别对路径的地址和参数作出处理。
    将地址存入req.path中,方便路由的时候进行处理。

      const url = req.url
      req.path = url.split('?')[0]
    

    对参数部分的处理,可以通过引入const querystring = require('querystring')来进行处理。只需要一句话就可以将a=2&b=3转换成对象{a:2,b:3}的形式
    然后存入req.query当中。

    req.query = querystring.parse(url.split('?')[1])
    

    4)对post的data进行处理

    由于获取data数据的时候,是需要一点点获取的,所以要使用req.on('data')函数来进行获取数据,因为该数据是二进制的形式,所以需要转换成字符串,然后使用req.on('end')来进行监听是否完成数据的接受。
    具体代码如下:

    const getPostData = req => {
      let promise = new Promise((resolve, reject) => {
        if (req.method == 'GET') {
          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
          }
          resolve(JSON.parse(postData))
        })
      })
      return promise
    }
    

    这里主要使用了promise的方式来检测是否完成,方便后面使用async、await的方式来进行同步的操作。因为这部分数据没有获取完之前是不能对数据进行获取,并做处理的。
    前面只是对method方式为get就返回,content-type不为application/json的就返回,实际上还有很多种情况。form表单提交等也可以作处理。

    所以这一部分放在serverHandle 的开头就可以的。然后将获取到的数据放入req.body当中,方便后续操作。

    3.路由操作

    1)主体框架

    路由的原理其实就是对req.path进行判断,是否和路径对应,对应则走这一步函数,没有对应则不作处理。
    首先创建一个route的文件夹,并且根据模块的不同,创建route文件。
    然后在app.js中引入该route文件。

    const handleBlogRouter = require('./src/router/blog')
    

    在serverHandle中调用该方法,该函数会返回一个promise对象,可以根据该对象的状态来判断是否执行,如果执行了就输出返回给用户。

    2)内部操作

    在路由的内部需要判断的有两点。(1)method,客户端传入的方法是get,post,delete还是put。(2)判断地址。

    (1)method

    可以通过req.method来进行获取。

    (2)判断地址

    这里主要用到的是RESTful API的形式来进行的。
    比如get中的/api/blog 和/api/blog/:id 同属于/api/blog/ 所以这里就需要进行判断。

    const num = req.path.split('/').pop()
    let numParam = true
    if (isNaN(Number(num))) {
        numParam = false
    }
    
    // 获取博客列表
    if (method === 'GET' && req.path === '/api/blog' && !numParam) {
         ...
    }
    
    //// 获取博客详情
    if (method === 'GET' && req.path.indexOf('/api/blog') !== -1 && numParam) {
         ...
    }
    

    numParam是用来判断:id是否为数字的。只有是数字的情况下才能进行查找详情内容。这只是简略的判断,最好还是使用正则来进行判断。
    另外的像post,delete,put就不多说了,其实原理都和get方式的是一样的。

    4.数据库操作

    在介绍完路由之后,客户端就需要在对应的api地址中得到返回,那么路由里面需要执行的就是逻辑代码和进行数据库处理了。并且返回数据了。

    1)配置文件

    首先在根目录下创建conf文件夹,用来存放数据库密码等。
    这个时候就可以用到cross-env了,还记得我们在package.json中配置了这么一段话么?

     "scripts": {
        "dev": "cross-env NODE_ENV=dev nodemon ./bin/www.js"
      }
    

    这个时候就可以用到这个环境变量了。通过生产环境的不同而导出不同的数据库信息,就可以做到在开发和上线的时候,不用频繁变更数据库的信息了。

    //获取环境变量
    let env = process.env.NODE_ENV
    
    let MYSQL_CONF = {}
    
    if (env === 'dev') {
      MYSQL_CONF = {
        host: 'localhost',
        user: 'root',
        password: '',
        port: '3306',
        database: 'myblog'
      }
    }
    
    if (env === 'production') {
       ...
    }
    

    2)配置mysql,编写执行函数

    我们需要在根目录下创建一个db文件夹,用来存放数据库的相关操作。然后通过npm install mysql来对mysql进行安装。
    导入mysql,导入配置文件,然后创建mysql连接实例con。使用con.connect()进行连接数据库。
    通过con.query方法来执行sql语句,这里创建了一个通用的方法导出,方便后续直接使用该方法来执行sql语句。
    con.query第一个为sql语句,第二个为回调函数。

    const mysql = require('mysql')
    
    const { MYSQL_CONF } = require('../conf/db')
    
    const con = mysql.createConnection(MYSQL_CONF)
    
    // 开始连接
    con.connect()
    // 执行sql语句
    function exec(sql) {
      return new Promise((resolve, reject) => {
        con.query(sql, (error, result) => {
          if (error) {
            console.log(error)
            reject(error)
            return
          }
          resolve(result)
        })
      })
    }
    
    

    当进行完这些步骤的时候,我们就可以正式开始业务代码的编写了,只需要在执行完之后返回mysql的exec方法即可。

    5.session和redis

    session和redis主要是用于登录模块,由于有些api需要登录了之后才能查看,比如单独用户的操作,发文章等等。
    session的原理就是在服务器开启的时候使用一个全局变量,然后将登录的信息存入全局变量当中,相当于放在了进程的内存当中,每次用户访问的时候就根据cookie来看在session中是否有信息,有的话就处于登录状态,否则就需要登录。
    弊端:
    (1)服务重启了之后,变量就会消失。
    (2)进程内存有限,访问量过大,内存会暴增。
    (3)上线 之后为多线程,多线程之间内存无法共享。

    由于存在着以上的弊端,所以则需要使用redis来进行存储cookie。redis相当于一个独立的个体,多线程之间也不会出先数据无法共存的情况。而且服务重启的时候,redis还依然在运行。

    1)解析cookie

    判断用户是否登录除了token之外就是使用cookie了。而且cookie可以是服务端在res中添加,并且返回到客户端。客户端就会带上cookie的信息了。
    我们可以通过req.headers.cookie来获取到客户端返回的cookie。然后将其转换成对象。

    req.cookie = {}
    const cookieStr = req.headers.cookie || ''
    cookieStr.split(';').forEach(item => {
        if (!item) {
          return
        }
        const arr = item.split('=')
        const key = arr[0].trim()
        const value = arr[1].trim()
        req.cookie[key] = value
    })
    
    

    而在登录完之后,则需要生成一个userId,然后放在返回的headers当中,这时候客户端就会有该cookie了

    res.setHeader(
      'Set-Cookie',
      `userId=${userId} ; path=/; httpOnly; expires=${getOneDay()}`
    )
    

    几个注意的点:
    (1)cookie做限制
    需要在服务端res.setHeader(’Set-Cookie‘, 'xxx=xxx')上在最后加一句httpOnly,防止被篡改。
    (2)加上时间
    在最后加expires= xxx表示有效期截止时间。

    2)配置redis

    和配置mysql方法差不多,需要在conf文件中配置redis的基本数据。然后编写一个get,set,这里主要用到的只有这两个方法。再将这两个方法导出就可以了。
    conf文件中redis配置

    REDIS_CONF = {
       port: '6379',
       host: '127.0.0.1'
     }
    

    在db文件夹中创建redis文件。创建一个redis实例,监听错误的方法,然后导出get和set方法。
    注:存储的是字符串而不是对象

    const redisClient = redis.createClient(REDIS_CONF.port, REDIS_CONF.host)
    
    redisClient.on('error', function(err) {
      console.log(err)
    })
    
    function set(key, value) {
      if (typeof value === 'object') {
        value = JSON.stringify(value)
      }
      redisClient.set(key, value)
    }
    
    function get(key) {
      return new Promise((resolve, reject) => {
        redisClient.get(key, function(err, value) {
          if (err) {
            reject(err)
          }
          if (!value) {
            resolve(null)
          }
          try {
            resolve(JSON.parse(value))
          } catch (e) {
            resolve(value)
          }
        })
      })
    }
    

    3)存储redis

    当做完配置的工作之后,使用redis大致的步骤就是:
    (1)解析cookie。
    (2)看cookie中是否存在userId
    (3)不存在则创建,存在则去redis中查找是否存在该userId,以此来判断用户是否登录
    (4)最后将结果赋值到req.session中,方便在个人操作时判断req.session的值来看是否能进行操作

      // 处理redis
      let needSetCookie = false
      let userId = req.cookie.userId
      if (!userId) {
        userId = Date.now() + '_' + Math.random()
        needSetCookie = true
      }
      req.sessionId = userId
      let result = await get(req.sessionId)
      if (result == null) {
        set(req.sessionId, {})
        // 设置session
        req.session = {}
      } else {
        req.session = result
      }
    

    然后在登录的时候将req.session中的userId保存到redis中即可。

    const result = await login(username, password)
    console.log(result)
    if (result[0]) {
      // 设置cookie
      req.session.username = result[0].username
      set(req.sessionId, req.session)
    }
    

    6.总结

    在没有使用express和koa的情况下,主要是为了分析express和koa的底层实现原理,主要是为了更好的理解框架本身,在实践过程中还是需要用到express和koa的,毕竟为了项目能快速上线,并不是所有都需要从零开始搭建的。
    至此大致的主体框架已经基本完成了,还剩下的主要就是系统日志的写入和对错误的处理。这一部分通过express和koa的中间件都能很好的进行处理的。

    相关文章

      网友评论

          本文标题:Node.js从零搭建

          本文链接:https://www.haomeiwen.com/subject/reostctx.html