371 lines
9.2 KiB
Markdown
371 lines
9.2 KiB
Markdown
# 模特管理功能优化计划
|
|
|
|
## 1. 性能优化 (高优先级)
|
|
|
|
### 1.1 数据库连接池优化
|
|
```rust
|
|
// 实现连接池管理
|
|
use r2d2::{Pool, PooledConnection};
|
|
use r2d2_sqlite::SqliteConnectionManager;
|
|
|
|
pub struct DatabasePool {
|
|
pool: Pool<SqliteConnectionManager>,
|
|
}
|
|
|
|
impl DatabasePool {
|
|
pub fn new(database_url: &str) -> Result<Self> {
|
|
let manager = SqliteConnectionManager::file(database_url);
|
|
let pool = Pool::new(manager)?;
|
|
Ok(Self { pool })
|
|
}
|
|
|
|
pub fn get_connection(&self) -> Result<PooledConnection<SqliteConnectionManager>> {
|
|
self.pool.get().map_err(|e| anyhow!("获取数据库连接失败: {}", e))
|
|
}
|
|
}
|
|
```
|
|
|
|
### 1.2 异步查询优化
|
|
```rust
|
|
// 使用异步数据库操作
|
|
use tokio_rusqlite::{Connection, Result};
|
|
|
|
impl ModelRepository {
|
|
pub async fn get_all_async(&self) -> Result<Vec<Model>> {
|
|
// 异步查询实现
|
|
let models = self.query_models_async().await?;
|
|
|
|
// 并行加载照片
|
|
let mut tasks = Vec::new();
|
|
for model in models {
|
|
let repo = self.clone();
|
|
let model_id = model.id.clone();
|
|
tasks.push(tokio::spawn(async move {
|
|
repo.get_photos_async(&model_id).await
|
|
}));
|
|
}
|
|
|
|
// 等待所有照片加载完成
|
|
let photos_results = futures::future::join_all(tasks).await;
|
|
// 组装结果...
|
|
}
|
|
}
|
|
```
|
|
|
|
### 1.3 缓存机制
|
|
```rust
|
|
use std::collections::HashMap;
|
|
use std::sync::{Arc, RwLock};
|
|
|
|
pub struct ModelCache {
|
|
models: Arc<RwLock<HashMap<String, Model>>>,
|
|
photos: Arc<RwLock<HashMap<String, Vec<ModelPhoto>>>>,
|
|
}
|
|
|
|
impl ModelCache {
|
|
pub fn get_model(&self, id: &str) -> Option<Model> {
|
|
self.models.read().unwrap().get(id).cloned()
|
|
}
|
|
|
|
pub fn invalidate_model(&self, id: &str) {
|
|
self.models.write().unwrap().remove(id);
|
|
self.photos.write().unwrap().remove(id);
|
|
}
|
|
}
|
|
```
|
|
|
|
## 2. 错误处理优化 (中优先级)
|
|
|
|
### 2.1 自定义错误类型
|
|
```rust
|
|
use thiserror::Error;
|
|
|
|
#[derive(Error, Debug)]
|
|
pub enum ModelError {
|
|
#[error("模特不存在: {id}")]
|
|
NotFound { id: String },
|
|
|
|
#[error("数据验证失败: {message}")]
|
|
ValidationError { message: String },
|
|
|
|
#[error("数据库操作失败: {source}")]
|
|
DatabaseError { source: rusqlite::Error },
|
|
|
|
#[error("文件操作失败: {path}")]
|
|
FileError { path: String },
|
|
}
|
|
```
|
|
|
|
### 2.2 用户友好的错误信息
|
|
```typescript
|
|
// 前端错误处理
|
|
export class ModelErrorHandler {
|
|
static handleError(error: string): string {
|
|
if (error.includes('NotFound')) {
|
|
return '找不到指定的模特,可能已被删除';
|
|
}
|
|
if (error.includes('ValidationError')) {
|
|
return '输入的信息格式不正确,请检查后重试';
|
|
}
|
|
if (error.includes('DatabaseError')) {
|
|
return '数据保存失败,请稍后重试';
|
|
}
|
|
return '操作失败,请联系技术支持';
|
|
}
|
|
}
|
|
```
|
|
|
|
## 3. 测试体系建设 (高优先级)
|
|
|
|
### 3.1 单元测试
|
|
```rust
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[tokio::test]
|
|
async fn test_create_model() {
|
|
let repo = create_test_repository().await;
|
|
let request = CreateModelRequest {
|
|
name: "测试模特".to_string(),
|
|
gender: Gender::Female,
|
|
// ...
|
|
};
|
|
|
|
let result = ModelService::create_model(&repo, request).await;
|
|
assert!(result.is_ok());
|
|
|
|
let model = result.unwrap();
|
|
assert_eq!(model.name, "测试模特");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_model_validation() {
|
|
let mut model = Model::new("".to_string(), Gender::Female);
|
|
let result = model.validate();
|
|
assert!(result.is_err());
|
|
assert!(result.unwrap_err().contains("姓名不能为空"));
|
|
}
|
|
}
|
|
```
|
|
|
|
### 3.2 集成测试
|
|
```rust
|
|
// tests/integration_test.rs
|
|
use mixvideo_desktop::*;
|
|
|
|
#[tokio::test]
|
|
async fn test_model_crud_workflow() {
|
|
let app_state = setup_test_app().await;
|
|
|
|
// 测试创建
|
|
let create_result = create_model(app_state.clone(), test_model_data()).await;
|
|
assert!(create_result.is_ok());
|
|
|
|
// 测试读取
|
|
let model_id = create_result.unwrap().id;
|
|
let get_result = get_model_by_id(app_state.clone(), model_id.clone()).await;
|
|
assert!(get_result.is_ok());
|
|
|
|
// 测试更新
|
|
let update_result = update_model(app_state.clone(), model_id.clone(), update_data()).await;
|
|
assert!(update_result.is_ok());
|
|
|
|
// 测试删除
|
|
let delete_result = delete_model(app_state.clone(), model_id).await;
|
|
assert!(delete_result.is_ok());
|
|
}
|
|
```
|
|
|
|
### 3.3 前端测试
|
|
```typescript
|
|
// src/components/__tests__/ModelList.test.tsx
|
|
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
import { ModelList } from '../ModelList';
|
|
import { modelService } from '../../services/modelService';
|
|
|
|
jest.mock('../../services/modelService');
|
|
|
|
describe('ModelList', () => {
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
});
|
|
|
|
test('应该正确加载和显示模特列表', async () => {
|
|
const mockModels = [
|
|
{ id: '1', name: '测试模特1', gender: 'Female' },
|
|
{ id: '2', name: '测试模特2', gender: 'Male' }
|
|
];
|
|
|
|
(modelService.getAllModels as jest.Mock).mockResolvedValue(mockModels);
|
|
|
|
render(<ModelList />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('测试模特1')).toBeInTheDocument();
|
|
expect(screen.getByText('测试模特2')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
test('应该正确处理删除操作', async () => {
|
|
// 测试删除功能...
|
|
});
|
|
});
|
|
```
|
|
|
|
## 4. 架构优化 (中优先级)
|
|
|
|
### 4.1 事件驱动架构
|
|
```rust
|
|
use tokio::sync::broadcast;
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub enum ModelEvent {
|
|
Created(Model),
|
|
Updated(Model),
|
|
Deleted(String),
|
|
}
|
|
|
|
pub struct EventBus {
|
|
sender: broadcast::Sender<ModelEvent>,
|
|
}
|
|
|
|
impl EventBus {
|
|
pub fn publish(&self, event: ModelEvent) {
|
|
let _ = self.sender.send(event);
|
|
}
|
|
|
|
pub fn subscribe(&self) -> broadcast::Receiver<ModelEvent> {
|
|
self.sender.subscribe()
|
|
}
|
|
}
|
|
```
|
|
|
|
### 4.2 分页和虚拟滚动
|
|
```typescript
|
|
// 前端分页优化
|
|
export interface PaginationParams {
|
|
page: number;
|
|
pageSize: number;
|
|
sortBy?: string;
|
|
sortOrder?: 'asc' | 'desc';
|
|
}
|
|
|
|
export class ModelListManager {
|
|
private cache = new Map<string, Model[]>();
|
|
|
|
async loadPage(params: PaginationParams): Promise<PaginatedResult<Model>> {
|
|
const cacheKey = this.getCacheKey(params);
|
|
|
|
if (this.cache.has(cacheKey)) {
|
|
return this.cache.get(cacheKey)!;
|
|
}
|
|
|
|
const result = await modelService.getModelsPaginated(params);
|
|
this.cache.set(cacheKey, result);
|
|
|
|
return result;
|
|
}
|
|
}
|
|
```
|
|
|
|
## 5. 用户体验优化 (中优先级)
|
|
|
|
### 5.1 加载状态优化
|
|
```typescript
|
|
// 骨架屏组件
|
|
export const ModelCardSkeleton: React.FC = () => (
|
|
<div className="animate-pulse">
|
|
<div className="bg-gray-300 h-48 w-full rounded-t-lg"></div>
|
|
<div className="p-4">
|
|
<div className="bg-gray-300 h-4 w-3/4 mb-2 rounded"></div>
|
|
<div className="bg-gray-300 h-3 w-1/2 rounded"></div>
|
|
</div>
|
|
</div>
|
|
);
|
|
```
|
|
|
|
### 5.2 离线支持
|
|
```typescript
|
|
// Service Worker for offline support
|
|
export class OfflineManager {
|
|
private cache = new Map<string, any>();
|
|
|
|
async getModels(): Promise<Model[]> {
|
|
try {
|
|
const models = await modelService.getAllModels();
|
|
this.cache.set('models', models);
|
|
return models;
|
|
} catch (error) {
|
|
// 返回缓存数据
|
|
return this.cache.get('models') || [];
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## 6. 安全性增强 (高优先级)
|
|
|
|
### 6.1 输入验证
|
|
```rust
|
|
use validator::{Validate, ValidationError};
|
|
|
|
#[derive(Validate)]
|
|
pub struct CreateModelRequest {
|
|
#[validate(length(min = 1, max = 50, message = "姓名长度必须在1-50字符之间"))]
|
|
pub name: String,
|
|
|
|
#[validate(email(message = "邮箱格式不正确"))]
|
|
pub email: Option<String>,
|
|
|
|
#[validate(range(min = 1, max = 100, message = "年龄必须在1-100之间"))]
|
|
pub age: Option<u32>,
|
|
}
|
|
```
|
|
|
|
### 6.2 权限控制
|
|
```rust
|
|
pub enum Permission {
|
|
ReadModel,
|
|
WriteModel,
|
|
DeleteModel,
|
|
ManagePhotos,
|
|
}
|
|
|
|
pub struct PermissionChecker;
|
|
|
|
impl PermissionChecker {
|
|
pub fn check_permission(user_role: &str, permission: Permission) -> bool {
|
|
match (user_role, permission) {
|
|
("admin", _) => true,
|
|
("editor", Permission::DeleteModel) => false,
|
|
("editor", _) => true,
|
|
("viewer", Permission::ReadModel) => true,
|
|
_ => false,
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## 实施优先级
|
|
|
|
1. **立即实施** (本周)
|
|
- 移除调试日志
|
|
- 添加基本单元测试
|
|
- 优化错误信息
|
|
|
|
2. **短期实施** (2周内)
|
|
- 实现连接池
|
|
- 添加缓存机制
|
|
- 完善测试覆盖
|
|
|
|
3. **中期实施** (1个月内)
|
|
- 事件驱动架构
|
|
- 分页优化
|
|
- 离线支持
|
|
|
|
4. **长期实施** (3个月内)
|
|
- 完整的权限系统
|
|
- 性能监控
|
|
- 用户体验优化
|