如何用 Serverless 优雅地给网站图片加水印

不少论坛、博客在进行图片上传以后,都会给本身的图像加上水印,这样能够证实这张图片「属于我」或者是「来自个人博客/网站」。那么使用 Serverless 技术来加水印的方法比传统方法好在哪儿呢,本文将对此进行一个简单的分享。

传统的加水印的方法,一般是在流程内进行。即:html

传统方法

这种作法虽然可行,可是无疑会增长单次请求,服务端的压力,若是是高并发的状况下,或者多人上传多张大图的时候,那么可能就会形成自身服务器资源效果过大。git

若是在加水印过程当中失败,就有可能致使图像存储失败,导致数据丢失,并不理智。因此后来有人作了以下改进:github

传统方法改进版

这样作法的好处就是 —— 咱们能够快速将图片存储,存储以后经过一个单独处理的线程,对任务列表进行处理,这样一方面是能够保证咱们立刻把用户上传的图片存储,而且能够显示,同时也能够在后台进行水印处理,待处理以后,再将图片覆盖或者单独存储,用户若是须要读取图片时,能够自动变为已经水印后的图片。数组

这种作法相对于前者来讲可能稍微复杂一些,可是实际上倒是在数据上更加稳定,服务端压力更小,更加可控的一种操做。可是,这整个流程仍是要在本身的服务器上作完,如今已经有不少人将图片等资源存储到腾讯云的 对象存储(COS)中,那么可不能够经过某些 COS 触发器与云函数 SCF 结合,实现一个不在本身服务器加水印的流程呢?服务器

本文将以腾讯云函数 SCF 的函数模板(Python 语言)为例,进行一个简单的分享。数据结构

▎实验

新建函数

在无服务器云函数中,选择模板函数:架构

新建函数

经过搜索「图像」关键词,选中图像压缩,而且肯定创建。保存以后,点击函数代码,进行代码编写。并发

编写代码

COS 触发器

有些人可能对 COS 触发器还不是很了解,此时能够点击配置,来熟悉 COS 触发器样式:app

触发器

能够看到以下:less

{
   "Records":[
      {
        "event": {
          "eventVersion":"1.0",
          "eventSource":"qcs::cos",
          "eventName":"cos: ObjectCreated: *",
          "eventTime":1501054710,
          "eventQueue":"qcs:0:cos:gz:1251111111:cos",
          "requestParameters":{
            "requestSourceIP": "111.111.111.111",
            "requestHeaders":{
              "Authorization": "上传的鉴权信息"
            }
          }
         },
         "cos":{
            "cosSchemaVersion":"1.0",
            "cosNotificationId":"设置的或返回的 ID",
            "cosBucket":{
               "name":"bucketname",
               "appid":"appId",
               "region":"gz"
            },
            "cosObject":{
               "key":"/appid/bucketname/DSC_0002.JPG",
               "size":2598526,
               "meta":{
                 "Content-Type": "text/plain",
                 "x-cos-meta-test": "自定义的 meta",
                 "x-image-test": "自定义的 meta"
               },
               "url": "访问文件的源站url"
            }
         }
      }
   ]
}

这里面能够看到整个一个数据结构,须要注意 Records 是一个数组格式,其次就是:

"cosBucket":{"name":"bucketname","appid":"appId","region":"gz"}

这里面是由该 bucket 触发

"cosObject":{"key":"/appid/bucketname/DSC_0002.JPG","size":2598526,"meta":{"Content-Type":"text/plain","x-cos-meta-test":"自定义的 meta","x-image-test":"自定义的 meta"},"url":"访问文件的源站 url"}

这里面的 key 是在上述 bucket 中新建的文件名字。

