千锋Node.js学习笔记

千锋Node.js学习笔记

写在前面

  • 封面 | 摘要 | 关键词

    封面

    千锋Node.js学习笔记

    Node.js
    李英俊
    前端
    千锋
    express
    
  • 学习链接:千锋HTML5前端开发教程1000集[P287: P427],共141集

  • 感想

    1. [20221008] node.js 这部分讲的真的很难顶,真想直接跳到vue。这个老师思想跳跃很大,讲课基于自己而不是基于受众。且不提我,很多老师体到的东西屏幕里的学生都很懵。前面说着说着就说到一些超纲知识点,说了又不详解,只会说后面会讲,会面会讲就不要体到前面,很多知识点之间跳跃性很大,缺乏逻辑性串讲。
    2. [20221010] 感觉node.js的主讲老师,很多常见的函数api都记不住,动不动就要查,跟着敲代码都要改来改去,脑阔痛。
    3. [20221017] 讲到cors的时候,老师本来想要直接复制代码,还说:“如果这段代码你没有看懂,说明前面的你没有认真听。” 我:???直接上PUA了可还行。
    4. [20221020] 讲fs.stat,这老师的基础真是一言难尽,如果不知道函数的使用和功能,你查也行啊,总不能蒙吧。。。
    5. [20221025] 讲express,中间件栈?明明是队列,自己编了个名字可还行,讲得乱七八糟、不知所云。我的评价是:稀烂。不就是路由匹配的问题嘛?加个next可以使得后面的路由继续被匹配到,这么简单的东西我一看就知道要说啥,真是服了。主动增加学习难度了这是。。。
    6. [20221116] 隔了一段时间了,中间学习的依然很痛苦。现在是学到了项目这一块,真的是想到哪儿写到哪儿。感觉就是做了一次项目就跑过来当老师了,啥也不懂啊这是,重构代码全靠蒙和试是吧。
    7. [20221121] 讲个项目,毫无章法,毫无逻辑,随时随地想重构就重构,想到哪儿说到哪儿,行就是赚到,不行就再找找,屏幕滚来滚去,啥都看不清楚,学尼玛。
    8. [20221215] 讲师的绝招:git add git commit 然后git check,合理。
  • 摘抄

    1. 父元素设置 position: relative;子元素设置 position: absolute; left: 50%; transform: translateX(-50%),可以实现内容的居中显示
    2. 激活当前元素,取消其他兄弟元素的激活:$(this).addClass('active').siblings().removeClass('active')
    3. 直接启动http服务器:npx http-server -p 9000
    4. 启动node.js服务器:nodemon cors.jsnode cors.js
  • 学习时遇到的问题

    1. CSS中的position:relative理解
    2. 『前端大白话』之 “flex:1”
    3. nvm-windows安装教程
    4. 查找第三方模块
    5. npm 全局安装与本地安装、开发依赖和生产依赖
    6. 箭头函数和function的区别
      • this的指向:使用function定义的函数,this的指向随着调用环境的变化而变化,而箭头函数中的this指向是固定不变的,一直指向定义函数的环境。
      • function是可以定义构造函数的,而箭头函数是不行的。
      • 由于js的内存机制,function的级别最高,而用箭头函数定义函数的时候,需要var(let const定义的时候更不必说)关键词,而var所定义的变量不能得到变量提升,故箭头函数一定要定义于调用之前!
      • 使用function声明函数时,可以使用arguments来获取传入函数的所有参数,而使用箭头函数声明的方法无法使用arguments来获取所有参数。
  • PS:相关工程代码都在 Github 上

1. 认识Node.js

  1. Node.js is a JavaScript runtime built on Chrome’s V8 JavaScript engine.

  2. 特性

    Node.js可以解析JS代码,没有浏览器安全级别的限制,提供很多系统级别的API

    • 文件的读写 File System

      const fs = require('fs')
      
      fs.writeFile('./log.text', 'hello', (err, data) => {
          if (err) {
      
          }else{
              console.log('文件创建成功');
          }
      })
      

      运行上述代码:node index.js

    • 进程的管理 Process

      function main(argv){
          console.log(argv);
      }
      
      main(process.argv)
      

      运行:node process.js 1 2

    • 网络通信 HTTP/HTTPS

      const http = require('http')
      
      const server = http.createServer((request, response) => {
          let url = request.url
          response.write(url)
          response.end()
      })
      
      server.listen(8090, 'localhost', ()=>{
          console.log('localhost:8090');
      })
      
    • 。。。

  3. Node相关工具

    1. nvm:Node Version Manager
    2. npm:Node Package Manager
    3. nrm:npm registry manager
    4. npx:npm package extention

2. NVM

  • node.js的版本管理工具,windows不支持,需要安装其他的

    nvm-windows
    nodist
    
  • 查看软件版本:npm view node versions

  • 查看node版本:node -v

  • 查看已安装的node版本:nvm list

  • 切换node版本:nvm user 14.15.0

  • 设置默认版本:nvm alias default 14.15.0

