一个用 Go 编写的微信公众号消息转发器:解决「公众号后台只能配置一个回调地址,但实际有多个业务/平台需要消费这些事件」的问题。
embed 内嵌,部署只需一个二进制 + 一个 YAMLwechat_forward/ ├── cmd/server/ # 主程序入口 ├── internal/ │ ├── config/ # 配置加载 │ ├── storage/ # SQLite 数据层(GORM) │ ├── wechat/ # 微信签名校验、AES 加解密、消息解析 │ ├── router/ # 路由匹配引擎 │ ├── forwarder/ # HTTP 转发 + 日志 │ ├── handler/ # 微信回调 / 管理 API │ └── server/ # HTTP 服务装配 ├── web/ # 管理面板(HTML/CSS/JS) ├── config.yaml.example ├── go.mod / go.sum └── README.md
# 1. 安装依赖(首次)
go mod tidy
# 2. 复制配置
cp config.yaml.example config.yaml
# 3. 启动
go run ./cmd/server -config config.yaml
# 或先编译
go build -o wechat_forward ./cmd/server
./wechat_forward -config config.yaml
启动后:
http(s)://你的域名/wechat/{AppID}http://localhost:8080/admin(默认账号 admin / admin)http://localhost:8080/healthz进入公众号后台 → 开发 → 基本配置 → 服务器配置:
| 字段 | 填写 |
|---|---|
| URL | http(s)://你的域名/wechat/{AppID} |
| Token | 与平台中该公众号配置的 token 一致 |
| EncodingAESKey | 加密模式(兼容/安全)下需填写 43 位 |
| 消息加解密方式 | 与平台中该公众号 encrypt_mode 一致 |
本服务采用原样透传(reverse-proxy 风格):
signature/timestamp/nonce/encrypt_type/msg_signature/openid 等)全部保留并附加到 target_url 之后Host / Content-Length / 各类 hop-by-hop 头),所以后端可以直接使用任意一个开源「微信公众号 SDK」对接收到的请求做校验、解密Authorization),覆盖原始头为了路由匹配(按 msg_type / event / keyword 分流),本服务会本地解析消息:
EncodingAESKey 解密后再 XML 解析无论哪种模式,转发出去的 body 仍是微信发来的原始 body,未做任何修改。后端拿到的就是 100% 一致的微信原始请求。
| 类型 | 含义 | match_value 示例 |
|---|---|---|
all | 匹配所有消息 | (留空) |
msgtype | 按消息类型,多个用逗号 | text,image,event |
event | 按事件类型(仅 MsgType=event) | subscribe,unsubscribe,CLICK |
keyword | 文本消息内容包含;re: 前缀启用正则 | 订单 或 re:^退款.* |
微信公众号要求 5 秒内返回回包,因此本服务对回包采用以下策略:
priority 与 id 排序取第一条)success、空响应、或完整的微信回包 XML(明文 / encrypt_type=aes 时为加密 XML)server:
listen: ":8080"
storage:
driver: sqlite
dsn: data/wechat_forward.db
admin:
enabled: true
path: /admin
username: admin
password: admin
forward:
concurrency: 16 # 单次扇出最大并发
default_timeout_ms: 3000 # 路由默认超时
max_body_kb: 512 # 响应体保存上限(KB)
log_retention_rows: 5000 # 预留:日志保留条数
log:
level: info # debug | info | warn | error
所有管理 API 都在 /admin/api/ 下,需要 Basic Auth。
GET /admin/api/accountsPOST /admin/api/accountsGET /admin/api/accounts/{id}PUT /admin/api/accounts/{id}DELETE /admin/api/accounts/{id}GET /admin/api/routes?account_id=1POST /admin/api/routesGET /admin/api/routes/{id}PUT /admin/api/routes/{id}DELETE /admin/api/routes/{id}GET /admin/api/logs?account_id=1&limit=100&offset=0DELETE /admin/api/logs?account_id=1admin.username / admin.password/admin/)go vet ./... go build ./...