Private
Public Access
1
0
Files
u-desk/docs/04-功能迭代/GO-DESK-9.插件系统/设计文档/架构设计.md

12 KiB
Raw Blame History

架构设计

一、架构全景

+==================================================================+
|                     u-desk Application                           |
+===================================================================+
|  Core (Go)                    Plugin Manager                      |
|  - App facade                 - Registry / Lifecycle / Loader     |
|  - ConfigStore (SQLite)      - Builtin Plugins (编译时)          |
|  - EventBus (Wails Events)   - External Plugins (运行时)         |
|  - UpdateEngine               - Marketplace Client                |
+----------+----------+---------+-----------+-----------+           |
           |          |         |           |          |            |
   +-------+  +------+  +-----+  +--------+  +------+  +----+       |
   |builtin: |builtin: |builtin | builtin:  |extern:|extern        |
   | file-sys| md-edit| drawio | db-cli    | user-A| user-B        |
   +---------+---------+--------+-----------+--------+--------------+
                                |
+=================================================================+|
|                   Frontend (Vue 3)                               ||
|  PluginRegistry (TS)                                            ||
|  - TabProviders        → 替代 App.vue 硬编码映射                ||
|  - FilePreviewHandlers → 替代 FileEditorPanel if/else 链       ||
|  - ComponentLoader     → defineAsyncComponent 懒加载             ||
|  - PluginSettingsUI    → 设置面板插件管理区块                    ||
+=================================================================+

二、两层插件体系

维度 内置插件 (Builtin) 外部插件 (External/Market)
来源 编译时打包进二进制 运行时从市场安装
位置 internal/plugin/builtin/ 用户数据目录 plugins/
生命周期 随应用启动 用户控制
管理操作 启用 / 禁用 安装 / 卸载 / 启用 / 禁用 / 更新
更新方式 随应用版本更新 独立更新(复用 UpdateAPI
安全级别 完全信任(同进程) 沙箱隔离Phase 5
示例 文件系统、Markdown、Draw.io 第三方预览器、云存储同步

三、设置面板形态(最终态)

┌─ 设置 ──────────────────────────────────────────────┐
│                                                      │
│  ┌─ Tab 配置 ─┐  ┌─ 版本更新 ─┐  ┌─ 插件管理 ─┐     │
│  │             │  │             │  │              │     │
│  │ 拖拽排序     │  │ 自动检查    │  │ 内置插件      │     │
│  │ 显隐控制     │  │ 手动检查    │  │ ✓ Draw.io    │     │
│  │ 默认选择     │  │ 下载/安装   │  │ ✓ Markdown   │     │
│  │             │  │             │  │ ○ 数据库CLI  │     │
│  └─────────────┘  └─────────────┘  │              │     │
│                                   │ 外部插件      │     │
│                                   │ ✓ 云盘同步 v2.1│     │
│                                   │ ○ 思维导图(停用)│     │
│                                   │              │     │
│                                   │ [浏览插件市场] │     │
│                                   └──────────────┘     │
└──────────────────────────────────────────────────────┘

四、UI 插槽体系Slot System

4.1 当前布局(硬编码)

┌─────────────────────────────────┐
│  标题栏 + Tab 导航 + 窗口控制    │  ← 固定区域,不可扩展
├─────────────────────────────────┤
│                                 │
│   <component :is="activeTab">  │  ← 内容区getComponent() 硬编码
│                                 │
├─────────────────────────────────┤
│  更新通知 / 设置抽屉            │  ← 固定区域
└─────────────────────────────────┘

问题:所有 UI 区域都是硬编码的。新插件无法在标题栏加按钮、无法在工具栏扩展、无法在侧边栏注入面板。

4.2 插件化后的插槽布局

┌── slot: titlebar-extra ──────────────────────────────┐
│  [u-desk] [文件管理 | Markdown] [🔌插件A] [🔌插件B] [—][□][×]  │
├──────────────────────────────────────────────────────┤
│  slot: sidebar-left  │  slot: main-content           │  slot: sidebar-right │
│                    │                                   │                      │
│  [内置侧边栏]       │  <component :is="activeTab">  │  (预留)              │
│  ─────────────     │                                   │                      │
│  [插件侧边面板]     │                                   │                      │
│                    │                                   │                      │
├────────────────────┴───────────────────────────────────┴──────────────────────┤
│  slot: toolbar-extra: [📋] [🔍] [🔌插件工具按钮...]                              │
├──────────────────────────────────────────────────────────────────────────────┤
│  slot: status-bar: [就绪] [行: 128] [编码: UTF-8] [🔌插件状态项...]             │
└──────────────────────────────────────────────────────────────────────────────┘

右键菜单 → slot: context-menu-extra追加菜单项
设置面板 → slot: settings-panel内嵌区块

4.3 插槽定义表

插槽 ID 位置 控制方式 谁可注册 Phase
titlebar-extra 标题栏右侧(窗口控制按钮左侧) 声明式PluginMetadata.uiSlots[] 内置 + 插件 3
tab-bar Tab 导航条内部 自动CapabilityTabProvider 插件自动合并 PluginManager 驱动 1
main-content 中央主内容区 自动getComponent() 查 registry registry 驱动 1
sidebar-left 左侧边栏底部FileSystem Sidebar 之后) 声明式 插件 3
sidebar-right 右侧边栏(预览区旁) 声明式 插件 3
toolbar-extra 工具栏末尾追加按钮 声明式 插件 2
status-bar 底部状态栏追加信息 声明式 插件 3
context-menu-extra 右键菜单追加项 声明式 插件 2
settings-panel 设置面板内嵌区块 声明式CapabilitySettings 插件 3

