Files
qihuaiflow/backend/python/app/api/assets.py
T
qyyfw 78ff261b5a 初始提交: Qihu-AIFlow v2.0.1 完整项目
- 前端 React+TypeScript (源码+构建产物)
- 后端 Python FastAPI
- 数据库迁移 001-015
- Docker部署配置
- 微信ClawBot API频道绑定
- 项目文档 README.md
2026-07-02 14:40:46 +08:00

380 lines
11 KiB
Python

from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from sqlalchemy import and_, desc, asc
from pydantic import BaseModel, Field
from typing import Optional
import uuid
from datetime import datetime
from ..database import get_db
from ..models import (
User, Asset, AssetCategory, NovelChapter, KnowledgeDocument,
KnowledgeBase,
gen_uuid
)
from ..auth import get_current_user
from ..middleware.log_service import LogService
router = APIRouter()
class CreateAssetRequest(BaseModel):
name: str = Field(min_length=1, max_length=200)
category: Optional[str] = None
description: Optional[str] = None
file_type: Optional[str] = None
content: Optional[str] = None
class UpdateAssetRequest(BaseModel):
name: Optional[str] = None
description: Optional[str] = None
file_type: Optional[str] = None
class CreateChapterRequest(BaseModel):
title: str = Field(min_length=1, max_length=500)
content: Optional[str] = None
sort_order: int = Field(default=0)
VALID_ASSET_CATEGORIES = ("file", "novel", "image", "video", "audio", "custom")
# ============================================================
# 静态路由 - 必须在动态路由之前定义
# ============================================================
@router.get("/storage-overview")
def get_storage_overview(
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""获取用户资产存储概览,包含资产和知识库文档的总存储量"""
# Asset表中的文件大小
from sqlalchemy import func as sa_func
asset_size = db.query(sa_func.sum(Asset.size)).filter(
and_(
Asset.user_id == current_user.id,
Asset.status != "deleted",
)
).scalar() or 0
# 知识库文档的文件大小
kb_doc_size = db.query(sa_func.sum(KnowledgeDocument.file_size)).join(
KnowledgeBase, KnowledgeDocument.knowledge_base_id == KnowledgeBase.id
).filter(
KnowledgeBase.user_id == current_user.id
).scalar() or 0
total_storage = asset_size + kb_doc_size
return {
"code": 0,
"data": {
"asset_storage": asset_size,
"knowledge_doc_storage": kb_doc_size,
"total_storage": total_storage,
},
"message": "ok",
}
@router.get("/categories")
def list_categories(
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
categories = (
db.query(AssetCategory)
.filter(AssetCategory.user_id == current_user.id)
.order_by(asc(AssetCategory.name))
.all()
)
result = []
for c in categories:
count = db.query(Asset).filter(
and_(
Asset.category == c.name,
Asset.user_id == current_user.id,
Asset.status != "deleted",
)
).count()
result.append({
"id": c.id,
"name": c.name,
"type": c.type,
"asset_count": count,
"created_at": str(c.created_at) if c.created_at else None,
})
return {"code": 0, "data": result, "message": "ok"}
@router.get("/novels/{asset_id}/chapters")
def get_novel_chapters(
asset_id: str,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
asset = db.query(Asset).filter(
and_(Asset.id == asset_id, Asset.user_id == current_user.id)
).first()
if not asset:
raise HTTPException(status_code=404, detail="资产不存在")
chapters = (
db.query(NovelChapter)
.filter(NovelChapter.asset_id == asset_id)
.order_by(asc(NovelChapter.sort_order), asc(NovelChapter.created_at))
.all()
)
result = []
for ch in chapters:
result.append({
"id": ch.id,
"asset_id": ch.asset_id,
"title": ch.title,
"sort_order": ch.sort_order,
"word_count": ch.word_count or 0,
"created_at": str(ch.created_at) if ch.created_at else None,
"updated_at": str(ch.updated_at) if ch.updated_at else None,
})
return {"code": 0, "data": result, "message": "ok"}
@router.post("/novels/{asset_id}/chapters")
def add_novel_chapter(
asset_id: str,
req: CreateChapterRequest,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
asset = db.query(Asset).filter(
and_(Asset.id == asset_id, Asset.user_id == current_user.id)
).first()
if not asset:
raise HTTPException(status_code=404, detail="资产不存在")
chapter = NovelChapter(
id=gen_uuid(),
asset_id=asset_id,
title=req.title,
content=req.content,
sort_order=req.sort_order,
word_count=len(req.content or ""),
created_at=datetime.now(),
updated_at=datetime.now(),
)
db.add(chapter)
LogService.log_operation(
user_id=current_user.id, username=current_user.username, action="add_chapter",
target="novel_chapter", target_id=chapter.id,
detail=f"为资产 {asset.name} 添加章节 {chapter.title}",
)
db.commit()
db.refresh(chapter)
return {
"code": 0,
"data": {
"id": chapter.id, "asset_id": chapter.asset_id, "title": chapter.title,
"sort_order": chapter.sort_order, "word_count": chapter.word_count,
"created_at": str(chapter.created_at),
},
"message": "章节添加成功",
}
# ============================================================
# 动态路由 - {asset_id} 必须放在静态路由之后
# ============================================================
@router.get("/")
def list_assets(
category: Optional[str] = Query(None),
search: Optional[str] = Query(None),
page: int = Query(default=1, ge=1),
page_size: int = Query(default=20, ge=1, le=100),
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
query = db.query(Asset).filter(
and_(
Asset.user_id == current_user.id,
Asset.status != "deleted",
)
)
if category:
query = query.filter(Asset.category == category)
if search:
query = query.filter(Asset.name.ilike(f"%{search}%"))
total = query.count()
assets = (
query.order_by(desc(Asset.updated_at))
.offset((page - 1) * page_size)
.limit(page_size)
.all()
)
result = []
for a in assets:
result.append({
"id": a.id,
"name": a.name,
"category": a.category,
"file_type": a.file_type,
"size": a.size or 0,
"description": a.description,
"url": a.url or "",
"content": a.content or "",
"status": a.status,
"created_at": str(a.created_at) if a.created_at else None,
"updated_at": str(a.updated_at) if a.updated_at else None,
})
return {
"code": 0,
"data": {"total": total, "page": page, "page_size": page_size, "items": result},
"message": "ok",
}
@router.post("/")
def create_asset(
req: CreateAssetRequest,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
cat = req.category if req.category in VALID_ASSET_CATEGORIES else "file"
asset = Asset(
id=gen_uuid(),
user_id=current_user.id,
name=req.name,
category=cat,
description=req.description or (req.content[:100] if req.content else None),
file_type=req.file_type or "unknown",
size=len(req.content.encode('utf-8')) if req.content else 0,
content=req.content,
status="active",
created_at=datetime.now(),
updated_at=datetime.now(),
)
db.add(asset)
db.commit()
db.refresh(asset)
LogService.log_operation(
user_id=current_user.id, username=current_user.username, action="create",
target="asset", target_id=asset.id,
detail=f"创建资产 {asset.name}",
)
return {
"code": 0,
"data": {
"id": asset.id, "name": asset.name, "category": asset.category,
"description": asset.description, "status": asset.status,
"created_at": str(asset.created_at),
},
"message": "创建成功",
}
@router.get("/{asset_id}")
def get_asset(
asset_id: str,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
asset = db.query(Asset).filter(
and_(Asset.id == asset_id, Asset.user_id == current_user.id)
).first()
if not asset:
raise HTTPException(status_code=404, detail="资产不存在")
return {
"code": 0,
"data": {
"id": asset.id, "name": asset.name, "category": asset.category,
"description": asset.description, "file_type": asset.file_type,
"size": asset.size or 0,
"url": asset.url or "",
"content": asset.content or "",
"status": asset.status,
"created_at": str(asset.created_at), "updated_at": str(asset.updated_at),
},
"message": "ok",
}
@router.put("/{asset_id}")
def update_asset(
asset_id: str,
req: UpdateAssetRequest,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
asset = db.query(Asset).filter(
and_(Asset.id == asset_id, Asset.user_id == current_user.id)
).first()
if not asset:
raise HTTPException(status_code=404, detail="资产不存在")
if req.name is not None:
asset.name = req.name
if req.description is not None:
asset.description = req.description
if req.file_type is not None:
asset.file_type = req.file_type
asset.updated_at = datetime.now()
LogService.log_operation(
user_id=current_user.id, username=current_user.username, action="update",
target="asset", target_id=asset.id,
detail=f"更新资产 {asset.name}",
)
db.commit()
db.refresh(asset)
return {"code": 0, "data": {
"id": asset.id, "name": asset.name, "status": asset.status,
"updated_at": str(asset.updated_at),
}, "message": "更新成功"}
@router.delete("/{asset_id}")
def delete_asset(
asset_id: str,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
asset = db.query(Asset).filter(
and_(Asset.id == asset_id, Asset.user_id == current_user.id)
).first()
if not asset:
raise HTTPException(status_code=404, detail="资产不存在")
# 软删除
asset.status = "deleted"
asset.updated_at = datetime.now()
LogService.log_operation(
user_id=current_user.id, username=current_user.username, action="delete",
target="asset", target_id=asset.id,
detail=f"删除资产 {asset.name}",
)
db.commit()
return {"code": 0, "message": "资产已删除"}