Implement compression quota refunds and admin manual subscription

This commit is contained in:
2025-12-19 23:28:32 +08:00
commit 11f48fd3dd
106 changed files with 27848 additions and 0 deletions

334
docs/architecture.md Normal file
View File

@@ -0,0 +1,334 @@
# 技术架构设计
> 目标:支撑“网站 + 对外 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 加速下载