from fastapi import APIRouter, Depends, HTTPException, File, UploadFile, Request from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, and_ from fastcrud import FastCRUD from pathlib import Path import os import shutil from datetime import datetime from database import get_db from models import SystemSetting, User from schemas import ( SystemSettingCreate, SystemSettingUpdate, SystemSettingResponse, ) from middleware import get_current_user from utils.like_filter import ilike_contains from utils.log_utils import get_client_ip, get_user_agent, create_operation_log router = APIRouter(prefix="/settings", tags=["settings"]) # 创建FastCRUD实例 setting_crud = FastCRUD(SystemSetting, SystemSettingResponse) # 图片存储目录 UPLOAD_DIR = Path(__file__).parent.parent.parent / "uploads" UPLOAD_DIR.mkdir(exist_ok=True) @router.post("/", response_model=SystemSettingResponse) async def create_setting( setting_data: SystemSettingCreate, request: Request, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): """创建设置项""" # 检查设置键名是否已存在 result = await db.execute(select(SystemSetting).filter(SystemSetting.key == setting_data.key)) existing_setting = result.scalar_one_or_none() if existing_setting: raise HTTPException(status_code=400, detail="设置键名已存在") # 创建设置实例 db_setting = SystemSetting( key=setting_data.key, value=setting_data.value, type=setting_data.type or "string", options=setting_data.options, description=setting_data.description, ) db.add(db_setting) await db.flush() await db.refresh(db_setting) # 记录操作日志 await create_operation_log( db, current_user.id, current_user.username, "create", "setting", db_setting.id, db_setting.key, {"key": setting_data.key, "type": setting_data.type}, get_client_ip(request), get_user_agent(request), ) await db.commit() return db_setting @router.get("/", response_model=list[SystemSettingResponse]) async def get_settings( request: Request, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): """获取所有设置项""" result = await db.execute(select(SystemSetting)) settings = result.scalars().all() # 记录查询日志 await create_operation_log( db, current_user.id, current_user.username, "search", "setting", None, None, None, get_client_ip(request), get_user_agent(request), ) await db.commit() return [SystemSettingResponse.model_validate(s) for s in settings] @router.get("/public", response_model=SystemSettingResponse) async def get_public_setting( setting_id: int = None, key: str = None, db: AsyncSession = Depends(get_db), ): """公开接口:获取系统设置(允许获取id为1/2/3或key为"系统标题"/"页脚信息"/"IPC信息"/"系统Logo"的内容)""" ALLOWED_IDS = {1, 2, 3, 4, 5, 6} ALLOWED_KEYS = {"系统标题", "页脚信息", "IPC信息", "系统Logo"} if setting_id is not None: if setting_id not in ALLOWED_IDS: raise HTTPException(status_code=403, detail="无权访问该设置项") result = await db.execute(select(SystemSetting).filter(SystemSetting.id == setting_id)) elif key is not None: if key not in ALLOWED_KEYS: raise HTTPException(status_code=403, detail="无权访问该设置项") result = await db.execute(select(SystemSetting).filter(SystemSetting.key == key)) else: raise HTTPException(status_code=400, detail="必须提供setting_id或key参数") setting = result.scalar_one_or_none() if not setting: raise HTTPException(status_code=404, detail="设置项不存在") return setting @router.get("/{setting_id}", response_model=SystemSettingResponse) async def get_setting( setting_id: int, request: Request, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): """获取单个设置项""" setting = await setting_crud.get(db, id=setting_id) if not setting: raise HTTPException(status_code=404, detail="设置项不存在") # 记录查询日志 await create_operation_log( db, current_user.id, current_user.username, "search", "setting", setting_id, setting.key, None, get_client_ip(request), get_user_agent(request), ) await db.commit() return setting @router.get("/key/{key}", response_model=SystemSettingResponse) async def get_setting_by_key( key: str, request: Request, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): """根据键名获取设置项""" result = await db.execute(select(SystemSetting).filter(SystemSetting.key == key)) setting = result.scalar_one_or_none() if not setting: raise HTTPException(status_code=404, detail="设置项不存在") # 记录查询日志 await create_operation_log( db, current_user.id, current_user.username, "search", "setting", setting.id, setting.key, None, get_client_ip(request), get_user_agent(request), ) await db.commit() return setting @router.put("/{setting_id}", response_model=SystemSettingResponse) async def update_setting( setting_id: int, setting_data: SystemSettingUpdate, request: Request, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): """更新设置项""" # 获取原设置数据 result = await db.execute(select(SystemSetting).filter(SystemSetting.id == setting_id)) old_setting = result.scalar_one_or_none() if not old_setting: raise HTTPException(status_code=404, detail="设置项不存在") # 检查设置键名是否被其他设置使用 if setting_data.key: result = await db.execute( select(SystemSetting).filter(SystemSetting.key == setting_data.key, SystemSetting.id != setting_id) ) existing_setting = result.scalar_one_or_none() if existing_setting: raise HTTPException(status_code=400, detail="设置键名已存在") # 准备更新数据 update_data = setting_data.model_dump(exclude_unset=True) # 使用FastCRUD更新设置 updated_setting = await setting_crud.update( db, update_data, id=setting_id, schema_to_select=SystemSettingResponse, return_as_model=True ) if not updated_setting: raise HTTPException(status_code=404, detail="设置项不存在") # 记录操作日志 changed = {} for field in ["key", "value", "type", "description"]: old_val = getattr(old_setting, field, None) new_val = update_data.get(field) if field in update_data else old_val if old_val != new_val: changed[field] = {"old": old_val, "new": new_val} await create_operation_log( db, current_user.id, current_user.username, "update", "setting", setting_id, old_setting.key, changed if changed else None, get_client_ip(request), get_user_agent(request), ) await db.commit() return updated_setting @router.delete("/{setting_id}") async def delete_setting( setting_id: int, request: Request, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): """删除设置项""" # 检查设置是否存在 result = await db.execute(select(SystemSetting).filter(SystemSetting.id == setting_id)) existing_setting = result.scalar_one_or_none() if not existing_setting: raise HTTPException(status_code=404, detail="设置项不存在") # 记录操作日志(删除前) await create_operation_log( db, current_user.id, current_user.username, "delete", "setting", setting_id, existing_setting.key, {"key": existing_setting.key, "value": existing_setting.value}, get_client_ip(request), get_user_agent(request), ) # 使用FastCRUD删除设置 deleted = await setting_crud.delete(db, id=setting_id) return {"message": "设置项删除成功"} @router.post("/upload") async def upload_image( file: UploadFile = File(...), current_user: User = Depends(get_current_user), ): """上传图片""" # 检查文件类型 if not file.filename or not file.filename.lower().endswith(('.png', '.jpg', '.jpeg', '.gif', '.webp')): raise HTTPException(status_code=400, detail="只支持图片格式(png, jpg, jpeg, gif, webp)") # 检查文件大小(2MB) file_size = 0 chunk = await file.read(1024) while chunk: file_size += len(chunk) if file_size > 2 * 1024 * 1024: raise HTTPException(status_code=400, detail="文件大小不能超过2MB") chunk = await file.read(1024) # 重置文件指针 await file.seek(0) # 生成文件名 timestamp = datetime.now().strftime("%Y%m%d%H%M%S") ext = file.filename.split('.')[-1] filename = f"setting_{timestamp}.{ext}" file_path = UPLOAD_DIR / filename # 保存文件 with open(file_path, "wb") as buffer: shutil.copyfileobj(file.file, buffer) # 返回文件路径 return {"url": f"/uploads/{filename}"}