from fastapi import APIRouter, Depends, Query from sqlalchemy import func, text from sqlalchemy.orm import Session from pydantic import BaseModel from typing import Optional, List from datetime import datetime, date from app.core.coolbot_db import get_coolbot_db router = APIRouter(prefix="/api/yichens", tags=["一尘看板"]) # ============ 数据模型 ============ class YichensPostStats(BaseModel): total_posts: int total_deals: int # 出售 total_wants: int # 求购 total_replies: int total_views: int avg_price: Optional[float] class CategoryStat(BaseModel): category: str count: int class PostItem(BaseModel): post_id: str title: str category: Optional[str] post_type: str price: Optional[float] author_username: str post_time: str reply_count: int view_count: int url: Optional[str] content: Optional[str] class UserStat(BaseModel): total_users: int new_users_today: int sellers: int class UserItem(BaseModel): user_id: str username: str avatar_url: Optional[str] content: Optional[str] credit_level: Optional[str] credit_score: Optional[int] post_count: int is_seller: bool registration_date: Optional[str] # ============ 统计接口 ============ @router.get("/stats/posts", response_model=YichensPostStats) def get_post_stats(days: int = Query(30, ge=1, le=365), db: Session = Depends(get_coolbot_db)): """获取帖子统计""" result = db.execute(text(""" SELECT COUNT(*) as total_posts, COUNT(*) FILTER (WHERE post_type = 'deal') as total_deals, COUNT(*) FILTER (WHERE post_type = 'want') as total_wants, COALESCE(SUM(reply_count), 0) as total_replies, COALESCE(SUM(view_count), 0) as total_views, AVG(price) as avg_price FROM yichens_posts WHERE post_time >= NOW() - INTERVAL '1 day' * :days """), {"days": days}).fetchone() return YichensPostStats( total_posts=result[0] or 0, total_deals=result[1] or 0, total_wants=result[2] or 0, total_replies=result[3] or 0, total_views=result[4] or 0, avg_price=float(result[5]) if result[5] else None ) @router.get("/stats/categories", response_model=List[CategoryStat]) def get_category_stats(days: int = Query(30, ge=1, le=365), db: Session = Depends(get_coolbot_db)): """按分类统计帖子数量""" results = db.execute(text(""" SELECT category, COUNT(*) as count FROM yichens_posts WHERE post_time >= NOW() - INTERVAL '1 day' * :days GROUP BY category ORDER BY count DESC """), {"days": days}).fetchall() return [CategoryStat(category=r[0] or "未知", count=r[1]) for r in results] @router.get("/stats/users", response_model=UserStat) def get_user_stats(db: Session = Depends(get_coolbot_db)): """获取用户统计""" result = db.execute(text(""" SELECT COUNT(*) as total_users, COUNT(*) FILTER (WHERE created_at::date = CURRENT_DATE) as new_users_today, COUNT(*) FILTER (WHERE is_seller = true) as sellers FROM yichens_users """)).fetchone() return UserStat( total_users=result[0] or 0, new_users_today=result[1] or 0, sellers=result[2] or 0 ) @router.get("/posts", response_model=List[PostItem]) def get_posts( limit: int = Query(20, ge=1, le=500), offset: int = Query(0, ge=0), category: Optional[str] = None, post_type: Optional[str] = None, db: Session = Depends(get_coolbot_db) ): """获取帖子列表""" query = """ SELECT post_id, title, content, category, post_type, price, author_username, post_time, reply_count, view_count, url FROM yichens_posts WHERE 1=1 """ params = {"limit": limit, "offset": offset} if category: query += " AND category = :category" params["category"] = category if post_type: query += " AND post_type = :post_type" params["post_type"] = post_type query += " ORDER BY post_time DESC LIMIT :limit OFFSET :offset" results = db.execute(text(query), params).fetchall() return [PostItem( post_id=r[0], title=r[1] or "", content=r[2] or "", category=r[3], post_type=r[4] or "", price=float(r[5]) if r[5] else None, author_username=r[6] or "", post_time=str(r[7]) if r[7] else "", reply_count=r[8] or 0, view_count=r[9] or 0, url=r[10] ) for r in results] @router.get("/users", response_model=List[UserItem]) def get_users( limit: int = Query(20, ge=1, le=500), offset: int = Query(0, ge=0), is_seller: Optional[bool] = None, db: Session = Depends(get_coolbot_db) ): """获取用户列表""" query = """ SELECT user_id, username, avatar_url, credit_level, credit_score, post_count, is_seller, registration_date FROM yichens_users WHERE 1=1 """ params = {"limit": limit, "offset": offset} if is_seller is not None: query += " AND is_seller = :is_seller" params["is_seller"] = is_seller query += " ORDER BY post_count DESC LIMIT :limit OFFSET :offset" results = db.execute(text(query), params).fetchall() return [UserItem( user_id=r[0], username=r[1] or "", avatar_url=r[2], credit_level=r[3], credit_score=r[4], post_count=r[5] or 0, is_seller=r[6] or False, registration_date=str(r[7]) if r[7] else None ) for r in results] @router.get("/stats/today") async def get_today_stats(db: Session = Depends(get_coolbot_db)): """获取今日新增帖子统计""" query = """ SELECT COUNT(*) as total, SUM(CASE WHEN post_type = 'deal' THEN 1 ELSE 0 END) as deals, SUM(CASE WHEN post_type = 'want' THEN 1 ELSE 0 END) as wants, SUM(CASE WHEN post_type = 'normal' THEN 1 ELSE 0 END) as others, SUM(CASE WHEN category LIKE '%龙%' THEN 1 ELSE 0 END) as dragons, SUM(CASE WHEN category LIKE '%马%' THEN 1 ELSE 0 END) as horses, SUM(CASE WHEN category LIKE '%蛇%' THEN 1 ELSE 0 END) as snakes, SUM(CASE WHEN title LIKE '%天马%' OR content LIKE '%天马%' THEN 1 ELSE 0 END) as tianma FROM yichens_posts WHERE post_time >= CURRENT_DATE """ result = db.execute(text(query)).fetchone() return { "total": result[0] or 0, "deals": result[1] or 0, "wants": result[2] or 0, "others": result[3] or 0, "dragons": result[4] or 0, "horses": result[5] or 0, "snakes": result[6] or 0, "tianma": result[7] or 0 } @router.get("/stats/hour") async def get_hour_stats(db: Session = Depends(get_coolbot_db)): """获取近一个小时新增帖子统计""" query = """ SELECT COUNT(*) as total, SUM(CASE WHEN post_type = 'deal' THEN 1 ELSE 0 END) as deals, SUM(CASE WHEN post_type = 'want' THEN 1 ELSE 0 END) as wants, SUM(CASE WHEN post_type = 'normal' THEN 1 ELSE 0 END) as others, SUM(CASE WHEN category LIKE '%龙%' THEN 1 ELSE 0 END) as dragons, SUM(CASE WHEN category LIKE '%马%' THEN 1 ELSE 0 END) as horses, SUM(CASE WHEN category LIKE '%蛇%' THEN 1 ELSE 0 END) as snakes FROM yichens_posts WHERE post_time >= NOW() - INTERVAL '1 hour' """ result = db.execute(text(query)).fetchone() return { "total": result[0] or 0, "deals": result[1] or 0, "wants": result[2] or 0, "others": result[3] or 0, "dragons": result[3] or 0, "horses": result[4] or 0, "snakes": result[5] or 0 } @router.get("/stats/today-category") async def get_today_category_stats(db: Session = Depends(get_coolbot_db)): """获取今日帖子分类统计""" query = """ SELECT category, COUNT(*) as count FROM yichens_posts WHERE post_time >= CURRENT_DATE GROUP BY category ORDER BY count DESC """ results = db.execute(text(query)).fetchall() return [{"category": r[0] or "未分类", "count": r[1]} for r in results] @router.get("/stats/dragons-today") def get_dragons_stats_today( db: Session = Depends(get_coolbot_db) ): """获取今日龙钞详细统计数据(按号码分类)""" from sqlalchemy import text # 查询今日龙钞数据,按分类统计 result = db.execute(text(""" SELECT category, COUNT(*) as total, SUM(CASE WHEN content LIKE '%带4%' OR title LIKE '%带4%' THEN 1 ELSE 0 END) as dai4, SUM(CASE WHEN content NOT LIKE '%带4%' AND title NOT LIKE '%带4%' AND (content LIKE '%无4%' OR title LIKE '%无4%') THEN 1 ELSE 0 END) as wu4, SUM(CASE WHEN (content LIKE '%无47%' OR title LIKE '%无47%') AND (content NOT LIKE '%无247%' AND title NOT LIKE '%无247%') THEN 1 ELSE 0 END) as wu47, SUM(CASE WHEN (content LIKE '%无247%' OR title LIKE '%无247%') AND (content NOT LIKE '%无347%' AND title NOT LIKE '%无347%') THEN 1 ELSE 0 END) as wu247, SUM(CASE WHEN content LIKE '%无347%' OR title LIKE '%无347%' THEN 1 ELSE 0 END) as wu347 FROM yichen_posts WHERE created_at >= CURRENT_DATE AND (category LIKE '%龙%' OR category LIKE '%龙钞%') GROUP BY category """)) rows = result.fetchall() # 汇总统计 dai4_count = sum(r[2] or 0 for r in rows) wu4_count = sum(r[3] or 0 for r in rows) wu47_count = sum(r[4] or 0 for r in rows) wu247_count = sum(r[5] or 0 for r in rows) wu347_count = sum(r[6] or 0 for r in rows) # 查询出售和求购分类 deals_result = db.execute(text(""" SELECT SUM(CASE WHEN content LIKE '%带4%' OR title LIKE '%带4%' THEN 1 ELSE 0 END) as dai4_deals, SUM(CASE WHEN (content NOT LIKE '%带4%' AND title NOT LIKE '%带4%') AND (content LIKE '%无4%' OR title LIKE '%无4%') THEN 1 ELSE 0 END) as wu4_deals, SUM(CASE WHEN (content LIKE '%无47%' OR title LIKE '%无47%') AND (content NOT LIKE '%无247%' AND title NOT LIKE '%无247%') THEN 1 ELSE 0 END) as wu47_deals, SUM(CASE WHEN (content LIKE '%无247%' OR title LIKE '%无247%') AND (content NOT LIKE '%无347%' AND title NOT LIKE '%无347%') THEN 1 ELSE 0 END) as wu247_deals, SUM(CASE WHEN content LIKE '%无347%' OR title LIKE '%无347%' THEN 1 ELSE 0 END) as wu347_deals FROM yichen_posts WHERE created_at >= CURRENT_DATE AND (category LIKE '%龙%' OR category LIKE '%龙钞%') AND category NOT LIKE '%求购%' """)) deals_row = deals_result.fetchone() wants_result = db.execute(text(""" SELECT SUM(CASE WHEN content LIKE '%带4%' OR title LIKE '%带4%' THEN 1 ELSE 0 END) as dai4_wants, SUM(CASE WHEN (content NOT LIKE '%带4%' AND title NOT LIKE '%带4%') AND (content LIKE '%无4%' OR title LIKE '%无4%') THEN 1 ELSE 0 END) as wu4_wants, SUM(CASE WHEN (content LIKE '%无47%' OR title LIKE '%无47%') AND (content NOT LIKE '%无247%' AND title NOT LIKE '%无247%') THEN 1 ELSE 0 END) as wu47_wants, SUM(CASE WHEN (content LIKE '%无247%' OR title LIKE '%无247%') AND (content NOT LIKE '%无347%' AND title NOT LIKE '%无347%') THEN 1 ELSE 0 END) as wu247_wants, SUM(CASE WHEN content LIKE '%无347%' OR title LIKE '%无347%' THEN 1 ELSE 0 END) as wu347_wants FROM yichen_posts WHERE created_at >= CURRENT_DATE AND (category LIKE '%龙%' OR category LIKE '%龙钞%') AND category LIKE '%求购%' """)) wants_row = wants_result.fetchone() return { "dai4": {"total": dai4_count, "deals": deals_row[0] or 0, "wants": wants_row[0] or 0}, "wu4": {"total": wu4_count, "deals": deals_row[1] or 0, "wants": wants_row[1] or 0}, "wu47": {"total": wu47_count, "deals": deals_row[2] or 0, "wants": wants_row[2] or 0}, "wu247": {"total": wu247_count, "deals": deals_row[3] or 0, "wants": wants_row[3] or 0}, "wu347": {"total": wu347_count, "deals": deals_row[4] or 0, "wants": wants_row[4] or 0} }