Django项目用户部分模块

用户模型类的创建

首先:Django中的认证系统提供了的用户模型类和方法很方便,我们可以使用这个模型类,对于没有的字段就需要额外添加。

Django提供的认证系统,可以同时处理认证和授权。
Django的认证系统包含有:

  1. 用户
  2. 权限:用二元标志来指示一个用户是否可以做一个特定的任务。
  3. 组:对多个用户运用标签和全新的一种通用的方式。
  4. 一个可配置的密码哈希系统
  5. 用户登录或内容显示的表单和视图
  6. 一个可插拔的后台系统

Django认证系统提供了用户模型类User保存用户的数据,默认的User包含一下常见的基本字段:

  1. username:用户名

  2. first_name:(可选)

  3. last_name:(可选)

  4. email:(可选)

  5. password:密码(Django不保存原始密码)

  6. is_staff:布尔值,指示用户是否可以访问Admin站点

  7. date_joined:账户创建时间

  8. last_login:用户最后一次登录的时间

    常用的方法:

  9. set_password(raw_password):设置用户的密码

  10. check_password(raw_password):如果给定的raw_password是用户的真实密码,则放回True,可以在检验用户密码时使用。

开始创建
Django提供了django.contrib.auth.models.AbstractUser用户抽象模型类允许我们继承,扩展字段来使用Django认证系统的用户模型类。
所以

  • 先导入AbstractUser,在创建用户模型类的时候继承它。
  • 在模型类中添加额外需要的字段
  • 在配置文件中进行设置,不然系统不知道按照认证系统的模型类去创建。
AUTH_USER_MODEL = 'users.User'
  • 执行数据库迁移,注意:一定要在AUTH_USER_MODEL配置到后再迁移。

注册业务接口分析

接口设计的思路

  • 分析要实现的业务逻辑,明确在这个业务中需要涉及到几个相关子业务,将每个子业务当做一个接口来设计。
  • 分析接口的功能任务,明确接口的访问方式与返回数据:
    比如:
  1. 接口的请求方式,如GET 、POST 、PUT等
  2. 接口的URL路径定义
  3. 需要前端传递的数据及数据格式(如路径参数、查询字符串、请求体表单、JSON等)
  4. 返回给前端的数据及数据格式

分析在用户注册中,需要实现以下接口:

  • 图片验证码
  • 短信验证码
  • 用户名判断是否存在
  • 手机号判断是否存在
  • 注册保存用户数据
    其中图片验证码和短信验证码考虑后续业务也会用到,故创建一个新应用verifications,在此应用中实现图片验证码、短信验证码方便别处调用。

图片验证码

实现逻辑:

  1. 将验证码图片返回前端
  2. 将正确内容保存到redis中。
    访问方式: GET /image_codes/(?P<image_code_id>[\w-]+)/
    由于是get请求,将图片ID参数携带在请求url中。而ID的是由前端生成的。
    这个项目使用的是第三方库,captcha:/卡谱洽/,用来生成图片验证码。
from rest_framework.views import APIView
from meiduo_mall.libs.captcha.captcha import captcha
from django_redis import get_redis_connection

class ImageCodeView(APIView):
    """
    图片验证码
    """

    def get(self, request, image_code_id):
        """
        获取图片验证码
        """
        # 生成验证码图片
        text, image = captcha.generate_captcha()

        redis_conn = get_redis_connection("verify_codes")
        redis_conn.setex("img_%s" % image_code_id, constants.IMAGE_CODE_REDIS_EXPIRES, text)

        # 固定返回验证码图片数据,不需要REST framework框架的Response帮助我们决定返回响应数据的格式
        # 所以此处直接使用Django原生的HttpResponse即可
        return HttpResponse(image, content_type="images/jpg")
        # 最后不能使用Response返回图片,应为Response默认会使用render对返回的值进行渲染,它无法对图片进行渲染,故使用Django原生的HttpResponse进行返回(它可以接受图片)。

