Node.js 从零开发 web server博客项目[express重构博客项目]

web server博客项目

  1. Node.js 从零开发 web server博客项目[项目介绍]
  2. Node.js 从零开发 web server博客项目[接口]
  3. Node.js 从零开发 web server博客项目[数据存储]
  4. Node.js 从零开发 web server博客项目[登录]
  5. Node.js 从零开发 web server博客项目[日志]
  6. Node.js 从零开发 web server博客项目[安全]
  7. Node.js 从零开发 web server博客项目[express重构博客项目]
  8. Node.js 从零开发 web server博客项目[koa2重构博客项目]
  9. Node.js 从零开发 web server博客项目[上线与配置]

express 下载 , 安装和使用 , express 中间件机制

Express 应用程序生成器

安装 ( 使用脚手架 express-generator )

  • 安装脚手架工具
  • cnpm i express-generator -g
  • 使用 express 脚手架创建项目
  • express blog-express
  • 进入项目目录
  • cd blog-express
  • 安装项目初始化所需要的插件
  • cnpm i
  • 安装热更新插件与环境插件
  • cnpm i nodemon cross-env -D
  • 修改相应的配置
  • package.json增加script项 : "dev": "cross-env NODE_ENV=dev nodemon ./bin/www"
  • 启动项目
  • npm run dev

初始化代码介绍 , 处理路由

介绍 app.js

  • 各个插件的作用
  • 思考各个插件的实现原理
  • 处理 get 请求和 post 请求
var createError = require('http-errors'); // 处理不存在或者错误的页面提供一个友好的提示
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser'); // 解析 cookie , 使用 req.cookies 访问 cookie
var logger = require('morgan'); // 日志功能

var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');

var app = express();

// view engine setup // 前端模板引擎
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');

app.use(logger('dev')); // 日志功能
app.use(express.json()); // 解析postdata中的json格式
app.use(express.urlencoded({ extended: false })); // 解析postdata中的from(x-www-form-urlencoded)格式
app.use(cookieParser());// 解析 cookie , 使用 req.cookies 访问 cookie
app.use(express.static(path.join(__dirname, 'public'))); // 

// 处理路由
app.use('/', indexRouter);
app.use('/users', usersRouter);

// catch 404 and forward to error handler // 处理不存在或者错误的页面
app.use(function (req, res, next) {
  next(createError(404)); 
});

// error handler // 服务出错时的处理
app.use(function (err, req, res, next) {
  // set locals, only providing error in development 
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

router/users.js

var express = require('express');
var router = express.Router();

/* GET users listing. */
router.get('/', function(req, res, next) {
  res.send('respond with a resource');
});

module.exports = router;

使用中间件

Writing middleware for use in Express apps
Using middleware

开发接口 , 连接数据库 , 实现登录 , 日志记录

初始化环境

  • 安装插件 mysql xss
  • mysql controller resModel 相关代码可以复用
  • 初始化目录

express 开发接口

  • 初始化项目 , 之前的部分代码可以复用

    • 开发路由 , 实现登录

      • 登录
        • 使用 express-session 和 connect-redis , 简单方便

cnpm i express-session -S
app.js

var session = require('express-session')
		...
app.use(session({
  secret: 'keyboard cat',
  cookie: {
    path: '/',
    httpOnly: true,
    maxAge: 24 * 60 * 60 * 1000
  }
}))
		...

cnpm i redis connect-redis -S
db/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)
})

module.exports = redisClient

使用
app.js

...
var RedisStore = require('connect-redis')(session)
var redisClient = require('./db/redis')
...
var sessionStore = new RedisStore({
  client: redisClient
})

app.use(session({
  secret: 'abcdefg',
  cookie: {
    path: '/',
    httpOnly: true,
    maxAge: 24 * 60 * 60 * 1000
  },
  store: sessionStore
}))
...

登录之后可查看到redis信息

express>redis-cli
127.0.0.1:6379> flushAll
OK
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> keys *
1) "sess:QDTy6D6sLv-bFU9qxTOk_zqf9msMQ-jL"
127.0.0.1:6379> get sess:QDTy6D6sLv-bFU9qxTOk_zqf9msMQ-jL
"{\"cookie\":{\"originalMaxAge\":86400000,\"expires\":\"2019-07-12T10:52:47.777Z\",\"httpOnly\":true,\"path\":\"/\"}}"
127.0.0.1:6379> get sess:QDTy6D6sLv-bFU9qxTOk_zqf9msMQ-jL
//  登录后
"{\"cookie\":{\"originalMaxAge\":86400000,\"expires\":\"2019-07-12T10:54:54.582Z\",\"httpOnly\":true,\"path\":\"/\"},\"username\":\"zhangsan\",\"realname\":\"\xe5\xbc\xa0\xe4\xb8\x89\"}"
127.0.0.1:6379>
  • req.session 保存登录信息 , 登录校验做成 express 中间件

创建middleware/loginCheck.js

const { ErrorModel } = require('../model/resModel')

module.exports = (req, res, next) => {
  if (req.session.username) {
    next()
    return
  }
  res.json(
    new ErrorModel('未登录')
  )
}
  • 使用
    router/blog.js
var express = require('express');
var router = express.Router();
const {
  getList,
  getDetail,
  newBlog,
  updateBlog,
  delBlog
} = require('../controller/blog')
const { SuccessModel, ErrorModel } = require('../model/resModel')
const loginCheck = require('../middleware/loginCheck')


/* GET users listing. */
router.get('/list', (req, res, next) => {
  let author = req.query.author || ''
  const keyword = req.query.keyword || ''

  if (req.query.isadmin) {
    // 管理员界面
    if (req.session.username == null) {
      // 未登录
      res.json(
        new ErrorModel('未登录')
      )
      return
    }
    // 强制查询自己的博客
    author = req.session.username
  }

  const result = getList(author, keyword)
  return result.then(listData => {
    res.json(
      new SuccessModel(listData)
    )
  })
});

// 获取博客详情
router.get('/detail', (req, res, next) => {
  const result = getDetail(req.query.id)
  return result.then(data => {
    res.json(
      new SuccessModel(data)
    )
  })
})

// 新建一篇博客
router.post('/new', loginCheck, (req, res, next) => {
  req.body.author = req.session.username
  const result = newBlog(req.body)
  return result.then(data => {
    res.json(
      new SuccessModel(data)
    )
  })
})

// 更新一篇博客
router.post('/update', loginCheck, (req, res, next) => {
  const result = updateBlog(req.query.id, req.body)
  return result.then(val => {
    if (val) {
      res.json(
        new SuccessModel()
      )
    } else {
      res.json(
        new ErrorModel('更新博客失败')
      )
    }
  })
})

// 删除一篇博客
router.post('/del', (req, res, next) => {
  const author = req.session.username
  const result = delBlog(req.query.id, author)
  return result.then(val => {
    if (val) {
      res.json(
        new SuccessModel()
      )
    } else {
      res.json(
        new ErrorModel('删除博客失败')
      )
    }
  })
})

module.exports = router;
  • 记录日志

    • access log 记录 , 直接使用脚手架推荐的 Morgan

package.json
"prd": "cross-env NODE_ENV=production nodemon ./bin/www"
app.js

const morgan = require('morgan');
const ENV = process.env.NODE_ENV
if (ENV !== 'production') {
  // 开发环境 / 测试环境
  app.use(morgan('dev'))
} else {
  // 线上环境
  const logFileName = path.join(__dirname, 'logs', 'access.log')
  const writeStream = fs.createWriteStream(logFileName, {
    flags: 'a'
  })
  app.use(morgan('combined'), {
    stream: writeStream
  })
}
  • 自定义日志使用 console.log 和 console.error 即可
    • 上线与配置会讲到
  • 日志文件拆分 , 日志内容分析 ,
    • 见上章节

分析 express 中间件原理

  • 分析如何实现

    • app.use 用来注册中间件 , 先收集起来
    • 遇到 http 请求 , 会根据 path 和 method 判断出发那些
    • 实现 next 机制 , 即上一个通过 next 触发下一个
  • 代码演示

middleware-express

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()
}
posted @ 2019-07-22 02:20  仲灏  阅读(518)  评论(0编辑  收藏  举报