前言
自从人类进入信息时代以来,办公文档是每个人日常频繁使用的工具,ppt、word、xls、wps、pdf 等为我们工作和生活带来了很多的便利,尤其进入云计算和移动互联网时代,人们可以利用各种终端来预览 ppt、word、pdf 等相关文档进行工作和学习,这种情况下,文档之间的格式转换,各种终端的适配预览显得尤为重要,在本文中,我们通过一个ppt 转换成 vector 并且进行预览的例子,来看看阿里云函数计算和智能媒体管理相结合,快速实现一个弹性高可用的文档转换/预览服务。
FC&IMM 文档预览体验入口地址:
http://fcdemo.mofangdegisn.cn/fc-imm-demo
您可以使用不同的设备(比如pc,ipad, 手机等)打开上面这个链接,预览效果很好,可以很好地适配各种终端
函数计算
阿里云函数计算是一个事件驱动的serverless计算服务。通过函数计算,您无需管理服务器等基础设施,只需编写代码并上传。函数计算会为您准备好计算资源,以弹性、可靠的方式运行您的代码,具体表现为:
- 无需采购和管理服务器等基础设施
- 按需付费
- 专注业务逻辑的开发,能极大提高开发效率
- 稳定高可用,毫秒级别弹性伸缩,快速实现底层扩容以应对峰值压力
- 提供日志查询、性能监控、报警等功能快速排查故障
智能媒体管理
阿里云智能媒体管理(Intelligent Media Management,简称 IMM),是阿里云提供的针对媒体数据的高级、智能管理服务。它具有与平台无关的 RESTful API 接口,为阿里云上的非结构化存储数据(例如,OSS 中的视频、图片、文档等数据)提供快捷的数据处理通道,比如 OFFICE 格式转换,图片、视频的编辑处理,以及人工智能的价值数据提取和检索(例如,标签识别、人脸分组)。IMM 提供场景化构建的一站式数据应用解决方案,适合媒资管理、智能网盘、社交应用、图库图床等开发者使用。
在本文中,我们主要讨论智能媒体管理产品文档转换/预览功能
弹性高可用的文档转换/预览服务快速构建
在本案例中,我们对一个ppt文档转换成vector格式,然后直接使用“智能媒体管理”服务提供的预览渲染引擎, 对转换成功后的文件进行预览。本文只针对ppt 转换成 vector,智能媒体支持十分丰富的文档转换,具体参考:https://help.aliyun.com/document_detail/63761.html
在案例中,我们使用的阿里云资源在同一个region-杭州
1. 开通函数计算、智能媒体管理和对象存储
2. 登录智能媒体管理控制台),创建一个project,假设为 imm-demo
3. 登录对象存储控制台, 创建一个bucket,假设为 fc-imm-demo
,
- 由于要浏览器进行预览,这里设置跨域访问配置
*.imm.aliyun.com
- 新建一个测试目录,上传测试ppt文件测试文件到指定目录
4. 登录函数计算控制台, 创建相应的service 和 function, 具体参考使用控制台编写函数
- 配置service的role 具有访问imm和oss读的权限
配置oss读权限主要是为了生成安全预览文档的url地址,在本文中直接授予service 的role 具有
AliyunIMMFullAccess
和AliyunOSSReadOnlyAccess
, 当然为了安全,您可以把oss读权限的粒度具体到某个 bucket 和某个文件夹。
如果您不想使用函数计算中 service 中的 role 参与生成预览url, 您可以直接创建单独的子账号,在函数调用ram相关服务获取sts,过程可以参考:https://yq.aliyun.com/articles/589902
- 编写函数, 函数计算 python/nodejs runtime 内置了 imm 的 sdk,直接编写代码, php runtime 将代码和函数实现文件一起打包(可以在附件直接下载代码 zip 包fc-imm-php.zip),具体可以参考 PHP runtime 使用自定义库
代码如下:
python 版本
#-*- coding: utf8 -*-
import json, time
from collections import OrderedDict
from aliyunsdkcore.client import AcsClient
from aliyunsdkimm.request.v20170906 import CreateOfficeConversionTaskRequest
from aliyunsdkimm.request.v20170906 import GetOfficeConversionTaskRequest
from aliyunsdkcore.auth.credentials import StsTokenCredential
try:
from urllib import quote
except:
from urllib.parse import quote
PREVIEWURL = 'https://preview.imm.aliyun.com/index.html';
PREVIEWTGTPATH = 'fc-demo-preview-output-py';
immProject = "imm-demo";
srcUri = "oss://fc-imm-demo-sz/test-data/office/test.pptx";
# https://help.aliyun.com/document_detail/63761.html
# https://help.aliyun.com/document_detail/74947.html
# 使用前端渲染引擎预览,这里的type必须是vector
tgtType = "vector"
def getBucketByUri(uri):
if uri.startswith("oss://"):
return uri[6:].split("/")[0]
raise Exception("SrcUri is invalid")
def getFileNameByUri(uri):
return uri.split("/")[-1]
def handler(event, context):
creds = context.credentials
sts_token_credential = StsTokenCredential(creds.access_key_id, creds.access_key_secret, creds.security_token)
clt = AcsClient(region_id=context.region, credential=sts_token_credential)
bucket = getBucketByUri(srcUri)
fileName = getFileNameByUri(srcUri)
tgtUri = "oss://{0}/{1}/{2}".format(bucket, PREVIEWTGTPATH, fileName)
createReq = CreateOfficeConversionTaskRequest.CreateOfficeConversionTaskRequest()
createReq.set_Project(immProject)
createReq.set_SrcUri(srcUri)
createReq.set_TgtUri(tgtUri)
createReq.set_TgtType(tgtType)
response = clt.do_action_with_exception(createReq)
print(response)
res = json.loads(response)
taskId = res["TaskId"]
getReq = GetOfficeConversionTaskRequest.GetOfficeConversionTaskRequest()
getReq.set_Project(immProject)
getReq.set_TaskId(taskId)
period = 1
timeout = 30
start = time.time()
while True:
response = clt.do_action_with_exception(getReq)
#print(response)
status = json.loads(response)["Status"]
if status == "Finished": #任务完成
print("Task finished.")
break
if status == "Failed": #任务失败
print("Task failed.")
break
if time.time() - start > timeout: #任务超时
print("Task timeout.")
break
time.sleep(period)
# get preview url
params = OrderedDict()
params["url"] = "http://" + context.region + ".aliyuncs.com/" + PREVIEWTGTPATH + "/" + fileName
params["accessKeyId"] = creds.access_key_id
params["accessKeySecret"] = creds.access_key_secret
params["stsToken"] = quote(creds.security_token)
params["region"] = "oss-" + context.region
params["bucket"] = bucket
paramsLi = [ k + "=" + v for k, v in params.items()]
paramsStr = "&" .join(paramsLi)
preview = PREVIEWURL + "?" + paramsStr
print(preview)
return preview
nodejs 版本
const { RPCClient } = require('@alicloud/pop-core');
function getBucketByUri(uri) {
if(uri.startsWith("oss://")){
return uri.substr(6).split("/")[0];
}
throw new Exception("SrcUri is invalid");
}
function getFileNameByUri(uri) {
var arr = uri.split("/");
return arr[arr.length - 1];
}
function parse(params){
return Object.keys(params).map(function (key){
return `${key}=${params[key]}`;
}).join("&");
}
exports.handler = function(event, context, callback) {
// 预览引擎的访问地址
const previewUrl = 'https://preview.imm.aliyun.com/index.html';
// 转换结果存放路径
const previewTgtPath = 'fc-demo-preview-js';
var immProject = "imm-demo";
var immSrcUri = "oss://fc-imm-demo/test-data/office/test.pptx";
// 获取AK信息
var akInfo = {
accessKeyId: context.credentials.accessKeyId,
accessKeySecret: context.credentials.accessKeySecret,
securityToken: context.credentials.securityToken
};
var bucket = getBucketByUri(immSrcUri)
var client = new RPCClient({
endpoint: `https://imm.${context.region}.aliyuncs.com`,
accessKeyId: akInfo.accessKeyId,
accessKeySecret: akInfo.accessKeySecret,
securityToken: akInfo.securityToken,
apiVersion: '2017-09-06'
});
var fileName = getFileNameByUri(immSrcUri);
// https://help.aliyun.com/document_detail/63761.html
// https://help.aliyun.com/document_detail/74947.html
// 使用前端渲染引擎预览,这里的type必须是vector
var immParams = {
Project: immProject,
SrcUri: immSrcUri,
TgtType: "vector",
TgtUri: `oss://${bucket}/${previewTgtPath}/${fileName}`
};
var ossRegion = `oss-${context.region}`
//get vector preview url
var params = {};
// 预览文档地址
params.url = `http://${bucket}.${ossRegion}.aliyuncs.com/${previewTgtPath}/${fileName}`;
params.accessKeyId = akInfo.accessKeyId;
// 访问预览文档的accessKeySecret
params.accessKeySecret = akInfo.accessKeySecret;
// 访问预览文档的SecurityToken
params.stsToken = encodeURIComponent(akInfo.securityToken);
// 预览文档的region
params.region = ossRegion;
// 预览文档的bucket
params.bucket = bucket;
// 拼接预览URL
var url = `${previewUrl}?${parse(params)}`;
client.request("createOfficeConversionTask", immParams).then(function() {
console.log(url);
callback(null, url);
});
};
php 版本
<?php
require_once __DIR__ . '/aliyun-openapi-php-sdk/aliyun-php-sdk-core/Config.php';
use imm\Request\V20170906 as Imm;
define('PREVIEWURL', 'https://preview.imm.aliyun.com/index.html');
function myErrorHandler($errno, $errstr, $errfile, $errline) {
if (!(error_reporting() & $errno)) {
return false;
}
switch ($errno) {
case E_USER_ERROR:
$errInfo = array(
"errorMessage" => $errstr,
"errorType" => \ServerlessFC\friendly_error_type($errno),
"stackTrace" => array(
"file" => $errfile,
"line" => $errline,
),
);
throw new \ErrorException($errstr, 0, $errno, $errfile, $errline);
break;
default: // E_USER_WARNING | E_USER_NOTICE
break;
}
/* Don't execute PHP internal error handler */
return true;
}
// set to the user defined error handler
set_error_handler("myErrorHandler");
function startsWith($haystack, $needle) {
$length = strlen($needle);
return (substr($haystack, 0, $length) === $needle);
}
function getBucketByUri($uri) {
if (startsWith($uri, "oss://")) {
$tmp = substr($uri, 6);
return explode("/", $tmp)[0];
}
throw new Exception("SrcUri is invalid");
}
function getFileNameByUri($uri) {
$tmp = explode("/", $uri);
return $tmp[count($tmp) - 1];
}
function handler($event, $context) {
$accessKeyId = $context["credentials"]["accessKeyId"];
$accessKeySecret = $context["credentials"]["accessKeySecret"];
$securityToken = $context["credentials"]["securityToken"];
$region = $context['region'];
$iClientProfile = DefaultProfile::getProfile(
$region,
$accessKeyId,
$accessKeySecret,
$securityToken
);
$client = new DefaultAcsClient($iClientProfile);
$PREVIEWTGTPATH = 'fc-demo-preview-output-php';
$immProject = "imm-demo";
$srcUri = "oss://fc-imm-demo/test-data/office/test.pptx";
$bucket = getBucketByUri($srcUri);
$fileName = getFileNameByUri($srcUri);
$tgtUri = sprintf("oss://%s/%s/%s", $bucket, $PREVIEWTGTPATH, $fileName);
$request = new Imm\CreateOfficeConversionTaskRequest();
$request->setProject($immProject);
$request->setSrcUri($srcUri);
$request->setTgtType("vector");
$request->setTgtUri($tgtUri);
$response = $client->getAcsResponse($request);
print_r($response);
$maxRetryCount = 30;
$retryDelay = 1;
$request = new Imm\GetOfficeConversionTaskRequest();
$request->setTaskId($response->TaskId);
$request->setProject($immProject);
while ($maxRetryCount--) {
$response = $client->getAcsResponse($request);
print_r($response);
if ($response->Status != 'Running') {
break;
}
sleep($retryDelay);
}
// get preview url
$params = array();
$params["url"] = "http://" . $region . ".aliyuncs.com/" . $PREVIEWTGTPATH . "/" . $fileName;
$params["accessKeyId"] = $accessKeyId;
$params["accessKeySecret"] = $accessKeySecret;
$params["stsToken"] = rawurlencode($securityToken);
$params["region"] = "oss-" . $region;
$params["bucket"] = $bucket;
$paramsLi = [];
foreach ($params as $key => $value) {
$paramsLi[] = $key . "=" . $value;
}
$paramsStr = implode("&", $paramsLi);
$preview = PREVIEWURL . "?" . $paramsStr;
echo $preview . PHP_EOL;
return $preview;
}
5. 点击执行函数,函数的返回值为预览的url,用浏览器打开生成的url,就可以预览转换格式后的ppt了
总结
从本文的示例中,我们可以看出,基于oss存储,使用函数计算和智能媒体管理,短短的几行代码就可以快速实现一个弹性高可用的文档转换/预览服务,在本文中,演示的函数的触发是通过手动触发,函数计算集成丰富的事件源,通过事件源可以触发函数,比如对于上传到oss的某个文档,自动触发文档格式转换, 结合 http trigger (比如上面的体验的入口地址) 来设置 event 来达到对主动对某个文档格式转换并直接预览,从而实现实现一个弹性高可用的文档转换/预览服务,类似函数计算实现Serverless图片处理服务