ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Domino开发指南:公式、LotusScript与Java代理的选型与实践

Domino开发指南:公式、LotusScript与Java代理的选型与实践

简介:《Domino开发指南精华》是一份面向Lotus Domino平台Java集成开发者的PDF技术指南,聚焦企业级协作应用中常见的数据库操作、文档管理、视图控制与代理自动化等核心技术。书中配有大量可运行代码示例,系统讲解如何通过lotus.domino包实现Notes数据库的增删改查、复杂文档字段处理、视图分类筛选与列设置,以及代理的定时自动化和后台运行机制;同时覆盖邮件发送与接收、富文本内容解析、URL头信息获取等典型集成场景,并针对企业环境中的异常处理与性能调优给出可落地的实践建议,帮助读者建立从API调用到业务应用的完整思路。全书既适合初次接触Domino的Java开发人员作为入门读物,也可供有经验者查阅企业级部署与功能扩展的关键细节。资源包内共1个PDF文件,大小3.82MB,文档结构完整,目录清晰。目前已有1936人学习下载,对需要构建高效协作系统、深挖Domino集成能力的开发者而言,是一份值得收藏的参考资料。

1. Domino开发指南:这套老平台还值得投入吗

如果你接到一个任务——在一套看起来像上个世纪的系统上做开发,别急着皱眉。Domino(从 Lotus Notes 一路演变过来的那套协作平台)至今仍跑在银行、政企、制造企业的审批流程和文档管理里,而且不只跑着老代码,还在支撑新业务。它最大的特点是开发路径完整且灵活:表单、视图、页面这类“界面件”用鼠标拖出来就能用,逻辑层再按场景选公式、LotusScript 或 Java。这篇指南写给两类人:一类是刚接手老 Domino 应用、要从零读懂开发脉络的新人;另一类是打算把 Notes 客户端业务迁移到 Web、需要评估工作量的老手。我把做真实项目时最常用的开发套路、参数取舍和踩坑记录都拆在下面,你可以直接照着搭一套最小可用的环境。

2. 三种开发范式怎么选:公式、LotusScript 与 Java 的边界

2.1 公式语言:最轻的校验、默认值与视图逻辑

Domino 里最老的执行语言不是脚本,而是公式。公式可以写进表单的校验规则、字段的默认值、视图的列值、按钮的 Click 事件和部分代理里。它最大的优点是免编译、执行快、全库通用;最大的缺点是表达能力弱,写不出循环和复杂的状态机。在表单校验里,公式是最常用的一层防线。比如我要求“客户名称”字段必填,就在表单的校验事件里写:

@If(@Trim(客户名称) = ""; @Failure("客户名称不能为空"); @Success)

这行公式的意思是:把字段内容去掉首尾空格后判断是否为空,为空就弹出错误信息并阻止保存。参数@Failure和@Success是公式的成对结果,前者带出错误提示文本,后者表示校验通过。公式还有一个高频用途是写视图的“选择条件”和“分类列”,这个到第 4 章具体展开。给新手的建议是:凡是“单条文档、一次判断、一个返回值”的场景,优先用公式;凡是超过三步的逻辑,就换 LotusScript 或 Java,否则后期维护的人会骂娘。

2.2 LotusScript:事件代理与批量处理的舒适区

LotusScript 是 Domino 里的 BASIC 方言,语法和 VBA 非常接近,凡是维护过 Excel 宏的人上手都很快。它的编程模型围绕几个核心类展开:NotesSession代表当前会话,NotesDatabase代表库,NotesDocument代表单篇文档,NotesView代表视图。写一个后台代理遍历某视图中所有文档,是 LotusScript 最常见的入门练习:

Dim session As New NotesSession Dim db As NotesDatabase Dim view As NotesView Dim doc As NotesDocument Set db = session.CurrentDatabase Set view = db.GetView("客户登记") Set doc = view.GetFirstDocument Do While Not (doc Is Nothing) ' 这里可以做字段读取或修改 MsgBox doc.GetItemValue("客户名称")(0) Set doc = view.GetNextDocument(doc) Loop

