NodeJS 入门笔记

文档地址

课程地址

源码 提取码:963h

hello wrold

console.log('hello, world');
node hello.js

nodejs 中不能使用 DOM(document) 和 BOM(window) 的 API:

  • document
  • window
  • history
  • navigator
  • location

在这里插入图片描述

但是下面的 API 是相通的:

  • console
  • timer
console.log('hello, world');setTimeout(() => {console.log('fuck the world');
}, 1000);

nodejs 中的顶级对象为 global,也可以使用 globalThis 访问顶级对象:

console.log(global);
console.log(global === globalThis);		// true

Buffer

字节序列

let buf1 = Buffer.alloc(10);
let buf2 = Buffer.allocUnsafe(10);	// 有旧数据
let buf3 = Buffer.from('hello');	// str -> buffer
let buf4 = Buffer.from([101, 102, 103, 104]);let s4 = buf4.toString();
console.log(buf3[0]);
buf3[0] = 361;		// 溢出

fs

writeFile

fs.write(file, data[, option], callback);
const fs = require('fs');fs.writeFile('./hello.txt', 'hello, world\n', err => {		// asyncif (err) {console.log('write failed');} else {console.log('write success');}
});

异步与同步

writeFile 默认是异步的,对于下面的代码:

const fs = require('fs');fs.writeFile('./hello.txt', 'hello, world\n', err => {		// asyncif (err) {console.log('write failed');} else {console.log('write success');}
});console(1 + 1);

2 会先输出。原因在于写文件被主线程派发给一个子线程去执行,写文件不会阻塞主线程

下面是使用同步写入:

const fs = require('fs');
fs.writeFileSync('./test.txt', 'test');
console(1 + 1);

appendFile

const fs = require('fs');fs.appendFile('./hello.txt', 'fuck, world\n', err => {		// asyncif (err) {console.log('append failed');} else {console.log('appeend success');}
});

同步版本:

fs.appendFileSync('./hello.txt', 'fuck, world\n')

使用 writeFile 实现追加写入:

fs.writeFile('./hello.txt', 'fuck, world\n', {flag: 'a'}, err => {		// asyncif (err) {console.log('append failed');} else {console.log('appeend success');}
});

流式写入

fs.createWriteStream(path[, options]);
let ws = fs.createWriteStream('./观书有感.txt');
ws.write('半亩方塘一鉴开\r\n');
ws.write('天光云影共徘徊\r\n');
ws.write('问渠那得清如许\r\n');
ws.write('为有源头活水来\r\n');
ws.end();

文件读取

异步读取

const fs = require('fs');fs.readFile('hello.txt', (err, data) => {		// asyncif (err) {console.log('append failed');} else {console.log(data.toString());}
});

同步读取

let data = readFileSync('hello.txt');

流式读取

 fs.createReadStream(path[, options])
//创建读取流对象
let rs = fs.createReadStream('./观书有感.txt');
//每次取出 64k 数据后执行一次 data 回调
rs.on('data', data => {console.log(data);console.log(data.length);
});
//读取完毕后, 执行 end 回调
rs.on('end', () => {console.log('读取完成');
});

文件复制

同步读写

const fs = require('fs');
let data = fs.readFileSync('hello.txt');
fs.writeFileSync('hello-2.txt', data);

流式读写

const rs = fs.createReadStream('hello.txt');
const ws = fs.createWriteStream('hello-3.txt');
rs.on('data', chunk => {ws.write(chunk);
});

或者如下简写:

rs.pipe(ws);

文件重命名

文件重命名通过 rename() 实现:

fs.rename(oldPath, newPath, callback);
fs.renameSync(oldPath, newPath);
fs.rename('./观书有感.txt', './论语/观书有感.txt', (err) =>{if(err) throw err;console.log('移动完成')
});
fs.renameSync('./座右铭.txt', './论语/我的座右铭.txt');

文件删除

fs.unlink(path, callback)
fs.unlinkSync(path)
const fs = require('fs');
fs.unlink('./test.txt', err => {if(err) throw err;console.log('删除成功');
});
fs.unlinkSync('./test2.txt');

使用 rm 方法也可以

文件夹操作

mkdir

fs.mkdir(path[, options], callback)
fs.mkdirSync(path[, options])
//异步创建文件夹
fs.mkdir('./page', err => {if(err) throw err;console.log('创建成功');
});
//递归异步创建
fs.mkdir('./1/2/3', {recursive: true}, err => {if(err) throw err;console.log('递归创建成功');
});
//递归同步创建文件夹
fs.mkdirSync('./x/y/z', {recursive: true});

readdir

fs.readdir(path[, options], callback)
fs.readdirSync(path[, options])
//异步读取
fs.readdir('./论语', (err, data) => {if(err) throw err;console.log(data);
});
//同步读取
let data = fs.readdirSync('./论语');
console.log(data);

rmdir

fs.rmdir(path[, options], callback)
fs.rmdirSync(path[, options])
//异步删除文件夹
fs.rmdir('./page', err => {if(err) throw err;console.log('删除成功');
});
//异步递归删除文件夹
fs.rmdir('./1', {recursive: true}, err => {if(err) {console.log(err);}console.log('递归删除')
});
//同步递归删除文件夹
fs.rmdirSync('./x', {recursive: true})

查看资源状态

fs.stat(path[, options], callback)
fs.statSync(path[, options])
//异步获取状态
fs.stat('./data.txt', (err, data) => {if(err) throw err;console.log(data);console.log(data.isFile());console.log(data.isDirectory());
});
//同步获取状态
let data = fs.statSync('./data.txt');

在这里插入图片描述

绝对路径:

__dirname

__dirname 保存着当前文件所在目录的绝对路径,可以使用 __dirname 与文件名拼接成绝对路径

批量重命名练习

const fs = require('fs');
const files = fs.readdirSync('./code');files.forEach(item => {let [num, name] = item.split('-');if (Number(num) < 10) {num = '0' + num;}let newName = num + '-' + name;fs.renameSync(`./code${item}`, `./code/${newName}`);console.log(item);
});

path

在这里插入图片描述

const path = require('path');
//获取路径分隔符
console.log(path.sep);
//拼接绝对路径
console.log(path.resolve(__dirname, 'test'));
//解析路径
let pathname = 'D:/program file/nodejs/node.exe';
console.log(path.parse(pathname));
//获取路径基础名称
console.log(path.basename(pathname))
//获取路径的目录名
console.log(path.dirname(pathname));
//获取路径的扩展名
console.log(path.extname(pathname));

HTTP 协议

在这里插入图片描述

请求行

在这里插入图片描述

请求方法

在这里插入图片描述

url

在这里插入图片描述

协议版本号

请求头

一系列键值对

请求体

请求体的内容格式是非常灵活的,可以设置任意内容

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

响应行

协议版本号

响应状态码

在这里插入图片描述

响应状态字符串

响应头

也是一系列键值对,可以自定义

响应体

响应体内容的类型是非常灵活的,常见的类型有 HTML、CSS、JS、图片、JSON

http

const http = require('http');const server = http.createServer((request, response) => {response.end('<h1>hello, http server</h1>');
});server.listen(9000, () => {console.log('listening on 9000');
});

解决中文乱码:

response.setHeader('content-type','text/html;charset=utf-8');

发送 post 请求

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

获取 HTTP 请求报文

在这里插入图片描述

获取请求体

const http = require('http');const server = http.createServer((request, response) => {response.setHeader('content-type','text/html;charset=utf-8');let body = '';request.on('data', chunk => {body += chunk;});request.on('end', () => {console.log(body);response.end('hello, nodejs http')});
});server.listen(9000, () => {console.log('listening on 9000');
});

获取请求路径和查询字符串

const http = require('http');
const url = require('url');const server = http.createServer((request, response) => {response.setHeader('content-type','text/html;charset=utf-8');console.log(url.parse(request.url).pathname);console.log(url.parse(request.url, true).query);
});server.listen(9000, () => {console.log('listening on 9000');
});

或者使用内置的 URL 类:

const http = require('http');const server = http.createServer((request, response) => {let url = new URL(request.url, 'http://localhost');console.log(url.pathname);console.log(url.searchParams.get('keyword'));response.end('url new');
});server.listen(9000, () => {console.log('listening on 9000');
});

练习

在这里插入图片描述

const http = require('http');const server = http.createServer((request, response) => {response.setHeader('content-type','text/html;charset=utf-8');let {url, method} = request;if(url == '/login' && method == 'GET') {response.end('登录');} else if (url == '/reg' && method == 'GET') {response.end('注册');} else {response.end('wrong');}
});server.listen(9000, () => {console.log('listening on 9000');
});

