feat: 添加短信验证码绕过功能并更新API权限同步
- 添加SMS_BYPASS_CODE配置允许特定验证码绕过验证 - 实现角色与API权限的自动同步功能 - 更新评估模型的时间字段为可空 - 移除前端PC路由配置 - 更新Docker镜像版本至v2.6 - 切换开发环境API基础地址
This commit is contained in:
parent
253ed14c87
commit
01ed8fb25b
@ -132,6 +132,10 @@ async def verify_code(payload: VerifyCodeRequest) -> BasicResponse[dict]:
|
||||
Returns:
|
||||
验证结果字典
|
||||
"""
|
||||
from app.settings import settings
|
||||
if settings.SMS_BYPASS_CODE and payload.code == settings.SMS_BYPASS_CODE:
|
||||
logger.info("sms.verify_code bypass phone={}", payload.phone)
|
||||
return Success(data={"status": "OK", "message": "verified"})
|
||||
ok, reason = store.can_verify(payload.phone)
|
||||
if not ok:
|
||||
raise HTTPException(status_code=status.HTTP_423_LOCKED, detail=str(reason))
|
||||
@ -161,21 +165,24 @@ class SMSLoginRequest(BaseModel):
|
||||
|
||||
@router.post("/login", summary="短信验证码登录", response_model=BasicResponse[dict])
|
||||
async def sms_login(payload: SMSLoginRequest) -> BasicResponse[dict]:
|
||||
ok, reason = store.can_verify(payload.phone_number)
|
||||
if not ok:
|
||||
raise HTTPException(status_code=status.HTTP_423_LOCKED, detail=str(reason))
|
||||
record = store.get_code(payload.phone_number)
|
||||
if not record:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="验证码过期")
|
||||
code, expires_at = record
|
||||
if time.time() > expires_at:
|
||||
store.clear_code(payload.phone_number)
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="验证码过期")
|
||||
if payload.verification_code != code:
|
||||
count, locked = store.record_verify_failure(payload.phone_number)
|
||||
if locked:
|
||||
raise HTTPException(status_code=status.HTTP_423_LOCKED, detail="尝试次数过多,已锁定")
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="验证码错误")
|
||||
from app.settings import settings
|
||||
bypass = settings.SMS_BYPASS_CODE and payload.verification_code == settings.SMS_BYPASS_CODE
|
||||
if not bypass:
|
||||
ok, reason = store.can_verify(payload.phone_number)
|
||||
if not ok:
|
||||
raise HTTPException(status_code=status.HTTP_423_LOCKED, detail=str(reason))
|
||||
record = store.get_code(payload.phone_number)
|
||||
if not record:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="验证码过期")
|
||||
code, expires_at = record
|
||||
if time.time() > expires_at:
|
||||
store.clear_code(payload.phone_number)
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="验证码过期")
|
||||
if payload.verification_code != code:
|
||||
count, locked = store.record_verify_failure(payload.phone_number)
|
||||
if locked:
|
||||
raise HTTPException(status_code=status.HTTP_423_LOCKED, detail="尝试次数过多,已锁定")
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="验证码错误")
|
||||
|
||||
from app.controllers.app_user import app_user_controller
|
||||
from app.schemas.app_user import AppUserRegisterSchema, AppUserInfoOut, AppUserJWTOut
|
||||
@ -187,8 +194,9 @@ async def sms_login(payload: SMSLoginRequest) -> BasicResponse[dict]:
|
||||
await app_user_controller.update_last_login(user.id)
|
||||
|
||||
access_token = create_app_user_access_token(user_id=user.id, phone=user.phone)
|
||||
store.clear_code(payload.phone_number)
|
||||
store.reset_failures(payload.phone_number)
|
||||
if not bypass:
|
||||
store.clear_code(payload.phone_number)
|
||||
store.reset_failures(payload.phone_number)
|
||||
logger.info("sms.login success phone={}", payload.phone_number)
|
||||
|
||||
user_info = AppUserInfoOut(
|
||||
|
||||
@ -621,12 +621,17 @@ class ValuationController:
|
||||
if 'certificate_url' in update_data and update_data.get('certificate_url'):
|
||||
from datetime import datetime
|
||||
update_data['audited_at'] = datetime.now()
|
||||
update_data['updated_at'] = datetime.now()
|
||||
else:
|
||||
from datetime import datetime
|
||||
update_data['updated_at'] = datetime.now()
|
||||
await valuation.update_from_dict(update_data)
|
||||
await valuation.save()
|
||||
from datetime import datetime
|
||||
valuation.status ="pending"
|
||||
if not getattr(valuation, "audited_at", None):
|
||||
valuation.audited_at = datetime.now()
|
||||
valuation.updated_at = datetime.now()
|
||||
await valuation.save()
|
||||
|
||||
out = ValuationAssessmentOut.model_validate(valuation)
|
||||
@ -643,12 +648,17 @@ class ValuationController:
|
||||
if 'certificate_url' in update_data and update_data.get('certificate_url'):
|
||||
from datetime import datetime
|
||||
update_data['audited_at'] = datetime.now()
|
||||
update_data['updated_at'] = datetime.now()
|
||||
else:
|
||||
from datetime import datetime
|
||||
update_data['updated_at'] = datetime.now()
|
||||
await valuation.update_from_dict(update_data)
|
||||
await valuation.save()
|
||||
from datetime import datetime
|
||||
valuation.status ="success"
|
||||
if not getattr(valuation, "audited_at", None):
|
||||
valuation.audited_at = datetime.now()
|
||||
valuation.updated_at = datetime.now()
|
||||
await valuation.save()
|
||||
|
||||
out = ValuationAssessmentOut.model_validate(valuation)
|
||||
@ -803,7 +813,7 @@ class ValuationController:
|
||||
return None
|
||||
|
||||
from datetime import datetime
|
||||
update_data = {"status": "pending", "audited_at": datetime.now()}
|
||||
update_data = {"status": "pending", "audited_at": datetime.now(), "updated_at": datetime.now()}
|
||||
if admin_notes:
|
||||
update_data["admin_notes"] = admin_notes
|
||||
|
||||
@ -818,7 +828,7 @@ class ValuationController:
|
||||
return None
|
||||
|
||||
from datetime import datetime
|
||||
update_data = {"status": "rejected", "audited_at": datetime.now()}
|
||||
update_data = {"status": "rejected", "audited_at": datetime.now(), "updated_at": datetime.now()}
|
||||
if admin_notes:
|
||||
update_data["admin_notes"] = admin_notes
|
||||
|
||||
@ -832,7 +842,8 @@ class ValuationController:
|
||||
if not valuation:
|
||||
return None
|
||||
|
||||
await valuation.update_from_dict({"admin_notes": admin_notes}).save()
|
||||
from datetime import datetime
|
||||
await valuation.update_from_dict({"admin_notes": admin_notes, "updated_at": datetime.now()}).save()
|
||||
out = ValuationAssessmentOut.model_validate(valuation)
|
||||
return await self._attach_user_phone(out)
|
||||
|
||||
|
||||
@ -279,9 +279,31 @@ async def init_menus():
|
||||
|
||||
|
||||
async def init_apis():
|
||||
apis = await api_controller.model.exists()
|
||||
if not apis:
|
||||
await api_controller.refresh_api()
|
||||
await api_controller.refresh_api()
|
||||
|
||||
|
||||
async def sync_role_api_bindings():
|
||||
"""确保角色与API权限绑定是最新的:管理员拥有全部API,普通用户拥有基础API"""
|
||||
from tortoise.expressions import Q
|
||||
try:
|
||||
admin_role = await Role.filter(name="管理员").first()
|
||||
if admin_role:
|
||||
all_apis = await Api.all()
|
||||
current = await admin_role.apis.all()
|
||||
current_keys = {(a.method, a.path) for a in current}
|
||||
missing = [a for a in all_apis if (a.method, a.path) not in current_keys]
|
||||
if missing:
|
||||
await admin_role.apis.add(*missing)
|
||||
user_role = await Role.filter(name="普通用户").first()
|
||||
if user_role:
|
||||
basic_apis = await Api.filter(Q(method__in=["GET"]) | Q(tags="基础模块"))
|
||||
current_u = await user_role.apis.all()
|
||||
current_u_keys = {(a.method, a.path) for a in current_u}
|
||||
missing_u = [a for a in basic_apis if (a.method, a.path) not in current_u_keys]
|
||||
if missing_u:
|
||||
await user_role.apis.add(*missing_u)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
async def _ensure_unique_index():
|
||||
@ -599,4 +621,5 @@ async def init_data():
|
||||
await init_menus()
|
||||
await init_apis()
|
||||
await init_roles()
|
||||
await sync_role_api_bindings()
|
||||
await init_demo_transactions()
|
||||
|
||||
@ -85,7 +85,7 @@ class ValuationAssessment(Model):
|
||||
status = fields.CharField(max_length=20, default="pending", description="评估状态: pending(待审核), success(已通过), rejected(已拒绝)")
|
||||
admin_notes = fields.TextField(null=True, description="管理员备注")
|
||||
created_at = fields.DatetimeField(auto_now_add=True, description="创建时间")
|
||||
updated_at = fields.DatetimeField(auto_now=True, description="更新时间")
|
||||
updated_at = fields.DatetimeField(null=True, description="更新时间")
|
||||
audited_at = fields.DatetimeField(null=True, description="审核时间")
|
||||
is_active = fields.BooleanField(default=True, description="是否激活")
|
||||
|
||||
@ -114,7 +114,7 @@ class ValuationCalculationStep(Model):
|
||||
status = fields.CharField(max_length=20, default="processing", description="步骤状态: processing, completed, failed")
|
||||
error_message = fields.TextField(null=True, description="错误信息")
|
||||
created_at = fields.DatetimeField(auto_now_add=True, description="创建时间")
|
||||
updated_at = fields.DatetimeField(auto_now=True, description="更新时间")
|
||||
updated_at = fields.DatetimeField(null=True, description="更新时间")
|
||||
|
||||
class Meta:
|
||||
table = "valuation_calculation_steps"
|
||||
|
||||
@ -105,6 +105,7 @@ class Settings(BaseSettings):
|
||||
SMS_DEBUG_LOG_CODE: bool = True
|
||||
ALIYUN_USE_DEFAULT_CREDENTIALS: bool = False
|
||||
ALIYUN_SMS_TEMPLATE_PARAM_CODE_KEY: typing.Optional[str] = "code"
|
||||
SMS_BYPASS_CODE: typing.Optional[str] = "202511"
|
||||
|
||||
SMTP_HOST: typing.Optional[str] = "smtp.qiye.aliyun.com"
|
||||
SMTP_PORT: typing.Optional[int] = 465
|
||||
|
||||
@ -21,15 +21,7 @@ server {
|
||||
index index.html index.htm;
|
||||
try_files $uri /index.html;
|
||||
}
|
||||
# PC 前端(/pc/ 前缀)
|
||||
location = /pc {
|
||||
return 302 /pc/;
|
||||
}
|
||||
location ^~ /pc/ {
|
||||
alias /opt/vue-fastapi-admin/web1/dist/;
|
||||
index index.html;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
location ^~ /api/ {
|
||||
proxy_pass http://127.0.0.1:9999;
|
||||
proxy_set_header Host $host;
|
||||
|
||||
@ -5,5 +5,5 @@ VITE_PUBLIC_PATH = '/'
|
||||
VITE_USE_PROXY = true
|
||||
|
||||
# base api
|
||||
VITE_BASE_API = 'http://139.224.70.152:9990/api/v1'
|
||||
# VITE_BASE_API = 'https://value.cdcee.net/api/v1'
|
||||
# VITE_BASE_API = 'http://139.224.70.152:9990/api/v1'
|
||||
VITE_BASE_API = 'https://value.cdcee.net/api/v1'
|
||||
|
||||
BIN
web1/dist.zip
BIN
web1/dist.zip
Binary file not shown.
6
估值字段.txt
6
估值字段.txt
@ -37,8 +37,8 @@
|
||||
|
||||
|
||||
export DOCKER_DEFAULT_PLATFORM=linux/amd64
|
||||
docker build -t zfc931912343/guzhi-fastapi-admin:v2.1 .
|
||||
docker push zfc931912343/guzhi-fastapi-admin:v2.1
|
||||
docker build -t zfc931912343/guzhi-fastapi-admin:v2.6 .
|
||||
docker push zfc931912343/guzhi-fastapi-admin:v2.6
|
||||
|
||||
|
||||
# 运行容器
|
||||
@ -75,4 +75,4 @@ docker pull nbg2akd8w5diy8.xuanyuan.run/zfc931912343/guzhi-fastapi-admin:v1.4 &&
|
||||
|
||||
|
||||
|
||||
|
||||
docker pull nbg2akd8w5diy8.xuanyuan.run/zfc931912343/guzhi-fastapi-admin:v2.5 && docker rm -f guzhi_pro && docker run -itd --name=guzhi_pro -p 8080:80 -v ~/guzhi-data/static/images:/opt/vue-fastapi-admin/app/static/images --restart=unless-stopped -e TZ=Asia/Shanghai nbg2akd8w5diy8.xuanyuan.run/zfc931912343/guzhi-fastapi-admin:v2.5
|
||||
Loading…
x
Reference in New Issue
Block a user