
作者介绍:刘星辰,TRAE 技术专家

项目背景
Cloudreve 简介
项目地址: github.com/cloudreve/c...
Cloudreve 是一个功能完善的自托管文件管理与分享系统 ,支持将文件存储到多种云服务(如本地存储、S3、OneDrive、阿里云 OSS、腾讯 COS 等)。该项目提供了完整的网页版网盘体验,包括文件上传下载、在线预览、分享链接、多用户与分组管理,以及 WebDAV 接入等功能。
项目亮点包括:
-
支持多种云存储服务的统一管理
-
前后端一体化的自部署方案(Go + React 技术栈)
-
文件加密、压缩、断点续传、在线预览等功能齐全
-
适用于个人网盘、团队文件协作、轻量级私有云等场景
一句话总结: 🌩️ Cloudreve 是一个开源的、可自己搭建的多云文件管理平台,让你拥有属于自己的「网盘系统」。


项目理解
面对一个陌生的开源项目,如何快速上手?可以直接把问题交给 SOLO Coder,让它把复杂的项目拆解为可理解的模块。

根据任务拆解,Coder 写了 6 篇技术报告:

首先需要理解项目的宏观结构。Coder 绘制了整体架构分析文档,涵盖技术栈、部署方式和数据流程。

以下是文档完整内容:
01-project-overview.md - 项目整体架构概览
-
技术栈分析(React + Go + 数据库)
-
部署架构图和数据流程
-
核心功能模块介绍
markdown
# Cloudreve 项目整体架构分析
## 项目概述
Cloudreve 是一个功能完整的自托管网盘系统,支持多种存储后端,提供完整的文件管理、分享、用户管理等功能。项目采用现代化的前后端分离架构,具有良好的可扩展性和维护性。
## 技术栈总览
### 前端技术栈
- **框架**: React 18 + TypeScript
- **UI 库**: Material-UI (MUI) v6
- **状态管理**: Redux Toolkit
- **路由**: React Router v6
- **构建工具**: Vite
- **HTTP 客户端**: Axios
- **拖拽**: React DnD
- **国际化**: i18next
### 后端技术栈
- **语言**: Go 1.23
- **Web 框架**: Gin
- **ORM**: Ent (Facebook 开源的 Go ORM)
- **数据库**: 支持 MySQL、PostgreSQL、SQLite
- **认证**: JWT + Session
- **文件存储**: 支持本地、阿里云 OSS、AWS S3、腾讯云 COS 等
- **任务队列**: 内置任务调度系统
## 项目结构
```mermaid
graph TB
subgraph "Cloudreve 项目结构"
A[根目录] --> B[Cloudreve/]
A --> C[assets/]
A --> D[.trae/]
B --> B1[main.go - 入口文件]
B --> B2[routers/ - 路由层]
B --> B3[service/ - 业务逻辑层]
B --> B4[ent/ - 数据模型层]
B --> B5[pkg/ - 工具包]
B --> B6[middleware/ - 中间件]
B --> B7[inventory/ - 库存管理]
B --> B8[application/ - 应用配置]
C --> C1[src/ - 前端源码]
C --> C2[assets/ - 静态资源]
C --> C3[locales/ - 国际化文件]
C1 --> C11[component/ - React 组件]
C1 --> C12[redux/ - 状态管理]
C1 --> C13[api/ - API 接口]
C1 --> C14[router/ - 前端路由]
C1 --> C15[session/ - 会话管理]
end
```
## 核心架构设计
### 1. 分层架构
```mermaid
graph TB
subgraph "后端分层架构"
A[路由层 - routers/] --> B[控制器层 - controllers/]
B --> C[服务层 - service/]
C --> D[数据访问层 - ent/]
D --> E[数据库]
F[中间件层 - middleware/] --> B
G[工具包 - pkg/] --> C
end
```
### 2. 前端组件架构
```mermaid
graph TB
subgraph "前端组件架构"
A[App.tsx - 根组件] --> B[Frame/ - 框架组件]
A --> C[Pages/ - 页面组件]
B --> B1[NavBarFrame - 导航框架]
B --> B2[HeadlessFrame - 无头框架]
B1 --> B11[TopAppBar - 顶部栏]
B1 --> B12[AppDrawer - 侧边栏]
B1 --> B13[AppMain - 主内容区]
C --> C1[FileManager/ - 文件管理器]
C --> C2[Login/ - 登录页面]
C --> C3[Admin/ - 管理页面]
C1 --> C11[Explorer/ - 文件浏览器]
C1 --> C12[Uploader/ - 文件上传器]
C1 --> C13[Viewers/ - 文件查看器]
end
```
## 部署架构
Cloudreve 支持两种部署模式:
### 1. 单机模式 (Master Mode)
```mermaid
graph LR
A[用户] --> B[Nginx/反向代理]
B --> C[Cloudreve 主节点]
C --> D[数据库]
C --> E[文件存储]
```
### 2. 集群模式 (Master-Slave Mode)
```mermaid
graph TB
A[用户] --> B[负载均衡器]
B --> C[Cloudreve 主节点]
B --> D[Cloudreve 从节点 1]
B --> E[Cloudreve 从节点 2]
C --> F[共享数据库]
D --> G[本地存储 1]
E --> H[本地存储 2]
C -.-> D
C -.-> E
```
## 核心功能模块
### 1. 用户管理系统
- 用户注册、登录、权限管理
- 用户组管理
- 存储配额管理
- 2FA 双因子认证
- Passkey 支持
### 2. 文件管理系统
- 文件上传、下载、预览
- 文件夹管理
- 文件分享(公开/私密)
- 文件版本控制
- 批量操作
### 3. 存储系统
- 多存储后端支持
- 存储策略管理
- 文件加密
- 缩略图生成
- 媒体元数据提取
### 4. 任务系统
- 异步任务处理
- 离线下载
- 文件压缩/解压
- 定时任务
### 5. 聊天系统 (已实现)
- OpenRouter API 集成
- 流式对话支持
- 多模态文件处理
- 文件上下文分析
## 数据流架构
```mermaid
sequenceDiagram
participant U as 用户
participant F as 前端 (React)
participant R as 路由层 (Gin)
participant S as 服务层
participant D as 数据层 (Ent)
participant DB as 数据库
U->>F: 用户操作
F->>R: HTTP 请求
R->>R: 中间件处理 (认证/授权)
R->>S: 调用业务服务
S->>D: 数据操作
D->>DB: SQL 查询
DB-->>D: 返回数据
D-->>S: 返回实体
S-->>R: 返回结果
R-->>F: JSON 响应
F-->>U: 更新界面
```
## 关键设计特点
### 1. 模块化设计
- 前后端完全分离
- 组件化开发
- 插件式存储后端
- 中间件架构
### 2. 可扩展性
- 支持集群部署
- 水平扩展能力
- 插件系统
- API 版本管理
### 3. 安全性
- JWT 认证
- RBAC 权限控制
- 文件加密
- CSRF 保护
- 输入验证
### 4. 性能优化
- 缓存机制
- 异步任务处理
- 文件分片上传
- CDN 支持
- 数据库连接池
## 开发环境要求
### 前端开发
- Node.js 18+
- npm 或 yarn
- 现代浏览器
### 后端开发
- Go 1.23+
- 数据库 (MySQL/PostgreSQL/SQLite)
- Redis (可选,用于缓存)
## 项目特色功能
1. **多存储后端**: 支持本地、云存储等多种存储方式
2. **WebDAV 支持**: 可通过 WebDAV 协议访问文件
3. **Office 在线编辑**: 集成 WOPI 协议支持 Office 文档在线编辑
4. **媒体处理**: 支持图片、视频、音频的预览和元数据提取
5. **离线下载**: 支持 Aria2 离线下载
6. **文件分享**: 灵活的文件分享机制
7. **AI 聊天**: 集成 OpenRouter API 的智能对话功能
## 总结
Cloudreve 是一个架构清晰、功能完整的现代化网盘系统。其前后端分离的设计、模块化的架构、以及丰富的功能特性,为开发者提供了良好的扩展基础。特别是已经实现的聊天功能,为你要开发的侧边栏对话功能提供了很好的参考和基础。
下一步建议深入了解前端组件架构和后端服务层的具体实现,这将有助于你更好地理解如何在现有架构基础上添加新功能。
02-frontend-architecture.md - 前端架构详解
-
React + TypeScript + Material-UI 技术栈
-
组件层次结构和状态管理
-
路由系统和性能优化策略
ini
# Cloudreve 前端架构学习指南
## 前端技术栈详解
### 核心技术栈
- **React 18**: 使用最新的 React 特性,包括 Hooks、Suspense、Concurrent Features
- **TypeScript**: 提供类型安全和更好的开发体验
- **Material-UI (MUI) v6**: Google Material Design 设计语言的 React 实现
- **Redux Toolkit**: 现代化的 Redux 状态管理解决方案
- **React Router v6**: 声明式路由管理
- **Vite**: 快速的构建工具和开发服务器
### 开发工具链
- **ESLint**: 代码质量检查
- **Prettier**: 代码格式化
- **Husky**: Git hooks 管理
- **TypeScript**: 静态类型检查
## 项目结构详解
```
assets/src/
├── component/ # React 组件
│ ├── Common/ # 通用组件
│ ├── Frame/ # 框架组件
│ ├── FileManager/ # 文件管理器组件
│ ├── Pages/ # 页面组件
│ ├── Admin/ # 管理后台组件
│ ├── Viewers/ # 文件查看器组件
│ ├── Uploader/ # 文件上传组件
│ ├── Icons/ # 图标组件
│ └── Dialogs/ # 对话框组件
├── redux/ # 状态管理
│ ├── hooks.ts # Redux hooks
│ ├── store.ts # Store 配置
│ └── slices/ # Redux slices
├── api/ # API 接口
│ ├── api.ts # API 函数
│ ├── request.ts # 请求封装
│ └── types.ts # 类型定义
├── router/ # 路由配置
├── session/ # 会话管理
├── util/ # 工具函数
└── App.tsx # 根组件
```
## 组件架构设计
### 1. 框架组件层次结构
```mermaid
graph TB
subgraph "框架组件架构"
A[App.tsx] --> B[NavBarFrame]
A --> C[HeadlessFrame]
B --> D[TopAppBar]
B --> E[AppDrawer]
B --> F[AppMain]
D --> D1[UserAction]
D --> D2[SearchBar]
D --> D3[NavBarMainActions]
E --> E1[SideNavItem]
E --> E2[StorageSummary]
E --> E3[PageNavigation]
F --> F1[FileManager]
F --> F2[Pages]
F --> F3[Admin]
end
```
### 2. 文件管理器组件结构
```mermaid
graph TB
subgraph "文件管理器组件"
A[FileManager] --> B[TopBar]
A --> C[Explorer]
A --> D[Uploader]
A --> E[Viewers]
A --> F[Dialogs]
B --> B1[Breadcrumb]
B --> B2[TopActions]
B --> B3[ViewOptions]
C --> C1[FileList]
C --> C2[ContextMenu]
C --> C3[DragDrop]
D --> D1[UploadQueue]
D --> D2[ProgressBar]
E --> E1[ImageViewer]
E --> E2[VideoPlayer]
E --> E3[DocumentViewer]
F --> F1[CreateNew]
F --> F2[ShareDialog]
F --> F3[DeleteConfirm]
end
```
## 状态管理架构
### Redux Store 结构
```typescript
// store.ts 核心配置
export const store = configureStore({
reducer: {
// 全局状态
globalState: globalStateSlice.reducer,
// 文件管理器状态
explorer: explorerSlice.reducer,
// 用户状态
user: userSlice.reducer,
// 上传状态
uploader: uploaderSlice.reducer,
// 对话框状态
dialog: dialogSlice.reducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
},
}),
});
```
### 状态管理模式
```mermaid
graph LR
subgraph "Redux 数据流"
A[Component] --> B[Action]
B --> C[Reducer]
C --> D[Store]
D --> A
E[Middleware] --> C
F[DevTools] --> D
end
```
### 主要 Slice 说明
#### 1. globalStateSlice
```typescript
interface GlobalState {
// 侧边栏状态
drawerOpen: boolean;
mobileDrawerOpen: boolean;
// 主题设置
theme: 'light' | 'dark' | 'auto';
// 语言设置
language: string;
// 加载状态
loading: boolean;
}
```
#### 2. explorerSlice
```typescript
interface ExplorerState {
// 当前路径
currentPath: string;
// 文件列表
files: FileResponse[];
// 选中的文件
selectedFiles: string[];
// 视图模式
viewMode: 'list' | 'grid' | 'small';
// 排序方式
sortBy: string;
sortOrder: 'asc' | 'desc';
}
```
## 路由系统
### 路由配置结构
```typescript
// router/index.tsx
export const router = createBrowserRouter([
{
path: "/",
element: <App />,
errorElement: <ErrorBoundary />,
children: [
{ path: "/", element: <HomeRedirect /> },
{
path: "/",
element: <HeadlessFrame />,
children: [
// 登录相关路由
{
path: "/session",
element: <SessionIntro />,
children: [
{ path: "/session", element: <SignIn /> },
{ path: "signup", element: <SignUp /> },
{ path: "activate", element: <Activate /> },
{ path: "reset", element: <Reset /> },
],
},
],
},
],
},
]);
```
### 路由守卫机制
```mermaid
sequenceDiagram
participant U as 用户
participant R as Router
participant A as Auth Guard
participant S as Session
participant C as Component
U->>R: 访问路由
R->>A: 检查权限
A->>S: 验证会话
alt 已登录
S-->>A: 返回用户信息
A-->>R: 允许访问
R->>C: 渲染组件
C-->>U: 显示页面
else 未登录
S-->>A: 返回未认证
A-->>R: 重定向登录
R->>U: 跳转登录页
end
```
## 组件设计模式
### 1. 容器组件 vs 展示组件
```typescript
// 容器组件 - 负责数据和逻辑
const FileManagerContainer: React.FC = () => {
const dispatch = useAppDispatch();
const files = useAppSelector(state => state.explorer.files);
const handleFileSelect = (fileId: string) => {
dispatch(selectFile(fileId));
};
return (
<FileList
files={files}
onFileSelect={handleFileSelect}
/>
);
};
// 展示组件 - 负责 UI 渲染
interface FileListProps {
files: FileResponse[];
onFileSelect: (fileId: string) => void;
}
const FileList: React.FC<FileListProps> = ({ files, onFileSelect }) => {
return (
<List>
{files.map(file => (
<FileItem
key={file.id}
file={file}
onClick={() => onFileSelect(file.id)}
/>
))}
</List>
);
};
```
### 2. 自定义 Hooks
```typescript
// hooks/useFileManager.ts
export const useFileManager = () => {
const dispatch = useAppDispatch();
const { files, currentPath, loading } = useAppSelector(state => state.explorer);
const loadFiles = useCallback(async (path: string) => {
dispatch(setLoading(true));
try {
const response = await api.listFiles(path);
dispatch(setFiles(response.data));
} catch (error) {
// 错误处理
} finally {
dispatch(setLoading(false));
}
}, [dispatch]);
return {
files,
currentPath,
loading,
loadFiles,
};
};
```
### 3. 高阶组件 (HOC)
```typescript
// hoc/withAuth.tsx
export const withAuth = <P extends object>(
Component: React.ComponentType<P>
) => {
return (props: P) => {
const { user } = useAppSelector(state => state.user);
if (!user) {
return <Navigate to="/session" />;
}
return <Component {...props} />;
};
};
// 使用方式
const ProtectedPage = withAuth(FileManager);
```
## Material-UI 主题系统
### 主题配置
```typescript
// App.tsx 中的主题配置
export const applyThemeWithOverrides = (themeConfig: ThemeOptions): ThemeOptions => {
return {
...themeConfig,
shape: {
borderRadius: 12,
},
components: {
MuiButton: {
styleOverrides: {
root: {
textTransform: "none",
},
},
},
MuiTooltip: {
defaultProps: {
enterDelay: 500,
},
},
},
};
};
```
### 响应式设计
```typescript
// 使用 MUI 的断点系统
const useStyles = () => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
const isTablet = useMediaQuery(theme.breakpoints.down('md'));
return {
container: {
padding: isMobile ? theme.spacing(1) : theme.spacing(3),
gridTemplateColumns: isMobile ? '1fr' : 'repeat(auto-fill, minmax(200px, 1fr))',
},
};
};
```
## API 集成模式
### 请求封装
```typescript
// api/request.ts
const instance = axios.create({
baseURL: '/api/v4',
});
// 请求拦截器
instance.interceptors.request.use(async (config) => {
const token = await SessionManager.getAccessToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// 响应拦截器
instance.interceptors.response.use(
(response) => response,
async (error) => {
if (error.response?.status === 401) {
// 处理认证失败
await SessionManager.refreshToken();
}
return Promise.reject(error);
}
);
```
### API 函数设计
```typescript
// api/explorer.ts
export const explorerAPI = {
// 获取文件列表
listFiles: (path: string): Promise<Response<FileResponse[]>> => {
return request.get('/file/list', { params: { path } });
},
// 上传文件
uploadFile: (
file: File,
path: string,
onProgress?: (progress: number) => void
): Promise<Response<FileResponse>> => {
const formData = new FormData();
formData.append('file', file);
formData.append('path', path);
return request.post('/file/upload', formData, {
onUploadProgress: (progressEvent) => {
const progress = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
onProgress?.(progress);
},
});
},
};
```
## 国际化 (i18n) 系统
### 配置结构
```typescript
// i18n.ts
import i18n from 'i18next';
import Backend from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
i18n
.use(Backend)
.use(LanguageDetector)
.init({
fallbackLng: 'en-US',
debug: false,
interpolation: {
escapeValue: false,
},
backend: {
loadPath: '/assets/locales/{{lng}}/{{ns}}.json',
},
});
```
### 使用方式
```typescript
// 在组件中使用
const FileManager: React.FC = () => {
const { t } = useTranslation();
return (
<Box>
<Typography variant="h6">
{t('fileManager.title')}
</Typography>
<Button>
{t('common.upload')}
</Button>
</Box>
);
};
```
## 性能优化策略
### 1. 代码分割
```typescript
// 路由级别的代码分割
const FileManager = lazy(() => import('../component/FileManager/FileManager'));
const AdminPanel = lazy(() => import('../component/Admin/AdminPanel'));
// 在路由中使用
<Route
path="/files"
element={
<Suspense fallback={<Loading />}>
<FileManager />
</Suspense>
}
/>
```
### 2. 组件优化
```typescript
// 使用 React.memo 优化渲染
const FileItem = React.memo<FileItemProps>(({ file, onSelect }) => {
return (
<ListItem onClick={() => onSelect(file.id)}>
<ListItemText primary={file.name} />
</ListItem>
);
});
// 使用 useMemo 优化计算
const FileList: React.FC<FileListProps> = ({ files, filter }) => {
const filteredFiles = useMemo(() => {
return files.filter(file =>
file.name.toLowerCase().includes(filter.toLowerCase())
);
}, [files, filter]);
return (
<List>
{filteredFiles.map(file => (
<FileItem key={file.id} file={file} />
))}
</List>
);
};
```
### 3. 虚拟滚动
```typescript
// 使用 react-virtuoso 处理大列表
import { Virtuoso } from 'react-virtuoso';
const VirtualFileList: React.FC<{ files: FileResponse[] }> = ({ files }) => {
return (
<Virtuoso
data={files}
itemContent={(index, file) => (
<FileItem key={file.id} file={file} />
)}
style={{ height: '400px' }}
/>
);
};
```
## 错误处理机制
### 错误边界
```typescript
// component/Common/ErrorBoundary.tsx
class ErrorBoundary extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error: Error): State {
return { hasError: true };
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.error('Error caught by boundary:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return <ErrorFallback />;
}
return this.props.children;
}
}
```
### 全局错误处理
```typescript
// 在 request.ts 中统一处理错误
instance.interceptors.response.use(
(response) => response,
(error) => {
const message = error.response?.data?.message || error.message;
// 显示错误提示
enqueueSnackbar(message, {
variant: 'error',
action: <DefaultCloseAction />
});
return Promise.reject(error);
}
);
```
## 测试策略
### 单元测试
```typescript
// __tests__/FileItem.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { FileItem } from '../FileItem';
describe('FileItem', () => {
const mockFile = {
id: '1',
name: 'test.txt',
size: 1024,
};
it('renders file name correctly', () => {
render(<FileItem file={mockFile} onSelect={jest.fn()} />);
expect(screen.getByText('test.txt')).toBeInTheDocument();
});
it('calls onSelect when clicked', () => {
const onSelect = jest.fn();
render(<FileItem file={mockFile} onSelect={onSelect} />);
fireEvent.click(screen.getByText('test.txt'));
expect(onSelect).toHaveBeenCalledWith('1');
});
});
```
## 开发最佳实践
### 1. 组件设计原则
- **单一职责**: 每个组件只负责一个功能
- **可复用性**: 设计通用的组件接口
- **可测试性**: 便于编写单元测试
- **性能优化**: 避免不必要的重渲染
### 2. 状态管理原则
- **最小化状态**: 只存储必要的状态
- **不可变更新**: 使用 Redux Toolkit 的 Immer
- **异步处理**: 使用 createAsyncThunk 处理异步操作
### 3. 代码组织原则
- **按功能分组**: 相关组件放在同一目录
- **类型定义**: 为所有 props 和状态定义类型
- **文档注释**: 为复杂组件添加 JSDoc 注释
## 总结
Cloudreve 的前端架构采用了现代化的 React 生态系统,具有以下特点:
1. **类型安全**: 全面使用 TypeScript
2. **组件化**: 高度模块化的组件设计
3. **状态管理**: 使用 Redux Toolkit 进行状态管理
4. **UI 一致性**: 基于 Material-UI 的设计系统
5. **性能优化**: 代码分割、虚拟滚动等优化策略
6. **国际化**: 完整的多语言支持
7. **错误处理**: 完善的错误边界和错误处理机制
这个架构为你开发侧边栏对话功能提供了良好的基础,你可以参考现有的组件设计模式和状态管理方式来实现新功能。
03-backend-architecture.md - 后端架构指南
-
Go 语言基础知识(专为前端开发者)
-
Gin 框架和 Ent ORM 使用
-
服务层设计和认证系统
scss
# Cloudreve 后端架构学习指南
## Go 语言基础知识
### 为什么选择 Go
- **高性能**: 编译型语言,接近 C/C++ 的性能
- **并发支持**: 原生的 goroutine 和 channel 支持
- **简洁语法**: 学习曲线平缓,代码可读性强
- **丰富生态**: 优秀的 Web 框架和工具链
- **内存安全**: 垃圾回收机制,避免内存泄漏
### Go 语言核心概念
#### 1. 包 (Package) 系统
```go
// 包声明
package main
// 导入其他包
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
)
// 函数定义
func main() {
fmt.Println("Hello, World!")
}
```
#### 2. 结构体 (Struct)
```go
// 定义结构体
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Password string `json:"-"` // 不序列化到 JSON
}
// 结构体方法
func (u *User) GetDisplayName() string {
return u.Name
}
```
#### 3. 接口 (Interface)
```go
// 定义接口
type UserService interface {
CreateUser(user *User) error
GetUser(id int) (*User, error)
UpdateUser(user *User) error
DeleteUser(id int) error
}
// 实现接口
type userServiceImpl struct {
db *sql.DB
}
func (s *userServiceImpl) CreateUser(user *User) error {
// 实现逻辑
return nil
}
```
#### 4. 错误处理
```go
func GetUser(id int) (*User, error) {
if id <= 0 {
return nil, fmt.Errorf("invalid user id: %d", id)
}
user, err := db.FindUser(id)
if err != nil {
return nil, fmt.Errorf("failed to find user: %w", err)
}
return user, nil
}
// 使用
user, err := GetUser(123)
if err != nil {
log.Printf("Error: %v", err)
return
}
```
## Cloudreve 后端技术栈
### 核心框架和库
- **Gin**: 高性能的 HTTP Web 框架
- **Ent**: Facebook 开源的 Go ORM 框架
- **Cobra**: 命令行应用框架
- **JWT**: JSON Web Token 认证
- **Viper**: 配置管理
- **Logrus**: 结构化日志
### 数据库支持
- **MySQL**: 主要生产数据库
- **PostgreSQL**: 企业级数据库
- **SQLite**: 轻量级嵌入式数据库
## 项目架构设计
### 分层架构
```mermaid
graph TB
subgraph "Cloudreve 后端分层架构"
A[HTTP 层] --> B[路由层 - routers/]
B --> C[控制器层 - controllers/]
C --> D[服务层 - service/]
D --> E[数据访问层 - ent/]
E --> F[数据库]
G[中间件层 - middleware/] --> C
H[工具包 - pkg/] --> D
I[应用配置 - application/] --> D
end
```
### 目录结构详解
```
Cloudreve/
├── main.go # 应用入口
├── cmd/ # 命令行工具
│ ├── root.go # 根命令
│ ├── server.go # 服务器命令
│ └── migrate.go # 数据库迁移
├── routers/ # 路由层
│ ├── router.go # 路由配置
│ └── controllers/ # 控制器
├── service/ # 业务逻辑层
│ ├── admin/ # 管理服务
│ ├── user/ # 用户服务
│ ├── explorer/ # 文件管理服务
│ └── chat/ # 聊天服务
├── ent/ # 数据模型层 (Ent ORM)
│ ├── schema/ # 数据库模式定义
│ ├── migrate/ # 数据库迁移
│ └── *.go # 生成的实体代码
├── middleware/ # 中间件
│ ├── auth.go # 认证中间件
│ ├── cors.go # 跨域中间件
│ └── session.go # 会话中间件
├── pkg/ # 工具包
│ ├── auth/ # 认证工具
│ ├── cache/ # 缓存工具
│ ├── conf/ # 配置管理
│ └── util/ # 通用工具
├── inventory/ # 库存管理
└── application/ # 应用配置
```
## Gin 框架详解
### 基本用法
```go
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
// 创建 Gin 引擎
r := gin.Default()
// 定义路由
r.GET("/ping", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
})
// 启动服务器
r.Run(":8080")
}
```
### 路由组织
```go
// routers/router.go
func InitRouter(dep dependency.Dep) *gin.Engine {
r := gin.New()
// 全局中间件
r.Use(gin.Recovery())
r.Use(middleware.CORS())
// API 版本分组
v4 := r.Group("/api/v4")
// 用户相关路由
user := v4.Group("/user")
user.Use(middleware.LoginRequired())
{
user.GET("/info", controllers.GetUserInfo)
user.PUT("/info", controllers.UpdateUserInfo)
}
// 文件相关路由
file := v4.Group("/file")
{
file.GET("/list", controllers.ListFiles)
file.POST("/upload", controllers.UploadFile)
}
return r
}
```
### 中间件系统
```go
// middleware/auth.go
func LoginRequired() gin.HandlerFunc {
return func(c *gin.Context) {
// 从请求头获取 token
token := c.GetHeader("Authorization")
if token == "" {
c.JSON(http.StatusUnauthorized, gin.H{
"error": "Missing authorization token",
})
c.Abort()
return
}
// 验证 token
user, err := validateToken(token)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{
"error": "Invalid token",
})
c.Abort()
return
}
// 将用户信息存储到上下文
c.Set("user", user)
c.Next()
}
}
```
## Ent ORM 框架
### 模式定义
```go
// ent/schema/user.go
package schema
import (
"entgo.io/ent"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/edge"
)
// User 用户实体
type User struct {
ent.Schema
}
// Fields 字段定义
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("name").
NotEmpty().
Comment("用户名"),
field.String("email").
Unique().
Comment("邮箱"),
field.String("password").
Sensitive().
Comment("密码"),
field.Time("created_at").
Default(time.Now).
Comment("创建时间"),
}
}
// Edges 关联关系
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("files", File.Type).
Comment("用户文件"),
edge.To("groups", Group.Type).
Through("user_groups", UserGroup.Type).
Comment("用户组"),
}
}
```
### 数据库操作
```go
// service/user/user.go
type UserService struct {
client *ent.Client
}
func NewUserService(client *ent.Client) *UserService {
return &UserService{client: client}
}
// 创建用户
func (s *UserService) CreateUser(ctx context.Context, req *CreateUserRequest) (*ent.User, error) {
user, err := s.client.User.
Create().
SetName(req.Name).
SetEmail(req.Email).
SetPassword(hashPassword(req.Password)).
Save(ctx)
if err != nil {
return nil, fmt.Errorf("failed to create user: %w", err)
}
return user, nil
}
// 查询用户
func (s *UserService) GetUser(ctx context.Context, id int) (*ent.User, error) {
user, err := s.client.User.
Query().
Where(user.ID(id)).
WithFiles(). // 预加载文件关联
Only(ctx)
if err != nil {
if ent.IsNotFound(err) {
return nil, fmt.Errorf("user not found")
}
return nil, fmt.Errorf("failed to get user: %w", err)
}
return user, nil
}
// 更新用户
func (s *UserService) UpdateUser(ctx context.Context, id int, req *UpdateUserRequest) (*ent.User, error) {
user, err := s.client.User.
UpdateOneID(id).
SetName(req.Name).
SetEmail(req.Email).
Save(ctx)
if err != nil {
return nil, fmt.Errorf("failed to update user: %w", err)
}
return user, nil
}
```
## 控制器层设计
### 控制器结构
```go
// routers/controllers/user.go
package controllers
import (
"github.com/gin-gonic/gin"
"github.com/cloudreve/Cloudreve/v4/service/user"
)
// GetUserInfo 获取用户信息
func GetUserInfo(c *gin.Context) {
// 从上下文获取用户
currentUser, exists := c.Get("user")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{
"error": "User not found in context",
})
return
}
user := currentUser.(*ent.User)
// 返回用户信息
c.JSON(http.StatusOK, gin.H{
"data": gin.H{
"id": user.ID,
"name": user.Name,
"email": user.Email,
},
})
}
// UpdateUserInfo 更新用户信息
func UpdateUserInfo(c *gin.Context) {
var req user.UpdateUserRequest
// 绑定请求参数
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}
// 获取当前用户
currentUser := c.MustGet("user").(*ent.User)
// 调用服务层
userService := user.NewUserService(ent.GetClient())
updatedUser, err := userService.UpdateUser(c.Request.Context(), currentUser.ID, &req)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"data": updatedUser,
})
}
```
### 参数绑定和验证
```go
// service/user/types.go
type CreateUserRequest struct {
Name string `json:"name" binding:"required,min=2,max=50"`
Email string `json:"email" binding:"required,email"`
Password string `json:"password" binding:"required,min=6"`
}
type UpdateUserRequest struct {
Name string `json:"name" binding:"omitempty,min=2,max=50"`
Email string `json:"email" binding:"omitempty,email"`
}
// 自定义验证器
func ValidatePassword(fl validator.FieldLevel) bool {
password := fl.Field().String()
// 密码必须包含字母和数字
hasLetter := regexp.MustCompile(`[a-zA-Z]`).MatchString(password)
hasNumber := regexp.MustCompile(`[0-9]`).MatchString(password)
return hasLetter && hasNumber
}
```
## 服务层设计模式
### 依赖注入
```go
// application/dependency/dependency.go
type Dep interface {
Logger() logging.Logger
ConfigProvider() conf.Provider
DatabaseClient() *ent.Client
CacheProvider() cache.Driver
}
type dep struct {
logger logging.Logger
configProvider conf.Provider
dbClient *ent.Client
cacheProvider cache.Driver
}
func NewDep() Dep {
return &dep{
logger: logging.NewLogger(),
configProvider: conf.NewProvider(),
dbClient: ent.NewClient(),
cacheProvider: cache.NewRedisDriver(),
}
}
```
### 服务接口设计
```go
// service/user/interface.go
type Service interface {
CreateUser(ctx context.Context, req *CreateUserRequest) (*ent.User, error)
GetUser(ctx context.Context, id int) (*ent.User, error)
UpdateUser(ctx context.Context, id int, req *UpdateUserRequest) (*ent.User, error)
DeleteUser(ctx context.Context, id int) error
ListUsers(ctx context.Context, req *ListUsersRequest) ([]*ent.User, error)
}
// service/user/service.go
type service struct {
dep dependency.Dep
client *ent.Client
logger logging.Logger
}
func NewService(dep dependency.Dep) Service {
return &service{
dep: dep,
client: dep.DatabaseClient(),
logger: dep.Logger(),
}
}
```
## 认证和授权系统
### JWT 认证
```go
// pkg/auth/jwt.go
type JWTAuth struct {
secretKey []byte
issuer string
}
type Claims struct {
UserID int `json:"user_id"`
Email string `json:"email"`
jwt.RegisteredClaims
}
func (j *JWTAuth) GenerateToken(user *ent.User) (string, error) {
claims := &Claims{
UserID: user.ID,
Email: user.Email,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
Issuer: j.issuer,
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(j.secretKey)
}
func (j *JWTAuth) ValidateToken(tokenString string) (*Claims, error) {
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return j.secretKey, nil
})
if err != nil {
return nil, err
}
if claims, ok := token.Claims.(*Claims); ok && token.Valid {
return claims, nil
}
return nil, fmt.Errorf("invalid token")
}
```
### 权限控制
```go
// middleware/rbac.go
func RequirePermission(permission string) gin.HandlerFunc {
return func(c *gin.Context) {
user := c.MustGet("user").(*ent.User)
// 检查用户权限
hasPermission, err := checkUserPermission(user.ID, permission)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": "Failed to check permission",
})
c.Abort()
return
}
if !hasPermission {
c.JSON(http.StatusForbidden, gin.H{
"error": "Insufficient permissions",
})
c.Abort()
return
}
c.Next()
}
}
// 使用方式
admin := v4.Group("/admin")
admin.Use(middleware.LoginRequired())
admin.Use(middleware.RequirePermission("admin:read"))
{
admin.GET("/users", controllers.ListUsers)
}
```
## 文件存储系统
### 存储接口设计
```go
// pkg/filesystem/driver.go
type Driver interface {
// 上传文件
Put(ctx context.Context, path string, file io.Reader) error
// 获取文件
Get(ctx context.Context, path string) (io.ReadCloser, error)
// 删除文件
Delete(ctx context.Context, path string) error
// 获取文件信息
Stat(ctx context.Context, path string) (*FileInfo, error)
// 列出文件
List(ctx context.Context, path string) ([]*FileInfo, error)
}
type FileInfo struct {
Name string
Size int64
ModTime time.Time
IsDir bool
}
```
### 本地存储实现
```go
// pkg/filesystem/local.go
type LocalDriver struct {
rootPath string
}
func NewLocalDriver(rootPath string) Driver {
return &LocalDriver{rootPath: rootPath}
}
func (d *LocalDriver) Put(ctx context.Context, path string, file io.Reader) error {
fullPath := filepath.Join(d.rootPath, path)
// 确保目录存在
if err := os.MkdirAll(filepath.Dir(fullPath), 0755); err != nil {
return fmt.Errorf("failed to create directory: %w", err)
}
// 创建文件
dst, err := os.Create(fullPath)
if err != nil {
return fmt.Errorf("failed to create file: %w", err)
}
defer dst.Close()
// 复制文件内容
_, err = io.Copy(dst, file)
if err != nil {
return fmt.Errorf("failed to write file: %w", err)
}
return nil
}
func (d *LocalDriver) Get(ctx context.Context, path string) (io.ReadCloser, error) {
fullPath := filepath.Join(d.rootPath, path)
file, err := os.Open(fullPath)
if err != nil {
return nil, fmt.Errorf("failed to open file: %w", err)
}
return file, nil
}
```
## 聊天服务实现
### OpenRouter 客户端
```go
// pkg/openrouter/client.go
type Client struct {
apiKey string
baseURL string
httpClient *http.Client
logger logging.Logger
}
type ChatRequest struct {
Model string `json:"model"`
Messages []Message `json:"messages"`
Stream bool `json:"stream"`
Temperature float64 `json:"temperature"`
MaxTokens int `json:"max_tokens"`
}
type Message struct {
Role string `json:"role"`
Content string `json:"content"`
}
func (c *Client) Chat(ctx context.Context, req ChatRequest) (*ChatResponse, error) {
reqBody, err := json.Marshal(req)
if err != nil {
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
httpReq, err := http.NewRequestWithContext(ctx, "POST", c.baseURL+"/chat/completions", bytes.NewReader(reqBody))
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
httpReq.Header.Set("Authorization", "Bearer "+c.apiKey)
httpReq.Header.Set("Content-Type", "application/json")
resp, err := c.httpClient.Do(httpReq)
if err != nil {
return nil, fmt.Errorf("failed to send request: %w", err)
}
defer resp.Body.Close()
var chatResp ChatResponse
if err := json.NewDecoder(resp.Body).Decode(&chatResp); err != nil {
return nil, fmt.Errorf("failed to decode response: %w", err)
}
return &chatResp, nil
}
```
### 流式响应处理
```go
// service/chat/chat.go
type StreamCallback func(content string, done bool) error
func (s *ChatService) StreamChat(ctx context.Context, user *ent.User, message string, callback StreamCallback) error {
req := openrouter.ChatRequest{
Model: "google/gemini-2.5-pro",
Messages: []openrouter.Message{
{
Role: "system",
Content: "你是 Cloudreve 云盘的 AI 助手",
},
{
Role: "user",
Content: message,
},
},
Stream: true,
Temperature: 0.7,
MaxTokens: 2000,
}
return s.client.ChatStream(ctx, req, callback)
}
// 在控制器中使用
func ChatStream(c *gin.Context) {
// 设置 SSE 响应头
c.Header("Content-Type", "text/event-stream")
c.Header("Cache-Control", "no-cache")
c.Header("Connection", "keep-alive")
// 获取参数
service := ParametersFromContext[*chatsvc.Service](c, ChatParameterCtx)
user := c.MustGet("user").(*ent.User)
// 创建聊天服务
chatService := chatsvc.NewChatService(dep)
// 流式回调
callback := func(content string, done bool) error {
data := map[string]interface{}{
"choices": []map[string]interface{}{
{
"delta": map[string]interface{}{
"content": content,
},
"finish_reason": nil,
},
},
}
if done {
data["choices"].([]map[string]interface{})[0]["finish_reason"] = "stop"
}
jsonData, _ := json.Marshal(data)
fmt.Fprintf(c.Writer, "data: %s\n\n", jsonData)
c.Writer.Flush()
return nil
}
// 执行流式聊天
err := chatService.StreamChat(c.Request.Context(), user, service.Message, callback)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
}
```
## 错误处理和日志
### 统一错误处理
```go
// pkg/errors/errors.go
type AppError struct {
Code int `json:"code"`
Message string `json:"message"`
Details string `json:"details,omitempty"`
}
func (e *AppError) Error() string {
return e.Message
}
func NewAppError(code int, message string) *AppError {
return &AppError{
Code: code,
Message: message,
}
}
// 预定义错误
var (
ErrUserNotFound = NewAppError(404, "User not found")
ErrInvalidPassword = NewAppError(400, "Invalid password")
ErrUnauthorized = NewAppError(401, "Unauthorized")
ErrForbidden = NewAppError(403, "Forbidden")
)
```
### 结构化日志
```go
// pkg/logging/logger.go
type Logger interface {
Debug(msg string, fields ...Field)
Info(msg string, fields ...Field)
Warn(msg string, fields ...Field)
Error(msg string, fields ...Field)
Fatal(msg string, fields ...Field)
}
type Field struct {
Key string
Value interface{}
}
func String(key, value string) Field {
return Field{Key: key, Value: value}
}
func Int(key string, value int) Field {
return Field{Key: key, Value: value}
}
// 使用方式
logger.Info("User created successfully",
String("user_id", user.ID),
String("email", user.Email),
String("ip", c.ClientIP()),
)
```
## 配置管理
### 配置结构
```go
// pkg/conf/conf.go
type Config struct {
System SystemConfig `mapstructure:"system"`
Database DatabaseConfig `mapstructure:"database"`
Redis RedisConfig `mapstructure:"redis"`
Storage StorageConfig `mapstructure:"storage"`
}
type SystemConfig struct {
Mode string `mapstructure:"mode"`
Port int `mapstructure:"port"`
Debug bool `mapstructure:"debug"`
SessionName string `mapstructure:"session_name"`
}
type DatabaseConfig struct {
Type string `mapstructure:"type"`
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
User string `mapstructure:"user"`
Password string `mapstructure:"password"`
Name string `mapstructure:"name"`
}
// 加载配置
func LoadConfig(path string) (*Config, error) {
viper.SetConfigFile(path)
viper.SetConfigType("yaml")
if err := viper.ReadInConfig(); err != nil {
return nil, fmt.Errorf("failed to read config: %w", err)
}
var config Config
if err := viper.Unmarshal(&config); err != nil {
return nil, fmt.Errorf("failed to unmarshal config: %w", err)
}
return &config, nil
}
```
## 测试策略
### 单元测试
```go
// service/user/user_test.go
func TestUserService_CreateUser(t *testing.T) {
// 设置测试数据库
client := enttest.Open(t, "sqlite3", "file:ent?mode=memory&cache=shared&_fk=1")
defer client.Close()
// 创建服务
service := NewUserService(client)
// 测试数据
req := &CreateUserRequest{
Name: "Test User",
Email: "test@example.com",
Password: "password123",
}
// 执行测试
user, err := service.CreateUser(context.Background(), req)
// 断言
assert.NoError(t, err)
assert.NotNil(t, user)
assert.Equal(t, req.Name, user.Name)
assert.Equal(t, req.Email, user.Email)
}
```
### 集成测试
```go
// test/integration/api_test.go
func TestUserAPI(t *testing.T) {
// 设置测试服务器
router := setupTestRouter()
// 创建用户
createReq := `{"name":"Test User","email":"test@example.com","password":"password123"}`
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/api/v4/user", strings.NewReader(createReq))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusCreated, w.Code)
var response map[string]interface{}
json.Unmarshal(w.Body.Bytes(), &response)
assert.Equal(t, "Test User", response["data"].(map[string]interface{})["name"])
}
```
## 性能优化
### 数据库优化
```go
// 使用索引
func (User) Indexes() []ent.Index {
return []ent.Index{
index.Fields("email").Unique(),
index.Fields("name", "created_at"),
}
}
// 批量操作
func (s *UserService) CreateUsers(ctx context.Context, users []*CreateUserRequest) ([]*ent.User, error) {
bulk := make([]*ent.UserCreate, len(users))
for i, req := range users {
bulk[i] = s.client.User.Create().
SetName(req.Name).
SetEmail(req.Email).
SetPassword(hashPassword(req.Password))
}
return s.client.User.CreateBulk(bulk...).Save(ctx)
}
// 预加载关联
func (s *UserService) GetUserWithFiles(ctx context.Context, id int) (*ent.User, error) {
return s.client.User.
Query().
Where(user.ID(id)).
WithFiles(func(q *ent.FileQuery) {
q.Order(ent.Desc(file.FieldCreatedAt)).
Limit(10)
}).
Only(ctx)
}
```
### 缓存策略
```go
// pkg/cache/cache.go
type Cache interface {
Get(ctx context.Context, key string) (string, error)
Set(ctx context.Context, key string, value string, ttl time.Duration) error
Delete(ctx context.Context, key string) error
}
// 在服务中使用缓存
func (s *UserService) GetUser(ctx context.Context, id int) (*ent.User, error) {
cacheKey := fmt.Sprintf("user:%d", id)
// 尝试从缓存获取
cached, err := s.cache.Get(ctx, cacheKey)
if err == nil {
var user ent.User
if json.Unmarshal([]byte(cached), &user) == nil {
return &user, nil
}
}
// 从数据库获取
user, err := s.client.User.Get(ctx, id)
if err != nil {
return nil, err
}
// 存入缓存
if data, err := json.Marshal(user); err == nil {
s.cache.Set(ctx, cacheKey, string(data), time.Hour)
}
return user, nil
}
```
## 部署和运维
### Docker 化
```dockerfile
# Dockerfile
FROM golang:1.23-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o cloudreve .
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/cloudreve .
COPY --from=builder /app/conf.ini .
EXPOSE 5212
CMD ["./cloudreve"]
```
### 健康检查
```go
// routers/controllers/health.go
func HealthCheck(c *gin.Context) {
// 检查数据库连接
if err := checkDatabase(); err != nil {
c.JSON(http.StatusServiceUnavailable, gin.H{
"status": "unhealthy",
"error": err.Error(),
})
return
}
// 检查缓存连接
if err := checkCache(); err != nil {
c.JSON(http.StatusServiceUnavailable, gin.H{
"status": "unhealthy",
"error": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"status": "healthy",
"timestamp": time.Now().Unix(),
})
}
```
## 总结
Cloudreve 的后端架构具有以下特点:
1. **清晰的分层设计**: 路由、控制器、服务、数据访问层职责分明
2. **现代化的 ORM**: 使用 Ent 提供类型安全的数据库操作
3. **完善的中间件系统**: 认证、授权、日志、错误处理等
4. **灵活的存储系统**: 支持多种存储后端
5. **强大的聊天功能**: 集成 OpenRouter API 支持 AI 对话
6. **良好的测试覆盖**: 单元测试和集成测试
7. **性能优化**: 缓存、数据库优化、并发处理
这个架构为你开发侧边栏对话功能提供了坚实的基础,你可以参考现有的聊天服务实现来扩展新功能。
04-api-communication.md - API 通信机制
- RESTful API 设计规范
- JWT 认证流程和 SSE 流式传输
- 错误处理和缓存策略
arduino
# Cloudreve 前后端通信机制详解
## API 设计概览
Cloudreve 采用 RESTful API 设计,前后端通过 HTTP/HTTPS 协议进行通信。API 遵循统一的设计规范,提供清晰的接口定义和错误处理机制。
### API 版本管理
```
基础路径: /api/v4
当前版本: v4
```
所有 API 请求都使用 `/api/v4` 作为基础路径,便于版本管理和向后兼容。
## 请求响应格式
### 统一响应格式
```typescript
interface Response<T> {
data: T; // 响应数据
code: number; // 状态码
msg: string; // 消息
error?: string; // 错误信息
correlation_id?: string; // 请求追踪ID
}
```
### 成功响应示例
```json
{
"data": {
"id": 1,
"name": "用户名",
"email": "user@example.com"
},
"code": 200,
"msg": "success"
}
```
### 错误响应示例
```json
{
"data": null,
"code": 400,
"msg": "参数验证失败",
"error": "email field is required",
"correlation_id": "req-123456789"
}
```
## 认证机制
### JWT Token 认证
```mermaid
sequenceDiagram
participant C as 客户端
participant S as 服务器
participant DB as 数据库
C->>S: POST /api/v4/session/token (用户名/密码)
S->>DB: 验证用户凭据
DB-->>S: 返回用户信息
S-->>C: 返回 JWT Token
Note over C: 存储 Token 到 localStorage
C->>S: GET /api/v4/user/info (Authorization: Bearer <token>)
S->>S: 验证 Token
S-->>C: 返回用户信息
```
### Token 管理
#### 1. 获取 Token
```typescript
// 前端登录请求
const loginRequest = {
email: "user@example.com",
password: "password123"
};
const response = await axios.post('/api/v4/session/token', loginRequest);
const { access_token, refresh_token } = response.data;
// 存储 Token
SessionManager.setTokens(access_token, refresh_token);
```
#### 2. 请求拦截器
```typescript
// api/request.ts
instance.interceptors.request.use(async (config) => {
const token = await SessionManager.getAccessToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
```
#### 3. Token 刷新机制
```typescript
instance.interceptors.response.use(
(response) => response,
async (error) => {
if (error.response?.status === 401) {
try {
// 尝试刷新 Token
await SessionManager.refreshToken();
// 重试原请求
return instance.request(error.config);
} catch (refreshError) {
// 刷新失败,跳转登录页
router.push('/session');
}
}
return Promise.reject(error);
}
);
```
## 核心 API 接口
### 1. 用户认证 API
#### 登录
```http
POST /api/v4/session/token
Content-Type: application/json
{
"email": "user@example.com",
"password": "password123"
}
```
响应:
```json
{
"data": {
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"refresh_token": "eyJhbGciOiJIUzI1NiIs...",
"expires_in": 3600,
"user": {
"id": 1,
"name": "用户名",
"email": "user@example.com"
}
},
"code": 200,
"msg": "登录成功"
}
```
#### 刷新 Token
```http
POST /api/v4/session/refresh
Content-Type: application/json
{
"refresh_token": "eyJhbGciOiJIUzI1NiIs..."
}
```
#### 登出
```http
DELETE /api/v4/session/token
Authorization: Bearer <access_token>
```
### 2. 文件管理 API
#### 获取文件列表
```http
GET /api/v4/file/list?path=/documents&sort=name&order=asc
Authorization: Bearer <access_token>
```
响应:
```json
{
"data": {
"files": [
{
"id": "file_123",
"name": "document.pdf",
"size": 1024000,
"type": "file",
"created_at": "2023-01-01T00:00:00Z",
"path": "cloudreve://my/documents/document.pdf"
}
],
"total": 1,
"path": "/documents"
},
"code": 200,
"msg": "success"
}
```
#### 文件上传
```http
POST /api/v4/file/upload
Authorization: Bearer <access_token>
Content-Type: multipart/form-data
file: <binary_data>
path: /documents
```
#### 文件下载
```http
GET /api/v4/file/download/<file_id>
Authorization: Bearer <access_token>
```
### 3. 聊天 API
#### 普通聊天
```http
POST /api/v4/chat
Authorization: Bearer <access_token>
Content-Type: application/json
{
"message": "请帮我总结一下文档内容"
}
```
响应:
```json
{
"data": {
"response": "根据您的文档内容,主要包含以下几个方面...",
"model": "google/gemini-2.5-pro",
"usage": {
"prompt_tokens": 100,
"completion_tokens": 200,
"total_tokens": 300
}
},
"code": 200,
"msg": "success"
}
```
#### 流式聊天
```http
POST /api/v4/chat/stream
Authorization: Bearer <access_token>
Content-Type: application/json
{
"message": "请帮我分析这些文件"
}
```
响应(SSE 格式):
```
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
data: {"choices":[{"delta":{"content":"根据"},"finish_reason":null}]}
data: {"choices":[{"delta":{"content":"您的"},"finish_reason":null}]}
data: {"choices":[{"delta":{"content":"文件"},"finish_reason":null}]}
data: {"choices":[{"delta":{"content":""},"finish_reason":"stop"}]}
```
## 错误处理机制
### HTTP 状态码规范
| 状态码 | 含义 | 使用场景 |
|--------|------|----------|
| 200 | 成功 | 请求成功处理 |
| 201 | 创建成功 | 资源创建成功 |
| 400 | 请求错误 | 参数验证失败 |
| 401 | 未认证 | Token 无效或过期 |
| 403 | 权限不足 | 没有访问权限 |
| 404 | 资源不存在 | 请求的资源不存在 |
| 409 | 冲突 | 资源冲突(如邮箱已存在) |
| 422 | 验证失败 | 业务逻辑验证失败 |
| 500 | 服务器错误 | 内部服务器错误 |
### 错误响应格式
```typescript
interface ErrorResponse {
code: number;
msg: string;
error?: string;
details?: {
field: string;
message: string;
}[];
correlation_id?: string;
}
```
### 前端错误处理
```typescript
// 全局错误处理
instance.interceptors.response.use(
(response) => response,
(error) => {
const { response } = error;
if (response) {
const { status, data } = response;
switch (status) {
case 400:
enqueueSnackbar(data.msg || '请求参数错误', { variant: 'error' });
break;
case 401:
enqueueSnackbar('登录已过期,请重新登录', { variant: 'error' });
router.push('/session');
break;
case 403:
enqueueSnackbar('权限不足', { variant: 'error' });
break;
case 404:
enqueueSnackbar('请求的资源不存在', { variant: error' });
break;
case 500:
enqueueSnackbar('服务器内部错误', { variant: 'error' });
break;
default:
enqueueSnackbar(data.msg || '未知错误', { variant: 'error' });
}
} else {
enqueueSnackbar('网络连接错误', { variant: 'error' });
}
return Promise.reject(error);
}
);
```
## 流式数据传输
### Server-Sent Events (SSE)
Cloudreve 使用 SSE 协议实现流式数据传输,主要用于聊天功能的实时响应。
#### 前端 SSE 处理
```typescript
// 流式聊天实现
export const streamChat = async (
message: string,
onMessage: (content: string) => void,
onComplete: () => void,
onError: (error: Error) => void
) => {
try {
const token = await SessionManager.getAccessToken();
const response = await fetch('/api/v4/chat/stream', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
},
body: JSON.stringify({ message }),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const reader = response.body?.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader!.read();
if (done) {
onComplete();
break;
}
const chunk = decoder.decode(value);
const lines = chunk.split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.slice(6);
if (data === '[DONE]') {
onComplete();
return;
}
try {
const parsed = JSON.parse(data);
const content = parsed.choices?.[0]?.delta?.content;
if (content) {
onMessage(content);
}
if (parsed.choices?.[0]?.finish_reason === 'stop') {
onComplete();
return;
}
} catch (parseError) {
console.warn('Failed to parse SSE data:', data);
}
}
}
}
} catch (error) {
onError(error as Error);
}
};
```
#### 后端 SSE 实现
```go
// 控制器中的流式响应
func ChatStream(c *gin.Context) {
// 设置 SSE 响应头
c.Header("Content-Type", "text/event-stream")
c.Header("Cache-Control", "no-cache")
c.Header("Connection", "keep-alive")
c.Header("Access-Control-Allow-Origin", "*")
// 获取参数
service := ParametersFromContext[*chatsvc.Service](c, ChatParameterCtx)
user := c.MustGet("user").(*ent.User)
// 创建聊天服务
chatService := chatsvc.NewChatService(dep)
// 流式回调函数
callback := func(content string, done bool) error {
data := map[string]interface{}{
"id": "chatcmpl-" + generateID(),
"object": "chat.completion.chunk",
"created": time.Now().Unix(),
"model": "google/gemini-2.5-pro",
"choices": []map[string]interface{}{
{
"index": 0,
"delta": map[string]interface{}{
"content": content,
},
"finish_reason": nil,
},
},
}
if done {
data["choices"].([]map[string]interface{})[0]["finish_reason"] = "stop"
}
jsonData, _ := json.Marshal(data)
fmt.Fprintf(c.Writer, "data: %s\n\n", jsonData)
c.Writer.Flush()
return nil
}
// 执行流式聊天
err := chatService.StreamChatWithFiles(c.Request.Context(), user, service.Message, callback)
if err != nil {
errorData := map[string]interface{}{
"error": map[string]interface{}{
"message": err.Error(),
"type": "server_error",
},
}
jsonData, _ := json.Marshal(errorData)
fmt.Fprintf(c.Writer, "data: %s\n\n", jsonData)
c.Writer.Flush()
}
// 发送结束标记
fmt.Fprintf(c.Writer, "data: [DONE]\n\n")
c.Writer.Flush()
}
```
## 文件上传机制
### 分片上传
对于大文件,Cloudreve 支持分片上传机制:
```mermaid
sequenceDiagram
participant C as 客户端
participant S as 服务器
participant FS as 文件系统
C->>S: 1. 创建上传会话
S-->>C: 返回 session_id
loop 分片上传
C->>S: 2. 上传分片 (session_id, chunk_index, chunk_data)
S->>FS: 存储分片
S-->>C: 返回上传进度
end
C->>S: 3. 完成上传 (session_id)
S->>FS: 合并分片
S-->>C: 返回文件信息
```
#### 前端分片上传实现
```typescript
export const uploadFileWithProgress = async (
file: File,
path: string,
onProgress: (progress: number) => void
): Promise<FileResponse> => {
const CHUNK_SIZE = 1024 * 1024; // 1MB per chunk
const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
// 1. 创建上传会话
const sessionResponse = await api.post('/file/upload/session', {
name: file.name,
size: file.size,
path: path,
chunks: totalChunks,
});
const sessionId = sessionResponse.data.session_id;
try {
// 2. 分片上传
for (let i = 0; i < totalChunks; i++) {
const start = i * CHUNK_SIZE;
const end = Math.min(start + CHUNK_SIZE, file.size);
const chunk = file.slice(start, end);
const formData = new FormData();
formData.append('chunk', chunk);
formData.append('session_id', sessionId);
formData.append('chunk_index', i.toString());
await api.post('/file/upload/chunk', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
});
// 更新进度
const progress = Math.round(((i + 1) / totalChunks) * 100);
onProgress(progress);
}
// 3. 完成上传
const completeResponse = await api.post('/file/upload/complete', {
session_id: sessionId,
});
return completeResponse.data;
} catch (error) {
// 上传失败,清理会话
await api.delete(`/file/upload/session/${sessionId}`);
throw error;
}
};
```
## 缓存策略
### 前端缓存
#### 1. HTTP 缓存
```typescript
// 为静态资源设置缓存
const cacheableRequests = [
'/api/v4/site/config',
'/api/v4/user/info',
];
instance.interceptors.request.use((config) => {
if (cacheableRequests.some(url => config.url?.includes(url))) {
config.headers['Cache-Control'] = 'max-age=300'; // 5分钟缓存
}
return config;
});
```
#### 2. 内存缓存
```typescript
class ApiCache {
private cache = new Map<string, { data: any; expires: number }>();
get(key: string): any | null {
const item = this.cache.get(key);
if (!item || Date.now() > item.expires) {
this.cache.delete(key);
return null;
}
return item.data;
}
set(key: string, data: any, ttl: number = 300000): void {
this.cache.set(key, {
data,
expires: Date.now() + ttl,
});
}
clear(): void {
this.cache.clear();
}
}
const apiCache = new ApiCache();
// 在请求拦截器中使用缓存
instance.interceptors.request.use((config) => {
if (config.method === 'GET') {
const cacheKey = `${config.url}?${JSON.stringify(config.params)}`;
const cached = apiCache.get(cacheKey);
if (cached) {
// 返回缓存的 Promise
return Promise.resolve({
...config,
adapter: () => Promise.resolve({
data: cached,
status: 200,
statusText: 'OK',
headers: {},
config,
}),
});
}
}
return config;
});
```
### 后端缓存
```go
// 缓存中间件
func CacheMiddleware(ttl time.Duration) gin.HandlerFunc {
return func(c *gin.Context) {
if c.Request.Method != "GET" {
c.Next()
return
}
cacheKey := fmt.Sprintf("api:%s:%s", c.Request.URL.Path, c.Request.URL.RawQuery)
// 尝试从缓存获取
if cached, err := cache.Get(c.Request.Context(), cacheKey); err == nil {
c.Header("X-Cache", "HIT")
c.Data(200, "application/json", []byte(cached))
return
}
// 包装 ResponseWriter 以捕获响应
w := &responseWriter{
ResponseWriter: c.Writer,
body: &bytes.Buffer{},
}
c.Writer = w
c.Next()
// 缓存响应
if w.status == 200 {
cache.Set(c.Request.Context(), cacheKey, w.body.String(), ttl)
}
}
}
```
## 跨域处理
### CORS 配置
```go
// middleware/cors.go
func CORS() gin.HandlerFunc {
return cors.New(cors.Config{
AllowOrigins: []string{"http://localhost:3000", "https://yourdomain.com"},
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowHeaders: []string{"Origin", "Content-Type", "Authorization"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
MaxAge: 12 * time.Hour,
})
}
```
### 预检请求处理
```typescript
// 前端处理预检请求
axios.defaults.withCredentials = true;
// 自定义预检请求处理
instance.interceptors.request.use((config) => {
// 对于复杂请求,浏览器会先发送 OPTIONS 预检请求
if (config.method !== 'GET' && config.method !== 'POST') {
config.headers['Content-Type'] = 'application/json';
}
return config;
});
```
## API 版本兼容
### 版本策略
```typescript
// API 版本管理
export const API_VERSIONS = {
V3: '/api/v3',
V4: '/api/v4',
} as const;
// 根据功能选择 API 版本
export const createApiClient = (version: keyof typeof API_VERSIONS = 'V4') => {
return axios.create({
baseURL: API_VERSIONS[version],
timeout: 10000,
});
};
// 向后兼容处理
export const legacyApiCall = async (endpoint: string, data: any) => {
try {
// 尝试使用新版本 API
return await v4Client.post(endpoint, data);
} catch (error) {
if (error.response?.status === 404) {
// 回退到旧版本 API
console.warn(`Falling back to v3 API for ${endpoint}`);
return await v3Client.post(endpoint, data);
}
throw error;
}
};
```
## 性能优化
### 请求优化
#### 1. 请求合并
```typescript
// 批量请求合并
class RequestBatcher {
private batches = new Map<string, any[]>();
private timers = new Map<string, NodeJS.Timeout>();
batch<T>(
key: string,
request: any,
batchFn: (requests: any[]) => Promise<T[]>,
delay: number = 100
): Promise<T> {
return new Promise((resolve, reject) => {
if (!this.batches.has(key)) {
this.batches.set(key, []);
}
const batch = this.batches.get(key)!;
batch.push({ request, resolve, reject });
// 清除之前的定时器
if (this.timers.has(key)) {
clearTimeout(this.timers.get(key)!);
}
// 设置新的定时器
const timer = setTimeout(async () => {
const currentBatch = this.batches.get(key)!;
this.batches.delete(key);
this.timers.delete(key);
try {
const requests = currentBatch.map(item => item.request);
const results = await batchFn(requests);
currentBatch.forEach((item, index) => {
item.resolve(results[index]);
});
} catch (error) {
currentBatch.forEach(item => {
item.reject(error);
});
}
}, delay);
this.timers.set(key, timer);
});
}
}
// 使用示例
const batcher = new RequestBatcher();
export const getUserInfo = (userId: number) => {
return batcher.batch(
'getUserInfo',
userId,
async (userIds: number[]) => {
const response = await api.post('/user/batch', { ids: userIds });
return response.data;
}
);
};
```
#### 2. 请求去重
```typescript
// 请求去重
class RequestDeduplicator {
private pending = new Map<string, Promise<any>>();
dedupe<T>(key: string, requestFn: () => Promise<T>): Promise<T> {
if (this.pending.has(key)) {
return this.pending.get(key)!;
}
const promise = requestFn().finally(() => {
this.pending.delete(key);
});
this.pending.set(key, promise);
return promise;
}
}
const deduplicator = new RequestDeduplicator();
export const getFileList = (path: string) => {
const key = `fileList:${path}`;
return deduplicator.dedupe(key, () =>
api.get('/file/list', { params: { path } })
);
};
```
## 监控和调试
### 请求日志
```typescript
// 请求日志中间件
instance.interceptors.request.use((config) => {
const requestId = generateRequestId();
config.metadata = { requestId, startTime: Date.now() };
console.log(`[${requestId}] ${config.method?.toUpperCase()} ${config.url}`, {
params: config.params,
data: config.data,
});
return config;
});
instance.interceptors.response.use(
(response) => {
const { requestId, startTime } = response.config.metadata;
const duration = Date.now() - startTime;
console.log(`[${requestId}] Response ${response.status} (${duration}ms)`, {
data: response.data,
});
return response;
},
(error) => {
const { requestId, startTime } = error.config?.metadata || {};
const duration = Date.now() - startTime;
console.error(`[${requestId}] Error ${error.response?.status} (${duration}ms)`, {
error: error.response?.data,
});
return Promise.reject(error);
}
);
```
### 性能监控
```typescript
// API 性能监控
class ApiMonitor {
private metrics = new Map<string, {
count: number;
totalTime: number;
errors: number;
}>();
record(endpoint: string, duration: number, success: boolean) {
if (!this.metrics.has(endpoint)) {
this.metrics.set(endpoint, { count: 0, totalTime: 0, errors: 0 });
}
const metric = this.metrics.get(endpoint)!;
metric.count++;
metric.totalTime += duration;
if (!success) {
metric.errors++;
}
}
getStats() {
const stats = Array.from(this.metrics.entries()).map(([endpoint, metric]) => ({
endpoint,
count: metric.count,
avgTime: metric.totalTime / metric.count,
errorRate: metric.errors / metric.count,
}));
return stats.sort((a, b) => b.avgTime - a.avgTime);
}
}
const monitor = new ApiMonitor();
// 在拦截器中使用
instance.interceptors.response.use(
(response) => {
const duration = Date.now() - response.config.metadata.startTime;
monitor.record(response.config.url!, duration, true);
return response;
},
(error) => {
const duration = Date.now() - error.config?.metadata?.startTime;
monitor.record(error.config?.url!, duration, false);
return Promise.reject(error);
}
);
```
## 总结
Cloudreve 的前后端通信机制具有以下特点:
1. **统一的 API 设计**: RESTful 风格,清晰的接口规范
2. **完善的认证机制**: JWT Token + 自动刷新
3. **灵活的错误处理**: 统一的错误格式和处理策略
4. **流式数据支持**: SSE 协议实现实时通信
5. **高效的文件传输**: 分片上传支持大文件
6. **智能缓存策略**: 前后端多层缓存优化
7. **性能监控**: 完整的请求监控和调试机制
这套通信机制为你开发侧边栏对话功能提供了可靠的基础,你可以直接使用现有的 API 接口和通信模式来实现新功能。
05-development-guide.md - 开发流程指南
-
环境搭建和开发工作流
-
调试技巧和性能优化
-
部署流程和最佳实践
因篇幅限制,完整文档内容详见:mp.weixin.qq.com/s/O65Ay8h1S...
06-chat-feature-extension.md - 聊天功能扩展实现方案
-
基于现有聊天功能的侧边栏扩展
-
完整的前后端代码示例
-
文件夹上下文读取和 AI 对话集成
因篇幅限制,完整文档内容详见:mp.weixin.qq.com/s/O65Ay8h1S...

