Files
DjangoAdmin_Django_NaiveVue/application/upload/services.py
T
2026-09-12 12:07:15 +08:00

273 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# +======================================================================
# | DjangoAdmin 企业数字化开发平台 [ 赋能开发者,助力企业发展 ]
# +----------------------------------------------------------------------
# | Copyright (c) 2021-2026 小蚂蚁云团队 版权所有
# +----------------------------------------------------------------------
# | 开源协议: Apache-2.0
# | 重要声明: 未经书面许可,不得移除或修改本框架相关版权标识。
# +----------------------------------------------------------------------
# | 官方网站: https://www.djangoadmin.cn
# +----------------------------------------------------------------------
# | 研发团队: 小蚂蚁云团队 荣誉出品
# +======================================================================
# | 版权和免责声明:
# | 一、知识产权声明
# | 本团队对该软件框架产品(以下简称“本产品”)拥有完整知识产权,包括但不限于商标权、专利权、
# | 著作权、商业秘密等。前述权利均受中华人民共和国及相关国际法律法规保护。未经本团队书面授权,
# | 任何个人或单位不得基于本产品本身申请任何形式的知识产权。
# | 二、使用限制与禁止条款
# | 您理解并同意,禁止将本产品用于以下任何行为:
# | 1. 任何违反中华人民共和国法律法规的用途;
# | 2. 任何侵害他人合法权益(包括但不限于人身权、财产权、知识产权等)的行为;
# | 3. 任何危害国家安全、公共利益及社会道德风尚的行为。
# | 三、责任豁免
# | 因使用本产品而产生的任何损失或法律纠纷(包括但不限于直接损失、间接损失、附带损失、衍生损失
# | 以及因违约、侵权等引发的责任),本团队均不承担任何法律责任。若因违规使用本产品导致任何第三
# | 方提起索赔或行政机关进行调查,您应独立承担全部责任。
# | 四、违约与法律责任
# | 如发现任何违反本声明的行为,本团队有权:
# | 1. 无条件配合公安机关及其他有权机关的调查取证工作;
# | 2. 依法追究相关方的民事责任,并要求赔偿全部损失;
# | 3. 保留进一步采取刑事控告等法律措施的权利。
# | 五、许可范围说明
# | 1、被授权方应视本产品源代码为核心机密信息,负有最高标准的保密与保管义务。禁止以任何形式(包
# | 括但不限于直接或间接的分享、复制、传播、分发、转售、出租、开源、反向工程、反编译、反汇编或通
# | 过网络服务提供给任何第三方)泄露、披露或处置源代码的全部或部分内容。任何未经授权的复制、使用、
# | 传播行为均构成对本团队知识产权的严重侵权,本团队有权依法追究其全部法律责任,并要求赔偿因此造
# | 成的一切经济损失及商誉损害。
# | 2、本产品仅限于公司或个人在法律允许范围内,用于内部且合规的软件产品研发。具体权利义务以《软件
# | 许可协议》附件为准。
# +======================================================================
"""
文件上传服务模块
=============================================================
该模块实现了文件上传相关的业务逻辑。
支持图片和各类文件的上传,自动进行文件类型验证、存储路径生成、
文件重命名等操作,并返回标准化的文件信息。
"""
import logging
import os
import random
import time
import mimetypes
from config.env import DJANGO_TEMP_PATH, DJANGO_FILE_URL, DJANGO_UPLOAD_DIR
from utils import R
from utils.file import mkdir
from utils.common import get_file_url
# =============================================================
# 辅助函数
# =============================================================
def _parse_allowed_extensions(exts):
"""
解析允许的扩展名参数(私有辅助函数)
将各种格式的扩展名参数统一转换为带点的列表格式。
支持的输入格式:
- 列表:['.jpg', '.png'] 或 ['jpg', 'png']
- 字符串:".jpg|.png|.gif" 或 "jpg,png,gif" 或 "jpg"
Args:
exts: 扩展名参数,可以是字符串或列表
Returns:
list: 允许的扩展名列表,格式为 ['.jpg', '.png']
"""
if not exts:
return []
if isinstance(exts, list):
return [ext if ext.startswith('.') else f'.{ext}' for ext in exts]
if isinstance(exts, str):
if '|' in exts:
parts = exts.split('|')
elif ',' in exts:
parts = exts.split(',')
else:
parts = [exts]
result = []
for part in parts:
part = part.strip().lower()
if part:
if not part.startswith('.'):
part = f'.{part}'
result.append(part)
return result
return []
def _guess_mime_type(ext_with_dot):
"""
根据扩展名推断MIME类型(私有辅助函数)
当 mimetypes.guess_type 无法识别时,使用此函数手动推断MIME类型。
Args:
ext_with_dot: 带点的扩展名,如 '.jpg'
Returns:
str: MIME类型,如果无法识别则返回默认的 'application/octet-stream'
"""
mime_map = {
# 图片类型
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.png': 'image/png',
'.gif': 'image/gif',
'.bmp': 'image/bmp',
'.webp': 'image/webp',
'.svg': 'image/svg+xml',
'.ico': 'image/x-icon',
# 文本类型
'.txt': 'text/plain',
'.html': 'text/html',
'.htm': 'text/html',
'.css': 'text/css',
'.js': 'application/javascript',
'.json': 'application/json',
'.xml': 'application/xml',
# 文档类型
'.pdf': 'application/pdf',
'.doc': 'application/msword',
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'.xls': 'application/vnd.ms-excel',
'.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'.ppt': 'application/vnd.ms-powerpoint',
'.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
# 音频视频类型
'.mp3': 'audio/mpeg',
'.mp4': 'video/mp4',
'.avi': 'video/x-msvideo',
'.mov': 'video/quicktime',
# 压缩包类型
'.zip': 'application/zip',
'.rar': 'application/x-rar-compressed',
'.7z': 'application/x-7z-compressed',
'.tar': 'application/x-tar',
'.gz': 'application/gzip',
}
return mime_map.get(ext_with_dot, 'application/octet-stream')
# =============================================================
# 文件上传服务
# =============================================================
def upload_file(request):
"""
上传文档
文档格式限制:.doc, .docx, .xls, .xlsx, .ppt, .pptx, .pdf, .txt
Args:
request: HttpRequest对象,包含上传的文件
Returns:
JsonResponse: 包含文档信息的JSON响应
"""
doc_exts = ['.jpeg', '.jpg', '.png', '.gif', '.bmp', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx',
'.pdf', '.txt']
return upload_files(request, doc_exts)
def upload_files(request, file_exts=None):
"""
上传文件(兼容图片和各类文件)
Args:
request: HttpRequest对象,包含上传的文件
- file: 上传的文件对象(表单字段名为 'file')
file_exts: 允许的文件扩展名列表或字符串
如果为 None,则允许所有文件类型
Returns:
JsonResponse: 包含文件信息的JSON响应
"""
try:
# 获取上传的文件对象
file = request.FILES.get('file')
if not file:
return R.failed(msg="请选择要上传的文件")
# 获取额外的表单字段
field_name = request.POST.get('name', '')
logging.info(f"上传文件的字段名称: {field_name}")
# 获取文件基本信息
original_name = file.name
file_size = file.size
# 获取并处理文件扩展名
file_ext_with_dot = os.path.splitext(original_name)[1].lower()
file_ext = file_ext_with_dot[1:] if file_ext_with_dot.startswith('.') else ''
# 验证文件扩展名
if file_exts:
allowed_exts = _parse_allowed_extensions(file_exts)
if allowed_exts and file_ext_with_dot not in allowed_exts:
logging.info(f"文件格式不正确,允许的格式: {', '.join(allowed_exts)},实际格式: {file_ext_with_dot}")
return R.failed(msg=f"文件格式不支持,仅支持 {', '.join(allowed_exts)} 格式")
# 获取文件的MIME类型
mime_type, _ = mimetypes.guess_type(original_name)
if not mime_type:
mime_type = _guess_mime_type(file_ext_with_dot)
# 生成新的文件名(避免文件名冲突)
# 格式:年月日时分秒 + 5位随机数 + 原扩展名
timestamp = time.strftime('%Y%m%d%H%M%S')
random_num = f'{random.randint(0, 99999):05d}'
generated_name = f"{timestamp}{random_num}{file_ext_with_dot}"
# 生成存储路径(按日期分目录存储)
date_dir = time.strftime('%Y%m%d')
save_dir = os.path.join(DJANGO_TEMP_PATH, date_dir)
# 创建存放目录
mkdir(save_dir)
# 生成完整的文件保存路径
file_path = os.path.join(save_dir, generated_name)
# 将上传的文件内容写入磁盘
with open(file_path, 'wb') as f:
for chunk in file.chunks():
f.write(chunk)
# 获取相对路径
file_path = file_path.replace(DJANGO_UPLOAD_DIR, "").replace("\\", "/")
# 生成文件的访问URL
file_url = get_file_url(file_path)
# 构建标准化的返回数据
result = {
'originalName': original_name,
'fileExtension': file_ext,
'fileType': mime_type,
'fileSize': file_size,
'fileName': generated_name,
'filePath': file_path,
'fileUrl': file_url,
}
return R.ok(msg="上传成功", data=result)
except Exception as e:
logging.error(f"文件上传异常: {str(e)}")
return R.failed(msg="上传失败,请稍后重试")