# 个人博客的三种搭建方式
# nvm
node版本管理工具
- mac 使用brew安装 brew install nvm
- windows github搜 nvm-windows
- nvm list 查看当前所有node版本
- nvm install v10.13.0 安装指定版本(v可不加)
nvm use 10.13.0 切换指定版本
# 原生搭建
使用了的api
- querystring:序列化url中传递的参数
- req/res
- post原生接收数据
- fs/path
# 创建博客目录
- bin
- www.js //启动服务
- logs //日志
- prod.log
- error.log
- src //主要源码
- conf //配置,适配线上和本地环境开发
- controller //获取后台数据
- db
- model //封装自定义code和error
- router //api路由管理
- utils
- app.js //启动服务函数
- package.json //包管理
# bin目录
const http = require('http')
const PORT = 8000
const serverHandle = require('../app')
const server = http.createServer(serverHandle)
server.listen(PORT)
# app
const BlogRouter = require("./src/router/blog.js")
const UserRouter = require("./src/router/user.js")
const qs =require("querystring")
const serverHandle=(req,res)=>{
const method =req.method;
const url = req.url;
const path =url.split("?")[0]
const params =qs.parse(url.split("?")[1])
const options={
method,
url,
params,
path
}
const blog =BlogRouter(req,res,options)
if(blog){
res.end(JSON.stringify(blog))
return
}
const user =UserRouter(req,res,options)
if(user){
return res.end(JSON.stringify(user))
}
res.writeHead(404,{"Content-type":"text/plain"})
res.write("404 not found")
res.end()
}
module.exports =serverHandle
# model
对获取的数据进行包装后返回给前端,有error和success
class BaseModel {
constructor(data, message) {
if (typeof data === 'string') {
this.message = data
data = null
message = null
}
if (data) {
this.data = data
}
if (message) {
this.message = message
}
}
}
class SuccessModel extends BaseModel {
constructor(data, message) {
super(data, message)
this.errno = 0
}
}
class ErrorModel extends BaseModel {
constructor(data, message) {
super(data, message)
this.errno = -1
}
}
module.exports = {
SuccessModel,
ErrorModel
}
# controller
数据库处理,获取需要的数据
const getList =(authorkeyword)=>{
return[
{
id:1,
title:"title1",
content:"xxxx",
createTime:1540030495132,
author:"zhangsan"
}
]
}
module.exports={
getList
}
const {newBlog} = require("../controller/blog.js")
const {SuccessModel,ErrorModel} = require("../model/resModel.js")
const BlogRouter=(req,res,options)=>{
res.setHeader("Content-type","application/json")
const{method,path,params}=options
const id =params.id
if(method=="POST"&&path=="/api/blog/new"){
const data =newBlog(req.body)
console.log("--------")
return data.then(data=>{
console.log(data)
return new SuccessModel(data)
}).catch(err=>{
return new ErrorModel("获取失败,错误信息 "+err)
})
return new SuccessModel(data)
}
}
module.exports =BlogRouter
# router/xxx
通过路由获取数据,然后返回
const {getList} = require("../controller/blog.js")
const {SuccessModel,ErrorModel} = require("../model/resModel.js")
const BlogRouter=(req,res,options)=>{
res.setHeader("Content-type","application/json")
const{method,path,params}=options
if(method=="GET"&&path=="/api/blog/list"){
const listData =getList(params.author,params.keyword)
return new SuccessModel(listData)
// return {
// msg:"博客列表"
// }
}
if(method=="POST"&&path=="/api/blog/del"){
return {
msg:"删除博客"
}
}
}
module.exports =BlogRouter
# cross-env解决跨平台设置NODE_ENV的问题
"scripts": {
"lint": "gulp lint",
"watch": "gulp watch",
"generate": "gulp generate",
"build": " NODE_ENV=production gulp build"
},
但是windows不支持NODE_ENV=development的设置方式。需要安装插件
npm install cross-env --save-dev
"scripts": {
"lint": "gulp lint",
"watch": "gulp watch",
"generate": "gulp generate",
"build": "cross-env NODE_ENV=production gulp build"
},
# nodemon
npm install nodemon --save-dev
可以自动重启服务
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "cross-env NODE_ENV=dev nodemon ./bin/www.js",
"prd": "cross-env NODE_ENV=production nodemon ./bin/www.js"
}
# node连接mysql
借助可视化工具创建数据库和对应的表;增删改查搜索mysql文档; 在node中连接使用mysql,先安装对应的mysql插件
cnpm i mysql --save
const mysql =require("mysql")
//mysql.createPool(与mysql.createConnection用法一致)
let con = mysql.createConnection({
host:"localhost",
user:"root",
port:3306,
password:"",
database:"myblog",
})
con.connect()
const sql="select username,realname from users";
con.query(sql,(err,result)=>{
if(err){
console.log(err)
return
}
console.log(result)
})
con.end()
let sql = `select * from blogs where 1=1 `
if(author){
sql += `and author='${author}' `
}
if(keyword){
sql += `and title like '%${keyword}%' `
}
sql += `order by createtime desc;`
提示
注意如果分行,末尾需要空格!sql语句结尾加上;
# return
function fun(){
setTimeout(()=>{
console.log(1)
},1000)
console.log(2)
return
console.log(3)
}
fun()
//2
//1 还是会执行的,因为它在return前面,虽然是之后执行的!
//增加数据
const newBlog = (blogData={})=>{
const {title,content,author} =blogData;
const createtime = Date.now()
let sql =`insert into blogs(title,content,author,createtime)
values('${title}','${content}','${author}','${createtime}')`
console.log(sql)
return exec(sql).then(insertData=>{
console.log(1,insertData)
return {id:insertData.insertId}
})
}
当需要的不是展示之类的,返回的可能就是以下这种格式的数据,可以根据affectedRows insertId changedRows等参数判断修改的步骤是否成功
OkPacket {
fieldCount: 0,
affectedRows: 1,//影响的行数
insertId: 4,//插入数据的id
serverStatus: 2,
warningCount: 0,
message: '',
protocol41: true,
changedRows: 0 //修改的内容行数
}
# http-server
cnpm i --save -g http-server
http-server -p 8001
# nginx在node中使用
正向代理隐藏真实客户端,反向代理隐藏真实服务端.

