chore: 清理未使用的代码文件和备份

- 删除 backend/main.py (已由 app/main.py 替代)
- 删除 backend/api/ 和 backend/core/ (已由 app/api/ 和 app/core/ 替代)
- 删除 backend/static 符号链接
- 删除前端测试/调试页面
- 删除未引用的 vendor 子目录 (element-plus/, vue/, axios/)
- 删除所有 .bak 备份文件
This commit is contained in:
lt
2026-05-09 19:43:49 +08:00
parent 71cb4c35a8
commit 25694db33b
28 changed files with 0 additions and 82730 deletions
-103
View File
@@ -1,103 +0,0 @@
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
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
})
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 = db.query(User).filter(User.username == username).first()
if existing:
raise HTTPException(status_code=400, detail="用户名已存在")
# 密码哈希
hashed_password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
user = User(
username=username,
password_hash=hashed_password,
role=role or "user"
)
db.add(user)
db.commit()
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="用户不存在")
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
if password:
user.password_hash = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
if role:
user.role = role
db.commit()
db.refresh(user)
return {"message": "更新成功"}
@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)
):
"""删除用户(管理员功能)"""
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()
return {"message": "删除成功"}
-182
View File
@@ -1,182 +0,0 @@
# 宇之然内容创作平台 - 管理员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 app.database import get_db
from app.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": "用户已成功删除"}
-63
View File
@@ -1,63 +0,0 @@
# 宇之然内容创作平台 - 文章预览API
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session
from typing import Dict
from app.database import get_db
from app.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
-154
View File
@@ -1,154 +0,0 @@
# 宇之然内容创作平台 - 认证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, get_current_user
)
from app.database import get_db
from app.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",
username=login_data.username,
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",
username=user.username,
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: User = Depends(get_current_user)
):
"""获取当前用户信息"""
return {
"user": {
"id": current_user.id,
"username": current_user.username,
"role": current_user.role,
"created_at": current_user.created_at.isoformat() if current_user.created_at else None,
"updated_at": current_user.updated_at.isoformat() if current_user.updated_at else None
}
}
@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",
username=new_user.username,
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
}
-134
View File
@@ -1,134 +0,0 @@
# 宇之然内容创作平台 - 文章生成API
from pydantic import BaseModel
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 app.database import get_db
from app.models import Topic, User
router = APIRouter()
class BatchGenerateRequest(BaseModel):
topic_ids: Optional[List[str]] = 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}个选题状态不正确或不存在"
)
# 更新选题状态为"待审查"
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(valid_topics)}"
)
return {
"result": {
"ok": True,
"count": len(valid_topics)
}
}
@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)
}
}
-92
View File
@@ -1,92 +0,0 @@
# 宇之然内容创作平台 - 日志API
from datetime import datetime, date
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from typing import List
from app.database import get_db
from app.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("", 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,
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": "日志清理功能待实现"}
-93
View File
@@ -1,93 +0,0 @@
# 宇之然内容创作平台 - 文章发布API
from pydantic import BaseModel
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 app.database import get_db
from app.models import Topic, User
router = APIRouter()
class PublishRequest(BaseModel):
topic_id: str
@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.platform_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])
-89
View File
@@ -1,89 +0,0 @@
# 宇之然内容创作平台 - 系统管理API
from sqlalchemy import func
from datetime import datetime, date
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from app.models import Topic
import os
from app.database import get_db
router = APIRouter()
@router.get("/status")
async def get_system_status(db: Session = Depends(get_db)):
"""获取系统概览状态(格式匹配前端)"""
today = date.today()
# 统计总数
total = db.query(func.count(Topic.id)).scalar()
# 今日新选题数
today_count = db.query(func.count(Topic.id)).filter(
func.date(Topic.created_at) == today
).scalar()
# 各状态选题数量(映射到前端字段)
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 {
"stats": {
"total": total,
"pending": pending,
"review": review,
"ready": ready,
"published": published,
"today": today_count
}
}
@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"
-189
View File
@@ -1,189 +0,0 @@
# 宇之然内容创作平台 - 选题管理API
from pydantic import BaseModel
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 app.database import get_db
from app.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,
"platform_urls": topic.platform_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: str,
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,
"platform_urls": topic.platform_urls
}
-189
View File
@@ -1,189 +0,0 @@
# 宇之然内容创作平台 - 选题管理API
from pydantic import BaseModel
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 app.database import get_db
from app.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,
"platform_urls": topic.platform_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: str,
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,
"platform_urls": topic.platform_urls
}
@@ -1,118 +0,0 @@
"""
NVIDIA 专用 LLM 客户端(fixed configuration
使用 OpenAI 兼容接口调用 stepfun-ai/step-3.5-flash
"""
import requests
import json
from typing import Optional
class LLMError(Exception):
pass
# 固定配置(你的可用 key
CONFIG = {
"base_url": "https://integrate.api.nvidia.com/v1",
"api_key": "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",
"model": "stepfun-ai/step-3.5-flash",
}
def call_llm(
prompt: str,
system_prompt: str = "你是一个专业的内容创作助手。",
temperature: float = 0.7,
max_tokens: int = 2000,
stream: bool = False,
) -> str:
"""
调用 NVIDIA LLM 生成文本
"""
endpoint = f"{CONFIG['base_url'].rstrip('/')}/chat/completions"
headers = {
"Authorization": f"Bearer {CONFIG['api_key']}",
"Content-Type": "application/json"
}
payload = {
"model": CONFIG["model"],
"messages": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": prompt}
],
"temperature": temperature,
"max_tokens": max_tokens,
"stream": stream,
}
try:
resp = requests.post(endpoint, json=payload, headers=headers, timeout=120, stream=stream)
if resp.status_code != 200:
raise LLMError(f"HTTP {resp.status_code}: {resp.text[:200]}")
if stream:
full = []
for line in resp.iter_lines():
if not line:
continue
if line.startswith(b'data: '):
data = line[6:]
if data == b'[DONE]':
break
try:
chunk = json.loads(data)
delta = chunk['choices'][0]['delta']
# 支持 reasoning_content 或 reasoning 字段
if 'reasoning_content' in delta and delta['reasoning_content']:
full.append(delta['reasoning_content'])
if 'content' in delta and delta['content']:
full.append(delta['content'])
except Exception:
continue
return "".join(full)
else:
data = resp.json()
msg = data["choices"][0]["message"]
content = msg.get('content') or msg.get('reasoning') or msg.get('reasoning_content')
return content.strip() if content else ''
except requests.RequestException as e:
raise LLMError(f"Request failed: {e}")
def expand_content_with_llm(topic: dict, section_title: str, section_content: str, context: str = "") -> str:
"""扩写大纲章节,返回包含 ## 标题的完整 Markdown"""
prompt = f"""你是一个专业的内容创作者。请将以下大纲扩展为完整的文章章节。
# 选题信息
- 标题:{topic.get('title')}
- 领域:{topic.get('field')}
- 核心观点:{topic.get('core_concept', '')}
- 受众痛点:{topic.get('audience_pain', '')}
- 独特视角:{topic.get('unique_angle', '')}
# 当前章节
## {section_title}
{section_content}
# 要求
- 以 `## {section_title}` 作为章节标题开头
- 字数:300-500字
- 风格:客观、专业、易懂
- 使用 Markdown 格式
- 包含具体数据或案例(如果有)
- 保持与整体文章调性一致
直接输出完整的 Markdown 章节(包括 ## 标题和正文段落)。"""
if context:
prompt = f"# 参考资料\n{context}\n\n{prompt}"
try:
result = call_llm(prompt, temperature=0.8, max_tokens=2000)
return result.strip()
except Exception as e:
return f"## {section_title}\n\nLLM 调用失败:{e},请手动补充)"
# 测试
if __name__ == "__main__":
try:
print(f"[nvidia_client] 使用模型: {CONFIG['model']}")
resp = call_llm("你好,请用一句话介绍你自己。", max_tokens=50)
print(f"[nvidia_client] 响应: {resp}")
except Exception as e:
print(f"[nvidia_client] 错误: {e}")
-130
View File
@@ -1,130 +0,0 @@
# 宇之然内容创作平台 - 安全模块
from datetime import datetime, timedelta
from typing import Optional
from jose import JWTError, jwt
from fastapi.security import OAuth2PasswordBearer
from fastapi import Depends, HTTPException, status
from sqlalchemy.orm import Session
from app.models import User
from app.database import get_db
import bcrypt
# JWT配置
SECRET_KEY = "your-secret-key-here" # 生产环境应从环境变量读取
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 10080 # 7天
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/login")
def verify_password(plain_password: str, hashed_password: str) -> bool:
"""验证密码(使用 bcrypt 直接比较)"""
try:
# 确保输入为字节
if isinstance(plain_password, str):
plain_password = plain_password.encode('utf-8')
if isinstance(hashed_password, str):
hashed_password = hashed_password.encode('utf-8')
return bcrypt.checkpw(plain_password, hashed_password)
except Exception:
return False
def get_password_hash(password: str) -> str:
"""生成密码哈希(使用 bcrypt"""
if isinstance(password, str):
password = password.encode('utf-8')
hashed = bcrypt.hashpw(password, bcrypt.gensalt())
return hashed.decode('utf-8')
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
"""创建JWT token"""
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
async def get_current_user(
token: str = Depends(oauth2_scheme),
db: Session = Depends(get_db)
) -> User:
"""获取当前用户"""
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="无效的认证凭据",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
except JWTError:
raise credentials_exception
user = db.query(User).filter(User.username == username).first()
if user is None:
raise credentials_exception
return user
async def get_current_active_user(current_user: User = Depends(get_current_user)):
"""获取活跃用户(简单检查)"""
# 这里可以添加更多活跃性检查逻辑
return current_user
async def get_current_admin_user(current_user: User = Depends(get_current_user)):
"""获取管理员用户"""
if current_user.role != "admin":
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="权限不足,需要管理员角色"
)
return current_user
def create_audit_log(
db: Session,
user_id: int,
action: str,
username: str = "",
resource_type: str = "",
resource_id: int = None,
details: str = "",
ip_address: str = "",
user_agent: str = ""
):
"""创建审计日志"""
from app.models import AuditLog, User
# 如果未提供 username,尝试从 user_id 查询
if not username:
if user_id and user_id != 0:
user = db.query(User).filter(User.id == user_id).first()
if user:
username = user.username
if not username:
username = "unknown"
audit_log = AuditLog(
user_id=user_id,
username=username,
action=action,
resource_type=resource_type,
resource_id=resource_id,
details=details,
ip_address=ip_address,
user_agent=user_agent
)
db.add(audit_log)
db.commit()
-103
View File
@@ -1,103 +0,0 @@
import sys
import os
sys.path.insert(0, os.path.dirname(__file__))
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse
from contextlib import asynccontextmanager
import uvicorn
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, generate
from app.api import cases, task_logs, llm_configs, system_configs
@asynccontextmanager
async def lifespan(app: FastAPI):
"""应用生命周期管理"""
print("正在初始化数据库...")
init_db()
print("数据库初始化完成")
yield
print("应用关闭")
app = FastAPI(
title="宇之然内容创作平台 API",
description="企业级内容创作管理系统",
version="1.0.0",
lifespan=lifespan
)
# CORS配置
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# API 路由(必须先于静态文件注册)
app.include_router(auth.router, prefix="/api/auth", tags=["认证"])
app.include_router(topics.router, prefix="/api/topics", tags=["选题管理"])
app.include_router(system.router, prefix="/api/system", tags=["系统状态"])
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")
async def health_check():
return {"status": "healthy", "timestamp": __import__('datetime').datetime.now().isoformat()}
# 独立页面路由(必须在 SPA catch-all 之前)
@app.get("/topics.html")
async def topics_page():
return FileResponse("static/topics.html")
@app.get("/logs.html")
async def logs_page():
return FileResponse("static/logs.html")
@app.get("/users.html")
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")
# SPA:所有非 API 路径返回 index.html(最后注册)
@app.get("/{full_path:path}")
async def serve_spa(full_path: str):
return FileResponse("static/index.html")
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
import traceback
print(f"全局异常: {exc}")
print(f"堆栈跟踪:\n{traceback.format_exc()}")
return {
"error": "服务器内部错误",
"message": str(exc),
"path": request.url.path
}
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=8001, reload=True, log_level="info")
-97
View File
@@ -1,97 +0,0 @@
import sys
import os
sys.path.insert(0, os.path.dirname(__file__))
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse
from contextlib import asynccontextmanager
import uvicorn
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
@asynccontextmanager
async def lifespan(app: FastAPI):
"""应用生命周期管理"""
print("正在初始化数据库...")
init_db()
print("数据库初始化完成")
yield
print("应用关闭")
app = FastAPI(
title="宇之然内容创作平台 API",
description="企业级内容创作管理系统",
version="1.0.0",
lifespan=lifespan
)
# CORS配置
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# API 路由(必须先于静态文件注册)
app.include_router(auth.router, prefix="/api/auth", tags=["认证"])
app.include_router(topics.router, prefix="/api/topics", tags=["选题管理"])
app.include_router(system.router, prefix="/api/system", tags=["系统状态"])
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.get("/health")
async def health_check():
return {"status": "healthy", "timestamp": __import__('datetime').datetime.now().isoformat()}
# 独立页面路由(必须在 SPA catch-all 之前)
@app.get("/topics.html")
async def topics_page():
return FileResponse("static/topics.html")
@app.get("/logs.html")
async def logs_page():
return FileResponse("static/logs.html")
@app.get("/users.html")
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")
# SPA:所有非 API 路径返回 index.html(最后注册)
@app.get("/{full_path:path}")
async def serve_spa(full_path: str):
return FileResponse("static/index.html")
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
import traceback
print(f"全局异常: {exc}")
print(f"堆栈跟踪:\n{traceback.format_exc()}")
return {
"error": "服务器内部错误",
"message": str(exc),
"path": request.url.path
}
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=8001, reload=True, log_level="info")
-1
View File
@@ -1 +0,0 @@
../frontend