2025-03-17 18:06:54 +08:00

455 lines
19 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from rest_framework.views import APIView
from mysystem.models import Users
from utils.jsonResponse import SuccessResponse,ErrorResponse,DetailResponse
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from rest_framework_simplejwt.views import TokenObtainPairView
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from utils.common import REGEX_MOBILE,get_parameter_dic,ast_convert
import re
from random import choice
from django.db.models import Q,F
from django_redis import get_redis_connection
from utils.yunpian import YunPian
from utils.uniappsms import UniCloudSms
from django.contrib.auth.hashers import make_password, check_password
import uuid
from config import ALIYUN_SMS_SIGN,ALIYUM_SMS_TEMPLATE
from utils.aliyunsms import send_sms
from utils.tencentsms import tencentsms
import json
# ================================================= #
# ************** 前端用户登录 view ************** #
# ================================================= #
"""
手机号密码登录
"""
class AppMoliePasswordLoginSerializer(TokenObtainPairSerializer):
"""
前端登录的序列化器:
重写djangorestframework-simplejwt的序列化器
"""
default_error_messages = {
'no_active_account': _('该账号已被禁用,请联系管理员')
}
def validate(self, attrs):
mobile = attrs['username']
# 验证手机号是否合法
if not re.match(REGEX_MOBILE, mobile):
result = {
"code": 4000,
"msg": "请输入正确的手机号",
"data": None
}
return result
password = attrs['password']
user = Users.objects.filter(username=mobile, identity=2).first()
if user and not user.is_active:
result = {
"code": 4000,
"msg": "该账号已被禁用,请联系管理员",
"data": None
}
return result
if user and user.check_password(password): # check_password() 对明文进行加密,并验证
# data = super().validate(attrs)
data={}
refresh = self.get_token(user)
data['identity'] = ast_convert(user.identity)
data['userId'] = user.id
data['refresh'] = str(refresh)
data['access'] = str(refresh.access_token)
result = {
"code": 2000,
"msg": "登录成功",
"data": {
"page": 1,
"limit": 1,
"total": 1,
"data": data
},
}
else:
result = {
"code": 4000,
"msg": "账号/密码不正确",
"data": None
}
return result
class APPMobilePasswordLoginView(TokenObtainPairView):
"""
手机号密码登录接口此种方式传参需要formdata方式
"""
serializer_class = AppMoliePasswordLoginSerializer
permission_classes = []
class UsernamePassWordLoginView(APIView):
"""
post:
账号密码登录
【功能描述】账号密码登录</br>
【参数说明】username 手机号</br>
【参数说明】password 密码</br>
"""
authentication_classes = []
permission_classes = []
def post(self,request):
username = get_parameter_dic(request)['username']
password = get_parameter_dic(request)['password']
user = Users.objects.filter(username=username, identity=2).first()
if user and not user.is_active:
return ErrorResponse(msg="该账号已被禁用,请联系管理员")
if user and user.check_password(password): # check_password() 对明文进行加密,并验证
resdata = APPMobileSMSLoginSerializer.get_token(user)
return DetailResponse(data=resdata, msg="登录成功")
return ErrorResponse(msg="账号/密码错误")
class MobilePassWordLoginView(APIView):
"""
post:
手机号密码登录
【功能描述】手机号密码登录</br>
【参数说明】mobile 手机号</br>
【参数说明】password 密码</br>
"""
authentication_classes = []
permission_classes = []
def post(self,request):
username = get_parameter_dic(request)['mobile']
password = get_parameter_dic(request)['password']
# 验证手机号是否合法
if not re.match(REGEX_MOBILE, username):
return ErrorResponse(msg="请输入正确的手机号")
user = Users.objects.filter(username=username, identity=2).first()
if user and not user.is_active:
return ErrorResponse(msg="该账号已被禁用,请联系管理员")
if user and user.check_password(password): # check_password() 对明文进行加密,并验证
resdata = APPMobileSMSLoginSerializer.get_token(user)
return DetailResponse(data=resdata, msg="登录成功")
return ErrorResponse(msg="账号/密码错误")
#发送短信序列化器
class SmsSerializer(serializers.Serializer):
mobile = serializers.CharField(max_length=11,required=True,help_text="手机号")
smstype=serializers.CharField(max_length=10,required=True,help_text="请求类型login/register/restpass/rebind")
# 函数名必须validate + 验证字段名
def validate_mobile(self, mobile):
"""
手机号码验证
"""
# 是否合法手机号
if not re.match(REGEX_MOBILE, mobile):
raise ValueError("手机号码非法")
if self.context['smstype']== "register":#用户注册
# 是否已经注册
if Users.objects.filter(mobile=mobile).count():
raise ValueError("该手机号已注册")
if self.context['smstype']== "restpass" or self.context['smstype']== "login":#重置密码/用户登录
#该手机号是否已注册
if not Users.objects.filter(username=mobile,identity=2,is_active=True).count():
raise ValueError("没有找到该用户")
if self.context['smstype']== "wxbind":#微信绑定
#该手机号是否已注册
if not Users.objects.filter(username=mobile,identity=2,is_active=True,oauthwxuser__isnull=True).count():
raise ValueError("没有找到该用户或该用户已绑定微信")
if self.context['smstype'] == "rebind":#换绑手机号,前提用户已经登录
#是否跟原来绑定过的号码一致
mqueryset = Users.objects.filter(id = self.context['request'].user.id)
if not mqueryset:
raise ValueError("该用户不存在")
if mqueryset[0].mobile == mobile:
raise ValueError("请使用新的手机号绑定")
if Users.objects.filter(mobile=mobile).count():
raise ValueError("该手机号已被其他用户绑定")
return mobile
#发送短信验证码(不需要登录验证就可以调用)
#注册,登录,忘记密码时使用
class SendSmsCodeView(APIView):
"""
post:
发送手机验证码
【参数说明】使用"application/json"编码传输,参数如下</br>
mobile 必要 手机号</br>
smstype 必要 短信类型register/restpass/login</br>
"""
authentication_classes = ()#不需要身份认证
permission_classes = ()#不需要权限分配
serializer_class = SmsSerializer
def generate_code(self):
"""
生成四位数字的验证码
"""
seeds = "1234567890"
random_str = []
for i in range(6):
random_str.append(choice(seeds))
return "".join(random_str)
def post(self, request, *args, **kwargs):
mobile = get_parameter_dic(request)['mobile']
smstype = get_parameter_dic(request)['smstype']
if smstype == "login" or smstype == "restpass" or smstype == "wxbind" or smstype == "register":
# 创建序列化器
serializer = SmsSerializer(data=request.data, context={"request": request, "smstype": smstype})
# 验证是否有效
serializer.is_valid(raise_exception=True)
# 判断该手机号60s内是否已经发送过短信
redis_conn = get_redis_connection('verify_codes')
send_flag = redis_conn.get('send_flag_%s' % mobile)
if send_flag: # 如果取到了标记说明该手机号60s内发送过短信验证码
return ErrorResponse(msg="请一分钟后再获取验证码")
# 验证码过期时间
codeexpire = 300 # 300秒默认5分钟
# 生成验证码
code = self.generate_code()
# 云片网api短信接口调用-----------开始
# yun_pian = YunPian(SMS_API_KEY)
# sms_status = yun_pian.send_sms(code=code, mobile=mobile)
#
# if sms_status["code"] != 0:
# mydata = {}
# mydata["mobile"] = mobile
# return Response(ly_api_res(400,mydata,sms_status["msg"]), status=status.HTTP_400_BAD_REQUEST)
# else:
# #存储短信验证码到redis
# redis_conn.setex('sms_%s'%mobile,codeexpire,code)#默认300秒5分钟过期时间
# #存储一个标记表示此手机号已发送过短信标记有效期为60s
# redis_conn.setex('send_flag_%s'%mobile,60,1)
# mydata = {}
# mydata["mobile"] = mobile
# return Response(ly_api_res(200,mydata,"短信验证码发送成功"), status=status.HTTP_200_OK)
# 云片网api短信接口调用-----------结束
# unicloud短信接口api调用-----------开始
# unicloudsms = UniCloudSms()
# sms_status = unicloudsms.send_sms(code=code, mobile=mobile,expminute=codeexpire)
# #返回内容
# #{"msg":"sendSms参数phone值不可为空"}
# #{"code":0,"errCode":0,"success":true}
# if 'code' in sms_status: #判断返回内容是否存在code的key错误时不返回code
# if sms_status["code"] == 0:
# # 存储短信验证码到redis
# redis_conn.setex('sms_%s' % mobile, codeexpire, code) # 默认300秒5分钟过期时间
# # 存储一个标记表示此手机号已发送过短信标记有效期为60s
# redis_conn.setex('send_flag_%s' % mobile, 60, 1)
# mydata = {}
# mydata["mobile"] = mobile
# return SuccessResponse(data=mydata, msg="短信验证码发送成功")
# else:
# mydata = {}
# mydata["mobile"] = mobile
# return ErrorResponse(data=mydata, msg=sms_status['msg'])
# else:
# mydata = {}
# mydata["mobile"] = mobile
# return ErrorResponse(data=mydata, msg=sms_status['msg'])
# unicloud短信接口api调用-----------结束
# 阿里云短信-----------开始
# __business_id = uuid.uuid1()
# # 一个验证码发送的例子
# params = '{\"code\":\"'+code+'\"}' # 模板参数
# sms_status= send_sms(__business_id,mobile,ALIYUN_SMS_SIGN,ALIYUM_SMS_TEMPLATE,params)
# # 返回内容byte类型
# # {
# # "Message": "OK",
# # "RequestId": "F655A8D5-B967-440B-8683-DAD6FF8DE990",
# # "Code": "OK",
# # "BizId": "900619746936498440^0"
# # }
# sms_status_str = sms_status.decode()
# sms_status_json = json.loads(sms_status)
# if 'Code' in sms_status_str: # 判断返回内容是否存在code的key错误时不返回code
# if sms_status_json["Code"] == 'OK':
# # 存储短信验证码到redis
# redis_conn.setex('sms_%s' % mobile, codeexpire, code) # 默认300秒5分钟过期时间
# # 存储一个标记表示此手机号已发送过短信标记有效期为60s
# redis_conn.setex('send_flag_%s' % mobile, 60, 1)
# mydata = {}
# mydata["mobile"] = mobile
# return SuccessResponse(data=mydata, msg="短信验证码发送成功")
# else:
# return ErrorResponse(data=sms_status_json, msg='发送失败')
# else:
# return ErrorResponse(data=sms_status_json, msg='发送失败')
# 阿里云短信-----------结束
# 腾讯云短信-----------开始
sms_status_json = tencentsms("+" + str(86) + str(mobile), code)
# sms_status_json = 'Ok'
if 'Ok' in sms_status_json: #
# 存储短信验证码到redis
redis_conn.setex('sms_%s' % mobile, codeexpire, code) # 默认300秒5分钟过期时间
# 存储一个标记表示此手机号已发送过短信标记有效期为60s
redis_conn.setex('send_flag_%s' % mobile, 60, 1)
mydata = {}
mydata["mobile"] = mobile
return SuccessResponse(data=mydata, msg="success")
else:
return ErrorResponse(data=sms_status_json, msg='error')
# 腾讯云短信-----------结束
else:
return ErrorResponse(msg="smstype短信验证码类型错误")
class APPMobileSMSLoginSerializer(TokenObtainPairSerializer):
"""
登录的序列化器:
重写djangorestframework-simplejwt的序列化器
"""
@classmethod
def get_token(cls, user):
refresh = super(APPMobileSMSLoginSerializer,cls).get_token(user)
data = {}
data['identity'] = ast_convert(user.identity)
data['userId'] = user.id
data['refresh'] = str(refresh)
data['access'] = str(refresh.access_token)
return data
class APPMobileSMSLoginView(APIView):
"""
post:
手机号短信登录接口
【功能描述】用户手机号短信验证码登录</br>
【参数说明】mobile为手机号</br>
【参数说明】code短信验证码</br>
"""
authentication_classes = []
permission_classes = []
def post(self,request):
mobile = get_parameter_dic(request)['mobile']
code = get_parameter_dic(request)['code']
# 验证手机号是否合法
if not re.match(REGEX_MOBILE, mobile):
return ErrorResponse(msg="请输入正确手机号")
# 判断短信验证码是否正确
redis_conn = get_redis_connection('verify_codes')
send_flag = redis_conn.get('sms_%s' % mobile) # send_flag的值为bytes需要转换成str ,send_flag.decode()
if not send_flag: # 如果取不到标记,则说明验证码过期
return ErrorResponse(msg="短信验证码已过期")
else:
if str(send_flag.decode()) != str(code):
return ErrorResponse(msg="验证码错误")
#开始登录
user = Users.objects.filter(username=mobile,identity=2).first()
if not user:
return ErrorResponse(msg="用户不存在")
if not user.is_active:
return ErrorResponse(msg="该账号已被禁用,请联系管理员")
resdata = APPMobileSMSLoginSerializer.get_token(user)
redis_conn.delete('sms_%s' % mobile)
return SuccessResponse(data=resdata, msg="登录成功")
#用户忘记密码重置密码
class ForgetPasswdResetView(APIView):
'''
post:
【功能描述】重置用户密码</br>
【参数说明】mobile为手机号</br>
【参数说明】code短信验证码</br>
【参数说明】password为密码</br>
'''
authentication_classes = []
permission_classes = []
def post(self, request, *args, **kwargs):
mobile = get_parameter_dic(request)['mobile']
code = get_parameter_dic(request)['code']
password = get_parameter_dic(request)['password']
if len(password) < 6:
return ErrorResponse(msg="密码长度至少6位")
# 验证手机号是否合法
if not re.match(REGEX_MOBILE, mobile):
return ErrorResponse(msg="请输入正确手机号")
# 判断短信验证码是否正确
redis_conn = get_redis_connection('verify_codes')
send_flag = redis_conn.get('sms_%s'%mobile)#send_flag的值为bytes需要转换成str ,,send_flag.decode()
if not send_flag: # 如果取不到标记,则说明验证码过期
return ErrorResponse(msg="短信验证码已过期")
else:
if str(send_flag.decode()) != str(code):
return ErrorResponse(msg="验证码错误")
#开始更换密码
user = Users.objects.filter(username=mobile,identity=2).first()
if not user:
return ErrorResponse(msg="用户不存在")
if not user.is_active:
return ErrorResponse(msg="该账号已被禁用,请联系管理员")
# 重置密码
user.password = make_password(password)
user.save()
redis_conn.delete('sms_%s' % mobile)
return SuccessResponse(msg="success")
class RegisterView(APIView):
'''
前端用户注册
post:
【功能描述】前端用户注册</br>
【参数说明】mobile为手机号</br>
【参数说明】code短信验证码</br>
【参数说明】password为密码</br>
【参数说明】password2为确认输入的密码</br>
'''
authentication_classes = []
permission_classes = []
def post(self, request, *args, **kwargs):
mobile = get_parameter_dic(request)['mobile']
code = get_parameter_dic(request)['code']
password = get_parameter_dic(request)['password']
password2 = get_parameter_dic(request)['password2']
if mobile is None or code is None or password is None or password2 is None:
return ErrorResponse(msg="提交的参数不能为空")
# 判断密码是否合法
if len(password) < 6:
return ErrorResponse(msg="密码长度至少6位")
if not re.match(r'^[a-zA_Z0-9]{6,20}$', password):
return ErrorResponse(msg="密码格式不正确(大小写字母、数字组合)")
if password != password2:
return ErrorResponse(msg="两次密码输入不一致")
# 验证手机号是否合法
if not re.match(REGEX_MOBILE, mobile):
return ErrorResponse(msg="请输入正确手机号")
# 判断短信验证码是否正确
if not re.match(r'^\d{6}$', code):
return ErrorResponse(msg="验证码格式错误")
redis_conn = get_redis_connection('verify_codes')
send_flag = redis_conn.get('sms_%s' % mobile) # send_flag的值为bytes需要转换成str ,,send_flag.decode()
if not send_flag: # 如果取不到标记,则说明验证码过期
return ErrorResponse(msg="短信验证码已过期")
else:
if str(send_flag.decode()) != str(code):
return ErrorResponse(msg="验证码错误")
# 开始注册
Users.objects.create(username=mobile,password=make_password(password),is_staff=False,identity=2)
redis_conn.delete('sms_%s' % mobile)
return SuccessResponse(msg="注册成功")