说明:(先要在配置用文件中配置好redis)django-redis提供了get_redis_connection的方法,通过调用get_redis_connection方法传递redis的配置名称可获取到redis的连接对象,通过redis连接对象可以执行redis命令。

短信验证码

有需要对数据的校验故使用序列化器较为简洁:

get_serializer 方法在创建序列化器对象时会自动补充context属性,context属性包含三个值:request(请求对象) 、format(请求数据格式) 、view(类视图对象)。
在django的类视图对象中,kwargs属性保存了从路径中提取出来的参数。
所以在序列化器中获取手机号可以用一下方式获取:

mobile = self.context['view'].kwargs['mobile']

发送短信使用第三方工具包:云通讯
视图代码:

    def get(self, request, mobile):
        # 校验参数  由序列化器完成
        serializer = self.get_serializer(data=request.query_params)
        serializer.is_valid(raise_exception=True)

        # 生成短信验证码
        sms_code = '%06d' % random.randint(0, 999999)

        # 保存短信验证码  保存发送记录
        redis_conn = get_redis_connection('verify_codes')
        # redis_conn.setex("sms_%s" % mobile, constants.SMS_CODE_REDIS_EXPIRES, sms_code)
        # redis_conn.setex("send_flag_%s" % mobile, constants.SEND_SMS_CODE_INTERVAL, 1)

        # redis管道
        pl = redis_conn.pipeline()
        pl.setex("sms_%s" % mobile, constants.SMS_CODE_REDIS_EXPIRES, sms_code)
        pl.setex("send_flag_%s" % mobile, constants.SEND_SMS_CODE_INTERVAL, 1)

        # 让管道通知redis执行命令
        pl.execute()
        
        # 使用celery发送短信验证码
        expires = constants.SMS_CODE_REDIS_EXPIRES // 60
        send_sms_code.delay(mobile, sms_code, expires, constants.SMS_CODE_TEMP_ID)

        return Response({'message': 'OK'})

其中代码优化:
使用redis管道来统一操作数据库,提高性能。
使用celery发送短信验证码,异步处理任务。
关于celery的异步实现参考下方链接:
celery异步实现短信验证码发送
其中短信验证时的跨域请求解决办法
图片验证码是采用浏览器发送请求:

  • 即将图片当做一种页面资源加载,使用 src=“不同的资源路径”
  • 每当资源路径改变浏览器就会自动请求资源

短信验证码是采用CORS实现跨域请求:
CORS原理:
浏览器在实现跨域请求之前会发送一个option请求询问后端是否支持
后端提供option请求的支持,告诉浏览器,支持那些域名的访问
创建Django中间件 提供option请求
实现步骤:

  1. 安装:pip install django-cors-headers
  2. 添加应用
INSTALLED_APPS = (
    ...
    'corsheaders',
    ...
)
  1. 中间层设置
MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    ...
]
  1. 添加白名单
# CORS
CORS_ORIGIN_WHITELIST = (
    '127.0.0.1:8080',
    'localhost:8080',
    'www.meiduo.site:8080',
    'api.meiduo.site:8000'
)
CORS_ALLOW_CREDENTIALS = True  # 允许携带cookie

凡是出现在白名单中的域名,都可以访问后端接口
CORS_ALLOW_CREDENTIALS 指明在跨域访问中,后端是否支持对cookie的操作。

注册功能实现
在注册的后端代码中需要实现的逻辑有:

  1. 接受参数
  2. 校验参数
  3. 保存用户数据,密码加密
  4. 序列化,返回数据

这几步逻辑可以直接使用CreateAPIView来实现

class UserView(CreateAPIView):
    """
    用户注册
    传入参数:
        username, password, password2, sms_code, mobile, allow
    """
    serializer_class = serializers.CreateUserSerializer

序列化器中需要重写create方法,添加密码加密的方法

