Django/Heroku:API数据写入数据库时的ObjectDoesNotExist异常处理
问题分析与解决方案
问题背景
我的Django应用从API接收数据,虽然API发送错误信息的概率极低,但需防范恶意请求引发的ObjectDoesNotExist异常。我尝试设置最多2次重试来处理该异常,但相关打印语句未触发,推测数据在初步验证时被判定为有效,仅在写入数据库阶段才发现对象不存在,现寻求可行的异常处理方案。
模型代码
class UserProfile(models.Model): user = models.OneToOneField(User, null=True, on_delete=models.CASCADE) def __str__(self): return str(self.user) class ItemisedLoyaltyCard(models.Model): user = models.ForeignKey(UserProfile, blank=True, null=True, on_delete=models.CASCADE) venue = models.ForeignKey(Venue, blank=True, null=True, on_delete=models.CASCADE, related_name="venueitemisedloyaltycard") add_points = models.DecimalField(name = 'add_points', null = True, blank=True, default=0, decimal_places=2, max_digits=5) def __str__(self): return f"{self.user} - {self.venue}" # 原代码未完成,此处补全以保证语法合法
处理函数代码
def API_data(request): userprofile_id = request.GET.get('userprofile_id', '') venue_id = request.GET.get('venue_id', '') user = UserProfile.objects.get(id=userprofile_id) venue = Venue.objects.get(id=venue_id) venue_loyalty_point_rule = VenueLoyaltyPointRule.objects.get(venue=venue) scans = ItemisedLoyaltyCard.objects.filter(user_id=user.id, venue=venue) is_point_valid = loyalty_card_validation_rule(scans, user, venue, venue_loyalty_point_rule) if is_point_valid: try_count = 0 max_retries = 2 # 定义最大重试次数 while try_count < max_retries: try: ItemisedLoyaltyCard.objects.create( user_id=user.id, venue=venue, add_points=venue_loyalty_point_rule.points) # 创建成功则设置数据并跳出循环 data = {'points': venue_loyalty_point_rule.points, 'user': user.id, 'venue': venue.name, 'success': is_point_valid, 'frequency_rule': venue_loyalty_point_rule.frequency} break except ObjectDoesNotExist: # 此处打印未触发 try_count += 1 print("Retrying...") else: # 超过最大重试次数,设置错误信息 print("Max retries exceeded. Unable to create loyalty card entry.") data = {'error': 'Max retries exceeded. Unable to create loyalty card entry.'} else: print("Invalid loyalty card validation rule.") data = {'error': 'Invalid loyalty card validation rule.'} return HttpResponse(json.dumps(data), content_type="application/json")
核心问题解析
- 前置
get操作未做异常处理:代码开头的UserProfile.objects.get()、Venue.objects.get()、VenueLoyaltyPointRule.objects.get()是最容易触发ObjectDoesNotExist的地方——恶意请求传入不存在的ID时,这些操作会直接抛出异常,根本不会走到后续的重试逻辑。 - 重试逻辑捕获错误类型错误:
ItemisedLoyaltyCard.objects.create()时,如果user或venue在之前get之后被删除(竞态条件),Django会抛出IntegrityError(数据库外键约束失败),而非ObjectDoesNotExist,这就是你的打印语句从未触发的原因。
可行解决方案
方案1:提前拦截无效请求,处理前置异常
首先在代码开头就对传入的ID做合法性校验,并捕获ObjectDoesNotExist,避免后续无效执行:
from django.core.exceptions import ObjectDoesNotExist from django.http import JsonResponse def API_data(request): userprofile_id = request.GET.get('userprofile_id', '') venue_id = request.GET.get('venue_id', '') # 先校验ID是否为有效数字 if not (userprofile_id.isdigit() and venue_id.isdigit()): return JsonResponse({'error': 'Invalid ID format'}, status=400) try: user = UserProfile.objects.get(id=userprofile_id) venue = Venue.objects.get(id=venue_id) venue_loyalty_point_rule = VenueLoyaltyPointRule.objects.get(venue=venue) except ObjectDoesNotExist as e: print(f"Invalid request: {str(e)}") return JsonResponse({'error': 'Invalid user or venue ID'}, status=404) # 后续校验与创建逻辑...
方案2:修正重试逻辑,捕获正确异常并重新获取对象
针对竞态条件(对象在get后被删除),调整重试逻辑:捕获IntegrityError,且每次重试前重新查询对象,避免使用已失效的实例:
from django.db import IntegrityError # ... 前置校验逻辑 ... if is_point_valid: try_count = 0 max_retries = 2 data = {} while try_count < max_retries: try: # 重试时重新查询对象,确保实例存在 fresh_user = UserProfile.objects.get(id=userprofile_id) fresh_venue = Venue.objects.get(id=venue_id) fresh_rule = VenueLoyaltyPointRule.objects.get(venue=fresh_venue) ItemisedLoyaltyCard.objects.create( user_id=fresh_user.id, venue=fresh_venue, add_points=fresh_rule.points) data = {'points': fresh_rule.points, 'user': fresh_user.id, 'venue': fresh_venue.name, 'success': is_point_valid, 'frequency_rule': fresh_rule.frequency} break except (ObjectDoesNotExist, IntegrityError) as e: try_count += 1 print(f"Retry {try_count}/{max_retries}: {str(e)}") else: print("Max retries exceeded. Unable to create loyalty card entry.") data = {'error': 'Max retries exceeded. Unable to create loyalty card entry.'}
方案3:使用事务保证原子性
如果需要确保整个操作的原子性(比如验证和创建不能被其他操作打断),可以用Django的事务装饰器:
from django.db import transaction @transaction.atomic def create_loyalty_entry(user_id, venue_id): # 锁定查询对象,避免其他事务修改或删除 user = UserProfile.objects.select_for_update().get(id=user_id) venue = Venue.objects.select_for_update().get(id=venue_id) rule = VenueLoyaltyPointRule.objects.get(venue=venue) ItemisedLoyaltyCard.objects.create(user_id=user.id, venue=venue, add_points=rule.points) return rule # 在处理函数中调用: if is_point_valid: try_count = 0 max_retries = 2 while try_count < max_retries: try: rule = create_loyalty_entry(userprofile_id, venue_id) data = {'points': rule.points, 'user': user.id, 'venue': venue.name, 'success': is_point_valid, 'frequency_rule': rule.frequency} break except (ObjectDoesNotExist, IntegrityError) as e: try_count += 1 print(f"Retrying... {str(e)}") else: data = {'error': 'Max retries exceeded. Unable to create loyalty card entry.'}
select_for_update()会锁定查询到的对象,避免其他事务修改或删除,降低竞态条件的概率。
内容的提问来源于stack exchange,提问作者PhilM
相关产品推荐
相关产品推荐

