"""互动记录接口""" from typing import Optional from fastapi import APIRouter, Depends, Query, HTTPException from fastapi.responses import StreamingResponse import io, pandas as pd from app.core.database import get_db from app.schemas import ApiResponse from app.services.stats_service import stats_service from app.models import InteractionRecord from sqlalchemy import select, update router = APIRouter() @router.get("") async def list_interactions( page: int = Query(default=1, ge=1), page_size: int = Query(default=20, ge=1, le=100), user_id: Optional[int] = None, interact_type: Optional[str] = None, status: Optional[int] = None, start_date: Optional[str] = None, end_date: Optional[str] = None, keyword: Optional[str] = None, db=Depends(get_db) ): result = await stats_service.get_interaction_records( db, page, page_size, user_id, interact_type, status, start_date, end_date, keyword ) return ApiResponse(data=result) @router.post("/{record_id}/retry") async def retry_interaction(record_id: int, db=Depends(get_db)): """手动重试失败任务""" result = await db.execute(select(InteractionRecord).where(InteractionRecord.id == record_id)) record = result.scalar_one_or_none() if not record: raise HTTPException(status_code=404, detail="记录不存在") if record.status != 2: raise HTTPException(status_code=400, detail="只能重试失败的任务") if record.retry_count >= 3: raise HTTPException(status_code=400, detail="已超过最大重试次数(3次)") from app.services.news_service import news_service from app.services.ai_service import ai_service from app.models import VirtualUser, UserPersonality user_result = await db.execute(select(VirtualUser).where(VirtualUser.id == record.user_id)) user = user_result.scalar_one_or_none() if not user or user.status != 2: raise HTTPException(status_code=400, detail="用户未登录,无法重试") success, err = False, "未知类型" if record.interact_type == "comment" and record.content: success, err = await news_service.post_comment(db, user, record.article_id, record.article_title or "", record.content) elif record.interact_type == "like": success, err = await news_service.like_news(db, user, record.article_id, org_id="", title=record.article_title or "") elif record.interact_type == "collect": success, err = await news_service.collect_news(db, user, record.article_id, title=record.article_title or "") elif record.interact_type == "forward": success, err = await news_service.forward_news(db, user, record.article_id) await db.execute( update(InteractionRecord).where(InteractionRecord.id == record_id).values( status=1 if success else 2, error_msg=None if success else err, retry_count=record.retry_count + 1, ) ) await db.commit() return ApiResponse(message="重试成功" if success else f"重试失败: {err}") @router.get("/export") async def export_interactions( user_id: Optional[int] = None, interact_type: Optional[str] = None, status: Optional[int] = None, start_date: Optional[str] = None, end_date: Optional[str] = None, db=Depends(get_db) ): """导出互动记录""" data = await stats_service.get_interaction_records( db, 1, 10000, user_id, interact_type, status, start_date, end_date ) rows = [{ "ID": r["id"], "用户昵称": r["user_nickname"], "用户账号": r["user_account"], "文章标题": r["article_title"], "互动类型": r["interact_type_label"], "内容": r["content"] or "", "Token消耗": r["token_consumed"], "状态": r["status_label"], "失败原因": r["error_msg"] or "", "重试次数": r["retry_count"], "执行时间": r["executed_at"], } for r in data["items"]] df = pd.DataFrame(rows) buf = io.BytesIO() df.to_excel(buf, index=False, sheet_name="互动记录") buf.seek(0) return StreamingResponse( buf, media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", headers={"Content-Disposition": "attachment; filename=interactions_export.xlsx"} ) @router.post("/{record_id}/cancel") async def cancel_interaction(record_id: int, db=Depends(get_db)): """取消互动(取消点赞/收藏/删除评论),转发不支持取消""" from sqlalchemy import select, update from app.models import InteractionRecord, VirtualUser from app.services.news_service import news_service # 查找互动记录 r = await db.execute(select(InteractionRecord).where(InteractionRecord.id == record_id)) record = r.scalar_one_or_none() if not record: return ApiResponse(code=404, message="记录不存在") if record.status != 1: return ApiResponse(code=400, message="只能取消成功的互动") if record.interact_type == "forward": return ApiResponse(code=400, message="转发互动无法取消") if record.interact_type == "read": return ApiResponse(code=400, message="阅读记录无法取消") # 查找对应用户 ur = await db.execute(select(VirtualUser).where(VirtualUser.id == record.user_id)) user = ur.scalar_one_or_none() if not user: return ApiResponse(code=404, message="用户不存在") # 执行取消 ok = False err = "" if record.interact_type in ("like",): ok, err = await news_service.cancel_like( db, user, news_id=record.article_id or "", org_id=record.session_id or "", # session_id 字段暂存 org_id title=record.article_title or "", ) elif record.interact_type == "collect": ok, err = await news_service.cancel_collect( db, user, news_id=record.article_id or "", title=record.article_title or "", ) elif record.interact_type in ("comment", "reply"): comment_id = record.platform_record_id or "" if not comment_id: return ApiResponse(code=400, message="缺少评论ID,无法删除") ok, err = await news_service.cancel_comment( db, user, news_id=record.article_id or "", comment_id=comment_id, ) if ok: # 更新状态为手动取消(status=3) await db.execute( update(InteractionRecord).where(InteractionRecord.id == record_id).values( status=3, error_msg="手动取消" ) ) await db.commit() return ApiResponse(message="取消成功") else: return ApiResponse(code=500, message=f"取消失败: {err}")