diff --git a/.dockerignore b/.dockerignore
deleted file mode 100644
index b7dab24..0000000
--- a/.dockerignore
+++ /dev/null
@@ -1,4 +0,0 @@
-config.yaml
-config.yaml.bak
-.idea/
-.git/
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index cc99b48..7813b76 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,3 +28,19 @@ go.work.sum
.idea
config.yaml.bak
+# Java
+*.class
+*.jar
+*.war
+*.ear
+*.zip
+*.tar.gz
+*.rar
+hs_err_pid*
+target/
+.idea/
+*.iml
+*.iws
+*.ipr
+.DS_Store
+
diff --git a/Dockerfile b/Dockerfile
deleted file mode 100644
index be7251a..0000000
--- a/Dockerfile
+++ /dev/null
@@ -1,41 +0,0 @@
-FROM golang:1.22-alpine AS builder
-
-WORKDIR /app
-
-# 复制go.mod和go.sum文件
-COPY go.mod go.sum ./
-
-# 下载依赖
-RUN go mod download
-
-# 复制源代码
-COPY . .
-
-# 构建应用
-RUN CGO_ENABLED=0 GOOS=linux go build -o /app/ai-code-review
-
-# 使用轻量级的基础镜像
-FROM alpine:latest
-
-WORKDIR /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 mkdir -p /app/logs && chmod 755 /app/logs
-
-# 暴露端口
-EXPOSE 53321
-
-# 设置卷挂载点
-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"]
\ No newline at end of file
diff --git a/README.md b/README.md
index d49bd72..2fc8e3b 100644
--- a/README.md
+++ b/README.md
@@ -1,80 +1,152 @@
-# ai-code-review
+# AI 代码审查服务
-一个基于AI的代码审查工具,支持多种Git平台和AI模型。
+这是一个基于 AI 的代码审查服务,支持多种 Git 平台和 AI 服务。
## 功能特点
-- 支持多个Git平台(GitLab、Gitee、Gogs等)
-- 支持多种AI模型(OpenAI、Ollama等)
-- 支持AI服务负载均衡
-- 提供Web界面进行配置管理
-- 支持自动禁用异常的AI服务
+- 支持多种 Git 平台:GitLab、Gitea
+- 支持多种 AI 服务:OpenAI、Ollama
+- 自动代码审查
+- 详细的审查报告
+- 可配置的审查规则
+- 支持自定义提示词
## 快速开始
-1. 下载并编译项目:
+### 环境要求
+
+- JDK 17+
+- Maven 3.8+
+- Git 平台(GitLab/Gitea)
+- AI 服务(OpenAI/Ollama)
+
+### 配置
+
+1. 复制配置文件模板:
+
```bash
-git clone https://github.com/your-username/ai-code-review.git
-cd ai-code-review
-go build
+cp src/main/resources/application.yml.template src/main/resources/application.yml
```
-2. 配置config.yaml:
+2. 编辑配置文件:
+
```yaml
-port: 53321
-admin_token: "your-admin-token" # 管理页面访问令牌
-ais:
- - name: "your-ai"
- type: "ollama" # 或 "openai"
- api_key: ""
- url: "http://localhost:11434" # AI服务地址
- model: "your-model" # 使用的模型名称
- temperature: 0
- stream: false
- priority: 0 # 添加优先级配置
- system_msg: |
- 你是一个代码审查员,你的职责是识别提交代码中的错误、性能问题和需要优化的地方。
- 你还负责提供建设性的反馈,并建议最佳实践来提高代码的整体质量。
+server:
+ port: 53321
+
+logging:
+ level:
+ com.codereview: DEBUG
+
+admin:
+ token: your-admin-token
+
+ai:
+ services:
+ - type: openai
+ enabled: true
+ api-key: your-openai-api-key
+ model: gpt-3.5-turbo
+ temperature: 0.7
+ system-msg: 你是一个代码审查助手
+ - type: ollama
+ enabled: false
+ url: http://localhost:11434
+ model: codellama
+ temperature: 0.7
+ system-msg: 你是一个代码审查助手
- 在审查代码时:
- - 审查代码变更(差异)并提供反馈
- - 仔细检查是否真的存在错误或需要优化的空间,突出显示它们
- - 不要突出显示小问题和细节
- - 如果有多个评论,请使用要点符号
- - 你不需要解释代码的功能
- - 请使用中文给出反馈
- - 如果你认为不需要优化或修改,请只回复 666
- weight: 1
- enabled: true
- auto_disable: true # 使用自己的自动禁用配置
- max_failures: 5 # 覆盖全局配置
- reset_after: 60 # 覆盖全局配置
git:
- - name: "your-git"
- type: "gitlab" # 或 "gitee", "gogs"
- token: "your-git-token"
- webhook_secret: "your-webhook-secret"
- api_base: "your-git-api-base"
- signature_header: X-Gitlab-Token
- event_header: X-Gitlab-Event
+ platforms:
+ - name: gitlab
+ type: gitlab
+ api-base: https://gitlab.com/api/v4
+ token: your-gitlab-token
+ webhook-secret: your-webhook-secret
+ - name: gitea
+ type: gitea
+ api-base: https://gitea.example.com/api/v1
+ token: your-gitea-token
+ webhook-secret: your-webhook-secret
```
-3. 运行服务:
+### 构建
+
```bash
-./ai-code-review
+mvn clean package
```
-4. 访问管理界面:
-- 打开浏览器访问 `http://localhost:53321`
-- 使用配置文件中的admin_token登录
-- 在Web界面中管理AI和Git平台配置
-5. 配置Git平台:
-- 在你的Git平台中添加Webhook
-- Webhook URL设置为: `http://your-server:53321/api/webhook/git-name`
-- 设置Webhook密钥与配置文件中的webhook_secret一致
+### 运行
-## 注意事项
+```bash
+java -jar target/ai-code-review.jar
+```
-- 请确保AI服务和Git平台的API地址可以正常访问
-- 妥善保管各类密钥和Token
-- 建议在生产环境中使用HTTPS
\ No newline at end of file
+## 使用指南
+
+### 配置 Git 平台 Webhook
+
+1. GitLab:
+ - 进入项目设置 -> Webhooks
+ - URL: `http://your-server:53321/webhook/gitlab`
+ - Secret Token: 配置文件中设置的 webhook-secret
+ - 触发事件: Push events
+
+2. Gitea:
+ - 进入仓库设置 -> Webhooks
+ - URL: `http://your-server:53321/webhook/gitea`
+ - Secret: 配置文件中设置的 webhook-secret
+ - 触发事件: Push events
+
+### 访问管理界面
+
+- URL: `http://your-server:53321/admin`
+- 使用配置文件中设置的 admin-token 进行认证
+
+## 开发指南
+
+### 项目结构
+
+```
+src/main/java/com/codereview/
+├── config/ # 配置类
+├── controller/ # 控制器
+├── service/ # 服务接口
+├── service/impl/ # 服务实现
+└── util/ # 工具类
+```
+
+### 添加新的 Git 平台支持
+
+1. 创建新的 WebhookEvent 实现类
+2. 在 WebhookController 中添加处理逻辑
+3. 更新配置文件结构
+
+### 添加新的 AI 服务支持
+
+1. 创建新的 AIService 实现类
+2. 在配置文件中添加服务配置
+3. 更新 AIServiceFactory
+
+## 常见问题
+
+1. Webhook 验证失败
+ - 检查 webhook-secret 配置
+ - 确认请求头中的签名
+
+2. AI 服务调用失败
+ - 检查 API 密钥配置
+ - 确认服务是否可用
+ - 查看日志获取详细信息
+
+## 贡献指南
+
+1. Fork 项目
+2. 创建特性分支
+3. 提交更改
+4. 推送到分支
+5. 创建 Pull Request
+
+## 许可证
+
+MIT License
\ No newline at end of file
diff --git a/config/config.go b/config/config.go
deleted file mode 100644
index c13881b..0000000
--- a/config/config.go
+++ /dev/null
@@ -1,360 +0,0 @@
-package config
-
-import (
- "fmt"
- "log"
- "math/rand"
- "os"
- "sort"
- "sync"
-
- "github.com/spf13/viper"
- "gopkg.in/yaml.v3"
-)
-
-var (
- cfg *Config
- once sync.Once
- mu sync.RWMutex
-
- aiBalancer *AIBalancer
- balancerMu sync.RWMutex
-)
-
-type Config struct {
- Port int `mapstructure:"port"`
- AdminToken string `mapstructure:"admin_token"` // 管理令牌
- AIs []AIConfig `mapstructure:"ais"`
- Git []GitConfig `mapstructure:"git"`
- AutoDisableConfig `mapstructure:"auto_disable"` // 全局自动禁用配置
-}
-
-// AutoDisableConfig 自动禁用配置
-type AutoDisableConfig struct {
- Enabled bool `mapstructure:"enabled"` // 是否启用自动禁用
- MaxFailures int `mapstructure:"max_failures"` // 最大失败次数
- ResetAfter int `mapstructure:"reset_after"` // 重置时间(分钟)
-}
-
-type AIConfig struct {
- Name string `mapstructure:"name"`
- Type string `mapstructure:"type"` // "ollama" 或 "openai"
- APIKey string `mapstructure:"api_key"`
- APIBase string `mapstructure:"url"`
- Model string `mapstructure:"model"`
- SystemMsg string `mapstructure:"system_msg"` // 系统提示词
- Temperature float64 `mapstructure:"temperature"` // 温度
- Stream bool `mapstructure:"stream"` // 是否使用流式响应
- Weight int `mapstructure:"weight"`
- Priority int `mapstructure:"priority"` // 优先级,数字越大优先级越高
- Enabled bool `mapstructure:"enabled"` // 是否启用
- AutoDisable bool `mapstructure:"auto_disable"` // 是否启用自动禁用(覆盖全局配置)
- MaxFailures *int `mapstructure:"max_failures"` // 最大失败次数(覆盖全局配置)
- ResetAfter *int `mapstructure:"reset_after"` // 重置时间(覆盖全局配置)
-}
-
-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 名称
- EventHeader string `mapstructure:"event_header"` // webhook 事件类型的 header 名称
-}
-
-// 添加一个新的结构体用于负载均衡
-type AIBalancer struct {
- ais []AIConfig
- current int
- mu sync.Mutex
-}
-
-func init() {
- // 设置默认值
- viper.SetDefault("port", 53321)
- viper.SetDefault("admin_token", "token")
- viper.SetDefault("ais", []interface{}{})
- viper.SetDefault("git", []interface{}{})
- viper.SetDefault("auto_disable", map[string]interface{}{
- "enabled": true,
- "max_failures": 3,
- "reset_after": 30,
- })
-}
-
-func Load() (*Config, error) {
- var err error
- once.Do(func() {
- viper.SetConfigName("config")
- viper.SetConfigType("yaml")
- viper.AddConfigPath(".")
-
- if err = viper.ReadInConfig(); err != nil {
- return
- }
-
- cfg = &Config{}
- if err = viper.Unmarshal(cfg); err != nil {
- return
- }
-
- // 添加日志输出以便调试
- log.Printf("已加载配置: %+v", cfg)
- log.Printf("使用的配置文件: %s", viper.ConfigFileUsed())
- })
-
- if err != nil {
- return nil, fmt.Errorf("加载配置失败: %w", err)
- }
-
- // 确保配置不为空
- if cfg == nil {
- return nil, fmt.Errorf("配置加载失败: 配置为空")
- }
-
- return cfg, nil
-}
-
-func GetConfig() *Config {
- if cfg == nil {
- panic("配置未初始化")
- }
- return cfg
-}
-
-// Save 保存配置到文件
-func Save(newConfig *Config) error {
- mu.Lock()
- defer mu.Unlock()
-
- // 创建备份
- backupFile := "config.yaml.bak"
- if _, err := os.Stat("config.yaml"); err == nil {
- if err := os.Rename("config.yaml", backupFile); err != nil {
- return fmt.Errorf("backup config failed: %w", err)
- }
- }
- // 使用自定义的 YAML 编码器
- file, err := os.Create("config.yaml")
- if err != nil {
- return fmt.Errorf("创建配置文件失败: %w", err)
- }
- yamlEncoder := yaml.NewEncoder(file)
- yamlEncoder.SetIndent(2) // 设置缩进
-
- // 将配置转换为 map 并保存
- configMap := newConfig.ToMap()
- if err := yamlEncoder.Encode(configMap); err != nil {
- // 如果保存失败,恢复备份
- if _, err := os.Stat(backupFile); err == nil {
- err := os.Rename(backupFile, "config.yaml")
- if err != nil {
- return err
- }
- }
- return fmt.Errorf("save config failed: %w", err)
- }
-
- // 更新内存中的配置
- cfg = newConfig
-
- return nil
-}
-
-// ToMap 将配置转换为 map
-func (c *Config) ToMap() map[string]interface{} {
- ais := make([]map[string]interface{}, len(c.AIs))
- for i, ai := range c.AIs {
- ais[i] = map[string]interface{}{
- "name": ai.Name,
- "type": ai.Type,
- "api_key": ai.APIKey,
- "url": ai.APIBase,
- "model": ai.Model,
- "system_msg": ai.SystemMsg,
- "temperature": ai.Temperature,
- "stream": ai.Stream,
- "weight": ai.Weight,
- "priority": ai.Priority,
- "enabled": ai.Enabled,
- "auto_disable": ai.AutoDisable,
- "max_failures": ai.MaxFailures,
- "reset_after": ai.ResetAfter,
- }
- }
-
- // 修改 Git 平台配置的转换逻辑
- platforms := make([]map[string]interface{}, len(c.Git))
- for i, platform := range c.Git {
- platforms[i] = 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,
- "event_header": platform.EventHeader,
- }
- }
-
- return map[string]interface{}{
- "port": c.Port,
- "admin_token": c.AdminToken,
- "ais": ais,
- "git": platforms,
- "auto_disable": map[string]interface{}{
- "enabled": c.AutoDisableConfig.Enabled,
- "max_failures": c.AutoDisableConfig.MaxFailures,
- "reset_after": c.AutoDisableConfig.ResetAfter,
- },
- }
-}
-
-// ToMapHtml 将配置转换为 map 页面展示
-func (c *Config) ToMapHtml() map[string]interface{} {
- ais := make([]map[string]interface{}, len(c.AIs))
- for i, ai := range c.AIs {
- ais[i] = map[string]interface{}{
- "name": ai.Name,
- "type": ai.Type,
- "api_key": ai.APIKey,
- "url": ai.APIBase,
- "model": ai.Model,
- "system_msg": ai.SystemMsg,
- "temperature": ai.Temperature,
- "stream": ai.Stream,
- "weight": ai.Weight,
- "priority": ai.Priority,
- "enabled": ai.Enabled,
- "auto_disable": ai.AutoDisable,
- "max_failures": ai.MaxFailures,
- "reset_after": ai.ResetAfter,
- }
- }
-
- // 修改 Git 平台配置的转换逻辑
- platforms := make([]map[string]interface{}, len(c.Git))
- for i, platform := range c.Git {
- platforms[i] = 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,
- "event_header": platform.EventHeader,
- }
- }
-
- return map[string]interface{}{
- "ais": ais,
- "git": platforms,
- "auto_disable": map[string]interface{}{
- "enabled": c.AutoDisableConfig.Enabled,
- "max_failures": c.AutoDisableConfig.MaxFailures,
- "reset_after": c.AutoDisableConfig.ResetAfter,
- },
- }
-}
-
-func LoadConfig(configFile string) error {
- log.Printf("加载配置文件: file=%s", configFile)
-
- viper.SetConfigFile(configFile)
- if err := viper.ReadInConfig(); err != nil {
- log.Printf("读取配置文件失败: file=%s, error=%v", configFile, err)
- return fmt.Errorf("读取配置文件失败: %w", err)
- }
-
- mu.Lock()
- defer mu.Unlock()
-
- if err := viper.Unmarshal(&cfg); err != nil {
- log.Printf("解析配置文件失败: file=%s, error=%v", configFile, err)
- return fmt.Errorf("解析配置文件失败: %w", err)
- }
-
- log.Printf("配置文件加载成功: file=%s", configFile)
- return nil
-}
-
-// 添加新的方法
-func NewAIBalancer(ais []AIConfig) *AIBalancer {
- // 过滤出已启用的 AI
- enabledAIs := make([]AIConfig, 0)
- for _, ai := range ais {
- if ai.Enabled {
- enabledAIs = append(enabledAIs, ai)
- }
- }
-
- // 按优先级排序(从高到低)
- sort.Slice(enabledAIs, func(i, j int) bool {
- return enabledAIs[i].Priority > enabledAIs[j].Priority
- })
-
- return &AIBalancer{
- ais: enabledAIs,
- current: 0,
- }
-}
-
-// 获取下一个可用的 AI 配置
-func (b *AIBalancer) Next() (*AIConfig, error) {
- b.mu.Lock()
- defer b.mu.Unlock()
-
- if len(b.ais) == 0 {
- return nil, fmt.Errorf("没有可用的 AI 配置")
- }
-
- // 获取最高优先级
- highestPriority := b.ais[0].Priority
-
- // 收集具有最高优先级的 AI
- highPriorityAIs := make([]AIConfig, 0)
- totalWeight := 0
- for _, ai := range b.ais {
- if ai.Priority == highestPriority {
- highPriorityAIs = append(highPriorityAIs, ai)
- totalWeight += ai.Weight
- }
- }
-
- // 在最高优先级的 AI 中按权重选择
- target := rand.Intn(totalWeight)
- currentWeight := 0
- for i, ai := range highPriorityAIs {
- currentWeight += ai.Weight
- if currentWeight > target {
- return &highPriorityAIs[i], nil
- }
- }
-
- // 如果没有选中任何一个(不应该发生),返回第一个最高优先级的 AI
- return &highPriorityAIs[0], nil
-}
-
-// ResetAIBalancer 重置 AI 负载均衡器
-func ResetAIBalancer(ais []AIConfig) {
- balancerMu.Lock()
- defer balancerMu.Unlock()
- aiBalancer = NewAIBalancer(ais)
-}
-
-// GetAIBalancer 获取当前的 AI 负载均衡器
-func GetAIBalancer() *AIBalancer {
- balancerMu.RLock()
- defer balancerMu.RUnlock()
- return aiBalancer
-}
diff --git a/docker-compose.yml b/docker-compose.yml
deleted file mode 100644
index 606bcf7..0000000
--- a/docker-compose.yml
+++ /dev/null
@@ -1,17 +0,0 @@
-version: '3.8'
-
-services:
- ai-code-review:
-# image: ai-code-review:lasted
- build:
- context: .
- dockerfile: Dockerfile
- container_name: ai-code-review
- ports:
- - "53321:53321"
-# volumes:
-# # 如果需要使用自定义配置文件,取消下面的注释并修改路径
-# - ./config.yaml:/app/config.yaml
- restart: unless-stopped
- environment:
- - TZ=Asia/Shanghai
\ No newline at end of file
diff --git a/go.mod b/go.mod
deleted file mode 100644
index d2389fc..0000000
--- a/go.mod
+++ /dev/null
@@ -1,55 +0,0 @@
-module code-review
-
-go 1.22.0
-
-toolchain go1.23.6
-
-require (
- github.com/gin-gonic/gin v1.10.0
- github.com/spf13/viper v1.19.0
-)
-
-require (
- github.com/bytedance/sonic v1.12.8 // indirect
- github.com/bytedance/sonic/loader v0.2.3 // indirect
- github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
- github.com/cloudwego/base64x v0.1.5 // indirect
- github.com/cloudwego/iasm v0.2.0 // indirect
- github.com/fsnotify/fsnotify v1.8.0 // indirect
- github.com/gabriel-vasile/mimetype v1.4.8 // indirect
- github.com/gin-contrib/sse v1.0.0 // indirect
- github.com/go-playground/locales v0.14.1 // indirect
- github.com/go-playground/universal-translator v0.18.1 // indirect
- github.com/go-playground/validator/v10 v10.24.0 // indirect
- github.com/goccy/go-json v0.10.5 // indirect
- github.com/hashicorp/hcl v1.0.0 // indirect
- github.com/json-iterator/go v1.1.12 // indirect
- github.com/klauspost/cpuid/v2 v2.2.9 // indirect
- github.com/leodido/go-urn v1.4.0 // indirect
- github.com/magiconair/properties v1.8.9 // indirect
- github.com/mattn/go-isatty v0.0.20 // indirect
- github.com/mitchellh/mapstructure v1.5.0 // indirect
- github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
- github.com/modern-go/reflect2 v1.0.2 // indirect
- github.com/pelletier/go-toml/v2 v2.2.3 // indirect
- github.com/sagikazarmark/locafero v0.7.0 // indirect
- github.com/sagikazarmark/slog-shim v0.1.0 // indirect
- github.com/sourcegraph/conc v0.3.0 // indirect
- github.com/spf13/afero v1.12.0 // indirect
- github.com/spf13/cast v1.7.1 // indirect
- github.com/spf13/pflag v1.0.6 // indirect
- github.com/subosito/gotenv v1.6.0 // indirect
- github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
- github.com/ugorji/go/codec v1.2.12 // indirect
- go.uber.org/atomic v1.11.0 // indirect
- go.uber.org/multierr v1.11.0 // indirect
- golang.org/x/arch v0.14.0 // indirect
- golang.org/x/crypto v0.33.0 // indirect
- golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac // indirect
- golang.org/x/net v0.35.0 // indirect
- golang.org/x/sys v0.30.0 // indirect
- golang.org/x/text v0.22.0 // indirect
- google.golang.org/protobuf v1.36.5 // indirect
- gopkg.in/ini.v1 v1.67.0 // indirect
- gopkg.in/yaml.v3 v3.0.1 // indirect
-)
diff --git a/go.sum b/go.sum
deleted file mode 100644
index b9411c3..0000000
--- a/go.sum
+++ /dev/null
@@ -1,166 +0,0 @@
-github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
-github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
-github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
-github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
-github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
-github.com/bytedance/sonic v1.12.8 h1:4xYRVRlXIgvSZ4e8iVTlMF5szgpXd4AfvuWgA8I8lgs=
-github.com/bytedance/sonic v1.12.8/go.mod h1:uVvFidNmlt9+wa31S1urfwwthTWteBgG0hWuoKAXTx8=
-github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
-github.com/bytedance/sonic/loader v0.2.3 h1:yctD0Q3v2NOGfSWPLPvG2ggA2kV6TS6s4wioyEqssH0=
-github.com/bytedance/sonic/loader v0.2.3/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
-github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
-github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
-github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
-github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
-github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
-github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
-github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
-github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
-github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
-github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
-github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
-github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
-github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
-github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
-github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
-github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
-github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
-github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
-github.com/gin-gonic/gin v1.8.2/go.mod h1:qw5AYuDrzRTnhvusDsrov+fDIxp9Dleuu12h8nfB398=
-github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
-github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
-github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
-github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
-github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
-github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
-github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
-github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
-github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
-github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg=
-github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
-github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
-github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
-github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
-github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
-github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
-github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
-github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
-github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
-github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
-github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
-github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
-github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
-github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
-github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
-github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
-github.com/leodido/go-urn v1.3.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
-github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
-github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
-github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
-github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM=
-github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
-github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
-github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
-github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
-github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
-github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
-github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
-github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
-github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
-github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
-github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
-github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
-github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
-github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
-github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
-github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
-github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo=
-github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
-github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
-github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
-github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
-github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
-github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
-github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs=
-github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4=
-github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
-github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
-github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
-github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
-github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
-github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
-github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
-github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
-github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
-github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
-github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
-github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
-github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
-github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
-github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
-github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
-github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
-github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
-github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
-github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
-github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
-github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
-github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
-go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
-go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
-go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
-go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
-go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
-go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
-golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
-golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
-golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
-golang.org/x/arch v0.14.0 h1:z9JUEZWr8x4rR0OU6c4/4t6E6jOZ8/QBS2bBYBm4tx4=
-golang.org/x/arch v0.14.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
-golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
-golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
-golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
-golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
-golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
-golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
-golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac h1:l5+whBCLH3iH2ZNHYLbAe58bo7yrN4mVcnkHDYz5vvs=
-golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac/go.mod h1:hH+7mtFmImwwcMvScyxUhjuVHR3HGaDPMn9rMSUUbxo=
-golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
-golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
-golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
-golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
-golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
-golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
-golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
-golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
-google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
-google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
-google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
-google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
-gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
-gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
-rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
diff --git a/handlers/config.go b/handlers/config.go
deleted file mode 100644
index 825ac7f..0000000
--- a/handlers/config.go
+++ /dev/null
@@ -1,160 +0,0 @@
-package handlers
-
-import (
- "net/http"
-
- "code-review/config"
- "code-review/utils"
-
- "log"
-
- "strings"
-
- "github.com/gin-gonic/gin"
-)
-
-type ConfigHandler struct {
- cfg *config.Config
-}
-
-func NewConfigHandler(cfg *config.Config) *ConfigHandler {
- return &ConfigHandler{
- cfg: cfg,
- }
-}
-
-// GetConfig 获取当前配置
-func (h *ConfigHandler) GetConfig(c *gin.Context) {
- // 使用 ToMap 方法转换配置
- configMap := h.cfg.ToMapHtml()
- c.JSON(http.StatusOK, configMap)
-}
-
-// UpdateConfig 更新配置
-func (h *ConfigHandler) UpdateConfig(c *gin.Context) {
- // 首先解析为 map
- var configMap map[string]interface{}
- if err := c.ShouldBindJSON(&configMap); err != nil {
- log.Printf("解析配置 JSON 失败: %v", err)
- c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
- return
- }
-
- log.Printf("开始更新配置")
-
- // 使用当前配置作为基础
- newConfig := *h.cfg
-
- // 处理自动禁用配置
- if autoDisable, ok := configMap["auto_disable"].(map[string]interface{}); ok {
- log.Printf("更新自动禁用配置")
- if enabled, exists := autoDisable["enabled"].(bool); exists {
- newConfig.AutoDisableConfig.Enabled = enabled
- log.Printf("自动禁用功能状态设置为: %v", enabled)
- }
- if maxFailures, ok := autoDisable["max_failures"].(float64); ok {
- newConfig.AutoDisableConfig.MaxFailures = int(maxFailures)
- }
- if resetAfter, ok := autoDisable["reset_after"].(float64); ok {
- newConfig.AutoDisableConfig.ResetAfter = int(resetAfter)
- }
- }
-
- // 处理 AI 配置
- newConfig.AIs = make([]config.AIConfig, 0)
- for key, value := range configMap {
- if strings.HasPrefix(key, "ais[") {
- if aiMap, ok := value.(map[string]interface{}); ok {
- aiConfig := config.AIConfig{
- Name: utils.GetString(aiMap, "name"),
- Type: utils.GetString(aiMap, "type"),
- APIKey: utils.GetString(aiMap, "api_key"),
- APIBase: utils.GetString(aiMap, "url"),
- Model: utils.GetString(aiMap, "model"),
- SystemMsg: utils.GetString(aiMap, "system_msg"),
- Temperature: utils.GetFloat64(aiMap, "temperature"),
- Stream: utils.GetBool(aiMap, "stream"),
- Weight: utils.GetInt(aiMap, "weight"),
- Priority: utils.GetInt(aiMap, "priority"),
- Enabled: utils.GetBool(aiMap, "enabled"),
- AutoDisable: utils.GetBool(aiMap, "auto_disable"),
- MaxFailures: utils.GetIntPtr(aiMap, "max_failures"),
- ResetAfter: utils.GetIntPtr(aiMap, "reset_after"),
- }
- log.Printf("添加 AI 配置: %s, 类型: %s", aiConfig.Name, aiConfig.Type)
- newConfig.AIs = append(newConfig.AIs, aiConfig)
- }
- }
- }
-
- // 处理 Git 平台配置
- newConfig.Git = make([]config.GitConfig, 0)
- for key, value := range configMap {
- if strings.HasPrefix(key, "git[") {
- if p, ok := value.(map[string]interface{}); ok {
- platformType := utils.GetString(p, "type")
- platformConfig := config.GitConfig{
- Name: utils.GetString(p, "name"),
- Type: platformType,
- Token: utils.GetString(p, "token"),
- Secret: utils.GetString(p, "webhook_secret"),
- APIBase: utils.GetString(p, "api_base"),
- }
-
- // 根据平台类型设置对应的 header
- switch platformType {
- case "gitlab":
- platformConfig.SignatureHeader = "X-Gitlab-Token"
- platformConfig.EventHeader = "X-Gitlab-Event"
- case "gogs":
- platformConfig.SignatureHeader = "X-Gogs-Signature"
- platformConfig.EventHeader = "X-Gogs-Event"
- case "github":
- platformConfig.SignatureHeader = "X-Hub-Signature"
- platformConfig.EventHeader = "X-GitHub-Event"
- case "gitee":
- platformConfig.SignatureHeader = "X-Gitee-Token"
- platformConfig.EventHeader = "X-Gitee-Event"
- default:
- // 如果前端传入了自定义的 header,则使用前端传入的值
- platformConfig.SignatureHeader = utils.GetString(p, "signature_header")
- platformConfig.EventHeader = utils.GetString(p, "event_header")
- }
-
- log.Printf("添加 Git 平台配置: %s, 类型: %s, SignatureHeader: %s, EventHeader: %s",
- platformConfig.Name, platformConfig.Type,
- platformConfig.SignatureHeader, platformConfig.EventHeader)
- newConfig.Git = append(newConfig.Git, platformConfig)
- }
- }
- }
-
- if err := config.Save(&newConfig); err != nil {
- log.Printf("保存配置失败: %v", err)
- c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
- return
- }
-
- // 更新运行时配置
- h.cfg = &newConfig
-
- // 重新初始化 AI 负载均衡器
- config.ResetAIBalancer(newConfig.AIs)
-
- log.Printf("配置更新成功")
- c.JSON(http.StatusOK, gin.H{"message": "配置更新成功"})
-}
-
-func AuthRequired() gin.HandlerFunc {
- return func(c *gin.Context) {
- token := c.GetHeader("X-Admin-Token")
- cfg := config.GetConfig()
-
- if token == "" || token != cfg.AdminToken {
- c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "未授权访问"})
- return
- }
-
- c.Next()
- }
-}
diff --git a/handlers/webhook.go b/handlers/webhook.go
deleted file mode 100644
index 44754a9..0000000
--- a/handlers/webhook.go
+++ /dev/null
@@ -1,161 +0,0 @@
-package handlers
-
-import (
- "bytes"
- "code-review/config"
- "code-review/services"
- "code-review/services/platforms"
- "crypto/hmac"
- "crypto/sha256"
- "encoding/hex"
- "fmt"
- "io/ioutil"
- "log"
-
- "github.com/gin-gonic/gin"
-)
-
-type WebhookHandler struct {
- reviewService *services.ReviewService
-}
-
-func NewWebhookHandler(reviewService *services.ReviewService) *WebhookHandler {
- return &WebhookHandler{
- reviewService: reviewService,
- }
-}
-
-func (h *WebhookHandler) HandleWebhook(c *gin.Context) {
- platform := c.Param("platform")
- log.Printf("收到 webhook 请求: platform=%s", platform)
-
- // 验证 webhook 签名
- if !h.verifySignature(c, platform) {
- log.Printf("webhook 签名验证失败: platform=%s", platform)
- c.JSON(400, gin.H{"error": "无效的签名"})
- return
- }
-
- // 解析请求体
- event, err := h.parseWebhookEvent(c, platform)
- if err != nil {
- log.Printf("解析 webhook 事件失败: platform=%s, error=%v", platform, err)
- c.JSON(400, gin.H{"error": err.Error()})
- return
- }
-
- // 启动代码审查
- if err := h.reviewService.Review(event); err != nil {
- log.Printf("代码审查失败: platform=%s, error=%v", platform, err)
- c.JSON(500, gin.H{"error": err.Error()})
- return
- }
-
- log.Printf("代码审查完成: platform=%s", platform)
- c.JSON(200, gin.H{"message": "success"})
-}
-
-func (h *WebhookHandler) verifySignature(c *gin.Context, platform string) bool {
- body, err := c.GetRawData()
- if err != nil {
- return false
- }
- c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(body))
-
- cfg := config.GetConfig()
- // 查找对应的平台配置
- var gitConfig config.GitConfig
- found := false
- for _, p := range cfg.Git {
- if p.Name == platform {
- gitConfig = p
- found = true
- break
- }
- }
- if !found {
- return false
- }
-
- // 获取签名
- signature := c.GetHeader(gitConfig.SignatureHeader)
- if signature == "" {
- return false
- }
-
- mac := hmac.New(sha256.New, []byte(gitConfig.Secret))
- mac.Write(body)
- expectedMAC := hex.EncodeToString(mac.Sum(nil))
-
- return hmac.Equal([]byte(signature), []byte(expectedMAC)) || hmac.Equal([]byte(signature), []byte(gitConfig.Secret))
-}
-
-func (h *WebhookHandler) parseWebhookEvent(c *gin.Context, platform string) (services.WebhookEvent, error) {
- cfg := config.GetConfig()
- // 查找对应的平台配置
- var platformConfig config.GitConfig
- found := false
- for _, p := range cfg.Git {
- if p.Name == platform {
- platformConfig = p
- found = true
- break
- }
- }
- if !found {
- return nil, fmt.Errorf("不支持的平台: %s", platform)
- }
-
- // 获取事件类型
- eventType := c.GetHeader(platformConfig.EventHeader)
- if eventType == "" {
- 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 "gitea":
- event = platforms.NewGiteaEvent(
- platformConfig.APIBase,
- auth,
- eventType,
- )
- // case "gitee":
- // event = platforms.NewGiteeEvent(
- // platformConfig.APIBase,
- // auth,
- // )
- case "gitlab":
- event = platforms.NewGitlabEvent(
- platformConfig.APIBase,
- auth,
- eventType,
- )
- default:
- return nil, fmt.Errorf("不支持的平台: %s", platform)
- }
-
- // 只处理 push 和 pull_request 事件
- //if eventType != "push" && eventType != "pull_request" {
- // return nil, fmt.Errorf("不支持的事件类型: %s", eventType)
- //}
-
- if err := c.ShouldBindJSON(event); err != nil {
- return nil, err
- }
-
- return event, nil
-}
diff --git a/main.go b/main.go
deleted file mode 100644
index 3b75329..0000000
--- a/main.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package main
-
-import (
- "code-review/config"
- "code-review/handlers"
- "code-review/services"
- "code-review/services/ai"
- "code-review/utils"
- "fmt"
- "log"
-
- "github.com/gin-gonic/gin"
-)
-
-func main() {
- // 初始化日志系统
- if err := utils.InitLogger(); err != nil {
- log.Fatalf("初始化日志系统失败: %v", err)
- }
- defer utils.CloseLogger()
-
- if err := config.LoadConfig("config.yaml"); err != nil {
- log.Fatalf("加载配置失败: %v", err)
- }
- cfg := config.GetConfig()
-
- // 检查是否有可用的 AI 配置
- if len(cfg.AIs) == 0 {
- log.Fatalf("未找到有效的 AI 配置")
- }
-
- // 初始化 AI 负载均衡器
- config.ResetAIBalancer(cfg.AIs)
-
- // 获取一个 AI 配置
- aiConfig, err := config.GetAIBalancer().Next()
- if err != nil {
- log.Fatalf("获取 AI 配置失败: %v", err)
- }
-
- // 创建 AI 客户端和审查器
- client := ai.NewClient(
- aiConfig.APIBase,
- aiConfig.APIKey,
- aiConfig.Model,
- aiConfig.Type,
- aiConfig.Temperature,
- )
- reviewer := ai.NewAI(aiConfig.Model, aiConfig.SystemMsg, client)
-
- // 创建服务和处理器
- reviewService := services.NewReviewService(reviewer)
- webhookHandler := handlers.NewWebhookHandler(reviewService)
- configHandler := handlers.NewConfigHandler(cfg)
-
- // 初始化路由
- r := gin.Default()
- r.Static("/static", "./static")
- r.StaticFile("/", "./static/index.html")
-
- api := r.Group("/api")
- configAPI := api.Group("/config")
- configAPI.Use(handlers.AuthRequired())
- {
- configAPI.GET("", configHandler.GetConfig)
- configAPI.POST("", configHandler.UpdateConfig)
- }
- api.POST("/webhook/:platform", webhookHandler.HandleWebhook)
-
- log.Printf("服务启动在 :%d", cfg.Port)
- if err := r.Run(fmt.Sprintf(":%d", cfg.Port)); err != nil {
- log.Fatalf("服务器启动失败: %v", err)
- }
-}
diff --git a/services/ai/ai.go b/services/ai/ai.go
deleted file mode 100644
index 09ff055..0000000
--- a/services/ai/ai.go
+++ /dev/null
@@ -1,84 +0,0 @@
-package ai
-
-import (
- "code-review/services"
- "code-review/services/types"
- "fmt"
- "log"
- "strings"
-)
-
-// AI 实现了 CodeReviewer 和 AIPool 接口
-type AI struct {
- model string
- systemMsg string
- client AIClient
-}
-
-// NewAI 创建新的 AI 实例
-func NewAI(model, systemMsg string, client AIClient) *AI {
- return &AI{
- model: model,
- systemMsg: systemMsg,
- client: client,
- }
-}
-
-// GetAI 实现 AIPool 接口
-func (a *AI) GetAI() services.CodeReviewer {
- return a
-}
-
-// Review 实现 CodeReviewer 接口
-func (a *AI) Review(changes *types.CodeChanges) (*types.ReviewResult, error) {
- log.Printf("AI 开始审查代码: model=%s", a.model)
-
- // 构建审查请求
- prompt := a.buildPrompt(changes)
-
- // 调用 AI API
- response, err := a.client.Chat(a.systemMsg, prompt)
- if err != nil {
- log.Printf("AI API 调用失败: model=%s, error=%v", a.model, err)
- return nil, fmt.Errorf("AI API 调用失败: %w", err)
- }
-
- // 解析审查结果
- result, err := a.parseResponse(response)
- if err != nil {
- log.Printf("解析 AI 响应失败: model=%s, error=%v", a.model, err)
- return nil, fmt.Errorf("解析 AI 响应失败: %w", err)
- }
-
- log.Printf("AI 代码审查完成: model=%s", a.model)
- return result, nil
-}
-
-// buildPrompt 构建代码审查的提示词
-func (a *AI) buildPrompt(changes *types.CodeChanges) string {
- var prompt strings.Builder
-
- prompt.WriteString("请审查以下代码变更:\n\n")
-
- for _, file := range changes.Files {
- prompt.WriteString(fmt.Sprintf("文件: %s\n", file.Path))
- prompt.WriteString("diff:\n")
- prompt.WriteString(file.Content)
- prompt.WriteString("\n---\n\n")
- }
-
- return prompt.String()
-}
-
-// parseResponse 解析 AI 的响应
-func (a *AI) parseResponse(response string) (*types.ReviewResult, error) {
- return &types.ReviewResult{
- Comments: []types.Comment{
- {
- Path: "全局",
- Content: response,
- },
- },
- Summary: "代码审查完成",
- }, nil
-}
diff --git a/services/ai/client.go b/services/ai/client.go
deleted file mode 100644
index cf6b99b..0000000
--- a/services/ai/client.go
+++ /dev/null
@@ -1,185 +0,0 @@
-package ai
-
-import (
- "bytes"
- "encoding/json"
- "fmt"
- "io"
- "log"
- "net/http"
- "regexp"
- "strings"
-)
-
-// AIClient 定义了与 AI 服务交互的接口
-type AIClient interface {
- Chat(systemMsg, prompt string) (string, error)
-}
-
-// Client 实现了 AIClient 接口
-type Client struct {
- apiBase string
- apiKey string
- model string
- aiType string
- stream bool
- temperature float64
- client *http.Client
-}
-
-// NewClient 创建新的 AI 客户端
-func NewClient(apiBase, apiKey, model, aiType string, temperature float64) *Client {
- return &Client{
- apiBase: apiBase,
- apiKey: apiKey,
- model: model,
- aiType: aiType,
- temperature: temperature,
- client: &http.Client{},
- }
-}
-
-// Chat 发送聊天请求到 AI 服务
-func (c *Client) Chat(systemMsg, prompt string) (string, error) {
- // 根据配置的类型判断使用哪个服务
- var response string
- var err error
-
- log.Printf("AImodel=%s, prompt=%s", c.model, prompt)
- if c.aiType == "ollama" {
- response, err = c.ollamaChat(systemMsg, prompt)
- } else {
- response, err = c.openAIChat(systemMsg, prompt)
- }
-
- if err != nil {
- log.Printf("AI 聊天请求失败: error=%v", err)
- return "", fmt.Errorf("AI 聊天请求失败: %w", err)
- }
-
- return response, nil
-}
-
-// ollamaChat 发送请求到 Ollama API
-func (c *Client) ollamaChat(systemMsg, prompt string) (string, error) {
- // 组合系统提示词和用户提示词
- fullPrompt := fmt.Sprintf("%s\n\n%s", systemMsg, prompt)
-
- reqBody := map[string]interface{}{
- "model": c.model,
- "prompt": fullPrompt,
- "stream": c.stream,
- "temperature": c.temperature,
- }
-
- jsonData, err := json.Marshal(reqBody)
- if err != nil {
- return "", fmt.Errorf("序列化请求失败: %w", err)
- }
-
- req, err := http.NewRequest("POST", c.apiBase+"/api/generate", bytes.NewBuffer(jsonData))
- if err != nil {
- return "", fmt.Errorf("创建请求失败: %w", err)
- }
-
- req.Header.Set("Content-Type", "application/json")
-
- resp, err := c.client.Do(req)
- if err != nil {
- return "", fmt.Errorf("发送请求失败: %w", err)
- }
- defer resp.Body.Close()
-
- if resp.StatusCode != http.StatusOK {
- body, _ := io.ReadAll(resp.Body)
- return "", fmt.Errorf("API 请求失败: status=%d, body=%s", resp.StatusCode, string(body))
- }
-
- var result struct {
- Response string `json:"response"`
- Done bool `json:"done"`
- }
-
- if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
- body, _ := io.ReadAll(resp.Body)
- log.Printf("响应内容: %s", string(body))
- return "", fmt.Errorf("解析响应失败: %w", err)
- }
-
- if !result.Done {
- return "", fmt.Errorf("AI 响应未完成")
- }
-
- pattern := "(?s)