跳轉到主要內容
本節說明如何將適用於 Windows 的 FRE 12 應用程式部署至 Azure Cloud Service。以下以一個 Worker 為例,示範其如何處理由 Azure Storage 帳戶中容器提供的檔案。 若要實作此案例,會使用連線至 *.abbyy.com 授權伺服器的 線上授權
ABBYY Licensing Service 同一時間只能搭配一個線上授權運作。
將您的應用程式部署到 Cloud Service 包含數個步驟:
  1. 依照先決條件設定您的線上授權、本機與雲端執行個體
  2. 在部署應用程式之前,執行準備步驟
  3. 將您的應用程式部署到 Cloud Service
請參閱程式碼範例,其中示範如何使用 ABBYY FineReader Engine 的方法來處理文件。

先決條件

線上授權

若要使用線上授權,您應向業務團隊取得以下資訊: 若要在安裝 ABBYY Licensing Service 的任何位置使用線上授權,您都必須符合下列條件:
  • 可用的網際網路連線
  • 允許透過連接埠 443 (HTTPS) 連線至 *.abbyy.com
  • 憑證授權單位的 GoDaddy 根憑證 (必須安裝在本機的「受信任的根憑證授權單位」憑證存放區中。憑證詳細資訊請參閱 GoDaddy 網站。)

本機

在建立您的 Cloud Service 之前,請先依照下列規格準備本機環境:
  • Visual Studio 2019 及其用於開發 Azure 應用程式的模組 (請查看 Visual Studio 中的 Azure 功能 或使用 Visual Studio Installer 下載這些模組)
  • 便於偵錯的 Cloud Service 模擬器與儲存體 (請參閱此處的資訊,並透過 Visual Studio Installer 下載)
  • Azure Cloud Service (extended preview) 的範本解決方案,內含單一 Worker 角色專案 (請參閱此處的資訊)
  • .NET Framework 4.7.2
  • Azure SDK (可在此處下載)
  • 用於 Azure Storage 和 Blob 容器的 NuGet 套件 - Azure.Storage.Blobs (可在此處下載)
  • 適用於 .NET Framework 4.7 的 ABBYY FineReader Engine 包裝程式 (開發人員安裝後位於 C:\ProgramData\ABBYY\SDK\12\FineReader Engine\Inc.NET interops 資料夾中)
  • 已覆寫的 IFileWriter 介面,以便用於 Blob 容器 (請參閱下方的範例)
  • Azure Storage Explorer (選用 - 可在此處下載)

Cloud Service 執行個體

Cloud Service 執行個體用於在 Azure 中存放您的 WorkerRole 專案。請依照下列規格設定此執行個體:
  • 您的 Service 所使用的 .NET Framework 版本
  • 用於部署 ABBYY FineReader Engine 的 PowerShell
  • 用於上傳 Azure SDK 的 NuGet 2.8.5.201 或更高版本
  • 可透過 PowerShell 搭配 Azure Storage 帳戶使用的 Azure SDK

準備步驟

以下準備步驟需在您的本機電腦上完成。完成這些步驟後,您將備妥開始部署應用程式所需的所有設定和檔案:
  1. 建立兩個壓縮檔,分別包含 ABBYY FineReader Engine Library 和 Licensing Service (例如 LibraryPackage.zip 和 LSPackage.zip) 。您可以藉助 FREngineDistribution.csv 檔案自動建立檔案清單。請使用來自同一套件的 ABBYY FineReader Engine 和 License Server;否則無法保證相容性。
  2. 建立 Azure Storage 帳戶 (本文中使用 frestorage) 。您可以在 Azure 網站上找到所有必要的說明。
  3. 在 frestorage 中建立三個 Blob 容器:
  • fre-lib - 用於存放 ABBYY FineReader Engine 檔案
  • fre-input - 用於存放輸入檔案
  • fre-output - 用於存放處理結果
  1. 以最方便的方式將 LibraryPackage.zip 和 LSPackage.zip 上傳至 fre-lib 容器 (使用 .NET、Powershell、Python 指令碼或 Azure Storage Explorer/Azure Portal 應用程式) 。
