Nodejs Koa2 学习笔记 一

cmd新建项目文件夹:mkdir name
初始化项目配置文件:npm init
保存自动重启:npm i nodemon -g;nodemon app.js
安装koa:npm i koa
新建启动文件:app.js
导入、实例化、编写中间件、注册、挂载到端口
导入koa:const Koa = require(‘koa’)
导入导出包:

  1. commonJS:require
  2. ES6: import from
  3. AMD
const Koa = require('koa')//导入koa

const app = new Koa()//实例化

// 应用程序对象 中间件

// 发送HTTP KOA 接收HTTP

// 定义中间件,就是定义普通函数
// function test() {
//   console.log('hello,koa')
// }

// 注册
// app.use(test)

// 匿名注册 一
app.use((ctx, next) => {
  // 上下文
  console.log('hello,111')
  const a = next()  // next的返回结果是一个promise
  a.then((res) => {
    console.log(res)
  })
  console.log('hello,222')
})
// 匿名注册 二 async await
app.use(async (ctx, next) => {
  // 上下文
  console.log('hello,111')
  // await对promise求值
  const a = await next()  // next的返回结果是一个promise
  console.log(a)
  console.log('hello,222')
})
app.use((ctx, next) => {
  console.log('hello,333')
  return 'abc'
})

app.listen(3000)

async和await

  1. 表达式求值,不仅仅是promise求值
const a = await 100*100
console.log(a)
->10000
  1. 对资源的操作是异步的:读文件,发送http请求,操作数据库sequelize
    阻塞当前线程
app.use(async (ctx, next) => {
  const axios = require('axios')
  const start = Date.now()
  const res = await axios.get('https://api.douban.com/v2/movie/imdb/tt0111161?apikey=0df993c66c0c636e29ecbb5344252a4a')
  const end = Date.now()
  console.log(end - start)
  console.log(res)
})
->217

koa框架的ctx上下文传值
koa是用中间件在编程

app.use(async (ctx, next) => {
  await next()
  const a = ctx.r
  console.log(a)
})
app.use(async (ctx, next) => {
  const axios = require('axios')
  const res = await axios.get('https://api.douban.com/v2/movie/imdb/tt0111161?apikey=0df993c66c0c636e29ecbb5344252a4a')
  ctx.r = res.data.alt_title
})

三、路由系统的改造

app.use(async (ctx, next) => {
  console.log(ctx.path)
  console.log(ctx.method)
  // 路由
  if (ctx.path === '/' && ctx.method === 'GET') {
    ctx.body = 'hello'
  }
})

koa-router基本用法

// 导入
const Koa = require('koa')
const Router = require('koa-router')
//实例化
const app = new Koa()
const router = new Router()
// 路径 返回
router.get('/', (ctx, next) => {
  ctx.body = { key: 'hello' }
})
// 注册
app.use(router.routes())

app.listen(3000)

根据数据类型,划分主题
API版本:
随着业务的变动,考虑到客户端兼容性,老版本返回‘classic’,新版本返回‘music’,所以服务器要兼容多版本:V1,V2,V3;
客户端发送请求携带版本号:url路径,查询参数,header
分层次:上层调用下层
book.js

const Router = require('koa-router')
const router = new Router()

router.get('/v1/book/latest', (ctx, next) => {
  ctx.body = { key: 'book' }
})

module.exports = router

app.js

const Koa = require('koa')
const book = require('./api/v1/book')

const app = new Koa()

app.use(book.routes())

app.listen(3000)

koa-router自动加载:npm i require-directory

const Koa = require('koa')
const requireDirectory = require('require-directory')
const Router = require('koa-router')

const app = new Koa()

requireDirectory(module, './api', {
  visit: whenLoadModule
})

function whenLoadModule(obj) {
  if (obj instanceof Router) {
    app.use(obj.routes())
  }
}

app.listen(3000)

api路径:path config 配置路径 or 绝对路径
全局变量process的cwd可以找到全局路径:process.cwd()
init.js

