ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

24-从提交到服务开通:交付一个可恢复、可升级的企业Workflow系统

24-从提交到服务开通:交付一个可恢复、可升级的企业Workflow系统 从提交到服务开通交付一个可恢复、可升级的企业 Workflow 系统图AI 生成的场景封面用于表现交付与运维协作并非真实企业现场照片也不是实验截图。这是「企业级 Workflow 实战」专栏的第 24 篇。我们用 AcmeFlow 星河设备客户维保服务开通案例交付一个可复现的端到端教学验收切片提交申请收集审批、签署和到账事实遇到远端结果未知时停下并对账再让新旧规则实例按各自版本运行。随文代码由一个 SQLite 业务库和一个独立的 SQLite 模拟远端库组成它没有把 Temporal、Camunda、n8n、LangGraph 同时装进一个生产系统也没有连接真实 ERP、企业身份平台或付款系统。一、客户要的是开通结果工程师交付的不能只是流程图想象交付会上客户问一个很普通的问题“我上午提交了合同下午能开通吗”产品演示通常会沿着一条亮绿色的箭头回答提交、审批、签约、付款、开通。真正接手运营的同事还会继续追问销售重复点了提交怎么办付款页面显示成功但财务还没有确认怎么办ERP 明明建了客户接口却超时重试会不会再建一次代码上线时还有几千条旧申请在跑新增加的合规门槛会不会突然卡住它们服务重启后谁知道该从哪里恢复如果这些问题的答案只保存在开发者脑中这个系统仍然是一个 Demo。本篇把“交付”定义为两类结果同时成立。一类是业务结果符合条件的客户最终获得 ERP 记录和维保权益条件不齐时不会偷偷开通。另一类是工程结果实例有稳定标识关键事实可查状态归属清楚远端结果未知时有恢复路线规则升级不重写旧实例语义换一位维护者也能按手册定位与处置。第二类结果不会自然从一张 BPMN 图或一段异步函数里长出来需要设计、代码、运行证据和责任分工一起落地。前 23 篇分别攻克了局部问题。本篇不会假称把那些独立实验机械拼接成一个同时运行的巨型部署。第 19 篇的 Temporal、第 20 篇的 Camunda、第 18 篇的 n8n以及第 22 篇的 LangGraph 是不同引擎或子流程的对照实践不能因为文章在同一系列中就推论它们共同编排了下文脚本。这里选择仅用标准库的两库本地模型是为了让每个读者都能验证核心业务不变量并据此编写自己的生产集成清单。图 1AcmeFlow 教学切片的责任拓扑。业务库与独立模拟远端库分别保存本地状态和远端效果箭头是设计示意不是线上系统监控截图。可编辑版本SVG。二、先确定对象、规则和“谁说了算”我们的主对象是一份客户维保开通申请。每条申请有tenant_id、UUIDapplication_id、人可识别的business_key、material_version、rule_version、状态和修订号。tenant_idxinghe-demo是教学租户验收脚本里的第一份申请 UUID 为11111111-1111-4111-8111-111111111111第二份为22222222-2222-4222-8222-222222222222。business_key用合同业务键表达“同一业务申请”与请求级幂等键、事件 ID、ERP 动作键不是同一个概念。业务库用(tenant_id,business_key)唯一约束限制重复业务申请所以第二次用同一键提交即使请求里换了新 UUID 和新规则版本也应该返回原申请而不是偷偷创建第二份流程。状态也必须有明确含义。WAITING_FACTS表示当前规则所需的独立事实还不齐READY表示这组事实达到当前实例绑定的规则要求可以申请执行PROVISIONING表示已经进入对外执行阶段远端结果可能尚未确认ACTIVE表示模拟 ERP 记录与权益记录都已得到确认MANUAL是需要人工处理的异常状态。实验另外用erp_stateUNKNOWN表示一次请求的远端效果尚未被本地确认。UNKNOWN不是FAILED接口回执丢失时远端可能已经提交。把二者混为一谈会直接决定系统是否错误重试和重复创建。图 2状态与事实分离。READY 是门禁结论ACTIVE 必须建立在 ERP 与权益两个模拟远端事实确认之上。可编辑版本SVG。审批、签署和合规确认都绑定material_version。如果客户在待事实阶段更新材料代码提高版本并把approval_version、signed_version、compliance_version清空旧审批、旧签名和旧合规结论不会被误用。到账在简化模型里是一个paid标记不随材料改版自动清空真实财务系统必须根据订单、合同与款项关系判断是否仍然有效。这些演示字段没有保留完整凭据或撤销记录不能照抄为唯一事实来源。规则版本在创建申请时固定第一版要求同版审批、同版签署和到账第二版还要求同版合规确认。这里的版本是实例的业务规则版本不是资料版本也不是数据库结构迁移版本。三种版本若混用运维人员很难解释“这个客户为什么等了这么久”。代码把两个效果保存在独立模拟远端库erp_records(action_key,external_id)和rights(action_key,entitlement_id)。两张表各以动作键为主键创建时使用INSERT OR IGNORE让同一动作键在这个沙盒里只产生一条记录。键由租户、申请 UUID、资料版本和动作种类拼成ERP 与权益有不同的后缀。这样的键是业务关联与重放控制的起点但它不自动把本地和远端变成一个原子事务。代码用两个独立 SQLite 连接、分别提交故障窗口真实存在。SQLite 官方的原子提交说明讨论了单库与特定多文件连接条件下的原子性本实验没有用一个连接把两库纳入同一事务因此绝不能把两库操作宣称为一次全局提交。这里特别要说清楚ACTIVE的所有权状态写在业务库里远端库写的是独立效果。业务库只能在确认两类效果后宣布ACTIVE不能因为“已发出请求”或“AI 认为可开通”就提前结束流程。第 23 篇的执行门禁讨论谁有权把建议变成命令本篇承接的是命令之后的效果确认和恢复。演示代码中的角色仅靠字符串比对不构成真实身份认证后文的交付清单会把这个缺口列为生产前必须补齐的条件。三、用两个脚本完成一次可复核的验收目录里的code/capstone.py是操作入口支持submit、fact、update、provision、reconcile、show。code/run_acceptance.py是验收驱动器。它每做一步都通过subprocess.run()启动一个新的 Python 进程使用同一临时目录里的business.sqlite与remote.sqlite文件。新进程并不是时间旅行也没有模拟机器断电但足以检验关键状态不是只存在于 Python 内存中。验收脚本用断言验证结果任何一项不符合就抛错最后打印六个汇总字段临时目录退出后删除不留下客户数据。运行方法很短在本篇目录执行python -X utf8 code/run_acceptance.py。-X utf8只是为了 Windows 控制台稳定显示文本并非业务依赖。所有功能使用 Python 标准库。实测环境是 Python 3.12.0、SQLite 3.42.0我们复跑这条命令得到退出码零。不同 Python 版本只要支持源码所用语法与标准库接口理论上也可运行但本文没有逐个版本测试。正式发布前建议把自己的 Python、SQLite、操作系统版本和退出码一起写进验收记录。先看提交去重。验收脚本第一次用contract-001提交 v1 申请第二次用同一业务键、另一 UUID 和rule2提交。submit()在业务库里按(tenant_id,business_key)查到旧记录返回旧application_id与旧rule_version并把deduplicated置为真。这个选择非常关键一次客户端重发不应顺手把尚未完成的 v1 流程升级为 v2更不应生成两个同名客户订单。用第二次请求带来的规则覆盖旧记录会让“重复请求”变成危险的隐藏迁移。实验只测顺序重复提交没有证明高并发下每个调用者都会得到优雅响应数据库唯一约束提供底线冲突处理还须在服务 API 中完善。随后运营角色写审批事实客户角色写签署事实。此时仍在WAITING_FACTS因为财务到账尚未写入。财务角色补入付款事实后facts()重新计算当前实例的规则v1 的条件齐备状态变成READY。这里的角色只是 CLI 传入的actor文本代码检查某事实类型对应哪个字符串没有企业身份提供方也没有签署和到账回调的真实性验证。它是规则演示不是一个安全可上线的授权方案。OWASP 的授权备忘单建议默认拒绝并逐请求校验权限商业部署应从可信认证上下文取得主体、校验租户与资源权限而不能允许客户端随手传--actor finance就写入到账。有资格开通以后provision()先在业务库把状态置为PROVISIONING审计写下PROVISION_START。然后它按稳定动作键在模拟远端创建 ERP 记录再创建权益记录。正常路径里业务库先记erp_stateDONE最终写rights_stateDONE和statusACTIVE。代码片段中最值得观察的不是INSERT而是两库之间的断点图 3新旧规则并行的正常路径。v1 在审批、签署、到账后可继续v2 增加合规门槛。图是教学示意结果需以脚本断言为准。可编辑版本SVG。所谓正常路径也要避免把“按顺序调用函数”误写成“业务自然串行”。验收脚本在 v1 事实收集阶段刻意拆成多个进程原因是企业事实通常由不同岗位、不同系统、不同时间产生。审批可能先到签署可能后到付款可能隔天才确认任何一个事实抵达时都应重新计算当前实例是否具备进入下一步的条件。这个示例对事实操作作了简单角色分配和状态检查却尚未处理事件乱序、重复通知、撤销和更正。接入真实财务或签署平台时必须先定义事件唯一标识、签名或令牌验证、到达时间与业务生效时间的区别并为已发布错误事实建立修正流程。withdb:db.execute(UPDATE applications SET statusPROVISIONING,revisionrevision1 WHERE tenant_id? AND application_id?,(tenant,app))log(db,tenant,app,worker,PROVISION_START,durable intent)create_erp(remote,erp_key)ifsimulateerp_response_lost:withdb:db.execute(UPDATE applications SET erp_stateUNKNOWN WHERE tenant_id? AND application_id?,(tenant,app))log(db,tenant,app,worker,ERP_UNKNOWN,remote committed; response hidden)returninspect(db,tenant,app)这个教学注入点把“远端已经提交但回执不可见”的结果写成UNKNOWN并提前返回。脚本没有真实网络也没有真的丢包它用控制参数隐藏回执专门模拟这一故障语义。第 07 篇重试与失败分类给出了为什么不能把未知结果当可直接重试的原理。本篇的处理是先读取远端记录再对账不凭本地UNKNOWN去创造一个新业务动作。四、正常结果、未知结果和新旧规则的同场演练验收脚本选择 v1 实例走一次异常先在远端库写入ERP-9001随后模拟响应丢失。本地可见的是statusPROVISIONING、erp_stateUNKNOWN。脚本接着另起一个进程执行show断言它得到与异常结束时完全相同的状态。这样就验证了“重启后仍能看见待恢复事实”而不是只在一个进程的局部变量里保持悬而未决。请注意这个断言没有证明进程在任意指令点被杀死时都能恢复也没有证明数据落盘策略在突然断电时满足生产要求。它覆盖的是一个有意设置并已提交的断点。图 4响应丢失故障的教学时间线。远端提交成功、本地 UNKNOWN、新进程读取状态、按原键查询并补权益图中 ERP-9001 是模拟远端记录。可编辑版本SVG。恢复由reconcile()完成。它先要求演示操作者字符串是operator且填写非空原因再检查实例确实在PROVISIONING或MANUAL。然后用原 ERP 动作键查询远端库查不到则保留MANUAL不贸然宣布成功。查到了才用稳定权益动作键创建或复用权益记录并把本地 ERP 与权益标记为完成最后进入ACTIVE。这就是“对账”与“重试一个请求”的区别先回答先前动作是否生效再决定剩余动作该做什么。若真正 ERP 的查询接口不支持按幂等键查询交付方必须与业务方制定人工核对外部单号、客户号和时间窗的规则不能把当前模拟库能力投射到真实 ERP。验收报告中的reconciled_v1ACTIVE只对这个模拟远端成立。reconcile()当前会在远端有 ERP 记录时调用create_rights()代码内的唯一动作键让重复调用在本地 fixture 中无副作用真实权益平台可能不支持同样语义需要适配器、调用预算和人工处理条件。恢复也不能无限执行若远端既没有确认成功也不能可靠确认失败应停在人工处理中保留调查证据与操作理由。更高级的补偿或撤销策略取决于合同与业务承诺不能靠单个脚本替客户决定。再看规则升级。v1 申请创建时记录rule_version1。脚本随后发布另一份contract-002新申请并指定rule_version2。新实例收到审批、签署、到账后仍是WAITING_FACTS直到合规角色对当前材料版写入合规确认才成为READY最终模拟 ERP 与权益成功后进入ACTIVE。这不是改变旧实例的规则而是让不同创建时间的实例遵循不同版本的解释。第 12 篇运行中流程的升级讨论更一般的兼容策略本实验验证的只是业务规则快照尚未演练真正工作流引擎的 Worker 代码版本、事件历史重放或数据库迁移。图 5实例级规则版本。v1 不要求合规确认v2 在相同审批、签署、到账外增加一项条件这是教学规则演练不代表实际生产已经发布 v2。可编辑版本SVG。通过这些实例读者应能区分四个看似相似的“版本”问题客户修改合同是资料版本变化业务增加合规要求是规则版本变化服务发布新程序是代码版本变化数据库增加字段是结构版本变化。实验用material_version和rule_version两个字段显式建模还用compliance_version等事实版本关联当前材料。若要进入商业生产每一种变化都需要相应迁移、回滚、兼容窗口和审计解释。简单地让所有老实例读取“当前最新规则”往往会让待处理客户在没有重新告知、重新审批的情况下改变交付条件。更新后的验收脚本还创建第三份 v2 申请先对材料第一版做合规确认再把材料改为第二版。material_update()清掉旧审批、签署与合规版本即使随后补上新版审批、新版签署和到账实例仍停在WAITING_FACTS直到合规人员针对第二版重新确认才到READY。这里证明的是代码在这条受测路径上拒绝复用旧合规事实而不是证明所有真实材料变更都需要重做付款。到底哪些事实要随材料改版失效必须由合同、财务和合规负责人与工程团队共同定义。五、实际输出是什么它证明了什么随文保存的code/run-output.txt是更新后的验收驱动器实跑结果。驱动器打印一行 JSON字段按排序输出{duplicate_request:true,erp_rows:4,old_compliance_invalid_after_material_update:WAITING_FACTS,payment_gate:WAITING_FACTS,reconciled_v1:ACTIVE,rights_failure_exit:MANUAL,rights_repaired:ACTIVE,rights_rows:4,unauthorized_fact_rejected:true,unknown_after_restart:UNKNOWN,v2_with_compliance:ACTIVE,v2_without_compliance:WAITING_FACTS}首批六个核心字段说明同一业务键的顺序重复提交返回原实例只审批和签署、缺财务事实时不开通模拟远端已创建而回执丢失后新进程仍看到未知状态随后通过远端查询与权益补齐恢复到活动状态新规则实例在缺合规事实时等待补齐后进入活动状态。新增字段把范围扩大材料改版后旧合规事实失效权益故障进入MANUAL经受控对账恢复ACTIVE错误角色字符串被拒绝写入付款事实最终模拟远端 ERP 与权益表各有四条记录。所有输出均在脚本断言后打印退出码零是验收通过的辅助证据。unauthorized_fact_rejectedtrue只证明 CLI 中不匹配的字符串被拒绝不是企业身份认证测试两张表各四行只对应本次四份申请不能推论真实远端的全局唯一性。图 6首批六项核心验收观察点的整理图。当前验收脚本还追加材料改版、权益故障、角色字符串拒绝和远端行数断言完整原始结果以文本为准。图是教学信息图不是自动化测试平台截图。可编辑版本SVG。如果交付报告只写“测试通过”下一位维护者无法判断已经覆盖什么。我们另外给出验收报告列出环境、命令、每项断言、证据路径及未覆盖范围给出演示运维手册写明如何运行、查看状态、识别未知结果、何时停止自动动作和如何记录人工对账理由。文档故意不写“生产已上线”因为脚本当前缺认证、凭据管理、真实外部接口和并发压力验证。交付可信度来自结论与证据匹配而不是结论听起来更大。六、故障矩阵哪些路径会停、哪些路径能恢复在设计故障矩阵时先按“明确失败”和“结果未知”分开。重复提交是请求语义问题业务键相同要返回旧申请不能因为第二次请求带了新 UUID 就复制一个客户。缺付款是事实门禁问题状态停在WAITING_FACTS操作人应去核对财务数据而不是重跑provision()。ERP 响应丢失是外部效果未知业务状态保持PROVISIONINGerp_stateUNKNOWN需要用原动作键查询远端再决定是否补权益。权益创建报错是部分成功ERP 记录已经存在状态进入MANUAL不能把整单当成从未发生。查不到远端 ERP 记录时reconcile()也停在MANUAL等待进一步调查而不是凭空写成ACTIVE。这些故障在本篇的证据等级并不相同。顺序重复提交、缺付款、模拟 ERP 回执丢失与重启后对账、新规则缺合规事实、旧版合规失效、权益失败后的人工出口与修复、错误角色字符串被拒绝都是run_acceptance.py实际断言过的。capstone.py中还有远端查不到时转人工的分支但标准验收脚本没有覆盖它本文把它列为下一轮故障演练。并发重复提交、数据库崩溃、远端权限失败、两个系统之一不可用、错误回滚与审计库篡改也都不在已验收范围内。没有这种分级故障矩阵就会变成愿望清单。场景可观测状态建议处置本篇证据同业务键重复提交返回旧申请与旧规则版本不创建第二条业务实例脚本断言通过审批、签署已到付款未到WAITING_FACTS核对财务事实禁止开通脚本断言通过ERP 远端已建回执丢失PROVISIONING、UNKNOWN按原动作键查询确认后补权益重启与对账断言通过权益创建失败MANUAL核对 ERP受控补权益脚本断言进入人工并恢复查询不到 ERP 记录MANUAL保留未知证据并人工调查代码有分支标准验收未覆盖v2 缺合规确认WAITING_FACTS补齐合规事实再计算门禁脚本断言通过资料改版旧合规确认仍在WAITING_FACTS对新资料重新审批、签署、合规确认脚本断言旧版失效并补齐错误角色字符串写付款CLI 非零退出拒绝写入记录真实授权缺口脚本断言通过但未接企业认证并发、断电、真实接口超时由生产设计确定在预生产环境注入故障本篇未验证故障矩阵里的“建议处置”不是无条件执行的命令。例如UNKNOWN时查询远端必须使用与原请求相同的业务动作键并核对租户、申请、资料版本和远端单号。若查询结果模糊操作者应保留PROVISIONING或转人工不应把“没查到”直接解释成“从未创建”。权益失败时恢复的目标是完成尚未确认的权益动作而不是再次创建 ERP 客户。多个系统对“客户已开通”的定义也可能不同ERP 记录存在但权益未激活时对外页面应展示处理中或异常而不是已开通。把这些语义写清值班人员才能在压力下做出同一类决定。第 10 篇跨系统补偿讨论一半完成后何时补偿、何时继续第 16 篇监控与受控修复讨论如何让异常可见。放进本篇案例运营人员至少应能按租户与申请 UUID 找到实例、读出资料版本、规则版本、状态、ERP 与权益结果再沿审计记录知道谁提交、谁写事实、谁发起对账及原因。图形界面可以更友好但这些信息不能只存在于图形界面的瞬时状态。OWASP 的日志备忘单强调应用层安全事件和日志保护本实验的audit表没有时间戳、防篡改或访问控制商业交付必须补齐。故障矩阵还应规定动作所有者。销售负责核对原始申请是否重复运营负责审批与异常工单财务负责到账真实性合规负责 v2 新条件平台工程师负责实例状态与消息链路ERP 业务负责人确认远端客户记录权益负责人确认服务权益。把所有异常都指派给“开发排查”不是交接。尤其是需要人工确认的未知结果执行人不能自己修改本地数据库把状态改为成功应使用受控操作入口记录外部证据、审批、操作者和原因并保留原始状态历史。七、规则升级与部署交接不是同一个按钮本地脚本里rule_version只是提交时写入的一列。生产环境上线一个新规则还要安排顺序先发布兼容旧版和新版数据的数据库迁移再发布能够识别两个规则版本的服务再开启新申请路由到 v2观察两个版本的等待时长、异常量和人工任务积压最后在旧实例自然结束或明确迁移之后才考虑删除 v1 代码。直接替换旧代码并把默认值改成 v2虽然新请求可能运行正常却可能让未完成的旧流程在恢复时读到不认识的状态或新的必填字段。在这个切片里submit()限定规则只能是 1 或 2这是显式拒绝未知版本的保护。第二次重复业务键请求即使提出rule2也返回原实例的rule_version1。如果客户真的要把老合同迁到新政策那应是另一项带有审批和审计的迁移操作而不是把重复提交当迁移。资料改版则走material_update()清空与原材料绑定的审批和签署要求重新收集。标准验收脚本没有测试该命令交接前还应给它增加回归用例并验证改版后旧版动作键不会误作用于新版申请。部署清单要比“跑起来”具体。至少列出环境和机密配置来源、数据库备份与恢复演练、迁移与回滚脚本、认证和授权路径、Webhook 验签、消息重复与死信处置、外部 ERP 与权益接口的幂等及查询能力、审计保留周期、告警阈值与值班联系、人工处理权限、版本路由、容量上限、灰度及回退标准。本文演示运维手册提供这些项目的可填写检查框它不会伪造生产环境的地址、密钥或责任人。不同企业对可用性、数据驻留和审批留痕的要求不同具体阈值应由实际业务与安全团队定稿。同样需要交接的是读者能自己跑的代码与证据。源码、验收驱动、原始输出、六张信息图、可编辑 SVG、故障矩阵和验收报告都放在这一篇目录下。接手人无需记住作者曾在哪次对话里说过“ERP 超时先别重试”她可以根据UNKNOWN说明和动作键定位远端事实再依手册进入人工对账。真正的生产手册还需要把这些步骤映射到企业实际控制台和审批系统并在预生产演练通过后才签署交接。八、项目导航24 篇不是 24 个互不相干的玩具系列的起点是第 03 篇 FastAPI 与 PostgreSQL 审批基座它定义申请、流程实例、任务和历史。第 05 篇并发审批解决同一任务相反决定的竞争第 06 篇幂等与 Outbox处理本地事务与事件投递边界第 07 篇失败分类给未知结果与明确错误不同的恢复策略。第 09 篇人任务和第 14 篇Webhook 与业务事件让审批、签署、到账这些事实有可靠的入口。第 12 篇流程升级说明版本不是改个布尔值。到集成层第 13 篇ERP 与 CRM 适配器把外部系统作为不可靠边界第 16 篇观测与修复关心实例出了错以后谁看得见、谁能修。第 17 篇引擎选型提供分阶段选择依据第 19、20 篇分别用 Temporal 和 Camunda 做独立的对照实现。最后第 21 篇Workflow 与 AI Agent 分工、第 22 篇LangGraph 材料分析子流程以及第 23 篇执行门禁界定了 AI 可以帮助判断却不拥有业务终态的边界。这份导航表达的是问题和契约的继承不是运行组件清单。第 03 篇是 PostgreSQL 加 FastAPI本篇是两份 SQLite 文件Temporal、Camunda、n8n、LangGraph 实验各有自己的运行条件。若要打造一个商业部署必须选定主编排方式与数据所有权逐个把实验中的事实入口、状态迁移、授权、Outbox、外部适配器和 AI 建议接到同一受测架构上。不能因为文章目录完整就宣称系统集成已经完成。九、Workflow Thinking交付的单位是“可恢复的承诺”我会把一个企业 Workflow 的最小交付单位定义为“对某个业务承诺的可恢复实现”。客户承诺得到服务就必须知道承诺在什么时候成立哪些人可以改变它哪些外部事实还在等待发生中断时凭什么继续。如果只把正向路径写成一串函数系统在第一处超时就会露出原形如果只留下很详细的业务流程图运维仍然不知道第二次点击是重复、补偿还是新申请。状态、业务键、动作键、事实来源、规则版本与审计共同构成了这个承诺的证据。这套实验也刻意保留了一个不完美的地方为了让任何读者能在本机复现我们用字符串模拟角色用两个 SQLite 库模拟本地与远端用--simulate制造回执丢失。这样做比把一套没有企业账号和 ERP 凭据的“完整生产架构”写在纸上更诚实。示例证明的是若干重要机制可运行未证明它满足高可用、强认证、数据保护和真实远端幂等。商业交付需要在这些缺口上继续投入并把每一次扩大结论的依据重新验收。真正值得带走的方法是先给出可判定的不变量再为正常和异常路径保存可复核证据最后把恢复操作交给明确的人与受控接口。客户在业务上看到“已开通”时维护者应该能够追溯是哪份申请、哪版规则、哪版材料、哪些独立事实、哪个远端单号与哪次操作使它成立。没有这些信息“自动化”只是在顺利时省掉几次点击有了这些信息系统才开始成为团队可以长期负责的企业 Workflow。
返回列表