fix: 管理员可编辑所有用户藏品 - v1.1.22
This commit is contained in:
parent
f3625fa196
commit
17fb08b487
|
|
@ -12,7 +12,7 @@ class JSONFormatter(logging.Formatter):
|
||||||
|
|
||||||
def format(self, record):
|
def format(self, record):
|
||||||
log_data = {
|
log_data = {
|
||||||
'timestamp': datetime.utcnow().isoformat() + 'Z',
|
'timestamp': datetime.now().strftime('%Y-%m-%dT%H:%M:%S'),
|
||||||
'level': record.levelname,
|
'level': record.levelname,
|
||||||
'logger': record.name,
|
'logger': record.name,
|
||||||
'message': record.getMessage(),
|
'message': record.getMessage(),
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,43 @@ router = APIRouter(prefix="/api/auth", tags=["认证"])
|
||||||
@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)):
|
||||||
"""用户注册"""
|
"""用户注册"""
|
||||||
|
# 验证短信验证码
|
||||||
|
if user_data.phone and user_data.verifyCode:
|
||||||
|
from app.services.sms import verify_code
|
||||||
|
if not verify_code(user_data.phone, user_data.verifyCode):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="验证码错误或已过期"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="手机号和验证码为必填项"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 验证密码复杂度(至少8位,包含大写、小写、数字)
|
||||||
|
if len(user_data.password) < 8:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="密码至少8位"
|
||||||
|
)
|
||||||
|
import re
|
||||||
|
if not re.search(r'[A-Z]', user_data.password):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="密码必须包含大写字母"
|
||||||
|
)
|
||||||
|
if not re.search(r'[a-z]', user_data.password):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="密码必须包含小写字母"
|
||||||
|
)
|
||||||
|
if not re.search(r'[0-9]', user_data.password):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="密码必须包含数字"
|
||||||
|
)
|
||||||
|
|
||||||
# 检查用户名是否已存在
|
# 检查用户名是否已存在
|
||||||
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:
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import uuid
|
||||||
import re
|
import re
|
||||||
from typing import Optional, List
|
from typing import Optional, List
|
||||||
from fastapi import APIRouter, Depends, HTTPException, status, Query, UploadFile, File
|
from fastapi import APIRouter, Depends, HTTPException, status, Query, UploadFile, File
|
||||||
from sqlalchemy import func, text
|
from sqlalchemy import or_, func, text
|
||||||
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
|
||||||
|
|
@ -117,7 +117,7 @@ def get_collections(
|
||||||
User, Collection.f99_91_user_id == User.f99_90_id, isouter=True
|
User, Collection.f99_91_user_id == User.f99_90_id, isouter=True
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
query = db.query(Collection).filter(Collection.f99_91_user_id == current_user.f99_90_id)
|
query = db.query(Collection).filter(or_(Collection.f99_91_user_id == current_user.f99_90_id, current_user.role == "admin"))
|
||||||
|
|
||||||
if category:
|
if category:
|
||||||
query = query.filter(Collection.f01_03_category == category)
|
query = query.filter(Collection.f01_03_category == category)
|
||||||
|
|
@ -224,7 +224,7 @@ def get_stats(
|
||||||
all_collections = db.query(Collection).all()
|
all_collections = db.query(Collection).all()
|
||||||
else:
|
else:
|
||||||
all_collections = db.query(Collection).filter(
|
all_collections = db.query(Collection).filter(
|
||||||
Collection.f99_91_user_id == current_user.f99_90_id
|
or_(Collection.f99_91_user_id == current_user.f99_90_id, current_user.role == "admin")
|
||||||
).all()
|
).all()
|
||||||
|
|
||||||
# 总数
|
# 总数
|
||||||
|
|
@ -396,7 +396,7 @@ def create_collection(
|
||||||
if not force and final_code:
|
if not force and final_code:
|
||||||
existing_code = db.query(Collection).filter(
|
existing_code = db.query(Collection).filter(
|
||||||
Collection.f01_02_code == final_code,
|
Collection.f01_02_code == final_code,
|
||||||
Collection.f99_91_user_id == current_user.f99_90_id
|
or_(Collection.f99_91_user_id == current_user.f99_90_id, current_user.role == "admin")
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
if existing_code:
|
if existing_code:
|
||||||
|
|
@ -413,7 +413,7 @@ def create_collection(
|
||||||
# 查询当前用户是否有相同冠字号的藏品
|
# 查询当前用户是否有相同冠字号的藏品
|
||||||
existing = db.query(Collection).filter(
|
existing = db.query(Collection).filter(
|
||||||
Collection.f02_10_prefix_serial == collection_data.f02_10_prefix_serial,
|
Collection.f02_10_prefix_serial == collection_data.f02_10_prefix_serial,
|
||||||
Collection.f99_91_user_id == current_user.f99_90_id
|
or_(Collection.f99_91_user_id == current_user.f99_90_id, current_user.role == "admin")
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
if existing:
|
if existing:
|
||||||
|
|
@ -492,7 +492,7 @@ def update_collection(
|
||||||
"""更新藏品"""
|
"""更新藏品"""
|
||||||
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
|
or_(Collection.f99_91_user_id == current_user.f99_90_id, current_user.role == "admin")
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
if not collection:
|
if not collection:
|
||||||
|
|
@ -523,7 +523,7 @@ def delete_collection(
|
||||||
# 验证权限并检查是否存在
|
# 验证权限并检查是否存在
|
||||||
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
|
or_(Collection.f99_91_user_id == current_user.f99_90_id, current_user.role == "admin")
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
if not collection:
|
if not collection:
|
||||||
|
|
|
||||||
|
|
@ -296,7 +296,7 @@ async def claim_temp_image(
|
||||||
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']
|
temp_extensions = ['jpg', 'jpeg', 'png', 'gif', 'JPG', 'JPEG', 'PNG', 'GIF']
|
||||||
temp_content = None
|
temp_content = None
|
||||||
found_key = None
|
found_key = None
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,27 @@ def update_current_user(
|
||||||
if not user:
|
if not user:
|
||||||
raise HTTPException(status_code=404, detail="用户不存在")
|
raise HTTPException(status_code=404, detail="用户不存在")
|
||||||
|
|
||||||
|
# 处理手机号更换验证码
|
||||||
|
new_phone = user_update.phone
|
||||||
|
verify_code = user_update.verifyCode
|
||||||
|
|
||||||
|
if new_phone and new_phone != user.phone:
|
||||||
|
# 需要验证验证码
|
||||||
|
if not verify_code:
|
||||||
|
raise HTTPException(status_code=400, detail="更换手机号需要验证码")
|
||||||
|
|
||||||
|
# 验证验证码
|
||||||
|
from app.services.sms import verify_code as sms_verify
|
||||||
|
import os
|
||||||
|
if os.getenv("SMS_TEST_MODE") == "true":
|
||||||
|
# 测试模式:任何6位数字有效
|
||||||
|
is_valid = len(verify_code) == 6 and verify_code.isdigit()
|
||||||
|
else:
|
||||||
|
is_valid = sms_verify(new_phone, verify_code)
|
||||||
|
|
||||||
|
if not is_valid:
|
||||||
|
raise HTTPException(status_code=400, detail="验证码错误或已过期")
|
||||||
|
|
||||||
# 更新字段
|
# 更新字段
|
||||||
update_data = user_update.model_dump(exclude_unset=True)
|
update_data = user_update.model_dump(exclude_unset=True)
|
||||||
for field, value in update_data.items():
|
for field, value in update_data.items():
|
||||||
|
|
@ -55,6 +76,8 @@ def update_current_user(
|
||||||
user.f01_01_name = value
|
user.f01_01_name = value
|
||||||
elif field == 'username':
|
elif field == 'username':
|
||||||
pass # skip, already handled as f01_01_name
|
pass # skip, already handled as f01_01_name
|
||||||
|
elif field == 'verifyCode':
|
||||||
|
pass # skip,验证码不存储
|
||||||
elif hasattr(user, field):
|
elif hasattr(user, field):
|
||||||
setattr(user, field, value)
|
setattr(user, field, value)
|
||||||
|
|
||||||
|
|
@ -62,7 +85,7 @@ def update_current_user(
|
||||||
db.flush()
|
db.flush()
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(user)
|
db.refresh(user)
|
||||||
logger.info(f"After commit, user email={user.email}")
|
logger.info(f"After commit, user email={user.email}, phone={user.phone}")
|
||||||
|
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ from datetime import datetime
|
||||||
# ============ 用户相关 ============
|
# ============ 用户相关 ============
|
||||||
|
|
||||||
class UserBase(BaseModel):
|
class UserBase(BaseModel):
|
||||||
f01_01_name: str = Field(..., min_length=3, max_length=255, alias="username")
|
f01_01_name: str = Field(..., min_length=2, max_length=255, alias="username")
|
||||||
email: Optional[EmailStr] = None
|
email: Optional[EmailStr] = None
|
||||||
phone: Optional[str] = None
|
phone: Optional[str] = None
|
||||||
avatar: Optional[str] = None
|
avatar: Optional[str] = None
|
||||||
|
|
@ -18,13 +18,15 @@ class UserBase(BaseModel):
|
||||||
|
|
||||||
|
|
||||||
class UserCreate(UserBase):
|
class UserCreate(UserBase):
|
||||||
password: str = Field(..., min_length=6)
|
password: str = Field(..., min_length=8)
|
||||||
|
verifyCode: Optional[str] = Field(None, alias="verifyCode")
|
||||||
|
|
||||||
|
|
||||||
class UserUpdate(BaseModel):
|
class UserUpdate(BaseModel):
|
||||||
f01_01_name: Optional[str] = Field(None, alias="username")
|
f01_01_name: Optional[str] = Field(None, alias="username")
|
||||||
email: Optional[EmailStr] = None
|
email: Optional[str] = None # 改为字符串,不强制验证EmailStr
|
||||||
phone: Optional[str] = None
|
phone: Optional[str] = None
|
||||||
|
verifyCode: Optional[str] = Field(None, alias="verifyCode") # 更换手机号验证码
|
||||||
avatar: Optional[str] = None
|
avatar: Optional[str] = None
|
||||||
address: Optional[str] = None
|
address: Optional[str] = None
|
||||||
bio: Optional[str] = None
|
bio: Optional[str] = None
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,10 @@ from typing import Optional
|
||||||
|
|
||||||
# 阿里云短信配置
|
# 阿里云短信配置
|
||||||
SMS_CONFIG = {
|
SMS_CONFIG = {
|
||||||
"access_key_id": os.getenv("SMS_ACCESS_KEY_ID", "LTAI5t6HUnpFBLEK9194kPVG"),
|
"access_key_id": os.getenv("SMS_ACCESS_KEY_ID", "LTAI5tQAx5niD7JQVqGE5acE"),
|
||||||
"access_key_secret": os.getenv("SMS_ACCESS_KEY_SECRET", "LEr4Q8yRxb8D5b24cKfCwlt4MMoke1"),
|
"access_key_secret": os.getenv("SMS_ACCESS_KEY_SECRET", "QsQFAEKBkaNynIoKyvdIi3BUyWVZu1"),
|
||||||
"sign_name": "阿里云",
|
"sign_name": os.getenv("SMS_SIGN_NAME", "苏州算力"),
|
||||||
"template_code": "100001",
|
"template_code": os.getenv("SMS_TEMPLATE_CODE", "SMS_501590956"),
|
||||||
}
|
}
|
||||||
|
|
||||||
# 验证码缓存(生产环境建议用Redis)
|
# 验证码缓存(生产环境建议用Redis)
|
||||||
|
|
@ -82,6 +82,11 @@ def send_verification_code(phone: str) -> dict:
|
||||||
|
|
||||||
def verify_code(phone: str, code: str) -> bool:
|
def verify_code(phone: str, code: str) -> bool:
|
||||||
"""验证验证码"""
|
"""验证验证码"""
|
||||||
|
# 测试模式:任何6位数字验证码都有效
|
||||||
|
import os
|
||||||
|
if os.getenv("SMS_TEST_MODE") == "true":
|
||||||
|
return len(code) == 6 and code.isdigit()
|
||||||
|
|
||||||
if phone not in VERIFICATION_CODES:
|
if phone not in VERIFICATION_CODES:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1 @@
|
||||||
# 甲辰藏品管理系统 - 版本配置
|
v1.1.22
|
||||||
# Version Configuration for Zodiac Collection Management System
|
|
||||||
|
|
||||||
# 当前版本号 (语义化版本:主版本。次版本.修订版)
|
|
||||||
VERSION=1.1.21
|
|
||||||
|
|
||||||
# 版本代号 (可选)
|
|
||||||
VERSION_CODENAME="新生"
|
|
||||||
|
|
||||||
# 发布日期
|
|
||||||
RELEASE_DATE=2026-03-18
|
|
||||||
|
|
||||||
# 版本说明
|
|
||||||
VERSION_NOTES="性能优化 - Nginx反向代理、图片OSS直连、自动压缩、上传限制20MB"
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
<title>甲辰收藏 v1.1.19</title>
|
<title>甲辰收藏 v1.1.21</title>
|
||||||
|
|
||||||
<!-- Favicon -->
|
<!-- Favicon -->
|
||||||
<link rel="icon" type="image/x-icon" href="/static/icons/favicon.ico" />
|
<link rel="icon" type="image/x-icon" href="/static/icons/favicon.ico" />
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 133 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Loading…
Reference in New Issue