下面展示的是一个.NET(其他语言可参考官方文档)可直接使用的 OCR 调用方案,包括:

  • PaddleOCR 调用封装(识别模型:PaddleOCR-VL-1.5,支持账号池轮询)

  • 文件识别业务方法示例(示例:RecognizeAsync

  • 完整可运行代码,无废话


一、PaddleOCR 调用封装(支持 Token 自动切换)

using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

namespace Common.Helper
{
    public class PaddleOCRSharpHelper
    {
        #region 内部模型

        private class ApiAccount
        {
            public string Url { get; init; }
            public string Token { get; init; }
        }

        public class OCRResult
        {
            public bool IsSuccess { get; set; }
            public string AccountUrl { get; set; }
            public int? StatusCode { get; set; }
            public string StatusDescription { get; set; }
            public string Text { get; set; }
            public string ErrorMessage { get; set; }
        }

        #endregion

        #region 账号池(后续可迁移到配置)

        private static readonly ApiAccount[] Accounts =
        {
            // 自己去百度PaddleOCR官网复制对应模型的个人API秘钥即可
            new()
            {
                Url = "https://xxxxxxxxx1.aistudio-app.com/layout-parsing",
                Token = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
            }
        };

        #endregion

        #region 状态码定义

        private static readonly Dictionary<int, string> StatusDescriptions = new()
        {
            { 403, "Token 错误或 URL 不匹配" },
            { 429, "超出单日解析最大页数" },
            { 500, "参数错误(fileType / payload)" },
            { 503, "服务繁忙,请稍后再试" },
            { 504, "网关超时,请稍后再试" }
        };

        // 这些错误 → 可以切换账号继续尝试
        private static readonly HashSet<int> RetryableStatusCodes =
            new() { 403, 429, 503, 504 };

        #endregion

        #region HttpClient

        private static readonly HttpClient HttpClient = new()
        {
            Timeout = TimeSpan.FromSeconds(180)
        };

        #endregion

        #region 主入口

        public static async Task<OCRResult> RecognizeTextWithStatusAsync(string filePath)
        {
            if (!File.Exists(filePath))
                throw new FileNotFoundException("File not found.", filePath);

            int fileType = Path.GetExtension(filePath)
                .Equals(".pdf", StringComparison.OrdinalIgnoreCase) ? 0 : 1;

            byte[] fileBytes = await File.ReadAllBytesAsync(filePath);
            string base64File = Convert.ToBase64String(fileBytes);

            var payload = new
            {
                file = base64File,
                fileType,
                useDocOrientationClassify = false,
                useDocUnwarping = false,
                useChartRecognition = false
            };

            string json = JsonSerializer.Serialize(payload);

            foreach (var account in Accounts)
            {
                try
                {
                    using var request = new HttpRequestMessage(HttpMethod.Post, account.Url)
                    {
                        Content = new StringContent(json, Encoding.UTF8, "application/json")
                    };

                    request.Headers.Add("Authorization", $"token {account.Token}");

                    var response = await HttpClient.SendAsync(request);
                    var responseText = await response.Content.ReadAsStringAsync();

                    int statusCode = (int)response.StatusCode;
                    StatusDescriptions.TryGetValue(statusCode, out var statusDesc);

                    if (!response.IsSuccessStatusCode)
                    {
                        if (RetryableStatusCodes.Contains(statusCode))
                        {
                            Log($"账号切换:{account.Url},状态码:{statusCode},原因:{statusDesc}");
                            continue;
                        }

                        return BuildFailResult(account, statusCode, statusDesc, responseText);
                    }

                    return ParseSuccessResult(account, statusCode, statusDesc, responseText);
                }
                catch (Exception ex)
                {
                    Log($"账号调用异常:{account.Url},{ex.Message}");
                }
            }

            return new OCRResult
            {
                IsSuccess = false,
                ErrorMessage = "所有账号均已尝试,OCR 识别失败。"
            };
        }

        #endregion

        #region 私有辅助方法

        private static OCRResult BuildFailResult(
            ApiAccount account,
            int statusCode,
            string statusDesc,
            string error)
        {
            return new OCRResult
            {
                IsSuccess = false,
                AccountUrl = account.Url,
                StatusCode = statusCode,
                StatusDescription = statusDesc,
                ErrorMessage = error
            };
        }

        private static OCRResult ParseSuccessResult(
            ApiAccount account,
            int statusCode,
            string statusDesc,
            string responseText)
        {
            var sb = new StringBuilder();

            using var doc = JsonDocument.Parse(responseText);
            if (doc.RootElement.TryGetProperty("result", out var result) &&
                result.TryGetProperty("layoutParsingResults", out var layouts))
            {
                foreach (var item in layouts.EnumerateArray())
                {
                    if (TryGetMarkdownText(item, out var text))
                    {
                        sb.AppendLine(text);
                        sb.AppendLine();
                    }
                }
            }

            return new OCRResult
            {
                IsSuccess = true,
                AccountUrl = account.Url,
                StatusCode = statusCode,
                StatusDescription = statusDesc,
                Text = sb.ToString()
            };
        }

        private static bool TryGetMarkdownText(JsonElement item, out string text)
        {
            text = null;

            if (item.TryGetProperty("markdown", out var markdown) &&
                markdown.TryGetProperty("text", out var textProp))
            {
                text = textProp.GetString();
                return !string.IsNullOrWhiteSpace(text);
            }

            return false;
        }

        private static void Log(string message)
        {
            // 后续可无缝替换成 ILogger
            Console.WriteLine($"[PaddleOCR] {message}");
        }

        #endregion
    }
}

二、业务方法:识别文件并返回 OCR 文本

public async Task<ResultObject<XxxDto>> RecognizeAsync(
    IFormFileCollection files)
{
    var result = new ResultObject<XxxDto>();

    if (files == null || files.Count == 0)
    {
        result.IsSuccess = false;
        result.Msg = "No file uploaded.";
        return result;
    }

    var dto = new XxxDto();
    string tempFilePath = null;

    try
    {
        tempFilePath = await SaveTempFileAsync(files[0]);

        var ocrResult = await PaddleOCRSharpHelper.RecognizeTextWithStatusAsync(tempFilePath);

        if (!ocrResult.IsSuccess)
        {
            result.IsSuccess = false;
            result.Msg = $"OCR 识别失败,状态码: {ocrResult.StatusCode},说明: {ocrResult.StatusDescription}";
            return result;
        }

        string ocrText = ocrResult.Text;

        // TODO: 这里处理你自己的文本解析逻辑

        result.IsSuccess = true;
        result.Data = dto;
        return result;
    }
    catch (Exception ex)
    {
        result.IsSuccess = false;
        result.Msg = "识别异常:" + ex.Message;
        return result;
    }
    finally
    {
        if (!string.IsNullOrWhiteSpace(tempFilePath) && File.Exists(tempFilePath))
            File.Delete(tempFilePath);
    }
}

private static async Task<string> SaveTempFileAsync(IFormFile file)
{
    if (file == null) throw new ArgumentNullException(nameof(file));

    // 保留原始扩展名
    var ext = Path.GetExtension(file.FileName);
    var tempFilePath = Path.Combine(
        Path.GetTempPath(),
        $"{Guid.NewGuid()}{ext}"
    );

    await using var stream = file.OpenReadStream();
    await using var fs = new FileStream(tempFilePath, FileMode.Create, FileAccess.Write);
    await stream.CopyToAsync(fs);

    return tempFilePath;
}

三、可直接运行,结构清晰

至此,OCR 调用与业务集成全部完成。

更多推荐