feat: 添加飞书用户模块和订阅功能支持

- 新增feishu_users模块用于处理飞书用户身份验证和权限管理
- 新增subscriptions模块用于处理订阅相关功能
- 新增personalization模块用于个性化服务
- 在alembic迁移配置中注册新的模型模块
- 在API路由器中添加feishu_users和subscriptions路由
- 实现事件调度服务的改进,包括错误处理和状态更新优化
- 添加飞书命令处理的权限检查机制
- 实现飞书应用票据事件处理
- 改进审计日志记录功能
```
This commit is contained in:
2026-07-27 08:02:17 +08:00
parent db751f03b4
commit d7db84571d
148 changed files with 17110 additions and 765 deletions

View File

@@ -1,6 +1,7 @@
import json
import os
from functools import lru_cache
from typing import Annotated, Any
from typing import Annotated, Any, Literal
from pydantic import Field, field_validator, model_validator
from pydantic_settings import BaseSettings, NoDecode, SettingsConfigDict
@@ -12,11 +13,22 @@ from app.core.constants import (
DEFAULT_OPENCLAW_ACTION_JSON,
)
_DOTENV_DISABLED = os.getenv("COMPANY_AI_DISABLE_DOTENV", "").strip().lower() in {
"1",
"true",
"yes",
"on",
}
class Settings(BaseSettings):
"""Runtime settings loaded from environment variables and `.env`."""
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8", extra="ignore")
model_config = SettingsConfigDict(
env_file=None if _DOTENV_DISABLED else ".env",
env_file_encoding="utf-8",
extra="ignore",
)
app_name: str = "Company AI Management Platform"
app_env: str = "local"
@@ -45,9 +57,14 @@ class Settings(BaseSettings):
feishu_base_url: str = "https://open.feishu.cn/open-apis"
feishu_app_id: str | None = None
feishu_app_secret: str | None = None
feishu_app_type: Literal["self", "store"] = "self"
feishu_app_ticket: str | None = None
feishu_verification_token: str | None = None
feishu_encrypt_key: str | None = None
feishu_default_chat_id: str | None = None
feishu_default_tenant_key: str | None = None
feishu_user_features_enabled: bool = False
feishu_admin_identities: Annotated[list[str], NoDecode] = Field(default_factory=list)
model_provider: str = DEFAULT_MODEL_PROVIDER
openclaw_base_url: str = "http://127.0.0.1:2070"
openclaw_http_url: str | None = None
@@ -109,6 +126,7 @@ class Settings(BaseSettings):
event_dispatch_lock_seconds: int = 300
event_dispatch_cron_minute: str = "*/5"
heartbeat_interval_seconds: int = 60
heartbeat_retention_seconds: int = Field(default=86400, ge=1)
ai_memory_enabled: bool = True
ai_memory_auto_write_enabled: bool = True
ai_memory_recall_limit: int = 5
@@ -135,6 +153,8 @@ class Settings(BaseSettings):
ai_memory_forbidden_keys: list[str] = Field(
default_factory=lambda: [
"authorization",
"app_access_token",
"app_ticket",
"api_key",
"apikey",
"access_token",
@@ -147,6 +167,7 @@ class Settings(BaseSettings):
"direct_llm_api_key",
"market_data_token",
"feishu_app_secret",
"feishu_app_ticket",
"feishu_verification_token",
]
)
@@ -166,11 +187,20 @@ class Settings(BaseSettings):
return [str(item).strip() for item in data if str(item).strip()]
return [item.strip() for item in text.split(",") if item.strip()]
@field_validator(
"feishu_app_type",
mode="before",
)
@classmethod
def normalize_feishu_app_type(cls, value: Any) -> str:
return str(value or "self").strip().lower()
@field_validator(
"openclaw_allowed_tools",
"openclaw_allowed_actions",
"ai_memory_forbidden_keys",
"ai_memory_blocked_content_terms",
"feishu_admin_identities",
mode="before",
)
@classmethod
@@ -240,19 +270,49 @@ class Settings(BaseSettings):
def validate_production_safety(self) -> "Settings":
if self.app_env.lower() not in {"prod", "production"}:
return self
def _enabled_keys(
legacy_key: str | None,
configured_keys: list[dict[str, Any]],
) -> set[str]:
values: set[str] = set()
if legacy_key and legacy_key.strip():
values.add(legacy_key)
for item in configured_keys:
enabled = item.get("enabled", True)
if not isinstance(enabled, bool):
enabled = str(enabled).strip().lower() not in {
"0", "false", "no", "off", "disabled"
}
key = item.get("key")
if enabled and key is not None and str(key).strip():
values.add(str(key))
return values
errors: list[str] = []
api_key_values = _enabled_keys(self.api_key, self.api_keys)
audit_key_values = _enabled_keys(self.audit_api_key, self.audit_api_keys)
if self.database_url.startswith("sqlite"):
errors.append("DATABASE_URL must use PostgreSQL in production")
if not self.api_key and not any(item.get("key") for item in self.api_keys):
if not api_key_values:
errors.append("API_KEY or API_KEYS is required in production")
if not self.audit_api_key and not any(
item.get("key") for item in self.audit_api_keys
):
if not audit_key_values:
errors.append("AUDIT_API_KEY or AUDIT_API_KEYS is required in production")
if api_key_values & audit_key_values:
errors.append(
"API_KEY/API_KEYS and AUDIT_API_KEY/AUDIT_API_KEYS cannot overlap in production"
)
if "*" in self.cors_origins:
errors.append("CORS_ORIGINS cannot contain '*' in production")
if self.debug:
errors.append("DEBUG must be false in production")
if not self.mask_sensitive_responses:
errors.append("MASK_SENSITIVE_RESPONSES must be true in production")
if not self.read_only_mode:
errors.append("READ_ONLY_MODE must be true in production")
if self.feishu_user_features_enabled and not self.feishu_admin_identities:
errors.append(
"FEISHU_ADMIN_IDENTITIES is required when Feishu user features are enabled"
)
if errors:
raise ValueError("; ".join(errors))
return self