Files
mayfly-go/server/internal/db/api/db.go

381 lines
10 KiB
Go
Raw Normal View History

2021-09-11 14:04:09 +08:00
package api
2021-01-08 15:37:32 +08:00
import (
"context"
2021-01-08 15:37:32 +08:00
"fmt"
2022-09-09 18:26:08 +08:00
"mayfly-go/internal/db/api/form"
"mayfly-go/internal/db/api/vo"
"mayfly-go/internal/db/application"
"mayfly-go/internal/db/application/dto"
"mayfly-go/internal/db/config"
"mayfly-go/internal/db/dbm/dbi"
2022-09-09 18:26:08 +08:00
"mayfly-go/internal/db/domain/entity"
2024-11-20 22:43:53 +08:00
"mayfly-go/internal/db/imsg"
"mayfly-go/internal/event"
2023-07-03 21:42:04 +08:00
msgapp "mayfly-go/internal/msg/application"
2023-10-10 17:39:46 +08:00
msgdto "mayfly-go/internal/msg/application/dto"
2022-10-26 20:49:29 +08:00
tagapp "mayfly-go/internal/tag/application"
tagentity "mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/global"
2024-11-20 22:43:53 +08:00
"mayfly-go/pkg/i18n"
"mayfly-go/pkg/logx"
"mayfly-go/pkg/model"
2023-01-14 16:29:52 +08:00
"mayfly-go/pkg/req"
2023-10-20 21:31:46 +08:00
"mayfly-go/pkg/utils/anyx"
"mayfly-go/pkg/utils/collx"
"mayfly-go/pkg/utils/cryptox"
"mayfly-go/pkg/utils/writerx"
2021-04-21 10:22:09 +08:00
"strings"
"time"
2024-03-26 21:46:03 +08:00
"github.com/may-fly/cast"
2021-04-16 15:10:07 +08:00
)
2021-01-08 15:37:32 +08:00
type Db struct {
instanceApp application.Instance `inject:"T"`
dbApp application.Db `inject:"T"`
dbSqlExecApp application.DbSqlExec `inject:"T"`
msgApp msgapp.Msg `inject:"T"`
tagApp tagapp.TagTree `inject:"T"`
}
func (d *Db) ReqConfs() *req.Confs {
reqs := [...]*req.Conf{
// 获取数据库列表
req.NewGet("", d.Dbs),
req.NewPost("", d.Save).Log(req.NewLogSaveI(imsg.LogDbSave)),
req.NewDelete(":dbId", d.DeleteDb).Log(req.NewLogSaveI(imsg.LogDbDelete)),
req.NewGet(":dbId/t-create-ddl", d.GetTableDDL),
req.NewGet(":dbId/version", d.GetVersion),
req.NewGet(":dbId/pg/schemas", d.GetSchemas),
req.NewPost(":dbId/exec-sql", d.ExecSql).Log(req.NewLogI(imsg.LogDbRunSql)),
req.NewPost(":dbId/exec-sql-file", d.ExecSqlFile).Log(req.NewLogSaveI(imsg.LogDbRunSqlFile)).RequiredPermissionCode("db:sqlscript:run"),
req.NewGet(":dbId/dump", d.DumpSql).Log(req.NewLogSaveI(imsg.LogDbDump)).NoRes(),
req.NewGet(":dbId/t-infos", d.TableInfos),
req.NewGet(":dbId/t-index", d.TableIndex),
req.NewGet(":dbId/c-metadata", d.ColumnMA),
req.NewGet(":dbId/hint-tables", d.HintTables),
req.NewPost(":dbId/copy-table", d.CopyTable),
}
return req.NewConfs("/dbs", reqs[:]...)
}
2021-01-08 15:37:32 +08:00
// @router /api/dbs [get]
2023-01-14 16:29:52 +08:00
func (d *Db) Dbs(rc *req.Ctx) {
queryCond, page := req.BindQueryAndPage[*entity.DbQuery](rc, new(entity.DbQuery))
2022-10-26 20:49:29 +08:00
// 不存在可访问标签id即没有可操作数据
tags := d.tagApp.GetAccountTags(rc.GetLoginAccount().Id, &tagentity.TagTreeQuery{
2024-11-26 17:32:44 +08:00
TypePaths: collx.AsArray(tagentity.NewTypePaths(tagentity.TagTypeDbInstance, tagentity.TagTypeAuthCert, tagentity.TagTypeDb)),
2024-11-23 17:23:18 +08:00
CodePathLikes: collx.AsArray(queryCond.TagPath),
})
if len(tags) == 0 {
rc.ResData = model.EmptyPageResult[any]()
2022-10-26 20:49:29 +08:00
return
}
2024-11-23 17:23:18 +08:00
queryCond.Codes = tags.GetCodes()
var dbvos []*vo.DbListVO
res, err := d.dbApp.GetPageList(queryCond, page, &dbvos)
biz.ErrIsNil(err)
instances, _ := d.instanceApp.GetByIds(collx.ArrayMap(dbvos, func(i *vo.DbListVO) uint64 {
return i.InstanceId
}))
instancesMap := collx.ArrayToMap(instances, func(i *entity.DbInstance) uint64 {
return i.Id
})
for _, dbvo := range dbvos {
di := instancesMap[dbvo.InstanceId]
if di != nil {
dbvo.InstanceCode = di.Code
dbvo.InstanceType = di.Type
dbvo.Host = di.Host
dbvo.Port = di.Port
}
}
rc.ResData = res
2021-01-08 15:37:32 +08:00
}
2023-01-14 16:29:52 +08:00
func (d *Db) Save(rc *req.Ctx) {
form := &form.DbForm{}
db := req.BindJsonAndCopyTo[*entity.Db](rc, form, new(entity.Db))
rc.ReqParam = form
biz.ErrIsNil(d.dbApp.SaveDb(rc.MetaCtx, db))
}
2023-01-14 16:29:52 +08:00
func (d *Db) DeleteDb(rc *req.Ctx) {
2024-02-25 12:46:18 +08:00
idsStr := rc.PathParam("dbId")
rc.ReqParam = idsStr
ids := strings.Split(idsStr, ",")
ctx := rc.MetaCtx
for _, v := range ids {
biz.ErrIsNil(d.dbApp.Delete(ctx, cast.ToUint64(v)))
}
}
/** 数据库操作相关、执行sql等 ***/
2023-01-14 16:29:52 +08:00
func (d *Db) ExecSql(rc *req.Ctx) {
form := req.BindJsonAndValid(rc, new(form.DbSqlExecForm))
dbId := getDbId(rc)
dbConn, err := d.dbApp.GetDbConn(dbId, form.Db)
biz.ErrIsNil(err)
biz.ErrIsNilAppendErr(d.tagApp.CanAccess(rc.GetLoginAccount().Id, dbConn.Info.CodePath...), "%s")
global.EventBus.Publish(rc.MetaCtx, event.EventTopicResourceOp, dbConn.Info.CodePath[0])
sqlStr, err := cryptox.AesDecryptByLa(form.Sql, rc.GetLoginAccount())
2024-11-20 22:43:53 +08:00
biz.ErrIsNilAppendErr(err, "sql decoding failure: %s")
2023-12-06 09:23:23 +08:00
rc.ReqParam = fmt.Sprintf("%s %s\n-> %s", dbConn.Info.GetLogDesc(), form.ExecId, sqlStr)
2024-11-20 22:43:53 +08:00
biz.NotEmpty(form.Sql, "sql cannot be empty")
2024-12-08 13:04:23 +08:00
execReq := &dto.DbSqlExecReq{
DbId: dbId,
Db: form.Db,
Remark: form.Remark,
DbConn: dbConn,
Sql: sqlStr,
CheckFlow: true,
}
ctx, cancel := context.WithTimeout(rc.MetaCtx, time.Duration(config.GetDbms().SqlExecTl)*time.Second)
defer cancel()
2022-11-02 19:27:40 +08:00
execRes, err := d.dbSqlExecApp.Exec(ctx, execReq)
biz.ErrIsNil(err)
rc.ResData = execRes
2021-01-08 15:37:32 +08:00
}
// progressCategory sql文件执行进度消息类型
const progressCategory = "execSqlFileProgress"
// progressMsg sql文件执行进度消息
type progressMsg struct {
Id string `json:"id"`
Title string `json:"title"`
ExecutedStatements int `json:"executedStatements"`
Terminated bool `json:"terminated"`
}
// 执行sql文件
2023-01-14 16:29:52 +08:00
func (d *Db) ExecSqlFile(rc *req.Ctx) {
2024-02-25 12:46:18 +08:00
multipart, err := rc.GetRequest().MultipartReader()
2024-11-20 22:43:53 +08:00
biz.ErrIsNilAppendErr(err, "failed to read sql file: %s")
file, err := multipart.NextPart()
2024-11-20 22:43:53 +08:00
biz.ErrIsNilAppendErr(err, "failed to read sql file: %s")
defer file.Close()
filename := file.FileName()
dbId := getDbId(rc)
dbName := getDbName(rc)
2024-02-25 12:46:18 +08:00
clientId := rc.Query("clientId")
dbConn, err := d.dbApp.GetDbConn(dbId, dbName)
biz.ErrIsNil(err)
biz.ErrIsNilAppendErr(d.tagApp.CanAccess(rc.GetLoginAccount().Id, dbConn.Info.CodePath...), "%s")
rc.ReqParam = fmt.Sprintf("filename: %s -> %s", filename, dbConn.Info.GetLogDesc())
biz.ErrIsNil(d.dbSqlExecApp.ExecReader(rc.MetaCtx, &dto.SqlReaderExec{
2024-12-08 13:04:23 +08:00
Reader: file,
Filename: filename,
DbConn: dbConn,
ClientId: clientId,
}))
}
// 数据库dump
2023-01-14 16:29:52 +08:00
func (d *Db) DumpSql(rc *req.Ctx) {
dbId := getDbId(rc)
2024-03-26 21:46:03 +08:00
dbName := rc.Query("db")
2024-02-25 12:46:18 +08:00
dumpType := rc.Query("type")
tablesStr := rc.Query("tables")
extName := rc.Query("extName")
switch extName {
case ".gz", ".gzip", "gz", "gzip":
extName = ".gz"
default:
extName = ""
}
// 是否需要导出表结构
needStruct := dumpType == "1" || dumpType == "3"
// 是否需要导出数据
needData := dumpType == "2" || dumpType == "3"
la := rc.GetLoginAccount()
dbConn, err := d.dbApp.GetDbConn(dbId, dbName)
2024-05-10 19:59:49 +08:00
biz.ErrIsNil(err)
biz.ErrIsNilAppendErr(d.tagApp.CanAccess(la.Id, dbConn.Info.CodePath...), "%s")
now := time.Now()
2024-05-10 19:59:49 +08:00
filename := fmt.Sprintf("%s-%s.%s.sql%s", dbConn.Info.Name, dbName, now.Format("20060102150405"), extName)
2024-02-25 12:46:18 +08:00
rc.Header("Content-Type", "application/octet-stream")
rc.Header("Content-Disposition", "attachment; filename="+filename)
if extName != ".gz" {
2024-02-25 12:46:18 +08:00
rc.Header("Content-Encoding", "gzip")
}
2024-03-26 21:46:03 +08:00
var tables []string
if len(tablesStr) > 0 {
2023-08-31 20:05:38 +08:00
tables = strings.Split(tablesStr, ",")
}
2023-10-20 21:31:46 +08:00
defer func() {
2023-10-20 21:31:46 +08:00
msg := anyx.ToString(recover())
if len(msg) > 0 {
2024-11-20 22:43:53 +08:00
msg = "DB dump error: " + msg
2024-03-26 21:46:03 +08:00
rc.GetWriter().Write([]byte(msg))
d.msgApp.CreateAndSend(la, msgdto.ErrSysMsg(i18n.T(imsg.DbDumpErr), msg))
}
}()
2023-10-20 21:31:46 +08:00
biz.ErrIsNil(d.dbApp.DumpDb(rc.MetaCtx, &dto.DumpDb{
2024-03-26 21:46:03 +08:00
DbId: dbId,
DbName: dbName,
Tables: tables,
DumpDDL: needStruct,
DumpData: needData,
Writer: writerx.NewGzipWriter(rc.GetWriter()),
2024-03-26 21:46:03 +08:00
}))
2024-05-10 19:59:49 +08:00
rc.ReqParam = collx.Kvs("db", dbConn.Info, "database", dbName, "tables", tablesStr, "dumpType", dumpType)
}
func (d *Db) TableInfos(rc *req.Ctx) {
res, err := d.getDbConn(rc).GetMetadata().GetTables()
2024-11-20 22:43:53 +08:00
biz.ErrIsNilAppendErr(err, "get table error: %s")
rc.ResData = res
}
func (d *Db) TableIndex(rc *req.Ctx) {
2024-02-25 12:46:18 +08:00
tn := rc.Query("tableName")
2024-11-20 22:43:53 +08:00
biz.NotEmpty(tn, "tableName cannot be empty")
res, err := d.getDbConn(rc).GetMetadata().GetTableIndex(tn)
2024-11-20 22:43:53 +08:00
biz.ErrIsNilAppendErr(err, "get table index error: %s")
rc.ResData = res
2021-01-08 15:37:32 +08:00
}
// @router /api/db/:dbId/c-metadata [get]
2023-01-14 16:29:52 +08:00
func (d *Db) ColumnMA(rc *req.Ctx) {
2024-02-25 12:46:18 +08:00
tn := rc.Query("tableName")
2024-11-20 22:43:53 +08:00
biz.NotEmpty(tn, "tableName cannot be empty")
dbi := d.getDbConn(rc)
res, err := dbi.GetMetadata().GetColumns(tn)
2024-11-20 22:43:53 +08:00
biz.ErrIsNilAppendErr(err, "get column metadata error: %s")
rc.ResData = res
2021-01-08 15:37:32 +08:00
}
// @router /api/db/:dbId/hint-tables [get]
2023-01-14 16:29:52 +08:00
func (d *Db) HintTables(rc *req.Ctx) {
dbi := d.getDbConn(rc)
metadata := dbi.GetMetadata()
2022-08-10 19:46:17 +08:00
// 获取所有表
2024-03-15 13:31:53 +08:00
tables, err := metadata.GetTables()
biz.ErrIsNil(err)
tableNames := make([]string, 0)
2021-04-16 15:10:07 +08:00
for _, v := range tables {
2023-05-24 12:32:17 +08:00
tableNames = append(tableNames, v.TableName)
}
// key = 表名value = 列名数组
res := make(map[string][]string)
// 表为空,则直接返回
if len(tableNames) == 0 {
rc.ResData = res
return
}
// 获取所有表下的所有列信息
2024-03-15 13:31:53 +08:00
columnMds, err := metadata.GetColumns(tableNames...)
biz.ErrIsNil(err)
for _, v := range columnMds {
2023-05-24 12:32:17 +08:00
tName := v.TableName
if res[tName] == nil {
res[tName] = make([]string, 0)
2021-01-08 15:37:32 +08:00
}
2024-03-21 17:15:52 +08:00
columnName := fmt.Sprintf("%s [%s]", v.ColumnName, v.GetColumnType())
2023-05-24 12:32:17 +08:00
comment := v.ColumnComment
// 如果字段备注不为空,则加上备注信息
2023-05-24 12:32:17 +08:00
if comment != "" {
columnName = fmt.Sprintf("%s[%s]", columnName, comment)
}
res[tName] = append(res[tName], columnName)
2021-04-16 15:10:07 +08:00
}
rc.ResData = res
2021-01-08 15:37:32 +08:00
}
2023-12-20 17:29:16 +08:00
func (d *Db) GetTableDDL(rc *req.Ctx) {
2024-02-25 12:46:18 +08:00
tn := rc.Query("tableName")
2024-11-20 22:43:53 +08:00
biz.NotEmpty(tn, "tableName cannot be empty")
res, err := d.getDbConn(rc).GetMetadata().GetTableDDL(tn, false)
2024-11-20 22:43:53 +08:00
biz.ErrIsNilAppendErr(err, "get table DDL error: %s")
rc.ResData = res
}
func (d *Db) GetVersion(rc *req.Ctx) {
version := d.getDbConn(rc).GetMetadata().GetCompatibleDbVersion()
rc.ResData = version
}
2023-12-20 17:29:16 +08:00
func (d *Db) GetSchemas(rc *req.Ctx) {
res, err := d.getDbConn(rc).GetMetadata().GetSchemas()
2024-11-20 22:43:53 +08:00
biz.ErrIsNilAppendErr(err, "get schemas error: %s")
rc.ResData = res
}
func (d *Db) CopyTable(rc *req.Ctx) {
form := &form.DbCopyTableForm{}
copy := req.BindJsonAndCopyTo[*dbi.DbCopyTable](rc, form, new(dbi.DbCopyTable))
conn, err := d.dbApp.GetDbConn(form.Id, form.Db)
2024-11-20 22:43:53 +08:00
biz.ErrIsNilAppendErr(err, "copy table error: %s")
err = conn.GetDialect().CopyTable(copy)
if err != nil {
2024-11-20 22:43:53 +08:00
logx.Errorf("copy table error: %s", err.Error())
}
2024-11-20 22:43:53 +08:00
biz.ErrIsNilAppendErr(err, "copy table error: %s")
}
func getDbId(rc *req.Ctx) uint64 {
2024-02-25 12:46:18 +08:00
dbId := rc.PathParamInt("dbId")
2024-11-20 22:43:53 +08:00
biz.IsTrue(dbId > 0, "dbId error")
2021-01-08 15:37:32 +08:00
return uint64(dbId)
}
func getDbName(rc *req.Ctx) string {
2024-02-25 12:46:18 +08:00
db := rc.Query("db")
2024-11-20 22:43:53 +08:00
biz.NotEmpty(db, "db cannot be empty")
return db
}
func (d *Db) getDbConn(rc *req.Ctx) *dbi.DbConn {
dc, err := d.dbApp.GetDbConn(getDbId(rc), getDbName(rc))
biz.ErrIsNil(err)
return dc
}