前言

橙单目前已支持,以独立服务的形式运行在线表单、工作流和报表打印等功能模块,同时可接入多个第三方应用,并以 app_code 字段进行隔离。接入后,第三方系统便可具备橙单所提供的在线表单、工作流和报表打印等功能,技术优势如下。

  • 被接入系统改动极少,前后端均与橙单架构 0 耦合,仅需参考本文档的示例编写插件代码即可。
  • 被接入系统前端可使用任何技术栈,均不会与橙单页面产生冲突。
  • 被接入系统后台可使用任何技术栈或不同的开发语言,其与橙单服务之间采用标准的 HTTP 接口进行通讯。
  • 被接入系统数据库表与橙单内置库表可独立部署,甚至可与橙单使用不同的数据库类型,从而实现数据层面的最大化解耦。
  • 橙单独立部署的在线表单、工作流和报表打印等组件服务,可同时服务于多个不同的业务系统。如出现性能瓶颈,亦可动态扩充橙单组件服务的实例数量,以缓解系统的并发处理压力。

示例说明

本文档以开源脚手架「若依」为例,通过为「若依」编写橙单的第三方接入插件,使基于「若依」开发的业务系统,在修改极少代码的情况下,可快速集成橙单的在线表单、工作流和报表打印等功能。对于使用其他脚手架、技术栈或不同开发语言的业务系统,开发者可参考当前文档的插件示例代码,为自己的系统编写橙单接入插件。

架构图

插件开发

这里我们以知名开源脚手架若依为例,我们通过为若依开发橙单插件,可以将橙单所支持的「在线表单」、「工作流」和「统计报表」等模块快速接入到若依的项目中。

第三方后端插件

  • 在若依后台系统中实现下图所示的插件代码。
  • 若依集成橙单在线表单、工作流和报表打印等模块的后台插件完整代码,请仔细阅读代码中的注释部分。
/**
 * 集成在若依中的橙单插件接口。
 *
 * @author 橙单团队
 */
@RestController
@RequestMapping("/orangePlugin")
public class OrangePluginController extends BaseController {
   @Autowired
   private ISysMenuService menuService;
   @Autowired
   private ISysRoleService roleService;
   @Autowired
   private ISysDeptService deptService;
   @Autowired
   private ISysUserService userService;
   @Autowired
   private ISysPostService postService;

   // sysDeptMapper和sysRoleMapper本来不应该在controller中直接使用的。
   // 可以自己分别修改若依的SysDeptService和SysRoleService方法封装一下这两个mapper的接口。
   // 我们这里之所以直接使用,主要还是为了简化文档的说明,以及尽量减少对若依基础框架代码的修改。
   @Autowired
   private SysDeptMapper sysDeptMapper;
   @Autowired
   private SysRoleDeptMapper roleDeptMapper;

   // 在线表单数据库链接管理管理菜单关联的接口权限集合。
   private static Set<String> onlineDblinkPerms;

   // 在线表单页面管理菜单关联的接口权限集合。
   private static Set<String> onlinePagePerms;

   // 在线表单字典管理菜单关联的接口权限集合。
   private static Set<String> onlineDictPerms;

   // 在线表单业务页面需要访问的运行时接口。第三方接入目前尚不支持"读写"权限的区分。既只要具有该菜单的权限,
   // 那么就具备读写权限。
   private static Set<String> onlineOperationPerms;

   // 在线表单的白名单接口集合。
   private static Set<String> onlineWhitelistPerms;

   // 工作流分类接口集合。
   private static Set<String> flowCategoryPerms;

   // 工作流设计菜单的接口集合。
   private static Set<String> flowEntryPerms;

   // 工作流流程实例菜单的接口集合。
   private static Set<String> flowInstancePerms;

   // 工作流待办任务菜单的接口集合。
   private static Set<String> flowRuntimeTaskPerms;

   // 工作流历史任务菜单的接口集合。
   private static Set<String> flowHistoricTaskPerms;

   // 工作流已办任务菜单的接口集合。
   private static Set<String> flowFinishTaskPerms;

   // 工作流工单中与流程定义标识相关的接口集合。
   private static Set<String> flowWorkOrderOperationPerms;

   // 工作流工单中的通用接口集合。
   private static Set<String> flowWorkOrderCommonPerms;

   // 工作流所需的白名单接口集合。
   private static Set<String> flowWhitelistPerms;

   // 报表数据库链接管理菜单关联的接口权限集合。
   private static Set<String> reportDblinkPerms;

   // 报表数据集管理菜单关联的接口权限集合。
   private static Set<String> reportDatasetPerms;

   // 报表字典管理菜单关联的接口权限集合。
   private static Set<String> reportDictPerms;

   // 报表页面管理菜单关联的接口权限集合。
   private static Set<String> reportPagePerms;

   // 报表打印管理菜单关联的接口权限集合。
   private static Set<String> reportPrintPerms;

   // 报表打印所需的白名单接口集合。
   private static Set<String> reportWhitelistPerms;

   // 报表业务页面需要访问的运行时接口。
   private static Set<String> reportOperationPerms;
   static {
       onlinePagePerms = CollUtil.newHashSet(
               "/admin/online/onlineDblink/list",
               "/admin/online/onlineDblink/listDblinkTableColumns",
               "/admin/online/onlineDblink/listDblinkTables",
               "/admin/online/onlineForm/clone",
               "/admin/online/onlineForm/delete",
               "/admin/online/onlineForm/update",
               "/admin/online/onlineForm/add",
               "/admin/online/onlineForm/list",
               "/admin/online/onlineDatasourceRelation/delete",
               "/admin/online/onlineDatasourceRelation/update",
               "/admin/online/onlineDatasourceRelation/add",
               "/admin/online/onlineDatasourceRelation/view",
               "/admin/online/onlineDatasourceRelation/list",
               "/admin/online/onlineDatasource/delete",
               "/admin/online/onlineDatasource/update",
               "/admin/online/onlineDatasource/add",
               "/admin/online/onlineDatasource/view",
               "/admin/online/onlineDatasource/list",
               "/admin/online/onlinePage/viewOnlinePageDatasource",
               "/admin/online/onlinePage/updateOnlinePageDatasource",
               "/admin/online/onlinePage/deleteOnlinePageDatasource",
               "/admin/online/onlinePage/addOnlinePageDatasource",
               "/admin/online/onlinePage/listNotInOnlinePageDatasource",
               "/admin/online/onlinePage/listOnlinePageDatasource",
               "/admin/online/onlinePage/delete",
               "/admin/online/onlinePage/updatePublished",
               "/admin/online/onlinePage/update",
               "/admin/online/onlinePage/add",
               "/admin/online/onlinePage/view",
               "/admin/online/onlinePage/listAllPageAndForm",
               "/admin/online/onlinePage/list",
               "/admin/online/onlineRule/delete",
               "/admin/online/onlineRule/update",
               "/admin/online/onlineRule/add",
               "/admin/online/onlineRule/view",
               "/admin/online/onlineRule/list",
               "/admin/online/onlineColumn/viewOnlineColumnRule",
               "/admin/online/onlineColumn/updateOnlineColumnRule",
               "/admin/online/onlineColumn/deleteOnlineColumnRule",
               "/admin/online/onlineColumn/addOnlineColumnRule",
               "/admin/online/onlineColumn/listNotInOnlineColumnRule",
               "/admin/online/onlineColumn/listOnlineColumnRule",
               "/admin/online/onlineColumn/refresh",
               "/admin/online/onlineColumn/delete",
               "/admin/online/onlineColumn/update",
               "/admin/online/onlineColumn/add",
               "/admin/online/onlineColumn/view",
               "/admin/online/onlineColumn/list",
               "/admin/online/onlineDblink/testConnection",
               "/admin/online/onlineVirtualColumn/delete",
               "/admin/online/onlineVirtualColumn/update",
               "/admin/online/onlineVirtualColumn/add",
               "/admin/online/onlineVirtualColumn/view",
               "/admin/online/onlineVirtualColumn/list");
       onlineDblinkPerms = CollUtil.newHashSet(
               "/admin/online/onlineDblink/add",
               "/admin/online/onlineDblink/update",
               "/admin/online/onlineDblink/delete",
               "/admin/online/onlineDblink/list",
               "/admin/online/onlineDblink/view");
       onlineDictPerms = CollUtil.newHashSet(
               "/admin/online/onlineDict/delete",
               "/admin/online/onlineDict/update",
               "/admin/online/onlineDict/add",
               "/admin/online/onlineDict/view",
               "/admin/online/onlineDict/list");
       onlineOperationPerms = CollUtil.newHashSet(
               "/admin/online/onlineOperation/viewByDatasourceId/",
               "/admin/online/onlineOperation/viewByOneToManyRelationId/",
               "/admin/online/onlineOperation/listByDatasourceId/",
               "/admin/online/onlineOperation/listByOneToManyRelationId/",
               "/admin/online/onlineOperation/exportByDatasourceId/",
               "/admin/online/onlineOperation/exportByOneToManyRelationId/",
               "/admin/online/onlineOperation/downloadDatasource/",
               "/admin/online/onlineOperation/downloadOneToManyRelation/",
               "/admin/online/onlineOperation/addDatasource/",
               "/admin/online/onlineOperation/addOneToManyRelation/",
               "/admin/online/onlineOperation/updateDatasource/",
               "/admin/online/onlineOperation/updateOneToManyRelation/",
               "/admin/online/onlineOperation/deleteDatasource/",
               "/admin/online/onlineOperation/deleteOneToManyRelation/",
               "/admin/online/onlineOperation/deleteBatchDatasource/",
               "/admin/online/onlineOperation/deleteBatchOneToManyRelation/",
               "/admin/online/onlineOperation/uploadDatasource/",
               "/admin/online/onlineOperation/uploadOneToManyRelation/");
       onlineWhitelistPerms = CollUtil.newHashSet(
               "/admin/online/onlineForm/render",
               "/admin/online/onlineForm/view",
               "/admin/online/onlineOperation/listDict",
               "/admin/commonext/bizwidget/list",
               "/admin/commonext/bizwidget/view");
       flowCategoryPerms = CollUtil.newHashSet(
               "/admin/flow/flowCategory/list",
               "/admin/flow/flowCategory/add",
               "/admin/flow/flowCategory/update",
               "/admin/flow/flowCategory/delete",
               "/admin/flow/flowCategory/view"
       );
       flowEntryPerms = CollUtil.newHashSet(
               "/admin/flow/flowEntry/activateFlowEntryPublish",
               "/admin/flow/flowEntry/add",
               "/admin/flow/flowEntry/delete",
               "/admin/flow/flowEntry/list",
               "/admin/flow/flowEntry/listFlowEntryPublish",
               "/admin/flow/flowEntry/publish",
               "/admin/flow/flowEntry/suspendFlowEntryPublish",
               "/admin/flow/flowEntry/update",
               "/admin/flow/flowEntry/updateMainVersion",
               "/admin/flow/flowEntry/view",
               "/admin/flow/flowEntryVariable/add",
               "/admin/flow/flowEntryVariable/delete",
               "/admin/flow/flowEntryVariable/list",
               "/admin/flow/flowEntryVariable/update",
               "/admin/flow/flowEntryVariable/view",
               "/admin/flow/flowOnlineOperation/download",
               "/admin/flow/flowOnlineOperation/startPreview",
               "/admin/flow/flowOnlineOperation/upload",
               "/admin/flow/flowOperation/startOnly",
               "/admin/flow/flowOperation/viewInitialTaskInfo",
               "/admin/flow/flowOperation/viewProcessBpmn",
               "/admin/online/onlineColumn/list",
               "/admin/online/onlineDatasourceRelation/list",
               "/admin/online/onlineForm/list",
               "/admin/online/onlineForm/render",
               "/admin/online/onlinePage/list",
               "/admin/online/onlinePage/listOnlinePageDatasource",
               "/admin/online/onlineVirtualColumn/list",
               "/admin/upms/sysUser/list"
       );
       flowInstancePerms = CollUtil.newHashSet(
               "/admin/flow/flowOnlineOperation/viewHistoricProcessInstance",
               "/admin/flow/flowOperation/deleteProcessInstance",
               "/admin/flow/flowOperation/listAllHistoricProcessInstance",
               "/admin/flow/flowOperation/stopProcessInstance",
               "/admin/flow/flowOperation/viewHighlightFlowData",
               "/admin/flow/flowOperation/viewInitialHistoricTaskInfo",
               "/admin/flow/flowOperation/viewProcessBpmn"
       );
       flowRuntimeTaskPerms = CollUtil.newHashSet(
               "/admin/flow/flowOnlineOperation/download",
               "/admin/flow/flowOnlineOperation/submitUserTask",
               "/admin/flow/flowOnlineOperation/upload",
               "/admin/flow/flowOnlineOperation/viewUserTask",
               "/admin/flow/flowOperation/listFlowTaskComment",
               "/admin/flow/flowOperation/listRuntimeTask",
               "/admin/flow/flowOperation/viewHighlightFlowData",
               "/admin/flow/flowOperation/viewProcessBpmn",
               "/admin/flow/flowOperation/viewRuntimeTaskInfo",
               "/admin/online/onlineForm/render"
       );
       flowHistoricTaskPerms = CollUtil.newHashSet(
               "/admin/flow/flowOnlineOperation/download",
               "/admin/flow/flowOnlineOperation/viewHistoricProcessInstance",
               "/admin/flow/flowOperation/listFlowTaskComment",
               "/admin/flow/flowOperation/listHistoricProcessInstance",
               "/admin/flow/flowOperation/viewHighlightFlowData",
               "/admin/flow/flowOperation/viewInitialHistoricTaskInfo",
               "/admin/flow/flowOperation/viewProcessBpmn",
               "/admin/online/onlineForm/render"
       );
       flowFinishTaskPerms = CollUtil.newHashSet(
               "/admin/flow/flowOnlineOperation/viewHistoricProcessInstance",
               "/admin/flow/flowOperation/listFlowTaskComment",
               "/admin/flow/flowOperation/listHistoricTask",
               "/admin/flow/flowOperation/submitConsign",
               "/admin/flow/flowOperation/viewHighlightFlowData",
               "/admin/flow/flowOperation/viewHistoricTaskInfo",
               "/admin/flow/flowOperation/viewProcessBpmn",
               "/admin/online/onlineForm/render",
               "/admin/online/onlineForm/view"
       );
       flowWorkOrderOperationPerms = CollUtil.newHashSet(
               "admin/online/flowOnlineOperation/startAndTakeUserTask/",
               "admin/online/flowOnlineOperation/startAndSaveDraft/",
               "admin/online/flowOnlineOperation/listWorkOrder/",
               "admin/online/flowOnlineOperation/printWorkOrder/"
       );
       flowWorkOrderOperationPerms = CollUtil.newHashSet(
               "/admin/online/onlineForm/view",
               "/admin/online/onlineForm/render",
               "/admin/flow/flowOperation/viewInitialHistoricTaskInfo",
               "/admin/flow/flowOperation/startOnly",
               "/admin/flow/flowOperation/viewInitialTaskInfo",
               "/admin/flow/flowOperation/viewRuntimeTaskInfo",
               "/admin/flow/flowOperation/viewProcessBpmn",
               "/admin/flow/flowOperation/viewHighlightFlowData",
               "/admin/flow/flowOperation/listFlowTaskComment",
               "/admin/flow/flowOperation/cancelWorkOrder",
               "/admin/flow/flowOnlineOperation/viewUserTask",
               "/admin/flow/flowOnlineOperation/viewHistoricProcessInstance",
               "/admin/flow/flowOnlineOperation/submitUserTask",
               "/admin/flow/flowOnlineOperation/upload",
               "/admin/flow/flowOnlineOperation/download",
               "/admin/flow/flowOperation/submitConsign"
       );
       flowWhitelistPerms = CollUtil.newHashSet(
               "/admin/flow/flowCategory/listDict",
               "/admin/flow/flowEntry/listDict",
               "/admin/flow/flowEntry/viewDict",
               "/admin/flow/flowOnlineOperation/listFlowEntryForm",
               "/admin/flow/flowOnlineOperation/viewCopyBusinessData",
               "/admin/flow/flowOnlineOperation/viewDraftData",
               "/admin/flow/flowOperation/viewDraftData",
               "/admin/flow/flowOperation/countRuntimeTask",
               "/admin/flow/flowOperation/viewInitialTaskInfo",
               "/admin/flow/flowOperation/viewRuntimeTaskInfo",
               "/admin/flow/flowOperation/viewHistoricTaskInfo",
               "/admin/flow/flowOperation/viewInitialHistoricTaskInfo",
               "/admin/flow/flowOperation/viewTaskUserInfo','流程管理",
               "/admin/flow/flowOperation/submitConsign",
               "/admin/flow/flowOperation/listMultiSignAssignees",
               "/admin/flow/flowOperation/listFlowTaskComment",
               "/admin/flow/flowOperation/viewProcessBpmn",
               "/admin/flow/flowOperation/viewHighlightFlowData",
               "/admin/flow/flowOperation/cancelWorkOrder",
               "/admin/flow/flowOperation/remindRuntimeTask",
               "/admin/flow/flowOperation/listRejectCandidateUserTask",
               "/admin/flow/flowOperation/rejectToStartUserTask",
               "/admin/flow/flowOperation/rejectRuntimeTask",
               "/admin/flow/flowOperation/revokeHistoricTask",
               "/admin/flow/flowOperation/freeJumpTo",
               "/admin/flow/flowOperation/viewCopyBusinessData",
               "/admin/flow/flowMessage/getMessageCount",
               "/admin/flow/flowMessage/listRemindingTask",
               "/admin/flow/flowMessage/listCopyMessage"
       );
       
       reportDictPerms = CollUtil.newHashSet(
               "/admin/report/reportDict/add",
               "/admin/report/reportDict/update",
               "/admin/report/reportDict/delete",
               "/admin/report/reportDict/list",
               "/admin/report/reportDict/view");
       reportPagePerms = CollUtil.newHashSet(
               "/admin/report/reportPageGroup/add",
               "/admin/report/reportPageGroup/update",
               "/admin/report/reportPageGroup/delete",
               "/admin/report/reportPageGroup/list",
               "/admin/report/reportPageGroup/view",
               "/admin/report/reportPage/add",
               "/admin/report/reportPage/update",
               "/admin/report/reportPage/delete",
               "/admin/report/reportPage/list");
       reportPrintPerms = CollUtil.newHashSet(
               "/admin/report/reportPrintGroup/add",
               "/admin/report/reportPrintGroup/update",
               "/admin/report/reportPrintGroup/delete",
               "/admin/report/reportPrintGroup/list",
               "/admin/report/reportPrintGroup/view",
               "/admin/report/reportPrint/add",
               "/admin/report/reportPrint/update",
               "/admin/report/reportPrint/delete",
               "/admin/report/reportPrint/list",
               "/admin/report/reportPrint/view");
       reportDblinkPerms = CollUtil.newHashSet(
               "/admin/report/reportDblink/add",
               "/admin/report/reportDblink/update",
               "/admin/report/reportDblink/delete",
               "/admin/report/reportDblink/list",
               "/admin/report/reportDblink/view",
               "/admin/report/reportDblink/listAllTables",
               "/admin/report/reportDblink/listTableColumn");
       reportDatasetPerms = CollUtil.newHashSet(
               "/admin/report/reportDatasetGroup/add",
               "/admin/report/reportDatasetGroup/update",
               "/admin/report/reportDatasetGroup/delete",
               "/admin/report/reportDatasetGroup/list",
               "/admin/report/reportDatasetGroup/view",
               "/admin/report/reportDataset/add",
               "/admin/report/reportDataset/update",
               "/admin/report/reportDataset/delete",
               "/admin/report/reportDataset/list",
               "/admin/report/reportDataset/view",
               "/admin/report/reportDataset/sync",
               "/admin/report/reportDataset/previewDataset",
               "/admin/report/reportDataset/listDataWithColumn",
               "/admin/report/reportDatasetColumn/update",
               "/admin/report/reportDatasetColumn/view",
               "/admin/report/reportDatasetRelation/add",
               "/admin/report/reportDatasetRelation/update",
               "/admin/report/reportDatasetRelation/delete",
               "/admin/report/reportDatasetRelation/list",
               "/admin/report/reportDatasetRelation/view",
               "/admin/report/reportOperation/previewData");
       reportWhitelistPerms = CollUtil.newHashSet(
               "/admin/report/reportDblink/testConnection",
               "/admin/report/reportDblink/listDict",
               "/admin/report/reportDict/listAllGlobalDict",
               "/admin/report/reportDict/listDict",
               "/admin/report/reportDict/listDictData",
               "/admin/report/reportPrint/preview",
               "/admin/report/reportPrint/listAll",
               "/admin/report/reportDataset/listByIds",
               "/admin/report/reportDataset/listDictByIds",
               "/admin/report/reportDataset/listDict",
               "/admin/report/reportPageGroup/listAll",
               "/admin/report/reportDatasetGroup/listAll",
               "/admin/report/reportPage/view");
       reportOperationPerms = CollUtil.newHashSet(
               "/admin/report/reportOperation/listData/");
   }

   // 验证Token并构造TokenData对象数据的访问接口。
   // GET请求,同时getTokenData的路径后缀,以及参数形式和参数名,必须与本示例保持一致。
   // 因为橙单的代码中会使用该值。
   @GetMapping("/getTokenData")
   public JSONObject getTokenData(@RequestParam String token) {
       LoginUser loginUser = super.getLoginUser();
       if (loginUser == null) {
           return makeResultData(false, "当前会话已过期或不存在!", null);
       }
       SysUser sysUser = loginUser.getUser();
       JSONObject tokenData = this.userConverter(sysUser);
       tokenData.put("sessionId", loginUser.getToken());
       tokenData.put("isAdmin", sysUser.isAdmin());
       tokenData.put("token", token);
       // makeResultData返回的对象,是橙单指定的格式,必须保持一致。
       return this.makeResultData(true, null, tokenData);
   }

   // 获取用户操作权限和数据权限数据的接口。
   // GET请求,同时getPermData的路径后缀,以及参数形式和参数名,必须与本示例保持一致。
   // 因为橙单的代码中会使用该值。
   @GetMapping("/getPermData")
   public JSONObject getPermData(@RequestParam String token) {
       LoginUser loginUser = super.getLoginUser();
       // 若依中获取当前登录用户菜单权限的查询操作。
       List<SysMenu> menuList = menuService.selectMenuList(loginUser.getUserId());
       menuList = menuList.stream().filter(m -> StrUtil.isNotBlank(m.getQuery())).collect(Collectors.toList());
       boolean hasOnlineDblinkPerms = false;
       boolean hasOnlinePagePerms = false;
       boolean hasOnlineDictPerms = false;
       boolean hasFlowCategoryPerms = false;
       boolean hasFlowEntryPerms = false;
       boolean hasFlowInstancePerms = false;
       boolean hasFlowRuntimeTaskPerms = false;
       boolean hasFlowHistoricTaskPerms = false;
       boolean hasFlowFinishTaskPerms = false;
       boolean hasFlowWorkOrderOperationPerms = false;
       boolean hasReportDblinkPerms = false;
       boolean hasReportDictPerms = false;
       boolean hasReportDatasetPerms = false;
       boolean hasReportPagePerms = false;
       boolean hasReportPrintPerms = false;
       // 操作权限列表,收集后会返回给橙单。
       List<String> urlPerms = new LinkedList<>();
       // 菜单的"路由参数"中,只要包含了指定的字符串,就被视为拥有"菜单管理"的权限了。
       // 下面的字符串部分,如"/#/thirdParty/thirdOnlineDblink",必须和菜单中的配置保持一致。具体配置见下面的截图。
       for (SysMenu m : menuList) {
           if (StrUtil.contains(m.getQuery(), "/#/thirdParty/thirdOnlineDblink")) {
               hasOnlineDblinkPerms = true;
           }
           if (StrUtil.contains(m.getQuery(), "/#/thirdParty/thirdOnlinePage")) {
               hasOnlinePagePerms = true;
           }
           if (StrUtil.contains(m.getQuery(), "/#/thirdParty/thirdFormOnlineDict")) {
               hasOnlineDictPerms = true;
           }
           if (StrUtil.contains(m.getQuery(), "/#/thirdParty/thirdFormFlowCategory")) {
               hasFlowCategoryPerms = true;
           }
           if (StrUtil.contains(m.getQuery(), "/#/thirdParty/thirdFormFlowEntry")) {
               hasFlowEntryPerms = true;
           }
           if (StrUtil.contains(m.getQuery(), "/#/thirdParty/thirdFormAllInstance")) {
               hasFlowInstancePerms = true;
           }
           if (StrUtil.contains(m.getQuery(), "/#/thirdParty/thirdFormMyTask")) {
               hasFlowRuntimeTaskPerms = true;
           }
           if (StrUtil.contains(m.getQuery(), "/#/thirdParty/thirdFormMyHistoryTask")) {
               hasFlowHistoricTaskPerms = true;
           }
           if (StrUtil.contains(m.getQuery(), "/#/thirdParty/thirdFormMyApprovedTask")) {
               hasFlowFinishTaskPerms = true;
           }
           if (StrUtil.contains(m.getQuery(), "/#/thirdParty/thirdReportDblink")) {
               hasReportDblinkPerms = true;
           }
           if (StrUtil.contains(m.getQuery(), "/#/thirdParty/thirdReportDict")) {
               hasReportDictPerms = true;
           }
           if (StrUtil.contains(m.getQuery(), "/#/thirdParty/thirdPrint")) {
               hasReportPrintPerms = true;
           }
           if (StrUtil.contains(m.getQuery(), "/#/thirdParty/thirdReportDataset")) {
               hasReportDatasetPerms = true;
           }
           if (StrUtil.contains(m.getQuery(), "/#/thirdParty/thirdReportPage")) {
               hasReportPagePerms = true;
           }
           JSONObject data = JSON.parseObject(m.getQuery());
           // 菜单的"路由参数"中,如果包含datasourceVariableName键,则被视为在线表单的业务页面菜单。
           String datasourceVariableName = data.getString("datasourceVariableName");
           // 根据数据源的标识值,构建该业务页面的接口权限数据。
           if (StrUtil.isNotBlank(datasourceVariableName)) {
               onlineOperationPerms.forEach(perm -> urlPerms.add(perm + datasourceVariableName));
           }
           String processDefinitionKey = data.getString("processDefinitionKey");
           if (StrUtil.isNotBlank(processDefinitionKey)) {
               hasFlowWorkOrderOperationPerms = true;
               flowWorkOrderOperationPerms.forEach(perm -> urlPerms.add(perm + processDefinitionKey));
           }
           // 菜单的"路由参数"中,如果包含pageCode键,则被视为报表的业务页面菜单。
           String pageCode = data.getString("pageCode");
           // 根据数据源的标识值,构建该业务页面的接口权限数据。
           if (StrUtil.isNotBlank(pageCode)) {
               reportOperationPerms.forEach(perm -> urlPerms.add(perm + pageCode));
           }
       }
       // 上面循环扫描的结果,可以判断当前用户被分配了哪些橙单集成页面的菜单。
       if (hasOnlineDblinkPerms) {
           urlPerms.addAll(onlineDblinkPerms);
       }
       if (hasOnlinePagePerms) {
           urlPerms.addAll(onlinePagePerms);
       }
       if (hasOnlineDictPerms) {
           urlPerms.addAll(onlineDictPerms);
       }
       if (hasFlowCategoryPerms) {
           urlPerms.addAll(flowCategoryPerms);
       }
       if (hasFlowEntryPerms) {
           urlPerms.addAll(flowEntryPerms);
       }
       if (hasFlowInstancePerms) {
           urlPerms.addAll(flowInstancePerms);
       }
       if (hasFlowRuntimeTaskPerms) {
           urlPerms.addAll(flowRuntimeTaskPerms);
       }
       if (hasFlowHistoricTaskPerms) {
           urlPerms.addAll(flowHistoricTaskPerms);
       }
       if (hasFlowFinishTaskPerms) {
           urlPerms.addAll(flowFinishTaskPerms);
       }
       if (hasFlowWorkOrderOperationPerms) {
           urlPerms.addAll(flowWorkOrderCommonPerms);
       }
       if (hasReportDblinkPerms) {
           urlPerms.addAll(reportDblinkPerms);
       }
       if (hasReportDatasetPerms) {
           urlPerms.addAll(reportDatasetPerms);
       }
       if (hasReportDictPerms) {
           urlPerms.addAll(reportDictPerms);
       }
       if (hasReportPagePerms) {
           urlPerms.addAll(reportPagePerms);
       }
       if (hasReportPrintPerms) {
           urlPerms.addAll(reportPrintPerms);
       }
       urlPerms.addAll(onlineWhitelistPerms);
       urlPerms.addAll(flowWhitelistPerms);
       urlPerms.addAll(reportWhitelistPerms);
       // OrangePermData是后台权限数据的保存格式,即便接入其他第三方框架时,也要保持一致,如确有修改,
       // 需要同时修改橙单中的代码。具体位置为,单体工程的 AuthenticationInterceptor,微服务的AuthenticationPreFilter。
       OrangePermData permData = new OrangePermData();
       permData.setUrlPerms(urlPerms);
       // 下面的查询是获取当前用户的角色列表,同时获取角色中绑定的数据权限。
       // 这里仅仅是若依获取数据权限的方式,其他第三方框架可自行修改。
       List<SysRole> roleList = roleService.selectRolesByUserId(loginUser.getUserId());
       List<SysRole> dataPermRoles = roleList.stream()
               .filter(r -> StrUtil.isNotBlank(r.getDataScope())).collect(Collectors.toList());
       // 如果当前用户没有数据权限配置,就直接返回操作权限数据列表了。
       if (CollUtil.isEmpty(dataPermRoles)) {
           return this.makeResultData(true, null, permData);
       }
       List<OrangeDataPermData> dataPermDataList = new LinkedList<>();
       for (SysRole role : dataPermRoles) {
           OrangeDataPermData dataPermData = new OrangeDataPermData();
           // mapDataPermType将若依中的数据权限过滤策略值,映射为橙单中的策略值。
           dataPermData.setRuleType(this.mapDataPermType(role.getDataScope()));
           // "4" 在若依中表示为本部门及子部门。
           // "2" 在若依中表示自定义部门。
           // 对于 "为本部门及子部门" 和 "自定义部门" 两个策略,需要在这里计算出具体的部门Id列表,
           // 并返回给橙单,橙单中会直接使用。下面是两种不同过滤策略,在若依中获取部门Id列表的逻辑。
           // 不同的第三方框架,或者内部系统,仅需参考此处的逻辑和数据格式即可。
           if (StrUtil.equals(role.getDataScope(), "2")) {
               List<SysRoleDept> roleDepts = roleDeptMapper.selectRoleDeptListByRoleId(role.getRoleId());
               Set<Long> roleDeptIds = roleDepts.stream().map(SysRoleDept::getDeptId).collect(Collectors.toSet());
               dataPermData.setDeptIds(CollUtil.join(roleDeptIds, ","));
           } else if (StrUtil.equals(role.getDataScope(), "4")) {
               List<SysDept> childrenDeptList = sysDeptMapper.selectChildrenDeptById(loginUser.getDeptId());
               Set<Long> deptIds = childrenDeptList.stream().map(SysDept::getDeptId).collect(Collectors.toSet());
               deptIds.add(loginUser.getDeptId());
               dataPermData.setDeptIds(CollUtil.join(deptIds, ","));
           }
           dataPermDataList.add(dataPermData);
       }
       permData.setDataPerms(dataPermDataList);
       // makeResultData返回的对象,是橙单指定的格式,必须保持一致。
       return this.makeResultData(true, null, permData);
   }

   // 在线表单高级组件中,查询用户和部门数据列表的接口地址,今后可以扩展组件时,添加更多的type即可。
   @PostMapping("/listBizWidgetData")
   public JSONObject listBizWidgetData(@RequestParam String token, @RequestBody JSONObject args) {
       // 目前仅仅支持upms_user和upms_dept两种类型。这两个值,可以自行定义,
       // 但是需要和橙单中common-ext.apps.types的配置值保持一直即可。推荐使用默认生成的值。
       String type = args.getString("type");
       // 分页对象。
       JSONObject pageParam = args.getJSONObject("pageParam");
       if (pageParam != null) {
           PageMethod.startPage(pageParam.getInteger("pageNum"), pageParam.getInteger("pageSize"));
       }
       // args参数中,还可能存在filter和orderParam两个键,分别代表过滤和排序对象。
       // 当前的示例中没有个给出,可根据实际需求添加。
       // 根据不同的类型,获取不同类型的业务数据列表。给橙单业务组件返回的数据格式,参考userConverter
       // 和deptConverter方法,今后自己增加新的业务组件,可以自行定义数据格式,只要两边保持一致即可。
       if (StrUtil.equals(type, "upms_user")) {
           List<SysUser> userList = userService.selectUserList(new SysUser());
           JSONObject pageData = this.makePageData(userList, this::userConverter);
           return this.makeResultData(true, null, pageData);
       } else if (StrUtil.equals(type, "upms_dept")) {
           List<SysDept> deptList = deptService.selectDeptList(new SysDept());
           JSONObject pageData = this.makePageData(deptList, this::deptConverter);
           return this.makeResultData(true, null, pageData);
       } else if (StrUtil.equals(type, "upms_role")) {
           List<SysRole> roleList = roleService.selectRoleList(new SysRole());
           JSONObject pageData = this.makePageData(roleList, this::roleConverter);
           return this.makeResultData(true, null, pageData);
       } else if (StrUtil.equals(type, "upms_post")) {
           List<SysPost> postList = postService.selectPostList(new SysPost());
           JSONObject pageData = this.makePageData(postList, this::postConverter);
           return this.makeResultData(true, null, pageData);
       } else if (StrUtil.equals(type, "upms_dept_post")) {
           // 这里因为若依没有支持部门和岗位之间的多对多关系,因为我们使用了和upms_post一样的逻辑
           // 作为样例代码,如需要可自行修改并扩展若依,以支持部门和岗位的多对多关系。
           List<SysPost> postList = postService.selectPostList(new SysPost());
           JSONObject pageData = this.makePageData(postList, this::postConverter);
           return this.makeResultData(true, null, pageData);
       }
       // 注意这里一定要返回具体的错误信息。
       return this.makeResultData(false, "尚不支持的组件类型!!!", null);
   }

