Example #1
0
	def get(self, request):
		access_token = request.query_params.get('access_token')
		if not access_token:
			return Response({'message': 'token不存在'}, 400)
		# 从access_token中取回取到手机号
		mobile = User.check_access_token_send_sms(access_token)
		if not mobile:
			return Response({'message': 'token失效或不存在'}, 400)
		# 60s发送一次
		redis_conn = get_redis_connection('verify_codes')
		send_flag = redis_conn.get("send_flag_%s" % mobile)
		if send_flag == 1:
			return Response({"message": "请求次数过于频繁"}, 429)

		sms_code = '%06d' % random.randint(0, 999999)
		logger.error('--->短信验证码:[%s]<---' % sms_code)

		pl = redis_conn.pipeline()
		pl.setex('sms_%s' % mobile, SMS_CODE_REDIS_EXPIRES, sms_code)
		pl.setex("send_flag_%s" % mobile, SEND_SMS_CODE_INTERVAL, 1)
		pl.execute()

		# 发送短信
		# try:
		# 	send_mes = SendMes()
		# 	send_mes.send_2_mes(mobile, sms_code)
		# except Exception as e:
		# 	logger.error(e)
		# 异步发短信
		return Response({'message': 'ok'})
Example #2
0
    def validate(self, data):
        # 检验access_token
        access_token = data['access_token']
        openid = OAuthQQ.check_token_by_openid(access_token)
        if not openid:
            raise serializers.ValidationError('access_token失效')
        # 检验短信验证码
        mobile = data['mobile']
        sms_code = data['sms_code']
        redis_conn = get_redis_connection('verify_codes')
        real_sms_code = redis_conn.get('sms_%s' % mobile).decode()
        if not real_sms_code:
            raise serializers.ValidationError('短信验证码失效或过期')

        if real_sms_code != sms_code:
            raise serializers.ValidationError('短信验证码错误')

        # 如果用户存在,检查用户密码
        try:
            user = User.objects.get(mobile=mobile)
        except Exception as e:
            logger.error('本站用户不存在,等待注册---%s' % e)
            pass
        else:
            # 如果存在就校验密码
            password = data['password']
            if not user.check_password(password):
                raise serializers.ValidationError('密码错误')
            data['user'] = user

        data['openid'] = openid
        return data
Example #3
0
    def validate(self, attrs):
        #接收具体的校验数据
        image_code=attrs['image_code']
        image_code_id=attrs['image_code_id']

        # 从redis中获取真实图片验证码
        redis_conn=get_redis_connection('verify_codes')
        real_image_code_text=redis_conn.get('img_%s' % image_code_id)

        # 如果根据当前的image_code_id获取不到值
        if not real_image_code_text:
            raise serializers.ValidationError('图片验证码无效')

        # 图形验证码只能使用一次,所以接下来,需要删除验证码
        try:
            redis_conn.delete('img_%s'%image_code_id)
        except RedisError as e:
            logger.error(e)

        # python中直接从redis中读取到的数据都是bytes类型
        real_image_code_text=real_image_code_text.decode()
        # 比较图片验证码
        if real_image_code_text.lower() !=image_code.lower():
            raise serializers.ValidationError('图片验证码错误')

        #检查是否在60s内有发送记录
        #在序列化器中要获取数据
        mobile=self.context['view'].kwargs.get('mobile')
        if mobile:
            send_flag=redis_conn.get('send_flag_%s'% mobile)
            #如果redis中有这个数据则标识60s发送过短信
            if send_flag:
                raise serializers.ValidationError("请求过于频繁")

        return attrs
Example #4
0
    def validate(self, attrs):
        '''
        校验
        '''
        image_code_id = attrs['image_code_id']
        text = attrs['text']
        # 查询真实图片验证码
        redis_conn = get_redis_connection('verify_codes')
        real_image_code_text = redis_conn.get('img_%s' % image_code_id)
        # 校验图片验证码
        if not real_image_code_text:
            raise serializers.ValidationError('图片验证码无效')
        # 删除图片验证码
        try:
            redis_conn.delete('img_%s' % image_code_id)
        except RedisError as e:
            from meiduo_mall.utils.exceptions import logger
            logger.error(e)
        # 对比图片验证码
        real_image_code_text = real_image_code_text.decode()
        if text.lower() != real_image_code_text.lower():
            raise serializers.ValidationError('图片验证码错误')
        # 判断是否在60s内
        mobile = self.context['view'].kwargs['mobile']
        send_flag = redis_conn.get('send_flag_' + mobile)
        if send_flag:
            raise serializers.ValidationError('请求次数过于频繁')

        return attrs
Example #5
0
    def get(self, request, mobile):

        # 1创建连接到redis的对象
        redis_conn = get_redis_connection('verify_codes')

        # 2,60秒内不允许重发短信
        send_flag = redis_conn.get('send_flag_%s' % mobile)

        if send_flag:

            return Response({"message": "发送短信过于频繁"}, status=status.HTTP_400_BAD_REQUEST)

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

        try:

            # 发送短信的异步任务必须通过delay调用
            tasks.send_sms_code.delay(mobile, sms_code, 5)

        except Exception as e:
            # 发送短信失败, 记录错误信息到日志中
            logger.error('发送短信失败!%s:%s' % (mobile, sms_code))
            return Response({"message": "发送短信失败!"}, status=status.HTTP_502_BAD_GATEWAY)

        # 4以下代码演示redis管道pipeline的使用
        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, constants.SEND_SMS_TEMPLATE_ID)
        # 执行
        pl.execute()

        # 响应发送短信验证码结果
        return Response({"message": "OK"})
