// main.go package main import ( "bytes" "context" "encoding/json" "fmt" "io" "log" "net/http" "os" "os/exec" "path/filepath" "strings" "sync" "time" ) // BuildRequest represents a build job in the queue type BuildRequest struct { RepoURL string Branch string } // BuildQueue manages the build requests type BuildQueue struct { queue []BuildRequest mu sync.Mutex } var buildQueue = &BuildQueue{} // Add adds a new build request to the queue func (bq *BuildQueue) Add(req BuildRequest) { bq.mu.Lock() defer bq.mu.Unlock() bq.queue = append(bq.queue, req) go bq.processQueue() // Start processing if not already running } // processQueue handles the build requests one at a time func (bq *BuildQueue) processQueue() { bq.mu.Lock() if len(bq.queue) == 0 { bq.mu.Unlock() return } // Get the next request req := bq.queue[0] queueLength := len(bq.queue) - 1 // 减去当前正在处理的请求 bq.queue = bq.queue[1:] bq.mu.Unlock() // 发送准备构建通知 webhookURL := "https://open.feishu.cn/open-apis/bot/v2/hook/8648822c-fbf8-45ba-bd4d-907c2abdb885" if err := sendFeishuNotification(webhookURL, &req, "", "prepare", queueLength); err != nil { log.Printf("发送准备构建通知失败: %v", err) } // Process the build request err := processBuild(&req) if err != nil { log.Printf("Error processing build: %v", err) } // Process next item in queue go bq.processQueue() } func processBuild(req *BuildRequest) error { log.Printf("开始处理构建请求 - 仓库: %s, 分支: %s", req.RepoURL, req.Branch) // 发送开始构建通知 webhookURL := "https://open.feishu.cn/open-apis/bot/v2/hook/8648822c-fbf8-45ba-bd4d-907c2abdb885" if err := sendFeishuNotification(webhookURL, req, "", "start", 0); err != nil { log.Printf("发送开始构建通知失败: %v", err) } // Create a temporary directory for the build buildDir, err := os.MkdirTemp("", "build-*") if err != nil { return fmt.Errorf("创建构建目录失败: %v", err) } log.Printf("创建临时构建目录: %s", buildDir) defer func() { log.Printf("清理临时构建目录: %s", buildDir) os.RemoveAll(buildDir) }() // Clone the repository log.Printf("开始克隆仓库...") cloneCmd := exec.Command("git", "clone", "-b", req.Branch, req.RepoURL, buildDir) output, err := cloneCmd.CombinedOutput() if err != nil { return fmt.Errorf("git clone失败:\n命令输出: %s\n错误: %v", string(output), err) } log.Printf("仓库克隆成功") // 获取版本号 log.Printf("获取版本号...") cmd := exec.Command("git", "describe", "--tags", "--abbrev=0", "--exact-match", "HEAD") cmd.Dir = buildDir version, err := cmd.Output() if err != nil { // 如果没有tag,则使用commit id cmd = exec.Command("git", "rev-parse", "--short", "HEAD") cmd.Dir = buildDir version, err = cmd.Output() if err != nil { return fmt.Errorf("获取commit id失败: %v", err) } } versionStr := strings.TrimSpace(string(version)) log.Printf("版本号: %s", versionStr) // Run make deploy log.Printf("开始执行make all命令...") ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) defer cancel() makeCmd := exec.CommandContext(ctx, "make") makeCmd.Dir = buildDir // 直接设置标准输出和错误输出 makeCmd.Stdout = os.Stdout makeCmd.Stderr = os.Stderr log.Printf("正在启动make 命令...") if err := makeCmd.Start(); err != nil { return fmt.Errorf("启动make命令失败: %v", err) } log.Printf("make all命令已启动,开始执行...") // 等待命令完成 if err := makeCmd.Wait(); err != nil { if exitErr, ok := err.(*exec.ExitError); ok { return fmt.Errorf("命令执行失败,退出码: %d", exitErr.ExitCode()) } return fmt.Errorf("make命令执行失败: %v", err) } if ctx.Err() == context.DeadlineExceeded { return fmt.Errorf("命令执行超时") } log.Printf("cicd执行完成") // 发送飞书通知 if err := sendFeishuNotification(webhookURL, req, versionStr, "success", 0); err != nil { log.Printf("发送飞书通知失败: %v", err) } else { log.Printf("飞书通知发送成功") } return nil } // FeishuMessage 飞书消息结构 type FeishuMessage struct { MsgType string `json:"msg_type"` Content struct { Text string `json:"text"` } `json:"content"` } // sendFeishuNotification 发送飞书通知 func sendFeishuNotification(webhook string, request *BuildRequest, version string, msgType string, queueLength int) error { var messageText string switch msgType { case "prepare": waitTime := queueLength * 3 // 假设每个构建大约需要3分钟 messageText = fmt.Sprintf("⌛ 构建请求已收到\n"+ "───────────────\n"+ "📦 项目: %s\n"+ "🌿 分支: %s\n"+ "📊 队列状态: 前面还有 %d 个构建请求\n"+ "⏳ 预计等待: 约 %d 分钟\n"+ "───────────────\n"+ "请耐心等待...", request.RepoURL, request.Branch, queueLength, waitTime) case "start": messageText = fmt.Sprintf("🏗️ 开始构建\n"+ "───────────────\n"+ "📦 项目: %s\n"+ "🌿 分支: %s\n"+ "🕐 时间: %s\n"+ "───────────────\n"+ "构建进行中...", request.RepoURL, request.Branch, time.Now().Format("2006-01-02 15:04:05")) case "success": messageText = fmt.Sprintf("🚀 部署通知\n"+ "───────────────\n"+ "📦 项目: %s\n"+ "🌿 分支: %s\n"+ "🏷️ 版本: %s\n"+ "✨ 状态: 部署成功\n"+ "🕐 时间: %s\n"+ "───────────────\n"+ "祝您工作愉快!👨‍💻", request.RepoURL, request.Branch, version, time.Now().Format("2006-01-02 15:04:05")) } message := FeishuMessage{ MsgType: "text", Content: struct { Text string `json:"text"` }{ Text: messageText, }, } jsonData, err := json.Marshal(message) if err != nil { return fmt.Errorf("序列化消息失败: %v", err) } resp, err := http.Post(webhook, "application/json", bytes.NewBuffer(jsonData)) if err != nil { return fmt.Errorf("发送通知失败: %v", 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)) } return nil } func webhookHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } var payload struct { Repository struct { GitHTTPURL string `json:"git_http_url"` } `json:"repository"` Ref string `json:"ref"` } if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { http.Error(w, "Failed to parse webhook payload", http.StatusBadRequest) return } // Extract branch name from ref (refs/heads/master -> master) branch := filepath.Base(payload.Ref) // Add build request to queue buildQueue.Add(BuildRequest{ RepoURL: payload.Repository.GitHTTPURL, Branch: branch, }) w.WriteHeader(http.StatusOK) fmt.Fprintf(w, "Build request queued") } func main() { http.HandleFunc("/webhook", webhookHandler) port := "29999" log.Printf("Starting server on port %s", port) if err := http.ListenAndServe(":"+port, nil); err != nil { log.Fatal(err) } }