3. NPM

  • 安装全局包:npm install jquery -g(--global)

  • 全局安装包的目录:C:\Users\用户\AppData\Roaming\npm\node_modules

  • 使用package.json可以实现本地包的安装:npm install xxx --save-dev

    • --save:可以替换为 -S
    • --save-dev:可以替换为 -D
    • 这里如果不加 -dev (开发环境),表示的是:将包安装在生产环境中,这样该包的信息会更新到 package.jsondependencies
    • 同理,开发环境的话,会把包的信息放到 devDependencies 键中
    • 最后使用 node i 来安装所有的依赖包
    • 查看特定名称的包:npm list | grep gulp
    • 安装 生产 环境下的包:npm i --production
    • 查看包有哪些版本:npm view jquery versions
    • 安装具体版本的包:npm i jquery@2.2.4
    • 安装某版本最高版本的包:npm i jquery@2
      • MAJOR:表示当前APR的主版本号,它的变化通常意味着APR的巨大的变化,比如体系结构的重新设计,API的重新设计等等,而且这种变化通常会导致APR版本的向前不兼容。
      • MINOR:称之为APR的次版本号,它通常只反映了一些较大的更改,比如APR的API的增加等等,但是这些更改并不影响与旧版本源代码和二进制代码之间的兼容性。
      • PATCH通常称之为补丁版本,通常情况下如果只是对APR函数的修改而不影响API接口的话都会导致PATCH的变化。 目前为止APR的最高版本是1.2.2,最早遵循这种规则的版本号是0.9.0,不过在0.9.0之前,APR还推出了两个版本a8和a9。
        • 如果为奇数:则是不稳定的patch
        • 所以一般某个major的最高版本为偶数patch
      • ^:该配置只锁定major版本号
      • ~:锁定major和minor版本号
      • :什么都不加是最严格的,指定版本号
      • *:最新版本
    • 清空npm缓存:npm cache clean --force
  • loadsh介绍(与underscore是竞品)

    • chunck:数组的分割
  • 自己发布包

    • 写一个函数 myChunk()

    • 暴露函数的接口:module.exports = myChunk

    • 调用:

      const myChunk = require('./index.js')
      console.log(myChunk([4, 5, 6, 7]));
      
    • 发布

      // npm登录
      npm adduser
      // 查看源
      npm config get registry
      // 切换淘宝源(样例,实际要切回官方的源)
      npm config set registry https://registry.npm.taobao.org
      // 切回官方源
      npm config set registry https://registry.npmjs.org
      // 发布
      npm publish
      // 查看当前项目引用了哪些包
      npm ls
      // 卸载包
      npm unpublish --force
      // 引用包
      var hello = require('pg19-npm')
      hello.sayHello()
      
  • package.json 描述文件

    • name:包名称
    • version:版本号
    • description:描述
    • main:暴露接口的主程序
    • scripts:执行时需要执行的脚本
    • respository:项目库(除了可以从npm官网安装,也可以通过github等安装)
    • keywords:关键词
    • author:作者
    • license:证数,一般为MIT
    • bugs:bug链接
    • homepage:主页地址
  • npm脚本

    • npm允许在package.json文件里面,使用scripts字段定义脚本命令

    • npm运行package.json中的脚本

      npm run runjs
      
    • 如果脚本中有多个,使用 &&& 连接

      • &:并行运行脚本
      • &&:串行运行脚本
    • 如果脚本名为 start 、 test 等特殊的名称时,可以省略 runnpm test

    • 获取package.json中的信息:

      console.log(process.env.npm_package_config_env)

      • 其中:congfig为第一级的key,env为二级key,取到的是config.env的值

      • 注意:如果直接获取的是config,是会报错的

      • 该方法只能通过配置package.json后运行里面的脚本才生效,如果直接运行对应的js会直接显示undefined

      • 在脚本内部也可以直接获取package.json的信息

        "scripts": {
        	"build": "echo $npm_package_config_env"
        }
        
    • npm安装git上发布的包

      • npm install git+https://git@xxx
      • npm install git+ssh://git@xxx
  • cross-env

    • windows不支持 NODE_ENV=production 的设置方式

    • 解决:cross-env使得可以使用单个命令,而不必担心为平台正确设置或使用环境变量。这个迷你的包(cross-env)能够提供一个设置环境变量的scripts,让你能够以Unix方式设置环境变量,然后再Windows上也能兼容运行

    • 安装:npm install --save-dev cross-env

    • 可以直接在脚本中设置环境变量的值,比如说

      "scripts": {
      	"dev": "NODE_ENV=development gulp -f gulp.config.js",
      	"prod": "NODE_ENV=production gulp -f gulp.config.js"
      }
      
    • 如果需要使用cross-env

      "scripts": {
      	"dev": "cross-env NODE_ENV=development gulp -f gulp.config.js",
      	"prod": "cross-env NODE_ENV=production gulp -f gulp.config.js"
      }
      

4. NRM

  • npm的镜像源管理工具,有时候国外资源太慢,使用这个就可以快速地在npm源之间切换
  • 安装:npm install -g nrm
  • 查看可选的源:nrm ls
  • 切换nrm:nrm use taobao
  • 测试速度:nrm test

