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'})
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
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
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
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"})
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})
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]
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
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
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')
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')
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
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
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
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
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
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
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
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
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
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
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)
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'})
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
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
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
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
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
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
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