# Qiming Agent 客户端存储数据结构 > 本文档记录了 Qiming Agent Tauri 客户端使用 `@tauri-apps/plugin-store` 存储的所有持久化数据。 > > 存储文件:`qiming_store.bin`(位于应用数据目录) --- ## 目录 - [1. 认证信息 (auth)](#1-认证信息-auth) - [2. 配置信息 (config)](#2-配置信息-config) - [3. 应用设置 (settings)](#3-应用设置-settings) - [4. 初始化向导 (setup)](#4-初始化向导-setup) - [5. 依赖管理 (deps)](#5-依赖管理-deps) - [6. Rust 后端读取示例](#6-rust-后端读取示例) - [7. TypeScript/React 前端调用示例](#7-typescriptreact-前端调用示例) --- ## 1. 认证信息 (auth) 用户登录和认证相关的持久化数据。 | 键名 | 类型 | 说明 | 示例值 | |------|------|------|--------| | `auth.username` | `string` | 登录用户名 | `"admin"` | | `auth.password` | `string` | 登录密码(建议加密存储) | `"******"` | | `auth.config_key` | `string` | 配置密钥(用于服务端验证) | `"abc123..."` | | `auth.saved_key` | `string` | 保存的密钥 | `"xyz789..."` | | `auth.user_info` | `AuthUserInfo` | 用户详细信息(JSON 对象) | 见下方类型定义 | | `auth.online_status` | `boolean` | 在线状态 | `true` | ### AuthUserInfo 类型定义 ```typescript interface AuthUserInfo { username: string; // 用户名 displayName?: string; // 显示名称 avatar?: string; // 头像 URL email?: string; // 邮箱 phone?: string; // 手机号 } ``` --- ## 2. 配置信息 (config) 场景配置和自定义配置相关数据。 | 键名 | 类型 | 说明 | 示例值 | |------|------|------|--------| | `config.current_scene` | `string` | 当前激活的场景 ID | `"production"` | | `config.custom_scenes` | `CustomScene[]` | 自定义场景列表(JSON 数组) | 见下方类型定义 | | `config.version` | `string` | 配置版本号(用于数据迁移) | `"1"` | ### CustomScene 类型定义 ```typescript interface CustomScene { id: string; // 场景唯一标识 name: string; // 场景名称 description?: string; // 场景描述 isDefault?: boolean; // 是否为默认场景 server: { apiUrl: string; // API 服务地址 apiKey?: string; // API 密钥 timeout?: number; // 请求超时(毫秒) }; local: { agent: { host: string; // Agent 主机 port: number; // Agent 端口 scheme?: string; // 协议(http/https) path?: string; // 路径前缀 }; vnc: { host: string; // VNC 主机 port: number; // VNC 端口 scheme?: string; // 协议 }; fileServer: { host: string; // 文件服务主机 port: number; // 文件服务端口 scheme?: string; // 协议 path?: string; // 路径前缀 }; websocket: { host: string; // WebSocket 主机 port: number; // WebSocket 端口 scheme?: string; // 协议(ws/wss) path?: string; // 路径前缀 }; }; } ``` --- ## 3. 应用设置 (settings) 应用行为相关的设置。 | 键名 | 类型 | 说明 | 默认值 | |------|------|------|--------| | `settings.auto_connect` | `boolean` | 自动连接服务 | `false` | | `settings.notifications` | `boolean` | 启用通知 | `true` | > **注意**:开机自启动功能通过系统 API 实现(LaunchAgent/Registry/XDG Autostart),不存储在此处。 --- ## 4. 初始化向导 (setup) 首次启动配置向导的状态和数据。 | 键名 | 类型 | 说明 | 默认值 | |------|------|------|--------| | `setup.completed` | `boolean` | 是否完成初始化向导 | `false` | | `setup.current_step` | `number` | 当前向导步骤(1/2/3) | `1` | | `setup.server_host` | `string` | 服务域名 | `"https://agent.qiming.com"` | | `setup.server_port` | `number` | 服务 HTTPS 端口 | `443` | | `setup.agent_port` | `number` | Agent 服务端口 | `9086` | | `setup.file_server_port` | `number` | 文件服务端口 | `60000` | | `setup.proxy_port` | `number` | 代理服务端口 | `9099` | | `setup.workspace_dir` | `string` | 工作区目录路径 | `""` | ### SetupState 类型定义 ```typescript interface SetupState { completed: boolean; // 是否完成初始化 currentStep: number; // 当前步骤 (1/2/3) serverHost: string; // 服务域名 serverPort: number; // 服务 HTTPS 端口 agentPort: number; // Agent 端口 fileServerPort: number; // 文件服务端口 proxyPort: number; // 代理服务端口 workspaceDir: string; // 工作区目录 } ``` ### 向导步骤说明 | 步骤 | 名称 | 说明 | |------|------|------| | 1 | 基础设置 | 配置服务域名、端口、工作区目录 | | 2 | 账号登录 | 用户名密码登录验证 | | 3 | 依赖安装 | 检测 Node.js、安装本地 npm 依赖 | --- ## 5. 依赖管理 (deps) 本地 npm 依赖安装相关路径。 | 键名 | 类型 | 说明 | 示例值 | |------|------|------|--------| | `deps.install_dir` | `string` | npm 包安装根目录(应用数据目录) | macOS: `~/Library/Application Support/com.qiming.agent-tauri-client/` | | `deps.node_modules_path` | `string` | node_modules 完整路径 | `~/Library/Application Support/com.qiming.agent-tauri-client/node_modules/` | ### 本地安装的 npm 依赖 | 包名 | 说明 | |------|------| | `qiming-file-server` | 文件服务器 | | `qimingcode` | Qiming 代码服务 | | `claude-code-acp-ts` | Claude Code ACP 服务 | ### 各平台应用数据目录 | 平台 | 路径 | |------|------| | macOS | `~/Library/Application Support/com.qiming.agent-tauri-client/` | | Windows | `C:\Users\<用户名>\AppData\Roaming\com.qiming.agent-tauri-client\` | | Linux | `~/.local/share/com.qiming.agent-tauri-client/` | --- ## 6. Rust 后端读取示例 Rust 后端只需读取存储数据,写入操作由前端 UI 负责。 ### 6.1 依赖配置 ```toml # Cargo.toml [dependencies] tauri-plugin-store = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" ``` ### 6.2 初始化插件 ```rust // src-tauri/src/lib.rs pub fn run() { tauri::Builder::default() .plugin(tauri_plugin_store::Builder::new().build()) // ... 其他配置 .run(tauri::generate_context!()) .expect("error while running tauri application"); } ``` ### 6.3 数据结构定义 ```rust use serde::{Deserialize, Serialize}; /// 用户认证信息 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AuthUserInfo { pub username: String, pub display_name: Option, pub avatar: Option, pub email: Option, pub phone: Option, } /// 初始化配置状态 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SetupState { pub completed: bool, pub current_step: i32, pub server_host: String, pub server_port: i32, pub agent_port: i32, pub file_server_port: i32, pub proxy_port: i32, pub workspace_dir: String, } /// 自定义场景配置 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CustomScene { pub id: String, pub name: String, pub description: Option, pub is_default: Option, pub server: ServerConfig, pub local: LocalConfig, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ServerConfig { pub api_url: String, pub api_key: Option, pub timeout: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct LocalConfig { pub agent: EndpointConfig, pub vnc: EndpointConfig, pub file_server: EndpointConfig, pub websocket: EndpointConfig, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct EndpointConfig { pub host: String, pub port: u16, pub scheme: Option, pub path: Option, } ``` ### 6.4 读取函数 ```rust use tauri::Manager; use tauri_plugin_store::StoreExt; const STORE_FILE: &str = "qiming_store.bin"; /// 读取字符串值 fn read_string(app: &tauri::AppHandle, key: &str) -> Option { app.store(STORE_FILE) .ok() .and_then(|store| store.get(key)) .and_then(|v| v.as_str().map(String::from)) } /// 读取布尔值 fn read_bool(app: &tauri::AppHandle, key: &str) -> Option { app.store(STORE_FILE) .ok() .and_then(|store| store.get(key)) .and_then(|v| v.as_bool()) } /// 读取数值 fn read_number(app: &tauri::AppHandle, key: &str) -> Option { app.store(STORE_FILE) .ok() .and_then(|store| store.get(key)) .and_then(|v| v.as_i64()) } /// 读取 JSON 对象 fn read_object(app: &tauri::AppHandle, key: &str) -> Option { app.store(STORE_FILE) .ok() .and_then(|store| store.get(key)) .and_then(|v| serde_json::from_value(v.clone()).ok()) } ``` ### 6.5 读取初始化配置 ```rust /// 获取初始化状态(用于服务启动) pub fn get_setup_state(app: &tauri::AppHandle) -> SetupState { SetupState { completed: read_bool(app, "setup.completed").unwrap_or(false), current_step: read_number(app, "setup.current_step").unwrap_or(1) as i32, server_host: read_string(app, "setup.server_host") .unwrap_or_else(|| "https://agent.qiming.com".to_string()), server_port: read_number(app, "setup.server_port").unwrap_or(443) as i32, agent_port: read_number(app, "setup.agent_port").unwrap_or(9086) as i32, file_server_port: read_number(app, "setup.file_server_port").unwrap_or(60000) as i32, proxy_port: read_number(app, "setup.proxy_port").unwrap_or(9099) as i32, workspace_dir: read_string(app, "setup.workspace_dir").unwrap_or_default(), } } // 使用示例 fn start_services(app: &tauri::AppHandle) { let config = get_setup_state(app); if !config.completed { log::warn!("初始化未完成,跳过服务启动"); return; } log::info!("服务配置: host={}, agent_port={}, file_port={}", config.server_host, config.agent_port, config.file_server_port); // 启动 Agent 服务 // start_agent_service(&config.server_host, config.agent_port); // 启动文件服务 // start_file_service(config.file_server_port); } ``` ### 6.6 读取认证信息 ```rust /// 获取用户认证信息 pub fn get_auth_info(app: &tauri::AppHandle) -> Option { read_object::(app, "auth.user_info") } /// 获取 ConfigKey(用于服务端验证) pub fn get_config_key(app: &tauri::AppHandle) -> Option { read_string(app, "auth.config_key") } /// 检查是否已登录 pub fn is_logged_in(app: &tauri::AppHandle) -> bool { read_bool(app, "auth.online_status").unwrap_or(false) } // 使用示例 fn connect_to_server(app: &tauri::AppHandle) { if !is_logged_in(app) { log::warn!("用户未登录"); return; } if let Some(config_key) = get_config_key(app) { log::info!("使用 ConfigKey 连接服务器"); // connect_with_key(&config_key); } if let Some(user) = get_auth_info(app) { log::info!("当前用户: {}", user.username); } } ``` ### 6.7 读取场景配置 ```rust /// 获取当前场景 ID pub fn get_current_scene_id(app: &tauri::AppHandle) -> Option { read_string(app, "config.current_scene") } /// 获取自定义场景列表 pub fn get_custom_scenes(app: &tauri::AppHandle) -> Vec { read_object::>(app, "config.custom_scenes").unwrap_or_default() } /// 获取当前激活的场景配置 pub fn get_current_scene(app: &tauri::AppHandle) -> Option { let scene_id = get_current_scene_id(app)?; let scenes = get_custom_scenes(app); scenes.into_iter().find(|s| s.id == scene_id) } // 使用示例 fn get_api_endpoint(app: &tauri::AppHandle) -> String { if let Some(scene) = get_current_scene(app) { scene.server.api_url } else { // 使用默认配置 let setup = get_setup_state(app); setup.server_host } } ``` ### 6.8 读取依赖路径 ```rust /// 获取 npm 包安装目录 pub fn get_deps_install_dir(app: &tauri::AppHandle) -> Option { read_string(app, "deps.install_dir") } /// 获取 node_modules 路径 pub fn get_node_modules_path(app: &tauri::AppHandle) -> Option { read_string(app, "deps.node_modules_path") } /// 获取本地 npm 包的 bin 路径 pub fn get_local_bin_path(app: &tauri::AppHandle, package_name: &str) -> Option { let node_modules = get_node_modules_path(app)?; let bin_path = std::path::Path::new(&node_modules) .join(".bin") .join(package_name); if bin_path.exists() { Some(bin_path.to_string_lossy().to_string()) } else { None } } // 使用示例 fn start_qiming_services(app: &tauri::AppHandle) { // 获取各服务的可执行文件路径 let file_server_bin = get_local_bin_path(app, "qiming-file-server"); let qimingcode_bin = get_local_bin_path(app, "qimingcode"); let claude_code_bin = get_local_bin_path(app, "claude-code-acp-ts"); if let Some(bin) = file_server_bin { log::info!("文件服务路径: {}", bin); // std::process::Command::new(&bin).spawn(); } } ``` ### 6.9 完整使用示例 ```rust use tauri::Manager; /// 应用启动时读取配置并初始化服务 pub fn on_app_ready(app: &tauri::AppHandle) { // 1. 检查初始化状态 let setup = get_setup_state(app); if !setup.completed { log::info!("等待用户完成初始化向导"); return; } // 2. 检查登录状态 if !is_logged_in(app) { log::info!("等待用户登录"); return; } // 3. 获取配置 let config_key = get_config_key(app); let workspace = &setup.workspace_dir; log::info!("工作区目录: {}", workspace); log::info!("Agent 端口: {}", setup.agent_port); log::info!("文件服务端口: {}", setup.file_server_port); // 4. 获取依赖路径 if let Some(deps_dir) = get_deps_install_dir(app) { log::info!("依赖安装目录: {}", deps_dir); } // 5. 启动服务 // start_all_services(app, &setup, config_key); } ``` --- ## 7. TypeScript/React 前端调用示例 在前端 UI 中访问存储数据。 ### 7.1 导入存储服务 ```typescript // 导入存储服务 import { initStore, STORAGE_KEYS, authStorage, configStorage, settingsStorage, setupStorage, getString, setString, getBoolean, setBoolean, getNumber, setNumber, getObject, setObject, remove, save, } from './services/store'; ``` ### 7.2 初始化存储(应用启动时) ```typescript // App.tsx 或 main.tsx import { useEffect, useState } from 'react'; import { initStore } from './services/store'; function App() { const [storeReady, setStoreReady] = useState(false); useEffect(() => { const init = async () => { try { await initStore(); setStoreReady(true); } catch (error) { console.error('存储初始化失败:', error); } }; init(); }, []); if (!storeReady) { return
加载中...
; } return ; } ``` ### 7.3 读取存储数据 ```typescript // 使用模块化 API import { authStorage, setupStorage, configStorage } from './services/store'; // 获取用户名 const username = await authStorage.getUsername(); console.log('用户名:', username); // 获取用户信息对象 const userInfo = await authStorage.getUserInfo(); console.log('用户信息:', userInfo); // 获取在线状态 const isOnline = await authStorage.getOnlineStatus(); console.log('在线状态:', isOnline); // 获取初始化状态 const setupState = await setupStorage.getState(); console.log('初始化状态:', setupState); /* { completed: false, currentStep: 1, serverHost: 'https://agent.qiming.com', serverPort: 443, agentPort: 9086, fileServerPort: 60000, proxyPort: 9099, workspaceDir: '' } */ // 检查是否完成初始化 const isSetupCompleted = await setupStorage.isCompleted(); console.log('是否完成初始化:', isSetupCompleted); // 获取当前步骤 const currentStep = await setupStorage.getCurrentStep(); console.log('当前步骤:', currentStep); // 获取当前场景 const sceneId = await configStorage.getCurrentSceneId(); console.log('当前场景:', sceneId); // 获取自定义场景列表 const customScenes = await configStorage.getCustomScenes(); console.log('自定义场景:', customScenes); ``` ### 7.4 写入存储数据 ```typescript import { authStorage, setupStorage, configStorage } from './services/store'; // 保存用户名和密码 await authStorage.setUsername('admin'); await authStorage.setPassword('encrypted_password'); // 保存用户信息对象 await authStorage.setUserInfo({ username: 'admin', displayName: '管理员', email: 'admin@example.com', }); // 保存在线状态 await authStorage.setOnlineStatus(true); // 保存初始化配置(步骤1) await setupStorage.saveStep1({ serverHost: 'https://api.example.com', agentPort: 9086, fileServerPort: 60000, proxyPort: 9099, workspaceDir: '/Users/test/workspace', }); // 完成步骤2(登录) await setupStorage.completeStep2(); // 完成初始化 await setupStorage.complete(); // 保存当前场景 await configStorage.setCurrentSceneId('production'); // 添加自定义场景 await configStorage.addCustomScene({ id: 'my-scene', name: '我的场景', description: '测试用场景', server: { apiUrl: 'https://api.example.com', }, local: { agent: { host: 'localhost', port: 9086 }, vnc: { host: 'localhost', port: 5900 }, fileServer: { host: 'localhost', port: 60000 }, websocket: { host: 'localhost', port: 9088 }, }, }); ``` ### 7.5 删除存储数据 ```typescript import { authStorage, configStorage, setupStorage, clear } from './services/store'; // 清除认证信息(登出) await authStorage.clear(); // 清除配置信息 await configStorage.clear(); // 重置初始化向导 await setupStorage.reset(); // 清除所有数据 await clear(); ``` ### 7.6 React Hook 示例 ```typescript // hooks/useSetupState.ts import { useState, useEffect, useCallback } from 'react'; import { setupStorage, SetupState } from '../services/store'; export function useSetupState() { const [state, setState] = useState(null); const [loading, setLoading] = useState(true); // 加载状态 const loadState = useCallback(async () => { setLoading(true); try { const data = await setupStorage.getState(); setState(data); } catch (error) { console.error('加载初始化状态失败:', error); } finally { setLoading(false); } }, []); // 初始加载 useEffect(() => { loadState(); }, [loadState]); // 更新状态 const updateState = useCallback(async (updates: Partial) => { await setupStorage.setState(updates); await loadState(); // 重新加载 }, [loadState]); // 重置状态 const resetState = useCallback(async () => { await setupStorage.reset(); await loadState(); }, [loadState]); return { state, loading, updateState, resetState, reload: loadState, }; } ``` ```typescript // hooks/useAuth.ts import { useState, useEffect, useCallback } from 'react'; import { authStorage, AuthUserInfo } from '../services/store'; export function useAuth() { const [userInfo, setUserInfo] = useState(null); const [isOnline, setIsOnline] = useState(false); const [loading, setLoading] = useState(true); // 加载认证信息 const loadAuth = useCallback(async () => { setLoading(true); try { const [info, online] = await Promise.all([ authStorage.getUserInfo(), authStorage.getOnlineStatus(), ]); setUserInfo(info); setIsOnline(online ?? false); } catch (error) { console.error('加载认证信息失败:', error); } finally { setLoading(false); } }, []); useEffect(() => { loadAuth(); }, [loadAuth]); // 登录 const login = useCallback(async (username: string, password: string, userInfo: AuthUserInfo) => { await authStorage.setUsername(username); await authStorage.setPassword(password); await authStorage.setUserInfo(userInfo); await authStorage.setOnlineStatus(true); await loadAuth(); }, [loadAuth]); // 登出 const logout = useCallback(async () => { await authStorage.clear(); setUserInfo(null); setIsOnline(false); }, []); return { userInfo, isOnline, loading, login, logout, reload: loadAuth, }; } ``` ### 7.7 React 组件示例 ```tsx // components/SetupWizard.tsx import React, { useState, useEffect } from 'react'; import { Form, Input, InputNumber, Button, Steps, message } from 'antd'; import { setupStorage, DEFAULT_SETUP_STATE } from '../services/store'; export default function SetupWizard({ onComplete }: { onComplete: () => void }) { const [currentStep, setCurrentStep] = useState(1); const [loading, setLoading] = useState(true); const [form] = Form.useForm(); // 加载已保存的配置 useEffect(() => { const loadConfig = async () => { const state = await setupStorage.getState(); setCurrentStep(state.currentStep); form.setFieldsValue({ serverHost: state.serverHost, agentPort: state.agentPort, fileServerPort: state.fileServerPort, proxyPort: state.proxyPort, workspaceDir: state.workspaceDir, }); setLoading(false); }; loadConfig(); }, [form]); // 保存步骤1 const handleStep1Submit = async () => { try { const values = await form.validateFields(); await setupStorage.saveStep1(values); setCurrentStep(2); message.success('基础配置已保存'); } catch (error) { message.error('请填写完整配置'); } }; // 完成初始化 const handleComplete = async () => { await setupStorage.complete(); message.success('初始化完成'); onComplete(); }; if (loading) { return
加载中...
; } return (
{currentStep === 1 && (
)} {currentStep === 2 && (
{/* 登录表单 */}
)} {currentStep === 3 && (
{/* 依赖安装界面 */}
)}
); } ``` ### 7.8 前端与 Rust 的职责划分 ``` ┌─────────────────────────────────────────────────────────────┐ │ 前端 (TypeScript/React) │ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ 读取数据 │ │ 写入数据 │ │ 删除数据 │ │ │ │ ✅ 支持 │ │ ✅ 支持 │ │ ✅ 支持 │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ │ 使用: store.ts 中的 authStorage / setupStorage / ... │ └─────────────────────────────────────────────────────────────┘ │ │ Tauri Store Plugin │ (qiming_store.bin) ▼ ┌─────────────────────────────────────────────────────────────┐ │ 后端 (Rust) │ │ │ │ ┌─────────────┐ │ │ │ 读取数据 │ 用于服务启动、配置获取等 │ │ │ ✅ 支持 │ │ │ └─────────────┘ │ │ │ │ 写入/删除: ❌ 不支持(由前端负责) │ └─────────────────────────────────────────────────────────────┘ ``` **设计原则**: - **前端**:负责所有 UI 交互相关的数据读写(用户配置、登录状态等) - **Rust 后端**:只读取配置用于服务启动、连接验证等系统级操作 这样设计的好处: 1. 数据流向清晰,避免前后端同时写入导致的冲突 2. 前端可即时响应用户操作,无需等待 Rust 命令返回 3. Rust 专注于服务逻辑,不处理 UI 状态 --- ## 存储 API 概览 ### 通用操作 ```typescript // 初始化存储 await initStore(); // 字符串操作 await getString(key); await setString(key, value); // 布尔值操作 await getBoolean(key); await setBoolean(key, value); // 数值操作 await getNumber(key); await setNumber(key, value); // 对象操作(JSON) await getObject(key); await setObject(key, value); // 其他操作 await remove(key); await has(key); await save(); await clear(); await keys(); ``` ### 模块化存储操作 | 模块 | 导出名 | 说明 | |------|--------|------| | 认证 | `authStorage` | 用户登录信息管理 | | 配置 | `configStorage` | 场景配置管理 | | 设置 | `settingsStorage` | 应用设置管理 | | 向导 | `setupStorage` | 初始化向导状态管理 | --- ## 数据清理 ### 清除认证信息 ```typescript await authStorage.clear(); ``` ### 清除配置信息 ```typescript await configStorage.clear(); ``` ### 重置初始化向导 ```typescript await setupStorage.reset(); ``` ### 清除所有数据 ```typescript await clear(); ``` --- ## 更新日志 | 版本 | 日期 | 变更 | |------|------|------| | 1.3 | 2026-02-05 | 更新默认域名:生产环境为 agent.qiming.com,新增 server_port 字段 | | 1.2 | 2026-02-04 | Rust 示例精简为只读操作,明确前后端职责划分 | | 1.1 | 2026-02-04 | 添加 Rust 和 TypeScript 调用示例 | | 1.0 | 2026-02-04 | 初始版本,包含认证、配置、设置、向导、依赖管理 |