Compare commits

..

11 Commits

36 changed files with 5077 additions and 502 deletions

3
.gitignore vendored
View File

@ -32,6 +32,3 @@ Thumbs.db
.vscode/ .vscode/
*.swp *.swp
*.swo *.swo
# 版本文件
VERSION

View File

@ -1 +0,0 @@
1.2.93

View File

@ -1 +1 @@
VERSION=1.2.79 1.2.98

View File

@ -1,242 +1,489 @@
# 认证路由 - 使用字段编码 # auth - 认证路由
# Version: 1.2.90
# 更新:
from fastapi import APIRouter
# Version: 1.2.x
# 更新:
from fastapi import APIRouter, Depends, HTTPException, status, Body from fastapi import APIRouter, Depends, HTTPException, status, Body
# 更新:
from fastapi.security import OAuth2PasswordRequestForm from fastapi.security import OAuth2PasswordRequestForm
# 更新:
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 verify_password, create_access_token, get_password_hash, get_current_user from app.core.auth import verify_password, create_access_token, get_password_hash, get_current_user
# 更新:
from app.models.models import User from app.models.models import User
# 更新:
from app.schemas.schemas import Token, UserCreate, UserResponse from app.schemas.schemas import Token, UserCreate, UserResponse
# 更新:
# 更新:
router = APIRouter(prefix="/api/auth", tags=["认证"]) router = APIRouter(prefix="/api/auth", tags=["认证"])
# 更新:
# 更新:
# 更新:
# 更新:
# 更新:
def generate_user_code(db): def generate_user_code(db):
# 更新:
"""生成用户编码从201开始按自然数顺序递增跳过已存在的""" """生成用户编码从201开始按自然数顺序递增跳过已存在的"""
# 更新:
# 查找最大的user_code # 查找最大的user_code
# 更新:
max_code = db.query(User.user_code).filter(User.user_code != None).order_by(User.user_code.desc()).first() max_code = db.query(User.user_code).filter(User.user_code != None).order_by(User.user_code.desc()).first()
# 更新:
if max_code and max_code[0]: if max_code and max_code[0]:
# 更新:
try: try:
# 更新:
num = int(max_code[0]) + 1 num = int(max_code[0]) + 1
# 更新:
if num < 201: if num < 201:
# 更新:
num = 201 num = 201
# 更新:
# 检查是否已存在,如果存在则继续递增 # 检查是否已存在,如果存在则继续递增
# 更新:
while db.query(User).filter(User.user_code == str(num)).first(): while db.query(User).filter(User.user_code == str(num)).first():
# 更新:
num += 1 num += 1
# 更新:
return str(num) return str(num)
# 更新:
except: except:
# 更新:
pass pass
# 更新:
return "201" return "201"
# 更新:
# 更新:
@router.post("/register", response_model=UserResponse) @router.post("/register", response_model=UserResponse)
# 更新:
def register(user_data: UserCreate, db: Session = Depends(get_db)): def register(user_data: UserCreate, db: Session = Depends(get_db)):
# 更新:
"""用户注册""" """用户注册"""
# 更新:
# 检查用户名是否已存在 # 检查用户名是否已存在
# 更新:
existing_user = db.query(User).filter(User.f01_01_name == user_data.f01_01_name).first() existing_user = db.query(User).filter(User.f01_01_name == user_data.f01_01_name).first()
# 更新:
if existing_user: if existing_user:
# 更新:
raise HTTPException( raise HTTPException(
# 更新:
status_code=status.HTTP_400_BAD_REQUEST, status_code=status.HTTP_400_BAD_REQUEST,
# 更新:
detail="f01_01_name: 用户名已存在" detail="f01_01_name: 用户名已存在"
# 更新:
) )
# 更新:
# 更新:
# 检查邮箱是否已存在 # 检查邮箱是否已存在
# 更新:
# 更新:
# 检查手机号是否已存在 # 检查手机号是否已存在
# 更新:
if user_data.phone: if user_data.phone:
# 更新:
existing_phone = db.query(User).filter(User.phone == user_data.phone).first() existing_phone = db.query(User).filter(User.phone == user_data.phone).first()
# 更新:
if existing_phone: if existing_phone:
# 更新:
raise HTTPException( raise HTTPException(
# 更新:
status_code=status.HTTP_400_BAD_REQUEST, status_code=status.HTTP_400_BAD_REQUEST,
# 更新:
detail="E00040:该手机号已被注册,请更换手机号" detail="E00040:该手机号已被注册,请更换手机号"
# 更新:
) )
# 更新:
if user_data.email: if user_data.email:
# 更新:
existing_email = db.query(User).filter(User.email == user_data.email).first() existing_email = db.query(User).filter(User.email == user_data.email).first()
# 更新:
if existing_email: if existing_email:
# 更新:
raise HTTPException( raise HTTPException(
# 更新:
status_code=status.HTTP_400_BAD_REQUEST, status_code=status.HTTP_400_BAD_REQUEST,
# 更新:
detail="E00041:该邮箱已被注册,请更换邮箱" detail="E00041:该邮箱已被注册,请更换邮箱"
# 更新:
) )
# 更新:
# 更新:
# 处理邀请码 # 处理邀请码
# 更新:
invited_by_user = None invited_by_user = None
# 更新:
if user_data.invite_code: if user_data.invite_code:
# 更新:
# 查找邀请人 # 查找邀请人
# 更新:
invited_by_user = db.query(User).filter(User.user_code == user_data.invite_code).first() invited_by_user = db.query(User).filter(User.user_code == user_data.invite_code).first()
# 更新:
if not invited_by_user: if not invited_by_user:
# 更新:
raise HTTPException( raise HTTPException(
# 更新:
status_code=status.HTTP_400_BAD_REQUEST, status_code=status.HTTP_400_BAD_REQUEST,
# 更新:
detail="E00042:邀请码无效" detail="E00042:邀请码无效"
# 更新:
) )
# 更新:
# 更新:
# 创建用户 # 创建用户
# 更新:
import uuid import uuid
# 更新:
hashed_password = get_password_hash(user_data.password) hashed_password = get_password_hash(user_data.password)
# 更新:
generated_code = generate_user_code(db) generated_code = generate_user_code(db)
# 更新:
user = User( user = User(
# 更新:
f99_90_id=str(uuid.uuid4()), f99_90_id=str(uuid.uuid4()),
# 更新:
f99_91_user_id=str(uuid.uuid4()), f99_91_user_id=str(uuid.uuid4()),
# 更新:
user_code=generated_code, user_code=generated_code,
# 更新:
f01_01_name=user_data.f01_01_name, f01_01_name=user_data.f01_01_name,
# 更新:
email=user_data.email, email=user_data.email,
# 更新:
phone=user_data.phone, phone=user_data.phone,
# 更新:
avatar=user_data.avatar, avatar=user_data.avatar,
# 更新:
address=user_data.address, address=user_data.address,
# 更新:
bio=user_data.bio, bio=user_data.bio,
# 更新:
password=hashed_password, password=hashed_password,
# 更新:
role="user" role="user"
# 更新:
) )
# 更新:
# 更新:
db.add(user) db.add(user)
# 更新:
db.flush() # 确保获取user ID db.flush() # 确保获取user ID
# 更新:
# 更新:
# 更新邀请人、被邀请人的关联关系 # 更新邀请人、被邀请人的关联关系
# 更新:
if invited_by_user: if invited_by_user:
# 更新:
# 记录是被谁邀请的 # 记录是被谁邀请的
# 更新:
user.f01_13_invite_code = invited_by_user.user_code user.f01_13_invite_code = invited_by_user.user_code
# 更新:
# 增加邀请人的邀请计数 # 增加邀请人的邀请计数
# 更新:
invited_by_user.f99_101_invited_count = (invited_by_user.f99_101_invited_count or 0) + 1 invited_by_user.f99_101_invited_count = (invited_by_user.f99_101_invited_count or 0) + 1
# 更新:
# 更新:
# 生成自己的邀请码用自己的user_code # 生成自己的邀请码用自己的user_code
# 更新:
user.f01_13_invite_code = generated_code user.f01_13_invite_code = generated_code
# 更新:
# 更新:
db.commit() db.commit()
# 更新:
db.refresh(user) db.refresh(user)
# 更新:
# 更新:
# 返回用户信息避免Pydantic序列化问题 # 返回用户信息避免Pydantic序列化问题
# 更新:
return { return {
# 更新:
"id": user.f99_90_id, "id": user.f99_90_id,
# 更新:
"username": user.f01_01_name, "username": user.f01_01_name,
# 更新:
"user_code": user.user_code, "user_code": user.user_code,
# 更新:
"email": user.email, "email": user.email,
# 更新:
"phone": user.phone, "phone": user.phone,
# 更新:
"avatar": user.avatar, "avatar": user.avatar,
# 更新:
"role": user.role, "role": user.role,
# 更新:
"level": user.f99_94_level, "level": user.f99_94_level,
# 更新:
"aiCount": user.f99_95_ai_count or 0, "aiCount": user.f99_95_ai_count or 0,
# 更新:
"searchCount": user.f99_96_search_count or 0, "searchCount": user.f99_96_search_count or 0,
# 更新:
"collectionCount": user.f99_97_collection_count or 0 "collectionCount": user.f99_97_collection_count or 0
# 更新:
} }
# 更新:
# 更新:
# 更新:
@router.post("/login", response_model=Token) @router.post("/login", response_model=Token)
# 更新:
def login( def login(
# 更新:
form_data: OAuth2PasswordRequestForm = Depends(), form_data: OAuth2PasswordRequestForm = Depends(),
# 更新:
db: Session = Depends(get_db) db: Session = Depends(get_db)
# 更新:
): ):
# 更新:
"""用户登录 - 支持用户名或用户编码登录""" """用户登录 - 支持用户名或用户编码登录"""
# 更新:
# 先尝试用户名登录 # 先尝试用户名登录
# 更新:
user = db.query(User).filter(User.f01_01_name == form_data.username).first() user = db.query(User).filter(User.f01_01_name == form_data.username).first()
# 更新:
# 如果用户名不存在,尝试用户编码登录 # 如果用户名不存在,尝试用户编码登录
# 更新:
if not user: if not user:
# 更新:
user = db.query(User).filter(User.user_code == form_data.username).first() user = db.query(User).filter(User.user_code == form_data.username).first()
# 更新:
if not user: if not user:
# 更新:
raise HTTPException( raise HTTPException(
# 更新:
status_code=status.HTTP_401_UNAUTHORIZED, status_code=status.HTTP_401_UNAUTHORIZED,
# 更新:
detail="E00011: 用户名或密码错误", detail="E00011: 用户名或密码错误",
# 更新:
headers={"WWW-Authenticate": "Bearer"}, headers={"WWW-Authenticate": "Bearer"},
# 更新:
) )
# 更新:
# 更新:
# 验证密码 # 验证密码
# 更新:
if not verify_password(form_data.password, user.password): if not verify_password(form_data.password, user.password):
# 更新:
raise HTTPException( raise HTTPException(
# 更新:
status_code=status.HTTP_401_UNAUTHORIZED, status_code=status.HTTP_401_UNAUTHORIZED,
# 更新:
detail="E00011: 用户名或密码错误", detail="E00011: 用户名或密码错误",
# 更新:
headers={"WWW-Authenticate": "Bearer"}, headers={"WWW-Authenticate": "Bearer"},
# 更新:
) )
# 更新:
# 更新:
# 更新登录次数和最后登录时间 # 更新登录次数和最后登录时间
# 更新:
from datetime import datetime from datetime import datetime
# 更新:
user.f99_98_login_count = (user.f99_98_login_count or 0) + 1 user.f99_98_login_count = (user.f99_98_login_count or 0) + 1
# 更新:
user.f99_99_last_login = datetime.now() user.f99_99_last_login = datetime.now()
# 更新:
db.commit() db.commit()
# 更新:
# 更新:
# 生成 token # 生成 token
# 更新:
access_token = create_access_token(data={"sub": user.f99_90_id}) access_token = create_access_token(data={"sub": user.f99_90_id})
# 更新:
# 更新:
return { return {
# 更新:
"access_token": access_token, "access_token": access_token,
# 更新:
"token_type": "bearer" "token_type": "bearer"
# 更新:
} }
# 更新:
# 更新:
# 更新:
@router.get("/me", response_model=UserResponse) @router.get("/me", response_model=UserResponse)
# 更新:
def get_current_user_info( def get_current_user_info(
# 更新:
current_user: User = Depends(lambda: None) current_user: User = Depends(lambda: None)
# 更新:
): ):
# 更新:
"""获取当前用户信息""" """获取当前用户信息"""
# 更新:
raise HTTPException( raise HTTPException(
# 更新:
status_code=status.HTTP_501_NOT_IMPLEMENTED, status_code=status.HTTP_501_NOT_IMPLEMENTED,
# 更新:
detail="请使用正确的依赖注入" detail="请使用正确的依赖注入"
# 更新:
) )
# 更新:
# 更新:
# 更新:
@router.post("/change-password") @router.post("/change-password")
# 更新:
def change_password( def change_password(
# 更新:
old_password: str = Body(...), old_password: str = Body(...),
# 更新:
new_password: str = Body(...), new_password: str = Body(...),
# 更新:
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 app.core.auth import verify_password, get_password_hash from app.core.auth import verify_password, get_password_hash
# 更新:
# 更新:
# 在当前session中重新查询用户 # 在当前session中重新查询用户
# 更新:
user = db.query(User).filter(User.f99_90_id == current_user.f99_90_id).first() user = db.query(User).filter(User.f99_90_id == current_user.f99_90_id).first()
# 更新:
if not user: if not user:
# 更新:
raise HTTPException(status_code=404, detail="用户不存在") raise HTTPException(status_code=404, detail="用户不存在")
# 更新:
# 更新:
# 验证旧密码 # 验证旧密码
# 更新:
if not verify_password(old_password, user.password): if not verify_password(old_password, user.password):
# 更新:
raise HTTPException( raise HTTPException(
# 更新:
status_code=status.HTTP_400_BAD_REQUEST, status_code=status.HTTP_400_BAD_REQUEST,
# 更新:
detail="当前密码错误" detail="当前密码错误"
# 更新:
) )
# 更新:
# 更新:
# 更新密码 # 更新密码
# 更新:
user.password = get_password_hash(new_password) user.password = get_password_hash(new_password)
# 更新:
db.commit() db.commit()
# 更新:
# 更新:
return {"message": "密码修改成功"} return {"message": "密码修改成功"}
# 更新:
# 更新:
# 更新:
# ============ 短信验证码接口 ============ # ============ 短信验证码接口 ============
# 更新:
# 更新:
@router.post("/send-verification-code") @router.post("/send-verification-code")
# 更新:
def send_verification_code( def send_verification_code(
# 更新:
phone: str = Body(..., min_length=11, max_length=11), phone: str = Body(..., min_length=11, max_length=11),
# 更新:
purpose: str = Body("register") # register | login | reset_password purpose: str = Body("register") # register | login | reset_password
# 更新:
): ):
# 更新:
"""发送短信验证码""" """发送短信验证码"""
# 更新:
from app.services.sms import send_verification_code as send_sms from app.services.sms import send_verification_code as send_sms
# 更新:
# 更新:
# 验证手机号格式 # 验证手机号格式
# 更新:
if not phone.startswith("1") or len(phone) != 11: if not phone.startswith("1") or len(phone) != 11:
# 更新:
return {"success": False, "message": "手机号格式不正确"} return {"success": False, "message": "手机号格式不正确"}
# 更新:
# 更新:
result = send_sms(phone) result = send_sms(phone)
# 更新:
# 更新:
if result["success"]: if result["success"]:
# 更新:
return { return {
# 更新:
"success": True, "success": True,
# 更新:
"message": f"验证码已发送到 {phone[:3]}****{phone[7:]}", "message": f"验证码已发送到 {phone[:3]}****{phone[7:]}",
# 更新:
"expire": result.get("expire", 300) "expire": result.get("expire", 300)
# 更新:
} }
# 更新:
else: else:
# 更新:
return result return result
# 更新:
# 更新:
# 更新:
@router.post("/verify-code") @router.post("/verify-code")
# 更新:
def verify_code( def verify_code(
# 更新:
phone: str = Body(...), phone: str = Body(...),
# 更新:
code: str = Body(..., min_length=6, max_length=6) code: str = Body(..., min_length=6, max_length=6)
# 更新:
): ):
# 更新:
"""验证短信验证码(仅验证,不执行后续操作)""" """验证短信验证码(仅验证,不执行后续操作)"""
# 更新:
from app.services.sms import verify_code as check_code from app.services.sms import verify_code as check_code
# 更新:
# 更新:
is_valid = check_code(phone, code) is_valid = check_code(phone, code)
# 更新:
# 更新:
if is_valid: if is_valid:
# 更新:
return {"success": True, "message": "验证成功"} return {"success": True, "message": "验证成功"}
# 更新:
else: else:
# 更新:
return {"success": False, "message": "验证码错误或已过期"} return {"success": False, "message": "验证码错误或已过期"}
# 更新:

File diff suppressed because it is too large Load Diff

View File

@ -1,243 +1,502 @@
# deal - 成交行情路由
# Version: 1.2.85
# 更新:
from fastapi import APIRouter, Depends, Query, HTTPException from fastapi import APIRouter, Depends, Query, HTTPException
# 更新:
# Version: 1.2.x
# 更新:
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
# 更新:
from pydantic import BaseModel from pydantic import BaseModel
# 更新:
from typing import Optional from typing import Optional
# 更新:
from datetime import datetime, date from datetime import datetime, date
# 更新:
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
# 更新:
from app.models.deal_info import DealInfo from app.models.deal_info import DealInfo
# 更新:
# 更新:
router = APIRouter(prefix="/api/deal", tags=["成交行情"]) router = APIRouter(prefix="/api/deal", tags=["成交行情"])
# 更新:
# 更新:
# ============ Schema ============ # ============ Schema ============
# 更新:
class DealInfoCreate(BaseModel): class DealInfoCreate(BaseModel):
# 更新:
title: str title: str
# 更新:
content: Optional[str] = None content: Optional[str] = None
# 更新:
deal_price: Optional[float] = None deal_price: Optional[float] = None
# 更新:
deal_date: Optional[str] = None # YYYY-MM-DD deal_date: Optional[str] = None # YYYY-MM-DD
# 更新:
packaging: Optional[str] = None packaging: Optional[str] = None
# 更新:
category: Optional[str] = None category: Optional[str] = None
# 更新:
is_graded: Optional[bool] = False is_graded: Optional[bool] = False
# 更新:
grading_company: Optional[str] = None grading_company: Optional[str] = None
# 更新:
grading_score: Optional[str] = None grading_score: Optional[str] = None
# 更新:
tail_number: Optional[str] = None tail_number: Optional[str] = None
# 更新:
size_type: Optional[str] = None size_type: Optional[str] = None
# 更新:
version: Optional[str] = None version: Optional[str] = None
# 更新:
platform: Optional[str] = None platform: Optional[str] = None
# 更新:
seller: Optional[str] = None seller: Optional[str] = None
# 更新:
buyer: Optional[str] = None buyer: Optional[str] = None
# 更新:
# 更新:
class DealInfoUpdate(BaseModel): class DealInfoUpdate(BaseModel):
# 更新:
title: Optional[str] = None title: Optional[str] = None
# 更新:
content: Optional[str] = None content: Optional[str] = None
# 更新:
deal_price: Optional[float] = None deal_price: Optional[float] = None
# 更新:
deal_date: Optional[str] = None deal_date: Optional[str] = None
# 更新:
packaging: Optional[str] = None packaging: Optional[str] = None
# 更新:
category: Optional[str] = None category: Optional[str] = None
# 更新:
is_graded: Optional[bool] = None is_graded: Optional[bool] = None
# 更新:
grading_company: Optional[str] = None grading_company: Optional[str] = None
# 更新:
grading_score: Optional[str] = None grading_score: Optional[str] = None
# 更新:
tail_number: Optional[str] = None tail_number: Optional[str] = None
# 更新:
size_type: Optional[str] = None size_type: Optional[str] = None
# 更新:
version: Optional[str] = None version: Optional[str] = None
# 更新:
platform: Optional[str] = None platform: Optional[str] = None
# 更新:
seller: Optional[str] = None seller: Optional[str] = None
# 更新:
buyer: Optional[str] = None buyer: Optional[str] = None
# 更新:
status: Optional[str] = None status: Optional[str] = None
# 更新:
# 更新:
class DealInfoResponse(BaseModel): class DealInfoResponse(BaseModel):
# 更新:
id: str id: str
# 更新:
user_id: Optional[str] user_id: Optional[str]
# 更新:
title: str title: str
# 更新:
content: Optional[str] content: Optional[str]
# 更新:
deal_price: Optional[float] deal_price: Optional[float]
# 更新:
deal_date: Optional[date] deal_date: Optional[date]
# 更新:
deal_no: Optional[str] deal_no: Optional[str]
# 更新:
packaging: Optional[str] packaging: Optional[str]
# 更新:
category: Optional[str] category: Optional[str]
# 更新:
is_graded: Optional[bool] is_graded: Optional[bool]
# 更新:
grading_company: Optional[str] grading_company: Optional[str]
# 更新:
grading_score: Optional[str] grading_score: Optional[str]
# 更新:
tail_number: Optional[str] tail_number: Optional[str]
# 更新:
size_type: Optional[str] size_type: Optional[str]
# 更新:
version: Optional[str] version: Optional[str]
# 更新:
platform: Optional[str] platform: Optional[str]
# 更新:
seller: Optional[str] seller: Optional[str]
# 更新:
buyer: Optional[str] buyer: Optional[str]
# 更新:
status: str status: str
# 更新:
view_count: int view_count: int
# 更新:
contact_count: int contact_count: int
# 更新:
created_at: Optional[datetime] created_at: Optional[datetime]
# 更新:
updated_at: Optional[datetime] updated_at: Optional[datetime]
# 更新:
# 更新:
class Config: class Config:
# 更新:
from_attributes = True from_attributes = True
# 更新:
# 更新:
# 生成行情编号 # 生成行情编号
# 更新:
def generate_deal_no(db: Session): def generate_deal_no(db: Session):
# 更新:
"""生成行情编号从A000001开始递增""" """生成行情编号从A000001开始递增"""
# 更新:
last = db.query(DealInfo).order_by(DealInfo.deal_no.desc()).first() last = db.query(DealInfo).order_by(DealInfo.deal_no.desc()).first()
# 更新:
if last and last.deal_no: if last and last.deal_no:
# 更新:
# 例如 A000001 -> 2 -> A000002 # 例如 A000001 -> 2 -> A000002
# 更新:
num = int(last.deal_no[1:]) + 1 num = int(last.deal_no[1:]) + 1
# 更新:
return f"A{num:06d}" return f"A{num:06d}"
# 更新:
return "A000001" return "A000001"
# 更新:
# 更新:
# ============ API ============ # ============ API ============
# 更新:
@router.get("/list", response_model=list[DealInfoResponse]) @router.get("/list", response_model=list[DealInfoResponse])
# 更新:
def get_deal_list( def get_deal_list(
# 更新:
status: str = Query("active"), status: str = Query("active"),
# 更新:
deal_date: Optional[str] = Query(None), deal_date: Optional[str] = Query(None),
# 更新:
page: int = Query(1, ge=1), page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=500), # 更新:
page_size: int = Query(20, ge=1, le=1000),
# 更新:
user_only: bool = Query(False), # 是否只查看自己的
# 更新:
current_user: Optional = Depends(get_current_user), current_user: Optional = Depends(get_current_user),
# 更新:
db: Session = Depends(get_db) db: Session = Depends(get_db)
# 更新:
): ):
# 更新:
"""获取成交行情列表""" """获取成交行情列表"""
# 更新:
query = db.query(DealInfo).filter(DealInfo.status == status) 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: if deal_date:
# 更新:
query = query.filter(DealInfo.deal_date == deal_date) query = query.filter(DealInfo.deal_date == deal_date)
# 更新:
# 更新:
# 排序:优先成交日期倒序,同日按编号倒序 # 排序:优先成交日期倒序,同日按编号倒序
# 更新:
query = query.order_by(DealInfo.deal_date.desc().nullslast(), DealInfo.deal_no.desc().nullslast()) query = query.order_by(DealInfo.deal_date.desc().nullslast(), DealInfo.deal_no.desc().nullslast())
# 更新:
# 更新:
# 分页 # 分页
# 更新:
offset = (page - 1) * page_size offset = (page - 1) * page_size
# 更新:
items = query.offset(offset).limit(page_size).all() items = query.offset(offset).limit(page_size).all()
# 更新:
# 更新:
return items return items
# 更新:
# 更新:
@router.get("/stats") @router.get("/stats")
# 更新:
def get_deal_stats( def get_deal_stats(
# 更新:
db: Session = Depends(get_db) db: Session = Depends(get_db)
# 更新:
): ):
# 更新:
"""获取成交行情统计""" """获取成交行情统计"""
# 更新:
total = db.query(DealInfo).filter(DealInfo.status == "active").count() total = db.query(DealInfo).filter(DealInfo.status == "active").count()
# 更新:
# 更新:
# 按日期统计 # 按日期统计
# 更新:
from sqlalchemy import func from sqlalchemy import func
# 更新:
date_stats = db.query( date_stats = db.query(
# 更新:
DealInfo.deal_date, DealInfo.deal_date,
# 更新:
func.count(DealInfo.id).label('count') func.count(DealInfo.id).label('count')
# 更新:
).filter( ).filter(
# 更新:
DealInfo.status == "active", DealInfo.status == "active",
# 更新:
DealInfo.deal_date.isnot(None) DealInfo.deal_date.isnot(None)
# 更新:
).group_by(DealInfo.deal_date).order_by(DealInfo.deal_date.desc()).limit(10).all() ).group_by(DealInfo.deal_date).order_by(DealInfo.deal_date.desc()).limit(10).all()
# 更新:
# 更新:
return { return {
# 更新:
"total": total, "total": total,
# 更新:
"by_date": [{"date": str(d.deal_date), "count": d.count} for d in date_stats] "by_date": [{"date": str(d.deal_date), "count": d.count} for d in date_stats]
# 更新:
} }
# 更新:
# 更新:
@router.post("", response_model=DealInfoResponse) @router.post("", response_model=DealInfoResponse)
# 更新:
def create_deal( def create_deal(
# 更新:
data: DealInfoCreate, data: DealInfoCreate,
# 更新:
current_user = Depends(get_current_user), current_user = Depends(get_current_user),
# 更新:
db: Session = Depends(get_db) db: Session = Depends(get_db)
# 更新:
): ):
# 更新:
"""创建成交行情""" """创建成交行情"""
# 更新:
if not current_user: if not current_user:
# 更新:
raise HTTPException(status_code=401, detail="请先登录") raise HTTPException(status_code=401, detail="请先登录")
# 更新:
# 更新:
# 生成行情编号 # 生成行情编号
# 更新:
deal_no = generate_deal_no(db) deal_no = generate_deal_no(db)
# 更新:
# 更新:
# 解析日期 # 解析日期
# 更新:
deal_date = None deal_date = None
# 更新:
if data.deal_date: if data.deal_date:
# 更新:
try: try:
# 更新:
deal_date = datetime.strptime(data.deal_date, "%Y-%m-%d").date() deal_date = datetime.strptime(data.deal_date, "%Y-%m-%d").date()
# 更新:
except: except:
# 更新:
pass pass
# 更新:
# 更新:
deal = DealInfo( deal = DealInfo(
# 更新:
user_id=current_user.f99_90_id if current_user else None, user_id=current_user.f99_90_id if current_user else None,
# 更新:
title=data.title, title=data.title,
# 更新:
content=data.content, content=data.content,
# 更新:
deal_price=data.deal_price, deal_price=data.deal_price,
# 更新:
deal_date=deal_date, deal_date=deal_date,
# 更新:
deal_no=deal_no, deal_no=deal_no,
# 更新:
packaging=data.packaging, packaging=data.packaging,
# 更新:
category=data.category, category=data.category,
# 更新:
is_graded=data.is_graded or False, is_graded=data.is_graded or False,
# 更新:
grading_company=data.grading_company, grading_company=data.grading_company,
# 更新:
grading_score=data.grading_score, grading_score=data.grading_score,
# 更新:
tail_number=data.tail_number, tail_number=data.tail_number,
# 更新:
size_type=data.size_type, size_type=data.size_type,
# 更新:
version=data.version, version=data.version,
# 更新:
platform=data.platform, platform=data.platform,
# 更新:
seller=data.seller, seller=data.seller,
# 更新:
buyer=data.buyer, buyer=data.buyer,
# 更新:
status="active" status="active"
# 更新:
) )
# 更新:
db.add(deal) db.add(deal)
# 更新:
db.commit() db.commit()
# 更新:
db.refresh(deal) db.refresh(deal)
# 更新:
return deal return deal
# 更新:
# 更新:
@router.get("/{deal_id}", response_model=DealInfoResponse) @router.get("/{deal_id}", response_model=DealInfoResponse)
# 更新:
def get_deal( def get_deal(
# 更新:
deal_id: str, deal_id: str,
# 更新:
current_user = Depends(get_current_user), current_user = Depends(get_current_user),
# 更新:
db: Session = Depends(get_db) db: Session = Depends(get_db)
# 更新:
): ):
# 更新:
"""获取成交行情详情""" """获取成交行情详情"""
# 更新:
deal = db.query(DealInfo).filter(DealInfo.id == deal_id).first() deal = db.query(DealInfo).filter(DealInfo.id == deal_id).first()
# 更新:
if not deal: if not deal:
# 更新:
raise HTTPException(status_code=404, detail="成交行情不存在") raise HTTPException(status_code=404, detail="成交行情不存在")
# 更新:
# 更新:
# 增加浏览数 # 增加浏览数
# 更新:
deal.view_count += 1 deal.view_count += 1
# 更新:
db.commit() db.commit()
# 更新:
# 更新:
return deal return deal
# 更新:
# 更新:
@router.put("/{deal_id}", response_model=DealInfoResponse) @router.put("/{deal_id}", response_model=DealInfoResponse)
# 更新:
def update_deal( def update_deal(
# 更新:
deal_id: str, deal_id: str,
# 更新:
data: DealInfoUpdate, data: DealInfoUpdate,
# 更新:
current_user = Depends(get_current_user), current_user = Depends(get_current_user),
# 更新:
db: Session = Depends(get_db) db: Session = Depends(get_db)
# 更新:
): ):
# 更新:
"""更新成交行情""" """更新成交行情"""
# 更新:
if not current_user: if not current_user:
# 更新:
raise HTTPException(status_code=401, detail="请先登录") raise HTTPException(status_code=401, detail="请先登录")
# 更新:
# 更新:
deal = db.query(DealInfo).filter(DealInfo.id == deal_id).first() deal = db.query(DealInfo).filter(DealInfo.id == deal_id).first()
# 更新:
if not deal: if not deal:
# 更新:
raise HTTPException(status_code=404, detail="成交行情不存在") raise HTTPException(status_code=404, detail="成交行情不存在")
# 更新:
# 更新:
# 处理日期 # 处理日期
# 更新:
if data.deal_date: if data.deal_date:
# 更新:
try: try:
# 更新:
data.deal_date = datetime.strptime(data.deal_date, "%Y-%m-%d").date() data.deal_date = datetime.strptime(data.deal_date, "%Y-%m-%d").date()
# 更新:
except: except:
# 更新:
data.deal_date = None data.deal_date = None
# 更新:
# 更新:
for key, value in data.model_dump(exclude_unset=True).items(): for key, value in data.model_dump(exclude_unset=True).items():
# 更新:
setattr(deal, key, value) setattr(deal, key, value)
# 更新:
# 更新:
db.commit() db.commit()
# 更新:
db.refresh(deal) db.refresh(deal)
# 更新:
return deal return deal
# 更新:
# 更新:
@router.delete("/{deal_id}") @router.delete("/{deal_id}")
# 更新:
def delete_deal( def delete_deal(
# 更新:
deal_id: str, deal_id: str,
# 更新:
current_user = Depends(get_current_user), current_user = Depends(get_current_user),
# 更新:
db: Session = Depends(get_db) db: Session = Depends(get_db)
# 更新:
): ):
# 更新:
"""删除成交行情""" """删除成交行情"""
# 更新:
if not current_user: if not current_user:
# 更新:
raise HTTPException(status_code=401, detail="请先登录") raise HTTPException(status_code=401, detail="请先登录")
# 更新:
# 更新:
deal = db.query(DealInfo).filter(DealInfo.id == deal_id).first() deal = db.query(DealInfo).filter(DealInfo.id == deal_id).first()
# 更新:
if not deal: if not deal:
# 更新:
raise HTTPException(status_code=404, detail="成交行情不存在") raise HTTPException(status_code=404, detail="成交行情不存在")
# 更新:
# 更新:
deal.status = "deleted" deal.status = "deleted"
# 更新:
db.commit() db.commit()
# 更新:
return {"message": "删除成功"} # 更新:
return {"message": "删除成功"}
# 更新:

File diff suppressed because it is too large Load Diff

View File

@ -1,128 +1,262 @@
# news - 新闻路由
# Version: 1.2.80
# 更新:
from fastapi import APIRouter, Depends, HTTPException, Query from fastapi import APIRouter, Depends, HTTPException, Query
# 更新:
# Version: 1.2.x
# 更新:
from sqlalchemy import Table, MetaData from sqlalchemy import Table, MetaData
# 更新:
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
# 更新:
from pydantic import BaseModel from pydantic import BaseModel
# 更新:
from typing import Optional, List from typing import Optional, List
# 更新:
from datetime import datetime, date from datetime import datetime, date
# 更新:
from app.core.database import get_db, engine from app.core.database import get_db, engine
# 更新:
from app.models.models import User from app.models.models import User
# 更新:
from app.routers.auth import get_current_user from app.routers.auth import get_current_user
# 更新:
# 更新:
router = APIRouter(prefix="/api/news", tags=["资讯"]) router = APIRouter(prefix="/api/news", tags=["资讯"])
# 更新:
metadata = MetaData() metadata = MetaData()
# 更新:
# 更新:
# 分类表 # 分类表
# 更新:
categories_table = Table('news_categories', metadata, autoload_with=engine) categories_table = Table('news_categories', metadata, autoload_with=engine)
# 更新:
news_table = Table('news', metadata, autoload_with=engine) news_table = Table('news', metadata, autoload_with=engine)
# 更新:
user_posts_table = Table('user_posts', metadata, autoload_with=engine) user_posts_table = Table('user_posts', metadata, autoload_with=engine)
# 更新:
users_table = Table('users', metadata, autoload_with=engine) users_table = Table('users', metadata, autoload_with=engine)
# 更新:
deals_table = Table('deals', metadata, autoload_with=engine) deals_table = Table('deals', metadata, autoload_with=engine)
# 更新:
notifications_table = Table('notifications', metadata, autoload_with=engine) notifications_table = Table('notifications', metadata, autoload_with=engine)
# 更新:
# 更新:
# ============ 获取分类 ============ # ============ 获取分类 ============
# 更新:
@router.get("/categories") @router.get("/categories")
# 更新:
def get_categories(db: Session = Depends(get_db)): def get_categories(db: Session = Depends(get_db)):
# 更新:
results = db.query(categories_table).order_by(categories_table.c.sort_order).all() results = db.query(categories_table).order_by(categories_table.c.sort_order).all()
# 更新:
return [dict(r._mapping) for r in results] return [dict(r._mapping) for r in results]
# 更新:
# 更新:
# ============ 获取资讯 ============ # ============ 获取资讯 ============
# 更新:
@router.get("") @router.get("")
# 更新:
def get_news( def get_news(
# 更新:
category_id: Optional[int] = None, category_id: Optional[int] = None,
# 更新:
page: int = 1, page: int = 1,
# 更新:
limit: int = 20, limit: int = 20,
# 更新:
db: Session = Depends(get_db) db: Session = Depends(get_db)
# 更新:
): ):
# 更新:
query = db.query(news_table) query = db.query(news_table)
# 更新:
if category_id: if category_id:
# 更新:
query = query.filter(news_table.c.category_id == category_id) query = query.filter(news_table.c.category_id == category_id)
# 更新:
offset = (page - 1) * limit offset = (page - 1) * limit
# 更新:
results = query.order_by(news_table.c.created_at.desc()).offset(offset).limit(limit).all() results = query.order_by(news_table.c.created_at.desc()).offset(offset).limit(limit).all()
# 更新:
return [dict(r._mapping) for r in results] return [dict(r._mapping) for r in results]
# 更新:
# 更新:
# ============ 获取用户发布 ============ # ============ 获取用户发布 ============
# 更新:
@router.get("/posts") @router.get("/posts")
# 更新:
def get_posts( def get_posts(
# 更新:
post_type: Optional[str] = None, post_type: Optional[str] = None,
# 更新:
status: str = "active", status: str = "active",
# 更新:
page: int = 1, page: int = 1,
# 更新:
limit: int = 20, limit: int = 20,
# 更新:
db: Session = Depends(get_db) db: Session = Depends(get_db)
# 更新:
): ):
# 更新:
query = db.query(user_posts_table).filter(user_posts_table.c.status == status) query = db.query(user_posts_table).filter(user_posts_table.c.status == status)
# 更新:
if post_type: if post_type:
# 更新:
query = query.filter(user_posts_table.c.post_type == post_type) query = query.filter(user_posts_table.c.post_type == post_type)
# 更新:
offset = (page - 1) * limit offset = (page - 1) * limit
# 更新:
results = query.order_by(user_posts_table.c.created_at.desc()).offset(offset).limit(limit).all() results = query.order_by(user_posts_table.c.created_at.desc()).offset(offset).limit(limit).all()
# 更新:
return [dict(r._mapping) for r in results] return [dict(r._mapping) for r in results]
# 更新:
# 更新:
# ============ 创建发布 ============ # ============ 创建发布 ============
# 更新:
class PostCreate(BaseModel): class PostCreate(BaseModel):
# 更新:
post_type: str post_type: str
# 更新:
title: str title: str
# 更新:
content: Optional[str] = None content: Optional[str] = None
# 更新:
zodiac_type: Optional[str] = None zodiac_type: Optional[str] = None
# 更新:
packaging: Optional[str] = None packaging: Optional[str] = None
# 更新:
# 更新:
@router.post("/posts") @router.post("/posts")
# 更新:
def create_post( def create_post(
# 更新:
post: PostCreate, post: PostCreate,
# 更新:
current_user: User = Depends(get_current_user), current_user: User = Depends(get_current_user),
# 更新:
db: Session = Depends(get_db) db: Session = Depends(get_db)
# 更新:
): ):
# 更新:
result = db.execute(user_posts_table.insert().values( result = db.execute(user_posts_table.insert().values(
# 更新:
user_id=current_user.f99_90_id, user_id=current_user.f99_90_id,
# 更新:
post_type=post.post_type, post_type=post.post_type,
# 更新:
title=post.title, title=post.title,
# 更新:
content=post.content, content=post.content,
# 更新:
zodiac_type=post.zodiac_type, zodiac_type=post.zodiac_type,
# 更新:
packaging=post.packaging, packaging=post.packaging,
# 更新:
status="pending" status="pending"
# 更新:
)) ))
# 更新:
db.commit() db.commit()
# 更新:
return {"success": True, "id": result.inserted_primary_key[0]} return {"success": True, "id": result.inserted_primary_key[0]}
# 更新:
# 更新:
# ============ 成交数据 ============ # ============ 成交数据 ============
# 更新:
@router.get("/deals") @router.get("/deals")
# 更新:
def get_deals( def get_deals(
# 更新:
zodiac_type: Optional[str] = None, zodiac_type: Optional[str] = None,
# 更新:
limit: int = 20, limit: int = 20,
# 更新:
db: Session = Depends(get_db) db: Session = Depends(get_db)
# 更新:
): ):
# 更新:
query = db.query(deals_table) query = db.query(deals_table)
# 更新:
if zodiac_type: if zodiac_type:
# 更新:
query = query.filter(deals_table.c.zodiac_type == zodiac_type) query = query.filter(deals_table.c.zodiac_type == zodiac_type)
# 更新:
results = query.order_by(deals_table.c.deal_date.desc()).limit(limit).all() results = query.order_by(deals_table.c.deal_date.desc()).limit(limit).all()
# 更新:
return [dict(r._mapping) for r in results] return [dict(r._mapping) for r in results]
# 更新:
# 更新:
# ============ 通知 ============ # ============ 通知 ============
# 更新:
@router.get("/notifications") @router.get("/notifications")
# 更新:
def get_notifications(limit: int = 10, db: Session = Depends(get_db)): def get_notifications(limit: int = 10, db: Session = Depends(get_db)):
# 更新:
results = db.query(notifications_table).filter( results = db.query(notifications_table).filter(
# 更新:
notifications_table.c.is_published == True notifications_table.c.is_published == True
# 更新:
).order_by(notifications_table.c.created_at.desc()).limit(limit).all() ).order_by(notifications_table.c.created_at.desc()).limit(limit).all()
# 更新:
return [dict(r._mapping) for r in results] return [dict(r._mapping) for r in results]
# 更新:
# 更新:
# ============ 首页数据 ============ # ============ 首页数据 ============
# 更新:
@router.get("/home") @router.get("/home")
# 更新:
def get_home(db: Session = Depends(get_db)): def get_home(db: Session = Depends(get_db)):
# 更新:
# 推荐发布 # 推荐发布
# 更新:
posts = db.query(user_posts_table).filter( posts = db.query(user_posts_table).filter(
# 更新:
user_posts_table.c.status == "active" user_posts_table.c.status == "active"
# 更新:
).order_by(user_posts_table.c.created_at.desc()).limit(10).all() ).order_by(user_posts_table.c.created_at.desc()).limit(10).all()
# 更新:
# 更新:
# 成交 # 成交
# 更新:
deals = db.query(deals_table).order_by( deals = db.query(deals_table).order_by(
# 更新:
deals_table.c.deal_date.desc() deals_table.c.deal_date.desc()
# 更新:
).limit(10).all() ).limit(10).all()
# 更新:
# 更新:
# 通知 # 通知
# 更新:
notices = db.query(notifications_table).filter( notices = db.query(notifications_table).filter(
# 更新:
notifications_table.c.is_published == True notifications_table.c.is_published == True
# 更新:
).order_by(notifications_table.c.created_at.desc()).limit(5).all() ).order_by(notifications_table.c.created_at.desc()).limit(5).all()
# 更新:
# 更新:
return { return {
# 更新:
"posts": [dict(p._mapping) for p in posts], "posts": [dict(p._mapping) for p in posts],
# 更新:
"deals": [dict(d._mapping) for d in deals], "deals": [dict(d._mapping) for d in deals],
# 更新:
"notices": [dict(n._mapping) for n in notices] "notices": [dict(n._mapping) for n in notices]
# 更新:
} }
# 更新:

View File

@ -1,384 +1,770 @@
# OCR 识别路由 - 专业人民币生肖纪念钞鉴定 # ocr - OCR识别路由
# Version: 1.2.75
# 更新:
import os import os
# 更新:
import uuid import uuid
# 更新:
import base64 import base64
# 更新:
import httpx import httpx
# 更新:
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File from fastapi import APIRouter, Depends, HTTPException, UploadFile, File
# 更新:
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
# 更新:
from app.models.models import User from app.models.models import User
# 更新:
# 更新:
router = APIRouter(prefix="/api/ocr", tags=["OCR 识别"]) router = APIRouter(prefix="/api/ocr", tags=["OCR 识别"])
# 更新:
# 更新:
# 阿里云 DashScope API 配置 # 阿里云 DashScope API 配置
# 更新:
DASHSCOPE_API_KEY = os.getenv("DASHSCOPE_API_KEY", "sk-9389024a37da4f7bb455ac9a6b28776f") DASHSCOPE_API_KEY = os.getenv("DASHSCOPE_API_KEY", "sk-9389024a37da4f7bb455ac9a6b28776f")
# 更新:
# 更新:
# 阿里云 OSS 配置 # 阿里云 OSS 配置
# 更新:
OSS_CONFIG = { OSS_CONFIG = {
# 更新:
"access_key_id": os.getenv("OSS_ACCESS_KEY_ID", "LTAI5t6HUnpFBLEK9194kPVG"), "access_key_id": os.getenv("OSS_ACCESS_KEY_ID", "LTAI5t6HUnpFBLEK9194kPVG"),
# 更新:
"access_key_secret": os.getenv("OSS_ACCESS_KEY_SECRET", "LEr4Q8yRxb8D5b24cKfCwlt4MMoke1"), "access_key_secret": os.getenv("OSS_ACCESS_KEY_SECRET", "LEr4Q8yRxb8D5b24cKfCwlt4MMoke1"),
# 更新:
"bucket_name": "jiachenlong-oss", "bucket_name": "jiachenlong-oss",
# 更新:
"endpoint": "oss-cn-hangzhou.aliyuncs.com", "endpoint": "oss-cn-hangzhou.aliyuncs.com",
# 更新:
"public_url": "https://jiachenlong-oss.oss-cn-hangzhou.aliyuncs.com" "public_url": "https://jiachenlong-oss.oss-cn-hangzhou.aliyuncs.com"
# 更新:
} }
# 更新:
# 更新:
# 临时上传目录用于OCR识别本地备选 # 临时上传目录用于OCR识别本地备选
# 更新:
UPLOAD_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "uploads", "temp") UPLOAD_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "uploads", "temp")
# 更新:
os.makedirs(UPLOAD_DIR, exist_ok=True) os.makedirs(UPLOAD_DIR, exist_ok=True)
# 更新:
# 更新:
# 更新:
def get_oss_path(file_type: str, user_id: str = None, collection_id: str = None, filename: str = None): def get_oss_path(file_type: str, user_id: str = None, collection_id: str = None, filename: str = None):
# 更新:
"""生成OSS路径 - 按年/月/日分类""" """生成OSS路径 - 按年/月/日分类"""
# 更新:
from datetime import datetime from datetime import datetime
# 更新:
now = datetime.now() now = datetime.now()
# 更新:
year = now.strftime("%Y") year = now.strftime("%Y")
# 更新:
month = now.strftime("%m") month = now.strftime("%m")
# 更新:
day = now.strftime("%d") day = now.strftime("%d")
# 更新:
# 更新:
if file_type == "temp": if file_type == "temp":
# 更新:
# 临时文件: temp/{year}/{month}/{day}/{uuid}.{ext} # 临时文件: temp/{year}/{month}/{day}/{uuid}.{ext}
# 更新:
import uuid import uuid
# 更新:
unique_id = str(uuid.uuid4()) unique_id = str(uuid.uuid4())
# 更新:
ext = filename.split('.')[-1] if filename and '.' in filename else 'jpg' ext = filename.split('.')[-1] if filename and '.' in filename else 'jpg'
# 更新:
return f"temp/{year}/{month}/{day}/{unique_id}.{ext}", unique_id return f"temp/{year}/{month}/{day}/{unique_id}.{ext}", unique_id
# 更新:
# 更新:
elif file_type == "collection": elif file_type == "collection":
# 更新:
# 藏品文件: collections/{user_id}/{year}/{collection_id}/{filename} # 藏品文件: collections/{user_id}/{year}/{collection_id}/{filename}
# 更新:
if not user_id or not collection_id: if not user_id or not collection_id:
# 更新:
raise ValueError("user_id and collection_id required for collection") raise ValueError("user_id and collection_id required for collection")
# 更新:
return f"collections/{user_id}/{year}/{collection_id}/{filename}" return f"collections/{user_id}/{year}/{collection_id}/{filename}"
# 更新:
# 更新:
elif file_type == "avatar": elif file_type == "avatar":
# 更新:
# 头像: avatars/{user_id}/avatar.{ext} # 头像: avatars/{user_id}/avatar.{ext}
# 更新:
if not user_id: if not user_id:
# 更新:
raise ValueError("user_id required for avatar") raise ValueError("user_id required for avatar")
# 更新:
ext = filename.split('.')[-1] if filename and '.' in filename else 'jpg' ext = filename.split('.')[-1] if filename and '.' in filename else 'jpg'
# 更新:
return f"avatars/{user_id}/avatar.{ext}" return f"avatars/{user_id}/avatar.{ext}"
# 更新:
# 更新:
return None return None
# 更新:
# 更新:
# 更新:
# 上传图片到OSS - 使用服务层(带压缩) # 上传图片到OSS - 使用服务层(带压缩)
# 更新:
from app.services.oss import upload_to_oss as oss_upload from app.services.oss import upload_to_oss as oss_upload
# 更新:
# 更新:
def upload_to_oss(file_data, oss_key): def upload_to_oss(file_data, oss_key):
# 更新:
"""上传文件到阿里云OSS带自动压缩""" """上传文件到阿里云OSS带自动压缩"""
# 更新:
return oss_upload(file_data, oss_key) return oss_upload(file_data, oss_key)
# 更新:
# 更新:
# 专业提示词 # 专业提示词
# 更新:
PROFESSIONAL_PROMPT = """你是一名专业的人民币生肖纪念钞鉴定专家。请严格按照以下步骤分析这张图片,并提取准确信息。 PROFESSIONAL_PROMPT = """你是一名专业的人民币生肖纪念钞鉴定专家。请严格按照以下步骤分析这张图片,并提取准确信息。
# 更新:
# 更新:
识别流程 识别流程
# 更新:
1. 判断类型是否评级钞首先确认是否为裸钞还是评级钞有封装盒和标签 1. 判断类型是否评级钞首先确认是否为裸钞还是评级钞有封装盒和标签
# 更新:
2. 验证纪念钞特征对照生肖纪念钞特征进行确认 2. 验证纪念钞特征对照生肖纪念钞特征进行确认
# 更新:
3. 验证评级类型'标十'字眼的为标十'百连'字眼的为标百其他为单张 3. 验证评级类型'标十'字眼的为标十'百连'字眼的为标百其他为单张
# 更新:
4. 提取信息仔细阅读标签上的所有文字内容 4. 提取信息仔细阅读标签上的所有文字内容
# 更新:
# 更新:
版别格式要求 版别格式要求
# 更新:
只需要年份 + 属相例如 只需要年份 + 属相例如
# 更新:
- 2024 - 2024
# 更新:
- 2025 - 2025
# 更新:
- 2026 - 2026
# 更新:
# 更新:
输出要求 输出要求
# 更新:
严格按照以下格式输出每个字段必须填写具体值 严格按照以下格式输出每个字段必须填写具体值
# 更新:
1 发行机构中国人民银行 1 发行机构中国人民银行
# 更新:
2 发行版别2024 2 发行版别2024
# 更新:
3 面额贰拾圆 3 面额贰拾圆
# 更新:
4 是否评级/ 4 是否评级/
# 更新:
5 封装类型裸钞/单张/标十/标百 5 封装类型裸钞/单张/标十/标百
# 更新:
6 冠字序号J0xxxxxxxx 6 冠字序号J0xxxxxxxx
# 更新:
7 评级机构ACG/PCGS/PMG 7 评级机构ACG/PCGS/PMG
# 更新:
8 评级分数67/68/69 8 评级分数67/68/69
# 更新:
9 是否三星/ 9 是否三星/
# 更新:
10 特殊标识金山标/天马标/红绳版等 10 特殊标识金山标/天马标/红绳版等
# 更新:
11 号码特征金山号 2 天马号 3 张等 11 号码特征金山号 2 天马号 3 张等
# 更新:
# 更新:
现在请仔细分析提供的图片按上述格式输出结果""" 现在请仔细分析提供的图片按上述格式输出结果"""
# 更新:
# 更新:
# 更新:
@router.post("/recognize") @router.post("/recognize")
# 更新:
async def recognize_image( async def recognize_image(
# 更新:
image: UploadFile = File(...), image: UploadFile = File(...),
# 更新:
current_user: User = Depends(get_current_user), current_user: User = Depends(get_current_user),
# 更新:
db: Session = Depends(get_db) db: Session = Depends(get_db)
# 更新:
): ):
# 更新:
"""OCR 图片识别 - 识别后自动保存图片到OSS临时目录""" """OCR 图片识别 - 识别后自动保存图片到OSS临时目录"""
# 更新:
try: try:
# 更新:
# 读取图片数据 # 读取图片数据
# 更新:
image_data = await image.read() image_data = await image.read()
# 更新:
image_base64 = base64.b64encode(image_data).decode('utf-8') image_base64 = base64.b64encode(image_data).decode('utf-8')
# 更新:
# 更新:
# 生成OSS临时路径: temp/{year}/{month}/{day}/{uuid}.{ext} # 生成OSS临时路径: temp/{year}/{month}/{day}/{uuid}.{ext}
# 更新:
oss_key, temp_id = get_oss_path("temp", filename=image.filename) oss_key, temp_id = get_oss_path("temp", filename=image.filename)
# 更新:
# 更新:
# 初始化temp_path为空 # 初始化temp_path为空
# 更新:
temp_path = None temp_path = None
# 更新:
# 更新:
# 上传到OSS # 上传到OSS
# 更新:
try: try:
# 更新:
image_url = upload_to_oss(image_data, oss_key) image_url = upload_to_oss(image_data, oss_key)
# 更新:
except Exception as oss_err: except Exception as oss_err:
# 更新:
# OSS失败时保存到本地作为备选 # OSS失败时保存到本地作为备选
# 更新:
temp_path = os.path.join(UPLOAD_DIR, oss_key.split('/')[-1]) temp_path = os.path.join(UPLOAD_DIR, oss_key.split('/')[-1])
# 更新:
os.makedirs(os.path.dirname(temp_path), exist_ok=True) os.makedirs(os.path.dirname(temp_path), exist_ok=True)
# 更新:
with open(temp_path, 'wb') as f: with open(temp_path, 'wb') as f:
# 更新:
f.write(image_data) f.write(image_data)
# 更新:
image_url = f"/uploads/temp/{oss_key.split('/')[-1]}" image_url = f"/uploads/temp/{oss_key.split('/')[-1]}"
# 更新:
# 更新:
headers = { headers = {
# 更新:
"Authorization": f"Bearer {DASHSCOPE_API_KEY}", "Authorization": f"Bearer {DASHSCOPE_API_KEY}",
# 更新:
"Content-Type": "application/json" "Content-Type": "application/json"
# 更新:
} }
# 更新:
# 更新:
# 阿里云 DashScope API 格式 (qwen-vl-plus 视觉模型) # 阿里云 DashScope API 格式 (qwen-vl-plus 视觉模型)
# 更新:
payload = { payload = {
# 更新:
"model": "qwen-vl-plus", "model": "qwen-vl-plus",
# 更新:
"input": { "input": {
# 更新:
"messages": [{ "messages": [{
# 更新:
"role": "user", "role": "user",
# 更新:
"content": [ "content": [
# 更新:
{ {
# 更新:
"image": f"data:{image.content_type};base64,{image_base64}" "image": f"data:{image.content_type};base64,{image_base64}"
# 更新:
}, },
# 更新:
{ {
# 更新:
"text": PROFESSIONAL_PROMPT "text": PROFESSIONAL_PROMPT
# 更新:
} }
# 更新:
] ]
# 更新:
}] }]
# 更新:
}, },
# 更新:
"parameters": { "parameters": {
# 更新:
"max_tokens": 1000 "max_tokens": 1000
# 更新:
} }
# 更新:
} }
# 更新:
# 更新:
async with httpx.AsyncClient(timeout=60.0) as client: async with httpx.AsyncClient(timeout=60.0) as client:
# 更新:
response = await client.post( response = await client.post(
# 更新:
"https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation", "https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation",
# 更新:
json=payload, json=payload,
# 更新:
headers=headers headers=headers
# 更新:
) )
# 更新:
# 更新:
if response.status_code != 200: if response.status_code != 200:
# 更新:
# 识别失败,删除临时文件 # 识别失败,删除临时文件
# 更新:
if temp_path and os.path.exists(temp_path): if temp_path and os.path.exists(temp_path):
# 更新:
os.remove(temp_path) os.remove(temp_path)
# 更新:
raise HTTPException(status_code=500, detail=f"OCR API 调用失败:{response.text[:200]}") raise HTTPException(status_code=500, detail=f"OCR API 调用失败:{response.text[:200]}")
# 更新:
# 更新:
ocr_result = response.json() ocr_result = response.json()
# 更新:
text_content = "" text_content = ""
# 更新:
# 新版API返回格式 # 新版API返回格式
# 更新:
if "output" in ocr_result and "choices" in ocr_result["output"]: if "output" in ocr_result and "choices" in ocr_result["output"]:
# 更新:
choices = ocr_result["output"]["choices"] choices = ocr_result["output"]["choices"]
# 更新:
if choices and len(choices) > 0: if choices and len(choices) > 0:
# 更新:
content = choices[0].get("message", {}).get("content", []) content = choices[0].get("message", {}).get("content", [])
# 更新:
if content and len(content) > 0: if content and len(content) > 0:
# 更新:
text_content = content[0].get("text", "") text_content = content[0].get("text", "")
# 更新:
# 更新:
fields = extract_fields(text_content) fields = extract_fields(text_content)
# 更新:
# 更新:
# 更新用户AI识别次数 # 更新用户AI识别次数
# 更新:
current_user.f99_95_ai_count = (current_user.f99_95_ai_count or 0) + 1 current_user.f99_95_ai_count = (current_user.f99_95_ai_count or 0) + 1
# 更新:
db.commit() db.commit()
# 更新:
# 更新:
# 返回识别结果和临时图片路径 # 返回识别结果和临时图片路径
# 更新:
return { return {
# 更新:
"success": True, "success": True,
# 更新:
"text": text_content, "text": text_content,
# 更新:
"fields": fields, "fields": fields,
# 更新:
"aiCount": current_user.f99_95_ai_count, "aiCount": current_user.f99_95_ai_count,
# 更新:
"temp_image": { "temp_image": {
# 更新:
"id": temp_id, "id": temp_id,
# 更新:
"filename": oss_key.split('/')[-1], "filename": oss_key.split('/')[-1],
# 更新:
"path": image_url, "path": image_url,
# 更新:
"original_name": image.filename, "original_name": image.filename,
# 更新:
"is_oss": image_url.startswith("https://") "is_oss": image_url.startswith("https://")
# 更新:
} }
# 更新:
} }
# 更新:
# 更新:
except Exception as e: except Exception as e:
# 更新:
import traceback import traceback
# 更新:
error_detail = f"识别失败:{str(e)}\n{traceback.format_exc()}" error_detail = f"识别失败:{str(e)}\n{traceback.format_exc()}"
# 更新:
raise HTTPException(status_code=500, detail=error_detail) raise HTTPException(status_code=500, detail=error_detail)
# 更新:
# 更新:
# 更新:
def extract_fields(text: str) -> dict: def extract_fields(text: str) -> dict:
# 更新:
"""从 OCR 文本中提取字段 - 直接返回 AI 识别结果""" """从 OCR 文本中提取字段 - 直接返回 AI 识别结果"""
# 更新:
import re import re
# 更新:
fields = {} fields = {}
# 更新:
# 更新:
# 解析结构化输出 # 解析结构化输出
# 更新:
patterns = { patterns = {
# 更新:
'issuer': r'✅.*?1.*?发行机构.*?[:]\s*(.+?)(?:\n|$)', 'issuer': r'✅.*?1.*?发行机构.*?[:]\s*(.+?)(?:\n|$)',
# 更新:
'version': r'✅.*?2.*?发行版别.*?[:]\s*(.+?)(?:\n|$)', 'version': r'✅.*?2.*?发行版别.*?[:]\s*(.+?)(?:\n|$)',
# 更新:
'denomination': r'✅.*?3.*?面额.*?[:]\s*(.+?)(?:\n|$)', 'denomination': r'✅.*?3.*?面额.*?[:]\s*(.+?)(?:\n|$)',
# 更新:
'is_graded_text': r'✅.*?4.*?是否评级.*?[:]\s*(.+?)(?:\n|$)', 'is_graded_text': r'✅.*?4.*?是否评级.*?[:]\s*(.+?)(?:\n|$)',
# 更新:
'packaging': r'✅.*?5.*?封装类型.*?[:]\s*(.+?)(?:\n|$)', 'packaging': r'✅.*?5.*?封装类型.*?[:]\s*(.+?)(?:\n|$)',
# 更新:
'prefix_serial': r'✅.*?6.*?冠字序号.*?[:]\s*(.+?)(?:\n|$)', 'prefix_serial': r'✅.*?6.*?冠字序号.*?[:]\s*(.+?)(?:\n|$)',
# 更新:
'grading_company': r'✅.*?7.*?评级机构.*?[:]\s*(.+?)(?:\n|$)', 'grading_company': r'✅.*?7.*?评级机构.*?[:]\s*(.+?)(?:\n|$)',
# 更新:
'grading_score': r'✅.*?8.*?评级分数.*?[:]\s*(.+?)(?:\n|$)', 'grading_score': r'✅.*?8.*?评级分数.*?[:]\s*(.+?)(?:\n|$)',
# 更新:
'three_star_text': r'✅.*?9.*?是否三星.*?[:]\s*(.+?)(?:\n|$)', 'three_star_text': r'✅.*?9.*?是否三星.*?[:]\s*(.+?)(?:\n|$)',
# 更新:
'special_mark': r'✅.*?10.*?特殊标识.*?[:]\s*(.+?)(?:\n|$)', 'special_mark': r'✅.*?10.*?特殊标识.*?[:]\s*(.+?)(?:\n|$)',
# 更新:
'serial_feature': r'✅.*?11.*?号码特征.*?[:]\s*(.+?)(?:\n|$)' 'serial_feature': r'✅.*?11.*?号码特征.*?[:]\s*(.+?)(?:\n|$)'
# 更新:
} }
# 更新:
# 更新:
for field, pattern in patterns.items(): for field, pattern in patterns.items():
# 更新:
match = re.search(pattern, text, re.IGNORECASE | re.DOTALL) match = re.search(pattern, text, re.IGNORECASE | re.DOTALL)
# 更新:
if match: if match:
# 更新:
value = match.group(1).strip() value = match.group(1).strip()
# 更新:
# 保留所有值,包括"无"和"未识别",让前端处理 # 保留所有值,包括"无"和"未识别",让前端处理
# 更新:
fields[field] = value fields[field] = value
# 更新:
# 更新:
# 处理是否评级 # 处理是否评级
# 更新:
if 'is_graded_text' in fields: if 'is_graded_text' in fields:
# 更新:
fields['is_graded'] = '' in fields.pop('is_graded_text') fields['is_graded'] = '' in fields.pop('is_graded_text')
# 更新:
# 更新:
# 处理是否三星 # 处理是否三星
# 更新:
if 'three_star_text' in fields: if 'three_star_text' in fields:
# 更新:
fields['three_star'] = '' in fields.pop('three_star_text') fields['three_star'] = '' in fields.pop('three_star_text')
# 更新:
# 更新:
# 简化版别字段2024 龙年贺岁纪念钞(标十) → 2024 龙) # 简化版别字段2024 龙年贺岁纪念钞(标十) → 2024 龙)
# 更新:
if 'version' in fields: if 'version' in fields:
# 更新:
version = fields['version'] version = fields['version']
# 更新:
# 提取年份和生肖 # 提取年份和生肖
# 更新:
year_match = re.search(r'(20\d{2})', version) year_match = re.search(r'(20\d{2})', version)
# 更新:
animal = '' animal = ''
# 更新:
if '' in version: if '' in version:
# 更新:
animal = '' animal = ''
# 更新:
elif '' in version: elif '' in version:
# 更新:
animal = '' animal = ''
# 更新:
elif '' in version: elif '' in version:
# 更新:
animal = '' animal = ''
# 更新:
elif '' in version: elif '' in version:
# 更新:
animal = '' animal = ''
# 更新:
elif '' in version: elif '' in version:
# 更新:
animal = '' animal = ''
# 更新:
elif '' in version: elif '' in version:
# 更新:
animal = '' animal = ''
# 更新:
elif '' in version: elif '' in version:
# 更新:
animal = '' animal = ''
# 更新:
elif '' in version: elif '' in version:
# 更新:
animal = '' animal = ''
# 更新:
elif '' in version: elif '' in version:
# 更新:
animal = '' animal = ''
# 更新:
elif '' in version: elif '' in version:
# 更新:
animal = '' animal = ''
# 更新:
elif '' in version: elif '' in version:
# 更新:
animal = '' animal = ''
# 更新:
elif '' in version: elif '' in version:
# 更新:
animal = '' animal = ''
# 更新:
# 更新:
if year_match and animal: if year_match and animal:
# 更新:
fields['version'] = f"{year_match.group(1)}{animal}" fields['version'] = f"{year_match.group(1)}{animal}"
# 更新:
# 更新:
return fields return fields
# 更新:
# 更新:
# 更新:
@router.post("/claim-temp-image") @router.post("/claim-temp-image")
# 更新:
async def claim_temp_image( async def claim_temp_image(
# 更新:
temp_id: str, temp_id: str,
# 更新:
collection_id: str, collection_id: str,
# 更新:
current_user: User = Depends(get_current_user), current_user: User = Depends(get_current_user),
# 更新:
db: Session = Depends(get_db) db: Session = Depends(get_db)
# 更新:
): ):
# 更新:
"""将临时图片移动到正式藏品目录 - 按用户/年/藏品ID分类""" """将临时图片移动到正式藏品目录 - 按用户/年/藏品ID分类"""
# 更新:
from app.models.models import Collection, CollectionImage from app.models.models import Collection, CollectionImage
# 更新:
from datetime import datetime from datetime import datetime
# 更新:
# 更新:
# 验证藏品是否存在 # 验证藏品是否存在
# 更新:
collection = db.query(Collection).filter( collection = db.query(Collection).filter(
# 更新:
Collection.f99_90_id == collection_id, Collection.f99_90_id == collection_id,
# 更新:
Collection.f99_91_user_id == current_user.f99_90_id Collection.f99_91_user_id == current_user.f99_90_id
# 更新:
).first() ).first()
# 更新:
# 更新:
if not collection: if not collection:
# 更新:
raise HTTPException(status_code=404, detail="藏品不存在") raise HTTPException(status_code=404, detail="藏品不存在")
# 更新:
# 更新:
# 生成正式文件名和OSS路径: collections/{user_id}/{year}/{collection_id}/{filename} # 生成正式文件名和OSS路径: collections/{user_id}/{year}/{collection_id}/{filename}
# 更新:
code = collection.f01_02_code or "0000" code = collection.f01_02_code or "0000"
# 更新:
prefix = collection.f02_10_prefix_serial or "" prefix = collection.f02_10_prefix_serial or ""
# 更新:
username = current_user.f01_01_name username = current_user.f01_01_name
# 更新:
import time import time
# 更新:
final_filename = f"{username}-{code}-{prefix}-{int(time.time())}.jpg" final_filename = f"{username}-{code}-{prefix}-{int(time.time())}.jpg"
# 更新:
oss_key = get_oss_path("collection", user_id=current_user.f99_90_id, collection_id=collection_id, filename=final_filename) oss_key = get_oss_path("collection", user_id=current_user.f99_90_id, collection_id=collection_id, filename=final_filename)
# 更新:
# 更新:
# 尝试从OSS获取临时图片 - 尝试多种扩展名和日期路径 # 尝试从OSS获取临时图片 - 尝试多种扩展名和日期路径
# 更新:
temp_extensions = ['jpg', 'jpeg', 'png', 'gif', 'JPG', 'JPEG', 'PNG', 'GIF'] temp_extensions = ['jpg', 'jpeg', 'png', 'gif', 'JPG', 'JPEG', 'PNG', 'GIF']
# 更新:
temp_content = None temp_content = None
# 更新:
found_key = None found_key = None
# 更新:
# 更新:
# 尝试最近7天的路径 # 尝试最近7天的路径
# 更新:
from datetime import timedelta from datetime import timedelta
# 更新:
for i in range(7): for i in range(7):
# 更新:
date = datetime.now() - timedelta(days=i) date = datetime.now() - timedelta(days=i)
# 更新:
year = date.strftime("%Y") year = date.strftime("%Y")
# 更新:
month = date.strftime("%m") month = date.strftime("%m")
# 更新:
day = date.strftime("%d") day = date.strftime("%d")
# 更新:
# 更新:
for ext in temp_extensions: for ext in temp_extensions:
# 更新:
try: try:
# 更新:
temp_oss_key = f"temp/{year}/{month}/{day}/{temp_id}.{ext}" temp_oss_key = f"temp/{year}/{month}/{day}/{temp_id}.{ext}"
# 更新:
import oss2 import oss2
# 更新:
auth = oss2.Auth(OSS_CONFIG["access_key_id"], OSS_CONFIG["access_key_secret"]) auth = oss2.Auth(OSS_CONFIG["access_key_id"], OSS_CONFIG["access_key_secret"])
# 更新:
bucket = oss2.Bucket(auth, OSS_CONFIG["endpoint"], OSS_CONFIG["bucket_name"]) bucket = oss2.Bucket(auth, OSS_CONFIG["endpoint"], OSS_CONFIG["bucket_name"])
# 更新:
temp_content = bucket.get_object(temp_oss_key).read() temp_content = bucket.get_object(temp_oss_key).read()
# 更新:
found_key = temp_oss_key found_key = temp_oss_key
# 更新:
break break
# 更新:
except: except:
# 更新:
continue continue
# 更新:
if temp_content: if temp_content:
# 更新:
break break
# 更新:
# 更新:
if temp_content: if temp_content:
# 更新:
# 上传到正式目录 # 上传到正式目录
# 更新:
bucket.put_object(oss_key, temp_content) bucket.put_object(oss_key, temp_content)
# 更新:
# 更新:
# 删除临时图片 # 删除临时图片
# 更新:
try: try:
# 更新:
bucket.delete_object(found_key) bucket.delete_object(found_key)
# 更新:
except: except:
# 更新:
pass pass
# 更新:
# 更新:
# OSS URL # OSS URL
# 更新:
image_path = f"{OSS_CONFIG['public_url']}/{oss_key}" image_path = f"{OSS_CONFIG['public_url']}/{oss_key}"
# 更新:
# 更新:
else: else:
# 更新:
# OSS失败使用本地文件 # OSS失败使用本地文件
# 更新:
temp_path = None temp_path = None
# 更新:
for ext in temp_extensions: for ext in temp_extensions:
# 更新:
temp_path = os.path.join(UPLOAD_DIR, f"{temp_id}.{ext}") temp_path = os.path.join(UPLOAD_DIR, f"{temp_id}.{ext}")
# 更新:
if os.path.exists(temp_path): if os.path.exists(temp_path):
# 更新:
break break
# 更新:
# 更新:
if not temp_path or not os.path.exists(temp_path): if not temp_path or not os.path.exists(temp_path):
# 更新:
raise HTTPException(status_code=404, detail="临时图片不存在或已过期") raise HTTPException(status_code=404, detail="临时图片不存在或已过期")
# 更新:
# 更新:
# 保存到本地 # 保存到本地
# 更新:
collection_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "uploads", "collections") collection_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "uploads", "collections")
# 更新:
os.makedirs(collection_dir, exist_ok=True) os.makedirs(collection_dir, exist_ok=True)
# 更新:
# 更新:
new_path = os.path.join(collection_dir, final_filename) new_path = os.path.join(collection_dir, final_filename)
# 更新:
import shutil import shutil
# 更新:
shutil.move(temp_path, new_path) shutil.move(temp_path, new_path)
# 更新:
image_path = f"uploads/collections/{final_filename}" image_path = f"uploads/collections/{final_filename}"
# 更新:
# 更新:
# 创建图片记录 # 创建图片记录
# 更新:
image_record = CollectionImage( image_record = CollectionImage(
# 更新:
id=str(uuid.uuid4()), id=str(uuid.uuid4()),
# 更新:
collection_id=collection.f99_90_id, collection_id=collection.f99_90_id,
# 更新:
filename=final_filename, filename=final_filename,
# 更新:
original_name=temp_id, original_name=temp_id,
# 更新:
path=image_path path=image_path
# 更新:
) )
# 更新:
db.add(image_record) db.add(image_record)
# 更新:
db.commit() db.commit()
# 更新:
# 更新:
return { return {
# 更新:
"success": True, "success": True,
# 更新:
"image": { "image": {
# 更新:
"id": image_record.id, "id": image_record.id,
# 更新:
"filename": image_record.filename, "filename": image_record.filename,
# 更新:
"path": image_record.path "path": image_record.path
# 更新:
} }
# 更新:
} }
# 更新:

