"""一尘数据管理路由""" from fastapi import APIRouter, HTTPException, Query from typing import Optional, List from datetime import datetime from api.middleware.response import ApiResponse, PaginatedData from database import db router = APIRouter(prefix="/api/v1/yichens", tags=["yichens"]) # ============ 用户管理 ============ @router.get("/users", response_model=ApiResponse) async def list_yichens_users( username: Optional[str] = Query(None, description="用户名搜索"), is_seller: Optional[bool] = Query(None, description="是否商家"), page: int = Query(1, ge=1), page_size: int = Query(20, ge=1, le=100) ): """获取一尘用户列表""" offset = (page - 1) * page_size where_clauses = [] params = [] if username: where_clauses.append("username LIKE %s") params.append(f"%{username}%") if is_seller is not None: where_clauses.append("is_seller = %s") params.append(is_seller) where_sql = " WHERE " + " AND ".join(where_clauses) if where_clauses else "" with db.get_cursor() as cursor: cursor.execute(f"SELECT COUNT(*) as total FROM yichens_users{where_sql}", params) total = cursor.fetchone()["total"] cursor.execute(f""" SELECT * FROM yichens_users{where_sql} ORDER BY crawl_latest_at DESC LIMIT %s OFFSET %s """, params + [page_size, offset]) items = cursor.fetchall() return ApiResponse.success(PaginatedData.create(items, page, page_size, total)) @router.get("/users/{user_id}", response_model=ApiResponse) async def get_yichens_user(user_id: str): """获取一尘用户详情""" with db.get_cursor() as cursor: cursor.execute("SELECT * FROM yichens_users WHERE user_id = %s", (user_id,)) item = cursor.fetchone() if not item: raise HTTPException(status_code=404, detail="User not found") return ApiResponse.success(item) @router.post("/users", response_model=ApiResponse, status_code=201) async def create_yichens_user(user_data: dict): """创建/更新一尘用户""" required = ["user_id", "username"] for field in required: if field not in user_data: raise HTTPException(status_code=400, detail=f"Missing required field: {field}") with db.get_cursor() as cursor: cursor.execute(""" INSERT INTO yichens_users ( user_id, username, nickname, avatar_url, user_level, credit_score, register_date, last_active_at, is_seller, seller_rating, is_verified, bio, province ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) ON DUPLICATE KEY UPDATE username = VALUES(username), nickname = VALUES(nickname), avatar_url = VALUES(avatar_url), user_level = VALUES(user_level), credit_score = VALUES(credit_score), is_seller = VALUES(is_seller), seller_rating = VALUES(seller_rating), is_verified = VALUES(is_verified), bio = VALUES(bio), province = VALUES(province), crawl_latest_at = NOW() """, ( user_data.get("user_id"), user_data.get("username"), user_data.get("nickname"), user_data.get("avatar_url"), user_data.get("user_level"), user_data.get("credit_score", 0), user_data.get("register_date"), user_data.get("last_active_at"), user_data.get("is_seller", False), user_data.get("seller_rating"), user_data.get("is_verified", False), user_data.get("bio"), user_data.get("province") )) return ApiResponse.success({"user_id": user_data["user_id"]}, "User saved successfully") # ============ 帖子管理 ============ @router.get("/posts", response_model=ApiResponse) async def list_yichens_posts( category: Optional[str] = Query(None, description="分类筛选"), post_type: Optional[str] = Query(None, description="帖子类型"), author_username: Optional[str] = Query(None, description="作者用户名"), keyword: Optional[str] = Query(None, description="关键词搜索"), min_price: Optional[float] = Query(None, description="最低价格"), max_price: Optional[float] = Query(None, description="最高价格"), page: int = Query(1, ge=1), page_size: int = Query(20, ge=1, le=100) ): """获取一尘帖子列表""" offset = (page - 1) * page_size where_clauses = [] params = [] if category: where_clauses.append("category = %s") params.append(category) if post_type: where_clauses.append("post_type = %s") params.append(post_type) if author_username: where_clauses.append("author_username LIKE %s") params.append(f"%{author_username}%") if keyword: where_clauses.append("MATCH(title, content) AGAINST(%s IN NATURAL LANGUAGE MODE)") params.append(keyword) if min_price is not None: where_clauses.append("price >= %s") params.append(min_price) if max_price is not None: where_clauses.append("price <= %s") params.append(max_price) where_sql = " WHERE " + " AND ".join(where_clauses) if where_clauses else "" with db.get_cursor() as cursor: cursor.execute(f"SELECT COUNT(*) as total FROM yichens_posts{where_sql}", params) total = cursor.fetchone()["total"] cursor.execute(f""" SELECT * FROM yichens_posts{where_sql} ORDER BY created_at DESC LIMIT %s OFFSET %s """, params + [page_size, offset]) items = cursor.fetchall() return ApiResponse.success(PaginatedData.create(items, page, page_size, total)) @router.get("/posts/{post_id}", response_model=ApiResponse) async def get_yichens_post(post_id: str): """获取一尘帖子详情""" with db.get_cursor() as cursor: cursor.execute("SELECT * FROM yichens_posts WHERE post_id = %s", (post_id,)) item = cursor.fetchone() if not item: raise HTTPException(status_code=404, detail="Post not found") return ApiResponse.success(item) @router.post("/posts", response_model=ApiResponse, status_code=201) async def create_yichens_post(post_data: dict): """创建/更新一尘帖子""" required = ["post_id", "topic_id", "title"] for field in required: if field not in post_data: raise HTTPException(status_code=400, detail=f"Missing required field: {field}") with db.get_cursor() as cursor: cursor.execute(""" INSERT INTO yichens_posts ( post_id, topic_id, title, content, content_html, author_id, author_username, category, sub_category, post_type, price, price_unit, view_count, reply_count, like_count, is_top, is_essence, is_closed, created_at, updated_at ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) ON DUPLICATE KEY UPDATE title = VALUES(title), content = VALUES(content), content_html = VALUES(content_html), author_username = VALUES(author_username), category = VALUES(category), post_type = VALUES(post_type), price = VALUES(price), view_count = VALUES(view_count), reply_count = VALUES(reply_count), like_count = VALUES(like_count), is_top = VALUES(is_top), is_essence = VALUES(is_essence), is_closed = VALUES(is_closed), updated_at = VALUES(updated_at), crawled_at = NOW() """, ( post_data.get("post_id"), post_data.get("topic_id"), post_data.get("title"), post_data.get("content"), post_data.get("content_html"), post_data.get("author_id"), post_data.get("author_username"), post_data.get("category"), post_data.get("sub_category"), post_data.get("post_type", "normal"), post_data.get("price"), post_data.get("price_unit"), post_data.get("view_count", 0), post_data.get("reply_count", 0), post_data.get("like_count", 0), post_data.get("is_top", False), post_data.get("is_essence", False), post_data.get("is_closed", False), post_data.get("created_at"), post_data.get("updated_at") )) return ApiResponse.success({"post_id": post_data["post_id"]}, "Post saved successfully") # ============ 回复管理 ============ @router.get("/posts/{post_id}/replies", response_model=ApiResponse) async def get_post_replies( post_id: str, page: int = Query(1, ge=1), page_size: int = Query(50, ge=1, le=200) ): """获取帖子回复列表""" offset = (page - 1) * page_size with db.get_cursor() as cursor: cursor.execute( "SELECT COUNT(*) as total FROM yichens_replies WHERE post_id = %s", (post_id,) ) total = cursor.fetchone()["total"] cursor.execute(""" SELECT * FROM yichens_replies WHERE post_id = %s ORDER BY floor_number ASC LIMIT %s OFFSET %s """, (post_id, page_size, offset)) items = cursor.fetchall() return ApiResponse.success(PaginatedData.create(items, page, page_size, total)) @router.post("/replies", response_model=ApiResponse, status_code=201) async def create_yichens_reply(reply_data: dict): """创建/更新回复""" required = ["reply_id", "post_id"] for field in required: if field not in reply_data: raise HTTPException(status_code=400, detail=f"Missing required field: {field}") with db.get_cursor() as cursor: cursor.execute(""" INSERT INTO yichens_replies ( reply_id, post_id, floor_number, content, content_html, author_id, author_username, like_count, created_at ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s) ON DUPLICATE KEY UPDATE content = VALUES(content), content_html = VALUES(content_html), like_count = VALUES(like_count), crawled_at = NOW() """, ( reply_data.get("reply_id"), reply_data.get("post_id"), reply_data.get("floor_number"), reply_data.get("content"), reply_data.get("content_html"), reply_data.get("author_id"), reply_data.get("author_username"), reply_data.get("like_count", 0), reply_data.get("created_at") )) return ApiResponse.success({"reply_id": reply_data["reply_id"]}, "Reply saved successfully") # ============ 统计 ============ @router.get("/statistics", response_model=ApiResponse) async def get_yichens_statistics(): """获取一尘数据统计""" with db.get_cursor() as cursor: cursor.execute("SELECT COUNT(*) as total FROM yichens_users") total_users = cursor.fetchone()["total"] cursor.execute("SELECT COUNT(*) as total FROM yichens_posts") total_posts = cursor.fetchone()["total"] cursor.execute(""" SELECT COUNT(*) as total FROM yichens_users WHERE is_seller = TRUE """) seller_count = cursor.fetchone()["total"] cursor.execute(""" SELECT category, COUNT(*) as count FROM yichens_posts WHERE category IS NOT NULL GROUP BY category ORDER BY count DESC LIMIT 10 """) category_stats = cursor.fetchall() cursor.execute(""" SELECT DATE(crawled_at) as date, COUNT(*) as count FROM yichens_posts WHERE crawled_at >= DATE_SUB(NOW(), INTERVAL 7 DAY) GROUP BY DATE(crawled_at) ORDER BY date DESC """) daily_stats = cursor.fetchall() return ApiResponse.success({ "total_users": total_users, "total_posts": total_posts, "seller_count": seller_count, "category_stats": category_stats, "daily_stats": daily_stats })