feat(通知): SafeW 只发正文并注入推送时间
去掉渠道标题前缀,模板可用 pushedAt 显示本地推送时间。
This commit is contained in:
@@ -307,9 +307,11 @@ Body 同创建。成功:`{"ok": true}`
|
|||||||
|
|
||||||
```
|
```
|
||||||
### {{.symbol}} {{case .action "OPEN" "开仓" "CLOSE" "平仓" "GAIN" "止盈" "SELL" "卖出" "ADD" "加仓" "REDUCE" "减仓"}}
|
### {{.symbol}} {{case .action "OPEN" "开仓" "CLOSE" "平仓" "GAIN" "止盈" "SELL" "卖出" "ADD" "加仓" "REDUCE" "减仓"}}
|
||||||
{{line "币种" .symbol}}{{line "周期" .period}}{{line "方向" .side}}{{line "价格" .price}}{{line "平均价" .totalAvgPx}}{{line "止盈价" .takeProfitPrice}}{{line "止损价" .stopLossPrice}}
|
{{line "币种" .symbol}}{{line "周期" .period}}{{line "方向" .side}}{{line "价格" .price}}{{line "平均价" .totalAvgPx}}{{line "止盈价" .takeProfitPrice}}{{line "止损价" .stopLossPrice}}{{line "推送时间" .pushedAt}}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
渲染时会自动注入 `event`、`pushedAt`(本地时间 `2006.01.02 15:04:05`)。
|
||||||
|
|
||||||
也可用事件名:`{{case .event ".open" "开仓" ".close" "平仓"}}`
|
也可用事件名:`{{case .event ".open" "开仓" ".close" "平仓"}}`
|
||||||
|
|
||||||
等价写法:`{{with .totalAvgPx}}平均价:{{.}}{{end}}`
|
等价写法:`{{with .totalAvgPx}}平均价:{{.}}{{end}}`
|
||||||
@@ -426,7 +428,7 @@ SMTP 使用全局 `config.yaml` 的 `smtp` 段;支持 587 STARTTLS / 465 TLS
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`chat_id` 可为数字或字符串(含 `@username`)。消息以纯文本发送,不做 Markdown 转义。
|
`chat_id` 可为数字或字符串(含 `@username`)。消息以纯文本发送正文,不附带 `{source}: {event}` 标题,也不做 Markdown 转义。
|
||||||
|
|
||||||
#### `POST /api/v1/channels/safew/chats` — 列出已监控的 SafeW 群
|
#### `POST /api/v1/channels/safew/chats` — 列出已监控的 SafeW 群
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ func (s *SafeWSender) Send(title, content string, config json.RawMessage) error
|
|||||||
|
|
||||||
payload := safewMessage{
|
payload := safewMessage{
|
||||||
ChatID: chatID,
|
ChatID: chatID,
|
||||||
Text: title + "\n" + content,
|
Text: content,
|
||||||
}
|
}
|
||||||
body, err := json.Marshal(payload)
|
body, err := json.Marshal(payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ func TestSafeWSenderSendSuccess(t *testing.T) {
|
|||||||
if gotBody["parse_mode"] != nil && gotBody["parse_mode"] != "" {
|
if gotBody["parse_mode"] != nil && gotBody["parse_mode"] != "" {
|
||||||
t.Fatalf("parse_mode = %#v, want omitted/plain", gotBody["parse_mode"])
|
t.Fatalf("parse_mode = %#v, want omitted/plain", gotBody["parse_mode"])
|
||||||
}
|
}
|
||||||
wantText := "hello_world.\nprice=1.5"
|
wantText := "price=1.5"
|
||||||
if gotBody["text"] != wantText {
|
if gotBody["text"] != wantText {
|
||||||
t.Fatalf("text = %#v, want %#v", gotBody["text"], wantText)
|
t.Fatalf("text = %#v, want %#v", gotBody["text"], wantText)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"log/slog"
|
"log/slog"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"aiaa-notification-service/internal/condition"
|
"aiaa-notification-service/internal/condition"
|
||||||
"aiaa-notification-service/internal/engine"
|
"aiaa-notification-service/internal/engine"
|
||||||
@@ -85,6 +86,9 @@ func (s *Service) Process(ctx context.Context, req Request) (Result, error) {
|
|||||||
if _, ok := req.Data["event"]; !ok && req.Event != "" {
|
if _, ok := req.Data["event"]; !ok && req.Event != "" {
|
||||||
req.Data["event"] = req.Event
|
req.Data["event"] = req.Event
|
||||||
}
|
}
|
||||||
|
if _, ok := req.Data["pushedAt"]; !ok {
|
||||||
|
req.Data["pushedAt"] = time.Now().In(time.Local).Format("2006.01.02 15:04:05")
|
||||||
|
}
|
||||||
|
|
||||||
content, err := s.renderer.Render(tmpl.Content, req.Data)
|
content, err := s.renderer.Render(tmpl.Content, req.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"aiaa-notification-service/internal/engine"
|
"aiaa-notification-service/internal/engine"
|
||||||
@@ -28,9 +29,15 @@ func (f *fakeTemplates) GetTemplate(context.Context, int) (*model.Template, erro
|
|||||||
return f.tmpl, f.err
|
return f.tmpl, f.err
|
||||||
}
|
}
|
||||||
|
|
||||||
type fakeRouter struct{ channels []string }
|
type fakeRouter struct {
|
||||||
|
channels []string
|
||||||
|
title string
|
||||||
|
content string
|
||||||
|
}
|
||||||
|
|
||||||
func (f *fakeRouter) Route(context.Context, *model.Rule, string, string) []string {
|
func (f *fakeRouter) Route(_ context.Context, _ *model.Rule, title, content string) []string {
|
||||||
|
f.title = title
|
||||||
|
f.content = content
|
||||||
return f.channels
|
return f.channels
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,6 +118,29 @@ func TestProcessTemplateCanSwitchOnEvent(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestProcessInjectsPushedAt(t *testing.T) {
|
||||||
|
rt := &fakeRouter{channels: []string{"safew:1"}}
|
||||||
|
svc := newSvc(
|
||||||
|
&fakeMatcher{rule: &model.Rule{ID: 9, TemplateID: 1}},
|
||||||
|
&fakeTemplates{tmpl: &model.Template{ID: 1, Content: `{{line "推送时间" .pushedAt}}`}},
|
||||||
|
rt,
|
||||||
|
)
|
||||||
|
res, err := svc.Process(context.Background(), Request{
|
||||||
|
Source: &model.Source{ID: 1, Name: "crypto-strategy"},
|
||||||
|
Event: "trade.open",
|
||||||
|
Data: map[string]interface{}{"symbol": "QNT"},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !res.Matched {
|
||||||
|
t.Fatalf("%+v", res)
|
||||||
|
}
|
||||||
|
if !strings.Contains(rt.content, "推送时间:") {
|
||||||
|
t.Fatalf("content=%q", rt.content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestProcessInvalidConditions(t *testing.T) {
|
func TestProcessInvalidConditions(t *testing.T) {
|
||||||
raw := json.RawMessage(`not-json`)
|
raw := json.RawMessage(`not-json`)
|
||||||
svc := newSvc(&fakeMatcher{rule: &model.Rule{ID: 1, TemplateID: 1, Conditions: &raw}}, &fakeTemplates{}, &fakeRouter{})
|
svc := newSvc(&fakeMatcher{rule: &model.Rule{ID: 1, TemplateID: 1, Conditions: &raw}}, &fakeTemplates{}, &fakeRouter{})
|
||||||
|
|||||||
Reference in New Issue
Block a user