Files
aiaa-notification-server/docs/superpowers/specs/2025-06-27-notification-service-design.md
T

12 KiB
Raw Blame History

Notification Service — 设计规格

概述

纯 Go 实现的通知服务。上游业务系统通过 webhook 推送消息,服务匹配规则、渲染模板、路由到多渠道发送。

核心三层关系:

source 1──N rule
template 1──N rule  
rule N──M channel  (via rule_channel)

技术栈

选型
语言 Go 1.22+
HTTP 框架 gin-gonic/gin
数据库驱动 go-sql-driver/mysql
SQL 工具 jmoiron/sqlx
模板引擎 text/template (标准库)
缓存 go-redis/redis
配置 viper
日志 slog (标准库)
迁移 golang-migrate

外部依赖: MySQL 8.0、Redis 7

部署形态: Docker 容器化 (docker-compose),最终镜像 ~12MB


架构

notification-service (Go)
│
├── cmd/server/main.go          ← 入口
├── internal/
│   ├── handler/                ← HTTP Handler (gin)
│   │   ├── notify.go           ← POST /api/v1/notify (上游调用)
│   │   ├── source.go           ← CRUD /api/v1/sources
│   │   ├── template.go         ← CRUD /api/v1/templates
│   │   ├── channel.go          ← CRUD /api/v1/channels
│   │   └── rule.go             ← CRUD /api/v1/rules + 开关控制
│   │
│   ├── engine/                 ← 核心引擎
│   │   ├── matcher.go          ← source + event → rule 匹配
│   │   ├── renderer.go         ← Go template 渲染
│   │   └── router.go           ← rule → channels 路由
│   │
│   ├── adapter/                ← 渠道适配器
│   │   ├── adapter.go          ← ChannelSender 接口
│   │   ├── dingtalk.go         ← 钉钉 webhook
│   │   ├── wecom.go            ← 企业微信机器人
│   │   ├── email.go            ← SMTP 邮件
│   │   └── bark.go             ← Bark HTTP
│   │
│   ├── model/                  ← 数据模型
│   ├── store/                  ← MySQL 访问层
│   └── cache/                  ← Redis 缓存层
│
├── config/config.yaml
├── migrations/
├── Dockerfile
├── docker-compose.yml
└── go.mod

数据模型

source(来源系统)

类型 说明
id INT PK
name VARCHAR(64) UNIQUE trading-system
api_key VARCHAR(128) UNIQUE 通知 API 鉴权 token,创建 source 时自动生成
parse_mode VARCHAR(16) DEFAULT 'json' json / regex / texttext=直接透传 body 到模板)
parse_pattern VARCHAR(512) regex 模式(含命名分组),parse_mode=regex 时必填
status TINYINT 1=启用 0=禁用

template(模板)

类型 说明
id INT PK
name VARCHAR(64) UNIQUE trade_open
content TEXT Go template 语法,markdown/纯文本

channel(渠道配置)

类型 说明
id INT PK
name VARCHAR(32) UNIQUE dingtalk-prod
type VARCHAR(32) dingtalk / wecom / email / bark
config JSON 渠道特定配置 (webhook URL 等)
status TINYINT 1=启用 0=禁用

rule(规则 — 核心绑定)

类型 说明
id INT PK
source_id INT FK→source
event VARCHAR(64) trade.open / error / order.created
template_id INT FK→template
conditions JSON 过滤条件数组,null=不过滤。例: [{"field":"symbol","op":"exists"}]
enabled TINYINT 1=启用 0=禁用
UNIQUE (source_id, event) 每个 source 下 event 唯一

条件操作符: eq / ne / gt / gte / lt / lte / exists / not_exists / contains 多个条件 AND 关系,全部满足才转发。

rule_channel(规则-渠道关联 — 独立开关)

类型 说明
id INT PK
rule_id INT FK→rule ON DELETE CASCADE
channel_id INT FK→channel
enabled TINYINT 每条规则下每个渠道可独立开关
UNIQUE (rule_id, channel_id)

message_log(消息记录 — 排查用)

