签入版本

This commit is contained in:
2026-09-12 12:07:15 +08:00
commit 0277034d24
939 changed files with 129356 additions and 0 deletions
+272
View File
@@ -0,0 +1,272 @@
# +======================================================================
# | 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="上传失败,请稍后重试")