feat: update docs
This commit is contained in:
parent
c465f0c427
commit
15cbad30b4
@ -1,4 +1,4 @@
|
|||||||
# ACL Role
|
# ACLRole
|
||||||
|
|
||||||
ACLRole,ACL 系统中的用户角色类。在 ACL 系统中,通常使用 `acl.define` 定义角色。
|
ACLRole,ACL 系统中的用户角色类。在 ACL 系统中,通常使用 `acl.define` 定义角色。
|
||||||
|
|
||||||
|
@ -1,8 +1,29 @@
|
|||||||
# ACL
|
# ACL
|
||||||
|
|
||||||
|
## 概览
|
||||||
|
|
||||||
ACL 为 Nocobase 中的权限控制模块。在 ACL 中注册角色、资源以及配置相应权限之后,即可对角色进行权限判断。
|
ACL 为 Nocobase 中的权限控制模块。在 ACL 中注册角色、资源以及配置相应权限之后,即可对角色进行权限判断。
|
||||||
|
|
||||||
## 概念解释
|
### 基本使用
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const { ACL } = require('@nocobase/acl');
|
||||||
|
|
||||||
|
const acl = new ACL();
|
||||||
|
|
||||||
|
// 定义一个名称为 member 的角色
|
||||||
|
const memberRole = acl.define({
|
||||||
|
role: 'member',
|
||||||
|
});
|
||||||
|
|
||||||
|
// 使 member 角色拥有 posts 资源的 list 权限
|
||||||
|
memberRole.grantAction('posts:list');
|
||||||
|
|
||||||
|
acl.can('member', 'posts:list'); // true
|
||||||
|
acl.can('member', 'posts:edit'); // null
|
||||||
|
```
|
||||||
|
|
||||||
|
### 概念解释
|
||||||
|
|
||||||
* 角色 (`ACLRole`):权限判断的对象
|
* 角色 (`ACLRole`):权限判断的对象
|
||||||
* 资源 (`ACLResource`):在 Nocobase ACL 中,资源通常对应一个数据库表,概念上可类比为 Restful API 中的 Resource。
|
* 资源 (`ACLResource`):在 Nocobase ACL 中,资源通常对应一个数据库表,概念上可类比为 Restful API 中的 Resource。
|
||||||
|
@ -1,20 +1,27 @@
|
|||||||
# 内置常用资源操作
|
# 内置常用资源操作
|
||||||
|
|
||||||
|
## 概览
|
||||||
|
|
||||||
针对常用的 CRUD 等数据资源的操作,NocoBase 内置了对应操作方法,并通过数据表资源自动映射相关的操作。
|
针对常用的 CRUD 等数据资源的操作,NocoBase 内置了对应操作方法,并通过数据表资源自动映射相关的操作。
|
||||||
|
|
||||||
所有的操作方法都是注册在 resourcer 实例上,也是标准兼容 Koa 的中间件函数(`(ctx, next) => Promise<void>`)。操作的参数由路由解析后附加在 `ctx.action` 对象上,后续参数相关介绍均基于此对象。
|
```javascript
|
||||||
|
import { Application } from "@nocobase/server";
|
||||||
|
|
||||||
通常情况下无需直接调用内置的 action 方法,在需要扩展默认操作行为时,可以在自定义的操作方法内调用默认方法。
|
const app = new Application({
|
||||||
|
database: {
|
||||||
|
dialect: 'sqlite',
|
||||||
|
storage: './db.sqlite',
|
||||||
|
},
|
||||||
|
registerActions: true // 注册内置资源操作,默认为 True
|
||||||
|
});
|
||||||
|
|
||||||
## 包结构
|
|
||||||
|
|
||||||
可通过以下方式引入相关实体:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import actions from '@nocobase/actions';
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 单一数据资源操作
|
|
||||||
|
内置的操作方法都是注册在 `application` 中的 `resourcer` 实例上。
|
||||||
|
通常情况下无需直接调用内置的 action 方法,在需要扩展默认操作行为时,可以在自定义的操作方法内调用默认方法。
|
||||||
|
|
||||||
|
## 资源操作
|
||||||
|
|
||||||
### `list()`
|
### `list()`
|
||||||
|
|
||||||
|
@ -1,13 +1,40 @@
|
|||||||
# Collection
|
# Collection
|
||||||
|
|
||||||
数据表结构管理类。
|
## 概览
|
||||||
|
|
||||||
大部分接口通常不会直接由开发者调用,除非进行较底层的扩展开发。
|
`Collection` 用于定义系统中的数据模型,如模型名称、字段、索引、关联等信息。
|
||||||
|
一般通过 `Database` 实例的 `collection` 方法作为代理入口调用。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const { Database } = require('@nocobase/database')
|
||||||
|
|
||||||
|
// 创建数据库实例
|
||||||
|
const db = new Database({...});
|
||||||
|
|
||||||
|
// 定义数据模型
|
||||||
|
db.collection({
|
||||||
|
name: 'users',
|
||||||
|
// 定义模型字段
|
||||||
|
fields: [
|
||||||
|
// 标量字段
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
|
||||||
|
// 关联字段
|
||||||
|
{
|
||||||
|
name: 'profile',
|
||||||
|
type: 'hasOne' // 'hasMany', 'belongsTo', 'belongsToMany'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
更多字段类型请参考 [Fields](/api/database/field.md)。
|
||||||
|
|
||||||
## 构造函数
|
## 构造函数
|
||||||
|
|
||||||
通常不会直接使用,主要通过 `Database` 实例的 `collection` 方法作为代理入口调用。
|
|
||||||
|
|
||||||
**签名**
|
**签名**
|
||||||
|
|
||||||
* `constructor(options: CollectionOptions, context: CollectionContext)`
|
* `constructor(options: CollectionOptions, context: CollectionContext)`
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
# Field
|
# Field
|
||||||
|
|
||||||
|
## 概览
|
||||||
|
|
||||||
数据表字段管理类(抽象类)。同时是所有字段类型的基类,其他任意字段类型均通过继承该类来实现。
|
数据表字段管理类(抽象类)。同时是所有字段类型的基类,其他任意字段类型均通过继承该类来实现。
|
||||||
|
|
||||||
|
如何自定义字段可参考[扩展字段类型](/development/guide/collections-fields#扩展字段类型)
|
||||||
|
|
||||||
## 构造函数
|
## 构造函数
|
||||||
|
|
||||||
通常不会直接由开发者调用,主要通过 `db.collection({ fields: [] })` 方法作为代理入口调用。
|
通常不会直接由开发者调用,主要通过 `db.collection({ fields: [] })` 方法作为代理入口调用。
|
||||||
|
@ -1,23 +1,106 @@
|
|||||||
# Database
|
# Database
|
||||||
|
|
||||||
NocoBase 内置的数据库访问类,通过封装 [Sequelize](https://sequelize.org/) 提供了更加简单的数据库访问接口和统一化的 JSON 数据库表配置方式,同时也提供了扩展字段类型和查询操作符的能力。
|
## 概览
|
||||||
|
|
||||||
Database 类继承自 EventEmitter,可以通过 `db.on('event', callback)` 监听数据库事件,以及 `db.off('event', callback)` 移除监听。
|
Database 是 Nocobase 提供的数据库交互工具,为无代码、低代码应用提供了非常方便的数据库交互功能。目前支持的数据库为:
|
||||||
|
|
||||||
## 包结构
|
* SQLite 3.8.8+
|
||||||
|
* MySQL 8.0.17+
|
||||||
|
* PostgreSQL 10.0+
|
||||||
|
|
||||||
可通过以下方式引入相关实体:
|
|
||||||
|
|
||||||
```ts
|
### 连接数据库
|
||||||
import Database, {
|
|
||||||
Field,
|
在 `Database` 构造函数中,可以通过传入 `options` 参数来配置数据库连接。
|
||||||
Collection,
|
|
||||||
Repository,
|
```javascript
|
||||||
RelationRepository,
|
const { Database } = require('@nocobase/database');
|
||||||
extend
|
|
||||||
} from '@nocobase/database';
|
// SQLite 数据库配置参数
|
||||||
|
const database = new Database({
|
||||||
|
dialect: 'sqlite',
|
||||||
|
storage: 'path/to/database.sqlite'
|
||||||
|
})
|
||||||
|
|
||||||
|
// MySQL \ PostgreSQL 数据库配置参数
|
||||||
|
const database = new Database({
|
||||||
|
dialect: /* 'postgres' 或者 'mysql' */,
|
||||||
|
database: 'database',
|
||||||
|
username: 'username',
|
||||||
|
password: 'password',
|
||||||
|
host: 'localhost',
|
||||||
|
port: 'port'
|
||||||
|
})
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
详细的配置参数请参考 [构造函数](#构造函数)。
|
||||||
|
|
||||||
|
### 数据模型定义
|
||||||
|
|
||||||
|
`Database` 通过 `Collection` 定义数据库结构,一个 `Collection` 对象代表了数据库中的一张表。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 定义 Collection
|
||||||
|
const UserCollection = database.collection({
|
||||||
|
name: 'users',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'age',
|
||||||
|
type: 'integer',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
数据库结构定义完成之后,可使用 `sync()` 方法来同步数据库结构。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
await database.sync();
|
||||||
|
```
|
||||||
|
|
||||||
|
更加详细的 `Collection` 使用方法请参考 [Collection](/api/database/collection.md)。
|
||||||
|
|
||||||
|
### 数据读写
|
||||||
|
|
||||||
|
`Database` 通过 `Repository` 对数据进行操作。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
|
||||||
|
const UserRepository = UserCollection.repository();
|
||||||
|
|
||||||
|
// 创建
|
||||||
|
await UserRepository.create({
|
||||||
|
name: '张三',
|
||||||
|
age: 18,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 查询
|
||||||
|
const user = await UserRepository.findOne({
|
||||||
|
filter: {
|
||||||
|
name: '张三',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 修改
|
||||||
|
await UserRepository.update({
|
||||||
|
values: {
|
||||||
|
age: 20,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
await UserRepository.destroy(user.id);
|
||||||
|
```
|
||||||
|
|
||||||
|
更加详细的数据 CRUD 使用方法请参考 [Repository](/api/database/repository.md)。
|
||||||
|
|
||||||
|
|
||||||
## 构造函数
|
## 构造函数
|
||||||
|
|
||||||
**签名**
|
**签名**
|
||||||
@ -28,7 +111,6 @@ import Database, {
|
|||||||
|
|
||||||
**参数**
|
**参数**
|
||||||
|
|
||||||
`options` 参数与 [Sequelize 的构造参数](https://sequelize.org/api/v6/class/src/sequelize.js~sequelize#instance-constructor-constructor)一致的部分会透传至 Sequelize,同时 NocoBase 也会使用一些额外的参数:
|
|
||||||
|
|
||||||
| 参数名 | 类型 | 默认值 | 描述 |
|
| 参数名 | 类型 | 默认值 | 描述 |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
@ -44,46 +126,6 @@ import Database, {
|
|||||||
| `options.tablePrefix?` | `string` | `''` | NocoBase 扩展,表名前缀 |
|
| `options.tablePrefix?` | `string` | `''` | NocoBase 扩展,表名前缀 |
|
||||||
| `options.migrator?` | `UmzugOptions` | `{}` | NocoBase 扩展,迁移管理器相关参数,参考 [Umzug](https://github.com/sequelize/umzug/blob/main/src/types.ts#L15) 实现 |
|
| `options.migrator?` | `UmzugOptions` | `{}` | NocoBase 扩展,迁移管理器相关参数,参考 [Umzug](https://github.com/sequelize/umzug/blob/main/src/types.ts#L15) 实现 |
|
||||||
|
|
||||||
**示例**
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import Database from '@nocobase/database';
|
|
||||||
|
|
||||||
const app = new Database({
|
|
||||||
dialect: 'mysql',
|
|
||||||
host: 'localhost',
|
|
||||||
port: 3306,
|
|
||||||
username: 'root',
|
|
||||||
password: '123456',
|
|
||||||
database: 'test',
|
|
||||||
tablePrefix: 'my_'
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## 实例成员
|
|
||||||
|
|
||||||
### `sequelize`
|
|
||||||
|
|
||||||
初始化后的 Sequelize 实例,在需要使用 sequelize 底层方法时可以调用,相关信息可以直接参考 sequelize 的文档。
|
|
||||||
|
|
||||||
### `options`
|
|
||||||
|
|
||||||
初始化的配置参数,包含了 Sequelize 的配置参数和 NocoBase 的额外配置参数。
|
|
||||||
|
|
||||||
### `version`
|
|
||||||
|
|
||||||
连接的数据库的版本信息对象,可通过 `await db.version.satisfies(<sem_ver>)` 检查是否满足特定数据库版本要求。
|
|
||||||
|
|
||||||
**示例**
|
|
||||||
|
|
||||||
```ts
|
|
||||||
const r = await this.db.version.satisfies({
|
|
||||||
mysql: '>=8.0.17',
|
|
||||||
sqlite: '3.x',
|
|
||||||
postgres: '>=10',
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## 迁移相关方法
|
## 迁移相关方法
|
||||||
|
|
||||||
### `addMigration()`
|
### `addMigration()`
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# Filter Operators
|
# Filter Operators
|
||||||
|
|
||||||
用于 Repository 的 find、findOne、findAndCount、count 等 API 的 filter 参数里。如:
|
用于 Repository 的 find、findOne、findAndCount、count 等 API 的 filter 参数中:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
const repository = db.getRepository('books');
|
const repository = db.getRepository('books');
|
||||||
@ -14,9 +14,7 @@ repository.find({
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
相当于 Sequelize Where 查询的 [Op](https://sequelize.org/docs/v6/core-concepts/model-querying-basics/#operators) 对象。
|
为了支持 JSON 化,NocoBase 中将查询运算符以 $ 为前缀的字符串标识。
|
||||||
|
|
||||||
为了支持 JSON 化,NocoBase 中将查询运算符转换为以 $ 为前缀的字符串标识。
|
|
||||||
|
|
||||||
另外,NocoBase 也提供了扩展运算符的 API,详见 [`db.registerOperators()`](../database#registeroperators)。
|
另外,NocoBase 也提供了扩展运算符的 API,详见 [`db.registerOperators()`](../database#registeroperators)。
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
## BelongsToRepository
|
## BelongsToRepository
|
||||||
|
|
||||||
`BelongsToRepository` 是用于处理 `BelongsTo` 关系的 `Repository`,它提供了一些便捷的方法来处理 `BelongsTo` 关系。其接口与 [HasOneRepository](./has-one-repository.md) 一致。
|
其接口与 [HasOneRepository](./has-one-repository.md) 一致。
|
||||||
|
`BelongsToRepository` 是用于处理 `BelongsTo` 关系的 `Repository`,它提供了一些便捷的方法来处理 `BelongsTo` 关系。
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
# HasOneRepository
|
# HasOneRepository
|
||||||
|
## 概览
|
||||||
|
|
||||||
`HasOneRepository` 为 `HasOne` 类型的关联 Repository。
|
`HasOneRepository` 为 `HasOne` 类型的关联 Repository。
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
@ -19,8 +21,14 @@ const user = await User.repository.create({
|
|||||||
values: { name: 'u1' },
|
values: { name: 'u1' },
|
||||||
});
|
});
|
||||||
|
|
||||||
// 创建 HasOneRepository 实例
|
|
||||||
const userProfileRepository = new HasOneRepository(User, 'profile', user.get('id'));
|
// 获取到关联 Repository
|
||||||
|
const userProfileRepository = User.repository.relation('profile').of(user.get('id'));
|
||||||
|
|
||||||
|
// 也可直接初始化
|
||||||
|
new HasOneRepository(User, 'profile', user.get('id'));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -1,6 +1,331 @@
|
|||||||
# Repository
|
# Repository
|
||||||
|
|
||||||
数据表数据仓库管理类。大部分基于数据表的数据存取等操作均通过该类实现。
|
## 概览
|
||||||
|
|
||||||
|
在一个给定的 `Collection` 对象上,可以获取到它的 `Repository` 对象来对数据表进行读写操作。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const { UserCollection } = require("./collections");
|
||||||
|
|
||||||
|
const UserRepository = UserCollection.repository;
|
||||||
|
|
||||||
|
const user = await UserRepository.findOne({
|
||||||
|
filter: {
|
||||||
|
id: 1
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
user.name = "new name";
|
||||||
|
await user.save();
|
||||||
|
```
|
||||||
|
|
||||||
|
### 查询
|
||||||
|
|
||||||
|
#### 基础查询
|
||||||
|
|
||||||
|
在 `Repository` 对象上,调用 `find*` 相关方法,可执行查询操作,查询方法都支持传入 `filter` 参数,用于过滤数据。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// SELECT * FROM users WHERE id = 1
|
||||||
|
userRepository.find({
|
||||||
|
filter: {
|
||||||
|
id: 1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
```
|
||||||
|
#### 操作符
|
||||||
|
|
||||||
|
`Repository` 中的 `filter` 参数,还提供了多种操作符,执行更加多样的查询操作。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// SELECT * FROM users WHERE age > 18
|
||||||
|
userRepository.find({
|
||||||
|
filter: {
|
||||||
|
age: {
|
||||||
|
$gt: 18
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// SELECT * FROM users WHERE age > 18 OR name LIKE '%张%'
|
||||||
|
userRepository.find({
|
||||||
|
filter: {
|
||||||
|
$or: [
|
||||||
|
{ age: { $gt: 18 } },
|
||||||
|
{ name: { $like: "%张%" } }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
操作符的更多详细信息请参考 [Filter Operators](/api/database/operators)。
|
||||||
|
|
||||||
|
#### 字段控制
|
||||||
|
|
||||||
|
在查询操作时,通过 `fields`, `except`, `appends` 参数可以控制输出字段。
|
||||||
|
|
||||||
|
* `fields`: 指定输出字段
|
||||||
|
* `except`: 排除输出字段
|
||||||
|
* `appends`: 追加输出关联字段
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 获取的结果只包含 id 和 name 字段
|
||||||
|
userRepository.find({
|
||||||
|
fields: ["id", "name"],
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取的结果不包含 password 字段
|
||||||
|
userRepository.find({
|
||||||
|
except: ["password"],
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取的结果会包含关联对象 posts 的数据
|
||||||
|
userRepository.find({
|
||||||
|
appends: ["posts"],
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 关联字段查询
|
||||||
|
|
||||||
|
`filter` 参数支持按关联字段进行过滤,例如:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 查询 user 对象,其所关联的 posts 存在 title 为 'title1' 的对象
|
||||||
|
userRepository.find({
|
||||||
|
filter: {
|
||||||
|
"posts.title": "post title"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
关联字段也可进行嵌套
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 查询 user 对象,查询结果满足其 posts 的 comments 包含 keywords
|
||||||
|
await userRepository.find({
|
||||||
|
filter: {
|
||||||
|
"posts.comments.content": {
|
||||||
|
$like: "%keywords%"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 排序
|
||||||
|
|
||||||
|
通过 `sort` 参数,可以对查询结果进行排序。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
|
||||||
|
// SELECT * FROM users ORDER BY age
|
||||||
|
await userRepository.find({
|
||||||
|
sort: 'age'
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// SELECT * FROM users ORDER BY age DESC
|
||||||
|
await userRepository.find({
|
||||||
|
sort: '-age'
|
||||||
|
});
|
||||||
|
|
||||||
|
// SELECT * FROM users ORDER BY age DESC, name ASC
|
||||||
|
await userRepository.find({
|
||||||
|
sort: ['-age', "name"],
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
也可按照关联对象的字段进行排序
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
await userRepository.find({
|
||||||
|
sort: 'profile.createdAt'
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 创建
|
||||||
|
|
||||||
|
#### 基础创建
|
||||||
|
|
||||||
|
通过 `Repository` 创建新的数据对象。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
|
||||||
|
await userRepository.create({
|
||||||
|
name: "张三",
|
||||||
|
age: 18,
|
||||||
|
});
|
||||||
|
// INSERT INTO users (name, age) VALUES ('张三', 18)
|
||||||
|
|
||||||
|
|
||||||
|
// 支持批量创建
|
||||||
|
await userRepository.create([
|
||||||
|
{
|
||||||
|
name: "张三",
|
||||||
|
age: 18,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "李四",
|
||||||
|
age: 20,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 创建关联
|
||||||
|
|
||||||
|
创建时可以同时创建关联对象,和查询类似,也支持关联对象的嵌套使用,例如:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
await userRepository.create({
|
||||||
|
name: "张三",
|
||||||
|
age: 18,
|
||||||
|
posts: [
|
||||||
|
{
|
||||||
|
title: "post title",
|
||||||
|
content: "post content",
|
||||||
|
tags: [
|
||||||
|
{
|
||||||
|
name: "tag1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tag2",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
// 创建用户的同时,创建 post 与用户关联,创建 tags 与 post 相关联。
|
||||||
|
```
|
||||||
|
若关联对象已在数据库中,可传入其ID,创建时会建立与关联对象的关联关系。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const tag1 = await tagRepository.findOne({
|
||||||
|
filter: {
|
||||||
|
name: "tag1"
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await userRepository.create({
|
||||||
|
name: "张三",
|
||||||
|
age: 18,
|
||||||
|
posts: [
|
||||||
|
{
|
||||||
|
title: "post title",
|
||||||
|
content: "post content",
|
||||||
|
tags: [
|
||||||
|
{
|
||||||
|
id: tag1.id, // 建立与已存在关联对象的关联关系
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tag2",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 更新
|
||||||
|
|
||||||
|
#### 基础更新
|
||||||
|
|
||||||
|
获取到数据对象后,可直接在数据对象(`Model`)上修改属性,然后调用 `save` 方法保存修改。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const user = await userRepository.findOne({
|
||||||
|
filter: {
|
||||||
|
name: "张三",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
user.age = 20;
|
||||||
|
await user.save();
|
||||||
|
```
|
||||||
|
|
||||||
|
数据对象 `Model` 继承自 Sequelize Model,对 `Model` 的操作可参考 [Sequelize Model](https://sequelize.org/master/manual/model-basics.html)。
|
||||||
|
|
||||||
|
也可通过 `Repository` 更新数据:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 修改满足筛选条件的数据记录
|
||||||
|
await userRepository.update({
|
||||||
|
filter: {
|
||||||
|
name: "张三",
|
||||||
|
},
|
||||||
|
values: {
|
||||||
|
age: 20,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
更新时,可以通过 `whitelist` 、`blacklist` 参数控制更新字段,例如:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
await userRepository.update({
|
||||||
|
filter: {
|
||||||
|
name: "张三",
|
||||||
|
},
|
||||||
|
values: {
|
||||||
|
age: 20,
|
||||||
|
name: "李四",
|
||||||
|
},
|
||||||
|
whitelist: ["age"], // 仅更新 age 字段
|
||||||
|
});
|
||||||
|
````
|
||||||
|
|
||||||
|
#### 更新关联字段
|
||||||
|
|
||||||
|
在更新时,可以设置关联对象,例如:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const tag1 = tagRepository.findOne({
|
||||||
|
filter: {
|
||||||
|
id: 1
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await postRepository.update({
|
||||||
|
filter: {
|
||||||
|
id: 1
|
||||||
|
},
|
||||||
|
values: {
|
||||||
|
title: "new post title",
|
||||||
|
tags: [
|
||||||
|
{
|
||||||
|
id: tag1.id // 与 tag1 建立关联
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tag2", // 创建新的 tag 并建立关联
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
await postRepository.update({
|
||||||
|
filter: {
|
||||||
|
id: 1
|
||||||
|
},
|
||||||
|
values: {
|
||||||
|
tags: null // 解除 post 与 tags 的关联
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 删除
|
||||||
|
|
||||||
|
可调用 `Repository` 中的 `destroy()`方法进行删除操作。删除时需指定筛选条件:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
await userRepository.destroy({
|
||||||
|
filter: {
|
||||||
|
status: "blocked",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
## 构造函数
|
## 构造函数
|
||||||
|
|
||||||
@ -56,7 +381,7 @@ await books.myQuery('SELECT * FROM books;');
|
|||||||
|
|
||||||
### `find()`
|
### `find()`
|
||||||
|
|
||||||
从数据库查询特定条件的数据集。相当于 Sequelize 中的 `Model.findAll()`。
|
从数据库查询数据集,可指定筛选条件、排序等。
|
||||||
|
|
||||||
**签名**
|
**签名**
|
||||||
|
|
||||||
|
@ -152,6 +152,17 @@ DB_TABLE_PREFIX=nocobase_
|
|||||||
DB_LOGGING=on
|
DB_LOGGING=on
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### LOGGER_TRANSPORT
|
||||||
|
|
||||||
|
日志 transport,默认值 `console,dailyRotateFile`,可选项
|
||||||
|
|
||||||
|
- `console`
|
||||||
|
- `dailyRotateFile`
|
||||||
|
|
||||||
|
### DAILY_ROTATE_FILE_DIRNAME
|
||||||
|
|
||||||
|
`dailyRotateFile` 日志的存储路径,默认为 `storage/logs`
|
||||||
|
|
||||||
## 临时环境变量
|
## 临时环境变量
|
||||||
|
|
||||||
安装 NocoBase 时,可以通过设置临时的环境变量来辅助安装,如:
|
安装 NocoBase 时,可以通过设置临时的环境变量来辅助安装,如:
|
||||||
|
@ -1,142 +0,0 @@
|
|||||||
# Action API
|
|
||||||
|
|
||||||
## Common
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Collection and Association resources are common.
|
|
||||||
|
|
||||||
### `create`
|
|
||||||
|
|
||||||
```bash
|
|
||||||
POST /api/users:create?whitelist=a,b&blacklist=c,d
|
|
||||||
|
|
||||||
{} # Request Body
|
|
||||||
```
|
|
||||||
|
|
||||||
- Parameters
|
|
||||||
- whitelist White list
|
|
||||||
- blacklist Black list
|
|
||||||
- Request body: JSON data to be inserted
|
|
||||||
- Response body data: Created data JSON
|
|
||||||
|
|
||||||
#### Add a User
|
|
||||||
|
|
||||||
```bash
|
|
||||||
POST /api/users:create
|
|
||||||
|
|
||||||
Request Body
|
|
||||||
{
|
|
||||||
"email": "demo@nocobase.com",
|
|
||||||
"name": "Admin"
|
|
||||||
}
|
|
||||||
|
|
||||||
Response 200 (application/json)
|
|
||||||
{
|
|
||||||
"data": {},
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Add a user's article
|
|
||||||
|
|
||||||
```bash
|
|
||||||
POST /api/users/1/posts:create
|
|
||||||
|
|
||||||
Request Body
|
|
||||||
{
|
|
||||||
"title": "My first post"
|
|
||||||
}
|
|
||||||
|
|
||||||
Response 200 (application/json)
|
|
||||||
{
|
|
||||||
"data": {},
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Association in Request Body
|
|
||||||
|
|
||||||
```bash
|
|
||||||
POST /api/posts:create
|
|
||||||
|
|
||||||
Request Body
|
|
||||||
{
|
|
||||||
"title": "My first post",
|
|
||||||
"user": 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Response 200 (application/json)
|
|
||||||
{
|
|
||||||
"data": {
|
|
||||||
"id": 1,
|
|
||||||
"title": "My first post",
|
|
||||||
"userId": 1,
|
|
||||||
"user": {
|
|
||||||
"id": 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### `update`
|
|
||||||
|
|
||||||
```bash
|
|
||||||
POST /api/users:create?filterByTk=1&whitelist=a,b&blacklist=c,d
|
|
||||||
|
|
||||||
{} # Request Body
|
|
||||||
```
|
|
||||||
|
|
||||||
- Parameters
|
|
||||||
- whitelist White list
|
|
||||||
- blacklist Black list
|
|
||||||
- filterByTk Filter by tk field, by default tk is the primary key of the data table
|
|
||||||
- filter Filter,support json string
|
|
||||||
- Request body: JSON data to be updated
|
|
||||||
|
|
||||||
#### Association in Request Body
|
|
||||||
|
|
||||||
```bash
|
|
||||||
POST /api/posts:update/1
|
|
||||||
|
|
||||||
Request Body
|
|
||||||
{
|
|
||||||
"title": "My first post 2",
|
|
||||||
"user": 2
|
|
||||||
}
|
|
||||||
|
|
||||||
Response 200 (application/json)
|
|
||||||
{
|
|
||||||
"data": [
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"title": "My first post 2",
|
|
||||||
"userId": 2,
|
|
||||||
"user": {
|
|
||||||
"id": 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### `list`
|
|
||||||
|
|
||||||
### `get`
|
|
||||||
|
|
||||||
### `destroy`
|
|
||||||
|
|
||||||
### `move`
|
|
||||||
|
|
||||||
## Association
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### `add`
|
|
||||||
|
|
||||||
### `set`
|
|
||||||
|
|
||||||
### `remove`
|
|
||||||
|
|
||||||
### `toggle`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
|||||||
# Filter operators
|
|
||||||
|
|
||||||
## Common
|
|
||||||
|
|
||||||
- $eq
|
|
||||||
- $ne
|
|
||||||
- $gte
|
|
||||||
- $gt
|
|
||||||
- $lte
|
|
||||||
- $lt
|
|
||||||
- $not
|
|
||||||
- $is
|
|
||||||
- $in
|
|
||||||
- $notIn
|
|
||||||
- $like
|
|
||||||
- $notLike
|
|
||||||
- $iLike
|
|
||||||
- $notILike
|
|
||||||
- $and
|
|
||||||
- $or
|
|
||||||
- $empty
|
|
||||||
- $notEmpty
|
|
||||||
|
|
||||||
## array
|
|
||||||
|
|
||||||
- $match
|
|
||||||
- $notMatch
|
|
||||||
- $anyOf
|
|
||||||
- $noneOf
|
|
||||||
- $arrayEmpty
|
|
||||||
- $arrayNotEmpty
|
|
||||||
|
|
||||||
## association
|
|
||||||
|
|
||||||
- $exists
|
|
||||||
- $notExists
|
|
||||||
|
|
||||||
## boolean
|
|
||||||
|
|
||||||
- $isTruthy
|
|
||||||
- $isFalsy
|
|
||||||
|
|
||||||
## date
|
|
||||||
|
|
||||||
- $dateOn
|
|
||||||
- $dateNotOn
|
|
||||||
- $dateBefore
|
|
||||||
- $dateNotBefore
|
|
||||||
- $dateAfter
|
|
||||||
- $dateNotAfter
|
|
||||||
|
|
||||||
## string
|
|
||||||
|
|
||||||
- $includes
|
|
||||||
- $notIncludes
|
|
||||||
- $startsWith
|
|
||||||
- $notStartsWith
|
|
||||||
- $endWith
|
|
||||||
- $notEndWith
|
|
@ -1,281 +0,0 @@
|
|||||||
# JavaScript SDK
|
|
||||||
|
|
||||||
## APIClient
|
|
||||||
|
|
||||||
```ts
|
|
||||||
class APIClient {
|
|
||||||
// axios instance
|
|
||||||
axios: AxiosInstance;
|
|
||||||
// constructors
|
|
||||||
constructor(instance?: AxiosInstance | AxiosRequestConfig);
|
|
||||||
// Client-side requests, support for AxiosRequestConfig and ResourceActionOptions
|
|
||||||
request<T = any, R = AxiosResponse<T>, D = any>(config: AxiosRequestConfig<D> | ResourceActionOptions): Promise<R>;
|
|
||||||
// Get Resources
|
|
||||||
resource<R = IResource>(name: string, of?: any): R;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Initialize instance
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import axios from 'axios';
|
|
||||||
import { APIClient } from '@nocobase/sdk';
|
|
||||||
|
|
||||||
// Provide AxiosRequestConfig configuration parameters
|
|
||||||
const api = new APIClient({
|
|
||||||
baseURL: 'https://localhost:8000/api',
|
|
||||||
});
|
|
||||||
|
|
||||||
// Provide AxiosInstance
|
|
||||||
const instance = axios.create({
|
|
||||||
baseURL: 'https://localhost:8000/api',
|
|
||||||
});
|
|
||||||
const api = new APIClient(instance);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Mock
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { APIClient } from '@nocobase/sdk';
|
|
||||||
import MockAdapter from 'axios-mock-adapter';
|
|
||||||
|
|
||||||
const api = new APIClient({
|
|
||||||
baseURL: 'https://localhost:8000/api',
|
|
||||||
});
|
|
||||||
|
|
||||||
const mock = new MockAdapter(api.axios);
|
|
||||||
|
|
||||||
mock.onGet('users:get').reply(200, {
|
|
||||||
data: { id: 1, name: 'John Smith' },
|
|
||||||
});
|
|
||||||
|
|
||||||
await api.request({ url: 'users:get' });
|
|
||||||
```
|
|
||||||
|
|
||||||
## Storage
|
|
||||||
|
|
||||||
APIClient uses localStorage by default, you can also custom storage.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { Storage } from '@nocobase/sdk';
|
|
||||||
|
|
||||||
class MemoryStorage extends Storage {
|
|
||||||
items = new Map();
|
|
||||||
|
|
||||||
clear() {
|
|
||||||
this.items.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
getItem(key: string) {
|
|
||||||
return this.items.get(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
setItem(key: string, value: string) {
|
|
||||||
return this.items.set(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
removeItem(key: string) {
|
|
||||||
return this.items.delete(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const api = new APIClient({
|
|
||||||
baseURL: 'https://localhost:8000/api',
|
|
||||||
storageClass: CustomStorage,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Auth
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// sign in and remember the current token
|
|
||||||
api.auth.signIn({ email, password });
|
|
||||||
// sign out and delete the token
|
|
||||||
api.auth.signOut();
|
|
||||||
// set the token
|
|
||||||
api.auth.setToken('123');
|
|
||||||
// set the role (multiple roles)
|
|
||||||
api.auth.setRole('admin');
|
|
||||||
// set the locale (multiple languages)
|
|
||||||
api.auth.setLocale('zh-CN');
|
|
||||||
```
|
|
||||||
|
|
||||||
Custom Auth
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { Auth } from '@nocobase/sdk';
|
|
||||||
|
|
||||||
class CustomAuth extends Auth {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const api = new APIClient({
|
|
||||||
baseURL: 'https://localhost:8000/api',
|
|
||||||
authClass: CustomAuth,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Request
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// url
|
|
||||||
await api.request({
|
|
||||||
url: 'users:list',
|
|
||||||
// request params
|
|
||||||
params: {
|
|
||||||
filter: {
|
|
||||||
'email.$includes': 'noco',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// request body
|
|
||||||
data,
|
|
||||||
});
|
|
||||||
|
|
||||||
// resource & action
|
|
||||||
await api.request({
|
|
||||||
resource: 'users',
|
|
||||||
action: 'list',
|
|
||||||
// action params
|
|
||||||
params: {
|
|
||||||
filter: {
|
|
||||||
'email.$includes': 'noco',
|
|
||||||
},
|
|
||||||
page: 1,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Resource action
|
|
||||||
|
|
||||||
```ts
|
|
||||||
await api.resource('collection')[action]();
|
|
||||||
await api.resource('collection.association', collectionId)[action]();
|
|
||||||
```
|
|
||||||
|
|
||||||
## Action API
|
|
||||||
|
|
||||||
```ts
|
|
||||||
await api.resource('collection').create();
|
|
||||||
await api.resource('collection').get();
|
|
||||||
await api.resource('collection').list();
|
|
||||||
await api.resource('collection').update();
|
|
||||||
await api.resource('collection').destroy();
|
|
||||||
await api.resource('collection.association', collectionId).create();
|
|
||||||
await api.resource('collection.association', collectionId).get();
|
|
||||||
await api.resource('collection.association', collectionId).list();
|
|
||||||
await api.resource('collection.association', collectionId).update();
|
|
||||||
await api.resource('collection.association', collectionId).destroy();
|
|
||||||
```
|
|
||||||
|
|
||||||
### `get`
|
|
||||||
|
|
||||||
```ts
|
|
||||||
interface Resource {
|
|
||||||
get: (options?: GetActionOptions) => Promise<any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface GetActionOptions {
|
|
||||||
filter?: any;
|
|
||||||
filterByTk?: any;
|
|
||||||
fields?: string || string[];
|
|
||||||
appends?: string || string[];
|
|
||||||
expect?: string || string[];
|
|
||||||
sort?: string[];
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### `list`
|
|
||||||
|
|
||||||
```ts
|
|
||||||
interface Resource {
|
|
||||||
list: (options?: ListActionOptions) => Promise<any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ListActionOptions {
|
|
||||||
filter?: any;
|
|
||||||
filterByTk?: any;
|
|
||||||
fields?: string || string[];
|
|
||||||
appends?: string || string[];
|
|
||||||
expect?: string || string[];
|
|
||||||
sort?: string[];
|
|
||||||
page?: number;
|
|
||||||
pageSize?: number;
|
|
||||||
paginate?: boolean;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### `create`
|
|
||||||
|
|
||||||
```ts
|
|
||||||
interface Resource {
|
|
||||||
create: (options?: CreateActionOptions) => Promise<any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CreateActionOptions {
|
|
||||||
whitelist?: string[];
|
|
||||||
blacklist?: string[];
|
|
||||||
values?: {[key: sting]: any};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### `update`
|
|
||||||
|
|
||||||
```ts
|
|
||||||
interface Resource {
|
|
||||||
update: (options?: UpdateActionOptions) => Promise<any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface UpdateActionOptions {
|
|
||||||
filter?: any;
|
|
||||||
filterByTk?: any;
|
|
||||||
whitelist?: string[];
|
|
||||||
blacklist?: string[];
|
|
||||||
values?: {[key: sting]: any};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### `destroy`
|
|
||||||
|
|
||||||
```ts
|
|
||||||
interface Resource {
|
|
||||||
destroy: (options?: DestroyActionOptions) => Promise<any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DestroyActionOptions {
|
|
||||||
filter?: any;
|
|
||||||
filterByTk?: any;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### `move`
|
|
||||||
|
|
||||||
```ts
|
|
||||||
interface Resource {
|
|
||||||
move: (options?: MoveActionOptions) => Promise<any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface MoveActionOptions {
|
|
||||||
sourceId: any;
|
|
||||||
targetId?: any;
|
|
||||||
/** @default 'sort' */
|
|
||||||
sortField?: any;
|
|
||||||
targetScope?: {[key: string]: any};
|
|
||||||
sticky?: boolean;
|
|
||||||
method?: 'insertAfter' | 'prepend';
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### `<custom>`
|
|
||||||
|
|
||||||
```ts
|
|
||||||
interface AttachmentResource {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
interface UploadActionOptions {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
api.resource<AttachmentResource>('attachments').upload();
|
|
||||||
api.resource('attachments').upload<UploadActionOptions>();
|
|
||||||
```
|
|
@ -1,8 +1,8 @@
|
|||||||
# 概览
|
# 概览
|
||||||
|
|
||||||
| 模块 | 包名 | 描述 |
|
| 模块 | 包名 | 描述 |
|
||||||
| --------------------------------- | --------------------- | ------------------- |
|
|-----------------------------------| --------------------- | ------------------- |
|
||||||
| [Server](/api/server) | `@nocobase/server` | 服务端应用 |
|
| [Server](/api/server/application) | `@nocobase/server` | 服务端应用 |
|
||||||
| [Database](/api/database) | `@nocobase/database` | 数据库访问层 |
|
| [Database](/api/database) | `@nocobase/database` | 数据库访问层 |
|
||||||
| [Resourcer](/api/resourcer) | `@nocobase/resourcer` | 资源与路由映射 |
|
| [Resourcer](/api/resourcer) | `@nocobase/resourcer` | 资源与路由映射 |
|
||||||
| [ACL](/api/acl) | `@nocobase/acl` | 访问控制表 |
|
| [ACL](/api/acl) | `@nocobase/acl` | 访问控制表 |
|
||||||
|
@ -1,25 +1,57 @@
|
|||||||
# Resourcer
|
# Resourcer
|
||||||
|
|
||||||
Resourcer 主要用于管理 API 资源与路由,也是 NocoBase 的内置模块,app 默认会自动创建一个 Resourcer 实例,大部分情况你可以通过 `app.resourcer` 访问。
|
## 概览
|
||||||
|
|
||||||
资源路由管理器主要通过 [资源](/api/server/resourcer/resource) + [操作](/api/server/resourcer/action) 的概念定义服务端 API 接口,与 RESTful 的概念相似。大部分资源通过映射数据库表生成,包含常规的 CRUD 操作,以覆盖常见场景。但如果有额外需求,也可以在此基础上扩展更多的资源类型和操作类型。
|
Nocobase 中的接口遵循面向资源的设计模式。Resourcer 主要用于管理 API 资源与路由。
|
||||||
|
|
||||||
## 包结构
|
```javascript
|
||||||
|
const Koa = require('koa');
|
||||||
|
const { Resourcer } = require('@nocobase/resourcer');
|
||||||
|
|
||||||
可通过以下方式引入相关实体:
|
const resourcer = new Resourcer();
|
||||||
|
|
||||||
```ts
|
// 定义一个资源接口
|
||||||
import Resourcer, {
|
resourcer.define({
|
||||||
Resource,
|
name: 'users',
|
||||||
Action,
|
actions: {
|
||||||
Middleware,
|
async list(ctx) {
|
||||||
branch
|
ctx.body = [
|
||||||
} from '@nocobase/resourcer';
|
{
|
||||||
|
name: "u1",
|
||||||
|
age: 18
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "u2",
|
||||||
|
age: 20
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const app = new Koa();
|
||||||
|
|
||||||
|
// 可在 koa 实例中使用
|
||||||
|
app.use(
|
||||||
|
resourcer.middleware({
|
||||||
|
prefix: '/api', // resourcer 路由前缀
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
app.listen(3000);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
启动服务后,使用`curl`发起请求:
|
||||||
|
```bash
|
||||||
|
>$ curl localhost:3000/api/users
|
||||||
|
[{"name":"u1","age":18},{"name":"u2","age":20}]
|
||||||
|
```
|
||||||
|
更多 Resourcer 的使用说明可参考[资源与操作](/development/guide/resources-actions)。 Resourcer 内置于 [NocoBase Application](/api/server/application#resourcer) ,可以通过 `app.resourcer` 访问。
|
||||||
|
|
||||||
|
|
||||||
## 构造函数
|
## 构造函数
|
||||||
|
|
||||||
用于创建 Resourcer 管理器实例。由于 app 默认创建一个内置实例,所以通常不会直接使用构造函数。
|
用于创建 Resourcer 管理器实例。
|
||||||
|
|
||||||
**签名**
|
**签名**
|
||||||
|
|
||||||
|
@ -1,6 +1,136 @@
|
|||||||
# Application
|
# Application
|
||||||
|
|
||||||
基于 [Koa](https://koajs.com/) 实现的 WEB 框架,兼容所有的 Koa 插件。
|
## 概览
|
||||||
|
|
||||||
|
### Web服务
|
||||||
|
Nocobase Application 是基于 [Koa](https://koajs.com/) 实现的 WEB 框架,兼容 Koa 的 API。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// index.js
|
||||||
|
const { Application } = require('@nocobase/server');
|
||||||
|
|
||||||
|
// 创建App实例,并配置数据库连接信息
|
||||||
|
const app = new Application({
|
||||||
|
database: {
|
||||||
|
dialect: 'sqlite',
|
||||||
|
storage: ':memory:',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 注册中间件 响应请求
|
||||||
|
app.use(async ctx => {
|
||||||
|
ctx.body = 'Hello World';
|
||||||
|
});
|
||||||
|
|
||||||
|
// 以命令行模式启动
|
||||||
|
app.runAsCLI();
|
||||||
|
```
|
||||||
|
|
||||||
|
在命令行中运行 `node index.js start` 启动服务后,使用 `curl` 请求服务。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$> curl localhost:3000
|
||||||
|
Hello World
|
||||||
|
```
|
||||||
|
|
||||||
|
### 命令行工具
|
||||||
|
Nocobase Application 中也内置了 `cli commander`,可以当作命令行工具运行。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// cmd.js
|
||||||
|
const {Application} = require('@nocobase/server');
|
||||||
|
const app = new Application({
|
||||||
|
database: {
|
||||||
|
dialect: 'sqlite',
|
||||||
|
storage: ':memory:',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.cli.command('hello').action(async () => {
|
||||||
|
console.log("hello world")
|
||||||
|
});
|
||||||
|
|
||||||
|
app.runAsCLI()
|
||||||
|
```
|
||||||
|
|
||||||
|
在命令行中运行
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$> node cmd.js hello
|
||||||
|
hello world
|
||||||
|
```
|
||||||
|
|
||||||
|
### 插件注入
|
||||||
|
|
||||||
|
Nocobase Application 被设计为高度可扩展的框架,可以编写插件注入到应用中扩展功能。
|
||||||
|
例如上面的 Web 服务可以替换为插件形式。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const { Application, Plugin } = require('@nocobase/server');
|
||||||
|
|
||||||
|
// 通过继承 Plugin 类来编写插件
|
||||||
|
class HelloWordPlugin extends Plugin {
|
||||||
|
load() {
|
||||||
|
this.app.use(async (ctx, next) => {
|
||||||
|
ctx.body = "Hello World";
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const app = new Application({
|
||||||
|
database: {
|
||||||
|
dialect: 'sqlite',
|
||||||
|
storage: ':memory:',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 注入插件
|
||||||
|
app.plugin(HelloWordPlugin, { name: 'hello-world-plugin'} );
|
||||||
|
|
||||||
|
app.runAsCLI()
|
||||||
|
```
|
||||||
|
|
||||||
|
### 更多示例
|
||||||
|
|
||||||
|
更加详细的插件开发文档请参考 [插件开发](./plugin.md)。
|
||||||
|
Application 类的更多示例可参考 [examples](https://github.com/nocobase/nocobase/blob/main/examples/index.md)
|
||||||
|
|
||||||
|
## 生命周期
|
||||||
|
|
||||||
|
根据不同运行模式,Application 有三种生命周期:
|
||||||
|
|
||||||
|
### 安装
|
||||||
|
使用 `cli` 中的 `install` 命令调用安装。
|
||||||
|
一般来说,插件在使用之前若需要在数据库中写入新表或者数据,都需要在安装时执行。在初次使用 Nocobase 时也需要调用安装。
|
||||||
|
|
||||||
|
* 调用 `load` 方法,载入已注册的插件。
|
||||||
|
* 触发 `beforeInstall` 事件。
|
||||||
|
* 调用 `db.sync` 方法,同步数据库。
|
||||||
|
* 调用 `pm.install` 方法,执行已注册插件的 `install` 方法。
|
||||||
|
* 写入 `nocobase` 版本。
|
||||||
|
* 触发 `afterInstall`。
|
||||||
|
* 调用 `stop` 方法,结束安装。
|
||||||
|
|
||||||
|
### 启动
|
||||||
|
使用 `cli` 中的 `start` 命令来启动 Nocobase Web 服务。
|
||||||
|
|
||||||
|
* 调用 `load` 方法,载入已注册的插件。
|
||||||
|
* 调用 `start` 方法
|
||||||
|
* 触发 `beforeStart`
|
||||||
|
* 启动端口监听
|
||||||
|
* 触发 `afterStart`
|
||||||
|
|
||||||
|
### 更新
|
||||||
|
|
||||||
|
当需要更新 Nocobase 时,可使用 `cli` 中的 `upgrade` 命令。
|
||||||
|
|
||||||
|
* 调用 `load` 方法,载入已注册的插件。
|
||||||
|
* 触发 `beforeUpgrade`。
|
||||||
|
* 调用 `db.migrator.up` 方法,执行数据库迁移。
|
||||||
|
* 调用 `db.sync` 方法,同步数据库。
|
||||||
|
* 调用 `version.update` 方法,更新 `nocobase` 版本。
|
||||||
|
* 触发 `afterUpgrade`。
|
||||||
|
* 调用 `stop` 方法,结束更新。
|
||||||
|
|
||||||
## 构造函数
|
## 构造函数
|
||||||
|
|
||||||
@ -18,6 +148,7 @@
|
|||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| `options.database` | `IDatabaseOptions` or `Database` | `{}` | 数据库配置 |
|
| `options.database` | `IDatabaseOptions` or `Database` | `{}` | 数据库配置 |
|
||||||
| `options.resourcer` | `ResourcerOptions` | `{}` | 资源路由配置 |
|
| `options.resourcer` | `ResourcerOptions` | `{}` | 资源路由配置 |
|
||||||
|
| `options.logger` | `AppLoggerOptions` | `{}` | 日志 |
|
||||||
| `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.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.dataWrapping` | `boolean` | `true` | 是否包装响应数据,`true` 则将把通常的 `ctx.body` 包装为 `{ data, meta }` 的结构。 |
|
||||||
| `options.registerActions` | `boolean` | `true` | 是否注册默认的 [actions](#) |
|
| `options.registerActions` | `boolean` | `true` | 是否注册默认的 [actions](#) |
|
||||||
@ -32,29 +163,6 @@ interface ApplicationOptions {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**示例**
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import Application from '@nocobase/server';
|
|
||||||
|
|
||||||
const app = new Application({
|
|
||||||
database: {
|
|
||||||
dialect: 'mysql',
|
|
||||||
host: 'localhost',
|
|
||||||
port: 3306,
|
|
||||||
username: 'root',
|
|
||||||
password: '123456',
|
|
||||||
database: 'test',
|
|
||||||
},
|
|
||||||
resourcer: {
|
|
||||||
prefix: '/api',
|
|
||||||
},
|
|
||||||
cors: {
|
|
||||||
origin: '*',
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## 实例成员
|
## 实例成员
|
||||||
|
|
||||||
### `cli`
|
### `cli`
|
||||||
@ -73,6 +181,11 @@ const app = new Application({
|
|||||||
|
|
||||||
ACL 实例,相关 API 参考 [ACL](/api/acl)。
|
ACL 实例,相关 API 参考 [ACL](/api/acl)。
|
||||||
|
|
||||||
|
|
||||||
|
### `logger`
|
||||||
|
|
||||||
|
Winston 实例,相关 API 参考 [Winston](https://github.com/winstonjs/winston#table-of-contents)。
|
||||||
|
|
||||||
### `i18n`
|
### `i18n`
|
||||||
|
|
||||||
I18next 实例,相关 API 参考 [I18next](https://www.i18next.com/overview/api)。
|
I18next 实例,相关 API 参考 [I18next](https://www.i18next.com/overview/api)。
|
||||||
@ -89,11 +202,12 @@ I18next 实例,相关 API 参考 [I18next](https://www.i18next.com/overview/ap
|
|||||||
|
|
||||||
内置的中间件有:
|
内置的中间件有:
|
||||||
|
|
||||||
|
- logger
|
||||||
- i18next
|
- i18next
|
||||||
- bodyParser
|
- bodyParser
|
||||||
- cors
|
- cors
|
||||||
- dataWrapping
|
- dataWrapping
|
||||||
- collection2resource
|
- db2resource
|
||||||
- restApiMiddleware
|
- restApiMiddleware
|
||||||
|
|
||||||
### `context`
|
### `context`
|
||||||
@ -108,6 +222,7 @@ NocoBase 默认对 context 注入了以下成员,可以在请求处理函数
|
|||||||
| `ctx.db` | `Database` | 数据库实例 |
|
| `ctx.db` | `Database` | 数据库实例 |
|
||||||
| `ctx.resourcer` | `Resourcer` | 资源路由管理器实例 |
|
| `ctx.resourcer` | `Resourcer` | 资源路由管理器实例 |
|
||||||
| `ctx.action` | `Action` | 资源操作相关对象实例 |
|
| `ctx.action` | `Action` | 资源操作相关对象实例 |
|
||||||
|
| `ctx.logger` | `Winston` | 日志实例 |
|
||||||
| `ctx.i18n` | `I18n` | 国际化实例 |
|
| `ctx.i18n` | `I18n` | 国际化实例 |
|
||||||
| `ctx.t` | `i18n.t` | 国际化翻译函数快捷方式 |
|
| `ctx.t` | `i18n.t` | 国际化翻译函数快捷方式 |
|
||||||
| `ctx.getBearerToken()` | `Function` | 获取请求头中的 bearer token |
|
| `ctx.getBearerToken()` | `Function` | 获取请求头中的 bearer token |
|
||||||
|
@ -1,51 +1,40 @@
|
|||||||
# Plugin
|
# Plugin
|
||||||
|
|
||||||
## 示例
|
## 概览
|
||||||
|
|
||||||
```ts
|
Nocobase 中的插件为 `Class` 的形式。如需自定义插件,需要继承 `Plugin` 类。
|
||||||
const app = new Application();
|
|
||||||
|
```typescript
|
||||||
|
import { Plugin } from '@nocobase/server';
|
||||||
|
|
||||||
class MyPlugin extends Plugin {
|
class MyPlugin extends Plugin {
|
||||||
afterAdd() {}
|
// ...
|
||||||
beforeLoad() {}
|
|
||||||
load() {}
|
|
||||||
install() {}
|
|
||||||
afterEnable() {}
|
|
||||||
afterDisable() {}
|
|
||||||
remove() {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
app.plugin(MyPlugin, { name: 'my-plugin' });
|
app.plugin(MyPlugin, { name: 'my-plugin' });
|
||||||
```
|
```
|
||||||
|
|
||||||
## 属性
|
## 插件生命周期
|
||||||
|
|
||||||
### `options`
|
每个插件都包含生命周期方法,你可以重写这些方法,以便于在运行过程中特定的阶段执行这些方法。
|
||||||
|
生命周期方法将由 `Application` 在特定阶段调用,可参考 [`Application`生命周期](./application.md)。
|
||||||
插件配置信息
|
|
||||||
|
|
||||||
### `name`
|
|
||||||
|
|
||||||
插件标识,只读
|
|
||||||
|
|
||||||
## 实例方法
|
|
||||||
|
|
||||||
### `afterAdd()`
|
|
||||||
|
|
||||||
插件 add/addStatic 之后
|
|
||||||
|
|
||||||
### `beforeLoad()`
|
### `beforeLoad()`
|
||||||
|
|
||||||
插件加载前,如事件或类注册
|
插件加载前,如事件或类注册,在此可访问核心接口,其他插件此阶段不可用。
|
||||||
|
|
||||||
### `load()`
|
### `load()`
|
||||||
|
|
||||||
加载插件,配置之类
|
加载插件,配置之类。在 `load` 中可调用其他插件实例,而在 `beforeLoad` 中是不行的。
|
||||||
|
|
||||||
### `install()`
|
### `install()`
|
||||||
|
|
||||||
插件安装逻辑,如初始化数据
|
插件安装逻辑,如初始化数据
|
||||||
|
|
||||||
|
### `afterAdd()`
|
||||||
|
|
||||||
|
插件 add/addStatic 之后
|
||||||
|
|
||||||
### `afterEnable()`
|
### `afterEnable()`
|
||||||
|
|
||||||
插件激活之后的逻辑
|
插件激活之后的逻辑
|
||||||
|
67
docs/en-US/welcome/release/logger.md
Normal file
67
docs/en-US/welcome/release/logger.md
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
# v0.8.1:NocoBase 的 Logging 系统
|
||||||
|
|
||||||
|
## `@nocobase/logger`
|
||||||
|
|
||||||
|
基于 Winston 实现,提供了便捷的创建 logger 实例的方法。
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const logger = createLogger();
|
||||||
|
logger.info('Hello distributed log files!');
|
||||||
|
|
||||||
|
const { instance, middleware } = createAppLogger(); // 用于 @nocobase/server
|
||||||
|
app.logger = instance;
|
||||||
|
app.use(middleware);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 新增的环境变量
|
||||||
|
|
||||||
|
logger 相关环境变量有:
|
||||||
|
|
||||||
|
- [LOGGER_TRANSPORT](/api/env#logger_transport)
|
||||||
|
- [DAILY_ROTATE_FILE_DIRNAME](/api/env#daily_rotate_file_dirname)
|
||||||
|
|
||||||
|
## Application 的 logger 配置
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const app = new Application({
|
||||||
|
logger: {
|
||||||
|
async skip(ctx) {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
requestWhitelist: [],
|
||||||
|
responseWhitelist: [],
|
||||||
|
transports: ['console', 'dailyRotateFile'],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
更多配置项参考 [Winston 文档](https://github.com/winstonjs/winston#table-of-contents)
|
||||||
|
|
||||||
|
## app.logger & ctx.logger
|
||||||
|
|
||||||
|
ctx.logger 带有 reqId,整个 ctx 周期里都是一个 reqId
|
||||||
|
|
||||||
|
```ts
|
||||||
|
ctx.logger = app.logger.child({ reqId: ctx.reqId });
|
||||||
|
```
|
||||||
|
|
||||||
|
`app.logger` 和 `ctx.logger` 都是 Winston 实例,详细用法参考 [Winston 文档](https://github.com/winstonjs/winston#table-of-contents)
|
||||||
|
|
||||||
|
|
||||||
|
## 自定义 Transports
|
||||||
|
|
||||||
|
除了 Winston 的方式以外,NocoBase 还提供了一种更便捷的方式
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { Transports } from '@nocobase/logger';
|
||||||
|
|
||||||
|
Transports['custom'] = () => {
|
||||||
|
return new winston.transports.Console();
|
||||||
|
};
|
||||||
|
|
||||||
|
const app = new Application({
|
||||||
|
logger: {
|
||||||
|
transports: ['custom'],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
67
docs/zh-CN/welcome/release/logger.md
Normal file
67
docs/zh-CN/welcome/release/logger.md
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
# v0.8.1:NocoBase 的 Logging 系统
|
||||||
|
|
||||||
|
## `@nocobase/logger`
|
||||||
|
|
||||||
|
基于 Winston 实现,提供了便捷的创建 logger 实例的方法。
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const logger = createLogger();
|
||||||
|
logger.info('Hello distributed log files!');
|
||||||
|
|
||||||
|
const { instance, middleware } = createAppLogger(); // 用于 @nocobase/server
|
||||||
|
app.logger = instance;
|
||||||
|
app.use(middleware);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 新增的环境变量
|
||||||
|
|
||||||
|
logger 相关环境变量有:
|
||||||
|
|
||||||
|
- [LOGGER_TRANSPORT](/api/env#logger_transport)
|
||||||
|
- [DAILY_ROTATE_FILE_DIRNAME](/api/env#daily_rotate_file_dirname)
|
||||||
|
|
||||||
|
## Application 的 logger 配置
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const app = new Application({
|
||||||
|
logger: {
|
||||||
|
async skip(ctx) {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
requestWhitelist: [],
|
||||||
|
responseWhitelist: [],
|
||||||
|
transports: ['console', 'dailyRotateFile'],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
更多配置项参考 [Winston 文档](https://github.com/winstonjs/winston#table-of-contents)
|
||||||
|
|
||||||
|
## app.logger & ctx.logger
|
||||||
|
|
||||||
|
ctx.logger 带有 reqId,整个 ctx 周期里都是一个 reqId
|
||||||
|
|
||||||
|
```ts
|
||||||
|
ctx.logger = app.logger.child({ reqId: ctx.reqId });
|
||||||
|
```
|
||||||
|
|
||||||
|
`app.logger` 和 `ctx.logger` 都是 Winston 实例,详细用法参考 [Winston 文档](https://github.com/winstonjs/winston#table-of-contents)
|
||||||
|
|
||||||
|
|
||||||
|
## 自定义 Transports
|
||||||
|
|
||||||
|
除了 Winston 的方式以外,NocoBase 还提供了一种更便捷的方式
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { Transports } from '@nocobase/logger';
|
||||||
|
|
||||||
|
Transports['custom'] = () => {
|
||||||
|
return new winston.transports.Console();
|
||||||
|
};
|
||||||
|
|
||||||
|
const app = new Application({
|
||||||
|
logger: {
|
||||||
|
transports: ['custom'],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
Loading…
Reference in New Issue
Block a user