初始化
This commit is contained in:
12
.env
Normal file
12
.env
Normal file
@@ -0,0 +1,12 @@
|
||||
# 数据库配置
|
||||
DATABASE_URL=mysql+pymysql://railway:Railway01.@8.134.75.237:3309/railway
|
||||
DB_HOST=8.134.75.237
|
||||
DB_PORT=3309
|
||||
DB_USER=railway
|
||||
DB_PASSWORD=Railway01.
|
||||
DB_NAME=railway
|
||||
|
||||
# 应用配置
|
||||
APP_HOST=0.0.0.0
|
||||
APP_PORT=8000
|
||||
APP_DEBUG=True
|
||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
.venv/
|
||||
*.md
|
||||
!README.md
|
||||
75
README.md
Normal file
75
README.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# 铁路项目管理系统
|
||||
|
||||
基于FastAPI、MySQL、SQLAlchemy的铁路项目管理系统。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- **账号管理**: 账号的增删改查,包含id、账号、密码、状态、是否更新、标段等字段
|
||||
- **数据库管理**: 表数据导入导出、SQL执行、建表删表等数据库操作
|
||||
- **定时任务**: 支持cron、间隔、指定时间的定时任务调度
|
||||
|
||||
## 技术栈
|
||||
|
||||
- FastAPI: Web框架
|
||||
- SQLAlchemy: ORM框架
|
||||
- MySQL: 数据库
|
||||
- APScheduler: 定时任务调度
|
||||
- Pandas: 数据处理
|
||||
- Pydantic: 数据验证
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
app/
|
||||
├── api/ # API路由
|
||||
├── core/ # 核心配置
|
||||
├── models/ # 数据库模型
|
||||
├── schemas/ # Pydantic模型
|
||||
├── services/ # 业务逻辑层
|
||||
└── utils/ # 工具类
|
||||
```
|
||||
|
||||
## 安装运行
|
||||
|
||||
1. 安装依赖:
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
2. 配置环境变量:
|
||||
复制 `.env.example` 到 `.env` 并修改配置
|
||||
|
||||
3. 运行应用:
|
||||
```bash
|
||||
python main.py
|
||||
```
|
||||
|
||||
## API文档
|
||||
|
||||
启动后访问 http://localhost:8000/docs 查看API文档
|
||||
|
||||
## 主要API端点
|
||||
|
||||
### 账号管理
|
||||
- `POST /api/accounts/` - 创建账号
|
||||
- `GET /api/accounts/` - 获取账号列表
|
||||
- `GET /api/accounts/{id}` - 获取单个账号
|
||||
- `PUT /api/accounts/{id}` - 更新账号
|
||||
- `DELETE /api/accounts/{id}` - 删除账号
|
||||
|
||||
### 数据库管理
|
||||
- `POST /api/database/execute` - 执行SQL
|
||||
- `POST /api/database/table-data` - 获取表数据
|
||||
- `POST /api/database/create-table` - 创建表
|
||||
- `DELETE /api/database/drop-table/{name}` - 删除表
|
||||
- `POST /api/database/import-data` - 导入数据
|
||||
- `GET /api/database/tables` - 获取表列表
|
||||
|
||||
### 定时任务
|
||||
- `POST /api/tasks/cron` - 添加cron任务
|
||||
- `POST /api/tasks/interval` - 添加间隔任务
|
||||
- `POST /api/tasks/date` - 添加定时任务
|
||||
- `GET /api/tasks/` - 获取任务列表
|
||||
- `DELETE /api/tasks/{id}` - 删除任务
|
||||
- `PUT /api/tasks/{id}/pause` - 暂停任务
|
||||
- `PUT /api/tasks/{id}/resume` - 恢复任务
|
||||
0
app/__init__.py
Normal file
0
app/__init__.py
Normal file
BIN
app/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
app/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
app/__pycache__/main.cpython-312.pyc
Normal file
BIN
app/__pycache__/main.cpython-312.pyc
Normal file
Binary file not shown.
5
app/api/__init__.py
Normal file
5
app/api/__init__.py
Normal 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"]
|
||||
BIN
app/api/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
app/api/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
app/api/__pycache__/account.cpython-312.pyc
Normal file
BIN
app/api/__pycache__/account.cpython-312.pyc
Normal file
Binary file not shown.
BIN
app/api/__pycache__/database.cpython-312.pyc
Normal file
BIN
app/api/__pycache__/database.cpython-312.pyc
Normal file
Binary file not shown.
BIN
app/api/__pycache__/task.cpython-312.pyc
Normal file
BIN
app/api/__pycache__/task.cpython-312.pyc
Normal file
Binary file not shown.
60
app/api/account.py
Normal file
60
app/api/account.py
Normal 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
56
app/api/database.py
Normal 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
187
app/api/task.py
Normal 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
0
app/core/__init__.py
Normal file
BIN
app/core/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
app/core/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
app/core/__pycache__/config.cpython-312.pyc
Normal file
BIN
app/core/__pycache__/config.cpython-312.pyc
Normal file
Binary file not shown.
BIN
app/core/__pycache__/database.cpython-312.pyc
Normal file
BIN
app/core/__pycache__/database.cpython-312.pyc
Normal file
Binary file not shown.
18
app/core/config.py
Normal file
18
app/core/config.py
Normal 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
20
app/core/database.py
Normal 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
95
app/main.py
Normal 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
3
app/models/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .account import Account
|
||||
|
||||
__all__ = ["Account"]
|
||||
BIN
app/models/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
app/models/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
app/models/__pycache__/account.cpython-312.pyc
Normal file
BIN
app/models/__pycache__/account.cpython-312.pyc
Normal file
Binary file not shown.
15
app/models/account.py
Normal file
15
app/models/account.py
Normal 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
14
app/schemas/__init__.py
Normal 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"
|
||||
]
|
||||
BIN
app/schemas/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
app/schemas/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
app/schemas/__pycache__/account.cpython-312.pyc
Normal file
BIN
app/schemas/__pycache__/account.cpython-312.pyc
Normal file
Binary file not shown.
BIN
app/schemas/__pycache__/database.cpython-312.pyc
Normal file
BIN
app/schemas/__pycache__/database.cpython-312.pyc
Normal file
Binary file not shown.
BIN
app/schemas/__pycache__/task.cpython-312.pyc
Normal file
BIN
app/schemas/__pycache__/task.cpython-312.pyc
Normal file
Binary file not shown.
42
app/schemas/account.py
Normal file
42
app/schemas/account.py
Normal 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
31
app/schemas/database.py
Normal 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
41
app/schemas/task.py
Normal 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
4
app/services/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from .account import AccountService
|
||||
from .database import DatabaseService
|
||||
|
||||
__all__ = ["AccountService", "DatabaseService"]
|
||||
BIN
app/services/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
app/services/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
app/services/__pycache__/account.cpython-312.pyc
Normal file
BIN
app/services/__pycache__/account.cpython-312.pyc
Normal file
Binary file not shown.
BIN
app/services/__pycache__/database.cpython-312.pyc
Normal file
BIN
app/services/__pycache__/database.cpython-312.pyc
Normal file
Binary file not shown.
51
app/services/account.py
Normal file
51
app/services/account.py
Normal 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
195
app/services/database.py
Normal 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
3
app/utils/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .scheduler import task_scheduler, example_task, database_cleanup_task
|
||||
|
||||
__all__ = ["task_scheduler", "example_task", "database_cleanup_task"]
|
||||
BIN
app/utils/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
app/utils/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
app/utils/__pycache__/scheduler.cpython-312.pyc
Normal file
BIN
app/utils/__pycache__/scheduler.cpython-312.pyc
Normal file
Binary file not shown.
124
app/utils/scheduler.py
Normal file
124
app/utils/scheduler.py
Normal 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 "数据库清理完成"
|
||||
11
main.py
Normal file
11
main.py
Normal file
@@ -0,0 +1,11 @@
|
||||
import uvicorn
|
||||
from app.main import app
|
||||
from app.core.config import settings
|
||||
|
||||
if __name__ == "__main__":
|
||||
uvicorn.run(
|
||||
"app.main:app",
|
||||
host=settings.APP_HOST,
|
||||
port=settings.APP_PORT,
|
||||
reload=settings.APP_DEBUG
|
||||
)
|
||||
10
requirements.txt
Normal file
10
requirements.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
fastapi==0.104.1
|
||||
uvicorn==0.24.0
|
||||
sqlalchemy==2.0.23
|
||||
pymysql==1.1.0
|
||||
cryptography==41.0.7
|
||||
pydantic==2.5.0
|
||||
python-dotenv==1.0.0
|
||||
apscheduler==3.10.4
|
||||
pandas==2.1.3
|
||||
python-multipart==0.0.6
|
||||
Reference in New Issue
Block a user