diff --git a/packages/database/src/table.ts b/packages/database/src/table.ts index 52e24b4b8..c2d3e007d 100644 --- a/packages/database/src/table.ts +++ b/packages/database/src/table.ts @@ -180,7 +180,8 @@ export class Table { public modelInit(reinitialize: Reinitialize = false) { if (reinitialize || !this.Model) { - this.Model = this.defaultModel || class extends Model { }; + let DefaultModel = this.defaultModel; + this.Model = DefaultModel ? (class extends DefaultModel {}) : (class extends Model { }); this.Model.database = this.database; // 关系的建立是在 model.init 之后,在配置中表字段(Column)和关系(Relation)都在 fields, // 所以需要单独提炼出 associations 字段,并在 Model.init 之后执行 Model.associate diff --git a/packages/plugin-saas/package.json b/packages/plugin-saas/package.json index f2966ad4e..ac9f3792d 100644 --- a/packages/plugin-saas/package.json +++ b/packages/plugin-saas/package.json @@ -10,8 +10,5 @@ "deepmerge": "^4.2.2", "flat-to-nested": "^1.1.1" }, - "devDependencies": { - "@nocobase/actions": "^0.4.0-alpha.7" - }, "gitHead": "f0b335ac30f29f25c95d7d137655fa64d8d67f1e" } diff --git a/packages/plugin-saas/src/server.ts b/packages/plugin-saas/src/server.ts new file mode 100644 index 000000000..7ca973381 --- /dev/null +++ b/packages/plugin-saas/src/server.ts @@ -0,0 +1,146 @@ +import compose from 'koa-compose'; +import { Application, PluginOptions } from '@nocobase/server'; +import Koa from 'koa'; + +function createApp(opts) { + const { name } = opts; + const options = { + 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 as any, + dialect: process.env.DB_DIALECT as any, + dialectOptions: { + charset: 'utf8mb4', + collate: 'utf8mb4_unicode_ci', + }, + pool: { + max: 5, + min: 0, + acquire: 60000, + idle: 10000, + }, + logging: process.env.DB_LOG_SQL === 'on' ? console.log : false, + define: {}, + sync: { + force: false, + alter: { + drop: false, + }, + }, + }, + // dataWrapping: false, + resourcer: { + prefix: `/api/saas/${name}`, + }, + }; + const app = new Application(options); + + app.db.sequelize.beforeDefine((model, options) => { + options.tableName = `saas_${name}_${ + options.tableName || options.name.plural + }`; + }); + + app.resource({ + name: 'saas', + actions: { + async getInfo(ctx, next) { + ctx.body = { + m: Object.values(ctx.db.sequelize.models).map((m: any) => m.tableName), + }; + await next(); + }, + }, + }); + + const plugins = [ + '@nocobase/plugin-collections', + '@nocobase/plugin-ui-router', + '@nocobase/plugin-ui-schema', + '@nocobase/plugin-users', + '@nocobase/plugin-action-logs', + '@nocobase/plugin-file-manager', + '@nocobase/plugin-permissions', + '@nocobase/plugin-export', + '@nocobase/plugin-system-settings', + '@nocobase/plugin-china-region', + ]; + + for (const plugin of plugins) { + app.plugin( + require(`${plugin}/${__filename.endsWith('.ts') ? 'src' : 'lib'}/server`) + .default, + ); + } + + return app; +} + +function multiApps({ getAppName }) { + return async function (ctx: Koa.Context, next) { + const appName = getAppName(ctx); + if (!appName) { + return next(); + } + const App = ctx.db.getModel('applications'); + const model = await App.findOne({ + where: { name: appName }, + }); + if (!model) { + return next(); + } + const apps = ctx.app['apps']; + if (!apps.has(appName)) { + const app = createApp({ + name: appName, + }); + await app.load(); + apps.set(appName, app); + } + const app = apps.get(appName); + // 完全隔离的做法 + const handleRequest = app.callback(); + await handleRequest(ctx.req, ctx.res); + }; +} + +export default { + name: 'saas', + async load() { + this.app['apps'] = new Map(); + this.app.collection({ + name: 'applications', + fields: [ + { type: 'string', name: 'name', unique: true }, + { type: 'belongsTo', name: 'user' }, + ], + }); + this.app.use( + multiApps({ + getAppName(ctx) { + return ctx.path.split('/')[3]; + }, + }), + ); + this.app + .command('app:create') + .argument('') + .action(async (appName) => { + const App = this.app.db.getModel('applications'); + const model = await App.findOne({ + where: { + name: appName, + }, + }); + if (!model) { + await App.create({ + name: appName, + }); + } + await this.app.destroy(); + }); + }, +} as PluginOptions; diff --git a/packages/server/src/application.ts b/packages/server/src/application.ts index d253e1b37..a431a82c5 100644 --- a/packages/server/src/application.ts +++ b/packages/server/src/application.ts @@ -104,7 +104,6 @@ export class Application< console.log('db sync...'); const cli = args.pop(); const force = cli.opts()?.force; - await this.load(); await this.db.sync( force ? { @@ -123,7 +122,6 @@ export class Application< // .option('-f, --force') .action(async (...args) => { const cli = args.pop(); - await this.load(); await this.db.sync({ force: true, alter: { @@ -141,7 +139,6 @@ export class Application< const cli = args.pop(); console.log(args); const opts = cli.opts(); - await this.load(); await this.emitAsync('beforeStart'); this.listen(opts.port || 3000); console.log(`http://localhost:${opts.port || 3000}/`); @@ -274,6 +271,7 @@ export class Application< } async parse(argv = process.argv) { + await this.load(); return this.cli.parseAsync(argv); }