Example #6
0
	def get(self, request, order_id):
		# 校验order_id
		try:
			order_obj = OrderInfo.objects.get(order_id=order_id)
		except Exception as e:
			logger.error(e)
			return Response({'message': 'order_id错误'})

		app_private_key_string = open(
			os.path.join(os.path.dirname(os.path.abspath(__file__)), "keys/app_private_key.pem")).read()
		alipay_public_key_string = open(
			os.path.join(os.path.dirname(os.path.abspath(__file__)), "keys/alipay_public_key.pem")).read()
		# 调用python-alipay-sdk中的类创建alipay对象
		alipay = AliPay(
			appid="2016091700529506",  # 沙箱的appid
			app_notify_url=None,  # 默认回调url
			app_private_key_string=app_private_key_string,
			alipay_public_key_string=alipay_public_key_string,
			sign_type="RSA2",  # RSA 或者 RSA2
			debug=True  # 默认False
		)

		# 调用python-alipay-sdk中的api_alipay_trade_page_pay生成用于支付的链接地址查询字符串
		order_string = alipay.api_alipay_trade_page_pay(
			out_trade_no=order_id,
			total_amount=str(order_obj.total_amount),
			subject='MeiDuoTest_%s' % order_id,
			return_url="http://www.meiduo.site:8080/pay_success.html",
			# notify_url="https://example.com/notify"  # 可选, 不填则使用默认notify url
		)
		alipay_url = 'https://openapi.alipaydev.com/gateway.do?' + order_string
		# print(alipay_url)
		return Response({'alipay_url': alipay_url})
Example #7
0
    def get_access_token(self, code):
        """
        获取acces_token
        :param code: qq提供的code
        :return: access_token
        """
        params = {
            'grant_type': 'authorization_code',
            'client_id': self.client_id,
            'client_secret': self.client_secret,
            'code': code,
            'redirect_uri': self.redirect_uri,
        }

        url = 'https://graph.qq.com/oauth2.0/token?' + urllib.parse.urlencode(
            params)
        # urlopen发送http请求
        try:
            response = urlopen(url)
            # 读取相应体数据, 转换为str类型
            response_data = response.read().decode()  # str

            # 解析access_token
            response_dict = urllib.parse.parse_qs(response_data)
        except Exception as e:
            logger.error('获取access_token异常: %s' % e)
            raise OAuthQQAPIError
        else:
            access_token = response_dict.get('access_token')
            return access_token[0]
Example #8
0
    def validate(self, attrs):
        """额外校验"""
        # 取出需要反序列化的值
        image_code_id = attrs['image_code_id']
        text = attrs['text']
        logger.error(self.context)

        # 取出真实的图片验证码
        redis_conn = get_redis_connection('verify_codes')
        real_text = redis_conn.get('img_%s' % image_code_id)

        try:
            redis_conn.delete('img_%s' % image_code_id)
        except Exception as e:
            logger.error('redis删除异常:%s' % e)
            pass

        if not real_text:
            raise serializers.ValidationError('图片验证码失效')

        if text.lower() != real_text.decode().lower():
            raise serializers.ValidationError('图片验证码输入错误')

        # {'request':obj,'format':'bbb','view':'obj'}
        # mobile = self.context['view'].kwargs['mobile']

        # 想要在找回密码时复用代码,而此时没有mobile,所以需要判断
        mobile = self.context['view'].kwargs.get('mobile', None)
        if mobile:
            # 图片验证码只能使用一次
            if redis_conn.get("send_flag_%s" % mobile) == 1:
                raise serializers.ValidationError('一分钟一次')

        return attrs
Example #9
0
 def validate_sku_id(self, value):
     try:
         SKU.objects.get(id=value)
     except Exception as e:
         logger.error(e)
         raise serializers.set_value('商品不存在')
     return value
Example #10
0
 def get(self, request, image_code_id):
     text, image = captcha.generate_captcha()
     logger.error('--->图片验证码:[%s]<---' % text)
     logger.error('--->UUID:[%s]<---' % image_code_id)
     redis_conn = get_redis_connection('verify_codes')
     redis_conn.setex('img_%s' % image_code_id, IMAGE_CODE_REDIS_EXPIRES,
                      text)
     return HttpResponse(image, content_type='images/jpg')
Example #11
0
	def check_access_token_send_sms(token):
		"""校验access_token获取真实的当前用户mobile"""
		serializer = TimedJSONWebSignatureSerializer(settings.SECRET_KEY, 300)
		try:
			data = serializer.loads(token)
		except Exception as e:
			logger.error('解析mobile异常%s' % e)
			return None
		else:
			return data.get('mobile')
Example #12
0
    def validate(self, data):
        try:
            sku = SKU.objects.get(id=data['sku_id'])
        except Exception as e:
            logger.error(e)
            raise serializers.ValidationError('商品不存在')

        if data['count'] > sku.stock:
            raise serializers.ValidationError('商品库存不足')

        return data
Example #13
0
	def check_access_token_reset_password(user_id, token):
		"""校验access_token获取真实的当前用户mobile"""
		serializer = TimedJSONWebSignatureSerializer(settings.SECRET_KEY, 300)
		try:
			data = serializer.loads(token)
		except Exception as e:
			logger.error('解析user_id异常%s' % e)
			return False
		else:
			if user_id != str(data.get('user_id')):
				return False
			else:
				return True
Example #14
0
    def put(self, request):
        serializer = CartSelectSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        selected = serializer.validated_data.get('selected')
        # 判断登录状态
        try:
            user = request.user
        except Exception as e:
            logger.error(e)
            user = None
        # 登录修改redis
        if user is not None and user.is_authenticated:
            redis_conn = get_redis_connection('cart')
            pl = redis_conn.pipeline()
            # 取出所有sku_id
            cart_dict = pl.hgetall('cart_%s' % user.id)
            sku_id_list = cart_dict.keys()

            if selected:
                # 勾选增加记录
                pl.sadd('cart_selected_%s' % user.id, *sku_id_list)
            else:
                # 未勾选 删除记录
                pl.srem('cart_selected_%s' % user.id, *sku_id_list)
            pl.execute()

            return Response({'message': 'ok'})
        # 未登录修改cookie
        else:
            cart_str = request.COOKIES.get('cart')
            response = Response({'message': 'ok'})
            if cart_str is not None:
                cart_dict = pickle.loads(base64.b64decode(cart_str.encode()))
                # {
                # 	sku_id:{'count':count,'selected':selected}
                # }
                sku_id_list = cart_dict.keys()
                for sku_id in sku_id_list:
                    cart_dict[sku_id]['selected'] = selected
                # 最后编码成str设置到cookie中保存
                cookie_cart_str = base64.b64encode(
                    pickle.dumps(cart_dict)).decode()
                response.set_cookie('cart',
                                    cookie_cart_str,
                                    max_age=365 * 24 * 60 * 60)
            return response
