前言:作为一个前端,如果想要学习一些后端的技术,但是又不想话太多成本,koa框架是一个不错的选择。本文将从零开始使用koa框架搭建一个完整后端服务,本文对网上的文章多有借鉴,对参考文章会贴在末尾,但是可能会有缺失,缺失的作者可在评论补充或者私聊我。文章还不完整,后面还会添加登录效验,redis缓存之类的内容。文章仅供参考,有自己的想法可以自己多实现或在评论区与大家交流。
koa官方文档
1 . koa-generator脚手架搭建项目
1.1 安装脚手架
npm install -g koa-generator
1.2 创建项目
koa2 -e project(项目名称) (-e代表使用ejs模板)
1.3 nodemon热更新 packge.json参考
"scripts": {
"start": "nodemon bin/www",
"dev": "./node_modules/.bin/nodemon bin/www",
"prd": "pm2 start bin/www",
"test": "echo \"Error: no test specified\" && exit 1"
},
2 . 连接mysql
2.1 安装mysql
npm install --save mysql
2.2 数据库设置
2.2.1 在根目录下创建config/default.js文件,配置数据库的相关内容
const database = {
host: 'localhost', // 数据库地址
port: '3306', // 数据库端口号
user: 'root', // 数据库登录用户名
password: 'wdnmddyy', // 数据库登录密码
database: 'database_01' // 数据库名称
}
module.exports = { database }
2.2.2 根目录下创建db/mysql.js文件,对数据库连接封装
const mysql = require('mysql')
const { database } = require('../config/default')
const pool = mysql.createPool({
host: database.host,
port: database.port,
user: database.user,
password: database.password,
database: database.database,
connectionLimit: 10
})
const query = function (sql, values) {
return new Promise((resolve, reject) => {
pool.getConnection(function (err, connection) {
if (err) {
// reject(err)
// console.error(err, '数据库连接失败')
resolve({
code: 500,
msg: `数据库连接失败:${err}`
})
} else {
connection.query(sql, values, (err, results) => {
if (err) {
// reject(err)
resolve({
code: 400,
msg: '语句错误:' + err
})
} else {
resolve({
code: 200,
data: results
})
connection.release() // 连接池释放
}
})
}
})
})
}
module.exports = {
query,
escape: mysql.escape // escape用于防止xss注入,后面会单独说明
}
2.3 使用
// 获取列表
router.post('/list', async (ctx, next) => {
const sql = 'select * from database_01.users'
const res = await query(sql)
ctx.body = res
})
// 注册
router.post('/register', async (ctx, next) => {
// 参数校验,后面说明
const { data, error } = await validator(ctx, registerRules)
// console.log(data, error)
if (error) return
const { name, password } = data
// ''是字符串,如果是int或者boolean类型则不需要
const sql = `insert into database_01.users (name, password) values ('${name}', '${password}')`
const res = await query(sql)
// 反馈给客户端
ctx.body = {
code: res.code,
msg: res.msg || '注册成功'
}
})
3 . 处理sql注入与xss攻击
如果先不关心这一块的可以先跳过,如果感兴趣请先阅读以下文章:
第五章:nodejs koa2 mysql redis 全栈开发–安全(sql注入,xss攻击)
3.1 处理sql注入的说明,对于koa2框架使用中间件是框架推荐的设计模式
对于上诉文章的xss案例,经测试使用’'将字符串括起来也能防止xss攻击。
3.2 处理xss攻击,需要依赖xss库,并且通过封装一个中间件来统一处理入参
3.2.1 根目录下创建middlewares/xss.js
const xss = require('xss') // 需要 npm install xss -S
const XssException = require('../exceptions/xssException')
const xssHandler = async (ctx, next) => {
try {
const body = ctx.request.body
for (const key in body) {
if (typeof body[key] === 'string') {
body[key] = xss(body[key])
}
}
// 一定要添加await
await next()
} catch (error) {
// console.error(error)
throw (new XssException())
}
}
module.exports = xssHandler
3.2.2 在app.js引入中间件并且使用
const xssHandler = require('./middlewares/xss')
app.use(xssHandler)
4 .错误处理
4.1 middlewares/exceptions.js中间件处理Error
/**
* catch error and handle error
*/
const ParamException = require('../exceptions/paramException')
const XssException = require('../exceptions/xssException')
const catchError = async (ctx, next) => {
try {
// 一定要添加await
await next()
} catch (error) {
// 错误返回客户端
// 自定义异常
if (error instanceof ParamException) {
ctx.body = error
return
}
if (error instanceof XssException) {
ctx.body = error
return
}
// 默认处理
ctx.body = {
code: 400,
errorCode: 10000,
msg: '服务内部错误'
}
}
}
module.exports = catchError
4.2 app.js文件注入并且注释掉脚手架自带的错误处理
// const onerror = require('koa-onerror') 脚手架自带
const catchError = require('./middlewares/exception')
app.use(catchError)
// error-handling 脚手架自带
// app.on('error', (err, ctx) => {
// console.error('server error', err, ctx)
// })
4.3 自定义异常
我们可以通过自定义异常来对Error进行扩展。如上图,在根目录创建exceptions文件夹对所有自定义异常进行管理,并且如4.1代码中可以在错误处理中间件对该异常进行特殊的处理。
4.3.1 自定义异常封装。为了符合面向对象的思想,我们封装一个类对Error进行继承
class XssException extends Error {
constructor(msg = 'xss解析参数错误', code = 400, errorCode = 10000) {
super()
this.msg = msg
this.code = code
this.errorCode = errorCode
}
}
module.exports = XssException
4.3.2 使用
router.post('/list', async (ctx, next) => {
throw (new ParamException('wdnmd'))
})
5 . 参数校验
对于参数校验,可以自己封装一个中间件来进行处理,文章这里推荐使用async-validator进行处理。因为前端的element-ui和antd都是通过该库来进行数据校验。
async-validator github地址
5.1 封装validator.js:在根目录下创建utils/validator.js
// import Schema from 'async-validator'
const Schema = require('async-validator/dist-node').default
/**
* 请求参数校验
* 参考 https://cloud.tencent.com/developer/article/1988905
* @param ctx
* @param rules
* @returns
*/
const validate = async (ctx, rules) => {
const validator = new Schema(rules)
let data = null
switch (ctx.method) {
case 'GET':
data = ctx.query
break
case 'POST':
data = ctx.request.body
break
case 'PUT':
data = ctx.request.body
break
case 'DELETE':
data = ctx.query
break
default:
data = ctx.request.body
}
return await validator
.validate(data)
.then(() => {
return {
data,
error: false
}
})
.catch((err) => {
// 统一在这里将异常返回给客户端,在业务代码里面就不需要再处理,直接return就行
ctx.body = {
code: 400,
msg: err.errors.map(r => r.message).join(';')
}
return {
data: {},
error: true
}
})
}
module.exports = validate
5.2 将项目的routes结构进行改造,使用controller对所有的校验规则进行管理
5.3 register参数校验示例
module.exports = {
// 写法与element-ui的表单规则相同
score: [
{
type: 'number',
required: true
},
{
validator: (rule, value, callback) => {
if (value === '23232') {
callback(new Error('重复'))
}
callback()
}
}
],
count: {
type: 'number',
min: 8,
required: false,
message: 'count type error'
}
}
5.4 接口示例
router.post('/register', async (ctx, next) => {
const { data, error } = await validator(ctx, registerRules)
// console.log(data, error)
// 如果validator返回error为true则不需要继续处理,这里validator已经将错误返回给客户端了。
if (error) return
const { name, password } = data
const sql = `insert into database_01.users (name, password) values ('${name}', '${password}')`
const res = await query(sql)
ctx.body = {
code: res.code,
msg: res.msg || '注册成功'
}
})
6 . 参考文章:
6.1 Koa2之koa-generator
6.2 从零一起学koa2(8)—连接MySQL和简单操作
6.3 第五章:nodejs koa2 mysql redis 全栈开发–安全(sql注入,xss攻击)
6.4 koa2异常处理_如何优雅的在 koa 中处理错误
6.5 手把手搭建koa2后端服务器-请求参数与数据校验
7. 源码