refactor: plugin initialization

This commit is contained in:
chenos 2021-04-01 09:58:30 +08:00
parent 29dca49b02
commit c74818049b
15 changed files with 97 additions and 851 deletions

View File

@ -10,9 +10,13 @@
"@nocobase/client": "^0.4.0-alpha.0", "@nocobase/client": "^0.4.0-alpha.0",
"@nocobase/database": "^0.4.0-alpha.0", "@nocobase/database": "^0.4.0-alpha.0",
"@nocobase/plugin-action-logs": "^0.4.0-alpha.0", "@nocobase/plugin-action-logs": "^0.4.0-alpha.0",
"@nocobase/plugin-automations": "^0.4.0-alpha.0",
"@nocobase/plugin-china-region": "^0.4.0-alpha.0", "@nocobase/plugin-china-region": "^0.4.0-alpha.0",
"@nocobase/plugin-collections": "^0.4.0-alpha.0", "@nocobase/plugin-collections": "^0.4.0-alpha.0",
"@nocobase/plugin-file-manager": "^0.4.0-alpha.0",
"@nocobase/plugin-permissions": "^0.4.0-alpha.0",
"@nocobase/plugin-pages": "^0.4.0-alpha.0", "@nocobase/plugin-pages": "^0.4.0-alpha.0",
"@nocobase/plugin-users": "^0.4.0-alpha.0",
"@nocobase/server": "^0.4.0-alpha.0" "@nocobase/server": "^0.4.0-alpha.0"
}, },
"devDependencies": { "devDependencies": {

View File

@ -1,9 +1,8 @@
import path from 'path'; import path from 'path';
import dotenv from 'dotenv'; import dotenv from 'dotenv';
import Api from '../../server/src'; import Api from '@nocobase/server';
import actions from '../../actions/src'; import actions from '@nocobase/actions';
import associated from '../../actions/src/middlewares/associated'; import { middlewares } from '@nocobase/actions';
import { DatabaseOptions } from '@nocobase/database';
// @ts-ignore // @ts-ignore
const sync = global.sync || { const sync = global.sync || {
@ -46,37 +45,22 @@ const api = Api.create({
}, },
}); });
api.resourcer.use(associated); api.resourcer.use(middlewares.associated);
api.resourcer.registerActionHandlers({ ...actions.common, ...actions.associate }); api.resourcer.registerActionHandlers({ ...actions.common, ...actions.associate });
// api.resourcer.use(async (ctx: actions.Context, next) => { const plugins = [
// const token = ctx.get('Authorization').replace(/^Bearer\s+/gi, ''); '@nocobase/plugin-collections',
// // console.log('user check', ctx.action.params.actionName); '@nocobase/plugin-action-logs',
// // const { actionName } = ctx.action.params; '@nocobase/plugin-pages',
// if (!token) { '@nocobase/plugin-users',
// return next(); '@nocobase/plugin-file-manager',
// } '@nocobase/plugin-permissions',
// const User = ctx.db.getModel('users'); '@nocobase/plugin-automations',
// const user = await User.findOne({ '@nocobase/plugin-china-region',
// where: { ];
// token,
// },
// });
// if (!user) {
// return next();
// }
// ctx.state.currentUser = user;
// // console.log('ctx.state.currentUser', ctx.state.currentUser);
// await next();
// });
api.registerPlugin('plugin-collections', [path.resolve(__dirname, '../../plugin-collections'), {}]); for (const plugin of plugins) {
api.registerPlugin('plugin-action-logs', [path.resolve(__dirname, '../../plugin-action-logs'), {}]); api.registerPlugin(plugin, [require(`${plugin}/src/server`).default]);
api.registerPlugin('plugin-pages', [path.resolve(__dirname, '../../plugin-pages'), {}]); }
api.registerPlugin('plugin-users', [path.resolve(__dirname, '../../plugin-users'), {}]);
api.registerPlugin('plugin-file-manager', [path.resolve(__dirname, '../../plugin-file-manager'), {}]);
api.registerPlugin('plugin-permissions', [path.resolve(__dirname, '../../plugin-permissions'), {}]);
api.registerPlugin('plugin-automations', [path.resolve(__dirname, '../../plugin-automations'), {}]);
api.registerPlugin('plugin-china-region', [path.resolve(__dirname, '../../plugin-china-region'), {}]);
export default api; export default api;

