初始化

This commit is contained in:
lhx
2025-09-26 15:58:32 +08:00
commit fe73da9cf5
45 changed files with 1075 additions and 0 deletions

0
app/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

5
app/api/__init__.py Normal file
View File

@@ -0,0 +1,5 @@
from .account import router as account_router
from .database import router as database_router
from .task import router as task_router
__all__ = ["account_router", "database_router", "task_router"]

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

60
app/api/account.py Normal file
View File

@@ -0,0 +1,60 @@
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session
from typing import List
from ..core.database import get_db
from ..schemas.account import (
AccountCreate, AccountUpdate, AccountResponse,
AccountListRequest, AccountGetRequest, AccountUpdateRequest, AccountDeleteRequest
)
from ..services.account import AccountService
router = APIRouter(prefix="/accounts", tags=["账号管理"])
@router.post("/create", response_model=AccountResponse, status_code=status.HTTP_201_CREATED)
def create_account(account: AccountCreate, db: Session = Depends(get_db)):
"""创建账号"""
# 检查账号是否已存在
existing_account = AccountService.get_account_by_account(db, account.account)
if existing_account:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="账号已存在"
)
return AccountService.create_account(db, account)
@router.post("/list", response_model=List[AccountResponse])
def get_accounts(request: AccountListRequest, db: Session = Depends(get_db)):
"""获取账号列表"""
return AccountService.get_accounts(db, skip=request.skip, limit=request.limit)
@router.post("/get", response_model=AccountResponse)
def get_account(request: AccountGetRequest, db: Session = Depends(get_db)):
"""根据ID获取账号"""
account = AccountService.get_account(db, request.account_id)
if not account:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="账号不存在"
)
return account
@router.post("/update", response_model=AccountResponse)
def update_account(request: AccountUpdateRequest, db: Session = Depends(get_db)):
"""更新账号"""
account = AccountService.update_account(db, request.account_id, request.account_data)
if not account:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="账号不存在"
)
return account
@router.post("/delete", status_code=status.HTTP_204_NO_CONTENT)
def delete_account(request: AccountDeleteRequest, db: Session = Depends(get_db)):
"""删除账号"""
if not AccountService.delete_account(db, request.account_id):
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="账号不存在"
)

56
app/api/database.py Normal file
View File

@@ -0,0 +1,56 @@
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session
from typing import List
from ..core.database import get_db
from ..schemas.database import (
SQLExecuteRequest, SQLExecuteResponse, TableDataRequest,
TableDataResponse, CreateTableRequest, ImportDataRequest
)
from ..services.database import DatabaseService
router = APIRouter(prefix="/database", tags=["数据库管理"])
@router.post("/execute", response_model=SQLExecuteResponse)
def execute_sql(request: SQLExecuteRequest, db: Session = Depends(get_db)):
"""执行SQL语句"""
result = DatabaseService.execute_sql(db, request.sql)
return SQLExecuteResponse(**result)
@router.post("/table-data", response_model=TableDataResponse)
def get_table_data(request: TableDataRequest, db: Session = Depends(get_db)):
"""获取表数据"""
result = DatabaseService.get_table_data(
db,
request.table_name,
request.limit or 100,
request.offset or 0
)
return TableDataResponse(**result)
@router.post("/create-table", response_model=SQLExecuteResponse)
def create_table(request: CreateTableRequest, db: Session = Depends(get_db)):
"""创建表"""
result = DatabaseService.create_table(
db,
request.table_name,
request.columns,
request.primary_key
)
return SQLExecuteResponse(**result)
@router.delete("/drop-table/{table_name}", response_model=SQLExecuteResponse)
def drop_table(table_name: str, db: Session = Depends(get_db)):
"""删除表"""
result = DatabaseService.drop_table(db, table_name)
return SQLExecuteResponse(**result)
@router.post("/import-data", response_model=SQLExecuteResponse)
def import_data(request: ImportDataRequest, db: Session = Depends(get_db)):
"""导入数据"""
result = DatabaseService.import_data(db, request.table_name, request.data)
return SQLExecuteResponse(**result)
@router.get("/tables", response_model=List[str])
def get_table_list():
"""获取所有表名"""
return DatabaseService.get_table_list()