因此,咱们能够按照咱们的实际状况,将上面的内容简单修改一下,成为咱们格式,以便测试(在生产中,这个是自动生成的触发格式,并不须要咱们修改,咱们修改只是为了测试.

{
   "Records":[
      {
        "event": {
          "eventVersion":"1.0",
          "eventSource":"qcs::cos",
          "eventName":"cos: ObjectCreated: *",
          "eventTime":1501054710,
          "eventQueue":"qcs:0:cos:gz:1251111111:cos",
          "requestParameters":{
            "requestSourceIP": "111.111.111.111",
            "requestHeaders":{
              "Authorization": "上传的鉴权信息"
            }
          }
         },
         "cos":{
            "cosSchemaVersion":"1.0",
            "cosNotificationId":"设置的或返回的 ID",
            "cosBucket":{
               "name":"mytestcos",
               "appid":"appId",
               "region":"gz"
            },
            "cosObject":{
               "key":"test.png",
               "size":2598526,
               "meta":{
                 "Content-Type": "text/plain",
                 "x-cos-meta-test": "自定义的 meta",
                 "x-image-test": "自定义的 meta"
               },
               "url": "访问文件的源站url"
            }
         }
      }
   ]
}

这里主要修改了个人 cosBucket-name: mytestcos,以及 key: test.png

cos 修改

代码修改

之因此使用现有的模板,是由于该模板的有 PIL 和 qcloud_cos_v5 等相关 package,而这两个 package 正是咱们即将须要的,这样就能够省去咱们打包函数的流程,只须要进行简单修改便可实现。

添加水印:

def add_word(pic_path, save_path):
    # 打开图片
    im = Image.open(pic_path).convert('RGBA')
    # 新建一个空白图片,尺寸与打开图片同样
    txt = Image.new('RGBA', im.size, (0, 0, 0, 0))
    # 设置字体
    fnt = ImageFont.truetype("/tmp/font.ttf", 40)
    # 操做新建的空白图片>>将新建的图片添入画板
    d = ImageDraw.Draw(txt)
    # 在新建的图片上添加字体
    d.text((txt.size[0] - 220, txt.size[1] - 80), "By Dfounder", font=fnt,  fill=(255, 255, 255, 255))
    # 合并两个图片
    out = Image.alpha_composite(im, txt)
    # 保存图像
    out.save(save_path)

在添加水印的时候,咱们设置的是文字水印,因此须要设置字体和字号:

fnt = ImageFont.truetype("/tmp/font.ttf",40)

此时,咱们须要在执行以前,先将字体文件传入到 /tmp/ 文件夹下:

`response = client.get_object(Bucket="mytestcos-12567**", Key="font.ttf", )
response['Body'].get_stream_to_file('/tmp/font.ttf')`

以个人 cos 为例:

例子

而后,接下来就是对触发的 event 进行解析,包括得到新建的图像名称,从 COS 拉取,放到本地,而后进行水印等,再上传回新的 COS 中:

for record in event['Records']:
        try:
            bucket = record['cos']['cosBucket']['name'] + '-' + str(appid)
            key = record['cos']['cosObject']['key']
            key = key.replace('/' + str(appid) + '/' + record['cos']['cosBucket']['name'] + '/', '', 1)
            download_path = '/tmp/{}'.format(key)
            upload_path = '/tmp/new_pic-{}'.format(key)

            # 下载图片
            try:
                response = client.get_object(Bucket=bucket, Key=key, )
                response['Body'].get_stream_to_file(download_path)
            except CosServiceError as e:
                print(e.get_error_code())
                print(e.get_error_msg())
                print(e.get_resource_location())

            # 图像增长水印
            add_word(download_path, upload_path)


            # 图像上传
            response = client.put_object_from_local_file(
                Bucket=to_bucket,
                LocalFilePath=upload_path.decode('utf-8'),
                Key=("upload-" + key).decode('utf-8')
            )

        except Exception as e:
            print(e)

此处说明一下,为何要有两个存储桶?

由于咱们要把一个存储桶做为触发 SCF 函数的「工具」,若是咱们将水印结果再次写回这个存储桶,在不进行额外判断和处理的前提下,那么这个水印后的图片会再次水印,反反复复形成恶劣的循环,因此此处创建两个存储桶,能够下降难度,也能够保护性能,减小 BUG 诞生。

完整代码以下:

# -*- coding: utf-8 -*-

from PIL import Image, ImageFont, ImageDraw
from qcloud_cos_v5 import CosConfig
from qcloud_cos_v5 import CosS3Client
from qcloud_cos_v5 import CosServiceError
from qcloud_cos_v5 import CosClientError

appid = **  # 请替换为您的 APPID
secret_id = ***'  # 请替换为您的 SecretId
secret_key = **'  # 请替换为您的 SecretKey
region = u'ap-chengdu'  # 请替换为您bucket 所在的地域
token = ''
to_bucket = 'tobucket-12567***'  # 请替换为您用于存放压缩后图片的bucket

config = CosConfig(Secret_id=secret_id, Secret_key=secret_key, Region=region, Token=token)
client = CosS3Client(config)

response = client.get_object(Bucket="mytestcos-12567***", Key="font.ttf", )
response['Body'].get_stream_to_file('/tmp/font.ttf')

def add_word(pic_path, save_path):
    # 打开图片
    im = Image.open(pic_path).convert('RGBA')
    # 新建一个空白图片,尺寸与打开图片同样
    txt = Image.new('RGBA', im.size, (0, 0, 0, 0))
    # 设置字体
    fnt = ImageFont.truetype("/tmp/font.ttf", 40)
    # 操做新建的空白图片>>将新建的图片添入画板
    d = ImageDraw.Draw(txt)
    # 在新建的图片上添加字体
    d.text((txt.size[0] - 220, txt.size[1] - 80), "By Dfounder", font=fnt,  fill=(255, 255, 255, 255))
    # 合并两个图片
    out = Image.alpha_composite(im, txt)
    # 保存图像
    out.save(save_path)

def main_handler(event, context):
    for record in event['Records']:
        try:
            bucket = record['cos']['cosBucket']['name'] + '-' + str(appid)
            key = record['cos']['cosObject']['key']
            key = key.replace('/' + str(appid) + '/' + record['cos']['cosBucket']['name'] + '/', '', 1)
            download_path = '/tmp/{}'.format(key)
            upload_path = '/tmp/new_pic-{}'.format(key)

            # 下载图片
            try:
                response = client.get_object(Bucket=bucket, Key=key, )
                response['Body'].get_stream_to_file(download_path)
            except CosServiceError as e:
                print(e.get_error_code())
                print(e.get_error_msg())
                print(e.get_resource_location())

            # 图像增长水印
            add_word(download_path, upload_path)


            # 图像上传
            response = client.put_object_from_local_file(
                Bucket=to_bucket,
                LocalFilePath=upload_path.decode('utf-8'),
                Key=("upload-" + key).decode('utf-8')
            )

        except Exception as e:
            print(e)

这里面须要注意这几个参数:appid、secret_id、secret_key、to_bucket

这几个参数的来源以下:

参数来源

而 secretid,secretkey 则须要在这里获取:

id 获取

测试

以前我已经上传了一个测试图片在这个 bucket 中,名字是:test.png

test 图片

图片是这样子:

test 图片 2

而后咱们进行一下测试:

测试

能够看到,已经测试成功,接下来咱们能够去咱们的目标 bucket 中看看:

目标 bucket

能够看到成功生成了一个图片:

生成图片

能够看到图片的右下角,有咱们代码中添加的水印:

水印代码

至此,咱们完成了经过云函数 SCF 为您的网站图片添加水印的基本流程。

▎额外想说的

其实,这篇文章也算是抛砖引玉,你们不只能够经过这个流程压缩图像、添加水印,甚至还能够对图像进行其余操做。例如图像风格化处理等一些深加工,而这一切过程,都不会占用自身服务器资源,而是经过云函数 SCF 来完成的。

即便面对高并发,有大量的图片上传时,咱们要作的也仅仅是经过 SDK,将图片传入到腾讯云对象存储 COS 中。

传送门:

欢迎访问:Serverless 中文网,您能够在 最佳实践 里体验更多关于 Serverless 应用的开发!


推荐阅读: 《Serverless 架构:从原理、设计到项目实战》
相关文章
相关标签/搜索