From 601fc5d23925617e70359732458263ac7ce6cb79 Mon Sep 17 00:00:00 2001 From: lt Date: Fri, 1 May 2026 10:05:50 +0800 Subject: [PATCH] refactor: remove publisher, add auto DB sync, clean backups, update docs --- README.md | 2 +- platform/backend/app/api/topics.py | 16 -- platform/backend/app/core/generator.py | 6 + platform/backend/app/core/publisher.py | 49 ------ scripts/publisher.py | 217 ------------------------- tests/test_publisher.sh | 3 - 6 files changed, 7 insertions(+), 286 deletions(-) delete mode 100644 platform/backend/app/core/publisher.py delete mode 100755 scripts/publisher.py delete mode 100755 tests/test_publisher.sh diff --git a/README.md b/README.md index 1582716..f35613b 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ python -m uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload | 📊 仪表盘 | 选题总数、待发布数、今日生成 | | 🔄 流水线控制 | 触发创作、合规优化、状态监控 | | 📝 选题管理 | 列表、筛选、预览、发布 | -| 📦 发布包管理 | 生成多平台HTML发布包、复制 | + | 📋 日志查看 | creator/optimizer/collector 日志 | ## 与自动化流水线的集成 diff --git a/platform/backend/app/api/topics.py b/platform/backend/app/api/topics.py index 3984989..e318729 100644 --- a/platform/backend/app/api/topics.py +++ b/platform/backend/app/api/topics.py @@ -177,22 +177,6 @@ def list_packages(topic_id: str): return {"packages": packages} -@router.post("/{topic_id}/packages/generate") -def generate_packages(topic_id: str): - """ - 手动触发单个选题的发布包生成。 - 相当于执行 publisher.py 针对单个选题。 - """ - from ..core.publisher import run_publisher - result = run_publisher(topic_id) - if not result["ok"]: - raise HTTPException(status_code=500, detail=result["error"]) - return { - "message": f"Package generation completed for topic {topic_id}", - "topic_id": topic_id, - "output": result.get("result", "") - } - @router.delete("/{topic_id}") def delete_topic(topic_id: str, db: Session = Depends(get_db)): """删除选题""" diff --git a/platform/backend/app/core/generator.py b/platform/backend/app/core/generator.py index ef5d0b2..dfe7062 100644 --- a/platform/backend/app/core/generator.py +++ b/platform/backend/app/core/generator.py @@ -30,6 +30,11 @@ def run_creator(topic_id: str = None): ) if result.returncode != 0: logger.error(f"Creator failed: {result.stderr}") + if topic_id: + try: + sync_topic_to_db(topic_id) + except Exception as e: + logger.warning(f"Sync after creation failed: {e}") return {"ok": False, "error": result.stderr} # 解析日志,找出选择了哪个选题 @@ -42,6 +47,7 @@ def run_creator(topic_id: str = None): if "选题" in line and "已标记为「待发布」" in line: # 如: 2026-04-16 ... INFO - 选题 A01 已标记为「待发布」 import re +from .sync import sync_topic_to_db m = re.search(r'选题\s+([A-Za-z0-9]+)', line) if m: topic_id = m.group(1) diff --git a/platform/backend/app/core/publisher.py b/platform/backend/app/core/publisher.py deleted file mode 100644 index a71a1ff..0000000 --- a/platform/backend/app/core/publisher.py +++ /dev/null @@ -1,49 +0,0 @@ -from pathlib import Path -import subprocess -import sys - -PROJECT_ROOT = Path(__file__).resolve().parents[4] - -def run_publisher(topic_id: str = None): - """运行发布包生成脚本 - - Args: - topic_id: 可选,指定单个选题ID - - Returns: - dict: 包含 ok, result/error, topic_id 等字段 - """ - try: - script_path = PROJECT_ROOT / "scripts" / "publisher.py" - if not script_path.exists(): - return {"ok": False, "error": f"Publisher script not found: {script_path}"} - - cmd = [sys.executable, str(script_path)] - if topic_id: - cmd.extend(["--topic-id", topic_id]) - - result = subprocess.run( - cmd, - capture_output=True, - text=True, - cwd=PROJECT_ROOT, - timeout=300 # 5分钟超时 - ) - - if result.returncode == 0: - return { - "ok": True, - "result": result.stdout, - "topic_id": topic_id - } - else: - return { - "ok": False, - "error": result.stderr, - "result": result.stdout, - "topic_id": topic_id - } - except subprocess.TimeoutExpired: - return {"ok": False, "error": "Publisher timed out after 5 minutes", "topic_id": topic_id} - except Exception as e: - return {"ok": False, "error": str(e), "topic_id": topic_id} diff --git a/scripts/publisher.py b/scripts/publisher.py deleted file mode 100755 index e1ead14..0000000 --- a/scripts/publisher.py +++ /dev/null @@ -1,217 +0,0 @@ -#!/usr/bin/env python3 -""" -多平台内容发布脚本 -将「待发布」的文章发布到各平台(知乎/公众号/小红书/B站/头条号) -支持单个 topic 生成发布包模式(--topic-id) -""" - -import json, datetime, logging, sys, subprocess, time -from pathlib import Path -from typing import Dict, List -import argparse - -PROJECT_ROOT = Path('/root/openclaw-workspace/projects/yu-zhi-ran') -sys.path.insert(0, str(PROJECT_ROOT)) - -DATA_DIR = PROJECT_ROOT / "automation" / "data" -TOPICS_FILE = DATA_DIR / "sustainability_topics.json" -RELEASES_DIR = DATA_DIR / "releases" -LOGS_DIR = PROJECT_ROOT / "automation" / "logs" -TODAY = datetime.datetime.now().strftime("%Y-%m-%d") - -logging.basicConfig( - level=logging.INFO, - format='%(asctime)s - %(levelname)s - %(message)s', - handlers=[ - logging.FileHandler(LOGS_DIR / f"publisher_{TODAY}.log"), - logging.StreamHandler() - ] -) -logger = logging.getLogger(__name__) - -# 命令行参数 -parser = argparse.ArgumentParser(description='发布管理脚本') -parser.add_argument('--topic-id', help='仅处理指定 topic ID') -args = parser.parse_args() - -# 平台配置 -PLATFORMS = { - "zhihu": {"name": "知乎", "enabled": True, "template": "zhihu.html"}, - "wechat": {"name": "微信公众号", "enabled": True, "template": "wechat.html"}, # 需手动授权 - "xiaohongshu": {"name": "小红书", "enabled": True, "template": "xiaohongshu.html"}, - "bilibili": {"name": "B站", "enabled": False, "template": "bilibili.html"}, # 规划中 - "toutiao": {"name": "头条号", "enabled": False, "template": "toutiao.html"} # 规划中 -} - -def load_topics() -> List[Dict]: - with open(TOPICS_FILE, 'r', encoding='utf-8') as f: - return json.load(f) - -def save_topics(topics: List[Dict]): - with open(TOPICS_FILE, 'w', encoding='utf-8') as f: - json.dump(topics, f, ensure_ascii=False, indent=2) - -def get_ready_topics() -> List[Dict]: - topics = load_topics() - ready = [t for t in topics if t.get('status') == '待发布'] - ready.sort(key=lambda t: t.get('ready_at', ''), reverse=True) # 优先最新 - return ready, topics - -def publish_to_xiaohongshu(html_path: Path, topic: Dict) -> bool: - """小红书:复制HTML到发布目录(供手动发布)""" - logger.info(f"准备小红书发布: {topic['id']}") - try: - publish_base = PROJECT_ROOT / "content" / "published" - dest_dir = publish_base / topic['id'] / "手动发布" / "小红书" - dest_dir.mkdir(parents=True, exist_ok=True) - - dest_html = dest_dir / "文章.html" - import shutil - shutil.copy2(html_path, dest_html) - - logger.info(f"✅ 小红书发布包就绪: {dest_dir}") - return True, str(dest_dir) - except Exception as e: - logger.error(f"小红书发布准备失败: {e}") - return False, None - -def publish_to_zhihu(html_path: Path, topic: Dict) -> bool: - """知乎:复制HTML到发布目录""" - logger.info(f"准备知乎发布: {topic['id']}") - try: - publish_base = PROJECT_ROOT / "content" / "published" - dest_dir = publish_base / topic['id'] / "手动发布" / "知乎" - dest_dir.mkdir(parents=True, exist_ok=True) - - dest_html = dest_dir / "文章.html" - import shutil - shutil.copy2(html_path, dest_html) - - logger.info(f"✅ 知乎发布包就绪: {dest_dir}") - return True, str(dest_dir) - except Exception as e: - logger.error(f"知乎发布准备失败: {e}") - return False, None - -def publish_to_wechat(html_path: Path, topic: Dict) -> bool: - """微信公众号:复制HTML到发布目录""" - logger.info(f"准备微信公众号发布: {topic['id']}") - try: - publish_base = PROJECT_ROOT / "content" / "published" - dest_dir = publish_base / topic['id'] / "手动发布" / "微信公众号" - dest_dir.mkdir(parents=True, exist_ok=True) - - dest_html = dest_dir / "文章.html" - import shutil - shutil.copy2(html_path, dest_html) - - logger.info(f"✅ 微信公众号发布包就绪: {dest_dir}") - return True, str(dest_dir) - except Exception as e: - logger.error(f"微信公众号发布准备失败: {e}") - return False, None - -def publish_to_platform(platform: str, html_path: Path, topic: Dict) -> (bool, str): - """生成平台发布包(人工发布)""" - if platform == "xiaohongshu": - return publish_to_xiaohongshu(html_path, topic) - elif platform == "zhihu": - return publish_to_zhihu(html_path, topic) - elif platform == "wechat": - return publish_to_wechat(html_path, topic) - else: - logger.warning(f"平台 {platform} 暂未支持") - return False, None - -def main(): - logger.info("=== 多平台内容发布包生成开始 ===") - ready, all_topics = get_ready_topics() - if not ready: - logger.info("没有待发布内容") - sys.exit(0) - - # 如果指定了 topic-id,只处理该选题 - if args.topic_id: - ready = [t for t in ready if t['id'] == args.topic_id] - if not ready: - logger.info(f"未找到指定 topic ID: {args.topic_id}") - sys.exit(0) - - results = [] - for topic in ready: - tid = topic['id'] - title = topic.get('title', '')[:50] - release_date = topic.get('ready_at', TODAY) - release_dir = RELEASES_DIR / release_date - - platform_urls = topic.get('platform_urls', {}) - - for platform, config in PLATFORMS.items(): - if not config['enabled']: - continue - # 检查是否已发布过 - if platform in platform_urls and platform_urls[platform]: - logger.info(f"跳过已发布: {tid} - {platform}") - continue - - html_file = release_dir / platform / f"{platform}_{tid}_{platform}.html" - if not html_file.exists(): - logger.warning(f"HTML文件不存在: {html_file}") - continue - - # 生成发布包(不自动发布) - success, info = publish_to_platform(platform, html_file, topic) - if success: - results.append((tid, platform, info)) - logger.info(f"✅ {tid} 发布包已准备: {platform}") - else: - logger.error(f"❌ {tid} 发布包准备失败: {platform}") - - # 汇总报告 - summary_file = LOGS_DIR / f"publisher_summary_{TODAY}.json" - summary = { - "date": TODAY, - "total_ready": len(ready), - "packages_generated": len(results), - "details": [{"topic_id": r[0], "platform": r[1], "path": r[2]} for r in results] - } - with open(summary_file, 'w', encoding='utf-8') as f: - json.dump(summary, f, ensure_ascii=False, indent=2) - - # === 发送日报通知 === - try: - # 统计发布数据(按话题去重) - unique_tids = set(r[0] for r in results) - platforms_set = set(r[1] for r in results) - notify_data = { - "task": "daily_summary", - "date": TODAY, - "published_count": len(unique_tids), - "platforms": list(platforms_set), - "publish_dir": str(PROJECT_ROOT / "content" / "published") - } - notify_file = LOGS_DIR / f"publisher_notify_{TODAY}.json" - with open(notify_file, 'w', encoding='utf-8') as f: - json.dump(notify_data, f, ensure_ascii=False, indent=2) - # 调用 notifier - notifier_script = PROJECT_ROOT / "scripts" / "wecom_notifier.py" - if notifier_script.exists(): - subprocess.run( - [sys.executable, str(notifier_script), str(notify_file)], - cwd=str(PROJECT_ROOT), - capture_output=True, - text=True, - timeout=30 - ) - else: - logger.warning("Notifier script not found, skipping notification") - except Exception as e: - logger.error(f"发送日报通知失败: {e}") - # === 通知结束 === - - logger.info(f"📦 发布包生成完成: {len(results)} 个平台发布包已就绪") - print(f"PUBLISH_PACKAGES_READY: {len(results)} packages generated") - sys.exit(0) - -if __name__ == "__main__": - main() diff --git a/tests/test_publisher.sh b/tests/test_publisher.sh deleted file mode 100755 index c1abe81..0000000 --- a/tests/test_publisher.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -cd /root/openclaw-workspace/projects/yu-zhi-ran -python3 scripts/publisher.py --topic-id A01 \ No newline at end of file