开发函数计算的正确姿势——借助 Ghostscript 将 PDF 转换成 JPG

前言

首先介绍下在本文出现的几个比较重要的概念:

函数计算(Function Compute):函数计算是一个事件驱动的服务,通过函数计算,用户无需管理服务器等运行情况,只需编写代码并上传。函数计算准备计算资源,并以弹性伸缩的方式运行用户代码,而用户只需根据实际代码运行所消耗的资源进行付费。函数计算更多信息参考
Fun:Fun 是一个用于支持 Serverless 应用部署的工具,能帮助您便捷地管理函数计算、API 网关、日志服务等资源。它通过一个资源配置文件(template.yml),协助您进行开发、构建、部署操作。Fun 的更多文档参考
Ghostscript:Ghostscript 是一套建基于Adobe、PostScript及可移植文档格式(PDF)的页面描述语言等而编译成的自由软件。参见维基百科词条

备注: 本文介绍的技巧需要 Fun 版本大于等于 3.0.0-beta.2 。

依赖工具

本项目是在 MacOS 下开发的,涉及到的工具是平台无关的,对于 Linux 和 Windows 桌面系统应该也同样适用。在开始本例之前请确保如下工具已经正确的安装,更新到最新版本,并进行正确的配置。

Fun 工具依赖于 docker 来模拟本地环境。

对于 MacOS 用户可以使用 homebrew 进行安装:

brew cask install docker
brew tap vangie/formula
brew install fun

Windows 和 Linux 用户安装请参考:

  1. https://github.com/aliyun/fun/blob/master/docs/usage/installation.md

安装好后,记得先执行 fun config 初始化一下配置。

注意, 如果你已经安装过了 fun,确保 fun 的版本在 3.0.0-beta.2 以上。

$ fun --version
3.0.0-beta.2

Linux 下将 PDF 转换成 JPG

函数计算默认的 linux 环境是 Debian Jessie,首先我们先解决 Debain Jessie 环境下如何将 PDF 文件转换成 JPG 文件的问题。使用 fun install sbox 启动一个沙箱环境,在沙箱环境进行如下操作:

$ fun install sbox -r nodejs10 -i
root@fc-nodejs10:/code# apt-get update && apt-get install -y ghostscript
....
root@fc-nodejs10:/code# ls
test.pdf
root@fc-nodejs10:/code# gs -sDEVICE=jpeg -dTextAlphaBits=4 -r144 -o test.jpg test.pdf
GPL Ghostscript 9.26 (2018-11-20)
Copyright (C) 2018 Artifex Software, Inc.  All rights reserved.
This software comes with NO WARRANTY: see the file PUBLIC for details.
Processing pages 1 through 1.
Page 1
root@fc-nodejs10:/code# ls
test.jpg  test.pdf

其中转换命令为 gs -sDEVICE=jpeg -dTextAlphaBits=4 -r144 -o test.jpg test.pdf,其选项为

  • -sDEVICE=jpeg 指定输出设备为 jpeg,另外一个可选值为 jpeggray
  • -o 指定输出文件
  • -dTextAlphaBits 指定文本采样抗锯齿,另一个类似的选项为 -dGraphicsAlphaBits 用于图像采样抗锯齿。
  • -r144 设定图像的 dpi 为 144。

更多选项参见 https://www.ghostscript.com/doc/9.21/Devices.htm

移植到函数计算

下面我们将上面在 sbox 中 ghostscript 转换的实验成果移植到函数计算的 nodejs10 runtime 中。首先初始化一个本地的 fun 项目,借助于 fun 可以本地方便地安装依赖,本地调试并打包上传。

初始化项目

$ fun init event-nodejs10
Start rendering template...
+ /Users/vangie/Desktop/test
+ /Users/vangie/Desktop/test/.funignore
+ /Users/vangie/Desktop/test/index.js
+ /Users/vangie/Desktop/test/template.yml
finish rendering template.

将 template.yml 的内容更新为

ROSTemplateFormatVersion: '2015-09-01'
Transform: 'Aliyun::Serverless-2018-04-03'
Resources:
  ghostscript:
    Type: 'Aliyun::Serverless::Service'
    Properties:
      Description: 'helloworld'
    pdf2jpg:
      Type: 'Aliyun::Serverless::Function'
      Properties:
        Handler: index.handler
        Runtime: nodejs10
        CodeUri: './'
        EnvironmentVariables:
          GS_LIB: ".fun/root/usr/share/ghostscript/9.26/Resource/Init:\
            .fun/root/usr/share/ghostscript/9.26/lib:\
            .fun/root/usr/share/ghostscript/9.26/Resource/Font:\
            .fun/root/usr/share/ghostscript/fonts:\
            .fun/root/var/lib/ghostscript/fonts:\
            .fun/root/usr/share/ghostscript/fonts:\
            .fun/root/usr/share/fonts"

其中环境变量 GS_LIB 是为了支持 ghostscript 安装在代码目录所需要的。

将 index.js 的内容更新为

const { exec } = require('child_process');