5. NPX

  • npm从5.2开始增加了npx命令,如果没有自带,可以手动安装:npm install -g npx
  • npx想要解决的主要问题,就是调用项目内部安装的模块
    • 需要使用某个库的时候,要么配置package.json后调用里面的脚本,或者进入到node_modules文件夹中找到对应的库,再运行
    • 比如:gulp -v 不行;但是 npx gulp -v 就可以了
    • 如果本地没有这个库,npx会自己下载,但不会在本地/全局安装。其实是安装在临时文件夹中,使用完后自动删除
  • npx --no-install http-server:让npx强制使用本地模块,不下载远程模块,本地不存在就会报错
  • npx --ignore-existing http-server:忽略本地的同名模块,强制安装使用远程模块

6. 模块/包与CommonJS

  1. 分类

    • 内置的模块
    • 第三方的模块
    • 自定义的模块
  2. 浏览器是没有require对象的

  3. 编写暴露的接口

    module.exports = {
    	name,
    	age
    }
    
    • 可以通过这种方式,暴露多个接口

    • 也可以使用如下方式,其中exports是 module.exports 的引用

      exports.name = name
      exports.age = age
      

7. 常用内置模块

1. url

  • url.parse(urlString[, parseQueryString[, slashesDenoteHost]]):将链接解析为一连串的信息

    const url = require('url')
    const urlString = 'https://www.baidu.com:443/path/index.html?id=2#tag=3'
    
    logger.debug(url.parse(urlString));
    
    // 输出
    Url {
      protocol: 'https:',
      slashes: true,
      auth: null,
      host: 'www.baidu.com:443',
      port: '443',
      hostname: 'www.baidu.com',
      hash: '#tag=3',
      search: '?id=2',
      query: 'id=2',
      pathname: '/path/index.html',
      path: '/path/index.html?id=2',
      href: 'https://www.baidu.com:443/path/index.html?id=2#tag=3'
    }
    
  • url.format(urlObject):将结构体的信息转为链接

    logger.debug(url.format(
        {
            protocol: 'https:',
            slashes: true,
            auth: null,
            host: 'www.baidu.com:443',
            port: '443',
            hostname: 'www.baidu.com',
            hash: '#tag=3',
            search: '?id=2',
            query: 'id=2',
            pathname: '/path/index.html',
            path: '/path/index.html?id=2',
            href: 'https://www.baidu.com:443/path/index.html?id=2#tag=3'
        }
    ))
    
    // 输出
    https://www.baidu.com:443/path/index.html?id=2#tag=3
    
  • url.resolve(from, to):实现路径的拼接,前向后向都可以

    logger.debug(url.resolve('http://www.abc.com/a', '/b'))
    logger.debug(url.resolve('http://www.abc.com/a', '../'))
    
    // 输出
    http://www.abc.com/b
    http://www.abc.com/
    
  • URLSearchParams:解析url之后得到数据体,然后获取对应的search(search: '?id=2',

    const urlParams = new URLSearchParams(url.parse(urlString).search)
    logger.debug(urlParams);
    
    // 输出
    URLSearchParams { 'id' => '2' }
    

2. querystring

  • querystring.parse(str[, sep[, eq[, options]]]):参数的解析,将url中的参数部分解析为数据体(对象)

    const querystring = require('querystring')
    var qs = 'x=3&y=4'
    var parsed = querystring.parse(qs)
    console.log(parsed)
    
    // 输出
    [Object: null prototype] { x: '3', y: '4' }
    
  • querystring.stringify(obj[, sep[, eq[, options]]]):将数据体中的参数和值解析为url的参数

    const querystring = require('querystring')
    var qo = {
      x: 3,
      y: 4
    }
    var parsed = querystring.stringify(qo)
    console.log(parsed)
    
    // 输出
    x=3&y=4
    
  • querystring.escape(str):对url的参数进行编码

    const querystring = require('querystring')
    var str = 'id=3&city=北京&url=https://www.baidu.com'
    var escaped = querystring.escape(str)
    console.log(escaped)
    
    // 输出
    id%3D3%26city%3D%E5%8C%97%E4%BA%AC%26url%3Dhttps%3A%2F%2Fwww.baidu.com
    
  • querystring.unescape(str):解码

    const querystring = require('querystring')
    var str = 'id%3D3%26city%3D%E5%8C%97%E4%BA%AC%26url%3Dhttps%3A%2F%2Fwww.baidu.com'
    var unescaped = querystring.unescape(str)
    console.log(unescaped)
    
    // 输出
    id=3&city=北京&url=https://www.baidu.com
    

3. http

  • node的浏览端调试:node --inspect --inspect-brk server.js

  • node进程管理工具:一直监听,如果代码有修改会自动重启

    • supervisor
    • forever
    • nodemon
    • pm2
  • response.end()中也可以写返回的信息

  • get

    const http = require('http')
    const querystring = require('querystring')
    const https = require('https');
    
    const server = http.createServer((request, response) => {
        // console.log(response);
    
        // const url = request.url
        // console.log(url);
    
        https.get('https://www.xiaomiyoupin.com/mtop/mf/cat/list', (result) => {
            let data = ''
            result.on('data', (chunk) => {
                data += chunk
            })
            result.on('end', () => {
                response.writeHead(200, {
                    // 'content-type': 'text/html'
                    'content-type': 'application/json;charset=utf-8'
                })
                // response.write('<div>hello</div>')
                // response.write('{"x": 1}')
                // response.end('{"x": 1}')
                // console.log(data);
                // response.write(`{"url": "${url}"}`)
                response.write(JSON.stringify(querystring.parse(data)))
                response.end()
            })
        })
    })
    
    server.listen(8080, () => {
        console.log('localhost:8080');
    })
    
  • post

    const http = require('http');
    const querystring = require('querystring');
    
    const postData = querystring.stringify({
        province: '上海',
        city: '上海',
        district: '宝山区',
        address: '同济啊吧啊吧',
        latitude: 43.0,
        longitude: 160.0,
        message: '求购一条小鱼',
        contact: '13666666666',
        type: 'sell',
        time: 1571217561
    })
    
    const options = {
        protocol: 'http:',
        hostname: 'localhost',
        method: 'post',
        port: 3000,
        path: '/data',
        headers: {
            'content-type': 'application/x-www-form-urlencoded',
            'Content-Length': Buffer.byteLength(postData),
        }
    }
    
    const server = http.createServer((req, res) => {
        const request = http.request(options, (result) => {
    
        })
        request.write(postData)
        request.end()
    
        res.end()
    })
    
    server.listen(8080, () => {
        console.log('localhost:8080');
    })
    

4. 跨域

  • jsonp利用 script 标签加载js不跨域的特性,从后端拉取js代码运行,jsonp中的p是padding(包裹数据的函数),拿到callback后,传入数据调用函数;cors就是在后台给前端返回一个首部字段:Access-Control-Allow-Origin;middleware:通过http-proxy-middleware实现地址的代理,从而实现了跨域。

jsonp

  • JSON with Padding,是 json 的一种"使用模式",可以让网页从别的域名(网站)那获取资料,即跨域读取数据。

  • 为什么我们从不同的域(网站)访问数据需要一个特殊的技术( JSONP )呢?这是因为 **同源策略 ** 。

  • 同源策略,它是由 Netscape 提出的一个著名的安全策略,现在所有支持 JavaScript 的浏览器都会使用这个策略。所谓同源是指,域名,协议,端口相同。

  • 当一个百度浏览器执行一个脚本的时候会检查这个脚本是属于哪个页面的 即检查是否同源,只有和百度同源的脚本才会被执行。

  • 核心<script> 标签的src属性并不被同源策略所约束,所以可以获取任何服务器上脚本并执行。

  • 案例:通过访问另一个服务器的api,并通过传参的方式把当前的函数传过去,实现了跨域 传参+调用函数

    • 当前html

      <!DOCTYPE html>
      <html lang="en">
      
      <head>
          <meta charset="UTF-8">
          <meta http-equiv="X-UA-Compatible" content="IE=edge">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>jsonp</title>
      </head>
      
      <body>
          <script>
              function getData(data) {
                  console.log(data);
              }
          </script>
          <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.js"></script>
          <script src="http://localhost:8080/api/data?cb=getData"></script>
      </body>
      
      </html>
      
    • html中引用的跨域的Node.js

      const http = require('http')
      const url = require('url')
      
      const server = http.createServer((req, res) => {
          let urlstr = req.url
      
          // true 是为了解析 url中的query为一个字典
          let urlObj = url.parse(urlstr, true)
          switch(urlObj.pathname) {
              case '/api/data':
                  res.write(`${urlObj.query.cb}("hello")`)
                  break
              default:
                  res.write('page not found.')
          }
          res.end()
      })
      
      server.listen(8080, ()=>{
          console.log('localhost:8080');
      })
      

cors

  • 核心:在请求头中加入:'Access-Control-Allow-Origin': '*'

middleware(http-proxy-middleware)

  • 中间件option详解

    var options = {
            target: 'http://www.example.org', // 目标服务器 host
            changeOrigin: true,               // 默认false,是否需要改变原始主机头为目标URL
            ws: true,                         // 是否代理websockets
            pathRewrite: {
                '^/api/old-path' : '/api/new-path',     // 重写请求,比如我们源访问的是api/old-path,那么请求会被解析为/api/new-path
                '^/api/remove/path' : '/path'           // 同上
            },
            router: {
                // 如果请求主机 == 'dev.localhost:3000',
                // 重写目标服务器 'http://www.example.org' 为 'http://localhost:8000'
                'dev.localhost:3000' : 'http://localhost:8000'
            }
        };
    
  • 实操:

    const http = require('http')
    const url = require('url')
    const { createProxyMiddleware } = require('http-proxy-middleware')
    
    const server = http.createServer((req, res) => {
        let urlStr = req.url
        if (/\/api/.test(urlStr)) {
            // console.log(urlStr);
            const proxy = createProxyMiddleware('/api', {
                target: 'https://silkroad.csdn.net/',
                changeOrigin: true
            })
    
            proxy(req, res)
        } else if (/\/aaa/.test(urlStr)) {
            const proxy2 = createProxyMiddleware('/aaa', {
                target: 'https://blog.csdn.net/',
                changeOrigin: true,
                pathRewrite: {
                    '^/aaa': ''
                }
            })
    
            proxy2(req, res)
    
        } 
        else {
            console.log('error');
        }
    })
    
    server.listen(8070, () => {
        console.log('localhost:8070');
    })
    
  • 第一种:通过正则表达式匹配,检测到 api 路由之后进行代理转发给target的字段中,同时 api 的字段以及后面的路由和参数都会保留,进行访问

  • 第二种:通过正则表达式匹配,检测到 aaa 路由之后进行代理转发给target的字段中,同时 aaa 的字段会被重写为 ,之后的路由和参数会保留,进行访问

5. 爬虫

  • 工具:cheerio

  • 样例:爬取魅族官网

    const http = require('http')
    const https = require('https')
    const cheerio = require('cheerio')
    
    function filterData(data) {
        const $ = cheerio.load(data)
        // console.log(data);
    
        $('.section-item-box p').each((index, el) => {
            console.log(index);
            console.log($(el).text());
        })
    }
    
    const server = http.createServer((req, res) => {
        let data = ''
        https.get('https:www.meizu.com', (result) => {
            result.on('data', (chunk) => {
                data += chunk
            })
            result.on('end', () => {
                filterData(data)
            })
        })
    })
    
    server.listen(8080, () => {
        console.log('localhost:8080');
    })
    

6. events

事件触发

const EventEmitter = require('events')

class MyEventEmitter extends EventEmitter { }


const event = new MyEventEmitter()

event.on('play', (value) => {
    console.log(value);
})

event.on('play', (value) => {
    console.log('another' + value);
})


// 触发
event.emit('play', 'movie')
event.emit('play', 'movie')
event.emit('play', 'movie')

7. File System

  • 文件夹操作

    • 增:fs.mkdir
    • 删:fs.rmdir
    • 改:fs.rename
    • 查:fs.readdir
  • 回调是异步的,因为函数是传进去的。

  • 同步的方法需要在后面加上 Sync,比如文件的读取对应的同步函数为:fs.readFileSync

  • 文件操作

    • 增:fs.writeFile

    • 删:fs.unlink

    • 改:fs.appendFile

    • 查:fs.readFile

      • 这样获取到的值是buffer,转换成字符串有两种方法,一种是在参数中增加 utf-8;另一种是在输出时增加 .toString()

        fs.readFile('./logs/log1.log', 'utf-8', (err, result) => {
            console.log(result.toString());
        })
        
  • fs.stat:读取文件(夹)信息,再调用 .isDirectory() 判断是否是文件夹

  • 递归获取文件目录下所有文档的内容

    function readDir(dir) {
        fs.readdir(dir, (err, content) => {
            content.forEach((value, index) => {
                let joinDir = `${dir}/${value}`
                fs.stat(joinDir, (err, stats) => {
                    if (stats.isDirectory()) {
                        readDir(joinDir)
                    } else {
                        fs.readFile(joinDir, 'utf-8', (err, content) => {
                            console.log(content);
                        })
                    }
                })
            })
        })
    }
    
  • fs.watch:监视文件(夹)的变化

8. Stream 和 Zlib

  • 读取流和写入流和压缩流

    const fs = require('fs')
    const zlib = require('zlib');
    
    const gzip = zlib.createGzip()
    
    const readStream = fs.createReadStream('./logs/log1.log')
    const writeStream = fs.createWriteStream('./logs/logs.gzip')
    
    readStream
    .pipe(gzip)
    .pipe(writeStream)
    

9. readline

  • 逐行读入

    const readline = require('readline');
    
    const rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout
    })
    
    rl.question('你如何看到你爹', (answer) => {
        // 记录
        console.log(`thx for your answer: ${answer}`);
        rl.close()
    })
    

10. Crypto

  • 加密模块,既可以做对称加密,也可以做非对称加密

    const crypto = require('crypto');
    const password = 'abc123'
    
    const hash = crypto.createHash('sha1').update(password, 'utf-8').digest('hex')
    
    console.log(hash);
    

8. 路由

  • 通过url的 switch case,实现路由的分发

    const fs = require('fs');
    require('http').createServer((req, res) => {
        // res.end('ok')
        const urlString = req.url
        switch (urlString) {
            case '/':
                res.end('hello')
                break;
            case '/home':
                fs.readFile('./home.html', (err, content) => {
                    res.end(content)
                })
                break;
            case '/home.js':
                fs.readFile('./home.js', (err, content) => {
                    res.end(content)
                })
                break
            case '/pics/a.png':
                fs.readFile('./pics/a.png', (err, content) => {
                    res.end(content)
                })
                break
            default:
                res.end('page 404')
        }
    })
        .listen(8088, () => {
            console.log('localhost: 8088');
        })
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>home</title>
        <script src="home.js"></script>
    </head>
    <body>
        home
        <img src="./pics/a.png" alt="">
    </body>
    </html>
    
  • 上述路由的分发存在一个问题:需要对涉及到的资源也要配上路由分发

  • 利用package:mime,通过文件类型 设置 content-type,自动获取url对应的静态文件

    const fs = require('fs');
    const mime = require('mime')
    
    require('http').createServer((req, res) => {
        const urlString = req.url
        const type = mime.getType(urlString.split('.')[1])
        res.writeHead(200, {
            'content-type': type
        })
    
        const file = fs.readFileSync(`./${urlString}`)
        res.end(file)
    })
        .listen(8088, () => {
            console.log('localhost: 8088');
        })
    