4.4 插槽注册机制(前端)

// frontend/src/plugin/slots.ts

interface SlotDefinition {
  id: string                   // 插槽 ID
  component?: Component         // 注入的 Vue 组件
  position?: 'prepend' | 'append' | 'before' | 'after' // 插入位置
  order?: number               // 同一插槽内的排序权重
}

interface UISlotRegistry {
  /** 插件声明要占用哪些插槽 */
  registerSlot(pluginId: string, def: SlotDefinition): void
  
  /** App.vue 查询某插槽有哪些组件 */
  getSlotComponents(slotId: string): SlotDefinition[]
  
  /** 所有已注册的插槽概览 */
  getAllSlots(): Map<string, SlotDefinition[]>
}

4.5 App.vue 插槽改造示意

<template>
  <!-- 标题栏 -->
  <div class="titlebar">
    <div class="titlebar-left">u-desk</div>
    <a-tabs v-model="activeTab" class="header-tabs">...</a-tabs>
    <!-- 插槽: titlebar-extra -->
    <component 
      v-for="slot in getSlotComponents('titlebar-extra')" 
      :key="slot.id"
      :is="slot.component" 
    />
    <WindowControls />
  </div>

  <!-- 主区域 -->
  <div class="main-layout">
    <!-- 插槽: sidebar-left -->
    <component
      v-for="slot in getSlotComponents('sidebar-left')"
      :is="slot.component"
    />

    <!-- 插槽: main-content核心 -->
    <KeepAlive :include="cacheableComponents">
      <component :is="getComponent(activeTab)" />
    </KeepAlive>
  </div>

  <!-- 工具栏插槽等... -->
</template>

4.6 后端接口支持

// PluginMetadata 新增字段
type PluginMetadata struct {
    // ... 现有字段 ...
    UISlots []string `json:"ui_slots,omitempty"` // 声明占用的 UI 插槽列表
}

// Manager 新增方法
func (m *Manager) GetUISlotContents(slotID string) []UISlotEntry

4.7 切割原则

原则 说明
最小暴露 只开放必要的插槽,不把整个布局变成"配置驱动"
位置固定 每个插槽的位置在 App.vue 中固定,插件只能决定"往里放什么",不能移动插槽本身
顺序可控 同一插槽多插件通过 order 控制排列顺序
隔离渲染 每个插槽内的插件组件有独立作用域,避免样式/状态污染
优雅降级 插件组件报错不影响宿主 UIErrorBoundary 包裹)

五、关键改造点对比

5.1 App.vue 改造Phase 3

改造前

import FileSystem from './components/FileSystem/index.vue'
import MarkdownEditor from './views/markdown-editor/index.vue'

const getComponent = (key: string) => ({
  'file-system': FileSystem,
  'markdown-editor': MarkdownEditor
}[key] || null)

改造后

import '@/plugin/built-in'  // 副作用:执行所有内置插件注册
import { getTabComponent } from '@/plugin/registry'

const getComponent = (key: string) => {
  const loader = getTabComponent(key)
  return loader ? defineAsyncComponent(loader) : null
}

5.2 FileEditorPanel.vue 改造Phase 2

改造前10 层 v-if/v-else-if 链binary/image/video/audio/pdf/excel/word/csv/html/md/text

改造后

<template>
  <div class="editor-content">
    <iframe v-if="renderConfig?.type === 'iframe'" :src="renderConfig.src" />
    <component v-else-if="previewComponent" :is="previewComponent" v-bind="previewProps" />
    <CodeEditor v-else ... />
  </div>
</template>

<script setup lang="ts">
import { resolvePreviewHandler } from '@/plugin/registry'
import { computed } from 'vue'

const handler = computed(() =>
  props.config.currentFileName ? resolvePreviewHandler(props.config.currentFileName) : null
)
const previewComponent = computed(() => handler.value?.getComponent?.())
const renderConfig = computed(() => handler.value?.getRenderConfig?.(props.filePath ?? ''))
</script>