Nodejs 相关知识

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

Nodejs是一个js运行环境可以让js开发后端程序实现几乎其他后端语言实现的所有功能能够让js与其他后端语言平起平坐。

nodejs是基于v8引擎v8是Google发布的开源js引擎本身就是用于chrome浏览器的js解释部分现在把v8转移到服务器上用于做服务器端的软件。

Nodejs超强的高并发能力能够实现高性能服务器

node环境

只有v8引擎解析js没有dom和bom对象。

浏览器环境

Blink起到排版作用对html/css进行解析确定每个元素位置

V8解析js

CommonJS规范

我们可以把公共功能抽离成一个单独的js文件作为一个模块默认情况下这个里面的方法或者属性外面是没法访问的如果要外部可以访问模块里的方法或者属性就必须在模块里面通过exports 或者 module.exports 暴露属性或者方法。

module.exports = {test,a}

module.exports = test

//可以一个一个的多个导出
exports.test = test 
exports.a = a

const a = require('./test.js')

npm

npm init

npm install/i -g

npm install/i --save -dev

npm list -g列举目录下的安装包

npm info 包名

npm i/install md5@1指定安装版本

npm outdated检查包是否已过时

--save添加在dependencies

-dev/-D添加在devDependencies

-g全局安装

依赖版本前的符号

'^2.1.0' ^表示会安装 2.*.* 的最新版本

'~2.1.0' ^表示会安装 2.1.* 的最新版本

'*' ^表示会安装最新版本

nrm

nrm是npm的镜像源管理工具国外资源太慢可以使用这个在npm源间切换手动切换

npm config set registry https://registry.npm.taobao.org

 全局安装 nrm

npm i -g nrm

使用nrm

执行命令nrm ls 查看可选源 其中带有 * 的是当前使用源上面的输出表明是官方源。

切换nrm

切到taobao源

npm use taobao

测试速度 

测试相应源的响应时间

nrm test

查看当前的仓库 

npm config get registry

 中国npm镜像

这是一个完整的npmjs.org镜像你可以用此代替官方版本只读同步频率目前为10分钟一次与官方服务同步之后可以使用 cnpm 下载。

npm i -g cnpm --registry=https://registry.npmmirror.com

yarn 

npm install -g yarn

比npm快yarn 缓存每个下载过的包所以再次使用无须重复下载同时利用并行下载以最大化资源利用率因此安装速度更快。

安全性执行代码前yarn 会通过算法检验每个安装包的完整性。

开始新项目yarn init

添加依赖yarn add 包名 |  yarn add 包名@版本 | yarn add 包名 --dev

升级依赖包yarn upgrade 包名@版本

移除依赖包yarn remove 包名

安装全部依赖yarn install

 ES模块化写法

在package.json中加入type选项即可使用es的模块化写法。

npm init 

{
  "name": "aaa",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "type": "module",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "jerry",
  "license": "ISC"
}
//暴露出 引入
export default obj => import obj from './1.js'

export const demo = 'hello' => import { demo } from './2/js'

 内置模块

http模块

创建服务器node app.js 启动服务器

全局安装 nodemon 自动重启node-dev也可以

npm i -g nodemon / npm i -g node-dev 

启动服务器

nodemon app.js 

const http = require('http')
console.log(http);

const server = http.createServer((req, res) => {

    res.writeHead(200, { 'content-type': 'application/json;charset=utf-8' });
    //通过访问不同的url发送不同的数据
    res.end(JSON.stringify(render(req.url)))
})

server.listen(8000, () => {
    console.log('start');
})
function render(url) {
    switch (url) {
        case '/home':

            return { data: 'home' }

        case '/about':
            return {
                data: 'about'
            }
        default:
            return 'index'
    }
}
JsonP 

解决跨域的办法之一后端直接返回一个函数并且执行前提是在html文件有已经定义好的函数前后端保持一致。

const http = require('http')
const url = require('url')

const server = http.createServer((req, res) => {

    res.writeHead(200, { 'content-type': 'application/json;charset=utf-8' });
    //通过访问不同的url发送不同的数据
    const {pathname,query} = url.parse(req.url,true) //http://localhost:8000/home?callback=test2
    res.end(`${query.callback}(${JSON.stringify(render(pathname))})`)
})

server.listen(8000, () => {
    console.log('start');
})