类型 说明
id BIGINT PK
rule_id INT
channel_id INT
source VARCHAR(64) 冗余,方便查询
event VARCHAR(64) 冗余,方便查询
payload JSON 原始请求 data
content TEXT 渲染后内容
status ENUM pending/success/failed/retrying
retry_count INT
response TEXT 渠道返回
error_msg TEXT
created_at DATETIME

API 设计

认证

  • 通知接口Authorization: Bearer <source.api_key> — 通过 api_key 识别来源系统
  • 管理接口:Authorization: Bearer <admin_key> — 全局管理 Key
  • Content-Type 不限 — source.parse_mode 决定如何解析 body
    • json: body 按 JSON 解析,event 取自 JSON 字段
    • regex: body 按正则解析,命名分组提取字段,event 需在 body 中显式提供
    • text: body 原样透传到模板(模板引用 .Body

通知接口

JSON 模式(parse_mode=json):

POST /api/v1/notify
Authorization: Bearer <source_api_key>
Content-Type: application/json

{
  "event": "trade.open",
  "data": {
    "symbol": "BTC",
    "price": 65000
  }
}

Regex 模式(parse_mode=regexparse_pattern 定义提取规则):

POST /api/v1/notify
Authorization: Bearer <source_api_key>
Content-Type: text/plain

BTC 开仓 价格:65000 方向:多

Text 模式(parse_mode=textbody 直接给模板):

POST /api/v1/notify
Authorization: Bearer <source_api_key>
Content-Type: text/plain

🚀 BTC 突破 68000,请注意风险

响应:

Response 200 (匹配且通过条件):
{
  "matched": true,
  "channels": ["dingtalk", "email"],
  "accepted": true
}

Response 200 (匹配但条件不满足):
{ "matched": true, "filtered": true, "reason": "condition not met" }

Response 200 (无匹配规则):
{ "matched": false }

Response 401:
{ "error": "unauthorized" }

Response 429:
{ "error": "rate_limit_exceeded", "message": "...", "retry_after": 1 }

管理 API

来源:
POST   /api/v1/sources
GET    /api/v1/sources
GET    /api/v1/sources/:id
PUT    /api/v1/sources/:id
DELETE /api/v1/sources/:id

模板:
POST   /api/v1/templates
GET    /api/v1/templates
GET    /api/v1/templates/:id
PUT    /api/v1/templates/:id
DELETE /api/v1/templates/:id

渠道:
POST   /api/v1/channels
GET    /api/v1/channels
GET    /api/v1/channels/:id
PUT    /api/v1/channels/:id
DELETE /api/v1/channels/:id

规则:
POST   /api/v1/rules
GET    /api/v1/rules
GET    /api/v1/rules/:id
PUT    /api/v1/rules/:id
DELETE /api/v1/rules/:id
PATCH  /api/v1/rules/:id/enable
PATCH  /api/v1/rules/:id/disable
PATCH  /api/v1/rules/:id/channels/:channel_id/enable
PATCH  /api/v1/rules/:id/channels/:channel_id/disable

消息记录:
GET    /api/v1/message-logs?source=&event=&status=&page=1&page_size=20

关键请求示例

创建渠道:

POST /api/v1/channels
{
  "name": "dingtalk-prod",
  "type": "dingtalk",
  "config": {
    "webhook_url": "https://oapi.dingtalk.com/robot/send?access_token=xxx",
    "secret": "SEC..."
  }
}

创建规则(带条件过滤):

POST /api/v1/rules
{
  "source_name": "trading-system",
  "event": "trade.open",
  "template_name": "trade_open",
  "channels": ["dingtalk", "email"],
  "conditions": [
    {"field": "symbol", "op": "exists"},
    {"field": "price", "op": "gt", "value": "0"}
  ]
}

创建 sourceRegex 解析模式):

POST /api/v1/sources
{
  "name": "legacy-monitor",
  "parse_mode": "regex",
  "parse_pattern": "(?P<event>\\w+)\\s+(?P<message>.+)"
}

核心执行流程

POST /api/v1/notify
    │
    ▼
