【剪映小助手源码精讲】31_工具类实现

40 阅读5分钟

第31章:工具类实现

31.1 概述

工具类是剪映小助手的基础功能模块,提供了一系列通用的辅助函数,包括文件下载、URL参数解析、唯一ID生成、文件遍历等功能。这些工具函数被广泛应用于各个服务模块中,为整个系统提供稳定可靠的基础支持。

31.2 核心工具函数

31.2.1 URL参数提取函数

get_url_param 函数用于从URL中提取指定的查询参数:

def get_url_param(url: str, key: str, default=None):
    """
    从 URL 中提取指定查询参数的值(返回第一个值)。
    若参数不存在,返回 default。
    """
    query = parse_qs(urlparse(url).query)
    return query.get(key, [default])[0]

该函数使用Python标准库的urlparseparse_qs来解析URL查询字符串,支持提取多个相同参数名的第一个值。

31.2.2 文件下载函数

download 函数是核心的文件下载工具,支持多种文件类型和大小限制:

def download(url, save_dir, limit=30*1024*1024, timeout=180) -> str:
    """
    下载文件并根据Content-Type判断文件类型
    
    Args:
        url: 文件的URL地址
        save_dir: 文件保存目录
        limit: 文件大小限制(字节),默认30MB
        timeout: 整体下载超时时间(秒),默认3分钟
    
    Returns:
        完整的文件路径

    Raises:
        CustomException: 自定义异常
    """
    # 1. 生成文件名
    save_path = os.path.join(save_dir, gen_unique_id())

    try:
        # 2. 发送GET请求下载文件
        headers = {
            'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36',
            'Referer': 'https://www.jcaigc.cn/',
            'Accept': 'image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8',
            'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8'
        }
        response = requests.get(url, stream=True, timeout=timeout, headers=headers)
        response.raise_for_status()
        
        # 3. 获取Content-Type,判断文件类型
        content_type = response.headers.get('Content-Type', '').split(';')[0].strip()
        
        # 4. 根据Content-Type猜测扩展名
        extension = mimetypes.guess_extension(content_type)
        if extension:
            save_path += extension

        # 5. 下载文件并实时检查大小
        downloaded_size = 0
        with open(save_path, 'wb') as f:
            for chunk in response.iter_content(chunk_size=8192):
                if chunk:
                    f.write(chunk)
                    downloaded_size += len(chunk)
                    
                    # 检查文件大小是否超过限制
                    if downloaded_size > limit:
                        # 删除部分下载的文件
                        f.close()
                        os.remove(save_path)
                        
                        logger.info(f"Download failed, url: {url}, error: File size exceeds the limit of {limit/1024/1024:.2f}MB")
                        raise CustomException(err=CustomError.FILE_SIZE_LIMIT_EXCEEDED, detail=f"{limit/1024/1024:.2f} MB")
        
        # 6. 验证下载完整性
        content_length = response.headers.get('Content-Length')
        if content_length and os.path.getsize(save_path) != int(content_length):
            os.remove(save_path)
            logger.warning(f"Download failed, url: {url}, error: File download incomplete")
            raise CustomException(err=CustomError.DOWNLOAD_FILE_FAILED)
        
        logger.info(f"Download success, url: {url}, save_path: {save_path}")
        return save_path
        
    except Exception as e:
        # 清理可能已部分下载的文件
        if os.path.exists(save_path):
            os.remove(save_path)
        logger.warning(f"Download failed, url: {url}, error: {str(e)}")
        raise CustomException(err=CustomError.DOWNLOAD_FILE_FAILED)

31.2.3 唯一ID生成函数

gen_unique_id 函数生成基于时间戳和UUID的唯一标识符:

def gen_unique_id() -> str:
    """
    生成唯一ID
    """
    timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
    unique_id = uuid.uuid4().hex[:8]

    return f"{timestamp}{unique_id}"

