fix: 管理员可编辑所有用户藏品 - v1.1.22

This commit is contained in:
甲辰生产 2026-03-20 00:25:53 +08:00
parent f3625fa196
commit 17fb08b487
19 changed files with 86 additions and 32 deletions

View File

@ -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(),

View File

@ -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:

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -4,7 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">
<title>甲辰收藏 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" />

0
static/README.md Normal file → Executable file
View File

0
static/fonts/.gitkeep Normal file → Executable file
View File

0
static/fonts/README.md Normal file → Executable file
View File

0
static/icons/.gitkeep Normal file → Executable file
View File

0
static/icons/README.md Normal file → Executable file
View File

0
static/images/.gitkeep Normal file → Executable file
View File

0
static/images/LOGO_GUIDE.md Normal file → Executable file
View File

0
static/images/README.md Normal file → Executable file
View File

0
static/images/jiachenlong-logo.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 133 KiB

After

Width:  |  Height:  |  Size: 133 KiB

0
static/images/title_logo.svg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB