功能特性: - 前端文章展示 (Markdown渲染、代码高亮) - 后台管理系统 (文章、分类、标签、作者管理) - 图片上传 (点击、拖拽、粘贴) - 网站设置 (Logo、底部宣传图片上传) - 访问统计 (IP记录、趋势图表) - 参考来源、附件支持 - 单端口部署 (16012) 技术栈: - Flask 3.0 + SQLAlchemy - SQLite数据库 - marked.js + highlight.js (Markdown渲染) - Chart.js (统计图表)
128 lines
4.1 KiB
Python
128 lines
4.1 KiB
Python
"""文章服务"""
|
|
from datetime import datetime
|
|
from slugify import slugify
|
|
from app import db
|
|
from app.models import Article, Category, Tag
|
|
|
|
|
|
class ArticleService:
|
|
"""文章业务逻辑"""
|
|
|
|
@staticmethod
|
|
def get_published_list(page=1, per_page=10, category_id=None, tag_id=None):
|
|
"""获取已发布文章列表"""
|
|
query = Article.query.filter_by(is_published=True)
|
|
|
|
if category_id:
|
|
query = query.filter_by(category_id=category_id)
|
|
if tag_id:
|
|
query = query.join(Article.tags).filter(Tag.id == tag_id)
|
|
|
|
return query.order_by(Article.published_at.desc()).paginate(
|
|
page=page, per_page=per_page, error_out=False
|
|
)
|
|
|
|
@staticmethod
|
|
def get_by_slug(slug):
|
|
"""通过slug获取文章"""
|
|
article = Article.query.filter_by(slug=slug, is_published=True).first()
|
|
if article:
|
|
# 增加浏览次数
|
|
article.view_count += 1
|
|
db.session.commit()
|
|
return article
|
|
|
|
@staticmethod
|
|
def get_by_id(article_id):
|
|
"""通过ID获取文章"""
|
|
return Article.query.get(article_id)
|
|
|
|
@staticmethod
|
|
def get_all_list(page=1, per_page=20, is_published=None):
|
|
"""获取所有文章列表(后台用)"""
|
|
query = Article.query
|
|
if is_published is not None:
|
|
query = query.filter_by(is_published=is_published)
|
|
return query.order_by(Article.updated_at.desc()).paginate(
|
|
page=page, per_page=per_page, error_out=False
|
|
)
|
|
|
|
@staticmethod
|
|
def create(title, content, summary=None, author_id=None, category_id=None, tag_ids=None,
|
|
cover_image=None, is_published=False):
|
|
"""创建文章"""
|
|
# 生成唯一slug
|
|
slug = slugify(title)
|
|
count = Article.query.filter(Article.slug.like(f'{slug}%')).count()
|
|
if count > 0:
|
|
slug = f'{slug}-{count + 1}'
|
|
|
|
article = Article(
|
|
title=title,
|
|
slug=slug,
|
|
content=content,
|
|
summary=summary or content[:200] + '...' if len(content) > 200 else content,
|
|
author_id=author_id,
|
|
category_id=category_id,
|
|
cover_image=cover_image,
|
|
is_published=is_published,
|
|
published_at=datetime.utcnow() if is_published else None
|
|
)
|
|
|
|
# 添加标签
|
|
if tag_ids:
|
|
tags = Tag.query.filter(Tag.id.in_(tag_ids)).all()
|
|
article.tags = tags
|
|
|
|
db.session.add(article)
|
|
db.session.commit()
|
|
return article
|
|
|
|
@staticmethod
|
|
def update(article_id, **kwargs):
|
|
"""更新文章"""
|
|
article = Article.query.get(article_id)
|
|
if not article:
|
|
return None
|
|
|
|
# 处理发布状态变化
|
|
if 'is_published' in kwargs and kwargs['is_published'] and not article.is_published:
|
|
kwargs['published_at'] = datetime.utcnow()
|
|
|
|
# 处理标签
|
|
tag_ids = kwargs.pop('tag_ids', None)
|
|
if tag_ids is not None:
|
|
tags = Tag.query.filter(Tag.id.in_(tag_ids)).all()
|
|
article.tags = tags
|
|
|
|
# 更新其他字段
|
|
for key, value in kwargs.items():
|
|
if hasattr(article, key):
|
|
setattr(article, key, value)
|
|
|
|
db.session.commit()
|
|
return article
|
|
|
|
@staticmethod
|
|
def delete(article_id):
|
|
"""删除文章"""
|
|
article = Article.query.get(article_id)
|
|
if article:
|
|
db.session.delete(article)
|
|
db.session.commit()
|
|
return True
|
|
return False
|
|
|
|
@staticmethod
|
|
def get_hot_articles(limit=5):
|
|
"""获取热门文章"""
|
|
return Article.query.filter_by(is_published=True)\
|
|
.order_by(Article.view_count.desc())\
|
|
.limit(limit).all()
|
|
|
|
@staticmethod
|
|
def get_recent_articles(limit=5):
|
|
"""获取最新文章"""
|
|
return Article.query.filter_by(is_published=True)\
|
|
.order_by(Article.published_at.desc())\
|
|
.limit(limit).all() |