# 个人博客的三种搭建方式

# 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


# 创建博客目录

  • 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);
最后更新: 3/4/2022, 9:47:34 AM