View File

@ -1,175 +0,0 @@
import { TableOptions } from '@nocobase/database';
export default {
title: '作者',
showInDataMenu: true,
name: 'authors',
// createdBy: true,
// updatedBy: true,
fields: [
{
interface: 'string',
title: '姓名',
name: 'name',
component: {
showInTable: true,
showInDetail: true,
showInForm: true,
},
},
{
interface: 'radio',
title: '性别',
dataSource: [
{ value: 'male', label: '男性' },
{ value: 'female', label: '女性' },
],
component: {
showInTable: true,
showInDetail: true,
showInForm: true,
},
},
{
interface: 'attachment',
title: '头像',
component: {
showInTable: true,
showInDetail: true,
showInForm: true,
},
},
{
interface: 'linkTo',
type: 'belongsToMany',
title: '书籍',
name: 'books',
target: 'books',
labelField: 'name',
component: {
showInTable: true,
showInDetail: true,
showInForm: true,
},
},
{
interface: 'createdBy',
title: '创建人',
component: {
showInTable: true,
showInDetail: true,
// showInForm: true,
},
},
{
interface: 'updatedBy',
title: '更新人',
component: {
showInTable: true,
showInDetail: true,
// showInForm: true,
},
},
{
interface: 'createdAt',
title: '创建日期',
component: {
showInTable: true,
showInDetail: true,
// showInForm: true,
},
},
{
interface: 'updatedAt',
title: '更新日期',
component: {
showInTable: true,
showInDetail: true,
// showInForm: true,
},
}
],
views_v2: [
{
type: 'table',
name: 'table',
title: '全部数据',
labelField: 'name',
actions: [
{
name: 'filter',
type: 'filter',
title: '过滤',
fields: [
'name',
],
// viewName: 'form',
},
{
name: 'create',
type: 'create',
title: '新增',
viewName: 'form',
},
{
name: 'destroy',
type: 'destroy',
title: '删除',
},
],
fields: ['name'],
detailsOpenMode: 'drawer', // window
details: ['descriptions', 'books'],
sort: ['id'],
},
{
type: 'descriptions',
name: 'descriptions',
title: '详情',
fields: ['name'],
actions: [
{
name: 'update',
type: 'update',
title: '编辑',
viewName: 'form',
},
],
},
{
type: 'form',
name: 'form',
title: '表单',
fields: ['name'],
},
{
type: 'association',
name: 'books',
title: '书籍',
targetViewName: 'table',
targetFieldName: 'books',
},
],
pages_v2: [
{
title: '全部数据',
name: 'all',
views: ['table'],
},
{
title: '详情',
name: 'descriptions',
views: ['descriptions'],
},
{
title: '表单',
name: 'form',
views: ['form'],
},
{
title: '书籍',
name: 'books',
views: ['books'],
},
],
};

View File

