转换器插件

将文档转换为 24 种目标格式

Converter 插件将 Doconut 变成文档转换服务。它提供公共 DocumentConverter 外观背后的引擎,并且(可选)提供一个带有自身 HTTP 合约的即插即用小部件,因而可以通过 C#、小部件或自行编写的前端进行文档转换。

安装包

安装最新的稳定版 Converter 插件:

bash
dotnet add package Doconut.NET8.Converter

若要将插件固定在当前 26.7.0 版本,单独传入版本号:

bash
dotnet add package Doconut.NET8.Converter --version 26.7.0

保持 Converter 包的版本与 Doconut.NET8 相同。包标识为 Doconut.NET8.Converter.26.7.0 只出现在下载的 .nupkg 文件名中。

注册插件

没有 AddConverter() 方法——Doconut 的插件模型是统一的。每个插件(包括 Converter)都以相同方式注册:在 AddDoconut() 中调用 AddPlugin<TPlugin>()ConverterPlugin 随其自身的 NuGet 包 Doconut.NET8.Converter 提供,需与基础查看器包一起安装。

csharp
builder.Services.AddDoconut(options =>
{
    options.LicensePath = "Doconut.Viewer.lic";
    options.AddPlugin<Doconut.Plugins.Converter.ConverterPlugin>();
});

此调用在启动时会因缺少许可证、遗留的 TRIAL 文件,或不具备 Converter 能力的非临时许可证而抛出 InvalidOperationException,该异常在 AddDoconut() 内部触发,发生在应用开始提供请求之前。接受临时演示/非正式评估(Demo/NFR)注册;其日历到期后,转换仍可使用,只是输出会带有水印。没有静默的免费层。请参阅许可证设置了解许可证的加载方式。

从 C# 转换

每次转换都会返回一个定位在 0 的可搜索 MemoryStream,可立即读取或复制。随时从 DI 中解析 DocumentConverter——它本身是无状态的,因此单实例可安全复用于多个请求。

csharp
// Inject DocumentConverter; its constructor is internal, so never `new` it.
Stream pdf = await converter.ConvertAsync("contract.docx", ConversionTarget.Pdf, ct: ct);
csharp
// sourceExtension includes the leading dot. password is null unless the document is protected.
Stream png = await converter.ConvertAsync(upload, ".xlsx", ConversionTarget.Png, password: null, ct: ct);
csharp
Stream html = await converter.WordToHtmlAsync("report.docx", ct);
csharp
Stream docx = await converter.HtmlToWordAsync(html, ConversionTarget.Docx, ct);

两个容易出错的细节:流重载的 sourceExtension 必须包含前导点(".xlsx" 而不是 "xlsx")——转换器会将其与格式目录匹配,单独的扩展名无法解析。还有,尽管名字是 WordToHtmlAsync,它返回的是 Task<Stream>,而不是 Task<string>——你得到的是 HTML 文档(图片已嵌入为 Base64)作为流,和其他所有转换结果一样。

目标格式

text
Pdf, Docx, Doc, Html, Xlsx, Pptx, Png, Jpeg, Csv, Tiff, Bmp, Gif, Svg, Xml,
Txt, Xls, Jp2, Rtf, Odt, Ods, Odp, Epub, Xps, Webp

并非每种源格式都能转换为每个目标格式——插件会根据每种源的格式族(Word、Excel、PowerPoint、PDF、CAD、Image、Email、Diagram、Project/Task、PSD、web 文档)映射到其固定的允许目标集合。不要在 UI 中硬编码此枚举作为目标列表:?convert=open 会返回实际的 allowedTargets,应以此驱动选择器。

即插即用小部件

小部件的 ?convert=open|run|download 端点默认是可选且关闭的——默认安全。可在服务器端启用,同时完成插件注册:

csharp
builder.Services.AddDoconut(options =>
{
    options.LicensePath = "Doconut.Viewer.lic";
    options.AddPlugin<Doconut.Plugins.Converter.ConverterPlugin>();
    options.AddConverterWidget(widget =>
    {
        widget.MaxUploadMb = 25;
    });
});
html
<div id="doconut-convert"></div>
<script src="/doconut-res/js/doconutConverter.js"></script>
<script>
  Doconut.convert('#doconut-convert', { basePath: '/doconut', resPath: '/doconut-res', maxUploadMb: 25 });
</script>

如果未调用 AddConverterWidget(),三个 ?convert= 端点会返回 404——但 JS 文件本身仍会被提供(它是普通的嵌入式静态资源,只有它所调用的端点受限)。AddConverterWidget() 仍然要求先注册 Converter 插件并拥有授予 Converter 的许可证——它本身并不授予转换权限。

自定义小部件

传递给 Doconut.convert(selector, options) 的初始化选项:

OptionTypeDefaultNotes
basePathstring/doconut?convert= 端点的基础路径;必须与实际挂载 UseDoconut() 的 ASP.NET 分支相匹配(通常通过 MiddlewarePath 协调)
resPathstring/doconut-res为了与其他 Doconut 小部件的配置保持一致而保留;转换器小部件目前并不会基于它构建任何 URL
maxUploadMbnumber25仅客户端预检查——在上传前拒绝超大文件。服务器会独立强制自己的上限,若超出则返回 413
licenseUrlstring | nullnull设置后,会将结果页面的水印提示转为指向该 URL 的链接
labelsobject{}覆盖小部件的任意子集英文默认字符串(下拉文本、按钮、aria-live 提示、错误信息)