Example #15
0
    def put(self, request):
        serializer = AddSkuSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        sku_id = serializer.validated_data.get('sku_id')
        count = serializer.validated_data.get('count')
        selected = serializer.validated_data.get('selected')
        # 判断登录状态
        try:
            user = request.user
        except Exception as e:
            logger.error(e)
            user = None
        # 登录修改redis
        if user is not None and user.is_authenticated:
            redis_conn = get_redis_connection('cart')
            pl = redis_conn.pipeline()
            # 修改数量
            pl.hset('cart_%s' % user.id, sku_id, count)
            if selected:
                # 勾选增加记录
                pl.sadd('cart_selected_%s' % user.id, sku_id)
            else:
                # 未勾选 删除记录
                pl.srem('cart_selected_%s' % user.id, sku_id)
            pl.execute()

            return Response(serializer.data)
        # 未登录修改cookie
        else:
            cart = request.COOKIES.get('cart')
            if cart is not None:
                cart = pickle.loads(base64.b64decode(cart.encode()))
                cart[sku_id]['count'] = count
                cart[sku_id]['selected'] = selected

            else:
                cart = {}

            # 最后编码成str设置到cookie中保存
            cookie_cart = base64.b64encode(pickle.dumps(cart)).decode()
            response = Response(serializer.data, 201)
            response.set_cookie('cart',
                                cookie_cart,
                                max_age=365 * 24 * 60 * 60)
            return response
Example #16
0
	def get(self, request):
		"""QQ登录"""
		code = request.query_params.get('code')
		if not code:
			return Response({'message': 'code不存在'}, 400)

		# 目标是通过 code获取access_token
		oauthqq = OAuthQQ()
		access_token = oauthqq.get_qq_access_token(code)

		# 通过access_token获取openid
		openid = oauthqq.get_qq_openid(access_token)

		# 获取openid后需要判断
		# oauthqquser = OAuthQQUser.get

		try:
			oauthqquser = OAuthQQUser.objects.get(openid=openid)
		except Exception as e:
			logger.error('此人未绑定或未注册:%s' % e)
			# 1.第一次用qq登录
			# 使用openid生成记录qq身份的token,以便注册或绑定时验证身份
			access_token = OAuthQQ.generate_save_user_token(openid)
			return Response({'access_token': access_token})
		# 1.1 已经注册本站账号--->跳转绑定界面
		# 1.2 未注册本站账号--->注册并绑定

		# 2.以前已经qq登录过(一定有本站账号)
		else:
			user = oauthqquser.user
			# 生成jwt_token,用于记录登录状态
			jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
			jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
			payload = jwt_payload_handler(user)
			jwt_token = jwt_encode_handler(payload)

			data = {
				'user_id': user.id,
				'username': user.username,
				'token': jwt_token
			}
			response = Response(data=data)
			response = merge_cookie_to_redis(request, user, response)
			return response
Example #17
0
	def check_verify_email_token(token):
		"""
		检查验证邮件的token
		"""
		serializer = TimedJSONWebSignatureSerializer(settings.SECRET_KEY, 300)
		try:
			data = serializer.loads(token)
		except Exception as e:
			logger.error(e)
			return None
		else:
			email = data.get('email')
			user_id = data.get('user_id')
			try:
				user = User.objects.get(id=user_id, email=email)
			except User.DoesNotExist:
				return None
			else:
				return user
Example #18
0
    def delete(self, request):
        serializer = DeleteCartSeralizer(data=request.data)
        serializer.is_valid(raise_exception=True)
        sku_id = serializer.validated_data.get('sku_id')

        try:
            user = request.user
        except Exception as e:
            logger.error(e)
            user = None

        if user is not None and user.is_authenticated:
            redis_conn = get_redis_connection('cart')
            pl = redis_conn.pipeline()
            pl.hdel('cart_%s' % user.id, sku_id)
            pl.srem('cart_selected_%s' % user.id, sku_id)
            pl.execute()
            return Response(status=204)
        else:
            # cookie
            cart_cookie = request.COOKIES.get('cart')

            if cart_cookie:

                cart_dict = pickle.loads(base64.b64decode(
                    cart_cookie.encode()))
            else:
                cart_dict = {}

            response = Response(serializer.data)

            if sku_id in cart_dict:
                # 删除字典的键值对
                del cart_dict[sku_id]

                cookie_cart = base64.b64encode(
                    pickle.dumps(cart_dict)).decode()

                response.set_cookie('cart', cookie_cart)

            return response
Example #19
0
    def get_openid(self, access_token):
        """获取openid"""
        url = 'https://graph.qq.com/oauth2.0/me?access_token=' + access_token

        try:
            response = urlopen(url)
            # 读取相应体数据, 转换为str类型
            response_data = response.read().decode()  # str

            # 返回的数据 callback( {"client_id":"YOUR_APPID","openid":"YOUR_OPENID"} )\n;

            # 解析
            response_data = response_data[10:-4]
            response_dict = json.loads(response_data)
        except Exception as e:
            logger.error('获取openid异常: %s' % e)
            raise OAuthQQAPIError
        else:
            openid = response_dict.get('openid', None)

        return openid
