You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Elixir脚本将S3桶子文件夹文件移至主目录并忽略已存在文件

解决S3三级文件夹文件移动至主目录并忽略已存在文件的问题

针对你需要将my_bucket/files/id/file.txt这类三级路径文件移动到my_bucket/files/file.txt、且跳过目标已存在文件的需求,下面是修改后的完整实现:

核心实现思路

  • 过滤仅处理三级目录下的文件,排除文件夹对象
  • 先检查目标路径是否已有文件,避免重复操作
  • S3无原生"移动"操作,采用复制+删除源文件的组合逻辑
  • 处理分页场景,支持桶内超过1000个对象的情况

修改后的完整代码

defmodule Script.Elixir do
  alias ExAws.S3

  def move_third_level_files do
    "my_bucket"
    |> list_all_objects()
    |> Enum.filter(&third_level_file?/1)
    |> Enum.each(&process_move/1)
  end

  # 递归列出桶内所有对象(处理分页)
  defp list_all_objects(bucket, next_marker \\ nil, acc \\ []) do
    opts = if next_marker, do: [max_keys: 1000, marker: next_marker], else: [max_keys: 1000]

    case S3.list_objects(bucket, opts) |> ExAws.request() do
      {:ok, %{body: %{contents: contents, is_truncated: true, next_marker: marker}}} ->
        list_all_objects(bucket, marker, acc ++ contents)

      {:ok, %{body: %{contents: contents}}} ->
        acc ++ contents

      {:error, reason} ->
        IO.puts("列出对象失败: #{inspect(reason)}")
        acc
    end
  end

  # 判断是否为三级目录的文件(匹配 files/id/file.txt 格式)
  defp third_level_file?(%{key: key}) do
    parts = String.split(key, "/")
    length(parts) == 3 && not String.ends_with?(key, "/")
  end

  # 单个文件的移动流程
  defp process_move(%{key: source_key}) do
    # 从源路径提取目标键:files/id/file.txt → files/file.txt
    [top_dir, _id, filename] = String.split(source_key, "/")
    target_key = "#{top_dir}/#{filename}"
    bucket = "my_bucket"

    case check_object_exists(bucket, target_key) do
      true ->
        IO.puts("目标文件 #{target_key} 已存在,跳过")

      false ->
        case copy_object(bucket, source_key, target_key) do
          :ok ->
            case delete_object(bucket, source_key) do
              :ok -> IO.puts("成功移动 #{source_key} 到 #{target_key}")
              {:error, reason} -> IO.puts("复制成功但删除源文件失败 #{source_key}: #{inspect(reason)}")
            end

          {:error, reason} ->
            IO.puts("复制失败 #{source_key}: #{inspect(reason)}")
        end
    end
  end

  # 复制源对象到目标路径
  defp copy_object(bucket, source_key, target_key) do
    case S3.put_object_copy(bucket, target_key, bucket, source_key) |> ExAws.request() do
      {:ok, _} -> :ok
      {:error, reason} -> {:error, reason}
    end
  end

  # 删除源对象
  defp delete_object(bucket, key) do
    case S3.delete_object(bucket, key) |> ExAws.request() do
      {:ok, _} -> :ok
      {:error, reason} -> {:error, reason}
    end
  end

  # 检查目标对象是否存在(404代表不存在)
  defp check_object_exists(bucket, key) do
    case S3.head_object(bucket, key) |> ExAws.request() do
      {:ok, _} -> true
      {:error, {:http_error, 404, _}} -> false
      {:error, reason} ->
        IO.puts("检查文件存在性失败 #{key}: #{inspect(reason)}")
        false
    end
  end
end

关键修改说明

  1. 修复原复制函数的参数错误:原put_object_copy的参数顺序和格式有误,正确参数为「目标桶、目标键、源桶、源键」
  2. 路径过滤逻辑:通过third_level_file?确保只处理三级目录的文件,排除以/结尾的文件夹对象
  3. 存在性校验:用head_object替代重复复制,避免覆盖已存在文件
  4. 分页兼容:递归遍历所有对象,解决原代码仅处理前1000个对象的限制
  5. 错误处理:每个步骤增加错误提示,方便排查问题

使用方式

直接调用入口函数即可执行移动操作:

Script.Elixir.move_third_level_files()

内容的提问来源于stack exchange,提问作者Ana

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 11:05:24