Private
Public Access
1
0

新增:文档体系重构+CHANGELOG补充+发布产物清理

This commit is contained in:
2026-05-01 22:22:06 +08:00
parent 3e1a540b83
commit 6eaaa56eb6
164 changed files with 40346 additions and 64 deletions

View File

@@ -0,0 +1,365 @@
# u-desk 插件化架构设计方案
> 状态:调研完成,待实施
> 日期2026-04-11
## 一、现状痛点
| 痛点 | 现状 | 影响 |
|------|------|------|
| **app.go God Object** | 958 行67 个方法全在一个 struct 上 | 难以维护,新功能必须改核心文件 |
| **App.vue 硬编码映射** | `getComponent()` 是 3 个 key 的字面量对象 | 新 Tab 必须改源码 |
| **文件预览 if/else 链** | `FileEditorPanel.vue` 有 ~12 层 v-if/v-else-if | 新增文件类型需改 5+ 处 |
| **大体积功能嵌入** | draw.io 等 ~12-15MB 功能无法按需加载 | 安装包膨胀 |
## 二、架构总览
```
+================================================================+
| u-desk Application |
+=================================================================
| Core (Go) Plugin Manager |
| - App facade - Registry / Lifecycle / Loader |
| - ConfigStore (SQLite) - TabRegistry |
| - EventBus (Wails Events) - PreviewRegistry |
+----------+----------+---------+-----------+----------+ |
| | | | | |
+-------+ +------+ +-----+ +--------+ +------+ +---+ |
|builtin: |builtin: |builtin | builtin: | future | future |
| file-sys| db-cli | md-edit| drawio | JS/WASM| Go .so |
+---------+---------+--------+-----------+--------+------------+
|
+==============================================================+|
| Frontend (Vue 3) ||
| PluginRegistry (TS) ||
| - TabProviders → 替代 App.vue 硬编码映射 ||
| - FilePreviewHandlers → 替代 FileEditorPanel if/else 链 ||
| - ComponentLoader → defineAsyncComponent 懒加载 ||
+==============================================================+
```
## 三、核心接口定义
### 3.1 后端插件接口Go
```go
// 文件路径: internal/plugin/plugin.go
// PluginID 插件唯一标识
type PluginID string
// PluginMetadata 插件元数据
type PluginMetadata struct {
ID PluginID `json:"id"`
Name string `json:"name"`
Version string `json:"version"`
Description string `json:"description"`
Author string `json:"author"`
TabKey string `json:"tab_key,omitempty"` // 提供的 Tab key
FileExtensions []string `json:"file_extensions,omitempty"` // 处理的文件扩展名
}
// PluginCapability 插件能力标志
type PluginCapability int
const (
CapabilityTabProvider PluginCapability = 1 << iota // 提供 Tab 页面
CapabilityFilePreview // 文件预览
)
// Plugin 核心插件接口
type Plugin interface {
Meta() PluginMetadata
Capabilities() PluginCapability
Init(ctx context.Context, core CoreServices) error
Start() error
Stop() error
}
// TabProvider Tab 提供者接口(可选)
type TabProvider interface {
TabDefinition() TabDef
TabComponentPath() string
}
// FilePreviewHandler 文件预览处理接口(可选)
type FilePreviewHandler interface {
CanPreview(filename string, mimeType string) bool
PreviewInfo(filename string) PreviewInfo
}
// PreviewInfo 预览元信息(发送给前端)
type PreviewInfo struct {
Type string `json:"type"` // "drawio", "image", "pdf"
Title string `json:"title"`
Icon string `json:"icon,omitempty"`
NeedsContainer bool `json:"needs_container,omitempty"`
ContainerConfig map[string]string `json:"container_config,omitempty"`
SupportsEdit bool `json:"supports_edit"`
PreloadHint string `json:"preload_hint,omitempty"`
}
// TabDef Tab 定义
type TabDef struct {
Key string `json:"key"`
Title string `json:"title"`
Icon string `json:"icon,omitempty"`
Enabled bool `json:"enabled"`
Order int `json:"order"`
}
```
### 3.2 前端插件接口TypeScript
```typescript
// 文件路径: frontend/src/plugin/types.ts
/** 插件能力标志 */
export enum PluginCapability {
None = 0,
TabProvider = 1 << 0, // 提供 Tab
FilePreview = 1 << 1, // 文件预览
Settings = 1 << 2, // 设置页
}
/** Tab 插件定义 */
export interface TabPluginDefinition {
key: string
title: string
icon?: string
componentLoader: () => Promise<Component> // 异步组件加载器
defaultVisible?: boolean
order?: number
}
/** 文件预览处理器定义 */
export interface FilePreviewHandlerDefinition {
id: string
name: string
icon: string
priority: number // 越大越优先
canHandle: (filename: string) => boolean
getComponent?: () => Promise<Component>
getRenderConfig?: (filePath: string) => RenderConfig
supportsEdit?: boolean
}
/** 渲染配置 */
export interface RenderConfig {
type: 'iframe' | 'html' | 'custom'
src?: string
htmlContent?: string
props?: Record<string, any>
}
```
## 四、插件管理器设计
### 4.1 后端 PluginManager
```go
// internal/plugin/manager.go
type Manager struct {
mu sync.RWMutex
plugins map[PluginID]Plugin
core CoreServices
tabReg *TabRegistry
previewReg *PreviewRegistry
ctx context.Context
}
func NewManager(core CoreServices) *Manager
func (m *Manager) Register(p Plugin) error // 注册插件
func (m *Manager) InitAll(ctx context.Context) error // 初始化所有插件
func (m *Manager) StartByTabKey(tabKey string) error // 按 Tab 懒启动
func (m *Manager) GetPluginInfos() []PluginMetadata // 获取插件列表
func (m *Manager) ResolvePreview(filename string) (*PreviewInfo, error)
func (m *Manager) Shutdown() error
```
### 4.2 前端注册中心
```typescript
// frontend/src/plugin/registry.ts
import { reactive } from 'vue'
const state = reactive({
tabPlugins: new Map<string, TabPluginDefinition>(),
previewHandlers: [] as FilePreviewHandlerDefinition[],
})
export function registerTabPlugin(def: TabPluginDefinition): void
export function getTabComponent(key: string): (() => Promise<Component>) | null
export function getAllTabDefinitions(): TabPluginDefinition[]
export function registerPreviewHandler(handler: FilePreviewHandlerDefinition): void
export function resolvePreviewHandler(filename: string): FilePreviewHandlerDefinition | null
```
## 五、关键改造点
### 5.1 FileEditorPanel.vue 重构(最大收益)
**改造前**12 层 v-if/v-else-if 链image/video/audio/pdf/html/md/excel/word/csv/text/binary
**改造后**
```vue
<template>
<!-- iframe 类型draw.io -->
<iframe v-if="renderConfig?.type === 'iframe'" :src="renderConfig.src" />
<!-- Vue 组件类型 -->
<component v-else-if="previewComponent" :is="previewComponent" v-bind="previewProps" />
</template>
<script setup>
import { resolvePreviewHandler } from '@/plugin/registry'
const handler = computed(() =>
props.config.currentFileName ? resolvePreviewHandler(props.config.currentFileName) : null
)
const previewComponent = computed(() => handler.value?.getComponent?.())
const renderConfig = computed(() => handler.value?.getRenderConfig?.(filePath))
</script>
```
### 5.2 App.vue 改造
**改造前**
```ts
const getComponent = (key) => ({ 'file-system': FileSystem, 'db-cli': DbCli }[key])
```
**改造后**
```ts
import '@/plugin/built-in' // 副作用:执行内置插件注册
import { getTabComponent } from '@/plugin/registry'
const getComponent = (key) => {
const loader = getTabComponent(key)
return loader ? defineAsyncComponent(loader) : null
}
```
### 5.3 Draw.io 插件示例(首个验证插件)
**后端** (`internal/plugin/builtin/drawio_plugin.go`)
```go
type DrawIoPlugin struct{ server *http.Server }
func (p *DrawIoPlugin) Meta() PluginMetadata {
return PluginMetadata{
ID: "builtin-drawio", Name: "Draw.io 查看器",
FileExtensions: []string{"drawio", "dio"},
}
}
func (p *DrawIoPlugin) Capabilities() PluginCapability { return CapabilityFilePreview }
func (p *DrawIoPlugin) CanPreview(filename, _ string) bool {
ext := filepath.Ext(filename)
return ext == ".drawio" || ext == ".dio"
}
```
**前端** (`frontend/src/plugin/built-in/drawio-handler.ts`)
```ts
registerPreviewHandler({
id: 'drawio-preview', priority: 95,
canHandle: (f) => /\.drawio?$/i.test(f),
getRenderConfig: (path) => ({
type: 'iframe',
src: `http://localhost:18765/drawio/index.html?chrome=0&lightbox=1&stealth=1#R${xmlContent}`
})
})
```
## 六、分阶段实施路径
### Phase 0基础设施搭建
不改现有功能,只建立骨架:
| 文件 | 内容 |
|------|------|
| `internal/plugin/plugin.go` | 核心 Plugin/TabProvider/FilePreviewHandler 接口 |
| `internal/plugin/manager.go` | PluginManager 实现 |
| `internal/plugin/tab_registry.go` | Tab 注册表 |
| `internal/plugin/preview_registry.go` | 预览处理器注册表 |
| `frontend/src/plugin/types.ts` | TS 类型定义 |
| `frontend/src/plugin/registry.ts` | 前端注册中心 |
### Phase 1Draw.io 插件验证
用第一个真实插件验证整条链路:
| 步骤 | 文件 | 操作 |
|------|------|------|
| 1 | `internal/plugin/builtin/drawio_plugin.go` | 新建 DrawIoPlugin |
| 2 | `frontend/src/plugin/built-in/drawio-handler.ts` | 新建前端 handler |
| 3 | `app.go` | 小改:引入 pluginMgr注册 DrawIoPlugin |
| 4 | `FileEditorPanel.vue` | 微改if/else 末尾追加 drawio 分支 |
**验证标准**:打开 `.drawio` 文件 → 显示预览 → 其他文件不受影响
### Phase 2文件预览系统重构
将全部 12 种预览迁移到插件注册表:
1.`preview-handlers.ts` 注册所有内置处理器
2. `FileEditorPanel.vue` template 改为 `<component :is>` / `<iframe>`
3. `filePreviewHandlers.js` 的 Excel/Word/CSV 逻辑拆分到对应组件
### Phase 3Tab 系统插件化
1. `built-in/index.ts` 注册 3 个内置 Tab
2. `App.vue` getComponent 改为查 registry
3. KeepAlive include 动态化
### Phase 4app.go 瘦身(可选延后)
方法签名保留Wails v2 绑定要求),实现委托给 pluginMgr。
## 七、新增/变更文件清单
```
新增:
internal/plugin/
plugin.go # 接口定义
manager.go # PluginManager
tab_registry.go # Tab 注册表
preview_registry.go # 预览注册表
builtin/
drawio_plugin.go # Draw.io 插件Phase 1
frontend/src/plugin/
types.ts # TS 接口
registry.ts # 注册中心
built-in/
drawio-handler.ts # Draw.io 前端 handlerPhase 1
preview-handlers.ts # 全部内置预览注册Phase 2
index.ts # 内置 Tab 注册Phase 3
修改:
app.go # 引入 pluginMgrPhase 1
frontend/src/.../FileEditorPanel.vue # 追加插件入口 / 重写
frontend/src/App.vue # getComponent 改 registry
```
## 八、风险与应对
| 风险 | 应对策略 |
|------|----------|
| Wails v2 无法动态绑定 API 方法 | App 上预留 `PluginCall(id, method, params)` 统一分发 |
| FileEditorPanel 改造影响面大 | Phase 1 只在 if/else 末尾追加Phase 2 用 feature flag 切换新旧路径 |
| 包体积膨胀draw.io ~12MB | 条件编译 `go build tags` 或未来外部下载缓存 |
| 过度抽象增加复杂度 | YAGNI 原则:只在确实需要扩展点时才加接口 |
## 九、Wails v3 兼容性预留
Wails v3 (alpha.74) 主要变化对插件架构的影响:
| 维度 | v2 | v3 | 影响 |
|------|----|----|------|
| 绑定方式 | struct 方法自动生成 | 手动注册 handler | 更有利于插件化 |
| 前端调用 | `window.go.main.App.Xxx()` | `window.go.invoke()` | 需适配层 |
| 事件系统 | `runtime.EventsEmit` | 类似但 API 不同 | 需抽象层 |
建议在 eventbus 中封装一层 Wails 版本抽象,切换 v3 时只需替换底层实现。