天天看點

十分鐘上線-FC&IMM建構serverless文檔轉換/預覽服務

前言

自從人類進入資訊時代以來,辦公文檔是每個人日常頻繁使用的工具,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

十分鐘上線-FC&IMM建構serverless文檔轉換/預覽服務

3. 登入 對象存儲控制台 , 建立一個bucket,假設為

fc-imm-demo

  • 由于要浏覽器進行預覽,這裡設定跨域通路配置

    *.imm.aliyun.com

    十分鐘上線-FC&IMM建構serverless文檔轉換/預覽服務
  • 建立一個測試目錄,上傳測試ppt檔案測試檔案到指定目錄
    十分鐘上線-FC&IMM建構serverless文檔轉換/預覽服務

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
十分鐘上線-FC&IMM建構serverless文檔轉換/預覽服務
  • 編寫函數, 函數計算 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了

十分鐘上線-FC&amp;IMM建構serverless文檔轉換/預覽服務

總結

從本文的示例中,我們可以看出,基于oss存儲,使用函數計算和智能媒體管理,短短的幾行代碼就可以快速實作一個彈性高可用的文檔轉換/預覽服務,在本文中,示範的函數的觸發是通過手動觸發,函數計算內建豐富的事件源,通過事件源可以觸發函數,比如對于上傳到oss的某個文檔,自動觸發文檔格式轉換, 結合 http trigger (比如上面的體驗的入口位址) 來設定 event 來達到對主動對某個文檔格式轉換并直接預覽,進而實作實作一個彈性高可用的文檔轉換/預覽服務,類似

函數計算實作Serverless圖檔處理服務