module.exports.handler = function (event, context, callback) {
  const cmd = 'gs -sDEVICE=jpeg -dTextAlphaBits=4 -r144 -o /tmp/test.jpg test.pdf';
  exec(cmd, (err, stdout, stderr) => {
    if (err) {
      console.log(stdout);
      console.log(stderr);
      callback(err, "convert fail.\n");
    } else {
      console.log(stdout)
      callback(null, 'convert success.\nJPG file save to /tmp/test.jpg\n');
    }
  });
};

安装 ghostscript

使用如下命令安装 ghostscript

$ fun install -p apt -r nodejs10 ghostscript --save

ghostscript 会被安装到当前目录下的 .fun 目录内

$  tree . -a -L 4
.
├── .fun
│   └── root
│       ├── etc
│       │   ├── fonts
│       │   ├── ghostscript
│       │   └── libpaper.d
│       ├── usr
│       │   ├── bin
│       │   ├── lib
│       │   ├── sbin
│       │   └── share
│       └── var
│           └── lib
├── .funignore
├── README.md
├── fun.yml
├── index.js
└── template.yml

16 directories, 7 files

本地测试

在代码目录放一个 test.pdf 文件

$  tree          
.
├── README.md
├── fun.yml
├── index.js
├── template.yml
└── test.pdf

然后通过 fun local invoke 本地调用

$  fun local invoke pdf2jpg     
using template: template.yml
FC Invoke Start RequestId: 3ea14d81-fd6b-4259-b9a5-dde29c2f022a
load code for handler:index.handler
2019-09-03T07:36:20.200Z 3ea14d81-fd6b-4259-b9a5-dde29c2f022a [verbose] stdout =================== START
2019-09-03T07:36:20.200Z 3ea14d81-fd6b-4259-b9a5-dde29c2f022a [verbose] GPL Ghostscript 9.26 (2018-11-20)
Copyright (C) 2018 Artifex Software, Inc.  All rights reserved.
This software comes with NO WARRANTY: see the file PUBLIC for details.
Warning: the map file cidfmap was not found.
Processing pages 1 through 1.
Page 1

2019-09-03T07:36:20.201Z 3ea14d81-fd6b-4259-b9a5-dde29c2f022a [verbose] stdout =================== END
FC Invoke End RequestId: 3ea14d81-fd6b-4259-b9a5-dde29c2f022a
convert success.
JPG file save to /tmp/test.jpg
2019-09-03T07:36:20.212Z 3ea14d81-fd6b-4259-b9a5-dde29c2f022a [error] (node:23) [DEP0005] DeprecationWarning: Buffer() is deprecated due to security and usability issues. Please use the Buffer.alloc(), Buffer.allocUnsafe(), or Buffer.from() methods instead.


RequestId: 3ea14d81-fd6b-4259-b9a5-dde29c2f022a          Billed Duration: 1590 ms        Memory Size: 1998 MB    Max Memory Used: 32 MB

生成的 test.jpg 文件可以在 .fun/tmp/invoke 目录下找到

$  tree .fun/tmp/invoke -L 3
.fun/tmp/invoke
└── ghostscript
    └── pdf2jpg
        ├── agenthubout.log
        └── test.jpg

部署并调用

通过 fun deploy 命令快捷部署到函数计算平台

$  fun deploy              
using template: template.yml
using region: cn-shanghai
using accountId: ***********4733
using accessKeyId: ***********EUz3
using timeout: 60

Waiting for service ghostscript to be deployed...
        Waiting for function pdf2jpg to be deployed...
                Waiting for packaging function pdf2jpg code...
                package function pdf2jpg code done, the number of files you have packaged is:1048
        function pdf2jpg deploy success
service ghostscript deploy success

使用 fun invoke <function_name> 调用函数。

$  fun invoke pdf2jpg
using template: template.yml
========= FC invoke Logs begin =========
FC Invoke Start RequestId: 99cc8b32-6084-4a6b-a1ff-444e06e10eca
load code for handler:index.handler
2019-09-03T07:49:47.454Z 99cc8b32-6084-4a6b-a1ff-444e06e10eca [verbose] stdout =================== START
2019-09-03T07:49:47.455Z 99cc8b32-6084-4a6b-a1ff-444e06e10eca [verbose] GPL Ghostscript 9.26 (2018-11-20)
Copyright (C) 2018 Artifex Software, Inc.  All rights reserved.
This software comes with NO WARRANTY: see the file PUBLIC for details.
Warning: the map file cidfmap was not found.
Processing pages 1 through 1.
Page 1

2019-09-03T07:49:47.455Z 99cc8b32-6084-4a6b-a1ff-444e06e10eca [verbose] stdout =================== END
FC Invoke End RequestId: 99cc8b32-6084-4a6b-a1ff-444e06e10eca

Duration: 526.16 ms, Billed Duration: 600 ms, Memory Size: 128 MB, Max Memory Used: 57.09 MB
========= FC invoke Logs end =========

FC Invoke Result:
convert success.
JPG file save to /tmp/test.jpg

参考阅读

  1. 源码项目
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351

推荐阅读更多精彩内容