This commit is contained in:
Ubuntu
2026-07-03 16:58:28 +08:00
commit 32b43f85bd
6240 changed files with 1987678 additions and 0 deletions
+222
View File
@@ -0,0 +1,222 @@
"""
文章管理路由
"""
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from typing import List, Optional
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from database import get_db
from models import Post, Category, Tag, PostTag, Image
from schemas import PostCreate, PostUpdate, PostResponse, PostListItem
from routers.auth import get_current_admin
import re
router = APIRouter(prefix="/api/posts", tags=["文章"])
@router.get("", response_model=List[PostListItem])
def get_posts(
skip: int = Query(0, ge=0),
limit: int = Query(10, ge=1, le=100),
category_id: Optional[int] = None,
tag_id: Optional[int] = None,
is_draft: Optional[bool] = None,
search: Optional[str] = None,
db: Session = Depends(get_db)
):
"""获取文章列表(支持筛选)"""
query = db.query(Post)
# 筛选条件
if category_id:
query = query.filter(Post.category_id == category_id)
if tag_id:
query = query.join(PostTag).filter(PostTag.tag_id == tag_id)
if is_draft is not None:
query = query.filter(Post.is_draft == is_draft)
if search:
query = query.filter(Post.title.contains(search) | Post.content.contains(search))
# 按时间倒序
query = query.order_by(Post.created_at.desc())
posts = query.offset(skip).limit(limit).all()
return posts
@router.get("/{post_id}", response_model=PostResponse)
def get_post(post_id: str, db: Session = Depends(get_db)):
"""获取单篇文章(支持 id 或 slug)"""
# 尝试作为 id 查询
try:
id_int = int(post_id)
post = db.query(Post).filter(Post.id == id_int).first()
except ValueError:
post = None
# 如果不是 id,尝试作为 slug 查询
if not post:
post = db.query(Post).filter(Post.slug == post_id).first()
if not post:
raise HTTPException(status_code=404, detail="文章不存在")
# 增加浏览量
post.view_count += 1
db.commit()
return post
@router.post("", response_model=PostResponse)
def create_post(
post_data: PostCreate,
current_admin=Depends(get_current_admin),
db: Session = Depends(get_db)
):
"""创建文章"""
# 处理标签
tags = []
if post_data.tag_ids:
tags = db.query(Tag).filter(Tag.id.in_(post_data.tag_ids)).all()
# 创建文章
post = Post(
title=post_data.title,
content=post_data.content,
content_type=post_data.content_type or "markdown",
summary=post_data.summary or post_data.content[:200] + "...",
cover_image=post_data.cover_image,
is_published=post_data.is_published,
is_draft=post_data.is_draft,
category_id=post_data.category_id,
tags=tags
)
db.add(post)
db.commit()
db.refresh(post)
return post
@router.put("/{post_id}", response_model=PostResponse)
def update_post(
post_id: int,
post_data: PostUpdate,
current_admin=Depends(get_current_admin),
db: Session = Depends(get_db)
):
"""更新文章"""
post = db.query(Post).filter(Post.id == post_id).first()
if not post:
raise HTTPException(status_code=404, detail="文章不存在")
# 更新字段
update_data = post_data.dict(exclude_unset=True)
# 处理标签
if "tag_ids" in update_data:
tag_ids = update_data.pop("tag_ids")
if tag_ids is not None:
post.tags = db.query(Tag).filter(Tag.id.in_(tag_ids)).all()
# 更新其他字段
for field, value in update_data.items():
setattr(post, field, value)
# 自动更新摘要
if post_data.content and not post_data.summary:
post.summary = post_data.content[:200] + "..."
db.commit()
db.refresh(post)
return post
def delete_post_images(post: Post, db: Session):
"""删除文章关联的所有图片(物理删除文件+数据库记录)"""
UPLOAD_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "uploads")
THUMB_DIR = os.path.join(UPLOAD_DIR, "thumbnails")
# 收集所有要删除的图片路径
image_paths = set()
# 1. 封面图
if post.cover_image:
image_paths.add(post.cover_image)
# 2. 内容中的图片
if post.content:
paths = re.findall(r'/uploads/[^\s"\')<>]+', post.content)
image_paths.update(paths)
# 删除每张图片
for path in image_paths:
filename = os.path.basename(path)
# 查找数据库记录
image = db.query(Image).filter(Image.file_path == path).first()
if image:
# 删除原图文件
file_path = os.path.join(UPLOAD_DIR, filename)
if os.path.exists(file_path):
os.remove(file_path)
# 删除缩略图文件
if image.thumb_path:
thumb_filename = os.path.basename(image.thumb_path)
thumb_path = os.path.join(THUMB_DIR, thumb_filename)
if os.path.exists(thumb_path):
os.remove(thumb_path)
# 删除数据库记录
db.delete(image)
@router.delete("/{post_id}")
def delete_post(
post_id: int,
current_admin=Depends(get_current_admin),
db: Session = Depends(get_db)
):
"""删除文章(同时删除关联的图片)"""
post = db.query(Post).filter(Post.id == post_id).first()
if not post:
raise HTTPException(status_code=404, detail="文章不存在")
# 先删除关联图片
delete_post_images(post, db)
# 再删除文章
db.delete(post)
db.commit()
return {"message": "文章已删除"}
@router.get("/stats/overview")
def get_stats(db: Session = Depends(get_db)):
"""获取统计数据"""
from sqlalchemy import func
total_posts = db.query(Post).filter(Post.is_draft == False).count()
total_views = db.query(func.sum(Post.view_count)).scalar() or 0
# 按分类统计
category_counts = {}
categories = db.query(Category).all()
for cat in categories:
count = db.query(Post).filter(Post.category_id == cat.id).count()
category_counts[cat.name] = count
# 最近文章
recent_posts = db.query(Post).order_by(Post.created_at.desc()).limit(5).all()
return {
"total_posts": total_posts,
"total_views": total_views,
"category_counts": category_counts,
"recent_posts": recent_posts
}