BookSystem/backend/auth/apps/settings/urls.py
jayhgq 0545d68018 feat(settings):增加系统Logo系统设置项,并支持根据上传的图片显示系统logo
1. 系统设置中增加系统Logo设置项
2. 系统标题增加logo的显示,并可自动判断,是否上传logo,若上传则显示
3. 完善系统设置功能,支持上传图片,支持设置值为布尔值或列表
4. 增加上传和获取图片的接口
5. 完善nginx,支持图片的接口代理
2026-06-19 23:17:13 +08:00

277 lines
9.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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}"}