mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-02 15:30:25 +08:00
feat: 机器定时删除终端操作记录
This commit is contained in:
@@ -22,7 +22,7 @@
|
||||
|
||||
### 介绍
|
||||
|
||||
web 版 **linux(终端[终端回放] 文件 脚本 进程 计划任务)、数据库(mysql postgres 达梦)、redis(单机 哨兵 集群)、mongo 统一管理操作平台**
|
||||
web 版 **linux(终端[终端回放] 文件 脚本 进程 计划任务)、数据库(mysql postgres oracle 达梦 高斯)、redis(单机 哨兵 集群)、mongo 统一管理操作平台**
|
||||
|
||||
### 开发语言与主要框架
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
* 列字段类型
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
17
server/pkg/utils/stringx/conv.go
Normal file
17
server/pkg/utils/stringx/conv.go
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user