from datetime import datetime from flask_sqlalchemy import SQLAlchemy from flask_login import UserMixin from werkzeug.security import generate_password_hash, check_password_hash db = SQLAlchemy() # 网站和标签的多对多关系表 site_tags = db.Table('site_tags', db.Column('site_id', db.Integer, db.ForeignKey('sites.id'), primary_key=True), db.Column('tag_id', db.Integer, db.ForeignKey('tags.id'), primary_key=True) ) class Site(db.Model): """网站模型""" __tablename__ = 'sites' id = db.Column(db.Integer, primary_key=True) code = db.Column(db.String(8), unique=True, nullable=False, comment='8位数字编码') name = db.Column(db.String(100), nullable=False, comment='网站名称') url = db.Column(db.String(500), nullable=False, comment='网站URL') slug = db.Column(db.String(100), unique=True, nullable=True, comment='URL别名(SEO用)') logo = db.Column(db.String(500), comment='Logo图片路径') short_desc = db.Column(db.String(200), comment='简短描述') description = db.Column(db.Text, comment='详细介绍') features = db.Column(db.Text, comment='主要功能') news_keywords = db.Column(db.String(200), comment='新闻获取关键词(用于精准匹配相关新闻)') is_active = db.Column(db.Boolean, default=True, comment='是否启用') is_recommended = db.Column(db.Boolean, default=False, nullable=False, comment='是否推荐') view_count = db.Column(db.Integer, default=0, comment='浏览次数') sort_order = db.Column(db.Integer, default=0, comment='排序权重') created_at = db.Column(db.DateTime, default=datetime.now, comment='创建时间') updated_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now, comment='更新时间') # 关联标签 tags = db.relationship('Tag', secondary=site_tags, lazy='subquery', backref=db.backref('sites', lazy=True)) def __repr__(self): return f'' def to_dict(self): """转换为字典""" return { 'id': self.id, 'code': self.code, 'name': self.name, 'url': self.url, 'slug': self.slug, 'logo': self.logo, 'short_desc': self.short_desc, 'description': self.description, 'features': self.features, 'news_keywords': self.news_keywords, 'is_active': self.is_active, 'is_recommended': self.is_recommended, 'view_count': self.view_count, 'tags': [tag.name for tag in self.tags], 'created_at': self.created_at.strftime('%Y-%m-%d %H:%M:%S') if self.created_at else None } class Tag(db.Model): """标签模型""" __tablename__ = 'tags' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(50), unique=True, nullable=False, comment='标签名称') slug = db.Column(db.String(50), unique=True, nullable=False, comment='URL别名') description = db.Column(db.String(200), comment='标签描述') seo_title = db.Column(db.String(100), comment='SEO标题(v2.4新增)') seo_description = db.Column(db.String(300), comment='SEO页面描述(v2.4新增)') seo_keywords = db.Column(db.String(200), comment='SEO关键词(v2.4新增)') icon = db.Column(db.String(100), comment='图标') sort_order = db.Column(db.Integer, default=0, comment='排序权重') created_at = db.Column(db.DateTime, default=datetime.now, comment='创建时间') def __repr__(self): return f'' def to_dict(self): """转换为字典""" return { 'id': self.id, 'name': self.name, 'slug': self.slug, 'description': self.description, 'seo_title': self.seo_title, 'seo_description': self.seo_description, 'seo_keywords': self.seo_keywords, 'icon': self.icon } class News(db.Model): """新闻模型""" __tablename__ = 'news' id = db.Column(db.Integer, primary_key=True) site_id = db.Column(db.Integer, db.ForeignKey('sites.id'), nullable=False, comment='关联网站ID') title = db.Column(db.String(200), nullable=False, comment='新闻标题') content = db.Column(db.Text, comment='新闻内容') news_type = db.Column(db.String(50), default='Industry News', comment='新闻类型') url = db.Column(db.String(500), comment='新闻链接') source_name = db.Column(db.String(100), comment='新闻来源网站名称') source_icon = db.Column(db.String(500), comment='新闻来源网站图标URL') published_at = db.Column(db.DateTime, default=datetime.now, comment='发布时间') is_active = db.Column(db.Boolean, default=True, comment='是否启用') created_at = db.Column(db.DateTime, default=datetime.now, comment='创建时间') updated_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now, comment='更新时间') # 关联网站 site = db.relationship('Site', backref=db.backref('news', lazy='dynamic', order_by='News.published_at.desc()')) def __repr__(self): return f'' def to_dict(self): """转换为字典""" return { 'id': self.id, 'site_id': self.site_id, 'title': self.title, 'content': self.content, 'news_type': self.news_type, 'url': self.url, 'source_name': self.source_name, 'source_icon': self.source_icon, 'published_at': self.published_at.strftime('%Y-%m-%d') if self.published_at else None, 'created_at': self.created_at.strftime('%Y-%m-%d %H:%M:%S') if self.created_at else None } class Admin(UserMixin, db.Model): """管理员模型""" __tablename__ = 'admins' id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(50), unique=True, nullable=False, comment='用户名') password_hash = db.Column(db.String(255), nullable=False, comment='密码哈希') email = db.Column(db.String(100), comment='邮箱') is_active = db.Column(db.Boolean, default=True, comment='是否启用') created_at = db.Column(db.DateTime, default=datetime.now, comment='创建时间') last_login = db.Column(db.DateTime, comment='最后登录时间') def set_password(self, password): """设置密码""" self.password_hash = generate_password_hash(password) def check_password(self, password): """验证密码""" return check_password_hash(self.password_hash, password) def get_id(self): """返回唯一标识,区分Admin和User""" return f"admin:{self.id}" def __repr__(self): return f'' class PromptTemplate(db.Model): """AI提示词模板模型""" __tablename__ = 'prompt_templates' id = db.Column(db.Integer, primary_key=True) key = db.Column(db.String(50), unique=True, nullable=False, comment='唯一标识(tags/features/description)') name = db.Column(db.String(100), nullable=False, comment='模板名称') system_prompt = db.Column(db.Text, nullable=False, comment='系统提示词') user_prompt_template = db.Column(db.Text, nullable=False, comment='用户提示词模板(支持变量)') description = db.Column(db.String(200), comment='模板说明') is_active = db.Column(db.Boolean, default=True, comment='是否启用') created_at = db.Column(db.DateTime, default=datetime.now, comment='创建时间') updated_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now, comment='更新时间') def __repr__(self): return f'' def to_dict(self): """转换为字典""" return { 'id': self.id, 'key': self.key, 'name': self.name, 'system_prompt': self.system_prompt, 'user_prompt_template': self.user_prompt_template, 'description': self.description, 'is_active': self.is_active, 'created_at': self.created_at.strftime('%Y-%m-%d %H:%M:%S') if self.created_at else None, 'updated_at': self.updated_at.strftime('%Y-%m-%d %H:%M:%S') if self.updated_at else None } class User(UserMixin, db.Model): """普通用户模型""" __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(50), unique=True, nullable=False, index=True, comment='用户名') password_hash = db.Column(db.String(255), nullable=False, comment='密码哈希') email = db.Column(db.String(100), unique=True, nullable=True, index=True, comment='邮箱') avatar = db.Column(db.String(500), comment='头像URL') bio = db.Column(db.String(200), comment='个人简介') is_active = db.Column(db.Boolean, default=True, comment='是否启用') is_public_profile = db.Column(db.Boolean, default=False, comment='是否公开个人资料') created_at = db.Column(db.DateTime, default=datetime.now, comment='创建时间') last_login = db.Column(db.DateTime, comment='最后登录时间') # 关联 collections = db.relationship('Collection', backref='user', lazy='dynamic', cascade='all, delete-orphan') folders = db.relationship('Folder', backref='user', lazy='dynamic', cascade='all, delete-orphan') def set_password(self, password): """设置密码""" self.password_hash = generate_password_hash(password) def check_password(self, password): """验证密码""" return check_password_hash(self.password_hash, password) def get_id(self): """返回唯一标识,区分Admin和User""" return f"user:{self.id}" def __repr__(self): return f'' class Folder(db.Model): """收藏分组模型""" __tablename__ = 'folders' id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False, index=True, comment='用户ID') name = db.Column(db.String(50), nullable=False, comment='文件夹名称') description = db.Column(db.String(200), comment='文件夹描述') icon = db.Column(db.String(50), default='📁', comment='图标emoji') sort_order = db.Column(db.Integer, default=0, comment='排序权重') is_public = db.Column(db.Boolean, default=False, comment='是否公开') public_slug = db.Column(db.String(100), unique=True, nullable=True, index=True, comment='公开链接标识') created_at = db.Column(db.DateTime, default=datetime.now, comment='创建时间') # 关联 collections = db.relationship('Collection', backref='folder', lazy='dynamic', cascade='all, delete-orphan') __table_args__ = ( db.UniqueConstraint('user_id', 'name', name='unique_user_folder'), ) def __repr__(self): return f'' class Collection(db.Model): """收藏记录模型""" __tablename__ = 'collections' id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False, index=True, comment='用户ID') site_id = db.Column(db.Integer, db.ForeignKey('sites.id'), nullable=False, index=True, comment='网站ID') folder_id = db.Column(db.Integer, db.ForeignKey('folders.id'), nullable=True, index=True, comment='文件夹ID') note = db.Column(db.Text, comment='备注') created_at = db.Column(db.DateTime, default=datetime.now, comment='创建时间') updated_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now, comment='更新时间') # 关联 site = db.relationship('Site', backref='collections') __table_args__ = ( db.UniqueConstraint('user_id', 'site_id', name='unique_user_site'), db.Index('idx_user_folder', 'user_id', 'folder_id'), ) def __repr__(self): return f''