108 lines
3.2 KiB
Go
108 lines
3.2 KiB
Go
package gateway
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"encoding/json"
|
||
|
|
"fmt"
|
||
|
|
|
||
|
|
"github.com/ssdomei232/goodBaby/handler/runner"
|
||
|
|
"github.com/ssdomei232/goodBaby/internal/meta"
|
||
|
|
"github.com/ssdomei232/goodBaby/model"
|
||
|
|
)
|
||
|
|
|
||
|
|
// messageFields 规则配置里可以被投递消息覆盖的字段名
|
||
|
|
var messageFields = []string{"msg", "message", "body"}
|
||
|
|
|
||
|
|
// titleField 规则配置里可以被投递标题覆盖的字段名
|
||
|
|
const titleField = "title"
|
||
|
|
|
||
|
|
// TestMessage 是「测试」网关规则时使用的示例投递内容,
|
||
|
|
// 让用户在页面上就能看到消息被替换后的效果。
|
||
|
|
var TestMessage = Message{
|
||
|
|
Title: "goodBaby 测试标题",
|
||
|
|
Content: "这是一条来自消息网关的测试消息",
|
||
|
|
}
|
||
|
|
|
||
|
|
// WebhookGateway 默认网关:外部系统 POST 一条消息来触发规则
|
||
|
|
type WebhookGateway struct{}
|
||
|
|
|
||
|
|
func (g *WebhookGateway) GetType() string {
|
||
|
|
return model.GatewayTypeWebhook
|
||
|
|
}
|
||
|
|
|
||
|
|
func (g *WebhookGateway) Meta() meta.GatewayMeta {
|
||
|
|
return meta.GatewayMeta{
|
||
|
|
Type: model.GatewayTypeWebhook,
|
||
|
|
Label: "Webhook",
|
||
|
|
Description: "为外部系统生成一个 Webhook 地址,POST 一条消息即可触发绑定在该网关上的规则。",
|
||
|
|
Docs: "docs/gateway-config.md",
|
||
|
|
PayloadHint: `{"title": "监控告警", "message": "服务已恢复"}`,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Deliver 逐条执行绑定在网关上的规则
|
||
|
|
//
|
||
|
|
// 单条规则失败不影响其它规则,失败原因汇总在 Result 里返回。
|
||
|
|
func (g *WebhookGateway) Deliver(ctx context.Context, task *Task) (*Result, error) {
|
||
|
|
// 初始化为空切片:nil 切片会被序列化成 JSON null,前端读 failed.length 时会报错
|
||
|
|
failed := []string{}
|
||
|
|
|
||
|
|
for i := range task.Rules {
|
||
|
|
rule := task.Rules[i]
|
||
|
|
|
||
|
|
executed := rule
|
||
|
|
configJSON, applied, err := ApplyMessage(rule.ConfigJson, task.Message)
|
||
|
|
if err != nil {
|
||
|
|
failed = append(failed, rule.Name+": "+err.Error())
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
if applied {
|
||
|
|
// 用投递进来的消息执行,但不改动数据库里的规则配置
|
||
|
|
executed.ConfigJson = configJSON
|
||
|
|
}
|
||
|
|
if err := runner.ExecuteGatewayRuleWithContext(ctx, &executed, model.TriggerGateway); err != nil {
|
||
|
|
failed = append(failed, rule.Name+": "+err.Error())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return &Result{Total: len(task.Rules), Failed: failed}, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// ApplyMessage 把投递进来的消息覆盖到规则配置的消息字段上。
|
||
|
|
//
|
||
|
|
// 返回覆盖后的配置;applied 为 false 表示配置里没有可覆盖的消息字段
|
||
|
|
// (例如「公开 GitHub 仓库」「删除 DNS 记录」),此时按规则里保存的配置执行。
|
||
|
|
func ApplyMessage(configJSON string, msg Message) (string, bool, error) {
|
||
|
|
var config map[string]any
|
||
|
|
if err := json.Unmarshal([]byte(configJSON), &config); err != nil {
|
||
|
|
return configJSON, false, fmt.Errorf("解析规则配置失败: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
matched := false
|
||
|
|
for _, key := range messageFields {
|
||
|
|
if _, ok := config[key]; ok {
|
||
|
|
if msg.Content != "" {
|
||
|
|
config[key] = msg.Content
|
||
|
|
}
|
||
|
|
matched = true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if msg.Title != "" {
|
||
|
|
if _, ok := config[titleField]; ok {
|
||
|
|
config[titleField] = msg.Title
|
||
|
|
matched = true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if !matched {
|
||
|
|
return configJSON, false, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
out, err := json.Marshal(config)
|
||
|
|
if err != nil {
|
||
|
|
return configJSON, false, fmt.Errorf("生成规则配置失败: %v", err)
|
||
|
|
}
|
||
|
|
return string(out), true, nil
|
||
|
|
}
|