325 lines
12 KiB
Python
325 lines
12 KiB
Python
"""一尘数据管理路由"""
|
|
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
|
|
})
|