feat: 添加二维码生成功能并简化架构

- 新增二维码生成服务层,支持自定义内容和配置
  - 移除 Platform 抽象层,简化为三层架构
  - 更新开发文档和架构说明
  - 添加前端二维码生成页面和状态管理
This commit is contained in:
2026-02-10 19:06:36 +08:00
parent 927eaa1e03
commit b2754bdad5
24 changed files with 1642 additions and 278 deletions

View File

@@ -13,7 +13,7 @@
## 架构概述
### 层架构
### 层架构
```
┌─────────────────────────────────────────────────┐
@@ -25,10 +25,6 @@
│ 职责:业务逻辑、流程编排、状态管理 │
│ 文件src/services/*.rs │
├─────────────────────────────────────────────────┤
│ Platform 层 │
│ 职责:平台差异抽象、统一接口 │
│ 文件src/platforms/*.rs │
├─────────────────────────────────────────────────┤
│ Utils 层 │
│ 职责:纯算法、无状态工具函数 │
│ 文件src/utils/*.rs │
@@ -38,9 +34,9 @@
### 依赖原则
- **单向依赖**:上层依赖下层,下层不依赖上层
- **依赖链**Command → Service → Platform → Utils
- **依赖链**Command → Service → Utils
- **Utils 完全独立**:只依赖标准库和 models
- **Platform 通过 trait 解耦**Service 层通过 trait 调用,不关心具体实现
- **Service 可以直接调用 Windows API**
---
@@ -53,8 +49,7 @@
1. **功能描述**:这个功能做什么?
2. **用户交互**:用户如何触发这个功能?
3. **输入输出**:需要什么参数?返回什么结果?
4. **平台差异**不同平台是否需要不同实现
5. **错误场景**:哪些情况下会出错?如何处理?
4. **错误场景**哪些情况下会出错?如何处理
### 步骤 2设计数据模型如需要
@@ -140,88 +135,7 @@ mod tests {
- ✅ 包含单元测试
- ✅ 不依赖任何外部状态
### 步骤 4定义平台抽象(如果需要平台特定实现
如果功能需要访问平台 API定义 trait
```rust
// src/platforms/new_feature.rs
use crate::error::AppResult;
/// 新功能平台抽象 trait
pub trait NewFeatureAccessor {
/// 执行平台特定的操作
///
/// # 参数
///
/// * `param1` - 参数 1 说明
///
/// # 返回
///
/// 返回操作结果
///
/// # 错误
///
/// 操作失败时返回错误
fn execute_platform_operation(&self, param1: &str) -> AppResult<String>;
}
/// 平台特定实现类型别名
#[cfg(windows)]
pub type PlatformNewFeature = crate::platforms::windows::new_feature_impl::WindowsNewFeature;
#[cfg(not(windows))]
pub type PlatformNewFeature = crate::platforms::windows::new_feature_impl::DummyNewFeature;
```
**规范**
- ✅ 使用 trait 定义接口
- ✅ 所有方法返回 `AppResult<T>`
- ✅ 方法参数使用引用(`&str` 而不是 `String`
- ✅ 添加详细的文档注释
### 步骤 5实现平台特定代码
```rust
// src/platforms/windows/new_feature_impl.rs
use crate::error::{AppError, AppResult};
use crate::platforms::new_feature::NewFeatureAccessor;
/// Windows 平台实现
#[cfg(windows)]
pub struct WindowsNewFeature;
#[cfg(windows)]
impl NewFeatureAccessor for WindowsNewFeature {
fn execute_platform_operation(&self, param1: &str) -> AppResult<String> {
// Windows 特定实现
Ok(format!("Windows: {}", param1))
}
}
/// 其他平台占位实现
#[cfg(not(windows))]
pub struct DummyNewFeature;
#[cfg(not(windows))]
impl NewFeatureAccessor for DummyNewFeature {
fn execute_platform_operation(&self, _param1: &str) -> AppResult<String> {
Err(AppError::PlatformNotSupported(
"此平台暂不支持该功能".to_string()
))
}
}
```
**规范**
- ✅ 使用 `#[cfg(windows)]` 条件编译
- ✅ 提供其他平台的占位实现
- ✅ 占位实现返回 `PlatformNotSupported` 错误
- ✅ 使用中文错误消息
### 步骤 6实现业务逻辑Service 层)
### 步骤 4实现业务逻辑Service 层
```rust
// src/services/new_feature_service.rs
@@ -252,7 +166,6 @@ impl NewFeatureService {
/// # 错误
///
/// - 配置无效时返回 `AppError::InvalidColorData`
/// - 平台不支持时返回 `AppError::PlatformNotSupported`
pub fn execute(&self, config: &NewFeatureConfig) -> AppResult<NewFeatureResult> {
// 1. 参数验证
if config.option1.is_empty() {
@@ -285,10 +198,11 @@ impl NewFeatureService {
- ✅ 使用 struct 命名空间(如 `NewFeatureService`
- ✅ 所有方法返回 `AppResult<T>`
- ✅ 参数验证放在 Service 层
- ✅ 可以直接调用 Windows API
- ✅ 添加详细的文档注释
- ✅ 包含同步和异步两个版本(如需要)
### 步骤 7:创建 Tauri 命令Command 层)
### 步骤 5:创建 Tauri 命令Command 层)
```rust
// src/commands/new_feature_commands.rs
@@ -366,7 +280,7 @@ pub async fn execute_new_feature_async(config: NewFeatureConfig) -> Result<NewFe
- ✅ 参数使用结构体,便于扩展
- ✅ 添加详细的文档注释
### 步骤 8:注册模块
### 步骤 6:注册模块
更新各模块的 `mod.rs``lib.rs`
@@ -396,7 +310,7 @@ pub mod new_feature_commands; // 新增
- ✅ 导出常用类型
- ✅ 注册所有新命令
### 步骤 9:错误处理扩展(如需要)
### 步骤 7:错误处理扩展(如需要)
如果需要新的错误类型,在 `error.rs` 中添加:
@@ -426,7 +340,7 @@ impl fmt::Display for AppError {
- ✅ 携带详细的错误信息(`String`
- ✅ 在 `Display` 实现中添加上下文
### 步骤 10:更新前端类型定义
### 步骤 8:更新前端类型定义
`src/types/` 或相关位置添加 TypeScript 类型定义:
@@ -460,7 +374,7 @@ export type NewFeatureCommands = {
};
```
### 步骤 11:编写测试
### 步骤 9:编写测试
```rust
// src/services/new_feature_service.rs 中的测试
@@ -500,7 +414,7 @@ mod tests {
- ✅ 使用 `assert!``assert_eq!` 断言
- ✅ 运行 `cargo test` 验证
### 步骤 12:验证和测试
### 步骤 10:验证和测试
```bash
# 1. 检查代码编译
@@ -588,33 +502,6 @@ mod tests {
}
```
### Platform 层规范
**职责**:抽象平台差异,提供统一接口
**规范清单**
- ✅ 使用 trait 定义接口
- ✅ 通过类型别名选择实现
- ✅ 提供所有平台的占位实现
- ✅ 使用 `#[cfg(windows)]` 条件编译
- ✅ 占位实现返回 `PlatformNotSupported`
**示例**
```rust
// trait 定义
pub trait FileAccessor {
fn read_file(&self, path: &str) -> AppResult<String>;
}
// Windows 实现
#[cfg(windows)]
pub type PlatformFile = WindowsFile;
// 占位实现
#[cfg(not(windows))]
pub type PlatformFile = DummyFile;
```
### Service 层规范
**职责**:业务逻辑实现和流程编排
@@ -623,7 +510,7 @@ pub type PlatformFile = DummyFile;
- ✅ 使用 struct 命名空间
- ✅ 所有方法返回 `AppResult<T>`
- ✅ 参数验证在 Service 层进行
-通过 trait 调用 Platform 层
-可以直接调用 Windows API
- ✅ 可以调用 Utils 层函数
- ✅ 提供同步和异步版本(如需要)
- ✅ 使用详细的中文档注释
@@ -639,8 +526,8 @@ impl FileService {
return Err(AppError::InvalidData("路径不能为空".to_string()));
}
// 2. 调用 Platform 层
let content = PlatformFile::read_file(path)?;
// 2. 调用 Windows API 或第三方库
let content = std::fs::read_to_string(path)?;
// 3. 调用 Utils 层处理
let processed = utils::text::trim_whitespace(&content);
@@ -709,55 +596,7 @@ pub struct ScreenshotResult {
}
```
#### 2. 定义平台抽象
```rust
// src/platforms/screenshot.rs
use crate::error::AppResult;
use crate::models::screenshot::ScreenshotConfig;
/// 屏幕截图 trait
pub trait ScreenshotCapturer {
/// 截取整个屏幕
///
/// # 参数
///
/// * `config` - 截图配置
///
/// # 返回
///
/// 返回截图结果,包含 Base64 编码的图片数据
fn capture_screen(&self, config: &ScreenshotConfig) -> AppResult<ScreenshotResult>;
/// 截取指定区域
///
/// # 参数
///
/// * `x` - 起始 X 坐标
/// * `y` - 起始 Y 坐标
/// * `width` - 宽度
/// * `height` - 高度
/// * `config` - 截图配置
fn capture_region(
&self,
x: u32,
y: u32,
width: u32,
height: u32,
config: &ScreenshotConfig,
) -> AppResult<ScreenshotResult>;
}
// 类型别名
#[cfg(windows)]
pub type PlatformScreenshot = crate::platforms::windows::screenshot_impl::WindowsScreenshot;
#[cfg(not(windows))]
pub type PlatformScreenshot = crate::platforms::windows::screenshot_impl::DummyScreenshot;
```
#### 3. 实现 Service
#### 2. 实现 Service
```rust
// src/services/screenshot_service.rs
@@ -768,7 +607,6 @@ pub type PlatformScreenshot = crate::platforms::windows::screenshot_impl::DummyS
use crate::error::{AppError, AppResult};
use crate::models::screenshot::{ScreenshotConfig, ScreenshotResult};
use crate::platforms::screenshot::ScreenshotCapturer;
/// 截图服务
pub struct ScreenshotService;
@@ -793,8 +631,30 @@ impl ScreenshotService {
));
}
// 调用平台实现
PlatformScreenshot::capture_screen(config)
// 调用 Windows API 或使用第三方库
// 例如:使用 screenshots-rs crate
let screenshots = screenshots::Screen::all().map_err(|e| {
AppError::InvalidData(format!("获取屏幕失败: {}", e))
})?;
if let Some(screen) = screenshots.first() {
let image = screen.capture().map_err(|e| {
AppError::InvalidData(format!("截图失败: {}", e))
})?;
// 转换为 Base64
let buffer = image.buffer();
let base64_data = base64::encode(buffer);
Ok(ScreenshotResult {
data: base64_data,
format: "png".to_string(),
width: image.width(),
height: image.height(),
})
} else {
Err(AppError::InvalidData("未找到屏幕".to_string()))
}
}
/// 截取指定区域
@@ -811,12 +671,19 @@ impl ScreenshotService {
));
}
PlatformScreenshot::capture_region(x, y, width, height, config)
// 调用 Windows API 实现区域截图
// ...
Ok(ScreenshotResult {
data: "base64_data".to_string(),
format: "png".to_string(),
width,
height,
})
}
}
```
#### 4. 创建命令
#### 3. 创建命令
```rust
// src/commands/screenshot_commands.rs
@@ -881,7 +748,7 @@ pub fn capture_region(
| 函数 | snake_case | `get_pixel_color`, `toggle_window` |
| 常量 | SCREAMING_SNAKE_CASE | `MAX_RETRY_COUNT` |
| Trait | PascalCase + 能力 | `ScreenAccessor`, `CursorController` |
| 类型别名 | PascalCase + Platform/Type | `PlatformScreen`, `AppResult` |
| 类型别名 | PascalCase + Type | `AppResult`, `JsonResult` |
### 2. 文档注释规范
@@ -975,33 +842,7 @@ impl SomeService {
}
```
### 6. 平台检测规范
```rust
// ✅ 使用条件编译和 trait
#[cfg(windows)]
fn platform_specific() -> AppResult<()> {
WindowsImpl::do_something()
}
#[cfg(not(windows))]
fn platform_specific() -> AppResult<()> {
Err(AppError::PlatformNotSupported(
"此平台暂不支持".to_string()
))
}
// ❌ 不推荐:运行时检测
fn platform_specific() -> AppResult<()> {
if cfg!(windows) {
// Windows 代码
} else {
Err(AppError::PlatformNotSupported("...".to_string()))
}
}
```
### 7. 代码组织规范
### 6. 代码组织规范
```rust
// ✅ 推荐:按功能分组
@@ -1114,27 +955,23 @@ impl fmt::Display for AppError {
}
```
### Q: 如何在多个平台实现同一个功能
### Q: 如何调用 Windows API
A: 为每个平台创建独立的实现文件,并通过条件编译选择
A: 在 Service 层直接使用 Windows 相关的 crate 或调用 Windows API
```rust
// platforms/windows/impl.rs
#[cfg(windows)]
pub struct PlatformImpl;
impl PlatformTrait for PlatformImpl { }
// 使用 windows-rs crate
use windows::Win32::UI::WindowsAndMessaging::GetCursorPos;
// platforms/macos/impl.rs
#[cfg(target_os = "macos")]
pub struct PlatformImpl;
impl PlatformTrait for PlatformImpl { }
// platforms/mod.rs
#[cfg(windows)]
pub type Platform = crate::platforms::windows::impl::PlatformImpl;
#[cfg(target_os = "macos")]
pub type Platform = crate::platforms::macos::impl::PlatformImpl;
impl CursorService {
pub fn get_cursor_position(&self) -> AppResult<(i32, i32)> {
let mut pos = POINT { x: 0, y: 0 };
unsafe {
GetCursorPos(&mut pos)?;
}
Ok((pos.x, pos.y))
}
}
```
### Q: 如何处理异步操作?
@@ -1168,7 +1005,6 @@ pub async fn get_data() -> Result<Data, String> {
- [ ] 文档注释包含示例代码
- [ ] 错误消息使用中文
- [ ] 遵循依赖原则(上层依赖下层)
- [ ] 平台特定代码正确使用条件编译
- [ ] 新增类型已导出(如需要)
- [ ] 命令已在 `lib.rs` 中注册