docs: server application api (#842)

* feat: update docs

* fix: improve code
This commit is contained in:
chenos 2022-09-23 09:22:17 +08:00 committed by GitHub
parent 00195fa779
commit 678b6cef21
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 288 additions and 208 deletions

View File

@ -39,10 +39,7 @@ export default {
title: 'Getting started',
'title.zh-CN': '快速开始',
type: 'group',
children: [
'/development/index',
'/development/your-fisrt-plugin'
],
children: ['/development/index', '/development/your-fisrt-plugin'],
},
{
title: 'Extension Guides',
@ -83,10 +80,7 @@ export default {
{
title: 'HTTP API',
type: 'group',
children: [
'/api/http-api/index',
'/api/http-api/rest-api',
],
children: ['/api/http-api/index', '/api/http-api/rest-api'],
},
],
// {
@ -166,7 +160,7 @@ export default {
{
title: '@nocobase/server',
type: 'subMenu',
children: ['/api/server/application', '/api/server/plugin-manager', '/api/server/i18n'],
children: ['/api/server/application', '/api/server/plugin-manager', '/api/server/plugin'],
},
{
title: '@nocobase/database',

View File

@ -1,29 +1,37 @@
# Application
NocoBase 的应用程序类,服务端的应用进程将实例化该类并启动。因为继承自 [Koa](https://koajs.com/),所以也可以直接调用 Koa 实例的方法。
同时因为 Koa 还继承自 [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter),所以也可以通过 `app.on` 监听事件。
基于 [Koa](https://koajs.com/) 实现的 WEB 框架,兼容所有的 Koa 插件。
## 构造函数
### `constructor()`
创建一个应用实例。
**签名**
* `constructor(options: ApplicationOptions)`
创建一个应用实例。
**参数**
| 参数名 | 类型 | 默认值 | 描述 |
| --- | --- | --- | --- |
| `options.database` | `IDatabaseOptions` or `Database` | `{}` | 数据库配置 |
| `options.resourcer` | `ResourcerOptions` | `{}` | 资源路由配置 |
| `options.cors` | `CorsOptions` | `{}` | 跨域配置,参考 [@koa/cors](https://npmjs.com/package/@koa/cors) |
| `options.cors` | [`CorsOptions`](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/koa__cors/index.d.ts#L24) | `{}` | 跨域配置,参考 [@koa/cors](https://npmjs.com/package/@koa/cors) |
| `options.dataWrapping` | `boolean` | `true` | 是否包装响应数据,`true` 则将把通常的 `ctx.body` 包装为 `{ data, meta }` 的结构。 |
| `options.registerActions` | `boolean` | `true` | 是否注册默认的 action |
| `options.i18n` | `I18nOptions` | `{}` | 国际化配置,参考 [i18next](https://www.npmjs.com/package/i18next) |
| `options.registerActions` | `boolean` | `true` | 是否注册默认的 [actions](#) |
| `options.i18n` | `I18nOptions` | `{}` | 国际化配置,参考 [i18next](https://www.i18next.com/overview/api) |
| `options.plugins` | `PluginConfiguration[]` | `[]` | 默认启用的插件配置 |
Type
```ts
interface ApplicationOptions {
}
```
**示例**
```ts
@ -51,31 +59,38 @@ const app = new Application({
### `db`
应用初始化自动创建的数据库实例,通过 `@nocobase/database` 封装的数据库访问类,相关方法参考 [Database](./database)。
数据库实例,相关 API 参考 [Database](/api/database)。
### `resourcer`
应用初始化自动创建的资源路由管理实例,相关方法参考 [Resourcer](./resourcer)。
### `cli`
应用初始化自动创建的命令行工具实例,相关方法参考 [CLI](../cli)。
应用初始化自动创建的资源路由管理实例,相关 API 参考 [Resourcer](/api/resourcer)。
### `acl`
应用初始化自动创建的访问控制表实例,相关方法参考 [ACL](./acl)。
ACL 实例,相关 API 参考 [ACL](/api/acl)。
### `i18n`
应用初始化自动创建的国际化工具实例,相关方法参考 [I18n](./i18n)。
### `appManager`
应用初始化自动创建的应用管理实例,相关方法参考 [AppManager](./app-manager)。
I18next 实例,相关 API 参考 [I18next](https://www.i18next.com/overview/api)。
### `pm`
应用初始化自动创建的插件管理器实例,相关方法参考 [PluginManager](./plugin-manager)。
插件管理器实例,相关 API 参考 [PluginManager](./plugin-manager)。
### `version`
应用版本实例,相关 API 参考 [ApplicationVersion](./application-version)。
### `middleware`
内置的中间件有:
- i18next
- bodyParser
- cors
- dataWrapping
- collection2resource
- restApiMiddleware
### `context`
@ -94,75 +109,42 @@ NocoBase 默认对 context 注入了以下成员,可以在请求处理函数
## 实例方法
### `getVersion()`
获取当前版本。
**签名**
* `getVersion(): string`
**示例**
```ts
console.log(app.getVersion()); // 0.7.4-alpha.7
```
### `plugin()`
向应用中注册插件的快捷方法,等同于 `app.pm.add()`,参考 [PluginManager](./plugin-manager#add)。
### `loadPluginConfig()`
批量注册插件与默认参数。
**签名**
* `loadPluginConfig(pluginsConfigurations: PluginConfiguration[]): void`
**参数**
| 参数名 | 类型 | 默认值 | 描述 |
| --- | --- | --- | --- |
| `pluginsConfigurations[]` | `string` | - | 插件的 npm 包名 |
| `pluginsConfigurations[]` | `[string, any]` | - | 插件的 npm 包名与默认参数 |
### `getPlugin()`
获取已注册的插件实例,等同于 `app.pm.get(name)`,参考 [PluginManager](./plugin-manager#get)。
### `use()`
注册应用级中间件的快捷方法,等同于 `koa.use()`
注册中间件,兼容所有 [Koa 插件](https://www.npmjs.com/search?q=koa)
### `on()`
订阅应用级事件,主要与生命周期相关,等同于 `eventEmitter.on()`。所有可订阅事件参考 [事件](#事件)。
### `collection()`
向数据库注册数据表模型的快捷方法,等同于 `app.db.collection()`,参考 [Database](./database#collection)。
### `resource()`
向资源路由管理器注册资源的快捷方法,等同于 `app.resourcer.define()`,参考 [Resourcer](./resourcer#define)。
### `actions()`
向资源路由管理器注册 action 的快捷方法,等同于 `app.resourcer.registerActions()`,参考 [Resourcer](./resourcer#registerActions)。
### `command()`
向命令行工具注册命令的快捷方法,等同于 `app.cli.command()`,参考 [cli](./cli)。
### `findCommand()`
### `runAsCLI()`
以 CLI 的方式运行。
### `load()`
应用生命周期方法,用于加载所有已注册的插件,等同于 `app.pm.load()`,参考 [PluginManager](./plugin-manager#load)。
加载应用配置。
**签名**
* `async load(): Promise<void>`
### `reload()`
重载应用配置。
### `install()`
初始化安装应用,同步安装插件。
### `upgrade()`
升级应用,同步升级插件。
### `start()`
启动应用,如果配置了监听的端口,将启动监听,之后应用即可接受 HTTP 请求。
@ -179,84 +161,16 @@ console.log(app.getVersion()); // 0.7.4-alpha.7
| `options.listen.port?` | `number` | 13000 | 端口 |
| `options.listen.host?` | `string` | `'localhost'` | 域名 |
**事件**
`start()` 调用会触发两个事件:
* `'beforeStart'`:在应用启动前触发。
* `'afterStart'`:在应用启动后触发。
### `stop()`
停止应用,如果应用正在监听,将停止监听,并关闭数据库连接。
**签名**
* `async stop(options: any): Promise<void>`
| 参数名 | 类型 | 默认值 | 描述 |
| --- | --- | --- | --- |
| `options?` | `any` | - | 将透传至停止事件的参数 |
**事件**
`stop()` 调用会触发两个事件:
* `'beforeStop'`:在应用停止前触发。
* `'afterStop'`:在应用停止后触发。
停止应用,此方法会关闭数据库连接,关闭 HTTP 端口,不会删除数据。
### `destroy()`
销毁并停止应用,包含 `stop()` 的调用。
**事件**
`destroy()` 调用会触发两个事件:
* `'beforeDestroy'`:在应用销毁前触发。
* `'afterDestroy'`:在应用销毁后触发。
插件相关的销毁事件可以基于该事件进行扩展。
### `install()`
应用初始化安装调用接口,用于安装应用的基础数据,包括:
* 同步已通过 `db.collection()``db.import()` 注入的数据库表结构到数据库。
* 调用所有已注册插件实例的 `install()` 方法,执行相应安装过程。
**事件**
* `'beforeInstall'`:在应用安装前触发。
* `'afterInstall'`:在应用安装后触发。
### `upgrade()`
应用调用接口,当更新新版本代码后通过命令行工具调用,用于执行应用的升级过程,包括:
* 数据库迁移工具执行。
**事件**
* `'beforeUpgrade'`:在应用升级前触发。
* `'afterUpgrade'`:在应用升级后触发。
删除应用,此方法会删除应用对应的数据库。
## 事件
应用生命周期会触发相应事件,扩展开发可以基于这些事件进行。
| 事件名称 | 是否异步 | 描述 |
| --- | --- | --- |
| `'beforeInstall'` | 是 | 安装前触发 |
| `'afterInstall'` | 是 | 安装后触发 |
| `'beforeUpgrade'` | 是 | 升级前触发 |
| `'afterUpgrade'` | 是 | 升级后触发 |
| `'beforeStart'` | 是 | 启动前触发 |
| `'afterStart'` | 是 | 启动后触发 |
| `'beforeStop'` | 是 | 停止前触发 |
| `'afterStop'` | 是 | 停止后触发 |
| `'beforeDestroy'` | 是 | 销毁前触发 |
| `'afterDestroy'` | 是 | 销毁后触发 |
**示例**
@ -265,3 +179,20 @@ app.on('beforeStart', async () => {
console.log('app before start');
});
```
### beforeLoadAll
### afterLoadAll
### beforeLoadPlugin
### afterLoadPlugin
### beforeInstallPlugin
### afterInstallPlugin
### beforeInstall
### afterInstall
### beforeUpgrade
### afterUpgrade
### beforeStart
### afterStart
### beforeStop
### afterStop
### beforeDestroy
### afterDestroy

View File

@ -4,30 +4,18 @@
## 实例方法
### `create()`
### `add()`
**签名**
### `get()`
* `add(pluginClass: PluginConstructor, options?: any): Plugin`
### `install()`
**参数**
### `upgrade()`
| 参数名 | 类型 | 默认值 | 描述 |
| --- | --- | --- | --- |
| `pluginClass` | `PluginConstructor` | - | 插件类,参考 [Plugin 类](./plugin) |
### `enable()`
**返回值**
### `disable()`
对应插件类的实例
**示例**
```ts
import Application, { Plugin } from '@nocobase/server';
class MyPlugin extends Plugin {}
const app = new Application();
app.pm.add(MyPlugin, { a: 1 });
```
### `remove()`

View File

@ -0,0 +1,29 @@
# Plugin
## 属性
### `name`
只读
### `options`
插件配置信息
## 实例方法
### `initialize()`
初始化
### `beforeLoad()`
加载前,如事件或类注册
### `load()`
加载配置
### `install()`
插件安装逻辑

View File

@ -0,0 +1,2 @@
# 区块

View File

@ -1 +1,128 @@
# Collection Manager
```tsx
import React from 'react';
import { observer, ISchema, useForm } from '@formily/react';
import {
SchemaComponent,
SchemaComponentProvider,
Form,
Action,
CollectionProvider,
CollectionField,
} from '@nocobase/client';
import 'antd/dist/antd.css';
import { FormItem, Input } from '@formily/antd';
export default observer(() => {
const collection = {
name: 'tests',
fields: [
{
type: 'string',
name: 'title1',
interface: 'input',
uiSchema: {
title: 'Title1',
type: 'string',
'x-component': 'Input',
required: true,
description: 'description1',
} as ISchema,
},
{
type: 'string',
name: 'title2',
interface: 'input',
uiSchema: {
title: 'Title2',
type: 'string',
'x-component': 'Input',
description: 'description',
default: 'ttt',
},
},
{
type: 'string',
name: 'title3',
},
],
};
const schema: ISchema = {
type: 'object',
properties: {
form1: {
type: 'void',
'x-component': 'Form',
properties: {
// 字段 title1 直接使用全局提供的 uiSchema
title1: {
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
default: '111',
},
// 等同于
// title1: {
// type: 'string',
// title: 'Title',
// required: true,
// 'x-component': 'Input',
// 'x-decorator': 'FormItem',
// },
title2: {
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
title: 'Title4', // 覆盖全局已定义的 Title2
required: true, // 扩展的配置参数
description: 'description4',
},
// 等同于
// title2: {
// type: 'string',
// title: 'Title22',
// required: true,
// 'x-component': 'Input',
// 'x-decorator': 'FormItem',
// },
// 字段 title3 没有提供 uiSchema自行处理
title3: {
'x-component': 'Input',
'x-decorator': 'FormItem',
title: 'Title3',
required: true,
},
action1: {
// type: 'void',
'x-component': 'Action',
title: 'Submit',
'x-component-props': {
type: 'primary',
useAction: '{{ useSubmit }}',
},
},
},
},
},
};
const useSubmit = () => {
const form = useForm();
return {
async run() {
form.submit(() => {
console.log(form.values);
});
},
};
};
return (
<SchemaComponentProvider scope={{ useSubmit }} components={{ Action, Form, CollectionField, Input, FormItem }}>
<CollectionProvider collection={collection}>
<SchemaComponent schema={schema} />
</CollectionProvider>
</SchemaComponentProvider>
);
});
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

@ -145,10 +145,13 @@ export default () => {
## 嵌套的 Schema
- `props.children` 嵌套,适用于 void 和 object 类型,例子见 [void 和 object 类型 schema 的嵌套](#void-和-object-类型-schema-的嵌套)
- `<RecursionField />` 嵌套,适用于各种场景,例子见 [array 类型 schema 的嵌套](#array-类型-schema-的嵌套)
- `props.children` 嵌套,适用于 void 和 object 类型的 properties,例子见 [void 和 object 类型 schema 的嵌套](#void-和-object-类型-schema-的嵌套)
- `<RecursionField />` 自定义嵌套,所有类型都适用,例子见 [array 类型 schema 的嵌套](#array-类型-schema-的嵌套)
注意:仅 void 和 object 类型的 schema 可以与 onlyRenderProperties 使用
注意:
- 除了 void 和 object 类型以外的 schema 的 `properties` 无法直接通过 `props.children` 渲染,但是可以使用 `<RecursionField />` 解决嵌套问题
- 仅 void 和 object 类型的 schema 可以与 onlyRenderProperties 使用
```tsx | pure
<RecursionField schema={schema} onlyRenderProperties />
@ -190,7 +193,7 @@ export default () => {
};
```
各类型对比array 和 string 并不会渲染 World 节点
各类型 properties 渲染结果对比
```tsx
import React from 'react';
@ -204,7 +207,7 @@ const schema = {
properties: {
title1: {
type: 'void',
'x-content': 'Void schema',
'x-content': 'Void schema,渲染 properties',
},
void: {
type: 'void',
@ -219,7 +222,7 @@ const schema = {
},
title2: {
type: 'void',
'x-content': 'Object schema',
'x-content': 'Object schema,渲染 properties',
},
object: {
type: 'object',
@ -234,7 +237,7 @@ const schema = {
},
title3: {
type: 'void',
'x-content': 'Array schema',
'x-content': 'Array schema,不渲染 properties',
},
array: {
type: 'array',
@ -249,7 +252,7 @@ const schema = {
},
title4: {
type: 'void',
'x-content': 'String schema',
'x-content': 'String schema,不渲染 properties',
},
string: {
type: 'string',

View File

@ -15,10 +15,10 @@ interface ISchema {
['x-component']?: string;
// 组件属性
['x-component-props']?: any;
// 组件的子节点,简单使用
['x-content']?: any;
// 展示状态,默认为 'visible'
['x-display']?: 'none' | 'hidden' | 'visible';
// 组件的子节点,简单使用
['x-content']?: any;
// children 节点 schema
properties?: Record<string, ISchema>;
@ -27,17 +27,17 @@ interface ISchema {
// 字段联动
['x-reactions']?: SchemaReactions;
// 字段 UI 交互模式,默认为 'editable'
['x-pattern']?: 'editable' | 'disabled' | 'readOnly' | 'readPretty';
['x-pattern']?: 'editable' | 'disabled' | 'readPretty';
// 字段校验
['x-validator']?: FieldValidator;
['x-validator']?: Validator;
// 默认数据
default: ?:any;
// 设计器相关
// 设计器组件(工具栏)
// 设计器组件(工具栏),包括:拖拽移动、插入新节点、修改参数、移除等
['x-designer']?: any;
// 初始化器组件(工具栏)
// 初始化器组件(工具栏),决定当前 schema 内部可以插入什么
['x-initializer']?: any;
}
```
@ -266,7 +266,7 @@ const schema = {
};
```
JSX 等
JSX 等
```tsx | pure
<div className={'form-item'}>
@ -293,7 +293,7 @@ const schema = {
};
```
JSX 等
JSX 等
```tsx | pure
<div className={'form-item'}>
@ -320,7 +320,7 @@ const schema = {
};
```
JSX 等
JSX 等
```tsx | pure
<div className={'form-item'}>

View File

@ -6,7 +6,7 @@ import { applyMixins, AsyncEmitter } from '@nocobase/utils';
import { Command, CommandOptions, ParseOptions } from 'commander';
import { Server } from 'http';
import { i18n, InitOptions } from 'i18next';
import Koa from 'koa';
import Koa, { DefaultContext as KoaDefaultContext, DefaultState as KoaDefaultState } from 'koa';
import { isBoolean } from 'lodash';
import semver from 'semver';
import { createACL } from './acl';
@ -35,14 +35,15 @@ export interface ApplicationOptions {
plugins?: PluginConfiguration[];
}
export interface DefaultState {
export interface DefaultState extends KoaDefaultState {
currentUser?: any;
[key: string]: any;
}
export interface DefaultContext {
export interface DefaultContext extends KoaDefaultContext {
db: Database;
resourcer: Resourcer;
i18n: any;
[key: string]: any;
}
@ -198,6 +199,8 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
this._resourcer = createResourcer(options);
this._cli = new Command('nocobase').usage('[command] [options]');
this._i18n = createI18n(options);
this.context.db = this._db;
this.context.resourcer = this._resourcer;
this._pm = new PluginManager({
app: this,

View File

@ -2,10 +2,10 @@ import cors from '@koa/cors';
import Database from '@nocobase/database';
import Resourcer from '@nocobase/resourcer';
import i18next from 'i18next';
import { DefaultContext, DefaultState } from 'koa';
import bodyParser from 'koa-bodyparser';
import Application, { ApplicationOptions } from './application';
import { dataWrapping } from './middlewares/data-wrapping';
import { i18n } from './middlewares/i18n';
import { table2resource } from './middlewares/table2resource';
export function createI18n(options: ApplicationOptions) {
@ -46,27 +46,15 @@ export function registerMiddlewares(app: Application, options: ApplicationOption
}),
);
app.use<DefaultState, DefaultContext>(async (ctx, next) => {
app.use(async (ctx, next) => {
ctx.getBearerToken = () => {
return ctx.get('Authorization').replace(/^Bearer\s+/gi, '');
};
ctx.db = app.db;
ctx.resourcer = app.resourcer;
const i18n = app.i18n.cloneInstance({ initImmediate: false });
ctx.i18n = i18n;
ctx.t = i18n.t.bind(i18n);
const lng =
ctx.get('X-Locale') ||
(ctx.request.query.locale as string) ||
app.i18n.language ||
ctx.acceptsLanguages().shift() ||
'en-US';
if (lng !== '*' && lng) {
i18n.changeLanguage(lng);
}
await next();
});
app.use(i18n);
if (options.dataWrapping !== false) {
app.use(dataWrapping());
}

View File

@ -0,0 +1,15 @@
export async function i18n(ctx, next) {
const i18n = ctx.app.i18n.cloneInstance({ initImmediate: false });
ctx.i18n = i18n;
ctx.t = i18n.t.bind(i18n);
const lng =
ctx.get('X-Locale') ||
(ctx.request.query.locale as string) ||
ctx.app.i18n.language ||
ctx.acceptsLanguages().shift() ||
'en-US';
if (lng !== '*' && lng) {
i18n.changeLanguage(lng);
}
await next();
}