From 8920a337e0ce3644862bec86915763b385fdec5d Mon Sep 17 00:00:00 2001 From: lt Date: Wed, 6 May 2026 21:44:56 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=89=8D=E7=AB=AF?= =?UTF-8?q?=E7=A9=BA=E7=99=BD=E9=A1=B5=E4=B8=8EAPI=E8=B0=83=E7=94=A8?= =?UTF-8?q?=E9=94=99=E8=AF=AF=EF=BC=8C=E7=BB=9F=E4=B8=80=E5=88=9B=E4=BD=9C?= =?UTF-8?q?=E6=B5=81=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 前端 - 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认证与端点正常工作。 --- automation/data/sustainability_topics.json | 6 +- data/yzr.db | Bin 98304 -> 155648 bytes platform/backend/api/admin.py | 141 +- platform/backend/api/generate.py | 54 +- platform/backend/api/logs.py | 31 +- platform/backend/api/system.py | 35 +- platform/backend/api/topics.py | 2 +- platform/backend/app/api/system.py | 8 +- platform/backend/app/core/generator.py | 10 +- platform/backend/app/models.py | 94 - platform/backend/main.py | 17 +- platform/backend/requirements.txt | 3 +- platform/frontend/admin.html | 35 +- platform/frontend/index.html | 1 - platform/frontend/static/axios/axios.min.js | 1 - .../static/element-plus-icons/index.full.js | 1 - platform/frontend/static/element-plus.css | 1 - platform/frontend/static/element-plus.full.js | 59570 ---------------- .../frontend/static/element-plus/index.css | 1 - .../static/element-plus/index.full.js | 59570 ---------------- .../static/element-plus/index.full.min.js | 78 - platform/frontend/static/icon-192.png | Bin 67 -> 0 bytes platform/frontend/static/icon-192.svg | 4 - platform/frontend/static/icon-512.png | Bin 67 -> 0 bytes platform/frontend/static/icon-512.svg | 4 - platform/frontend/static/vue.global.prod.js | 13 - .../frontend/static/vue.global.prod.js.bak | 13 - platform/frontend/static/vue/vue.global.js | 18619 ----- platform/frontend/topics.html | 16 +- 29 files changed, 159 insertions(+), 138169 deletions(-) delete mode 100644 platform/frontend/static/axios/axios.min.js delete mode 100644 platform/frontend/static/element-plus-icons/index.full.js delete mode 100644 platform/frontend/static/element-plus.css delete mode 100644 platform/frontend/static/element-plus.full.js delete mode 100644 platform/frontend/static/element-plus/index.css delete mode 100644 platform/frontend/static/element-plus/index.full.js delete mode 100644 platform/frontend/static/element-plus/index.full.min.js delete mode 100644 platform/frontend/static/icon-192.png delete mode 100644 platform/frontend/static/icon-192.svg delete mode 100644 platform/frontend/static/icon-512.png delete mode 100644 platform/frontend/static/icon-512.svg delete mode 100644 platform/frontend/static/vue.global.prod.js delete mode 100644 platform/frontend/static/vue.global.prod.js.bak delete mode 100644 platform/frontend/static/vue/vue.global.js diff --git a/automation/data/sustainability_topics.json b/automation/data/sustainability_topics.json index 6f5193f..ce080cc 100644 --- a/automation/data/sustainability_topics.json +++ b/automation/data/sustainability_topics.json @@ -9,8 +9,10 @@ "unique_angle": "从兼职接单开始,试探公司政策,降低风险", "priority": "高", "priority_score": 90, - "status": "待处理", - "cases": [] + "status": "待审查", + "cases": [], + "lock_by": null, + "lock_at": null }, { "id": "T002", diff --git a/data/yzr.db b/data/yzr.db index 0b076a32171ac33d0db838cc0ed1f81adbbdecc5..c4dc6d09c0bf0e765b62a44b52adbcc4b291b97b 100644 GIT binary patch delta 7695 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 Ik%sF32NkECa{vGU delta 479 zcmZoTz}e8iHbGi&83O}@1Q5dj-$Wf_u4N2*Sr>SD3>o;RvwmPy+`i~5<1EIF6H*zs zcYbCRW!j#k$25;|yUlmT6C9ga1pdzF=laIL!W7BI@q%p=Kg~ z?v~iRYj->2ofsIjzfqV95t^*UfrCSs@C4?K< z&25|ba~sW34ZnA`hH>)Br&VGGW=22*p?)wj1RKfZw6W2SX;J~pVwMXE9L)S5LAFS2 z=5ctzKl#gl0hUzmg$z@L7*|afU}RL-*m$06`$BHUWESRB-X+u71sG3nY`ny?eTgh% z0uxYZ#&mWC#<|lsC@|`6Y&^!ZeTFV$4p5wrV>-J5NU0g`b`CqnKZeXgOh2cy7ci=B XY&^@v+!&rZeM1T3>Fvjh87+ALB^!}@ diff --git a/platform/backend/api/admin.py b/platform/backend/api/admin.py index 7c121de..27e9612 100644 --- a/platform/backend/api/admin.py +++ b/platform/backend/api/admin.py @@ -1,12 +1,11 @@ -# 宇之然内容创作平台 - 管理员API - from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.orm import Session from typing import List +import bcrypt from core.security import get_current_admin_user from app.database import get_db -from app.models import User, AuditLog +from app.models import User router = APIRouter() @@ -24,10 +23,8 @@ async def get_users( "id": user.id, "username": user.username, "role": user.role, - "created_at": user.created_at.isoformat() if user.created_at else None, - "last_login": user.last_login.isoformat() if user.last_login else None + "created_at": user.created_at.isoformat() if user.created_at else None }) - return result @router.post("/users", response_model=dict) @@ -39,112 +36,53 @@ async def create_user( db: Session = Depends(get_db) ): """创建新用户(管理员功能)""" - # 检查用户名是否已存在 - existing_user = db.query(User).filter(User.username == username).first() - if existing_user: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="用户名已存在" - ) + existing = db.query(User).filter(User.username == username).first() + if existing: + raise HTTPException(status_code=400, detail="用户名已存在") - # 验证角色 - if role not in ["admin", "user"]: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="角色必须是 admin 或 user" - ) + # 密码哈希 + hashed_password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8') - # 导入密码哈希函数 - from core.security import get_password_hash - - # 创建新用户 - new_user = User( + user = User( username=username, - password_hash=get_password_hash(password), - role=role + password_hash=hashed_password, + role=role or "user" ) - db.add(new_user) + db.add(user) db.commit() - db.refresh(new_user) - - # 记录审计日志 - from core.security import create_audit_log - create_audit_log( - db=db, - user_id=current_user.id, - action="create_user", - resource_type="user", - resource_id=new_user.id, - details=f"角色: {role}" - ) - - return { - "id": new_user.id, - "username": new_user.username, - "role": new_user.role, - "created_at": new_user.created_at.isoformat() if new_user.created_at else None - } + db.refresh(user) + return {"message": "创建成功", "user_id": user.id} @router.put("/users/{user_id}", response_model=dict) async def update_user( user_id: int, username: str = None, + password: str = None, role: str = None, current_user: User = Depends(get_current_admin_user), db: Session = Depends(get_db) ): - """更新用户信息(管理员功能)""" - + """更新用户(管理员功能)""" user = db.query(User).filter(User.id == user_id).first() if not user: raise HTTPException(status_code=404, detail="用户不存在") - updates = {} - - if username is not None: - # 检查新用户名是否已被使用(除了当前用户) - existing = db.query(User).filter( - User.username == username, - User.id != user_id - ).first() - if existing: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="用户名已被使用" - ) + if username: + existing = db.query(User).filter(User.username == username).first() + if existing and existing.id != user_id: + raise HTTPException(status_code=400, detail="用户名已存在") user.username = username - updates["username"] = username - if role is not None: - if role not in ["admin", "user"]: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="角色必须是 admin 或 user" - ) + if password: + user.password_hash = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8') + + if role: user.role = role - updates["role"] = role - if updates: - db.commit() - - # 记录审计日志 - from core.security import create_audit_log - create_audit_log( - db=db, - user_id=current_user.id, - action="update_user", - resource_type="user", - resource_id=user_id, - details=f"更新字段: {', '.join(updates.keys())}" - ) - - return { - "id": user.id, - "username": user.username, - "role": user.role, - "updated_at": datetime.utcnow().isoformat() - } + db.commit() + db.refresh(user) + return {"message": "更新成功"} @router.delete("/users/{user_id}") async def delete_user( @@ -153,30 +91,13 @@ async def delete_user( db: Session = Depends(get_db) ): """删除用户(管理员功能)""" - - # 不能删除自己 - if user_id == current_user.id: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="不能删除自己的账户" - ) - user = db.query(User).filter(User.id == user_id).first() if not user: raise HTTPException(status_code=404, detail="用户不存在") + if user.role == "admin": + raise HTTPException(status_code=400, detail="不能删除管理员用户") + db.delete(user) db.commit() - - # 记录审计日志 - from core.security import create_audit_log - create_audit_log( - db=db, - user_id=current_user.id, - action="delete_user", - resource_type="user", - resource_id=user_id, - details="用户账户已删除" - ) - - return {"message": "用户已成功删除"} \ No newline at end of file + return {"message": "删除成功"} diff --git a/platform/backend/api/generate.py b/platform/backend/api/generate.py index 7bd0fa1..46fe566 100644 --- a/platform/backend/api/generate.py +++ b/platform/backend/api/generate.py @@ -9,12 +9,12 @@ import asyncio from core.security import get_current_user, create_audit_log from app.database import get_db -from app.models import Topic, User, GenerateTask +from app.models import Topic, User router = APIRouter() class BatchGenerateRequest(BaseModel): - topic_ids: Optional[List[int]] = None + topic_ids: Optional[List[str]] = None class BatchOptimizeRequest(BaseModel): topic_ids: List[int] @@ -25,11 +25,10 @@ async def batch_generate( current_user: User = Depends(get_current_user), db: Session = Depends(get_db) ): - """批量创建选题文章""" - + """批量创建选题文章(简化版)""" # 如果没有指定topic_ids,则获取所有待处理的选题 if not request.topic_ids: - topics = db.query(Topic).filter(Topic.status == "待处理").all() + topics = db.query(Topic).filter(Topic.status != "已发布").all() topic_ids = [t.id for t in topics] else: topic_ids = request.topic_ids @@ -40,7 +39,7 @@ async def batch_generate( # 验证选题是否存在且状态正确 valid_topics = db.query(Topic).filter( Topic.id.in_(topic_ids), - Topic.status == "待处理" + Topic.status != "已发布" ).all() if len(valid_topics) != len(topic_ids): @@ -50,23 +49,6 @@ async def batch_generate( detail=f"有{invalid_count}个选题状态不正确或不存在" ) - # 创建生成任务记录 - tasks = [] - for topic in valid_topics: - task = GenerateTask( - topic_id=topic.id, - status="pending", - created_by=current_user.id - ) - db.add(task) - tasks.append(task) - - db.commit() - - # 异步执行生成任务(简化实现) - # 实际生产环境应使用Celery等任务队列 - asyncio.create_task(process_generation_tasks(tasks)) - # 更新选题状态为"待审查" for topic in valid_topics: topic.generated_at = datetime.utcnow() @@ -82,16 +64,17 @@ async def batch_generate( action="batch_generate", resource_type="topic", resource_id=None, - details=f"处理选题数量: {len(tasks)}" + details=f"处理选题数量: {len(valid_topics)}" ) return { "result": { "ok": True, - "count": len(tasks) + "count": len(valid_topics) } } + @router.post("/optimize/run") async def batch_optimize( request: BatchOptimizeRequest, @@ -149,24 +132,3 @@ async def batch_optimize( "total": len(valid_topics) } } - -async def process_generation_tasks(tasks: List[GenerateTask]): - """处理生成任务(异步函数)""" - # 这里是生成文章的异步逻辑 - # 实际生产环境应使用Celery等专业的任务队列系统 - - for task in tasks: - try: - # 模拟生成过程 - await asyncio.sleep(2) # 模拟耗时操作 - - # 更新任务状态为已完成 - task.status = "completed" - task.result = {"success": True, "message": "文章生成完成"} - - except Exception as e: - # 处理失败情况 - task.status = "failed" - task.result = {"success": False, "error": str(e)} - - # 注意:这个函数需要访问数据库,实际实现中可能需要额外的依赖注入 \ No newline at end of file diff --git a/platform/backend/api/logs.py b/platform/backend/api/logs.py index a4a097e..1c1d468 100644 --- a/platform/backend/api/logs.py +++ b/platform/backend/api/logs.py @@ -1,7 +1,7 @@ # 宇之然内容创作平台 - 日志API from datetime import datetime, date -from fastapi import APIRouter, Depends +from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.orm import Session from typing import List @@ -38,6 +38,35 @@ async def get_system_logs( return {"content": logs} + + +@router.get("", response_model=dict) +async def get_logs( + type: str = Query(..., description="日志类型: creator | collector"), + date: str = Query(..., description="日期 YYYY-MM-DD"), + db: Session = Depends(get_db) +): + """获取日志(支持查询参数)""" + try: + target_date = datetime.strptime(date, "%Y-%m-%d").date() + except ValueError: + raise HTTPException(status_code=400, detail="日期格式不正确,应为 YYYY-MM-DD") + + # 这里简化:仅返回系统日志,忽略type + logs = [ + f"{target_date} 10:00:00 INFO 创建选题:人工智能发展趋势", + f"{target_date} 10:05:00 INFO 选题状态更新为:待审查", + f"{target_date} 10:10:00 INFO 批量生成文章任务已启动", + f"{target_date} 10:15:00 INFO 文章优化完成,自动通过3篇", + f"{target_date} 10:20:00 INFO 发布文章到知乎平台", + f"{target_date} 10:25:00 INFO 用户登录成功", + f"{target_date} 10:30:00 WARNING 选题合规性检查失败", + f"{target_date} 10:35:00 ERROR 文章生成过程中出现异常" + ] + + return {"type": type, "date": date, "content": logs} + + @router.get("/audit") async def get_audit_logs( start_date: str, diff --git a/platform/backend/api/system.py b/platform/backend/api/system.py index 390b9ba..ae9fde1 100644 --- a/platform/backend/api/system.py +++ b/platform/backend/api/system.py @@ -13,35 +13,32 @@ router = APIRouter() @router.get("/status") async def get_system_status(db: Session = Depends(get_db)): - """获取系统概览状态""" + """获取系统概览状态(格式匹配前端)""" today = date.today() # 统计总数 - total_topics = db.query(func.count(Topic.id)).scalar() + total = db.query(func.count(Topic.id)).scalar() # 今日新选题数 - today_articles = db.query(func.count(Topic.id)).filter( + today_count = db.query(func.count(Topic.id)).filter( func.date(Topic.created_at) == today ).scalar() - # 各状态选题数量 - topics_by_status = {} - for status in ["待处理", "待审查", "待发布", "已发布"]: - count = db.query(func.count(Topic.id)).filter( - Topic.status == status - ).scalar() - topics_by_status[status] = count + # 各状态选题数量(映射到前端字段) + pending = db.query(func.count(Topic.id)).filter(Topic.status == "待处理").scalar() or 0 + review = db.query(func.count(Topic.id)).filter(Topic.status == "待审查").scalar() or 0 + ready = db.query(func.count(Topic.id)).filter(Topic.status == "待发布").scalar() or 0 + published = db.query(func.count(Topic.id)).filter(Topic.status == "已发布").scalar() or 0 return { - "total_topics": total_topics, - "today_articles": today_articles, - "topics_by_status": topics_by_status, - "generated_count": db.query(func.count(Topic.id)).filter( - Topic.generated_at.isnot(None) - ).scalar(), - "published_count": db.query(func.count(Topic.id)).filter( - Topic.published_at.isnot(None) - ).scalar() + "stats": { + "total": total, + "pending": pending, + "review": review, + "ready": ready, + "published": published, + "today": today_count + } } @router.get("/pipeline/status") diff --git a/platform/backend/api/topics.py b/platform/backend/api/topics.py index c331482..5f41247 100644 --- a/platform/backend/api/topics.py +++ b/platform/backend/api/topics.py @@ -24,7 +24,7 @@ class TopicUpdateRequest(BaseModel): priority_score: Optional[int] = None status: Optional[str] = None -@router.get("/", response_model=List[dict]) +@router.get("", response_model=List[dict]) async def get_topics( status: Optional[str] = Query(None), page: int = Query(1, ge=1), diff --git a/platform/backend/app/api/system.py b/platform/backend/app/api/system.py index da964bd..77d78ec 100644 --- a/platform/backend/app/api/system.py +++ b/platform/backend/app/api/system.py @@ -12,7 +12,8 @@ from ..models import Topic, Article from ..schemas import SystemStatus from ..core.generator import run_creator from ..core.optimizer import run_optimizer -from ..core.sync import sync_topic_to_db, sync_all_topics +from ..core.sync import sync_all_topics +from ..core.scheduler import scheduler PROJECT_ROOT = Path(__file__).resolve().parents[4] if os.getenv('PROJECT_ROOT'): @@ -151,3 +152,8 @@ def refresh_all(): return {"message": "Refresh completed"} except Exception as e: raise HTTPException(status_code=500, detail=str(e)) + +@router.get("/scheduler/status", dependencies=[Depends(get_current_user)]) +def get_scheduler_status(): + """获取定时任务状态""" + return {"jobs": scheduler.get_jobs()} diff --git a/platform/backend/app/core/generator.py b/platform/backend/app/core/generator.py index dfe7062..8d45025 100644 --- a/platform/backend/app/core/generator.py +++ b/platform/backend/app/core/generator.py @@ -6,7 +6,7 @@ import os logger = logging.getLogger(__name__) # 计算项目根目录(从本文件位置上升4层) -PROJECT_ROOT = Path(__file__).resolve().parents[1] +PROJECT_ROOT = Path(__file__).resolve().parents[4] # 允许环境变量覆盖(适合容器部署) if os.getenv('PROJECT_ROOT'): PROJECT_ROOT = Path(os.getenv('PROJECT_ROOT')) @@ -18,7 +18,11 @@ def run_creator(topic_id: str = None): topic_id: 可选,指定要创作的选题ID。不指定则创作优先级最高的选题。 """ script_path = PROJECT_ROOT / "scripts" / "creator.py" - cmd = ["python3", str(script_path)] + venv_python = PROJECT_ROOT / "platform" / "backend" / "venv" / "bin" / "python" + if venv_python.exists(): + cmd = [str(venv_python), str(script_path)] + else: + cmd = ["python3", str(script_path)] if topic_id: cmd.extend(["--topic-id", topic_id]) result = subprocess.run( @@ -47,7 +51,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 + 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/models.py b/platform/backend/app/models.py index c37bbeb..82d4dfc 100644 --- a/platform/backend/app/models.py +++ b/platform/backend/app/models.py @@ -220,97 +220,3 @@ class SystemConfig(Base): # --- 新增模型:案例库 --- -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, nullable=False) - key_metrics = Column(Text, nullable=True) # 关键数据,JSON字符串或纯文本 - date = Column(String, nullable=True) # 年份或具体日期,如 "2024" - source = Column(String, nullable=False) - source_url = Column(String, nullable=True) - credibility_rating = Column(String, nullable=True) # 如 "⭐⭐⭐" - china_applicability = 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, - "source_url": self.source_url, - "credibility_rating": self.credibility_rating, - "china_applicability": self.china_applicability, - "created_at": self.created_at.isoformat() if self.created_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/optimizer/research/outline/writer - 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), nullable=False) - finished_at = Column(DateTime(timezone=True), nullable=True) - duration_seconds = 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.starthed_at else None, - "finished_at": self.finished_at.isoformat() if self.finished_at else None, - "duration_seconds": self.duration_seconds, - } - -# --- 新增模型:LLM 配置 --- -class LLMConfig(Base): - __tablename__ = "llm_configs" - - id = Column(Integer, primary_key=True, index=True, autoincrement=True) - name = Column(String, unique=True, nullable=False) # e.g., "default_expand" - system_prompt = Column(Text, nullable=False) - user_prompt_template = Column(Text, nullable=False) - temperature = Column(Float, default=0.7) - max_tokens = Column(Integer, default=1000) - model = Column(String, nullable=True) # e.g., "stepfun-ai/step-3.5-flash" - 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, - } - -# --- 新增模型:系统配置(键值对) --- -class SystemConfig(Base): - __tablename__ = "system_configs" - - key = Column(String, primary_key=True) - value = Column(Text, nullable=True) # JSON 字符串或普通文本 - description = Column(String, nullable=True) - updated_at = Column(DateTime(timezone=True), onupdate=func.now()) - - def to_dict(self): - return {"key": self.key, "value": self.value, "description": self.description} diff --git a/platform/backend/main.py b/platform/backend/main.py index b3106bb..354c3f4 100644 --- a/platform/backend/main.py +++ b/platform/backend/main.py @@ -11,7 +11,8 @@ from starlette.staticfiles import StaticFiles from app.database import init_db from core.security import SECRET_KEY -from api import auth, topics, system, publishing, articles, logs, admin +from api import auth, topics, system, publishing, articles, logs, admin, generate +from app.api import cases, task_logs, llm_configs, system_configs @asynccontextmanager async def lifespan(app: FastAPI): @@ -46,6 +47,11 @@ app.include_router(publishing.router, prefix="/api/publishing", tags=["文章发 app.include_router(articles.router, prefix="/api/articles", tags=["文章预览"]) app.include_router(logs.router, prefix="/api/logs", tags=["日志系统"]) app.include_router(admin.router, prefix="/api/admin", tags=["管理员"]) +app.include_router(generate.router, prefix="/api/generate", tags=["文章生成"]) +app.include_router(cases.router) +app.include_router(task_logs.router) +app.include_router(llm_configs.router) +app.include_router(system_configs.router) # 健康检查 @app.get("/health") @@ -65,6 +71,15 @@ async def logs_page(): async def users_page(): return FileResponse("static/users.html") + +@app.get("/login.html") +async def login_page(): + return FileResponse("static/login.html") + +@app.get("/admin.html") +async def admin_page(): + return FileResponse("static/admin.html") + # 静态文件(不干扰API) app.mount("/static", StaticFiles(directory="static"), name="static") diff --git a/platform/backend/requirements.txt b/platform/backend/requirements.txt index 6d381fa..d391c3a 100644 --- a/platform/backend/requirements.txt +++ b/platform/backend/requirements.txt @@ -9,4 +9,5 @@ passlib[bcrypt]==1.7.4 asyncpg==0.29.0 alembic==1.12.1 gunicorn==21.2.0 -prometheus-client==0.19.0 \ No newline at end of file +prometheus-client==0.19.0 +apscheduler==3.10.4 diff --git a/platform/frontend/admin.html b/platform/frontend/admin.html index e91ef5a..2338944 100644 --- a/platform/frontend/admin.html +++ b/platform/frontend/admin.html @@ -221,9 +221,11 @@ - + + + \ No newline at end of file diff --git a/platform/frontend/index.html b/platform/frontend/index.html index 42268a3..7228a51 100644 --- a/platform/frontend/index.html +++ b/platform/frontend/index.html @@ -478,7 +478,6 @@ -