Files
MuMuAINovel/backend/app/api/changelog.py
T

240 lines
7.1 KiB
Python

"""
更新日志API
提供GitHub提交历史的缓存和代理服务
"""
from fastapi import APIRouter, HTTPException, Query, Request, Depends
from typing import List, Optional
import httpx
from datetime import datetime, timedelta
from pydantic import BaseModel
import logging
logger = logging.getLogger(__name__)
router = APIRouter()
def require_login(request: Request):
if not hasattr(request.state, "user") or not request.state.user:
raise HTTPException(status_code=401, detail="需要登录")
return request.state.user
# GitHub API配置
GITHUB_API_BASE = "https://api.github.com"
REPO_OWNER = "xiamuceer-j"
REPO_NAME = "MuMuAINovel"
# 缓存配置
_cache = {
"data": None,
"timestamp": None,
"ttl": timedelta(hours=1) # 缓存1小时
}
class GitHubAuthor(BaseModel):
"""GitHub作者信息"""
name: str
email: str
date: str
class GitHubCommitInfo(BaseModel):
"""GitHub提交信息"""
author: GitHubAuthor
message: str
class GitHubUser(BaseModel):
"""GitHub用户信息"""
login: str
avatar_url: str
class GitHubCommit(BaseModel):
"""GitHub提交数据"""
sha: str
commit: GitHubCommitInfo
html_url: str
author: Optional[GitHubUser] = None
class ChangelogResponse(BaseModel):
"""更新日志响应"""
commits: List[GitHubCommit]
cached: bool
cache_time: Optional[str] = None
def is_cache_valid() -> bool:
"""检查缓存是否有效"""
if _cache["data"] is None or _cache["timestamp"] is None:
return False
now = datetime.now()
cache_age = now - _cache["timestamp"]
return cache_age < _cache["ttl"]
async def fetch_github_commits(page: int = 1, per_page: int = 30) -> List[dict]:
"""从GitHub API获取提交历史"""
url = f"{GITHUB_API_BASE}/repos/{REPO_OWNER}/{REPO_NAME}/commits"
params = {
"author": REPO_OWNER,
"page": page,
"per_page": per_page
}
headers = {
"Accept": "application/vnd.github.v3+json",
"User-Agent": "MuMuAINovel-App"
}
try:
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.get(url, params=params, headers=headers)
response.raise_for_status()
return response.json()
except httpx.HTTPError as e:
logger.error(f"GitHub API请求失败: {str(e)}")
raise HTTPException(
status_code=502,
detail=f"获取GitHub提交历史失败: {str(e)}"
)
@router.get("/changelog", response_model=ChangelogResponse)
async def get_changelog(
page: int = Query(1, ge=1, description="页码"),
per_page: int = Query(30, ge=1, le=100, description="每页数量")
):
"""
获取更新日志
从GitHub获取项目的提交历史,支持缓存以减少API调用
- **page**: 页码,从1开始
- **per_page**: 每页返回的提交数量,最大100
"""
try:
# 只缓存第一页
if page == 1 and is_cache_valid():
logger.info("使用缓存的更新日志")
return ChangelogResponse(
commits=_cache["data"],
cached=True,
cache_time=_cache["timestamp"].isoformat()
)
# 从GitHub获取数据
logger.info(f"从GitHub获取更新日志 (page={page}, per_page={per_page})")
commits_data = await fetch_github_commits(page, per_page)
# 解析数据
commits = []
for commit_data in commits_data:
try:
commit = GitHubCommit(
sha=commit_data["sha"],
commit=GitHubCommitInfo(
author=GitHubAuthor(
name=commit_data["commit"]["author"]["name"],
email=commit_data["commit"]["author"]["email"],
date=commit_data["commit"]["author"]["date"]
),
message=commit_data["commit"]["message"]
),
html_url=commit_data["html_url"],
author=GitHubUser(
login=commit_data["author"]["login"],
avatar_url=commit_data["author"]["avatar_url"]
) if commit_data.get("author") else None
)
commits.append(commit)
except (KeyError, TypeError) as e:
logger.warning(f"解析提交数据失败: {str(e)}")
continue
# 缓存第一页数据
if page == 1:
_cache["data"] = commits
_cache["timestamp"] = datetime.now()
logger.info("已缓存更新日志")
return ChangelogResponse(
commits=commits,
cached=False,
cache_time=None
)
except HTTPException:
raise
except Exception as e:
logger.error(f"获取更新日志时发生错误: {str(e)}")
raise HTTPException(
status_code=500,
detail=f"获取更新日志失败: {str(e)}"
)
@router.post("/changelog/refresh")
async def refresh_changelog(user=Depends(require_login)):
"""
刷新更新日志缓存
强制从GitHub重新获取最新的提交历史
"""
try:
logger.info("刷新更新日志缓存")
# 清除缓存
_cache["data"] = None
_cache["timestamp"] = None
# 重新获取
commits_data = await fetch_github_commits(1, 30)
# 解析数据
commits = []
for commit_data in commits_data:
try:
commit = GitHubCommit(
sha=commit_data["sha"],
commit=GitHubCommitInfo(
author=GitHubAuthor(
name=commit_data["commit"]["author"]["name"],
email=commit_data["commit"]["author"]["email"],
date=commit_data["commit"]["author"]["date"]
),
message=commit_data["commit"]["message"]
),
html_url=commit_data["html_url"],
author=GitHubUser(
login=commit_data["author"]["login"],
avatar_url=commit_data["author"]["avatar_url"]
) if commit_data.get("author") else None
)
commits.append(commit)
except (KeyError, TypeError) as e:
logger.warning(f"解析提交数据失败: {str(e)}")
continue
# 更新缓存
_cache["data"] = commits
_cache["timestamp"] = datetime.now()
return {
"success": True,
"message": "缓存已刷新",
"commit_count": len(commits),
"cache_time": _cache["timestamp"].isoformat()
}
except Exception as e:
logger.error(f"刷新缓存时发生错误: {str(e)}")
raise HTTPException(
status_code=500,
detail=f"刷新缓存失败: {str(e)}"
)