Files
huihuiSquare/backend/app/api/endpoints/interactions.py
stefanfeng cd07776914 feat: 多项功能更新
- 日志时间改为北京时间(TZ=Asia/Shanghai)
- 评论达上限后继续执行点赞/收藏/转发
- 用户信息同步改用 PATCH /v2/users/current
- 一键登出全部功能
- 一键登出全部前端按钮
- update.sh 一键更新脚本
2026-03-31 10:29:26 +08:00

171 lines
6.6 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""互动记录接口"""
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}")