news 2026/7/14 5:31:26

C# + WPF 做了一个 AI 文档批量处理工具,我是怎么从 0 到发布的

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C# + WPF 做了一个 AI 文档批量处理工具,我是怎么从 0 到发布的

一、背景

做了快 9 年 C# / WPF / WinForm 开发,最近开始认真尝试 AI 应用开发方向。

与其一直想"学 AI 以后能做什么",不如先做一个真正能跑起来的小工具。
所以我给自己定的思路很简单:

  • 先做一个体量不大的工具
  • 先解决一个明确的问题
  • 先把东西发布出去
  • 再根据真实反馈慢慢迭代

于是我做了这个工具:文省事

它的定位很直接:批量读取文档,用 AI 生成摘要或提取关键信息。

目前支持:

  • TXT
  • PDF
  • DOCX

输出结果支持:

  • Excel
  • TXT

这篇文章不只讲技术实现,也记录一下从选题、开发到发布的完整过程。


二、为什么做这个工具

我一开始没有想做特别大的 AI 产品,也没有打算一上来就做 Agent、知识库或者复杂工作流。

因为对我来说,现阶段更重要的是:先把一个真正可用的小工具做出来。

选择"文档处理"这个方向,主要有几个原因:

1. 需求比较真实

很多场景都会遇到:

  • 文档太多,根本没时间一个个看
  • 需要快速知道每份文档讲了什么
  • 想批量提取关键信息,比如价格、日期、联系人、结论

2. 技术实现难度适中

这个方向刚好能把我现有的能力用上:

  • WPF 做界面
  • C# 处理文件
  • Semantic Kernel 接 AI
  • Excel 导出结果

3. 容易快速做出 MVP

不需要复杂的用户系统,也不需要部署服务端,
先做成桌面工具就可以验证。


三、技术选型

模块选择原因
UI 框架WPF .NET 8自己最熟悉,开发效率高
AI 接入Semantic Kernel微软官方,.NET 生态友好
模型平台硅基流动 / DeepSeek国内可用,成本低
PDF 读取Aspose.PDF提取文字比较方便
Word 读取DocumentFormat.OpenXml官方库,免费
Excel 导出ClosedXMLAPI 简洁,够用
架构简化版 MVVM项目不大,控制复杂度

为什么没用 Python

因为我现在做的是AI 应用开发,不是训练模型。

对我来说,现阶段重点是:

  • 能不能把 AI 能力接起来
  • 能不能做成一个真实可用的工具
  • 能不能跑通从开发到发布的完整过程

这些事情用 C# 完全可以做。


四、项目结构

项目结构比较简单:

AiDocTool/ ├── Models/ │ └── DocumentItem.cs ├── Services/ │ ├── DocumentReader.cs │ ├── AiDocumentService.cs │ └── BatchProcessor.cs ├── ViewModels/ │ └── MainViewModel.cs ├── MainWindow.xaml ├── MainWindow.xaml.cs ├── ApiKeyDialog.xaml └── ApiKeyDialog.xaml.cs

主要分成三层:

DocumentReader
负责读取不同格式的文档内容:txt、pdf、docx

AiDocumentService
负责和 AI 模型通信:摘要、提取信息、流式输出

BatchProcessor
负责把"文件读取"和"AI处理"串起来,
并处理批量执行、导出结果这些事情。


五、核心功能设计

1. 文档统一读取入口

不同文件格式底层处理方式不同,
但对上层来说,只有一个统一入口:

publicstaticasyncTask<string>ReadAsync(stringfilePath){varext=Path.GetExtension(filePath).ToLower();returnextswitch{".txt"=>awaitReadTxtAsync(filePath),".pdf"=>awaitReadPdfAsync(filePath),".docx"=>awaitReadDocxAsync(filePath),_=>thrownewNotSupportedException($"不支持的格式:{ext}")};}

这样上层不用关心具体格式,只要拿到文本内容就行。


2. AI 摘要和提取

AI 层主要做两件事:

  • SummarizeAsync:生成摘要
  • ExtractAsync:提取指定信息

本质上就是 Prompt + 模型调用。

摘要 Prompt 核心代码:

varprompt=$""" 请对以下文档内容进行总结,要求:1.{lengthDesc}2.语言简洁,重点突出3.直接输出摘要内容,不需要任何前缀 文档内容:{truncated}""";varhistory=newChatHistory();history.AddUserMessage(prompt);varresult=await_chatService!.GetChatMessageContentAsync(history,cancellationToken:ct);returnresult.Content?.Trim()??"";

提取信息 Prompt 核心代码:

varprompt=$""" 请从以下文档中提取:{extractTarget}要求:1.只提取文档中实际存在的信息2.找不到时明确说"未找到相关信息"3.结果格式清晰,每条信息单独一行4.直接输出结果,不需要额外解释 文档内容:{truncated}""";

Prompt 设计几个关键点:

  • 明确说「直接输出,不需要前缀」,否则 AI 会加「以下是摘要:」之类的废话
  • 长度描述要具体,「200字」比「适中」效果好很多
  • 文档内容要截断,避免超出 token 限制

3. 流式输出

v1.1 新增了流式输出,
就是像 ChatGPT 那样,结果一个字一个字蹦出来,
而不是等全部生成完再一次性显示。

核心实现:

publicasyncIAsyncEnumerable<string>SummarizeStreamAsync(stringcontent,SummaryLengthlength=SummaryLength.Medium,[EnumeratorCancellation]CancellationTokenct=default){EnsureInitialized();vartruncated=TruncateContent(content,8000);varhistory=newChatHistory();history.AddUserMessage(prompt);awaitforeach(varchunkin_chatService!.GetStreamingChatMessageContentsAsync(history,cancellationToken:ct)){if(!string.IsNullOrEmpty(chunk.Content))yieldreturnchunk.Content;}}

批量处理时实时更新 UI:

varsb=newStringBuilder();awaitforeach(varchunkin_aiService.SummarizeStreamAsync(content,summaryLength,ct)){sb.Append(chunk);// 直接更新绑定属性,WPF 自动刷新 UIitem.Result=sb.ToString();}

用户看到的效果是:
点击开始后,右侧结果框立刻开始有内容出现,
一个字一个字地显示,而不是一直等待。


4. 批量处理与导出

批量处理器负责把所有步骤串起来:

for(inti=0;i<list.Count;i++){if(ct.IsCancellationRequested)break;varitem=list[i];try{// 读取文档内容varcontent=awaitDocumentReader.ReadAsync(item.FilePath);// AI 处理(流式)varsb=newStringBuilder();awaitforeach(varchunkin_aiService.SummarizeStreamAsync(content,summaryLength,ct)){sb.Append(chunk);item.Result=sb.ToString();// 实时更新 UI}item.Status=ProcessStatus.Completed;progress.Report(newBatchProgressReport(i,true,""));}catch(OperationCanceledException){throw;}catch(Exceptionex){item.ErrorMessage=ex.Message;item.Status=ProcessStatus.Failed;progress.Report(newBatchProgressReport(i,false,ex.Message));}// 避免频繁请求被限流if(i<list.Count-1)awaitTask.Delay(600,ct);}

导出 Excel 用 ClosedXML 实现,
每一行对应一个文件,包含文件名、摘要内容、处理状态。


六、踩过的坑

坑1:DeepSeek 返回 HTTP 402

现象:

调用 DeepSeek API 返回:

HTTP 402: invalid_request_error

原因:

  • 免费额度有有效期,过期直接返回 402
  • 错误信息不够直观,容易误判为代码问题

解决:

方案 A:DeepSeek 充值,10 元够用很久

方案 B:换用硅基流动(推荐测试阶段使用)

  • 新用户送 14 元免费额度
  • API 格式完全兼容 OpenAI
  • 只需要改两个参数:
// DeepSeekbuilder.AddOpenAIChatCompletion(modelId:"deepseek-chat",apiKey:"你的Key",endpoint:newUri("https://api.deepseek.com"));// 硅基流动builder.AddOpenAIChatCompletion(modelId:"deepseek-ai/DeepSeek-V3",apiKey:"你的Key",endpoint:newUri("https://api.siliconflow.cn/v1"));

坑2:PDF 扫描版读取内容为空

现象:
用 Aspose.PDF 提取某些 PDF 文字,返回空字符串。

原因:
扫描版 PDF 本质是图片,没有真正的文字层,
文字提取库无法处理。

解决:
提取后判断是否为空,明确提示用户:

vartext=absorber.Text?.Trim();if(string.IsNullOrEmpty(text))thrownewException("PDF 内容为空或为扫描版图片,暂不支持扫描版 PDF");

坑3:WPF 事件初始化阶段触发空引用

现象:
程序启动时直接崩溃,报NullReferenceException

原因:
RadioButton.Checked事件会在InitializeComponent()过程中就触发,
这时候其他控件可能还没有初始化完成,直接访问就会报空引用。

解决:
IsLoaded判断,拦截初始化阶段的事件触发:

privatevoidSummaryLength_Changed(objectsender,RoutedEventArgse){// IsLoaded 为 true 才说明窗口已完全加载if(!IsLoaded||_vm==null||senderisnotRadioButtonrb)return;if(rb==RadioShort)_vm.SummaryLength=SummaryLength.Short;elseif(rb==RadioDetailed)_vm.SummaryLength=SummaryLength.Detailed;else_vm.SummaryLength=SummaryLength.Medium;}

坑4:文档内容超出 Token 限制

现象:
大文档处理时 API 直接报错。

原因:
模型有 token 上限,文档内容过长会超出限制。

解决:
处理前截断内容,超长部分提示用户:

privatestaticstringTruncateContent(stringcontent,intmaxLength=8000){if(content.Length<=maxLength)returncontent;returncontent[..maxLength]+"\n\n[文档内容较长,已截取前段进行处理]";}

后续版本计划做分段处理,逐段调用 AI 再合并结果。


坑5:API Key 不能写死在代码里

错误做法:

privateconststringApiKey="sk-xxxx";// 危险!

正确做法:
让用户自己输入,保存到本地 AppData 目录:

varconfigPath=Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),"AiDocTool","config.txt");// 保存File.WriteAllText(configPath,apiKey);// 读取if(File.Exists(configPath))ApiKey=File.ReadAllText(configPath).Trim();

同时把配置文件加入.gitignore
避免提交到 GitHub 时泄露密钥。


七、从"开发完"到"发布出去",是完全不同的阶段

做完这个工具之后,我最大的感受不是技术层面的,而是:

“做出来"和"发出去”,是两件完全不同的事情。

开发阶段关注的是:

  • 功能有没有跑通
  • 代码逻辑对不对
  • 界面有没有问题

发布之后开始关注的是:

  • 标题别人看得懂吗
  • 封面图够不够吸引人
  • 用户为什么没有点进来
  • 为什么曝光有了但浏览很低
  • 描述里的卖点够不够清晰

这部分反而让我感觉,比写代码更像是在学产品和运营。

而且我发现一个很有意思的现象:
技术文章的数据,比工具销售的数据更容易起来。

写文章、讲技术、记录过程,反而更容易被别人找到、认可。

这也是为什么我决定继续坚持写技术文章,
把每一个功能点、每一个踩过的坑都记录下来。


八、目前发布后的真实情况

工具目前已经发布,说说真实的数据情况。

咸鱼:

  • 曝光:225
  • 浏览:11
  • 成交:0

曝光到浏览的转化率偏低,
说明标题或者封面图不够吸引人,
用户看到了但没有点进去的欲望。

这周已经调整了标题,下周看数据变化。

CSDN 技术文章:

  • 第一篇:阅读 198,点赞 4,收藏 3
  • 第二篇:阅读 190,点赞 9,收藏 4

这个数据比我预期的要好,
说明技术内容是有价值的,方向继续就行。

整体感受:

第一周没有成交很正常,
但数据告诉我问题在哪里,这比什么都重要。

知道问题在标题和封面,那就改标题和封面。
知道技术文章数据不错,那就继续写技术文章。


九、接下来怎么迭代

工具层面:

  • 流式输出(v1.1,已在做)
  • 支持超长文档分段处理
  • 支持扫描版 PDF(接 OCR)
  • 支持更多格式(Excel、PPT)
  • 处理前后文件大小对比显示

运营层面:

  • 继续优化咸鱼标题和封面
  • 调整小红书内容风格,从"工具介绍"改成"真实记录"
  • 每周持续发技术文章,积累长尾流量
  • 等有用户咨询后,主动问反馈

内容层面:

  • 每周一篇技术文章(CSDN / 知乎 / 掘金)
  • 每周三篇小红书(工具类 / 生活类 / 复盘类)
  • 技术文章和功能迭代同步,做完就写

十、写在最后

这个工具不一定会很快带来多少收入,
但对我来说有一个很重要的意义:

我开始把"会开发"这件事,往"能做出可发布的产品"这件事上走了。

以前做项目,交付给甲方就结束了。
现在做工具,要想的是:

  • 用户是谁
  • 他们有什么问题
  • 我的东西能不能真正帮到他们
  • 他们会不会愿意为这个付钱

这些问题,比任何技术框架的学习都让我成长更快。

如果你也是 C# / .NET 开发者,
也在考虑做 AI 方向的副业,
我的建议只有一句话:

先做一个小而完整的东西,把它发出去。

不用等完全准备好,不用一上来就做很大,
先跑通一次完整的流程,比想很多都重要。

工具已发布,咸鱼搜索「文省事」可以找到。

如果本文对你有帮助,点个赞 🙏
后续会持续更新 C# + AI 实战内容,欢迎关注。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/14 5:29:37

C++实现亚像素级碰撞检测:高精度物理引擎的核心算法与优化

1. 项目概述&#xff1a;从“撞上”到“擦过”的精度革命在游戏开发、仿真模拟乃至工业设计软件里&#xff0c;物理引擎的碰撞检测是基石。我们通常的认知是&#xff0c;两个物体要么“撞上了”&#xff0c;要么“没撞上”。但当你开发一款需要极高精度的模拟器&#xff0c;比如…

作者头像 李华
网站建设 2026/7/14 5:28:17

JS--URL对象全解析:从location到URLSearchParams的现代实践

1. 从window.location到URL对象&#xff1a;前端URL处理的进化史十年前我刚入行前端时&#xff0c;处理URL参数最常用的方式就是手动解析window.location.search。那时候经常要写这样的代码&#xff1a;function getParam(name) {const search window.location.search.substri…

作者头像 李华
网站建设 2026/7/14 5:27:08

多伦多大学ECE1508深度生成AI课程:VAE、GAN与扩散模型实战指南

这次我们来看多伦多大学 ECE1508 深度生成AI课程&#xff0c;这是2025年夏季学期的最新版本。作为计算机工程系的旗舰课程&#xff0c;它系统覆盖了从基础理论到前沿应用的完整知识体系&#xff0c;特别适合想要深入理解生成式AI技术原理和实践的开发者。课程最值得关注的特点是…

作者头像 李华
网站建设 2026/7/14 5:26:02

从CLI到Web:构建双模代码安全扫描工具的架构设计与实战

1. 项目概述与核心价值最近在折腾一个内部用的代码安全扫描工具&#xff0c;从最初只想着做个命令行工具&#xff08;CLI&#xff09;给开发自己跑跑&#xff0c;到后来业务方和测试团队都喊着要个能看报告的Web界面&#xff0c;整个过程踩了不少坑&#xff0c;也积累了不少关于…

作者头像 李华
网站建设 2026/7/14 5:25:58

3D高斯泼溅网页查看器:照片级真实感渲染技术解析

第一次在浏览器里拖入一个 3D 高斯模型文件&#xff0c;看到它瞬间渲染出照片级真实感的小熊时&#xff0c;我意识到这不再是一个“技术演示”&#xff0c;而是一个能真正改变 3D 内容查看方式的产品级工具。过去要在本地打开一个高质量的 3D 模型&#xff0c;往往需要安装几个…

作者头像 李华
网站建设 2026/7/14 5:22:58

Linux权限机制解析与实战操作指南

1. Linux权限机制的本质与设计哲学第一次接触Linux权限时&#xff0c;很多人会被那一串rwx字符搞得晕头转向。但如果你理解了Unix/Linux系统的设计哲学&#xff0c;就会发现这套机制的精妙之处——它用极简的规则实现了复杂的安全控制。Linux作为多用户系统的典范&#xff0c;其…

作者头像 李华