const requireDirectory = require('require-directory')
const Router = require('koa-router')

class InitManager {
  // 入口方法
  static initCore(app) {
    InitManager.app = app
    InitManager.initLoadRouters()
  }

  static initLoadRouters() {
    // path config 配置路径 or 绝对路径
    const apiDirectory = `${process.cwd()}/app/api`
    requireDirectory(module, apiDirectory, {
      visit: whenLoadModule
    })

    function whenLoadModule(obj) {
      if (obj instanceof Router) {
        InitManager.app.use(obj.routes())
      }
    }
  }

}

module.exports = InitManager

app.js

const Koa = require('koa')

const InitManager = require('./core/init')

const app = new Koa()
// 全局变量process的cwd可以找到全局路径
process.cwd()
InitManager.initCore(app)

app.listen(3000)

四、异步异常与全局异常处理

传参:
1、路径中的整型参数中传参:’/v1/{param}/book/latest’
在这里插入图片描述

2、后面的查询参数中传参:’/v1/book/latest?param=’
在这里插入图片描述

3、http的header中传参
在这里插入图片描述

4、http的body中传参:koa-bodyparser
在这里插入图片描述

router.post('/v1/:id/classic/latest', (ctx, next) => {
  const path = ctx.params
  const query = ctx.request.query
  const headers = ctx.request.header
  const body = ctx.request.body
})

异常处理:
1、没有发生异常 正确返回结果 or 不需要结果直接执行(undefined)
2、 发生了异常

	// 函数设计
	// 函数内部判断出异常 return false or null
	// 函数内部判断出异常 throw new Error 编程规范
function f1() {
  try {
    f2()
  } catch (error) {
    throw error
  }
}
function f2() {
  try {
    f3()
  } catch (error) {
    throw error
  }
}
function f3() {
  try {
    1 / a
  } catch (error) {
    throw error
  }
  return 'success'
}

异常处理方法

// 机制 监听任何异常
function f1() {
  f2()
}
async function f2() {
  try {
    await f3()
  } catch (error) {
    console.log('error')
  }
}
// 返回结果是promise 用async await

// 全局异常处理 异常 promise async 回调 难
// 不同
function f3() {
  return new Promise((resolve, reject) => {
    setTimeout(function () {
      const r = Math.random()
      if (r < 0.5) {
        reject('error123')
      }
    })
  })
}

f1()

全局异常处理中间件编写
exception.js

const catchError = async (ctx, next) => {
  try {
    await next()
  }
  catch (error) {
    ctx.body = 'dasfasdfasdfas'
  }
}

module.exports = catchError

classic.js

const Router = require('koa-router')
const router = new Router()

router.post('/v1/:id/classic/latest', (ctx, next) => {
  ctx.body = { key: 'classic' }
  throw new Error('服务器错误')
  // AOP 面向切面编程
  // 全局监听到错误
  // 输出一段有意义的提示信息
})

module.exports = router

app.js

const Koa = require('koa')
const parser = require('koa-bodyparser')

const InitManager = require('./core/init')
const catchError = require('./middlewares/exception')

const app = new Koa()
app.use(catchError)
app.use(parser())
// 全局变量process的cwd可以找到全局路径
process.cwd()
InitManager.initCore(app)

app.listen(3000)

程序里捕捉到的error,不应该直接返回到客户端去,因为信息量非常大:名称,原因,堆栈调用信息

处理后返回:

// HTTP Status Code状态码:2xx,4xx,5xx
// message
// error_code 详细,开发者自己定义 10001 20003
// request_url 当前请求的url

错误类型:

// 已知型错误 param int 'abc', 已知
// 已知:可以处理错误,并且明确告诉前端出错了,并改正错误
// try catch 明确 处理

// 未知型错误 程序潜在错误 无意识 根本就不知道他出错了
// 链接数据库 账号密码输错了 未知错误
 // try catch

exception.js

