蒋吉兆
3 years ago
20 changed files with 225 additions and 754 deletions
-
2README.md
-
154server/docs/docs.go
-
154server/docs/swagger.json
-
95server/docs/swagger.yaml
-
4server/go.mod
-
9server/initialize/plugin.go
-
74server/plugin/notify/README.MD
-
67server/plugin/notify/api/api.go
-
7server/plugin/notify/api/enter.go
-
7server/plugin/notify/config/dingding.go
-
5server/plugin/notify/global/global.go
-
28server/plugin/notify/main.go
-
21server/plugin/notify/model/response/notify.go
-
7server/plugin/notify/router/enter.go
-
18server/plugin/notify/router/router.go
-
7server/plugin/notify/service/enter.go
-
157server/plugin/notify/service/notify.go
-
1server/plugin/notify/utils/utils.go
-
2web/.env.development
-
160web/package-lock.json
@ -1,74 +0,0 @@ |
|||||
## GVA 钉钉群通知插件 |
|
||||
|
|
||||
本插件用于向钉钉群推送消息 |
|
||||
|
|
||||
### 1. 使用场景 |
|
||||
|
|
||||
- 当服务运行异常时,可以向钉钉推送异常信息,便于及时发现解决问题 |
|
||||
- 推送一些关键业务的运行日志等 |
|
||||
|
|
||||
### 2. 配置说明 |
|
||||
|
|
||||
钉钉 token 等相关信息的获取,请参考 [钉钉官网](https://developers.dingtalk.com/document/robots/custom-robot-access?spm=ding_open_doc.document.0.0.7f8710afbfzduV#topic-2026027) |
|
||||
|
|
||||
在`plugin/notify/global/global.go` 文件中配置钉钉通知的URL ,Token 等 |
|
||||
|
|
||||
```go |
|
||||
// 在gin-vue-admin 主程序的initialize中的plugin的InstallPlugin 函数中写入如下代码 |
|
||||
PluginInit(PublicGroup, notify.CreateDDPlug( |
|
||||
URL, |
|
||||
Token, |
|
||||
密钥)) |
|
||||
} |
|
||||
``` |
|
||||
|
|
||||
### 3 参数说明 |
|
||||
#### 3-1 全局参数说明 |
|
||||
|
|
||||
```go |
|
||||
Url string `mapstructure:"url" json:"url" yaml:"url"` // Url |
|
||||
Token string `mapstructure:"token" json:"token" yaml:"token"` // access_token |
|
||||
Secret string `mapstructure:"secret" json:"secret" yaml:"secret"` // 密钥 |
|
||||
``` |
|
||||
#### 3-2 请求入参说明 |
|
||||
```go |
|
||||
|
|
||||
|
|
||||
``` |
|
||||
|
|
||||
### 3方法API(可调用方法) |
|
||||
```go |
|
||||
|
|
||||
//content 发送的内容 |
|
||||
//atMobiles 需要艾特的人的手机号 |
|
||||
//isAtAll 是否艾特全体 |
|
||||
SendTextMessage(content string,atMobiles []string,isAtAll bool) |
|
||||
|
|
||||
//content 发送的内容 |
|
||||
//title 内容标题 |
|
||||
//picUrl 配图 |
|
||||
//messageUrl 点击跳转路径 |
|
||||
SendLinkMessage(content,title,picUrl,messageUrl string) |
|
||||
|
|
||||
//content 发送的内容(markdown语法) |
|
||||
//title 内容标题 |
|
||||
//atMobiles 需要艾特的人的手机号 |
|
||||
//isAtAll 是否艾特全体 |
|
||||
SendMarkdownMessage(content,title string,atMobiles []string,isAtAll bool) |
|
||||
|
|
||||
``` |
|
||||
|
|
||||
### 4. 可直接调用接口 |
|
||||
|
|
||||
发送文字消息接口: /notify/sendTextMessage [post] 已配置swagger |
|
||||
发送图文链接消息接口: /notify/sendLinkMessage [post] 已配置swagger |
|
||||
发送markdown消息接口: /notify/sendMarkdownMessage [post] 已配置swagger |
|
||||
|
|
||||
入参: |
|
||||
type Email struct { |
|
||||
To string `json:"to"` // 邮件发送给谁 |
|
||||
Subject string `json:"subject"` // 邮件标题 |
|
||||
Body string `json:"body"` // 邮件内容 |
|
||||
} |
|
||||
|
|
||||
|
|
@ -1,67 +0,0 @@ |
|||||
package api |
|
||||
|
|
||||
import ( |
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global" |
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response" |
|
||||
notify_response "github.com/flipped-aurora/gin-vue-admin/server/plugin/notify/model/response" |
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/plugin/notify/service" |
|
||||
"github.com/gin-gonic/gin" |
|
||||
"go.uber.org/zap" |
|
||||
) |
|
||||
|
|
||||
type Api struct { |
|
||||
} |
|
||||
|
|
||||
// @Tags Notify
|
|
||||
// @Summary 发送文字消息接口
|
|
||||
// @Security ApiKeyAuth
|
|
||||
// @Produce application/json
|
|
||||
// @Param data body notify_response.TextNotify true "发送文字消息的参数"
|
|
||||
// @Success 200 {string} string "{"success":true,"data":{},"msg":"发送成功"}"
|
|
||||
// @Router /notify/sendTextMessage [post]
|
|
||||
func (s *Api) SendTextMessage(c *gin.Context) { |
|
||||
var textNotify notify_response.TextNotify |
|
||||
_ = c.ShouldBindJSON(&textNotify) |
|
||||
if err := service.ServiceGroupApp.SendTextMessage(textNotify.Content, textNotify.AtMobiles, textNotify.IsAtAll); err != nil { |
|
||||
global.GVA_LOG.Error("发送失败!", zap.Any("err", err)) |
|
||||
response.FailWithMessage("发送失败", c) |
|
||||
} else { |
|
||||
response.OkWithData("发送成功", c) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// @Tags Notify
|
|
||||
// @Summary 发送图文链接消息接口
|
|
||||
// @Security ApiKeyAuth
|
|
||||
// @Produce application/json
|
|
||||
// @Param data body notify_response.LinkNotify true "发送图文链接消息的参数"
|
|
||||
// @Success 200 {string} string "{"success":true,"data":{},"msg":"发送成功"}"
|
|
||||
// @Router /notify/sendLinkMessage [post]
|
|
||||
func (s *Api) SendLinkMessage(c *gin.Context) { |
|
||||
var linkNotify notify_response.LinkNotify |
|
||||
_ = c.ShouldBindJSON(&linkNotify) |
|
||||
if err := service.ServiceGroupApp.SendLinkMessage(linkNotify.Content, linkNotify.Title, linkNotify.PicUrl, linkNotify.MessageUrl); err != nil { |
|
||||
global.GVA_LOG.Error("发送失败!", zap.Any("err", err)) |
|
||||
response.FailWithMessage("发送失败", c) |
|
||||
} else { |
|
||||
response.OkWithData("发送成功", c) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// @Tags Notify
|
|
||||
// @Summary 发送markdown消息接口
|
|
||||
// @Security ApiKeyAuth
|
|
||||
// @Produce application/json
|
|
||||
// @Param data body notify_response.MarkdownNotify true "发送markdown消息的参数"
|
|
||||
// @Success 200 {string} string "{"success":true,"data":{},"msg":"发送成功"}"
|
|
||||
// @Router /notify/sendMarkdownMessage [post]
|
|
||||
func (s *Api) SendMarkdownMessage(c *gin.Context) { |
|
||||
var markdownNotify notify_response.MarkdownNotify |
|
||||
_ = c.ShouldBindJSON(&markdownNotify) |
|
||||
if err := service.ServiceGroupApp.SendMarkdownMessage(markdownNotify.Content, markdownNotify.Title, markdownNotify.AtMobiles, markdownNotify.IsAtAll); err != nil { |
|
||||
global.GVA_LOG.Error("发送失败!", zap.Any("err", err)) |
|
||||
response.FailWithMessage("发送失败", c) |
|
||||
} else { |
|
||||
response.OkWithData("发送成功", c) |
|
||||
} |
|
||||
} |
|
@ -1,7 +0,0 @@ |
|||||
package api |
|
||||
|
|
||||
type ApiGroup struct { |
|
||||
Api |
|
||||
} |
|
||||
|
|
||||
var ApiGroupApp = new(ApiGroup) |
|
@ -1,7 +0,0 @@ |
|||||
package config |
|
||||
|
|
||||
type DingDing struct { |
|
||||
Url string `mapstructure:"url" json:"url" yaml:"url"` // Url
|
|
||||
Token string `mapstructure:"token" json:"token" yaml:"token"` // access_token
|
|
||||
Secret string `mapstructure:"secret" json:"secret" yaml:"secret"` // 密钥
|
|
||||
} |
|
@ -1,5 +0,0 @@ |
|||||
package global |
|
||||
|
|
||||
import "github.com/flipped-aurora/gin-vue-admin/server/plugin/notify/config" |
|
||||
|
|
||||
var GlobalConfig_ = &config.DingDing{} |
|
@ -1,28 +0,0 @@ |
|||||
package notify |
|
||||
|
|
||||
import ( |
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/plugin/notify/global" |
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/plugin/notify/router" |
|
||||
"github.com/gin-gonic/gin" |
|
||||
) |
|
||||
|
|
||||
type ddPlugin struct { |
|
||||
Secret string |
|
||||
Token string |
|
||||
Url string |
|
||||
} |
|
||||
|
|
||||
func CreateDDPlug(url, Token, Secret string) *ddPlugin { |
|
||||
global.GlobalConfig_.Url = url |
|
||||
global.GlobalConfig_.Token = Token |
|
||||
global.GlobalConfig_.Secret = Secret |
|
||||
return &ddPlugin{} |
|
||||
} |
|
||||
|
|
||||
func (*ddPlugin) Register(group *gin.RouterGroup) { |
|
||||
router.RouterGroupApp.InitRouter(group) |
|
||||
} |
|
||||
|
|
||||
func (*ddPlugin) RouterPath() string { |
|
||||
return "notify" |
|
||||
} |
|
@ -1,21 +0,0 @@ |
|||||
package response |
|
||||
|
|
||||
type TextNotify struct { // 文字信息
|
|
||||
Content string `json:"content"` // 发送的内容
|
|
||||
AtMobiles []string `json:"atMobiles"` // 需要艾特的人的手机号
|
|
||||
IsAtAll bool `json:"isAtAll"` // 是否艾特全体
|
|
||||
} |
|
||||
|
|
||||
type LinkNotify struct { // 图文链接信息
|
|
||||
Content string `json:"content"` // 发送的内容
|
|
||||
Title string `json:"title"` // 内容标题
|
|
||||
PicUrl string `json:"picUrl"` // 配图
|
|
||||
MessageUrl string `json:"messageUrl"` // 点击跳转路径
|
|
||||
} |
|
||||
|
|
||||
type MarkdownNotify struct { // markdown信息
|
|
||||
Title string `json:"title"` // 内容标题
|
|
||||
Content string `json:"content"` // 发送的内容
|
|
||||
AtMobiles []string `json:"atMobiles"` // 需要艾特的人的手机号
|
|
||||
IsAtAll bool `json:"isAtAll"` // 是否艾特全体
|
|
||||
} |
|
@ -1,7 +0,0 @@ |
|||||
package router |
|
||||
|
|
||||
type RouterGroup struct { |
|
||||
NotifyRouter |
|
||||
} |
|
||||
|
|
||||
var RouterGroupApp = new(RouterGroup) |
|
@ -1,18 +0,0 @@ |
|||||
package router |
|
||||
|
|
||||
import ( |
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/middleware" |
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/plugin/notify/api" |
|
||||
"github.com/gin-gonic/gin" |
|
||||
) |
|
||||
|
|
||||
type NotifyRouter struct { |
|
||||
} |
|
||||
|
|
||||
func (s *NotifyRouter) InitRouter(Router *gin.RouterGroup) { |
|
||||
router := Router.Use(middleware.OperationRecord()) |
|
||||
var SendTextMessage = api.ApiGroupApp.Api.SendTextMessage |
|
||||
{ |
|
||||
router.POST("sendTextMessage", SendTextMessage) |
|
||||
} |
|
||||
} |
|
@ -1,7 +0,0 @@ |
|||||
package service |
|
||||
|
|
||||
type ServiceGroup struct { |
|
||||
NotifyService |
|
||||
} |
|
||||
|
|
||||
var ServiceGroupApp = new(ServiceGroup) |
|
@ -1,157 +0,0 @@ |
|||||
package service |
|
||||
|
|
||||
import ( |
|
||||
"bytes" |
|
||||
"crypto/hmac" |
|
||||
"crypto/sha256" |
|
||||
"encoding/base64" |
|
||||
"encoding/json" |
|
||||
"fmt" |
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/plugin/notify/global" |
|
||||
"io/ioutil" |
|
||||
"net/http" |
|
||||
"net/url" |
|
||||
"time" |
|
||||
) |
|
||||
|
|
||||
type NotifyService struct { |
|
||||
} |
|
||||
|
|
||||
//@author: [Espoir](https://github.com/nightsimon)
|
|
||||
//@function: SendTextMessage
|
|
||||
//@description: 发送钉钉文字信息
|
|
||||
//@params content string发送的文字内容
|
|
||||
//@params atMobiles []string 艾特的手机号
|
|
||||
//@params isAtAll bool 是否艾特全体
|
|
||||
//@return: err error
|
|
||||
|
|
||||
func (e *NotifyService) SendTextMessage(content string, atMobiles []string, isAtAll bool) (err error) { |
|
||||
msg := map[string]interface{}{ |
|
||||
"msgtype": "text", |
|
||||
"text": map[string]string{ |
|
||||
"content": content, |
|
||||
}, |
|
||||
"at": map[string]interface{}{ |
|
||||
"atMobiles": atMobiles, |
|
||||
"isAtAll": isAtAll, |
|
||||
}, |
|
||||
} |
|
||||
return SendMessage(msg) |
|
||||
} |
|
||||
|
|
||||
//@author: [Espoir](https://github.com/nightsimon)
|
|
||||
//@function: SendLinkMessage
|
|
||||
//@description: 发送钉钉图文链接信息
|
|
||||
//@params content string 发送的文字内容
|
|
||||
//@params title string 发送的标题
|
|
||||
//@params picUrl string 艾特的手机号
|
|
||||
//@params messageUrl string 是否艾特全体
|
|
||||
//@return: err error
|
|
||||
|
|
||||
func (e *NotifyService) SendLinkMessage(content, title, picUrl, messageUrl string) (err error) { |
|
||||
msg := map[string]interface{}{ |
|
||||
"msgtype": "link", |
|
||||
"link": map[string]string{ |
|
||||
"text": content, |
|
||||
"title": title, |
|
||||
"picUrl": picUrl, |
|
||||
"messageUrl": messageUrl, |
|
||||
}, |
|
||||
} |
|
||||
return SendMessage(msg) |
|
||||
} |
|
||||
|
|
||||
//@author: [Espoir](https://github.com/nightsimon)
|
|
||||
//@function: SendMarkdownMessage
|
|
||||
//@description: 发送钉钉Markdown信息
|
|
||||
//@params content 发送的文字内容
|
|
||||
//@params title 发送的标题
|
|
||||
//@params atMobiles []string 艾特的手机号
|
|
||||
//@params isAtAll bool 是否艾特全体
|
|
||||
//@return: err error
|
|
||||
|
|
||||
func (e *NotifyService) SendMarkdownMessage(content, title string, atMobiles []string, isAtAll bool) (err error) { |
|
||||
msg := map[string]interface{}{ |
|
||||
"msgtype": "markdown", |
|
||||
"markdown": map[string]string{ |
|
||||
"text": content, |
|
||||
"title": title, |
|
||||
}, |
|
||||
"at": map[string]interface{}{ |
|
||||
"atMobiles": atMobiles, |
|
||||
"isAtAll": isAtAll, |
|
||||
}, |
|
||||
} |
|
||||
return SendMessage(msg) |
|
||||
} |
|
||||
|
|
||||
func SendMessage(msg interface{}) error { |
|
||||
body := bytes.NewBuffer(nil) |
|
||||
err := json.NewEncoder(body).Encode(msg) |
|
||||
if err != nil { |
|
||||
return fmt.Errorf("msg json failed, msg: %v, err: %v", msg, err.Error()) |
|
||||
} |
|
||||
|
|
||||
value := url.Values{} |
|
||||
value.Set("access_token", global.GlobalConfig_.Token) |
|
||||
if global.GlobalConfig_.Secret != "" { |
|
||||
t := time.Now().UnixNano() / 1e6 |
|
||||
value.Set("timestamp", fmt.Sprintf("%d", t)) |
|
||||
value.Set("sign", sign(t, global.GlobalConfig_.Secret)) |
|
||||
} |
|
||||
|
|
||||
request, err := http.NewRequest(http.MethodPost, global.GlobalConfig_.Url, body) |
|
||||
if err != nil { |
|
||||
return fmt.Errorf("error request: %v", err.Error()) |
|
||||
} |
|
||||
request.URL.RawQuery = value.Encode() |
|
||||
request.Header.Add("Content-Type", "application/json") |
|
||||
res, err := (&http.Client{}).Do(request) |
|
||||
if err != nil { |
|
||||
return fmt.Errorf("send dingTalk message failed, error: %v", err.Error()) |
|
||||
} |
|
||||
defer func() { _ = res.Body.Close() }() |
|
||||
result, err := ioutil.ReadAll(res.Body) |
|
||||
|
|
||||
if res.StatusCode != 200 { |
|
||||
return fmt.Errorf("send dingTalk message failed, %s", httpError(request, res, result, "http code is not 200")) |
|
||||
} |
|
||||
if err != nil { |
|
||||
return fmt.Errorf("send dingTalk message failed, %s", httpError(request, res, result, err.Error())) |
|
||||
} |
|
||||
|
|
||||
type response struct { |
|
||||
ErrCode int `json:"errcode"` |
|
||||
} |
|
||||
var ret response |
|
||||
|
|
||||
if err := json.Unmarshal(result, &ret); err != nil { |
|
||||
return fmt.Errorf("send dingTalk message failed, %s", httpError(request, res, result, err.Error())) |
|
||||
} |
|
||||
|
|
||||
if ret.ErrCode != 0 { |
|
||||
return fmt.Errorf("send dingTalk message failed, %s", httpError(request, res, result, "errcode is not 0")) |
|
||||
} |
|
||||
|
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
func httpError(request *http.Request, response *http.Response, body []byte, error string) string { |
|
||||
return fmt.Sprintf( |
|
||||
"http request failure, error: %s, status code: %d, %s %s, body:\n%s", |
|
||||
error, |
|
||||
response.StatusCode, |
|
||||
request.Method, |
|
||||
request.URL.String(), |
|
||||
string(body), |
|
||||
) |
|
||||
} |
|
||||
func sign(t int64, secret string) string { |
|
||||
strToHash := fmt.Sprintf("%d\n%s", t, secret) |
|
||||
hmac256 := hmac.New(sha256.New, []byte(secret)) |
|
||||
hmac256.Write([]byte(strToHash)) |
|
||||
data := hmac256.Sum(nil) |
|
||||
return base64.StdEncoding.EncodeToString(data) |
|
||||
} |
|
||||
|
|
||||
// 其余方法请参考 https://developers.dingtalk.com/document/robots/custom-robot-access?spm=ding_open_doc.document.0.0.7f8710afbfzduV#topic-2026027
|
|
@ -1 +0,0 @@ |
|||||
package utils |
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue