求助:基于PeriodicTask实现user_club延迟删除及撤销功能失败
问题描述
我需要实现以下业务逻辑:
- 用户点击删除
user_club时,弹出允许撤销该删除请求的提示; - 若用户在5秒内未进行任何操作,则执行删除操作;
- 若用户执行撤销操作,则取消数据库中的删除请求。
我尝试用PeriodicTask实现该功能,但未成功。以下是我的代码,麻烦帮忙排查问题,谢谢。
代码实现
models.py
class UserClub(ItemBase): user = models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='user_club') club = models.ForeignKey(Club, on_delete=models.CASCADE) roles = models.ManyToManyField(Role) membership_id = models.CharField(max_length=255, blank=True, null=True) membership_expiry_date = models.DateField(max_length=255, blank=True, null=True) subscription = models.ForeignKey(to=Subscription, on_delete=models.SET_NULL, blank=True, null=True, related_name='user_club_subscription') default = models.BooleanField(default=False) reputation_point = models.PositiveIntegerField(default=0) class Meta: db_table = "sport_user_club" verbose_name = 'User Club' verbose_name_plural = 'User Club'
views.py
def destroy(self, request, *args, **kwargs): user_club = self.get_object() task = task_schedule_del_user_club.apply_async(args=[user_club.uuid], countdown=10) self.response_format['data'] = { "task_id": task.id } self.response_format['message'] = "Club will be deleted after 5 seconds" return JsonResponse(data=self.response_format, status=status.HTTP_200_OK) @swagger_auto_schema(methods=['post'], request_body=UndoDeleteClubProcessSerializer) @action(methods=['post'], detail=False, url_path="undo-request", url_name='undo-request') def undo_request_remove_club(self, request, *args, **kwargs): serializer = UndoDeleteClubProcessSerializer(data=request.data) serializer.is_valid(raise_exception=True) serializer_data = serializer.validated_data if serializer_data['is_undo_request']: try: task_schedule_del_user_club.AsyncResult(serializer_data['task_uuid']) self.response_format['message'] = "Undo request successfully" return JsonResponse(self.response_format, status=status.HTTP_200_OK) except Exception as e: logger.error(e) raise BaseError(_("Cancel an unsuccessful idea or proposal. Please try again!"))
tasks.py
@app.task def task_schedule_del_user_club(user_club_uuid): try: user_club = UserClub.objects.get(uuid=user_club_uuid) user_club.delete() except UserClub.DoesNotExist: logger.warning(f"UserClub with id {user_club_uuid} does not exist.") except Exception as ex: logger.error(f"An error occurred while deleting UserClub: {ex}")
问题排查与修复方案
你的代码存在3个核心问题,直接导致功能失效:
- 撤销逻辑未实际取消任务:撤销方法中仅获取了任务的
AsyncResult,但未调用revoke()方法终止任务,导致任务仍会执行删除操作; - 倒计时与提示信息不一致:
destroy方法设置的是10秒倒计时,但提示信息显示5秒,前后逻辑矛盾; - 无任务执行兜底校验:若用户在任务即将执行时点击撤销,任务可能已开始运行,此时仅靠
revoke()无法完全终止,会导致数据不一致。
修复后的代码调整
1. 修正撤销方法(views.py)
@swagger_auto_schema(methods=['post'], request_body=UndoDeleteClubProcessSerializer) @action(methods=['post'], detail=False, url_path="undo-request", url_name='undo-request') def undo_request_remove_club(self, request, *args, **kwargs): serializer = UndoDeleteClubProcessSerializer(data=request.data) serializer.is_valid(raise_exception=True) serializer_data = serializer.validated_data if serializer_data['is_undo_request']: try: task_result = task_schedule_del_user_club.AsyncResult(serializer_data['task_uuid']) # 取消队列中的任务,terminate=True强制终止已启动的任务 task_result.revoke(terminate=True) self.response_format['message'] = "撤销请求成功" return JsonResponse(self.response_format, status=status.HTTP_200_OK) except Exception as e: logger.error(e) raise BaseError(_("撤销失败,请重试!"))
2. 统一倒计时与提示信息(views.py)
def destroy(self, request, *args, **kwargs): user_club = self.get_object() # 改为5秒倒计时,与提示信息一致 task = task_schedule_del_user_club.apply_async(args=[user_club.uuid], countdown=5) self.response_format['data'] = { "task_id": task.id } self.response_format['message'] = "俱乐部将在5秒后删除,可点击撤销取消操作" return JsonResponse(data=self.response_format, status=status.HTTP_200_OK)
3. 增加数据库状态兜底校验(可选但推荐)
给UserClub模型增加一个待删除标记,避免任务已启动时无法终止的情况:
models.py 修改
class UserClub(ItemBase): # ... 原有字段 pending_delete = models.BooleanField(default=False) class Meta: db_table = "sport_user_club" verbose_name = '用户俱乐部' verbose_name_plural = '用户俱乐部'
views.py 的destroy方法更新
def destroy(self, request, *args, **kwargs): user_club = self.get_object() user_club.pending_delete = True user_club.save() task = task_schedule_del_user_club.apply_async(args=[user_club.uuid], countdown=5) self.response_format['data'] = { "task_id": task.id } self.response_format['message'] = "俱乐部将在5秒后删除,可点击撤销取消操作" return JsonResponse(data=self.response_format, status=status.HTTP_200_OK)
tasks.py 更新
@app.task def task_schedule_del_user_club(user_club_uuid): try: user_club = UserClub.objects.get(uuid=user_club_uuid) # 校验是否已撤销删除 if user_club.pending_delete: user_club.delete() else: logger.info(f"UserClub {user_club_uuid} 删除已撤销") except UserClub.DoesNotExist: logger.warning(f"UserClub with id {user_club_uuid} 不存在。") except Exception as ex: logger.error(f"删除UserClub时出错: {ex}")
撤销方法新增状态更新
try: task_result = task_schedule_del_user_club.AsyncResult(serializer_data['task_uuid']) task_result.revoke(terminate=True) # 更新数据库标记,确保任务即使启动也不会执行删除 UserClub.objects.filter(uuid=serializer_data.get('club_uuid')).update(pending_delete=False) self.response_format['message'] = "撤销请求成功" return JsonResponse(self.response_format, status=status.HTTP_200_OK)
额外注意事项
- 确保Celery服务正常运行,任务队列配置正确,否则
revoke()无法生效; terminate=True依赖Celery的工作进程支持,若使用Windows系统可能无法正常终止任务,建议用数据库状态标记的方式兜底。
内容的提问来源于stack exchange,提问作者Hưng Yi
相关产品推荐
相关产品推荐

