From 49f7503978ca8cce6df6cdd3cb6de59245034898 Mon Sep 17 00:00:00 2001 From: QihuAI Design Date: Wed, 22 Jul 2026 12:42:43 +0800 Subject: [PATCH] fix(models): widen market_apps.skill_content to MEDIUMTEXT for large SKILL.md --- backend/python/app/models.py | 2329 +++++++++++++++++----------------- 1 file changed, 1165 insertions(+), 1164 deletions(-) diff --git a/backend/python/app/models.py b/backend/python/app/models.py index b493f35..efc8a29 100644 --- a/backend/python/app/models.py +++ b/backend/python/app/models.py @@ -1,1164 +1,1165 @@ -""" -AIFlow 数据库模型定义 - SQLAlchemy ORM -所有用户数据查询必须带 user_id 过滤实现物理隔离 -""" -import uuid -from datetime import datetime, date -from sqlalchemy import ( - Text, - Column, String, Integer, BigInteger, Float, Boolean, - DateTime, Date, Text, Enum, ForeignKey, Index, JSON, - DECIMAL, UniqueConstraint -) -from sqlalchemy.orm import relationship -from .database import Base - - -def gen_uuid() -> str: - return str(uuid.uuid4()) - - -# ============================================================ -# 用户与权限 -# ============================================================ - -class User(Base): - __tablename__ = "users" - - id = Column(String(36), primary_key=True, default=gen_uuid) - username = Column(String(50), unique=True, nullable=False) - email = Column(String(100), default=None) - phone = Column(String(20), default=None) - password_hash = Column(String(255), nullable=False) - avatar = Column(String(500), default=None) - role = Column(String(10), nullable=False, default="user") # user / admin - status = Column(String(10), nullable=False, default="active") # active / banned - real_name_status = Column(String(20), nullable=False, default="unverified") - real_name = Column(String(50), default=None) - id_card = Column(String(20), default=None) - membership_plan_id = Column(String(36), default=None) - membership_level = Column(Integer, default=0) - membership_expire_at = Column(DateTime, default=None) - auto_renew = Column(Boolean, default=False) - created_at = Column(DateTime, default=datetime.now) - updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) - - -class LoginLog(Base): - __tablename__ = "login_logs" - - id = Column(String(36), primary_key=True, default=gen_uuid) - user_id = Column(String(36), ForeignKey("users.id"), nullable=False) - ip = Column(String(45), default=None) - user_agent = Column(String(500), default=None) - login_at = Column(DateTime, default=datetime.now) - - -class ApiKey(Base): - __tablename__ = "api_keys" - - id = Column(String(36), primary_key=True, default=gen_uuid) - user_id = Column(String(36), ForeignKey("users.id"), nullable=False) - api_key = Column(String(64), unique=True, nullable=False) - secret = Column(String(128), nullable=False) - name = Column(String(100), default=None) - status = Column(String(10), nullable=False, default="active") # active / revoked - last_used_at = Column(DateTime, default=None) - created_at = Column(DateTime, default=datetime.now) - expired_at = Column(DateTime, default=None) - description = Column(String(500), default=None, comment="密钥描述") - rate_limit = Column(Integer, default=60, comment="速率限制(请求/分钟)") - total_calls = Column(BigInteger, default=0, comment="总调用次数") - - -# ============================================================ -# AI聊天与频道 -# ============================================================ - -class Conversation(Base): - __tablename__ = "conversations" - - id = Column(String(36), primary_key=True, default=gen_uuid) - user_id = Column(String(36), ForeignKey("users.id"), nullable=False) - title = Column(String(200), nullable=False, default="新对话") - model_id = Column(String(36), default=None) - status = Column(String(10), nullable=False, default="active") # active / archived - pinned = Column(Boolean, default=False) - knowledge_graph_count = Column(Integer, default=0) - knowledge_graph_base_id = Column(String(64), default=None, comment="已生成的知识图谱知识库ID") - source = Column(String(32), default=None, comment="对话来源,如 wechat_bot") - ext_agent_conversation_id = Column(String(128), default=None, comment="外部Agent(Dify/Coze)会话ID,用于维持多轮上下文") - created_at = Column(DateTime, default=datetime.now) - updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) - - -class Message(Base): - __tablename__ = "messages" - - id = Column(String(36), primary_key=True, default=gen_uuid) - conversation_id = Column(String(36), ForeignKey("conversations.id"), nullable=False) - role = Column(String(10), nullable=False) # user / assistant / system - content = Column(Text, nullable=False) - token_used = Column(Integer, default=0) - model_id = Column(String(36), default=None) - knowledge_ids = Column(JSON, default=None) - agent_ids = Column(JSON, default=None) - skill_ids = Column(JSON, default=None) - mcp_ids = Column(JSON, default=None, comment="该轮对话选择的MCP应用ID列表") - attachments = Column(JSON, default=None, comment="该轮对话附带的文件附件列表") - used_skills = Column(JSON, default=None, comment="该轮对话调用的进化技能名列表") - used_mcp = Column(JSON, default=None, comment="该轮对话调用的MCP应用名列表") - created_at = Column(DateTime, default=datetime.now) - - -class Channel(Base): - __tablename__ = "channels" - - id = Column(String(36), primary_key=True, default=gen_uuid) - owner_id = Column(String(36), ForeignKey("users.id"), nullable=False) - name = Column(String(100), nullable=False) - description = Column(Text, default=None) - platform = Column(String(20), nullable=False) - webhook_url = Column(String(500), default=None) - credential = Column(JSON, default=None) - oauth_state = Column(String(500), default=None) - settings = Column(JSON, default=None) - account_name = Column(String(100), default=None) - default_agent_id = Column(String(36), default=None) - default_skill_id = Column(String(36), default=None) - knowledge_base_id = Column(String(36), default=None) - bound_model_id = Column(String(36), default=None) - features = Column(JSON, default=None) - status = Column(String(20), nullable=False, default="pending") - is_active = Column(Boolean, default=True) - last_active = Column(DateTime, default=None) - created_at = Column(DateTime, default=datetime.now) - updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) - - -class ChannelMember(Base): - __tablename__ = "channel_members" - __table_args__ = (UniqueConstraint("channel_id", "user_id"),) - - id = Column(String(36), primary_key=True, default=gen_uuid) - channel_id = Column(String(36), ForeignKey("channels.id"), nullable=False) - user_id = Column(String(36), ForeignKey("users.id"), nullable=False) - role = Column(String(10), nullable=False, default="member") # owner / member - joined_at = Column(DateTime, default=datetime.now) - - -class ChannelMessage(Base): - __tablename__ = "channel_messages" - - id = Column(String(36), primary_key=True, default=gen_uuid) - channel_id = Column(String(36), ForeignKey("channels.id"), nullable=False) - user_id = Column(String(36), ForeignKey("users.id"), nullable=False) - role = Column(String(10), nullable=False) - content = Column(Text, nullable=False) - token_used = Column(Integer, default=0) - created_at = Column(DateTime, default=datetime.now) - - -# ============================================================ -# 知识库 -# ============================================================ - -class KnowledgeBase(Base): - __tablename__ = "knowledge_bases" - - id = Column(String(36), primary_key=True, default=gen_uuid) - user_id = Column(String(36), ForeignKey("users.id"), nullable=False) - name = Column(String(200), nullable=False) - description = Column(Text, default=None) - directory_path = Column(String(500), default="/") - document_count = Column(Integer, default=0) - knowledge_graph_count = Column(Integer, default=0) - created_at = Column(DateTime, default=datetime.now) - updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) - is_official = Column(Boolean, default=False) - status = Column(String(20), default="active") - external_service_id = Column(String(36), default=None) - external_id = Column(String(100), default=None) - external_sync_status = Column(String(20), default="none") - external_config = Column(JSON, default=None) - - -class KnowledgeDirectory(Base): - __tablename__ = "knowledge_directories" - - id = Column(String(36), primary_key=True, default=gen_uuid) - user_id = Column(String(36), ForeignKey("users.id"), nullable=False) - knowledge_base_id = Column(String(36), ForeignKey("knowledge_bases.id"), default=None) - parent_id = Column(String(36), default=None) - name = Column(String(200), nullable=False) - created_at = Column(DateTime, default=datetime.now) - - -class KnowledgeDocument(Base): - __tablename__ = "knowledge_documents" - - id = Column(String(36), primary_key=True, default=gen_uuid) - knowledge_base_id = Column(String(36), ForeignKey("knowledge_bases.id"), nullable=False) - directory_id = Column(String(36), ForeignKey("knowledge_directories.id"), default=None) - filename = Column(String(255), nullable=False) - file_type = Column(String(20), nullable=False) - file_size = Column(BigInteger, default=0) - file_path = Column(String(500), default=None) - status = Column(String(20), nullable=False, default="uploading") - chunk_count = Column(Integer, default=0) - error_message = Column(Text, default=None) - content = Column(Text, default=None) - created_at = Column(DateTime, default=datetime.now) - updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) - - - - -class KnowledgeDocumentChunk(Base): - """知识库文档分段表 — Stage C.1""" - __tablename__ = "knowledge_document_chunks" - - id = Column(String(36), primary_key=True, default=gen_uuid) - document_id = Column(String(36), ForeignKey("knowledge_documents.id"), nullable=False) - knowledge_base_id = Column(String(36), ForeignKey("knowledge_bases.id"), nullable=False) - chunk_index = Column(Integer, nullable=False, default=0, comment="分段序号") - content = Column(Text, nullable=False, comment="分段文本内容") - start_char = Column(Integer, default=0, comment="起始字符位置") - end_char = Column(Integer, default=0, comment="结束字符位置") - chunk_metadata = Column(JSON, default=None, comment="分段元数据(标题、关键词等)") - token_count = Column(Integer, default=0, comment="分段token数估算") - created_at = Column(DateTime, default=datetime.now) - - __table_args__ = ( - Index("idx_kdc_doc_id", "document_id"), - Index("idx_kdc_kb_id", "knowledge_base_id"), - ) - -class KnowledgeGraphNode(Base): - __tablename__ = "knowledge_graph_nodes" - - id = Column(String(36), primary_key=True, default=gen_uuid) - knowledge_base_id = Column(String(36), ForeignKey("knowledge_bases.id"), nullable=False) - source_doc_id = Column(String(36), ForeignKey("knowledge_documents.id"), default=None) - label = Column(String(500), nullable=False) - type = Column(String(50), nullable=False, comment="entity/attribute/concept/event") - properties = Column(JSON, default=None) - description = Column(Text, default=None) - created_at = Column(DateTime, default=datetime.now) - - -class KnowledgeGraphEdge(Base): - __tablename__ = "knowledge_graph_edges" - - id = Column(String(36), primary_key=True, default=gen_uuid) - knowledge_base_id = Column(String(36), ForeignKey("knowledge_bases.id"), nullable=False) - source_node_id = Column(String(36), ForeignKey("knowledge_graph_nodes.id"), nullable=False) - target_node_id = Column(String(36), ForeignKey("knowledge_graph_nodes.id"), nullable=False) - relation = Column(String(200), nullable=False) - weight = Column(Float, default=1.0) - description = Column(Text, default=None) - created_at = Column(DateTime, default=datetime.now) - - -class KnowledgeGraphBuildLog(Base): - __tablename__ = "knowledge_graph_build_logs" - - id = Column(String(36), primary_key=True, default=gen_uuid) - knowledge_base_id = Column(String(36), ForeignKey("knowledge_bases.id"), nullable=False) - document_id = Column(String(36), ForeignKey("knowledge_documents.id"), nullable=False) - node_count = Column(Integer, default=0) - edge_count = Column(Integer, default=0) - status = Column(String(20), default="completed") - created_at = Column(DateTime, default=datetime.now) - - -# ============================================================ -# 应用市场 -# ============================================================ - -class MarketApp(Base): - __tablename__ = "market_apps" - - id = Column(String(36), primary_key=True, default=gen_uuid) - name = Column(String(200), nullable=False) - type = Column(String(10), nullable=False) # skill / agent / mcp - source = Column(String(10), nullable=False, default="user") # official / user - publisher_id = Column(String(36), ForeignKey("users.id"), default=None) - publisher_name = Column(String(100), default=None) - description = Column(Text, default=None) - # 分类与标签:DB 表 market_apps 已存在 category(varchar50)/tags(varchar500) 两列, - # 此前 ORM 漏声明导致后台读取分类恒为 None -> 回退 "other"(#679 根因)。此处补齐。 - category = Column(String(50), default="other", comment="技能/应用分类(9类: writing/coding/data_analysis/qa/automation/creativity/communication/reasoning/other)") - tags = Column(String(500), default=None, comment="标签,逗号分隔") - icon = Column(String(500), default=None) - tutorial = Column(Text, default=None) - price = Column(DECIMAL(10, 2), nullable=False, default=0.00) - is_member_free = Column(Boolean, default=False) - join_promotion = Column(Boolean, default=False) - status = Column(String(10), nullable=False, default="pending") # pending / published / removed - rating_avg = Column(DECIMAL(3, 2), default=0.00) - review_count = Column(Integer, default=0) - install_count = Column(Integer, default=0) - skill_content = Column(Text, default=None) - mcp_transport = Column(String(20), default=None) - platform = Column(String(20), default=None, comment="Agent 接入平台:dify/n8n/coze") - capability = Column(String(20), default=None, comment="能力分类:text/thinking/vision/tool/image/audio/asr/ocr/embedding/rerank/video") - agent_endpoint = Column(String(500), default=None) - agent_api_key = Column(Text, default=None, comment="Agent 接入 API Key(3.2 起 Fernet 加密存储)") - tool_policy = Column(JSON, default=None, comment="Agent 工具/MCP 权限沙箱策略(3.1)") - orchestration = Column(JSON, default=None, comment="Agent 多Agent编排图(3.3)") - # Phase 4.1: 市场安检 + 版本化 + 绿标 - version = Column(String(20), default="1.0.0", comment="应用版本号(4.1)") - security_scan = Column(JSON, default=None, comment="安全扫描结果(4.1)") - dependencies = Column(JSON, default=None, comment="依赖声明(4.1)") - verified = Column(Boolean, default=False, comment="绿标/官方安检通过(4.1)") - created_at = Column(DateTime, default=datetime.now) - updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) - - -class MarketAppVersion(Base): - """Phase 4.1: 应用版本快照表,支持灰度回滚。""" - __tablename__ = "market_app_versions" - - id = Column(String(36), primary_key=True, default=gen_uuid) - app_id = Column(String(36), ForeignKey("market_apps.id"), nullable=False, index=True) - version = Column(String(20), nullable=False) - type = Column(String(10), nullable=False) - config = Column(JSON, default=None, comment="该版本完整配置快照(4.1)") - security_scan = Column(JSON, default=None, comment="该版本安检结果(4.1)") - dependencies = Column(JSON, default=None, comment="该版本依赖声明(4.1)") - created_by = Column(String(36), default=None) - created_at = Column(DateTime, default=datetime.now) - - -class AppPurchase(Base): - __tablename__ = "app_purchases" - - id = Column(String(36), primary_key=True, default=gen_uuid) - user_id = Column(String(36), ForeignKey("users.id"), nullable=False) - app_id = Column(String(36), ForeignKey("market_apps.id"), nullable=False) - price = Column(DECIMAL(10, 2), nullable=False, default=0.00) - order_id = Column(String(36), default=None) - status = Column(String(20), nullable=False, default="purchased") # purchased / installed - installed_at = Column(DateTime, default=None) - purchased_at = Column(DateTime, default=None) - created_at = Column(DateTime, default=datetime.now) - - -class AppReview(Base): - __tablename__ = "app_reviews" - __table_args__ = (UniqueConstraint("user_id", "app_id"),) - - id = Column(String(36), primary_key=True, default=gen_uuid) - user_id = Column(String(36), ForeignKey("users.id"), nullable=False) - app_id = Column(String(36), ForeignKey("market_apps.id"), nullable=False) - rating = Column(Integer, nullable=False) - content = Column(Text, default=None) - created_at = Column(DateTime, default=datetime.now) - - -class AppInstallation(Base): - __tablename__ = "app_installations" - __table_args__ = (UniqueConstraint("user_id", "app_id"),) - - id = Column(String(36), primary_key=True, default=gen_uuid) - user_id = Column(String(36), ForeignKey("users.id"), nullable=False) - app_id = Column(String(36), ForeignKey("market_apps.id"), nullable=False) - installed_at = Column(DateTime, default=datetime.now) - - -# ============================================================ -# 付费与订单 -# ============================================================ - -class MembershipPlan(Base): - __tablename__ = "membership_plans" - - id = Column(String(36), primary_key=True, default=gen_uuid) - name = Column(String(100), nullable=False) - level = Column(Integer, nullable=False, default=0) - duration_type = Column(String(10), nullable=False) # month / quarter / year - price = Column(DECIMAL(10, 2), nullable=False) - daily_free_tokens = Column(Integer, default=0) - features = Column(JSON, default=None) - free_app_ids = Column(JSON, default=None) - free_model_ids = Column(JSON, default=None) - # 配额字段 - max_agents = Column(Integer, default=5) - max_workflows = Column(Integer, default=3) - max_knowledge_bases = Column(Integer, default=2) - max_storage_mb = Column(Integer, default=1024) - api_calls_per_day = Column(Integer, default=100) - api_calls_per_min = Column(Integer, default=10) - # 权限字段 - can_publish_apps = Column(Boolean, default=False) - can_create_api_keys = Column(Boolean, default=False) - can_use_workflow = Column(Boolean, default=False) - can_use_mcp = Column(Boolean, default=False) - can_use_code_exec = Column(Boolean, default=False) - can_use_privacy_mode = Column(Boolean, default=False) - can_use_dag = Column(Boolean, default=False) - allowed_model_tiers = Column(JSON, default=None) - permissions = Column(JSON, default=None) - is_active = Column(Boolean, default=True) - created_at = Column(DateTime, default=datetime.now) - updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) - - -class MembershipSubscription(Base): - __tablename__ = "membership_subscriptions" - - id = Column(String(36), primary_key=True, default=gen_uuid) - user_id = Column(String(36), ForeignKey("users.id"), nullable=False) - plan_id = Column(String(36), ForeignKey("membership_plans.id"), nullable=False) - order_id = Column(String(36), default=None) - start_at = Column(DateTime, nullable=False) - end_at = Column(DateTime, nullable=False) - auto_renew = Column(Boolean, default=False) - status = Column(String(15), nullable=False, default="active") # active / expired / downgraded - created_at = Column(DateTime, default=datetime.now) - - -class Order(Base): - __tablename__ = "orders" - - id = Column(String(36), primary_key=True, default=gen_uuid) - user_id = Column(String(36), ForeignKey("users.id"), nullable=False) - order_type = Column(String(15), nullable=False) # membership / service / app - item_id = Column(String(36), default=None) - item_name = Column(String(200), default=None) - amount = Column(DECIMAL(10, 2), nullable=False) - coupon_id = Column(String(36), default=None) - discount_amount = Column(DECIMAL(10, 2), default=0.00) - payment_method = Column(String(10), default=None) # wechat / alipay - payment_id = Column(String(200), default=None) - status = Column(String(15), nullable=False, default="pending") # pending / paid / refunded / cancelled - paid_at = Column(DateTime, default=None) - receipt_number = Column(String(100), default=None) - receipt_type = Column(String(20), default=None) - receipt_title = Column(String(200), default=None) - receipt_tax_number = Column(String(50), default=None) - receipt_issued_at = Column(DateTime, default=None) - created_at = Column(DateTime, default=datetime.now) - updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) - - -class Coupon(Base): - __tablename__ = "coupons" - - id = Column(String(36), primary_key=True, default=gen_uuid) - code = Column(String(50), unique=True, nullable=False) - type = Column(String(10), nullable=False) # fixed / percent - value = Column(DECIMAL(10, 2), nullable=False) - min_amount = Column(DECIMAL(10, 2), default=0.00) - valid_from = Column(DateTime, nullable=False) - valid_to = Column(DateTime, nullable=False) - total_count = Column(Integer, default=None) - used_count = Column(Integer, default=0) - status = Column(String(10), nullable=False, default="active") # active / expired / disabled - created_at = Column(DateTime, default=datetime.now) - - -class UserCoupon(Base): - __tablename__ = "user_coupons" - - id = Column(String(36), primary_key=True, default=gen_uuid) - user_id = Column(String(36), ForeignKey("users.id"), nullable=False) - coupon_id = Column(String(36), ForeignKey("coupons.id"), nullable=False) - status = Column(String(10), nullable=False, default="unused") # unused / used / expired - used_at = Column(DateTime, default=None) - created_at = Column(DateTime, default=datetime.now) - - -class ValueAddedService(Base): - __tablename__ = "value_added_services" - - id = Column(String(36), primary_key=True, default=gen_uuid) - name = Column(String(200), nullable=False) - description = Column(Text, default=None) - price = Column(DECIMAL(10, 2), nullable=False) - type = Column(String(20), nullable=False) # app_custom / platform_proxy / data_custom / private_project - features = Column(JSON, default=None) # 服务特性 - status = Column(String(10), nullable=False, default="active") # active / inactive / deleted - assigned_agent_id = Column(String(36), default=None) # 应用定制服务绑定的Agent(市场应用ID) - created_at = Column(DateTime, default=datetime.now) - updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) - - -class CustomServiceOrder(Base): - __tablename__ = "custom_service_orders" - - id = Column(String(36), primary_key=True, default=gen_uuid) - user_id = Column(String(36), ForeignKey("users.id"), nullable=False) - service_id = Column(String(36), default=None) - service_type = Column(String(10), nullable=False) - description = Column(Text, default=None) - status = Column(String(15), nullable=False, default="pending") - price = Column(DECIMAL(10, 2), default=0.00) - order_id = Column(String(36), default=None) - created_at = Column(DateTime, default=datetime.now) - updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) - - -class CustomServiceRequest(Base): - """增值服务工单表 - 用于数据定制服务和私有化项目服务""" - __tablename__ = "custom_service_requests" - - id = Column(String(36), primary_key=True, default=gen_uuid) - user_id = Column(String(36), ForeignKey("users.id"), nullable=False) - service_id = Column(String(36), ForeignKey("value_added_services.id"), nullable=False) - request_type = Column(String(20), nullable=False) # data_custom / private_project - title = Column(String(200), nullable=False) - description = Column(Text, default=None) - requirements = Column(JSON, default=None) # 需求详情JSON - status = Column(String(20), nullable=False, default="pending") # pending / processing / completed / rejected - assigned_agent_id = Column(String(36), default=None) - created_at = Column(DateTime, default=datetime.now) - updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) - - -# ============================================================ -# Token与计费 -# ============================================================ - -class ModelProvider(Base): - __tablename__ = "model_providers" - - id = Column(String(36), primary_key=True, default=gen_uuid) - name = Column(String(100), nullable=False) - protocol = Column(String(30), nullable=False, default="openai_compatible") - api_key = Column(String(255), default=None) - api_base_url = Column(String(500), default=None) - created_at = Column(DateTime, default=datetime.now) - updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) - - -class AIModel(Base): - __tablename__ = "models" - - id = Column(String(36), primary_key=True, default=gen_uuid) - provider_id = Column(String(36), ForeignKey("model_providers.id"), nullable=False) - name = Column(String(100), nullable=False) - display_name = Column(String(200), default=None, comment="display name") - api_key = Column(String(255), default=None) - endpoint = Column(String(500), default=None) - capabilities = Column(JSON, default=None) - timeout = Column(Integer, default=60) - status = Column(String(10), nullable=False, default="paid") # free / paid - price_per_million_tokens = Column(DECIMAL(10, 4), nullable=False, default=0.0000) - is_enabled = Column(Boolean, default=True) - is_default = Column(Boolean, default=False) # 全局默认模型 - total_calls = Column(BigInteger, default=0) - total_tokens = Column(BigInteger, default=0) - created_at = Column(DateTime, default=datetime.now) - updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) - - -class TokenUsageLog(Base): - __tablename__ = "token_usage_logs" - - id = Column(String(36), primary_key=True, default=gen_uuid) - user_id = Column(String(36), ForeignKey("users.id"), nullable=False) - model_id = Column(String(36), default=None) - model_name = Column(String(100), default=None) - app_id = Column(String(36), default=None) - app_name = Column(String(200), default=None) - conversation_id = Column(String(36), default=None) - trace_id = Column(String(64), default=None) - prompt_tokens = Column(Integer, default=0) - completion_tokens = Column(Integer, default=0) - total_tokens = Column(Integer, default=0) - cost = Column(DECIMAL(10, 6), default=0.000000) - created_at = Column(DateTime, default=datetime.now) - - -class TokenBalance(Base): - __tablename__ = "token_balances" - - id = Column(String(36), primary_key=True, default=gen_uuid) - user_id = Column(String(36), ForeignKey("users.id"), unique=True, nullable=False) - total_consumed = Column(BigInteger, default=0) - balance = Column(BigInteger, default=0) - daily_free_used = Column(BigInteger, default=0) - daily_free_date = Column(Date, default=None) - updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) - - -# ============================================================ -# 通知与日志 -# ============================================================ - -class Notification(Base): - __tablename__ = "notifications" - - id = Column(String(36), primary_key=True, default=gen_uuid) - user_id = Column(String(36), ForeignKey("users.id"), nullable=False) - type = Column(String(15), nullable=False) - title = Column(String(200), nullable=False) - content = Column(Text, default=None) - is_read = Column(Boolean, default=False) - created_at = Column(DateTime, default=datetime.now) - - -class OperationLog(Base): - __tablename__ = "operation_logs" - - id = Column(String(36), primary_key=True, default=gen_uuid) - user_id = Column(String(36), nullable=False) - username = Column(String(50), default=None) - action = Column(String(100), nullable=False) - target = Column(String(100), default=None) - target_id = Column(String(36), default=None) - detail = Column(Text, default=None) - ip = Column(String(45), default=None) - created_at = Column(DateTime, default=datetime.now) - - -class AuditLog(Base): - __tablename__ = "audit_logs" - - id = Column(String(36), primary_key=True, default=gen_uuid) - admin_id = Column(String(36), nullable=False) - admin_name = Column(String(50), default=None) - action = Column(String(100), nullable=False) - target_type = Column(String(50), default=None) - target_id = Column(String(36), default=None) - detail = Column(Text, default=None) - created_at = Column(DateTime, default=datetime.now) - - -class RiskRule(Base): - __tablename__ = "risk_rules" - - id = Column(String(36), primary_key=True, default=gen_uuid) - name = Column(String(100), nullable=False) - type = Column(String(15), nullable=False) - threshold = Column(Integer, default=0) - duration = Column(Integer, default=3600) - is_enabled = Column(Boolean, default=True) - created_at = Column(DateTime, default=datetime.now) - updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) - - -# ============================================================ -# 系统配置 -# ============================================================ - -class SiteSetting(Base): - __tablename__ = "site_settings" - - id = Column(String(36), primary_key=True, default=gen_uuid) - key = Column(String(100), unique=True, nullable=False) - value = Column(Text, default=None) - updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) - - -class EmailConfig(Base): - __tablename__ = "email_configs" - - id = Column(String(36), primary_key=True, default=gen_uuid) - smtp_host = Column(String(200), nullable=False) - smtp_port = Column(Integer, nullable=False, default=587) - username = Column(String(200), nullable=False) - password = Column(String(200), nullable=False) - from_address = Column(String(200), nullable=False) - use_tls = Column(Boolean, default=True) - use_ssl = Column(Boolean, default=False) - created_at = Column(DateTime, default=datetime.now) - updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) - - -class SmsConfig(Base): - __tablename__ = "sms_configs" - - id = Column(String(36), primary_key=True, default=gen_uuid) - provider = Column(String(50), nullable=False) - api_key = Column(String(200), nullable=False) - api_secret = Column(String(200), nullable=False) - sign_name = Column(String(100), default=None) - template_code = Column(String(100), default=None) - # 短信宝 (SMSBao) 字段 - username = Column(String(100), default=None) # 短信宝用户名 u - password = Column(String(200), default=None) # 短信宝密码(明文存储,发送时 MD5 32位) - goods_id = Column(String(100), default=None) # 短信宝专属通道产品 ID g - created_at = Column(DateTime, default=datetime.now) - updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) - - -class PaymentConfig(Base): - __tablename__ = "payment_configs" - - id = Column(String(36), primary_key=True, default=gen_uuid) - type = Column(String(10), nullable=False) # wechat / alipay - app_id = Column(String(100), nullable=False) - merchant_id = Column(String(100), default=None) # 微信: 商户号 - api_key = Column(String(255), nullable=False) # 微信: APIv2密钥 / 支付宝: 应用私钥 - api_v3_key = Column(String(255), default=None) # 微信: APIv3密钥 - cert_path = Column(String(500), default=None) # 微信: 证书路径 - cert_serial_no = Column(String(200), default=None) # 微信: 证书序列号 - alipay_public_key = Column(Text, default=None) # 支付宝: 支付宝公钥 - app_private_key = Column(Text, default=None) # 支付宝: 应用私钥 - gateway_url = Column(String(500), default=None) # 支付宝: 网关地址 - notify_url = Column(String(500), default=None) # 支付回调通知地址 - return_url = Column(String(500), default=None) # 支付宝: 同步跳转地址 - status = Column(String(10), nullable=False, default="inactive") # active / inactive - created_at = Column(DateTime, default=datetime.now) - updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) - - -class RegisterSetting(Base): - __tablename__ = "register_settings" - - id = Column(String(36), primary_key=True, default=gen_uuid) - phone_required = Column(Boolean, default=True) - phone_sms_required = Column(Boolean, default=True) - email_required = Column(Boolean, default=True) - email_verify_required = Column(Boolean, default=True) - real_name_required = Column(Boolean, default=False) - aliyun_access_key = Column(String(200), default=None) - aliyun_secret_key = Column(String(200), default=None) - aliyun_endpoint = Column(String(200), default=None) - sms_sign_name = Column(String(100), default=None) - sms_template_code = Column(String(50), default=None) - created_at = Column(DateTime, default=datetime.now) - updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) - - -# ============================================================ -# 广告位 -# ============================================================ - -class AdBanner(Base): - __tablename__ = "ad_banners" - - id = Column(String(36), primary_key=True, default=gen_uuid) - title = Column(String(200), nullable=False) - image_url = Column(String(500), default=None) - link_app_id = Column(String(36), default=None) - link_app_name = Column(String(200), default=None) - sort_order = Column(Integer, default=0) - click_count = Column(Integer, default=0) - status = Column(String(10), nullable=False, default="active") # active / inactive - created_at = Column(DateTime, default=datetime.now) - updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) - - -# ============================================================ -# 资产管理 -# ============================================================ - -class AssetCategory(Base): - __tablename__ = "asset_categories" - - id = Column(String(36), primary_key=True, default=gen_uuid) - name = Column(String(100), nullable=False) - type = Column(String(10), nullable=False) - icon = Column(String(200), default=None) - color = Column(String(20), default=None) - is_default = Column(Boolean, default=False) - user_id = Column(String(36), default=None) - created_at = Column(DateTime, default=datetime.now) - - -class Asset(Base): - __tablename__ = "assets" - - id = Column(String(36), primary_key=True, default=gen_uuid) - user_id = Column(String(36), ForeignKey("users.id"), nullable=False) - name = Column(String(200), nullable=False) - category = Column(String(10), nullable=False) - sub_category = Column(String(50), default=None) - file_type = Column(String(50), default=None) - size = Column(BigInteger, default=0) - description = Column(Text, default=None) - cover_url = Column(String(500), default=None) - url = Column(String(500), default=None) - content = Column(Text, default=None) - novel_meta = Column(JSON, default=None) - status = Column(String(10), nullable=False, default="active") # active / deleted - knowledge_doc_id = Column(String(36), default=None, comment="关联的知识库文档ID(由知识库同步资产时写入)") - created_at = Column(DateTime, default=datetime.now) - updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) - - -class NovelChapter(Base): - __tablename__ = "novel_chapters" - - id = Column(String(36), primary_key=True, default=gen_uuid) - asset_id = Column(String(36), ForeignKey("assets.id"), nullable=False) - title = Column(String(200), nullable=False) - word_count = Column(Integer, default=0) - sort_order = Column(Integer, default=0) - content = Column(Text(length=4294967295), default=None) - created_at = Column(DateTime, default=datetime.now) - updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) - - -# ============================================================ -# 预留扩展 -# ============================================================ - -class Team(Base): - __tablename__ = "teams" - - id = Column(String(36), primary_key=True, default=gen_uuid) - owner_id = Column(String(36), ForeignKey("users.id"), nullable=False) - name = Column(String(100), nullable=False) - description = Column(Text, default=None) - status = Column(String(10), nullable=False, default="active") - created_at = Column(DateTime, default=datetime.now) - updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) - - -class Referral(Base): - __tablename__ = "referrals" - - id = Column(String(36), primary_key=True, default=gen_uuid) - referrer_id = Column(String(36), ForeignKey("users.id"), nullable=False) - referee_id = Column(String(36), ForeignKey("users.id"), nullable=False) - referral_code = Column(String(50), default=None) - status = Column(String(10), nullable=False, default="pending") - commission_rate = Column(DECIMAL(5, 2), default=0.00) - created_at = Column(DateTime, default=datetime.now) - - -class OpenApiClient(Base): - __tablename__ = "open_api_clients" - - id = Column(String(36), primary_key=True, default=gen_uuid) - user_id = Column(String(36), ForeignKey("users.id"), nullable=False) - name = Column(String(100), nullable=False) - client_id = Column(String(64), unique=True, nullable=False) - client_secret = Column(String(128), nullable=False) - allowed_models = Column(JSON, default=None) - rate_limit = Column(Integer, default=60) - status = Column(String(10), nullable=False, default="active") - created_at = Column(DateTime, default=datetime.now) - total_calls = Column(BigInteger, default=0, comment="总调用次数") - last_used_at = Column(DateTime, default=None, comment="最后使用时间") - - -# ============================================================ -# API调用统计 -# ============================================================ - -class ApiCallStat(Base): - """API调用统计表 - 按端点聚合""" - __tablename__ = "api_call_stats" - - id = Column(String(36), primary_key=True, default=gen_uuid) - endpoint = Column(String(200), nullable=False) - method = Column(String(10), nullable=False) - call_count = Column(BigInteger, default=0) - total_response_time = Column(Float, default=0.0) - error_count = Column(BigInteger, default=0) - last_called_at = Column(DateTime, default=None) - created_at = Column(DateTime, default=datetime.now) - updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) - - __table_args__ = ( - UniqueConstraint("endpoint", "method", name="uk_endpoint_method"), - ) - - -class SlowRequestLog(Base): - """慢请求记录表""" - __tablename__ = "slow_request_logs" - - id = Column(String(36), primary_key=True, default=gen_uuid) - request_id = Column(String(64), default=None) - method = Column(String(10), nullable=False) - endpoint = Column(String(500), nullable=False) - status_code = Column(Integer, default=0) - response_time = Column(Float, default=0.0) - threshold = Column(Float, default=3.0) - created_at = Column(DateTime, default=datetime.now) - - __table_args__ = ( - Index("idx_srl_created", "created_at"), - ) - - -# ============================================================ -# AI自进化引擎 - 用户进化配置 -# ============================================================ -class UserEvolutionConfig(Base): - __tablename__ = "user_evolution_config" - id = Column(String(36), primary_key=True, default=gen_uuid) - user_id = Column(String(36), ForeignKey("users.id"), unique=True, nullable=False, index=True) - skill_dir_path = Column(String(500), nullable=True, comment="用户Skill仓库根目录") - trajectory_dir_path = Column(String(500), nullable=True, comment="用户对话轨迹目录") - watcher_status = Column(String(20), default="stopped", comment="运行中/已停止/错误") - watcher_error = Column(String(500), nullable=True, comment="watcher异常信息") - pipeline_status = Column(String(20), default="idle", comment="流水线状态: running/idle/error") - pipeline_processed = Column(Integer, default=0, comment="已处理轨迹数") - created_at = Column(DateTime, default=datetime.now) - updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) - - -class EvolutionSkill(Base): - __tablename__ = "evolution_skills" - id = Column(String(36), primary_key=True, default=gen_uuid) - user_id = Column(String(36), ForeignKey("users.id"), nullable=False, index=True) - skill_name = Column(String(200), nullable=False, comment="技能名称") - description = Column(Text, nullable=True) - tags = Column(JSON, nullable=True, comment="标签列表") - category = Column(String(100), nullable=True, comment="技能分类") - state = Column(String(20), default="baby", comment="baby/main/staging/frozen") - use_count = Column(Integer, default=0, comment="使用次数") - version = Column(Integer, default=1) - skill_md_path = Column(String(500), nullable=True, comment="SKILL.md文件路径") - git_commit_sha = Column(String(40), nullable=True) - last_used_at = Column(DateTime, nullable=True) - created_at = Column(DateTime, default=datetime.now) - updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) - - -class EvolutionEvent(Base): - __tablename__ = "evolution_events" - id = Column(String(36), primary_key=True, default=gen_uuid) - user_id = Column(String(36), ForeignKey("users.id"), nullable=False, index=True) - skill_name = Column(String(200), nullable=False) - event_type = Column(String(50), nullable=False, comment="created/promoted/rejected/frozen/unfrozen/edited/deleted/milestone") - description = Column(Text, nullable=True) - version_before = Column(Integer, nullable=True) - version_after = Column(Integer, nullable=True) - created_at = Column(DateTime, default=datetime.now) - - -class EvolutionTrajectory(Base): - __tablename__ = "evolution_trajectories" - id = Column(String(36), primary_key=True, default=gen_uuid) - user_id = Column(String(36), ForeignKey("users.id"), nullable=False, index=True) - conversation_id = Column(String(36), nullable=True, comment="关联的对话ID") - file_path = Column(String(500), nullable=True, comment="轨迹文件路径") - status = Column(String(20), default="discovered", comment="discovered/splitting/split_done/indexed/clustering/done/error/filtered") - atom_count = Column(Integer, default=0) - token_used = Column(Integer, default=0) - error_msg = Column(String(500), nullable=True, comment="轨迹处理失败的错误信息") - retry_count = Column(Integer, default=0, comment="失败重试次数") - tasks_extracted = Column(Integer, default=0, comment="拆分的AtomTask数量") - created_at = Column(DateTime, default=datetime.now) - processed_at = Column(DateTime, nullable=True) - - -class SkillTrajectoryLink(Base): - """技能-轨迹关联表:记录技能与对话轨迹的关联关系""" - __tablename__ = "skill_trajectory_links" - id = Column(String(36), primary_key=True, default=gen_uuid) - skill_name = Column(String(200), nullable=False, index=True, comment="关联的技能名称") - user_id = Column(String(36), ForeignKey("users.id"), nullable=False, index=True) - trajectory_id = Column(String(36), nullable=True, comment="关联的轨迹ID") - atom_id = Column(String(128), nullable=True, comment="原子任务ID(形如 trajectory_id_atom_N,需容纳 UUID+后缀)") - weightscore = Column(Integer, default=0, comment="关联权重分") - created_at = Column(DateTime, default=datetime.now) - - -# ============================================================ -# AI自进化v2 - 新增表 -# ============================================================ - -class SkillVersion(Base): - """技能版本历史表:存储每个版本的SKILL.md完整内容,支持回滚""" - __tablename__ = "skill_versions" - id = Column(String(36), primary_key=True, default=gen_uuid) - user_id = Column(String(36), ForeignKey("users.id"), nullable=False) - skill_name = Column(String(200), nullable=False) - version = Column(Integer, nullable=False, default=1) - skill_md_content = Column(Text(4294967295), nullable=True, comment="该版本的SKILL.md完整内容") - git_commit_sha = Column(String(40), nullable=True) - description = Column(Text, nullable=True, comment="版本变更说明") - created_at = Column(DateTime, default=datetime.now) - - __table_args__ = ( - Index("idx_sv_user_skill", "user_id", "skill_name"), - Index("idx_sv_version", "version"), - ) - - -class EvolutionReport(Base): - """进化报告表:存储周报/月报""" - __tablename__ = "evolution_reports" - id = Column(String(36), primary_key=True, default=gen_uuid) - user_id = Column(String(36), ForeignKey("users.id"), nullable=False) - report_type = Column(String(20), nullable=False, comment="weekly / monthly") - period_start = Column(Date, nullable=False) - period_end = Column(Date, nullable=False) - report_data = Column(JSON, nullable=True, comment="报告详细数据(JSON)") - created_at = Column(DateTime, default=datetime.now) - - __table_args__ = ( - Index("idx_er_user_type", "user_id", "report_type"), - Index("idx_er_period", "period_start", "period_end"), - ) - - -class SkillPublishRecord(Base): - """技能市场发布记录表""" - __tablename__ = "skill_publish_records" - id = Column(String(36), primary_key=True, default=gen_uuid) - skill_id = Column(String(36), nullable=False, comment="关联evolution_skills.id") - skill_name = Column(String(200), nullable=False) - user_id = Column(String(36), ForeignKey("users.id"), nullable=False) - market_app_id = Column(String(36), nullable=True, comment="关联market_apps.id") - price = Column(DECIMAL(10, 2), default=0.00) - market_description = Column(Text, nullable=True) - status = Column(String(20), nullable=False, default="published", comment="published/unpublished") - published_at = Column(DateTime, default=datetime.now) - created_at = Column(DateTime, default=datetime.now) - - __table_args__ = ( - Index("idx_spr_skill_id", "skill_id"), - Index("idx_spr_user_id", "user_id"), - Index("idx_spr_status", "status"), - ) - - -class TeamSharedSkill(Base): - """团队共享技能表""" - __tablename__ = "team_shared_skills" - id = Column(String(36), primary_key=True, default=gen_uuid) - team_id = Column(String(36), ForeignKey("teams.id"), nullable=False) - skill_id = Column(String(36), nullable=False, comment="关联evolution_skills.id") - skill_name = Column(String(200), nullable=False) - shared_by = Column(String(36), ForeignKey("users.id"), nullable=False) - shared_by_name = Column(String(100), nullable=True, comment="分享者昵称") - is_public = Column(Boolean, default=True, comment="团队内是否可见") - created_at = Column(DateTime, default=datetime.now) - - __table_args__ = ( - UniqueConstraint("team_id", "skill_id", name="uk_tss_team_skill"), - ) - - -class ExternalKBService(Base): - """外部知识库服务配置""" - __tablename__ = "external_kb_services" - - id = Column(String(36), primary_key=True, default=gen_uuid) - name = Column(String(200), nullable=False) - service_type = Column(String(20), nullable=False) - api_url = Column(String(500), nullable=False) - api_key = Column(String(255), default="") - config = Column(JSON, default=None) - is_enabled = Column(Boolean, default=True) - status = Column(String(20), default="disconnected") - created_at = Column(DateTime, default=datetime.now) - updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) - -class OSSStorageConfig(Base): - __tablename__ = "oss_storage_configs" - - id = Column(String(36), primary_key=True, default=gen_uuid) - name = Column(String(200), nullable=False) - provider = Column(String(20), nullable=False) - endpoint = Column(String(500), nullable=False) - access_key = Column(String(255), nullable=False) - secret_key = Column(String(255), nullable=False) - bucket = Column(String(200), nullable=False) - region = Column(String(100), default="") - base_path = Column(String(300), default="") - is_default = Column(Boolean, default=False) - is_enabled = Column(Boolean, default=True) - status = Column(String(20), default="disconnected") - config = Column(JSON, default=None) - created_at = Column(DateTime, default=datetime.now) - updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) - - - -class UserStorageQuota(Base): - __tablename__ = "user_storage_quotas" - - id = Column(String(36), primary_key=True, default=gen_uuid) - user_id = Column(String(36), nullable=False, unique=True) - quota_bytes = Column(BigInteger, nullable=False, default=1073741824) - used_bytes = Column(BigInteger, nullable=False, default=0) - quota_type = Column(String(20), nullable=False, default="membership") - membership_level = Column(Integer, default=0) - plan_id = Column(String(36), default=None) - created_at = Column(DateTime, default=datetime.now) - updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) - - -class AutomatedTask(Base): - __tablename__ = "automated_tasks" - - id = Column(String(36), primary_key=True, default=gen_uuid) - name = Column(String(200), nullable=False) - description = Column(Text, default="") - trigger_type = Column(String(20), nullable=False) - cron_expression = Column(String(100), default=None) - event_condition = Column(JSON, default=None) - action_type = Column(String(30), nullable=False) - action_config = Column(JSON, default=None) - is_enabled = Column(Boolean, default=True) - status = Column(String(20), default="idle") - max_retries = Column(Integer, default=3) - timeout_seconds = Column(Integer, default=300) - last_run_at = Column(DateTime, default=None) - next_run_at = Column(DateTime, default=None) - total_runs = Column(Integer, default=0) - success_runs = Column(Integer, default=0) - created_at = Column(DateTime, default=datetime.now) - updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) - -class TaskExecutionLog(Base): - __tablename__ = "task_execution_logs" - - id = Column(String(36), primary_key=True, default=gen_uuid) - task_id = Column(String(36), nullable=False) - task_name = Column(String(200), default="") - status = Column(String(20), default="running") - trigger_type = Column(String(20), default="manual") - started_at = Column(DateTime, default=None) - finished_at = Column(DateTime, default=None) - duration_ms = Column(Integer, default=0) - result_summary = Column(Text, default="") - error_message = Column(Text, default="") - created_at = Column(DateTime, default=datetime.now) - - - -class MCPServer(Base): - __tablename__ = "mcp_servers" - - id = Column(String(36), primary_key=True, default=gen_uuid) - name = Column(String(100), nullable=False) - description = Column(Text, default="") - transport_type = Column(String(20), default="stdio") - command = Column(String(500), default="") - args = Column(Text, default="") - env_vars = Column(Text, default="") - url = Column(String(500), default="") - headers = Column(Text, default="") - is_enabled = Column(Boolean, default=True) - status = Column(String(20), default="disconnected") - last_connected_at = Column(DateTime, default=None) - tools_count = Column(Integer, default=0) - resources_count = Column(Integer, default=0) - created_at = Column(DateTime, default=datetime.now) - updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) - - -class SmsVerificationCode(Base): - __tablename__ = "sms_verification_codes" - - id = Column(String(36), primary_key=True, default=gen_uuid) - phone = Column(String(20), nullable=False, index=True) - code = Column(String(10), nullable=False) - purpose = Column(String(30), nullable=False) # register / reset_password - expires_at = Column(DateTime, nullable=False) - used = Column(Boolean, default=False) - created_at = Column(DateTime, default=datetime.now) - - - -class ConversationSummary(Base): - __tablename__ = "conversation_summaries" - - conversation_id = Column(String(64), primary_key=True) - summary_text = Column(Text, nullable=False, default="") - token_count = Column(Integer, default=0) - structured_memory = Column(JSON, default=None) # T2: Semantica 式结构化长期记忆 - updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) +""" +AIFlow 数据库模型定义 - SQLAlchemy ORM +所有用户数据查询必须带 user_id 过滤实现物理隔离 +""" +import uuid +from datetime import datetime, date +from sqlalchemy import ( + Text, + Column, String, Integer, BigInteger, Float, Boolean, + DateTime, Date, Text, Enum, ForeignKey, Index, JSON, + DECIMAL, UniqueConstraint +) +from sqlalchemy.orm import relationship +from sqlalchemy.dialects.mysql import MEDIUMTEXT +from .database import Base + + +def gen_uuid() -> str: + return str(uuid.uuid4()) + + +# ============================================================ +# 用户与权限 +# ============================================================ + +class User(Base): + __tablename__ = "users" + + id = Column(String(36), primary_key=True, default=gen_uuid) + username = Column(String(50), unique=True, nullable=False) + email = Column(String(100), default=None) + phone = Column(String(20), default=None) + password_hash = Column(String(255), nullable=False) + avatar = Column(String(500), default=None) + role = Column(String(10), nullable=False, default="user") # user / admin + status = Column(String(10), nullable=False, default="active") # active / banned + real_name_status = Column(String(20), nullable=False, default="unverified") + real_name = Column(String(50), default=None) + id_card = Column(String(20), default=None) + membership_plan_id = Column(String(36), default=None) + membership_level = Column(Integer, default=0) + membership_expire_at = Column(DateTime, default=None) + auto_renew = Column(Boolean, default=False) + created_at = Column(DateTime, default=datetime.now) + updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) + + +class LoginLog(Base): + __tablename__ = "login_logs" + + id = Column(String(36), primary_key=True, default=gen_uuid) + user_id = Column(String(36), ForeignKey("users.id"), nullable=False) + ip = Column(String(45), default=None) + user_agent = Column(String(500), default=None) + login_at = Column(DateTime, default=datetime.now) + + +class ApiKey(Base): + __tablename__ = "api_keys" + + id = Column(String(36), primary_key=True, default=gen_uuid) + user_id = Column(String(36), ForeignKey("users.id"), nullable=False) + api_key = Column(String(64), unique=True, nullable=False) + secret = Column(String(128), nullable=False) + name = Column(String(100), default=None) + status = Column(String(10), nullable=False, default="active") # active / revoked + last_used_at = Column(DateTime, default=None) + created_at = Column(DateTime, default=datetime.now) + expired_at = Column(DateTime, default=None) + description = Column(String(500), default=None, comment="密钥描述") + rate_limit = Column(Integer, default=60, comment="速率限制(请求/分钟)") + total_calls = Column(BigInteger, default=0, comment="总调用次数") + + +# ============================================================ +# AI聊天与频道 +# ============================================================ + +class Conversation(Base): + __tablename__ = "conversations" + + id = Column(String(36), primary_key=True, default=gen_uuid) + user_id = Column(String(36), ForeignKey("users.id"), nullable=False) + title = Column(String(200), nullable=False, default="新对话") + model_id = Column(String(36), default=None) + status = Column(String(10), nullable=False, default="active") # active / archived + pinned = Column(Boolean, default=False) + knowledge_graph_count = Column(Integer, default=0) + knowledge_graph_base_id = Column(String(64), default=None, comment="已生成的知识图谱知识库ID") + source = Column(String(32), default=None, comment="对话来源,如 wechat_bot") + ext_agent_conversation_id = Column(String(128), default=None, comment="外部Agent(Dify/Coze)会话ID,用于维持多轮上下文") + created_at = Column(DateTime, default=datetime.now) + updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) + + +class Message(Base): + __tablename__ = "messages" + + id = Column(String(36), primary_key=True, default=gen_uuid) + conversation_id = Column(String(36), ForeignKey("conversations.id"), nullable=False) + role = Column(String(10), nullable=False) # user / assistant / system + content = Column(Text, nullable=False) + token_used = Column(Integer, default=0) + model_id = Column(String(36), default=None) + knowledge_ids = Column(JSON, default=None) + agent_ids = Column(JSON, default=None) + skill_ids = Column(JSON, default=None) + mcp_ids = Column(JSON, default=None, comment="该轮对话选择的MCP应用ID列表") + attachments = Column(JSON, default=None, comment="该轮对话附带的文件附件列表") + used_skills = Column(JSON, default=None, comment="该轮对话调用的进化技能名列表") + used_mcp = Column(JSON, default=None, comment="该轮对话调用的MCP应用名列表") + created_at = Column(DateTime, default=datetime.now) + + +class Channel(Base): + __tablename__ = "channels" + + id = Column(String(36), primary_key=True, default=gen_uuid) + owner_id = Column(String(36), ForeignKey("users.id"), nullable=False) + name = Column(String(100), nullable=False) + description = Column(Text, default=None) + platform = Column(String(20), nullable=False) + webhook_url = Column(String(500), default=None) + credential = Column(JSON, default=None) + oauth_state = Column(String(500), default=None) + settings = Column(JSON, default=None) + account_name = Column(String(100), default=None) + default_agent_id = Column(String(36), default=None) + default_skill_id = Column(String(36), default=None) + knowledge_base_id = Column(String(36), default=None) + bound_model_id = Column(String(36), default=None) + features = Column(JSON, default=None) + status = Column(String(20), nullable=False, default="pending") + is_active = Column(Boolean, default=True) + last_active = Column(DateTime, default=None) + created_at = Column(DateTime, default=datetime.now) + updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) + + +class ChannelMember(Base): + __tablename__ = "channel_members" + __table_args__ = (UniqueConstraint("channel_id", "user_id"),) + + id = Column(String(36), primary_key=True, default=gen_uuid) + channel_id = Column(String(36), ForeignKey("channels.id"), nullable=False) + user_id = Column(String(36), ForeignKey("users.id"), nullable=False) + role = Column(String(10), nullable=False, default="member") # owner / member + joined_at = Column(DateTime, default=datetime.now) + + +class ChannelMessage(Base): + __tablename__ = "channel_messages" + + id = Column(String(36), primary_key=True, default=gen_uuid) + channel_id = Column(String(36), ForeignKey("channels.id"), nullable=False) + user_id = Column(String(36), ForeignKey("users.id"), nullable=False) + role = Column(String(10), nullable=False) + content = Column(Text, nullable=False) + token_used = Column(Integer, default=0) + created_at = Column(DateTime, default=datetime.now) + + +# ============================================================ +# 知识库 +# ============================================================ + +class KnowledgeBase(Base): + __tablename__ = "knowledge_bases" + + id = Column(String(36), primary_key=True, default=gen_uuid) + user_id = Column(String(36), ForeignKey("users.id"), nullable=False) + name = Column(String(200), nullable=False) + description = Column(Text, default=None) + directory_path = Column(String(500), default="/") + document_count = Column(Integer, default=0) + knowledge_graph_count = Column(Integer, default=0) + created_at = Column(DateTime, default=datetime.now) + updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) + is_official = Column(Boolean, default=False) + status = Column(String(20), default="active") + external_service_id = Column(String(36), default=None) + external_id = Column(String(100), default=None) + external_sync_status = Column(String(20), default="none") + external_config = Column(JSON, default=None) + + +class KnowledgeDirectory(Base): + __tablename__ = "knowledge_directories" + + id = Column(String(36), primary_key=True, default=gen_uuid) + user_id = Column(String(36), ForeignKey("users.id"), nullable=False) + knowledge_base_id = Column(String(36), ForeignKey("knowledge_bases.id"), default=None) + parent_id = Column(String(36), default=None) + name = Column(String(200), nullable=False) + created_at = Column(DateTime, default=datetime.now) + + +class KnowledgeDocument(Base): + __tablename__ = "knowledge_documents" + + id = Column(String(36), primary_key=True, default=gen_uuid) + knowledge_base_id = Column(String(36), ForeignKey("knowledge_bases.id"), nullable=False) + directory_id = Column(String(36), ForeignKey("knowledge_directories.id"), default=None) + filename = Column(String(255), nullable=False) + file_type = Column(String(20), nullable=False) + file_size = Column(BigInteger, default=0) + file_path = Column(String(500), default=None) + status = Column(String(20), nullable=False, default="uploading") + chunk_count = Column(Integer, default=0) + error_message = Column(Text, default=None) + content = Column(Text, default=None) + created_at = Column(DateTime, default=datetime.now) + updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) + + + + +class KnowledgeDocumentChunk(Base): + """知识库文档分段表 — Stage C.1""" + __tablename__ = "knowledge_document_chunks" + + id = Column(String(36), primary_key=True, default=gen_uuid) + document_id = Column(String(36), ForeignKey("knowledge_documents.id"), nullable=False) + knowledge_base_id = Column(String(36), ForeignKey("knowledge_bases.id"), nullable=False) + chunk_index = Column(Integer, nullable=False, default=0, comment="分段序号") + content = Column(Text, nullable=False, comment="分段文本内容") + start_char = Column(Integer, default=0, comment="起始字符位置") + end_char = Column(Integer, default=0, comment="结束字符位置") + chunk_metadata = Column(JSON, default=None, comment="分段元数据(标题、关键词等)") + token_count = Column(Integer, default=0, comment="分段token数估算") + created_at = Column(DateTime, default=datetime.now) + + __table_args__ = ( + Index("idx_kdc_doc_id", "document_id"), + Index("idx_kdc_kb_id", "knowledge_base_id"), + ) + +class KnowledgeGraphNode(Base): + __tablename__ = "knowledge_graph_nodes" + + id = Column(String(36), primary_key=True, default=gen_uuid) + knowledge_base_id = Column(String(36), ForeignKey("knowledge_bases.id"), nullable=False) + source_doc_id = Column(String(36), ForeignKey("knowledge_documents.id"), default=None) + label = Column(String(500), nullable=False) + type = Column(String(50), nullable=False, comment="entity/attribute/concept/event") + properties = Column(JSON, default=None) + description = Column(Text, default=None) + created_at = Column(DateTime, default=datetime.now) + + +class KnowledgeGraphEdge(Base): + __tablename__ = "knowledge_graph_edges" + + id = Column(String(36), primary_key=True, default=gen_uuid) + knowledge_base_id = Column(String(36), ForeignKey("knowledge_bases.id"), nullable=False) + source_node_id = Column(String(36), ForeignKey("knowledge_graph_nodes.id"), nullable=False) + target_node_id = Column(String(36), ForeignKey("knowledge_graph_nodes.id"), nullable=False) + relation = Column(String(200), nullable=False) + weight = Column(Float, default=1.0) + description = Column(Text, default=None) + created_at = Column(DateTime, default=datetime.now) + + +class KnowledgeGraphBuildLog(Base): + __tablename__ = "knowledge_graph_build_logs" + + id = Column(String(36), primary_key=True, default=gen_uuid) + knowledge_base_id = Column(String(36), ForeignKey("knowledge_bases.id"), nullable=False) + document_id = Column(String(36), ForeignKey("knowledge_documents.id"), nullable=False) + node_count = Column(Integer, default=0) + edge_count = Column(Integer, default=0) + status = Column(String(20), default="completed") + created_at = Column(DateTime, default=datetime.now) + + +# ============================================================ +# 应用市场 +# ============================================================ + +class MarketApp(Base): + __tablename__ = "market_apps" + + id = Column(String(36), primary_key=True, default=gen_uuid) + name = Column(String(200), nullable=False) + type = Column(String(10), nullable=False) # skill / agent / mcp + source = Column(String(10), nullable=False, default="user") # official / user + publisher_id = Column(String(36), ForeignKey("users.id"), default=None) + publisher_name = Column(String(100), default=None) + description = Column(Text, default=None) + # 分类与标签:DB 表 market_apps 已存在 category(varchar50)/tags(varchar500) 两列, + # 此前 ORM 漏声明导致后台读取分类恒为 None -> 回退 "other"(#679 根因)。此处补齐。 + category = Column(String(50), default="other", comment="技能/应用分类(9类: writing/coding/data_analysis/qa/automation/creativity/communication/reasoning/other)") + tags = Column(String(500), default=None, comment="标签,逗号分隔") + icon = Column(String(500), default=None) + tutorial = Column(Text, default=None) + price = Column(DECIMAL(10, 2), nullable=False, default=0.00) + is_member_free = Column(Boolean, default=False) + join_promotion = Column(Boolean, default=False) + status = Column(String(10), nullable=False, default="pending") # pending / published / removed + rating_avg = Column(DECIMAL(3, 2), default=0.00) + review_count = Column(Integer, default=0) + install_count = Column(Integer, default=0) + skill_content = Column(MEDIUMTEXT, default=None) + mcp_transport = Column(String(20), default=None) + platform = Column(String(20), default=None, comment="Agent 接入平台:dify/n8n/coze") + capability = Column(String(20), default=None, comment="能力分类:text/thinking/vision/tool/image/audio/asr/ocr/embedding/rerank/video") + agent_endpoint = Column(String(500), default=None) + agent_api_key = Column(Text, default=None, comment="Agent 接入 API Key(3.2 起 Fernet 加密存储)") + tool_policy = Column(JSON, default=None, comment="Agent 工具/MCP 权限沙箱策略(3.1)") + orchestration = Column(JSON, default=None, comment="Agent 多Agent编排图(3.3)") + # Phase 4.1: 市场安检 + 版本化 + 绿标 + version = Column(String(20), default="1.0.0", comment="应用版本号(4.1)") + security_scan = Column(JSON, default=None, comment="安全扫描结果(4.1)") + dependencies = Column(JSON, default=None, comment="依赖声明(4.1)") + verified = Column(Boolean, default=False, comment="绿标/官方安检通过(4.1)") + created_at = Column(DateTime, default=datetime.now) + updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) + + +class MarketAppVersion(Base): + """Phase 4.1: 应用版本快照表,支持灰度回滚。""" + __tablename__ = "market_app_versions" + + id = Column(String(36), primary_key=True, default=gen_uuid) + app_id = Column(String(36), ForeignKey("market_apps.id"), nullable=False, index=True) + version = Column(String(20), nullable=False) + type = Column(String(10), nullable=False) + config = Column(JSON, default=None, comment="该版本完整配置快照(4.1)") + security_scan = Column(JSON, default=None, comment="该版本安检结果(4.1)") + dependencies = Column(JSON, default=None, comment="该版本依赖声明(4.1)") + created_by = Column(String(36), default=None) + created_at = Column(DateTime, default=datetime.now) + + +class AppPurchase(Base): + __tablename__ = "app_purchases" + + id = Column(String(36), primary_key=True, default=gen_uuid) + user_id = Column(String(36), ForeignKey("users.id"), nullable=False) + app_id = Column(String(36), ForeignKey("market_apps.id"), nullable=False) + price = Column(DECIMAL(10, 2), nullable=False, default=0.00) + order_id = Column(String(36), default=None) + status = Column(String(20), nullable=False, default="purchased") # purchased / installed + installed_at = Column(DateTime, default=None) + purchased_at = Column(DateTime, default=None) + created_at = Column(DateTime, default=datetime.now) + + +class AppReview(Base): + __tablename__ = "app_reviews" + __table_args__ = (UniqueConstraint("user_id", "app_id"),) + + id = Column(String(36), primary_key=True, default=gen_uuid) + user_id = Column(String(36), ForeignKey("users.id"), nullable=False) + app_id = Column(String(36), ForeignKey("market_apps.id"), nullable=False) + rating = Column(Integer, nullable=False) + content = Column(Text, default=None) + created_at = Column(DateTime, default=datetime.now) + + +class AppInstallation(Base): + __tablename__ = "app_installations" + __table_args__ = (UniqueConstraint("user_id", "app_id"),) + + id = Column(String(36), primary_key=True, default=gen_uuid) + user_id = Column(String(36), ForeignKey("users.id"), nullable=False) + app_id = Column(String(36), ForeignKey("market_apps.id"), nullable=False) + installed_at = Column(DateTime, default=datetime.now) + + +# ============================================================ +# 付费与订单 +# ============================================================ + +class MembershipPlan(Base): + __tablename__ = "membership_plans" + + id = Column(String(36), primary_key=True, default=gen_uuid) + name = Column(String(100), nullable=False) + level = Column(Integer, nullable=False, default=0) + duration_type = Column(String(10), nullable=False) # month / quarter / year + price = Column(DECIMAL(10, 2), nullable=False) + daily_free_tokens = Column(Integer, default=0) + features = Column(JSON, default=None) + free_app_ids = Column(JSON, default=None) + free_model_ids = Column(JSON, default=None) + # 配额字段 + max_agents = Column(Integer, default=5) + max_workflows = Column(Integer, default=3) + max_knowledge_bases = Column(Integer, default=2) + max_storage_mb = Column(Integer, default=1024) + api_calls_per_day = Column(Integer, default=100) + api_calls_per_min = Column(Integer, default=10) + # 权限字段 + can_publish_apps = Column(Boolean, default=False) + can_create_api_keys = Column(Boolean, default=False) + can_use_workflow = Column(Boolean, default=False) + can_use_mcp = Column(Boolean, default=False) + can_use_code_exec = Column(Boolean, default=False) + can_use_privacy_mode = Column(Boolean, default=False) + can_use_dag = Column(Boolean, default=False) + allowed_model_tiers = Column(JSON, default=None) + permissions = Column(JSON, default=None) + is_active = Column(Boolean, default=True) + created_at = Column(DateTime, default=datetime.now) + updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) + + +class MembershipSubscription(Base): + __tablename__ = "membership_subscriptions" + + id = Column(String(36), primary_key=True, default=gen_uuid) + user_id = Column(String(36), ForeignKey("users.id"), nullable=False) + plan_id = Column(String(36), ForeignKey("membership_plans.id"), nullable=False) + order_id = Column(String(36), default=None) + start_at = Column(DateTime, nullable=False) + end_at = Column(DateTime, nullable=False) + auto_renew = Column(Boolean, default=False) + status = Column(String(15), nullable=False, default="active") # active / expired / downgraded + created_at = Column(DateTime, default=datetime.now) + + +class Order(Base): + __tablename__ = "orders" + + id = Column(String(36), primary_key=True, default=gen_uuid) + user_id = Column(String(36), ForeignKey("users.id"), nullable=False) + order_type = Column(String(15), nullable=False) # membership / service / app + item_id = Column(String(36), default=None) + item_name = Column(String(200), default=None) + amount = Column(DECIMAL(10, 2), nullable=False) + coupon_id = Column(String(36), default=None) + discount_amount = Column(DECIMAL(10, 2), default=0.00) + payment_method = Column(String(10), default=None) # wechat / alipay + payment_id = Column(String(200), default=None) + status = Column(String(15), nullable=False, default="pending") # pending / paid / refunded / cancelled + paid_at = Column(DateTime, default=None) + receipt_number = Column(String(100), default=None) + receipt_type = Column(String(20), default=None) + receipt_title = Column(String(200), default=None) + receipt_tax_number = Column(String(50), default=None) + receipt_issued_at = Column(DateTime, default=None) + created_at = Column(DateTime, default=datetime.now) + updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) + + +class Coupon(Base): + __tablename__ = "coupons" + + id = Column(String(36), primary_key=True, default=gen_uuid) + code = Column(String(50), unique=True, nullable=False) + type = Column(String(10), nullable=False) # fixed / percent + value = Column(DECIMAL(10, 2), nullable=False) + min_amount = Column(DECIMAL(10, 2), default=0.00) + valid_from = Column(DateTime, nullable=False) + valid_to = Column(DateTime, nullable=False) + total_count = Column(Integer, default=None) + used_count = Column(Integer, default=0) + status = Column(String(10), nullable=False, default="active") # active / expired / disabled + created_at = Column(DateTime, default=datetime.now) + + +class UserCoupon(Base): + __tablename__ = "user_coupons" + + id = Column(String(36), primary_key=True, default=gen_uuid) + user_id = Column(String(36), ForeignKey("users.id"), nullable=False) + coupon_id = Column(String(36), ForeignKey("coupons.id"), nullable=False) + status = Column(String(10), nullable=False, default="unused") # unused / used / expired + used_at = Column(DateTime, default=None) + created_at = Column(DateTime, default=datetime.now) + + +class ValueAddedService(Base): + __tablename__ = "value_added_services" + + id = Column(String(36), primary_key=True, default=gen_uuid) + name = Column(String(200), nullable=False) + description = Column(Text, default=None) + price = Column(DECIMAL(10, 2), nullable=False) + type = Column(String(20), nullable=False) # app_custom / platform_proxy / data_custom / private_project + features = Column(JSON, default=None) # 服务特性 + status = Column(String(10), nullable=False, default="active") # active / inactive / deleted + assigned_agent_id = Column(String(36), default=None) # 应用定制服务绑定的Agent(市场应用ID) + created_at = Column(DateTime, default=datetime.now) + updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) + + +class CustomServiceOrder(Base): + __tablename__ = "custom_service_orders" + + id = Column(String(36), primary_key=True, default=gen_uuid) + user_id = Column(String(36), ForeignKey("users.id"), nullable=False) + service_id = Column(String(36), default=None) + service_type = Column(String(10), nullable=False) + description = Column(Text, default=None) + status = Column(String(15), nullable=False, default="pending") + price = Column(DECIMAL(10, 2), default=0.00) + order_id = Column(String(36), default=None) + created_at = Column(DateTime, default=datetime.now) + updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) + + +class CustomServiceRequest(Base): + """增值服务工单表 - 用于数据定制服务和私有化项目服务""" + __tablename__ = "custom_service_requests" + + id = Column(String(36), primary_key=True, default=gen_uuid) + user_id = Column(String(36), ForeignKey("users.id"), nullable=False) + service_id = Column(String(36), ForeignKey("value_added_services.id"), nullable=False) + request_type = Column(String(20), nullable=False) # data_custom / private_project + title = Column(String(200), nullable=False) + description = Column(Text, default=None) + requirements = Column(JSON, default=None) # 需求详情JSON + status = Column(String(20), nullable=False, default="pending") # pending / processing / completed / rejected + assigned_agent_id = Column(String(36), default=None) + created_at = Column(DateTime, default=datetime.now) + updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) + + +# ============================================================ +# Token与计费 +# ============================================================ + +class ModelProvider(Base): + __tablename__ = "model_providers" + + id = Column(String(36), primary_key=True, default=gen_uuid) + name = Column(String(100), nullable=False) + protocol = Column(String(30), nullable=False, default="openai_compatible") + api_key = Column(String(255), default=None) + api_base_url = Column(String(500), default=None) + created_at = Column(DateTime, default=datetime.now) + updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) + + +class AIModel(Base): + __tablename__ = "models" + + id = Column(String(36), primary_key=True, default=gen_uuid) + provider_id = Column(String(36), ForeignKey("model_providers.id"), nullable=False) + name = Column(String(100), nullable=False) + display_name = Column(String(200), default=None, comment="display name") + api_key = Column(String(255), default=None) + endpoint = Column(String(500), default=None) + capabilities = Column(JSON, default=None) + timeout = Column(Integer, default=60) + status = Column(String(10), nullable=False, default="paid") # free / paid + price_per_million_tokens = Column(DECIMAL(10, 4), nullable=False, default=0.0000) + is_enabled = Column(Boolean, default=True) + is_default = Column(Boolean, default=False) # 全局默认模型 + total_calls = Column(BigInteger, default=0) + total_tokens = Column(BigInteger, default=0) + created_at = Column(DateTime, default=datetime.now) + updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) + + +class TokenUsageLog(Base): + __tablename__ = "token_usage_logs" + + id = Column(String(36), primary_key=True, default=gen_uuid) + user_id = Column(String(36), ForeignKey("users.id"), nullable=False) + model_id = Column(String(36), default=None) + model_name = Column(String(100), default=None) + app_id = Column(String(36), default=None) + app_name = Column(String(200), default=None) + conversation_id = Column(String(36), default=None) + trace_id = Column(String(64), default=None) + prompt_tokens = Column(Integer, default=0) + completion_tokens = Column(Integer, default=0) + total_tokens = Column(Integer, default=0) + cost = Column(DECIMAL(10, 6), default=0.000000) + created_at = Column(DateTime, default=datetime.now) + + +class TokenBalance(Base): + __tablename__ = "token_balances" + + id = Column(String(36), primary_key=True, default=gen_uuid) + user_id = Column(String(36), ForeignKey("users.id"), unique=True, nullable=False) + total_consumed = Column(BigInteger, default=0) + balance = Column(BigInteger, default=0) + daily_free_used = Column(BigInteger, default=0) + daily_free_date = Column(Date, default=None) + updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) + + +# ============================================================ +# 通知与日志 +# ============================================================ + +class Notification(Base): + __tablename__ = "notifications" + + id = Column(String(36), primary_key=True, default=gen_uuid) + user_id = Column(String(36), ForeignKey("users.id"), nullable=False) + type = Column(String(15), nullable=False) + title = Column(String(200), nullable=False) + content = Column(Text, default=None) + is_read = Column(Boolean, default=False) + created_at = Column(DateTime, default=datetime.now) + + +class OperationLog(Base): + __tablename__ = "operation_logs" + + id = Column(String(36), primary_key=True, default=gen_uuid) + user_id = Column(String(36), nullable=False) + username = Column(String(50), default=None) + action = Column(String(100), nullable=False) + target = Column(String(100), default=None) + target_id = Column(String(36), default=None) + detail = Column(Text, default=None) + ip = Column(String(45), default=None) + created_at = Column(DateTime, default=datetime.now) + + +class AuditLog(Base): + __tablename__ = "audit_logs" + + id = Column(String(36), primary_key=True, default=gen_uuid) + admin_id = Column(String(36), nullable=False) + admin_name = Column(String(50), default=None) + action = Column(String(100), nullable=False) + target_type = Column(String(50), default=None) + target_id = Column(String(36), default=None) + detail = Column(Text, default=None) + created_at = Column(DateTime, default=datetime.now) + + +class RiskRule(Base): + __tablename__ = "risk_rules" + + id = Column(String(36), primary_key=True, default=gen_uuid) + name = Column(String(100), nullable=False) + type = Column(String(15), nullable=False) + threshold = Column(Integer, default=0) + duration = Column(Integer, default=3600) + is_enabled = Column(Boolean, default=True) + created_at = Column(DateTime, default=datetime.now) + updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) + + +# ============================================================ +# 系统配置 +# ============================================================ + +class SiteSetting(Base): + __tablename__ = "site_settings" + + id = Column(String(36), primary_key=True, default=gen_uuid) + key = Column(String(100), unique=True, nullable=False) + value = Column(Text, default=None) + updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) + + +class EmailConfig(Base): + __tablename__ = "email_configs" + + id = Column(String(36), primary_key=True, default=gen_uuid) + smtp_host = Column(String(200), nullable=False) + smtp_port = Column(Integer, nullable=False, default=587) + username = Column(String(200), nullable=False) + password = Column(String(200), nullable=False) + from_address = Column(String(200), nullable=False) + use_tls = Column(Boolean, default=True) + use_ssl = Column(Boolean, default=False) + created_at = Column(DateTime, default=datetime.now) + updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) + + +class SmsConfig(Base): + __tablename__ = "sms_configs" + + id = Column(String(36), primary_key=True, default=gen_uuid) + provider = Column(String(50), nullable=False) + api_key = Column(String(200), nullable=False) + api_secret = Column(String(200), nullable=False) + sign_name = Column(String(100), default=None) + template_code = Column(String(100), default=None) + # 短信宝 (SMSBao) 字段 + username = Column(String(100), default=None) # 短信宝用户名 u + password = Column(String(200), default=None) # 短信宝密码(明文存储,发送时 MD5 32位) + goods_id = Column(String(100), default=None) # 短信宝专属通道产品 ID g + created_at = Column(DateTime, default=datetime.now) + updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) + + +class PaymentConfig(Base): + __tablename__ = "payment_configs" + + id = Column(String(36), primary_key=True, default=gen_uuid) + type = Column(String(10), nullable=False) # wechat / alipay + app_id = Column(String(100), nullable=False) + merchant_id = Column(String(100), default=None) # 微信: 商户号 + api_key = Column(String(255), nullable=False) # 微信: APIv2密钥 / 支付宝: 应用私钥 + api_v3_key = Column(String(255), default=None) # 微信: APIv3密钥 + cert_path = Column(String(500), default=None) # 微信: 证书路径 + cert_serial_no = Column(String(200), default=None) # 微信: 证书序列号 + alipay_public_key = Column(Text, default=None) # 支付宝: 支付宝公钥 + app_private_key = Column(Text, default=None) # 支付宝: 应用私钥 + gateway_url = Column(String(500), default=None) # 支付宝: 网关地址 + notify_url = Column(String(500), default=None) # 支付回调通知地址 + return_url = Column(String(500), default=None) # 支付宝: 同步跳转地址 + status = Column(String(10), nullable=False, default="inactive") # active / inactive + created_at = Column(DateTime, default=datetime.now) + updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) + + +class RegisterSetting(Base): + __tablename__ = "register_settings" + + id = Column(String(36), primary_key=True, default=gen_uuid) + phone_required = Column(Boolean, default=True) + phone_sms_required = Column(Boolean, default=True) + email_required = Column(Boolean, default=True) + email_verify_required = Column(Boolean, default=True) + real_name_required = Column(Boolean, default=False) + aliyun_access_key = Column(String(200), default=None) + aliyun_secret_key = Column(String(200), default=None) + aliyun_endpoint = Column(String(200), default=None) + sms_sign_name = Column(String(100), default=None) + sms_template_code = Column(String(50), default=None) + created_at = Column(DateTime, default=datetime.now) + updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) + + +# ============================================================ +# 广告位 +# ============================================================ + +class AdBanner(Base): + __tablename__ = "ad_banners" + + id = Column(String(36), primary_key=True, default=gen_uuid) + title = Column(String(200), nullable=False) + image_url = Column(String(500), default=None) + link_app_id = Column(String(36), default=None) + link_app_name = Column(String(200), default=None) + sort_order = Column(Integer, default=0) + click_count = Column(Integer, default=0) + status = Column(String(10), nullable=False, default="active") # active / inactive + created_at = Column(DateTime, default=datetime.now) + updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) + + +# ============================================================ +# 资产管理 +# ============================================================ + +class AssetCategory(Base): + __tablename__ = "asset_categories" + + id = Column(String(36), primary_key=True, default=gen_uuid) + name = Column(String(100), nullable=False) + type = Column(String(10), nullable=False) + icon = Column(String(200), default=None) + color = Column(String(20), default=None) + is_default = Column(Boolean, default=False) + user_id = Column(String(36), default=None) + created_at = Column(DateTime, default=datetime.now) + + +class Asset(Base): + __tablename__ = "assets" + + id = Column(String(36), primary_key=True, default=gen_uuid) + user_id = Column(String(36), ForeignKey("users.id"), nullable=False) + name = Column(String(200), nullable=False) + category = Column(String(10), nullable=False) + sub_category = Column(String(50), default=None) + file_type = Column(String(50), default=None) + size = Column(BigInteger, default=0) + description = Column(Text, default=None) + cover_url = Column(String(500), default=None) + url = Column(String(500), default=None) + content = Column(Text, default=None) + novel_meta = Column(JSON, default=None) + status = Column(String(10), nullable=False, default="active") # active / deleted + knowledge_doc_id = Column(String(36), default=None, comment="关联的知识库文档ID(由知识库同步资产时写入)") + created_at = Column(DateTime, default=datetime.now) + updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) + + +class NovelChapter(Base): + __tablename__ = "novel_chapters" + + id = Column(String(36), primary_key=True, default=gen_uuid) + asset_id = Column(String(36), ForeignKey("assets.id"), nullable=False) + title = Column(String(200), nullable=False) + word_count = Column(Integer, default=0) + sort_order = Column(Integer, default=0) + content = Column(Text(length=4294967295), default=None) + created_at = Column(DateTime, default=datetime.now) + updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) + + +# ============================================================ +# 预留扩展 +# ============================================================ + +class Team(Base): + __tablename__ = "teams" + + id = Column(String(36), primary_key=True, default=gen_uuid) + owner_id = Column(String(36), ForeignKey("users.id"), nullable=False) + name = Column(String(100), nullable=False) + description = Column(Text, default=None) + status = Column(String(10), nullable=False, default="active") + created_at = Column(DateTime, default=datetime.now) + updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) + + +class Referral(Base): + __tablename__ = "referrals" + + id = Column(String(36), primary_key=True, default=gen_uuid) + referrer_id = Column(String(36), ForeignKey("users.id"), nullable=False) + referee_id = Column(String(36), ForeignKey("users.id"), nullable=False) + referral_code = Column(String(50), default=None) + status = Column(String(10), nullable=False, default="pending") + commission_rate = Column(DECIMAL(5, 2), default=0.00) + created_at = Column(DateTime, default=datetime.now) + + +class OpenApiClient(Base): + __tablename__ = "open_api_clients" + + id = Column(String(36), primary_key=True, default=gen_uuid) + user_id = Column(String(36), ForeignKey("users.id"), nullable=False) + name = Column(String(100), nullable=False) + client_id = Column(String(64), unique=True, nullable=False) + client_secret = Column(String(128), nullable=False) + allowed_models = Column(JSON, default=None) + rate_limit = Column(Integer, default=60) + status = Column(String(10), nullable=False, default="active") + created_at = Column(DateTime, default=datetime.now) + total_calls = Column(BigInteger, default=0, comment="总调用次数") + last_used_at = Column(DateTime, default=None, comment="最后使用时间") + + +# ============================================================ +# API调用统计 +# ============================================================ + +class ApiCallStat(Base): + """API调用统计表 - 按端点聚合""" + __tablename__ = "api_call_stats" + + id = Column(String(36), primary_key=True, default=gen_uuid) + endpoint = Column(String(200), nullable=False) + method = Column(String(10), nullable=False) + call_count = Column(BigInteger, default=0) + total_response_time = Column(Float, default=0.0) + error_count = Column(BigInteger, default=0) + last_called_at = Column(DateTime, default=None) + created_at = Column(DateTime, default=datetime.now) + updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) + + __table_args__ = ( + UniqueConstraint("endpoint", "method", name="uk_endpoint_method"), + ) + + +class SlowRequestLog(Base): + """慢请求记录表""" + __tablename__ = "slow_request_logs" + + id = Column(String(36), primary_key=True, default=gen_uuid) + request_id = Column(String(64), default=None) + method = Column(String(10), nullable=False) + endpoint = Column(String(500), nullable=False) + status_code = Column(Integer, default=0) + response_time = Column(Float, default=0.0) + threshold = Column(Float, default=3.0) + created_at = Column(DateTime, default=datetime.now) + + __table_args__ = ( + Index("idx_srl_created", "created_at"), + ) + + +# ============================================================ +# AI自进化引擎 - 用户进化配置 +# ============================================================ +class UserEvolutionConfig(Base): + __tablename__ = "user_evolution_config" + id = Column(String(36), primary_key=True, default=gen_uuid) + user_id = Column(String(36), ForeignKey("users.id"), unique=True, nullable=False, index=True) + skill_dir_path = Column(String(500), nullable=True, comment="用户Skill仓库根目录") + trajectory_dir_path = Column(String(500), nullable=True, comment="用户对话轨迹目录") + watcher_status = Column(String(20), default="stopped", comment="运行中/已停止/错误") + watcher_error = Column(String(500), nullable=True, comment="watcher异常信息") + pipeline_status = Column(String(20), default="idle", comment="流水线状态: running/idle/error") + pipeline_processed = Column(Integer, default=0, comment="已处理轨迹数") + created_at = Column(DateTime, default=datetime.now) + updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) + + +class EvolutionSkill(Base): + __tablename__ = "evolution_skills" + id = Column(String(36), primary_key=True, default=gen_uuid) + user_id = Column(String(36), ForeignKey("users.id"), nullable=False, index=True) + skill_name = Column(String(200), nullable=False, comment="技能名称") + description = Column(Text, nullable=True) + tags = Column(JSON, nullable=True, comment="标签列表") + category = Column(String(100), nullable=True, comment="技能分类") + state = Column(String(20), default="baby", comment="baby/main/staging/frozen") + use_count = Column(Integer, default=0, comment="使用次数") + version = Column(Integer, default=1) + skill_md_path = Column(String(500), nullable=True, comment="SKILL.md文件路径") + git_commit_sha = Column(String(40), nullable=True) + last_used_at = Column(DateTime, nullable=True) + created_at = Column(DateTime, default=datetime.now) + updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) + + +class EvolutionEvent(Base): + __tablename__ = "evolution_events" + id = Column(String(36), primary_key=True, default=gen_uuid) + user_id = Column(String(36), ForeignKey("users.id"), nullable=False, index=True) + skill_name = Column(String(200), nullable=False) + event_type = Column(String(50), nullable=False, comment="created/promoted/rejected/frozen/unfrozen/edited/deleted/milestone") + description = Column(Text, nullable=True) + version_before = Column(Integer, nullable=True) + version_after = Column(Integer, nullable=True) + created_at = Column(DateTime, default=datetime.now) + + +class EvolutionTrajectory(Base): + __tablename__ = "evolution_trajectories" + id = Column(String(36), primary_key=True, default=gen_uuid) + user_id = Column(String(36), ForeignKey("users.id"), nullable=False, index=True) + conversation_id = Column(String(36), nullable=True, comment="关联的对话ID") + file_path = Column(String(500), nullable=True, comment="轨迹文件路径") + status = Column(String(20), default="discovered", comment="discovered/splitting/split_done/indexed/clustering/done/error/filtered") + atom_count = Column(Integer, default=0) + token_used = Column(Integer, default=0) + error_msg = Column(String(500), nullable=True, comment="轨迹处理失败的错误信息") + retry_count = Column(Integer, default=0, comment="失败重试次数") + tasks_extracted = Column(Integer, default=0, comment="拆分的AtomTask数量") + created_at = Column(DateTime, default=datetime.now) + processed_at = Column(DateTime, nullable=True) + + +class SkillTrajectoryLink(Base): + """技能-轨迹关联表:记录技能与对话轨迹的关联关系""" + __tablename__ = "skill_trajectory_links" + id = Column(String(36), primary_key=True, default=gen_uuid) + skill_name = Column(String(200), nullable=False, index=True, comment="关联的技能名称") + user_id = Column(String(36), ForeignKey("users.id"), nullable=False, index=True) + trajectory_id = Column(String(36), nullable=True, comment="关联的轨迹ID") + atom_id = Column(String(128), nullable=True, comment="原子任务ID(形如 trajectory_id_atom_N,需容纳 UUID+后缀)") + weightscore = Column(Integer, default=0, comment="关联权重分") + created_at = Column(DateTime, default=datetime.now) + + +# ============================================================ +# AI自进化v2 - 新增表 +# ============================================================ + +class SkillVersion(Base): + """技能版本历史表:存储每个版本的SKILL.md完整内容,支持回滚""" + __tablename__ = "skill_versions" + id = Column(String(36), primary_key=True, default=gen_uuid) + user_id = Column(String(36), ForeignKey("users.id"), nullable=False) + skill_name = Column(String(200), nullable=False) + version = Column(Integer, nullable=False, default=1) + skill_md_content = Column(Text(4294967295), nullable=True, comment="该版本的SKILL.md完整内容") + git_commit_sha = Column(String(40), nullable=True) + description = Column(Text, nullable=True, comment="版本变更说明") + created_at = Column(DateTime, default=datetime.now) + + __table_args__ = ( + Index("idx_sv_user_skill", "user_id", "skill_name"), + Index("idx_sv_version", "version"), + ) + + +class EvolutionReport(Base): + """进化报告表:存储周报/月报""" + __tablename__ = "evolution_reports" + id = Column(String(36), primary_key=True, default=gen_uuid) + user_id = Column(String(36), ForeignKey("users.id"), nullable=False) + report_type = Column(String(20), nullable=False, comment="weekly / monthly") + period_start = Column(Date, nullable=False) + period_end = Column(Date, nullable=False) + report_data = Column(JSON, nullable=True, comment="报告详细数据(JSON)") + created_at = Column(DateTime, default=datetime.now) + + __table_args__ = ( + Index("idx_er_user_type", "user_id", "report_type"), + Index("idx_er_period", "period_start", "period_end"), + ) + + +class SkillPublishRecord(Base): + """技能市场发布记录表""" + __tablename__ = "skill_publish_records" + id = Column(String(36), primary_key=True, default=gen_uuid) + skill_id = Column(String(36), nullable=False, comment="关联evolution_skills.id") + skill_name = Column(String(200), nullable=False) + user_id = Column(String(36), ForeignKey("users.id"), nullable=False) + market_app_id = Column(String(36), nullable=True, comment="关联market_apps.id") + price = Column(DECIMAL(10, 2), default=0.00) + market_description = Column(Text, nullable=True) + status = Column(String(20), nullable=False, default="published", comment="published/unpublished") + published_at = Column(DateTime, default=datetime.now) + created_at = Column(DateTime, default=datetime.now) + + __table_args__ = ( + Index("idx_spr_skill_id", "skill_id"), + Index("idx_spr_user_id", "user_id"), + Index("idx_spr_status", "status"), + ) + + +class TeamSharedSkill(Base): + """团队共享技能表""" + __tablename__ = "team_shared_skills" + id = Column(String(36), primary_key=True, default=gen_uuid) + team_id = Column(String(36), ForeignKey("teams.id"), nullable=False) + skill_id = Column(String(36), nullable=False, comment="关联evolution_skills.id") + skill_name = Column(String(200), nullable=False) + shared_by = Column(String(36), ForeignKey("users.id"), nullable=False) + shared_by_name = Column(String(100), nullable=True, comment="分享者昵称") + is_public = Column(Boolean, default=True, comment="团队内是否可见") + created_at = Column(DateTime, default=datetime.now) + + __table_args__ = ( + UniqueConstraint("team_id", "skill_id", name="uk_tss_team_skill"), + ) + + +class ExternalKBService(Base): + """外部知识库服务配置""" + __tablename__ = "external_kb_services" + + id = Column(String(36), primary_key=True, default=gen_uuid) + name = Column(String(200), nullable=False) + service_type = Column(String(20), nullable=False) + api_url = Column(String(500), nullable=False) + api_key = Column(String(255), default="") + config = Column(JSON, default=None) + is_enabled = Column(Boolean, default=True) + status = Column(String(20), default="disconnected") + created_at = Column(DateTime, default=datetime.now) + updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) + +class OSSStorageConfig(Base): + __tablename__ = "oss_storage_configs" + + id = Column(String(36), primary_key=True, default=gen_uuid) + name = Column(String(200), nullable=False) + provider = Column(String(20), nullable=False) + endpoint = Column(String(500), nullable=False) + access_key = Column(String(255), nullable=False) + secret_key = Column(String(255), nullable=False) + bucket = Column(String(200), nullable=False) + region = Column(String(100), default="") + base_path = Column(String(300), default="") + is_default = Column(Boolean, default=False) + is_enabled = Column(Boolean, default=True) + status = Column(String(20), default="disconnected") + config = Column(JSON, default=None) + created_at = Column(DateTime, default=datetime.now) + updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) + + + +class UserStorageQuota(Base): + __tablename__ = "user_storage_quotas" + + id = Column(String(36), primary_key=True, default=gen_uuid) + user_id = Column(String(36), nullable=False, unique=True) + quota_bytes = Column(BigInteger, nullable=False, default=1073741824) + used_bytes = Column(BigInteger, nullable=False, default=0) + quota_type = Column(String(20), nullable=False, default="membership") + membership_level = Column(Integer, default=0) + plan_id = Column(String(36), default=None) + created_at = Column(DateTime, default=datetime.now) + updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) + + +class AutomatedTask(Base): + __tablename__ = "automated_tasks" + + id = Column(String(36), primary_key=True, default=gen_uuid) + name = Column(String(200), nullable=False) + description = Column(Text, default="") + trigger_type = Column(String(20), nullable=False) + cron_expression = Column(String(100), default=None) + event_condition = Column(JSON, default=None) + action_type = Column(String(30), nullable=False) + action_config = Column(JSON, default=None) + is_enabled = Column(Boolean, default=True) + status = Column(String(20), default="idle") + max_retries = Column(Integer, default=3) + timeout_seconds = Column(Integer, default=300) + last_run_at = Column(DateTime, default=None) + next_run_at = Column(DateTime, default=None) + total_runs = Column(Integer, default=0) + success_runs = Column(Integer, default=0) + created_at = Column(DateTime, default=datetime.now) + updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) + +class TaskExecutionLog(Base): + __tablename__ = "task_execution_logs" + + id = Column(String(36), primary_key=True, default=gen_uuid) + task_id = Column(String(36), nullable=False) + task_name = Column(String(200), default="") + status = Column(String(20), default="running") + trigger_type = Column(String(20), default="manual") + started_at = Column(DateTime, default=None) + finished_at = Column(DateTime, default=None) + duration_ms = Column(Integer, default=0) + result_summary = Column(Text, default="") + error_message = Column(Text, default="") + created_at = Column(DateTime, default=datetime.now) + + + +class MCPServer(Base): + __tablename__ = "mcp_servers" + + id = Column(String(36), primary_key=True, default=gen_uuid) + name = Column(String(100), nullable=False) + description = Column(Text, default="") + transport_type = Column(String(20), default="stdio") + command = Column(String(500), default="") + args = Column(Text, default="") + env_vars = Column(Text, default="") + url = Column(String(500), default="") + headers = Column(Text, default="") + is_enabled = Column(Boolean, default=True) + status = Column(String(20), default="disconnected") + last_connected_at = Column(DateTime, default=None) + tools_count = Column(Integer, default=0) + resources_count = Column(Integer, default=0) + created_at = Column(DateTime, default=datetime.now) + updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) + + +class SmsVerificationCode(Base): + __tablename__ = "sms_verification_codes" + + id = Column(String(36), primary_key=True, default=gen_uuid) + phone = Column(String(20), nullable=False, index=True) + code = Column(String(10), nullable=False) + purpose = Column(String(30), nullable=False) # register / reset_password + expires_at = Column(DateTime, nullable=False) + used = Column(Boolean, default=False) + created_at = Column(DateTime, default=datetime.now) + + + +class ConversationSummary(Base): + __tablename__ = "conversation_summaries" + + conversation_id = Column(String(64), primary_key=True) + summary_text = Column(Text, nullable=False, default="") + token_count = Column(Integer, default=0) + structured_memory = Column(JSON, default=None) # T2: Semantica 式结构化长期记忆 + updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)