1. 鉴权 ── api_key 反查 source → source_id + parse_mode + parse_pattern
   key 无效 → 401
    │
    ▼
2. 消息解析 ── 根据 source.parse_mode:
   json:   body → JSON decode → event + data
   regex:  body → regex 匹配 → 命名分组 → data map
   text:   body → .Body 字段
    │
    ▼
3. 规则匹配 ── 缓存: notify:rule:{source_id}:{event}
   miss → SELECT * FROM rule WHERE source_id=? AND event=? AND enabled=1
   无匹配 → 200 {matched: false}
    │
    ▼
4. 条件过滤 ── 检查 rule.conditions (如果非空)
   对每条 condition 求值 (eq/ne/gt/lt/exists/contains...)
   任一不满足 → 200 {matched: true, filtered: true}
    │
    ▼
5. 模板渲染 ── Go text/template 渲染 template.content + 解析后的 data
   失败 → 422 {error: "template render failed"}
    │
    ▼
6. 查找渠道 ── 缓存: notify:channels:{rule_id}
   miss → SELECT * FROM rule_channel WHERE rule_id=? AND enabled=1
   无可用渠道 → 200 {matched: true, sent: 0}
    │
    ▼
7. 并发发送 ── 每个渠道 goroutine 异步发送
   记录 message_log
   失败 → 重试队列 (3次, 指数退避 1s/5s/30s)
    │
    ▼
8. 立即返回 200 {matched: true, channels: [...], accepted: true}

渠道适配器

type ChannelSender interface {
    Type() string
    Send(title, content string, config json.RawMessage) error
}
渠道 协议 备注
钉钉 POST webhook 签名计算 + markdown 消息体
企业微信 POST webhook markdown/json 消息体
邮件 SMTP net/smtp 标准库,支持 TLS
Bark POST HTTP title + body POST 到设备 URL

新增渠道:实现 ChannelSender 接口 + 工厂注册一行。


缓存策略

Key 内容 TTL 失效
notify:rule:{source_id}:{event} rule_id + template_id + template_content 5min 规则/模板 CUD 时主动删除
notify:channels:{rule_id} [channel_id, type, config] 5min 规则-渠道变更时主动删除

模式:Cache-Aside,管理 API 做 CUD 时主动失效,不依赖 TTL 被动过期。


限流 & 重试

限流:

  • Per-source 限流,默认 100 req/s
  • Redis 滑动窗口: ratelimit:{source_id}:{window} 1秒窗口
  • 超限返回 429 + Retry-After

重试:

  • 异步发送失败 → 内存重试队列
  • 最多 3 次,退避 1s → 5s → 30s
  • 3 次全失败 → message_log.status = 'failed'
  • 后续可扩展为 Redis 死信队列 + 手动重发 API

配置

server:
  port: 8080
  admin_key: "admin-sk-xxx"

database:
  host: mysql
  port: 3306
  user: notify
  password: ${DB_PASSWORD}
  database: notification

redis:
  host: redis
  port: 6379

smtp:
  host: smtp.example.com
  port: 587
  user: notify@example.com
  password: ${SMTP_PASSWORD}

rate_limit:
  default: 100

部署

docker-compose.yml:

services:
  mysql:
    image: mysql:8.0
  redis:
    image: redis:7-alpine
  api:
    build: .
    ports: ["8080:8080"]
    depends_on: [mysql, redis]

Dockerfile: 多阶段构建 (golang:1.22-alpine → alpine:3.20),最终镜像 ~12MB。


v1 范围

  • Source/Template/Channel/Rule CRUD 管理 API
  • POST /api/v1/notify 核心通知接口
  • 消息解析:JSON / Regex / Text 三种模式
  • 规则条件过滤(eq/ne/gt/lt/exists/not_exists/contains
  • API Key 鉴权(source 用 source key,管理用 admin key
  • 4 个渠道:钉钉、企业微信、邮件、Bark
  • Go template 渲染
  • 规则级 + 规则-渠道级开关控制
  • 异步发送 + 3 次指数退避重试
  • Redis 缓存规则和渠道配置
  • Per-source 限流
  • message_log 消息记录
  • Docker 部署