9. 静态资源服务

  • server.js:

    const http = require('http');
    const path = require('path');
    const readStaticFile = require('./fileStatic');
    
    http.createServer(async (req, res) => {
        let urlString = req.url
        let filePathName = path.join(__dirname, './public', urlString)
        console.log(filePathName);
    
        let { data, mimeType } = await readStaticFile(filePathName)
    
        res.writeHead(200, {
            'content-type': `${mimeType}; charset=utf-8`
        })
        res.write(data)
        res.end()
    }).listen(8888, () => {
        console.log('visit success');
    })
    
  • fileStatic.js

    const path = require('path');
    const mime = require('mime');
    const fs = require('fs');
    
    
    function myReadFile(file) {
        return new Promise((resolve, reject) => {
            fs.readFile(file, (err, data) => {
                if (err) {
                    resolve('You visit a folder that index.html not in. / 你访问的文件夹里面没有index.html')
                } else {
                    resolve(data)
                }
            })
        })
    }
    
    async function readStaticFile(filePathName) {
        let ext = path.parse(filePathName).ext
        // 如果前面的值为none,则取后面的值
        let mimeType = mime.getType(ext) || 'text/html'
        let data
    
        // 判断文件是否存在
        if (fs.existsSync(filePathName)) {
            if (ext) {
                // await myReadFile(filePathName).then(result => data = result)
                //     .catch((err) => data = err)
                data = await myReadFile(filePathName)
            } else {
                // await myReadFile(path.join(filePathName, '/index.html')).then(result => data = result)
                //     .catch((err) => data = err)
                data = await myReadFile(path.join(filePathName, '/index.html'))
            }
        } else {
            // console.log('file is not found');
            // res.end('file not found')
            data = 'file or folder not found'
        }
    
        return {
            data,
            mimeType
        }
    }
    
    module.exports = readStaticFile
    