187
app/api/task.py Normal file
View File

@@ -0,0 +1,187 @@
from fastapi import APIRouter, HTTPException, status
from typing import List
from ..schemas.task import (
JobResponse, AddCronJobRequest, AddIntervalJobRequest,
AddDateJobRequest, TaskResponse
)
from ..utils.scheduler import task_scheduler, example_task, database_cleanup_task
router = APIRouter(prefix="/tasks", tags=["定时任务管理"])
# 可用的任务函数映射
AVAILABLE_FUNCTIONS = {
"example_task": example_task,
"database_cleanup_task": database_cleanup_task,
}
@router.post("/cron", response_model=TaskResponse)
def add_cron_job(request: AddCronJobRequest):
"""添加cron定时任务"""
try:
if request.func_name not in AVAILABLE_FUNCTIONS:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"函数 {request.func_name} 不可用"
)
func = AVAILABLE_FUNCTIONS[request.func_name]
# 构建cron参数
cron_kwargs = {}
if request.year is not None:
cron_kwargs['year'] = request.year
if request.month is not None:
cron_kwargs['month'] = request.month
if request.day is not None:
cron_kwargs['day'] = request.day
if request.week is not None:
cron_kwargs['week'] = request.week
if request.day_of_week is not None:
cron_kwargs['day_of_week'] = request.day_of_week
if request.hour is not None:
cron_kwargs['hour'] = request.hour
if request.minute is not None:
cron_kwargs['minute'] = request.minute
if request.second is not None:
cron_kwargs['second'] = request.second
job = task_scheduler.add_cron_job(func, request.job_id, **cron_kwargs)
return TaskResponse(
success=True,
message=f"Cron任务 {request.job_id} 添加成功",
data={"job_id": job.id, "next_run": str(job.next_run_time)}
)
except Exception as e:
return TaskResponse(
success=False,
message=f"添加Cron任务失败: {str(e)}"
)
@router.post("/interval", response_model=TaskResponse)
def add_interval_job(request: AddIntervalJobRequest):
"""添加间隔执行任务"""
try:
if request.func_name not in AVAILABLE_FUNCTIONS:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"函数 {request.func_name} 不可用"
)
func = AVAILABLE_FUNCTIONS[request.func_name]
# 构建interval参数
interval_kwargs = {}
if request.seconds is not None:
interval_kwargs['seconds'] = request.seconds
if request.minutes is not None:
interval_kwargs['minutes'] = request.minutes
if request.hours is not None:
interval_kwargs['hours'] = request.hours
if request.days is not None:
interval_kwargs['days'] = request.days
job = task_scheduler.add_interval_job(func, request.job_id, **interval_kwargs)
return TaskResponse(
success=True,
message=f"间隔任务 {request.job_id} 添加成功",
data={"job_id": job.id, "next_run": str(job.next_run_time)}
)
except Exception as e:
return TaskResponse(
success=False,
message=f"添加间隔任务失败: {str(e)}"
)
@router.post("/date", response_model=TaskResponse)
def add_date_job(request: AddDateJobRequest):
"""添加指定时间执行任务"""
try:
if request.func_name not in AVAILABLE_FUNCTIONS:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"函数 {request.func_name} 不可用"
)
func = AVAILABLE_FUNCTIONS[request.func_name]
job = task_scheduler.add_date_job(func, request.job_id, run_date=request.run_date)
return TaskResponse(
success=True,
message=f"定时任务 {request.job_id} 添加成功",
data={"job_id": job.id, "run_date": str(job.next_run_time)}
)
except Exception as e:
return TaskResponse(
success=False,
message=f"添加定时任务失败: {str(e)}"
)
@router.get("/", response_model=List[JobResponse])
def get_jobs():
"""获取所有任务"""
jobs = task_scheduler.get_jobs()
result = []
for job in jobs:
result.append(JobResponse(
id=job.id,
name=job.name,
func=str(job.func),
trigger=str(job.trigger),
next_run_time=job.next_run_time
))
return result
@router.delete("/{job_id}", response_model=TaskResponse)
def remove_job(job_id: str):
"""删除任务"""
success = task_scheduler.remove_job(job_id)
if success:
return TaskResponse(
success=True,
message=f"任务 {job_id} 删除成功"
)
else:
return TaskResponse(
success=False,
message=f"删除任务 {job_id} 失败"
)
@router.put("/{job_id}/pause", response_model=TaskResponse)
def pause_job(job_id: str):
"""暂停任务"""
success = task_scheduler.pause_job(job_id)
if success:
return TaskResponse(
success=True,
message=f"任务 {job_id} 已暂停"
)
else:
return TaskResponse(
success=False,
message=f"暂停任务 {job_id} 失败"
)
@router.put("/{job_id}/resume", response_model=TaskResponse)
def resume_job(job_id: str):
"""恢复任务"""
success = task_scheduler.resume_job(job_id)
if success:
return TaskResponse(
success=True,
message=f"任务 {job_id} 已恢复"
)
else:
return TaskResponse(
success=False,
message=f"恢复任务 {job_id} 失败"
)
@router.get("/functions", response_model=List[str])
def get_available_functions():
"""获取可用的任务函数列表"""
return list(AVAILABLE_FUNCTIONS.keys())

