Skip to content

Commit 3d57341

Browse files
Merge pull request #588 from actiontech/feat-maintenace-time-ce
Feat maintenace time ce
2 parents 261a8a9 + e720440 commit 3d57341

15 files changed

Lines changed: 287 additions & 142 deletions

File tree

api/swagger.json

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8702,12 +8702,6 @@
87028702
},
87038703
"x-go-package": "github.com/actiontech/dms/api/dms/service/v1"
87048704
},
8705-
"DateTime": {
8706-
"description": "DateTime is a time but it serializes to ISO8601 format with millis\nIt knows how to read 3 different variations of a RFC3339 date time.\nMost APIs we encounter want either millisecond or second precision times.\nThis just tries to make it worry-free.",
8707-
"type": "string",
8708-
"format": "date-time",
8709-
"x-go-package": "github.com/go-openapi/strfmt"
8710-
},
87118705
"DbServiceConnections": {
87128706
"type": "object",
87138707
"properties": {
@@ -9355,7 +9349,7 @@
93559349
"x-go-name": "Message"
93569350
}
93579351
},
9358-
"x-go-package": "github.com/actiontech/dms/api/dms/service/v1"
9352+
"x-go-package": "github.com/actiontech/dms/pkg/dms-common/api/dms/v1"
93599353
},
93609354
"GetOauth2ConfigurationResData": {
93619355
"type": "object",
@@ -10006,6 +10000,10 @@
1000610000
"x-go-package": "github.com/actiontech/dms/api/dms/service/v1"
1000710001
},
1000810002
"I18nStr": {
10003+
"type": "object",
10004+
"additionalProperties": {
10005+
"type": "string"
10006+
},
1000910007
"x-go-package": "github.com/actiontech/dms/pkg/dms-common/i18nPkg"
1001010008
},
1001110009
"ImportDBService": {
@@ -11891,17 +11889,6 @@
1189111889
"ListMemberRoleWithOpRange": {
1189211890
"type": "object",
1189311891
"properties": {
11894-
"member_group": {
11895-
"$ref": "#/definitions/ProjectMemberGroup"
11896-
},
11897-
"op_permissions": {
11898-
"description": "member op permissions",
11899-
"type": "array",
11900-
"items": {
11901-
"$ref": "#/definitions/UidWithName"
11902-
},
11903-
"x-go-name": "OpPermissions"
11904-
},
1190511892
"op_range_type": {
1190611893
"description": "op permission range type, only support db service now\nunknown OpRangeTypeUnknown\nglobal OpRangeTypeGlobal 全局权限: 该权限只能被用户使用\nproject OpRangeTypeProject 项目权限: 该权限只能被成员使用\ndb_service OpRangeTypeDBService 项目内的数据源权限: 该权限只能被成员使用",
1190711894
"type": "string",
@@ -11926,7 +11913,7 @@
1192611913
"$ref": "#/definitions/UidWithName"
1192711914
}
1192811915
},
11929-
"x-go-package": "github.com/actiontech/dms/api/dms/service/v1"
11916+
"x-go-package": "github.com/actiontech/dms/pkg/dms-common/api/dms/v1"
1193011917
},
1193111918
"ListMemberTipsItem": {
1193211919
"type": "object",
@@ -13862,6 +13849,13 @@
1386213849
"type": "boolean",
1386313850
"x-go-name": "AuditEnabled"
1386413851
},
13852+
"maintenance_times": {
13853+
"type": "array",
13854+
"items": {
13855+
"$ref": "#/definitions/MaintenanceTime"
13856+
},
13857+
"x-go-name": "MaintenanceTimes"
13858+
},
1386513859
"max_pre_query_rows": {
1386613860
"type": "integer",
1386713861
"format": "int64",
@@ -14311,7 +14305,7 @@
1431114305
"x-go-name": "Uid"
1431214306
}
1431314307
},
14314-
"x-go-package": "github.com/actiontech/dms/api/dms/service/v1"
14308+
"x-go-package": "github.com/actiontech/dms/pkg/dms-common/api/dms/v1"
1431514309
},
1431614310
"UpdateBusinessTagReq": {
1431714311
"type": "object",

api/swagger.yaml

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1389,15 +1389,6 @@ definitions:
13891389
x-go-name: Params
13901390
type: object
13911391
x-go-package: github.com/actiontech/dms/api/dms/service/v1
1392-
DateTime:
1393-
description: |-
1394-
DateTime is a time but it serializes to ISO8601 format with millis
1395-
It knows how to read 3 different variations of a RFC3339 date time.
1396-
Most APIs we encounter want either millisecond or second precision times.
1397-
This just tries to make it worry-free.
1398-
format: date-time
1399-
type: string
1400-
x-go-package: github.com/go-openapi/strfmt
14011392
DbServiceConnections:
14021393
properties:
14031394
db_service_uid:
@@ -1889,7 +1880,7 @@ definitions:
18891880
type: string
18901881
x-go-name: Message
18911882
type: object
1892-
x-go-package: github.com/actiontech/dms/api/dms/service/v1
1883+
x-go-package: github.com/actiontech/dms/pkg/dms-common/api/dms/v1
18931884
GetOauth2ConfigurationResData:
18941885
properties:
18951886
access_token_tag:
@@ -2417,6 +2408,9 @@ definitions:
24172408
type: object
24182409
x-go-package: github.com/actiontech/dms/api/dms/service/v1
24192410
I18nStr:
2411+
additionalProperties:
2412+
type: string
2413+
type: object
24202414
x-go-package: github.com/actiontech/dms/pkg/dms-common/i18nPkg
24212415
ImportDBService:
24222416
properties:
@@ -3891,14 +3885,6 @@ definitions:
38913885
x-go-package: github.com/actiontech/dms/api/dms/service/v1
38923886
ListMemberRoleWithOpRange:
38933887
properties:
3894-
member_group:
3895-
$ref: '#/definitions/ProjectMemberGroup'
3896-
op_permissions:
3897-
description: member op permissions
3898-
items:
3899-
$ref: '#/definitions/UidWithName'
3900-
type: array
3901-
x-go-name: OpPermissions
39023888
op_range_type:
39033889
description: |-
39043890
op permission range type, only support db service now
@@ -3927,7 +3913,7 @@ definitions:
39273913
role_uid:
39283914
$ref: '#/definitions/UidWithName'
39293915
type: object
3930-
x-go-package: github.com/actiontech/dms/api/dms/service/v1
3916+
x-go-package: github.com/actiontech/dms/pkg/dms-common/api/dms/v1
39313917
ListMemberTipsItem:
39323918
properties:
39333919
user_id:
@@ -5599,6 +5585,11 @@ definitions:
55995585
audit_enabled:
56005586
type: boolean
56015587
x-go-name: AuditEnabled
5588+
maintenance_times:
5589+
items:
5590+
$ref: '#/definitions/MaintenanceTime'
5591+
type: array
5592+
x-go-name: MaintenanceTimes
56025593
max_pre_query_rows:
56035594
format: int64
56045595
type: integer
@@ -5928,7 +5919,7 @@ definitions:
59285919
type: string
59295920
x-go-name: Uid
59305921
type: object
5931-
x-go-package: github.com/actiontech/dms/api/dms/service/v1
5922+
x-go-package: github.com/actiontech/dms/pkg/dms-common/api/dms/v1
59325923
UpdateBusinessTagReq:
59335924
properties:
59345925
business_tag:

internal/dms/biz/cloudbeaver.go

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,10 @@ type CloudbeaverUsecase struct {
8989
projectUsecase *ProjectUsecase
9090
repo CloudbeaverRepo
9191
proxyTargetRepo ProxyTargetRepo
92+
maintenanceTimeUsecase *MaintenanceTimeUsecase
9293
}
9394

94-
func NewCloudbeaverUsecase(log utilLog.Logger, cfg *CloudbeaverCfg, userUsecase *UserUsecase, dbServiceUsecase *DBServiceUsecase, opPermissionVerifyUsecase *OpPermissionVerifyUsecase, dmsConfigUseCase *DMSConfigUseCase, dataMaskingUseCase *DataMaskingUsecase, cloudbeaverRepo CloudbeaverRepo, proxyTargetRepo ProxyTargetRepo, cbOperationUseDase *CbOperationLogUsecase, projectUsecase *ProjectUsecase) (cu *CloudbeaverUsecase) {
95+
func NewCloudbeaverUsecase(log utilLog.Logger, cfg *CloudbeaverCfg, userUsecase *UserUsecase, dbServiceUsecase *DBServiceUsecase, opPermissionVerifyUsecase *OpPermissionVerifyUsecase, dmsConfigUseCase *DMSConfigUseCase, dataMaskingUseCase *DataMaskingUsecase, cloudbeaverRepo CloudbeaverRepo, proxyTargetRepo ProxyTargetRepo, cbOperationUseDase *CbOperationLogUsecase, projectUsecase *ProjectUsecase, maintenanceTimeUsecase *MaintenanceTimeUsecase) (cu *CloudbeaverUsecase) {
9596
cu = &CloudbeaverUsecase{
9697
repo: cloudbeaverRepo,
9798
proxyTargetRepo: proxyTargetRepo,
@@ -104,6 +105,7 @@ func NewCloudbeaverUsecase(log utilLog.Logger, cfg *CloudbeaverCfg, userUsecase
104105
projectUsecase: projectUsecase,
105106
cloudbeaverCfg: cfg,
106107
log: utilLog.NewHelper(log, utilLog.WithMessageKey("biz.cloudbeaver")),
108+
maintenanceTimeUsecase: maintenanceTimeUsecase,
107109
}
108110

109111
// 启动缓存清理协程
@@ -629,6 +631,11 @@ func (cu *CloudbeaverUsecase) GraphQLDistributor() echo.MiddlewareFunc {
629631
return nil, c.JSON(http.StatusOK, convertToResp(ctx, resp))
630632
}
631633

634+
// [运维时间管控检查] 在审核通过后、工单判断前检查运维时间管控
635+
if blocked, err := cu.checkMaintenanceTime(c, resp.Results, dbService); blocked || err != nil {
636+
return nil, err
637+
}
638+
632639
// 判断是否需要通过工单执行(非 DQL 语句)
633640
if cu.shouldExecuteByWorkflow(dbService, resp.Results) {
634641
return cu.executeNonDQLByWorkflow(ctx, c, dbService, params, resp)
@@ -2067,6 +2074,46 @@ func (cu *CloudbeaverUsecase) shouldExecuteByWorkflow(dbService *DBService, audi
20672074
return false
20682075
}
20692076

2077+
// checkMaintenanceTime 检查运维时间管控(CloudBeaver工作台)
2078+
// 返回 blocked=true 表示已构造拦截响应,调用方应立即返回
2079+
func (cu *CloudbeaverUsecase) checkMaintenanceTime(c echo.Context, auditResults []cloudbeaver.AuditSQLResV2, dbService *DBService) (blocked bool, err error) {
2080+
if cu.maintenanceTimeUsecase == nil {
2081+
return false, fmt.Errorf("maintenance time usecase is nil")
2082+
}
2083+
2084+
currentUserUid, _ := c.Get(dmsUserIdKey).(string)
2085+
if currentUserUid == "" {
2086+
return false, fmt.Errorf("current user uid is empty")
2087+
}
2088+
2089+
sqlTypes := make([]string, 0, len(auditResults))
2090+
for _, r := range auditResults {
2091+
sqlTypes = append(sqlTypes, r.SQLType)
2092+
}
2093+
2094+
var sqlQueryConfig *SQLQueryConfig
2095+
if dbService != nil && dbService.SQLEConfig != nil {
2096+
sqlQueryConfig = dbService.SQLEConfig.SQLQueryConfig
2097+
}
2098+
2099+
allowed, message, checkErr := cu.maintenanceTimeUsecase.CheckSQLExecutionAllowed(
2100+
c.Request().Context(),
2101+
currentUserUid,
2102+
sqlTypes,
2103+
time.Now(),
2104+
sqlQueryConfig,
2105+
)
2106+
if checkErr != nil {
2107+
cu.log.Errorf("check maintenance time failed: %v", checkErr)
2108+
return false, checkErr
2109+
}
2110+
if !allowed {
2111+
return true, c.JSON(http.StatusOK,
2112+
newResp(c.Request().Context(), "Maintenance Time Blocked", CBErrorCode, message))
2113+
}
2114+
return false, nil
2115+
}
2116+
20702117
// workflowExecParams 工单执行所需的参数
20712118
type workflowExecParams struct {
20722119
contextIdStr string

internal/dms/biz/db_service.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -223,13 +223,14 @@ type BizDBServiceArgs struct {
223223
}
224224

225225
type SQLQueryConfig struct {
226-
MaxPreQueryRows int `json:"max_pre_query_rows"`
227-
QueryTimeoutSecond int `json:"query_timeout_second"`
228-
AuditEnabled bool `json:"audit_enabled"`
229-
WorkflowExecEnabled bool `json:"workflow_exec_enabled"`
230-
AllowQueryWhenLessThanAuditLevel string `json:"allow_query_when_less_than_audit_level"`
231-
RuleTemplateID string `json:"rule_template_id"`
232-
RuleTemplateName string `json:"rule_template_name"`
226+
MaxPreQueryRows int `json:"max_pre_query_rows"`
227+
QueryTimeoutSecond int `json:"query_timeout_second"`
228+
AuditEnabled bool `json:"audit_enabled"`
229+
WorkflowExecEnabled bool `json:"workflow_exec_enabled"`
230+
AllowQueryWhenLessThanAuditLevel string `json:"allow_query_when_less_than_audit_level"`
231+
RuleTemplateID string `json:"rule_template_id"`
232+
RuleTemplateName string `json:"rule_template_name"`
233+
MaintenancePeriods pkgPeriods.Periods `json:"maintenance_periods"`
233234
}
234235

235236
func (d *DBServiceUsecase) CreateDBService(ctx context.Context, args *BizDBServiceArgs, currentUserUid string) (uid string, err error) {
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package biz
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strings"
7+
"time"
8+
9+
"github.com/actiontech/dms/internal/pkg/locale"
10+
pkgPeriods "github.com/actiontech/dms/pkg/periods"
11+
12+
utilLog "github.com/actiontech/dms/pkg/dms-common/pkg/log"
13+
)
14+
15+
// MaintenanceTimeConfig 运维时间管控配置
16+
type MaintenanceTimeConfig struct {
17+
Enabled bool
18+
Periods pkgPeriods.Periods
19+
}
20+
21+
// MaintenanceTimeUsecase 运维时间管控业务逻辑
22+
type MaintenanceTimeUsecase struct {
23+
opPermissionVerifyUsecase *OpPermissionVerifyUsecase
24+
log *utilLog.Helper
25+
}
26+
27+
// NewMaintenanceTimeUsecase 创建运维时间管控业务逻辑实例
28+
func NewMaintenanceTimeUsecase(
29+
log utilLog.Logger,
30+
opPermissionVerifyUsecase *OpPermissionVerifyUsecase,
31+
) *MaintenanceTimeUsecase {
32+
return &MaintenanceTimeUsecase{
33+
opPermissionVerifyUsecase: opPermissionVerifyUsecase,
34+
log: utilLog.NewHelper(log, utilLog.WithMessageKey("biz.maintenance_time")),
35+
}
36+
}
37+
38+
// MaintenanceTimeConfigFromSQLQuery 从数据源 sql_query_config 解析运维时间管控配置
39+
func MaintenanceTimeConfigFromSQLQuery(sqlQueryConfig *SQLQueryConfig) *MaintenanceTimeConfig {
40+
cfg := &MaintenanceTimeConfig{}
41+
if sqlQueryConfig == nil {
42+
return cfg
43+
}
44+
cfg.Enabled = len(sqlQueryConfig.MaintenancePeriods) > 0
45+
if cfg.Enabled {
46+
cfg.Periods = sqlQueryConfig.MaintenancePeriods.Copy()
47+
}
48+
return cfg
49+
}
50+
51+
// CheckSQLExecutionAllowed 检查SQL执行是否被运维时间管控允许
52+
// 参数:
53+
// - userUid: 当前执行SQL的用户UID
54+
// - sqlTypes: SQLE审核返回的sql_type列表(每条SQL对应一个)
55+
// - currentTime: 当前时间(参数化便于测试)
56+
// - sqlQueryConfig: 数据源上的 SQL 查询配置(含运维时间窗口)
57+
//
58+
// 返回值:
59+
// - allowed: 是否允许执行
60+
// - message: 拦截时的提示消息(allowed=true时为空)
61+
// - err: 内部错误
62+
func (m *MaintenanceTimeUsecase) CheckSQLExecutionAllowed(
63+
ctx context.Context,
64+
userUid string,
65+
sqlTypes []string,
66+
currentTime time.Time,
67+
sqlQueryConfig *SQLQueryConfig,
68+
) (allowed bool, message string, err error) {
69+
// 1. 获取配置
70+
config := MaintenanceTimeConfigFromSQLQuery(sqlQueryConfig)
71+
72+
// 2. 如果开关关闭,直接允许执行
73+
if !config.Enabled {
74+
return true, "", nil
75+
}
76+
77+
// 3. 检查 sqlTypes 中是否有非DQL语句
78+
hasNonDQL := false
79+
for _, sqlType := range sqlTypes {
80+
if sqlType != "dql" { // 空字符串("")也视为非DQL(保守策略)
81+
hasNonDQL = true
82+
break
83+
}
84+
}
85+
if !hasNonDQL {
86+
return true, "", nil
87+
}
88+
89+
// 4. 检查用户是否为管理员
90+
isAdmin, err := m.opPermissionVerifyUsecase.CanOpGlobal(ctx, userUid)
91+
if err != nil {
92+
return false, "", fmt.Errorf("failed to check user admin permission: %v", err)
93+
}
94+
if isAdmin {
95+
m.log.Warnf("user %s is admin, skip cloudbeaver maintenance time check", userUid)
96+
return true, "", nil
97+
}
98+
99+
// 5. 检查当前时间是否在配置的运维时间段内
100+
if config.Periods.IsWithinScope(currentTime) {
101+
return true, "", nil
102+
}
103+
104+
// 6. 构造拦截消息
105+
periodsStr := formatPeriodsToReadableString(config.Periods)
106+
message = fmt.Sprintf(locale.Bundle.LocalizeMsgByCtx(ctx, locale.SqlWorkbenchMaintenanceTimeBlocked), periodsStr)
107+
108+
// 7. 返回拦截结果
109+
return false, message, nil
110+
}
111+
112+
// formatPeriodsToReadableString 将时间段格式化为可读字符串
113+
// 例如: "01:00-06:00, 22:00-02:00"
114+
func formatPeriodsToReadableString(ps pkgPeriods.Periods) string {
115+
parts := make([]string, 0, len(ps))
116+
for _, p := range ps {
117+
parts = append(parts, fmt.Sprintf("%02d:%02d-%02d:%02d",
118+
p.StartHour, p.StartMinute, p.EndHour, p.EndMinute))
119+
}
120+
return strings.Join(parts, ", ")
121+
}

0 commit comments

Comments
 (0)