10. Yarn

  • Yarn是facebook发布的一款取代npm的包管理工具

    • 速度超快:Yarn 缓存了每个下载过的包,所以再次使用时无需重复下载。 同时利用并行下载以最大化资源利用率,因此安装速度更快。
    • 超级安全:在执行代码之前,Yarn 会通过算法校验每个安装包的完整性。
    • 超级可靠:使用详细、简洁的锁文件格式和明确的安装算法,Yarn 能够保证在不同系统上无差异的工作。
  • 参考链接:https://blog.csdn.net/yw00yw/article/details/81354533

  • yarn init

  • yarn add

  • yarn add [–-dev –-peer -–optional]

  • peerDependencies:同等依赖,也叫同伴依赖,用于指定当前包兼容的宿主版本

  • optionalDependencies:可选依赖,如果有一些依赖包即使安装失败,项目仍然能够运行或者希望npm继续运行,就可以使用。会覆盖 dependencies 中的同名依赖包,不要在两个地方都写

  • bundledDependencies/bundleDependencies

  • yarn 源:yarn config get

11. Express

  • 基于Node.js 平台,快速、开放、极简的Web开发框架

  • 创建Express项目

    • 初始化:yarn init -y,这里y是yes,只直接跳过交互式对话初始化一个项目
    • 安装生产环境的包:yarn add express -S
  • 之前是 res.write(); res.end(),express是 res.send()

  • 注意:这里路由的匹配,也是从前往后匹配。//api为例,将永远匹配不到后者;解决方案上,在代码中将 /api 放在 / 的前面

  • 回调函数又被称为中间件

    • 自定义中间件:自己用use写回调函数, 自己调用next
    • 路由中间件:express.Router()
    • 第三方中间件:body-parser
    • 静态资源服务中间件(内置中间件):app.use(express.static('public'))
  • 通过给中间件增加 next() 的参数和函数,使得后续的路由依然可以被匹配和执行

  • MVC -> MVP:Model-View-Presenter。它们的基本思想有相通的地方:Controller/Presenter负责逻辑的处理,Model提供数据,View负责显示。在MVP里,Presenter完全把Model和View进行了分离,主要的程序逻辑在Presenter里实现。

    • View与Model完全隔离
    • Presenter与View的具体实现技术无关
    • 可以进行View的模拟测试
    • RMVP:R->route,路由
  • express路由配置

    • server.js

      const express = require('express');
      const app = express()
      
      const router = require('./router/index');
      
      app.use('/', router)
      
      app.listen(8088, () => {
          console.log('assaas');
      })
      
    • router/index.js

      const express = require('express');
      // 路由中间件
      const router = express.Router()
      
      router.get('/', (req, res, next) => {
          res.send('hello wwww')
      })
      
      router.get('/index', (req, res, next) => {
          res.send('index pages')
      })
      
      module.exports = router
      
  • 获取前端数据

    • get:const query = req.query,获取数据

    • post:添加数据

      const bodyParser = require('body-parser');
      const router = require('./router/index');
      app.use(bodyParser.urlencoded({ extended: false }))
      app.use('/', router)
      
      
      router.post('/index', (req, res, next) => {
          const data = req.body
          console.log(data);
          res.send(data)
      })
      
    • put、patch、delete:覆盖式修改数据、增量修改数据、删除数据

    • router.all:接收上述所有的请求

    • 工具:body-parser

      • 解析form:app.use(bodyParser.urlencoded({ extended: false }))
      • 解析json:app.use(bodyParser.json())
  • 服务端渲染与客户端渲染

    • express template
      • ejs
      • pug
      • jade
      • art-template
    • 页面 render
      • SSR:Server Side Render,服务端渲染
      • CSR:Client Side Render,客户端渲染
  • art-template

    • install:yarn add art-template express-art-template -S
  • CMS:Content Management System,内容管理系统

