feat: 数据源统一与前端预览修复

=== 后端核心 ===
- db_helper: 统一数据库访问抽象层
- system.py API:
  * 参数绑定修复: 使用 Body(embed=True) 接收 JSON
  * 添加请求日志记录
- sync.py: 仅导出 DB→JSON(备份)

=== 合规与流水线 ===
- compliance_checker: 标签检测优化(仅检查容器,避免正文误判)
- 所有脚本(creator/collector/writer/outline/research等)统一使用数据库

=== 前端改版 ===
- topics.html:
  * 创作/优化 API 路径修正
  * 预览弹窗重设计:多平台并行加载、富文本显示、单复制按钮
  * 状态中文映射(getStatusLabel)
  * 认证检查
- 所有 HTML 静态资源路径修复(移除 /static 前缀)

=== 数据一致性 ===
- 数据库状态统一为英文(pending/review/ready/published)
- 前端显示中文化映射

已测试 A03 流水线完整通过。
This commit is contained in:
lt
2026-05-07 11:25:42 +08:00
parent 8dd19a2179
commit 31d6306e3b
24 changed files with 1018 additions and 530 deletions
+77 -33
View File
@@ -13,52 +13,96 @@ if os.getenv('PROJECT_ROOT'):
TOPICS_FILE = PROJECT_ROOT / "automation" / "data" / "sustainability_topics.json"
def sync_topic_to_db(topic_id: str, db: Session = None) -> Topic:
topics = json.loads(TOPICS_FILE.read_text(encoding='utf-8'))
topic_data = next((t for t in topics if t['id'] == topic_id), None)
if not topic_data:
raise ValueError(f"Topic {topic_id} not found in file")
"""注意:此函数原用于将JSON单个选题同步到数据库。现已不需要,保留用于兼容。当前方向相反(DB为主),此处仅从数据库导出到JSON(如果需要)"""
# 为了不破坏旧调用,我们改为从数据库读取并写入 JSON 文件(单条更新)
close_db = False
if db is None:
db = SessionLocal()
close_db = True
try:
db_topic = db.query(Topic).filter(Topic.id == topic_id).first()
if db_topic is None:
db_topic = Topic(
id=topic_data['id'],
title=topic_data['title'],
field=topic_data['field'],
format=topic_data.get('format'),
core_concept=topic_data.get('core_concept'),
audience_pain=topic_data.get('audience_pain'),
unique_angle=topic_data.get('unique_angle'),
priority=topic_data.get('priority'),
priority_score=topic_data.get('priority_score', 0),
total_score=topic_data.get('total_score')
)
db.add(db_topic)
db_topic.status = topic_data.get('status', db_topic.status)
db_topic.ready_at = datetime.strptime(topic_data['ready_at'], '%Y-%m-%d').date() if topic_data.get('ready_at') else None
db_topic.published_at = datetime.strptime(topic_data['published_at'], '%Y-%m-%d').date() if topic_data.get('published_at') else None
db_topic.compliance_score = topic_data.get('compliance_score', db_topic.compliance_score)
db_topic.platform_urls = topic_data.get('platform_urls', {})
db_topic.generated_at = datetime.now() if db_topic.generated_at is None and topic_data.get("status") in ["ready", "published"] else db_topic.generated_at
db_topic.updated_at = datetime.now()
db.commit()
db.refresh(db_topic)
return db_topic
topic = db.query(Topic).filter(Topic.id == topic_id).first()
if not topic:
raise ValueError(f"Topic {topic_id} not found in DB")
# 写入 JSON 文件(作为备份)
try:
if TOPICS_FILE.exists():
with open(TOPICS_FILE, 'r', encoding='utf-8') as f:
topics = json.load(f)
else:
topics = []
# 转为字典
tdict = {
'id': topic.id,
'title': topic.title,
'field': topic.field,
'format': topic.format,
'core_concept': topic.core_concept,
'audience_pain': topic.audience_pain,
'unique_angle': topic.unique_angle,
'priority': topic.priority,
'priority_score': topic.priority_score,
'total_score': topic.total_score,
'status': topic.status,
'cases': topic.cases or [],
'source_file': topic.source_file,
'created_at': topic.created_at.isoformat() if topic.created_at else None,
'updated_at': topic.updated_at.isoformat() if topic.updated_at else None,
'ready_at': topic.ready_at.isoformat() if topic.ready_at else None,
'published_at': topic.published_at.isoformat() if topic.published_at else None,
'compliance_score': topic.compliance_score,
'platform_urls': topic.platform_urls or {}
}
# 更新或追加
found = False
for i, t in enumerate(topics):
if t['id'] == topic_id:
topics[i] = tdict
found = True
break
if not found:
topics.append(tdict)
with open(TOPICS_FILE, 'w', encoding='utf-8') as f:
json.dump(topics, f, ensure_ascii=False, indent=2)
except Exception as e:
print(f"[Warning] JSON backup failed: {e}")
return topic
finally:
if close_db:
db.close()
def sync_all_topics():
"""导出所有选题到 JSON 文件(用于备份或兼容)"""
db = SessionLocal()
try:
topics = json.loads(TOPICS_FILE.read_text(encoding='utf-8'))
topics = db.query(Topic).order_by(Topic.created_at).all()
topic_list = []
for t in topics:
sync_topic_to_db(t['id'], db)
print(f"✅ 同步 {len(topics)} 个选题到数据库")
tdict = {
'id': t.id,
'title': t.title,
'field': t.field,
'format': t.format,
'core_concept': t.core_concept,
'audience_pain': t.audience_pain,
'unique_angle': t.unique_angle,
'priority': t.priority,
'priority_score': t.priority_score,
'total_score': t.total_score,
'status': t.status,
'cases': t.cases or [],
'source_file': t.source_file,
'created_at': t.created_at.isoformat() if t.created_at else None,
'updated_at': t.updated_at.isoformat() if t.updated_at else None,
'ready_at': t.ready_at.isoformat() if t.ready_at else None,
'published_at': t.published_at.isoformat() if t.published_at else None,
'compliance_score': t.compliance_score,
'platform_urls': t.platform_urls or {}
}
topic_list.append(tdict)
TOPICS_FILE.parent.mkdir(parents=True, exist_ok=True)
with open(TOPICS_FILE, 'w', encoding='utf-8') as f:
json.dump(topic_list, f, ensure_ascii=False, indent=2)
print(f"✅ 导出 {len(topic_list)} 个选题到 JSON (兼容模式)")
finally:
db.close()