cmd新建项目文件夹:mkdir name
初始化项目配置文件:npm init
保存自动重启:npm i nodemon -g;nodemon app.js
安装koa:npm i koa
新建启动文件:app.js
导入、实例化、编写中间件、注册、挂载到端口
导入koa:const Koa = require(‘koa’)
导入导出包:
- commonJS:require
- ES6: import from
- 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
- 表达式求值,不仅仅是promise求值
const a = await 100*100
console.log(a)
->10000
- 对资源的操作是异步的:读文件,发送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
})