该函数结合时间戳(精确到秒)和UUID的前8位字符,确保生成的ID具有唯一性和可读性。

31.2.4 文件遍历函数

get_all_files 函数递归获取目录下所有文件的路径列表:

def get_all_files(dir: str) -> list:
    """
    使用 pathlib.Path.rglob() 递归获取目录下所有文件的路径列表。

    参数:
        dir (str): 要遍历的目录路径。

    返回:
        list: 包含所有文件完整路径的列表。
    """
    path_obj = Path(dir)
    
    # 检查目录是否存在
    if not path_obj.exists():
        return []
    
    # 使用 rglob('*') 递归匹配所有条目,并用 is_file() 过滤出文件
    file_list = [str(file_path) for file_path in path_obj.rglob('*') if file_path.is_file()]
    return file_list

该函数使用pathlib.Path.rglob()方法递归遍历目录,自动过滤出文件类型的条目,忽略子目录。

31.3 错误处理机制

工具类中的函数都实现了完善的错误处理机制:

31.3.1 下载错误处理

文件下载函数包含多重错误检查:

  1. 网络错误处理:捕获requests库抛出的网络异常
  2. 文件大小限制:实时监控下载文件大小,超出限制时立即终止
  3. 完整性验证:对比Content-Length头与实际下载文件大小
  4. 清理机制:在任何错误情况下都会清理部分下载的文件

31.3.2 自定义异常抛出

工具函数使用统一的自定义异常体系:

# 文件大小超出限制
raise CustomException(err=CustomError.FILE_SIZE_LIMIT_EXCEEDED, detail=f"{limit/1024/1024:.2f} MB")

# 下载失败
raise CustomException(err=CustomError.DOWNLOAD_FILE_FAILED)

31.4 性能优化特性

31.4.1 流式下载

文件下载采用流式处理,避免大文件占用过多内存:

response = requests.get(url, stream=True, timeout=timeout, headers=headers)
# ...
for chunk in response.iter_content(chunk_size=8192):
    if chunk:
        f.write(chunk)

31.4.2 实时大小检查

在下载过程中实时检查文件大小,避免下载完成后才发现超出限制:

downloaded_size += len(chunk)
if downloaded_size > limit:
    # 立即终止并清理

31.4.3 智能文件类型检测

通过HTTP响应头的Content-Type自动确定文件扩展名:

content_type = response.headers.get('Content-Type', '').split(';')[0].strip()
extension = mimetypes.guess_extension(content_type)

31.5 使用示例

31.5.1 URL参数提取

url = "https://example.com/api?draft_id=12345&user=abc"
draft_id = get_url_param(url, "draft_id")  # 返回 "12345"
user = get_url_param(url, "user")  # 返回 "abc"

31.5.2 文件下载

try:
    save_path = download(
        url="https://example.com/video.mp4",
        save_dir="/tmp/downloads",
        limit=50*1024*1024,  # 50MB限制
        timeout=300  # 5分钟超时
    )
    print(f"文件下载成功: {save_path}")
except CustomException as e:
    print(f"下载失败: {e.err.cn_message}")

31.5.3 唯一ID生成

unique_id = gen_unique_id()  # 例如: "20240115143025a1b2c3d4"

31.5.4 文件遍历

files = get_all_files("/path/to/directory")
print(f"找到 {len(files)} 个文件")
for file_path in files:
    print(file_path)

31.6 扩展性设计

工具类的设计具有良好的扩展性:

  • 模块化设计:每个函数功能单一,便于维护和扩展
  • 参数化配置:支持通过参数调整函数行为
  • 异常统一:使用统一的异常体系,便于错误处理
  • 日志集成:集成了日志记录功能,便于调试和监控

附录

代码仓库地址:

  • GitHub: https://github.com/Hommy-master/capcut-mate
  • Gitee: https://gitee.com/taohongmin-gitee/capcut-mate

接口文档地址:

  • API文档地址: https://docs.jcaigc.cn