diff --git a/README.md b/README.md index 7f8cc769..4a9622ef 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ ### 介绍 -web 版 **linux(终端[终端回放] 文件 脚本 进程 计划任务)、数据库(mysql postgres 达梦)、redis(单机 哨兵 集群)、mongo 统一管理操作平台** +web 版 **linux(终端[终端回放] 文件 脚本 进程 计划任务)、数据库(mysql postgres oracle 达梦 高斯)、redis(单机 哨兵 集群)、mongo 统一管理操作平台** ### 开发语言与主要框架 diff --git a/mayfly_go_web/package.json b/mayfly_go_web/package.json index cd836a2e..13949b14 100644 --- a/mayfly_go_web/package.json +++ b/mayfly_go_web/package.json @@ -10,7 +10,7 @@ }, "dependencies": { "@element-plus/icons-vue": "^2.3.1", - "@vueuse/core": "^10.7.0", + "@vueuse/core": "^10.7.2", "asciinema-player": "^3.6.3", "axios": "^1.6.2", "clipboard": "^2.0.11", @@ -31,9 +31,9 @@ "screenfull": "^6.0.2", "sortablejs": "^1.15.0", "splitpanes": "^3.1.5", - "sql-formatter": "^14.0.0", + "sql-formatter": "^15.0.2", "uuid": "^9.0.1", - "vue": "^3.4.12", + "vue": "^3.4.13", "vue-router": "^4.2.5", "xterm": "^5.3.0", "xterm-addon-fit": "^0.8.0", @@ -48,7 +48,7 @@ "@typescript-eslint/eslint-plugin": "^6.7.4", "@typescript-eslint/parser": "^6.7.4", "@vitejs/plugin-vue": "^5.0.3", - "@vue/compiler-sfc": "^3.4.12", + "@vue/compiler-sfc": "^3.4.13", "dotenv": "^16.3.1", "eslint": "^8.35.0", "eslint-plugin-vue": "^9.19.2", diff --git a/mayfly_go_web/src/views/ops/db/component/sqleditor/SqlExecBox.ts b/mayfly_go_web/src/views/ops/db/component/sqleditor/SqlExecBox.ts index 9cc5e8f8..b026a8c1 100644 --- a/mayfly_go_web/src/views/ops/db/component/sqleditor/SqlExecBox.ts +++ b/mayfly_go_web/src/views/ops/db/component/sqleditor/SqlExecBox.ts @@ -1,12 +1,11 @@ import { h, render, VNode } from 'vue'; import SqlExecDialog from './SqlExecDialog.vue'; -import { SqlLanguage } from 'sql-formatter/lib/src/sqlFormatter'; export type SqlExecProps = { sql: string; dbId: number; db: string; - dbType?: SqlLanguage; + dbType?: string; runSuccessCallback?: Function; cancelCallback?: Function; }; diff --git a/mayfly_go_web/src/views/ops/db/component/table/DbTableDataOp.vue b/mayfly_go_web/src/views/ops/db/component/table/DbTableDataOp.vue index 7bb1f2bb..d58b0dff 100644 --- a/mayfly_go_web/src/views/ops/db/component/table/DbTableDataOp.vue +++ b/mayfly_go_web/src/views/ops/db/component/table/DbTableDataOp.vue @@ -329,11 +329,6 @@ const getNowDbInst = () => { onMounted(async () => { console.log('in table data mounted'); state.tableHeight = props.tableHeight; - const columns = await getNowDbInst().loadColumns(props.dbName, props.tableName); - columns.forEach((x: any) => { - x.show = true; - }); - state.columns = columns; await onRefresh(); state.dbDialect = getDbDialect(getNowDbInst().type); @@ -367,6 +362,14 @@ const selectData = async () => { const db = props.dbName; const table = props.tableName; try { + if (state.columns.length == 0) { + const columns = await getNowDbInst().loadColumns(props.dbName, props.tableName); + columns.forEach((x: any) => { + x.show = true; + }); + state.columns = columns; + } + const countRes = await dbInst.runSql(db, dbInst.getDefaultCountSql(table, state.condition)); state.count = countRes.res[0].count || countRes.res[0].COUNT || 0; let sql = dbInst.getDefaultSelectSql(table, state.condition, state.orderBy, state.pageNum, state.pageSize); diff --git a/mayfly_go_web/src/views/ops/db/dialect/index.ts b/mayfly_go_web/src/views/ops/db/dialect/index.ts index d0f1c948..8295b2cd 100644 --- a/mayfly_go_web/src/views/ops/db/dialect/index.ts +++ b/mayfly_go_web/src/views/ops/db/dialect/index.ts @@ -1,7 +1,6 @@ import { MysqlDialect } from './mysql_dialect'; import { PostgresqlDialect } from './postgres_dialect'; import { DMDialect } from '@/views/ops/db/dialect/dm_dialect'; -import { SqlLanguage } from 'sql-formatter/lib/src/sqlFormatter'; import { OracleDialect } from '@/views/ops/db/dialect/oracle_dialect'; import { MariadbDialect } from '@/views/ops/db/dialect/mariadb_dialect'; @@ -92,7 +91,7 @@ export interface DialectInfo { /** * 格式化sql的方言 */ - formatSqlDialect: SqlLanguage; + formatSqlDialect: string; /** * 列字段类型 diff --git a/server/internal/auth/config/config.go b/server/internal/auth/config/config.go index dbfc066a..69de23f8 100644 --- a/server/internal/auth/config/config.go +++ b/server/internal/auth/config/config.go @@ -26,8 +26,8 @@ func GetAccountLoginSecurity() *AccountLoginSecurity { als := new(AccountLoginSecurity) als.UseCaptcha = c.ConvBool(jm["useCaptcha"], true) als.UseOtp = c.ConvBool(jm["useOtp"], false) - als.LoginFailCount = c.ConvInt(jm["loginFailCount"], 5) - als.LoginFailMin = c.ConvInt(jm["loginFailMin"], 10) + als.LoginFailCount = stringx.ConvInt(jm["loginFailCount"], 5) + als.LoginFailMin = stringx.ConvInt(jm["loginFailMin"], 10) otpIssuer := jm["otpIssuer"] if otpIssuer == "" { otpIssuer = "mayfly-go" diff --git a/server/internal/machine/application/machine_term_op.go b/server/internal/machine/application/machine_term_op.go index bf8d0a37..a8497cf0 100644 --- a/server/internal/machine/application/machine_term_op.go +++ b/server/internal/machine/application/machine_term_op.go @@ -10,7 +10,9 @@ import ( "mayfly-go/pkg/base" "mayfly-go/pkg/contextx" "mayfly-go/pkg/errorx" + "mayfly-go/pkg/logx" "mayfly-go/pkg/model" + "mayfly-go/pkg/scheduler" "mayfly-go/pkg/utils/stringx" "os" "path" @@ -26,6 +28,9 @@ type MachineTermOp interface { TermConn(ctx context.Context, cli *mcm.Cli, wsConn *websocket.Conn, rows, cols int) error GetPageList(condition *entity.MachineTermOp, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) + + // 定时删除终端文件回放记录 + TimerDeleteTermOp() } func newMachineTermOpApp(machineTermOpRepo repository.MachineTermOp) MachineTermOp { @@ -38,7 +43,7 @@ type machineTermOpAppImpl struct { base.AppImpl[*entity.MachineTermOp, repository.MachineTermOp] } -func (a *machineTermOpAppImpl) TermConn(ctx context.Context, cli *mcm.Cli, wsConn *websocket.Conn, rows, cols int) error { +func (m *machineTermOpAppImpl) TermConn(ctx context.Context, cli *mcm.Cli, wsConn *websocket.Conn, rows, cols int) error { var recorder *mcm.Recorder var termOpRecord *entity.MachineTermOp @@ -83,11 +88,41 @@ func (a *machineTermOpAppImpl) TermConn(ctx context.Context, cli *mcm.Cli, wsCon if termOpRecord != nil { now := time.Now() termOpRecord.EndTime = &now - return a.Insert(ctx, termOpRecord) + return m.Insert(ctx, termOpRecord) } return nil } -func (a *machineTermOpAppImpl) GetPageList(condition *entity.MachineTermOp, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) { - return a.GetRepo().GetPageList(condition, pageParam, toEntity) +func (m *machineTermOpAppImpl) GetPageList(condition *entity.MachineTermOp, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) { + return m.GetRepo().GetPageList(condition, pageParam, toEntity) +} + +func (m *machineTermOpAppImpl) TimerDeleteTermOp() { + logx.Debug("开始定时删除机器终端回放记录...") + scheduler.AddFun("@every 60m", func() { + startDate := time.Now().AddDate(0, 0, -config.GetMachine().TermOpSaveDays) + cond := &entity.MachineTermOpQuery{ + StartCreateTime: &startDate, + } + termOps, err := m.GetRepo().SelectByQuery(cond) + if err != nil { + return + } + + basePath := config.GetMachine().TerminalRecPath + for _, termOp := range termOps { + if err := m.DeleteTermOp(basePath, termOp); err != nil { + logx.Warnf("删除终端操作记录失败: %s", err.Error()) + } + } + }) +} + +// 删除终端记录即对应文件 +func (m *machineTermOpAppImpl) DeleteTermOp(basePath string, termOp *entity.MachineTermOp) error { + if err := m.DeleteById(context.Background(), termOp.Id); err != nil { + return err + } + + return os.Remove(path.Join(basePath, termOp.RecordFilePath)) } diff --git a/server/internal/machine/config/config.go b/server/internal/machine/config/config.go index c87276db..451a4879 100644 --- a/server/internal/machine/config/config.go +++ b/server/internal/machine/config/config.go @@ -4,6 +4,7 @@ import ( sysapp "mayfly-go/internal/sys/application" "mayfly-go/pkg/logx" "mayfly-go/pkg/utils/bytex" + "mayfly-go/pkg/utils/stringx" ) const ( @@ -13,6 +14,7 @@ const ( type Machine struct { TerminalRecPath string // 终端操作记录存储位置 UploadMaxFileSize int64 // 允许上传的最大文件size + TermOpSaveDays int // 终端记录保存天数 } // 获取机器相关配置 @@ -39,5 +41,6 @@ func GetMachine() *Machine { } } mc.UploadMaxFileSize = uploadMaxFileSize + mc.TermOpSaveDays = stringx.ConvInt(jm["termOpSaveDays"], 30) return mc } diff --git a/server/internal/machine/domain/entity/query.go b/server/internal/machine/domain/entity/query.go index ed288c84..4cd5bff5 100644 --- a/server/internal/machine/domain/entity/query.go +++ b/server/internal/machine/domain/entity/query.go @@ -1,5 +1,7 @@ package entity +import "time" + type MachineQuery struct { Ids string `json:"ids" form:"ids"` Name string `json:"name" form:"name"` @@ -15,3 +17,7 @@ type AuthCertQuery struct { Name string `json:"name" form:"name"` AuthMethod string `json:"authMethod" form:"authMethod"` // IP地址 } + +type MachineTermOpQuery struct { + StartCreateTime *time.Time +} diff --git a/server/internal/machine/domain/repository/machine_term_op.go b/server/internal/machine/domain/repository/machine_term_op.go index 5a12ee64..698e295f 100644 --- a/server/internal/machine/domain/repository/machine_term_op.go +++ b/server/internal/machine/domain/repository/machine_term_op.go @@ -11,4 +11,7 @@ type MachineTermOp interface { // 分页获取机器终端执行记录列表 GetPageList(condition *entity.MachineTermOp, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) + + // 根据条件获取记录列表 + SelectByQuery(cond *entity.MachineTermOpQuery) ([]*entity.MachineTermOp, error) } diff --git a/server/internal/machine/infrastructure/persistence/machine_term_op.go b/server/internal/machine/infrastructure/persistence/machine_term_op.go index fe168d24..a08a6942 100644 --- a/server/internal/machine/infrastructure/persistence/machine_term_op.go +++ b/server/internal/machine/infrastructure/persistence/machine_term_op.go @@ -20,3 +20,11 @@ func (m *machineTermOpRepoImpl) GetPageList(condition *entity.MachineTermOp, pag qd := gormx.NewQuery(condition).WithCondModel(condition).WithOrderBy(orderBy...) return gormx.PageQuery(qd, pageParam, toEntity) } + +// 根据条件获取记录列表 +func (m *machineTermOpRepoImpl) SelectByQuery(cond *entity.MachineTermOpQuery) ([]*entity.MachineTermOp, error) { + qd := gormx.NewQuery(m.GetModel()).Le("create_time", cond.StartCreateTime) + var res []*entity.MachineTermOp + err := gormx.ListByQueryCond(qd, &res) + return res, err +} diff --git a/server/internal/machine/init/init.go b/server/internal/machine/init/init.go index 8ff59add..01497dc6 100644 --- a/server/internal/machine/init/init.go +++ b/server/internal/machine/init/init.go @@ -14,6 +14,8 @@ func Init() { application.GetMachineApp().TimerUpdateStats() + application.GetMachineTermOpApp().TimerDeleteTermOp() + global.EventBus.Subscribe(consts.DeleteMachineEventTopic, "machineFile", func(ctx context.Context, event *eventbus.Event) error { me := event.Val.(*entity.Machine) return application.GetMachineFileApp().DeleteByCond(ctx, &entity.MachineFile{MachineId: me.Id}) diff --git a/server/internal/sys/domain/entity/config.go b/server/internal/sys/domain/entity/config.go index f8d232e4..532b54fc 100644 --- a/server/internal/sys/domain/entity/config.go +++ b/server/internal/sys/domain/entity/config.go @@ -3,7 +3,7 @@ package entity import ( "encoding/json" "mayfly-go/pkg/model" - "strconv" + "mayfly-go/pkg/utils/stringx" ) const ( @@ -49,7 +49,7 @@ func (c *Config) IntValue(defaultValue int) int { if c.Id == 0 { return defaultValue } - return c.ConvInt(c.Value, defaultValue) + return stringx.ConvInt(c.Value, defaultValue) } // 转换配置中的值为bool类型(默认"1"或"true"为true,其他为false) @@ -59,15 +59,3 @@ func (c *Config) ConvBool(value string, defaultValue bool) bool { } return value == "1" || value == "true" } - -// 转换配置值中的值为int -func (c *Config) ConvInt(value string, defaultValue int) int { - if value == "" { - return defaultValue - } - if intV, err := strconv.Atoi(value); err != nil { - return defaultValue - } else { - return intV - } -} diff --git a/server/pkg/utils/stringx/conv.go b/server/pkg/utils/stringx/conv.go new file mode 100644 index 00000000..1821b1f4 --- /dev/null +++ b/server/pkg/utils/stringx/conv.go @@ -0,0 +1,17 @@ +package stringx + +import ( + "strconv" +) + +// 将字符串值转为int值, 若value为空或者转换失败,则返回默认值 +func ConvInt(value string, defaultValue int) int { + if value == "" { + return defaultValue + } + if intV, err := strconv.Atoi(value); err != nil { + return defaultValue + } else { + return intV + } +} diff --git a/server/resources/script/sql/mayfly-go.sql b/server/resources/script/sql/mayfly-go.sql index f7cc4d9b..9546c858 100644 --- a/server/resources/script/sql/mayfly-go.sql +++ b/server/resources/script/sql/mayfly-go.sql @@ -622,7 +622,7 @@ INSERT INTO `t_sys_config` (name, `key`, params, value, remark, permission, crea 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, create_time, creator_id, creator, update_time, modifier_id, modifier)VALUES ('数据库查询最大结果集', 'DbQueryMaxCount', '[]', '200', '允许sql查询的最大结果集数。注: 0=不限制', '2023-02-11 14:29:03', 1, 'admin', '2023-02-11 14:40:56', 1, 'admin'); INSERT INTO `t_sys_config` (name, `key`, params, value, remark, create_time, creator_id, creator, update_time, modifier_id, modifier)VALUES ('数据库是否记录查询SQL', 'DbSaveQuerySQL', '[]', '0', '1: 记录、0:不记录', '2023-02-11 16:07:14', 1, 'admin', '2023-02-11 16:44:17', 1, 'admin'); -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等)"}]', '{"terminalRecPath":"./rec","uploadMaxFileSize":"1GB"}', '机器相关配置,如终端回放路径等', 'admin,', '2023-07-13 16:26:44', 1, 'admin', '2023-11-09 22:01:31', 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":"终端记录保存时间(单位天)"}]', '{"terminalRecPath":"./rec","uploadMaxFileSize":"100MB"}', '机器相关配置,如终端回放路径等', 'all', '2023-07-13 16:26:44', 1, 'admin', '2024-01-15 16:30:22', 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('数据库备份恢复', 'DbBackupRestore', '[{"model":"backupPath","name":"备份路径","placeholder":"备份文件存储路径"}]', '{"backupPath":"./db/backup"}', '', 'admin,', '2023-12-29 09:55:26', 1, 'admin', '2023-12-29 15:45:24', 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);