ARTICLE DETAIL

资讯详情

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

开放平台怎么做:参与者模型的插件化设计

开放平台怎么做:参与者模型的插件化设计 作者AITester 团队转载请联系授权每家企业的组织架构都不一样前面两篇文章聊了场景编排和工作流引擎。多角色流程测试里有一个绕不开的问题每个节点由谁来执行答案看似简单员工、主管、HR。但在真实企业里“主管”是谁怎么确定不同企业完全不一样有的企业用 RuoYi 自带用户体系有的企业用钉钉组织架构有的企业用企业微信有的企业用 SAP 或自研 HR 系统有的企业还有复杂的矩阵式管理项目部 职能部双重汇报如果平台把“找主管”这个逻辑写死比如只能从 RuoYi 的 sys_user 表查那用钉钉或企业微信的企业就用不了。这就是企业级产品面临的一个核心问题你无法预测所有客户的业务形态所以必须给客户留出自定义的空间。插件化设计就是解决这个问题的关键。传统方案改源码、二次开发、分支维护很多产品做扩展性时常见的做法有三种方案一改源码客户提需求厂商在代码里加 if/elseif (customer.equals(dingtalk)) {// 钉钉逻辑} else if (customer.equals(wecom)) {// 企业微信逻辑} else {// 默认逻辑}短期看最快长期看是灾难。代码里塞满客户定制逻辑耦合越来越重版本一发就全量回归。方案二二次开发把源代码给客户客户自己改。问题是客户要有懂代码的人升级时合并代码很痛苦版本一致性难以保证方案三分支维护每个大客户一个代码分支。想想就头皮发麻每个版本要合并 N 个分支bug 修复要同步到 N 个分支最终没有人能理清楚。这三种方案都不是好方案。更好的做法是把扩展点抽象成接口让客户自己实现平台动态加载。插件化的核心不是平台做所有事而是平台定义规则插件化的本质是平台把一部分能力“开放”出去让外部开发者参与。但开放不是无限制的。好的插件化设计要做到平台定义清晰的接口和契约插件只实现平台规定的接口平台负责加载、隔离、调用插件插件和平台核心代码解耦对于多角色流程测试来说一个关键的扩展点就是参与者解析器。平台只问插件一个问题“给定一个参与者定义比如‘主管’请告诉我应该由哪个具体用户来执行”插件怎么回答这个问题平台不管。你可以查 RuoYi、可以查钉钉、可以查企业微信、可以查数据库、可以调 HR 系统。整体架构是平台核心通过插件管理器加载各个独立插件插件把解析能力注册到统一的注册表工作流引擎按类型调用图 7-1 插件架构平台核心 → PluginManager → 独立 ClassLoader → 参与者解析器注册表我们怎么设计参与者解析器接口我们把参与者解析器抽象成一个很简单的接口public interface ParticipantResolver {/*** 返回解析器类型如 ruoyi、dingtalk、wecom*/String getType();/*** 从参与者定义中选取一个代表用户ID* param participant 参与者定义* return 代表用户ID返回 null 表示无可匹配用户*/Long pickRepresentative(ParticipantDefinition participant);}ParticipantDefinition 是参与者定义包含typeuser/role/post/org/org_role/org_postrefId主引用IDorgId机构IDsubType / subRefId子类型和子引用ID平台只关心输入和输出不关心内部实现。这种设计有几个好处接口简单容易理解实现方只需要关注业务逻辑平台可以加载任意多实现不同客户用不同实现互不干扰用户开发一个插件有多简单假设一个企业用钉钉他们想从钉钉通讯录里解析“主管”。整个开发流程只有简单的几步图 7-2 参与者插件开发流程实现接口 → 注册 SPI → 打包上传 → 项目切换解析器类型第一步拿到平台提供的 API JAR平台会发布一个轻量的 API 包只包含接口和实体类没有任何业务依赖。企业只需要在自己的 Maven 项目里引入这个 JAR。dependencygroupIdcom.aitester/groupIdartifactIdparticipant-api/artifactIdversion1.0.0/version/dependency第二步实现接口public class DingTalkParticipantResolver implements ParticipantResolver {Overridepublic String getType() {return dingtalk;}Overridepublic Long pickRepresentative(ParticipantDefinition participant) {// 调用钉钉 OpenAPI 查询组织架构// 根据 participant 中的类型和引用ID找到匹配的用户// 返回用户ID}}第三步注册服务在 JAR 包的 META-INF/services/ 目录下创建文件META-INF/services/com.aitester.workflow.participant.ParticipantResolver文件内容com.example.DingTalkParticipantResolver这是 Java SPI 的标准做法。第四步打包并上传执行 mvn package得到一个 dingtalk-resolver-1.0.jar。然后上传到平台的管理页面系统设置 → 参与者插件 → 上传 JAR。第五步在项目中使用在项目配置里把参与者解析器类型从默认的 “ruoyi” 切换为 “dingtalk”。后续流程执行时平台就会自动调用钉钉插件来解析参与者。整个过程不需要修改平台任何一行代码。平台端怎么加载和管理插件用户实现了插件平台怎么加载这里涉及几个技术点。独立 ClassLoader 隔离每个插件 JAR 用一个独立的 URLClassLoader 加载。这样不同插件之间的依赖不会冲突也不会污染平台主类的 ClassLoader。比如插件 A 依赖了钉钉 SDK 的某个版本插件 B 依赖了企业微信 SDK 的某个版本如果共用一个 ClassLoader很容易冲突。独立 ClassLoader 能避免这个问题。使用 ServiceLoader 发现实现Java 提供了 ServiceLoader 机制可以从 JAR 的 META-INF/services/ 里自动发现接口实现。平台加载 JAR 后遍历所有实现类注册到解析器注册中心。PluginClassLoader loader new PluginClassLoader(jarName);ListParticipantResolver resolvers loader.loadServices(ParticipantResolver.class);for (ParticipantResolver r : resolvers) {registry.register(r.getType(), r);}内置 动态合并平台既有内置解析器比如 RuoYi也有动态加载的插件。调用时按类型查找先找动态插件找不到再找内置都找不到就报错这样既能保证开箱即用又能支持客户扩展。插件生命周期管理平台提供插件管理页面上传安装查看已安装插件列表查看插件类型、版本、上传时间卸载插件插件元数据可以持久化到数据库平台重启后自动重新加载。插件化设计的几个原则做插件化设计时有几个坑我们踩过也总结了一些原则。原则一接口要稳定比实现更稳定接口一旦发布就不要轻易修改。如果必须修改要做版本兼容。插件开发者是基于接口写代码的。接口频繁变动他们会被搞疯。原则二接口要尽量小参与者解析器接口只有两个方法获取类型和解析代表用户。越小越稳定越容易实现。不要试图一个接口解决所有问题。大接口等于把平台内部逻辑暴露给外部耦合反而更重。原则三插件和平台核心隔离插件应该只能访问平台明确暴露的 API不能直接操作数据库、不能直接调用平台内部类。独立 ClassLoader 是物理隔离API 限制是逻辑隔离。两者都要做。原则四提供清晰的示例和文档开发者不会读你源码猜接口。必须提供接口定义数据模型说明完整示例项目打包和上传步骤最好提供一个 Maven 项目模板用户改几行代码就能跑通。原则五要有错误处理和安全机制插件是外部代码可能写得很烂可能抛异常可能执行很慢。平台必须捕获插件异常不让它拖垮整个流程对插件调用设置超时插件加载失败时给出明确错误信息记录插件调用日志便于排查插件化不只解决参与者问题虽然这篇文章主要讲参与者解析器但插件化的思路可以扩展到很多地方自定义通知渠道钉钉、飞书、企业微信、邮件自定义登录方式LDAP、OAuth、SAML自定义报告模板自定义用例生成策略自定义数据清理逻辑核心思想都是一样的平台定义契约外部实现细节动态加载隔离。写在最后企业级产品不可能满足所有客户的个性化需求。与其在每个客户需求里堆 if/else不如一开始就把扩展点设计好。插件化不是炫技而是企业级产品必备的工程能力。它让平台保持核心的稳定同时给客户提供足够的灵活性。对于自动化测试平台来说参与者解析器的插件化设计让我们能够服务不同组织架构的企业而不需要为每个客户改代码。这是我们产品化的重要一步。下一篇预告插件化解决了“不同企业有不同参与者”的问题。但还有一个更基础的问题前端、后端、测试平台之间怎么保证对业务语义的理解一致下一篇我会写《Schema 驱动让测试、开发、前端用同一套语言说话》聊聊我们是怎么用 Schema 契约来统一团队协作的。AITester专注企业级 Web 自动化测试平台建设让测试从成本中心变成效率引擎。
返回列表