From a22bf1ad49babb7a6d81f7d951a3ac1c6a79b7c4 Mon Sep 17 00:00:00 2001 From: Yaosanqi137 Date: Sat, 20 Sep 2025 15:23:39 +0800 Subject: [PATCH] fix: fix delete email issue --- src/backend/email_notice/middleware.py | 235 ++++++------------------- src/backend/finance/views.py | 98 +++++++++++ 2 files changed, 149 insertions(+), 184 deletions(-) diff --git a/src/backend/email_notice/middleware.py b/src/backend/email_notice/middleware.py index 42ccaf3..9189583 100644 --- a/src/backend/email_notice/middleware.py +++ b/src/backend/email_notice/middleware.py @@ -14,86 +14,6 @@ class EmailNotificationMiddleware(MiddlewareMixin): def __init__(self, get_response): self.get_response = get_response super().__init__(get_response) - # 用于存储删除前的数据 - self._delete_data_cache = {} - - def process_request(self, request): - """在处理请求前,如果是删除操作,先获取要删除的数据""" - try: - # 只处理DELETE操作 - if request.method != 'DELETE': - return None - - # 只处理API请求 - if not request.path.startswith('/api/'): - return None - - # 从URL路径中提取ID - path_parts = request.path.split('/') - record_id = None - for part in path_parts: - if part.isdigit(): - record_id = part - break - - if not record_id: - return None - - # 根据路径类型获取对应的数据 - if '/api/items/' in request.path: - data = self._get_item_data_before_delete(record_id) - if data: - self._delete_data_cache[f"item_{record_id}"] = data - elif '/api/records/' in request.path or '/api/finance/' in request.path: - data = self._get_finance_data_before_delete(record_id) - if data: - self._delete_data_cache[f"finance_{record_id}"] = data - - except Exception as e: - logger.error(f"获取删除前数据失败: {e}") - - return None - - def _get_item_data_before_delete(self, item_id): - """获取物品删除前的数据""" - try: - from items.models import Item - item = Item.objects.get(id=item_id) - return { - 'id': item.id, - 'name': item.name, - 'serial_number': item.serial_number, - 'status': item.status, - 'category': item.category, - 'owner': item.owner, - 'purchase_date': str(item.purchase_date) if item.purchase_date else '', - 'purchase_price': str(item.purchase_price) if item.purchase_price else '', - 'description': item.description, - 'updated_at': str(item.updated_at) if hasattr(item, 'updated_at') else '', - } - except Exception as e: - logger.error(f"获取物品数据失败: {e}") - return None - - def _get_finance_data_before_delete(self, record_id): - """获取财务记录删除前的数据""" - try: - from finance.models import FinanceRecord - record = FinanceRecord.objects.get(id=record_id) - return { - 'id': record.id, - 'amount': str(record.amount), - 'transaction_type': record.transaction_type, - 'description': record.description, - 'department': record.department.name if record.department else '', - 'category': record.category.name if record.category else '', - 'approver': record.approver, - 'transaction_date': str(record.transaction_date), - 'proof_images': [str(img.image) for img in record.proof_images.all()] if hasattr(record, 'proof_images') else [] - } - except Exception as e: - logger.error(f"获取财务记录数据失败: {e}") - return None def process_response(self, request, response): """处理响应,在数据修改操作成功后异步发送邮件通知""" @@ -102,8 +22,8 @@ class EmailNotificationMiddleware(MiddlewareMixin): if not request.path.startswith('/api/'): return response - # 只处理数据修改操作 - if request.method not in ['POST', 'PUT', 'PATCH', 'DELETE']: + # 只处理数据新增修改操作,但排除DELETE操作,毕竟删除操作已经另外重写 + if request.method not in ['POST', 'PUT', 'PATCH']: return response # 只处理成功的响应 @@ -149,67 +69,39 @@ class EmailNotificationMiddleware(MiddlewareMixin): try: operation_type = self._get_operation_type(request.method, request.path) - # 对于DELETE操作,使用预先获取的数据 - if request.method == 'DELETE': - # 从URL路径中提取ID - path_parts = request.path.split('/') - item_id = None - for part in path_parts: - if part.isdigit(): - item_id = part - break - - # 尝试从缓存中获取删除前的数据 - cached_data = self._delete_data_cache.get(f"item_{item_id}") - if cached_data: - notification_data = { - 'id': cached_data.get('id', item_id), - 'name': f"[已删除] {cached_data.get('name', '未知物品')}", - 'serial_number': cached_data.get('serial_number', ''), - 'status': f"[删除前状态: {cached_data.get('status', '未知')}]", - 'category': cached_data.get('category', ''), - 'owner': cached_data.get('owner', ''), - 'purchase_date': cached_data.get('purchase_date', ''), - 'purchase_price': cached_data.get('purchase_price', ''), - 'description': f"删除前描述: {cached_data.get('description', '')}", - 'timestamp': cached_data.get('updated_at', ''), - 'operation_path': request.path, - 'operation_method': request.method - } + # 尝试从响应中获取数据 + if hasattr(response, 'data'): + item_data = response.data else: - # 尝试从响应中获取数据 - if hasattr(response, 'data'): - item_data = response.data - else: - try: - content = response.content.decode('utf-8') - item_data = json.loads(content) if content else {} - except: - item_data = {} - - # 确保item_data不为None - if item_data is None: + try: + content = response.content.decode('utf-8') + item_data = json.loads(content) if content else {} + except: item_data = {} - # 构建通知数据 - notification_data = { - 'id': item_data.get('id', ''), - 'name': item_data.get('name', ''), - 'serial_number': item_data.get('serial_number', ''), - 'status': item_data.get('status', ''), - 'category': item_data.get('category', ''), - 'owner': item_data.get('owner', ''), - 'timestamp': item_data.get('updated_at', ''), - 'operation_path': request.path, - 'operation_method': request.method - } + # 确保item_data不为None + if item_data is None: + item_data = {} + + # 构建通知数据 + notification_data = { + 'id': item_data.get('id', ''), + 'name': item_data.get('name', ''), + 'serial_number': item_data.get('serial_number', ''), + 'status': item_data.get('status', ''), + 'category': item_data.get('category', ''), + 'owner': item_data.get('owner', ''), + 'timestamp': item_data.get('updated_at', ''), + 'operation_path': request.path, + 'operation_method': request.method + } EmailNotificationService.send_operation_notification( operation_type, '物品', notification_data, user_info ) except Exception as e: - logger.error(f"异步处理物品操作通知失败: {e} 如果需要查看删除前的数据,请检查以前的邮件。") + logger.error(f"异步处理物品操作通知失败: {e}") # 创建并启动后台线程 thread = threading.Thread(target=send_notification) @@ -222,63 +114,40 @@ class EmailNotificationMiddleware(MiddlewareMixin): try: operation_type = self._get_operation_type(request.method, request.path) - # 对于DELETE操作,响应通常不包含数据,需要从URL路径中提取ID - if request.method == 'DELETE': - # 从URL路径中提取ID - path_parts = request.path.split('/') - record_id = None - for part in path_parts: - if part.isdigit(): - record_id = part - break - - notification_data = { - 'id': record_id or '未知', - 'amount': '已删除记录', - 'transaction_type': '已删除', - 'description': '财务记录已被删除', - 'department': '未知', - 'category': '未知', - 'approver': '未知', - 'timestamp': '删除时间未知', - 'operation_path': request.path, - 'operation_method': request.method - } + # 尝试从响应中获取数据 + if hasattr(response, 'data'): + finance_data = response.data else: - # 尝试从响应中获取数据 - if hasattr(response, 'data'): - finance_data = response.data - else: - try: - content = response.content.decode('utf-8') - finance_data = json.loads(content) if content else {} - except: - finance_data = {} - - # 确保finance_data不为None - if finance_data is None: + try: + content = response.content.decode('utf-8') + finance_data = json.loads(content) if content else {} + except: finance_data = {} - # 构建通知数据 - notification_data = { - 'id': finance_data.get('id', ''), - 'amount': finance_data.get('amount', ''), - 'transaction_type': finance_data.get('transaction_type', ''), - 'description': finance_data.get('description', ''), - 'department': finance_data.get('department', ''), - 'category': finance_data.get('category', ''), - 'approver': finance_data.get('approver', ''), - 'timestamp': finance_data.get('transaction_date', ''), - 'operation_path': request.path, - 'operation_method': request.method - } + # 确保finance_data不为None + if finance_data is None: + finance_data = {} + + # 构建通知数据 + notification_data = { + 'id': finance_data.get('id', ''), + 'amount': finance_data.get('amount', ''), + 'transaction_type': finance_data.get('transaction_type', ''), + 'description': finance_data.get('description', ''), + 'department': finance_data.get('department', ''), + 'category': finance_data.get('category', ''), + 'approver': finance_data.get('approver', ''), + 'timestamp': finance_data.get('transaction_date', ''), + 'operation_path': request.path, + 'operation_method': request.method + } EmailNotificationService.send_operation_notification( operation_type, '财务记录', notification_data, user_info ) except Exception as e: - logger.error(f"异步处理财务操作通知失败: {e} 如果需要查看删除前的数据,请检查以前的邮件。") + logger.error(f"异步处理财务操作通知失败: {e}") # 创建并启动后台线程 thread = threading.Thread(target=send_notification) @@ -291,7 +160,5 @@ class EmailNotificationMiddleware(MiddlewareMixin): return 'CREATE' elif method in ['PUT', 'PATCH']: return 'UPDATE' - elif method == 'DELETE': - return 'DELETE' else: return 'UNKNOWN' diff --git a/src/backend/finance/views.py b/src/backend/finance/views.py index 3383d65..96f10c9 100644 --- a/src/backend/finance/views.py +++ b/src/backend/finance/views.py @@ -1,7 +1,9 @@ import os +import threading from rest_framework import viewsets, status from rest_framework.decorators import action from rest_framework.response import Response +from django.utils import timezone from .models import FinancialRecord, Department, Category, ProofImage from .serializers import ( FinancialRecordWriteSerializer, @@ -23,6 +25,102 @@ class FinancialRecordViewSet(viewsets.ModelViewSet): return FinancialRecordReadSerializer return FinancialRecordWriteSerializer + def _get_user_info(self, request): + """获取用户信息""" + try: + if hasattr(request, 'user') and request.user.is_authenticated: + return f"{request.user.username} ({request.user.email})" + else: + return f"匿名用户 (IP: {self._get_client_ip(request)})" + except: + return "未知用户" + + def _get_client_ip(self, request): + """获取客户端IP""" + x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR') + if x_forwarded_for: + ip = x_forwarded_for.split(',')[0] + else: + ip = request.META.get('REMOTE_ADDR') + return ip + + def destroy(self, request, *args, **kwargs): + """删除方法,删除并发送邮件通知""" + instance = self.get_object() + + # 获取记录信息用于邮件通知 + record_id = instance.id + record_info = { + 'title': instance.title, + 'amount': str(instance.amount), + 'description': instance.description, + 'record_type': instance.record_type, + 'transaction_date': str(instance.transaction_date), + 'department': instance.department.name if instance.department else '', + 'category': instance.category.name if instance.category else '', + 'fund_manager': instance.fund_manager if hasattr(instance, 'fund_manager') else '' + } + + # 删除关联的凭证图片文件 + proof_images = ProofImage.objects.filter(financial_record=instance) + for proof_image in proof_images: + if proof_image.image: + try: + file_path = proof_image.image.path + if os.path.isfile(file_path): + os.remove(file_path) + print(f"成功删除凭证文件 {file_path}") + except (ValueError, AttributeError, OSError) as e: + print(f"删除凭证文件失败 {e}") + + # 立即删除财务记录(会级联删除相关的凭证图片记录) + super().destroy(request, *args, **kwargs) + + # 异步发送删除通知邮件 + def send_delete_notification(): + """异步发送删除通知邮件""" + try: + from email_notice.services import EmailNotificationService + + # 获取用户信息 + user_info = self._get_user_info(request) + + # 构建邮件通知数据 + notification_data = { + 'id': record_id, + 'title': f"[已删除] {record_info['title']}", + 'amount': record_info['amount'], + 'record_type': record_info['record_type'], + 'description': record_info['description'], + 'department': record_info['department'], + 'category': record_info['category'], + 'fund_manager': record_info['fund_manager'], + 'transaction_date': record_info['transaction_date'], + 'deleted_at': timezone.now().isoformat(), + 'operation_path': request.path, + 'operation_method': request.method + } + + # 发送删除通知邮件 + EmailNotificationService.send_operation_notification( + 'DELETE', '财务记录', notification_data, user_info + ) + print(f"删除通知邮件已发送: 财务记录 ID:{record_id}, 标题:{record_info['title']}") + + except Exception as e: + print(f"发送删除通知邮件失败: {e}") + + # 启动异步邮件发送线程 + email_thread = threading.Thread(target=send_delete_notification) + email_thread.daemon = True + email_thread.start() + + # 返回删除成功响应 + return Response({ + 'message': f'财务记录 "{record_info["title"]}" 已成功删除,删除通知邮件正在发送', + 'deleted_record_info': record_info + }, status=status.HTTP_200_OK) + @action(detail=True, methods=['post']) def upload_images(self, request, pk=None): """为财务记录上传多张凭证图片"""