function render(url) {
    switch (url) {
        case '/home':

            return { data: 'home' }

        case '/about':
            return {
                data: 'about'
            }
        default:
            return 'index'
    }
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    
</body>
<script>
    function test(param){
        console.log(param);
    }
    function test2(param){
        console.log(param);
    }
</script>
<script src="http://localhost:8000/home?callback=test2"></script>
</html>
 cors

添加cors头解决跨域

const http = require('http')
const url = require('url')

const server = http.createServer((req, res) => {

    res.writeHead(200, { 'content-type': 'application/json;charset=utf-8', 'access-control-allow-origin': '*' });
    //通过访问不同的url发送不同的数据
    const { pathname, query } = url.parse(req.url, true) //http://localhost:8000/home
    console.log(query);
    res.end(JSON.stringify(render(pathname)))
})

server.listen(8000, () => {
    console.log('start');
})

function render(url) {
    switch (url) {
        case '/home':

            return { data: 'home' }

        case '/about':
            return {
                data: 'about'
            }
        default:
            return 'index'
    }
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    
</body>
<script>
    fetch('http://localhost:8000/home').then(res=>res.json()).then(res=>{
        console.log(res);
    })
</script>
</html>
 get

http既可以做服务端也可以做客户端可以解决跨域成为代理服务

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    
</body>
<script>
    fetch('http://localhost:8000/home').then(res=>res.json()).then(res=>{
        console.log(res);
    })
</script>
</html>
const http = require('http')
const https = require('https')
const url = require('url')

const server = http.createServer((req, res) => {

    res.writeHead(200, { 'content-type': 'application/json;charset=utf-8', 'access-control-allow-origin': '*' });
    //通过访问不同的url发送不同的数据
    const { pathname, query } = url.parse(req.url, true) //http://localhost:8000/home
    switch (pathname) {
        case '/home':
            httpget((data)=>{
                res.end(data)
            })
        case '/about':
            return {
                data: 'about'
            }
        default:
            return 'index'
    }
})

server.listen(8000, () => {
    console.log('start');
})


function httpget(callback){
    let data = ''
    //https://api.douban.com/v2/movie/in_theaters
    https.get('https://i.maoyan.com/api/mmdb/movie/v3/list/hot.json',res=>{
        res.on('data',chunk=>{
            data+=chunk
        })
        res.on('end',()=>{
            callback(data)
        })
    })
}
 post
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    
</body>
<script>
    fetch('http://localhost:8000/home').then(res=>res.json()).then(res=>{
        console.log(res);
    })
</script>
</html>
const http = require('http')
const https = require('https')
const url = require('url')

const server = http.createServer((req, res) => {

    res.writeHead(200, { 'content-type': 'application/json;charset=utf-8', 'access-control-allow-origin': '*' });
    //通过访问不同的url发送不同的数据
    const { pathname, query } = url.parse(req.url, true) //http://localhost:8000/home
    switch (pathname) {
        case '/home':
            httppost((data)=>{
                res.end(data)
            })
        case '/about':
            return {
                data: 'about'
            }
        default:
            return 'index'
    }
})

server.listen(8000, () => {
    console.log('start');
})


function httppost(callback){
    let data = ''
    //'https://m.xiaomiyoupin.com/mtop/market/search/placeHolder'
    //请求的信息
    let options={
        hostname:'m.xiaomiyoupin.com',
        port:"443",
        path:"/mtop/market/search/placeHolder",
        method: "POST",
        headers:{
            'Content-Type':"application/json"
            //'Content-Type':"x-www-form-urlencoded" 这种对应传参req.write("name=zhangsan&age=6")
        }
    }
    let req = https.request(options,res=>{
        res.on('data',chunk=>{
            data+=chunk
        })
        res.on('end',()=>{
            callback(data)
        })
    })
    req.write(JSON.stringify([{},{"baseParam":{"ypClient":1}}]))
    req.end()
}

爬虫 

可以直接爬出网页调用接口可以拿到网页的html结构数据。然后通过过滤出有效的信息返回给前端。需要工具 cheerio用法类似于jQuery

npm i --save cheerio

const http = require('http')
const https = require('https')
const url = require('url')
const cheerio = require('cheerio')

const server = http.createServer((req, res) => {

    res.writeHead(200, { 'content-type': 'application/json;charset=utf-8', 'access-control-allow-origin': '*' });
    //通过访问不同的url发送不同的数据
    const { pathname, query } = url.parse(req.url, true) //http://localhost:8000/home
    switch (pathname) {
        case '/home':
            httpget((data) => {
                res.end(spider(data))
            })
        case '/about':
            return {
                data: 'about'
            }
        default:
            return 'index'
    }
})

server.listen(8000, () => {
    console.log('start');
})


function httpget(callback) {
    let data = ''
    //https://api.douban.com/v2/movie/in_theaters
    https.get('https://i.maoyan.com/?requestCode=32779c7926dc56716d208cc297ee8da1z5u0y', res => {
        res.on('data', chunk => {
            data += chunk
        })
        res.on('end', () => {
            callback(data)
        })
    })
}

function spider(data) {
    let $ = cheerio.load(data)
    //通过选择器来定位到元素
    let $movielist = $(".column.content")
    let movies = []
    $movielist.each((index, value) => {
        movies.push({ title: $(value).find('.title').text(), grade: $(value).find('.grade').text(),actor:  $(value).find('.actor').text()})
    })
    console.log($movielist);
    console.log(movies);
    return JSON.stringify(data)
}

url模块

parse和format旧版

parse在使用查询字符串的方式请求时可以用来解析带有查询字符串的请求。两个参数url传入的url第二个是否将参数解析为对象格式默认是falsepathname是对应的urlquery是请求的请求参数对象。

format与parse正好相对立将url.parse解析的对象转为对应的url地址

const http = require('http')
const url = require('url')

const server = http.createServer((req, res) => {

    res.writeHead(200, { 'content-type': 'application/json;charset=utf-8' });
    //通过访问不同的url发送不同的数据
    //   {
    //     protocol: null,
    //     slashes: null,
    //     auth: null,
    //     host: null,
    //     port: null,
    //     hostname: null,
    //     hash: null,
    //     search: '?a=1&b=2',
    //     query: [Object: null prototype] { a: '1', b: '2' },
    //     pathname: '/home',
    //     path: '/home?a=1&b=2',
    //     href: '/home?a=1&b=2'
    //   }
    const {pathname,query} = url.parse(req.url,true) //http://localhost:8000/home?a=1&b=2
    console.log(url.format(url.parse(req.url,true))); //格式化为url地址
    res.end(JSON.stringify(render(pathname)))
})

server.listen(8000, () => {
    console.log('start');
})

function render(url) {
    switch (url) {
        case '/home':

            return { data: 'home' }

        case '/about':
            return {
                data: 'about'
            }
        default:
            return 'index'
    }
}
resolve 旧版

用于拼接url

const url = require('url')
let a = url.resolve('1/2/3/','4') // '1/2/3/4'
let b = url.resolve('1/2/3','4') // '1/2/4'

let c = url.resolve('http://baidu.com/','/4') // 'http://baidu.com/4'域名后的所有都被替换
let d = url.resolve('http://baidu.com/1','/4') //'http://baidu.com/4'
URL对象新

也是用于获取请求的url地址以及参数

const http = require('http')
const url = require('url')

const server = http.createServer((req, res) => {

    res.writeHead(200, { 'content-type': 'application/json;charset=utf-8' });
    //通过访问不同的url发送不同的数据
    const myURL = new URL(req.url,'http://127.0.0.1:3000') //第二个参数必填合法地址这里取本地因为这里请求的是本地
    console.log(myURL);
    // {
    //     href: 'http://127.0.0.1:3000/home?a=1&b=2',
    //     origin: 'http://127.0.0.1:3000',
    //     protocol: 'http:',
    //     username: '',
    //     password: '',
    //     host: '127.0.0.1:3000',
    //     hostname: '127.0.0.1',
    //     port: '3000',
    //     pathname: '/home',
    //     search: '?a=1&b=2',
    //     searchParams: URLSearchParams { 'a' => '1', 'b' => '2' },
    //     hash: ''
    //   }
    const pathname = myURL.pathname
    res.end(JSON.stringify(render(pathname)))
})

server.listen(8000, () => {
    console.log('start');
})

function render(url) {
    switch (url) {
        case '/home':

            return { data: 'home' }

        case '/about':
            return {
                data: 'about'
            }
        default:
            return 'index'
    }
}
URLSearchParams类

提供对 URL 查询的读写访问在全局对象上也可用WHATWG URLSearchParams 接口和querystring 模块有相似的用途但querystring模块用途更广因为它允许自定义的分隔符& 和 =此api纯粹是为网址查询字符串设计的。

    const myURL = new URL("https://example.org/?abc=123")
    console.log(myURL.searchParams.get('abc')); //123

    myURL.searchParams.append('www','xyz')
    console.log(myURL.href); //"https://example.org/?abc=123&www=xyz"

    myURL.searchParams.delete('abc')
    myURL.searchParams.set('a','b')
    console.log(myURL.href) //"https://example.org/?a=b"

    const newSearchParams = new URLSearchParams(myURL.searchParams)
    console.log(newSearchParams); // { 'www' => 'xyz', 'a' => 'b' }

    newSearchParams.append('a','c')
    console.log(myURL.href); //https://example.org/?a=b
    console.log(newSearchParams.toString());//www=xyz&a=b&a=c

拼接作用

 let b = new URL('one',"https://example.org/?abc=123")
 console.log(b.href); //https://example.org/one

详情参考URL | Node.js v19 API

format新

参数

urlWHATWG 网址对象

optionsauth<boolean> 如果序列化的网址字符串包含用户名和密码则为true否则为false默认值true。

fragment<boolean>如果序列化的网址字符串包含片段则为true否则为false默认值true。

search<boolean>如果序列化的网址字符串包含搜索查询则为true否则为false默认值true。

unicode<boolean>如果序列化的网址字符串的主机组件中的unicode字符应该被直接编码而不是Punycode编码默认值false。

返回值<string>

返回 WHATWG 网址 对象的网址 String 表示的可自定义的序列化。

网址对象具有 toString方法和href属性用于返回网址的字符串序列化。但是这些都不能以任何方式自定义。url.format(url,[options])方法允许对输出进行基本的自定义。

    const myURL = new URL("https://a:b@测试?abc#foo")
    console.log(url.format(myURL));//https://a:b@xn--0zwm56d/?abc#foo 编译了unicode码
    console.log(url.format(myURL,{unicode:true}));//https://a:b@测试/?abc#foo 保持unicode码
    console.log(url.format(myURL,{unicode:true,auth:false}));//https://测试/?abc#foo 去除了a:b
    console.log(url.format(myURL,{unicode:true,fragment:false}));//https://a:b@测试/?abc 去除了#后面的内容
    console.log(url.format(myURL,{unicode:true,search:false}));//https://a:b@测试/#foo 去除了?后面的内容到#结束
url.fileURLToPath(url) 和 url.pathToFileURL(path)

url.fileURLToPath(url)  新增于10.12

参数url<URL> | <string> 要转换为路径的文件网址字符串或网址对象必须绝对路径

返回<string> 完全解析的特定于平台的node.js文件路径。

此函数可确保正确解码宝粉笔编码字符并确保跨平台有效的绝对路径字符串。

 new URL的方式对于文件路径是有问题的所以采用fileURLToPath。

    console.log(url.fileURLToPath('file:///C:/path/')) // /C:/path/
    console.log(new URL('file:///C:/path/').pathname) // /C:/path/

    console.log(url.fileURLToPath('file://nas/foo.txt')) // //nas//foo.txt(Windows)
    console.log(new URL('file://nas/foo.txt').pathname) // /foo.txt

    console.log(url.fileURLToPath('file:///你好.txt')) // /你好.txt(POSIX)
    console.log(new URL('file:///你好.txt').pathname) // /%E4%BD%A0%E5%A5%BD.txt

    console.log(url.fileURLToPath('file:///hello world')) // /hello world (POSIX)
    console.log(new URL('file:///hello world').pathname) // /hello%20worldconst _

 url.pathToFileURL(path) 新增于10.12

参数path<string> 要转为文件网址的路径

返回<URL> 文件网址对象

该函数确保path被绝对解析并且在转换为文件网址时正确编码网址控制字符。

import {pathToFileURL} from 'url'

new URL('/foo#1','file')  //错误file:///foo#1
pathToFileURL('/foo#1')   //正确file:///foo%231(POSIX)

new URL('/some/path%.c','file')  //错误file:///some/path%.c
pathToFileURL('/some/path%.c')   //正确file:///some/path%25.c(POSIX)

 url.urlToHttpOptions(url)

参数url<url> 要转换为选项对象的 WHATWG对象。

返回<Object>选项对象

protocal <string> 使用的协议

hostname <string> 向其发出请求的服务器域名或ip地址

hash <string> 网址的片段部分

search <string> 网址的序列化的查询部分

pathname <string> 网址的路径部分

path <string> 请求的绝对路径。应包括查询字符串如果有。当请求路径包含非法字符时抛出异常。目前只有空格被拒绝。

href <string> 序列化的网址

port <number> 远程服务器的端口

auth <string> 基本身份验证

该实用函数按照http.request() 和 https.request() API的预期将网址对象转换为普通选项对象。

    const url = require('url')
    const myURL = new URL("https://a:b@测试?abc#foo")
    console.log(url.urlToHttpOptions(myURL));
    // {
    //     protocol: 'https:',
    //     hostname: 'xn--0zwm56d',
    //     hash: '#foo',
    //     search: '?abc',
    //     pathname: '/',
    //     path: '/?abc',
    //     href: 'https://a:b@xn--0zwm56d/?abc#foo',
    //     auth: 'a:b'
    //   }

querystring模块

parse解析

const querystring = require('querystring')

let str = 'name=zhangsan&age=12'
let obj = querystring.parse(str) 
console.log(obj); //{ name: 'zhangsan', age: '12' }

stringify编码

const querystring = require('querystring')

let obj = { name: 'zhangsan', age: '12' }
let str = querystring.stringify(obj)
console.log(str); //name=zhangsan&age=12

escape 转译特殊字符

const querystring = require('querystring')

let str = 'name=zhangsan&age=8&url=https://baidu.com'
let escape = querystring.escape(str)
console.log(escape); //name%3Dzhangsan%26age%3D8%26url%3Dhttps%3A%2F%2Fbaidu.com

unescape 解码特殊字符

const querystring = require('querystring')

let str = 'name%3Dzhangsan%26age%3D8%26url%3Dhttps%3A%2F%2Fbaidu.com'
let unescape = querystring.unescape(str)
console.log(unescape); //name=zhangsan&age=8&url=https://baidu.com

event 模块

类似于发布订阅使用event模块将之前的get方法做一下优化需要引入EventEmitter对象对event添加监听的方法在数据处理好后抛出并发送个给前端。

const http = require('http')
const https = require('https')
const { EventEmitter } = require('events')
const url = require('url')
let event = null
const server = http.createServer((req, res) => {

    res.writeHead(200, { 'content-type': 'application/json;charset=utf-8', 'access-control-allow-origin': '*' });
    //通过访问不同的url发送不同的数据
    const { pathname, query } = url.parse(req.url, true) //http://localhost:8000/home
    switch (pathname) {
        case '/home':
            event = new EventEmitter()
            event.on('play',(data)=>{
                res.end(data)
            })
            httpget()
        case '/about':
            return {
                data: 'about'
            }
        default:
            return 'index'
    }
})

server.listen(8000, () => {
    console.log('start');
})


function httpget(){
    let data = ''
    //https://api.douban.com/v2/movie/in_theaters
    https.get('https://i.maoyan.com/api/mmdb/movie/v3/list/hot.json',res=>{
        res.on('data',chunk=>{
            data+=chunk
        })
        res.on('end',()=>{
            event.emit('play',data)
        })
    })
}

fs文件操作模块

包括同步和异步的方法同步会阻塞进程需要搭配 try catch 语句来进行捕获错误。

同步方法在异步方法后面添加Sync即可由于node环境执行的js代码是服务器代码所以绝大部分需要在服务器运行期反复执行业务逻辑的代码必须使用异步否则同步代码在执行时期服务器停止响应js只有一个执行线程。

服务器启动时如果需要读取配置文件或者结束时需要写入到状态文件时可以使用同步代码因为这些代码只在启动和结束时执行一次不影响服务器正常运行时的异步操作。

但是在异步方法中容易产生回调地狱可以使用promise的写法

const fs = require('fs').promises

fs.mkdir('./avater').then(res=>{
    console.log(res);
}).catch(err=>{
    console.log(err);
})
创建目录
const fs = require('fs')
//创建文件夹
fs.mkdir('./avater',err=>{
    if(err && err.code == "EEXIST"){
        console.log("文件夹已存在");
    }
})

//同步写法
try {
    fs.mkdirSync('./avater2')
} catch (error) {
    console.log(error);
}
修改目录名称
const fs = require('fs')
//修改文件夹名称
fs.rename('./avater','./avater2',err=>{
    if(err && err.code == "ENOENT"){
        console.log("当前文件夹不存在");
    }
})
创建新的文件写入内容是覆盖之前的内容
const fs = require('fs')
//创建文件
fs.writeFile('./avater2/a.txt','hello world',err=>{
    console.log(err);
})
 删除文件夹
//删除文件夹
fs.rmdir('./avater2',err=>{
    console.log(err)
})
追加文件内容
// 追加内容
fs.appendFile('./avater2/a.txt','\n你好',err=>{
    console.log(err);
})
读取文件内容
//读取文件
fs.readFile("./avater2/a.txt",'utf-8',(err,data)=>{
    if(!err){
        console.log(data);
    }
})
删除文件
//删除文件
fs.unlink("./avater2/a.txt",(err)=>{
    console.log(err);
})
读取文件夹下的文件 
//读取文件夹下的文件
fs.readdir('./avater2', (err, data) => {
    if (!err) {
        console.log(data);//[ 'a.txt', 'b.txt', 'c.js' ]
    }
})
查看文件或文件夹信息 

主要用里面的判断文件或文件夹的方法。

//查看文件或文件夹信息
fs.stat('./avater2',(err,data)=>{
    console.log(data.isDirectory()); //判断是否是文件目录
    console.log(data.isFile()); //判断是否是文件
})

// {
//     dev: 16777231,
//     mode: 16877,
//     nlink: 5,
//     uid: 501,
//     gid: 20,
//     rdev: 0,
//     blksize: 4096,
//     ino: 12893475,
//     size: 160,
//     blocks: 0,
//     atimeMs: 1692457394626.1108,
//     mtimeMs: 1692457394548.2456,
//     ctimeMs: 1692457394548.2456,
//     birthtimeMs: 1692453342095.4685,
//     atime: 2023-08-19T15:03:14.626Z,
//     mtime: 2023-08-19T15:03:14.548Z,
//     ctime: 2023-08-19T15:03:14.548Z,
//     birthtime: 2023-08-19T13:55:42.095Z
//   }

stream流模块

stream是node.js提供的又一个仅在服务端可用的模块目的是支持流这种数据结构。

创建可读流

const fs = require('fs')

const rs = fs.createReadStream('./1.txt','utf-8')

rs.on('data',chunk=>{
    console.log(chunk);
})

rs.on('end',()=>{
    console.log('end');
})

rs.on('error',(err)=>{
    console.log(err);
})

创建可写流

const ws = fs.createWriteStream('./2.txt','utf-8')

ws.write('111')
ws.write('222')

ws.end()

把读取的数据写入新的文件

const fs = require('fs')
const rs = fs.createReadStream('./1.txt')
const ws = fs.createWriteStream('./2.txt')
rs.pipe(ws)

zlib

压缩资源传输

const http = require('http')
const fs = require('fs')
const zlib = require('zlib')
const gzip = zlib.createGzip()

const server = http.createServer((req, res) => {
    const rs = fs.createReadStream('./1.txt')
    res.writeHead(200, { 'content-type': 'application/x-javascript;charset=utf-8', 'access-control-allow-origin': '*','Content-Encoding':'gzip' }); //告诉浏览器根据什么方式解压缩如不添加就是流
    rs.pipe(gzip).pipe(res) // 压缩
})

server.listen(8000, () => {
    console.log('start');
})

crypto

crypto模块的额目的是为了提供通用的加密和哈希算法。用纯js实现起来比较麻烦并且速度会很慢所以nodejs通过c/c++实现后通过crypto这个模块暴露出来方便快捷。

MD5是一个常用的哈希算法用于给任意数据一个签名通常以十六进制的字符串表示也可以用 base64 的形式展示。

const crypto = require("crypto")

const hash = crypto.createHash('md5')

hash.update('hello world') //update方法默认字符串编码为utf-8也可以传入buffer

console.log(hash.digest('hex')); //十六进制的形式展示
//console.log(hash.digest('base64')); //base64的形式展示

update方法默认字符串编码为utf-8也可以传入buffer。

如果要计算SHA1只需要将 md5 改为 sha1就可以得到结果md5sha1sha256

const crypto = require("crypto")

const hash = crypto.createHash('sha1')

hash.update('hello world') //update方法默认字符串编码为utf-8也可以传入buffer

console.log(hash.digest('hex')); //十六进制的形式展示
// console.log(hash.digest('base64')); //base64的形式展示

Hmac算法

也是一种hash算法他可以利用md5或者SHA1等hash算法。不同的是Hmac还需要一个秘钥。只要秘钥发生变化生成的签名也就不同。

const crypto = require("crypto")

const hash = crypto.createHmac('sha256','secret')

hash.update('hello world') //update方法默认字符串编码为utf-8也可以传入buffer

console.log(hash.digest('hex')); //十六进制的形式展示
// console.log(hash.digest('base64')); //base64的形式展示

AES是一种常用的对称加密算法加解密都用同一个秘钥。crypto模块提供了AES支持但是需要自己封装好函数便于使用。

const crypto = require("crypto")

function encrypt(key,iv,data){ //iv是秘钥
    let dep = crypto.createCipheriv("aes-128-cbc",key,iv)
    return dep.update(data,'binary','hex')+dep.final('hex')  //update参数原始数据输入格式为 binary以十六进制展示
}

function decrypt(key,iv,data){
    let crypted = Buffer.from(data,'hex').toString('binary')//十六进制的对象转为buffer,再转为二进制。
    let dep = crypto.createDecipheriv('aes-128-cbc',key,iv)
    return dep.update(crypted,'binary','utf8')+dep.final('utf8')
}
//16 * 8 = 128 必须是8的倍数 key和iv都要遵循
let key = "abcdef1234567890"
let iv = "1234567890abcdef"

let data ='jerry'

console.log(encrypt(key,iv,data));//895f852b13da04523548c2cfebf25eff
console.log(decrypt(key,iv,encrypt(key,iv,data)));//jerry

路由

可以返回前端html页面也可以实现api接口

//server.js

const http = require('http')

let Router ={}
function use(obj){
    Router ={...Router,...obj}
}

function start() {
    const server = http.createServer((req, res) => {
        let myurl = new URL(req.url, 'http://127.0.0.1')
        try {
            Router[myurl.pathname](req,res)
        } catch (error) {
            Router['/404'](req,res)
        }
    })
    server.listen('3000', () => {
        console.log('启动服务器');
    })
}

exports.start = start
exports.use = use 
const server = require('./server')
const route= require("./route")
const api = require("./api")

server.use({...route,...api}) //合并接口
server.start()
//route.js

const routes = {
    '/home':(req,res)=>{
        res.writeHead(200,{'Content-Type':"text/html;charset=utf-8"})
        res.write(fs.readFileSync('./home.html'),'utf-8')
        res.end()
    },
    '/index':(req,res)=>{
        res.writeHead(200,{'Content-Type':"text/html;charset=utf-8"})
        res.write(fs.readFileSync('./index.html'),'utf-8')
        res.end()
    },
    '/favicon.ico':(req,res)=>{
        // res.writeHead(200,{'Content-Type':"image/x-icon;charset=utf8"})
        // res.write(fs.readFileSync('./favicon.ico'))
        res.end()
    },
    '/404':(req,res)=>{
        res.write('404','utf-8')
        res.end()
    }
}
module.exports = routes
//api.js

const api = {
    '/api/findStr':(req,res)=>{
        res.writeHead(200,{'Content-Type':"application/json;charset=utf-8"})
        res.write(`{"a":"str"}`)
        res.end()
    },
}
module.exports = api

路由获取请求参数

根据服务端的地址访问 localhost:3000/index

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<button id="login">登录-get</button>
<button id="login2">登录-post</button>

<body>

</body>
<script>
    //get请求
    login.onclick = function () {
        let username = 'zhangsan'
        let passsword = '123456'
        fetch(`/api/findStr?username=${username}&password=${passsword}`)
            .then(res => res.json()).then(res => {
                console.log(res)
            })
    }

    //post请求
    login2.onclick = function () {
        fetch(`/api/findStrpost`,{
            method:'post',
            body:JSON.stringify({
                username:'zhangsan',
                passsword:'123456'
            }),
            headers:{
                'Content-Type':'application/json'
            }
        })
            .then(res => res.json()).then(res => {
                console.log(res)
            })
    }

</script>

</html>

参数获取上述的URL模块即可

const api = {
    //get请求
    '/api/findStr':(req,res)=>{
        const myUrl = new URL(req.url,'http://127.0.0.1')
        let username = myUrl.searchParams.get('username')
        let password = myUrl.searchParams.get('password')
        console.log(username,password);
        res.writeHead(200,{'Content-Type':"application/json;charset=utf-8"})
        res.write(`{"a":"str"}`)
        res.end()
    },
    //post请求
    '/api/findStrpost':(req,res)=>{
        let post = ""
        req.on('data',chunk=>{ //收集数据
            post+=chunk
        })
        req.on('end',()=>{
            console.log(post);
            res.writeHead(200,{'Content-Type':"application/json;charset=utf-8"})
            res.write(`{"a":"str"}`)
            res.end()
        })
    },
}
module.exports = api

路由获取静态资源

这里需要一个插件 mime可以能够获取到对应的文件应当的content-type

npm i mime

const mime = require('mime')

mime.getType('txt')   // 'text/plain'

mime.getExtension('text/plain')   // 'txt'

const fs =require('fs')
const path = require('path')
const mime = require('mime')

const routes = {
    '/home':(req,res)=>{
        res.writeHead(200,{'Content-Type':"text/html;charset=utf-8"})
        res.write(fs.readFileSync('./home.html'),'utf-8')
        res.end()
    },
    '/index':(req,res)=>{
        res.writeHead(200,{'Content-Type':"text/html;charset=utf-8"})
        res.write(fs.readFileSync('./index.html'),'utf-8')
        res.end()
    },
    '/favicon.ico':(req,res)=>{
        // res.writeHead(200,{'Content-Type':"image/x-icon;charset=utf8"})
        // res.write(fs.readFileSync('./favicon.ico'))
        res.end()
    },
    '/404':(req,res)=>{
        if(readStaticFile(req,res)){ //判断是否存在文件
            return
        }
        res.write('404','utf-8')
        res.end()
    }
}
function readStaticFile(req,res){
    const myURL = new URL(req.url,'http://127.0.0.1:3000')
    const pathname = path.join(__dirname,myURL.pathname); //绝对路径
    const type = mime.getType(myURL.pathname.split('.')[1]) //获取文件后缀

    if(myURL.pathname=='/') return false
    if(fs.existsSync(pathname)){
        res.writeHead(200,{'Content-Type':`${type};charset=utf8`}) //设置响应头
        res.write(fs.readFileSync(pathname),'utf-8')
        res.end()
        return true
    }else{
        return false
    }
}
module.exports = routes
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<link rel="stylesheet" href="./static/css/index.css">

<body>
    <div>
        <button id="login">登录-get</button>
        <button id="login2">登录-post</button>
    </div>
</body>
<script>
    //get请求
    login.onclick = function () {
        let username = 'zhangsan'
        let passsword = '123456'
        fetch(`/api/findStr?username=${username}&password=${passsword}`)
            .then(res => res.json()).then(res => {
                console.log(res)
            })
    }

    //post请求
    login2.onclick = function () {
        fetch(`/api/findStrpost`, {
            method: 'post',
            body: JSON.stringify({
                username: 'zhangsan',
                passsword: '123456'
            }),
            headers: {
                'Content-Type': 'application/json'
            }
        })
            .then(res => res.json()).then(res => {
                console.log(res)
            })
    }

</script>

</html>

Express

基于nodejs平台快速开放极简的web开发框架

npm i  express --save

 路由路径和请求方法一起定义了请求的端点可以是字符串字符串模式或者是正则表达式。

基本路由 

const express = require('express')

const app = express() //创建服务器
app.get('/',(req,res)=>{
    res.send('123') //res.send可以发送任意格式的内容
})

app.listen(3000,()=>{
    console.log('启动了');
})

 以下情况访问 http://localhost:3000/ac 或者 http://localhost:3000/abc 均可请求

app.get('/ab?c',(req,res)=>{
    res.send('123') //res.send可以发送任意格式的内容
})

 占位符能够匹配参数请求http://localhost:3000/abc/1 格式即可

app.get('/abc/:id',(req,res)=>{
    console.log(req.params.id)
    res.send('456') //res.send可以发送任意格式的内容
})

可匹配 abcd abbcd abbbcd 等b可以一次或多次。 

app.get('/ab+cd',(req,res)=>{
    res.send('ab+cd') 
})

可以在之间写任意内容 

app.get('/ab*cd',(req,res)=>{
    res.send('ab*cd') 
})

 匹配正则表达式

app.get(/q/,(req,res)=>{ //路径中包含 q 即可
    res.send('q') 
})

 在接口的参数中除了对应的访问路径以外还可以写多个回调函数来操作处理然后返回给前端数据。其中next函数非常重要是进入下一个回调函数的开关一旦使用了res.send之后的 代码都不再执行。

app.get('/home',(req,res,next)=>{
    //验证token
    console.log('验证成功')
    next()
},(req,res)=>{
    res.send('home') //res.send可以发送任意格式的内容
})

 多个回调函数可以写成数组的形式并且能够传参数使用res传递。

const a = function (req,res,next){
    //验证token
    console.log('验证')
    res.name='zhangsan'
    next()
}
const b = function (req,res,next){
    console.log(res.name)
    res.send('home') //res.send可以发送任意格式的内容
}
app.get('/home',[a,b])

res.send() 支持发送片段以及json

res.json() 只能支持发送json

res.render() 支持发送模版

中间件

express是一个资深功能极简完全是由路由和中间件构成的一个web开发框架从本质上说一个express应用就是在调用各种中间件。

中间件是一个函数可以访问请求对象响应对象以及web应用中处于响应循环流程中的中间件一般被命名为next的变量。

中间件的功能包括

执行任何代码 修改请求和响应对象 终结请求-响应循环 调用堆栈中的下一个中间件。

如果当前中间件始终没有 终结请求-响应循环则必须调用next 方法将控制权交给下一个中间件否则就会挂起。

Express应用可以使用如下几种中间件

应用级中间件

应用级中间件绑定到app对象使用 app.use() 和 app.method() 包括app.get app.post等其中method是需要处理http请求的方法例如getputpost等。在请求接口前先验证token是否有效。

const express = require('express')

const app = express()
const a = function (req,res,next){
    //验证token
    console.log('验证')
    res.name = 123
    next()
}
app.use(a)
const b = function (req,res,next){
    console.log(res.name);
    res.send('home') //res.send可以发送任意格式的内容
}
app.get('/home',[b])
app.get('/ab?c',(req,res)=>{
    res.send('123') //res.send可以发送任意格式的内容
})
app.get('/abc/:id',(req,res)=>{
    res.send('456') //res.send可以发送任意格式的内容
})
app.get(/q/,(req,res)=>{
    res.send('q') //res.send可以发送任意格式的内容
})
app.listen(3000,()=>{
    console.log('启动了');
})
路由级中间件

类似于接口模块化可以给对应的模块加上前缀使用express.Router() 来创建

下面的访问为http://localhost:3000/index/home 等。

//indexRouter.js
const express = require('express')

const router = express.Router()

router.get('/',(req,res)=>{
    res.send('/')
})
router.get('/home',(req,res)=>{
    res.send('home')
})

router.get('/ab?c',(req,res)=>{
    res.send('123')
})
router.get('/abc/:id',(req,res)=>{
    res.send('456')
})
router.get(/q/,(req,res)=>{
    res.send('q')
})

module.exports = router
const express = require('express')
const IndexRouter = require('./router/IndexRouter')

const app = express()
const a = function (req,res,next){
    //验证token
    console.log('验证')
    res.name = 123
    next()
}
app.use(a)
app.use('/index',IndexRouter)
//app.use('/login',LoginRouter)
//app.use('/home',HomeRouter)

app.listen(3000,()=>{
    console.log('启动了');
})
错误处理中间件

在匹配不到接口时报错放在app.use() 应用中间件的最后。没有任何路径匹配万能中间件任何数据都可以返回。使用4个参数errreqresnexterr是参数状态码。

app.use((req,res)=>{
    res.status(404).send('丢了')
})
内置中间件

express.static是express唯一内置的中间件。它基于serve-static负责在express应用中替托管静态资源。每个应用可有多个静态目录。

第三方中间件

安装所需功能的node模块并在应用中加载可以在应用级加载也可以在路由级加载。

路由获取前端参数

http://localhost:3000/index/home?a=1&b=2

 get请求通过req.query来获取查询字符串

router.get('/home',(req,res)=>{
    console.log(req.query);
    res.send('home')
})

post请求通过req.body来获取但是要提前配置解析post请求的中间件旧版需要下载body-parser现在已经内置需要引入配置。

const express = require('express')
const IndexRouter = require('./router/IndexRouter')

const app = express()
const a = function (req,res,next){
    //验证token
    console.log('验证')
    res.name = 123
    next()
}
//配置post请求的中间件 
//form 表单的形式a=1&b=2
app.use(express.urlencoded({extended:false})) 
//Json对象形式{a:'1',b:'2'}
app.use(express.json()) 

app.use(a)
app.use('/index',IndexRouter)


app.use((req,res)=>{
    res.status(404).send('丢了')

})
app.listen(3000,()=>{
    console.log('启动了');
})
router.post('/homePost',(req,res)=>{
    console.log(req.body); //必须配置中间件
    res.send('homePost')
})

router.post('/homePostJson',(req,res)=>{
    console.log(req.body); //必须配置中间件
    res.send('homePost')
})
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div>
        <button id="login">登录-post</button>
        <button id="login2">登录-postJson</button>
    </div>
</body>
<script>
    login.onclick = function () {
        let username = 'zhangsan'
        let passsword = '123456'
        fetch(`/index/homePost`, {
            method: 'post',
            body:`username=${username}&password=${passsword}`,
            headers: {
                'Content-Type': 'applicatin/x-www-form-urlencoded'
            }
        })
            .then(res => res.text()).then(res => {
                console.log(res)
            })
    }
    login2.onclick = function () {
        fetch(`/index/homePostJson`, {
            method: 'POST',
            body: JSON.stringify({
                username: 'zhangsan',
                passsword: '123456'
            }),
            headers: {
                'Content-Type': 'application/json'
            }
        })
            .then(res => res.text()).then(res => {
                console.log(res)
            })
    }

</script>

</html>

 利用express托管静态文件

通过express内置的express.static可以方便地托管静态文件例如图片cssjs文件等。

将静态资源文件所在的目录作为参数传递给express.static中间件就可以提供静态资源文件的访问了。

app.use(express.static('public')) //可以是任意文件夹。

然后就可以访问文件了

 http://localhost:3000/index/images/kitten.jpg

 http://localhost:3000/index/js/app.js

 http://localhost:3000/index/css/style.css

 http://localhost:3000/index/hello.html

所有文件的路径都是相对于存放目录的因此存放静态文件的目录不会出现在URL中

 只需访问 http://localhost:3000/index.html

同时可以设置多个静态文件

const express = require('express')
const IndexRouter = require('./router/IndexRouter')

const app = express()
app.use(express.urlencoded({extended:false})) //配置post请求的中间件 a=1&b=2
app.use(express.json()) //配置post请求的中间件
app.use(express.static('public')) //配置静态资源
app.use(express.static('static'))

const a = function (req,res,next){
    //验证token
    console.log('验证')
    res.name = 123
    next()
}


app.use(a)
app.use('/index',IndexRouter)


app.use((req,res)=>{
    res.status(404).send('丢了')

})
app.listen(3000,()=>{
    console.log('启动了');
})

服务端渲染模版引擎

客户端渲染前后端分离BSR前段中组装页面做好静态页面动态效果json模拟ajax动态创建页面真实接口数据前后联调。

服务端渲染后端镶嵌模版后端渲染模版SSRServer-side-render 后端把页面组装做好静态页面动态效果把前端代码提供给后端后端把静态html以及html里的假数据给删掉通过模版进行动态生成html的内容。

需要ejs 模版引擎工具在应用中设置让express渲染模版文件

views放模版文件的目录比如app.set('views','./views')

view engine模版引擎比如app.set('view engine','ejs')

<img src="%E7%AC%94%E8.assets/image-2021.png" alt="not-found" style="zoom:50%"/>

<% %> 流程控制标签写入js代码

<%= %> 输出标签原文输出HTML标签html片段

<%- %> 输出标签 HTML会被浏览器解析

<%# %> 注释标签

<%- include('user/show',{user.user}) %> 导入公模版内容

npm i ejs
//服务端
const express = require('express')
const IndexRouter = require('./router/IndexRouter')
const HomeRouter = require('./router/HomeRouter')

const app = express()
 //配置模版引擎
app.set('views',"./views") //配置文件夹views下的ejs文件为渲染路由页面
app.set('view engine',"ejs")

app.use(express.urlencoded({extended:false})) //配置post请求的中间件 a=1&b=2
app.use(express.json()) //配置post请求的中间件
app.use(express.static('public')) //配置静态资源

const a = function (req,res,next){
    //验证token
    console.log('验证')
    res.name = 123
    next()
}


app.use(a)
app.use('/index',IndexRouter)
app.use('/home',HomeRouter)


app.use((req,res)=>{
    res.status(404).send('丢了')

})
app.listen(3000,()=>{
    console.log('启动了');
})

配置路由接口 

//IndexRouter.js
const express = require('express')

const router = express.Router()

router.get('/',(req,res)=>{
    res.render('index',{title:"hello world"})//渲染模版后自动返回给前端找到views下的index.ejs
})
router.post('/validate',(req,res)=>{
    console.log(req.body);
    res.redirect('/home') //重定向到home页面
})

module.exports = router
//HomeRouter.js
const express = require('express')
const router = express.Router()

router.get('/',((req,res)=>{
    res.render('home',{list:[1,2,3],content:`<h1>我是片段</h1>`})
}))

module.exports=router

在views文件下创建ejs文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    index 页面
    <div>标题<%=title%></div>

    <form action="/index/validate" method="post">
        <div>用户名<input type="text" name="username"></div>
        <div>密码<input type="password" name="password"></div>
        <div><input type="submit" value='登录'></div>
    </form>
</body>
</html>
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    home 页面
    <ul>
        <% for(let i=0;i<list.length;i++){ %>
            <li>
                <%= list[i]%>
            </li>
            <%}%>
    </ul>
    <div>
        最新消息<%- content  %>
    </div>
    <%# 注释的写法 不会在页面资源中显示,只存在开发环境中 %>  
    <div>
        <%-include('./about.ejs',{showItem:true})  %>
    </div>
</body>
<script>
</script>

</html>

有时候我们不需要ejs文件只需要html文件那么应该怎么做

 //配置模版引擎
app.set('views',"./views")
app.set('view engine',"html") 
app.engine('html',require('ejs').renderFile) //直接支持html渲染

配置好后在views下写好对应的html文件即可。

Express脚手架

通过express-generator可以快速创建一个应用骨架

npm i -g express-generator

创建项目并下载依赖启动项目 

express myapp --view=ejs

npm i

npm start 

此时启动后不能随时重编译做一下修改package.json完成脚手架的配置。

"scripts": {
    "start": "nodemon ./bin/www"
  },
//一些模块
res.locals.message = err.message; //res.locals类似于上下文在ejs中这种可以直接获取到里面的内容
app.use(logger('dev')); //记录请求的生成器控制台可以输出错误

MongoDB

关系型与非关系型数据库

关系型数据库sql语句增删改查操作保持事务的一致性事务机制回滚包括mysqlsqlserverdb2oracle。

非关系型数据库no sql:not only sql轻量高效自由包括mongodbHbaseredis。

mongoDB的优势

由于独特的数据处理方式可以将热点数据加载到内存故而对查询来讲会非常快当然也消耗内存同时由于采用了BSON的方式存储数据对JSON格式数据具有非常好的支持性以及友好的表结构修改性文档式的存储方式数据有好可见数据库的分片集群负载具有非常好的扩展性以及非常不错的自动 鼓掌转移。

sqlMongoDB说明
databasedatabase数据库
tablecollection表/集合
rowdocument表行/文档
columnfield表列字段/域
indexindex索引
table joins不支持表连接
primary keyprimary key主键

安装数据库

Install MongoDB Community Edition — MongoDB Manual

可自行选择系统匹配的下载

这里使用mac版本先下载homebrew然后安装mongodb

//终端安装homebrew
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
//根据提示继续执行
eval "$(/opt/homebrew/bin/brew shellenv)"
brew install mongodb

 如果报错No available formula with the name "mongodb". Did you mean mongosh or monetdb?

首先tap一个仓库

brew tap mongodb/brew

安装社区版

brew install mongodb-community

安装好mongodb以后启动

mongosh

在命令行中操作数据库

操作库

查看命令提示

help

db.help()

db.test.help()

db.test.find().help()

创建/切换数据库use testdb
查询数据库show dbs
查看当前使用的数据库db/db.getName()
显示当前DB状态db.stats()
查看当前DB版本db.version
产看当前DB的连接机器地址db.getMongo()
删除数据库db.dropDatabase()

操作集合 

创建一个集合db.createCollection('cillName',{size:222,capped:true,max:5000});最大存储空间为5M最多5000个文档的集合。
得到指定名称的聚集集合db.getCollection('account')
得到当前db的所有聚集集合db.getCollectionNames()
显示当前db所有聚集的状态db.printCollectionStats()
删除集合db.collection.drop();

操作集合数据 

添加集合数据

db.users.insert({name:'zhangsan',age:25,sex:1})

db.users.insert([{name:'zhangsan',age:25,sex:1},{name:'lisi',age:25,sex:1}])

在这里插入的数据数据结构可以完全不一样也能够插入。

修改

db.users.update(age:25,$set{name:'wanger',false,true})

相当于sqlupdate users set name = "wanger" where age = 25

db.users.update({name:'zhangsan'},{$inc:{age:50}},false,true)

相当于sqlupdate users set age = age + 50 where name = 'zhangsan'

减的话$inc:{age:-50}即可

db.users.update({name:'zhangsan'},{$inc:{age:50},$set{name:'lisi'}},false,true)

相当于sqlupdate users set age = age + 50, name = 'zhangssan' where name = 'lisi'

删除

db.users.remove({age:32});

db.users.remove({}); 删除所有

db.users.deleteOne({age:32});

db.users.deleteMany({age:32});

查询所有数据

db.userInfo.find() 

相当于sqlselect * from userInfo

查询某字段去重后的数据

db.userInfo.distinct('name')

相当于sqlselect distinct name from userInfo

查询age=22的数据

db.userInfo.find({age:22})

相当于sqlselect * from userInfo where age = 22

查询age>22的数据

db.userInfo.find({age:{$gt:22}})

相当于sqlselect * from userInfo where age > 22

查询age<22的数据

db.userInfo.find({age:{$lt:22}})

相当于sqlselect * from userInfo where age < 22

查询age>=22的数据

db.userInfo.find({age:{$gte:22}})

相当于sqlselect * from userInfo where age > =22

查询age<=22的数据

db.userInfo.find({age:{$lte:22}})

相当于sqlselect * from userInfo where age <= 22

查询age>=22 age<=26的数据

db.userInfo.find({age:{$gte:26,$lte:22}})

相当于sqlselect * from userInfo where age >= 22 and age <= 26

查询name包含 mongo的数据

db.userInfo.find({name:/mongo/})写入正则表达式

相当于sqlselect * from userInfo where name like 'mongo%'

查询指定列 nameage数据想显示哪列就将字段设置为1不想显示的就设置为0

db.userInfo.find({},{name:1,age:1})

相当于sqlselect name,age from userInfo

查询指定列 nameage数据 age>25

db.userInfo.find({age:{$gt:25}},{name:1,age:1});

相当于sqlselect name,age from userInfo where age >25

按照年龄排序数组就是多列查询

升序.db.userInfo.find().sort({age:1})

降序.db.userInfo.find().sort({age:-1})

查询 name:'zhangsa',age:22的数据

db.userInfo.find({name:'zhangsan',age:22})

sqlselect * from userInfo where name='zhangsan' and age=22

查询前5条数据

db.userInfo.find().limit(5)

sqlselect top 5 * from userInfo

查询10条以后的数据

db.userInfo.find().skip(10)

sqlselect * from userInfo where id not in ( select top 10 * from userInfo )

查询5-10之间的数据db.userInfo.find().skip(5).limit(5)
or 与 查询

db.userInfo.find({$or:[{age:22},{age:25}]})

sqlselect * from userInfo whrer age = 22 or age = 25

查询第一条数据

db.userInfo.findOne() / db.userInfo.find().limit(1)

sqlselect 1 * from userInfo

查询某个结果集的记录条数

db.userInfo.find({age:{$gte:25}}).count()

sqlselect count(*) from userInfo where age >= 20

可视化工具进行增删改查

 Robomogo Robo3T adminMongo 可自行下载使用 

 使用nodejs操作数据库

这里使用生成的express ejs的脚手架数据库连接新建数据库文件连接在启动文件下引入可以在app.js或者bin下面的www文件这里在express脚手架中www文件引入

npm i mongoose

const mongoose =require('mongoose')

mongoose.connect('mongodb://127.0.0.1:27017/express_test') 
//插入集合数据数据库会自动创建
#!/usr/bin/env node

/**
 * Module dependencies.
 */

var app = require('../app');
var debug = require('debug')('myapp:server');
var http = require('http');
require('../config/db.config')
/**
 * Get port from environment and store in Express.
 */

var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);

/**
 * Create HTTP server.
 */

var server = http.createServer(app);

/**
 * Listen on provided port, on all network interfaces.
 */

server.listen(port);
server.on('error', onError);
server.on('listening', onListening);

/**
 * Normalize a port into a number, string, or false.
 */

function normalizePort(val) {
  var port = parseInt(val, 10);

  if (isNaN(port)) {
    // named pipe
    return val;
  }

  if (port >= 0) {
    // port number
    return port;
  }

  return false;
}

/**
 * Event listener for HTTP server "error" event.
 */

function onError(error) {
  if (error.syscall !== 'listen') {
    throw error;
  }

  var bind = typeof port === 'string'
    ? 'Pipe ' + port
    : 'Port ' + port;

  // handle specific listen errors with friendly messages
  switch (error.code) {
    case 'EACCES':
      console.error(bind + ' requires elevated privileges');
      process.exit(1);
      break;
    case 'EADDRINUSE':
      console.error(bind + ' is already in use');
      process.exit(1);
      break;
    default:
      throw error;
  }
}

/**
 * Event listener for HTTP server "listening" event.
 */

function onListening() {
  var addr = server.address();
  var bind = typeof addr === 'string'
    ? 'pipe ' + addr
    : 'port ' + addr.port;
  debug('Listening on ' + bind);
}

创建数据库模型由于mongodb存储数据比较自由所以需要添加这样的模型来限制传入的字段以及字段类型新建限制模型

const mongoose = require("mongoose")

// 限制模型 类似于接口interface限制参数
const type = {
    username:String,
    password:String
}
// 模型user将会对应users集合
const UserModel = mongoose.model('user',new mongoose.Schema(type))

module.exports=UserModel

在接口文件中引入限制模型并操作数据库。

var express = require('express');
var router = express.Router();
const UserModel = require('../dbModel/UserModel')
/* GET home page. */
router.get('/', function (req, res, next) { //服务端渲染
  //创建数据库模型要限制field类型一一对应数据库集合
  let page = 1
  let limit = 3
  UserModel.find({},['username']).sort({_id:-1}).skip((page-1)*limit).limit(3).then(result => { //数据查询
    res.render('index', {list:result});
  })
});
//注册
router.post('/register', function (req, res, next) {
  const { username, password } = req.body
  //创建数据库模型要限制field类型一一对应数据库集合
  UserModel.create({ username, password }).then(result => {
    res.send('register success');
  })
});
//修改
router.post('/update/:id', function (req, res, next) {
  const { username, password } = req.body
  const { id } = req.params
  //创建数据库模型要限制field类型一一对应数据库集合
  UserModel.updateOne({ id, username, password }).then(result => { 
    res.send('update success');
  })
});
// 删除
router.get('/delete', function (req, res, next) {
  const { id } = req.query
  //创建数据库模型要限制field类型一一对应数据库集合
  UserModel.deleteOne({ _id:id }).then(result => { //插入数据
    res.send('delete success')
  })
});
module.exports = router;

页面请求index.ejs页面

<!DOCTYPE html>
<html>
  <head>
    <link rel='stylesheet' href='/stylesheets/style.css' />
  </head>
  <body>
    <div>
      用户名<input id="username" type="text">
    </div>
    <div>
      密码<input id="password" type="text">
    </div>
    <button id="reg">注册</button>
    <button id="update">修改</button>
    <button id="deleteItem">删除</button>
    <br>
    <div>
      <ul>
        <% for (let index = 0; index < list.length; index++) { %>
          <li><%=list[index].username %>-</li>
        
          <% } %>
      </ul>
      
    </div>
  </body>
  <script>
    reg.onclick=()=>{
      fetch('/register',{
        method:'post',
        headers:{
          "Content-Type":"application/json"
        },
        body:JSON.stringify({username:username.value,password:password.value})
      }).then(res=>res.text()).then(res=>{
        console.log(res);
      })
    }

    update.onclick=()=>{
      fetch('/update/64eb4d5df24d574cfca6e518',{
        method:'post',
        headers:{
          "Content-Type":"application/json"
        },
        body:JSON.stringify({username:'修改',password:'修改'})
      }).then(res=>res.text()).then(res=>{
        console.log(res);
      })
    }

    deleteItem.onclick=()=>{
      fetch('/delete?id=64eb4df708eff7b8d60cadbf').then(res=>res.text()).then(res=>{
        console.log(res);
      })
    }
  </script>
</html>

fetch接口

        //post
      fetch('/register',{
        method:'post',
        headers:{
          "Content-Type":"application/json"
        },
        body:JSON.stringify({username:username.value,password:password.value})
      }).then(res=>res.text()).then(res=>{
        console.log(res);
      })

        //get 也可以 /delete/id 的占位符写法
      fetch('/delete?id=64eb4df708eff7b8d60cadbf').then(res=>res.text()).then(res=>{
        console.log(res);
      })

 接口规范与业务分层

接口规范

restful架构

服务器上每一种资源比如一个文件一张图片一部电影都有对应的url地址如果我们的额客户端要对服务器的资源进行操作就要通过http协议执行形影的动作来操作比如获取更新删除。

简单来说就是url地址中只包含名词表示资源使用http动词表示动作进行操作资源下面对比右边为规范的写法

Get /blog/getArticles ---> Get /blog/Articles  //获取

Get /blog/addArticles ---> Post /blog/Articles //新增

Get /blog/editArticles ---> Put /blog/Articles //编辑

Get /rest/api/deleteArticles ---> Delete /blog/Articles/1  //删除

使用方式

GET http://www.test.com/api/user //获取列表

POST http://www.test.com/api/user //创建用户

PUT http://www.test.com/api/user/{id} //修改用户信息

DELETE http://www.test.com/api/user/{id} //删除用户信息
fetch('/api/user',{
        method:'POST',
        headers:{
          "Content-Type":"application/json"
        },
        body:JSON.stringify({username:username.value,password:password.value})
      }).then(res=>res.text()).then(res=>{
        console.log(res);
      })

fetch('/api/user/id',{
        method:'PUT',
        headers:{
          "Content-Type":"application/json"
        },
        body:JSON.stringify({username:'修改',password:'修改'})
      }).then(res=>res.text()).then(res=>{
        console.log(res);
      })

fetch('/api/user/id',{
        method:'DELETE',
        headers:{
          "Content-Type":"application/json"
        },
        body:JSON.stringify({username:'修改',password:'修改'})
      }).then(res=>res.text()).then(res=>{
        console.log(res);
      })
fetch('/api/user'}).then(res=>res.text()).then(res=>{
        console.log(res);
      })
router.get('/user', function (req, res, next) { //服务端渲染
  //创建数据库模型要限制field类型一一对应数据库集合
  let page = 1
  let limit = 3
  UserModel.find({},['username']).sort({_id:-1}).skip((page-1)*limit).limit(3).then(result => { //数据查询
    res.render('index', {list:result});
  })
});

//注册
router.post('/user', function (req, res, next) {
  const { username, password } = req.body
  //创建数据库模型要限制field类型一一对应数据库集合
  UserModel.create({ username, password }).then(result => {
    res.send('register success');
  })
});
//修改
router.put('/user/:id', function (req, res, next) {
  const { username, password } = req.body
  const { id } = req.params
  //创建数据库模型要限制field类型一一对应数据库集合
  UserModel.updateOne({ id, username, password }).then(result => { 
    res.send('update success');
  })
});
// 删除
router.delete('/user/:id', function (req, res, next) {
  const { id } = req.query
  //创建数据库模型要限制field类型一一对应数据库集合
  UserModel.deleteOne({ _id:id }).then(result => { //插入数据
    res.send('delete success')
  })
});

业务分层

router.js负责将请求分发给c端

controller.jsc层负责处理业务逻辑v与m之间的沟通

viewsv层 负责展示页面

modelm层 负责处理数据增删改查

创建controllers service 

//indexRouter.js
const UserModel = require('../dbModel/UserModel')
var express = require('express');
var router = express.Router();
const IndexController = require('../controllers/indexController')
/* GET home page. */
router.get('/', IndexController.queryUser);
//注册
router.post('/register',IndexController.addUser);
//修改
router.post('/update/:id',IndexController.updateUser );
// 删除
router.get('/delete', IndexController.deleteUser);
module.exports = router;
//indexControllers.js
const UserService = require('../service/UserService')
const IndexController = {
    addUser: async (req, res, next) => {
        const { username, password } = req.body
        //创建数据库模型要限制field类型一一对应数据库集合
        await UserService.addUser(username, password)
        res.send('register success');
    },
    updateUser: async (req, res, next) => {
        const { username, password } = req.body
        const { id } = req.params
        //创建数据库模型要限制field类型一一对应数据库集合
        await UserService.updateUser(id, username, password)
        res.send('update success');
    },
    queryUser: async (req, res, next) => { //服务端渲染
        //创建数据库模型要限制field类型一一对应数据库集合
        let page = 1
        let limit = 3
        let result = await UserService.queryUser(page, limit)
        res.render('index', { list: result });
    },
    deleteUser:async (req, res, next)=>{
        const { id } = req.query
        //创建数据库模型要限制field类型一一对应数据库集合
        await UserService.deleteUser(id)
        res.send('delete success')

      }

}

