mirror of
https://gitee.com/dromara/mayfly-go
synced 2026-03-09 19:45:39 +08:00
feat: 新增统一文件模块,统一文件操作
This commit is contained in:
@@ -25,7 +25,7 @@ import (
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
"mayfly-go/pkg/utils/cryptox"
|
||||
"mayfly-go/pkg/utils/stringx"
|
||||
"mayfly-go/pkg/utils/writer"
|
||||
"mayfly-go/pkg/utils/writerx"
|
||||
"mayfly-go/pkg/ws"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -196,9 +196,6 @@ func (d *Db) ExecSqlFile(rc *req.Ctx) {
|
||||
|
||||
executedStatements++
|
||||
_, err = dbConn.Exec(sql)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
})
|
||||
|
||||
@@ -258,7 +255,7 @@ func (d *Db) DumpSql(rc *req.Ctx) {
|
||||
Tables: tables,
|
||||
DumpDDL: needStruct,
|
||||
DumpData: needData,
|
||||
Writer: writer.NewGzipWriter(rc.GetWriter()),
|
||||
Writer: writerx.NewGzipWriter(rc.GetWriter()),
|
||||
}))
|
||||
|
||||
rc.ReqParam = collx.Kvs("db", dbConn.Info, "database", dbName, "tables", tablesStr, "dumpType", dumpType)
|
||||
|
||||
@@ -1,30 +1,29 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"mayfly-go/internal/db/api/form"
|
||||
"mayfly-go/internal/db/api/vo"
|
||||
"mayfly-go/internal/db/application"
|
||||
"mayfly-go/internal/db/config"
|
||||
"mayfly-go/internal/db/dbm/dbi"
|
||||
"mayfly-go/internal/db/dbm/sqlparser"
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
fileapp "mayfly-go/internal/file/application"
|
||||
msgapp "mayfly-go/internal/msg/application"
|
||||
msgdto "mayfly-go/internal/msg/application/dto"
|
||||
tagapp "mayfly-go/internal/tag/application"
|
||||
"mayfly-go/pkg/biz"
|
||||
"mayfly-go/pkg/errorx"
|
||||
"mayfly-go/pkg/model"
|
||||
"mayfly-go/pkg/req"
|
||||
"mayfly-go/pkg/utils/anyx"
|
||||
"mayfly-go/pkg/utils/stringx"
|
||||
"mayfly-go/pkg/ws"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/may-fly/cast"
|
||||
)
|
||||
|
||||
type DbTransferTask struct {
|
||||
@@ -34,6 +33,7 @@ type DbTransferTask struct {
|
||||
TagApp tagapp.TagTree `inject:"TagTreeApp"`
|
||||
MsgApp msgapp.Msg `inject:""`
|
||||
DbSqlExecApp application.DbSqlExec `inject:""`
|
||||
FileApp fileapp.File `inject:""`
|
||||
}
|
||||
|
||||
func (d *DbTransferTask) Tasks(rc *req.Ctx) {
|
||||
@@ -41,11 +41,13 @@ func (d *DbTransferTask) Tasks(rc *req.Ctx) {
|
||||
res, err := d.DbTransferTask.GetPageList(queryCond, page, new([]vo.DbTransferTaskListVO))
|
||||
biz.ErrIsNil(err)
|
||||
|
||||
list := res.List.(*[]vo.DbTransferTaskListVO)
|
||||
for _, item := range *list {
|
||||
item.RunningState = entity.DbTransferTaskRunStateSuccess
|
||||
if d.DbTransferTask.IsRunning(item.Id) {
|
||||
item.RunningState = entity.DbTransferTaskRunStateRunning
|
||||
if res.List != nil {
|
||||
list := res.List.(*[]vo.DbTransferTaskListVO)
|
||||
for _, item := range *list {
|
||||
item.RunningState = entity.DbTransferTaskRunStateSuccess
|
||||
if d.DbTransferTask.IsRunning(item.Id) {
|
||||
item.RunningState = entity.DbTransferTaskRunStateRunning
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,13 +108,6 @@ func (d *DbTransferTask) Files(rc *req.Ctx) {
|
||||
rc.ResData = res
|
||||
}
|
||||
|
||||
func (d *DbTransferTask) FileRename(rc *req.Ctx) {
|
||||
fm := &form.DbTransferFileForm{}
|
||||
tFile := req.BindJsonAndCopyTo[*entity.DbTransferFile](rc, fm, new(entity.DbTransferFile))
|
||||
_ = d.DbTransferFile.UpdateById(rc.MetaCtx, tFile)
|
||||
rc.ReqParam = fm
|
||||
}
|
||||
|
||||
func (d *DbTransferTask) FileDel(rc *req.Ctx) {
|
||||
fileId := rc.PathParam("fileId")
|
||||
rc.ReqParam = fileId // 记录操作日志
|
||||
@@ -120,50 +115,11 @@ func (d *DbTransferTask) FileDel(rc *req.Ctx) {
|
||||
|
||||
uIds := make([]uint64, len(ids))
|
||||
for _, v := range ids {
|
||||
value, err := strconv.Atoi(v)
|
||||
biz.ErrIsNilAppendErr(err, "string类型转换为int异常: %s")
|
||||
uIds = append(uIds, uint64(value))
|
||||
uIds = append(uIds, cast.ToUint64(v))
|
||||
}
|
||||
biz.ErrIsNil(d.DbTransferFile.Delete(rc.MetaCtx, uIds...))
|
||||
}
|
||||
|
||||
func (d *DbTransferTask) FileDown(rc *req.Ctx) {
|
||||
fileUuid := rc.PathParam("fileUuid")
|
||||
if fileUuid == "" {
|
||||
panic(errorx.NewBiz("文件id不能为空"))
|
||||
}
|
||||
|
||||
tFile := &entity.DbTransferFile{FileUuid: fileUuid}
|
||||
|
||||
err := d.DbTransferFile.GetByCond(model.NewModelCond(tFile).Dest(tFile))
|
||||
biz.ErrIsNilAppendErr(err, "查询文件出错 %s")
|
||||
|
||||
// 拼接文件地址,并把文件流输出到客户端
|
||||
brc := config.GetDbBackupRestore()
|
||||
filePath := filepath.Join(fmt.Sprintf("%s/%d/%s.sql", brc.TransferPath, tFile.TaskId, fileUuid))
|
||||
|
||||
file, err := os.Open(filePath)
|
||||
biz.ErrIsNilAppendErr(err, "读取文件失败:%s")
|
||||
|
||||
defer file.Close()
|
||||
|
||||
// Get the file information to set the correct response headers
|
||||
fileInfo, err := file.Stat()
|
||||
biz.ErrIsNilAppendErr(err, "读取文件失败:%s")
|
||||
|
||||
rc.ReqParam = tFile // 记录操作日志
|
||||
// 如果文件名不以 .sql 结尾,则加上 .sql
|
||||
if !strings.HasSuffix(tFile.FileName, ".sql") {
|
||||
tFile.FileName += ".sql"
|
||||
}
|
||||
|
||||
rc.Header("Content-Type", "application/octet-stream")
|
||||
rc.Header("Content-Disposition", "attachment; filename="+tFile.FileName)
|
||||
rc.Header("Content-Length", strconv.FormatInt(fileInfo.Size(), 10))
|
||||
_, err = io.Copy(rc.GetWriter(), file)
|
||||
|
||||
}
|
||||
|
||||
func (d *DbTransferTask) FileRun(rc *req.Ctx) {
|
||||
|
||||
fm := req.BindJsonAndValid(rc, &form.DbTransferFileRunForm{})
|
||||
@@ -183,7 +139,7 @@ func (d *DbTransferTask) FileRun(rc *req.Ctx) {
|
||||
if len(errInfo) > 300 {
|
||||
errInfo = errInfo[:300] + "..."
|
||||
}
|
||||
d.MsgApp.CreateAndSend(rc.GetLoginAccount(), msgdto.ErrSysMsg("sql脚本执行失败", fmt.Sprintf("[%s][%s]执行失败: [%s]", tFile.FileName, targetDbConn.Info.GetLogDesc(), errInfo)).WithClientId(fm.ClientId))
|
||||
d.MsgApp.CreateAndSend(rc.GetLoginAccount(), msgdto.ErrSysMsg("sql脚本执行失败", fmt.Sprintf("[%s][%s]执行失败: [%s]", tFile.FileKey, targetDbConn.Info.GetLogDesc(), errInfo)).WithClientId(fm.ClientId))
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -194,13 +150,7 @@ func (d *DbTransferTask) FileRun(rc *req.Ctx) {
|
||||
}
|
||||
|
||||
func (d *DbTransferTask) fileRun(la *model.LoginAccount, fm *form.DbTransferFileRunForm, tFile *entity.DbTransferFile, targetDbConn *dbi.DbConn) {
|
||||
|
||||
filePath := d.DbTransferFile.GetFilePath(tFile)
|
||||
_, err := os.Stat(filePath)
|
||||
biz.ErrIsNilAppendErr(err, "sql文件不存在:%s")
|
||||
|
||||
file, err := os.Open(filePath)
|
||||
biz.ErrIsNilAppendErr(err, "sql文件读取出错:%s")
|
||||
filename, reader, err := d.FileApp.GetReader(context.TODO(), tFile.FileKey)
|
||||
|
||||
executedStatements := 0
|
||||
progressId := stringx.Rand(32)
|
||||
@@ -213,12 +163,12 @@ func (d *DbTransferTask) fileRun(la *model.LoginAccount, fm *form.DbTransferFile
|
||||
biz.ErrIsNilAppendErr(err, "连接目标数据库失败: %s")
|
||||
}
|
||||
|
||||
err = sqlparser.SQLSplit(file, func(sql string) error {
|
||||
err = sqlparser.SQLSplit(reader, func(sql string) error {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
ws.SendJsonMsg(ws.UserId(laId), fm.ClientId, msgdto.InfoSqlProgressMsg("sql脚本执行进度", &progressMsg{
|
||||
Id: progressId,
|
||||
Title: tFile.FileName,
|
||||
Title: filename,
|
||||
ExecutedStatements: executedStatements,
|
||||
Terminated: false,
|
||||
}).WithCategory(progressCategory))
|
||||
@@ -233,5 +183,5 @@ func (d *DbTransferTask) fileRun(la *model.LoginAccount, fm *form.DbTransferFile
|
||||
biz.ErrIsNilAppendErr(err, "执行sql失败: %s")
|
||||
}
|
||||
|
||||
d.MsgApp.CreateAndSend(la, msgdto.SuccessSysMsg("sql脚本执行成功", fmt.Sprintf("sql脚本执行完成:%s", tFile.FileName)).WithClientId(fm.ClientId))
|
||||
d.MsgApp.CreateAndSend(la, msgdto.SuccessSysMsg("sql脚本执行成功", fmt.Sprintf("sql脚本执行完成:%s", filename)).WithClientId(fm.ClientId))
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ type DbTransferFileListVO struct {
|
||||
CreateTime *time.Time `json:"createTime"`
|
||||
Status int8 `json:"status"`
|
||||
FileDbType string `json:"fileDbType"`
|
||||
FileName string `json:"fileName"`
|
||||
FileUuid string `json:"fileUuid"`
|
||||
FileKey string `json:"fileKey"`
|
||||
LogId uint64 `json:"logId"` // 日志ID
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"mayfly-go/pkg/model"
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
"mayfly-go/pkg/utils/stringx"
|
||||
"mayfly-go/pkg/utils/writerx"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -231,7 +232,7 @@ func (d *dbAppImpl) DumpDb(ctx context.Context, reqParam *dto.DumpDb) error {
|
||||
log = reqParam.Log
|
||||
}
|
||||
|
||||
writer := reqParam.Writer
|
||||
writer := writerx.NewStringWriter(reqParam.Writer)
|
||||
defer writer.Close()
|
||||
dbId := reqParam.DbId
|
||||
dbName := reqParam.DbName
|
||||
@@ -241,6 +242,7 @@ func (d *dbAppImpl) DumpDb(ctx context.Context, reqParam *dto.DumpDb) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
writer.WriteString("\n-- ----------------------------")
|
||||
writer.WriteString("\n-- 导出平台: mayfly-go")
|
||||
writer.WriteString(fmt.Sprintf("\n-- 导出时间: %s ", time.Now().Format("2006-01-02 15:04:05")))
|
||||
@@ -306,7 +308,6 @@ func (d *dbAppImpl) DumpDb(ctx context.Context, reqParam *dto.DumpDb) error {
|
||||
log(fmt.Sprintf("获取表[%s]信息...", tableName))
|
||||
quoteTableName := targetMeta.QuoteIdentifier(tableName)
|
||||
|
||||
writer.TryFlush()
|
||||
// 查询表信息,主要是为了查询表注释
|
||||
tbs, err := srcMeta.GetTables(tableName)
|
||||
if err != nil {
|
||||
|
||||
@@ -5,11 +5,11 @@ import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/google/uuid"
|
||||
"mayfly-go/internal/db/application/dto"
|
||||
"mayfly-go/internal/db/dbm/dbi"
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
"mayfly-go/internal/db/domain/repository"
|
||||
fileapp "mayfly-go/internal/file/application"
|
||||
sysapp "mayfly-go/internal/sys/application"
|
||||
sysentity "mayfly-go/internal/sys/domain/entity"
|
||||
"mayfly-go/pkg/base"
|
||||
@@ -20,12 +20,13 @@ import (
|
||||
"mayfly-go/pkg/model"
|
||||
"mayfly-go/pkg/scheduler"
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
"mayfly-go/pkg/utils/writer"
|
||||
"os"
|
||||
"mayfly-go/pkg/utils/timex"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
@@ -57,9 +58,10 @@ type DbTransferTask interface {
|
||||
type dbTransferAppImpl struct {
|
||||
base.AppImpl[*entity.DbTransferTask, repository.DbTransferTask]
|
||||
|
||||
dbApp Db `inject:"DbApp"`
|
||||
logApp sysapp.Syslog `inject:"SyslogApp"`
|
||||
fileApp DbTransferFile `inject:"DbTransferFileApp"`
|
||||
dbApp Db `inject:"DbApp"`
|
||||
logApp sysapp.Syslog `inject:"SyslogApp"`
|
||||
transferFileApp DbTransferFile `inject:"DbTransferFileApp"`
|
||||
fileApp fileapp.File `inject:"FileApp"`
|
||||
}
|
||||
|
||||
func (app *dbTransferAppImpl) InjectDbTransferTaskRepo(repo repository.DbTransferTask) {
|
||||
@@ -133,7 +135,7 @@ func (app *dbTransferAppImpl) InitCronJob() {
|
||||
}
|
||||
}
|
||||
// 把所有运行中的文件状态设置为失败
|
||||
_ = app.fileApp.UpdateByCond(context.TODO(), &entity.DbTransferFile{Status: entity.DbTransferFileStatusFail}, &entity.DbTransferFile{Status: entity.DbTransferFileStatusRunning})
|
||||
_ = app.transferFileApp.UpdateByCond(context.TODO(), &entity.DbTransferFile{Status: entity.DbTransferFileStatusFail}, &entity.DbTransferFile{Status: entity.DbTransferFileStatusRunning})
|
||||
|
||||
// 把所有需要定时执行的任务添加到定时任务中
|
||||
pageParam := &model.PageParam{
|
||||
@@ -255,7 +257,6 @@ func (app *dbTransferAppImpl) transfer2Db(ctx context.Context, taskId uint64, lo
|
||||
}
|
||||
|
||||
func (app *dbTransferAppImpl) transfer2File(ctx context.Context, taskId uint64, logId uint64, task *entity.DbTransferTask, srcConn *dbi.DbConn, start time.Time, tables []dbi.Table) {
|
||||
|
||||
// 1、新增迁移文件数据
|
||||
nowTime := time.Now()
|
||||
tFile := &entity.DbTransferFile{
|
||||
@@ -263,14 +264,16 @@ func (app *dbTransferAppImpl) transfer2File(ctx context.Context, taskId uint64,
|
||||
CreateTime: &nowTime,
|
||||
Status: entity.DbTransferFileStatusRunning,
|
||||
FileDbType: cmp.Or(task.TargetFileDbType, task.TargetDbType),
|
||||
FileName: fmt.Sprintf("%s.sql", task.TaskName), // 用于下载和展示
|
||||
FileUuid: uuid.New().String(), // 用于存放到磁盘
|
||||
LogId: logId,
|
||||
}
|
||||
_ = app.fileApp.Save(ctx, tFile)
|
||||
_ = app.transferFileApp.Save(ctx, tFile)
|
||||
|
||||
// 新建一个文件,文件位置为 {transferPath}/{taskId}/{uuid}.sql
|
||||
filePath := app.fileApp.GetFilePath(tFile)
|
||||
filename := fmt.Sprintf("dtf_%s_%s.sql", task.TaskName, timex.TimeNo())
|
||||
fileKey, writer, saveFileFunc, err := app.fileApp.NewWriter(ctx, "", filename)
|
||||
if err != nil {
|
||||
app.EndTransfer(ctx, logId, taskId, "创建文件失败", err, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 从tables提取表名
|
||||
tableNames := make([]string, 0)
|
||||
@@ -278,11 +281,12 @@ func (app *dbTransferAppImpl) transfer2File(ctx context.Context, taskId uint64,
|
||||
tableNames = append(tableNames, table.TableName)
|
||||
}
|
||||
// 2、把源库数据迁移到文件
|
||||
app.Log(ctx, logId, fmt.Sprintf("开始迁移表数据到文件: %s", filePath))
|
||||
app.Log(ctx, logId, fmt.Sprintf("开始迁移表数据到文件: %s", filename))
|
||||
|
||||
app.Log(ctx, logId, fmt.Sprintf("目标库文件语言类型: %s", task.TargetFileDbType))
|
||||
|
||||
go func() {
|
||||
defer saveFileFunc()
|
||||
defer app.MarkStop(taskId)
|
||||
defer app.logApp.Flush(logId, true)
|
||||
ctx = context.Background()
|
||||
@@ -294,7 +298,7 @@ func (app *dbTransferAppImpl) transfer2File(ctx context.Context, taskId uint64,
|
||||
Tables: tableNames,
|
||||
DumpDDL: true,
|
||||
DumpData: true,
|
||||
Writer: writer.NewFileWriter(filePath),
|
||||
Writer: writer,
|
||||
Log: func(msg string) { // 记录日志
|
||||
app.Log(ctx, logId, msg)
|
||||
},
|
||||
@@ -302,15 +306,16 @@ func (app *dbTransferAppImpl) transfer2File(ctx context.Context, taskId uint64,
|
||||
if err != nil {
|
||||
app.EndTransfer(ctx, logId, taskId, "数据库迁移失败", err, nil)
|
||||
tFile.Status = entity.DbTransferFileStatusFail
|
||||
_ = app.fileApp.UpdateById(ctx, tFile)
|
||||
_ = app.transferFileApp.UpdateById(ctx, tFile)
|
||||
// 删除文件
|
||||
_ = os.Remove(filePath)
|
||||
_ = app.fileApp.Remove(ctx, fileKey)
|
||||
return
|
||||
}
|
||||
app.EndTransfer(ctx, logId, taskId, "数据库迁移完成", err, nil)
|
||||
|
||||
tFile.Status = entity.DbTransferFileStatusSuccess
|
||||
_ = app.fileApp.UpdateById(ctx, tFile)
|
||||
tFile.FileKey = fileKey
|
||||
_ = app.transferFileApp.UpdateById(ctx, tFile)
|
||||
}()
|
||||
|
||||
}
|
||||
|
||||
@@ -2,15 +2,11 @@ package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/google/uuid"
|
||||
"mayfly-go/internal/db/config"
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
"mayfly-go/internal/db/domain/repository"
|
||||
fileapp "mayfly-go/internal/file/application"
|
||||
"mayfly-go/pkg/base"
|
||||
"mayfly-go/pkg/model"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type DbTransferFile interface {
|
||||
@@ -22,14 +18,14 @@ type DbTransferFile interface {
|
||||
Save(ctx context.Context, instanceEntity *entity.DbTransferFile) error
|
||||
|
||||
Delete(ctx context.Context, id ...uint64) error
|
||||
|
||||
GetFilePath(ent *entity.DbTransferFile) string
|
||||
}
|
||||
|
||||
var _ DbTransferFile = (*dbTransferFileAppImpl)(nil)
|
||||
|
||||
type dbTransferFileAppImpl struct {
|
||||
base.AppImpl[*entity.DbTransferFile, repository.DbTransferFile]
|
||||
|
||||
fileApp fileapp.File `inject:"FileApp"`
|
||||
}
|
||||
|
||||
func (app *dbTransferFileAppImpl) InjectDbTransferFileRepo(repo repository.DbTransferFile) {
|
||||
@@ -51,28 +47,16 @@ func (app *dbTransferFileAppImpl) Save(ctx context.Context, taskEntity *entity.D
|
||||
}
|
||||
|
||||
func (app *dbTransferFileAppImpl) Delete(ctx context.Context, id ...uint64) error {
|
||||
|
||||
arr, err := app.GetByIds(id, "task_id", "file_uuid")
|
||||
arr, err := app.GetByIds(id, "task_id", "file_key")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 删除对应的文件
|
||||
for _, file := range arr {
|
||||
_ = os.Remove(app.GetFilePath(file))
|
||||
_ = app.fileApp.Remove(ctx, file.FileKey)
|
||||
}
|
||||
|
||||
// 删除数据
|
||||
return app.DeleteById(ctx, id...)
|
||||
}
|
||||
|
||||
func (app *dbTransferFileAppImpl) GetFilePath(ent *entity.DbTransferFile) string {
|
||||
brc := config.GetDbBackupRestore()
|
||||
if ent.FileUuid == "" {
|
||||
ent.FileUuid = uuid.New().String()
|
||||
}
|
||||
|
||||
filePath := filepath.Join(fmt.Sprintf("%s/%d/%s.sql", brc.TransferPath, ent.TaskId, ent.FileUuid))
|
||||
|
||||
return filePath
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"io"
|
||||
"mayfly-go/internal/db/dbm/dbi"
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
tagentity "mayfly-go/internal/tag/domain/entity"
|
||||
"mayfly-go/pkg/utils/writer"
|
||||
)
|
||||
|
||||
type SaveDbInstance struct {
|
||||
@@ -22,7 +22,7 @@ type DumpDb struct {
|
||||
|
||||
LogId uint64
|
||||
|
||||
Writer writer.CustomWriter
|
||||
Writer io.WriteCloser
|
||||
Log func(msg string)
|
||||
TargetDbType dbi.DbType
|
||||
}
|
||||
|
||||
@@ -71,15 +71,19 @@ func parseSQL(r io.Reader, callback SQLCallback) error {
|
||||
buffer.Next(size)
|
||||
case inString:
|
||||
if escapeNextChar {
|
||||
// 当前字符是转义后的字符,直接写入。如后一个为" 避免进入r==stringDelimiter判断被当做字符串结束符中断
|
||||
currentStatement.WriteRune(r)
|
||||
escapeNextChar = false
|
||||
} else if r == '\\' {
|
||||
// 当前字符是转义符,设置标志位并写入
|
||||
escapeNextChar = true
|
||||
currentStatement.WriteRune(r)
|
||||
} else if r == stringDelimiter {
|
||||
// 当前字符是字符串结束符,结束字符串处理
|
||||
inString = false
|
||||
currentStatement.WriteRune(r)
|
||||
} else {
|
||||
// 其他字符,直接写入
|
||||
currentStatement.WriteRune(r)
|
||||
}
|
||||
buffer.Next(size)
|
||||
|
||||
@@ -13,8 +13,7 @@ type DbTransferFile struct {
|
||||
TaskId uint64 `orm:"column(task_id)" json:"taskId"` // 迁移任务ID
|
||||
LogId uint64 `orm:"column(log_id)" json:"logId"` // 日志ID
|
||||
FileDbType string `orm:"column(file_db_type)" json:"fileDbType"` // sql文件数据库类型
|
||||
FileName string `orm:"column(file_name)" json:"fileName"` // 显式文件名
|
||||
FileUuid string `orm:"column(file_uuid)" json:"fileUuid"` // 文件真实id,拼接后可以下载
|
||||
FileKey string `orm:"column(file_key)" json:"fileKey"` // 文件
|
||||
}
|
||||
|
||||
func (d *DbTransferFile) TableName() string {
|
||||
|
||||
@@ -37,15 +37,10 @@ func InitDbTransferRouter(router *gin.RouterGroup) {
|
||||
// 导出文件管理-列表
|
||||
req.NewGet("/files/:taskId", d.Files),
|
||||
|
||||
req.NewPost("/files/rename", d.FileRename).Log(req.NewLogSave("dts-删除迁移文件")).RequiredPermissionCode("db:transfer:files:rename"),
|
||||
|
||||
// 导出文件管理-删除
|
||||
req.NewPost("/files/del/:fileId", d.FileDel).Log(req.NewLogSave("dts-删除迁移文件")).RequiredPermissionCode("db:transfer:files:del"),
|
||||
|
||||
req.NewPost("/files/run", d.FileRun).Log(req.NewLogSave("dts-执行sql文件")).RequiredPermissionCode("db:transfer:files:run"),
|
||||
|
||||
// 导出文件管理-下载
|
||||
req.NewGet("/files/down/:fileUuid", d.FileDown).Log(req.NewLogSave("dts-下载迁移文件")).RequiredPermissionCode("db:transfer:files:down"),
|
||||
}
|
||||
|
||||
req.BatchSetGroup(instances, reqs[:])
|
||||
|
||||
53
server/internal/file/api/file.go
Normal file
53
server/internal/file/api/file.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/file/api/vo"
|
||||
"mayfly-go/internal/file/application"
|
||||
"mayfly-go/pkg/biz"
|
||||
"mayfly-go/pkg/model"
|
||||
"mayfly-go/pkg/req"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type File struct {
|
||||
FileApp application.File `inject:""`
|
||||
}
|
||||
|
||||
func (f *File) GetFileByKeys(rc *req.Ctx) {
|
||||
keysStr := rc.PathParam("keys")
|
||||
biz.NotEmpty(keysStr, "keys不能为空")
|
||||
|
||||
var files []vo.SimpleFile
|
||||
err := f.FileApp.ListByCondToAny(model.NewCond().In("file_key", strings.Split(keysStr, ",")), &files)
|
||||
biz.ErrIsNil(err)
|
||||
rc.ResData = files
|
||||
}
|
||||
|
||||
func (f *File) GetFileContent(rc *req.Ctx) {
|
||||
key := rc.PathParam("key")
|
||||
biz.NotEmpty(key, "key不能为空")
|
||||
|
||||
filename, reader, err := f.FileApp.GetReader(rc.MetaCtx, key)
|
||||
if err != nil {
|
||||
rc.GetWriter().Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
defer reader.Close()
|
||||
rc.Download(reader, filename)
|
||||
}
|
||||
|
||||
func (f *File) Upload(rc *req.Ctx) {
|
||||
multipart, err := rc.GetRequest().MultipartReader()
|
||||
biz.ErrIsNilAppendErr(err, "读取文件失败: %s")
|
||||
file, err := multipart.NextPart()
|
||||
biz.ErrIsNilAppendErr(err, "读取文件失败: %s")
|
||||
defer file.Close()
|
||||
|
||||
fileKey, err := f.FileApp.Upload(rc.MetaCtx, rc.Query("fileKey"), file.FileName(), file)
|
||||
biz.ErrIsNil(err)
|
||||
rc.ResData = fileKey
|
||||
}
|
||||
|
||||
func (f *File) Remove(rc *req.Ctx) {
|
||||
biz.ErrIsNil(f.FileApp.Remove(rc.MetaCtx, rc.PathParam("key")))
|
||||
}
|
||||
7
server/internal/file/api/vo/file.go
Normal file
7
server/internal/file/api/vo/file.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package vo
|
||||
|
||||
type SimpleFile struct {
|
||||
Filename string `json:"filename"`
|
||||
FileKey string `json:"fileKey"`
|
||||
Size int64 `json:"size"`
|
||||
}
|
||||
9
server/internal/file/application/application.go
Normal file
9
server/internal/file/application/application.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"mayfly-go/pkg/ioc"
|
||||
)
|
||||
|
||||
func InitIoc() {
|
||||
ioc.Register(new(fileAppImpl), ioc.WithComponentName("FileApp"))
|
||||
}
|
||||
169
server/internal/file/application/file.go
Normal file
169
server/internal/file/application/file.go
Normal file
@@ -0,0 +1,169 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"mayfly-go/internal/file/config"
|
||||
"mayfly-go/internal/file/domain/entity"
|
||||
"mayfly-go/internal/file/domain/repository"
|
||||
"mayfly-go/pkg/base"
|
||||
"mayfly-go/pkg/errorx"
|
||||
"mayfly-go/pkg/logx"
|
||||
"mayfly-go/pkg/utils/stringx"
|
||||
"mayfly-go/pkg/utils/writerx"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/may-fly/cast"
|
||||
)
|
||||
|
||||
type File interface {
|
||||
base.App[*entity.File]
|
||||
|
||||
// Upload 上传文件
|
||||
//
|
||||
// @param fileKey 文件key,若存在则使用存在的文件key,否则生成新的文件key。
|
||||
//
|
||||
// @param filename 文件名,带文件后缀
|
||||
//
|
||||
// @return fileKey 文件key
|
||||
Upload(ctx context.Context, fileKey string, filename string, r io.Reader) (string, error)
|
||||
|
||||
// NewWriter 创建文件writer
|
||||
//
|
||||
// @param canEmptyFileKey 文件key,若不为空则使用该文件key,否则生成新的文件key。
|
||||
//
|
||||
// @param filename 文件名,带文件后缀
|
||||
//
|
||||
// @return fileKey 文件key
|
||||
//
|
||||
// @return writer 文件writer
|
||||
//
|
||||
// @return saveFunc 保存文件信息的回调函数 (必须要defer中调用才会入库保存该文件信息)
|
||||
NewWriter(ctx context.Context, canEmptyFileKey string, filename string) (fileKey string, writer *writerx.CountingWriteCloser, saveFunc func() error, err error)
|
||||
|
||||
// GetReader 获取文件reader
|
||||
//
|
||||
// @return filename 文件名
|
||||
//
|
||||
// @return reader 文件reader
|
||||
//
|
||||
// @return err 错误
|
||||
GetReader(ctx context.Context, fileKey string) (string, io.ReadCloser, error)
|
||||
|
||||
// Remove 删除文件
|
||||
Remove(ctx context.Context, fileKey string) error
|
||||
}
|
||||
|
||||
type fileAppImpl struct {
|
||||
base.AppImpl[*entity.File, repository.File]
|
||||
}
|
||||
|
||||
func (f *fileAppImpl) InjectFileRepo(repo repository.File) {
|
||||
f.Repo = repo
|
||||
}
|
||||
|
||||
func (f *fileAppImpl) Upload(ctx context.Context, fileKey string, filename string, r io.Reader) (string, error) {
|
||||
fileKey, writer, saveFileFunc, err := f.NewWriter(ctx, fileKey, filename)
|
||||
if err != nil {
|
||||
return fileKey, err
|
||||
}
|
||||
defer saveFileFunc()
|
||||
|
||||
if _, err := io.Copy(writer, r); err != nil {
|
||||
return fileKey, err
|
||||
}
|
||||
return fileKey, nil
|
||||
}
|
||||
|
||||
func (f *fileAppImpl) NewWriter(ctx context.Context, canEmptyFileKey string, filename string) (fileKey string, writer *writerx.CountingWriteCloser, saveFunc func() error, err error) {
|
||||
isNewFile := true
|
||||
file := &entity.File{}
|
||||
|
||||
if canEmptyFileKey == "" {
|
||||
canEmptyFileKey = stringx.RandUUID()
|
||||
file.FileKey = canEmptyFileKey
|
||||
} else {
|
||||
file.FileKey = canEmptyFileKey
|
||||
if err := f.GetByCond(file); err == nil {
|
||||
isNewFile = false
|
||||
}
|
||||
}
|
||||
file.Filename = filename
|
||||
|
||||
if !isNewFile {
|
||||
// 先删除旧文件
|
||||
f.remove(ctx, file)
|
||||
}
|
||||
|
||||
// 生产新的文件名
|
||||
newFilename := canEmptyFileKey + filepath.Ext(filename)
|
||||
filepath, w, err := f.newWriter(newFilename)
|
||||
if err != nil {
|
||||
return "", nil, nil, err
|
||||
}
|
||||
file.Path = filepath
|
||||
|
||||
fileKey = canEmptyFileKey
|
||||
writer = writerx.NewCountingWriteCloser(w)
|
||||
// 创建回调函数
|
||||
saveFunc = func() error {
|
||||
// 获取已写入的字节数
|
||||
file.Size = writer.BytesWritten()
|
||||
writer.Close()
|
||||
// 保存文件信息
|
||||
return f.Save(ctx, file)
|
||||
}
|
||||
|
||||
return fileKey, writer, saveFunc, nil
|
||||
}
|
||||
|
||||
func (f *fileAppImpl) GetReader(ctx context.Context, fileKey string) (string, io.ReadCloser, error) {
|
||||
file := &entity.File{FileKey: fileKey}
|
||||
if err := f.GetByCond(file); err != nil {
|
||||
return "", nil, errorx.NewBiz("文件不存在")
|
||||
}
|
||||
r, err := os.Open(filepath.Join(config.GetFileConfig().BasePath, file.Path))
|
||||
return file.Filename, r, err
|
||||
}
|
||||
|
||||
func (f *fileAppImpl) Remove(ctx context.Context, fileKey string) error {
|
||||
file := &entity.File{FileKey: fileKey}
|
||||
if err := f.GetByCond(file); err != nil {
|
||||
return errorx.NewBiz("文件不存在")
|
||||
}
|
||||
f.DeleteById(ctx, file.Id)
|
||||
return f.remove(ctx, file)
|
||||
}
|
||||
|
||||
func (f *fileAppImpl) newWriter(filename string) (string, io.WriteCloser, error) {
|
||||
now := time.Now()
|
||||
filePath := filepath.Join(cast.ToString(now.Year()), cast.ToString(int(now.Month())), cast.ToString(now.Day()), cast.ToString(now.Hour()), filename)
|
||||
fileAbsPath := filepath.Join(config.GetFileConfig().BasePath, filePath)
|
||||
|
||||
// 目录不存在则创建
|
||||
fileDir := filepath.Dir(fileAbsPath)
|
||||
if _, err := os.Stat(fileDir); os.IsNotExist(err) {
|
||||
err = os.MkdirAll(fileDir, os.ModePerm)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 创建目标文件
|
||||
out, err := os.OpenFile(fileAbsPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0766)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return filePath, out, nil
|
||||
}
|
||||
|
||||
func (f *fileAppImpl) remove(ctx context.Context, file *entity.File) error {
|
||||
if err := os.Remove(filepath.Join(config.GetFileConfig().BasePath, file.Path)); err != nil {
|
||||
logx.ErrorfContext(ctx, "删除旧文件[%s] 失败: %s", file.Path, err.Error())
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
24
server/internal/file/config/config.go
Normal file
24
server/internal/file/config/config.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
sysapp "mayfly-go/internal/sys/application"
|
||||
|
||||
"github.com/may-fly/cast"
|
||||
)
|
||||
|
||||
const (
|
||||
ConfigKeyFile string = "FileConfig" // 文件配置key
|
||||
)
|
||||
|
||||
type FileConfig struct {
|
||||
BasePath string // 文件基础路径
|
||||
}
|
||||
|
||||
func GetFileConfig() *FileConfig {
|
||||
c := sysapp.GetConfigApp().GetConfig(ConfigKeyFile)
|
||||
jm := c.GetJsonMap()
|
||||
|
||||
fc := new(FileConfig)
|
||||
fc.BasePath = cast.ToStringD(jm["basePath"], "./file")
|
||||
return fc
|
||||
}
|
||||
16
server/internal/file/domain/entity/file.go
Normal file
16
server/internal/file/domain/entity/file.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package entity
|
||||
|
||||
import "mayfly-go/pkg/model"
|
||||
|
||||
type File struct {
|
||||
model.Model
|
||||
|
||||
FileKey string `json:"fikeKey"` // 文件key
|
||||
Filename string `json:"filename"` // 文件名
|
||||
Path string `json:"path" ` // 文件路径
|
||||
Size int64 `json:"size"`
|
||||
}
|
||||
|
||||
func (a *File) TableName() string {
|
||||
return "t_sys_file"
|
||||
}
|
||||
5
server/internal/file/domain/entity/query.go
Normal file
5
server/internal/file/domain/entity/query.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package entity
|
||||
|
||||
type FileQuery struct {
|
||||
Keys []string
|
||||
}
|
||||
10
server/internal/file/domain/repository/file.go
Normal file
10
server/internal/file/domain/repository/file.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/file/domain/entity"
|
||||
"mayfly-go/pkg/base"
|
||||
)
|
||||
|
||||
type File interface {
|
||||
base.Repo[*entity.File]
|
||||
}
|
||||
15
server/internal/file/infrastructure/persistence/file.go
Normal file
15
server/internal/file/infrastructure/persistence/file.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package persistence
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/file/domain/entity"
|
||||
"mayfly-go/internal/file/domain/repository"
|
||||
"mayfly-go/pkg/base"
|
||||
)
|
||||
|
||||
type fileRepoImpl struct {
|
||||
base.RepoImpl[*entity.File]
|
||||
}
|
||||
|
||||
func newFileRepo() repository.File {
|
||||
return &fileRepoImpl{base.RepoImpl[*entity.File]{M: new(entity.File)}}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package persistence
|
||||
|
||||
import (
|
||||
"mayfly-go/pkg/ioc"
|
||||
)
|
||||
|
||||
func InitIoc() {
|
||||
ioc.Register(newFileRepo(), ioc.WithComponentName("FileRepo"))
|
||||
}
|
||||
16
server/internal/file/init/init.go
Normal file
16
server/internal/file/init/init.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package init
|
||||
|
||||
import (
|
||||
"mayfly-go/initialize"
|
||||
"mayfly-go/internal/file/application"
|
||||
"mayfly-go/internal/file/infrastructure/persistence"
|
||||
"mayfly-go/internal/file/router"
|
||||
)
|
||||
|
||||
func init() {
|
||||
initialize.AddInitIocFunc(func() {
|
||||
persistence.InitIoc()
|
||||
application.InitIoc()
|
||||
})
|
||||
initialize.AddInitRouterFunc(router.Init)
|
||||
}
|
||||
29
server/internal/file/router/file.go
Normal file
29
server/internal/file/router/file.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/file/api"
|
||||
"mayfly-go/pkg/biz"
|
||||
"mayfly-go/pkg/ioc"
|
||||
"mayfly-go/pkg/req"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func InitFileRouter(router *gin.RouterGroup) {
|
||||
file := router.Group("sys/files")
|
||||
f := new(api.File)
|
||||
biz.ErrIsNil(ioc.Inject(f))
|
||||
|
||||
reqs := [...]*req.Conf{
|
||||
|
||||
req.NewGet("/detail/:keys", f.GetFileByKeys).DontNeedToken(),
|
||||
|
||||
req.NewGet("/:key", f.GetFileContent).DontNeedToken().NoRes(),
|
||||
|
||||
req.NewPost("/upload", f.Upload).Log(req.NewLogSave("file-文件上传")),
|
||||
|
||||
req.NewDelete("/:key", f.Remove).Log(req.NewLogSave("file-文件删除")),
|
||||
}
|
||||
|
||||
req.BatchSetGroup(file, reqs[:])
|
||||
}
|
||||
9
server/internal/file/router/router.go
Normal file
9
server/internal/file/router/router.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func Init(router *gin.RouterGroup) {
|
||||
InitFileRouter(router)
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"mayfly-go/internal/common/consts"
|
||||
"mayfly-go/internal/event"
|
||||
@@ -9,7 +8,6 @@ import (
|
||||
"mayfly-go/internal/machine/api/vo"
|
||||
"mayfly-go/internal/machine/application"
|
||||
"mayfly-go/internal/machine/application/dto"
|
||||
"mayfly-go/internal/machine/config"
|
||||
"mayfly-go/internal/machine/domain/entity"
|
||||
"mayfly-go/internal/machine/guac"
|
||||
"mayfly-go/internal/machine/mcm"
|
||||
@@ -25,8 +23,6 @@ import (
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
"mayfly-go/pkg/ws"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -243,15 +239,6 @@ func (m *Machine) MachineTermOpRecords(rc *req.Ctx) {
|
||||
rc.ResData = res
|
||||
}
|
||||
|
||||
func (m *Machine) MachineTermOpRecord(rc *req.Ctx) {
|
||||
termOp, err := m.MachineTermOpApp.GetById(uint64(rc.PathParamInt("recId")))
|
||||
biz.ErrIsNil(err)
|
||||
|
||||
bytes, err := os.ReadFile(path.Join(config.GetMachine().TerminalRecPath, termOp.RecordFilePath))
|
||||
biz.ErrIsNilAppendErr(err, "读取终端操作记录失败: %s")
|
||||
rc.ResData = base64.StdEncoding.EncodeToString(bytes)
|
||||
}
|
||||
|
||||
const (
|
||||
SocketTimeout = 15 * time.Second
|
||||
MaxGuacMessage = 8192
|
||||
|
||||
@@ -3,6 +3,7 @@ package application
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
fileapp "mayfly-go/internal/file/application"
|
||||
"mayfly-go/internal/machine/config"
|
||||
"mayfly-go/internal/machine/domain/entity"
|
||||
"mayfly-go/internal/machine/domain/repository"
|
||||
@@ -15,8 +16,7 @@ import (
|
||||
"mayfly-go/pkg/scheduler"
|
||||
"mayfly-go/pkg/utils/jsonx"
|
||||
"mayfly-go/pkg/utils/stringx"
|
||||
"os"
|
||||
"path"
|
||||
"mayfly-go/pkg/utils/timex"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
@@ -38,6 +38,7 @@ type machineTermOpAppImpl struct {
|
||||
base.AppImpl[*entity.MachineTermOp, repository.MachineTermOp]
|
||||
|
||||
machineCmdConfApp MachineCmdConf `inject:"MachineCmdConfApp"`
|
||||
fileApp fileapp.File `inject:"FileApp"`
|
||||
}
|
||||
|
||||
// 注入MachineTermOpRepo
|
||||
@@ -63,20 +64,14 @@ func (m *machineTermOpAppImpl) TermConn(ctx context.Context, cli *mcm.Cli, wsCon
|
||||
termOpRecord.MachineId = cli.Info.Id
|
||||
termOpRecord.Username = cli.Info.Username
|
||||
|
||||
// 回放文件路径为: 基础配置路径/机器编号/操作日期(202301)/day/hour/randstr.cast
|
||||
recRelPath := path.Join(cli.Info.Code, now.Format("200601"), fmt.Sprintf("%d", now.Day()), fmt.Sprintf("%d", now.Hour()))
|
||||
// 文件绝对路径
|
||||
recAbsPath := path.Join(config.GetMachine().TerminalRecPath, recRelPath)
|
||||
os.MkdirAll(recAbsPath, 0766)
|
||||
filename := fmt.Sprintf("%s.cast", stringx.RandByChars(18, stringx.LowerChars))
|
||||
f, err := os.OpenFile(path.Join(recAbsPath, filename), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0766)
|
||||
fileKey, wc, saveFileFunc, err := m.fileApp.NewWriter(ctx, "", fmt.Sprintf("mto_%d_%s.cast", termOpRecord.MachineId, timex.TimeNo()))
|
||||
if err != nil {
|
||||
return errorx.NewBiz("创建终端回放记录文件失败: %s", err.Error())
|
||||
}
|
||||
defer f.Close()
|
||||
defer saveFileFunc()
|
||||
|
||||
termOpRecord.RecordFilePath = path.Join(recRelPath, filename)
|
||||
recorder = mcm.NewRecorder(f)
|
||||
termOpRecord.FileKey = fileKey
|
||||
recorder = mcm.NewRecorder(wc)
|
||||
}
|
||||
|
||||
createTsParam := &mcm.CreateTerminalSessionParam{
|
||||
@@ -134,9 +129,8 @@ func (m *machineTermOpAppImpl) TimerDeleteTermOp() {
|
||||
return
|
||||
}
|
||||
|
||||
basePath := config.GetMachine().TerminalRecPath
|
||||
for _, termOp := range termOps {
|
||||
if err := m.DeleteTermOp(basePath, termOp); err != nil {
|
||||
if err := m.DeleteTermOp(termOp); err != nil {
|
||||
logx.Warnf("删除终端操作记录失败: %s", err.Error())
|
||||
}
|
||||
}
|
||||
@@ -144,10 +138,10 @@ func (m *machineTermOpAppImpl) TimerDeleteTermOp() {
|
||||
}
|
||||
|
||||
// 删除终端记录即对应文件
|
||||
func (m *machineTermOpAppImpl) DeleteTermOp(basePath string, termOp *entity.MachineTermOp) error {
|
||||
func (m *machineTermOpAppImpl) DeleteTermOp(termOp *entity.MachineTermOp) error {
|
||||
if err := m.DeleteById(context.Background(), termOp.Id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.Remove(path.Join(basePath, termOp.RecordFilePath))
|
||||
return m.fileApp.Remove(context.TODO(), termOp.FileKey)
|
||||
}
|
||||
|
||||
@@ -13,13 +13,11 @@ const (
|
||||
)
|
||||
|
||||
type Machine struct {
|
||||
TerminalRecPath string // 终端操作记录存储位置
|
||||
UploadMaxFileSize int64 // 允许上传的最大文件size
|
||||
TermOpSaveDays int // 终端记录保存天数
|
||||
GuacdHost string // guacd服务地址 默认 127.0.0.1
|
||||
GuacdPort int // guacd服务端口 默认 4822
|
||||
GuacdFilePath string // guacd服务文件存储位置,用于挂载RDP文件夹
|
||||
GuacdRecPath string // guacd服务记录存储位置,用于记录rdp操作记录
|
||||
}
|
||||
|
||||
// 获取机器相关配置
|
||||
@@ -29,12 +27,6 @@ func GetMachine() *Machine {
|
||||
|
||||
mc := new(Machine)
|
||||
|
||||
terminalRecPath := jm["terminalRecPath"]
|
||||
if terminalRecPath == "" {
|
||||
terminalRecPath = "./rec"
|
||||
}
|
||||
mc.TerminalRecPath = terminalRecPath
|
||||
|
||||
// 将1GB等字符串转为int64的byte
|
||||
uploadMaxFileSizeStr := jm["uploadMaxFileSize"]
|
||||
var uploadMaxFileSize int64 = 1 * bytex.GB
|
||||
@@ -51,7 +43,6 @@ func GetMachine() *Machine {
|
||||
mc.GuacdHost = cast.ToString(jm["guacdHost"])
|
||||
mc.GuacdPort = cast.ToIntD(jm["guacdPort"], 4822)
|
||||
mc.GuacdFilePath = cast.ToStringD(jm["guacdFilePath"], "")
|
||||
mc.GuacdRecPath = cast.ToStringD(jm["guacdRecPath"], "")
|
||||
|
||||
return mc
|
||||
}
|
||||
|
||||
@@ -8,10 +8,10 @@ import (
|
||||
type MachineTermOp struct {
|
||||
model.DeletedModel
|
||||
|
||||
MachineId uint64 `json:"machineId"`
|
||||
Username string `json:"username"`
|
||||
RecordFilePath string `json:"recordFilePath"` // 回放文件路径
|
||||
ExecCmds string `json:"execCmds"` // 执行的命令
|
||||
MachineId uint64 `json:"machineId"`
|
||||
Username string `json:"username"`
|
||||
FileKey string `json:"fileKey"` // 文件key
|
||||
ExecCmds string `json:"execCmds"` // 执行的命令
|
||||
|
||||
CreateTime *time.Time `json:"createTime"`
|
||||
CreatorId uint64 `json:"creatorId"`
|
||||
|
||||
@@ -47,9 +47,6 @@ func InitMachineRouter(router *gin.RouterGroup) {
|
||||
|
||||
// 获取机器终端回放记录列表,目前具有保存机器信息的权限标识才有权限查看终端回放
|
||||
req.NewGet(":machineId/term-recs", m.MachineTermOpRecords).RequiredPermission(saveMachineP),
|
||||
|
||||
// 获取机器终端回放记录
|
||||
req.NewGet(":machineId/term-recs/:recId", m.MachineTermOpRecord).RequiredPermission(saveMachineP),
|
||||
}
|
||||
|
||||
req.BatchSetGroup(machines, reqs[:])
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
_ "mayfly-go/internal/auth/init"
|
||||
_ "mayfly-go/internal/common/init"
|
||||
_ "mayfly-go/internal/db/init"
|
||||
_ "mayfly-go/internal/file/init"
|
||||
_ "mayfly-go/internal/flow/init"
|
||||
_ "mayfly-go/internal/machine/init"
|
||||
_ "mayfly-go/internal/mongo/init"
|
||||
|
||||
@@ -9,10 +9,16 @@ import (
|
||||
|
||||
const DefaultDateTimeFormat = "2006-01-02 15:04:05"
|
||||
|
||||
// DefaultFormat 使用默认格式进行格式化: 2006-01-02 15:04:05
|
||||
func DefaultFormat(time time.Time) string {
|
||||
return time.Format(DefaultDateTimeFormat)
|
||||
}
|
||||
|
||||
// TimeNo 获取当前时间编号,格式为20060102150405
|
||||
func TimeNo() string {
|
||||
return time.Now().Format("20060102150405")
|
||||
}
|
||||
|
||||
func NewNullTime(t time.Time) NullTime {
|
||||
return NullTime{
|
||||
NullTime: sql.NullTime{
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
package writer
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type FileWriter struct {
|
||||
tryFlushCount int
|
||||
writer *os.File
|
||||
aborted bool
|
||||
}
|
||||
|
||||
func NewFileWriter(filePath string) *FileWriter {
|
||||
if filePath == "" {
|
||||
panic("filePath is empty")
|
||||
}
|
||||
|
||||
// 使用filepath.Dir函数提取文件夹路径
|
||||
dir := filepath.Dir(filePath)
|
||||
if dir != "" {
|
||||
// 检查文件夹路径,不存在则创建
|
||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||
err = os.MkdirAll(dir, os.ModePerm)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fw, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &FileWriter{writer: fw}
|
||||
}
|
||||
|
||||
func (f *FileWriter) Close() {
|
||||
f.writer.Close()
|
||||
}
|
||||
|
||||
func (f *FileWriter) TryFlush() {
|
||||
}
|
||||
func (f *FileWriter) Write(b []byte) (n int, err error) {
|
||||
return f.writer.Write(b)
|
||||
}
|
||||
|
||||
func (f *FileWriter) WriteString(data string) {
|
||||
io.WriteString(f.writer, data)
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package writer
|
||||
|
||||
import "io"
|
||||
|
||||
type CustomWriter interface {
|
||||
io.Writer
|
||||
WriteString(data string)
|
||||
Close()
|
||||
TryFlush()
|
||||
}
|
||||
26
server/pkg/utils/writerx/counting_writer.go
Normal file
26
server/pkg/utils/writerx/counting_writer.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package writerx
|
||||
|
||||
import "io"
|
||||
|
||||
type CountingWriteCloser struct {
|
||||
w io.WriteCloser
|
||||
n int64 // 已写入的字节数
|
||||
}
|
||||
|
||||
func (c *CountingWriteCloser) Write(p []byte) (int, error) {
|
||||
n, err := c.w.Write(p)
|
||||
c.n += int64(n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (c *CountingWriteCloser) Close() error {
|
||||
return c.w.Close()
|
||||
}
|
||||
|
||||
func (c *CountingWriteCloser) BytesWritten() int64 {
|
||||
return c.n
|
||||
}
|
||||
|
||||
func NewCountingWriteCloser(writer io.WriteCloser) *CountingWriteCloser {
|
||||
return &CountingWriteCloser{w: writer}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package writer
|
||||
package writerx
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
@@ -38,8 +38,8 @@ func (g *GzipWriter) Write(p []byte) (n int, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (g *GzipWriter) Close() {
|
||||
g.writer.Close()
|
||||
func (g *GzipWriter) Close() error {
|
||||
return g.writer.Close()
|
||||
}
|
||||
|
||||
func (g *GzipWriter) TryFlush() {
|
||||
21
server/pkg/utils/writerx/string_writer.go
Normal file
21
server/pkg/utils/writerx/string_writer.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package writerx
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
type StringWriter struct {
|
||||
io.WriteCloser
|
||||
}
|
||||
|
||||
func (sw *StringWriter) WriteString(s string) (n int, err error) {
|
||||
return sw.WriteCloser.Write([]byte(s))
|
||||
}
|
||||
|
||||
func (sw *StringWriter) Close() error {
|
||||
return sw.WriteCloser.Close()
|
||||
}
|
||||
|
||||
func NewStringWriter(writer io.WriteCloser) *StringWriter {
|
||||
return &StringWriter{WriteCloser: writer}
|
||||
}
|
||||
Binary file not shown.
@@ -101,8 +101,7 @@ CREATE TABLE `t_db_transfer_files` (
|
||||
`task_id` bigint COMMENT '迁移任务ID',
|
||||
`log_id` bigint COMMENT '日志ID',
|
||||
`file_db_type` varchar(200) COMMENT 'sql文件数据库类型',
|
||||
`file_name` varchar(200) COMMENT '显式文件名 默认: 年月日时分秒.zip',
|
||||
`file_uuid` varchar(50) COMMENT '文件真实uuid,拼接后可以下载',
|
||||
`file_key` varchar(50) COMMENT '文件',
|
||||
PRIMARY KEY (id)
|
||||
) COMMENT '数据库迁移文件管理';
|
||||
|
||||
@@ -668,11 +667,11 @@ INSERT INTO `t_sys_config` (name, `key`, params, value, remark, create_time, cre
|
||||
INSERT INTO `t_sys_config` (name, `key`, params, value, remark, permission, create_time, creator_id, creator, update_time, modifier_id, modifier, is_deleted, delete_time) VALUES('oauth2登录配置', 'Oauth2Login', '[{"name":"是否启用","model":"enable","placeholder":"是否启用oauth2登录","options":"true,false"},{"name":"名称","model":"name","placeholder":"oauth2名称"},{"name":"Client ID","model":"clientId","placeholder":"Client ID"},{"name":"Client Secret","model":"clientSecret","placeholder":"Client Secret"},{"name":"Authorization URL","model":"authorizationURL","placeholder":"Authorization URL"},{"name":"AccessToken URL","model":"accessTokenURL","placeholder":"AccessToken URL"},{"name":"Redirect URL","model":"redirectURL","placeholder":"本系统地址"},{"name":"Scopes","model":"scopes","placeholder":"Scopes"},{"name":"Resource URL","model":"resourceURL","placeholder":"获取用户信息资源地址"},{"name":"UserIdentifier","model":"userIdentifier","placeholder":"用户唯一标识字段;格式为type:fieldPath(string:username)"},{"name":"是否自动注册","model":"autoRegister","placeholder":"","options":"true,false"}]', '', 'oauth2登录相关配置信息', 'admin,', '2023-07-22 13:58:51', 1, 'admin', '2023-07-22 19:34:37', 1, 'admin', 0, NULL);
|
||||
INSERT INTO `t_sys_config` (name, `key`, params, value, remark, permission, create_time, creator_id, creator, update_time, modifier_id, modifier, is_deleted, delete_time) VALUES('ldap登录配置', 'LdapLogin', '[{"name":"是否启用","model":"enable","placeholder":"是否启用","options":"true,false"},{"name":"host","model":"host","placeholder":"host"},{"name":"port","model":"port","placeholder":"port"},{"name":"bindDN","model":"bindDN","placeholder":"LDAP 服务的管理员账号,如: \\"cn=admin,dc=example,dc=com\\""},{"name":"bindPwd","model":"bindPwd","placeholder":"LDAP 服务的管理员密码"},{"name":"baseDN","model":"baseDN","placeholder":"用户所在的 base DN, 如: \\"ou=users,dc=example,dc=com\\""},{"name":"userFilter","model":"userFilter","placeholder":"过滤用户的方式, 如: \\"(uid=%s)、(&(objectClass=organizationalPerson)(uid=%s))\\""},{"name":"uidMap","model":"uidMap","placeholder":"用户id和 LDAP 字段名之间的映射关系,如: cn"},{"name":"udnMap","model":"udnMap","placeholder":"用户姓名(dispalyName)和 LDAP 字段名之间的映射关系,如: displayName"},{"name":"emailMap","model":"emailMap","placeholder":"用户email和 LDAP 字段名之间的映射关系"},{"name":"skipTLSVerify","model":"skipTLSVerify","placeholder":"客户端是否跳过 TLS 证书验证","options":"true,false"},{"name":"安全协议","model":"securityProtocol","placeholder":"安全协议(为Null不使用安全协议),如: StartTLS, LDAPS","options":"Null,StartTLS,LDAPS"}]', '', 'ldap登录相关配置', 'admin,', '2023-08-25 21:47:20', 1, 'admin', '2023-08-25 22:56:07', 1, 'admin', 0, NULL);
|
||||
INSERT INTO `t_sys_config` (`name`, `key`, `params`, `value`, `remark`, `permission`, `create_time`, `creator_id`, `creator`, `update_time`, `modifier_id`, `modifier`, `is_deleted`, `delete_time`) VALUES('系统全局样式设置', 'SysStyleConfig', '[{"model":"logoIcon","name":"logo图标","placeholder":"系统logo图标(base64编码, 建议svg格式,不超过10k)","required":false},{"model":"title","name":"菜单栏标题","placeholder":"系统菜单栏标题展示","required":false},{"model":"viceTitle","name":"登录页标题","placeholder":"登录页标题展示","required":false},{"model":"useWatermark","name":"是否启用水印","placeholder":"是否启用系统水印","options":"true,false","required":false},{"model":"watermarkContent","name":"水印补充信息","placeholder":"额外水印信息","required":false}]', '{"title":"mayfly-go","viceTitle":"mayfly-go","logoIcon":"","useWatermark":"true","watermarkContent":""}', '系统icon、标题、水印信息等配置', 'all', '2024-01-04 15:17:18', 1, 'admin', '2024-01-05 09:40:44', 1, 'admin', 0, NULL);
|
||||
INSERT INTO t_sys_config ( name, `key`, params, value, remark, permission, create_time, creator_id, creator, update_time, modifier_id, modifier, is_deleted, delete_time) VALUES('机器相关配置', 'MachineConfig', '[{"name":"终端回放存储路径","model":"terminalRecPath","placeholder":"终端回放存储路径"},{"name":"uploadMaxFileSize","model":"uploadMaxFileSize","placeholder":"允许上传的最大文件大小(1MB、2GB等)"},{"model":"termOpSaveDays","name":"终端记录保存时间","placeholder":"终端记录保存时间(单位天)"},{"model":"guacdHost","name":"guacd服务ip","placeholder":"guacd服务ip,默认 127.0.0.1","required":false},{"name":"guacd服务端口","model":"guacdPort","placeholder":"guacd服务端口,默认 4822","required":false},{"model":"guacdFilePath","name":"guacd服务文件存储位置","placeholder":"guacd服务文件存储位置,用于挂载RDP文件夹"},{"name":"guacd服务记录存储位置","model":"guacdRecPath","placeholder":"guacd服务记录存储位置,用于记录rdp操作记录"}]', '{"terminalRecPath":"./rec","uploadMaxFileSize":"1000MB","termOpSaveDays":"30","guacdHost":"","guacdPort":"","guacdFilePath":"./guacd/rdp-file","guacdRecPath":"./guacd/rdp-rec"}', '机器相关配置,如终端回放路径等', 'all', '2023-07-13 16:26:44', 1, 'admin', '2024-04-06 12:25:03', 1, 'admin', 0, NULL);
|
||||
INSERT INTO `t_sys_config` (`id`, `name`, `key`, `params`, `value`, `remark`, `permission`, `create_time`, `creator_id`, `creator`, `update_time`, `modifier_id`, `modifier`, `is_deleted`, `delete_time`) VALUES(10, '数据库备份恢复', 'DbBackupRestore', '[{"model":"backupPath","name":"备份路径","placeholder":"备份文件存储路径"},{"model":"transferPath","name":"迁移路径","placeholder":"数据库迁移文件存储路径"}]', '{"backupPath":"./db/backup","transferPath":"./db/transfer"}', '数据库备份恢复', 'all', '2023-12-29 09:55:26', 1, 'admin', '2024-08-27 15:22:22', 12, 'liuzongyang', 0, NULL);
|
||||
INSERT INTO `t_sys_config` (`name`, `key`, `params`, `value`, `remark`, `permission`, `create_time`, `creator_id`, `creator`, `update_time`, `modifier_id`, `modifier`, `is_deleted`, `delete_time`) VALUES('机器相关配置', 'MachineConfig', '[{"name":"uploadMaxFileSize","model":"uploadMaxFileSize","placeholder":"允许上传的最大文件大小(1MB、2GB等)"},{"model":"termOpSaveDays","name":"终端记录保存时间","placeholder":"终端记录保存时间(单位天)"},{"model":"guacdHost","name":"guacd服务ip","placeholder":"guacd服务ip,默认 127.0.0.1","required":false},{"name":"guacd服务端口","model":"guacdPort","placeholder":"guacd服务端口,默认 4822","required":false},{"model":"guacdFilePath","name":"guacd服务文件存储位置","placeholder":"guacd服务文件存储位置,用于挂载RDP文件夹"}]', '{"uploadMaxFileSize":"1000MB","termOpSaveDays":"30","guacdHost":"","guacdPort":"","guacdFilePath":"./guacd/rdp-file"}', '机器相关配置,如终端回放路径等', 'all', '2023-07-13 16:26:44', 1, 'admin', '2024-10-21 17:02:55', 1, 'admin', 0, NULL);
|
||||
INSERT INTO `t_sys_config` (`name`, `key`, `params`, `value`, `remark`, `permission`, `create_time`, `creator_id`, `creator`, `update_time`, `modifier_id`, `modifier`, `is_deleted`, `delete_time`) VALUES('Mysql可执行文件', 'MysqlBin', '[{"model":"path","name":"路径","placeholder":"可执行文件路径","required":true},{"model":"mysql","name":"mysql","placeholder":"mysql命令路径(空则为 路径/mysql)","required":false},{"model":"mysqldump","name":"mysqldump","placeholder":"mysqldump命令路径(空则为 路径/mysqldump)","required":false},{"model":"mysqlbinlog","name":"mysqlbinlog","placeholder":"mysqlbinlog命令路径(空则为 路径/mysqlbinlog)","required":false}]', '{"mysql":"","mysqldump":"","mysqlbinlog":"","path":"./db/mysql/bin"}', '', 'admin,', '2023-12-29 10:01:33', 1, 'admin', '2023-12-29 13:34:40', 1, 'admin', 0, NULL);
|
||||
INSERT INTO `t_sys_config` (`name`, `key`, `params`, `value`, `remark`, `permission`, `create_time`, `creator_id`, `creator`, `update_time`, `modifier_id`, `modifier`, `is_deleted`, `delete_time`) VALUES('MariaDB可执行文件', 'MariadbBin', '[{"model":"path","name":"路径","placeholder":"可执行文件路径","required":true},{"model":"mysql","name":"mysql","placeholder":"mysql命令路径(空则为 路径/mysql)","required":false},{"model":"mysqldump","name":"mysqldump","placeholder":"mysqldump命令路径(空则为 路径/mysqldump)","required":false},{"model":"mysqlbinlog","name":"mysqlbinlog","placeholder":"mysqlbinlog命令路径(空则为 路径/mysqlbinlog)","required":false}]', '{"mysql":"","mysqldump":"","mysqlbinlog":"","path":"./db/mariadb/bin"}', '', 'admin,', '2023-12-29 10:01:33', 1, 'admin', '2023-12-29 13:34:40', 1, 'admin', 0, NULL);
|
||||
INSERT INTO `t_sys_config` (`name`, `key`, `params`, `value`, `remark`, `permission`, `create_time`, `creator_id`, `creator`, `update_time`, `modifier_id`, `modifier`, `is_deleted`, `delete_time`) VALUES('DBMS配置', 'DbmsConfig', '[{"model":"querySqlSave","name":"记录查询sql","placeholder":"是否记录查询类sql","options":"true,false"},{"model":"maxResultSet","name":"最大结果集","placeholder":"允许sql查询的最大结果集数。注: 0=不限制","options":""},{"model":"sqlExecTl","name":"sql执行时间限制","placeholder":"超过该时间(单位:秒),执行将被取消"}]', '{"querySqlSave":"false","maxResultSet":"0","sqlExecTl":"60"}', 'DBMS相关配置', 'admin,', '2024-03-06 13:30:51', 1, 'admin', '2024-03-06 14:07:16', 1, 'admin', 0, NULL);
|
||||
INSERT INTO `t_sys_config` (`name`, `key`, `params`, `value`, `remark`, `permission`, `create_time`, `creator_id`, `creator`, `update_time`, `modifier_id`, `modifier`, `is_deleted`, `delete_time`) VALUES('文件配置', 'FileConfig', '[{"model":"basePath","name":"基础路径","placeholder":"默认为可执行文件对应目录下./file"}]', '{"basePath":"./file"}', '系统文件相关配置', 'admin,', '2024-10-20 22:30:01', 1, 'admin', '2024-10-21 13:51:17', 1, 'admin', 0, NULL);
|
||||
COMMIT;
|
||||
|
||||
-- ----------------------------
|
||||
@@ -856,7 +855,6 @@ INSERT INTO `t_sys_resource` (`id`, `pid`, `type`, `status`, `name`, `code`, `we
|
||||
INSERT INTO `t_sys_resource` (`id`, `pid`, `type`, `status`, `name`, `code`, `weight`, `meta`, `creator_id`, `creator`, `modifier_id`, `modifier`, `create_time`, `update_time`, `ui_path`, `is_deleted`, `delete_time`) VALUES(1724376022, 1709194669, 2, 1, '文件-删除', 'db:transfer:files:del', 1724376022, 'null', 12, 'liuzongyang', 12, 'liuzongyang', '2024-08-23 09:20:23', '2024-08-23 14:50:21', 'SmLcpu6c/HIURtJJA/', 0, NULL);
|
||||
INSERT INTO `t_sys_resource` (`id`, `pid`, `type`, `status`, `name`, `code`, `weight`, `meta`, `creator_id`, `creator`, `modifier_id`, `modifier`, `create_time`, `update_time`, `ui_path`, `is_deleted`, `delete_time`) VALUES(1724395850, 1709194669, 2, 1, '文件-下载', 'db:transfer:files:down', 1724395850, 'null', 12, 'liuzongyang', 12, 'liuzongyang', '2024-08-23 14:50:51', '2024-08-23 14:50:51', 'SmLcpu6c/FmqK4azt/', 0, NULL);
|
||||
INSERT INTO `t_sys_resource` (`id`, `pid`, `type`, `status`, `name`, `code`, `weight`, `meta`, `creator_id`, `creator`, `modifier_id`, `modifier`, `create_time`, `update_time`, `ui_path`, `is_deleted`, `delete_time`) VALUES(1724398262, 1709194669, 2, 1, '文件', 'db:transfer:files', 1724376021, 'null', 12, 'liuzongyang', 12, 'liuzongyang', '2024-08-23 15:31:02', '2024-08-23 15:31:16', 'SmLcpu6c/btVtrbhk/', 0, NULL);
|
||||
INSERT INTO `t_sys_resource` (`id`, `pid`, `type`, `status`, `name`, `code`, `weight`, `meta`, `creator_id`, `creator`, `modifier_id`, `modifier`, `create_time`, `update_time`, `ui_path`, `is_deleted`, `delete_time`) VALUES(1724817775, 1709194669, 2, 1, '文件-重命名', 'db:transfer:files:rename', 1724376021, 'null', 12, 'liuzongyang', 12, 'liuzongyang', '2024-08-28 12:02:56', '2024-08-28 12:03:01', 'SmLcpu6c/zu4fvnuA/', 0, NULL);
|
||||
INSERT INTO t_sys_resource (id, pid, ui_path, `type`, status, name, code, weight, meta, creator_id, creator, modifier_id, modifier, create_time, update_time, is_deleted, delete_time) VALUES(1714032002, 1713875842, '12sSjal1/UnWIUhW0/0tJwC3Gf/', 2, 1, '命令配置-删除', 'cmdconf:del', 1714032002, 'null', 1, 'admin', 1, 'admin', '2024-04-25 16:00:02', '2024-04-25 16:00:02', 0, NULL);
|
||||
INSERT INTO t_sys_resource (id, pid, ui_path, `type`, status, name, code, weight, meta, creator_id, creator, modifier_id, modifier, create_time, update_time, is_deleted, delete_time) VALUES(1714031981, 1713875842, '12sSjal1/UnWIUhW0/tEzIKecl/', 2, 1, '命令配置-保存', 'cmdconf:save', 1714031981, 'null', 1, 'admin', 1, 'admin', '2024-04-25 15:59:41', '2024-04-25 15:59:41', 0, NULL);
|
||||
INSERT INTO t_sys_resource (id, pid, ui_path, `type`, status, name, code, weight, meta, creator_id, creator, modifier_id, modifier, create_time, update_time, is_deleted, delete_time) VALUES(1713875842, 2, '12sSjal1/UnWIUhW0/', 1, 1, '安全配置', 'security', 1713875842, '{"component":"ops/machine/security/SecurityConfList","icon":"Setting","isKeepAlive":true,"routeName":"SecurityConfList"}', 1, 'admin', 1, 'admin', '2024-04-23 20:37:22', '2024-04-23 20:37:22', 0, NULL);
|
||||
@@ -915,6 +913,25 @@ INSERT INTO `t_sys_role_resource` (role_id,resource_id,creator_id,creator,create
|
||||
(7,1,1,'admin','2021-07-06 15:07:09', 0, NULL);
|
||||
COMMIT;
|
||||
|
||||
DROP TABLE IF EXISTS `t_sys_file`;
|
||||
CREATE TABLE `t_sys_file` (
|
||||
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`file_key` varchar(32) NOT NULL COMMENT 'key',
|
||||
`filename` varchar(255) NOT NULL COMMENT '文件名',
|
||||
`path` varchar(555) NOT NULL COMMENT '文件路径',
|
||||
`size` int NULL DEFAULT NULL COMMENT '文件大小',
|
||||
`creator_id` bigint NULL DEFAULT NULL,
|
||||
`creator` varchar(32) NULL DEFAULT NULL,
|
||||
`modifier_id` bigint NULL DEFAULT NULL,
|
||||
`modifier` varchar(255) NULL DEFAULT NULL,
|
||||
`create_time` datetime NULL DEFAULT NULL,
|
||||
`update_time` datetime NULL DEFAULT NULL,
|
||||
`is_deleted` tinyint NOT NULL DEFAULT 0,
|
||||
`delete_time` datetime NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
KEY `idx_file_key` (`file_key`) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COMMENT = '系统文件表';
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for t_tag_tree
|
||||
-- ----------------------------
|
||||
|
||||
@@ -7,12 +7,12 @@ ALTER TABLE `t_db_data_sync_task`
|
||||
INSERT INTO `t_sys_resource` (`id`, `pid`, `type`, `status`, `name`, `code`, `weight`, `meta`, `creator_id`, `creator`, `modifier_id`, `modifier`, `create_time`, `update_time`, `ui_path`, `is_deleted`, `delete_time`) VALUES(1724376022, 1709194669, 2, 1, '文件-删除', 'db:transfer:files:del', 1724376022, 'null', 12, 'liuzongyang', 12, 'liuzongyang', '2024-08-23 09:20:23', '2024-08-23 14:50:21', 'SmLcpu6c/HIURtJJA/', 0, NULL);
|
||||
INSERT INTO `t_sys_resource` (`id`, `pid`, `type`, `status`, `name`, `code`, `weight`, `meta`, `creator_id`, `creator`, `modifier_id`, `modifier`, `create_time`, `update_time`, `ui_path`, `is_deleted`, `delete_time`) VALUES(1724395850, 1709194669, 2, 1, '文件-下载', 'db:transfer:files:down', 1724395850, 'null', 12, 'liuzongyang', 12, 'liuzongyang', '2024-08-23 14:50:51', '2024-08-23 14:50:51', 'SmLcpu6c/FmqK4azt/', 0, NULL);
|
||||
INSERT INTO `t_sys_resource` (`id`, `pid`, `type`, `status`, `name`, `code`, `weight`, `meta`, `creator_id`, `creator`, `modifier_id`, `modifier`, `create_time`, `update_time`, `ui_path`, `is_deleted`, `delete_time`) VALUES(1724398262, 1709194669, 2, 1, '文件', 'db:transfer:files', 1724376021, 'null', 12, 'liuzongyang', 12, 'liuzongyang', '2024-08-23 15:31:02', '2024-08-23 15:31:16', 'SmLcpu6c/btVtrbhk/', 0, NULL);
|
||||
INSERT INTO `t_sys_resource` (`id`, `pid`, `type`, `status`, `name`, `code`, `weight`, `meta`, `creator_id`, `creator`, `modifier_id`, `modifier`, `create_time`, `update_time`, `ui_path`, `is_deleted`, `delete_time`) VALUES(1724817775, 1709194669, 2, 1, '文件-重命名', 'db:transfer:files:rename', 1724376021, 'null', 12, 'liuzongyang', 12, 'liuzongyang', '2024-08-28 12:02:56', '2024-08-28 12:03:01', 'SmLcpu6c/zu4fvnuA/', 0, NULL);
|
||||
INSERT INTO `t_sys_resource` (`id`, `pid`, `type`, `status`, `name`, `code`, `weight`, `meta`, `creator_id`, `creator`, `modifier_id`, `modifier`, `create_time`, `update_time`, `ui_path`, `is_deleted`, `delete_time`) VALUES(1724998419, 1709194669, 2, 1, '文件-执行', 'db:transfer:files:run', 1724998419, 'null', 12, 'liuzongyang', 12, 'liuzongyang', '2024-08-30 14:13:39', '2024-08-30 14:13:39', 'SmLcpu6c/qINungml/', 0, NULL);
|
||||
|
||||
-- 新增数据库迁移相关的系统配置
|
||||
DELETE FROM `t_sys_config` WHERE `key` = 'DbBackupRestore';
|
||||
INSERT INTO `t_sys_config` (`id`, `name`, `key`, `params`, `value`, `remark`, `permission`, `create_time`, `creator_id`, `creator`, `update_time`, `modifier_id`, `modifier`, `is_deleted`, `delete_time`) VALUES(10, '数据库备份恢复', 'DbBackupRestore', '[{"model":"backupPath","name":"备份路径","placeholder":"备份文件存储路径"},{"model":"transferPath","name":"迁移路径","placeholder":"数据库迁移文件存储路径"}]', '{"backupPath":"./db/backup","transferPath":"./db/transfer"}', '数据库备份恢复', 'all', '2023-12-29 09:55:26', 1, 'admin', '2024-08-27 15:22:22', 12, 'liuzongyang', 0, NULL);
|
||||
UPDATE `t_sys_config` SET param = '[{"name":"uploadMaxFileSize","model":"uploadMaxFileSize","placeholder":"允许上传的最大文件大小(1MB、2GB等)"},{"model":"termOpSaveDays","name":"终端记录保存时间","placeholder":"终端记录保存时间(单位天)"},{"model":"guacdHost","name":"guacd服务ip","placeholder":"guacd服务ip,默认 127.0.0.1","required":false},{"name":"guacd服务端口","model":"guacdPort","placeholder":"guacd服务端口,默认 4822","required":false},{"model":"guacdFilePath","name":"guacd服务文件存储位置","placeholder":"guacd服务文件存储位置,用于挂载RDP文件夹"}]' WHERE `key`='MachineConfig';
|
||||
INSERT INTO `t_sys_config` (`name`, `key`, `params`, `value`, `remark`, `permission`, `create_time`, `creator_id`, `creator`, `update_time`, `modifier_id`, `modifier`, `is_deleted`, `delete_time`) VALUES('文件配置', 'FileConfig', '[{"model":"basePath","name":"基础路径","placeholder":"默认为可执行文件对应目录下./file"}]', '{"basePath":"./file"}', '系统文件相关配置', 'admin,', '2024-10-20 22:30:01', 1, 'admin', '2024-10-21 13:51:17', 1, 'admin', 0, NULL);
|
||||
|
||||
-- 数据库迁移到文件
|
||||
ALTER TABLE `t_db_transfer_task`
|
||||
@@ -37,11 +37,28 @@ CREATE TABLE `t_db_transfer_files` (
|
||||
`task_id` bigint COMMENT '迁移任务ID',
|
||||
`log_id` bigint COMMENT '日志ID',
|
||||
`file_db_type` varchar(200) COMMENT 'sql文件数据库类型',
|
||||
`file_name` varchar(200) COMMENT '显式文件名 默认: 年月日时分秒.zip',
|
||||
`file_uuid` varchar(50) COMMENT '文件真实uuid,拼接后可以下载',
|
||||
`file_key` varchar(50) COMMENT '文件',
|
||||
PRIMARY KEY (id)
|
||||
) COMMENT '数据库迁移文件管理';
|
||||
|
||||
|
||||
ALTER TABLE `t_flow_procdef`
|
||||
ADD COLUMN `condition` text NULL comment '触发审批的条件(计算结果返回1则需要启用该流程)';
|
||||
ADD COLUMN `condition` text NULL comment '触发审批的条件(计算结果返回1则需要启用该流程)';
|
||||
|
||||
CREATE TABLE `t_sys_file` (
|
||||
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`file_key` varchar(32) NOT NULL COMMENT 'key',
|
||||
`filename` varchar(255) NOT NULL COMMENT '文件名',
|
||||
`path` varchar(255) NOT NULL COMMENT '文件路径',
|
||||
`size` int NULL DEFAULT NULL COMMENT '文件大小',
|
||||
`creator_id` bigint NULL DEFAULT NULL,
|
||||
`creator` varchar(32) NULL DEFAULT NULL,
|
||||
`modifier_id` bigint NULL DEFAULT NULL,
|
||||
`modifier` varchar(255) NULL DEFAULT NULL,
|
||||
`create_time` datetime NULL DEFAULT NULL,
|
||||
`update_time` datetime NULL DEFAULT NULL,
|
||||
`is_deleted` tinyint NOT NULL DEFAULT 0,
|
||||
`delete_time` datetime NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
KEY `idx_file_key` (`file_key`) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COMMENT = '系统文件表';
|
||||
Reference in New Issue
Block a user