尧图精选

OLE Automation 实战:VFP 通过 RichText 格式向 Lotus Notes 发送邮件时如何把 OLE 端点改到 TaoToken

🕒 发布时间:2026/10/1 6:55:00 📁 来源:尧图网络
1. VFP 老系统发 Notes 邮件RTF 正文为什么变成一串花括号如果你维护过 VFP6/VFP9 的老系统大概率见过这个场景业务单据生成后要自动发邮件收件端是 Lotus Notes正文用的是 RichText 控件richtext.richtextctrl.1里存进 memo 字段的 RTF 字符串。代码跑起来没报错邮件也发出去了但收件人打开一看正文是一坨{\rtf1\ansi\deff0{\fonttbl...的原始标记格式全丢。这个问题的本质不是 VFP 的锅也不是 Notes 的锅而是MIME 内容类型和 Notes 内部转换开关没对齐。你从 RichText 控件拿到的是 RTF 源码它是一段带控制字的纯文本而 Notes 的CreateMIMEEntity()需要你明确告诉它「这段文本是什么类型」同时Session.ConvertMime这个开关决定了 Notes 在实例化 NotesItem 时要不要把 MIME 部件转成富文本。两者只要有一个不对RTF 就会以字面量形式显示出来。我先把结论摆出来方便你对号入座SetContentFromText(Stream, Text/RTF;charsetUTF-8, .F.)里的类型串必须是text/rtf大小写不敏感但分号前的类型要写对写成Text/RTF在部分 Notes 版本上能识别但更稳妥的是小写。Session.ConvertMime在写入 MIME 之前应该是.F.不转换在发送之前再设成.T.让 Notes 在渲染时把 MIME 转成富文本。你 excerpt 里的代码把ConvertMime.F.放在最前面、发送前又设.T.顺序其实是对的问题往往出在类型串和 Stream 的写入方式上。Stream.WriteText(lcbody)默认按平台编码写如果 RTF 里有中文charset 声明和实际字节不一致也会导致解析失败退化成纯文本。这篇就围绕「VFP 通过 OLE Automation 把 RichText 正文投递到 Lotus Notes」这条链路把 OLE 端点配置、RTF 类型声明、发送验证和常见报错一次讲清楚。适合还在维护 VFP 老系统、需要对接 Notes 邮件通道的开发者。整个思路是保留你原有的 VFP 代码结构只改 OLE 连接配置和几个关键参数不动业务逻辑。顺带说一句现在很多团队会把这类老系统的外部依赖逐步收敛到统一的 API 网关上比如模型调用、邮件通道这类外部服务用 TaoToken 这类平台做端点统一管理VFP 侧只需要改 Base URL 和 Key不用动 OLE 调用结构。下面会结合这个思路给出可复制的配置片段。2. 前置准备OLE 端点、Notes 会话与 RichText 数据源在动手改代码之前先把三个东西确认清楚否则后面排错会很难定位。2.1 确认 RichText 控件写入的确实是 RTFVFP 里用richtext.richtextctrl.1时取正文有两种方式.TextRTF拿到的是带格式的 RTF 源码.Text拿到的是纯文本。你要发富文本邮件必须用.TextRTF。可以先用一句调试代码确认* 确认 memo 字段里存的是 RTF 源码 lcBody Temp.whatvalue ? LEFT(lcBody, 60) * 正常应该看到 {\rtf1\ansi... 开头 * 如果看到的是纯文字说明写入时用错了属性如果这里输出的不是{\rtf1开头那后面怎么调 Notes 都没用先回去检查 RichText 控件的赋值语句。2.2 Notes 会话初始化与 OLE 端点CreateObject(Lotus.NotesSession)这一步依赖本机安装的 Notes 客户端和注册表里的 OLE 注册信息。老系统常见的问题是 Notes 版本升级后 ProgID 变了或者 32/64 位不匹配。VFP6 是 32 位必须配 32 位 Notes 客户端。* 初始化 Notes 会话 Session CreateObject(Lotus.NotesSession) Session.Initialize() 会读取 Notes.INI 里的当前 ID可能弹密码框 Session.ConvertMime .F. 写入阶段先关闭转换Session.Initialize()不带参数时会用 Notes.INI 里配置的 ID 文件如果那台机器上配了多个 ID会弹密码框自动化场景下要提前把 ID 配好或者传入密码参数不推荐硬编码用配置文件读。2.3 把外部端点收敛到统一网关如果你的 VFP 系统除了 Notes 还要调别的外部服务比如模型接口、短信网关建议把端点统一到一个可配置的网关地址而不是散落在代码各处。TaoToken 的 API 入口是https://taotoken.net/api控制台和 Key 管理在 console接入文档在 doc。VFP 侧只需要把 Base URL 和 Key 抽成配置项OLE 调用结构完全不用改。这一步的意义在于以后换邮件通道、换模型供应商只改配置不改代码。对老系统维护来说这是性价比最高的改造。3. 可复制配置RTF 正文投递的完整 OLE 片段这一节给出可以直接粘进 VFP 的代码重点标出和 excerpt 里不同的地方。核心改动有三处MIME 类型串、Stream 写入方式、ConvertMime 的时序。3.1 端点与连接配置片段先把配置抽出来放在一个 PRG 或表里方便切换环境* config_mail.prg * 邮件通道配置切换环境只改这里 PROCEDURE GetMailConfig LOCAL loCfg loCfg CREATEOBJECT(Empty) ADDPROPERTY(loCfg, NotesServer, ) ADDPROPERTY(loCfg, MailDbName, ) ADDPROPERTY(loCfg, ApiBaseUrl, https://taotoken.net/api) ADDPROPERTY(loCfg, ApiKey, ) ADDPROPERTY(loCfg, ModelId, gpt-4o-mini) RETURN loCfg ENDPROC如果你用 JSON 或 TOML 管理配置可以写成这样路径和字段名保持一致方便脚本读取{ mail: { notes_server: , mail_db_name: , mime_type: text/rtf, charset: UTF-8 }, gateway: { base_url: https://taotoken.net/api, api_key: , model_id: gpt-4o-mini } }注意mime_type写text/rtf不要写Text/RTF虽然多数版本能识别但小写更稳。charset用UTF-8前提是你的 RTF 字符串确实是 UTF-8 编码的字节流。3.2 发送 RTF 正文的核心代码下面是改好的发送逻辑和 excerpt 的差异我用注释标出来了* 发送 RTF 正文到 Notes LOCAL Maildb, MailDoc, Session, lcBody, Server, MaildbName LOCAL Stream, BodyMIME, RichTextItem, attached lcBody Temp.whatvalue 来自 RichText 控件的 .TextRTF attached LOCFILE(, DOC, Select File) * 1. 建立会话 Session CreateObject(Lotus.NotesSession) Session.Initialize() Session.ConvertMime .F. 写入阶段不转换 * 2. 打开邮件库 Server Session.GetEnvironmentString(MailServer, .T.) MaildbName Session.GetEnvironmentString(MailFile, .T.) Maildb Session.GetDatabase(Server, MaildbName) * 3. 创建邮件文档 MailDoc Maildb.CreateDocument MailDoc.ReplaceItemValue(Form, Memo) MailDoc.ReplaceItemValue(SendTo, exampleexample.com) MailDoc.ReplaceItemValue(Subject, Subject Text) * 4. 写入 RTF 正文 —— 关键改动 Stream Session.CreateStream() Stream.WriteText(lcBody) 写入 RTF 源码 BodyMIME MailDoc.CreateMIMEEntity() * 类型串用小写 text/rtf第三个参数 .F. 表示不立即转换 BodyMIME.SetContentFromText(Stream, text/rtf;charsetUTF-8, .F.) * 5. 附件可选 IF !EMPTY(attached) RichTextItem MailDoc.CreateRichTextItem(Attachment) RichTextItem.EmbedObject(1454, , attached, Attachment) ENDIF * 6. 发送前打开转换开关 Session.ConvertMime .T. MailDoc.SaveMessageOnSend .T. MailDoc.ReplaceItemValue(PostedDate, DATETIME()) MailDoc.Send(.F.)和 excerpt 相比改动点SetContentFromText的类型串从Text/RTF改成text/rtf。明确Stream.WriteText写入的是 RTF 源码不要先做ALLTRIMRTF 首尾的空格和换行有时是格式的一部分。ConvertMime在写入前.F.、发送前.T.顺序不要颠倒。3.3 如果走统一网关端点怎么改如果你把邮件通道也收敛到网关VFP 侧不再直接CreateObject(Lotus.NotesSession)而是通过 HTTP 调网关接口。VFP 可以用MSXML2.XMLHTTP或WinHttp.WinHttpRequest.5.1* 通过网关发送端点可配置 LOCAL loHttp, lcUrl, lcPayload loHttp CREATEOBJECT(WinHttp.WinHttpRequest.5.1) lcUrl https://taotoken.net/api/v1/mail/send loHttp.Open(POST, lcUrl, .F.) loHttp.SetRequestHeader(Content-Type, application/json) loHttp.SetRequestHeader(Authorization, Bearer lcApiKey) lcPayload {to:exampleexample.com,subject:Subject Text,body_rtf: ; STRTRAN(lcBody, , \) } loHttp.Send(lcPayload) ? loHttp.Status, loHttp.ResponseText这样端点、Key、模型 ID 都在配置里换环境只改 JSON。模型相关的调用可以走 模型对话长期编码和 Agent 场景可以看 Coding Plan。4. 验证请求发送成功与失败两种动作改完代码不能直接上生产先做两个验证动作。4.1 成功验证正文格式正确渲染发送后打开 Notes 收件箱检查三件事正文是否显示为带格式的文本加粗、颜色、字号生效而不是{\rtf1...源码。附件是否正常挂载文件名是否正确。发件箱Sent里是否有一条记录PostedDate是否写入。如果正文格式正确说明 MIME 类型和 ConvertMime 时序都对上了。可以在 VFP 里加一句日志把Stream的字节长度和BodyMIME的 ContentType 打出来* 验证 MIME 类型是否设置成功 ? ContentType:, BodyMIME.ContentType ? Stream Size:, Stream.Bytes正常应该看到text/rtf和大于 0 的字节数。4.2 失败验证RTF 源码裸露如果收件人看到的还是{\rtf1\ansi...按这个顺序排查检查SetContentFromText的类型串是不是写成了text/plain或text/html。检查Session.ConvertMime在发送前是否设成了.T.。检查Stream.WriteText写入的内容是不是被ALLTRIM或STRTRAN破坏过。检查 Notes 客户端版本6.5 和 8 以上对 MIME 的处理有差异老版本对text/rtf支持不完整可以试text/html包一层。失败验证的价值在于你能通过现象反推是哪一层出了问题而不是盲目改代码。5. 常见报错排查401、local proxy failed、reading choices、OAuth这一节对照真实报错给出定位思路。注意这些报错大多出现在走网关或 API 的场景纯 OLE 直连 Notes 时不一定遇到但如果你把通道收敛到网关就会碰到。5.1 401 UnauthorizedHTTP 401 Unauthorized {error:invalid api key}原因Key 没传、传错、或者带了多余空格。检查Authorization头是不是Bearer加 Key中间一个空格。VFP 里字符串拼接容易多空格用ALLTRIM处理。loHttp.SetRequestHeader(Authorization, Bearer ALLTRIM(lcApiKey))5.2 local proxy failedlocal proxy failed: dial tcp 127.0.0.1:7890: connect: connection refused原因本机配了代理但代理没启动。VFP 的WinHttp默认会读系统代理设置。解决办法是在代码里显式禁用代理或者确保代理服务在跑。注意这里说的是本机网络配置问题不涉及任何绕过网络管理的手段企业环境请按 IT 规范配置。* 显式不走代理 loHttp.SetProxy(0)5.3 reading choices 相关报错error reading choices: unexpected end of JSON input原因请求体 JSON 不完整或者响应被截断。VFP 拼接 JSON 时如果 RTF 正文里有未转义的双引号会导致 JSON 解析失败。用STRTRAN把转成\把换行转成\n。lcBodyEscaped STRTRAN(lcBody, , \) lcBodyEscaped STRTRAN(lcBodyEscaped, CHR(13), \n) lcBodyEscaped STRTRAN(lcBodyEscaped, CHR(10), \n)5.4 OAuth 相关报错OAuth token expired or invalid_grant原因如果网关用的是 OAuth 而非静态 Keytoken 过期后会报这个。检查 token 刷新逻辑或者改用长期有效的 API Key。TaoToken 的 Key 管理在 API Keys可以生成和管理。5.5 三件套配置检查如果你用 Cline MCP、CC Switch 或 Codex 的auth.json出现连接问题时先检查三件套是否齐全配置项示例值说明Base URLhttps://taotoken.net/api不带 UTM纯 API 入口API Keysk-xxxx从控制台生成Model IDgpt-4o-mini按需选择auth.json里对应字段{ base_url: https://taotoken.net/api, api_key: sk-xxxx, model: gpt-4o-mini }三件套缺一个都会连不上报错信息各不相同但根因都是配置不全。6. 把通道切换做扎实老系统集成的几个实用习惯VFP 老系统集成外部服务最怕的不是写不出代码而是出了问题定位不到。我自己的习惯是第一所有外部端点抽成配置代码里不出现硬编码的 URL 和 Key。切换环境、换供应商只改配置。第二每个外部调用都加日志记录请求参数、响应状态、耗时。VFP 可以用STRTOFILE写文本日志简单够用。第三先做最小验证再上生产。比如先发一封纯文本邮件确认通道通再发 RTF 确认格式对最后加附件。每一步都验证出问题范围小。第四保留原有代码结构。老系统的业务逻辑是资产改造时只动集成层不动业务层。OLE 调用封装成一个 PRG业务代码只调接口。回到这篇的主题RTF 正文变源码这个问题核心就是 MIME 类型串和 ConvertMime 时序。把text/rtf写对把转换开关的时机放对问题基本就解决了。如果还要接更多外部服务把端点收敛到统一网关VFP 侧只改配置这是老系统能长期维护下去的关键。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →