Documentation Index
Fetch the complete documentation index at: https://docs.superun.ai/llms.txt
Use this file to discover all available pages before exploring further.
一、整体架构
方案 A:直连模式(默认)
方案 B:反向代理模式(推荐)
核心原则:前端不直接调用微信 API,所有微信接口调用都通过 Supabase Edge Functions 代理,避免在客户端暴露 AppSecret。用户可以选择直连模式或反向代理模式解决 IP 白名单问题。
二、前置准备
2.1 微信开发者平台后台配置
在 微信开发者平台 完成以下操作:
-
获取开发者凭证
- 访问 微信开发者平台
- 选择对应的公众号
- 在「基础信息」中查看或重置
- 记录
AppID(开发者 ID)
- 记录或重置
AppSecret(开发密钥)
-
配置 IP 白名单
- 在「基础信息 → 开发密钥 → API IP白名单」中添加 Edge Function 的出口 IP
- ⚠️ 重要:Supabase Edge Functions 使用动态 IP,首次调用会返回
errcode: 40164,从错误信息中提取 IP 并添加到白名单
- 提取方式:解析
errmsg 中的 invalid ip x.x.x.x
-
确认公众号类型
- 已认证的服务号拥有全部接口权限
- 订阅号部分接口受限(如自定义菜单、数据统计)
2.2 Supabase 项目配置
-
环境变量 / Edge Function Secrets
需要配置以下 Secrets(通过 Supabase Dashboard 或 CLI):
| Secret 名称 | 说明 | 示例 |
|---|
CUSTOM_MINIPROGRAM_APP_ID | 公众号 AppID | wx1234567890abcdef |
CUSTOM_MINIPROGRAM_APP_SECRET | 公众号 AppSecret | a1b2c3d4e5f6... |
SUPERUN_WECHAT_API_PROXY_URL | 反向代理地址(可选) | https://wechat-proxy.example.com |
SUPABASE_URL | Supabase 项目 URL(内置) | https://xxxxx.supabase.co |
SUPABASE_ANON_KEY | Supabase 匿名密钥(内置) | eyJhbG... |
SUPABASE_SERVICE_ROLE_KEY | Supabase 服务角色密钥(内置) | eyJhbG... |
其中 SUPABASE_URL、SUPABASE_ANON_KEY、SUPABASE_SERVICE_ROLE_KEY 是 Supabase 内置的,无需手动配置。
SUPERUN_WECHAT_API_PROXY_URL 为可选项,不配置则直连微信 API(需手动维护 IP 白名单),配置后所有微信请求走代理服务器。
-
Edge Function 配置(
supabase/config.toml)
project_id = "<your-project-id>"
[functions.wechat-token]
verify_jwt = false
[functions.wechat-data]
verify_jwt = false
[functions.wechat-articles]
verify_jwt = false
[functions.wechat-menu]
verify_jwt = false
verify_jwt = false 允许 Edge Function 之间互相调用。认证逻辑在函数内部通过解析 JWT 手动实现。
三、数据库表结构
3.1 wechat_tokens — 令牌缓存表
CREATE TABLE wechat_tokens (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
access_token TEXT NOT NULL,
expires_at TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
用途:缓存微信 access_token,避免每次请求都调用微信令牌接口(2 小时有效期,每日调用次数有限)。
3.2 articles — 图文内容表
CREATE TABLE articles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL,
title TEXT NOT NULL DEFAULT '',
content TEXT NOT NULL DEFAULT '',
author TEXT NOT NULL DEFAULT '',
digest TEXT NOT NULL DEFAULT '',
thumb_url TEXT NOT NULL DEFAULT '',
status TEXT NOT NULL DEFAULT 'draft', -- draft / published / deleted
wechat_media_id TEXT NOT NULL DEFAULT '', -- 微信侧的 article_id
wechat_article_url TEXT NOT NULL DEFAULT '', -- 发布后的文章链接
published_at TIMESTAMPTZ NOT NULL DEFAULT now(),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE menu_configs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL,
menu_data JSONB NOT NULL DEFAULT '{}', -- 微信菜单 JSON 结构
version INTEGER NOT NULL DEFAULT 1,
status TEXT NOT NULL DEFAULT 'draft', -- draft / published / deleted
published_at TIMESTAMPTZ NOT NULL DEFAULT now(),
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
3.4 auto_reply_rules — 自动回复规则表
CREATE TABLE auto_reply_rules (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL,
rule_type TEXT NOT NULL DEFAULT 'keyword', -- keyword / subscribe / default
keyword TEXT NOT NULL DEFAULT '',
match_type TEXT NOT NULL DEFAULT 'exact', -- exact / contain
reply_content TEXT NOT NULL DEFAULT '',
reply_type TEXT NOT NULL DEFAULT 'text', -- text / image / voice
is_enabled BOOLEAN NOT NULL DEFAULT true,
priority INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
四、Edge Functions 详解
4.1 wechat-token — 令牌管理服务
职责:获取并缓存微信 access_token,所有其他 Edge Function 通过调用此服务获取令牌。
请求方式:GET 或 POST
核心逻辑:
import { serve } from "https://deno.land/std@0.190.0/http/server.ts";
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
// 支持反向代理:配置 SUPERUN_WECHAT_API_PROXY_URL 后走代理,否则直连
const WECHAT_API_BASE = Deno.env.get("SUPERUN_WECHAT_API_PROXY_URL") || "https://api.weixin.qq.com";
const WECHAT_TOKEN_URL = `${WECHAT_API_BASE}/cgi-bin/token`;
const TOKEN_BUFFER_MINUTES = 5; // 提前 5 分钟刷新
serve(async (req) => {
const supabaseUrl = Deno.env.get("SUPABASE_URL")!;
const supabaseServiceKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!;
const supabase = createClient(supabaseUrl, supabaseServiceKey);
// 1. 检查数据库中是否有未过期的缓存令牌
const bufferTime = new Date(
Date.now() + TOKEN_BUFFER_MINUTES * 60 * 1000
).toISOString();
const { data: cachedToken } = await supabase
.from("wechat_tokens")
.select("access_token, expires_at")
.gt("expires_at", bufferTime)
.order("created_at", { ascending: false })
.limit(1)
.maybeSingle();
if (cachedToken) {
return new Response(
JSON.stringify({ access_token: cachedToken.access_token }),
{ status: 200 }
);
}
// 2. 缓存失效,向微信 API 请求新令牌
const appId = Deno.env.get("CUSTOM_MINIPROGRAM_APP_ID");
const appSecret = Deno.env.get("CUSTOM_MINIPROGRAM_APP_SECRET");
const tokenUrl =
`${WECHAT_TOKEN_URL}?grant_type=client_credential&appid=${appId}&secret=${appSecret}`;
const wechatResponse = await fetch(tokenUrl);
const wechatData = await wechatResponse.json();
// 3. 处理 IP 白名单错误(errcode 40164)
if (wechatData.errcode) {
let blockedIp: string | null = null;
if (wechatData.errcode === 40164 && wechatData.errmsg) {
const ipMatch = wechatData.errmsg.match(/invalid ip (\d+\.\d+\.\d+\.\d+)/);
if (ipMatch) blockedIp = ipMatch[1];
}
return new Response(
JSON.stringify({
error: `WeChat API error: ${wechatData.errmsg}`,
errcode: wechatData.errcode,
blocked_ip: blockedIp,
}),
{ status: 502 }
);
}
// 4. 缓存新令牌到数据库
const expiresAt = new Date(
Date.now() + wechatData.expires_in * 1000
).toISOString();
await supabase.from("wechat_tokens").insert({
access_token: wechatData.access_token,
expires_at: expiresAt,
});
// 5. 清理已过期的旧令牌
await supabase
.from("wechat_tokens")
.delete()
.lt("expires_at", new Date().toISOString());
return new Response(
JSON.stringify({ access_token: wechatData.access_token }),
{ status: 200 }
);
});
返回格式:
// 成功
{ "access_token": "ACCESS_TOKEN_VALUE" }
// 失败(IP 白名单问题)
{
"error": "WeChat API error: invalid ip 1.2.3.4...",
"errcode": 40164,
"blocked_ip": "1.2.3.4"
}
4.2 wechat-data — 数据统计服务
职责:代理微信数据统计接口(datacube)和粉丝 / 菜单查询。
请求方式:POST
TokenResult 模式(所有需要 access_token 的函数统一使用):
interface TokenResult {
token?: string;
error?: string;
blocked_ip?: string | null;
}
async function getAccessToken(): Promise<TokenResult> {
const response = await fetch(
`${SUPABASE_URL}/functions/v1/wechat-token`,
{
headers: {
Authorization: `Bearer ${SUPABASE_ANON_KEY}`,
},
}
);
const data = await response.json();
if (!data.access_token) {
return {
error: data.error || "Failed to get access token",
blocked_ip: data.blocked_ip || null,
};
}
return { token: data.access_token };
}
关键设计:当令牌获取失败(如 IP 被封)时,不抛异常,而是返回 { blocked_ip } 给前端,让前端展示友好提示。
支持的 action:
| action | 微信 API | 说明 | 日期限制 |
|---|
get-menu | GET /cgi-bin/menu/get | 获取当前菜单 | 无 |
get-follower-count | GET /cgi-bin/user/get | 获取粉丝总数 | 无 |
user-summary | POST /datacube/getusersummary | 用户增减数据 | 最多 7 天 |
user-cumulate | POST /datacube/getusercumulate | 累计用户数据 | 最多 7 天 |
article-summary | POST /datacube/getarticlesummary | 图文群发日数据 | 最多 1 天 |
article-total | POST /datacube/getarticletotal | 图文群发总数据 | 最多 1 天 |
upstream-msg | POST /datacube/getupstreammsg | 消息概况 | 最多 7 天 |
interface-summary | POST /datacube/getinterfacesummary | 接口分析 | 最多 30 天 |
调用示例(前端 → Edge Function):
import { supabase } from "@/integrations/supabase/client";
// 获取粉丝数
const { data } = await supabase.functions.invoke("wechat-data", {
body: { action: "get-follower-count" },
});
console.log(data.data.total); // 粉丝总数
// 获取用户增减数据(默认最近 7 天)
const { data: userData } = await supabase.functions.invoke("wechat-data", {
body: {
action: "user-summary",
begin_date: "2026-02-26", // 可选,格式 YYYY-MM-DD
end_date: "2026-03-04", // 可选
},
});
console.log(userData.data); // [{ ref_date, user_source, ... }]
4.3 wechat-articles — 图文管理服务
职责:图文 CRUD、发布到微信、从微信同步已发布文章。
支持的 action:
| action | 说明 | 需要认证 |
|---|
save-draft | 保存 / 更新草稿 | ✅ |
publish | 发布到微信(草稿 → 群发) | ✅ |
delete | 软删除文章 | ✅ |
sync-published | 从微信同步已发布文章 | ✅ |
发布流程(两步操作):
// 所有函数开头都有这行,支持代理切换
const WECHAT_API_BASE = Deno.env.get("SUPERUN_WECHAT_API_PROXY_URL") || "https://api.weixin.qq.com";
// 1. 先添加到微信草稿箱
const draftResponse = await fetch(
`${WECHAT_API_BASE}/cgi-bin/draft/add?access_token=${accessToken}`,
{
method: "POST",
body: JSON.stringify({
articles: [{
title: "文章标题",
author: "作者",
digest: "摘要",
content: "<p>正文 HTML</p>",
content_source_url: "",
thumb_media_id: "", // 封面素材 ID(可为空)
need_open_comment: 0,
only_fans_can_comment: 0,
}],
}),
}
);
const { media_id } = await draftResponse.json();
// 2. 提交群发
const publishResponse = await fetch(
`${WECHAT_API_BASE}/cgi-bin/freepublish/submit?access_token=${accessToken}`,
{
method: "POST",
body: JSON.stringify({ media_id }),
}
);
const { publish_id } = await publishResponse.json();
同步已发布文章(核心逻辑):
// ⚠️ 关键注意点:freepublish/batchget 返回的是 article_id,不是 media_id!
const WECHAT_API_BASE = Deno.env.get("SUPERUN_WECHAT_API_PROXY_URL") || "https://api.weixin.qq.com";
const batchResponse = await fetch(
`${WECHAT_API_BASE}/cgi-bin/freepublish/batchget?access_token=${accessToken}`,
{
method: "POST",
body: JSON.stringify({ offset: 0, count: 20, no_content: 0 }),
}
);
const batchResult = await batchResponse.json();
// 返回结构示例:
// {
// "total_count": 5,
// "item_count": 5,
// "item": [
// {
// "article_id": "Ai6E...", ← 正确字段名
// "content": {
// "news_item": [
// { "title": "...", "author": "...", "url": "...", ... }
// ]
// },
// "update_time": 1709712000
// }
// ]
// }
// 去重逻辑:用 article_id 匹配数据库中的 wechat_media_id 字段
const articleIds = allItems.map((item) => item.article_id);
const { data: existing } = await supabaseAdmin
.from("articles")
.select("wechat_media_id")
.in("wechat_media_id", articleIds);
// 插入时,多图文用 article_id + 下标区分
const wechat_media_id = newsIndex === 0
? item.article_id
: `${item.article_id}_${newsIndex}`;
前端调用:
// 从微信同步
const { data } = await supabase.functions.invoke("wechat-articles", {
body: { action: "sync-published" },
});
// data: { success: true, new_count: 5, total_count: 5 }
// 保存草稿
const { data } = await supabase.functions.invoke("wechat-articles", {
body: {
action: "save-draft",
title: "标题",
content: "<p>正文</p>",
author: "作者",
digest: "摘要",
},
});
// 发布到微信
const { data } = await supabase.functions.invoke("wechat-articles", {
body: { action: "publish", article_id: "uuid-of-article" },
});
职责:将本地菜单配置同步到微信、清空微信菜单。
支持的 action:
| action | 说明 | 参数 |
|---|
publish | 发布菜单到微信 | menu_config_id (必填) |
delete | 清空微信菜单 | menu_config_id (可选) |
核心逻辑:
const WECHAT_API_BASE = Deno.env.get("SUPERUN_WECHAT_API_PROXY_URL") || "https://api.weixin.qq.com";
// 发布菜单
const response = await fetch(
`${WECHAT_API_BASE}/cgi-bin/menu/create?access_token=${accessToken}`,
{
method: "POST",
body: JSON.stringify(menuData), // { button: [...] }
}
);
// 清空菜单
const response = await fetch(
`${WECHAT_API_BASE}/cgi-bin/menu/delete?access_token=${accessToken}`,
{ method: "GET" }
);
智能处理:当 publish 时发现菜单为空(button.length === 0),自动调用 delete 接口清空微信菜单,而非创建一个空菜单。
菜单数据结构(微信标准格式):
{
"button": [
{
"type": "view",
"name": "官网",
"url": "https://example.com"
},
{
"name": "服务",
"sub_button": [
{
"type": "view",
"name": "在线客服",
"url": "https://example.com/service"
},
{
"type": "click",
"name": "最新活动",
"key": "LATEST_ACTIVITY"
}
]
}
]
}
五、前端集成模式
5.1 统一的 Hook 封装
所有微信 API 调用都封装为 React Query hooks:
// hooks/useArticles.ts
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { supabase } from "@/integrations/supabase/client";
interface SyncResult {
success?: boolean;
new_count?: number;
total_count?: number;
error?: string;
blocked_ip?: string | null;
}
export function useSyncWechatArticles() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (): Promise<SyncResult> => {
const { data, error } = await supabase.functions.invoke(
"wechat-articles",
{ body: { action: "sync-published" } }
);
if (error) throw error;
if (data?.blocked_ip) {
return { blocked_ip: data.blocked_ip, new_count: 0 };
}
if (data?.error) throw new Error(data.error);
return data as SyncResult;
},
onSuccess: (result) => {
if (!result.blocked_ip) {
queryClient.invalidateQueries({ queryKey: ["articles"] });
}
},
});
}
5.2 IP 白名单错误处理(通用模式)
所有与微信交互的页面都实现了统一的 IP 白名单错误处理:
const [lastBlockedIp, setLastBlockedIp] = useState<string | null>(null);
// 处理响应
const handleResult = (result: { blocked_ip?: string | null }) => {
if (result.blocked_ip) {
setLastBlockedIp(result.blocked_ip);
toast.error(
`微信接口连接失败,请将 IP ${result.blocked_ip} 添加到公众号 IP 白名单`,
{ duration: 15000 }
);
return;
}
// ... 正常处理
};
// UI 提示条
{lastBlockedIp && (
<div className="bg-orange-50 border border-orange-200 rounded-lg p-3">
<p>
IP <code>{lastBlockedIp}</code> 未在微信公众号白名单中
</p>
<button onClick={() => navigator.clipboard.writeText(lastBlockedIp)}>
复制 IP
</button>
</div>
)}
5.3 手动同步(连通性检查)
// hooks/useWechatSync.ts
export function useWechatManualSync() {
const queryClient = useQueryClient();
const [isSyncing, setIsSyncing] = useState(false);
const [lastBlockedIp, setLastBlockedIp] = useState<string | null>(null);
const syncWechatData = useCallback(async () => {
setIsSyncing(true);
setLastBlockedIp(null);
// 用轻量的 get-follower-count 作为连通性检查
const { data, error } = await supabase.functions.invoke("wechat-data", {
body: { action: "get-follower-count" },
});
setIsSyncing(false);
if (data?.blocked_ip) {
setLastBlockedIp(data.blocked_ip);
return { success: false, blockedIp: data.blocked_ip };
}
if (error || data?.error) {
return { success: false };
}
// 成功后刷新相关数据
queryClient.invalidateQueries({ queryKey: ["wechat-overview"] });
return { success: true };
}, [queryClient]);
return { syncWechatData, isSyncing, lastBlockedIp };
}
六、关键注意事项
6.1 IP 白名单问题 — 两种解决方案
Supabase Edge Functions 使用动态出口 IP,无法预先配置白名单。用户可根据自身情况选择以下任一方案:
方案 A:IP 白名单(手动维护)
适合场景:快速验证、临时使用、没有服务器资源。
步骤:
- 首次调用微信 API 会返回
errcode: 40164
- 从
errmsg 中提取被拒绝的 IP:invalid ip x.x.x.x
- 将该 IP 添加到公众号白名单
- 应用前端会展示橙色提示条,支持一键复制 IP
缺点:
- Edge Function IP 可能会变动,需要重新添加
- 需要手动操作微信后台
配置:无需额外配置,默认就是这个模式。
方案 B:反向代理(推荐,一劳永逸)
适合场景:生产环境、长期使用、不想每次 IP 变动都手动加白名单。
原理:在一台有固定公网 IP 的服务器上部署 Nginx 反向代理,所有微信 API 请求都经过这台服务器转发。微信只看到代理服务器的固定 IP,加一次白名单就永久生效。
步骤 1:部署代理服务器
准备一台有固定公网 IP 的服务器(云服务器即可,最低配置就行),安装 Nginx,配置如下:
server {
listen 443 ssl;
server_name wechat-proxy.your-domain.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass https://api.weixin.qq.com;
proxy_set_header Host api.weixin.qq.com;
proxy_ssl_server_name on;
}
}
也可以用 HTTP(端口 80),但建议使用 HTTPS 保证传输安全。
步骤 2:配置微信 IP 白名单
将代理服务器的固定公网 IP 添加到微信开发者平台「基础信息 → 开发密钥 → API IP白名单」。
步骤 3:配置 Edge Function Secret
在 Supabase 后台添加 Edge Function Secret:
| Secret 名称 | 值 | 示例 |
|---|
SUPERUN_WECHAT_API_PROXY_URL | 代理服务器地址 | https://wechat-proxy.your-domain.com |
注意:地址末尾不要加 /。
步骤 4:验证
配置完成后重新部署 Edge Functions,调用任意微信接口验证连通性。
代码层面的实现:
所有 Edge Function 均通过以下一行代码自动切换直连 / 代理模式:
const WECHAT_API_BASE = Deno.env.get("SUPERUN_WECHAT_API_PROXY_URL") || "https://api.weixin.qq.com";
- 未配置
SUPERUN_WECHAT_API_PROXY_URL → 直连 api.weixin.qq.com(方案 A)
- 已配置 → 所有请求走代理地址(方案 B)
以下 4 个 Edge Function 均已支持该配置:
wechat-token(1 处)
wechat-data(8 处)
wechat-articles(3 处)
wechat-menu(3 处)
切换方式:
- 从方案 A 切到方案 B:添加
SUPERUN_WECHAT_API_PROXY_URL Secret 并重新部署
- 从方案 B 切回方案 A:删除该 Secret 并重新部署
- 切换无需修改任何代码
两种方案对比
| 方案 A:IP 白名单 | 方案 B:反向代理 |
|---|
| 配置难度 | 无需额外服务器 | 需要一台云服务器 + 域名 |
| 维护成本 | IP 变动时需手动更新 | 配置一次后无需维护 |
| 稳定性 | 依赖 Edge Function IP 不变 | 始终稳定 |
| 适合场景 | 开发测试、临时使用 | 生产环境、长期运行 |
| 切换方式 | 默认 | 添加/删除 Secret 即可 |
6.2 Access Token 管理
- 微信 access_token 有效期 2 小时,每日调用次数有限
- 通过
wechat_tokens 表缓存,提前 5 分钟刷新
- 所有 Edge Function 都通过调用
wechat-token 函数获取令牌,避免重复请求
- 定期清理过期令牌记录
6.3 微信 API 常见 errcode
| errcode | 含义 | 处理方式 |
|---|
40001 | access_token 无效 | 清除缓存重新获取 |
40164 | IP 不在白名单 | 提取 IP 提示用户添加 |
42001 | access_token 过期 | 自动刷新 |
46003 | 菜单不存在 | 正常情况,返回空菜单 |
61501 | 日期范围超限 | 检查日期区间是否超过 API 限制 |
45047 | 超出发布频率限制 | 提示用户稍后重试 |
6.4 数据统计日期限制
- 微信数据有 1 天延迟,
end_date 最早只能是昨天
getusersummary / getusercumulate:最大跨度 7 天(含首尾)
getarticlesummary / getarticletotal:最大跨度 1 天
getupstreammsg:最大跨度 7 天
getinterfacesummary:最大跨度 30 天
- 日期格式:
YYYY-MM-DD
6.5 freepublish/batchget 字段注意
- 该接口返回的每条记录中,标识字段名为
article_id,不是 media_id
media_id 是草稿箱接口(draft/add)返回的字段
- 混淆这两个字段会导致同步时所有值为
undefined,数据无法写入
6.6 Edge Function 间调用
wechat-data、wechat-articles、wechat-menu 都通过 HTTP 调用 wechat-token
- 调用时使用
SUPABASE_ANON_KEY 或 SUPABASE_SERVICE_ROLE_KEY 做 Bearer Token
config.toml 中设置 verify_jwt = false 以允许跨函数调用
6.7 错误返回策略
所有涉及微信 API 的 Edge Function 统一遵循:
- 令牌获取失败 + IP 被封:返回 HTTP
200 + { blocked_ip: "x.x.x.x" }
- 返回 200 是为了让前端
supabase.functions.invoke() 能正确读取 data(非 200 会被放入 error)
- 微信 API 业务错误:返回 HTTP
200 + { error: "...", errcode: xxx }
- 系统错误:返回 HTTP
500 + { error: "..." }
七、部署清单
第一次部署前检查
通用步骤(两种方案都需要):
方案 A 额外步骤(IP 白名单):
方案 B 额外步骤(反向代理):
Edge Function 文件结构
supabase/functions/
├── wechat-token/
│ └── index.ts # 令牌管理(获取、缓存、清理)
├── wechat-data/
│ └── index.ts # 数据统计代理(粉丝、菜单、datacube)
├── wechat-articles/
│ └── index.ts # 图文管理(CRUD、发布、同步)
├── wechat-menu/
│ └── index.ts # 菜单管理(发布、清空)
└── ai-content-assist/
└── index.ts # AI 内容辅助(非微信相关)
八、微信 API 参考链接