一、node.js基础
开发环境搭建
-
下载安装包,一路点击下一步
-
如何确认已下载安装好:集成终端输入node --version,查看版本号
node --version
1、运行结果查看
// 创建一个node.js 文件
console.log("你好");
控制台输出:你好
在当前文件夹下打开控制台输入: node .\01-node.js ,然后回车即可
[.\01-node.js]路径名称
2、模块化语法
-
在那用在那引入
- 通过 exports 或者 module.exports 暴露属性或者方法
function test() {
console.log("test--aaa");
}
function upp(params) {
let str = params.slice(0,1).toUpperCase() + params.slice(1)
console.log(str);
}
// 导出方法一
module.exports = {
test,
upp
}
// 导出方法二
exports.test = test;
exports.upp= upp;
引入使用
const ModuleA = require("./a")
ModuleA.upp("haifei")
ModuleA.test()
3、Npm&Yarn
01 npm的使用
npm init 如果报错请参考npm init 报错 npm install 包名 –g (uninstall,update) npm install 包名 --save-dev (uninstall,update) npm list -g (不加-g,列举当前目录下的安装包) npm info 包名(详细信息) npm info 包名 version(获取最新版本) npm install md5@1(安装指定版本) npm outdated( 检查包是否已经过时) "dependencies": { "md5": "^2.1.0" } ^ 表示 如果 直接npm install 将会 安md5 2.*.* 最新版本 "dependencies": { "md5": "~2.1.0" } ~ 表示 如果 直接npm install 将会 安装md5 2.1.* 最新版本 "dependencies": { "md5": "*" } * 表示 如果 直接npm install 将会 安装 md5 最新版本
02 全局安装 nrm
NRM (npm registry manager)是npm的镜像源管理工具,有时候国外资源太慢,使用这个就可以快速地在 npm 源间切换。
查看当前使用镜像: npm config get registry
手动切换方法: npm config set registry https://registry.npm.taobao.org
安装 nrm
在命令行执行命令,npm install -g nrm,全局安装nrm。
使用 nrm
执行命令 nrm ls 查看可选的源。 其中,带*的是当前使用的源,上面的输出表明当前源是官方源。
切换 nrm
如果要切换到taobao源,执行命令nrm use taobao。
测试速度
你还可以通过 nrm test 测试相应源的响应时间。
nrm test
npm install -g cnpm --registry=https://registry.npmmirror.com
03 yarn使用
npm install -g yarn
对比npm: 速度超快: Yarn 缓存了每个下载过的包,所以再次使用时无需重复下载。 同时利用并行下载以最大化资源利用率,因此安装速度更快。 超级安全: 在执行代码之前,Yarn 会通过算法校验每个安装包的完整性。 开始新项目 yarn init 添加依赖包 yarn add [package] yarn add [package]@[version] yarn add [package] --dev 升级依赖包 yarn upgrade [package]@[version] 移除依赖包 yarn remove [package] 安装项目的全部依赖 yarn install
4、ES模块化写法
package.json 文件中设置 "type":"module"
导出方法1:
const moduleA = {
getname(){
console.log('haifei');
}
}
export default moduleA;
导出方法1的引入方法:
import moduleA from "./module/moduleA.js"; // 注意这里的.js 不可以省略
moduleA.getname();
导出方法2:
const moduleB = {
getname(){
console.log("moduleB");
}
}
export {
moduleB
}
导出方法2的引入方法:
import { moduleB } from "./module/moduleB.js";
moduleB.getname();
5、内置模块
1. http模块
要使用 HTTP 服务器和客户端,则必须 require('http')
。
// 引入内置http模块
const http = require('http');
//创建服务器
http.createServer(function (req, res) {
// 响应头 200 是状态码
// res.writeHead(200, {"Content-Type": "application/json"});
// res.write(JSON.stringify({"success": true}))
// 渲染json字符串
// res.writeHead(200, {"Content-Type": "text/plain;charset=utf-8"});
// res.write("Hello World,你好");
// 渲染HTML格式
res.writeHead(200, {"Content-Type": "text/html;charset=utf-8"});
res.write("<h1>Hello World,你好</h1>")
res.end()
}).listen(3000,()=>{ //3000 是端口号
console.log("server listening on port");
})
req.url 拿到的是相对路径 例如:/home
npm i -g nodemon 或者 npm i -g node-dev安装热更新
1.1 接口:jsonp
const http = require('http');
const url = require('url');
const server = http.createServer((req, res) => {
let urlobj = url.parse(req.url,true)
console.log(urlobj);
switch (urlobj.pathname) {
case "/api/aaa":
res.end(`${urlobj.query.callback}(${JSON.stringify({
name: 'haifei',
age: 18
})})
`)
break;
default:
res.end("404")
break;
}
})
server.listen(3000)
1.2 跨域:CORS
const http = require('http');
const url = require('url');
const server = http.createServer((req, res) => {
let urlobj = url.parse(req.url,true)
console.log(urlobj);
res.writeHead(200, {
'Content-Type': 'application/json; charset=utf-8',
// 添加cors头,允许所有域访问
'access-control-allow-origin': '*'
})
switch (urlobj.pathname) {
case "/api/aaa":
res.end(`${JSON.stringify({
name: 'haifei',
age: 18
})}`)
break;
default:
res.end("404")
break;
}
})
server.listen(3000)
1.3 模拟get
const http = require('http');
const https = require('https');
const url = require('url');
const server = http.createServer((req, res) => {
let urlobj = url.parse(req.url,true)
console.log(urlobj);
res.writeHead(200, {
'Content-Type': 'application/json; charset=utf-8',
// 添加cors头,允许所有域访问
'access-control-allow-origin': '*'
})
switch (urlobj.pathname) {
case "/api/aaa":
// 客户端 去猫眼要数据
httpGet((data)=>{
res.end(data)
})
break;
default:
res.end("404")
break;
}
})
server.listen(3000)
function httpGet(cb) {
let data = "";
https.get('https://i.maoyan.com/ajax/moreCinemas?movieId=0&day=2023-08-20&offset=0&limit=20&districtId=-1&lineId=-1&hallType=-1&brandId=-1&serviceId=-1&areaId=-1&stationId=-1&item=&updateShowDay=true&reqId=1692528909659&cityId=57&optimus_uuid=0C582500C08511EDAB779582E39528B719F961A672AD4162B86243271138476D&optimus_risk_level=71&optimus_code=10', (res) => {
res.on('data', (chunk) => {
data += chunk
});
res.on('end', () => {
// console.log(data);
cb(data);
});
}).on('error', (e) => {
console.error(e);
});
}
1.4 模拟post:服务器提交(攻击)
const http = require('http');
const https = require('https');
const url = require('url');
const server = http.createServer((req, res) => {
let urlobj = url.parse(req.url,true)
console.log(urlobj);
res.writeHead(200, {
'Content-Type': 'application/json; charset=utf-8',
// 添加cors头,允许所有域访问
'access-control-allow-origin': '*'
})
switch (urlobj.pathname) {
case "/api/aaa":
// 客户端 去猫眼要数据
httpPost((data)=>{
res.end(data)
})
break;
default:
res.end("404")
break;
}
})
server.listen(3000)
function httpPost(cb) {
let data = "";
var options = {
hostname: "www.xiaomiyoupin.com",
port:"443",
path:"/mtop/market/search/placeHolder",
method:"POST",
Headers:{
"content-type": "application/json",
}
}
var req = https.request(options, (res) => {
res.on('data', (chunk) => {
data += chunk
});
res.on('end', () => {
// console.log(data);
cb(data);
});
}).on('error', (e) => {
console.error(e);
});
req.write(JSON.stringify([{}, {ypClient: 3}]))
req.end()
}
4.5 爬虫
略
2.url模块
2.1 url.parse()
url.parse()
方法接受网址字符串,解析并返回网址对象。
2.2 url.resolve(from, to)
url.resolve()
方法以类似于 Web 浏览器解析锚标记的方式解析相对于基本 URL 的目标 URL。
2.3 url.format(urlObject)
url.format()
方法返回从 urlObject
派生的格式化网址字符串。
const url = require('node:url');
url.format({
protocol: 'https',
hostname: 'example.com',
pathname: '/some/path',
query: {
page: 1,
format: 'json'
}
});
// => 'https://example.com/some/path?page=1&format=json'
3.querystring模块
3.1 parse
// 引入querystring模块
const querystring = require('querystring');
// 解析成对象 querystring.parse(qs)
let qs = "name=haifei&age=12"
let obj = querystring.parse(qs)
console.log(obj); // [Object: null prototype] { name: 'haifei', age: '12' }
3.2 stringify
// 引入querystring模块
const querystring = require('querystring');
// 编码成字符串 querystring.stringify(str)
let str = {name: 'haifei', age:18}
let strq = querystring.stringify(str)
console.log(strq); //name=haifei&age=18
3.3 escape
const querystring = require('querystring');
var str = 'id=3&city=北京&url=https://www.baidu.com'
var escaped = querystring.escape(str) // id%3D3%26city%3D%E5%8C%97%E4%BA%AC%26url%3Dhttps%3A%2F%2Fwww.baidu.com
console.log(escaped)
3.4 unescape
const querystring = require('querystring');
var str1 = 'id%3D3%26city%3D%E5%8C%97%E4%BA%AC%26url%3Dhttps%3A%2F%2Fwww.baidu.com'
var unescaped = querystring.unescape(str1)
console.log(unescaped) // id=3&city=北京&url=https://www.baidu.com
4. event模块
const EventEmitter = require('events');
class myEventEmitter extends EventEmitter{}
const event = new myEventEmitter()
// 监听事件
event.on("play",(move)=>{
console.log("事件触发了",move);
})
// 触发事件play,并传参(第二个是参数)
event.emit("play","1111");
event.emit("play","事件我也触发了")
5.fs文件操作模块
5.1 创建文件夹
const fs = require('fs');
// 创建文件夹
fs.mkdir("./objects",(err)=>{
if (err) throw err;
});
5.2 文件夹改名
const fs = require('fs');
// 文件夹改名,参数1:原文件夹名,参数2:新文件夹名
fs.rename("./objects","./aaa",(err)=>{
if (err) throw err;
})
5.3 删除文件夹
const fs = require('fs');
// 删除文件夹
fs.rmdir("./aaa",(err)=>{
if (err) throw err;
})
5.4 写入文件内容
const fs = require('fs');
// 写内容到文件里,参数1:路径文件名,参数2:写入的内容
fs.writeFile("./objects/hello.txt","Hello",(err)=>{
if (err) {
console.log(err.message);
}else{
console.log("文件创建成功");
}
})
5.5 追加文件内容
const fs = require('fs');
// 文件追加内容
fs.appendFile("./objects/hello.txt","\word",()=>{
console.log("内容追加成功");
})
5.6 读取文件内容
const fs = require('fs');
// 读取文件内容,参数1:文件路径名,参数2:编码格式
fs.readFile("./objects/hello.txt","utf-8",(err,data)=>{
console.log(data);
})
5.7 删除文件
const fs = require('fs');
// 删除文件
fs.unlink("./objects/hello.txt",(err)=>{
console.log("文件删除成功");
})
5.8 批量写入文件
const fs = require('fs');
// 批量写文件
for (let i = 0; i < 6; i++) {
fs.writeFile(`./objects/hello-${i}.txt`,`feige-${i}`,(err)=>{
if(err) throw err;
})
}
5.9 读取文件/目录信息
const fs = require('fs')
fs.readdir("./objects",(err,data)=>{
if (!err) {
console.log(data);
}
})
5.10 stat
const fs = require('fs')
fs.stat("./objects",(err,data)=>{
if (!err) {
// 是否是文件
console.log(data.isFile()); // false
// 是否是文件夹
console.log(data.isDirectory()); // true
}
})
5.11 rmdir
const fs = require('fs')
fs.readdir("./objects",(err,data)=>{
// 先遍历文件,删除文件
data.forEach(item=>{
fs.unlink(`./objects/${item}`, (err)=>{
if (err) {
console.log(err);
}
})
})
// 删除文件夹,但因上面的函数是异步,不会阻塞下面代码执行,可能导致文件夹不为空无法删除文件夹
fs.rmdir("./objects", (err,data)=>{
if (err) {
console.log(err);
}
})
})
6、文件系统同步/异步方法
6.1 mkdirSync 同步方法创建文件夹
const fs = require('fs');
// 同步方法创建文件夹
try {
fs.mkdirSync("./objects")
} catch (error) {
console.log("err",error);
}
// 会阻塞后续代码执行
6.2 rmdirSync 同步方法删除文件/文件夹
const fs = require('fs')
// 同步方法删除文件/文件夹
fs.readdir("./objects",(err,data)=>{
// 先遍历文件,删除文件
data.forEach(item=>{
fs.unlinkSync(`./objects/${item}`, (err)=>{
if (err) {
console.log(err);
}
})
})
// 上面代码会阻塞下面代码执行
fs.rmdir("./objects", (err,data)=>{
if (err) {
console.log(err);
}
})
})
6.3 异步创建文件夹
const fs = require('fs').promises;
// 异步创建文件夹
fs.mkdir("./objects").then(data=>{
console.log(data);
})
6.4 异步读取文件内容
const fs = require('fs').promises;
// 异步读取文件内容
fs.readFile("./objects/1.txt","utf-8").then(data=>{
console.log(data);
})
6.5 异步删除文件夹/文件的方法
const fs = require('fs').promises;
fs.readdir("./objects").then(async data=>{
// 写法1
// let arr = [];
// data.forEach(item=>{
// arr.push(fs.unlink(`./objects/${item}`))
// })
// await Promise.all(arr)
// 写法2
await Promise.all(data.map(item=>fs.unlink(`./objects/${item}`)))
await fs.rmdir("./objects")
}).catch(err => {
console.log(err);
})
由于Node环境执行的JavaScript代码是服务器端代码,所以,绝大部分需要在服务器运行期反复执行业务逻辑的代码,必须使用异步代码,否则,同步代码在执行时期,服务器将停止响应,因为JavaScript只有一个执行线程。
服务器启动时如果需要读取配置文件,或者结束时需要写入到状态文件时,可以使用同步代码,因为这些代码只在启动和结束时执行一次,不影响服务器正常运行时的异步执行。
7.stream流模块
Node.js,Stream 有四种流类型:
-
Readable - 可读操作。
-
Writable - 可写操作。
-
Duplex - 可读可写操作.
-
Transform - 操作被写入数据,然后读出结果。
所有的 Stream 对象都是 EventEmitter 的实例。常用的事件有:
-
data - 当有数据可读时触发。
-
end - 没有更多的数据可读时触发。
-
error - 在接收和写入过程中发生错误时触发。
-
finish - 所有数据已被写入到底层系统时触发。
7.1读取文件内容
const fs = require('fs');
// 读取文件内容
const rs = fs.createReadStream("./1.txt","utf-8")
// 有数据可读时
rs.on("data",(chunk)=>{
console.log("chunk",chunk);
})
// 没有数据可读时
rs.on("end",()=>{
console.log("end");
});
// 发生错误时
rs.on("error",(err)=>{
console.log(err);
})
7.2 写入文件内容
const fs = require('fs');
// 向文件写入内容
const ws = fs.createWriteStream("2.txt","utf8")
ws.write("hello world1111")
ws.write("hello world1222")
ws.write("hello world333")
ws.end()
7.3 边读边写,可用于大型文件复制
const fs = require('fs');
const rs = fs.createReadStream("./1.txt","utf-8");
const ws = fs.createWriteStream("./2.txt","utf-8");
rs.pipe(ws);
8.zlib
常用于文件压缩
// 创建服务器
const http = require('http');
const fs = require('fs');
const zlib = require('zlib');
const gzip = zlib.createGzip();
http.createServer((req,res)=>{
// res 本身就是可写流
const readstream = fs.createReadStream("./1.txt")
res.writeHead(200, {"Content-Type": "text/plain;charset=utf-8","Content-Encoding": "gzip"});
readstream.pipe(gzip).pipe(res);
}).listen(3000,()=>{
console.log("服务器创建成功");
});
9.crypto
9.1crypto.createHash(algorithm)
创建并返回一个哈希对象,使用指定的算法来生成哈希摘要。
参数algorithm
取决于平台的OpenSSL版本所支持的算法。例如,'sha1'
、'md5'
、'sha256'
、'sha512'
等等。
9.1.1类:Hash
Hase用来生成数据的哈希值。
它是可读写的流stream。写入的数据来用计算哈希值。当写入流结束后,使用read()
方法来获取计算后的哈希值。也支持旧的update
和digest
方法。
通过crypto.createHash
返回。
9.1.1.1hash.update(data[, input_encoding])
根据data
来更新哈希内容,编码方式根据input_encoding
来定,有'utf8'
、'ascii'
或'binary'
。如果没有传入值,默认编码方式是'binary'
。如果 data
是Buffer
,则input_encoding
将会被忽略。
因为它是流式数据,所以可以使用不同的数据调用很多次。
9.1.1.2hash.digest([encoding])
计算传入的数据的哈希摘要。
encoding
可以是'hex'
、'binary'
或'base64'
,如果没有指定encoding
,将返回buffer。
注意:调用digest()
后不能再用hash
对象。
const crypto = require('crypto');
// 创建并返回一个哈希对象,使用指定的算法来生成哈希摘要
const hash = crypto.createHash("md5");
// 根据传入的内容更新哈希内容
hash.update("feige");
// 计算传入数据的哈希摘要
let hdigest = hash.digest("base64");
console.log(hdigest); // RgoDbQgTK0GnzOTGnuZIXw==
9.2 crypto.createHmac(algorithm, key)
创建并返回一个hmac对象,用指定的算法和秘钥生成hmac图谱。
它是可读写的流stream。写入的数据来用计算hmac。当写入流结束后,使用read()
方法来获取计算后的值。也支持旧的update
和digest
方法。
参数algorithm
取决于平台上OpenSSL版本所支持的算法,参见前面的createHash。key
是hmac算法中用的key。
9.2.1类:Hmac
用来创建hmac加密图谱。
通过crypto.createHmac
返回。
9.2.1.1hmac.update(data)
根据data
更新hmac对象。因为它是流式数据,所以可以使用新数据调用多次。
9.2.1.2hmac.digest([encoding])
计算传入数据的hmac值。encoding
可以是'hex'
、'binary'
或'base64'
,如果没有指定encoding
,将返回buffer。
注意:调用digest()
后不能再用hmac
对象。
const crypto = require('crypto');
// 创建并返回一个hmac对象,用指定的算法和秘钥生成hmac图谱
const hmac = crypto.createHmac("sha256","feige")
// 根据传入内容更新hmac对象
hmac.update("123456");
// 计算传入数据的hmac值
console.log(hmac.digest("hex")); // 54457715ab275476a5418578c2d3aa16f5fd889b645a8a07fea80ecdba0fa5ae
9.3crypto.createCipheriv(algorithm, key, iv)
创建并返回一个加密对象,用指定的算法,key和iv。
algorithm
取决于OpenSSL,例如'aes192'
等。key
在算法中用到。iv
是一个initialization vector.
key
和iv
必须是'binary'
的编码字符串或buffers.
9.3.1类: Cipher
加密数据的类。.
通过crypto.createCipher
和crypto.createCipheriv
返回。
它是可读写的stream流。写入的数据来用计算hmac。当写入流结束后,使用read()
方法来获取计算后的值。也支持旧的update
和digest
方法。
9.3.1.1cipher.update(data[, input_encoding][, output_encoding])
根据data
来更新哈希内容,编码方式根据input_encoding
来定,有'utf8'
、'ascii'
或者'binary'
。如果没有传入值,默认编码方式是'binary'
。如果data
是Buffer
,input_encoding
将会被忽略。
output_encoding
指定了输出的加密数据的编码格式,它可用是'binary'
、'base64'
或'hex'
。如果没有提供编码,将返回buffer。
返回加密后的内容,因为它是流式数据,所以可以使用不同的数据调用很多次。
9.3.1.2cipher.final([output_encoding])
返回加密后的内容,编码方式是由output_encoding
指定,可以是'binary'
、'base64'
或'hex'
。如果没有传入值,将返回buffer。
注意:cipher
对象不能在final()
方法之后调用。
9.4crypto.createDecipheriv(algorithm, key, iv)
根据传入的算法,密钥和iv,创建并返回一个解密对象。这是createCipheriv()的镜像。
9.4.1类:Decipher
解密数据类。
通过crypto.createDecipher
和crypto.createDecipheriv
返回。
解密对象是可读写的streams流。用写入的加密数据生成可读的纯文本数据。也支持老的update
和digest
方法。
9.4.1.1decipher.update(data[, input_encoding][, output_encoding])
使用参数data
更新需要解密的内容,其编码方式是'binary'
、'base64'
或'hex'
。如果没有指定编码方式,则把data
当成buffer
对象。
如果data
是Buffer
,则忽略input_encoding
参数。
参数output_decoding
指定返回文本的格式,是'binary'
、'ascii'
或'utf8'
之一。如果没有提供编码格式,则返回buffer。
9.4.1.2decipher.final([output_encoding])
返回剩余的解密过的内容,参数output_encoding
是'binary'
、'ascii'
或'utf8'
,如果没有指定编码方式,返回buffer。
注意:decipher
对象不能在final()
方法之后使用。
// 加密/解密
const crypto = require('crypto');
// 加密
function encrypto(key,iv,data) {
let cip = crypto.createCipheriv("aes-128-cbc",key,iv);
return cip.update(data,"binary","hex")+cip.final("hex");
}
// 解密
function decrypto(key,iv,crypted) {
crypted = Buffer.from(crypted,"hex").toString("binary")
let dcip = crypto.createDecipheriv("aes-128-cbc",key,iv)
return dcip.update(crypted,"binary","utf-8")+dcip.final("utf-8")
}
let key = "1234567890abcdef"
let iv = "abcdef1234567890"
let data = "feige"
let crypted = encrypto(key,iv,data)
console.log("加密结果:",crypted); // 加密结果: 209418e9fcc25be3e83cea74ab52154b
let decrypted = decrypto(key,iv,crypted)
console.log("解密结果:",decrypted); // 解密结果: feige
6、路由
6.1基础
基础版
// 创建服务器
const http = require('http');
const fs = require('fs');
const app = http.createServer((req,res)=>{
const myurl = new URL(req.url,"http://127.0.0.1:");
console.log(myurl.pathname);
switch (myurl.pathname) {
case "/home":
res.writeHead(200,{"Content-Type": "text/html,charset=utf-8"});
res.write(fs.readFileSync("./static/Home.html"),"utf-8")
break;
case "/login":
res.writeHead(200,{"Content-Type": "text/html,charset=utf-8"});
res.write(fs.readFileSync("./static/Login.html"),"utf-8")
break;
default:
res.writeHead(404,{"Content-Type": "text/html,charset=utf-8"});
res.write(fs.readFileSync("./static/404.html"),"utf-8")
break;
}
})
app.listen(3000,()=>{
console.log("server starting");
})
优化版
// server.js
// 创建服务器
const http = require('http');
const route = require('./route');
const app = http.createServer((req,res)=>{
const myurl = new URL(req.url,"http://127.0.0.1:");
console.log(myurl.pathname);
route(myurl.pathname,res)
res.end();
})
app.listen(3000,()=>{
console.log("server starting");
})
// route.js
const fs = require('fs');
function route(pathname,res) {
switch (pathname) {
case "/home":
res.writeHead(200,{"Content-Type": "text/html,charset=utf-8"});
res.write(fs.readFileSync("./static/Home.html"),"utf-8")
break;
case "/login":
res.writeHead(200,{"Content-Type": "text/html,charset=utf-8"});
res.write(fs.readFileSync("./static/Login.html"),"utf-8")
break;
default:
res.writeHead(404,{"Content-Type": "text/html,charset=utf-8"});
res.write(fs.readFileSync("./static/404.html"),"utf-8")
break;
}
}
module.exports = route;
升级版
// server.js
// 创建服务器
const http = require('http');
const route = require('./route');
const app = http.createServer((req,res)=>{
const myurl = new URL(req.url,"http://127.0.0.1:");
console.log(myurl.pathname);
try {
route[myurl.pathname](res)
} catch (error) {
route["/404"](res)
}
res.end();
})
app.listen(3000,()=>{
console.log("server starting");
})
// route.js
const fs = require('fs');
const route = {
"/home": (res)=>{
res.writeHead(200,{"Content-Type": "text/html,charset=utf-8"});
res.write(fs.readFileSync("./static/Home.html"),"utf-8")
},
"/login": (res)=>{
res.writeHead(200,{"Content-Type": "text/html,charset=utf-8"});
res.write(fs.readFileSync("./static/Login.html"),"utf-8")
},
"/404": (res)=>{
res.writeHead(404,{"Content-Type": "text/html,charset=utf-8"});
res.write(fs.readFileSync("./static/404.html"),"utf-8")
}
}
module.exports = route;
优雅版
// index.js
const server = require("./server")
const api = require("./api")
const route = require("./route");
// 合并路由升级版
server.use(api)
server.use(route)
server.start()
// server.js
// 创建服务器
const http = require('http');
// const api = require("./api")
// const route = require("./route");
// 合并路由
const Router = {}
// Object.assign(Router,api)
// Object.assign(Router,route)
function use(obj) {
Object.assign(Router,obj)
}
function start() {
const app = http.createServer((req,res)=>{
const myurl = new URL(req.url,"http://127.0.0.1:");
console.log(myurl.pathname);
try {
Router[myurl.pathname](res)
} catch (error) {
Router["/404"](res)
}
})
app.listen(3000,()=>{
console.log("server starting");
})
}
exports.start = start;
exports.use = use;
// route.js
const fs = require('fs');
function render(res,path,type="") {
res.writeHead(200,{"Content-Type": `${type?type:'text/html'},charset=utf-8`});
res.write(fs.readFileSync(path),"utf-8")
res.end();
}
const route = {
"/home": (res)=>{
render(res,"./static/Home.html")
},
"/login": (res)=>{
render(res,"./static/Login.html")
},
"/404": (res)=>{
res.writeHead(404,{"Content-Type": "text/html,charset=utf-8"});
res.write(fs.readFileSync("./static/404.html"),"utf-8")
res.end();
},
"/favicon.ico": (res)=>{
render(res,"./static/favicon.ico","image/x-icon")
}
}
module.exports = route;
// api.js 数据接口
function render(res,data,type="") {
res.writeHead(200,{"Content-Type": `${type?type:'application/json'},charset=utf-8`});
res.write(data)
res.end();
}
const apiRouter = {
"/api/login":(res)=>{
render(res,"{ok:1}")
}
}
module.exports = apiRouter
6.2获取请求参数
登陆页面
// login.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>
用户名:
<input type="text" id="username">
</div>
<div>
密码:
<input type="password" id="password">
</div>
<div>
<button id="login">登录GET</button>
</div>
<div>
<button id="plogin">登录POST</button>
</div>
<script>
let ologin = document.querySelector("#login");
let plogin = document.querySelector("#plogin");
let username = document.querySelector("#username");
let password = document.querySelector("#password");
ologin.onclick = ()=>{
// console.log(username.value,password.value);
// GET请求
fetch(`/api/login?username=${username.value}&password=${password.value}`)
.then(res=>res.json())
.then(res=>{console.log(res);})
}
plogin.onclick = ()=>{
fetch("/api/loginpost",{
method: "POST",
body: JSON.stringify({
username:username.value,
password:password.value
}),
headers: {'Content-Type': 'application/json'}
}).then(res=>res.text()).then(res=>{
console.log(res);
})
}
</script>
</body>
</html>
6.2.1GET请求
// api.js
// 获取get请求参数
"/api/login":(req,res)=>{
// 获取参数
const myURL = new URL(req.url,"http://127.0.0.1")
console.log(myURL.searchParams);
if (myURL.searchParams.get("username") === "haifei" && myURL.searchParams.get("password") === "123"){
render(res,`{"ok":1}`)
}else{
render(res,`{"ok":0}`)
}
}
6.2.2POST请求
// api.js
// 获取post请求参数
"/api/loginpost":(req,res)=>{
// 获取参数
let data = "";
req.on("data", chunk=>{
data += chunk
})
req.on("end",()=>{
// console.log(data); // {"username":"haifei","password":"123"}
data = JSON.parse(data)
if (data.username === "haifei" && data.password === "123456"){
render(res,`{"ok":1}`)
}else{
render(res,`{"ok":0}`)
}
})
}
6.3静态资源处理
// route.js
const fs = require('fs');
const path = require('path');
const mime = require('mime');
function render(res,path,type="") {
res.writeHead(200,{"Content-Type": `${type?type:'text/html'},charset=utf-8`});
res.write(fs.readFileSync(path),"utf-8")
res.end();
}
const route = {
"/": (req,res)=>{
render(res,"./static/Home.html")
},
"/home": (req,res)=>{
render(res,"./static/Home.html")
},
"/login": (req,res)=>{
render(res,"./static/Login.html")
},
"/404": (req,res)=>{
// 判断是否是静态资源,如果是走静态资源处理路线,如果不是则继续执行后面的404
if (readStaticFile(req,res)) {
return
}
res.writeHead(404,{"Content-Type": "text/html,charset=utf-8"});
res.write(fs.readFileSync("./static/404.html"),"utf-8")
res.end();
},
// 因为配置了静态资源管理,此处的favicon.ico就不需要处理了,会走静态资源处理路线
// "/favicon.ico": (res)=>{
// render(res,"./static/favicon.ico","image/x-icon")
// }
}
// 静态资源管理
function readStaticFile(req,res) {
// 获取路径
const myURL = new URL(req.url,"http://127.0.0.1:3000")
// console.log(__dirname,myURL.pathname); // F:\大前端\前端\Node\node-class1\028-路由-静态资源 /css/login.css
// 拼接完整路径
const pathname = path.join(__dirname,"/static",myURL.pathname)
// console.log(pathname); // F:\大前端\前端\Node\node-class1\028-路由-静态资源\static\css\login.css
if (fs.existsSync(pathname)) {
// 如果请求路径存在执行代码,然后返回true
// 使用上面定义的render函数返回读取的文件,此处需安装第三方模块mime,传入文件后缀名,返回对应的contentType
render(res,pathname,mime.getType(myURL.pathname.split(".")[1]))
return true
}else{
return false
}
}
module.exports = route;
mime模块,传入文件后缀名,返回对应的Content-Type:https://www.npmjs.com/package/mime
补充:
fs.existsSync(path)
如果路径存在则返回 true
,否则返回 false
。
path.join()
方法使用特定于平台的分隔符作为定界符将所有给定的 path
片段连接在一起,然后规范化生成的路径。
二、Express
express是基于 Node.js 平台,快速、开放、极简的 Web 开发框架
官网:Express - 基于 Node.js 平台的 web 应用开发框架 - Express 中文文档 | Express 中文网https://www.expressjs.com.cn/
1、特色
Web 应用程序
Express 是一个保持最小规模的灵活的 Node.js Web 应用程序开发框架,为 Web 和移动应用程序提供一组强大的功能。
API
使用您所选择的各种 HTTP 实用工具和中间件,快速方便地创建强大的 API。
性能
Express 提供精简的基本 Web 应用程序功能,而不会隐藏您了解和青睐的 Node.js 功能。
框架
许多 流行的开发框架 都基于 Express 构建。
2、安装
$ npm install express --save
3、路由
路由指的是应用程序的端点(URI)如何响应客户端请求。
使用与 HTTP 方法对应的 Express 应用程序对象的方法定义路由; 例如,app.get()处理 GET 请求,app.post处理 POST 请求。有关完整列表,请参见 app.Method。还可以使用 app.all ()处理所有 HTTP 方法,使用 app.use ()指定中间件作为回调函数(详见使用中间件)。
这些路由方法指定当应用程序接收到对指定路由(端点)和 HTTP 方法的请求时调用的回调函数(有时称为“处理程序函数”)。换句话说,应用程序“监听”与指定路由和方法匹配的请求,当它检测到匹配时,它调用指定的回调函数。
事实上,路由方法可以有多个回调函数作为参数。对于多个回调函数,重要的是提供 next 作为回调函数的参数,然后在函数体内调用 next ()将控制权交给下一个回调函数
3.1基础路由示例
// 一个简单的路由示例
const express = require('express')
const app = express()
// 匹配/ 路径的请求
app.get('/', (req, res) => {
res.send({
name:"feige",
age:28
})
})
// 匹配about 路径的请求
app.get("/about", (req, res) => {
res.send("about")
})
// 匹配random.txt 路径的请求
app.get("/random.txt", (req, res) => {
res.send("random.txt")
})
// 监听端口号
app.listen(3000, (req, res) => {
console.log("server starting");
})
3.2路由方法
路由方法派生自 HTTP 方法之一,并附加到 Express 类的实例。
下面的代码是为 GET 和 POST 方法定义的到应用程序根目录的路由示例。
// GET method route
app.get('/', function (req, res) {
res.send('GET request to the homepage')
})
// POST method route
app.post('/', function (req, res) {
res.send('POST request to the homepage')
})
3.3路由路径
3.3.1 基于字符串的路由路径示例
// 基于字符串的路由路径示例
// 匹配/ 路径的请求
app.get('/', (req, res) => {
res.send({
name:"feige",
age:28
})
})
// 匹配about 路径的请求
app.get("/about", (req, res) => {
res.send("about")
})
// 匹配random.txt 路径的请求
app.get("/random.txt", (req, res) => {
res.send("random.txt")
})
3.3.2 基于字符串模式的路由路径示例
// 基于字符串模式的路由路径示例
// 此路径将匹配 acd 和 abcd。
app.get("/ab?cd",(req,res)=>{
res.send("ab?cd")
})
// 此路径将匹配 abbcd、 abbcd、 abbbcd 等。
app.get("/ab+cd",(req,res)=>{
res.send("ab+cd")
})
// 此路径将匹配 abcd、 abxcd、 abRANDOMcd、 ab123cd 等。
app.get("/ab*cd",(req,res)=>{
res.send("ab*cd")
})
// 此路径将匹配/abe 和/abcde。
app.get("/ab(cd)?e",(req,res)=>{
res.send("ab(cd)?e")
})
3.3.3 基于正则表达式的路由路径示例
// 基于正则表达式的路由路径示例:
// 这条路径将匹配任何带有“ a”的路径。
app.get(/zzz/,(req,res)=>{
res.send("/zzz/")
})
// 这条路径将匹配以fly结尾的路径
app.get(/.*fly$/,(req,res)=>{
res.send(".*fly$")
})
3.4路由参数
路由参数被命名为 URL 段,用于捕获在其 URL 中的位置指定的值。捕获的值在 req.params 对象中填充,路径中指定的路由参数的名称作为它们各自的键。
Route path: /users/:userId/books/:bookId
Request URL: http://localhost:3000/users/34/books/8989
req.params: { "userId": "34", "bookId": "8989" }
要使用路由参数定义路由,只需在路由的路径中指定路由参数,如下所示。
app.get('/users/:userId/books/:bookId', function (req, res) {
res.send(req.params)
})
3.5 路由处理
您可以提供多个回调函数,这些函数的行为类似于处理请求的中间件。唯一的例外是,这些回调可能会调用 next (‘ path’)来绕过剩余的路由回调。您可以使用此机制对路由施加前提条件,然后如果没有理由继续当前路由,则将控制传递给后续路由。
路由处理程序可以是函数、函数数组或两者的组合形式,如下面的示例所示。
3.5.1 单个回调函数可以处理路由
// 单个回调函数可以处理路由。例如:
app.get('/', (req, res) => {
res.send("Welcome")
})
3.5.2 多个回调函数可以处理一个路由(确保指定了下一个对象)
// 多个回调函数可以处理一个路由(确保指定了下一个对象)。例如:
app.get('/login', (req, res,next) => {
// 验证token过期,cookie过期
console.log("验证token");
const isValid = true;
if (isValid) {
res.haifei = "一个计算结果"
next();
}else{
res.send(err)
}
},(req,res)=>{
res.send("login")
})
3.5.3 回调函数的数组可以处理路由
// 回调函数的数组可以处理路由。例如:
let fun1 = (req,res,next)=>{
console.log("fun1");
next()
}
let fun2 = (req,res,next)=>{
console.log("fun2");
next()
}
let fun3 = (req,res)=>{
res.send("home")
}
app.get("/home",[fun1,fun2,fun3])
3.6 响应方法
下表中响应对象(res)上的方法可以向客户端发送响应,并终止请求-响应周期。如果没有从路由处理程序调用这些方法,则客户端请求将处于挂起状态。
方法 | 描述 |
res.download() | 提示要下载的文件。 |
res.end() | 结束响应过程。 |
res.json() | 发送一个 JSON 响应。 |
res.jsonp() | 发送带有 JSONP 支持的 JSON 响应。 |
res.redirect() | 重定向请求。 |
res.render() | 呈现视图模板。 |
res.send() | 发送各种类型的响应。 |
res.sendFile() | 以八位流的形式发送文件。 |
res.sendStatus() | 设置响应状态代码并将其字符串表示形式作为响应主体发送。 |
3.7 app.route()
可以使用 app.route()为路由路径创建可链接的路由处理程序。因为路径是在单个位置指定的,所以创建模块化路由是有帮助的,同样减少冗余和输入错误也是有帮助的。有关路由的详细信息,请参阅: Router ()文档。
下面是使用 app.route()定义的链式路由处理程序的示例。
app.route('/book')
.get(function (req, res) {
res.send('Get a random book')
})
.post(function (req, res) {
res.send('Add a book')
})
.put(function (req, res) {
res.send('Update the book')
})
3.8 express.Router()
用express.Router()创建模块化、可挂载路由处理程序的路由器类。路由器实例是一个完整的中间件和路由系统; 因此,它通常被称为“迷你应用程序”。
下面的示例将路由器创建为模块,在其中加载中间件函数,定义一些路由,并将路由器模块挂载到主应用程序中的路径上。
在 app 目录中创建一个名为 user.js 的路由器文件,其内容如下:
// user.js
const express = require("express");
const router = express.Router();
router.get("/",(req, res) => {
console.log("获取用户");
res.send("获取用户")
})
router.get("/add",(req, res) => {
console.log("添加用户");
res.send("添加用户")
})
module.exports = router;
然后,在应用程序中加载路由器模块:
// 主入口
const express = require('express');
const app = express();
const route = express.route
const user = require("./router/user")
app.use("/user",user)
app.listen("3000",()=>{
console.log("server started");
})
4、中间件
中间件函数是具有对请求对象(req)、响应对象(res)和应用程序请求-响应周期中的下一个函数的访问权限的函数。下一个函数是 Express 路由器中的一个函数,当被调用时,它将执行当前中间件之后的中间件。
中间件功能可以执行以下任务:
- 执行任何代码。
- 对请求和响应对象进行更改。
- 结束请求-响应周期。
- 调用堆栈中的下一个中间件。
如果当前中间件函数没有结束请求-响应周期,它必须调用 next ()将控制传递给下一个中间件函数。否则,请求将被挂起。
下图显示了中间件函数调用的元素:
| 应用中间件函数的 HTTP 方法。 应用中间件函数的路径(路由)。 中间件函数。 中间件函数的回调参数,按照约定称为“ next”。 中间件函数的 HTTP 响应参数,约定称为“ res”。 中间件函数的 HTTP 请求参数,按照约定称为“ req”。 |
Express 应用程序可以使用下列类型的中间件:
- 应用程序级中间件
- 路由器级中间件
- 错误处理中间件
- 内置的中间件
- 第三方中间件
4.1应用级中间件
应用级中间件绑定到 app 对象 使用 app.use() 和 app.METHOD(), 其中, METHOD 是需要处理的 HTTP 请求的方法,例如 GET, PUT, POST 等等,全部小写。例如
// index.js
const express = require('express');
const IndexRouter = require('./router2/indexRouter');
const app = express();
// 应用级中间件
app.use(function(req, res, next) {
console.log("验证token");
next();
})
// 应用级中间件
app.use("/api",IndexRouter);
app.listen(3000,()=>{
console.log("server starting");
})
4.2 路由级中间件
路由级中间件和应用级中间件一样,只是它绑定的对象为 express.Router()。
let router = express.Router()
// 路由级中间件
const express = require('express')
const router = express.Router()
// 路由级中间件
router.get("/home",(req,res)=>{
res.send("home-get");
})
// 路由级中间件
router.get("/swiper",(req,res)=>{
res.send("swiper-get");
});
// 路由级中间件
router.get("/list",(req,res)=>{
res.send("list-get");
})
module.exports = router
4.3 错误处理中间件
错误处理中间件和其他中间件定义类似,只是要使用 4 个参数,而不是 3 个,其签名如下: (err, req, res, next)。
app.use(function(err, req, res, next) {
console.error(err.stack)
res.status(500).send('Something broke!')
})
4.4 内置中间件
从4.x 版本开始,Express 不再依赖 Connect。以前包含在 Express 中的中间件函数现在分别位于单独的模块中; 请参见中间件函数列表。
Express 具有以下内置中间件功能:
Static 服务于静态资产,如 HTML 文件、图像等。
JSON 使用 JSON 有效负载解析传入请求
Urlencode 使用 URL 编码的有效负载解析传入请求
app.use(express.static('public'))
app.use(express.static('uploads'))
app.use(express.static('files'))
4.5 第三方中间件
使用第三方中间件为 Express 应用程序添加功能。
为所需的功能安装 Node.js 模块,然后在应用程序级或路由器级将其加载到应用程序中。
下面的示例说明如何安装和加载 cookie 解析中间件函数 cookie 解析器。
$ npm install cookie-parser
var express = require('express')
var app = express()
var cookieParser = require('cookie-parser')
// load the cookie-parsing middleware
app.use(cookieParser())
有关 Express 常用的第三方中间件函数的部分列表,请参见: Third-party middlewarehttps://www.expressjs.com.cn/resources/middleware.html
5、获取请求参数
5.1 get 获取参数
req.query
router.get("/login",(req,res)=>{
console.log(req.query); // get请求获取参数
res.send("login-get");
})
5.2 post获取参数
app.use(express.urlencoded({extended:false}))
app.use(express.json())
req.body
router.post("/login",(req,res)=>{
console.log(req.body); // post请求获取参数,必须配置中间件
res.send("login-get");
})
配置中间件
// 配置解析post参数的中间件,不用下载,内置的
app.use(express.urlencoded({extended:false})) // post参数- username=feige&password=1234
app.use(express.json()) // post参数-{name:"feige",age:28}
6、静态资源配置
通过 Express 内置的 express.static 可以方便地托管静态文件,例如图片、CSS、JavaScript 文件等。
将静态资源文件所在的目录作为参数传递给 express.static 中间件就可以提供静态资源文件的访问了。例如,假设在 public 目录放置了图片、CSS 和 JavaScript 文件,你就可以:
app.use(express.static('public'))
现在,public 目录下面的文件就可以访问了。
http://localhost:3000/images/kitten.jpg
http://localhost:3000/css/style.css
http://localhost:3000/js/app.js
http://localhost:3000/images/bg.png
http://localhost:3000/hello.html
所有文件的路径都是相对于存放目录的,因此,存放静态文件的目录名不会出现在 URL 中。
如果你的静态资源存放在多个目录下面,你可以多次调用 express.static 中间件:
app.use(express.static('public'))
app.use(express.static('files'))
访问静态资源文件时,express.static 中间件会根据目录添加的顺序查找所需的文件。
如果你希望所有通过 express.static 访问的文件都存放在一个“虚拟(virtual)”目录(即目录根本不存在)下面,可以通过为静态资源目录指定一个挂载路径的方式来实现,如下所示:
app.use('/static', express.static('public'))
现在,你就可以通过带有 “/static” 前缀的地址来访问 public 目录下面的文件了。
http://localhost:3000/static/images/kitten.jpg
http://localhost:3000/static/css/style.css
http://localhost:3000/static/js/app.js
http://localhost:3000/static/images/bg.png
http://localhost:3000/static/hello.html
7、服务端渲染(模板引擎)
npm i ejs
需要在应用中进行如下设置才能让 Express 渲染模板文件:
-
views, 放模板文件的目录,比如: app.set('views', './views')
-
view engine, 模板引擎,比如: app.set('view engine', 'ejs')
// 入口文件index.js
// 配置模板文件目录
app.set('views', './views')
// 配置模板引擎
app.set("view engine","ejs")
// 路由级中间件
router.get("/",(req,res)=>{
// console.log(req.query); // get请求获取参数
// res.send("login-get");
// 返回渲染模板给前端
res.render("login") // 自动去找views文件夹下的login.ejs
})
支持直接渲染html代替ejs,配置代码如下
//配置模板引擎
app.set("views","./views")
app.set("view engine","html")
app.engine("html",require("ejs").renderFile) //支持直接渲染html文件
8、express生成器
参见:Express 应用程序生成器 - Express 中文文档 | Express 中文网https://www.expressjs.com.cn/starter/generator.html
三、MongoDB
1、关系型数据库与非关系型数据库的区别
关系型数据库
- 高度组织化结构化数据
- 结构化查询语言(SQL) (SQL)
- 数据和关系都存储在单独的表中。
- 数据操纵语言,数据定义语言
- 严格的一致性
- 基础事务
非关系型数据库
- 代表着不仅仅是SQL
- 没有声明性查询语言
- 没有预定义的模式
-键 - 值对存储,列存储,文档存储,图形数据库
- 最终一致性,而非ACID属性
- 非结构化和不可预知的数据
- CAP定理
- 高性能,高可用性和可伸缩性
2、非关系型数据库的优点/缺点
优点:
- - 高可扩展性
- - 分布式计算
- - 低成本
- - 架构的灵活性,半结构化数据
- - 没有复杂的关系
缺点:
- - 没有标准化
- - 有限的查询功能(到目前为止)
- - 最终一致是不直观的程序
3、术语概念
SQL术语/概念 | MongoDB术语/概念 | 解释/说明 |
---|---|---|
database | database | 数据库 |
table | collection | 数据库表/集合 |
row | document | 数据记录行/文档 |
column | field | 数据字段/域 |
index | index | 索引 |
table joins | 表连接,MongoDB不支持 | |
primary key | primary key | 主键,MongoDB自动将_id字段设置为主键 |
通过下图实例,我们也可以更直观的的了解Mongo中的一些概念:
4、数据库的安装
5、数据库的启动
必须从 MongoDB 目录的 bin 目录中执行 mongod.exe 文件
C:\Program Files\MongoDB\Server\6.0\bin> .\mongod.exe --dbpath F:\feige\db
注意:不能有中文
6、在命令行中操作数据库
MongoDB 4.0 数据库命令行中连接方法
C:\Program Files\MongoDB\Server\4.0\bin> .\mongo.exe
MongoDB 6.0 数据库命令行中连接方法
C:\Program Files\MongoDB\Server\6.0\bin> mongo
db.help() help on db methods
db.mycoll.help() help on collection methods
sh.help() sharding helpers
rs.help() replica set helpers
help admin administrative help
help connect connecting to a db help
help keys key shortcuts
help misc misc things to know
help mr mapreduce
show dbs show database names
show collections show collections in current database
show users show users in current database
show profile show most recent system.profile entries with time >= 1ms
show logs show the accessible logger names
show log [name] prints out the last segment of log in memory, 'global' is default
use <db_name> set current database
db.foo.find() list objects in collection foo
db.foo.find( { a : 1 } ) list objects in foo where a == 1
it result of the last line evaluated; use to further iterate
DBQuery.shellBatchSize = x set default number of items to display on shell
exit quit the mongo shell
6.1 数据库操作
1、显示数据库列表
show dbs
2、切换或创建数据(有则切换,无则创建)
use 数据库名
3、删除数据库
db.dropDatabase()
6.2 集合操作
1、创建集合
db.createCollection(集合名, [参数])
// 示例
> db.createCollection("users")
{ "ok" : 1 }
2、查看集合
show collections/show tables
// 示例
> show collections
users
3、删除集合
db.集合名.drop()
// 示例
> db.users.drop()
true
6.3数据基础操作
1、新增
db.集合名.insert({"键名1":值1, "键名2": 值2 ...})
// 示例
db.users.insert({"username":"feige","age":18})
WriteResult({ "nInserted" : 1 })
2、查询
db.集合名.findOne() # 查询一行
db.集合名.find() # 查询全部
db.集合名.find().pretty() # 格式化打印db.集合名.find({查找条件}) # 按条件查找
// 示例
db.users.find()
{ "_id" : ObjectId("64fe7d6a70868135e7ef5f8a"), "username" : "feige", "age" : 18 }
3、修改
db.集合名.update({查询条件}, {修改后结果}) #修改整行
db.students.update({查找条件}, {$set:{"要修改的字段名1":修改后的值, "要修改的字段名2": "值2"}}) #修改指定字段的值
// 示例
db.users.update({"username":"feige"},{"username":"dafeige","age":"28"})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
4、删除
db.集合名.remove({查询条件})
db.集合名.remove({}) # 删除全部数据
// 示例
db.users.remove({"username":"dafeige"})
WriteResult({ "nRemoved" : 1 })
6.4、高级查询
1、比较运算符查询
db.集合名.find({"键名": {比较运算符1:值1, 比较运算符2:值2} })
db.yunfan_test.find({"age": {$lt:24}})
说明:
符号 | 释义 |
---|---|
$gt | 大于 |
$lt | 小于 |
$gte | 大于等于 |
$lte | 小于等于欧 |
$ne | 不等于 |
2、in/not in
db.集合名.find({"键名": {$in:[值1, 值2, 值3 ...]} })
db.集合名.find({"键名": {$nin:[值1, 值2, 值3 ...]} })
// 示例
db.users.find({"age":{$in:[19,17]}})
{ "_id" : ObjectId("64fe80e0024353ba6b89574c"), "username" : "aaa", "age" : 17 }
{ "_id" : ObjectId("64fe80ed024353ba6b89574d"), "username" : "bbb", "age" : 19 }
3、size
db.集合名.find({"键名": {$size:n} })
db.users.find({"list":{$size:3}})
4、exists
db.集合名.find({"键名": {$exist: true|false} })
db.users.find({"flag":{$exists:true}})
5、or
db.集合名.find({$or:[{条件1}, {条件2}, {条件3}...]})
db.users.find({$or:[{"name":"张三"},{"name":"李四"}]})
6、模糊查询
db.集合名.find({"键名": js正则表达)
db.users.find({"name":/张三/})
7、查询结果排序(sort)
db.集合名.find().sort({"键名": 1|-1, "键名": 1|-1...}) #1为升序, -1为降序
db.users.find().sort({"age":-1})
8、限定返回结果数量(limit)
db.集合名.find().limit(n)
db.集合名.find().skip(n) # 跳过n条,返回从n+1k开始的数据
db.集合名.find().skip(n).limit(m) # 跳过n条,返回后面的m条
db.users.find().limit(1).sort({"age":1})
9、查询返回结果数量(count)
db.集合名.find().count()
db.集合名.find().skip(n).count(true) # 与skip结合使用时,要加true
db.users.find().count()
10、聚合函数
分组函数 | 说明 |
---|---|
$sum | 计算总和,$sum:1同count表示计数 |
$avg | 计算平均值 |
$min | 获取最小值 |
$max | 获取最大值 |
$push | 在结果文档中插入值到一个数组中,相当于拼接字段 |
$first | 根据资源文档的排序获取第一个文档数据 |
$last | 根据资源文档的排序获取最后一个文档数据 |
db.集合名.aggregate({$group:{_id:'$字段名', 别名:{$聚合函数:'$字段名'}}}
);
例:
# 统计同年龄的人数
db.users.aggregate({$group:{_id:'$age',count_age:{$sum:1}}}
);# 统计所有人平均年龄
db.users.aggregate({$group:{_id:null,总人数:{$sum:1},avg_age:{$avg:"$age"},min_age:{$min:"$age"},max_age:{$max:"$age"}}}
);
7、可视化工具进行CURD
Robomongo Robo3T adminMongo
8、nodejs链接操作数据
首先需要下载mongoose模块
npm i mongoose
8.1 连接数据库
创建config文件夹,里面创建 db.config.js,代码如下:
// 引入mongoose模块
const mongoose = require("mongoose");
mongoose.connect("mongodb://127.0.0.1:27017/feige_test")
// 插入集合和数据,数据库feige_test会自动创建
入口文件中,引入数据库模块
// 引入数据库模块
require("../config/db.config.js")
8.2 创建模型
为了可以复用,建议创建model文件夹,通过模块导入导出的方法使用
// 模块再次引入,是同一个实例
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
// 限制了数据的字段和类型
const UserType = {
username:String,
password:String,
age:Number
}
// 模型user将会对应 users 集合
const UserModel = mongoose.model("user",new Schema(UserType))
module.exports = UserModel;
8.3 增加数据
UserModel.create({username,password,age})
var express = require('express');
const UserModel = require('../model/UserModel');
var router = express.Router();
/* GET users listing. */
router.get('/', function(req, res, next) {
res.send('respond with a resource');
});
router.post('/users/add', function(req, res, next) {
// console.log(req.body);
let {username,age,password} = req.body;
// 插入数据库
// 1.创建一个数据模型(user,限制filed类型),一一对应数据库的集合(users)
// user.create user.find user.delete user.update
UserModel.create({
username,password,age
}).then(data =>{ // 此处不要写res避免与上面的res冲突
console.log(data);
res.send({"ok":1});
})
});
module.exports = router;
8.4 查询数据
UserModel.find({},["username","age"]).sort({age:1}).skip((page-1)*limit).limit(limit)
- {} 表示查询全部;
- ["username","age"],表示限制查询的字段,及只需要那几个字段的
- sort({age:1})排序,1正序,-1倒叙
- skip((page-1)*limit) 要第几页的
- limit(limit) 一页要几个
// 获取数据列表接口
router.get("/users/list",(req, res) =>{
// 前端请求路径为 /api/users/list?page=2&limit=2
console.log(req.query); // { page: '2', limit: '2' }
let {page,limit} = req.query
UserModel.find({},["username","age"]).sort({age:1}).skip((page-1)*limit).limit(limit).then(data =>{
res.send(data); // 返回数据给前端
})
})
8.5 更新数据
UserModel.updateOne({_id:req.params.id},{username,age,password})
前面的参数:{_id:req.params.id} 要修改数据的id
后面的参数:{username,age,password} 要修改数据的内容
// 修改数据接口
router.post("/users/update/:id",(req, res) =>{
console.log(req.params.id); // 获取传来的id
let {username,age,password} = req.body;
UserModel.updateOne({_id:req.params.id},{
username,age,password
}).then(data =>{
res.send({"ok":1});
})
})
8.6 删除数据
UserModel.deleteOne({_id:req.params.id})
// 删除数据接口
router.get("/users/delete/:id",(req, res) =>{
console.log(req.params.id); // 获取传来的id
UserModel.deleteOne({_id:req.params.id}).then(data =>{
res.send({"ok":1});
})
})
四、接口规范与业务分层
1、接口规范
1.1 RESTful 架构
RESTful 是目前最流行的 API 设计规范,用于 Web 数据接口的设计。
RESTful 的核心思想就是,客户端发出的数据操作指令都是"动词 + 宾语"的结构。比如,GET /articles这个命令,GET是动词,/articles是宾语。
- Representational State Transfer,简称REST,即表述性的状态传递。
- 是一组对架构的约束条件和原则。RESTful API即充满表述性状态传递的API。
- REST是设计风格,而不是标准。
- REST常基于HTTP、URI和XML以及HTML等。
- REST常使用JSON的数据格式。
REST基本架构的四个方法:
- GET:用于数据的获取;
- POST:用于添加数据;
- PUT:用于更新或添加数据;
- DELETE:用于删除数据.
2、业务分层
2.1 业务分层之前
// 没有业务分层前,在routes文件夹中users.js
// 响应前端的的post请求-新增数据接口
router.post('/users', function(req, res, next) {
// console.log(req.body);
let {username,age,password} = req.body;
// 插入数据库
// 1.创建一个数据模型(user,限制filed类型),一一对应数据库的集合(users)
// user.create user.find user.delete user.update
UserModel.create({
username,password,age
}).then(data =>{ // 此处不要写res避免与上面的res冲突
console.log(data);
res.send({"ok":1});
})
});
// 响应前端put请求-修改数据接口
router.put("/users/:id",(req, res) =>{
console.log(req.params.id); // 获取传来的id
let {username,age,password} = req.body;
UserModel.updateOne({_id:req.params.id},{
username,age,password
}).then(data =>{
res.send({"ok":1});
})
})
// 响应前端delete请求-删除数据接口
router.delete("/users/:id",(req, res) =>{
console.log(req.params.id); // 获取传来的id
UserModel.deleteOne({_id:req.params.id}).then(data =>{
res.send({"ok":1});
})
})
// 响应前端get请求-获取数据列表接口
router.get("/users",(req, res) =>{
// 前端请求路径为 /api/users/list?page=2&limit=2
console.log(req.query); // { page: '2', limit: '2' }
let {page,limit} = req.query
UserModel.find({},["username","age"]).sort({age:1}).skip((page-1)*limit).limit(limit).then(data =>{
res.send(data); // 返回数据给前端
})
})
2.2 业务分层之后
分发
// routes下的users.js文件
// 响应前端的的post请求-新增数据接口
router.post('/users',UserController.addUser);
// 响应前端put请求-修改数据接口
router.put("/users/:id",UserController.updateUser);
// 响应前端delete请求-删除数据接口
router.delete("/users/:id",UserController.deleteUser);
// 响应前端get请求-获取数据列表接口
router.get("/users",UserController.getUser);
C层
// controllers文件夹下的UserController.js文件
const UserService = require("../services/UserService")
const UserController = {
addUser: async (req, res) =>{
// console.log(req.body);
let {username,age,password} = req.body;
// 插入数据库
// 1.创建一个数据模型(user,限制filed类型),一一对应数据库的集合(users)
// user.create user.find user.delete user.update
await UserService.addUser(username,age,password) // 防止异步,数据没回来就给前端send,使用await
res.send({"ok":1});
},
updateUser:async (req, res) =>{
console.log(req.params.id); // 获取传来的id
let {username,age,password} = req.body;
await UserService.updateUser(username,age,password,req.params.id)
res.send({"ok":1});
},
deleteUser:async (req, res) =>{
console.log(req.params.id); // 获取传来的id
await UserService.deleteUser(req.params.id)
res.send({"ok":1});
},
getUser:async (req, res) =>{
// 前端请求路径为 /api/users/list?page=2&limit=2
console.log(req.query); // { page: '2', limit: '2' }
let {page,limit} = req.query
const data = await UserService.getUser(page,limit)
res.send(data); // 返回数据给前端
}
}
module.exports = UserController
M层
//service文件夹下的UserService.js文件
const UserModel = require("../model/UserModel")
const UserService = {
addUser:(username,age,password)=>{
return UserModel.create({
username,password,age
})
},
updateUser:(username,age,password,id)=>{
return UserModel.updateOne({_id:id},{
username,age,password
})
},
deleteUser:(id)=>{
return UserModel.deleteOne({_id:id})
},
getUser:(page,limit)=>{
return UserModel.find({},["username","age"]).sort({age:1})
.skip((page-1)*limit).limit(limit)
}
}
module.exports = UserService
五、登录鉴权
1. cookie与session
1.1 下载 express-session
PS F:\大前端\前端\Node\node-class1\043-登录鉴权-cookie与session\server> npm i express-session
1.2 图解
「HTTP 无状态」我们知道,HTTP 是无状态的。也就是说,HTTP 请求方和响应方间无法维护状态,都是一次性的,它不知道前后的请求都发生了什么。但有的场景下,我们需要维护状态。最典型的,一个用户登陆微博,发布、关注、评论,都应是在登录后的用户状态下的。「标记」那解决办法是什么呢?
const express = require("express");
const session = require("express-session");
const MongoStore = require("connect-mongo");
const app = express();
app.use(
session({
secret: "this is session", // 服务器生成 session 的签名
resave: true,
saveUninitialized: true, //强制将为初始化的 session 存储
cookie: {
maxAge: 1000 * 60 * 10,// 过期时间
secure: false, // 为 true 时候表示只有 https 协议才能访问cookie
},
rolling: true, //为 true 表示 超时前刷新,cookie 会重新计时; 为 false 表示在超时前刷新多少次,都是按照第一次刷新开始计时。
store: MongoStore.create({
mongoUrl: 'mongodb://127.0.0.1:27017/kerwin_session',
ttl: 1000 * 60 * 10 // 过期时间
}),
})
);
app.use((req,res,next)=>{
if(req.url==="/login"){
next()
return;
}
if(req.session.user){
req.session.garbage = Date();
next();
}else{
res.redirect("/login")
}
})
在Node.js中,express-session
是一个流行的中间件,用于在Express应用程序中处理会话数据。以下是对express-session
配置项的详细解释:
-
secret
: 此选项是用于签署会话ID cookie的密钥。当使用签名cookie时,浏览器将不会发送cookie,除非它包含与先前签署会话ID cookie匹配的签名。建议将其设置为随机字符串。 -
resave
: 这是一个布尔值,用于强制将会话保存回存储区,即使在请求期间会话未被修改。通常建议将其设置为false
,因为通常您不希望在不必要的情况下执行存储操作。 -
saveUninitialized
: 这也是一个布尔值,用于强制将会话保存到客户端,即使会话未被初始化或修改。如果未设置此选项,则未修改的会话将不会被保存到客户端。建议将其设置为true
。 -
cookie
: 此选项用于配置会话ID cookie的选项。可以设置多个选项,例如:secure
: 布尔值,用于指示是否仅通过HTTPS传输cookie。设置为true
时,cookie只能通过HTTPS发送。httpOnly
: 布尔值,指示cookie是否仅可通过HTTP访问。设置为true
时,客户端脚本无法访问cookie。maxAge
: 一个数字或日期对象,用于设置cookie的有效期时长。它表示cookie何时过期。
-
name
: 会话ID cookie的名称。默认值为session_id
。可以根据需要更改。 -
store
: 用于存储会话数据的存储对象。默认情况下,express-session
使用内存存储,但您也可以使用其他存储库,如Redis、Memcached等。 -
rolling
: 布尔值,当设置为true
时,将会话ID cookie的有效期与当前请求的响应关联起来。这意味着每当用户发送请求时,将会话ID cookie的有效期都会延长一段时间。这有助于防止会话劫持攻击。 -
proxy
: 布尔值,指示是否启用反向代理(例如,使用Nginx作为反向代理)。如果设置为true
,则将根据代理设置会话cookie。 -
errorHandler
: 错误处理函数,用于捕获与会话相关的错误。可以根据需要自定义错误处理逻辑。
这些是express-session
中间件的主要配置项。根据您的应用程序需求和安全性要求,您可以选择使用其中的一些选项来调整会话处理行为。
在request.session
上挂载的session
对象,除了有你添加的内容外,还有默认的方法存在:
req.session.regenerate(function(err) {
// 调用这个方法从新生成一个新的会话,完成后触发
})
req.session.destroy(function(err) {
// 删除这个会话,完成后触发
})
req.session.reload(function(err) {
// 从新加载session数据,完成后触发回调
})
req.session.save(function(err) {
// 使用当前内存中的数据保存到储存器中
// 默认在会话结束的时候就会自动调用这个方法
})
req.session.touch() // 更新cookie中的maxAge,一般不需要手动操作,交由中间件
1.3 缺陷
express-session
中间件在处理会话数据时存在一些缺陷。以下是其中一些问题:
- 会话劫持:会话劫持是一种安全威胁,攻击者可以通过窃取会话ID cookie来冒充其他用户的身份。虽然
express-session
可以通过使用签名cookie来保护会话ID,但仍然存在潜在的安全风险。 - 存储限制:
express-session
使用内存存储会话数据,这意味着它不适合用于大型应用程序,因为存储在内存中的数据有限制,并且会话数据可能会丢失。 - 不支持集群:
express-session
不支持集群环境,因为它将所有会话数据存储在单个内存中。这意味着如果应用程序需要扩展到多个服务器,则需要使用其他类型的存储来共享会话数据。 - 客户端存储:会话数据存储在客户端的cookie中,这使得会话数据可能被泄露或篡改。虽然可以使用加密和签名来保护cookie,但这增加了开发人员的负担,并增加了计算开销。
- 性能问题:由于会话数据存储在客户端,每次请求都需要发送cookie到服务器。这可能会增加网络传输量,特别是在客户端和服务器之间进行频繁通信的情况下。
- 生命周期管理:
express-session
没有提供直接的方法来管理会话的生命周期。会话的创建、更新和删除等操作需要手动实现,这增加了开发人员的负担。
总之,虽然express-session
是一个流行的中间件,但在使用时需要注意其存在的缺陷,并采取适当的措施来保护应用程序的安全性和性能。
2. JWT
六、文件上传管理
七、APIDOC - API 文档生成工具
apidoc官网https://apidocjs.com/#configuration
vscode 安装 apidoc插件 "apiDoc Snippets"
输入"apiDocumentation"辅助编辑
/**
*
* @api {method} /path title
* @apiName apiName
* @apiGroup group
* @apiVersion major.minor.patch
*
*
* @apiParam {String} paramName description
*
* @apiSuccess (200) {type} name description
*
* @apiParamExample {type} Request-Example:
* {
* property : value
* }
*
*
* @apiSuccessExample {type} Success-Response:
* {
* property : value
* }
*
*
*/
八、koa
Koa 不与任何中间件绑定
参考网址https://www.npmjs.com/package/koa
Koa (koajs) -- 基于 Node.js 平台的下一代 web 开发框架 | Koajs 中文文档
1.安装koa2
# 初始化package.json
npm init# 安装koa2
npm install koa
2.示例
// 引入koa
const Koa = require('koa');
const app = new Koa();
// 注册应用级中间件
app.use(ctx=>{
ctx.body = "Hello"
})
// 监听3000端口号
app.listen(3000);
3. koa与express的区别
通常都会说 Koa 是洋葱模型,这重点在于中间件的设计。但是按照上面的分析,会发现 Express 也是类似的,不同的是Express 中间件机制使用了 Callback 实现,这样如果出现异步则可能会使你在执行顺序上感到困惑,因此如果我们想做接口耗时统计、错误处理 Koa 的这种中间件模式处理起来更方便些。最后一点响应机制也很重要,Koa 不是立即响应,是整个中间件处理完成在最外层进行了响应,而 Express 则是立即响应。
1.koa与express的同步区别不大
// express同步
// 引入express
const express = require('express');
const app = express();
app.use((req, res, next) => {
if (req.url==="/favicon.ico") {
return
}
console.log("11111");
next();
console.log("33333");
res.send("hello world");
})
app.use((req, res, next) => {
console.log("22222");
})
// 监听3000端口
app.listen(3000);
// 结果是:
// 11111
// 22222
// 33333
// koa同步
// 引入koa
const Koa = require('koa');
const app = new Koa();
app.use((ctx, next) => {
if (ctx.url === "/favicon.ico") {
return
}
console.log("11111");
next();
console.log("33333");
ctx.body = "Hello"
})
app.use((ctx, next) => {
console.log("22222");
})
// 监听3000端口
app.listen(3000);
// 结果是:
// 11111
// 22222
// 33333
2.koa与express的同步区别很大
// express异步
// 引入express
const express = require('express');
const app = express();
app.use((req, res, next) => {
if (req.url==="/favicon.ico") {
return
}
console.log("11111");
next();
console.log("44444");
res.send("hello world");
})
app.use(async(req, res, next) => {
console.log("22222");
await asycfun(2)
console.log("33333");
})
function asycfun(time) {
return new Promise((resolve, reject) => {
setTimeout(resolve, time);
})
}
// 监听3000端口
app.listen(3000);
// 结果是:
// 11111
// 22222
// 44444
// 33333
// koa异步
// 引入koa
const Koa = require('koa');
const app = new Koa();
app.use((ctx, next) => {
if (ctx.url === "/favicon.ico") {
return
}
console.log("11111");
next();
console.log("44444");
ctx.body = "Hello"
})
app.use((ctx, next) => {
console.log("22222");
asycfun(2);
console.log("33333");
})
function asycfun(time) {
return new Promise((resolve, reject) => {
setTimeout(resolve, time);
})
}
// 监听3000端口
app.listen(3000);
// 结果是:
// 11111
// 22222
// 33333
// 44444
3.更轻量
-
koa 不提供内置的中间件;
-
koa 不提供路由,而是把路由这个库分离出来了(koa/router)
4.Context对象
koa增加了一个Context的对象,作为这次请求的上下文对象(在koa2中作为中间件的第一个参数传入)。同时Context上也挂载了Request和Response两个对象。和Express类似,这两个对象都提供了大量的便捷方法辅助开发, 这样的话对于在保存一些公有的参数的话变得更加合情合理
5. 异步流程控制
express采用callback来处理异步, koa v1采用generator,koa v2 采用async/await。
generator和async/await使用同步的写法来处理异步,明显好于callback和promise,
6. 中间件模型
express基于connect中间件,线性模型;
koa中间件采用洋葱模型(对于每个中间件,在完成了一些事情后,可以非常优雅的将控制权传递给下一个中间件,并能够等待它完成,当后续的中间件完成处理后,控制权又回到了自己)
4.koa路由
1.基本用法
var Koa = require("koa")
var Router = require("koa-router")
var app = new Koa()
var router = new Router()
router.post("/list",(ctx)=>{
ctx.body=["111","222","333"]
})
app.use(router.routes()).use(router.allowedMethods())
app.listen(3000)
2.router.allowedMethods作用
3.请求方式
Koa-router 请求方式: get
、 put
、 post
、 patch
、 delete
、 del
,
使用方法就是 router.方式()
,比如 router.get()
和 router.post()
。
router.all()
会匹配所有的请求方法。
var Koa = require("koa")
var Router = require("koa-router")
var app = new Koa()
var router = new Router()
router.get("/user",(ctx)=>{
ctx.body=["aaa","bbb","ccc"]
})
.put("/user/:id",(ctx)=>{
ctx.body={ok:1,info:"user update"}
})
.post("/user",(ctx)=>{
ctx.body={ok:1,info:"user post"}
})
.del("/user/:id",(ctx)=>{
ctx.body={ok:1,info:"user del"}
})
app.use(router.routes()).use(router.allowedMethods())
app.listen(3000)
4.拆分路由
// 入口index.js
// 引入koa
const Koa = require('koa');
const router = require("./routes/index")
const app = new Koa();
// 注册应用级中间件
app.use(router.routes())
app.use(router.allowedMethods()) // 允许方法
// 监听3000端口号
app.listen(3000);
// index.js
const Router = require("koa-router")
const router = new Router();
const listRouter = require("./list")
const userRouter = require("./user")
const homeRouter = require("./home")
// 添加路由前缀
// router.prefix('/api')
// 注册路由级中间件
router.use("/list", listRouter.routes(),listRouter.allowedMethods())
router.use("/user", userRouter.routes(),userRouter.allowedMethods())
router.use("/home", homeRouter.routes(),homeRouter.allowedMethods())
// 路由重定向
router.redirect("/","/home")
module.exports = router
// list.js
const Router = require("koa-router")
const router = new Router();
// 获取数据
router.get('/',(ctx)=>{
ctx.body = ["111","222","333"]
})
// 增加数据
router.post('/',(ctx)=>{
ctx.body = {
ok:1,
info:"list added successfully"
}
})
//修改数据
router.put('/:id',(ctx)=>{
// 获取参数
console.log(ctx.params); // { id: '111' }
ctx.body = {
ok:1,
info:"list updata successfully"
}
})
// 删除数据
router.del('/:id',(ctx)=>{
ctx.body = {
ok:1,
info:"list deleted successfully"
}
})
module.exports = router
// user.js
const Router = require("koa-router")
const router = new Router();
// 获取数据
router.get('/',(ctx)=>{
ctx.body = ["aaa","bbb","ccc"]
})
// 增加数据
router.post('/',(ctx)=>{
ctx.body = {
ok:1,
info:"user added successfully"
}
})
//修改数据
router.put('/:id',(ctx)=>{
// 获取参数
console.log(ctx.params); // { id: '111' }
ctx.body = {
ok:1,
info:"user updata successfully"
}
})
// 删除数据
router.del('/:id',(ctx)=>{
ctx.body = {
ok:1,
info:"user deleted successfully"
}
})
module.exports = router
// home.js
const Router = require("koa-router")
const router = new Router();
// 获取数据
router.get('/',(ctx)=>{
ctx.body = `
<html>
<body>
<h1>home</h1>
</body>
</html>
`
})
module.exports = router
5.路由重定向
//写法1
router.redirect('/', '/home');
//写法2
router.get("/",(ctx)=>{
ctx.redirect("/home")
})
5.静态资源配置
下载koa-static模块
npm i koa-static
const path = require('path')
const static = require('koa-static')
// 配置静态资源
app.use(static(
path.join(__dirname, 'public')
))
6.获取参数
1.get参数
console.log(ctx.query,ctx.querystring);
// [Object: null prototype] { username: 'aaa', password: '111' } username=aaa&password=111
koa中,获取GET请求数据源头是koa中request对象中的query方法或querystring方法,query返回是格式化好的参数对象,querystring返回的是请求字符串,由于ctx对request的API有直接引用的方式,所以获取GET请求数据有两个途径。
-
是从上下文中直接获取 请求对象ctx.query,返回如 { a:1, b:2 } 请求字符串 ctx.querystring,返回如 a=1&b=2
-
是从上下文的request对象中获取 请求对象ctx.request.query,返回如 { a:1, b:2 } 请求字符串 ctx.request.querystring,返回如 a=1&b=2
2.post参数
需要用"koa-bodyparser '中间件加持,无法直接获取
安装引入"koa-bodyparser "
npm i koa-bodypa
// 引入koa
const Koa = require('koa');
const router = require("./routes/index")
const path = require('path')
const static = require('koa-static')
// 引入bodyparser
const bodyparser = require('koa-bodyparser')
const app = new Koa();
// 配置静态资源
app.use(static(
path.join(__dirname, 'public')
))
// 注册应用级中间件
app.use(bodyparser())
app.use(router.routes())
app.use(router.allowedMethods()) // 允许方法
// 监听3000端口号
app.listen(3000);
koa-bodyparser中间件可以把koa2上下文的formData数据解析到ctx.request.body中
// 获取post参数需引入“koa-bodyparser”中间件,用ctx.request.body获取
console.log(ctx.request.body); // { username: 'aa', password: '11' }
7.ejs模板
1.安装模板
# 安装koa模板使用中间件
npm i koa-views
# 安装ejs模板引擎
npm i ejs
// 入口index.js文件
// 引入koa
const Koa = require('koa');
const router = require("./routes/index")
const path = require('path')
const static = require('koa-static')
const bodyparser = require('koa-bodyparser')
const views = require('koa-views')
const app = new Koa();
// 配置静态资源
app.use(static(
path.join(__dirname, 'public')
))
// 注册应用级中间件
app.use(bodyparser()) // 获取post参数
app.use(views(path.join(__dirname, './views'),{extension:"ejs"})) // 配置模板文件夹,以及模板语言
app.use(router.routes())
app.use(router.allowedMethods()) // 允许方法
// 监听3000端口号
app.listen(3000);
2.home.ejs模板
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>home模板页面</h1>
<div>欢迎<%= username%>回来</div>
</body>
</html>
3.home接口返回渲染模板
const Router = require("koa-router")
const router = new Router();
// 获取数据
router.get('/',async(ctx,next)=>{
// 注意此处的渲染是异步
await ctx.render("home",{username:"feige"})
})
module.exports = router
8.cookie&session
1.cookie
koa提供了从上下文直接读取、写入cookie的方法
-
ctx.cookies.get(name, [options]) 读取上下文请求中的cookie
-
ctx.cookies.set(name, value, [options]) 在上下文中写入cookie
// 设置cookie
ctx.cookies.set("username","feige")
// 获取cookie
console.log(ctx.cookies.get("username"));
2.session
koa-session-minimal 适用于koa2 的session中间件,提供存储介质的读写接口
const session = require('koa-session-minimal')
// session配置,session配置需放在router之前,否则设置session会报错
app.use(session({
key: 'feigeSssesion',
cookie: {
maxAge:1000*60*60
}
}))
// session判断拦截,同样在路由跳转之前
app.use(async(ctx,netx)=>{
// 排除登录 login
if (ctx.url.includes("login")) {
await netx() // 洋葱模型,如果包含login,执行权交给下面跳转路由
return
}
// 如果session存在
if (ctx.session.user) {
// 更新session
ctx.session.mydate = Date.now()
await netx()
}else{
ctx.redirect("/login")
}
})
设置session
// 登录成功 设置sessionid
ctx.session.user = {
username:"feige"
}
9.JWT(前后端分离推荐)
9.1服务器入口中设置token拦截
// token拦截
app.use(async(ctx, next) => {
//排除login相关的路由和接口
if (ctx.url.includes("login")) {
await next()
return
}
const token = ctx.headers["authorization"]?.split(" ")[1]
// console.log(req.headers["authorization"])
if(token){
const payload= JWT.verify(token)
if(payload){
//重新计算token过期时间
const newToken = JWT.generate({
_id:payload._id,
username:payload.username
},"10s")
ctx.set("Authorization",newToken)
await next()
}else{
ctx.status = 401
ctx.body = {errCode:-1,errInfo:"token过期"}
}
}else{
await next()
}
})
9.2 登录接口中设置token
// login登录
router.post('/login',(ctx)=>{
// 获取post参数需引入“koa-bodyparser”中间件,用ctx.request.body获取
// console.log(ctx.request.body); // { username: 'aa', password: '11' }
const {username,password} = ctx.request.body
// 验证用户名密码
if (username==="feige" && password==="111") {
// 登录成功 设置token
const token = JWT.generate({
_id:"111",
username:"feige"
},"10s")
// token 返回在header中
ctx.set("Authorization",token)
// 登录成功 返回数据
ctx.body = {
ok:1,
info:"login successfully"
}
}else{
ctx.body = {
ok:0,
info:"login failed"
}
}
})
9.3 axios响应拦截返回数据时,在本地存储重新设置的token
//拦截器,
axios.interceptors.request.use(
function (config) {
// console.log("请求发出前,执行的方法")
// Do something before request is sent
const token = localStorage.getItem("token");
config.headers.Authorization = `Bearer ${token}`;
return config;
},
function (error) {
// Do something with request error
return Promise.reject(error);
}
);
// Add a response interceptor
axios.interceptors.response.use(
function (response) {
// console.log("请求成功后 ,第一个调用的方法")
const { authorization } = response.headers;
authorization && localStorage.setItem("token", authorization);
return response;
},
function (error) {
// console.log(error.response.status)
if (error.response.status === 401) {
localStorage.removeItem("token");
location.href = "/login";
}
return Promise.reject(error);
}
);