const catchError = async (ctx, next) => {
  try {
    await next()
  }
  catch (error) {
    if (error.errorCode) {
      ctx.body = {
        msg: error.message,
        error_code: error.errorCode,
        request: error.requestUrl
      }
      ctx.status = error.status
    }
  }
}

module.exports = catchError

classic.js

const Router = require('koa-router')
const router = new Router()

router.post('/v1/:id/classic/latest', (ctx, next) => {
  const path = ctx.params
  const query = ctx.request.query
  const headers = ctx.request.header
  const body = ctx.request.body

  if (true) {
    // 动态语言
    const error = new Error('为什么错误')
    error.errorCode = 10001
    error.status = 400
    error.requestUrl = `${ctx.method} ${ctx.path}`
    throw error
  }

  ctx.body = { key: 'classic' }

})

module.exports = router

简化:面向对象思想,构造函数,继承Error,定义HttpException异常基类
http-exception.js

class HttpException extends Error {
  constructor(msg = '服务器异常', errorCode = 10000, code = 400) {
    super() // 基类
    this.errorCode = errorCode
    this.code = code
    this.msg = msg
  }
}

module.exports = { HttpException }

exception.js

catch (error) {
    if (error instanceof HttpException) {
      ctx.body = {
        msg: error.msg,
        error_code: error.errorCode,
        request: `${ctx.method} ${ctx.path}`
      }
      ctx.status = error.code
    }
  }

classic.js

  if (true) {
    const error = new HttpException('为什么错误', 10001, 400)
    throw error
  }

特定异常类
http-exception.js

class HttpException extends Error {
  constructor(msg = '服务器异常', errorCode = 10000, code = 400) {
    super() // 基类
    this.errorCode = errorCode
    this.code = code
    this.msg = msg
  }
}

class ParameterException extends HttpException {
  constructor(msg, errorCode) {
    super()
    this.code = 400
    this.msg = msg || '参数错误'
    this.errorCode = errorCode || 10000
  }
}

module.exports = { HttpException, ParameterException }

classic.js

  if (true) {
    const error = new ParameterException()
    throw error
  }

未知异常
exception.js

const { HttpException } = require("../core/http-exception")

const catchError = async (ctx, next) => {
  try {
    await next()
  }
  catch (error) {
    if (error instanceof HttpException) {
      ctx.body = {
        msg: error.msg,
        error_code: error.errorCode,
        request: `${ctx.method} ${ctx.path}`
      }
      ctx.status = error.code
    }
    else {
      ctx.body = {
        msg: '这是一个未知异常',
        error_code: 999,
        request: `${ctx.method} ${ctx.path}`
      }
      ctx.status = 500
    }
  }
}

module.exports = catchError

五、校验

好用的基础库:lodash、validator
validator.js

const { LinValidator, Rule } = require('../../core/lin-validator')

class PositiveIntegerValidator extends LinValidator {
  constructor() {
    super()  // 子类里面要用this,一定要用super
    this.id = [
      new Rule('isInt', '需要是正整数', { min: 1 })
    ]
  }
}

module.exports = { PositiveIntegerValidator }

classic.js

router.post('/v1/:id/classic/latest', (ctx, next) => {
  const path = ctx.params
  const query = ctx.request.query
  const headers = ctx.request.header
  const body = ctx.request.body

  const v = new PositiveIntegerValidator().validate(ctx)

  ctx.body = { key: 'classic' }

})

全局异常处理:

// 开发环境dev:有必要看到异常信息
// 生产环境prod

config.js

module.exports = {
  // prod 生产环境
  environment: 'dev'  // 开发环境
}

exception.js

 if (global.config.environment === 'dev') {
      throw error
    }

validator不用中间件,每个请求都实例化一次,
因为koa在初始化只执行一次中间件,全局只会有一个validator,重复赋值
中间件,尽量不要以类的形式来组织,中间件一般是函数,用function,但是不会保存变量的状态,
类实例化出来的对象,可以保存变量的状态
在这里插入图片描述
exception.js

