!3771 feat(#I6AY0F): add baidu ocr component

* doc: 更新使用方法
* doc: 增加源码链接配置
* doc: 增加 IBarduOcr 菜单
* chore: 移除重复菜单
* doc: 增加增值税票据识别组件菜单
* Merge branch 'main' into feat/Baidu.Orc
* doc: 更新示例
* refactor: 更改 SaveToFile 方法为异步方法
* refactor: SaveToFile 更改为扩展方法
* refactor: GetBytes 更改为扩展方法
* refactor: RequestBase64ImageFileAsync 更改为扩展方法
* doc: 增加格式化
* doc: 增加发票明细列表
* doc: 更新 Tips 文档
* doc: 不显示已上传列表
* chore: 更新项目依赖项
* doc: 格式化文档
* doc: 增加 BaiduOcr 示例
* chore: 增加 Logo
* refactor: 更新 UploadFile
* doc: 更新示例
* feat: 增加增值税识别方法
* feat: 增加增值税实体类
* doc: 增加反序列化示例
* doc: 增加 BaiduOrc 示例
* feat: 更新 IBaiduOrc 接口
* chore: 增加 BaiduOrc 识别服务
* feat: 增加百度增值税发票识别接口
* chore: 增加百度文字识别配置项
* doc: 增加注释
* Merge branch 'main' into feat/Baidu.Orc
* chore: 增加 BaiduOcr 工程
* refactor: 精简代码消除警告信息
* feat: 增加文字识别服务
This commit is contained in:
Argo 2023-01-18 06:04:15 +00:00
parent 35726b0a38
commit 293bbf2c23
26 changed files with 564 additions and 152 deletions

View File

@ -140,6 +140,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "localization", "localizatio
localization\zh-TW.json = localization\zh-TW.json localization\zh-TW.json = localization\zh-TW.json
EndProjectSection EndProjectSection
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.BaiduOcr", "src\Extensions\Components\BootstrapBlazor.BaiduOcr\BootstrapBlazor.BaiduOcr.csproj", "{F800DC0F-6829-4B64-B96B-B301A512AF2A}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -234,6 +236,10 @@ Global
{7F7F3B85-7B15-485C-8DC2-BAFD8C086957}.Debug|Any CPU.Build.0 = Debug|Any CPU {7F7F3B85-7B15-485C-8DC2-BAFD8C086957}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7F7F3B85-7B15-485C-8DC2-BAFD8C086957}.Release|Any CPU.ActiveCfg = Release|Any CPU {7F7F3B85-7B15-485C-8DC2-BAFD8C086957}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7F7F3B85-7B15-485C-8DC2-BAFD8C086957}.Release|Any CPU.Build.0 = Release|Any CPU {7F7F3B85-7B15-485C-8DC2-BAFD8C086957}.Release|Any CPU.Build.0 = Release|Any CPU
{F800DC0F-6829-4B64-B96B-B301A512AF2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F800DC0F-6829-4B64-B96B-B301A512AF2A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F800DC0F-6829-4B64-B96B-B301A512AF2A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F800DC0F-6829-4B64-B96B-B301A512AF2A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -271,6 +277,7 @@ Global
{9552B649-17E2-4BCA-8774-664C83A960CB} = {7C1D79F1-87BC-42C1-BD5A-CDE4044AC1BD} {9552B649-17E2-4BCA-8774-664C83A960CB} = {7C1D79F1-87BC-42C1-BD5A-CDE4044AC1BD}
{6312863E-771D-4EFE-9B9D-071A01222E7A} = {CD062AB6-244D-402A-8F33-C37DAC5856CC} {6312863E-771D-4EFE-9B9D-071A01222E7A} = {CD062AB6-244D-402A-8F33-C37DAC5856CC}
{7F7F3B85-7B15-485C-8DC2-BAFD8C086957} = {CD062AB6-244D-402A-8F33-C37DAC5856CC} {7F7F3B85-7B15-485C-8DC2-BAFD8C086957} = {CD062AB6-244D-402A-8F33-C37DAC5856CC}
{F800DC0F-6829-4B64-B96B-B301A512AF2A} = {CD062AB6-244D-402A-8F33-C37DAC5856CC}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0DCB0756-34FA-4FD0-AE1D-D3F08B5B3A6B} SolutionGuid = {0DCB0756-34FA-4FD0-AE1D-D3F08B5B3A6B}

View File

@ -55,6 +55,9 @@ internal static class ServicesExtensions
// 设置地理位置定位器 // 设置地理位置定位器
services.ConfigureIPLocatorOption(op => op.LocatorFactory = sp => new BaiDuIPLocator()); services.ConfigureIPLocatorOption(op => op.LocatorFactory = sp => new BaiDuIPLocator());
// 增加 Baidu ORC 服务
services.AddBootstrapBlazorBaiduOcr();
// 增加多语言支持配置信息 // 增加多语言支持配置信息
services.AddRequestLocalization<IOptionsMonitor<BootstrapBlazorOptions>>((localizerOption, blazorOption) => services.AddRequestLocalization<IOptionsMonitor<BootstrapBlazorOptions>>((localizerOption, blazorOption) =>
{ {

View File

@ -63,5 +63,10 @@
"AppId": "<AppId>", "AppId": "<AppId>",
"ApiKey": "<ApiKey>", "ApiKey": "<ApiKey>",
"Secret": "<Secret>" "Secret": "<Secret>"
},
"BaiduOcrOption": {
"AppId": "<AppId>",
"ApiKey": "<ApiKey>",
"Secret": "<Secret>"
} }
} }

View File

@ -19,10 +19,12 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="BootstrapBlazor.BaiduSpeech" Version="7.*" /> <PackageReference Include="BootstrapBlazor.BaiduSpeech" Version="7.*" />
<PackageReference Include="BootstrapBlazor.BaiduOcr" Version="7.*" />
<PackageReference Include="BootstrapBlazor.BarCode" Version="7.*" /> <PackageReference Include="BootstrapBlazor.BarCode" Version="7.*" />
<PackageReference Include="BootstrapBlazor.Bluetooth" Version="7.*" /> <PackageReference Include="BootstrapBlazor.Bluetooth" Version="7.*" />
<PackageReference Include="BootstrapBlazor.Chart" Version="7.*" /> <PackageReference Include="BootstrapBlazor.Chart" Version="7.*" />
<PackageReference Include="BootstrapBlazor.CherryMarkdown" Version="7.*" /> <PackageReference Include="BootstrapBlazor.CherryMarkdown" Version="7.*" />
<PackageReference Include="BootstrapBlazor.FileViewer" Version="7.*" />
<PackageReference Include="BootstrapBlazor.FontAwesome" Version="7.*" /> <PackageReference Include="BootstrapBlazor.FontAwesome" Version="7.*" />
<PackageReference Include="BootstrapBlazor.Markdown" Version="7.*" /> <PackageReference Include="BootstrapBlazor.Markdown" Version="7.*" />
<PackageReference Include="BootstrapBlazor.OnScreenKeyboard" Version="7.*" /> <PackageReference Include="BootstrapBlazor.OnScreenKeyboard" Version="7.*" />
@ -32,7 +34,6 @@
<PackageReference Include="BootstrapBlazor.TableExport" Version="7.*" /> <PackageReference Include="BootstrapBlazor.TableExport" Version="7.*" />
<PackageReference Include="BootstrapBlazor.Topology" Version="7.*" /> <PackageReference Include="BootstrapBlazor.Topology" Version="7.*" />
<PackageReference Include="BootstrapBlazor.VideoPlayer" Version="7.*" /> <PackageReference Include="BootstrapBlazor.VideoPlayer" Version="7.*" />
<PackageReference Include="BootstrapBlazor.FileViewer" Version="7.*" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -0,0 +1,100 @@
@inject IBaiduOcr OcrService
<ButtonUpload TValue="string" OnChange="@OnClickToUpload" BrowserButtonText="VATInvoice" ShowUploadFileList="false"></ButtonUpload>
@if (Invoice != null)
{
<div class="row form-inline g-3 mt-0">
<div class="col-12 col-sm-6">
<Display ShowLabel="true" DisplayText="发票代码" @bind-Value="Invoice.InvoiceCode"></Display>
</div>
<div class="col-12 col-sm-6">
<Display ShowLabel="true" DisplayText="发票号码" Value="Invoice.InvoiceNum"></Display>
</div>
<div class="col-12 col-sm-6">
<Display ShowLabel="true" DisplayText="发票类型" @bind-Value="Invoice.ServiceType"></Display>
</div>
<div class="col-12 col-sm-6">
<Display ShowLabel="true" DisplayText="联次信息" Value="Invoice.SheetNum"></Display>
</div>
<div class="col-12 col-sm-6">
<Display ShowLabel="true" DisplayText="发票种类" @bind-Value="Invoice.InvoiceType"></Display>
</div>
<div class="col-12 col-sm-6">
<Display ShowLabel="true" DisplayText="发票名称" Value="Invoice.InvoiceTypeOrg"></Display>
</div>
<div class="col-12 col-sm-6">
<Display ShowLabel="true" DisplayText="发票标志" @bind-Value="Invoice.InvoiceTag"></Display>
</div>
<div class="col-12 col-sm-6">
<Display ShowLabel="true" DisplayText="开票日期" Value="Invoice.InvoiceDate"></Display>
</div>
<div class="col-12 col-sm-6">
<Display ShowLabel="true" DisplayText="购方名称" @bind-Value="Invoice.PurchaserName"></Display>
</div>
<div class="col-12 col-sm-6">
<Display ShowLabel="true" DisplayText="纳税人识别号" Value="Invoice.PurchaserRegisterNum"></Display>
</div>
<div class="col-12 col-sm-6">
<Display ShowLabel="true" DisplayText="开户行及账号" @bind-Value="Invoice.PurchaserBank"></Display>
</div>
<div class="col-12 col-sm-6">
<Display ShowLabel="true" DisplayText="购方地址" Value="Invoice.PurchaserAddress"></Display>
</div>
<div class="col-12 col-sm-6">
<Display ShowLabel="true" DisplayText="销售方名称" @bind-Value="Invoice.SellerName"></Display>
</div>
<div class="col-12 col-sm-6">
<Display ShowLabel="true" DisplayText="纳税人识别号" Value="Invoice.SellerRegisterNum"></Display>
</div>
<div class="col-12 col-sm-6">
<Display ShowLabel="true" DisplayText="销售方及账号" @bind-Value="Invoice.SellerBank"></Display>
</div>
<div class="col-12 col-sm-6">
<Display ShowLabel="true" DisplayText="销售方地址" Value="Invoice.SellerAddress"></Display>
</div>
<div class="col-12">
<Display ShowLabel="true" DisplayText="备注" @bind-Value="Invoice.Remarks"></Display>
</div>
<div class="col-12 col-sm-6">
<Display ShowLabel="true" DisplayText="价税合计" Value="Invoice.AmountInFiguers"></Display>
</div>
<div class="col-12 col-sm-6">
<Display ShowLabel="true" DisplayText="合计金额" Value="Invoice.TotalAmount"></Display>
</div>
<div class="col-12 col-sm-6">
<Display ShowLabel="true" DisplayText="合计税额" @bind-Value="Invoice.TotalTax"></Display>
</div>
<div class="col-12 col-sm-6">
</div>
@if (Invoice.CommodityName?.Any() ?? false)
{
<div class="col-12 col-sm-6">
<Display ShowLabel="true" DisplayText="税率" Value="Invoice.CommodityName.First().CommodityName"></Display>
</div>
}
@if (Invoice.CommodityTaxRate?.Any() ?? false)
{
<div class="col-12 col-sm-6">
<Display ShowLabel="true" DisplayText="税率" Value="Invoice.CommodityTaxRate.First().CommodityTaxRate"></Display>
</div>
}
</div>
}
@code {
private InvoiceEntity? Invoice { get; set; }
private async Task OnClickToUpload(UploadFile file)
{
if (file.File != null)
{
var payload = await file.GetBytesAsync(file.File.Size);
if (payload?.Any() ?? false)
{
Invoice = await OcrService.CheckVatInvoiceAsync(payload);
StateHasChanged();
}
}
}
}

