From c74bab30336b3bb73a194311d38dcaa887ce604d Mon Sep 17 00:00:00 2001 From: ryan Date: Fri, 31 Jul 2026 22:09:39 +0800 Subject: [PATCH] =?UTF-8?q?fix(=E6=95=B0=E6=8D=AE=E5=AD=98=E5=82=A8):=20?= =?UTF-8?q?=E7=A1=AE=E4=BF=9D=E5=88=97=E8=A1=A8=E6=9F=A5=E8=AF=A2=E8=BF=94?= =?UTF-8?q?=E5=9B=9E=E7=A9=BA=E6=95=B0=E7=BB=84=E8=80=8C=E9=9D=9E=20null?= =?UTF-8?q?=EF=BC=8C=E5=B9=B6=E8=A1=A5=E5=85=85=20HTTPie/Postman=20API=20?= =?UTF-8?q?=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Motivation: 当数据库查询无结果时,Go 的 `var slice []Type` 声明会使 JSON 序列化输出 `null`,而非 `[]`。这在 API 响应中造成不一致,也增加了客户端的空值处理负担。改为 `make([]Type, 0)` 后空结果统一输出为 `[]`,提升 API 规范性。 Changes: * 统一 store 层所有 List 方法使用 `make([]Type, 0)` 初始化切片,覆盖 Sources、Channels、Templates、Rules、MessageLogs 及 RuleChannels 查询 * 新增完整的 cURL 示例文档,覆盖全部 API 端点(通知、Source、Template、Channel、Rule、消息记录) * 新增 Postman Collection JSON 文件,可直接导入 HTTPie 使用,包含环境变量配置 --- docs/httpie/curls.md | 396 +++++++++++++++++++ docs/httpie/postman_collection.json | 566 ++++++++++++++++++++++++++++ internal/store/channel.go | 2 +- internal/store/message_log.go | 2 +- internal/store/rule.go | 4 +- internal/store/source.go | 2 +- internal/store/template.go | 2 +- 7 files changed, 968 insertions(+), 6 deletions(-) create mode 100644 docs/httpie/curls.md create mode 100644 docs/httpie/postman_collection.json diff --git a/docs/httpie/curls.md b/docs/httpie/curls.md new file mode 100644 index 0000000..154d40c --- /dev/null +++ b/docs/httpie/curls.md @@ -0,0 +1,396 @@ +# HTTPie 可导入 cURL 集合 + +**推荐:** 直接导入 Postman 集合(一次导入全部) + +``` +docs/httpie/postman_collection.json +``` + +HTTPie → Import → Postman → 选择该文件。 + +**单条导入:** Import → cURL → 粘贴下方某一条完整 `curl`。 + +替换占位符: + +| 占位符 | 说明 | +|--------|------| +| `http://localhost:8080` | 服务地址 | +| `admin-sk-change-me` | `config.yaml` 的 `server.admin_key` | +| `SOURCE_API_KEY` | 创建 Source 返回的 `api_key` | +| `:id` / `1` | 资源数字 ID | + +--- + +## Health + +### 健康检查 + +```bash +curl -X GET 'http://localhost:8080/health' +``` + +--- + +## Notify + +### 发送通知(JSON) + +```bash +curl -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 + } + }' +``` + +### 发送通知(Regex) + +```bash +curl -X POST 'http://localhost:8080/api/v1/notify' \ + -H 'Authorization: Bearer SOURCE_API_KEY' \ + -H 'Content-Type: text/plain' \ + --data-binary 'trade.open BTC 开仓 价格:65000' +``` + +### 发送通知(Text,event=default) + +```bash +curl -X POST 'http://localhost:8080/api/v1/notify' \ + -H 'Authorization: Bearer SOURCE_API_KEY' \ + -H 'Content-Type: text/plain' \ + --data-binary 'BTC 突破 68000,请注意风险' +``` + +--- + +## Sources + +### 创建 Source(JSON) + +```bash +curl -X POST 'http://localhost:8080/api/v1/sources' \ + -H 'Authorization: Bearer admin-sk-change-me' \ + -H 'Content-Type: application/json' \ + -d '{ + "name": "trading-system", + "parse_mode": "json", + "status": 1 + }' +``` + +### 创建 Source(Regex) + +```bash +curl -X POST 'http://localhost:8080/api/v1/sources' \ + -H 'Authorization: Bearer admin-sk-change-me' \ + -H 'Content-Type: application/json' \ + -d '{ + "name": "legacy-monitor", + "parse_mode": "regex", + "parse_pattern": "(?P\\w+)\\s+(?P.+)" + }' +``` + +### 创建 Source(Text) + +```bash +curl -X POST 'http://localhost:8080/api/v1/sources' \ + -H 'Authorization: Bearer admin-sk-change-me' \ + -H 'Content-Type: application/json' \ + -d '{ + "name": "alert-bot", + "parse_mode": "text" + }' +``` + +### 列出 Source + +```bash +curl -X GET 'http://localhost:8080/api/v1/sources' \ + -H 'Authorization: Bearer admin-sk-change-me' +``` + +### 获取 Source + +```bash +curl -X GET 'http://localhost:8080/api/v1/sources/1' \ + -H 'Authorization: Bearer admin-sk-change-me' +``` + +### 更新 Source + +```bash +curl -X PUT 'http://localhost:8080/api/v1/sources/1' \ + -H 'Authorization: Bearer admin-sk-change-me' \ + -H 'Content-Type: application/json' \ + -d '{ + "name": "trading-system", + "parse_mode": "json", + "status": 1 + }' +``` + +### 删除 Source + +```bash +curl -X DELETE 'http://localhost:8080/api/v1/sources/1' \ + -H 'Authorization: Bearer admin-sk-change-me' +``` + +--- + +## Templates + +### 创建 Template + +```bash +curl -X POST 'http://localhost:8080/api/v1/templates' \ + -H 'Authorization: Bearer admin-sk-change-me' \ + -H 'Content-Type: application/json' \ + -d '{ + "name": "trade_open", + "content": "### {{.symbol}} 开仓\n价格: {{.price}}" + }' +``` + +### 列出 Template + +```bash +curl -X GET 'http://localhost:8080/api/v1/templates' \ + -H 'Authorization: Bearer admin-sk-change-me' +``` + +### 获取 Template + +```bash +curl -X GET 'http://localhost:8080/api/v1/templates/1' \ + -H 'Authorization: Bearer admin-sk-change-me' +``` + +### 更新 Template + +```bash +curl -X PUT 'http://localhost:8080/api/v1/templates/1' \ + -H 'Authorization: Bearer admin-sk-change-me' \ + -H 'Content-Type: application/json' \ + -d '{ + "name": "trade_open", + "content": "### {{.symbol}} 开仓\n价格: {{.price}}" + }' +``` + +### 删除 Template + +```bash +curl -X DELETE 'http://localhost:8080/api/v1/templates/1' \ + -H 'Authorization: Bearer admin-sk-change-me' +``` + +--- + +## Channels + +### 创建 Channel(Email) + +```bash +curl -X POST 'http://localhost:8080/api/v1/channels' \ + -H 'Authorization: Bearer admin-sk-change-me' \ + -H 'Content-Type: application/json' \ + -d '{ + "name": "email-ops", + "type": "email", + "config": { + "to": ["148516886@qq.com"] + }, + "status": 1 + }' +``` + +### 创建 Channel(钉钉) + +```bash +curl -X POST 'http://localhost:8080/api/v1/channels' \ + -H 'Authorization: Bearer admin-sk-change-me' \ + -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..." + }, + "status": 1 + }' +``` + +### 创建 Channel(企业微信) + +```bash +curl -X POST 'http://localhost:8080/api/v1/channels' \ + -H 'Authorization: Bearer admin-sk-change-me' \ + -H 'Content-Type: application/json' \ + -d '{ + "name": "wecom-ops", + "type": "wecom", + "config": { + "webhook_url": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx" + }, + "status": 1 + }' +``` + +### 创建 Channel(Bark) + +```bash +curl -X POST 'http://localhost:8080/api/v1/channels' \ + -H 'Authorization: Bearer admin-sk-change-me' \ + -H 'Content-Type: application/json' \ + -d '{ + "name": "bark-phone", + "type": "bark", + "config": { + "url": "https://api.day.app/" + }, + "status": 1 + }' +``` + +### 列出 Channel + +```bash +curl -X GET 'http://localhost:8080/api/v1/channels' \ + -H 'Authorization: Bearer admin-sk-change-me' +``` + +### 获取 Channel + +```bash +curl -X GET 'http://localhost:8080/api/v1/channels/1' \ + -H 'Authorization: Bearer admin-sk-change-me' +``` + +### 更新 Channel + +```bash +curl -X PUT 'http://localhost:8080/api/v1/channels/1' \ + -H 'Authorization: Bearer admin-sk-change-me' \ + -H 'Content-Type: application/json' \ + -d '{ + "name": "email-ops", + "type": "email", + "config": { + "to": ["148516886@qq.com"] + }, + "status": 1 + }' +``` + +### 删除 Channel + +```bash +curl -X DELETE 'http://localhost:8080/api/v1/channels/1' \ + -H 'Authorization: Bearer admin-sk-change-me' +``` + +--- + +## Rules + +### 创建 Rule + +```bash +curl -X POST 'http://localhost:8080/api/v1/rules' \ + -H 'Authorization: Bearer admin-sk-change-me' \ + -H 'Content-Type: application/json' \ + -d '{ + "source_name": "trading-system", + "event": "trade.open", + "template_name": "trade_open", + "channels": ["email-ops"], + "conditions": [ + {"field": "symbol", "op": "exists"}, + {"field": "price", "op": "gt", "value": "0"} + ], + "enabled": 1 + }' +``` + +### 列出 Rule + +```bash +curl -X GET 'http://localhost:8080/api/v1/rules' \ + -H 'Authorization: Bearer admin-sk-change-me' +``` + +### 获取 Rule + +```bash +curl -X GET 'http://localhost:8080/api/v1/rules/1' \ + -H 'Authorization: Bearer admin-sk-change-me' +``` + +### 更新 Rule + +```bash +curl -X PUT 'http://localhost:8080/api/v1/rules/1' \ + -H 'Authorization: Bearer admin-sk-change-me' \ + -H 'Content-Type: application/json' \ + -d '{ + "source_name": "trading-system", + "event": "trade.open", + "template_name": "trade_open", + "channels": ["email-ops"], + "enabled": 1 + }' +``` + +### 删除 Rule + +```bash +curl -X DELETE 'http://localhost:8080/api/v1/rules/1' \ + -H 'Authorization: Bearer admin-sk-change-me' +``` + +### 启用 Rule + +```bash +curl -X PATCH 'http://localhost:8080/api/v1/rules/1/enable' \ + -H 'Authorization: Bearer admin-sk-change-me' +``` + +### 禁用 Rule + +```bash +curl -X PATCH 'http://localhost:8080/api/v1/rules/1/disable' \ + -H 'Authorization: Bearer admin-sk-change-me' +``` + +### 启用规则下某渠道 + +```bash +curl -X PATCH 'http://localhost:8080/api/v1/rules/1/channels/1/enable' \ + -H 'Authorization: Bearer admin-sk-change-me' +``` + +### 禁用规则下某渠道 + +```bash +curl -X PATCH 'http://localhost:8080/api/v1/rules/1/channels/1/disable' \ + -H 'Authorization: Bearer admin-sk-change-me' +``` + +--- + +## Message Logs + +### 查询消息记录 + +```bash +curl -X GET 'http://localhost:8080/api/v1/message-logs?source=trading-system&event=trade.open&status=success&page=1&page_size=20' \ + -H 'Authorization: Bearer admin-sk-change-me' +``` diff --git a/docs/httpie/postman_collection.json b/docs/httpie/postman_collection.json new file mode 100644 index 0000000..c3fe645 --- /dev/null +++ b/docs/httpie/postman_collection.json @@ -0,0 +1,566 @@ +{ + "info": { + "_postman_id": "aiaa-notification-service", + "name": "AIAA Notification Service", + "description": "通知服务 API。HTTPie:Import → Postman → 选择本文件。\n\n变量:\n- baseUrl:服务地址\n- adminKey:config.yaml → server.admin_key\n- sourceApiKey:创建 Source 时返回的 api_key", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "variable": [ + { "key": "baseUrl", "value": "http://localhost:8080" }, + { "key": "adminKey", "value": "admin-sk-change-me" }, + { "key": "sourceApiKey", "value": "SOURCE_API_KEY" }, + { "key": "sourceId", "value": "1" }, + { "key": "templateId", "value": "1" }, + { "key": "channelId", "value": "1" }, + { "key": "ruleId", "value": "1" } + ], + "item": [ + { + "name": "Health", + "item": [ + { + "name": "健康检查", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/health" + } + } + ] + }, + { + "name": "Notify", + "item": [ + { + "name": "发送通知(JSON)", + "request": { + "auth": { + "type": "bearer", + "bearer": [{ "key": "token", "value": "{{sourceApiKey}}", "type": "string" }] + }, + "method": "POST", + "header": [ + { "key": "Content-Type", "value": "application/json" } + ], + "body": { + "mode": "raw", + "raw": "{\n \"event\": \"trade.open\",\n \"data\": {\n \"symbol\": \"BTC\",\n \"price\": 65000\n }\n}" + }, + "url": "{{baseUrl}}/api/v1/notify" + } + }, + { + "name": "发送通知(Regex)", + "request": { + "auth": { + "type": "bearer", + "bearer": [{ "key": "token", "value": "{{sourceApiKey}}", "type": "string" }] + }, + "method": "POST", + "header": [ + { "key": "Content-Type", "value": "text/plain" } + ], + "body": { + "mode": "raw", + "raw": "trade.open BTC 开仓 价格:65000" + }, + "url": "{{baseUrl}}/api/v1/notify" + } + }, + { + "name": "发送通知(Text)", + "request": { + "auth": { + "type": "bearer", + "bearer": [{ "key": "token", "value": "{{sourceApiKey}}", "type": "string" }] + }, + "method": "POST", + "header": [ + { "key": "Content-Type", "value": "text/plain" } + ], + "body": { + "mode": "raw", + "raw": "BTC 突破 68000,请注意风险" + }, + "url": "{{baseUrl}}/api/v1/notify", + "description": "parse_mode=text 时 event 固定为 default" + } + } + ] + }, + { + "name": "Sources", + "item": [ + { + "name": "创建 Source(JSON)", + "request": { + "auth": { + "type": "bearer", + "bearer": [{ "key": "token", "value": "{{adminKey}}", "type": "string" }] + }, + "method": "POST", + "header": [ + { "key": "Content-Type", "value": "application/json" } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"trading-system\",\n \"parse_mode\": \"json\",\n \"status\": 1\n}" + }, + "url": "{{baseUrl}}/api/v1/sources" + } + }, + { + "name": "创建 Source(Regex)", + "request": { + "auth": { + "type": "bearer", + "bearer": [{ "key": "token", "value": "{{adminKey}}", "type": "string" }] + }, + "method": "POST", + "header": [ + { "key": "Content-Type", "value": "application/json" } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"legacy-monitor\",\n \"parse_mode\": \"regex\",\n \"parse_pattern\": \"(?P\\\\w+)\\\\s+(?P.+)\"\n}" + }, + "url": "{{baseUrl}}/api/v1/sources" + } + }, + { + "name": "创建 Source(Text)", + "request": { + "auth": { + "type": "bearer", + "bearer": [{ "key": "token", "value": "{{adminKey}}", "type": "string" }] + }, + "method": "POST", + "header": [ + { "key": "Content-Type", "value": "application/json" } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"alert-bot\",\n \"parse_mode\": \"text\"\n}" + }, + "url": "{{baseUrl}}/api/v1/sources" + } + }, + { + "name": "列出 Source", + "request": { + "auth": { + "type": "bearer", + "bearer": [{ "key": "token", "value": "{{adminKey}}", "type": "string" }] + }, + "method": "GET", + "header": [], + "url": "{{baseUrl}}/api/v1/sources" + } + }, + { + "name": "获取 Source", + "request": { + "auth": { + "type": "bearer", + "bearer": [{ "key": "token", "value": "{{adminKey}}", "type": "string" }] + }, + "method": "GET", + "header": [], + "url": "{{baseUrl}}/api/v1/sources/{{sourceId}}" + } + }, + { + "name": "更新 Source", + "request": { + "auth": { + "type": "bearer", + "bearer": [{ "key": "token", "value": "{{adminKey}}", "type": "string" }] + }, + "method": "PUT", + "header": [ + { "key": "Content-Type", "value": "application/json" } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"trading-system\",\n \"parse_mode\": \"json\",\n \"status\": 1\n}" + }, + "url": "{{baseUrl}}/api/v1/sources/{{sourceId}}" + } + }, + { + "name": "删除 Source", + "request": { + "auth": { + "type": "bearer", + "bearer": [{ "key": "token", "value": "{{adminKey}}", "type": "string" }] + }, + "method": "DELETE", + "header": [], + "url": "{{baseUrl}}/api/v1/sources/{{sourceId}}" + } + } + ] + }, + { + "name": "Templates", + "item": [ + { + "name": "创建 Template", + "request": { + "auth": { + "type": "bearer", + "bearer": [{ "key": "token", "value": "{{adminKey}}", "type": "string" }] + }, + "method": "POST", + "header": [ + { "key": "Content-Type", "value": "application/json" } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"trade_open\",\n \"content\": \"### {{.symbol}} 开仓\\n价格: {{.price}}\"\n}" + }, + "url": "{{baseUrl}}/api/v1/templates" + } + }, + { + "name": "列出 Template", + "request": { + "auth": { + "type": "bearer", + "bearer": [{ "key": "token", "value": "{{adminKey}}", "type": "string" }] + }, + "method": "GET", + "header": [], + "url": "{{baseUrl}}/api/v1/templates" + } + }, + { + "name": "获取 Template", + "request": { + "auth": { + "type": "bearer", + "bearer": [{ "key": "token", "value": "{{adminKey}}", "type": "string" }] + }, + "method": "GET", + "header": [], + "url": "{{baseUrl}}/api/v1/templates/{{templateId}}" + } + }, + { + "name": "更新 Template", + "request": { + "auth": { + "type": "bearer", + "bearer": [{ "key": "token", "value": "{{adminKey}}", "type": "string" }] + }, + "method": "PUT", + "header": [ + { "key": "Content-Type", "value": "application/json" } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"trade_open\",\n \"content\": \"### {{.symbol}} 开仓\\n价格: {{.price}}\"\n}" + }, + "url": "{{baseUrl}}/api/v1/templates/{{templateId}}" + } + }, + { + "name": "删除 Template", + "request": { + "auth": { + "type": "bearer", + "bearer": [{ "key": "token", "value": "{{adminKey}}", "type": "string" }] + }, + "method": "DELETE", + "header": [], + "url": "{{baseUrl}}/api/v1/templates/{{templateId}}" + } + } + ] + }, + { + "name": "Channels", + "item": [ + { + "name": "创建 Channel(Email)", + "request": { + "auth": { + "type": "bearer", + "bearer": [{ "key": "token", "value": "{{adminKey}}", "type": "string" }] + }, + "method": "POST", + "header": [ + { "key": "Content-Type", "value": "application/json" } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"email-ops\",\n \"type\": \"email\",\n \"config\": {\n \"to\": [\"148516886@qq.com\"]\n },\n \"status\": 1\n}" + }, + "url": "{{baseUrl}}/api/v1/channels" + } + }, + { + "name": "创建 Channel(钉钉)", + "request": { + "auth": { + "type": "bearer", + "bearer": [{ "key": "token", "value": "{{adminKey}}", "type": "string" }] + }, + "method": "POST", + "header": [ + { "key": "Content-Type", "value": "application/json" } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"dingtalk-prod\",\n \"type\": \"dingtalk\",\n \"config\": {\n \"webhook_url\": \"https://oapi.dingtalk.com/robot/send?access_token=xxx\",\n \"secret\": \"SEC...\"\n },\n \"status\": 1\n}" + }, + "url": "{{baseUrl}}/api/v1/channels" + } + }, + { + "name": "创建 Channel(企业微信)", + "request": { + "auth": { + "type": "bearer", + "bearer": [{ "key": "token", "value": "{{adminKey}}", "type": "string" }] + }, + "method": "POST", + "header": [ + { "key": "Content-Type", "value": "application/json" } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"wecom-ops\",\n \"type\": \"wecom\",\n \"config\": {\n \"webhook_url\": \"https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx\"\n },\n \"status\": 1\n}" + }, + "url": "{{baseUrl}}/api/v1/channels" + } + }, + { + "name": "创建 Channel(Bark)", + "request": { + "auth": { + "type": "bearer", + "bearer": [{ "key": "token", "value": "{{adminKey}}", "type": "string" }] + }, + "method": "POST", + "header": [ + { "key": "Content-Type", "value": "application/json" } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"bark-phone\",\n \"type\": \"bark\",\n \"config\": {\n \"url\": \"https://api.day.app/\"\n },\n \"status\": 1\n}" + }, + "url": "{{baseUrl}}/api/v1/channels" + } + }, + { + "name": "列出 Channel", + "request": { + "auth": { + "type": "bearer", + "bearer": [{ "key": "token", "value": "{{adminKey}}", "type": "string" }] + }, + "method": "GET", + "header": [], + "url": "{{baseUrl}}/api/v1/channels" + } + }, + { + "name": "获取 Channel", + "request": { + "auth": { + "type": "bearer", + "bearer": [{ "key": "token", "value": "{{adminKey}}", "type": "string" }] + }, + "method": "GET", + "header": [], + "url": "{{baseUrl}}/api/v1/channels/{{channelId}}" + } + }, + { + "name": "更新 Channel", + "request": { + "auth": { + "type": "bearer", + "bearer": [{ "key": "token", "value": "{{adminKey}}", "type": "string" }] + }, + "method": "PUT", + "header": [ + { "key": "Content-Type", "value": "application/json" } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"email-ops\",\n \"type\": \"email\",\n \"config\": {\n \"to\": [\"148516886@qq.com\"]\n },\n \"status\": 1\n}" + }, + "url": "{{baseUrl}}/api/v1/channels/{{channelId}}" + } + }, + { + "name": "删除 Channel", + "request": { + "auth": { + "type": "bearer", + "bearer": [{ "key": "token", "value": "{{adminKey}}", "type": "string" }] + }, + "method": "DELETE", + "header": [], + "url": "{{baseUrl}}/api/v1/channels/{{channelId}}" + } + } + ] + }, + { + "name": "Rules", + "item": [ + { + "name": "创建 Rule", + "request": { + "auth": { + "type": "bearer", + "bearer": [{ "key": "token", "value": "{{adminKey}}", "type": "string" }] + }, + "method": "POST", + "header": [ + { "key": "Content-Type", "value": "application/json" } + ], + "body": { + "mode": "raw", + "raw": "{\n \"source_name\": \"trading-system\",\n \"event\": \"trade.open\",\n \"template_name\": \"trade_open\",\n \"channels\": [\"email-ops\"],\n \"conditions\": [\n {\"field\": \"symbol\", \"op\": \"exists\"},\n {\"field\": \"price\", \"op\": \"gt\", \"value\": \"0\"}\n ],\n \"enabled\": 1\n}" + }, + "url": "{{baseUrl}}/api/v1/rules" + } + }, + { + "name": "列出 Rule", + "request": { + "auth": { + "type": "bearer", + "bearer": [{ "key": "token", "value": "{{adminKey}}", "type": "string" }] + }, + "method": "GET", + "header": [], + "url": "{{baseUrl}}/api/v1/rules" + } + }, + { + "name": "获取 Rule", + "request": { + "auth": { + "type": "bearer", + "bearer": [{ "key": "token", "value": "{{adminKey}}", "type": "string" }] + }, + "method": "GET", + "header": [], + "url": "{{baseUrl}}/api/v1/rules/{{ruleId}}" + } + }, + { + "name": "更新 Rule", + "request": { + "auth": { + "type": "bearer", + "bearer": [{ "key": "token", "value": "{{adminKey}}", "type": "string" }] + }, + "method": "PUT", + "header": [ + { "key": "Content-Type", "value": "application/json" } + ], + "body": { + "mode": "raw", + "raw": "{\n \"source_name\": \"trading-system\",\n \"event\": \"trade.open\",\n \"template_name\": \"trade_open\",\n \"channels\": [\"email-ops\"],\n \"enabled\": 1\n}" + }, + "url": "{{baseUrl}}/api/v1/rules/{{ruleId}}" + } + }, + { + "name": "删除 Rule", + "request": { + "auth": { + "type": "bearer", + "bearer": [{ "key": "token", "value": "{{adminKey}}", "type": "string" }] + }, + "method": "DELETE", + "header": [], + "url": "{{baseUrl}}/api/v1/rules/{{ruleId}}" + } + }, + { + "name": "启用 Rule", + "request": { + "auth": { + "type": "bearer", + "bearer": [{ "key": "token", "value": "{{adminKey}}", "type": "string" }] + }, + "method": "PATCH", + "header": [], + "url": "{{baseUrl}}/api/v1/rules/{{ruleId}}/enable" + } + }, + { + "name": "禁用 Rule", + "request": { + "auth": { + "type": "bearer", + "bearer": [{ "key": "token", "value": "{{adminKey}}", "type": "string" }] + }, + "method": "PATCH", + "header": [], + "url": "{{baseUrl}}/api/v1/rules/{{ruleId}}/disable" + } + }, + { + "name": "启用规则下某渠道", + "request": { + "auth": { + "type": "bearer", + "bearer": [{ "key": "token", "value": "{{adminKey}}", "type": "string" }] + }, + "method": "PATCH", + "header": [], + "url": "{{baseUrl}}/api/v1/rules/{{ruleId}}/channels/{{channelId}}/enable" + } + }, + { + "name": "禁用规则下某渠道", + "request": { + "auth": { + "type": "bearer", + "bearer": [{ "key": "token", "value": "{{adminKey}}", "type": "string" }] + }, + "method": "PATCH", + "header": [], + "url": "{{baseUrl}}/api/v1/rules/{{ruleId}}/channels/{{channelId}}/disable" + } + } + ] + }, + { + "name": "Message Logs", + "item": [ + { + "name": "查询消息记录", + "request": { + "auth": { + "type": "bearer", + "bearer": [{ "key": "token", "value": "{{adminKey}}", "type": "string" }] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/api/v1/message-logs?source=trading-system&event=trade.open&status=success&page=1&page_size=20", + "host": ["{{baseUrl}}"], + "path": ["api", "v1", "message-logs"], + "query": [ + { "key": "source", "value": "trading-system" }, + { "key": "event", "value": "trade.open" }, + { "key": "status", "value": "success" }, + { "key": "page", "value": "1" }, + { "key": "page_size", "value": "20" } + ] + } + } + } + ] + } + ] +} diff --git a/internal/store/channel.go b/internal/store/channel.go index 6b53776..a028554 100644 --- a/internal/store/channel.go +++ b/internal/store/channel.go @@ -54,7 +54,7 @@ func (s *Store) ListChannels(ctx context.Context) ([]model.Channel, error) { } defer rows.Close() - var channels []model.Channel + channels := make([]model.Channel, 0) for rows.Next() { var ch model.Channel var configBytes []byte diff --git a/internal/store/message_log.go b/internal/store/message_log.go index 42d52ce..1e71d6e 100644 --- a/internal/store/message_log.go +++ b/internal/store/message_log.go @@ -62,7 +62,7 @@ func (s *Store) ListMessageLogs(ctx context.Context, filter MessageLogFilter) ([ } offset := (filter.Page - 1) * filter.PageSize - var logs []model.MessageLog + logs := make([]model.MessageLog, 0) query := "SELECT * FROM notification_message_log " + where + " ORDER BY id DESC LIMIT ? OFFSET ?" args = append(args, filter.PageSize, offset) if err := s.DB.SelectContext(ctx, &logs, query, args...); err != nil { diff --git a/internal/store/rule.go b/internal/store/rule.go index 9d2db18..acfb6dd 100644 --- a/internal/store/rule.go +++ b/internal/store/rule.go @@ -127,7 +127,7 @@ func (s *Store) SetRuleEnabled(ctx context.Context, id int, enabled bool) error } func (s *Store) GetRuleChannels(ctx context.Context, ruleID int) ([]model.RuleChannel, error) { - var rcs []model.RuleChannel + rcs := make([]model.RuleChannel, 0) err := s.DB.SelectContext(ctx, &rcs, `SELECT id, rule_id, channel_id, enabled FROM notification_rule_channel WHERE rule_id = ? AND enabled = 1`, ruleID) if err != nil { return nil, fmt.Errorf("get rule channels: %w", err) @@ -156,7 +156,7 @@ func marshalJSON(v *json.RawMessage) (interface{}, error) { } func scanRules(rows *sql.Rows) ([]model.Rule, error) { - var rules []model.Rule + rules := make([]model.Rule, 0) for rows.Next() { var r model.Rule var condsBytes []byte diff --git a/internal/store/source.go b/internal/store/source.go index f0760b6..e2fe63a 100644 --- a/internal/store/source.go +++ b/internal/store/source.go @@ -55,7 +55,7 @@ func (s *Store) GetSourceByName(ctx context.Context, name string) (*model.Source } func (s *Store) ListSources(ctx context.Context) ([]model.Source, error) { - var sources []model.Source + sources := make([]model.Source, 0) err := s.DB.SelectContext(ctx, &sources, `SELECT * FROM notification_source ORDER BY id`) if err != nil { return nil, fmt.Errorf("list sources: %w", err) diff --git a/internal/store/template.go b/internal/store/template.go index d238164..b7a3d31 100644 --- a/internal/store/template.go +++ b/internal/store/template.go @@ -37,7 +37,7 @@ func (s *Store) GetTemplateByName(ctx context.Context, name string) (*model.Temp } func (s *Store) ListTemplates(ctx context.Context) ([]model.Template, error) { - var templates []model.Template + templates := make([]model.Template, 0) err := s.DB.SelectContext(ctx, &templates, `SELECT * FROM notification_template ORDER BY id`) if err != nil { return nil, fmt.Errorf("list templates: %w", err)