设置响应报文

在这里插入图片描述

const http = require('http');const server = http.createServer((request, response) => {response.statusCode = 200;	// 响应行response.setHeader('content-type','text/html;charset=utf-8');	// 响应头response.end('response');	// 响应体
});server.listen(9000, () => {console.log('listening on 9000');
});

设置响应头:

response.setHeader('server','nodejs');
response.setHeader('myheader','test test test');
response.setHeader('test',['a', 'b', 'c']);		// 设置多个同名响应头

设置响应体:

response.write('hello');

练习

搭建 HTTP 服务,响应一个 4 行 3 列的表格,并且要求表格有隔行换色效果 ,且点击单元格能高亮显示

服务端代码:

const http = require('http');
const fs = require('fs');const server = http.createServer((request, response) => {response.setHeader('content-type','text/html;charset=utf-8');fs.readFile('table.html', (err, data) => {response.end(data);});
});server.listen(9000, () => {console.log('listening on 9000');
});

前端代码:

<!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>
<style>td {padding: 20px 40px;}table tr:nth-child(odd) {background-color: #aef;}table tr:nth-child(even) {background-color: #fcb;}table, td {border-collapse: collapse;}
</style>
<body><table border="1"><tr><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td></tr></table>
</body>
<script>let tds = document.querySelectorAll('td');tds.forEach(item => {item.onclick = function() {this.style.background = '#bfa';}});
</script>
</html>

网页资源的加载都是循序渐进的,首先获取 HTML 的内容, 然后解析 HTML 在发送其他资源的请求,如 CSS,Javascript,图片等

练习扩展

将前端的 html css js 三者分离:

<!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="table.css">
<body><table border="1"><tr><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td></tr></table>
</body>
<script src="table.js"></script>
</html>
// table.js
let tds = document.querySelectorAll('td');
tds.forEach(item => {item.onclick = function() {this.style.background = '#bfa';}
});
// table.css
td {padding: 20px 40px;
}
table tr:nth-child(odd) {background-color: #aef;
}
table tr:nth-child(even) {background-color: #fcb;
}
table, td {border-collapse: collapse;
}

服务端:

const http = require('http');
const fs = require('fs');const server = http.createServer((request, response) => {let {pathname} = new URL(request.url, 'http://localhost');if(pathname === '/') {fs.readFile('table.html', (err, data) => {response.end(data);});} else if (pathname === '/table.js') {fs.readFile('table.js', (err, data) => {response.end(data);});} else if (pathname === '/table.css') {fs.readFile('table.css', (err, data) => {response.end(data);});} else {response.end('error');}
});server.listen(9000, () => {console.log('listening on 9000');
});

获取 page 下的静态资源服务:

const http = require('http');
const fs = require('fs');const server = http.createServer((request, response) => {let {pathname} = new URL(request.url, 'http://localhost');let filePath = __dirname + '/page' + pathname;fs.readFile(filePath, (err, data) => {if (err) {response.statusCode = 500;response.end('read file failed');}response.end(data);});
});server.listen(9000, () => {console.log('listening on 9000');
});

在这里插入图片描述

<link rel="stylesheet" href="/css/app.css">

MIME 类型

在这里插入图片描述

错误处理

const http = require('http');
const fs = require('fs');const server = http.createServer((request, response) => {if(request.method !== 'GET') {response.statusCode = 405;response.end('<h1> 405 Method not allowed </h1>');return;}let {pathname} = new URL(request.url, 'http://localhost');let filePath = __dirname + '/page' + pathname;fs.readFile(filePath, (err, data) => {if (err) {console.log(err);switch(err.code) {case 'ENOENT':response.statusCode = 404;response.end('<h1> 404 Not Found </h1>');case 'EPERM':response.statusCode = 403;response.end('<h1> 403 Forbidden </h1>');default:response.statusCode = 500;response.end('error');}}response.end(data);});
});server.listen(9000, () => {console.log('listening on 9000');
});

GET 与 POST 场景区别

在这里插入图片描述

在这里插入图片描述

模块化

其中拆分出的每个文件就是一个模块 ,模块的内部数据是私有的,不过模块可以暴露内部数据以便其他模块使用

// me.js
function tiemo() {console.log("tiemo");
}function niejiao() {console.log('niejiao');
}module.exports = {tiemo,niejiao
};
const me = require('./me.js');       // 不能省略 ./me.tiemo();
me.niejiao();

在这里插入图片描述

导入文件夹

// package.json
{"main": "./app.js"
}
// app.js
module.exports = "this is module1";
// index.js
const m1 = require('./module1');       // 不能省略 ./
console.log(m1);		// "this is module1"

导入模块的流程

在这里插入图片描述

CommonJS 模块化规范

在这里插入图片描述

包管理工具

npm

node package manager

初始化包

npm init

npm init 命令的作用是将文件夹初始化为一个『包』, 交互式创建 package.json 文件

{"name": "test","version": "1.0.0","description": "learning npm","main": "index.js","scripts": {"test": "echo \"Error: no test specified\" && exit 1"},"author": "daniel","license": "ISC"
}

在这里插入图片描述

搜索包

下载安装包

npm install uniq

在这里插入图片描述

使用包:

const uniq = require('uniq');let a1 = [1, 2, 3, 4, 5, 3, 2, 1];const result = uniq(a1);console.log(result)

开发依赖与生产依赖

npm i -S jquery		# 生产依赖
npm i -D less		# 开发依赖

全局安装

npm i -g nodemon
npm root -g	# 查看全局安装位置

安装所有依赖:

在这里插入图片描述

指定版本与删除包

npm i jquery@1.11.2
npm remove uniq		# 局部删除
npm r -g nodemon	# 全局删除

配置命令别名

{
...
"scripts": {
"server": "node server.js",
"start": "node index.js",
},
...
}

配置完成之后,可以使用别名执行命令

npm run server
npm run start

在这里插入图片描述

cnpm

cnpm 是一个淘宝构建的 npmjs.com 的完整镜像,也称为『淘宝镜像』,网址https://npmmirror.com/

cnpm 服务部署在国内阿里云服务器上 , 可以提高包的下载速度 官方也提供了一个全局工具包 cnpm ,操作命令与 npm 大体相同

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

用 npm 也可以使用淘宝镜像:

使用命令行配置:

npm config set registry https://registry.npmmirror.com/

或者使用 nrm 工具配置:

nrm = npm registry manager

npm i -g nrm
nrm use taobao
nrm use npm

yarn

npm i -g yarn

在这里插入图片描述

nvm = node version manager

express 框架

基于 nodejs 的 web 开发框架

node init
node i express

一个最简单的 webserver:

const express = require('express');const app = express();app.get('/home', (req, res) => {res.end('express hello');
});app.listen(3000, () => {console.log('listening...');
});

路由

在这里插入图片描述

const express = require('express');const app = express();app.get('/home', (req, res) => {res.end('express hello');
});app.get('/', (req, res) => {res.end('home');
});app.post('/login', (req, res) => {res.end('login');
});app.all('/test', (req, res) => {res.end('test');
});app.all('*', (req, res) => {res.end('404 not found');
});app.listen(3000, () => {console.log('listening...');
});

发送 post 请求:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><form action="http://localhost:3000/login" method="post"><button>登录</button></form>
</body>
</html>

获取请求报文参数

//导入 express
const express = require('express');
//创建应用对象
const app = express();
//获取请求的路由规则
app.get('/request', (req, res) => {//1. 获取报文的方式与原生 HTTP 获取方式是兼容的console.log(req.method);console.log(req.url);console.log(req.httpVersion);console.log(req.headers);//2. express 独有的获取报文的方式//获取查询字符串console.log(req.query); // 『相对重要』console.log(req.path);// 获取指定的请求头console.log(req.get('host'));res.send('请求报文的获取');
});
//启动服务
app.listen(3000, () => {console.log('启动成功....')
});

获取路由参数

路由参数指的是 URL 路径中的参数(数据),使用 req.params 获取

app.get('/:id.html', (req, res) => {		//:id 是占位符res.send('商品详情, 商品 id 为' + req.params.id);
});
const express = require('express');
const {singers} = require('singers.json');const app = express();app.get('/singer/:id.html', (req, res) => {let {id} = req.params;let singer = singers.find(item => {if (item.id === Number(id)) {return true;}});if (!singer) {res.statusCode = 404;res.end('404 not found');} else {res.end(singer);}
});

响应设置

express 框架封装了一些 API 来方便给客户端响应数据,并且兼容原生 HTTP 模块的获取方式

//获取请求的路由规则
app.get("/response", (req, res) => {//1. express 中设置响应的方式兼容 HTTP 模块的方式res.statusCode = 404;res.statusMessage = 'xxx';res.setHeader('abc','xyz');res.write('响应体');res.end('xxx');//2. express 的响应方法res.status(500); //设置响应状态码res.set('xxx','yyy');//设置响应头res.send('中文响应不乱码');//设置响应体//连贯操作res.status(404).set('xxx','yyy').send('你好朋友')//3. 其他响应res.redirect('http://atguigu.com'); //重定向res.download('./package.json'); //下载响应res.json(); //响应 JSON,一般用于接口化开发res.sendFile(__dirname + '/home.html') //响应文件内容
});

中间件

在这里插入图片描述

全局中间件

每一个请求到达服务端之后都会执行全局中间件函数

const express = require('express');
const fs  = require('fs');
const path = require('path');const app = express();function recordMiddleware(req, res, next) { // 全局中间件函数let {url, ip} = req;fs.appendFileSync(path.resolve(__dirname, './access.log'), `${url} ${ip}\r\n`);//执行next函数(当如果希望执行完中间件函数之后,仍然继续执行路由中的回调函数,必须调用next)next();
}app.use(recordMiddleware);	// 注册全局中间件app.get('/home', (req, res) => {res.end('home');
});app.get('/admin', (req, res) => {res.end('admin');
});app.all('*', (req, res) => {res.end('404 not found');
});app.listen(3000, () => {console.log('listening...');
});

在这里插入图片描述

路由中间件

如果只需要对某一些路由进行功能封装 ,则就需要路由中间件

const express = require('express');
const fs  = require('fs');
const path = require('path');const app = express();let checkCodeMiddleware = (req, res, next) => { // 全局中间件函数if (req.query.code === '521') {next();} else {res.send('code error');}
}app.get('/setting', checkCodeMiddleware, (req, res) => {res.end('home');
});app.get('/admin', checkCodeMiddleware, (req, res) => {res.end('admin');
});app.all('*', (req, res) => {res.end('404 not found');
});app.listen(3000, () => {console.log('listening...');
});

静态资源中间件

app.use(express.static(__dirname + '/public'));

在这里插入图片描述

获取请求体数据

按照要求搭建 http 服务:

  • GET /login 显示表单网页
  • POST /login 获取表单中的用户名和密码

使用 body-parser 中间件:

const express = require('express');
const bodyParser = require('body-parser');//处理 querystring 格式的请求体
let urlParser = bodyParser.urlencoded({extended:false});const app = express();app.get('/login', (req, res) => {res.sendFile(__dirname + '/form.html');
});app.post('/login', urlParser, (req, res) => {//用户名console.log(req.body.username);//密码console.log(req.body.password);res.send('获取请求体数据');
});app.all('*', (req, res) => {res.end('404 not found');
});app.listen(3000, () => {console.log('listening...');
});
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>登录</title>
</head>
<body><form action="/login" method="post">username: <input type="text" name="username"> <br />password: <input type="text" name="password"> <br /><button>submit</button></form>
</body>
</html>

防盗链

禁止其他域名访问本网站资源

referer 请求头能够携带当前域名信息:

referer: https://www.jj20.com/
const express = require('express');const app = express();app.use((req, res, next) => {let referer = req.get('referer');	// 获取 referer 请求头if (referer) {let url = new URL(referer);let hostname = url.hostname;if (hostname !== '127.0.0.1') {res.status(404).send('<h1> 404 not found</h1>');return;}}next();
});app.use(express.static(__dirname + '/public'));app.all('*', (req, res) => {res.end('404 not found');
});app.listen(3000, () => {console.log('listening...');
});

Router 路由模块化

什么是 Router:express 中的 Router 是一个完整的中间件路由系统,可以看作是一个小型的 app 对象

Router 的作用:对路由进行模块化,更好地管理路由

//server.js
const express = require('express');const app = express();const homeRouter = require('./homeRouter');
const adminRouter = require('./adminRouter');app.use(homeRouter);
app.use(adminRouter);app.all('*', (req, res) => {res.end('404 not found');
});app.listen(3000, () => {console.log('listening...');
});
// homeRouter.js
const express = require('express');const router = express.Router();router.get('/home', (req, res) => {res.send('home');
});router.get('/search', (req, res) => {res.send('search');
});module.exports = router;
// adminRouter.js
const express = require('express');const router = express.Router();router.get('/admin', (req, res) => {res.send('admin');
});router.get('/setting', (req, res) => {res.send('setting');
});module.exports = router;

ejs 模板引擎

模板引擎:分离用户界面和业务数据

npm i ejs
<% code %>		// 执行 js 代码
<%= code %>		// 输出转义的数据到模板上
<%- code %>		// 输出非转义的数据到模板上
const ejs = require('ejs');let y = 'you';let res = ejs.render('i love <%= y %>', {y: y});
console.log(res);

也可以对 html 文档中的内容进行替换:

// ejs.js
const ejs = require('ejs');
const fs = require('fs');let y = 'you';let html = fs.readFileSync('./index.html').toString();let res = ejs.render(html, {y: y});
console.log(res);
<!-- 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><h1>i love <%= y %> </h1>
</body>
</html>

列表渲染

// ejs.js
const ejs = require('ejs');
const fs = require('fs');const arr = ['appal', 'orange', 'banana', 'grapes'];let html = fs.readFileSync('./index.html').toString();let res = ejs.render(html, {arr: arr});console.log(res);
<!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><ul><% arr.forEach(item => { %><li><%= item %></li><% }) %></ul>
</body>
</html>

条件渲染

在这里插入图片描述

// ejs.js
const ejs = require('ejs');
const fs = require('fs');let isLogin = false;let html = fs.readFileSync('./index.html').toString();let res = ejs.render(html, {isLogin: isLogin});console.log(res);
<!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><% if(isLogin) { %><span> 欢迎回来 </span><% } else { %><button>登录</button> <button>注册</button><% } %>
</body>
</html>

express 中使用 ejs

// ejs.js
const express = require('express');
const path = require('path');const app = express();app.set('view engine', 'ejs');	// 设置引擎
app.set('views', path.resolve(__dirname, './views'));	// 设置模板路径app.get('/home', (req, res) => {let title = 'hello, world';res.render('home', {title});	// 将 title render 给 home.ejs
});app.listen(3000, () => {console.log('listening on 3000');
})
// views/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><%= title %>
</body>
</html>

express-generator

用于快速创建一个应用的骨架

npm i -g express-generator
express -e prj_name
cd prj_name
npm i	// 安装依赖
npm start

然后浏览器访问 http://localhost:3000/ 即可

// app.js
app.use('/', indexRouter);
app.use('/users', usersRouter);		// users 是路由前缀

文件上传

// routers/index.js
var express = require('express');
var router = express.Router();
const {formidable} = require('formidable');/* GET home page. */
router.get('/', function(req, res, next) {res.render('index', { title: 'Express' });
});router.get('/portrait', (req, res) => {res.render('portrait');
});router.post('/portrait', (req, res) => {const form = formidable({multiples: true,uploadDir: __dirname + '/../public/images',keepExtensions: true});form.parse(req, (err, fields, files) => {if (err) {next(err);return;}let url = '/images/' + files.portrait[0].newFilename;res.send(url);});
});module.exports = router;
<!--portrait.ejs-->
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Portrait</title>
</head>
<body><h1>Portrait</h1><form action="/portrait" method="post" enctype="multipart/form-data">用户名:<input type="text" name="username"> <br>头像:<input type="file" name="portrait"><button>点击提交</button></form>
</body>
</html>

记账本案例实践

初始化项目:

express -e
npm i
npm start

添加 2 个路由和 2 个静态页面:

// routes/index.js
/* GET home page. */
router.get('/account', function(req, res, next) {// res.render('index', { title: 'Express' });res.render('list');
});// 发送表单页面
router.get('/account/create', function(req, res, next) {// res.render('index', { title: 'Express' });res.render('create');
});// 处理表单数据
router.post('/account', (req, res) => {console.log(req.body);res.send('add account');
});
<!-- views/list.ejs -->
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title><linkhref="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.css"rel="stylesheet"/><style>label {font-weight: normal;}.panel-body .glyphicon-remove{display: none;}.panel-body:hover .glyphicon-remove{display: inline-block}</style></head><body><div class="container"><div class="row"><div class="col-xs-12 col-lg-8 col-lg-offset-2"><h2>记账本</h2><hr /><div class="accounts"><div class="panel panel-danger"><div class="panel-heading">2023-04-05</div><div class="panel-body"><div class="col-xs-6">抽烟只抽煊赫门,一生只爱一个人</div><div class="col-xs-2 text-center"><span class="label label-warning">支出</span></div><div class="col-xs-2 text-right">25 元</div><div class="col-xs-2 text-right"><spanclass="glyphicon glyphicon-remove"aria-hidden="true"></span></div></div></div><div class="panel panel-success"><div class="panel-heading">2023-04-15</div><div class="panel-body"><div class="col-xs-6">3 月份发工资</div><div class="col-xs-2 text-center"><span class="label label-success">收入</span></div><div class="col-xs-2 text-right">4396 元</div><div class="col-xs-2 text-right"><spanclass="glyphicon glyphicon-remove"aria-hidden="true"></span></div></div></div></div></div></div></div></body>
</html>

表单页面:

<!-- views/create.ejs -->
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>添加记录</title><linkhref="/css/bootstrap.css"rel="stylesheet"/><!-- 以 public 为根目录 --><link href="/css/bootstrap-datepicker.css" rel="stylesheet"></head><body><div class="container"><div class="row"><div class="col-xs-12 col-lg-8 col-lg-offset-2"><h2>添加记录</h2><hr /><form method="post" action="/account"><div class="form-group"><label for="item">事项</label><inputname="title"type="text"class="form-control"id="item"/></div><div class="form-group"><label for="time">发生时间</label><inputname="time"type="text"class="form-control"id="time"/></div><div class="form-group"><label for="type">类型</label><select class="form-control" id="type" name="type"><option value="-1">支出</option><option value="1">收入</option></select></div><div class="form-group"><label for="account">金额</label><inputname="account"type="text"class="form-control"id="account"/></div><div class="form-group"><label for="remarks">备注</label><textarea class="form-control" id="remarks" name="remark"></textarea></div><hr><button type="submit" class="btn btn-primary btn-block">添加</button></form></div></div></div><!-- 以 public 为根目录 --><script src="/js/jquery.min.js"></script><script src="/js/bootstrap.min.js"></script><script src="/js/bootstrap-datepicker.min.js"></script><script src="/js/bootstrap-datepicker.zh-CN.min.js"></script><script src="/js/main.js"></script></body>
</html>

lowdb

npm i lowdb@1.0.0
const low = require('lowdb');
const FileSync = require('lowdb/adapters/FileSync');const adapter = new FileSync('db.json');
const db = low(adapter);// Set some defaults
db.defaults({ posts: [], user: {} }).write()
/*
{"posts": [],"user": {}
}
*/// Add a post
db.get('posts').push({ id: 1, title: 'lowdb is awesome'}).write()
/*
{"posts": [{"id": 1,"title": "lowdb is awesome"}],"user": {}
}
*/db.get('posts').unshift({id: 2, title: 'hello, world'}).write()
/*{"posts": [{"id": 2,"title": "hello, world"},{"id": 1,"title": "lowdb is awesome"}],"user": {}}
*/console.log(db.get('posts').value())db.get('posts').remove({id: 2}).write()db.get('posts').find({id: 1}).assign({title: "fuck, world"}).write()

保存账单信息

// data/db.json
{"accounts": []
}

安装 shortid:

npm i shortid

收到 post 请求后存入数据库:

// index.js
const low = require('lowdb');
const FileSync = require('lowdb/adapters/FileSync'); 
const adapter = new FileSync(__dirname + '/../data/db.json');
const db = low(adapter);
const shortid = require("shortid");router.post('/account', (req, res) => {console.log(req.body);let id = shortid.generate();db.get("accounts").unshift({id: id, ...req.body}).write();res.render('success',  {msg: "添加成功~~~", url: '/account'});
});
<!-- success.ejs -->
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>提醒</title><linkhref="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.css"rel="stylesheet"/><style>.h-50{height: 50px;}</style>
</head>
<body><div class="container"><div class="h-50"></div><div class="alert alert-success" role="alert"><h1><%= msg %></h1><p><a href="<%= url %>">点击跳转</a></p></div></div>
</body>
</html>

账单列表

<!-- list.ejs -->
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title><linkhref="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.css"rel="stylesheet"/><style>label {font-weight: normal;}.panel-body .glyphicon-remove{display: none;}.panel-body:hover .glyphicon-remove{display: inline-block}</style></head><body><div class="container"><div class="row"><div class="col-xs-12 col-lg-8 col-lg-offset-2"><h2>记账本</h2><hr /><div class="accounts"><% accounts.forEach(item => {%><div class="panel <%= item.type === '-1' ? 'panel-danger' : 'panel-success'%>"><div class="panel-heading"><%= item.time %></div><div class="panel-body"><div class="col-xs-6"><%= item.title %></div><div class="col-xs-2 text-center"><span class="label <%= item.type === '-1' ? 'label-warning' : 'label-success'%>"><%= item.type === '-1' ? '支出' : '收入'%></span></div><div class="col-xs-2 text-right"><%= item.account %></div><div class="col-xs-2 text-right"><spanclass="glyphicon glyphicon-remove"aria-hidden="true"></span></div></div></div><% }) %>            </div></div></div></div></body>
</html>

删除账单

前端代码修改:

<!-- list.ejs -->
<a href="/account/<%= item.id %>">
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
</a>

后端代码修改:

// index.js
router.get("/account/:id", (req, res) => {let id = req.params.id;db.get('accounts').remove({id: id}).write();res.render('success', {msg: '删除成功', url: '/account'});
});

MongoDB

MongoDB 是一个基于分布式文件存储的数据库,跟 json 文件非常类似

在这里插入图片描述

  • 一个 json 文件好比是一个数据库,一个 Mongodb 服务下可以有 N 个数据库
  • json 文件中的一级属性的数组值好比是集合
  • 数组中的对象好比是文档
  • 对象中的属性有时也称之为字段

一般情况下:
一个项目使用一个数据库
一个集合会存储同一类型的数据

启动数据库

Mongodb 默认使用 C:/data/db 作为文件存储路径

mongod	# 启动数据库服务端
mongo	# 启动数据库客户端
show dbs

默认有 3 个数据库:

admin
config
local

数据库与集合命令

show dbs	// 显示所有的数据库
use db_name		// 切换到指定的数据库,如果不存在则创建
db		// 显示当前所在的数据库
use db_name
db.dropDatabase()	// 删除当前数据库
db.createCollection('users')
show collections
db.collection_name.drop()	// 删除某个集合
db.collection_name.renameCollection(collection_name2)	// 重命名集合

文档命令

use bilibili
db.users.insert({name: 'hcc', age: 18})
db.users.insert({name: 'hnp', age: 19})
db.users.insert({name: 'ss', age: 20})db.users.find()		// 查询所有文档
db.users.find({age: 20})	// 按条件查询
db.users.update({name: 'ss'}, {$set: {age: 24}})	// 更新
db.users.remove({name: 'ss'})

mongoose

Mongoose 是一个对象文档模型库,方便使用代码操作 mongodb 数据库

npm init
npm i mongoose

连接数据库

const mongoose = require('mongoose');// 如果数据库不存在,则自动创建
mongoose.connect('mongodb://127.0.0.1:27017/bilibili');// 使用 once 绑定的回调函数,只执行一次
mongoose.connection.once('open', () => {console.log('open');
});
mongoose.connection.on('error', () => {console.log('error');
});
mongoose.connection.on('close', () => {console.log('close');
});
setTimeout(() => {mongoose.disconnect();
});

插入文档

mongoose.connection.once('open', () => {let BookSchema = new mongoose.Schema({title: String,author: String,price: Number});let BookModel = mongoose.model('books', BookSchema);BookModel.create({title: 'xiyouji',author: 'wuchengen',price: 19}).then((data) => {console.log(data);mongoose.disconnect();});console.log('open');
});

在这里插入图片描述

mongoose.connection.once('open', () => {let BookSchema = new mongoose.Schema({title: String,author: String,price: Numberis_hot: Boolean,tags: Array,pub_time: Date});let BookModel = mongoose.model('books', BookSchema);BookModel.create({title: 'xiyouji',author: 'wuchengen',price: 19,is_hot: true,tags: ['a', 'b', 'c']pub_time: new Date()}).then((data) => {console.log(data);mongoose.disconnect();})console.log('open');
});

字段值验证(约束):Mongoose 有一些内建验证器,可以对字段值进行验证

    let BookSchema = new mongoose.Schema({title: {type: String,required: true},author: {type: String,default: 'anonymous'},gender: {type: String,enum: ['M', 'F']},username: {type: String,unique: true	// unique 需要重建集合才能有效果},price: Number});

删除文档

// 删除单条
SongModel.deleteOne({_id:'5dd65f32be6401035cb5b1ed'}, function(err){if(err) throw err;console.log('删除成功');mongoose.connection.close();
});// 删除多条
SongModel.deleteMany({author:'Jay'}, function(err){if(err) throw err;console.log('删除成功');mongoose.connection.close();
});

更新文档

SongModel.updateOne({author: 'JJ Lin'}, {author: '林俊杰'}, function (err) {if(err) throw err;mongoose.connection.close();
});SongModel.updateMany({author: 'Leehom Wang'}, {author: '王力宏'}, function (err) {if(err) throw err;mongoose.connection.close();
});

读取文档

查询一条数据:

SongModel.findOne({author: '王力宏'}, function(err, data){if(err) throw err;console.log(data);mongoose.connection.close();
});
//根据 id 查询数据
SongModel.findById('5dd662b5381fc316b44ce167', function(err, data){if(err) throw err;console.log(data);mongoose.connection.close();
});

批量查询数据:

//不加条件查询
SongModel.find(function(err, data){if(err) throw err;console.log(data);mongoose.connection.close();
});
//加条件查询
SongModel.find({author: '王力宏'}, function(err, data){if(err) throw err;console.log(data);mongoose.connection.close();
});

条件控制

在这里插入图片描述

db.students.find({id:{$gt:3}});

逻辑运算:

db.students.find({$or:[{age:18}, {age:24}]});
db.students.find({$and: [{age: {$lt:20}}, {age: {$gt: 15}}]});

正则:

db.students.find({name:/imissyou/});
db.students.find({name: new RegExp('imissyou')});

个性化读取

字段筛选:

//0:不要的字段
//1:要的字段
SongModel.find().select({_id:0,title:1}).exec(function(err,data){if(err) throw err;console.log(data);mongoose.connection.close();
});

数据排序:

//sort 排序
//1:升序
//-1:倒序
SongModel.find().sort({hot:1}).exec(function(err,data){if(err) throw err;console.log(data);mongoose.connection.close();
});
BookModel.find().select({name: 1, price: 1, _id: 0}).sort({price: 1}).exec((err, data) => {if (err) {console.log('error');return;}console.log(data);
});

数据截取:

//skip 跳过 limit 限定
SongModel.find().skip(10).limit(10).exec(function(err,data){if(err) throw err;console.log(data);mongoose.connection.close();
});
BookModel.find()
.select({name: 1, price: 1, _id: 0})
.sort({price: -1})
.skip(3)		// 跨过 3 个
.limit(3)		// 取 3 个
.exec((err, data) => {if (err) {console.log('error');return;}console.log(data);
});

模块化

// db/db.js
const {DBHOST, DBPORT, DBNAME}= require('../config/config');module.exports = function (success, error) {if (typeof error !== 'function') {error = () => {console.log('connection error');}}const mongoose = require('mongoose');mongoose.connect(`mongodb://${DBHOST}:${DBPORT}/${DBNAME}`);mongoose.connection.once('open', () => {success();});mongoose.connection.on('error', () => {error();});mongoose.connection.on('close', () => {console.log('close');});
}
// config/config.js
module.exports = {DBHOST: '127.0.0.1', DBPORT: 27017,DBNAME: 'bilibili'
}
// models/BookModel.js
const mongoose = require('mongoose');let BookSchema = new mongoose.Schema({title: String,author: String,price: Number
});
let BookModel = mongoose.model('books', BookSchema);module.exports = BookModel;
// demo1.js
const mongoose = require('mongoose');
const db = require('./db/db.js');
const BookModel = require('./models/BookModel.js');db(() => {BookModel.create({title: 'xiyouji',author: 'wuchengen',price: 19}).then((data) => {console.log(data);mongoose.disconnect();});console.log('success');
});

数据库可视化工具:robo3t

记账本案例优化

// config/config.js
module.exports = {DBHOST: '127.0.0.1', DBPORT: 27017,DBNAME: 'bilibili'
}
// db/db.js
const {DBHOST, DBPORT, DBNAME}= require('../config/config');module.exports = function (success, error) {if (typeof error !== 'function') {error = () => {console.log('connection error');}}const mongoose = require('mongoose');mongoose.connect(`mongodb://${DBHOST}:${DBPORT}/${DBNAME}`);mongoose.connection.once('open', () => {success();});mongoose.connection.on('error', () => {error();});mongoose.connection.on('close', () => {console.log('close');});
}
// models/AccountModel.js
const mongoose = require('mongoose');let AccountSchema = new mongoose.Schema({title: {type: String,required: true},time: Date,type: {type: Number,default: -1},account: {type: Number,required: true},remark: String
});let AccountModel= mongoose.model('accounts', AccountSchema);
module.exports = AccountModel;
// bin/www
db(() => {// content of www
});

将字符串转为日期对象:

npm i moment
moment('2023-02-24').toDate();
// index.js
var express = require('express');
var router = express.Router();
const low = require('lowdb');
const FileSync = require('lowdb/adapters/FileSync');const adapter = new FileSync(__dirname + '/../data/db.json');
const db = low(adapter);
const shortid = require("shortid");
const moment = require("moment");
const AccountModel = require("../models/AccountModel")/* GET home page. */
router.get('/account', function(req, res, next) {// res.render('index', { title: 'Express' });// let accounts = db.get('accounts').value();AccountModel.find().sort({time: -1}).exec((err, data) => {if (err) {res.status(500).send("读取失败");	// 修改 mongoose 版本号到 6.8.0return;}// data.time = moment(data.time).format("YYYY-MM-DD");res.render('list', {accounts: data, moment: moment});});
});router.get('/account/create', function(req, res, next) {// res.render('index', { title: 'Express' });res.render('create');
});router.post('/account', (req, res) => {console.log(req.body);// let id = shortid.generate();// db.get("accounts").unshift({id: id, ...req.body}).write();AccountModel.create({...req.body,time: moment(req.time).toDate(),}).then((data) => {console.log(data);// res.status(500).send("插入失败!");// mongoose.disconnect();});res.render('success', {msg: "添加成功~~~", url: '/account'});
});router.get("/account/:id", (req, res) => {let id = req.params.id;// db.get('accounts').remove({id: id}).write();AccountModel.deleteOne({_id: id}, (err, data) => {if (err) {res.status(500).send("删除失败");return;}res.render('success', {msg: '删除成功', url: '/account'});});
});module.exports = router;
<!-- list.ejs -->
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title><linkhref="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.css"rel="stylesheet"/><style>label {font-weight: normal;}.panel-body .glyphicon-remove{display: none;}.panel-body:hover .glyphicon-remove{display: inline-block}</style></head><body><div class="container"><div class="row"><div class="col-xs-12 col-lg-8 col-lg-offset-2"><div class="row"><h2 class="col-xs-6">记账本</h2><h2 class="col-xs-6 text-right"><a href="/account/create" class="btn btn-primary">添加账单</a></h2></div><hr /><div class="accounts"><% accounts.forEach(item => {%><div class="panel <%= item.type === -1 ? 'panel-danger' : 'panel-success'%>"><div class="panel-heading"><%= moment(item.time).format("YYYY-MM-DD") %></div><div class="panel-body"><div class="col-xs-6"><%= item.title %></div><div class="col-xs-2 text-center"><span class="label <%= item.type === -1 ? 'label-warning' : 'label-success'%>"><%= item.type === -1 ? '支出' : '收入'%></span></div><div class="col-xs-2 text-right"><%= item.account %></div><div class="col-xs-2 text-right"><a class="delBtn" href="/account/<%= item._id %>"><spanclass="glyphicon glyphicon-remove"aria-hidden="true"></span></a></div></div></div><% }) %>            </div></div></div></div></body>
</html>
<script>let delBtns = document.querySelectorAll(".delBtn");delBtns.forEach(item => {item.addEventListener("click", function(e) {if (confirm("确认删除?")) {return true;} else{e.preventDefault();}});});
</script>

API接口

一个接口就是 服务中的一个路由规则 ,根据请求响应结果。一个接口一般由如下几个部分组成:

  • 请求方法
  • 接口地址(URL)
  • 请求参数
  • 响应结果

接口示例

在这里插入图片描述

RESTful API

在这里插入图片描述

接口化有利于前后端分离

json-server

json-server 本身是一个 JS 编写的工具包,可以快速搭建 RESTful API 服务

npm i -g json-server

准备一个 json 文件:

// db.json
{"song": [{ "id": 1, "name": "干杯", "singer": "五月天" },{ "id": 2, "name": "当", "singer": "动力火车" },{ "id": 3, "name": "不能说的秘密", "singer": "周杰伦" }]
}

启动 json-server:

json-server --watch db.json

访问:

http://localhost:3000/song
http://localhost:3000/song/1

apifox

GET

获取所有歌曲:

在这里插入图片描述

POST

新增一首歌曲:

在这里插入图片描述

DELETE

删除 id 为 4 的歌曲:

在这里插入图片描述

PATCH

修改 id 为 3 的歌曲:

在这里插入图片描述

记账本案例增加接口

// api/account.jsvar express = require('express');
var router = express.Router();
const moment = require("moment");
const AccountModel = require("../../models/AccountModel")// 获取账单列表
router.get('/account', function(req, res, next) {AccountModel.find().sort({time: -1}).exec((err, data) => {if (err) {// res.status(500).send("读取失败");res.json({code: '1001',msg: '读取失败',data: null});return;}// res.render('list', {accounts: data, moment: moment});res.json({code: '0000',msg: '读取成功',data: data});});
});// 新增记录
router.post('/account', (req, res) => {console.log(req.body);// 表单验证...AccountModel.create({...req.body,time: moment(req.time).toDate(),}).then((data) => {console.log(data);res.json({code: '0000',msg: '添加成功',data: data});});// res.render('success', {msg: "添加成功~~~", url: '/account'});
});// 删除账单
router.delete("/account/:id", (req, res) => {let id = req.params.id;AccountModel.deleteOne({_id: id}, (err, data) => {if (err) {// res.status(500).send("删除失败");res.json({code: '1000',msg: '删除失败',data: null});return;}// res.render('success', {msg: '删除成功', url: '/account'});res.json({code: '0000',msg: '删除成功',data: null});});
});// 获取单条账单
router.get('/account/:id', (req, res) => {let id = req.params.id;AccountModel.findById(id, (err, data) => {if (err) {return res.json({code: '1004',msg: '查询失败',data: null});}res.json({code: '0000',msg: '查询成功',data: data});});
});// 更新单个账单
router.patch('/account/:id', (req, res) => {let {id} = req.params;AccountModel.updateOne({_id: id}, req.body, (err, data) => {if (err) {return res.json({code: '1005',msg: '更新失败',data: null});}AccountModel.findById(id, (err, data) => {if (err) {return res.json({code: '1004',msg: '查询失败',data: null});}res.json({code: '0000',msg: '更新成功',data: data});});});
});module.exports = router;
// app.js
...
const accountRouter = require('./routes/api/account');
app.use('/api', accountRouter);
...

在这里插入图片描述

会话控制

所谓会话控制就是 对会话进行控制。HTTP 是一种 无状态 的协议,它没有办法区分多次的请求是否来自于同一个客户端, 无法区分用户,而产品中又大量存在的这样的需求,所以我们需要通过 会话控制 来解决该问题

常见的会话控制技术有三种:

  • cookie
  • session
  • token

cookie

cookie 是 HTTP 服务器发送到用户浏览器并保存在本地的一小块数据

  • cookie 是保存在浏览器端的一小块数据
  • cookie 是按照域名划分保存的

在这里插入图片描述

浏览器向服务器发送请求时,会自动将 当前域名下 可用的 cookie 设置在请求头中,然后传递给服务器

这个请求头的名字也叫 cookie ,所以将 cookie 理解为一个 HTTP 的请求头也是可以的

在这里插入图片描述
在这里插入图片描述

express 设置 cookie

const express = require('express');const app = express();app.get('/set-cookie', (req, res) => {// res.cookie('name', 'zhangsan'); // 会在浏览器关闭后自动销毁res.cookie('name', 'zhangsan', {maxAge: 60 * 1000});res.cookie('theme', 'blue');res.send('home');
});
app.listen(3000);

express 删除 cookie

app.get('/remove-cookie', (req, res) => {res.clearCookie('theme');res.send('删除成功');
});

express 提取 cookie

cookie-parser 中间件:

npm i cookie-parser
const cookieParser = require('cookie-parser');
app.use(cookieParser());
app.get('/get-cookie', (req, res) => {console.log(req.cookies);res.send(`欢迎您 ${req.cookies.name}`);
});app.listen(3000);

session

session 是保存在服务器端的一块儿数据 ,保存当前访问用户的相关信息

作用:实现会话控制,可以识别用户的身份,快速获取当前用户的相关信息

在这里插入图片描述

const express = require('express');
const session = require('express-session');
const mongoStore = require('connect-mongo');const app = express();app.use(session({name: 'sid', //设置cookie的name,默认值是:connect.sidsecret: 'atguigu', //参与加密的字符串(又称签名)saveUninitialized: false, //是否为每次请求都设置一个cookie用来存储session的idresave: true, //是否在每次请求时重新保存sessionstore: mongoStore.create({mongoUrl: 'mongodb://127.0.0.1:27017/bilibili' //数据库的连接配置}),cookie: {httpOnly: true, // 开启后前端无法通过 JS 操作 document.cookie() ×maxAge: 1000 * 60 * 5 // 这一条 是控制 sessionID 的过期时间的!!!},
}));    app.get('/', (req, res) => {res.send('home');
});app.get('/login', (req, res) => {// http://localhost:3000/login?username=admin&password=adminif(req.query.username === 'admin' && req.query.password === 'admin') {req.session.username = 'admin';res.send('登录成功');} else {res.send('登录失败');}
});app.get('/cart', (req, res) => {if(req.session.username) {  // 中间件根据 sid 查询数据库设置 req.session.usernameres.send('购物车');} else {res.send('请登录');}
});app.get('/logout', (req, res) => {req.session.destroy(() => {res.send('退出成功');});
});app.listen(3000);

cookie 与 session 的区别

在这里插入图片描述

记账本案例注册功能

// views/auth/reg.ejs
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>注册</title><link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.css" rel="stylesheet" />
</head><body><div class="container"><div class="row"><div class="col-xs-12 col-md-8 col-md-offset-2 col-lg-4 col-lg-offset-4"><h2>注册</h2><hr /><form method="post" action="/reg"><div class="form-group"><label for="item">用户名</label><input name="username" type="text" class="form-control" id="item" /></div><div class="form-group"><label for="time">密码</label><input name="password" type="password" class="form-control" id="time" /></div><hr><button type="submit" class="btn btn-primary btn-block">注册</button></form></div></div></div>
</body></html>
// UserModel.js
const mongoose = require('mongoose');let UserSchema = new mongoose.Schema({username: String,password: String
});let UserModel= mongoose.model('users', UserSchema);
module.exports = UserModel;
// routes/web/auth.js
var express = require('express');
var router = express.Router();
const UserModel= require("../../models/UserModel");
const md5 = require('md5');
//注册
router.get('/reg', (req, res) => {//响应 HTML 内容res.render('auth/reg');
});//注册用户
router.post('/reg', (req, res) => {//做表单验证//获取请求体的数据UserModel.create({...req.body, password: md5(req.body.password)}, (err, data) => {if(err){res.status(500).send('注册失败, 请稍后再试~~');return;}res.render('success', {msg: '注册成功', url: '/login'});});});//登录页面
router.get('/login', (req, res) => {//响应 ejs 内容res.render('auth/login');
});//登录操作
router.post('/login', (req, res) => {//获取用户名和密码let {username, password} = req.body;//查询数据库UserModel.findOne({username: username, password: md5(password)}, (err, data) => {//判断if(err){res.status(500).send('登录, 请稍后再试~~');return;}//判断 dataif(!data){return res.send('账号或密码错误~~');}//写入sessionreq.session.username = data.username;req.session._id = data._id;//登录成功响应res.render('success', {msg: '登录成功', url: '/account'});});
});//退出登录
router.post('/logout', (req, res) => {//销毁 sessionreq.session.destroy(() => {res.render('success', {msg: '退出成功', url: '/login'});})
});module.exports = router;
// views/auth/login.ejs
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>登录</title><link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.css" rel="stylesheet" />
</head><body><div class="container"><div class="row"><div class="col-xs-12 col-md-8 col-md-offset-2 col-lg-4 col-lg-offset-4"><h2>登录</h2><hr /><form method="post" action="/login"><div class="form-group"><label for="item">用户名</label><input name="username" type="text" class="form-control" id="item" /></div><div class="form-group"><label for="time">密码</label><input name="password" type="password" class="form-control" id="time" /></div><hr><button type="submit" class="btn btn-primary btn-block">登录</button></form></div></div></div>
</body>
</html>
// app.js
const session = require('express-session');
const MongoStore = require('connect-mongo');
const {DBHOST, DBNAME, DBPORT} = require('./config/config');
const authRouter = require('./routes/web/auth');//设置 session 的中间件
app.use(session({name: 'sid',   //设置cookie的name,默认值是:connect.sidsecret: 'atguigu', //参与加密的字符串(又称签名)  加盐saveUninitialized: false, //是否为每次请求都设置一个cookie用来存储session的idresave: true,  //是否在每次请求时重新保存session  20 分钟    4:00  4:20store: MongoStore.create({mongoUrl: `mongodb://${DBHOST}:${DBPORT}/${DBNAME}` //数据库的连接配置}),cookie: {httpOnly: true, // 开启后前端无法通过 JS 操作maxAge: 1000 * 60 * 60 * 24 * 7 // 这一条 是控制 sessionID 的过期时间的!!!},
}));

用户登录检测

// index.js
var express = require('express');
var router = express.Router();
const moment = require("moment");
const AccountModel = require("../../models/AccountModel");
const checkLoginMiddleware = require('../../middlewares/checkLoginMiddleware');/* GET home page. */
router.get('/account', checkLoginMiddleware, function(req, res, next) {AccountModel.find().sort({time: -1}).exec((err, data) => {if (err) {res.status(500).send("读取失败");return;}res.render('list', {accounts: data, moment: moment});});
});router.get('/account/create', checkLoginMiddleware, function(req, res, next) {res.render('create');
});router.post('/account', checkLoginMiddleware, (req, res) => {console.log(req.body);AccountModel.create({...req.body,time: moment(req.time).toDate(),}).then((data) => {console.log(data);});res.render('success', {msg: "添加成功~~~", url: '/account'});
});router.get("/account/:id", checkLoginMiddleware, (req, res) => {let id = req.params.id;AccountModel.deleteOne({_id: id}, (err, data) => {if (err) {res.status(500).send("删除失败");return;}res.render('success', {msg: '删除成功', url: '/account'});});
});module.exports = router;
// middlewares/checkLoginMiddleware.js
function checkLoginMiddleware(req, res, next) {if (!req.session.username) {return res.redirect('/login');}next();
}
module.exports = checkLoginMiddleware;

退出登录:

// list.ejs
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title><linkhref="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.css"rel="stylesheet"/><style>label {font-weight: normal;}.panel-body .glyphicon-remove{display: none;}.panel-body:hover .glyphicon-remove{display: inline-block}</style></head><body><div class="container"><div class="row"><div class="col-xs-12 col-lg-8 col-lg-offset-2"><div class="row text-right"><div class="col-xs-12" style="padding-top: 20px;"><form method="post" action="/logout"><button href="/logout" class="btn btn-danger">退出</a>	<!--新增退出按钮--></form></div></div><hr><div class="row"><h2 class="col-xs-6">记账本</h2><h2 class="col-xs-6 text-right"><a href="/account/create" class="btn btn-primary">添加账单</a></h2></div><hr /><div class="accounts"><% accounts.forEach(item => {%><div class="panel <%= item.type === -1 ? 'panel-danger' : 'panel-success'%>"><div class="panel-heading"><%= moment(item.time).format("YYYY-MM-DD") %></div><div class="panel-body"><div class="col-xs-6"><%= item.title %></div><div class="col-xs-2 text-center"><span class="label <%= item.type === -1 ? 'label-warning' : 'label-success'%>"><%= item.type === -1 ? '支出' : '收入'%></span></div><div class="col-xs-2 text-right"><%= item.account %></div><div class="col-xs-2 text-right"><a class="delBtn" href="/account/<%= item._id %>"><spanclass="glyphicon glyphicon-remove"aria-hidden="true"></span></a></div></div></div><% }) %>            </div></div></div></div></body>
</html>
<script>let delBtns = document.querySelectorAll(".delBtn");delBtns.forEach(item => {item.addEventListener("click", function(e) {if (confirm("确认删除?")) {return true;} else{e.preventDefault();}});});
</script>

CSRF = Cross-Site Request Forgery,跨站请求伪造

在同一浏览器下,访问一个攻击页面,攻击页面就可以向被攻击页面的服务器发送请求:

<link rel="stylesheet" href="http://127.0.0.1:3000/logout">

首页和 404

// views/404.ejs
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>404</title>
</head>
<body><script src="//volunteer.cdn-go.cn/404/latest/404.js"></script>
</body>
</html>
// routes/web/index.js
/* GET home page. */
router.get('/', (req, res) => {res.redirect('/account');
});
// app.js
// catch 404 and forward to error handler
app.use(function(req, res, next) {// next(createError(404));res.render('404');
});

token

token 是什么:token 是服务端生成并返回给 HTTP 客户端的一串加密字符串, token 中保存着用户信息

token 的作用:实现会话控制,可以识别用户的身份,主要用于移动端 APP

在这里插入图片描述
token 需要手动添加到请求报文中

在这里插入图片描述

JWT

JWT(JSON Web Token )是目前最流行的跨域认证解决方案,可用于基于 token 的身份验证。JWT 使 token 的生成与校验更规范

npm i jsonwebtoken
const jwt = require('jsonwebtoken');//创建 token
// jwt.sign(数据, 加密字符串, 配置对象)
let token = jwt.sign({username: 'zhangsan'
}, 'atguigu', {expiresIn: 60 //单位是 秒
});console.log(token);//解析 token
jwt.verify(token, 'atguigu', (err, data) => {if (err) {console.log('校验失败~~');return;}console.log(data);
});

jwt

一个 token 实例:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJ1c2VybmFtZSI6InpoYW5nc2FuIiwiaWF0IjoxNjk5MTg4NDU0LCJleHAiOjE2OTkxODg1MTR9.
Y1cQlEPAFIcfl5w4rUj9Xh_WOXYpG1c6QZmpjtkhFbM

分为 3 部分:

  • Header(头部)
  • Payload(负载)
  • Signature(签名)

记账本案例添加token

// api/auth.js
var express = require('express');
var router = express.Router();
const UserModel= require("../../models/UserModel");
const md5 = require('md5');
const jwt = require('jsonwebtoken');//登录操作
router.post('/login', (req, res) => {//获取用户名和密码let {username, password} = req.body;//查询数据库UserModel.findOne({username: username, password: md5(password)}, (err, data) => {//判断if(err){res.json({code: '2001',msg: '数据库读取失败',data: null});return;}//判断 dataif(!data){return res.json({code: '2002',msg: '账号或密码错误~~',data: null});}let token = jwt.sign({username: data.username, _id: data._id}, 'atguigu', {expiresIn: 60 * 60 *24});res.json({code: '0000',msg: '登录成功',data: token});});
});//退出登录
router.post('/logout', (req, res) => {//销毁 sessionreq.session.destroy(() => {res.render('success', {msg: '退出成功', url: '/login'});})
});module.exports = router;
// app.js
const authApiRouter = require('./routes/api/auth');
app.use('/api', authApiRouter);

用户登录,下放 token
在这里插入图片描述

token校验

// middleWares/checkTokenMiddleware.js
const jwt = require('jsonwebtoken');function checkTokenMiddleware(req, res, next) {let token = req.get('token');if(!token) {return res.json({code: '2003',msg: 'token 缺失',data: null});}jwt.verify(token, 'atguigu', (err, data) => {if (err) {return res.json({code: '2004',msg: '身份校验失败',data: null});}req.user = data;next();});
}
module.exports = checkTokenMiddleware;

将 token 放在头中发送请求:

// api/account.js
const express = require('express');
const router = express.Router();
const moment = require("moment");
const AccountModel = require("../../models/AccountModel")
const jwt = require('jsonwebtoken');
const checkTokenMiddleware = require('../../middlewares/checkTokenMiddleware');// 获取账单列表
router.get('/account', checkTokenMiddleware, function(req, res, next) {// console.log(req.user);AccountModel.find({username: req.user.username}).sort({time: -1}).exec((err, data) => {if (err) {// res.status(500).send("读取失败");res.json({code: '1001',msg: '读取失败',data: null});return;}// res.render('list', {accounts: data, moment: moment});res.json({code: '0000',msg: '读取成功',data: data});});
});// 新增记录
router.post('/account', checkTokenMiddleware, (req, res) => {console.log(req.body);// 表单验证...AccountModel.create({...req.body,time: moment(req.time).toDate(),}).then((data) => {console.log(data);res.json({code: '0000',msg: '添加成功',data: data});});// res.render('success', {msg: "添加成功~~~", url: '/account'});
});// 删除账单
router.delete("/account/:id", checkTokenMiddleware, (req, res) => {let id = req.params.id;AccountModel.deleteOne({_id: id}, (err, data) => {if (err) {// res.status(500).send("删除失败");res.json({code: '1000',msg: '删除失败',data: null});return;}// res.render('success', {msg: '删除成功', url: '/account'});res.json({code: '0000',msg: '删除成功',data: null});});
});// 获取单条账单
router.get('/account/:id', checkTokenMiddleware, (req, res) => {let id = req.params.id;AccountModel.findById(id, (err, data) => {if (err) {return res.json({code: '1004',msg: '查询失败',data: null});}res.json({code: '0000',msg: '查询成功',data: data});});
});// 更新单个账单
router.patch('/account/:id', checkTokenMiddleware, (req, res) => {let {id} = req.params;AccountModel.updateOne({_id: id}, req.body, (err, data) => {if (err) {return res.json({code: '1005',msg: '更新失败',data: null});}AccountModel.findById(id, (err, data) => {if (err) {return res.json({code: '1004',msg: '查询失败',data: null});}res.json({code: '0000',msg: '更新成功',data: data});});});
});module.exports = router;

前后端介绍

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/186845.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

自动化测试中的失败截图和存log

如果我们在执行自动化测试的时候&#xff0c;希望能在失败的时候保存现场&#xff0c;方便事后分析。 对于UI自动化&#xff0c;我们希望截图在测试报告中。 对于api自动化&#xff0c;我们希望截取出错的log在测试报告中。 我开始自己蛮干&#xff0c;写了两个出错截图的方法。…

试题:最大的矩形(给定直方图里面积最大的矩形)

问题描述 在横轴上放了n个相邻的矩形&#xff0c;每个矩形的宽度是1&#xff0c;而第i&#xff08;1 ≤ i ≤ n&#xff09;个矩形的高度是hi。这n个矩形构成了一个直方图。例如&#xff0c;下图中六个矩形的高度就分别是3, 1, 6, 5, 2, 3。 请找出能放在给定直方图里面积最大的…

ARMday04(开发版简介、LED点灯)

开发版简介 开发板为stm32MP157AAA,附加一个拓展版 硬件相关基础知识 PCB PCB&#xff08; Printed Circuit Board&#xff09;&#xff0c;中文名称为印制电路板&#xff0c;又称印刷线路板&#xff0c;是重要的电子部件&#xff0c;是电子元器件的支撑体&#xff0c;是电子…

什么是自动化测试框架?我们该如何搭建自动化测试框架?

无论是在自动化测试实践&#xff0c;还是日常交流中&#xff0c;经常听到一个词&#xff1a;框架。之前学习自动化测试的过程中&#xff0c;一直对“框架”这个词知其然不知其所以然。 最近看了很多自动化相关的资料&#xff0c;加上自己的一些实践&#xff0c;算是对“框架”…

uniapp使用vur-cli新建项目并打包

新建项目 npm install -g vue/cli vue create -p dcloudio/uni-preset-vue my-project选择默认模板npm run dev:h5 运行 安装sass和uview &#xff08;npm安装失败&#xff09; bug&#xff1a;使用uni.scss中的变量或样式&#xff0c;<style lang"scss"> 必…

『 Linux 』进程概念

文章目录 &#x1f5de;️ 冯诺依曼体系结构 &#x1f5de;️&#x1f4c3; 为什么在计算机当中需要使用内存充当中间介质而不使CUP与外设直接进行交互?&#x1f4c3; CPU如何读取数据 &#x1f5de;️ 操作系统(Operating system) &#x1f5de;️&#x1f4c3; 操作系统如何…

无人机航拍技术基础入门,无人机拍摄的方法与技巧

一、教程描述 买了无人机&#xff0c;可是我不敢飞怎么办&#xff1f;禁飞区越来越多&#xff0c;到底哪儿才能飞&#xff1f;我的无人机跟你一样&#xff0c;为什么我拍不出大片&#xff1f;厂家的说明书看不进去&#xff0c;有没有一套无人机的课程&#xff0c;可以快速上手…

C++初阶(十)模板初阶

&#x1f4d8;北尘_&#xff1a;个人主页 &#x1f30e;个人专栏:《Linux操作系统》《经典算法试题 》《C》 《数据结构与算法》 ☀️走在路上&#xff0c;不忘来时的初心 文章目录 一、泛型编程1、如何实现一个通用的交换函数呢&#xff1f;2、引出模板 二、函数模板1、函数模…

2023 全栈工程师 Node.Js 服务器端 web 框架 Express.js 详细教程(更新中)

Express 框架概述 Express 是一个基于 Node.js 平台的快速、开放、极简的Web开发框架。它本身仅仅提供了 web 开发的基础功能&#xff0c;但是通过中间件的方式集成了外部插件来处理HTTP请求&#xff0c;例如 body-parser 用于解析 HTTP 请求体&#xff0c;compression 用于压…

软件测试/测试开发丨接口测试学习笔记,TcpDump与WireShark

点此获取更多相关资料 本文为霍格沃兹测试开发学社学员学习笔记分享 原文链接&#xff1a;https://ceshiren.com/t/topic/27859 协议分析工具 网络监听&#xff1a;TcpDump WireShark 代理 Proxy 推荐工具&#xff1a;手工测试charles [全平台]、安全测试burpsuite [全平台 j…

网络安全自学手册

想自学网络安全&#xff08;黑客技术&#xff09;首先你得了解什么是网络安全&#xff01;什么是黑客&#xff01; 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队”、“安全运营”、“安全…

QT QSplitter

分裂器QSplitter类提供了一个分裂器部件。和QBoxLayout类似&#xff0c;可以完成布局管理器的功能,但是包含在它里面的部件,默认是可以随着分裂器的大小变化而变化的。 比如一个按钮放在布局管理器中,它的垂直方向默认是不会被拉伸的,但是放到分裂器中就可以被拉伸。还有一点不…

如何在Visual Studio上创建项目并运行【超级详细】

工欲善其事&#xff0c;必先利其器。想要学好编程&#xff0c;首先要把手中的工具利用好&#xff0c;今天小编教一下大家如何在史上最强大的编译器--Visual Studio上创建项目。&#x1f357; 一.打开编译器&#x1f357; 双击你电脑上的vs&#xff0c;(2012,2019,2022)都行。&…

视频批量剪辑:AI智剪入门,轻松掌握智能剪辑技巧

在数字媒体时代&#xff0c;视频剪辑已经成为一项必备的技能。无论是为了工作需要&#xff0c;还是为了在社交媒体上分享生活&#xff0c;掌握视频剪辑技巧都能为我们的生活和工作带来很多便利。然而&#xff0c;对于初学者来说&#xff0c;视频剪辑可能是一项艰巨的任务。现在…

03【远程协作开发、TortoiseGit、IDEA绑定Git插件的使用】

上一篇&#xff1a;02【Git分支的使用、Git回退、还原】 下一篇&#xff1a;【已完结】 目录&#xff1a;【Git系列教程-目录大纲】 文章目录 一、远程协作开发1.1 远程仓库简介1.1.1 Github1.1.2 Gitee1.1.3 其他托管平台 1.2 发布远程仓库1.2.1 创建项目1&#xff09; 新…

2023/11/10 JAVA学习

取文件夹本身大小 打开文件 文件改名案例 输出流,文件依照你起的文件名自动创建 哪个流后创建,哪个流先关闭 虚拟机退出跑不了 finally别返回值

金蝶云星空表单插件实现父窗体打开子窗体,并携带参数到子窗体

文章目录 金蝶云星空表单插件实现父窗体打开子窗体&#xff0c;并携带参数到子窗体父窗体打开子窗体准备设置携带参数打开子窗体子窗体接收参数 金蝶云星空表单插件实现父窗体打开子窗体&#xff0c;并携带参数到子窗体 父窗体打开子窗体准备 BillShowParameter OtherInAdd n…

外贸企业GMS认证|SD-WAN专线解决方案支持 IPv6、IPv4

IP地址是英文internet protocol的缩写&#xff0c;是网络之间互连的协议。互联网诞生后&#xff0c;很长一段时间都是使用v4版本的IP协议&#xff0c;也就是 IPv4 &#xff0c;目前全球使用互联网的人数达到了48.8亿&#xff0c;而IPv4的地址库总共约43亿个地址&#xff0c;每个…

blender动画制作软件拓扑全流程

拓扑在三维动画制作中至关重要&#xff0c;原因如下&#xff1a; 1. 动画变形&#xff1a; 自然形变&#xff1a; 良好的拓扑结构能够支持角色或物体在动画中的自然形变&#xff0c;例如关节弯曲、肌肉收缩等。流畅运动&#xff1a; 适当的拓扑有助于保持模型表面的平滑性&…