From b27ca61db795eb26d294dc32d0e968536b10d7fd Mon Sep 17 00:00:00 2001 From: ump45nose Date: Fri, 16 Oct 2026 17:31:34 +0800 Subject: [PATCH] fix(oss): return 404 when file large object is missing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the file row still exists but PostgreSQL lo_get fails with "large object … does not exist", map that to NotFound404 instead of leaking the database error as HTTP 500. Fixes #7356 --- apps/oss/serializers/file.py | 13 +++++++++-- apps/oss/tests.py | 44 ++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/apps/oss/serializers/file.py b/apps/oss/serializers/file.py index eba30c71ef6..981a2187272 100644 --- a/apps/oss/serializers/file.py +++ b/apps/oss/serializers/file.py @@ -9,6 +9,7 @@ from common.constants.authentication_type import AuthenticationType from common.database_model_manage.database_model_manage import DatabaseModelManage from common.exception.app_exception import AppApiException, AppUnauthorizedFailed, NotFound404 +from django.db import DatabaseError from django.db.models import QuerySet from django.http import HttpResponse from django.utils.translation import gettext @@ -299,8 +300,16 @@ def get(self, mk_file_auth=None, with_valid=True): file_type = file.file_name.split(".")[-1].lower() content_type = mime_types.get(file_type, 'application/octet-stream') encoded_filename = urllib.parse.quote(file.file_name) - # 获取文件内容 - file_bytes = file.get_bytes() + # 获取文件内容。元数据还在但 PostgreSQL 大对象已丢失时, + # lo_get 会抛 DatabaseError(large object … does not exist), + # 不应以 500 把库原始报错透出给前端(#7356)。 + try: + file_bytes = file.get_bytes() + except DatabaseError as e: + msg = str(e).lower() + if 'large object' in msg and 'does not exist' in msg: + raise NotFound404(404, _('File content not found')) from e + raise file_size = len(file_bytes) response = None diff --git a/apps/oss/tests.py b/apps/oss/tests.py index 4703c42b873..31bc307bd40 100644 --- a/apps/oss/tests.py +++ b/apps/oss/tests.py @@ -52,3 +52,47 @@ def test_get_url_retrieval_routes_pass_application_id(self): 'chat_oss_retrieval', {'application_id': 'application-id'}, ) + + +class MissingLargeObjectTests(SimpleTestCase): + """#7356: metadata exists but Postgres large object is gone → 404, not 500.""" + + def test_operate_get_maps_missing_large_object_to_404(self): + from unittest.mock import MagicMock, patch + + from django.db import DatabaseError + + from common.exception.app_exception import NotFound404 + from oss.serializers.file import FileSerializer + + operate = FileSerializer.Operate(data={'id': '00000000-0000-0000-0000-000000000001'}) + self.assertTrue(operate.is_valid()) + + fake_file = MagicMock() + fake_file.file_name = 'pic.png' + fake_file.get_bytes.side_effect = DatabaseError('large object 90563 does not exist') + + with patch('oss.serializers.file.QuerySet') as qs, patch('oss.serializers.file.auth'): + qs.return_value.filter.return_value.first.return_value = fake_file + with self.assertRaises(NotFound404) as ctx: + operate.get(with_valid=False) + self.assertEqual(ctx.exception.status_code, 404) + + def test_operate_get_rethrows_unrelated_database_error(self): + from unittest.mock import MagicMock, patch + + from django.db import DatabaseError + + from oss.serializers.file import FileSerializer + + operate = FileSerializer.Operate(data={'id': '00000000-0000-0000-0000-000000000001'}) + self.assertTrue(operate.is_valid()) + + fake_file = MagicMock() + fake_file.file_name = 'pic.png' + fake_file.get_bytes.side_effect = DatabaseError('connection already closed') + + with patch('oss.serializers.file.QuerySet') as qs, patch('oss.serializers.file.auth'): + qs.return_value.filter.return_value.first.return_value = fake_file + with self.assertRaises(DatabaseError): + operate.get(with_valid=False)