   // 在线表单高级组件中,查询用户和部门数据详情的接口地址,今后可以扩展组件时,添加更多的type即可。
   // 为了提高效率,这里可以通过指定多个主键ID,一次性返回多个主键的详情数据。
   @PostMapping("/viewBizWidgetData")
   public JSONObject viewBizWidgetData(@RequestParam String token, @RequestBody JSONObject args) {
       // 这个方法的具体逻辑,和上面的listBizWidgetData基本一致。
       String type = args.getString("type");
       String ids = args.getString("fieldValues");
       if (StrUtil.equals(type, "upms_user")) {
           List<JSONObject> userList = new LinkedList<>();
           List<String> idList = StrUtil.split(ids, ",");
           for (String id : idList) {
               // 这里需要hardcode判断一下,获取用户是基于userId还是loginName。
               String fieldName = args.getString("fieldName");
               SysUser user;
               if (StrUtil.equals(fieldName, "loginName")) {
                   user = userService.selectUserByUserName(id);
               } else {
                   user = userService.selectUserById(Long.valueOf(id));
               }
               if (user != null) {
                   userList.add(this.userConverter(user));
               }
           }
           return this.makeResultData(true, null, userList);
       } else if (StrUtil.equals(type, "upms_dept")) {
           List<JSONObject> deptList = new LinkedList<>();
           List<String> idList = StrUtil.split(ids, ",");
           for (String id : idList) {
               SysDept dept = deptService.selectDeptById(Long.valueOf(id));
               if (dept != null) {
                   deptList.add(this.deptConverter(dept));
               }
           }
           return this.makeResultData(true, null, deptList);
       } else if (StrUtil.equals(type, "upms_role")) {
           List<JSONObject> roleList = new LinkedList<>();
           List<String> idList = StrUtil.split(ids, ",");
           for (String id : idList) {
               SysRole role = roleService.selectRoleById(Long.valueOf(id));
               if (role != null) {
                   roleList.add(this.roleConverter(role));
               }
           }
           return this.makeResultData(true, null, roleList);
       } else if (StrUtil.equals(type, "upms_post")) {
           List<JSONObject> postList = new LinkedList<>();
           List<String> idList = StrUtil.split(ids, ",");
           for (String id : idList) {
               SysPost post = postService.selectPostById(Long.valueOf(id));
               if (post != null) {
                   postList.add(this.postConverter(post));
               }
           }
           return this.makeResultData(true, null, postList);
       } else if (StrUtil.equals(type, "upms_dept_post")) {
           // 这里因为若依没有支持部门和岗位之间的多对多关系,因为我们使用了和upms_post一样的逻辑
           // 作为样例代码,如需要可自行修改并扩展若依,以支持部门和岗位的多对多关系。
           List<JSONObject> postList = new LinkedList<>();
           List<String> idList = StrUtil.split(ids, ",");
           for (String id : idList) {
               SysPost post = postService.selectPostById(Long.valueOf(id));
               if (post != null) {
                   postList.add(this.postConverter(post));
               }
           }
           return this.makeResultData(true, null, postList);
       }
       // 注意这里一定要返回具体的错误信息。
       return this.makeResultData(false, "尚不支持的组件类型!!!", null);
   }
   private JSONObject userConverter(SysUser user) {
       JSONObject result = new JSONObject();
       result.put("userId", user.getUserId());
       result.put("loginName", user.getUserName());
       result.put("showName", user.getNickName());
       result.put("deptId", user.getDeptId());
       if (user.getDept() != null) {
           Map<String, Object> deptIdDictMap = new HashMap<>(2);
           deptIdDictMap.put("id", user.getDept().getDeptId());
           deptIdDictMap.put("name", user.getDept().getDeptName());
           result.put("deptIdDictMap", deptIdDictMap);
       }
       return result;
   }
   private JSONObject deptConverter(SysDept dept) {
       JSONObject result = new JSONObject();
       result.put("deptId", dept.getDeptId());
       result.put("deptName", dept.getDeptName());
       return result;
   }
   private JSONObject roleConverter(SysRole role) {
       JSONObject result = new JSONObject();
       result.put("roleId", role.getRoleId());
       result.put("roleName", role.getRoleName());
       return result;
   }
   private JSONObject postConverter(SysPost post) {
       JSONObject result = new JSONObject();
       result.put("postId", post.getPostId());
       result.put("postName", post.getPostName());
       // 若依没有支持岗位级别,所以我们暂时使用了若依中岗位的显示顺序来代替一下。
       // 如果要完全兼容橙单,需要扩展若依的岗位支持岗位级别。
       result.put("postLevel", post.getPostSort());
       // 若依中没有领导岗位的标记,所以我们缺省使用位置为0的是领导岗位了,也可自行
       // 修改若依代码,以便准确的支持该功能。
       result.put("leaderPost", StrUtil.equals("0", post.getPostSort()));
       return result;
   }
   private <T, R> JSONObject makePageData(List<T> dataList, Function<T, R> converter) {
       long totalCount = 0L;
       if (dataList instanceof Page) {
           totalCount = ((Page<SysUser>) dataList).getTotal();
       }
       List<R> resultList = new LinkedList<>();
       for (T data : dataList) {
           resultList.add(converter.apply(data));
       }
       JSONObject pageData = new JSONObject();
       pageData.put("dataList", resultList);
       pageData.put("totalCount", totalCount);
       return pageData;
   }
   private JSONObject makeResultData(boolean success, String errorMsg, Object data) {
       JSONObject result = new JSONObject();
       result.put("success", success);
       result.put("errorMessage", errorMsg);
       result.put("data", data);
       return result;
   }
   private Integer mapDataPermType(String localDataPerm) {
       // 该变量为橙单中的数据权限过滤值,具体值可参考橙单中的DataPermRuleType常量类。
       int orangeDataPermRuleType = 0;
       // 数据范围(1:所有数据权限;2:自定义数据权限;3:本部门数据权限;4:本部门及以下数据权限;5:仅本人数据权限)
       switch (localDataPerm) {
           case "1":
               orangeDataPermRuleType = 0;
               break;
           case "2":
               orangeDataPermRuleType = 5;
               break;
           case "3":
               orangeDataPermRuleType = 2;
               break;
           case "4":
               orangeDataPermRuleType = 3;
               break;
           case "5":
               orangeDataPermRuleType = 1;
               break;
           default:
               break;
       }
       return orangeDataPermRuleType;
   }
   public static class OrangePermData {