Example #20
0
    def get(self, request):

        code = request.query_params.get("code")

        if not code:
            return Response({"message": "缺少code"},
                            status=status.HTTP_400_BAD_REQUEST)
        oauth = OAuthQQ(client_id=settings.QQ_CLIENT_ID,
                        client_secret=settings.QQ_CLIENT_SECRET,
                        redirect_uri=settings.QQ_REDIRECT_URI)
        # 通过code向qq服务器请求获取access_token
        try:
            access_token = oauth.get_access_token(code)
            openid = oauth.get_open_id(access_token)
        except Exception:
            logger.error("向qq服务器请求失败")
            return Response({'message': 'QQ服务异常'},
                            status=status.HTTP_503_SERVICE_UNAVAILABLE)
        try:
            oauth_user = OAuthQQUser.objects.get(openid=openid)
        except OAuthQQUser.DoesNotExist:
            # 如果openid没绑定美多商城用户,创建用户并绑定到openid
            # 为了能够在后续的绑定用户操作中前端可以使用openid,在这里将openid签名后响应给前端
            access_token_openid = generate_save_user_token(openid)
            return Response({'access_token': access_token_openid})
        else:
            # 如果openid已绑定美多商城用户,直接生成JWT token,并返回
            user = oauth_user.user
            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)

            response = Response({
                'token': token,
                'user_id': user.id,
                'username': user.username
            })

            return response
Example #21
0
    def get(self, request):
        # 1. 获取QQ返回的code
        code = request.query_params.get('code')

        try:
            # 2. 根据code获取access_token
            oauth = OAuthQQ()
            access_token = oauth.get_access_token(code)
            # 3. 根据access_token获取授权QQ用户的openid
            openid = oauth.get_openid(access_token)
        except BaseException as e:
            logger.error(e)
            return Response({'message': 'qq服务异常'},
                            status=status.HTTP_503_SERVICE_UNAVAILABLE)

        # 4. 根据openid 查询tb_oatu_qq表,判断用户是否已经绑定账号
        try:
            oauth_user = OAuthQQUser.objects.get(openid=openid)
        except OAuthQQUser.DoesNotExist:
            # 4.2 未绑定  返回token
            token = oauth.generate_save_user_token(openid)
            return Response({'access_token': token})
        else:
            # 4.1 已绑定 生成JWT token
            # 补充生成记录登录状态的token
            user = oauth_user.user
            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)

            response = Response({
                'token': token,
                'user_id': user.id,
                'username': user.username
            })
            response = merge_cart_cookie_to_redis(request, user, response)

            return response
Example #22
0
    def get(self, request):
        # 判断用户的登录状态,
        try:
            user = request.user
        except Exception as e:
            logger.error(e)
            user = None
        # 如果已经登录从redis中查询
        if user is not None and user.is_authenticated:
            redis_conn = get_redis_connection('cart')
            # 分别查询两张表,byte类型
            sku_id_count_dict = redis_conn.hgetall('cart_%s' % user.id)
            selected_list = redis_conn.smembers('cart_selected_%s' % user.id)
            # 调整
            cart = {}
            for sku_id, count in sku_id_count_dict.items():
                cart[int(sku_id)] = {
                    'selected': sku_id in selected_list,
                    'count': int(count),
                }

        # 没有登录从cookie中查询
        else:
            cart_cookie = request.COOKIES.get('cart')
            if cart_cookie is not None:
                cart = pickle.loads(base64.b64decode(cart_cookie.encode()))
            else:
                cart = {}
        # 返回数据给前端
        sku_query_set = SKU.objects.filter(id__in=cart.keys())
        for sku in sku_query_set:
            # 再返回的数据中新增不在model中的额外字段
            sku.count = cart[sku.id]['count']
            sku.selected = cart[sku.id]['selected']

        serializer = CartSKUSerializer(sku_query_set, many=True)
        return Response(serializer.data)
Example #23
0
    def get(self, request, mobile):
        # 需要校验的参数,{'image_code_id':xxx,'text':yyy}
        data = request.query_params
        serializer = self.get_serializer(data=data)
        serializer.is_valid(raise_exception=True)
        # 校验成功后,生成并保存真实的短信验证码到redis
        sms_code = '%06d' % random.randint(0, 999999)
        logger.error('--->短信验证码:[%s]<---' % sms_code)

        redis_conn = get_redis_connection('verify_codes')
        pl = redis_conn.pipeline()
        pl.setex('sms_%s' % mobile, SMS_CODE_REDIS_EXPIRES, sms_code)
        pl.setex("send_flag_%s" % mobile, SEND_SMS_CODE_INTERVAL, 1)
        pl.execute()

        # 发送短信
        # try:
        # 	send_mes = SendMes()
        # 	send_mes.send_2_mes(mobile, sms_code)
        # except Exceptione:
        # 	logger.error(e)
        # 异步发短信
        send_to_mes.delay(mobile, sms_code)
        return Response({'message': 'OK'})
Example #24
0
    def validate(self, attrs):  # 对多个字段进行校验
        """校验"""

        image_code_id = attrs["image_code_id"]
        text = attrs["text"]

        # 查询真实图片验证码
        redis_conn = get_redis_connection('verify_codes')  # 连接到redis
        real_image_code_text = redis_conn.get('img_%s' %
                                              image_code_id)  # 根据键名取出图片验证码

        if not real_image_code_text:
            raise serializers.ValidationError("图片验证失败")

        # 验证一次后就从redis删除图片验证码
        try:
            redis_conn.delete('img_%s' % image_code_id)
        except Exception as e:
            logger.error(e)

        # 比较图片验证码
        real_image_code_text = real_image_code_text.decode()

        if real_image_code_text.lower() != text.lower():
            raise serializers.ValidationError("图片验证码错误")

        # 判断是否在60s以内
        # 知识点1: get_serializer()方法在创建序列化器对象的时候会补充 context属性
        #          context属性包含三个值: request formet view
        #  知识点2:
        # django 的类视图中, kwrags包含了路径提取出来的参数
        mobile = self.context["view"].kwargs[
            "mobile"]  # 理解: 获取序列化器的视图对象中的mobile属性
        send_flag = redis_conn.get("send_flag_%s" % mobile)

        return attrs