模块重构
分析并规划重构


重构计划详情
markdown
# Cloudreve 前端重构优化计划
## 📊 项目现状分析
### 技术栈概况
- **React 18.2.0 + TypeScript 5.2.2** - 现代化技术栈
- **Material-UI 6.x** - 成熟的组件库
- **Redux Toolkit** - 现代状态管理
- **119个依赖包** - 依赖较为复杂
### 主要问题识别
1. **性能问题**: Bundle过大、缺少虚拟化、不必要的重渲染
2. **代码质量**: 75个文件使用any类型、组件职责不清、代码重复
3. **架构问题**: 组件耦合度高、状态管理分散、缺少抽象层
4. **技术债务**: 8个TODO标记、重复样式定义、错误处理不统一
## 🎯 重构优先级排序
### 第一优先级:快速胜利项目(2-3周)
**风险:🟢低 | 收益:💰💰中高**
1. **代码规范统一**
- 统一TypeScript配置,消除any类型滥用
- 建立组件命名规范和文件组织标准
- 实施自动化代码格式化
2. **国际化优化**
- 优化10种语言的翻译文件结构
- 实施按需加载机制
- 统一翻译key命名规范
3. **工具函数抽取**
- 创建统一的时间格式化、文件类型判断工具
- 抽取重复的API调用模式
- 建立公共Hook库
### 第二优先级:性能优化项目(3-4周)
**风险:🔴高 | 收益:💰💰💰高**
1. **Bundle优化**
- 实施精细化代码分割,预计减少35%包大小
- 优化Monaco Editor、Excalidraw等大型库的加载
- 实施懒加载和预加载策略
2. **组件性能优化**
- 为核心组件添加React.memo和useCallback
- 实施虚拟滚动解决长列表性能问题
- 优化文件缩略图加载机制
3. **运行时优化**
- 修复内存泄漏问题
- 优化状态更新频率
- 实施智能缓存策略
### 第三优先级:架构重构项目(4-6周)
**风险:🔴高 | 收益:💰💰💰高**
1. **组件架构重构**
- 重构GridFile等大型组件(463行→拆分为多个小组件)
- 分离数据获取和UI渲染逻辑
- 建立统一的错误处理和加载状态管理
2. **状态管理优化**
- 重构聊天系统的状态管理
- 统一Redux和本地状态的使用模式
- 实施状态机管理复杂状态转换
3. **依赖升级**
- 分批升级核心依赖包
- 解决安全漏洞和兼容性问题
- 获得新版本特性支持
## 🚀 实施策略
### 渐进式重构方案
```
阶段一(2-3周):基础优化
├── 代码规范统一
├── 国际化优化
└── 技术债务清理
阶段二(3-4周):性能优化
├── Bundle分割优化
├── 组件渲染优化
└── 缓存策略实施
阶段三(4-6周):架构重构
├── 组件拆分重构
├── 状态管理统一
└── 依赖版本升级
```
### 测试和质量保证
- **自动化测试**: 核心组件90%+覆盖率
- **性能测试**: FCP<1.5s, LCP<2.5s目标
- **回滚计划**: 每阶段设置稳定版本标签
- **监控告警**: 实时监控关键性能指标
## 📈 预期收益
### 性能提升
- **加载速度**: 提升50%
- **Bundle大小**: 减少35%
- **内存占用**: 降低20-30%
### 开发效率
- **新功能开发**: 速度提升40%
- **组件复用率**: 提升60%
- **Bug修复时间**: 减少40%
### 代码质量
- **技术债务**: 减少80%
- **测试覆盖率**: 达到85%+
- **类型安全**: 消除75个any类型使用
## ⚠️ 风险控制
### 技术风险缓解
- 渐进式重构避免大爆炸式改动
- 充分的自动化和手工测试
- 灰度发布和实时监控
### 项目风险管理
- 每周进度和质量评审
- 明确的里程碑和交付物
- 备用方案和知识分享机制
## 💼 资源需求
- **人力**: 前端架构师1人 + 高级工程师2人 + 工程师1人
- **时间**: 总计13-16周
- **工具**: 自动化测试框架、性能监控工具、CI/CD流水线
这个重构计划通过科学的优先级排序和风险控制,确保在提升代码质量的同时保持项目稳定性,为长期的可维护性和开发效率奠定基础。
重构实施结果




