1. 背景与结论
自从ChatGPT o1-preview放出来,在网上时不时看到介绍说这模型会思考,武力值爆棚。于是就试试。
结论:武力值的确是爆棚,起码理解设计意图,写代码,快速定位并解决问题的能力,远超一般程序员,起码超过我身边90%以上的中高级程序员。之所以限定中高级程序员,因为再往上还有架构能力我还无法测试验证,无法给出答案。
生成的这个流程引擎框架(代码)适合处理支付业务,也可继续扩展。当前驱动完成类似下面这样的配置:
whenOrderState(CommonOrderState.INIT) // 初始条件:主单状态INIT
.onEvent(CommonEvent.CREATE) // 触发事件:创建
.transitionOrderStateTo(CommonOrderState.PROCESS) // 推进到:支付中
.request(CommonOperation.PAY) // 操作:外发银行
.when("subOrder.currentState == SubOrderState.S") // 银行返回成功推进主单成功
.transitionOrderStateTo(CommonOrderState.SUCCESS)
.when("subOrder.currentState == SubOrderState.F") // 银行返回失败推进主单失败
.transitionOrderStateTo(CommonOrderState.FAIL)
.when("subOrder.currentState == SubOrderState.U && subOrder.webForm != null") // 推进发消息
.notifyNode();
为什么选择这种配置形式?因为简单得像自然语言,看懂单词就知道什么意思。
软件行业应用非常广泛。
比如在知名的测试Mock框架Mockito中,我们这样使用:
when(mockedList.get(0)).thenReturn("first element");
在java的流式代码中,我们这样使用:
names.stream()
.filter(name -> name.startsWith("A"))
.collect(Collectors.toList());
$('#element').css('color', 'red').show()
.click(function() { alert('Clicked!'); });
所有以上这些都有一个共同的特点:链式调用。
链式调用有个好处:非常接近自然语言,容易理解。如果扩展一下,专门针对支付领域定制几个专用的语法,既可减少出错可能性,又能提高效率。
为什么不使用像Activiti、jBPM、liteflow这样成熟的流程引擎框架?原因在后面有详细说明。
2. 效果
核心代码99%都是ChatGPT生成,基本没有动。运行报错就把错误信息扔给GPT,都是秒定位,秒修复。
如果想获取ChatGPT已生成的源代码,请参考“结束语”部分。
支付成功测试:
支付失败测试:
自动生成的JAVA代码:
自动生成的测试用例:
其中一个测试方法示例:
3. 提示词
提供一篇提示词,可直接用于ChatGPT生成所有流程引擎代码,生成内容包括:可直接运行的核心代码,测试代码,常用配置等。生成的代码规范性和可读性都非常高。
这篇文章最有价值的就是这篇提示词。我先做了N轮对话,在生成了完整的可运行代码并验证通过后,再让ChatGPT根据我和它的交互,由它生成一份提示词给我,再做了几次调整后,发起多轮新的独立对话(没有使用以前的上下文)验证基本可行。
需要留意一点:AI每次生成的都不一样,所以仍然可能需要调整,如何调整参考后面的调优说明。
完整提示词如下,有点长,基本就是用自然语言写一篇设计。
提示:
角色:你是一位支付领域的java技术专家,同时精通java,架构设计知识,领域驱动设计知识,设计模式知识,代码简洁之道知识,DSL,支付业务,与外部银行渠道的交互流程。
任务:请帮助我根据以下详细要求,生成一个流程引擎项目的完整Java代码。
任务概述:
● 目标:实现一个可以处理链式请求和操作的流程引擎,例如在进行支付之前先刷新令牌(Token)。
● 功能:引擎应支持通过配置状态、事件、操作和转换来定义流程。它应通过构建处理节点的链表并按顺序执行,来处理包括链式在内的操作。
● 原子操作:像 SubOrderSendHandler 和 SubOrderCallbackHandler 这样的操作是原子性的,不应处理链式操作。链式应在更高层次(特别是在 FlowEngineServiceImpl 中)管理。
● 可扩展性:设计应允许未来的扩展,例如添加新的原子操作如发送通知等。
● 测试:提供测试类来验证流程引擎的功能,确保所有代码正确执行。
● 可读性:代码可读性强,所有代码要有合适的包名,类注释,属性注释,方法注释,关键代码的注释。所有注释使用中文。
● 完整性:代码完整,可直接运行。
详细要求:
1. 流程引擎采用DSL设计思路。比如java的流式处理,或者Mockitor的测试条件处理等。
2. 流程引擎脚本涉及的关键字:
○ whenOrderState:初始条件,主订单状态要满足给定条件。比如:whenOrderState(CommonOrderState.INIT)。
○ onEvent:触发事件。比如:onEvent(CommonEvent.CREATE)。
○ transitionOrderStateTo:推进主订单状态。比如:transitionOrderStateTo(CommonOrderState.PROCESS)。
○ request:请求操作。比如:request(CommonOperation.PAY)。
○ when:判断request返回的数据,比如:when("subOrder.currentState == SubOrderState.S")。
○ notify:发出订单消息给其它域。比如:notify()。
○ then:进入子流程。比如:when("subOrder.currentState == SubOrderState.S")
.then(xxx)。
○ subFlow:子流程标识。比如subFlow().request(xxx)。
3. 流程引擎脚本配置:
○ 要求所有流程脚本不要修改。
○ 发起支付:whenOrderState(CommonOrderState.INIT)
.onEvent(CommonEvent.CREATE)
.transitionOrderStateTo(CommonOrderState.PROCESS)
.request(CommonOperation.PAY)
.when("subOrder.currentState == SubOrderState.S")
.transitionOrderStateTo(CommonOrderState.SUCCESS)
.when("subOrder.currentState == SubOrderState.F")
.transitionOrderStateTo(CommonOrderState.FAIL)
.when("subOrder.currentState == SubOrderState.U && subOrder.webForm != null")
.notify();
○ 发起刷新TOKEN后支付:super.whenOrderState(CommonOrderState.INIT)
.onEvent(CommonEvent.CREATE)
.transitionOrderStateTo(CommonOrderState.PROCESS)
.request(CommonOperation.REFRESH_TOKEN)
.when("subOrder.currentState == SubOrderState.F")
.transitionOrderStateTo(CommonOrderState.FAIL)
.when("subOrder.currentState == SubOrderState.S")
.then(
subFlow()
.request(CommonOperation.PAY)
.when("subOrder.currentState == SubOrderState.S")
.transitionOrderStateTo(CommonOrderState.SUCCESS)
.when("subOrder.currentState == SubOrderState.F")
.transitionOrderStateTo(CommonOrderState.FAIL)
);
○ 发起支付查询:super.whenOrderState(CommonOrderState.PROCESS)
.onEvent(CommonEvent.QUERY)
.request(CommonOperation.QUERY)
.when("subOrder.currentState == SubOrderState.S")
.transitionOrderStateTo(CommonOrderState.SUCCESS)
.when("subOrder.currentState == SubOrderState.F")
.transitionOrderStateTo(CommonOrderState.FAIL)
.when("subOrder.currentState == SubOrderState.U && subOrder.webForm != null")
.notify();
○ 渠道发起支付回调通知:super.whenOrderState(CommonOrderState.PROCESS)
.onEvent(CommonEvent.CALLBACK)
.request(CommonOperation.PARSE)
.when("subOrder.currentState == SubOrderState.S")
.transitionOrderStateTo(CommonOrderState.SUCCESS)
.when("subOrder.currentState == SubOrderState.F")
.transitionOrderStateTo(CommonOrderState.FAIL)
.when("subOrder.currentState == SubOrderState.U && subOrder.webForm != null")
.notify();
○ 要注意脚本的层级。比如上面的 .request(CommonOperation.PAY)后面的.when("subOrder.currentState == SubOrderState.S").transitionOrderStateTo(CommonOrderState.SUCCESS)和.when("subOrder.currentState == SubOrderState.F").transitionOrderStateTo(CommonOrderState.FAIL),两者是平级的,不能生成F为S的子结点。
4. groovy脚本支持:
○ 需要支持配置中的groovy脚本。比如.when("subOrder.currentState == SubOrderState.F")中"subOrder.currentState == SubOrderState.F"就是groovy脚本,当脚本结果为真,就执行后面的操作。
○ 需要在groovy脚本引擎上下文中导入必要的类,包括但不限于 SubOrderState等类,确保脚本不会出错。
5. 包、类、导入和代码规范:
○ 将代码组织到适当的包中,包结构:
■ 包名为 com.yinmo.flowengine,包含以下子包:
● config:用于存放流程配置类。
● context:用于存放流程上下文类。
● enums:用于存放枚举类型。
● handler:用于存放处理器类。
● model:用于存放模型类。
● service:用于存放服务类。有子包impl存放实现类。
● test:用于存放测试类。
○ 在每个类中包含所有必要的import语句。
○ 使用标准注解,如 get/set方法。 、 、 ,以及适当的自定义注解。如果是PO类,加上 、 , , , 等。使用lombok取代所有通用的
○ 注释要求:所有文件要求有类注释,方法注释,属性注释,关键逻辑的注释。类注释要求有作者:隐墨星辰。
○ 代码风格参考Springboot框架。
○ 所有类的属性名和方法不能一样。比如不能定义一个属性isCallback,有一个方法也是isCallback();
○ 除了测试类外,其它正式代码不能有魔法值。
6. 模型:
○ Order 模型,属性包括:String orderId, FlowState previousState, FlowState currentState, Money transactionAmount, List subOrders, String webForm等,增加一个方法transitionToState(FlowState)。
○ SubOrder 模型,属性包含String subOrderId, String parentOrderId,SubOrderState previousState, SubOrderState currentState, Money transactionAmount, String channelResponseCode, String channelResponseMessage, String standardResponseCode, String standardResponseMessage, String webForm, String sendToChannelContext, String receiveFromChannelContext等,增加一个方法transitionToState(SubOrderState)。
○ Money 模型。属性:金额、币种。方法:加、减、乘、除等操作。
7. 枚举:
○ 所有枚举全部继承于interface,方便扩展。所有枚举要有description字段,description的内容使用中文描述。
○ 定义interface:FlowState。
○ 定义interface:FlowEvent。
○ 定义interface:FlowOperation。包含方法 String name()、String getMethod() 和 boolean isCallback()。isCallback()用于表示是否是一个外部渠道回调操作,回调是直接解析,不需要发给渠道。getMethod()获取方法名,会发给网关,网关根据这个参数组装发给外部渠道的参数。
○ 定义:CommonOrderState继承FlowState,有INIT, PROCESS, SUCCESS, FAIL。
○ 定义:SubOrderState继承FlowState,有I(Init), U(Unkown), S(Success), F(Fail)。
○ 定义:CommonOperation继承FlowOperation,有PAY("pay", false), PAY_QUERY("payQuery", false), REFUND("refund", false), REFRESH_TOKEN("refreshToken", false), PARSE("parse", true)等,需要补充完整其它操作。PAY("pay", false)为例:说明是一个支付操作,“pay"会发给网关,网关根据这个参数组装发给外部渠道的参数,“false"表示是否是一个回调。
○ 定义:CommonEvent继承FlowEvent,有CREATE(订单创建事件,驱动系统向渠道发起支付退款等操作), QUERY(订单查询事件,驱动系统向渠道发起查询操作), CALLBACK(外部渠道主动回调通知事件,驱动系统直接解析网关接收到的报文)。
○ 定义节点处理器类型枚举:HandlerNodeType,不需要继承接口。有:TRANSITION(推进主订单状态变更),REQUEST(向外部渠道发送请求,或解析外部渠道回调报文),NOTIFY(发送消息通知其他域)
8. FlowContext 和GatewayCallbackContext:
○ FlowContext 类,包含channelName、 order、event、configName、gatewayCallbackContext 和 subOrder 等属性。
○ GatewayCallbackContext 类应包含 subOrderId、state、callbackMessage、channelResponseCode 、 channelResponseMessage 、standardResponseCode 、 standardResponseMessage 等属性。
○ 确保上下文在处理器和服务之间适当传递。
9. AbstractConfig 和 配置实现类:
○ 定义接口:FlowConfig
○ 实现一个 AbstractConfig 类,实现FlowConfig,允许使用构建器模式来定义流程。
○ AbstractConfig 维护一个 Map<FlowState, Map<FlowEvent, HandlerNode>> 来存储配置。AbstractConfig定义abstract initConfig()让子类实现,在 init()调用initConfig进行初始化,子类在initConfig()配置脚本。
○ 提供方法来定义状态、事件、转换、请求和通知。
○ 所有流程配置类继续自AbstractConfig。所有流程配置类提供String name()方法,返回当前的类简要名称用于标识流程名称。示例:getClass().getSimpleName()。
○ 定义子类:CommonPayConfig ,RefreshTokenPayConfig ,CommonPayQueryConfig,CommonPayCallbackConfig ,全部继承自 AbstractConfig。配置内容见前面的“流程引擎脚本配置”描述,不要修改。
10. 处理节点:
○ 创建一个 HandlerNodeType 枚举,值包括 TRANSITION(推进主订单状态变更)、REQUEST (向外部渠道发送请求,或解释外部渠道主动回调通知的报文)和 NOTIFY(发送消息通知其它域)。
○ 定义一个 HandlerNode 类,表示流程中的一个节点,包含节点类型、下一个状态、操作、回调处理器和子节点列表等字段。
○ 确保 HandlerNode 能通过其子节点来完成链式操作。
11. 处理器和服务:
○ 创建一个 SubOrderHandler 类,根据操作委托给 SubOrderSendHandler 或 SubOrderCallbackHandler。
○ SubOrderSendHandler 和 SubOrderCallbackHandler 只处理原子操作,不处理链式操作。
○ SubOrderHandler:负责子订单业务。
○ SubOrderSendHandler:创建子订单,保存到数据库,组装网关报文,发送给网关,由网关发送给外部渠道,解析渠道返回的报文,更新子订单,保存子订单最新数据到数据库。把子订单放到FlowContext中。
○ SubOrderCallbackHandler:创建子订单,解析渠道主动回调通知返回的报文,更新子订单,保存子订单最新数据到数据库。把子订单放到FlowContext中。
○ 定义HandlerNode用于节点处理。
○ 定义FlowEngineService 和“FlowEngineServiceImpl”,有方法execute(FlowContext)。
○ FlowEngineServiceImpl 类通过执行从配置生成的处理节点列表来处理流程。
○ FlowEngineServiceImpl 的 execute() 方法应迭代处理节点,并根据需要处理链式操作,通过递归处理子节点来实现。
○ FlowEngineServiceImpl不需要带参构造函数。增加属性init()方法用于初始化,在init()方法中,把自动装配的List configs转存到configMap,在execute方法中直接使用configMap.get(context.getConfigName())获取配置。初始化完成后,使用fastjson格式化打印configMap的内容。 List configs, Map<String, Config> configMap; 提供一个
12. 日志:
○ 在需要日志的类中使用 。
○ 以下情况需要打印日志:关键处理,状态变更,只写了空方法还没有具体实现的方法。
13. 测试:
○ 编写测试类:CommonPayTest, CommonPayQueyTest,CommonPayCallbackConfig ,RefreshTokenPayTest。
○ 所有测试类使用Mockitor进行mock子订单的返回值。数据校验使用断言。
○ 所有测试类需要有三个方法:testSubOrderSuccess(), testSubOrderSuccessFail(),testSubOrderUnkown()。分别是子订单返回成功,返回失败,返回未知。
○ 使用注解的形式使用Mock。
14. 代码质量:
○ 确保所有类都有完整的包名和导入语句。
○ 方法和属性应有清晰简洁的注释,解释其用途。
○ 代码应组织良好、可读,并遵循Java最佳实践。
○ 整个代码库应编译并运行无误。
注意:请提供所有类的完整代码,包括包声明、导入语句、类定义、方法,以及任何必要的辅助类或接口。确保代码是自包含的,不依赖于标准Java库和常用框架(如Spring)之外的外部依赖,如果有,需要给出maven配置,比如groovy,junit等。
示例结构:
● com.yinmo.flowengine.enums
● com.yinmo.flowengine.handler
● com.yinmo.flowengine.config
● com.yinmo.flowengine.context
● com.yinmo.flowengine.model
● com.yinmo.flowengine.service
● com.yinmo.flowengine.test
指示:
● 参考使用上述结构和要求,生成流程引擎项目的完整Java代码。
● 确保所有类都完整实现了必要的逻辑,import正确,并正确交互。
● 按照指定添加注释、注解和日志。
● 提供测试类,包含适当的测试方法以验证流程。
=============提示词结束=============
4. 调优
因为AI每次生成的东西都或多或少有一些不一样,所以需要调优,直到满足自己的设计意图为止。
好在调优不需要自己写代码,直接把想做的,想优化的,或者出错信息给ChatGPT,基本都是秒定位,秒解决。
还会给出思考过程,比如:
5. 为何重复造流程引擎的轮子
一是本人乐意,可以学点新东西。二是这个轮子核心代码只有几百行,极其轻量级。三是在支付系统中,流程编排随处可见,比如调用外部的渠道进行支付。而市场上的流程引擎因为考虑通用性,配置都较为复杂。像Activiti、jBPM、liteflow都试用过。
Activiti和jBPM:配置文件非常繁琐。
liteflow:配置文件很简单,但是配置上只知道节点的流转,核心业务逻辑在代码里面,比如什么条件下推进到成功在配置上是看不出来的。
下面是一个支付流程编排图:
想要这样的配置:
whenOrderState(CommonOrderState.INIT) // 初始条件:主单状态INIT
.onEvent(CommonEvent.CREATE) // 触发事件:创建
.transitionOrderStateTo(CommonOrderState.PROCESS) // 推进到:支付中
.request(CommonOperation.PAY) // 操作:外发银行
.when("subOrder.currentState == SubOrderState.S") // 银行返回成功推进主单成功
.transitionOrderStateTo(CommonOrderState.SUCCESS)
.when("subOrder.currentState == SubOrderState.F") // 银行返回失败推进主单失败
.transitionOrderStateTo(CommonOrderState.FAIL)
.when("subOrder.currentState == SubOrderState.U && subOrder.webForm != null") // 推进发消息
.notifyNode();
通过这个配置,知道初始状态,触发条件,如何推进。
Activiti和jBPM类似,配置差不多长这样:
liteflow大概长这样:
配置简单,但是需要配合代码才知道怎么做了推进。无法在配置上看清楚,如何怎么推进到成功。
6. 结束语
如果要我说说感想,我只能说,AI的发展是“日新月异”。
我不知道AI后面会不会让我丢了工作,起码目前为止,我们需要好好利用AI来提高工作效率,多一些工作产出,倒是更现实一点。
最后想重复提一句,ChatGPT的o1-preview真是强到离谱,文章中涉及的代码99%都是AI生成的,我基本只描述需求,ChatGPT生成代码,我拿到代码去跑一下,跑不过就把错误信息抛给GPT,如果跑得过但是觉得设计不好,也再提需求。基本都是秒解决,还给出详细分析。
如果想要本次ChatGPT o1-preview模型生成的源代码,可加个人微信,添加备注:gpt。
欢迎关注公众号“隐墨星辰”,和我一起深入解码支付系统设计与实现的方方面面。