Example #25
0
    def create(self, validated_data):
        """
        保存订单
        """
        # 获取当前用户
        user = self.context["request"].user

        # 生成订单编号
        # 组织订单编号 20170903153611+user.id
        # timezone.now()  -> datetime
        # time.strftime(format[,t]) 接收以时间元组,并返回以可读字符串表示的当地时间,格式由参数format决定
        # order_id = timezone.now().strftime("%Y%m%d%H%M%S") + ("%09d" % user.id)
        order_id = timezone.now().strftime('%Y%m%d%H%M%S') + ('%09d' % user.id)

        # 保存订单基本信息数据OrderInfo
        address = validated_data["address"]
        pay_method = validated_data["pay_method"]

        # 生成订单
        with transaction.atomic():
            # 创建一个保存点
            save_id = transaction.savepoint()

            try:
                # 创建订单信息
                order = OrderInfo.objects.create(
                    order_id=order_id,
                    user=user,
                    address=address,
                    total_count=0,
                    total_amount=Decimal(10),
                    freight=Decimal(10),
                    pay_method=pay_method,
                    status=OrderInfo.ORDER_STATUS_ENUM['UNSEND']
                    if pay_method == OrderInfo.PAY_METHODS_ENUM['CASH'] else
                    OrderInfo.ORDER_STATUS_ENUM['UNPAID'])

                # 获取购物车信息
                redis_con = get_redis_connection("cart")
                redis_cart = redis_con.hgetall("cart_%s" % user.id)
                cart_selected = redis_con.smembers("cart_selected_%s" %
                                                   user.id)

                # 将bytes类型转换成int类型
                cart = {}
                for sku_id in cart_selected:
                    cart[int(sku_id)] = int(redis_cart[sku_id])

                # 从redis中一次查出所有商品数据
                skus = SKU.objects.filter(id__in=cart_selected)

                # 遍历结算商品
                for sku in skus:
                    while True:
                        sku_count = cart[sku.id]  # 判断商品库存

                        origin_stock = sku.stock  # 原始库存
                        origin_sales = sku.sales  # 原始销量

                        if sku_count > origin_stock:
                            transaction.savepoint_rollback(save_id)  # 回滚节点
                            raise serializers.ValidationError("商品库存不足")

                        # 演示并发下单
                        # import time
                        # time.sleep(5)

                        # 减少库存 增加销量
                        new_stock = origin_stock - sku_count
                        new_sales = origin_sales + sku_count

                        # 加入乐观锁, 根据原始库存条件更新, 返回更新的条目数
                        ret = SKU.objects.filter(
                            id=sku.id,
                            stock=origin_stock).update(stock=new_stock)

                        if ret == 0:  # 说明没有更新,结束本次循环执行下一次
                            continue

                        # 累计商品的SPU 销量信息
                        sku.goods.sales += sku_count
                        sku.goods.save()

                        # 累计订单基本信息的数据
                        order.total_count += sku_count  # 累计总金额
                        order.total_amount += (sku.price * sku_count)  # 累计总额

                        # 保存订单商品数据
                        OrderGoods.objects.create(
                            order=order,
                            sku=sku,
                            count=sku_count,
                            price=sku.price,
                        )

                        break  # 更新成功

                    # 更新订单的金额数量信息
                    order.total_amount += order.freight
                    order.save()

            except serializers.ValidationError:
                raise
            except Exception as e:
                logger.error(e)
                transaction.savepoint_rollback(save_id)  # 回滚事务
                raise

            # 提交事务
            transaction.savepoint_commit(save_id)

            # 删除redis中保存的购物车数据
            pl = redis_con.pipeline()
            pl.hdel('cart_%s' % user.id, *cart_selected)
            pl.srem("cart_selected_%s" % user.id, *cart_selected)
            pl.execute()
            return order
Example #26
0
    def create(self, validated_data):
        """
        保存订单
        """
        # 获取当前下单用户
        user = self.context['request'].user

        # 组织订单编号 20170903153611+user.id
        # timezone.now() -> datetime
        order_id = timezone.now().strftime('%Y%m%d%H%M%S') + ('%09d' % user.id)

        address = validated_data['address']
        pay_method = validated_data['pay_method']

        # 生成订单
        with transaction.atomic():
            # 创建一个保存点
            save_id = transaction.savepoint()

            try:
                # 创建订单信息
                order = OrderInfo.objects.create(
                    order_id=order_id,
                    user=user,
                    address=address,
                    total_count=0,
                    total_amount=Decimal(0),
                    freight=Decimal(10),
                    pay_method=pay_method,
                    status=OrderInfo.ORDER_STATUS_ENUM['UNSEND']
                    if pay_method == OrderInfo.PAY_METHODS_ENUM['CASH'] else
                    OrderInfo.ORDER_STATUS_ENUM['UNPAID'])
                # 获取购物车信息
                redis_conn = get_redis_connection("cart")
                redis_cart = redis_conn.hgetall("cart_%s" % user.id)
                cart_selected = redis_conn.smembers('cart_selected_%s' %
                                                    user.id)

                # 将bytes类型转换为int类型
                cart = {}
                for sku_id in cart_selected:
                    cart[int(sku_id)] = int(redis_cart[sku_id])

                # # 一次查询出所有商品数据
                # skus = SKU.objects.filter(id__in=cart.keys())

                # 处理订单商品
                sku_id_list = cart.keys()
                for sku_id in sku_id_list:
                    while True:
                        sku = SKU.objects.get(id=sku_id)

                        sku_count = cart[sku.id]

                        # 判断库存
                        origin_stock = sku.stock  # 原始库存
                        origin_sales = sku.sales  # 原始销量

                        if sku_count > origin_stock:
                            transaction.savepoint_rollback(save_id)
                            raise serializers.ValidationError('商品库存不足')

                        # 用于演示并发下单
                        # import time
                        # time.sleep(5)

                        # 减少库存
                        # sku.stock -= sku_count
                        # sku.sales += sku_count
                        # sku.save()
                        new_stock = origin_stock - sku_count
                        new_sales = origin_sales + sku_count

                        # 根据原始库存条件更新,返回更新的条目数,乐观锁
                        ret = SKU.objects.filter(id=sku.id,
                                                 stock=origin_stock).update(
                                                     stock=new_stock,
                                                     sales=new_sales)
                        if ret == 0:
                            continue

                        # 累计商品的SPU 销量信息
                        sku.goods.sales += sku_count
                        sku.goods.save()

                        # 累计订单基本信息的数据
                        order.total_count += sku_count  # 累计总金额
                        order.total_amount += (sku.price * sku_count)  # 累计总额

                        # 保存订单商品
                        OrderGoods.objects.create(
                            order=order,
                            sku=sku,
                            count=sku_count,
                            price=sku.price,
                        )

                        # 更新成功
                        break

                # 更新订单的金额数量信息
                order.total_amount += order.freight
                order.save()

            except serializers.ValidationError:
                raise
            except Exception as e:
                logger.error(e)
                transaction.savepoint_rollback(save_id)
                raise

            # 提交事务
            transaction.savepoint_commit(save_id)

            # 更新redis中保存的购物车数据
            pl = redis_conn.pipeline()
            pl.hdel('cart_%s' % user.id, *cart_selected)
            pl.srem('cart_selected_%s' % user.id, *cart_selected)
            pl.execute()
            return order
Example #27
0
    def create(self, validated_data):
        """
        保存订单
        """
        # 获取当前下单用户
        # note--注意context是python标准字典, 但是request对象不是, 所以使用request.user
        user = self.context['request'].user

        # 获取地址支付信息
        pay_method = validated_data['pay_method']
        address = validated_data['address']

        # 组织订单编号 20170903153611+user.id
        # timezone.now() -> datetime
        order_id = timezone.now().strftime('%Y%m%d%H%M%S') + ('%09d' % user.id)
        """
        Django提供了数据库操作的事务机制, 使用方法:

        1. @transaction.atomic 装饰一个函数, 则函数里面所有的数据库操作都默认开启了事务机制

        2. with transaction.atomic(): 使用with语句, 则只有with内部的数据库操作才开启了事物

        保存点:事务支持记录保存点, 可以根据需要手动回到保存点:

        1. 创建保存点
        save_id = transaction.savepoint()

        2. 回滚到保存点
        transaction.savepoint_rollback(save_id)

        3. 提交从保存点到当前状态的所有数据库事务操作
        transaction.savepoint_commit(save_id)
        """
        # 生成订单(如果事务未完成则也不删除redis)
        with transaction.atomic():
            # note--创建一个保存点()
            save_id = transaction.savepoint()

            try:
                order = OrderInfo.objects.create(
                    order_id=order_id,
                    user=user,
                    address=address,
                    total_count=0,
                    total_amount=Decimal(0),
                    freight=Decimal(10),
                    pay_method=pay_method,
                    status=OrderInfo.ORDER_STATUS_ENUM['UNSEND']
                    if pay_method == OrderInfo.PAY_METHODS_ENUM['CASH'] else
                    OrderInfo.ORDER_STATUS_ENUM['UNPAID'])

                # 获取购物车信息
                redis_conn = get_redis_connection(
                    "cart")  # type: redis.StrictRedis
                redis_cart = redis_conn.hgetall("cart_%s" % user.id)
                cart_selected = redis_conn.smembers('cart_selected_%s' %
                                                    user.id)

                # tips--构建勾选商品数据结构
                # 查询所有的商品, 构建 { sku_id: count } 数据格式
                # cart = {}
                # for sku_id, count in redis_cart.items():
                #     cart[int(sku_id)] = int(redis_cart[sku_id])

                # 查询所有的勾选产品的状态
                # selected = []
                # for i in cart_selected:
                #     selected.append(int(i))

                # note-- 构建勾选商品数据结构:这种方法更高效
                # 构建一个 { select_sku_id: count } 数据格式, 将bytes类型转换为int类型
                cart = {}
                for sku_id in cart_selected:
                    cart[int(sku_id)] = int(redis_cart[sku_id])

                # 查询出所有购买的商品数据
                # skus = SKU.objects.filter(id__in=cart.keys())

                # 处理订单商品
                sku_id_list = cart.keys()
                # for sku in skus:
                for sku_id in sku_id_list:
                    while True:
                        sku_count = cart[sku_id]
                        sku = SKU.objects.get(id=sku_id)

                        # 判断库存
                        origin_stock = sku.stock  # 原始库存
                        origin_sales = sku.sales  # 原始销量

                        if sku_count > origin_stock:
                            transaction.savepoint_rollback(save_id)
                            raise serializers.ValidationError('商品库存不足')

                        # 用于演示并发下单
                        # import time
                        # time.sleep(5)

                        # 减少库存, 然后再更新
                        new_stock = origin_stock - sku_count
                        new_sales = origin_sales + sku_count

                        # sku.stock = new_stock
                        # sku.sales = new_sales

                        # note--在一句内完成数据的更新, 避免出现数据的变动

                        # tips--根据原始库存条件更新, 返回更新的条目数, 乐观锁
                        # tips--返回受影响的行数(如果更新时候的stock是原始的stock才会进行更新, 否则循环执行)
                        # 更新的时候判断此时的库存是否是之前查询出的库存
                        ret = SKU.objects.filter(id=sku.id,
                                                 stock=origin_stock).update(
                                                     stock=new_stock,
                                                     sales=new_sales)
                        if ret == 0:
                            continue

                        # 只有乐观锁更新成功才继续执行
                        sku.save()

                        # 累计商品的SPU 销量信息, 这里不需要使用乐观锁进行锁定
                        sku.goods.sales += sku_count
                        sku.goods.save()

                        # 累计订单基本信息的数据, 每循环一个产品则累加一次
                        order.total_count += sku_count  # 累计商品数
                        order.total_amount += (sku.price * sku_count)  # 累计总金额

                        # 保存订单商品
                        OrderGoods.objects.create(
                            order=order,
                            sku=sku,
                            count=sku_count,
                            price=sku.price,
                        )

                        # 更新成功
                        break

                    # 更新订单的金额数量信息
                    order.total_amount += order.freight
                    order.save()

            except ValidationError:
                # tips--对于库存不足的错误, 直接往外抛出
                # tips--因为上面已经回滚过一次了, 下面的exception会进行二次回滚, 没有必要
                # tips--所以对于上面已经导致回滚过一次的异常, 我们直接抛出, 不再回滚
                raise
            except Exception as e:
                # note--try_except语句可以抛出多个错误!
                logger.error(e)
                # note--回滚到保存点的数据库状态, 这里只是作为演示使用, 现实需要灵活使用
                # note--这里主要捕获其他非库存导致的数据库异常触发回滚
                transaction.savepoint_rollback(save_id)

                # 且错误不会被处理, 直接抛出, 因为下面的操作也不能继续了
                raise

            # 提交事务
            transaction.savepoint_commit(save_id)

            # 更新redis中购物车的数据
            pl = redis_conn.pipeline()
            pl.hdel('cart_%s' % user.id, *cart_selected)
            pl.srem('cart_selected_%s' % user.id, *cart_selected)
            pl.execute()
            """
            MySQL数据库事务隔离级别主要有四种:

            Serializable 串行化,一个事务一个事务的执行
            Repeatable read 可重复读,无论其他事务是否修改并提交了数据,在这个事务中看到的数据值始终不受其他事务影响
            Read committed 读取已提交,其他事务提交了对数据的修改后,本事务就能读取到修改后的数据值
            Read uncommitted 读取为提交,其他事务只要修改了数据,即使未提交,本事务也能看到修改后的数据值

            默认为Repeatable read, 如果要使用乐观锁, 需要改为读取已提交, 即其他事物修改之后本事务里面能看到

            """
            return order
