2026-04-16 14:27:41 +08:00
|
|
|
|
from fastapi import APIRouter, Depends, Query, HTTPException
|
|
|
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
|
|
from pydantic import BaseModel
|
|
|
|
|
|
from typing import Optional
|
|
|
|
|
|
from datetime import datetime, date
|
|
|
|
|
|
from app.core.database import get_db
|
|
|
|
|
|
from app.core.auth import get_current_user
|
|
|
|
|
|
from app.models.deal_info import DealInfo
|
|
|
|
|
|
|
|
|
|
|
|
router = APIRouter(prefix="/api/deal", tags=["成交行情"])
|
|
|
|
|
|
|
|
|
|
|
|
# ============ Schema ============
|
|
|
|
|
|
class DealInfoCreate(BaseModel):
|
|
|
|
|
|
title: str
|
|
|
|
|
|
content: Optional[str] = None
|
|
|
|
|
|
deal_price: Optional[float] = None
|
|
|
|
|
|
deal_date: Optional[str] = None # YYYY-MM-DD
|
|
|
|
|
|
packaging: Optional[str] = None
|
|
|
|
|
|
category: Optional[str] = None
|
|
|
|
|
|
is_graded: Optional[bool] = False
|
|
|
|
|
|
grading_company: Optional[str] = None
|
|
|
|
|
|
grading_score: Optional[str] = None
|
|
|
|
|
|
tail_number: Optional[str] = None
|
|
|
|
|
|
size_type: Optional[str] = None
|
|
|
|
|
|
version: Optional[str] = None
|
|
|
|
|
|
platform: Optional[str] = None
|
|
|
|
|
|
seller: Optional[str] = None
|
|
|
|
|
|
buyer: Optional[str] = None
|
|
|
|
|
|
|
|
|
|
|
|
class DealInfoUpdate(BaseModel):
|
|
|
|
|
|
title: Optional[str] = None
|
|
|
|
|
|
content: Optional[str] = None
|
|
|
|
|
|
deal_price: Optional[float] = None
|
|
|
|
|
|
deal_date: Optional[str] = None
|
|
|
|
|
|
packaging: Optional[str] = None
|
|
|
|
|
|
category: Optional[str] = None
|
|
|
|
|
|
is_graded: Optional[bool] = None
|
|
|
|
|
|
grading_company: Optional[str] = None
|
|
|
|
|
|
grading_score: Optional[str] = None
|
|
|
|
|
|
tail_number: Optional[str] = None
|
|
|
|
|
|
size_type: Optional[str] = None
|
|
|
|
|
|
version: Optional[str] = None
|
|
|
|
|
|
platform: Optional[str] = None
|
|
|
|
|
|
seller: Optional[str] = None
|
|
|
|
|
|
buyer: Optional[str] = None
|
|
|
|
|
|
status: Optional[str] = None
|
|
|
|
|
|
|
|
|
|
|
|
class DealInfoResponse(BaseModel):
|
|
|
|
|
|
id: str
|
|
|
|
|
|
user_id: Optional[str]
|
|
|
|
|
|
title: str
|
|
|
|
|
|
content: Optional[str]
|
|
|
|
|
|
deal_price: Optional[float]
|
|
|
|
|
|
deal_date: Optional[date]
|
|
|
|
|
|
deal_no: Optional[str]
|
|
|
|
|
|
packaging: Optional[str]
|
|
|
|
|
|
category: Optional[str]
|
|
|
|
|
|
is_graded: Optional[bool]
|
|
|
|
|
|
grading_company: Optional[str]
|
|
|
|
|
|
grading_score: Optional[str]
|
|
|
|
|
|
tail_number: Optional[str]
|
|
|
|
|
|
size_type: Optional[str]
|
|
|
|
|
|
version: Optional[str]
|
|
|
|
|
|
platform: Optional[str]
|
|
|
|
|
|
seller: Optional[str]
|
|
|
|
|
|
buyer: Optional[str]
|
|
|
|
|
|
status: str
|
|
|
|
|
|
view_count: int
|
|
|
|
|
|
contact_count: int
|
|
|
|
|
|
created_at: Optional[datetime]
|
|
|
|
|
|
updated_at: Optional[datetime]
|
|
|
|
|
|
|
|
|
|
|
|
class Config:
|
|
|
|
|
|
from_attributes = True
|
|
|
|
|
|
|
|
|
|
|
|
# 生成行情编号
|
|
|
|
|
|
def generate_deal_no(db: Session):
|
|
|
|
|
|
"""生成行情编号,从A000001开始递增"""
|
|
|
|
|
|
last = db.query(DealInfo).order_by(DealInfo.deal_no.desc()).first()
|
|
|
|
|
|
if last and last.deal_no:
|
|
|
|
|
|
# 例如 A000001 -> 2 -> A000002
|
|
|
|
|
|
num = int(last.deal_no[1:]) + 1
|
|
|
|
|
|
return f"A{num:06d}"
|
|
|
|
|
|
return "A000001"
|
|
|
|
|
|
|
|
|
|
|
|
# ============ API ============
|
|
|
|
|
|
@router.get("/list", response_model=list[DealInfoResponse])
|
|
|
|
|
|
def get_deal_list(
|
|
|
|
|
|
status: str = Query("active"),
|
|
|
|
|
|
deal_date: Optional[str] = Query(None),
|
|
|
|
|
|
page: int = Query(1, ge=1),
|
|
|
|
|
|
page_size: int = Query(20, ge=1, le=1000),
|
|
|
|
|
|
user_only: bool = Query(False), # 是否只查看自己的
|
|
|
|
|
|
current_user: Optional = Depends(get_current_user),
|
|
|
|
|
|
db: Session = Depends(get_db)
|
|
|
|
|
|
):
|
|
|
|
|
|
"""获取成交行情列表"""
|
|
|
|
|
|
query = db.query(DealInfo).filter(DealInfo.status == status)
|
|
|
|
|
|
|
|
|
|
|
|
# 我的行情:只查看自己的(管理员也只看自己的)
|
|
|
|
|
|
if user_only and current_user:
|
|
|
|
|
|
query = query.filter(DealInfo.user_id == current_user.f99_90_id)
|
|
|
|
|
|
|
|
|
|
|
|
# 成交日期过滤
|
|
|
|
|
|
if deal_date:
|
|
|
|
|
|
query = query.filter(DealInfo.deal_date == deal_date)
|
|
|
|
|
|
|
|
|
|
|
|
# 排序:优先成交日期倒序,同日按编号倒序
|
|
|
|
|
|
query = query.order_by(DealInfo.deal_date.desc().nullslast(), DealInfo.deal_no.desc().nullslast())
|
|
|
|
|
|
|
|
|
|
|
|
# 分页
|
|
|
|
|
|
offset = (page - 1) * page_size
|
2026-04-16 23:43:12 +08:00
|
|
|
|
total_count = query.count()
|
|
|
|
|
|
total_pages = (total_count + page_size - 1) // page_size
|
2026-04-16 14:27:41 +08:00
|
|
|
|
items = query.offset(offset).limit(page_size).all()
|
|
|
|
|
|
|
2026-04-16 23:43:12 +08:00
|
|
|
|
# 返回Response对象以添加自定义头
|
|
|
|
|
|
from fastapi.responses import JSONResponse
|
|
|
|
|
|
return JSONResponse(
|
2026-04-17 00:40:29 +08:00
|
|
|
|
content=[DealInfoResponse.model_validate(item).model_dump(mode='json') for item in items],
|
2026-04-16 23:43:12 +08:00
|
|
|
|
headers={"X-Total-Pages": str(total_pages), "X-Total-Count": str(total_count)}
|
|
|
|
|
|
)
|
2026-04-16 14:27:41 +08:00
|
|
|
|
|
|
|
|
|
|
@router.get("/stats")
|
|
|
|
|
|
def get_deal_stats(
|
|
|
|
|
|
db: Session = Depends(get_db)
|
|
|
|
|
|
):
|
|
|
|
|
|
"""获取成交行情统计"""
|
|
|
|
|
|
total = db.query(DealInfo).filter(DealInfo.status == "active").count()
|
|
|
|
|
|
|
|
|
|
|
|
# 按日期统计
|
|
|
|
|
|
from sqlalchemy import func
|
|
|
|
|
|
date_stats = db.query(
|
|
|
|
|
|
DealInfo.deal_date,
|
|
|
|
|
|
func.count(DealInfo.id).label('count')
|
|
|
|
|
|
).filter(
|
|
|
|
|
|
DealInfo.status == "active",
|
|
|
|
|
|
DealInfo.deal_date.isnot(None)
|
|
|
|
|
|
).group_by(DealInfo.deal_date).order_by(DealInfo.deal_date.desc()).limit(10).all()
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
"total": total,
|
|
|
|
|
|
"by_date": [{"date": str(d.deal_date), "count": d.count} for d in date_stats]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("", response_model=DealInfoResponse)
|
|
|
|
|
|
def create_deal(
|
|
|
|
|
|
data: DealInfoCreate,
|
|
|
|
|
|
current_user = Depends(get_current_user),
|
|
|
|
|
|
db: Session = Depends(get_db)
|
|
|
|
|
|
):
|
|
|
|
|
|
"""创建成交行情"""
|
|
|
|
|
|
if not current_user:
|
|
|
|
|
|
raise HTTPException(status_code=401, detail="请先登录")
|
|
|
|
|
|
|
|
|
|
|
|
# 生成行情编号
|
|
|
|
|
|
deal_no = generate_deal_no(db)
|
|
|
|
|
|
|
|
|
|
|
|
# 解析日期
|
|
|
|
|
|
deal_date = None
|
|
|
|
|
|
if data.deal_date:
|
|
|
|
|
|
try:
|
|
|
|
|
|
deal_date = datetime.strptime(data.deal_date, "%Y-%m-%d").date()
|
|
|
|
|
|
except:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
deal = DealInfo(
|
|
|
|
|
|
user_id=current_user.f99_90_id if current_user else None,
|
|
|
|
|
|
title=data.title,
|
|
|
|
|
|
content=data.content,
|
|
|
|
|
|
deal_price=data.deal_price,
|
|
|
|
|
|
deal_date=deal_date,
|
|
|
|
|
|
deal_no=deal_no,
|
|
|
|
|
|
packaging=data.packaging,
|
|
|
|
|
|
category=data.category,
|
|
|
|
|
|
is_graded=data.is_graded or False,
|
|
|
|
|
|
grading_company=data.grading_company,
|
|
|
|
|
|
grading_score=data.grading_score,
|
|
|
|
|
|
tail_number=data.tail_number,
|
|
|
|
|
|
size_type=data.size_type,
|
|
|
|
|
|
version=data.version,
|
|
|
|
|
|
platform=data.platform,
|
|
|
|
|
|
seller=data.seller,
|
|
|
|
|
|
buyer=data.buyer,
|
|
|
|
|
|
status="active"
|
|
|
|
|
|
)
|
|
|
|
|
|
db.add(deal)
|
|
|
|
|
|
db.commit()
|
|
|
|
|
|
db.refresh(deal)
|
|
|
|
|
|
return deal
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/category-stats")
|
|
|
|
|
|
def get_deal_category_stats(
|
|
|
|
|
|
version: str = Query("龙钞", description="版本筛选:龙钞、马钞、蛇钞、其他"),
|
|
|
|
|
|
db: Session = Depends(get_db)
|
|
|
|
|
|
):
|
|
|
|
|
|
"""获取成交行情分类汇总统计数据 - 后端计算优化版"""
|
|
|
|
|
|
from collections import defaultdict
|
|
|
|
|
|
|
|
|
|
|
|
# 定义版本前缀映射
|
|
|
|
|
|
version_prefix_map = {"龙钞": "J0", "马钞": "J1", "蛇钞": "J3"}
|
|
|
|
|
|
packagings = ["标百", "标十", "单张"]
|
|
|
|
|
|
category_map = {"通货": "带4号", "无4": "带7号", "永恒": "永恒号", "钻石": "钻石号"}
|
|
|
|
|
|
|
|
|
|
|
|
# 构建查询
|
|
|
|
|
|
query = db.query(DealInfo).filter(
|
|
|
|
|
|
DealInfo.status == "active", DealInfo.deal_price.isnot(None), DealInfo.deal_price > 0
|
|
|
|
|
|
)
|
|
|
|
|
|
if version != "其他" and version in version_prefix_map:
|
|
|
|
|
|
query = query.filter(DealInfo.title.startswith(version_prefix_map[version]))
|
|
|
|
|
|
|
|
|
|
|
|
deals = query.all()
|
|
|
|
|
|
stats = defaultdict(lambda: defaultdict(lambda: {"count": 0, "total": 0}))
|
|
|
|
|
|
|
|
|
|
|
|
for deal in deals:
|
|
|
|
|
|
content = deal.content or ""
|
|
|
|
|
|
packaging = deal.packaging
|
|
|
|
|
|
if not packaging and "包装:" in content:
|
|
|
|
|
|
packaging = content.split("包装:")[1].split("\n")[0].strip()
|
|
|
|
|
|
category = deal.category
|
|
|
|
|
|
if not category and "分类:" in content:
|
|
|
|
|
|
category = content.split("分类:")[1].split("\n")[0].strip()
|
|
|
|
|
|
if category in category_map:
|
|
|
|
|
|
category = category_map[category]
|
|
|
|
|
|
packaging = packaging or "单张"
|
|
|
|
|
|
category = category or "带4号"
|
|
|
|
|
|
stats[packaging][category]["count"] += 1
|
|
|
|
|
|
stats[packaging][category]["total"] += deal.deal_price
|
|
|
|
|
|
|
|
|
|
|
|
result = []
|
|
|
|
|
|
category_order = ['带4号', '带7号', '永恒号', '天马号', '天马王', '金山号', '金山王', '钻石号', '朦胧号', '朦胧王', '金马号', '金马王', '倒置号', '圆圆号']
|
|
|
|
|
|
for cat in category_order:
|
|
|
|
|
|
row = {"category": cat}
|
|
|
|
|
|
has_data = False
|
|
|
|
|
|
for pkg in packagings:
|
|
|
|
|
|
data = stats[pkg][cat]
|
|
|
|
|
|
if data["count"] > 0:
|
|
|
|
|
|
row[pkg] = {"avg": round(data["total"] / data["count"]), "count": data["count"]}
|
|
|
|
|
|
has_data = True
|
|
|
|
|
|
else:
|
|
|
|
|
|
row[pkg] = None
|
|
|
|
|
|
if has_data:
|
|
|
|
|
|
result.append(row)
|
|
|
|
|
|
return {"version": version, "data": result}
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/{deal_id}", response_model=DealInfoResponse)
|
|
|
|
|
|
def get_deal(
|
|
|
|
|
|
deal_id: str,
|
|
|
|
|
|
current_user = Depends(get_current_user),
|
|
|
|
|
|
db: Session = Depends(get_db)
|
|
|
|
|
|
):
|
|
|
|
|
|
"""获取成交行情详情"""
|
|
|
|
|
|
deal = db.query(DealInfo).filter(DealInfo.id == deal_id).first()
|
|
|
|
|
|
if not deal:
|
|
|
|
|
|
raise HTTPException(status_code=404, detail="成交行情不存在")
|
|
|
|
|
|
|
|
|
|
|
|
# 增加浏览数
|
|
|
|
|
|
deal.view_count += 1
|
|
|
|
|
|
db.commit()
|
|
|
|
|
|
|
|
|
|
|
|
return deal
|
|
|
|
|
|
|
|
|
|
|
|
@router.put("/{deal_id}", response_model=DealInfoResponse)
|
|
|
|
|
|
def update_deal(
|
|
|
|
|
|
deal_id: str,
|
|
|
|
|
|
data: DealInfoUpdate,
|
|
|
|
|
|
current_user = Depends(get_current_user),
|
|
|
|
|
|
db: Session = Depends(get_db)
|
|
|
|
|
|
):
|
|
|
|
|
|
"""更新成交行情"""
|
|
|
|
|
|
if not current_user:
|
|
|
|
|
|
raise HTTPException(status_code=401, detail="请先登录")
|
|
|
|
|
|
|
|
|
|
|
|
deal = db.query(DealInfo).filter(DealInfo.id == deal_id).first()
|
|
|
|
|
|
if not deal:
|
|
|
|
|
|
raise HTTPException(status_code=404, detail="成交行情不存在")
|
|
|
|
|
|
|
|
|
|
|
|
# 处理日期
|
|
|
|
|
|
if data.deal_date:
|
|
|
|
|
|
try:
|
|
|
|
|
|
data.deal_date = datetime.strptime(data.deal_date, "%Y-%m-%d").date()
|
|
|
|
|
|
except:
|
|
|
|
|
|
data.deal_date = None
|
|
|
|
|
|
|
|
|
|
|
|
for key, value in data.model_dump(exclude_unset=True).items():
|
|
|
|
|
|
setattr(deal, key, value)
|
|
|
|
|
|
|
|
|
|
|
|
db.commit()
|
|
|
|
|
|
db.refresh(deal)
|
|
|
|
|
|
return deal
|
|
|
|
|
|
|
|
|
|
|
|
@router.delete("/{deal_id}")
|
|
|
|
|
|
def delete_deal(
|
|
|
|
|
|
deal_id: str,
|
|
|
|
|
|
current_user = Depends(get_current_user),
|
|
|
|
|
|
db: Session = Depends(get_db)
|
|
|
|
|
|
):
|
|
|
|
|
|
"""删除成交行情"""
|
|
|
|
|
|
if not current_user:
|
|
|
|
|
|
raise HTTPException(status_code=401, detail="请先登录")
|
|
|
|
|
|
|
|
|
|
|
|
deal = db.query(DealInfo).filter(DealInfo.id == deal_id).first()
|
|
|
|
|
|
if not deal:
|
|
|
|
|
|
raise HTTPException(status_code=404, detail="成交行情不存在")
|
|
|
|
|
|
|
|
|
|
|
|
deal.status = "deleted"
|
|
|
|
|
|
db.commit()
|
|
|
|
|
|
|
|
|
|
|
|
return {"message": "删除成功"}
|