package platforms import ( "code-review/services/types" "code-review/utils" "fmt" "log" "strings" ) // GitlabEvent Gitlab 平台的 webhook 事件 type GitlabEvent struct { apiBase string auth *AuthConfig Event string client *httpClient ObjectKind string `json:"object_kind"` // "push", "merge_request" 等 Before string `json:"before"` After string `json:"after"` Ref string `json:"ref"` Project struct { ID int `json:"id"` Name string `json:"name"` Description string `json:"description"` WebURL string `json:"web_url"` PathWithNamespace string `json:"path_with_namespace"` } `json:"project"` Commits []struct { ID string `json:"id"` Message string `json:"message"` Title string `json:"title"` } `json:"commits"` } // NewGitlabEvent 创建 GitLab 事件实例 func NewGitlabEvent(baseURL string, auth *AuthConfig, event string) *GitlabEvent { return &GitlabEvent{ apiBase: baseURL, auth: auth, Event: event, } } // 定义 GitLab diff 响应的结构 type gitlabDiff struct { Diff string `json:"diff"` NewPath string `json:"new_path"` OldPath string `json:"old_path"` AMode string `json:"a_mode"` BMode string `json:"b_mode"` NewFile bool `json:"new_file"` RenamedFile bool `json:"renamed_file"` DeletedFile bool `json:"deleted_file"` } func (e *GitlabEvent) ExtractChanges() (*types.CodeChanges, error) { // 检查是否有提交记录 if len(e.Commits) == 0 { log.Printf("没有提交记录,跳过代码审查") return nil, nil } // 检查是否跳过代码审查 validCommits := make([]struct { ID string `json:"id"` Message string `json:"message"` Title string `json:"title"` }, 0) for _, commit := range e.Commits { if strings.Contains(commit.Message, "[skip codereview]") { log.Printf("跳过代码审查: commit=%s", commit.ID) return nil, nil } // 过滤合并提交 if strings.HasPrefix(commit.Message, "Merge remote-tracking branch") || strings.HasPrefix(commit.Message, "Merge branch") { log.Printf("跳过合并提交: commit=%s", commit.ID) continue } validCommits = append(validCommits, commit) } if len(validCommits) == 0 { log.Printf("没有有效的提交记录(所有提交都是合并提交),跳过代码审查") return nil, nil } if e.client == nil { e.client = newHTTPClient(e.apiBase, e.auth) log.Printf("初始化 HTTP 客户端: url=%s", e.apiBase) } changes := &types.CodeChanges{ Repository: e.Project.PathWithNamespace, Branch: e.Ref, CommitID: e.After, Files: make([]types.FileChange, 0), } for _, commit := range validCommits { // 移除 URL 中的 token apiPath := fmt.Sprintf("/api/v4/projects/%d/repository/commits/%s/diff", e.Project.ID, commit.ID) headers := map[string]string{ "PRIVATE-TOKEN": e.auth.Token, } var diffs []gitlabDiff if err := e.client.getWithHeaders(apiPath, &diffs, headers); err != nil { log.Printf("获取提交详情失败: commit=%s, error=%v", commit.ID, err) continue } for _, diff := range diffs { status := "modified" if diff.NewFile { status = "added" } else if diff.DeletedFile { status = "deleted" } else if diff.RenamedFile { status = "renamed" } changes.Files = append(changes.Files, types.FileChange{ Path: diff.NewPath, Content: diff.Diff, Type: utils.ParseFileType(status), }) } } return changes, nil } func (e *GitlabEvent) PostComments(result *types.ReviewResult) error { if e.client == nil { e.client = newHTTPClient(e.apiBase, e.auth) } // 创建 issue path := fmt.Sprintf("/api/v4/projects/%d/issues", e.Project.ID) issueData := map[string]interface{}{ "title": fmt.Sprintf("AI 代码审查 - %s", e.After[:7]), "description": utils.FormatReviewResult(result), } headers := map[string]string{ "PRIVATE-TOKEN": e.auth.Token, } if err := e.client.postWithHeaders(path, issueData, headers); err != nil { log.Printf("创建 issue 失败: path=%s, error=%v", path, err) return fmt.Errorf("创建 issue 失败: %w", err) } log.Printf("成功创建 issue: path=%s, commitID=%s", path, e.After[:7]) return nil } func (e *GitlabEvent) GetPlatform() string { return "gitlab" }