初始项目
This commit is contained in:
17
.env.example
Normal file
17
.env.example
Normal file
@@ -0,0 +1,17 @@
|
||||
# 数据库配置
|
||||
DATABASE_URL=postgresql+asyncpg://user:password@localhost:5432/ai_talk_callback_db
|
||||
|
||||
# Redis配置
|
||||
REDIS_URL=redis://localhost:6379/0
|
||||
REDIS_LOCK_TIMEOUT=30
|
||||
REDIS_LOCK_RETRY_DELAY=0.1
|
||||
REDIS_LOCK_MAX_RETRIES=100
|
||||
|
||||
# 业务配置
|
||||
COUNT_THRESHOLD=3
|
||||
EXTERNAL_API_RETRY_MAX=4
|
||||
EXTERNAL_API_URL=https://external.com/api/openapi/customerApi/aiTaskResultFail
|
||||
|
||||
# 应用配置
|
||||
APP_NAME=AI Talk Callback API
|
||||
DEBUG=false
|
||||
1
app/__init__.py
Normal file
1
app/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# AI Talk Callback API Application
|
||||
28
app/config.py
Normal file
28
app/config.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
# 数据库配置
|
||||
database_url: str = "postgresql+asyncpg://user:password@localhost:5432/ai_talk_callback_db"
|
||||
|
||||
# Redis配置
|
||||
redis_url: str = "redis://localhost:6379/0"
|
||||
redis_lock_timeout: int = 30 # 锁超时时间(秒)
|
||||
redis_lock_retry_delay: float = 0.1 # 锁重试延迟(秒)
|
||||
redis_lock_max_retries: int = 100 # 最大重试次数
|
||||
|
||||
# 业务配置
|
||||
count_threshold: int = 3 # count阈值,大于等于此值直接返回
|
||||
external_api_retry_max: int = 4 # 外部API最大重试次数
|
||||
external_api_url: str = "https://external.com/api/openapi/customerApi/aiTaskResultFail"
|
||||
|
||||
# 应用配置
|
||||
app_name: str = "AI Talk Callback API"
|
||||
debug: bool = False
|
||||
|
||||
class Config:
|
||||
env_file = ".env"
|
||||
case_sensitive = False
|
||||
|
||||
|
||||
settings = Settings()
|
||||
62
app/database.py
Normal file
62
app/database.py
Normal file
@@ -0,0 +1,62 @@
|
||||
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
|
||||
from sqlalchemy.orm import DeclarativeBase
|
||||
from sqlalchemy import Column, String, Integer, DateTime, Text, JSON
|
||||
from datetime import datetime
|
||||
from app.config import settings
|
||||
|
||||
|
||||
class Base(DeclarativeBase):
|
||||
pass
|
||||
|
||||
|
||||
class CallbackLog(Base):
|
||||
__tablename__ = "callback_logs"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
site_id = Column(String(100), nullable=False, comment="站点ID") # 新增siteId字段
|
||||
request_url = Column(String(500), nullable=False)
|
||||
request_headers = Column(JSON, nullable=False)
|
||||
request_body = Column(JSON, nullable=False)
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
|
||||
class ExternalApiLog(Base):
|
||||
__tablename__ = "external_api_logs" # 重命名表,更通用
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
request_url = Column(String(500), nullable=False)
|
||||
request_headers = Column(JSON, nullable=False)
|
||||
request_body = Column(JSON, nullable=False)
|
||||
response_status = Column(Integer)
|
||||
response_headers = Column(JSON)
|
||||
response_body = Column(Text)
|
||||
retry_count = Column(Integer, default=0) # 重试次数
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
|
||||
# 创建数据库引擎
|
||||
engine = create_async_engine(
|
||||
settings.database_url,
|
||||
echo=settings.debug,
|
||||
future=True
|
||||
)
|
||||
|
||||
# 创建会话工厂
|
||||
AsyncSessionLocal = async_sessionmaker(
|
||||
engine,
|
||||
class_=AsyncSession,
|
||||
expire_on_commit=False
|
||||
)
|
||||
|
||||
|
||||
async def get_db():
|
||||
async with AsyncSessionLocal() as session:
|
||||
try:
|
||||
yield session
|
||||
finally:
|
||||
await session.close()
|
||||
|
||||
|
||||
async def init_db():
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
63
app/models.py
Normal file
63
app/models.py
Normal file
@@ -0,0 +1,63 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import List, Optional
|
||||
|
||||
|
||||
class NumberData(BaseModel):
|
||||
number: str
|
||||
province: str
|
||||
city: str
|
||||
operator: str
|
||||
|
||||
|
||||
class Group(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
|
||||
|
||||
class Task(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
|
||||
|
||||
class User(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
|
||||
|
||||
class CustomerData(BaseModel):
|
||||
name: str
|
||||
email: str
|
||||
company: Optional[str] = None
|
||||
extra: Optional[str] = None
|
||||
|
||||
|
||||
class CallbackItem(BaseModel):
|
||||
bill: int
|
||||
duration: int
|
||||
callid: str
|
||||
calldate: str
|
||||
number: str
|
||||
numberid: str
|
||||
customer_id: str
|
||||
status: int
|
||||
status_str: str
|
||||
user_id: str
|
||||
type: int
|
||||
number_data: NumberData
|
||||
group: Group
|
||||
task: Task
|
||||
user: User
|
||||
customer_data: CustomerData
|
||||
|
||||
|
||||
class CallbackRequest(BaseModel):
|
||||
count: int = Field(..., ge=0, description="通话失败个数")
|
||||
data: List[CallbackItem]
|
||||
|
||||
|
||||
class CallbackResponse(BaseModel):
|
||||
success: bool
|
||||
message: str
|
||||
processed: bool = False
|
||||
retry_count: int = 0
|
||||
site_id: str # 添加siteId到响应中
|
||||
102
app/redis_lock.py
Normal file
102
app/redis_lock.py
Normal file
@@ -0,0 +1,102 @@
|
||||
import redis.asyncio as redis
|
||||
import asyncio
|
||||
import uuid
|
||||
from typing import Optional
|
||||
from app.config import settings
|
||||
|
||||
|
||||
class RedisLock:
|
||||
def __init__(self, redis_client: redis.Redis, key: str, timeout: int = None):
|
||||
self.redis_client = redis_client
|
||||
self.key = f"lock:{key}"
|
||||
self.timeout = timeout or settings.redis_lock_timeout
|
||||
self.identifier = str(uuid.uuid4())
|
||||
self.acquired = False
|
||||
|
||||
async def acquire(self) -> bool:
|
||||
"""获取分布式锁"""
|
||||
lua_script = """
|
||||
if redis.call("GET", KEYS[1]) == false then
|
||||
return redis.call("SETEX", KEYS[1], ARGV[1], ARGV[2])
|
||||
else
|
||||
return false
|
||||
end
|
||||
"""
|
||||
|
||||
result = await self.redis_client.eval(
|
||||
lua_script,
|
||||
1,
|
||||
self.key,
|
||||
self.timeout,
|
||||
self.identifier
|
||||
)
|
||||
|
||||
self.acquired = bool(result)
|
||||
return self.acquired
|
||||
|
||||
async def release(self) -> bool:
|
||||
"""释放分布式锁"""
|
||||
if not self.acquired:
|
||||
return False
|
||||
|
||||
lua_script = """
|
||||
if redis.call("GET", KEYS[1]) == ARGV[1] then
|
||||
return redis.call("DEL", KEYS[1])
|
||||
else
|
||||
return 0
|
||||
end
|
||||
"""
|
||||
|
||||
result = await self.redis_client.eval(
|
||||
lua_script,
|
||||
1,
|
||||
self.key,
|
||||
self.identifier
|
||||
)
|
||||
|
||||
self.acquired = False
|
||||
return bool(result)
|
||||
|
||||
async def __aenter__(self):
|
||||
"""异步上下文管理器入口"""
|
||||
retries = 0
|
||||
while retries < settings.redis_lock_max_retries:
|
||||
if await self.acquire():
|
||||
return self
|
||||
await asyncio.sleep(settings.redis_lock_retry_delay)
|
||||
retries += 1
|
||||
|
||||
raise TimeoutError(f"Failed to acquire lock {self.key} after {retries} retries")
|
||||
|
||||
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
||||
"""异步上下文管理器出口"""
|
||||
await self.release()
|
||||
|
||||
|
||||
class RedisManager:
|
||||
def __init__(self):
|
||||
self.redis_client: Optional[redis.Redis] = None
|
||||
|
||||
async def connect(self):
|
||||
"""连接Redis"""
|
||||
self.redis_client = redis.from_url(
|
||||
settings.redis_url,
|
||||
encoding="utf-8",
|
||||
decode_responses=True
|
||||
)
|
||||
await self.redis_client.ping()
|
||||
|
||||
async def disconnect(self):
|
||||
"""断开Redis连接"""
|
||||
if self.redis_client:
|
||||
await self.redis_client.close()
|
||||
|
||||
def create_lock(self, key: str, timeout: int = None) -> RedisLock:
|
||||
"""创建分布式锁"""
|
||||
if not self.redis_client:
|
||||
raise RuntimeError("Redis client not connected")
|
||||
return RedisLock(self.redis_client, key, timeout)
|
||||
|
||||
|
||||
# 全局Redis管理器实例
|
||||
redis_manager = RedisManager()
|
||||
187
app/routes.py
Normal file
187
app/routes.py
Normal file
@@ -0,0 +1,187 @@
|
||||
from fastapi import APIRouter, Request, HTTPException, Depends, Path
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
import httpx
|
||||
import asyncio
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
from app.database import get_db, CallbackLog, ExternalApiLog
|
||||
from app.models import CallbackRequest, CallbackResponse
|
||||
from app.redis_lock import redis_manager
|
||||
from app.config import settings
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
async def log_callback_request(
|
||||
db: AsyncSession,
|
||||
request: Request,
|
||||
site_id: str,
|
||||
callback_data: CallbackRequest
|
||||
):
|
||||
"""记录回调请求到数据库"""
|
||||
callback_log = CallbackLog(
|
||||
site_id=site_id, # 记录siteId
|
||||
request_url=str(request.url),
|
||||
request_headers=dict(request.headers),
|
||||
request_body=callback_data.model_dump()
|
||||
)
|
||||
db.add(callback_log)
|
||||
await db.commit()
|
||||
|
||||
|
||||
async def log_external_api_request(
|
||||
db: AsyncSession,
|
||||
request_url: str,
|
||||
request_headers: Dict[str, Any],
|
||||
request_body: Dict[str, Any],
|
||||
response_status: Optional[int],
|
||||
response_headers: Optional[Dict[str, Any]],
|
||||
response_body: Optional[str],
|
||||
retry_count: int = 0
|
||||
):
|
||||
"""记录外部API请求到数据库"""
|
||||
external_api_log = ExternalApiLog(
|
||||
request_url=request_url,
|
||||
request_headers=request_headers,
|
||||
request_body=request_body,
|
||||
response_status=response_status,
|
||||
response_headers=response_headers,
|
||||
response_body=response_body,
|
||||
retry_count=retry_count
|
||||
)
|
||||
db.add(external_api_log)
|
||||
await db.commit()
|
||||
|
||||
|
||||
async def call_external_api_with_retry(
|
||||
db: AsyncSession,
|
||||
request_body: Dict[str, Any],
|
||||
max_retries: int = None
|
||||
) -> tuple[bool, int]:
|
||||
"""
|
||||
调用外部API并支持重试机制
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
request_body: 请求体
|
||||
max_retries: 最大重试次数
|
||||
|
||||
Returns:
|
||||
tuple[是否成功, 实际重试次数]
|
||||
"""
|
||||
if max_retries is None:
|
||||
max_retries = settings.external_api_retry_max
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": "AITalkCallbackService/1.0"
|
||||
}
|
||||
|
||||
for attempt in range(max_retries + 1): # +1 因为第一次不算重试
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=30.0) as client:
|
||||
response = await client.post(
|
||||
settings.external_api_url,
|
||||
headers=headers,
|
||||
json=request_body
|
||||
)
|
||||
|
||||
# 记录每次尝试的结果
|
||||
await log_external_api_request(
|
||||
db=db,
|
||||
request_url=settings.external_api_url,
|
||||
request_headers=headers,
|
||||
request_body=request_body,
|
||||
response_status=response.status_code,
|
||||
response_headers=dict(response.headers),
|
||||
response_body=response.text,
|
||||
retry_count=attempt
|
||||
)
|
||||
|
||||
# 检查响应状态
|
||||
if response.status_code < 400:
|
||||
return True, attempt
|
||||
else:
|
||||
# 如果是最后一次尝试,直接返回失败
|
||||
if attempt == max_retries:
|
||||
return False, attempt
|
||||
# 否则等待一段时间后重试
|
||||
await asyncio.sleep(1 * (attempt + 1)) # 递增延迟
|
||||
|
||||
except httpx.RequestError as e:
|
||||
# 记录网络错误
|
||||
await log_external_api_request(
|
||||
db=db,
|
||||
request_url=settings.external_api_url,
|
||||
request_headers=headers,
|
||||
request_body=request_body,
|
||||
response_status=None,
|
||||
response_headers=None,
|
||||
response_body=f"RequestError: {str(e)}",
|
||||
retry_count=attempt
|
||||
)
|
||||
|
||||
# 如果是最后一次尝试,直接返回失败
|
||||
if attempt == max_retries:
|
||||
return False, attempt
|
||||
# 否则等待一段时间后重试
|
||||
await asyncio.sleep(1 * (attempt + 1)) # 递增延迟
|
||||
|
||||
return False, max_retries
|
||||
|
||||
|
||||
@router.post("/ai-talk/callback/{siteId}", response_model=CallbackResponse)
|
||||
async def ai_talk_callback(
|
||||
callback_data: CallbackRequest,
|
||||
request: Request,
|
||||
siteId: str = Path(..., description="站点ID"),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
AI Talk回调接口处理
|
||||
"""
|
||||
try:
|
||||
# 记录回调请求(包含siteId)
|
||||
await log_callback_request(db, request, siteId, callback_data)
|
||||
|
||||
# 判断count是否大于等于阈值,如果是直接返回
|
||||
if callback_data.count >= settings.count_threshold:
|
||||
return CallbackResponse(
|
||||
success=True,
|
||||
message=f"count={callback_data.count} >= {settings.count_threshold},直接返回",
|
||||
processed=False,
|
||||
retry_count=0,
|
||||
site_id=siteId
|
||||
)
|
||||
|
||||
# count < 3,调用外部API,使用分布式锁防止并发调用
|
||||
async with redis_manager.create_lock(f"external_api_call_{siteId}_{callback_data.count}"):
|
||||
# 调用外部API并支持重试
|
||||
request_body = callback_data.model_dump()
|
||||
success, retry_count = await call_external_api_with_retry(
|
||||
db=db,
|
||||
request_body=request_body,
|
||||
max_retries=settings.external_api_retry_max
|
||||
)
|
||||
|
||||
if success:
|
||||
return CallbackResponse(
|
||||
success=True,
|
||||
message="外部API调用成功",
|
||||
processed=True,
|
||||
retry_count=retry_count,
|
||||
site_id=siteId
|
||||
)
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"外部API调用失败,已重试{retry_count}次"
|
||||
)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"服务器内部错误: {str(e)}"
|
||||
)
|
||||
57
main.py
Normal file
57
main.py
Normal file
@@ -0,0 +1,57 @@
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from app.config import settings
|
||||
from app.database import init_db
|
||||
from app.redis_lock import redis_manager
|
||||
from app.routes import router
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
# 启动时初始化
|
||||
await init_db()
|
||||
await redis_manager.connect()
|
||||
yield
|
||||
# 关闭时清理
|
||||
await redis_manager.disconnect()
|
||||
|
||||
|
||||
app = FastAPI(
|
||||
title=settings.app_name,
|
||||
version="1.0.0",
|
||||
lifespan=lifespan
|
||||
)
|
||||
|
||||
# 添加CORS中间件
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# 注册路由
|
||||
app.include_router(router)
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
return {"message": f"Welcome to {settings.app_name}"}
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
async def health_check():
|
||||
return {"status": "healthy"}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
uvicorn.run(
|
||||
"main:app",
|
||||
host="0.0.0.0",
|
||||
port=8000,
|
||||
reload=settings.debug
|
||||
)
|
||||
12
requirements.txt
Normal file
12
requirements.txt
Normal file
@@ -0,0 +1,12 @@
|
||||
fastapi==0.104.1
|
||||
uvicorn[standard]==0.24.0
|
||||
sqlalchemy==2.0.23
|
||||
asyncpg==0.29.0
|
||||
alembic==1.12.1
|
||||
redis==5.0.1
|
||||
aioredis==2.0.1
|
||||
pydantic==2.5.0
|
||||
pydantic-settings==2.1.0
|
||||
python-multipart==0.0.6
|
||||
httpx==0.25.2
|
||||
python-dotenv==1.0.0
|
||||
167
setup.py
Normal file
167
setup.py
Normal file
@@ -0,0 +1,167 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
数据库初始化脚本
|
||||
用于创建数据库表结构
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
import os
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from app.database import init_db, engine
|
||||
from app.config import settings
|
||||
from sqlalchemy import text
|
||||
|
||||
|
||||
async def create_database():
|
||||
"""创建数据库(如果不存在)"""
|
||||
try:
|
||||
# 从DATABASE_URL中提取数据库名
|
||||
db_url = settings.database_url
|
||||
if "postgresql+asyncpg://" in db_url:
|
||||
# 解析数据库URL获取数据库名
|
||||
# 格式: postgresql+asyncpg://user:password@host:port/dbname
|
||||
import re
|
||||
match = re.match(r'.+/([^/]+)$', db_url)
|
||||
if match:
|
||||
db_name = match.group(1)
|
||||
# 创建不包含数据库名的连接URL
|
||||
base_url = db_url.rsplit('/', 1)[0]
|
||||
|
||||
# 连接到postgres默认数据库创建新数据库
|
||||
from sqlalchemy.ext.asyncio import create_async_engine
|
||||
temp_engine = create_async_engine(f"{base_url}/postgres")
|
||||
|
||||
async with temp_engine.begin() as conn:
|
||||
# 检查数据库是否已存在
|
||||
result = await conn.execute(
|
||||
text("SELECT 1 FROM pg_database WHERE datname = :db_name"),
|
||||
{"db_name": db_name}
|
||||
)
|
||||
exists = result.scalar()
|
||||
|
||||
if not exists:
|
||||
await conn.execute(text(f"CREATE DATABASE {db_name}"))
|
||||
print(f"✅ 数据库 '{db_name}' 创建成功")
|
||||
else:
|
||||
print(f"✅ 数据库 '{db_name}' 已存在")
|
||||
|
||||
await temp_engine.dispose()
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 创建数据库失败: {e}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def init_tables():
|
||||
"""初始化数据库表结构"""
|
||||
try:
|
||||
print("🔄 开始初始化数据库表结构...")
|
||||
|
||||
# 初始化所有表
|
||||
await init_db()
|
||||
|
||||
print("✅ 数据库表结构初始化成功")
|
||||
|
||||
# 验证表是否创建成功
|
||||
async with engine.begin() as conn:
|
||||
result = await conn.execute(text("""
|
||||
SELECT table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'public'
|
||||
ORDER BY table_name
|
||||
"""))
|
||||
tables = [row[0] for row in result.fetchall()]
|
||||
|
||||
print(f"📋 已创建的表: {', '.join(tables)}")
|
||||
|
||||
# 检查必要的表
|
||||
required_tables = ['callback_logs', 'external_api_logs']
|
||||
missing_tables = [table for table in required_tables if table not in tables]
|
||||
|
||||
if missing_tables:
|
||||
print(f"❌ 缺少必要的表: {', '.join(missing_tables)}")
|
||||
return False
|
||||
else:
|
||||
print("✅ 所有必要的表都已创建")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 初始化数据库表结构失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
async def verify_connection():
|
||||
"""验证数据库连接"""
|
||||
try:
|
||||
print("🔄 验证数据库连接...")
|
||||
|
||||
async with engine.begin() as conn:
|
||||
result = await conn.execute(text("SELECT version()"))
|
||||
version = result.scalar()
|
||||
print(f"✅ 数据库连接成功")
|
||||
print(f"📊 PostgreSQL版本: {version}")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 数据库连接失败: {e}")
|
||||
print("\n💡 请检查以下配置:")
|
||||
print(f" - DATABASE_URL: {settings.database_url}")
|
||||
print(" - PostgreSQL服务是否运行")
|
||||
print(" - 用户名密码是否正确")
|
||||
print(" - 网络连接是否正常")
|
||||
return False
|
||||
|
||||
|
||||
async def main():
|
||||
"""主函数"""
|
||||
print("🚀 开始数据库初始化...")
|
||||
print(f"📋 配置信息:")
|
||||
print(f" - 数据库URL: {settings.database_url}")
|
||||
print(f" - Redis URL: {settings.redis_url}")
|
||||
print(f" - 应用名称: {settings.app_name}")
|
||||
print(f" - 外部API URL: {settings.external_api_url}")
|
||||
print()
|
||||
|
||||
# 验证连接
|
||||
if not await verify_connection():
|
||||
sys.exit(1)
|
||||
|
||||
print()
|
||||
|
||||
# 创建数据库(如果需要)
|
||||
if not await create_database():
|
||||
sys.exit(1)
|
||||
|
||||
print()
|
||||
|
||||
# 初始化表结构
|
||||
if not await init_tables():
|
||||
sys.exit(1)
|
||||
|
||||
print()
|
||||
print("🎉 数据库初始化完成!")
|
||||
print("\n📝 下一步:")
|
||||
print(" 1. 配置 .env 文件中的数据库连接信息")
|
||||
print(" 2. 确保 Redis 服务正在运行")
|
||||
print(" 3. 运行应用: python main.py")
|
||||
print(f" 4. 访问API文档: http://localhost:8000/docs")
|
||||
print(f" 5. 接口地址: POST /ai-talk/callback/{{siteId}}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
asyncio.run(main())
|
||||
except KeyboardInterrupt:
|
||||
print("\n⚠️ 用户中断操作")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"\n❌ 初始化过程中发生错误: {e}")
|
||||
sys.exit(1)
|
||||
Reference in New Issue
Block a user