parameter地址
// Koa接口参数校验与返回值统一处理 将参数校验变成一个路由中间件,正确的话继续执行,错误的话抛出错误异常。
// 在第一个中间件中自定义返回格式这里叫ctx.DATA(ctx上自定义一个对象,对象包含code默认200,msg默认‘成功’,data默认空对象)
// 这里对错误异常做了处理,抛出的是带code码以及与code码对应的msg和错误原因data的对象,此时我们将错误信息赋值给ctx.DATA.data。app中先写一个捕获错误的中间件,这样抛出的错误我们能够捕获到,然后从这里我们给body赋值,这样错误时返回的就是code码和message都有的返回值
// 当成功查找到数据库中的数据时,我们只需要将数据给ctx.DATA.data,那么返回值就是code码为200,msg为成功,data为正确数据的对象了。万一出现了登陆失败,我们也可以改变ctx.DATA.message为登陆失败,改变code码等
/**
* 数据校验
* wiki:https://github.com/node-modules/parameter/blob/master/example.js
* @type {Parameter}
*/
// const { HttpError } = require('../../utils/tool/error')
const util = require('util')
// const ERROR_MSG = require('./errorMsg')
const ERROR_MSG = Object.freeze({
// en
// 0: 'fail',
// 1: 'validation error',
// 200: 'ok',
// 400: 'invalid param',
// 401: 'unauthorized',
// 403: 'forbidden',
// 404: 'not found',
// 500: 'internal server error',
// 503: 'service busy',
// zh-cn
0: '失败',
1: '验证码错误',
200: '成功',
400: '请求出错',
401: '未授权的请求',
403: '禁止:禁止执行访问',
404: '找不到:请检查URL以确保路径正确',
500: '服务器的内部错误',
503: '服务不可用'
})
function CustomError(code, msg) {
Error.call(this, '')
this.code = code
this.msg = msg || ERROR_MSG[code] || 'unknown error'
this.getCodeMsg = function() {
return {
code: this.code,
msg: this.msg
}
}
}
util.inherits(CustomError, Error)
function HttpError(code, msg) {
if ([0, 1, 200, 400, 401, 403, 404, 500, 503].indexOf(code) < 0) {
throw Error('not an invalid http code')
}
CustomError.call(this, code, msg)
}
util.inherits(HttpError, CustomError)
const Parameter = require('parameter')
const parm = new Parameter()
// 自定义校验
parm.addRule('name', function(e, v) {
let sta = /^[a-z]$/.test(v)
return sta || '只能输入一个字母'
})
// 路由校验列表
const ruleList = {
// 登录
'post/api/login': {
mobile: { type: 'string', required: true },
password: { type: 'string', required: true }
},
// 获取配置
'get/api/setting': {
id: { type: 'string', required: true }
},
// 保存配置
'post/api/setting': {
id: { type: 'number', required: true },
title: { type: 'string', required: false },
report_fix_id: { type: 'string', required: false },
fix_url: { type: 'string', required: false },
env: { type: 'string', required: false },
report: { type: 'number', required: false },
report_interval: { type: 'number', required: false }
},
// CICD
'post/api/deploy': {
name: { type: 'string', required: true },
branch: { type: 'string', required: false },
env: { type: 'string', required: false }
},
// 用户详情
'get/api/user-details': {
user_id: { type: 'string', required: true }
}
}
/**
* 校验方法
* @param ctx
* @param next
* @returns {Promise<void>}
*/
const parameter = async (ctx, next) => {
let errors, data
let method = 'get'
if (ctx.request.method === 'GET') {
data = ctx.query
} else {
method = 'post'
data = ctx.request.body
}
console.log(method, data)
try {
let name = ctx.req._parsedUrl.pathname
errors = parm.validate(ruleList[method + name], data)
} catch (e) {
throw new HttpError(0, e.toString())
}
if (errors && errors.length) {
ctx.DATA.data = errors
throw new HttpError(0, '数据校验未通过')
}
await next()
}
module.exports = parameter
router.js
// 数据校验
const router = require('koa-router')()
const parameter = require('../utils/parameter')
// project
router.get('/api/user-details', parameter, userDetail)
router.get('/api/setting', parameter, getSetting)
router.post('/api/setting', parameter, setSetting)
// common
router.post('/api/login', parameter, login)
// devops
router.post('/api/deploy', parameter, checkToken, deploy)
// swagger
router.get('/api/swagger.json', async function (ctx) {
ctx.set('Content-Type', 'application/json')
ctx.body = openapiSpecification
})
// index
router.get('/', index)
module.exports = router
app.js
const Koa = require('koa')
const app = new Koa()
const views = require('koa-views')
const json = require('koa-json')
const favicon = require('koa-favicon')
const koaBody = require('koa-body')
const logger = require('koa-logger')
const colors = require('colors')
const { resolve } = require('path')
const { koaSwagger } = require('koa2-swagger-ui')
const mysql = require('mysql2')
const conf = require('./config')
const index = require('./routes')
// 允许上传文件
app.use(
koaBody({
multipart: true,
formidable: {
maxFileSize: 1000 * 1024 * 1024 // 设置上传文件大小最大限制
}
})
)
// 网站图标
app.use(favicon(resolve(__dirname, './public', 'favicon.ico')))
// 返回美化json
app.use(json())
// koa-logger
app.use(logger())
// 资源文件
app.use(require('koa-static')(resolve(__dirname, './public')))
// 模板引擎
app.use(views(resolve(__dirname, './views'), { map: { html: 'nunjucks' } }))
// sql特殊字符处理
const toEscapeString = val => {
return mysql.escape(val)
}
const toEscapeObject = dat => {
for (let key in dat) {
typeof dat[key] === 'string' && (dat[key] = toEscapeString(dat[key]))
typeof dat[key] === 'object' && toEscapeObject(dat[key])
}
return dat
}
// 加入cookie.get、set及自定义返回格式
app.use(async (ctx, next) => {
ctx.cookie = {
set: (k, v, opt) => {
opt = Object.assign({}, conf.cookieOptions, opt)
return ctx.cookies.set(k, v, opt)
},
get: (k, opt) => {
opt = Object.assign({}, conf.cookieOptions, opt)
return ctx.cookies.get(k, opt)
}
}
let msg = {
0: '失败',
1: '验证码错误',
200: '成功',
400: '请求出错',
401: '未授权的请求',
403: '禁止:禁止执行访问',
404: '找不到:请检查URL以确保路径正确',
500: '服务器的内部错误',
503: '服务不可用'
}
ctx.json = dat => {
!dat.message && (dat.message = msg[dat.code])
return dat
}
// 自定义返回格式
ctx.DATA = {
data: {},
message: '',
code: 200
}
// 状态统一判断
ctx.state = res => {
return !(res && res.length ? res[0] : res)
}
await next()
})
// swagger
app.use(
koaSwagger({
routePrefix: '/swagger', // host at /swagger instead of default /docs
swaggerOptions: {
url: '/api/swagger.json' // example path to json 其实就是之后swagger-jsdoc生成的文档地址
}
})
)
// 错误捕获
app.use((ctx, next) => {
return next().catch(err => {
console.log(err)
let msg = err ? err.msg || err.toString() : 'unknown error'
let code = err ? (err.code >= 0 ? err.code : 500) : 500
ctx.DATA.code = code
ctx.DATA.message = msg
ctx.body = ctx.DATA
ctx.status =
[200, 400, 401, 403, 404, 500, 503].indexOf(code) >= 0 ? code : 200
})
})
// routes
app.use(index.routes(), index.allowedMethods())
app.proxy = true
// koa error-handling 服务端、http错误
app.on('error', (err, ctx) => {
console.error('server error', err, ctx)
})
module.exports = app
controller层的接口文件
const resData = {
os_type_data,
ip_data,
browser_data,
channel_group_data,
client_id_group_data,
sortArrFormat,
userActions
}
// 正常返回写法,一个个的定义很麻烦
// const successData = {
// code: 0,
// data: resData,
// status: '请求成功'
// }
// ctx.body = successData
// 直接把结果赋值,code码和message不用管
ctx.DATA.data = resData
ctx.body = ctx.json(ctx.DATA)