From 5d20c1ed97e5bcd26924e3c860ba2e938bfdb52d Mon Sep 17 00:00:00 2001 From: XingQue Date: Thu, 9 Jul 2026 05:00:13 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20JWT=20=E7=94=A8=E6=88=B7=E7=BC=93?= =?UTF-8?q?=E5=AD=98=E9=81=BF=E5=85=8D=E6=AF=8F=E8=AF=B7=E6=B1=82=E6=9F=A5?= =?UTF-8?q?=E5=BA=93=20+=20=E7=A7=BB=E9=99=A4=E5=85=A8=20API=20=E9=99=90?= =?UTF-8?q?=E6=B5=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- a_long_dianjing/settings.py | 5 +---- deploy/verify-status.sh | 32 ++++++++++++++++++++++++++++++++ gvsdsdk/auth/jwt_auth.py | 18 ++++++++++-------- 3 files changed, 43 insertions(+), 12 deletions(-) create mode 100644 deploy/verify-status.sh diff --git a/a_long_dianjing/settings.py b/a_long_dianjing/settings.py index fc26cd8..ddbb7b5 100644 --- a/a_long_dianjing/settings.py +++ b/a_long_dianjing/settings.py @@ -191,10 +191,7 @@ REST_FRAMEWORK = { 'DEFAULT_PERMISSION_CLASSES': ( 'rest_framework.permissions.IsAuthenticated', ), - 'DEFAULT_THROTTLE_CLASSES': [ - 'rest_framework.throttling.AnonRateThrottle', - 'rest_framework.throttling.UserRateThrottle', - ], + # 全局限流会作用于每个 API;登录/支付等敏感接口在各自 view 上单独配置了 throttle_classes 'DEFAULT_THROTTLE_RATES': { 'anon': '100/minute', 'user': '1000/minute', diff --git a/deploy/verify-status.sh b/deploy/verify-status.sh new file mode 100644 index 0000000..570d57e --- /dev/null +++ b/deploy/verify-status.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# 只读:汇总已排查项,输出结论 +set -e +DJANGO_DIR="/opt/1panel/apps/openresty/nginx/www/sites/43.142.166.152.443/django" +LOG="/opt/1panel/apps/openresty/nginx/log/access.log" +cd "$DJANGO_DIR" + +echo "=== 1. 数据库地址 ===" +venv/bin/python -c "import app_secrets; print(app_secrets.DATABASE_HOST, app_secrets.DATABASE_PORT)" + +echo "" +echo "=== 2. Redis 延迟 ===" +venv/bin/python manage.py shell -c " +import time +from django.core.cache import cache +t=time.perf_counter() +cache.set('ping',1,10); cache.get('ping') +print(round((time.perf_counter()-t)*1000), 'ms') +" + +echo "" +echo "=== 3. 最近429限流条数 ===" +grep -a 'hqhd' "$LOG" 2>/dev/null | awk '$9==429' | wc -l | xargs echo "429 count:" + +echo "" +echo "=== 4. 最近20条 hqhd rt= ===" +grep -a 'hqhd' "$LOG" 2>/dev/null | tail -20 | grep -oP 'rt=\K[0-9.]+|POST \K[^ ]+' | paste - - | head -10 + +echo "" +echo "=== 5. Gunicorn ===" +systemctl is-active gunicorn-dianjing.service +pgrep -cf 'gunicorn.*a_long_dianjing' || true diff --git a/gvsdsdk/auth/jwt_auth.py b/gvsdsdk/auth/jwt_auth.py index 5d9f108..d65ba43 100644 --- a/gvsdsdk/auth/jwt_auth.py +++ b/gvsdsdk/auth/jwt_auth.py @@ -1,5 +1,6 @@ """自定义 JWT 认证 — 向后兼容旧 token 的 user_id claim""" +from django.core.cache import cache from rest_framework_simplejwt.authentication import JWTAuthentication from rest_framework_simplejwt.exceptions import InvalidToken, AuthenticationFailed from rest_framework_simplejwt.settings import api_settings @@ -16,28 +17,29 @@ class CompatibleJWTAuthentication(JWTAuthentication): 值为 user.UserUID(7位业务ID)。 此类先尝试新 claim,找不到时回退到旧 claim,并用对应字段查库。 + 用户对象缓存 300 秒(Redis),避免每个 API 都打一次 MySQL。 """ def get_user(self, validated_token): - # 1. 先尝试当前配置的 USER_ID_CLAIM user_id = validated_token.get(api_settings.USER_ID_CLAIM) lookup_field = api_settings.USER_ID_FIELD if user_id is None: - # 2. 回退:尝试旧 token 中的 'user_id' claim user_id = validated_token.get('user_id') if user_id is None: raise InvalidToken( {'detail': _('令牌未包含用户标识符'), 'code': 'token_not_valid'} ) - # 旧 token 的 user_id 来自 user.id(即 UserUUID), - # 需要用 UserUUID 字段查库 lookup_field = 'UserUUID' - try: - user = self.user_model.objects.get(**{lookup_field: user_id}) - except self.user_model.DoesNotExist: - raise AuthenticationFailed(_('用户不存在'), code='user_not_found') + cache_key = f'jwt_user:{lookup_field}:{user_id}' + user = cache.get(cache_key) + if user is None: + try: + user = self.user_model.objects.get(**{lookup_field: user_id}) + except self.user_model.DoesNotExist: + raise AuthenticationFailed(_('用户不存在'), code='user_not_found') + cache.set(cache_key, user, 300) if not user.is_active: raise AuthenticationFailed(_('用户已被禁用'), code='user_inactive')