Nodejs 相关知识

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 暴露属性或者方法。

javascript 复制代码
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

javascript 复制代码
{
  "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"
}
javascript 复制代码
//暴露出 引入
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

javascript 复制代码
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文件有已经定义好的函数,前后端保持一致。

javascript 复制代码
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'
    }
}
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>
    
</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头,解决跨域

javascript 复制代码
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'
    }
}
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>
    
</body>
<script>
    fetch('http://localhost:8000/home').then(res=>res.json()).then(res=>{
        console.log(res);
    })
</script>
</html>
get

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

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>
    
</body>
<script>
    fetch('http://localhost:8000/home').then(res=>res.json()).then(res=>{
        console.log(res);
    })
</script>
</html>
javascript 复制代码
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
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>
    
</body>
<script>
    fetch('http://localhost:8000/home').then(res=>res.json()).then(res=>{
        console.log(res);
    })
</script>
</html>
javascript 复制代码
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

javascript 复制代码
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,第二个是否将参数解析为对象格式,默认是false,pathname是对应的url,query是请求的请求参数对象。

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

javascript 复制代码
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

javascript 复制代码
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地址以及参数

javascript 复制代码
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纯粹是为网址查询字符串设计的。

javascript 复制代码
    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

拼接作用:

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

详情参考:URL | Node.js v19 API

format(新)

参数:

url:WHATWG 网址对象

options:auth<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])方法允许对输出进行基本的自定义。

javascript 复制代码
    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。

javascript 复制代码
    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被绝对解析,并且在转换为文件网址时正确编码网址控制字符。

javascript 复制代码
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的预期将网址对象转换为普通选项对象。

javascript 复制代码
    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解析

javascript 复制代码
const querystring = require('querystring')

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

stringify编码

javascript 复制代码
const querystring = require('querystring')

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

escape 转译特殊字符

javascript 复制代码
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 解码特殊字符

javascript 复制代码
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添加监听的方法,在数据处理好后抛出,并发送个给前端。

javascript 复制代码
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的写法

javascript 复制代码
const fs = require('fs').promises

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

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

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

javascript 复制代码
//查看文件或文件夹信息
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提供的又一个仅在服务端可用的模块,目的是支持流这种数据结构。

创建可读流

javascript 复制代码
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);
})

创建可写流

javascript 复制代码
const ws = fs.createWriteStream('./2.txt','utf-8')

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

ws.end()

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

javascript 复制代码
const fs = require('fs')
const rs = fs.createReadStream('./1.txt')
const ws = fs.createWriteStream('./2.txt')
rs.pipe(ws)
zlib

压缩资源传输

javascript 复制代码
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 的形式展示。

javascript 复制代码
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,就可以得到结果(md5,sha1,sha256)

javascript 复制代码
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还需要一个秘钥。只要秘钥发生变化,生成的签名也就不同。

javascript 复制代码
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支持,但是需要自己封装好函数便于使用。

javascript 复制代码
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接口

javascript 复制代码
//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 
javascript 复制代码
const server = require('./server')
const route= require("./route")
const api = require("./api")

server.use({...route,...api}) //合并接口
server.start()
javascript 复制代码
//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
javascript 复制代码
//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

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>
<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模块即可

javascript 复制代码
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'

javascript 复制代码
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
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="./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

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

基本路由
javascript 复制代码
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 格式即可,

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

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

javascript 复制代码
app.get('/ab+cd',(req,res)=>{
    res.send('ab+cd') 
})

可以在之间写任意内容

javascript 复制代码
app.get('/ab*cd',(req,res)=>{
    res.send('ab*cd') 
})

匹配正则表达式

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

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

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

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

javascript 复制代码
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请求的方法,例如get,put,post等。在请求接口前先验证token是否有效。

javascript 复制代码
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 等。

javascript 复制代码
//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
javascript 复制代码
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个参数,(err,req,res,next)err是参数状态码。

javascript 复制代码
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来获取查询字符串

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

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

javascript 复制代码
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('启动了');
})
javascript 复制代码
router.post('/homePost',(req,res)=>{
    console.log(req.body); //必须配置中间件
    res.send('homePost')
})

router.post('/homePostJson',(req,res)=>{
    console.log(req.body); //必须配置中间件
    res.send('homePost')
})
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>
    <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可以方便地托管静态文件,例如图片,css,js文件等。

将静态资源文件所在的目录作为参数传递给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

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

javascript 复制代码
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,动态创建页面,真实接口数据,前后联调。

服务端渲染:后端镶嵌模版,后端渲染模版,SSR(Server-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}) %> 导入公模版内容

html 复制代码
npm i ejs
javascript 复制代码
//服务端
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('启动了');
})

