gitea获取diff更改 dockerfile更改 日志输出至文件

This commit is contained in:
Hua
2025-04-29 11:30:15 +08:00
parent e5cf1c348c
commit 5a3117dc72
8 changed files with 220 additions and 65 deletions

View File

@ -22,14 +22,20 @@ WORKDIR /app
# 从构建阶段复制二进制文件 # 从构建阶段复制二进制文件
COPY --from=builder /app/ai-code-review /app/ COPY --from=builder /app/ai-code-review /app/
# 复制静态文件目录
COPY --from=builder /app/static /app/static
# 创建默认配置文件到临时位置 # 创建默认配置文件到临时位置
RUN echo 'port: 53321\nadmin_token: "token"\n\nauto_disable:\n enabled: true\n max_failures: 3\n reset_after: 30\n\nais: []\n\ngit: []' > /app/config.default.yaml RUN echo 'port: 53321\nadmin_token: "token"\n\nauto_disable:\n enabled: true\n max_failures: 3\n reset_after: 30\n\nais: []\n\ngit: []' > /app/config.default.yaml
# 创建日志目录并设置权限
RUN mkdir -p /app/logs && chmod 755 /app/logs
# 暴露端口 # 暴露端口
EXPOSE 53321 EXPOSE 53321
# 设置卷挂载点 # 设置卷挂载点
VOLUME ["/app/config.yaml"] VOLUME ["/app/config.yaml", "/app/logs"]
# 运行应用,如果配置文件不存在则使用默认配置 # 运行应用,如果配置文件不存在则使用默认配置
CMD ["sh", "-c", "if [ ! -f /app/config.yaml ]; then cp /app/config.default.yaml /app/config.yaml; fi && /app/ai-code-review"] CMD ["sh", "-c", "if [ ! -f /app/config.yaml ]; then cp /app/config.default.yaml /app/config.yaml; fi && /app/ai-code-review"]

View File

@ -5,6 +5,7 @@ import (
"code-review/handlers" "code-review/handlers"
"code-review/services" "code-review/services"
"code-review/services/ai" "code-review/services/ai"
"code-review/utils"
"fmt" "fmt"
"log" "log"
@ -12,6 +13,12 @@ import (
) )
func main() { func main() {
// 初始化日志系统
if err := utils.InitLogger(); err != nil {
log.Fatalf("初始化日志系统失败: %v", err)
}
defer utils.CloseLogger()
if err := config.LoadConfig("config.yaml"); err != nil { if err := config.LoadConfig("config.yaml"); err != nil {
log.Fatalf("加载配置失败: %v", err) log.Fatalf("加载配置失败: %v", err)
} }

View File

@ -45,6 +45,7 @@ func (c *Client) Chat(systemMsg, prompt string) (string, error) {
var response string var response string
var err error var err error
log.Printf("AImodel=%s, prompt=%s", c.model, prompt)
if c.aiType == "ollama" { if c.aiType == "ollama" {
response, err = c.ollamaChat(systemMsg, prompt) response, err = c.ollamaChat(systemMsg, prompt)
} else { } else {

View File

@ -195,3 +195,38 @@ func (c *httpClient) postWithHeaders(path string, data interface{}, headers map[
log.Printf("POST 请求成功: url=%s", url) log.Printf("POST 请求成功: url=%s", url)
return nil return nil
} }
func (c *httpClient) getRaw(path string) (string, error) {
url := fmt.Sprintf("%s%s", c.url, path)
log.Printf("发送 GET 请求: url=%s", url)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
log.Printf("创建 GET 请求失败: url=%s, error=%v", url, err)
return "", fmt.Errorf("创建请求失败: %w", err)
}
c.setAuthHeaders(req)
resp, err := c.client.Do(req)
if err != nil {
log.Printf("发送 GET 请求失败: url=%s, error=%v", url, err)
return "", fmt.Errorf("发送请求失败: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
log.Printf("GET 请求返回错误状态码: url=%s, status=%d, response=%s", url, resp.StatusCode, string(body))
return "", fmt.Errorf("请求失败,状态码: %d响应: %s", resp.StatusCode, string(body))
}
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Printf("读取响应失败: url=%s, error=%v", url, err)
return "", fmt.Errorf("读取响应失败: %w", err)
}
log.Printf("GET 请求成功: url=%s", url)
return string(body), nil
}

View File

@ -172,27 +172,7 @@ func NewGiteaEvent(apiBase string, auth *AuthConfig, event string) *GiteaEvent {
// 定义 Gitea commit 响应的结构 // 定义 Gitea commit 响应的结构
type giteaCommitResponse struct { type giteaCommitResponse struct {
Commit struct { Diff string `json:"diff"`
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) { func (e *GiteaEvent) ExtractChanges() (*types.CodeChanges, error) {
@ -202,14 +182,6 @@ func (e *GiteaEvent) ExtractChanges() (*types.CodeChanges, error) {
return nil, nil return nil, nil
} }
// 检查是否跳过代码审查
for _, commit := range e.Commits {
if strings.Contains(commit.Commit.Message, "[skip codereview]") {
log.Printf("跳过代码审查: commit=%s", commit.ID)
return nil, nil
}
}
if e.client == nil { if e.client == nil {
e.client = newHTTPClient(e.apiBase, e.auth) e.client = newHTTPClient(e.apiBase, e.auth)
log.Printf("初始化 HTTP 客户端: url=%s", e.apiBase) log.Printf("初始化 HTTP 客户端: url=%s", e.apiBase)
@ -223,48 +195,69 @@ func (e *GiteaEvent) ExtractChanges() (*types.CodeChanges, error) {
} }
for _, commit := range e.Commits { for _, commit := range e.Commits {
// 直接获取 diff 内容 // 检查提交信息是否包含跳过标记
apiPath := fmt.Sprintf("/api/v1/repos/%s/%s/git/commits/%s", e.Repository.Owner.Login, e.Repository.Name, e.After) if strings.Contains(commit.Message, "[skip codereview]") {
var diffContent giteaCommitResponse log.Printf("提交包含跳过标记,跳过所有文件审查: commit=%s", commit.ID)
continue
}
if err := e.client.get(apiPath, &diffContent); err != nil { // 检查是否是合并提交
if strings.HasPrefix(commit.Message, "Merge remote-tracking branch") ||
strings.HasPrefix(commit.Message, "Merge branch") {
log.Printf("跳过合并提交的文件审查: commit=%s", commit.ID)
continue
}
// 获取 diff 内容
apiPath := fmt.Sprintf("/api/v1/repos/%s/%s/git/commits/%s.diff", e.Repository.Owner.Login, e.Repository.Name, e.After)
diffContent, err := e.client.getRaw(apiPath)
if err != nil {
log.Printf("获取提交详情失败: commit=%s, error=%v", commit.ID, err) log.Printf("获取提交详情失败: commit=%s, error=%v", commit.ID, err)
continue continue
} }
// 处理每个文件的变更 // 解析 diff 内容
for _, file := range diffContent.Files { diffLines := strings.Split(diffContent, "\n")
var content strings.Builder var currentFile *types.FileChange
content.WriteString(fmt.Sprintf("### 变更说明\n")) var currentContent strings.Builder
content.WriteString(fmt.Sprintf("提交信息: %s\n\n", diffContent.Commit.Message))
status := "modified" for _, line := range diffLines {
switch file.Status { if strings.HasPrefix(line, "diff --git") {
case "added": // 保存前一个文件的内容
status = "added" if currentFile != nil {
content.WriteString(fmt.Sprintf("新增文件: %s\n\n", file.Filename)) currentFile.Content = currentContent.String() + "```\n"
case "removed": changes.Files = append(changes.Files, *currentFile)
status = "deleted" }
content.WriteString(fmt.Sprintf("删除文件: %s\n\n", file.Filename))
case "renamed": // 解析文件名
status = "renamed" parts := strings.Split(line, " ")
content.WriteString(fmt.Sprintf("重命名文件: %s\n\n", file.Filename)) if len(parts) >= 3 {
default: filePath := strings.TrimPrefix(parts[2], "b/")
content.WriteString(fmt.Sprintf("修改文件: %s\n\n", file.Filename)) if shouldSkipFile(filePath) {
log.Printf("跳过文件审查: file=%s", filePath)
currentFile = nil
continue
}
currentFile = &types.FileChange{
Path: filePath,
Type: utils.ParseFileType("modified"),
}
currentContent.Reset()
currentContent.WriteString(fmt.Sprintf("### 变更说明\n"))
currentContent.WriteString(fmt.Sprintf("提交信息: %s\n\n", commit.Message))
currentContent.WriteString("### 变更内容\n")
currentContent.WriteString("```diff\n")
}
} else if currentFile != nil {
currentContent.WriteString(line + "\n")
} }
}
if file.Content != "" { // 保存最后一个文件的内容
content.WriteString("### 变更内容\n") if currentFile != nil {
content.WriteString("```diff\n") currentFile.Content = currentContent.String() + "```\n"
content.WriteString(file.Content) changes.Files = append(changes.Files, *currentFile)
content.WriteString("\n```\n")
}
changes.Files = append(changes.Files, types.FileChange{
Path: file.Filename,
Content: content.String(),
Type: utils.ParseFileType(status),
})
} }
} }
@ -295,3 +288,24 @@ func (e *GiteaEvent) PostComments(result *types.ReviewResult) error {
func (e *GiteaEvent) GetPlatform() string { func (e *GiteaEvent) GetPlatform() string {
return "gitea" return "gitea"
} }
// 判断是否应该跳过文件审查
func shouldSkipFile(filename string) bool {
// 跳过特定文件类型
skipExtensions := []string{".md", ".txt", ".json", ".yaml", ".yml", ".lock"}
for _, ext := range skipExtensions {
if strings.HasSuffix(filename, ext) {
return true
}
}
// 跳过特定目录
skipDirs := []string{"node_modules/", "dist/", "build/", "vendor/"}
for _, dir := range skipDirs {
if strings.Contains(filename, dir) {
return true
}
}
return false
}

View File

@ -4,6 +4,8 @@ import (
"code-review/services/types" "code-review/services/types"
"code-review/utils" "code-review/utils"
"fmt" "fmt"
"log"
"strings"
) )
// GiteeEvent Gitee 平台的 webhook 事件 // GiteeEvent Gitee 平台的 webhook 事件
@ -70,6 +72,31 @@ func (e *GiteeEvent) ExtractChanges() (*types.CodeChanges, error) {
}, },
} }
// 过滤合并提交
validCommits := make([]struct {
ID string `json:"id"`
Message string `json:"message"`
}, 0)
for _, commit := range e.PullRequest.Commits {
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
}
// 更新最后的提交ID
if len(validCommits) > 0 {
changes.CommitID = validCommits[len(validCommits)-1].ID
}
for _, change := range e.PullRequest.Changes { for _, change := range e.PullRequest.Changes {
fileChange := types.FileChange{ fileChange := types.FileChange{
Path: change.Path, Path: change.Path,

View File

@ -62,11 +62,29 @@ func (e *GitlabEvent) ExtractChanges() (*types.CodeChanges, error) {
} }
// 检查是否跳过代码审查 // 检查是否跳过代码审查
validCommits := make([]struct {
ID string `json:"id"`
Message string `json:"message"`
Title string `json:"title"`
}, 0)
for _, commit := range e.Commits { for _, commit := range e.Commits {
if strings.Contains(commit.Message, "[skip codereview]") { if strings.Contains(commit.Message, "[skip codereview]") {
log.Printf("跳过代码审查: commit=%s", commit.ID) log.Printf("跳过代码审查: commit=%s", commit.ID)
return nil, nil 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 { if e.client == nil {
@ -81,7 +99,7 @@ func (e *GitlabEvent) ExtractChanges() (*types.CodeChanges, error) {
Files: make([]types.FileChange, 0), Files: make([]types.FileChange, 0),
} }
for _, commit := range e.Commits { for _, commit := range validCommits {
// 移除 URL 中的 token // 移除 URL 中的 token
apiPath := fmt.Sprintf("/api/v4/projects/%d/repository/commits/%s/diff", apiPath := fmt.Sprintf("/api/v4/projects/%d/repository/commits/%s/diff",
e.Project.ID, commit.ID) e.Project.ID, commit.ID)

47
utils/logger.go Normal file
View File

@ -0,0 +1,47 @@
package utils
import (
"fmt"
"io"
"log"
"os"
"path/filepath"
"time"
)
var (
// 日志文件句柄
logFile *os.File
// 日志目录
logDir = "logs"
)
// InitLogger 初始化日志系统
func InitLogger() error {
// 创建日志目录
if err := os.MkdirAll(logDir, 0755); err != nil {
return fmt.Errorf("创建日志目录失败: %w", err)
}
// 生成日志文件名
logFileName := filepath.Join(logDir, time.Now().Format("2006-01-02")+".log")
// 打开日志文件
file, err := os.OpenFile(logFileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
return fmt.Errorf("打开日志文件失败: %w", err)
}
// 设置日志输出
log.SetOutput(io.MultiWriter(os.Stdout, file))
logFile = file
return nil
}
// CloseLogger 关闭日志文件
func CloseLogger() {
if logFile != nil {
logFile.Close()
}
}