Files
yu-zhi-ran/platform/backend/app/models.py
T
lt 8920a337e0 fix: 修复前端空白页与API调用错误,统一创作流程
- 前端
  - topics.html: 恢复结构并修复Vue初始化问题
  - 调整创作按钮逻辑:仅已发布选题禁用
  - 修正API端点与payload格式(generate/optimizer/publishing使用topic_ids数组)
  - 移除ElementPlus图标模块依赖,使用全局构建
  - admin.html: 回退至Options API版本,解决this上下文错误
- 后端
  - 注册/api/generate/run路由
  - 简化generate逻辑:允许非已发布选题重创作,更新状态为“待审查”
  - 统一logs查询接口支持query参数
  - 修复admin用户管理字段引用
  - 系统概览返回{ stats }结构
- 静态资源整理
  - 删除冗余element-plus-icons、重复CSS/JS、图标文件
  - 正确放置Vue和ElementPlus全局文件
- 数据库与数据
  - 补充30个案例
  - 更新选题状态与初始数据

验证:所有页面可访问,API认证与端点正常工作。
2026-05-06 21:44:56 +08:00

223 lines
9.2 KiB
Python

from sqlalchemy import Column, String, Integer, Float, Date, DateTime, Text, Boolean, JSON
from sqlalchemy.sql import func
from .database import Base
from datetime import datetime
class AuditLog(Base):
__tablename__ = "audit_logs"
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
user_id = Column(Integer, nullable=True, index=True) # 操作用户ID(未登录/匿名可为空)
username = Column(String, nullable=False) # 操作用户名(冗余存储)
action = Column(String, nullable=False, index=True) # 操作类型: login/logout/create_user/update_user/delete_user/publish/etc.
resource_type = Column(String, nullable=True, index=True) # 资源类型: user/topic/publish_record/etc.
resource_id = Column(String, nullable=True) # 资源ID
details = Column(JSON, default=dict, nullable=True) # 操作详情(变更前后、额外信息等)
ip_address = Column(String, nullable=True) # IP 地址
user_agent = Column(String, nullable=True) # User-Agent
created_at = Column(DateTime(timezone=True), server_default=func.now())
def to_dict(self):
return {
"id": self.id,
"user_id": self.user_id,
"username": self.username,
"action": self.action,
"resource_type": self.resource_type,
"resource_id": self.resource_id,
"details": self.details or {},
"ip_address": self.ip_address,
"user_agent": self.user_agent,
"created_at": self.created_at.isoformat() if self.created_at else None
}
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
username = Column(String, unique=True, nullable=False, index=True)
password_hash = Column(String, nullable=False) # bcrypt 哈希
role = Column(String, default="user", nullable=False) # admin/user
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
def to_dict(self):
return {
"id": self.id,
"username": self.username,
"role": self.role,
"created_at": self.created_at.isoformat() if self.created_at else None
}
class Topic(Base):
__tablename__ = "topics"
id = Column(String, primary_key=True, index=True)
title = Column(String, nullable=False)
field = Column(String, nullable=False)
format = Column(String)
core_concept = Column(Text)
audience_pain = Column(Text)
unique_angle = Column(Text)
priority = Column(String) # 高/中
priority_score = Column(Integer, default=0)
total_score = Column(Float)
status = Column(String, default="pending") # pending/draft/ready/published
cases = Column(JSON, default=list)
source_file = Column(String)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
generated_at = Column(DateTime(timezone=True), nullable=True) # 选题创作完成时间
ready_at = Column(Date)
published_at = Column(Date)
compliance_score = Column(Integer)
platform_urls = Column(JSON, default=dict) # {"zhihu": "...", "wechat": "...", "xiaohongshu": "..."}
class Article(Base):
__tablename__ = "articles"
id = Column(String, primary_key=True) # e.g., A01_zhihu
topic_id = Column(String, nullable=False)
platform = Column(String, nullable=False)
file_path = Column(String, nullable=False)
status = Column(String, default="draft") # draft/optimized/published
created_at = Column(DateTime(timezone=True), server_default=func.now())
compliance_score = Column(Integer)
html_content = Column(Text) # 可缓存HTML内容以便预览
class PublishRecord(Base):
__tablename__ = "publish_records"
id = Column(Integer, primary_key=True, index=True)
topic_id = Column(String, nullable=False)
platform = Column(String, nullable=False) # 发布平台:zhihu/wechat/xiaohongshu
action = Column(String, nullable=False) # 操作:publish/update/delete/invalid
status = Column(String, nullable=False) # 状态:success/failed/partial/cancelled
operator = Column(String, nullable=True) # 操作人
description = Column(Text, nullable=True) # 发布说明
suggestion = Column(Text, nullable=True) # 建议内容
url = Column(String, nullable=True) # 发布后链接
error_msg = Column(Text, nullable=True) # 错误信息
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
# 关联查询
# 可以添加外键关联到 Topic, 但这里保持简单
class Case(Base):
__tablename__ = "cases"
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
title = Column(String, nullable=False)
field = Column(String, nullable=False) # 对应四大支柱或其子领域
summary = Column(Text)
key_metrics = Column(Text, nullable=True) # 关键数据
date = Column(String, nullable=True) # 日期或年份字符串
source = Column(String, nullable=True)
credibility_rating = Column(String, nullable=True) # 如 "⭐⭐⭐"
china_applicability = Column(String, nullable=True) # 如 "⭐⭐⭐⭐"
source_url = Column(String, nullable=True)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
def to_dict(self):
return {
"id": self.id,
"title": self.title,
"field": self.field,
"summary": self.summary,
"key_metrics": self.key_metrics,
"date": self.date,
"source": self.source,
"credibility_rating": self.credibility_rating,
"china_applicability": self.china_applicability,
"source_url": self.source_url,
"created_at": self.created_at.isoformat() if self.created_at else None,
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
}
class TaskLog(Base):
__tablename__ = "task_logs"
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
task_name = Column(String, nullable=False) # collector, creator, research, outline, writer, optimizer
topic_id = Column(String, nullable=True) # 关联选题ID
status = Column(String, nullable=False) # started, completed, failed
message = Column(Text, nullable=True)
started_at = Column(DateTime(timezone=True), server_default=func.now())
finished_at = Column(DateTime(timezone=True), nullable=True)
duration = Column(Integer, nullable=True) # 秒数
def to_dict(self):
return {
"id": self.id,
"task_name": self.task_name,
"topic_id": self.topic_id,
"status": self.status,
"message": self.message,
"started_at": self.started_at.isoformat() if self.started_at else None,
"finished_at": self.finished_at.isoformat() if self.finished_at else None,
"duration": self.duration,
}
class LLMConfig(Base):
__tablename__ = "llm_configs"
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
name = Column(String, unique=True, nullable=False) # 如 default_expand
system_prompt = Column(Text, nullable=True)
user_prompt_template = Column(Text, nullable=False) # 含占位符 {topic.get('title')} 等
temperature = Column(Float, default=0.7)
max_tokens = Column(Integer, default=2000)
model = Column(String, nullable=True) # 后端模型名
is_active = Column(Boolean, default=True)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
def to_dict(self):
return {
"id": self.id,
"name": self.name,
"system_prompt": self.system_prompt,
"user_prompt_template": self.user_prompt_template,
"temperature": self.temperature,
"max_tokens": self.max_tokens,
"model": self.model,
"is_active": self.is_active,
"created_at": self.created_at.isoformat() if self.created_at else None,
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
}
class SystemConfig(Base):
__tablename__ = "system_configs"
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
key = Column(String, unique=True, nullable=False)
value = Column(Text, nullable=True) # 可存储 JSON 字符串
description = Column(String, nullable=True)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
def to_dict(self):
import json
return {
"id": self.id,
"key": self.key,
"value": json.loads(self.value) if self.value else None,
"description": self.description,
"created_at": self.created_at.isoformat() if self.created_at else None,
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
}
# --- 新增模型:案例库 ---