配置路由接口

javascript 复制代码
//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
javascript 复制代码
//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文件

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>
    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>
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文件,那么应该怎么做?

javascript 复制代码
 //配置模版引擎
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

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

javascript 复制代码
express myapp --view=ejs

npm i

npm start 

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

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

MongoDB

关系型与非关系型数据库

关系型数据库:sql语句增删改查操作,保持事务的一致性,事务机制(回滚),包括mysql,sqlserver,db2,oracle。

非关系型数据库:no sql:not only sql;轻量,高效,自由,包括mongodb,Hbase,redis。

mongoDB的优势:

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

|-------------|-------------|--------|
| sql | MongoDB | 说明 |
| database | database | 数据库 |
| table | collection | 表/集合 |
| row | document | 表行/文档 |
| column | field | 表列字段/域 |
| index | index | 索引 |
| table joins | 不支持 | 表连接 |
| primary key | primary key | 主键 |

安装数据库

Install MongoDB Community Edition --- MongoDB Manual

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

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

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

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

首先tap一个仓库

javascript 复制代码
brew tap mongodb/brew

安装社区版

javascript 复制代码
brew install mongodb-community

安装好mongodb以后,启动:

javascript 复制代码
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}) 相当于sql:update users set name = "wanger" where age = 25 db.users.update({name:'zhangsan'},{inc:{age:50}},false,true) 相当于sql:update users set age = age + 50 where name = 'zhangsan' 减的话,inc:{age:-50}即可 db.users.update({name:'zhangsan'},{inc:{age:50},$set{name:'lisi'}},false,true) 相当于sql:update 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() 相当于sql:select * from userInfo |
| 查询某字段去重后的数据 | db.userInfo.distinct('name') 相当于sql:select distinct name from userInfo |
| 查询age=22的数据 | db.userInfo.find({age:22}) 相当于sql:select * from userInfo where age = 22 |
| 查询age>22的数据 | db.userInfo.find({age:{gt:22}}) 相当于sql:select \* from userInfo where age \> 22 | | 查询age\<22的数据 | db.userInfo.find({age:{lt:22}}) 相当于sql:select * from userInfo where age < 22 |
| 查询age>=22的数据 | db.userInfo.find({age:{gte:22}}) 相当于sql:select \* from userInfo where age \> =22 | | 查询age\<=22的数据 | db.userInfo.find({age:{lte:22}}) 相当于sql:select * from userInfo where age <= 22 |
| 查询age>=22 age<=26的数据 | db.userInfo.find({age:{gte:26,lte:22}}) 相当于sql:select * from userInfo where age >= 22 and age <= 26 |
| 查询name包含 mongo的数据 | db.userInfo.find({name:/mongo/}),写入正则表达式 相当于sql:select * from userInfo where name like 'mongo%' |
| 查询指定列 name,age数据(想显示哪列,就将字段设置为1,不想显示的就设置为0) | db.userInfo.find({},{name:1,age:1}) 相当于sql:select name,age from userInfo |
| 查询指定列 name,age数据 age>25 | db.userInfo.find({age:{gt:25}},{name:1,age:1}); 相当于sql:select 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}) sql:select \* from userInfo where name='zhangsan' and age=22 | | 查询前5条数据 | db.userInfo.find().limit(5) sql:select top 5 \* from userInfo | | 查询10条以后的数据 | db.userInfo.find().skip(10) sql:select \* 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}]}) sql:select * from userInfo whrer age = 22 or age = 25 |
| 查询第一条数据 | db.userInfo.findOne() / db.userInfo.find().limit(1) sql:select 1 * from userInfo |
| 查询某个结果集的记录条数 | db.userInfo.find({age:{$gte:25}}).count() sql:select count(*) from userInfo where age >= 20 |

可视化工具进行增删改查

Robomogo Robo3T adminMongo ,可自行下载使用

使用nodejs操作数据库

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

npm i mongoose

javascript 复制代码
const mongoose =require('mongoose')

mongoose.connect('mongodb://127.0.0.1:27017/express_test') 
//插入集合数据,数据库会自动创建
javascript 复制代码
#!/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存储数据比较自由,所以需要添加这样的模型来限制传入的字段,以及字段类型,新建限制模型

javascript 复制代码
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

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

javascript 复制代码
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页面

html 复制代码
<!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接口
javascript 复制代码
        //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动词表示动作进行操作资源,下面对比,右边为规范的写法

javascript 复制代码
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  //删除

使用方式

javascript 复制代码
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} //删除用户信息
javascript 复制代码
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);
      })
javascript 复制代码
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.js:c层负责处理业务逻辑(v与m之间的沟通)

views:v层 负责展示页面

model:m层 负责处理数据(增删改查)

创建controllers service

javascript 复制代码
//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;
javascript 复制代码
//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
javascript 复制代码
//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中配置

javascript 复制代码
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')
  }
})

在登录时添加session,indexController中配置

javascript 复制代码
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接口文件中引用

javascript 复制代码
router.post('/loginUser', IndexController.loginUser);

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

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

刷新重置session

javascript 复制代码
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的配置:

javascript 复制代码
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

javascript 复制代码
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

在登录接口处引用

javascript 复制代码
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。

javascript 复制代码
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 = '/'
        }
      })
javascript 复制代码
//设置中间件,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')
    res.header('Authorization',newToken)
      next()
    }else{
      res.status(401).send({msg:'token过期'})
    }
  }else{
    next()
  }
})
html 复制代码
<!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并且跳转到登录界面

javascript 复制代码
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文件

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>
    <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路由服务端渲染页面

javascript 复制代码
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;

添加路由模块

javascript 复制代码
var uploadRouter = require('./routes/upload');

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

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

javascript 复制代码
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,并且存入数据库

javascript 复制代码
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
javascript 复制代码
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');
    },
javascript 复制代码
addUser: (username, password,avatar) => {
        return UserModel.create({ username, password,avatar }).then(result => {
        })
    },

前端获取服务端渲染

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

controller

javascript 复制代码
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

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

controller 返回渲染

javascript 复制代码
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 });
    },
html 复制代码
<!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对象:

html 复制代码
<!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>

如果想要批量上传

javascript 复制代码
//前端
<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.跨平台,linux,windows,macOS等都支持

2.支持语言广泛

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

4.输出模版可自定义

5.根据模版生成mock数据

npm i -g apidoc

vscode可以下载插件 APIDoc Snippets

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

javascript 复制代码
/**
 *
 * @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文件夹

javascript 复制代码
apidoc -i ./routes/ -o ./doc

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

配置apidoc

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

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

koa2

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

安装koa2

初始化:npm init

安装:npm i koa

javascript 复制代码
//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.req | Node的request对象 |
| ctx.res | Node的response对象 |
| ctx.request | Koa的request对象 |
| ctx.response | Koa的response对象 |

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

|-------------------|----------|
| ctx.response.body | ctx.body |
| ctx.request.path | ctx.path |
| ...... | ...... |

Koa vs express

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

更加轻量

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

Context对象

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

异步流程控制

express采用callback来处理异步,koa1采用generator,koa2采用async/await

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

中间件模型

express基于connect中间件,线性模型

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

javascript 复制代码
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

路由
javascript 复制代码
npm i koa-router

基本用法

javascript 复制代码
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)

分模块引入

javascript 复制代码
//home.js
const Router = require('koa-router')
const router = new Router()

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

module.exports = router
javascript 复制代码
//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
javascript 复制代码
//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
javascript 复制代码
//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
javascript 复制代码
//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)

托管静态资源

javascript 复制代码
npm i koa-static
javascript 复制代码
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直接访问

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中,不可简写。

javascript 复制代码
npm i koa-bodyparser
javascript 复制代码
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)
javascript 复制代码
router.post('/:id',(ctx,next)=>{
    console.log(ctx.request.body); //获取post传参
    ctx.body = 'add success'
})
ejs模版
javascript 复制代码
npm i --save ejs

npm i koa-views
javascript 复制代码
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)
javascript 复制代码
//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
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>
    homePage模版页面
</body>
</html>
cookie&session

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

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

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

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

javascript 复制代码
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)
javascript 复制代码
router.get('/',async (ctx,next)=>{
    ctx.session.user = {
        username:"test"
    }
    await ctx.render('home')
})
javascript 复制代码
// 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模块

javascript 复制代码
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
javascript 复制代码
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

javascript 复制代码
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)
  })
javascript 复制代码
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()
    }
})
文件上传
javascript 复制代码
npm i --save @koa/multer multer

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

javascript 复制代码
axios.post('/register', formdata,{
      headers:{
        "Content-Type":"multipart/form-data"
      }
    }).then(res => {
      console.log(res);
    })
javascript 复制代码
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

javascript 复制代码
const mongoose =require('mongoose')

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

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

javascript 复制代码
const mongoose =require('mongoose')

mongoose.connect('mongodb://127.0.0.1:27017/koa_test') 
//插入集合数据,数据库会自动创建
javascript 复制代码
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
javascript 复制代码
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是一个很大的瓶颈。

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

优点:

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

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

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

缺点:

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

sql语句

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

javascript 复制代码
INSERT INTO students(id,name,score,gender) VALUES (null,"test",100,1)

更新

javascript 复制代码
UPDATE student SET name = "test",score = 22 WHERE id=2

删除

javascript 复制代码
DELETE FROM student WHERE id=2

查询

javascript 复制代码
//查询所有
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
javascript 复制代码
//多表查询(多表查询成为笛卡尔查询,使用时要非常小心由于结果是目标表的行数乘积,各有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 操作数据库
javascript 复制代码
npm i mysql2
javascript 复制代码
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请求,格式如下:

javascript 复制代码
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的协议版本。

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

javascript 复制代码
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的请求。目前支持的主流浏览器如下:chrome,firefox,ie>1=0,Safari>=6,Android >=4.4 ,ios>=8。

服务器支持

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

ws模块

服务器

javascript 复制代码
npm init 
npm i ws express
javascript 复制代码
//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("欢迎来到聊天室")
})

客户端

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>
    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>

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

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>
javascript 复制代码
//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(createMessage(WebSocket.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协议

给前端返回数据

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")
                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>
javascript 复制代码
ws.send({type:1}) //获取所有用户

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

ws.send({type:3,data:"你好",'tom') //私聊
javascript 复制代码
//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(createMessage(WebSocket.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

javascript 复制代码
npm i socket.io

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

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

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

javascript 复制代码
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: '欢迎来到聊天室' })
        //获取列表
        socket.on('groupList', () => {
            //用户数据存储在io.sockets.sockets,以map对象存储
            const arr = Array.from(io.sockets.sockets).map(item => item[1].user)
            //发送所有人io.sockets可以直接发所有人
            io.sockets.emit('groupList', { data: arr.filter(el=>el) })//首次或刷新会出现报错的情况,是因为用户会有个断开再连接的过程,所以或出现undfined的情况,过滤后就不会了
            //断开连接时的再次发送所有人list
            socket.on('disconnect', () => {
                socket.emit('groupList', { data: arr })
            })
            //群聊
            socket.on("groupChat", (msg) => {
                //所有人都发
                // io.sockets.emit('groupChat',{user:socket.user, data:msg})
                // 除了自己不发,其他人发
                socket.broadcast.emit('groupChat', { user: socket.user, data: msg })
            })
            socket.on("single", (msg) => {
                Array.from(io.sockets.sockets).forEach(item => {
                    if (item[1].user.username == msg.to) {
                        item[1].emit("single", { user: socket.user, msg: msg.data })
                    }
                })
            })
        })
    } else {
        socket.emit("error", { errorMessage: "token过期" })
    }

})
server.listen(3000)
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 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'
    })
    socket.on("groupList",(msg)=>{
        console.log(msg);
    })
    socket.on("groupChat",(msg)=>{
        console.log(msg);
    })
    socket.on("single",(msg)=>{
        console.log(msg);
    })
    socket.emit('groupChat',{data:"群聊"})
    socket.emit('single',{data:"私聊",to:"**"})
</script>
</body>
</html>

mocha

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

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

编写测试
javascript 复制代码
npm i init

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

module.exports = sum

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

javascript 复制代码
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断言工具

javascript 复制代码
npm i chai
javascript 复制代码
// 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测试",()=>{

})
javascript 复制代码
//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测试",()=>{

})
javascript 复制代码
//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测试",()=>{

})
异步测试
javascript 复制代码
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测试

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

javascript 复制代码
sudo npm i supertest
javascript 复制代码
//两种
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()//结束执行后关闭服务器
    })
})
javascript 复制代码
//对应上述两种
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
钩子函数
javascript 复制代码
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(()=>{//在每个用例执行后

    })
})

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

相关推荐
STER labo20 分钟前
mysql配置环境变量——(‘mysql‘ 不是内部或外部命令,也不是可运行的程序 或批处理文件解决办法)
数据库·mysql·adb
dreamZhanglx40 分钟前
MySQL进阶
数据库·mysql
xmjd msup41 分钟前
MySQL 函数
数据库·mysql
donecoding1 小时前
别再让 pnpm 跟着 nvm 跑了!独立安装终极指南
前端·node.js·前端工程化
jefl jxak2 小时前
mysql用户名怎么看
数据库·mysql
unDl IONA2 小时前
mysql之如何获知版本
数据库·mysql
俺不要写代码2 小时前
数据库:约束
数据库·mysql
WL_Aurora3 小时前
MySQL 5 卸载到 MySQL 8 安装完整指南(不踩坑版)
数据库·mysql
灰阳阳3 小时前
MySQL的基本架构
数据库·mysql·架构
@小柯555m3 小时前
MySql(高级操作符--Where in 和Not in)
数据库·sql·mysql