RYZZ-主站
RYZZ 主站是整个平台的核心前端应用,基于 Next.js 13 构建的社交直播平台 Web 端。集成了直播观看、创作者管理、Web3 钱包交互、实时通讯、预测市场(Betting)、Token War、礼物打赏、NFT 铸造等核心业务,是一个融合 Web2 社交直播与 Web3 去中心化金融的复杂单页应用。
一、项目技术架构
1.1 整体架构图
1.2 核心技术栈
| 领域 | 技术选型 | 版本 |
|---|---|---|
| 框架 | Next.js (Pages Router) | 13.4.1 |
| UI | React + NextUI | 18.2.0 / 1.0.0-beta.12 |
| 状态管理 | Recoil | 0.7.6 |
| 样式 | Tailwind CSS + SCSS | 3.4.3 |
| Web3 | Wagmi + Ethers.js + Viem | 1.3.10 / 5.7.2 / 1.5.4 |
| 实时通讯 | WebSocket + Protobuf | 原生 / protobufjs 7.2.3 |
| 视频播放 | Aliplayer(阿里云) | 2.15.5 |
| 国际化 | next-i18next | 14.1.2 |
| HTTP | Axios | 1.3.3 |
| Hooks 工具 | ahooks | 3.7.8 |
| 数据可视化 | Recharts | 2.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(账户抽象) 技术:
DAuth MPC 钱包让用户通过邮箱/手机号即可拥有链上钱包,无需接触助记词和 Gas 费概念。2-of-3 密钥分片方案兼顾安全性和可用性,配合 EIP-4337 账户抽象实现 Paymaster 代付 Gas。
技术优势:
- 用户无需管理助记词,降低 Web3 使用门槛
- 2-of-3 密钥分片方案,兼顾安全性和可用性
- 支持 Gas 费预估和 Paymaster 代付
- 支持通过邮箱/手机号/第三方账号创建钱包
支持的链上操作
| 操作 | 说明 | 合约方法 |
|---|---|---|
| 投注开盘 | 主播创建预测市场 | startGuessingGame |
| 用户下注 | 用户参与预测 | betUp |
| Keys 交易 | 创作者股份交易 | buyKeys / withdrawKeys |
| 频道质押 | 质押频道代币 | pledgeChannel |
| NFT 铸造 | 创作者资料 NFT | mint 操作 |
| 加密转账 | 代币转账 | send |
2.4 预测市场(Betting)系统
实现要点:
- 投注话题通过合约
encodeFunctionData编码后上链 - 实时赔率通过 WebSocket 推送更新
- 支持多种投注选项和动态赔率调整
- 通过
TransactionReceiverWrapper 统一处理链上交易签名和确认
2.5 Token War 系统
Token War 是平台的特色玩法——代币对战竞猜活动:
- 多个代币分组参与对战
- 用户通过火力值(Flame)为支持的代币投票
- 实时排行榜和贡献榜
- WebSocket 实时推送对战消息
- 活动周期管理(未开始→进行中→已结束)
核心 Hook:useTokenWar.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 操作 |
hooks | React Hooks 库 | useVisibility(页面可见性监听) |
types | TypeScript 类型定义 | NFT 类型枚举(Channel, Ticket, External, Guild, Market) |
contract | 智能合约配置 | 链 ID 枚举、Token/Currency 抽象类、USDT 代币实例、合约地址 |
login | 统一认证系统 | useAuth Hook、LoginProvider、钱包/DAuth/第三方登录、密码管理、Gas 预估 |
dauth-web | DAuth MPC 钱包 | WalletManager(密钥生成、钱包创建、交易执行、登录认证) |
js-bridge-sdk | App 桥接 SDK | User/Device/Router/Transaction/Share/Report 模块 |
report-sdk | 埋点分析 SDK | Google Analytics + Facebook Pixel + Twitter Conversion 三平台集成 |
ryzz-uikit | UI 组件库 | 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)— 两级路由架构
这是整个通讯系统最精巧的设计——两级事件路由:
第一级路由: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 编码后本地缓存 |
| s1 | DAuth 官方服务器 | 通过 remoteKeyStorer 上传 |
| s2 | 业务方服务器 | 通过 remoteKeyStorer 上传 |
密钥生成流程(keyGenerater.ts):
- 计算验证消息
"DAuth Verify"的 Keccak256 哈希 - 向
/mpc/genkey端点请求生成密钥,返回s0, s1, s2三片 + 签名(r, s, v) - 调用
stringCaculator.encodeString([s0, s1, s2])将三片密钥压缩编码为keyResult s1+keyResult上传 DAuth 服务器,s2上传业务服务器,s0本地保留
密钥恢复机制(keyStorer.ts):当用户换设备或清除缓存时:
- 从 DAuth 服务器获取
s1,从业务服务器获取s2 - 从 DAuth 服务器获取
keyResult - 通过
stringCaculator.deCodeString(keyResult, [s1, s2])恢复s0 - 恢复公式:
s0 = decompress(BigNumber(keyResult) - BigNumber(compress(s1)) - BigNumber(compress(s2)))
MPC 签名流程(walletSigner.ts):
- 获取本地密钥分片
s0(优先缓存,无缓存则从远程恢复) - 将消息哈希 +
s0发送到/mpc/proxy/sign(指定signtype: "gg20") - DAuth 服务器与业务服务器协作计算签名,返回
(r, s, v)分量 - 客户端验证返回的哈希一致性,拼接为完整 ECDSA 签名
EIP-4337 账户抽象(AA)集成
文件:web3Helper.ts,核心合约:
- EntryPoint:EIP-4337 标准入口合约,处理 UserOperation
- DAuthAccountFactory:创建 AA 账户的工厂合约,通过
getAddress(owner, salt)计算确定性地址 - DAuthAccount:用户钱包合约,实现
execute(target, value, calldata)接口
钱包创建流程(createWallet()):
- MPC 生成密钥 → 通过
Signer.verifyMessage(hash, r, s, v)恢复 EOA 地址作为 AA Owner - 调用
DAuthAccountFactory.getAddress(owner, 0)计算 AA 合约地址(CREATE2 确定性) - 向后端
/wallet/v1/bind绑定 AA 地址(wallet_type: 10代表 DAuth AA 钱包) - 首次交易时才真正部署合约(
initCode包含createAccount调用)
交易执行流程(execute()):
- 构造
UserOperation:编码DAuthAccount.execute(target, value, calldata)为callData - 检查 AA 合约是否已部署(
getCode()),未部署则生成initCode - 填充 Gas 参数:
maxFeePerGas(当前 gasPrice + priorityFee)、callGasLimit、verificationGasLimit - 计算 UserOp 哈希(符合 EIP-4337 规范:
keccak256(pack(userOp)) + entryPoint + chainId) - MPC 签名 UserOp 哈希
- 提交到
/relayer/committrans(Relayer 代提交到 EntryPoint)
Gas 预估(eistmateGas()):
- 调用
entryPoint.estimateGas.simulateHandleOp(userOp) - 该方法总是 revert,从 revert data 中解析
verificationGasLimit和callGasLimit - 支持 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):
用户发起投注 → 检查 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.ts | 6 个 | supportedCurrencyMap(支持币种映射)、coinBalance/pointBalance(余额)、tokenWarBalance、cryptoAssetVo(加密资产)、isTopUpCoinsModalVisible |
channel.ts | 6 个 | channelList(频道列表)、currentChannel、creatorLiveDetail/liveDetail(直播详情,区分创作者/观众视角)、isMyChannel、isCategoriesCollapsed |
betting.ts | 4 Atom + 1 Selector | isEnableBetting、openingBetting(当前开盘投注)、hasOpeningBettingState(唯一的 Selector,派生自 openingBetting 判断是否有进行中的投注)、bettingModalInfo、isCreatorBettingModalVisible |
gift.ts | 5 个 | giftList、giftTopic(联合类型:背包/排行榜/游戏)、luckyUserDrawPoolMap、cryptoList、isGiftCryptoModalVisible |
activity.ts | 1 个 | activityInfo(含 WarActivityStatus 枚举:未开始/进行中/已结束) |
| 其他 | 多个 | stream.ts(推流状态)、dom.ts(DOM 引用)、wallet.ts、task.ts、lottery.ts、ticket.ts、timecard.ts |
设计决策:选择 Recoil 而非 Redux 的核心原因——原子化状态天然适合直播场景中「频道切换时部分状态重置、部分状态保留」的需求。每个 Atom 独立订阅,余额变化不会触发礼物列表重渲染。
15 层 Provider 嵌套架构
_app.tsx 中的 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
嵌套顺序的设计逻辑:
WagmiConfig在Auth之上:钱包连接是认证的前提WebSocket在Auth之下:WS 连接需要认证 TokenCurrency在WebSocket之下:余额变化通过 WS 实时推送更新Betting在Currency之下:投注操作需要读取余额状态
useCentralizedAssets — 统一资产管理 Hook
这是整个资产体系的核心 Hook(hooks/useCentralizedAssets.tsx),管理三种货币的余额和支付:
支持的货币类型:
- Coins(硬币,
currencyId=100):平台虚拟货币,用于礼物打赏 - Points(积分):任务奖励获取
- TokenWar Tokens:Token War 活动专用代币
乐观更新机制:
用户支付 → 立即扣减本地 Recoil 余额(乐观更新)
→ API 请求成功:保持本地状态
→ API 返回 13002(余额不足):弹出充值弹窗 + 刷新真实余额
→ API 请求失败:回滚本地余额
payWithCoins 流程:
- 弹出支付确认弹窗(
SweePayConfirmModal) - 用户确认后调用支付 API
- 成功:
coinBalanceAtom 立即减去支付金额 - 失败(错误码 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.js 的 BigNumber 处理链上 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,作为所有阶段的基础镜像 |
| builder | Monorepo 裁剪 | 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 Standalone:
output: 'standalone'模式输出自包含的 Node.js 服务器,无需node_modules,最终镜像体积大幅缩减 - PM2 进程管理:生产环境使用
pm2-runtime运行,提供进程守护、自动重启、优雅关闭等能力 - 非 Root 运行:创建专用
nextjs用户(UID 1001)运行应用,符合容器安全最佳实践
Kubernetes 部署配置
使用 Kustomize 管理多环境 K8s 配置:
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 工作流
# 手动触发,可选环境
on:
workflow_dispatch:
inputs:
environment:
type: choice
options: [test, production]
工作流步骤:
- 登录阿里云 ACR:使用 AccessKey 认证
- 构建 & 推送镜像:环境变量注入 + Turbo Remote Cache + 推送到
vico-test/或vico-prod/命名空间 - ACR 镜像安全扫描:自动扫描镜像漏洞
- 设置 ACK 集群上下文:连接阿里云 K8s 集群
- 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/login 的 useAuth 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(主通道):
// 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 事件(降级通道):
// 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 交换,每一层解决不同的信任问题:
| 层级 | 交换操作 | 输入 | 输出 | 目的 |
|---|---|---|---|---|
| 1 | OAuth 授权码 | 用户授权 | code(一次性授权码) | 验证用户身份 |
| 2 | Google/Twitter Token | code + PKCE | access_token + id_token | 获取第三方平台身份 |
| 3 | DAuth Token | 第三方 Token | d_access_token + did_token | 统一身份到 DAuth 体系 |
| 4 | 业务 Token | DAuth Token + AA 钱包地址 | accessToken + uid | 获取业务系统访问权限 |
Google 登录的 Token 交换细节(googleLoginer.ts):
- 从回调 URL 解析
code - 向
https://oauth2.googleapis.com/token发送code+code_verifier(PKCE),换取 Googleaccess_token+id_token - 向 DAuth
/account/v1/sociallogin/exchangedtoken发送 Google Token(user_type: 30),换取d_access_token - 解析
did_token(JWT 格式),提取sub(DAuth 用户 ID)和aud(验证 appId 匹配) - 用 DAuth Token 查询/创建 AA 钱包地址
- 最终调用
/x/user/open/web3_login/dauth_login获取业务accessToken
Twitter 登录差异:Twitter 不需要客户端自行交换 Token,直接将 code 发送到 DAuth /account/v1/sociallogin/webexchangedtoken(user_type: 110),由 DAuth 服务端完成 Twitter Token 交换。
账户绑定流程(/fallback)
除登录外,binding 应用还承载 5 个平台的社交账户绑定:
enum EBindPlatform {
youtube = 1, twitter = 2, facebook = 3, discord = 4, line = 5
}
绑定流程:OAuth 回调到 /fallback → 调用 POST /x/social/user/bind_account/bind → 如果是 YouTube/Discord 还需选择频道(list_channel → confirm_channel)→ 通过 postMessage({ hasAuthorized: true }) 通知父窗口。
跨域 Cookie 共享策略
所有子域名(site.ryzz.tv、binding.ryzz.tv、live.ryzz.tv)通过 getPrimaryDomain() 提取主域名 ryzz.tv,Cookie 设置 domain: '.ryzz.tv' 实现跨子域共享认证状态。同一策略用于 VICO_ACCESS_TOKEN 和 VICO_LOGIN_URL 两个关键 Cookie。
五、业务全景图
六、项目数据概览
| 指标 | 数据 |
|---|---|
| TypeScript/TSX 文件 | 578+ |
| 通用组件 | 92+ |
| 自定义 Hooks | 20+ |
| Protobuf 定义 | 90+ |
| API 模块 | 15+ 个服务目录 |
| Recoil Atoms | 10+ |
| 公共包依赖 | 8 个 workspace 包 |
| 支持语言 | 英文/中文 |
| 支持链 | Polygon / Arbitrum |
| 页面路由 | 30+ |