go语言实现gogs代码推送到飞书群聊

1. 分析参数

gogs推送入参如下

{
  "ref": "refs/heads/develop",
  "before": "28e1879d029cb852e4844d9c718537df08844e03",
  "after": "bffeb74224043ba2feb48d137756c8a9331c449a",
  "compare_url": "http://localhost:3000/unknwon/webhooks/compare/28e1879d029cb852e4844d9c718537df08844e03...bffeb74224043ba2feb48d137756c8a9331c449a",
  "commits": [
    {
      "id": "bffeb74224043ba2feb48d137756c8a9331c449a",
      "message": "!@#0^%u003eu003eu003eu003eu003cu003cu003cu003cu003eu003eu003eu003e
",
      "url": "http://localhost:3000/unknwon/webhooks/commit/bffeb74224043ba2feb48d137756c8a9331c449a",
      "author": {
        "name": "Unknwon",
        "email": "[email protected]",
        "username": "unknwon"
      },
      "committer": {
        "name": "Unknwon",
        "email": "[email protected]",
        "username": "unknwon"
      },
      "timestamp": "2017-03-13T13:52:11-04:00"
    }
  ],
  "repository": {
    "id": 140,
    "owner": {
      "id": 1,
      "login": "unknwon",
      "full_name": "Unknwon",
      "email": "[email protected]",
      "avatar_url": "https://secure.gravatar.com/avatar/d8b2871cdac01b57bbda23716cc03b96",
      "username": "unknwon"
    },
    "name": "webhooks",
    "full_name": "unknwon/webhooks",
    "description": "",
    "private": false,
    "fork": false,
    "html_url": "http://localhost:3000/unknwon/webhooks",
    "ssh_url": "ssh://unknwon@localhost:2222/unknwon/webhooks.git",
    "clone_url": "http://localhost:3000/unknwon/webhooks.git",
    "website": "",
    "stars_count": 0,
    "forks_count": 1,
    "watchers_count": 1,
    "open_issues_count": 7,
    "default_branch": "master",
    "created_at": "2017-02-26T04:29:06-05:00",
    "updated_at": "2017-03-13T13:51:58-04:00"
  },
  "pusher": {
    "id": 1,
    "login": "unknwon",
    "full_name": "Unknwon",
    "email": "[email protected]",
    "avatar_url": "https://secure.gravatar.com/avatar/d8b2871cdac01b57bbda23716cc03b96",
    "username": "unknwon"
  },
  "sender": {
    "id": 1,
    "login": "unknwon",
    "full_name": "Unknwon",
    "email": "[email protected]",
    "avatar_url": "https://secure.gravatar.com/avatar/d8b2871cdac01b57bbda23716cc03b96",
    "username": "unknwon"
  }
}

对我们而言,有需要通知的就:谁推送了代码到哪个分支,什么时间,仓库地址和代码详情,还有就是变更的代码明细。

2. 开发接口

使用go中gin框架作为基础,将需要处理的参数声明成结构体

type Commit struct {
	ID        string    `json:"id"`
	Message   string    `json:"message"`
	URL       string    `json:"url"`
	Author    Author    `json:"author"`
	Committer Committer `json:"committer"`
	Modified  []string  `json:"modified"`
	Timestamp string    `json:"timestamp"`
}

type Author struct {
	Name     string `json:"name"`
	Email    string `json:"email"`
	Username string `json:"username"`
}

type Committer struct {
	Name     string `json:"name"`
	Email    string `json:"email"`
	Username string `json:"username"`
}

type Repository struct {
	HTMLURL string `json:"html_url"`
}

type PushEvent struct {
	Ref        string     `json:"ref"`
	Commits    []Commit   `json:"commits"`
	Repository Repository `json:"repository"`
}

处理请求参数

