578 lines
13 KiB
Markdown
578 lines
13 KiB
Markdown
# Monorepo架构技术设计文档
|
||
|
||
## 架构概览
|
||
|
||
本设计文档详细描述了DeotalandAi项目从分散的独立项目向Monorepo架构迁移的技术实施方案。
|
||
|
||
## 技术选型分析
|
||
|
||
### 包管理器选择: pnpm vs Yarn vs npm
|
||
|
||
#### 选择: pnpm
|
||
**原因:**
|
||
- **磁盘空间优化**: 硬链接和符号链接减少80%磁盘占用
|
||
- **速度快**: 并行安装,比npm快2-3倍
|
||
- **工作空间支持**: 原生Monorepo支持,配置简洁
|
||
- **依赖隔离**: 每个包独立node_modules,避免依赖污染
|
||
- **严格模式**: 防止"吊死依赖"问题
|
||
|
||
#### 对比分析:
|
||
```
|
||
pnpm:
|
||
✅ 安装速度快
|
||
✅ 磁盘占用少
|
||
✅ 工作空间原生支持
|
||
✅ 依赖严格模式
|
||
❌ 相对较新,生态还在完善
|
||
|
||
Yarn Workspaces:
|
||
✅ 成熟稳定
|
||
✅ 社区支持好
|
||
❌ 安装速度中等
|
||
❌ 磁盘占用大
|
||
|
||
npm Workspaces:
|
||
✅ Node.js原生支持
|
||
❌ 功能相对简单
|
||
❌ 性能不如pnpm
|
||
```
|
||
|
||
### 构建工具选择: Turborepo vs Lerna vs Nx
|
||
|
||
#### 选择: Turborepo
|
||
**原因:**
|
||
- **任务管道**: 声明式任务依赖图,易于理解
|
||
- **缓存机制**: 本地和远程缓存,显著提升构建速度
|
||
- **增量构建**: 只重新构建变更的包
|
||
- **简单配置**: JSON配置,学习成本低
|
||
- **Vercel支持**: 来自Vercel,现代化工具链
|
||
|
||
#### 任务管道设计:
|
||
```json
|
||
{
|
||
"pipeline": {
|
||
"build": {
|
||
"dependsOn": ["^build"],
|
||
"outputs": ["dist/**", "build/**"]
|
||
},
|
||
"test": {
|
||
"dependsOn": ["build"],
|
||
"outputs": []
|
||
},
|
||
"lint": {
|
||
"outputs": []
|
||
},
|
||
"dev": {
|
||
"cache": false,
|
||
"persistent": true
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
## 项目结构设计
|
||
|
||
### 目录结构详解
|
||
|
||
```
|
||
DeotalandAi/
|
||
├── package.json # 根级别项目配置
|
||
├── pnpm-workspace.yaml # pnpm工作空间定义
|
||
├── turbo.json # Turborepo任务配置
|
||
├── .npmrc # npm配置(使用pnpm时)
|
||
├── .eslintrc.base.json # 基础ESLint配置
|
||
├── .prettierrc # Prettier配置
|
||
├── .prettierrc # Prettier配置
|
||
├── .gitignore # Git忽略规则
|
||
├── README.md # 项目总体说明
|
||
├── CHANGELOG.md # 版本变更记录
|
||
├── apps/ # 应用程序
|
||
│ ├── frontend/ # 主前端应用
|
||
│ │ ├── package.json
|
||
│ │ ├── vite.config.js
|
||
│ │ ├── index.html
|
||
│ │ └── src/
|
||
│ │ ├── main.ts
|
||
│ │ ├── App.vue
|
||
│ │ ├── components/
|
||
│ │ ├── views/
|
||
│ │ ├── stores/
|
||
│ │ └── router/
|
||
│ └── designer/ # 设计器应用
|
||
│ ├── package.json
|
||
│ │ ├── vite.config.js
|
||
│ │ ├── index.html
|
||
│ │ └── src/
|
||
├── packages/ # 共享包
|
||
│ ├── ui/ # UI组件库
|
||
│ │ ├── package.json
|
||
│ │ ├── vite.config.js
|
||
│ │ ├── src/
|
||
│ │ │ ├── index.js # 统一导出
|
||
│ │ │ ├── components/ # Vue组件
|
||
│ │ │ ├── styles/ # 样式文件
|
||
│ │ │ └── composables/ # 组合式函数
|
||
│ │ └── stories/ # Storybook文档
|
||
│ ├── utils/ # 工具库
|
||
│ │ ├── package.json
|
||
│ │ ├── src/
|
||
│ │ │ ├── index.ts
|
||
│ │ │ ├── api/ # API相关工具
|
||
│ │ │ ├── format/ # 格式化工具
|
||
│ │ │ ├── validate/ # 验证工具
|
||
│ │ │ └── storage/ # 存储工具
|
||
│ │ └── tests/
|
||
│ └── config/ # 配置文件包
|
||
│ ├── package.json
|
||
│ ├── src/
|
||
│ │ ├── index.js
|
||
│ │ ├── vite.js # Vite配置
|
||
│ │ └── eslint.js # ESLint配置
|
||
├── scripts/ # 构建和部署脚本
|
||
│ ├── build.js
|
||
│ ├── dev.js
|
||
│ ├── deploy.js
|
||
│ └── utils.js
|
||
└── docs/ # 项目文档
|
||
├── architecture.md
|
||
├── contributing.md
|
||
└── guides/
|
||
```
|
||
|
||
### 工作空间配置详解
|
||
|
||
#### pnpm-workspace.yaml
|
||
```yaml
|
||
packages:
|
||
- 'apps/*'
|
||
- 'packages/*'
|
||
- '!apps/*/dist/**'
|
||
- '!apps/*/.vite/**'
|
||
```
|
||
|
||
#### package.json (根级别)
|
||
```json
|
||
{
|
||
"name": "deotalandai-monorepo",
|
||
"private": true,
|
||
"version": "1.0.0",
|
||
"description": "DeotalandAI Monorepo with Vue3, Element Plus, and shared components",
|
||
"scripts": {
|
||
"dev": "turbo run dev",
|
||
"build": "turbo run build",
|
||
"lint": "turbo run lint",
|
||
"test": "turbo run test",
|
||
"clean": "turbo run clean && rimraf node_modules",
|
||
"type-check": "turbo run type-check",
|
||
"storybook": "turbo run storybook",
|
||
"deploy": "turbo run deploy"
|
||
},
|
||
"devDependencies": {
|
||
"turbo": "^1.10.0",
|
||
"concurrently": "^8.2.0",
|
||
"rimraf": "^5.0.0",
|
||
"eslint": "^8.45.0",
|
||
"prettier": "^3.0.0",
|
||
"@vitejs/plugin-vue": "^6.0.1",
|
||
"vite": "^7.2.2"
|
||
},
|
||
"engines": {
|
||
"node": ">=18.0.0",
|
||
"pnpm": ">=8.0.0"
|
||
}
|
||
}
|
||
```
|
||
|
||
## 共享组件设计
|
||
|
||
### UI组件库架构
|
||
|
||
#### 组件分类体系
|
||
```
|
||
packages/ui/src/components/
|
||
├── base/ # 基础组件
|
||
│ ├── Button/
|
||
│ ├── Input/
|
||
│ ├── Card/
|
||
│ ├── Modal/
|
||
│ └── Loading/
|
||
├── layout/ # 布局组件
|
||
│ ├── Header/
|
||
│ ├── Sidebar/
|
||
│ ├── Footer/
|
||
│ └── Container/
|
||
├── business/ # 业务组件
|
||
│ ├── AgentCard/
|
||
│ ├── OrderCard/
|
||
│ ├── Gallery/
|
||
│ └── PurchaseModal/
|
||
└── form/ # 表单组件
|
||
├── Form/
|
||
├── FormItem/
|
||
├── Upload/
|
||
└── Select/
|
||
```
|
||
|
||
#### 主题系统设计
|
||
```typescript
|
||
// packages/ui/src/styles/theme.ts
|
||
export const theme = {
|
||
colors: {
|
||
primary: '#6B46C1',
|
||
secondary: '#8B5CF6',
|
||
success: '#10B981',
|
||
warning: '#F59E0B',
|
||
error: '#EF4444',
|
||
info: '#3B82F6'
|
||
},
|
||
spacing: {
|
||
xs: '4px',
|
||
sm: '8px',
|
||
md: '16px',
|
||
lg: '24px',
|
||
xl: '32px'
|
||
},
|
||
borderRadius: {
|
||
sm: '4px',
|
||
md: '8px',
|
||
lg: '12px',
|
||
full: '9999px'
|
||
},
|
||
typography: {
|
||
fontFamily: {
|
||
sans: ['Inter', 'system-ui', 'sans-serif'],
|
||
mono: ['JetBrains Mono', 'monospace']
|
||
},
|
||
fontSize: {
|
||
xs: '12px',
|
||
sm: '14px',
|
||
md: '16px',
|
||
lg: '18px',
|
||
xl: '20px',
|
||
'2xl': '24px'
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 组件使用示例
|
||
```vue
|
||
<!-- apps/frontend/src/components/AgentCard.vue -->
|
||
<template>
|
||
<UiCard class="agent-card">
|
||
<template #header>
|
||
<UiCardHeader>
|
||
<UiAgentCard :agent="agent" />
|
||
</UiCardHeader>
|
||
</template>
|
||
|
||
<UiCardContent>
|
||
<p>{{ agent.description }}</p>
|
||
</UiCardContent>
|
||
|
||
<template #footer>
|
||
<UiButton @click="onSelect">
|
||
{{ $t('common.select') }}
|
||
</UiButton>
|
||
</template>
|
||
</UiCard>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { UiCard, UiButton, UiAgentCard } from '@deotalandai/ui'
|
||
import type { Agent } from '@deotalandai/types'
|
||
|
||
interface Props {
|
||
agent: Agent
|
||
}
|
||
|
||
const props = defineProps<Props>()
|
||
const emit = defineEmits<{
|
||
select: [agent: Agent]
|
||
}>()
|
||
|
||
const onSelect = () => {
|
||
emit('select', props.agent)
|
||
}
|
||
</script>
|
||
```
|
||
|
||
### 工具库设计
|
||
|
||
#### API工具模块
|
||
```javascript
|
||
// packages/utils/src/api/request.js
|
||
import axios from 'axios'
|
||
|
||
export class ApiClient {
|
||
constructor(config) {
|
||
this.instance = axios.create(config)
|
||
this.setupInterceptors()
|
||
}
|
||
|
||
setupInterceptors() {
|
||
this.instance.interceptors.request.use(
|
||
(config) => {
|
||
// 添加认证token
|
||
const token = localStorage.getItem('auth_token')
|
||
if (token) {
|
||
config.headers.Authorization = `Bearer ${token}`
|
||
}
|
||
return config
|
||
},
|
||
(error) => Promise.reject(error)
|
||
)
|
||
|
||
this.instance.interceptors.response.use(
|
||
(response) => response.data,
|
||
(error) => {
|
||
// 统一错误处理
|
||
return Promise.reject(this.handleError(error))
|
||
}
|
||
)
|
||
}
|
||
|
||
handleError(error) {
|
||
if (error.response) {
|
||
const { status, data } = error.response
|
||
return {
|
||
status,
|
||
message: data.message || '请求失败',
|
||
code: data.code
|
||
}
|
||
}
|
||
return {
|
||
status: 0,
|
||
message: error.message || '网络错误',
|
||
code: -1
|
||
}
|
||
}
|
||
|
||
async get(url, params) {
|
||
return this.instance.get(url, { params })
|
||
}
|
||
|
||
async post(url, data) {
|
||
return this.instance.post(url, data)
|
||
}
|
||
|
||
async put(url, data) {
|
||
return this.instance.put(url, data)
|
||
}
|
||
|
||
async delete(url) {
|
||
return this.instance.delete(url)
|
||
}
|
||
}
|
||
|
||
export const createApiClient = (baseURL) => {
|
||
return new ApiClient({ baseURL })
|
||
}
|
||
```
|
||
|
||
#### 国际化工具
|
||
```typescript
|
||
// packages/utils/src/i18n/index.ts
|
||
import { createI18n } from 'vue-i18n'
|
||
import type { App } from 'vue'
|
||
|
||
export const setupI18n = (app: App, locale: string, messages: Record<string, any>) => {
|
||
const i18n = createI18n({
|
||
legacy: false,
|
||
locale,
|
||
fallbackLocale: 'en',
|
||
messages
|
||
})
|
||
|
||
app.use(i18n)
|
||
return i18n
|
||
}
|
||
|
||
export const formatDate = (date: Date, locale: string = 'zh-CN') => {
|
||
return new Intl.DateTimeFormat(locale, {
|
||
year: 'numeric',
|
||
month: 'long',
|
||
day: 'numeric'
|
||
}).format(date)
|
||
}
|
||
|
||
export const formatCurrency = (amount: number, currency: string = 'CNY', locale: string = 'zh-CN') => {
|
||
return new Intl.NumberFormat(locale, {
|
||
style: 'currency',
|
||
currency
|
||
}).format(amount)
|
||
}
|
||
```
|
||
|
||
### 类型定义设计
|
||
|
||
#### API类型定义
|
||
```typescript
|
||
// packages/types/src/api.ts
|
||
export interface ApiResponse<T = any> {
|
||
code: number
|
||
message: string
|
||
data: T
|
||
success: boolean
|
||
}
|
||
|
||
export interface PaginatedResponse<T> extends ApiResponse<T[]> {
|
||
pagination: {
|
||
page: number
|
||
pageSize: number
|
||
total: number
|
||
totalPages: number
|
||
}
|
||
}
|
||
|
||
export interface Agent {
|
||
id: string
|
||
name: string
|
||
description: string
|
||
avatar: string
|
||
category: string
|
||
tags: string[]
|
||
created_at: string
|
||
updatedAt: string
|
||
}
|
||
|
||
export interface Order {
|
||
id: string
|
||
agentId: string
|
||
userId: string
|
||
status: 'pending' | 'processing' | 'completed' | 'failed'
|
||
amount: number
|
||
currency: string
|
||
created_at: string
|
||
updatedAt: string
|
||
}
|
||
```
|
||
|
||
#### 组件Props类型
|
||
```typescript
|
||
// packages/types/src/components.ts
|
||
import type { Agent, Order } from './api'
|
||
|
||
export interface ButtonProps {
|
||
variant?: 'primary' | 'secondary' | 'outline' | 'ghost'
|
||
size?: 'sm' | 'md' | 'lg'
|
||
disabled?: boolean
|
||
loading?: boolean
|
||
block?: boolean
|
||
}
|
||
|
||
export interface CardProps {
|
||
shadow?: 'none' | 'sm' | 'md' | 'lg'
|
||
border?: boolean
|
||
padding?: 'none' | 'sm' | 'md' | 'lg'
|
||
}
|
||
|
||
export interface AgentCardProps {
|
||
agent: Agent
|
||
selectable?: boolean
|
||
showActions?: boolean
|
||
}
|
||
|
||
export interface ModalProps {
|
||
visible: boolean
|
||
title?: string
|
||
width?: string | number
|
||
confirmText?: string
|
||
cancelText?: string
|
||
maskClosable?: boolean
|
||
}
|
||
```
|
||
|
||
## 开发工具配置
|
||
|
||
### VS Code工作区配置
|
||
```json
|
||
// .vscode/settings.json
|
||
{
|
||
"editor.formatOnSave": true,
|
||
"editor.codeActionsOnSave": {
|
||
"source.fixAll.eslint": true
|
||
},
|
||
"typescript.preferences.importModuleSpecifier": "relative",
|
||
"emmet.includeLanguages": {
|
||
"vue": "html"
|
||
},
|
||
"files.associations": {
|
||
"*.vue": "vue"
|
||
},
|
||
"search.exclude": {
|
||
"**/node_modules": true,
|
||
"**/dist": true,
|
||
"**/build": true,
|
||
"**/.turbo": true
|
||
}
|
||
}
|
||
```
|
||
|
||
### 调试配置
|
||
```json
|
||
// .vscode/launch.json
|
||
{
|
||
"version": "0.2.0",
|
||
"configurations": [
|
||
{
|
||
"name": "Frontend Dev",
|
||
"type": "node",
|
||
"request": "launch",
|
||
"program": "${workspaceFolder}/node_modules/.bin/vite",
|
||
"args": ["--config", "apps/frontend/vite.config.ts"],
|
||
"cwd": "${workspaceFolder}/apps/frontend",
|
||
"console": "integratedTerminal"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
## 性能优化策略
|
||
|
||
### 构建优化
|
||
1. **依赖预构建**: 使用vite的依赖预构建
|
||
2. **代码分割**: 每个应用独立打包
|
||
3. **Tree Shaking**: 消除未使用的代码
|
||
4. **缓存策略**: 利用Turborepo缓存
|
||
|
||
### 开发优化
|
||
1. **热模块替换**: 跨包的热更新
|
||
2. **增量构建**: 只构建变更的包
|
||
3. **并行开发**: 同时启动多个应用
|
||
4. **类型检查**: 增量TypeScript检查
|
||
|
||
### 部署优化
|
||
1. **CDN分发**: 静态资源CDN部署
|
||
2. **压缩优化**: Gzip/Brotli压缩
|
||
3. **缓存头**: 合理的缓存策略
|
||
4. **域名分片**: 多个域名提升加载速度
|
||
|
||
## 迁移风险与缓解
|
||
|
||
### 技术风险
|
||
1. **构建复杂度**: 复杂的工作空间依赖图
|
||
- **缓解**: 使用Turborepo简化配置
|
||
|
||
2. **依赖冲突**: 不同包的依赖版本冲突
|
||
- **缓解**: 使用pnpm的严格模式
|
||
|
||
3. **构建时间**: 大型Monorepo构建时间长
|
||
- **缓解**: 利用Turborepo缓存和增量构建
|
||
|
||
### 团队风险
|
||
1. **学习成本**: 团队需要学习新的工作方式
|
||
- **缓解**: 提供详细的文档和培训
|
||
|
||
2. **工具兼容性**: 现有工具可能不支持Monorepo
|
||
- **缓解**: 选择兼容性好的工具
|
||
|
||
3. **CI/CD复杂性**: 流水线配置更复杂
|
||
- **缓解**: 提供完整的CI/CD模板
|
||
|
||
---
|
||
|
||
**设计版本**: 1.0
|
||
**创建时间**: 2025-11-18
|
||
**技术负责人**:
|
||
**审核状态**: 待审核 |