项目初始化
// 初始化项目,生成package.json
npm init
复制代码
指定入口文件 main.js
项目的基础搭建
创建 src 工作目录
创建 main.js 主入口文件
在 main.js 中引入 koa
const koa = require('koa')
const app = new Koa()
app.listen(3000, () => {
// 监听成功的回调
console.log('server is running:http://localhost:3000')
})
复制代码
node main.js 后即可通过访问 http://localhost:3000,访问到此项目
项目的基本优化
自动重启
// 热更新,只在开发模式下才会用的到
npm install nodemon -D
复制代码
这时候我们安装的 nodemon 会在 package.json 中的 devDependencies 下
修改 script 选项
"scripts":{
"dev":"nodemon ./src/main.js"
}
复制代码
使用 nodemon 启动,开发过程中的改动会自动重启
配置文件
我们开发的过程中还需要区分环境,开发、正式、测试等
// 安装dotenv
npm install dotenv -S
复制代码
在项目的根目录下创建.env
文件
尽可能早的在项目中引入 dotenv 并进行环境变量的配置
const dotenv = require('dotenv')
dotenv.config()
// 经过了上面的配置,我们在.env文件中所配置的环境变量就已经被加载进process.env中了
// 可以将环境变量导出,在需要用到的时候进行引入
module.exports = process.env
复制代码
这样我们就在项目中配置了环境变量,配置环境变量还有另外一种方式,就是在 package.json 中的 script 中配置执行的命令,并指定环境变量,这样我们就不用新开一个文件在 js 文件中引用了
添加路由
// 这是一个构造函数
const Router = require('koa-router')
const router = new Router({ prefix: '/user' })
router.post('/register', (ctx, next) => {})
复制代码
通过引入 koa 的路由中间件 koa-router,我们可以设置项目的路由,通过在构造函数中传入prefix:'/user'
可以设置路由的前缀,以作为不同功能模块的区分
目录结构的划分
我们在 main.js 中引入了 koa 启动了服务
又在 main.js 中引入了 koa-router 设置了项目的路由
但是随着功能的逐渐增多,项目变大,我们不能把所有的东西都写在 main.js 中,我们需要做功能模块的区分
抽离路由
在 src 目录下新建 router 文件夹,这个文件夹专门存放并管理项目中的路由。
新建好了各个功能模块的路由,我们需要一个index.js
文件来作为路由的总入口文件,它负责引入各个功能模块的路由
const Router = require('koa-router')
const router = new Router()
const fs = require('fs')
// 需要使用nodejs的fs模块,来进行文件的读取和引入
fs.readdirSync(__dirname).forEach((file) => {
// 读取当前目录下的文件['user.route.js','order.route.js']
if (file !== 'index.js') {
const currentFile = require('./' + file)
// 注册路由
router.use(currentFile.routes())
}
})
module.exports = router
这样我们所有的路由都注册在了index.js
中的总路由中,我们只需要在main.js
中注册在 app 上,就可以实现路由的功能
const router = require('./router') // 引入index.js可以不用写
app.use(router.routes())
// 这个是路由做的http允许的请求方法处理,如果不写这条语句,那么在使用别的httpMethod请求时,会抛出500的错误,加上了这一句,在请求方法不当的时候,会进行提示
app.use(router.allowedMethods())
复制代码
抽离 app 服务
我们需要在 src 底下新建一个 app 文件夹专门管理我们的服务,因为有时候我们可能在一个项目中使用多个服务。可能是 express、可能是 koa、也可能是 node 中 http 模块
const koa = require('koa')
const app = new Koa()
const router = require('./router')
app.use(router.routes())
app.use(router.allowedMethods())
// 注册号路由之后将app导出
module.exports = app
复制代码
这样我们将服务抽离出来,在main.js
中进行引入的时候,将 app 服务引入并监听即可
const app = require('./app/index.js')
app.listen(3000, () => {
// 监听成功的回调
console.log('server is running:http://localhost:3000')
})
复制代码
这样main.js
就变的更加简洁了
抽离 controller
我们在 user.route.js 中写下了这样的代码
router.post('/register', (ctx, next) => {})
// (ctx,next)=>{} 这个是用来处理register逻辑的函数,我们可以把它抽离成一个controller,专门用于处理各个访问的逻辑
复制代码
在 src 目录下新建一个文件夹 controller
在 controller 文件夹下新建一个文件,叫做 user.controller.js
在 controller 文件夹下新建一个文件,叫做 order.controller.js
class UserController {
async registerUser() {
// 这里处理register的逻辑
}
async loginUser() {
// 这里处理login的逻辑
}
}
module.exports = new UserController()
复制代码
抽离出来之后在路由文件中引入相应的 controller
抽离 service
我们在 controller 中要进行数据库的操作,我们把操作数据库的这一部分,抽离为 service
class UserService {
// 处理创建用户的service
async createUser() {
// 这个内部封装了数据库的操作
}
// 处理更新用户的service
async updateUser() {}
}
复制代码
抽离数据库定义
sequelize 这个包专门用于项目中处理关系型数据库的操作,它是基于 promise 的
我们需要借助它来对数据库进行操作
const { Sequelize } = require('sequelize')
const { HOST, PORT /*等等需要的配置*/ } = process.env
// 实例化sequelize对象
const seq = new Sequelize(
'要连接的数据库名称',
'数据路的用户名',
'数据库的密码',
{
// options
host: '要连接的数据库的host',
port: '要连接的数据路的端口',
dialect: 'mysql', // 要操作的数据库类型
}
)
// 实例化过后就进行连接
seq
.authenticate()
.then((res) => {
console.log(res, '连接成功的回调')
})
.catch((err) => {
console.log(err, '连接失败的回调')
})
module.exports = seq
复制代码
抽离 model
连接好了数据库之后,我们需要定义数据库表,这时候需要抽离一个 model 层,来定义数据库的表结构
const seq = require('../db/seq')
// 创建User表,表名为user,user中有各项字段
const User = seq.define('user', {
{
userName:{
type:DataTypes.String,// DataTypes是Sequelize中为我们提供的类型,需要引入
allowNull:false, // 是否允许空值,参考Sequelize文档
unique:true,// 是否允许唯一
comment:'字段注释',
},
{
password:{
type:DataTypes.String,
allowNull:false,
//...
}
}
}
})
module.exports = User
复制代码
当我们定义好了 User 的 model 之后,就可以在 user.service.js 中引入并使用它
const User = require('../model/user.model.js')
class UserService {
// 处理创建用户的service
async createUser(userName, password) {
// 这个内部封装了数据库的操作,都是基于promise,需要进行try...catch错误捕获
const res = User.create({ userName, password })
return res.dataValues
}
// 处理更新用户的service
async updateUser() {}
}
module.exports = new UserService()
复制代码
抽离中间件
当我们完成了这一系列的操作之后,就搭建起了一个接口编写的框架。我们可以在每一个 controller 中编写对应的业务处理。但是在我们编写接口的过程中,时常会碰到相同或相似的处理模块,这时候我们为了避免重复冗余的代码,需要把这些相同或相似的功能抽离成中间件。
const validateUserInfo = (ctx, next) => {
// 这里可以填充用户登录或注册时的校验方法
}
const comparePassword = (ctx, next) => {
// 这里可以填充修改密码时,两个密码进行对比的方法
}
module.exports = {
validateUserInfo,
comparePassword,
}
复制代码
抽离错误处理
Sequelize 是基于 promise 的数据库操作工具,我们在进行数据库操作或者日常代码编写的时候要进行错误处理,将错误处理的这一部分抽离出来,也会方便我们排查问题。
module.exports = {
UserValidError = {
code:'10001',
message:'用户校验失败',
result:''
},
UserLoginError = {
code:'10002',
message:'用户登录失败',
result:''
}
}
复制代码
将错误归集起来了之后,我们只需要在捕获到这个错误的时候使用它
// ctx中提供了当前的app,其中有一个emit的方法,可以传递一个事件,后面为该事件需要的参数
ctx.app.emit('error', UserValidError, ctx)
// 在app中使用on作为接收
app.on('error', errHandler)
复制代码
// errHandler
module.exports = (error, ctx) => {
// 这里的error就是UserValidError
// ctx 就是传递过来的ctx上下文
ctx.body = error // 把当前错误返回给前端
}
复制代码
至此我们就完成了项目中的功能拆分,接下来就是在每个模块中填充相应的内容
注册接口的编写
注册的逻辑一般为用户提供用户名密码,传递给后端,后端拿到用户名和密码以后,首先要判断数据库中是否已经存在此用户,如果已经存在了这个用户,就返回提示码并告知前端,此用户已经注册。如果不存在,则对用户传递过来的密码进行加密,然后存储到数据库中。
这里只记录加密接口的步骤
// 使用bcryptjs
const bcrypt = require('bcrypt')
const cryptPassword = async (ctx, next) => {
const { password } = ctx.request.body
const salt = bcrypt.genSaltSync(10) // 加盐
const hash = bcrypt.hashSync(password, salt)
ctx.request.body.password = hash
await next()
}
复制代码
经过此步骤加密之后,我们就可以往下处理注册是逻辑
登录接口的编写
登录的逻辑一般为,用户输入用户名和密码进行登录。我们拿取到用户名和密码之后,要和数据库中的用户名和密码进行比较,如果比较失败,则返回用户失败的结果,否则登录成功,成功之后需要下发 token 以及 cookie 等。
这里只记录密码对比和 token 下发的步骤
// 密码对比
bcrypt.compareSync('当前密码', '用户传递过来的密码') // 如果相同返回true,如果不同返回false
// 下发token 需要用到jsonwebtoken这个库
// npm install jsonwebtoken -S
const jwt = require('jsonwebtoken')
// 从数据库中拿取出数据之后,除了密码以外,将其它的信息都用于token下发,也可以用作userInfo返回
const { password, ...res } = await getUserInfo({ userName })
// 那么这个res就是我们下发token需要用到
const token = jwt.sign(res, '自己设置的加密串', {
expiresIn: '1d' /*token的有效时间*/,
})
ctx.body = {
code: 200,
message: '登录成功',
result: {
token,
},
}
复制代码
这样我们就完成了登录的流程,将 token 下发给用户之后,用户以后的资源请求都需要将 token 携带过来,我们进行验证,如果验证成功,那么可以进行后续的操作,如果验证失败,那么用户就不能获取我们的真实资源。
验证中间件的编写
由于我们下发 token 之后的每一个接口都要通过验证之后才能向下进行,所以我们需要编写一个验证 token 的中间件
const jwt = require('jsonwebtoken')
const auth = (ctx, next) => {
// 这里编写验证token的相关内容
const { authorization } = ctx.request.header
const token = authorization.replace('Bearer ', '')
try {
// 如果通过验证,会把我们先前用来生成token的信息返回,这里也就是除了password之外的其他用户信息
const user = jwt.verify(token, '我们先前设置的加密串')
ctx.state.user = user // 我们把通过验证的用户信息放入state属性下的user中
} catch (error) {
// 如果没有通过验证,那么有几种情况
// error.name === TokenExpiredError
// error.name === JsonWebTokenError
// 详情参考jsonwebtoken这个库的介绍
}
}
复制代码
验证的中间件编写完成之后,我们的每一次需要验证 token 的请求,都会使用到它
数据上传
编写接口的同时我们要处理前端传递过来的数据,那么在 koa 中,数据上传需要用到一个中间件,就是koa-body
const KoaBody = require('koa-body')
app.use(
KoaBody({
// ...options
})
)
// 注册路由
app.use(router.routes())
复制代码
因为经过 koa-body 的处理,前端传递过来的请求数据会挂在ctx.request.body
上,我们在后续的路由处理中,在此处获取并处理即可
koa-body
有很多选项(比如是否支持文件上传等),具体参考手册
静态资源管理
如果想要前端通过浏览器的 uri 访问到本服务的静态资源,那么需要进行静态资源配置
需要使用到koa-static
const koaStatic = require('koa-static')
app.use(koaStatic('静态资源路径,最好借助path模块'))
复制代码
通过了这样的静态资源配置,前端就可以在浏览器上输入 uri 来访问到本服务的静态资源
sequelize 的基本理解
模型 model 时 sequelize 的本质,是数据库中表的抽象,在 sequelize 中是一个类
比如说,我们要创建一个用户表,那么首先需要定义一个 User 类,这个 User 类就是 sequelize 的模型。表中的每一条数据都是一个对象,每一个对象都是这个类的实例。而我们对 User 类的操作,或者是对实例(表中的每一条数据)的操作,都是类似操作 js 对象一样思想。有了这样的认识,可以帮助我们更好的理解 sequelize 的各项操作。
sequelize 文档
增删改查
做完前面的一些基础工作之后,最常见也是最经常写的就是 CRUD 了
####新增接口
第一步:定义路由,遵守 restfull 规范,定义为router.post('/order','中间件1','中间件2')
第二步:在controller
中定义处理该路由的中间件
第三步:在service
中定义写入数据库的方法,如果这一步需要用到新的 model,则先在model
中定义好数据字段
//需要借助sequelize来进行数据库操作
// 先把User模型给引进来
const User = require('../model/user.model.js')
// 新增操作需要在User表中新增一条数据,从类的角度来说,就是创建一个实例
// 假设我们此时是在/src/service/user.service.js
class UserService {
// 创建一个用于处理User model的类
async addUser({ id, userName }) {
// ID,userName是从controller中解析的
// 模型中有一个创建的方法
// 方法一:
const res = await User.create({ id, userName })
// 在没有错误的情况下,执行完毕这个操作,就会在user表中新增一条数据
// 方法二:
// sequelize的model为我们提供了创建实例的方法build
const res = User.build({ id, userName })
// 但是此时的build的方法,仅仅是创建出的一个对象,表示可以将这个对象映射到数据库中的数据,这个对象还并未真正的保存在数据库中,我们应该使用save方法,将其同步
return res.save()
// 执行完这一步才算是真正的同步至了数据库中
// 建议直接使用create方法,具体操作详见sequelize官方文档
}
}
module.exports = new UserService()
复制代码
第四步:注意错误捕获与错误处理
修改接口
第一步:定义路由,定义为router.put('/order/:id','中间件','中间件')
第二步:在controller
中定义处理该路由的中间件
第三步:在 service 中定义修改数据库的方法
// 修改接口同新增接口
// 假设我们此时在/src/service/user.service.js
class UserService {
// 新增用户的接口
async addUser(){}
// 更新用户的接口
async updateUser({id,userName}){
// sequelize中为我们提供的更新方法也有两种
// 方法一:
const res = await User.update({userName},{
where:{
id,
userName
}
})
// 方法二:
const res = await User.create({id,userName})
res.set({
userName:'xxx'
})
return awaut res.save()
}
}
复制代码
第四步:注意错误捕获与错误处理
删除接口
删除首先要确定是使用硬删除,还是软删除。这二者的区别为硬删除为直接从数据库中的记录抹去,软删除为在数据库中增加一个标识字段,该字段标记了就代表删除了,但不是真正意义上的删除。
第一步:定义路由,定义为router.delete('/order/:id','中间件','中间件','中间件')
第二步:在controller
中定义处理该路由的中间件
第三步:在service
中定义删除该数据的方法,此时可以选择硬删除,或者是软删除,详见sequelize文档
查询接口
查询接口的思路同上
评论