feat(saas): Phase 1 — 基础框架与账号管理模块
- 新增 zclaw-saas crate 作为 workspace 成员 - 配置系统 (TOML + 环境变量覆盖) - 错误类型体系 (SaasError 16 变体, IntoResponse) - SQLite 数据库 (12 表 schema, 内存/文件双模式, 3 系统角色种子数据) - JWT 认证 (签发/验证/刷新) - Argon2id 密码哈希 - 认证中间件 (公开/受保护路由分层) - 账号管理 CRUD + API Token 管理 + 操作日志 - 7 单元测试 + 5 集成测试全部通过
This commit is contained in:
144
crates/zclaw-saas/src/config.rs
Normal file
144
crates/zclaw-saas/src/config.rs
Normal file
@@ -0,0 +1,144 @@
|
||||
//! SaaS 服务器配置
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
use secrecy::SecretString;
|
||||
|
||||
/// SaaS 服务器完整配置
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SaaSConfig {
|
||||
pub server: ServerConfig,
|
||||
pub database: DatabaseConfig,
|
||||
pub auth: AuthConfig,
|
||||
pub relay: RelayConfig,
|
||||
}
|
||||
|
||||
/// 服务器配置
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ServerConfig {
|
||||
#[serde(default = "default_host")]
|
||||
pub host: String,
|
||||
#[serde(default = "default_port")]
|
||||
pub port: u16,
|
||||
#[serde(default)]
|
||||
pub cors_origins: Vec<String>,
|
||||
}
|
||||
|
||||
/// 数据库配置
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DatabaseConfig {
|
||||
#[serde(default = "default_db_url")]
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
/// 认证配置
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AuthConfig {
|
||||
#[serde(default = "default_jwt_hours")]
|
||||
pub jwt_expiration_hours: i64,
|
||||
#[serde(default = "default_totp_issuer")]
|
||||
pub totp_issuer: String,
|
||||
}
|
||||
|
||||
/// 中转服务配置
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct RelayConfig {
|
||||
#[serde(default = "default_max_queue")]
|
||||
pub max_queue_size: usize,
|
||||
#[serde(default = "default_max_concurrent")]
|
||||
pub max_concurrent_per_provider: usize,
|
||||
#[serde(default = "default_batch_window")]
|
||||
pub batch_window_ms: u64,
|
||||
#[serde(default = "default_retry_delay")]
|
||||
pub retry_delay_ms: u64,
|
||||
#[serde(default = "default_max_attempts")]
|
||||
pub max_attempts: u32,
|
||||
}
|
||||
|
||||
fn default_host() -> String { "0.0.0.0".into() }
|
||||
fn default_port() -> u16 { 8080 }
|
||||
fn default_db_url() -> String { "sqlite:./saas-data.db".into() }
|
||||
fn default_jwt_hours() -> i64 { 24 }
|
||||
fn default_totp_issuer() -> String { "ZCLAW SaaS".into() }
|
||||
fn default_max_queue() -> usize { 1000 }
|
||||
fn default_max_concurrent() -> usize { 5 }
|
||||
fn default_batch_window() -> u64 { 50 }
|
||||
fn default_retry_delay() -> u64 { 1000 }
|
||||
fn default_max_attempts() -> u32 { 3 }
|
||||
|
||||
impl Default for SaaSConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
server: ServerConfig::default(),
|
||||
database: DatabaseConfig::default(),
|
||||
auth: AuthConfig::default(),
|
||||
relay: RelayConfig::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ServerConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
host: default_host(),
|
||||
port: default_port(),
|
||||
cors_origins: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for DatabaseConfig {
|
||||
fn default() -> Self {
|
||||
Self { url: default_db_url() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AuthConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
jwt_expiration_hours: default_jwt_hours(),
|
||||
totp_issuer: default_totp_issuer(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RelayConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
max_queue_size: default_max_queue(),
|
||||
max_concurrent_per_provider: default_max_concurrent(),
|
||||
batch_window_ms: default_batch_window(),
|
||||
retry_delay_ms: default_retry_delay(),
|
||||
max_attempts: default_max_attempts(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SaaSConfig {
|
||||
/// 加载配置文件,优先级: 环境变量 > ZCLAW_SAAS_CONFIG > ./saas-config.toml
|
||||
pub fn load() -> anyhow::Result<Self> {
|
||||
let config_path = std::env::var("ZCLAW_SAAS_CONFIG")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|_| PathBuf::from("saas-config.toml"));
|
||||
|
||||
let config = if config_path.exists() {
|
||||
let content = std::fs::read_to_string(&config_path)?;
|
||||
toml::from_str(&content)?
|
||||
} else {
|
||||
tracing::warn!("Config file {:?} not found, using defaults", config_path);
|
||||
SaaSConfig::default()
|
||||
};
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
/// 获取 JWT 密钥 (从环境变量或生成默认值)
|
||||
pub fn jwt_secret(&self) -> SecretString {
|
||||
std::env::var("ZCLAW_SAAS_JWT_SECRET")
|
||||
.map(SecretString::from)
|
||||
.unwrap_or_else(|_| {
|
||||
tracing::warn!("ZCLAW_SAAS_JWT_SECRET not set, using default (insecure!)");
|
||||
SecretString::from("zclaw-saas-default-secret-change-in-production".to_string())
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user