catch (error) {
    // 开发环境 生产环境
    const isHttpException = error instanceof HttpException
    const isDev = global.config.environment === 'dev'
    // 开发环境 不是HttpException
    if (isDev && !isHttpException) {
      throw error
    }

    if (isHttpException) {
      ctx.body = {
        msg: error.msg,
        error_code: error.errorCode,
        request: `${ctx.method} ${ctx.path}`
      }
      ctx.status = error.code
    }
    else {
      ctx.body = {
        msg: '这是一个未知异常',
        error_code: 999,
        request: `${ctx.method} ${ctx.path}`
      }
      ctx.status = 500
    }
  }

用户系统设计

通用型、争对小程序
账号、密码、副属信息:名称、手机、邮箱
操作:注册、登录

  // 思维路径
  // 接收参数 LinValidator校验
  // email password1 password2 nickname

数据库

关系型数据库:MySQL、Oracle、Access、Microsoft SQL Server、PostgreSQL
非关系型数据库:Redis(键值对,主要用途做缓存的)、MongoDB(文档型数据库,存储于一个个类似于js对象)

自动生成数据库:npm i sequelize

sequelize 连接数据库,配置一些数据库的参数
重命名:const { sequelize: db } = require('../../core/db')
强制删除表,并重新新建,就可以新增字段,不建议使用,尤其是生产环境,因为会把现有数据删除:
	sequelize.sync({ force: true })

在这里插入图片描述

扩展:一个用户对于所有的小程序或者公众号活各种微信产品,唯一标识:unionID
密码:bcryptjs

// 10 成本 安全性
// salt 明文加密不同
const salt = bcrypt.genSaltSync(10)
const psw = bcrypt.hashSync(v.get('body.password2'), salt)

\app\models\user.js

password: {
    // 扩展 设计模式 观察者模式
    // ES6 Reflect Vue3.0
    type: Sequelize.STRING,
    set(val) {
      // 10 成本 安全性
      // salt 明文加密不同
      const salt = bcrypt.genSaltSync(10)
      const psw = bcrypt.hashSync(val, salt)
      this.setDataValue('password', psw)
    }
  },

token校验

是必须要传入的吗?
登录 多元化 小程序 密码 手机登录
微信 打开小程序 合法 只需要获取account
web 账号+密码 account+secret

可以传,可以不传
查询 分页 1,2,3

class TokenValidator extends LinValidator {
  constructor() {
    super()
    this.account = [
      new Rule('isLength', '不符合账号规则', { min: 4, max: 32 })
    ]
    this.secret = [
      new Rule('isOptional'), // 可传,可不传
      new Rule('isLength', '至少6个字符', { min: 6, max: 128 })
    ]
    // 登录方式 枚举 js没有枚举,模拟

  }
  validateLoginType(vals) {
    if (!vals.body.type) {
      throw new Error('type是必须参数')
    }
    if (!LoginType.isThisType(vals.body.type)) {
      throw new Error('type参数不合法')
    }
  }
}

生成jwt令牌:npm i jsonwebtoken

  // token 检测
  // token 开发者 传递令牌 body header 约定
  // Http 规定 身份验证机制 HttpBasicAuth
  const userToken = basicAuth(ctx.req)
  // koa 封装 node.js
  // ctx.req 是 Node.js 的 request
  // ctx.request 是 koa 的 request
  jwt.verify(userToken.name, global.config.security.secretKey)

API 权限控制

\middlewares\auth.js

constructor(level) {
    // 类上的实例属性
    this.level = level || 1
    // 定义类变量
    Auth.USER = 8
    Auth.ADMIN = 16
  }
  
if (decode.scope < this.level) {
        errMsg = '权限不足'
        throw new global.errs.Forbbiden(errMsg)
      }

\app\api\v1\classic.js

router.get('/latest', new Auth().m, async (ctx, next) => {
  // 权限 token 角色 普通用户 管理员
  // 分级 scope 8 16 new Auth(9).m
  ctx.body = ctx.auth.uid
})
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值