View File

@ -0,0 +1,14 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/
using System;
namespace BootstrapBlazor.Shared.Demos.BaiduOcr;
/// <summary>
/// 增值税发票识别示例
/// </summary>
public partial class BaiduOcrNormal
{
}

View File

@ -532,11 +532,6 @@ internal static class MenusLocalizerExtensions
Url = "qrcodes" Url = "qrcodes"
}, },
new() new()
{
Text = Localizer["Recognizer"],
Url = "recognizers"
},
new()
{ {
Text = Localizer["Search"], Text = Localizer["Search"],
Url = "searchs" Url = "searchs"
@ -594,6 +589,12 @@ internal static class MenusLocalizerExtensions
Url = "transitions" Url = "transitions"
}, },
new() new()
{
IsNew = true,
Text = Localizer["BaiduOcr"],
Url = "ocr"
},
new()
{ {
IsNew = true, IsNew = true,
Text = Localizer["VideoPlayer"], Text = Localizer["VideoPlayer"],

View File

@ -559,6 +559,16 @@
"NoRenderIntro": "By setting the <code>Editable=false</code> of the <code>IEditorItem</code> instance, the editable pop-up window cannot be edited.", "NoRenderIntro": "By setting the <code>Editable=false</code> of the <code>IEditorItem</code> instance, the editable pop-up window cannot be edited.",
"EditDialogOption": "EditDialogOption property" "EditDialogOption": "EditDialogOption property"
}, },
"BootstrapBlazor.Shared.Samples.BaiduOcr": {
"Title": "IBaiduOcr",
"SubTitle": "Character recognition",
"BasicUsageTitle": "VAT Invoice",
"BasicUsageIntro": "Call Baidu Ocr interface for text recognition by uploading VAT invoice image",
"BaiduOcrDesc": "本组件通过调用 Baidu AI 平台文字识别接口进行增值税发票文字识别。支持对增值税普票、专票、全电发票(新版全国统一电子发票,专票/普票)、卷票、区块链发票的所有字段进行结构化识别,包括发票基本信息、销售方及购买方信息、商品信息、价税信息等,其中五要素字段的识别准确率超过 99.9% 同时,支持对增值税卷票的 21 个关键字段进行识别,包括发票类型、发票代码、发票号码、机打号码、机器编号、收款人、销售方名称、销售方纳税人识别号、开票日期、购买方名称、购买方纳税人识别号、项目、单价、数量、金额、税额、合计金额(小写)、合计金额(大写)、校验码、省、市四要素字段的识别准确率可达95%。上传图片不能超过 4M",
"BaiduOcrIntro": "Usage",
"BaiduOcrStep1": "1. Get <code>IBaiduOcr</code> instance by inject service",
"BaiduOcrStep2": "2. Call <code>IBaiduOcr</code> instance method"
},
"BootstrapBlazor.Shared.Demos.EditDialog.EditDialogNormal": { "BootstrapBlazor.Shared.Demos.EditDialog.EditDialogNormal": {
"LeftAlignedButton": "Edit popup (left-aligned)", "LeftAlignedButton": "Edit popup (left-aligned)",
"RightAlignedButton": "Edit popup (right aligned)" "RightAlignedButton": "Edit popup (right aligned)"
@ -4883,7 +4893,8 @@
"Bluetooth": "Bluetooth & Printer", "Bluetooth": "Bluetooth & Printer",
"PdfReader": "PDF Reader", "PdfReader": "PDF Reader",
"VideoPlayer": "VideoPlayer", "VideoPlayer": "VideoPlayer",
"FileViewer": "FileViewer" "FileViewer": "FileViewer",
"BaiduOcr": "IBaiduOcr "
}, },
"BootstrapBlazor.Shared.Samples.Table.TablesHeader": { "BootstrapBlazor.Shared.Samples.Table.TablesHeader": {
"H1": "Header grouping function", "H1": "Header grouping function",

View File

@ -560,6 +560,16 @@
"NoRenderIntro": "通过设置 <code>IEditorItem</code> 实例的 <code>Editable=false</code>, 实现编辑弹窗不可编辑此属性", "NoRenderIntro": "通过设置 <code>IEditorItem</code> 实例的 <code>Editable=false</code>, 实现编辑弹窗不可编辑此属性",
"EditDialogOption": "EditDialogOption 属性" "EditDialogOption": "EditDialogOption 属性"
}, },
"BootstrapBlazor.Shared.Samples.BaiduOcr": {
"Title": "IBaiduOcr 百度文字识别服务",
"SubTitle": "文字识别服务",
"BasicUsageTitle": "增值税发票文字识别",
"BasicUsageIntro": "通过上传增值税发票图片调用百度 Ocr 接口进行文字识别",
"BaiduOcrDesc": "本组件通过调用 Baidu AI 平台文字识别接口进行增值税发票文字识别。支持对增值税普票、专票、全电发票(新版全国统一电子发票,专票/普票)、卷票、区块链发票的所有字段进行结构化识别,包括发票基本信息、销售方及购买方信息、商品信息、价税信息等,其中五要素字段的识别准确率超过 99.9% 同时,支持对增值税卷票的 21 个关键字段进行识别,包括发票类型、发票代码、发票号码、机打号码、机器编号、收款人、销售方名称、销售方纳税人识别号、开票日期、购买方名称、购买方纳税人识别号、项目、单价、数量、金额、税额、合计金额(小写)、合计金额(大写)、校验码、省、市四要素字段的识别准确率可达95%。上传图片不能超过 4M",
"BaiduOcrIntro": "使用方法",
"BaiduOcrStep1": "1. 通过注入服务获得 <code>IBaiduOcr</code> 实例",
"BaiduOcrStep2": "2. 调用服务相对应的识别方法即可"
},
"BootstrapBlazor.Shared.Demos.EditDialog.EditDialogNormal": { "BootstrapBlazor.Shared.Demos.EditDialog.EditDialogNormal": {
"LeftAlignedButton": "编辑弹窗(左对齐)", "LeftAlignedButton": "编辑弹窗(左对齐)",
"RightAlignedButton": "编辑弹窗(右对齐)" "RightAlignedButton": "编辑弹窗(右对齐)"
@ -4897,7 +4907,8 @@
"Bluetooth": "蓝牙和打印 Bluetooth & Printer", "Bluetooth": "蓝牙和打印 Bluetooth & Printer",
"PdfReader": "PDF阅读器 PDF Reader", "PdfReader": "PDF阅读器 PDF Reader",
"VideoPlayer": "视频播放器 VideoPlayer", "VideoPlayer": "视频播放器 VideoPlayer",
"FileViewer": "文件预览器 FileViewer" "FileViewer": "文件预览器 FileViewer",
"BaiduOcr": "文字识别服务 IBaiduOcr "
}, },
"BootstrapBlazor.Shared.Samples.Table.TablesHeader": { "BootstrapBlazor.Shared.Samples.Table.TablesHeader": {
"H1": "表头分组功能", "H1": "表头分组功能",

View File

@ -0,0 +1,17 @@
@page "/ocr"
@inject IStringLocalizer<BaiduOcr> Localizer
<h3>@Localizer["Title"]</h3>
<h4>@Localizer["SubTitle"]</h4>
<Tips class="mt-3">@Localizer["BaiduOcrDesc"]</Tips>
<PackageTips Name="BootstrapBlazor.BaiduOcr" />
<div class="mb-2"><b>@Localizer["BaiduOcrIntro"]</b></div>
<div>@((MarkupString)Localizer["BaiduOcrStep1"].Value)</div>
<div>@((MarkupString)Localizer["BaiduOcrStep2"].Value)</div>
<DemoBlock Title="@Localizer["BasicUsageTitle"]" Introduction="@Localizer["BasicUsageIntro"]" Name="Normal" Demo="typeof(Demos.BaiduOcr.BaiduOcrNormal)" />

View File

@ -0,0 +1,12 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/
namespace BootstrapBlazor.Shared.Samples;
/// <summary>
/// 百度文字识别示例
/// </summary>
public partial class BaiduOcr
{
}

View File

@ -1,7 +1,6 @@
@page "/barcodereaders" @page "/barcodereaders"
@inject IStringLocalizer<BarcodeReaders> Localizer @inject IStringLocalizer<BarcodeReaders> Localizer
<h4>@Localizer["SubTitle"]</h4> <h4>@Localizer["SubTitle"]</h4>
<PackageTips Name="BootstrapBlazor.BarCode" /> <PackageTips Name="BootstrapBlazor.BarCode" />

View File

@ -79,7 +79,8 @@
"ribbontabs": "RibbonTabs", "ribbontabs": "RibbonTabs",
"pdfreaders": "PdfReaders", "pdfreaders": "PdfReaders",
"videoPlayers": "VideoPlayers", "videoPlayers": "VideoPlayers",
"fileviewers": "FileViewers" "fileviewers": "FileViewers",
"ocr": "BaiduOcr"
}, },
"video": { "video": {
"autorefresh": "BV1ap4y1x7Qn?p=8", "autorefresh": "BV1ap4y1x7Qn?p=8",

View File

@ -82,142 +82,4 @@ public class UploadFile
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public string? GetExtension() => Path.GetExtension(GetFileName()); public string? GetExtension() => Path.GetExtension(GetFileName());
/// <summary>
/// 获取 base64 格式图片字符串
/// </summary>
/// <param name="format"></param>
/// <param name="maxWidth"></param>
/// <param name="maxHeight"></param>
/// <param name="maxAllowedSize"></param>
/// <param name="token"></param>
[ExcludeFromCodeCoverage]
public async Task RequestBase64ImageFileAsync(string format, int maxWidth, int maxHeight, long maxAllowedSize = 512000, CancellationToken token = default)
{
if (File != null)
{
try
{
var imageFile = await File.RequestImageFileAsync(format, maxWidth, maxHeight);
using var fileStream = imageFile.OpenReadStream(maxAllowedSize, token);
using var memoryStream = new MemoryStream();
await fileStream.CopyToAsync(memoryStream, token);
PrevUrl = $"data:{format};base64,{Convert.ToBase64String(memoryStream.ToArray())}";
Uploaded = true;
}
catch (Exception ex)
{
Code = 1004;
Error = ex.Message;
PrevUrl = null;
}
}
}
/// <summary>
///
/// </summary>
/// <param name="format"></param>
/// <param name="maxWidth"></param>
/// <param name="maxHeight"></param>
/// <param name="maxAllowedSize"></param>
/// <param name="token"></param>
/// <returns></returns>
[ExcludeFromCodeCoverage]
public async Task<byte[]?> GetByteArray(string format, int maxWidth, int maxHeight, long maxAllowedSize = 512000, CancellationToken token = default)
{
if (File != null)
{
try
{
var imageFile = await File.RequestImageFileAsync(format, maxWidth, maxHeight);
using var fileStream = imageFile.OpenReadStream(maxAllowedSize, token);
using var memoryStream = new MemoryStream();
await fileStream.CopyToAsync(memoryStream, token);
return memoryStream.ToArray();
}
catch (Exception ex)
{
Code = 1004;
Error = ex.Message;
PrevUrl = null;
}
}
return null;
}
/// <summary>
/// 保存到文件
/// </summary>
/// <param name="fileName"></param>
/// <param name="maxAllowedSize"></param>
/// <param name="token"></param>
/// <returns></returns>
[ExcludeFromCodeCoverage]
public async Task<bool> SaveToFile(string fileName, long maxAllowedSize = 512000, CancellationToken token = default)
{
var ret = false;
if (File != null)
{
// 文件保护,如果文件存在则先删除
if (System.IO.File.Exists(fileName))
{
try
{
System.IO.File.Delete(fileName);
}
catch (Exception ex)
{
Code = 1002;
Error = ex.Message;
}
}
var folder = Path.GetDirectoryName(fileName);
if (!string.IsNullOrEmpty(folder) && !Directory.Exists(folder))
{
Directory.CreateDirectory(folder);
}
if (Code == 0)
{
using var uploadFile = System.IO.File.OpenWrite(fileName);
try
{
// 打开文件流
var stream = File.OpenReadStream(maxAllowedSize, token);
var buffer = new byte[4 * 1096];
int bytesRead = 0;
double totalRead = 0;
// 开始读取文件
while ((bytesRead = await stream.ReadAsync(buffer, token)) > 0)
{
totalRead += bytesRead;
await uploadFile.WriteAsync(buffer.AsMemory(0, bytesRead), token);
if (UpdateCallback != null)
{
var percent = (int)((totalRead / File.Size) * 100);
if (percent > ProgressPercent)
{
ProgressPercent = percent;
UpdateCallback(this);
}
}
}
Uploaded = true;
ret = true;
}
catch (Exception ex)
{
Code = 1003;
Error = ex.Message;
}
}
}
return ret;
}
} }

