import { fastify, FastifyError, FastifyInstance, FastifyReply, FastifyRequest } from 'fastify' import { IncomingMessage, Server, ServerResponse } from 'http' import { RouterMap } from 'decorators/router' import helmet from '@fastify/helmet' import logger from 'logger/logger' import * as dotenv from 'dotenv' const NODE_ENV = process.env.NODE_ENV || 'development' const zReqParserPlugin = require('plugins/zReqParser') const zTokenParserPlugin = require('plugins/zTokenParser') const apiAuthPlugin = require('plugins/apiauth') const fs = require('fs') const join = require('path').join const env = process.env ;(() => { let path switch (process.env.NODE_ENV) { case 'test': path = `${__dirname}/../.env.development` break case 'production': path = `${__dirname}/../.env.production` break default: path = `${__dirname}/../.env.development` } dotenv.config({ path: path, debug: NODE_ENV === 'development' }) })() export class ApiServer { server: FastifyInstance public constructor() { this.server = fastify({ logger: true, trustProxy: true }) this.registerPlugins() } private registerPlugins() { this.server.register(require('@fastify/formbody')) this.server.register(zReqParserPlugin) this.server.register(helmet, { hidePoweredBy: false }) this.server.register(zTokenParserPlugin) this.server.register(apiAuthPlugin, { secret: process.env.API_TOKEN_SECRET, expiresIn: process.env.API_TOKEN_EXPIRESIN, }) if (process.env.NODE_ENV !== 'production') { this.server.register(require('@fastify/cors'), {}) } } private registerRouter() { logger.log('register api routers') let self = this for (let [controller, config] of RouterMap.decoratedRouters) { for (let data of config.data) { logger.info( 'add router', data.method || 'all', data.path, `${data.target.constructor.name}.${controller.name}()`, ) // @ts-ignore self.server[data.method || 'all']( data.path, { preValidation: async function (request: FastifyRequest, reply: FastifyReply) { request.roles = config.roles await this.apiAuth(request, reply) }, }, controller, ) } } } /** * 加载所有的controller */ initControllers() { logger.info('Bootstrap controllers...') const controllers = join(__dirname, './controllers') fs.readdirSync(controllers) .filter((file: string) => ~file.search(/^[^.].*\.(ts|js)$/)) .forEach((file: any) => { // logger.log(file); return require(join(controllers, file)) }) } private setErrHandler() { this.server.setNotFoundHandler(function ( request: any, reply: { send: (arg0: { errcode: number; errmsg: string }) => void }, ) { reply.send({ errcode: 404, errmsg: 'page not found' }) }) this.server.setErrorHandler(function (error: FastifyError, request: FastifyRequest, reply: FastifyReply) { let statusCode = (error && error.statusCode) || 100 if (statusCode >= 500) { logger.error(error) } else if (statusCode >= 400) { logger.info(error) } else { logger.error(error) } reply.code(200).send({ errcode: statusCode, errmsg: error ? error.message : 'unknown error', }) }) } /** * 格式化接口返回数据, 统一封装成如下格式 * { * code: 0, * msg?: '', * data: any * } * @private */ private setFormatSend() { this.server.addHook('preSerialization', async (request: FastifyRequest, reply: FastifyReply, payload) => { reply.header('X-Powered-By', 'PHP/5.4.16') // @ts-ignore if (!payload.errcode) { payload = { errcode: 0, data: payload, } } return payload }) } public async start() { let self = this return new Promise(async (resolve, reject) => { self.initControllers() self.registerRouter() self.setErrHandler() self.setFormatSend() this.server.listen({ port: +env.API_PORT, host: env.API_HOST }, (err: any, address: any) => { if (err) { logger.log(err) process.exit(0) } resolve && resolve(address) }) }) } }