module.exports = IndexController
//UserService

const UserModel = require('../dbModel/UserModel')
const UserService = {
    addUser: (username, password) => {
        return UserModel.create({ username, password }).then(result => {
        })
    },
    updateUser:(id,username, password)=>{
        return UserModel.updateOne({ id, username, password }).then(result => {
          })
    },
    queryUser:(page,limit)=>{
        return UserModel.find({}, ['username']).sort({ _id: -1 }).skip((page - 1) * limit).limit(3)
    },
    deleteUser:(id)=>{
        UserModel.deleteOne({ _id: id }).then(result => { //插入数据
        })
    }
}

module.exports = UserService

Cookie与session

http是无状态的也就是说http请求方和响应方之间无法维持状态都是一次性的他不知道前后的请求都发生了什么但有的场景下我们需要维护状态。比如登录情况下才可以发送业务请求等。

使用express-session插件

npm i express-session

 在app.js中配置

var session = require("express-session")

app.use(session({ //注册session
  name:'sys', //名称
  secret:'qwer1234', //服务器生成session的签名
  cookie:{
    maxAge:1000*60*60, //过期时间
    secure:false //为true只有https协议才能访问cookie
  },
  resave:true, //刷新session
  saveUninitialized:true //true一开始会给一个无效的cookie
}))
//设置中间件session过期校验
app.use((req,res,next)=>{
  if(req.url.includes("login")){ //登录页面放行
    next()
    return
  }
  if(req.session.user){
    next()
  }else{
    res.redirect('/login')
  }
})

 在登录时添加sessionindexController中配置

loginUser: async (req, res, next) => {
        const { username, password } = req.body
        //创建数据库模型要限制field类型一一对应数据库集合
        let arr = await UserService.loginUser(username, password)

        console.log(arr);
        if (arr.length == 0) {
            res.send({ code: 0 })
        } else {
            req.session.user = "123" //设置sesion对象
            res.send({ code: 1 })
        }
    },

在index.js接口文件中引用

router.post('/loginUser', IndexController.loginUser);

清除session,session失效后可以通过前端跳转到login界面

router.get('/logout',(req,res)=>{
    req.sessiion.destroy(()=>{
        res.send({ok:1})
    })
})

刷新重置session

app.use((req,res,next)=>{
  if(req.url.includes("login")){ //登录页面放行
    req.session.date = Date.now() //刷新重置session
    next()
    return
  }
  if(req.session.user){
    next()
  }else{
    res.redirect('/login')
  }
})

将session存储在数据库保证每次后端变化前端不用重新获取session使用中间件connect-mongo

npm i connect-mongo

app.js的配置 

const MongoStore = require("connect-mongo")


app.use(session({ //注册session
  name:'sys', //名称
  secret:'qwer1234', //服务器生成session的签名
  cookie:{
    maxAge:1000*60*60, //过期时间
    secure:false //为true只有https协议才能访问cookie
  },
  resave:true, //刷新session
  saveUninitialized:true, //true一开始会给一个无效的cookie
  store:MongoStore.create({
    mongoUrl:'mongodb://127.0.0.1:27017/sys',
    ttl:1000*60*10 //过期时间
  })
}))

缺点由于session都是存在内存或者数据库中但是如果用户量越来越多所占的空间也就会越来越大那么就是问题了。 

JSON Web Token

cookie容易被csrf跨站请求伪造导致安全性问题可以存储在客户端localstorage并且实行加密使用sha256加密。前端请求会自动带上cookie但不会带上token。

缺点占带宽正常情况下要比session_id更大需要消耗更多流量挤占更多宽带。无法在服务端注销那么就很难解决劫持问题。性能问题JWT的卖点之一就是加密签名由于这个特性接收方得以验证JWT是否有效且被信任。对于有着严格性能要求的web应用并不理想尤其是对于单线程环境。

npm i jsonwebtoken

封装token

const jwt = require("jsonwebtoken")
const secret = 'test' 


const JWT = {
    generate(data,expires){
        return jwt.sign(data,secret,{expiresIn:expires})
    },
    verify(token){
        try {
            return jwt.verify(token,secret)
        } catch (error) {
            return false
        }
    }
}

module.exports = JWT

 在登录接口处引用

loginUser: async (req, res, next) => {
        const { username, password } = req.body
        //创建数据库模型要限制field类型一一对应数据库集合
        let arr = await UserService.loginUser(username, password)
        console.log(arr);
        const token = JWT.generate({id:arr[0]._id,username:arr[0].username},'10s')
        res.header('Authorization',token)
        if (arr.length == 0) {
            res.send({ code: 0 })
        } else {
            req.session.user = "123" //设置sesion对象
            res.send({ code: 1 })
        }
    },

前端使用axios可以在响应拦截器中获取到响应头中的token然后存储在localStorage中同理在发送请求时在请求头中添加localstorage中的token同时后端要时刻刷新token。

axios.post('/loginUser',{username:username.value,password:password.value}).then(res=>{
        console.log(res);
        if(res.code==0){
            alert(res)
        }else{
            this.localStorage.setItem('token',res.headers.authorization)
            location.href = '/'
        }
      })
//设置中间件session过期校验
app.use((req,res,next)=>{
  if(req.url.includes("login")){
    next()
    return
  }
  const token = req.headers['authorization']?.split(' ')[1]
  if(token){
    const payload = JWT.verify(token)
    if(payload){
      const newToken = JWT.generate({
        _id:payload._id,
        username:payload.username
      },'10s')
      next()
    }else{
      res.status(401).send({msg:'token过期'})
    }
  }else{
    next()
  }
})

<!DOCTYPE html>
<html>
<head>
    <link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>

<body>
    <h1>login页面</h1>
    <div>
        用户名<input id="username" type="text">
    </div>
    <div>
        密码<input id="password" type="text">
    </div>
    <button id="log">登录</button>
    <br>
</body>
<script>
    axios.interceptors.request.use((config) => {
        console.log(config);
        const token = localStorage.getItem('token')
        config.headers.Authorization = 'Bearer ${token}'
        return config
    }, err => {
        return Promise.reject(err)
    })
    axios.interceptors.response.use((response) => {
        this.localStorage.setItem('token', response.headers.authorization)//响应拦截获取存储token
        return response
    }, err => {
        return Promise.reject(err)
    })
    log.onclick = () => {
        axios.post('/loginUser', { username: username.value, password: password.value }).then(res => {
            console.log(res);
            if (res.code == 0) {
                alert(res)
            } else {
                location.href = '/'
            }
        })
    }
</script>

</html>

其它接口token失效要清除token并且跳转到登录界面

axios.interceptors.request.use((config) => {
    const token = localStorage.getItem('token')
    config.headers.Authorization = `Bearer ${token}`
    return config
  }, err => {
    return Promise.reject(err)
  })
  axios.interceptors.response.use((response) => {
    localStorage.setItem('token', response.headers.authorization)//响应拦截获取存储token
    return response
  }, err => {
    if (err.response.status == 401) {
      localStorage.removeItem('token')
      location.href = '/login'
    }
    return Promise.reject(err)
  })

  reg.onclick = () => {
    axios.post('/register', { username: username.value, password: password.value }).then(res => {
      console.log(res);
    })
  }

文件上传

前端通过multipart/form-data表单传递给后端但是后端没办法处理所以使用multer中间件

npm i multer 

 前端上传请求 upload.ejs文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <form action="/register" enctype="multipart/form-data" method="post">
        <input type="text" name="username">
        <input type="password" name="password">
        <input type="file" name="avatar">
        <input type="submit" value="提交">
    </form>
</body>
</html>

添加upload路由服务端渲染页面

var express = require('express');
const multer = require('multer');
var router = express.Router();
const upload = multer({dest:'public/uploads/'})
/* GET users listing. */
router.get('/', function(req, res, next) {
  res.render('upload')
});