View File

@ -0,0 +1,29 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/
namespace BootstrapBlazor.Components;
/// <summary>
/// BaiduOcrOption 配置类
/// </summary>
public class BaiduOcrOption
{
/// <summary>
/// 获得/设置 Baidu AppId
/// </summary>
[NotNull]
public string? AppId { get; set; }
/// <summary>
/// 获得/设置 Baidu ApiKey
/// </summary>
[NotNull]
public string? ApiKey { get; set; }
/// <summary>
/// 获得/设置 Baidu 密钥
/// </summary>
[NotNull]
public string? Secret { get; set; }
}

View File

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<Version>7.0.0</Version>
</PropertyGroup>
<PropertyGroup>
<PackageTags>Bootstrap Blazor Baidu Ocr Components</PackageTags>
<Description>Bootstrap UI components extensions of Baidu Ocr</Description>
<RootNamespace>BootstrapBlazor.Components</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Baidu.AI" Version="4.15.10" />
<PackageReference Include="BootstrapBlazor" Version="7.0.0" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,32 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace BootstrapBlazor.Components;
/// <summary>
/// BootstrapBlazor 服务扩展类
/// </summary>
public static class ServiceCollectionExtension
{
/// <summary>
/// 添加百度文字识别服务
/// </summary>
/// <param name="services"></param>
/// <param name="configOptions"></param>
/// <returns></returns>
public static IServiceCollection AddBootstrapBlazorBaiduOcr(this IServiceCollection services, Action<
BaiduOcrOption>? configOptions = null)
{
services.TryAddSingleton<IBaiduOcr, BaiduOcr>();
services.AddOptionsMonitor<BaiduOcrOption>();
services.Configure<BaiduOcrOption>(option =>
{
configOptions?.Invoke(option);
});
return services;
}
}

