Skip to content

Commit 4f39ced

Browse files
authored
Merge pull request #574 from actiontech/feat-operation-record-ce
Feat operation record ce
2 parents 96a49bc + ab9c1bb commit 4f39ced

File tree

10 files changed

+252
-26
lines changed

10 files changed

+252
-26
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package middleware
2+
3+
import (
4+
"strings"
5+
6+
"github.com/actiontech/dms/pkg/dms-common/i18nPkg"
7+
"github.com/labstack/echo/v4"
8+
)
9+
10+
type ApiInterfaceInfo struct {
11+
RouterPath string
12+
Method string
13+
OperationType string
14+
OperationAction string
15+
GetProjectAndContentFunc func(c echo.Context, dms interface{}) (projectName string, content i18nPkg.I18nStr, err error)
16+
}
17+
18+
var ApiInterfaceInfoList []ApiInterfaceInfo
19+
20+
func pathMatch(pattern, path string) bool {
21+
ps := strings.Split(strings.Trim(pattern, "/"), "/")
22+
pathSegs := strings.Split(strings.Trim(path, "/"), "/")
23+
if len(ps) != len(pathSegs) {
24+
return false
25+
}
26+
for i := range ps {
27+
if len(ps[i]) > 0 && ps[i][0] == ':' {
28+
continue
29+
}
30+
if ps[i] != pathSegs[i] {
31+
return false
32+
}
33+
}
34+
return true
35+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//go:build !enterprise
2+
3+
package middleware
4+
5+
import (
6+
"github.com/actiontech/dms/internal/dms/service"
7+
"github.com/labstack/echo/v4"
8+
)
9+
10+
func OperationRecordMiddleware(_ *service.DMSService) echo.MiddlewareFunc {
11+
return func(next echo.HandlerFunc) echo.HandlerFunc {
12+
return next
13+
}
14+
}

internal/apiserver/service/router.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,8 @@ func (s *APIServer) installMiddleware() error {
466466
i18nPkg.GetLangByAcceptLanguage,
467467
))
468468

469+
s.echo.Use(dmsMiddleware.OperationRecordMiddleware(s.DMSController.DMS))
470+
469471
return nil
470472
}
471473

internal/dms/biz/cron_task.go

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,28 +6,27 @@ import (
66
)
77

88
type CronTaskUsecase struct {
9-
log *utilLog.Helper
10-
cronTask *cronTask
11-
workflowUsecase *DataExportWorkflowUsecase
12-
cbOperationLogUsecase *CbOperationLogUsecase
13-
licenseUsecase *LicenseUsecase
14-
oauth2SessionUsecase *OAuth2SessionUsecase
9+
log *utilLog.Helper
10+
cronTask *cronTask
11+
workflowUsecase *DataExportWorkflowUsecase
12+
cbOperationLogUsecase *CbOperationLogUsecase
13+
operationRecordUsecase *OperationRecordUsecase
14+
licenseUsecase *LicenseUsecase
15+
oauth2SessionUsecase *OAuth2SessionUsecase
1516
}
1617
type cronTask struct {
1718
cron *cron.Cron
1819
}
1920

20-
func NewCronTaskUsecase(log utilLog.Logger, wu *DataExportWorkflowUsecase, cu *CbOperationLogUsecase, os *OAuth2SessionUsecase) *CronTaskUsecase {
21+
func NewCronTaskUsecase(log utilLog.Logger, wu *DataExportWorkflowUsecase, cu *CbOperationLogUsecase, oru *OperationRecordUsecase, os *OAuth2SessionUsecase) *CronTaskUsecase {
2122
ctu := &CronTaskUsecase{
22-
log: utilLog.NewHelper(log, utilLog.WithMessageKey("biz.cronTask")),
23-
cronTask: &cronTask{
24-
cron: cron.New(),
25-
},
26-
workflowUsecase: wu,
27-
cbOperationLogUsecase: cu,
28-
oauth2SessionUsecase: os,
23+
log: utilLog.NewHelper(log, utilLog.WithMessageKey("biz.cronTask")),
24+
cronTask: &cronTask{cron: cron.New()},
25+
workflowUsecase: wu,
26+
cbOperationLogUsecase: cu,
27+
operationRecordUsecase: oru,
28+
oauth2SessionUsecase: os,
2929
}
30-
3130
return ctu
3231
}
3332

@@ -48,6 +47,10 @@ func (ctu *CronTaskUsecase) InitialTask() error {
4847
return err
4948
}
5049

50+
if _, err := ctu.cronTask.cron.AddFunc("@hourly", ctu.operationRecordUsecase.DoClean); err != nil {
51+
return err
52+
}
53+
5154
if _, err := ctu.cronTask.cron.AddFunc("@hourly", ctu.oauth2SessionUsecase.DeleteExpiredSessions); err != nil {
5255
return err
5356
}

internal/dms/biz/operation_record.go

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package biz
22

33
import (
44
"context"
5+
"strconv"
56
"time"
67

78
"github.com/actiontech/dms/pkg/dms-common/i18nPkg"
@@ -12,6 +13,7 @@ type OperationRecordRepo interface {
1213
SaveOperationRecord(ctx context.Context, record *OperationRecord) error
1314
ListOperationRecords(ctx context.Context, opt *ListOperationRecordOption) ([]*OperationRecord, uint64, error)
1415
ExportOperationRecords(ctx context.Context, opt *ListOperationRecordOption) ([]*OperationRecord, error)
16+
CleanOperationRecordOpTimeBefore(ctx context.Context, t time.Time) (rowsAffected int64, err error)
1517
}
1618

1719
type OperationRecord struct {
@@ -37,18 +39,68 @@ type ListOperationRecordOption struct {
3739
FilterOperateTypeName string
3840
FilterOperateAction string
3941
// 权限相关字段
40-
CanViewGlobal bool // 是否有全局查看权限(admin/sys/全局权限)
41-
AccessibleProjectNames []string // 可访问的项目名称列表(项目管理员)
42+
CanViewGlobal bool // 是否有全局查看权限(admin/sys/全局权限)
43+
AccessibleProjectNames []string // 可访问的项目名称列表(项目管理员)
4244
}
4345

4446
type OperationRecordUsecase struct {
45-
repo OperationRecordRepo
46-
log *utilLog.Helper
47+
repo OperationRecordRepo
48+
systemVariableUsecase *SystemVariableUsecase
49+
log *utilLog.Helper
4750
}
4851

49-
func NewOperationRecordUsecase(logger utilLog.Logger, repo OperationRecordRepo) *OperationRecordUsecase {
52+
func NewOperationRecordUsecase(logger utilLog.Logger, repo OperationRecordRepo, svu *SystemVariableUsecase) *OperationRecordUsecase {
5053
return &OperationRecordUsecase{
51-
repo: repo,
52-
log: utilLog.NewHelper(logger, utilLog.WithMessageKey("biz.operationRecord")),
54+
repo: repo,
55+
systemVariableUsecase: svu,
56+
log: utilLog.NewHelper(logger, utilLog.WithMessageKey("biz.operationRecord")),
5357
}
5458
}
59+
60+
func (u *OperationRecordUsecase) DoClean() {
61+
if u.systemVariableUsecase == nil {
62+
u.log.Errorf("failed to clean operation record when get systemVariableUsecase")
63+
return
64+
}
65+
66+
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
67+
defer cancel()
68+
69+
variables, err := u.systemVariableUsecase.GetSystemVariables(ctx)
70+
if err != nil {
71+
u.log.Errorf("failed to clean operation record when get expired duration: %v", err)
72+
return
73+
}
74+
75+
operationRecordExpiredHoursVar, ok := variables[SystemVariableOperationRecordExpiredHours]
76+
if !ok {
77+
u.log.Debugf("system variable %s not found, using default value", SystemVariableOperationRecordExpiredHours)
78+
operationRecordExpiredHoursVar = SystemVariable{
79+
Key: SystemVariableOperationRecordExpiredHours,
80+
Value: strconv.Itoa(DefaultOperationRecordExpiredHours),
81+
}
82+
}
83+
84+
operationRecordExpiredHours, err := strconv.Atoi(operationRecordExpiredHoursVar.Value)
85+
if err != nil {
86+
u.log.Errorf("failed to parse operation_record_expired_hours value: %v", err)
87+
return
88+
}
89+
90+
if operationRecordExpiredHours <= 0 {
91+
u.log.Errorf("got OperationRecordExpiredHours: %d", operationRecordExpiredHours)
92+
return
93+
}
94+
95+
cleanTime := time.Now().Add(time.Duration(-operationRecordExpiredHours) * time.Hour)
96+
rowsAffected, err := u.repo.CleanOperationRecordOpTimeBefore(ctx, cleanTime)
97+
if err != nil {
98+
u.log.Errorf("failed to clean operation record: %v", err)
99+
return
100+
}
101+
u.log.Infof("OperationRecord regular cleaned rows: %d operation time before: %s", rowsAffected, cleanTime.Format("2006-01-02 15:04:05"))
102+
}
103+
104+
func (u *OperationRecordUsecase) GetLog() *utilLog.Helper {
105+
return u.log
106+
}

internal/dms/service/service.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ func NewAndInitDMSService(logger utilLog.Logger, opts *conf.DMSOptions) (*DMSSer
144144
swaggerUseCase := biz.NewSwaggerUseCase(logger, dmsProxyUsecase)
145145
systemVariableUsecase := biz.NewSystemVariableUsecase(logger, storage.NewSystemVariableRepo(logger, st))
146146
operationRecordRepo := storage.NewOperationRecordRepo(logger, st)
147-
operationRecordUsecase := biz.NewOperationRecordUsecase(logger, operationRecordRepo)
147+
operationRecordUsecase := biz.NewOperationRecordUsecase(logger, operationRecordRepo, systemVariableUsecase)
148148
cbOperationRepo := storage.NewCbOperationLogRepo(logger, st)
149149
CbOperationLogUsecase := biz.NewCbOperationLogUsecase(logger, cbOperationRepo, opPermissionVerifyUsecase, dmsProxyTargetRepo, systemVariableUsecase)
150150
workflowRepo := storage.NewWorkflowRepo(logger, st)
@@ -156,7 +156,7 @@ func NewAndInitDMSService(logger utilLog.Logger, opts *conf.DMSOptions) (*DMSSer
156156
}
157157
dataMaskingUsecase := biz.NewMaskingUsecase(logger, dataMasking)
158158

159-
cronTask := biz.NewCronTaskUsecase(logger, DataExportWorkflowUsecase, CbOperationLogUsecase, oauth2SessionUsecase)
159+
cronTask := biz.NewCronTaskUsecase(logger, DataExportWorkflowUsecase, CbOperationLogUsecase, operationRecordUsecase, oauth2SessionUsecase)
160160
err = cronTask.InitialTask()
161161
if err != nil {
162162
return nil, fmt.Errorf("failed to new cron task: %v", err)

internal/dms/storage/operation_record.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package storage
33
import (
44
"context"
55
"fmt"
6+
"time"
67

78
"github.com/actiontech/dms/internal/dms/biz"
89
"github.com/actiontech/dms/internal/dms/storage/model"
@@ -136,6 +137,18 @@ func (d *operationRecordRepo) ExportOperationRecords(ctx context.Context, opt *b
136137
return ret, nil
137138
}
138139

140+
func (d *operationRecordRepo) CleanOperationRecordOpTimeBefore(ctx context.Context, t time.Time) (rowsAffected int64, err error) {
141+
err = transaction(d.log, ctx, d.db, func(tx *gorm.DB) error {
142+
result := tx.WithContext(ctx).Unscoped().Delete(&model.OperationRecord{}, "operation_time < ?", t)
143+
if err := result.Error; err != nil {
144+
return err
145+
}
146+
rowsAffected = result.RowsAffected
147+
return nil
148+
})
149+
return
150+
}
151+
139152
func convertBizOperationRecord(src *biz.OperationRecord) *model.OperationRecord {
140153
return &model.OperationRecord{
141154
ID: src.ID,

internal/pkg/locale/active.en.toml

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ NameRoleDevEngineer = "Developer"
130130
NameRoleDevManager = "Development manager"
131131
NameRoleOpsEngineer = "Operation engineer"
132132
NameRoleProjectAdmin = "Project admin"
133+
NotifyDataWorkflowBodyApprovalReminder = "⏰ The export workflow has been approved. Please complete the export within 1 day, otherwise it will expire and cannot be executed"
133134
NotifyDataWorkflowBodyConfigUrl = "Please add a global URL in the system settings - global configuration"
134135
NotifyDataWorkflowBodyHead = "\n📋 Data Export Workflow Topic: %v\n📍 ProjectName: %v\n🆔 Workflow ID: %v\n📝 Workflow Description: %v\n👤 Applicant: %v\n⏰ Creation Time: %v"
135136
NotifyDataWorkflowBodyInstanceAndSchema = "🗄️ Data Source: %v\n📊 Schema: %v"
@@ -138,7 +139,6 @@ NotifyDataWorkflowBodyReason = "❌ Rejection Reason: %v"
138139
NotifyDataWorkflowBodyReport = "⭐ Data Export Workflow Audit Score: %v"
139140
NotifyDataWorkflowBodyStartEnd = "▶️ Execute Start Time: %v\n◀️ Execute End Time: %v"
140141
NotifyDataWorkflowBodyWorkFlowErr = "⚠️ Failed to read data export workflow task content, please check the workflow status through the SQLE interface"
141-
NotifyDataWorkflowBodyApprovalReminder = "⏰ The export workflow has been approved. Please complete the export within 1 day, otherwise it will expire and cannot be executed"
142142
OAuth2AutoCreateUserErr = "Failed to automatically create user: %v"
143143
OAuth2AutoCreateUserWithoutDefaultPwdErr = "Failed to automatically create user: default password not configured"
144144
OAuth2BackendLogoutFailed = "; Failed to log out of third-party platform session: %v"
@@ -155,6 +155,40 @@ OAuth2SyncSessionErr = "Failed to synchronize OAuth2 session: %v"
155155
OAuth2UserNotBoundAndDisableManuallyBindErr = "No user associated with %q was found and manual binding is disabled; please contact the system administrator"
156156
OAuth2UserNotBoundAndNoPermErr = "This OAuth2 user is not bound and has no login permissions"
157157
OAuth2UserStatIsDisableErr = "User %q is disabled"
158+
OpRecordConfigFeishu = "Update Feishu configuration"
159+
OpRecordConfigLDAP = "Update LDAP configuration"
160+
OpRecordConfigLogin = "Update login configuration"
161+
OpRecordConfigOAuth2 = "Update OAuth2 configuration"
162+
OpRecordConfigSMTP = "Update SMTP configuration"
163+
OpRecordConfigSms = "Update SMS configuration"
164+
OpRecordConfigSystemVariables = "Update system variables configuration"
165+
OpRecordConfigWebhook = "Update Webhook configuration"
166+
OpRecordConfigWechat = "Update WeChat Work configuration"
167+
OpRecordDBServiceCreate = "Create data source"
168+
OpRecordDBServiceCreateWithName = "Create data source %s"
169+
OpRecordDBServiceDelete = "Delete data source %s"
170+
OpRecordDBServiceImport = "Import data sources"
171+
OpRecordDBServiceUpdate = "Update data source %s"
172+
OpRecordDataExportApproveWithName = "Approve data export workflow %s"
173+
OpRecordDataExportCancelWithName = "Cancel data export workflow %s"
174+
OpRecordDataExportCreate = "Create data export workflow"
175+
OpRecordDataExportCreateWithName = "Create data export workflow %s"
176+
OpRecordDataExportExportWithName = "Execute data export %s"
177+
OpRecordDataExportRejectWithName = "Reject data export workflow %s"
178+
OpRecordMemberCreate = "Add member"
179+
OpRecordMemberCreateWithName = "Add member %s"
180+
OpRecordMemberDelete = "Delete member %s"
181+
OpRecordMemberUpdate = "Update member %s"
182+
OpRecordProjectArchive = "Archive project %s"
183+
OpRecordProjectCreate = "Create project"
184+
OpRecordProjectCreateWithName = "Create project %s"
185+
OpRecordProjectDelete = "Delete project %s"
186+
OpRecordProjectUnarchive = "Unarchive project %s"
187+
OpRecordProjectUpdate = "Update project %s"
188+
OpRecordUserCreate = "Create user"
189+
OpRecordUserCreateWithName = "Create user %s"
190+
OpRecordUserDelete = "Delete user %s"
191+
OpRecordUserUpdate = "Update user %s"
158192
ProjectAvailable = "Available"
159193
ProjectBusiness = "Available business"
160194
ProjectCreateTime = "Create time"

internal/pkg/locale/active.zh.toml

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ NameRoleDevEngineer = "开发工程师"
130130
NameRoleDevManager = "开发主管"
131131
NameRoleOpsEngineer = "运维工程师"
132132
NameRoleProjectAdmin = "项目管理员"
133+
NotifyDataWorkflowBodyApprovalReminder = "⏰ 导出工单已审批通过,请在1天内完成导出,过期后将无法执行"
133134
NotifyDataWorkflowBodyConfigUrl = "请在系统设置-全局配置中补充全局url"
134135
NotifyDataWorkflowBodyHead = "\n📋 数据导出工单主题: %v\n📍 所属项目: %v\n🆔 数据导出工单ID: %v\n📝 数据导出工单描述: %v\n👤 申请人: %v\n⏰ 创建时间: %v\n"
135136
NotifyDataWorkflowBodyInstanceAndSchema = "🗄️ 数据源: %v\n📊 schema: %v\n"
@@ -138,7 +139,6 @@ NotifyDataWorkflowBodyReason = "❌ 驳回原因: %v"
138139
NotifyDataWorkflowBodyReport = "⭐ 数据导出工单审核得分: %v"
139140
NotifyDataWorkflowBodyStartEnd = "▶️ 数据导出开始时间: %v\n◀️ 数据导出结束时间: %v"
140141
NotifyDataWorkflowBodyWorkFlowErr = "❌ 读取工单任务内容失败,请通过SQLE界面确认工单状态"
141-
NotifyDataWorkflowBodyApprovalReminder = "⏰ 导出工单已审批通过,请在1天内完成导出,过期后将无法执行"
142142
OAuth2AutoCreateUserErr = "自动创建用户失败: %v"
143143
OAuth2AutoCreateUserWithoutDefaultPwdErr = "自动创建用户失败,默认密码未配置"
144144
OAuth2BackendLogoutFailed = ";注销第三方平台会话失败: %v"
@@ -155,6 +155,40 @@ OAuth2SyncSessionErr = "同步OAuth2会话失败: %v"
155155
OAuth2UserNotBoundAndDisableManuallyBindErr = "未查询到 %q 关联的用户且关闭了手动绑定功能,请联系系统管理员"
156156
OAuth2UserNotBoundAndNoPermErr = "该OAuth2用户未绑定且没有登陆权限"
157157
OAuth2UserStatIsDisableErr = "用户 %q 被禁用"
158+
OpRecordConfigFeishu = "更新飞书配置"
159+
OpRecordConfigLDAP = "更新LDAP配置"
160+
OpRecordConfigLogin = "更新登录配置"
161+
OpRecordConfigOAuth2 = "更新OAuth2配置"
162+
OpRecordConfigSMTP = "更新SMTP配置"
163+
OpRecordConfigSms = "更新短信配置"
164+
OpRecordConfigSystemVariables = "更新系统变量配置"
165+
OpRecordConfigWebhook = "更新Webhook配置"
166+
OpRecordConfigWechat = "更新企业微信配置"
167+
OpRecordDBServiceCreate = "创建数据源"
168+
OpRecordDBServiceCreateWithName = "创建数据源 %s"
169+
OpRecordDBServiceDelete = "删除数据源 %s"
170+
OpRecordDBServiceImport = "导入数据源"
171+
OpRecordDBServiceUpdate = "更新数据源 %s"
172+
OpRecordDataExportApproveWithName = "审批通过数据导出工单 %s"
173+
OpRecordDataExportCancelWithName = "取消数据导出工单 %s"
174+
OpRecordDataExportCreate = "创建数据导出工单"
175+
OpRecordDataExportCreateWithName = "创建数据导出工单 %s"
176+
OpRecordDataExportExportWithName = "执行数据导出 %s"
177+
OpRecordDataExportRejectWithName = "驳回数据导出工单 %s"
178+
OpRecordMemberCreate = "添加成员"
179+
OpRecordMemberCreateWithName = "添加成员 %s"
180+
OpRecordMemberDelete = "删除成员 %s"
181+
OpRecordMemberUpdate = "更新成员 %s"
182+
OpRecordProjectArchive = "归档项目 %s"
183+
OpRecordProjectCreate = "创建项目"
184+
OpRecordProjectCreateWithName = "创建项目 %s"
185+
OpRecordProjectDelete = "删除项目 %s"
186+
OpRecordProjectUnarchive = "取消归档项目 %s"
187+
OpRecordProjectUpdate = "更新项目 %s"
188+
OpRecordUserCreate = "创建用户"
189+
OpRecordUserCreateWithName = "创建用户 %s"
190+
OpRecordUserDelete = "删除用户 %s"
191+
OpRecordUserUpdate = "更新用户 %s"
158192
ProjectAvailable = "可用"
159193
ProjectBusiness = "所属业务"
160194
ProjectCreateTime = "创建时间"

0 commit comments

Comments
 (0)