4.密码设置的多种方式
进一步完善序列化如下:
class UserRegSerializer(serializers.ModelSerializer): '''用户序列化''' code = serializers.CharField(max_length=4, min_length=4, label='验证码', help_text='验证码', error_messages={ 'required': '请输入验证码', 'blank': '请输入验证码', 'max_length': '请输入4位验证码', 'min_length': '请输入4位验证码' }) username = serializers.CharField(required=True, allow_blank=False, label='用户名', validators=[UniqueValidator(queryset=User.objects.filter(is_delete=False), message='用户已经存在')]) password = serializers.CharField(label='密码', style={'input_type': 'password'}) def validate_code(self, code): verify_records = VerifyCode.objects.filter(mobile=self.initial_data['username']).order_by('-add_time') # 验证验证码是否存在 if verify_records: last_record = verify_records[0] five_minute_ago = datetime.now() - timedelta(minutes=5) # 验证验证码是否过期 if five_minute_ago > last_record.add_time: raise serializers.ValidationError('验证码已过期,请重新验证') # 验证验证码是否正确 if last_record.code != code: raise serializers.ValidationError('验证码错误') else: raise serializers.ValidationError('数据有误,请重新验证') def validate(self, attrs): attrs['mobile'] = attrs['username'] del attrs['code'] return attrs class Meta: model = User fields = ['username', 'code', 'mobile', 'password']
此时再访问模拟注册如下:
显然,报错如下:
raise type(exc)(msg) AttributeError: Got AttributeError when attempting to get a value for field `code` on serializer `UserRegSerializer`. The serializer field might be named incorrectly and not match any attribute or key on the `UserProfile` instance. Original exception text was: 'UserProfile' object has no attribute 'code'.
报错提示很明显,UserProfile没有code属性。具体来说,这是因为Meta中指定了fields = ['username', 'code', 'mobile', 'password'],包含code字段,而在验证时为了判断验证码的正误而临时加入code字段,但是在validate(attrs)又将其删去,导致在序列化时找不到code字段,因此出错,这是需要将字段的write_only设置True,以确保在更新或创建实例时可以使用该字段,但是在序列化表示形式时不包括该字段。
同时查询用户表如下:
+----+--------------------------------------------------------------------------------+----------------------------+--------------+-------------+------------+-----------+----------+-----------+----------------------------+------+----------+--------+-------------+-------------+-----------+ | id | password | last_login | is_superuser | username | first_name | last_name | is_staff | is_active | date_joined | name | birthday | gender | mobile | email | is_delete | +----+--------------------------------------------------------------------------------+----------------------------+--------------+-------------+------------+-----------+----------+-----------+----------------------------+------+----------+--------+-------------+-------------+-----------+ | 1 | pbkdf2_sha256$180000$wpfCm77Dcpee$rHfFjBNZ2SzLLHdd0ZtbiIRqNB86VvgwTJv6ZCXTbfk= | 2020-07-28 20:11:10.453289 | 1 | admin | | | 1 | 1 | 2020-07-20 10:12:43.787964 | NULL | NULL | female | | 123@123.com | 0 | | 2 | pbkdf2_sha256$180000$VqEN1rdsS4ts$hgqzLLzxvIk3au1osUB/yrJA5ffFubE87gRBumUAqUE= | NULL | 1 | admin2 | | | 1 | 1 | 2020-07-27 18:46:54.826360 | NULL | NULL | female | | 456@123.com | 0 | | 4 | admin12345 | NULL | 0 | 13388888888 | | | 0 | 1 | 2020-07-28 20:24:01.808609 | NULL | NULL | female | 13388888888 | NULL | 0 | +----+--------------------------------------------------------------------------------+----------------------------+--------------+-------------+------------+-----------+----------+-----------+----------------------------+------+----------+--------+-------------+-------------+-----------+ 3 rows in set (0.01 sec)
显然,刚刚的用户已经保存到用户表中,但是密码为明文,存在很大的风险,需要进行加密设置,可以重载create(validated_data)
实现密码设置即可。除此之外,为了password字段不返回前端,也需要为其加write_only属性,serializers.py完善如下:
class UserRegSerializer(serializers.ModelSerializer): '''用户序列化''' code = serializers.CharField(max_length=4, min_length=4, label='验证码', write_only=True, help_text='验证码', error_messages={ 'required': '请输入验证码', 'blank': '请输入验证码', 'max_length': '请输入4位验证码', 'min_length': '请输入4位验证码' }) username = serializers.CharField(required=True, allow_blank=False, label='用户名', validators=[UniqueValidator(queryset=User.objects.filter(is_delete=False), message='用户已经存在')]) password = serializers.CharField(label='密码', write_only=True, style={'input_type': 'password'}) def create(self, validated_data): user = super(UserRegSerializer, self).create(validated_data=validated_data) user.set_password(validated_data['password']) user.save() return user def validate_code(self, code): verify_records = VerifyCode.objects.filter(mobile=self.initial_data['username']).order_by('-add_time') # 验证验证码是否存在 if verify_records: last_record = verify_records[0] five_minute_ago = datetime.now() - timedelta(minutes=5) # 验证验证码是否过期 if five_minute_ago > last_record.add_time: raise serializers.ValidationError('验证码已过期,请重新验证') # 验证验证码是否正确 if last_record.code != code: raise serializers.ValidationError('验证码错误') else: raise serializers.ValidationError('数据有误,请重新验证') def validate(self, attrs): attrs['mobile'] = attrs['username'] del attrs['code'] return attrs class Meta: model = User fields = ['username', 'code', 'mobile', 'password']
此时再进行测试如下:
显然,测试成功,在提交之后返回数据,查询用户表如下:
+----+--------------------------------------------------------------------------------+----------------------------+--------------+-------------+------------+-----------+----------+-----------+----------------------------+------+----------+--------+-------------+-------------+-----------+ | id | password | last_login | is_superuser | username | first_name | last_name | is_staff | is_active | date_joined | name | birthday | gender | mobile | email | is_delete | +----+--------------------------------------------------------------------------------+----------------------------+--------------+-------------+------------+-----------+----------+-----------+----------------------------+------+----------+--------+-------------+-------------+-----------+ | 1 | pbkdf2_sha256$180000$wpfCm77Dcpee$rHfFjBNZ2SzLLHdd0ZtbiIRqNB86VvgwTJv6ZCXTbfk= | 2020-07-28 20:11:10.453289 | 1 | admin | | | 1 | 1 | 2020-07-20 10:12:43.787964 | NULL | NULL | female | | 123@123.com | 0 | | 2 | pbkdf2_sha256$180000$VqEN1rdsS4ts$hgqzLLzxvIk3au1osUB/yrJA5ffFubE87gRBumUAqUE= | NULL | 1 | admin2 | | | 1 | 1 | 2020-07-27 18:46:54.826360 | NULL | NULL | female | | 456@123.com | 0 | | 4 | admin12345 | NULL | 0 | 13388888888 | | | 0 | 1 | 2020-07-28 20:24:01.808609 | NULL | NULL | female | 13388888888 | NULL | 0 | | 5 | pbkdf2_sha256$180000$dKdR8lvqcymO$OZunKajLJo6q+b3ub+NYNTuKNyOzlz9wGN08DYobUrY= | NULL | 0 | 13377777777 | | | 0 | 1 | 2020-07-28 20:55:39.193938 | NULL | NULL | female | 13377777777 | NULL | 0 | +----+--------------------------------------------------------------------------------+----------------------------+--------------+-------------+------------+-----------+----------+-----------+----------------------------+------+----------+--------+-------------+-------------+-----------+ 4 rows in set (0.00 sec)
显然,新注册的用户密码位密文,而不再是明文。
除了用以上方式实现密码设置,还可以通过Django信号量实现,具体可查看https://docs.djangoproject.com/en/1.10/ref/signals/。其中一类信号是模型信号,django.db.models.signals模块定义了模型系统发送的一组信号,对模型进行操作后,Django会发出全局信号,捕捉到之后可以加入需要的业务逻辑,具体包括pre_init、post_init、pre_save和post_save等,这里我们使用post_save信号实现密码设置。
在apps/users下创建signals.py如下:
from django.db.models.signals import post_save from django.contrib.auth import get_user_model from django.dispatch import receiver from rest_framework.authtoken.models import Token User = get_user_model() @receiver(post_save, sender=User) def create_auth_token(sender, instance=None, created=False, **kwargs): if created: password = instance.password instance.set_password(password) instance.save()
在apps/users/apps.py中进行配置如下:
from django.apps import AppConfig class UsersConfig(AppConfig): name = 'users' verbose_name = '用户管理' def ready(self): import users.signals
serializers.py去掉设置密码的逻辑如下:
class UserRegSerializer(serializers.ModelSerializer): '''用户序列化''' code = serializers.CharField(max_length=4, min_length=4, label='验证码', write_only=True, help_text='验证码', error_messages={ 'required': '请输入验证码', 'blank': '请输入验证码', 'max_length': '请输入4位验证码', 'min_length': '请输入4位验证码' }) username = serializers.CharField(required=True, allow_blank=False, label='用户名', validators=[UniqueValidator(queryset=User.objects.filter(is_delete=False), message='用户已经存在')]) password = serializers.CharField(label='密码', write_only=True, style={'input_type': 'password'}) def validate_code(self, code): verify_records = VerifyCode.objects.filter(mobile=self.initial_data['username']).order_by('-add_time') # 验证验证码是否存在 if verify_records: last_record = verify_records[0] five_minute_ago = datetime.now() - timedelta(minutes=5) # 验证验证码是否过期 if five_minute_ago > last_record.add_time: raise serializers.ValidationError('验证码已过期,请重新验证') # 验证验证码是否正确 if last_record.code != code: raise serializers.ValidationError('验证码错误') else: raise serializers.ValidationError('数据有误,请重新验证') def validate(self, attrs): attrs['mobile'] = attrs['username'] del attrs['code'] return attrs class Meta: model = User fields = ['username', 'code', 'mobile', 'password']
此时在后台进行测试如下:
前台测试如下:
再查询数据库,如下:
+----+--------------------------------------------------------------------------------+----------------------------+--------------+-------------+------------+-----------+----------+-----------+----------------------------+------+----------+--------+-------------+-------------+-----------+ | id | password | last_login | is_superuser | username | first_name | last_name | is_staff | is_active | date_joined | name | birthday | gender | mobile | email | is_delete | +----+--------------------------------------------------------------------------------+----------------------------+--------------+-------------+------------+-----------+----------+-----------+----------------------------+------+----------+--------+-------------+-------------+-----------+ | 1 | pbkdf2_sha256$180000$wpfCm77Dcpee$rHfFjBNZ2SzLLHdd0ZtbiIRqNB86VvgwTJv6ZCXTbfk= | 2020-07-28 20:11:10.453289 | 1 | admin | | | 1 | 1 | 2020-07-20 10:12:43.787964 | NULL | NULL | female | | 123@123.com | 0 | | 2 | pbkdf2_sha256$180000$VqEN1rdsS4ts$hgqzLLzxvIk3au1osUB/yrJA5ffFubE87gRBumUAqUE= | NULL | 1 | admin2 | | | 1 | 1 | 2020-07-27 18:46:54.826360 | NULL | NULL | female | | 456@123.com | 0 | | 4 | admin12345 | NULL | 0 | 13388888888 | | | 0 | 1 | 2020-07-28 20:24:01.808609 | NULL | NULL | female | 13388888888 | NULL | 0 | | 5 | pbkdf2_sha256$180000$dKdR8lvqcymO$OZunKajLJo6q+b3ub+NYNTuKNyOzlz9wGN08DYobUrY= | NULL | 0 | 13377777777 | | | 0 | 1 | 2020-07-28 20:55:39.193938 | NULL | NULL | female | 13377777777 | NULL | 0 | | 6 | pbkdf2_sha256$180000$h6Daqay8tHp3$7Cuw+iigsrqBFldUJybFt8hq5SDwjiwxXhgRMYvs6iw= | NULL | 0 | 13366666666 | | | 0 | 1 | 2020-07-28 21:28:27.957466 | NULL | NULL | female | NULL | | 0 | | 7 | pbkdf2_sha256$180000$JM8j2c0fl81i$DesmherPz0ZUC+orr5kDREVmDJPTc4ahb4vL3Zd/s5s= | NULL | 0 | 13355555555 | | | 0 | 1 | 2020-07-28 21:31:56.062071 | NULL | NULL | female | 13355555555 | NULL | 0 | +----+--------------------------------------------------------------------------------+----------------------------+--------------+-------------+------------+-----------+----------+-----------+----------------------------+------+----------+--------+-------------+-------------+-----------+ 6 rows in set (0.00 sec)
显然,用户均创建成功,并且密码为密文,说明信号成功实现了密码设置。
四、Vue实现注册功能
现在实现前端注册功能,前端src/views/register下定义了注册的组件register.vue,如下:
isRegister(){ var that = this; register({ password:that.password, username:that.mobile , code:that.code, }).then((response)=> { cookie.setCookie('name',response.data.username,7); cookie.setCookie('token',response.data.token,7) //存储在store // 更新store数据 that.$store.dispatch('setInfo'); //跳转到首页页面 this.$router.push({ name: 'index'}) }) .catch(function (error) { that.error.mobile = error.username?error.username[0]:''; that.error.password = error.password?error.password[0]:''; that.error.username = error.mobile?error.mobile[0]:''; that.error.code = error.code?error.code[0]:''; }); },
因为一般在注册成功之后会有两种情况:
一种是注册成功后直接自动登录并跳转到指定页,这里采用的就是这种方式;
另一种是注册后不自动登录,但是跳转到登录页或其他页面,需要自己手动登录,这时只需要注释掉
cookie.setCookie('name',response.data.username,7); cookie.setCookie('token',response.data.token,7) //存储在store // 更新store数据 that.$store.dispatch('setInfo');
部分即可。
这里传递了注册需要用到的3个字段,并且使用了register接口,在api.js中定义修改如下:
//注册 export const register = parmas => { return axios.post(`${local_host}/users/`, parmas) }
在实现注册后自动登录的效果时,还需要设置token,但是后端还并未设置token接口,需要进行配置,views.py配置如下:
class UserViewSet(CreateModelMixin, viewsets.GenericViewSet): '''用户''' serializer_class = UserRegSerializer queryset = User.objects.filter(is_delete=False) def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) user = self.perform_create(serializer) re_dict = serializer.data payload = jwt_payload_handler(user) re_dict['token'] = jwt_encode_handler(payload) re_dict['name'] = user.name if user.name else user.username headers = self.get_success_headers(serializer.data) return Response(re_dict, status=status.HTTP_201_CREATED, headers=headers) def perform_create(self, serializer): return serializer.save()
进行测试如下:
显然,已经可以正常注册并登录。
可以看到,在登录之后可以退出,在src/views/head/head.vue中实现逻辑如下:
<a @click="loginOut">退出</a> ... loginOut(){ // this.$http.get('/getMenu') // .then((response)=> { //跳转到登录 this.$router.push({ name: 'login' }) // }) // .catch(function (error) { // console.log(error); // }); },
src/views/head/shophead.vue如下:
<a @click="loginOut">退出</a> ... loginOut(){ cookie.delCookie('token'); cookie.delCookie('name'); //重新触发store //更新store数据 this.$store.dispatch('setInfo'); //跳转到登录 this.$router.push({name: 'login'}) },
显然,退出登录的逻辑是cookie中删除token和name,并重定向到登录页。