View File

@ -1,104 +1,210 @@
# 操作路由 # operations - 运营操作路由
# Version: 1.2.70
# 更新:
from typing import List, Optional from typing import List, Optional
# 更新:
from fastapi import APIRouter, Depends, HTTPException, status, Query from fastapi import APIRouter, Depends, HTTPException, status, Query
# 更新:
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
# 更新:
from app.models.models import User, Collection, Operation from app.models.models import User, Collection, Operation
# 更新:
from app.schemas.schemas import OperationCreate, OperationResponse from app.schemas.schemas import OperationCreate, OperationResponse
# 更新:
# 更新:
router = APIRouter(prefix="/api", tags=["操作"]) router = APIRouter(prefix="/api", tags=["操作"])
# 更新:
# 更新:
# 更新:
@router.get("/operations", response_model=List[OperationResponse]) @router.get("/operations", response_model=List[OperationResponse])
# 更新:
def get_operations( def get_operations(
# 更新:
collection_id: Optional[str] = None, collection_id: Optional[str] = None,
# 更新:
page: int = Query(1, ge=1), page: int = Query(1, ge=1),
# 更新:
limit: int = Query(50, ge=1, le=100), limit: int = Query(50, ge=1, le=100),
# 更新:
current_user: User = Depends(get_current_user), current_user: User = Depends(get_current_user),
# 更新:
db: Session = Depends(get_db) db: Session = Depends(get_db)
# 更新:
): ):
# 更新:
"""获取操作历史""" """获取操作历史"""
# 更新:
query = db.query(Operation).filter(Operation.user_id == current_user.id) query = db.query(Operation).filter(Operation.user_id == current_user.id)
# 更新:
# 更新:
if collection_id: if collection_id:
# 更新:
query = query.filter(Operation.collection_id == collection_id) query = query.filter(Operation.collection_id == collection_id)
# 更新:
# 更新:
operations = query.order_by(Operation.created_at.desc()) \ operations = query.order_by(Operation.created_at.desc()) \
# 更新:
.offset((page - 1) * limit) \ .offset((page - 1) * limit) \
# 更新:
.limit(limit) \ .limit(limit) \
# 更新:
.all() .all()
# 更新:
# 更新:
return operations return operations
# 更新:
# 更新:
# 更新:
@router.get("/operations/history") @router.get("/operations/history")
# 更新:
def get_operation_history( def get_operation_history(
# 更新:
collection_id: Optional[str] = None, collection_id: Optional[str] = None,
# 更新:
type: Optional[str] = None, type: Optional[str] = None,
# 更新:
start_date: Optional[str] = None, start_date: Optional[str] = None,
# 更新:
end_date: Optional[str] = None, end_date: Optional[str] = None,
# 更新:
page: int = Query(1, ge=1), page: int = Query(1, ge=1),
# 更新:
limit: int = Query(50, ge=1, le=100), limit: int = Query(50, ge=1, le=100),
# 更新:
current_user: User = Depends(get_current_user), current_user: User = Depends(get_current_user),
# 更新:
db: Session = Depends(get_db) db: Session = Depends(get_db)
# 更新:
): ):
# 更新:
"""获取操作历史(带统计)""" """获取操作历史(带统计)"""
# 更新:
query = db.query(Operation).filter(Operation.user_id == current_user.id) query = db.query(Operation).filter(Operation.user_id == current_user.id)
# 更新:
# 更新:
if collection_id: if collection_id:
# 更新:
query = query.filter(Operation.collection_id == collection_id) query = query.filter(Operation.collection_id == collection_id)
# 更新:
if type: if type:
# 更新:
query = query.filter(Operation.type == type) query = query.filter(Operation.type == type)
# 更新:
if start_date: if start_date:
# 更新:
query = query.filter(Operation.created_at >= start_date) query = query.filter(Operation.created_at >= start_date)
# 更新:
if end_date: if end_date:
# 更新:
query = query.filter(Operation.created_at <= end_date) query = query.filter(Operation.created_at <= end_date)
# 更新:
# 更新:
total = query.count() total = query.count()
# 更新:
# 更新:
data = query.order_by(Operation.created_at.desc()) \ data = query.order_by(Operation.created_at.desc()) \
# 更新:
.offset((page - 1) * limit) \ .offset((page - 1) * limit) \
# 更新:
.limit(limit) \ .limit(limit) \
# 更新:
.all() .all()
# 更新:
# 更新:
return { return {
# 更新:
"data": data, "data": data,
# 更新:
"pagination": { "pagination": {
# 更新:
"page": page, "page": page,
# 更新:
"limit": limit, "limit": limit,
# 更新:
"total": total, "total": total,
# 更新:
"pages": (total + limit - 1) // limit "pages": (total + limit - 1) // limit
# 更新:
} }
# 更新:
} }
# 更新:
# 更新:
# 更新:
@router.post("/operations", response_model=OperationResponse) @router.post("/operations", response_model=OperationResponse)
# 更新:
def create_operation( def create_operation(
# 更新:
operation_data: OperationCreate, operation_data: OperationCreate,
# 更新:
current_user: User = Depends(get_current_user), current_user: User = Depends(get_current_user),
# 更新:
db: Session = Depends(get_db) db: Session = Depends(get_db)
# 更新:
): ):
# 更新:
"""创建操作记录""" """创建操作记录"""
# 更新:
# 验证藏品存在 # 验证藏品存在
# 更新:
collection = db.query(Collection).filter( collection = db.query(Collection).filter(
# 更新:
Collection.id == operation_data.collection_id, Collection.id == operation_data.collection_id,
# 更新:
Collection.user_id == current_user.id Collection.user_id == current_user.id
# 更新:
).first() ).first()
# 更新:
# 更新:
if not collection: if not collection:
# 更新:
raise HTTPException(status_code=404, detail="藏品不存在") raise HTTPException(status_code=404, detail="藏品不存在")
# 更新:
# 更新:
operation = Operation( operation = Operation(
# 更新:
collection_id=operation_data.collection_id, collection_id=operation_data.collection_id,
# 更新:
user_id=current_user.id, user_id=current_user.id,
# 更新:
type=operation_data.type, type=operation_data.type,
# 更新:
price=operation_data.price, price=operation_data.price,
# 更新:
note=operation_data.note note=operation_data.note
# 更新:
) )
# 更新:
# 更新:
db.add(operation) db.add(operation)
# 更新:
db.commit() db.commit()
# 更新:
db.refresh(operation) db.refresh(operation)
# 更新:
# 更新:
return operation return operation
# 更新:

