什么?我上传图片到minio,返回的图片带水印

11/3/2024

先给大家来个效果视频⬇️

ef9777deb4f028f2eee6a854759e2737_754391889362_v_1732699089376050

仔细观看,当我上传图片后,返回了minio的路径,而且原图片右下角有水印了

你希望实现上传文件到 MinIO 后,通过自动化处理逻辑生成一份加水印的文件并覆盖原文件。以下是详细的实现步骤和逻辑说明:

# 整体逻辑概述

  1. 上传文件到 MinIO

    • 用户将文件上传到 MinIO 中指定的存储桶。
    • MinIO 触发事件通知,将文件的元信息通过 Webhook 回调发送到处理程序。
  2. Webhook 处理程序

    • 接收 Webhook 请求,提取文件名、存储桶名称等信息。

    • 下载原始文件到本地临时路径。

    • 验证文件的有效性(是否为图片)。

    • 给文件添加水印,并保存覆盖原文件。

    • 将加水印后的文件上传回 MinIO(覆盖原文件)。

    • 记录文件已处理,避免重复操作(通过元数据标记)。

  3. 完成处理

    • 文件在 MinIO 中覆盖为加水印版本,完成流程。

# 详细实现步骤

# MinIO 上传文件并触发事件

# 配置 MinIO 存储桶

为 MinIO 的目标存储桶启用事件通知功能(Webhook)。

运行以下命令启用 Webhook:

 mc admin config set myminio notify_webhook:webhook \
 enable=on \
 endpoint=http://<处理程序服务器的IP>:9002/webhook \
 queue_limit=1000
1
2
3
4

其中这个9002是你服务的端口

# 接收 Webhook 请求

# 提取关键数据

Webhook 请求会发送事件数据,提取其中的存储桶名称、文件键名(key)和事件类型:

data = request.get_json()
record = data.get("Records", [])[0]
bucket_name = record.get("s3", {}).get("bucket", {}).get("name")
file_key = record.get("s3", {}).get("object", {}).get("key")
event_name = record.get("eventName")
if event_name != "s3:ObjectCreated:Put":
  \# 忽略非上传事件
  return jsonify({"status": "ignored"}), 200
1
2
3
4
5
6
7
8

# 下载文件到临时路径

# 使用 MinIO 的 Python SDK 下载文件

通过 fget_object 下载文件到本地临时路径。文件名根据文件键的扩展名设置:

with tempfile.NamedTemporaryFile(delete=False, suffix=os.path.splitext(file_key)[1]) as tmp_file:
  tmp_file_path = tmp_file.name
  minio_client.fget_object(bucket_name, file_key, tmp_file_path)
1
2
3

# 验证并处理图片

# 验证图片有效性

检查文件是否为有效图片(支持的格式、完整性等),使用 Pillow 库的 Image.verify() 方法:

def validate_image(file_path):
  try:
    mime_type, _ = mimetypes.guess_type(file_path)
    if not mime_type or not mime_type.startswith("image/"):
      raise ValueError("File is not a valid image.")
    with Image.open(file_path) as img:
      img.verify()
    return True
  except Exception as e:
    raise ValueError(f"Invalid image: {e}")
1
2
3
4
5
6
7
8
9
10

# 添加水印

打开图片后,使用 Pillow 在右下角绘制水印。水印颜色根据图片亮度动态调整(浅色背景用深色水印,反之亦然):

def add_watermark(image_path, watermark_text):
  with Image.open(image_path) as img:
    width, height = img.size
    draw = ImageDraw.Draw(img)
    \# 加载字体
    font = ImageFont.truetype(FONT_PATH, 20)
    \# 根据亮度选择水印颜色
    avg_brightness = calculate_average_brightness(img)
    text_color = (255, 255, 255, 128) if avg_brightness < 128 else (0, 0, 0, 128)
    \# 计算水印文本位置
    text_width, text_height = draw.textbbox((0, 0), watermark_text, font=font)[2:4]
    position = (width - text_width - 10, height - text_height - 10)
    \# 绘制水印
    draw.text(position, watermark_text, font=font, fill=text_color)
    \# 覆盖保存
    img.save(image_path)
    return image_path
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 上传加水印的文件并覆盖

# 添加水印后上传

将处理后的文件上传到 MinIO 覆盖原文件,并通过元数据标记文件已处理过:

minio_client.fput_object(
  bucket_name,
  file_key, # 覆盖原文件
  watermarked_file_path, # 带水印的文件路径
  content_type="image/png", # 假设所有图片均为 PNG
  metadata={"X-Amz-Meta-Watermarked": "true"} # 标记文件已加水印
)
logging.info(f"Uploaded watermarked file: {file_key}")
1
2
3
4
5
6
7
8

# 避免重复处理

在每次处理前,通过读取文件元数据检查是否已加过水印:

obj_info = minio_client.stat_object(bucket_name, file_key)
if "X-Amz-Meta-Watermarked" in obj_info.metadata:
  logging.info(f"File {file_key} already watermarked. Skipping.")
  return jsonify({"status": "ignored"}), 200
1
2
3
4

# 清理临时文件

在任务完成或失败时,清理下载的临时文件以节省磁盘空间:

finally:
  for path in [tmp_file_path, watermarked_file_path]:
    if path and os.path.exists(path):
      os.remove(path)
1
2
3
4

# 7. 流程总结

​ 1. 用户上传图片到 MinIO。

​ 2. MinIO 触发 Webhook,调用处理程序。

3.	Webhook 处理程序:
 - 下载文件到本地。
 - 验证图片并添加水印。
 - 将处理后的图片覆盖上传到 MinIO。
 - 添加元数据标记,防止重复处理。

​ 4. 用户获得带水印的图片(覆盖原文件)。

# 生成图片样式

image-20241129101105291

Last Updated: 12/19/2024, 3:29:07 PM