add phone login
Some checks failed
Build and Deploy Backend / build-and-deploy (push) Failing after 59s
Some checks failed
Build and Deploy Backend / build-and-deploy (push) Failing after 59s
This commit is contained in:
parent
88b8f023f4
commit
bc28ef00f1
@ -23,17 +23,9 @@ class UserDetailSerializer(serializers.ModelSerializer):
|
||||
|
||||
|
||||
class PhoneLoginSerializer(serializers.Serializer):
|
||||
"""手机号一键登录序列化器"""
|
||||
|
||||
phone = serializers.CharField(max_length=20, help_text='手机号')
|
||||
# 实际项目中应该有验证码或token
|
||||
# code = serializers.CharField(max_length=10, help_text='验证码')
|
||||
|
||||
def validate_phone(self, value):
|
||||
# 简单验证手机号格式
|
||||
if not value.isdigit() or len(value) != 11:
|
||||
raise serializers.ValidationError('手机号格式不正确')
|
||||
return value
|
||||
"""手机号一键登录序列化器(阿里云号码认证)"""
|
||||
|
||||
token = serializers.CharField(help_text='阿里云号码认证 SDK 返回的 accessToken')
|
||||
|
||||
|
||||
class UpdateUserSerializer(serializers.ModelSerializer):
|
||||
|
||||
@ -12,6 +12,7 @@ from django.utils import timezone
|
||||
from utils.response import success, error
|
||||
from utils.exceptions import ErrorCode
|
||||
from utils.sms import send_sms_code, verify_sms_code
|
||||
from utils.phone_auth import get_phone_auth_client
|
||||
from utils.oss import get_oss_client
|
||||
from apps.admins.authentication import AppJWTAuthentication, AdminJWTAuthentication
|
||||
from apps.admins.permissions import IsAdminUser
|
||||
@ -51,27 +52,34 @@ class AuthViewSet(viewsets.ViewSet):
|
||||
@action(detail=False, methods=['post'], url_path='phone-login')
|
||||
def phone_login(self, request):
|
||||
"""
|
||||
手机号一键登录
|
||||
手机号一键登录(阿里云号码认证)
|
||||
POST /api/v1/auth/phone-login
|
||||
App 端通过阿里云 SDK 获取 token,后端用 token 换取真实手机号
|
||||
"""
|
||||
serializer = PhoneLoginSerializer(data=request.data)
|
||||
if not serializer.is_valid():
|
||||
return error(message=str(serializer.errors))
|
||||
|
||||
phone = serializer.validated_data['phone']
|
||||
|
||||
|
||||
access_token = serializer.validated_data['token']
|
||||
|
||||
# 用 token 换取真实手机号
|
||||
client = get_phone_auth_client()
|
||||
phone, err = client.get_mobile(access_token)
|
||||
if not phone:
|
||||
return error(code=102, message=err or '号码认证失败')
|
||||
|
||||
# 获取或创建用户
|
||||
user, created = User.objects.get_or_create(
|
||||
phone=phone,
|
||||
defaults={'nickname': f'用户{phone[-4:]}'}
|
||||
)
|
||||
|
||||
|
||||
if not user.is_active:
|
||||
return error(code=101, message='账号已被禁用')
|
||||
|
||||
|
||||
# 生成JWT Token(带user_type='app'标识)
|
||||
tokens = get_app_tokens(user)
|
||||
|
||||
|
||||
return success(data={
|
||||
'user': UserSerializer(user).data,
|
||||
'token': tokens,
|
||||
|
||||
@ -180,6 +180,12 @@ ALIYUN_SMS = {
|
||||
'SEND_INTERVAL': 60, # 发送间隔(秒) 60秒
|
||||
}
|
||||
|
||||
# Aliyun Phone Auth (号码认证/一键登录)
|
||||
ALIYUN_PHONE_AUTH = {
|
||||
'ACCESS_KEY_ID': os.environ.get('PHONE_AUTH_ACCESS_KEY_ID', ALIYUN_ACCESS_KEY_ID),
|
||||
'ACCESS_KEY_SECRET': os.environ.get('PHONE_AUTH_ACCESS_KEY_SECRET', ALIYUN_ACCESS_KEY_SECRET),
|
||||
}
|
||||
|
||||
# Swagger/OpenAPI Settings
|
||||
SPECTACULAR_SETTINGS = {
|
||||
'TITLE': 'RTC API',
|
||||
|
||||
@ -28,3 +28,4 @@ sqlparse==0.5.5
|
||||
urllib3==2.6.3
|
||||
drf-spectacular==0.27.1
|
||||
alibabacloud_dysmsapi20170525>=4.4.0
|
||||
alibabacloud_dypnsapi20170525>=3.0.0
|
||||
|
||||
82
utils/phone_auth.py
Normal file
82
utils/phone_auth.py
Normal file
@ -0,0 +1,82 @@
|
||||
"""
|
||||
阿里云号码认证服务工具类
|
||||
用于一键登录:App 端获取 token → 后端用 token 换取真实手机号
|
||||
"""
|
||||
import logging
|
||||
from django.conf import settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
from alibabacloud_dypnsapi20170525.client import Client
|
||||
from alibabacloud_tea_openapi.models import Config
|
||||
from alibabacloud_dypnsapi20170525.models import GetMobileRequest
|
||||
PHONE_AUTH_SDK_AVAILABLE = True
|
||||
except ImportError:
|
||||
PHONE_AUTH_SDK_AVAILABLE = False
|
||||
|
||||
|
||||
class PhoneAuthClient:
|
||||
"""阿里云号码认证客户端(单例)"""
|
||||
|
||||
_instance = None
|
||||
|
||||
def __new__(cls):
|
||||
if cls._instance is None:
|
||||
cls._instance = super().__new__(cls)
|
||||
cls._instance._initialized = False
|
||||
return cls._instance
|
||||
|
||||
def __init__(self):
|
||||
if self._initialized:
|
||||
return
|
||||
|
||||
if not PHONE_AUTH_SDK_AVAILABLE:
|
||||
self.client = None
|
||||
self._initialized = True
|
||||
return
|
||||
|
||||
auth_config = settings.ALIYUN_PHONE_AUTH
|
||||
if not auth_config.get('ACCESS_KEY_ID'):
|
||||
self.client = None
|
||||
self._initialized = True
|
||||
return
|
||||
|
||||
config = Config(
|
||||
access_key_id=auth_config['ACCESS_KEY_ID'],
|
||||
access_key_secret=auth_config['ACCESS_KEY_SECRET'],
|
||||
endpoint='dypnsapi.aliyuncs.com',
|
||||
)
|
||||
self.client = Client(config)
|
||||
self._initialized = True
|
||||
|
||||
def get_mobile(self, access_token):
|
||||
"""
|
||||
用 App 端 SDK 获取的 token 换取真实手机号
|
||||
:param access_token: App 端 SDK 返回的 token
|
||||
:return: (phone: str|None, error_msg: str)
|
||||
"""
|
||||
if not self.client:
|
||||
logger.warning('号码认证 SDK 未配置')
|
||||
return None, '号码认证服务未配置'
|
||||
|
||||
try:
|
||||
request = GetMobileRequest(access_token=access_token)
|
||||
response = self.client.get_mobile(request)
|
||||
|
||||
if response.body.code == 'OK':
|
||||
mobile = response.body.get_mobile_result_dto.mobile
|
||||
logger.info('号码认证成功: %s', mobile)
|
||||
return mobile, ''
|
||||
else:
|
||||
msg = response.body.message or response.body.code
|
||||
logger.error('号码认证失败: %s', msg)
|
||||
return None, msg
|
||||
except Exception as e:
|
||||
logger.error('号码认证异常: %s', str(e))
|
||||
return None, str(e)
|
||||
|
||||
|
||||
def get_phone_auth_client():
|
||||
"""获取号码认证客户端单例"""
|
||||
return PhoneAuthClient()
|
||||
Loading…
x
Reference in New Issue
Block a user