335 lines
12 KiB
Markdown
335 lines
12 KiB
Markdown
# 技术架构设计
|
||
|
||
> 目标:支撑“网站 + 对外 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 加速下载
|