以下以 WorkerRole 專案為例,說明如何在已設定完成的環境中作業。建立專案後,所有必要的組態檔 (.csdef 和 Cloud.cscfg) 都會自動產生。

將 ABBYY FineReader Engine 部署至 Cloud Service

若要將 ABBYY FineReader Engine 部署至新的 WorkerRole 專案:
  1. 依需求指定 Cloud.cscfg 的 parameters。
  2. 指定 .csdef 檔案的 parameters:
  • (選用) 您的 WorkerRole 設定
  • (選用) 虛擬機器大小
  • (必要) 角色的本機儲存體 (LocalStorage 區段,在本文中為名為 LocalStorage1 的儲存體) 。請至少設為 3 GB,以確保 ABBYY FineReader Engine 套件能正確部署。
  • (必要) 角色的啟動順序 (使用程式碼範例一節中的程式碼範例和設定)
  1. 實作您的 WorkerRole 專案,以處理來自 fre-input 容器的檔案,並將處理結果發佈至 fre-output 容器:
  • 實作 OnStart 方法,讓 Cloud Service 做好執行準備。此方法用於初始化 Engine 並設定 TLS 通訊協定。
  • 實作 RunAsync 方法,循環處理從 fre-input 容器取得的檔案。此方法會偵測 fre-input 容器中的檔案、在記憶體中處理這些檔案,然後將其放入 fre-output 容器 (請參閱 Processor.csIFileWriter.csConfig.cs) 。
  • 實作 OnStop 方法,以完成 Cloud Service 的作業。此方法用於解除初始化 Engine。

程式碼範例

本節包含用於設定角色啟動順序及 ABBYY FineReader Engine 設定的程式碼範例: 啟動您的應用程式之前,必須嚴格依照建議順序執行上述指令碼。若要自訂指令碼的執行順序,請指定下列屬性:
  • taskType=“simple” - 工作會同步執行,一次執行一個。
  • executionContext=“elevated” - 以系統管理員權限執行啟動指令碼 (安裝任何應用程式及執行 LicensingService.exe 時為必要)
因此,系統會建立一個可由 Cloud Service 管理的資料夾。上述指令碼會使用此資料夾來上傳 ABBYY FineReader Engine,並載入 FREngine.dll,以便後續在 sample 中實作。
rem   用於清除先前 ABBYY FineReader Engine 和 Licensing Service 套件的指令碼
echo Cleaning up before launching service >> ".\CleanUpOnStart.log" 2>&1
PowerShell -ExecutionPolicy Unrestricted .\StartupScripts\CleanUpOnStart.ps1 >> ".\CleanUpOnStart.log" 2>&1
echo Cleaning up beafore launching service. >> ".\CleanUpOnStart.log" 2>&1
 
exit /B %errorlevel%
Write-Host "CleanUpOnStart.ps1 started...";
# 本機儲存體路徑(請參閱 ServiceDefinition.csdef)
$local_storage_name = "LocalStorage1";
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.WindowsAzure.ServiceRuntime");
$local_storage_path = ([Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironment]::GetLocalResource($local_storage_name)).RootPath.TrimEnd('\\');
# 如果 Licensing Service 處理程序正在執行,則將其停止
$processes = Get-Process;
foreach( $process in $processes ) {
    if( $process.Name -eq "LicesingService" ) {
        Stop-Process $process;
    }
}
 