0
app/core/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

18
app/core/config.py Normal file
View File

@@ -0,0 +1,18 @@
import os
from dotenv import load_dotenv
load_dotenv()
class Settings:
DATABASE_URL = os.getenv("DATABASE_URL", "mysql+pymysql://root:password@localhost:3306/tielu_db")
DB_HOST = os.getenv("DB_HOST", "localhost")
DB_PORT = int(os.getenv("DB_PORT", 3306))
DB_USER = os.getenv("DB_USER", "root")
DB_PASSWORD = os.getenv("DB_PASSWORD", "password")
DB_NAME = os.getenv("DB_NAME", "tielu_db")
APP_HOST = os.getenv("APP_HOST", "0.0.0.0")
APP_PORT = int(os.getenv("APP_PORT", 8000))
APP_DEBUG = os.getenv("APP_DEBUG", "True").lower() == "true"
settings = Settings()

20
app/core/database.py Normal file
View File

@@ -0,0 +1,20 @@
from sqlalchemy import create_engine, MetaData
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from .config import settings
engine = create_engine(settings.DATABASE_URL, echo=settings.APP_DEBUG)
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():
"""初始化数据库表"""
Base.metadata.create_all(bind=engine)

95
app/main.py Normal file
View File

@@ -0,0 +1,95 @@
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from contextlib import asynccontextmanager
import logging
from .core.config import settings
from .core.database import init_db
from .api import account_router, database_router, task_router
from .utils.scheduler import task_scheduler
# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
@asynccontextmanager
async def lifespan(app: FastAPI):
"""应用生命周期管理"""
# 启动时执行
logger.info("应用启动中...")
# 初始化数据库
try:
init_db()
logger.info("数据库初始化完成")
except Exception as e:
logger.error(f"数据库初始化失败: {e}")
# 启动定时任务调度器
try:
task_scheduler.start()
logger.info("定时任务调度器启动完成")
except Exception as e:
logger.error(f"定时任务调度器启动失败: {e}")
yield
# 关闭时执行
logger.info("应用关闭中...")
try:
task_scheduler.shutdown()
logger.info("定时任务调度器已关闭")
except Exception as e:
logger.error(f"定时任务调度器关闭失败: {e}")
# 创建FastAPI应用
app = FastAPI(
title="铁路项目管理系统",
description="基于FastAPI、MySQL、SQLAlchemy的铁路项目管理系统",
version="1.0.0",
lifespan=lifespan
)
# 配置CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # 生产环境建议配置具体域名
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# 注册路由
app.include_router(account_router, prefix="/api")
app.include_router(database_router, prefix="/api")
app.include_router(task_router, prefix="/api")
# 根路径
@app.get("/")
async def root():
"""根路径"""
return {
"message": "铁路项目管理系统 API",
"version": "1.0.0",
"docs": "/docs",
"redoc": "/redoc"
}
# 健康检查
@app.get("/health")
async def health_check():
"""健康检查"""
return {
"status": "healthy",
"database": "connected",
"scheduler": "running" if task_scheduler.scheduler.running else "stopped"
}
# 全局异常处理
@app.exception_handler(Exception)
async def global_exception_handler(request, exc):
logger.error(f"全局异常: {str(exc)}")
return HTTPException(
status_code=500,
detail="服务器内部错误"
)

