restframework-express
Version:
ES6新特性,使用class来定义API接口,集成认证,权限,序列化,版本,视图,频率,过滤等公共能,插拔式设计模式
194 lines (168 loc) • 4.61 kB
JavaScript
// import {NextFunction, Request, Response} from "express";
const {APIException, Throttled} = require("../exceptions");
const HttpMethodNotAllowed = require("../exceptions/index").HttpMethodNotAllowed
class APIView {
httpMethodNames = ["get", "post", "put", "patch", 'delete', "head", "options", "trace"]
// 权限认证列表
permissionClasses = []
authenticationClasses = []
throttleClasses = []
/**
* 不允许的请求方法
* @param req {Request}
* @param res {Response}
* @param next {NextFunction}
*/
httpRequestNotAllowed(req, res, next) {
throw new HttpMethodNotAllowed(`'${req.method}'方法不被允许`)
}
/**
* 发生错误的请求
* @param exc {Error | APIException}
* @param res {Response}
* @returns {*}
*/
handlerException(exc, res) {
if (exc instanceof APIException) {
return res.send({detail: exc.message})
}
throw exc
}
getAuthenticators() {
return this.authenticationClasses.map(m => {
return new m()
})
}
/**
*
* @param req {Request}
* @private
*/
_notAuthenticated(req) {
Reflect.set(req, "user", null)
Reflect.set(req, "auth", null)
}
/**
* 用户认证
* @param req {Request}
*/
performAuthentication(req) {
for (let authenticator of this.getAuthenticators()) {
let userAuthTuple;
try {
userAuthTuple = authenticator.authenticate(req) || null
} catch (e) {
if (e instanceof APIException) {
this._notAuthenticated(req)
}
throw e
}
if (userAuthTuple !== null) {
Reflect.set(req, "user", userAuthTuple[0] || null)
Reflect.set(req, "auth", userAuthTuple[1] || null)
return
}
}
this._notAuthenticated(req)
}
getPermissions() {
return this.permissionClasses.map(p => {
return new p()
})
}
/**
* 校验用户权限
* @param req
*/
checkPermissions(req) {
for (let permission of this.getPermissions()) {
permission.hasPermission(req)
}
}
/**
* 获取当前视图定义的频限组件
* @returns {*[]}
*/
getThrottles() {
return this.throttleClasses.map(t => {
return new t()
})
}
/**
* 频率限制异常
* @param req
* @param wait
*/
throttled(req, wait) {
throw new Throttled(wait)
}
/**
* 请求频率限制验证
* @param req {Request}
*/
checkThrottles(req) {
let throttleDurations = []
for (let throttle of this.getThrottles()) {
if (!throttle.allowRequest(req, this)) {
throttleDurations.push(throttle.wait())
}
}
if (throttleDurations.length > 0) {
let durations = throttleDurations.filter(d => {
if (d !== null) {
return d
}
})
let duration = Math.floor(Math.max(...durations) * 100) / 100
this.throttled(req, duration)
}
}
/**
* 初始化请求
* @param req {Request}
*/
initial(req) {
this.performAuthentication(req)
this.checkPermissions(req)
this.checkThrottles(req)
}
/**
* 请求分发
* @param req {Request}
* @param res {Response}
* @param next {NextFunction}
* @returns {*}
*/
dispatch(req, res, next) {
let httpMethod = req.method.toLocaleLowerCase();
let response;
try {
this.initial(req)
let handle;
if (this.httpMethodNames.indexOf(httpMethod) > -1 &&
Reflect.has(this, httpMethod)) {
handle = Reflect.get(this, httpMethod)
} else {
handle = this.httpRequestNotAllowed(req, res, next)
}
response = handle(req, res, next)
} catch (exc) {
response = this.handlerException(exc, res)
}
return response
}
/**
* 视图方入口函数
* @returns {function(*=, *=, *=): *}
*/
static asView() {
let self = new this()
function view(req, res, next) {
return self.dispatch(req, res, next)
}
return view
}
}
module.exports = {
APIView
}