├── .gitignore ├── README.md ├── metadata.json └── {{ projectName }} ├── custom ├── Funfile ├── bootstrap ├── example.docx ├── package-lock.json └── package.json ├── nodejs8 ├── .funignore ├── Funfile ├── example.docx ├── index.js ├── package-lock.json └── package.json ├── python3 ├── Funfile ├── example.docx └── index.py └── template.yml /.gitignore: -------------------------------------------------------------------------------- 1 | .fun 2 | node_modules 3 | event.evt 4 | .vscode 5 | .DS_Store 6 | .Funfile.generated.dockerfile -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LibreOffice 模板项目 2 | 3 | 该项目模板是一个基于 libreoffice 的 Word 转 PDF 工具,该项目是一个 funcraft 工程,借助 funcraft 工具进行依赖安装,上传 libreoffice 二进制文件到 NAS,并部署代码到阿里云的函数计算平台,作为一个 serverless 的 word 转换服务。 4 | 5 | ## 依赖工具 6 | 7 | 本项目是在 MacOS 下开发的,涉及到的工具是平台无关的,对于 Linux 和 Windows 桌面系统应该也同样适用。在开始本例之前请确保如下工具已经正确的安装,更新到最新版本,并进行正确的配置。 8 | 9 | * [Docker](https://www.docker.com/) 10 | * [Funcraft](https://github.com/alibaba/funcraft) 11 | 12 | 对于 MacOS 用户可以使用 [homebrew](https://brew.sh/) 进行安装: 13 | 14 | ```bash 15 | brew cask install docker 16 | brew tap vangie/formula 17 | brew install fun 18 | ``` 19 | 20 | Windows 和 Linux 用户安装请参考: 21 | 22 | 1. https://github.com/aliyun/fun/blob/master/docs/usage/installation.md 23 | 2. https://github.com/aliyun/fcli/releases 24 | 25 | 安装好后,记得先执行 `fun config` 初始化一下配置。 26 | 27 | **备注: 本文介绍的技巧需要 Fun 版本大于等于 3.0.6 。** 28 | 29 | ## 初始化 30 | 31 | 使用 fun init 命令可以快捷地将本模板项目初始化到本地。 32 | 33 | ```bash 34 | $ fun init vangie/libreoffice-example 35 | ``` 36 | 37 | **修改 template.yml 文件,将 OSS_REGION 和 OSS_BUCKET 改为您账号的 OSS 配置。** 38 | 39 | ## 安装依赖 40 | 41 | 42 | ```bash 43 | $ fun install 44 | ``` 45 | 46 | ## 本地测试 47 | 48 | ```bash 49 | # 本地测试 nodejs8 版本 50 | $ DISABLE_BIND_MOUNT_TMP_DIR=true fun local invoke word2pdf-nodejs8 51 | 52 | # 本地测试 python3 版本 53 | $ DISABLE_BIND_MOUNT_TMP_DIR=true fun local invoke word2pdf-python3 54 | 55 | # 本地测试 custom 版本 56 | $ DISABLE_BIND_MOUNT_TMP_DIR=true fun local invoke word2pdf-custom 57 | ``` 58 | 59 | 注意:`DISABLE_BIND_MOUNT_TMP_DIR=true` 环境变量主要是让 fun 不往 container 里挂载 /tmp 目录,因为 libreoffice 有个已知的问题,如果 /tmp 是 bind mount 的会直接退出,没有任何报错。 60 | 61 | ## 同步文件到 NAS 62 | 63 | 同步本地 NAS 目录 .fun/nas/auto-default/libreoffice/ 下的文件到 NAS 网盘 64 | 65 | ```bash 66 | $ fun nas sync 67 | ``` 68 | 69 | ## 部署 70 | 71 | 经过测试 libreoffice 在 MacOS 本地 Docker 环境里无法运行,可能和 Linux 的 kernel 版本有关,所以本例跳过了 fun local invoke,直接 deploy 到云端运行。 72 | 73 | ```bash 74 | $ fun deploy 75 | ``` 76 | 77 | ## 执行 78 | 79 | ### 调用 nodejs8 版本 80 | 81 | 82 | ```bash 83 | $ fun invoke word2pdf-nodejs8 84 | using template: template.yml 85 | ========= FC invoke Logs begin ========= 86 | FC Invoke Start RequestId: 9f68ec8b-a1ca-4fab-a605-e5414a878c7e 87 | load code for handler:index.handler 88 | 2019-10-14T08:20:52.559Z 9f68ec8b-a1ca-4fab-a605-e5414a878c7e [verbose] convert /tmp/example.docx -> /tmp/example.pdf using filter : writer_pdf_Export 89 | 90 | FC Invoke End RequestId: 9f68ec8b-a1ca-4fab-a605-e5414a878c7e 91 | 92 | Duration: 3397.91 ms, Billed Duration: 3400 ms, Memory Size: 640 MB, Max Memory Used: 203.46 MB 93 | ========= FC invoke Logs end ========= 94 | 95 | FC Invoke Result: 96 | http://vangie-test.oss-cn-shanghai.aliyuncs.com/example.pdf 97 | ``` 98 | 99 | ### 调用 python3 版本 100 | 101 | ```bash 102 | $ fun invoke word2pdf-python3 103 | using template: template.yml 104 | ========= FC invoke Logs begin ========= 105 | FC Invoke Start RequestId: 4bbb507d-1f57-4ca1-a877-4ce4d9096d4d 106 | convert /tmp/example.docx -> /tmp/example.pdf using filter : writer_pdf_Export 107 | FC Invoke End RequestId: 4bbb507d-1f57-4ca1-a877-4ce4d9096d4d 108 | 109 | Duration: 1855.37 ms, Billed Duration: 1900 ms, Memory Size: 640 MB, Max Memory Used: 67.22 MB 110 | ========= FC invoke Logs end ========= 111 | 112 | FC Invoke Result: 113 | upload to oss success, the url is http://vangie-test.oss-cn-shanghai.aliyuncs.com/example.pdf?security-token=CAISmwJ1q6Ft5B2yfSjIr4%2BHJvjBgI9I%2F6CEM1D1oEMFf7dD16D61Tz2IHpFfnFsBukftvU3nW5U5%2FYYlqZdVplOWU3Da%2BB364xK7Q757QoRDHjwv9I%2Bk5SANTW5KXyShb3%2FAYjQSNfaZY3eCTTtnTNyxr3XbCirW0ffX7SClZ9gaKZ8PGD6F00kYu1bPQx%2FssQXGGLMPPK2SH7Qj3HXEVBjt3gX6wo9y9zmnZDFtUKD0AymkbRJ%2BN%2BqGPX%2BMZkwZqUYesyuwel7epDG1CNt8BVQ%2FM909vceqG2f4o7EWgEAu0zYb7uEqMcqJQt4d7U8FaVLof7xj%2FRkt%2BDJkID6jh1LeOFSVSvcQ4avhc%2FFEvmkMdg3dL32K8pAU1cDq3ieGoABAiBcEZ5ZzqlsXeIfiEKFAsIlNr3yYTflfBp%2FOr%2BktvB54GISQyX%2BzAlhBeq1IkBl3pudBcz%2FSsluxyR9kySjvx07UU4Zdh5dS%2BaNrDimZVvKxxYZaMtA%2FFqetiO1NZ6iE6GPOBe0lb5Hg%2FRHUrSaVK5JgQR%2B0JSvy%2BEUcw%2B44jE%3D&OSSAccessKeyId=STS.NL2mBumPiHbo1vDDCPs8o8eQ7&Expires=1571041909&Signature=kfDwm6JRZ2odtXU5IML3ETQjHds%3D 114 | ``` 115 | 116 | 通过最后输出的 OSS 临时链接访问 pdf 文件,临时链接的有效期 10 分钟。 117 | 118 | ### 调用 custom 版本 119 | 120 | ```bash 121 | $fun invoke word2pdf-custom 122 | using template: template.yml 123 | ========= FC invoke Logs begin ========= 124 | Not all function logs are available, please retry 125 | FC Invoke End RequestId: d1e1be15-afca-48d8-a97c-8f55fd016567 126 | Duration: 1955.96 ms, Billed Duration: 2000 ms, Memory Size: 640 MB, Max Memory Used: 55.70 MB 127 | ========= FC invoke Logs end ========= 128 | 129 | FC Invoke Result: 130 | example.docx 131 | example.pdf 132 | hsperfdata_root 133 | pear 134 | ``` 135 | 136 | ## 参考阅读 137 | 138 | 1. [五分钟上线——函数计算 Word 转 PDF 云服务](https://www.atatech.org/articles/126345) 139 | 2. [手把手教您将 libreoffice 移植到函数计算平台](https://www.atatech.org/articles/126379) 140 | 3. https://github.com/awesome-fc/fc-libreoffice 141 | 4. [模板项目](https://github.com/vangie/libreoffice-example) 142 | 5. [Funcraft](https://github.com/alibaba/funcraft) 143 | 6. [Aliyun Serverless VSCode Extension](https://github.com/alibaba/serverless-vscode) 144 | -------------------------------------------------------------------------------- /metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "libreoffice-example", 3 | "description": "convert word to pdf by libreoffice which deploy on fc.", 4 | "copyOnlyPaths": [ "**/*.docx" ] 5 | } -------------------------------------------------------------------------------- /{{ projectName }}/custom/Funfile: -------------------------------------------------------------------------------- 1 | RUNTIME custom 2 | RUN fun-install apt-get install libcurl3-gnutls 3 | RUN fun-install apt-get install libxslt1.1 4 | RUN mkdir -p /mnt/auto/ 5 | WORKDIR /mnt/auto 6 | RUN curl https://fc-demo-public.oss-cn-hangzhou.aliyuncs.com/fun/examples/lo_7.0.0.0.alpha0.tar.gz -o lo.tar.gz 7 | RUN tar -xzf lo.tar.gz 8 | RUN rm lo.tar.gz 9 | 10 | -------------------------------------------------------------------------------- /{{ projectName }}/custom/bootstrap: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { exec, execSync } = require("child_process"); 4 | const express = require('express'); 5 | const app = express(); 6 | 7 | const binPath = '/mnt/auto/instdir/program/soffice'; 8 | const defaultArgs = ["--headless", "--invisible", "--nodefault", "--view", "--nolockcheck", "--nologo", "--norestore"]; 9 | const filePath = '/tmp/example.docx'; 10 | 11 | const invoke = (req, res) => { 12 | 13 | execSync(`cp -f /code/example.docx ${filePath}`); 14 | 15 | exec(`cd /tmp && ${binPath} ${defaultArgs.join(' ')} --convert-to pdf --outdir /tmp ${filePath}`, (error, stdout, stderr) => { 16 | if (error) { 17 | console.log(`error: ${error.message}`); 18 | } 19 | if (stderr) { 20 | console.log(`stderr: ${stderr}`); 21 | } 22 | console.log(`stdout: ${stdout}`); 23 | // execSync(`cd /tmp && rm ${filePath}`); 24 | // res.send(stdout); 25 | res.send(execSync(`ls /tmp`)); 26 | }); 27 | } 28 | 29 | const initialize = (req, res) => { 30 | res.send('NOT Implement'); 31 | } 32 | 33 | 34 | app.post('/', (req, res) => { 35 | const ctrlPath = req.headers['x-fc-control-path']; 36 | if (ctrlPath == '/invoke') { 37 | invoke(req, res); 38 | } else if (ctrlPath == '/initialize') { 39 | initialize(req, res); 40 | } else { 41 | res.send(JSON.stringify(req.headers, null, 2)); 42 | } 43 | }); 44 | 45 | app.post('/invoke', invoke); 46 | 47 | app.listen(9000, () => { 48 | console.log('server listening on port 9000!'); 49 | }); -------------------------------------------------------------------------------- /{{ projectName }}/custom/example.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vangie/libreoffice-example/38b918f516543bf680a61d853cac46dcc47b5e95/{{ projectName }}/custom/example.docx -------------------------------------------------------------------------------- /{{ projectName }}/custom/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "custom", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "accepts": { 8 | "version": "1.3.7", 9 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 10 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 11 | "requires": { 12 | "mime-types": "~2.1.24", 13 | "negotiator": "0.6.2" 14 | } 15 | }, 16 | "array-flatten": { 17 | "version": "1.1.1", 18 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 19 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 20 | }, 21 | "body-parser": { 22 | "version": "1.19.0", 23 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", 24 | "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", 25 | "requires": { 26 | "bytes": "3.1.0", 27 | "content-type": "~1.0.4", 28 | "debug": "2.6.9", 29 | "depd": "~1.1.2", 30 | "http-errors": "1.7.2", 31 | "iconv-lite": "0.4.24", 32 | "on-finished": "~2.3.0", 33 | "qs": "6.7.0", 34 | "raw-body": "2.4.0", 35 | "type-is": "~1.6.17" 36 | } 37 | }, 38 | "bytes": { 39 | "version": "3.1.0", 40 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", 41 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" 42 | }, 43 | "content-disposition": { 44 | "version": "0.5.3", 45 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", 46 | "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", 47 | "requires": { 48 | "safe-buffer": "5.1.2" 49 | } 50 | }, 51 | "content-type": { 52 | "version": "1.0.4", 53 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 54 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 55 | }, 56 | "cookie": { 57 | "version": "0.4.0", 58 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", 59 | "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" 60 | }, 61 | "cookie-signature": { 62 | "version": "1.0.6", 63 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 64 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 65 | }, 66 | "debug": { 67 | "version": "2.6.9", 68 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 69 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 70 | "requires": { 71 | "ms": "2.0.0" 72 | } 73 | }, 74 | "depd": { 75 | "version": "1.1.2", 76 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 77 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 78 | }, 79 | "destroy": { 80 | "version": "1.0.4", 81 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 82 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 83 | }, 84 | "ee-first": { 85 | "version": "1.1.1", 86 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 87 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 88 | }, 89 | "encodeurl": { 90 | "version": "1.0.2", 91 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 92 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 93 | }, 94 | "escape-html": { 95 | "version": "1.0.3", 96 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 97 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 98 | }, 99 | "etag": { 100 | "version": "1.8.1", 101 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 102 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 103 | }, 104 | "express": { 105 | "version": "4.17.1", 106 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", 107 | "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", 108 | "requires": { 109 | "accepts": "~1.3.7", 110 | "array-flatten": "1.1.1", 111 | "body-parser": "1.19.0", 112 | "content-disposition": "0.5.3", 113 | "content-type": "~1.0.4", 114 | "cookie": "0.4.0", 115 | "cookie-signature": "1.0.6", 116 | "debug": "2.6.9", 117 | "depd": "~1.1.2", 118 | "encodeurl": "~1.0.2", 119 | "escape-html": "~1.0.3", 120 | "etag": "~1.8.1", 121 | "finalhandler": "~1.1.2", 122 | "fresh": "0.5.2", 123 | "merge-descriptors": "1.0.1", 124 | "methods": "~1.1.2", 125 | "on-finished": "~2.3.0", 126 | "parseurl": "~1.3.3", 127 | "path-to-regexp": "0.1.7", 128 | "proxy-addr": "~2.0.5", 129 | "qs": "6.7.0", 130 | "range-parser": "~1.2.1", 131 | "safe-buffer": "5.1.2", 132 | "send": "0.17.1", 133 | "serve-static": "1.14.1", 134 | "setprototypeof": "1.1.1", 135 | "statuses": "~1.5.0", 136 | "type-is": "~1.6.18", 137 | "utils-merge": "1.0.1", 138 | "vary": "~1.1.2" 139 | } 140 | }, 141 | "finalhandler": { 142 | "version": "1.1.2", 143 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 144 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 145 | "requires": { 146 | "debug": "2.6.9", 147 | "encodeurl": "~1.0.2", 148 | "escape-html": "~1.0.3", 149 | "on-finished": "~2.3.0", 150 | "parseurl": "~1.3.3", 151 | "statuses": "~1.5.0", 152 | "unpipe": "~1.0.0" 153 | } 154 | }, 155 | "forwarded": { 156 | "version": "0.1.2", 157 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 158 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 159 | }, 160 | "fresh": { 161 | "version": "0.5.2", 162 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 163 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 164 | }, 165 | "http-errors": { 166 | "version": "1.7.2", 167 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", 168 | "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", 169 | "requires": { 170 | "depd": "~1.1.2", 171 | "inherits": "2.0.3", 172 | "setprototypeof": "1.1.1", 173 | "statuses": ">= 1.5.0 < 2", 174 | "toidentifier": "1.0.0" 175 | } 176 | }, 177 | "iconv-lite": { 178 | "version": "0.4.24", 179 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 180 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 181 | "requires": { 182 | "safer-buffer": ">= 2.1.2 < 3" 183 | } 184 | }, 185 | "inherits": { 186 | "version": "2.0.3", 187 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 188 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 189 | }, 190 | "ipaddr.js": { 191 | "version": "1.9.1", 192 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 193 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" 194 | }, 195 | "media-typer": { 196 | "version": "0.3.0", 197 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 198 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 199 | }, 200 | "merge-descriptors": { 201 | "version": "1.0.1", 202 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 203 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 204 | }, 205 | "methods": { 206 | "version": "1.1.2", 207 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 208 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 209 | }, 210 | "mime": { 211 | "version": "1.6.0", 212 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 213 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 214 | }, 215 | "mime-db": { 216 | "version": "1.43.0", 217 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", 218 | "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==" 219 | }, 220 | "mime-types": { 221 | "version": "2.1.26", 222 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", 223 | "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", 224 | "requires": { 225 | "mime-db": "1.43.0" 226 | } 227 | }, 228 | "ms": { 229 | "version": "2.0.0", 230 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 231 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 232 | }, 233 | "negotiator": { 234 | "version": "0.6.2", 235 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 236 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" 237 | }, 238 | "on-finished": { 239 | "version": "2.3.0", 240 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 241 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 242 | "requires": { 243 | "ee-first": "1.1.1" 244 | } 245 | }, 246 | "parseurl": { 247 | "version": "1.3.3", 248 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 249 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 250 | }, 251 | "path-to-regexp": { 252 | "version": "0.1.7", 253 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 254 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 255 | }, 256 | "proxy-addr": { 257 | "version": "2.0.6", 258 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", 259 | "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", 260 | "requires": { 261 | "forwarded": "~0.1.2", 262 | "ipaddr.js": "1.9.1" 263 | } 264 | }, 265 | "qs": { 266 | "version": "6.7.0", 267 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 268 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" 269 | }, 270 | "range-parser": { 271 | "version": "1.2.1", 272 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 273 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 274 | }, 275 | "raw-body": { 276 | "version": "2.4.0", 277 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", 278 | "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", 279 | "requires": { 280 | "bytes": "3.1.0", 281 | "http-errors": "1.7.2", 282 | "iconv-lite": "0.4.24", 283 | "unpipe": "1.0.0" 284 | } 285 | }, 286 | "safe-buffer": { 287 | "version": "5.1.2", 288 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 289 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 290 | }, 291 | "safer-buffer": { 292 | "version": "2.1.2", 293 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 294 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 295 | }, 296 | "send": { 297 | "version": "0.17.1", 298 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", 299 | "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", 300 | "requires": { 301 | "debug": "2.6.9", 302 | "depd": "~1.1.2", 303 | "destroy": "~1.0.4", 304 | "encodeurl": "~1.0.2", 305 | "escape-html": "~1.0.3", 306 | "etag": "~1.8.1", 307 | "fresh": "0.5.2", 308 | "http-errors": "~1.7.2", 309 | "mime": "1.6.0", 310 | "ms": "2.1.1", 311 | "on-finished": "~2.3.0", 312 | "range-parser": "~1.2.1", 313 | "statuses": "~1.5.0" 314 | }, 315 | "dependencies": { 316 | "ms": { 317 | "version": "2.1.1", 318 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 319 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 320 | } 321 | } 322 | }, 323 | "serve-static": { 324 | "version": "1.14.1", 325 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", 326 | "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", 327 | "requires": { 328 | "encodeurl": "~1.0.2", 329 | "escape-html": "~1.0.3", 330 | "parseurl": "~1.3.3", 331 | "send": "0.17.1" 332 | } 333 | }, 334 | "setprototypeof": { 335 | "version": "1.1.1", 336 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", 337 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" 338 | }, 339 | "statuses": { 340 | "version": "1.5.0", 341 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 342 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 343 | }, 344 | "toidentifier": { 345 | "version": "1.0.0", 346 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", 347 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" 348 | }, 349 | "type-is": { 350 | "version": "1.6.18", 351 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 352 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 353 | "requires": { 354 | "media-typer": "0.3.0", 355 | "mime-types": "~2.1.24" 356 | } 357 | }, 358 | "unpipe": { 359 | "version": "1.0.0", 360 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 361 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 362 | }, 363 | "utils-merge": { 364 | "version": "1.0.1", 365 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 366 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 367 | }, 368 | "vary": { 369 | "version": "1.1.2", 370 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 371 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 372 | } 373 | } 374 | } 375 | -------------------------------------------------------------------------------- /{{ projectName }}/custom/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "custom", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "express": "^4.17.1" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /{{ projectName }}/nodejs8/.funignore: -------------------------------------------------------------------------------- 1 | .env 2 | package-lock.json 3 | template.yml 4 | .funignore 5 | ./node_modules/fc-libreoffice/bin/lo.tar.br -------------------------------------------------------------------------------- /{{ projectName }}/nodejs8/Funfile: -------------------------------------------------------------------------------- 1 | RUNTIME nodejs8 2 | RUN fun-install apt-get install libnss3 3 | RUN curl -L https://fc-demo-public.oss-cn-hangzhou.aliyuncs.com/fun/examples/libstdc%2B%2B6_6.3.0-18%2Bdeb9u1_amd64.deb -o /tmp/libstdc++6_6.3.0-18+deb9u1_amd64.deb 4 | RUN dpkg -x /tmp/libstdc++6_6.3.0-18+deb9u1_amd64.deb /code/.fun/root 5 | RUN rm /tmp/libstdc++6_6.3.0-18+deb9u1_amd64.deb 6 | RUN mkdir -p /mnt/auto/ 7 | WORKDIR /mnt/auto 8 | RUN curl https://fc-demo-public.oss-cn-hangzhou.aliyuncs.com/fun/examples/lo_7.0.0.0.alpha0.tar.gz -o lo.tar.gz 9 | RUN tar -xzf lo.tar.gz 10 | RUN rm lo.tar.gz -------------------------------------------------------------------------------- /{{ projectName }}/nodejs8/example.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vangie/libreoffice-example/38b918f516543bf680a61d853cac46dcc47b5e95/{{ projectName }}/nodejs8/example.docx -------------------------------------------------------------------------------- /{{ projectName }}/nodejs8/index.js: -------------------------------------------------------------------------------- 1 | const OSS = require('ali-oss').Wrapper; 2 | const { execSync } = require('child_process'); 3 | 4 | const binPath = '/mnt/auto/instdir/program/soffice'; 5 | const defaultArgs = ["--headless", "--invisible", "--nodefault", "--view", "--nolockcheck", "--nologo", "--norestore"]; 6 | 7 | module.exports.handler = (event, context, callback) => { 8 | const filePath = '/tmp/example.docx'; 9 | 10 | execSync(`cp -f /code/example.docx ${filePath}`); 11 | 12 | const cmd = `${binPath} ${defaultArgs.join(' ')} --convert-to pdf --outdir /tmp ${filePath}`; 13 | 14 | const logs = execSync(cmd, { cwd: '/tmp' }); 15 | 16 | execSync(`rm ${filePath}`, { cwd: '/tmp'}); 17 | 18 | console.log(logs.toString('utf8')); 19 | 20 | uploadToOss(context, '/tmp/example.pdf').then((url) => { 21 | callback(null, url); 22 | }).catch((e) => { 23 | callback(e); 24 | }) 25 | }; 26 | 27 | async function uploadToOss(context, file) { 28 | let client = new OSS({ 29 | region: `oss-${process.env.OSS_REGION || context.region}`, 30 | accessKeyId: context.credentials.accessKeyId, 31 | accessKeySecret: context.credentials.accessKeySecret, 32 | stsToken: context.credentials.securityToken, 33 | bucket: process.env.OSS_BUCKET 34 | }); 35 | 36 | let result = await client.put('example.pdf', file); 37 | await client.putACL('example.pdf', 'public-read'); 38 | 39 | return result.url.replace('-internal.aliyuncs.com/', '.aliyuncs.com/'); 40 | } -------------------------------------------------------------------------------- /{{ projectName }}/nodejs8/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "lockfileVersion": 1 3 | } 4 | -------------------------------------------------------------------------------- /{{ projectName }}/nodejs8/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "libreoffice-example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /{{ projectName }}/python3/Funfile: -------------------------------------------------------------------------------- 1 | RUNTIME python3 2 | RUN fun-install apt-get install libnss3 3 | RUN curl -L https://fc-demo-public.oss-cn-hangzhou.aliyuncs.com/fun/examples/libstdc%2B%2B6_6.3.0-18%2Bdeb9u1_amd64.deb -o /tmp/libstdc++6_6.3.0-18+deb9u1_amd64.deb 4 | RUN dpkg -x /tmp/libstdc++6_6.3.0-18+deb9u1_amd64.deb /code/.fun/root 5 | RUN rm /tmp/libstdc++6_6.3.0-18+deb9u1_amd64.deb 6 | RUN mkdir -p /mnt/auto/ 7 | WORKDIR /mnt/auto 8 | RUN curl https://fc-demo-public.oss-cn-hangzhou.aliyuncs.com/fun/examples/lo_7.0.0.0.alpha0.tar.gz -o lo.tar.gz 9 | RUN tar -xzf lo.tar.gz 10 | RUN rm lo.tar.gz -------------------------------------------------------------------------------- /{{ projectName }}/python3/example.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vangie/libreoffice-example/38b918f516543bf680a61d853cac46dcc47b5e95/{{ projectName }}/python3/example.docx -------------------------------------------------------------------------------- /{{ projectName }}/python3/index.py: -------------------------------------------------------------------------------- 1 | import io, os 2 | from shutil import copy2 3 | import oss2 4 | 5 | default_args = ["--headless", "--invisible", "--nodefault", "--view", "--nolockcheck", "--nologo", "--norestore"] 6 | 7 | def handler(event, context): 8 | copy2('/code/example.docx', '/tmp/example.docx') 9 | os.chdir('/tmp') 10 | os.system("/mnt/auto/instdir/program/soffice %s --convert-to pdf --outdir /tmp /tmp/example.docx" % (' '.join(default_args))) 11 | return uploadToOss(context.region, context.credentials) 12 | 13 | def uploadToOss(region, credentials): 14 | oss_endpoint = "http://oss-%s.aliyuncs.com" % (os.environ['OSS_REGION'] or region) 15 | bucket_name = '' 16 | if credentials.security_token: 17 | auth = oss2.StsAuth(credentials.access_key_id, credentials.access_key_secret, credentials.security_token) 18 | else: 19 | auth = oss2.Auth(credentials.access_key_id, credentials.access_key_secret) 20 | bucket = oss2.Bucket(auth, oss_endpoint, os.environ['OSS_BUCKET']) 21 | result = bucket.put_object_from_file('example.pdf','/tmp/example.pdf') 22 | if result.status == 200: 23 | return "upload to oss success, the url is %s" % bucket.sign_url('GET', 'example.pdf', 600) 24 | else: 25 | return "upload fail, error code %s " % result.status -------------------------------------------------------------------------------- /{{ projectName }}/template.yml: -------------------------------------------------------------------------------- 1 | ROSTemplateFormatVersion: '2015-09-01' 2 | Transform: 'Aliyun::Serverless-2018-04-03' 3 | Resources: 4 | libreoffice: 5 | Type: 'Aliyun::Serverless::Service' 6 | Properties: 7 | Description: 'libreoffice example' 8 | NasConfig: Auto 9 | Policies: 10 | - AliyunOSSFullAccess 11 | word2pdf-nodejs8: 12 | Type: 'Aliyun::Serverless::Function' 13 | Properties: 14 | Handler: index.handler 15 | Runtime: nodejs8 16 | Timeout: 60 17 | MemorySize: 640 18 | CodeUri: './nodejs8' 19 | EnvironmentVariables: 20 | LD_LIBRARY_PATH: /code/.fun/root/usr/lib/x86_64-linux-gnu/nss 21 | OSS_REGION: cn-shanghai 22 | OSS_BUCKET: vangie-test 23 | word2pdf-python3: 24 | Type: 'Aliyun::Serverless::Function' 25 | Properties: 26 | Handler: index.handler 27 | Runtime: python3 28 | Timeout: 60 29 | MemorySize: 640 30 | CodeUri: './python3' 31 | EnvironmentVariables: 32 | LD_LIBRARY_PATH: /code/.fun/root/usr/lib/x86_64-linux-gnu/nss 33 | OSS_REGION: cn-shanghai 34 | OSS_BUCKET: vangie-test 35 | word2pdf-custom: 36 | Type: 'Aliyun::Serverless::Function' 37 | Properties: 38 | Handler: 'project::example.App::HandleRequest' 39 | Runtime: custom 40 | Timeout: 60 41 | MemorySize: 640 42 | CodeUri: ./custom --------------------------------------------------------------------------------