diff --git a/backend/app/routers/information.py b/backend/app/routers/information.py index 655220f..e9392de 100644 --- a/backend/app/routers/information.py +++ b/backend/app/routers/information.py @@ -1,2948 +1,1473 @@ -# information - 资讯路由 -# Version: 0.0.1 -# 更新: - +# 资讯API路由 from fastapi import APIRouter, Depends, HTTPException, Query, Response, Body -# 更新: from sqlalchemy.orm import Session, joinedload -# 更新: from sqlalchemy import text -# 更新: from typing import List, Optional -# 更新: from pydantic import BaseModel -# 更新: from datetime import datetime, date -# 更新: import os -# 更新: -# 更新: from app.core.database import get_db -# 更新: from app.core.auth import get_current_user -# 更新: from app.core.coolbot_db import coolbot_engine -# 更新: from app.models.models import User, Information, Collection -# 更新: -# 更新: router = APIRouter(prefix="/api/information", tags=["资讯"]) -# 更新: -# 更新: -# 更新: # Schema -# 更新: class InformationCreate(BaseModel): -# 更新: info_type: str # seek-寻配号, deal-成交数据, publish-发布 -# 更新: title: str -# 更新: content: Optional[str] -# 更新: collection_id: Optional[str] = None -# 更新: expect_category: Optional[str] = None -# 更新: expect_version: Optional[str] = None -# 更新: expect_packaging: Optional[str] = None -# 更新: expect_number: Optional[str] = None -# 更新: expect_price_min: Optional[float] = None -# 更新: expect_price_max: Optional[float] = None -# 更新: deal_price: Optional[float] = None -# 更新: deal_date: Optional[date] = None -# 更新: packaging: Optional[str] = None -# 更新: is_graded: Optional[bool] = False -# 更新: grading_company: Optional[str] = None -# 更新: grading_score: Optional[str] = None -# 更新: category: Optional[str] = None -# 更新: deal_no: Optional[str] = None -# 更新: -# 更新: -# 更新: class InformationUpdate(BaseModel): -# 更新: title: Optional[str] = None -# 更新: content: Optional[str] = None -# 更新: status: Optional[str] = None -# 更新: expect_category: Optional[str] = None -# 更新: expect_version: Optional[str] = None -# 更新: expect_packaging: Optional[str] = None -# 更新: expect_number: Optional[str] = None -# 更新: expect_price_min: Optional[float] = None -# 更新: expect_price_max: Optional[float] = None -# 更新: deal_price: Optional[float] = None -# 更新: deal_date: Optional[date] = None -# 更新: packaging: Optional[str] = None -# 更新: is_graded: Optional[bool] = None -# 更新: grading_company: Optional[str] = None -# 更新: grading_score: Optional[str] = None -# 更新: -# 更新: -# 更新: class InformationResponse(BaseModel): -# 更新: id: str -# 更新: user_id: str -# 更新: info_type: str -# 更新: title: str -# 更新: content: Optional[str] -# 更新: collection_id: Optional[str] -# 更新: expect_category: Optional[str] -# 更新: expect_version: Optional[str] -# 更新: expect_packaging: Optional[str] -# 更新: expect_number: Optional[str] -# 更新: expect_price_min: Optional[float] -# 更新: expect_price_max: Optional[float] -# 更新: deal_price: Optional[float] -# 更新: deal_date: Optional[date] -# 更新: status: str -# 更新: is_matched: Optional[str] = "pending" -# 更新: matched_user_id: Optional[str] = None -# 更新: matched_contact: Optional[str] = None -# 更新: view_count: int -# 更新: contact_count: int -# 更新: created_at: datetime -# 更新: # 评级相关字段 -# 更新: packaging: Optional[str] = None -# 更新: is_graded: Optional[bool] = False -# 更新: grading_company: Optional[str] = None -# 更新: grading_score: Optional[str] = None -# 更新: category: Optional[str] = None -# 更新: deal_no: Optional[str] = None -# 更新: # 用户信息 -# 更新: user_name: Optional[str] = None -# 更新: user_avatar: Optional[str] = None -# 更新: # 关联藏品信息 -# 更新: collection_name: Optional[str] = None -# 更新: collection_category: Optional[str] = None -# 更新: collection_version: Optional[str] = None -# 更新: collection_number: Optional[str] = None -# 更新: # 匹配数量(我的藏品中满足条件的数量) -# 更新: matched_count: Optional[int] = 0 -# 更新: # 网络数据匹配数量(coolbot_data数据库中满足条件的数量) -# 更新: network_matched_count: Optional[int] = 0 -# 更新: -# 更新: class Config: -# 更新: from_attributes = True -# 更新: -# 更新: -# 更新: # 资讯列表 -# 更新: @router.get("/list", response_model=List[InformationResponse]) -# 更新: def get_information_list( -# 更新: info_type: Optional[str] = Query(None, description="类型: seek/deal/publish"), -# 更新: status: str = Query("active", description="状态: active/closed/expired"), -# 更新: user_id: Optional[str] = Query(None, description="用户ID,用于获取该用户的行情"), -# 更新: deal_date: Optional[str] = Query(None, description="成交日期过滤,格式YYYY-MM-DD"), -# 更新: page: int = Query(1, ge=1), -# 更新: page_size: int = Query(20, ge=1, le=500), -# 更新: current_user: Optional[User] = Depends(get_current_user), -# 更新: db: Session = Depends(get_db), -# 更新: response: Response = None -# 更新: ): -# 更新: """获取资讯列表(公开,无需登录)""" -# 更新: query = db.query(Information).options( -# 更新: joinedload(Information.user), -# 更新: joinedload(Information.collection) -# 更新: ).filter(Information.status == status) -# 更新: -# 更新: if info_type: -# 更新: query = query.filter(Information.info_type == info_type) -# 更新: -# 更新: # 如果传入了user_id,只返回该用户的行情 -# 更新: if user_id: -# 更新: query = query.filter(Information.user_id == user_id) -# 更新: -# 更新: # 成交日期过滤 -# 更新: if deal_date: -# 更新: from datetime import date -# 更新: deal_date_obj = date.fromisoformat(deal_date) -# 更新: query = query.filter(Information.deal_date == deal_date_obj) -# 更新: -# 更新: # 按创建时间倒序 -# 更新: query = query.order_by(Information.deal_date.desc().nullslast(), Information.deal_no.desc().nullslast()) -# 更新: -# 更新: # 分页 -# 更新: offset = (page - 1) * page_size -# 更新: items = query.offset(offset).limit(page_size).all() -# 更新: -# 更新: # 转换结果 -# 更新: result = [] -# 更新: for item in items: -# 更新: # 计算匹配数量(仅对seek类型,且用户登录时) -# 更新: matched_count = 0 -# 更新: if item.info_type == 'seek' and item.expect_number and current_user: -# 更新: matched_count = match_collections_count(db, current_user.f99_90_id, item.expect_number) -# 更新: -# 更新: result.append(InformationResponse( -# 更新: id=item.id, -# 更新: user_id=item.user_id, -# 更新: info_type=item.info_type, -# 更新: title=item.title, -# 更新: content=item.content, -# 更新: collection_id=item.collection_id, -# 更新: expect_category=item.expect_category, -# 更新: expect_version=item.expect_version, -# 更新: expect_packaging=item.expect_packaging, -# 更新: expect_number=item.expect_number, -# 更新: expect_price_min=item.expect_price_min, -# 更新: expect_price_max=item.expect_price_max, -# 更新: deal_price=item.deal_price, -# 更新: deal_date=item.deal_date, -# 更新: status=item.status, -# 更新: is_matched=item.is_matched, -# 更新: matched_user_id=item.matched_user_id, -# 更新: matched_contact=item.matched_contact, -# 更新: view_count=item.view_count, -# 更新: contact_count=item.contact_count, -# 更新: created_at=item.created_at, -# 更新: user_name=item.user.f01_01_name if item.user else None, -# 更新: user_avatar=item.user.avatar if item.user else None, -# 更新: collection_name=item.collection.f01_01_name if item.collection else None, -# 更新: collection_category=item.collection.f01_03_category if item.collection else None, -# 更新: collection_version=item.collection.f02_11_version if item.collection else None, -# 更新: collection_number=item.collection.f02_10_prefix_serial if item.collection else None, -# 更新: packaging=item.packaging, -# 更新: is_graded=item.is_graded or False, -# 更新: grading_company=item.grading_company, -# 更新: grading_score=item.grading_score, -# 更新: category=item.category, -# 更新: deal_no=item.deal_no, -# 更新: matched_count=matched_count, -# 更新: network_matched_count=match_collections_count_from_coolbot(item.expect_number) if item.info_type == 'seek' and item.expect_number else 0, -# 更新: )) -# 更新: -# 更新: # 获取总数并设置响应头 -# 更新: from fastapi import Response -# 更新: total_query = db.query(Information).filter(Information.status == status) -# 更新: if info_type: -# 更新: total_query = total_query.filter(Information.info_type == info_type) -# 更新: total_count = total_query.count() -# 更新: total_pages = (total_count + page_size - 1) // page_size -# 更新: -# 更新: # 设置响应头 -# 更新: response.headers['X-Total-Pages'] = str(total_pages) -# 更新: response.headers['X-Total-Count'] = str(total_count) -# 更新: -# 更新: return result -# 更新: -# 更新: -# 更新: def match_collections_count(db: Session, user_id: str, expect_number: str) -> int: -# 更新: """根据号码特征计算匹配藏品数量""" -# 更新: if not expect_number or len(expect_number) != 10: -# 更新: return 0 -# 更新: -# 更新: # 固定前缀 -# 更新: if not expect_number.startswith('J0'): -# 更新: return 0 -# 更新: -# 更新: pattern = expect_number[2:] # 后8位 -# 更新: if not pattern: -# 更新: return 0 -# 更新: -# 更新: # 获取用户所有藏品 -# 更新: collections = db.query(Collection).filter( -# 更新: Collection.f99_91_user_id == user_id, -# 更新: Collection.f01_04_status == "in_collection" -# 更新: ).all() -# 更新: -# 更新: count = 0 -# 更新: for c in collections: -# 更新: number = c.f02_10_prefix_serial or '' -# 更新: # 去掉J0前缀后取前8位 -# 更新: if len(number) >= 10 and number.startswith('J0'): -# 更新: col_pattern = number[2:10] -# 更新: if match_pattern(col_pattern, pattern): -# 更新: count += 1 -# 更新: elif len(number) >= 8: -# 更新: col_pattern = number[:8] -# 更新: if match_pattern(col_pattern, pattern): -# 更新: count += 1 -# 更新: -# 更新: return count -# 更新: -# 更新: -# 更新: def match_collections_count_from_coolbot(expect_number: str) -> int: -# 更新: """根据号码特征计算匹配藏品数量(从coolbot_data数据库)""" -# 更新: if not expect_number or len(expect_number) != 10: -# 更新: return 0 -# 更新: -# 更新: # 固定前缀 -# 更新: if not expect_number.startswith('J0'): -# 更新: return 0 -# 更新: -# 更新: pattern = expect_number[2:] # 后8位 -# 更新: if not pattern: -# 更新: return 0 -# 更新: -# 更新: # 直接查询coolbot_data数据库 -# 更新: query = text(""" -# 更新: SELECT COUNT(*) FROM collections -# 更新: WHERE crown_code IS NOT NULL -# 更新: AND crown_code != '' -# 更新: AND LENGTH(crown_code) >= 10 -# 更新: AND crown_code LIKE 'J0%' -# 更新: """) -# 更新: -# 更新: try: -# 更新: with coolbot_engine.connect() as conn: -# 更新: result = conn.execute(query) -# 更新: total_count = result.scalar() or 0 -# 更新: -# 更新: # 遍历匹配 -# 更新: query_all = text(""" -# 更新: SELECT id, crown_code FROM collections -# 更新: WHERE crown_code IS NOT NULL -# 更新: AND crown_code != '' -# 更新: AND LENGTH(crown_code) >= 10 -# 更新: AND crown_code LIKE 'J0%' -# 更新: """) -# 更新: result = conn.execute(query_all) -# 更新: -# 更新: match_count = 0 -# 更新: for row in result: -# 更新: crown_code = row[1] -# 更新: if crown_code and len(crown_code) >= 10: -# 更新: col_pattern = crown_code[2:10] -# 更新: if match_pattern(col_pattern, pattern): -# 更新: match_count += 1 -# 更新: -# 更新: return match_count -# 更新: except Exception as e: -# 更新: print(f"Error querying coolbot_data: {e}") -# 更新: return 0 -# 更新: -# 更新: -# 更新: def match_collections_list_from_coolbot(expect_number: str, limit: int = 20) -> List[dict]: -# 更新: """获取匹配的藏品列表(从coolbot_data数据库)""" -# 更新: if not expect_number or len(expect_number) != 10: -# 更新: return [] -# 更新: -# 更新: # 固定前缀 -# 更新: if not expect_number.startswith('J0'): -# 更新: return [] -# 更新: -# 更新: pattern = expect_number[2:] # 后8位 -# 更新: if not pattern: -# 更新: return [] -# 更新: -# 更新: # 直接查询coolbot_data数据库 -# 更新: query = text(""" -# 更新: SELECT id, name, category, crown_code, price, post_title, post_url, author, post_crawled_at -# 更新: FROM collections -# 更新: WHERE crown_code IS NOT NULL -# 更新: AND crown_code != '' -# 更新: AND LENGTH(crown_code) >= 10 -# 更新: AND crown_code LIKE 'J0%' -# 更新: """) -# 更新: -# 更新: try: -# 更新: with coolbot_engine.connect() as conn: -# 更新: result = conn.execute(query) -# 更新: -# 更新: matched = [] -# 更新: for row in result: -# 更新: crown_code = row[3] -# 更新: if crown_code and len(crown_code) >= 10: -# 更新: col_pattern = crown_code[2:10] -# 更新: if match_pattern(col_pattern, pattern): -# 更新: matched.append({ -# 更新: "id": row[0], -# 更新: "name": row[1], -# 更新: "category": row[2], -# 更新: "crown_code": crown_code, -# 更新: "price": float(row[4]) if row[4] else None, -# 更新: "post_title": row[5], -# 更新: "post_url": row[6], -# 更新: "author": row[7], -# 更新: "post_crawled_at": row[8].isoformat() if row[8] else None -# 更新: }) -# 更新: if len(matched) >= limit: -# 更新: break -# 更新: -# 更新: return matched -# 更新: except Exception as e: -# 更新: print(f"Error querying coolbot_data: {e}") -# 更新: return [] -# 更新: -# 更新: -# 更新: def match_pattern(col_number: str, pattern: str) -> bool: -# 更新: """匹配号码特征模式""" -# 更新: # X = 任意数字 -# 更新: # A = 非4 -# 更新: # B = 非47 -# 更新: # C = 非347 -# 更新: # D = 非247 -# 更新: # E = 非2347 -# 更新: # F = 非23457 -# 更新: # G = 非123457 -# 更新: -# 更新: # 注意:col_number已经是去掉J0前缀后的8位号码,不需要再处理 -# 更新: col_num = col_number -# 更新: -# 更新: for i, p in enumerate(pattern): -# 更新: if i >= len(col_num): -# 更新: return False -# 更新: -# 更新: c = col_num[i] -# 更新: -# 更新: if p == 'X': -# 更新: if not c.isdigit(): -# 更新: return False -# 更新: elif p == 'A': -# 更新: if c == '4': -# 更新: return False -# 更新: elif p == 'B': -# 更新: if c in '47': -# 更新: return False -# 更新: elif p == 'C': -# 更新: if c in '347': -# 更新: return False -# 更新: elif p == 'D': -# 更新: if c in '247': -# 更新: return False -# 更新: elif p == 'E': -# 更新: if c in '2347': -# 更新: return False -# 更新: elif p == 'F': -# 更新: if c in '23457': -# 更新: return False -# 更新: elif p == 'G': -# 更新: if c in '123457': -# 更新: return False -# 更新: else: -# 更新: # 数字或字母必须完全匹配 -# 更新: if p != c: -# 更新: return False -# 更新: -# 更新: return True -# 更新: -# 更新: -# 更新: # 获取单条资讯 -# 更新: @router.get("/{info_id}", response_model=InformationResponse) -# 更新: def get_information( -# 更新: info_id: str, -# 更新: db: Session = Depends(get_db) -# 更新: ): -# 更新: """获取资讯详情""" -# 更新: item = db.query(Information).options( -# 更新: joinedload(Information.user), -# 更新: joinedload(Information.collection) -# 更新: ).filter(Information.id == info_id).first() -# 更新: -# 更新: if not item: -# 更新: raise HTTPException(status_code=404, detail="资讯不存在") -# 更新: -# 更新: # 增加浏览次数 -# 更新: item.view_count += 1 -# 更新: db.commit() -# 更新: -# 更新: return InformationResponse( -# 更新: id=item.id, -# 更新: user_id=item.user_id, -# 更新: info_type=item.info_type, -# 更新: title=item.title, -# 更新: content=item.content, -# 更新: collection_id=item.collection_id, -# 更新: expect_category=item.expect_category, -# 更新: expect_version=item.expect_version, -# 更新: expect_packaging=item.expect_packaging, -# 更新: expect_number=item.expect_number, -# 更新: expect_price_min=item.expect_price_min, -# 更新: expect_price_max=item.expect_price_max, -# 更新: deal_price=item.deal_price, -# 更新: deal_date=item.deal_date, -# 更新: status=item.status, -# 更新: view_count=item.view_count, -# 更新: contact_count=item.contact_count, -# 更新: created_at=item.created_at, -# 更新: packaging=item.packaging, -# 更新: is_graded=item.is_graded or False, -# 更新: grading_company=item.grading_company, -# 更新: grading_score=item.grading_score, -# 更新: category=item.category, -# 更新: user_name=item.user.f01_01_name if item.user else None, -# 更新: user_avatar=item.user.avatar if item.user else None, -# 更新: collection_name=item.collection.f01_01_name if item.collection else None, -# 更新: collection_category=item.collection.f01_03_category if item.collection else None, -# 更新: collection_version=item.collection.f02_11_version if item.collection else None, -# 更新: collection_number=item.collection.f02_10_prefix_serial if item.collection else None, -# 更新: ) -# 更新: -# 更新: -# 更新: # 发布资讯 -# 更新: @router.post("/", response_model=InformationResponse) -# 更新: def create_information( -# 更新: data: InformationCreate, -# 更新: current_user: User = Depends(get_current_user), -# 更新: db: Session = Depends(get_db) -# 更新: ): -# 更新: """发布资讯""" -# 更新: # 生成行情编号:日期 + 5位自然数(从00001开始) -# 更新: deal_no = None -# 更新: if data.info_type == 'deal': -# 更新: today = datetime.now().strftime('%Y%m%d') -# 更新: # 查询当天已有行情数量 -# 更新: - from app.models.models import Information -# 更新: + count_today = db.query(Information).filter( -# 更新: Information.info_type == 'deal', -# 更新: Information.deal_no.like(f'DJ{today}%') -# 更新: ).count() -# 更新: # 编号 = 日期 + 5位自然数(如 DJ2026041100001) -# 更新: seq = count_today + 1 -# 更新: deal_no = f"{today[2:]}{seq:04d}" -# 更新: -# 更新: info = Information( -# 更新: user_id=current_user.f99_90_id, -# 更新: info_type=data.info_type, -# 更新: title=data.title, -# 更新: content=data.content, -# 更新: collection_id=data.collection_id, -# 更新: expect_category=data.expect_category, -# 更新: expect_version=data.expect_version, -# 更新: expect_packaging=data.expect_packaging, -# 更新: expect_number=data.expect_number, -# 更新: expect_price_min=data.expect_price_min, -# 更新: expect_price_max=data.expect_price_max, -# 更新: deal_price=data.deal_price, -# 更新: deal_date=data.deal_date, -# 更新: packaging=data.packaging, -# 更新: is_graded=data.is_graded or False, -# 更新: grading_company=data.grading_company, -# 更新: grading_score=data.grading_score, -# 更新: category=data.category, -# 更新: deal_no=deal_no, -# 更新: status="active" -# 更新: ) -# 更新: db.add(info) -# 更新: db.commit() -# 更新: db.refresh(info) -# 更新: -# 更新: return InformationResponse( -# 更新: id=info.id, -# 更新: user_id=info.user_id, -# 更新: info_type=info.info_type, -# 更新: title=info.title, -# 更新: content=info.content, -# 更新: collection_id=info.collection_id, -# 更新: expect_category=info.expect_category, -# 更新: expect_version=info.expect_version, -# 更新: expect_packaging=info.expect_packaging, -# 更新: expect_number=info.expect_number, -# 更新: expect_price_min=info.expect_price_min, -# 更新: expect_price_max=info.expect_price_max, -# 更新: deal_price=info.deal_price, -# 更新: deal_date=info.deal_date, -# 更新: status=info.status, -# 更新: view_count=info.view_count, -# 更新: contact_count=info.contact_count, -# 更新: created_at=info.created_at, -# 更新: user_name=current_user.f01_01_name, -# 更新: user_avatar=current_user.avatar, -# 更新: collection_name=None, -# 更新: collection_category=None, -# 更新: collection_version=None, -# 更新: collection_number=None, -# 更新: ) -# 更新: -# 更新: -# 更新: # 更新资讯 -# 更新: @router.put("/{info_id}", response_model=InformationResponse) -# 更新: def update_information( -# 更新: info_id: str, -# 更新: data: InformationUpdate, -# 更新: current_user: Optional[User] = Depends(get_current_user), -# 更新: db: Session = Depends(get_db) -# 更新: ): -# 更新: """更新资讯""" -# 更新: if not current_user: -# 更新: raise HTTPException(status_code=401, detail="请先登录") -# 更新: # 处理f99_90_id为None的情况 -# 更新: user_filter = current_user.f99_90_id if current_user and current_user.f99_90_id else Information.user_id -# 更新: info = db.query(Information).filter( -# 更新: Information.id == info_id, -# 更新: Information.user_id == user_filter -# 更新: ).first() -# 更新: -# 更新: if not info: -# 更新: raise HTTPException(status_code=404, detail="资讯不存在或无权修改") -# 更新: -# 更新: # 更新字段 -# 更新: if data.title is not None: -# 更新: info.title = data.title -# 更新: if data.content is not None: -# 更新: info.content = data.content -# 更新: if data.status is not None: -# 更新: info.status = data.status -# 更新: if data.expect_category is not None: -# 更新: info.expect_category = data.expect_category -# 更新: if data.expect_version is not None: -# 更新: info.expect_version = data.expect_version -# 更新: if data.expect_packaging is not None: -# 更新: info.expect_packaging = data.expect_packaging -# 更新: if data.expect_number is not None: -# 更新: info.expect_number = data.expect_number -# 更新: if data.expect_price_min is not None: -# 更新: info.expect_price_min = data.expect_price_min -# 更新: if data.expect_price_max is not None: -# 更新: info.expect_price_max = data.expect_price_max -# 更新: if data.deal_price is not None: -# 更新: info.deal_price = data.deal_price -# 更新: if data.deal_date is not None: -# 更新: info.deal_date = data.deal_date -# 更新: if data.packaging is not None: -# 更新: info.packaging = data.packaging -# 更新: if data.is_graded is not None: -# 更新: info.is_graded = data.is_graded -# 更新: if data.grading_company is not None: -# 更新: info.grading_company = data.grading_company -# 更新: if data.grading_score is not None: -# 更新: info.grading_score = data.grading_score -# 更新: -# 更新: db.commit() -# 更新: db.refresh(info) -# 更新: -# 更新: return InformationResponse( -# 更新: id=info.id, -# 更新: user_id=info.user_id, -# 更新: info_type=info.info_type, -# 更新: title=info.title, -# 更新: content=info.content, -# 更新: collection_id=info.collection_id, -# 更新: expect_category=info.expect_category, -# 更新: expect_version=info.expect_version, -# 更新: expect_packaging=info.expect_packaging, -# 更新: expect_number=info.expect_number, -# 更新: expect_price_min=info.expect_price_min, -# 更新: expect_price_max=info.expect_price_max, -# 更新: deal_price=info.deal_price, -# 更新: deal_date=info.deal_date, -# 更新: status=info.status, -# 更新: view_count=info.view_count, -# 更新: contact_count=info.contact_count, -# 更新: created_at=info.created_at, -# 更新: packaging=info.packaging, -# 更新: is_graded=info.is_graded or False, -# 更新: grading_company=info.grading_company, -# 更新: grading_score=info.grading_score, -# 更新: category=info.category, -# 更新: user_name=current_user.f01_01_name, -# 更新: user_avatar=current_user.avatar, -# 更新: collection_name=info.collection.f01_01_name if info.collection else None, -# 更新: collection_category=info.collection.f01_03_category if info.collection else None, -# 更新: collection_version=info.collection.f02_11_version if info.collection else None, -# 更新: collection_number=info.collection.f02_10_prefix_serial if info.collection else None, -# 更新: ) -# 更新: -# 更新: -# 更新: # 删除资讯 -# 更新: @router.delete("/{info_id}") -# 更新: def delete_information( -# 更新: info_id: str, -# 更新: current_user: User = Depends(get_current_user), -# 更新: db: Session = Depends(get_db) -# 更新: ): -# 更新: """删除资讯""" -# 更新: # 允许admin删除任何人的资讯 -# 更新: if current_user.role == "admin": -# 更新: info = db.query(Information).filter(Information.id == info_id).first() -# 更新: else: -# 更新: info = db.query(Information).filter( -# 更新: Information.id == info_id, -# 更新: Information.user_id == current_user.f99_90_id -# 更新: ).first() -# 更新: -# 更新: if not info: -# 更新: raise HTTPException(status_code=404, detail="资讯不存在或无权删除") -# 更新: -# 更新: db.delete(info) -# 更新: db.commit() -# 更新: -# 更新: return {"message": "删除成功"} -# 更新: -# 更新: -# 更新: # 寻配号 - 自动匹配推荐藏品 -# 更新: @router.get("/seek/match") -# 更新: def get_seek_match( -# 更新: info_id: str, -# 更新: current_user: Optional[User] = Depends(get_current_user), -# 更新: db: Session = Depends(get_db) -# 更新: ): -# 更新: """获取符合条件的我的藏品推荐""" -# 更新: if not current_user: -# 更新: raise HTTPException(status_code=401, detail="请先登录") -# 更新: -# 更新: info = db.query(Information).filter( -# 更新: Information.id == info_id, -# 更新: Information.info_type == "seek" -# 更新: ).first() -# 更新: -# 更新: if not info: -# 更新: raise HTTPException(status_code=404, detail="寻配号信息不存在") -# 更新: -# 更新: # 更新用户配号(寻号)次数 -# 更新: current_user.f99_96_search_count = (current_user.f99_96_search_count or 0) + 1 -# 更新: db.commit() -# 更新: -# 更新: # 获取用户所有藏品 -# 更新: collections = db.query(Collection).filter( -# 更新: Collection.f99_91_user_id == current_user.f99_90_id, -# 更新: Collection.f01_04_status == "in_collection" -# 更新: ).all() -# 更新: -# 更新: # 去掉版别筛选,因为藏品分类和发布需求的版别不同 -# 更新: # if info.expect_category: -# 更新: # collections = [c for c in collections if c.f01_03_category == info.expect_category] -# 更新: -# 更新: # 按号码特征模式匹配 -# 更新: matched = [] -# 更新: if info.expect_number and len(info.expect_number) == 10: -# 更新: pattern = info.expect_number[2:] # 后8位 -# 更新: for c in collections: -# 更新: number = c.f02_10_prefix_serial or '' -# 更新: # 去掉J0前缀后取前8位 -# 更新: if len(number) >= 10 and number.startswith('J0'): -# 更新: col_pattern = number[2:10] # 取J0后面的8位 -# 更新: if match_pattern(col_pattern, pattern): -# 更新: matched.append(c) -# 更新: elif len(number) >= 8: -# 更新: col_pattern = number[:8] # 取前8位 -# 更新: if match_pattern(col_pattern, pattern): -# 更新: matched.append(c) -# 更新: else: -# 更新: matched = collections -# 更新: -# 更新: return { -# 更新: "info_id": info_id, -# 更新: "matched_count": len(matched), -# 更新: "collections": [ -# 更新: { -# 更新: "id": c.f99_90_id, -# 更新: "code": c.f01_02_code or '', -# 更新: "name": c.f01_01_name, -# 更新: "number": c.f02_10_prefix_serial, -# 更新: "status": c.f01_04_status, -# 更新: "category": c.f01_03_category, -# 更新: "version": c.f02_11_version, -# 更新: "packaging": c.f02_12_packaging, -# 更新: "cost_price": c.f05_40_cost_price, -# 更新: } -# 更新: for c in matched -# 更新: ], -# 更新: "network_matched_count": match_collections_count_from_coolbot(info.expect_number) if info.expect_number else 0, -# 更新: "network_collections": match_collections_list_from_coolbot(info.expect_number, limit=20) if info.expect_number else [] -# 更新: } -# 更新: -# 更新: -# 更新: # 获取网络数据匹配列表 -# 更新: @router.get("/seek/network-match/{info_id}") -# 更新: def get_network_match( -# 更新: info_id: str, -# 更新: limit: int = Query(20, ge=1, le=100), -# 更新: db: Session = Depends(get_db) -# 更新: ): -# 更新: """获取一尘数据库中匹配的藏品列表""" -# 更新: info = db.query(Information).filter( -# 更新: Information.id == info_id, -# 更新: Information.info_type == "seek" -# 更新: ).first() -# 更新: -# 更新: if not info: -# 更新: raise HTTPException(status_code=404, detail="寻配号信息不存在") -# 更新: -# 更新: if not info.expect_number: -# 更新: return {"matched_count": 0, "collections": []} -# 更新: -# 更新: matched = match_collections_list_from_coolbot(info.expect_number, limit=limit) -# 更新: -# 更新: return { -# 更新: "matched_count": len(matched), -# 更新: "collections": matched -# 更新: } -# 更新: -# 更新: -# 更新: # 我的寻号列表 -# 更新: @router.get("/my-seeks") -# 更新: def get_my_seeks( -# 更新: current_user: Optional[User] = Depends(get_current_user), -# 更新: db: Session = Depends(get_db) -# 更新: ): -# 更新: """获取当前用户发布的所有寻号信息""" -# 更新: if not current_user: -# 更新: raise HTTPException(status_code=401, detail="请先登录") -# 更新: -# 更新: items = db.query(Information).filter( -# 更新: Information.user_id == current_user.f99_90_id, -# 更新: Information.info_type == "seek", -# 更新: Information.status == "active" -# 更新: ).order_by(Information.created_at.desc()).all() -# 更新: -# 更新: result = [] -# 更新: for item in items: -# 更新: # 计算匹配数量 -# 更新: matched_count = 0 -# 更新: if item.expect_number: -# 更新: matched_count = match_collections_count(db, current_user.f99_90_id, item.expect_number) -# 更新: -# 更新: result.append(InformationResponse( -# 更新: id=item.id, -# 更新: user_id=item.user_id, -# 更新: info_type=item.info_type, -# 更新: title=item.title, -# 更新: content=item.content, -# 更新: collection_id=item.collection_id, -# 更新: expect_category=item.expect_category, -# 更新: expect_version=item.expect_version, -# 更新: expect_packaging=item.expect_packaging, -# 更新: expect_number=item.expect_number, -# 更新: expect_price_min=item.expect_price_min, -# 更新: expect_price_max=item.expect_price_max, -# 更新: deal_price=item.deal_price, -# 更新: deal_date=item.deal_date, -# 更新: status=item.status, -# 更新: is_matched=item.is_matched, -# 更新: matched_user_id=item.matched_user_id, -# 更新: matched_contact=item.matched_contact, -# 更新: view_count=item.view_count, -# 更新: contact_count=item.contact_count, -# 更新: created_at=item.created_at, -# 更新: user_name=item.user.f01_01_name if item.user else None, -# 更新: user_avatar=item.user.avatar if item.user else None, -# 更新: collection_name=item.collection.f01_01_name if item.collection else None, -# 更新: collection_category=item.collection.f01_03_category if item.collection else None, -# 更新: collection_version=item.collection.f02_11_version if item.collection else None, -# 更新: collection_number=item.collection.f02_10_prefix_serial if item.collection else None, -# 更新: matched_count=matched_count, -# 更新: network_matched_count=match_collections_count_from_coolbot(item.expect_number) if item.info_type == 'seek' and item.expect_number else 0, -# 更新: )) -# 更新: -# 更新: # 获取总数并设置响应头 -# 更新: from fastapi import Response -# 更新: total_query = db.query(Information).filter(Information.status == status) -# 更新: if info_type: -# 更新: total_query = total_query.filter(Information.info_type == info_type) -# 更新: total_count = total_query.count() -# 更新: total_pages = (total_count + page_size - 1) // page_size -# 更新: -# 更新: # 设置响应头 -# 更新: response.headers['X-Total-Pages'] = str(total_pages) -# 更新: response.headers['X-Total-Count'] = str(total_count) -# 更新: -# 更新: return result -# 更新: -# 更新: -# 更新: # 成交数据统计 -# 更新: @router.get("/deal/stats") -# 更新: def get_deal_stats( -# 更新: days: int = Query(7, ge=1, le=90, description="统计天数"), -# 更新: db: Session = Depends(get_db) -# 更新: ): -# 更新: """获取成交数据统计""" -# 更新: from sqlalchemy import func -# 更新: from datetime import timedelta -# 更新: -# 更新: start_date = datetime.now() - timedelta(days=days) -# 更新: -# 更新: # 按版别统计 -# 更新: by_version = db.query( -# 更新: Information.expect_version, -# 更新: func.count(Information.id).label("count"), -# 更新: func.avg(Information.deal_price).label("avg_price"), -# 更新: func.max(Information.deal_price).label("max_price"), -# 更新: func.min(Information.deal_price).label("min_price") -# 更新: ).filter( -# 更新: Information.info_type == "deal", -# 更新: Information.status == "active", -# 更新: Information.created_at >= start_date -# 更新: ).group_by(Information.expect_version).all() -# 更新: -# 更新: # 按包装统计 -# 更新: by_packaging = db.query( -# 更新: Information.expect_packaging, -# 更新: func.count(Information.id).label("count"), -# 更新: func.avg(Information.deal_price).label("avg_price") -# 更新: ).filter( -# 更新: Information.info_type == "deal", -# 更新: Information.status == "active", -# 更新: Information.created_at >= start_date -# 更新: ).group_by(Information.expect_packaging).all() -# 更新: -# 更新: # 按号码分类统计 -# 更新: by_number = db.query( -# 更新: Information.expect_number, -# 更新: func.count(Information.id).label("count"), -# 更新: func.avg(Information.deal_price).label("avg_price") -# 更新: ).filter( -# 更新: Information.info_type == "deal", -# 更新: Information.status == "active", -# 更新: Information.expect_number.isnot(None), -# 更新: Information.created_at >= start_date -# 更新: ).group_by(Information.expect_number).all() -# 更新: -# 更新: return { -# 更新: "days": days, -# 更新: "by_version": [ -# 更新: {"version": r[0] or "未知", "count": r[1], "avg_price": float(r[2] or 0), "max_price": float(r[3] or 0), "min_price": float(r[4] or 0)} -# 更新: for r in by_version if r[0] -# 更新: ], -# 更新: "by_packaging": [ -# 更新: {"packaging": r[0] or "未知", "count": r[1], "avg_price": float(r[2] or 0)} -# 更新: for r in by_packaging if r[0] -# 更新: ], -# 更新: "by_number": [ -# 更新: {"number": r[0], "count": r[1], "avg_price": float(r[2] or 0)} -# 更新: for r in by_number -# 更新: ] -# 更新: } -# 更新: -# 更新: -# 更新: # 获取我的发布列表 -# 更新: @router.get("/my/list", response_model=List[InformationResponse]) -# 更新: def get_my_information_list( -# 更新: page: int = Query(1, ge=1), -# 更新: page_size: int = Query(20, ge=1, le=500), -# 更新: current_user: User = Depends(get_current_user), -# 更新: db: Session = Depends(get_db) -# 更新: ): -# 更新: """获取我的发布列表""" -# 更新: items = db.query(Information).options( -# 更新: joinedload(Information.collection) -# 更新: ).filter( -# 更新: Information.user_id == current_user.f99_90_id -# 更新: ).order_by(Information.created_at.desc()).offset((page-1)*page_size).limit(page_size).all() -# 更新: -# 更新: result = [] -# 更新: for item in items: -# 更新: result.append(InformationResponse( -# 更新: id=item.id, -# 更新: user_id=item.user_id, -# 更新: info_type=item.info_type, -# 更新: title=item.title, -# 更新: content=item.content, -# 更新: collection_id=item.collection_id, -# 更新: expect_category=item.expect_category, -# 更新: expect_version=item.expect_version, -# 更新: expect_packaging=item.expect_packaging, -# 更新: expect_number=item.expect_number, -# 更新: expect_price_min=item.expect_price_min, -# 更新: expect_price_max=item.expect_price_max, -# 更新: deal_price=item.deal_price, -# 更新: deal_date=item.deal_date, -# 更新: status=item.status, -# 更新: is_matched=item.is_matched, -# 更新: matched_user_id=item.matched_user_id, -# 更新: matched_contact=item.matched_contact, -# 更新: view_count=item.view_count, -# 更新: contact_count=item.contact_count, -# 更新: created_at=item.created_at, -# 更新: user_name=current_user.f01_01_name, -# 更新: user_avatar=current_user.avatar, -# 更新: collection_name=item.collection.f01_01_name if item.collection else None, -# 更新: collection_category=item.collection.f01_03_category if item.collection else None, -# 更新: collection_version=item.collection.f02_11_version if item.collection else None, -# 更新: collection_number=item.collection.f02_10_prefix_serial if item.collection else None, -# 更新: )) -# 更新: -# 更新: return result -# 更新: -# 更新: # ============ 获取当前用户发布的列表 ============ -# 更新: @router.get("/my") -# 更新: def get_my_information( -# 更新: page: int = Query(1, ge=1), -# 更新: limit: int = Query(20, ge=1, le=100), -# 更新: current_user: User = Depends(get_current_user), -# 更新: db: Session = Depends(get_db) -# 更新: ): -# 更新: """获取当前用户发布的信息列表""" -# 更新: total = db.query(Information).filter(Information.author == current_user.f01_01_name).count() -# 更新: infos = db.query(Information).filter( -# 更新: Information.author == current_user.f01_01_name -# 更新: ).order_by(Information.created_at.desc()).offset((page-1)*limit).limit(limit).all() -# 更新: -# 更新: return { -# 更新: "total": total, -# 更新: "list": [{ -# 更新: "id": i.id, -# 更新: "title": i.title, -# 更新: "content": i.content, -# 更新: "info_type": i.info_type, -# 更新: "author": i.author, -# 更新: "created_at": i.created_at.isoformat() if i.created_at else None -# 更新: } for i in infos] -# 更新: } -# 更新: -# 更新: -# 更新: # ============ 匹配寻号 ============ -# 更新: class MatchSeekRequest(BaseModel): -# 更新: info_id: str -# 更新: collection_id: Optional[str] = None -# 更新: contact: Optional[str] = None -# 更新: -# 更新: -# 更新: @router.post("/seek/match-confirm") -# 更新: def match_seek( -# 更新: request: MatchSeekRequest, -# 更新: current_user: User = Depends(get_current_user), -# 更新: db: Session = Depends(get_db) -# 更新: ): -# 更新: """确认匹配寻号 - 用户愿意交换联系方式给发布者""" -# 更新: info = db.query(Information).filter( -# 更新: Information.id == request.info_id, -# 更新: Information.info_type == "seek", -# 更新: Information.status == "active" -# 更新: ).first() -# 更新: -# 更新: if not info: -# 更新: raise HTTPException(status_code=404, detail="寻配号信息不存在") -# 更新: -# 更新: # 检查是否已被匹配(一个寻号只能被一个用户匹配) -# 更新: if info.is_matched == "matched": -# 更新: raise HTTPException(status_code=400, detail="该寻号已被其他用户匹配") -# 更新: -# 更新: # 更新匹配状态 -# 更新: info.is_matched = "matched" -# 更新: info.matched_user_id = current_user.f99_90_id -# 更新: # 保存匹配者的联系方式 -# 更新: info.matched_contact = request.contact or '' -# 更新: -# 更新: # 更新发布寻号者的内容,显示有藏品被匹配 -# 更新: original_content = info.content or "" -# 更新: # 添加匹配信息:藏品被XX藏友匹配,联系方式为:xxx -# 更新: match_info = f"\n藏品被{current_user.f01_01_name}匹配成功!联系方式:{request.collection_id or '已交换'}" -# 更新: info.content = original_content + match_info -# 更新: -# 更新: db.commit() -# 更新: -# 更新: return {"message": "匹配成功,已通知发布者", "is_matched": "matched"} -# 更新: -# 更新: -# 更新: # ============ 添加留言 ============ -# 更新: class CommentRequest(BaseModel): -# 更新: information_id: str -# 更新: content: str -# 更新: -# 更新: -# 更新: @router.post("/comment") -# 更新: def add_comment( -# 更新: request: CommentRequest, -# 更新: current_user: User = Depends(get_current_user), -# 更新: db: Session = Depends(get_db) -# 更新: ): -# 更新: """添加留言""" -# 更新: info = db.query(Information).filter( -# 更新: Information.id == request.information_id -# 更新: ).first() -# 更新: -# 更新: if not info: -# 更新: raise HTTPException(status_code=404, detail="资讯不存在") -# 更新: -# 更新: # 创建留言 -# 更新: from app.models.models import InformationComment -# 更新: comment = InformationComment( -# 更新: information_id=request.information_id, -# 更新: user_id=current_user.f99_90_id, -# 更新: content=request.content -# 更新: ) -# 更新: db.add(comment) -# 更新: db.commit() -# 更新: -# 更新: return { -# 更新: "message": "留言成功", -# 更新: "comment": { -# 更新: "id": comment.id, -# 更新: "content": comment.content, -# 更新: "user_name": current_user.f01_01_name, -# 更新: "user_avatar": current_user.avatar, -# 更新: "created_at": comment.created_at -# 更新: } -# 更新: } -# 更新: -# 更新: -# 更新: # ============ 获取评论列表 ============ -# 更新: @router.get("/comments/{information_id}") -# 更新: def get_comments( -# 更新: information_id: str, -# 更新: db: Session = Depends(get_db) -# 更新: ): -# 更新: """获取资讯的评论列表""" -# 更新: from app.models.models import InformationComment -# 更新: comments = db.query(InformationComment).filter( -# 更新: InformationComment.information_id == information_id -# 更新: ).order_by(InformationComment.created_at.desc()).all() -# 更新: -# 更新: return [ -# 更新: { -# 更新: "id": c.id, -# 更新: "content": c.content, -# 更新: "user_name": c.user.f01_01_name if c.user else '匿名用户', -# 更新: "user_avatar": c.user.avatar if c.user else None, -# 更新: "created_at": c.created_at -# 更新: } -# 更新: for c in comments -# 更新: ] -# 更新: -# 更新: -# 更新: # ============ 获取匹配者信息 ============ -# 更新: @router.get("/seek/matched-user/{info_id}") -# 更新: def get_matched_user( -# 更新: info_id: str, -# 更新: current_user: User = Depends(get_current_user), -# 更新: db: Session = Depends(get_db) -# 更新: ): -# 更新: """获取寻号的匹配者信息(仅发布者可见)""" -# 更新: info = db.query(Information).filter( -# 更新: Information.id == info_id, -# 更新: Information.info_type == "seek" -# 更新: ).first() -# 更新: -# 更新: if not info: -# 更新: raise HTTPException(status_code=404, detail="寻配号信息不存在") -# 更新: -# 更新: # 只有发布者可以看到匹配者信息 -# 更新: if info.user_id != current_user.f99_90_id: -# 更新: raise HTTPException(status_code=403, detail="无权访问") -# 更新: -# 更新: if not info.matched_user_id: -# 更新: return {"message": "暂无匹配者"} -# 更新: -# 更新: # 获取匹配者信息 -# 更新: matched_user = db.query(User).filter(User.f99_90_id == info.matched_user_id).first() -# 更新: if not matched_user: -# 更新: return {"message": "匹配者不存在"} -# 更新: -# 更新: return { -# 更新: "matched_user_id": info.matched_user_id, -# 更新: "user_name": matched_user.f01_01_name, -# 更新: "phone": matched_user.phone, -# 更新: "matched_contact": info.matched_contact, -# 更新: "matched_at": info.updated_at.isoformat() if info.updated_at else None -# 更新: } -# 更新: -# 更新: -# 更新: # ============ 获取发布者信息 ============ -# 更新: @router.get("/seek/publisher/{info_id}") -# 更新: def get_publisher_info( -# 更新: info_id: str, -# 更新: current_user: User = Depends(get_current_user), -# 更新: db: Session = Depends(get_db) -# 更新: ): -# 更新: """获取寻号的发布者信息(仅匹配者可见)""" -# 更新: info = db.query(Information).filter( -# 更新: Information.id == info_id, -# 更新: Information.info_type == "seek" -# 更新: ).first() -# 更新: -# 更新: if not info: -# 更新: raise HTTPException(status_code=404, detail="寻配号信息不存在") -# 更新: -# 更新: # 只有匹配者可以看到发布者信息 -# 更新: if not info.matched_user_id or info.matched_user_id != current_user.f99_90_id: -# 更新: raise HTTPException(status_code=403, detail="无权访问") -# 更新: -# 更新: # 获取发布者信息 -# 更新: publisher = db.query(User).filter(User.f99_90_id == info.user_id).first() -# 更新: if not publisher: -# 更新: return {"message": "发布者不存在"} -# 更新: -# 更新: # 从content中解析联系方式 -# 更新: contact = '' -# 更新: if info.content: -# 更新: import re -# 更新: match = re.search(r'联系方式[:\s]*(.+?)(?:\n|$)', info.content) -# 更新: if match: -# 更新: contact = match.group(1).strip() -# 更新: -# 更新: return { -# 更新: "user_id": info.user_id, -# 更新: "user_name": publisher.f01_01_name, -# 更新: "phone": publisher.phone, -# 更新: "contact": contact, -# 更新: "created_at": info.created_at.isoformat() if info.created_at else None -# 更新: } -# 更新: -# 更新: -# 更新: @router.get("/yichen-posts") -# 更新: def get_yichen_posts(category: str = None, search: str = None, page: int = 1, page_size: int = 20): -# 更新: from app.models.models import Information -# 更新: from sqlalchemy import desc -# 更新: query = db.query(Information).filter(Information.info_type == 'yichen') -# 更新: if category: -# 更新: query = query.filter(Information.expect_category == category) -# 更新: if search: -# 更新: query = query.filter(Information.title.contains(search)) -# 更新: total = query.count() -# 更新: offset = (page - 1) * page_size -# 更新: items = query.order_by(desc(Information.created_at)).offset(offset).limit(page_size).all() -# 更新: return {"data": items, "pagination": {"page": page, "limit": page_size, "total": total}} -# 更新: -# 更新: -# 更新: @router.get("/seek/stats") -# 更新: def get_seek_stats( -# 更新: current_user: User = Depends(get_current_user), -# 更新: db: Session = Depends(get_db) -# 更新: ): -# 更新: """获取寻配号统计数据""" -# 更新: # 寻号需求数(seek类型且expect_number不为空的总数) -# 更新: seek_count = db.query(Information).filter( -# 更新: Information.info_type == 'seek', -# 更新: Information.expect_number.isnot(None), -# 更新: Information.expect_number != '' -# 更新: ).count() -# 更新: -# 更新: # 我的匹配:自有藏品匹配成功的寻号帖子数量 -# 更新: # 即 is_matched = 'confirmed' 的记录,用户ID等于当前用户 -# 更新: user_matched_count = 0 -# 更新: if current_user: -# 更新: user_matched_count = db.query(Information).filter( -# 更新: Information.info_type == 'seek', -# 更新: Information.expect_number.isnot(None), -# 更新: Information.expect_number != '', -# 更新: Information.matched_user_id == current_user.f99_90_id, -# 更新: Information.is_matched == 'confirmed' -# 更新: ).count() -# 更新: -# 更新: # 总共匹配:自有匹配成功 + 网络数据匹配成功 -# 更新: # 自有匹配成功:is_matched = 'confirmed' -# 更新: # 网络数据匹配成功:查询每个帖子的network_matched_count并求和 -# 更新: seeks = db.query(Information).filter( -# 更新: Information.info_type == 'seek', -# 更新: Information.expect_number.isnot(None), -# 更新: Information.expect_number != '' -# 更新: ).all() -# 更新: -# 更新: total_self_matched = 0 -# 更新: total_network_matched = 0 -# 更新: for seek in seeks: -# 更新: # 自身匹配成功 -# 更新: if seek.is_matched == 'confirmed': -# 更新: total_self_matched += 1 -# 更新: # 网络数据匹配成功(通过coolbot数据库查询) -# 更新: if seek.expect_number: -# 更新: network_count = match_collections_count_from_coolbot(seek.expect_number) -# 更新: total_network_matched += network_count -# 更新: -# 更新: total_matched_count = total_self_matched + total_network_matched -# 更新: -# 更新: return { -# 更新: "seekCount": seek_count, -# 更新: "userMatchedCount": user_matched_count, -# 更新: "totalMatchedCount": total_matched_count -# 更新: } -# 更新: -# 更新: # 批量解析行情数据API -# 更新: @router.post("/batch-parse") -# 更新: async def batch_parse_deals(text: str = Body(..., embed=True)): -# 更新: """使用AI智能解析批量行情文本""" -# 更新: import httpx -# 更新: import json -# 更新: import re -# 更新: -# 更新: # 使用阿里云百炼Coding Plan API -# 更新: api_key = "sk-sp-d5ce68bb203e48ca857c2aea25255b26" -# 更新: base_url = "https://coding.dashscope.aliyuncs.com/v1" -# 更新: -# 更新: # 更详细的解析提示词 -# 更新: prompt = f"""你是一个专业的龙钞行情数据提取助手。请从以下文本中提取所有龙钞行情记录。 -# 更新: -# 更新: 【解析规则】 -# 更新: 1. 每条记录格式:冠字号 价格 评级/包装 出售者 -# 更新: 2. 冠字号:J0开头的9位数字(如J0298810101) -# 更新: 3. 价格:¥xxx,xxx 格式,去掉逗号转为数字 -# 更新: 4. 评级/包装:PC69/PMG68/爱藏67+/爱藏67 标十 标百 单张 -# 更新: 5. 出售者:人名 -# 更新: 6. 号码分类:根据冠字号数字特征判断(圆圆号/倒置号/金马王/金马号/金山王/天马王/金山号/天马号/朦胧王/朦胧号/如意号/钻石号/永恒号/带7号/带4号) -# 更新: -# 更新: 【输出格式】 -# 更新: 返回JSON数组,每条记录包含: -# 更新: - serial: 冠字号(完整9位,如J0298810101) -# 更新: - price: 价格(数字) -# 更新: - grade: 评级(如PC69, PMG68, 爱藏67+, 爱藏67) -# 更新: - packaging: 包装类型(标十/标百/单张) -# 更新: - category: 号码分类 -# 更新: - seller: 出售者 -# 更新: - date: 交易日(从文本中提取日期,如2026-03-29) -# 更新: -# 更新: 只返回JSON数组,不要其他内容。 -# 更新: -# 更新: 文本: -# 更新: {text}""" -# 更新: -# 更新: try: -# 更新: async with httpx.AsyncClient(timeout=120.0) as client: -# 更新: response = await client.post( -# 更新: f"{base_url}/chat/completions", -# 更新: json={ -# 更新: "model": "qwen3.6-plus", -# 更新: "messages": [ -# 更新: {"role": "system", "content": "你是一个专业的收藏品行情数据提取助手,擅长从文本中提取结构化的交易数据。只返回JSON数组。"}, -# 更新: {"role": "user", "content": prompt} -# 更新: ], -# 更新: "temperature": 0.1 -# 更新: }, -# 更新: headers={ -# 更新: "Authorization": f"Bearer {api_key}", -# 更新: "Content-Type": "application/json" -# 更新: } -# 更新: ) -# 更新: -# 更新: if response.status_code != 200: -# 更新: return {"success": False, "error": f"API错误: {response.status_code}, {response.text[:200]}"} -# 更新: -# 更新: result = response.json() -# 更新: # 阿里云百炼OpenAI兼容格式 -# 更新: choices = result.get("choices", []) -# 更新: content = "" -# 更新: if choices and len(choices) > 0: -# 更新: content = choices[0].get("message", {}).get("content", "") -# 更新: -# 更新: # 解析JSON -# 更新: try: -# 更新: # 尝试提取JSON -# 更新: if "```json" in content: -# 更新: content = content.split("```json")[1].split("```")[0] -# 更新: elif "```" in content: -# 更新: content = content.split("```")[1].split("```")[0] -# 更新: -# 更新: # 尝试直接解析 -# 更新: data = json.loads(content.strip()) -# 更新: return {"success": True, "data": data} -# 更新: except json.JSONDecodeError: -# 更新: # 尝试用正则提取 -# 更新: match = re.search(r'\[.*\]', content, re.DOTALL) -# 更新: if match: -# 更新: try: -# 更新: data = json.loads(match.group()) -# 更新: return {"success": True, "data": data} -# 更新: except: -# 更新: pass -# 更新: return {"success": False, "error": "解析失败", "raw": content[:500]} -# 更新: -# 更新: except Exception as e: -# 更新: return {"success": False, "error": str(e)} -# 更新: -# 更新: # 本地正则解析函数 -# 更新: def parse_deals_locally(text: str, default_packaging: str = '', default_date: str = '', default_platform: str = ''): -# 更新: """本地正则解析批量行情文本""" -# 更新: import re -# 更新: from datetime import datetime -# 更新: results = [] -# 更新: -# 更新: # 尝试从文本中提取日期(可能出现在标题或时间戳中) -# 更新: # 格式如: 3月29日, 2026年3月29日, 2026-03-29 -# 更新: date_patterns = [ -# 更新: r'(\d{1,2})月(\d{1,2})日', -# 更新: r'(\d{4})年(\d{1,2})月(\d{1,2})日', -# 更新: r'(\d{4})-(\d{1,2})-(\d{1,2})' -# 更新: ] -# 更新: -# 更新: extracted_date = None -# 更新: for pattern in date_patterns: -# 更新: match = re.search(pattern, text) -# 更新: if match: -# 更新: try: -# 更新: if len(match.groups()) == 2: -# 更新: # 3月29日 - 使用当前年份 -# 更新: extracted_date = f"2026-{int(match.group(1)):02d}-{int(match.group(2)):02d}" -# 更新: elif len(match.groups()) == 3: -# 更新: if int(match.group(1)) > 2000: -# 更新: # 2026年3月29日 -# 更新: extracted_date = f"{match.group(1)}-{int(match.group(2)):02d}-{int(match.group(3)):02d}" -# 更新: else: -# 更新: # 3月29日格式 -# 更新: extracted_date = f"2026-{int(match.group(1)):02d}-{int(match.group(2)):02d}" -# 更新: break -# 更新: except: -# 更新: pass -# 更新: -# 更新: # 默认使用今天 -# 更新: default_date = datetime.now().strftime('%Y-%m-%d') -# 更新: deal_date = extracted_date or default_date -# 更新: -# 更新: lines = text.strip().split('\n') -# 更新: -# 更新: for line in lines: -# 更新: line = line.strip() -# 更新: if not line or 'J0' not in line: -# 更新: continue -# 更新: -# 更新: # 提取冠字号 J0 + 8-9位数字 -# 更新: serial_match = re.search(r'J0(\d{8,9})', line) -# 更新: if not serial_match: -# 更新: continue -# 更新: -# 更新: serial_num = serial_match.group(1) -# 更新: if len(serial_num) == 9: -# 更新: serial_num = serial_num[:8] -# 更新: serial = 'J0' + serial_num -# 更新: -# 更新: # 提取价格 ¥xxx,xxx 或 xxx,xxx(必须在J0之后) -# 更新: serial_pos = line.find(serial) -# 更新: after_serial = line[serial_pos + len(serial):] -# 更新: price_match = re.search(r'[¥¥]?\s*([1-9][\d,]{2,})', after_serial) -# 更新: if not price_match: -# 更新: continue -# 更新: price = int(price_match.group(1).replace(',', '')) -# 更新: -# 更新: # 提取卖家(价格后面的中文字符) -# 更新: after_price_pos = after_serial.find(price_match.group(0)) + len(price_match.group(0)) -# 更新: after_price = after_serial[after_price_pos:] -# 更新: seller_match = re.search(r'([^\d\s¥¥]+?)(?:\s*$|\s*\n)', after_price) -# 更新: seller = seller_match.group(1).strip() if seller_match else '' -# 更新: -# 更新: # 分类判断 -# 更新: digits = serial_num -# 更新: d = digits -# 更新: category = '通货' -# 更新: if not any(c in d for c in ['1','2','3','4','5','7']): category = '圆圆号' -# 更新: elif not any(c in d for c in ['2','3','4','5','7']): category = '倒置号' -# 更新: elif not any(c in d for c in ['1','2','3','4','7']): category = '金马王' -# 更新: elif not any(c in d for c in ['2','3','4','7']): category = '金马号' -# 更新: elif not any(c in d for c in ['1','2','4','5','7']): category = '金山王' -# 更新: elif not any(c in d for c in ['1','2','4','7']): category = '天马王' -# 更新: elif not any(c in d for c in ['2','4','5','7']): category = '金山号' -# 更新: elif not any(c in d for c in ['2','4','7']): category = '天马号' -# 更新: elif not any(c in d for c in ['1','3','4','5','7']): category = '朦胧王' -# 更新: elif not any(c in d for c in ['3','4','5','7']): category = '朦胧号' -# 更新: elif not any(c in d for c in ['1','3','4','7']): category = '如意号' -# 更新: elif not any(c in d for c in ['3','4','7']): category = '钻石号' -# 更新: elif not any(c in d for c in ['4','7']): category = '永恒号' -# 更新: elif '4' not in d: category = '带7号' -# 更新: -# 更新: # 提取评级机构 PCGS/PMG/ACG/爱藏 -# 更新: grade = '' -# 更新: grading_company = '' -# 更新: packaging = '单张' -# 更新: -# 更新: if 'PC69' in line or 'PC68' in line or 'PC67' in line: -# 更新: grade_match = re.search(r'PC(6[789]|5\d?)', line) -# 更新: grade = 'PC' + grade_match.group(1) if grade_match else '' -# 更新: grading_company = 'PCGS' -# 更新: elif 'PMG68' in line or 'PMG67' in line: -# 更新: grade_match = re.search(r'PMG(6[789]|5\d?)', line) -# 更新: grade = 'PMG' + grade_match.group(1) if grade_match else '' -# 更新: grading_company = 'PMG' -# 更新: elif 'ACG' in line: -# 更新: grade_match = re.search(r'ACG(6[789]|5\d?)', line) -# 更新: grade = 'ACG' + grade_match.group(1) if grade_match else '' -# 更新: grading_company = 'ACG' -# 更新: elif '爱藏67+' in line: -# 更新: grade = '67+' -# 更新: grading_company = '爱藏' -# 更新: elif '爱藏67' in line: -# 更新: grade = '67' -# 更新: grading_company = '爱藏' -# 更新: -# 更新: # 判断包装类型 -# 更新: packaging = '单张' -# 更新: -# 更新: # 如果传入了默认包装类型,先使用默认 -# 更新: if default_packaging: -# 更新: packaging = default_packaging -# 更新: -# 更新: # 简化识别:带"刀"字=标百,带"标"字=标十 -# 更新: if '刀' in line: -# 更新: packaging = '标百' -# 更新: elif '标' in line: -# 更新: packaging = '标十' -# 更新: -# 更新: # 尾号判断:如果冠字号尾号是01/11/21/31/41/51/61/71/81/91,且有刀/标字样,基本确认是标百 -# 更新: if len(serial_num) >= 2: -# 更新: tail = serial_num[-2:] -# 更新: if tail in ['01', '11', '21', '31', '41', '51', '61', '71', '81', '91']: -# 更新: if '刀' in line or ('标' in line and packaging == '单张'): -# 更新: packaging = '标百' -# 更新: packaging = '标百' -# 更新: -# 更新: # 如果没有刀/标字样,但尾号是01且没有其他特征,可能是标百 -# 更新: if packaging == '单张' and len(serial_num) >= 2: -# 更新: tail = serial_num[-2:] -# 更新: if tail == '01': -# 更新: # 检查是否在特定语境下 -# 更新: packaging = '标百' -# 更新: -# 更新: # 计算尾号和大小号 -# 更新: tail_number = '' -# 更新: size_type = '' -# 更新: if packaging == '标十' and len(serial_num) >= 2: -# 更新: tail_number = serial_num[-2:] -# 更新: size_type = tail_number in ['01','11','21','31','41','51'] and '小号' or '大号' -# 更新: elif packaging == '标百' and len(serial_num) >= 3: -# 更新: tail_number = serial_num[-3:] -# 更新: size_type = tail_number in ['101','201','301','401','501'] and '小号' or '大号' -# 更新: -# 更新: results.append({ -# 更新: 'serial': serial, -# 更新: 'price': price, -# 更新: 'category': category, -# 更新: 'seller': seller, -# 更新: 'packaging': packaging, -# 更新: 'grade': grade, -# 更新: 'grading_company': grading_company, -# 更新: 'deal_date': deal_date or default_date, # 成交时间 -# 更新: 'entry_date': default_date, # 录入时间 -# 更新: 'is_graded': bool(grade), -# 更新: 'tail_number': tail_number, # 尾号 -# 更新: 'size_type': size_type, # 大小号 -# 更新: 'platform': default_platform # 平台 -# 更新: }) -# 更新: -# 更新: return results -# 更新: -# 更新: @router.post("/batch-parse-local") -# 更新: async def batch_parse_deals_local(request: dict = Body(...)): -# 更新: """本地正则解析批量行情文本(无需AI)""" -# 更新: text = request.get('text', '') -# 更新: default_packaging = request.get('defaultPackaging', '') -# 更新: default_date = request.get('defaultDate', '') -# 更新: default_platform = request.get('defaultPlatform', '') -# 更新: results = parse_deals_locally(text, default_packaging, default_date, default_platform) -# 更新: return {"success": True, "data": results} -# 更新: diff --git a/backend/app/routers/seek.py b/backend/app/routers/seek.py index faf0833..255c7ae 100644 --- a/backend/app/routers/seek.py +++ b/backend/app/routers/seek.py @@ -1,384 +1,301 @@ -# seek - 寻号匹配路由 -# Version: 0.0.1 -# 更新: - from fastapi import APIRouter, Depends, Query, HTTPException -# 更新: -# Version: 1.2.x -# 更新: from sqlalchemy.orm import Session -# 更新: from pydantic import BaseModel -# 更新: from typing import Optional -# 更新: from datetime import datetime -# 更新: from app.core.database import get_db -# 更新: from app.core.auth import get_current_user -# 更新: from app.models.seek_info import SeekInfo -# 更新: +from app.models.models import User, Collection, Information +from sqlalchemy import text -# 更新: router = APIRouter(prefix="/api/seek", tags=["寻配号"]) -# 更新: -# 更新: # ============ Schema ============ -# 更新: class SeekInfoCreate(BaseModel): -# 更新: title: str -# 更新: content: Optional[str] = None -# 更新: expect_category: Optional[str] = None -# 更新: expect_version: Optional[str] = None -# 更新: expect_packaging: Optional[str] = None -# 更新: expect_number: Optional[str] = None -# 更新: expect_price_min: Optional[float] = None -# 更新: expect_price_max: Optional[float] = None -# 更新: -# 更新: class SeekInfoUpdate(BaseModel): -# 更新: title: Optional[str] = None -# 更新: content: Optional[str] = None -# 更新: expect_category: Optional[str] = None -# 更新: expect_version: Optional[str] = None -# 更新: expect_packaging: Optional[str] = None -# 更新: expect_number: Optional[str] = None -# 更新: expect_price_min: Optional[float] = None -# 更新: expect_price_max: Optional[float] = None -# 更新: status: Optional[str] = None -# 更新: -# 更新: class SeekInfoResponse(BaseModel): -# 更新: id: str -# 更新: user_id: str -# 更新: title: str -# 更新: content: Optional[str] -# 更新: expect_category: Optional[str] -# 更新: expect_version: Optional[str] -# 更新: expect_packaging: Optional[str] -# 更新: expect_number: Optional[str] -# 更新: expect_price_min: Optional[float] -# 更新: expect_price_max: Optional[float] -# 更新: status: str -# 更新: is_matched: Optional[str] -# 更新: matched_user_id: Optional[str] -# 更新: matched_contact: Optional[str] -# 更新: view_count: int -# 更新: contact_count: int -# 更新: created_at: Optional[datetime] -# 更新: updated_at: Optional[datetime] -# 更新: + user_name: Optional[str] = None # 发布者用户名 + matched_count: Optional[int] = 0 # 自有匹配数量 + network_matched_count: Optional[int] = 0 # 网络匹配数量 -# 更新: class Config: -# 更新: from_attributes = True -# 更新: -# 更新: # ============ API ============ -# 更新: @router.get("/list", response_model=list[SeekInfoResponse]) -# 更新: def get_seek_list( -# 更新: status: str = Query("active"), -# 更新: 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(SeekInfo).filter(SeekInfo.status == status) -# 更新: -# 更新: # 我的寻配号:只查看自己的 -# 更新: if user_only and current_user: -# 更新: query = query.filter(SeekInfo.user_id == current_user.f99_90_id) -# 更新: -# 更新: # 排序 -# 更新: query = query.order_by(SeekInfo.created_at.desc()) -# 更新: -# 更新: # 分页 -# 更新: offset = (page - 1) * page_size -# 更新: items = query.offset(offset).limit(page_size).all() -# 更新: -# 更新: - return items -# 更新: + # 添加用户名和匹配数量 + from app.routers.information import match_collections_count, match_collections_count_from_coolbot + result = [] + for item in items: + user = db.query(User).filter(User.f99_90_id == item.user_id).first() + user_name = user.f01_01_name if user else '匿名用户' + + # 计算匹配数量 + matched_count = 0 + network_matched_count = 0 + if item.expect_number and len(item.expect_number) == 10: + if current_user and current_user.f99_90_id: + matched_count = match_collections_count(db, current_user.f99_90_id, item.expect_number) + network_matched_count = match_collections_count_from_coolbot(item.expect_number) + + # 构建响应 + result.append(SeekInfoResponse( + id=item.id, + user_id=item.user_id, + title=item.title, + content=item.content, + expect_category=item.expect_category, + expect_version=item.expect_version, + expect_packaging=item.expect_packaging, + expect_number=item.expect_number, + expect_price_min=item.expect_price_min, + expect_price_max=item.expect_price_max, + status=item.status, + is_matched=item.is_matched, + matched_user_id=item.matched_user_id, + matched_contact=item.matched_contact, + view_count=item.view_count, + contact_count=item.contact_count, + created_at=item.created_at, + updated_at=item.updated_at, + user_name=user_name, + matched_count=matched_count, + network_matched_count=network_matched_count + )) + + return result -# 更新: @router.get("/stats") -# 更新: def get_seek_stats( -# 更新: current_user = Depends(get_current_user), -# 更新: db: Session = Depends(get_db) -# 更新: ): -# 更新: """获取寻配号统计""" -# 更新: total = db.query(SeekInfo).filter(SeekInfo.status == "active").count() -# 更新: matched = db.query(SeekInfo).filter(SeekInfo.is_matched == "true").count() -# 更新: -# 更新: return { -# 更新: "total": total, -# 更新: "matched": matched, -# 更新: "unmatched": total - matched -# 更新: } -# 更新: -# 更新: @router.post("", response_model=SeekInfoResponse) -# 更新: def create_seek( -# 更新: data: SeekInfoCreate, -# 更新: current_user = Depends(get_current_user), -# 更新: db: Session = Depends(get_db) -# 更新: ): -# 更新: """创建寻配号""" -# 更新: if not current_user: -# 更新: raise HTTPException(status_code=401, detail="请先登录") -# 更新: -# 更新: seek = SeekInfo( -# 更新: user_id=current_user.f99_90_id, -# 更新: title=data.title, -# 更新: content=data.content, -# 更新: expect_category=data.expect_category, -# 更新: expect_version=data.expect_version, -# 更新: expect_packaging=data.expect_packaging, -# 更新: expect_number=data.expect_number, -# 更新: expect_price_min=data.expect_price_min, -# 更新: expect_price_max=data.expect_price_max, -# 更新: status="active" -# 更新: ) -# 更新: db.add(seek) -# 更新: db.commit() -# 更新: db.refresh(seek) -# 更新: return seek -# 更新: -# 更新: @router.get("/{seek_id}", response_model=SeekInfoResponse) -# 更新: def get_seek( -# 更新: seek_id: str, -# 更新: current_user = Depends(get_current_user), -# 更新: db: Session = Depends(get_db) -# 更新: ): -# 更新: """获取寻配号详情""" -# 更新: seek = db.query(SeekInfo).filter(SeekInfo.id == seek_id).first() -# 更新: if not seek: -# 更新: raise HTTPException(status_code=404, detail="寻配号不存在") -# 更新: -# 更新: # 增加浏览数 -# 更新: seek.view_count += 1 -# 更新: db.commit() -# 更新: -# 更新: return seek -# 更新: -# 更新: @router.put("/{seek_id}", response_model=SeekInfoResponse) -# 更新: def update_seek( -# 更新: seek_id: str, -# 更新: data: SeekInfoUpdate, -# 更新: current_user = Depends(get_current_user), -# 更新: db: Session = Depends(get_db) -# 更新: ): -# 更新: """更新寻配号""" -# 更新: if not current_user: -# 更新: raise HTTPException(status_code=401, detail="请先登录") -# 更新: -# 更新: seek = db.query(SeekInfo).filter( -# 更新: SeekInfo.id == seek_id, -# 更新: SeekInfo.user_id == current_user.f99_90_id -# 更新: ).first() -# 更新: -# 更新: if not seek: -# 更新: raise HTTPException(status_code=404, detail="寻配号不存在") -# 更新: -# 更新: for key, value in data.model_dump(exclude_unset=True).items(): -# 更新: setattr(seek, key, value) -# 更新: -# 更新: db.commit() -# 更新: db.refresh(seek) -# 更新: return seek -# 更新: -# 更新: @router.delete("/{seek_id}") -# 更新: def delete_seek( -# 更新: seek_id: str, -# 更新: current_user = Depends(get_current_user), -# 更新: db: Session = Depends(get_db) -# 更新: ): -# 更新: """删除寻配号""" -# 更新: if not current_user: -# 更新: raise HTTPException(status_code=401, detail="请先登录") -# 更新: -# 更新: seek = db.query(SeekInfo).filter( -# 更新: SeekInfo.id == seek_id, -# 更新: SeekInfo.user_id == current_user.f99_90_id -# 更新: ).first() -# 更新: -# 更新: if not seek: -# 更新: raise HTTPException(status_code=404, detail="寻配号不存在") -# 更新: -# 更新: seek.status = "deleted" -# 更新: db.commit() -# 更新: -# 更新: return {"message": "删除成功"} -# 更新: +# 获取自有藏品匹配列表 +@router.get("/my-match") +def get_seek_match( + info_id: str, + current_user = Depends(get_current_user), + db: Session = Depends(get_db) +): + """获取符合条件的我的藏品推荐""" + if not current_user: + raise HTTPException(status_code=401, detail="请先登录") + + info = db.query(SeekInfo).filter(SeekInfo.id == info_id).first() + if not info: + raise HTTPException(status_code=404, detail="寻配号信息不存在") + + # 获取用户所有藏品 + collections = db.query(Collection).filter( + Collection.f99_91_user_id == current_user.f99_90_id, + Collection.f01_04_status == "in_collection" + ).all() + + # 按号码特征模式匹配 + from app.routers.information import match_pattern + matched = [] + if info.expect_number and len(info.expect_number) == 10: + pattern = info.expect_number[2:] + for c in collections: + number = c.f02_10_prefix_serial or '' + if len(number) >= 10 and number.startswith('J0'): + col_pattern = number[2:10] + if match_pattern(col_pattern, pattern): + matched.append(c) + elif len(number) >= 8: + col_pattern = number[:8] + if match_pattern(col_pattern, pattern): + matched.append(c) + else: + matched = collections + + return { + "matched_count": len(matched), + "collections": [ + {"id": c.f99_90_id, "code": c.f01_02_code or '', "name": c.f01_01_name, + "number": c.f02_10_prefix_serial, "status": c.f01_04_status} + for c in matched[:20] + ] + } + +# 获取网络数据匹配列表 +@router.get("/network-match/{info_id}") +def get_network_match( + info_id: str, + limit: int = Query(20, ge=1, le=100), + db: Session = Depends(get_db) +): + """获取一尘数据库中匹配的藏品列表""" + from app.routers.information import match_collections_list_from_coolbot + + info = db.query(SeekInfo).filter(SeekInfo.id == info_id).first() + if not info: + raise HTTPException(status_code=404, detail="寻配号信息不存在") + + if not info.expect_number: + return {"matched_count": 0, "collections": []} + + matched = match_collections_list_from_coolbot(info.expect_number, limit=limit) + return {"matched_count": len(matched), "collections": matched} diff --git a/config/VERSION b/config/VERSION index 8e3c90d..4560429 100644 --- a/config/VERSION +++ b/config/VERSION @@ -1 +1 @@ -VERSION=v0.0.3 +VERSION=v0.0.5 diff --git a/frontend/index.html b/frontend/index.html index 1a4446f..0bc01c7 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,7 +4,7 @@ -