Browse Source
Merge branch 'gva-plugins' of github.com:flipped-aurora/gin-vue-admin into gva-vue3-2.4.5
Merge branch 'gva-plugins' of github.com:flipped-aurora/gin-vue-admin into gva-vue3-2.4.5
Conflicts: server/initialize/plugin.gomain
蒋吉兆
3 years ago
18 changed files with 474 additions and 3 deletions
-
7server/initialize/plugin.go
-
28server/plugin/email/README.MD
-
19server/plugin/email/api/sys_email.go
-
7server/plugin/email/model/response/email.go
-
2server/plugin/email/router/sys_email.go
-
13server/plugin/email/service/sys_email.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
@ -0,0 +1,7 @@ |
|||
package response |
|||
|
|||
type Email struct { |
|||
To string `json:"to"` // 邮件发送给谁
|
|||
Subject string `json:"subject"` // 邮件标题
|
|||
Body string `json:"body"` // 邮件内容
|
|||
} |
@ -0,0 +1,74 @@ |
|||
## 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"` // 邮件内容 |
|||
} |
|||
|
|||
|
@ -0,0 +1,67 @@ |
|||
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) |
|||
} |
|||
} |
@ -0,0 +1,7 @@ |
|||
package api |
|||
|
|||
type ApiGroup struct { |
|||
Api |
|||
} |
|||
|
|||
var ApiGroupApp = new(ApiGroup) |
@ -0,0 +1,7 @@ |
|||
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"` // 密钥
|
|||
} |
@ -0,0 +1,5 @@ |
|||
package global |
|||
|
|||
import "github.com/flipped-aurora/gin-vue-admin/server/plugin/notify/config" |
|||
|
|||
var GlobalConfig_ = &config.DingDing{} |
@ -0,0 +1,28 @@ |
|||
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" |
|||
} |
@ -0,0 +1,21 @@ |
|||
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"` // 是否艾特全体
|
|||
} |
@ -0,0 +1,7 @@ |
|||
package router |
|||
|
|||
type RouterGroup struct { |
|||
NotifyRouter |
|||
} |
|||
|
|||
var RouterGroupApp = new(RouterGroup) |
@ -0,0 +1,18 @@ |
|||
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) |
|||
} |
|||
} |
@ -0,0 +1,7 @@ |
|||
package service |
|||
|
|||
type ServiceGroup struct { |
|||
NotifyService |
|||
} |
|||
|
|||
var ServiceGroupApp = new(ServiceGroup) |
@ -0,0 +1,157 @@ |
|||
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
|
@ -0,0 +1 @@ |
|||
package utils |
Write
Preview
Loading…
Cancel
Save
Reference in new issue