Example #28
0
    def post(self, request):
        # 校验参数,取出参数备用
        serializer = AddSkuSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        sku_id = serializer.validated_data.get('sku_id')
        count = serializer.validated_data.get('count')
        selected = serializer.validated_data.get('selected')

        # 判断用户的登录状态,
        try:
            user = request.user
        except Exception as e:
            logger.error(e)
            user = None
        # 如果user存在且能通过验证,就视为已经登录
        if user is not None and user.is_authenticated:
            # 已登录将购物车数据保存到,redis
            # 数据的格式hash/list

            # sku_id 和 count
            # {
            # 	user:{sku_id1:count1,sku_id2:count2}
            # },

            # sku_id和selected
            # {
            # 	user:[sku_id1,sku_id2...]
            # }
            redis_conn = get_redis_connection('cart')
            pl = redis_conn.pipeline()
            pl.hincrby('cart_%s' % user.id, sku_id, count)
            if selected:
                pl.sadd('cart_selected_%s' % user.id, sku_id)
            pl.execute()
            return Response(serializer.data, 201)
        else:
            # 未登录将购物车数据保存到,cookie
            # 数据结构
            # {
            # sku_id:{
            # 	'count':count,
            # 	'selected':True or False
            # },
            # ...
            # }
            # 从cookie中取出可能已有的cart数据进行修改
            cart = request.COOKIES.get('cart')
            if cart is not None:
                # 将cookie中的字符串编码成byte类型,b'xxx'
                # 然后base64解码成二进制,b'01x/...'
                # 最后转为python字典
                cart = cart.encode()
                cart = base64.b64decode(cart)
                cart = pickle.loads(cart)
            else:
                cart = {}

            # 判断原有的购物车中是否已经存在该商品
            # 如果存在修改数量
            sku = cart.get(sku_id)
            if sku:
                count += int(sku.get('count'))

            cart[sku_id] = {'count': count, 'selected': selected}

            # 最后编码成str设置到cookie中保存
            cookie_cart = base64.b64encode(pickle.dumps(cart)).decode()
            response = Response(serializer.data, 201)
            response.set_cookie('cart',
                                cookie_cart,
                                max_age=365 * 24 * 60 * 60)
            return response