3
app/models/__init__.py Normal file
View File

@@ -0,0 +1,3 @@
from .account import Account
__all__ = ["Account"]

Binary file not shown.

Binary file not shown.

15
app/models/account.py Normal file
View File

@@ -0,0 +1,15 @@
from sqlalchemy import Column, Integer, String, Boolean, DateTime
from sqlalchemy.sql import func
from ..core.database import Base
class Account(Base):
__tablename__ = "accounts"
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
account = Column(String(50), unique=True, index=True, nullable=False, comment="账号")
password = Column(String(100), nullable=False, comment="密码")
status = Column(Integer, default=1, comment="状态: 1-正常, 0-禁用")
today_updated = Column(Integer, default=0, comment="是否更新")
section = Column(String(100), comment="标段")
created_at = Column(DateTime, server_default=func.now(), comment="创建时间")
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now(), comment="更新时间")

14
app/schemas/__init__.py Normal file
View File

@@ -0,0 +1,14 @@
from .account import (
AccountBase, AccountCreate, AccountUpdate, AccountResponse,
AccountListRequest, AccountGetRequest, AccountUpdateRequest, AccountDeleteRequest
)
from .database import SQLExecuteRequest, SQLExecuteResponse, TableDataRequest, TableDataResponse, CreateTableRequest, ImportDataRequest
from .task import JobResponse, AddCronJobRequest, AddIntervalJobRequest, AddDateJobRequest, TaskResponse
__all__ = [
"AccountBase", "AccountCreate", "AccountUpdate", "AccountResponse",
"AccountListRequest", "AccountGetRequest", "AccountUpdateRequest", "AccountDeleteRequest",
"SQLExecuteRequest", "SQLExecuteResponse", "TableDataRequest",
"TableDataResponse", "CreateTableRequest", "ImportDataRequest",
"JobResponse", "AddCronJobRequest", "AddIntervalJobRequest", "AddDateJobRequest", "TaskResponse"
]

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

42
app/schemas/account.py Normal file
View File

@@ -0,0 +1,42 @@
from pydantic import BaseModel
from typing import Optional
from datetime import datetime
class AccountBase(BaseModel):
account: str
password: str
status: Optional[int] = 1
today_updated: Optional[int] = 0
section: Optional[str] = None
class AccountCreate(AccountBase):
pass
class AccountUpdate(BaseModel):
account: Optional[str] = None
password: Optional[str] = None
status: Optional[int] = None
today_updated: Optional[int] = None
section: Optional[str] = None
class AccountResponse(AccountBase):
id: int
created_at: datetime
updated_at: datetime
class Config:
from_attributes = True
class AccountListRequest(BaseModel):
skip: Optional[int] = 0
limit: Optional[int] = 100
class AccountGetRequest(BaseModel):
account_id: int
class AccountUpdateRequest(BaseModel):
account_id: int
account_data: AccountUpdate
class AccountDeleteRequest(BaseModel):
account_id: int

31
app/schemas/database.py Normal file
View File

