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

@@ -0,0 +1,136 @@
//! 二维码生成服务
//!
//! 提供二维码生成的核心业务逻辑
use crate::error::{AppError, AppResult};
use crate::models::qrcode::{QrConfig, QrResult};
use crate::utils::qrcode_renderer::{image_to_base64, render_basic_qr};
/// 二维码生成服务
pub struct QrCodeService;
impl QrCodeService {
/// 生成二维码预览
///
/// 根据配置生成二维码并返回 Base64 编码的图片数据
///
/// # 参数
///
/// * `config` - 二维码配置
///
/// # 返回
///
/// 返回二维码生成结果,包含 Base64 编码的图片数据
///
/// # 错误
///
/// - 配置无效时返回 `AppError::InvalidData`
/// - 图片编码失败时返回 `AppError::IoError`
pub fn generate_preview(config: &QrConfig) -> AppResult<QrResult> {
// 验证尺寸
if config.size < 128 || config.size > 4096 {
return Err(AppError::InvalidData(
"尺寸必须在 128 到 4096 之间".to_string(),
));
}
// 验证边距
if config.margin > 50 {
return Err(AppError::InvalidData("边距不能超过 50".to_string()));
}
// 渲染二维码
let img = render_basic_qr(config)?;
// 转换为 Base64
let base64_data = image_to_base64(&img)?;
Ok(QrResult {
data: base64_data,
format: "png".to_string(),
})
}
/// 生成二维码并保存到文件
///
/// 根据配置生成二维码并保存为 PNG 文件
///
/// # 参数
///
/// * `config` - 二维码配置
/// * `output_path` - 输出文件路径
///
/// # 返回
///
/// 成功时返回 Ok(()),失败时返回错误
///
/// # 错误
///
/// - 配置无效时返回 `AppError::InvalidData`
/// - 文件写入失败时返回 `AppError::IoError`
pub fn generate_to_file(config: &QrConfig, output_path: &str) -> AppResult<()> {
// 验证尺寸
if config.size < 128 || config.size > 4096 {
return Err(AppError::InvalidData(
"尺寸必须在 128 到 4096 之间".to_string(),
));
}
// 渲染二维码
let img = render_basic_qr(config)?;
// 保存到文件
img.save(output_path)
.map_err(|e| AppError::IoError(format!("保存文件失败: {}", e)))?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generate_preview() {
let config = QrConfig {
content: "https://example.com".to_string(),
size: 512,
margin: 4,
error_correction: "M".to_string(),
};
let result = QrCodeService::generate_preview(&config);
assert!(result.is_ok());
let qr_result = result.unwrap();
assert!(qr_result.data.starts_with("data:image/png;base64,"));
assert_eq!(qr_result.format, "png");
}
#[test]
fn test_generate_preview_invalid_size() {
let config = QrConfig {
content: "https://example.com".to_string(),
size: 50, // 太小
margin: 4,
error_correction: "M".to_string(),
};
let result = QrCodeService::generate_preview(&config);
assert!(result.is_err());
}
#[test]
fn test_generate_preview_invalid_margin() {
let config = QrConfig {
content: "https://example.com".to_string(),
size: 512,
margin: 100, // 太大
error_correction: "M".to_string(),
};
let result = QrCodeService::generate_preview(&config);
assert!(result.is_err());
}
}