diff --git a/sqle/api/app.go b/sqle/api/app.go index ce975589ce..a5ca1f117f 100644 --- a/sqle/api/app.go +++ b/sqle/api/app.go @@ -424,6 +424,7 @@ func StartApi(net *gracenet.Net, exitChan chan struct{}, config config.SqleConfi // sql audit v1Router.POST("/sql_audit", v1.DirectAudit) v1Router.POST("/audit_files", v1.DirectAuditFiles) + v2Router.POST("/audit_files", v2.DirectAuditFiles) v1Router.GET("/sql_analysis", v1.DirectGetSQLAnalysis) // enterprise customized apis diff --git a/sqle/api/controller/v2/sql_audit.go b/sqle/api/controller/v2/sql_audit.go index d7636bfc03..4f5d1eef47 100644 --- a/sqle/api/controller/v2/sql_audit.go +++ b/sqle/api/controller/v2/sql_audit.go @@ -1,11 +1,15 @@ package v2 import ( + e "errors" + "fmt" "net/http" + "strings" parser "github.com/actiontech/mybatis-mapper-2-sql" "github.com/actiontech/sqle/sqle/api/controller" v1 "github.com/actiontech/sqle/sqle/api/controller/v1" + "github.com/actiontech/sqle/sqle/errors" "github.com/actiontech/sqle/sqle/log" "github.com/actiontech/sqle/sqle/model" "github.com/actiontech/sqle/sqle/server" @@ -28,10 +32,10 @@ type AuditResDataV2 struct { } type AuditSQLResV2 struct { - Number uint `json:"number"` - ExecSQL string `json:"exec_sql"` - AuditResult []*AuditResult `json:"audit_result"` - AuditLevel string `json:"audit_level"` + Number uint `json:"number"` + ExecSQL string `json:"exec_sql"` + AuditResult []AuditResult `json:"audit_result"` + AuditLevel string `json:"audit_level"` } type DirectAuditResV2 struct { @@ -94,7 +98,7 @@ func convertTaskResultToAuditResV2(task *model.Task) *AuditResDataV2 { results[i] = AuditSQLResV2{ Number: sql.Number, ExecSQL: sql.Content, - AuditResult: ar, + AuditResult: convertAuditResultToAuditResV2(sql.AuditResults), AuditLevel: sql.AuditLevel, } @@ -106,3 +110,120 @@ func convertTaskResultToAuditResV2(task *model.Task) *AuditResDataV2 { SQLResults: results, } } + +type DirectAuditFileReqV2 struct { + InstanceType string `json:"instance_type" form:"instance_type" example:"MySQL" valid:"required"` + // 调用方不应该关心SQL是否被完美的拆分成独立的条目, 拆分SQL由SQLE实现 + // 每个数组元素是一个文件内容 + FileContents []string `json:"file_contents" form:"file_contents" example:"select * from t1; select * from t2;"` + SQLType string `json:"sql_type" form:"sql_type" example:"sql" enums:"sql,mybatis," valid:"omitempty,oneof=sql mybatis"` + ProjectName string `json:"project_name" form:"project_name" example:"project1" valid:"required"` + InstanceName *string `json:"instance_name" form:"instance_name" example:"instance1"` + SchemaName *string `json:"schema_name" form:"schema_name" example:"schema1"` +} + +// @Summary 直接从文件内容提取SQL并审核,SQL文件暂时只支持一次解析一个文件 +// @Description Direct audit sql from SQL files and MyBatis files +// @Id directAuditFilesV2 +// @Tags sql_audit +// @Security ApiKeyAuth +// @Param req body v2.DirectAuditFileReqV2 true "files that should be audited" +// @Success 200 {object} v2.DirectAuditResV2 +// @router /v2/audit_files [post] +func DirectAuditFiles(c echo.Context) error { + req := new(DirectAuditFileReqV2) + err := controller.BindAndValidateReq(c, req) + if err != nil { + return err + } + + user := controller.GetUserName(c) + s := model.GetStorage() + if yes, err := s.IsProjectMember(user, req.ProjectName); err != nil { + return controller.JSONBaseErrorReq(c, fmt.Errorf("check privilege failed: %v", err)) + } else if !yes { + return controller.JSONBaseErrorReq(c, errors.New(errors.ErrAccessDeniedError, e.New("you are not the project member"))) + } + + if len(req.FileContents) <= 0 { + return controller.JSONBaseErrorReq(c, e.New("file_contents is required")) + } + + sqls := "" + if req.SQLType == v1.SQLTypeMyBatis { + ss, err := parser.ParseXMLs(req.FileContents, false) + if err != nil { + return controller.JSONBaseErrorReq(c, err) + } + sqls = strings.Join(ss, ";") + } else { + // sql文件暂时只支持一次解析一个文件 + sqls = req.FileContents[0] + } + + l := log.NewEntry().WithField("api", "[post]/v2/audit_files") + + var instance *model.Instance + var exist bool + if req.InstanceName != nil { + instance, exist, err = s.GetInstanceByNameAndProjectName(*req.InstanceName, req.ProjectName) + if err != nil { + return controller.JSONBaseErrorReq(c, err) + } + if !exist { + return controller.JSONBaseErrorReq(c, v1.ErrInstanceNotExist) + } + } + + var schemaName string + if req.SchemaName != nil { + schemaName = *req.SchemaName + } + + var task *model.Task + if instance != nil && schemaName != "" { + task, err = server.DirectAuditByInstance(l, sqls, schemaName, instance) + } else { + task, err = server.AuditSQLByDBType(l, sqls, req.InstanceType, nil, "") + } + if err != nil { + l.Errorf("audit sqls failed: %v", err) + return controller.JSONBaseErrorReq(c, v1.ErrDirectAudit) + } + + return c.JSON(http.StatusOK, DirectAuditResV2{ + BaseRes: controller.BaseRes{}, + Data: convertFileAuditTaskResultToAuditResV2(task), + }) +} + +func convertFileAuditTaskResultToAuditResV2(task *model.Task) *AuditResDataV2 { + results := make([]AuditSQLResV2, len(task.ExecuteSQLs)) + for i, sql := range task.ExecuteSQLs { + results[i] = AuditSQLResV2{ + Number: sql.Number, + ExecSQL: sql.Content, + AuditResult: convertAuditResultToAuditResV2(sql.AuditResults), + AuditLevel: sql.AuditLevel, + } + + } + return &AuditResDataV2{ + AuditLevel: task.AuditLevel, + Score: task.Score, + PassRate: task.PassRate, + SQLResults: results, + } +} + +func convertAuditResultToAuditResV2(auditResults model.AuditResults) []AuditResult { + ar := make([]AuditResult, len(auditResults)) + for i := range auditResults { + ar[i] = AuditResult{ + Level: auditResults[i].Level, + Message: auditResults[i].Message, + RuleName: auditResults[i].RuleName, + } + } + return ar +} diff --git a/sqle/docs/docs.go b/sqle/docs/docs.go index c5a5aab2e5..07e0860f2f 100644 --- a/sqle/docs/docs.go +++ b/sqle/docs/docs.go @@ -8775,6 +8775,40 @@ var doc = `{ } } }, + "/v2/audit_files": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Direct audit sql from SQL files and MyBatis files", + "tags": [ + "sql_audit" + ], + "summary": "直接从文件内容提取SQL并审核,SQL文件暂时只支持一次解析一个文件", + "operationId": "directAuditFilesV2", + "parameters": [ + { + "description": "files that should be audited", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v2.DirectAuditFileReqV2" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v2.DirectAuditResV2" + } + } + } + } + }, "/v2/configurations/drivers": { "get": { "security": [ @@ -9898,6 +9932,26 @@ var doc = `{ } } }, + "model.AuditResult": { + "type": "object", + "properties": { + "level": { + "type": "string" + }, + "message": { + "type": "string" + }, + "rule_name": { + "type": "string" + } + } + }, + "model.AuditResults": { + "type": "array", + "items": { + "$ref": "#/definitions/model.AuditResult" + } + }, "v1.AuditPlanCount": { "type": "object", "properties": { @@ -16400,10 +16454,8 @@ var doc = `{ "type": "string" }, "audit_result": { - "type": "array", - "items": { - "$ref": "#/definitions/v2.AuditResult" - } + "type": "object", + "$ref": "#/definitions/model.AuditResults" }, "exec_sql": { "type": "string" @@ -16564,6 +16616,46 @@ var doc = `{ } } }, + "v2.DirectAuditFileReqV2": { + "type": "object", + "properties": { + "file_contents": { + "description": "调用方不应该关心SQL是否被完美的拆分成独立的条目, 拆分SQL由SQLE实现\n每个数组元素是一个文件内容", + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "select * from t1; select * from t2;" + ] + }, + "instance_name": { + "type": "string", + "example": "instance1" + }, + "instance_type": { + "type": "string", + "example": "MySQL" + }, + "project_name": { + "type": "string", + "example": "project1" + }, + "schema_name": { + "type": "string", + "example": "schema1" + }, + "sql_type": { + "type": "string", + "enum": [ + "sql", + "mybatis", + "" + ], + "example": "sql" + } + } + }, "v2.DirectAuditReqV2": { "type": "object", "properties": { diff --git a/sqle/docs/swagger.json b/sqle/docs/swagger.json index e5a0fa04c0..27e63b84ce 100644 --- a/sqle/docs/swagger.json +++ b/sqle/docs/swagger.json @@ -8759,6 +8759,40 @@ } } }, + "/v2/audit_files": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Direct audit sql from SQL files and MyBatis files", + "tags": [ + "sql_audit" + ], + "summary": "直接从文件内容提取SQL并审核,SQL文件暂时只支持一次解析一个文件", + "operationId": "directAuditFilesV2", + "parameters": [ + { + "description": "files that should be audited", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v2.DirectAuditFileReqV2" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v2.DirectAuditResV2" + } + } + } + } + }, "/v2/configurations/drivers": { "get": { "security": [ @@ -9882,6 +9916,26 @@ } } }, + "model.AuditResult": { + "type": "object", + "properties": { + "level": { + "type": "string" + }, + "message": { + "type": "string" + }, + "rule_name": { + "type": "string" + } + } + }, + "model.AuditResults": { + "type": "array", + "items": { + "$ref": "#/definitions/model.AuditResult" + } + }, "v1.AuditPlanCount": { "type": "object", "properties": { @@ -16384,10 +16438,8 @@ "type": "string" }, "audit_result": { - "type": "array", - "items": { - "$ref": "#/definitions/v2.AuditResult" - } + "type": "object", + "$ref": "#/definitions/model.AuditResults" }, "exec_sql": { "type": "string" @@ -16548,6 +16600,46 @@ } } }, + "v2.DirectAuditFileReqV2": { + "type": "object", + "properties": { + "file_contents": { + "description": "调用方不应该关心SQL是否被完美的拆分成独立的条目, 拆分SQL由SQLE实现\n每个数组元素是一个文件内容", + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "select * from t1; select * from t2;" + ] + }, + "instance_name": { + "type": "string", + "example": "instance1" + }, + "instance_type": { + "type": "string", + "example": "MySQL" + }, + "project_name": { + "type": "string", + "example": "project1" + }, + "schema_name": { + "type": "string", + "example": "schema1" + }, + "sql_type": { + "type": "string", + "enum": [ + "sql", + "mybatis", + "" + ], + "example": "sql" + } + } + }, "v2.DirectAuditReqV2": { "type": "object", "properties": { diff --git a/sqle/docs/swagger.yaml b/sqle/docs/swagger.yaml index 5ad2664600..d689064588 100644 --- a/sqle/docs/swagger.yaml +++ b/sqle/docs/swagger.yaml @@ -9,6 +9,19 @@ definitions: example: ok type: string type: object + model.AuditResult: + properties: + level: + type: string + message: + type: string + rule_name: + type: string + type: object + model.AuditResults: + items: + $ref: '#/definitions/model.AuditResult' + type: array v1.AuditPlanCount: properties: audit_plan_count: @@ -4437,9 +4450,8 @@ definitions: audit_level: type: string audit_result: - items: - $ref: '#/definitions/v2.AuditResult' - type: array + $ref: '#/definitions/model.AuditResults' + type: object exec_sql: type: string number: @@ -4547,6 +4559,37 @@ definitions: workflow_id: type: string type: object + v2.DirectAuditFileReqV2: + properties: + file_contents: + description: |- + 调用方不应该关心SQL是否被完美的拆分成独立的条目, 拆分SQL由SQLE实现 + 每个数组元素是一个文件内容 + example: + - select * from t1; select * from t2; + items: + type: string + type: array + instance_name: + example: instance1 + type: string + instance_type: + example: MySQL + type: string + project_name: + example: project1 + type: string + schema_name: + example: schema1 + type: string + sql_type: + enum: + - sql + - mybatis + - "" + example: sql + type: string + type: object v2.DirectAuditReqV2: properties: instance_type: @@ -10579,6 +10622,27 @@ paths: summary: 获取全局工单列表 tags: - workflow + /v2/audit_files: + post: + description: Direct audit sql from SQL files and MyBatis files + operationId: directAuditFilesV2 + parameters: + - description: files that should be audited + in: body + name: req + required: true + schema: + $ref: '#/definitions/v2.DirectAuditFileReqV2' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/v2.DirectAuditResV2' + security: + - ApiKeyAuth: [] + summary: 直接从文件内容提取SQL并审核,SQL文件暂时只支持一次解析一个文件 + tags: + - sql_audit /v2/configurations/drivers: get: description: get drivers