Refactor API response models and improve documentation

- Updated response model functions in emoji, expression, jargon, and person routers to include detailed docstrings.
- Enhanced the clarity of function signatures by specifying return types.
- Removed redundant comments and improved code readability.
- Added error handling and logging improvements in various endpoints.
- Deleted outdated code documentation file.
This commit is contained in:
DrSmoothl
2026-04-22 23:33:57 +08:00
parent 1716272b25
commit c0be64f268
7 changed files with 538 additions and 299 deletions

View File

@@ -1,12 +1,7 @@
"""表情包管理 API 路由"""
import asyncio
import hashlib
import io
import os
from datetime import datetime
from pathlib import Path
import re
from typing import Any, Dict, List, Optional
from fastapi import APIRouter, Cookie, HTTPException, Query
@@ -15,6 +10,12 @@ from PIL import Image
from sqlalchemy import func
from sqlmodel import col, select
import asyncio
import hashlib
import io
import os
import re
from src.common.database.database import get_db_session
from src.common.database.database_model import Images, ImageType
from src.webui.core import get_token_manager
@@ -57,7 +58,15 @@ router = APIRouter(prefix="/emoji", tags=["Emoji"])
def _normalize_emoji_description(description: str = "", emotion: str = "") -> str:
"""将上传参数中的描述/情绪标签归一化为可存储 description。"""
"""将上传参数中的描述情绪标签归一化为可存储描述。
Args:
description: 用户输入的表情包描述。
emotion: 用户输入的情绪标签。
Returns:
str: 归一化后的描述字符串。
"""
normalized_description = str(description or "").strip()
normalized_emotion = str(emotion or "").strip()
if normalized_description:
@@ -80,7 +89,21 @@ async def get_emoji_list(
sort_order: Optional[str] = Query("desc", description="排序方向"),
maibot_session: Optional[str] = Cookie(None),
) -> EmojiListResponse:
"""获取表情包列表。"""
"""获取表情包列表。
Args:
page: 页码,从 1 开始。
page_size: 每页数量,范围为 1-100。
search: 搜索关键词,用于匹配描述或哈希。
is_registered: 是否已注册筛选条件。
is_banned: 是否被禁用筛选条件。
sort_by: 排序字段。
sort_order: 排序方向。
maibot_session: WebUI 登录会话 Cookie。
Returns:
EmojiListResponse: 分页后的表情包列表。
"""
try:
verify_auth_token(maibot_session)
@@ -123,13 +146,14 @@ async def get_emoji_list(
if is_banned is not None:
count_statement = count_statement.where(col(Images.is_banned) == is_banned)
total = session.exec(count_statement).one()
data = [emoji_to_response(emoji) for emoji in emojis]
return EmojiListResponse(
success=True,
total=total,
page=page,
page_size=page_size,
data=[emoji_to_response(emoji) for emoji in emojis],
data=data,
)
except HTTPException:
raise
@@ -140,7 +164,15 @@ async def get_emoji_list(
@router.get("/{emoji_id}", response_model=EmojiDetailResponse)
async def get_emoji_detail(emoji_id: int, maibot_session: Optional[str] = Cookie(None)) -> EmojiDetailResponse:
"""获取表情包详细信息。"""
"""获取表情包详细信息。
Args:
emoji_id: 表情包 ID。
maibot_session: WebUI 登录会话 Cookie。
Returns:
EmojiDetailResponse: 表情包详细信息。
"""
try:
verify_auth_token(maibot_session)
@@ -166,7 +198,16 @@ async def update_emoji(
request: EmojiUpdateRequest,
maibot_session: Optional[str] = Cookie(None),
) -> EmojiUpdateResponse:
"""增量更新表情包。"""
"""增量更新表情包。
Args:
emoji_id: 表情包 ID。
request: 只包含需要更新字段的请求数据。
maibot_session: WebUI 登录会话 Cookie。
Returns:
EmojiUpdateResponse: 更新结果和更新后的表情包数据。
"""
try:
verify_auth_token(maibot_session)
@@ -195,8 +236,14 @@ async def update_emoji(
update_data["description"] = normalized_description
update_data.pop("emotion", None)
for field, value in update_data.items():
setattr(emoji, field, value)
if "description" in update_data:
emoji.description = update_data["description"]
if "is_registered" in update_data:
emoji.is_registered = update_data["is_registered"]
if "is_banned" in update_data:
emoji.is_banned = update_data["is_banned"]
if "register_time" in update_data:
emoji.register_time = update_data["register_time"]
session.add(emoji)
logger.info(f"表情包已更新: ID={emoji_id}, 字段: {list(update_data.keys())}")
@@ -215,7 +262,15 @@ async def update_emoji(
@router.delete("/{emoji_id}", response_model=EmojiDeleteResponse)
async def delete_emoji(emoji_id: int, maibot_session: Optional[str] = Cookie(None)) -> EmojiDeleteResponse:
"""删除表情包。"""
"""删除表情包。
Args:
emoji_id: 表情包 ID。
maibot_session: WebUI 登录会话 Cookie。
Returns:
EmojiDeleteResponse: 删除结果。
"""
try:
verify_auth_token(maibot_session)
@@ -242,7 +297,14 @@ async def delete_emoji(emoji_id: int, maibot_session: Optional[str] = Cookie(Non
@router.get("/stats/summary")
async def get_emoji_stats(maibot_session: Optional[str] = Cookie(None)) -> Dict[str, Any]:
"""获取表情包统计数据。"""
"""获取表情包统计数据。
Args:
maibot_session: WebUI 登录会话 Cookie。
Returns:
Dict[str, Any]: 表情包总数、格式分布和高频使用统计。
"""
try:
verify_auth_token(maibot_session)
@@ -312,7 +374,15 @@ async def get_emoji_stats(maibot_session: Optional[str] = Cookie(None)) -> Dict[
@router.post("/{emoji_id}/register", response_model=EmojiUpdateResponse)
async def register_emoji(emoji_id: int, maibot_session: Optional[str] = Cookie(None)) -> EmojiUpdateResponse:
"""注册表情包。"""
"""注册表情包。
Args:
emoji_id: 表情包 ID。
maibot_session: WebUI 登录会话 Cookie。
Returns:
EmojiUpdateResponse: 注册结果和更新后的表情包数据。
"""
try:
verify_auth_token(maibot_session)
@@ -344,7 +414,15 @@ async def register_emoji(emoji_id: int, maibot_session: Optional[str] = Cookie(N
@router.post("/{emoji_id}/ban", response_model=EmojiUpdateResponse)
async def ban_emoji(emoji_id: int, maibot_session: Optional[str] = Cookie(None)) -> EmojiUpdateResponse:
"""禁用表情包。"""
"""禁用表情包。
Args:
emoji_id: 表情包 ID。
maibot_session: WebUI 登录会话 Cookie。
Returns:
EmojiUpdateResponse: 禁用结果和更新后的表情包数据。
"""
try:
verify_auth_token(maibot_session)
@@ -378,7 +456,17 @@ async def get_emoji_thumbnail(
maibot_session: Optional[str] = Cookie(None),
original: bool = Query(False, description="是否返回原图"),
) -> FileResponse | JSONResponse:
"""获取表情包缩略图。"""
"""获取表情包缩略图。
Args:
emoji_id: 表情包 ID。
token: URL 中携带的访问令牌。
maibot_session: WebUI 登录会话 Cookie。
original: 是否返回原图。
Returns:
FileResponse | JSONResponse: 缩略图文件、原图文件或生成中的状态响应。
"""
try:
token_manager = get_token_manager()
is_valid = False
@@ -456,7 +544,15 @@ async def batch_delete_emojis(
request: BatchDeleteRequest,
maibot_session: Optional[str] = Cookie(None),
) -> BatchDeleteResponse:
"""批量删除表情包。"""
"""批量删除表情包。
Args:
request: 包含要删除表情包 ID 列表的请求。
maibot_session: WebUI 登录会话 Cookie。
Returns:
BatchDeleteResponse: 批量删除结果。
"""
try:
verify_auth_token(maibot_session)
@@ -512,7 +608,18 @@ async def upload_emoji(
is_registered: IsRegisteredForm = True,
maibot_session: Optional[str] = Cookie(None),
) -> EmojiUploadResponse:
"""上传并注册表情包。"""
"""上传并注册表情包。
Args:
file: 上传的表情包文件。
description: 表情包描述。
emotion: 情绪标签。
is_registered: 是否上传后直接注册。
maibot_session: WebUI 登录会话 Cookie。
Returns:
EmojiUploadResponse: 上传结果和新表情包数据。
"""
try:
verify_auth_token(maibot_session)
@@ -574,7 +681,6 @@ async def upload_emoji(
full_path=full_path,
image_hash=emoji_hash,
description=final_description,
emotion=None,
query_count=0,
is_registered=is_registered,
is_banned=False,
@@ -605,7 +711,17 @@ async def batch_upload_emoji(
is_registered: IsRegisteredForm = True,
maibot_session: Optional[str] = Cookie(None),
) -> Dict[str, Any]:
"""批量上传表情包。"""
"""批量上传表情包。
Args:
files: 上传的表情包文件列表。
emotion: 批量应用的情绪标签。
is_registered: 是否上传后直接注册。
maibot_session: WebUI 登录会话 Cookie。
Returns:
Dict[str, Any]: 每个文件的上传结果和汇总统计。
"""
try:
verify_auth_token(maibot_session)
@@ -685,7 +801,6 @@ async def batch_upload_emoji(
full_path=full_path,
image_hash=emoji_hash,
description=final_description,
emotion=None,
query_count=0,
is_registered=is_registered,
is_banned=False,
@@ -713,7 +828,14 @@ async def batch_upload_emoji(
@router.get("/thumbnail-cache/stats", response_model=ThumbnailCacheStatsResponse)
async def get_thumbnail_cache_stats(maibot_session: Optional[str] = Cookie(None)) -> ThumbnailCacheStatsResponse:
"""获取缩略图缓存统计信息。"""
"""获取缩略图缓存统计信息。
Args:
maibot_session: WebUI 登录会话 Cookie。
Returns:
ThumbnailCacheStatsResponse: 缩略图缓存数量、大小和覆盖率统计。
"""
try:
verify_auth_token(maibot_session)
@@ -744,7 +866,14 @@ async def get_thumbnail_cache_stats(maibot_session: Optional[str] = Cookie(None)
@router.post("/thumbnail-cache/cleanup", response_model=ThumbnailCleanupResponse)
async def cleanup_thumbnail_cache(maibot_session: Optional[str] = Cookie(None)) -> ThumbnailCleanupResponse:
"""清理孤立的缩略图缓存。"""
"""清理孤立的缩略图缓存。
Args:
maibot_session: WebUI 登录会话 Cookie。
Returns:
ThumbnailCleanupResponse: 清理结果和删除数量。
"""
try:
verify_auth_token(maibot_session)
@@ -767,7 +896,15 @@ async def preheat_thumbnail_cache(
limit: int = Query(100, ge=1, le=1000, description="最多预热数量"),
maibot_session: Optional[str] = Cookie(None),
) -> ThumbnailPreheatResponse:
"""预热缩略图缓存。"""
"""预热缩略图缓存。
Args:
limit: 最多预热的缩略图数量。
maibot_session: WebUI 登录会话 Cookie。
Returns:
ThumbnailPreheatResponse: 预热生成、跳过和失败数量统计。
"""
try:
verify_auth_token(maibot_session)
@@ -783,7 +920,13 @@ async def preheat_thumbnail_cache(
.order_by(col(Images.query_count).desc())
.limit(limit * 2)
)
emojis = session.exec(statement).all()
emojis = [
{
"image_hash": emoji.image_hash,
"full_path": emoji.full_path,
}
for emoji in session.exec(statement).all()
]
generated = 0
skipped = 0
@@ -793,22 +936,25 @@ async def preheat_thumbnail_cache(
if generated >= limit:
break
cache_path = get_thumbnail_cache_path(emoji.image_hash)
image_hash = emoji["image_hash"]
full_path = emoji["full_path"]
cache_path = get_thumbnail_cache_path(image_hash)
if cache_path.exists():
skipped += 1
continue
if not os.path.exists(emoji.full_path):
if not os.path.exists(full_path):
failed += 1
continue
try:
loop = asyncio.get_event_loop()
await loop.run_in_executor(
get_thumbnail_executor(), generate_thumbnail, emoji.full_path, emoji.image_hash
get_thumbnail_executor(), generate_thumbnail, full_path, image_hash
)
generated += 1
except Exception as e:
logger.warning(f"预热缩略图失败 {emoji.image_hash}: {e}")
logger.warning(f"预热缩略图失败 {image_hash}: {e}")
failed += 1
return ThumbnailPreheatResponse(
@@ -827,7 +973,14 @@ async def preheat_thumbnail_cache(
@router.delete("/thumbnail-cache/clear", response_model=ThumbnailCleanupResponse)
async def clear_all_thumbnail_cache(maibot_session: Optional[str] = Cookie(None)) -> ThumbnailCleanupResponse:
"""清空所有缩略图缓存。"""
"""清空所有缩略图缓存。
Args:
maibot_session: WebUI 登录会话 Cookie。
Returns:
ThumbnailCleanupResponse: 清空结果和删除数量。
"""
try:
verify_auth_token(maibot_session)

View File

@@ -1,9 +1,10 @@
import re
from typing import Annotated, List, Optional
from fastapi import File, Form, UploadFile
from pydantic import BaseModel
import re
from src.common.database.database_model import Images
EmojiFile = Annotated[UploadFile, File(description="表情包上传文件")]
@@ -125,13 +126,19 @@ class ThumbnailPreheatResponse(BaseModel):
def emoji_to_response(image: Images) -> EmojiResponse:
"""将表情包模型转换为响应对象。
Args:
image: 数据库中的表情包记录。
Returns:
EmojiResponse: WebUI 可直接序列化的表情包数据。
"""
emotions: list[str] = []
if image.description:
emotions.extend(
item.strip() for item in re.split(r"[,,、;\s]+", image.description) if item and item.strip()
)
if not emotions and image.emotion:
emotions.extend(item.strip() for item in re.split(r"[,,、;\s]+", image.emotion) if item and item.strip())
deduped_emotions: list[str] = []
for item in emotions: