美多商场 - 用户部分 - 2

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/apollo_miracle/article/details/84036268

1 使用Celery完成发送短信

meiduo/meiduo_mall下创建celery_tasks用于保存celery异步任务。

在celery_tasks中创建main.py、config.py文件,sms包(sms包中创建tasks.py文件),最终celery目录结构如下:

sms是专门存放发送短信业务逻辑的代码包

config.py是用于配置broker的

main是celery的主入口文件

在celery_tasks目录下创建config.py文件,用于保存celery的配置信息

broker_url = "redis://127.0.0.1/15"

在celery_tasks目录下创建main.py文件,用于作为celery的启动文件

from celery import Celery

# 为celery使用django配置文件进行设置
import os
if not os.getenv('DJANGO_SETTINGS_MODULE'):
    os.environ['DJANGO_SETTINGS_MODULE'] = 'meiduo_mall.settings.dev'

# 创建celery应用
app = Celery('meiduo')

# 导入celery配置
app.config_from_object('celery_tasks.config')

# 自动注册celery任务
app.autodiscover_tasks(['celery_tasks.sms'])

在celery_tasks目录下创建的sms目录,用于放置发送短信的异步任务相关代码。

将提供的发送短信的云通讯SDK放到celery_tasks/sms/目录下。

在celery_tasks/sms/目录下创建的tasks.py文件,用于保存发送短信的异步任务

将之前的发送短信验证码的代码copy过来:

进行修改,如下:

from celery_tasks.main import celery_app
from meiduo_mall.libs.yuntongxun.sms import CCP
import logging

# celery要执行的任务中有要获取日志looger,就需要知道django项目的配置文件才行
logger = logging.getLogger("django")

# 将这个方法变为异步任务
@celery_app.task(name="send_sms_code")
def send_sms_code(mobile, sms_code, expires, temp_id):
    # 发送短信
    # 使用云通讯第三方平台
    try:
        ccp = CCP()
        # expires = constants.SMS_CODE_REDIS_EXPIRES // 60
        # result = ccp.send_template_sms(mobile, [sms_code, expires], constants.SMS_CODE_TEMP_ID)
        result = ccp.send_template_sms(mobile, [sms_code, expires], temp_id)
    except Exception as e:
        logger.error("发送验证码短信【异常】[ mobile: %s, message: %s]" % (mobile, e))
        # return Response({"message": "failed"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
    else:
        if result == 0:
            logger.error("发送验证码短信【正常】[ mobile: %s]" % mobile)
            # return Response({"message": "OK"})
        else:
            logger.error("发送验证码短信【异常】[ mobile: %s]" % mobile)
            # return Response({"message": "failed"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

注意:这个任务执行,已经不需要给前端返回响应了,所以将return response代码删掉: 

在verifications/views.py中改写SMSCodeView视图,使用celery异步任务发送短信

from celery_tasks.sms import tasks as sms_tasks

class SMSCodeView(GenericAPIView):
    ...
        # 发送短信验证码
        expires = constants.SMS_CODE_REDIS_EXPIRES // 60
        temp_id = constants.SMS_CODE_TEMP_ID
        # 调用这个异步任务
        send_sms_code.delay(mobile, sms_code, expires, temp_id)

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

celery的main是需要单独启动的,启动命令如下:

celery -A celery_tasks.main worker -l info

运行成功之后,有一些提示信息:

transport:就是中间的那个broker:

results:我们先不用管,这里不涉及。

concurrency:4个进程 (concurrency并发,默认4个进程)

当前任务列表和准备好了的消息:

大家在测试celery的时候,如果有修改任务的代码,比如修改了send_sms_code函数的代码,那就需要注意,需要重启worker,不然修改的代码不会生效 

2 判断帐号是否存在

2.1 判断用户名是否存在

后端接口设计:

请求方式: GET usernames/(?P<username>\w{5,20})/count/

请求参数: 路径参数

参数 类型 是否必传 说明
username str 用户名

返回数据: JSON

{
    "username": "apollo",
    "count": "1"
}
返回值 类型 是否必须 说明
username str 用户名
count int 数量

2.1.1 后端实现

在users/views.py中定义视图

# url(r'^usernames/(?P<username>\w{5,20})/count/$', views.UsernameCountView.as_view()), 
class UsernameCountView(APIView):
    """
    用户名数量
    """
    def get(self, request, username):
        """
        获取指定用户名数量
        """
        count = User.objects.filter(username=username).count()

        data = {
            'username': username,
            'count': count
        }

        return Response(data)

这里没有用到GenericAPIView,是因为逻辑本身就比较简单,如果用GenericAPIView还得定义序列化器,所以就不用了,直接继承APIView。 

2.1.2 前端实现

在js/register.js中修改

    // 检查用户名
    check_username: function (){
            var len = this.username.length;
            if(len<5||len>20) {
                this.error_name_message = '请输入5-20个字符的用户名';
                this.error_name = true;
            } else {
                this.error_name = false;
            }
            // 检查重名
            if (this.error_name == false) {
                axios.get(this.host + '/usernames/' + this.username + '/count/', {
                        responseType: 'json'
                    })
                    .then(response => {
                        if (response.data.count > 0) {
                            this.error_name_message = '用户名已存在';
                            this.error_name = true;
                        } else {
                            this.error_name = false;
                        }
                    })
                    .catch(error => {
                        console.log(error.response.data);
                    })
            }
        },

