共计 9159 个字符,预计需要花费 23 分钟才能阅读完成。
我的项目初始化
// 初始化我的项目,生成 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 文件夹,这个文件夹专门寄存并治理我的项目中的路由。
- 如果须要新增 user 的路由,就新建 user.route.js 文件
如果须要新增 order 的路由,就新建 order.route.js 文件
const Router = require('koa-router') const router = new Router({prefix: '/order'}) router.post('/add', addOrderController) module.exports = 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 模块
- 在 src 下新建 app 目录
- 在 app 目录下新建一个
index.js
文件,这个文件用于编写咱们当初这个我的项目中次要用的服务,例如 koa,其余的服务可新开文件编写。
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
- 在 src 文件夹下创立 service 文件夹
- 在 service 文件夹下创立 user.service.js
参考 前端进阶面试题具体解答
class UserService {
// 解决创立用户的 service
async createUser() {// 这个外部封装了数据库的操作}
// 解决更新用户的 service
async updateUser() {}
}
抽离数据库定义
sequelize这个包专门用于我的项目中解决关系型数据库的操作,它是基于 promise 的
咱们须要借助它来对数据库进行操作
npm install sequelize -S
先装置- 在 src 下新建一个 db 目录用于治理此我的项目须要连贯的数据库
- 在 db 目录下新建一个 seq.js
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 层,来定义数据库的表构造
- 在 src 下新建一个 model 目录
- 在 model 中新建一个 user.model.js,进行如下定义:
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 中编写对应的业务解决。然而在咱们编写接口的过程中,时常会碰到雷同或类似的解决模块,这时候咱们为了防止反复冗余的代码,须要把这些雷同或类似的性能抽离成中间件。
- 在 src 下新建一个文件夹叫做 middleware
- 在 middleware 中新建一个文件叫做 user.middleware.js
const validateUserInfo = (ctx, next) => {// 这里能够填充用户登录或注册时的校验办法}
const comparePassword = (ctx, next) => {// 这里能够填充批改明码时,两个明码进行比照的办法}
module.exports = {
validateUserInfo,
comparePassword,
}
抽离错误处理
Sequelize 是基于 promise 的数据库操作工具,咱们在进行数据库操作或者日常代码编写的时候要进行错误处理,将错误处理的这一部分抽离进去,也会不便咱们排查问题。
- 在 src 下新建一个 constant 问价夹,专门用来寄存代码中须要用到的常量
- 在 constant 文件夹下新建一个 err.type.js 用来存储返回给前端的谬误提醒
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()}
通过此步骤加密之后,咱们就能够往下解决注册是逻辑
- 从 request.body 中取出加密之后的明码
- 存储用户名和明码至数据库
- 向用户返回后果
登录接口的编写
登录的逻辑个别为,用户输出用户名和明码进行登录。咱们拿取到用户名和明码之后,要和数据库中的用户名和明码进行比拟,如果比拟失败,则返回用户失败的后果,否则登录胜利,胜利之后须要下发 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 的中间件
- 在 middleware 这个文件加下创立 auth.middleware.js 文件
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
npm install koa-body -S
装置依赖- 在路由注册之前先注册
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
npm install koa-static -S
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 文档
查问接口
查问接口的思路同上