Compare commits
14 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
d35749e14b | |
|
|
23aa5d3de1 | |
|
|
241385ef0c | |
|
|
e962548ce3 | |
|
|
3f6296202b | |
|
|
6b03f5e9c1 | |
|
|
1d8246402c | |
|
|
88e734d6f7 | |
|
|
e4ef142ad2 | |
|
|
e7039d6bfa | |
|
|
404b966ba9 | |
|
|
2510f42b68 | |
|
|
25fc88f683 | |
|
|
6de39e55ae |
|
|
@ -1 +1 @@
|
||||||
1.2.38
|
1.2.57
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,9 @@ from app.core.database import SessionLocal
|
||||||
from app.models.models import User
|
from app.models.models import User
|
||||||
|
|
||||||
# 配置
|
# 配置
|
||||||
SECRET_KEY = os.getenv("SECRET_KEY", "your-secret-key-change-in-production")
|
SECRET_KEY = os.getenv("SECRET_KEY")
|
||||||
|
if not SECRET_KEY:
|
||||||
|
raise ValueError("SECRET_KEY environment variable is not set. Please configure it in production!")
|
||||||
ALGORITHM = "HS256"
|
ALGORITHM = "HS256"
|
||||||
ACCESS_TOKEN_EXPIRE_MINUTES = int(os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES", "10080")) # 7天
|
ACCESS_TOKEN_EXPIRE_MINUTES = int(os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES", "10080")) # 7天
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ from app.routers import auth, collections, operations
|
||||||
from app.routers import ocr as ocr_router
|
from app.routers import ocr as ocr_router
|
||||||
from app.routers import users as users_router
|
from app.routers import users as users_router
|
||||||
from app.routers import information as information_router
|
from app.routers import information as information_router
|
||||||
|
from app.routers import yichens as yichens_router
|
||||||
|
|
||||||
# 版本信息 - 从 config/VERSION 文件读取
|
# 版本信息 - 从 config/VERSION 文件读取
|
||||||
def get_version():
|
def get_version():
|
||||||
|
|
@ -51,10 +52,11 @@ app = FastAPI(
|
||||||
# 设置全局错误处理器
|
# 设置全局错误处理器
|
||||||
setup_error_handlers(app)
|
setup_error_handlers(app)
|
||||||
|
|
||||||
# CORS 配置
|
# CORS 配置 - 生产环境限制域名
|
||||||
|
ALLOWED_ORIGINS = os.getenv("ALLOWED_ORIGINS", "http://47.103.29.111,http://120.55.81.21,https://socoolbot.com").split(",")
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
allow_origins=["*"], # 生产环境应该限制域名
|
allow_origins=ALLOWED_ORIGINS, # 生产环境限制域名
|
||||||
allow_credentials=True,
|
allow_credentials=True,
|
||||||
allow_methods=["*"],
|
allow_methods=["*"],
|
||||||
allow_headers=["*"],
|
allow_headers=["*"],
|
||||||
|
|
@ -81,6 +83,7 @@ app.include_router(ocr_router.router) # OCR 识别
|
||||||
app.include_router(users_router.router) # 当前用户接口
|
app.include_router(users_router.router) # 当前用户接口
|
||||||
app.include_router(users_router.admin_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("/")
|
@app.get("/")
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ import uuid
|
||||||
import re
|
import re
|
||||||
from typing import Optional, List
|
from typing import Optional, List
|
||||||
from fastapi import APIRouter, Depends, HTTPException, status, Query, UploadFile, File
|
from fastapi import APIRouter, Depends, HTTPException, status, Query, UploadFile, File
|
||||||
from sqlalchemy import func, text
|
from sqlalchemy import func, text, case, nullsfirst
|
||||||
|
from sqlalchemy.dialects import postgresql as pg
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from app.core.database import get_db
|
from app.core.database import get_db
|
||||||
from app.core.auth import get_current_user
|
from app.core.auth import get_current_user
|
||||||
|
|
@ -56,6 +57,7 @@ def to_camel_case(data: dict) -> dict:
|
||||||
'f05_43_repair_fee': 'repairFee',
|
'f05_43_repair_fee': 'repairFee',
|
||||||
'f05_44_grading_fee': 'gradingFee',
|
'f05_44_grading_fee': 'gradingFee',
|
||||||
'f06_50_purpose': 'purpose',
|
'f06_50_purpose': 'purpose',
|
||||||
|
'images': 'images',
|
||||||
}
|
}
|
||||||
|
|
||||||
return {mapping.get(k, k): v for k, v in data.items()}
|
return {mapping.get(k, k): v for k, v in data.items()}
|
||||||
|
|
@ -63,23 +65,27 @@ def to_camel_case(data: dict) -> dict:
|
||||||
|
|
||||||
# 编码生成函数
|
# 编码生成函数
|
||||||
def generate_code(version: str, user_id: str, db: Session) -> str:
|
def generate_code(version: str, user_id: str, db: Session) -> str:
|
||||||
"""自动生成藏品编号 - 按用户独立编码"""
|
"""自动生成藏品编号 - 按用户独立编码,使用行锁防止并发冲突"""
|
||||||
import re
|
from sqlalchemy import text
|
||||||
|
|
||||||
# 查询当前用户的非空编码(不与其他用户混算)
|
# 使用 FOR UPDATE 行锁防止并发冲突
|
||||||
user_codes = db.query(Collection.f01_02_code).filter(
|
result = db.execute(
|
||||||
Collection.f01_02_code.isnot(None),
|
text("""
|
||||||
Collection.f99_91_user_id == user_id
|
SELECT f01_02_code FROM collections
|
||||||
).all()
|
WHERE f99_91_user_id = :user_id
|
||||||
|
AND f01_02_code IS NOT NULL
|
||||||
|
AND f01_02_code ~ '^\\d{4,5}$'
|
||||||
|
ORDER BY f01_02_code::int DESC
|
||||||
|
LIMIT 1
|
||||||
|
FOR UPDATE
|
||||||
|
"""),
|
||||||
|
{"user_id": user_id}
|
||||||
|
).fetchone()
|
||||||
|
|
||||||
max_num = 0
|
max_num = 0
|
||||||
for (code,) in user_codes:
|
if result and result[0]:
|
||||||
# 处理纯数字编码(支持4位和5位)
|
|
||||||
if re.match(r'^\d{4,5}$', code):
|
|
||||||
try:
|
try:
|
||||||
num = int(code)
|
max_num = int(result[0])
|
||||||
if num > max_num:
|
|
||||||
max_num = num
|
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
@ -275,68 +281,117 @@ def get_stats(
|
||||||
current_user: User = Depends(get_current_user),
|
current_user: User = Depends(get_current_user),
|
||||||
db: Session = Depends(get_db)
|
db: Session = Depends(get_db)
|
||||||
):
|
):
|
||||||
"""获取藏品统计"""
|
"""获取藏品统计 - 使用数据库聚合查询优化性能"""
|
||||||
# 获取所有藏品
|
from sqlalchemy import case
|
||||||
if current_user.role == "admin":
|
|
||||||
all_collections = db.query(Collection).all()
|
|
||||||
else:
|
|
||||||
all_collections = db.query(Collection).filter(
|
|
||||||
Collection.f99_91_user_id == current_user.f99_90_id
|
|
||||||
).all()
|
|
||||||
|
|
||||||
# 总数
|
# 基础查询条件 - 管理员查看所有藏品,普通用户只看自己的
|
||||||
total_count = len(all_collections)
|
base_filter = True if current_user.role == "admin" else Collection.f99_91_user_id == current_user.f99_90_id
|
||||||
|
|
||||||
|
# 总数 - 使用 COUNT
|
||||||
|
total_count = db.query(func.count(Collection.f99_90_id)).filter(base_filter).scalar() or 0
|
||||||
|
|
||||||
# 按分类统计
|
# 按分类统计
|
||||||
from collections import Counter
|
by_category = db.query(
|
||||||
by_category = Counter(c.f01_03_category for c in all_collections).items()
|
Collection.f01_03_category,
|
||||||
|
func.count(Collection.f99_90_id)
|
||||||
|
).filter(base_filter).group_by(Collection.f01_03_category).all()
|
||||||
|
|
||||||
# 按状态统计
|
# 按状态统计
|
||||||
by_status = Counter(c.f01_04_status for c in all_collections).items()
|
by_status = db.query(
|
||||||
|
Collection.f01_04_status,
|
||||||
|
func.count(Collection.f99_90_id)
|
||||||
|
).filter(base_filter).group_by(Collection.f01_04_status).all()
|
||||||
|
|
||||||
# 按是否评级统计
|
# 按是否评级统计
|
||||||
by_graded = Counter(c.f03_20_is_graded for c in all_collections).items()
|
by_graded = db.query(
|
||||||
|
Collection.f03_20_is_graded,
|
||||||
|
func.count(Collection.f99_90_id)
|
||||||
|
).filter(base_filter).group_by(Collection.f03_20_is_graded).all()
|
||||||
|
|
||||||
# 新增:8 个分布统计
|
# 按包装统计
|
||||||
by_packaging = Counter(c.f02_12_packaging for c in all_collections if c.f02_12_packaging).items()
|
by_packaging = db.query(
|
||||||
by_rarity = Counter(c.f02_13_rarity for c in all_collections if c.f02_13_rarity).items()
|
Collection.f02_12_packaging,
|
||||||
by_version = Counter(c.f02_11_version for c in all_collections if c.f02_11_version).items()
|
func.count(Collection.f99_90_id)
|
||||||
by_grading_company = Counter(c.f03_21_grading_company for c in all_collections if c.f03_20_is_graded and c.f03_21_grading_company).items()
|
).filter(base_filter, Collection.f02_12_packaging.isnot(None)).group_by(Collection.f02_12_packaging).all()
|
||||||
by_grading_score = Counter(c.f03_22_grading_score for c in all_collections if c.f03_20_is_graded and c.f03_22_grading_score).items()
|
|
||||||
by_special_mark = Counter(c.f04_30_special_mark for c in all_collections if c.f04_30_special_mark).items()
|
|
||||||
by_number_category = Counter(c.f02_14_number_category for c in all_collections if c.f02_14_number_category).items()
|
|
||||||
|
|
||||||
# 总成本: SUM(cost_price + repair_fee + grading_fee) for ALL collections
|
# 按稀有度统计
|
||||||
total_cost = sum(
|
by_rarity = db.query(
|
||||||
(c.f05_40_cost_price or 0) + (c.f05_43_repair_fee or 0) + (c.f05_44_grading_fee or 0)
|
Collection.f02_13_rarity,
|
||||||
for c in all_collections
|
func.count(Collection.f99_90_id)
|
||||||
|
).filter(base_filter, Collection.f02_13_rarity.isnot(None)).group_by(Collection.f02_13_rarity).all()
|
||||||
|
|
||||||
|
# 按版本统计
|
||||||
|
by_version = db.query(
|
||||||
|
Collection.f02_11_version,
|
||||||
|
func.count(Collection.f99_90_id)
|
||||||
|
).filter(base_filter, Collection.f02_11_version.isnot(None)).group_by(Collection.f02_11_version).all()
|
||||||
|
|
||||||
|
# 按评级公司统计
|
||||||
|
by_grading_company = db.query(
|
||||||
|
Collection.f03_21_grading_company,
|
||||||
|
func.count(Collection.f99_90_id)
|
||||||
|
).filter(base_filter, Collection.f03_20_is_graded == True, Collection.f03_21_grading_company.isnot(None)).group_by(Collection.f03_21_grading_company).all()
|
||||||
|
|
||||||
|
# 按评级分数统计
|
||||||
|
by_grading_score = db.query(
|
||||||
|
Collection.f03_22_grading_score,
|
||||||
|
func.count(Collection.f99_90_id)
|
||||||
|
).filter(base_filter, Collection.f03_20_is_graded == True, Collection.f03_22_grading_score.isnot(None)).group_by(Collection.f03_22_grading_score).all()
|
||||||
|
|
||||||
|
# 按特殊标记统计
|
||||||
|
by_special_mark = db.query(
|
||||||
|
Collection.f04_30_special_mark,
|
||||||
|
func.count(Collection.f99_90_id)
|
||||||
|
).filter(base_filter, Collection.f04_30_special_mark.isnot(None)).group_by(Collection.f04_30_special_mark).all()
|
||||||
|
|
||||||
|
# 按号码分类统计
|
||||||
|
by_number_category = db.query(
|
||||||
|
Collection.f02_14_number_category,
|
||||||
|
func.count(Collection.f99_90_id)
|
||||||
|
).filter(base_filter, Collection.f02_14_number_category.isnot(None)).group_by(Collection.f02_14_number_category).all()
|
||||||
|
|
||||||
|
# 财务统计 - 使用 SUM
|
||||||
|
cost_result = db.query(
|
||||||
|
func.sum(
|
||||||
|
(Collection.f05_40_cost_price or 0) +
|
||||||
|
(Collection.f05_43_repair_fee or 0) +
|
||||||
|
(Collection.f05_44_grading_fee or 0)
|
||||||
)
|
)
|
||||||
|
).filter(base_filter).scalar() or 0
|
||||||
|
|
||||||
# 预期利润: SUM(target_price - cost_price) for collections with target_price > 0
|
target_result = db.query(
|
||||||
expected_profit = sum(
|
func.sum(Collection.f05_41_target_price)
|
||||||
(c.f05_41_target_price or 0) - (c.f05_40_cost_price or 0)
|
).filter(base_filter, Collection.f05_41_target_price > 0).scalar() or 0
|
||||||
for c in all_collections
|
|
||||||
if c.f05_41_target_price and c.f05_41_target_price > 0
|
expected_profit = target_result - cost_result
|
||||||
|
|
||||||
|
# 已售商品统计 - 处理管理员和普通用户两种情况
|
||||||
|
if current_user.role == "admin":
|
||||||
|
sold_filter = (Collection.f01_04_status == 'sold') & (Collection.f05_42_goal_price > 0)
|
||||||
|
else:
|
||||||
|
sold_filter = (Collection.f99_91_user_id == current_user.f99_90_id) & (Collection.f01_04_status == 'sold') & (Collection.f05_42_goal_price > 0)
|
||||||
|
|
||||||
|
total_revenue = db.query(func.sum(Collection.f05_42_goal_price)).filter(sold_filter).scalar() or 0
|
||||||
|
|
||||||
|
from sqlalchemy import case
|
||||||
|
total_profit = db.query(
|
||||||
|
func.sum(
|
||||||
|
case(
|
||||||
|
(Collection.f05_42_goal_price.isnot(None),
|
||||||
|
Collection.f05_42_goal_price - func.coalesce(Collection.f05_40_cost_price, 0) - func.coalesce(Collection.f05_43_repair_fee, 0) - func.coalesce(Collection.f05_44_grading_fee, 0)),
|
||||||
|
else_=0
|
||||||
)
|
)
|
||||||
|
|
||||||
# 已售商品:状态为 sold 且出售价 > 0
|
|
||||||
sold_collections = [
|
|
||||||
c for c in all_collections
|
|
||||||
if c.f01_04_status == 'sold' and c.f05_42_goal_price and c.f05_42_goal_price > 0
|
|
||||||
]
|
|
||||||
|
|
||||||
# 总收入:SUM(出售价) for 已售商品(售价>0)
|
|
||||||
total_revenue = sum(
|
|
||||||
c.f05_42_goal_price or 0
|
|
||||||
for c in sold_collections
|
|
||||||
)
|
)
|
||||||
|
).filter(sold_filter).scalar() or 0
|
||||||
|
|
||||||
# 总利润(已实现利润):SUM(出售价 - 成本价 - 修复费 - 评级费) for 已售商品
|
# 盈亏统计
|
||||||
# 单藏品总成本 = 成本价 + 修复费 + 评级费
|
profit_count = db.query(func.count(Collection.f99_90_id)).filter(
|
||||||
total_profit = sum(
|
sold_filter, Collection.f05_42_goal_price > Collection.f05_40_cost_price
|
||||||
(c.f05_42_goal_price or 0) - (c.f05_40_cost_price or 0) - (c.f05_43_repair_fee or 0) - (c.f05_44_grading_fee or 0)
|
).scalar() or 0
|
||||||
for c in sold_collections
|
|
||||||
)
|
loss_count = db.query(func.count(Collection.f99_90_id)).filter(
|
||||||
|
sold_filter, Collection.f05_42_goal_price <= Collection.f05_40_cost_price
|
||||||
|
).scalar() or 0
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"totalCount": total_count,
|
"totalCount": total_count,
|
||||||
|
|
@ -350,13 +405,12 @@ def get_stats(
|
||||||
"byGradingScore": [{"score": s, "count": n} for s, n in by_grading_score],
|
"byGradingScore": [{"score": s, "count": n} for s, n in by_grading_score],
|
||||||
"bySpecialMark": [{"mark": m, "count": n} for m, n in by_special_mark],
|
"bySpecialMark": [{"mark": m, "count": n} for m, n in by_special_mark],
|
||||||
"byNumberCategory": [{"numberCategory": n, "count": c} for n, c in by_number_category],
|
"byNumberCategory": [{"numberCategory": n, "count": c} for n, c in by_number_category],
|
||||||
# 盈亏统计(只统计已售且有价格的藏品)
|
|
||||||
"byProfitLoss": [
|
"byProfitLoss": [
|
||||||
{"type": "profit", "label": "盈利", "count": sum(1 for c in sold_collections if c.f05_42_goal_price and c.f05_40_cost_price and c.f05_42_goal_price > c.f05_40_cost_price)},
|
{"type": "profit", "label": "盈利", "count": profit_count},
|
||||||
{"type": "loss", "label": "亏损", "count": sum(1 for c in sold_collections if c.f05_42_goal_price and c.f05_40_cost_price and c.f05_42_goal_price <= c.f05_40_cost_price)}
|
{"type": "loss", "label": "亏损", "count": loss_count}
|
||||||
],
|
],
|
||||||
"totalCost": total_cost,
|
"totalCost": cost_result,
|
||||||
"totalTarget": sum(c.f05_41_target_price or 0 for c in all_collections),
|
"totalTarget": target_result,
|
||||||
"expectedProfit": expected_profit,
|
"expectedProfit": expected_profit,
|
||||||
"totalRevenue": total_revenue,
|
"totalRevenue": total_revenue,
|
||||||
"totalProfit": total_profit
|
"totalProfit": total_profit
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,252 @@
|
||||||
|
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]
|
||||||
|
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", response_model=List[PostItem])
|
||||||
|
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,
|
||||||
|
db: Session = Depends(get_coolbot_db)
|
||||||
|
):
|
||||||
|
"""获取帖子列表"""
|
||||||
|
query = """
|
||||||
|
SELECT post_id, title, content, 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 "",
|
||||||
|
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]
|
||||||
|
|
||||||
|
@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]
|
||||||
|
|
@ -1 +1 @@
|
||||||
v1.2.41
|
VERSION=1.2.57
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
<title>甲辰收藏 v1.2.38</title>
|
<title>甲辰收藏 v1.2.53</title>
|
||||||
|
|
||||||
<!-- Favicon -->
|
<!-- Favicon -->
|
||||||
<link rel="icon" type="image/png" href="/static/icons/favicon.png" />
|
<link rel="icon" type="image/png" href="/static/icons/favicon.png" />
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "jiachenlong-frontend",
|
"name": "jiachenlong-frontend",
|
||||||
"version": "1.2.12",
|
"version": "1.2.57",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "甲辰藏品管理系统 - 移动端前端",
|
"description": "甲辰藏品管理系统 - 移动端前端",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue