You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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")

核心问题解析

  1. 前置get操作未做异常处理:代码开头的UserProfile.objects.get()、Venue.objects.get()、VenueLoyaltyPointRule.objects.get()是最容易触发ObjectDoesNotExist的地方——恶意请求传入不存在的ID时,这些操作会直接抛出异常,根本不会走到后续的重试逻辑。
  2. 重试逻辑捕获错误类型错误: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 21:22:35