import base64 import datetime import hashlib import json import os import random from pathlib import PurePosixPath from django.http import HttpResponse from django.views.decorators.csrf import csrf_exempt from rest_framework import serializers from rest_framework.decorators import action from application.settings import BASE_DIR from application import dispatch, settings from dvadmin.system.models import FileList, media_file_name from dvadmin.system.views.ueditor_settings import ueditor_upload_settings, ueditor_settings from dvadmin.utils.json_response import DetailResponse from dvadmin.utils.serializers import CustomModelSerializer from dvadmin.utils.string_util import format_bytes from dvadmin.utils.viewset import CustomModelViewSet class FileSerializer(CustomModelSerializer): url = serializers.SerializerMethodField(read_only=True) def get_url(self, instance): if self.request.query_params.get('prefix'): if settings.ENVIRONMENT in ['local']: prefix = 'http://127.0.0.1:8000' elif settings.ENVIRONMENT in ['test']: prefix = 'http://{host}/api'.format(host=self.request.get_host()) else: prefix = 'https://{host}/api'.format(host=self.request.get_host()) if instance.file_url: return instance.file_url if instance.file_url.startswith('http') else f"{prefix}/{instance.file_url}" return (f'{prefix}/media/{str(instance.url)}') return instance.file_url or (f'media/{str(instance.url)}') class Meta: model = FileList fields = "__all__" def create(self, validated_data): file_engine = dispatch.get_system_config_values("file_storage.file_engine") or 'local' file_backup = dispatch.get_system_config_values("file_storage.file_backup") file = self.initial_data.get('file') file_size = file.size validated_data['name'] = file.name validated_data['size'] = file_size md5 = hashlib.md5() for chunk in file.chunks(): md5.update(chunk) validated_data['md5sum'] = md5.hexdigest() validated_data['engine'] = file_engine validated_data['mime_type'] = file.content_type if file_backup: validated_data['url'] = file if file_engine == 'oss': from dvadmin_cloud_storage.views.aliyun import ali_oss_upload h = validated_data['md5sum'] basename, ext = os.path.splitext(file.name) file_path = ali_oss_upload(file, file_name=PurePosixPath("files", h[:1], h[1:2], h + ext.lower())) if file_path: validated_data['file_url'] = file_path else: raise ValueError("上传失败") elif file_engine == 'cos': from dvadmin_cloud_storage.views.tencent import tencent_cos_upload h = validated_data['md5sum'] basename, ext = os.path.splitext(file.name) file_path = tencent_cos_upload(file, file_name=PurePosixPath("files", h[:1], h[1:2], h + ext.lower())) if file_path: validated_data['file_url'] = file_path else: raise ValueError("上传失败") else: validated_data['url'] = file # 审计字段 try: request_user = self.request.user validated_data['dept_belong_id'] = request_user.dept.id validated_data['creator'] = request_user.id validated_data['modifier'] = request_user.id except: pass return super().create(validated_data) class FileViewSet(CustomModelViewSet): """ 文件管理接口 list:查询 create:新增 update:修改 retrieve:单例 destroy:删除 """ queryset = FileList.objects.all() serializer_class = FileSerializer filter_fields = ['name', ] permission_classes = [] def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data, request=request) serializer.is_valid(raise_exception=True) self.perform_create(serializer) return DetailResponse(data=serializer.data, msg="新增成功") @csrf_exempt @action(methods=["GET", "POST"], detail=False, permission_classes=[]) def ueditor(self, request): action = self.request.query_params.get("action", "") reponse_action = { "config": self.get_ueditor_settings, "uploadimage": self.upload_file, "uploadscrawl": self.upload_file, "uploadvideo": self.upload_file, "uploadfile": self.upload_file, } return reponse_action[action](request) def get_ueditor_settings(self, request): return HttpResponse(json.dumps(ueditor_upload_settings, ensure_ascii=False), content_type="application/javascript") # 保存上传的文件 def save_upload_file(self, file, file_path): with open(file_path, 'wb') as f: try: for chunk in file.chunks(): f.write(chunk) except Exception as e: return f"写入文件错误:{e}" return u"SUCCESS" def get_path_format_vars(self): return { "year": datetime.datetime.now().strftime("%Y"), "month": datetime.datetime.now().strftime("%m"), "day": datetime.datetime.now().strftime("%d"), "date": datetime.datetime.now().strftime("%Y%m%d"), "time": datetime.datetime.now().strftime("%H%M%S"), "datetime": datetime.datetime.now().strftime("%Y%m%d%H%M%S"), "rnd": random.randrange(100, 999) } def get_output_path(self, path_format_var): """ 取得输出文件的路径 :param path_format_var: :return: """ file_name = (ueditor_settings["defaultPathFormat"] % path_format_var).replace("\\", "/") # 分解OutputPathFormat output_path = os.path.join('media', 'ueditor', f'{self.request.user.id}') if not os.path.exists(output_path): os.makedirs(output_path) return (file_name, output_path) # 涂鸦功能上传处理 def save_scrawl_file(self, request, file_path, file_name): import base64 instance = None try: content = request.data.get(ueditor_upload_settings.get("scrawlFieldName", "upfile")) f = open(os.path.join(BASE_DIR, file_path, file_name), 'wb') f.write(base64.b64decode(content)) f.close() state = "SUCCESS" instance = FileList.save_file(request, file_path, file_name, mime_type='image/png') except Exception as e: state = f"写入图片文件错误:{e}" return state, instance def upload_file(self, request): """上传文件""" state = "SUCCESS" action = self.request.query_params.get("action") # 上传文件 upload_field_name_list = { "uploadfile": "fileFieldName", "uploadimage": "imageFieldName", "uploadscrawl": "scrawlFieldName", "catchimage": "catcherFieldName", "uploadvideo": "videoFieldName", } upload_field_name = self.request.query_params.get(upload_field_name_list[action], ueditor_upload_settings.get(action, "upfile")) # 上传涂鸦,涂鸦是采用base64编码上传的,需要单独处理 if action == "uploadscrawl": upload_file_name = "scrawl.png" upload_file_size = 0 else: # 取得上传的文件 file = request.FILES.get(upload_field_name, None) if file is None: return HttpResponse(json.dumps(u"{'state:'ERROR'}"), content_type="application/javascript") upload_file_name = file.name upload_file_size = file.size # 取得上传的文件的原始名称 upload_original_name, upload_original_ext = os.path.splitext(upload_file_name) # 文件类型检验 upload_allow_type = { "uploadfile": "fileAllowFiles", "uploadimage": "imageAllowFiles", "uploadvideo": "videoAllowFiles" } if action in upload_allow_type: allow_type = list(self.request.query_params.get(upload_allow_type[action], ueditor_upload_settings.get(upload_allow_type[action], ""))) if not upload_original_ext.lower() in allow_type: state = u"服务器不允许上传%s类型的文件。" % upload_original_ext return HttpResponse({"state": state}, content_type="application/javascript") # 大小检验 upload_max_size = { "uploadfile": "filwMaxSize", "uploadimage": "imageMaxSize", "uploadscrawl": "scrawlMaxSize", "uploadvideo": "videoMaxSize" } max_size = int(self.request.query_params.get(upload_max_size[action], ueditor_upload_settings.get(upload_max_size[action], 0))) if max_size != 0: if upload_file_size > max_size: state = u"上传文件大小不允许超过%s。" % format_bytes(max_size) return HttpResponse({"state": state}, content_type="application/javascript") path_format_var = self.get_path_format_vars() path_format_var.update({ "basename": upload_original_name, "extname": upload_original_ext[1:], "filename": upload_file_name, }) # 取得输出文件的路径 format_file_name, output_path = self.get_output_path(path_format_var) # 所有检测完成后写入文件 file_instance = None if state == "SUCCESS": if action == "uploadscrawl": state, file_instance = self.save_scrawl_file(request, file_path=output_path, file_name=format_file_name) else: file = request.FILES.get(upload_field_name, None) # 保存到文件中,如果保存错误,需要返回ERROR state = self.save_upload_file(file, os.path.join(BASE_DIR, output_path, format_file_name)) # 保存到附件管理中 file_instance = FileList.save_file(request, output_path, format_file_name, mime_type=file.content_type) # 返回数据 return_info = { 'url': file_instance.file_url if file_instance else os.path.join(output_path, format_file_name), # 保存后的文件名称 'original': upload_file_name, # 原始文件名 'type': upload_original_ext, 'state': state, # 上传状态,成功时返回SUCCESS,其他任何值将原样返回至图片上传框中 'size': upload_file_size } return HttpResponse(json.dumps(return_info, ensure_ascii=False), content_type="application/javascript")