# 清理並重新建立 fre_packages 資料夾
$destination_path = Join-Path -Path $local_storage_path -ChildPath 'fre_packages';
if( Test-Path -Path $destination_path ) {
    Remove-Item $destination_path -Recurse -Force;
}
New-Item -ItemType Directory -Path $destination_path;
# 清理並重新建立 Input 資料夾
$destination_path = Join-Path -Path $local_storage_path -ChildPath 'Input';
if( Test-Path -Path $destination_path ) {
    Remove-Item $destination_path -Recurse -Force;
}
New-Item -ItemType Directory -Path $destination_path;
# 清理並重新建立 Results 資料夾
$destination_path = Join-Path -Path $local_storage_path -ChildPath 'Results';
if( Test-Path -Path $destination_path ) {
    Remove-Item $destination_path -Recurse -Force;
}
New-Item -ItemType Directory -Path $destination_path;
Write-Host ("Resource folder was cleaned up.");
rem   用於準備模組的指令碼,以便連線到儲存體帳戶並下載 ABBYY FineReader Engine 和 Licensing Service 套件
 
echo Preparing PoSh Modules >> ".\PreparePoShModules.log" 2>&1
PowerShell -ExecutionPolicy Unrestricted .\StartupScripts\PreparePoShModules.ps1 >> ".\PreparePoShModules.log" 2>&1
echo PoSh Modules wer prepared. >> ".\PreparePoShModules.log" 2>&1
 
exit /B %errorlevel%
# 安裝模組
Write-Host "PreparePoShModules.ps1 started...";
# 安裝用於下載 Azure 模組的 NuGet 提供者
Write-Host "Installing NuGet package provider...";
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force;
 