func GetMessage(c *gin.Context) {
	body, err := io.ReadAll(c.Request.Body)
	if err != nil {
		c.JSON(500, gin.H{"error": "无法读取请求体"})
		return
	}
	var pushEvent PushEvent
	err = json.Unmarshal(body, &pushEvent)
	if err != nil {
		c.JSON(400, gin.H{"error": "JSON 解析错误"})
		return
	}
	var times string
	var info string
	var author string
	var link string
	var timestamp int64
	var sign string
	now := time.Now()
	// 获取时间戳(秒)
	timestamp = now.Unix()
	for _, commit := range pushEvent.Commits {
		// 将时间字符串解析为时间对象
		timeObj, err := time.Parse(time.RFC3339, commit.Timestamp)
		if err != nil {
			fmt.Println("时间解析错误:", err)
			continue
		}
		// 格式化时间为 "2024/01/15"
		formattedTime := timeObj.Format("2006/01/02")
		times = formattedTime
		info = commit.Message
		link = commit.URL
		author = commit.Author.Name
	}

将入参读取,序列化成json,然后把json数据装回结构体,然后取出参数
并赋值
代码解耦再单独写一个推送飞书的方法,新建结构体

type MessageData struct {
	Timestamp int64
	Sign      string
	Author    string
	Stash     string
	Branch    string
	Time      string
	Info      string
	Link      string
}

func pushFeiShu(data MessageData) {
url := “https://open.feishu.cn/open-apis/bot/v2/hook/你的机器人hookid”

// 构建飞书消息体
msg := map[string]interface{}{
	"timestamp": data.Timestamp,
	"sign":      data.Sign, // 得到的签名字符串。
	"msg_type":  "interactive",
	"card": map[string]interface{}{
		"config": map[string]interface{}{
			"wide_screen_mode": true,
		},
		"header": map[string]interface{}{
			"template": "green",
			"title": map[string]interface{}{
				"tag":     "plain_text",
				"content": fmt.Sprintf("?? %s推送了代码", data.Author),
			},
		},
		"elements": []map[string]interface{}{
			{
				"fields": []map[string]interface{}{
					{
						"is_short": false,
						"text": map[string]interface{}{
							"content": fmt.Sprintf("? 目标仓库: %s", data.Stash),
							"tag":     "lark_md",
						},
					},
					{
						"is_short": false,
						"text": map[string]interface{}{
							"content": fmt.Sprintf("?? 目标分支: %s", data.Branch),
							"tag":     "lark_md",
						},
					},
					{
						"is_short": false,
						"text": map[string]interface{}{
							"content": fmt.Sprintf("?? 推送时间: %s", data.Time),
							"tag":     "lark_md",
						},
					},
					{
						"is_short": false,
						"text": map[string]interface{}{
							"content": fmt.Sprintf("?? 最新提交:%s", data.Info),
							"tag":     "lark_md",
						},
					},
				},
				"tag": "div",
			},
			{
				"tag": "action",
				"actions": []map[string]interface{}{
					{
						"tag": "button",
						"text": map[string]interface{}{
							"tag":     "plain_text",
							"content": "查看代码",
						},
						"type": "primary",
						"multi_url": map[string]interface{}{
							"url": data.Link,
						},
					},
				},
			},
		},
	},
}

// 将消息体转换为 JSON 字节数组
payload, err := json.Marshal(msg)
if err != nil {
	fmt.Println("消息体转换失败:", err)
	return
}

// 创建一个 HTTP 请求
req, err := http.NewRequest("POST", url, bytes.NewBuffer(payload))
if err != nil {
	fmt.Println("创建请求失败:", err)
	return
}
// 设置请求头
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
	fmt.Println("请求发送失败:", err)
	return
}
defer resp.Body.Close()

body, err := ioutil.ReadAll(resp.Body)
if err != nil {
	fmt.Println("读取响应失败:", err)
	return
}
fmt.Println("响应:", string(body))

}

push方法调用

	data := MessageData{
		Author:    author,
		Timestamp: timestamp,
		Sign:      sign,
		Stash:     pushEvent.Repository.HTMLURL,
		Branch:    pushEvent.Ref,
		Time:      times,
		Info:      info,
		Link:      link,
	}
	pushFeiShu(data)
	c.JSON(200, gin.H{"message": "处理成功"})

注意:
如果飞书机器人配置了验证签名则需要单独处理添加签名,代码如下

package utils

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"fmt"
)

func GenSign(secret string, timestamp int64) (string, error) {
	//timestamp + key 做sha256, 再进行base64 encode
	stringToSign := fmt.Sprintf("%v", timestamp) + "
" + secret

	var data []byte
	h := hmac.New(sha256.New, []byte(stringToSign))
	_, err := h.Write(data)
	if err != nil {
		return "", err
	}

	signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
	return signature, nil
}

3. 配置gogs

在这里插入图片描述

添加接口地址
测试推送
在这里插入图片描述

在这里插入图片描述