实验目标
- 掌握 OSS 文件上传和函数计算触发机制。
- 掌握 Python 图像处理库 Pillow 基本操作。
- 实现自动生成缩略图并加水印到 OSS。
- 理解 Serverless 事件驱动处理流程。
实验背景
企业网站或电商平台经常需要对上传图片自动处理:生成缩略图、加水印、压缩或格式转换。利用 Serverless 架构,可以实现自动化处理,无需手动或长期运行服务器。
实验准备
- 阿里云账号,拥有 OSS 和函数计算权限。
- 准备 Python 3.10 运行环境。
- 安装 Pillow 库(函数计算控制台可在依赖配置中填写
Pillow)。 - 准备测试图片若干(如 JPG 或 PNG)。
实验步骤
步骤 1:创建 OSS Bucket
-
登录阿里云控制台 → OSS → 创建 Bucket,例如:
image-bucket -
创建两个文件夹:
original/:存放原始图片processed/:存放处理后的图片
-
设置 Bucket 访问权限为私有。
步骤 2:上传测试图片
- 上传几张图片到
original/文件夹,用于触发函数计算。
步骤 3:创建函数计算服务
- 控制台 → 函数计算 → 创建服务,例如:
ImageProcessingService - 创建函数,例如:
GenerateThumbnailWatermark - 运行环境:Python 3.10
- 配置函数内存:128MB,超时:30秒即可
步骤 4:添加 OSS 触发器
- 选择触发类型:OSS 文件触发
- Bucket:
image-bucket - 文件夹:
original/ - 事件类型:
ObjectCreated
步骤 5:部署函数代码
# -*- coding: utf-8 -*-
import oss2
from PIL import Image, ImageDraw, ImageFont
import io
import os
import json
import traceback
# ===================== OSS 配置 =====================
OSS_ENDPOINT = os.environ.get('OSS_ENDPOINT')
OSS_BUCKET_NAME = os.environ.get('OSS_BUCKET_NAME', 'image-bucket')
ORIGINAL_FOLDER = 'original/'
PROCESSED_FOLDER = 'processed/'
# 注意:如果配置了函数计算的 RAM 角色,AK/SK 可以不从环境变量取,直接在 handler 中用 context 拿临时密钥更安全。
OSS_ACCESS_KEY_ID = os.environ.get('OSS_ACCESS_KEY_ID')
OSS_ACCESS_KEY_SECRET = os.environ.get('OSS_ACCESS_KEY_SECRET')
def handler(event, context):
# 1. 【修复】将 bytes 类型的 event 解析为 dict
try:
evt_dict = json.loads(event)
except Exception:
print("无法解析 Event 为 JSON")
return {"statusCode": 400, "body": "Invalid Event Format"}
if 'events' not in evt_dict:
print("非 OSS 触发事件")
return {"statusCode": 400, "body": "非 OSS 触发事件"}
# 初始化 OSS 客户端(优先尝试使用环境变量的长期密钥,若无则尝试取 context 中的临时 STS 密钥)
creds = context.credentials
ak = OSS_ACCESS_KEY_ID or creds.access_key_id
sk = OSS_ACCESS_KEY_SECRET or creds.access_key_secret
token = creds.security_token if not OSS_ACCESS_KEY_ID else None
if token:
auth = oss2.StsAuth(ak, sk, token)
else:
auth = oss2.Auth(ak, sk)
bucket = oss2.Bucket(auth, OSS_ENDPOINT, OSS_BUCKET_NAME)
for record in evt_dict['events']:
key = record['oss']['object']['key']
print(f"触发处理的 Key: {key}")
# ====== 只处理 original/ 文件夹 ======
if not key.startswith(ORIGINAL_FOLDER):
print(f"跳过非 original/ 目录的文件: {key}")
continue
try:
# 下载 OSS 对象
obj = bucket.get_object(key)
image_data = obj.read()
# ====== 尝试打开图片,非图片跳过 ======
try:
img = Image.open(io.BytesIO(image_data))
except Exception:
print(f"非图片文件,跳过: {key}")
continue
print(f"原图大小: {img.size}, 模式: {img.mode}")
# 2. 【修复】兼容 Pillow 10+ 的缩略图抗锯齿参数
resample_algo = getattr(Image, 'Resampling', Image).LANCZOS
img.thumbnail((200, 200), resample_algo)
# ====== 透明 PNG 转换处理 ======
if img.mode in ('RGBA', 'LA') or (img.mode == 'P' and 'transparency' in img.info):
background = Image.new("RGB", img.size, (255, 255, 255))
background.paste(img, mask=img.split()[-1])
img = background
elif img.mode != 'RGB':
img = img.convert('RGB')
# ====== 添加水印 ======
draw = ImageDraw.Draw(img)
text = "Serverless"
try:
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 20)
except Exception:
font = ImageFont.load_default()
# 3. 【修复】使用 textbbox 替代已废弃的 textsize
if hasattr(draw, 'textbbox'):
# textbbox 返回元组 (left, top, right, bottom)
bbox = draw.textbbox((0, 0), text, font=font)
text_width = bbox[2] - bbox[0]
text_height = bbox[3] - bbox[1]
else:
# 兼容极老版本的 Pillow
text_width, text_height = draw.textsize(text, font=font)
draw.text((img.width - text_width - 10, img.height - text_height - 10),
text, fill=(255, 0, 0), font=font)
# ====== 保存到内存并写回 OSS ======
out_buffer = io.BytesIO()
img.save(out_buffer, format='JPEG')
out_buffer.seek(0)
# 4. 【优化】保留相对路径结构,避免拍扁目录
# 例如: original/sub/a.jpg -> processed/thumb_sub_a.jpg 或者 processed/sub/thumb_a.jpg
# 这里采用截取掉 original/ 前缀,并在文件名前加 thumb_ 的方式
relative_path = key[len(ORIGINAL_FOLDER):]
path_parts = relative_path.split('/')
path_parts[-1] = f"thumb_{path_parts[-1]}"
processed_key = f"{PROCESSED_FOLDER}{'/'.join(path_parts)}"
bucket.put_object(processed_key, out_buffer)
print(f"生成文件成功: {processed_key}")
except Exception as e:
print(f"处理 {key} 失败: {e}")
traceback.print_exc()
continue
return {"statusCode": 200, "body": "处理完成"}必须配置的基础变量:
在函数计算(FC)控制台的函数配置 -> 环境变量中,你必须添加以下两项:OSS_ENDPOINT、OSS_BUCKET_NAME
密钥变量:1、使用 RAM 角色免密配置,进入函数计算控制台 -> 选择你的服务 -> 服务配置,在“角色配置”中,绑定一个包含 AliyunOSSFullAccess(或更精细权限)的 RAM 角色。代码会自动通过 context.credentials 获取平台临时派发的 STS 凭证,安全且不用担心密钥泄露。2、如果你没有配置 RAM 角色,那就必须在环境变量中提供你阿里云账号(或 RAM 用户)的 AccessKey。你需要额外添加以下两个环境变量:OSS_ACCESS_KEY_ID、OSS_ACCESS_KEY_SECRET
步骤 6:测试函数
-
上传一张新图片到
original/文件夹。 -
函数计算触发后,在
processed/文件夹检查生成的文件。- 文件名示例:
thumb_example.jpg - 图片已生成缩略图并带水印。
- 文件名示例:
步骤 7:扩展实验
-
批量上传多张图片,观察函数批量处理效果。
-
修改函数,实现:
- 图像格式转换(PNG → JPG)
- 压缩图片大小
- 水印位置或内容自定义