View File

@ -0,0 +1,24 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/
using Newtonsoft.Json;
namespace BootstrapBlazor.Components;
/// <summary>
/// 货物信息实体类
/// </summary>
public class CommodityEntity
{
/// <summary>
/// 获得/设置 行号
/// </summary>
public string? Row { get; set; }
/// <summary>
/// 获得/设置 货物名称内容
/// </summary>
[JsonProperty("word")]
public string? CommodityName { get; set; }
}

View File

@ -0,0 +1,24 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/
using Newtonsoft.Json;
namespace BootstrapBlazor.Components;
/// <summary>
/// 税率信息实体类
/// </summary>
public class CommodityTaxRateEntity
{
/// <summary>
/// 获得/设置 行号
/// </summary>
public string? Row { get; set; }
/// <summary>
/// 获得/设置 税率内容
/// </summary>
[JsonProperty("word")]
public string? CommodityTaxRate { get; set; }
}

View File

@ -0,0 +1,176 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/
namespace BootstrapBlazor.Components;
/// <summary>
/// 发票实体类
/// </summary>
public class InvoiceEntity
{
/// <summary>
/// 获得/设置 发票消费类型
/// </summary>
public string? ServiceType { get; set; }
/// <summary>
/// 获得/设置 联次信息。专票第一联到第三联分别输出:第一联:记账联、第二联:抵扣联、第三联:发票联;普通发票第一联到第二联分别输出:第一联:记账联、第二联:发票联
/// </summary>
public string? SheetNum { get; set; }
/// <summary>
/// 获得/设置 发票种类。不同类型发票输出:普通发票、专用发票、电子普通发票、电子专用发票、通行费电子普票、区块链发票、通用机打电子发票、电子发票(专用发票)、电子发票(普通发票)
/// </summary>
public string? InvoiceType { get; set; }
/// <summary>
/// 获得/设置 发票名称
/// </summary>
public string? InvoiceTypeOrg { get; set; }
/// <summary>
/// 获得/设置 发票代码
/// </summary>
public string? InvoiceCode { get; set; }
/// <summary>
/// 获得/设置 发票号码
/// </summary>
public string? InvoiceNum { get; set; }
/// <summary>
/// 获得/设置 增值税发票左上角标志。 包含:通行费,销项负数、代开、收购、成品油、其他
/// </summary>
public string? InvoiceTag { get; set; }
/// <summary>
/// 获得/设置 开票日期
/// </summary>
public string? InvoiceDate { get; set; }
/// <summary>
/// 获得/设置 机打号码。仅增值税卷票含有此参数
/// </summary>
public string? MachineNum { get; set; }
/// <summary>
/// 获得/设置 机器编号。仅增值税卷票含有此参数
/// </summary>
public string? MachineCode { get; set; }
/// <summary>
/// 获得/设置 校验码。增值税专票无此参数
/// </summary>
public string? CheckCode { get; set; }
/// <summary>
/// 获得/设置 购方名称
/// </summary>
public string? PurchaserName { get; set; }
/// <summary>
/// 获得/设置 购方纳税人识别号
/// </summary>
public string? PurchaserRegisterNum { get; set; }
/// <summary>
/// 获得/设置 购方地址及电话
/// </summary>
public string? PurchaserAddress { get; set; }
/// <summary>
/// 获得/设置 购方开户行及账号
/// </summary>
public string? PurchaserBank { get; set; }
/// <summary>
/// 获得/设置 密码区
/// </summary>
public string? Password { get; set; }
/// <summary>
/// 获得/设置 省
/// </summary>
public string? Province { get; set; }
/// <summary>
/// 获得/设置 市
/// </summary>
public string? City { get; set; }
/// <summary>
/// 获得/设置 是否代开
/// </summary>
public string? Agent { get; set; }
/// <summary>
/// 获得/设置 备注
/// </summary>
public string? Remarks { get; set; }
/// <summary>
/// 获得/设置 销售方名称
/// </summary>
public string? SellerName { get; set; }
/// <summary>
/// 获得/设置 销售方地址及电话
/// </summary>
public string? SellerAddress { get; set; }
/// <summary>
/// 获得/设置 销售方开户行及账号
/// </summary>
public string? SellerBank { get; set; }
/// <summary>
/// 获得/设置 销售方纳税人识别号
/// </summary>
public string? SellerRegisterNum { get; set; }
/// <summary>
/// 获得/设置 合计金额
/// </summary>
public string? TotalAmount { get; set; }
/// <summary>
/// 获得/设置 合计税额
/// </summary>
public string? TotalTax { get; set; }
/// <summary>
/// 获得/设置 价税合计(大写)
/// </summary>
public string? AmountInWords { get; set; }
/// <summary>
/// 获得/设置 价税合计(小写)
/// </summary>
public string? AmountInFiguers { get; set; }
/// <summary>
/// 获得/设置 收款人
/// </summary>
public string? Payee { get; set; }
/// <summary>
/// 获得/设置 复核
/// </summary>
public string? Checker { get; set; }
/// <summary>
/// 获得/设置 开票人
/// </summary>
public string? NoteDrawer { get; set; }
/// <summary>
/// 获得/设置 货物名称
/// </summary>
public List<CommodityEntity>? CommodityName { get; set; }
/// <summary>
/// 获得/设置 税率
/// </summary>
public List<CommodityTaxRateEntity>? CommodityTaxRate { get; set; }
}

