第一次提交
This commit is contained in:
5
backend/app/core/__init__.py
Normal file
5
backend/app/core/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
# Core package
|
||||
from app.core.config import settings
|
||||
from app.core.database import get_db, Base
|
||||
|
||||
__all__ = ["settings", "get_db", "Base"]
|
||||
23
backend/app/core/celery_app.py
Normal file
23
backend/app/core/celery_app.py
Normal file
@@ -0,0 +1,23 @@
|
||||
"""
|
||||
Celery 应用配置
|
||||
"""
|
||||
from celery import Celery
|
||||
from app.core.config import settings
|
||||
|
||||
celery_app = Celery(
|
||||
"aiagent",
|
||||
broker=settings.REDIS_URL,
|
||||
backend=settings.REDIS_URL,
|
||||
include=["app.tasks.workflow_tasks", "app.tasks.agent_tasks"]
|
||||
)
|
||||
|
||||
celery_app.conf.update(
|
||||
task_serializer="json",
|
||||
accept_content=["json"],
|
||||
result_serializer="json",
|
||||
timezone="Asia/Shanghai",
|
||||
enable_utc=True,
|
||||
task_track_started=True,
|
||||
task_time_limit=30 * 60, # 30分钟
|
||||
task_soft_time_limit=25 * 60, # 25分钟
|
||||
)
|
||||
47
backend/app/core/config.py
Normal file
47
backend/app/core/config.py
Normal file
@@ -0,0 +1,47 @@
|
||||
"""
|
||||
应用配置
|
||||
"""
|
||||
from pydantic_settings import BaseSettings
|
||||
from typing import List
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
"""应用设置"""
|
||||
|
||||
# 应用基本信息
|
||||
APP_NAME: str = "低代码智能体平台"
|
||||
APP_VERSION: str = "1.0.0"
|
||||
DEBUG: bool = True
|
||||
SECRET_KEY: str = "dev-secret-key-change-in-production"
|
||||
|
||||
# 数据库配置(MySQL)
|
||||
DATABASE_URL: str = "mysql+pymysql://root:!Rjb12191@gz-cynosdbmysql-grp-d26pzce5.sql.tencentcdb.com:24936/agent_db?charset=utf8mb4"
|
||||
|
||||
# Redis配置
|
||||
REDIS_URL: str = "redis://localhost:6379/0"
|
||||
|
||||
# CORS配置(支持字符串或列表)
|
||||
CORS_ORIGINS: str = "http://localhost:3000,http://127.0.0.1:3000,http://localhost:8038,http://101.43.95.130:8038"
|
||||
|
||||
# OpenAI配置
|
||||
OPENAI_API_KEY: str = ""
|
||||
OPENAI_BASE_URL: str = "https://api.openai.com/v1"
|
||||
|
||||
# DeepSeek配置
|
||||
DEEPSEEK_API_KEY: str = ""
|
||||
DEEPSEEK_BASE_URL: str = "https://api.deepseek.com"
|
||||
|
||||
# Anthropic配置
|
||||
ANTHROPIC_API_KEY: str = ""
|
||||
|
||||
# JWT配置
|
||||
JWT_SECRET_KEY: str = "dev-jwt-secret-key-change-in-production"
|
||||
JWT_ALGORITHM: str = "HS256"
|
||||
JWT_ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
|
||||
|
||||
class Config:
|
||||
env_file = ".env"
|
||||
case_sensitive = True
|
||||
|
||||
|
||||
settings = Settings()
|
||||
45
backend/app/core/database.py
Normal file
45
backend/app/core/database.py
Normal file
@@ -0,0 +1,45 @@
|
||||
"""
|
||||
数据库配置
|
||||
"""
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from app.core.config import settings
|
||||
|
||||
# 创建数据库引擎(MySQL)
|
||||
engine = create_engine(
|
||||
settings.DATABASE_URL,
|
||||
pool_pre_ping=True,
|
||||
pool_size=10,
|
||||
max_overflow=20,
|
||||
echo=settings.DEBUG # 开发环境显示SQL
|
||||
)
|
||||
|
||||
# 创建会话工厂
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
# 创建基础模型类
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
def get_db():
|
||||
"""获取数据库会话"""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def init_db():
|
||||
"""初始化数据库,创建所有表"""
|
||||
# 导入所有模型,确保它们被注册
|
||||
import app.models.user
|
||||
import app.models.workflow
|
||||
import app.models.agent
|
||||
import app.models.execution
|
||||
import app.models.model_config
|
||||
import app.models.workflow_template
|
||||
import app.models.permission
|
||||
import app.models.alert_rule
|
||||
Base.metadata.create_all(bind=engine)
|
||||
75
backend/app/core/error_handler.py
Normal file
75
backend/app/core/error_handler.py
Normal file
@@ -0,0 +1,75 @@
|
||||
"""
|
||||
全局错误处理器
|
||||
"""
|
||||
import logging
|
||||
import traceback
|
||||
from fastapi import Request, status
|
||||
from fastapi.responses import JSONResponse
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from app.core.exceptions import BaseAPIException
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def validation_exception_handler(request: Request, exc: RequestValidationError):
|
||||
"""处理验证错误"""
|
||||
errors = []
|
||||
for error in exc.errors():
|
||||
field = ".".join(str(loc) for loc in error.get("loc", []))
|
||||
errors.append({
|
||||
"field": field,
|
||||
"message": error.get("msg"),
|
||||
"type": error.get("type")
|
||||
})
|
||||
|
||||
logger.warning(f"验证错误: {errors}")
|
||||
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
content={
|
||||
"error": "VALIDATION_ERROR",
|
||||
"message": "请求参数验证失败",
|
||||
"details": errors
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def api_exception_handler(request: Request, exc: BaseAPIException):
|
||||
"""处理自定义API异常"""
|
||||
logger.error(f"API异常: {exc.detail} (错误码: {exc.error_code})")
|
||||
|
||||
return JSONResponse(
|
||||
status_code=exc.status_code,
|
||||
content={
|
||||
"error": exc.error_code or "API_ERROR",
|
||||
"message": exc.detail
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def sqlalchemy_exception_handler(request: Request, exc: SQLAlchemyError):
|
||||
"""处理数据库错误"""
|
||||
logger.error(f"数据库错误: {str(exc)}", exc_info=True)
|
||||
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
content={
|
||||
"error": "DATABASE_ERROR",
|
||||
"message": "数据库操作失败,请稍后重试"
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def general_exception_handler(request: Request, exc: Exception):
|
||||
"""处理通用异常"""
|
||||
logger.error(f"未处理的异常: {str(exc)}", exc_info=True)
|
||||
logger.error(f"异常堆栈: {traceback.format_exc()}")
|
||||
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
content={
|
||||
"error": "INTERNAL_ERROR",
|
||||
"message": "服务器内部错误,请稍后重试"
|
||||
}
|
||||
)
|
||||
86
backend/app/core/exceptions.py
Normal file
86
backend/app/core/exceptions.py
Normal file
@@ -0,0 +1,86 @@
|
||||
"""
|
||||
自定义异常类
|
||||
"""
|
||||
from fastapi import HTTPException, status
|
||||
|
||||
|
||||
class BaseAPIException(HTTPException):
|
||||
"""基础API异常"""
|
||||
def __init__(self, status_code: int, detail: str, error_code: str = None):
|
||||
super().__init__(status_code=status_code, detail=detail)
|
||||
self.error_code = error_code
|
||||
|
||||
|
||||
class ValidationError(BaseAPIException):
|
||||
"""验证错误"""
|
||||
def __init__(self, detail: str):
|
||||
super().__init__(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=detail,
|
||||
error_code="VALIDATION_ERROR"
|
||||
)
|
||||
|
||||
|
||||
class NotFoundError(BaseAPIException):
|
||||
"""资源未找到错误"""
|
||||
def __init__(self, resource: str, resource_id: str = None):
|
||||
detail = f"{resource}不存在"
|
||||
if resource_id:
|
||||
detail += f": {resource_id}"
|
||||
super().__init__(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=detail,
|
||||
error_code="NOT_FOUND"
|
||||
)
|
||||
|
||||
|
||||
class UnauthorizedError(BaseAPIException):
|
||||
"""未授权错误"""
|
||||
def __init__(self, detail: str = "未授权访问"):
|
||||
super().__init__(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail=detail,
|
||||
error_code="UNAUTHORIZED"
|
||||
)
|
||||
|
||||
|
||||
class ForbiddenError(BaseAPIException):
|
||||
"""禁止访问错误"""
|
||||
def __init__(self, detail: str = "无权访问此资源"):
|
||||
super().__init__(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=detail,
|
||||
error_code="FORBIDDEN"
|
||||
)
|
||||
|
||||
|
||||
class ConflictError(BaseAPIException):
|
||||
"""资源冲突错误"""
|
||||
def __init__(self, detail: str):
|
||||
super().__init__(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail=detail,
|
||||
error_code="CONFLICT"
|
||||
)
|
||||
|
||||
|
||||
class InternalServerError(BaseAPIException):
|
||||
"""内部服务器错误"""
|
||||
def __init__(self, detail: str = "服务器内部错误"):
|
||||
super().__init__(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=detail,
|
||||
error_code="INTERNAL_ERROR"
|
||||
)
|
||||
|
||||
|
||||
class WorkflowExecutionError(BaseAPIException):
|
||||
"""工作流执行错误"""
|
||||
def __init__(self, detail: str, node_id: str = None):
|
||||
if node_id:
|
||||
detail = f"节点 {node_id} 执行失败: {detail}"
|
||||
super().__init__(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=detail,
|
||||
error_code="WORKFLOW_EXECUTION_ERROR"
|
||||
)
|
||||
55
backend/app/core/security.py
Normal file
55
backend/app/core/security.py
Normal file
@@ -0,0 +1,55 @@
|
||||
"""
|
||||
安全相关功能:密码加密、JWT等
|
||||
"""
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional
|
||||
from jose import JWTError, jwt
|
||||
import bcrypt
|
||||
from app.core.config import settings
|
||||
|
||||
|
||||
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
||||
"""验证密码"""
|
||||
try:
|
||||
# bcrypt限制密码长度最多72字节
|
||||
password_bytes = plain_password.encode('utf-8')
|
||||
if len(password_bytes) > 72:
|
||||
password_bytes = password_bytes[:72]
|
||||
return bcrypt.checkpw(password_bytes, hashed_password.encode('utf-8'))
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def get_password_hash(password: str) -> str:
|
||||
"""获取密码哈希"""
|
||||
# bcrypt限制密码长度最多72字节
|
||||
password_bytes = password.encode('utf-8')
|
||||
if len(password_bytes) > 72:
|
||||
password_bytes = password_bytes[:72]
|
||||
|
||||
# 生成盐并哈希密码
|
||||
salt = bcrypt.gensalt()
|
||||
hashed = bcrypt.hashpw(password_bytes, salt)
|
||||
return hashed.decode('utf-8')
|
||||
|
||||
|
||||
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
|
||||
"""创建访问令牌"""
|
||||
to_encode = data.copy()
|
||||
if expires_delta:
|
||||
expire = datetime.utcnow() + expires_delta
|
||||
else:
|
||||
expire = datetime.utcnow() + timedelta(minutes=settings.JWT_ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
|
||||
to_encode.update({"exp": expire})
|
||||
encoded_jwt = jwt.encode(to_encode, settings.JWT_SECRET_KEY or settings.SECRET_KEY, algorithm=settings.JWT_ALGORITHM)
|
||||
return encoded_jwt
|
||||
|
||||
|
||||
def decode_access_token(token: str) -> Optional[dict]:
|
||||
"""解码访问令牌"""
|
||||
try:
|
||||
payload = jwt.decode(token, settings.JWT_SECRET_KEY or settings.SECRET_KEY, algorithms=[settings.JWT_ALGORITHM])
|
||||
return payload
|
||||
except JWTError:
|
||||
return None
|
||||
Reference in New Issue
Block a user