module.exports = router;

 添加路由模块

var uploadRouter = require('./routes/upload');

app.use('/upload', uploadRouter);

 在注册接口中添加中间件法则会依次执行

const UserModel = require('../dbModel/UserModel')
var express = require('express');
var router = express.Router();
const multer = require('multer');
const upload = multer({dest:'public/uploads/'}) //指定前端传来图片文件放在的路径
const IndexController = require('../controllers/indexController')
/* GET home page. */
router.get('/', IndexController.queryUser);
//注册
router.post('/register',upload.single('avatar'),IndexController.addUser);//将头像传入到指定的目录
//修改
router.post('/update/:id',IndexController.updateUser );
// 删除
router.get('/delete', IndexController.deleteUser);
//登录页面
router.get('/login', IndexController.login);
//用户登录
router.post('/loginUser', IndexController.loginUser);

module.exports = router;

在controller中获取文件同时修改model限制模型穿参到service并且存入数据库

const mongoose = require("mongoose")

// 限制模型 类似于接口interface限制参数
const type = {
    username:String,
    password:String,
    avatar:String //添加头像的字段
}
// 模型user将会对应users集合
const UserModel = mongoose.model('user',new mongoose.Schema(type))

module.exports=UserModel
addUser: async (req, res, next) => {
        const { username, password } = req.body
        const avatar =req.file? `/uploads/${req.file.filename}`:`/images/default.png` //文件信息 避免不传文件给默认值避免报错
        //创建数据库模型要限制field类型一一对应数据库集合
        await UserService.addUser(username, password,avatar)
        res.send('register success');
    },
addUser: (username, password,avatar) => {
        return UserModel.create({ username, password,avatar }).then(result => {
        })
    },

前端获取服务端渲染

在service中查询接口中查询avatar的数据返回给index.ejs页面渲染通过img标签src属性来获取存入的图片

controller

queryUser: async (req, res, next) => { //服务端渲染
            //创建数据库模型要限制field类型一一对应数据库集合
            let page = 1
            let limit = 3
            let result = await UserService.queryUser(page, limit)
            console.log(result);
            res.render('index', { list: result });
    },

service 

queryUser:(page,limit)=>{
        return UserModel.find({}, ['username','avatar']).sort({ _id: -1 }).skip((page - 1) * limit).limit(3)
    },

 controller 返回渲染

queryUser: async (req, res, next) => { //服务端渲染
            //创建数据库模型要限制field类型一一对应数据库集合
            let page = 1
            let limit = 3
            let result = await UserService.queryUser(page, limit)
            console.log(result);
            res.render('index', { list: result });
    },
<!DOCTYPE html>
<html>

<head>
  <link rel='stylesheet' href='/stylesheets/style.css' />
</head>

<body>
  <h1>index页面</h1>
  <div>
    用户名<input id="username" type="text">
  </div>
  <div>
    密码<input id="password" type="text">
  </div>
  <button id="reg">注册</button>
  <button id="update">修改</button>
  <button id="deleteItem">删除</button>
  <br>
  <div>
    <ul>
      <% for (let index=0; index < list.length; index++) { %>
        <li>
          <%=list[index].username %>-  <img src="<%= list[index].avatar%>" alt=""> 
        </li>

        <% } %>
    </ul>

  </div>
</body>

</html>

在前端数据上使用非form的表单使用FormData对象

<!DOCTYPE html>
<html>

<head>
  <link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<style>
  img{
    width: 100px;
  }
</style>
<body>
  <h1>index页面</h1>
  <div>
    用户名<input id="username" type="text">
  </div>
  <div>
    密码<input id="password" type="text">
  </div>
  <div>
    头像<input type="file" id="avatar">
  </div>
  <button id="reg">注册</button>
  <button id="update">修改</button>
  <button id="deleteItem">删除</button>
  <br>
  <div>
    <ul>
      <% for (let index=0; index < list.length; index++) { %>
        <li>
          <%=list[index].username %>-  <img src="<%= list[index].avatar%>" alt=""> 
        </li>

        <% } %>
    </ul>

  </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
  axios.interceptors.request.use((config) => {
    const token = localStorage.getItem('token')
    config.headers.Authorization = `Bearer ${token}`
    return config
  }, err => {
    return Promise.reject(err)
  })
  axios.interceptors.response.use((response) => {
    localStorage.setItem('token', response.headers.authorization)//响应拦截获取存储token
    return response
  }, err => {
    if (err.response.status == 401) {
      localStorage.removeItem('token')
      location.href = '/login'
    }
    return Promise.reject(err)
  })
  reg.onclick = () => {
    const formdata = new FormData()
    formdata.append('username',username.value)
    formdata.append('password',password.value)
    formdata.append('avatar',avatar.files[0])
    axios.post('/register', formdata,{
      headers:{
        "Content-Type":"multipart/form-data"
      }
    }).then(res => {
      console.log(res);
    })
  }

  update.onclick = () => {
    fetch('/update/64eb4d5df24d574cfca6e518', {
      method: 'post',
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify({ username: '修改', password: '修改' })
    }).then(res => res.text()).then(res => {
      console.log(res);
    })
  }

  deleteItem.onclick = () => {
    fetch('/delete?id=64eb4df708eff7b8d60cadbf').then(res => res.text()).then(res => {
      console.log(res);
    })
  }
</script>

</html>

如果想要批量上传

//前端
<input type="file" id="avatar" multiple> 

//后端
//注册
router.post('/photoUpload',upload.array('photos',12),(req,res,next)=>{
    //req.files是文件数组信息
    //req.body 文本域数据
    //pjotos是对应的参数名
});

APIDOC--api文档生成工具

apidoc是一个简单的restful api 文档生成工具有以下特点

1.跨平台linuxwindowsmacOS等都支持

2.支持语言广泛

3.支持多个不同语言的多个项目生成一份文档

4.输出模版可自定义

5.根据模版生成mock数据

npm i -g apidoc

vscode可以下载插件 APIDoc Snippets

输入apiDocumentation会生成以下的代码我们可以按照自己的接口情况修改

/**
 *
 * @api {post} /register 添加用户
 * @apiName addUser
 * @apiGroup usergroup
 * @apiVersion  1.0.0
 *
 * @apiParam  {String} username 用户名
 * @apiParam  {String} password 密码
 * @apiParam  {File} avatar 头像
 *
 * @apiSuccess (200) {string} register success
 *
 * @apiParamExample  {multipart/form-data} Request-Example:
 * {
 *     username : 'test'
 *     password : '123123'
 *     avatar : file对象
 * }
 *
 *
 * @apiSuccessExample {string} 
 * register success
 */
//注册
router.post('/register',upload.single('avatar'),IndexController.addUser);

 在终端中输入指令生成doc文件夹

apidoc -i ./routes/ -o ./doc

doc文件夹中打开index.html页面就可以看到接口文档。

 配置apidoc

根目录下创建apidoc.json文件配置以下选项即可

{
    "name":"后台系统接口文档",
    "version":"1.0.0",
    "description":"接口文档",
    "title":"定制系统"
}

 koa2

koa编写web应用通过组合不同的generator可以免除重复繁琐的回调函数嵌套并极大地提升错误处理的效率。koa不再内核方法中绑定任何中间件仅仅提供了一个轻量优雅的函数库使编写web应用变得得心应手。防守打法第三方结算单

安装koa2

初始化npm init

安装npm i koa

//index.js
const Koa = require('koa')

const app = new Koa()

app.use((ctx,next)=>{ //ctx执行上下文中含有request以及response
    console.log('server start');
    ctx.response.body='hello world'
})
app.listen(3000)

启动服务nodemon index.js 

ctx.reqNode的request对象
ctx.resNode的response对象
ctx.requestKoa的request对象
ctx.responseKoa的response对象

在访问时可以不访问request或者response对象直接访问属性即可简写

ctx.response.bodyctx.body
ctx.request.pathctx.path
............

Koa vs express

通常会说Koa是洋葱模型是由于在于中间件的设计。express也是类似的但是express中间件使用了callback实现如果出现异步问题则会让你在执行顺序上感到困惑因此如果我们想要做接口耗时同级错误处理koa的这种中间件模式处理起来更方便些。最后一点响应机制也很重要koa不是立即响应使整个中间件处理完再最外层进行了响应而express是立即响应。

更加轻量

koa不提供内置的中间件不提供路由把路由这个库分离出来了。

Context对象

koa增加了一个context对象作为这次请求的上下文对象在koa中作为中间件的第一个参数传入。同时context上也挂载了request和response两个对象。和express类似这两个额对象都提供了大量的便捷方法辅助开发。

异步流程控制

express采用callback来处理异步koa1采用generatorkoa2采用async/await

generator和async/await使用同步的写法来处理异步明显好于callback和promise

中间件模型

express基于connect中间件线性模型

koa采用洋葱模型对于每个中间件在完成了一些事情后可以有丫的将控制权传递给下一个中间件并且能够等待它完成网后续的中间件完成处理后控制权又回到了自己

const Koa = require('koa')

const app = new Koa()

app.use(async (ctx,next)=>{ //ctx执行上下文中含有request以及response
    console.log('server start');
    console.log(1111);
    ctx.response.body='hello world' //类似于express中res.send
    await next()
    console.log(2222);
})

app.use(async (ctx,next)=>{ //ctx执行上下文中含有request以及response
    console.log(3333);
    await test()
    console.log(444);
})

function test(){
    return new Promise((resolve,reject)=>{
        resolve('123')
    })
}

app.listen(3000)

 以上代码结果为

1111
3333
444
2222

 路由

npm i koa-router

基本用法 

const Koa = require('koa')
const Router = require('koa-router')

const app = new Koa()
const router = new Router()

router.get('/list',(ctx,next)=>{
    ctx.body = [1,2,3]
})

.post('/list/:id',(ctx,next)=>{
    console.log(ctx.params); //id存储在params中
    ctx.body = 'add success'
})

.put('/list/:id',(ctx,next)=>{
    ctx.body = 'update success'
})

.delete('/list/:id',(ctx,next)=>{
    ctx.body = 'delete success'
})

app.use(router.routes()).use(router.allowedMethods()) //router.allowedMethods()能够给出错误提示

app.listen(3000)

分模块引入

//home.js
const Router = require('koa-router')
const router = new Router()

router.get('/',(ctx,next)=>{
    ctx.body = '<h1>HomePage</h1>'
})

module.exports = router
//user.js
const Router = require('koa-router')
const router = new Router()

router.get('/',(ctx,next)=>{
    ctx.body = [1,2,3]
})

.post('/:id',(ctx,next)=>{
    console.log(ctx.params); //id存储在params中
    ctx.body = 'add success'
})

.put('/:id',(ctx,next)=>{
    ctx.body = 'update success'
})

.delete('/:id',(ctx,next)=>{
    ctx.body = 'delete success'
})

module.exports = router
//list.js
const Router = require('koa-router')
const router = new Router()

router.get('/',(ctx,next)=>{
    ctx.body = [1,2,3]
})

.post('/:id',(ctx,next)=>{
    console.log(ctx.params); //id存储在params中
    ctx.body = 'add success'
})

.put('/:id',(ctx,next)=>{
    ctx.body = 'update success'
})

.delete('/:id',(ctx,next)=>{
    ctx.body = 'delete success'
})

module.exports = router
//routes文件夹index.js 整合路由
const Router = require('koa-router')
const router = new Router()
//引入路由
const userRouter = require('./user')
const listRouter = require('./list')
const homeRouter = require('./home')
//统一加前缀
// router.prefix("/api")
//注册路由级组件
router.use('/user',userRouter.routes(),userRouter.allowedMethods())
router.use('/list',listRouter.routes(),listRouter.allowedMethods())
router.use('/home',homeRouter.routes(),homeRouter.allowedMethods())
router.redirect('/',"/home") //重定向
module.exports = router
//index.js入口文件
const Koa = require('koa')
const router = require('./routes/index')

const app = new Koa()


//注册应用级组件
app.use(router.routes()).use(router.allowedMethods()) //router.allowedMethods()能够给出错误提示
app.listen(3000)

托管静态资源

npm i koa-static
const Koa = require('koa')
const router = require('./routes/index')
const static = require("koa-static")
const path = require('path')
const app = new Koa()


app.use(static(path.join(__dirname,'public')))
//注册应用级组件
app.use(router.routes()).use(router.allowedMethods()) //router.allowedMethods()能够给出错误提示
app.listen(3000)

在public下创建html文件并且在http://localhost:3000/center.html直接访问

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<link rel="stylesheet" href="./css/center.css">
<body>
    <div>
        center
    </div>
</body>
</html>

获取请求参数

获取get请求数据

获取get请求数据源头是koa中request对象中的query或者querystring方法query返回是格式化好的参数对象querystring返回的是请求字符串ctx有对request的api有直接引入放入方式所以获取get请求数据有两个途径

1.从上下文中直接获取请求对象ctx.query返回如{ a:1,b:2}请求字符串ctx.querystring返回 a=1&b=2。

2.从上下文中的request直接获取请求对象ctx.request.query返回如{ a:1,b:2}请求字符串ctx.request.querystring返回 a=1&b=2。

获取post参数

对于post请求的处理koa-bodyparser中间件可以吧koa2上下文的formData数据解析到ctx.request.body中不可简写。

npm i koa-bodyparser
const Koa = require('koa')
const router = require('./routes/index')
const static = require("koa-static")
const path = require('path')
const bodyParser = require('koa-bodyparser')
const app = new Koa()


