"""文章扩展服务""" 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)