feat(multi-app-manager): custom sub app start options ( #1498 ) (#1506)

Reviewed-on: daoyoucloud/tachybase#1506
Reviewed-by: sealday <zhanglin@daoyoucloud.com>
Co-authored-by: TomyJan <TomyJan6@gmail.com>
Co-committed-by: TomyJan <TomyJan6@gmail.com>
This commit is contained in:
TomyJan 2024-09-05 14:07:42 +08:00 committed by sealday
parent 829adcfc4e
commit 9900aa24a8
10 changed files with 173 additions and 4 deletions

View File

@ -10,7 +10,7 @@ import { telemetryOptions } from './config';
import { Metric, MetricOptions } from './metric'; import { Metric, MetricOptions } from './metric';
import { Trace, TraceOptions } from './trace'; import { Trace, TraceOptions } from './trace';
export { telemetryOptions } from './config'; export { telemetryOptions };
export interface TelemetryOptions { export interface TelemetryOptions {
serviceName?: string; serviceName?: string;

View File

@ -11,3 +11,25 @@ yarn pm enable multi-app-manager
``` ```
## 使用方法 ## 使用方法
注意目前仅支持部分在应用启动时传入的环境变量,具体列表如下:
```ini
APP_KEY=test-key
API_BASE_PATH=/api/
DB_DIALECT=sqlite
DB_STORAGE=storage/db/tachybase.sqlite
DB_TABLE_PREFIX=
DB_HOST=localhost
DB_PORT=5432
DB_DATABASE=postgres
DB_USER=tachybase
DB_PASSWORD=tachybase
DB_UNDERSCORED=false
DB_TIMEZONE=+08:00
DB_TABLE_PREFIX=
DB_SCHEMA=
CACHE_DEFAULT_STORE=memory
CACHE_MEMORY_MAX=2000
```

View File

@ -11,3 +11,25 @@ yarn pm enable multi-app-manager
``` ```
## 使用方法 ## 使用方法
注意目前仅支持部分在应用启动时传入的环境变量,具体列表如下:
```ini
APP_KEY=test-key
API_BASE_PATH=/api/
DB_DIALECT=sqlite
DB_STORAGE=storage/db/tachybase.sqlite
DB_TABLE_PREFIX=
DB_HOST=localhost
DB_PORT=5432
DB_DATABASE=postgres
DB_USER=tachybase
DB_PASSWORD=tachybase
DB_UNDERSCORED=false
DB_TIMEZONE=+08:00
DB_TABLE_PREFIX=
DB_SCHEMA=
CACHE_DEFAULT_STORE=memory
CACHE_MEMORY_MAX=2000
```

View File

@ -168,6 +168,12 @@ export const formSchema: ISchema = {
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-disabled': '{{ !createOnly }}', 'x-disabled': '{{ !createOnly }}',
}, },
'options.startEnvs': {
type: 'string',
title: i18nText('Start environment variables'),
'x-component': 'Input.TextArea',
'x-decorator': 'FormItem',
},
pinned: { pinned: {
'x-component': 'CollectionField', 'x-component': 'CollectionField',
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',

View File

@ -20,5 +20,6 @@
"Start": "Start", "Start": "Start",
"Stop": "Stop", "Stop": "Stop",
"Refresh": "Refresh", "Refresh": "Refresh",
"Template is in use": "Template is in use" "Template is in use": "Template is in use",
"Start environment variables": "Start environment variables"
} }

View File

@ -20,5 +20,6 @@
"Start": "启动", "Start": "启动",
"Stop": "停止", "Stop": "停止",
"Refresh": "刷新", "Refresh": "刷新",
"Template is in use": "模板正在使用" "Template is in use": "模板正在使用",
"Start environment variables": "启动环境变量"
} }

View File