@@ -0,0 +1,31 @@
from pydantic import BaseModel
from typing import Any, Dict, List, Optional
class SQLExecuteRequest(BaseModel):
sql: str
class SQLExecuteResponse(BaseModel):
success: bool
message: str
data: Optional[Any] = None
rows_affected: Optional[int] = None
class TableDataRequest(BaseModel):
table_name: str
limit: Optional[int] = 100
offset: Optional[int] = 0
class TableDataResponse(BaseModel):
success: bool
message: str
data: Optional[List[Dict[str, Any]]] = None
total_count: Optional[int] = None
class CreateTableRequest(BaseModel):
table_name: str
columns: Dict[str, str] # 列名: 数据类型
primary_key: Optional[str] = None
class ImportDataRequest(BaseModel):
table_name: str
data: List[Dict[str, Any]]

41
app/schemas/task.py Normal file
View File

@@ -0,0 +1,41 @@
from pydantic import BaseModel
from typing import Optional, List, Dict, Any
from datetime import datetime
class JobResponse(BaseModel):
id: str
name: Optional[str] = None
func: str
trigger: str
next_run_time: Optional[datetime] = None
class AddCronJobRequest(BaseModel):
job_id: str
func_name: str
cron_expression: Optional[str] = None
year: Optional[int] = None
month: Optional[int] = None
day: Optional[int] = None
week: Optional[int] = None
day_of_week: Optional[int] = None
hour: Optional[int] = None
minute: Optional[int] = None
second: Optional[int] = None
class AddIntervalJobRequest(BaseModel):
job_id: str
func_name: str
seconds: Optional[int] = None
minutes: Optional[int] = None
hours: Optional[int] = None
days: Optional[int] = None
class AddDateJobRequest(BaseModel):
job_id: str
func_name: str
run_date: datetime
class TaskResponse(BaseModel):
success: bool
message: str
data: Optional[Any] = None

4
app/services/__init__.py Normal file
View File

@@ -0,0 +1,4 @@
from .account import AccountService
from .database import DatabaseService
__all__ = ["AccountService", "DatabaseService"]

Binary file not shown.

Binary file not shown.

Binary file not shown.

51
app/services/account.py Normal file
View File

@@ -0,0 +1,51 @@
from sqlalchemy.orm import Session
from ..models.account import Account
from ..schemas.account import AccountCreate, AccountUpdate
from typing import List, Optional
class AccountService:
@staticmethod
def create_account(db: Session, account_data: AccountCreate) -> Account:
"""创建账号"""
db_account = Account(**account_data.dict())
db.add(db_account)
db.commit()
db.refresh(db_account)
return db_account
@staticmethod
def get_account(db: Session, account_id: int) -> Optional[Account]:
"""根据ID获取账号"""
return db.query(Account).filter(Account.id == account_id).first()
@staticmethod
def get_account_by_account(db: Session, account: str) -> Optional[Account]:
"""根据账号名获取账号"""
return db.query(Account).filter(Account.account == account).first()
@staticmethod
def get_accounts(db: Session, skip: int = 0, limit: int = 100) -> List[Account]:
"""获取账号列表"""
return db.query(Account).offset(skip).limit(limit).all()
@staticmethod
def update_account(db: Session, account_id: int, account_data: AccountUpdate) -> Optional[Account]:
"""更新账号"""
db_account = db.query(Account).filter(Account.id == account_id).first()
if db_account:
update_data = account_data.dict(exclude_unset=True)
for field, value in update_data.items():
setattr(db_account, field, value)
db.commit()
db.refresh(db_account)
return db_account
@staticmethod
def delete_account(db: Session, account_id: int) -> bool:
"""删除账号"""
db_account = db.query(Account).filter(Account.id == account_id).first()
if db_account:
db.delete(db_account)
db.commit()
return True
return False

195
app/services/database.py Normal file
View File

