← 返回文档导航
通道 Meta Cloud API v25.0
测试服务 api-id-test
正式服务 api-id
包 core.whatsapp
登录 回 App 后消费
去重 DB 唯一键幂等
BeeX 当前主登录方式是 App/H5 自己生成登录码 (例如 SX-9LEBRQ),拼出 wa.me 链接打开 WhatsApp,用户把 LOGIN SX-9LEBRQ 发给 BeeX 官方号。
Meta 将消息推送到 /webhooks/whatsapp,服务端解析后把登录码标记为 VERIFIED 。用户回到 App 后,H5 才调用 POST /api/v1/auth/whatsapp/login-codes/consume 换取 accessToken / refreshToken。
重点变化 :不再在页面后台每 2 秒轮询;只有从 WhatsApp 回到 App / 页面恢复时才消费登录码。测试环境使用 +62 851-7842-6342 ,正式环境使用 +62 851-6815-5794 。
0 · 整体架构
flowchart LR
U(["📱 用户 WhatsApp"]):::trunk
META["☁️ Meta Cloud API graph.facebook.com/v25.0"]:::meta
subgraph CORE["core service · com.seahub.x.core.whatsapp"]
direction TB
WH["WhatsappWebhookController GET 验签 · POST 入站"]:::issue
DP["WhatsappInboundDispatcher 幂等 · 四层路由(同步)"]:::decision
CA["WhatsappCloudApiClient sendText / sendTemplate"]:::a
OTP["WhatsappFirstPhoneOtpSender WA 优先 + 短信兜底"]:::b
end
U -- "① 发消息" --> META -- "② webhook POST" --> WH --> DP
DP -- "③ 即时回复" --> CA -- "出站" --> META -- "④ 推达" --> U
OTP -- "OTP 认证模板" --> CA
classDef trunk fill:#ffffff,stroke:#e8d9b8,color:#1a1410;
classDef meta fill:#eff6ff,stroke:#3b82f6,color:#1e40af;
classDef issue fill:#fff7e6,stroke:#f59e0b,color:#b45309;
classDef decision fill:#fef3c7,stroke:#e07c00,color:#7a4f24,stroke-width:2px;
classDef a fill:#f5f3ff,stroke:#8b5cf6,color:#5b21b6;
classDef b fill:#fff7ed,stroke:#fb923c,color:#9a3412;
📌 两个方向 :入站 =用户主动发消息给官号(登录码 / 邀请码 / 带货链接 / 命令);出站 =系统主动或被动发消息给用户(Bot 回复 / OTP 验证码)。两侧都经 WhatsappCloudApiClient 与 Meta 交互,所有收发记录分别落 wa_inbound_messages / wa_outbound_messages。
1 · App/H5 WhatsApp 登录主流程(当前主流程)
sequenceDiagram
autonumber
participant U as 用户
participant H5 as BeeX H5/App
participant WA as WhatsApp
participant META as Meta Cloud API
participant API as BeeX API
participant DB as MySQL
U->>H5: 点击“WhatsApp 登录”
H5->>H5: 本地生成登录码 SX-XXXXXX
H5->>WA: 打开 wa.me/{officialNumber}?text=LOGIN SX-XXXXXX
U->>WA: 点击发送消息
WA->>META: 消息进入 BeeX 官方号
META->>API: POST /webhooks/whatsapp
API->>DB: 保存入站消息,按 wa_message_id 幂等
API->>DB: 查找/创建 login intent,状态置为 VERIFIED
API->>WA: 回复“登录成功,请返回 BeeX”
U->>H5: 从 WhatsApp 回到 App/H5
H5->>API: POST /api/v1/auth/whatsapp/login-codes/consume
API->>DB: 校验 code 已 VERIFIED、未过期、未消费
API-->>H5: accessToken / refreshToken / userId / referralCode
H5->>H5: 保存登录态,进入业务页面
步骤 端 说明 关键约束
1 H5/App 生成登录码,例如 SX-9LEBRQ,并写入本地 pending 状态。 登录码 TTL 默认 5 分钟;生成在端上完成,减少一次创建意图接口依赖。
2 H5/App 打开 WhatsApp:https://wa.me/{officialNumber}?text=LOGIN+SX-9LEBRQ 测试环境 officialNumber=6285178426342;正式环境 officialNumber=6285168155794。
3 Webhook 收到用户发来的 LOGIN SX-9LEBRQ,解析登录码并置为 VERIFIED。 中间多个空格、换行、大小写差异都应容错;只认有效期内的码。
4 H5/App 用户回到 BeeX 后才调用 consume 接口换 token。 不在后台持续轮询 ;页面恢复、App foreground、深链回跳时触发一次或短窗口重试。
5 API consume 成功后一次性消费登录码。 同一个 code 只能消费一次;已消费/过期需要重新登录。
⚠️ 体验约束 :用户点击允许跳转 WhatsApp 后,BeeX 不应该一直卡在等待页。回到 App 才开始 consume;如果 code 未发送或未验证,提示“验证码未确认,请先在 WhatsApp 发送登录消息或重新登录”。如果 code 过期,提示“登录码已过期,请重新开始登录”。
2 · 入站 INBOUND(用户发 WA 给系统)
2.1 Webhook 入口 · WhatsappWebhookController → /webhooks/whatsapp
方法 用途 逻辑
GET Meta 订阅验证 校验 hub.verify_token == seahub.whatsapp.verify-token,回 hub.challenge
POST 接收消息事件 交 WhatsappInboundDispatcher,同步处理完 才返回 WebhookResult(含 messageCount / verifiedLoginCount / joinedCount / botReplyCount / duplicateMessageCount)
2.2 处理主链 · WhatsappInboundDispatcher.dispatch()
flowchart TD
IN["POST /webhooks/whatsapp"]:::issue --> PARSE["WhatsappWebhookPayloadParser 解析 entry[].changes[].value.messages[]"]:::trunk
PARSE --> LOOP{{"逐条消息"}}:::decision
LOOP --> DUP["① 幂等落库 wa_inbound_messages INSERT IGNORE on wa_message_id"]:::trunk
DUP -->|"已存在"| SKIP["重复推送 → duplicateCount++ 跳过"]:::b
DUP -->|"新消息"| TXT{"② 是文本吗?"}:::decision
TXT -->|"否(图片/语音…)"| NT["回「只读文本」双语提示"]:::a
TXT -->|"是"| R1{"③ 登录码 SX-XXXXX?"}:::decision
R1 -->|"命中"| L["verifyByInboundMessage 意图→VERIFIED · 回登录成功链接"]:::key
R1 -->|"否"| R2{"④ 邀请码 JOIN BXxxxx?"}:::decision
R2 -->|"命中"| J["joinByInboundMessage 建用户 + 绑定邀请人 · 回欢迎语"]:::key
R2 -->|"否"| R3["⑤ WhatsappBotService.handleMessage 带货链接 / 通用命令"]:::a
L --> REPLY["sendText 回复 + 落 wa_outbound_messages"]:::a
J --> REPLY
R3 --> REPLY
classDef trunk fill:#ffffff,stroke:#e8d9b8,color:#1a1410;
classDef issue fill:#fff7e6,stroke:#f59e0b,color:#b45309;
classDef decision fill:#fef3c7,stroke:#e07c00,color:#7a4f24,stroke-width:2px;
classDef a fill:#f5f3ff,stroke:#8b5cf6,color:#5b21b6;
classDef b fill:#fff7ed,stroke:#fb923c,color:#9a3412;
classDef key fill:#fff5f5,stroke:#dc2626,color:#991b1b,stroke-width:2px;
🔒 幂等 :靠 wa_inbound_messages 的唯一键 uk_wa_message_id + INSERT IGNORE,Meta 重推同一条直接跳过(createInbound() 返回 false)。
👤 建用户收敛 :三类命令首次见到一个 WA 号都经 WhatsappAuthService.ensureWhatsappUser() 建 users + user_identities + whatsapp_users;JOIN 额外 bindInviterIfAbsent()——邀请人永久锁定,只认第一次 。
3 · 四层命令路由(命中即停)
优先级 命令 解析器 识别 动作 回复
1 登录 WhatsappLoginMessageParserLOGIN SX-XXXXX 或消息内包含 SX-XXXXX校验/创建登录意图 → 标记 VERIFIED 登录成功 + 引导返回 BeeX(双语)
2 邀请 WhatsappJoinMessageParserJOIN BXxxxx建用户 + 绑定邀请人(永久锁定) 欢迎语 4 态:BOUND/ALREADY_BOUND/SELF_INVITE/INVALID_CODE
3 带货链接 WhatsappProductLinkExtractorTikTok/Shopee URL 建用户 + 生成联盟链接 返回 affiliate 链接
4 通用命令 WhatsappCommandParserHELP/ORDERS/WALLET/ACCOUNT/WITHDRAWWhatsappBotService 查数据订单统计 / 钱包余额 / 账户信息 / 菜单
🤖 Bot 开关 seahub.whatsapp.bot.enabled(默认 true)。所有回复均为中文 + 印尼语双语 。带货链接平台由 ProductLinkParser 识别(当前 TikTok Shop / Shopee)。
4 · 出站 OUTBOUND(系统发 WA 给用户)
统一经 WhatsappCloudApiClient:POST https://graph.facebook.com/v25.0/{phoneNumberId}/messages,Authorization: Bearer {accessToken}。
类型 方法 场景 限制
text sendText()Bot 回复(登录成功 / JOIN / 命令 / 联盟链接) 仅限用户来消息后的 24h 服务窗口 内(Meta 规则)
template sendAuthenticationTemplate()OTP 验证码 模板需 Meta 预审;可主动推 ,不受 24h 限制
📤 出站结果封装为 WhatsappSendMessageResult(success / waMessageId / rawResponse / errorMessage),落 wa_outbound_messages(status=SENT/FAILED),失败接 OpsMonitor 告警 (wa_outbound_failed)。认证模板带 Meta 原生「复制验证码 」按钮,body + button 各带同一码。
5 · 手机 OTP 兜底(验证码由 WA 或短信下发)
这一节不是当前主登录链路,而是手机号验证码兜底 :现有「手机号 OTP 登录」全链路(建意图→生成码→校验→建/认手机号账号→签 token)已实现。它与上面的 WA 主登录方向相反:主登录 是用户把登录码发给 BeeX 官号;手机 OTP 是 BeeX 把验证码发给用户,用户再填回 App。
flowchart TD
S([用户在 App / H5 登录页]):::trunk --> IN["输入手机号 选「用 WhatsApp 收验证码」"]:::trunk
IN --> REQ["① POST /api/v1/auth/phone/otp-intents phone + deviceId + purpose"]:::issue
REQ --> GEN["② 生成 6 位码 codeHash(SHA-256) + TTL 5min + attempts=0 返回 otpIntentId + maskedPhone"]:::trunk
GEN --> SEND{"③ WhatsappFirstPhoneOtpSender 选路"}:::decision
SEND -->|"WA 优先(配齐+有模板)"| WA["WhatsappCloudApiClient 认证模板(含复制按钮)"]:::a
SEND -->|"WA 失败/未配/ mock"| SMS["SmsGatewayService 短信兜底"]:::b
WA --> RECV["④ 用户收到验证码"]:::a
SMS --> RECV
RECV --> TYPE["⑤ 读码 → 填回 App"]:::trunk
TYPE --> VER["⑥ POST /api/v1/auth/phone/otp-intents/{id}/verify"]:::issue
VER --> CHK{"⑦ codeHash 对? · 未过期? · attempts < 上限?"}:::decision
CHK -->|"✅"| OK["⑧ 认/建账号(provider=PHONE,键=手机号) 🔐 签 token · 消费意图(一次性)"]:::key
CHK -->|"❌ 码错"| RT["attempts+1 → 重填(到上限锁定)"]:::issue
CHK -->|"⌛ 过期/超限"| EXP["意图失效 → 重新获取"]:::issue
RT --> TYPE
OK --> DONE(["✅ 登录成功 · 携裂变归因码 → 绑定上线"]):::money
classDef trunk fill:#ffffff,stroke:#e8d9b8,color:#1a1410;
classDef issue fill:#fff7e6,stroke:#f59e0b,color:#b45309;
classDef decision fill:#fef3c7,stroke:#e07c00,color:#7a4f24,stroke-width:2px;
classDef a fill:#f5f3ff,stroke:#8b5cf6,color:#5b21b6;
classDef b fill:#fff7ed,stroke:#fb923c,color:#9a3412;
classDef key fill:#fff5f5,stroke:#dc2626,color:#991b1b,stroke-width:2px;
classDef money fill:#f0fdf4,stroke:#15803d,color:#15803d;
登录方式关系
方式 验证码方向 现状 用在哪
WhatsApp 主登录 App/H5 生成码 → 用户把码发给官号 → 回 App 后 consume ✅ 已实现 BeeX 当前主登录方式
手机 OTP · 短信下发 后端发码 → 用户填回✅ 已实现 有手机号、短信可达
手机 OTP · WhatsApp 下发 后端发码经 WA → 用户填回✅ 代码就绪 · 依赖模板配置 设置/修改登录密码、支付密码、手机号验证等
三方登录 Google/Apple/TikTok OAuth ✅ 代码在 ⛔ 裂变绑定流程禁用 (会断绑定)
6 · 数据表
表 用途 关键点
wa_inbound_messages 入站消息 唯一键 uk_wa_message_id 做幂等;存 type/content/raw_payload
wa_outbound_messages 出站消息 status SENT/FAILED、template_name;接 OpsMonitor 告警
whatsapp_users WA 用户 记 24h 服务窗口、绑定的 BeeX user_id、opt-in 状态
whatsapp_login_intent WA 登录意图 SX-XXXXXX 码,状态 CREATED/VERIFIED/CONSUMED/EXPIRED,5min 过期。当前支持 H5/App 先生成 code,Webhook 收到后反查或创建并置 VERIFIED。
phone_otp_intents 手机 OTP 意图 存 哈希不存明文 ,purpose 区分 LOGIN/SET_LOGIN_PASSWORD/SET_PAY_PASSWORD/SET_WITHDRAW_PASSWORD 等。
7 · 配置项
7.1 WhatsApp 账号环境
环境 Display Phone Number wa.me 号码 WABA ID Phone Number ID 用途
id-test +62 851-7842-6342 628517842634220106631398543041158162724037776印尼测试环境登录 / Bot / 验证
id-prod +62 851-6815-5794 628516815579427228260348012821204657329390185印尼正式环境登录 / Bot / 验证
id-test 客服 +62 851-6815-5850 628516815585032327759504445941227019117161727测试环境客服入口,不用于登录
id-prod 客服 +62 851-9966-4342 628519966434224430268962081141189780530883484正式环境客服入口,不用于登录
7.2 服务端与 H5 配置
配置 默认 含义
SEAHUB_WHATSAPP_OFFICIAL_NUMBER 环境指定 服务端生成 WA 登录 intent / 回复链接时使用的官方号。id-test=6285178426342,id-prod=6285168155794。
NUXT_PUBLIC_WHATSAPP_OFFICIAL_NUMBER 构建脚本指定 H5 自生成登录码并打开 WA 时使用的官方号。prod H5 必须是 6285168155794。
NUXT_PUBLIC_WHATSAPP_CUSTOMER_SUPPORT_NUMBER 6285199664342联系客服入口,必须跳客服号,不要跳登录号。
seahub.whatsapp.verify-token change-meMeta webhook 订阅验证令牌(GET 校验)
seahub.whatsapp.phone-number-id 空 WA 官号电话号码 ID(Cloud API 必需)
seahub.whatsapp.access-token 空 Meta Cloud API Bearer Token
seahub.whatsapp.meta-app-secret 空 Meta App Secret,用于校验 POST webhook 的 X-Hub-Signature-256。测试、正式环境都必须通过密钥变量配置,不能留空。
seahub.whatsapp.graph-api-version v25.0Graph API 版本
seahub.whatsapp.otp-template-name login_code_id登录/手机号验证使用的 WA OTP 模板名;模板不可用时才按发送器配置降级短信。
seahub.whatsapp.pay-password-template-name edit_password_id设置或修改支付密码时使用的 WA OTP 模板名。
seahub.whatsapp.otp-template-language idOTP 模板语言(id-test)
seahub.whatsapp.bot.enabled trueBot 入站处理开关
seahub.auth.phone.mock-enabled false真实发送开关。测试和正式部署都必须保持 false;Mock 只允许本地自动化测试显式开启。
seahub.auth.phone.ttl-seconds 300OTP 有效期;max-attempts=5、code-length=6
8 · 现状 vs 待办
组件 / 能力 状态 说明
入站 webhook + 四层路由 ✅ 已实现并部署 登录码 / 邀请码 / 带货链接 / 命令全通,幂等去重就绪
出站 Cloud API(text/template) ✅ 已实现 WhatsappCloudApiClient 收发就绪
App/H5 自生成登录码 ✅ 已实现 H5 生成 SX-XXXXXX,打开 wa.me;用户回到 App 后调用 consume。
正式/测试 WA 号区分 ✅ 已修复 id-test 使用 6285178426342;id-prod 使用 6285168155794。H5 构建脚本会显式传 NUXT_PUBLIC_WHATSAPP_OFFICIAL_NUMBER。
回 App 后消费登录码 ✅ 已实现 只有 App foreground / 页面恢复时 consume;不再常驻后台 2 秒轮询。
手机 OTP 全链路 + WA 优先发送器 ✅ 已实现 WhatsappFirstPhoneOtpSender(@Primary):WA 优先 + 短信兜底 + 认证模板复制按钮
WA OTP 真实发送 ✅ 代码已接入 WhatsappFirstPhoneOtpSender 优先使用 WA 模板;部署环境必须具备对应号码的 phone-number-id、access-token 和已审核模板。模板或通道失败时才按配置降级短信。
POST webhook HMAC 签名校验 ✅ 已实现 WhatsappWebhookController 使用 Meta App Secret 校验 X-Hub-Signature-256。为兼容旧环境,Secret 缺失时不会执行验签,因此测试和正式部署必须配置 Secret,并监控验签失败。
异步队列(outbox + worker) ⏳ 可选优化 当前 webhook 同步处理 ,量大时响应慢 / Meta 可能重推(幂等已兜底);后续可改异步
🔑 不变的铁律 : ① 绑定键永远是手机号 ——无论码走 WA 还是短信,验证通过都按 provider=PHONE + 手机号 认/建同一账号 ② 登录那刻 App 手里的裂变归因码 并入绑定上线,与裂变底座对齐 ③ 码只存 hash 、TTL 5min、attempts 上限、一次性消费 ④ 入站幂等 靠 wa_message_id 唯一键——Meta 重推不会重复建号/发奖。
⚠️ 部署基线 :测试和正式环境都必须配置 meta-app-secret,使 POST webhook 强制执行 X-Hub-Signature-256(HMAC-SHA256)校验;不得依赖 Secret 为空时的兼容放行。
BeeX · WhatsApp 登录与 OTP 技术设计 · 更新至 App/H5 自生成登录码 + 回 App 后消费登录码流程