Files
ystp/docs/architecture.md

335 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 技术架构设计
> 目标:支撑“网站 + 对外 API + 计费”的商用闭环。产品范围见 `docs/prd.md`,计费口径见 `docs/billing.md`。
## 系统架构图
```
┌───────────────────────────────┐
│ CDN (静态资源/下载可选加速) │
└──────────────┬────────────────┘
┌──────▼──────┐
│ Nginx/Caddy │
│ TLS/反向代理 │
└──────┬──────┘
┌───────────────────────────┼───────────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Web 前端 (Vue3) │ │ API 服务 (Axum) │ │ Admin 前端(Vue3)│
│ 上传/结果/账单 │ │ 认证/计费/接口 │ │ 运营/风控/配置 │
└─────────────────┘ └───────┬─────────┘ └─────────────────┘
┌──────────────────┼──────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ PostgreSQL (DB) │ │ Redis (缓存/队列)│ │ 对象存储 (S3) │
│ 用户/任务/账单/用量│ │ Streams/RateLimit│ │ 原图/压缩结果 │
└─────────┬───────┘ └───────┬─────────┘ └─────────┬───────┘
│ │ │
│ ▼ │
│ ┌───────────────┐ │
│ │ Worker (Rust) │ │
│ │ 压缩/计量/回写 │ │
│ └───────────────┘ │
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ 支付渠道/网关 │◄──────Webhooks───────│ API(Webhook处理) │
│ Stripe │ │ 订阅/发票/状态 │
└─────────────────┘ └─────────────────┘
```
## 核心组件设计
### 0. 服务拆分(推荐)
为避免 CPU 密集的压缩任务影响 API 延迟,建议最小拆分为:
- **API 服务**:认证、限流、计费/订阅、任务编排、回调、签名 URL、管理后台 API。
- **Worker 服务**执行图片压缩CPU 密集)、写入结果、落用量账本、推送进度。
本地开发可以合并进一个进程feature flag生产建议分开部署并可独立扩容。
### 1. 压缩引擎
```rust
// 压缩配置
pub enum CompressionLevel {
/// 高压缩比 - 有损压缩,文件最小
High,
/// 中等压缩 - 平衡模式
Medium,
/// 低压缩比 - 无损/近无损,质量优先
Low,
}
pub struct CompressionConfig {
pub level: CompressionLevel,
pub output_format: Option<ImageFormat>, // 可选转换格式
pub max_width: Option<u32>, // 可选调整尺寸
pub max_height: Option<u32>,
pub preserve_metadata: bool, // 是否保留元数据(默认 false
}
```
### 2. 压缩策略
| 格式 | 高压缩比(有损) | 低压缩比(无损) | 使用库 |
|------|-----------------|-----------------|--------|
| PNG | pngquant 量化到 256 色 | oxipng 无损优化 | `imagequant` + `oxipng` |
| JPEG | mozjpeg quality=60 | mozjpeg quality=90 | `mozjpeg` |
| WebP | lossy quality=75 | lossless | `webp` |
| AVIF | quality=50 | quality=90 | `ravif` |
```rust
// 压缩核心逻辑
pub trait ImageCompressor: Send + Sync {
async fn compress(&self, input: &[u8], config: &CompressionConfig) -> Result<Vec<u8>>;
fn supported_formats(&self) -> Vec<ImageFormat>;
}
pub struct PngCompressor;
pub struct JpegCompressor;
pub struct WebpCompressor;
pub struct AvifCompressor;
// 统一压缩入口
pub struct CompressionEngine {
compressors: HashMap<ImageFormat, Box<dyn ImageCompressor>>,
}
impl CompressionEngine {
pub async fn compress(&self, input: &[u8], config: &CompressionConfig) -> Result<CompressionResult> {
let format = detect_format(input)?;
let compressor = self.compressors.get(&format)
.ok_or(Error::UnsupportedFormat)?;
let output = compressor.compress(input, config).await?;
Ok(CompressionResult {
original_size: input.len(),
compressed_size: output.len(),
format,
data: output,
})
}
}
```
### 3. 任务处理模型
支持两种模式:
#### 同步模式(小文件/单文件)
```
请求 -> 压缩 -> 直接返回结果
```
#### 异步模式(大文件/批量)
```
请求 -> 创建任务 -> 返回任务ID
后台Worker处理
客户端轮询/WebSocket通知
下载结果
```
```rust
pub enum TaskStatus {
Pending,
Processing,
Completed,
Failed,
}
pub struct CompressionTask {
pub id: Uuid,
pub user_id: Option<Uuid>, // 游客为空
pub session_id: Option<String>, // 游客会话Cookie
pub status: TaskStatus,
pub files: Vec<FileTask>,
pub config: CompressionConfig,
pub created_at: DateTime<Utc>,
pub completed_at: Option<DateTime<Utc>>,
}
pub struct FileTask {
pub id: Uuid,
pub original_name: String,
pub original_size: u64,
pub compressed_size: Option<u64>,
pub status: TaskStatus,
pub error: Option<String>,
}
```
### 4. 用户认证系统
```rust
// JWT Claims
#[derive(Serialize, Deserialize)]
pub struct Claims {
pub sub: Uuid, // 用户ID
pub role: UserRole, // 用户角色
pub exp: i64, // 过期时间
}
pub enum UserRole {
User,
Admin,
}
// API Key 认证
pub struct ApiKey {
pub id: Uuid,
pub user_id: Uuid,
pub key_prefix: String, // 前缀索引(仅展示用)
pub key_hash: String, // 推荐HMAC-SHA256(key, server_pepper) 或 sha256+pepper
pub name: String,
pub permissions: Vec<Permission>,
pub rate_limit: u32, // 每分钟请求数
pub created_at: DateTime<Utc>,
pub last_used_at: Option<DateTime<Utc>>,
}
pub enum Permission {
Compress,
BatchCompress,
ReadStats,
BillingRead, // 查看账单/用量(可选)
WebhookManage, // 管理 Webhook可选
}
```
### 5. 限流与配额Rate Limit & Quota
用途区分:
- **限流**:保护服务(超出返回 HTTP `429`
- **配额**:试用/计费限制(超出返回 HTTP `402` / `QUOTA_EXCEEDED`
默认建议(最终以 `system_config` + 套餐 `plans.*` 为准):
- 匿名试用:`10 req/min` + `10 units/day` + `5MB/文件` + `5 文件/批量`
- 登录用户:`60 req/min`;文件大小/批量/保留期/周期额度由套餐决定
- API Key`100 req/min`(可配置);文件大小/批量/周期额度由套餐/Key 覆盖决定
### 6. 用量计量与计费Metering & Billing
计量口径见 `docs/billing.md`,架构上建议:
- Worker 在每个文件成功输出后写入 `usage_events`(账本明细),并更新 `usage_periods`(按订阅周期聚合)。
- API 在创建任务/接收同步压缩请求时做**配额预检**快速失败Worker 做**最终扣减**(账本落地,保证一致性)。
- 对外 API 强烈建议支持 `Idempotency-Key`DB 侧存储“幂等记录 + 响应摘要”,避免重复扣减与重复任务。
### 7. 支付回调Webhooks
Stripe 通常通过 webhook 推送订阅/支付状态变更:
- API 服务提供 `/webhooks/{provider}` 入口,**验签 + 幂等 + 可重放**。
- webhook 事件入库后异步处理(避免回调超时),更新订阅/发票状态,并写审计日志。
## 核心依赖
```toml
[dependencies]
# Web 框架
axum = "0.7"
tokio = { version = "1", features = ["full"] }
tower = "0.4"
tower-http = { version = "0.5", features = ["cors", "fs", "compression"] }
# 图片处理
image = "0.25"
oxipng = "9"
imagequant = "4" # PNG 有损压缩pngquant 核心)
mozjpeg = "0.10" # JPEG 压缩
webp = "0.3" # WebP 编解码
ravif = "0.11" # AVIF 编码
# 数据库
sqlx = { version = "0.7", features = ["runtime-tokio", "postgres", "uuid", "chrono"] }
# Redis
redis = { version = "0.24", features = ["tokio-comp"] }
# 认证
jsonwebtoken = "9"
argon2 = "0.5" # 密码哈希
# 工具
uuid = { version = "1", features = ["v4", "serde"] }
chrono = { version = "0.4", features = ["serde"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
thiserror = "1"
tracing = "0.1"
tracing-subscriber = "0.3"
# 配置
config = "0.14"
dotenvy = "0.15"
```
## 性能考虑
### 1. 并发处理
- 使用 Tokio 异步运行时
- 图片压缩使用 `spawn_blocking` 避免阻塞异步线程
- 可配置 Worker 线程数
```rust
// 在独立线程池中执行 CPU 密集型压缩
let result = tokio::task::spawn_blocking(move || {
compress_image_sync(&data, &config)
}).await??;
```
### 2. 内存管理
- 流式处理大文件
- 限制并发压缩任务数
- 压缩完成后立即清理临时文件
### 3. 缓存策略
- Redis 缓存用户会话
- 可选:相同图片哈希缓存结果(去重)
## 安全考虑
1. **输入验证**:检查文件魔数,不仅依赖扩展名
2. **文件大小限制**:防止 DoS
3. **像素/维度限制**:防止“图片炸弹”(解码后超大)
4. **路径遍历防护**:存储时使用 UUID 命名
5. **SQL 注入防护**使用参数化查询SQLx 自动处理)
6. **XSS 防护**:前端输出转义
7. **CSRF 防护**SameSite Cookie + Token
8. **速率限制**:防止滥用
9. **默认移除元数据**:避免泄露定位/设备信息(除非用户明确开启保留)
## 可扩展性
### 水平扩展
```
┌─────────────┐
│ Load Balancer│
└──────┬──────┘
┌───────────────┼───────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ API 实例1 │ │ API 实例2 │ │ API 实例3 │
└──────────┘ └──────────┘ └──────────┘
│ │ │
└───────────────┼───────────────┘
┌─────────────────┐
│ 共享 PostgreSQL │
│ 共享 Redis │
│ 共享 S3 存储 │
└─────────────────┘
```
### 后续可添加功能
- 消息队列RabbitMQ/NATS处理异步任务
- 分布式任务调度
- CDN 加速下载