# 安裝用於操作 Blob 儲體的 Azure Storage 模組
Write-Host "Installing Az.Storage module...";
Install-Module -Name Az.Storage -Scope CurrentUser -Repository PSGallery -Force -AllowClobber;
Write-Host ("PoSh modules were prepared.");
rem   用於準備 ABBYY FineReader Engine 套件的指令碼
echo Preparing Library >> ".\PrepareLibrary.log" 2>&1
PowerShell -ExecutionPolicy Unrestricted .\StartupScripts\PrepareLibrary.ps1 >> ".\PrepareLibrary.log" 2>&1
echo Library was prepared. >> ".\PrepareLibrary.log" 2>&1
exit /B %errorlevel%
Write-Host "PrepareLibrary.ps1 started...";
# 包含 ABBYY FineReader Engine 和 Licensing Service 套件的容器
$container_name = 'fre-lib';
# 儲存體帳戶連線字串 
$connection_string = "<connection_string_to_frestorage>";
Write-Host "Configuration";
Write-Host ("Container name: " + $container_name);
Write-Host ("Connection string: " + $storage_account);
# 連線至儲存體物件
Write-Host ("Connecting to storage account...");
$storage_account = New-AzStorageContext -ConnectionString $connection_string;
# 取得 Blob(基本上就是檔案)
Write-Host ("Getting blobs from container...");
$fre_blobs = Get-AzStorageBlob -Container $container_name -Context $storage_account;
# 本機儲存體的路徑(請參閱 ServiceDefinition.csdef)
$local_storage_name = "LocalStorage1";
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.WindowsAzure.ServiceRuntime");
$local_storage_path = ([Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironment]::GetLocalResource($local_storage_name)).RootPath.TrimEnd('\\');
$destination_path = Join-Path -Path $local_storage_path -ChildPath 'fre_packages';
# 下載唯一需要的 Blob
Write-Host ("Downloading FRE package from container...");
foreach( $blob in $fre_blobs ) {
    Write-Host $blob.Name;
    if( $blob.Name -eq "LibraryPackage.zip" ) {
        Write-Host ("Downloading blob: " + $blob.Name + "to " + $destination_path);
        Get-AzStorageBlobContent -Container $container_name -Blob $blob.Name `
            -Destination (Join-Path -Path $destination_path -ChildPath $blob.Name) -Context $storage_account;
    } else {
        Write-Host ("Downloading blob " + $blob.Name + " was skipped.");
    }
}
# 解壓縮已下載的套件(將強制覆寫舊版程式庫)
Write-Host ("Unzipping library package...");
Expand-Archive -Path (Join-Path -Path $destination_path -ChildPath "LibraryPackage.zip") `
    -DestinationPath (Join-Path -Path $destination_path -ChildPath "LibraryPackage") -Force;
Write-Host ("Library package was prepared.");
rem   用來準備 Licensing Service 套件並啟動 LicensingService.exe 的指令碼
echo Preparing LS >> ".\PrepareLS.log" 2>&1
PowerShell -ExecutionPolicy Unrestricted .\StartupScripts\PrepareLS.ps1 >> ".\PrepareLS.log" 2>&1
echo LS was prepared. >> ".\PrepareLS.log" 2>&1
exit /B %errorlevel%
Write-Host "PrepareLS.ps1 started...";
# 包含 ABBYY FineReader Engine 和 Licensing Service 套件的容器
$container_name = 'fre-lib';
# 儲存體帳戶連線字串
$connection_string = "<connection_string_to_frestorage>";
Write-Host "Configuration";
Write-Host ("Container name: " + $container_name);
Write-Host ("Connection string: " + $storage_account);
# 連線到儲存體物件
Write-Host ("Connecting to storage account...");
$storage_account = New-AzStorageContext -ConnectionString $connection_string;
# 從容器中取得 blob(基本上就是檔案)
Write-Host ("Getting blobs from container...");
$fre_blobs = Get-AzStorageBlob -Container $container_name -Context $storage_account;
# 本機儲存體路徑(請參閱 ServiceDefinition.csdef)
$local_storage_name = "LocalStorage1";
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.WindowsAzure.ServiceRuntime");
$local_storage_path = ([Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironment]::GetLocalResource($local_storage_name)).RootPath.TrimEnd('\\');
$destination_path = Join-Path -Path $local_storage_path -ChildPath 'fre_packages'; 
# 下載 Licensing Service 套件
Write-Host ("Downloading Licensing Service package from container...");
foreach( $blob in $fre_blobs ) {
    Write-Host $blob.Name;
    if( $blob.Name -eq "LSPackage.zip" ) {
        Write-Host ("Downloading blob: " + $blob.Name + "to " + $destination_path);
        Get-AzStorageBlobContent -Container $container_name -Blob $blob.Name `
            -Destination (Join-Path -Path $destination_path -ChildPath $blob.Name) -Context $storage_account;
    } else {
        Write-Host ("Downloading blob " + $blob.Name + " was skipped.");
    }
}
# 解壓縮下載的套件
Write-Host ("Unzipping library package...");
Expand-Archive -Path (Join-Path -Path $destination_path -ChildPath "LSPackage.zip") `
    -DestinationPath (Join-Path -Path $destination_path -ChildPath "LSPackage") -Force;
# 建立授權資料夾
$program_data_path = [System.Environment]::ExpandEnvironmentVariables("%programdata%");
$path = Join-Path -Path $program_data_path -ChildPath "ABBYY\SDK\12\Licenses";
New-Item -ItemType Directory -Path $path -Force;
# 以獨立模式啟動 Licensing Service
Write-Host ("Starting Licensing Service...");
 
$licensing_service_app = Join-Path -Path $destination_path -ChildPath "LSPackage\LicensingService.exe";
Start-Process -FilePath $licesing_service_app -ArgumentList "/standalone";
Write-Host ("LS package was prepared and LS was started.");
namespace WorkerRole1.Engine
{
    public class FileWriter : FREngine.IFileWriter, IDisposable
    {
        public FileWriter(string _resultBlobName, string _fileExtension)
        {
            resultBlobName = _resultBlobName;
            fileExtension = _fileExtension;
        }
        public void Open(string fileName, ref int bufferSize)
        {
            stream = new MemoryStream();
        }
        public void Write(byte[] data)
        {
            stream.Write(data, 0, data.Length);
        }
        public void Close() 
        {
            // 在 <OutputContainerName> 中建立新 blob 的連線
            // 處理結果將儲存在該處
            BlobClient resultBlobClient = new BlobClient(Config.ConnectionString, Config.OutputContainerName,
                resultBlobName + fileExtension);
            // 覆寫現有檔案
            resultBlobClient.DeleteIfExists();
            // 將位置設為 0,以便從檔案開頭開始寫入
            stream.Position = 0;
            resultBlobClient.Upload(stream);
 
            stream.Close();
        }
        public void Dispose()
        {
            // 釋放時關閉記憶體串流,以便在資料寫入後可存取它
            stream.Close();
        }
        private string resultBlobName;
        private string fileExtension;
        private MemoryStream stream;
    }
}
namespace WorkerRole1
{
    public class WorkerRole : RoleEntryPoint
    {
        private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
        private readonly ManualResetEvent runCompleteEvent = new ManualResetEvent(false);
        Processor processor = new Processor();
 
        public override void Run()
        {
            Trace.TraceInformation("WorkerRole1 is running");
 
            try
            {
                this.RunAsync(this.cancellationTokenSource.Token).Wait();
            }
            finally
            {
                this.runCompleteEvent.Set();
            }
        }
        public override bool OnStart()
        {
            // 設定並行連線數上限
            ServicePointManager.DefaultConnectionLimit = 12;
 
            // 將 TLS 設為 1.2,以便連線到儲存體帳戶
            System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12;
 
            // 如需有關處理組態變更的資訊,
            // 請參閱 https://go.microsoft.com/fwlink/?LinkId=166357 上的 MSDN 主題
 
            bool result = base.OnStart();
            Trace.TraceInformation("WorkerRole1 has been started");
 
            processor.LoadEngine();
            return result;
        }
 
        public override void OnStop()
        {
            processor.UnloadEngine();
            Trace.TraceInformation("WorkerRole1 is stopping");
            this.cancellationTokenSource.Cancel();
            this.runCompleteEvent.WaitOne();
            base.OnStop();
            Trace.TraceInformation("WorkerRole1 has stopped");
        }
 
        private async Task RunAsync(CancellationToken cancellationToken)
        {
            // TODO: 請以您自己的邏輯取代下列程式碼
            while (!cancellationToken.IsCancellationRequested)
            {
                Trace.TraceInformation("Working");
 
                try
                {
                    // 建立容器,並傳回容器客戶端物件
                    BlobContainerClient inputContainerClient = new BlobContainerClient(Config.ConnectionString, Config.InputContainerName);
                    foreach (BlobItem blobItem in inputContainerClient.GetBlobs())
                    {
                        Trace.TraceInformation("\t" + blobItem.Name);
                        BlobClient blobClient = new BlobClient(Config.ConnectionString, Config.InputContainerName, blobItem.Name);
 
                        Trace.TraceInformation("\t Downloading blob to memory: " + blobItem.Name);
                        MemoryStream memoryStream = new MemoryStream();
                        blobClient.DownloadTo(memoryStream);
                        Trace.TraceInformation("\t Processing in FRE: " + blobItem.Name);
                        string resultBlobName = processor.ProcessImage(memoryStream, blobItem.Name);
                        Trace.TraceInformation("\t Result blob name in output container: " + resultBlobName);
                        Trace.TraceInformation("\t Deleting from input container: " + blobItem.Name);
                        blobClient.Delete();
                    }
                }
                catch (Exception error)
                {
                    Trace.TraceInformation("error: " + error.Message);
                }
 
                await Task.Delay(1000);
            }
        }
    }
}
namespace WorkerRole1
{
    class Processor : IDisposable
    {
        EngineLoader engineLoader = null;
        private void displayMessage(string text)
        {
            File.AppendAllText(".\\FRE.log", text + "\n");
            Trace.TraceInformation("\t" + text );
        }        
        private void loadProfile()
        {
            engineLoader.Engine.LoadPredefinedProfile("DocumentConversion_Accuracy");
        }        
        private void setupFREngine()
        {
            displayMessage("Loading predefined profile...");
            loadProfile();
        }
        public void LoadEngine()
        {
            try {
                if (engineLoader == null)
                {
                    // 與 Hello 範例中使用的是相同的 EngineLoader
                    engineLoader = new EngineLoader();
                }
                setupFREngine();
            }
            catch (Exception error)
            {
                displayMessage("error: " + error.Message);
            }
        }
        public void UnloadEngine()
        {
            try
            {
                if (engineLoader != null)
                {
                    engineLoader.Dispose();
                    engineLoader = null;
                }
            }
            catch (Exception error)
            {
                displayMessage("error: " + error.Message);
            }
        }
 
        public string ProcessImage( MemoryStream inputMemoryStream, string inputBlobName )
        {
            FRDocument document = engineLoader.Engine.CreateFRDocument();
            string resultBlobName = "";
            try
            {
               document.PageFlushingPolicy = FREngine.PageFlushingPolicyEnum.PFP_KeepInMemory;
 
                // 將影像檔加入文件
                displayMessage("Loading image...");
                IntPtr handle = Marshal.AllocHGlobal(inputMemoryStream.GetBuffer().Length);
                Marshal.Copy(inputMemoryStream.GetBuffer(), 0, handle, inputMemoryStream.GetBuffer().Length);
                document.AddImageFileFromMemory(handle.ToInt64(), null, null);
 
                // 辨識文件內容
                displayMessage("Recognizing...");
                document.Process(null);
 
                // 儲存結果
                displayMessage("Saving results...");
                // 使用 'balanced' 情境將結果儲存為 pdf
                // FREngine.PDFExportParams pdfParams = engineLoader.Engine.CreatePDFExportParams();
                // pdfParams.Scenario = FREngine.PDFExportScenarioEnum.PES_Balanced;
 
                WorkerRole1.Engine.FileWriter fileWriter = new WorkerRole1.Engine.FileWriter(inputBlobName, ".pdf");
                resultBlobName = inputBlobName + ".pdf";
                document.ExportToMemory(fileWriter, FREngine.FileExportFormatEnum.FEF_PDF, null);
 
            }
            catch (Exception error)
            {
                displayMessage("error: " + error.Message);
            }
            finally
            {
                // 關閉文件
                document.Close();
            }
 
            return resultBlobName;
        }
 
        public void Dispose()
        {
            UnloadEngine();
        }
    }
}
class Config
{
    // ServiceDefinition.csdef 中定義的本機儲存體
    private static string LocalStorageName = "LocalStorage1";
    // Blob 容器的連線字串
    public static readonly string ConnectionString = "<connection_string_to_frestorage>";
    // 儲存體中輸入與輸出容器的名稱
    public static readonly string InputContainerName = "fre-input";
    public static readonly string OutputContainerName = "fre-output";
 
    // 傳回 ABBYY FineReader Engine 的 Customer Project ID
    public static String GetCustomerProjectId()
    {
        return "<Your_Customer_Project_ID>";
    }
 
    // 傳回授權權杖名稱
    public static String GetLicenseTokenName()
    {
        return "<Token_number>.ABBYY.ActivationToken";
    }
 
    // 傳回授權密碼
    public static String GetLicensePassword()
    {
        return "<Your_Online_License_token_password>";
    }
    // 傳回 Engine 資料夾路徑
    public static String GetEngineFolder()
    {
        string engineSubfolder = "fre_packages\\LibraryPackage\\Bin64";
        string engineDllFolder = Path.Combine(RoleEnvironment.GetLocalResource(LocalStorageName).RootPath, engineSubfolder);
        return engineDllFolder;
    }
}