接口地址与反向代理

微信要求获取 access_token 的服务器出口 IP 必须在公众号「IP 白名单」中。若你的 Halo 服务器没有固定公网 IP(动态 IP、家用宽带、部署在 NAT 之后等),白名单会频繁失效,导致同步失败。

解决办法:准备一台有固定公网 IP 的低配服务器(VPS)作为反向代理 / 白名单代理,只把这台代理服务器的固定 IP 加入微信 IP 白名单。Halo 将微信接口请求发往「接口地址」,由代理转发到 api.weixin.qq.com——微信看到的出口 IP 始终是代理的固定 IP,从而绕开 Halo 无固定公网 IP 的限制。

Halo 服务器(无固定公网 IP)
        │  请求发往插件设置的「接口地址」
        ▼
反向代理服务器(固定公网 IP,已加入微信 IP 白名单)
        │  原样转发到
        ▼
https://api.weixin.qq.com

配置:在插件设置的 接口地址 中填入代理服务器地址(如 https://wechat-proxy.example.com);留空则直连微信官方接口。地址支持带路径前缀(如 https://example.com/wechat-proxy),插件会保留前缀并去除尾部 /。

⚠️ 重要提醒:代理服务器会获取请求中的敏感信息(包括但不限于 access_token、AppID 等敏感信息),务必使用自行部署或可信的服务器进行代理,切勿使用来源不明的公共代理,以免造成信息泄露。

⚠️ 你必须在该地址上反向代理插件用到的全部微信接口,并保持原始请求路径不变,否则相应步骤会失败。其中 material/get_material(封面复用前的校验)与 draft/get(重复同步前校验已有草稿)是校验用接口:漏掉它们不会让同步失败,只会退化为「每次同步都重传一份封面 / 每次同步都新建一份草稿」,建议一并放行(见下方说明)。

插件用到的微信接口(相对于「接口地址」基址,均为微信官方路径):

方法路径用途请求体
GET/cgi-bin/token获取 access_token查询参数
POST/cgi-bin/media/uploadimg上传正文图片multipart/form-data
POST/cgi-bin/material/add_material?type=image上传封面为永久图片素材multipart/form-data
POST/cgi-bin/material/get_material复用封面前校验该永久素材是否仍在(请求体 {"media_id":"..."})application/json
POST/cgi-bin/draft/add创建图文草稿application/json
POST/cgi-bin/draft/get重复同步前校验上次写入的草稿是否仍在(请求体 {"media_id":"..."};仅开启「重复同步更新草稿」时调用)application/json
POST/cgi-bin/draft/update更新既有草稿(请求体 {"media_id":"...","index":0,"articles":{...}};仅开启「重复同步更新草稿」时调用)application/json

ℹ️ 关于 material/get_material:它是只读校验接口,用来判断缓存里的封面 media_id 还能不能复用——素材仍在时微信直接返回图片二进制流(不是 JSON),素材已被删除时返回 {"errcode":40007,"errmsg":"invalid media_id"}。代理没有转发它不会导致同步失败(插件按「给不出结论」处理,保守地重新上传封面),但代价是每次同步都新增一份封面永久素材:永久图片素材有 10 万份上限,长期如此会白占配额,日志里也会反复出现「校验给不出结论…重新上传」。因此建议一并转发该路径;转发时同样要保持路径原样(该接口是 POST,请求体为 JSON)。

ℹ️ 关于 draft/get 与 draft/update:开启「重复同步更新草稿」(默认开启,见配置)时,重复同步同一篇文章会先用 draft/get 校验上次写入的草稿是否还在——在则用 draft/update 更新那份草稿,不在(或校验给不出结论)则用 draft/add 新建;该开关关闭时这两个路径都不会被调用。只转发旧的那几个路径也不会让同步失败:draft/get 拿不到结论时按「草稿不存在」处理、走新建,只是会退化为「每次同步都新建一份草稿」(草稿箱里会越积越多,需自行清理)。因此建议连同这两个路径一并转发;它们同样是 POST + JSON 请求体,路径也须原样透传。

Nginx 反向代理示例(部署在代理服务器上,将上述路径整体转发到微信):

server {
    listen 443 ssl;
    server_name wechat-proxy.example.com;

    # ssl_certificate     /path/to/fullchain.pem;
    # ssl_certificate_key /path/to/privkey.pem;

    # 仅放行插件用到的 7 个接口路径,其余一律拒绝,避免沦为开放代理
    location ~ ^/cgi-bin/(token|media/uploadimg|material/add_material|material/get_material|draft/add|draft/get|draft/update)$ {
        proxy_pass https://api.weixin.qq.com;   # 不含 URI,nginx 会原样透传路径与查询参数
        proxy_set_header Host api.weixin.qq.com;
        proxy_ssl_server_name on;               # 关键:向微信发起 TLS 时携带 SNI
        proxy_ssl_name api.weixin.qq.com;

        client_max_body_size 20m;               # 素材上传可能较大,按需调整
        proxy_request_buffering off;
        proxy_read_timeout 60s;
    }

    location / {
        return 403;
    }
}

1Panel 反向代理示例(适用于通过 1Panel 建站面板管理的服务器):

location ~ ^/cgi-bin/(token|media/uploadimg|material/add_material|material/get_material|draft/add|draft/get|draft/update)$ {
    proxy_pass https://api.weixin.qq.com;
    proxy_set_header Host api.weixin.qq.com;
    proxy_ssl_server_name on;
    proxy_ssl_name api.weixin.qq.com;
    client_max_body_size 30m; # 素材上传可能较大,按需调整
    proxy_request_buffering off;
    proxy_read_timeout 120s;
}

在 1Panel 中创建「反向代理」网站后,进入该网站的 反向代理,将上方整段 location 配置粘贴到源文文件中保存即可生效。

要点:

  • proxy_pass 到 https:// 上游时务必开启 proxy_ssl_server_name on;(SNI),否则与微信的 TLS 握手可能失败。
  • 保持路径原样转发:proxy_pass 后不要带会改写路径的 URI 部分,让 nginx 原样透传 /cgi-bin/... 路径与查询参数。
  • 代理服务器的出口 IP 必须与加入微信白名单的 IP 一致。
  • 建议用 location 精确匹配上面 7 个路径、拒绝其它请求,防止代理被滥用;material/get_material 与 draft/get 漏掉不会报错,但会让「封面复用 / 草稿更新」分别退化为每次都重传封面、每次都新建草稿(draft/get、draft/update 仅在开启「重复同步更新草稿」时才会用到,见上文说明)。
  • 生产环境请为代理配置 https:// 与合法证书;仅内网测试时可用 http://。