Files
tech-blog/app/services/upload_service.py
hz4th_coder e689fed71e 初始化技术博客项目
功能特性:
- 前端文章展示 (Markdown渲染、代码高亮)
- 后台管理系统 (文章、分类、标签、作者管理)
- 图片上传 (点击、拖拽、粘贴)
- 网站设置 (Logo、底部宣传图片上传)
- 访问统计 (IP记录、趋势图表)
- 参考来源、附件支持
- 单端口部署 (16012)

技术栈:
- Flask 3.0 + SQLAlchemy
- SQLite数据库
- marked.js + highlight.js (Markdown渲染)
- Chart.js (统计图表)
2026-06-04 23:12:34 +08:00

255 lines
8.0 KiB
Python
Raw Permalink 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.
"""文章扩展服务"""
import os
import uuid
from datetime import datetime
from werkzeug.utils import secure_filename
from app import db
from app.models import ArticleAuthor, Reference, Attachment, UploadedImage, Article
class UploadService:
"""文件上传服务"""
UPLOAD_FOLDER = 'uploads'
ALLOWED_IMAGE_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'webpo', 'svg'}
ALLOWED_FILE_EXTENSIONS = {'pdf', 'doc', 'docx', 'xls', 'xlsx', 'zip', 'rar', 'tar', 'gz', 'txt', 'md'}
MAX_FILE_SIZE = 10 * 1024 * 1024 # 10MB
@staticmethod
def get_upload_folder():
"""获取上传目录路径"""
from flask import current_app
upload_folder = os.path.join(current_app.root_path, '..', UploadService.UPLOAD_FOLDER)
os.makedirs(upload_folder, exist_ok=True)
return upload_folder
@staticmethod
def allowed_image(filename):
"""检查是否是允许的图片格式"""
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in UploadService.ALLOWED_IMAGE_EXTENSIONS
@staticmethod
def allowed_file(filename):
"""检查是否是允许的文件格式"""
ext = filename.rsplit('.', 1)[1].lower() if '.' in filename else ''
return ext in UploadService.ALLOWED_IMAGE_EXTENSIONS or \
ext in UploadService.ALLOWED_FILE_EXTENSIONS
@staticmethod
def upload_image(file, article_id=None, uploader='admin'):
"""上传图片"""
if not file or not UploadService.allowed_image(file.filename):
return None, '不支持的图片格式'
# 检查文件大小
file.seek(0, os.SEEK_END)
size = file.tell()
file.seek(0)
if size > UploadService.MAX_FILE_SIZE:
return None, '文件大小超过限制最大10MB'
# 生成唯一文件名
ext = file.filename.rsplit('.', 1)[1].lower()
filename = f'{datetime.now().strftime("%Y%m%d")}_{uuid.uuid4().hex[:8]}.{ext}'
# 保存文件
filepath = os.path.join(UploadService.get_upload_folder(), filename)
file.save(filepath)
# 记录到数据库
image = UploadedImage(
filename=filename,
filepath=filepath,
article_id=article_id,
uploader=uploader
)
db.session.add(image)
db.session.commit()
return image, None
@staticmethod
def upload_attachment(file, article_id, description=None):
"""上传附件"""
if not file or not UploadService.allowed_file(file.filename):
return None, '不支持的文件格式'
# 检查文件大小
file.seek(0, os.SEEK_END)
size = file.tell()
file.seek(0)
if size > UploadService.MAX_FILE_SIZE:
return None, '文件大小超过限制最大10MB'
# 生成唯一文件名
original_name = secure_filename(file.filename)
ext = original_name.rsplit('.', 1)[1].lower() if '.' in original_name else ''
filename = f'{datetime.now().strftime("%Y%m%d")}_{uuid.uuid4().hex[:8]}_{original_name}'
# 保存文件
filepath = os.path.join(UploadService.get_upload_folder(), filename)
file.save(filepath)
# 记录到数据库
attachment = Attachment(
article_id=article_id,
filename=original_name,
filepath=filepath,
filesize=size,
filetype=ext,
description=description
)
db.session.add(attachment)
db.session.commit()
return attachment, None
@staticmethod
def delete_image(image_id):
"""删除图片"""
image = UploadedImage.query.get(image_id)
if image:
# 删除文件
if os.path.exists(image.filepath):
os.remove(image.filepath)
# 删除记录
db.session.delete(image)
db.session.commit()
return True
return False
@staticmethod
def delete_attachment(attachment_id):
"""删除附件"""
attachment = Attachment.query.get(attachment_id)
if attachment:
# 删除文件
if os.path.exists(attachment.filepath):
os.remove(attachment.filepath)
# 删除记录
db.session.delete(attachment)
db.session.commit()
return True
return False
@staticmethod
def get_image_url(filename):
"""获取图片URL相对路径"""
return f'/uploads/{filename}'
@staticmethod
def get_image_url(filename):
"""获取图片URL"""
return f'/uploads/{filename}'
class AuthorService:
"""作者服务"""
@staticmethod
def get_all():
"""获取所有作者"""
return ArticleAuthor.query.order_by(ArticleAuthor.name).all()
@staticmethod
def get_by_id(author_id):
"""通过ID获取作者"""
return ArticleAuthor.query.get(author_id)
@staticmethod
def create(name, avatar=None, bio=None, website=None):
"""创建作者"""
author = ArticleAuthor(name=name, avatar=avatar, bio=bio, website=website)
db.session.add(author)
db.session.commit()
return author
@staticmethod
def update(author_id, **kwargs):
"""更新作者"""
author = ArticleAuthor.query.get(author_id)
if author:
for key, value in kwargs.items():
if hasattr(author, key):
setattr(author, key, value)
db.session.commit()
return author
@staticmethod
def delete(author_id):
"""删除作者"""
author = ArticleAuthor.query.get(author_id)
if author:
db.session.delete(author)
db.session.commit()
return True
return False
class ReferenceService:
"""参考来源服务"""
@staticmethod
def get_by_article(article_id):
"""获取文章的参考来源"""
return Reference.query.filter_by(article_id=article_id).order_by(Reference.created_at).all()
@staticmethod
def add(article_id, title, url=None, description=None):
"""添加参考来源"""
ref = Reference(article_id=article_id, title=title, url=url, description=description)
db.session.add(ref)
db.session.commit()
return ref
@staticmethod
def update(reference_id, **kwargs):
"""更新参考来源"""
ref = Reference.query.get(reference_id)
if ref:
for key, value in kwargs.items():
if hasattr(ref, key):
setattr(ref, key, value)
db.session.commit()
return ref
@staticmethod
def delete(reference_id):
"""删除参考来源"""
ref = Reference.query.get(reference_id)
if ref:
db.session.delete(ref)
db.session.commit()
return True
return False
@staticmethod
def update_article_references(article_id, references_data):
"""批量更新文章参考来源"""
# 删除旧的参考来源
Reference.query.filter_by(article_id=article_id).delete()
# 添加新的参考来源
for ref_data in references_data:
if ref_data.get('title'): # 只添加有标题的
ReferenceService.add(
article_id=article_id,
title=ref_data.get('title'),
url=ref_data.get('url'),
description=ref_data.get('description')
)
class AttachmentService:
"""附件服务"""
@staticmethod
def get_by_article(article_id):
"""获取文章附件"""
return Attachment.query.filter_by(article_id=article_id).order_by(Attachment.upload_time).all()
@staticmethod
def delete_attachment(attachment_id):
"""删除附件"""
return UploadService.delete_attachment(attachment_id)