跳到主要内容

RYZZ-主站

RYZZ 主站是整个平台的核心前端应用,基于 Next.js 13 构建的社交直播平台 Web 端。集成了直播观看、创作者管理、Web3 钱包交互、实时通讯、预测市场(Betting)、Token War、礼物打赏、NFT 铸造等核心业务,是一个融合 Web2 社交直播与 Web3 去中心化金融的复杂单页应用。


一、项目技术架构

1.1 整体架构图

1.2 核心技术栈

领域技术选型版本
框架Next.js (Pages Router)13.4.1
UIReact + NextUI18.2.0 / 1.0.0-beta.12
状态管理Recoil0.7.6
样式Tailwind CSS + SCSS3.4.3
Web3Wagmi + Ethers.js + Viem1.3.10 / 5.7.2 / 1.5.4
实时通讯WebSocket + Protobuf原生 / protobufjs 7.2.3
视频播放Aliplayer(阿里云)2.15.5
国际化next-i18next14.1.2
HTTPAxios1.3.3
Hooks 工具ahooks3.7.8
数据可视化Recharts2.12.7
部署Docker + Next.js Standalone-

1.3 Provider 嵌套架构

架构决策

应用采用 15 层 Provider 嵌套 模式,每层有明确职责和依赖关系。嵌套顺序经过精心设计——例如 WagmiConfig 必须在 Auth 之上(钱包连接是认证的前提),WebSocket 必须在 Auth 之下(WS 连接需要认证 Token)。

应用采用多层 Provider 嵌套模式,实现关注点分离:


二、核心功能模块

2.1 实时直播系统

架构设计

关键技术实现

视频播放器配置

  • 使用阿里云 Aliplayer SDK,支持 FLV(原画低延迟)和 HLS 降级
  • 启用帧追赶(hlsFrameChasing)实现低延迟直播
  • 自动倍速播放(1.1x~1.2x)追赶直播进度
  • 30 次自动重试机制保障播放连续性