@ -1,148 +0,0 @@
import { TableOptions } from '@nocobase/database';
export default {
title: '书籍',
showInDataMenu: true,
name: 'books',
// createdBy: true,
// updatedBy: true,
fields: [
{
interface: 'string',
title: '书籍名称',
name: 'name',
component: {
showInTable: true,
showInDetail: true,
showInForm: true,
},
},
{
interface: 'attachment',
title: '封面',
component: {
showInTable: true,
showInDetail: true,
showInForm: true,
},
},
// {
// interface: 'linkTo',
// title: '作者',
// target: 'authors',
// labelField: 'name',
// component: {
// showInTable: true,
// showInDetail: true,
// showInForm: true,
// },
// },
{
interface: 'createdBy',
title: '创建人',
component: {
showInTable: true,
showInDetail: true,
// showInForm: true,
},
},
{
interface: 'updatedBy',
title: '更新人',
component: {
showInTable: true,
showInDetail: true,
// showInForm: true,
},
},
{
interface: 'createdAt',
title: '创建日期',
component: {
showInTable: true,
showInDetail: true,
// showInForm: true,
},
},
{
interface: 'updatedAt',
title: '更新日期',
component: {
showInTable: true,
showInDetail: true,
// showInForm: true,
},
}
],
views_v2: [
{
type: 'table',
name: 'table',
title: '全部数据',
labelField: 'name',
actions: [
{
name: 'filter',
type: 'filter',
title: '过滤',
fields: [
'name',
],
// viewName: 'form',
},
{
name: 'create',
type: 'create',
title: '新增',
viewName: 'form',
},
{
name: 'destroy',
type: 'destroy',
title: '删除',
},
],
fields: ['name'],
detailsOpenMode: 'drawer', // window
details: ['descriptions', 'form'],
sort: ['id'],
},
{
type: 'descriptions',
name: 'descriptions',
title: '详情',
fields: ['name'],
actions: [
{
name: 'update',
type: 'update',
title: '编辑',
viewName: 'form',
},
],
},
{
type: 'form',
name: 'form',
title: '表单',
fields: ['name'],
},
],
pages_v2: [
{
title: '全部数据',
name: 'all',
views: ['table'],
},
{
title: '详情',
name: 'descriptions',
views: ['descriptions'],
},
{
title: '表单',
name: 'form',
views: ['form'],
},
],
};

View File

@ -1,272 +0,0 @@
import { TableOptions } from '@nocobase/database';
export default {
title: '示例',
name: 'examples',
showInDataMenu: true,
logging: false,
// createdBy: true,
// updatedBy: true,
fields: [
{
interface: 'string',
title: '单行文本',
name: 'title',
component: {
showInTable: true,
showInDetail: true,
showInForm: true,
},
},
{
interface: 'textarea',
title: '多行文本',
component: {
showInTable: true,
showInDetail: true,
showInForm: true,
},
},
{
interface: 'phone',
title: '手机号',
component: {
showInTable: true,
showInDetail: true,
showInForm: true,
},
},
{
interface: 'email',
title: '邮箱',
component: {
showInTable: true,
showInDetail: true,
showInForm: true,
},
},
{
interface: 'number',
title: '数字',
component: {
showInTable: true,
showInDetail: true,
showInForm: true,
},
},
{
interface: 'percent',
title: '百分比',
component: {
showInTable: true,
showInDetail: true,
showInForm: true,
},
},
{
interface: 'boolean',
title: '是/否',
component: {
showInTable: true,
showInDetail: true,
showInForm: true,
},
},
{
interface: 'select',
title: '下拉',
dataSource: [
{ value: 'value1', label: '选项1' },
{ value: 'value2', label: '选项2' },
{ value: 'value3', label: '选项3' },
],
component: {
showInTable: true,
showInDetail: true,
showInForm: true,
},
},
{
interface: 'multipleSelect',
title: '下拉多选',
dataSource: [
{ value: 'value1', label: '选项1' },
{ value: 'value2', label: '选项2' },
{ value: 'value3', label: '选项3' },
],
component: {
showInTable: true,
showInDetail: true,
showInForm: true,
},
},
{
interface: 'radio',
title: '单选框',
dataSource: [
{ value: 'value1', label: '选项1' },
{ value: 'value2', label: '选项2' },
{ value: 'value3', label: '选项3' },
],
component: {
showInTable: true,
showInDetail: true,
showInForm: true,
},
},
{
interface: 'checkboxes',
title: '多选框',
dataSource: [
{ value: 'value1', label: '选项1' },
{ value: 'value2', label: '选项2' },
{ value: 'value3', label: '选项3' },
],
component: {
showInTable: true,
showInDetail: true,
showInForm: true,
},
},
{
interface: 'subTable',
title: '子表格',
children: [
{
interface: 'string',
title: '标题',
component: {
showInTable: true,
showInDetail: true,
showInForm: true,
},
},
{
interface: 'textarea',
title: '内容1',
component: {
showInTable: true,
showInDetail: true,
showInForm: true,
},
},
{
interface: 'textarea',
title: '内容2',
component: {
showInTable: true,
showInDetail: true,
showInForm: true,
},
},
{
interface: 'createdBy',
title: '创建人',
component: {
showInTable: true,
showInDetail: true,
// showInForm: true,
},
},
{
interface: 'updatedBy',
title: '更新人',
component: {
showInTable: true,
showInDetail: true,
// showInForm: true,
},
},
{
interface: 'createdAt',
title: '创建日期',
component: {
showInTable: true,
showInDetail: true,
// showInForm: true,
},
},
{
interface: 'updatedAt',
title: '更新日期',
component: {
showInTable: true,
showInDetail: true,
// showInForm: true,
},
}
],
component: {
// showInTable: true,
showInDetail: true,
showInForm: true,
},
},
{
interface: 'datetime',
title: '日期',
component: {
showInTable: true,
showInDetail: true,
showInForm: true,
},
},
{
interface: 'linkTo',
type: 'belongsToMany',
title: '关联1',
target: 'examples',
labelField: 'title',
component: {
showInTable: true,
showInDetail: true,
showInForm: true,
},
},
{
interface: 'time',
title: '时间',
component: {
showInTable: true,
showInDetail: true,
showInForm: true,
},
},
{
interface: 'createdBy',
title: '创建人',
component: {
showInTable: true,
showInDetail: true,
// showInForm: true,
},
},
{
interface: 'updatedBy',
title: '更新人',
component: {
showInTable: true,
showInDetail: true,
// showInForm: true,
},
},
{
interface: 'createdAt',
title: '创建日期',
component: {
showInTable: true,
showInDetail: true,
// showInForm: true,
},
},
{
interface: 'updatedAt',
title: '更新日期',
component: {
showInTable: true,
showInDetail: true,
// showInForm: true,
},
}
],
};

View File

@ -7,7 +7,6 @@ global.sync = {
}; };
import Database from '@nocobase/database'; import Database from '@nocobase/database';
import { init as chinaRegionSeederInit } from '@nocobase/plugin-china-region/src/db/seeders';
import api from '../app'; import api from '../app';
const data = [ const data = [
@ -136,11 +135,8 @@ const data = [
]); ]);
// 导入地域数据 // 导入地域数据
await chinaRegionSeederInit(api); const ChinaRegion = database.getModel('china_regions');
ChinaRegion && await ChinaRegion.importData();
// await database.getModel('collections').import(require('./collections/example').default);
// await database.getModel('collections').import(require('./collections/authors').default);
// await database.getModel('collections').import(require('./collections/books').default);
const Menu = database.getModel('menus'); const Menu = database.getModel('menus');
@ -180,13 +176,13 @@ const data = [
], ],
}, },
{ {
title: '动态', title: '日志',
icon: 'NotificationOutlined', icon: 'NotificationOutlined',
type: 'group', type: 'group',
developerMode: true, developerMode: true,
children: [ children: [
{ {
title: '数据动态', title: '操作记录',
icon: 'DatabaseOutlined', icon: 'DatabaseOutlined',
type: 'group', type: 'group',
developerMode: true, developerMode: true,
@ -230,11 +226,11 @@ const data = [
developerMode: true, developerMode: true,
children: [ children: [
{ {
name: 'system_settings', name: 'menus',
title: '系统配置', title: '菜单和页面配置',
icon: 'DatabaseOutlined', icon: 'MenuOutlined',
type: 'page', type: 'page',
views: ['system_settings.descriptions'], views: ['menus.table'],
developerMode: true, developerMode: true,
}, },
{ {
@ -245,14 +241,6 @@ const data = [
views: ['collections.table'], views: ['collections.table'],
developerMode: true, developerMode: true,
}, },
{
name: 'menus',
title: '菜单和页面配置',
icon: 'MenuOutlined',
type: 'page',
views: ['menus.table'],
developerMode: true,
},
{ {
name: 'permissions', name: 'permissions',
title: '权限配置', title: '权限配置',
@ -269,6 +257,14 @@ const data = [
views: ['automations.table'], views: ['automations.table'],
developerMode: true, developerMode: true,
}, },
{
name: 'system_settings',
title: '系统配置',
icon: 'DatabaseOutlined',
type: 'page',
views: ['system_settings.descriptions'],
developerMode: true,
},
], ],
}, },
]; ];

View File

@ -5,6 +5,7 @@ export default {
title: '中国行政区划', title: '中国行政区划',
internal: true, internal: true,
developerMode: true, developerMode: true,
model: 'ChinaRegion',
fields: [ fields: [
// 如使用代码作为 id 可能更节省,但由于代码数字最长为 12 字节,除非使用 bigint(64) 才够放置 // 如使用代码作为 id 可能更节省,但由于代码数字最长为 12 字节,除非使用 bigint(64) 才够放置
{ {

View File

@ -1 +0,0 @@
export { default as init } from './init';

View File

@ -1,47 +0,0 @@
import { provinces, cities, areas, streets, villages } from 'china-division';
export default async function ({ database }) {
const Model = database.getModel('china_regions');
// TODO(feature): 可以考虑再加一级大区,编码规则应该是支持的
// 华北、东北、华东、华南、西南、西北、华中
const timer = Date.now();
await Model.bulkCreate(provinces.map(item => ({
code: item.code,
name: item.name,
level: 1
})));
await Model.bulkCreate(cities.map(item => ({
code: item.code,
name: item.name,
level: 2,
parent_code: item.provinceCode
})));
await Model.bulkCreate(areas.map(item => ({
code: item.code,
name: item.name,
level: 3,
parent_code: item.cityCode
})));
// // 乡级数据 2856 条
// await Model.bulkCreate(streets.map(item => ({
// code: item.code,
// name: item.name,
// level: 4,
// parent_code: item.areaCode
// })));
// // 村级数据 658001 条
// await Model.bulkCreate(villages.map(item => ({
// code: item.code,
// name: item.name,
// level: 5,
// parent_code: item.streetCode
// })));
const count = await Model.count();
console.log(`${count} rows of region data imported in ${(Date.now() - timer) / 1000}s`);
};

View File

@ -0,0 +1,47 @@
import { Model } from '@nocobase/database';
import { provinces, cities, areas, streets, villages } from 'china-division';
export class ChinaRegion extends Model {
static async importData() {
const timer = Date.now();
await this.bulkCreate(provinces.map(item => ({
code: item.code,
name: item.name,
level: 1
})));
await this.bulkCreate(cities.map(item => ({
code: item.code,
name: item.name,
level: 2,
parent_code: item.provinceCode
})));
await this.bulkCreate(areas.map(item => ({
code: item.code,
name: item.name,
level: 3,
parent_code: item.cityCode
})));
// // 乡级数据 2856 条
// await this.bulkCreate(streets.map(item => ({
// code: item.code,
// name: item.name,
// level: 4,
// parent_code: item.areaCode
// })));
// // 村级数据 658001 条
// await this.bulkCreate(villages.map(item => ({
// code: item.code,
// name: item.name,
// level: 5,
// parent_code: item.streetCode
// })));
const count = await this.count();
console.log(`${count} rows of region data imported in ${(Date.now() - timer) / 1000}s`);
}
}

View File

@ -1,18 +1,11 @@
import path from 'path'; import path from 'path';
// import actions from '@nocobase/actions'; import { registerModels } from '@nocobase/database';
import { ChinaRegion } from './models/china-region';
// 不从主入口导出,考虑到加载的数据量太大比较占内存,只在迁移时处理
// export * as seeders from './db/seeders';
export default async function (options = {}) { export default async function (options = {}) {
const { database, resourcer } = this; const { database } = this;
registerModels({ ChinaRegion });
database.import({ database.import({
directory: path.resolve(__dirname, 'collections'), directory: path.resolve(__dirname, 'collections'),
}); });
// resourcer.define({
// name: 'china_regions.china_regions',
// actions: actions.common
// });
} }

View File

@ -1,5 +0,0 @@
NODE_ENV=
DATABASE_URL=postgres://localhost:5432/database
API_BASE_PATH=/api

View File

@ -1,58 +0,0 @@
**Server** 是 NocoBase 的应用服务入口,提供一个可基于 NocoBase 低代码框架启动的应用服务容器。
应用启动后除了拥有基于框架快速提供服务的能力,也可以通过内置的插件机制安装各类插件为应用扩展更多的功能。
快速开始
----------
<!-- TODO: docker 使用方式 -->
### Docker
如果需要直接启动一个无代码应用服务,使用 Docker 是最容易的方式。
~~~shell
docker run nocobase/server
~~~
### Node 应用
~~~shell
npm install @nocobase/server
~~~
在应用入口文件(例如 `server.js`)中:
~~~JavaScript
const Server = require('@nocobase/server');
const app = new Server({
basePath: process.env.BASE_PATH,
database: process.env.DATABASE_URL
});
app.listen(3000); // start http server
~~~
启动应用:
~~~shell
node server.js
~~~
Server 继承自 Koa所以实例也可以作为其他 Koa 应用的中间件使用。
### 深度定制
NocoBase 对开发者是透明的,你可以直接 clone 仓库代码进行更复杂的扩展或定制,以实现特定业务场景的功能。
插件
----------
插件是对 NocoBase 进行扩展的机制,插件通常是一段可执行的代码,在进程启动的时被自动加载(热插拔机制将在未来提供),在加载同时会注入插件可以使用的应用上下文对象,以便对应用进行扩展。
### 加载和初始化
NocoBase 将自动加载以下规则的插件:
<!-- TODO -->

View File

@ -1,76 +0,0 @@
import Api from '../src';
import dotenv from 'dotenv';
import path from 'path';
import Database from '@nocobase/database';
const sync = {
force: true,
alter: {
drop: true,
},
};
dotenv.config();
const api = Api.create({
database: {
username: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_DATABASE,
host: process.env.DB_HOST,
port: process.env.DB_PORT,
dialect: process.env.DB_DIALECT,
dialectOptions: {
charset: 'utf8mb4',
collate: 'utf8mb4_unicode_ci',
},
logging: false,
define: {},
sync,
},
resourcer: {
prefix: '/api',
},
});
(async () => {
await api
.plugins([
[path.resolve(__dirname, '../../plugin-collections'), {}],
[path.resolve(__dirname, '../../plugin-pages'), {}],
// [require('../../plugin-collections/src/index').default, {}],
// [require('../../plugin-pages/src/index').default, {}],
]);
const database: Database = api.database;
const Collection = database.getModel('collections');
const tables = database.getTables([]);
for (let table of tables) {
const options = table.getOptions();
const collection = await Collection.create({
name: options.name,
title: options.title,
});
await collection.updateAssociations({
fields: options.fields.map(field => {
return {
type: field.type,
name: field.name,
options: field,
};
}),
});
}
// const collections = await Collection.findAll();
// await Promise.all(collections.map(async (collection) => {
// return await collection.modelInit();
// }));
api.listen(23001, () => {
console.log('http://localhost:23001/');
});
})();

View File

@ -53,11 +53,14 @@ export class Application extends Koa {
} }
protected async loadPlugin({ entry, options = {} }: { entry: string | Function, options: any }) { protected async loadPlugin({ entry, options = {} }: { entry: string | Function, options: any }) {
const main = typeof entry === 'function' let main: any;
? entry if (typeof entry === 'function') {
: require(`${entry}/${__filename.endsWith('.ts') ? 'src' : 'lib'}/server`).default; main = entry;
} else if (typeof entry === 'string') {
return await main.call(this, options); const pathname = `${entry}/${__filename.endsWith('.ts') ? 'src' : 'lib'}/server`;
main = require(pathname).default;
}
return main && await main.call(this, options);
} }
} }