背景: 开发需要在提交merge时触发一个通知.展示一些特定信息,方便开发及时知道mr状态.按照以往的开源设计软件,直接找文档查看相关api
gitlab_webhook 事件样例: https://git.xkool.org/help/user/project/integrations/webhooks ,可以根据请求数据封装自定义信息总体思路:根据gitlab自带events事件,拿到请求数据再封装企业微信数据结构,然后构造webhook服务api,主要是在方法中实现请求企业微信机器人,到达通知目的
下面是主进程逻辑
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"strings"
"github.com/gin-gonic/gin"
)
type Dataform struct {
Content string `json:"content"`
}
type TextDataform struct {
Content string `json:"content"`
MentionedMobileList []string `json:"mentioned_mobile_list"`
}
type Markdowndata struct {
Msgtype string `json:"msgtype"`
*Dataform `json:"markdown"`
}
type Textdata struct {
Msgtype string `json:"msgtype"`
*TextDataform `json:"text"`
}
// 找到需要@的人员对应手机号
func phonelist(s string) string {
Someone := map[string]string{
"ns***": "18127386881",
"tr***": "131**456",
"ss***": "13***259",
}
if v, ok := Someone[s]; ok {
fmt.Println(v)
return v
} else {
fmt.Println("Key Not Found")
return ""
}
}
// 找到需要被@的开发人员
func getuser(g *Bodydata) string {
if g.ObjectAttributes.Action == "approved" && g.ObjectAttributes.LastCommit.Author.Name != "" {
return fmt.Sprintf("%v", g.ObjectAttributes.LastCommit.Author.Name)
} else if g.ObjectAttributes.Action == "open" && g.ObjectAttributes.Assignee.Name != "" {
return fmt.Sprintf("%v", g.ObjectAttributes.Assignee.Name)
} else {
return ""
}
}
// 序列化text模板
func textjson(da string, at string) string {
a := phonelist(at)
t := &Textdata{
Msgtype: "text",
TextDataform: &TextDataform{
da,
[]string{fmt.Sprintf("%v", a)},
},
}
d, err := json.Marshal(t)
if err != nil {
fmt.Println(err)
os.Exit(2)
}
return string(d)
}
// 序列化md模板
func mdjson(da string) string {
t := &Markdowndata{
Msgtype: "markdown",
Dataform: &Dataform{da},
}
d, err := json.Marshal(t)
if err != nil {
fmt.Println(err)
os.Exit(2)
}
return string(d)
}
// 触发企业微信机器人
func WetchatWebhook(s string) {
uri := os.Getenv("URL")
req, _ := http.NewRequest("POST", uri, strings.NewReader(s))
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
return
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return
}
fmt.Println(res.StatusCode)
fmt.Println(body)
}
// md类型企业微信模板
func template(g *Bodydata) string {
kind := g.ObjectKind
switch kind {
case "note1":
return fmt.Sprintf(`<font color="warning">Gitlab事件通知</font>。
>事件类型: <font color="red">%v</font>
>源分支: <font color="green">%v</font>
>目的分支: <font color="green">%v</font>
>Title: <font color="green">%v</font>
>描述: <font color="green">%v</font>
>更新时间: <font color="green">%v</font>
>MR地址: <font color="warning">%v</font>
>评论内容: <font color="comment">%v</font>
>评论人: <font color="comment">%v</font>
>评论时间: <font color="green">%v</font>
>提交人:<font color="comment">%v</font>
`, g.ObjectKind, g.MergeRequest.SourceBranch, g.MergeRequest.TargetBranch, g.MergeRequest.Title, g.MergeRequest.Description, g.MergeRequest.UpdatedAt, g.ObjectAttributes.URL, g.ObjectAttributes.Note, g.ObjectAttributes.AuthorID, g.ObjectAttributes.UpdatedAt, g.MergeRequest.LastCommit.Author.Name)
case "merge_request":
if g.ObjectAttributes.State != "merged" && !g.ObjectAttributes.WorkInProgress && g.ObjectAttributes.Action != "update" {
return fmt.Sprintf(
`%v <font color="warning">%v</font> [Merge Request](%V)
>分支: <font color="green">%v ---> %v</font>
>Title: <font color="green">%v</font>
>描述: <font color="green">%v</font>
>Merge状态: <font color="green">%v</font>`, g.User.Name, g.ObjectAttributes.Action, g.ObjectAttributes.URL, g.ObjectAttributes.SourceBranch, g.ObjectAttributes.TargetBranch, g.ObjectAttributes.Title, g.ObjectAttributes.Description, g.ObjectAttributes.MergeStatus)
}
return ""
case "build1":
if g.BuildStatus == "failed" {
return fmt.Sprintf(`<font color="warning">Gitlab事件通知</font>。
>事件类型: <font color="red">%v</font>
>构建项目: <font color="green">%v</font>
>构建状态: <font color="warning">%v</font>
>构建开始时间: <font color="green">%v</font>
>构建结束时间: <font color="green">%v</font>
>commIt_id: <font color="green">%v</font>
>提交人: <font color="green">%v</font>
`, g.ObjectKind, g.ProjectName, g.BuildStatus, g.Commit.StartedAt, g.Commit.FinishedAt, g.Commit.ID, g.Commit.AuthorName)
}
return ""
default:
return ""
}
}
// text 类型模板
func templatetext(g *Bodydata) string {
kind := g.ObjectKind
switch kind {
case "merge_request":
if g.ObjectAttributes.State != "merged" && !g.ObjectAttributes.WorkInProgress && g.ObjectAttributes.Action != "update" {
return g.ObjectAttributes.URL
}
return ""
default:
return ""
}
}
// 请求函数
func gitPush(c *gin.Context) {
var bodydata = &Bodydata{}
matched, _ := VerifySignature(c)
if !matched {
err := "Token did not match"
c.String(http.StatusForbidden, err)
fmt.Println(err)
return
}
fmt.Println("Token is matched ~")
// >>>>>>>>>>>>>>>>>
body, err := c.GetRawData()
if err != nil {
fmt.Println("error:", err)
return
}
err = json.Unmarshal(body, bodydata) // 解析完request body 数据
if err != nil {
fmt.Println("error:", err)
return
} else {
md := template(bodydata)
text := templatetext(bodydata)
user := getuser(bodydata)
fmt.Println("user=", user)
fmt.Println("md=", md)
fmt.Println("text=", text)
tj := textjson(text, user)
mj := mdjson(md)
fmt.Println("md:", mj)
fmt.Println("text:", tj)
WetchatWebhook(mj) // 调用企业微信机器人接口
WetchatWebhook(tj) // 单独发送url,因为企业微信md类型模板的链接无法用默认浏览器直接跳转
c.String(http.StatusOK, "ok")
}
}
// 验证token
func VerifySignature(c *gin.Context) (bool, error) {
// Get Header with X-Hub-Signature
XLibToken := c.GetHeader("X-Gitlab-Token")
signature := GetToken("TOKEN_KEY")
fmt.Println(signature)
return XLibToken == signature, nil
}
// 从环境变量获取token
func GetToken(e string) string {
env := os.Getenv(e)
if env != "" {
fmt.Printf("from os get successfully env!, %s=%s", e, env)
} else {
fmt.Printf("%s env not found", e)
}
return env
}
func main() {
router := gin.Default()
router.POST("/send", gitPush)
_ = router.Run(":8079")
}
程序启动之前需要给环境变量
export TOKEN_KEY=009473401a3b0***fb59f
export URL=https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=a19c***9
dockerfile 构建镜像
ARG IMAGE=alpine:3.12
FROM golang:1.16-alpine as builder
RUN mkdir ${GOPATH}/src/eventserver
WORKDIR ${GOPATH}/src/
ENV GOPROXY https://goproxy.io,direct
COPY . ./
RUN CGO_ENABLED=0 GOOS=linux go build -o /usr/bin/eventserver
FROM ${IMAGE}
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories \
&& apk add --no-cache bash openssl curl
COPY --from=builder /usr/bin/eventserver /usr/bin/
COPY . ./
RUN chmod +x ./start.sh
ENTRYPOINT ["bash", "-c", "./start.sh"]