瀏覽代碼

Merge branch 'gva-plugins' of github.com:flipped-aurora/gin-vue-admin into gva-vue3-2.4.5

 Conflicts:
	server/initialize/plugin.go
蒋吉兆 3 年之前
父節點
當前提交
52e6167b3d

+ 7 - 0
server/initialize/plugin.go

@@ -2,6 +2,7 @@ package initialize
 
 import (
 	"github.com/flipped-aurora/gin-vue-admin/server/global"
+	"github.com/flipped-aurora/gin-vue-admin/server/plugin/notify"
 	email "github.com/flipped-aurora/gva-plugins/email" // 在线仓库模式go
 	//"github.com/flipped-aurora/gin-vue-admin/server/plugin/email" // 本地插件仓库地址模式
 	"github.com/flipped-aurora/gin-vue-admin/server/plugin/example_plugin"
@@ -30,4 +31,10 @@ func InstallPlugin(PublicGroup *gin.RouterGroup, PrivateGroup *gin.RouterGroup)
 		global.GVA_CONFIG.Email.Port,
 		global.GVA_CONFIG.Email.IsSSL,
 	))
+
+	//  钉钉通知,暂时开放权限
+	PluginInit(PublicGroup, notify.CreateDDPlug(
+		"https://oapi.dingtalk.com/robot/send",
+		"8ded23f91917dc4f6275f44ba5ef243e6ed1d2cc74de83f01a6f5f5f39905671",
+		"SECaecf452bd6e671ab0d47469c3ad933e32fcc47b335333049a1b8961606192f38"))
 }

+ 26 - 2
server/plugin/email/README.MD

@@ -1,4 +1,5 @@
 ## GVA 邮件发送功能插件
+#### 开发者:GIN-VUE-ADMIN 官方
 
 ### 使用步骤
 
@@ -27,8 +28,9 @@
     true,
     ))
 
-#### 2. 配置说明
+### 2. 配置说明
 
+#### 2-1 全局配置结构体说明
     //其中 Form 和 Secret 通常来说就是用户名和密码
 
     type Email struct {
@@ -40,8 +42,17 @@
 	    Port     int     // 端口     请前往QQ或者你要发邮件的邮箱查看其smtp协议 大多为 465
 	    IsSSL    bool    // 是否SSL   是否开启SSL
     }
+#### 2-2 入参结构说明
+    //其中 Form 和 Secret 通常来说就是用户名和密码
+
+    type Email struct {
+        To      string `json:"to"`      // 邮件发送给谁
+        Subject string `json:"subject"` // 邮件标题
+        Body    string `json:"body"`    // 邮件内容
+    }
+
 
-### 方法API
+### 3. 方法API
 
     utils.EmailTest(邮件标题,邮件主体) 发送测试邮件
     例:utils.EmailTest("测试邮件","测试邮件")
@@ -49,3 +60,16 @@
     例:utils.ErrorToEmail("测试邮件","测试邮件")
     utils.Email(目标邮箱多个的话用逗号分隔,邮件标题,邮件主体) 发送测试邮件
     例:utils.Email(”a.qq.com,b.qq.com“,"测试邮件","测试邮件")
+
+### 4. 可直接调用的接口
+
+    测试接口: /email/emailTest [post] 已配置swagger
+
+    发送邮件接口接口: /email/emailSend [post] 已配置swagger
+    入参:
+    type Email struct {
+        To      string `json:"to"`      // 邮件发送给谁
+        Subject string `json:"subject"` // 邮件标题
+        Body    string `json:"body"`    // 邮件内容
+    }
+   

+ 19 - 0
server/plugin/email/api/sys_email.go

@@ -3,6 +3,7 @@ package api
 import (
 	"github.com/flipped-aurora/gin-vue-admin/server/global"
 	"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
+	email_response "github.com/flipped-aurora/gin-vue-admin/server/plugin/email/model/response"
 	"github.com/flipped-aurora/gin-vue-admin/server/plugin/email/service"
 	"github.com/gin-gonic/gin"
 	"go.uber.org/zap"
@@ -25,3 +26,21 @@ func (s *EmailApi) EmailTest(c *gin.Context) {
 		response.OkWithData("发送成功", c)
 	}
 }
