2026-04-30 17:54:22 +08:00

15 KiB
Raw Blame History

RedPacket 后端接口说明

本文档按当前 internal/api/redpacket.gointernal/rpc/redpacket/* 实现整理。红包服务已经从独立 Gin 服务迁移为 OpenIM 标准 RPC 服务:

  • HTTP 入口在 internal/api 网关,路由前缀为 /redpacket
  • 网关通过 pbredpacket.RedPacketClient 调用 internal/rpc/redpacket
  • RPC 服务使用 MongoDB 存储,通过 pkg/common/storage/controller.RedPacketDatabase 聚合 DAO
  • 服务注册名为 redPacket,配置文件为 config/openim-rpc-redpacket.yml

1. 基础约定

1.1 Base URL

网关地址由 openim-api 部署决定,例如:

http://127.0.0.1:10002

红包接口统一挂在:

/redpacket

1.2 鉴权

当前 internal/api/router.goWhitelist 未包含 /redpacket/*,因此所有红包 HTTP 接口默认都需要登录 token。

请求头:

token: <OpenIM user token>
operationID: <request id>

RPC 层不信任请求体中的 user_id。当前登录用户统一从 mcontext.GetOpUserID(ctx) 读取。

1.3 请求字段命名

HTTP 请求建议使用 snake_case。网关使用 a2r.ParseRequestNotCheck 解析到 protobuf 请求对象。

示例:

  • HTTP: packet_id
  • protobuf Go 字段: PacketID

1.4 响应格式

网关使用 apiresp.GinSuccess / apiresp.GinError 包装响应。不同 OpenIM 版本的外层字段可能略有差异,下面示例重点展示 data 内容。

成功示意:

{
  "errCode": 0,
  "errMsg": "",
  "data": {}
}

失败示意:

{
  "errCode": 1001,
  "errMsg": "packet_id is required"
}

2. 接口总览

用户侧接口:

  • POST /redpacket/create_order
  • POST /redpacket/created_callback
  • POST /redpacket/detail
  • POST /redpacket/issue_claim_sign
  • POST /redpacket/claim_result
  • POST /redpacket/wallet_bind/challenge
  • POST /redpacket/wallet_bind/confirm
  • POST /redpacket/wallet_bind/detail

管理员接口:

  • POST /redpacket/admin/set_signer
  • POST /redpacket/admin/set_token
  • POST /redpacket/admin/set_expiry
  • POST /redpacket/admin/set_allow_all_tokens
  • POST /redpacket/admin/set_native_token_enabled
  • POST /redpacket/admin/parse_tx_events

3. 用户侧接口

3.1 创建红包业务单

POST /redpacket/create_order
gRPC: CreateOrder(CreateOrderReq) returns (CreateOrderResp)

链上创建红包前调用,服务端创建一条 PENDING 业务记录并返回 biz_id

请求示例:

{
  "chain_type": "EVM",
  "chain_id": 1,
  "contract_address": "0xA1f42567559aBA5Ff0aac84cdE1AaF1F9DbB888F",
  "creator_wallet": "0x1111111111111111111111111111111111111111",
  "group_id": "g001",
  "scope_type": "GROUP",
  "receiver_user_id": "",
  "receiver_user_ids": [],
  "packet_type": 1,
  "token": "0x2222222222222222222222222222222222222222",
  "total_amount": "1000000000000000000",
  "total_shares": 10,
  "expiry_at": 0,
  "remark": "happy new year"
}

字段说明:

  • chain_type: 必填,当前支持 EVMTRON
  • chain_id: 可选EVM client 可用时为空会使用配置的 chainID
  • contract_address: 可选EVM/TRON client 可用时为空会使用配置地址
  • creator_wallet: 必填,发红包钱包地址
  • scope_type: GROUPDIRECTPUBLIC;空值默认 PUBLIC
  • group_id: scope_type=GROUP 时必填
  • receiver_user_id / receiver_user_ids: scope_type=DIRECT 时至少一个非空
  • packet_type: 0 固定红包,1 拼手气红包,2 转账
  • total_amount: 链上最小单位十进制字符串
  • total_shares: 总份数
  • expiry_at: Unix 秒;0 表示使用合约默认过期

成功响应 data

{
  "biz_id": "f8a0f87e-d9cb-4d4a-8350-7bd43ab2e9a4"
}

服务端写入:

  • collection: red_packet
  • status: PENDING
  • creatorUserID: 来自登录上下文,不来自请求体

3.2 创建交易回写

POST /redpacket/created_callback
gRPC: CreatedCallback(CreatedCallbackReq) returns (CreatedCallbackResp)

链上创建交易确认后调用,用于把 biz_id 与链上 packet_id / tx_hash 绑定。

请求示例:

{
  "biz_id": "f8a0f87e-d9cb-4d4a-8350-7bd43ab2e9a4",
  "tx_hash": "0xabc123...",
  "packet_id": "10001",
  "group_id": "g001",
  "scope_type": "GROUP",
  "receiver_user_id": "",
  "receiver_user_ids": []
}

成功响应 data

{}

服务端逻辑:

  • biz_idtx_hash 必填
  • 如果链客户端可用,会解析交易 receipt 中的 PacketCreated
  • 解析成功后校验 creator、packetType、token、amount、shares、expiry 是否与业务单一致
  • 如果链客户端不可用或解析失败,但请求提供了 packet_id,会使用 fallback
  • 成功后更新 red_packet.status=ACTIVE

3.3 查询红包详情

POST /redpacket/detail
gRPC: GetDetail(GetDetailReq) returns (GetDetailResp)

请求示例:

{
  "packet_id": "10001"
}

成功响应 data

{
  "record": {
    "biz_id": "f8a0f87e-d9cb-4d4a-8350-7bd43ab2e9a4",
    "chain_type": "EVM",
    "packet_id": "10001",
    "chain_id": 1,
    "contract_address": "0xA1f42567559aBA5Ff0aac84cdE1AaF1F9DbB888F",
    "creator_user_id": "u1001",
    "creator_wallet": "0x1111111111111111111111111111111111111111",
    "group_id": "g001",
    "scope_type": "GROUP",
    "receiver_user_id": "",
    "receiver_user_ids": [],
    "packet_type": 1,
    "token": "0x2222222222222222222222222222222222222222",
    "total_amount": "1000000000000000000",
    "total_shares": 10,
    "claimed_amount": "123456789",
    "claimed_shares": 1,
    "expiry_at": 0,
    "tx_hash": "0xabc123...",
    "status": "ACTIVE",
    "created_at": 1777000000,
    "updated_at": 1777000060
  },
  "claims": [
    {
      "packet_id": "10001",
      "user_id": "u2002",
      "claimer_wallet": "0x3333333333333333333333333333333333333333",
      "auth_nonce": "328840239847239847",
      "claim_tx_hash": "0xdef456...",
      "claimed_amount": "123456789",
      "block_number": 1234567,
      "status": "CONFIRMED",
      "created_at": 1777000100,
      "updated_at": 1777000100
    }
  ]
}

说明:

  • created_at / updated_at 为 Unix 秒
  • claims 按 Mongo 查询返回DAO 层按 created_at desc 排序

3.4 申请领取签名

POST /redpacket/issue_claim_sign
gRPC: IssueClaimSign(IssueClaimSignReq) returns (IssueClaimSignResp)

请求示例:

{
  "packet_id": "10001",
  "claimer": "0x3333333333333333333333333333333333333333",
  "random_seed": "0"
}

成功响应 data

{
  "auth_nonce": "328840239847239847",
  "deadline": 1777012345,
  "signature": "0x7b1e...a2",
  "random_seed": "8888812345"
}

校验逻辑:

  1. 当前用户必须存在:mcontext.GetOpUserID(ctx) != ""
  2. packet_idclaimer 必填
  3. 红包必须存在且 status=ACTIVE
  4. 未过期、未退款
  5. 当前用户与 claimer 钱包必须有 ACTIVE 绑定
  6. 同一用户 / 同一钱包不能重复领取
  7. 固定红包和拼手气红包要求 group_id 存在
  8. 转账红包要求当前用户为 receiver_user_id

签名逻辑:

  • EVM client 可用时调用 getSignMessage(packetId, claimer, authNonce, randomSeed, deadline) 获取 digest
  • 使用 chain.signerPrivateKey 裸签 digest
  • v 从 0/1 调整为 27/28
  • 如果 signer 私钥未配置,当前代码会返回 placeholder 签名,仅适合本地调试

3.5 领取结果回写

POST /redpacket/claim_result
gRPC: ClaimResult(ClaimResultReq) returns (ClaimResultResp)

请求示例:

{
  "packet_id": "10001",
  "claimer": "0x3333333333333333333333333333333333333333",
  "tx_hash": "0xdef456..."
}

成功响应 data

{}

服务端逻辑:

  • 先保存一条 PENDING claim
  • 若能立即解析 PacketClaimed 事件,则更新为 CONFIRMED
  • 成功解析后会累计 claimed_amount / claimed_shares
  • 红包领完时状态变为 COMPLETED
  • 如果 receipt 暂不可用,保持 PENDING,等待 indexer 补偿

3.6 发起钱包绑定挑战

POST /redpacket/wallet_bind/challenge
gRPC: IssueWalletBindChallenge(IssueWalletBindChallengeReq)

请求示例:

{
  "chain_type": "EVM",
  "chain_id": 1,
  "wallet_address": "0x3333333333333333333333333333333333333333",
  "domain": "redpacket.example.com",
  "uri": "https://redpacket.example.com/wallet-bind"
}

成功响应 data

{
  "challenge_id": "1f7d9b0d-7b43-4d84-bb11-65f2ecf7e321",
  "user_id": "u2002",
  "chain_type": "EVM",
  "chain_id": 1,
  "wallet": "0x3333333333333333333333333333333333333333",
  "protocol": "siwe-eip4361",
  "sign_method": "personal_sign",
  "nonce": "7b7d8d48-9db6-4e95-9daa-40e9517a2a85",
  "message": "redpacket.example.com wants you to sign in with your Ethereum account:\n...",
  "issued_at": "2026-04-30T03:00:00Z",
  "expires_at": "2026-04-30T03:10:00Z"
}

说明:

  • EVM 使用 siwe-eip4361 + personal_sign
  • TRON 使用 tron-signmessagev2 + signMessageV2
  • challenge 有效期为 10 分钟

3.7 确认钱包绑定

POST /redpacket/wallet_bind/confirm
gRPC: ConfirmWalletBind(ConfirmWalletBindReq)

请求示例:

{
  "challenge_id": "1f7d9b0d-7b43-4d84-bb11-65f2ecf7e321",
  "signature": "0x8f..."
}

成功响应 data

{
  "user_id": "u2002",
  "chain_type": "EVM",
  "chain_id": 1,
  "wallet_address": "0x3333333333333333333333333333333333333333",
  "status": "ACTIVE",
  "verified_at": "2026-04-30T03:01:00Z"
}

当前限制:

  • EVM 验签已实现
  • TRON 验签当前返回 TRON wallet binding verification is not implemented yet

3.8 查询钱包绑定

POST /redpacket/wallet_bind/detail
gRPC: GetWalletBinding(GetWalletBindingReq)

请求示例:

{
  "chain_type": "EVM",
  "wallet_address": "0x3333333333333333333333333333333333333333"
}

成功响应 data

{
  "user_id": "u2002",
  "chain_type": "EVM",
  "chain_id": 1,
  "wallet_address": "0x3333333333333333333333333333333333333333",
  "status": "ACTIVE",
  "challenge_id": "1f7d9b0d-7b43-4d84-bb11-65f2ecf7e321",
  "verified_at": "2026-04-30T03:01:00Z"
}

4. 管理员接口

4.1 设置 signer

POST /redpacket/admin/set_signer
gRPC: SetSigner(SetSignerReq)

请求:

{
  "signer_address": "0x4444444444444444444444444444444444444444"
}

响应:

{
  "message": "signer address updated successfully"
}

4.2 设置 token 白名单

POST /redpacket/admin/set_token
gRPC: SetToken(SetTokenReq)

请求:

{
  "token_address": "0x2222222222222222222222222222222222222222",
  "allowed": true,
  "min_amount": "1000000"
}

响应:

{
  "message": "token configuration updated"
}

4.3 设置默认过期时间

POST /redpacket/admin/set_expiry
gRPC: SetExpiry(SetExpiryReq)

请求:

{
  "expiry_seconds": 86400
}

4.4 设置是否允许所有 token

POST /redpacket/admin/set_allow_all_tokens
gRPC: SetAllowAllTokens(SetAllowAllTokensReq)

请求:

{
  "allow_all": false
}

4.5 设置原生币开关

POST /redpacket/admin/set_native_token_enabled
gRPC: SetNativeTokenEnabled(SetNativeTokenEnabledReq)

请求:

{
  "enabled": true
}

4.6 解析交易事件

POST /redpacket/admin/parse_tx_events
gRPC: ParseTxEvents(ParseTxEventsReq)

请求:

{
  "tx_hash": "0xabc123...",
  "chain": "eth"
}

EVM 响应:

{
  "chain": "eth",
  "tx_hash": "0xabc123...",
  "events": [
    {
      "name": "PacketCreated",
      "data": {
        "packetId": "10001",
        "creator": "0x1111111111111111111111111111111111111111"
      }
    }
  ]
}

TRON 当前响应:

{
  "chain": "tron",
  "tx_hash": "7d9e...txid",
  "note": "TRON event parsing not fully implemented in this version"
}

4.7 管理接口当前行为边界

  • EVM admin 接口当前为 mock仅记录日志并返回 message不发链上交易。
  • TRON admin 接口会调用 SendAdminTransaction(...) 尝试发链上交易。
  • 管理接口目前没有单独管理员校验,默认只依赖 API 网关 token。生产建议补管理员鉴权与审计。

5. 业务状态

红包状态:

  • PENDING: 已创建业务单,尚未确认链上创建
  • ACTIVE: 链上创建已确认,可领取
  • COMPLETED: 已领取完成
  • REFUNDED: 已退款

领取状态:

  • PENDING: 已提交领取 txHashreceipt 尚未解析或未确认
  • CONFIRMED: 已解析 PacketClaimed
  • FAILED: 预留失败状态,当前逻辑仅用于重复领取判断时放行失败记录

钱包绑定 challenge 状态:

  • PENDING
  • VERIFIED
  • FAILED
  • EXPIRED

钱包绑定状态:

  • ACTIVE

6. 常见错误

  • op user id is empty: 缺少 token 或 token 未正确注入上下文
  • unsupported chain_type: chain_type 不是 EVMTRON
  • packet_id is required: 缺少红包链上 ID
  • wallet is not bound to user: 当前用户未绑定该领取钱包
  • user already claimed: 当前用户已领取
  • already claimed: 当前钱包已领取
  • packet is not active: 红包尚未激活或已经完成/退款
  • packet is expired: 红包已过期
  • TRON wallet binding verification is not implemented yet: 当前未实现 TRON 绑定验签

7. 前端推荐调用顺序

创建红包:

  1. POST /redpacket/create_order
  2. 钱包发起 createFixedPacket/createRandomPacket/createTransfer
  3. PacketCreated 解析 packetId
  4. POST /redpacket/created_callback
  5. POST /redpacket/detail 刷新状态

绑定钱包:

  1. POST /redpacket/wallet_bind/challenge
  2. 钱包按 sign_method 签名 message
  3. POST /redpacket/wallet_bind/confirm
  4. POST /redpacket/wallet_bind/detail

领取红包:

  1. POST /redpacket/detail
  2. POST /redpacket/issue_claim_sign
  3. 钱包调用链上 claim(packetId, authNonce, randomSeed, deadline, signature)
  4. 可选:POST /redpacket/claim_result
  5. POST /redpacket/detail 刷新状态

8. 存储与索引

Mongo collections

  • red_packet
  • red_packet_claim
  • red_packet_claim_auth
  • red_packet_refund
  • wallet_binding_challenge
  • wallet_binding

主要索引:

  • red_packet.biz_id 唯一
  • red_packet.packet_id
  • red_packet.group_id
  • red_packet_claim.claim_tx_hash 唯一
  • red_packet_claim.packet_id + user_id
  • red_packet_claim.packet_id + claimer_wallet
  • red_packet_claim_auth.auth_nonce 唯一
  • wallet_binding_challenge.challenge_id 唯一
  • wallet_binding.user_id + chain_type + wallet_address 唯一

9. 配置文件

config/openim-rpc-redpacket.yml

rpc:
  registerIP: ""
  listenIP: 0.0.0.0
  autoSetPorts: false
  ports: [10560]

prometheus:
  enable: false
  ports: [12560]

chain:
  rpcURL: ""
  contractAddress: ""
  chainID: 0
  signerPrivateKey: ""
  configAdminPrivateKey: ""

tron:
  fullNodeURL: ""
  contractBase58: ""
  ownerBase58: ""
  privateKeyHex: ""
  feeLimit: 100000000

indexer:
  pollInterval: 5

chain.rpcURL 为空时 EVM client 初始化会失败并降级;tron.fullNodeURL 为空时 TRON client 不启用。服务会继续启动。