快速开始
本文将从开发者角度,详解如何搭建一个 ArtusX 应用。
环境
- 操作系统:支持 macOS、Linux、Windows(未验证)
- 运行环境:建议选择 LTS 版本,最低要求 18.x
初始化
我们推荐直接使用脚手架。只需几条简单指令,即可快速生成项目
# install init tool
npm i -g @artusx/init
# create app
artusx-init --name webapp --type apps
启动项目:
cd webapp
# install deps
pnpm i
# run the app
pnpm run dev
使用
目录结构
在 ArtusX 中,除 config
外,不对目录进行限定,为了更好的协作,建议通过 MVC
或者 Module
方式组织代码。
Module(推荐使用)
通过 module 方式组织代码,可避免在 import 是出现形如 ../../../service/api
代码,便于快速找到所需文件
.
├── package.json
├── README.md
├── src
│ ├── bootstrap.ts
│ ├── config
│ ├── index.ts
│ ├── module-api
│ │ ├── api.controller.ts
│ │ ├── api.schedule.ts
│ │ ├── api.middleware.ts
│ │ └── api.service.ts
│ └── view
└── tsconfig.json
Model-View-Controller
此处参考 Egg.js 中常用的目录结构来组织代码,历史项目可通过该方式迁移。
.
├── package.json
├── src
│ ├── bootstrap.ts
│ ├── config
│ ├── index.ts
│ ├── controller
│ │ └── home.ts
│ ├── model
│ │ └── user.ts
│ ├── service
│ │ └── user.ts
│ ├── schedule
│ │ └── user.ts
│ └── view
└── tsconfig.json
插件策略
通过插件配置,可指定插件是否启用,也可使用本地插件。
├── plugin.development.ts
└── plugin.ts
默认配置
export default {
artusx: {
enable: true,
package: '@artusx/core',
},
redis: {
enable: false,
package: '@artusx/plugin-redis',
// path: '../../plugins/plugin-redis'
},
};
应用配置
可通过运行时指定 ARTUS_SERVER_ENV=development
来启用不同的配置文件。
├── config.default.ts
├── config.development.ts
└── config.production.ts
默认配置
import path from 'path';
import { ArtusXConfig } from '@artusx/core';
export default () => {
const artusx: ArtusXConfig = {
port: 7001,
static: {
prefix: '/public/',
dir: path.resolve(__dirname, '../public'),
dynamic: true,
preload: false,
buffer: false,
maxFiles: 1000,
},
};
return {
artusx,
};
};
Controller
@arutsx/core
默认集成了 web 常用插件,并提供了常用注解,方便用户快速增加添加处理逻辑。
框架的 Web 能力由 Koa 上层封装实现,故而可以完整支持 Koa 的生态,同时也可以通过 Koa
的上下文扩展来实现更多功能。 在业务开发中,我们需要注意: 基于 Koa
的中间件机制,书写的业务逻辑,相当于一个 Koa 插件,无法向上兼容 ArtusX,故而挂载在 ctx
上的数据可能无法被 ArtusX 插件、中间件获取,如数据需被 ArtusX 插件、中间件消费,请使用
ctx.context.output.data
进行数据传递。
@Controller
import { ArtusXInjectEnum } from '@artusx/utils';
import { Controller, GET, POST, Inject } from '@artusx/core';
import type { ArtusXContext, NunjucksClient } from '@artusx/core';
@Controller()
export default class HomeController {
@Inject(ArtusXInjectEnum.Nunjucks)
nunjucks: NunjucksClient;
@GET('/')
@POST('/')
async home(ctx: ArtusXContext) {
ctx.body = this.nunjucks.render('index.html', { title: 'ArtusX', message: 'Hello ArtusX!' });
}
}
@Headers
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
想要返回的数据。
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) {
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)
import { 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(APIService)
apiService: APIService;
@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
中。
定义数据类型
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,
};
调用示例
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,
};
}
}
Service
业务逻辑,添加 @Injectable
注解即可,可在其中通过 @Inject
注入其他对象使用。
import { Inject, Injectable, ArtusInjectEnum, ArtusApplication } from '@artusx/core';
@Injectable()
export default class APIService {
@Inject(ArtusInjectEnum.Application)
app: ArtusApplication;
async mockApi() {
return {
data: {
name: 'artusx',
},
};
}
}
如何通过 controller 调用?
import { 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(APIService)
apiService: APIService;
@GET('/mockApi')
@ContentType('application/json; charset=utf-8')
async getInfo(ctx: ArtusXContext) {
ctx.body = await this.apiService.mockApi();
}
}
Middleware
类中间件
类中间件为全局中间件,可通过 config.artusx.middlewares
控制执行顺序,否则根据加载顺序执行。
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();
}
}
函数中间件
import { ArtusXContext, ArtusXNext } from '@artusx/core';
export default async function traceTime(_ctx: ArtusXContext, next: ArtusXNext): Promise<void> {
console.time('TraceTime');
await next();
console.timeEnd('TraceTime');
}
函数中间件在 route 中通过 @MV()
使用
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';
}
}
Schedule
定时器支持 cron
语法,并可指定是否启动时运行,通过 enable
控制启用。
import { Schedule } from '@artusx/core';
import type { ArtusXSchedule } from '@artusx/core';
@Schedule({
enable: true,
cron: '30 * * * * *',
runOnInit: true,
})
export default class NotifySchedule implements ArtusXSchedule {
async run() {
console.log('ScheduleTaskClass.run', Date.now());
}
}