Compare commits
11 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
a9a68de436 | |
|
|
36b467895c | |
|
|
591db03ee1 | |
|
|
fb987dc5b9 | |
|
|
d578289c7c | |
|
|
b0f28466c8 | |
|
|
57e51f6c28 | |
|
|
524601afc3 | |
|
|
58c2291e83 | |
|
|
4ae29eb4bf | |
|
|
257ca40079 |
|
|
@ -32,6 +32,3 @@ Thumbs.db
|
||||||
.vscode/
|
.vscode/
|
||||||
*.swp
|
*.swp
|
||||||
*.swo
|
*.swo
|
||||||
|
|
||||||
# 版本文件
|
|
||||||
VERSION
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
VERSION=1.2.79
|
1.2.98
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
@ -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
|
|
@ -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]
|
||||||
|
# 更新:
|
||||||
}
|
}
|
||||||
|
# 更新:
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
# 更新:
|
||||||
}
|
}
|
||||||
|
# 更新:
|
||||||
}
|
}
|
||||||
|
# 更新:
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
# 更新:
|
||||||
|
|
|
||||||
|
|
@ -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": "删除成功"}
|
||||||
|
# 更新:
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
# 更新:
|
||||||
}
|
}
|
||||||
|
# 更新:
|
||||||
|
|
||||||
|
# 更新:
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
### 更新内容
|
||||||
|
- (待记录)
|
||||||
|
|
@ -1 +1 @@
|
||||||
1.2.93
|
1.2.98
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
VERSION=1.2.97
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
1.2.98
|
||||||
|
|
@ -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" />
|
||||||
|
|
|
||||||
|
|
@ -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++
|
||||||
|
|
|
||||||
|
|
@ -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}次
|
||||||
|
|
|
||||||
|
|
@ -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() {
|
||||||
|
|
|
||||||
|
|
@ -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 的定义)
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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() {
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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() {
|
||||||
|
|
|
||||||
|
|
@ -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]
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -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 "========================================="
|
||||||
|
|
@ -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后端服务已启动'
|
||||||
Loading…
Reference in New Issue