一、koa基础
1 koa简介
koa
是基于node
环境的后端开发框架
安装:
1 | //前提先安装node.js |
- bug
1 | //问题(终端): |
技术:
- Node.js
- Node.js不能重复安装 若计算机上已有老版本 安装新版本之前要把老版本先卸载 或者 用nvm管理两个不同的版本
- Node.js 的本质是让JS 可以脱离浏览器运行 而非是为了web开发 web开发只是其能力的一个方面
- Node.js的能力与应用
- 脱离浏览器运行JS
- NodeJS Stream(前端工程化基础)——开源的、框架级别的产品
- 服务端API
- 作为中间层 ———— 中大型项目中才有中间层
- Node.js 对ES6-10支持情况
- 目前Node.js 对ES6-10的一些特性是不支持的:1、import from导入方式不支持 需要用require 2、decorator装饰器 不支持 3、class类的属性不支持 必须用this.x = x 设置属性 (相对其他语言没有属性这一说 必须在构造函数中设置属性)
- npm:Node.js中的一个工具包
- koa:基于Node.js 专业开发web的框架
- koa特点:洋葱圈模型 精简 使用时需要二次开发,否则会很难用
2 koa使用基础及流程
2.1 入口文件
在项目根目录下新建入口文件 一般为app.js (文件名自定) 在入口文件中导入koa、new koa、设置端口
- 一般称koa对象为 应用程序对象
- 以下3行代码即可启动koa框架
- 前端发送请求地址:localhost:3000(本地时)
1 | const koa = requrie('koa') |
2.2 koa中间件
理解koa中间件:
- koa中间件本质就是函数,当前端向服务端发送请求时,由这些函数接受请求、处理并返回结果
- koa中间件是按代码顺序执行的,在前面的中间件就先执行,需要执行后面的中间件就在前面的中间件中调用next()函数
注册中间件:
- 通过
app.use()
方法,传入匿名函数,将该函数注册为中间件 - koa中间件返回的总是一个promise对象
- async await是标配, 确保返回的一定是promise对象,维持洋葱模型
- 参数:
- ctx: 上下文
- next: next()函数用于调用下一个函数(简单的指中间件代码顺序上的下一个中间件)
- 通过
1 | //app.js |
2.3 koa-router
安装
1
npm install koa-router
使用
1
2
3
4
5
6
7
8
9const Router = require('koa-router')
const router = new Router()
// 路由传递两个参数:第一个:路由地址 第二个:匿名函数,即满足方法和路由要求后的处理函数,就是中间件
router.get('/classic/latest', async (ctx, next) => {
ctx.body = {key : 'chuckie'}
})
app.use(router.routes())//将所有路由的匿名函数注册为中间件理解koa-router:
- 是对处理网络请求中间件的封装,根据不同的请求方法、请求地址,通过匿名函数处理并返回结果
- 定义后需要注册成中间件:
app.use(router.routes())
3 koa获取请求参数
1 | router.get('/v1/:id/classic/latest', async (ctx, next) => { |
路劲参数获取
- koa中定义:
'/v1/:id/classic/latest'
- 其中
/:id
即为路劲中传来的参数
- 其中
- 获取:
const path = ctx.params
- koa中定义:
?param
参数- koa中不用定义
- 前端请求样式:
/v1/classic/latest?param=maxthon
- 获取:
const query = ctx.request.query
headers
参数- 获取:
const headers = ctx.request.header
- 获取:
body
参数前提:安装
require-directory
第三方库1 安装:
npm i require-directory
2
app.js
文件中注册:1
2
3
4
5
6
7
8const Koa = require('koa')
const parser = require('koa-bodyparser')
const app = new Koa()
app.use(parser()) //该库返回结果本质是一个中间价,所以需要注册
app.listen(3000)
获取:
const body = ctx.request.body
二、项目框架
1 全局异常处理中间件
全局异常处理原理:
- 步骤一中的
catchError
中间件,注册在最前面,当后面的中间件都执行完后如果有异常抛出都会在这里被捕捉到
1.1 目录结构
- middlewares
- exception.js 全局异常处理中间件
- core
- http-exception.js http错误类
- app.js 注册全局异常处理中间件
- config
- config.js 配置信息-环境变量的配置
1.2 使用前提
自定义http错误类
- 参考http错误类源码
1
const {HttpException} = require('../core/http-exception')
1.3 源码
在根目录下新建
middlewares
文件夹,文件夹下新建exception.js
文件- 返回给前端的内容包括
- msg: 错误信息
- error_code: 自定义错误码
- request: 发生错误的请求地址
- status: http状态码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33const {HttpException} = require('../core/http-exception')
//全局错误处理中间件
const catchError = async (ctx, next) => {
try {
await next()
} catch (error) {
//判断为开发环境则抛出异常,便于终端查看错误原因------环境变量判断-------
if(global.config.environment === 'dev'){
throw error
}
//判断为已知错误,抛出的错误是封装的http错误类的实例
if(error instanceof HttpException){
//已知错误的处理
ctx.body = {
msg: error.msg, //错误描述
error_code: error.errorCode, //自定义错误码
request: `${ctx.method} ${ctx.path}` //发送错误的请求地址
}
ctx.status = error.code //http状态码
}else{
//未知错误的处理
ctx.body = {
msg: '服务器内部错误 未知错误',
error_code: 99999,
request: `${ctx.method} ${ctx.path}`
}
ctx.status = 500
}
}
}
module.exports = catchError- 返回给前端的内容包括
app.js
中注册中间件1
2
3
4
5
6
7
8const Koa = require('koa')
const catchError = require('./middlewares/exception')
const app = new Koa()
app.use(catchError)//注册中间件
app.listen(3000)
2 http错误类
应用场景:
- 每次有异常或错误抛出时,直接实例化http错误类即可,更加方便,抛出的错误和异常也更加规范
2.1 目录结构
- core
- http-exception.js
2.2 源码
- 根据具体业务的要求可以继续添加特定的错误类
- 使用时,导入并实例化错误类,然后抛出错误即可
1 | //统一封装的http错误类 |
3 koa路由框架
3.1 目录结构
api版本管理:
- 通过v1、v2、v3、、、、、方式表示不同版本的路由
- URL也通过
/v1/,,,
、/v2/,,,,
来区分不同版本
- app
- api
- v1 版本1的路由
- user.js 相关用户的路由(案例)
- v2 版本2的路由
- v1 版本1的路由
- models
- user.js 相关用户的操作数据库的数据模型(案例)
- validators
- validator.js 定义参数校验类
- api
3.2 使用前提
安装
koa-router
1
npm i koa-router
参数校验器
数据模型操作数据库
3.3 源码
路由核心步骤:
- 1 参数校验 ,依赖参数校验器
- 2 对数据库数据进行增删改查,依赖实例化的数据模型(sequelize)
- 3 将对数据库的操作结果整理后返回前端
1 | const Router = require('koa-router') |
4 校验器(参数校验)
封装了LinValidator校验类,方便参数的校验
LinValidator
校验类整合了第三方库validator.js
LinValidator
校验类使用教程查看->LinValidator使用教程
LinValidator
中Rule
参数中第一个参数(校验函数)可查看validator.js官方文档
4.1 目录结构
- core
- lin-validator.js 封装的LinValidator类
- utils.js
- http-exception.js 封装的http错误类
- app
- validators
- validator.js 继承LinValidator类,自定义具体业务的校验类
- validators
4.2 使用前提
安装
validator
1
npm install validator
安装
jsonwebtoken
1
npm i jsonwebtoken
安装
lodash
- lodash官方文档
- 便于获取http请求中的参数,具体使用参考LinValidator使用教程
1
npm i lodash
自定义参数错误http错误类
- 参考http错误类源码
1
const {ParameterException} = require('./http-exception')
4.3 源码
4.3.1 lin-validator.js
1 | //lin-validator.js |
4.3.2 utils.js
1 | const jwt = require('jsonwebtoken')//第三方库 ,需要安装 |
4.3.3 http-exception.js
1 | //统一封装的http错误类 |
5 Sequelize+MySQL
关于MySQL的一些使用可以查看本站分类:tools/MySQL文章
使用Sequelize (一个基于 promise 的 Node.js ORM)
5.1 目录结构
调用关系:
- api/v1/user.js中使用实例化的sequelize实现对数据库的操作,依赖app/models/user.js
- app/models/user.js中定义数据表的字段,以及操作数据的业务流程,依赖core/db.js
- core/db.js 中建立数据库连接,依赖config/config.js
- config/config.js中配置了建立数据库连接相应的信息
- core
- db.js 建立数据库连接
- config
- config.js 配置信息-数据库基本信息
- app
- models 数据模型
- user.js 通过模型在数据库自动生成数据表
- api
- v1
- user.js 对数据库中数据进行增删改查
- v1
- models 数据模型
5.2 使用前提
安装
sequelize
1
npm i sequelize
安装驱动
1
2
3
4
5
6# 选择以下之一:
$ npm install --save pg pg-hstore # Postgres
$ npm install --save mysql2 //使用mysql时安装
$ npm install --save mariadb
$ npm install --save sqlite3
$ npm install --save tedious # Microsoft SQL Server
5.3 源码
5.3.1 config.js
1 | module.exports = { |
5.3.2 db.js
- 更多参数可以查看—->官方API
- 注意:
timezone: '+08:00'
- 一定为’+08:00’,才能以北京的时间来记录所有的时间
1 | const { Model } = require('sequelize') |
5.3.3 app/models/user.js
模型功能:
- 定义数据库中表单各字段
- 自定义相关数据库操作的函数(也包括一部分业务逻辑)
1 | const bcrypt = require('bcrypt')//密码加密库 |
5.3.4 app/api/v1/user.js
- 在路由中通过实例化的sequelize数据模型对数据进行增删改查,下面例子为插入数据库操作
- 增删改查语法请查看:sequelize官方文档
1 | const Router = require('koa-router') |
6 注册及登录模块
此部分源码只列出了入口部分,依赖的其他源码可查看本项目github上的源码
6.1 目录结构
- app
- api
- v1
- user.js 用户注册(邮箱注册方式)
- token.js 用户登录(包括小程序、web等多种登录方式,本质是身份验证并返回token)
- v1
- validators
- validator.js 参数校验(注册和登录参数的校验)
- models
- user.js 操作数据库的用户模型(操作数据库的相关业务逻辑写在这里)
- lib
- enum.js 枚举,定义一些常量(用户类型、用户权限)
- services
- wx.js 相关微信小程序登录的业务逻辑(app/models/user.js用户模型的上一层)
- api
- core
- utils.js 封装生成token的函数
- middlewares
- auth.js token令牌验证
6.2 用户注册
这里的注册只针对于邮箱注册
注册流程:
- 参数校验 (校验器源码:app/validators/validator.js - RegisterValidater)
- 参数要求:
- nickname :用户昵称
- password : 用户密码
- email :用户邮箱,唯一标识,不可重复
- 参数要求:
- 写入数据库 (调用User模型:app/models/user.js)
- 返回前端操作结果
源码:
1 | //app/api/v1/user.js |
6.3 用户登录
登录的本质是验证身份合法性并获取token
登录包括多种方式:
- 小程序用户登录
- web邮箱登录
登录路由源码:
1 | //app/api/v1/token.js |
6.3.1 web邮箱登录
登录流程:
需要先调用注册接口注册
- 参数校验 (校验器源码:app/validators/validator.js - TokenValidator)
- 参数要求:
- account: 账户即为邮箱
- secret: 用户密码
- type: 登录方式(101)
- 参数要求:
- 身份验证 (调用User模型的方法: app/models/user.js)
- 数据库中查找该用户是否存在
- 用户名与密码是否匹配 (调用加密库
bcryp
t对加密的密码比对)
- 生成token并返回前端 (调用:core/utils.js - generateToken)
- 在token中保存用户id:uid 和用户权限角色:scope
6.3.2 小程序登录
登录流程:
不需要注册,直接在小程序端发来js_code即可
- 参数校验 (校验器源码:app/validators/validator.js - TokenValidator)
- 参数要求:
- account: 账户即为js_code
- secret: 不用传
- type: 登录方式(100)
- 参数要求:
- 获取openid (源码:app/services/wx.js)
- 向微信发送请求获取openid,相当于微信帮忙做了用户身份验证,若返回openid则用户身份合法
- 写入数据库 (源码:app/models/user.js)
- 通过openid在数据中查找用户,若没有查到则写入数据库(相当于注册)
- 生成token并返回前端 (调用:core/utils.js - generateToken)
6.4 token校验
校验流程:
- 参数校验 (校验器源码:app/validators/validator.js -NotEmptyValidator)
- 参数:token
- 验证token是否有效 (源码:middlewares/auth.js)
- 返回前端验证结果
验证意义:调用的jsonwebtoken
库验证的token, 只有token有效时(没过期,正确)才返回正确,否则返回false
6.5 用户权限验证
使用
basic-auth
库,解析前端通过base64加密的token
验证包括:
- 是否有token,token是否过期、是否合法
- 要求前端以
http-basic-auth
方式给后端传递token
- 要求前端以
- 用户角色是否有权限(普通用户、管理员、超级管理员)
源码:
1 | //middlewares/auth.js |
使用:
- 在具体路由中加入这个中间件,先作一层校验,一定是加在业务逻辑中间件前面
new Auth(LoginAuth.ADMIN).m
- LoginAuth.ADMIN: 传入可以访问的权限限制 (更多等级可以查看源码:app/lib/enum.js)
1 | const Router = require('koa-router') |
6.5.1 前端以basic-auth
方式给后端传递token
用户在访问接口时需要进行用户权限验证,其中就包括token的验证,因此需要前端每次在请求接口时携带上token,本项目统一规定以
basic-auth
的方式在header中传给后端。关于前端如何在header中以basic-auth
的方式传递token给后端,下面有详细讲解
1 安装
js-base64
1
npm i js-base64
2 对token进行base64加密
1
2
3
4
5
6//给token进行base64加密
encode(){
const token = wx.getStorageSync('token')
const base64 = Base64.encode(token+":")
return 'Basic '+base64
}3 将base64加密的token通过
basic-auth
传递给后端1
2
3header:{
Authorization: encode() //将加密的token传递在header中传递给前端
},案例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28import {Base64} from 'js-base64'
//小程序端以`basic-auth`方式携带token向后端发请求
wx.request({
url: 'http://localhost:3000/v1/classic/latest',
method: "GET",
header:{
Authorization: encode() //将加密的token传递在header中传递给前端
},
success: (res) => {
console.log(res)
}
})
//web端以`basic-auth`方式携带token向后端发请求
axios.get('http://localhost:3000/v1/classic/latest',{
headers:{
Authorization: encode() //将加密的token传递在header中传递给前端
},
}).then((res)=>{
console.log(res)
})
//给token进行base64加密
encode(){
const token = wx.getStorageSync('token')
const base64 = Base64.encode(token+":")
return 'Basic '+base64
}
三、辅助库
1 密码加密处理
安装
bcrypt
1
npm i bcrypt
使用
1
2
3
4
5
6
7
8
9
10const bcrypt = require('bcrypt')
//加密
const salt = bcrypt.genSaltSync(10)//传入数字越大则密码加密越难,成本消耗更大,一般设置为10
const psw = bcrypt.hashSync('123456',salt)//psw即为‘123456’加密码后的密码
//密码校验
//plainPassword是原密码,user.password是加密后的密码
//若原密码与加密密码是一致的则返回true
const res = bcrypt.compareSync(plainPassword, user.password)
2 生成令牌
安装
jsonwebtoken
1
npm i jsonwebtoken
使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14const jwt = require('jsonwebtoken')//第三方库 ,需要安装
//生成令牌
const generateToken = function (uid, scope) {
const secretKey = global.config.security.secretKey
const expiresIn = global.config.security.expiresIn
const token = jwt.sign({
uid,//用户id
scope //用户权限
}, secretKey, {
expiresIn: expiresIn
})
return token
}
3 Sequelize
Sequelize使用的详细教程请查看:Sequelize官方文档
以下部分主要总结了
sequelize
一些常用功能的使用方法
3.1 in查询
转入:数组(记录的属性)、 返回:数组(记录)
1 | //记录中有id属性, ids是一个数组,数组内容为多个id号 |
3.2 查询结果排除特定属性
定义在Model上
使用场景:实例化的对象查询的数据都需要排除某些属性
定义排除的属性
1
2
3
4
5
6
7
8
9
10
11
12//下面定义了所有查询结果都排除'updated_at', 'deleted_at', 'created_at'属性
const sequelize = new Sequelize(....., {
define: {
scopes: {//自定义查询语句,
bh : {//bh为自定义名称
attributes: {//查询时调用scope('bh'),则可以在查询时排除以下字段,即查询结果不包含一下字段
exclude : ['updated_at', 'deleted_at', 'created_at']
}
}
}
}
})使用
1
2//使用时在实例对象上加上`scope('bh')` bh就是上面自定义的名称
Movie.scope('bh').findAll(....)
3.3 不包括属性的某个值
1 | // type为属性名, 400是属性的某一个值, type允许的值可以有:100,200,300,400 |
3.4 事务(数据一致性)
事务,确保数据的一致性(两个操作保持一致,要么都操作,要么都没有操作)
1 | //下面是确保了删除和-1操作能一致进行 |
3.5 删除操作
destory关键字
1 | //favor为查询返回的一条记录的实例化对象 |
3.6 属性+1-1
decrement: -1
increment: +1
1 | //art为查询返回的一条记录的实例化对象 fav_nums是记录的一个属性 |
3.7 排序
1 | HotBook.findAll({ |