diff --git a/config/config.go b/config/config.go index a66271e..c13881b 100644 --- a/config/config.go +++ b/config/config.go @@ -57,6 +57,10 @@ type GitConfig struct { Name string `mapstructure:"name"` // 平台名称 Type string `mapstructure:"type"` // 平台类型 Token string `mapstructure:"token"` // 访问令牌 + Username string `mapstructure:"username"` // 用户名(用于基本认证) + Password string `mapstructure:"password"` // 密码(用于基本认证) + SudoUser string `mapstructure:"sudo_user"` // sudo 用户 + TOTP string `mapstructure:"totp"` // TOTP 令牌 Secret string `mapstructure:"webhook_secret"` // webhook 密钥 APIBase string `mapstructure:"api_base"` // API 基础 URL SignatureHeader string `mapstructure:"signature_header"` // webhook 签名的 header 名称 @@ -148,7 +152,10 @@ func Save(newConfig *Config) error { if err := yamlEncoder.Encode(configMap); err != nil { // 如果保存失败,恢复备份 if _, err := os.Stat(backupFile); err == nil { - os.Rename(backupFile, "config.yaml") + err := os.Rename(backupFile, "config.yaml") + if err != nil { + return err + } } return fmt.Errorf("save config failed: %w", err) } @@ -188,6 +195,10 @@ func (c *Config) ToMap() map[string]interface{} { "name": platform.Name, "type": platform.Type, "token": platform.Token, + "username": platform.Username, + "password": platform.Password, + "sudo_user": platform.SudoUser, + "totp": platform.TOTP, "webhook_secret": platform.Secret, "api_base": platform.APIBase, "signature_header": platform.SignatureHeader, @@ -237,6 +248,7 @@ func (c *Config) ToMapHtml() map[string]interface{} { "name": platform.Name, "type": platform.Type, "token": platform.Token, + "username": platform.Username, "webhook_secret": platform.Secret, "api_base": platform.APIBase, "signature_header": platform.SignatureHeader, diff --git a/handlers/webhook.go b/handlers/webhook.go index 753afca..44754a9 100644 --- a/handlers/webhook.go +++ b/handlers/webhook.go @@ -112,29 +112,36 @@ func (h *WebhookHandler) parseWebhookEvent(c *gin.Context, platform string) (ser return nil, fmt.Errorf("未找到事件类型 header: %s", platformConfig.EventHeader) } + // 创建认证配置 + auth := &platforms.AuthConfig{ + Token: platformConfig.Token, + Username: platformConfig.Username, + Password: platformConfig.Password, + SudoUser: platformConfig.SudoUser, + TOTP: platformConfig.TOTP, + UseBasicAuth: platformConfig.Username != "" && platformConfig.Password != "", + UseSudoHeader: platformConfig.SudoUser != "", + UseSudoParam: platformConfig.SudoUser != "", + UseTOTPHeader: platformConfig.TOTP != "", + } + var event services.WebhookEvent switch platformConfig.Type { - case "gogs": - event = platforms.NewGogsEvent( - platformConfig.APIBase, - platformConfig.Token, - eventType, - ) case "gitea": event = platforms.NewGiteaEvent( platformConfig.APIBase, - platformConfig.Token, + auth, eventType, ) - //case "gitee": - // event = &platforms.GiteeEvent{ - // ApiBase: platformConfig.APIBase, - // Token: platformConfig.Token, - // } + // case "gitee": + // event = platforms.NewGiteeEvent( + // platformConfig.APIBase, + // auth, + // ) case "gitlab": event = platforms.NewGitlabEvent( platformConfig.APIBase, - platformConfig.Token, + auth, eventType, ) default: diff --git a/services/platforms/client.go b/services/platforms/client.go index e1f738c..534530f 100644 --- a/services/platforms/client.go +++ b/services/platforms/client.go @@ -9,20 +9,43 @@ import ( "net/http" ) +type AuthConfig struct { + Token string + Username string + Password string + SudoUser string + TOTP string + UseBasicAuth bool + UseSudoHeader bool + UseSudoParam bool + UseTOTPHeader bool +} + type httpClient struct { url string - token string + auth *AuthConfig client *http.Client } -func newHTTPClient(baseURL, token string) *httpClient { +func newHTTPClient(baseURL string, authConfig *AuthConfig) *httpClient { return &httpClient{ url: baseURL, - token: token, + auth: authConfig, client: &http.Client{}, } } +func (c *httpClient) setAuthHeaders(req *http.Request) { + if c.auth == nil { + return + } + + // 设置 Token 认证 + if c.auth.Token != "" { + req.Header.Set("Authorization", "token "+c.auth.Token) + } +} + func (c *httpClient) get(path string, result interface{}) error { url := fmt.Sprintf("%s%s", c.url, path) log.Printf("发送 GET 请求: url=%s", url) @@ -34,9 +57,7 @@ func (c *httpClient) get(path string, result interface{}) error { } req.Header.Set("Content-Type", "application/json") - if c.token != "" { - req.Header.Set("Authorization", "token "+c.token) - } + c.setAuthHeaders(req) resp, err := c.client.Do(req) if err != nil { @@ -77,9 +98,7 @@ func (c *httpClient) post(path string, data interface{}) error { } req.Header.Set("Content-Type", "application/json") - if c.token != "" { - req.Header.Set("Authorization", "token "+c.token) - } + c.setAuthHeaders(req) resp, err := c.client.Do(req) if err != nil { @@ -113,6 +132,8 @@ func (c *httpClient) getWithHeaders(path string, result interface{}, headers map req.Header.Set(key, value) } + c.setAuthHeaders(req) + resp, err := c.client.Do(req) if err != nil { log.Printf("发送 GET 请求失败: url=%s, error=%v", url, err) @@ -156,6 +177,8 @@ func (c *httpClient) postWithHeaders(path string, data interface{}, headers map[ req.Header.Set(key, value) } + c.setAuthHeaders(req) + resp, err := c.client.Do(req) if err != nil { log.Printf("发送 POST 请求失败: url=%s, error=%v", url, err) diff --git a/services/platforms/gitea.go b/services/platforms/gitea.go index e4b4404..d39ec08 100644 --- a/services/platforms/gitea.go +++ b/services/platforms/gitea.go @@ -10,40 +10,191 @@ import ( type GiteaEvent struct { apiBase string - token string + auth *AuthConfig Event string client *httpClient + Secret string `json:"secret"` Ref string `json:"ref"` Before string `json:"before"` After string `json:"after"` CompareURL string `json:"compare_url"` Commits []struct { - ID string `json:"id"` - Message string `json:"message"` - URL string `json:"url"` - Author Author `json:"author"` - Committer Author `json:"committer"` - Timestamp string `json:"timestamp"` + ID string `json:"sha"` + Message string `json:"commit.message"` + URL string `json:"url"` + HTMLURL string `json:"html_url"` + Created string `json:"created"` + Author struct { + Active bool `json:"active"` + AvatarURL string `json:"avatar_url"` + Created string `json:"created"` + Description string `json:"description"` + Email string `json:"email"` + FollowersCount int `json:"followers_count"` + FollowingCount int `json:"following_count"` + FullName string `json:"full_name"` + HTMLURL string `json:"html_url"` + ID int `json:"id"` + IsAdmin bool `json:"is_admin"` + Language string `json:"language"` + LastLogin string `json:"last_login"` + Location string `json:"location"` + Login string `json:"login"` + LoginName string `json:"login_name"` + ProhibitLogin bool `json:"prohibit_login"` + Restricted bool `json:"restricted"` + SourceID int `json:"source_id"` + Visibility string `json:"visibility"` + Website string `json:"website"` + } `json:"author"` + Committer struct { + Active bool `json:"active"` + AvatarURL string `json:"avatar_url"` + Created string `json:"created"` + Description string `json:"description"` + Email string `json:"email"` + FollowersCount int `json:"followers_count"` + FollowingCount int `json:"following_count"` + FullName string `json:"full_name"` + HTMLURL string `json:"html_url"` + ID int `json:"id"` + IsAdmin bool `json:"is_admin"` + Language string `json:"language"` + LastLogin string `json:"last_login"` + Location string `json:"location"` + Login string `json:"login"` + LoginName string `json:"login_name"` + ProhibitLogin bool `json:"prohibit_login"` + Restricted bool `json:"restricted"` + SourceID int `json:"source_id"` + Visibility string `json:"visibility"` + Website string `json:"website"` + } `json:"committer"` + Commit struct { + Author struct { + Date string `json:"date"` + Email string `json:"email"` + Name string `json:"name"` + } `json:"author"` + Committer struct { + Date string `json:"date"` + Email string `json:"email"` + Name string `json:"name"` + } `json:"committer"` + Message string `json:"message"` + Tree struct { + Created string `json:"created"` + SHA string `json:"sha"` + URL string `json:"url"` + } `json:"tree"` + URL string `json:"url"` + Verification struct { + Payload string `json:"payload"` + Reason string `json:"reason"` + Signature string `json:"signature"` + Signer struct { + Email string `json:"email"` + Name string `json:"name"` + Username string `json:"username"` + } `json:"signer"` + Verified bool `json:"verified"` + } `json:"verification"` + } `json:"commit"` + Files []struct { + Filename string `json:"filename"` + Status string `json:"status"` + } `json:"files"` + Parents []struct { + Created string `json:"created"` + SHA string `json:"sha"` + URL string `json:"url"` + } `json:"parents"` + Stats struct { + Additions int `json:"additions"` + Deletions int `json:"deletions"` + Total int `json:"total"` + } `json:"stats"` } `json:"commits"` Repository struct { - ID int `json:"id"` - Name string `json:"name"` - FullName string `json:"full_name"` - HTMLURL string `json:"html_url"` - Private bool `json:"private"` + ID int `json:"id"` + Name string `json:"name"` + FullName string `json:"full_name"` + Description string `json:"description"` + Private bool `json:"private"` + Fork bool `json:"fork"` + HTMLURL string `json:"html_url"` + SSHURL string `json:"ssh_url"` + CloneURL string `json:"clone_url"` + Website string `json:"website"` + StarsCount int `json:"stars_count"` + ForksCount int `json:"forks_count"` + WatchersCount int `json:"watchers_count"` + OpenIssuesCount int `json:"open_issues_count"` + DefaultBranch string `json:"default_branch"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` + Owner struct { + ID int `json:"id"` + Login string `json:"login"` + FullName string `json:"full_name"` + Email string `json:"email"` + AvatarURL string `json:"avatar_url"` + Username string `json:"username"` + } `json:"owner"` } `json:"repository"` + Pusher struct { + ID int `json:"id"` + Login string `json:"login"` + FullName string `json:"full_name"` + Email string `json:"email"` + AvatarURL string `json:"avatar_url"` + Username string `json:"username"` + } `json:"pusher"` + Sender struct { + ID int `json:"id"` + Login string `json:"login"` + FullName string `json:"full_name"` + Email string `json:"email"` + AvatarURL string `json:"avatar_url"` + Username string `json:"username"` + } `json:"sender"` } // NewGiteaEvent 创建 Gitea 事件 -func NewGiteaEvent(apiBase, token string, event string) *GiteaEvent { +func NewGiteaEvent(apiBase string, auth *AuthConfig, event string) *GiteaEvent { return &GiteaEvent{ apiBase: apiBase, - token: token, + auth: auth, Event: event, } } +// 定义 Gitea commit 响应的结构 +type giteaCommitResponse struct { + Commit struct { + Message string `json:"message"` + Author struct { + Date string `json:"date"` + Email string `json:"email"` + Name string `json:"name"` + } `json:"author"` + Committer struct { + Date string `json:"date"` + Email string `json:"email"` + Name string `json:"name"` + } `json:"committer"` + } `json:"commit"` + Files []struct { + Filename string `json:"filename"` + Status string `json:"status"` + Additions int `json:"additions"` + Deletions int `json:"deletions"` + Changes int `json:"changes"` + Content string `json:"content"` + } `json:"files"` +} + func (e *GiteaEvent) ExtractChanges() (*types.CodeChanges, error) { // 检查是否有提交记录 if len(e.Commits) == 0 { @@ -53,14 +204,14 @@ func (e *GiteaEvent) ExtractChanges() (*types.CodeChanges, error) { // 检查是否跳过代码审查 for _, commit := range e.Commits { - if strings.Contains(commit.Message, "[skip codereview]") { + if strings.Contains(commit.Commit.Message, "[skip codereview]") { log.Printf("跳过代码审查: commit=%s", commit.ID) return nil, nil } } if e.client == nil { - e.client = newHTTPClient(e.apiBase, e.token) + e.client = newHTTPClient(e.apiBase, e.auth) log.Printf("初始化 HTTP 客户端: url=%s", e.apiBase) } @@ -73,89 +224,46 @@ func (e *GiteaEvent) ExtractChanges() (*types.CodeChanges, error) { for _, commit := range e.Commits { // 直接获取 diff 内容 - apiPath := fmt.Sprintf("/api/v1/repos/%s/git/commits/%s.diff?access_token=%s", e.Repository.FullName, commit.ID, e.token) - var diffContent string + apiPath := fmt.Sprintf("/api/v1/repos/%s/%s/git/commits/%s", e.Repository.Owner.Login, e.Repository.Name, e.After) + var diffContent giteaCommitResponse if err := e.client.get(apiPath, &diffContent); err != nil { log.Printf("获取提交详情失败: commit=%s, error=%v", commit.ID, err) continue } - // 去除首尾空白 - diffContent = strings.TrimSpace(diffContent) - if diffContent == "" { - log.Printf("提交没有变更内容: commit=%s", commit.ID) - continue - } - - // 解析 diff 内容 - diffBlocks := strings.Split(diffContent, "diff --git ") - // 去除空块 - for _, block := range diffBlocks { - if strings.TrimSpace(block) == "" { - continue - } - - // 移除 'diff --git ' 前缀 - block = strings.TrimPrefix(block, "diff --git ") - - // 解析文件名和变更类型 - lines := strings.Split(block, "\n") - if len(lines) < 2 { - continue - } - - // 获取文件名 - filename := "" - status := "modified" - for _, line := range lines { - if strings.HasPrefix(line, "--- a/") { - filename = strings.TrimPrefix(line, "--- a/") - break - } else if strings.HasPrefix(line, "+++ b/") { - filename = strings.TrimPrefix(line, "+++ b/") - break - } - } - - // 如果没有找到文件名,跳过 - if filename == "" { - continue - } - - // 确定变更类型 - if strings.Contains(block, "new file mode") { - status = "added" - } else if strings.Contains(block, "deleted file mode") { - status = "deleted" - } else if strings.Contains(block, "rename from") { - status = "renamed" - } - + // 处理每个文件的变更 + for _, file := range diffContent.Files { var content strings.Builder content.WriteString(fmt.Sprintf("### 变更说明\n")) - content.WriteString(fmt.Sprintf("提交信息: %s\n\n", commit.Message)) + content.WriteString(fmt.Sprintf("提交信息: %s\n\n", diffContent.Commit.Message)) - switch status { + status := "modified" + switch file.Status { case "added": - content.WriteString(fmt.Sprintf("新增文件: %s\n\n", filename)) - case "modified": - content.WriteString(fmt.Sprintf("修改文件: %s\n\n", filename)) - case "deleted": - content.WriteString(fmt.Sprintf("删除文件: %s\n\n", filename)) + status = "added" + content.WriteString(fmt.Sprintf("新增文件: %s\n\n", file.Filename)) + case "removed": + status = "deleted" + content.WriteString(fmt.Sprintf("删除文件: %s\n\n", file.Filename)) case "renamed": - content.WriteString(fmt.Sprintf("重命名文件: %s\n\n", filename)) + status = "renamed" + content.WriteString(fmt.Sprintf("重命名文件: %s\n\n", file.Filename)) + default: + content.WriteString(fmt.Sprintf("修改文件: %s\n\n", file.Filename)) } - content.WriteString("### 变更内容\n") - content.WriteString("```diff\n") - content.WriteString(block) - content.WriteString("\n```\n") + if file.Content != "" { + content.WriteString("### 变更内容\n") + content.WriteString("```diff\n") + content.WriteString(file.Content) + content.WriteString("\n```\n") + } changes.Files = append(changes.Files, types.FileChange{ - Path: filename, + Path: file.Filename, Content: content.String(), - Type: parseFileType(status), + Type: utils.ParseFileType(status), }) } } @@ -165,11 +273,11 @@ func (e *GiteaEvent) ExtractChanges() (*types.CodeChanges, error) { func (e *GiteaEvent) PostComments(result *types.ReviewResult) error { if e.client == nil { - e.client = newHTTPClient(e.apiBase, e.token) + e.client = newHTTPClient(e.apiBase, e.auth) } // 创建 issue - path := fmt.Sprintf("/api/v1/repos/%s/issues", e.Repository.FullName) + path := fmt.Sprintf("/api/v1/repos/%s/%s/issues", e.Repository.Owner.Login, e.Repository.Name) issueData := map[string]interface{}{ "title": fmt.Sprintf("AI 代码审查 - %s", e.After[:7]), "body": utils.FormatReviewResult(result), diff --git a/services/platforms/gitee.go b/services/platforms/gitee.go index aead45e..92cf935 100644 --- a/services/platforms/gitee.go +++ b/services/platforms/gitee.go @@ -2,11 +2,15 @@ package platforms import ( "code-review/services/types" + "code-review/utils" "fmt" ) // GiteeEvent Gitee 平台的 webhook 事件 type GiteeEvent struct { + apiBase string + auth *AuthConfig + Event string client *httpClient Action string `json:"action"` ActionDesc string `json:"action_desc"` @@ -43,9 +47,11 @@ type GiteeEvent struct { } `json:"pull_request"` } -func NewGiteeEvent(baseURL, token string) *GiteeEvent { +func NewGiteeEvent(baseURL string, auth *AuthConfig) *GiteeEvent { return &GiteeEvent{ - client: newHTTPClient(baseURL, token), + apiBase: baseURL, + auth: auth, + client: newHTTPClient(baseURL, auth), } } @@ -71,16 +77,7 @@ func (e *GiteeEvent) ExtractChanges() (*types.CodeChanges, error) { OldPath: change.OldPath, } - switch change.Type { - case "added": - fileChange.Type = types.Added - case "modified": - fileChange.Type = types.Modified - case "deleted": - fileChange.Type = types.Deleted - case "renamed": - fileChange.Type = types.Renamed - } + fileChange.Type = utils.ParseFileType(change.Type) changes.Files = append(changes.Files, fileChange) } @@ -90,12 +87,12 @@ func (e *GiteeEvent) ExtractChanges() (*types.CodeChanges, error) { func (e *GiteeEvent) PostComments(result *types.ReviewResult) error { if e.client == nil { - return fmt.Errorf("client not initialized") + e.client = newHTTPClient(e.apiBase, e.auth) } for _, comment := range result.Comments { body := map[string]interface{}{ - "access_token": e.client.token, + "access_token": e.auth.Token, "body": fmt.Sprintf("**Code Review Comment**\n\nFile: %s\nLine: %d\nSeverity: %s\n\n%s", comment.Path, comment.Line, @@ -119,7 +116,7 @@ func (e *GiteeEvent) PostComments(result *types.ReviewResult) error { // 发送总结评论 if result.Summary != "" { body := map[string]interface{}{ - "access_token": e.client.token, + "access_token": e.auth.Token, "body": fmt.Sprintf("**Code Review Summary**\n\n%s", result.Summary), } diff --git a/services/platforms/gitlab.go b/services/platforms/gitlab.go index 1d00a8f..a2ebf3a 100644 --- a/services/platforms/gitlab.go +++ b/services/platforms/gitlab.go @@ -11,7 +11,7 @@ import ( // GitlabEvent Gitlab 平台的 webhook 事件 type GitlabEvent struct { apiBase string - token string + auth *AuthConfig Event string client *httpClient @@ -34,10 +34,10 @@ type GitlabEvent struct { } // NewGitlabEvent 创建 GitLab 事件实例 -func NewGitlabEvent(baseURL, token string, event string) *GitlabEvent { +func NewGitlabEvent(baseURL string, auth *AuthConfig, event string) *GitlabEvent { return &GitlabEvent{ apiBase: baseURL, - token: token, + auth: auth, Event: event, } } @@ -70,7 +70,7 @@ func (e *GitlabEvent) ExtractChanges() (*types.CodeChanges, error) { } if e.client == nil { - e.client = newHTTPClient(e.apiBase, e.token) + e.client = newHTTPClient(e.apiBase, e.auth) log.Printf("初始化 HTTP 客户端: url=%s", e.apiBase) } @@ -87,7 +87,7 @@ func (e *GitlabEvent) ExtractChanges() (*types.CodeChanges, error) { e.Project.ID, commit.ID) headers := map[string]string{ - "PRIVATE-TOKEN": e.token, + "PRIVATE-TOKEN": e.auth.Token, } var diffs []gitlabDiff @@ -109,7 +109,7 @@ func (e *GitlabEvent) ExtractChanges() (*types.CodeChanges, error) { changes.Files = append(changes.Files, types.FileChange{ Path: diff.NewPath, Content: diff.Diff, - Type: parseFileType(status), + Type: utils.ParseFileType(status), }) } } @@ -119,7 +119,7 @@ func (e *GitlabEvent) ExtractChanges() (*types.CodeChanges, error) { func (e *GitlabEvent) PostComments(result *types.ReviewResult) error { if e.client == nil { - e.client = newHTTPClient(e.apiBase, e.token) + e.client = newHTTPClient(e.apiBase, e.auth) } // 创建 issue @@ -130,7 +130,7 @@ func (e *GitlabEvent) PostComments(result *types.ReviewResult) error { } headers := map[string]string{ - "PRIVATE-TOKEN": e.token, + "PRIVATE-TOKEN": e.auth.Token, } if err := e.client.postWithHeaders(path, issueData, headers); err != nil { diff --git a/services/platforms/gogs.go b/services/platforms/gogs.go deleted file mode 100644 index 2652c64..0000000 --- a/services/platforms/gogs.go +++ /dev/null @@ -1,245 +0,0 @@ -package platforms - -import ( - "code-review/services/types" - "fmt" - "log" - "strings" -) - -// GogsEvent Gogs 平台的 webhook 事件 -type GogsEvent struct { - apiBase string - token string - Event string // 事件类型 - client *httpClient - Ref string `json:"ref"` - Before string `json:"before"` - After string `json:"after"` - CompareURL string `json:"compare_url"` - Commits []struct { - ID string `json:"id"` - Message string `json:"message"` - URL string `json:"url"` - Author Author `json:"author"` - Committer Author `json:"committer"` - Timestamp string `json:"timestamp"` - } `json:"commits"` - CommitDetail struct { - Files []struct { - Filename string `json:"filename"` - Status string `json:"status"` - Additions int `json:"additions"` - Deletions int `json:"deletions"` - Patch string `json:"patch"` - } `json:"files"` - } - Repository struct { - ID int `json:"id"` - Owner Author `json:"owner"` - Name string `json:"name"` - FullName string `json:"full_name"` - Description string `json:"description"` - Private bool `json:"private"` - Fork bool `json:"fork"` - HTMLURL string `json:"html_url"` - SSHURL string `json:"ssh_url"` - CloneURL string `json:"clone_url"` - Website string `json:"website"` - StarsCount int `json:"stars_count"` - ForksCount int `json:"forks_count"` - WatchersCount int `json:"watchers_count"` - OpenIssuesCount int `json:"open_issues_count"` - DefaultBranch string `json:"default_branch"` - CreatedAt string `json:"created_at"` - UpdatedAt string `json:"updated_at"` - } `json:"repository"` - Pusher Author `json:"pusher"` - Sender Author `json:"sender"` -} - -type Author struct { - ID int `json:"id"` - Login string `json:"login"` - FullName string `json:"full_name"` - Email string `json:"email"` - AvatarURL string `json:"avatar_url"` - Username string `json:"username"` -} - -// NewGogsEvent 创建 Gogs 事件 -func NewGogsEvent(apiBase, token string, event string) *GogsEvent { - return &GogsEvent{ - apiBase: apiBase, - token: token, - Event: event, - } -} - -func (e *GogsEvent) ExtractChanges() (*types.CodeChanges, error) { - // 检查是否有提交记录 - if len(e.Commits) == 0 { - log.Printf("没有提交记录,跳过代码审查") - return nil, nil - } - - // 检查是否跳过代码审查 - for _, commit := range e.Commits { - if strings.Contains(commit.Message, "[skip codereview]") { - log.Printf("跳过代码审查: commit=%s", commit.ID) - return nil, nil - } - } - - if e.client == nil { - e.client = newHTTPClient(e.apiBase, e.token) - log.Printf("初始化 HTTP 客户端: url=%s", e.apiBase) - } - - changes := &types.CodeChanges{ - Repository: e.Repository.FullName, - Branch: e.Ref, - CommitID: e.After, - Files: make([]types.FileChange, 0), - } - - for _, commit := range e.Commits { - // 直接获取 diff 内容 - apiPath := fmt.Sprintf("/api/v1/repos/%s/git/commits/%s.diff?access_token=%s", e.Repository.FullName, commit.ID, e.token) - var diffContent string - - if err := e.client.get(apiPath, &diffContent); err != nil { - log.Printf("获取提交详情失败: commit=%s, error=%v", commit.ID, err) - continue - } - - // 去除首尾空白 - diffContent = strings.TrimSpace(diffContent) - if diffContent == "" { - log.Printf("提交没有变更内容: commit=%s", commit.ID) - continue - } - - // 解析 diff 内容 - diffBlocks := strings.Split(diffContent, "diff --git ") - // 去除空块 - for _, block := range diffBlocks { - if strings.TrimSpace(block) == "" { - continue - } - - // 移除 'diff --git ' 前缀 - block = strings.TrimPrefix(block, "diff --git ") - - // 解析文件名和变更类型 - lines := strings.Split(block, "\n") - if len(lines) < 2 { - continue - } - - // 获取文件名 - filename := "" - status := "modified" - for _, line := range lines { - if strings.HasPrefix(line, "--- a/") { - filename = strings.TrimPrefix(line, "--- a/") - break - } else if strings.HasPrefix(line, "+++ b/") { - filename = strings.TrimPrefix(line, "+++ b/") - break - } - } - - // 如果没有找到文件名,跳过 - if filename == "" { - continue - } - - // 确定变更类型 - if strings.Contains(block, "new file mode") { - status = "added" - } else if strings.Contains(block, "deleted file mode") { - status = "deleted" - } else if strings.Contains(block, "rename from") { - status = "renamed" - } - - var content strings.Builder - content.WriteString(fmt.Sprintf("### 变更说明\n")) - content.WriteString(fmt.Sprintf("提交信息: %s\n\n", commit.Message)) - - switch status { - case "added": - content.WriteString(fmt.Sprintf("新增文件: %s\n\n", filename)) - case "modified": - content.WriteString(fmt.Sprintf("修改文件: %s\n\n", filename)) - case "deleted": - content.WriteString(fmt.Sprintf("删除文件: %s\n\n", filename)) - case "renamed": - content.WriteString(fmt.Sprintf("重命名文件: %s\n\n", filename)) - } - - content.WriteString("### 变更内容\n") - content.WriteString("```diff\n") - content.WriteString(block) - content.WriteString("\n```\n") - - changes.Files = append(changes.Files, types.FileChange{ - Path: filename, - Content: content.String(), - Type: parseFileType(status), - }) - } - } - - return changes, nil -} - -func (e *GogsEvent) PostComments(result *types.ReviewResult) error { - if e.client == nil { - e.client = newHTTPClient(e.apiBase, e.token) - log.Printf("初始化 HTTP 客户端: url=%s", e.apiBase) - } - - // 创建 issue 评论 - issueBody := fmt.Sprintf("**代码审查报告**\n\n提交: %s\n\n", e.After) - for _, comment := range result.Comments { - issueBody += fmt.Sprintf("### 文件: %s\n\n%s\n\n", comment.Path, comment.Content) - } - if result.Summary != "" { - issueBody += fmt.Sprintf("\n### 总结\n\n%s", result.Summary) - } - - // 创建 issue - createIssuePath := fmt.Sprintf("/api/v1/repos/%s/issues", e.Repository.FullName) - issueData := map[string]interface{}{ - "title": fmt.Sprintf("代码审查: %s", e.After[:7]), - "body": issueBody, - } - - if err := e.client.post(createIssuePath, issueData); err != nil { - log.Printf("创建 issue 失败: path=%s, error=%v", createIssuePath, err) - return fmt.Errorf("创建 issue 失败: %w", err) - } - - log.Printf("成功创建 issue: path=%s, commitID=%s", createIssuePath, e.After[:7]) - - return nil -} - -func (e *GogsEvent) GetPlatform() string { - return "gogs" -} - -func parseFileType(t string) types.ChangeType { - switch t { - case "add": - return types.Added - case "modify": - return types.Modified - case "delete": - return types.Deleted - default: - return types.Modified - } -} diff --git a/static/index.html b/static/index.html index cd7e722..c2d7f4f 100644 --- a/static/index.html +++ b/static/index.html @@ -399,7 +399,7 @@ ${createPasswordField(`git[${index}].token`, platform.token, 'Token', '请输入平台访问令牌')} @@ -510,8 +510,8 @@ token: platform.token, webhook_secret: platform.webhook_secret, api_base: platform.api_base, - signature_header: platform.type === 'gitlab' ? 'X-Gitlab-Token' : 'X-Gitee-Token', - event_header: platform.type === 'gitlab' ? 'X-Gitlab-Event' : 'X-Gitee-Event' + signature_header: platform.type === 'gitlab' ? 'X-Gitlab-Token' : 'X-Gitea-Signature', + event_header: platform.type === 'gitlab' ? 'X-Gitlab-Event' : 'X-Gitea-Event' })) }; } diff --git a/utils/utils.go b/utils/utils.go index 20ca9b2..05d6a8f 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -87,3 +87,19 @@ func FormatReviewResult(result *types.ReviewResult) string { return body.String() } + +// ParseFileType 将文件变更类型字符串转换为 ChangeType +func ParseFileType(t string) types.ChangeType { + switch t { + case "add", "added": + return types.Added + case "modify", "modified": + return types.Modified + case "delete", "deleted": + return types.Deleted + case "renamed": + return types.Renamed + default: + return types.Modified + } +}