先给大家来个效果视频⬇️
仔细观看,当我上传图片后,返回了minio的路径,而且原图片右下角有水印了
你希望实现上传文件到 MinIO 后,通过自动化处理逻辑生成一份加水印的文件并覆盖原文件。以下是详细的实现步骤和逻辑说明:
# 整体逻辑概述
上传文件到 MinIO
- 用户将文件上传到 MinIO 中指定的存储桶。
- MinIO 触发事件通知,将文件的元信息通过 Webhook 回调发送到处理程序。
Webhook 处理程序
接收 Webhook 请求,提取文件名、存储桶名称等信息。
下载原始文件到本地临时路径。
验证文件的有效性(是否为图片)。
给文件添加水印,并保存覆盖原文件。
将加水印后的文件上传回 MinIO(覆盖原文件)。
记录文件已处理,避免重复操作(通过元数据标记)。
完成处理
- 文件在 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
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
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
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
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
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
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
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
2
3
4
# 7. 流程总结
1. 用户上传图片到 MinIO。
2. MinIO 触发 Webhook,调用处理程序。
3. Webhook 处理程序:
- 下载文件到本地。
- 验证图片并添加水印。
- 将处理后的图片覆盖上传到 MinIO。
- 添加元数据标记,防止重复处理。
4. 用户获得带水印的图片(覆盖原文件)。