class CreateUserSerializer(serializers.ModelSerializer):
    """创建用户的序列化器"""
    password2 = serializers.CharField(label='确认密码', write_only=True)
    sms_code = serializers.CharField(label='短信验证码', write_only=True)
    allow = serializers.CharField(label='同意协议', write_only=True)
    token = serializers.CharField(label='JWT token', read_only=True)

    class Meta:
        model = User
        fields = ('id', 'username', 'password', 'password2', 'sms_code', 'mobile', 'allow', 'token')
        extra_kwargs = {
            'username': {
                'min_length': 5,
                'max_length': 20,
                'error_messages': {
                    'min_length': '仅允许5-20个字符的用户名',
                    'max_length': '仅允许5-20个字符的用户名',
                }
            },
            'password': {
                'write_only': True,
                'min_length': 8,
                'max_length': 20,
                'error_messages': {
                    'min_length': '仅允许8-20个字符的密码',
                    'max_length': '仅允许8-20个字符的密码',
                }
            }
        }

    def validate_mobile(self, value):
        """验证手机号"""
        if not re.match(r'^1[3-9]\d{9}$', value):
            raise serializers.ValidationError('手机号格式错误')
        return value

    def validate_allow(self, value):
        """检验用户是否同意协议"""
        if value != 'true':
            raise serializers.ValidationError('请同意用户协议')
        return value

    def validate(self, data):
        # 判断两次密码
        if data['password'] != data['password2']:
            raise serializers.ValidationError('两次密码不一致')

        # 判断短信验证码
        redis_conn = get_redis_connection('verify_codes')
        mobile = data['mobile']
        real_sms_code = redis_conn.get('sms_%s' % mobile)
        if real_sms_code is None:
            raise serializers.ValidationError('无效的短信验证码')
        if data['sms_code'] != real_sms_code.decode():
            raise serializers.ValidationError('短信验证码错误')

        return data

    def create(self, validated_data):
        """重写保存方法,增加密码加密"""

        # 移除数据库模型类中不存在的属性
        del validated_data['password2']
        del validated_data['sms_code']
        del validated_data['allow']

        # user = User.objects.create(username=xxx, password=xx)
        # user = User.objects.create(**validated_data)

        user = super().create(validated_data)

        user.set_password(validated_data['password'])  # 这是Django认证系统中自带的加密方法
        user.save()

        # 签发jwt token
        jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
        jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER

        payload = jwt_payload_handler(user)
        token = jwt_encode_handler(payload)

        user.token = token

        return user

由于传统的cookie和session的验证机制有些许不足之处本项目使用JWT认证机制,详细原理请点链接查看:https://blog.csdn.net/weixin_43269166/article/details/88381575

登录部分

在Django REST framework JWT提供了登录签发JWTde视图,可以直接使用。
使用时直接定义路由即可:

from rest_framework_jwt.views import obtain_jwt_token

urlpatterns = [
    url(r'^authorizations/$', obtain_jwt_token),
]

但是默认返回的值仅有token,我们还需要在返回值汇总增加username和user_id。通过修改该视图的返回值可以完成我们的需求,故在users/utils.py中创建下列函数:
这个函数是路由在向前端返回的时候会自动调用的

def jwt_response_payload_handler(token, user=None, request=None):
    """
    自定义jwt认证成功返回数据
    """
    return {
        'token': token,
        'user_id': user.id,
        'username': user.username
    }

修改配置文件:指明修改的返回值的函数位置

# JWT
JWT_AUTH = {
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
    'JWT_RESPONSE_PAYLOAD_HANDLER': 'users.utils.jwt_response_payload_handler',
}

如果需要修改登录认证的方式则可以修改Django认证系统的认证后端,需要继承django.contrib.auth.backends.ModelBackend,并重写authenticate方法。
并在配置文件中告知Django使用我们自定义的认证后端

AUTHENTICATION_BACKENDS = [
    'users.utils.UsernameMobileAuthBackend',
]
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值