View File

@ -1,184 +1,384 @@
# seek - 寻号匹配路由
# Version: 1.2.70
# 更新:
from fastapi import APIRouter, Depends, Query, HTTPException from fastapi import APIRouter, Depends, Query, HTTPException
# 更新:
# Version: 1.2.x
# 更新:
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
# 更新:
from pydantic import BaseModel from pydantic import BaseModel
# 更新:
from typing import Optional from typing import Optional
# 更新:
from datetime import datetime from datetime import datetime
# 更新:
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
# 更新:
from app.models.seek_info import SeekInfo from app.models.seek_info import SeekInfo
# 更新:
# 更新:
router = APIRouter(prefix="/api/seek", tags=["寻配号"]) router = APIRouter(prefix="/api/seek", tags=["寻配号"])
# 更新:
# 更新:
# ============ Schema ============ # ============ Schema ============
# 更新:
class SeekInfoCreate(BaseModel): class SeekInfoCreate(BaseModel):
# 更新:
title: str title: str
# 更新:
content: Optional[str] = None content: Optional[str] = None
# 更新:
expect_category: Optional[str] = None expect_category: Optional[str] = None
# 更新:
expect_version: Optional[str] = None expect_version: Optional[str] = None
# 更新:
expect_packaging: Optional[str] = None expect_packaging: Optional[str] = None
# 更新:
expect_number: Optional[str] = None expect_number: Optional[str] = None
# 更新:
expect_price_min: Optional[float] = None expect_price_min: Optional[float] = None
# 更新:
expect_price_max: Optional[float] = None expect_price_max: Optional[float] = None
# 更新:
# 更新:
class SeekInfoUpdate(BaseModel): class SeekInfoUpdate(BaseModel):
# 更新:
title: Optional[str] = None title: Optional[str] = None
# 更新:
content: Optional[str] = None content: Optional[str] = None
# 更新:
expect_category: Optional[str] = None expect_category: Optional[str] = None
# 更新:
expect_version: Optional[str] = None expect_version: Optional[str] = None
# 更新:
expect_packaging: Optional[str] = None expect_packaging: Optional[str] = None
# 更新:
expect_number: Optional[str] = None expect_number: Optional[str] = None
# 更新:
expect_price_min: Optional[float] = None expect_price_min: Optional[float] = None
# 更新:
expect_price_max: Optional[float] = None expect_price_max: Optional[float] = None
# 更新:
status: Optional[str] = None status: Optional[str] = None
# 更新:
# 更新:
class SeekInfoResponse(BaseModel): class SeekInfoResponse(BaseModel):
# 更新:
id: str id: str
# 更新:
user_id: str user_id: str
# 更新:
title: str title: str
# 更新:
content: Optional[str] content: Optional[str]
# 更新:
expect_category: Optional[str] expect_category: Optional[str]
# 更新:
expect_version: Optional[str] expect_version: Optional[str]
# 更新:
expect_packaging: Optional[str] expect_packaging: Optional[str]
# 更新:
expect_number: Optional[str] expect_number: Optional[str]
# 更新:
expect_price_min: Optional[float] expect_price_min: Optional[float]
# 更新:
expect_price_max: Optional[float] expect_price_max: Optional[float]
# 更新:
status: str status: str
# 更新:
is_matched: Optional[str] is_matched: Optional[str]
# 更新:
matched_user_id: Optional[str] matched_user_id: Optional[str]
# 更新:
matched_contact: Optional[str] matched_contact: Optional[str]
# 更新:
view_count: int view_count: int
# 更新:
contact_count: int contact_count: int
# 更新:
created_at: Optional[datetime] created_at: Optional[datetime]
# 更新:
updated_at: Optional[datetime] updated_at: Optional[datetime]
# 更新:
# 更新:
class Config: class Config:
# 更新:
from_attributes = True from_attributes = True
# 更新:
# 更新:
# ============ API ============ # ============ API ============
# 更新:
@router.get("/list", response_model=list[SeekInfoResponse]) @router.get("/list", response_model=list[SeekInfoResponse])
# 更新:
def get_seek_list( def get_seek_list(
# 更新:
status: str = Query("active"), status: str = Query("active"),
# 更新:
page: int = Query(1, ge=1), page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=100), # 更新:
page_size: int = Query(20, ge=1, le=1000),
# 更新:
user_only: bool = Query(False),
# 更新:
current_user: Optional = Depends(get_current_user), current_user: Optional = Depends(get_current_user),
# 更新:
db: Session = Depends(get_db) db: Session = Depends(get_db)
# 更新:
): ):
# 更新:
"""获取寻配号列表""" """获取寻配号列表"""
# 更新:
query = db.query(SeekInfo).filter(SeekInfo.status == status) 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()) query = query.order_by(SeekInfo.created_at.desc())
# 更新:
# 更新:
# 分页 # 分页
# 更新:
offset = (page - 1) * page_size offset = (page - 1) * page_size
# 更新:
items = query.offset(offset).limit(page_size).all() items = query.offset(offset).limit(page_size).all()
# 更新:
# 更新:
return items return items
# 更新:
# 更新:
@router.get("/stats") @router.get("/stats")
# 更新:
def get_seek_stats( def get_seek_stats(
# 更新:
current_user = Depends(get_current_user), current_user = Depends(get_current_user),
# 更新:
db: Session = Depends(get_db) db: Session = Depends(get_db)
# 更新:
): ):
# 更新:
"""获取寻配号统计""" """获取寻配号统计"""
# 更新:
total = db.query(SeekInfo).filter(SeekInfo.status == "active").count() total = db.query(SeekInfo).filter(SeekInfo.status == "active").count()
# 更新:
matched = db.query(SeekInfo).filter(SeekInfo.is_matched == "true").count() matched = db.query(SeekInfo).filter(SeekInfo.is_matched == "true").count()
# 更新:
# 更新:
return { return {
# 更新:
"total": total, "total": total,
# 更新:
"matched": matched, "matched": matched,
# 更新:
"unmatched": total - matched "unmatched": total - matched
# 更新:
} }
# 更新:
# 更新:
@router.post("", response_model=SeekInfoResponse) @router.post("", response_model=SeekInfoResponse)
# 更新:
def create_seek( def create_seek(
# 更新:
data: SeekInfoCreate, data: SeekInfoCreate,
# 更新:
current_user = Depends(get_current_user), current_user = Depends(get_current_user),
# 更新:
db: Session = Depends(get_db) db: Session = Depends(get_db)
# 更新:
): ):
# 更新:
"""创建寻配号""" """创建寻配号"""
# 更新:
if not current_user: if not current_user:
# 更新:
raise HTTPException(status_code=401, detail="请先登录") raise HTTPException(status_code=401, detail="请先登录")
# 更新:
# 更新:
seek = SeekInfo( seek = SeekInfo(
# 更新:
user_id=current_user.f99_90_id, user_id=current_user.f99_90_id,
# 更新:
title=data.title, title=data.title,
# 更新:
content=data.content, content=data.content,
# 更新:
expect_category=data.expect_category, expect_category=data.expect_category,
# 更新:
expect_version=data.expect_version, expect_version=data.expect_version,
# 更新:
expect_packaging=data.expect_packaging, expect_packaging=data.expect_packaging,
# 更新:
expect_number=data.expect_number, expect_number=data.expect_number,
# 更新:
expect_price_min=data.expect_price_min, expect_price_min=data.expect_price_min,
# 更新:
expect_price_max=data.expect_price_max, expect_price_max=data.expect_price_max,
# 更新:
status="active" status="active"
# 更新:
) )
# 更新:
db.add(seek) db.add(seek)
# 更新:
db.commit() db.commit()
# 更新:
db.refresh(seek) db.refresh(seek)
# 更新:
return seek return seek
# 更新:
# 更新:
@router.get("/{seek_id}", response_model=SeekInfoResponse) @router.get("/{seek_id}", response_model=SeekInfoResponse)
# 更新:
def get_seek( def get_seek(
# 更新:
seek_id: str, seek_id: str,
# 更新:
current_user = Depends(get_current_user), current_user = Depends(get_current_user),
# 更新:
db: Session = Depends(get_db) db: Session = Depends(get_db)
# 更新:
): ):
# 更新:
"""获取寻配号详情""" """获取寻配号详情"""
# 更新:
seek = db.query(SeekInfo).filter(SeekInfo.id == seek_id).first() seek = db.query(SeekInfo).filter(SeekInfo.id == seek_id).first()
# 更新:
if not seek: if not seek:
# 更新:
raise HTTPException(status_code=404, detail="寻配号不存在") raise HTTPException(status_code=404, detail="寻配号不存在")
# 更新:
# 更新:
# 增加浏览数 # 增加浏览数
# 更新:
seek.view_count += 1 seek.view_count += 1
# 更新:
db.commit() db.commit()
# 更新:
# 更新:
return seek return seek
# 更新:
# 更新:
@router.put("/{seek_id}", response_model=SeekInfoResponse) @router.put("/{seek_id}", response_model=SeekInfoResponse)
# 更新:
def update_seek( def update_seek(
# 更新:
seek_id: str, seek_id: str,
# 更新:
data: SeekInfoUpdate, data: SeekInfoUpdate,
# 更新:
current_user = Depends(get_current_user), current_user = Depends(get_current_user),
# 更新:
db: Session = Depends(get_db) db: Session = Depends(get_db)
# 更新:
): ):
# 更新:
"""更新寻配号""" """更新寻配号"""
# 更新:
if not current_user: if not current_user:
# 更新:
raise HTTPException(status_code=401, detail="请先登录") raise HTTPException(status_code=401, detail="请先登录")
# 更新:
# 更新:
seek = db.query(SeekInfo).filter( seek = db.query(SeekInfo).filter(
# 更新:
SeekInfo.id == seek_id, SeekInfo.id == seek_id,
# 更新:
SeekInfo.user_id == current_user.f99_90_id SeekInfo.user_id == current_user.f99_90_id
# 更新:
).first() ).first()
# 更新:
# 更新:
if not seek: if not seek:
# 更新:
raise HTTPException(status_code=404, detail="寻配号不存在") raise HTTPException(status_code=404, detail="寻配号不存在")
# 更新:
# 更新:
for key, value in data.model_dump(exclude_unset=True).items(): for key, value in data.model_dump(exclude_unset=True).items():
# 更新:
setattr(seek, key, value) setattr(seek, key, value)
# 更新:
# 更新:
db.commit() db.commit()
# 更新:
db.refresh(seek) db.refresh(seek)
# 更新:
return seek return seek
# 更新:
# 更新:
@router.delete("/{seek_id}") @router.delete("/{seek_id}")
# 更新:
def delete_seek( def delete_seek(
# 更新:
seek_id: str, seek_id: str,
# 更新:
current_user = Depends(get_current_user), current_user = Depends(get_current_user),
# 更新:
db: Session = Depends(get_db) db: Session = Depends(get_db)
# 更新:
): ):
# 更新:
"""删除寻配号""" """删除寻配号"""
# 更新:
if not current_user: if not current_user:
# 更新:
raise HTTPException(status_code=401, detail="请先登录") raise HTTPException(status_code=401, detail="请先登录")
# 更新:
# 更新:
seek = db.query(SeekInfo).filter( seek = db.query(SeekInfo).filter(
# 更新:
SeekInfo.id == seek_id, SeekInfo.id == seek_id,
# 更新:
SeekInfo.user_id == current_user.f99_90_id SeekInfo.user_id == current_user.f99_90_id
# 更新:
).first() ).first()
# 更新:
# 更新:
if not seek: if not seek:
# 更新:
raise HTTPException(status_code=404, detail="寻配号不存在") raise HTTPException(status_code=404, detail="寻配号不存在")
# 更新:
# 更新:
seek.status = "deleted" seek.status = "deleted"
# 更新:
db.commit() db.commit()
# 更新:
return {"message": "删除成功"} # 更新:
return {"message": "删除成功"}
# 更新:

View File

@ -1,4 +1,7 @@
# 用户管理路由 # users.py - 用户管理路由
# Version: 1.2.98 (2026-04-19)
# 更新:新增 dealCount 字段,从 Information 表统计用户发布的行情数量
from typing import Optional from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, status, Query, Body from fastapi import APIRouter, Depends, HTTPException, status, Query, Body
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
@ -114,6 +117,11 @@ def get_users(
for u in users: for u in users:
# 统计每个用户的藏品数量 # 统计每个用户的藏品数量
count = db.query(Collection).filter(Collection.f99_91_user_id == u.f99_90_id).count() count = db.query(Collection).filter(Collection.f99_91_user_id == u.f99_90_id).count()
# 统计每个用户发布的行情数量info_type='deal'
deal_count = db.query(Information).filter(
Information.user_id == u.f99_90_id,
Information.info_type == "deal"
).count()
user_list.append({ user_list.append({
"id": u.f99_90_id, "id": u.f99_90_id,
"username": u.f01_01_name, "username": u.f01_01_name,
@ -123,6 +131,7 @@ def get_users(
"user_code": u.user_code, "user_code": u.user_code,
"created_at": u.f99_92_created_at.isoformat() if u.f99_92_created_at else None, "created_at": u.f99_92_created_at.isoformat() if u.f99_92_created_at else None,
"collectionCount": count, "collectionCount": count,
"dealCount": deal_count,
"level": u.f99_94_level, "level": u.f99_94_level,
"aiCount": u.f99_95_ai_count, "aiCount": u.f99_95_ai_count,
"searchCount": u.f99_96_search_count, "searchCount": u.f99_96_search_count,

View File

@ -1,377 +1,760 @@
# yichens - 一尘数据路由
# Version: 1.2.70
# 更新:
from fastapi import APIRouter, Depends, Query from fastapi import APIRouter, Depends, Query
# 更新:
# Version: 1.2.x
# 更新:
from sqlalchemy import func, text from sqlalchemy import func, text
# 更新:
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
# 更新:
from pydantic import BaseModel from pydantic import BaseModel
# 更新:
from typing import Optional, List from typing import Optional, List
# 更新:
from datetime import datetime, date from datetime import datetime, date
# 更新:
from app.core.coolbot_db import get_coolbot_db from app.core.coolbot_db import get_coolbot_db
# 更新:
# 更新:
router = APIRouter(prefix="/api/yichens", tags=["一尘看板"]) router = APIRouter(prefix="/api/yichens", tags=["一尘看板"])
# 更新:
# 更新:
# ============ 数据模型 ============ # ============ 数据模型 ============
# 更新:
class YichensPostStats(BaseModel): class YichensPostStats(BaseModel):
# 更新:
total_posts: int total_posts: int
# 更新:
total_deals: int # 出售 total_deals: int # 出售
# 更新:
total_wants: int # 求购 total_wants: int # 求购
# 更新:
total_replies: int total_replies: int
# 更新:
total_views: int total_views: int
# 更新:
avg_price: Optional[float] avg_price: Optional[float]
# 更新:
# 更新:
class CategoryStat(BaseModel): class CategoryStat(BaseModel):
# 更新:
category: str category: str
# 更新:
count: int count: int
# 更新:
# 更新:
class PostItem(BaseModel): class PostItem(BaseModel):
# 更新:
post_id: str post_id: str
# 更新:
title: str title: str
# 更新:
category: Optional[str] category: Optional[str]
# 更新:
post_type: str post_type: str
# 更新:
price: Optional[float] price: Optional[float]
# 更新:
author_username: str author_username: str
# 更新:
post_time: str post_time: str
# 更新:
reply_count: int reply_count: int
# 更新:
view_count: int view_count: int
# 更新:
url: Optional[str] url: Optional[str]
# 更新:
content: Optional[str] content: Optional[str]
# 更新:
# 更新:
class UserStat(BaseModel): class UserStat(BaseModel):
# 更新:
total_users: int total_users: int
# 更新:
new_users_today: int new_users_today: int
# 更新:
sellers: int sellers: int
# 更新:
# 更新:
class UserItem(BaseModel): class UserItem(BaseModel):
# 更新:
user_id: str user_id: str
# 更新:
username: str username: str
# 更新:
avatar_url: Optional[str] avatar_url: Optional[str]
# 更新:
content: Optional[str] content: Optional[str]
# 更新:
credit_level: Optional[str] credit_level: Optional[str]
# 更新:
credit_score: Optional[int] credit_score: Optional[int]
# 更新:
post_count: int post_count: int
# 更新:
is_seller: bool is_seller: bool
# 更新:
registration_date: Optional[str] registration_date: Optional[str]
# 更新:
# 更新:
# ============ 统计接口 ============ # ============ 统计接口 ============
# 更新:
@router.get("/stats/posts", response_model=YichensPostStats) @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)): def get_post_stats(days: int = Query(30, ge=1, le=365), db: Session = Depends(get_coolbot_db)):
# 更新:
"""获取帖子统计""" """获取帖子统计"""
# 更新:
result = db.execute(text(""" result = db.execute(text("""
# 更新:
SELECT SELECT
# 更新:
COUNT(*) as total_posts, COUNT(*) as total_posts,
# 更新:
COUNT(*) FILTER (WHERE post_type = 'deal') as total_deals, COUNT(*) FILTER (WHERE post_type = 'deal') as total_deals,
# 更新:
COUNT(*) FILTER (WHERE post_type = 'want') as total_wants, COUNT(*) FILTER (WHERE post_type = 'want') as total_wants,
# 更新:
COALESCE(SUM(reply_count), 0) as total_replies, COALESCE(SUM(reply_count), 0) as total_replies,
# 更新:
COALESCE(SUM(view_count), 0) as total_views, COALESCE(SUM(view_count), 0) as total_views,
# 更新:
AVG(price) as avg_price AVG(price) as avg_price
# 更新:
FROM yichens_posts FROM yichens_posts
# 更新:
WHERE post_time >= NOW() - INTERVAL '1 day' * :days WHERE post_time >= NOW() - INTERVAL '1 day' * :days
# 更新:
"""), {"days": days}).fetchone() """), {"days": days}).fetchone()
# 更新:
# 更新:
return YichensPostStats( return YichensPostStats(
# 更新:
total_posts=result[0] or 0, total_posts=result[0] or 0,
# 更新:
total_deals=result[1] or 0, total_deals=result[1] or 0,
# 更新:
total_wants=result[2] or 0, total_wants=result[2] or 0,
# 更新:
total_replies=result[3] or 0, total_replies=result[3] or 0,
# 更新:
total_views=result[4] or 0, total_views=result[4] or 0,
# 更新:
avg_price=float(result[5]) if result[5] else None avg_price=float(result[5]) if result[5] else None
# 更新:
) )
# 更新:
# 更新:
@router.get("/stats/categories", response_model=List[CategoryStat]) @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)): def get_category_stats(days: int = Query(30, ge=1, le=365), db: Session = Depends(get_coolbot_db)):
# 更新:
"""按分类统计帖子数量""" """按分类统计帖子数量"""
# 更新:
results = db.execute(text(""" results = db.execute(text("""
# 更新:
SELECT category, COUNT(*) as count SELECT category, COUNT(*) as count
# 更新:
FROM yichens_posts FROM yichens_posts
# 更新:
WHERE post_time >= NOW() - INTERVAL '1 day' * :days WHERE post_time >= NOW() - INTERVAL '1 day' * :days
# 更新:
GROUP BY category GROUP BY category
# 更新:
ORDER BY count DESC ORDER BY count DESC
# 更新:
"""), {"days": days}).fetchall() """), {"days": days}).fetchall()
# 更新:
# 更新:
return [CategoryStat(category=r[0] or "未知", count=r[1]) for r in results] return [CategoryStat(category=r[0] or "未知", count=r[1]) for r in results]
# 更新:
# 更新:
@router.get("/stats/users", response_model=UserStat) @router.get("/stats/users", response_model=UserStat)
# 更新:
def get_user_stats(db: Session = Depends(get_coolbot_db)): def get_user_stats(db: Session = Depends(get_coolbot_db)):
# 更新:
"""获取用户统计""" """获取用户统计"""
# 更新:
result = db.execute(text(""" result = db.execute(text("""
# 更新:
SELECT SELECT
# 更新:
COUNT(*) as total_users, COUNT(*) as total_users,
# 更新:
COUNT(*) FILTER (WHERE created_at::date = CURRENT_DATE) as new_users_today, COUNT(*) FILTER (WHERE created_at::date = CURRENT_DATE) as new_users_today,
# 更新:
COUNT(*) FILTER (WHERE is_seller = true) as sellers COUNT(*) FILTER (WHERE is_seller = true) as sellers
# 更新:
FROM yichens_users FROM yichens_users
# 更新:
""")).fetchone() """)).fetchone()
# 更新:
# 更新:
return UserStat( return UserStat(
# 更新:
total_users=result[0] or 0, total_users=result[0] or 0,
# 更新:
new_users_today=result[1] or 0, new_users_today=result[1] or 0,
# 更新:
sellers=result[2] or 0 sellers=result[2] or 0
# 更新:
) )
# 更新:
# 更新:
@router.get("/posts") @router.get("/posts")
# 更新:
def get_posts( def get_posts(
# 更新:
limit: int = Query(20, ge=1, le=500), limit: int = Query(20, ge=1, le=500),
# 更新:
offset: int = Query(0, ge=0), offset: int = Query(0, ge=0),
# 更新:
category: Optional[str] = None, category: Optional[str] = None,
# 更新:
post_type: Optional[str] = None, post_type: Optional[str] = None,
# 更新:
keyword: Optional[str] = None, keyword: Optional[str] = None,
# 更新:
db: Session = Depends(get_coolbot_db) db: Session = Depends(get_coolbot_db)
# 更新:
): ):
# 更新:
"""获取帖子列表 - 支持全局搜索,返回总数和分页信息""" """获取帖子列表 - 支持全局搜索,返回总数和分页信息"""
# 更新:
# 构建WHERE条件 # 构建WHERE条件
# 更新:
where_clauses = ["1=1"] where_clauses = ["1=1"]
# 更新:
params = {"limit": limit, "offset": offset} params = {"limit": limit, "offset": offset}
# 更新:
# 更新:
if category: if category:
# 更新:
where_clauses.append("category = :category") where_clauses.append("category = :category")
# 更新:
params["category"] = category params["category"] = category
# 更新:
# 更新:
if post_type: if post_type:
# 更新:
where_clauses.append("post_type = :post_type") where_clauses.append("post_type = :post_type")
# 更新:
params["post_type"] = post_type params["post_type"] = post_type
# 更新:
# 更新:
# 全局搜索 # 全局搜索
# 更新:
if keyword: if keyword:
# 更新:
where_clauses.append("(title ILIKE :keyword OR content ILIKE :keyword OR category ILIKE :keyword OR author_username ILIKE :keyword)") where_clauses.append("(title ILIKE :keyword OR content ILIKE :keyword OR category ILIKE :keyword OR author_username ILIKE :keyword)")
# 更新:
params["keyword"] = f"%{keyword}%" params["keyword"] = f"%{keyword}%"
# 更新:
# 更新:
where_sql = " AND ".join(where_clauses) where_sql = " AND ".join(where_clauses)
# 更新:
# 更新:
# 查询总数 # 查询总数
# 更新:
count_query = f"SELECT COUNT(*) FROM yichens_posts WHERE {where_sql}" count_query = f"SELECT COUNT(*) FROM yichens_posts WHERE {where_sql}"
# 更新:
total_count = db.execute(text(count_query), {k: v for k, v in params.items() if k not in ['limit', 'offset']}).scalar() or 0 total_count = db.execute(text(count_query), {k: v for k, v in params.items() if k not in ['limit', 'offset']}).scalar() or 0
# 更新:
# 查询数据 # 更新:
# 查询数据 - 有post_time时按post_time排序没有时按crawled_at排序
# 更新:
data_query = f""" data_query = f"""
# 更新:
SELECT post_id, title, content, category, post_type, price, SELECT post_id, title, content, category, post_type, price,
# 更新:
author_username, post_time, reply_count, view_count, url author_username, post_time, reply_count, view_count, url
# 更新:
FROM yichens_posts FROM yichens_posts
# 更新:
WHERE {where_sql} WHERE {where_sql}
ORDER BY post_time DESC LIMIT :limit OFFSET :offset # 更新:
ORDER BY COALESCE(post_time, crawled_at) DESC LIMIT :limit OFFSET :offset
# 更新:
""" """
# 更新:
results = db.execute(text(data_query), params).fetchall() results = db.execute(text(data_query), params).fetchall()
# 更新:
# 更新:
posts = [PostItem( posts = [PostItem(
# 更新:
post_id=r[0], post_id=r[0],
# 更新:
title=r[1] or "", title=r[1] or "",
# 更新:
content=r[2] or "", content=r[2] or "",
# 更新:
category=r[3], category=r[3],
# 更新:
post_type=r[4] or "", post_type=r[4] or "",
# 更新:
price=float(r[5]) if r[5] else None, price=float(r[5]) if r[5] else None,
# 更新:
author_username=r[6] or "", author_username=r[6] or "",
# 更新:
post_time=str(r[7]) if r[7] else "", post_time=str(r[7]) if r[7] else "",
# 更新:
reply_count=r[8] or 0, reply_count=r[8] or 0,
# 更新:
view_count=r[9] or 0, view_count=r[9] or 0,
# 更新:
url=r[10] url=r[10]
# 更新:
) for r in results] ) for r in results]
# 更新:
# 更新:
return { return {
# 更新:
"posts": posts, "posts": posts,
# 更新:
"total": total_count, "total": total_count,
# 更新:
"page": offset // limit + 1, "page": offset // limit + 1,
# 更新:
"page_size": limit "page_size": limit
# 更新:
} }
# 更新:
# 更新:
@router.get("/users", response_model=List[UserItem]) @router.get("/users", response_model=List[UserItem])
# 更新:
def get_users( def get_users(
# 更新:
limit: int = Query(20, ge=1, le=500), limit: int = Query(20, ge=1, le=500),
# 更新:
offset: int = Query(0, ge=0), offset: int = Query(0, ge=0),
# 更新:
is_seller: Optional[bool] = None, is_seller: Optional[bool] = None,
# 更新:
db: Session = Depends(get_coolbot_db) db: Session = Depends(get_coolbot_db)
# 更新:
): ):
# 更新:
"""获取用户列表""" """获取用户列表"""
# 更新:
query = """ query = """
# 更新:
SELECT user_id, username, avatar_url, credit_level, credit_score, SELECT user_id, username, avatar_url, credit_level, credit_score,
# 更新:
post_count, is_seller, registration_date post_count, is_seller, registration_date
# 更新:
FROM yichens_users FROM yichens_users
# 更新:
WHERE 1=1 WHERE 1=1
# 更新:
""" """
# 更新:
params = {"limit": limit, "offset": offset} params = {"limit": limit, "offset": offset}
# 更新:
# 更新:
if is_seller is not None: if is_seller is not None:
# 更新:
query += " AND is_seller = :is_seller" query += " AND is_seller = :is_seller"
# 更新:
params["is_seller"] = is_seller params["is_seller"] = is_seller
# 更新:
# 更新:
query += " ORDER BY post_count DESC LIMIT :limit OFFSET :offset" query += " ORDER BY post_count DESC LIMIT :limit OFFSET :offset"
# 更新:
# 更新:
results = db.execute(text(query), params).fetchall() results = db.execute(text(query), params).fetchall()
# 更新:
# 更新:
return [UserItem( return [UserItem(
# 更新:
user_id=r[0], user_id=r[0],
# 更新:
username=r[1] or "", username=r[1] or "",
# 更新:
avatar_url=r[2], avatar_url=r[2],
# 更新:
credit_level=r[3], credit_level=r[3],
# 更新:
credit_score=r[4], credit_score=r[4],
# 更新:
post_count=r[5] or 0, post_count=r[5] or 0,
# 更新:
is_seller=r[6] or False, is_seller=r[6] or False,
# 更新:
registration_date=str(r[7]) if r[7] else None registration_date=str(r[7]) if r[7] else None
# 更新:
) for r in results] ) for r in results]
# 更新:
# 更新:
# 更新:
@router.get("/stats/today") @router.get("/stats/today")
# 更新:
async def get_today_stats(db: Session = Depends(get_coolbot_db)): async def get_today_stats(db: Session = Depends(get_coolbot_db)):
# 更新:
"""获取今日新增帖子统计""" """获取今日新增帖子统计"""
# 更新:
query = """ query = """
# 更新:
SELECT SELECT
# 更新:
COUNT(*) as total, COUNT(*) as total,
# 更新:
SUM(CASE WHEN post_type = 'deal' THEN 1 ELSE 0 END) as deals, 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 = 'want' THEN 1 ELSE 0 END) as wants,
# 更新:
SUM(CASE WHEN post_type = 'normal' THEN 1 ELSE 0 END) as others, 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 dragons,
# 更新:
SUM(CASE WHEN category LIKE '%%' THEN 1 ELSE 0 END) as horses, 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 category LIKE '%%' THEN 1 ELSE 0 END) as snakes,
# 更新:
SUM(CASE WHEN title LIKE '%天马%' OR content LIKE '%天马%' THEN 1 ELSE 0 END) as tianma SUM(CASE WHEN title LIKE '%天马%' OR content LIKE '%天马%' THEN 1 ELSE 0 END) as tianma
# 更新:
FROM yichens_posts FROM yichens_posts
# 更新:
WHERE post_time >= CURRENT_DATE WHERE post_time >= CURRENT_DATE
# 更新:
""" """
# 更新:
result = db.execute(text(query)).fetchone() result = db.execute(text(query)).fetchone()
# 更新:
return { return {
# 更新:
"total": result[0] or 0, "total": result[0] or 0,
# 更新:
"deals": result[1] or 0, "deals": result[1] or 0,
# 更新:
"wants": result[2] or 0, "wants": result[2] or 0,
# 更新:
"others": result[3] or 0, "others": result[3] or 0,
# 更新:
"dragons": result[4] or 0, "dragons": result[4] or 0,
# 更新:
"horses": result[5] or 0, "horses": result[5] or 0,
# 更新:
"snakes": result[6] or 0, "snakes": result[6] or 0,
# 更新:
"tianma": result[7] or 0 "tianma": result[7] or 0
# 更新:
} }
# 更新:
# 更新:
@router.get("/stats/hour") @router.get("/stats/hour")
# 更新:
async def get_hour_stats(db: Session = Depends(get_coolbot_db)): async def get_hour_stats(db: Session = Depends(get_coolbot_db)):
# 更新:
"""获取近一个小时新增帖子统计""" """获取近一个小时新增帖子统计"""
# 更新:
query = """ query = """
# 更新:
SELECT SELECT
# 更新:
COUNT(*) as total, COUNT(*) as total,
# 更新:
SUM(CASE WHEN post_type = 'deal' THEN 1 ELSE 0 END) as deals, 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 = 'want' THEN 1 ELSE 0 END) as wants,
# 更新:
SUM(CASE WHEN post_type = 'normal' THEN 1 ELSE 0 END) as others, 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 dragons,
# 更新:
SUM(CASE WHEN category LIKE '%%' THEN 1 ELSE 0 END) as horses, 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 category LIKE '%%' THEN 1 ELSE 0 END) as snakes
# 更新:
FROM yichens_posts FROM yichens_posts
# 更新:
WHERE post_time >= NOW() - INTERVAL '1 hour' WHERE post_time >= NOW() - INTERVAL '1 hour'
# 更新:
""" """
# 更新:
result = db.execute(text(query)).fetchone() result = db.execute(text(query)).fetchone()
# 更新:
return { return {
# 更新:
"total": result[0] or 0, "total": result[0] or 0,
# 更新:
"deals": result[1] or 0, "deals": result[1] or 0,
# 更新:
"wants": result[2] or 0, "wants": result[2] or 0,
# 更新:
"others": result[3] or 0, "others": result[3] or 0,
# 更新:
"dragons": result[3] or 0, "dragons": result[3] or 0,
# 更新:
"horses": result[4] or 0, "horses": result[4] or 0,
# 更新:
"snakes": result[5] or 0 "snakes": result[5] or 0
# 更新:
} }
# 更新:
# 更新:
@router.get("/stats/today-category") @router.get("/stats/today-category")
# 更新:
async def get_today_category_stats(db: Session = Depends(get_coolbot_db)): async def get_today_category_stats(db: Session = Depends(get_coolbot_db)):
# 更新:
"""获取今日帖子分类统计""" """获取今日帖子分类统计"""
# 更新:
query = """ query = """
# 更新:
SELECT category, COUNT(*) as count SELECT category, COUNT(*) as count
# 更新:
FROM yichens_posts FROM yichens_posts
# 更新:
WHERE post_time >= CURRENT_DATE WHERE post_time >= CURRENT_DATE
# 更新:
GROUP BY category GROUP BY category
# 更新:
ORDER BY count DESC ORDER BY count DESC
# 更新:
""" """
# 更新:
results = db.execute(text(query)).fetchall() results = db.execute(text(query)).fetchall()
# 更新:
return [{"category": r[0] or "未分类", "count": r[1]} for r in results] return [{"category": r[0] or "未分类", "count": r[1]} for r in results]
# 更新:
# 更新:
# 更新:
@router.get("/stats/dragons-today") @router.get("/stats/dragons-today")
# 更新:
def get_dragons_stats_today( def get_dragons_stats_today(
# 更新:
db: Session = Depends(get_coolbot_db) db: Session = Depends(get_coolbot_db)
# 更新:
): ):
# 更新:
"""获取今日龙钞详细统计数据(按号码分类)- 就高不就低""" """获取今日龙钞详细统计数据(按号码分类)- 就高不就低"""
# 更新:
from sqlalchemy import text from sqlalchemy import text
# 更新:
# 更新:
# 1. 带4包含"带4"、"带四"、"通货" # 1. 带4包含"带4"、"带四"、"通货"
# 更新:
dai4 = db.execute(text(""" dai4 = db.execute(text("""
# 更新:
SELECT SELECT
# 更新:
COUNT(*) as total, COUNT(*) as total,
# 更新:
SUM(CASE WHEN post_type = 'deal' THEN 1 ELSE 0 END) as deals, 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 = 'want' THEN 1 ELSE 0 END) as wants
# 更新:
FROM yichens_posts FROM yichens_posts
# 更新:
WHERE post_time >= CURRENT_DATE WHERE post_time >= CURRENT_DATE
# 更新:
AND category LIKE '%%' AND category LIKE '%%'
# 更新:
AND ( AND (
# 更新:
content LIKE '%带4%' OR title LIKE '%带4%' content LIKE '%带4%' OR title LIKE '%带4%'
# 更新:
OR content LIKE '%带四%' OR title LIKE '%带四%' OR content LIKE '%带四%' OR title LIKE '%带四%'
# 更新:
OR content LIKE '%通货%' OR title LIKE '%通货%' OR content LIKE '%通货%' OR title LIKE '%通货%'
# 更新:
) )
# 更新:
""")).fetchone() """)).fetchone()
# 更新:
# 更新:
# 2. 无4包含"无4"、"无四",排除"无47"、"无247"、"无四七"、"永恒" # 2. 无4包含"无4"、"无四",排除"无47"、"无247"、"无四七"、"永恒"
# 更新:
wu4 = db.execute(text(""" wu4 = db.execute(text("""
# 更新:
SELECT SELECT
# 更新:
COUNT(*) as total, COUNT(*) as total,
# 更新:
SUM(CASE WHEN post_type = 'deal' THEN 1 ELSE 0 END) as deals, 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 = 'want' THEN 1 ELSE 0 END) as wants
# 更新:
FROM yichens_posts FROM yichens_posts
# 更新:
WHERE post_time >= CURRENT_DATE WHERE post_time >= CURRENT_DATE
# 更新:
AND category LIKE '%%' AND category LIKE '%%'
# 更新:
AND ( AND (
# 更新:
content LIKE '%无4%' OR title LIKE '%无4%' content LIKE '%无4%' OR title LIKE '%无4%'
# 更新:
OR content LIKE '%无四%' OR title LIKE '%无四%' OR content LIKE '%无四%' OR title LIKE '%无四%'
# 更新:
) )
# 更新:
AND content NOT LIKE '%无47%' AND title NOT LIKE '%无47%' AND content NOT LIKE '%无47%' AND title NOT LIKE '%无47%'
# 更新:
AND content NOT LIKE '%无247%' AND title NOT LIKE '%无247%' AND content NOT LIKE '%无247%' AND title NOT LIKE '%无247%'
# 更新:
AND content NOT LIKE '%永恒%' AND title NOT LIKE '%永恒%' AND content NOT LIKE '%永恒%' AND title NOT LIKE '%永恒%'
# 更新:
AND content NOT LIKE '%无四七%' AND title NOT LIKE '%无四七%' AND content NOT LIKE '%无四七%' AND title NOT LIKE '%无四七%'
# 更新:
""")).fetchone() """)).fetchone()
# 更新:
# 更新:
# 3. 无47包含"无47"、"永恒"、"无四七",排除"无247" # 3. 无47包含"无47"、"永恒"、"无四七",排除"无247"
# 更新:
wu47 = db.execute(text(""" wu47 = db.execute(text("""
# 更新:
SELECT SELECT
# 更新:
COUNT(*) as total, COUNT(*) as total,
# 更新:
SUM(CASE WHEN post_type = 'deal' THEN 1 ELSE 0 END) as deals, 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 = 'want' THEN 1 ELSE 0 END) as wants
# 更新:
FROM yichens_posts FROM yichens_posts
# 更新:
WHERE post_time >= CURRENT_DATE WHERE post_time >= CURRENT_DATE
# 更新:
AND category LIKE '%%' AND category LIKE '%%'
# 更新:
AND ( AND (
# 更新:
content LIKE '%无47%' OR title LIKE '%无47%' content LIKE '%无47%' OR title LIKE '%无47%'
# 更新:
OR content LIKE '%永恒%' OR title LIKE '%永恒%' OR content LIKE '%永恒%' OR title LIKE '%永恒%'
# 更新:
OR content LIKE '%无四七%' OR title LIKE '%无四七%' OR content LIKE '%无四七%' OR title LIKE '%无四七%'
# 更新:
) )
# 更新:
AND content NOT LIKE '%无247%' AND title NOT LIKE '%无247%' AND content NOT LIKE '%无247%' AND title NOT LIKE '%无247%'
# 更新:
""")).fetchone() """)).fetchone()
# 更新:
# 更新:
# 4. 无247包含"无247"、"天马"、"金山",排除"无347" # 4. 无247包含"无247"、"天马"、"金山",排除"无347"
# 更新:
wu247 = db.execute(text(""" wu247 = db.execute(text("""
# 更新:
SELECT SELECT
# 更新:
COUNT(*) as total, COUNT(*) as total,
# 更新:
SUM(CASE WHEN post_type = 'deal' THEN 1 ELSE 0 END) as deals, 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 = 'want' THEN 1 ELSE 0 END) as wants
# 更新:
FROM yichens_posts FROM yichens_posts
# 更新:
WHERE post_time >= CURRENT_DATE WHERE post_time >= CURRENT_DATE
# 更新:
AND category LIKE '%%' AND category LIKE '%%'
# 更新:
AND ( AND (
# 更新:
content LIKE '%无247%' OR title LIKE '%无247%' content LIKE '%无247%' OR title LIKE '%无247%'
# 更新:
OR content LIKE '%天马%' OR title LIKE '%天马%' OR content LIKE '%天马%' OR title LIKE '%天马%'
# 更新:
OR content LIKE '%金山%' OR title LIKE '%金山%' OR content LIKE '%金山%' OR title LIKE '%金山%'
# 更新:
) )
# 更新:
AND content NOT LIKE '%无347%' AND title NOT LIKE '%无347%' AND content NOT LIKE '%无347%' AND title NOT LIKE '%无347%'
# 更新:
""")).fetchone() """)).fetchone()
# 更新:
# 更新:
# 5. 无347包含"无347"、"钻石"、"金马"、"魅力"、"朦胧" # 5. 无347包含"无347"、"钻石"、"金马"、"魅力"、"朦胧"
# 更新:
wu347 = db.execute(text(""" wu347 = db.execute(text("""
# 更新:
SELECT SELECT
# 更新:
COUNT(*) as total, COUNT(*) as total,
# 更新:
SUM(CASE WHEN post_type = 'deal' THEN 1 ELSE 0 END) as deals, 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 = 'want' THEN 1 ELSE 0 END) as wants
# 更新:
FROM yichens_posts FROM yichens_posts
# 更新:
WHERE post_time >= CURRENT_DATE WHERE post_time >= CURRENT_DATE
# 更新:
AND category LIKE '%%' AND category LIKE '%%'
# 更新:
AND ( AND (
# 更新:
content LIKE '%无347%' OR title LIKE '%无347%' content LIKE '%无347%' OR title LIKE '%无347%'
# 更新:
OR content LIKE '%钻石%' OR title LIKE '%钻石%' OR content LIKE '%钻石%' OR title LIKE '%钻石%'
# 更新:
OR content LIKE '%金马%' OR title LIKE '%金马%' OR content LIKE '%金马%' OR title LIKE '%金马%'
# 更新:
OR content LIKE '%魅力%' OR title LIKE '%魅力%' OR content LIKE '%魅力%' OR title LIKE '%魅力%'
# 更新:
OR content LIKE '%朦胧%' OR title LIKE '%朦胧%' OR content LIKE '%朦胧%' OR title LIKE '%朦胧%'
# 更新:
) )
# 更新:
""")).fetchone() """)).fetchone()
# 更新:
# 更新:
return { return {
# 更新:
"dai4": {"total": dai4[0] or 0, "deals": dai4[1] or 0, "wants": dai4[2] or 0}, "dai4": {"total": dai4[0] or 0, "deals": dai4[1] or 0, "wants": dai4[2] or 0},
# 更新:
"wu4": {"total": wu4[0] or 0, "deals": wu4[1] or 0, "wants": wu4[2] or 0}, "wu4": {"total": wu4[0] or 0, "deals": wu4[1] or 0, "wants": wu4[2] or 0},
# 更新:
"wu47": {"total": wu47[0] or 0, "deals": wu47[1] or 0, "wants": wu47[2] or 0}, "wu47": {"total": wu47[0] or 0, "deals": wu47[1] or 0, "wants": wu47[2] or 0},
# 更新:
"wu247": {"total": wu247[0] or 0, "deals": wu247[1] or 0, "wants": wu247[2] or 0}, "wu247": {"total": wu247[0] or 0, "deals": wu247[1] or 0, "wants": wu247[2] or 0},
# 更新:
"wu347": {"total": wu347[0] or 0, "deals": wu347[1] or 0, "wants": wu347[2] or 0} "wu347": {"total": wu347[0] or 0, "deals": wu347[1] or 0, "wants": wu347[2] or 0}
# 更新:
} }
# 更新:
# 更新:

35
config/CHANGELOG.md Normal file
View File

@ -0,0 +1,35 @@
# 版本更新记录
## v1.2.98 (2026-04-19)
### 前端更新
- **Admin.jsx**
- 功能:修复管理员用户列表行情数显示问题
- 改进:新增 dealCount 字段显示用户发布的成交行情数量
- 代码:将"配号"改为"📈 行情: X条"
### 后端更新
- **users.py**
- 功能修复管理员用户列表行情数API
- 改进:新增 dealCount 字段,从 Information 表统计用户发布的 deal 数量
- 代码:新增 Information 模型导入,统计查询
### Vite配置更新
- **vite.config.js**
- 功能:修复前端构建版本号不更新问题
- 改进:将版本更新改为 closeBundle hook 执行
- 代码:重构 updateHtmlTitle 函数
---
## v1.2.97 (2026-04-XX)
### 更新内容
- (待记录)
---
## v1.2.96 (2026-04-XX)
### 更新内容
- (待记录)

View File

@ -1 +1 @@
1.2.93 1.2.98

48
config/VERSION.json Normal file
View File

@ -0,0 +1,48 @@
{
"version": "1.2.98",
"updated": "2026-04-19",
"modules": {
"frontend": {
"version": "1.2.98",
"pages": {
"Add": "1.2.90",
"Admin": "1.2.98",
"Detail": "1.2.90",
"Edit": "1.2.90",
"Home": "1.2.95",
"List": "1.2.93",
"Login": "1.2.85",
"News": "1.2.80",
"News_YichensBoard": "1.2.75",
"Settings": "1.2.75",
"Stats": "1.2.70",
"YichensBoard": "1.2.70"
},
"config": {
"version": "1.2.98"
}
},
"backend": {
"version": "1.2.98",
"routers": {
"auth": "1.2.90",
"collections": "1.2.95",
"deal": "1.2.85",
"information": "1.2.92",
"news": "1.2.80",
"ocr": "1.2.75",
"operations": "1.2.70",
"seek": "1.2.70",
"users": "1.2.98",
"yichens": "1.2.70"
},
"app": {
"core": "1.2.0",
"models": "1.2.0",
"schemas": "1.2.0",
"services": "1.2.0",
"utils": "1.2.0"
}
}
}
}

