file_list.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. import base64
  2. import datetime
  3. import hashlib
  4. import json
  5. import os
  6. import random
  7. from pathlib import PurePosixPath
  8. from django.http import HttpResponse
  9. from django.views.decorators.csrf import csrf_exempt
  10. from rest_framework import serializers
  11. from rest_framework.decorators import action
  12. from application.settings import BASE_DIR
  13. from application import dispatch, settings
  14. from dvadmin.system.models import FileList, media_file_name
  15. from dvadmin.system.views.ueditor_settings import ueditor_upload_settings, ueditor_settings
  16. from dvadmin.utils.json_response import DetailResponse
  17. from dvadmin.utils.serializers import CustomModelSerializer
  18. from dvadmin.utils.string_util import format_bytes
  19. from dvadmin.utils.viewset import CustomModelViewSet
  20. class FileSerializer(CustomModelSerializer):
  21. url = serializers.SerializerMethodField(read_only=True)
  22. def get_url(self, instance):
  23. if self.request.query_params.get('prefix'):
  24. if settings.ENVIRONMENT in ['local']:
  25. prefix = 'http://127.0.0.1:8000'
  26. elif settings.ENVIRONMENT in ['test']:
  27. prefix = 'http://{host}/api'.format(host=self.request.get_host())
  28. else:
  29. prefix = 'https://{host}/api'.format(host=self.request.get_host())
  30. if instance.file_url:
  31. return instance.file_url if instance.file_url.startswith('http') else f"{prefix}/{instance.file_url}"
  32. return (f'{prefix}/media/{str(instance.url)}')
  33. return instance.file_url or (f'media/{str(instance.url)}')
  34. class Meta:
  35. model = FileList
  36. fields = "__all__"
  37. def create(self, validated_data):
  38. file_engine = dispatch.get_system_config_values("file_storage.file_engine") or 'local'
  39. file_backup = dispatch.get_system_config_values("file_storage.file_backup")
  40. file = self.initial_data.get('file')
  41. file_size = file.size
  42. validated_data['name'] = file.name
  43. validated_data['size'] = file_size
  44. md5 = hashlib.md5()
  45. for chunk in file.chunks():
  46. md5.update(chunk)
  47. validated_data['md5sum'] = md5.hexdigest()
  48. validated_data['engine'] = file_engine
  49. validated_data['mime_type'] = file.content_type
  50. if file_backup:
  51. validated_data['url'] = file
  52. if file_engine == 'oss':
  53. from dvadmin_cloud_storage.views.aliyun import ali_oss_upload
  54. h = validated_data['md5sum']
  55. basename, ext = os.path.splitext(file.name)
  56. file_path = ali_oss_upload(file, file_name=PurePosixPath("files", h[:1], h[1:2], h + ext.lower()))
  57. if file_path:
  58. validated_data['file_url'] = file_path
  59. else:
  60. raise ValueError("上传失败")
  61. elif file_engine == 'cos':
  62. from dvadmin_cloud_storage.views.tencent import tencent_cos_upload
  63. h = validated_data['md5sum']
  64. basename, ext = os.path.splitext(file.name)
  65. file_path = tencent_cos_upload(file, file_name=PurePosixPath("files", h[:1], h[1:2], h + ext.lower()))
  66. if file_path:
  67. validated_data['file_url'] = file_path
  68. else:
  69. raise ValueError("上传失败")
  70. else:
  71. validated_data['url'] = file
  72. # 审计字段
  73. try:
  74. request_user = self.request.user
  75. validated_data['dept_belong_id'] = request_user.dept.id
  76. validated_data['creator'] = request_user.id
  77. validated_data['modifier'] = request_user.id
  78. except:
  79. pass
  80. return super().create(validated_data)
  81. class FileViewSet(CustomModelViewSet):
  82. """
  83. 文件管理接口
  84. list:查询
  85. create:新增
  86. update:修改
  87. retrieve:单例
  88. destroy:删除
  89. """
  90. queryset = FileList.objects.all()
  91. serializer_class = FileSerializer
  92. filter_fields = ['name', ]
  93. permission_classes = []
  94. def create(self, request, *args, **kwargs):
  95. serializer = self.get_serializer(data=request.data, request=request)
  96. serializer.is_valid(raise_exception=True)
  97. self.perform_create(serializer)
  98. return DetailResponse(data=serializer.data, msg="新增成功")
  99. @csrf_exempt
  100. @action(methods=["GET", "POST"], detail=False, permission_classes=[])
  101. def ueditor(self, request):
  102. action = self.request.query_params.get("action", "")
  103. reponse_action = {
  104. "config": self.get_ueditor_settings,
  105. "uploadimage": self.upload_file,
  106. "uploadscrawl": self.upload_file,
  107. "uploadvideo": self.upload_file,
  108. "uploadfile": self.upload_file,
  109. }
  110. return reponse_action[action](request)
  111. def get_ueditor_settings(self, request):
  112. return HttpResponse(json.dumps(ueditor_upload_settings, ensure_ascii=False),
  113. content_type="application/javascript")
  114. # 保存上传的文件
  115. def save_upload_file(self, file, file_path):
  116. with open(file_path, 'wb') as f:
  117. try:
  118. for chunk in file.chunks():
  119. f.write(chunk)
  120. except Exception as e:
  121. return f"写入文件错误:{e}"
  122. return u"SUCCESS"
  123. def get_path_format_vars(self):
  124. return {
  125. "year": datetime.datetime.now().strftime("%Y"),
  126. "month": datetime.datetime.now().strftime("%m"),
  127. "day": datetime.datetime.now().strftime("%d"),
  128. "date": datetime.datetime.now().strftime("%Y%m%d"),
  129. "time": datetime.datetime.now().strftime("%H%M%S"),
  130. "datetime": datetime.datetime.now().strftime("%Y%m%d%H%M%S"),
  131. "rnd": random.randrange(100, 999)
  132. }
  133. def get_output_path(self, path_format_var):
  134. """
  135. 取得输出文件的路径
  136. :param path_format_var:
  137. :return:
  138. """
  139. file_name = (ueditor_settings["defaultPathFormat"] % path_format_var).replace("\\", "/")
  140. # 分解OutputPathFormat
  141. output_path = os.path.join('media', 'ueditor', f'{self.request.user.id}')
  142. if not os.path.exists(output_path):
  143. os.makedirs(output_path)
  144. return (file_name, output_path)
  145. # 涂鸦功能上传处理
  146. def save_scrawl_file(self, request, file_path, file_name):
  147. import base64
  148. instance = None
  149. try:
  150. content = request.data.get(ueditor_upload_settings.get("scrawlFieldName", "upfile"))
  151. f = open(os.path.join(BASE_DIR, file_path, file_name), 'wb')
  152. f.write(base64.b64decode(content))
  153. f.close()
  154. state = "SUCCESS"
  155. instance = FileList.save_file(request, file_path, file_name, mime_type='image/png')
  156. except Exception as e:
  157. state = f"写入图片文件错误:{e}"
  158. return state, instance
  159. def upload_file(self, request):
  160. """上传文件"""
  161. state = "SUCCESS"
  162. action = self.request.query_params.get("action")
  163. # 上传文件
  164. upload_field_name_list = {
  165. "uploadfile": "fileFieldName",
  166. "uploadimage": "imageFieldName",
  167. "uploadscrawl": "scrawlFieldName",
  168. "catchimage": "catcherFieldName",
  169. "uploadvideo": "videoFieldName",
  170. }
  171. upload_field_name = self.request.query_params.get(upload_field_name_list[action],
  172. ueditor_upload_settings.get(action, "upfile"))
  173. # 上传涂鸦,涂鸦是采用base64编码上传的,需要单独处理
  174. if action == "uploadscrawl":
  175. upload_file_name = "scrawl.png"
  176. upload_file_size = 0
  177. else:
  178. # 取得上传的文件
  179. file = request.FILES.get(upload_field_name, None)
  180. if file is None:
  181. return HttpResponse(json.dumps(u"{'state:'ERROR'}"), content_type="application/javascript")
  182. upload_file_name = file.name
  183. upload_file_size = file.size
  184. # 取得上传的文件的原始名称
  185. upload_original_name, upload_original_ext = os.path.splitext(upload_file_name)
  186. # 文件类型检验
  187. upload_allow_type = {
  188. "uploadfile": "fileAllowFiles",
  189. "uploadimage": "imageAllowFiles",
  190. "uploadvideo": "videoAllowFiles"
  191. }
  192. if action in upload_allow_type:
  193. allow_type = list(self.request.query_params.get(upload_allow_type[action],
  194. ueditor_upload_settings.get(upload_allow_type[action], "")))
  195. if not upload_original_ext.lower() in allow_type:
  196. state = u"服务器不允许上传%s类型的文件。" % upload_original_ext
  197. return HttpResponse({"state": state}, content_type="application/javascript")
  198. # 大小检验
  199. upload_max_size = {
  200. "uploadfile": "filwMaxSize",
  201. "uploadimage": "imageMaxSize",
  202. "uploadscrawl": "scrawlMaxSize",
  203. "uploadvideo": "videoMaxSize"
  204. }
  205. max_size = int(self.request.query_params.get(upload_max_size[action],
  206. ueditor_upload_settings.get(upload_max_size[action], 0)))
  207. if max_size != 0:
  208. if upload_file_size > max_size:
  209. state = u"上传文件大小不允许超过%s。" % format_bytes(max_size)
  210. return HttpResponse({"state": state}, content_type="application/javascript")
  211. path_format_var = self.get_path_format_vars()
  212. path_format_var.update({
  213. "basename": upload_original_name,
  214. "extname": upload_original_ext[1:],
  215. "filename": upload_file_name,
  216. })
  217. # 取得输出文件的路径
  218. format_file_name, output_path = self.get_output_path(path_format_var)
  219. # 所有检测完成后写入文件
  220. file_instance = None
  221. if state == "SUCCESS":
  222. if action == "uploadscrawl":
  223. state, file_instance = self.save_scrawl_file(request, file_path=output_path,
  224. file_name=format_file_name)
  225. else:
  226. file = request.FILES.get(upload_field_name, None)
  227. # 保存到文件中,如果保存错误,需要返回ERROR
  228. state = self.save_upload_file(file, os.path.join(BASE_DIR, output_path, format_file_name))
  229. # 保存到附件管理中
  230. file_instance = FileList.save_file(request, output_path, format_file_name, mime_type=file.content_type)
  231. # 返回数据
  232. return_info = {
  233. 'url': file_instance.file_url if file_instance else os.path.join(output_path, format_file_name), # 保存后的文件名称
  234. 'original': upload_file_name, # 原始文件名
  235. 'type': upload_original_ext,
  236. 'state': state, # 上传状态,成功时返回SUCCESS,其他任何值将原样返回至图片上传框中
  237. 'size': upload_file_size
  238. }
  239. return HttpResponse(json.dumps(return_info, ensure_ascii=False), content_type="application/javascript")