@@ -0,0 +1,195 @@
from sqlalchemy.orm import Session
from sqlalchemy import text, MetaData, Table, Column, create_engine, inspect
from sqlalchemy.exc import SQLAlchemyError
from typing import List, Dict, Any, Optional
from ..core.database import engine
import pandas as pd
class DatabaseService:
@staticmethod
def execute_sql(db: Session, sql: str) -> Dict[str, Any]:
"""执行SQL语句"""
try:
result = db.execute(text(sql))
# 判断是否为SELECT查询
if sql.strip().upper().startswith('SELECT'):
data = []
columns = result.keys()
for row in result:
data.append(dict(zip(columns, row)))
return {
"success": True,
"message": "查询成功",
"data": data
}
else:
# 非SELECT语句提交事务
db.commit()
return {
"success": True,
"message": "执行成功",
"rows_affected": result.rowcount
}
except SQLAlchemyError as e:
db.rollback()
return {
"success": False,
"message": f"SQL执行失败: {str(e)}"
}
except Exception as e:
db.rollback()
return {
"success": False,
"message": f"未知错误: {str(e)}"
}
@staticmethod
def get_table_data(db: Session, table_name: str, limit: int = 100, offset: int = 0) -> Dict[str, Any]:
"""获取表数据"""
try:
# 先检查表是否存在
inspector = inspect(engine)
if table_name not in inspector.get_table_names():
return {
"success": False,
"message": f"{table_name} 不存在"
}
# 获取总数
count_sql = f"SELECT COUNT(*) as total FROM {table_name}"
count_result = db.execute(text(count_sql)).fetchone()
total_count = count_result.total if count_result else 0
# 获取数据
sql = f"SELECT * FROM {table_name} LIMIT {limit} OFFSET {offset}"
result = db.execute(text(sql))
data = []
columns = result.keys()
for row in result:
data.append(dict(zip(columns, row)))
return {
"success": True,
"message": "获取数据成功",
"data": data,
"total_count": total_count
}
except SQLAlchemyError as e:
return {
"success": False,
"message": f"获取表数据失败: {str(e)}"
}
except Exception as e:
return {
"success": False,
"message": f"未知错误: {str(e)}"
}
@staticmethod
def create_table(db: Session, table_name: str, columns: Dict[str, str], primary_key: Optional[str] = None) -> Dict[str, Any]:
"""创建表"""
try:
# 构建CREATE TABLE语句
column_definitions = []
for col_name, col_type in columns.items():
column_definitions.append(f"{col_name} {col_type}")
if primary_key:
column_definitions.append(f"PRIMARY KEY ({primary_key})")
sql = f"CREATE TABLE {table_name} ({', '.join(column_definitions)})"
db.execute(text(sql))
db.commit()
return {
"success": True,
"message": f"{table_name} 创建成功"
}
except SQLAlchemyError as e:
db.rollback()
return {
"success": False,
"message": f"创建表失败: {str(e)}"
}
except Exception as e:
db.rollback()
return {
"success": False,
"message": f"未知错误: {str(e)}"
}
@staticmethod
def drop_table(db: Session, table_name: str) -> Dict[str, Any]:
"""删除表"""
try:
sql = f"DROP TABLE IF EXISTS {table_name}"
db.execute(text(sql))
db.commit()
return {
"success": True,
"message": f"{table_name} 删除成功"
}
except SQLAlchemyError as e:
db.rollback()
return {
"success": False,
"message": f"删除表失败: {str(e)}"
}
except Exception as e:
db.rollback()
return {
"success": False,
"message": f"未知错误: {str(e)}"
}
@staticmethod
def import_data(db: Session, table_name: str, data: List[Dict[str, Any]]) -> Dict[str, Any]:
"""导入数据到表"""
try:
if not data:
return {
"success": False,
"message": "导入数据不能为空"
}
# 使用pandas DataFrame来处理数据导入
df = pd.DataFrame(data)
# 使用pandas的to_sql方法
df.to_sql(table_name, engine, if_exists='append', index=False)
return {
"success": True,
"message": f"成功导入 {len(data)} 条数据到表 {table_name}"
}
except SQLAlchemyError as e:
db.rollback()
return {
"success": False,
"message": f"导入数据失败: {str(e)}"
}
except Exception as e:
db.rollback()
return {
"success": False,
"message": f"未知错误: {str(e)}"
}
@staticmethod
def get_table_list() -> List[str]:
"""获取所有表名"""
try:
inspector = inspect(engine)
return inspector.get_table_names()
except Exception as e:
return []

3
app/utils/__init__.py Normal file
View File

@@ -0,0 +1,3 @@
from .scheduler import task_scheduler, example_task, database_cleanup_task
__all__ = ["task_scheduler", "example_task", "database_cleanup_task"]

Binary file not shown.

Binary file not shown.

124
app/utils/scheduler.py Normal file
View File

@@ -0,0 +1,124 @@
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.executors.pool import ThreadPoolExecutor
from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore
from apscheduler.events import EVENT_JOB_EXECUTED, EVENT_JOB_ERROR
import logging
from ..core.config import settings
# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class TaskScheduler:
def __init__(self):
# 配置作业存储
jobstores = {
'default': SQLAlchemyJobStore(url=settings.DATABASE_URL)
}
# 配置执行器
executors = {
'default': ThreadPoolExecutor(20)
}
# 作业默认配置
job_defaults = {
'coalesce': False,
'max_instances': 3
}
# 创建调度器
self.scheduler = BackgroundScheduler(
jobstores=jobstores,
executors=executors,
job_defaults=job_defaults,
timezone='Asia/Shanghai'
)
# 添加事件监听器
self.scheduler.add_listener(self._job_listener, EVENT_JOB_EXECUTED | EVENT_JOB_ERROR)
def _job_listener(self, event):
"""作业执行监听器"""
if event.exception:
logger.error(f"Job {event.job_id} crashed: {event.exception}")
else:
logger.info(f"Job {event.job_id} executed successfully")
def start(self):
"""启动调度器"""
if not self.scheduler.running:
self.scheduler.start()
logger.info("定时任务调度器已启动")
def shutdown(self):
"""关闭调度器"""
if self.scheduler.running:
self.scheduler.shutdown()
logger.info("定时任务调度器已关闭")
def add_cron_job(self, func, job_id: str, **kwargs):
"""添加cron定时任务"""
return self.scheduler.add_job(func, 'cron', id=job_id, **kwargs)
def add_interval_job(self, func, job_id: str, **kwargs):
"""添加间隔执行任务"""
return self.scheduler.add_job(func, 'interval', id=job_id, **kwargs)
def add_date_job(self, func, job_id: str, **kwargs):
"""添加指定时间执行任务"""
return self.scheduler.add_job(func, 'date', id=job_id, **kwargs)
def remove_job(self, job_id: str):
"""移除任务"""
try:
self.scheduler.remove_job(job_id)
logger.info(f"任务 {job_id} 已移除")
return True
except Exception as e:
logger.error(f"移除任务失败: {e}")
return False
def get_job(self, job_id: str):
"""获取任务"""
return self.scheduler.get_job(job_id)
def get_jobs(self):
"""获取所有任务"""
return self.scheduler.get_jobs()
def pause_job(self, job_id: str):
"""暂停任务"""
try:
self.scheduler.pause_job(job_id)
logger.info(f"任务 {job_id} 已暂停")
return True
except Exception as e:
logger.error(f"暂停任务失败: {e}")
return False
def resume_job(self, job_id: str):
"""恢复任务"""
try:
self.scheduler.resume_job(job_id)
logger.info(f"任务 {job_id} 已恢复")
return True
except Exception as e:
logger.error(f"恢复任务失败: {e}")
return False
# 全局调度器实例
task_scheduler = TaskScheduler()
# 示例定时任务函数
def example_task():
"""示例定时任务"""
logger.info("执行示例定时任务")
# 这里可以添加具体的业务逻辑
return "任务执行完成"
def database_cleanup_task():
"""数据库清理任务示例"""
logger.info("执行数据库清理任务")
# 这里可以添加数据库清理逻辑
return "数据库清理完成"