app.use(bodyParser()) //获取前端post请求传来的参数
app.use(static(path.join(__dirname,'public')))
//注册应用级组件
app.use(router.routes()).use(router.allowedMethods()) //router.allowedMethods()能够给出错误提示
app.listen(3000)
router.post('/:id',(ctx,next)=>{
    console.log(ctx.request.body); //获取post传参
    ctx.body = 'add success'
})

ejs模版

npm i --save ejs

npm i koa-views
const Koa = require('koa')
const router = require('./routes/index')
const static = require("koa-static")
const path = require('path')
const bodyParser = require('koa-bodyparser')
const views = require('koa-views')
const app = new Koa()


app.use(bodyParser()) //获取前端传来的参数
app.use(static(path.join(__dirname,'public')))
app.use(views(path.join(__dirname,'views'),{extension:'ejs'})) //配置views为模版引擎
//注册应用级组件
app.use(router.routes()).use(router.allowedMethods()) //router.allowedMethods()能够给出错误提示
app.listen(3000)
//home.js
const Router = require('koa-router')
const router = new Router()

router.get('/',async (ctx,next)=>{
    console.log(123);
    // ctx.body = '<h1>HomePage</h1>'
    await ctx.render('home')
})

module.exports = router
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    homePage模版页面
</body>
</html>

cookie&session

cookie

koa提供了从上下文直接读取写入cookie的方法

ctx.cookies.get(name,[options]) //读取上下文请求中的cookie

ctx.cookies.set(name,[options]) //写入上下文中的cookie
session

koa-session-minimal适用于koa2的session中间件提供存储介质的读写接口

const Koa = require('koa')
const router = require('./routes/index')
const static = require("koa-static")
const path = require('path')
const bodyParser = require('koa-bodyparser')
const views = require('koa-views')
const app = new Koa()
const session = require('koa-session-minimal')


app.use(bodyParser()) //获取前端传来的参数
app.use(static(path.join(__dirname,'public')))
app.use(views(path.join(__dirname,'views'),{extension:'ejs'})) //配置views为模版引擎
//注册应用级组件
app.use(session({
    key:'test',
    cookie:{
        maxAge:1000*60*60
    }
}))
app.use(router.routes()).use(router.allowedMethods()) //router.allowedMethods()能够给出错误提示
app.listen(3000)
router.get('/',async (ctx,next)=>{
    ctx.session.user = {
        username:"test"
    }
    await ctx.render('home')
})
// session判断拦截
app.use(async (ctx,next)=>{
    if(ctx.url.includes("/login")){
        await next()
        return
    }
    if(ctx.session.user){
        //重置session
        ctx.session.mydate = Date.now()
        await next()
    }else{
        ctx.redirect('/login')
    }
})

koa-jwt

npm i jsonwebtoken

可以使用之前写好的jwt模块

const jwt = require("jsonwebtoken")
const secret = 'test' 

const JWT = {
    generate(data,expires){
        return jwt.sign(data,secret,{expiresIn:expires})
    },
    verify(token){
        try {
            return jwt.verify(token,secret)
        } catch (error) {
            return false
        }
    }
}

module.exports = JWT
const JWT = require('../util/jwt')

router.post('/login',async (ctx , next) => {
        const { username, password } = req.body
        //创建数据库模型要限制field类型一一对应数据库集合
        let arr = await UserService.loginUser(username, password)
        const token = JWT.generate({id:arr[0]._id,username:arr[0].username},'10s')
        ctx.set('Authorization',token)
        ctx.body = {
            ok:1
        }
    });

前端请求拦截器以及后端校验刷新token

axios.interceptors.request.use((config) => {
    const token = localStorage.getItem('token')
    config.headers.Authorization = `Bearer ${token}`
    return config
  }, err => {
    return Promise.reject(err)
  })


axios.interceptors.response.use((response) => {
    localStorage.setItem('token', response.headers.authorization)//响应拦截获取存储token
    return response
  }, err => {
    if (err.response.status == 401) {
      localStorage.removeItem('token')
      location.href = '/login'
    }
    return Promise.reject(err)
  })
app.use(async (ctx,next)=>{
    if(ctx.url.includes('login')){
        await next()
        return
    }
    const token = ctx.headers["authorization"]?.split("")[1]
    if(token){
        const payload = JWT.verify(token)
        if(payload){
            const newToken = JWT.generate({
                _id:payload.id,
                username:payload.username
            },'10s')
            ctx.set("authorization",newToken) //重置token
            await next()
        }else{
            ctx.status = 401
            ctx.body = {
                errCode:-1,errInfo:'token过期'
            }
        }
    }else{
        await next()
    }
})

文件上传

npm i --save @koa/multer multer

前端传参必须是表单的形式

axios.post('/register', formdata,{
      headers:{
        "Content-Type":"multipart/form-data"
      }
    }).then(res => {
      console.log(res);
    })
const multer = require('@koa/multer');
const upload = multer({dest:'public/uploads/'}) //目标地址

//前端传输的文件参数名为avatar
router.post('/upload',upload.single('avatar'),(ctx)=>{
    console.log(ctx.request.body,ctx.file) //表单信息文件信息
    ctx.body = 'upload success'
});

操作MongoDB

npm i mongoose

const mongoose =require('mongoose')

mongoose.connect('mongodb://127.0.0.1:27017/koa_test') 
//插入集合数据数据库会自动创建

 可以直接使用之前express的配置文件以及限制模型

const mongoose =require('mongoose')

mongoose.connect('mongodb://127.0.0.1:27017/koa_test') 
//插入集合数据数据库会自动创建
const mongoose = require("mongoose")

// 限制模型 类似于接口interface限制参数
const type = {
    username:String,
    password:String,
    // avatar:String
}
// 模型user将会对应users集合
const UserModel = mongoose.model('user',new mongoose.Schema(type))

module.exports=UserModel
router.post('/',async (ctx,next)=>{
    console.log(ctx.params); //id存储在params中
    const {username,password} = ctx.request.body
    await UserModel.create({username,password})
    ctx.body = 'add success'
})

MySql关系型数据库

免费数据库mysql与非关系型数据库区别

主要差异是数据存储的方式。关系型数据天然就是表格格式因此存储在数据表的行和列中。数据表可以彼此关联写作存储也很容易提取数据。

与其相反非关系型数据库不适合存储在数据表的行和列中而是大块组合在一起非关系型数据通常存储在数据集中就像文档键值对或者图结构你的数据及其特性是选择数据存储和提取方式的首要影响因素。

优点

易于维护都是使用表结构格式一致。

使用方便sql语言通用可用于复杂查询。

复杂操作支持sql可用于一个表以及多个表之间非常复杂的查询。

缺点

读写性能比较差尤其是海量数据的高效率读写

固定的表结构灵活度稍欠。

高并发读写需求传统关系型数据库来说硬盘I/O是一个很大的瓶颈。

非关系型数据库严格上不是一种数据库应该是一种数据结构化存储方法的集合可以是文档或者键值对等

优点

格式灵活存储数据的格式可以是keyvalue形式文档形式图片形式等使用灵活应用场景广泛而关系型数据库则只支持基础类型。

速度快nosql可以使用硬盘或者随机存储器作为载体而关系型数据库只能用硬盘。

高扩展性成本低nosql数据库部署简单基本都是开源。

缺点

不提供sql支持无事务处理数据结构相对复杂复杂查询方面稍欠。

sql语句

插入插入的数据的类型需要严格按照表中数据的数据类型

INSERT INTO students(id,name,score,gender) VALUES (null,"test",100,1)

更新

UPDATE student SET name = "test",score = 22 WHERE id=2

删除

DELETE FROM student WHERE id=2

查询

//查询所有
SELECT * FROM student WHERE 1

//查询所有数据某个字段
SELECT id,name,score,gender FROM student WHERE 1

//条件查询
SELECT * FROM student WHERE id=1

//模糊查询
SELECT * FROM student WHERE name like "&k%"

//排序
SELECT id,name,gender,score FROM student ORDER BY score 
SELECT id,name,gender,score FROM student ORDER BY score DESC //降序

//分页查询
SELECT id,name,gender,score FROM student LIMIT 50 OFFSET 0

//记录条数
SELECT COUNT(*) FROM student
SELECT COUNT(*)totalNumber FROM student
//多表查询(多表查询成为笛卡尔查询使用时要非常小心由于结果是目标表的行数乘积各有100条返回10000条)
SELECt * FROM students,class;

//要使用表名列名这样的方式来引用列和设置别名这样就避免了结果集的列名重复问题。
SELECT students.id sid,
        students.name,
        students.gender,
        students.score,
        class.id cid,
        class.name cname FROM students,class


//联表查询
SELECT s.id,
        s.name sname,
        s.gender,
        s.score,
        c.name cname FROM students s INNER JOIN class c ON s.class_id = c.id

//如果students的数据匹配不到也会被查出来对应的数据会为null
SELECT s.id,
        s.name sname,
        s.gender,
        s.score,
        c.name cname FROM students s LEFT JOIN class c ON s.class_id = c.id

//如果class的数据匹配不到也会被查出来对应的数据会为null
SELECT s.id,
        s.name sname,
        s.gender,
        s.score,
        c.name cname FROM students s RIGHT JOIN class c ON s.class_id = c.id

//如果class或students的数据匹配不到也都会被查出来对应的数据会为null
SELECT s.id,
        s.name sname,
        s.gender,
        s.score,
        c.name cname FROM students s FULL JOIN class c ON s.class_id = c.id

外键约束

InnoDB支持事务MyISAM不支持事务。这是mysql将默认存储引擎从MyISAM

变为InnoDB的重要原因之一。

InnoDB支持外键MyISAM不支持。对一个包含外键的InnoDB表转为MyISAM会失败。

cascade

在父表上update/delete时同步update/delete子表

的记录

set null

在父表上update/delete时将子表上匹配记录的列设为null要注意子表的外键列不能为null

no action

如果子表中有匹配的记录则不允许父表对应候选键进行update/delete操作

restrict

同no action都是立即检查外键约束。

nodejs 操作数据库

npm i mysql2
const express = require('express')
const msql2 = require('mysql2')
const app = express()

app.get('/',(req,res)=>{
    const config = getDBconfig()
    const promisePool = mysql2.createPool(config).promise()
    let name = "test"
    let gender = "1"
    let id = "1"
    //let result = await promisePool.query(`select * from students`)
    //let result = await promisePool.query(`select * from students where name=? and gender=?`,[name,gender] )
    //let result = await promisePool.query(`insert into student (name,gender) values (?,?)`,[name,gender])
    //let result = await promisePool.query(`update students set name=? where id=?`,[name,id])
    let result = await promisePool.query(`delete from students where id=?`,[id])
    res.send({
        ok:1,
        data:result[0]
    })
})

app.listen(3000)

function getDBconfig(){
    return {
        host:'127.0.0.1',
        port:3306,
        user:'root',
        password:'',
        database:'test',
        connectionLimit:1 //一个连接池
    }
}

Socket编程 

webSocket

应用场景弹幕媒体聊天协同编辑基于位置的应用体育实况更新股票基金报价实时更新。

webSocket并不是全新的协议而是利用了http协议来建立连接必须由浏览器发起因为请求协议是一个标准的http请求格式如下

GET ws://loacalhost:3000/ws/chat HTTP/1.1
Host:localhost
Upgrade:websocket
Connection:Upgrade
origin: http://localhost:3000
Sec-webSocket-Key:client-random-string
Sec- WebSocket-Version:  13

该请求和普通的HTTP请求有几点不同

1.get请求的地址不是类似/path/而是以ws://开头的地址

2.请求头Upgrade:websocket和connection:Upgrade 表示这个连接将要被转换为WebSocket连接。

3.Sec-webSocket-Key是用于标识这个连接并非用与加密数据。

4.Sec- WebSocket-Version指定了WebSocket的协议版本。

如果服务器接收该请求就会有如下反应

HTTP/1.1 101 Switching Protocols
Upgrade:webSocket
Connection:Upgrade
Sec-WebSocket-Accept: server-random-string

该响应代码101表示本次连接的http协议即将被更高更改后就是Upgrade:websocket指定的WebSocket协议。

版本号和子协议规定了双方能理解的数据格式以及是否支持压缩等。如果仅仅使用WebSocket

的api就不用关心这些。

现在一个webSocket连接就能建立成功浏览器和服务器可以随时主动发送消息给对方。消息有两种一种是文本一种是二进制通常我们发送json格式文本在浏览器处理会很方便。

为什么WebSocket链接可以事项全双工通信而http不可以实际上http是建立在tcp协议之上的tcp协议本身就实现了全双工通信但是http协议的请求-响应机制限制了全双工通信websocket链接建立以后其实就是简单规定了一下接下来咱们不用http协议直接互相发消息。

安全的websocket连接机制和https类似。首先浏览器用wss://xxx创建websocket连接时回先通过https创建安全的连接。然后该https链接升级为websocket连接底层走的仍然是安全的SSL/TSL协议。

浏览器支持

很显然要支持websocket通信浏览器要支持这个协议才能发出ws://xxx的请求。目前支持的主流浏览器如下chromefirefoxie>1=0Safari>=6Android >=4.4 ios>=8。

服务器支持

由于websocket是一个协议服务器具体怎么实现取决于所用的编程语言和框架本身。node.js本身支持的协议包括tcp和http要支持websocket协议需要对node.js提供法的httpServer做额外的开发。已经有若干基于node.js的稳定可靠的webSocket实现我们直接用npm安装即可。

ws模块

服务器