       private List<String> urlPerms;

       private List<OrangeDataPermData> dataPerms;
       public List<String> getUrlPerms() {
           return urlPerms;
       }
       public void setUrlPerms(List<String> urlPerms) {
           this.urlPerms = urlPerms;
       }
       public List<OrangeDataPermData> getDataPerms() {
           return dataPerms;
       }
       public void setDataPerms(List<OrangeDataPermData> dataPerms) {
           this.dataPerms = dataPerms;
       }
   }
   public static class OrangeDataPermData {

       // 数据权限的规则类型。需要按照橙单的约定返回。
       // 0. 查看全部
       // 1. 仅看当前用户
       // 2. 仅看本部门
       // 3. 本部门及子部门
       // 4. 多部门及子部门
       // 5. 自定义部门
       private Integer ruleType;

       // 部门Id集合,多个部门Id之间逗号分隔。
       // 注意:仅当ruleType为3、4、5时需要包含该字段值。
       private String deptIds;
       public String getDeptIds() {
           return deptIds;
       }
       public void setDeptIds(String deptIds) {
           this.deptIds = deptIds;
       }
       public Integer getRuleType() {
           return ruleType;
       }
       public void setRuleType(Integer ruleType) {
           this.ruleType = ruleType;
       }
   }
}
```

第三方后端授权

上一步中给出的插件接口路径为 /orangePlugin/**,他们必须是白名单接口,同时必须能够得到 Token 数据,因此不能是「匿名用户」。这里我们以若依使用的 Spring Security 为例,在下图中,我们修改了若依工程中的 SecurityConfig.java 配置文件,至于其他第三方框架或内部系统,可根据实际情况自行修改。

橙单后台配置

橙单默认生成的工程中,会包含下面截图中的配置项,只需根据实际情况按需修改即可。

  • 单体工程配置。
  • 微服务工程。下面两个截图,分别是 gateway 和 upms 服务的配置。

  • 数据权限配置。因为若依等第三方框架并不支持精确到菜单的数据权限机制,因此需要禁用下图中的配置项,以禁用菜单 ID 和权限列表之间的关联验证。这里仅以单体为例,微服务工程的配置也大同小异。

数据库应用隔离

部分在线表单内置表,添加了 app_code 字段,用以逻辑隔离不同应用的表单配置数据。因此每个第三方接入应用,仅能查看本应用配置的在线表单数据。如果 app_code 值为 null,仅当接入应用没有 appCode 时可见。

后台服务启动

  • 默认情况下以 8082 端口启动橙单后台服务,具体启动方式不变。
  • 默认情况下以 8083 端口启动第三方后台应用,如本例中的「若依」。如果修改第三方应用的启动地址和端口,请同步修改上面小节中介绍的后台配置参数。下图仅以单体工程的配置项为例。

前端第三方插件

  • 在若依前端代码中实现下图所示的插件代码。
  • 若依集成橙单在线表单的前端插件完整代码。需要额外说明的是,在以下示例中,我们使用了 LayerUI 弹窗,开发者可根据原系统的前端技术栈,自行调整。
import { getToken } from '@/utils/auth'
import $ from 'jquery'
import './index.css'
window.jQuery = $
const layer = require('layui-layer')
export default {
 data() {
   return {
     dialogMap: new Map()
   }
 },
 methods: {
   // 发送消息
   postMessage(sender, type, data) {
     if (sender != null && type != null) {
       sender.postMessage({
         type,
         data
       }, '*')
     }
   },
   // 打开弹窗
   handlerOpenDialog(data, event) {
     const this_ = this
     let area = [data.width || '40vw', data.height || '70vh']
     if (data.dlgFullScreen) {
       area = ['100vw', '100vh']
     }
     const layerOptions = {
       title: data.title,
       type: 2,
       skin: data.dlgFullScreen ? 'fullscreen-dialog' : 'layer-dialog',
       resize: false,
       area: area,
       offset: data.dlgFullScreen ? undefined : (data.top || '50px'),
       zIndex: data.zIndex || 1000,
       index: 0,
       content: data.url,
       success: function(res, index) {
         this_.dialogMap.set(index, {
           source: event.source
         })
         var iframe = $(res).find('iframe')
         if (data.dlgFullScreen) iframe[0].style.height = '100vh'
         this_.postMessage(iframe[0].contentWindow, 'dialogIndex', index)
       }
     }
     layer.open(layerOptions)
   },
   // 关闭弹窗
   handlerCloseDialog(data) {
     if (data != null) {
       layer.close(data.index)
       const dialog = this.dialogMap.get(data.index)
       if (dialog && dialog.source) {
         this.postMessage(dialog.source, 'refreshData', data)
       }
       this.dialogMap.delete(data)
     }
   },
   // 刷新token
   handlerRefreshToken(data, event) {
     this.postMessage(event.source, 'setToken', {
       token: getToken()
     })
   },
   // 通知消息,例如成功、错误通知等
   handlerUIMessage(data, event) {
     this.$message[data.type](data.text)
   },
   handlerMessage(type, data, event) {
     switch (type) {
       // 打开弹窗
       case 'openDialog':
         this.handlerOpenDialog(data, event)
         break
       // 关闭弹窗
       case 'closeDialog':
         this.handlerCloseDialog(data, event)
         break
       // 刷新token
       case 'refreshToken':
         this.handlerRefreshToken(data, event)
         break
       // 通知消息,例如成功、错误通知等
       case 'message':
         this.handlerUIMessage(data, event)
     }
   },
   eventListener(e) {
     if (e.data == null) return
     this.handlerMessage(e.data.type, e.data.data, e)
   }
 },
 created() {
   window.addEventListener('message', this.eventListener, false)
 },
 destoryed() {
   window.removeEventListener('message', this.eventListener)
 }
}
  • 插件与若依代码的集成。

  • 若依并没有支持「iframe」菜单实现,所以需要实现一个基于「iframe」的菜单组件,如下所示。
<template>
 <div>
   <iframe :src="getLinkUrl"
     style="width: 100%; height: calc(100vh - 87px); border: none;"
   />
 </div>
</template>
<script>
import { getToken } from '@/utils/auth'
export default {
 // 此处的名称,会在后面配置菜单时用到。
 name: 'LinkPage',
 computed: {
   getLinkUrl() {
     const tempUrl = this.$route.query ? this.$route.query.url : undefined
     if (tempUrl != null) {
       if (tempUrl.indexOf('?') === -1) {
         // 这里appId的值,比如和后台的appCode保持一致。
         return tempUrl + '?appId=ruoyi&token=' + getToken()
       } else {
         return tempUrl + '&appId=ruoyi&token=' + getToken()
       }
     }
     return tempUrl
   }
 }
}
</script>

请求 URL 中的 appId 参数值, 必须与后端的 appCode 配置值保持一致。

前端服务启动

  • 启动第三方前端工程,如本例中若依的前端服务。
  • 启动橙单前端工程。需要特别说明的是,该前端服务的启动地址和端口,必须与后面配置菜单中指定的地址和端口相匹配,见下图。

页面配置操作

  • 以管理员身份登录系统。用户账号鉴权操作,全部由接入系统负责,既本例中的若依框架完成。

  • 为橙单的在线表单配置管理菜单,下图分别为在线表单的「表单管理」和「字典管理」的配置截图。

  • 上面两图中「路由参数」的填写格式,需要和自定义的插件代码保持一致。这里仅给出基于「若依」集成的示例,至于其他框架或内部系统,可参考本示例的逻辑,自行修改。

表单管理的「路由参数」{"url": "http://localhost:8085/#/thirdParty/thirdOnlinePage"}
管理的「路由参数」{"url": "http://localhost:8085/#/thirdParty/thirdFormOnlineDict"}

  1. localhost:8085 指向了「橙单前端服务」的主机地址。
  2. /#/thirdParty/thirdOnlinePage 和 /#/thirdParty/thirdFormOnlineDict 是前端代码中的路由地址,这里的配置需要和前端代码保持一致。
  3. 前面小节已经介绍过了,我们为若依编写的后台插件,也会使用到该路由 /#/thirdParty/thirdOnlinePage 和 /#/thirdParty/thirdFormOnlineDict。
  • 重新以管理员身份登录,上一步配置的「表单管理」和「字典管理」菜单将会出现在菜单栏的左侧。

  • 通过在线表单「字典管理」菜单配置所需的字典。
  • 通过在线表单「表单管理」菜单配置「在线表单业务页面」,具体配置操作与在橙单中完全一致,具体可参考本章节的其余内容。

  • 为上一步配置的「在线表单业务页面」绑定菜单,具体操作也与在橙单中完全一致,具体可参考本章节的其余内容。

上图菜单中「路由参数」的数据 {"url": "http://localhost:8085/#/thirdParty/thirdOnlineForm?formId=1591062003777015808", "datasourceVariableName":"dsProductTwo"}

  1. url 中的 localhost:8085 指向了「橙单前端服务」的主机地址。
  2. /#/thirdParty/thirdOnlineForm 是前端代码中的路由地址,这里的配置需要和前端插件代码中保持一致。
  3. formId=1591062003777015808 是在线表单的主键值,需要自行查询数据库 zz_online_form 表 form_id 字段。
  4. datasourceVariableName 是在线表单的数据源变量,对于业务页面这个是必须的。具体位置见下图。
  • 重新以管理员身份登录,上一步配置的「在线表单业务页面」即可出现在左侧的菜单栏中。

  • 权限配置的具体方式,可与原系统如「若依」保持一致。
  • 数据权限配置的具体方式,可与原系统如「若依」保持一致。在本例中,若依支持的数据权限过滤策略,在橙单中全部都有与之一一对应的策略,只是策略值不同。因此需要在后台集成插件中,实现该策略值的映射转换。

结语

赠人玫瑰,手有余香,感谢您的支持和关注,选择橙单,效率乘三,收入翻番。