跳转到主要内容
本节介绍如何将适用于 Windows 的 FRE 12 应用程序部署到 Azure Cloud Service。下面以一个 Worker 为例,说明如何处理 Azure 存储帐户中容器内的文件。 要实现此场景,需要使用连接到 *.abbyy.com 许可证服务器的 Online License
ABBYY 许可服务一次只能使用一个 Online License。
将应用程序部署到 Cloud Service 包括以下几个步骤:
  1. 根据前提条件配置 Online License、本地计算机和云实例
  2. 在部署应用程序之前执行准备步骤
  3. 将应用程序部署到 Cloud Service
有关如何使用 ABBYY FineReader Engine 方法进行文档处理,请参阅代码示例

先决条件

Online License

要使用 Online License,您应从销售部门获取以下信息:
  • 您的 Customer Project ID
  • 一个 Online License 令牌文件
  • 该许可证令牌文件的密码
在任何已安装 ABBYY 许可服务 的环境中使用 Online License,都需要满足以下条件:
  • 可用的 Internet 连接
  • 允许通过 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 (扩展预览版) 的模板解决方案,包含单个 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 文件夹中)
  • 为使用 Blob 容器而重写的 IFileWriter 接口 (请参阅下面的示例)
  • 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 存储帐户的 Azure SDK

准备步骤

这些准备步骤需要在本机上完成。完成后,您将备齐开始部署应用程序所需的全部设置和文件:
  1. 创建两个压缩包,其中分别包含 ABBYY FineReader Engine Library 和许可服务 (例如,LibraryPackage.zip 和 LSPackage. zip) 。您可以借助 FREngineDistribution.csv 文件自动生成文件列表。请确保 ABBYY FineReader Engine 和许可证服务器来自同一软件包;否则无法保证兼容性。
  2. 创建 Azure 存储帐户 (本文中使用 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 的参数。
  2. 指定 .csdef 文件中的参数:
  • (可选) 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,以便后续在示例中使用。
rem   Script to clean up previous ABBYY FineReader Engine and Licensing Service packages
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('\\');
# 如果许可服务进程正在运行,则将其停止
$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 和许可服务包
 
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 和许可服务包的容器
$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   用于准备许可服务包并启动 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 和许可服务包的容器
$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'; 
# 下载许可服务包
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;
# 以 standalone 模式启动许可服务
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;
 
            // 有关如何处理配置更改的信息
            // 请参阅 MSDN 主题:https://go.microsoft.com/fwlink/?LinkId=166357
 
            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>";
    }
    // 返回引擎路径
    public static String GetEngineFolder()
    {
        string engineSubfolder = "fre_packages\\LibraryPackage\\Bin64";
        string engineDllFolder = Path.Combine(RoleEnvironment.GetLocalResource(LocalStorageName).RootPath, engineSubfolder);
        return engineDllFolder;
    }
}