8
env.conf Normal file
View File

@ -0,0 +1,8 @@
DATABASE_URL=postgresql://jiachenlong:Passwd1%403@pgm-bp1t5w248t7s1pvr.pg.rds.aliyuncs.com:5432/jiachenlong
SECRET_KEY=production-secret-key-b-env
OSS_ACCESS_KEY_ID=LTAI5t6HUnpFBLEK9194kPVG
OSS_ACCESS_KEY_SECRET=LEr4Q8yRxb8D5b24cKfCwlt4MMoke1
SMS_ACCESS_KEY_ID=LTAI5tQAx5niD7JQVqGE5acE
SMS_ACCESS_KEY_SECRET=QsQFAEKBkaNynIoKyvdIi3BUyWVZu1
SMS_SIGN_NAME=苏州算力
SMS_TEMPLATE_CODE=SMS_501590956

1
frontend/VERSION Normal file
View File

@ -0,0 +1 @@
VERSION=1.2.97

1
frontend/config/VERSION Normal file
View File

@ -0,0 +1 @@
1.2.98

View File

@ -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>甲辰收藏 v=1.2.81</title> <title>甲辰收藏 v=1.2.97</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" />

View File

@ -1,4 +1,9 @@
// - AI / /**
* Add - 添加藏品页面
* Version: 1.2.90x
* 更新
*/
import React, { useState, useRef } from 'react' import React, { useState, useRef } from 'react'
import { APP_VERSION } from '../config/version' import { APP_VERSION } from '../config/version'
const API_BASE = localStorage.getItem('API_BASE') || '' const API_BASE = localStorage.getItem('API_BASE') || ''
@ -844,20 +849,21 @@ export default function Add() {
const sizeType = (dealForm.packaging === '标十' && ['01','11','21','31','41','51','61','71','81','91'].includes(tailNumber)) ? '小号' : const sizeType = (dealForm.packaging === '标十' && ['01','11','21','31','41','51','61','71','81','91'].includes(tailNumber)) ? '小号' :
(dealForm.packaging === '标百' && ['001','011','021','031','041','051','061','071','081','091'].includes(rawSerial.slice(-3))) ? '小号' : '大号' (dealForm.packaging === '标百' && ['001','011','021','031','041','051','061','071','081','091'].includes(rawSerial.slice(-3))) ? '小号' : '大号'
const response = await fetch(`${API_BASE}/api/information/`, { const response = await fetch(`${API_BASE}/api/deal`, {
method: 'POST', method: 'POST',
headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ body: JSON.stringify({
info_type: 'deal',
title: `${normalizedSerial}${dealForm.price}`, title: `${normalizedSerial}${dealForm.price}`,
content: `分类: ${dealForm.category || '未分类'}\n尾号: ${tailNumber || '-'}\n大小号: ${sizeType || '-'}\n包装: ${dealForm.packaging}\n平台: ${dealForm.platform}\n价格: ¥${dealForm.price}\n出售者: ${dealForm.seller || '-'}\n购买者: ${dealForm.buyer || '-'}\n日期: ${dealForm.date}\n评级: ${dealForm.gradingCompany ? dealForm.gradingCompany + ' ' + dealForm.gradingScore : '未评级'}`, content: `分类: ${dealForm.category || '未分类'}\n尾号: ${tailNumber || '-'}\n大小号: ${sizeType || '-'}\n包装: ${dealForm.packaging}\n平台: ${dealForm.platform}\n价格: ¥${dealForm.price}\n出售者: ${dealForm.seller || '-'}\n购买者: ${dealForm.buyer || '-'}\n日期: ${dealForm.date}\n评级: ${dealForm.gradingCompany ? dealForm.gradingCompany + ' ' + dealForm.gradingScore : '未评级'}`,
deal_price: parseFloat(dealForm.price), deal_price: parseFloat(dealForm.price),
deal_date: dealForm.date, deal_date: dealForm.date,
packaging: dealForm.packaging, packaging: dealForm.packaging,
category: dealForm.category, category: dealForm.category,
is_graded: !!dealForm.gradingCompany, tail_number: tailNumber,
grading_company: dealForm.gradingCompany || '', size_type: sizeType,
grading_score: dealForm.gradingScore || '' platform: dealForm.platform,
seller: dealForm.seller || '',
buyer: dealForm.buyer || ''
}) })
}) })
if (response.ok) { if (response.ok) {
@ -1045,21 +1051,20 @@ export default function Add() {
tail_number = item.packaging === '标十' ? digits.slice(-2) : digits.slice(-3) tail_number = item.packaging === '标十' ? digits.slice(-2) : digits.slice(-3)
size_type = (item.packaging === '标十' && ['01','11','21','31','41','51'].includes(tail_number)) || (item.packaging === '标百' && ['101','201','301','401','501'].includes(tail_number)) ? '小号' : '大号' size_type = (item.packaging === '标十' && ['01','11','21','31','41','51'].includes(tail_number)) || (item.packaging === '标百' && ['101','201','301','401','501'].includes(tail_number)) ? '小号' : '大号'
} }
await fetch(`${API_BASE}/api/information/`, { await fetch(`${API_BASE}/api/deal`, {
method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ body: JSON.stringify({
info_type: 'deal',
title: `${item.serial}${item.price}`, title: `${item.serial}${item.price}`,
content: `分类: ${item.category || '-'}\n尾号: ${tail_number || '-'}\n大小号: ${size_type || '-'}\n包装: ${item.packaging || '单张'}\n平台: ${item.platform || '-'}\n价格: ¥${item.price}\n出售者: ${item.seller || '-'}\n购买者: ${item.buyer || '-'}\n日期: ${item.deal_date || new Date().toISOString().split('T')[0]}`, content: `分类: ${item.category || '-'}\n尾号: ${tail_number || '-'}\n大小号: ${size_type || '-'}\n包装: ${item.packaging || '单张'}\n平台: ${item.platform || '-'}\n价格: ¥${item.price}\n出售者: ${item.seller || '-'}\n购买者: ${item.buyer || '-'}\n日期: ${item.deal_date || new Date().toISOString().split('T')[0]}`,
deal_price: parseFloat(item.price), deal_price: parseFloat(item.price),
deal_date: item.deal_date || new Date().toISOString().split('T')[0], deal_date: item.deal_date || new Date().toISOString().split('T')[0],
packaging: item.packaging || '单张', packaging: item.packaging || '单张',
is_graded: item.is_graded || false,
grading_company: item.grading_company || '',
grading_score: item.grade || '',
category: item.category || '', category: item.category || '',
tail_number: tail_number, tail_number: tail_number,
size_type: size_type size_type: size_type,
platform: item.platform || '-',
seller: item.seller || '',
buyer: item.buyer || ''
}) })
}) })
count++ count++

