目录

卡牌游戏对局调试工具

一、背景

随着游戏上线,每天产出大量玩家操作数据。当玩家来反馈bug时,除了其提供的录屏,这些操作数据也是很重要的参考资料。然而:

  • 线上出 Bug 时需要查数据仓库看玩家状态,流程繁琐
  • 从数据仓库导出的 CSV 是原始日志,可读性差

因此我开发了一个工具,把数据渲染成易读的样子,另外顺便加一些调试性操作进去,比如加一张卡什么的。

为了给 windows 系统的同事一起用,正好我又喜欢 rust,于是选择了用 Tauri 进行开发。

本应用是一个 Tauri 桌面应用,为卡牌游戏的开发和测试提供对局调试能力,包括查询实时游戏状态、回放对局记录等功能。

二、整体架构

flowchart TB subgraph Frontend["前端 - Vue 3"] Nav["Tab 导航<br/>查询游戏 / 数仓记录 / 日志记录"] end subgraph Rust["后端 - Tauri Rust"] Commands["Tauri Commands<br/>get_http / post_http / load_config / save_config"] Auth["SSO 认证模块<br/>TOTP → Dashboard 登录 → Cookie"] end subgraph Storage["本地存储"] Config["config.json<br/>username / password / dashboard_token / cookie"] end subgraph External["外部服务"] Taishan["数据仓库<br/>业务数据库<br/>KV存储<br/>日志"] Dashboard["SSO 登录"] end Frontend -- "invoke" --> Rust Rust -- "持久化" --> Config Rust -- "HTTP 请求 + Cookie" --> External

三、核心功能实现

一、SSO 登录认证模块

内网的 API 需要在请求头中带 SSO Cookie 才能访问。Cookie 会过期,需要在 401 时自动用 TOTP 重新登录。

二、游戏状态查询

数据来源

游戏的状态数据(血量、护甲、手牌、弃牌区、牌库)存放在 KV 存储中。

UI 展示

查询结果包含两个玩家的完整状态:HP、护甲、手牌(横向展示)、弃牌区(竖向滚动,红底)、牌库(竖向滚动,蓝底)。卡牌 ID 通过映射表转为中文名。

<div class="players flex gap-6">
  <div v-for="player in gameInfo.players" class="player flex-1">
    <h3>{{ player.player_id }}</h3>
    <p>生命值: {{ player.hp }}</p>

    <!-- 手牌横向排列 -->
    <div class="hand flex flex-wrap gap-1">
      <span v-for="card in player.cards_hand" class="card bg-gray-200">
        {{ get_card_name(card.card_id) }}
      </span>
    </div>

    <!-- 弃牌区竖向滚动 -->
    <div class="discard max-h-48 overflow-y-auto">
      <div v-for="card in player.cards_discard" class="card bg-red-200">
        {{ get_card_name(card.card_id) }}
      </div>
    </div>

    <!-- 牌库竖向滚动 -->
    <div class="deck max-h-48 overflow-y-auto">
      <div v-for="card in player.cards_deck" class="card bg-blue-200">
        {{ get_card_name(card.card_id) }}
      </div>
    </div>
  </div>
</div>

三、对局记录回放

对局记录有两个数据来源,两个页面都是通过 CSV 上传 + papaparse 解析。

Recorder(数仓版本)

数据源是数据仓库表,包含出牌前的状态快照。CSV 中每一行是一次出牌操作,包含 player_idcard_idbefore_player_hpbefore_player_buff 等字段。

解析后按 turn_count 分组,逐回合展示:

const onFileChange = (e) => {
  Papa.parse(file, {
    header: true,
    complete: (result) => {
      // 解析 before_player_buff JSON 字符串
      const raw = result.data.map((row) => ({
        ...row,
        before_player_buff: JSON.parse(row.before_player_buff || "[]"),
        play_time: Number(row.play_time),
      }));
      raw.sort((a, b) => a.play_time - b.play_time);
      records.value = raw;
    },
  });
};

Recorder2(日志版本)

数据源是服务端广播日志,CSV 中 msg 字段包含完整的协议日志。需要从字符串中用正则提取关键字段,再二次解析内层 JSON:

const playerId = msg.match(/playerId:(\d+)/)?.[1];
const action = msg.match(/action:(\d+)/)?.[1];
const timestamp = msg.match(/"timestamp":(\d+)/)?.[1];
const dataStr = msg.match(/data:(\{.*\})$/)?.[1];

const dataObj = JSON.parse(dataStr);
const inner = JSON.parse(dataObj.data); // 二次解析

这里的难点是日志字段是格式化的字符串而非 JSON,需要先通过正则提取外层字段,再逐层解析嵌套结构。action 通过映射表转为可读名称:

const action_map = {
    1000: "出牌",
    1030: "选择卡牌",
    1040: "结束回合",
    1050: "投降",
    2028: "爆牌",
    2037: "受到伤害",
};

四、Settings 窗口的特殊处理

需求

设置窗口(OA 账号密码、Dashboard Token)是一个独立的配置界面,关闭时不应该真正退出,“X” 按钮应该只是隐藏窗口。

实现方案

WebviewWindowBuilder::new(app, "preference", WebviewUrl::App("/#/settings"))
    .visible(false) // 初始不显示
    .build()?;

// 劫持关闭事件,隐藏而非销毁
if let Some(win) = app_handle.get_webview_window(&"preference") {
    win.on_window_event(move |event| {
        if let WindowEvent::CloseRequested { api, .. } = event {
            api.prevent_close();       // 阻止默认关闭
            win.hide().unwrap();        // 隐藏窗口
        }
    });
}

// 菜单触发时显示
app.on_menu_event(move |app_handle, event| {
    if event.id().0.as_str() == "preferences" {
        let win = app_handle.get_webview_window("preference");
        win.show().unwrap();
        win.set_focus().unwrap();
    }
});

api.prevent_close() 是 Tauri 提供的阻止关闭机制,配合 win.hide() 实现"假关闭"效果。相比每次重新创建窗口,隐藏/显示的方式可以保留输入框中的未保存内容。

五、技术选型

技术 选择理由
桌面框架 Tauri 2 比 Electron 体积小(< 10MB),Rust 后端性能好
前端框架 Vue 3 + Vite 轻量,Composition API 适合复杂交互
路由 vue-router Hash 模式,兼容 Tauri 的 file:// 协议
CSV 解析 papaparse 成熟稳定,支持流式解析大文件
HTTP 客户端 reqwest (Rust) Tauri 原生 Rust,异步支持好
TOTP totp-rs Rust 生态中最完善的 TOTP 实现
样式 纯 CSS 桌面工具类应用,不需要组件库

六、总结

本应用是一个典型的"把重复工作工具化"的项目。它将查 KV 系统、解析日志、管理 SSO 登录等这些分散的操作整合到一个桌面 App 中。

优势:

  1. SSO Cookie 自动管理 — 缓存 + 401 自动重登,省去手动获取 Cookie 的步骤
  2. 双源日志回放 — 数仓版本展示精确的出牌前状态,日志版本展示实时广播消息,对照使用定位问题更高效