2.2 判断手机号是否存在:

后端接口设计:

请求方式: GET mobiles/(?P<mobile>1[3-9]\d{9})/count

请求参数: 路径参数

参数 类型 是否必须 说明
mobile str 手机号

返回数据: JSON

{
    "mobile": "18512345678",
    "count": 0
}
返回值 类型 是否必须 说明
mobile str 手机号
count int 数量

2.2.1 后端实现

在users/views.py中定义视图

# url(r'^mobiles/(?P<mobile>1[3-9]\d{9})/count/$', views.MobileCountView.as_view()),
class MobileCountView(APIView):
    """
    手机号数量
    """
    def get(self, request, mobile):
        """
        获取指定手机号数量
        """
        count = User.objects.filter(mobile=mobile).count()

        data = {
            'mobile': mobile,
            'count': count
        }

        return Response(data)

2.2.2 前端实现

在js/register.js中修改

    // 检查手机号
    check_phone: function (){
            var re = /^1[345789]\d{9}$/;
            if(re.test(this.mobile)) {
                this.error_phone = false;
            } else {
                this.error_phone_message = '您输入的手机号格式不正确';
                this.error_phone = true;
            }
            if (this.error_phone == false) {
                axios.get(this.host + '/mobiles/'+ this.mobile + '/count/', {
                        responseType: 'json'
                    })
                    .then(response => {
                        if (response.data.count > 0) {
                            this.error_phone_message = '手机号已存在';
                            this.error_phone = true;
                        } else {
                            this.error_phone = false;
                        }
                    })
                    .catch(error => {
                        console.log(error.response.data);
                    })
            }
        },

注意:注册路由 

3 注册

3.1 后端接口设计:

请求方式: POST /users/

请求参数: JSON 或 表单

参数名 类型 是否必须 说明
username str 用户名
password str 密码
password2 str 确认密码
sms_code str 短信验证码
mobile str 手机号
allow str 是否同意用户协议

返回数据: JSON

{
    "id": 9,
    "username": "python8",
    "mobile": "18512345678",
}
返回值 类型 是否必须 说明
id int 用户id
username str 用户名
mobile str 手机号

视图原型

# url(r'^users/$', views.UserView.as_view()),
class UserView(CreateAPIView):
    """
    用户注册
    传入参数:
        username, password, password2, sms_code, mobile, allow
    """
    接收参数
    校验参数
    保存用户数据,密码加密
    序列化,返回数据

(CreateAPIView) 相当于(CreateModelMixin, GenericAPIView)

指定序列化器,定义post方法,post方法中调用CreateModelMixin的create方法即可 

3.2 后端实现

在users/serializers.py中创建序列化器对象

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)

    class Meta:
        model = User
        fields = ('id', 'username', 'password', 'password2', 'sms_code', 'mobile', 'allow')
        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个字符的密码',
                }
            }
        }

fields中的字段并非全是User模型类中的字段,而且还包含了上边自己定义的属性。 

验证方法如下:

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

 这个序列化器处理做校验,需要创建对象,而ModelSerializer中有create方法,我们重写即可:

 def create(self, validated_data):
        """
        重写保存方法,增加密码加密
        """
        # 移除数据库模型类中不存在的属性
        del validated_data['password2']
        del validated_data['sms_code']
        del validated_data['allow']
        user = super().create(validated_data)

        # 调用django的认证系统加密密码
        user.set_password(validated_data['password'])
        user.save()

        return user
  • set_password(raw_password)

    设置用户的密码为给定的原始字符串,并负责密码的。 不会保存User 对象。当Noneraw_password 时,密码将设置为一个不可用的密码。

  • check_password(raw_password)

    如果给定的raw_password是用户的真实密码,则返回True,可以在校验用户密码时使用。

在users/views.py中定义视图

class UserView(CreateAPIView):
    """
    用户注册
    """
    serializer_class = serializers.CreateUserSerializer

注意:注册路由 

3.3 前端编写

修改js/register.js

        // 注册
        on_submit: function(){
            this.check_username();
            this.check_pwd();
            this.check_cpwd();
            this.check_phone();
            this.check_sms_code();
            this.check_allow();

            if(this.error_name == false && this.error_password == false && this.error_check_password == false 
                && this.error_phone == false && this.error_sms_code == false && this.error_allow == false) {
                axios.post(this.host + '/users/', {
                        username: this.username,
                        password: this.password,
                        password2: this.password2,
                        mobile: this.mobile,
                        sms_code: this.sms_code,
                        allow: this.allow.toString()
                    }, {
                        responseType: 'json'
                    })
                    .then(response => {
                        location.href = '/index.html';    
                    })
                    .catch(error=> {
                        if (error.response.status == 400) {
                            if ('non_field_errors' in error.response.data) {
                                this.error_sms_code_message = error.response.data.non_field_errors[0];
                            } else {
                                this.error_sms_code_message = '数据有误';
                            }
                            this.error_sms_code = true;
                        } else {
                            console.log(error.response.data);
                        }
                    })
            }
        }

猜你喜欢

转载自blog.csdn.net/apollo_miracle/article/details/84036268
今日推荐