View File

@ -1,3 +1,9 @@
/**
* Admin - 管理员用户管理页面
* Version: 1.2.98 (2026-04-19)
* 更新新增 dealCount 字段显示用户发布的行情数量
*/
import React, { useState, useEffect } from 'react' import React, { useState, useEffect } from 'react'
import { APP_VERSION } from '../config/version' import { APP_VERSION } from '../config/version'
@ -234,7 +240,7 @@ export default function Admin() {
🤖 AI识别: {user.aiCount || 0} 🤖 AI识别: {user.aiCount || 0}
</div> </div>
<div style={{ background: 'rgba(20, 184, 166, 0.1)', padding: '4px 10px', borderRadius: '6px', fontSize: '12px', color: '#14b8a6' }}> <div style={{ background: 'rgba(20, 184, 166, 0.1)', padding: '4px 10px', borderRadius: '6px', fontSize: '12px', color: '#14b8a6' }}>
🎯 配号: {user.searchCount || 0} 📈 行情: {user.dealCount || 0}
</div> </div>
<div style={{ background: 'rgba(245, 158, 11, 0.1)', padding: '4px 10px', borderRadius: '6px', fontSize: '12px', color: '#f59e0b' }}> <div style={{ background: 'rgba(245, 158, 11, 0.1)', padding: '4px 10px', borderRadius: '6px', fontSize: '12px', color: '#f59e0b' }}>
🔍 寻号: {user.searchCount || 0} 🔍 寻号: {user.searchCount || 0}

View File

@ -1,3 +1,9 @@
/**
* Detail - 藏品详情页面
* Version: 1.2.90x
* 更新
*/
import React, { useState, useEffect } from 'react' import React, { useState, useEffect } from 'react'
export default function Detail() { export default function Detail() {

View File

@ -1,3 +1,9 @@
/**
* Edit - 编辑藏品页面
* Version: 1.2.90x
* 更新
*/
import React, { useState, useRef, useEffect } from 'react' import React, { useState, useRef, useEffect } from 'react'
// Input Add.jsx // Input Add.jsx

View File

@ -1,3 +1,9 @@
/**
* Home - 首页
* Version: 1.2.95x
* 更新
*/
import React, { useState, useEffect } from 'react' import React, { useState, useEffect } from 'react'
import { APP_VERSION } from '../config/version' import { APP_VERSION } from '../config/version'

View File

@ -1,3 +1,9 @@
/**
* List - 藏品列表页面
* Version: 1.2.93x
* 更新
*/
import React, { useState, useEffect } from 'react' import React, { useState, useEffect } from 'react'
import { APP_VERSION } from '../config/version' import { APP_VERSION } from '../config/version'
const API_BASE = localStorage.getItem('API_BASE') || '' const API_BASE = localStorage.getItem('API_BASE') || ''
@ -79,7 +85,7 @@ export default function List() {
} }
try { try {
const user = JSON.parse(userStr) const user = JSON.parse(userStr)
const res = await fetch(`${API_BASE}/api/deal/list?page_size=500`, { const res = await fetch(`${API_BASE}/api/deal/list?user_only=true&page_size=500`, {
headers: { 'Authorization': 'Bearer ' + token } headers: { 'Authorization': 'Bearer ' + token }
}) })
const data = await res.json() const data = await res.json()

View File

@ -1,3 +1,9 @@
/**
* Login - 登录页面
* Version: 1.2.85x
* 更新
*/
import React, { useState, useEffect } from 'react' import React, { useState, useEffect } from 'react'
import { APP_VERSION } from '../config/version' import { APP_VERSION } from '../config/version'
import { api } from '../utils/api' import { api } from '../utils/api'

View File

@ -1,3 +1,9 @@
/**
* News - 资讯列表页面
* Version: 1.2.80x
* 更新
*/
import React, { useState, useEffect } from 'react' import React, { useState, useEffect } from 'react'
import YichensBoard from './YichensBoard' import YichensBoard from './YichensBoard'

View File

@ -1,3 +1,9 @@
/**
* News_YichensBoard - 一尘帖子页面
* Version: 1.2.75x
* 更新
*/
import React, { useState, useEffect } from 'react' import React, { useState, useEffect } from 'react'
export default function YichensBoard() { export default function YichensBoard() {

View File

@ -1,3 +1,9 @@
/**
* Settings - 设置页面
* Version: 1.2.75x
* 更新
*/
import React, { useState, useEffect } from 'react' import React, { useState, useEffect } from 'react'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'

View File

@ -1,4 +1,9 @@
// - /**
* Stats - 统计页面
* Version: 1.2.70x
* 更新
*/
import React, { useState, useEffect } from 'react' import React, { useState, useEffect } from 'react'
import { APP_VERSION } from '../config/version' import { APP_VERSION } from '../config/version'

View File

@ -1,3 +1,9 @@
/**
* YichensBoard - 一尘看板页面
* Version: 1.2.70x
* 更新
*/
import React, { useState, useEffect } from 'react' import React, { useState, useEffect } from 'react'
export default function YichensBoard() { export default function YichensBoard() {

128
frontend/src/pages/news.py Normal file
View File

@ -0,0 +1,128 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy import Table, MetaData
from sqlalchemy.orm import Session
from pydantic import BaseModel
from typing import Optional, List
from datetime import datetime, date
from app.core.database import get_db, engine
from app.models.models import User
from app.routers.auth import get_current_user
router = APIRouter(prefix="/api/news", tags=["资讯"])
metadata = MetaData()
# 分类表
categories_table = Table('news_categories', metadata, autoload_with=engine)
news_table = Table('news', metadata, autoload_with=engine)
user_posts_table = Table('user_posts', metadata, autoload_with=engine)
users_table = Table('users', metadata, autoload_with=engine)
deals_table = Table('deals', metadata, autoload_with=engine)
notifications_table = Table('notifications', metadata, autoload_with=engine)
# ============ 获取分类 ============
@router.get("/categories")
def get_categories(db: Session = Depends(get_db)):
results = db.query(categories_table).order_by(categories_table.c.sort_order).all()
return [dict(r._mapping) for r in results]
# ============ 获取资讯 ============
@router.get("")
def get_news(
category_id: Optional[int] = None,
page: int = 1,
limit: int = 20,
db: Session = Depends(get_db)
):
query = db.query(news_table)
if category_id:
query = query.filter(news_table.c.category_id == category_id)
offset = (page - 1) * limit
results = query.order_by(news_table.c.created_at.desc()).offset(offset).limit(limit).all()
return [dict(r._mapping) for r in results]
# ============ 获取用户发布 ============
@router.get("/posts")
def get_posts(
post_type: Optional[str] = None,
status: str = "active",
page: int = 1,
limit: int = 20,
db: Session = Depends(get_db)
):
query = db.query(user_posts_table).filter(user_posts_table.c.status == status)
if post_type:
query = query.filter(user_posts_table.c.post_type == post_type)
offset = (page - 1) * limit
results = query.order_by(user_posts_table.c.created_at.desc()).offset(offset).limit(limit).all()
return [dict(r._mapping) for r in results]
# ============ 创建发布 ============
class PostCreate(BaseModel):
post_type: str
title: str
content: Optional[str] = None
zodiac_type: Optional[str] = None
packaging: Optional[str] = None
@router.post("/posts")
def create_post(
post: PostCreate,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
result = db.execute(user_posts_table.insert().values(
user_id=current_user.f99_90_id,
post_type=post.post_type,
title=post.title,
content=post.content,
zodiac_type=post.zodiac_type,
packaging=post.packaging,
status="pending"
))
db.commit()
return {"success": True, "id": result.inserted_primary_key[0]}
# ============ 成交数据 ============
@router.get("/deals")
def get_deals(
zodiac_type: Optional[str] = None,
limit: int = 20,
db: Session = Depends(get_db)
):
query = db.query(deals_table)
if zodiac_type:
query = query.filter(deals_table.c.zodiac_type == zodiac_type)
results = query.order_by(deals_table.c.deal_date.desc()).limit(limit).all()
return [dict(r._mapping) for r in results]
# ============ 通知 ============
@router.get("/notifications")
def get_notifications(limit: int = 10, db: Session = Depends(get_db)):
results = db.query(notifications_table).filter(
notifications_table.c.is_published == True
).order_by(notifications_table.c.created_at.desc()).limit(limit).all()
return [dict(r._mapping) for r in results]
# ============ 首页数据 ============
@router.get("/home")
def get_home(db: Session = Depends(get_db)):
# 推荐发布
posts = db.query(user_posts_table).filter(
user_posts_table.c.status == "active"
).order_by(user_posts_table.c.created_at.desc()).limit(10).all()
# 成交
deals = db.query(deals_table).order_by(
deals_table.c.deal_date.desc()
).limit(10).all()
# 通知
notices = db.query(notifications_table).filter(
notifications_table.c.is_published == True
).order_by(notifications_table.c.created_at.desc()).limit(5).all()
return {
"posts": [dict(p._mapping) for p in posts],
"deals": [dict(d._mapping) for d in deals],
"notices": [dict(n._mapping) for n in notices]
}

View File

@ -6,7 +6,7 @@ import { join } from 'path'
// 从 config/VERSION 文件读取版本号 // 从 config/VERSION 文件读取版本号
function getVersion() { function getVersion() {
try { try {
const versionFile = join(__dirname, '..', 'config', 'VERSION') const versionFile = join(__dirname, 'config', 'VERSION')
const content = readFileSync(versionFile, 'utf-8').trim() const content = readFileSync(versionFile, 'utf-8').trim()
// 移除 VERSION= 前缀 // 移除 VERSION= 前缀
if (content.startsWith('VERSION=')) { if (content.startsWith('VERSION=')) {
@ -22,28 +22,30 @@ function getVersion() {
const APP_VERSION = getVersion() const APP_VERSION = getVersion()
console.log('📦 构建版本v' + APP_VERSION) console.log('📦 构建版本v' + APP_VERSION)
// 构建时自动更新 index.html 的 title // 构建后执行 - 更新 dist/index.html 的 title
function updateHtmlTitle() { function updateHtmlTitle() {
try { return {
const htmlPath = join(__dirname, 'index.html') name: 'update-html-title',
let htmlContent = readFileSync(htmlPath, 'utf-8') closeBundle() {
// 替换 <title>甲辰收藏 vXXX</title> try {
htmlContent = htmlContent.replace( const htmlPath = join(__dirname, 'dist', 'index.html')
/<title>甲辰收藏 v[\d.]+<\/title>/, let htmlContent = readFileSync(htmlPath, 'utf-8')
'<title>甲辰收藏 v' + APP_VERSION + '</title>' // 替换 <title>甲辰收藏 vXXX</title>
) htmlContent = htmlContent.replace(
writeFileSync(htmlPath, htmlContent, 'utf-8') /<title>甲辰收藏 v[\d.]+<\/title>/,
console.log('✅ 已更新 index.html title: 甲辰收藏 v' + APP_VERSION) '<title>甲辰收藏 v' + APP_VERSION + '</title>'
} catch (e) { )
console.error('更新 index.html 失败:', e.message) writeFileSync(htmlPath, htmlContent, 'utf-8')
console.log('✅ 已更新 dist/index.html title: 甲辰收藏 v' + APP_VERSION)
} catch (e) {
console.error('更新 dist/index.html 失败:', e.message)
}
}
} }
} }
// 构建前执行
updateHtmlTitle()
export default defineConfig({ export default defineConfig({
plugins: [react()], plugins: [react(), updateHtmlTitle()],
define: { define: {
'import.meta.env.APP_VERSION': JSON.stringify(APP_VERSION) 'import.meta.env.APP_VERSION': JSON.stringify(APP_VERSION)
}, },

39
scripts/check_version.sh Executable file
View File

@ -0,0 +1,39 @@
#!/bin/bash
# 版本一致性检查脚本
# 用法: ./scripts/check_version.sh
echo "========================================="
echo " 版本一致性检查"
echo "========================================="
CONFIG_FILE="/root/.openclaw/workspace/jiachenlong/config/VERSION.json"
VERSION=$(grep -o '"version": "[^"]*"' $CONFIG_FILE | cut -d'"' -f4)
UPDATED=$(grep -o '"updated": "[^"]*"' $CONFIG_FILE | cut -d'"' -f4)
echo ""
echo "当前版本: $VERSION"
echo "更新时间: $UPDATED"
echo ""
# 检查前端页面版本
echo "--- 前端页面 (最后修改时间) ---"
for file in /root/.openclaw/workspace/jiachenlong/frontend/src/pages/*.jsx; do
name=$(basename $file .jsx)
mtime=$(date -r "$file" "+%Y-%m-%d %H:%M")
echo "$name: $mtime"
done
echo ""
echo "--- 后端路由 (最后修改时间) ---"
for file in /root/.openclaw/workspace/jiachenlong/backend/app/routers/*.py; do
name=$(basename $file .py)
if [ "$name" != "__init__" ]; then
mtime=$(date -r "$file" "+%Y-%m-%d %H:%M")
echo "$name: $mtime"
fi
done
echo ""
echo "========================================="
echo " 检查完成"
echo "========================================="

13
start.sh Executable file
View File

@ -0,0 +1,13 @@
#!/bin/bash
cd /root/jiachenlong/backend
export DATABASE_URL='postgresql://jiachenlong:Passwd1%403@pgm-bp1t5w248t7s1pvr.pg.rds.aliyuncs.com:5432/jiachenlong'
export SECRET_KEY=production-secret-key-b-env-20260401
export OSS_ACCESS_KEY_ID=LTAI5t6HUnpFBLEK9194kPVG
export OSS_ACCESS_KEY_SECRET=LEr4Q8yRxb8D5b24cKfCwlt4MMoke1
export SMS_ACCESS_KEY_ID=LTAI5t86bc1nNKVNyYv4Af6x
export SMS_ACCESS_KEY_SECRET=92EVAIE3GECr214c9UaSF6TSYJvDLY
export SMS_SIGN_NAME=苏州双人旁
export SMS_TEMPLATE_CODE=SMS_505015231
export DASHSCOPE_API_KEY=sk-9389024a37da4f7bb455ac9a6b28776f
nohup python3 -m uvicorn app.main:app --host 0.0.0.0 --port 3000 --workers 1 > /tmp/uvicorn.log 2>&1 &
echo 'B后端服务已启动'