npm init 
npm i ws express
//webSocket响应
const WebSocket = require('ws')
const { WebSocketServer } = WebSocket
const wss = new WebSocketServer({ port: 8080 })
wss.on('connection', (ws) => {
    ws.on('message', (data) => {
        console.log(data);
        wss.clients.forEach((client) => { //给所有用户转发
            //检查所有用户是否处于连接状态不用发送给自己
            if (client!==ws && client.readyState == WebSocket.OPEN) { 
                client.send(data,{binary:false})//数据为非二进制数据否则会成为blob类型
            }
        })
    })

    ws.send("欢迎来到聊天室")
})

客户端

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    chatRoom
<script>
    let ws = new WebSocket("ws://localhost:8080")

    ws.onopen = ()=>{
        console.log('连接成功');
    }
    ws.onmessage = (msgObj)=>{
        console.log(msgObj);
    }
    ws.onerror = ()=>{
        console.log('err');
    }
</script>
</body>
</html>

获取客户端请求参数做登录鉴权



<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    chatRoom
<script>
    const WebSocketType = {
        Error:0, //错误走这里
        GroupList:1,
        GroupChat:2,
        SingleChat:3
    }
    let ws = new WebSocket(`ws://localhost:8080?token=${localStorage.getItem("token")}`)

    ws.onopen = ()=>{
        console.log('连接成功');
    }
    ws.onmessage = (msgObj)=>{
        console.log(JSON.parse(msgObj.data));
        const {data} = JSON.parse(msgObj)
        switch(data.type){
            case WebSocket.Error:
                localStorage.removeItem("token")
                break;
            case WebSocket.GroupChat:
                console.log(data)
                break;
            
        }
    }
    ws.onerror = ()=>{
        console.log('err');
    }
</script>
</body>
</html>
//webSocket响应
const WebSocket = require('ws')
const JWT = require('./jwt.js')
const { WebSocketServer } = WebSocket
const wss = new WebSocketServer({ port: 8080 })
wss.on('connection', (ws,req) => {
    const myUrl = new URL(req.url,"http://127.0.0.1:3000")
    let mytoken = myUrl.searchParams.get('token')
    const payload = JWT.verify(myToken)
    if(myToken){
        ws.send(createMessageWebSocket.GroupChat,null,"欢迎来到聊天室")
    }else{
        ws.send(createMessage(WebSocketType.Error,null,'token过期'))
    }
})

const WebSocketType = {
    Error:0, //错误走这里
    GroupList:1,
    GroupChat:2,
    SingleChat:3
    
}
function createMessage(type,user,data){
    return JSON.stringify({type,user,data})
}

 注意在登录时还是使用httpx协议获取token在聊天的页面使用ws协议

给前端返回数据



<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    chatRoom
<script>
    const WebSocketType = {
        Error:0, //错误走这里
        GroupList:1,
        GroupChat:2,
        SingleChat:3
    }
    let ws = new WebSocket(`ws://localhost:8080?token=${localStorage.getItem("token")}`)

    ws.onopen = ()=>{
        console.log('连接成功');
    }
    ws.onmessage = (msgObj)=>{
        console.log(JSON.parse(msgObj.data));
        const {data} = JSON.parse(msgObj)
        switch(data.type){
            case WebSocket.Error:
                localStorage.removeItem("token")
                location.href='/login'
                break;
            case WebSocket.GroupList:
                console.log(JSON.parse(data))
                break;
            case WebSocket.GroupChat:
                console.log(data)
                break;
            
        }
    }
    ws.onerror = ()=>{
        console.log('err');
    }
</script>
</body>
</html>
ws.send({type:1}) //获取所有用户

ws.send({type:2,data:"你好") //群发给所有人

ws.send({type:3,data:"你好",'tom') //私聊
//webSocket响应
const WebSocket = require('ws')
const JWT = require('./jwt.js')
const { WebSocketServer } = WebSocket
const wss = new WebSocketServer({ port: 8080 })
wss.on('connection', (ws,req) => {
    const myUrl = new URL(req.url,"http://127.0.0.1:3000")
    let mytoken = myUrl.searchParams.get('token')
    const payload = JWT.verify(myToken)
    if(myToken){
        ws.send(createMessageWebSocket.GroupChat,null,"欢迎来到聊天室"))
        ws.user = payload //user中包含username以及password
    }else{
        ws.send(createMessage(WebSocketType.Error,null,'token过期'))
    }
    ws.on('message', (data) => {
        console.log(data);
        const msgObj = JSON.parse(data)

        switch(msgObj.type){
            //获取所有成员
            case WebSocketType.GroupList:
                let userList = Array.from(wss.clients).map(el=>el.user)
                sendAll()
                bresk;
            //群发
            case WebSocketType.GroupChat:
                wss.clients.forEach((client) => { //给所有用户转发
                    //检查所有用户是否处于连接状态不用发送给自己
                    if (client!==ws && client.readyState == WebSocket.OPEN) { 
                        client.send(createMessage(WebSocketType.GroupChat,ws.user,msgObj.data),{binary:false})//数据为非二进制数据否则会成为blob类型
                    }
                })
                bresk;
            case WebSocketType.SingleChat:
                wss.clients.forEach((client) => {
                    if (client.user.username == msgObj.to && client.readyState == WebSocket.OPEN) { 
                        client.send(createMessage(WebSocketType.SingleChat,ws.user,msgObj.data),{binary:false})//数据为非二进制数据否则会成为blob类型
                    }
                })
                bresk;
        }
    })
    ws.on('close',()=>{
        //断开连接时候的回调
        sendAll()
    })
})

const WebSocketType = {
    Error:0, //错误走这里
    GroupList:1,
    GroupChat:2,
    SingleChat:3
    
}
function createMessage(type,user,data){
    return JSON.stringify({type,user,data})
}

function sendAll(){
    wss.clients.forEach((client) => { //给所有用户转发
        //检查所有用户是否处于连接状态不用发送给自己
        if (client.readyState == WebSocket.OPEN) { 
               ws.send(createMessge(WebSocket.GroupList,null,JSON.stringify(userList)))
        }
    })
}

参考文档ws - npm

socket.io模块 

有express/koa集成的用法可以自己选择能够自定义的创建想要的事件参考文档socket.io - npm

npm i socket.io

前端需要socket.io.min.js参考https://github.com/socketio/socket.io/blob/main/client-dist/socket.io.min.js

在复制后放在本地public文件夹下

固定的事件connectiondisconnect其他可自定义传递数据会自动转为JSON或解析JSON。

const express = require('express')
const JWT = require("./util/jwt")
const app = express()

app.use(express.static('public'))
const server = require('http').createServer(app)
const io = require('socket.io')(server)
io.on('connection',(socket,req)=>{
    const payload = JWT.verify(socket.handshake.query.token)
    console.log(payload);
    if(payload){
        console.log(222);
        socket.user = payload
        //发送欢迎
        socket.emit("connect",{user:socket.user,data:'欢迎来到聊天室'})

    }else{
        socket.emit("error",{errorMessage:"token过期"})
    }
})
server.listen(3000)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    chatRoom
    <script src="./js/socketio.js"></script>
<script>
    
    //const socket = io() //默认连接localhos:3000
    const socket = io(`ws://localhost:3000?token=${
        localStorage.getItem("token")
    }`)
    socket.on("connect",(msg)=>{
        console.log(msg);
    })
    socket.on("error",(msg)=>{
        localStorage.removeItem("token")
        location.href = '/login'
    })
</script>
</body>
</html>

mocha

单元测试是用来对弈歌迷快一个函数或者一个类来进行正确性检验的测试工作mocha是js的一种单元测试框架既可以咋浏览器环境下运行也可以在node.js环境下运行。使用mocha只需要专注于编写单元测试本身然偶让mocha去自动运行所有的测试并给出测试结果。

特点既可以测试简单的js函数又可以测试异步代码因为异步是js的特性之一可以自动运行所有测试也可以只运行特定的测试可以支持beforeafterbeforeEach和afterEach来编写初始化代码。

编写测试

npm i init

npm i mocha
"scripts": {
    "test": "mocha"
  },
npm test
//sum.js 测试文件
function sum(...rest){
    return rest.reduce((pre,next)=>{
        return pre + next
    },0)
}

module.exports = sum

配合mocha测试创建test文件夹将要执行的test1.js文件放入文件夹下。

const sum = require("../sum")
const { describe } = require("mocha");
const assert = require("assert")//node内置模块断言函数

//describe 一组测试可嵌套
describe("大的组1测试",()=>{
    describe('小的组1测试',()=>{
        //it 一个测试
        it('sum()结果返回为0',()=>{
            assert.strictEqual(sum(),0)
        })
        it('sum(1)结果返回为1',()=>{
            assert.strictEqual(sum(1),1)
        })
        it('sum(1,2)结果返回为3',()=>{
            assert.strictEqual(sum(1,2),3)
        })
        it('sum(1,2,3)结果返回为6',()=>{
            assert.strictEqual(sum(1,2,3),6)
        })
    })
    describe('小的组2测试',()=>{
        
    })
})
describe("大的组2测试",()=>{

})

chai断言库

mocha允许你使用任意的断言库比如

should.js BDD风格贯穿始终

expect.js expect()样式断言

chai expect()assert()和should风格的断言

better-assert C风格的自文档化的assert()

unexpected 可扩展到饿BDD断言工具

npm i chai
// assert风格
const chai = require('chai')
const { describe } = require("mocha");
const assert = chai.assert
const sum = require("../sum")

//describe 一组测试可嵌套
describe("大的组1测试",()=>{
    describe('小的组1测试',()=>{
        //it 一个测试
        it('sum()结果返回为0',()=>{
            assert.equal(sum(),0)
        })
        it('sum(1)结果返回为1',()=>{
            assert.equal(sum(1),1)
        })
        it('sum(1,2)结果返回为3',()=>{
            assert.equal(sum(1,2),3)
        })
        it('sum(1,2,3)结果返回为6',()=>{
            assert.equal(sum(1,2,3),6)
        })
    })
    describe('小的组2测试',()=>{
        
    })
})
describe("大的组2测试",()=>{

})
//should风格
const chai = require('chai')
const { describe } = require("mocha");
const sum = require("../sum")
chai.should()

//describe 一组测试可嵌套
describe("大的组1测试",()=>{
    describe('小的组1测试',()=>{
        //it 一个测试
        it('sum()结果返回为0',()=>{
            sum().should.exist.and.equal(0)
            //sum.should.be.a('string')
            //sum.should.not.equal(6)
            //sum.should.have.length(5)
        })
    })
    describe('小的组2测试',()=>{
        
    })
})
describe("大的组2测试",()=>{

})
//expect风格
const chai = require('chai')
const { describe } = require("mocha");
const sum = require("../sum")
const expect = chai.expect

//describe 一组测试可嵌套
describe("大的组1测试",()=>{
    describe('小的组1测试',()=>{
        //it 一个测试
        it('sum()结果返回为0',()=>{
            expect(sum()).to.equal(0)
            //expect(sum()).to.be.at.most(5)
            //expect(sum()).to.be.at.least(3)
            //expect(sum()).to.be.at.within(1,4)
            //expect(sum()).to.exist
            //expect(sum()).to.be.a('string')
            //expect(sum()).to.not.equal('你好')
            //expect(sum()).to.have.length(5)
        })
    })
    describe('小的组2测试',()=>{
        
    })
})
describe("大的组2测试",()=>{

})

 异步测试

const fs = require("fs")
const fsp = fs.promises
const { describe } = require("mocha");
const assert = require("assert")

//describe 一组测试可嵌套
describe('异步测试1', () => {
    //it 一个测试
    it('异步读取文件', (done) => {
        fs.readFile('./1.txt','utf8',(err,data)=>{
            if(err){
                done(err)
            }else{
                assert.strictEqual(data,'12344')
                done()
            }
        })
    })
})
describe('异步测试2', () => {
    //it 一个测试
    it('异步读取文件', async () => {
        let data = await fsp.readFile('./1.txt','utf8')
        assert.strictEqual(data,'123446')
    })
})

http测试

实现能够在测试时启动服务器

sudo npm i supertest
//两种
const { describe } = require("mocha");
const assert = require("assert")
const axios = require('axios')
describe('测试接口',()=>{
    it('返回接口数据',async ()=>{
        let res = await axios.get('http://localhost:3000/')
        assert.strictEqual(res.data,"你好")
    })
})


----------------------------------------
//能够自己启动服务器并且关闭
const { describe } = require("mocha");
const supertest = require('supertest')
const app = require("../app")
describe('测试接口',()=>{
    let server = app.listen(3000)
    it('返回接口数据',async ()=>{
        await supertest(server).get("/").expect("Content-Type",/text\/plain/).expect(200,'你好')
    })

    after(()=>{ //mocha钩子函数
        server.close()//结束执行后关闭服务器
    })
})
//对应上述两种
const koa = require('koa')
const app = new koa()

app.use((ctx)=>{
    ctx.body="你好"
})

app.listen(3000)


---------------------------------------------------

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

app.use((ctx)=>{
    ctx.body="你好"
})

// app.listen(3000)

module.exports = app

钩子函数

const { describe } = require("mocha");
const supertest = require('supertest')
const app = require("../app")
describe('测试接口', () => {
    let server;
    it('返回接口数据', async () => {
        await supertest(server).get("/").expect("Content-Type", /text\/plain/).expect(200, '你好')
    })
    before(() => {//在用所有例执行之前调用
        server = app.listen(3000) 
    })
    after(() => { //所有用例执行后执行关闭服务器
        server.close()
    })
    beforeEach(()=>{//在每个用例执行之前

    })
    afterEach(()=>{//在每个用例执行后

    })
})

 目前基础就是这些有后续的再补充。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: NodeJS