GLM-OCR跨平台实战:.NET Core后端服务集成
GLM-OCR跨平台实战:.NET Core后端服务集成
最近在做一个内部文档处理系统,需要从各种上传的图片里提取文字。一开始试了几个开源的OCR方案,效果总是不太稳定,要么识别率不高,要么对中文支持不好。后来团队引入了GLM-OCR,效果确实提升了不少,但怎么把它优雅地集成到我们现有的.NET Core后端服务里,成了一个新的问题。
直接在每个Controller里写HttpClient调用?代码太乱,也不好维护。网络偶尔波动导致调用失败怎么办?返回的JSON结构有点复杂,每次都手动解析也挺麻烦。这篇文章,我就来分享一下我们团队最终落地的方案:如何用.NET Core的标准姿势,把GLM-OCR封装成一个稳定、易用、好维护的后端服务。
1. 场景与核心挑战
我们的系统是一个典型的.NET Core WebAPI项目,运行在Linux服务器上。用户通过前端页面上传合同、发票、名片等图片,后端需要快速、准确地提取出文字内容,然后进行后续的结构化处理和入库。
直接调用GLM-OCR的HTTP接口听起来简单,但放到生产环境,就得考虑几个实际问题:
- 连接管理:频繁地创建和销毁HttpClient实例是性能大忌,也容易导致端口耗尽。
- 网络容错:服务间调用难免遇到网络抖动或目标服务短暂不可用,需要重试机制,不能一失败就报错给用户。
- 响应处理:OCR接口返回的JSON结构包含文本块、坐标、置信度等多层信息,解析代码要清晰、高效,并且容易应对接口字段的变化。
- 服务抽象:最好能把OCR能力封装成一个标准的服务(Service),这样业务代码调用起来就像调用本地方法一样简单,后续换用其他OCR提供商也方便。
接下来,我们就围绕这几个点,一步步构建解决方案。
2. 项目结构与基础准备
首先,我们创建一个新的ASP.NET Core Web API项目,或者在你现有的项目中添加相关代码。我们假设你已经有一个运行中的GLM-OCR服务,它提供了一个HTTP API端点,例如 http://your-ocr-server:8000/v1/ocr。
我们需要安装几个关键的NuGet包来助力:
dotnet add package Microsoft.Extensions.Http
dotnet add package Polly
dotnet add package Polly.Extensions.Http
dotnet add package System.Text.Json
Microsoft.Extensions.Http:提供了IHttpClientFactory,这是我们管理HttpClient生命周期的核心。Polly和Polly.Extensions.Http:用来定义和执行各种弹性策略,比如重试、熔断。System.Text.Json:.NET Core高性能的JSON序列化库,我们会用它来解析响应。
在项目里,我们规划了以下几个核心部分:
- 一个
Models文件夹,存放请求和响应的数据模型类。 - 一个
Services文件夹,存放我们的OCR服务实现。 - 在
Program.cs(或Startup.cs)中进行依赖注入配置。
3. 定义数据模型
先根据GLM-OCR接口的文档,定义我们调用时需要发送的数据和预期返回的数据结构。这能让我们的代码强类型化,避免魔法字符串。
在Models文件夹下创建两个类:
OcrRequest.cs
namespace YourProjectName.Models;
public class OcrRequest
{
/// <summary>
/// Base64编码的图片数据
/// </summary>
public string ImageData { get; set; } = string.Empty;
/// <summary>
/// 可选参数,例如识别语言等
/// </summary>
public Dictionary<string, object>? Parameters { get; set; }
}
OcrResponse.cs
using System.Text.Json.Serialization;
namespace YourProjectName.Models;
public class OcrResponse
{
[JsonPropertyName("text")]
public string FullText { get; set; } = string.Empty;
[JsonPropertyName("blocks")]
public List<TextBlock> Blocks { get; set; } = new List<TextBlock>();
[JsonPropertyName("status")]
public string Status { get; set; } = "unknown";
[JsonPropertyName("message")]
public string? Message { get; set; }
}
public class TextBlock
{
[JsonPropertyName("box")]
public List<List<int>>? Box { get; set; } // 文字框坐标
[JsonPropertyName("text")]
public string Text { get; set; } = string.Empty;
[JsonPropertyName("score")]
public float Confidence { get; set; }
}
这里用了[JsonPropertyName]特性来映射JSON字段名,这样即使接口返回的字段名是text,我们也能用FullText这样更符合C#命名规范的属性来访问。
4. 构建核心OCR服务
现在来创建服务层。在Services文件夹下,我们定义一个接口和它的实现。
IGlmOcrService.cs
using YourProjectName.Models;
namespace YourProjectName.Services;
public interface IGlmOcrService
{
Task<OcrResponse> RecognizeTextAsync(OcrRequest request, CancellationToken cancellationToken = default);
}
GlmOcrService.cs 这是重头戏,我们一步步来实现。
using System.Net.Http.Json; // 用于方便的JSON序列化/反序列化
using Microsoft.Extensions.Logging;
using Polly;
using Polly.Retry;
using YourProjectName.Models;
namespace YourProjectName.Services;
public class GlmOcrService : IGlmOcrService
{
private readonly HttpClient _httpClient;
private readonly ILogger<GlmOcrService> _logger;
private readonly AsyncRetryPolicy<HttpResponseMessage> _retryPolicy;
// 构造函数注入配置好的HttpClient和Logger
public GlmOcrService(HttpClient httpClient, ILogger<GlmOcrService> logger)
{
_httpClient = httpClient;
_logger = logger;
// 使用Polly定义重试策略:针对网络超时和5xx服务器错误重试3次,每次间隔指数退避
_retryPolicy = Policy
.HandleResult<HttpResponseMessage>(r =>
(int)r.StatusCode >= 500 || // 服务器错误
r.StatusCode == System.Net.HttpStatusCode.RequestTimeout // 超时
)
.Or<HttpRequestException>() // 网络请求异常
.WaitAndRetryAsync(
retryCount: 3,
sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), // 2, 4, 8秒
onRetry: (outcome, timespan, retryCount, context) =>
{
_logger.LogWarning("OCR API调用失败,正在进行第 {RetryCount} 次重试。错误:{Error}",
retryCount, outcome.Exception?.Message ?? outcome.Result?.StatusCode.ToString());
});
}
public async Task<OcrResponse> RecognizeTextAsync(OcrRequest request, CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace(request.ImageData))
{
throw new ArgumentException("图片数据不能为空。");
}
try
{
// 使用Polly包装的HttpClient执行请求
var response = await _retryPolicy.ExecuteAsync(async () =>
{
// 这里假设OCR服务接收JSON body,包含image_data字段
var requestBody = new { image_data = request.ImageData, parameters = request.Parameters };
return await _httpClient.PostAsJsonAsync("", requestBody, cancellationToken); // 地址在配置中设置
});
// 确保响应成功
response.EnsureSuccessStatusCode();
// 使用System.Text.Json反序列化响应内容
var ocrResult = await response.Content.ReadFromJsonAsync<OcrResponse>(cancellationToken: cancellationToken);
if (ocrResult == null)
{
throw new InvalidOperationException("OCR服务返回了空或无法解析的响应。");
}
_logger.LogInformation("OCR识别成功,共识别出 {BlockCount} 个文本块。", ocrResult.Blocks.Count);
return ocrResult;
}
catch (HttpRequestException ex)
{
_logger.LogError(ex, "调用OCR服务时发生网络错误。");
// 可以返回一个包含错误状态的OcrResponse,或者直接抛出,根据业务逻辑决定
return new OcrResponse
{
Status = "error",
Message = $"网络请求失败:{ex.Message}"
};
}
catch (TaskCanceledException) when (cancellationToken.IsCancellationRequested)
{
_logger.LogInformation("OCR识别任务被用户取消。");
throw;
}
catch (Exception ex)
{
_logger.LogError(ex, "处理OCR响应时发生未知错误。");
throw;
}
}
}
代码要点解析:
- 依赖注入:通过构造函数注入
HttpClient和ILogger,这是.NET Core服务的标准做法。 - Polly重试策略:在构造函数中定义了一个重试策略。如果遇到服务器错误(5xx)或超时,它会自动重试最多3次,并且每次重试的等待时间指数级增加(2秒、4秒、8秒),避免给故障服务造成更大压力。
- 核心调用:
RecognizeTextAsync方法中,使用_retryPolicy.ExecuteAsync来执行实际的HTTP POST请求。PostAsJsonAsync方法会自动将对象序列化为JSON并设置正确的Content-Type。 - 响应处理:使用
ReadFromJsonAsync将响应的JSON流直接反序列化成我们定义好的OcrResponse对象,非常高效。 - 异常处理与日志:使用
ILogger记录了不同级别的日志,这对于生产环境调试和监控至关重要。对不同的异常(网络异常、取消请求、解析异常)进行了分类处理。
5. 配置依赖注入
服务写好了,怎么让它跑起来呢?需要在Program.cs中把它注册到依赖注入容器里。
using YourProjectName.Services;
var builder = WebApplication.CreateBuilder(args);
// 添加服务到容器
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// 关键配置:注册OCR服务
builder.Services.AddHttpClient<IGlmOcrService, GlmOcrService>((serviceProvider, client) =>
{
// 从配置文件中读取OCR服务的基础地址
var configuration = serviceProvider.GetRequiredService<IConfiguration>();
client.BaseAddress = new Uri(configuration["OcrService:BaseUrl"] ?? "http://localhost:8000/v1/");
// 可以配置一些默认的HTTP客户端设置,比如超时
client.Timeout = TimeSpan.FromSeconds(30);
// 如果需要,可以在这里添加默认请求头,例如API Key
// client.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiKey}");
})
.AddPolicyHandlerFromRegistry((policyRegistry, request) =>
{
// 这里可以配置更丰富的策略,比如熔断器
// 我们已经在GlmOcrService内部实现了重试,这里也可以添加一个基础的超时策略
return policyRegistry.Get<IAsyncPolicy<HttpResponseMessage>>("RetryPolicy") ?? Policy.NoOpAsync<HttpResponseMessage>();
});
// 如果你有多个HttpClient需要不同的策略,可以这样配置一个策略注册表
// builder.Services.AddPolicyRegistry();
var app = builder.Build();
// 配置HTTP请求管道
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
在appsettings.json中配置你的OCR服务地址:
{
"OcrService": {
"BaseUrl": "http://your-ocr-server:8000/v1/"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
6. 在Controller中调用
最后,在API控制器中注入并使用我们的OCR服务,就非常简单了。
using Microsoft.AspNetCore.Mvc;
using YourProjectName.Models;
using YourProjectName.Services;
namespace YourProjectName.Controllers;
[ApiController]
[Route("api/[controller]")]
public class OcrController : ControllerBase
{
private readonly IGlmOcrService _ocrService;
public OcrController(IGlmOcrService ocrService)
{
_ocrService = ocrService;
}
[HttpPost("recognize")]
public async Task<ActionResult<OcrResponse>> RecognizeText([FromBody] OcrRequest request)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var result = await _ocrService.RecognizeTextAsync(request);
if (result.Status == "error")
{
// 根据业务逻辑,可以返回400或500等状态码
return BadRequest(result);
}
return Ok(result);
}
}
现在,你的前端或者其他服务就可以向/api/ocr/recognize发送一个包含Base64图片数据的POST请求,后端会稳定、高效地返回OCR识别结果。
7. 总结
回顾一下,我们通过几个步骤在.NET Core后端项目中集成了GLM-OCR:
- 定义清晰的模型:用强类型类来映射请求和响应,让代码更安全、易读。
- 利用IHttpClientFactory:这是管理HttpClient生命周期的官方推荐做法,解决了资源管理和DNS刷新等问题。
- 引入Polly实现弹性策略:简单的重试机制就能显著提升服务间调用的成功率,应对临时性故障。
- 封装成可注入的服务:将OCR能力抽象成一个服务接口和实现,业务代码调用简单,也符合单一职责原则,未来替换底层OCR引擎成本很低。
- 完善的日志和异常处理:这对生产环境排查问题至关重要。
这套方案在我们自己的项目中运行稳定,大大提升了文档处理流程的自动化程度和可靠性。如果你也在做类似的功能集成,不妨试试这个模式,可以根据自己的实际需求调整重试策略、超时时间或者添加熔断等更高级的弹性机制。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。
更多推荐



所有评论(0)