392 lines
13 KiB
Python
392 lines
13 KiB
Python
|
|
#!/usr/bin/env python3
|
|||
|
|
# -*- coding: utf-8 -*-
|
|||
|
|
"""
|
|||
|
|
FastAPI接口应用
|
|||
|
|
提供现代化的API接口和自动生成的文档
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import os
|
|||
|
|
import sys
|
|||
|
|
from datetime import datetime, timedelta
|
|||
|
|
from typing import List, Optional
|
|||
|
|
|
|||
|
|
from fastapi import FastAPI, HTTPException, Depends, status
|
|||
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|||
|
|
from pydantic import BaseModel
|
|||
|
|
from sqlalchemy import create_engine, Column, Integer, String, Text, DateTime, Boolean, ForeignKey
|
|||
|
|
from sqlalchemy.orm import declarative_base, sessionmaker, Session
|
|||
|
|
|
|||
|
|
# 添加项目根目录到Python路径
|
|||
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|||
|
|
|
|||
|
|
# 导入配置
|
|||
|
|
from config import Config
|
|||
|
|
|
|||
|
|
# 数据库配置
|
|||
|
|
DATABASE_URL = Config.SQLALCHEMY_DATABASE_URI
|
|||
|
|
engine = create_engine(DATABASE_URL)
|
|||
|
|
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
|||
|
|
Base = declarative_base()
|
|||
|
|
|
|||
|
|
# 创建FastAPI应用
|
|||
|
|
app = FastAPI(
|
|||
|
|
title="KaMiXiTong API",
|
|||
|
|
description="软件授权管理系统的FastAPI接口",
|
|||
|
|
version="1.0.0",
|
|||
|
|
docs_url="/docs",
|
|||
|
|
redoc_url="/redoc"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 添加CORS中间件
|
|||
|
|
app.add_middleware(
|
|||
|
|
CORSMiddleware,
|
|||
|
|
allow_origins=["*"],
|
|||
|
|
allow_credentials=True,
|
|||
|
|
allow_methods=["*"],
|
|||
|
|
allow_headers=["*"],
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 数据模型定义
|
|||
|
|
class APIBase(BaseModel):
|
|||
|
|
api_name: str
|
|||
|
|
description: Optional[str] = None
|
|||
|
|
status: Optional[int] = 1
|
|||
|
|
|
|||
|
|
class Config:
|
|||
|
|
from_attributes = True # Pydantic V2中orm_mode已重命名为from_attributes
|
|||
|
|
|
|||
|
|
class APICreate(APIBase):
|
|||
|
|
api_id: Optional[str] = None
|
|||
|
|
|
|||
|
|
class APIUpdate(APIBase):
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
class APIInDB(APIBase):
|
|||
|
|
api_id: str
|
|||
|
|
create_time: datetime
|
|||
|
|
update_time: datetime
|
|||
|
|
|
|||
|
|
class APIKeyBase(BaseModel):
|
|||
|
|
name: str
|
|||
|
|
description: Optional[str] = None
|
|||
|
|
status: Optional[int] = 1
|
|||
|
|
expire_time: Optional[datetime] = None
|
|||
|
|
|
|||
|
|
class Config:
|
|||
|
|
from_attributes = True # Pydantic V2中orm_mode已重命名为from_attributes
|
|||
|
|
|
|||
|
|
class APIKeyCreate(APIKeyBase):
|
|||
|
|
api_id: str
|
|||
|
|
|
|||
|
|
class APIKeyUpdate(APIKeyBase):
|
|||
|
|
api_id: Optional[str] = None
|
|||
|
|
|
|||
|
|
class APIKeyInDB(APIKeyBase):
|
|||
|
|
id: int
|
|||
|
|
key: str
|
|||
|
|
api_id: str
|
|||
|
|
create_time: datetime
|
|||
|
|
update_time: datetime
|
|||
|
|
|
|||
|
|
class APIVersionBase(BaseModel):
|
|||
|
|
version_num: str
|
|||
|
|
description: Optional[str] = None
|
|||
|
|
publish_status: Optional[int] = 0
|
|||
|
|
|
|||
|
|
class Config:
|
|||
|
|
from_attributes = True # Pydantic V2中orm_mode已重命名为from_attributes
|
|||
|
|
|
|||
|
|
class APIVersionCreate(APIVersionBase):
|
|||
|
|
api_id: str
|
|||
|
|
|
|||
|
|
class APIVersionUpdate(APIVersionBase):
|
|||
|
|
api_id: Optional[str] = None
|
|||
|
|
|
|||
|
|
class APIVersionInDB(APIVersionBase):
|
|||
|
|
id: int
|
|||
|
|
api_id: str
|
|||
|
|
create_time: datetime
|
|||
|
|
update_time: datetime
|
|||
|
|
|
|||
|
|
# 数据库模型
|
|||
|
|
class DBAPI(Base):
|
|||
|
|
__tablename__ = "api"
|
|||
|
|
|
|||
|
|
api_id = Column(String(32), primary_key=True)
|
|||
|
|
api_name = Column(String(64), nullable=False)
|
|||
|
|
description = Column(Text, nullable=True)
|
|||
|
|
status = Column(Integer, nullable=False, default=1)
|
|||
|
|
create_time = Column(DateTime, default=datetime.utcnow)
|
|||
|
|
update_time = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|||
|
|
|
|||
|
|
class DBAPIKey(Base):
|
|||
|
|
__tablename__ = "api_key"
|
|||
|
|
|
|||
|
|
id = Column(Integer, primary_key=True)
|
|||
|
|
key = Column(String(64), nullable=False, unique=True)
|
|||
|
|
api_id = Column(String(32), ForeignKey('api.api_id'), nullable=False)
|
|||
|
|
name = Column(String(64), nullable=False)
|
|||
|
|
description = Column(Text, nullable=True)
|
|||
|
|
status = Column(Integer, nullable=False, default=1)
|
|||
|
|
create_time = Column(DateTime, default=datetime.utcnow)
|
|||
|
|
update_time = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|||
|
|
expire_time = Column(DateTime, nullable=True)
|
|||
|
|
|
|||
|
|
class DBAPIVersion(Base):
|
|||
|
|
__tablename__ = "api_version"
|
|||
|
|
|
|||
|
|
id = Column(Integer, primary_key=True)
|
|||
|
|
version_num = Column(String(32), nullable=False)
|
|||
|
|
api_id = Column(String(32), ForeignKey('api.api_id'), nullable=False)
|
|||
|
|
description = Column(Text, nullable=True)
|
|||
|
|
publish_status = Column(Integer, nullable=False, default=0)
|
|||
|
|
create_time = Column(DateTime, default=datetime.utcnow)
|
|||
|
|
update_time = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|||
|
|
|
|||
|
|
# 依赖项
|
|||
|
|
def get_db():
|
|||
|
|
db = SessionLocal()
|
|||
|
|
try:
|
|||
|
|
yield db
|
|||
|
|
finally:
|
|||
|
|
db.close()
|
|||
|
|
|
|||
|
|
# API路由
|
|||
|
|
@app.get("/")
|
|||
|
|
async def root():
|
|||
|
|
return {"message": "欢迎使用KaMiXiTong FastAPI接口", "version": "1.0.0"}
|
|||
|
|
|
|||
|
|
@app.get("/apis", response_model=List[APIInDB])
|
|||
|
|
async def get_apis(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
|
|||
|
|
"""获取API列表"""
|
|||
|
|
apis = db.query(DBAPI).offset(skip).limit(limit).all()
|
|||
|
|
return apis
|
|||
|
|
|
|||
|
|
@app.post("/apis", response_model=APIInDB)
|
|||
|
|
async def create_api(api: APICreate, db: Session = Depends(get_db)):
|
|||
|
|
"""创建API"""
|
|||
|
|
# 检查自定义ID是否重复
|
|||
|
|
if api.api_id:
|
|||
|
|
existing = db.query(DBAPI).filter(DBAPI.api_id == api.api_id).first()
|
|||
|
|
if existing:
|
|||
|
|
raise HTTPException(status_code=400, detail="API ID已存在")
|
|||
|
|
|
|||
|
|
# 准备API数据
|
|||
|
|
api_data = api.model_dump()
|
|||
|
|
# 处理API ID生成
|
|||
|
|
if api.api_id is None or api.api_id == "":
|
|||
|
|
# 自动生成API ID
|
|||
|
|
import uuid
|
|||
|
|
api_data['api_id'] = f"API_{uuid.uuid4().hex[:8]}".upper()
|
|||
|
|
|
|||
|
|
# 创建API
|
|||
|
|
db_api = DBAPI(**api_data)
|
|||
|
|
db.add(db_api)
|
|||
|
|
db.commit()
|
|||
|
|
db.refresh(db_api)
|
|||
|
|
return db_api
|
|||
|
|
|
|||
|
|
@app.get("/apis/{api_id}", response_model=APIInDB)
|
|||
|
|
async def get_api(api_id: str, db: Session = Depends(get_db)):
|
|||
|
|
"""获取API详情"""
|
|||
|
|
api = db.query(DBAPI).filter(DBAPI.api_id == api_id).first()
|
|||
|
|
if not api:
|
|||
|
|
raise HTTPException(status_code=404, detail="API不存在")
|
|||
|
|
return api
|
|||
|
|
|
|||
|
|
@app.put("/apis/{api_id}", response_model=APIInDB)
|
|||
|
|
async def update_api(api_id: str, api: APIUpdate, db: Session = Depends(get_db)):
|
|||
|
|
"""更新API"""
|
|||
|
|
db_api = db.query(DBAPI).filter(DBAPI.api_id == api_id).first()
|
|||
|
|
if not db_api:
|
|||
|
|
raise HTTPException(status_code=404, detail="API不存在")
|
|||
|
|
|
|||
|
|
for key, value in api.model_dump().items():
|
|||
|
|
setattr(db_api, key, value)
|
|||
|
|
|
|||
|
|
db.commit()
|
|||
|
|
db.refresh(db_api)
|
|||
|
|
return db_api
|
|||
|
|
|
|||
|
|
@app.delete("/apis/{api_id}")
|
|||
|
|
async def delete_api(api_id: str, db: Session = Depends(get_db)):
|
|||
|
|
"""删除API"""
|
|||
|
|
db_api = db.query(DBAPI).filter(DBAPI.api_id == api_id).first()
|
|||
|
|
if not db_api:
|
|||
|
|
raise HTTPException(status_code=404, detail="API不存在")
|
|||
|
|
|
|||
|
|
# 检查是否有关联的密钥
|
|||
|
|
key_count = db.query(DBAPIKey).filter(DBAPIKey.api_id == api_id).count()
|
|||
|
|
if key_count > 0:
|
|||
|
|
raise HTTPException(status_code=400, detail=f"API下还有 {key_count} 个密钥,无法删除")
|
|||
|
|
|
|||
|
|
db.delete(db_api)
|
|||
|
|
db.commit()
|
|||
|
|
return {"message": "API删除成功"}
|
|||
|
|
|
|||
|
|
# API密钥路由
|
|||
|
|
@app.get("/api_keys", response_model=List[APIKeyInDB])
|
|||
|
|
async def get_api_keys(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
|
|||
|
|
"""获取API密钥列表"""
|
|||
|
|
keys = db.query(DBAPIKey).offset(skip).limit(limit).all()
|
|||
|
|
return keys
|
|||
|
|
|
|||
|
|
@app.post("/api_keys", response_model=APIKeyInDB)
|
|||
|
|
async def create_api_key(key: APIKeyCreate, db: Session = Depends(get_db)):
|
|||
|
|
"""生成API密钥"""
|
|||
|
|
# 检查API是否存在
|
|||
|
|
api = db.query(DBAPI).filter(DBAPI.api_id == key.api_id).first()
|
|||
|
|
if not api:
|
|||
|
|
raise HTTPException(status_code=404, detail="指定的API不存在")
|
|||
|
|
|
|||
|
|
# 生成唯一的API密钥
|
|||
|
|
import secrets
|
|||
|
|
import string
|
|||
|
|
characters = string.ascii_letters + string.digits
|
|||
|
|
api_key_value = ''.join(secrets.choice(characters) for _ in range(32))
|
|||
|
|
|
|||
|
|
# 确保密钥唯一
|
|||
|
|
max_attempts = 10
|
|||
|
|
for _ in range(max_attempts):
|
|||
|
|
existing = db.query(DBAPIKey).filter(DBAPIKey.key == api_key_value).first()
|
|||
|
|
if not existing:
|
|||
|
|
break
|
|||
|
|
api_key_value = ''.join(secrets.choice(characters) for _ in range(32))
|
|||
|
|
else:
|
|||
|
|
raise HTTPException(status_code=500, detail="无法生成唯一的API密钥,请稍后重试")
|
|||
|
|
|
|||
|
|
# 准备密钥数据
|
|||
|
|
key_data = key.model_dump()
|
|||
|
|
key_data['key'] = api_key_value
|
|||
|
|
|
|||
|
|
# 创建API密钥
|
|||
|
|
db_key = DBAPIKey(**key_data)
|
|||
|
|
db.add(db_key)
|
|||
|
|
db.commit()
|
|||
|
|
db.refresh(db_key)
|
|||
|
|
return db_key
|
|||
|
|
|
|||
|
|
@app.get("/api_keys/{key_id}", response_model=APIKeyInDB)
|
|||
|
|
async def get_api_key(key_id: int, db: Session = Depends(get_db)):
|
|||
|
|
"""获取API密钥详情"""
|
|||
|
|
key = db.query(DBAPIKey).filter(DBAPIKey.id == key_id).first()
|
|||
|
|
if not key:
|
|||
|
|
raise HTTPException(status_code=404, detail="API密钥不存在")
|
|||
|
|
return key
|
|||
|
|
|
|||
|
|
@app.put("/api_keys/{key_id}", response_model=APIKeyInDB)
|
|||
|
|
async def update_api_key(key_id: int, key: APIKeyUpdate, db: Session = Depends(get_db)):
|
|||
|
|
"""更新API密钥"""
|
|||
|
|
db_key = db.query(DBAPIKey).filter(DBAPIKey.id == key_id).first()
|
|||
|
|
if not db_key:
|
|||
|
|
raise HTTPException(status_code=404, detail="API密钥不存在")
|
|||
|
|
|
|||
|
|
# 如果更新了api_id,检查API是否存在
|
|||
|
|
if key.api_id and key.api_id != db_key.api_id:
|
|||
|
|
api = db.query(DBAPI).filter(DBAPI.api_id == key.api_id).first()
|
|||
|
|
if not api:
|
|||
|
|
raise HTTPException(status_code=404, detail="指定的API不存在")
|
|||
|
|
|
|||
|
|
for field, value in key.model_dump().items():
|
|||
|
|
if value is not None:
|
|||
|
|
setattr(db_key, field, value)
|
|||
|
|
|
|||
|
|
db.commit()
|
|||
|
|
db.refresh(db_key)
|
|||
|
|
return db_key
|
|||
|
|
|
|||
|
|
@app.delete("/api_keys/{key_id}")
|
|||
|
|
async def delete_api_key(key_id: int, db: Session = Depends(get_db)):
|
|||
|
|
"""删除API密钥"""
|
|||
|
|
db_key = db.query(DBAPIKey).filter(DBAPIKey.id == key_id).first()
|
|||
|
|
if not db_key:
|
|||
|
|
raise HTTPException(status_code=404, detail="API密钥不存在")
|
|||
|
|
|
|||
|
|
db.delete(db_key)
|
|||
|
|
db.commit()
|
|||
|
|
return {"message": "API密钥删除成功"}
|
|||
|
|
|
|||
|
|
# API版本路由
|
|||
|
|
@app.get("/api_versions", response_model=List[APIVersionInDB])
|
|||
|
|
async def get_api_versions(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
|
|||
|
|
"""获取API版本列表"""
|
|||
|
|
versions = db.query(DBAPIVersion).offset(skip).limit(limit).all()
|
|||
|
|
return versions
|
|||
|
|
|
|||
|
|
@app.post("/api_versions", response_model=APIVersionInDB)
|
|||
|
|
async def create_api_version(version: APIVersionCreate, db: Session = Depends(get_db)):
|
|||
|
|
"""创建API版本"""
|
|||
|
|
# 检查API是否存在
|
|||
|
|
api = db.query(DBAPI).filter(DBAPI.api_id == version.api_id).first()
|
|||
|
|
if not api:
|
|||
|
|
raise HTTPException(status_code=404, detail="指定的API不存在")
|
|||
|
|
|
|||
|
|
# 检查版本号是否重复
|
|||
|
|
existing = db.query(DBAPIVersion).filter(
|
|||
|
|
DBAPIVersion.api_id == version.api_id,
|
|||
|
|
DBAPIVersion.version_num == version.version_num
|
|||
|
|
).first()
|
|||
|
|
if existing:
|
|||
|
|
raise HTTPException(status_code=400, detail="该API下已存在相同版本号")
|
|||
|
|
|
|||
|
|
# 创建API版本
|
|||
|
|
db_version = DBAPIVersion(**version.model_dump())
|
|||
|
|
db.add(db_version)
|
|||
|
|
db.commit()
|
|||
|
|
db.refresh(db_version)
|
|||
|
|
return db_version
|
|||
|
|
|
|||
|
|
@app.get("/api_versions/{version_id}", response_model=APIVersionInDB)
|
|||
|
|
async def get_api_version(version_id: int, db: Session = Depends(get_db)):
|
|||
|
|
"""获取API版本详情"""
|
|||
|
|
version = db.query(DBAPIVersion).filter(DBAPIVersion.id == version_id).first()
|
|||
|
|
if not version:
|
|||
|
|
raise HTTPException(status_code=404, detail="API版本不存在")
|
|||
|
|
return version
|
|||
|
|
|
|||
|
|
@app.put("/api_versions/{version_id}", response_model=APIVersionInDB)
|
|||
|
|
async def update_api_version(version_id: int, version: APIVersionUpdate, db: Session = Depends(get_db)):
|
|||
|
|
"""更新API版本"""
|
|||
|
|
db_version = db.query(DBAPIVersion).filter(DBAPIVersion.id == version_id).first()
|
|||
|
|
if not db_version:
|
|||
|
|
raise HTTPException(status_code=404, detail="API版本不存在")
|
|||
|
|
|
|||
|
|
# 如果更新了api_id,检查API是否存在
|
|||
|
|
if version.api_id and version.api_id != db_version.api_id:
|
|||
|
|
api = db.query(DBAPI).filter(DBAPI.api_id == version.api_id).first()
|
|||
|
|
if not api:
|
|||
|
|
raise HTTPException(status_code=404, detail="指定的API不存在")
|
|||
|
|
|
|||
|
|
# 如果更新了version_num,检查版本号是否重复(排除自己)
|
|||
|
|
if version.version_num and version.version_num != db_version.version_num:
|
|||
|
|
existing = db.query(DBAPIVersion).filter(
|
|||
|
|
DBAPIVersion.api_id == db_version.api_id,
|
|||
|
|
DBAPIVersion.version_num == version.version_num,
|
|||
|
|
DBAPIVersion.id != version_id
|
|||
|
|
).first()
|
|||
|
|
if existing:
|
|||
|
|
raise HTTPException(status_code=400, detail="该API下已存在相同版本号")
|
|||
|
|
|
|||
|
|
for field, value in version.model_dump().items():
|
|||
|
|
if value is not None:
|
|||
|
|
setattr(db_version, field, value)
|
|||
|
|
|
|||
|
|
db.commit()
|
|||
|
|
db.refresh(db_version)
|
|||
|
|
return db_version
|
|||
|
|
|
|||
|
|
@app.delete("/api_versions/{version_id}")
|
|||
|
|
async def delete_api_version(version_id: int, db: Session = Depends(get_db)):
|
|||
|
|
"""删除API版本"""
|
|||
|
|
db_version = db.query(DBAPIVersion).filter(DBAPIVersion.id == version_id).first()
|
|||
|
|
if not db_version:
|
|||
|
|
raise HTTPException(status_code=404, detail="API版本不存在")
|
|||
|
|
|
|||
|
|
db.delete(db_version)
|
|||
|
|
db.commit()
|
|||
|
|
return {"message": "API版本删除成功"}
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
import uvicorn
|
|||
|
|
# 使用127.0.0.1而不是0.0.0.0来避免权限问题
|
|||
|
|
uvicorn.run(app, host="127.0.0.1", port=9002, log_level="info")
|