页面路由结构

  • /channel/[channelId] - 直播间主页(观众视角)
  • /channel/[channelId]/feed - 频道动态流
  • /channel/[channelId]/betting - 投注页面
  • /creator/channel/[channelId]/live - 创作者直播后台
  • /creator/channel/[channelId]/fans - 粉丝管理
  • /creator/channel/[channelId]/interactive/* - 互动功能管理(游戏、预测、Token War、门票、时间卡)

2.2 WebSocket 实时通讯系统

技术亮点

核心技术方案

这是整个主站最具技术深度的模块之一——基于 Protobuf 二进制编码的 WebSocket 通讯协议,相比 JSON 节省约 50% 带宽。系统包含 90+ 个 Proto 定义文件、两级事件路由架构,支撑弹幕、礼物、投注、Token War 等全业务实时消息。

这是整个主站最具技术深度的模块之一,实现了一套基于 Protobuf 的高性能二进制 WebSocket 通讯协议

核心实现

特性实现方式
协议序列化Protobuf 二进制编码(90+ proto 定义文件),相比 JSON 节省约 50% 带宽
连接管理自定义 Ws 类,支持心跳保活(30s)、自动重连(5s 间隔)、消息队列缓冲
事件分发基于 EventEmitter 的事件驱动架构,解耦消息处理与业务逻辑
消息批处理支持 MessageBatch 批量消息,降低网络开销
协议路径请求/响应分离的路径枚举(EReqProtocolPath / ERspProtocolPath),覆盖认证、房间、评论、直播、礼物、投注等全业务

支持的实时消息类型

  • 弹幕/评论消息推送
  • 礼物效果通知与动画触发
  • 投注状态变更通知
  • Token War 实时对战消息
  • 房间在线人数更新
  • 用户余额变化推送
  • 频道火力值刷新
  • 抽奖结果通知
  • 踢出/登出通知

2.3 Web3 集成体系

多层次 Web3 架构

DAuth MPC 钱包(核心亮点)

平台自研的 DAuth 钱包系统,基于 MPC(多方计算)+ AA(账户抽象) 技术:

Web3 无感化核心亮点

DAuth MPC 钱包让用户通过邮箱/手机号即可拥有链上钱包,无需接触助记词和 Gas 费概念。2-of-3 密钥分片方案兼顾安全性和可用性,配合 EIP-4337 账户抽象实现 Paymaster 代付 Gas。

技术优势

  • 用户无需管理助记词,降低 Web3 使用门槛
  • 2-of-3 密钥分片方案,兼顾安全性和可用性
  • 支持 Gas 费预估和 Paymaster 代付
  • 支持通过邮箱/手机号/第三方账号创建钱包

支持的链上操作

操作说明合约方法
投注开盘主播创建预测市场startGuessingGame
用户下注用户参与预测betUp
Keys 交易创作者股份交易buyKeys / withdrawKeys
频道质押质押频道代币pledgeChannel
NFT 铸造创作者资料 NFTmint 操作
加密转账代币转账send

2.4 预测市场(Betting)系统

实现要点

  • 投注话题通过合约 encodeFunctionData 编码后上链
  • 实时赔率通过 WebSocket 推送更新
  • 支持多种投注选项和动态赔率调整
  • 通过 TransactionReceiver Wrapper 统一处理链上交易签名和确认

2.5 Token War 系统

Token War 是平台的特色玩法——代币对战竞猜活动

  • 多个代币分组参与对战
  • 用户通过火力值(Flame)为支持的代币投票
  • 实时排行榜和贡献榜
  • WebSocket 实时推送对战消息
  • 活动周期管理(未开始→进行中→已结束)

核心 HookuseTokenWar.tsx(24,662 行),包含注册、排行榜查询、贡献排名、奖励规则引擎等完整业务逻辑。

2.6 礼物打赏系统

  • 支持虚拟礼物和加密货币礼物两种类型
  • 礼物面板组件(GiftPanel)支持分类浏览和搜索
  • 礼物效果动画通过 video-animation-player 实现
  • 打赏通过 WebSocket 实时推送,所有观众可见
  • 支持硬币支付和零钱包支付两种方式

2.7 创作者经济体系

创作者后台页面

  • /creator/channel/[channelId]/live - 直播控制台
  • /creator/channel/[channelId]/fans - 粉丝数据
  • /creator/channel/[channelId]/interactive/games - 游戏管理
  • /creator/channel/[channelId]/interactive/predicting - 预测游戏管理
  • /creator/channel/[channelId]/interactive/war - Token War 管理
  • /creator/channel/[channelId]/setting - 频道设置
  • /creator/channel/mint - NFT 铸造
  • /creator/channel/[channelId]/ratio - 收益分配比例

2.8 用户系统

多种登录方式

  • MetaMask / WalletConnect 等主流钱包登录
  • DAuth MPC 钱包登录(邮箱/手机号)
  • Google / Twitter 第三方登录
  • JWT Token + Refresh Token 机制

用户资料页面

  • /profile/bundles - 用户包裹/资产
  • /profile/revenue - 收入管理
  • /profile/transactions - 交易历史
  • /profile/points - 积分管理
  • /profile/rewards - 奖励系统(含 Flame 奖励)
  • /profile/settings/* - 账户设置(邮箱、密码、支付码、注销)

2.9 推荐与发现系统

  • 首页推荐 Banner 和内容流
  • 游戏推荐列表
  • 分区(Zones)浏览
  • 搜索功能(直播、用户)
  • Feed 流内容

三、公共包生态

3.1 包依赖关系图

3.2 核心公共包说明

包名职责关键导出
constant全局常量、环境检测、API 端点accessToken, isApp, API_BASE_URL, WS_BASE_URL, 货币 ID 常量
function通用工具函数msToTime, JSONParse, getLocaleString, loadScript, URL 操作
hooksReact Hooks 库useVisibility(页面可见性监听)
typesTypeScript 类型定义NFT 类型枚举(Channel, Ticket, External, Guild, Market)
contract智能合约配置链 ID 枚举、Token/Currency 抽象类、USDT 代币实例、合约地址
login统一认证系统useAuth Hook、LoginProvider、钱包/DAuth/第三方登录、密码管理、Gas 预估
dauth-webDAuth MPC 钱包WalletManager(密钥生成、钱包创建、交易执行、登录认证)
js-bridge-sdkApp 桥接 SDKUser/Device/Router/Transaction/Share/Report 模块
report-sdk埋点分析 SDKGoogle Analytics + Facebook Pixel + Twitter Conversion 三平台集成
ryzz-uikitUI 组件库SVG 图标、Calendar、RangeNumber、Token 组件、Tailwind 预设

四、技术亮点总结

4.1 高性能实时通讯

项目自研了一套基于 Protobuf + WebSocket 的高性能二进制实时通讯系统(src/utils/websocket/),核心由三层架构组成:

连接管理层(core.ts)

自定义 Ws 类封装原生 WebSocket,核心机制:

  • 二进制传输new WebSocket(url) 设置 binaryType: 'arraybuffer',所有消息以 Protobuf 二进制帧传输,相比 JSON 减少约 50% 带宽
  • 心跳保活:30 秒间隔递归 setTimeout 发送 /core/heartbeat 协议(携带时间戳),非 setInterval 避免消息堆积
  • 断线重连:5 秒固定延迟重连,isReconnectionLoading 标志位防止并发重连。重连成功后触发 reconnected 事件,上层 Wrapper 监听后自动重发认证消息
  • 消息队列缓冲:维护 pendingMessageQueue: ISendParams[],当 readyState !== OPEN 时消息入队缓冲,连接恢复后在 onopen 回调中逐条 flush 发送,保障消息不丢失
  • 批量消息协议:发送时将每条消息包装为 MessageBatch.list[](Protobuf 定义),接收时遍历 MessageBatch 中的每个 Message 独立分发,降低小消息的网络开销

编解码层(protobuf.ts)

基于 protobufjs 实现 Protobuf 编解码,90+ 个 .proto 定义文件覆盖全业务:

  • 编码protocol.create(data)protocol.encode(msg).finish()Uint8Array
  • 解码protocol.decode(new Uint8Array(data)) → 业务对象
  • 协议映射表
    • reqProtocolMap:5 个请求协议(heartbeat、login、enterRoom、getLatestComment、reportView)
    • rspProtocolMap:17 个响应协议(覆盖认证、房间、评论、直播、礼物、投注、Token War 等)
    • commentTypeMap:6 种评论子类型,每种对应独立的 Proto 定义

事件分发层(index.ts)— 两级路由架构

这是整个通讯系统最精巧的设计——两级事件路由

src/utils/websocket/index.ts — 两级事件路由
第一级路由:ws.on(ERspProtocolPath.comment, handleCommentResponse)
↓ 收到评论响应后,按 comment.type 二次分发
第二级路由:根据 ECommentPath 解码嵌套的 comment.data,emit 独立事件
→ ECommentPath.sendComment(弹幕文字)
→ ECommentPath.giftEffect(礼物特效)
→ ECommentPath.bettingNotify(投注通知)
→ ECommentPath.tokenWarMsg(Token War 消息)
→ ECommentPath.systemNotice(系统通知)
→ ECommentPath.liveNotice(直播通知)

设计意图:服务端将所有互动消息统一通过 comment 协议下发,客户端在第一级接收后按 type 字段做二次 Protobuf 解码和事件分发。这样服务端只需维护一个推送通道,客户端各业务模块(弹幕组件、礼物动效、投注面板等)独立监听对应的 ECommentPath 事件,完全解耦。

协议路径枚举全景types/websocket.ts):

  • EReqProtocolPath:7 个请求路径(认证、心跳、进房、获取评论、上报观看等)
  • ERspProtocolPath:18 个响应路径(认证结果、房间信息、评论、礼物效果、投注状态、余额变化、踢出通知等)
  • EPushMsgType:30+ 种服务端推送消息类型(钱包通知、活动更新、抽奖结果等)

上层集成wrappers/WebSocket/index.tsx):

  • 组件挂载时初始化 WS 连接,监听 auth 状态变化自动发送登录协议
  • 监听 reconnected 事件重新认证
  • 监听 kickoff 消息执行强制登出

4.2 Web3 无感化体验

项目实现了一套完整的 DAuth MPC 钱包 + EIP-4337 账户抽象 体系,让用户通过邮箱/手机号即可拥有链上钱包,无需接触助记词和 Gas 费概念。核心代码位于 packages/dauth-web/packages/login/

DAuth MPC 密钥方案(2-of-3 分片)

采用 GG20(Gennaro-Goldfeder)多方计算签名协议,密钥分为三片:

分片存储位置说明
s0用户浏览器 LocalStorage经 zlib 压缩 + Base64 编码后本地缓存
s1DAuth 官方服务器通过 remoteKeyStorer 上传
s2业务方服务器通过 remoteKeyStorer 上传

密钥生成流程keyGenerater.ts):

  1. 计算验证消息 "DAuth Verify" 的 Keccak256 哈希
  2. /mpc/genkey 端点请求生成密钥,返回 s0, s1, s2 三片 + 签名 (r, s, v)
  3. 调用 stringCaculator.encodeString([s0, s1, s2]) 将三片密钥压缩编码为 keyResult
  4. s1 + keyResult 上传 DAuth 服务器,s2 上传业务服务器,s0 本地保留

密钥恢复机制keyStorer.ts):当用户换设备或清除缓存时:

  1. 从 DAuth 服务器获取 s1,从业务服务器获取 s2
  2. 从 DAuth 服务器获取 keyResult
  3. 通过 stringCaculator.deCodeString(keyResult, [s1, s2]) 恢复 s0
  4. 恢复公式:s0 = decompress(BigNumber(keyResult) - BigNumber(compress(s1)) - BigNumber(compress(s2)))

MPC 签名流程walletSigner.ts):

  1. 获取本地密钥分片 s0(优先缓存,无缓存则从远程恢复)
  2. 将消息哈希 + s0 发送到 /mpc/proxy/sign(指定 signtype: "gg20"
  3. DAuth 服务器与业务服务器协作计算签名,返回 (r, s, v) 分量
  4. 客户端验证返回的哈希一致性,拼接为完整 ECDSA 签名

EIP-4337 账户抽象(AA)集成

文件web3Helper.ts,核心合约:

  • EntryPoint:EIP-4337 标准入口合约,处理 UserOperation
  • DAuthAccountFactory:创建 AA 账户的工厂合约,通过 getAddress(owner, salt) 计算确定性地址
  • DAuthAccount:用户钱包合约,实现 execute(target, value, calldata) 接口

钱包创建流程createWallet()):

  1. MPC 生成密钥 → 通过 Signer.verifyMessage(hash, r, s, v) 恢复 EOA 地址作为 AA Owner
  2. 调用 DAuthAccountFactory.getAddress(owner, 0) 计算 AA 合约地址(CREATE2 确定性)
  3. 向后端 /wallet/v1/bind 绑定 AA 地址(wallet_type: 10 代表 DAuth AA 钱包)
  4. 首次交易时才真正部署合约(initCode 包含 createAccount 调用)

交易执行流程execute()):

  1. 构造 UserOperation:编码 DAuthAccount.execute(target, value, calldata)callData
  2. 检查 AA 合约是否已部署(getCode()),未部署则生成 initCode
  3. 填充 Gas 参数:maxFeePerGas(当前 gasPrice + priorityFee)、callGasLimitverificationGasLimit
  4. 计算 UserOp 哈希(符合 EIP-4337 规范:keccak256(pack(userOp)) + entryPoint + chainId
  5. MPC 签名 UserOp 哈希
  6. 提交到 /relayer/committrans(Relayer 代提交到 EntryPoint)

Gas 预估eistmateGas()):

  • 调用 entryPoint.estimateGas.simulateHandleOp(userOp)
  • 该方法总是 revert,从 revert data 中解析 verificationGasLimitcallGasLimit
  • 支持 Paymaster 代付:传入 paymasterAndData 参数即可用非原生 Token 支付 Gas

多登录方式与钱包绑定

项目支持三种登录方式,全部最终关联到 AA 钱包地址:

1. Web3 钱包登录(MetaMask / WalletConnect):

  • 使用 Wagmi v1 + Web3Modal v2 连接钱包
  • 链配置:生产环境 Arbitrum One (42161),测试环境 ArbitrumGoerli (421613)
  • 流程:连接钱包 → 获取 Nonce → 钱包签名 Nonce → 提交签名到后端 → 获取 accessToken

2. DAuth 登录(邮箱验证码 / 邮箱密码 / 手机号):

  • 内部使用 PKCE(RFC 7636)OAuth2 授权码流程
  • 流程:验证凭证 → 生成 code_challenge(S256)→ 获取授权码 → 交换 Token → 查询/创建 AA 钱包 → 业务登录

3. 第三方登录(Google / Twitter OAuth):

  • 打开授权窗口 → 通过 postMessage 或 Cookie 回调接收授权码 → 交换 DAuth Token → 创建 AA 钱包 → 业务登录

合约交互示例(投注系统 TransactionReceiver.tsx):

wrappers/Betting/TransactionReceiver.tsx — 链上投注流程
用户发起投注 → 检查 ERC20 allowance
→ 不足则 approve(contractAddress, MaxUint256)
→ 预估 Gas(simulateHandleOp)
→ 用户输入支付密码确认
→ encodeFunctionData('startGuessingGame', args)
→ MPC 签名 → Relayer 提交 → 等待交易确认

4.3 复杂状态管理

项目基于 Recoil 构建了一套精细化的原子状态管理体系,配合 15 层 Provider 嵌套实现业务域隔离和生命周期管理。

Recoil 原子化状态设计

共 9 个状态文件、17+ 个 Atom、1 个 Selector,按业务域严格分离:

状态文件Atoms核心状态
currency.ts6 个supportedCurrencyMap(支持币种映射)、coinBalance/pointBalance(余额)、tokenWarBalancecryptoAssetVo(加密资产)、isTopUpCoinsModalVisible
channel.ts6 个channelList(频道列表)、currentChannelcreatorLiveDetail/liveDetail(直播详情,区分创作者/观众视角)、isMyChannelisCategoriesCollapsed
betting.ts4 Atom + 1 SelectorisEnableBettingopeningBetting(当前开盘投注)、hasOpeningBettingState唯一的 Selector,派生自 openingBetting 判断是否有进行中的投注)、bettingModalInfoisCreatorBettingModalVisible
gift.ts5 个giftListgiftTopic(联合类型:背包/排行榜/游戏)、luckyUserDrawPoolMapcryptoListisGiftCryptoModalVisible
activity.ts1 个activityInfo(含 WarActivityStatus 枚举:未开始/进行中/已结束)
其他多个stream.ts(推流状态)、dom.ts(DOM 引用)、wallet.tstask.tslottery.tsticket.tstimecard.ts

设计决策:选择 Recoil 而非 Redux 的核心原因——原子化状态天然适合直播场景中「频道切换时部分状态重置、部分状态保留」的需求。每个 Atom 独立订阅,余额变化不会触发礼物列表重渲染。

15 层 Provider 嵌套架构

_app.tsx 中的 Provider 嵌套顺序经过精心设计,每层有明确职责:

pages/_app.tsx — 15 层 Provider 嵌套
RecoilRoot                    // Recoil 状态容器
→ Internationalization // i18n 语言环境
→ NextUIProvider // UI 主题(暗色模式)
→ WagmiConfig // Web3 链配置 + 钱包连接
→ Auth // 认证状态(accessToken / 用户信息)
→ PortalProvider // 全局弹窗传送门
→ Page // 页面级布局控制
→ WebSocket // WS 连接初始化 + 认证 + 重连监听
→ CustomWrapper // 页面自定义包装器
→ Currency // 货币状态初始化 + 实时余额同步
→ Activity // Token War 活动状态
→ Betting // 投注状态 + 合约交易接收器
→ Wallet // 钱包操作(转账 TransactionReceiver)
→ Modal // 全局模态框管理
→ Layout // 最终页面布局
→ Content

嵌套顺序的设计逻辑

  • WagmiConfigAuth 之上:钱包连接是认证的前提
  • WebSocketAuth 之下:WS 连接需要认证 Token
  • CurrencyWebSocket 之下:余额变化通过 WS 实时推送更新
  • BettingCurrency 之下:投注操作需要读取余额状态

useCentralizedAssets — 统一资产管理 Hook

这是整个资产体系的核心 Hook(hooks/useCentralizedAssets.tsx),管理三种货币的余额和支付:

支持的货币类型

  • Coins(硬币,currencyId=100):平台虚拟货币,用于礼物打赏
  • Points(积分):任务奖励获取
  • TokenWar Tokens:Token War 活动专用代币

乐观更新机制

hooks/useCentralizedAssets.tsx — 乐观更新流程
用户支付 → 立即扣减本地 Recoil 余额(乐观更新)
→ API 请求成功:保持本地状态
→ API 返回 13002(余额不足):弹出充值弹窗 + 刷新真实余额
→ API 请求失败:回滚本地余额

payWithCoins 流程

  1. 弹出支付确认弹窗(SweePayConfirmModal
  2. 用户确认后调用支付 API
  3. 成功:coinBalance Atom 立即减去支付金额
  4. 失败(错误码 13002):触发 isTopUpCoinsModalVisible = true 弹出充值界面,同时请求 /user/balance 刷新真实余额

实时余额同步wrappers/Currency/index.tsx):

  • 登录后立即拉取初始余额,写入 Recoil Atoms
  • 监听 ERspProtocolPath.userBalanceChangeMsg(WebSocket 推送),实时更新 coinBalance / pointBalance
  • 监听 EPushMsgType.WALLET_RECEIVE_NOTIFY,处理钱包充值到账通知
  • 监听 window.postMessage,处理 iframe 内嵌游戏的跨窗口充值请求

BigNumber 精度处理:使用 ethers.jsBigNumber 处理链上 Token 余额,避免 JavaScript 浮点数精度丢失。payWithSpendingWallet 方法中涉及链上 Token 支付时,所有金额计算均通过 BigNumber 操作。

4.4 CI/CD 与云原生部署

项目构建了一套完整的 GitHub Actions + Docker + 阿里云 ACR + 阿里云 ACK (K8s) + Kustomize 自动化部署流水线。

Docker 多阶段构建(核心亮点)

构建优化成果

四阶段 Docker 构建 + turbo prune --docker Monorepo 裁剪 + Next.js Standalone 输出,最终镜像体积大幅缩减。配合 Turbo Remote Cache 跨 CI 共享构建产物,增量构建显著提速。

Dockerfile 采用 四阶段构建 策略,针对 Monorepo 场景深度优化:

阶段职责关键操作
base基础环境Node 18 Alpine + PNPM 9.7.0,作为所有阶段的基础镜像
builderMonorepo 裁剪turbo prune site --docker 将 Monorepo 裁剪为仅包含 site 及其依赖的最小子集,大幅减少构建上下文
installer依赖安装与构建先复制 lockfile 安装依赖(利用 Docker 层缓存),再复制源码执行 turbo build:${ENV} --filter=site...,支持 Turbo Remote Cache 加速
runner生产运行时仅复制 Next.js standalone 产物 + static + public,以非 root 用户 (nextjs:1001) 运行,PM2 管理进程

关键技术细节

  • turbo prune --docker:Turborepo 专为 Docker 设计的命令,将 Monorepo 拆分为 json/(lockfile + package.json)和 full/(完整源码)两层,充分利用 Docker 构建缓存——依赖不变时跳过 pnpm install
  • Turbo Remote Cache:构建时通过 TURBO_TEAM + TURBO_TOKEN 环境变量启用远程缓存,跨 CI 运行共享构建产物,显著加速重复构建
  • Next.js Standaloneoutput: 'standalone' 模式输出自包含的 Node.js 服务器,无需 node_modules,最终镜像体积大幅缩减
  • PM2 进程管理:生产环境使用 pm2-runtime 运行,提供进程守护、自动重启、优雅关闭等能力
  • 非 Root 运行:创建专用 nextjs 用户(UID 1001)运行应用,符合容器安全最佳实践

Kubernetes 部署配置

使用 Kustomize 管理多环境 K8s 配置:

deploy/kustomize/ — 多环境目录结构
deploy/kustomize/
├── base/ # 基础配置
│ ├── deployment.yaml # Deployment 定义
│ ├── service.yaml # Service 定义
│ └── kustomization.yaml # 基础 Kustomize 配置
└── overlays/
├── test/ # 测试环境覆盖
│ └── kustomization.yaml → namespace: vico
└── production/ # 生产环境覆盖
└── kustomization.yaml → namespace: vico-prod

资源配置

  • 临时存储:10Gi(requests = limits)
  • 内存:1Gi request / 2Gi limit
  • 命名空间隔离:测试环境 vico / 生产环境 vico-prod
  • 镜像拉取密钥:docker-secret-frontend

GitHub Actions 工作流

.github/workflows/deploy-site.yml
# 手动触发,可选环境
on:
workflow_dispatch:
inputs:
environment:
type: choice
options: [test, production]

工作流步骤

  1. 登录阿里云 ACR:使用 AccessKey 认证
  2. 构建 & 推送镜像:环境变量注入 + Turbo Remote Cache + 推送到 vico-test/vico-prod/ 命名空间
  3. ACR 镜像安全扫描:自动扫描镜像漏洞
  4. 设置 ACK 集群上下文:连接阿里云 K8s 集群
  5. Kustomize 部署:根据目标环境选择 overlay,kustomize build | kubectl apply

4.5 工程化实践

  • Monorepo 架构:Turborepo + PNPM Workspace,8 个公共包支撑多应用共享
  • 自动化工具链:OpenAPI 类型自动生成、Proto 文件自动编译、组件脚手架、图片压缩
  • 多环境构建:开发/测试/生产三套环境独立构建,API 和 WebSocket 地址自动切换
  • Turbo Remote Cache:跨 CI 运行共享构建缓存,增量构建显著提速

4.6 数据分析体系

  • 三平台埋点:Google Analytics + Facebook Pixel + Twitter Conversion 同时上报
  • 自有后端埋点:扩展数据采集(浏览器信息、网络状态、广告来源、页面加载时间)
  • 页面停留追踪PageTime 工具记录用户在各页面的停留时长

4.7 性能优化

优化手段实现方式
代码分割next/dynamic 动态导入,大组件(Toaster、PayWithCoin、ReportModal)懒加载
函数稳定性useMemoizedFn(ahooks)避免回调函数重新创建导致的不必要渲染
资源缓存图片资源设置 max-age=31536000, immutable 长缓存策略
二进制通信Protobuf 替代 JSON,降低 WebSocket 带宽消耗
视频低延迟Aliplayer 帧追赶 + 倍速播放策略

4.8 跨应用第三方登录架构

项目通过独立的 binding 应用apps/binding,UmiJS)作为 OAuth 回调中转站,配合 packages/loginuseAuth Hook 和 packages/dauth-web 的 DAuth SDK,实现了一套跨子域、多平台、双通道回调的第三方登录体系。

整体架构 — 四层 Token 交换

为什么需要独立的 binding 应用?

OAuth 提供商(Google、Twitter)要求预注册回调 URL,且 URL 必须固定。如果直接重定向回主站,会面临以下问题:

  • 主站路由复杂(30+ 页面),在路由层混入 OAuth 回调逻辑增加耦合
  • 不同环境(test/production)需要注册不同的回调 URL
  • 回调页面需要处理跨域 Cookie 和 postMessage,逻辑独立

因此将 OAuth 回调收敛到专门的 binding 子域名应用,职责单一,仅负责:接收回调 → 提取授权码 → 通知父窗口 → 关闭自身

binding 应用路由与职责

路由职责触发场景
/afterlogin第三方登录回调Google/Twitter 登录后重定向
/fallback第三方账户绑定回调YouTube/Twitter/Discord/LINE 绑定后重定向
/afterconnect市场平台社交连接回调市场频道社交账号连接
/guide登录引导页受保护路由,需要登录
/task任务管理受保护路由,需要登录

双通道回调机制(核心亮点)

/afterlogin 页面同时使用两种方式将 OAuth 回调 URL 传回主站,保障跨浏览器兼容性:

通道 1 — postMessage(主通道)

apps/binding/afterlogin.tsx + apps/site/useAuth.ts
// binding/afterlogin 页面
window.opener?.postMessage({ url: window.location.href }, '*');

// site 主站监听
window.addEventListener('message', (event) => {
const url = event.data.url; // 包含 code、state 等 OAuth 参数
loginWithThirdPartyCode(url);
});

通道 2 — Cookie + Focus 事件(降级通道)

apps/binding/afterlogin.tsx + apps/site/useAuth.ts
// binding/afterlogin 页面
Cookie.set('vico_login_url', window.location.href, {
domain: getPrimaryDomain(window.location.href), // 设置为主域名 ryzz.tv
expires: 1,
});

// site 主站监听
window.addEventListener('focus', () => {
const url = Cookie.get('vico_login_url'); // 弹窗关闭后父窗口获得焦点时读取
if (url) loginWithThirdPartyCode(url);
Cookie.remove('vico_login_url', { domain: 'ryzz.tv' });
});
浏览器兼容性踩坑

Safari 及部分移动端浏览器中 window.opener 可能为 null(安全策略限制),导致 postMessage 无法送达。因此必须实现 Cookie + Focus 事件降级通道,并使用 hasProcessed 标志位防止双通道重复处理。

为什么需要双通道:Safari/部分移动端浏览器中 window.opener 可能为 null(安全策略限制),postMessage 无法送达。此时用户关闭授权窗口后,主站通过 focus 事件触发,从主域名 Cookie 中读取回调 URL 作为降级方案。两个通道共用 hasProcessed 标志位防止重复处理。

四层 Token 交换链路

第三方登录涉及 4 次 Token 交换,每一层解决不同的信任问题:

层级交换操作输入输出目的
1OAuth 授权码用户授权code(一次性授权码)验证用户身份
2Google/Twitter Tokencode + PKCEaccess_token + id_token获取第三方平台身份
3DAuth Token第三方 Tokend_access_token + did_token统一身份到 DAuth 体系
4业务 TokenDAuth Token + AA 钱包地址accessToken + uid获取业务系统访问权限

Google 登录的 Token 交换细节googleLoginer.ts):

  1. 从回调 URL 解析 code
  2. https://oauth2.googleapis.com/token 发送 code + code_verifier(PKCE),换取 Google access_token + id_token
  3. 向 DAuth /account/v1/sociallogin/exchangedtoken 发送 Google Token(user_type: 30),换取 d_access_token
  4. 解析 did_token(JWT 格式),提取 sub(DAuth 用户 ID)和 aud(验证 appId 匹配)
  5. 用 DAuth Token 查询/创建 AA 钱包地址
  6. 最终调用 /x/user/open/web3_login/dauth_login 获取业务 accessToken

Twitter 登录差异:Twitter 不需要客户端自行交换 Token,直接将 code 发送到 DAuth /account/v1/sociallogin/webexchangedtokenuser_type: 110),由 DAuth 服务端完成 Twitter Token 交换。

账户绑定流程(/fallback)

除登录外,binding 应用还承载 5 个平台的社交账户绑定

apps/binding/types.ts
enum EBindPlatform {
youtube = 1, twitter = 2, facebook = 3, discord = 4, line = 5
}

绑定流程:OAuth 回调到 /fallback → 调用 POST /x/social/user/bind_account/bind → 如果是 YouTube/Discord 还需选择频道(list_channelconfirm_channel)→ 通过 postMessage({ hasAuthorized: true }) 通知父窗口。

所有子域名(site.ryzz.tvbinding.ryzz.tvlive.ryzz.tv)通过 getPrimaryDomain() 提取主域名 ryzz.tv,Cookie 设置 domain: '.ryzz.tv' 实现跨子域共享认证状态。同一策略用于 VICO_ACCESS_TOKENVICO_LOGIN_URL 两个关键 Cookie。


五、业务全景图


六、项目数据概览

指标数据
TypeScript/TSX 文件578+
通用组件92+
自定义 Hooks20+
Protobuf 定义90+
API 模块15+ 个服务目录
Recoil Atoms10+
公共包依赖8 个 workspace 包
支持语言英文/中文
支持链Polygon / Arbitrum
页面路由30+