Example #29
0
    def create(self, validated_data):
        """保存订单"""
        address = validated_data['address']
        pay_method = validated_data['pay_method']

        # 组织参数
        # 获取登录用户
        user = self.context['request'].user
        # 订单编号 格式: 年月日时分秒 + 用户id
        order_id = datetime.now().strftime('%Y%m%d%H%M%S') + '%010d' % user.id

        # 订单商品总数量和实付款
        total_count = 0
        total_amount = Decimal(0)

        # 运费
        freight = Decimal(10)

        # 订单状态
        status = OrderInfo.ORDER_STATUS_ENUM[
            'UNSEND'] if pay_method == OrderInfo.PAY_METHODS_ENUM[
                'CASH'] else OrderInfo.ORDER_STATUS_ENUM['UNPAID']
        with transaction.atomic():
            # 创建一个保存点
            save_id = transaction.savepoint()
            try:
                # 1.向订单基本信息表添加一条记录
                order = OrderInfo.objects.create(order_id=order_id,
                                                 user=user,
                                                 address=address,
                                                 total_count=total_count,
                                                 total_amount=total_amount,
                                                 freight=freight,
                                                 pay_method=pay_method,
                                                 status=status)
                # 2.订单有几个商品, 需要向订单商品表添加几条记录
                redis_conn = get_redis_connection('carts')
                # 从redis中获取用户购物车中被勾选的商品的sku_id
                cart_selected_key = 'cart_selected_%s' % user.id
                sku_ids = redis_conn.smembers(cart_selected_key)
                # 从redis中获取用户购物车中所有商品的sku_id 和对应数量count hash
                cart_key = 'cart_%s' % user.id
                cart_dict = redis_conn.hgetall(cart_key)

                for sku_id in sku_ids:
                    # 获取用户所要购买的该商品的数量count
                    count = cart_dict[sku_id]
                    count = int(count)
                    for i in range(3):
                        # 根据sku_id 获取对应商品
                        sku = SKU.objects.get(id=sku_id)
                        # print('user: %s try get lock' % user.id)
                        # sku = SKU.objects.select_for_update().get(id=sku_id)
                        # print('user: %s get locked' % user.id)
                        # 判断商品的库存
                        if count > sku.stock:
                            # 回滚事务到sid保存点
                            transaction.savepoint_rollback(save_id)
                            raise serializers.ValidationError('商品库存不足')

                        # 记录原始商品的库存
                        origin_stock = sku.stock
                        new_stock = origin_stock - count
                        new_sales = sku.sales + count

                        # 模拟订单并发问题
                        print('user : %s times: %s stock: %s' %
                              (user.id, i, origin_stock))
                        # import time
                        # time.sleep(10)

                        # 减少商品的库存, 增加销量
                        # sku.stock -= count
                        # sku.sales += count
                        # sku.save()
                        # update 返回更新的行数
                        res = SKU.objects.filter(id=sku_id, stock=origin_stock).\
                            update(stock=new_stock, sales=new_sales)
                        if res == 0:
                            # 更新失败,重新尝试
                            if i == 2:
                                # 说明重新尝试了3次,更新仍然失败,直接报下单失败
                                # 回滚事务到sid保存点
                                transaction.savepoint_rollback(save_id)
                                raise serializers.ValidationError('商品库存不足')
                            continue

                        # 向订单商品列表中添加一条记录
                        OrderGoods.objects.create(order=order,
                                                  sku=sku,
                                                  count=count,
                                                  price=sku.price)
                        # 累加订单商品的总数量和总金额
                        total_count += count
                        total_amount += sku.price * count
                        # 更新成功,跳出循环
                        break

                # 计算实付款
                total_amount += freight
                # 更新订单记录中商品总数量和实付款
                order.total_count = total_count
                order.total_amount = total_amount
                order.save()
            except serializers.ValidationError:
                raise
            except Exception as e:
                logger.error(e)
                transaction.savepoint_rollback(save_id)
                raise
            # 3.清除redis中对应的购物车记录
            pl = redis_conn.pipeline()
            pl.hdel(cart_key, *sku_ids)
            pl.srem(cart_selected_key, *sku_ids)
            pl.execute()

            # 返回订单对象
            return order
Example #30
0
	def create(self, validated_data):
		"""保存订单"""
		# 获取user_id
		user = self.context['request'].user
		# 生成order_id,时间戳+user.id
		order_id = timezone.now().strftime('%Y%m%d%H%M%S') + ('%09d' % user.id)
		# 获取address
		address = validated_data['address']
		# 获取pay_method
		pay_method = validated_data['pay_method']

		# 开启事务
		with transaction.atomic():
			# 创建保存点
			save_id = transaction.savepoint()
			try:
				# 保存数据到OrderInfo表
				order_obj = OrderInfo.objects.create(
					order_id=order_id,
					user=user,
					address=address,
					total_count=0,
					total_amount=Decimal(0),
					freight=Decimal(10),
					pay_method=pay_method,
					status=OrderInfo.ORDER_STATUS_ENUM['UNSEND'] if pay_method == OrderInfo.PAY_METHODS_ENUM[
						'CASH'] else
					OrderInfo.ORDER_STATUS_ENUM['UNPAID']
				)
				# 保存数据到OrderGoods
				redis_conn = get_redis_connection('cart')
				redis_cart_hash = redis_conn.hgetall("cart_%s" % user.id)
				redis_cart_set = redis_conn.smembers('cart_selected_%s' % user.id)

				cart = {}
				# {sku_id:count,sku_id:count,...}
				for sku_id in redis_cart_set:
					count = redis_cart_hash[sku_id]
					cart[int(sku_id)] = int(count)

				sku_query_set = SKU.objects.filter(id__in=cart.keys())
				for sku_obj in sku_query_set:
					# 判断库存是否充足
					while True:
						sku_count = cart[sku_obj.id]  # 订单中的count
						sku_origin = SKU.objects.get(id=sku_obj.id)  # 每次都需要查询数据库中实时的数据
						# sku_count_origin = sku_obj.stock
						sku_count_origin = sku_origin.stock  # 数据库中的实时的count
						# sku_sales_origin = sku_obj.sales
						sku_sales_origin = sku_origin.sales  # 数据库中的实时的sales
						if sku_count > sku_count_origin:
							transaction.savepoint_rollback(save_id)
							raise serializers.ValidationError('库存不足')
						# import time
						# time.sleep(5)
						# 减少数据库的库存,增加销量
						new_stock = sku_count_origin - sku_count
						new_sales = sku_sales_origin + sku_count
						# 使用乐观锁,在更新时增加条件
						# 判断此时库存的数量是否已经改变
						# 如果已经改变,表示同一时间其他用户进行了数据库操作,需要重新判断库存
						ret = SKU.objects.filter(id=sku_obj.id, stock=sku_count_origin).update(stock=new_stock,
																							   sales=new_sales)
						if ret == 0:
							continue
						sku_obj.stock = new_stock
						sku_obj.sales = new_sales
						sku_obj.save()
						# 获取total_count
						order_obj.total_count += sku_count
						# 获取total_amount
						order_obj.total_amount += (sku_count * sku_obj.price)
						# 创建OrderGoods
						OrderGoods.objects.create(
							order=order_obj,
							sku=sku_obj,
							count=sku_count,
							price=sku_obj.price,
						)
						break
				# 增加运费,更新总价
				order_obj.total_amount += order_obj.freight
				order_obj.save()
			except serializers.ValidationError:
				raise
			except Exception as e:
				print(111)
				logger.error(e)
				transaction.savepoint_rollback(save_id)
				raise
			transaction.savepoint_commit(save_id)
			# 订单提交成功,cart中移除相应的商品
			redis_conn.hdel('cart_%s' % user.id, *redis_cart_set)
			redis_conn.srem('cart_selected_%s' % user.id, *redis_cart_set)
			return order_obj