2026-04-06 21:17:15 +08:00
|
|
|
|
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]
|
2026-04-06 23:22:57 +08:00
|
|
|
|
content: Optional[str]
|
2026-04-06 21:17:15 +08:00
|
|
|
|
|
|
|
|
|
|
class UserStat(BaseModel):
|
|
|
|
|
|
total_users: int
|
|
|
|
|
|
new_users_today: int
|
|
|
|
|
|
sellers: int
|
|
|
|
|
|
|
|
|
|
|
|
class UserItem(BaseModel):
|
|
|
|
|
|
user_id: str
|
|
|
|
|
|
username: str
|
|
|
|
|
|
avatar_url: Optional[str]
|
2026-04-06 23:22:57 +08:00
|
|
|
|
content: Optional[str]
|
2026-04-06 21:17:15 +08:00
|
|
|
|
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(
|
2026-04-06 23:22:57 +08:00
|
|
|
|
limit: int = Query(20, ge=1, le=500),
|
2026-04-06 21:17:15 +08:00
|
|
|
|
offset: int = Query(0, ge=0),
|
|
|
|
|
|
category: Optional[str] = None,
|
|
|
|
|
|
post_type: Optional[str] = None,
|
|
|
|
|
|
db: Session = Depends(get_coolbot_db)
|
|
|
|
|
|
):
|
|
|
|
|
|
"""获取帖子列表"""
|
|
|
|
|
|
query = """
|
2026-04-06 23:22:57 +08:00
|
|
|
|
SELECT post_id, title, content, category, post_type, price,
|
2026-04-06 21:17:15 +08:00
|
|
|
|
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 "",
|
2026-04-06 23:22:57 +08:00
|
|
|
|
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]
|
2026-04-06 21:17:15 +08:00
|
|
|
|
) for r in results]
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/users", response_model=List[UserItem])
|
|
|
|
|
|
def get_users(
|
2026-04-06 23:22:57 +08:00
|
|
|
|
limit: int = Query(20, ge=1, le=500),
|
2026-04-06 21:17:15 +08:00
|
|
|
|
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]
|
2026-04-06 23:22:57 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@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,
|
2026-04-07 13:07:20 +08:00
|
|
|
|
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
|
2026-04-06 23:22:57 +08:00
|
|
|
|
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,
|
2026-04-07 13:07:20 +08:00
|
|
|
|
"dragons": result[4] or 0,
|
|
|
|
|
|
"horses": result[5] or 0,
|
|
|
|
|
|
"snakes": result[6] or 0,
|
|
|
|
|
|
"tianma": result[7] or 0
|
2026-04-06 23:22:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@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]
|
2026-04-08 15:20:15 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/stats/dragons-today")
|
|
|
|
|
|
def get_dragons_stats_today(
|
|
|
|
|
|
db: Session = Depends(get_coolbot_db)
|
|
|
|
|
|
):
|
|
|
|
|
|
"""获取今日龙钞详细统计数据(按号码分类)"""
|
|
|
|
|
|
from sqlalchemy import text
|
|
|
|
|
|
|
2026-04-08 15:27:52 +08:00
|
|
|
|
# 带4龙钞(包含"龙"且包含"4")
|
|
|
|
|
|
dai4 = db.execute(text("""
|
2026-04-08 15:20:15 +08:00
|
|
|
|
SELECT
|
|
|
|
|
|
COUNT(*) as total,
|
2026-04-08 15:27:52 +08:00
|
|
|
|
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%')
|
|
|
|
|
|
""")).fetchone()
|
2026-04-08 15:20:15 +08:00
|
|
|
|
|
2026-04-08 15:27:52 +08:00
|
|
|
|
# 无4龙钞(包含"龙"且包含"无4"但不包含"无47")
|
|
|
|
|
|
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%')
|
|
|
|
|
|
AND content NOT LIKE '%无47%'
|
|
|
|
|
|
AND title NOT LIKE '%无47%'
|
|
|
|
|
|
AND content NOT LIKE '%无247%'
|
|
|
|
|
|
AND title NOT LIKE '%无247%'
|
|
|
|
|
|
""")).fetchone()
|
2026-04-08 15:20:15 +08:00
|
|
|
|
|
2026-04-08 15:27:52 +08:00
|
|
|
|
# 无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%')
|
|
|
|
|
|
AND content NOT LIKE '%无247%'
|
|
|
|
|
|
AND title NOT LIKE '%无247%'
|
|
|
|
|
|
""")).fetchone()
|
2026-04-08 15:20:15 +08:00
|
|
|
|
|
2026-04-08 15:27:52 +08:00
|
|
|
|
# 无247龙钞(包含"龙"且包含"无247"但不包含"无347")
|
|
|
|
|
|
wu247 = db.execute(text("""
|
2026-04-08 15:20:15 +08:00
|
|
|
|
SELECT
|
2026-04-08 15:27:52 +08:00
|
|
|
|
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%')
|
|
|
|
|
|
AND content NOT LIKE '%无347%'
|
|
|
|
|
|
AND title NOT LIKE '%无347%'
|
|
|
|
|
|
""")).fetchone()
|
2026-04-08 15:20:15 +08:00
|
|
|
|
|
2026-04-08 15:27:52 +08:00
|
|
|
|
# 无347龙钞(包含"龙"且包含"无347")
|
|
|
|
|
|
wu347 = db.execute(text("""
|
2026-04-08 15:20:15 +08:00
|
|
|
|
SELECT
|
2026-04-08 15:27:52 +08:00
|
|
|
|
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%')
|
|
|
|
|
|
""")).fetchone()
|
2026-04-08 15:20:15 +08:00
|
|
|
|
|
|
|
|
|
|
return {
|
2026-04-08 15:27:52 +08:00
|
|
|
|
"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}
|
2026-04-08 15:20:15 +08:00
|
|
|
|
}
|
2026-04-08 15:27:52 +08:00
|
|
|
|
|