diff --git a/apps/common/field/common.py b/apps/common/field/common.py index c615e587a..b64277560 100644 --- a/apps/common/field/common.py +++ b/apps/common/field/common.py @@ -40,3 +40,11 @@ class UploadedImageField(serializers.ImageField): def to_representation(self, value): return value + + +class UploadedFileField(serializers.FileField): + def __init__(self, **kwargs): + super().__init__(**kwargs) + + def to_representation(self, value): + return value diff --git a/apps/dataset/migrations/0005_file.py b/apps/dataset/migrations/0005_file.py new file mode 100644 index 000000000..3c74fc8db --- /dev/null +++ b/apps/dataset/migrations/0005_file.py @@ -0,0 +1,30 @@ +# Generated by Django 4.2.13 on 2024-07-05 18:59 + +from django.db import migrations, models +import uuid + +from smartdoc.const import CONFIG + + +class Migration(migrations.Migration): + dependencies = [ + ('dataset', '0004_document_directly_return_similarity'), + ] + + operations = [ + migrations.RunSQL(f"grant execute on function lo_from_bytea to {CONFIG.get('DB_USER')}"), + migrations.CreateModel( + name='File', + fields=[ + ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('update_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')), + ('id', models.UUIDField(default=uuid.uuid1, editable=False, primary_key=True, serialize=False, + verbose_name='主键id')), + ('file_name', models.CharField(default='', max_length=256, verbose_name='文件名称')), + ('loid', models.IntegerField(verbose_name='loid')), + ], + options={ + 'db_table': 'file', + }, + ), + ] diff --git a/apps/dataset/models/data_set.py b/apps/dataset/models/data_set.py index d0f56a017..ca3b05e0c 100644 --- a/apps/dataset/models/data_set.py +++ b/apps/dataset/models/data_set.py @@ -10,6 +10,7 @@ import uuid from django.db import models +from common.db.sql_execute import select_one from common.mixins.app_model_mixin import AppModelMixin from users.models import User @@ -123,3 +124,26 @@ class Image(AppModelMixin): class Meta: db_table = "image" + + +class File(AppModelMixin): + id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid1, editable=False, verbose_name="主键id") + + file_name = models.CharField(max_length=256, verbose_name="文件名称", default="") + + loid = models.IntegerField(verbose_name="loid") + + class Meta: + db_table = "file" + + def save( + self, bytea=None, force_insert=False, force_update=False, using=None, update_fields=None + ): + result = select_one("SELECT lo_from_bytea(%s, %s::bytea) as loid", [0, bytea]) + self.loid = result['loid'] + self.file_name = 'speech.mp3' + super().save() + + def get_byte(self): + result = select_one(f'SELECT lo_get({self.loid}) as "data"', []) + return result['data'] diff --git a/apps/dataset/serializers/file_serializers.py b/apps/dataset/serializers/file_serializers.py new file mode 100644 index 000000000..894f14904 --- /dev/null +++ b/apps/dataset/serializers/file_serializers.py @@ -0,0 +1,79 @@ +# coding=utf-8 +""" + @project: maxkb + @Author:虎 + @file: image_serializers.py + @date:2024/4/22 16:36 + @desc: +""" +import uuid + +from django.db.models import QuerySet +from django.http import HttpResponse +from rest_framework import serializers + +from common.exception.app_exception import NotFound404 +from common.field.common import UploadedFileField +from common.util.field_message import ErrMessage +from dataset.models import File + +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", + "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, error_messages=ErrMessage.image("文件")) + + def upload(self, with_valid=True): + if with_valid: + self.is_valid(raise_exception=True) + file_id = uuid.uuid1() + file = File(id=file_id, file_name=self.data.get('file').name) + 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, "不存在的文件") + return HttpResponse(file.get_byte(), status=200, + headers={'Content-Type': mime_types.get(file.file_name.split(".")[-1], 'text/plain')}) diff --git a/apps/dataset/urls.py b/apps/dataset/urls.py index 4731bc830..8162a13ab 100644 --- a/apps/dataset/urls.py +++ b/apps/dataset/urls.py @@ -55,5 +55,7 @@ urlpatterns = [ path('dataset//problem/', views.Problem.Operate.as_view()), path('dataset//problem//paragraph', views.Problem.Paragraph.as_view()), path('image/', views.Image.Operate.as_view()), - path('image', views.Image.as_view()) + path('image', views.Image.as_view()), + path('file/', views.FileView.Operate.as_view()), + path('file', views.FileView.as_view()) ] diff --git a/apps/dataset/views/__init__.py b/apps/dataset/views/__init__.py index 6b2abcfb1..e434cec86 100644 --- a/apps/dataset/views/__init__.py +++ b/apps/dataset/views/__init__.py @@ -11,3 +11,4 @@ from .document import * from .paragraph import * from .problem import * from .image import * +from .file import * diff --git a/apps/dataset/views/file.py b/apps/dataset/views/file.py new file mode 100644 index 000000000..7ec437d71 --- /dev/null +++ b/apps/dataset/views/file.py @@ -0,0 +1,43 @@ +# coding=utf-8 +""" + @project: maxkb + @Author:虎 + @file: image.py + @date:2024/4/22 16:23 + @desc: +""" +from drf_yasg import openapi +from drf_yasg.utils import swagger_auto_schema +from rest_framework.decorators import action +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.response import result +from dataset.serializers.file_serializers import FileSerializer + + +class FileView(APIView): + authentication_classes = [TokenAuth] + parser_classes = [MultiPartParser] + + @action(methods=['POST'], detail=False) + @swagger_auto_schema(operation_summary="上传文件", + operation_id="上传文件", + manual_parameters=[openapi.Parameter(name='file', + in_=openapi.IN_FORM, + type=openapi.TYPE_FILE, + required=True, + description='上传文件')], + tags=["文件"]) + def post(self, request: Request): + return result.success(FileSerializer(data={'file': request.FILES.get('file')}).upload()) + + class Operate(APIView): + @action(methods=['GET'], detail=False) + @swagger_auto_schema(operation_summary="获取图片", + operation_id="获取图片", + tags=["文件"]) + def get(self, request: Request, file_id: str): + return FileSerializer.Operate(data={'id': file_id}).get()