
SpringBoot Vue3 AI从一句需求到可审批的业务单据中间还缺哪些能力本文由 RuoYi Office 团队整理结合实际项目分享企业软件的设计思路与开发经验。给企业做系统AI 生成一张表单只是开头。费用算得对不对数据存到哪里提交失败后能不能重试这些才决定它能否交给员工使用。我们拿差旅单据举例把生成之后要补的几步拆开讲。▲ AI 对话负责起草真正落地还需要字段契约、数据存储和审批关联。封面为系统截图素材合成。一、先明确让 AI 生成什么不要一句话包办整个系统“帮我生成一张差旅报销单”模型通常能很快给出申请人、部门、日期、金额和附件。放在演示里已经挺像那么回事。但开发者还得追问这张单只是收集材料还是要占用预算、核销借款最后推送付款这几种需求的页面可能很像背后的处理完全不同。我们建议先把目标定小让 AI 生成一份可以继续编辑的表单草案再把确定的业务规则接进去。本文讨论的是从草案到可提交、可审批的技术路径不把“审批通过”延伸成“财务已经付款”。在 RuoYi Office 的这套接入里AI 输出的是 FormCreate Rule也就是描述组件、字段和校验方式的规则设计器接收规则后呈现页面。它不是让模型随意写一段 HTML更不是直接让模型执行建表语句。本文使用的智能助理入口来自 FormCreate Pro相关商业组件仍需按其许可使用。▲ 已有系统中的需求输入界面。输入内容区分了主表和费用明细这张截图展示的是生成前的输入状态。可以把需求再说具体一点生成差旅报销表单草案。主表包含申请人、部门、出差起止日期和事由费用明细包含发生日期、类别、说明和金额。申请人与部门使用系统选择器金额保留两位小数。先生成结构不添加付款和自动审批逻辑。这不是为了把提示词写得很长而是减少模型需要猜的部分。哪些字段是人员、哪些是一组明细、哪些动作不在本次范围内最好提前说清楚。生成后先验结构再谈功能。比如费用明细应该是可增行的tableForm不能变成主表上四个孤立输入框。发现错了先修这一步否则后面的存储和计算都要跟着返工。二、字段看着对还要确认它传出去的值是什么表单里的中文标题是给人看的字段标识和取值才是系统之间的约定。举个例子“申请人”显示张三提交时应该传人员 ID还是姓名如果只存姓名遇到重名、改名和跨部门调动审批和查询就很难稳定关联。部门也一样页面能选中“研发部”不代表它一定提交了正确的部门 ID。对这张示例单据我们会先列一张很短的字段契约内容建议提交的值需要核对的事情申请人、部门系统中的 ID是否允许代填人员和部门是否匹配出差日期明确格式的日期字符串起止顺序、是否把纯日期误当时间戳费用明细对象数组每行字段一致不能只传一段说明报销总额服务端核算结果不直接采信浏览器传来的合计下面是一个简化的 FormCreate 金额字段示例用来说明规则与取值的关系不是完整报销单{type:inputNumber,field:expenseAmount,title:费用金额,$required:true,props:{precision:2,min:0,placeholder:请输入本行费用金额}}这里的expenseAmount以后可能同时出现在明细数据、存储映射和计算逻辑里。把标题从“费用金额”改成“报销金额”与把字段名也改掉是两件不同的事。后者要检查所有引用不能当成一次文字润色。RuoYi Office 的独立数据表发布解析会检查列名冲突、明细字段标识重复等结构问题。但结构检查不会替公司决定“能否代别人报销”也不能代替服务端业务校验。▲ 预览适合检查字段、必填标识和组件类型。图中仍是空表单是否满足业务规则需要输入样例并检查提交结果。日期尤其值得单独试一下。出差开始日是纯日期审批发生时间才是时间点。前者可以约定YYYY-MM-DD后者按接口使用时间戳或明确格式的时间字符串。前后端各猜一种格式页面也许能显示保存和再次打开时却未必一致。还有一个容易漏的细节隐藏字段不等于删除字段。页面不让改的申请人、租户或状态仍可能被人为构造进请求。应该由服务端读取登录上下文或校验允许的业务范围而不是相信“界面上没有这个输入框”。三、金额可以在前端算但最终结果要在服务端确认前端自动合计能改善体验不能作为金额正确的最后一道保证。假设有两笔费用分别是 128.50 元和 36.80 元。页面合计显示 165.30 元员工不用自己按计算器这是联动计算的价值。但如果请求中的总额被改成 16.53 元服务端不能因为它也是一个合法数字就直接保存。比较稳妥的方式是前端即时显示服务端从明细重新计算。审批条件和最终落库使用同一份核算结果避免数据库存一个金额流程却拿另一个金额判断是否需要加签。下面是面向“人民币费用、每行非负、最多两位小数”的二开示例并非通用表单引擎已经包含的报销制度。它只演示金额核算完整业务还要校验费用类别、日期、附件和用户权限staticBigDecimalcalculateTotal(ListBigDecimalamounts){if(amountsnull||amounts.isEmpty()){thrownewIllegalArgumentException(至少填写一行费用);}BigDecimaltotalnewBigDecimal(0.00);for(BigDecimalamount:amounts){if(amountnull||amount.signum()0){thrownewIllegalArgumentException(费用金额不能为空或为负数);}BigDecimalnormalized;try{normalizedamount.setScale(2,RoundingMode.UNNECESSARY);}catch(ArithmeticExceptionex){thrownewIllegalArgumentException(费用金额最多保留两位小数);}totaltotal.add(normalized);}returntotal;}为什么这里不直接四舍五入因为例子约定的是“最多两位小数”。传入 12.345 时拒绝并提示修改比悄悄变成 12.35 更符合这个约定。如果业务允许税额分摊、汇率换算就需要重新定义计算精度、舍入时点和尾差归属不能照搬这段代码。BigDecimal用于十进制计算构造测试金额时也应使用字符串或可靠的十进制来源不要先用double算一遍再补救。实际接口还应限制单行金额、明细行数及总额上限避免超出数据库字段范围。现有独立数据表转换器能把小数转换为BigDecimal输入框的精度配置也会参与列模型解析。它解决的是“值能不能按这个类型存下来”不是“这笔钱按公司制度能不能报”。两层规则分开后续才能分别维护。四、规则、业务数据和流程变量不要混成同一份东西生成一张表单不等于生成了一张已经填好的单据。我们可以把它理解成空白报销模板与员工实际提交的报销单模板描述“要填哪些项”单据记录“这一次填了什么”。同一个模板应该能产生很多张业务单据。再往下走审批引擎还需要一部分数据判断路线例如申请部门和报销金额。这些参与流程运行的数据就是流程变量。它可以与业务数据重叠但用途不同。数据主要回答的问题典型内容表单规则与发布版本当时用什么结构填写字段、组件、布局、模型快照业务单据与明细这次申请了什么人员、费用行、金额、附件引用流程实例与变量谁处理走哪条路线发起人、条件值、任务、审批状态只有简单资料收集时把数据保存在流程变量中可能就够了。如果后续需要按部门统计费用、查询明细、提供业务接口独立数据表通常更方便。如果已经有成熟的报销模块还涉及借款核销和财务处理优先复用业务服务而不是再生成一套平行账。▲ 表单目录负责维护模板和进入编辑这里展示的是表单定义不是员工提交的报销记录。本文后续以“独立数据表表单”这条路径展开。在当前实现中主单保存业务字段及form_version、process_status等信息明细通过主单 ID 关联新增单据时还会写入租户和审计字段。注意通用主子表存储也有适用边界。当前明细更新路径在请求包含该明细字段时会删除旧明细再插入新明细。因此如果外部系统要长期引用某一条费用行的 ID或者每行都有独立核销记录就应设计稳定的明细身份和差量更新不能只依赖这种整体替换方式。这一点比“自动建了几张表”更值得提前确认。表能建出来并不代表它适合所有后续业务。五、保存草稿和发布版本分开AI 修改才不会随手影响在途单据生成以后还会改需求所以要先明确这次修改只影响设计稿还是会改变正在审批的单据在 RuoYi Office 的独立数据表路径里保存设计与发布版本是分开的。发布会解析字段和库表差异生成变更预览确认时重新计算预览检查是否存在阻断项以及预览是否已经过期。下面摘取发布方法里的核心判断省略了外围的发布锁、结果组装等代码BpmFormDOformvalidateManagedForm(reqVO.getFormId());BpmFormManagedPublishPlanplancomputePlan(form);BpmFormManagedPreviewRespVOpreviewbuildPreview(form,plan);if(preview.getBlockedCount()0){throwexception(FORM_MANAGED_PUBLISH_BLOCKED,preview.getBlockedCount());}if(!Objects.equals(preview.getPreviewHash(),reqVO.getPreviewHash())){throwexception(FORM_MANAGED_PUBLISH_EXPIRED);}executeDdl(form.getId(),plan);transactionTemplate.executeWithoutResult(status-applyPublish(form,plan));previewHash可以理解成这次变更预览的指纹。你打开预览后如果表单或相关结构又发生变化就不能拿旧确认继续执行。这个机制检查的是变更是否一致不是让 AI 自己批准自己的修改。发布结果会保存配置、字段和模型快照。流程定义部署时绑定当时的表单发布版本因此后续只保存独立表单草稿不会直接把草稿同步给已部署的流程定义。需要使用新版本时还要处理相关流程模型的更新和部署。这里的版本行为特指独立数据表表单不能推广成所有存储模式都相同。版本快照也不是任意数据库变更的“撤销按钮”当前发布先执行 DDL再事务性保存发布元数据不能据此承诺跨两步自动回滚。实际发布失败后要根据执行日志核对数据库状态。对使用者来说可以先记住一个判断只改提示文字和修改字段身份、数据类型不是同一级别的操作。后两种变更尤其需要检查旧数据和在途流程不能只看新表单是否漂亮。六、接上审批需要把单据身份和流程身份关联起来表单能保存之后还需要让流程明确“我审批的是哪一张单”。这里有两个 ID业务主键标识一张单据流程实例 ID 标识一次审批运行。Flowable 的businessKey可以携带业务主键方便从流程回到单据单据也记录流程实例 ID方便展示审批进度。当前独立数据表的新单发起路径先写业务数据把行 ID 作为businessKey再启动流程并回写实例 ID。下面是核心顺序的源码节选省略了前置权限检查、变量准备以及中间的流程编号和名称设置LongmanagedDataIdnull;if(BpmFormManagedDataService.isManaged(processDefinitionInfo)){if(StrUtil.isEmpty(businessKey)){managedDataIdformManagedDataService.createData(processDefinitionInfo,variables,userId);businessKeyString.valueOf(managedDataId);}else{formManagedDataService.updateData(processDefinitionInfo,businessKey,variables,userId);}}ProcessInstanceBuilderprocessInstanceBuilderruntimeService.createProcessInstanceBuilder().processDefinitionId(definition.getId()).businessKey(businessKey).variables(variables);// 此处省略流程编号与名称设置ProcessInstanceinstanceprocessInstanceBuilder.start();if(managedDataId!null){formManagedDataService.bindProcessInstance(processDefinitionInfo,managedDataId,instance.getId());}它说明了两种身份怎么关联但不能把businessKey当成防重复提交的保证。员工连点两次或者第一次请求成功、响应却丢了第二次请求是否会新增另一张单需要另外处理。针对这类二开场景可以为同一次提交分配请求编号按租户、用户和操作范围校验唯一性并保存对应结果。重试时返回原结果而不是再创建一次。数据库唯一约束、并发处理和失败后的重试规则要一起设计光把按钮置灰不够。管理端发起入口有事务声明。在业务表和流程引擎确实共享本地事务的部署中可以把保存和启动放在一起提交一旦跨库或调用外部服务就要重新检查事务边界不能以为一个Transactional能让远端动作一起回滚。▲ 以已有用车流程展示通用发起权限配置入口差旅流程需单独配置自己的发起范围。审批入口还要区分“谁可以发起”和“谁可以查看或修改这张单”。前者通过了后者并不自动成立。尤其是代填单据、跨部门访问和附件下载应分别校验。流程状态变化后当前独立数据表有监听器把状态同步回单据。但“审批通过”仍然只代表这次审批的结果。如果还需要付款、核销或推送财务系统应交给对应业务处理并记录执行成功或失败不能直接把流程状态当付款状态。七、验收别只看生成成功要把错误输入和第二次操作也跑一遍一张单能打开、能点提交还不足以判断它可以交付。对本文这类差旅表单我们建议先准备一组很小但完整的验收样例。下面是测试目标不是已经跑过的测试报告验收动作应检查的结果填两行 128.50 和 36.80 的费用服务端合计、落库金额、审批条件值均为 165.30将请求总额改成 16.53按约定拒绝不一致请求或使用服务端核算值不采信篡改值输入负金额、三位小数或空明细在确定的业务校验处拒绝给出可理解的提示用无权用户或其他部门记录发起访问发起、详情、修改和附件分别验证权限提交后模拟响应丢失再次重试验证幂等设计是否返回原结果避免重复建单修改表单但不发布再打开在途单据独立数据表路径仍按绑定版本解释数据让流程启动或外部调用失败核对实际事务边界能查清状态并按设计恢复这些检查不需要都塞进提示词。组件选择和草案修改交给 AI确定的规则交给程序涉及公司制度的决定交给业务负责人。职责分开定位问题也更快。如果只是收集一份申请材料动态表单加审批就可能够用如果涉及预算占用、发票查重、核销和付款应该让动态表单接入已有业务服务或者转成专门的业务页面。不要为了坚持“全靠配置”把关键规则藏进越来越难维护的页面脚本。我们更看重的不是 AI 一次生成了多少组件而是生成结果能否被检查、被修改并且在保存、发布和提交时有清楚的处理边界。这样AI 才真正减少重复劳动而不是把需要开发者确认的问题藏了起来。你们接企业项目时更常遇到的是“表单画得慢”还是“表单已经画好后面的规则没人说清楚”两种问题需要投入的工作其实不一样。如果这篇对你有用欢迎点赞支持。我们会持续分享企业软件开发实践与 RuoYi Office 产品进展。