diff --git a/backend/app/core/coolbot_db.py b/backend/app/core/coolbot_db.py new file mode 100644 index 0000000..a0b9014 --- /dev/null +++ b/backend/app/core/coolbot_db.py @@ -0,0 +1,37 @@ +import os +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker +from sqlalchemy.pool import QueuePool + +COOLBOT_DB_URL = os.getenv( + "COOLBOT_DB_URL", + "postgresql://coolbot:Coolbot123@pgm-bp1t1008h019ez6cno.pg.rds.aliyuncs.com:5432/coolbot_data" +) + +coolbot_engine = create_engine( + COOLBOT_DB_URL, + poolclass=QueuePool, + pool_size=10, + max_overflow=20, + pool_timeout=30, + pool_recycle=1800, + pool_pre_ping=True, + echo=False, + connect_args={ + "connect_timeout": 10, + "application_name": "zodiac-coolbot" + } +) + +CoolbotSession = sessionmaker(autocommit=False, autoflush=False, bind=coolbot_engine) + +def get_coolbot_db(): + """获取coolbot数据库会话""" + db = CoolbotSession() + try: + yield db + except Exception: + db.rollback() + raise + finally: + db.close() diff --git a/backend/app/main.py b/backend/app/main.py index 7624c39..e43e400 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -16,6 +16,7 @@ from app.routers import auth, collections, operations from app.routers import ocr as ocr_router from app.routers import users as users_router from app.routers import information as information_router +from app.routers import yichens as yichens_router # 版本信息 - 从 config/VERSION 文件读取 def get_version(): @@ -80,7 +81,8 @@ app.include_router(operations.router) app.include_router(ocr_router.router) # OCR 识别 app.include_router(users_router.router) # 当前用户接口 app.include_router(users_router.admin_router) # 管理员用户管理 -app.include_router(information_router.router) # 资讯 +app.include_router(information_router.router) +app.include_router(yichens_router.router) # 一尘看板 @app.get("/") diff --git a/backend/app/models/models.py b/backend/app/models/models.py index 40811e7..387f670 100644 --- a/backend/app/models/models.py +++ b/backend/app/models/models.py @@ -163,7 +163,7 @@ class Information(Base): # 内容描述 content = Column(Text, nullable=True) - + # 关联藏品ID collection_id = Column(String(36), ForeignKey("collections.f99_90_id", ondelete="SET NULL"), nullable=True) diff --git a/backend/app/routers/information.py b/backend/app/routers/information.py index 87e3baf..2987785 100644 --- a/backend/app/routers/information.py +++ b/backend/app/routers/information.py @@ -16,7 +16,7 @@ router = APIRouter(prefix="/api/information", tags=["资讯"]) class InformationCreate(BaseModel): info_type: str # seek-寻配号, deal-成交数据, publish-发布 title: str - content: Optional[str] = None + content: Optional[str] collection_id: Optional[str] = None expect_category: Optional[str] = None expect_version: Optional[str] = None @@ -30,7 +30,7 @@ class InformationCreate(BaseModel): class InformationUpdate(BaseModel): title: Optional[str] = None - content: Optional[str] = None + content: Optional[str] status: Optional[str] = None expect_category: Optional[str] = None expect_version: Optional[str] = None diff --git a/backend/app/routers/yichens.py b/backend/app/routers/yichens.py new file mode 100644 index 0000000..e163b4a --- /dev/null +++ b/backend/app/routers/yichens.py @@ -0,0 +1,181 @@ +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] + +class UserStat(BaseModel): + total_users: int + new_users_today: int + sellers: int + +class UserItem(BaseModel): + user_id: str + username: str + avatar_url: 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=100), + 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, 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 "", + category=r[2], + post_type=r[3] or "", + price=float(r[4]) if r[4] else None, + author_username=r[5] or "", + post_time=str(r[6]) if r[6] else "", + reply_count=r[7] or 0, + view_count=r[8] or 0, + url=r[9] + ) for r in results] + +@router.get("/users", response_model=List[UserItem]) +def get_users( + limit: int = Query(20, ge=1, le=100), + 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] diff --git a/config/VERSION b/config/VERSION index 1ce7217..4aee9f9 100644 --- a/config/VERSION +++ b/config/VERSION @@ -1 +1 @@ -VERSION=1.2.41 +VERSION=1.2.42 diff --git a/frontend/src/pages/Info.jsx b/frontend/src/pages/Info.jsx index 390bdb8..2804236 100644 --- a/frontend/src/pages/Info.jsx +++ b/frontend/src/pages/Info.jsx @@ -9,7 +9,7 @@ export default function Info() { } // 顶部tab:寻配号发布 / 发布管理 - const [activeTab, setActiveTab] = useState('manage') + const [activeTab, setActiveTab] = useState('yichens') const [showPublish, setShowPublish] = useState(false) const [publishType, setPublishType] = useState('deal') // 'seek' 或 'deal' const [myList, setMyList] = useState([]) @@ -227,7 +227,8 @@ export default function Info() {