+
+// @Tags System
+// @Summary 发送邮件
+// @Security ApiKeyAuth
+// @Produce  application/json
+// @Param data body email_response.Email true "发送邮件必须的参数"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"发送成功"}"
+// @Router /email/sendEmail [post]
+func (s *EmailApi) SendEmail(c *gin.Context) {
+	var email email_response.Email
+	_ = c.ShouldBindJSON(&email)
+	if err := service.ServiceGroupApp.SendEmail(email.To, email.Subject, email.Body); err != nil {
+		global.GVA_LOG.Error("发送失败!", zap.Any("err", err))
+		response.FailWithMessage("发送失败", c)
+	} else {
+		response.OkWithData("发送成功", c)
+	}
+}

+ 7 - 0
server/plugin/email/model/response/email.go

@@ -0,0 +1,7 @@
+package response
+
+type Email struct {
+	To      string `json:"to"`      // 邮件发送给谁
+	Subject string `json:"subject"` // 邮件标题
+	Body    string `json:"body"`    // 邮件内容
+}

+ 3 - 1
server/plugin/email/router/sys_email.go

@@ -12,7 +12,9 @@ type EmailRouter struct {
 func (s *EmailRouter) InitEmailRouter(Router *gin.RouterGroup) {
 	emailRouter := Router.Use(middleware.OperationRecord())
 	var EmailApi = api.ApiGroupApp.EmailApi.EmailTest
+	var SendEmail = api.ApiGroupApp.EmailApi.SendEmail
 	{
-		emailRouter.POST("emailTest", EmailApi) // 发送测试邮件
+		emailRouter.POST("emailTest", EmailApi)  // 发送测试邮件
+		emailRouter.POST("sendEmail", SendEmail) // 发送邮件
 	}
 }

+ 13 - 0
server/plugin/email/service/sys_email.go

@@ -18,3 +18,16 @@ func (e *EmailService) EmailTest() (err error) {
 	err = utils.EmailTest(subject, body)
 	return err
 }
+
+//@author: [maplepie](https://github.com/maplepie)
+//@function: EmailTest
+//@description: 发送邮件测试
+//@return: err error
+//@params to string 	 收件人
+//@params subject string   标题(主题)
+//@params body  string 	 邮件内容
+
+func (e *EmailService) SendEmail(to, subject, body string) (err error) {
+	err = utils.Email(to, subject, body)
+	return err
+}

+ 74 - 0
server/plugin/notify/README.MD

@@ -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"`    // 邮件内容
+    }
+
+

+ 67 - 0
server/plugin/notify/api/api.go

@@ -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)
+	}
+}

+ 7 - 0
server/plugin/notify/api/enter.go

@@ -0,0 +1,7 @@
+package api
+
+type ApiGroup struct {
+	Api
+}
+
+var ApiGroupApp = new(ApiGroup)

+ 7 - 0
server/plugin/notify/config/dingding.go

@@ -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"` // 密钥
+}

+ 5 - 0
server/plugin/notify/global/global.go

@@ -0,0 +1,5 @@
+package global
+
+import "github.com/flipped-aurora/gin-vue-admin/server/plugin/notify/config"
+
+var GlobalConfig_ = &config.DingDing{}

+ 28 - 0
server/plugin/notify/main.go

@@ -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"
+}

+ 21 - 0
server/plugin/notify/model/response/notify.go

@@ -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"`   // 是否艾特全体
+}

+ 7 - 0
server/plugin/notify/router/enter.go

@@ -0,0 +1,7 @@
+package router
+
+type RouterGroup struct {
+	NotifyRouter
+}
+
+var RouterGroupApp = new(RouterGroup)

+ 18 - 0
server/plugin/notify/router/router.go

@@ -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)
+	}
+}

+ 7 - 0
server/plugin/notify/service/enter.go

@@ -0,0 +1,7 @@
+package service
+
+type ServiceGroup struct {
+	NotifyService
+}
+
+var ServiceGroupApp = new(ServiceGroup)

+ 157 - 0
server/plugin/notify/service/notify.go

@@ -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

+ 1 - 0
server/plugin/notify/utils/utils.go

@@ -0,0 +1 @@
+package utils