node.js 课程
第二章 node.js 介绍
2.1 下载和安装
nvm(Node.js version manager) 在github下载
几个指令
+ nvm list
+ nvm install v10.1.0
+ nvm use --delete-prefix 切换版本
2.2 nodejs 和 JavaScript 区别
ECMAScript 是语法基础 定义了语法规范
JavaScript 是 ECMAScript + web API
nodejs 是 ECMAScript + nodeAPI
2.3 commonjs 演示
实现引入导出
module.exports = function add(){}
const add = require("path")
1. 建立两个文件通过b文件引入a文件中的函数 实现 求和
2. 导出多个函数 分别求和 和 求乘(利用解构的语法)
3. 引入npm 包
安装lodash
1. npm init -y //npm 初始化
2. npm i lodash
3. 引入 lodash
4. 合并数组 concat([1,2],3)
2.4 debugger
const http=require('http')
const server = http.createServer((req,res)=>{
res.writeHead(200,{"contetnt-type":"text/html"})
res.end("<h1>Hello World</h1>")
})
server.listen(3000,()=>{
console.log("listening on 3000 prot")
})
第三章 项目介绍
3.1 项目需求分析
一、目标
开发一个博客系统,具有博客的基本功能
只开发server端,不关心前端
二、需求
- 首页,作者主页,博客详情页
- 登录页
- 管理中心,新建页,编辑页
3.2 技术方案
1、数据如何存储
2、如何与前端对接,接口设计
第四章 开发博客项目接口
4.1 http 概述
-
从输入url到显示页面的整个过程
- DNS解析,建立TCP链接,发送http请求
- server接收到请求 ,处理, 返回
- 客户端接收到返回数据,处理数据(如执行页面渲染,执行js)
几个单词
request 请求
response 相应
4.2 nodejs 处理get请求
const http = require("http");
const querystring = require("querystring");
const server = http.createServer((req, res) => {
console.log("method", req.method);
const url = req.url;
console.log("url", req.url);
req.query = querystring.parse(url.split("?")[1]);
console.log("req.query", req.query);
res.end(JSON.stringify(req.query));
});
server.listen(8000, () => {
console.log("OK");
});
4.3 nodejs处理post请求
- 安装postman 发送数据请求
const http = require("http");
const server = http.createServer((req, res) => {
console.log("获取到请求");
if (req.method === "POST") {
console.log("log content-type:", req.headers["content-type"]);
let dataString = "";
req.on("data", (chunk) => {
dataString += chunk.toString();
});
req.on("end", () => {
console.log("dataString:", dataString);
res.end("hello world");
});
}
});
server.listen(3000, () => {
console.log("ok");
});
4.4 处理http 请求综合实例
const http = require("http");
const querystring = require("querystring");
const server = http.createServer((req, res) => {
const method = req.method;
const url = req.url;
const path = url.split("?")[0];
const query = querystring.parse(url.split("?")[1]);
res.setHeader("Content-type", "application/json");
const resData = {
method,
url,
path,
query,
};
if (method === "GET") {
res.end(JSON.stringify(resData));
}
if (method === "POST") {
let postData = "";
req.on("data", (chunk) => {
postData += chunk.toString();
});
req.on("end", () => {
resData.postData = JSON.parse(postData);
res.end(JSON.stringify(resData));
});
}
});
server.listen(3000, () => {
console.log("ok");
});
4.5 搭建开发环境
-
新建项目路径 并搭建基础代码
-
项目目录
|-- blog-1 |-- bin |-- www.js // 项目入口 服务器创建 |-- res |-- router // 处理路由 |-- blog.js |-- user.js |-- model // 定义数据格式 |-- resModel.js |-- controller // 业务逻辑 |-- blog.js |-- app.js // 服务器配置等 |-- package.json
-
const http = require("http"); const PORT = 3000; const serverHandle = require("../app"); const server = http.createServer(serverHandle); server.listen(PORT, () => { console.log("node server ok"); });
-
app.js
const serverHandle = (req, res) => { res.setHeader("Content-type", "application/json"); const resData = { name: "黑兔", age: "23", }; res.end(JSON.stringify(resData)); }; module.exports = serverHandle;
-
使用nodemon 监听文件变化,自动重启node
-
使用 cross-env 设置环境变量,兼容 mac linux 和 windows
1. 安装依赖 npm install nodemon cross-env --save-dev 2. 配置启动命令 "dev": "cross-env NODE_ENV=dev nodemon ./bin/www.js" 3. 使用配置的node_env process.env.NODE_ENV
-
单词
- Handle
4.6 初始化路由
-
blog.js
const handleBlogRouter = (req, res) => { const method = req.method; const url = req.url; const path = url.split("?")[0]; console.log("获取到的路径:", path); // 获取博客列表 if (method == "GET" && path == "/api/blog/list") { return { msg: "调用博客列表", }; } // 获取博客详情 if (method == "GET" && path == "/api/blog/detailed") { return { msg: "获取博客详情", }; } // 创建博客 if (method == "POST" && path == "/api/blog/create") { return { msg: "创建博客", }; } // 删除博客 if (method == "POST" && path == "/api/blog/delete") { return { msg: "删除博客", }; } // 修改博客 if (method == "POST" && path == "/api/blog/update") { return { msg: "修改博客", }; } }; module.exports = handleBlogRouter;
-
user.js
const handleUserRouer = (req, res) => { const method = req.method; const url = req.url; const path = url.split("?")[0]; // 用户登录 if (method == "POST" && path == "/api/user/login") { return { msg: "调用用户登录aip", }; } }; module.exports = handleUserRouer;
-
app.js
const handleBlogRouter = require("./src/router/blog"); const handleUserRouer = require("./src/router/user"); const serverHandle = (req, res) => { res.setHeader("Content-type", "application/json"); const blogData = handleBlogRouter(req, res); if (blogData) { res.end(JSON.stringify(blogData)); return; } const userData = handleUserRouer(req, res); if (userData) { res.end(JSON.stringify(userData)); return; } res.writeHeader(404, { "Content-type": "text/plan" }); res.write("404 Not"); res.end(); }; module.exports = serverHandle;
4.7 开发路由(博客列表路由)
-
定义数据格式
class BaseModel { constructor(data, message) { if (typeof data == "string") { this.message = data; data = null; message = null; } if (data) { this.data = data; } if (message) { this.data = data; } } } class SuccessModel extends BaseModel { constructor(data, message) { super(data, message); this.errno = 0; } } class ErrorModel extends BaseModel { constructor(data, message) { super(data, message); this.erron = -1; } } module.exports = { SuccessModel, ErrorModel, };
-
创建controller(控制器)文件夹
const getList = (author, keyword) => { return [ { id: 1, title: "标题A", contentText: "内容eAAAnei", createTime: "1635941708713", author: "张三", }, { id: 1, title: "标题A", contentText: "内容eAAAnei", createTime: "1635941708713", author: "张三", }, ]; }; module.exports = { getList };
-
返回数据
~~~javascript if (method == "GET" && req.path == "/api/blog/list") { const author = req.query.author || ""; const keyword = req.query.keyword || ""; const listData = getList(author, keyword); return new SuccessModel(listData); } ~~~
4.8博客详情页开发
-
Promise 简单讲解callback 方式
const fs = require("fs"); const path = require("path"); // callback 方式获取一个文件内容 function getFileContent(fileName, callback) { const fullFileName = path.resolve(__dirname, "files", fileName); fs.readFile(fullFileName, (err, data) => { if (err) { console.log(err); return; } callback(JSON.parse(data.toString())); }); } getFileContent("a.json", (adata) => { console.log("a data:", adata); getFileContent(adata.next, (bdata) => { console.log("b data:", bdata); getFileContent(bdata.next, (cdata) => { console.log("c data:", cdata); }); }); });
promise 方式
~~~javascript
function getFileContent(fileName) {
const promise = new Promise((resolve, reject) => {
const filePath = path.resolve(__dirname, "files", fileName);
fs.readFile(filePath, (err, data) => {
if (err) {
reject(err);
return;
}
return resolve(JSON.parse(data.toString()));
});
});
return promise;
}
getFileContent("a.json")
.then((data) => {
console.log("a data:", data);
return getFileContent(data.next);
})
.then((bdata) => {
console.log("b data:", bdata);
return getFileContent(bdata.next);
})
.then((cdata) => {
console.log("cdata", cdata);
});
~~~
4.9 处理post 请求
-
通过promise 解析post 数据
const getPostData = (req) => { const promise = new Promise((resolve, reject) => { if (method !== "POST") { resolve({}); return; } if (req.headers["Content-type"] !== "application/json") { resolve({}); return; } let postData = ""; req.on("data", (chunk) => { postData += chunk.toString(); }); req.on("end", () => { if (postData) { resolve({}); return; } else { resolve(JSON.parse(postData)); } }); }); return promise; };
使用 处理数据
getPostData(req).then((postData) => {
req.body = postData;
// 创建博客
if (method == "POST" && req.path == "/api/blog/create") {
return {
msg: "创建博客",
};
}
// 删除博客
if (method == "POST" && req.path == "/api/blog/delete") {
return {
msg: "删除博客",
};
}
// 修改博客
if (method == "POST" && req.path == "/api/blog/update") {
return {
msg: "修改博客",
};
}
});
4.12 补充路由和Api
路由 和 api
API: 前段和后端,不同端(子系统)直接对接的一个术语
路由: API 的一部分 后端系统内部的一个定义
第五章 数据存储
5.1 MySql 介绍
1. 下载mysql
1. 下载workBench
1. 查看全部的表 show databases
5.2 数据库操作
- 数据库缩写
- PK primary key 主键
- NN not null 是否为空
- UQ unique 外键
- BIN binary 二进制
- UN unsigned 无符号
- ZF zero fill 补零
- AI auto increment 自动增加
简单的指令
1. show databases // 查看数据库
2. use // 数据名称
3. show tables // 显示数据库
4. SET SQL_SAFF_UPDATAS = 0 // 关闭数据库安全检查
5. <> // 设置不等于零
-
指令:
- 增
- insert into 表名 (参数一,参数二,.....) values(参数一,参数二,..)
- 删
- delete from users where username="lisi"
- 改
- update users set realname = "测试"
- update users set realname = "李四" where username="lisi"
- 查
- select * from 表名; //查询全部
- select 列名,列名 from 表名 // 查询某几列
- select * from 表名 where 列名=值 // 具有筛选条件的
- select * from 表名 where 列名=值 and 列名=值 // 多重与查询
- select * from 表名 where 列名=值 or 列名=值 // 多重或查询
- select * from 表名 where 列名 like %值% // 模糊查询
- select * from 表名 order by 列名 // 排序 desc
- 增
5.4 nodejs 操作数据库
1. 新建一个项目 (初始化)
2. 安装mysql 依赖
- npm install mysql
3. 引入mysql
1. const con = require('mysql')
4. 创建链接对象
+ con = mysql.createConnection({})
+ host:"localhost" //域地址
+ user:"root"
+ password:"root"
+ port:"3306"
+ database:"myblog"
5. 开始链接数据库
1. con.connect()
6. 执行sql语句
1. const sql = `'select * from users'`
2. con.query(sql,(err,result)=>{})
7. 关闭链接
1. con.end()
5.5 nodejs 链接mysql 做成工具
- 获取环境变量
- 链接 const env = process.env.NODE_ENV
新建两个文件
-
config
const env = process.env.NODE_ENV; // 获取环境参数 let MYSQL_CONF; if (env === "dev") { console.log("链接dev:"); MYSQL_CONF = { host: "localhost", user: "root", password: "root", port: "3306", database: "myblog", }; } if (env === "production") { console.log("链接production:"); MYSQL_CONF = { host: "localhost", user: "root", password: "root", port: "3306", database: "myblog", }; } module.exports = { MYSQL_CONF, };
mysql.js
~~~ javascript
const mysql = require("mysql");
const { MYSQL_CONF } = require("../conf/db.js");
// 创建链接对象
const con = mysql.createConnection(MYSQL_CONF);
// 开始链接
con.connect();
// 执行sql的函数
function exec(sql) {
const promise = new Promise((resolve, reject) => {
con.query(sql, (err, result) => {
console.log("数据库获取回来的数据:", sql, err, result);
if (err) {
reject(err);
return;
}
resolve(result);
});
});
return promise;
}
module.exports = {
exec,
escape: mysql.escape,
};
~~~
第六章 登录
6.1 cook介绍
-
什么是cookie
- 存储在浏览器中的一段字符串(最大5kb)
- 跨越不共享
- 格式如 k1=v1;k2=v2; k3=v3
- 没次发送http请求,会将请求的cookie 一起发送给server
- server 可以修改cookie 并返回给浏览器
- 浏览器中也可以通过JavaScript修改cookie(有限制)
-
JavaScript 操作 cookie 浏览器中查看cookie
- 查看cookie 的三种方式
- 直接在请求中查看
- 在application 中查看
- document.cookie
- 操作cookie的方式
- document.cookie = "k1=100"
- 只能以累加的方式修改
-
server 端操作 cookie, 实现登录验证
-
查看cookie
- const cookieStr = req.headers.cookie
-
修改cookie
- res.serHeader("Set-Cookie",
username = zhangshan; path=/; httpOnly
) - 注意设置 path
- 设置httpOnly 实现禁止前端修改
- 设置expires过期时间
- expires = ${getCookieExpires()}
- res.serHeader("Set-Cookie",
-
设置cookie过期时间
const getCookieExpires = ()=>{ const d = new Date() d.setTime(d.getTime() + (24 * 60 * 60 * 1000)) return d.toGMTString() }
-
6.2 session介绍学习
- 设置session的作用
cookie不够安全 seesion 用于存储用户数据 前端存储虚拟的报文
加密数据 解决cooki不安全的问题
设置 session data 存储 数据
session 的问题
1. 进程内存有限 访问量过大 内存暴增
2. 正式上线运行是多进程,进程之间内存无法共享
6.3 redis(内存数据库) 学习
web server 最常用的缓存数据库,数据放在内存中
优点读写特别快 缺点 内存很贵 存储空间小
- session 访问频繁 对性能要求极高
- session 可不考虑断电数据丢失的问题(内存的硬伤)也可以断电不丢失 要配置
- session 数据量不会太大(相比于MySQL中的数据)
安装redis
windows https://www.runoob.com/redis/redis-install.html 安装教程
redis 命令
启动命令
- redis-server.exe redis.windows.conf
- redis-cli.exe -h 127.0.0.1 -p 6379
- set myKey abc
- get myKey
- keys *
6.4 nodejs 链接 redis 的demo
- 安装 redis
npm i redis@3.1.2 --save
const redis = require("redis"); // 创建redis客户端 const redisClient = redis.createClient(6379, "127.0.0.1"); // 监听错误 redisClient.on("err", (err) => { console.error(err); }); // 测试 redisClient.set("myname", "zhangshan1", redis.print); redisClient.get("myname", (err, val) => { if (err) { console.log("err:", err); return; } console.log("val:", val); redisClient.quit(); });
6.5 redis封装成组件
const redis = require("redis");
const { REDIS_CONF } = require("../conf/db");
// 創建客戶端
const redisClient = redis.createClient(REDIS_CONF.port, REDIS_CONF.host);
redisClient.on("error", (err) => {
console.log("error:", err);
});
// set value
function set(key, val) {
if (typeof val === "object") {
val = JSON.stringify(val);
}
redisClient.set(key, val, redis.print);
}
// get value
function get(key) {
const promise = new Promise((resolve, reject) => {
redisClient.get(key, (err, val) => {
if (err) {
reject(err);
return;
}
if (val == null) {
resolve(null);
return;
}
// 兼容json 操作
try {
resolve(JSON.parse(val));
} catch {
resolve(val);
}
});
});
return promise;
}
module.exports = {
set,
get,
};
6.6 session+cookie 联调
- 判断用户是否通过cookie传入userid 如果没有传入则随机生成 并设置到用户浏览器和 redis中
- 如果传入cookie 中有userid 则去redis中查询userid 如何返回非空对象则登录 如果返回空对象 则未登录
- 添加登录验证 在调用接口前判断是否登录成功(有 username)
6.7 前端联调配置nginx
问题:
1. 登录功能依赖cookie 必须用浏览器联调
2. cookie,跨越不共享,前端和server端必须同域
3. 需要用到nignx做代理,让前后端端同域
6.8 nginx 介绍
- 高性能的web服务器,开源免费
- 一般用于做静态服务,负载均衡
- 反向代理
[图片上传失败...(image-6e6580-1642776253504)]
-
Nginx下载地址 https://nginx.org/en/download.html
-
nginx 的几个命令
- 查看配置是否错误 nginx -t
- 启动nginx nginx
- 重启nginx nginx -s reload
- 停止nginx -s stop
#user nobody; # 启动几何; worker_processes 1; #error_log logs/error.log; #error_log logs/error.log notice; #error_log logs/error.log info; #pid logs/nginx.pid; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' # '$status $body_bytes_sent "$http_referer" ' # '"$http_user_agent" "$http_x_forwarded_for"'; #access_log logs/access.log main; sendfile on; #tcp_nopush on; #keepalive_timeout 0; keepalive_timeout 65; #gzip on; server { listen 8080; server_name localhost; #charset koi8-r; #access_log logs/host.access.log main; # location / { # root html; # index index.html index.htm; # } location / { proxy_pass http://localhost:8001; } location /api/ { proxy_pass http://localhost:8000; proxy_set_header Host $host; } #error_page 404 /404.html; # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } # proxy the PHP scripts to Apache listening on 127.0.0.1:80 # #location ~ \.php$ { # proxy_pass http://127.0.0.1; #} # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 # #location ~ \.php$ { # root html; # fastcgi_pass 127.0.0.1:9000; # fastcgi_index index.php; # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; # include fastcgi_params; #} # deny access to .htaccess files, if Apache's document root # concurs with nginx's one # #location ~ /\.ht { # deny all; #} } # another virtual host using mix of IP-, name-, and port-based configuration # #server { # listen 8000; # listen somename:8080; # server_name somename alias another.alias; # location / { # root html; # index index.html index.htm; # } #} # HTTPS server # #server { # listen 443 ssl; # server_name localhost; # ssl_certificate cert.pem; # ssl_certificate_key cert.key; # ssl_session_cache shared:SSL:1m; # ssl_session_timeout 5m; # ssl_ciphers HIGH:!aNULL:!MD5; # ssl_prefer_server_ciphers on; # location / { # root html; # index index.html index.htm; # } #} }
-
第七章 博客项目日志
7.1 开始
1. 访问日志 access log(server 端最重要的日志)
1. 自定义日志(包括自定义事件,错误记录等)
nodejs 文件操作 nodejs stream
日志功能开发和使用
日志文件拆分,日志内容分析
7.2 nodeJs 文件操作
引入fs 和 path 模块
path 模块用于统一文件目录 Linux mac windows
-
读取文件
const fs = require("fs"); const path = require("path"); const filePath = path.relative(__dirname, "data.txt"); fs.readFile(filePath, (err, data) => { if (err) { console.error(err); return; } // data 是二进制类型 需要转换为字符串 console.log("data.toString", data.toString()); });
-
写入文件
const fs = require("fs"); const path = require("path"); const filePath = path.relative(__dirname, "data.txt"); const content = "这里是要写入的内容\n"; const opt = { flag: "a", // 追加内容 覆盖用w }; fs.writeFile(filePath, content, opt, (err) => { if (err) { console.log("err", err); } });
-
判断文件是否存在
fs.exists(filePath, (exists) => { console.log("exists", exists); });
7.3 stream 介绍
- IO操作的性能瓶颈
- IO 包括 网络IO 和文件 IO
- 相比于 CPU 计算和内存读写 IO 突出的特殊的就是:慢!
- 如何在有限的硬件资源下提供IO 的操作效率
7.4 stream 实例
-
复制文件
const fs = require("fs"); const path = require("path"); // 复制文件 let index = 0; const filePath1 = path.relative(__dirname, "test01.txt"); const filePath2 = path.relative(__dirname, "text03.txt"); const streamFile1 = fs.createReadStream(filePath1); const streamFile2 = fs.createWriteStream(filePath2); streamFile1.pipe(streamFile2); streamFile1.on("data", (chunk) => { console.log("index:", index++); }); streamFile1.on("end", () => { console.log("file ok"); });
server 利用stream
const fs = require("fs"); const path = require("path"); const http = require("http"); const filePath = path.relative(__dirname, "test01.txt"); const server = http.createServer((req, res) => { if (req.method == "GET") { const readStreamFile = fs.createReadStream(filePath); readStreamFile.pipe(res); } }); server.listen(4401);
7.5 stream封装组件
const fs = require("fs");
const path = require("path");
// 写入文件
function writeLog(writeStream, log) {
writeStream.write(log + "\n");
}
// 创建读stream
function createWriteStream(fileName) {
const falePath = path.join(__dirname, "../", "../", "logs", fileName);
const streamFile = fs.createWriteStream(falePath, { flags: "a" });
return streamFile;
}
const accessWriteStream = createWriteStream("access.log");
accessWriteStream.on("err", (err) => {
console.log("err", err);
});
function access(log) {
writeLog(accessWriteStream, log);
}
module.exports = {
access,
};
7.6 日志拆分(运维处理)
1. 日志内容会慢慢累积,放在一个文件中不好处理
2. 按时间划分日志文件,如 2019-02-10.access.log
3. 实现方式: linux 的 crontab 命令,定时任务
-
设置定时任务
> **** command 1. 分钟 2. 小时 3. 日期 4. 月份 5. 星期
将 access.log 拷贝并重命名为 2019-02-10.access.log
清空access.log 文件 ,继续积累日志
-
配置
#!/bin/sh cd 目录文件下面 cp access.lgo $(date +%Y-%m-%d).access.log echo '' > access.log >> 运行 sh copy.sh 可以用于测试 >> 编辑 crontab | crontab -e >> 在文件中编辑 | * 0 * * * sh 目录 成功的话显示 installing new crontab 查看任务 crontab -l
7.7 日志分析 readline
const fs = require("fs");
const path = require("path");
const readline = require("readline");
// 文件路径
const filePath = path.join(__dirname, "../", "../", "logs", "access.log");
// 创建 read stream
const readStreamFile = fs.createReadStream(filePath);
// 创建一个 readline 对象
const rl = readline.createInterface({ input: readStreamFile });
let chromeNum = 0;
let sum = 0;
rl.on("line", (lineData) => {
if (lineData == "") {
return;
}
sum++;
const str = lineData.split("--")[2];
if (str && str.indexOf("Chrome") > 0) {
chromeNum++;
}
});
rl.on("close", () => {
console.log("chrome 占比:" + chromeNum / sum);
});
第八章 node安全
8.1 开始和sql注入
1. sql注入: 窃取数据库内容
1. XSS 攻击: 窃取前端cookie内容
1. 密码加密: 保障用户信息安全( 重要! )
一 、 sql 注入
1. 最原始最简单的攻击
2. 攻击方式 输入一个sql片段,最终拼接成一段攻击代码
3. 使用mysql的 escape 函数处理输入内容即可
const mysql = require("mysql");
const login = (username, password) => {
const username1 = mysql.escape(username);
const password1 = mysql.escape(password);
8.2 XSS攻击
攻击范式:在页面展示内容中掺杂js代码,以获取网页信息
预防措施:转换生成js 的特殊字符
- 使用专业的工具 xss
- npm install xss --save
- 引入后直接使用即可
- const title = escape(xss(blogData.title));
8.3 密码加密
- 万一数据库被用户攻破,最不应该泄露的就是用户信息
- 攻击方式: 获取用户名和密码,在去尝试登录其他系统
- 预防措施: 将密码加密,即便拿到密码也不知道明文
总结
1. 服务端稳定性
2. 内存CPU 优化 扩展
3. 日志记录
4. 安全
5. 集群 和 服务拆分
const crypto = require("crypto");
// 密匙
const SERET_KEY = "BLACKITRABBIT";
// md5 加密
function md5(content) {
let md5 = crypto.createHash("md5");
return md5.update(content).digest("hex");
}
// 加密函数
function genPassword(password) {
const str = `password=${password}&key=${SERET_KEY}`;
return md5(str);
}
console.log(genPassword(123));
第九章 express 重构项目
9.1 开始
- express 是最常用的 web server 框架
- express 下载、安装、和使用,express 中间件机制
- 开发接口,链接数据库,实现登录,日志记录
- 分解 express 中间件原理
9.2 express 安装
-
安装(使用脚手架 express-generator)
npm install express-generator -g
-
利用工具生成项目
express express-test
npm install && npm start
9.3 express 中间件机制
- 有很多 app.use
- 代码中的 next 参数是什么
9.4 环境初始化
- 使用express-session
- connect-redis
9.5 链接mysql
"mysql": "^2.18.1",
"xss": "^1.0.10"
9.6 链接redis
- redis
- connect-redis
const RedisStore = require("connect-redis")(session);
const { redisClient } = require("./db/redis");
const sessionStore = new RedisStore({
client: redisClient,
});
app.use(
session({
secret: "asdwerd_sad",
cookie: {
path: "/", // 默认配置
httpOnly: true, // 默认配置
maxAge: 24 * 60 * 60 * 1000,
},
store: sessionStore,
})
);
9.7 登录中间件
const { ErrorModel } = require("../model/resModel");
module.exports = (req, res, next) => {
if (req.session.username) {
next();
return;
}
res.json(new ErrorModel("未登录"));
};
9.8 介绍morgan日志
- 引入 > const logger = require("morgan");
- 配置 app.use(logger("dev"));
const filePath = path.join(__dirname, "logs", "access.log");
const writeStream = fs.createWriteStream(filePath, { flags: "a" });
app.use(
logger("combined", {
stream: writeStream,
})
);
9.9express 中间件原理
- 回顾中间件使用
- 分析如何实现
- 代码演示
const http = require("http");
const slice = Array.prototype.slice;
class LikeExpress {
constructor() {
// 存放中间件的列表
this.routes = {
all: [],
get: [],
post: [],
};
}
register(path) {
const info = {};
if (typeof path == "string") {
info.path = path;
// 从第二个参数开始, 转换为数组 stack
info.stack = slice.call(arguments, 1);
} else {
info.path = "/";
// 从第一个参数开始, 转换为数组 存入 stack
info.stack = slice.call(arguments, 0);
}
return info;
}
use() {
const info = this.register.apply(this, arguments);
this.routes.all.push(info);
}
get() {
const info = this.register.apply(this, arguments);
this.routes.get.push(info);
}
post() {
const info = this.register.apply(this, arguments);
this.routes.post.push(info);
}
// 核心想next 函数
handle(req, res, stack) {
const next = () => {
// 拿到第一个匹配的中间件
const middleware = stack.shift();
if (middleware) {
// 执行中间件函数
middleware(req, res, next);
}
};
next();
}
match(method, url) {
let stack = {};
if (url == "/favicon.icon") {
return stack;
}
// 获取 routes
let curRoutes = [];
curRoutes = curRoutes.concat(this.routes.all);
curRoutes = curRoutes.concat(this.routes[method]);
curRoutes.forEach((routeInfo) => {
if (url.indexOf(routeInfo.path) == 0) {
stack = stack.concat(routeInfo.stack);
}
});
}
callback() {
return (req, res) => {
res.json = (data) => {
res.setHeader("Content-tyoe", "application/json");
res.end(JSON.stringify(data));
};
const url = req.url;
const method = req.method.toLowerCase();
const resulList = this.match(method, url);
this.handle(req, res, resulList);
};
}
listen(...args) {
const server = http.createServer(this.callback());
}
}
// 工厂函数
module.exports = () => {
return new LikeExpress();
};
第十章 Ko2 重构博客项目
10.1 开始
- express 中间件是异步回调,koa2 原生支持 async/await(异步函数)
- 新开发的框架和系统,都开始基于koa2, 例如 egg.js
- express 虽然未过时,但是koa2 肯定是未来的趋势
10.2 koa2 介绍
-
安装
npm install koa-generator -g
-
koa2 环境初始化
Koa2 koa2-test
启动项目
> npm install & npm run dev
10.3 介绍koa2 中间件机制
- 有很多 app.use
- 代码中的next参数是什么
10.4 koa2实现session
-
基于 koa-generic-session 和 koa-redis
npm i koa-generic-session koa-redis redis --save
-
引用
const session = require('koa-generic-session') const reidsStore = require('koa-redis')
-
配置session 设置key
app.keys = ["key1"] app.use(session({ // 配置cookie cookie: { path: '/', httpOnlay: true, maxAge: 24 * 60 * 60 * 1000 } // 配置 redis store: redisStore({ all: '127.0.0.1:6379' // 写死本地的 redis server }) }))
-
配置验证接口
router.get('/session-test',async function (ctx, next) { if(ctx.session.viewCount == null){ ctx.session.viewCount = 0; } ctx.session.viewCount ++; ctx.body = { errno: 0, viewCount:ctx.session.viewCount } })
10.5 开发路由-准备工作
-
安装mysql 和 xss
npm i mysql xss --save
拷贝module、config 文件夹
配置 redis
拷贝db,controller 文件夹加
10.6 日志
access log 记录 ,使用 morgan
-
安装koa-morgan
npm i koa-morgan --save
新建logs文件夹 创建access.log 文件
-
引入morgan
const fs = require('fs') const path = require('path') const morgan = require('koa-morgan') const ENV = process.env.NODE_ENV if(ENV !== 'production'){ app.use(morgan('dev')) }else{ const logFilePath = path.join(__dirname,'logs','access.log') const writeStream = fs.createWriteStream(logFilePath,{flags:'a'}) app.user(morgan("combined",{ stream:writeStram })) }
10.7 Koa2 中间件原理分析
洋葱圈模型 从最外层依次进入 从内层向外走出
const Koa = require('koa');
const app = new Koa();
// logger
app.use(async (ctx, next) => {
await next();
const rt = ctx.response.get('X-Response-Time');
console.log(`${ctx.method} ${ctx.url} - ${rt}`);
});
// x-response-time
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
ctx.set('X-Response-Time', `${ms}ms`);
});
// response
app.use(async ctx => {
ctx.body = 'Hello World';
});
app.listen(3000);
10.8
const http = require("http");
// 组合中间件
function compose(middlewareList) {
return function (ctx) {
// 中间件调用的逻辑
function dispatch(i) {
const fn = middlewareList[i];
try {
return Promise.resolve(fn(ctx, dispatch.bind(null, i + 1)));
} catch (err) {
Promise.resolve(err);
}
}
return dispatch(0);
};
}
class LikeKoa2 {
constructor() {
this.middlewareList = [];
}
use(fn) {
this.middlewareList.push(fn);
return this;
}
createContext = (req, res) => {
const ctx = {
req,
res,
};
ctx.query = req.query;
return ctx;
};
handleRequest(ctx, fn) {
return fn(ctx);
}
callback() {
const fn = compose(this.middlewareList);
return (req, res) => {
const ctx = this.createContext(req, res);
return this.handleRequest(ctx, fn);
};
}
listen(...args) {
const server = http.createServer(this.callback());
server.listen(...args);
}
}
module.exports = LikeKoa2;
第十一章 上线与配置
11.1 开始和PM2介绍
一、pm2做什么
- 进程守护,系统崩溃自动重启。
- 启动多进程,充分利用CPU和内存
- 自带日志记录功能
二 、pm2学什么
pm2 介绍
pm2 进程守护
pm2 配置和日志记录
pm2 多进程
pm2 关于服务运维
11.2 pm2 下载和安装
-
下载和安装
npm install pm2 -g
pm2 -version
-
配置pm启动
"prd": "cross-env NODE_ENV=production pm2 start app.js"
-
查看启动列表
pm2 ls
11.3 pm2 常用命令
- pm2 start ...
- pm2 list // 获取当前运行的列表
- pm2 restart <appName>/<id> // 重启服务器
- pm2 stop <appName>/<id> // 停止
- pm2 delete <appName>/<id> // 删除
- pm2 info <appName>/<id> // 获取某一个进程的基本信息
- pm2 log <appName>/<id> // 打印log信息
- pm2 monit <appName>/<id> // 监听服务器状态
11.4 pm2 常用配置
新建pm2 配置文件(包括进程数量,日志文件目录等)
修改pm2 启动命令,重启
访问server,检查日志文件的内容(日志记录是否生效)
-
新建配置文件
pm2.conf.json
-
配置内容
{ "apps": { "name": "pm2-test-server", "script": "app.js", "watch": true, "ignore_watch": ["node_modules", "logs"], "error_file": "logs/err.log", "out_file": "logs/out.log", "log_date_format": "YYYY-MM-DD HH:mm:ss" } }
-
配置启动项
"prd": "cross-env NODE_ENV=production pm2 start pm2.conf.json"
11.5 pm2 多进程
-
为什么使用多进程
- 操作系统会限制进程的最大可用内存
- 无法充分利用多核cpu的优势
- 某一个进程程序崩溃 其他的进程不受影响
-
配置多进程
"instances": 4
扩展项目上线发发布
CentOs nodejs搭建
-
安装nvm
wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
-
查看配置
vim .bashrc
退出文件
> :w file 将修改另外保存到file中,不退出vi
>
> :w! 强制保存,不推出vi
>
> :wq 保存文件并退出vi
>
> :wq! 强制保存文件,并退出vi
>
> :q 不保存文件,退出vi
>
> :q! 不保存文件,强制退出vi
>
> :e! 放弃所有修改,从上次保存文件开始再编辑
-
执行source 使环境变量生效
source .bashrc
CentOs Nginx环境搭建
- 安装nginx服务器需要配置 yum CentOs 服务自带
yum -y install pcre*
-
安装 openssl *
yum -y install openssl*
-
新建文件夹
mkdir nginx
-
切换到nginx中安装nginx
-
解压
tar -zxvf 解压的文件
-
进入到解压好的文件目录下 执行 configure
./configure 或者 sh configure --profix = 安装路径
-
利用make指令进行安装
make install
查看是否安装成功
/user/local/nginx/sbin/nginx -t
- 路径进行软连接到 usr/bin 路径下 -snf 可以修改
ln -s /root/nginx/ninxtstart nginx
-
查看是否创建成功
ll nginx
-
查看进程
ps -ef|grep nginx
-
关闭nginx进程
nginx -s stop
nginx配置
配置权限
-
配置引入其他配置
include /root/nginx/*.conf;y
-
在其他文件中配置
server{ listen 80; server_name localhost; root /root/nginx/upload; autoindex on; add_header Cache-Control "no-cache,must-revalidate"; location / { add_header Access-Control-Allow-Origin *; } }
-
配置 vim 显示行数
:set nu
配置好对应展示的文件即可
CentOs git 部署+免密更新
-
安装git
npm install -y git