{"id":29155421,"url":"https://github.com/lenye/postpoint","last_synced_at":"2026-01-20T16:30:24.146Z","repository":{"id":298937990,"uuid":"982560398","full_name":"lenye/postpoint","owner":"lenye","description":"PostPoint一个API请求将消息推送到企业微信、飞书、钉钉、Slack、Discord、Mattermost和通用Webhook，可设置失败重试，遵守各平台消息调用频率；无限期推送日志","archived":false,"fork":false,"pushed_at":"2026-01-19T13:21:49.000Z","size":106,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-01-19T19:32:46.649Z","etag":null,"topics":["dingtalk-bot","dingtalk-webhook","discord-bot","discord-webhook","feishu-bot","feishu-webhook","mattermost-bot","slack-bot","slack-webhook","webhook","workweixin-bot","workweixin-webhook"],"latest_commit_sha":null,"homepage":"","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lenye.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-05-13T04:33:53.000Z","updated_at":"2026-01-19T12:45:17.000Z","dependencies_parsed_at":"2025-06-13T18:32:17.606Z","dependency_job_id":"d7d9a9c0-3228-4a0f-beaa-f3aeea186a07","html_url":"https://github.com/lenye/postpoint","commit_stats":null,"previous_names":["lenye/postpoint"],"tags_count":20,"template":false,"template_full_name":null,"purl":"pkg:github/lenye/postpoint","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lenye%2Fpostpoint","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lenye%2Fpostpoint/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lenye%2Fpostpoint/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lenye%2Fpostpoint/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lenye","download_url":"https://codeload.github.com/lenye/postpoint/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lenye%2Fpostpoint/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28607161,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-20T16:10:39.856Z","status":"ssl_error","status_checked_at":"2026-01-20T16:10:39.493Z","response_time":117,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dingtalk-bot","dingtalk-webhook","discord-bot","discord-webhook","feishu-bot","feishu-webhook","mattermost-bot","slack-bot","slack-webhook","webhook","workweixin-bot","workweixin-webhook"],"created_at":"2025-07-01T02:39:46.678Z","updated_at":"2026-01-20T16:30:24.139Z","avatar_url":"https://github.com/lenye.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# PostPoint 一个 API，连接所有核心工作群\n\nPostPoint 提供了一个统一、高可用的 API 接口，你无需关心各平台的接口差异和复杂的频率限制，\n只需一次集成，即可将业务系统的关键通知，稳定、即时地发送到任何主流工作平台和通用Webhook。\n\n* 极致简化： 单一 API，告别重复开发和维护。\n* 智能可靠： 内置失败自动重试，并智能遵守各平台发送频率，确保消息100%稳定触达。\n* 全面覆盖： 无缝支持企业微信群机器人、飞书自定义机器人、钉钉自定义机器人、Slack机器人、Discord机器人、Mattermost机器人和通用Webhook。\n* 无限期推送日志：每一次推送（无论成功与否）的详细信息都被永久记录。\n\n## 支持的操作系统\n\n* Windows\n* Linux\n* macOS\n* FreeBSD\n\n## 上手指南\n\n```shell\nC:\\\u003epostpoint.exe -h\n一个 API 请求将消息发送到企业微信、飞书、钉钉、Slack、Discord、Mattermost、Webhook\n\nUsage:\n  postpoint [command]\n\nAvailable Commands:\n  help        Help about any command\n  serve       API / OpenAPI / Swagger UI 服务，消息推送服务\n  test        测试已配置的企业微信群机器人、飞书自定义机器人、钉钉自定义机器人、Slack机器人、Discord机器人、Mattermost机器人、Webhook\n\nFlags:\n  -h, --help      help for postpoint\n  -v, --version   version for postpoint\n\nUse \"postpoint [command] --help\" for more information about a command.\n```\n\n### 安装`PostPoint`\n\n`PostPoint`下载 [最新版本](https://github.com/lenye/postpoint/releases/tag/v26.1.19)\n\n#### Windows 操作系统\n\n1. 解压下载文件`postpoint_v26.1.19_windows_x86_64.zip`；\n2. 创建`config.toml`配置文件，保存到`postpoint.exe`相同目录下，配置一个新通道：**企业微信群机器人**；\n    ```toml\n    # 企业微信群机器人\n    [provider.workweixin_bot]\n    # webhook 地址\n    endpoint.url = \"https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx\"\n    ```\n   endpoint.url 填写成你的企业微信群机器人 webhook 地址\n3. 运行`postpoint.exe test`，测试发送消息到配置的通道；\n   ```shell\n   C:\\\u003epostpoint.exe test\n   2025-07-01T22:07:01.788+0800    info    测试    {\"text\": \"postpoint 测试消息\\n\\n就到这儿吧！我要去睡今天的第三个午觉了。\"}\n   2025-07-01T22:07:02.081+0800    info    workweixin_bot    成功    {\"耗时\": \"482.784ms\"}\n   ```\n4. 运行`postpoint.exe serve`，开始消息推送 API 服务；\n   ```shell\n   C:\\\u003epostpoint.exe serve\n   2025-07-01T22:07:01.627+0800    info    PostPoint Free v26.1.19 windows/amd64, https://github.com/lenye/postpoint\n   ```   \n\n运行`postpoint.exe test -h`查看通道的测试命令。\n\n单独测试企业微信群机器人，运行`postpoint.exe test workweixin_bot`。\n\n#### 完成\n\n如果你顺利完成了以上步骤，那么恭喜你，属于你的`PostPoint`搭建成功。\n\n访问页面: http://localhost:39270/send ，开始**发消息**。\n\n访问页面: http://localhost:39270/log ，查看**推送日志**。\n\n*执行通道的测试命令`postpoint.exe test`，无推送日志；`postpoint.exe serve`启动服务后，调用 API 发送消息，记录推送日志。*\n\n#### 配置监听端口\n\nAPI 服务的默认监听端口是 39270。\n\n配置修改步骤：\n\n1. 打开`config.toml`配置文件。\n2. 找到`[api]`配置段。\n3. 将`port`参数的值修改为你希望使用的端口号。\n4. 保存文件并重启 API 服务，以使更改生效。\n\n**建议：**\n\n* 确保你选择的端口没有被其他服务占用。\n* 如果服务部署的云环境中有防火墙，请确保在防火墙规则中允许流量通过你配置的端口。\n\n### 通道集成指南\n\n`PostPoint`配置文件的详细定义，请参考[通道集成指南](provider/README.md)，样例配置文件 [config.toml](config.toml)\n\n## 调用 API 发送消息\n\n* HTTP 方法: POST\n* Endpoint: http://localhost:39270/api/text\n* 数据格式: 请求和响应数据编码均为 UTF-8。支持 application/json 和 application/x-www-form-urlencoded 两种提交方式。\n\n### 幂等消息\n\n为确保在网络不稳定或客户端重试等情况下，API请求能够被安全地重复调用，而不会产生非预期的副作用（例如重复创建资源或多次执行同一操作），我们引入了幂等性（Idempotency）设计。\n\n通过追踪唯一的`msg_id`，系统能够识别并处理重复的请求，从而保证接口的幂等性。\n\n#### 工作原理\n\n当你发起一个请求时，服务器会检查你提供的`msg_id`。\n\n* **首次请求**：如果该`msg_id`是第一次被接收，API 服务器会正常处理该请求，并缓存请求的结果。\n* **重复请求**：如果在 2 分钟的窗口期内，API 服务器再次接收到具有相同`msg_id`的请求，它将不会重新处理该请求，而是直接返回响应结果。\n  这可以防止意外的重复操作，并确保数据的一致性。\n\n**追踪窗口**：API 服务器会追踪过去 2 分钟内所有请求的`msg_id`。超过此窗口期的`msg_id`将被丢弃，如果客户端在 2 分钟后使用相同的\n`msg_id`再次发送请求，该请求将被视为一个全新的请求进行处理。\n\n#### 如何发送幂等请求\n\n为了利用幂等性保障，客户端需要在请求中提供一个唯一的`msg_id`。系统通过以下方式确定`msg_id`：\n\n1. **默认行为**：\n   默认情况下，系统会生成唯一 ID 作为`msg_id`。\n\n2. **自定义`msg_id`**：\n   我们强烈建议你通过在 HTTP 请求头（Header）中提供一个自定义的唯一标识符来主动控制幂等性。为此，你需要添加\n   `X-Ppt-Request-ID`\n   字段。\n\n    * **Header名称**：`X-Ppt-Request-ID`\n    * **值**：一个由客户端生成的、独一无二的字符串（例如UUID v4），以确保其唯一性。\n\n   当请求头中包含`X-Ppt-Request-ID`且其内容不为空时，系统将使用该`request_id`作为`msg_id`。\n\n    ```http\n    POST /api/text\n    Content-Type: application/json\n    X-Ppt-Request-ID: a-unique-uuid-v4-string\n    \n    {\n        \"msg\": \"测试，测试，测试\"\n    }\n    \n    \n    \n    HTTP/1.1 200 OK\n    Content-Type: application/json\n    \n    {\n        \"code\": \"ok\",\n        \"msg\": \"success\",\n        \"id\": \"1234567EC64G97ZRAS211JHHX7\",\n        \"request_id\": \"a-unique-uuid-v4-string\"\n    }\n    ```\n\n### 请求参数\n\n请求体 (Body)\n\n| 参数名   | 类型     | 必填 | 描述   |\n|:------|:-------|:---|:-----|\n| `msg` | string | 是  | 消息内容 |\n\n### 响应数据\n\n| 字段名          | 类型     | 描述                                             |\n|:-------------|:-------|:-----------------------------------------------|\n| `code`       | string | 结果代码，`ok` 表示成功                                 |\n| `msg`        | string | 结果描述                                           |\n| `id`         | string | 本次请求的唯一 ID，可用于问题排查和日志跟踪                        |\n| `request_id` | string | 本次请求ID，来自 request.http.header.X-Ppt-Request-ID |\n\n使用 API 发送消息，发出如下所示的 HTTP POST 请求：\n\n```\nPOST /api/text\nContent-type: application/json\n\n{\n    \"msg\": \"测试，测试，测试\"\n}\n```\n\n#### 成功响应\n\n当消息成功进入 PostPoint 的发送队列时，将收到 `HTTP 200 OK` 响应。\n\n```\nHTTP/1.1 200 OK\nContent-Type: application/json\n\n{\n    \"code\": \"ok\",\n    \"msg\": \"success\",\n    \"id\": \"1234567EC64G97ZRAS211JHHX7\"\n}\n```\n\n#### 失败响应\n\n当请求本身存在问题时（如参数错误、Token 无效），将收到 `4xx` 或 `5xx` 的 HTTP 状态码。\n\n```\nHTTP/1.1 400 Bad Request\nContent-Type: application/json\n\n{\n    \"code\": \"invalid_argument\",\n    \"msg\": \"msg 是必填项\",\n    \"id\": \"1234567EC64G97ZRAS211JHHX8\"\n}\n```\n\n#### 通用错误码\n\n| HTTP 状态码                    | `code` 值             | 描述                        |\n|:----------------------------|:---------------------|:--------------------------|\n| `400 Bad Request`           | `invalid_argument`   | 请求参数无效或缺失。`msg` 字段会提供详细信息 |\n| `401 Unauthorized`          | `unauthenticated`    | 无效的令牌，请检查 token 是否正确      |\n| `404 Not Found`             | `not_found`          | url 不存在或已被删除              |\n| `405 Method Not Allowed`    | `method_not_allowed` | 请求方法错误，请使用 `POST` 方法      |\n| `429 Too Many Requests`     | `resource_exhausted` | 请求频率过高，请稍后重试              |\n| `500 Internal Server Error` | `internal`           | PostPoint 服务器内部错误，请联系我们处理 |\n| `503 Service Unavailable`   | `unavailable`        | PostPoint 许可证到期，请联系我们处理   |\n\n### 使用 Swagger UI\n\n`PostPoint`OpenAPI http://localhost:39270/swagger/openapi.yaml\n\n你可以访问 Swagger UI 调用 API 发送消息，http://localhost:39270/swagger/\n\n可以使用代码生成器 [Swagger Codegen](https://swagger.io/tools/swagger-codegen/)，根据 OpenAPI 规范自动生成各种语言的客户端\nSDK。\n\n### 使用 curl\n\n可以使用 curl 命令转代码（复制 linux 环境的命令到转码工具），自动生成各种语言的客户端 SDK。\n\n下面是使用 curl 发送一个消息的示例，消息内容：测试，测试，测试\n\n1. 发送 json 数据\n    ```shell\n    # linux 环境\n    $ curl -X 'POST' 'http://localhost:39270/api/text' \\\n      -H 'Accept: application/json' \\\n      -H 'Content-Type: application/json' \\\n      -d '{\"msg\": \"测试，测试，测试\"}'\n   \n   \n    # windows 环境\n    C:\\\u003ecurl -X \"POST\" \"http://localhost:39270/api/text\" ^\n      -H \"Accept: application/json\" ^\n      -H \"Content-Type: application/json\" ^\n      -d \"{\\\"msg\\\": \\\"测试，测试，测试\\\"}\"         \n    ```\n2. 发送 form 数据\n    ```shell\n    # linux 环境\n    $ curl -X 'POST' 'http://localhost:39270/api/text' \\\n      -H 'Accept: application/json' \\\n      -H 'Content-Type: application/x-www-form-urlencoded' \\\n      -d 'msg=测试，测试，测试'\n   \n   \n    # windows 环境\n    C:\\\u003ecurl -X \"POST\" \"http://localhost:39270/api/text\" ^\n      -H \"Accept: application/json\" ^\n      -H \"Content-Type: application/x-www-form-urlencoded\" ^\n      -d \"msg=测试，测试，测试\"\n    ```\n\n### 使用 Python\n\n```python\nimport requests\nimport json\n\napiUrl = f\"http://localhost:39270/api/text\"\n\npayload = {\n    \"msg\": \"来自 Python 的监控告警：\\n\u003e 服务 **API-Gateway** 在 5 分钟内错误率超过 5%。\"\n}\n\nheaders = {\n    'Content-Type': 'application/json'\n}\n\ntry:\n    response = requests.post(apiUrl, headers=headers, data=json.dumps(payload), timeout=10)\n    response.raise_for_status()  # 如果状态码不是 2xx，则会抛出异常\n\n    # 打印成功响应\n    print(\"请求成功!\")\n    print(f\"状态码: {response.status_code}\")\n    print(f\"响应内容: {response.json()}\")\n\nexcept requests.exceptions.RequestException as e:\n    # 打印错误响应\n    print(f\"请求失败: {e}\")\n    if e.response:\n        print(f\"状态码: {e.response.status_code}\")\n        print(f\"响应内容: {e.response.text}\")\n\n```\n\n### 使用 PHP\n\n```PHP\n\u003c?php\n\n/**\n * 使用 PostPoint API 发送消息。\n *\n * @param string $message 要发送的消息内容。\n * @param string $apiUrl API 的完整 URL 地址。\n * @return array 包含响应结果的关联数组 ['success' =\u003e bool, 'data' =\u003e array|string]\n */\nfunction sendMessage(string $message, string $apiUrl): array\n{\n    // 1. 准备请求数据\n    // 要发送的数据 (PHP 关联数组)\n    $postData = [\n        'msg' =\u003e $message\n    ];\n\n    // 将数据编码为 JSON 字符串\n    $jsonData = json_encode($postData);\n\n    // 2. 设置请求头，完全符合文档要求\n    $headers = [\n        'Content-Type: application/json',\n        'Accept: application/json' // 最好也加上 Accept 头，以表明期望接收 JSON 响应\n    ];\n\n    // 3. 初始化 cURL 并设置选项\n    $ch = curl_init();\n\n    // 设置请求的 URL\n    curl_setopt($ch, CURLOPT_URL, $apiUrl);\n    // 设置为 POST 请求\n    curl_setopt($ch, CURLOPT_POST, true);\n    // 设置 POST 的数据体 (JSON 字符串)\n    curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonData);\n    // 设置自定义请求头\n    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);\n    // 将执行结果以字符串返回，而不是直接输出\n    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);\n    // 设置一个合理的超时时间 (例如：10秒)\n    curl_setopt($ch, CURLOPT_TIMEOUT, 10);\n\n    // 4. 执行请求\n    $responseBody = curl_exec($ch);\n    // 获取 HTTP 响应状态码\n    $httpStatusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);\n\n    // 5. 检查 cURL 本身是否出错 (例如网络连接失败)\n    if (curl_errno($ch)) {\n        $error_msg = curl_error($ch);\n        curl_close($ch);\n        return ['success' =\u003e false, 'data' =\u003e 'cURL 请求错误: ' . $error_msg];\n    }\n\n    // 6. 关闭 cURL\n    curl_close($ch);\n\n    // 7. 根据 HTTP 状态码处理响应\n    $responseData = json_decode($responseBody, true); // 将 JSON 响应体解码为 PHP 数组\n\n    if ($httpStatusCode == 200) {\n        // 成功响应 (HTTP 200 OK)\n        return ['success' =\u003e true, 'data' =\u003e $responseData];\n    } else {\n        // 失败响应 (4xx 或 5xx)\n        return ['success' =\u003e false, 'data' =\u003e $responseData];\n    }\n}\n\n\n// --- 使用示例 ---\n\n// 请将这里的 URL 替换为你的实际 API 地址\n// 注意：文档中的 `/text` 是路径，需要拼接上主机地址\n$apiUrl = 'http://localhost:39270/api/text'; // 假设 API 服务运行在本地的 39270 端口\n\necho \"--- 正在尝试发送消息 ---\\n\";\n$result = sendMessage('测试，测试，测试', $apiUrl);\n\nif ($result['success']) {\n    echo \"消息发送成功！\\n\";\n    echo \"状态: \" . $result['data']['code'] . \"\\n\";\n    echo \"信息: \" . $result['data']['msg'] . \"\\n\";\n    echo \"消息 ID: \" . $result['data']['id'] . \"\\n\";\n} else {\n    // 即使逻辑上成功，但如果 API 端返回错误，也会进入这里\n    echo \"API 返回错误！\\n\";\n    echo \"HTTP 状态码: \" . ($result['http_code'] ?? 'N/A') . \"\\n\"; // http_code 不一定存在\n    echo \"错误码: \" . ($result['data']['code'] ?? '未知') . \"\\n\";\n    echo \"错误信息: \" . ($result['data']['msg'] ?? '无法解析的错误') . \"\\n\";\n    echo \"请求 ID: \" . ($result['data']['id'] ?? 'N/A') . \"\\n\";\n}\n?\u003e\n```\n\n### 使用 JavaScript\n\n```javascript\n/**\n * 使用 API 发送消息。\n * @param {string} message 要发送的消息内容。\n * @param {string} apiUrl API 的完整 URL 地址。\n * @returns {Promise\u003cobject\u003e} 一个包含响应结果的对象 { success: boolean, data: object | string }\n */\nasync function sendMessage(message, apiUrl) {\n    // 1. 准备请求数据和请求头\n    const postData = {\n        msg: message\n    };\n\n    const requestOptions = {\n        method: 'POST',\n        headers: {\n            'Content-Type': 'application/json',\n            'Accept': 'application/json'\n        },\n        body: JSON.stringify(postData) // 必须将 JavaScript 对象转换为 JSON 字符串\n    };\n\n    try {\n        // 2. 发送 HTTP 请求\n        const response = await fetch(apiUrl, requestOptions);\n\n        // 3. 将响应体解析为 JSON\n        // response.json() 总是会尝试解析，即使是 4xx/5xx 错误\n        const responseData = await response.json();\n\n        // 4. 根据 HTTP 状态码判断成功或失败\n        // response.ok 为 true 表示 HTTP 状态码为 200-299\n        if (response.ok) {\n            // 成功响应 (HTTP 200 OK)\n            return {success: true, data: responseData};\n        } else {\n            // 失败响应 (HTTP 4xx or 5xx)\n            return {success: false, data: responseData, status: response.status};\n        }\n\n    } catch (error) {\n        // 捕获网络错误或 JSON 解析错误等\n        console.error('Request failed:', error);\n        return {success: false, error: error.message};\n    }\n}\n\n// --- 使用示例 ---\n\n// 请将这里的 URL 替换为你的实际 API 地址\n// 注意：文档中的 `/text` 是路径，需要拼接上主机地址\nconst apiUrl = 'http://localhost:39270/api/text';\n\nconsole.log(\"--- 正在尝试发送消息 ---\");\nsendMessage('测试，测试，测试', apiUrl).then(result =\u003e {\n    if (result.success) {\n        console.log(\"消息发送成功！\");\n        console.log(\"状态:\", result.data.code);\n        console.log(\"信息:\", result.data.msg);\n        console.log(\"消息 ID:\", result.data.id);\n    } else {\n        // 处理 API 返回的业务错误\n        console.log(\"API 返回错误！\");\n        console.log(\"HTTP 状态码:\", result.status);\n        console.log(\"错误码:\", result.data.code);\n        console.log(\"错误信息:\", result.data.msg);\n        console.log(\"请求 ID:\", result.data.id);\n    }\n    console.log(\"\\n----------------------------------------\\n\");\n});\n\n```\n\n## 反馈与建议\n\n如果你发现此软件存在问题，欢迎创建 [Issue](https://github.com/lenye/postpoint/issues) 帮助改进项目。\n\n## 免责声明\n\n本软件仅供学习和研究之用，使用本软件的风险由你自行承担，我们对使用本软件时产生的任何损失概不负责。","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flenye%2Fpostpoint","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flenye%2Fpostpoint","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flenye%2Fpostpoint/lists"}