回调:

CallbackFires whenPayload
onReady()小部件渲染出空闲/下拉屏幕时
onSourceLoaded({ token, pages, sourceExt, allowedTargets })?convert=open 成功时源会话令牌、页数、源扩展名(无前导点)、允许的目标列表
onConverted({ downloadToken, resultToken, resultPages, downloadName, watermarked, target })?convert=run 成功时与运行响应相同的字段,另加请求的 target
onDownload({ downloadName, downloadToken })用户点击下载链接时与浏览器原生下载同时触发——不会拦截或替代它
onError({ phase, message })打开或运行请求失败时phase'open''run'message 为已清理的服务器错误(或上传大小预检查的客户端消息)

Doconut.convert() 返回小部件实例本身——保存该实例即可以编程方式控制小部件:

javascript
const conv = Doconut.convert('#doconut-convert', { basePath: '/doconut' });
conv.reset();         // 返回空闲/下拉屏幕;不会重新触发 onReady
conv.loadFile(file);  // 使用 File 对象启动流程;除非当前为空闲状态,否则不做任何操作
conv.destroy();       // 移除监听器,清空挂载点;实例在此之后不可再用

构建自己的前端

小部件本质上是此 HTTP 合约的客户端——直接基于它构建不同交互体验的前端。所有三条路由都位于挂载 UseDoconut() 的 ASP.NET 分支下(通常为 /doconut):

RoutePurposeSuccess response
POST ?convert=open (multipart, field file)上传并打开源文档以供预览200{ token, pages, sourceExt, allowedTargets }
POST ?token=<token>&convert=run&target=<ext>将已缓存的源转换为 target200{ downloadToken, resultToken, resultPages, downloadName, watermarked }
GET ?convert=download&token=<downloadToken>流式返回已转换的文件200 — 文件字节,Content-Disposition: attachmentCache-Control: no-store

上传的源字节会在服务器端缓存 30 分钟 TTL;该窗口过期后,run 会返回 404,必须重新打开文件。转换结果同样存放在缓存中——downloadToken 在转换完成后会获得新的 30 分钟窗口,而 resultToken 是普通的查看器会话令牌,其生命周期随查看器会话缓存而定,独立于缓存。

open 响应中的 sourceExt 不带前导点(例如 "docx")——这与 DocumentConverter.ConvertAsyncsourceExtension 参数相反,后者需要前导点。

失败模式(按路由分组)

RouteStatusWhenBody
any404小部件未启用(未调用 AddConverterWidget())——在任何路由分发前先检查仅状态码
any405使用了错误的 HTTP 方法(open/runPOSTdownloadGET仅状态码
open413上传文件超过 MaxUploadMb{ "error": "File is too large." }
open400没有 multipart body、没有文件,或源扩展名无法转换{ "error": "..." }
run400令牌格式错误(非 GUID),或 target 不能解析为 ConversionTarget{ "error": "Invalid token." } / { "error": "Unknown target format." }
run400target 不在源的 allowedTargets{ "error": "That target format is not available for this file." }
run404缓存的上传已过期(30 分钟 TTL)或令牌从未打开过{ "error": "Upload expired — please re-open the file." }
open, run500内部处理失败{ "error": "<sanitized message>" } — 与所有其他 Doconut 错误路径相同的清理方式;不会泄露内部引擎名称
download400令牌格式错误(非 GUID)仅状态码
download404未知或已过期的下载令牌仅状态码

资源所有权

转换器返回的 MemoryStream 可搜索且定位在零。调用方拥有该流,使用完毕后应在复制或返回内容后进行释放。DocumentConverter 服务本身是无状态的,通过依赖注入解析;不要手动实例化或释放该服务。

对于网页小部件,上传和下载缓存各自拥有 30 分钟 TTL。查看器的 resultToken 则遵循查看器会话的生命周期。关闭查看器结果不会删除仍然有效的下载缓存,重置浏览器小部件也不会延长任一 TTL。

故障排查

SymptomCheck
Resolving DocumentConverter failsConverterPlugin registration happened inside AddDoconut()
Application fails during startupThe loaded license grants Converter
Stream conversion says the format is unsupportedsourceExtension includes the leading dot
Widget JavaScript loads but requests return 404AddConverterWidget() was not called
Widget requests use the wrong URLbasePath matches the branch where UseDoconut() is mapped
Target is missingUse allowedTargets returned by convert=open; not every source supports every enum target
Download expiredRepeat convert=open/convert=run; stash tokens are intentionally temporary

水印

在注册 ConverterPlugin 后,宿主的许可证会处于以下三种状态之一:

License stateStartup gateConversion output
Paid viewer license granting Converter, within its validity periodPassesClean — watermarked: false
Active evaluation (demo/NFR) licensePassesConverts successfully, stamped with the evaluation watermark — watermarked: true
Unlicensed, a legacy TRIAL file, or a non-temporary license that doesn't grant ConverterApp never starts — the startup gate described above throws
Expired Temporary/Demo licenseRegistration survives expiryConverts with the evaluation watermark — watermarked: true

两条调用路径都使用相同的规则计算该标记:DocumentConverter 的 C# 外观内部依据许可证的 IsViewerLicensedIsTemporary 状态得出,而小部件的 ?convert=run 处理程序则进行等价检查(IsViewerLicensed && !IsTrial && !IsTemporary)以填充返回的 watermarked 字段。可以在评估许可证下完整构建并测试集成——唯一变化的只是输出字节。

此页面有帮助吗?