# yichens - 一尘数据路由 # Version: 0.0.1 # 更新: from fastapi import APIRouter, Depends, Query # 更新: # Version: 1.2.x # 更新: 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") # 更新: 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, # 更新: keyword: Optional[str] = None, # 更新: db: Session = Depends(get_coolbot_db) # 更新: ): # 更新: """获取帖子列表 - 支持全局搜索,返回总数和分页信息""" # 更新: # 构建WHERE条件 # 更新: where_clauses = ["1=1"] # 更新: params = {"limit": limit, "offset": offset} # 更新: # 更新: if category: # 更新: where_clauses.append("category = :category") # 更新: params["category"] = category # 更新: # 更新: if post_type: # 更新: where_clauses.append("post_type = :post_type") # 更新: params["post_type"] = post_type # 更新: # 更新: # 全局搜索 # 更新: if keyword: # 更新: where_clauses.append("(title ILIKE :keyword OR content ILIKE :keyword OR category ILIKE :keyword OR author_username ILIKE :keyword)") # 更新: params["keyword"] = f"%{keyword}%" # 更新: # 更新: where_sql = " AND ".join(where_clauses) # 更新: # 更新: # 查询总数 # 更新: count_query = f"SELECT COUNT(*) FROM yichens_posts WHERE {where_sql}" # 更新: total_count = db.execute(text(count_query), {k: v for k, v in params.items() if k not in ['limit', 'offset']}).scalar() or 0 # 更新: # 更新: # 查询数据 - 有post_time时按post_time排序,没有时按crawled_at排序 # 更新: data_query = f""" # 更新: SELECT post_id, title, content, category, post_type, price, # 更新: author_username, post_time, reply_count, view_count, url # 更新: FROM yichens_posts # 更新: WHERE {where_sql} # 更新: ORDER BY COALESCE(post_time, crawled_at) DESC LIMIT :limit OFFSET :offset # 更新: """ # 更新: results = db.execute(text(data_query), params).fetchall() # 更新: # 更新: posts = [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] # 更新: # 更新: return { # 更新: "posts": posts, # 更新: "total": total_count, # 更新: "page": offset // limit + 1, # 更新: "page_size": limit # 更新: } # 更新: # 更新: @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 # 更新: # 更新: # 1. 带4:包含"带4"、"带四"、"通货" # 更新: dai4 = db.execute(text(""" # 更新: 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 # 更新: FROM yichens_posts # 更新: WHERE post_time >= CURRENT_DATE # 更新: AND category LIKE '%龙%' # 更新: AND ( # 更新: content LIKE '%带4%' OR title LIKE '%带4%' # 更新: OR content LIKE '%带四%' OR title LIKE '%带四%' # 更新: OR content LIKE '%通货%' OR title LIKE '%通货%' # 更新: ) # 更新: """)).fetchone() # 更新: # 更新: # 2. 无4:包含"无4"、"无四",排除"无47"、"无247"、"无四七"、"永恒" # 更新: wu4 = db.execute(text(""" # 更新: 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 # 更新: FROM yichens_posts # 更新: WHERE post_time >= CURRENT_DATE # 更新: AND category LIKE '%龙%' # 更新: AND ( # 更新: content LIKE '%无4%' OR title LIKE '%无4%' # 更新: OR content LIKE '%无四%' OR title LIKE '%无四%' # 更新: ) # 更新: AND content NOT LIKE '%无47%' AND title NOT LIKE '%无47%' # 更新: AND content NOT LIKE '%无247%' AND title NOT LIKE '%无247%' # 更新: AND content NOT LIKE '%永恒%' AND title NOT LIKE '%永恒%' # 更新: AND content NOT LIKE '%无四七%' AND title NOT LIKE '%无四七%' # 更新: """)).fetchone() # 更新: # 更新: # 3. 无47:包含"无47"、"永恒"、"无四七",排除"无247" # 更新: wu47 = db.execute(text(""" # 更新: 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 # 更新: FROM yichens_posts # 更新: WHERE post_time >= CURRENT_DATE # 更新: AND category LIKE '%龙%' # 更新: AND ( # 更新: content LIKE '%无47%' OR title LIKE '%无47%' # 更新: OR content LIKE '%永恒%' OR title LIKE '%永恒%' # 更新: OR content LIKE '%无四七%' OR title LIKE '%无四七%' # 更新: ) # 更新: AND content NOT LIKE '%无247%' AND title NOT LIKE '%无247%' # 更新: """)).fetchone() # 更新: # 更新: # 4. 无247:包含"无247"、"天马"、"金山",排除"无347" # 更新: wu247 = db.execute(text(""" # 更新: 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 # 更新: FROM yichens_posts # 更新: WHERE post_time >= CURRENT_DATE # 更新: AND category LIKE '%龙%' # 更新: AND ( # 更新: content LIKE '%无247%' OR title LIKE '%无247%' # 更新: OR content LIKE '%天马%' OR title LIKE '%天马%' # 更新: OR content LIKE '%金山%' OR title LIKE '%金山%' # 更新: ) # 更新: AND content NOT LIKE '%无347%' AND title NOT LIKE '%无347%' # 更新: """)).fetchone() # 更新: # 更新: # 5. 无347:包含"无347"、"钻石"、"金马"、"魅力"、"朦胧" # 更新: wu347 = db.execute(text(""" # 更新: 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 # 更新: FROM yichens_posts # 更新: WHERE post_time >= CURRENT_DATE # 更新: AND category LIKE '%龙%' # 更新: AND ( # 更新: content LIKE '%无347%' OR title LIKE '%无347%' # 更新: OR content LIKE '%钻石%' OR title LIKE '%钻石%' # 更新: OR content LIKE '%金马%' OR title LIKE '%金马%' # 更新: OR content LIKE '%魅力%' OR title LIKE '%魅力%' # 更新: OR content LIKE '%朦胧%' OR title LIKE '%朦胧%' # 更新: ) # 更新: """)).fetchone() # 更新: # 更新: return { # 更新: "dai4": {"total": dai4[0] or 0, "deals": dai4[1] or 0, "wants": dai4[2] or 0}, # 更新: "wu4": {"total": wu4[0] or 0, "deals": wu4[1] or 0, "wants": wu4[2] or 0}, # 更新: "wu47": {"total": wu47[0] or 0, "deals": wu47[1] or 0, "wants": wu47[2] or 0}, # 更新: "wu247": {"total": wu247[0] or 0, "deals": wu247[1] or 0, "wants": wu247[2] or 0}, # 更新: "wu347": {"total": wu347[0] or 0, "deals": wu347[1] or 0, "wants": wu347[2] or 0} # 更新: } # 更新: # 更新: