feat: 完成布局优化 - 操作列固定、批量按钮自适应、分类标签带数量
优化内容: 1. 表格布局: - 使用 calc(100vw - 160px) 确保表格不超出视口 - 操作列 fixed='right' 固定在右侧,宽度 300px - 按钮 3 个后自动换行 (max-width: 200px) - 恢复合理列宽,不再过度压缩 2. 批量操作区域: - 容器改为 inline-block,宽度自适应按钮内容 - 背景宽度与按钮总宽度匹配 3. 分类标签: - 显示数量 (如 '待处理 (20)') - 点击切换筛选,去掉误导的 'X' 图标 4. 删除功能: - 操作列增加删除按钮 - 删除前弹出确认对话框 5. 系统日志: - 修复后端日志路径 (parents[4]) - 404 时显示友好提示 6. 其他: - 左侧菜单宽度 160px - 所有功能保留 (登录、用户管理、批量操作等)
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
# 宇之然内容创作平台 - 管理员API
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
|
||||
from ..core.security import get_current_admin_user
|
||||
from ..database import get_db
|
||||
from ..models import User, AuditLog
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.get("/users", response_model=List[dict])
|
||||
async def get_users(
|
||||
current_user: User = Depends(get_current_admin_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取用户列表(管理员功能)"""
|
||||
|
||||
users = db.query(User).all()
|
||||
result = []
|
||||
for user in users:
|
||||
result.append({
|
||||
"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
|
||||
})
|
||||
|
||||
return result
|
||||
|
||||
@router.post("/users", response_model=dict)
|
||||
async def create_user(
|
||||
username: str,
|
||||
password: str,
|
||||
role: str = "user",
|
||||
current_user: User = Depends(get_current_admin_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="用户名已存在"
|
||||
)
|
||||
|
||||
# 验证角色
|
||||
if role not in ["admin", "user"]:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="角色必须是 admin 或 user"
|
||||
)
|
||||
|
||||
# 导入密码哈希函数
|
||||
from ..core.security import get_password_hash
|
||||
|
||||
# 创建新用户
|
||||
new_user = User(
|
||||
username=username,
|
||||
password_hash=get_password_hash(password),
|
||||
role=role
|
||||
)
|
||||
db.add(new_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
|
||||
}
|
||||
|
||||
@router.put("/users/{user_id}", response_model=dict)
|
||||
async def update_user(
|
||||
user_id: int,
|
||||
username: 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="用户名已被使用"
|
||||
)
|
||||
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"
|
||||
)
|
||||
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()
|
||||
}
|
||||
|
||||
@router.delete("/users/{user_id}")
|
||||
async def delete_user(
|
||||
user_id: int,
|
||||
current_user: User = Depends(get_current_admin_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="用户不存在")
|
||||
|
||||
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": "用户已成功删除"}
|
||||
@@ -0,0 +1,63 @@
|
||||
# 宇之然内容创作平台 - 文章预览API
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import Dict
|
||||
|
||||
from ..database import get_db
|
||||
from ..models import Topic
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.get("/{topic_id}/preview")
|
||||
async def get_preview(
|
||||
topic_id: int,
|
||||
platform: str = "zhihu",
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取文章预览HTML"""
|
||||
|
||||
# 检查选题是否存在
|
||||
topic = db.query(Topic).filter(Topic.id == topic_id).first()
|
||||
if not topic:
|
||||
raise HTTPException(status_code=404, detail="文章不存在")
|
||||
|
||||
# 生成预览HTML(简化实现)
|
||||
preview_html = generate_preview_html(topic.title, platform)
|
||||
|
||||
return {"html": preview_html}
|
||||
|
||||
def generate_preview_html(title: str, platform: str) -> str:
|
||||
"""根据平台和标题生成预览HTML"""
|
||||
|
||||
base_template = f"""
|
||||
<div class="article-preview">
|
||||
<header class="header">
|
||||
<h1>{title}</h1>
|
||||
<div class="meta">
|
||||
<span class="platform">{platform}</span>
|
||||
<span class="date">2026-04-26</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="content">
|
||||
<p>这里是文章的正文内容...</p>
|
||||
<p>文章包含多个段落,展示不同的写作风格和结构。</p>
|
||||
<p>在实际生产环境中,这里应该是完整的文章内容。</p>
|
||||
</div>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="tags">
|
||||
<span class="tag">#人工智能</span>
|
||||
<span class="tag">#科技</span>
|
||||
<span class="tag">#趋势</span>
|
||||
</div>
|
||||
<div class="interaction">
|
||||
<button class="like">👍 点赞</button>
|
||||
<button class="share">🔗 分享</button>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
"""
|
||||
|
||||
return base_template
|
||||
@@ -0,0 +1,146 @@
|
||||
# 宇之然内容创作平台 - 认证API
|
||||
|
||||
from datetime import timedelta
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Request
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import Optional
|
||||
import secrets
|
||||
|
||||
from ..core.security import (
|
||||
verify_password, get_password_hash, create_access_token,
|
||||
create_audit_log
|
||||
)
|
||||
from ..database import get_db
|
||||
from ..models import User
|
||||
from pydantic import BaseModel
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
class LoginRequest(BaseModel):
|
||||
username: str
|
||||
password: str
|
||||
|
||||
class LoginResponse(BaseModel):
|
||||
token: str
|
||||
role: str
|
||||
user: dict
|
||||
|
||||
class RegisterRequest(BaseModel):
|
||||
username: str
|
||||
password: str
|
||||
role: str = "user"
|
||||
|
||||
@router.post("/login", response_model=LoginResponse)
|
||||
async def login(
|
||||
login_data: LoginRequest,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""用户登录"""
|
||||
user = db.query(User).filter(User.username == login_data.username).first()
|
||||
|
||||
# 检查用户是否存在和密码是否正确
|
||||
if not user or not verify_password(login_data.password, user.password_hash):
|
||||
# 记录失败的登录尝试
|
||||
create_audit_log(
|
||||
db=db,
|
||||
user_id=0, # 未知用户
|
||||
action="login_failed",
|
||||
resource_type="user",
|
||||
resource_id=None,
|
||||
details=f"用户名: {login_data.username}",
|
||||
ip_address=request.client.host,
|
||||
user_agent=request.headers.get("User-Agent", "")
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="用户名或密码错误"
|
||||
)
|
||||
|
||||
# 更新最后登录时间
|
||||
user.last_login = __import__('datetime').datetime.utcnow()
|
||||
db.commit()
|
||||
|
||||
# 创建访问令牌
|
||||
access_token_expires = timedelta(minutes=10080) # 7天
|
||||
access_token = create_access_token(
|
||||
data={"sub": user.username},
|
||||
expires_delta=access_token_expires
|
||||
)
|
||||
|
||||
# 记录成功的登录
|
||||
create_audit_log(
|
||||
db=db,
|
||||
user_id=user.id,
|
||||
action="login",
|
||||
resource_type="user",
|
||||
resource_id=user.id,
|
||||
details=f"登录成功",
|
||||
ip_address=request.client.host,
|
||||
user_agent=request.headers.get("User-Agent", "")
|
||||
)
|
||||
|
||||
return {
|
||||
"token": access_token,
|
||||
"role": user.role,
|
||||
"user": {
|
||||
"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
|
||||
}
|
||||
}
|
||||
|
||||
@router.get("/me")
|
||||
async def read_users_me(
|
||||
current_user=Depends(lambda: None), # 占位符,实际由依赖注入
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取当前用户信息"""
|
||||
# 这里应该使用JWT验证中间件获取current_user
|
||||
# 简化实现...
|
||||
raise HTTPException(status_code=501, detail="功能待实现")
|
||||
|
||||
@router.post("/register")
|
||||
async def register(
|
||||
user_data: RegisterRequest,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""用户注册(管理员功能)"""
|
||||
# 检查用户名是否已存在
|
||||
existing_user = db.query(User).filter(User.username == user_data.username).first()
|
||||
if existing_user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="用户名已存在"
|
||||
)
|
||||
|
||||
# 创建新用户
|
||||
hashed_password = get_password_hash(user_data.password)
|
||||
new_user = User(
|
||||
username=user_data.username,
|
||||
password_hash=hashed_password,
|
||||
role=user_data.role
|
||||
)
|
||||
db.add(new_user)
|
||||
db.commit()
|
||||
db.refresh(new_user)
|
||||
|
||||
# 记录审计日志
|
||||
from .auth import create_audit_log
|
||||
create_audit_log(
|
||||
db=db,
|
||||
user_id=new_user.id,
|
||||
action="create_user",
|
||||
resource_type="user",
|
||||
resource_id=new_user.id,
|
||||
details=f"角色: {user_data.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
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
# 宇之然内容创作平台 - 文章生成API
|
||||
|
||||
from datetime import datetime
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List, Optional
|
||||
import asyncio
|
||||
|
||||
from ..core.security import get_current_user, create_audit_log
|
||||
from ..database import get_db
|
||||
from ..models import Topic, User, GenerateTask
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
class BatchGenerateRequest(BaseModel):
|
||||
topic_ids: Optional[List[int]] = None
|
||||
|
||||
class BatchOptimizeRequest(BaseModel):
|
||||
topic_ids: List[int]
|
||||
|
||||
@router.post("/run")
|
||||
async def batch_generate(
|
||||
request: BatchGenerateRequest,
|
||||
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()
|
||||
topic_ids = [t.id for t in topics]
|
||||
else:
|
||||
topic_ids = request.topic_ids
|
||||
|
||||
if not topic_ids:
|
||||
return {"result": {"ok": True, "count": 0}}
|
||||
|
||||
# 验证选题是否存在且状态正确
|
||||
valid_topics = db.query(Topic).filter(
|
||||
Topic.id.in_(topic_ids),
|
||||
Topic.status == "待处理"
|
||||
).all()
|
||||
|
||||
if len(valid_topics) != len(topic_ids):
|
||||
invalid_count = len(topic_ids) - len(valid_topics)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
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()
|
||||
topic.status = "待审查"
|
||||
topic.updated_at = datetime.utcnow()
|
||||
|
||||
db.commit()
|
||||
|
||||
# 记录审计日志
|
||||
create_audit_log(
|
||||
db=db,
|
||||
user_id=current_user.id,
|
||||
action="batch_generate",
|
||||
resource_type="topic",
|
||||
resource_id=None,
|
||||
details=f"处理选题数量: {len(tasks)}"
|
||||
)
|
||||
|
||||
return {
|
||||
"result": {
|
||||
"ok": True,
|
||||
"count": len(tasks)
|
||||
}
|
||||
}
|
||||
|
||||
@router.post("/optimize/run")
|
||||
async def batch_optimize(
|
||||
request: BatchOptimizeRequest,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""批量优化选题文章"""
|
||||
|
||||
# 检查选题是否存在且状态正确(必须是待审查)
|
||||
valid_topics = db.query(Topic).filter(
|
||||
Topic.id.in_(request.topic_ids),
|
||||
Topic.status == "待审查"
|
||||
).all()
|
||||
|
||||
if len(valid_topics) != len(request.topic_ids):
|
||||
invalid_count = len(request.topic_ids) - len(valid_topics)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=f"有{invalid_count}个选题状态不正确或不存在"
|
||||
)
|
||||
|
||||
# 执行优化逻辑(简化实现)
|
||||
auto_passed = 0
|
||||
need_manual = 0
|
||||
|
||||
for topic in valid_topics:
|
||||
# 这里应该调用实际的合规性检查逻辑
|
||||
# 简化实现:随机决定通过或不通过
|
||||
import random
|
||||
if random.choice([True, False]):
|
||||
topic.status = "待发布"
|
||||
auto_passed += 1
|
||||
else:
|
||||
topic.status = "待审查"
|
||||
need_manual += 1
|
||||
|
||||
topic.updated_at = datetime.utcnow()
|
||||
|
||||
db.commit()
|
||||
|
||||
# 记录审计日志
|
||||
create_audit_log(
|
||||
db=db,
|
||||
user_id=current_user.id,
|
||||
action="batch_optimize",
|
||||
resource_type="topic",
|
||||
resource_id=None,
|
||||
details=f"自动通过: {auto_passed}, 需人工: {need_manual}"
|
||||
)
|
||||
|
||||
return {
|
||||
"summary": {
|
||||
"passed_auto": auto_passed,
|
||||
"need_manual": need_manual,
|
||||
"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)}
|
||||
|
||||
# 注意:这个函数需要访问数据库,实际实现中可能需要额外的依赖注入
|
||||
@@ -0,0 +1,63 @@
|
||||
# 宇之然内容创作平台 - 日志API
|
||||
|
||||
from datetime import datetime, date
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
|
||||
from ..database import get_db
|
||||
from ..models import AuditLog
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.get("/system/{date}")
|
||||
async def get_system_logs(
|
||||
date: str,
|
||||
log_type: str = "creator", # creator | collector
|
||||
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")
|
||||
|
||||
# 查询指定日期的审计日志(简化实现)
|
||||
# 实际生产环境应从专门的日志系统中查询
|
||||
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 {"content": logs}
|
||||
|
||||
@router.get("/audit")
|
||||
async def get_audit_logs(
|
||||
start_date: str,
|
||||
end_date: str,
|
||||
action: str = None,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取审计日志(管理员功能)"""
|
||||
|
||||
# 这里应该实现真实的数据库查询
|
||||
# 简化实现返回空列表
|
||||
return []
|
||||
|
||||
@router.post("/clear")
|
||||
async def clear_old_logs(
|
||||
days: int = 30,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""清理旧日志(管理员功能)"""
|
||||
|
||||
# 这里应该实现真实的数据库删除操作
|
||||
# 简化实现
|
||||
return {"message": "日志清理功能待实现"}
|
||||
@@ -0,0 +1,92 @@
|
||||
# 宇之然内容创作平台 - 文章发布API
|
||||
|
||||
from datetime import datetime
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
import random
|
||||
|
||||
from ..core.security import get_current_user, create_audit_log
|
||||
from ..database import get_db
|
||||
from ..models import Topic, User
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
class PublishRequest(BaseModel):
|
||||
topic_id: int
|
||||
|
||||
@router.post("/create")
|
||||
async def create_publication(
|
||||
request: PublishRequest,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""发布选题到各平台"""
|
||||
|
||||
# 检查选题是否存在
|
||||
topic = db.query(Topic).filter(Topic.id == request.topic_id).first()
|
||||
if not topic:
|
||||
raise HTTPException(status_code=404, detail="选题不存在")
|
||||
|
||||
# 检查选题状态是否正确(必须是待发布)
|
||||
if topic.status != "待发布":
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=f"选题状态不正确,当前状态: {topic.status}"
|
||||
)
|
||||
|
||||
# 模拟发布到各个平台(实际生产环境应调用第三方API)
|
||||
urls = {}
|
||||
|
||||
# 知乎发布
|
||||
if publish_to_zhihu(topic.title):
|
||||
urls["zhihu"] = f"https://zhihu.com/article/{random.randint(100000, 999999)}"
|
||||
|
||||
# 微信公众号发布
|
||||
if publish_to_wechat(topic.title):
|
||||
urls["wechat"] = f"https://mp.weixin.qq.com/s/{random.randint(100000, 999999)}"
|
||||
|
||||
# 小红书发布
|
||||
if publish_to_xiaohongshu(topic.title):
|
||||
urls["xiaohongshu"] = f"https://www.xiaohongshu.com/discovery/item/{random.randint(100000, 999999)}"
|
||||
|
||||
# 更新选题状态和发布时间
|
||||
topic.published_at = datetime.utcnow()
|
||||
topic.published_urls = urls
|
||||
topic.status = "已发布"
|
||||
topic.updated_at = datetime.utcnow()
|
||||
|
||||
db.commit()
|
||||
|
||||
# 记录审计日志
|
||||
create_audit_log(
|
||||
db=db,
|
||||
user_id=current_user.id,
|
||||
action="publish",
|
||||
resource_type="topic",
|
||||
resource_id=topic.id,
|
||||
details=f"发布到平台: {list(urls.keys())}"
|
||||
)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"urls": urls,
|
||||
"published_at": topic.published_at.isoformat() if topic.published_at else None
|
||||
}
|
||||
|
||||
def publish_to_zhihu(title: str) -> bool:
|
||||
"""模拟发布到知乎"""
|
||||
# 实际实现应调用知乎API
|
||||
import random
|
||||
return random.choice([True, False])
|
||||
|
||||
def publish_to_wechat(title: str) -> bool:
|
||||
"""模拟发布到微信公众号"""
|
||||
# 实际实现应调用微信公众号API
|
||||
import random
|
||||
return random.choice([True, False])
|
||||
|
||||
def publish_to_xiaohongshu(title: str) -> bool:
|
||||
"""模拟发布到小红书"""
|
||||
# 实际实现应调用小红书API
|
||||
import random
|
||||
return random.choice([True, False])
|
||||
@@ -0,0 +1,90 @@
|
||||
# 宇之然内容创作平台 - 系统管理API
|
||||
|
||||
from datetime import datetime, date
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.orm import Session
|
||||
import os
|
||||
|
||||
from ..database import get_db
|
||||
|
||||
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()
|
||||
|
||||
# 今日新选题数
|
||||
today_articles = 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
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
@router.get("/pipeline/status")
|
||||
async def get_pipeline_status():
|
||||
"""获取流水线状态"""
|
||||
import subprocess
|
||||
import json
|
||||
|
||||
# 检查各个模块的运行状态
|
||||
pipeline_modules = {
|
||||
"creator": {
|
||||
"exists": os.path.exists("modules/creator"),
|
||||
"has_error": False, # 简化实现,实际应检查日志或进程状态
|
||||
"last_run": get_last_run_time("creator"),
|
||||
"error": None
|
||||
},
|
||||
"collector": {
|
||||
"exists": os.path.exists("modules/collector"),
|
||||
"has_error": False,
|
||||
"last_run": get_last_run_time("collector"),
|
||||
"error": None
|
||||
}
|
||||
}
|
||||
|
||||
# 统计分布(这里应该从数据库查询,简化为静态数据)
|
||||
status_distribution = {
|
||||
"待处理": 0,
|
||||
"待审查": 0,
|
||||
"待发布": 0
|
||||
}
|
||||
|
||||
# 实际实现中应该从数据库查询真实数据
|
||||
# for status in ["待处理", "待审查", "待发布"]:
|
||||
# count = db.query(func.count(Topic.id)).filter(
|
||||
# Topic.status == status
|
||||
# ).scalar()
|
||||
# status_distribution[status] = count
|
||||
|
||||
return {
|
||||
"status_distribution": status_distribution,
|
||||
"pipeline_modules": pipeline_modules,
|
||||
"topics_count": 0 # 简化实现
|
||||
}
|
||||
|
||||
def get_last_run_time(module_name: str) -> str:
|
||||
"""获取模块最后运行时间(简化实现)"""
|
||||
# 实际实现应检查日志文件或数据库记录
|
||||
return "2026-04-26 15:30:00"
|
||||
@@ -0,0 +1,188 @@
|
||||
# 宇之然内容创作平台 - 选题管理API
|
||||
|
||||
from datetime import datetime
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List, Optional
|
||||
import json
|
||||
|
||||
from ..core.security import get_current_admin_user, create_audit_log
|
||||
from ..database import get_db
|
||||
from ..models import Topic, User
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
class TopicCreateRequest(BaseModel):
|
||||
title: str
|
||||
field: Optional[str] = None
|
||||
priority_score: int = 0
|
||||
|
||||
class TopicUpdateRequest(BaseModel):
|
||||
title: Optional[str] = None
|
||||
field: Optional[str] = None
|
||||
priority_score: Optional[int] = None
|
||||
status: Optional[str] = None
|
||||
|
||||
@router.get("/", response_model=List[dict])
|
||||
async def get_topics(
|
||||
status: Optional[str] = Query(None),
|
||||
page: int = Query(1, ge=1),
|
||||
size: int = Query(20, ge=1, le=100),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取选题列表"""
|
||||
query = db.query(Topic)
|
||||
|
||||
# 状态筛选
|
||||
if status:
|
||||
query = query.filter(Topic.status == status)
|
||||
|
||||
# 分页
|
||||
offset = (page - 1) * size
|
||||
topics = query.offset(offset).limit(size).all()
|
||||
|
||||
# 转换为字典格式
|
||||
result = []
|
||||
for topic in topics:
|
||||
result.append({
|
||||
"id": topic.id,
|
||||
"title": topic.title,
|
||||
"field": topic.field,
|
||||
"priority_score": topic.priority_score,
|
||||
"status": topic.status,
|
||||
"compliance_score": topic.compliance_score,
|
||||
"created_at": topic.created_at.isoformat() if topic.created_at else None,
|
||||
"updated_at": topic.updated_at.isoformat() if topic.updated_at else None,
|
||||
"generated_at": topic.generated_at.isoformat() if topic.generated_at else None,
|
||||
"published_at": topic.published_at.isoformat() if topic.published_at else None,
|
||||
"published_urls": topic.published_urls
|
||||
})
|
||||
|
||||
return result
|
||||
|
||||
@router.post("/", response_model=dict)
|
||||
async def create_topic(
|
||||
topic_data: TopicCreateRequest,
|
||||
current_user: User = Depends(get_current_admin_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""创建新选题(管理员功能)"""
|
||||
new_topic = Topic(
|
||||
title=topic_data.title,
|
||||
field=topic_data.field,
|
||||
priority_score=topic_data.priority_score,
|
||||
status="待处理"
|
||||
)
|
||||
db.add(new_topic)
|
||||
db.commit()
|
||||
db.refresh(new_topic)
|
||||
|
||||
# 记录审计日志
|
||||
create_audit_log(
|
||||
db=db,
|
||||
user_id=current_user.id,
|
||||
action="create_topic",
|
||||
resource_type="topic",
|
||||
resource_id=new_topic.id,
|
||||
details=f"标题: {topic_data.title}"
|
||||
)
|
||||
|
||||
return {
|
||||
"id": new_topic.id,
|
||||
"title": new_topic.title,
|
||||
"status": new_topic.status,
|
||||
"created_at": new_topic.created_at.isoformat() if new_topic.created_at else None
|
||||
}
|
||||
|
||||
@router.put("/{topic_id}", response_model=dict)
|
||||
async def update_topic(
|
||||
topic_id: int,
|
||||
topic_data: TopicUpdateRequest,
|
||||
current_user: User = Depends(get_current_admin_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""更新选题信息(管理员功能)"""
|
||||
topic = db.query(Topic).filter(Topic.id == topic_id).first()
|
||||
if not topic:
|
||||
raise HTTPException(status_code=404, detail="选题不存在")
|
||||
|
||||
# 更新字段
|
||||
if topic_data.title is not None:
|
||||
topic.title = topic_data.title
|
||||
if topic_data.field is not None:
|
||||
topic.field = topic_data.field
|
||||
if topic_data.priority_score is not None:
|
||||
topic.priority_score = topic_data.priority_score
|
||||
if topic_data.status is not None:
|
||||
topic.status = topic_data.status
|
||||
|
||||
topic.updated_at = datetime.utcnow()
|
||||
db.commit()
|
||||
db.refresh(topic)
|
||||
|
||||
# 记录审计日志
|
||||
create_audit_log(
|
||||
db=db,
|
||||
user_id=current_user.id,
|
||||
action="update_topic",
|
||||
resource_type="topic",
|
||||
resource_id=topic_id,
|
||||
details=f"状态更新为: {topic_data.status}"
|
||||
)
|
||||
|
||||
return {
|
||||
"id": topic.id,
|
||||
"title": topic.title,
|
||||
"status": topic.status,
|
||||
"updated_at": topic.updated_at.isoformat() if topic.updated_at else None
|
||||
}
|
||||
|
||||
@router.delete("/{topic_id}")
|
||||
async def delete_topic(
|
||||
topic_id: int,
|
||||
current_user: User = Depends(get_current_admin_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""删除选题(管理员功能)"""
|
||||
topic = db.query(Topic).filter(Topic.id == topic_id).first()
|
||||
if not topic:
|
||||
raise HTTPException(status_code=404, detail="选题不存在")
|
||||
|
||||
db.delete(topic)
|
||||
db.commit()
|
||||
|
||||
# 记录审计日志
|
||||
create_audit_log(
|
||||
db=db,
|
||||
user_id=current_user.id,
|
||||
action="delete_topic",
|
||||
resource_type="topic",
|
||||
resource_id=topic_id,
|
||||
details="选题已删除"
|
||||
)
|
||||
|
||||
return {"message": "选题已成功删除"}
|
||||
|
||||
@router.get("/{topic_id}", response_model=dict)
|
||||
async def get_topic(
|
||||
topic_id: int,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取单个选题详情"""
|
||||
topic = db.query(Topic).filter(Topic.id == topic_id).first()
|
||||
if not topic:
|
||||
raise HTTPException(status_code=404, detail="选题不存在")
|
||||
|
||||
return {
|
||||
"id": topic.id,
|
||||
"title": topic.title,
|
||||
"field": topic.field,
|
||||
"priority_score": topic.priority_score,
|
||||
"status": topic.status,
|
||||
"compliance_score": topic.compliance_score,
|
||||
"created_at": topic.created_at.isoformat() if topic.created_at else None,
|
||||
"updated_at": topic.updated_at.isoformat() if topic.updated_at else None,
|
||||
"generated_at": topic.generated_at.isoformat() if topic.generated_at else None,
|
||||
"published_at": topic.published_at.isoformat() if topic.published_at else None,
|
||||
"published_urls": topic.published_urls
|
||||
}
|
||||
Reference in New Issue
Block a user