上传即压缩:华为云 FunctionGraph 图片处理函数

举报
海是岛思念的泪 发表于 2026/09/25 09:36:38 2026/09/25
【摘要】 用 FunctionGraph + OBS 事件触发器做图片上传即压缩:Pillow 限尺寸(thumbnail)+控质量(quality)+转RGB防RGBA报错,写回压缩桶。重点讲防循环触发三防护(分桶/前缀过滤/代码判断)与 quality/尺寸取舍。

一、背景:图片压缩这种活,最适合事件驱动

网站、App 让用户传图片,原图往往几 MB,直接存、直接展示都费流量费存储。常规做法是"上传时顺手压成小图",但如果在应用服务器里同步压,大图会拖慢上传响应;如果用定时任务扫着压,又有延迟、还漏。

图片压缩是典型的"事件驱动"场景:原图一落到对象存储,就触发一个函数去压,压完写回缩略图,全程异步、不阻塞上传。这和第 25 篇"上传生成缩略图"思路一样,只是这次明着用华为云 FunctionGraph 当计算、OBS 当存储,把"上传即压缩"在托管 Serverless 上跑通。具体函数与触发器配置以官方文档为准,本文讲清链路和压缩代码要点。

二、链路:OBS 事件 → 函数 → 压缩 → 回写

  1. 用户把原图 photo.jpg 上传到 OBS 源桶 origin/;
  2. OBS 产生 PutObject 事件,触发器推给 FunctionGraph;
  3. 函数从源桶下载 photo.jpg 到内存;
  4. 用图像处理库(Python 用 Pillow)压到目标质量/尺寸;
  5. 把压缩图写回目标桶 compressed/;
  6. 返回,链路结束。

全程无服务器常驻:没人传图函数就不跑、不计费;有人传一张就跑一次压一张。比"常驻一台机器专职压缩"省资源。和第 25 篇不同,本文聚焦"压缩"(降质量/降尺寸)而非"缩略图"(只缩小),用途上压缩更省存储和流量。

三、函数里的压缩逻辑

函数运行时把原图读进内存,用 Pillow 压质量/尺寸后回写:

from PIL import Image
import io

def compress(object_body: bytes, max_size=(1280, 1280), quality=80):
    img = Image.open(io.BytesIO(object_body))
    img.thumbnail(max_size)                      # 超尺寸则等比缩
    buf = io.BytesIO()
    # 转 RGB 避免 RGBA 存 JPEG 报错;quality 控制压缩率
    img.convert("RGB").save(buf, format="JPEG", quality=quality)
    return buf.getvalue()

thumbnail(max_size) 把最长边压到 1280 以内(等比、不变形);quality=80 是 JPEG 压缩质量,80 通常肉眼几乎无差、体积能砍一大半。img.convert("RGB") 很关键——PNG 带透明通道(RGBA)直接存 JPEG 会报错(JPEG 不支持透明),先转 RGB 最稳。压缩结果用 io.BytesIO 存字节,直接上传不落盘(函数临时盘小,且没必要)。Pillow 是否预装、OBS SDK 取/存方法以官方文档为准——有些运行时需把依赖打进函数包。

四、触发与回写:避免循环

和第 25 篇一样,循环触发是头号雷:压缩图写回源桶会再次触发函数、再压、再触发,无限递归。正确做法是写回另一个桶(如 compressed-bucket),源桶触发器只监听 origin/ 前缀,目标桶不挂触发器:

def handler(event, context):
    key = event["Records"][0]["s3"]["object"]["key"]
    if not key.startswith("origin/"):        # 双保险:只处理源前缀
        return {"skipped": True}
    body = obs_get(src_bucket, key)
    out = compress(body)
    obs_put(compressed_bucket, "c/" + key.split("/")[-1], out)
    return {"ok": True}

if not key.startswith("origin/") 是代码内双保险:即使触发器前缀配错,函数也只处理源文件,杜绝递归把调用次数和费用打爆。压缩图写 compressed_bucket 的 c/ 前缀,和源桶物理隔离,绝不回头触发自己。

五、压缩参数的取舍

第一,quality 不是越低越好。JPEG quality 从 100 往下,体积骤降、画质缓降;到 60~80 之间通常性价比最高,再低就明显糊、还可能出现块状伪影。建议对普通照片 75~85,对截图/文字图(线条锐利)别压太低否则字糊,这类图 PNG 或无损更合适。不同图类型最优 quality 不同,按内容试。

第二,尺寸也要限。手机原图常 4000px 宽,展示最多 1280~1920 就够了,thumbnail 先把尺寸压下来,质量压缩才有效——只降 quality 不降尺寸,体积改善有限(大图细节多)。尺寸 + 质量双压,存储和流量都省。

第三,格式选择。照片类 JPEG 体积小;截图/带文字/需透明用 PNG(但 PNG 是无损、体积大)。如果业务允许,把 PNG 转 JPEG 能大幅瘦身(代价丢透明)。转换时记得 convert("RGB") 防 RGBA 报错,前面提过。

第四,大图内存。函数内存有限,几十 MB 的原图读进内存处理要注意函数内存配置,否则 OOM 失败。极端大图考虑先限制上传尺寸,或函数侧做流式/分块处理。

六、几个工程注意点

第一,循环触发三防护。分桶 + 触发器只监听源前缀 + 代码内前缀判断,三层防护最稳(见第四节)。这是这类 OBS 触发函数的头号事故源,务必上齐。

第二,压缩是破坏性的。有损压缩不可逆,原图如果还要保留(如用户想看大图),别覆盖源桶原图——源桶留原图、compressed 桶存压缩版,列表用压缩、详情用原图,和第 25 篇缩略图思路一致。

第三,格式一致性。压缩后统一 JPEG,前端展示就按 JPEG 处理;若某些图必须 PNG,函数要按原格式分支处理,别一刀切转 JPEG 把透明图弄黑底。

第四,触发器只勾上传事件。PutObject/PostObject 勾上,Delete 别勾,否则删原图又触发一次无意义压缩。

第五,成本。每次上传触发一次函数 + 一次 OBS 读写,量大自然有费用,但比常驻压缩服务按调用计费更省。函数内存配到刚好够处理最大图,别盲目给大(内存越大单价越高)。

七、小结

用 FunctionGraph + OBS 触发器做"上传即压缩":原图落地触发函数、Pillow 压尺寸+质量、写回压缩桶。压缩核心 thumbnail 限尺寸 + quality 控体积 + convert("RGB") 防 RGBA 存 JPEG 报错。防循环三防护(分桶/触发器只监听源前缀/代码内前缀判断)是头号重点;原图保留、压缩版写另桶,列表用小的、详情用原图。quality 75~85 性价比高、大图先限尺寸再压。函数依赖打包与触发器配置以官方文档为准。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。