BookSystem/backend/auth/apps/settings/urls.py
jayhgq 19d6db6ecc feat: 添加登录日志和操作日志功能
1. 新增日志相关数据库模型、API路由、前端页面与权限配置
2. 为现有业务接口添加操作日志记录功能
3. 优化导入顺序与权限初始化数据
2026-06-12 22:41:41 +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或key为"系统标题"/"IPC内容"的内容)"""
ALLOWED_IDS = {1, 2, 3}
ALLOWED_KEYS = {"系统标题", "页脚信息", "IPC信息"}
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}"}