190 lines
6.6 KiB
Rust
190 lines
6.6 KiB
Rust
use crate::error::{AppError, ErrorCode};
|
|
|
|
static TRUST_PROXY_HEADERS: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct Config {
|
|
pub role: String,
|
|
pub host: String,
|
|
pub port: u16,
|
|
pub public_base_url: String,
|
|
|
|
pub database_url: String,
|
|
pub database_max_connections: u32,
|
|
|
|
pub redis_url: String,
|
|
|
|
pub worker_concurrency: u32,
|
|
pub image_processing_concurrency: u32,
|
|
|
|
pub jwt_secret: String,
|
|
pub jwt_expiry_hours: i64,
|
|
|
|
pub api_key_pepper: String,
|
|
|
|
pub stripe_secret_key: Option<String>,
|
|
pub stripe_webhook_secret: Option<String>,
|
|
|
|
pub storage_path: String,
|
|
|
|
pub allow_anonymous_upload: bool,
|
|
pub anon_max_file_size_mb: u64,
|
|
pub anon_max_files_per_batch: u32,
|
|
pub anon_daily_units: u32,
|
|
pub anon_retention_hours: u64,
|
|
|
|
pub max_image_pixels: u64,
|
|
pub idempotency_ttl_hours: u64,
|
|
|
|
pub mail_enabled: bool,
|
|
pub mail_log_links_when_disabled: bool,
|
|
pub mail_provider: String,
|
|
pub mail_from: String,
|
|
pub mail_password: String,
|
|
pub mail_from_name: String,
|
|
pub mail_smtp_host: Option<String>,
|
|
pub mail_smtp_port: Option<u16>,
|
|
pub mail_smtp_encryption: Option<String>,
|
|
}
|
|
|
|
impl Config {
|
|
pub fn from_env() -> Result<Self, AppError> {
|
|
let role = env_string("IMAGEFORGE_ROLE").unwrap_or_else(|| "api".to_string());
|
|
let host = env_string("HOST").unwrap_or_else(|| "0.0.0.0".to_string());
|
|
let port = env_u16("PORT").unwrap_or(8080);
|
|
let public_base_url =
|
|
env_string("PUBLIC_BASE_URL").unwrap_or_else(|| "http://localhost:8080".to_string());
|
|
|
|
let database_url = env_string("DATABASE_URL")
|
|
.ok_or_else(|| AppError::new(ErrorCode::InvalidRequest, "缺少环境变量 DATABASE_URL"))?;
|
|
let database_max_connections = env_u32("DATABASE_MAX_CONNECTIONS").unwrap_or(10);
|
|
|
|
let redis_url = env_string("REDIS_URL")
|
|
.ok_or_else(|| AppError::new(ErrorCode::InvalidRequest, "缺少环境变量 REDIS_URL"))?;
|
|
|
|
let worker_concurrency = env_u32("WORKER_CONCURRENCY").unwrap_or_else(|| {
|
|
std::thread::available_parallelism()
|
|
.map(|v| v.get() as u32)
|
|
.unwrap_or(4)
|
|
});
|
|
let image_processing_concurrency = env_u32("IMAGE_PROCESSING_CONCURRENCY")
|
|
.filter(|value| *value > 0)
|
|
.unwrap_or_else(|| {
|
|
std::thread::available_parallelism()
|
|
.map(|v| v.get() as u32)
|
|
.unwrap_or(4)
|
|
});
|
|
|
|
let jwt_secret = env_string("JWT_SECRET")
|
|
.ok_or_else(|| AppError::new(ErrorCode::InvalidRequest, "缺少环境变量 JWT_SECRET"))?;
|
|
let jwt_expiry_hours = env_i64("JWT_EXPIRY_HOURS").unwrap_or(168);
|
|
|
|
let api_key_pepper = env_string("API_KEY_PEPPER").ok_or_else(|| {
|
|
AppError::new(ErrorCode::InvalidRequest, "缺少环境变量 API_KEY_PEPPER")
|
|
})?;
|
|
|
|
let billing_provider =
|
|
env_string("BILLING_PROVIDER").unwrap_or_else(|| "stripe".to_string());
|
|
if !billing_provider.eq_ignore_ascii_case("stripe") {
|
|
return Err(AppError::new(
|
|
ErrorCode::InvalidRequest,
|
|
"BILLING_PROVIDER 目前仅支持 stripe",
|
|
));
|
|
}
|
|
let stripe_secret_key = env_string("STRIPE_SECRET_KEY");
|
|
let stripe_webhook_secret = env_string("STRIPE_WEBHOOK_SECRET");
|
|
|
|
let storage_path = env_string("STORAGE_PATH").unwrap_or_else(|| "./uploads".to_string());
|
|
|
|
let allow_anonymous_upload = env_bool("ALLOW_ANONYMOUS_UPLOAD").unwrap_or(true);
|
|
let anon_max_file_size_mb = env_u64("ANON_MAX_FILE_SIZE_MB").unwrap_or(5);
|
|
let anon_max_files_per_batch = env_u32("ANON_MAX_FILES_PER_BATCH").unwrap_or(5);
|
|
let anon_daily_units = env_u32("ANON_DAILY_UNITS").unwrap_or(10);
|
|
let anon_retention_hours = env_u64("ANON_RETENTION_HOURS").unwrap_or(24);
|
|
|
|
let max_image_pixels = env_u64("MAX_IMAGE_PIXELS").unwrap_or(40_000_000);
|
|
let idempotency_ttl_hours = env_u64("IDEMPOTENCY_TTL_HOURS").unwrap_or(24);
|
|
|
|
let mail_enabled = env_bool("MAIL_ENABLED").unwrap_or(false);
|
|
let mail_log_links_when_disabled =
|
|
env_bool("MAIL_LOG_LINKS_WHEN_DISABLED").unwrap_or(false);
|
|
let mail_provider = env_string("MAIL_PROVIDER").unwrap_or_else(|| "qq".to_string());
|
|
let mail_from =
|
|
env_string("MAIL_FROM").unwrap_or_else(|| "noreply@example.com".to_string());
|
|
let mail_password = env_string("MAIL_PASSWORD").unwrap_or_default();
|
|
let mail_from_name =
|
|
env_string("MAIL_FROM_NAME").unwrap_or_else(|| "ImageForge".to_string());
|
|
let mail_smtp_host = env_string("MAIL_SMTP_HOST");
|
|
let mail_smtp_port = env_u16("MAIL_SMTP_PORT");
|
|
let mail_smtp_encryption = env_string("MAIL_SMTP_ENCRYPTION");
|
|
|
|
Ok(Self {
|
|
role,
|
|
host,
|
|
port,
|
|
public_base_url,
|
|
database_url,
|
|
database_max_connections,
|
|
redis_url,
|
|
worker_concurrency,
|
|
image_processing_concurrency,
|
|
jwt_secret,
|
|
jwt_expiry_hours,
|
|
api_key_pepper,
|
|
stripe_secret_key,
|
|
stripe_webhook_secret,
|
|
storage_path,
|
|
allow_anonymous_upload,
|
|
anon_max_file_size_mb,
|
|
anon_max_files_per_batch,
|
|
anon_daily_units,
|
|
anon_retention_hours,
|
|
max_image_pixels,
|
|
idempotency_ttl_hours,
|
|
mail_enabled,
|
|
mail_log_links_when_disabled,
|
|
mail_provider,
|
|
mail_from,
|
|
mail_password,
|
|
mail_from_name,
|
|
mail_smtp_host,
|
|
mail_smtp_port,
|
|
mail_smtp_encryption,
|
|
})
|
|
}
|
|
}
|
|
|
|
fn env_string(key: &str) -> Option<String> {
|
|
std::env::var(key)
|
|
.ok()
|
|
.filter(|value| !value.trim().is_empty())
|
|
}
|
|
|
|
fn env_u16(key: &str) -> Option<u16> {
|
|
env_string(key).and_then(|v| v.parse::<u16>().ok())
|
|
}
|
|
|
|
fn env_u32(key: &str) -> Option<u32> {
|
|
env_string(key).and_then(|v| v.parse::<u32>().ok())
|
|
}
|
|
|
|
fn env_i64(key: &str) -> Option<i64> {
|
|
env_string(key).and_then(|v| v.parse::<i64>().ok())
|
|
}
|
|
|
|
fn env_u64(key: &str) -> Option<u64> {
|
|
env_string(key).and_then(|v| v.parse::<u64>().ok())
|
|
}
|
|
|
|
fn env_bool(key: &str) -> Option<bool> {
|
|
env_string(key).and_then(|v| match v.trim().to_ascii_lowercase().as_str() {
|
|
"1" | "true" | "yes" | "y" | "on" => Some(true),
|
|
"0" | "false" | "no" | "n" | "off" => Some(false),
|
|
_ => None,
|
|
})
|
|
}
|
|
|
|
pub fn trust_proxy_headers() -> bool {
|
|
*TRUST_PROXY_HEADERS.get_or_init(|| env_bool("TRUST_PROXY_HEADERS").unwrap_or(false))
|
|
}
|