前言
橙单目前已支持,以独立服务的形式运行在线表单、工作流和报表打印等功能模块,同时可接入多个第三方应用,并以 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/"
);
flowWorkOrderCommonPerms = 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);
urlPerms.addAll(flowWorkOrderOperationPerms);
}
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"}
- localhost:8085 指向了「橙单前端服务」的主机地址。
- /#/thirdParty/thirdOnlinePage 和 /#/thirdParty/thirdFormOnlineDict 是前端代码中的路由地址,这里的配置需要和前端代码保持一致。
- 前面小节已经介绍过了,我们为若依编写的后台插件,也会使用到该路由 /#/thirdParty/thirdOnlinePage 和 /#/thirdParty/thirdFormOnlineDict。
- 重新以管理员身份登录,上一步配置的「表单管理」和「字典管理」菜单将会出现在菜单栏的左侧。
- 通过在线表单「字典管理」菜单配置所需的字典。
- 通过在线表单「表单管理」菜单配置「在线表单业务页面」,具体配置操作与在橙单中完全一致,具体可参考本章节的其余内容。
- 为上一步配置的「在线表单业务页面」绑定菜单,具体操作也与在橙单中完全一致,具体可参考本章节的其余内容。
上图菜单中「路由参数」的数据 {"url": "http://localhost:8085/#/thirdParty/thirdOnlineForm?formId=1591062003777015808", "datasourceVariableName":"dsProductTwo"}
- url 中的 localhost:8085 指向了「橙单前端服务」的主机地址。
- /#/thirdParty/thirdOnlineForm 是前端代码中的路由地址,这里的配置需要和前端插件代码中保持一致。
- formId=1591062003777015808 是在线表单的主键值,需要自行查询数据库 zz_online_form 表 form_id 字段。
- datasourceVariableName 是在线表单的数据源变量,对于业务页面这个是必须的。具体位置见下图。
- 重新以管理员身份登录,上一步配置的「在线表单业务页面」即可出现在左侧的菜单栏中。
- 权限配置的具体方式,可与原系统如「若依」保持一致。
- 数据权限配置的具体方式,可与原系统如「若依」保持一致。在本例中,若依支持的数据权限过滤策略,在橙单中全部都有与之一一对应的策略,只是策略值不同。因此需要在后台集成插件中,实现该策略值的映射转换。
结语
赠人玫瑰,手有余香,感谢您的支持和关注,选择橙单,效率乘三,收入翻番。