这段代码的逻辑是:先取当前库,再按名字取视图,然后从视图第一份文档开始循环,GetNextDocument负责逐步向后移动,直到末尾返回Nothing。参数上要注意GetItemValue返回的是数组,即使字段是单值也要用(0)取第一个元素。GetView的名字必须和视图名称完全一致,多一个空格就取不到。LotusScript 适合写定时批量业务,比如每日归档、到期提醒、数据补齐;但它处理外部系统交互比较吃力,虽然有HTTPRequest类,写起来远不如 Java 顺手。

2.3 Java 代理:面向外部系统与 Web 服务的正路

当业务需要从 Domino 里调外部 REST 接口,或者反过来把 Domino 的数据推给别的系统,Java 是更稳定的选择。Domino 的 Java 代理不是普通 Java 类,它要继承AgentBase并实现NotesMain方法,通过getSession()拿到会话对象。一个最简的 Java 代理框架长这样:

import lotus.domino.*; public class QueryExternal extends AgentBase { public void NotesMain() { try { Session session = getSession(); Database db = session.getCurrentDatabase(); // 这里写读取文档或调用外部接口的逻辑 System.out.println("代理运行完成"); } catch (Exception e) { e.printStackTrace(); } } }

getSession()是 AgentBase 提供的入口,不需要自己NotesFactory.createSession,这是它和独立 Java 程序最大的区别。这段代码里没有写具体业务,但框架是固定的:所有 Domino 后台对象都从 Session 出发去拿。Java 代理的优势是能直接用标准库里的java.net.HttpURLConnection发请求,缺点是要在 Designer 里编译,出错后调试不如 LotusScript 直观。如果一个代理既要操作 Domino 文档又要对接外部系统,我会选 Java;如果只是内部文档加工,LotusScript 更快。

2.4 选型判断:三张表定场景

三种语言不是越新越好,而是各管一摊。我做过一个项目,表单校验用公式,定时归档用 LotusScript,对接企业微信接口用 Java 代理,三种语言在一个库里共存,运行得很稳。下面这张表是我选型时对照用的:

语言运行位置适合场景不适合场景
公式服务器或客户端字段默认值、校验、视图列、简单按钮循环、文件操作、外部接口
LotusScript服务器或客户端文档批处理、定时代理、复杂 UI 事件调用 Java 生态类库、高并发 HTTP
Java服务器外部接口对接、复杂算法、Web 服务快速改界面逻辑、表单内轻量校验

选择标准其实就一句话:看这段逻辑的数据从哪里来、要到哪里去。数据只在本库流转,用 LotusScript 或公式;要出库对接外部,用 Java。这三种写法在同一个代理里还能混合——LotusScript 里可以通过JavaSession调 Java 类,Java 里反过来也能执行公式,但混合用会显著提高排查成本,非必要别这么干。

3. 从装环境到跑通第一个应用:最小可用的开发闭环

3.1 开发环境搭建:ID、服务器连接与那个“玄学”

Domino 开发的第一步不是写代码,而是把开发环境和服务器连起来。常见做法是:服务器上由管理员在 Domino Administrator 里创建开发者的 ID 文件(.id),并把 ID 注册到公共通讯录names.nsf里;开发者本机安装 Domino Designer,启动时指定这个 ID 文件,就能连接服务器。这里有个坑:Designer 连接服务器时填的服务器名要是 Domino 目录名,比如“DEV01/Corp”,不是 IP 也不是机器名。很多人第一次连不上,就是因为把服务器名字填成了 Windows 主机名。连接后先检查 ACL 权限——自己在数据库的 ACL 里至少要读到Designer权限,否则能打开设计元素却保存不了。ID 文件和密码是 Domino 的信任根,丢失了只能找管理员重置,这块没什么技巧,靠流程。

3.2 用 NTF 模板建库:一个模板体系的意义

在 Designer 里新建数据库时,会看到一堆.ntf模板文件,最常用的是local.ntf和mail98.ntf。.ntf是模板,.nsf是实例。基于模板建库后,模板的窗体、视图、代理会被复制进新库;后续模板升级时,可以按“继承模板”的习惯把改动推送到库。实际项目中,我一般会在服务器上做好一个企业标准模板,再基于它派生业务库。这样做的好处是公共字段、统一皮肤、公共函数库都集中在模板里,新库不用从零搭。初始化选模板时注意别选成空的local.ntf,它只包含最基础的系统元素,很多人建完库发现连视图都没有,就是因为选错了模板。

3.3 最小应用:客户信息登记表单

理论说多了没用,我习惯先把一个最小业务跑起来。下面几步就是一套可复现的“客户登记”流程:

第一步,在 Designer 里新建数据库,选择模板local.ntf,命名customer.nsf。第二步,创建一个表单,命名为“客户登记”,添加三个字段:客户名称(文本)、联系方式(文本)、登记日期(日期)。第三步,把“登记日期”的默认值设为公式@Today,这样打开新文档时日期自动填当天。第四步,在表单上放一个按钮,点击事件的公式写成@Command([FileSave]),给用户一个明确的保存入口。第五步,创建视图“客户登记”,视图选择条件写:

SELECT Form = "客户登记"

这个公式从当前库里筛选出所有Form字段等于“客户登记”的文档,注意等号右边的文本要用英文引号包住,值的大小写必须和表单名完全一致。设好这五步,在 Notes 客户端里新建文档、填写、保存,然后回到视图刷新,就能看到刚录入的文档。

3.4 发布与测试:先本地后服务器

开发机上跑通只是第一步,发布到测试服务器要检查三件事:数据库 ACL 是否给测试账号开了Author以上权限;代理的“运行权限”是否指定了可执行者;数据库属性里是否勾选了“允许使用存储在数据库中的代理”。我有一个习惯:在 Designer 里直接右键数据库选“新建副本”,把副本放到服务器,然后在服务器上打开测试。这样能确保不把本地调试的残留数据带到测试环境。发布后第一件事是打开服务器控制台看日志,Domino 控制台是黑匣子也是后悔药,很多运行期错误只在控制台打印。

4. 写文档、刷视图与跑代理:核心对象的落地操作

4.1 用 LotusScript 新增与更新文档:必备类与方法

在 Domino 里写程序,绕不开“创建文档-写入字段-保存”这组动作。下面这段代码是一个按钮的 Click 事件,在“客户登记”表单里新增一条记录:

Dim session As New NotesSession Dim db As NotesDatabase Dim doc As NotesDocument Set db = session.CurrentDatabase Set doc = db.CreateDocument doc.Form = "客户登记" doc.客户名称 = InputBox("请输入客户名称") doc.联系方式 = InputBox("请输入联系方式") doc.登记日期 = Today Call doc.Save(True, False)

这段代码的要点有三个。第一,doc.Form = "客户登记"是文档和表单绑定的关键,Domino 的文档本身不存“格式”,只存Form字段,显示时靠这个名字去匹配表单。第二,Today是 LotusScript 的日期关键字,直接赋给日期字段就行,不需要转字符串。第三,Save(True, False)的两个布尔参数——第一个True表示强制保存,即使文档已被别人修改也覆盖;第二个False表示不保留为冲突文档。实战中我几乎总是用Save(True, False),但如果业务要求多人在同一份文档上协作,把第二个参数改成True,系统会留下冲突副本供人工合并。新手常犯的错误是把Save写成Update,那是 NotesView 的方法,用在这会直接编译报错。

4.2 视图的 SELECT 公式与分类汇总

视图是 Domino 应用的门面,所有列表都要靠它输出。视图的列值可以用字段名、公式或 @Function 计算。最典型的分类视图是按部门汇总:在“部门”列点右键设“分类”,列值公式写成部门,然后后续列写金额,视图底部会自动出现小计。分类视图还有一个用法是“嵌套分类”,但嵌套层级越多,索引越慢,我一般最多两层。视图选择公式也可以写得复杂一些,比如筛掉已删除标记的文档:

SELECT Form = "客户登记" & @IsAvailable(客户名称)

@IsAvailable(客户名称)的意思是这个字段存在才算数,防止有人建了半截文档。视图刷新机制值得说一下:视图的索引默认不是实时的,服务器上可以通过“视图属性-高级”里设刷新频率,有“自动刷新”“手动刷新”“定时间隔”。对用户来说,手动刷新意味着在 Web 上要点浏览器的刷新按钮,Notes 客户端里按 F9;对开发者来说,调视图性能时优先看这里的索引设置,别一上来就加索引列。

4.3 代理的三种触发方式与运行权限

代理是 Domino 的“后台任务”,按触发方式分成三类:手动代理(用户点按钮运行)、定时代理(服务器到点跑)、Web 代理(通过 URL 触发)。它们的代码主体一样,区别在代理属性里的“触发”和“目标”。下面是一个定时归档代理的骨架:

Sub Initialize Dim session As New NotesSession Dim db As NotesDatabase Dim doc As NotesDocument Dim view As NotesView Set db = session.CurrentDatabase Set view = db.GetView("客户登记") Set doc = view.GetFirstDocument While Not (doc Is Nothing) If doc.登记日期(0) < Today - 365 Then Call doc.PutInFolder("归档\过期客户") End If Set doc = view.GetNextDocument(doc) Wend End Sub

PutInFolder不会把文档移走,只会建一份指向它的视图条目,所以源文档还在原位置。真正要删除文档是doc.Remove(True),这个操作不可逆,我建议删除前先把文档 ID 写进日志库。定时代理的运行权限是另一个高频坑:在代理属性里要明确选“以调用者身份运行”还是“以代理所有者身份运行”。归档这类操作建议选“代理所有者”,因为发起人可能没有删除权限;而 Web 代理如果选所有者运行,被调用时会有权限漏洞,应当在设计阶段就定好。

4.4 用 Java 写一个无界面批处理任务

在需要在服务器上做批量数据清洗的场景里,Java 代理的可维护性比 LotusScript 更好,因为逻辑可以用标准库做单元测试。我用 Java 代理写过一套数据补全任务:读取“客户登记”视图里所有文档,检查联系方式字段是否为空,为空就调外部接口补齐。代码骨架如下:

import lotus.domino.*; public class FillContact extends AgentBase { public void NotesMain() { try { Session session = getSession(); Database db = session.getCurrentDatabase(); View view = db.getView("客户登记"); Document doc = view.getFirstDocument(); while (doc != null) { String phone = doc.getItemValueString("联系方式"); if (phone.isEmpty()) { doc.replaceItemValue("联系方式", "000-0000-0000"); doc.save(true, false); } Document next = view.getNextDocument(doc); doc.recycle(); doc = next; } } catch (Exception e) { e.printStackTrace(); } } }

在这里,getItemValueString适合拿单值字段,比getItemValue直接返回字符串省去数组转换。doc.recycle()是 Java 代理的关键——Java 对象持有的是原生句柄,不及时回收会耗尽内存,很多代理跑着跑着就戳在那不动,多数是忘了recycle。replaceItemValue在字段不存在时会新建字段,存在时会覆盖,行为符合直觉。这茬写出来是想说:Java 代理虽好,内存管理比 LotusScript 严格得多,写完务必自查每个Document对象是否都调了recycle。

5. 常见开发翻车点与排查:五个高频问题实录

5.1 保存时提示“文档已被修改”,或者同事的修改被覆盖

现象:两个人同时打开同一份文档,后保存的人弹出冲突提示;如果用了Save(True, False)强制保存,先保存的人改的内容丢失。

原因:Domino 的并发控制是文档级的,它不知道你改了哪个字段,只能比较整份文档的修改标记。后保存方的Save(True, ...)会直接用新版本覆盖旧版本。

解决:给表单加一个隐藏字段$ConflictAction,值为“合并/新建冲突副本”;更稳妥的做法是在代码里先读doc.LastModified,和表单里的_lastMod比较,不一致就提示用户重新加载。实际项目里我会用第二种,把 LastModified 转成数字比较,逻辑直观。

5.2 视图里看不到刚提交的文档

现象:表单里保存成功,视图却不显示新文档。

原因:一是视图选择公式的条件不满足,最常见是Form字段大小写不一致,比如表单名叫“客户登记”,新文档的Form值被代码设成“客户登记 ”带了空格;二是视图索引刷新间隔太长,定时刷新还没到;三是文档被存进了别的文件夹分类,视图只看某个分类。

解决:先在文档属性里看Form字段的实际值,把它复制出来对比视图公式。然后到视图属性里把刷新方式临时改成“自动刷新”,确认不是索引问题。最后查“文件夹”字段,确认文档没有被误放进被隐藏的分类。排查顺序从数据到界面,别一上来就重建索引。

5.3 代理运行失败,但看不出任何错误日志

现象:定时代理到点没跑,控制台没输出;手动点按钮也没反应。

原因:代理的运行权限没配好,或者服务器禁用了代理运行。Domino 服务器有个总开关:“禁止运行数据库代理”,很多管理员在安全加固时顺手关掉,导致所有代理静默失败。

解决:到服务器配置里确认“允许运行存储的代理”是启用状态;再到代理属性里指定“以调用者身份运行”;最后在代理里加一行Print "代理开始"和一个On Error捕获,打日志到控制台或写进日志文档。没有日志的代理就是黑匣子,加日志是第一步。

5.4 中文内容在浏览器里显示为乱码

现象:Web 页面上的中文变成问号或乱得完全不可读,Notes 客户端里却正常。

原因:Domino 的 Web 引擎默认字符集和前端页面声明的编码不一致。常见的是服务器跑在 Linux 上,locale 没有设置为中文;或者表单的 HTML 属性里没写charset=UTF-8。

解决:在服务器 notes.ini 里加一行SiteLocale=zh_CN并重启;在表单的“HTML Head 内容”里加<meta charset="utf-8">。如果是 Java 代理输出内容乱码,把getItemValueString的返回值用 UTF-8 再编码一次。这套组合拳能覆盖九成乱码场景。

5.5 数据库损坏:Fixup、Compact 与备份的取舍

现象:视图打开极慢或直接报“数据库结构无效”,服务器控制台提示 NSF 损坏。

原因:非正常断电、磁盘写满、或者用compact时参数选错导致重建中止。

解决:先停库,用服务器控制台命令fixup customer.nsf修复不一致的文档;还打不开再用compact customer.nsf -B做后台压缩。这两条命令不是万能的,fixup会删掉损坏的文档视图项,所以跑之前必须备份。我吃过一次亏:跑了compact忘了备份,结果一张表的富文本全部丢失,再也不敢跳过备份步骤。Domino 没有后悔药,备份是唯一的后悔药。

6. 批量导入数据:把外围系统的 CSV 灌进 Domino

最后一个技巧是批量导入,这在实际项目里出现频率极高——老系统迁移、Excel 台账转 Domino、第三方系统历史数据兜底,都绕不开。LotusScript 里直接用文件操作函数做导入,不依赖外部组件。下面这段读取 CSV 并创建文档的代码,我反复用过很多次:

Dim session As New NotesSession Dim db As NotesDatabase Dim doc As NotesDocument Dim fileNum As Integer Dim line As String Dim fields() As String Set db = session.CurrentDatabase fileNum = Freefile Open "C:\data\customer.csv" For Input As fileNum Do While Not EOF(fileNum) Line Input #fileNum, line fields = Split(line, ",") If fields(0) <> "客户名称" Then Set doc = db.CreateDocument doc.Form = "客户登记" doc.客户名称 = fields(0) doc.联系方式 = fields(1) Call doc.Save(True, False) End If Loop Close fileNum

Split(line, ",")是按逗号切分字符串,这个函数比字符串截取可靠得多。首行判断fields(0) <> "客户名称"是为了跳过表头,这种硬编码表头的方式在格式稳定时没问题,但遇到列顺序调整就会出错。更稳的方式是先读第一行,解析出列名和字段的映射关系,然后按映射逐行写。如果 CSV 文件特别大(几万行以上),逐行Save会很慢,可以把文档先放进NotesDocumentCollection,攒一批再统一处理,或者干脆用NotesStream按缓冲读。导入完务必抽查文档数,用视图的“总计”功能和源文件行数对比,差一行都要查原因——我遇到过一次字段里带逗号导致Split出错,从那以后都在导入前先统一转义分隔符。

另外要提醒一点:导入的文档如果有富文本域,用doc.RTItem.AppendText往富文本里塞文本,别直接赋字符串值,不然 Web 上打开富文本会丢格式。这个坑让我在迁移项目里多花了两天返工。多年做 Domino 开发,我最深的体会是:这个平台不新,但设计哲学很实在——一切数据都是文档,一切操作都有对象。顺着这套思路把基础类和常用参数摸透,开发效率并不比别的平台低,而且还能稳稳支撑生产系统。希望这篇指南能帮你少走一段我当时走过的弯路。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进