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 區(qū)別
ECMAScript 是語法基礎 定義了語法規(guī)范
JavaScript 是 ECMAScript + web API
nodejs 是 ECMAScript + nodeAPI
2.3 commonjs 演示
實現(xiàn)引入導出
module.exports = function add(){}
const add = require("path")
1. 建立兩個文件通過b文件引入a文件中的函數(shù) 實現(xiàn) 求和
2. 導出多個函數(shù) 分別求和 和 求乘(利用解構的語法)
3. 引入npm 包
安裝lodash
1. npm init -y //npm 初始化
2. npm i lodash
3. 引入 lodash
4. 合并數(shù)組 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 項目需求分析
一笼痛、目標
開發(fā)一個博客系統(tǒng)鹏氧,具有博客的基本功能
只開發(fā)server端锨推,不關心前端
二、需求
- 首頁,作者主頁赔嚎,博客詳情頁
- 登錄頁
- 管理中心皮假,新建頁,編輯頁
3.2 技術方案
1衩椒、數(shù)據(jù)如何存儲
2蚌父、如何與前端對接,接口設計
第四章 開發(fā)博客項目接口
4.1 http 概述
-
從輸入url到顯示頁面的整個過程
- DNS解析,建立TCP鏈接毛萌,發(fā)送http請求
- server接收到請求 苟弛,處理, 返回
- 客戶端接收到返回數(shù)據(jù)阁将,處理數(shù)據(jù)(如執(zhí)行頁面渲染膏秫,執(zhí)行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 發(fā)送數(shù)據(jù)請求
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 搭建開發(fā)環(huán)境
-
新建項目路徑 并搭建基礎代碼
-
項目目錄
|-- blog-1 |-- bin |-- www.js // 項目入口 服務器創(chuàng)建 |-- res |-- router // 處理路由 |-- blog.js |-- user.js |-- model // 定義數(shù)據(jù)格式 |-- resModel.js |-- controller // 業(yè)務邏輯 |-- 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 監(jiān)聽文件變化,自動重啟node
-
使用 cross-env 設置環(huán)境變量做盅,兼容 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: "獲取博客詳情", }; } // 創(chuàng)建博客 if (method == "POST" && path == "/api/blog/create") { return { msg: "創(chuàng)建博客", }; } // 刪除博客 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 開發(fā)路由(博客列表路由)
-
定義數(shù)據(jù)格式
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, };
-
創(chuàng)建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 };
-
返回數(shù)據(jù)
~~~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博客詳情頁開發(fā)
-
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 數(shù)據(jù)
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; };
使用 處理數(shù)據(jù)
getPostData(req).then((postData) => {
req.body = postData;
// 創(chuàng)建博客
if (method == "POST" && req.path == "/api/blog/create") {
return {
msg: "創(chuàng)建博客",
};
}
// 刪除博客
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: 前段和后端缤削,不同端(子系統(tǒng))直接對接的一個術語
路由: API 的一部分 后端系統(tǒng)內部的一個定義
第五章 數(shù)據(jù)存儲
5.1 MySql 介紹
1. 下載mysql
1. 下載workBench
1. 查看全部的表 show databases
5.2 數(shù)據(jù)庫操作
- 數(shù)據(jù)庫縮寫
- PK primary key 主鍵
- NN not null 是否為空
- UQ unique 外鍵
- BIN binary 二進制
- UN unsigned 無符號
- ZF zero fill 補零
- AI auto increment 自動增加
簡單的指令
1. show databases // 查看數(shù)據(jù)庫
2. use // 數(shù)據(jù)名稱
3. show tables // 顯示數(shù)據(jù)庫
4. SET SQL_SAFF_UPDATAS = 0 // 關閉數(shù)據(jù)庫安全檢查
5. <> // 設置不等于零
-
指令:
- 增
- insert into 表名 (參數(shù)一,參數(shù)二,.....) values(參數(shù)一,參數(shù)二,..)
- 刪
- 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 操作數(shù)據(jù)庫
1. 新建一個項目 (初始化)
2. 安裝mysql 依賴
- npm install mysql
3. 引入mysql
1. const con = require('mysql')
4. 創(chuàng)建鏈接對象
+ con = mysql.createConnection({})
+ host:"localhost" //域地址
+ user:"root"
+ password:"root"
+ port:"3306"
+ database:"myblog"
5. 開始鏈接數(shù)據(jù)庫
1. con.connect()
6. 執(zhí)行sql語句
1. const sql = `'select * from users'`
2. con.query(sql,(err,result)=>{})
7. 關閉鏈接
1. con.end()
5.5 nodejs 鏈接mysql 做成工具
- 獲取環(huán)境變量
- 鏈接 const env = process.env.NODE_ENV
新建兩個文件
-
config
const env = process.env.NODE_ENV; // 獲取環(huán)境參數(shù) 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");
// 創(chuàng)建鏈接對象
const con = mysql.createConnection(MYSQL_CONF);
// 開始鏈接
con.connect();
// 執(zhí)行sql的函數(shù)
function exec(sql) {
const promise = new Promise((resolve, reject) => {
con.query(sql, (err, result) => {
console.log("數(shù)據(jù)庫獲取回來的數(shù)據(jù):", 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
- 沒次發(fā)送http請求,會將請求的cookie 一起發(fā)送給server
- server 可以修改cookie 并返回給瀏覽器
- 瀏覽器中也可以通過JavaScript修改cookie(有限制)
-
JavaScript 操作 cookie 瀏覽器中查看cookie
- 查看cookie 的三種方式
- 直接在請求中查看
- 在application 中查看
- document.cookie
- 操作cookie的方式
- document.cookie = "k1=100"
- 只能以累加的方式修改
-
server 端操作 cookie, 實現(xiàn)登錄驗證
-
查看cookie
- const cookieStr = req.headers.cookie
-
修改cookie
- res.serHeader("Set-Cookie",
username = zhangshan; path=/; httpOnly
) - 注意設置 path
- 設置httpOnly 實現(xiàn)禁止前端修改
- 設置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 用于存儲用戶數(shù)據(jù) 前端存儲虛擬的報文
加密數(shù)據(jù) 解決cooki不安全的問題
設置 session data 存儲 數(shù)據(jù)
session 的問題
1. 進程內存有限 訪問量過大 內存暴增
2. 正式上線運行是多進程吹榴,進程之間內存無法共享
6.3 redis(內存數(shù)據(jù)庫) 學習
web server 最常用的緩存數(shù)據(jù)庫亭敢,數(shù)據(jù)放在內存中
優(yōu)點讀寫特別快 缺點 內存很貴 存儲空間小
- session 訪問頻繁 對性能要求極高
- session 可不考慮斷電數(shù)據(jù)丟失的問題(內存的硬傷)也可以斷電不丟失 要配置
- session 數(shù)據(jù)量不會太大(相比于MySQL中的數(shù)據(jù))
安裝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"); // 創(chuàng)建redis客戶端 const redisClient = redis.createClient(6379, "127.0.0.1"); // 監(jiān)聽錯誤 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");
// 創(chuàng)建客戶端
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 聯(lián)調
- 判斷用戶是否通過cookie傳入userid 如果沒有傳入則隨機生成 并設置到用戶瀏覽器和 redis中
- 如果傳入cookie 中有userid 則去redis中查詢userid 如何返回非空對象則登錄 如果返回空對象 則未登錄
- 添加登錄驗證 在調用接口前判斷是否登錄成功(有 username)
6.7 前端聯(lián)調配置nginx
問題:
1. 登錄功能依賴cookie 必須用瀏覽器聯(lián)調
2. cookie,跨越不共享图筹,前端和server端必須同域
3. 需要用到nignx做代理帅刀,讓前后端端同域
6.8 nginx 介紹
- 高性能的web服務器让腹,開源免費
- 一般用于做靜態(tài)服務,負載均衡
- 反向代理
[圖片上傳失敗...(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
日志功能開發(fā)和使用
日志文件拆分骇窍,日志內容分析
7.2 nodeJs 文件操作
引入fs 和 path 模塊
path 模塊用于統(tǒng)一文件目錄 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 包括 網(wǎng)絡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");
}
// 創(chuàng)建讀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. 實現(xiàn)方式: linux 的 crontab 命令像鸡,定時任務
-
設置定時任務
> **** command 1. 分鐘 2. 小時 3. 日期 4. 月份 5. 星期
將 access.log 拷貝并重命名為 2019-02-10.access.log
清空access.log 文件 ,繼續(xù)積累日志
-
配置
#!/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");
// 創(chuàng)建 read stream
const readStreamFile = fs.createReadStream(filePath);
// 創(chuàng)建一個 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注入: 竊取數(shù)據(jù)庫內容
1. XSS 攻擊: 竊取前端cookie內容
1. 密碼加密: 保障用戶信息安全( 重要! )
一 哈恰、 sql 注入
1. 最原始最簡單的攻擊
2. 攻擊方式 輸入一個sql片段只估,最終拼接成一段攻擊代碼
3. 使用mysql的 escape 函數(shù)處理輸入內容即可
const mysql = require("mysql");
const login = (username, password) => {
const username1 = mysql.escape(username);
const password1 = mysql.escape(password);
8.2 XSS攻擊
攻擊范式:在頁面展示內容中摻雜js代碼,以獲取網(wǎng)頁信息
預防措施:轉換生成js 的特殊字符
- 使用專業(yè)的工具 xss
- npm install xss --save
- 引入后直接使用即可
- const title = escape(xss(blogData.title));
8.3 密碼加密
- 萬一數(shù)據(jù)庫被用戶攻破着绷,最不應該泄露的就是用戶信息
- 攻擊方式: 獲取用戶名和密碼蛔钙,在去嘗試登錄其他系統(tǒng)
- 預防措施: 將密碼加密,即便拿到密碼也不知道明文
總結
1. 服務端穩(wěn)定性
2. 內存CPU 優(yōu)化 擴展
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");
}
// 加密函數(shù)
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 中間件機制
- 開發(fā)接口彬向,鏈接數(shù)據(jù)庫兼贡,實現(xiàn)登錄,日志記錄
- 分解 express 中間件原理
9.2 express 安裝
-
安裝(使用腳手架 express-generator)
npm install express-generator -g
-
利用工具生成項目
express express-test
npm install && npm start
9.3 express 中間件機制
- 有很多 app.use
- 代碼中的 next 參數(shù)是什么
9.4 環(huán)境初始化
- 使用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 中間件原理
- 回顧中間件使用
- 分析如何實現(xiàn)
- 代碼演示
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;
// 從第二個參數(shù)開始, 轉換為數(shù)組 stack
info.stack = slice.call(arguments, 1);
} else {
info.path = "/";
// 從第一個參數(shù)開始, 轉換為數(shù)組 存入 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 函數(shù)
handle(req, res, stack) {
const next = () => {
// 拿到第一個匹配的中間件
const middleware = stack.shift();
if (middleware) {
// 執(zhí)行中間件函數(shù)
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());
}
}
// 工廠函數(shù)
module.exports = () => {
return new LikeExpress();
};
第十章 Ko2 重構博客項目
10.1 開始
- express 中間件是異步回調娃胆,koa2 原生支持 async/await(異步函數(shù))
- 新開發(fā)的框架和系統(tǒng)遍希,都開始基于koa2, 例如 egg.js
- express 雖然未過時里烦,但是koa2 肯定是未來的趨勢
10.2 koa2 介紹
-
安裝
npm install koa-generator -g
-
koa2 環(huán)境初始化
Koa2 koa2-test
啟動項目
> npm install & npm run dev
10.3 介紹koa2 中間件機制
- 有很多 app.use
- 代碼中的next參數(shù)是什么
10.4 koa2實現(xiàn)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 開發(fā)路由-準備工作
-
安裝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文件夾 創(chuàng)建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做什么
- 進程守護,系統(tǒng)崩潰自動重啟丧蘸。
- 啟動多進程漂洋,充分利用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> // 監(jiān)聽服務器狀態(tài)
11.4 pm2 常用配置
新建pm2 配置文件(包括進程數(shù)量力喷,日志文件目錄等)
修改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 多進程
-
為什么使用多進程
- 操作系統(tǒng)會限制進程的最大可用內存
- 無法充分利用多核cpu的優(yōu)勢
- 某一個進程程序崩潰 其他的進程不受影響
-
配置多進程
"instances": 4
擴展項目上線發(fā)發(fā)布
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! 放棄所有修改,從上次保存文件開始再編輯
-
執(zhí)行source 使環(huán)境變量生效
source .bashrc
CentOs Nginx環(huán)境搭建
- 安裝nginx服務器需要配置 yum CentOs 服務自帶
yum -y install pcre*
-
安裝 openssl *
yum -y install openssl*
-
新建文件夾
mkdir nginx
-
切換到nginx中安裝nginx
-
解壓
tar -zxvf 解壓的文件
-
進入到解壓好的文件目錄下 執(zhí)行 configure
./configure 或者 sh configure --profix = 安裝路徑
-
利用make指令進行安裝
make install
查看是否安裝成功
/user/local/nginx/sbin/nginx -t
- 路徑進行軟連接到 usr/bin 路徑下 -snf 可以修改
ln -s /root/nginx/ninxtstart nginx
-
查看是否創(chuàng)建成功
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 顯示行數(shù)
:set nu
配置好對應展示的文件即可
CentOs git 部署+免密更新
-
安裝git
npm install -y git