12. MongoDB

  • MongoDB是一个基于分布式文件存储的数据库。由 C++ 语言编写。旨在为WEB应用提供可扩展的高性能数据存储解决方案。

  • 特点:高性能、易部署、易使用,存储数据非常方便。

  • MongoDB的数据类型

    在这里插入图片描述

  • 数据库的常用命令

    在这里插入图片描述

  • mongodb启动服务:mongod --dbpath D:\mdb_database,后面的路径是数据存放的路径

  • mongodb shell:开启另一个cmd,mongodb

  • 注意:删除数据库:db.dropDatabase() ,是删除当前打开的数据库

  • 集合操作:

    • db.createCollection('collextionName'):创建一个集合
    • db.getCollection('account'):得到指定名称的集合
    • db.getCollectionNames():得到当前db的所有集合
    • db.printCollectionStats():显示当前db所有集合的状态
  • 文档操作

    • 插入数据:

      • db.mabaoguo.insert([{name: 'm1', release: '2020-12-05'}])
      • db.mabaoguo.insert([{name: 'm2', release: '2020-12-06'}, {name: 'm3', release: '2020-12-07'}])
      • db.mabaoguo.save([{name: 'm4', release: '2020-12-06'}, {name: 'm5', release: '2020-12-07'}])
      • db.mabaoguo.insert([{name: 'm1', release: '2020-12-05', publishNum: 100}])
    • 修改数据:

      • db.mabaoguo.update({name: 'm1'}, {$set: {release: '2022-11-11'}}):修改name为m1的release为2022-11-11,默认只修改第一个找到的
      • db.mabaoguo.update({name: 'm1'}, {$inc: {publishNum: 200}}):找到第一条符合的记录,然后增加记录中对应字段publishNum的值+200
      • db.mabaoguo.update({name: 'm12'}, {$inc: {publishNum: 200}}, true):后面的true是:找不到就创建该条数据;如果是false,就无事发生
      • db.mabaoguo.update({name: 'm1'}, {$inc: {publishNum: 200}}, true, true):最后的true是:是否匹配所有,例子中的代码就是找到所有符合条件的,然后都加200
    • 删除数据:

      • db.mabaoguo.remove({name: 'm12'})
    • 查询数据:

      在这里插入图片描述

      在这里插入图片描述

      在这里插入图片描述

      • db.mabaoguo.find() 查看所有数据
      • db.mabaoguo.distinct('name') 查看name字段的去重数据
      • db.mabaoguo.find({release: '2020-12-06'}) 查询满足条件的数据
      • db.mabaoguo.find({release: {$gt: '2020-12-06'}}) 查询数据大于该值的数据;所以小于就是 lt;大于等于是 gte
      • db.mabaoguo.find({release: {$gt: '2020-12-05', $lt: '2020-12-07'}}) 查询区间内的数据
      • db.mabaoguo.find({name: /1/}) 查询name中包含1的 类似%1%
      • db.mabaoguo.find({name: /^1/}) 查询name中包含1,且1开头的,类似 1%;同样的 /1$/,则是类似 %1
      • db.mabaoguo.find({}, {_id: 0, publishNum: 0}) 查询结果中不显示id和publishNum
      • db.mabaoguo.find({name: /1$/}, {_id: 0, publishNum: 0}) 查询name以1结尾的数据
      • db.mabaoguo.find().sort({release: 1}) 排序,按照对应字段的 1:升序;-1:降序 排序
      • db.mabaoguo.find().limit(3) 只要查询结果的前3条数据
      • db.mabaoguo.find().limit(3).skip(2) 查询结果跳过前2条后取前3条数据
      • 无论 find limit sort 的顺序如何,都是先sort后find最后limit
      • db.mabaoguo.find({$or: [{release: '2020-12-05'}, {release: '2020-12-07'}]}) or 或 条件查询
      • db.mabaoguo.findOne() 获取第一条记录
      • db.mabaoguo.find().count() 输出结果集的记录数
    • 注意:

      • 创建表的时候(schema)会自动给表名加s

