Ver código fonte

feat: support tencent custom alarm now (close #87)

JustSong 2 anos atrás
pai
commit
8ead1f76cd

+ 17 - 14
README.md

@@ -58,6 +58,7 @@ _✨ 搭建专属于你的消息推送服务,支持多种消息推送方式,
    + WebSocket 客户端([官方客户端](https://github.com/songquanpeng/personal-assistant),[接入文档](./docs/API.md#websocket-客户端)),
    + Telegram 机器人,
    + Discord 群机器人,
+   + 腾讯云自定义告警:免费的短信提醒,
    + 群组消息:可以将多个推送通道组合成一个群组,然后向群组发送消息,可以实现一次性推送到多个渠道的功能,
    + 自定义消息,可以自定义消息请求 URL 和请求体格式,实现与其他服务的对接,支持[众多第三方服务](https://iamazing.cn/page/message-pusher-common-custom-templates)。
 2. 支持**自定义 Webhook,反向适配各种调用平台**,你可以接入各种已有的系统,而无需修改其代码。
@@ -211,7 +212,8 @@ proxy_send_timeout 300s;
       12. `one_api`:通过 OneAPI 协议推送消息到 QQ。
       13. `group`:通过预先配置的消息推送通道群组进行推送。
       14. `custom`:通过预先配置好的自定义推送通道进行推送。
-      15. `none`:仅保存到数据库,不做推送。
+      15. `tencent_alarm`:通过腾讯云监控告警进行推送,仅支持 `description` 字段。
+      16. `none`:仅保存到数据库,不做推送。
    5. `token`:如果你在后台设置了推送 token,则此项必填。另外可以通过设置 HTTP `Authorization` 头部设置此项。
    6. `url`:选填,如果不填则系统自动为消息生成 URL,其内容为消息详情。
    7. `to`:选填,推送给指定用户,如果不填则默认推送给自己,受限于具体的消息推送方式,有些推送方式不支持此项。
@@ -224,19 +226,20 @@ proxy_send_timeout 300s;
 
 **各种通道的支持程度:**
 
-|    通道类型    | `title` | `description` | `content` | `url` | `to` | Markdown 支持 |
-|:----------:|:-------:|:-------------:|:---------:|:-----:|:----:|:-----------:|
-|  `email`   |    ✅    |       ✅       |     ✅     |   ❌   |  ✅️  |     ✅️      |
-|   `test`   |    ✅    |       ✅       |     ✅     |  ✅️   |  ✅️  |      ✅      |
-| `corp_app` |    ✅    |       ✅       |     ✅     |  ✅️   |  ✅   |      ✅      |
-|   `corp`   |    ❌    |       ✅       |     ✅     |  ✅️   |  ✅️  |      ✅      |
-|   `lark`   |    ❌    |       ✅       |     ✅     |   ❌   |  ✅   |      ✅      |
-| `lark_app` |    ❌    |       ✅       |     ✅     |  ❌️   |  ✅   |      ✅      |
-|   `ding`   |    ✅    |       ✅       |     ✅     |  ✅️   |  ✅   |      ✅      |
-|   `bark`   |    ✅    |       ✅       |     ✅     |  ✅️   |  ❌   |      ✅      |
-|  `client`  |    ✅    |       ✅       |     ❌     |   ❌   |  ❌   |      ❌      |
-| `telegram` |    ❌    |       ❌       |     ✅     |   ❌   |  ✅   |      ✅      |
-| `discord`  |    ❌    |       ❌       |     ✅     |   ❌   |  ✅   |      ❌      |
+|      通道类型       | `title` | `description` | `content` | `url` | `to` | Markdown 支持 |
+|:---------------:|:-------:|:-------------:|:---------:|:-----:|:----:|:-----------:|
+|     `email`     |    ✅    |       ✅       |     ✅     |   ❌   |  ✅️  |     ✅️      |
+|     `test`      |    ✅    |       ✅       |     ✅     |  ✅️   |  ✅️  |      ✅      |
+|   `corp_app`    |    ✅    |       ✅       |     ✅     |  ✅️   |  ✅   |      ✅      |
+|     `corp`      |    ❌    |       ✅       |     ✅     |  ✅️   |  ✅️  |      ✅      |
+|     `lark`      |    ❌    |       ✅       |     ✅     |   ❌   |  ✅   |      ✅      |
+|   `lark_app`    |    ❌    |       ✅       |     ✅     |  ❌️   |  ✅   |      ✅      |
+|     `ding`      |    ✅    |       ✅       |     ✅     |  ✅️   |  ✅   |      ✅      |
+|     `bark`      |    ✅    |       ✅       |     ✅     |  ✅️   |  ❌   |      ✅      |
+|    `client`     |    ✅    |       ✅       |     ❌     |   ❌   |  ❌   |      ❌      |
+|   `telegram`    |    ❌    |       ❌       |     ✅     |   ❌   |  ✅   |      ✅      |
+|    `discord`    |    ❌    |       ❌       |     ✅     |   ❌   |  ✅   |      ❌      |
+| `tencent_alarm` |    ❌    |       ✅       |     ❌     |   ❌   |  ❌   |      ❌      |
 
 注意:
 1. 对于大部分通道,`description` 字段和 `content` 是不能同时存在的,如果你只需要文字消息,请使用 `description` 字段,如果你需要发送 Markdown 消息,请使用 `content` 字段。

+ 2 - 0
channel/main.go

@@ -37,6 +37,8 @@ func SendMessage(message *model.Message, user *model.User, channel_ *model.Chann
 		return SendLarkAppMessage(message, user, channel_)
 	case model.TypeCustom:
 		return SendCustomMessage(message, user, channel_)
+	case model.TypeTencentAlarm:
+		return SendTencentAlarmMessage(message, user, channel_)
 	default:
 		return errors.New("不支持的消息通道:" + channel_.Type)
 	}

+ 89 - 0
channel/tencent-alarm.go

@@ -0,0 +1,89 @@
+package channel
+
+import (
+	"crypto/hmac"
+	"crypto/sha1"
+	"encoding/base64"
+	"encoding/json"
+	"errors"
+	"math/rand"
+	"message-pusher/model"
+	"net/http"
+	"net/url"
+	"sort"
+	"strconv"
+	"strings"
+	"time"
+)
+
+type TencentAlarmResponse struct {
+	Code     int    `json:"code"`
+	Message  string `json:"message"`
+	CodeDesc string `json:"codeDesc"`
+}
+
+func SendTencentAlarmMessage(message *model.Message, user *model.User, channel_ *model.Channel) error {
+	secretId := channel_.AppId
+	secretKey := channel_.Secret
+	policyId := channel_.AccountId
+	region := channel_.Other
+	if message.Description == "" {
+		message.Description = message.Content
+	}
+	params := map[string]string{
+		"Action":    "SendCustomAlarmMsg",
+		"Region":    region,
+		"Timestamp": strconv.FormatInt(time.Now().Unix(), 10),
+		"Nonce":     strconv.Itoa(rand.Intn(65535)),
+		"SecretId":  secretId,
+		"policyId":  policyId,
+		"msg":       message.Description,
+	}
+
+	keys := make([]string, 0, len(params))
+	for key := range params {
+		keys = append(keys, key)
+	}
+	sort.Strings(keys)
+
+	srcStr := "GETmonitor.api.qcloud.com/v2/index.php?"
+	for _, key := range keys {
+		srcStr += key + "=" + params[key] + "&"
+	}
+	srcStr = srcStr[:len(srcStr)-1]
+
+	h := hmac.New(sha1.New, []byte(secretKey))
+	h.Write([]byte(srcStr))
+	signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
+
+	params["Signature"] = signature
+
+	urlStr := "https://monitor.api.qcloud.com/v2/index.php?" + urlEncode(params)
+
+	client := &http.Client{}
+	req, err := http.NewRequest("GET", urlStr, nil)
+	if err != nil {
+		return err
+	}
+	resp, err := client.Do(req)
+	if err != nil {
+		return err
+	}
+	var response TencentAlarmResponse
+	err = json.NewDecoder(resp.Body).Decode(&response)
+	if err != nil {
+		return err
+	}
+	if response.Code != 0 {
+		return errors.New(response.Message)
+	}
+	return nil
+}
+
+func urlEncode(params map[string]string) string {
+	var encodedParams []string
+	for key, value := range params {
+		encodedParams = append(encodedParams, url.QueryEscape(key)+"="+url.QueryEscape(value))
+	}
+	return strings.Join(encodedParams, "&")
+}

+ 1 - 0
model/channel.go

@@ -21,6 +21,7 @@ const (
 	TypeGroup             = "group"
 	TypeLarkApp           = "lark_app"
 	TypeCustom            = "custom"
+	TypeTencentAlarm      = "tencent_alarm"
 )
 
 type Channel struct {

+ 6 - 0
web/src/constants/channel.constants.js

@@ -53,6 +53,12 @@ export const CHANNEL_OPTIONS = [
     value: 'group',
     color: '#FF9800',
   },
+  {
+    key: 'tencent_alarm',
+    text: '腾讯云消息告警',
+    value: 'tencent_alarm',
+    color: '#00a4ff',
+  },
   {
     key: 'none',
     text: '不推送',

+ 52 - 0
web/src/pages/Channel/EditChannel.js

@@ -209,6 +209,58 @@ const EditChannel = () => {
             </Form.Group>
           </>
         );
+      case 'tencent_alarm':
+        return (
+          <>
+            <Message>
+              通过腾讯云自定义消息告警进行推送,
+              <a
+                target='_blank'
+                href='https://github.com/songquanpeng/message-pusher/issues/87#issuecomment-1547971847'
+              >
+                配置教程
+              </a>
+              。
+            </Message>
+            <Form.Group widths={3}>
+              <Form.Input
+                label='SecretId'
+                name='app_id'
+                onChange={handleInputChange}
+                autoComplete='new-password'
+                value={inputs.app_id}
+                placeholder='子账号的 SecretId'
+              />
+              <Form.Input
+                label='SecretKey'
+                name='secret'
+                type='password'
+                onChange={handleInputChange}
+                autoComplete='new-password'
+                value={inputs.secret}
+                placeholder='子账号的 SecretKey'
+              />
+              <Form.Input
+                label='消息策略 ID'
+                name='account_id'
+                onChange={handleInputChange}
+                autoComplete='new-password'
+                value={inputs.account_id}
+                placeholder='例如:cm-6gl3pq19'
+              />
+            </Form.Group>
+            <Form.Group widths={3}>
+              <Form.Input
+                label='区域'
+                name='other'
+                onChange={handleInputChange}
+                autoComplete='new-password'
+                value={inputs.other}
+                placeholder='例如:ap-shanghai'
+              />
+            </Form.Group>
+          </>
+        );
       case 'corp_app':
         return (
           <>