基于日期删除文件系统目录:Phoenix中SeaweedFS图片删除端点开发
Hey there! Let's walk through how to build this deletion endpoint in Phoenix for your SeaweedFS setup, since you already have the snapshot fetching logic in place. Here's a step-by-step breakdown tailored to your directory structure:
Step 1: Convert Timestamps to Directory Prefixes
First, you need to map the start/end timestamps to the corresponding directory paths in SeaweedFS. Your structure uses Camera-name/recordings/year/month/day/hour/, so we'll convert Unix timestamps into these components and build prefixes to target.
Add this helper function to a utility module (like YourApp.SeaweedFS.Utils):
defmodule YourApp.SeaweedFS.Utils do def timestamp_to_prefix(timestamp, camera_name) do datetime = DateTime.from_unix!(timestamp, :second) # Pad single-digit values with leading zeros to match your directory structure year = to_string(datetime.year) month = pad_zero(datetime.month) day = pad_zero(datetime.day) hour = pad_zero(datetime.hour) "#{camera_name}/recordings/#{year}/#{month}/#{day}/#{hour}/" end defp pad_zero(num) when num < 10, do: "0#{num}" defp pad_zero(num), do: to_string(num) end
For time ranges spanning multiple days/hours, you can generate all intermediate prefixes, but we'll cover a smarter way using your existing snapshot data next.
Step 2: Fetch All File Keys to Delete
Since you already have a function that returns snapshot details for a time range, you can reuse this to extract the full file paths (keys) of the JPEGs you need to delete. Assuming your snapshot objects include a file_path field (the full SeaweedFS key):
def get_deletion_keys(start_ts, end_ts, camera_name) do # Call your existing snapshot fetching function %{"snapshots" => snapshots} = YourApp.Snapshots.get_snapshots(start_ts, end_ts, camera_name) # Extract just the file paths from each snapshot Enum.map(snapshots, fn snapshot -> snapshot["file_path"] end) end
If you prefer to query SeaweedFS directly (instead of reusing the snapshot function), you can call SeaweedFS's List API with your prefixes to fetch matching keys. Using Tesla as the HTTP client:
def list_keys_by_prefix(prefix, collection) do url = "http://your_seaweedfs_master:9333/dir/list?collection=#{collection}&prefix=#{prefix}" case Tesla.get(url) do {:ok, %Tesla.Env{status: 200, body: body}} -> Jason.decode!(body) |> Map.get("entries", []) |> Enum.map(& &1["fullPath"]) {:error, reason} -> {:error, reason} end end
Step 3: Implement SeaweedFS Deletion Logic
SeaweedFS provides a simple HTTP API to delete files by their key. Let's wrap this in a reusable client module:
defmodule YourApp.SeaweedFS.Client do @seaweedfs_master "http://your_seaweedfs_master:9333" @default_collection "your_collection_name" # Replace with your actual collection def delete_file(key, collection \\ @default_collection) do url = "#{@seaweedfs_master}/dir/delete?collection=#{collection}&key=#{key}" case Tesla.delete(url) do {:ok, %Tesla.Env{status: 200}} -> :ok {:ok, %Tesla.Env{status: status}} -> {:error, "Delete failed with status #{status}"} {:error, reason} -> {:error, inspect(reason)} end end # Batch delete with parallel processing for better performance def batch_delete(keys, collection \\ @default_collection) do keys |> Task.async_stream(fn key -> {key, delete_file(key, collection)} end, max_concurrency: 10) |> Enum.map(fn {:ok, result} -> result end) end end
Using Task.async_stream lets you delete files in parallel, which is much faster than deleting one-by-one for large batches.
Step 4: Build the Phoenix Deletion Endpoint
Now, add a controller action to handle the deletion request, and wire it up in your router.
Controller Action
defmodule YourAppWeb.CameraSnapshotController do use YourAppWeb, :controller alias YourApp.SeaweedFS.Client alias YourApp.Snapshots action_fallback YourAppWeb.FallbackController # For centralized error handling def delete_snapshots(conn, %{"camera_name" => camera_name, "start_ts" => start_ts, "end_ts" => end_ts}) do with start_ts <- String.to_integer(start_ts), end_ts <- String.to_integer(end_ts), keys <- Snapshots.get_deletion_keys(start_ts, end_ts, camera_name), results <- Client.batch_delete(keys) do # Calculate success/failure counts {success, failures} = Enum.split_with(results, fn {_key, res} -> res == :ok end) conn |> put_status(:ok) |> json(%{ message: "Deletion process completed", deleted_count: length(success), failed_count: length(failures), failed_files: Enum.map(failures, fn {key, err} -> %{file_key: key, error: err} end) }) end end end
Router Configuration
Add this route to your lib/your_app_web/router.ex:
scope "/api", YourAppWeb do pipe_through :api delete "/cameras/:camera_name/snapshots", CameraSnapshotController, :delete_snapshots end
Key Considerations
- Authentication & Authorization: Don't forget to add auth checks (like Guardian or Pow) to this endpoint to prevent unauthorized deletions. Add a plug to your controller:
plug Guardian.Plug.EnsureAuthenticated, handler: YourAppWeb.AuthErrorHandler when action in [:delete_snapshots] - Error Handling: Use Phoenix's fallback controller to handle cases like invalid timestamps, SeaweedFS connection issues, etc.
- Prefix Deletion Shortcut: If your SeaweedFS version supports it, you can delete all files under a prefix in one request with
DELETE /dir/delete?collection=your_collection&prefix=your_prefix. This is faster than deleting individual files, but verify your version supports this feature first. - Idempotency: Deleting a file that's already removed will return a 200, so retries are safe if needed.
内容的提问来源于stack exchange,提问作者Junaid Farooq