功能迭代
在完成项目理解和代码优化后,正式进入功能开发阶段。我计划为 Cloudreve 添加一个智能对话功能,让用户可以通过自然语言与云盘交互。
准备工作:接入 Context7 MCP
为了提高 AI 对 OpenRouter API 的理解和使用能力,我先接入了 Context7 MCP 服务。
Context7 链接: context7.com?q=openrouter
MCP 配置:
json
{
"mcpServers": {
"context7": {
"url": "https://mcp.context7.com/mcp"
}
}
}

Context7 MCP 配置界面

MCP 服务状态
第一阶段:基础对话功能
需求规划
打开 Plan,明确基础对话功能的核心需求。
需求描述:
diff
需求:
给云盘加个 AI 对话功能,用户可以用自然语言跟 AI 聊天
后端 :
- 新增流式对话接口并注册到路由中,需要身份鉴权
- 集成 OpenRouter 调用模型
前端 :
- 右下角加个圆形悬浮按钮
- 点击弹出 600px 宽的侧边栏对话面板
- 基本的聊天界面:消息列表 + 输入框
- 调用流式对话接口时,处理好鉴权参数、流式输出状态

Coder 会自动调用内置的 Search Agent 分析项目结构,理解代码上下文:

Search Agent 工作过程
分析完成后,Coder 将技术方案以文档形式呈现,支持实时修改和编辑:

技术方案文档
任务执行
确认方案后,Coder 会根据技术规划自动拆解任务并分步执行

任务拆解列表
在实施过程中,可以实时看到每个步骤的状态更新:

实施进度追踪
开发完成后,Coder 会自动启动项目进行预览验证:

自动启动预览
实现效果:
对话功能已经成功实现,UI 风格与 Cloudreve 原生界面保持高度一致:

对话功能界面
第二阶段:增量迭代,增加多模态召回
基础对话功能完成后,我进一步提出了更高级的需求:让 AI 能够理解和检索云盘中的文件内容。
增强需求
需求描述:
diff
需求:
AI 聊天支持多模态文件智能检索
后端:
- 流式对话接口中读取全量云盘文内容作为上下文提供给模型,模型通过文件元信息及多模态内容进行文件召回
- 返回:summary + file list 的结构,前端渲染为文本 + GUI 列表
- 问"帮我找包含小狗的图片"这类问题时,能够召回文件元信息或文件内容与用户意图相关的文件
前端:
- 消息中展示文件列表
- 3 列网格布局,显示缩略图、文件名
实现效果
Coder 会自动启动前后端服务,并通过终端进行功能测试:

功能测试过程
最终效果:
系统成功实现了文件内容识别和智能召回功能。当用户询问"帮我找包含小狗的图片"时,AI 能够准确识别图片内容并返回相关文件:

文件召回效果展示

总结与思考
通过这次实践,可以看到 SOLO Coder 在项目理解、代码重构到功能开发的每个环节都可以提供支持。这个项目未来还可以进一步探索的有:更复杂的多模态交互场景、智能文件管理和推荐和基于用户行为的个性化体验,欢迎大家去创造体验。