13. JWT基础

  • cookie和session:前端存cookie,后端存session,通过cookie和session对比或者dict[cookie] == session 来判断用户信息

  • token:第一次访问时,后端返回给前端,之后每一次前端访问都带token,通过对token的验证了判断用户信息

  • jwt:生成token的方法,json web token

  • 密钥生成

  • openssl

  • 生成私钥:genrsa -out rsa_private_key.pem 2048

  • 根据私钥生成公钥:rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem

  • 加密解密:

    var template = require('art-template');
    var path = require('path');
    var fs = require('fs');
    var jwt = require('jsonwebtoken');
    
    const listModel = require('../model/list');
    
    const list = (req, res, next) => {
        // let data = '<ul>'
        // for (let i = 0; i < 100; i++) {
        //     data += `<li>line ${i}</li>`
        // }
        
        // data += '</ul>'
        // res.send(data)
    
        // let dataObj = {
        //     ret: true,
        //     data: []
        // }
        // for(var i = 0; i<100; i++) {
        //     dataObj.data.push('line' + i)
        // }
        // res.send(dataObj)
    
        // let dataArray = []
        // for (let i = 0; i < 1000; i++) {
        //     dataArray.push('line' + i)
        // }
    
        // res.set('content-type', 'application/json;charset=utf-8')
        // res.header('Content-Type', 'application/json;charset=utf-8')
    
        // res.render('list', {
        //     data: JSON.stringify(dataArray)
        // })
    
        // res.render('list-html', {
        //     data: dataArray
        // })
    
        var html = template(path.join(__dirname, '../view/list-html.art'), {
            data: listModel.dataArray
        })
        fs.writeFileSync(path.join(__dirname, '../public/list.html'), html)
        // console.log(html);
        res.send(html)
    }
    
    const token = (req, res, next) => {
        // res.send('ok')
    
        // 对称加密
        const token = jwt.sign({username: 'admin'}, 'hahaha')
        // res.send(token)
        const result = jwt.verify(token, 'hahaha')
        // res.send(result)
    
        // 非对称加密
        const privateKey = fs.readFileSync(path.join(__dirname, '../keys/rsa_private_key.pem'))
        const tk = jwt.sign({username: 'admin'}, privateKey, {algorithm: 'RS256'})
        const publicKey = fs.readFileSync(path.join(__dirname, '../keys/rsa_public_key.pem'))
        const result2 = jwt.verify(tk, publicKey)
        res.send(result2)
    
    }
        
    exports.list = list
    exports.token = token
    

14. Socket

  • 网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket

    在这里插入图片描述

  • 实现

    • 基于net模块实现socket
    • WebSocket
      • 缺点:浏览器必须支持html5
    • Socket.io
      • 优点:可以兼容ie8

15. Node.js项目

  1. 前端 Frontend

    • 前端工程化环境 webpack
    • CSS 预处理工具 sass
    • JS库 jQuery
    • SPA:single page application,路由:SME-Router
    • JS模块化:ES Module,CommonJS Module
    • UI 组件库:Bootstrap(AdminLTE)
    • RMVC:Art-template
    • 常用bootstrap框架
    • 启动:npm run dev
  2. 后端 Backend

    • Node.js
    • Express
    • MongoDB(Mongoose)
    • 启动:yarn start
  3. 开发架构

    • 前后端分离的开发架构
  4. 笔记

    • 自定义首字段是以 x 开头的,比如把token放在头部字段中

    • 翻页点一次会执行多次是因为分页功能中绑定了多次,每次触发分页都会绑定一次。所以需要在绑定事件中,对第一个点击事件进行解绑,即:

      $('#users-page').off('click').on('click', ...)

    • str转int:~~temp


  • ☁️ 我的CSDN:https://blog.csdn.net/qq_21579045/
  • ❄️ 我的博客园:https://www.cnblogs.com/lyjun/
  • ☀️ 我的Github:https://github.com/TinyHandsome/
  • 🌈 我的bilibili:https://space.bilibili.com/8182822/
  • 🥑 我的思否:https://segmentfault.com/u/liyj/
  • 🍅 我的知乎:https://www.zhihu.com/people/lyjun_/
  • 🥔 我的豆瓣:https://www.douban.com/people/lyjun_/
  • 🐧 粉丝交流群:1060163543,神秘暗号:为干饭而来

碌碌谋生,谋其所爱。🌊 @李英俊小朋友

posted @ 2023-01-18 16:16  李英俊小朋友  阅读(127)  评论(0编辑  收藏  举报