完善基础框架,对原有APILoader进行接口响应的错误处理

This commit is contained in:
tone
2024-09-25 01:21:23 +08:00
parent a9c67cd013
commit 7acdfc2c68
12 changed files with 398 additions and 0 deletions

17
src/api/GetTest.ts Normal file
View File

@@ -0,0 +1,17 @@
import { API } from "@lib/API/API";
import MountIP, { MountIPRequestData } from "@lib/APIMiddleware/MountIP";
import MountUserAgent, { MountUserAgentRequestDate } from "@lib/APIMiddleware/MountUserAgent";
import Unbind from "@lib/APIMiddleware/Unbind";
import ServerStdResponse from "@lib/ServerResponse/ServerStdResponse";
import { Response } from "express";
class GetTest extends API {
constructor() {
super('GET', '/test', MountIP, MountUserAgent, Unbind);
}
public async onRequset(data: MountIPRequestData | MountUserAgentRequestDate, res: Response): Promise<void> {
this.logger.info(`request ip: ${data._ip} useragent: ${data._userAgent}`)
res.json(ServerStdResponse.OK);
}
}
export default GetTest;

9
src/config.ts Normal file
View File

@@ -0,0 +1,9 @@
const config = {
cors: {
origin: ['http://localhost:5173'],
allowedHeaders: ['Content-Type'],
methods: ['GET', 'POST']
},
API_Port: 8080
};
export default config;

18
src/index.ts Normal file
View File

@@ -0,0 +1,18 @@
import { APILoader } from "@lib/API/APILoader";
import Logger from '@lib/Logger/Logger'
import config from "./config";
import GetTest from "./api/GetTest";
const logger = new Logger('Server')
async function main(): Promise<void> {
logger.info('Starting...');
const apiLoader = new APILoader(config.cors);
// loadAPI
apiLoader.add(GetTest);
await apiLoader.start(config.API_Port);
logger.info('Server started successfully')
}
main().catch((reason) => {
logger.error(`An error occurs in the main function: ${reason}`)
})

28
src/lib/API/API.ts Normal file
View File

@@ -0,0 +1,28 @@
import { Request, Response, NextFunction } from "express";
import Logger from "@lib/Logger/Logger";
interface MiddlewareFunction {
(req: Request, res: Response, next: NextFunction): void;
}
abstract class API {
protected logger: Logger;
public middlewareFunc: Function[] = [];
/**
* @param method API Method
* @param path API Path
* @param func API MiddlewareFunctions
*/
constructor(public method: string, public path: string, ...func: MiddlewareFunction[]) {
this.logger = new Logger('API][' + method + '][' + path);
this.middlewareFunc.push(...func);
}
// to override
public abstract onRequset(data: any, res: Response): Promise<void>;
}
export { API };
export type { MiddlewareFunction };

72
src/lib/API/APILoader.ts Normal file
View File

@@ -0,0 +1,72 @@
import express, { NextFunction, Request, Response } from "express";
import cors, { CorsOptions } from "cors";
import Logger from "@lib/Logger/Logger";
import { API } from "./API";
import ServerStdResponse from "@lib/ServerResponse/ServerStdResponse";
class APILoader {
private app = express();
private logger = new Logger('APILoader');
constructor(corsOptions?: CorsOptions) {
this.logger.info('API service is loading...');
this.app.use(express.json({ limit: '10mb' }));
this.app.use(express.urlencoded({ extended: true }));
this.app.use(cors(corsOptions));
if (corsOptions)
this.logger.info('Cors configuration is complete');
else
this.logger.warn('Cors is not configured');
}
add(api: { new(): API }) {
const instance = new api();
// register middleware
for (let func of instance.middlewareFunc) {
this.app[instance.method.toLowerCase() as keyof express.Application](instance.path, async (req: Request, res: Response, next: NextFunction) => {
try {
await func(req, res, next);
} catch (error) {
this.logger.error(`[${instance.method}][${instance.path}][API Middleware Function: ${func.name}]: ${error}`);
}
});
this.logger.info(`[${instance.method}][${instance.path}][API Middleware Function: ${func.name}] is enabled`);
}
// handle request
this.app[instance.method.toLowerCase() as keyof express.Application](instance.path, async (req: Request, res: Response) => {
let ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress || req.ip;
this.logger.info(`[Request][${instance.method}][${instance.path}] request by ${(ip as string).replace('::ffff:', '')}`);
const data = Object.assign({}, req.query, req.body);
try {
await instance.onRequset(data, res);
} catch (error) {
this.logger.error(`[${instance.method}][${instance.path}] ${error}`);
}
});
this.logger.info(`[${instance.method}][${instance.path}] loaded`);
}
async start(port: number) {
return new Promise((resolve) => {
// handle undefined API
this.app.use((req: Request, res: Response) => {
this.logger.warn(`[Request][${req.method}][${req.url.split('?')[0]}] undefined API`);
res.json(ServerStdResponse.API.NOT_FOUND)
})
// listen port
const server = this.app.listen(port, () => {
this.logger.info(`The API service is listening on port ${port}`);
resolve(undefined);
});
server.on('error', (error: NodeJS.ErrnoException) => {
this.logger.error(`${error.message}`);
this.logger.error(`process killed`);
process.exit(1);
});
})
}
}
export {
APILoader,
}

View File

@@ -0,0 +1,27 @@
import { Request, Response, NextFunction } from "express"
import Logger from "@lib/Logger/Logger";
const logger = new Logger('API', 'Middleware', 'MountIP');
let MountIP = (req: Request, res: Response, next: NextFunction) => {
let ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress || req.ip;
if (ip == undefined || ip.length <= 0) {
logger.warn(`[${req.method}][${req.url.split('?')[0]}] ip resolution was fail`);
} else {
if (typeof ip === 'object')
req.body._ip = ip.join(' ').replace('::ffff:', '');
else
req.body._ip = ip.replace('::ffff:', '');
logger.info(`[${req.method}][${req.url.split('?')[0]}] ip resolution was successful: ${req.body._ip}`);
}
next();
}
export default MountIP;
interface MountIPRequestData {
_ip: string,
[key: string | number | symbol]: any
}
export type { MountIPRequestData };

View File

@@ -0,0 +1,19 @@
import { Request, Response, NextFunction } from "express"
import Logger from "@lib/Logger/Logger";
const logger = new Logger('API', 'Middleware', 'MountUserAgent')
let MountUserAgent = (req: Request, res: Response, next: NextFunction) => {
req.body._userAgent = req.headers['user-agent'];
logger.info(`[${req.method}][${req.url.split('?')[0]}] User agent parsed successfully: ${req.body._userAgent}`);
next();
}
export default MountUserAgent;
interface MountUserAgentRequestDate {
_userAgent: string,
[key: string | number | symbol]: any
}
export type { MountUserAgentRequestDate };

View File

@@ -0,0 +1,9 @@
import { Request, Response, NextFunction } from "express";
import Logger from "@lib/Logger/Logger";
const logger = new Logger('API', 'Middleware', "Unbind");
const Unbind = (req: Request, res: Response, next: NextFunction) => {
let ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress || req.ip;
logger.warn(`API[${req.method}][${req.url.split('?')[0]}] requested an unbound endpoint [${ip}]`);
}
export default Unbind;

46
src/lib/Logger/Logger.ts Normal file
View File

@@ -0,0 +1,46 @@
class Logger {
private fullNamespace: string;
constructor(primaryNamespace: string, ...additionalNamespaces: string[]) {
if (additionalNamespaces.length < 1) {
this.fullNamespace = primaryNamespace;
} else {
this.fullNamespace = [primaryNamespace, ...additionalNamespaces].join('][');
}
}
private getTime(): string {
return new Date().toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' });
}
public info(info: string, ...args: any): void {
args = args.map((arg: any) => {
if (typeof arg === 'object') {
return JSON.stringify(arg);
}
return arg;
})
console.log(`\x1b[32m[${this.getTime()}][INFO][${this.fullNamespace}]${info[0] == '[' ? '' : ' '}${info} ` + args.join(' ') + '\x1b[0m');
}
public warn(info: string, ...args: any): void {
args = args.map((arg: any) => {
if (typeof arg === 'object') {
return JSON.stringify(arg);
}
return arg;
})
console.log(`\x1b[33m[${this.getTime()}][WARN][${this.fullNamespace}]${info[0] == '[' ? '' : ' '}${info} ` + args.join(' ') + '\x1b[0m');
}
public error(info: string, ...args: any): void {
args = args.map((arg: any) => {
if (typeof arg === 'object') {
return JSON.stringify(arg);
}
return arg;
})
console.log(`\x1b[31m[${this.getTime()}][ERROR][${this.fullNamespace}]${info[0] == '[' ? '' : ' '}${info} ` + args.join(' ') + '\x1b[0m');
}
}
export default Logger;

View File

@@ -0,0 +1,18 @@
const ServerStdResponse = {
OK: {
code: 0,
message: 'ok'
},
ERROR: {
code: -1,
message: 'error'
},
API: {
NOT_FOUND: {
code: -1001,
message: 'api not found'
}
}
} as const;
export default ServerStdResponse;