```
feat: 添加公司AI管理平台基础架构 添加了完整的FastAPI后端项目结构,包括: - 环境配置文件(.env.example)和项目说明文档(README.md、AGENTS.md) - Dockerfile用于容器化部署 - 核心基础设施:配置管理、数据库连接、调度器、安全认证 - 模块化设计:AI代理、审批流程、审计日志、业务台账等功能模块 - 支持多数据库连接(主库和遗留系统只读库) - AI适配器支持OpenClaw、Hermes、OpenAI兼容接口 - 飞书集成、报表生成、风险监控等企业级功能 - 完整的依赖管理和测试指南 ```
This commit is contained in:
1
app/core/__init__.py
Normal file
1
app/core/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Core infrastructure."""
|
||||
60
app/core/config.py
Normal file
60
app/core/config.py
Normal file
@@ -0,0 +1,60 @@
|
||||
from functools import lru_cache
|
||||
|
||||
from pydantic import Field, field_validator
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
"""Runtime settings loaded from environment variables and `.env`."""
|
||||
|
||||
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8", extra="ignore")
|
||||
|
||||
app_name: str = "Company AI Management Platform"
|
||||
app_env: str = "local"
|
||||
debug: bool = False
|
||||
api_prefix: str = "/api/v1"
|
||||
api_key: str | None = None
|
||||
cors_origins: list[str] = Field(default_factory=lambda: ["*"])
|
||||
|
||||
database_url: str = "mysql+pymysql://root:password@127.0.0.1:3306/company_ai?charset=utf8mb4"
|
||||
legacy_database_url: str | None = None
|
||||
legacy_project_query: str | None = None
|
||||
legacy_project_code_prefix: str = "LEGACY"
|
||||
redis_url: str = "redis://127.0.0.1:6379/0"
|
||||
|
||||
feishu_base_url: str = "https://open.feishu.cn/open-apis"
|
||||
feishu_app_id: str | None = None
|
||||
feishu_app_secret: str | None = None
|
||||
feishu_verification_token: str | None = None
|
||||
feishu_encrypt_key: str | None = None
|
||||
feishu_default_chat_id: str | None = None
|
||||
|
||||
model_provider: str = "noop"
|
||||
openclaw_base_url: str = "http://127.0.0.1:18789"
|
||||
openclaw_api_key: str | None = None
|
||||
hermes_base_url: str = "http://127.0.0.1:8080"
|
||||
hermes_api_key: str | None = None
|
||||
direct_llm_base_url: str = "https://api.openai.com/v1"
|
||||
direct_llm_api_key: str | None = None
|
||||
direct_llm_model: str = "gpt-4.1-mini"
|
||||
|
||||
scheduler_enabled: bool = False
|
||||
daily_brief_cron_hour: int = 9
|
||||
daily_brief_cron_minute: int = 0
|
||||
weekly_project_report_day_of_week: str = "mon"
|
||||
weekly_project_report_cron_hour: int = 9
|
||||
weekly_project_report_cron_minute: int = 30
|
||||
|
||||
@field_validator("cors_origins", mode="before")
|
||||
@classmethod
|
||||
def parse_cors_origins(cls, value: str | list[str]) -> list[str]:
|
||||
if isinstance(value, list):
|
||||
return value
|
||||
return [item.strip() for item in value.split(",") if item.strip()]
|
||||
|
||||
|
||||
@lru_cache
|
||||
def get_settings() -> Settings:
|
||||
"""Return cached application settings."""
|
||||
|
||||
return Settings()
|
||||
51
app/core/database.py
Normal file
51
app/core/database.py
Normal file
@@ -0,0 +1,51 @@
|
||||
from collections.abc import Generator
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import DeclarativeBase, Session, sessionmaker
|
||||
|
||||
from app.core.config import get_settings
|
||||
|
||||
|
||||
class Base(DeclarativeBase):
|
||||
"""Base class for SQLAlchemy ORM models."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
settings = get_settings()
|
||||
|
||||
engine = create_engine(settings.database_url, pool_pre_ping=True, pool_recycle=3600)
|
||||
SessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False, expire_on_commit=False)
|
||||
|
||||
legacy_engine = (
|
||||
create_engine(settings.legacy_database_url, pool_pre_ping=True, pool_recycle=3600)
|
||||
if settings.legacy_database_url
|
||||
else None
|
||||
)
|
||||
LegacySessionLocal = (
|
||||
sessionmaker(bind=legacy_engine, autoflush=False, autocommit=False, expire_on_commit=False)
|
||||
if legacy_engine
|
||||
else None
|
||||
)
|
||||
|
||||
|
||||
def get_db() -> Generator[Session, None, None]:
|
||||
"""Yield an application database session for FastAPI dependencies."""
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def get_legacy_db() -> Generator[Session, None, None]:
|
||||
"""Yield a legacy database session when the legacy connection is configured."""
|
||||
|
||||
if LegacySessionLocal is None:
|
||||
raise RuntimeError("LEGACY_DATABASE_URL is not configured")
|
||||
db = LegacySessionLocal()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
82
app/core/scheduler.py
Normal file
82
app/core/scheduler.py
Normal file
@@ -0,0 +1,82 @@
|
||||
from fastapi import FastAPI
|
||||
|
||||
from app.core.config import get_settings
|
||||
|
||||
|
||||
def attach_scheduler(app: FastAPI) -> None:
|
||||
"""Attach optional APScheduler jobs to the FastAPI application."""
|
||||
|
||||
settings = get_settings()
|
||||
if not settings.scheduler_enabled:
|
||||
return
|
||||
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
|
||||
from app.core.database import SessionLocal
|
||||
from app.modules.reports.service import ReportService
|
||||
|
||||
scheduler = BackgroundScheduler(timezone="Asia/Shanghai")
|
||||
|
||||
def run_daily_brief() -> None:
|
||||
db = SessionLocal()
|
||||
try:
|
||||
report = ReportService(db).daily_brief()
|
||||
app.state.last_daily_brief = report
|
||||
if (
|
||||
settings.feishu_app_id
|
||||
and settings.feishu_app_secret
|
||||
and settings.feishu_default_chat_id
|
||||
):
|
||||
ReportService(db).push_report(
|
||||
report,
|
||||
receive_id=settings.feishu_default_chat_id,
|
||||
receive_id_type="chat_id",
|
||||
actor="scheduler",
|
||||
)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
def run_project_weekly() -> None:
|
||||
db = SessionLocal()
|
||||
try:
|
||||
report = ReportService(db).project_weekly()
|
||||
app.state.last_project_weekly = report
|
||||
if (
|
||||
settings.feishu_app_id
|
||||
and settings.feishu_app_secret
|
||||
and settings.feishu_default_chat_id
|
||||
):
|
||||
ReportService(db).push_report(
|
||||
report,
|
||||
receive_id=settings.feishu_default_chat_id,
|
||||
receive_id_type="chat_id",
|
||||
actor="scheduler",
|
||||
)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
scheduler.add_job(
|
||||
run_daily_brief,
|
||||
trigger="cron",
|
||||
hour=settings.daily_brief_cron_hour,
|
||||
minute=settings.daily_brief_cron_minute,
|
||||
id="daily_brief_push",
|
||||
replace_existing=True,
|
||||
)
|
||||
scheduler.add_job(
|
||||
run_project_weekly,
|
||||
trigger="cron",
|
||||
day_of_week=settings.weekly_project_report_day_of_week,
|
||||
hour=settings.weekly_project_report_cron_hour,
|
||||
minute=settings.weekly_project_report_cron_minute,
|
||||
id="project_weekly_push",
|
||||
replace_existing=True,
|
||||
)
|
||||
|
||||
@app.on_event("startup")
|
||||
def start_scheduler() -> None:
|
||||
scheduler.start()
|
||||
|
||||
@app.on_event("shutdown")
|
||||
def stop_scheduler() -> None:
|
||||
scheduler.shutdown(wait=False)
|
||||
13
app/core/security.py
Normal file
13
app/core/security.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from fastapi import Header, HTTPException, status
|
||||
|
||||
from app.core.config import get_settings
|
||||
|
||||
|
||||
def require_api_key(x_api_key: str | None = Header(default=None)) -> None:
|
||||
"""Validate the optional internal API key header."""
|
||||
|
||||
settings = get_settings()
|
||||
if not settings.api_key:
|
||||
return
|
||||
if x_api_key != settings.api_key:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid API key")
|
||||
Reference in New Issue
Block a user