# wechat-publisher **Repository Path**: copy_Soft/wechat-publisher ## Basic Information - **Project Name**: wechat-publisher - **Description**: WeChat Official Account article publisher service (PHP 7.2) - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-08-11 - **Last Updated**: 2026-09-27 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # WeChat Publisher Service 微信公众号文章发布服务。接收本地/脚本发送的 JSON,直接调用微信公众号官方接口完成**草稿箱保存、永久素材发布、群发**等操作,无需浏览器自动化。 ## 功能特性 - **发布链路**:永久素材发布(旧版)、草稿箱保存(`draft` 标志) - **草稿管理**:创建、列表、查询、更新、删除草稿 - **群发能力**:全员群发、按标签群发、按 openid 群发、群发前预览、群发状态查询与撤回 - **素材管理**:素材列表、删除、数量统计、正文插图上传 - **用户与标签**:粉丝列表、粉丝资料、标签创建/删除/打标签 - **消息接口**:客服消息、模板消息 - **安全**:请求鉴权(`X-Shared-Secret` 头)、自动缓存 access_token - **SSL 兼容**:内置 CA 证书包,适配未配置 `curl.cainfo` 的主机(如 Windows/IIS) - **单元测试**:内置测试套件,`php tests/run.php` 一键运行 ## 环境要求 - PHP 7.2+(项目开发于 7.2.5) - 扩展:`curl`、`json` - 微信公众号应用凭证(APPID / APPSECRET) ## 快速开始 ### 1. 配置 ```bash cp config.example.php config.php ``` 编辑 `config.php`: ```php return [ // 微信凭证:中转模式下留空(凭证由客户端加密传参,服务器不存储) // 或填写,由服务器直接持有(客户端无需传凭证) 'app_id' => '你的APPID', 'app_secret' => '你的APPSECRET', 'storage_path' => __DIR__ . '/storage', // token 缓存目录(需可写) 'ca_cert_path' => __DIR__ . '/cacert.pem', // CA 证书包(一般无需修改) 'shared_secret' => '设置一个随机字符串' // 客户端加密凭证 + 请求鉴权 ]; ``` > ⚠️ `config.php` 含敏感信息,已在 `.gitignore` 中排除,**切勿提交到仓库**。 ### 2. 启动/部署 **本地开发:** ```bash php -S 127.0.0.1:8080 -t public ``` **Apache 主机:** 将整个项目上传到网站根目录,`public/` 为入口。项目自带的 `.htaccess` 会把所有请求路由到 `public/index.php`。 **Windows IIS 主机:** 参考 `web.config`(URL Rewrite 到 `public/index.php`,需安装 URL Rewrite 模块)。 ### 3. 调用 所有接口需携带鉴权头: ```bash curl -X POST http://your-host/publish \ -H 'Content-Type: application/json' \ -H 'X-Shared-Secret: 你的shared_secret' \ -d '{"draft":true,"articles":[...]}' ``` ## API 文档 ### 统一约定 - 鉴权:请求头 `X-Shared-Secret`,值须与 `config.php` 中的 `shared_secret` 一致,否则返回 `403` - 请求体:`application/json` - 成功响应:`{"ok":true,"result":{...}}` - 失败响应:`{"error":"exception","message":"..."}`(HTTP 500)或 `{"error":"forbidden"}`(HTTP 403) - 所有接口返回微信官方字段,中文参数无需转义 ### 发布 | 方法 | 路径 | 说明 | |---|---|---| | POST | `/publish` | 发布文章。`articles` 必填;`draft:true` 存草稿箱,否则创建永久素材;`send:true` 全员群发(非草稿时) | ### 上传文件 | 方法 | 路径 | 说明 | 参数 | |---|---|---|---| | POST | `/upload` | 上传图片文件(multipart/form-data) | `file`(表单字段);`?type=material` 返回 `media_id`(默认),`?type=content` 返回微信图片 URL | ```bash # 上传封面图,获取 media_id(用于 thumb_media_id) curl -X POST http://your-host/upload?type=material \ -H 'X-Shared-Secret: secret' \ -F 'file=@cover.png' # 上传正文插图,获取微信图片 URL(可嵌入 content HTML) curl -X POST http://your-host/upload?type=content \ -H 'X-Shared-Secret: secret' \ -F 'file=@image.jpg' ``` `articles` 字段: | 字段 | 必填 | 说明 | |---|---|---| | title | 是 | 标题 | | content | 是 | HTML 正文 | | thumb_url | 否 | 封面图 URL(自动下载上传,支持 https) | | thumb_media_id | 否 | 封面图 media_id(已上传过时用) | | author / digest / show_cover_pic / content_source_url | 否 | 作者 / 摘要 / 封面 / 原文链接 | ### 草稿管理 | 方法 | 路径 | 说明 | 参数 | |---|---|---|---| | POST | `/draft/add` | 新建草稿 | `articles` | | POST | `/draft/list` | 草稿列表 | `offset` `count` `no_content` | | POST | `/draft/get` | 查询草稿 | `media_id` | | POST | `/draft/update` | 更新草稿 | `media_id` `index` `articles`(单篇对象) | | POST | `/draft/delete` | 删除草稿 | `media_id` | ### 群发 | 方法 | 路径 | 说明 | 参数 | |---|---|---|---| | POST | `/mass/sendall` | 全员或按标签群发 | `media_id`;`tag_id`(可选,指定则按标签) | | POST | `/mass/send` | 按 openid 群发 | `media_id` `touser`(数组) | | POST | `/mass/preview` | 群发前预览 | `media_id`;`touser`(openid)或 `towxname`(微信号) | | POST | `/mass/status` | 群发状态查询 | `msg_id` | | POST | `/mass/delete` | 撤回群发 | `msg_id` `article_idx`(可选) | ### 素材管理 | 方法 | 路径 | 说明 | 参数 | |---|---|---|---| | POST | `/material/count` | 素材数量统计 | 无 | | POST | `/material/list` | 素材列表 | `type`(image/video/voice/news)`offset` `count` | | POST | `/material/delete` | 删除素材 | `media_id` | | POST | `/material/uploadimg` | 正文插图上传 | `url`(返回微信图片链接,可嵌入正文) | ### 用户 | 方法 | 路径 | 说明 | 参数 | |---|---|---|---| | GET | `/user/list` | 粉丝列表 | `next_openid`(分页游标,可选) | | GET | `/user/info` | 粉丝资料 | `openid` `lang`(可选) | | POST | `/user/batchget` | 批量粉丝资料 | `user_list` | ### 标签 | 方法 | 路径 | 说明 | 参数 | |---|---|---|---| | GET | `/tags/list` | 标签列表 | 无 | | POST | `/tags/create` | 创建标签 | `name` | | POST | `/tags/update` | 修改标签 | `id` `name` | | POST | `/tags/delete` | 删除标签 | `id` | | POST | `/tags/tagging` | 批量打标签 | `openid_list` `tagid` | | POST | `/tags/untagging` | 批量取消标签 | `openid_list` `tagid` | ### 消息 | 方法 | 路径 | 说明 | 参数 | |---|---|---|---| | POST | `/message/custom` | 客服消息(48h 窗口) | `touser` `msgtype` + 类型字段(如 `text`/`mpnews`) | | POST | `/message/template` | 模板消息 | `touser` `template_id` `data` | ### 方糖 (Server酱) 个人微信通知网关 `/notify/sct` 把一条通知经本服务转发到你的**个人微信**(方糖 Turbo)。 token 只存在本服务 `config.php` 的 `sct_token`,**绝不下发到前端**,业务项目 (如 plane_war 的反馈模块)经此统一转发,避免 token 泄漏。 | 方法 | 路径 | 说明 | 参数 | |---|---|---|---| | POST | `/notify/sct` | 推送到方糖个人微信 | `title`(必填)`content`(正文,支持简单 markdown) | ```bash curl -X POST http://your-host/notify/sct \ -H 'Content-Type: application/json' \ -H 'X-Shared-Secret: secret' \ -d '{"title":"飞机大战反馈","content":"正文..."}' ``` > 依赖 `config.php` 的 `sct_token`;未配置时返回 `invalid_params: sct_token 未配置`。 ## 微信公众号 AI 自动回复 基于大模型的公众号自动回复(认证服务号)。**与发布能力共用同一套部署**,新增一个端点即可。 ### 工作原理 ``` 微信用户 ──消息回调──> /wechat-reply (ReplyController) ├─ 校验签名(sha1) ├─ 解析消息(明文/安全模式) ├─ 命中关键词规则?→ 直接回复(省 token) ├─ 否则组装多轮会话 → 调大模型(DeepSeek等) └─ 通过客服消息异步推给用户(绕开 5s 超时) ``` - **客服消息**:认证服务号专属,48 小时窗口内主动推送,不受 5s 被动回复超时限制。 - **会话记忆**:多轮上下文存 MySQL(自动建表 `wechat_reply_sessions`)。 - **多账号**:每账号独立 `app_id`/`token`/`encoding_aes_key`。 ### 配置(config.php) ```php 'wechat_token' => '', // 公众号后台服务器配置的 Token 'encoding_aes_key' => '', // 公众号后台 EncodingAESKey(安全模式用) 'db_host' => '127.0.0.1', // MySQL(会话存储) 'db_name' => '', 'db_user' => '', 'db_pass' => '', 'llm_base_url' => 'https://api.deepseek.com', // OpenAI 兼容协议 'llm_model' => 'deepseek-chat', 'llm_api_key' => '', // 大模型 Key 'max_turns' => 10, 'fallback_prompt' => '你是一位友好专业的微信客服助手,请简洁回答用户问题。', 'keywords_map' => ['价格' => '请发“联系客服”咨询价格'], // 关键词规则(可选) ``` ### 公众号后台配置 在 微信公众平台 → 设置与开发 → 基本配置: - **URL**:`http://your-host/wechat-reply` - **Token / EncodingAESKey**:与 config.php 中一致 - **消息加解密方式**:明文或安全模式皆可(安全模式需填 `encoding_aes_key`) ### 本地联调 `scripts/dev-server` 或直接 `curl` 模拟微信回调(带正确签名)验证链路。 ## 中转模式(服务器不存凭证) **适用场景**:不想把微信 APPID/APPSECRET 存在中转服务器上(如公共/低信任主机)。 **原理**: - 服务器 `config.php` 中 `app_id`/`app_secret` 留空 - 客户端用 `shared_secret` 派生密钥,将凭证做 **AES-256-CBC + HMAC-SHA256** 加密后随请求发送(`credentials` 字段) - 服务器解密后**仅本次请求**使用,不落盘;access_token 缓存按 appid 隔离 - 明文 HTTP 环境下凭证也不会泄露(密文传输),仍建议配置 HTTPS ### 客户端脚本 `client/wechat-publish.js` 已内置加密与发送逻辑,**跨平台、无需 PHP**(仅需 Node 12+): ```bash # 方式一:本机配置文件(推荐,凭证可选存本地) cp client/client_config.example.json client/client_config.json # 填写 server/shared_secret/app_id/app_secret # 方式二:命令行直接传参(凭证不落盘) # node client/wechat-publish.js --server http://your-host --secret '共享密钥' \ # --appid 'APPID' --appsecret 'APPSECRET' --file demo.md # md / html 直接发,不用先转 JSON(标题自动取首个 # /

) node client/wechat-publish.js --file demo.md --cover ./cover.png node client/wechat-publish.js --file demo.html --thumb-media-id '已上传的封面素材id' # 先看要发什么,再决定发不发(不会发布,也不会上传图片) node client/wechat-publish.js --file demo.md --cover ./cover.png --dry-run # 调用其他接口(--endpoint) echo '{"count":5}' | node client/wechat-publish.js --endpoint /draft/list # 本地图片直传(不经 URL):默认返回 media_id 作封面;--image-type content 返回正文图 URL node client/wechat-publish.js --upload-image ./cover.png node client/wechat-publish.js --upload-image ./pic.png --image-type content # 忘了选项? node client/wechat-publish.js --help ``` > `--upload-image` 走 `POST /upload`(multipart),凭证经 `X-Credentials` 头传递;返回的 `media_id` 写进文章 `thumb_media_id`,`content` 模式返回的 `mmbiz.qpic.cn` URL 直接写进正文 ``(发布时不会被重复上传)。单图上限 10MB。 > > md/html 模式下这些事都会自动做,不用手工: > - `--cover` 既接受本地图片路径(自动上传成封面素材)也接受 http(s) 地址 > - 正文里 `![](./img/a.png)` / `` 这类本地路径会按文件所在目录解析、上传并换成微信 URL;同一张图只上传一次;找不到文件会先报错而不是传到一半失败 > - 标题缺省取首个 `#` / `

`,都没有就用文件名 > - `.md` / `.html` 顶部可用 **frontmatter** 写元数据,省去命令行参数;优先级:**CLI 参数 > frontmatter > 正文 `#`/`

` > 文件名** > ```markdown > --- > title: 文章标题 > author: 作者 > digest: 摘要 > cover: ./cover.png # 本地路径(自动上传)或 http(s) 地址 > thumb_media_id: 已上传的素材id # 与 cover 二选一 > content_source_url: https://... # 原文链接(可选) > --- > # 正文标题(若 frontmatter 已给 title 则以此为准的优先级规则同上) > ``` > 仅当文件**首行恰为 `---` 且前 100 行内有闭合 `---`** 才识别,否则整篇当作正文(不会误把分隔线/代码块里的 `---` 当 frontmatter)。解析只支持扁平 `key: value`,不引入额外依赖。 > - `--format md|html|json` 可强制解析方式,管道喂 markdown 时用(默认 stdin 按 JSON 解析) `article.json` 示例: ```json { "draft": true, "articles": [{ "title": "示例", "content": "

正文

", "thumb_url": "https://example.com/cover.jpg" }] } ``` > `client/client_config.json` 同样已加入 `.gitignore`。 ## 调用示例 保存到草稿箱: ```bash curl -X POST http://your-host/publish \ -H 'Content-Type: application/json' \ -H 'X-Shared-Secret: secret' \ -d '{ "draft": true, "articles": [{ "title": "示例文章", "author": "CorySoft", "digest": "摘要", "thumb_url": "https://example.com/cover.jpg", "content": "

正文

" }] }' ``` 列出草稿: ```bash curl -X POST http://your-host/draft/list \ -H 'Content-Type: application/json' \ -H 'X-Shared-Secret: secret' \ -d '{"offset":0,"count":10}' ``` ## 安全说明 1. **鉴权**:所有接口要求 `X-Shared-Secret` 请求头。生产环境请务必更换 `config.php` 中的默认值,并建议: - 在 Web 服务器层加 IP 白名单 / 限流 - 改用 HTTPS 2. **凭证保护**:`config.php`(含 APPID/APPSECRET)已在 `.gitignore` 中排除,不会入库 3. **IP 白名单**:微信开放平台要求服务器出口 IP 加入公众号后台「IP 白名单」,否则返回 `40164` ## 已知限制 微信部分接口依赖公众号**认证资质**。未认证公众号调用以下接口会返回 `errcode 48001 api unauthorized`: - 发布(`freepublish/*`) - 群发(`mass/*`) - 用户与标签(`user/*`、`tags/*`) - 客服消息、模板消息(`message/*`) 草稿箱与素材管理不受影响。公众号认证后以上接口自动可用。 ## 测试 ```bash php tests/run.php ``` 内置 70 项单元测试,覆盖客户端 payload/URL 构造、加密凭证、上传嗅探与控制器参数校验。 ## 目录结构 ``` ├── public/ # Web 入口(index.php 路由器) ├── src/ # 核心代码 │ ├── WeChatClient.php # 微信 API 客户端 │ ├── ArticleController.php # 文章发布控制器 │ └── ApiController.php # 通用 API 控制器(草稿/素材/群发等) ├── tests/ # 单元测试(php tests/run.php) ├── storage/ # token 缓存目录(可写) ├── config.example.php # 配置模板 ├── cacert.pem # CA 证书包(curl SSL 校验) ├── .htaccess # Apache 路由 └── web.config # IIS 路由 ``` ## 许可证 [MIT](LICENSE) © 2026 CorySoft