#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;
server {
listen 8080;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;、
# 注释掉默认的location
#location / {
# root html;
# index index.html index.htm;
#}
location /{
proxy_pass http://localhost:8081;
}
location /api/{
proxy_pass http://localhost:3000;
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;
}
}
}
解释
location /{
proxy_pass http://localhost:8081;
}
//如果路径为/,则proxy_pass代理到http://localhost:8081
# stream
网络和文件传输大的时候,需要分段传输,更好的保证cpu和内存的运用
//标准输入输出
process.stdin.pipe(process.stdout)
const http = require('http')
const server = http.createServer((req, res) => {
if (req.method === 'POST') {
req.pipe(res) // 最主要
}
})
server.listen(8000)
// 复制文件
const fs = require('fs')
const path = require('path')
const fileName1 = path.resolve(__dirname, 'data.txt')
const fileName2 = path.resolve(__dirname, 'data-bak.txt')
const readStream = fs.createReadStream(fileName1)
const writeStream = fs.createWriteStream(fileName2)
readStream.pipe(writeStream)
readStream.on('data', chunk => {
console.log(chunk.toString())
})
//ondata监听可以写,也可以省略
readStream.on('end', () => {
console.log('copy done')
})
//直接放回数据
const http = require('http')
const fs = require('fs')
const path = require('path')
const fileName1 = path.resolve(__dirname, 'data.txt')
const server = http.createServer((req, res) => {
if (req.method === 'GET') {
const readStream = fs.createReadStream(fileName1)
readStream.pipe(res)
}
})
server.listen(8000)
# 日志
const fs = require('fs')
const path = require('path')
// 写日志
function writeLog(writeStream, log) {
writeStream.write(log + '\n') // 关键代码
}
// 生成 write Stream
function createWriteStream(fileName) {
const fullFileName = path.join(__dirname, '../', '../', 'logs', fileName)
const writeStream = fs.createWriteStream(fullFileName, {
flags: 'a'
})
return writeStream
}
// 写访问日志
const accessWriteStream = createWriteStream('access.log')
function access(log) {
writeLog(accessWriteStream, log)
}
module.exports = {
access
}
# 日志拆分与分析
- 使用crontab做定时任务 需要安装,在liunx下操作。
#!/bin/sh
cd /Users/wfp/Project/video-tutorial/node-tutorial/code-demo/blog-1/logs
cp access.log $(date +%Y-%m-%d).access.log
echo "" > access.log
cd /Users/wfp/Project/video-tutorial/node-tutorial/code-demo/blog-1/logs //寻找目录
cp access.log $(date +%Y-%m-%d).access.log //copy
echo "" >access.log //清空
最后运行这个 sh copy.sh
crontab 定时任务的守护进程,精确到分,设计秒的我们一般写脚本 -->相当于闹钟 crontab l=>查看任务
* 0 * * * /Users/wfp/Project/video-tutorial/node-tutorial/code-demo/blog-1/src/utils/copy.sh
- readline 逐行读取 nodejs自身提供的
const fs = require('fs')
const path = require('path')
const readline = require('readline')
// 文件名
const fileName = path.join(__dirname, '../', '../', 'logs', 'access.log')
// 创建 read stream
const readStream = fs.createReadStream(fileName)
// 创建 readline 对象
const rl = readline.createInterface({
input: readStream
})
let chromeNum = 0
let sum = 0
// 逐行读取
rl.on('line', (lineData) => {
if (!lineData) {
return
}
// 记录总行数
sum++
//日志中以--分割数据
const arr = lineData.split(' -- ')
if (arr[2] && arr[2].indexOf('Chrome') > 0) {
// 累加 chrome 的数量
chromeNum++
}
})
// 监听读取完成
rl.on('close', () => {
console.log('chrome 占比:' + chromeNum / sum)
})
# 安全
# sql注入
防止窃取数据库内容
zhangsan'delete from users;--
mysql.escape
username=mysql.escape(username)
# xss攻击
防止窃取前端cookie内容等
- 前端可以通过转化特殊符号 如大于号小于号之类的转化
- 后端可以安装插件
cnpm i --save xss
const xss = require('xss')
content=xss(content)
# 密码加密
//node 自带模块
const crypto = require('crypto');
const SECRET_KEY='J1$2HjsU_2+#';
function md5(content){
let md5 = crypto.createHash('md5');
return md5.update(content).digest('hex')
}
function pass(p){
const str=`password=${p}&key=${SECRET_KEY}`
return md5(str)
}
console.log(pass(123))
# 原生博客代码部分
# package(入口文件和配置插件)
- main 是入口文件,scripts里是需要启动的方法,用npm run dev/prd启动
- 安装相应的插件
- cross-env是为了解决兼容,linux和windows
{
"name": "blog-1",
"version": "1.0.0",
"description": "",
"main": "bin/www.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "cross-env NODE_ENV=dev nodemon ./bin/www.js",
"prd": "cross-env NODE_ENV=production nodemon ./bin/www.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"cross-env": "^5.2.0",
"nodemon": "^1.18.9"
},
"dependencies": {
"mysql": "^2.16.0",
"redis": "^2.8.0",
"xss": "^1.0.3"
}
}
# bin目录下的www.js(启动服务)
+ 定义端口,开启服务
const http = require('http')
const PORT = 3000
const serverHandle = require('../app')
const server = http.createServer(serverHandle)
server.listen(PORT,()=>{
console.log("server is ok")
})
# app.js(主要文件)
- 引入querystring解析query
- 设置cookie
- 设置session
const querystring = require('querystring')
const { get, set } = require('./src/db/redis')
const { access } = require('./src/utils/log')
const handleBlogRouter = require('./src/router/blog')
const handleUserRouter = require('./src/router/user')
// 获取 cookie 的过期时间
const getCookieExpires = () => {
const d = new Date()
d.setTime(d.getTime() + (24 * 60 * 60 * 1000))
console.log('d.toGMTString() is ', d.toGMTString())
return d.toGMTString()
}
// 用于处理 post data
const getPostData = (req) => {
const promise = new Promise((resolve, reject) => {
if (req.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
}
resolve(
JSON.parse(postData)
)
})
})
return promise
}
const serverHandle = (req, res) => {
// 记录 access log
access(`${req.method} -- ${req.url} -- ${req.headers['user-agent']} -- ${Date.now()}`)
// 设置返回格式 JSON
res.setHeader('Content-type', 'application/json')
// 获取 path
const url = req.url
req.path = url.split('?')[0]
// 解析 query
req.query = querystring.parse(url.split('?')[1])
// 解析 cookie
req.cookie = {}
const cookieStr = req.headers.cookie || '' // k1=v1;k2=v2;k3=v3
cookieStr.split(';').forEach(item => {
if (!item) {
return
}
const arr = item.split('=')
const key = arr[0].trim()
const val = arr[1].trim()
req.cookie[key] = val
})
// 解析 session (使用 redis)
let needSetCookie = false
let userId = req.cookie.userid
if (!userId) {
needSetCookie = true
userId = `${Date.now()}_${Math.random()}`
// 初始化 redis 中的 session 值
set(userId, {})
}
// 获取 session
req.sessionId = userId
get(req.sessionId).then(sessionData => {
if (sessionData == null) {
// 初始化 redis 中的 session 值
set(req.sessionId, {})
// 设置 session
req.session = {}
} else {
// 设置 session
req.session = sessionData
}
// console.log('req.session ', req.session)
// 处理 post data
return getPostData(req)
})
.then(postData => {
req.body = postData
const blogResult = handleBlogRouter(req, res)
if (blogResult) {
blogResult.then(blogData => {
if (needSetCookie) {
res.setHeader('Set-Cookie', `userid=${userId}; path=/; httpOnly; expires=${getCookieExpires()}`)
}
res.end(
JSON.stringify(blogData)
)
})
return
}
const userResult = handleUserRouter(req, res)
if (userResult) {
userResult.then(userData => {
if (needSetCookie) {
res.setHeader('Set-Cookie', `userid=${userId}; path=/; httpOnly; expires=${getCookieExpires()}`)
}
res.end(
JSON.stringify(userData)
)
})
return
}
// 未命中路由,返回 404
res.writeHead(404, {"Content-type": "text/plain"})
res.write("404 Not Found\n")
res.end()
})
}
module.exports = serverHandle
# conf.js
const env = process.env.NODE_ENV // 环境参数
console.log(process.env.NODE_ENV)
// 配置
let MYSQL_CONF
let REDIS_CONF
if (env === 'dev') {
// mysql
MYSQL_CONF = {
host: 'localhost',
user: 'root',
password: '',
port: '3306',
database: 'myblog'
}
// redis
REDIS_CONF = {
port: 6379,
host: '127.0.0.1'
}
}
if (env === 'production') {
// mysql
MYSQL_CONF = {
host: 'localhost',
user: 'root',
password: '',
port: '3306',
database: 'myblog'
}
// redis
REDIS_CONF = {
port: 6379,
host: '127.0.0.1'
}
}
module.exports = {
MYSQL_CONF,
REDIS_CONF
}
# mysql.js
const mysql = require('mysql')
const { MYSQL_CONF } = require('../conf/db')
// 创建链接对象
const con = mysql.createConnection(MYSQL_CONF)
// 开始链接
con.connect()
// 统一执行 sql 的函数
function exec(sql) {
const promise = new Promise((resolve, reject) => {
con.query(sql, (err, result) => {
if (err) {
reject(err)
return
}
resolve(result)
})
})
return promise
}
module.exports = {
exec,
escape: mysql.escape
}
# redis.js
const redis = require('redis')
const { REDIS_CONF } = require('../conf/db.js')
// 创建客户端
const redisClient = redis.createClient(REDIS_CONF.port, REDIS_CONF.host)
redisClient.on('error', err => {
console.error(err)
})
function set(key, val) {
if (typeof val === 'object') {
val = JSON.stringify(val)
}
redisClient.set(key, val, redis.print)
}
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
}
try {
resolve(
JSON.parse(val)
)
} catch (ex) {
resolve(val)
}
})
})
return promise
}
module.exports = {
set,
get
}
# router/blog.js
const {
getList,
getDetail,
newBlog,
updateBlog,
delBlog
} = require('../controller/blog')
const { SuccessModel, ErrorModel } = require('../model/resModel')
// 统一的登录验证函数
const loginCheck = (req) => {
if (!req.session.username) {
return Promise.resolve(
new ErrorModel('尚未登录')
)
}
}
const handleBlogRouter = (req, res) => {
const method = req.method // GET POST
const id = req.query.id
// 获取博客列表
if (method === 'GET' && req.path === '/api/blog/list') {
let author = req.query.author || ''
const keyword = req.query.keyword || ''
// const listData = getList(author, keyword)
// return new SuccessModel(listData)
if (req.query.isadmin) {
// 管理员界面
const loginCheckResult = loginCheck(req)
if (loginCheckResult) {
// 未登录
return loginCheckResult
}
// 强制查询自己的博客
author = req.session.username
}
const result = getList(author, keyword)
return result.then(listData => {
return new SuccessModel(listData)
})
}
// 获取博客详情
if (method === 'GET' && req.path === '/api/blog/detail') {
// const data = getDetail(id)
// return new SuccessModel(data)
const result = getDetail(id)
return result.then(data => {
return new SuccessModel(data)
})
}
// 新建一篇博客
if (method === 'POST' && req.path === '/api/blog/new') {
// const data = newBlog(req.body)
// return new SuccessModel(data)
const loginCheckResult = loginCheck(req)
if (loginCheckResult) {
// 未登录
return loginCheckResult
}
req.body.author = req.session.username
const result = newBlog(req.body)
return result.then(data => {
return new SuccessModel(data)
})
}
// 更新一篇博客
if (method === 'POST' && req.path === '/api/blog/update') {
const loginCheckResult = loginCheck(req)
if (loginCheckResult) {
// 未登录
return loginCheckResult
}
const result = updateBlog(id, req.body)
return result.then(val => {
if (val) {
return new SuccessModel()
} else {
return new ErrorModel('更新博客失败')
}
})
}
// 删除一篇博客
if (method === 'POST' && req.path === '/api/blog/del') {
const loginCheckResult = loginCheck(req)
if (loginCheckResult) {
// 未登录
return loginCheckResult
}
const author = req.session.username
const result = delBlog(id, author)
return result.then(val => {
if (val) {
return new SuccessModel()
} else {
return new ErrorModel('删除博客失败')
}
})
}
}
module.exports = handleBlogRouter
# router/user.js
const { login } = require('../controller/user')
const { SuccessModel, ErrorModel } = require('../model/resModel')
const { set } = require('../db/redis')
const handleUserRouter = (req, res) => {
const method = req.method // GET POST
// 登录
if (method === 'POST' && req.path === '/api/user/login') {
const { username, password } = req.body
// const { username, password } = req.query
const result = login(username, password)
return result.then(data => {
if (data.username) {
// 设置 session
req.session.username = data.username
req.session.realname = data.realname
// 同步到 redis
set(req.sessionId, req.session)
return new SuccessModel()
}
return new ErrorModel('登录失败')
})
}
// // 登录验证的测试
// if (method === 'GET' && req.path === '/api/user/login-test') {
// if (req.session.username) {
// return Promise.resolve(
// new SuccessModel({
// session: req.session
// })
// )
// }
// return Promise.resolve(
// new ErrorModel('尚未登录')
// )
// }
}
module.exports = handleUserRouter
# model/resmodel.js
class BaseModel {
constructor(data, message) {
if (typeof data === 'string') {
this.message = data
data = null
message = null
}
if (data) {
this.data = data
}
if (message) {
this.message = message
}
}
}
class SuccessModel extends BaseModel {
constructor(data, message) {
super(data, message)
this.errno = 0
}
}
class ErrorModel extends BaseModel {
constructor(data, message) {
super(data, message)
this.errno = -1
}
}
module.exports = {
SuccessModel,
ErrorModel
}
# controller/blog
const xss = require('xss')
const { exec } = require('../db/mysql')
const getList = (author, keyword) => {
let sql = `select * from blogs where 1=1 `
if (author) {
sql += `and author='${author}' `
}
if (keyword) {
sql += `and title like '%${keyword}%' `
}
sql += `order by createtime desc;`
// 返回 promise
return exec(sql)
}
const getDetail = (id) => {
const sql = `select * from blogs where id='${id}'`
return exec(sql).then(rows => {
return rows[0]
})
}
const newBlog = (blogData = {}) => {
// blogData 是一个博客对象,包含 title content author 属性
const title = xss(blogData.title)
// console.log('title is', title)
const content = xss(blogData.content)
const author = blogData.author
const createTime = Date.now()
const sql = `
insert into blogs (title, content, createtime, author)
values ('${title}', '${content}', ${createTime}, '${author}');
`
return exec(sql).then(insertData => {
// console.log('insertData is ', insertData)
return {
id: insertData.insertId
}
})
}
const updateBlog = (id, blogData = {}) => {
// id 就是要更新博客的 id
// blogData 是一个博客对象,包含 title content 属性
const title = xss(blogData.title)
const content = xss(blogData.content)
const sql = `
update blogs set title='${title}', content='${content}' where id=${id}
`
return exec(sql).then(updateData => {
// console.log('updateData is ', updateData)
if (updateData.affectedRows > 0) {
return true
}
return false
})
}
const delBlog = (id, author) => {
// id 就是要删除博客的 id
const sql = `delete from blogs where id='${id}' and author='${author}';`
return exec(sql).then(delData => {
// console.log('delData is ', delData)
if (delData.affectedRows > 0) {
return true
}
return false
})
}
module.exports = {
getList,
getDetail,
newBlog,
updateBlog,
delBlog
}
# controller/user
const { exec, escape } = require('../db/mysql')
const { genPassword } = require('../utils/cryp')
const login = (username, password) => {
username = escape(username)
// 生成加密密码
password = genPassword(password)
console.log(password)
password = escape(password)
const sql = `
select username, realname from users where username=${username} and password=${password}
`
// console.log('sql is', sql)
return exec(sql).then(rows => {
return rows[0] || {}
})
}
module.exports = {
login
}
# utils/cryp.js
const crypto = require('crypto')
// 密匙
const SECRET_KEY = 'WJiol_8776#'
// md5 加密
function md5(content) {
let md5 = crypto.createHash('md5')
return md5.update(content).digest('hex')
}
// 加密函数
function genPassword(password) {
const str = `password=${password}&key=${SECRET_KEY}`
return md5(str)
}
module.exports = {
genPassword
}
# express编写blog
- 安装 express-generator脚手架
cnpm i express-generator -g
express express-test
npm i&& npm start
- express路由
var express = require('express');
var router = express.Router();
/* GET users listing. */
router.get('/', function(req, res, next) {
res.send('respond with a resource');//这种智能返回字符串类型,且需要content-type
// res.render('index', { title: 'Express' }); express-generator中views的模板
//res.json(
// new ErrorModel('登录失败')
//)
//res.json可以不需要通过tostring和配置content-type,这两步有框架做好了
});
module.exports = router;
在app.js中配置
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var fs = require('fs');
var cookieParser = require('cookie-parser');//cookie添加req.cooke
var logger = require('morgan');
const blogRouter = require('./routes/blog')
const userRouter = require('./routes/user')
......
var app = express();
app.use(express.json());//post请求将data添加到req.body
app.use(express.urlencoded({ extended: false }));//post请求将data非application/json格式的也传到req.body
app.use(cookieParser());
......
app.use('/api/blog', blogRouter);
app.use('/api/user', userRouter);
module.exports = app;
# express中间件
- app.use可以直接跟处理函数,也可以在前面加上路由,如果没有路由,意味着会接受所有的请求
- app.use app.get app.post 中处理函数中的next方法如果不调用,请求如果正好匹配到这个方法,就会至此而止
- 匹配只要包含即可,不需要全等
- 一个app.use(get/post),可以接受多个处理函数,但记住,前一个函数如果没有执行next方法,就不会继续下去
const express = require('express')
// 本次 http 请求的实例
const app = express()
app.use((req, res, next) => {
console.log('请求开始...', req.method, req.url)
next()
})
app.use((req, res, next) => {
// 假设在处理 cookie
req.cookie = {
userId: 'abc123'
}
next()
})
app.use((req, res, next) => {
// 假设处理 post data
// 异步
setTimeout(() => {
req.body = {
a: 100,
b: 200
}
next()
})
})
app.use('/api', (req, res, next) => {
console.log('处理 /api 路由')
next()
})
app.get('/api', (req, res, next) => {
console.log('get /api 路由')
next()
})
app.post('/api', (req, res, next) => {
console.log('post /api 路由')
next()
})
// 模拟登录验证
function loginCheck(req, res, next) {
setTimeout(() => {
console.log('模拟登陆失败')
res.json({
errno: -1,
msg: '登录失败'
})
// console.log('模拟登陆成功')
// next()
})
}
app.get('/api/get-cookie', loginCheck, (req, res, next) => {
console.log('get /api/get-cookie')
res.json({
errno: 0,
data: req.cookie
})
})
app.post('/api/get-post-data', loginCheck, (req, res, next) => {
console.log('post /api/get-post-data')
res.json({
errno: 0,
data: req.body
})
})
app.use((req, res, next) => {
console.log('处理 404')
res.json({
errno: -1,
msg: '404 not fount'
})
})
app.listen(3000, () => {
console.log('server is running on port 3000')
})
# express中间件express-session connect-redis
var express = require('express');
var path = require('path');
var fs = require('fs');
var cookieParser = require('cookie-parser');
const session = require('express-session')
const RedisStore = require('connect-redis')(session)
var app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
const redisClient = require('./db/redis')
const sessionStore = new RedisStore({
client: redisClient
})
app.use(session({
secret: 'WJiol#23123_',
cookie: {
// path: '/', // 默认配置
// httpOnly: true, // 默认配置
maxAge: 24 * 60 * 60 * 1000
},
store: sessionStore
}))
......
module.exports = app;
var express = require('express');
var router = express.Router();
const { login } = require('../controller/user')
const { SuccessModel, ErrorModel } = require('../model/resModel')
router.post('/login', function(req, res, next) {
const { username, password } = req.body
const result = login(username, password)
return result.then(data => {
if (data.username) {
// 设置 session
req.session.username = data.username
req.session.realname = data.realname
res.json(
new SuccessModel()
)
return
}
res.json(
new ErrorModel('登录失败')
)
})
});
// router.get('/login-test', (req, res, next) => {
// if (req.session.username) {
// res.json({
// errno: 0,
// msg: '已登录'
// })
// return
// }
// res.json({
// errno: -1,
// msg: '未登录'
// })
// })
module.exports = router;
# morgan 日志
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var fs = require('fs');
var logger = require('morgan');
var app = express();
const ENV = process.env.NODE_ENV
if (ENV !== 'production') {
// 开发环境 / 测试环境
app.use(logger('dev'));
} else {
// 线上环境
const logFileName = path.join(__dirname, 'logs', 'access.log')
const writeStream = fs.createWriteStream(logFileName, {
flags: 'a'
})
app.use(logger('combined', {
stream: writeStream
}));
}
module.exports = app;
# express中间件原理
- app.use用来注册中间件,先收集起来
- 遇到http请求,根据path和method判断触发哪些
- 实现next机制
const http = require('http')
const slice = Array.prototype.slice
class LikeExpress {
constructor() {
// 存放中间件的列表
this.routes = {
all: [], // app.use(...)
get: [], // app.get(...)
post: [] // app.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)
}
match(method, url) {
let stack = []
if (url === '/favicon.ico') {
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) {
// url === '/api/get-cookie' 且 routeInfo.path === '/'
// url === '/api/get-cookie' 且 routeInfo.path === '/api'
// url === '/api/get-cookie' 且 routeInfo.path === '/api/get-cookie'
stack = stack.concat(routeInfo.stack)
}
})
return stack
}
// 核心的 next 机制
handle(req, res, stack) {
const next = () => {
// 拿到第一个匹配的中间件
const middleware = stack.shift()
if (middleware) {
// 执行中间件函数
middleware(req, res, next)
}
}
next()
}
callback() {
return (req, res) => {
res.json = (data) => {
res.setHeader('Content-type', 'application/json')
res.end(
JSON.stringify(data)
)
}
const url = req.url
const method = req.method.toLowerCase()
const resultList = this.match(method, url)
this.handle(req, res, resultList)
}
}
listen(...args) {
const server = http.createServer(this.callback())
server.listen(...args)
}
}
// 工厂函数
module.exports = () => {
return new LikeExpress()
}
const express = require('./like-express')
// 本次 http 请求的实例
const app = express()
app.use((req, res, next) => {
console.log('请求开始...', req.method, req.url)
next()
})
app.use((req, res, next) => {
// 假设在处理 cookie
console.log('处理 cookie ...')
req.cookie = {
userId: 'abc123'
}
next()
})
app.use('/api', (req, res, next) => {
console.log('处理 /api 路由')
next()
})
app.get('/api', (req, res, next) => {
console.log('get /api 路由')
next()
})
// 模拟登录验证
function loginCheck(req, res, next) {
setTimeout(() => {
console.log('模拟登陆成功')
next()
})
}
app.get('/api/get-cookie', loginCheck, (req, res, next) => {
console.log('get /api/get-cookie')
res.json({
errno: 0,
data: req.cookie
})
})
app.listen(8000, () => {
console.log('server is running on port 8000')
})
# koa2编写blog
cnpm i koa-generator -g
Koa2 koa2-test
npm i & npm run dev
const Koa = require('koa')
const app = new Koa()
const json = require('koa-json')
const onerror = require('koa-onerror')
const bodyparser = require('koa-bodyparser')
const path = require('path')
const fs = require('fs')
const blog = require('./routes/blog')
const user = require('./routes/user')
// error handler
onerror(app)
// middlewares 解析post请求数据成json格式
app.use(bodyparser({
enableTypes:['json', 'form', 'text']
}))
app.use(json())
// routes路由注册写法
app.use(blog.routes(), blog.allowedMethods())
app.use(user.routes(), user.allowedMethods())
// error-handling 错误处理
app.on('error', (err, ctx) => {
console.error('server error', err, ctx)
});
module.exports = app
# koa路由前缀prefix
- 写法1:
router.prefix 路由前缀
const router = require('koa-router')()
const { login } = require('../controller/user')
const { SuccessModel, ErrorModel } = require('../model/resModel')
//路由前缀
router.prefix('/api/user')
router.post('/login', async function (ctx, next) {
const { username, password } = ctx.request.body
const data = await login(username, password)
if (data.username) {
// 设置 session
ctx.session.username = data.username
ctx.session.realname = data.realname
ctx.body = new SuccessModel()
return
}
ctx.body = new ErrorModel('登录失败')
})
module.exports = router
- 写法2:
const Router = require('koa-router');
const router = new Router({ prefix: '/questions/:questionId/answers' });
......
router.post('/', auth, create);
router.get('/:id', checkAnswerExist, findById);
module.exports = router;
# koa2实现session登录
基于koa-generic 和koa-redis,redis
const Koa = require('koa')
const app = new Koa()
const session = require('koa-generic-session')
const redisStore = require('koa-redis')
const path = require('path')
const fs = require('fs')
// redis
// REDIS_CONF = {
// port: 6379,
// host: '127.0.0.1'
// }
const { REDIS_CONF } = require('./conf/db')
// session 配置
app.keys = ['WJiol#23123_']
app.use(session({
// 配置 cookie
cookie: {
path: '/',
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000
},
// 配置 redis
store: redisStore({
// all: '127.0.0.1:6379' // 写死本地的 redis
all: `${REDIS_CONF.host}:${REDIS_CONF.port}`
})
}))
module.exports = app
# koa2手写
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)) // promise
)
} catch (err) {
return Promise.reject(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
const Koa = require('./like-koa2');
const app = new Koa();
// logger
app.use(async (ctx, next) => {
await next();
const rt = ctx['X-Response-Time'];
console.log(`${ctx.req.method} ${ctx.req.url} - ${rt}`);
});
// x-response-time
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
ctx['X-Response-Time'] = `${ms}ms`;
});
// response
app.use(async ctx => {
ctx.res.end('This is like koa2');
});
app.listen(8000);