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):
log_data = {
'timestamp': datetime.utcnow().isoformat() + 'Z',
'timestamp': datetime.now().strftime('%Y-%m-%dT%H:%M:%S'),
'level': record.levelname,
'logger': record.name,
'message': record.getMessage(),

View File

@ -13,6 +13,43 @@ router = APIRouter(prefix="/api/auth", tags=["认证"])
@router.post("/register", response_model=UserResponse)
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()
if existing_user:

View File

@ -4,7 +4,7 @@ import uuid
import re
from typing import Optional, List
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 app.core.database import get_db
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
)
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:
query = query.filter(Collection.f01_03_category == category)
@ -224,7 +224,7 @@ def get_stats(
all_collections = db.query(Collection).all()
else:
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()
# 总数
@ -396,7 +396,7 @@ def create_collection(
if not force and final_code:
existing_code = db.query(Collection).filter(
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()
if existing_code:
@ -413,7 +413,7 @@ def create_collection(
# 查询当前用户是否有相同冠字号的藏品
existing = db.query(Collection).filter(
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()
if existing:
@ -492,7 +492,7 @@ def update_collection(
"""更新藏品"""
collection = db.query(Collection).filter(
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()
if not collection:
@ -523,7 +523,7 @@ def delete_collection(
# 验证权限并检查是否存在
collection = db.query(Collection).filter(
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()
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获取临时图片 - 尝试多种扩展名和日期路径
temp_extensions = ['jpg', 'jpeg', 'png', 'gif']
temp_extensions = ['jpg', 'jpeg', 'png', 'gif', 'JPG', 'JPEG', 'PNG', 'GIF']
temp_content = None
found_key = None

View File

@ -48,6 +48,27 @@ def update_current_user(
if not user:
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)
for field, value in update_data.items():
@ -55,6 +76,8 @@ def update_current_user(
user.f01_01_name = value
elif field == 'username':
pass # skip, already handled as f01_01_name
elif field == 'verifyCode':
pass # skip,验证码不存储
elif hasattr(user, field):
setattr(user, field, value)
@ -62,7 +85,7 @@ def update_current_user(
db.flush()
db.commit()
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

View File

@ -7,7 +7,7 @@ from datetime import datetime
# ============ 用户相关 ============
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
phone: Optional[str] = None
avatar: Optional[str] = None
@ -18,13 +18,15 @@ class UserBase(BaseModel):
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):
f01_01_name: Optional[str] = Field(None, alias="username")
email: Optional[EmailStr] = None
email: Optional[str] = None # 改为字符串不强制验证EmailStr
phone: Optional[str] = None
verifyCode: Optional[str] = Field(None, alias="verifyCode") # 更换手机号验证码
avatar: Optional[str] = None
address: Optional[str] = None
bio: Optional[str] = None

View File

@ -8,10 +8,10 @@ from typing import Optional
# 阿里云短信配置
SMS_CONFIG = {
"access_key_id": os.getenv("SMS_ACCESS_KEY_ID", "LTAI5t6HUnpFBLEK9194kPVG"),
"access_key_secret": os.getenv("SMS_ACCESS_KEY_SECRET", "LEr4Q8yRxb8D5b24cKfCwlt4MMoke1"),
"sign_name": "阿里云",
"template_code": "100001",
"access_key_id": os.getenv("SMS_ACCESS_KEY_ID", "LTAI5tQAx5niD7JQVqGE5acE"),
"access_key_secret": os.getenv("SMS_ACCESS_KEY_SECRET", "QsQFAEKBkaNynIoKyvdIi3BUyWVZu1"),
"sign_name": os.getenv("SMS_SIGN_NAME", "苏州算力"),
"template_code": os.getenv("SMS_TEMPLATE_CODE", "SMS_501590956"),
}
# 验证码缓存生产环境建议用Redis
@ -82,6 +82,11 @@ def send_verification_code(phone: str) -> dict:
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:
return False

View File

@ -1,14 +1 @@
# 甲辰藏品管理系统 - 版本配置
# Version Configuration for Zodiac Collection Management System
# 当前版本号 (语义化版本:主版本。次版本.修订版)
VERSION=1.1.21
# 版本代号 (可选)
VERSION_CODENAME="新生"
# 发布日期
RELEASE_DATE=2026-03-18
# 版本说明
VERSION_NOTES="性能优化 - Nginx反向代理、图片OSS直连、自动压缩、上传限制20MB"
v1.1.22

View File

@ -4,7 +4,7 @@
<meta charset="UTF-8">
<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">
<title>甲辰收藏 v1.1.19</title>
<title>甲辰收藏 v1.1.21</title>
<!-- Favicon -->
<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