@artusx/core
插件默认集成了 http 应用开发的常用插件,并导出了对应的类型,本章将演示如何使用插件启动一个 http 服务。
packages | Version |
---|---|
@artusx/plugin-koa | (opens in a new tab) |
@artusx/plugin-log4js | (opens in a new tab) |
@artusx/plugin-nunjucks | (opens in a new tab) |
@artusx/plugin-xtransit | (opens in a new tab) |
@artusx/plugin-schedule | (opens in a new tab) |
概览
框架不限定目录结构,可选用 MVC(Model - View - Controller
方式组织代码,也可以参考 module-api
,使用 Module 的方式组织代码。
iTerm
.
├── bootstrap.ts
├── config
│ ├── config.default.ts
│ ├── plugin.development.ts (optional)
│ └── plugin.ts
├── middleware
│ └── LimitRate.ts
│ └── traceTime.ts
├── controller
│ └── home.ts
└── module-api (optional)
│ ├── api.controller.ts
│ └── api.service.ts
└── index.ts
示例
启动文件,配置启动参数。
bootstrap.ts
import path from 'path';
import { Application } from '@artusx/utils';
(async () => {
const app = await Application.start({
root: path.resolve(__dirname),
configDir: 'config',
});
console.log(app.config);
})();
插件配置,由于 @artusx/core
默认集成了部分插件,可通过该配置文件选择性开启或者关闭。
config/plugin.ts
export default {
artusx: {
enable: true,
package: '@artusx/core',
},
// 集成插件,开启关闭无需配置 path/package;否则将优先使用此处设置的 npm 包
xtransit: {
enable: false,
},
};
类中间件,全局开启
middleware/LimitRate.ts
import { ArtusInjectEnum, Inject } from '@artus/core';
import { ArtusXContext, ArtusXNext, Middleware } from '@artusx/core';
import { RateLimiterMemory } from 'rate-limiter-flexible';
const rateLimiterOptions = {
points: 6,
duration: 1,
};
@Middleware({
enable: true,
})
export default class LimitRateMiddleware {
@Inject(ArtusInjectEnum.Config)
config: Record<string, string | number>;
private rateLimiter: RateLimiterMemory;
constructor() {
this.rateLimiter = new RateLimiterMemory(rateLimiterOptions);
}
async use(ctx: ArtusXContext, next: ArtusXNext): Promise<void> {
try {
const rateLimiterRes = await this.rateLimiter.consume(ctx.ip);
ctx.set('Retry-After', `${rateLimiterRes.msBeforeNext / 1000}`);
ctx.set('X-RateLimit-Limit', `${rateLimiterOptions.points}`);
ctx.set('X-RateLimit-Remaining', `${rateLimiterRes.remainingPoints}`);
ctx.set('X-RateLimit-Reset', `${new Date(Date.now() + rateLimiterRes.msBeforeNext)}`);
} catch (rejRes) {
ctx.status = 429;
ctx.body = 'Too Many Requests';
return;
}
await next();
}
}
函数中间件,在 route 中使用。
middleware/traceTime.ts
import { ArtusInjectEnum, Inject } from '@artus/core';
import { ArtusXContext, ArtusXNext, Middleware } from '@artusx/core';
@Middleware({
enable: true,
})
export default class TraceTimeMiddleware {
@Inject(ArtusInjectEnum.Config)
config: Record<string, string | number>;
async use(ctx: ArtusXContext, next: ArtusXNext): Promise<void> {
const { data } = ctx.context.output;
data.traced = true;
console.log('middleware - traceTime', ctx.context);
console.time('trace');
await next();
console.timeEnd('trace');
}
}
插件已导出 koa 相关注解与类型,在 controller 中 import 即可。
controller/home.ts
import { Controller, GET, POST, MW } from '@artusx/core';
import type { ArtusXContext } from '@artusx/core';
import traceTime from '../middleware/traceTime';
@Controller()
export default class HomeController {
@MW([traceTime])
@GET('/can-be-get')
@POST('/post')
async home(ctx: ArtusXContext) {
ctx.body = 'Hello World';
}
}
运行
开发环境,可使用 nodemon 启动,文件变更自动重启应用。
iTerm
npx nodemon src/index.ts
生产环境,通过 tsc 编译后,用 nodejs 启动即可。
iTerm
tsc
node dist/index.js
注解
core 内置部分注解,简化业务使用
@Headers
controller/home.ts
import { Controller, GET, Headers } from '@artusx/core';
import type { ArtusXContext } from '@artusx/core';
@Controller()
export default class HomeController {
@GET('/')
@Headers({
'x-method': 'home-controller',
})
async home(ctx: ArtusXContext) {
ctx.body = 'Hello ArtusX!';
}
}
@StatusCode
使用该注解时候,请勿直接通过 ctx.body = 'Hello ArtusX!';
赋值,请直接 return
想要返回的数据。
controller/home.ts
import { ArtusXInjectEnum } from '@artusx/utils';
import {
ArtusInjectEnum,
ArtusXErrorEnum,
ArtusApplication,
Inject,
Controller,
GET,
StatusCode,
} from '@artusx/core';
import type { ArtusXContext, NunjucksClient } from '@artusx/core';
@Controller()
export default class HomeController {
@Inject(ArtusInjectEnum.Application)
app: ArtusApplication;
@Inject(ArtusXInjectEnum.Nunjucks)
nunjucks: NunjucksClient;
@GET('/')
@StatusCode(209)
async home(ctx: ArtusXContext) {
const mockError = ctx.query.error;
if (mockError) {
this.app.throwException(ArtusXErrorEnum.ARTUSX_UNKNOWN_ERROR);
}
return this.nunjucks.render('index.html', { title: 'ArtusX', message: 'Hello ArtusX!' });
}
}
@ContentType
设置 ContentType,传参为 string
,通过 mine.lookup
匹配正确类型。
https://www.npmjs.com/package/mime-types (opens in a new tab)
controller/api.ts
import { ArtusInjectEnum, Inject, GET, Controller, ContentType } from '@artusx/core';
import type { ArtusXContext } from '@artusx/core';
import APIService from './api.service';
@Controller('/api')
export default class APIController {
@Inject(ArtusInjectEnum.Config)
config: Record<string, string | number>;
@Inject(APIService)
apiService: APIService;
@GET('/')
async home(ctx: ArtusXContext) {
ctx.body = 'api';
}
@GET('/mockApi')
@ContentType('application/json; charset=utf-8')
async getInfo(ctx: ArtusXContext) {
ctx.body = await this.apiService.mockApi();
}
}
@Query / @Params / @Body
在业务开发中,通常需要对请求参数进行验证,此处使用 json-schema 定义参数类型,并使用 ajv 进行校验,校验结果存放在 ctx.context.output.data
中。
定义数据类型
validator.validator.ts
import { JSONSchemaType } from '@artusx/core';
export interface QueryTypes {
foo: string;
bar?: string;
}
export const QueryScheme: JSONSchemaType<QueryTypes> = {
type: 'object',
properties: {
foo: { type: 'string' },
bar: { type: 'string', nullable: true },
},
required: ['foo'],
additionalProperties: false,
};
export interface ParamsTypes {
uuid: string;
}
export const ParamsScheme: JSONSchemaType<ParamsTypes> = {
type: 'object',
properties: {
uuid: { type: 'string', nullable: false },
},
required: ['uuid'],
additionalProperties: false,
};
export interface BodyTypes {
key: number;
}
export const BodyScheme: JSONSchemaType<BodyTypes> = {
type: 'object',
properties: {
key: { type: 'integer', nullable: false },
},
required: ['key'],
additionalProperties: false,
};
调用示例
validator.controller.ts
import { Controller, StatusCode, GET, Query, Params, Body, POST } from '@artusx/core';
import type { ArtusXContext } from '@artusx/core';
import {
QueryTypes,
QueryScheme,
BodyTypes,
BodyScheme,
ParamsTypes,
ParamsScheme,
} from './validator.validator';
@Controller('/validator')
export default class ValidatorController {
@GET('/:uuid')
@POST('/:uuid')
/**
* validator.index.handler
* @description validate query / params / body
* @example:
* - url: /validator/e8b847b9-cb23-4fbf-8e7c-0c4ba72b9629?foo=foo&bar=bar
* - body: { "key": 123456 }
*/
@Query<QueryTypes>(QueryScheme)
@Body<BodyTypes>(BodyScheme)
@Params<ParamsTypes>(ParamsScheme)
@StatusCode(200)
async index(ctx: ArtusXContext): Promise<Object> {
const query = ctx.context.output.data.query;
const params = ctx.context.output.data.params;
const body = ctx.context.output.data.body;
return {
query,
body,
params,
};
}
}