feat: implement File API for file upload and retrieval with new endpoints
This commit is contained in:
parent
06d5c10ac6
commit
3010fa835f
@ -19,13 +19,7 @@ class DocumentSplitAPI(APIMixin):
|
|||||||
location='path',
|
location='path',
|
||||||
required=True,
|
required=True,
|
||||||
),
|
),
|
||||||
OpenApiParameter(
|
|
||||||
name="file",
|
|
||||||
description="文件",
|
|
||||||
type=OpenApiTypes.BINARY,
|
|
||||||
location='query',
|
|
||||||
required=False,
|
|
||||||
),
|
|
||||||
OpenApiParameter(
|
OpenApiParameter(
|
||||||
name="limit",
|
name="limit",
|
||||||
description="分段长度",
|
description="分段长度",
|
||||||
@ -49,6 +43,20 @@ class DocumentSplitAPI(APIMixin):
|
|||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_request():
|
||||||
|
return {
|
||||||
|
'multipart/form-data': {
|
||||||
|
'type': 'object',
|
||||||
|
'properties': {
|
||||||
|
'file': {
|
||||||
|
'type': 'string',
|
||||||
|
'format': 'binary' # Tells Swagger it's a file
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class DocumentBatchAPI(APIMixin):
|
class DocumentBatchAPI(APIMixin):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -197,15 +205,23 @@ class TableDocumentCreateAPI(APIMixin):
|
|||||||
location='path',
|
location='path',
|
||||||
required=True,
|
required=True,
|
||||||
),
|
),
|
||||||
OpenApiParameter(
|
|
||||||
name="file",
|
|
||||||
description="文件",
|
|
||||||
type=OpenApiTypes.BINARY,
|
|
||||||
location='query',
|
|
||||||
required=False,
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_request():
|
||||||
|
return {
|
||||||
|
'multipart/form-data': {
|
||||||
|
'type': 'object',
|
||||||
|
'properties': {
|
||||||
|
'file': {
|
||||||
|
'type': 'string',
|
||||||
|
'format': 'binary' # Tells Swagger it's a file
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_response():
|
def get_response():
|
||||||
return DefaultResultSerializer
|
return DefaultResultSerializer
|
||||||
|
|||||||
44
apps/knowledge/api/file.py
Normal file
44
apps/knowledge/api/file.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
from drf_spectacular.types import OpenApiTypes
|
||||||
|
from drf_spectacular.utils import OpenApiParameter
|
||||||
|
|
||||||
|
from common.mixins.api_mixin import APIMixin
|
||||||
|
from common.result import DefaultResultSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class FileUploadAPI(APIMixin):
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_request():
|
||||||
|
return {
|
||||||
|
'multipart/form-data': {
|
||||||
|
'type': 'object',
|
||||||
|
'properties': {
|
||||||
|
'file': {
|
||||||
|
'type': 'string',
|
||||||
|
'format': 'binary' # Tells Swagger it's a file
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_response():
|
||||||
|
return DefaultResultSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class FileGetAPI(APIMixin):
|
||||||
|
@staticmethod
|
||||||
|
def get_parameters():
|
||||||
|
return [
|
||||||
|
OpenApiParameter(
|
||||||
|
name="file_id",
|
||||||
|
description="文件id",
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_response():
|
||||||
|
return DefaultResultSerializer
|
||||||
95
apps/knowledge/serializers/file.py
Normal file
95
apps/knowledge/serializers/file.py
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
|
||||||
|
import uuid_utils.compat as uuid
|
||||||
|
from django.db.models import QuerySet
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from common.exception.app_exception import NotFound404
|
||||||
|
from knowledge.models import File
|
||||||
|
from tools.serializers.tool import UploadedFileField
|
||||||
|
|
||||||
|
mime_types = {
|
||||||
|
"html": "text/html", "htm": "text/html", "shtml": "text/html", "css": "text/css", "xml": "text/xml",
|
||||||
|
"gif": "image/gif", "jpeg": "image/jpeg", "jpg": "image/jpeg", "js": "application/javascript",
|
||||||
|
"atom": "application/atom+xml", "rss": "application/rss+xml", "mml": "text/mathml", "txt": "text/plain",
|
||||||
|
"jad": "text/vnd.sun.j2me.app-descriptor", "wml": "text/vnd.wap.wml", "htc": "text/x-component",
|
||||||
|
"avif": "image/avif", "png": "image/png", "svg": "image/svg+xml", "svgz": "image/svg+xml",
|
||||||
|
"tif": "image/tiff", "tiff": "image/tiff", "wbmp": "image/vnd.wap.wbmp", "webp": "image/webp",
|
||||||
|
"ico": "image/x-icon", "jng": "image/x-jng", "bmp": "image/x-ms-bmp", "woff": "font/woff",
|
||||||
|
"woff2": "font/woff2", "jar": "application/java-archive", "war": "application/java-archive",
|
||||||
|
"ear": "application/java-archive", "json": "application/json", "hqx": "application/mac-binhex40",
|
||||||
|
"doc": "application/msword", "pdf": "application/pdf", "ps": "application/postscript",
|
||||||
|
"docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||||
|
"xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||||
|
"pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
||||||
|
"eps": "application/postscript", "ai": "application/postscript", "rtf": "application/rtf",
|
||||||
|
"m3u8": "application/vnd.apple.mpegurl", "kml": "application/vnd.google-earth.kml+xml",
|
||||||
|
"kmz": "application/vnd.google-earth.kmz", "xls": "application/vnd.ms-excel",
|
||||||
|
"eot": "application/vnd.ms-fontobject", "ppt": "application/vnd.ms-powerpoint",
|
||||||
|
"odg": "application/vnd.oasis.opendocument.graphics",
|
||||||
|
"odp": "application/vnd.oasis.opendocument.presentation",
|
||||||
|
"ods": "application/vnd.oasis.opendocument.spreadsheet", "odt": "application/vnd.oasis.opendocument.text",
|
||||||
|
"wmlc": "application/vnd.wap.wmlc", "wasm": "application/wasm", "7z": "application/x-7z-compressed",
|
||||||
|
"cco": "application/x-cocoa", "jardiff": "application/x-java-archive-diff",
|
||||||
|
"jnlp": "application/x-java-jnlp-file", "run": "application/x-makeself", "pl": "application/x-perl",
|
||||||
|
"pm": "application/x-perl", "prc": "application/x-pilot", "pdb": "application/x-pilot",
|
||||||
|
"rar": "application/x-rar-compressed", "rpm": "application/x-redhat-package-manager",
|
||||||
|
"sea": "application/x-sea", "swf": "application/x-shockwave-flash", "sit": "application/x-stuffit",
|
||||||
|
"tcl": "application/x-tcl", "tk": "application/x-tcl", "der": "application/x-x509-ca-cert",
|
||||||
|
"pem": "application/x-x509-ca-cert", "crt": "application/x-x509-ca-cert",
|
||||||
|
"xpi": "application/x-xpinstall", "xhtml": "application/xhtml+xml", "xspf": "application/xspf+xml",
|
||||||
|
"zip": "application/zip", "bin": "application/octet-stream", "exe": "application/octet-stream",
|
||||||
|
"dll": "application/octet-stream", "deb": "application/octet-stream", "dmg": "application/octet-stream",
|
||||||
|
"iso": "application/octet-stream", "img": "application/octet-stream", "msi": "application/octet-stream",
|
||||||
|
"msp": "application/octet-stream", "msm": "application/octet-stream", "mid": "audio/midi",
|
||||||
|
"midi": "audio/midi", "kar": "audio/midi", "mp3": "audio/mpeg", "ogg": "audio/ogg", "m4a": "audio/x-m4a",
|
||||||
|
"ra": "audio/x-realaudio", "3gpp": "video/3gpp", "3gp": "video/3gpp", "ts": "video/mp2t",
|
||||||
|
"mp4": "video/mp4", "mpeg": "video/mpeg", "mpg": "video/mpeg", "mov": "video/quicktime",
|
||||||
|
"webm": "video/webm", "flv": "video/x-flv", "m4v": "video/x-m4v", "mng": "video/x-mng",
|
||||||
|
"asx": "video/x-ms-asf", "asf": "video/x-ms-asf", "wmv": "video/x-ms-wmv", "avi": "video/x-msvideo"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class FileSerializer(serializers.Serializer):
|
||||||
|
file = UploadedFileField(required=True, label=_('file'))
|
||||||
|
meta = serializers.JSONField(required=False, allow_null=True)
|
||||||
|
|
||||||
|
def upload(self, with_valid=True):
|
||||||
|
if with_valid:
|
||||||
|
self.is_valid(raise_exception=True)
|
||||||
|
meta = self.data.get('meta', None)
|
||||||
|
if not meta:
|
||||||
|
meta = {'debug': True}
|
||||||
|
file_id = meta.get('file_id', uuid.uuid7())
|
||||||
|
file = File(id=file_id, file_name=self.data.get('file').name, meta=meta)
|
||||||
|
file.save(self.data.get('file').read())
|
||||||
|
return f'/api/file/{file_id}'
|
||||||
|
|
||||||
|
class Operate(serializers.Serializer):
|
||||||
|
id = serializers.UUIDField(required=True)
|
||||||
|
|
||||||
|
def get(self, with_valid=True):
|
||||||
|
if with_valid:
|
||||||
|
self.is_valid(raise_exception=True)
|
||||||
|
file_id = self.data.get('id')
|
||||||
|
file = QuerySet(File).filter(id=file_id).first()
|
||||||
|
if file is None:
|
||||||
|
raise NotFound404(404, _('File not found'))
|
||||||
|
# 如果是音频文件,直接返回文件流
|
||||||
|
file_type = file.file_name.split(".")[-1]
|
||||||
|
if file_type in ['mp3', 'wav', 'ogg', 'aac']:
|
||||||
|
return HttpResponse(
|
||||||
|
file.get_bytes(),
|
||||||
|
status=200,
|
||||||
|
headers={
|
||||||
|
'Content-Type': f'audio/{file_type}',
|
||||||
|
'Content-Disposition': 'attachment; filename="{}"'.format(file.file_name)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return HttpResponse(
|
||||||
|
file.get_bytes(),
|
||||||
|
status=200,
|
||||||
|
headers={'Content-Type': mime_types.get(file_type, 'text/plain')}
|
||||||
|
)
|
||||||
@ -38,4 +38,7 @@ urlpatterns = [
|
|||||||
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/problem/<int:current_page>/<int:page_size>', views.ProblemView.Page.as_view()),
|
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/problem/<int:current_page>/<int:page_size>', views.ProblemView.Page.as_view()),
|
||||||
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/<int:current_page>/<int:page_size>', views.DocumentView.Page.as_view()),
|
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/<int:current_page>/<int:page_size>', views.DocumentView.Page.as_view()),
|
||||||
path('workspace/<str:workspace_id>/knowledge/<int:current_page>/<int:page_size>', views.KnowledgeView.Page.as_view()),
|
path('workspace/<str:workspace_id>/knowledge/<int:current_page>/<int:page_size>', views.KnowledgeView.Page.as_view()),
|
||||||
|
path('file', views.FileView.as_view()),
|
||||||
|
path('file/<str:file_id>', views.FileView.Operate.as_view()),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|||||||
@ -2,3 +2,4 @@ from .document import *
|
|||||||
from .knowledge import *
|
from .knowledge import *
|
||||||
from .paragraph import *
|
from .paragraph import *
|
||||||
from .problem import *
|
from .problem import *
|
||||||
|
from .file import *
|
||||||
|
|||||||
42
apps/knowledge/views/file.py
Normal file
42
apps/knowledge/views/file.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from drf_spectacular.utils import extend_schema
|
||||||
|
from rest_framework.parsers import MultiPartParser
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
from rest_framework.views import Request
|
||||||
|
|
||||||
|
from common.auth import TokenAuth
|
||||||
|
from common.result import result
|
||||||
|
from knowledge.api.file import FileUploadAPI, FileGetAPI
|
||||||
|
from knowledge.serializers.file import FileSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class FileView(APIView):
|
||||||
|
authentication_classes = [TokenAuth]
|
||||||
|
parser_classes = [MultiPartParser]
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
methods=['POST'],
|
||||||
|
summary=_('Upload file'),
|
||||||
|
description=_('Upload file'),
|
||||||
|
operation_id=_('Upload file'),
|
||||||
|
parameters=FileUploadAPI.get_parameters(),
|
||||||
|
request=FileUploadAPI.get_request(),
|
||||||
|
responses=FileUploadAPI.get_response(),
|
||||||
|
tags=[_('file')]
|
||||||
|
)
|
||||||
|
def post(self, request: Request):
|
||||||
|
return result.success(FileSerializer(data={'file': request.FILES.get('file')}).upload())
|
||||||
|
|
||||||
|
class Operate(APIView):
|
||||||
|
@extend_schema(
|
||||||
|
methods=['GET'],
|
||||||
|
summary=_('Get file'),
|
||||||
|
description=_('Get file'),
|
||||||
|
operation_id=_('Get file'),
|
||||||
|
parameters=FileGetAPI.get_parameters(),
|
||||||
|
responses=FileGetAPI.get_response(),
|
||||||
|
tags=[_('file')]
|
||||||
|
)
|
||||||
|
def get(self, request: Request, file_id: str):
|
||||||
|
return FileSerializer.Operate(data={'id': file_id}).get()
|
||||||
@ -139,14 +139,23 @@ class ToolImportAPI(APIMixin):
|
|||||||
location='path',
|
location='path',
|
||||||
required=True,
|
required=True,
|
||||||
),
|
),
|
||||||
OpenApiParameter(
|
|
||||||
name='file',
|
|
||||||
type=OpenApiTypes.BINARY,
|
|
||||||
description='工具文件',
|
|
||||||
required=True
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_request():
|
||||||
|
return {
|
||||||
|
'multipart/form-data': {
|
||||||
|
'type': 'object',
|
||||||
|
'properties': {
|
||||||
|
'file': {
|
||||||
|
'type': 'string',
|
||||||
|
'format': 'binary' # Tells Swagger it's a file
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_response():
|
def get_response():
|
||||||
return DefaultResultSerializer
|
return DefaultResultSerializer
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user