View File

@ -0,0 +1,42 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/
using Baidu.Aip.Ocr;
using Microsoft.Extensions.Options;
namespace BootstrapBlazor.Components;
internal class BaiduOcr : IBaiduOcr
{
protected IOptionsMonitor<BaiduOcrOption> Options { get; set; }
/// <summary>
/// 构造函数
/// </summary>
/// <param name="options"></param>
public BaiduOcr(IOptionsMonitor<BaiduOcrOption> options)
{
Options = options;
}
/// <summary>
/// 身份证识别
/// </summary>
public void Scan(byte[] image)
{
var client = new Ocr(Options.CurrentValue.ApiKey, Options.CurrentValue.Secret);
client.GeneralBasic(image);
}
/// <summary>
/// 识别增值税发票方法
/// </summary>
public Task<InvoiceEntity> CheckVatInvoiceAsync(byte[] image) => Task.Run(() =>
{
var client = new Ocr(Options.CurrentValue.ApiKey, Options.CurrentValue.Secret);
var resp = client.VatInvoice(image);
var ret = resp.GetValue("words_result").ToObject(typeof(InvoiceEntity)) as InvoiceEntity;
return ret ?? new InvoiceEntity() { CommodityName = new(), CommodityTaxRate = new() };
});
}

View File

@ -0,0 +1,17 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/
namespace BootstrapBlazor.Components;
/// <summary>
/// 百度文字识别服务
/// </summary>
public interface IBaiduOcr
{
/// <summary>
/// 增值税发票识别
/// </summary>
/// <param name="image"></param>
Task<InvoiceEntity> CheckVatInvoiceAsync(byte[] image);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 B

View File

@ -0,0 +1,6 @@
// This is a JavaScript module that is loaded on demand. It can export any number of
// functions, and may import other JavaScript modules if required.
export function showPrompt(message) {
return prompt(message, 'Type anything here');
}

View File

@ -57,7 +57,7 @@ public class BaiduRecognizerProvider : IRecognizerProvider, IAsyncDisposable
if (Module == null) if (Module == null)
{ {
var moduleName = "./_content/BootstrapBlazor.BaiduSpeech/js/recognizer.js"; var moduleName = "./_content/BootstrapBlazor.BaiduSpeech/js/recognizer.js";
Logger.LogInformation($"load module {moduleName}"); Logger.LogInformation("load module {moduleName}", moduleName);
Module = await JSRuntime.InvokeAsync<IJSObjectReference>("import", moduleName); Module = await JSRuntime.InvokeAsync<IJSObjectReference>("import", moduleName);
} }
Interop ??= DotNetObjectReference.Create(this); Interop ??= DotNetObjectReference.Create(this);
@ -71,7 +71,7 @@ public class BaiduRecognizerProvider : IRecognizerProvider, IAsyncDisposable
[JSInvokable] [JSInvokable]
public async Task RecognizeCallback(RecognizerStatus status, byte[]? bytes) public async Task RecognizeCallback(RecognizerStatus status, byte[]? bytes)
{ {
Logger.LogInformation($"RecognizerStatus: {status}"); Logger.LogInformation("RecognizerStatus: {status}", status);
string data = ""; string data = "";
if (status == RecognizerStatus.Finished) if (status == RecognizerStatus.Finished)
{ {
@ -89,11 +89,11 @@ public class BaiduRecognizerProvider : IRecognizerProvider, IAsyncDisposable
sb.Append(item.ToString()); sb.Append(item.ToString());
} }
data = sb.ToString(); data = sb.ToString();
Logger.LogInformation($"recognizer: {data}"); Logger.LogInformation("recognizer: {data}", data);
} }
else else
{ {
Logger.LogError($"err_no: {err_no}"); Logger.LogError("err_no: {err_no}", err_no);
} }
}); });
} }