feat: add comprehensive README documentation for AIAA Notification Service, detailing core concepts, setup instructions, API endpoints, and configuration options
This commit is contained in:
@@ -0,0 +1,571 @@
|
|||||||
|
# AIAA Notification Service
|
||||||
|
|
||||||
|
纯 Go 实现的多渠道通知服务。上游系统通过 Webhook 推送消息,服务按来源解析、匹配规则、条件过滤、渲染模板后,异步分发到钉钉 / 企业微信 / 邮件 / Bark。
|
||||||
|
|
||||||
|
## 核心概念
|
||||||
|
|
||||||
|
```
|
||||||
|
source 1──N rule
|
||||||
|
template 1──N rule
|
||||||
|
rule N──M channel (via rule_channel,可独立开关)
|
||||||
|
```
|
||||||
|
|
||||||
|
| 实体 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| **Source** | 来源系统,持有独立 `api_key`,定义 body 解析方式(json / regex / text) |
|
||||||
|
| **Template** | Go `text/template` 模板 |
|
||||||
|
| **Channel** | 发送渠道配置(钉钉 / 企微 / 邮件 / Bark) |
|
||||||
|
| **Rule** | 绑定 `source + event → template + channels`,可选条件过滤 |
|
||||||
|
| **MessageLog** | 发送记录,便于排查 |
|
||||||
|
|
||||||
|
**处理流程:**
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /api/v1/notify
|
||||||
|
→ 鉴权(source api_key)+ 限流
|
||||||
|
→ 按 parse_mode 解析 body
|
||||||
|
→ 匹配 enabled 规则
|
||||||
|
→ 条件过滤(全部 AND)
|
||||||
|
→ 渲染模板
|
||||||
|
→ 异步发送到启用渠道(失败重试 3 次:1s / 5s / 30s)
|
||||||
|
→ 立即返回 accepted
|
||||||
|
```
|
||||||
|
|
||||||
|
## 技术栈
|
||||||
|
|
||||||
|
- Go 1.22+、Gin、sqlx、go-redis、Viper、slog
|
||||||
|
- MySQL 8.0、Redis 7
|
||||||
|
- Docker 多阶段构建
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
### 依赖
|
||||||
|
|
||||||
|
- Go 1.22+
|
||||||
|
- MySQL 8.0
|
||||||
|
- Redis 7
|
||||||
|
- [golang-migrate](https://github.com/golang-migrate/migrate)(本地迁移时)
|
||||||
|
|
||||||
|
### Docker Compose(推荐)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
会启动 MySQL、Redis 与 API(默认 `http://localhost:8080`)。
|
||||||
|
|
||||||
|
### 本地开发
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 启动依赖
|
||||||
|
docker-compose up -d mysql redis
|
||||||
|
|
||||||
|
# 2. 迁移数据库
|
||||||
|
make migrate-up
|
||||||
|
|
||||||
|
# 3. 修改配置(务必更换 admin_key)
|
||||||
|
# config/config.yaml
|
||||||
|
|
||||||
|
# 4. 运行
|
||||||
|
make run
|
||||||
|
# 或
|
||||||
|
make build && ./bin/server
|
||||||
|
```
|
||||||
|
|
||||||
|
### 常用命令
|
||||||
|
|
||||||
|
| 命令 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `make run` | 本地启动 |
|
||||||
|
| `make build` | 编译到 `bin/server` |
|
||||||
|
| `make test` | 运行单元测试 |
|
||||||
|
| `make migrate-up` / `migrate-down` | 数据库迁移 |
|
||||||
|
| `make docker-up` / `docker-down` | Compose 启停 |
|
||||||
|
|
||||||
|
### 配置
|
||||||
|
|
||||||
|
主配置文件:`config/config.yaml`。支持 `${ENV_VAR}` / `${ENV_VAR:-default}` 插值,也可用 `NOTIFY_` 前缀环境变量覆盖(`.` → `_`,例如 `NOTIFY_DATABASE_HOST`)。
|
||||||
|
|
||||||
|
| 配置项 | 说明 | 默认 |
|
||||||
|
|--------|------|------|
|
||||||
|
| `server.port` | HTTP 端口 | `8080` |
|
||||||
|
| `server.admin_key` | 管理 API Bearer Token | 须修改 |
|
||||||
|
| `database.*` | MySQL 连接 | `notify` / `notification` |
|
||||||
|
| `redis.*` | Redis(缓存 + 限流) | `127.0.0.1:6379` |
|
||||||
|
| `smtp.*` | 邮件发送(email 渠道) | — |
|
||||||
|
| `rate_limit.default` | 每 source 每秒请求上限 | `100` |
|
||||||
|
|
||||||
|
健康检查:`GET /health` → `{"status":"ok"}`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 认证
|
||||||
|
|
||||||
|
| 接口类型 | Header | 说明 |
|
||||||
|
|----------|--------|------|
|
||||||
|
| 通知接口 | `Authorization: Bearer <source.api_key>` | 创建 Source 时自动生成 |
|
||||||
|
| 管理接口 | `Authorization: Bearer <admin_key>` | 来自 `config.yaml` 的 `server.admin_key` |
|
||||||
|
|
||||||
|
未授权返回 `401`:`{"error":"unauthorized"}` 或 `{"error":"missing api key"}` / `{"error":"invalid api key"}`。
|
||||||
|
|
||||||
|
通知接口超限返回 `429`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error": "rate_limit_exceeded",
|
||||||
|
"message": "...",
|
||||||
|
"retry_after": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 接口文档
|
||||||
|
|
||||||
|
Base URL:`http://localhost:8080`
|
||||||
|
|
||||||
|
通用错误体:`{"error": "<message>"}`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 1. 通知接口
|
||||||
|
|
||||||
|
#### `POST /api/v1/notify`
|
||||||
|
|
||||||
|
上游业务推送入口。Body 解析方式由 Source 的 `parse_mode` 决定。
|
||||||
|
|
||||||
|
**鉴权:** Source API Key
|
||||||
|
**限流:** 按 source,默认 100 req/s(需 Redis)
|
||||||
|
|
||||||
|
##### JSON 模式(`parse_mode=json`)
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /api/v1/notify
|
||||||
|
Authorization: Bearer <source_api_key>
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"event": "trade.open",
|
||||||
|
"data": {
|
||||||
|
"symbol": "BTC",
|
||||||
|
"price": 65000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- 必须包含 `event`(string)
|
||||||
|
- `data` 为模板变量;若省略 `data`,则除 `event` 外的顶层字段作为 data
|
||||||
|
|
||||||
|
##### Regex 模式(`parse_mode=regex`)
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /api/v1/notify
|
||||||
|
Authorization: Bearer <source_api_key>
|
||||||
|
Content-Type: text/plain
|
||||||
|
|
||||||
|
trade.open BTC 开仓 价格:65000
|
||||||
|
```
|
||||||
|
|
||||||
|
- 使用 Source 的 `parse_pattern`(需含命名分组)
|
||||||
|
- 命名分组 `event` 作为事件名,其余命名分组进入模板 data
|
||||||
|
|
||||||
|
示例 pattern:`(?P<event>\w+)\s+(?P<symbol>\w+)\s+(?P<message>.+)`
|
||||||
|
|
||||||
|
##### Text 模式(`parse_mode=text`)
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /api/v1/notify
|
||||||
|
Authorization: Bearer <source_api_key>
|
||||||
|
Content-Type: text/plain
|
||||||
|
|
||||||
|
BTC 突破 68000,请注意风险
|
||||||
|
```
|
||||||
|
|
||||||
|
- body 原样透传,模板变量为 `.Body`
|
||||||
|
- **固定 event 为 `default`**,规则需按 `event=default` 配置
|
||||||
|
|
||||||
|
##### 成功响应
|
||||||
|
|
||||||
|
匹配并接受发送:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"matched": true,
|
||||||
|
"channels": ["dingtalk:1", "email:2"],
|
||||||
|
"accepted": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
匹配但条件未通过:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"matched": true,
|
||||||
|
"filtered": true,
|
||||||
|
"reason": "condition not met"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
无匹配规则:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"matched": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 错误码
|
||||||
|
|
||||||
|
| HTTP | 场景 |
|
||||||
|
|------|------|
|
||||||
|
| 400 | 读取 body 失败 |
|
||||||
|
| 401 | 鉴权失败 |
|
||||||
|
| 422 | 解析失败 / 模板渲染失败 / 规则 conditions 非法 |
|
||||||
|
| 429 | 限流 |
|
||||||
|
| 500 | 模板缺失等内部错误 |
|
||||||
|
|
||||||
|
发送为异步:接口返回 `accepted` 不代表渠道侧已投递成功,请查 Message Log。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Source(来源)
|
||||||
|
|
||||||
|
前缀:`/api/v1/sources`
|
||||||
|
**鉴权:** Admin Key
|
||||||
|
|
||||||
|
#### `POST /api/v1/sources` — 创建
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "trading-system",
|
||||||
|
"parse_mode": "json",
|
||||||
|
"parse_pattern": "",
|
||||||
|
"status": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `name` | string | 是 | 唯一名称 |
|
||||||
|
| `parse_mode` | string | 否 | `json`(默认)/ `regex` / `text` |
|
||||||
|
| `parse_pattern` | string | regex 时建议填 | 正则(命名分组) |
|
||||||
|
| `status` | int | 否 | `1` 启用(默认)/ `0` 禁用 |
|
||||||
|
|
||||||
|
**201** 返回完整 Source(含自动生成的 `api_key`)。名称冲突 → **409**。
|
||||||
|
|
||||||
|
Regex 示例:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "legacy-monitor",
|
||||||
|
"parse_mode": "regex",
|
||||||
|
"parse_pattern": "(?P<event>\\w+)\\s+(?P<message>.+)"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `GET /api/v1/sources` — 列表
|
||||||
|
|
||||||
|
**200** → `Source[]`
|
||||||
|
|
||||||
|
#### `GET /api/v1/sources/:id` — 详情
|
||||||
|
|
||||||
|
**200** → Source;不存在 → **404**
|
||||||
|
|
||||||
|
#### `PUT /api/v1/sources/:id` — 更新
|
||||||
|
|
||||||
|
Body 同创建。成功:`{"ok": true}`
|
||||||
|
|
||||||
|
#### `DELETE /api/v1/sources/:id` — 删除
|
||||||
|
|
||||||
|
成功:`{"ok": true}`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. Template(模板)
|
||||||
|
|
||||||
|
前缀:`/api/v1/templates`
|
||||||
|
**鉴权:** Admin Key
|
||||||
|
|
||||||
|
模板语法为 Go `text/template`,变量来自解析后的 data。
|
||||||
|
|
||||||
|
示例:
|
||||||
|
|
||||||
|
```
|
||||||
|
### {{.symbol}} 开仓
|
||||||
|
价格: {{.price}}
|
||||||
|
```
|
||||||
|
|
||||||
|
Text 模式示例:`{{.Body}}`
|
||||||
|
|
||||||
|
#### `POST /api/v1/templates` — 创建
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "trade_open",
|
||||||
|
"content": "### {{.symbol}} 开仓\n价格: {{.price}}"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 |
|
||||||
|
|------|------|------|
|
||||||
|
| `name` | string | 是 |
|
||||||
|
| `content` | string | 是 |
|
||||||
|
|
||||||
|
**201** → Template;冲突 → **409**
|
||||||
|
|
||||||
|
#### `GET /api/v1/templates` / `GET /api/v1/templates/:id`
|
||||||
|
|
||||||
|
#### `PUT /api/v1/templates/:id` / `DELETE /api/v1/templates/:id`
|
||||||
|
|
||||||
|
更新/删除成功返回 `{"ok": true}`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. Channel(渠道)
|
||||||
|
|
||||||
|
前缀:`/api/v1/channels`
|
||||||
|
**鉴权:** Admin Key
|
||||||
|
|
||||||
|
#### `POST /api/v1/channels` — 创建
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "dingtalk-prod",
|
||||||
|
"type": "dingtalk",
|
||||||
|
"config": { ... },
|
||||||
|
"status": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `name` | string | 是 | 唯一名称(规则里用此名引用) |
|
||||||
|
| `type` | string | 是 | `dingtalk` / `wecom` / `email` / `bark` |
|
||||||
|
| `config` | object | 是 | 见下方各渠道配置 |
|
||||||
|
| `status` | int | 否 | 默认 `1` |
|
||||||
|
|
||||||
|
**201** → Channel;冲突 → **409**
|
||||||
|
|
||||||
|
##### 渠道 config
|
||||||
|
|
||||||
|
**钉钉 `dingtalk`**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"webhook_url": "https://oapi.dingtalk.com/robot/send?access_token=xxx",
|
||||||
|
"secret": "SEC..."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`secret` 可选(加签机器人时填写)。消息类型:markdown。
|
||||||
|
|
||||||
|
**企业微信 `wecom`**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"webhook_url": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
消息类型:markdown。
|
||||||
|
|
||||||
|
**邮件 `email`**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"to": ["ops@example.com", "dev@example.com"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
SMTP 使用全局 `config.yaml` 的 `smtp` 段;支持 587 STARTTLS / 465 TLS。
|
||||||
|
|
||||||
|
**Bark `bark`**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"url": "https://api.day.app/<device_key>"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `GET /api/v1/channels` / `GET /api/v1/channels/:id`
|
||||||
|
|
||||||
|
#### `PUT /api/v1/channels/:id` / `DELETE /api/v1/channels/:id`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. Rule(规则)
|
||||||
|
|
||||||
|
前缀:`/api/v1/rules`
|
||||||
|
**鉴权:** Admin Key
|
||||||
|
|
||||||
|
同一 Source 下 `event` 唯一。
|
||||||
|
|
||||||
|
#### `POST /api/v1/rules` — 创建
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"source_name": "trading-system",
|
||||||
|
"event": "trade.open",
|
||||||
|
"template_name": "trade_open",
|
||||||
|
"channels": ["dingtalk-prod", "email-ops"],
|
||||||
|
"conditions": [
|
||||||
|
{"field": "symbol", "op": "exists"},
|
||||||
|
{"field": "price", "op": "gt", "value": "0"}
|
||||||
|
],
|
||||||
|
"enabled": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `source_name` | string | 是 | Source 名称 |
|
||||||
|
| `event` | string | 是 | 事件名;text 模式请用 `default` |
|
||||||
|
| `template_name` | string | 是 | Template 名称 |
|
||||||
|
| `channels` | string[] | 否 | Channel 名称列表 |
|
||||||
|
| `conditions` | object[] | 否 | 过滤条件,全部 AND;省略则不过滤 |
|
||||||
|
| `enabled` | int | 否 | 默认 `1` |
|
||||||
|
|
||||||
|
**条件操作符:**
|
||||||
|
|
||||||
|
| op | 说明 |
|
||||||
|
|----|------|
|
||||||
|
| `eq` / `ne` | 等于 / 不等于(字符串比较) |
|
||||||
|
| `gt` / `gte` / `lt` / `lte` | 数值比较 |
|
||||||
|
| `exists` / `not_exists` | 字段是否存在 |
|
||||||
|
| `contains` | 字符串包含 |
|
||||||
|
|
||||||
|
条件结构:`{"field":"<字段>","op":"<操作符>","value":"<可选>"}`
|
||||||
|
|
||||||
|
**201** → Rule;冲突 → **409**
|
||||||
|
|
||||||
|
#### `GET /api/v1/rules` / `GET /api/v1/rules/:id`
|
||||||
|
|
||||||
|
#### `PUT /api/v1/rules/:id` — 更新
|
||||||
|
|
||||||
|
Body 同创建。成功:`{"ok": true}`
|
||||||
|
|
||||||
|
#### `DELETE /api/v1/rules/:id`
|
||||||
|
|
||||||
|
成功:`{"ok": true}`(关联 `rule_channel` 级联删除)
|
||||||
|
|
||||||
|
#### 开关
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `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` | 禁用该规则下某渠道 |
|
||||||
|
|
||||||
|
均返回 `{"ok": true}`。`:channel_id` 为渠道数字 ID。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6. Message Log(消息记录)
|
||||||
|
|
||||||
|
#### `GET /api/v1/message-logs`
|
||||||
|
|
||||||
|
**鉴权:** Admin Key
|
||||||
|
|
||||||
|
Query 参数:
|
||||||
|
|
||||||
|
| 参数 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `source` | 按来源名过滤 |
|
||||||
|
| `event` | 按事件过滤 |
|
||||||
|
| `status` | `pending` / `success` / `failed` / `retrying` |
|
||||||
|
| `page` | 页码(从 1) |
|
||||||
|
| `page_size` | 每页条数 |
|
||||||
|
|
||||||
|
**200:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": [ /* MessageLog[] */ ],
|
||||||
|
"total": 100,
|
||||||
|
"page": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
MessageLog 主要字段:`rule_id`、`channel_id`、`source`、`event`、`payload`、`content`、`status`、`retry_count`、`response`、`error_msg`、`created_at`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 端到端示例
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ADMIN="Authorization: Bearer admin-sk-change-me"
|
||||||
|
|
||||||
|
# 1. 创建来源
|
||||||
|
curl -s -X POST http://localhost:8080/api/v1/sources \
|
||||||
|
-H "$ADMIN" -H "Content-Type: application/json" \
|
||||||
|
-d '{"name":"trading-system","parse_mode":"json"}'
|
||||||
|
# 记下返回的 api_key
|
||||||
|
|
||||||
|
# 2. 创建模板
|
||||||
|
curl -s -X POST http://localhost:8080/api/v1/templates \
|
||||||
|
-H "$ADMIN" -H "Content-Type: application/json" \
|
||||||
|
-d '{"name":"trade_open","content":"### {{.symbol}} 开仓\n价格: {{.price}}"}'
|
||||||
|
|
||||||
|
# 3. 创建渠道
|
||||||
|
curl -s -X POST http://localhost:8080/api/v1/channels \
|
||||||
|
-H "$ADMIN" -H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"name":"dingtalk-prod",
|
||||||
|
"type":"dingtalk",
|
||||||
|
"config":{"webhook_url":"https://oapi.dingtalk.com/robot/send?access_token=xxx","secret":"SEC..."}
|
||||||
|
}'
|
||||||
|
|
||||||
|
# 4. 创建规则
|
||||||
|
curl -s -X POST http://localhost:8080/api/v1/rules \
|
||||||
|
-H "$ADMIN" -H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"source_name":"trading-system",
|
||||||
|
"event":"trade.open",
|
||||||
|
"template_name":"trade_open",
|
||||||
|
"channels":["dingtalk-prod"],
|
||||||
|
"conditions":[{"field":"symbol","op":"exists"}]
|
||||||
|
}'
|
||||||
|
|
||||||
|
# 5. 发送通知
|
||||||
|
curl -s -X POST http://localhost:8080/api/v1/notify \
|
||||||
|
-H "Authorization: Bearer <source_api_key>" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"event":"trade.open","data":{"symbol":"BTC","price":65000}}'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
cmd/server/ 入口
|
||||||
|
config/ 配置文件
|
||||||
|
migrations/ MySQL 迁移
|
||||||
|
internal/
|
||||||
|
handler/ HTTP(鉴权、限流、CRUD、notify)
|
||||||
|
engine/ 匹配 / 渲染 / 路由
|
||||||
|
adapter/ 渠道适配器(ChannelSender)
|
||||||
|
parser/ json / regex / text
|
||||||
|
condition/ 条件求值
|
||||||
|
store/ MySQL
|
||||||
|
cache/ Redis
|
||||||
|
model/ 数据模型
|
||||||
|
retry/ 重试
|
||||||
|
config/ 配置加载
|
||||||
|
```
|
||||||
|
|
||||||
|
## 扩展渠道
|
||||||
|
|
||||||
|
实现 `adapter.ChannelSender` 接口并在工厂中注册即可:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type ChannelSender interface {
|
||||||
|
Type() string
|
||||||
|
Send(title, content string, config json.RawMessage) error
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
内部项目,按团队约定使用。
|
||||||
Reference in New Issue
Block a user