feat: 新增数据库版本查看

This commit is contained in:
meilin.huang
2023-12-20 17:29:16 +08:00
parent 2ae0cd7ab4
commit f5c90277b1
17 changed files with 185 additions and 59 deletions

View File

@@ -95,6 +95,16 @@ export function useApiFetch<T>(api: Api, params: any = null, reqOptions: Request
return rejectPromise;
}
const respStatus = uaf.response.value?.status;
if (respStatus == 404) {
ElMessage.error('请求接口不存在');
return rejectPromise;
}
if (respStatus == 500) {
ElMessage.error('服务器响应异常');
return rejectPromise;
}
console.error(e);
ElMessage.error('网络请求错误');
return rejectPromise;

View File

@@ -184,6 +184,10 @@ defineExpose({
z-index: 2190;
position: fixed;
.el-dropdown-menu__item {
padding: 5px 10px;
}
.el-dropdown-menu__item {
font-size: 12px !important;
white-space: nowrap;

View File

@@ -5,7 +5,14 @@
<tag-tree :resource-type="TagResourceTypeEnum.Db.value" :tag-path-node-type="NodeTypeTagPath" ref="tagTreeRef">
<template #prefix="{ data }">
<span v-if="data.type.value == SqlExecNodeType.DbInst">
<el-popover :show-after="500" placement="right-start" title="数据库实例信息" trigger="hover" :width="250">
<el-popover
@show="showDbInfo(data.params)"
:show-after="500"
placement="right-start"
title="数据库实例信息"
trigger="hover"
:width="250"
>
<template #reference>
<SvgIcon :name="getDbDialect(data.params.type).getInfo().icon" :size="18" />
</template>
@@ -17,6 +24,9 @@
<el-descriptions-item label="host">
{{ `${data.params.host}:${data.params.port}` }}
</el-descriptions-item>
<el-descriptions-item label="数据库版本">
<span v-loading="loadingServerInfo"> {{ `${dbServerInfo?.version}` }}</span>
</el-descriptions-item>
<el-descriptions-item label="user">
{{ data.params.username }}
</el-descriptions-item>
@@ -381,10 +391,19 @@ const state = reactive({
tabs,
dataTabsTableHeight: '600px',
tablesOpHeight: '600',
dbServerInfo: {
loading: true,
version: '',
},
});
const { nowDbInst } = toRefs(state);
const serverInfoReqParam = ref({
instanceId: 0,
});
const { execute: getDbServerInfo, isFetching: loadingServerInfo, data: dbServerInfo } = dbApi.getInstanceServerInfo.useApi<any>(serverInfoReqParam);
onMounted(() => {
setHeight();
// 监听浏览器窗口大小变化,更新对应组件高度
@@ -403,6 +422,14 @@ const setHeight = () => {
state.tablesOpHeight = window.innerHeight - 225 + 'px';
};
const showDbInfo = async (db: any) => {
if (dbServerInfo.value) {
dbServerInfo.value.version = '';
}
serverInfoReqParam.value.instanceId = db.instanceId;
await getDbServerInfo();
};
// 选择数据库,改变当前正在操作的数据库信息
const changeDb = (db: any, dbName: string) => {
state.nowDbInst = DbInst.getOrNewInst(db);

View File

@@ -36,6 +36,7 @@ export const dbApi = {
instances: Api.newGet('/instances'),
getInstance: Api.newGet('/instances/{instanceId}'),
getAllDatabase: Api.newGet('/instances/{instanceId}/databases'),
getInstanceServerInfo: Api.newGet('/instances/{instanceId}/server-info'),
testConn: Api.newPost('/instances/test-conn'),
saveInstance: Api.newPost('/instances'),
getInstancePwd: Api.newGet('/instances/{id}/pwd'),

View File

@@ -64,17 +64,12 @@
</el-text>
</div>
<el-dropdown trigger="click" class="column-header-op">
<el-dropdown trigger="click" class="column-header-op" size="small">
<SvgIcon :size="16" name="CaretBottom" />
<template #dropdown>
<el-dropdown-menu>
<template v-for="menu in tableHeadlerMenu">
<el-dropdown-item
:key="menu.clickId"
v-if="!menu.isHide(column)"
@click="menu?.onClickFunc(column)"
class="font12"
>
<el-dropdown-item :key="menu.clickId" v-if="!menu.isHide(column)" @click="menu?.onClickFunc(column)">
<SvgIcon v-if="menu.icon" :name="menu.icon" />
{{ menu.txt }}
</el-dropdown-item>

View File

@@ -26,8 +26,8 @@ require (
github.com/robfig/cron/v3 v3.0.1 //
github.com/stretchr/testify v1.8.4
go.mongodb.org/mongo-driver v1.13.1 // mongo
golang.org/x/crypto v0.16.0 // ssh
golang.org/x/oauth2 v0.14.0
golang.org/x/crypto v0.17.0 // ssh
golang.org/x/oauth2 v0.15.0
gopkg.in/yaml.v3 v3.0.1
// gorm
gorm.io/driver/mysql v1.5.2
@@ -79,7 +79,7 @@ require (
golang.org/x/arch v0.3.0 // indirect
golang.org/x/exp v0.0.0-20230519143937-03e91628a987
golang.org/x/image v0.13.0 // indirect
golang.org/x/net v0.18.0 // indirect
golang.org/x/net v0.19.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect

View File

@@ -339,7 +339,7 @@ func (d *Db) dumpDb(writer *gzipWriter, dbId uint64, dbName string, tables []str
if needStruct {
writer.WriteString(fmt.Sprintf("\n-- ----------------------------\n-- 表结构: %s \n-- ----------------------------\n", table))
writer.WriteString(fmt.Sprintf("DROP TABLE IF EXISTS %s;\n", quotedTable))
ddl, err := dbMeta.GetCreateTableDdl(table)
ddl, err := dbMeta.GetTableDDL(table)
biz.ErrIsNil(err)
writer.WriteString(ddl + "\n")
}
@@ -441,17 +441,15 @@ func (d *Db) HintTables(rc *req.Ctx) {
rc.ResData = res
}
func (d *Db) GetCreateTableDdl(rc *req.Ctx) {
func (d *Db) GetTableDDL(rc *req.Ctx) {
tn := rc.GinCtx.Query("tableName")
biz.NotEmpty(tn, "tableName不能为空")
res, err := d.getDbConn(rc.GinCtx).GetDialect().GetCreateTableDdl(tn)
res, err := d.getDbConn(rc.GinCtx).GetDialect().GetTableDDL(tn)
biz.ErrIsNilAppendErr(err, "获取表ddl失败: %s")
rc.ResData = res
}
func (d *Db) GetPgsqlSchemas(rc *req.Ctx) {
conn := d.getDbConn(rc.GinCtx)
biz.IsTrue(conn.Info.Type == dbm.DbTypePostgres || conn.Info.Type == dbm.DM, "非postgres无法获取该schemas")
func (d *Db) GetSchemas(rc *req.Ctx) {
res, err := d.getDbConn(rc.GinCtx).GetDialect().GetSchemas()
biz.ErrIsNilAppendErr(err, "获取schemas失败: %s")
rc.ResData = res

View File

@@ -89,11 +89,7 @@ func (d *Instance) DeleteInstance(rc *req.Ctx) {
value, err := strconv.Atoi(v)
biz.ErrIsNilAppendErr(err, "string类型转换为int异常: %s")
instanceId := uint64(value)
if d.DbApp.Count(&entity.DbQuery{InstanceId: instanceId}) != 0 {
instance, err := d.InstanceApp.GetById(new(entity.DbInstance), instanceId, "name")
biz.ErrIsNil(err, "获取数据库实例错误数据库实例ID为: %d", instance.Id)
biz.IsTrue(false, "不能删除数据库实例【%s】请先删除其关联的数据库资源。", instance.Name)
}
biz.IsTrue(d.DbApp.Count(&entity.DbQuery{InstanceId: instanceId}) == 0, "不能删除数据库实例【%d】, 请先删除其关联的数据库资源", instanceId)
d.InstanceApp.Delete(rc.MetaCtx, instanceId)
}
}
@@ -109,6 +105,16 @@ func (d *Instance) GetDatabaseNames(rc *req.Ctx) {
rc.ResData = res
}
// 获取数据库实例server信息
func (d *Instance) GetDbServer(rc *req.Ctx) {
instanceId := getInstanceId(rc.GinCtx)
conn, err := d.DbApp.GetDbConnByInstanceId(instanceId)
biz.ErrIsNil(err)
res, err := conn.GetDialect().GetDbServer()
biz.ErrIsNil(err)
rc.ResData = res
}
func getInstanceId(g *gin.Context) uint64 {
instanceId, _ := strconv.Atoi(g.Param("instanceId"))
biz.IsTrue(instanceId > 0, "instanceId 错误")

View File

@@ -31,8 +31,12 @@ type Db interface {
// 获取数据库连接实例
// @param id 数据库id
// @param dbName 数据库
//
// @param dbName 数据库名
GetDbConn(dbId uint64, dbName string) (*dbm.DbConn, error)
// 根据数据库实例id获取连接随机返回该instanceId下已连接的conn若不存在则是使用该instanceId关联的db进行连接并返回。
GetDbConnByInstanceId(instanceId uint64) (*dbm.DbConn, error)
}
func newDbApp(dbRepo repository.Db, dbSqlRepo repository.DbSql, dbInstanceApp Instance, tagApp tagapp.TagTree) Db {
@@ -168,8 +172,26 @@ func (d *dbAppImpl) GetDbConn(dbId uint64, dbName string) (*dbm.DbConn, error) {
})
}
func (d *dbAppImpl) GetDbConnByInstanceId(instanceId uint64) (*dbm.DbConn, error) {
conn := dbm.GetDbConnByInstanceId(instanceId)
if conn != nil {
return conn, nil
}
var dbs []*entity.Db
err := d.ListByCond(&entity.Db{InstanceId: instanceId}, &dbs, "id", "database")
if err != nil || len(dbs) == 0 {
return nil, errorx.NewBiz("该实例未配置数据库, 请先进行配置")
}
// 使用该实例关联的已配置数据库中的第一个库进行连接并返回
firstDb := dbs[0]
return d.GetDbConn(firstDb.Id, strings.Split(firstDb.Database, " ")[0])
}
func toDbInfo(instance *entity.DbInstance, dbId uint64, database string, tagPath ...string) *dbm.DbInfo {
di := new(dbm.DbInfo)
di.InstanceId = instance.Id
di.Id = dbId
di.Database = database
di.TagPath = tagPath

View File

@@ -67,6 +67,17 @@ func GetDbConn(dbId uint64, database string, getDbInfo func() (*DbInfo, error))
return dbConn, nil
}
// 根据实例id获取连接
func GetDbConnByInstanceId(instanceId uint64) *DbConn {
for _, connItem := range connCache.Items() {
conn := connItem.Value.(*DbConn)
if conn.Info.InstanceId == instanceId {
return conn
}
}
return nil
}
// 删除db缓存并关闭该数据库所有连接
func CloseDb(dbId uint64, db string) {
connCache.Delete(GetDbConnId(dbId, db))

View File

@@ -3,10 +3,17 @@ package dbm
import (
"embed"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/utils/collx"
"mayfly-go/pkg/utils/stringx"
"strings"
)
// 数据库服务实例信息
type DbServer struct {
Version string `json:"version"` // 版本信息
Extra collx.M `json:"extra"` // 其他额外信息
}
// 表信息
type Table struct {
TableName string `json:"tableName"` // 表名
@@ -19,15 +26,15 @@ type Table struct {
// 表的列信息
type Column struct {
TableName string `json:"tableName"` // 表名
ColumnName string `json:"columnName"` // 列名
ColumnType string `json:"columnType"` // 列类型
ColumnComment string `json:"columnComment"` // 列备注
ColumnKey string `json:"columnKey"` // 是否为主键逐渐的话值钱为PRI
ColumnDefault string `json:"columnDefault"` // 默认值
Nullable string `json:"nullable"` // 是否可为null
NumScale string `json:"numScale"` // 小数点
Extra string `json:"extra"` // 其他信息
TableName string `json:"tableName"` // 表名
ColumnName string `json:"columnName"` // 列名
ColumnType string `json:"columnType"` // 列类型
ColumnComment string `json:"columnComment"` // 列备注
ColumnKey string `json:"columnKey"` // 是否为主键逐渐的话值钱为PRI
ColumnDefault string `json:"columnDefault"` // 默认值
Nullable string `json:"nullable"` // 是否可为null
NumScale string `json:"numScale"` // 小数点
Extra collx.M `json:"extra"` // 其他额外信息
}
// 表索引信息
@@ -43,6 +50,9 @@ type Index struct {
// -----------------------------------元数据接口定义------------------------------------------
// 数据库方言、元信息接口(表、列、获取表数据等元信息)
type DbDialect interface {
// 获取数据库服务实例信息
GetDbServer() (*DbServer, error)
// 获取数据库名称列表
GetDbNames() ([]string, error)
@@ -59,7 +69,7 @@ type DbDialect interface {
GetTableIndex(tableName string) ([]Index, error)
// 获取建表ddl
GetCreateTableDdl(tableName string) (string, error)
GetTableDDL(tableName string) (string, error)
// 获取指定表的数据-分页查询
// @return columns: 列字段名result: 结果集error: 错误

View File

@@ -42,6 +42,17 @@ type DMDialect struct {
dc *DbConn
}
func (dd *DMDialect) GetDbServer() (*DbServer, error) {
_, res, err := dd.dc.Query("select * from v$instance")
if err != nil {
return nil, err
}
ds := &DbServer{
Version: anyx.ConvString(res[0]["SVR_VERSION"]),
}
return ds, nil
}
func (pd *DMDialect) GetDbNames() ([]string, error) {
_, res, err := pd.dc.Query("SELECT name AS DBNAME FROM v$database")
if err != nil {
@@ -168,7 +179,7 @@ func (pd *DMDialect) GetTableIndex(tableName string) ([]Index, error) {
}
// 获取建表ddl
func (pd *DMDialect) GetCreateTableDdl(tableName string) (string, error) {
func (pd *DMDialect) GetTableDDL(tableName string) (string, error) {
ddlSql := fmt.Sprintf("CALL SP_TABLEDEF((SELECT SF_GET_SCHEMA_NAME_BY_ID(CURRENT_SCHID)), '%s')", tableName)
_, res, err := pd.dc.Query(ddlSql)
if err != nil {
@@ -185,13 +196,14 @@ func (pd *DMDialect) GetCreateTableDdl(tableName string) (string, error) {
select OWNER, COMMENTS from DBA_TAB_COMMENTS where TABLE_TYPE='TABLE' and TABLE_NAME = '%s'
and owner = (SELECT SF_GET_SCHEMA_NAME_BY_ID(CURRENT_SCHID))
`, tableName))
if res != nil {
for _, re := range res {
// COMMENT ON TABLE "SYS_MENU" IS '菜单表';
if re["COMMENTS"] != nil {
tableComment := fmt.Sprintf("\n\nCOMMENT ON TABLE \"%s\".\"%s\" IS '%s';", re["OWNER"].(string), tableName, re["COMMENTS"].(string))
builder.WriteString(tableComment)
}
if err != nil {
return "", err
}
for _, re := range res {
// COMMENT ON TABLE "SYS_MENU" IS '菜单表';
if re["COMMENTS"] != nil {
tableComment := fmt.Sprintf("\n\nCOMMENT ON TABLE \"%s\".\"%s\" IS '%s';", re["OWNER"].(string), tableName, re["COMMENTS"].(string))
builder.WriteString(tableComment)
}
}
@@ -203,14 +215,16 @@ func (pd *DMDialect) GetCreateTableDdl(tableName string) (string, error) {
AND TABLE_NAME = '%s'
`, tableName)
_, res, err = pd.dc.Query(fieldSql)
if res != nil {
builder.WriteString("\n")
for _, re := range res {
// COMMENT ON COLUMN "SYS_MENU"."BIZ_CODE" IS '业务编码应用编码1';
if re["COMMENTS"] != nil {
fieldComment := fmt.Sprintf("\nCOMMENT ON COLUMN \"%s\".\"%s\".\"%s\" IS '%s';", re["OWNER"].(string), tableName, re["COLUMN_NAME"].(string), re["COMMENTS"].(string))
builder.WriteString(fieldComment)
}
if err != nil {
return "", err
}
builder.WriteString("\n")
for _, re := range res {
// COMMENT ON COLUMN "SYS_MENU"."BIZ_CODE" IS '业务编码应用编码1';
if re["COMMENTS"] != nil {
fieldComment := fmt.Sprintf("\nCOMMENT ON COLUMN \"%s\".\"%s\".\"%s\" IS '%s';", re["OWNER"].(string), tableName, re["COLUMN_NAME"].(string), re["COMMENTS"].(string))
builder.WriteString(fieldComment)
}
}
@@ -223,10 +237,11 @@ func (pd *DMDialect) GetCreateTableDdl(tableName string) (string, error) {
and indexdef(b.object_id,1) != '禁止查看系统定义的索引信息'
`, tableName)
_, res, err = pd.dc.Query(indexSql)
if res != nil {
for _, re := range res {
builder.WriteString("\n\n" + re["INDEX_DEF"].(string))
}
if err != nil {
return "", err
}
for _, re := range res {
builder.WriteString("\n\n" + re["INDEX_DEF"].(string))
}
return builder.String(), nil

View File

@@ -43,6 +43,17 @@ type MysqlDialect struct {
dc *DbConn
}
func (md *MysqlDialect) GetDbServer() (*DbServer, error) {
_, res, err := md.dc.Query("SELECT VERSION() version")
if err != nil {
return nil, err
}
ds := &DbServer{
Version: anyx.ConvString(res[0]["version"]),
}
return ds, nil
}
func (md *MysqlDialect) GetDbNames() ([]string, error) {
_, res, err := md.dc.Query("SELECT SCHEMA_NAME AS dbname FROM SCHEMATA")
if err != nil {
@@ -166,7 +177,7 @@ func (md *MysqlDialect) GetTableIndex(tableName string) ([]Index, error) {
}
// 获取建表ddl
func (md *MysqlDialect) GetCreateTableDdl(tableName string) (string, error) {
func (md *MysqlDialect) GetTableDDL(tableName string) (string, error) {
_, res, err := md.dc.Query(fmt.Sprintf("show create table `%s` ", tableName))
if err != nil {
return "", err

View File

@@ -106,6 +106,17 @@ type PgsqlDialect struct {
dc *DbConn
}
func (pd *PgsqlDialect) GetDbServer() (*DbServer, error) {
_, res, err := pd.dc.Query("SHOW server_version")
if err != nil {
return nil, err
}
ds := &DbServer{
Version: anyx.ConvString(res[0]["server_version"]),
}
return ds, nil
}
func (pd *PgsqlDialect) GetDbNames() ([]string, error) {
_, res, err := pd.dc.Query("SELECT datname AS dbname FROM pg_database WHERE datistemplate = false AND has_database_privilege(datname, 'CONNECT')")
if err != nil {
@@ -227,7 +238,7 @@ func (pd *PgsqlDialect) GetTableIndex(tableName string) ([]Index, error) {
}
// 获取建表ddl
func (pd *PgsqlDialect) GetCreateTableDdl(tableName string) (string, error) {
func (pd *PgsqlDialect) GetTableDDL(tableName string) (string, error) {
_, err := pd.dc.Exec(GetLocalSql(PGSQL_META_FILE, PGSQL_TABLE_DDL_KEY))
if err != nil {
return "", err

View File

@@ -8,8 +8,9 @@ import (
)
type DbInfo struct {
Id uint64
Name string
InstanceId uint64 // 实例id
Id uint64 // dbId
Name string
Type DbType // 类型mysql postgres等
Host string

View File

@@ -29,9 +29,9 @@ func InitDbRouter(router *gin.RouterGroup) {
req.NewDelete(":dbId", d.DeleteDb).Log(req.NewLogSave("db-删除数据库信息")),
req.NewGet(":dbId/t-create-ddl", d.GetCreateTableDdl),
req.NewGet(":dbId/t-create-ddl", d.GetTableDDL),
req.NewGet(":dbId/pg/schemas", d.GetPgsqlSchemas),
req.NewGet(":dbId/pg/schemas", d.GetSchemas),
req.NewPost(":dbId/exec-sql", d.ExecSql).Log(req.NewLog("db-执行Sql")),

View File

@@ -25,10 +25,14 @@ func InitInstanceRouter(router *gin.RouterGroup) {
req.NewPost("", d.SaveInstance).Log(req.NewLogSave("db-保存数据库实例信息")),
req.NewGet(":instanceId", d.GetInstance),
req.NewGet(":instanceId/pwd", d.GetInstancePwd),
// 获取数据库实例的所有数据库名
req.NewGet(":instanceId/databases", d.GetDatabaseNames),
req.NewGet(":instanceId/server-info", d.GetDbServer),
req.NewDelete(":instanceId", d.DeleteInstance).Log(req.NewLogSave("db-删除数据库实例")),
}