diff --git a/internal/apiserver/conf/options.go b/internal/apiserver/conf/options.go index 59ef2b3c..57e8f0f6 100644 --- a/internal/apiserver/conf/options.go +++ b/internal/apiserver/conf/options.go @@ -4,7 +4,6 @@ import ( dmsCommonConf "github.com/actiontech/dms/pkg/dms-common/conf" utilConf "github.com/actiontech/dms/pkg/dms-common/pkg/config" utilLog "github.com/actiontech/dms/pkg/dms-common/pkg/log" - pkgParams "github.com/actiontech/dms/pkg/params" ) type Options struct { @@ -21,9 +20,8 @@ type SQLEOptions struct { type DMSOptions struct { dmsCommonConf.BaseOptions `yaml:",inline"` - CloudbeaverOpts *CloudbeaverOpts `yaml:"cloudbeaver"` - ServiceOpts *ServiceOptions `yaml:"service"` - DatabaseDriverOptions []DatabaseDriverOption `yaml:"database_driver_options"` + CloudbeaverOpts *CloudbeaverOpts `yaml:"cloudbeaver"` + ServiceOpts *ServiceOptions `yaml:"service"` } type CloudbeaverOpts struct { @@ -52,12 +50,6 @@ type ServiceOptions struct { } `yaml:"log"` } -type DatabaseDriverOption struct { - DbType string `yaml:"db_type"` - LogoPath string `yaml:"logo_path"` - Params pkgParams.Params `yaml:"params"` -} - var optimizationEnabled bool func IsOptimizationEnabled() bool { diff --git a/internal/dms/biz/db_service.go b/internal/dms/biz/db_service.go index dc10c41e..2ecc49c0 100644 --- a/internal/dms/biz/db_service.go +++ b/internal/dms/biz/db_service.go @@ -7,7 +7,6 @@ import ( "time" dmsV1 "github.com/actiontech/dms/api/dms/service/v1" - "github.com/actiontech/dms/internal/apiserver/conf" pkgConst "github.com/actiontech/dms/internal/dms/pkg/constant" "github.com/actiontech/dms/internal/pkg/locale" v1Base "github.com/actiontech/dms/pkg/dms-common/api/base/v1" @@ -166,18 +165,16 @@ type DBServiceUsecase struct { pluginUsecase *PluginUsecase opPermissionVerifyUsecase *OpPermissionVerifyUsecase projectUsecase *ProjectUsecase - databaseDriverOptions []conf.DatabaseDriverOption log *utilLog.Helper } -func NewDBServiceUsecase(log utilLog.Logger, repo DBServiceRepo, pluginUsecase *PluginUsecase, opPermissionVerifyUsecase *OpPermissionVerifyUsecase, projectUsecase *ProjectUsecase, proxyTargetRepo ProxyTargetRepo, databaseDriverOptions []conf.DatabaseDriverOption) *DBServiceUsecase { +func NewDBServiceUsecase(log utilLog.Logger, repo DBServiceRepo, pluginUsecase *PluginUsecase, opPermissionVerifyUsecase *OpPermissionVerifyUsecase, projectUsecase *ProjectUsecase, proxyTargetRepo ProxyTargetRepo) *DBServiceUsecase { return &DBServiceUsecase{ repo: repo, opPermissionVerifyUsecase: opPermissionVerifyUsecase, pluginUsecase: pluginUsecase, projectUsecase: projectUsecase, dmsProxyTargetRepo: proxyTargetRepo, - databaseDriverOptions: databaseDriverOptions, log: utilLog.NewHelper(log, utilLog.WithMessageKey("biz.dbService")), } } @@ -450,8 +447,12 @@ func (d *DBServiceUsecase) ListDBServiceTips(ctx context.Context, req *dmsV1.Lis return ret, nil } -func (d *DBServiceUsecase) ListDBServiceDriverOption(ctx context.Context) ([]conf.DatabaseDriverOption, error) { - return d.databaseDriverOptions, nil +func (d *DBServiceUsecase) ListDBServiceDriverOption(ctx context.Context) ([]*dmsV1.DatabaseDriverOption, error) { + options, err := d.pluginUsecase.GetDatabaseDriverOptionsHandle(ctx) + if err != nil { + return nil, err + } + return options, nil } func (d *DBServiceUsecase) GetDriverParamsByDBType(ctx context.Context, dbType string) (pkgParams.Params, error) { @@ -460,14 +461,27 @@ func (d *DBServiceUsecase) GetDriverParamsByDBType(ctx context.Context, dbType s return nil, err } for _, driverOptions := range databaseOptions { - if driverOptions.DbType == dbType { - return driverOptions.Params, nil + if driverOptions.DBType == dbType { + return convertAdditionParamsToParams(driverOptions.Params), nil } } return nil, fmt.Errorf("db type %v is not support", dbType) } +func convertAdditionParamsToParams(additionalParam []*dmsV1.DatabaseDriverAdditionalParam) pkgParams.Params { + params := make(pkgParams.Params, len(additionalParam)) + for i, item := range additionalParam { + params[i] = &pkgParams.Param{ + Key: item.Name, + Value: item.Value, + Desc: item.Description, + Type: pkgParams.ParamType(item.Type), + } + } + return params +} + func (d *DBServiceUsecase) GetActiveDBServices(ctx context.Context, dbServiceIds []string) (dbServices []*DBService, err error) { services, err := d.repo.GetDBServicesByIds(ctx, dbServiceIds) if err != nil { diff --git a/internal/dms/biz/plugin.go b/internal/dms/biz/plugin.go index a2f14a82..b46adce6 100644 --- a/internal/dms/biz/plugin.go +++ b/internal/dms/biz/plugin.go @@ -4,11 +4,13 @@ import ( "context" "encoding/json" "fmt" + "strings" "sync" + v1 "github.com/actiontech/dms/api/dms/service/v1" pkgConst "github.com/actiontech/dms/internal/dms/pkg/constant" - dmsV1 "github.com/actiontech/dms/pkg/dms-common/api/dms/v1" + _const "github.com/actiontech/dms/pkg/dms-common/pkg/const" pkgHttp "github.com/actiontech/dms/pkg/dms-common/pkg/http" utilLog "github.com/actiontech/dms/pkg/dms-common/pkg/log" @@ -32,6 +34,7 @@ type Plugin struct { // eg: 删除数据源前: // 需要sqle服务中实现接口逻辑,判断该数据源上已经没有进行中的工单 OperateDataResourceHandleUrl string + GetDatabaseDriverOptionsUrl string } func (p *Plugin) String() string { @@ -241,3 +244,141 @@ func (p *PluginUsecase) CallOperateDataResourceHandle(ctx context.Context, url s return nil } + +const LogoPath = "/logo/" + +var databaseDriverOptions []*v1.DatabaseDriverOption + +func (p *PluginUsecase) GetDatabaseDriverOptionsCache() []*v1.DatabaseDriverOption { + return databaseDriverOptions +} + +func (p *PluginUsecase) ClearDatabaseDriverOptionsCache() { + databaseDriverOptions = []*v1.DatabaseDriverOption{} +} + +func (p *PluginUsecase) GetDatabaseDriverOptionsHandle(ctx context.Context) ([]*v1.DatabaseDriverOption, error) { + cacheOptions := p.GetDatabaseDriverOptionsCache() + if len(cacheOptions) != 0 { + return cacheOptions, nil + } + var ( + mu sync.Mutex + errs []error + wg sync.WaitGroup + dbOptions []struct { + options []*v1.DatabaseDriverOption + source string + } + ) + + for _, plugin := range p.registeredPlugins { + if plugin.GetDatabaseDriverOptionsUrl != "" { + wg.Add(1) + go func(plugin *Plugin) { + defer wg.Done() + op, err := p.CallDatabaseDriverOptionsHandle(ctx, plugin.GetDatabaseDriverOptionsUrl) + if err != nil { + mu.Lock() + errs = append(errs, fmt.Errorf("call plugin %s get database driver options handle failed: %v", plugin.Name, err)) + mu.Unlock() + return + } + mu.Lock() + dbOptions = append(dbOptions, struct { + options []*v1.DatabaseDriverOption + source string + }{ + options: op, + source: plugin.Name, + }) + mu.Unlock() + }(plugin) + } + } + + wg.Wait() + + if len(errs) > 0 { + return nil, fmt.Errorf("encountered errors: %v", errs) + } + databaseDriverOptions = append(databaseDriverOptions, aggregateOptions(dbOptions)...) + return databaseDriverOptions, nil +} + +// 根据数据库类型合并各插件的options +func aggregateOptions(optionRes []struct { + options []*v1.DatabaseDriverOption + source string +}) []*v1.DatabaseDriverOption { + dbTypeMap := make(map[string]*v1.DatabaseDriverOption) + + for _, res := range optionRes { + for _, opt := range res.options { + if aggOpt, exists := dbTypeMap[opt.DBType]; exists { + // 聚合Params, 合并时如有重复以sqle为主 + aggOpt.Params = mergeParamsByName(aggOpt.Params, opt.Params, res.source == _const.SqleComponentName) + } else { + dbTypeMap[opt.DBType] = &v1.DatabaseDriverOption{ + DBType: opt.DBType, + LogoPath: LogoPath + getLogoFileNameByDBType(opt.DBType), + Params: opt.Params, + } + } + } + } + + // 转换为切片返回 + result := make([]*v1.DatabaseDriverOption, 0, len(dbTypeMap)) + for _, opt := range dbTypeMap { + result = append(result, opt) + } + return result +} + +func getLogoFileNameByDBType(dbType string) string { + return strings.ToLower(strings.ReplaceAll(dbType, " ", "_")) + ".png" +} + +// 根据参数名合并additional和params, overwriteExisting代表是不是要以新参数覆盖旧参数 +func mergeParamsByName(existing, newParams []*v1.DatabaseDriverAdditionalParam, overwriteExisting bool) []*v1.DatabaseDriverAdditionalParam { + paramMap := make(map[string]*v1.DatabaseDriverAdditionalParam) + + // 添加已有参数 + for _, param := range existing { + paramMap[param.Name] = param + } + + // 合并新参数 + for _, param := range newParams { + if _, exists := paramMap[param.Name]; exists && overwriteExisting { + newAggParam := *param + paramMap[param.Name] = &newAggParam // 覆盖已有参数 + } else if !exists { + paramMap[param.Name] = param + } + } + + // 转换为切片返回 + result := make([]*v1.DatabaseDriverAdditionalParam, 0, len(paramMap)) + for _, param := range paramMap { + result = append(result, param) + } + return result +} + +func (p *PluginUsecase) CallDatabaseDriverOptionsHandle(ctx context.Context, url string) ([]*v1.DatabaseDriverOption, error) { + header := map[string]string{ + "Authorization": pkgHttp.DefaultDMSToken, + } + reply := &v1.ListDBServiceDriverOptionReply{} + + if err := pkgHttp.Get(ctx, url, header, nil, reply); err != nil { + return nil, err + } + if reply.Code != 0 { + return nil, fmt.Errorf("reply code(%v) error: %v", reply.Code, reply.Message) + } + + return reply.Data, nil +} diff --git a/internal/dms/service/cloudbeaver.go b/internal/dms/service/cloudbeaver.go index faf8327d..a58a799e 100644 --- a/internal/dms/service/cloudbeaver.go +++ b/internal/dms/service/cloudbeaver.go @@ -48,7 +48,7 @@ func NewAndInitCloudbeaverService(logger utilLog.Logger, opts *conf.DMSOptions) projectRepo := storage.NewProjectRepo(logger, st) projectUsecase := biz.NewProjectUsecase(logger, tx, projectRepo, memberUsecase, opPermissionVerifyUsecase, pluginUseCase) dbServiceRepo := storage.NewDBServiceRepo(logger, st) - dbServiceUseCase := biz.NewDBServiceUsecase(logger, dbServiceRepo, pluginUseCase, opPermissionVerifyUsecase, projectUsecase, dmsProxyTargetRepo, opts.DatabaseDriverOptions) + dbServiceUseCase := biz.NewDBServiceUsecase(logger, dbServiceRepo, pluginUseCase, opPermissionVerifyUsecase, projectUsecase, dmsProxyTargetRepo) ldapConfigurationRepo := storage.NewLDAPConfigurationRepo(logger, st) ldapConfigurationUsecase := biz.NewLDAPConfigurationUsecase(logger, tx, ldapConfigurationRepo) diff --git a/internal/dms/service/db_service.go b/internal/dms/service/db_service.go index fd28e2d6..7e7b3bf7 100644 --- a/internal/dms/service/db_service.go +++ b/internal/dms/service/db_service.go @@ -610,20 +610,10 @@ func (d *DMSService) ListDBServiceDriverOption(ctx context.Context) (reply *dmsV ret := make([]*dmsV1.DatabaseDriverOption, 0, len(options)) for _, item := range options { - additionalParams := make([]*dmsV1.DatabaseDriverAdditionalParam, 0, len(item.Params)) - for _, param := range item.Params { - additionalParams = append(additionalParams, &dmsV1.DatabaseDriverAdditionalParam{ - Name: param.Key, - Value: param.Value, - Type: string(param.Type), - Description: param.Desc, - }) - } - ret = append(ret, &dmsV1.DatabaseDriverOption{ - DBType: item.DbType, + DBType: item.DBType, LogoPath: item.LogoPath, - Params: additionalParams, + Params: item.Params, }) } diff --git a/internal/dms/service/plugin.go b/internal/dms/service/plugin.go index d4440c4f..8b114a5c 100644 --- a/internal/dms/service/plugin.go +++ b/internal/dms/service/plugin.go @@ -18,9 +18,11 @@ func (d *DMSService) RegisterDMSPlugin(ctx context.Context, currentUserUid strin if err := d.PluginUsecase.RegisterPlugin(ctx, &biz.Plugin{ Name: req.Plugin.Name, OperateDataResourceHandleUrl: req.Plugin.OperateDataResourceHandleUrl, + GetDatabaseDriverOptionsUrl: req.Plugin.GetDatabaseDriverOptionsUrl, }, currentUserUid); err != nil { return fmt.Errorf("register dms plugin failed: %v", err) } - + // 当有plugin注册时,初始化切片,重新调用接口获取数据库选项 + d.PluginUsecase.ClearDatabaseDriverOptionsCache() return nil } diff --git a/internal/dms/service/service.go b/internal/dms/service/service.go index 3a6a42b0..36a1ab6e 100644 --- a/internal/dms/service/service.go +++ b/internal/dms/service/service.go @@ -13,35 +13,35 @@ import ( ) type DMSService struct { - BasicUsecase *biz.BasicUsecase - PluginUsecase *biz.PluginUsecase - DBServiceUsecase *biz.DBServiceUsecase - DBServiceSyncTaskUsecase *biz.DBServiceSyncTaskUsecase - UserUsecase *biz.UserUsecase - UserGroupUsecase *biz.UserGroupUsecase - RoleUsecase *biz.RoleUsecase - OpPermissionUsecase *biz.OpPermissionUsecase - MemberUsecase *biz.MemberUsecase - MemberGroupUsecase *biz.MemberGroupUsecase - OpPermissionVerifyUsecase *biz.OpPermissionVerifyUsecase - ProjectUsecase *biz.ProjectUsecase - DmsProxyUsecase *biz.DmsProxyUsecase - Oauth2ConfigurationUsecase *biz.Oauth2ConfigurationUsecase - LDAPConfigurationUsecase *biz.LDAPConfigurationUsecase - SMTPConfigurationUsecase *biz.SMTPConfigurationUsecase - WeChatConfigurationUsecase *biz.WeChatConfigurationUsecase - WebHookConfigurationUsecase *biz.WebHookConfigurationUsecase - IMConfigurationUsecase *biz.IMConfigurationUsecase - CompanyNoticeUsecase *biz.CompanyNoticeUsecase - LicenseUsecase *biz.LicenseUsecase - ClusterUsecase *biz.ClusterUsecase - DataExportWorkflowUsecase *biz.DataExportWorkflowUsecase - CbOperationLogUsecase *biz.CbOperationLogUsecase - DataMaskingUsecase *biz.DataMaskingUsecase - AuthAccessTokenUseCase *biz.AuthAccessTokenUsecase - SwaggerUseCase *biz.SwaggerUseCase - log *utilLog.Helper - shutdownCallback func() error + BasicUsecase *biz.BasicUsecase + PluginUsecase *biz.PluginUsecase + DBServiceUsecase *biz.DBServiceUsecase + DBServiceSyncTaskUsecase *biz.DBServiceSyncTaskUsecase + UserUsecase *biz.UserUsecase + UserGroupUsecase *biz.UserGroupUsecase + RoleUsecase *biz.RoleUsecase + OpPermissionUsecase *biz.OpPermissionUsecase + MemberUsecase *biz.MemberUsecase + MemberGroupUsecase *biz.MemberGroupUsecase + OpPermissionVerifyUsecase *biz.OpPermissionVerifyUsecase + ProjectUsecase *biz.ProjectUsecase + DmsProxyUsecase *biz.DmsProxyUsecase + Oauth2ConfigurationUsecase *biz.Oauth2ConfigurationUsecase + LDAPConfigurationUsecase *biz.LDAPConfigurationUsecase + SMTPConfigurationUsecase *biz.SMTPConfigurationUsecase + WeChatConfigurationUsecase *biz.WeChatConfigurationUsecase + WebHookConfigurationUsecase *biz.WebHookConfigurationUsecase + IMConfigurationUsecase *biz.IMConfigurationUsecase + CompanyNoticeUsecase *biz.CompanyNoticeUsecase + LicenseUsecase *biz.LicenseUsecase + ClusterUsecase *biz.ClusterUsecase + DataExportWorkflowUsecase *biz.DataExportWorkflowUsecase + CbOperationLogUsecase *biz.CbOperationLogUsecase + DataMaskingUsecase *biz.DataMaskingUsecase + AuthAccessTokenUseCase *biz.AuthAccessTokenUsecase + SwaggerUseCase *biz.SwaggerUseCase + log *utilLog.Helper + shutdownCallback func() error } func NewAndInitDMSService(logger utilLog.Logger, opts *conf.DMSOptions) (*DMSService, error) { @@ -72,7 +72,7 @@ func NewAndInitDMSService(logger utilLog.Logger, opts *conf.DMSOptions) (*DMSSer projectUsecase := biz.NewProjectUsecase(logger, tx, projectRepo, &memberUsecase, opPermissionVerifyUsecase, pluginUseCase) dbServiceRepo := storage.NewDBServiceRepo(logger, st) dmsProxyTargetRepo := storage.NewProxyTargetRepo(logger, st) - dbServiceUseCase := biz.NewDBServiceUsecase(logger, dbServiceRepo, pluginUseCase, opPermissionVerifyUsecase, projectUsecase, dmsProxyTargetRepo, opts.DatabaseDriverOptions) + dbServiceUseCase := biz.NewDBServiceUsecase(logger, dbServiceRepo, pluginUseCase, opPermissionVerifyUsecase, projectUsecase, dmsProxyTargetRepo) dbServiceTaskRepo := storage.NewDBServiceSyncTaskRepo(logger, st) dbServiceTaskUsecase := biz.NewDBServiceSyncTaskUsecase(logger, dbServiceTaskRepo, opPermissionVerifyUsecase, projectUsecase, dbServiceUseCase) ldapConfigurationRepo := storage.NewLDAPConfigurationRepo(logger, st) @@ -132,34 +132,34 @@ func NewAndInitDMSService(logger utilLog.Logger, opts *conf.DMSOptions) (*DMSSer } s := &DMSService{ - BasicUsecase: basicUsecase, - PluginUsecase: pluginUseCase, - DBServiceUsecase: dbServiceUseCase, - DBServiceSyncTaskUsecase: dbServiceTaskUsecase, - UserUsecase: userUsecase, - UserGroupUsecase: userGroupUsecase, - RoleUsecase: roleUsecase, - OpPermissionUsecase: opPermissionUsecase, - MemberUsecase: &memberUsecase, - MemberGroupUsecase: memberGroupUsecase, - OpPermissionVerifyUsecase: opPermissionVerifyUsecase, - ProjectUsecase: projectUsecase, - DmsProxyUsecase: dmsProxyUsecase, - Oauth2ConfigurationUsecase: oauth2ConfigurationUsecase, - LDAPConfigurationUsecase: ldapConfigurationUsecase, - SMTPConfigurationUsecase: smtpConfigurationUsecase, - WeChatConfigurationUsecase: wechatConfigurationUsecase, - WebHookConfigurationUsecase: webhookConfigurationUsecase, - IMConfigurationUsecase: imConfigurationUsecase, - CompanyNoticeUsecase: companyNoticeRepoUsecase, - LicenseUsecase: LicenseUsecase, - ClusterUsecase: clusterUsecase, - DataExportWorkflowUsecase: DataExportWorkflowUsecase, - CbOperationLogUsecase: CbOperationLogUsecase, - DataMaskingUsecase: dataMaskingUsecase, - AuthAccessTokenUseCase: authAccessTokenUsecase, - SwaggerUseCase: swaggerUseCase, - log: utilLog.NewHelper(logger, utilLog.WithMessageKey("dms.service")), + BasicUsecase: basicUsecase, + PluginUsecase: pluginUseCase, + DBServiceUsecase: dbServiceUseCase, + DBServiceSyncTaskUsecase: dbServiceTaskUsecase, + UserUsecase: userUsecase, + UserGroupUsecase: userGroupUsecase, + RoleUsecase: roleUsecase, + OpPermissionUsecase: opPermissionUsecase, + MemberUsecase: &memberUsecase, + MemberGroupUsecase: memberGroupUsecase, + OpPermissionVerifyUsecase: opPermissionVerifyUsecase, + ProjectUsecase: projectUsecase, + DmsProxyUsecase: dmsProxyUsecase, + Oauth2ConfigurationUsecase: oauth2ConfigurationUsecase, + LDAPConfigurationUsecase: ldapConfigurationUsecase, + SMTPConfigurationUsecase: smtpConfigurationUsecase, + WeChatConfigurationUsecase: wechatConfigurationUsecase, + WebHookConfigurationUsecase: webhookConfigurationUsecase, + IMConfigurationUsecase: imConfigurationUsecase, + CompanyNoticeUsecase: companyNoticeRepoUsecase, + LicenseUsecase: LicenseUsecase, + ClusterUsecase: clusterUsecase, + DataExportWorkflowUsecase: DataExportWorkflowUsecase, + CbOperationLogUsecase: CbOperationLogUsecase, + DataMaskingUsecase: dataMaskingUsecase, + AuthAccessTokenUseCase: authAccessTokenUsecase, + SwaggerUseCase: swaggerUseCase, + log: utilLog.NewHelper(logger, utilLog.WithMessageKey("dms.service")), shutdownCallback: func() error { if err := st.Close(); nil != err { return fmt.Errorf("failed to close storage: %v", err) diff --git a/internal/dms/storage/convert.go b/internal/dms/storage/convert.go index f5fd18c3..8d2ac557 100644 --- a/internal/dms/storage/convert.go +++ b/internal/dms/storage/convert.go @@ -582,6 +582,7 @@ func convertBizPlugin(t *biz.Plugin) (*model.Plugin, error) { return &model.Plugin{ Name: t.Name, OperateDataResourceHandleUrl: t.OperateDataResourceHandleUrl, + GetDatabaseDriverOptionsUrl: t.GetDatabaseDriverOptionsUrl, }, nil } @@ -589,6 +590,7 @@ func convertModelPlugin(t *model.Plugin) (*biz.Plugin, error) { p := &biz.Plugin{ Name: t.Name, OperateDataResourceHandleUrl: t.OperateDataResourceHandleUrl, + GetDatabaseDriverOptionsUrl: t.GetDatabaseDriverOptionsUrl, } return p, nil } diff --git a/internal/dms/storage/model/model.go b/internal/dms/storage/model/model.go index 9bf3ab37..0786725d 100644 --- a/internal/dms/storage/model/model.go +++ b/internal/dms/storage/model/model.go @@ -238,6 +238,7 @@ type Plugin struct { DelUserPreCheckUrl string `json:"del_user_pre_check_url" gorm:"size:255;column:del_user_pre_check_url"` DelUserGroupPreCheckUrl string `json:"del_user_group_pre_check_url" gorm:"size:255;column:del_user_group_pre_check_url"` OperateDataResourceHandleUrl string `json:"operate_data_resource_handle_url" gorm:"size:255;column:operate_data_resource_handle_url"` + GetDatabaseDriverOptionsUrl string `json:"get_database_driver_options_url" gorm:"size:255;column:get_database_driver_options_url"` } // Oauth2Configuration store oauth2 server configuration. diff --git a/pkg/dms-common/api/dms/v1/plugin.go b/pkg/dms-common/api/dms/v1/plugin.go index 25e30e67..9bc54664 100644 --- a/pkg/dms-common/api/dms/v1/plugin.go +++ b/pkg/dms-common/api/dms/v1/plugin.go @@ -36,6 +36,7 @@ type Plugin struct { // eg: 删除数据源前: // 需要sqle服务中实现接口逻辑,判断该数据源上已经没有进行中的工单 OperateDataResourceHandleUrl string `json:"operate_data_resource_handle_url"` + GetDatabaseDriverOptionsUrl string `json:"get_database_driver_options_url"` } // swagger:model