Xisound 前端路由与 Deep-link 协议规范 v1.0
文档定位
- 上游:D3-FE-ARCH-001 顶层架构 §7.2 新引入技术(vue-router 4) · §6.2 XiStudio deep-link 机制
- 本文范围:7 个 app 的路由树统一规范 +
xi://URL Scheme 权威定义 + 跨 app Intent 分发机制 - 约束强度:所有 apps 必须遵守 · 废弃现有
appMode单体切换范式 - 目标读者:前端工程师 / 其他智能体 / XiStudio Tool Launcher 开发者
1. 路由架构总览
1.1 路由技术选型
| 维度 | 选择 | 理由 |
|---|---|---|
| 路由库 | vue-router 4 | Vue 3 官方 · 类型完备 |
| 路由模式 | history mode(HTML5 pushState) | 对 SEO 友好(尤其 XiVST Marketplace 公网站点)· 支持 Deep-link 直接访问 |
| 路由懒加载 | Dynamic import | 首屏减小 20-40% · 每个主路由单独 chunk |
| Fallback | SPA 模式需要服务端配置 rewrite 规则 | Nginx/Cloudflare 需 try_files $uri $uri/ /index.html; |
1.2 全局路由约定
所有 app 的路由遵循以下统一约定:
| 规则 | 说明 |
|---|---|
| 路径用 kebab-case | 如 /algo-library 不用 /algoLibrary |
| URL 参数用 camelCase | 如 ?projectId=xxx 不用 ?project_id=xxx |
:id 表示主资源 ID |
如 /project/:id/link |
/intent/:intentName |
Deep-link 跳转用 |
| meta.title 必填 | 用于浏览器标签页标题 + 面包屑 |
| meta.requiresAuth | 默认 true · 公开页(landing/login)显式声明 false |
1.3 7 产品路由树汇总
graph LR
subgraph XiStudio["xi-studio"]
SW[/workspace]
SP[/project/:id/link]
SS[/project/:id/signal ⭐]
SC[/project/:id/compile]
SA[/algo-library]
SF[/forge]
SV[/vst]
ST[/tools]
end
subgraph XiForge["xi-forge (内嵌 local tabs)"]
FM[module-designer]
FC[code-generator]
FU[ui-designer]
FP[preset-editor]
FB[publisher]
end
subgraph XiTune["xi-tune"]
TS[/sessions]
TSI[/session/:id/tuner]
TSM[/session/:id/measurement]
TSC[/session/:id/compare]
TSR[/session/:id/report]
TI[/intent/:intentName]
end
subgraph XiTest["xi-test"]
TES[/suites]
TER[/run/:id]
TERP[/reports]
TMT[/module-test]
TTR[/tuning-regression]
TEI[/intent/:intentName]
end
subgraph XiProbe["xi-probe"]
PD[/devices]
PS[/session/:id/acquire]
PA[/session/:id/analyze]
PR[/session/:id/report]
end
subgraph XiVST["xi-vst (双形态)"]
VE[embedded: /plugins]
VM[marketplace: /browse]
VMD[marketplace: /plugin/:slug]
VMDD[marketplace: /developer]
end
subgraph XiMind["xi-mind (非独立 app · SDK 形态)"]
MS[(ai-sdk 作为 package 被所有 app 引用)]
end
class XiStudio,XiForge,XiVST xySgL4; class XiTune,XiTest,XiProbe xySgL2; class XiMind xySgL5;
2. xi:// URL Scheme 协议(权威)
2.1 Scheme 语法
定义:
<app>:目标 app 标识,必须是xi-studio/xi-forge/xi-vst/xi-tune/xi-test/xi-probe之一<route>:app 内部路由路径(不含前导/),支持嵌套(session/123/tuner)或 intent(intent/auto-tune)<query>:URL query string,标准编码
示例:
xi://xi-tune/intent/auto-tune?projectId=proj001&token=tmp_xyz
xi://xi-test/run/test-suite-42?autoStart=true
xi://xi-probe/session/s001/acquire
xi://xi-studio/project/proj001/signal
2.2 标准参数(所有 app 可能用到)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
token |
string | ⭐ 推荐 | 短时效一次性 token(60s 有效期)· 避免长 token 暴露 |
projectId |
string | ⚪ | 关联的 XiStudio 工程 ID |
sessionId |
string | ⚪ | app 内部 session ID |
intent |
string | ⚪ | 显式意图关键字(等价于 /intent/:intentName 路径形式) |
lang |
string | ⚪ | zh-CN / en-US · 覆盖用户默认语言 |
theme |
string | ⚪ | light / dark · 覆盖默认主题 |
autoStart |
boolean | ⚪ | 进入后自动执行主动作(如自动开始测试) |
returnUrl |
string | ⚪ | 完成后返回的 URL(通常回到 xi-studio) |
context |
base64 string | ⚪ | 上下文 JSON 的 base64 编码(过大数据不建议 URL,用 IndexedDB 存 handoff) |
2.3 Token 交换机制(Security)
禁止直接在 URL 中传递长期 JWT token。采用短时效一次性 token 机制:
sequenceDiagram
participant S as XiStudio
participant BE as Backend /api/tool-token
participant T as XiTune
participant Auth as Auth Service
S->>BE: POST /api/tool-token<br/>{tool: 'xi-tune', projectId, ttl: 60s}
BE->>Auth: 验证 Studio 当前 user 有权访问 projectId
Auth-->>BE: OK
BE->>BE: 生成 tmpToken(32 bytes · 60s 有效 · 单次使用)
BE-->>S: {tmpToken, expiresAt}
S->>T: window.open('xi://xi-tune/...&token=tmpToken')
T->>BE: POST /api/auth/exchange-tool-token<br/>{tmpToken}
BE->>BE: 验证 tmpToken(单次 + 未过期)
BE->>BE: 标记 tmpToken 已使用
BE-->>T: {userToken, user, tenant, projectContext}
T->>T: 用 userToken 正常 API 调用
实现要点:
- tmpToken 存后端 Redis(key:
tool_token:<tmpToken>, TTL: 60s) - 单次使用(换取 userToken 后立即 delete)
- 跨 app 调用日志用
toolTokenId串联追踪
2.4 Deep-link 解析 API(@xi/protocol)
// packages/protocol/src/deep-link/parser.ts
import { z } from 'zod'
const DeepLinkSchema = z.object({
app: z.enum(['xi-studio', 'xi-forge', 'xi-vst', 'xi-tune', 'xi-test', 'xi-probe']),
route: z.string(),
params: z.record(z.string()),
})
export type DeepLink = z.infer<typeof DeepLinkSchema>
export function parseDeepLink(url: string): DeepLink {
const u = new URL(url)
if (u.protocol !== 'xi:') {
throw new Error(`Invalid scheme: ${u.protocol} (expected xi:)`)
}
const app = u.hostname as DeepLink['app']
const route = u.pathname.replace(/^\//, '') // 去前导 /
const params: Record<string, string> = {}
u.searchParams.forEach((v, k) => { params[k] = v })
return DeepLinkSchema.parse({ app, route, params })
}
export function buildDeepLink(link: DeepLink): string {
const qs = new URLSearchParams(link.params).toString()
const path = link.route ? `/${link.route}` : ''
return `xi://${link.app}${path}${qs ? `?${qs}` : ''}`
}
3. 各 app 路由树详细定义
3.1 xi-studio
// apps/xi-studio/src/router/index.ts
const routes: RouteRecordRaw[] = [
{
path: '/',
component: ShellLayout,
children: [
{ path: '', redirect: '/workspace' },
{ path: 'workspace', component: WorkspaceView, meta: { title: '工作台', requiresAuth: true } },
{
path: 'project/:id',
component: ProjectContextLayout,
props: true,
children: [
{ path: '', redirect: 'link' },
{ path: 'link', component: LinkEditorView, meta: { title: '链路编辑器(算法画布)' } },
{ path: 'signal', component: SignalFlowView, meta: { title: 'Signal Flow(信号画布)' } }, // ⭐ 新增
{ path: 'compile', component: CompileView, meta: { title: '编译烧录' } },
{ path: 'xml-tune', component: XmlTuneCompatView, meta: { title: 'XML 调音(兼容)' } },
],
},
{ path: 'algo-library', component: AlgoLibraryView, meta: { title: '算法库' } },
{ path: 'algo-library/xialgo/:id', component: AlgoDetailView, props: true },
{ path: 'algo-library/xivst', component: VSTEmbeddedView, meta: { title: 'XiVST Marketplace' } },
{ path: 'algo-library/private', component: PrivateAlgoView },
{ path: 'tools', component: ToolsView, meta: { title: '工具' } },
{ path: 'forge', component: ForgePanelView, meta: { title: 'XiForge 锻造台' } },
{ path: 'settings', component: SettingsView, meta: { title: '设置' } },
],
},
{ path: '/login', component: LoginView, meta: { requiresAuth: false, title: '登录' } },
{ path: '/:pathMatch(.*)*', component: NotFoundView },
]
3.2 xi-tune
// apps/xi-tune/src/router/index.ts
const routes: RouteRecordRaw[] = [
{
path: '/',
component: TuneLayout,
children: [
{ path: '', redirect: '/sessions' },
{ path: 'sessions', component: SessionListView, meta: { title: '调音会话' } },
{
path: 'session/:id',
component: SessionContextLayout,
props: true,
children: [
{ path: '', redirect: 'tuner' },
{ path: 'measurement', component: MeasurementView, meta: { title: '测量' } },
{ path: 'tuner', component: TunerView, meta: { title: '调音' } },
{ path: 'compare', component: CompareView, meta: { title: 'A/B 对比' } },
{ path: 'report', component: ReportView, meta: { title: '报告' } },
],
},
{ path: 'intent/:intentName', component: IntentHandlerView, props: true }, // Deep-link 意图分发
],
},
{ path: '/login', component: LoginView, meta: { requiresAuth: false } },
{ path: '/:pathMatch(.*)*', component: NotFoundView },
]
// Intent 清单
const TUNE_INTENTS = {
'auto-tune': '自动调音(需传 projectId)',
'measure': '直接进入测量流程',
'load-preset': '加载调音预设(需传 presetId)',
'blind-compare': '盲听对比(需传 scenarioIds)',
}
3.3 xi-test
const routes: RouteRecordRaw[] = [
{
path: '/',
component: TestLayout,
children: [
{ path: '', redirect: '/suites' },
{ path: 'suites', component: TestSuiteListView, meta: { title: '测试套件' } },
{ path: 'suites/:id', component: TestSuiteDetailView, props: true },
{ path: 'run/:id', component: TestRunView, props: true, meta: { title: '运行测试' } },
{ path: 'reports', component: ReportsView, meta: { title: '报告' } },
{ path: 'reports/:id', component: ReportDetailView, props: true },
{ path: 'module-test', component: ModuleUnitTestView, meta: { title: '模块单测' } },
{ path: 'tuning-regression', component: TuningRegressionView },
{ path: 'intent/:intentName', component: IntentHandlerView, props: true },
],
},
]
const TEST_INTENTS = {
'run-suite': '运行指定套件(需传 suiteId + autoStart=true)',
'regression': '对比当前工程 vs baseline',
'inject-signal': '快速模块单测(需传 moduleId + signalType)',
}
3.4 xi-probe
const routes: RouteRecordRaw[] = [
{
path: '/',
component: ProbeLayout,
children: [
{ path: '', redirect: '/devices' },
{ path: 'devices', component: DeviceManagerView, meta: { title: '设备管理' } },
{
path: 'session/:id',
component: ProbeSessionLayout,
props: true,
children: [
{ path: '', redirect: 'acquire' },
{ path: 'acquire', component: AcquisitionView },
{ path: 'analyze', component: AnalysisView },
{ path: 'report', component: CertificationReportView },
],
},
{ path: 'signal-generator', component: SignalGeneratorView },
{ path: 'intent/:intentName', component: IntentHandlerView, props: true },
],
},
]
const PROBE_INTENTS = {
'thd-test': 'THD 测试快捷入口',
'frequency-response': '频响测量',
'cert-report': '生成 AES/ITU 认证报告',
}
3.5 xi-vst(双形态)
内嵌形态(在 xi-studio 中):
// 通过 xi-studio 路由 /algo-library/xivst 访问
const EmbeddedRoutes: RouteRecordRaw[] = [
{ path: '', component: PluginBrowserView, meta: { title: 'XiVST 商店' } },
{ path: 'plugin/:slug', component: PluginDetailView, props: true },
]
公网 Marketplace 形态(独立站点):
// apps/xi-vst/marketplace/src/router/index.ts
const MarketplaceRoutes: RouteRecordRaw[] = [
{ path: '/', component: LandingView, meta: { requiresAuth: false } },
{ path: '/browse', component: BrowseView, meta: { title: '浏览插件' } },
{ path: '/plugin/:slug', component: PluginDetailView, props: true },
{ path: '/categories/:cat', component: CategoryView, props: true },
{ path: '/search', component: SearchView },
{
path: '/developer',
component: DeveloperLayout,
children: [
{ path: '', component: DeveloperDashboardView },
{ path: 'plugins', component: DeveloperPluginsView },
{ path: 'plugins/new', component: PluginSubmitView },
{ path: 'plugins/:id/edit', component: PluginEditView, props: true },
{ path: 'revenue', component: RevenueView },
{ path: 'analytics', component: AnalyticsView },
],
},
{ path: '/docs', component: SDKDocsView },
{ path: '/docs/:slug', component: SDKDocDetailView, props: true },
]
3.6 xi-forge(内嵌 local tabs · 无独立 vue-router)
XiForge 作为 XiStudio 内嵌组件,不使用顶层 vue-router,而是用内部 <Tabs> 组件切换 5 个子页面:
<ForgePanel>
<Tabs v-model="activeTab">
<TabPanel name="module-designer">...</TabPanel>
<TabPanel name="code-generator">...</TabPanel>
<TabPanel name="ui-designer">...</TabPanel>
<TabPanel name="preset-editor">...</TabPanel>
<TabPanel name="publisher">...</TabPanel>
</Tabs>
</ForgePanel>
Deep-link 呼起 XiForge 特定 tab:xi://xi-studio/forge?tab=ui-designer
4. 路由守卫与生命周期
4.1 认证守卫
// router/guards.ts
router.beforeEach(async (to, from, next) => {
const auth = useAuthStore()
if (to.meta.requiresAuth === false) return next()
if (!auth.user) {
await auth.tryRestoreSession()
if (!auth.user) {
return next({ path: '/login', query: { returnUrl: to.fullPath } })
}
}
next()
})
4.2 未保存确认守卫
router.beforeEach((to, from, next) => {
if (from.name === 'LinkEditor' || from.name === 'SignalFlow') {
const linkStore = useLinkStore()
if (linkStore.isDirty) {
const confirmed = window.confirm('有未保存的修改,确定离开?')
if (!confirmed) return next(false)
}
}
next()
})
4.3 Deep-link Intent 处理
// views/IntentHandlerView.vue
const route = useRoute()
const intent = route.params.intentName as string
const { token, projectId, ...rest } = route.query
onMounted(async () => {
// 1. 交换 token
const session = await authStore.exchangeToolToken(token as string)
// 2. 加载 context
if (projectId) await projectStore.open(projectId as string)
// 3. 按 intent 分发
switch (intent) {
case 'auto-tune':
await sessionStore.createSession({ projectRef: projectId })
router.replace({ path: `/session/${sessionStore.active.id}/tuner`, query: { mode: 'ai-full' } })
break
// ... 更多 intents
default:
console.warn(`Unknown intent: ${intent}`)
router.replace('/')
}
})
5. 跨 app 通信补充:BroadcastChannel
除 Deep-link 外,同域名打开的多个 app tab 间可通过 BroadcastChannel 广播事件(无需服务端中转):
// packages/protocol/src/bus/broadcast-channel.ts
const XI_CHANNEL = 'xi-platform-bus'
export class XiBus {
private channel: BroadcastChannel
constructor() {
this.channel = new BroadcastChannel(XI_CHANNEL)
}
emit<K extends keyof XiBusEvents>(event: K, payload: XiBusEvents[K]): void {
this.channel.postMessage({ event, payload, timestamp: Date.now() })
}
on<K extends keyof XiBusEvents>(event: K, handler: (p: XiBusEvents[K]) => void): () => void {
const listener = (msg: MessageEvent) => {
if (msg.data.event === event) handler(msg.data.payload)
}
this.channel.addEventListener('message', listener)
return () => this.channel.removeEventListener('message', listener)
}
}
// 事件清单(所有 app 共享)
export interface XiBusEvents {
'project.updated': { projectId: string; source: string }
'algo-library.refreshed': {}
'copilot.handoff': { fromApp: string; toApp: string; intent: string; context: any }
'signal.value-update-broadcast': { signalName: string; value: number } // Signal I/O 全局广播
'session.status-changed': { sessionId: string; status: string }
}
限制:同源(same origin)才可互通 · 跨域场景需退回 postMessage(iframe)或 WebSocket 中转
6. DQ 清单
| DQ 编号 | 问题 | 建议 |
|---|---|---|
| DQ-ROUTE-01 | 是否支持浏览器 back/forward 跨 app(history stack) | 不支持跨 app history(每个 app 独立 tab);单 app 内正常支持 |
| DQ-ROUTE-02 | SPA 路由 + SEO 冲突(XiVST Marketplace 公网站点) | XiVST Marketplace 单独用 SSR(Nuxt)或预渲染(vite-ssg)优化 SEO |
| DQ-ROUTE-03 | xi:// scheme 的 Tauri OS 级注册 |
Y2 Tauri 化时通过 tauri.conf.json 注册 system-wide URL handler |
| DQ-ROUTE-04 | Intent 清单如何跨 team 统一维护 | 在 @xi/protocol 中央定义 TUNE_INTENTS / TEST_INTENTS / PROBE_INTENTS 常量 + 文档 |
7. 版本与变更记录
| 版本 | 日期 | 作者 | 说明 |
|---|---|---|---|
| v1.0 | 2026-05-08 | work-cline | 初稿 · 7 产品路由树 + xi:// URL Scheme 权威定义 + Token 交换机制 + Intent 分发 + BroadcastChannel |
附录 A · 引用
- D3-FE-ARCH-001 顶层架构(§6.2 XiStudio deep-link · §7.2 vue-router 4 引入)
- D3-FE-ARCH-003 共享 UI kit 架构(§3.2
@xi/protocoldeep-link/bus 目录) - D3-FE-ARCH-006 构建与部署拓扑(域名规划 + Nginx rewrite 规则)