@ -41,6 +41,19 @@ export async function create(ctx: Context, next: Next) {
const params = ctx.action.params; const params = ctx.action.params;
const tmpl = params.values?.tmpl; const tmpl = params.values?.tmpl;
if (tmpl) { if (tmpl) {
const startEnvs = params.values?.options?.startEnvs;
if (startEnvs) {
const dbDialect = startEnvs.split('\n').find((line) => line.startsWith('DB_DIALECT='));
if (dbDialect) {
const dbType = dbDialect.split('=')[1].trim();
if (dbType !== 'postgres') {
ctx.throw(
400,
ctx.t('This database does not support to create application using template', { ns: NAMESPACE }),
);
}
}
}
const dbType = ctx.db.options.dialect; const dbType = ctx.db.options.dialect;
if (dbType !== 'postgres') { if (dbType !== 'postgres') {
ctx.throw(400, ctx.t('This database does not support to create application using template', { ns: NAMESPACE })); ctx.throw(400, ctx.t('This database does not support to create application using template', { ns: NAMESPACE }));

View File

@ -0,0 +1,91 @@
import { Application } from '@tachybase/server';
export default function mergeApplicationStartEnvs(appName: String, mainApp: Application, options: any, startEnvs: any) {
if (typeof startEnvs === 'string') {
const envs = startEnvs.trim().split('\n');
startEnvs = {};
for (const env of envs) {
const [key, value] = env.split('=');
startEnvs[key] = value;
}
}
if (startEnvs && Object.keys(startEnvs).length !== 0) {
// TODO: 因为环境变量和对应的配置项无法直接平铺,所以需要逐个匹配,以后或许有更好的解决方案
if (startEnvs.APP_KEY) {
options = {
...options,
authManager: {
jwt: {
secret: startEnvs.APP_KEY,
},
},
};
delete startEnvs.APP_KEY;
}
if (startEnvs.API_BASE_PATH) {
options.resourcer.prefix = startEnvs.API_BASE_PATH;
delete startEnvs.API_BASE_PATH;
}
if (startEnvs.DB_DIALECT) {
options.database.dialect = startEnvs.DB_DIALECT;
delete startEnvs.DB_DIALECT;
}
if (startEnvs.DB_STORAGE) {
options.database.storage = startEnvs.DB_STORAGE;
delete startEnvs.DB_STORAGE;
}
if (startEnvs.DB_TABLE_PREFIX) {
options.database.tablePrefix = startEnvs.DB_TABLE_PREFIX;
delete startEnvs.DB_TABLE_PREFIX;
}
if (startEnvs.DB_HOST) {
options.database.host = startEnvs.DB_HOST;
delete startEnvs.DB_HOST;
}
if (startEnvs.DB_PORT) {
options.database.port = parseInt(startEnvs.DB_PORT);
delete startEnvs.DB_PORT;
}
if (startEnvs.DB_DATABASE) {
options.database.database = startEnvs.DB_DATABASE;
delete startEnvs.DB_DATABASE;
}
if (startEnvs.DB_USER) {
options.database.username = startEnvs.DB_USER;
delete startEnvs.DB_USER;
}
if (startEnvs.DB_PASSWORD) {
options.database.password = startEnvs.DB_PASSWORD;
delete startEnvs.DB_PASSWORD;
}
if (startEnvs.DB_UNDERSCORED) {
options.database.underscored = startEnvs.DB_UNDERSCORED === 'true';
delete startEnvs.DB_UNDERSCORED;
}
if (startEnvs.DB_TIMEZONE) {
options.database.timezone = startEnvs.DB_TIMEZONE;
delete startEnvs.DB_TIMEZONE;
}
if (startEnvs.DB_TABLE_PREFIX) {
options.database.tablePrefix = startEnvs.DB_TABLE_PREFIX;
delete startEnvs.DB_TABLE_PREFIX;
}
if (startEnvs.DB_SCHEMA) {
options.database.schema = startEnvs.DB_SCHEMA;
delete startEnvs.DB_SCHEMA;
}
if (startEnvs.CACHE_DEFAULT_STORE) {
options.cacheManager.defaultStore = startEnvs.CACHE_DEFAULT_STORE;
delete startEnvs.CACHE_DEFAULT_STORE;
}
if (startEnvs.CACHE_MEMORY_MAX) {
options.cacheManager.stores.memory.max = parseInt(startEnvs.CACHE_MEMORY_MAX);
delete startEnvs.CACHE_MEMORY_MAX;
}
if (Object.keys(startEnvs).length !== 0) {
mainApp.logger.warn(`Application ${appName} has unsupported startEnvs: ${JSON.stringify(startEnvs)}`);
}
}
return options;
}

View File

@ -2,6 +2,7 @@ import { Model, Transactionable } from '@tachybase/database';
import { Application } from '@tachybase/server'; import { Application } from '@tachybase/server';
import { merge } from '@tachybase/utils'; import { merge } from '@tachybase/utils';
import mergeApplicationStartEnvs from '../app-start-env';
import { AppOptionsFactory } from '../server'; import { AppOptionsFactory } from '../server';
export interface registerAppOptions extends Transactionable { export interface registerAppOptions extends Transactionable {
@ -15,8 +16,10 @@ export class ApplicationModel extends Model {
const preset = this.get('preset') as string; const preset = this.get('preset') as string;
const tmpl = this.get('tmpl') as string; const tmpl = this.get('tmpl') as string;
const appModelOptions = (this.get('options') as any) || {}; const appModelOptions = (this.get('options') as any) || {};
const startEnvs = appModelOptions.startEnvs || {};
const appOptions = options.appOptionsFactory(appName, mainApp, preset); let appOptions = options.appOptionsFactory(appName, mainApp, preset);
appOptions = mergeApplicationStartEnvs(appName, mainApp, appOptions, startEnvs);
const subAppOptions = { const subAppOptions = {
...(merge(appOptions, appModelOptions) as object), ...(merge(appOptions, appModelOptions) as object),

View File

@ -176,8 +176,18 @@ export default {
example: true, example: true,
description: '应用是否默认跟随主应用启动', description: '应用是否默认跟随主应用启动',
}, },
startEnvs: {
type: 'string',
example: 'APP_KEY=test-key\nAPI_BASE_PATH=/api/',
description: '应用启动时的环境变量',
}, },
}, },
},
tmpl: {
type: 'string',
example: 'default',
description: '模板应用的 ID',
},
createdAt: { createdAt: {
type: 'string', type: 'string',
format: 'date-time', format: 'date-time',